| 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 | |