Add contains method to QQuickItem public API
authorAdriano Rezende <atdrez@gmail.com>
Sun, 26 Feb 2012 16:26:53 +0000 (17:26 +0100)
committerQt by Nokia <qt-info@nokia.com>
Thu, 19 Apr 2012 04:16:28 +0000 (06:16 +0200)
This method can be overwritten in order to provide fine grained control
over the mouse events handled by the item.

Change-Id: I23cb61958d3ac0b2f5091c47fa9e0ed07dc5e5d0
Reviewed-by: Martin Jones <martin.jones@nokia.com>
19 files changed:
examples/quick/maskedmousearea/images/cloud_1.png [new file with mode: 0644]
examples/quick/maskedmousearea/images/cloud_2.png [new file with mode: 0644]
examples/quick/maskedmousearea/images/moon.png [new file with mode: 0644]
examples/quick/maskedmousearea/main.cpp [new file with mode: 0644]
examples/quick/maskedmousearea/maskedmousearea.cpp [new file with mode: 0644]
examples/quick/maskedmousearea/maskedmousearea.h [new file with mode: 0644]
examples/quick/maskedmousearea/maskedmousearea.pro [new file with mode: 0644]
examples/quick/maskedmousearea/maskedmousearea.qml [new file with mode: 0644]
examples/quick/maskedmousearea/maskedmousearea.qmlproject [new file with mode: 0644]
src/quick/items/qquickcanvas.cpp
src/quick/items/qquickflickable.cpp
src/quick/items/qquickitem.cpp
src/quick/items/qquickitem.h
src/quick/items/qquickmousearea.cpp
src/quick/items/qquickmultipointtoucharea.cpp
src/quick/items/qquickpathview.cpp
src/quick/items/qquickpincharea.cpp
tests/auto/quick/qquickitem2/data/hollowTestItem.qml [new file with mode: 0644]
tests/auto/quick/qquickitem2/tst_qquickitem.cpp

