5907 lines
161 KiB
C++
5907 lines
161 KiB
C++
/****************************************************************************
|
||
**
|
||
** 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();
|
||
} else {
|
||
if (update_timer)
|
||
delete update_timer;
|
||
update_timer = new QTimer(this);
|
||
connect(update_timer, SIGNAL(timeout()), this, SLOT(update()));
|
||
update_timer->start(ms);
|
||
}
|
||
}
|
||
|
||
/*
|
||
Moves all QtCanvasItem::animated() canvas items on the canvas and
|
||
refreshes all changes to all views of the canvas. (An `animated'
|
||
item is an item that is in motion; see setVelocity().)
|
||
|
||
The advance takes place in two phases. In phase 0, the
|
||
QtCanvasItem::advance() function of each QtCanvasItem::animated()
|
||
canvas item is called with paramater 0. Then all these canvas
|
||
items are called again, with parameter 1. In phase 0, the canvas
|
||
items should not change position, merely examine other items on
|
||
the canvas for which special processing is required, such as
|
||
collisions between items. In phase 1, all canvas items should
|
||
change positions, ignoring any other items on the canvas. This
|
||
two-phase approach allows for considerations of "fairness",
|
||
although no QtCanvasItem subclasses supplied with Qt do anything
|
||
interesting in phase 0.
|
||
|
||
The canvas can be configured to call this function periodically
|
||
with setAdvancePeriod().
|
||
|
||
\sa update()
|
||
*/
|
||
void QtCanvas::advance()
|
||
{
|
||
QSetIterator<QtCanvasItem *> it = d->animDict;
|
||
while (it.hasNext()) {
|
||
QtCanvasItem *i = it.next();
|
||
if (i)
|
||
i->advance(0);
|
||
}
|
||
// we expect the dict contains the exact same items as in the
|
||
// first pass.
|
||
it.toFront();
|
||
while (it.hasNext()) {
|
||
QtCanvasItem* i = it.next();
|
||
if (i)
|
||
i->advance(1);
|
||
}
|
||
update();
|
||
}
|
||
|
||
// Don't call this unless you know what you're doing.
|
||
// p is in the content's co-ordinate example.
|
||
/*
|
||
\internal
|
||
*/
|
||
void QtCanvas::drawViewArea(QtCanvasView* view, QPainter* p, const QRect& vr, bool)
|
||
{
|
||
QMatrix wm = view->worldMatrix();
|
||
QMatrix iwm = wm.inverted();
|
||
// ivr = covers all chunks in vr
|
||
QRect ivr = iwm.mapRect(vr);
|
||
|
||
p->setMatrix(wm);
|
||
drawCanvasArea(ivr, p, false);
|
||
}
|
||
|
||
/*
|
||
Repaints changed areas in all views of the canvas.
|
||
|
||
\sa advance()
|
||
*/
|
||
void QtCanvas::update()
|
||
{
|
||
QRect r = changeBounds();
|
||
for (int i = 0; i < d->viewList.size(); ++i) {
|
||
QtCanvasView* view = d->viewList.at(i);
|
||
if (!r.isEmpty()) {
|
||
QRect tr = view->worldMatrix().mapRect(r);
|
||
view->widget()->update(tr);
|
||
}
|
||
}
|
||
setUnchanged(r);
|
||
}
|
||
|
||
|
||
/*
|
||
Marks the whole canvas as changed.
|
||
All views of the canvas will be entirely redrawn when
|
||
update() is called next.
|
||
*/
|
||
void QtCanvas::setAllChanged()
|
||
{
|
||
setChanged(QRect(0, 0, width(), height()));
|
||
}
|
||
|
||
/*
|
||
Marks \a area as changed. This \a area will be redrawn in all
|
||
views that are showing it when update() is called next.
|
||
*/
|
||
void QtCanvas::setChanged(const QRect& area)
|
||
{
|
||
QRect thearea = area.intersected(QRect(0, 0, width(), height()));
|
||
|
||
int mx = (thearea.x()+thearea.width()+chunksize)/chunksize;
|
||
int my = (thearea.y()+thearea.height()+chunksize)/chunksize;
|
||
if (mx>chwidth)
|
||
mx = chwidth;
|
||
if (my>chheight)
|
||
my = chheight;
|
||
|
||
int x = thearea.x()/chunksize;
|
||
while(x < mx) {
|
||
int y = thearea.y()/chunksize;
|
||
while(y < my) {
|
||
chunk(x, y).change();
|
||
y++;
|
||
}
|
||
x++;
|
||
}
|
||
}
|
||
|
||
/*
|
||
Marks \a area as \e unchanged. The area will \e not be redrawn in
|
||
the views for the next update(), unless it is marked or changed
|
||
again before the next call to update().
|
||
*/
|
||
void QtCanvas::setUnchanged(const QRect& area)
|
||
{
|
||
QRect thearea = area.intersected(QRect(0, 0, width(), height()));
|
||
|
||
int mx = (thearea.x()+thearea.width()+chunksize)/chunksize;
|
||
int my = (thearea.y()+thearea.height()+chunksize)/chunksize;
|
||
if (mx>chwidth)
|
||
mx = chwidth;
|
||
if (my>chheight)
|
||
my = chheight;
|
||
|
||
int x = thearea.x()/chunksize;
|
||
while(x < mx) {
|
||
int y = thearea.y()/chunksize;
|
||
while(y < my) {
|
||
chunk(x, y).takeChange();
|
||
y++;
|
||
}
|
||
x++;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
\internal
|
||
*/
|
||
QRect QtCanvas::changeBounds()
|
||
{
|
||
QRect area = QRect(0, 0, width(), height());
|
||
|
||
int mx = (area.x()+area.width()+chunksize)/chunksize;
|
||
int my = (area.y()+area.height()+chunksize)/chunksize;
|
||
if (mx > chwidth)
|
||
mx = chwidth;
|
||
if (my > chheight)
|
||
my = chheight;
|
||
|
||
QRect result;
|
||
|
||
int x = area.x()/chunksize;
|
||
while(x < mx) {
|
||
int y = area.y()/chunksize;
|
||
while(y < my) {
|
||
QtCanvasChunk& ch = chunk(x, y);
|
||
if (ch.hasChanged())
|
||
result |= QRect(x*chunksize, y*chunksize, chunksize + 1, chunksize + 1);
|
||
y++;
|
||
}
|
||
x++;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
Paints all canvas items that are in the area \a clip to \a
|
||
painter, using double-buffering if \a dbuf is true.
|
||
|
||
e.g. to print the canvas to a printer:
|
||
\code
|
||
QPrinter pr;
|
||
if (pr.setup()) {
|
||
QPainter p(&pr);
|
||
canvas.drawArea(canvas.rect(), &p);
|
||
}
|
||
\endcode
|
||
*/
|
||
void QtCanvas::drawArea(const QRect& clip, QPainter* painter, bool dbuf)
|
||
{
|
||
if (painter)
|
||
drawCanvasArea(clip, painter, dbuf);
|
||
}
|
||
|
||
#include <QtCore/QDebug>
|
||
/*
|
||
\internal
|
||
*/
|
||
void QtCanvas::drawCanvasArea(const QRect& inarea, QPainter* p, bool /*double_buffer*/)
|
||
{
|
||
QRect area = inarea.intersected(QRect(0, 0, width(), height()));
|
||
|
||
if (!p) return; // Nothing to do.
|
||
|
||
int lx = area.x()/chunksize;
|
||
int ly = area.y()/chunksize;
|
||
int mx = area.right()/chunksize;
|
||
int my = area.bottom()/chunksize;
|
||
if (mx>= chwidth)
|
||
mx = chwidth-1;
|
||
if (my>= chheight)
|
||
my = chheight-1;
|
||
|
||
QtCanvasItemList allvisible;
|
||
|
||
// Stores the region within area that need to be drawn. It is relative
|
||
// to area.topLeft() (so as to keep within bounds of 16-bit XRegions)
|
||
QRegion rgn;
|
||
|
||
for (int x = lx; x <= mx; x++) {
|
||
for (int y = ly; y <= my; y++) {
|
||
// Only reset change if all views updating, and
|
||
// wholy within area. (conservative: ignore entire boundary)
|
||
//
|
||
// Disable this to help debugging.
|
||
//
|
||
if (!p) {
|
||
if (chunk(x, y).takeChange()) {
|
||
// ### should at least make bands
|
||
rgn |= QRegion(x*chunksize-area.x(), y*chunksize-area.y(),
|
||
chunksize, chunksize);
|
||
allvisible += chunk(x, y).list();
|
||
}
|
||
} else {
|
||
allvisible += chunk(x, y).list();
|
||
}
|
||
}
|
||
}
|
||
qSort(allvisible.begin(), allvisible.end(), QtCanvasItemLess());
|
||
|
||
drawBackground(*p, area);
|
||
if (!allvisible.isEmpty()) {
|
||
QtCanvasItem* prev = 0;
|
||
for (int i = allvisible.size() - 1; i >= 0; --i) {
|
||
QtCanvasItem *g = allvisible[i];
|
||
if (g != prev) {
|
||
g->draw(*p);
|
||
prev = g;
|
||
}
|
||
}
|
||
}
|
||
|
||
drawForeground(*p, area);
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
This method to informs the QtCanvas that a given chunk is
|
||
`dirty' and needs to be redrawn in the next Update.
|
||
|
||
(\a x, \a y) is a chunk location.
|
||
|
||
The sprite classes call this. Any new derived class of QtCanvasItem
|
||
must do so too. SetChangedChunkContaining can be used instead.
|
||
*/
|
||
void QtCanvas::setChangedChunk(int x, int y)
|
||
{
|
||
if (validChunk(x, y)) {
|
||
QtCanvasChunk& ch = chunk(x, y);
|
||
ch.change();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
This method to informs the QtCanvas that the chunk containing a given
|
||
pixel is `dirty' and needs to be redrawn in the next Update.
|
||
|
||
(\a x, \a y) is a pixel location.
|
||
|
||
The item classes call this. Any new derived class of QtCanvasItem must
|
||
do so too. SetChangedChunk can be used instead.
|
||
*/
|
||
void QtCanvas::setChangedChunkContaining(int x, int y)
|
||
{
|
||
if (x>= 0 && x < width() && y>= 0 && y < height()) {
|
||
QtCanvasChunk& chunk = chunkContaining(x, y);
|
||
chunk.change();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
This method adds the QtCanvasItem \a g to the list of those which need to be
|
||
drawn if the given chunk at location (\a x, \a y) is redrawn. Like
|
||
SetChangedChunk and SetChangedChunkContaining, this method marks the
|
||
chunk as `dirty'.
|
||
*/
|
||
void QtCanvas::addItemToChunk(QtCanvasItem* g, int x, int y)
|
||
{
|
||
if (validChunk(x, y)) {
|
||
chunk(x, y).add(g);
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
This method removes the QtCanvasItem \a g from the list of those which need to
|
||
be drawn if the given chunk at location (\a x, \a y) is redrawn. Like
|
||
SetChangedChunk and SetChangedChunkContaining, this method marks the chunk
|
||
as `dirty'.
|
||
*/
|
||
void QtCanvas::removeItemFromChunk(QtCanvasItem* g, int x, int y)
|
||
{
|
||
if (validChunk(x, y)) {
|
||
chunk(x, y).remove(g);
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
\internal
|
||
This method adds the QtCanvasItem \a g to the list of those which need to be
|
||
drawn if the chunk containing the given pixel (\a x, \a y) is redrawn. Like
|
||
SetChangedChunk and SetChangedChunkContaining, this method marks the
|
||
chunk as `dirty'.
|
||
*/
|
||
void QtCanvas::addItemToChunkContaining(QtCanvasItem* g, int x, int y)
|
||
{
|
||
if (x>= 0 && x < width() && y>= 0 && y < height()) {
|
||
chunkContaining(x, y).add(g);
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
This method removes the QtCanvasItem \a g from the list of those which need to
|
||
be drawn if the chunk containing the given pixel (\a x, \a y) is redrawn.
|
||
Like SetChangedChunk and SetChangedChunkContaining, this method
|
||
marks the chunk as `dirty'.
|
||
*/
|
||
void QtCanvas::removeItemFromChunkContaining(QtCanvasItem* g, int x, int y)
|
||
{
|
||
if (x>= 0 && x < width() && y>= 0 && y < height()) {
|
||
chunkContaining(x, y).remove(g);
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the color set by setBackgroundColor(). By default, this is
|
||
white.
|
||
|
||
This function is not a reimplementation of
|
||
QWidget::backgroundColor() (QtCanvas is not a subclass of QWidget),
|
||
but all QtCanvasViews that are viewing the canvas will set their
|
||
backgrounds to this color.
|
||
|
||
\sa setBackgroundColor(), backgroundPixmap()
|
||
*/
|
||
QColor QtCanvas::backgroundColor() const
|
||
{
|
||
return bgcolor;
|
||
}
|
||
|
||
/*
|
||
Sets the solid background to be the color \a c.
|
||
|
||
\sa backgroundColor(), setBackgroundPixmap(), setTiles()
|
||
*/
|
||
void QtCanvas::setBackgroundColor(const QColor& c)
|
||
{
|
||
if (bgcolor != c) {
|
||
bgcolor = c;
|
||
for (int i = 0; i < d->viewList.size(); ++i) {
|
||
QtCanvasView *view = d->viewList.at(i);
|
||
QPalette::ColorRole role = view->widget()->backgroundRole();
|
||
QPalette viewPalette = view->widget()->palette();
|
||
viewPalette.setColor(role, bgcolor);
|
||
view->widget()->setPalette(viewPalette);
|
||
}
|
||
setAllChanged();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the pixmap set by setBackgroundPixmap(). By default,
|
||
this is a null pixmap.
|
||
|
||
\sa setBackgroundPixmap(), backgroundColor()
|
||
*/
|
||
QPixmap QtCanvas::backgroundPixmap() const
|
||
{
|
||
return pm;
|
||
}
|
||
|
||
/*
|
||
Sets the solid background to be the pixmap \a p repeated as
|
||
necessary to cover the entire canvas.
|
||
|
||
\sa backgroundPixmap(), setBackgroundColor(), setTiles()
|
||
*/
|
||
void QtCanvas::setBackgroundPixmap(const QPixmap& p)
|
||
{
|
||
setTiles(p, 1, 1, p.width(), p.height());
|
||
for (int i = 0; i < d->viewList.size(); ++i) {
|
||
QtCanvasView* view = d->viewList.at(i);
|
||
view->widget()->update();
|
||
}
|
||
}
|
||
|
||
/*
|
||
This virtual function is called for all updates of the canvas. It
|
||
renders any background graphics using the painter \a painter, in
|
||
the area \a clip. If the canvas has a background pixmap or a tiled
|
||
background, that graphic is used, otherwise the canvas is cleared
|
||
using the background color.
|
||
|
||
If the graphics for an area change, you must explicitly call
|
||
setChanged(const QRect&) for the result to be visible when
|
||
update() is next called.
|
||
|
||
\sa setBackgroundColor(), setBackgroundPixmap(), setTiles()
|
||
*/
|
||
void QtCanvas::drawBackground(QPainter& painter, const QRect& clip)
|
||
{
|
||
if (pm.isNull()) {
|
||
painter.fillRect(clip, bgcolor);
|
||
} else if (!grid) {
|
||
for (int x = clip.x()/pm.width();
|
||
x < (clip.x()+clip.width()+pm.width()-1)/pm.width(); x++)
|
||
{
|
||
for (int y = clip.y()/pm.height();
|
||
y < (clip.y()+clip.height()+pm.height()-1)/pm.height(); y++)
|
||
{
|
||
painter.drawPixmap(x*pm.width(), y*pm.height(), pm);
|
||
}
|
||
}
|
||
} else {
|
||
const int x1 = clip.left()/tilew;
|
||
int x2 = clip.right()/tilew;
|
||
const int y1 = clip.top()/tileh;
|
||
int y2 = clip.bottom()/tileh;
|
||
|
||
const int roww = pm.width()/tilew;
|
||
|
||
for (int j = y1; j <= y2; j++) {
|
||
int jj = j%tilesVertically();
|
||
for (int i = x1; i <= x2; i++) {
|
||
int t = tile(i%tilesHorizontally(), jj);
|
||
int tx = t % roww;
|
||
int ty = t / roww;
|
||
painter.drawPixmap(i*tilew, j*tileh, pm,
|
||
tx*tilew, ty*tileh, tilew, tileh);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
This virtual function is called for all updates of the canvas. It
|
||
renders any foreground graphics using the painter \a painter, in
|
||
the area \a clip.
|
||
|
||
If the graphics for an area change, you must explicitly call
|
||
setChanged(const QRect&) for the result to be visible when
|
||
update() is next called.
|
||
|
||
The default is to draw nothing.
|
||
*/
|
||
void QtCanvas::drawForeground(QPainter& painter, const QRect& clip)
|
||
{
|
||
if (debug_redraw_areas) {
|
||
painter.setPen(red);
|
||
painter.setBrush(NoBrush);
|
||
painter.drawRect(clip);
|
||
}
|
||
}
|
||
|
||
/*
|
||
Sets the QtCanvas to be composed of \a h tiles horizontally and \a
|
||
v tiles vertically. Each tile will be an image \a tilewidth by \a
|
||
tileheight pixels 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
|
||
|
||
If the canvas is larger than the matrix of tiles, the entire
|
||
matrix is repeated as necessary to cover the whole canvas. If it
|
||
is smaller, tiles to the right and bottom are not visible.
|
||
|
||
The width and height of \a p must be a multiple of \a tilewidth
|
||
and \a tileheight. If they are not the function will do nothing.
|
||
|
||
If you want to unset any tiling set, then just pass in a null
|
||
pixmap and 0 for \a h, \a v, \a tilewidth, and
|
||
\a tileheight.
|
||
*/
|
||
void QtCanvas::setTiles(QPixmap p,
|
||
int h, int v, int tilewidth, int tileheight)
|
||
{
|
||
if (!p.isNull() && (!tilewidth || !tileheight ||
|
||
p.width() % tilewidth != 0 || p.height() % tileheight != 0))
|
||
return;
|
||
|
||
htiles = h;
|
||
vtiles = v;
|
||
delete[] grid;
|
||
pm = p;
|
||
if (h && v && !p.isNull()) {
|
||
grid = new ushort[h*v];
|
||
memset(grid, 0, h*v*sizeof(ushort));
|
||
tilew = tilewidth;
|
||
tileh = tileheight;
|
||
} else {
|
||
grid = 0;
|
||
}
|
||
if (h + v > 10) {
|
||
int s = scm(tilewidth, tileheight);
|
||
retune(s < 128 ? s : qMax(tilewidth, tileheight));
|
||
}
|
||
setAllChanged();
|
||
}
|
||
|
||
/*
|
||
\fn int QtCanvas::tile(int x, int y) const
|
||
|
||
Returns the tile at position (\a x, \a y). Initially, all tiles
|
||
are 0.
|
||
|
||
The parameters must be within range, i.e.
|
||
0 \< \a x \< tilesHorizontally() and
|
||
0 \< \a y \< tilesVertically().
|
||
|
||
\sa setTile()
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvas::tilesHorizontally() const
|
||
|
||
Returns the number of tiles horizontally.
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvas::tilesVertically() const
|
||
|
||
Returns the number of tiles vertically.
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvas::tileWidth() const
|
||
|
||
Returns the width of each tile.
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvas::tileHeight() const
|
||
|
||
Returns the height of each tile.
|
||
*/
|
||
|
||
|
||
/*
|
||
Sets the tile at (\a x, \a y) to use tile number \a tilenum, which
|
||
is an index into the tile pixmaps. The canvas will update
|
||
appropriately when update() is next called.
|
||
|
||
The images are taken from the pixmap set by setTiles() and are
|
||
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
|
||
|
||
\sa tile() setTiles()
|
||
*/
|
||
void QtCanvas::setTile(int x, int y, int tilenum)
|
||
{
|
||
ushort& t = grid[x+y*htiles];
|
||
if (t != tilenum) {
|
||
t = tilenum;
|
||
if (tilew == tileh && tilew == chunksize)
|
||
setChangedChunk(x, y); // common case
|
||
else
|
||
setChanged(QRect(x*tilew, y*tileh, tilew, tileh));
|
||
}
|
||
}
|
||
|
||
|
||
// lesser-used data in canvas item, plus room for extension.
|
||
// Be careful adding to this - check all usages.
|
||
class QtCanvasItemExtra {
|
||
QtCanvasItemExtra() : vx(0.0), vy(0.0) { }
|
||
double vx, vy;
|
||
friend class QtCanvasItem;
|
||
};
|
||
|
||
|
||
/*
|
||
\class QtCanvasItem qtcanvas.h
|
||
\brief The QtCanvasItem class provides an abstract graphic object on a QtCanvas.
|
||
|
||
A variety of QtCanvasItem subclasses provide immediately usable
|
||
behaviour. This class is a pure abstract superclass providing the
|
||
behaviour that is shared among all the concrete canvas item classes.
|
||
QtCanvasItem is not intended for direct subclassing. It is much easier
|
||
to subclass one of its subclasses, e.g. QtCanvasPolygonalItem (the
|
||
commonest base class), QtCanvasRectangle, QtCanvasSprite, QtCanvasEllipse
|
||
or QtCanvasText.
|
||
|
||
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 setCanvas().
|
||
|
||
Items appear on the canvas after their \link show() show()\endlink
|
||
function has been called (or \link setVisible()
|
||
setVisible(true)\endlink), and \e after update() has been called. The
|
||
canvas only shows items that are \link setVisible() visible\endlink,
|
||
and then only if \l update() is called. If you created the canvas
|
||
without passing a width and height to the constructor you'll also need
|
||
to call \link QtCanvas::resize() resize()\endlink. Since the canvas
|
||
background defaults to white and canvas items default to white,
|
||
you may need to change colors to see your items.
|
||
|
||
A QtCanvasItem object can be moved in the x(), y() and z() dimensions
|
||
using functions such as move(), moveBy(), setX(), setY() and setZ(). A
|
||
canvas item can be set in motion, `animated', using setAnimated() and
|
||
given a velocity in the x and y directions with setXVelocity() and
|
||
setYVelocity() -- the same effect can be achieved by calling
|
||
setVelocity(). Use the collidesWith() function to see if the canvas item
|
||
will collide on the \e next advance(1) and use collisions() to see what
|
||
collisions have occurred.
|
||
|
||
Use QtCanvasSprite or your own subclass of QtCanvasSprite to create canvas
|
||
items which are animated, i.e. which change over time.
|
||
|
||
The size of a canvas item is given by boundingRect(). Use
|
||
boundingRectAdvanced() to see what the size of the canvas item will be
|
||
\e after the next advance(1) call.
|
||
|
||
The rtti() function is used for identifying subclasses of QtCanvasItem.
|
||
The canvas() function returns a pointer to the canvas which contains the
|
||
canvas item.
|
||
|
||
QtCanvasItem provides the show() and isVisible() functions like those in
|
||
QWidget.
|
||
|
||
QtCanvasItem also provides the setEnabled(), setActive() and
|
||
setSelected() functions; these functions set the relevant boolean and
|
||
cause a repaint but the boolean values they set are not used in
|
||
QtCanvasItem itself. You can make use of these booleans in your subclasses.
|
||
|
||
By default, canvas items have no velocity, no size, and are not in
|
||
motion. The subclasses provided in Qt do not change these defaults
|
||
except where noted.
|
||
|
||
*/
|
||
|
||
/*
|
||
\enum QtCanvasItem::RttiValues
|
||
|
||
This enum is used to name the different types of canvas item.
|
||
|
||
\value Rtti_Item Canvas item abstract base class
|
||
\value Rtti_Ellipse
|
||
\value Rtti_Line
|
||
\value Rtti_Polygon
|
||
\value Rtti_PolygonalItem
|
||
\value Rtti_Rectangle
|
||
\value Rtti_Spline
|
||
\value Rtti_Sprite
|
||
\value Rtti_Text
|
||
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasItem::update()
|
||
|
||
Call this function to repaint the canvas's changed chunks.
|
||
*/
|
||
|
||
/*
|
||
Constructs a QtCanvasItem on canvas \a canvas.
|
||
|
||
\sa setCanvas()
|
||
*/
|
||
QtCanvasItem::QtCanvasItem(QtCanvas* canvas) :
|
||
cnv(canvas),
|
||
myx(0), myy(0), myz(0)
|
||
{
|
||
ani = 0;
|
||
vis = 0;
|
||
val = 0;
|
||
sel = 0;
|
||
ena = 0;
|
||
act = 0;
|
||
|
||
ext = 0;
|
||
if (cnv) cnv->addItem(this);
|
||
}
|
||
|
||
/*
|
||
Destroys the QtCanvasItem and removes it from its canvas.
|
||
*/
|
||
QtCanvasItem::~QtCanvasItem()
|
||
{
|
||
if (cnv) {
|
||
cnv->removeItem(this);
|
||
cnv->removeAnimation(this);
|
||
}
|
||
delete ext;
|
||
}
|
||
|
||
QtCanvasItemExtra& QtCanvasItem::extra()
|
||
{
|
||
if (!ext)
|
||
ext = new QtCanvasItemExtra;
|
||
return *ext;
|
||
}
|
||
|
||
/*
|
||
\fn double QtCanvasItem::x() const
|
||
|
||
Returns the horizontal position of the canvas item. Note that
|
||
subclasses often have an origin other than the top-left corner.
|
||
*/
|
||
|
||
/*
|
||
\fn double QtCanvasItem::y() const
|
||
|
||
Returns the vertical position of the canvas item. Note that
|
||
subclasses often have an origin other than the top-left corner.
|
||
*/
|
||
|
||
/*
|
||
\fn double QtCanvasItem::z() const
|
||
|
||
Returns the z index of the canvas item, which is used for visual
|
||
order: higher-z items obscure (are in front of) lower-z items.
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasItem::setX(double x)
|
||
|
||
Moves the canvas item so that its x-position is \a x.
|
||
|
||
\sa x(), move()
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasItem::setY(double y)
|
||
|
||
Moves the canvas item so that its y-position is \a y.
|
||
|
||
\sa y(), move()
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasItem::setZ(double z)
|
||
|
||
Sets the z index of the canvas item to \a z. Higher-z items
|
||
obscure (are in front of) lower-z items.
|
||
|
||
\sa z(), move()
|
||
*/
|
||
|
||
|
||
/*
|
||
Moves the canvas item relative to its current position by (\a dx,
|
||
\a dy).
|
||
*/
|
||
void QtCanvasItem::moveBy(double dx, double dy)
|
||
{
|
||
if (dx || dy) {
|
||
removeFromChunks();
|
||
myx += dx;
|
||
myy += dy;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
Moves the canvas item to the absolute position (\a x, \a y).
|
||
*/
|
||
void QtCanvasItem::move(double x, double y)
|
||
{
|
||
moveBy(x-myx, y-myy);
|
||
}
|
||
|
||
|
||
/*
|
||
Returns true if the canvas item is in motion; otherwise returns
|
||
false.
|
||
|
||
\sa setVelocity(), setAnimated()
|
||
*/
|
||
bool QtCanvasItem::animated() const
|
||
{
|
||
return (bool)ani;
|
||
}
|
||
|
||
/*
|
||
Sets the canvas item to be in motion if \a y is true, or not if \a
|
||
y is false. The speed and direction of the motion is set with
|
||
setVelocity(), or with setXVelocity() and setYVelocity().
|
||
|
||
\sa advance(), QtCanvas::advance()
|
||
*/
|
||
void QtCanvasItem::setAnimated(bool y)
|
||
{
|
||
if (y != (bool)ani) {
|
||
ani = (uint)y;
|
||
if (y) {
|
||
cnv->addAnimation(this);
|
||
} else {
|
||
cnv->removeAnimation(this);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
\fn void QtCanvasItem::setXVelocity(double vx)
|
||
|
||
Sets the horizontal component of the canvas item's velocity to \a vx.
|
||
|
||
\sa setYVelocity() setVelocity()
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasItem::setYVelocity(double vy)
|
||
|
||
Sets the vertical component of the canvas item's velocity to \a vy.
|
||
|
||
\sa setXVelocity() setVelocity()
|
||
*/
|
||
|
||
/*
|
||
Sets the canvas item to be in motion, moving by \a vx and \a vy
|
||
pixels in the horizontal and vertical directions respectively.
|
||
|
||
\sa advance() setXVelocity() setYVelocity()
|
||
*/
|
||
void QtCanvasItem::setVelocity(double vx, double vy)
|
||
{
|
||
if (ext || vx!= 0.0 || vy!= 0.0) {
|
||
if (!ani)
|
||
setAnimated(true);
|
||
extra().vx = vx;
|
||
extra().vy = vy;
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the horizontal velocity component of the canvas item.
|
||
*/
|
||
double QtCanvasItem::xVelocity() const
|
||
{
|
||
return ext ? ext->vx : 0;
|
||
}
|
||
|
||
/*
|
||
Returns the vertical velocity component of the canvas item.
|
||
*/
|
||
double QtCanvasItem::yVelocity() const
|
||
{
|
||
return ext ? ext->vy : 0;
|
||
}
|
||
|
||
/*
|
||
The default implementation moves the canvas item, if it is
|
||
animated(), by the preset velocity if \a phase is 1, and does
|
||
nothing if \a phase is 0.
|
||
|
||
Note that if you reimplement this function, the reimplementation
|
||
must not change the canvas in any way, for example it must not add
|
||
or remove items.
|
||
|
||
\sa QtCanvas::advance() setVelocity()
|
||
*/
|
||
void QtCanvasItem::advance(int phase)
|
||
{
|
||
if (ext && phase == 1)
|
||
moveBy(ext->vx, ext->vy);
|
||
}
|
||
|
||
/*
|
||
\fn void QtCanvasItem::draw(QPainter& painter)
|
||
|
||
This abstract virtual function draws the canvas item using \a painter.
|
||
*/
|
||
|
||
/*
|
||
Sets the QtCanvas upon which the canvas item is to be drawn to \a c.
|
||
|
||
\sa canvas()
|
||
*/
|
||
void QtCanvasItem::setCanvas(QtCanvas* c)
|
||
{
|
||
bool v = isVisible();
|
||
setVisible(false);
|
||
if (cnv) {
|
||
if (ext)
|
||
cnv->removeAnimation(this);
|
||
cnv->removeItem(this);
|
||
}
|
||
cnv = c;
|
||
if (cnv) {
|
||
cnv->addItem(this);
|
||
if (ext)
|
||
cnv->addAnimation(this);
|
||
}
|
||
setVisible(v);
|
||
}
|
||
|
||
/*
|
||
\fn QtCanvas* QtCanvasItem::canvas() const
|
||
|
||
Returns the canvas containing the canvas item.
|
||
*/
|
||
|
||
/* Shorthand for setVisible(true). */
|
||
void QtCanvasItem::show()
|
||
{
|
||
setVisible(true);
|
||
}
|
||
|
||
/* Shorthand for setVisible(false). */
|
||
void QtCanvasItem::hide()
|
||
{
|
||
setVisible(false);
|
||
}
|
||
|
||
/*
|
||
Makes the canvas item visible if \a yes is true, or invisible if
|
||
\a yes is false. The change takes effect when QtCanvas::update() is
|
||
next called.
|
||
*/
|
||
void QtCanvasItem::setVisible(bool yes)
|
||
{
|
||
if ((bool)vis!= yes) {
|
||
if (yes) {
|
||
vis = (uint)yes;
|
||
addToChunks();
|
||
} else {
|
||
removeFromChunks();
|
||
vis = (uint)yes;
|
||
}
|
||
}
|
||
}
|
||
/*
|
||
\obsolete
|
||
\fn bool QtCanvasItem::visible() const
|
||
Use isVisible() instead.
|
||
*/
|
||
|
||
/*
|
||
\fn bool QtCanvasItem::isVisible() const
|
||
|
||
Returns true if the canvas item is visible; otherwise returns
|
||
false.
|
||
|
||
Note that in this context true does \e not mean that the canvas
|
||
item is currently in a view, merely that if a view is showing the
|
||
area where the canvas item is positioned, and the item is not
|
||
obscured by items with higher z values, and the view is not
|
||
obscured by overlaying windows, it would be visible.
|
||
|
||
\sa setVisible(), z()
|
||
*/
|
||
|
||
/*
|
||
\obsolete
|
||
\fn bool QtCanvasItem::selected() const
|
||
Use isSelected() instead.
|
||
*/
|
||
|
||
/*
|
||
\fn bool QtCanvasItem::isSelected() const
|
||
|
||
Returns true if the canvas item is selected; otherwise returns false.
|
||
*/
|
||
|
||
/*
|
||
Sets the selected flag of the item to \a yes. If this changes the
|
||
item's selected state the item will be redrawn when
|
||
QtCanvas::update() is next called.
|
||
|
||
The QtCanvas, QtCanvasItem and the Qt-supplied QtCanvasItem
|
||
subclasses do not make use of this value. The setSelected()
|
||
function is supplied because many applications need it, but it is
|
||
up to you how you use the isSelected() value.
|
||
*/
|
||
void QtCanvasItem::setSelected(bool yes)
|
||
{
|
||
if ((bool)sel!= yes) {
|
||
sel = (uint)yes;
|
||
changeChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\obsolete
|
||
\fn bool QtCanvasItem::enabled() const
|
||
Use isEnabled() instead.
|
||
*/
|
||
|
||
/*
|
||
\fn bool QtCanvasItem::isEnabled() const
|
||
|
||
Returns true if the QtCanvasItem is enabled; otherwise returns false.
|
||
*/
|
||
|
||
/*
|
||
Sets the enabled flag of the item to \a yes. If this changes the
|
||
item's enabled state the item will be redrawn when
|
||
QtCanvas::update() is next called.
|
||
|
||
The QtCanvas, QtCanvasItem and the Qt-supplied QtCanvasItem
|
||
subclasses do not make use of this value. The setEnabled()
|
||
function is supplied because many applications need it, but it is
|
||
up to you how you use the isEnabled() value.
|
||
*/
|
||
void QtCanvasItem::setEnabled(bool yes)
|
||
{
|
||
if (ena!= (uint)yes) {
|
||
ena = (uint)yes;
|
||
changeChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\obsolete
|
||
\fn bool QtCanvasItem::active() const
|
||
Use isActive() instead.
|
||
*/
|
||
|
||
/*
|
||
\fn bool QtCanvasItem::isActive() const
|
||
|
||
Returns true if the QtCanvasItem is active; otherwise returns false.
|
||
*/
|
||
|
||
/*
|
||
Sets the active flag of the item to \a yes. If this changes the
|
||
item's active state the item will be redrawn when
|
||
QtCanvas::update() is next called.
|
||
|
||
The QtCanvas, QtCanvasItem and the Qt-supplied QtCanvasItem
|
||
subclasses do not make use of this value. The setActive() function
|
||
is supplied because many applications need it, but it is up to you
|
||
how you use the isActive() value.
|
||
*/
|
||
void QtCanvasItem::setActive(bool yes)
|
||
{
|
||
if (act!= (uint)yes) {
|
||
act = (uint)yes;
|
||
changeChunks();
|
||
}
|
||
}
|
||
|
||
bool qt_testCollision(const QtCanvasSprite* s1, const QtCanvasSprite* s2)
|
||
{
|
||
const QImage* s2image = s2->imageAdvanced()->collision_mask;
|
||
QRect s2area = s2->boundingRectAdvanced();
|
||
|
||
QRect cyourarea(s2area.x(), s2area.y(),
|
||
s2area.width(), s2area.height());
|
||
|
||
QImage* s1image = s1->imageAdvanced()->collision_mask;
|
||
|
||
QRect s1area = s1->boundingRectAdvanced();
|
||
|
||
QRect ourarea = s1area.intersected(cyourarea);
|
||
|
||
if (ourarea.isEmpty())
|
||
return false;
|
||
|
||
int x2 = ourarea.x()-cyourarea.x();
|
||
int y2 = ourarea.y()-cyourarea.y();
|
||
int x1 = ourarea.x()-s1area.x();
|
||
int y1 = ourarea.y()-s1area.y();
|
||
int w = ourarea.width();
|
||
int h = ourarea.height();
|
||
|
||
if (!s2image) {
|
||
if (!s1image)
|
||
return w>0 && h>0;
|
||
// swap everything around
|
||
int t;
|
||
t = x1; x1 = x2; x2 = t;
|
||
t = y1; x1 = y2; y2 = t;
|
||
s2image = s1image;
|
||
s1image = 0;
|
||
}
|
||
|
||
// s2image != 0
|
||
|
||
// A non-linear search may be more efficient.
|
||
// Perhaps spiralling out from the center, or a simpler
|
||
// vertical expansion from the centreline.
|
||
|
||
// We assume that sprite masks don't have
|
||
// different bit orders.
|
||
//
|
||
// Q_ASSERT(s1image->bitOrder() == s2image->bitOrder());
|
||
|
||
if (s1image) {
|
||
if (s1image->format() == QImage::Format_MonoLSB) {
|
||
for (int j = 0; j < h; j++) {
|
||
uchar* ml = s1image->scanLine(y1+j);
|
||
const uchar* yl = s2image->scanLine(y2+j);
|
||
for (int i = 0; i < w; i++) {
|
||
if (*(yl + ((x2+i) >> 3)) & (1 << ((x2+i) & 7))
|
||
&& *(ml + ((x1+i) >> 3)) & (1 << ((x1+i) & 7)))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
for (int j = 0; j < h; j++) {
|
||
uchar* ml = s1image->scanLine(y1+j);
|
||
const uchar* yl = s2image->scanLine(y2+j);
|
||
for (int i = 0; i < w; i++) {
|
||
if (*(yl + ((x2+i) >> 3)) & (1 << (7-((x2+i) & 7)))
|
||
&& *(ml + ((x1+i) >> 3)) & (1 << (7-((x1+i) & 7))))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
if (s2image->format() == QImage::Format_MonoLSB) {
|
||
for (int j = 0; j < h; j++) {
|
||
const uchar* yl = s2image->scanLine(y2+j);
|
||
for (int i = 0; i < w; i++) {
|
||
if (*(yl + ((x2+i) >> 3)) & (1 << ((x2+i) & 7)))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
for (int j = 0; j< h; j++) {
|
||
const uchar* yl = s2image->scanLine(y2+j);
|
||
for (int i = 0; i < w; i++) {
|
||
if (*(yl + ((x2+i) >> 3)) & (1 << (7-((x2+i) & 7))))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
static bool collision_double_dispatch(const QtCanvasSprite* s1,
|
||
const QtCanvasPolygonalItem* p1,
|
||
const QtCanvasRectangle* r1,
|
||
const QtCanvasEllipse* e1,
|
||
const QtCanvasText* t1,
|
||
const QtCanvasSprite* s2,
|
||
const QtCanvasPolygonalItem* p2,
|
||
const QtCanvasRectangle* r2,
|
||
const QtCanvasEllipse* e2,
|
||
const QtCanvasText* t2)
|
||
{
|
||
const QtCanvasItem* i1 = s1 ?
|
||
(const QtCanvasItem*)s1 : p1 ?
|
||
(const QtCanvasItem*)p1 : r1 ?
|
||
(const QtCanvasItem*)r1 : e1 ?
|
||
(const QtCanvasItem*)e1 : (const QtCanvasItem*)t1;
|
||
const QtCanvasItem* i2 = s2 ?
|
||
(const QtCanvasItem*)s2 : p2 ?
|
||
(const QtCanvasItem*)p2 : r2 ?
|
||
(const QtCanvasItem*)r2 : e2 ?
|
||
(const QtCanvasItem*)e2 : (const QtCanvasItem*)t2;
|
||
|
||
if (s1 && s2) {
|
||
// a
|
||
return qt_testCollision(s1, s2);
|
||
} else if ((r1 || t1 || s1) && (r2 || t2 || s2)) {
|
||
// b
|
||
QRect rc1 = i1->boundingRectAdvanced();
|
||
QRect rc2 = i2->boundingRectAdvanced();
|
||
return rc1.intersects(rc2);
|
||
} else if (e1 && e2
|
||
&& e1->angleLength()>= 360*16 && e2->angleLength()>= 360*16
|
||
&& e1->width() == e1->height()
|
||
&& e2->width() == e2->height()) {
|
||
// c
|
||
double xd = (e1->x()+e1->xVelocity())-(e2->x()+e1->xVelocity());
|
||
double yd = (e1->y()+e1->yVelocity())-(e2->y()+e1->yVelocity());
|
||
double rd = (e1->width()+e2->width())/2;
|
||
return xd*xd+yd*yd <= rd*rd;
|
||
} else if (p1 && (p2 || s2 || t2)) {
|
||
// d
|
||
QPolygon pa1 = p1->areaPointsAdvanced();
|
||
QPolygon pa2 = p2 ? p2->areaPointsAdvanced()
|
||
: QPolygon(i2->boundingRectAdvanced());
|
||
bool col = !(QRegion(pa1) & QRegion(pa2, Qt::WindingFill)).isEmpty();
|
||
|
||
return col;
|
||
} else {
|
||
return collision_double_dispatch(s2, p2, r2, e2, t2,
|
||
s1, p1, r1, e1, t1);
|
||
}
|
||
}
|
||
|
||
/*
|
||
\fn bool QtCanvasItem::collidesWith(const QtCanvasItem* other) const
|
||
|
||
Returns true if the canvas item will collide with the \a other
|
||
item \e after they have moved by their current velocities;
|
||
otherwise returns false.
|
||
|
||
\sa collisions()
|
||
*/
|
||
|
||
|
||
/*
|
||
\class QtCanvasSprite qtcanvas.h
|
||
\brief The QtCanvasSprite class provides an animated canvas item on a QtCanvas.
|
||
|
||
A canvas sprite is an object which can contain any number of images
|
||
(referred to as frames), only one of which is current, i.e.
|
||
displayed, at any one time. The images can be passed in the
|
||
constructor or set or changed later with setSequence(). If you
|
||
subclass QtCanvasSprite you can change the frame that is displayed
|
||
periodically, e.g. whenever QtCanvasItem::advance(1) is called to
|
||
create the effect of animation.
|
||
|
||
The current frame can be set with setFrame() or with move(). The
|
||
number of frames available is given by frameCount(). The bounding
|
||
rectangle of the current frame is returned by boundingRect().
|
||
|
||
The current frame's image can be retrieved with image(); use
|
||
imageAdvanced() to retrieve the image for the frame that will be
|
||
shown after advance(1) is called. Use the image() overload passing
|
||
it an integer index to retrieve a particular image from the list of
|
||
frames.
|
||
|
||
Use width() and height() to retrieve the dimensions of the current
|
||
frame.
|
||
|
||
Use leftEdge() and rightEdge() to retrieve the current frame's
|
||
left-hand and right-hand x-coordinates respectively. Use
|
||
bottomEdge() and topEdge() to retrieve the current frame's bottom
|
||
and top y-coordinates respectively. These functions have an overload
|
||
which will accept an integer frame number to retrieve the
|
||
coordinates of a particular frame.
|
||
|
||
QtCanvasSprite draws very quickly, at the expense of memory.
|
||
|
||
The current frame's image can be drawn on a painter with draw().
|
||
|
||
Like any other canvas item, canvas sprites can be moved with
|
||
move() which sets the x and y coordinates and the frame number, as
|
||
well as with QtCanvasItem::move() and QtCanvasItem::moveBy(), or by
|
||
setting coordinates with QtCanvasItem::setX(), QtCanvasItem::setY()
|
||
and QtCanvasItem::setZ().
|
||
|
||
*/
|
||
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
bool QtCanvasSprite::collidesWith(const QtCanvasItem* i) const
|
||
{
|
||
return i->collidesWith(this, 0, 0, 0, 0);
|
||
}
|
||
|
||
/*
|
||
Returns true if the canvas item collides with any of the given
|
||
items; otherwise returns false. The parameters, \a s, \a p, \a r,
|
||
\a e and \a t, are all the same object, this is just a type
|
||
resolution trick.
|
||
*/
|
||
bool QtCanvasSprite::collidesWith(const QtCanvasSprite* s,
|
||
const QtCanvasPolygonalItem* p,
|
||
const QtCanvasRectangle* r,
|
||
const QtCanvasEllipse* e,
|
||
const QtCanvasText* t) const
|
||
{
|
||
return collision_double_dispatch(s, p, r, e, t, this, 0, 0, 0, 0);
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
bool QtCanvasPolygonalItem::collidesWith(const QtCanvasItem* i) const
|
||
{
|
||
return i->collidesWith(0, this, 0, 0, 0);
|
||
}
|
||
|
||
bool QtCanvasPolygonalItem::collidesWith(const QtCanvasSprite* s,
|
||
const QtCanvasPolygonalItem* p,
|
||
const QtCanvasRectangle* r,
|
||
const QtCanvasEllipse* e,
|
||
const QtCanvasText* t) const
|
||
{
|
||
return collision_double_dispatch(s, p, r, e, t, 0, this, 0, 0, 0);
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
bool QtCanvasRectangle::collidesWith(const QtCanvasItem* i) const
|
||
{
|
||
return i->collidesWith(0, this, this, 0, 0);
|
||
}
|
||
|
||
bool QtCanvasRectangle::collidesWith(const QtCanvasSprite* s,
|
||
const QtCanvasPolygonalItem* p,
|
||
const QtCanvasRectangle* r,
|
||
const QtCanvasEllipse* e,
|
||
const QtCanvasText* t) const
|
||
{
|
||
return collision_double_dispatch(s, p, r, e, t, 0, this, this, 0, 0);
|
||
}
|
||
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
bool QtCanvasEllipse::collidesWith(const QtCanvasItem* i) const
|
||
{
|
||
return i->collidesWith(0,this, 0, this, 0);
|
||
}
|
||
|
||
bool QtCanvasEllipse::collidesWith(const QtCanvasSprite* s,
|
||
const QtCanvasPolygonalItem* p,
|
||
const QtCanvasRectangle* r,
|
||
const QtCanvasEllipse* e,
|
||
const QtCanvasText* t) const
|
||
{
|
||
return collision_double_dispatch(s, p, r, e, t, 0, this, 0, this, 0);
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
bool QtCanvasText::collidesWith(const QtCanvasItem* i) const
|
||
{
|
||
return i->collidesWith(0, 0, 0, 0, this);
|
||
}
|
||
|
||
bool QtCanvasText::collidesWith(const QtCanvasSprite* s,
|
||
const QtCanvasPolygonalItem* p,
|
||
const QtCanvasRectangle* r,
|
||
const QtCanvasEllipse* e,
|
||
const QtCanvasText* t) const
|
||
{
|
||
return collision_double_dispatch(s, p, r, e, t, 0, 0, 0, 0, this);
|
||
}
|
||
|
||
/*
|
||
Returns the list of canvas items that this canvas item has
|
||
collided with.
|
||
|
||
A collision is generally defined as occurring when the pixels of
|
||
one item draw on the pixels of another item, but not all
|
||
subclasses are so precise. Also, since pixel-wise collision
|
||
detection can be slow, this function works in either exact or
|
||
inexact mode, according to the \a exact parameter.
|
||
|
||
If \a exact is true, the canvas items returned have been
|
||
accurately tested for collision with the canvas item.
|
||
|
||
If \a exact is false, the canvas items returned are \e near the
|
||
canvas item. You can test the canvas items returned using
|
||
collidesWith() if any are interesting collision candidates. By
|
||
using this approach, you can ignore some canvas items for which
|
||
collisions are not relevant.
|
||
|
||
The returned list is a list of QtCanvasItems, but often you will
|
||
need to cast the items to their subclass types. The safe way to do
|
||
this is to use rtti() before casting. This provides some of the
|
||
functionality of the standard C++ dynamic cast operation even on
|
||
compilers where dynamic casts are not available.
|
||
|
||
Note that a canvas item may be `on' a canvas, e.g. it was created
|
||
with the canvas as parameter, even though its coordinates place it
|
||
beyond the edge of the canvas's area. Collision detection only
|
||
works for canvas items which are wholly or partly within the
|
||
canvas's area.
|
||
|
||
Note that if items have a velocity (see \l setVelocity()), then
|
||
collision testing is done based on where the item \e will be when
|
||
it moves, not its current location. For example, a "ball" item
|
||
doesn't need to actually embed into a "wall" item before a
|
||
collision is detected. For items without velocity, plain
|
||
intersection is used.
|
||
*/
|
||
QtCanvasItemList QtCanvasItem::collisions(bool exact) const
|
||
{
|
||
return canvas()->collisions(chunks(), this, exact);
|
||
}
|
||
|
||
/*
|
||
Returns a list of canvas items that collide with the point \a p.
|
||
The list is ordered by z coordinates, from highest z coordinate
|
||
(front-most item) to lowest z coordinate (rear-most item).
|
||
*/
|
||
QtCanvasItemList QtCanvas::collisions(const QPoint& p) const
|
||
{
|
||
return collisions(QRect(p, QSize(1, 1)));
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns a list of items which collide with the rectangle \a r. The
|
||
list is ordered by z coordinates, from highest z coordinate
|
||
(front-most item) to lowest z coordinate (rear-most item).
|
||
*/
|
||
QtCanvasItemList QtCanvas::collisions(const QRect& r) const
|
||
{
|
||
QtCanvasRectangle i(r, (QtCanvas*)this);
|
||
i.setPen(NoPen);
|
||
i.show(); // doesn't actually show, since we destroy it
|
||
QtCanvasItemList l = i.collisions(true);
|
||
qSort(l.begin(), l.end(), QtCanvasItemLess());
|
||
return l;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns a list of canvas items which intersect with the chunks
|
||
listed in \a chunklist, excluding \a item. If \a exact is true,
|
||
only those which actually \link QtCanvasItem::collidesWith()
|
||
collide with\endlink \a item are returned; otherwise canvas items
|
||
are included just for being in the chunks.
|
||
|
||
This is a utility function mainly used to implement the simpler
|
||
QtCanvasItem::collisions() function.
|
||
*/
|
||
QtCanvasItemList QtCanvas::collisions(const QPolygon& chunklist,
|
||
const QtCanvasItem* item, bool exact) const
|
||
{
|
||
QSet<QtCanvasItem *> seen;
|
||
QtCanvasItemList result;
|
||
for (int i = 0; i <(int)chunklist.count(); i++) {
|
||
int x = chunklist[i].x();
|
||
int y = chunklist[i].y();
|
||
if (validChunk(x, y)) {
|
||
const QtCanvasItemList &l = chunk(x, y).list();
|
||
for (int i = 0; i < l.size(); ++i) {
|
||
QtCanvasItem *g = l.at(i);
|
||
if (g != item) {
|
||
if (!seen.contains(g)) {
|
||
seen.insert(g);
|
||
if (!exact || item->collidesWith(g))
|
||
result.append(g);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
Adds the item to all the chunks it covers.
|
||
*/
|
||
void QtCanvasItem::addToChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
QPolygon pa = chunks();
|
||
for (int i = 0; i < (int)pa.count(); i++)
|
||
canvas()->addItemToChunk(this, pa[i].x(), pa[i].y());
|
||
val = (uint)true;
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
Removes the item from all the chunks it covers.
|
||
*/
|
||
void QtCanvasItem::removeFromChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
QPolygon pa = chunks();
|
||
for (int i = 0; i < (int)pa.count(); i++)
|
||
canvas()->removeItemFromChunk(this, pa[i].x(), pa[i].y());
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
Sets all the chunks covered by the item to be refreshed with QtCanvas::update()
|
||
is next called.
|
||
*/
|
||
void QtCanvasItem::changeChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
if (!val)
|
||
addToChunks();
|
||
QPolygon pa = chunks();
|
||
for (int i = 0; i < (int)pa.count(); i++)
|
||
canvas()->setChangedChunk(pa[i].x(), pa[i].y());
|
||
}
|
||
}
|
||
|
||
/*
|
||
\fn QRect QtCanvasItem::boundingRect() const
|
||
|
||
Returns the bounding rectangle in pixels that the canvas item covers.
|
||
|
||
\sa boundingRectAdvanced()
|
||
*/
|
||
|
||
/*
|
||
Returns the bounding rectangle of pixels that the canvas item \e
|
||
will cover after advance(1) is called.
|
||
|
||
\sa boundingRect()
|
||
*/
|
||
QRect QtCanvasItem::boundingRectAdvanced() const
|
||
{
|
||
int dx = int(x()+xVelocity())-int(x());
|
||
int dy = int(y()+yVelocity())-int(y());
|
||
QRect r = boundingRect();
|
||
r.translate(dx, dy);
|
||
return r;
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasPixmap qtcanvas.h
|
||
\brief The QtCanvasPixmap class provides pixmaps for QtCanvasSprites.
|
||
|
||
If you want to show a single pixmap on a QtCanvas use a
|
||
QtCanvasSprite with just one pixmap.
|
||
|
||
When pixmaps are inserted into a QtCanvasPixmapArray they are held
|
||
as QtCanvasPixmaps. \l{QtCanvasSprite}s are used to show pixmaps on
|
||
\l{QtCanvas}es and hold their pixmaps in a QtCanvasPixmapArray. If
|
||
you retrieve a frame (pixmap) from a QtCanvasSprite it will be
|
||
returned as a QtCanvasPixmap.
|
||
|
||
The pixmap is a QPixmap and can only be set in the constructor.
|
||
There are three different constructors, one taking a QPixmap, one
|
||
a QImage and one a file name that refers to a file in any
|
||
supported file format (see QImageReader).
|
||
|
||
QtCanvasPixmap can have a hotspot which is defined in terms of an (x,
|
||
y) offset. When you create a QtCanvasPixmap from a PNG file or from
|
||
a QImage that has a QImage::offset(), the offset() is initialized
|
||
appropriately, otherwise the constructor leaves it at (0, 0). You
|
||
can set it later using setOffset(). When the QtCanvasPixmap is used
|
||
in a QtCanvasSprite, the offset position is the point at
|
||
QtCanvasItem::x() and QtCanvasItem::y(), not the top-left corner of
|
||
the pixmap.
|
||
|
||
Note that for QtCanvasPixmap objects created by a QtCanvasSprite, the
|
||
position of each QtCanvasPixmap object is set so that the hotspot
|
||
stays in the same position.
|
||
|
||
\sa QtCanvasPixmapArray QtCanvasItem QtCanvasSprite
|
||
*/
|
||
|
||
|
||
/*
|
||
Constructs a QtCanvasPixmap that uses the image stored in \a
|
||
datafilename.
|
||
*/
|
||
QtCanvasPixmap::QtCanvasPixmap(const QString& datafilename)
|
||
{
|
||
QImage image(datafilename);
|
||
init(image);
|
||
}
|
||
|
||
|
||
/*
|
||
Constructs a QtCanvasPixmap from the image \a image.
|
||
*/
|
||
QtCanvasPixmap::QtCanvasPixmap(const QImage& image)
|
||
{
|
||
init(image);
|
||
}
|
||
/*
|
||
Constructs a QtCanvasPixmap from the pixmap \a pm using the offset
|
||
\a offset.
|
||
*/
|
||
QtCanvasPixmap::QtCanvasPixmap(const QPixmap& pm, const QPoint& offset)
|
||
{
|
||
init(pm, offset.x(), offset.y());
|
||
}
|
||
|
||
void QtCanvasPixmap::init(const QImage& image)
|
||
{
|
||
this->QPixmap::operator = (QPixmap::fromImage(image));
|
||
hotx = image.offset().x();
|
||
hoty = image.offset().y();
|
||
#ifndef QT_NO_IMAGE_DITHER_TO_1
|
||
if(image.hasAlphaChannel()) {
|
||
QImage i = image.createAlphaMask();
|
||
collision_mask = new QImage(i);
|
||
} else
|
||
#endif
|
||
collision_mask = 0;
|
||
}
|
||
|
||
void QtCanvasPixmap::init(const QPixmap& pixmap, int hx, int hy)
|
||
{
|
||
(QPixmap&)*this = pixmap;
|
||
hotx = hx;
|
||
hoty = hy;
|
||
if(pixmap.hasAlphaChannel()) {
|
||
QImage i = mask().toImage();
|
||
collision_mask = new QImage(i);
|
||
} else
|
||
collision_mask = 0;
|
||
}
|
||
|
||
/*
|
||
Destroys the pixmap.
|
||
*/
|
||
QtCanvasPixmap::~QtCanvasPixmap()
|
||
{
|
||
delete collision_mask;
|
||
}
|
||
|
||
/*
|
||
\fn int QtCanvasPixmap::offsetX() const
|
||
|
||
Returns the x-offset of the pixmap's hotspot.
|
||
|
||
\sa setOffset()
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvasPixmap::offsetY() const
|
||
|
||
Returns the y-offset of the pixmap's hotspot.
|
||
|
||
\sa setOffset()
|
||
*/
|
||
|
||
/*
|
||
\fn void QtCanvasPixmap::setOffset(int x, int y)
|
||
|
||
Sets the offset of the pixmap's hotspot to (\a x, \a y).
|
||
|
||
\warning Do not call this function if any QtCanvasSprites are
|
||
currently showing this pixmap.
|
||
*/
|
||
|
||
/*
|
||
\class QtCanvasPixmapArray qtcanvas.h
|
||
\brief The QtCanvasPixmapArray class provides an array of QtCanvasPixmaps.
|
||
|
||
This class is used by QtCanvasSprite to hold an array of pixmaps.
|
||
It is used to implement animated sprites, i.e. images that change
|
||
over time, with each pixmap in the array holding one frame.
|
||
|
||
Depending on the constructor you use you can load multiple pixmaps
|
||
into the array either from a directory (specifying a wildcard
|
||
pattern for the files), or from a list of QPixmaps. You can also
|
||
read in a set of pixmaps after construction using readPixmaps().
|
||
|
||
Individual pixmaps can be set with setImage() and retrieved with
|
||
image(). The number of pixmaps in the array is returned by
|
||
count().
|
||
|
||
QtCanvasSprite uses an image's mask for collision detection. You
|
||
can change this by reading in a separate set of image masks using
|
||
readCollisionMasks().
|
||
|
||
*/
|
||
|
||
/*
|
||
Constructs an invalid array (i.e. isValid() will return false).
|
||
You must call readPixmaps() before being able to use this
|
||
QtCanvasPixmapArray.
|
||
*/
|
||
QtCanvasPixmapArray::QtCanvasPixmapArray()
|
||
: framecount(0), img(0)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Constructs a QtCanvasPixmapArray from files.
|
||
|
||
The \a fc parameter sets the number of frames to be loaded for
|
||
this image.
|
||
|
||
If \a fc is not 0, \a datafilenamepattern should contain "%1",
|
||
e.g. "foo%1.png". The actual filenames are formed by replacing the
|
||
%1 with four-digit integers from 0 to (fc - 1), e.g. foo0000.png,
|
||
foo0001.png, foo0002.png, etc.
|
||
|
||
If \a fc is 0, \a datafilenamepattern is asssumed to be a
|
||
filename, and the image contained in this file will be loaded as
|
||
the first (and only) frame.
|
||
|
||
If \a datafilenamepattern does not exist, is not readable, isn't
|
||
an image, or some other error occurs, the array ends up empty and
|
||
isValid() returns false.
|
||
*/
|
||
|
||
QtCanvasPixmapArray::QtCanvasPixmapArray(const QString& datafilenamepattern,
|
||
int fc)
|
||
: framecount(0), img(0)
|
||
{
|
||
readPixmaps(datafilenamepattern, fc);
|
||
}
|
||
|
||
/*
|
||
\obsolete
|
||
Use QtCanvasPixmapArray::QtCanvasPixmapArray(QtValueList<QPixmap>, QPolygon)
|
||
instead.
|
||
|
||
Constructs a QtCanvasPixmapArray from the list of QPixmaps \a
|
||
list. The \a hotspots list has to be of the same size as \a list.
|
||
*/
|
||
QtCanvasPixmapArray::QtCanvasPixmapArray(const QList<QPixmap> &list, const QPolygon &hotspots)
|
||
: framecount(list.count()),
|
||
img(new QtCanvasPixmap*[list.count()])
|
||
{
|
||
if (list.count() != hotspots.count()) {
|
||
qWarning("QtCanvasPixmapArray: lists have different lengths");
|
||
reset();
|
||
img = 0;
|
||
} else {
|
||
for (int i = 0; i < framecount; i++)
|
||
img[i] = new QtCanvasPixmap(list.at(i), hotspots.at(i));
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
Destroys the pixmap array and all the pixmaps it contains.
|
||
*/
|
||
QtCanvasPixmapArray::~QtCanvasPixmapArray()
|
||
{
|
||
reset();
|
||
}
|
||
|
||
void QtCanvasPixmapArray::reset()
|
||
{
|
||
for (int i = 0; i < framecount; i++)
|
||
delete img[i];
|
||
delete [] img;
|
||
img = 0;
|
||
framecount = 0;
|
||
}
|
||
|
||
/*
|
||
Reads one or more pixmaps into the pixmap array.
|
||
|
||
If \a fc is not 0, \a filenamepattern should contain "%1", e.g.
|
||
"foo%1.png". The actual filenames are formed by replacing the %1
|
||
with four-digit integers from 0 to (fc - 1), e.g. foo0000.png,
|
||
foo0001.png, foo0002.png, etc.
|
||
|
||
If \a fc is 0, \a filenamepattern is asssumed to be a filename,
|
||
and the image contained in this file will be loaded as the first
|
||
(and only) frame.
|
||
|
||
If \a filenamepattern does not exist, is not readable, isn't an
|
||
image, or some other error occurs, this function will return
|
||
false, and isValid() will return false; otherwise this function
|
||
will return true.
|
||
|
||
\sa isValid()
|
||
*/
|
||
bool QtCanvasPixmapArray::readPixmaps(const QString& filenamepattern,
|
||
int fc)
|
||
{
|
||
return readPixmaps(filenamepattern, fc, false);
|
||
}
|
||
|
||
/*
|
||
Reads new collision masks for the array.
|
||
|
||
By default, QtCanvasSprite uses the image mask of a sprite to
|
||
detect collisions. Use this function to set your own collision
|
||
image masks.
|
||
|
||
If count() is 1 \a filename must specify a real filename to read
|
||
the mask from. If count() is greater than 1, the \a filename must
|
||
contain a "%1" that will get replaced by the number of the mask to
|
||
be loaded, just like QtCanvasPixmapArray::readPixmaps().
|
||
|
||
All collision masks must be 1-bit images or this function call
|
||
will fail.
|
||
|
||
If the file isn't readable, contains the wrong number of images,
|
||
or there is some other error, this function will return false, and
|
||
the array will be flagged as invalid; otherwise this function
|
||
returns true.
|
||
|
||
\sa isValid()
|
||
*/
|
||
bool QtCanvasPixmapArray::readCollisionMasks(const QString& filename)
|
||
{
|
||
return readPixmaps(filename, framecount, true);
|
||
}
|
||
|
||
|
||
bool QtCanvasPixmapArray::readPixmaps(const QString& datafilenamepattern,
|
||
int fc, bool maskonly)
|
||
{
|
||
if (!maskonly) {
|
||
reset();
|
||
framecount = fc;
|
||
if (!framecount)
|
||
framecount = 1;
|
||
img = new QtCanvasPixmap*[framecount];
|
||
}
|
||
if (!img)
|
||
return false;
|
||
|
||
bool ok = true;
|
||
bool arg = fc > 1;
|
||
if (!arg)
|
||
framecount = 1;
|
||
for (int i = 0; i < framecount; i++) {
|
||
QString r;
|
||
r.sprintf("%04d", i);
|
||
if (maskonly) {
|
||
if (!img[i]->collision_mask)
|
||
img[i]->collision_mask = new QImage();
|
||
img[i]->collision_mask->load(
|
||
arg ? datafilenamepattern.arg(r) : datafilenamepattern);
|
||
ok = ok
|
||
&& !img[i]->collision_mask->isNull()
|
||
&& img[i]->collision_mask->depth() == 1;
|
||
} else {
|
||
img[i] = new QtCanvasPixmap(
|
||
arg ? datafilenamepattern.arg(r) : datafilenamepattern);
|
||
ok = ok && !img[i]->isNull();
|
||
}
|
||
}
|
||
if (!ok) {
|
||
reset();
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
/*
|
||
\obsolete
|
||
|
||
Use isValid() instead.
|
||
|
||
This returns false if the array is valid, and true if it is not.
|
||
*/
|
||
bool QtCanvasPixmapArray::operator!()
|
||
{
|
||
return img == 0;
|
||
}
|
||
|
||
/*
|
||
Returns true if the pixmap array is valid; otherwise returns
|
||
false.
|
||
*/
|
||
bool QtCanvasPixmapArray::isValid() const
|
||
{
|
||
return (img != 0);
|
||
}
|
||
|
||
/*
|
||
\fn QtCanvasPixmap* QtCanvasPixmapArray::image(int i) const
|
||
|
||
Returns pixmap \a i in the array, if \a i is non-negative and less
|
||
than than count(), and returns an unspecified value otherwise.
|
||
*/
|
||
|
||
// ### wouldn't it be better to put empty QtCanvasPixmaps in there instead of
|
||
// initializing the additional elements in the array to 0? Lars
|
||
/*
|
||
Replaces the pixmap at index \a i with pixmap \a p.
|
||
|
||
The array takes ownership of \a p and will delete \a p when the
|
||
array itself is deleted.
|
||
|
||
If \a i is beyond the end of the array the array is extended to at
|
||
least i+1 elements, with elements count() to i-1 being initialized
|
||
to 0.
|
||
*/
|
||
void QtCanvasPixmapArray::setImage(int i, QtCanvasPixmap* p)
|
||
{
|
||
if (i >= framecount) {
|
||
QtCanvasPixmap** newimg = new QtCanvasPixmap*[i+1];
|
||
memcpy(newimg, img, sizeof(QtCanvasPixmap *)*framecount);
|
||
memset(newimg + framecount, 0, sizeof(QtCanvasPixmap *)*(i+1 - framecount));
|
||
framecount = i+1;
|
||
delete [] img;
|
||
img = newimg;
|
||
}
|
||
delete img[i]; img[i] = p;
|
||
}
|
||
|
||
/*
|
||
\fn uint QtCanvasPixmapArray::count() const
|
||
|
||
Returns the number of pixmaps in the array.
|
||
*/
|
||
|
||
/*
|
||
Returns the x-coordinate of the current left edge of the sprite.
|
||
(This may change as the sprite animates since different frames may
|
||
have different left edges.)
|
||
|
||
\sa rightEdge() bottomEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::leftEdge() const
|
||
{
|
||
return int(x()) - image()->hotx;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns what the x-coordinate of the left edge of the sprite would
|
||
be if the sprite (actually its hotspot) were moved to x-position
|
||
\a nx.
|
||
|
||
\sa rightEdge() bottomEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::leftEdge(int nx) const
|
||
{
|
||
return nx - image()->hotx;
|
||
}
|
||
|
||
/*
|
||
Returns the y-coordinate of the top edge of the sprite. (This may
|
||
change as the sprite animates since different frames may have
|
||
different top edges.)
|
||
|
||
\sa leftEdge() rightEdge() bottomEdge()
|
||
*/
|
||
int QtCanvasSprite::topEdge() const
|
||
{
|
||
return int(y()) - image()->hoty;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns what the y-coordinate of the top edge of the sprite would
|
||
be if the sprite (actually its hotspot) were moved to y-position
|
||
\a ny.
|
||
|
||
\sa leftEdge() rightEdge() bottomEdge()
|
||
*/
|
||
int QtCanvasSprite::topEdge(int ny) const
|
||
{
|
||
return ny - image()->hoty;
|
||
}
|
||
|
||
/*
|
||
Returns the x-coordinate of the current right edge of the sprite.
|
||
(This may change as the sprite animates since different frames may
|
||
have different right edges.)
|
||
|
||
\sa leftEdge() bottomEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::rightEdge() const
|
||
{
|
||
return leftEdge() + image()->width()-1;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns what the x-coordinate of the right edge of the sprite
|
||
would be if the sprite (actually its hotspot) were moved to
|
||
x-position \a nx.
|
||
|
||
\sa leftEdge() bottomEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::rightEdge(int nx) const
|
||
{
|
||
return leftEdge(nx) + image()->width()-1;
|
||
}
|
||
|
||
/*
|
||
Returns the y-coordinate of the current bottom edge of the sprite.
|
||
(This may change as the sprite animates since different frames may
|
||
have different bottom edges.)
|
||
|
||
\sa leftEdge() rightEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::bottomEdge() const
|
||
{
|
||
return topEdge() + image()->height()-1;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Returns what the y-coordinate of the top edge of the sprite would
|
||
be if the sprite (actually its hotspot) were moved to y-position
|
||
\a ny.
|
||
|
||
\sa leftEdge() rightEdge() topEdge()
|
||
*/
|
||
int QtCanvasSprite::bottomEdge(int ny) const
|
||
{
|
||
return topEdge(ny) + image()->height()-1;
|
||
}
|
||
|
||
/*
|
||
\fn QtCanvasPixmap* QtCanvasSprite::image() const
|
||
|
||
Returns the current frame's image.
|
||
|
||
\sa frame(), setFrame()
|
||
*/
|
||
|
||
/*
|
||
\fn QtCanvasPixmap* QtCanvasSprite::image(int f) const
|
||
\overload
|
||
|
||
Returns the image for frame \a f. Does not do any bounds checking on \a f.
|
||
*/
|
||
|
||
/*
|
||
Returns the image the sprite \e will have after advance(1) is
|
||
called. By default this is the same as image().
|
||
*/
|
||
QtCanvasPixmap* QtCanvasSprite::imageAdvanced() const
|
||
{
|
||
return image();
|
||
}
|
||
|
||
/*
|
||
Returns the bounding rectangle for the image in the sprite's
|
||
current frame. This assumes that the images are tightly cropped
|
||
(i.e. do not have transparent pixels all along a side).
|
||
*/
|
||
QRect QtCanvasSprite::boundingRect() const
|
||
{
|
||
return QRect(leftEdge(), topEdge(), width(), height());
|
||
}
|
||
|
||
|
||
/*
|
||
\internal
|
||
Returns the chunks covered by the item.
|
||
*/
|
||
QPolygon QtCanvasItem::chunks() const
|
||
{
|
||
QPolygon r;
|
||
int n = 0;
|
||
QRect br = boundingRect();
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
br &= QRect(0, 0, canvas()->width(), canvas()->height());
|
||
if (br.isValid()) {
|
||
r.resize((br.width()/chunksize+2)*(br.height()/chunksize+2));
|
||
for (int j = br.top()/chunksize; j <= br.bottom()/chunksize; j++) {
|
||
for (int i = br.left()/chunksize; i <= br.right()/chunksize; i++) {
|
||
r[n++] = QPoint(i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
r.resize(n);
|
||
return r;
|
||
}
|
||
|
||
|
||
/*
|
||
\internal
|
||
Add the sprite to the chunks in its QtCanvas which it overlaps.
|
||
*/
|
||
void QtCanvasSprite::addToChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = topEdge()/chunksize; j <= bottomEdge()/chunksize; j++) {
|
||
for (int i = leftEdge()/chunksize; i <= rightEdge()/chunksize; i++) {
|
||
canvas()->addItemToChunk(this, i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
Remove the sprite from the chunks in its QtCanvas which it overlaps.
|
||
|
||
\sa addToChunks()
|
||
*/
|
||
void QtCanvasSprite::removeFromChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = topEdge()/chunksize; j <= bottomEdge()/chunksize; j++) {
|
||
for (int i = leftEdge()/chunksize; i <= rightEdge()/chunksize; i++) {
|
||
canvas()->removeItemFromChunk(this, i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
The width of the sprite for the current frame's image.
|
||
|
||
\sa frame()
|
||
*/
|
||
//### mark: Why don't we have width(int) and height(int) to be
|
||
//consistent with leftEdge() and leftEdge(int)?
|
||
int QtCanvasSprite::width() const
|
||
{
|
||
return image()->width();
|
||
}
|
||
|
||
/*
|
||
The height of the sprite for the current frame's image.
|
||
|
||
\sa frame()
|
||
*/
|
||
int QtCanvasSprite::height() const
|
||
{
|
||
return image()->height();
|
||
}
|
||
|
||
|
||
/*
|
||
Draws the current frame's image at the sprite's current position
|
||
on painter \a painter.
|
||
*/
|
||
void QtCanvasSprite::draw(QPainter& painter)
|
||
{
|
||
painter.drawPixmap(leftEdge(), topEdge(), *image());
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasView qtcanvas.h
|
||
\brief The QtCanvasView class provides an on-screen view of a QtCanvas.
|
||
|
||
A QtCanvasView is widget which provides a view of a QtCanvas.
|
||
|
||
If you want users to be able to interact with a canvas view,
|
||
subclass QtCanvasView. You might then reimplement
|
||
QtScrollView::contentsMousePressEvent(). For example:
|
||
|
||
\code
|
||
void MyCanvasView::contentsMousePressEvent(QMouseEvent* e)
|
||
{
|
||
QtCanvasItemList l = canvas()->collisions(e->pos());
|
||
for (QtCanvasItemList::Iterator it = l.begin(); it!= l.end(); ++it) {
|
||
if ((*it)->rtti() == QtCanvasRectangle::RTTI)
|
||
qDebug("A QtCanvasRectangle lies somewhere at this point");
|
||
}
|
||
}
|
||
\endcode
|
||
|
||
The canvas view shows canvas canvas(); this can be changed using
|
||
setCanvas().
|
||
|
||
A transformation matrix can be used to transform the view of the
|
||
canvas in various ways, for example, zooming in or out or rotating.
|
||
For example:
|
||
|
||
\code
|
||
QMatrix wm;
|
||
wm.scale(2, 2); // Zooms in by 2 times
|
||
wm.rotate(90); // Rotates 90 degrees counter clockwise
|
||
// around the origin.
|
||
wm.translate(0, -canvas->height());
|
||
// moves the canvas down so what was visible
|
||
// before is still visible.
|
||
myCanvasView->setWorldMatrix(wm);
|
||
\endcode
|
||
|
||
Use setWorldMatrix() to set the canvas view's world matrix: you must
|
||
ensure that the world matrix is invertible. The current world matrix
|
||
is retrievable with worldMatrix(), and its inversion is retrievable
|
||
with inverseWorldMatrix().
|
||
|
||
Example:
|
||
|
||
The following code finds the part of the canvas that is visible in
|
||
this view, i.e. the bounding rectangle of the view in canvas coordinates.
|
||
|
||
\code
|
||
QRect rc = QRect(myCanvasView->contentsX(), myCanvasView->contentsY(),
|
||
myCanvasView->visibleWidth(), myCanvasView->visibleHeight());
|
||
QRect canvasRect = myCanvasView->inverseWorldMatrix().mapRect(rc);
|
||
\endcode
|
||
|
||
\sa QMatrix QPainter::setWorldMatrix()
|
||
|
||
*/
|
||
|
||
class QtCanvasWidget : public QWidget
|
||
{
|
||
public:
|
||
QtCanvasWidget(QtCanvasView *view) : QWidget(view) { m_view = view; }
|
||
protected:
|
||
void paintEvent(QPaintEvent *e);
|
||
void mousePressEvent(QMouseEvent *e) {
|
||
m_view->contentsMousePressEvent(e);
|
||
}
|
||
void mouseMoveEvent(QMouseEvent *e) {
|
||
m_view->contentsMouseMoveEvent(e);
|
||
}
|
||
void mouseReleaseEvent(QMouseEvent *e) {
|
||
m_view->contentsMouseReleaseEvent(e);
|
||
}
|
||
void mouseDoubleClickEvent(QMouseEvent *e) {
|
||
m_view->contentsMouseDoubleClickEvent(e);
|
||
}
|
||
void dragEnterEvent(QDragEnterEvent *e) {
|
||
m_view->contentsDragEnterEvent(e);
|
||
}
|
||
void dragMoveEvent(QDragMoveEvent *e) {
|
||
m_view->contentsDragMoveEvent(e);
|
||
}
|
||
void dragLeaveEvent(QDragLeaveEvent *e) {
|
||
m_view->contentsDragLeaveEvent(e);
|
||
}
|
||
void dropEvent(QDropEvent *e) {
|
||
m_view->contentsDropEvent(e);
|
||
}
|
||
void wheelEvent(QWheelEvent *e) {
|
||
m_view->contentsWheelEvent(e);
|
||
}
|
||
void contextMenuEvent(QContextMenuEvent *e) {
|
||
m_view->contentsContextMenuEvent(e);
|
||
}
|
||
|
||
QtCanvasView *m_view;
|
||
};
|
||
|
||
void QtCanvasWidget::paintEvent(QPaintEvent *e)
|
||
{
|
||
QPainter p(this);
|
||
if (m_view->d->highQuality) {
|
||
p.setRenderHint(QPainter::Antialiasing);
|
||
p.setRenderHint(QPainter::SmoothPixmapTransform);
|
||
}
|
||
m_view->drawContents(&p, e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height());
|
||
}
|
||
|
||
/*
|
||
Constructs a QtCanvasView with parent \a parent. The canvas view
|
||
is not associated with a canvas, so you must to call setCanvas()
|
||
to view a canvas.
|
||
*/
|
||
QtCanvasView::QtCanvasView(QWidget* parent)
|
||
: QScrollArea(parent)
|
||
{
|
||
d = new QtCanvasViewData;
|
||
setWidget(new QtCanvasWidget(this));
|
||
d->highQuality = false;
|
||
viewing = 0;
|
||
setCanvas(0);
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
Constructs a QtCanvasView which views canvas \a canvas, with parent
|
||
\a parent.
|
||
*/
|
||
QtCanvasView::QtCanvasView(QtCanvas* canvas, QWidget* parent)
|
||
: QScrollArea(parent)
|
||
{
|
||
d = new QtCanvasViewData;
|
||
d->highQuality = false;
|
||
setWidget(new QtCanvasWidget(this));
|
||
viewing = 0;
|
||
setCanvas(canvas);
|
||
}
|
||
|
||
/*
|
||
Destroys the canvas view. The associated canvas is \e not deleted.
|
||
*/
|
||
QtCanvasView::~QtCanvasView()
|
||
{
|
||
delete d;
|
||
d = 0;
|
||
setCanvas(0);
|
||
}
|
||
|
||
/*
|
||
\property QtCanvasView::highQualityRendering
|
||
\brief whether high quality rendering is turned on
|
||
|
||
If high quality rendering is turned on, the canvas view will paint itself
|
||
using the QPainter::Antialiasing and QPainter::SmoothPixmapTransform
|
||
rendering flags.
|
||
|
||
Enabling these flag will usually improve the visual appearance on the screen
|
||
at the cost of rendering speed.
|
||
*/
|
||
bool QtCanvasView::highQualityRendering() const
|
||
{
|
||
return d->highQuality;
|
||
}
|
||
|
||
void QtCanvasView::setHighQualityRendering(bool enable)
|
||
{
|
||
d->highQuality = enable;
|
||
widget()->update();
|
||
}
|
||
|
||
|
||
void QtCanvasView::contentsMousePressEvent(QMouseEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
void QtCanvasView::contentsMouseReleaseEvent(QMouseEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
void QtCanvasView::contentsMouseDoubleClickEvent(QMouseEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
void QtCanvasView::contentsMouseMoveEvent(QMouseEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
void QtCanvasView::contentsDragEnterEvent(QDragEnterEvent *)
|
||
{
|
||
}
|
||
|
||
void QtCanvasView::contentsDragMoveEvent(QDragMoveEvent *)
|
||
{
|
||
}
|
||
|
||
void QtCanvasView::contentsDragLeaveEvent(QDragLeaveEvent *)
|
||
{
|
||
}
|
||
|
||
void QtCanvasView::contentsDropEvent(QDropEvent *)
|
||
{
|
||
}
|
||
|
||
void QtCanvasView::contentsWheelEvent(QWheelEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
void QtCanvasView::contentsContextMenuEvent(QContextMenuEvent *e)
|
||
{
|
||
e->ignore();
|
||
}
|
||
|
||
/*
|
||
\fn QtCanvas* QtCanvasView::canvas() const
|
||
|
||
Returns a pointer to the canvas which the QtCanvasView is currently
|
||
showing.
|
||
*/
|
||
|
||
|
||
/*
|
||
Sets the canvas that the QtCanvasView is showing to the canvas \a
|
||
canvas.
|
||
*/
|
||
void QtCanvasView::setCanvas(QtCanvas* canvas)
|
||
{
|
||
if (viewing == canvas)
|
||
return;
|
||
|
||
if (viewing) {
|
||
disconnect(viewing);
|
||
viewing->removeView(this);
|
||
}
|
||
viewing = canvas;
|
||
if (viewing) {
|
||
connect(viewing, SIGNAL(resized()), this, SLOT(updateContentsSize()));
|
||
viewing->addView(this);
|
||
}
|
||
if (d) // called by d'tor
|
||
updateContentsSize();
|
||
update();
|
||
}
|
||
|
||
/*
|
||
Returns a reference to the canvas view's current transformation matrix.
|
||
|
||
\sa setWorldMatrix() inverseWorldMatrix()
|
||
*/
|
||
const QMatrix &QtCanvasView::worldMatrix() const
|
||
{
|
||
return d->xform;
|
||
}
|
||
|
||
/*
|
||
Returns a reference to the inverse of the canvas view's current
|
||
transformation matrix.
|
||
|
||
\sa setWorldMatrix() worldMatrix()
|
||
*/
|
||
const QMatrix &QtCanvasView::inverseWorldMatrix() const
|
||
{
|
||
return d->ixform;
|
||
}
|
||
|
||
/*
|
||
Sets the transformation matrix of the QtCanvasView to \a wm. The
|
||
matrix must be invertible (i.e. if you create a world matrix that
|
||
zooms out by 2 times, then the inverse of this matrix is one that
|
||
will zoom in by 2 times).
|
||
|
||
When you use this, you should note that the performance of the
|
||
QtCanvasView will decrease considerably.
|
||
|
||
Returns false if \a wm is not invertable; otherwise returns true.
|
||
|
||
\sa worldMatrix() inverseWorldMatrix() QMatrix::isInvertible()
|
||
*/
|
||
bool QtCanvasView::setWorldMatrix(const QMatrix & wm)
|
||
{
|
||
bool ok = wm.isInvertible();
|
||
if (ok) {
|
||
d->xform = wm;
|
||
d->ixform = wm.inverted();
|
||
updateContentsSize();
|
||
widget()->update();
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
void QtCanvasView::updateContentsSize()
|
||
{
|
||
if (viewing) {
|
||
QRect br;
|
||
br = d->xform.mapRect(QRect(0, 0, viewing->width(), viewing->height()));
|
||
|
||
widget()->resize(br.size());
|
||
} else {
|
||
widget()->resize(size());
|
||
}
|
||
}
|
||
|
||
/*
|
||
Repaints part of the QtCanvas that the canvas view is showing
|
||
starting at \a cx by \a cy, with a width of \a cw and a height of \a
|
||
ch using the painter \a p.
|
||
*/
|
||
void QtCanvasView::drawContents(QPainter *p, int cx, int cy, int cw, int ch)
|
||
{
|
||
if (!viewing)
|
||
return;
|
||
QPainterPath clipPath;
|
||
clipPath.addRect(viewing->rect());
|
||
p->setClipPath(d->xform.map(clipPath), Qt::IntersectClip);
|
||
viewing->drawViewArea(this, p, QRect(cx, cy, cw, ch), false);
|
||
}
|
||
|
||
/*
|
||
Suggests a size sufficient to view the entire canvas.
|
||
*/
|
||
QSize QtCanvasView::sizeHint() const
|
||
{
|
||
if (!canvas())
|
||
return QScrollArea::sizeHint();
|
||
// should maybe take transformations into account
|
||
return (canvas()->size() + 2 * QSize(frameWidth(), frameWidth()))
|
||
.boundedTo(3 * QApplication::desktop()->size() / 4);
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasPolygonalItem qtcanvas.h
|
||
\brief The QtCanvasPolygonalItem class provides a polygonal canvas item
|
||
on a QtCanvas.
|
||
|
||
The mostly rectangular classes, such as QtCanvasSprite and
|
||
QtCanvasText, use the object's bounding rectangle for movement,
|
||
repainting and collision calculations. For most other items, the
|
||
bounding rectangle can be far too large -- a diagonal line being
|
||
the worst case, and there are many other cases which are also bad.
|
||
QtCanvasPolygonalItem provides polygon-based bounding rectangle
|
||
handling, etc., which is much faster for non-rectangular items.
|
||
|
||
Derived classes should try to define as small an area as possible
|
||
to maximize efficiency, but the polygon must \e definitely be
|
||
contained completely within the polygonal area. Calculating the
|
||
exact requirements is usually difficult, but if you allow a small
|
||
overestimate it can be easy and quick, while still getting almost
|
||
all of QtCanvasPolygonalItem's speed.
|
||
|
||
Note that all subclasses \e must call hide() in their destructor
|
||
since hide() needs to be able to access areaPoints().
|
||
|
||
Normally, QtCanvasPolygonalItem uses the odd-even algorithm for
|
||
determining whether an object intersects this object. You can
|
||
change this to the winding algorithm using setWinding().
|
||
|
||
The bounding rectangle is available using boundingRect(). The
|
||
points bounding the polygonal item are retrieved with
|
||
areaPoints(). Use areaPointsAdvanced() to retrieve the bounding
|
||
points the polygonal item \e will have after
|
||
QtCanvasItem::advance(1) has been called.
|
||
|
||
If the shape of the polygonal item is about to change while the
|
||
item is visible, call invalidate() before updating with a
|
||
different result from \l areaPoints().
|
||
|
||
By default, QtCanvasPolygonalItem objects have a black pen and no
|
||
brush (the default QPen and QBrush constructors). You can change
|
||
this with setPen() and setBrush(), but note that some
|
||
QtCanvasPolygonalItem subclasses only use the brush, ignoring the
|
||
pen setting.
|
||
|
||
The polygonal item can be drawn on a painter with draw().
|
||
Subclasses must reimplement drawShape() to draw themselves.
|
||
|
||
Like any other canvas item polygonal items can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting coordinates
|
||
with QtCanvasItem::setX(), QtCanvasItem::setY() and QtCanvasItem::setZ().
|
||
|
||
*/
|
||
|
||
|
||
/*
|
||
Since most polygonal items don't have a pen, the default is
|
||
NoPen and a black brush.
|
||
*/
|
||
static const QPen& defaultPolygonPen()
|
||
{
|
||
static QPen* dp = 0;
|
||
if (!dp)
|
||
dp = new QPen;
|
||
return *dp;
|
||
}
|
||
|
||
static const QBrush& defaultPolygonBrush()
|
||
{
|
||
static QBrush* db = 0;
|
||
if (!db)
|
||
db = new QBrush;
|
||
return *db;
|
||
}
|
||
|
||
/*
|
||
Constructs a QtCanvasPolygonalItem on the canvas \a canvas.
|
||
*/
|
||
QtCanvasPolygonalItem::QtCanvasPolygonalItem(QtCanvas* canvas) :
|
||
QtCanvasItem(canvas),
|
||
br(defaultPolygonBrush()),
|
||
pn(defaultPolygonPen())
|
||
{
|
||
wind = 0;
|
||
}
|
||
|
||
/*
|
||
Note that all subclasses \e must call hide() in their destructor
|
||
since hide() needs to be able to access areaPoints().
|
||
*/
|
||
QtCanvasPolygonalItem::~QtCanvasPolygonalItem()
|
||
{
|
||
}
|
||
|
||
/*
|
||
Returns true if the polygonal item uses the winding algorithm to
|
||
determine the "inside" of the polygon. Returns false if it uses
|
||
the odd-even algorithm.
|
||
|
||
The default is to use the odd-even algorithm.
|
||
|
||
\sa setWinding()
|
||
*/
|
||
bool QtCanvasPolygonalItem::winding() const
|
||
{
|
||
return wind;
|
||
}
|
||
|
||
/*
|
||
If \a enable is true, the polygonal item will use the winding
|
||
algorithm to determine the "inside" of the polygon; otherwise the
|
||
odd-even algorithm will be used.
|
||
|
||
The default is to use the odd-even algorithm.
|
||
|
||
\sa winding()
|
||
*/
|
||
void QtCanvasPolygonalItem::setWinding(bool enable)
|
||
{
|
||
wind = enable;
|
||
}
|
||
|
||
/*
|
||
Invalidates all information about the area covered by the canvas
|
||
item. The item will be updated automatically on the next call that
|
||
changes the item's status, for example, move() or update(). Call
|
||
this function if you are going to change the shape of the item (as
|
||
returned by areaPoints()) while the item is visible.
|
||
*/
|
||
void QtCanvasPolygonalItem::invalidate()
|
||
{
|
||
val = (uint)false;
|
||
removeFromChunks();
|
||
}
|
||
|
||
/*
|
||
\fn QtCanvasPolygonalItem::isValid() const
|
||
|
||
Returns true if the polygonal item's area information has not been
|
||
invalidated; otherwise returns false.
|
||
|
||
\sa invalidate()
|
||
*/
|
||
|
||
/*
|
||
Returns the points the polygonal item \e will have after
|
||
QtCanvasItem::advance(1) is called, i.e. what the points are when
|
||
advanced by the current xVelocity() and yVelocity().
|
||
*/
|
||
QPolygon QtCanvasPolygonalItem::areaPointsAdvanced() const
|
||
{
|
||
int dx = int(x()+xVelocity())-int(x());
|
||
int dy = int(y()+yVelocity())-int(y());
|
||
QPolygon r = areaPoints();
|
||
r.detach(); // Explicit sharing is stupid.
|
||
if (dx || dy)
|
||
r.translate(dx, dy);
|
||
return r;
|
||
}
|
||
|
||
//#define QCANVAS_POLYGONS_DEBUG
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
static QWidget* dbg_wid = 0;
|
||
static QPainter* dbg_ptr = 0;
|
||
#endif
|
||
|
||
class QPolygonalProcessor {
|
||
public:
|
||
QPolygonalProcessor(QtCanvas* c, const QPolygon& pa) :
|
||
canvas(c)
|
||
{
|
||
QRect pixelbounds = pa.boundingRect();
|
||
int cs = canvas->chunkSize();
|
||
QRect canvasbounds = pixelbounds.intersected(canvas->rect());
|
||
bounds.setLeft(canvasbounds.left()/cs);
|
||
bounds.setRight(canvasbounds.right()/cs);
|
||
bounds.setTop(canvasbounds.top()/cs);
|
||
bounds.setBottom(canvasbounds.bottom()/cs);
|
||
bitmap = QImage(bounds.width(), bounds.height(), QImage::Format_MonoLSB);
|
||
pnt = 0;
|
||
bitmap.fill(0);
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
dbg_start();
|
||
#endif
|
||
}
|
||
|
||
inline void add(int x, int y)
|
||
{
|
||
if (pnt >= (int)result.size()) {
|
||
result.resize(pnt*2+10);
|
||
}
|
||
result[pnt++] = QPoint(x+bounds.x(), y+bounds.y());
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) {
|
||
int cs = canvas->chunkSize();
|
||
QRect r(x*cs+bounds.x()*cs, y*cs+bounds.y()*cs, cs-1, cs-1);
|
||
dbg_ptr->setPen(Qt::blue);
|
||
dbg_ptr->drawRect(r);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
inline void addBits(int x1, int x2, uchar newbits, int xo, int yo)
|
||
{
|
||
for (int i = x1; i <= x2; i++)
|
||
if (newbits & (1 <<i))
|
||
add(xo+i, yo);
|
||
}
|
||
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
void dbg_start()
|
||
{
|
||
if (!dbg_wid) {
|
||
dbg_wid = new QWidget;
|
||
dbg_wid->resize(800, 600);
|
||
dbg_wid->show();
|
||
dbg_ptr = new QPainter(dbg_wid);
|
||
dbg_ptr->setBrush(Qt::NoBrush);
|
||
}
|
||
dbg_ptr->fillRect(dbg_wid->rect(), Qt::white);
|
||
}
|
||
#endif
|
||
|
||
void doSpans(int n, QPoint* pt, int* w)
|
||
{
|
||
int cs = canvas->chunkSize();
|
||
for (int j = 0; j < n; j++) {
|
||
int y = pt[j].y()/cs-bounds.y();
|
||
if (y >= bitmap.height() || y < 0) continue;
|
||
uchar* l = bitmap.scanLine(y);
|
||
int x = pt[j].x();
|
||
int x1 = x/cs-bounds.x();
|
||
if (x1 > bounds.width()) continue;
|
||
x1 = qMax(0,x1);
|
||
int x2 = (x+w[j])/cs-bounds.x();
|
||
if (x2 < 0) continue;
|
||
x2 = qMin(bounds.width(), x2);
|
||
int x1q = x1/8;
|
||
int x1r = x1%8;
|
||
int x2q = x2/8;
|
||
int x2r = x2%8;
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) dbg_ptr->setPen(Qt::yellow);
|
||
#endif
|
||
if (x1q == x2q) {
|
||
uchar newbits = (~l[x1q]) & (((2 <<(x2r-x1r))-1) <<x1r);
|
||
if (newbits) {
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) dbg_ptr->setPen(Qt::darkGreen);
|
||
#endif
|
||
addBits(x1r, x2r, newbits, x1q*8, y);
|
||
l[x1q] |= newbits;
|
||
}
|
||
} else {
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) dbg_ptr->setPen(Qt::blue);
|
||
#endif
|
||
uchar newbits1 = (~l[x1q]) & (0xff <<x1r);
|
||
if (newbits1) {
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) dbg_ptr->setPen(Qt::green);
|
||
#endif
|
||
addBits(x1r, 7, newbits1, x1q*8, y);
|
||
l[x1q] |= newbits1;
|
||
}
|
||
for (int i = x1q+1; i < x2q; i++) {
|
||
if (l[i] != 0xff) {
|
||
addBits(0, 7, ~l[i], i*8, y);
|
||
l[i] = 0xff;
|
||
}
|
||
}
|
||
uchar newbits2 = (~l[x2q]) & (0xff>>(7-x2r));
|
||
if (newbits2) {
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) dbg_ptr->setPen(Qt::red);
|
||
#endif
|
||
addBits(0, x2r, newbits2, x2q*8, y);
|
||
l[x2q] |= newbits2;
|
||
}
|
||
}
|
||
#ifdef QCANVAS_POLYGONS_DEBUG
|
||
if (dbg_ptr) {
|
||
dbg_ptr->drawLine(pt[j], pt[j]+QPoint(w[j], 0));
|
||
}
|
||
#endif
|
||
}
|
||
result.resize(pnt);
|
||
}
|
||
|
||
int pnt;
|
||
QPolygon result;
|
||
QtCanvas* canvas;
|
||
QRect bounds;
|
||
QImage bitmap;
|
||
};
|
||
|
||
|
||
QPolygon QtCanvasPolygonalItem::chunks() const
|
||
{
|
||
QPolygon pa = areaPoints();
|
||
|
||
if (!pa.size()) {
|
||
pa.detach(); // Explicit sharing is stupid.
|
||
return pa;
|
||
}
|
||
|
||
QPolygonalProcessor processor(canvas(), pa);
|
||
|
||
scanPolygon(pa, wind, processor);
|
||
|
||
return processor.result;
|
||
}
|
||
/*
|
||
Simply calls QtCanvasItem::chunks().
|
||
*/
|
||
QPolygon QtCanvasRectangle::chunks() const
|
||
{
|
||
// No need to do a polygon scan!
|
||
return QtCanvasItem::chunks();
|
||
}
|
||
|
||
/*
|
||
Returns the bounding rectangle of the polygonal item, based on
|
||
areaPoints().
|
||
*/
|
||
QRect QtCanvasPolygonalItem::boundingRect() const
|
||
{
|
||
return areaPoints().boundingRect();
|
||
}
|
||
|
||
/*
|
||
Reimplemented from QtCanvasItem, this draws the polygonal item by
|
||
setting the pen and brush for the item on the painter \a p and
|
||
calling drawShape().
|
||
*/
|
||
void QtCanvasPolygonalItem::draw(QPainter & p)
|
||
{
|
||
p.setPen(pn);
|
||
p.setBrush(br);
|
||
drawShape(p);
|
||
}
|
||
|
||
/*
|
||
\fn void QtCanvasPolygonalItem::drawShape(QPainter & p)
|
||
|
||
Subclasses must reimplement this function to draw their shape. The
|
||
pen and brush of \a p are already set to pen() and brush() prior
|
||
to calling this function.
|
||
|
||
\sa draw()
|
||
*/
|
||
|
||
/*
|
||
\fn QPen QtCanvasPolygonalItem::pen() const
|
||
|
||
Returns the QPen used to draw the outline of the item, if any.
|
||
|
||
\sa setPen()
|
||
*/
|
||
|
||
/*
|
||
\fn QBrush QtCanvasPolygonalItem::brush() const
|
||
|
||
Returns the QBrush used to fill the item, if filled.
|
||
|
||
\sa setBrush()
|
||
*/
|
||
|
||
/*
|
||
Sets the QPen used when drawing the item to the pen \a p.
|
||
Note that many QtCanvasPolygonalItems do not use the pen value.
|
||
|
||
\sa setBrush(), pen(), drawShape()
|
||
*/
|
||
void QtCanvasPolygonalItem::setPen(QPen p)
|
||
{
|
||
if (pn != p) {
|
||
removeFromChunks();
|
||
pn = p;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Sets the QBrush used when drawing the polygonal item to the brush \a b.
|
||
|
||
\sa setPen(), brush(), drawShape()
|
||
*/
|
||
void QtCanvasPolygonalItem::setBrush(QBrush b)
|
||
{
|
||
if (br != b) {
|
||
br = b;
|
||
changeChunks();
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
\class QtCanvasPolygon qtcanvas.h
|
||
\brief The QtCanvasPolygon class provides a polygon on a QtCanvas.
|
||
|
||
Paints a polygon with a QBrush. The polygon's points can be set in
|
||
the constructor or set or changed later using setPoints(). Use
|
||
points() to retrieve the points, or areaPoints() to retrieve the
|
||
points relative to the canvas's origin.
|
||
|
||
The polygon can be drawn on a painter with drawShape().
|
||
|
||
Like any other canvas item polygons can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting
|
||
coordinates with QtCanvasItem::setX(), QtCanvasItem::setY() and
|
||
QtCanvasItem::setZ().
|
||
|
||
Note: QtCanvasPolygon does not use the pen.
|
||
*/
|
||
|
||
/*
|
||
Constructs a point-less polygon on the canvas \a canvas. You
|
||
should call setPoints() before using it further.
|
||
*/
|
||
QtCanvasPolygon::QtCanvasPolygon(QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Destroys the polygon.
|
||
*/
|
||
QtCanvasPolygon::~QtCanvasPolygon()
|
||
{
|
||
hide();
|
||
}
|
||
|
||
/*
|
||
Draws the polygon using the painter \a p.
|
||
|
||
Note that QtCanvasPolygon does not support an outline (the pen is
|
||
always NoPen).
|
||
*/
|
||
void QtCanvasPolygon::drawShape(QPainter & p)
|
||
{
|
||
// ### why can't we draw outlines? We could use drawPolyline for it. Lars
|
||
// ### see other message. Warwick
|
||
|
||
p.setPen(NoPen); // since QRegion(QPolygon) excludes outline :-()-:
|
||
p.drawPolygon(poly);
|
||
}
|
||
|
||
/*
|
||
Sets the points of the polygon to be \a pa. These points will have
|
||
their x and y coordinates automatically translated by x(), y() as
|
||
the polygon is moved.
|
||
*/
|
||
void QtCanvasPolygon::setPoints(QPolygon pa)
|
||
{
|
||
removeFromChunks();
|
||
poly = pa;
|
||
poly.detach(); // Explicit sharing is stupid.
|
||
poly.translate((int)x(), (int)y());
|
||
addToChunks();
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
void QtCanvasPolygon::moveBy(double dx, double dy)
|
||
{
|
||
// Note: does NOT call QtCanvasPolygonalItem::moveBy(), since that
|
||
// only does half this work.
|
||
//
|
||
int idx = int(x()+dx)-int(x());
|
||
int idy = int(y()+dy)-int(y());
|
||
if (idx || idy) {
|
||
removeFromChunks();
|
||
poly.translate(idx, idy);
|
||
}
|
||
myx+= dx;
|
||
myy+= dy;
|
||
if (idx || idy) {
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasSpline qtcanvas.h
|
||
\brief The QtCanvasSpline class provides multi-bezier splines on a QtCanvas.
|
||
|
||
A QtCanvasSpline is a sequence of 4-point bezier curves joined
|
||
together to make a curved shape.
|
||
|
||
You set the control points of the spline with setControlPoints().
|
||
|
||
If the bezier is closed(), then the first control point will be
|
||
re-used as the last control point. Therefore, a closed bezier must
|
||
have a multiple of 3 control points and an open bezier must have
|
||
one extra point.
|
||
|
||
The beziers are not necessarily joined "smoothly". To ensure this,
|
||
set control points appropriately (general reference texts about
|
||
beziers will explain this in detail).
|
||
|
||
Like any other canvas item splines can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting
|
||
coordinates with QtCanvasItem::setX(), QtCanvasItem::setY() and
|
||
QtCanvasItem::setZ().
|
||
|
||
*/
|
||
|
||
/*
|
||
Create a spline with no control points on the canvas \a canvas.
|
||
|
||
\sa setControlPoints()
|
||
*/
|
||
QtCanvasSpline::QtCanvasSpline(QtCanvas* canvas) :
|
||
QtCanvasPolygon(canvas),
|
||
cl(true)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Destroy the spline.
|
||
*/
|
||
QtCanvasSpline::~QtCanvasSpline()
|
||
{
|
||
}
|
||
|
||
/*
|
||
Set the spline control points to \a ctrl.
|
||
|
||
If \a close is true, then the first point in \a ctrl will be
|
||
re-used as the last point, and the number of control points must
|
||
be a multiple of 3. If \a close is false, one additional control
|
||
point is required, and the number of control points must be one of
|
||
(4, 7, 10, 13, ...).
|
||
|
||
If the number of control points doesn't meet the above conditions,
|
||
the number of points will be truncated to the largest number of
|
||
points that do meet the requirement.
|
||
*/
|
||
void QtCanvasSpline::setControlPoints(QPolygon ctrl, bool close)
|
||
{
|
||
if ((int)ctrl.count() % 3 != (close ? 0 : 1)) {
|
||
qWarning("QtCanvasSpline::setControlPoints(): Number of points doesn't fit.");
|
||
int numCurves = (ctrl.count() - (close ? 0 : 1))/ 3;
|
||
ctrl.resize(numCurves*3 + (close ? 0 : 1));
|
||
}
|
||
|
||
cl = close;
|
||
bez = ctrl;
|
||
recalcPoly();
|
||
}
|
||
|
||
/*
|
||
Returns the current set of control points.
|
||
|
||
\sa setControlPoints(), closed()
|
||
*/
|
||
QPolygon QtCanvasSpline::controlPoints() const
|
||
{
|
||
return bez;
|
||
}
|
||
|
||
/*
|
||
Returns true if the control points are a closed set; otherwise
|
||
returns false.
|
||
*/
|
||
bool QtCanvasSpline::closed() const
|
||
{
|
||
return cl;
|
||
}
|
||
|
||
void QtCanvasSpline::recalcPoly()
|
||
{
|
||
if (bez.count() == 0)
|
||
return;
|
||
|
||
QPainterPath path;
|
||
path.moveTo(bez[0]);
|
||
for (int i = 1; i < (int)bez.count()-1; i+= 3) {
|
||
path.cubicTo(bez[i], bez[i+1], cl ? bez[(i+2)%bez.size()] : bez[i+2]);
|
||
}
|
||
QPolygon p = path.toFillPolygon().toPolygon();
|
||
QtCanvasPolygon::setPoints(p);
|
||
}
|
||
|
||
/*
|
||
\fn QPolygon QtCanvasPolygonalItem::areaPoints() const
|
||
|
||
This function must be reimplemented by subclasses. It \e must
|
||
return the points bounding (i.e. outside and not touching) the
|
||
shape or drawing errors will occur.
|
||
*/
|
||
|
||
/*
|
||
\fn QPolygon QtCanvasPolygon::points() const
|
||
|
||
Returns the vertices of the polygon, not translated by the position.
|
||
|
||
\sa setPoints(), areaPoints()
|
||
*/
|
||
QPolygon QtCanvasPolygon::points() const
|
||
{
|
||
QPolygon pa = areaPoints();
|
||
pa.translate(int(-x()), int(-y()));
|
||
return pa;
|
||
}
|
||
|
||
/*
|
||
Returns the vertices of the polygon translated by the polygon's
|
||
current x(), y() position, i.e. relative to the canvas's origin.
|
||
|
||
\sa setPoints(), points()
|
||
*/
|
||
QPolygon QtCanvasPolygon::areaPoints() const
|
||
{
|
||
return poly;
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasLine qtcanvas.h
|
||
\brief The QtCanvasLine class provides a line on a QtCanvas.
|
||
|
||
The line inherits functionality from QtCanvasPolygonalItem, for
|
||
example the setPen() function. The start and end points of the
|
||
line are set with setPoints().
|
||
|
||
Like any other canvas item lines can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting
|
||
coordinates with QtCanvasItem::setX(), QtCanvasItem::setY() and
|
||
QtCanvasItem::setZ().
|
||
*/
|
||
|
||
/*
|
||
Constructs a line from (0, 0) to (0, 0) on \a canvas.
|
||
|
||
\sa setPoints()
|
||
*/
|
||
QtCanvasLine::QtCanvasLine(QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas)
|
||
{
|
||
x1 = y1 = x2 = y2 = 0;
|
||
}
|
||
|
||
/*
|
||
Destroys the line.
|
||
*/
|
||
QtCanvasLine::~QtCanvasLine()
|
||
{
|
||
hide();
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
void QtCanvasLine::setPen(QPen p)
|
||
{
|
||
QtCanvasPolygonalItem::setPen(p);
|
||
}
|
||
|
||
/*
|
||
\fn QPoint QtCanvasLine::startPoint () const
|
||
|
||
Returns the start point of the line.
|
||
|
||
\sa setPoints(), endPoint()
|
||
*/
|
||
|
||
/*
|
||
\fn QPoint QtCanvasLine::endPoint () const
|
||
|
||
Returns the end point of the line.
|
||
|
||
\sa setPoints(), startPoint()
|
||
*/
|
||
|
||
/*
|
||
Sets the line's start point to (\a xa, \a ya) and its end point to
|
||
(\a xb, \a yb).
|
||
*/
|
||
void QtCanvasLine::setPoints(int xa, int ya, int xb, int yb)
|
||
{
|
||
if (x1 != xa || x2 != xb || y1 != ya || y2 != yb) {
|
||
removeFromChunks();
|
||
x1 = xa;
|
||
y1 = ya;
|
||
x2 = xb;
|
||
y2 = yb;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
void QtCanvasLine::drawShape(QPainter &p)
|
||
{
|
||
p.drawLine((int)(x()+x1), (int)(y()+y1), (int)(x()+x2), (int)(y()+y2));
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
|
||
Note that the area defined by the line is somewhat thicker than
|
||
the line that is actually drawn.
|
||
*/
|
||
QPolygon QtCanvasLine::areaPoints() const
|
||
{
|
||
QPolygon p(4);
|
||
int xi = int(x());
|
||
int yi = int(y());
|
||
int pw = pen().width();
|
||
int dx = qAbs(x1-x2);
|
||
int dy = qAbs(y1-y2);
|
||
pw = pw*4/3+2; // approx pw*sqrt(2)
|
||
int px = x1 < x2 ? -pw : pw ;
|
||
int py = y1 < y2 ? -pw : pw ;
|
||
if (dx && dy && (dx > dy ? (dx*2/dy <= 2) : (dy*2/dx <= 2))) {
|
||
// steep
|
||
if (px == py) {
|
||
p[0] = QPoint(x1+xi, y1+yi+py);
|
||
p[1] = QPoint(x2+xi-px, y2+yi);
|
||
p[2] = QPoint(x2+xi, y2+yi-py);
|
||
p[3] = QPoint(x1+xi+px, y1+yi);
|
||
} else {
|
||
p[0] = QPoint(x1+xi+px, y1+yi);
|
||
p[1] = QPoint(x2+xi, y2+yi-py);
|
||
p[2] = QPoint(x2+xi-px, y2+yi);
|
||
p[3] = QPoint(x1+xi, y1+yi+py);
|
||
}
|
||
} else if (dx > dy) {
|
||
// horizontal
|
||
p[0] = QPoint(x1+xi+px, y1+yi+py);
|
||
p[1] = QPoint(x2+xi-px, y2+yi+py);
|
||
p[2] = QPoint(x2+xi-px, y2+yi-py);
|
||
p[3] = QPoint(x1+xi+px, y1+yi-py);
|
||
} else {
|
||
// vertical
|
||
p[0] = QPoint(x1+xi+px, y1+yi+py);
|
||
p[1] = QPoint(x2+xi+px, y2+yi-py);
|
||
p[2] = QPoint(x2+xi-px, y2+yi-py);
|
||
p[3] = QPoint(x1+xi-px, y1+yi+py);
|
||
}
|
||
return p;
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
|
||
*/
|
||
|
||
void QtCanvasLine::moveBy(double dx, double dy)
|
||
{
|
||
QtCanvasPolygonalItem::moveBy(dx, dy);
|
||
}
|
||
|
||
/*
|
||
\class QtCanvasRectangle qtcanvas.h
|
||
\brief The QtCanvasRectangle class provides a rectangle on a QtCanvas.
|
||
|
||
This item paints a single rectangle which may have any pen() and
|
||
brush(), but may not be tilted/rotated. For rotated rectangles,
|
||
use QtCanvasPolygon.
|
||
|
||
The rectangle's size and initial position can be set in the
|
||
constructor. The size can be set or changed later using setSize().
|
||
Use height() and width() to retrieve the rectangle's dimensions.
|
||
|
||
The rectangle can be drawn on a painter with drawShape().
|
||
|
||
Like any other canvas item rectangles can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting
|
||
coordinates with QtCanvasItem::setX(), QtCanvasItem::setY() and
|
||
QtCanvasItem::setZ().
|
||
|
||
*/
|
||
|
||
/*
|
||
Constructs a rectangle at position (0,0) with both width and
|
||
height set to 32 pixels on \a canvas.
|
||
*/
|
||
QtCanvasRectangle::QtCanvasRectangle(QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(32), h(32)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Constructs a rectangle positioned and sized by \a r on \a canvas.
|
||
*/
|
||
QtCanvasRectangle::QtCanvasRectangle(const QRect& r, QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(r.width()), h(r.height())
|
||
{
|
||
move(r.x(), r.y());
|
||
}
|
||
|
||
/*
|
||
Constructs a rectangle at position (\a x, \a y) and size \a width
|
||
by \a height, on \a canvas.
|
||
*/
|
||
QtCanvasRectangle::QtCanvasRectangle(int x, int y, int width, int height,
|
||
QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(width), h(height)
|
||
{
|
||
move(x, y);
|
||
}
|
||
|
||
/*
|
||
Destroys the rectangle.
|
||
*/
|
||
QtCanvasRectangle::~QtCanvasRectangle()
|
||
{
|
||
hide();
|
||
}
|
||
|
||
|
||
/*
|
||
Returns the width of the rectangle.
|
||
*/
|
||
int QtCanvasRectangle::width() const
|
||
{
|
||
return w;
|
||
}
|
||
|
||
/*
|
||
Returns the height of the rectangle.
|
||
*/
|
||
int QtCanvasRectangle::height() const
|
||
{
|
||
return h;
|
||
}
|
||
|
||
/*
|
||
Sets the \a width and \a height of the rectangle.
|
||
*/
|
||
void QtCanvasRectangle::setSize(int width, int height)
|
||
{
|
||
if (w != width || h != height) {
|
||
removeFromChunks();
|
||
w = width;
|
||
h = height;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\fn QSize QtCanvasRectangle::size() const
|
||
|
||
Returns the width() and height() of the rectangle.
|
||
|
||
\sa rect(), setSize()
|
||
*/
|
||
|
||
/*
|
||
\fn QRect QtCanvasRectangle::rect() const
|
||
|
||
Returns the integer-converted x(), y() position and size() of the
|
||
rectangle as a QRect.
|
||
*/
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
QPolygon QtCanvasRectangle::areaPoints() const
|
||
{
|
||
QPolygon pa(4);
|
||
int pw = (pen().width()+1)/2;
|
||
if (pw < 1) pw = 1;
|
||
if (pen() == NoPen) pw = 0;
|
||
pa[0] = QPoint((int)x()-pw, (int)y()-pw);
|
||
pa[1] = pa[0] + QPoint(w+pw*2, 0);
|
||
pa[2] = pa[1] + QPoint(0, h+pw*2);
|
||
pa[3] = pa[0] + QPoint(0, h+pw*2);
|
||
return pa;
|
||
}
|
||
|
||
/*
|
||
Draws the rectangle on painter \a p.
|
||
*/
|
||
void QtCanvasRectangle::drawShape(QPainter & p)
|
||
{
|
||
p.drawRect((int)x(), (int)y(), w, h);
|
||
}
|
||
|
||
|
||
/*
|
||
\class QtCanvasEllipse qtcanvas.h
|
||
\brief The QtCanvasEllipse class provides an ellipse or ellipse segment on a QtCanvas.
|
||
|
||
A canvas item that paints an ellipse or ellipse segment with a QBrush.
|
||
The ellipse's height, width, start angle and angle length can be set
|
||
at construction time. The size can be changed at runtime with
|
||
setSize(), and the angles can be changed (if you're displaying an
|
||
ellipse segment rather than a whole ellipse) with setAngles().
|
||
|
||
Note that angles are specified in 16ths of a degree.
|
||
|
||
\target anglediagram
|
||
\img qcanvasellipse.png Ellipse
|
||
|
||
If a start angle and length angle are set then an ellipse segment
|
||
will be drawn. The start angle is the angle that goes from zero in a
|
||
counter-clockwise direction (shown in green in the diagram). The
|
||
length angle is the angle from the start angle in a
|
||
counter-clockwise direction (shown in blue in the diagram). The blue
|
||
segment is the segment of the ellipse that would be drawn. If no
|
||
start angle and length angle are specified the entire ellipse is
|
||
drawn.
|
||
|
||
The ellipse can be drawn on a painter with drawShape().
|
||
|
||
Like any other canvas item ellipses can be moved with move() and
|
||
moveBy(), or by setting coordinates with setX(), setY() and setZ().
|
||
|
||
Note: QtCanvasEllipse does not use the pen.
|
||
*/
|
||
|
||
/*
|
||
Constructs a 32x32 ellipse, centered at (0, 0) on \a canvas.
|
||
*/
|
||
QtCanvasEllipse::QtCanvasEllipse(QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(32), h(32),
|
||
a1(0), a2(360*16)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Constructs a \a width by \a height pixel ellipse, centered at
|
||
(0, 0) on \a canvas.
|
||
*/
|
||
QtCanvasEllipse::QtCanvasEllipse(int width, int height, QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(width), h(height),
|
||
a1(0), a2(360*16)
|
||
{
|
||
}
|
||
|
||
// ### add a constructor taking degrees in float. 1/16 degrees is stupid. Lars
|
||
// ### it's how QPainter does it, so QtCanvas does too for consistency. If it's
|
||
// ### a good idea, it should be added to QPainter, not just to QtCanvas. Warwick
|
||
/*
|
||
Constructs a \a width by \a height pixel ellipse, centered at
|
||
(0, 0) on \a canvas. Only a segment of the ellipse is drawn,
|
||
starting at angle \a startangle, and extending for angle \a angle
|
||
(the angle length).
|
||
|
||
Note that angles are specified in sixteenths of a degree.
|
||
*/
|
||
QtCanvasEllipse::QtCanvasEllipse(int width, int height,
|
||
int startangle, int angle, QtCanvas* canvas) :
|
||
QtCanvasPolygonalItem(canvas),
|
||
w(width), h(height),
|
||
a1(startangle), a2(angle)
|
||
{
|
||
}
|
||
|
||
/*
|
||
Destroys the ellipse.
|
||
*/
|
||
QtCanvasEllipse::~QtCanvasEllipse()
|
||
{
|
||
hide();
|
||
}
|
||
|
||
/*
|
||
Returns the width of the ellipse.
|
||
*/
|
||
int QtCanvasEllipse::width() const
|
||
{
|
||
return w;
|
||
}
|
||
|
||
/*
|
||
Returns the height of the ellipse.
|
||
*/
|
||
int QtCanvasEllipse::height() const
|
||
{
|
||
return h;
|
||
}
|
||
|
||
/*
|
||
Sets the \a width and \a height of the ellipse.
|
||
*/
|
||
void QtCanvasEllipse::setSize(int width, int height)
|
||
{
|
||
if (w != width || h != height) {
|
||
removeFromChunks();
|
||
w = width;
|
||
h = height;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\fn int QtCanvasEllipse::angleStart() const
|
||
|
||
Returns the start angle in 16ths of a degree. Initially
|
||
this will be 0.
|
||
|
||
\sa setAngles(), angleLength()
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvasEllipse::angleLength() const
|
||
|
||
Returns the length angle (the extent of the ellipse segment) in
|
||
16ths of a degree. Initially this will be 360 * 16 (a complete
|
||
ellipse).
|
||
|
||
\sa setAngles(), angleStart()
|
||
*/
|
||
|
||
/*
|
||
Sets the angles for the ellipse. The start angle is \a start and
|
||
the extent of the segment is \a length (the angle length) from the
|
||
\a start. The angles are specified in 16ths of a degree. By
|
||
default the ellipse will start at 0 and have an angle length of
|
||
360 * 16 (a complete ellipse).
|
||
|
||
\sa angleStart(), angleLength()
|
||
*/
|
||
void QtCanvasEllipse::setAngles(int start, int length)
|
||
{
|
||
if (a1 != start || a2 != length) {
|
||
removeFromChunks();
|
||
a1 = start;
|
||
a2 = length;
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
QPolygon QtCanvasEllipse::areaPoints() const
|
||
{
|
||
QPainterPath path;
|
||
path.arcTo(QRectF(x()-w/2.0+0.5-1, y()-h/2.0+0.5-1, w+3, h+3), a1/16., a2/16.);
|
||
return path.toFillPolygon().toPolygon();
|
||
}
|
||
|
||
/*
|
||
Draws the ellipse, centered at x(), y() using the painter \a p.
|
||
|
||
Note that QtCanvasEllipse does not support an outline (the pen is
|
||
always NoPen).
|
||
*/
|
||
void QtCanvasEllipse::drawShape(QPainter & p)
|
||
{
|
||
p.setPen(NoPen); // since QRegion(QPolygon) excludes outline :-()-:
|
||
if (!a1 && a2 == 360*16) {
|
||
p.drawEllipse(int(x()-w/2.0+0.5), int(y()-h/2.0+0.5), w, h);
|
||
} else {
|
||
p.drawPie(int(x()-w/2.0+0.5), int(y()-h/2.0+0.5), w, h, a1, a2);
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
\class QtCanvasText
|
||
\brief The QtCanvasText class provides a text object on a QtCanvas.
|
||
|
||
A canvas text item has text with font, color and alignment
|
||
attributes. The text and font can be set in the constructor or set
|
||
or changed later with setText() and setFont(). The color is set
|
||
with setColor() and the alignment with setTextFlags(). The text
|
||
item's bounding rectangle is retrieved with boundingRect().
|
||
|
||
The text can be drawn on a painter with draw().
|
||
|
||
Like any other canvas item text items can be moved with
|
||
QtCanvasItem::move() and QtCanvasItem::moveBy(), or by setting
|
||
coordinates with QtCanvasItem::setX(), QtCanvasItem::setY() and
|
||
QtCanvasItem::setZ().
|
||
*/
|
||
|
||
/*
|
||
Constructs a QtCanvasText with the text "\<text\>", on \a canvas.
|
||
*/
|
||
QtCanvasText::QtCanvasText(QtCanvas* canvas) :
|
||
QtCanvasItem(canvas),
|
||
txt("<text>"), flags(0)
|
||
{
|
||
setRect();
|
||
}
|
||
|
||
// ### add textflags to the constructor? Lars
|
||
/*
|
||
Constructs a QtCanvasText with the text \a t, on canvas \a canvas.
|
||
*/
|
||
QtCanvasText::QtCanvasText(const QString& t, QtCanvas* canvas) :
|
||
QtCanvasItem(canvas),
|
||
txt(t), flags(0)
|
||
{
|
||
setRect();
|
||
}
|
||
|
||
// ### see above
|
||
/*
|
||
Constructs a QtCanvasText with the text \a t and font \a f, on the
|
||
canvas \a canvas.
|
||
*/
|
||
QtCanvasText::QtCanvasText(const QString& t, QFont f, QtCanvas* canvas) :
|
||
QtCanvasItem(canvas),
|
||
txt(t), flags(0),
|
||
fnt(f)
|
||
{
|
||
setRect();
|
||
}
|
||
|
||
/*
|
||
Destroys the canvas text item.
|
||
*/
|
||
QtCanvasText::~QtCanvasText()
|
||
{
|
||
removeFromChunks();
|
||
}
|
||
|
||
/*
|
||
Returns the bounding rectangle of the text.
|
||
*/
|
||
QRect QtCanvasText::boundingRect() const { return brect; }
|
||
|
||
void QtCanvasText::setRect()
|
||
{
|
||
brect = QFontMetrics(fnt).boundingRect(int(x()), int(y()), 0, 0, flags, txt);
|
||
}
|
||
|
||
/*
|
||
\fn int QtCanvasText::textFlags() const
|
||
|
||
Returns the currently set alignment flags.
|
||
|
||
\sa setTextFlags() Qt::AlignmentFlag Qt::TextFlag
|
||
*/
|
||
|
||
|
||
/*
|
||
Sets the alignment flags to \a f. These are a bitwise OR of the
|
||
flags available to QPainter::drawText() -- see the
|
||
\l{Qt::AlignmentFlag}s and \l{Qt::TextFlag}s.
|
||
|
||
\sa setFont() setColor()
|
||
*/
|
||
void QtCanvasText::setTextFlags(int f)
|
||
{
|
||
if (flags != f) {
|
||
removeFromChunks();
|
||
flags = f;
|
||
setRect();
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the text item's text.
|
||
|
||
\sa setText()
|
||
*/
|
||
QString QtCanvasText::text() const
|
||
{
|
||
return txt;
|
||
}
|
||
|
||
|
||
/*
|
||
Sets the text item's text to \a t. The text may contain newlines.
|
||
|
||
\sa text(), setFont(), setColor() setTextFlags()
|
||
*/
|
||
void QtCanvasText::setText(const QString& t)
|
||
{
|
||
if (txt != t) {
|
||
removeFromChunks();
|
||
txt = t;
|
||
setRect();
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the font in which the text is drawn.
|
||
|
||
\sa setFont()
|
||
*/
|
||
QFont QtCanvasText::font() const
|
||
{
|
||
return fnt;
|
||
}
|
||
|
||
/*
|
||
Sets the font in which the text is drawn to font \a f.
|
||
|
||
\sa font()
|
||
*/
|
||
void QtCanvasText::setFont(const QFont& f)
|
||
{
|
||
if (f != fnt) {
|
||
removeFromChunks();
|
||
fnt = f;
|
||
setRect();
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Returns the color of the text.
|
||
|
||
\sa setColor()
|
||
*/
|
||
QColor QtCanvasText::color() const
|
||
{
|
||
return col;
|
||
}
|
||
|
||
/*
|
||
Sets the color of the text to the color \a c.
|
||
|
||
\sa color(), setFont()
|
||
*/
|
||
void QtCanvasText::setColor(const QColor& c)
|
||
{
|
||
col = c;
|
||
changeChunks();
|
||
}
|
||
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
void QtCanvasText::moveBy(double dx, double dy)
|
||
{
|
||
int idx = int(x()+dx)-int(x());
|
||
int idy = int(y()+dy)-int(y());
|
||
if (idx || idy) {
|
||
removeFromChunks();
|
||
}
|
||
myx+= dx;
|
||
myy+= dy;
|
||
if (idx || idy) {
|
||
brect.translate(idx, idy);
|
||
addToChunks();
|
||
}
|
||
}
|
||
|
||
/*
|
||
Draws the text using the painter \a painter.
|
||
*/
|
||
void QtCanvasText::draw(QPainter& painter)
|
||
{
|
||
painter.setFont(fnt);
|
||
painter.setPen(col);
|
||
painter.drawText(painter.fontMetrics().boundingRect(int(x()), int(y()), 0, 0, flags, txt), flags, txt);
|
||
}
|
||
|
||
/*
|
||
\reimp
|
||
*/
|
||
void QtCanvasText::changeChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = brect.top()/chunksize; j <= brect.bottom()/chunksize; j++) {
|
||
for (int i = brect.left()/chunksize; i <= brect.right()/chunksize; i++) {
|
||
canvas()->setChangedChunk(i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Adds the text item to the appropriate chunks.
|
||
*/
|
||
void QtCanvasText::addToChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = brect.top()/chunksize; j <= brect.bottom()/chunksize; j++) {
|
||
for (int i = brect.left()/chunksize; i <= brect.right()/chunksize; i++) {
|
||
canvas()->addItemToChunk(this, i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Removes the text item from the appropriate chunks.
|
||
*/
|
||
void QtCanvasText::removeFromChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = brect.top()/chunksize; j <= brect.bottom()/chunksize; j++) {
|
||
for (int i = brect.left()/chunksize; i <= brect.right()/chunksize; i++) {
|
||
canvas()->removeItemFromChunk(this, i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
Returns 0 (QtCanvasItem::Rtti_Item).
|
||
|
||
Make your derived classes return their own values for rtti(), so
|
||
that you can distinguish between objects returned by
|
||
QtCanvas::at(). You should use values greater than 1000 to allow
|
||
for extensions to this class.
|
||
|
||
Overuse of this functionality can damage its extensibility. For
|
||
example, once you have identified a base class of a QtCanvasItem
|
||
found by QtCanvas::at(), cast it to that type and call meaningful
|
||
methods rather than acting upon the object based on its rtti
|
||
value.
|
||
|
||
For example:
|
||
|
||
\code
|
||
QtCanvasItem* item;
|
||
// Find an item, e.g. with QtCanvasItem::collisions().
|
||
...
|
||
if (item->rtti() == MySprite::RTTI) {
|
||
MySprite* s = (MySprite*)item;
|
||
if (s->isDamagable()) s->loseHitPoints(1000);
|
||
if (s->isHot()) myself->loseHitPoints(1000);
|
||
...
|
||
}
|
||
\endcode
|
||
*/
|
||
int QtCanvasItem::rtti() const { return RTTI; }
|
||
int QtCanvasItem::RTTI = Rtti_Item;
|
||
|
||
/*
|
||
Returns 1 (QtCanvasItem::Rtti_Sprite).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasSprite::rtti() const { return RTTI; }
|
||
int QtCanvasSprite::RTTI = Rtti_Sprite;
|
||
|
||
/*
|
||
Returns 2 (QtCanvasItem::Rtti_PolygonalItem).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasPolygonalItem::rtti() const { return RTTI; }
|
||
int QtCanvasPolygonalItem::RTTI = Rtti_PolygonalItem;
|
||
|
||
/*
|
||
Returns 3 (QtCanvasItem::Rtti_Text).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasText::rtti() const { return RTTI; }
|
||
int QtCanvasText::RTTI = Rtti_Text;
|
||
|
||
/*
|
||
Returns 4 (QtCanvasItem::Rtti_Polygon).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasPolygon::rtti() const { return RTTI; }
|
||
int QtCanvasPolygon::RTTI = Rtti_Polygon;
|
||
|
||
/*
|
||
Returns 5 (QtCanvasItem::Rtti_Rectangle).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasRectangle::rtti() const { return RTTI; }
|
||
int QtCanvasRectangle::RTTI = Rtti_Rectangle;
|
||
|
||
/*
|
||
Returns 6 (QtCanvasItem::Rtti_Ellipse).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasEllipse::rtti() const { return RTTI; }
|
||
int QtCanvasEllipse::RTTI = Rtti_Ellipse;
|
||
|
||
/*
|
||
Returns 7 (QtCanvasItem::Rtti_Line).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasLine::rtti() const { return RTTI; }
|
||
int QtCanvasLine::RTTI = Rtti_Line;
|
||
|
||
/*
|
||
Returns 8 (QtCanvasItem::Rtti_Spline).
|
||
|
||
\sa QtCanvasItem::rtti()
|
||
*/
|
||
int QtCanvasSpline::rtti() const { return RTTI; }
|
||
int QtCanvasSpline::RTTI = Rtti_Spline;
|
||
|
||
/*
|
||
Constructs a QtCanvasSprite which uses images from the
|
||
QtCanvasPixmapArray \a a.
|
||
|
||
The sprite in initially positioned at (0, 0) on \a canvas, using
|
||
frame 0.
|
||
*/
|
||
QtCanvasSprite::QtCanvasSprite(QtCanvasPixmapArray* a, QtCanvas* canvas) :
|
||
QtCanvasItem(canvas),
|
||
frm(0),
|
||
anim_val(0),
|
||
anim_state(0),
|
||
anim_type(0),
|
||
images(a)
|
||
{
|
||
}
|
||
|
||
|
||
/*
|
||
Set the array of images used for displaying the sprite to the
|
||
QtCanvasPixmapArray \a a.
|
||
|
||
If the current frame() is larger than the number of images in \a
|
||
a, the current frame will be reset to 0.
|
||
*/
|
||
void QtCanvasSprite::setSequence(QtCanvasPixmapArray* a)
|
||
{
|
||
bool isvisible = isVisible();
|
||
if (isvisible && images)
|
||
hide();
|
||
images = a;
|
||
if (frm >= (int)images->count())
|
||
frm = 0;
|
||
if (isvisible)
|
||
show();
|
||
}
|
||
|
||
/*
|
||
\internal
|
||
|
||
Marks any chunks the sprite touches as changed.
|
||
*/
|
||
void QtCanvasSprite::changeChunks()
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
int chunksize = canvas()->chunkSize();
|
||
for (int j = topEdge()/chunksize; j <= bottomEdge()/chunksize; j++) {
|
||
for (int i = leftEdge()/chunksize; i <= rightEdge()/chunksize; i++) {
|
||
canvas()->setChangedChunk(i, j);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
Destroys the sprite and removes it from the canvas. Does \e not
|
||
delete the images.
|
||
*/
|
||
QtCanvasSprite::~QtCanvasSprite()
|
||
{
|
||
removeFromChunks();
|
||
}
|
||
|
||
/*
|
||
Sets the animation frame used for displaying the sprite to \a f,
|
||
an index into the QtCanvasSprite's QtCanvasPixmapArray. The call
|
||
will be ignored if \a f is larger than frameCount() or smaller
|
||
than 0.
|
||
|
||
\sa frame() move()
|
||
*/
|
||
void QtCanvasSprite::setFrame(int f)
|
||
{
|
||
move(x(), y(), f);
|
||
}
|
||
|
||
/*
|
||
\enum QtCanvasSprite::FrameAnimationType
|
||
|
||
This enum is used to identify the different types of frame
|
||
animation offered by QtCanvasSprite.
|
||
|
||
\value Cycle at each advance the frame number will be incremented by
|
||
1 (modulo the frame count).
|
||
\value Oscillate at each advance the frame number will be
|
||
incremented by 1 up to the frame count then decremented to by 1 to
|
||
0, repeating this sequence forever.
|
||
*/
|
||
|
||
/*
|
||
Sets the animation characteristics for the sprite.
|
||
|
||
For \a type == \c Cycle, the frames will increase by \a step
|
||
at each advance, modulo the frameCount().
|
||
|
||
For \a type == \c Oscillate, the frames will increase by \a step
|
||
at each advance, up to the frameCount(), then decrease by \a step
|
||
back to 0, repeating forever.
|
||
|
||
The \a state parameter is for internal use.
|
||
*/
|
||
void QtCanvasSprite::setFrameAnimation(FrameAnimationType type, int step, int state)
|
||
{
|
||
anim_val = step;
|
||
anim_type = type;
|
||
anim_state = state;
|
||
setAnimated(true);
|
||
}
|
||
|
||
/*
|
||
Extends the default QtCanvasItem implementation to provide the
|
||
functionality of setFrameAnimation().
|
||
|
||
The \a phase is 0 or 1: see QtCanvasItem::advance() for details.
|
||
|
||
\sa QtCanvasItem::advance() setVelocity()
|
||
*/
|
||
void QtCanvasSprite::advance(int phase)
|
||
{
|
||
if (phase == 1) {
|
||
int nf = frame();
|
||
if (anim_type == Oscillate) {
|
||
if (anim_state)
|
||
nf += anim_val;
|
||
else
|
||
nf -= anim_val;
|
||
if (nf < 0) {
|
||
nf = abs(anim_val);
|
||
anim_state = !anim_state;
|
||
} else if (nf >= frameCount()) {
|
||
nf = frameCount()-1-abs(anim_val);
|
||
anim_state = !anim_state;
|
||
}
|
||
} else {
|
||
nf = (nf + anim_val + frameCount()) % frameCount();
|
||
}
|
||
move(x()+xVelocity(), y()+yVelocity(), nf);
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
\fn int QtCanvasSprite::frame() const
|
||
|
||
Returns the index of the current animation frame in the
|
||
QtCanvasSprite's QtCanvasPixmapArray.
|
||
|
||
\sa setFrame(), move()
|
||
*/
|
||
|
||
/*
|
||
\fn int QtCanvasSprite::frameCount() const
|
||
|
||
Returns the number of frames in the QtCanvasSprite's
|
||
QtCanvasPixmapArray.
|
||
*/
|
||
|
||
|
||
/*
|
||
Moves the sprite to (\a x, \a y).
|
||
*/
|
||
void QtCanvasSprite::move(double x, double y) { QtCanvasItem::move(x, y); }
|
||
|
||
/*
|
||
\fn void QtCanvasSprite::move(double nx, double ny, int nf)
|
||
|
||
Moves the sprite to (\a nx, \a ny) and sets the current
|
||
frame to \a nf. \a nf will be ignored if it is larger than
|
||
frameCount() or smaller than 0.
|
||
*/
|
||
void QtCanvasSprite::move(double nx, double ny, int nf)
|
||
{
|
||
if (isVisible() && canvas()) {
|
||
hide();
|
||
QtCanvasItem::move(nx, ny);
|
||
if (nf >= 0 && nf < frameCount())
|
||
frm = nf;
|
||
show();
|
||
} else {
|
||
QtCanvasItem::move(nx, ny);
|
||
if (nf >= 0 && nf < frameCount())
|
||
frm = nf;
|
||
}
|
||
}
|
||
|
||
|
||
class QPoint;
|
||
|
||
class QtPolygonScanner {
|
||
public:
|
||
virtual ~QtPolygonScanner() {}
|
||
void scan(const QPolygon& pa, bool winding, int index = 0, int npoints = -1);
|
||
void scan(const QPolygon& pa, bool winding, int index, int npoints, bool stitchable);
|
||
enum Edge { Left = 1, Right = 2, Top = 4, Bottom = 8 };
|
||
void scan(const QPolygon& pa, bool winding, int index, int npoints, Edge edges);
|
||
virtual void processSpans(int n, QPoint* point, int* width) = 0;
|
||
};
|
||
|
||
|
||
// Based on Xserver code miFillGeneralPoly...
|
||
/*
|
||
*
|
||
* Written by Brian Kelleher; Oct. 1985
|
||
*
|
||
* Routine to fill a polygon. Two fill rules are
|
||
* supported: frWINDING and frEVENODD.
|
||
*
|
||
* See fillpoly.h for a complete description of the algorithm.
|
||
*/
|
||
|
||
/*
|
||
* These are the data structures needed to scan
|
||
* convert regions. Two different scan conversion
|
||
* methods are available -- the even-odd method, and
|
||
* the winding number method.
|
||
* The even-odd rule states that a point is inside
|
||
* the polygon if a ray drawn from that point in any
|
||
* direction will pass through an odd number of
|
||
* path segments.
|
||
* By the winding number rule, a point is decided
|
||
* to be inside the polygon if a ray drawn from that
|
||
* point in any direction passes through a different
|
||
* number of clockwise and counterclockwise path
|
||
* segments.
|
||
*
|
||
* These data structures are adapted somewhat from
|
||
* the algorithm in (Foley/Van Dam) for scan converting
|
||
* polygons.
|
||
* The basic algorithm is to start at the top (smallest y)
|
||
* of the polygon, stepping down to the bottom of
|
||
* the polygon by incrementing the y coordinate. We
|
||
* keep a list of edges which the current scanline crosses,
|
||
* sorted by x. This list is called the Active Edge Table (AET)
|
||
* As we change the y-coordinate, we update each entry in
|
||
* in the active edge table to reflect the edges new xcoord.
|
||
* This list must be sorted at each scanline in case
|
||
* two edges intersect.
|
||
* We also keep a data structure known as the Edge Table (ET),
|
||
* which keeps track of all the edges which the current
|
||
* scanline has not yet reached. The ET is basically a
|
||
* list of ScanLineList structures containing a list of
|
||
* edges which are entered at a given scanline. There is one
|
||
* ScanLineList per scanline at which an edge is entered.
|
||
* When we enter a new edge, we move it from the ET to the AET.
|
||
*
|
||
* From the AET, we can implement the even-odd rule as in
|
||
* (Foley/Van Dam).
|
||
* The winding number rule is a little trickier. We also
|
||
* keep the EdgeTableEntries in the AET linked by the
|
||
* nextWETE (winding EdgeTableEntry) link. This allows
|
||
* the edges to be linked just as before for updating
|
||
* purposes, but only uses the edges linked by the nextWETE
|
||
* link as edges representing spans of the polygon to
|
||
* drawn (as with the even-odd rule).
|
||
*/
|
||
|
||
/* $XConsortium: miscanfill.h, v 1.5 94/04/17 20:27:50 dpw Exp $ */
|
||
/*
|
||
|
||
Copyright (c) 1987 X Consortium
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining
|
||
a copy of this software and associated documentation files (the
|
||
"Software"), to deal in the Software without restriction, including
|
||
without limitation the rights to use, copy, modify, merge, publish,
|
||
distribute, sublicense, and/or sell copies of the Software, and to
|
||
permit persons to whom the Software is furnished to do so, subject to
|
||
the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included
|
||
in all copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||
OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
Except as contained in this notice, the name of the X Consortium shall
|
||
not be used in advertising or otherwise to promote the sale, use or
|
||
other dealings in this Software without prior written authorization
|
||
from the X Consortium.
|
||
|
||
*/
|
||
|
||
|
||
/*
|
||
* scanfill.h
|
||
*
|
||
* Written by Brian Kelleher; Jan 1985
|
||
*
|
||
* This file contains a few macros to help track
|
||
* the edge of a filled object. The object is assumed
|
||
* to be filled in scanline order, and thus the
|
||
* algorithm used is an extension of Bresenham's line
|
||
* drawing algorithm which assumes that y is always the
|
||
* major axis.
|
||
* Since these pieces of code are the same for any filled shape,
|
||
* it is more convenient to gather the library in one
|
||
* place, but since these pieces of code are also in
|
||
* the inner loops of output primitives, procedure call
|
||
* overhead is out of the question.
|
||
* See the author for a derivation if needed.
|
||
*/
|
||
|
||
/*
|
||
* In scan converting polygons, we want to choose those pixels
|
||
* which are inside the polygon. Thus, we add .5 to the starting
|
||
* x coordinate for both left and right edges. Now we choose the
|
||
* first pixel which is inside the pgon for the left edge and the
|
||
* first pixel which is outside the pgon for the right edge.
|
||
* Draw the left pixel, but not the right.
|
||
*
|
||
* How to add .5 to the starting x coordinate:
|
||
* If the edge is moving to the right, then subtract dy from the
|
||
* error term from the general form of the algorithm.
|
||
* If the edge is moving to the left, then add dy to the error term.
|
||
*
|
||
* The reason for the difference between edges moving to the left
|
||
* and edges moving to the right is simple: If an edge is moving
|
||
* to the right, then we want the algorithm to flip immediately.
|
||
* If it is moving to the left, then we don't want it to flip until
|
||
* we traverse an entire pixel.
|
||
*/
|
||
#define BRESINITPGON(dy, x1, x2, xStart, d, m, m1, incr1, incr2) { \
|
||
int dx; /* local storage */ \
|
||
\
|
||
/* \
|
||
* if the edge is horizontal, then it is ignored \
|
||
* and assumed not to be processed. Otherwise, do this stuff. \
|
||
*/ \
|
||
if ((dy) != 0) { \
|
||
xStart = (x1); \
|
||
dx = (x2) - xStart; \
|
||
if (dx < 0) { \
|
||
m = dx / (dy); \
|
||
m1 = m - 1; \
|
||
incr1 = -2 * dx + 2 * (dy) * m1; \
|
||
incr2 = -2 * dx + 2 * (dy) * m; \
|
||
d = 2 * m * (dy) - 2 * dx - 2 * (dy); \
|
||
} else { \
|
||
m = dx / (dy); \
|
||
m1 = m + 1; \
|
||
incr1 = 2 * dx - 2 * (dy) * m1; \
|
||
incr2 = 2 * dx - 2 * (dy) * m; \
|
||
d = -2 * m * (dy) + 2 * dx; \
|
||
} \
|
||
} \
|
||
}
|
||
|
||
#define BRESINCRPGON(d, minval, m, m1, incr1, incr2) { \
|
||
if (m1 > 0) { \
|
||
if (d > 0) { \
|
||
minval += m1; \
|
||
d += incr1; \
|
||
} \
|
||
else { \
|
||
minval += m; \
|
||
d += incr2; \
|
||
} \
|
||
} else {\
|
||
if (d >= 0) { \
|
||
minval += m1; \
|
||
d += incr1; \
|
||
} \
|
||
else { \
|
||
minval += m; \
|
||
d += incr2; \
|
||
} \
|
||
} \
|
||
}
|
||
|
||
|
||
/*
|
||
* This structure contains all of the information needed
|
||
* to run the bresenham algorithm.
|
||
* The variables may be hardcoded into the declarations
|
||
* instead of using this structure to make use of
|
||
* register declarations.
|
||
*/
|
||
typedef struct {
|
||
int minor; /* minor axis */
|
||
int d; /* decision variable */
|
||
int m, m1; /* slope and slope+1 */
|
||
int incr1, incr2; /* error increments */
|
||
} BRESINFO;
|
||
|
||
|
||
#define BRESINITPGONSTRUCT(dmaj, min1, min2, bres) \
|
||
BRESINITPGON(dmaj, min1, min2, bres.minor, bres.d, \
|
||
bres.m, bres.m1, bres.incr1, bres.incr2)
|
||
|
||
#define BRESINCRPGONSTRUCT(bres) \
|
||
BRESINCRPGON(bres.d, bres.minor, bres.m, bres.m1, bres.incr1, bres.incr2)
|
||
|
||
|
||
typedef struct _EdgeTableEntry {
|
||
int ymax; /* ycoord at which we exit this edge. */
|
||
BRESINFO bres; /* Bresenham info to run the edge */
|
||
struct _EdgeTableEntry *next; /* next in the list */
|
||
struct _EdgeTableEntry *back; /* for insertion sort */
|
||
struct _EdgeTableEntry *nextWETE; /* for winding num rule */
|
||
int ClockWise; /* flag for winding number rule */
|
||
} EdgeTableEntry;
|
||
|
||
|
||
typedef struct _ScanLineList{
|
||
int scanline; /* the scanline represented */
|
||
EdgeTableEntry *edgelist; /* header node */
|
||
struct _ScanLineList *next; /* next in the list */
|
||
} ScanLineList;
|
||
|
||
|
||
typedef struct {
|
||
int ymax; /* ymax for the polygon */
|
||
int ymin; /* ymin for the polygon */
|
||
ScanLineList scanlines; /* header node */
|
||
} EdgeTable;
|
||
|
||
|
||
/*
|
||
* Here is a struct to help with storage allocation
|
||
* so we can allocate a big chunk at a time, and then take
|
||
* pieces from this heap when we need to.
|
||
*/
|
||
#define SLLSPERBLOCK 25
|
||
|
||
typedef struct _ScanLineListBlock {
|
||
ScanLineList SLLs[SLLSPERBLOCK];
|
||
struct _ScanLineListBlock *next;
|
||
} ScanLineListBlock;
|
||
|
||
/*
|
||
* number of points to buffer before sending them off
|
||
* to scanlines() : Must be an even number
|
||
*/
|
||
#define NUMPTSTOBUFFER 200
|
||
|
||
/*
|
||
*
|
||
* a few macros for the inner loops of the fill code where
|
||
* performance considerations don't allow a procedure call.
|
||
*
|
||
* Evaluate the given edge at the given scanline.
|
||
* If the edge has expired, then we leave it and fix up
|
||
* the active edge table; otherwise, we increment the
|
||
* x value to be ready for the next scanline.
|
||
* The winding number rule is in effect, so we must notify
|
||
* the caller when the edge has been removed so he
|
||
* can reorder the Winding Active Edge Table.
|
||
*/
|
||
#define EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET) { \
|
||
if (pAET->ymax == y) { /* leaving this edge */ \
|
||
pPrevAET->next = pAET->next; \
|
||
pAET = pPrevAET->next; \
|
||
fixWAET = 1; \
|
||
if (pAET) \
|
||
pAET->back = pPrevAET; \
|
||
} \
|
||
else { \
|
||
BRESINCRPGONSTRUCT(pAET->bres); \
|
||
pPrevAET = pAET; \
|
||
pAET = pAET->next; \
|
||
} \
|
||
}
|
||
|
||
|
||
/*
|
||
* Evaluate the given edge at the given scanline.
|
||
* If the edge has expired, then we leave it and fix up
|
||
* the active edge table; otherwise, we increment the
|
||
* x value to be ready for the next scanline.
|
||
* The even-odd rule is in effect.
|
||
*/
|
||
#define EVALUATEEDGEEVENODD(pAET, pPrevAET, y) { \
|
||
if (pAET->ymax == y) { /* leaving this edge */ \
|
||
pPrevAET->next = pAET->next; \
|
||
pAET = pPrevAET->next; \
|
||
if (pAET) \
|
||
pAET->back = pPrevAET; \
|
||
} \
|
||
else { \
|
||
BRESINCRPGONSTRUCT(pAET->bres) \
|
||
pPrevAET = pAET; \
|
||
pAET = pAET->next; \
|
||
} \
|
||
}
|
||
|
||
/***********************************************************
|
||
|
||
Copyright (c) 1987 X Consortium
|
||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
of this software and associated documentation files (the "Software"), to deal
|
||
in the Software without restriction, including without limitation the rights
|
||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
copies of the Software, and to permit persons to whom the Software is
|
||
furnished to do so, subject to the following conditions:
|
||
|
||
The above copyright notice and this permission notice shall be included in
|
||
all copies or substantial portions of the Software.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
||
Except as contained in this notice, the name of the X Consortium shall not be
|
||
used in advertising or otherwise to promote the sale, use or other dealings
|
||
in this Software without prior written authorization from the X Consortium.
|
||
|
||
|
||
Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
|
||
|
||
All Rights Reserved
|
||
|
||
Permission to use, copy, modify, and distribute this software and its
|
||
documentation for any purpose and without fee is hereby granted,
|
||
provided that the above copyright notice appear in all copies and that
|
||
both that copyright notice and this permission notice appear in
|
||
supporting documentation, and that the name of Digital not be
|
||
used in advertising or publicity pertaining to distribution of the
|
||
software without specific, written prior permission.
|
||
|
||
DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
|
||
ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
|
||
DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
|
||
ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||
SOFTWARE.
|
||
|
||
******************************************************************/
|
||
|
||
#define MAXINT 0x7fffffff
|
||
#define MININT -MAXINT
|
||
|
||
/*
|
||
* fillUtils.c
|
||
*
|
||
* Written by Brian Kelleher; Oct. 1985
|
||
*
|
||
* This module contains all of the utility functions
|
||
* needed to scan convert a polygon.
|
||
*
|
||
*/
|
||
/*
|
||
* InsertEdgeInET
|
||
*
|
||
* Insert the given edge into the edge table.
|
||
* First we must find the correct bucket in the
|
||
* Edge table, then find the right slot in the
|
||
* bucket. Finally, we can insert it.
|
||
*
|
||
*/
|
||
static bool
|
||
miInsertEdgeInET(EdgeTable *ET, EdgeTableEntry *ETE,
|
||
int scanline, ScanLineListBlock **SLLBlock, int *iSLLBlock)
|
||
{
|
||
register EdgeTableEntry *start, *prev;
|
||
register ScanLineList *pSLL, *pPrevSLL;
|
||
ScanLineListBlock *tmpSLLBlock;
|
||
|
||
/*
|
||
* find the right bucket to put the edge into
|
||
*/
|
||
pPrevSLL = &ET->scanlines;
|
||
pSLL = pPrevSLL->next;
|
||
while (pSLL && (pSLL->scanline < scanline))
|
||
{
|
||
pPrevSLL = pSLL;
|
||
pSLL = pSLL->next;
|
||
}
|
||
|
||
/*
|
||
* reassign pSLL (pointer to ScanLineList) if necessary
|
||
*/
|
||
if ((!pSLL) || (pSLL->scanline > scanline))
|
||
{
|
||
if (*iSLLBlock > SLLSPERBLOCK-1)
|
||
{
|
||
tmpSLLBlock =
|
||
(ScanLineListBlock *)malloc(sizeof(ScanLineListBlock));
|
||
if (!tmpSLLBlock)
|
||
return false;
|
||
(*SLLBlock)->next = tmpSLLBlock;
|
||
tmpSLLBlock->next = 0;
|
||
*SLLBlock = tmpSLLBlock;
|
||
*iSLLBlock = 0;
|
||
}
|
||
pSLL = &((*SLLBlock)->SLLs[(*iSLLBlock)++]);
|
||
|
||
pSLL->next = pPrevSLL->next;
|
||
pSLL->edgelist = 0;
|
||
pPrevSLL->next = pSLL;
|
||
}
|
||
pSLL->scanline = scanline;
|
||
|
||
/*
|
||
* now insert the edge in the right bucket
|
||
*/
|
||
prev = 0;
|
||
start = pSLL->edgelist;
|
||
while (start && (start->bres.minor < ETE->bres.minor))
|
||
{
|
||
prev = start;
|
||
start = start->next;
|
||
}
|
||
ETE->next = start;
|
||
|
||
if (prev)
|
||
prev->next = ETE;
|
||
else
|
||
pSLL->edgelist = ETE;
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* CreateEdgeTable
|
||
*
|
||
* This routine creates the edge table for
|
||
* scan converting polygons.
|
||
* The Edge Table (ET) looks like:
|
||
*
|
||
* EdgeTable
|
||
* --------
|
||
* | ymax | ScanLineLists
|
||
* |scanline|-->------------>-------------->...
|
||
* -------- |scanline| |scanline|
|
||
* |edgelist| |edgelist|
|
||
* --------- ---------
|
||
* | |
|
||
* | |
|
||
* V V
|
||
* list of ETEs list of ETEs
|
||
*
|
||
* where ETE is an EdgeTableEntry data structure,
|
||
* and there is one ScanLineList per scanline at
|
||
* which an edge is initially entered.
|
||
*
|
||
*/
|
||
|
||
typedef struct {
|
||
#if defined(Q_OS_MAC)
|
||
int y, x;
|
||
#else
|
||
int x, y;
|
||
#endif
|
||
|
||
} DDXPointRec, *DDXPointPtr;
|
||
|
||
/*
|
||
* Clean up our act.
|
||
*/
|
||
static void
|
||
miFreeStorage(ScanLineListBlock *pSLLBlock)
|
||
{
|
||
register ScanLineListBlock *tmpSLLBlock;
|
||
|
||
while (pSLLBlock)
|
||
{
|
||
tmpSLLBlock = pSLLBlock->next;
|
||
free(pSLLBlock);
|
||
pSLLBlock = tmpSLLBlock;
|
||
}
|
||
}
|
||
|
||
static bool
|
||
miCreateETandAET(int count, DDXPointPtr pts, EdgeTable *ET,
|
||
EdgeTableEntry *AET, EdgeTableEntry *pETEs, ScanLineListBlock *pSLLBlock)
|
||
{
|
||
register DDXPointPtr top, bottom;
|
||
register DDXPointPtr PrevPt, CurrPt;
|
||
int iSLLBlock = 0;
|
||
|
||
int dy;
|
||
|
||
if (count < 2) return true;
|
||
|
||
/*
|
||
* initialize the Active Edge Table
|
||
*/
|
||
AET->next = 0;
|
||
AET->back = 0;
|
||
AET->nextWETE = 0;
|
||
AET->bres.minor = MININT;
|
||
|
||
/*
|
||
* initialize the Edge Table.
|
||
*/
|
||
ET->scanlines.next = 0;
|
||
ET->ymax = MININT;
|
||
ET->ymin = MAXINT;
|
||
pSLLBlock->next = 0;
|
||
|
||
PrevPt = &pts[count-1];
|
||
|
||
/*
|
||
* for each vertex in the array of points.
|
||
* In this loop we are dealing with two vertices at
|
||
* a time -- these make up one edge of the polygon.
|
||
*/
|
||
while (count--)
|
||
{
|
||
CurrPt = pts++;
|
||
|
||
/*
|
||
* find out which point is above and which is below.
|
||
*/
|
||
if (PrevPt->y > CurrPt->y)
|
||
{
|
||
bottom = PrevPt, top = CurrPt;
|
||
pETEs->ClockWise = 0;
|
||
}
|
||
else
|
||
{
|
||
bottom = CurrPt, top = PrevPt;
|
||
pETEs->ClockWise = 1;
|
||
}
|
||
|
||
/*
|
||
* don't add horizontal edges to the Edge table.
|
||
*/
|
||
if (bottom->y != top->y)
|
||
{
|
||
pETEs->ymax = bottom->y-1; /* -1 so we don't get last scanline */
|
||
|
||
/*
|
||
* initialize integer edge algorithm
|
||
*/
|
||
dy = bottom->y - top->y;
|
||
BRESINITPGONSTRUCT(dy, top->x, bottom->x, pETEs->bres)
|
||
|
||
if (!miInsertEdgeInET(ET, pETEs, top->y, &pSLLBlock, &iSLLBlock))
|
||
{
|
||
miFreeStorage(pSLLBlock->next);
|
||
return false;
|
||
}
|
||
|
||
ET->ymax = qMax(ET->ymax, PrevPt->y);
|
||
ET->ymin = qMin(ET->ymin, PrevPt->y);
|
||
pETEs++;
|
||
}
|
||
|
||
PrevPt = CurrPt;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* loadAET
|
||
*
|
||
* This routine moves EdgeTableEntries from the
|
||
* EdgeTable into the Active Edge Table,
|
||
* leaving them sorted by smaller x coordinate.
|
||
*
|
||
*/
|
||
|
||
static void
|
||
miloadAET(EdgeTableEntry *AET, EdgeTableEntry *ETEs)
|
||
{
|
||
register EdgeTableEntry *pPrevAET;
|
||
register EdgeTableEntry *tmp;
|
||
|
||
pPrevAET = AET;
|
||
AET = AET->next;
|
||
while (ETEs)
|
||
{
|
||
while (AET && (AET->bres.minor < ETEs->bres.minor))
|
||
{
|
||
pPrevAET = AET;
|
||
AET = AET->next;
|
||
}
|
||
tmp = ETEs->next;
|
||
ETEs->next = AET;
|
||
if (AET)
|
||
AET->back = ETEs;
|
||
ETEs->back = pPrevAET;
|
||
pPrevAET->next = ETEs;
|
||
pPrevAET = ETEs;
|
||
|
||
ETEs = tmp;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* computeWAET
|
||
*
|
||
* This routine links the AET by the
|
||
* nextWETE (winding EdgeTableEntry) link for
|
||
* use by the winding number rule. The final
|
||
* Active Edge Table (AET) might look something
|
||
* like:
|
||
*
|
||
* AET
|
||
* ---------- --------- ---------
|
||
* |ymax | |ymax | |ymax |
|
||
* | ... | |... | |... |
|
||
* |next |->|next |->|next |->...
|
||
* |nextWETE| |nextWETE| |nextWETE|
|
||
* --------- --------- ^--------
|
||
* | | |
|
||
* V-------------------> V---> ...
|
||
*
|
||
*/
|
||
static void
|
||
micomputeWAET(EdgeTableEntry *AET)
|
||
{
|
||
register EdgeTableEntry *pWETE;
|
||
register int inside = 1;
|
||
register int isInside = 0;
|
||
|
||
AET->nextWETE = 0;
|
||
pWETE = AET;
|
||
AET = AET->next;
|
||
while (AET)
|
||
{
|
||
if (AET->ClockWise)
|
||
isInside++;
|
||
else
|
||
isInside--;
|
||
|
||
if ((!inside && !isInside) ||
|
||
(inside && isInside))
|
||
{
|
||
pWETE->nextWETE = AET;
|
||
pWETE = AET;
|
||
inside = !inside;
|
||
}
|
||
AET = AET->next;
|
||
}
|
||
pWETE->nextWETE = 0;
|
||
}
|
||
|
||
/*
|
||
* InsertionSort
|
||
*
|
||
* Just a simple insertion sort using
|
||
* pointers and back pointers to sort the Active
|
||
* Edge Table.
|
||
*
|
||
*/
|
||
|
||
static int
|
||
miInsertionSort(EdgeTableEntry *AET)
|
||
{
|
||
register EdgeTableEntry *pETEchase;
|
||
register EdgeTableEntry *pETEinsert;
|
||
register EdgeTableEntry *pETEchaseBackTMP;
|
||
register int changed = 0;
|
||
|
||
AET = AET->next;
|
||
while (AET)
|
||
{
|
||
pETEinsert = AET;
|
||
pETEchase = AET;
|
||
while (pETEchase->back->bres.minor > AET->bres.minor)
|
||
pETEchase = pETEchase->back;
|
||
|
||
AET = AET->next;
|
||
if (pETEchase != pETEinsert)
|
||
{
|
||
pETEchaseBackTMP = pETEchase->back;
|
||
pETEinsert->back->next = AET;
|
||
if (AET)
|
||
AET->back = pETEinsert->back;
|
||
pETEinsert->next = pETEchase;
|
||
pETEchase->back->next = pETEinsert;
|
||
pETEchase->back = pETEinsert;
|
||
pETEinsert->back = pETEchaseBackTMP;
|
||
changed = 1;
|
||
}
|
||
}
|
||
return changed;
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
*/
|
||
void QtPolygonScanner::scan(const QPolygon& pa, bool winding, int index, int npoints)
|
||
{
|
||
scan(pa, winding, index, npoints, true);
|
||
}
|
||
|
||
/*
|
||
\overload
|
||
|
||
If \a stitchable is false, the right and bottom edges of the
|
||
polygon are included. This causes adjacent polygons to overlap.
|
||
*/
|
||
void QtPolygonScanner::scan(const QPolygon& pa, bool winding, int index, int npoints, bool stitchable)
|
||
{
|
||
scan(pa, winding, index, npoints,
|
||
stitchable ? Edge(Left+Top) : Edge(Left+Right+Top+Bottom));
|
||
}
|
||
|
||
/*
|
||
Calls processSpans() for all scanlines of the polygon defined by
|
||
\a npoints starting at \a index in \a pa.
|
||
|
||
If \a winding is true, the Winding algorithm rather than the
|
||
Odd-Even rule is used.
|
||
|
||
The \a edges is any bitwise combination of:
|
||
\list
|
||
\i QtPolygonScanner::Left
|
||
\i QtPolygonScanner::Right
|
||
\i QtPolygonScanner::Top
|
||
\i QtPolygonScanner::Bottom
|
||
\endlist
|
||
\a edges determines which edges are included.
|
||
|
||
\warning The edges feature does not work properly.
|
||
|
||
*/
|
||
void QtPolygonScanner::scan(const QPolygon& pa, bool winding, int index, int npoints, Edge edges)
|
||
{
|
||
|
||
|
||
DDXPointPtr ptsIn = (DDXPointPtr)pa.data();
|
||
ptsIn += index;
|
||
register EdgeTableEntry *pAET; /* the Active Edge Table */
|
||
register int y; /* the current scanline */
|
||
register int nPts = 0; /* number of pts in buffer */
|
||
register EdgeTableEntry *pWETE; /* Winding Edge Table */
|
||
register ScanLineList *pSLL; /* Current ScanLineList */
|
||
register DDXPointPtr ptsOut; /* ptr to output buffers */
|
||
int *width;
|
||
DDXPointRec FirstPoint[NUMPTSTOBUFFER]; /* the output buffers */
|
||
int FirstWidth[NUMPTSTOBUFFER];
|
||
EdgeTableEntry *pPrevAET; /* previous AET entry */
|
||
EdgeTable ET; /* Edge Table header node */
|
||
EdgeTableEntry AET; /* Active ET header node */
|
||
EdgeTableEntry *pETEs; /* Edge Table Entries buff */
|
||
ScanLineListBlock SLLBlock; /* header for ScanLineList */
|
||
int fixWAET = 0;
|
||
int edge_l = (edges & Left) ? 1 : 0;
|
||
int edge_r = (edges & Right) ? 1 : 0;
|
||
int edge_t = 1; //#### (edges & Top) ? 1 : 0;
|
||
int edge_b = (edges & Bottom) ? 1 : 0;
|
||
|
||
if (npoints == -1)
|
||
npoints = pa.size();
|
||
|
||
if (npoints < 3)
|
||
return;
|
||
|
||
if(!(pETEs = (EdgeTableEntry *)
|
||
malloc(sizeof(EdgeTableEntry) * npoints)))
|
||
return;
|
||
ptsOut = FirstPoint;
|
||
width = FirstWidth;
|
||
if (!miCreateETandAET(npoints, ptsIn, &ET, &AET, pETEs, &SLLBlock))
|
||
{
|
||
free(pETEs);
|
||
return;
|
||
}
|
||
pSLL = ET.scanlines.next;
|
||
|
||
if (!winding)
|
||
{
|
||
/*
|
||
* for each scanline
|
||
*/
|
||
for (y = ET.ymin+1-edge_t; y < ET.ymax+edge_b; y++)
|
||
{
|
||
/*
|
||
* Add a new edge to the active edge table when we
|
||
* get to the next edge.
|
||
*/
|
||
if (pSLL && y == pSLL->scanline)
|
||
{
|
||
miloadAET(&AET, pSLL->edgelist);
|
||
pSLL = pSLL->next;
|
||
}
|
||
pPrevAET = &AET;
|
||
pAET = AET.next;
|
||
|
||
/*
|
||
* for each active edge
|
||
*/
|
||
while (pAET)
|
||
{
|
||
ptsOut->x = pAET->bres.minor + 1 - edge_l;
|
||
ptsOut++->y = y;
|
||
*width++ = pAET->next->bres.minor - pAET->bres.minor
|
||
- 1 + edge_l + edge_r;
|
||
nPts++;
|
||
|
||
/*
|
||
* send out the buffer when its full
|
||
*/
|
||
if (nPts == NUMPTSTOBUFFER)
|
||
{
|
||
processSpans(nPts, (QPoint*)FirstPoint, FirstWidth);
|
||
ptsOut = FirstPoint;
|
||
width = FirstWidth;
|
||
nPts = 0;
|
||
}
|
||
EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
|
||
EVALUATEEDGEEVENODD(pAET, pPrevAET, y)
|
||
}
|
||
miInsertionSort(&AET);
|
||
}
|
||
}
|
||
else /* default to WindingNumber */
|
||
{
|
||
/*
|
||
* for each scanline
|
||
*/
|
||
for (y = ET.ymin+1-edge_t; y < ET.ymax+edge_b; y++)
|
||
{
|
||
/*
|
||
* Add a new edge to the active edge table when we
|
||
* get to the next edge.
|
||
*/
|
||
if (pSLL && y == pSLL->scanline)
|
||
{
|
||
miloadAET(&AET, pSLL->edgelist);
|
||
micomputeWAET(&AET);
|
||
pSLL = pSLL->next;
|
||
}
|
||
pPrevAET = &AET;
|
||
pAET = AET.next;
|
||
pWETE = pAET;
|
||
|
||
/*
|
||
* for each active edge
|
||
*/
|
||
while (pAET)
|
||
{
|
||
/*
|
||
* if the next edge in the active edge table is
|
||
* also the next edge in the winding active edge
|
||
* table.
|
||
*/
|
||
if (pWETE == pAET)
|
||
{
|
||
ptsOut->x = pAET->bres.minor + 1 - edge_l;
|
||
ptsOut++->y = y;
|
||
*width++ = pAET->nextWETE->bres.minor - pAET->bres.minor - 1 + edge_l + edge_r;
|
||
nPts++;
|
||
|
||
/*
|
||
* send out the buffer
|
||
*/
|
||
if (nPts == NUMPTSTOBUFFER)
|
||
{
|
||
processSpans(nPts, (QPoint*)FirstPoint, FirstWidth);
|
||
ptsOut = FirstPoint;
|
||
width = FirstWidth;
|
||
nPts = 0;
|
||
}
|
||
|
||
pWETE = pWETE->nextWETE;
|
||
while (pWETE != pAET) {
|
||
EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET)
|
||
}
|
||
pWETE = pWETE->nextWETE;
|
||
}
|
||
EVALUATEEDGEWINDING(pAET, pPrevAET, y, fixWAET)
|
||
}
|
||
|
||
/*
|
||
* reevaluate the Winding active edge table if we
|
||
* just had to resort it or if we just exited an edge.
|
||
*/
|
||
if (miInsertionSort(&AET) || fixWAET)
|
||
{
|
||
micomputeWAET(&AET);
|
||
fixWAET = 0;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Get any spans that we missed by buffering
|
||
*/
|
||
|
||
|
||
processSpans(nPts, (QPoint*)FirstPoint, FirstWidth);
|
||
free(pETEs);
|
||
miFreeStorage(SLLBlock.next);
|
||
}
|
||
/***** END OF X11-based CODE *****/
|
||
|
||
|
||
|
||
|
||
|
||
class QtCanvasPolygonScanner : public QtPolygonScanner {
|
||
QPolygonalProcessor& processor;
|
||
public:
|
||
QtCanvasPolygonScanner(QPolygonalProcessor& p) :
|
||
processor(p)
|
||
{
|
||
}
|
||
void processSpans(int n, QPoint* point, int* width)
|
||
{
|
||
processor.doSpans(n, point, width);
|
||
}
|
||
};
|
||
|
||
void QtCanvasPolygonalItem::scanPolygon(const QPolygon& pa, int winding, QPolygonalProcessor& process) const
|
||
{
|
||
QtCanvasPolygonScanner scanner(process);
|
||
scanner.scan(pa, winding);
|
||
}
|