diff options
-rw-r--r-- | src/quick/items/qquickitem.cpp | 91 | ||||
-rw-r--r-- | src/quick/items/qquickitem.h | 7 | ||||
-rw-r--r-- | src/quick/items/qquickitem_p.h | 6 | ||||
-rw-r--r-- | src/quick/items/qquickmousearea.cpp | 25 | ||||
-rw-r--r-- | src/quick/items/qquickmousearea_p.h | 2 | ||||
-rw-r--r-- | src/quick/items/qquickwindow.cpp | 50 | ||||
-rw-r--r-- | src/quick/items/qquickwindow_p.h | 7 | ||||
-rw-r--r-- | tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp | 33 | ||||
-rw-r--r-- | tests/auto/quick/qquickwindow/tst_qquickwindow.cpp | 143 |
9 files changed, 345 insertions, 19 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index fefddad4b6..5930b5fb0b 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -68,6 +68,10 @@ #include <private/qqmlaccessors_p.h> #include <QtQuick/private/qquickaccessibleattached_p.h> +#ifndef QT_NO_CURSOR +# include <QtGui/qcursor.h> +#endif + #include <float.h> // XXX todo Check that elements that create items handle memory correctly after visual ownership change @@ -2266,6 +2270,10 @@ void QQuickItemPrivate::derefWindow() } if (c->mouseGrabberItem == q) c->mouseGrabberItem = 0; +#ifndef QT_NO_CURSOR + if (c->cursorItem == q) + c->cursorItem = 0; +#endif if ( hoverEnabled ) c->hoverItems.removeAll(q); if (itemNodeInstance) @@ -2429,6 +2437,7 @@ QQuickItemPrivate::QQuickItemPrivate() , inheritMirrorFromItem(false) , isAccessible(false) , culled(false) + , hasCursor(false) , dirtyAttributes(0) , nextDirtyItem(0) , prevDirtyItem(0) @@ -5113,6 +5122,88 @@ void QQuickItem::setAcceptHoverEvents(bool enabled) d->hoverEnabled = enabled; } +#ifndef QT_NO_CURSOR + + +/*! + Returns the cursor shape for this item. + + The mouse cursor will assume this shape when it is over this + item, unless an override cursor is set. + See the \l{Qt::CursorShape}{list of predefined cursor objects} for a + range of useful shapes. + + If no cursor shape has been set this returns a cursor with the Qt::ArrowCursor shape, however + another cursor shape may be displayed if an overlapping item has a valid cursor. + + \sa setCursor(), unsetCursor() +*/ + +QCursor QQuickItem::cursor() const +{ + Q_D(const QQuickItem); + return d->extra.isAllocated() + ? d->extra->cursor + : QCursor(); +} + +/*! + Sets the \a cursor shape for this item. + + \sa cursor(), unsetCursor() +*/ + +void QQuickItem::setCursor(const QCursor &cursor) +{ + Q_D(QQuickItem); + + Qt::CursorShape oldShape = d->extra.isAllocated() ? d->extra->cursor.shape() : Qt::ArrowCursor; + + if (oldShape != cursor.shape() || oldShape >= Qt::LastCursor || cursor.shape() >= Qt::LastCursor) { + d->extra.value().cursor = cursor; + if (d->window) { + QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(d->window); + if (windowPrivate->cursorItem == this) + d->window->setCursor(cursor); + } + } + + if (!d->hasCursor) { + d->hasCursor = true; + if (d->window) { + QPointF pos = d->window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition.toPoint()); + if (contains(mapFromScene(pos))) + QQuickWindowPrivate::get(d->window)->updateCursor(pos); + } + } +} + +/*! + Clears the cursor shape for this item. + + \sa cursor(), setCursor() +*/ + +void QQuickItem::unsetCursor() +{ + Q_D(QQuickItem); + if (!d->hasCursor) + return; + d->hasCursor = false; + if (d->extra.isAllocated()) + d->extra->cursor = QCursor(); + + if (d->window) { + QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(d->window); + if (windowPrivate->cursorItem == this) { + QPointF pos = d->window->mapFromGlobal(QGuiApplicationPrivate::lastCursorPosition.toPoint()); + windowPrivate->updateCursor(pos); + } + } +} + +#endif + void QQuickItem::grabMouse() { Q_D(QQuickItem); diff --git a/src/quick/items/qquickitem.h b/src/quick/items/qquickitem.h index 61bb3250ea..8854449d2b 100644 --- a/src/quick/items/qquickitem.h +++ b/src/quick/items/qquickitem.h @@ -80,6 +80,7 @@ private: Q_DECLARE_PRIVATE(QQuickTransform) }; +class QCursor; class QQuickItemLayer; class QQmlV8Function; class QQuickState; @@ -282,6 +283,12 @@ public: bool acceptHoverEvents() const; void setAcceptHoverEvents(bool enabled); +#ifndef QT_NO_CURSOR + QCursor cursor() const; + void setCursor(const QCursor &cursor); + void unsetCursor(); +#endif + bool isUnderMouse() const; void grabMouse(); void ungrabMouse(); diff --git a/src/quick/items/qquickitem_p.h b/src/quick/items/qquickitem_p.h index adfe384b25..9dc7344b89 100644 --- a/src/quick/items/qquickitem_p.h +++ b/src/quick/items/qquickitem_p.h @@ -342,6 +342,9 @@ public: QQuickLayoutMirroringAttached* layoutDirectionAttached; QQuickItemKeyFilter *keyHandler; mutable QQuickItemLayer *layer; +#ifndef QT_NO_CURSOR + QCursor cursor; +#endif QPointF userTransformOriginPoint; int effectRefCount; @@ -408,7 +411,8 @@ public: bool inheritMirrorFromItem:1; bool isAccessible:1; bool culled:1; - // bool dummy:2 + bool hasCursor:1; + // bool dummy:1 // Bit 32 enum DirtyType { diff --git a/src/quick/items/qquickmousearea.cpp b/src/quick/items/qquickmousearea.cpp index 5cfd62ff8f..85a224fc93 100644 --- a/src/quick/items/qquickmousearea.cpp +++ b/src/quick/items/qquickmousearea.cpp @@ -1136,15 +1136,6 @@ void QQuickMouseArea::setHovered(bool h) d->hovered = h; emit hoveredChanged(); d->hovered ? emit entered() : emit exited(); -#ifndef QT_NO_CURSOR - if (d->cursor) { - if (d->hovered) { - window()->setCursor(QCursor(*d->cursor)); - } else { - window()->unsetCursor(); - } - } -#endif } } @@ -1274,19 +1265,19 @@ bool QQuickMouseArea::setPressed(Qt::MouseButton button, bool p) #ifndef QT_NO_CURSOR Qt::CursorShape QQuickMouseArea::cursorShape() const { - Q_D(const QQuickMouseArea); - if (d->cursor) - return d->cursor->shape(); - return Qt::ArrowCursor; + return cursor().shape(); } void QQuickMouseArea::setCursorShape(Qt::CursorShape shape) { - Q_D(QQuickMouseArea); - setHoverEnabled(true); - delete d->cursor; - d->cursor = new QCursor(shape); + if (cursor().shape() == shape) + return; + + setCursor(shape); + + emit cursorShapeChanged(); } + #endif /*! diff --git a/src/quick/items/qquickmousearea_p.h b/src/quick/items/qquickmousearea_p.h index 8a8de3bdc3..3385485cac 100644 --- a/src/quick/items/qquickmousearea_p.h +++ b/src/quick/items/qquickmousearea_p.h @@ -144,7 +144,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickMouseArea : public QQuickItem Q_PROPERTY(bool preventStealing READ preventStealing WRITE setPreventStealing NOTIFY preventStealingChanged) Q_PROPERTY(bool propagateComposedEvents READ propagateComposedEvents WRITE setPropagateComposedEvents NOTIFY propagateComposedEventsChanged) #ifndef QT_NO_CURSOR - Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape NOTIFY cursorShapeChanged) + Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape RESET unsetCursor NOTIFY cursorShapeChanged) #endif public: diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 1d3adea5cb..4cfe1a101c 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -326,6 +326,9 @@ QQuickWindowPrivate::QQuickWindowPrivate() : rootItem(0) , activeFocusItem(0) , mouseGrabberItem(0) +#ifndef QT_NO_CURSOR + , cursorItem(0) +#endif , touchMouseId(-1) , touchMousePressTimestamp(0) , renderWithoutShowing(false) @@ -1301,6 +1304,10 @@ void QQuickWindow::mouseMoveEvent(QMouseEvent *event) qWarning() << "QQuickWindow::mouseMoveEvent()" << event->localPos() << event->button() << event->buttons(); #endif +#ifndef QT_NO_CURSOR + d->updateCursor(event->windowPos()); +#endif + if (!d->mouseGrabberItem) { if (d->lastMousePosition.isNull()) d->lastMousePosition = event->windowPos(); @@ -1832,6 +1839,49 @@ bool QQuickWindowPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickIte } #endif // QT_NO_DRAGANDDROP +#ifndef QT_NO_CURSOR +void QQuickWindowPrivate::updateCursor(const QPointF &scenePos) +{ + Q_Q(QQuickWindow); + + QQuickItem *oldCursorItem = cursorItem; + cursorItem = findCursorItem(rootItem, scenePos); + + if (cursorItem != oldCursorItem) { + if (cursorItem) + q->setCursor(cursorItem->cursor()); + else + q->unsetCursor(); + } +} + +QQuickItem *QQuickWindowPrivate::findCursorItem(QQuickItem *item, const QPointF &scenePos) +{ + QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item); + if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) { + QPointF p = item->mapFromScene(scenePos); + if (!item->contains(p)) + return 0; + } + + QList<QQuickItem *> children = itemPrivate->paintOrderChildItems(); + for (int ii = children.count() - 1; ii >= 0; --ii) { + QQuickItem *child = children.at(ii); + if (!child->isVisible() || !child->isEnabled()) + continue; + if (QQuickItem *cursorItem = findCursorItem(child, scenePos)) + return cursorItem; + } + + if (itemPrivate->hasCursor) { + QPointF p = item->mapFromScene(scenePos); + if (item->contains(p)) + return item; + } + return 0; +} +#endif + bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem *item, QTouchEvent *event) { if (!target) diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 52e46ca6a8..267c8d3104 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -113,6 +113,9 @@ public: // Keeps track of the item currently receiving mouse events QQuickItem *mouseGrabberItem; +#ifndef QT_NO_CURSOR + QQuickItem *cursorItem; +#endif #ifndef QT_NO_DRAGANDDROP QQuickDragGrabber dragGrabber; #endif @@ -145,6 +148,10 @@ public: void deliverDragEvent(QQuickDragGrabber *, QEvent *); bool deliverDragEvent(QQuickDragGrabber *, QQuickItem *, QDragMoveEvent *); #endif +#ifndef QT_NO_CURSOR + void updateCursor(const QPointF &scenePos); + QQuickItem *findCursorItem(QQuickItem *item, const QPointF &scenePos); +#endif QList<QQuickItem*> hoverItems; enum FocusOption { diff --git a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp index 67a36af6bb..f7ccd67f5f 100644 --- a/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp +++ b/tests/auto/quick/qquickmousearea/tst_qquickmousearea.cpp @@ -85,6 +85,9 @@ private slots: void pressedMultipleButtons_data(); void pressedMultipleButtons(); void changeAxis(); +#ifndef QT_NO_CURSOR + void cursorShape(); +#endif private: void acceptedButton_data(); @@ -1366,6 +1369,36 @@ void tst_QQuickMouseArea::changeAxis() delete view; } +#ifndef QT_NO_CURSOR +void tst_QQuickMouseArea::cursorShape() +{ + QQmlEngine engine; + QQmlComponent component(&engine); + component.setData("import QtQuick 2.0\n MouseArea {}", QUrl()); + QScopedPointer<QObject> object(component.create()); + QQuickMouseArea *mouseArea = qobject_cast<QQuickMouseArea *>(object.data()); + QVERIFY(mouseArea); + + QSignalSpy spy(mouseArea, SIGNAL(cursorShapeChanged())); + + QCOMPARE(mouseArea->cursorShape(), Qt::ArrowCursor); + QCOMPARE(mouseArea->cursor().shape(), Qt::ArrowCursor); + + mouseArea->setCursorShape(Qt::IBeamCursor); + QCOMPARE(mouseArea->cursorShape(), Qt::IBeamCursor); + QCOMPARE(mouseArea->cursor().shape(), Qt::IBeamCursor); + QCOMPARE(spy.count(), 1); + + mouseArea->setCursorShape(Qt::IBeamCursor); + QCOMPARE(spy.count(), 1); + + mouseArea->setCursorShape(Qt::WaitCursor); + QCOMPARE(mouseArea->cursorShape(), Qt::WaitCursor); + QCOMPARE(mouseArea->cursor().shape(), Qt::WaitCursor); + QCOMPARE(spy.count(), 2); +} +#endif + QTEST_MAIN(tst_QQuickMouseArea) #include "tst_qquickmousearea.moc" diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index 9800c724b9..01d60361b4 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -310,6 +310,11 @@ private slots: void ignoreUnhandledMouseEvents(); void ownershipRootItem(); + +#ifndef QT_NO_CURSOR + void cursor(); +#endif + private: QTouchDevice *touchDevice; QTouchDevice *touchDeviceWithVelocity; @@ -1061,6 +1066,144 @@ void tst_qquickwindow::ownershipRootItem() QCoreApplication::processEvents(); QVERIFY(!accessor->isRootItemDestroyed()); } + +#ifndef QT_NO_CURSOR +void tst_qquickwindow::cursor() +{ + QQuickWindow window; + window.resize(320, 240); + + QQuickItem parentItem; + parentItem.setPos(QPointF(0, 0)); + parentItem.setSize(QSizeF(180, 180)); + parentItem.setParentItem(window.rootItem()); + + QQuickItem childItem; + childItem.setPos(QPointF(60, 90)); + childItem.setSize(QSizeF(120, 120)); + childItem.setParentItem(&parentItem); + + QQuickItem clippingItem; + clippingItem.setPos(QPointF(120, 120)); + clippingItem.setSize(QSizeF(180, 180)); + clippingItem.setClip(true); + clippingItem.setParentItem(window.rootItem()); + + QQuickItem clippedItem; + clippedItem.setPos(QPointF(-30, -30)); + clippedItem.setSize(QSizeF(120, 120)); + clippedItem.setParentItem(&clippingItem); + + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + // Position the cursor over the parent and child item and the clipped section of clippedItem. + QTest::mouseMove(&window, QPoint(100, 100)); + + // No items cursors, window cursor is the default arrow. + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + // The section of clippedItem under the cursor is clipped, and so doesn't affect the window cursor. + clippedItem.setCursor(Qt::ForbiddenCursor); + QCOMPARE(clippedItem.cursor().shape(), Qt::ForbiddenCursor); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + // parentItem is under the cursor, so the window cursor is changed. + parentItem.setCursor(Qt::IBeamCursor); + QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); + + // childItem is under the cursor and is in front of its parent, so the window cursor is changed. + childItem.setCursor(Qt::WaitCursor); + QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + childItem.setCursor(Qt::PointingHandCursor); + QCOMPARE(childItem.cursor().shape(), Qt::PointingHandCursor); + QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); + + // childItem is the current cursor item, so this has no effect on the window cursor. + parentItem.unsetCursor(); + QCOMPARE(parentItem.cursor().shape(), Qt::ArrowCursor); + QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); + + parentItem.setCursor(Qt::IBeamCursor); + QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor); + QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); + + // With the childItem cursor cleared, parentItem is now foremost. + childItem.unsetCursor(); + QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); + + // Setting the childItem cursor to the default still takes precedence over parentItem. + childItem.setCursor(Qt::ArrowCursor); + QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + childItem.setCursor(Qt::WaitCursor); + QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + // Move the cursor so it is over just parentItem. + QTest::mouseMove(&window, QPoint(20, 20)); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); + + // Move the cursor so that is over all items, clippedItem wins because its a child of + // clippingItem which is in from of parentItem in painting order. + QTest::mouseMove(&window, QPoint(125, 125)); + QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor); + + // Over clippingItem only, so no cursor. + QTest::mouseMove(&window, QPoint(200, 280)); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + // Over no item, so no cursor. + QTest::mouseMove(&window, QPoint(10, 280)); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + + // back to the start. + QTest::mouseMove(&window, QPoint(100, 100)); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + // Try with the mouse pressed. + QTest::mousePress(&window, Qt::LeftButton, 0, QPoint(100, 100)); + QTest::mouseMove(&window, QPoint(20, 20)); + QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); + QTest::mouseMove(&window, QPoint(125, 125)); + QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor); + QTest::mouseMove(&window, QPoint(200, 280)); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + QTest::mouseMove(&window, QPoint(10, 280)); + QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); + QTest::mouseMove(&window, QPoint(100, 100)); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + QTest::mouseRelease(&window, Qt::LeftButton, 0, QPoint(100, 100)); + + // Remove the cursor item from the scene. Theoretically this should make parentItem the + // cursorItem, but given the situation will correct itself after the next mouse move it's + // probably better left as is to avoid unnecessary work during tear down. + childItem.setParentItem(0); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + parentItem.setCursor(Qt::SizeAllCursor); + QCOMPARE(parentItem.cursor().shape(), Qt::SizeAllCursor); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + // Changing the cursor of an un-parented item doesn't affect the window's cursor. + childItem.setCursor(Qt::ClosedHandCursor); + QCOMPARE(childItem.cursor().shape(), Qt::ClosedHandCursor); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + childItem.unsetCursor(); + QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); + QCOMPARE(window.cursor().shape(), Qt::WaitCursor); + + QTest::mouseRelease(&window, Qt::LeftButton, 0, QPoint(100, 101)); + QCOMPARE(window.cursor().shape(), Qt::SizeAllCursor); +} +#endif + QTEST_MAIN(tst_qquickwindow) #include "tst_qquickwindow.moc" |