1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | |
30 | #include <QtTest/QtTest> |
31 | #include <QtGui/QPainterPath> |
32 | #include <QtWidgets/qgraphicsscene.h> |
33 | #include <private/qgraphicsscenebsptreeindex_p.h> |
34 | #include <private/qgraphicssceneindex_p.h> |
35 | #include <private/qgraphicsscenelinearindex_p.h> |
36 | |
37 | class tst_QGraphicsSceneIndex : public QObject |
38 | { |
39 | Q_OBJECT |
40 | public slots: |
41 | void initTestCase(); |
42 | |
43 | private slots: |
44 | void scatteredItems_data(); |
45 | void scatteredItems(); |
46 | void overlappedItems_data(); |
47 | void overlappedItems(); |
48 | void movingItems_data(); |
49 | void movingItems(); |
50 | void connectedToSceneRectChanged(); |
51 | void items(); |
52 | void boundingRectPointIntersection_data(); |
53 | void boundingRectPointIntersection(); |
54 | void removeItems(); |
55 | void clear(); |
56 | |
57 | private: |
58 | void common_data(); |
59 | QGraphicsSceneIndex *createIndex(const QString &name); |
60 | }; |
61 | |
62 | void tst_QGraphicsSceneIndex::initTestCase() |
63 | { |
64 | } |
65 | |
66 | void tst_QGraphicsSceneIndex::common_data() |
67 | { |
68 | QTest::addColumn<QString>(name: "indexMethod" ); |
69 | |
70 | QTest::newRow(dataTag: "BSP" ) << QString("bsp" ); |
71 | QTest::newRow(dataTag: "Linear" ) << QString("linear" ); |
72 | } |
73 | |
74 | QGraphicsSceneIndex *tst_QGraphicsSceneIndex::createIndex(const QString &indexMethod) |
75 | { |
76 | QGraphicsSceneIndex *index = 0; |
77 | QGraphicsScene *scene = new QGraphicsScene(); |
78 | if (indexMethod == "bsp" ) |
79 | index = new QGraphicsSceneBspTreeIndex(scene); |
80 | |
81 | if (indexMethod == "linear" ) |
82 | index = new QGraphicsSceneLinearIndex(scene); |
83 | |
84 | return index; |
85 | } |
86 | |
87 | void tst_QGraphicsSceneIndex::scatteredItems_data() |
88 | { |
89 | common_data(); |
90 | } |
91 | |
92 | void tst_QGraphicsSceneIndex::scatteredItems() |
93 | { |
94 | QFETCH(QString, indexMethod); |
95 | |
96 | QGraphicsScene scene; |
97 | scene.setItemIndexMethod(indexMethod == "linear" ? QGraphicsScene::NoIndex : QGraphicsScene::BspTreeIndex); |
98 | |
99 | for (int i = 0; i < 10; ++i) |
100 | scene.addRect(x: i*50, y: i*50, w: 40, h: 35); |
101 | |
102 | QCOMPARE(scene.items(QPointF(5, 5)).count(), 1); |
103 | QCOMPARE(scene.items(QPointF(55, 55)).count(), 1); |
104 | QCOMPARE(scene.items(QPointF(-100, -100)).count(), 0); |
105 | |
106 | QCOMPARE(scene.items(QRectF(0, 0, 10, 10)).count(), 1); |
107 | QCOMPARE(scene.items(QRectF(0, 0, 1000, 1000)).count(), 10); |
108 | QCOMPARE(scene.items(QRectF(-100, -1000, 0, 0)).count(), 0); |
109 | } |
110 | |
111 | void tst_QGraphicsSceneIndex::overlappedItems_data() |
112 | { |
113 | common_data(); |
114 | } |
115 | |
116 | void tst_QGraphicsSceneIndex::overlappedItems() |
117 | { |
118 | QFETCH(QString, indexMethod); |
119 | |
120 | QGraphicsScene scene; |
121 | scene.setItemIndexMethod(indexMethod == "linear" ? QGraphicsScene::NoIndex : QGraphicsScene::BspTreeIndex); |
122 | |
123 | for (int i = 0; i < 10; ++i) |
124 | for (int j = 0; j < 10; ++j) |
125 | scene.addRect(x: i*50, y: j*50, w: 200, h: 200)->setPen(QPen(Qt::black, 0)); |
126 | |
127 | QCOMPARE(scene.items(QPointF(5, 5)).count(), 1); |
128 | QCOMPARE(scene.items(QPointF(55, 55)).count(), 4); |
129 | QCOMPARE(scene.items(QPointF(105, 105)).count(), 9); |
130 | QCOMPARE(scene.items(QPointF(-100, -100)).count(), 0); |
131 | |
132 | QCOMPARE(scene.items(QRectF(0, 0, 1000, 1000)).count(), 100); |
133 | QCOMPARE(scene.items(QRectF(-100, -1000, 0, 0)).count(), 0); |
134 | QCOMPARE(scene.items(QRectF(0, 0, 200, 200)).count(), 16); |
135 | QCOMPARE(scene.items(QRectF(0, 0, 100, 100)).count(), 4); |
136 | QCOMPARE(scene.items(QRectF(0, 0, 1, 100)).count(), 2); |
137 | QCOMPARE(scene.items(QRectF(0, 0, 1, 1000)).count(), 10); |
138 | } |
139 | |
140 | void tst_QGraphicsSceneIndex::movingItems_data() |
141 | { |
142 | common_data(); |
143 | } |
144 | |
145 | void tst_QGraphicsSceneIndex::movingItems() |
146 | { |
147 | QFETCH(QString, indexMethod); |
148 | |
149 | QGraphicsScene scene; |
150 | scene.setItemIndexMethod(indexMethod == "linear" ? QGraphicsScene::NoIndex : QGraphicsScene::BspTreeIndex); |
151 | |
152 | for (int i = 0; i < 10; ++i) |
153 | scene.addRect(x: i*50, y: i*50, w: 40, h: 35); |
154 | |
155 | QGraphicsRectItem *box = scene.addRect(x: 0, y: 0, w: 10, h: 10); |
156 | QCOMPARE(scene.items(QPointF(5, 5)).count(), 2); |
157 | QCOMPARE(scene.items(QPointF(-1, -1)).count(), 0); |
158 | QCOMPARE(scene.items(QRectF(0, 0, 5, 5)).count(), 2); |
159 | |
160 | box->setPos(ax: 10, ay: 10); |
161 | QCOMPARE(scene.items(QPointF(9, 9)).count(), 1); |
162 | QCOMPARE(scene.items(QPointF(15, 15)).count(), 2); |
163 | QCOMPARE(scene.items(QRectF(0, 0, 1, 1)).count(), 1); |
164 | |
165 | box->setPos(ax: -5, ay: -5); |
166 | QCOMPARE(scene.items(QPointF(-1, -1)).count(), 1); |
167 | QCOMPARE(scene.items(QRectF(0, 0, 1, 1)).count(), 2); |
168 | |
169 | QCOMPARE(scene.items(QRectF(0, 0, 1000, 1000)).count(), 11); |
170 | } |
171 | |
172 | void tst_QGraphicsSceneIndex::connectedToSceneRectChanged() |
173 | { |
174 | |
175 | class MyScene : public QGraphicsScene |
176 | { |
177 | public: |
178 | using QGraphicsScene::receivers; |
179 | }; |
180 | |
181 | MyScene scene; // Uses QGraphicsSceneBspTreeIndex by default. |
182 | QCOMPARE(scene.receivers(SIGNAL(sceneRectChanged(QRectF))), 1); |
183 | |
184 | scene.setItemIndexMethod(QGraphicsScene::NoIndex); // QGraphicsSceneLinearIndex |
185 | QCOMPARE(scene.receivers(SIGNAL(sceneRectChanged(QRectF))), 1); |
186 | } |
187 | |
188 | void tst_QGraphicsSceneIndex::items() |
189 | { |
190 | QGraphicsScene scene; |
191 | QGraphicsItem *item1 = scene.addRect(x: 0, y: 0, w: 10, h: 10); |
192 | QGraphicsItem *item2 = scene.addRect(x: 10, y: 10, w: 10, h: 10); |
193 | QCOMPARE(scene.items().size(), 2); |
194 | |
195 | // Move from unindexed items into bsp tree. |
196 | QTest::qWait(ms: 50); |
197 | QCOMPARE(scene.items().size(), 2); |
198 | |
199 | // Add untransformable item. |
200 | QGraphicsItem *item3 = new QGraphicsRectItem(QRectF(20, 20, 10, 10)); |
201 | item3->setFlag(flag: QGraphicsItem::ItemIgnoresTransformations); |
202 | scene.addItem(item: item3); |
203 | QCOMPARE(scene.items().size(), 3); |
204 | |
205 | // Move from unindexed items into untransformable items. |
206 | QTest::qWait(ms: 50); |
207 | QCOMPARE(scene.items().size(), 3); |
208 | |
209 | // Move from untransformable items into unindexed items. |
210 | item3->setFlag(flag: QGraphicsItem::ItemIgnoresTransformations, enabled: false); |
211 | QCOMPARE(scene.items().size(), 3); |
212 | QTest::qWait(ms: 50); |
213 | QCOMPARE(scene.items().size(), 3); |
214 | |
215 | // Make all items untransformable. |
216 | item1->setFlag(flag: QGraphicsItem::ItemIgnoresTransformations); |
217 | item2->setParentItem(item1); |
218 | item3->setParentItem(item2); |
219 | QCOMPARE(scene.items().size(), 3); |
220 | |
221 | // Move from unindexed items into untransformable items. |
222 | QTest::qWait(ms: 50); |
223 | QCOMPARE(scene.items().size(), 3); |
224 | } |
225 | |
226 | class CustomShapeItem : public QGraphicsItem |
227 | { |
228 | public: |
229 | CustomShapeItem(const QPainterPath &shape) : QGraphicsItem(0), mShape(shape) {} |
230 | |
231 | QPainterPath shape() const { return mShape; } |
232 | QRectF boundingRect() const { return mShape.boundingRect(); } |
233 | void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*) {} |
234 | private: |
235 | QPainterPath mShape; |
236 | }; |
237 | |
238 | Q_DECLARE_METATYPE(Qt::ItemSelectionMode) |
239 | Q_DECLARE_METATYPE(QPainterPath) |
240 | |
241 | void tst_QGraphicsSceneIndex::boundingRectPointIntersection_data() |
242 | { |
243 | QTest::addColumn<QPainterPath>(name: "itemShape" ); |
244 | QTest::addColumn<Qt::ItemSelectionMode>(name: "mode" ); |
245 | |
246 | QTest::newRow(dataTag: "zero shape - intersects rect" ) << QPainterPath() << Qt::IntersectsItemBoundingRect; |
247 | QTest::newRow(dataTag: "zero shape - contains rect" ) << QPainterPath() << Qt::ContainsItemBoundingRect; |
248 | |
249 | QPainterPath triangle; |
250 | triangle.moveTo(x: 50, y: 0); |
251 | triangle.lineTo(x: 0, y: 50); |
252 | triangle.lineTo(x: 100, y: 50); |
253 | triangle.lineTo(x: 50, y: 0); |
254 | QTest::newRow(dataTag: "triangle shape - intersects rect" ) << triangle << Qt::IntersectsItemBoundingRect; |
255 | QTest::newRow(dataTag: "triangle shape - contains rect" ) << triangle << Qt::ContainsItemBoundingRect; |
256 | |
257 | QPainterPath rect; |
258 | rect.addRect(rect: QRectF(0, 0, 100, 100)); |
259 | QTest::newRow(dataTag: "rectangle shape - intersects rect" ) << rect << Qt::IntersectsItemBoundingRect; |
260 | QTest::newRow(dataTag: "rectangle shape - contains rect" ) << rect << Qt::ContainsItemBoundingRect; |
261 | } |
262 | |
263 | void tst_QGraphicsSceneIndex::boundingRectPointIntersection() |
264 | { |
265 | QFETCH(QPainterPath, itemShape); |
266 | QFETCH(Qt::ItemSelectionMode, mode); |
267 | |
268 | QGraphicsScene scene; |
269 | CustomShapeItem *item = new CustomShapeItem(itemShape); |
270 | scene.addItem(item); |
271 | QList<QGraphicsItem*> items = scene.items(pos: QPointF(0, 0), mode, order: Qt::AscendingOrder); |
272 | QVERIFY(!items.isEmpty()); |
273 | QCOMPARE(items.first(), item); |
274 | } |
275 | |
276 | class RectWidget : public QGraphicsWidget |
277 | { |
278 | Q_OBJECT |
279 | public: |
280 | RectWidget(QGraphicsItem *parent = 0) : QGraphicsWidget(parent) |
281 | { |
282 | } |
283 | |
284 | void paint(QPainter *painter, const QStyleOptionGraphicsItem * /* option */, QWidget * /* widget */) |
285 | { |
286 | painter->setBrush(brush); |
287 | painter->drawRect(rect: boundingRect()); |
288 | } |
289 | public: |
290 | QBrush brush; |
291 | }; |
292 | |
293 | void tst_QGraphicsSceneIndex::removeItems() |
294 | { |
295 | QGraphicsScene scene; |
296 | |
297 | RectWidget *parent = new RectWidget; |
298 | parent->brush = QBrush(QColor(Qt::magenta)); |
299 | parent->setGeometry(ax: 250, ay: 250, aw: 400, ah: 400); |
300 | |
301 | RectWidget *widget = new RectWidget(parent); |
302 | widget->brush = QBrush(QColor(Qt::blue)); |
303 | widget->setGeometry(ax: 10, ay: 10, aw: 200, ah: 200); |
304 | |
305 | RectWidget *widgetChild1 = new RectWidget(widget); |
306 | widgetChild1->brush = QBrush(QColor(Qt::green)); |
307 | widgetChild1->setGeometry(ax: 20, ay: 20, aw: 100, ah: 100); |
308 | |
309 | RectWidget *widgetChild2 = new RectWidget(widgetChild1); |
310 | widgetChild2->brush = QBrush(QColor(Qt::yellow)); |
311 | widgetChild2->setGeometry(ax: 25, ay: 25, aw: 50, ah: 50); |
312 | |
313 | scene.addItem(item: parent); |
314 | |
315 | QGraphicsView view(&scene); |
316 | view.resize(w: 600, h: 600); |
317 | view.show(); |
318 | QApplication::setActiveWindow(&view); |
319 | QVERIFY(QTest::qWaitForWindowActive(&view)); |
320 | |
321 | scene.removeItem(item: widgetChild1); |
322 | |
323 | delete widgetChild1; |
324 | |
325 | //We move the parent |
326 | scene.items(rect: QRectF(295, 295, 50, 50)); |
327 | |
328 | //This should not crash |
329 | } |
330 | |
331 | void tst_QGraphicsSceneIndex::clear() |
332 | { |
333 | class MyItem : public QGraphicsItem |
334 | { |
335 | public: |
336 | MyItem(QGraphicsItem *parent = 0) : QGraphicsItem(parent), numPaints(0) {} |
337 | int numPaints; |
338 | protected: |
339 | QRectF boundingRect() const { return QRectF(0, 0, 10, 10); } |
340 | void paint(QPainter * /* painter */, const QStyleOptionGraphicsItem *, QWidget *) |
341 | { ++numPaints; } |
342 | }; |
343 | |
344 | QGraphicsScene scene; |
345 | scene.setSceneRect(x: 0, y: 0, w: 100, h: 100); |
346 | scene.addItem(item: new MyItem); |
347 | |
348 | QGraphicsView view(&scene); |
349 | view.show(); |
350 | qApp->setActiveWindow(&view); |
351 | QVERIFY(QTest::qWaitForWindowActive(&view)); |
352 | scene.clear(); |
353 | |
354 | // Make sure the index is re-generated after QGraphicsScene::clear(); |
355 | // otherwise no items will be painted. |
356 | MyItem *item = new MyItem; |
357 | scene.addItem(item); |
358 | qApp->processEvents(); |
359 | #ifdef Q_OS_WINRT |
360 | QEXPECT_FAIL("" , "There is one additional paint event on WinRT - QTBUG-68297" , Abort); |
361 | #endif |
362 | QTRY_COMPARE(item->numPaints, 1); |
363 | } |
364 | |
365 | QTEST_MAIN(tst_QGraphicsSceneIndex) |
366 | #include "tst_qgraphicssceneindex.moc" |
367 | |