/****************************************************************************
**
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of a Qt Solutions component.
**
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
** the names of its contributors may be used to endorse or promote
** products derived from this software without specific prior written
** permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
****************************************************************************/
#include "qtcanvas.h"
#include <QApplication>
#include <QBitmap>
#include <QDesktopWidget>
#include <QImage>
#include <QPainter>
#include <QTimer>
#include <QHash>
#include <QSet>
#include <QtAlgorithms>
#include <QEvent>
#include <QPaintEvent>
#include <QPainterPath>
#include <stdlib.h>
using namespace Qt;
class QtCanvasData {
public:
QtCanvasData()
{
}
QList<QtCanvasView *> viewList;
QSet<QtCanvasItem *> itemDict;
QSet<QtCanvasItem *> animDict;
};
class QtCanvasViewData {
public:
QtCanvasViewData() {}
QMatrix xform;
QMatrix ixform;
bool highQuality;
};
// clusterizer
class QtCanvasClusterizer {
public:
QtCanvasClusterizer(int maxclusters);
~QtCanvasClusterizer();
void add(int x, int y); // 1x1 rectangle (point)
void add(int x, int y, int w, int h);
void add(const QRect& rect);
void clear();
int clusters() const { return count; }
const QRect& operator[](int i) const;
private:
QRect* cluster;
int count;
const int maxcl;
};
static
void include(QRect& r, const QRect& rect)
{
if (rect.left() < r.left()) {
r.setLeft(rect.left());
}
if (rect.right()>r.right()) {
r.setRight(rect.right());
}
if (rect.top() < r.top()) {
r.setTop(rect.top());
}
if (rect.bottom()>r.bottom()) {
r.setBottom(rect.bottom());
}
}
/*
A QtCanvasClusterizer groups rectangles (QRects) into non-overlapping rectangles
by a merging heuristic.
*/
QtCanvasClusterizer::QtCanvasClusterizer(int maxclusters) :
cluster(new QRect[maxclusters]),
count(0),
maxcl(maxclusters)
{ }
QtCanvasClusterizer::~QtCanvasClusterizer()
{
delete [] cluster;
}
void QtCanvasClusterizer::clear()
{
count = 0;
}
void QtCanvasClusterizer::add(int x, int y)
{
add(QRect(x, y, 1, 1));
}
void QtCanvasClusterizer::add(int x, int y, int w, int h)
{
add(QRect(x, y, w, h));
}
void QtCanvasClusterizer::add(const QRect& rect)
{
QRect biggerrect(rect.x()-1, rect.y()-1, rect.width()+2, rect.height()+2);
//assert(rect.width()>0 && rect.height()>0);
int cursor;
for (cursor = 0; cursor < count; cursor++) {
if (cluster[cursor].contains(rect)) {
// Wholly contained already.
return;
}
}
int lowestcost = 9999999;
int cheapest = -1;
cursor = 0;
while(cursor < count) {
if (cluster[cursor].intersects(biggerrect)) {
QRect larger = cluster[cursor];
include(larger, rect);
int cost = larger.width()*larger.height() -
cluster[cursor].width()*cluster[cursor].height();
if (cost < lowestcost) {
bool bad = false;
for (int c = 0; c < count && !bad; c++) {
bad = cluster[c].intersects(larger) && c!= cursor;
}
if (!bad) {
cheapest = cursor;
lowestcost = cost;
}
}
}
cursor++;
}
if (cheapest>= 0) {
include(cluster[cheapest], rect);
return;
}
if (count < maxcl) {
cluster[count++] = rect;
return;
}
// Do cheapest of:
// add to closest cluster
// do cheapest cluster merge, add to new cluster
lowestcost = 9999999;
cheapest = -1;
cursor = 0;
while(cursor < count) {
QRect larger = cluster[cursor];
include(larger, rect);
int cost = larger.width()*larger.height()
- cluster[cursor].width()*cluster[cursor].height();
if (cost < lowestcost) {
bool bad = false;
for (int c = 0; c < count && !bad; c++) {
bad = cluster[c].intersects(larger) && c!= cursor;
}
if (!bad) {
cheapest = cursor;
lowestcost = cost;
}
}
cursor++;
}
// ###
// could make an heuristic guess as to whether we need to bother
// looking for a cheap merge.
int cheapestmerge1 = -1;
int cheapestmerge2 = -1;
int merge1 = 0;
while(merge1 < count) {
int merge2 = 0;
while(merge2 < count) {
if(merge1!= merge2) {
QRect larger = cluster[merge1];
include(larger, cluster[merge2]);
int cost = larger.width()*larger.height()
- cluster[merge1].width()*cluster[merge1].height()
- cluster[merge2].width()*cluster[merge2].height();
if (cost < lowestcost) {
bool bad = false;
for (int c = 0; c < count && !bad; c++) {
bad = cluster[c].intersects(larger) && c!= cursor;
}
if (!bad) {
cheapestmerge1 = merge1;
cheapestmerge2 = merge2;
lowestcost = cost;
}
}
}
merge2++;
}
merge1++;
}
if (cheapestmerge1>= 0) {
include(cluster[cheapestmerge1], cluster[cheapestmerge2]);
cluster[cheapestmerge2] = cluster[count--];
} else {
// if (!cheapest) debugRectangles(rect);
include(cluster[cheapest], rect);
}
// NB: clusters do not intersect (or intersection will
// overwrite). This is a result of the above algorithm,
// given the assumption that (x, y) are ordered topleft
// to bottomright.
// ###
//
// add explicit x/y ordering to that comment, move it to the top
// and rephrase it as pre-/post-conditions.
}
const QRect& QtCanvasClusterizer::operator[](int i) const
{
return cluster[i];
}
// end of clusterizer
class QtCanvasItemLess
{
public:
inline bool operator()(const QtCanvasItem *i1, const QtCanvasItem *i2) const
{
if (i1->z() == i2->z())
return i1 > i2;
return (i1->z() > i2->z());
}
};
class QtCanvasChunk {
public:
QtCanvasChunk() : changed(true) { }
// Other code assumes lists are not deleted. Assignment is also
// done on ChunkRecs. So don't add that sort of thing here.
void sort()
{
qSort(m_list.begin(), m_list.end(), QtCanvasItemLess());
}
const QtCanvasItemList &list() const
{
return m_list;
}
void add(QtCanvasItem* item)
{
m_list.prepend(item);
changed = true;
}
void remove(QtCanvasItem* item)
{
m_list.removeAll(item);
changed = true;
}
void change()
{
changed = true;
}
bool hasChanged() const
{
return changed;
}
bool takeChange()
{
bool y = changed;
changed = false;
return y;
}
private:
QtCanvasItemList m_list;
bool changed;
};
static int gcd(int a, int b)
{
int r;
while ((r = a%b)) {
a = b;
b = r;
}
return b;
}
static int scm(int a, int b)
{
int g = gcd(a, b);
return a/g*b;
}
/*
\class QtCanvas qtcanvas.h
\brief The QtCanvas class provides a 2D area that can contain QtCanvasItem objects.
The QtCanvas class manages its 2D graphic area and all the canvas
items the area contains. The canvas has no visual appearance of
its own. Instead, it is displayed on screen using a QtCanvasView.
Multiple QtCanvasView widgets may be associated with a canvas to
provide multiple views of the same canvas.
The canvas is optimized for large numbers of items, particularly
where only a small percentage of the items change at any
one time. If the entire display changes very frequently, you should
consider using your own custom QtScrollView subclass.
Qt provides a rich
set of canvas item classes, e.g. QtCanvasEllipse, QtCanvasLine,
QtCanvasPolygon, QtCanvasPolygonalItem, QtCanvasRectangle, QtCanvasSpline,
QtCanvasSprite and QtCanvasText. You can subclass to create your own
canvas items; QtCanvasPolygonalItem is the most common base class used
for this purpose.
Items appear on the canvas after their \link QtCanvasItem::show()
show()\endlink function has been called (or \link
QtCanvasItem::setVisible() setVisible(true)\endlink), and \e after
update() has been called. The canvas only shows items that are
\link QtCanvasItem::setVisible() visible\endlink, and then only if
\l update() is called. (By default the canvas is white and so are
canvas items, so if nothing appears try changing colors.)
If you created the canvas without passing a width and height to
the constructor you must also call resize().
Although a canvas may appear to be similar to a widget with child
widgets, there are several notable differences:
\list
\i Canvas items are usually much faster to manipulate and redraw than
child widgets, with the speed advantage becoming especially great when
there are \e many canvas items and non-rectangular items. In most
situations canvas items are also a lot more memory efficient than child
widgets.
\i It's easy to detect overlapping items (collision detection).
\i The canvas can be larger than a widget. A million-by-million canvas
is perfectly possible. At such a size a widget might be very
inefficient, and some window systems might not support it at all,
whereas QtCanvas scales well. Even with a billion pixels and a million
items, finding a particular canvas item, detecting collisions, etc.,
is still fast (though the memory consumption may be prohibitive
at such extremes).
\i Two or more QtCanvasView objects can view the same canvas.
\i An arbitrary transformation matrix can be set on each QtCanvasView
which makes it easy to zoom, rotate or shear the viewed canvas.
\i Widgets provide a lot more functionality, such as input (QKeyEvent,
QMouseEvent etc.) and layout management (QGridLayout etc.).
\endlist
A canvas consists of a background, a number of canvas items organized by
x, y and z coordinates, and a foreground. A canvas item's z coordinate
can be treated as a layer number -- canvas items with a higher z
coordinate appear in front of canvas items with a lower z coordinate.
The background is white by default, but can be set to a different color
using setBackgroundColor(), or to a repeated pixmap using
setBackgroundPixmap() or to a mosaic of smaller pixmaps using
setTiles(). Individual tiles can be set with setTile(). There
are corresponding get functions, e.g. backgroundColor() and
backgroundPixmap().
Note that QtCanvas does not inherit from QWidget, even though it has some
functions which provide the same functionality as those in QWidget. One
of these is setBackgroundPixmap(); some others are resize(), size(),
width() and height(). \l QtCanvasView is the widget used to display a
canvas on the screen.
Canvas items are added to a canvas by constructing them and passing the
canvas to the canvas item's constructor. An item can be moved to a
different canvas using QtCanvasItem::setCanvas().
Canvas items are movable (and in the case of QtCanvasSprites, animated)
objects that inherit QtCanvasItem. Each canvas item has a position on the
canvas (x, y coordinates) and a height (z coordinate), all of which are
held as floating-point numbers. Moving canvas items also have x and y
velocities. It's possible for a canvas item to be outside the canvas
(for example QtCanvasItem::x() is greater than width()). When a canvas
item is off the canvas, onCanvas() returns false and the canvas
disregards the item. (Canvas items off the canvas do not slow down any
of the common operations on the canvas.)
Canvas items can be moved with QtCanvasItem::move(). The advance()
function moves all QtCanvasItem::animated() canvas items and
setAdvancePeriod() makes QtCanvas move them automatically on a periodic
basis. In the context of the QtCanvas classes, to `animate' a canvas item
is to set it in motion, i.e. using QtCanvasItem::setVelocity(). Animation
of a canvas item itself, i.e. items which change over time, is enabled
by calling QtCanvasSprite::setFrameAnimation(), or more generally by
subclassing and reimplementing QtCanvasItem::advance(). To detect collisions
use one of the QtCanvasItem::collisions() functions.
The changed parts of the canvas are redrawn (if they are visible in a
canvas view) whenever update() is called. You can either call update()
manually after having changed the contents of the canvas, or force
periodic updates using setUpdatePeriod(). If you have moving objects on
the canvas, you must call advance() every time the objects should
move one step further. Periodic calls to advance() can be forced using
setAdvancePeriod(). The advance() function will call
QtCanvasItem::advance() on every item that is \link
QtCanvasItem::animated() animated\endlink and trigger an update of the
affected areas afterwards. (A canvas item that is `animated' is simply
a canvas item that is in motion.)
QtCanvas organizes its canvas items into \e chunks; these are areas on
the canvas that are used to speed up most operations. Many operations
start by eliminating most chunks (i.e. those which haven't changed)
and then process only the canvas items that are in the few interesting
(i.e. changed) chunks. A valid chunk, validChunk(), is one which is on
the canvas.
The chunk size is a key factor to QtCanvas's speed: if there are too many
chunks, the speed benefit of grouping canvas items into chunks is
reduced. If the chunks are too large, it takes too long to process each
one. The QtCanvas constructor tries to pick a suitable size, but you
can call retune() to change it at any time. The chunkSize() function
returns the current chunk size. The canvas items always make sure
they're in the right chunks; all you need to make sure of is that
the canvas uses the right chunk size. A good rule of thumb is that
the size should be a bit smaller than the average canvas item
size. If you have moving objects, the chunk size should be a bit
smaller than the average size of the moving items.
The foreground is normally nothing, but if you reimplement
drawForeground(), you can draw things in front of all the canvas
items.
Areas can be set as changed with setChanged() and set unchanged with
setUnchanged(). The entire canvas can be set as changed with
setAllChanged(). A list of all the items on the canvas is returned by
allItems().
An area can be copied (painted) to a QPainter with drawArea().
If the canvas is resized it emits the resized() signal.
The examples/canvas application and the 2D graphics page of the
examples/demo application demonstrate many of QtCanvas's facilities.
\sa QtCanvasView QtCanvasItem
*/
void QtCanvas::init(int w, int h, int chunksze, int mxclusters)
{
d = new QtCanvasData;
awidth = w;
aheight = h;
chunksize = chunksze;
maxclusters = mxclusters;
chwidth = (w+chunksize-1)/chunksize;
chheight = (h+chunksize-1)/chunksize;
chunks = new QtCanvasChunk[chwidth*chheight];
update_timer = 0;
bgcolor = white;
grid = 0;
htiles = 0;
vtiles = 0;
debug_redraw_areas = false;
}
/*
Create a QtCanvas with no size. \a parent is passed to the QObject
superclass.
\warning You \e must call resize() at some time after creation to
be able to use the canvas.
*/
QtCanvas::QtCanvas(QObject* parent)
: QObject(parent)
{
init(0, 0);
}
/*
Constructs a QtCanvas that is \a w pixels wide and \a h pixels high.
*/
QtCanvas::QtCanvas(int w, int h)
{
init(w, h);
}
/*
Constructs a QtCanvas which will be composed of \a h tiles
horizontally and \a v tiles vertically. Each tile will be an image
\a tilewidth by \a tileheight pixels taken from pixmap \a p.
The pixmap \a p is a list of tiles, arranged left to right, (and
in the case of pixmaps that have multiple rows of tiles, top to
bottom), with tile 0 in the top-left corner, tile 1 next to the
right, and so on, e.g.
\table
\row \i 0 \i 1 \i 2 \i 3
\row \i 4 \i 5 \i 6 \i 7
\endtable
The QtCanvas is initially sized to show exactly the given number of
tiles horizontally and vertically. If it is resized to be larger,
the entire matrix of tiles will be repeated as often as necessary
to cover the area. If it is smaller, tiles to the right and bottom
will not be visible.
\sa setTiles()
*/
QtCanvas::QtCanvas(QPixmap p,
int h, int v, int tilewidth, int tileheight)
{
init(h*tilewidth, v*tileheight, scm(tilewidth, tileheight));
setTiles(p, h, v, tilewidth, tileheight);
}
/*
Destroys the canvas and all the canvas's canvas items.
*/
QtCanvas::~QtCanvas()
{
for (int i = 0; i < d->viewList.size(); ++i)
d->viewList[i]->viewing = 0;
QtCanvasItemList all = allItems();
for (QtCanvasItemList::Iterator it = all.begin(); it!= all.end(); ++it)
delete *it;
delete [] chunks;
delete [] grid;
delete d;
}
/*
\internal
Returns the chunk at a chunk position \a i, \a j.
*/
QtCanvasChunk& QtCanvas::chunk(int i, int j) const
{
return chunks[i+chwidth*j];
}
/*
\internal
Returns the chunk at a pixel position \a x, \a y.
*/
QtCanvasChunk& QtCanvas::chunkContaining(int x, int y) const
{
return chunk(x/chunksize, y/chunksize);
}
/*
Returns a list of all the items in the canvas.
*/
QtCanvasItemList QtCanvas::allItems()
{
return d->itemDict.toList();
}
/*
Changes the size of the canvas to have a width of \a w and a
height of \a h. This is a slow operation.
*/
void QtCanvas::resize(int w, int h)
{
if (awidth == w && aheight == h)
return;
QList<QtCanvasItem *> hidden;
for (QSet<QtCanvasItem *>::const_iterator it = d->itemDict.begin(); it != d->itemDict.end(); ++it) {
if ((*it)->isVisible()) {
(*it)->hide();
hidden.append(*it);
}
}
int nchwidth = (w+chunksize-1)/chunksize;
int nchheight = (h+chunksize-1)/chunksize;
QtCanvasChunk* newchunks = new QtCanvasChunk[nchwidth*nchheight];
// Commit the new values.
//
awidth = w;
aheight = h;
chwidth = nchwidth;
chheight = nchheight;
delete [] chunks;
chunks = newchunks;
for (int i = 0; i < hidden.size(); ++i)
hidden.at(i)->show();
setAllChanged();
emit resized();
}
/*
\fn void QtCanvas::resized()
This signal is emitted whenever the canvas is resized. Each
QtCanvasView connects to this signal to keep the scrollview's size
correct.
*/
/*
Change the efficiency tuning parameters to \a mxclusters clusters,
each of size \a chunksze. This is a slow operation if there are
many objects on the canvas.
The canvas is divided into chunks which are rectangular areas \a
chunksze wide by \a chunksze high. Use a chunk size which is about
the average size of the canvas items. If you choose a chunk size
which is too small it will increase the amount of calculation
required when drawing since each change will affect many chunks.
If you choose a chunk size which is too large the amount of
drawing required will increase because for each change, a lot of
drawing will be required since there will be many (unchanged)
canvas items which are in the same chunk as the changed canvas
items.
Internally, a canvas uses a low-resolution "chunk matrix" to keep
track of all the items in the canvas. A 64x64 chunk matrix is the
default for a 1024x1024 pixel canvas, where each chunk collects
canvas items in a 16x16 pixel square. This default is also
affected by setTiles(). You can tune this default using this
function. For example if you have a very large canvas and want to
trade off speed for memory then you might set the chunk size to 32
or 64.
The \a mxclusters argument is the number of rectangular groups of
chunks that will be separately drawn. If the canvas has a large
number of small, dispersed items, this should be about that
number. Our testing suggests that a large number of clusters is
almost always best.
*/
void QtCanvas::retune(int chunksze, int mxclusters)
{
maxclusters = mxclusters;
if (chunksize!= chunksze) {
QList<QtCanvasItem *> hidden;
for (QSet<QtCanvasItem *>::const_iterator it = d->itemDict.begin(); it != d->itemDict.end(); ++it) {
if ((*it)->isVisible()) {
(*it)->hide();
hidden.append(*it);
}
}
chunksize = chunksze;
int nchwidth = (awidth+chunksize-1)/chunksize;
int nchheight = (aheight+chunksize-1)/chunksize;
QtCanvasChunk* newchunks = new QtCanvasChunk[nchwidth*nchheight];
// Commit the new values.
//
chwidth = nchwidth;
chheight = nchheight;
delete [] chunks;
chunks = newchunks;
for (int i = 0; i < hidden.size(); ++i)
hidden.at(i)->show();
}
}
/*
\fn int QtCanvas::width() const
Returns the width of the canvas, in pixels.
*/
/*
\fn int QtCanvas::height() const
Returns the height of the canvas, in pixels.
*/
/*
\fn QSize QtCanvas::size() const
Returns the size of the canvas, in pixels.
*/
/*
\fn QRect QtCanvas::rect() const
Returns a rectangle the size of the canvas.
*/
/*
\fn bool QtCanvas::onCanvas(int x, int y) const
Returns true if the pixel position (\a x, \a y) is on the canvas;
otherwise returns false.
\sa validChunk()
*/
/*
\fn bool QtCanvas::onCanvas(const QPoint& p) const
\overload
Returns true if the pixel position \a p is on the canvas;
otherwise returns false.
\sa validChunk()
*/
/*
\fn bool QtCanvas::validChunk(int x, int y) const
Returns true if the chunk position (\a x, \a y) is on the canvas;
otherwise returns false.
\sa onCanvas()
*/
/*
\fn bool QtCanvas::validChunk(const QPoint& p) const
\overload
Returns true if the chunk position \a p is on the canvas; otherwise
returns false.
\sa onCanvas()
*/
/*
\fn int QtCanvas::chunkSize() const
Returns the chunk size of the canvas.
\sa retune()
*/
/*
\fn bool QtCanvas::sameChunk(int x1, int y1, int x2, int y2) const
\internal
Tells if the points (\a x1, \a y1) and (\a x2, \a y2) are within the same chunk.
*/
/*
\internal
This method adds an the item \a item to the list of QtCanvasItem objects
in the QtCanvas. The QtCanvasItem class calls this.
*/
void QtCanvas::addItem(QtCanvasItem* item)
{
d->itemDict.insert(item);
}
/*
\internal
This method adds the item \a item to the list of QtCanvasItem objects
to be moved. The QtCanvasItem class calls this.
*/
void QtCanvas::addAnimation(QtCanvasItem* item)
{
d->animDict.insert(item);
}
/*
\internal
This method adds the item \a item to the list of QtCanvasItem objects
which are no longer to be moved. The QtCanvasItem class calls this.
*/
void QtCanvas::removeAnimation(QtCanvasItem* item)
{
d->animDict.remove(item);
}
/*
\internal
This method removes the item \a item from the list of QtCanvasItem objects
in this QtCanvas. The QtCanvasItem class calls this.
*/
void QtCanvas::removeItem(QtCanvasItem* item)
{
d->itemDict.remove(item);
}
/*
\internal
This method adds the view \a view to the list of QtCanvasView objects
viewing this QtCanvas. The QtCanvasView class calls this.
*/
void QtCanvas::addView(QtCanvasView* view)
{
d->viewList.append(view);
if (htiles>1 || vtiles>1 || pm.isNull()) {
QPalette::ColorRole role = view->widget()->backgroundRole();
QPalette viewPalette = view->widget()->palette();
viewPalette.setColor(role, backgroundColor());
view->widget()->setPalette(viewPalette);
}
}
/*
\internal
This method removes the view \a view from the list of QtCanvasView objects
viewing this QtCanvas. The QtCanvasView class calls this.
*/
void QtCanvas::removeView(QtCanvasView* view)
{
d->viewList.removeAll(view);
}
/*
Sets the canvas to call advance() every \a ms milliseconds. Any
previous setting by setAdvancePeriod() or setUpdatePeriod() is
overridden.
If \a ms is less than 0 advancing will be stopped.
*/
void QtCanvas::setAdvancePeriod(int ms)
{
if (ms < 0) {
if (update_timer)
update_timer->stop();
} else {
if (update_timer)
delete update_timer;
update_timer = new QTimer(this);
connect(update_timer, SIGNAL(timeout()), this, SLOT(advance()));
update_timer->start(ms);
}
}
/*
Sets the canvas to call update() every \a ms milliseconds. Any
previous setting by setAdvancePeriod() or setUpdatePeriod() is
overridden.
If \a ms is less than 0 automatic updating will be stopped.
*/
void QtCanvas::setUpdatePeriod(int ms)
{
if (ms < 0) {
if (update_timer)
update_timer->stop();