diff --git a/examples/quick/maskedmousearea/images/cloud_1.png b/examples/quick/maskedmousearea/images/cloud_1.png
new file mode 100644 (file)
index 0000000..87c54af
Binary files /dev/null and b/examples/quick/maskedmousearea/images/cloud_1.png differ
diff --git a/examples/quick/maskedmousearea/images/cloud_2.png b/examples/quick/maskedmousearea/images/cloud_2.png
new file mode 100644 (file)
index 0000000..981bbd2
Binary files /dev/null and b/examples/quick/maskedmousearea/images/cloud_2.png differ
diff --git a/examples/quick/maskedmousearea/images/moon.png b/examples/quick/maskedmousearea/images/moon.png
new file mode 100644 (file)
index 0000000..0a8037d
Binary files /dev/null and b/examples/quick/maskedmousearea/images/moon.png differ
diff --git a/examples/quick/maskedmousearea/main.cpp b/examples/quick/maskedmousearea/main.cpp
new file mode 100644 (file)
index 0000000..407497e
--- /dev/null
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QGuiApplication>
+#include <QQuickView>
+
+#include "maskedmousearea.h"
+
+
+int main(int argc, char* argv[])
+{
+    QGuiApplication app(argc,argv);
+    QQuickView view;
+
+    qmlRegisterType<MaskedMouseArea>("Example", 1, 0, "MaskedMouseArea");
+
+    view.setSource(QUrl::fromLocalFile("maskedmousearea.qml"));
+    view.show();
+    return app.exec();
+}
diff --git a/examples/quick/maskedmousearea/maskedmousearea.cpp b/examples/quick/maskedmousearea/maskedmousearea.cpp
new file mode 100644 (file)
index 0000000..3f872a6
--- /dev/null
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "maskedmousearea.h"
+
+#include <QStyleHints>
+#include <QGuiApplication>
+
+
+MaskedMouseArea::MaskedMouseArea(QQuickItem *parent)
+    : QQuickItem(parent),
+      m_pressed(false),
+      m_alphaThreshold(0.0),
+      m_containsMouse(false)
+{
+    setAcceptHoverEvents(true);
+    setAcceptedMouseButtons(Qt::LeftButton);
+}
+
+void MaskedMouseArea::setPressed(bool pressed)
+{
+    if (m_pressed != pressed) {
+        m_pressed = pressed;
+        emit pressedChanged();
+    }
+}
+
+void MaskedMouseArea::setContainsMouse(bool containsMouse)
+{
+    if (m_containsMouse != containsMouse) {
+        m_containsMouse = containsMouse;
+        emit containsMouseChanged();
+    }
+}
+
+void MaskedMouseArea::setMaskSource(const QUrl &source)
+{
+    if (m_maskSource != source) {
+        m_maskSource = source;
+        m_maskImage = QImage(source.toLocalFile());
+        emit maskSourceChanged();
+    }
+}
+
+void MaskedMouseArea::setAlphaThreshold(qreal threshold)
+{
+    if (m_alphaThreshold != threshold) {
+        m_alphaThreshold = threshold;
+        emit alphaThresholdChanged();
+    }
+}
+
+bool MaskedMouseArea::contains(const QPointF &point) const
+{
+    if (!QQuickItem::contains(point) || m_maskImage.isNull())
+        return false;
+
+    QPoint p = point.toPoint();
+
+    if (p.x() < 0 || p.x() >= m_maskImage.width() ||
+        p.y() < 0 || p.y() >= m_maskImage.height())
+        return false;
+
+    qreal r = qBound<int>(0, m_alphaThreshold * 255, 255);
+    return qAlpha(m_maskImage.pixel(p)) > r;
+}
+
+void MaskedMouseArea::mousePressEvent(QMouseEvent *event)
+{
+    setPressed(true);
+    m_pressPoint = event->pos();
+    emit pressed();
+}
+
+void MaskedMouseArea::mouseReleaseEvent(QMouseEvent *event)
+{
+    setPressed(false);
+    emit released();
+
+    const int threshold = qApp->styleHints()->startDragDistance();
+    const bool isClick = (threshold >= qAbs(event->x() - m_pressPoint.x()) &&
+                          threshold >= qAbs(event->y() - m_pressPoint.y()));
+
+    if (isClick)
+        emit clicked();
+}
+
+void MaskedMouseArea::mouseUngrabEvent()
+{
+    setPressed(false);
+    emit canceled();
+}
+
+void MaskedMouseArea::hoverEnterEvent(QHoverEvent *event)
+{
+    Q_UNUSED(event);
+    setContainsMouse(true);
+}
+
+void MaskedMouseArea::hoverLeaveEvent(QHoverEvent *event)
+{
+    Q_UNUSED(event);
+    setContainsMouse(false);
+}
diff --git a/examples/quick/maskedmousearea/maskedmousearea.h b/examples/quick/maskedmousearea/maskedmousearea.h
new file mode 100644 (file)
index 0000000..045d02d
--- /dev/null
@@ -0,0 +1,98 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef MASKEDMOUSEAREA_H
+#define MASKEDMOUSEAREA_H
+
+#include <QImage>
+#include <QQuickItem>
+
+
+class MaskedMouseArea : public QQuickItem
+{
+    Q_OBJECT
+    Q_PROPERTY(bool pressed READ isPressed NOTIFY pressedChanged)
+    Q_PROPERTY(bool containsMouse READ containsMouse NOTIFY containsMouseChanged)
+    Q_PROPERTY(QUrl maskSource READ maskSource WRITE setMaskSource NOTIFY maskSourceChanged)
+    Q_PROPERTY(qreal alphaThreshold READ alphaThreshold WRITE setAlphaThreshold NOTIFY alphaThresholdChanged)
+
+public:
+    MaskedMouseArea(QQuickItem *parent = 0);
+
+    bool contains(const QPointF &point) const;
+
+    bool isPressed() const { return m_pressed; }
+    bool containsMouse() const { return m_containsMouse; }
+
+    QUrl maskSource() const { return m_maskSource; }
+    void setMaskSource(const QUrl &source);
+
+    qreal alphaThreshold() const { return m_alphaThreshold; }
+    void setAlphaThreshold(qreal threshold);
+
+signals:
+    void pressed();
+    void released();
+    void clicked();
+    void canceled();
+    void pressedChanged();
+    void maskSourceChanged();
+    void containsMouseChanged();
+    void alphaThresholdChanged();
+
+protected:
+    void setPressed(bool pressed);
+    void setContainsMouse(bool containsMouse);
+    void mousePressEvent(QMouseEvent *event);
+    void mouseReleaseEvent(QMouseEvent *event);
+    void hoverEnterEvent(QHoverEvent *event);
+    void hoverLeaveEvent(QHoverEvent *event);
+    void mouseUngrabEvent();
+
+private:
+    bool m_pressed;
+    QUrl m_maskSource;
+    QImage m_maskImage;
+    QPointF m_pressPoint;
+    qreal m_alphaThreshold;
+    bool m_containsMouse;
+};
+
+#endif
diff --git a/examples/quick/maskedmousearea/maskedmousearea.pro b/examples/quick/maskedmousearea/maskedmousearea.pro
new file mode 100644 (file)
index 0000000..53e14aa
--- /dev/null
@@ -0,0 +1,14 @@
+TEMPLATE = app
+
+QT += quick qml
+
+HEADERS += maskedmousearea.h
+
+SOURCES += main.cpp \
+           maskedmousearea.cpp
+
+target.path = $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qtquick/maskedmousearea
+qml.files = maskedmousearea.qml images
+qml.path = $$[QT_INSTALL_EXAMPLES]/qtdeclarative/qtquick/maskedmousearea
+INSTALLS += target qml
+
diff --git a/examples/quick/maskedmousearea/maskedmousearea.qml b/examples/quick/maskedmousearea/maskedmousearea.qml
new file mode 100644 (file)
index 0000000..82d351b
--- /dev/null
@@ -0,0 +1,135 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:BSD$
+** 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."
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.0
+import Example 1.0
+
+Rectangle {
+    height: 480
+    width: 320
+    color: "black"
+
+    Text {
+        text: qsTr("CLICK AND HOVER")
+        opacity: 0.6
+        color: "white"
+        font.pixelSize: 20
+        anchors.top: parent.top
+        anchors.horizontalCenter: parent.horizontalCenter
+        anchors.topMargin: 50
+    }
+
+    Image {
+        id: moon
+        smooth: true
+        anchors.centerIn: parent
+        scale: moonArea.pressed ? 1.1 : 1.0
+        opacity: moonArea.containsMouse ? 1.0 : 0.7
+        source: Qt.resolvedUrl("images/moon.png")
+
+        MaskedMouseArea {
+            id: moonArea
+            anchors.fill: parent
+            alphaThreshold: 0.4
+            maskSource: moon.source
+        }
+
+        Behavior on opacity {
+            NumberAnimation { duration: 200 }
+        }
+        Behavior on scale {
+            NumberAnimation { duration: 100 }
+        }
+    }
+
+    Image {
+        id: rightCloud
+        anchors {
+            centerIn: moon
+            verticalCenterOffset: 30
+            horizontalCenterOffset: 80
+        }
+        smooth: true
+        scale: rightCloudArea.pressed ? 1.1 : 1.0
+        opacity: rightCloudArea.containsMouse ? 1.0 : 0.7
+        source: Qt.resolvedUrl("images/cloud_2.png")
+
+        MaskedMouseArea {
+            id: rightCloudArea
+            anchors.fill: parent
+            alphaThreshold: 0.4
+            maskSource: rightCloud.source
+        }
+
+        Behavior on opacity {
+            NumberAnimation { duration: 200 }
+        }
+        Behavior on scale {
+            NumberAnimation { duration: 100 }
+        }
+    }
+
+    Image {
+        id: leftCloud
+        anchors {
+            centerIn: moon
+            verticalCenterOffset: 40
+            horizontalCenterOffset: -80
+        }
+        smooth: true
+        scale: leftCloudArea.pressed ? 1.1 : 1.0
+        opacity: leftCloudArea.containsMouse ? 1.0 : 0.7
+        source: Qt.resolvedUrl("images/cloud_1.png")
+
+        MaskedMouseArea {
+            id: leftCloudArea
+            anchors.fill: parent
+            alphaThreshold: 0.4
+            maskSource: leftCloud.source
+        }
+
+        Behavior on opacity {
+            NumberAnimation { duration: 200 }
+        }
+        Behavior on scale {
+            NumberAnimation { duration: 100 }
+        }
+    }
+}
diff --git a/examples/quick/maskedmousearea/maskedmousearea.qmlproject b/examples/quick/maskedmousearea/maskedmousearea.qmlproject
new file mode 100644 (file)
index 0000000..709c198
--- /dev/null
@@ -0,0 +1,16 @@
+import QmlProject 1.1
+
+Project {
+    mainFile: "maskedmousearea.qml"
+
+    /* Include .qml, .js, and image files from current directory and subdirectories */
+    QmlFiles {
+        directory: "."
+    }
+    JavaScriptFiles {
+        directory: "."
+    }
+    ImageFiles {
+        directory: "."
+    }
+}
index a288531..9110233 100644 (file)
@@ -1039,7 +1039,7 @@ bool QQuickCanvasPrivate::deliverInitialMousePressEvent(QQuickItem *item, QMouse
 
     if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
         QPointF p = item->mapFromScene(event->windowPos());
-        if (!QRectF(0, 0, item->width(), item->height()).contains(p))
+        if (!item->contains(p))
             return false;
     }
 
@@ -1054,7 +1054,7 @@ bool QQuickCanvasPrivate::deliverInitialMousePressEvent(QQuickItem *item, QMouse
 
     if (itemPrivate->acceptedMouseButtons() & event->button()) {
         QPointF p = item->mapFromScene(event->windowPos());
-        if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
+        if (item->contains(p)) {
             QMouseEvent me(event->type(), p, event->windowPos(), event->screenPos(),
                            event->button(), event->buttons(), event->modifiers());
             me.accept();
@@ -1218,7 +1218,7 @@ bool QQuickCanvasPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce
 
     if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
         QPointF p = item->mapFromScene(scenePos);
-        if (!QRectF(0, 0, item->width(), item->height()).contains(p))
+        if (!item->contains(p))
             return false;
     }
 
@@ -1233,7 +1233,7 @@ bool QQuickCanvasPrivate::deliverHoverEvent(QQuickItem *item, const QPointF &sce
 
     if (itemPrivate->hoverEnabled) {
         QPointF p = item->mapFromScene(scenePos);
-        if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
+        if (item->contains(p)) {
             if (!hoverItems.isEmpty() && hoverItems[0] == item) {
                 //move
                 accepted = sendHoverEvent(QEvent::HoverMove, item, scenePos, lastScenePos, modifiers, accepted);
@@ -1286,7 +1286,7 @@ bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event
 
     if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
         QPointF p = item->mapFromScene(event->posF());
-        if (!QRectF(0, 0, item->width(), item->height()).contains(p))
+        if (!item->contains(p))
             return false;
     }
 
@@ -1300,7 +1300,8 @@ bool QQuickCanvasPrivate::deliverWheelEvent(QQuickItem *item, QWheelEvent *event
     }
 
     QPointF p = item->mapFromScene(event->posF());
-    if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
+
+    if (item->contains(p)) {
         QWheelEvent wheel(p, p, event->pixelDelta(), event->angleDelta(), event->delta(),
                           event->orientation(), event->buttons(), event->modifiers());
         wheel.accept();
@@ -1422,10 +1423,9 @@ bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *even
         return false;
 
     if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
-        QRectF bounds(0, 0, item->width(), item->height());
         for (int i=0; i<newPoints.count(); i++) {
             QPointF p = item->mapFromScene(newPoints[i].scenePos());
-            if (!bounds.contains(p))
+            if (!item->contains(p))
                 return false;
         }
     }
@@ -1441,12 +1441,11 @@ bool QQuickCanvasPrivate::deliverTouchPoints(QQuickItem *item, QTouchEvent *even
 
     QList<QTouchEvent::TouchPoint> matchingPoints;
     if (newPoints.count() > 0 && acceptedNewPoints->count() < newPoints.count()) {
-        QRectF bounds(0, 0, item->width(), item->height());
         for (int i=0; i<newPoints.count(); i++) {
             if (acceptedNewPoints->contains(newPoints[i].id()))
                 continue;
             QPointF p = item->mapFromScene(newPoints[i].scenePos());
-            if (bounds.contains(p))
+            if (item->contains(p))
                 matchingPoints << newPoints[i];
         }
     }
@@ -1537,7 +1536,7 @@ void QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QEvent *e
                 moveEvent->setAccepted(true);
                 for (++grabItem; grabItem != grabber->end();) {
                     QPointF p = (**grabItem)->mapFromScene(moveEvent->pos());
-                    if (QRectF(0, 0, (**grabItem)->width(), (**grabItem)->height()).contains(p)) {
+                    if ((**grabItem)->contains(p)) {
                         QDragMoveEvent translatedEvent(
                                 p.toPoint(),
                                 moveEvent->possibleActions(),
@@ -1582,7 +1581,7 @@ bool QQuickCanvasPrivate::deliverDragEvent(QQuickDragGrabber *grabber, QQuickIte
         return false;
 
     QPointF p = item->mapFromScene(event->pos());
-    if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
+    if (item->contains(p)) {
         if (event->type() == QEvent::DragMove || itemPrivate->flags & QQuickItem::ItemAcceptsDrops) {
             QDragMoveEvent translatedEvent(
                     p.toPoint(),
index 75c9919..76d3698 100644 (file)
@@ -1772,14 +1772,14 @@ void QQuickFlickable::mouseUngrabEvent()
 bool QQuickFlickable::sendMouseEvent(QMouseEvent *event)
 {
     Q_D(QQuickFlickable);
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+    QPointF localPos = mapFromScene(event->windowPos());
 
     QQuickCanvas *c = canvas();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool disabledItem = grabber && !grabber->isEnabled();
     bool stealThisEvent = d->stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
-        QQuickMouseEventEx mouseEvent(event->type(), mapFromScene(event->windowPos()),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
+        QQuickMouseEventEx mouseEvent(event->type(), localPos,
                                 event->windowPos(), event->screenPos(),
                                 event->button(), event->buttons(), event->modifiers());
         QQuickMouseEventEx *eventEx = QQuickMouseEventEx::extended(event);
index ffaec54..628e19d 100644 (file)
@@ -4886,9 +4886,7 @@ bool QQuickItem::isUnderMouse() const
         return false;
 
     QPointF cursorPos = QGuiApplicationPrivate::lastCursorPosition;
-    if (QRectF(0, 0, width(), height()).contains(mapFromScene(cursorPos))) // ### refactor: d->canvas->mapFromGlobal(cursorPos))))
-        return true;
-    return false;
+    return contains(mapFromScene(cursorPos)); // ### refactor: d->canvas->mapFromGlobal(cursorPos))))
 }
 
 bool QQuickItem::acceptHoverEvents() const
@@ -5056,6 +5054,24 @@ void QQuickItem::setKeepTouchGrab(bool keep)
 }
 
 /*!
+  Returns true if this item contains \a point, which is in local coordinates;
+  returns false otherwise.
+
+  This function can be overwritten in order to handle point collisions in items
+  with custom shapes. The default implementation checks if the point is inside
+  the item's bounding rect.
+
+  Note that it's normally used to check if the item is under the mouse cursor,
+  and for that reason, the implementation of this function should be as light-weight
+  as possible.
+*/
+bool QQuickItem::contains(const QPointF &point) const
+{
+    Q_D(const QQuickItem);
+    return QRectF(0, 0, d->width, d->height).contains(point);
+}
+
+/*!
     \qmlmethod object QtQuick2::Item::mapFromItem(Item item, real x, real y)
 
     Maps the point (\a x, \a y), which is in \a item's coordinate system, to
index 16dee80..0d74cc0 100644 (file)
@@ -292,6 +292,8 @@ public:
     bool keepTouchGrab() const;
     void setKeepTouchGrab(bool);
 
+    Q_INVOKABLE virtual bool contains(const QPointF &point) const;
+
     QTransform itemTransform(QQuickItem *, bool *) const;
     QPointF mapToItem(const QQuickItem *item, const QPointF &point) const;
     QPointF mapToScene(const QPointF &point) const;
index b855512..956ca09 100644 (file)
@@ -263,7 +263,7 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i
 
     if (itemPrivate->flags & QQuickItem::ItemClipsChildrenToShape) {
         QPointF p = item->mapFromScene(sp);
-        if (!QRectF(0, 0, item->width(), item->height()).contains(p))
+        if (!item->contains(p))
             return false;
     }
 
@@ -293,7 +293,7 @@ bool QQuickMouseAreaPrivate::propagateHelper(QQuickMouseEvent *ev, QQuickItem *i
             break;
         }
         QPointF p = item->mapFromScene(sp);
-        if (QRectF(0, 0, item->width(), item->height()).contains(p)) {
+        if (item->contains(p)) {
             ev->setX(p.x());
             ev->setY(p.y());
             ev->setAccepted(true);//It is connected, they have to explicitly ignore to let it slide
@@ -725,10 +725,10 @@ void QQuickMouseArea::mouseMoveEvent(QMouseEvent *event)
 
     // ### we should skip this if these signals aren't used
     // ### can GV handle this for us?
-    bool contains = boundingRect().contains(d->lastPos);
-    if (d->hovered && !contains)
+    const bool isInside = contains(d->lastPos);
+    if (d->hovered && !isInside)
         setHovered(false);
-    else if (!d->hovered && contains)
+    else if (!d->hovered && isInside)
         setHovered(true);
 
     if (d->drag && d->drag->target()) {
@@ -921,13 +921,13 @@ void QQuickMouseArea::mouseUngrabEvent()
 bool QQuickMouseArea::sendMouseEvent(QMouseEvent *event)
 {
     Q_D(QQuickMouseArea);
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+    QPointF localPos = mapFromScene(event->windowPos());
 
     QQuickCanvas *c = canvas();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool stealThisEvent = d->stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
-        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
                                event->button(), event->buttons(), event->modifiers());
         mouseEvent.setAccepted(false);
 
index 2ba7b80..df5edfb 100644 (file)
@@ -644,13 +644,13 @@ void QQuickMultiPointTouchArea::touchUngrabEvent()
 
 bool QQuickMultiPointTouchArea::sendMouseEvent(QMouseEvent *event)
 {
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+    QPointF localPos = mapFromScene(event->windowPos());
 
     QQuickCanvas *c = canvas();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool stealThisEvent = _stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
-        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
                                event->button(), event->buttons(), event->modifiers());
         mouseEvent.setAccepted(false);
 
@@ -724,25 +724,23 @@ bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool disabledItem = grabber && !grabber->isEnabled();
     bool stealThisEvent = _stealMouse;
-    bool contains = false;
+    bool containsPoint = false;
     if (!stealThisEvent) {
         switch (event->type()) {
         case QEvent::MouseButtonPress:
         case QEvent::MouseMove:
         case QEvent::MouseButtonRelease: {
                 QMouseEvent *me = static_cast<QMouseEvent*>(event);
-                QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
-                contains = myRect.contains(me->windowPos());
+                containsPoint = contains(mapFromScene(me->windowPos()));
             }
             break;
         case QEvent::TouchBegin:
         case QEvent::TouchUpdate:
         case QEvent::TouchEnd: {
                 QTouchEvent *te = static_cast<QTouchEvent*>(event);
-                QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
                 foreach (const QTouchEvent::TouchPoint &point, te->touchPoints()) {
-                    if (myRect.contains(point.scenePos())) {
-                        contains = true;
+                    if (contains(mapFromScene(point.scenePos()))) {
+                        containsPoint = true;
                         break;
                     }
                 }
@@ -752,7 +750,7 @@ bool QQuickMultiPointTouchArea::shouldFilter(QEvent *event)
             break;
         }
     }
-    if ((stealThisEvent || contains) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
+    if ((stealThisEvent || containsPoint) && (!grabber || !grabber->keepMouseGrab() || disabledItem)) {
         return true;
     }
     ungrab();
index 836943c..ba5b237 100644 (file)
@@ -1300,12 +1300,10 @@ void QQuickPathViewPrivate::handleMousePressEvent(QMouseEvent *event)
     if (!interactive || !items.count() || !model || !modelCount)
         return;
     velocityBuffer.clear();
-    QPointF scenePoint = q->mapToScene(event->localPos());
     int idx = 0;
     for (; idx < items.count(); ++idx) {
-        QRectF rect = items.at(idx)->boundingRect();
-        rect = items.at(idx)->mapRectToScene(rect);
-        if (rect.contains(scenePoint))
+        QQuickItem *item = items.at(idx);
+        if (item->contains(item->mapFromScene(event->windowPos())))
             break;
     }
     if (idx == items.count() && dragMargin == 0.)  // didn't click on an item
@@ -1470,12 +1468,13 @@ void QQuickPathViewPrivate::handleMouseReleaseEvent(QMouseEvent *)
 bool QQuickPathView::sendMouseEvent(QMouseEvent *event)
 {
     Q_D(QQuickPathView);
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+    QPointF localPos = mapFromScene(event->windowPos());
+
     QQuickCanvas *c = canvas();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool stealThisEvent = d->stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
-        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
                                event->button(), event->buttons(), event->modifiers());
         mouseEvent.setAccepted(false);
 
index 6f79385..eba9d09 100644 (file)
@@ -503,13 +503,13 @@ void QQuickPinchArea::mouseUngrabEvent()
 bool QQuickPinchArea::sendMouseEvent(QMouseEvent *event)
 {
     Q_D(QQuickPinchArea);
-    QRectF myRect = mapRectToScene(QRectF(0, 0, width(), height()));
+    QPointF localPos = mapFromScene(event->windowPos());
 
     QQuickCanvas *c = canvas();
     QQuickItem *grabber = c ? c->mouseGrabberItem() : 0;
     bool stealThisEvent = d->stealMouse;
-    if ((stealThisEvent || myRect.contains(event->windowPos())) && (!grabber || !grabber->keepMouseGrab())) {
-        QMouseEvent mouseEvent(event->type(), mapFromScene(event->windowPos()), event->windowPos(), event->screenPos(),
+    if ((stealThisEvent || contains(localPos)) && (!grabber || !grabber->keepMouseGrab())) {
+        QMouseEvent mouseEvent(event->type(), localPos, event->windowPos(), event->screenPos(),
                                event->button(), event->buttons(), event->modifiers());
         mouseEvent.setAccepted(false);
 
diff --git a/tests/auto/quick/qquickitem2/data/hollowTestItem.qml b/tests/auto/quick/qquickitem2/data/hollowTestItem.qml
new file mode 100644 (file)
index 0000000..d07d89c
--- /dev/null
@@ -0,0 +1,38 @@
+import QtQuick 2.0
+import Test 1.0
+
+Rectangle {
+    width: 400
+    height: 400
+
+    Rectangle {
+        x: 100
+        y: 100
+        width: 200
+        height: 200
+        rotation: 45
+
+        Rectangle {
+            scale: 0.5
+            color: "black"
+            anchors.fill: parent
+            radius: hollowItem.circle ? 100 : 0
+
+            Rectangle {
+                color: "white"
+                anchors.centerIn: parent
+                width: hollowItem.holeRadius * 2
+                height: hollowItem.holeRadius * 2
+                radius: hollowItem.circle ? 100 : 0
+            }
+
+            HollowTestItem {
+                id: hollowItem
+                anchors.fill: parent
+                objectName: "hollowItem"
+                holeRadius: 50
+                circle: circleShapeTest
+            }
+        }
+    }
+}
index c2390c4..34f5187 100644 (file)
@@ -93,6 +93,9 @@ private slots:
     void qtbug_16871();
     void visibleChildren();
     void parentLoop();
+    void contains_data();
+    void contains();
+
 private:
     QQmlEngine engine;
 };
@@ -178,6 +181,69 @@ public:
 
 QML_DECLARE_TYPE(KeyTestItem);
 
+class HollowTestItem : public QQuickItem
+{
+    Q_OBJECT
+    Q_PROPERTY(bool circle READ isCircle WRITE setCircle)
+    Q_PROPERTY(qreal holeRadius READ holeRadius WRITE setHoleRadius)
+
+public:
+    HollowTestItem(QQuickItem *parent = 0)
+        : QQuickItem(parent),
+          m_isPressed(false),
+          m_isHovered(false),
+          m_isCircle(false),
+          m_holeRadius(50)
+    {
+        setAcceptHoverEvents(true);
+        setAcceptedMouseButtons(Qt::LeftButton);
+    }
+
+    bool isPressed() const { return m_isPressed; }
+    bool isHovered() const { return m_isHovered; }
+
+    bool isCircle() const { return m_isCircle; }
+    void setCircle(bool circle) { m_isCircle = circle; }
+
+    qreal holeRadius() const { return m_holeRadius; }
+    void setHoleRadius(qreal radius) { m_holeRadius = radius; }
+
+    bool contains(const QPointF &point) const {
+        const qreal w = width();
+        const qreal h = height();
+        const qreal r = m_holeRadius;
+
+        // check boundaries
+        if (!QRectF(0, 0, w, h).contains(point))
+            return false;
+
+        // square shape
+        if (!m_isCircle)
+            return !QRectF(w / 2 - r, h / 2 - r, r * 2, r * 2).contains(point);
+
+        // circle shape
+        const qreal dx = point.x() - (w / 2);
+        const qreal dy = point.y() - (h / 2);
+        const qreal dd = (dx * dx) + (dy * dy);
+        const qreal outerRadius = qMin<qreal>(w / 2, h / 2);
+        return dd > (r * r) && dd <= outerRadius * outerRadius;
+    }
+
+protected:
+    void hoverEnterEvent(QHoverEvent *) { m_isHovered = true; }
+    void hoverLeaveEvent(QHoverEvent *) { m_isHovered = false; }
+    void mousePressEvent(QMouseEvent *) { m_isPressed = true; }
+    void mouseReleaseEvent(QMouseEvent *) { m_isPressed = false; }
+
+private:
+    bool m_isPressed;
+    bool m_isHovered;
+    bool m_isCircle;
+    qreal m_holeRadius;
+};
+
+QML_DECLARE_TYPE(HollowTestItem);
+
 
 tst_QQuickItem::tst_QQuickItem()
 {
@@ -187,6 +253,7 @@ void tst_QQuickItem::initTestCase()
 {
     QQmlDataTest::initTestCase();
     qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem");
+    qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem");
 }
 
 void tst_QQuickItem::cleanup()
@@ -1375,6 +1442,110 @@ void tst_QQuickItem::parentLoop()
     delete canvas;
 }
 
+void tst_QQuickItem::contains_data()
+{
+    QTest::addColumn<bool>("circleTest");
+    QTest::addColumn<bool>("insideTarget");
+    QTest::addColumn<QList<QPoint> >("points");
+
+    QList<QPoint> points;
+
+    points << QPoint(176, 176)
+           << QPoint(176, 226)
+           << QPoint(226, 176)
+           << QPoint(226, 226)
+           << QPoint(150, 200)
+           << QPoint(200, 150)
+           << QPoint(200, 250)
+           << QPoint(250, 200);
+    QTest::newRow("hollow square: testing points inside") << false << true << points;
+
+    points.clear();
+    points << QPoint(162, 162)
+           << QPoint(162, 242)
+           << QPoint(242, 162)
+           << QPoint(242, 242)
+           << QPoint(200, 200)
+           << QPoint(175, 200)
+           << QPoint(200, 175)
+           << QPoint(200, 228)
+           << QPoint(228, 200)
+           << QPoint(200, 122)
+           << QPoint(122, 200)
+           << QPoint(200, 280)
+           << QPoint(280, 200);
+    QTest::newRow("hollow square: testing points outside") << false << false << points;
+
+    points.clear();
+    points << QPoint(174, 174)
+           << QPoint(174, 225)
+           << QPoint(225, 174)
+           << QPoint(225, 225)
+           << QPoint(165, 200)
+           << QPoint(200, 165)
+           << QPoint(200, 235)
+           << QPoint(235, 200);
+    QTest::newRow("hollow circle: testing points inside") << true << true << points;
+
+    points.clear();
+    points << QPoint(160, 160)
+           << QPoint(160, 240)
+           << QPoint(240, 160)
+           << QPoint(240, 240)
+           << QPoint(200, 200)
+           << QPoint(185, 185)
+           << QPoint(185, 216)
+           << QPoint(216, 185)
+           << QPoint(216, 216)
+           << QPoint(145, 200)
+           << QPoint(200, 145)
+           << QPoint(255, 200)
+           << QPoint(200, 255);
+    QTest::newRow("hollow circle: testing points outside") << true << false << points;
+}
+
+void tst_QQuickItem::contains()
+{
+    QFETCH(bool, circleTest);
+    QFETCH(bool, insideTarget);
+    QFETCH(QList<QPoint>, points);
+
+    QQuickView *canvas = new QQuickView(0);
+    canvas->rootContext()->setContextProperty("circleShapeTest", circleTest);
+    canvas->setBaseSize(QSize(400, 400));
+    canvas->setSource(testFileUrl("hollowTestItem.qml"));
+    canvas->show();
+    canvas->requestActivateWindow();
+    QTest::qWaitForWindowShown(canvas);
+    QTRY_VERIFY(QGuiApplication::focusWindow() == canvas);
+
+    QQuickItem *root = qobject_cast<QQuickItem *>(canvas->rootObject());
+    QVERIFY(root);
+
+    HollowTestItem *hollowItem = root->findChild<HollowTestItem *>("hollowItem");
+    QVERIFY(hollowItem);
+
+    foreach (const QPoint &point, points) {
+        // check mouse hover
+        QTest::mouseMove(canvas, point);
+        QTest::qWait(10);
+        QCOMPARE(hollowItem->isHovered(), insideTarget);
+
+        // check mouse press
+        QTest::mousePress(canvas, Qt::LeftButton, 0, point);
+        QTest::qWait(10);
+        QCOMPARE(hollowItem->isPressed(), insideTarget);
+
+        // check mouse release
+        QTest::mouseRelease(canvas, Qt::LeftButton, 0, point);
+        QTest::qWait(10);
+        QCOMPARE(hollowItem->isPressed(), false);
+    }
+
+    delete canvas;
+}
+
+
 QTEST_MAIN(tst_QQuickItem)
 
 #include "tst_qquickitem.moc"