on xenial help sign in
Dash: disable close mode when you click outside app thumbnails
b26ce73Daniel d'Andrada
Dash/Apps/RunningApplicationsGrid.qml+17
@@ -17,6 +17,8 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import "../../Components"
18import "../../Components"
19
19
20+import Ubuntu.Gestures 0.1
21+
20ResponsiveFlowView {
22ResponsiveFlowView {
21 id: root
23 id: root
22 clip: true
24 clip: true
@@ -100,4 +102,19 @@ ResponsiveFlowView {
100 move: Transition {
102 move: Transition {
101 NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutCubic }
103 NumberAnimation { properties: "x,y"; duration: 400; easing.type: Easing.OutCubic }
102 }
104 }
105+
106+ MouseArea {
107+ anchors.fill: parent
108+ z: -1 // behind all RunningApplicationTiles
109+ enabled: root.terminationModeEnabled
110+ onPressed: { root.terminationModeEnabled = false; }
111+ }
112+
113+ PressedOutsideNotifier {
114+ anchors.fill: parent
115+ enabled: root.terminationModeEnabled
116+ onPressedOutside: {
117+ root.terminationModeEnabled = false;
118+ }
119+ }
103}
120}
plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -6,6 +6,7 @@ set(UbuntuGestureQml_SOURCES
6 AxisVelocityCalculator.cpp
6 AxisVelocityCalculator.cpp
7 Direction.cpp
7 Direction.cpp
8 DirectionalDragArea.cpp
8 DirectionalDragArea.cpp
9+ PressedOutsideNotifier.cpp
9 TimeSource.cpp
10 TimeSource.cpp
10)
11)
11
12
plugins/Ubuntu/Gestures/PressedOutsideNotifier.cppnew file+119
@@ -0,0 +1,119 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "PressedOutsideNotifier.h"
18+
19+#include <QMouseEvent>
20+
21+PressedOutsideNotifier::PressedOutsideNotifier(QQuickItem *parent)
22+ : QQuickItem(parent)
23+{
24+ connect(this, &QQuickItem::enabledChanged,
25+ this, &PressedOutsideNotifier::setupOrTearDownEventFiltering);
26+
27+ m_signalEmissionTimer.setSingleShot(true);
28+ m_signalEmissionTimer.setInterval(0); // times out on the next iteration of the event loop
29+ connect(&m_signalEmissionTimer, &QTimer::timeout,
30+ this, &PressedOutsideNotifier::pressedOutside);
31+}
32+
33+bool PressedOutsideNotifier::eventFilter(QObject *watched, QEvent *event)
34+{
35+ Q_UNUSED(watched);
36+ Q_ASSERT(watched == m_filteredWindow);
37+
38+ // We are already going to emit pressedOutside() anyway, thus no need
39+ // for new checks.
40+ // This case takes place when a QTouchEvent comes in and isn't handled by any item,
41+ // causing QQuickWindow to synthesize a QMouseEvent out of it, which would
42+ // be filtered by us as well and count as a second press, which is wrong.
43+ if (m_signalEmissionTimer.isActive()) {
44+ return false;
45+ }
46+
47+ switch (event->type()) {
48+ case QEvent::MouseButtonPress: {
49+ QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
50+ QPointF p = mapFromScene(mouseEvent->windowPos());
51+ if (!contains(p)) {
52+ m_signalEmissionTimer.start();
53+ }
54+ break;
55+ }
56+ case QEvent::TouchBegin:
57+ processFilteredTouchBegin(static_cast<QTouchEvent*>(event));
58+ default:
59+ break;
60+ }
61+
62+ // let the event be handled further
63+ return false;
64+}
65+
66+void PressedOutsideNotifier::itemChange(ItemChange change, const ItemChangeData &value)
67+{
68+ if (change == QQuickItem::ItemSceneChange) {
69+ setupOrTearDownEventFiltering();
70+ }
71+
72+ QQuickItem::itemChange(change, value);
73+}
74+
75+void PressedOutsideNotifier::setupOrTearDownEventFiltering()
76+{
77+ if (isEnabled() && window()) {
78+ setupEventFiltering();
79+ } else if (m_filteredWindow) {
80+ tearDownEventFiltering();
81+ }
82+}
83+
84+void PressedOutsideNotifier::setupEventFiltering()
85+{
86+ QQuickWindow *currentWindow = window();
87+ Q_ASSERT(currentWindow != nullptr);
88+
89+ if (currentWindow == m_filteredWindow)
90+ return;
91+
92+ if (m_filteredWindow) {
93+ m_filteredWindow->removeEventFilter(this);
94+ }
95+
96+ currentWindow->installEventFilter(this);
97+ m_filteredWindow = currentWindow;
98+}
99+
100+void PressedOutsideNotifier::tearDownEventFiltering()
101+{
102+ m_filteredWindow->removeEventFilter(this);
103+ m_filteredWindow.clear();
104+}
105+
106+void PressedOutsideNotifier::processFilteredTouchBegin(QTouchEvent *event)
107+{
108+ const QList<QTouchEvent::TouchPoint> &touchPoints = event->touchPoints();
109+ for (int i = 0; i < touchPoints.count(); ++i) {
110+ const QTouchEvent::TouchPoint &touchPoint = touchPoints.at(i);
111+ if (touchPoint.state() == Qt::TouchPointPressed) {
112+ QPointF p = mapFromScene(touchPoint.pos());
113+ if (!contains(p)) {
114+ m_signalEmissionTimer.start();
115+ return;
116+ }
117+ }
118+ }
119+}
plugins/Ubuntu/Gestures/PressedOutsideNotifier.hnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#ifndef PRESSED_OUTSIDE_NOTIFIER_H
18+#define PRESSED_OUTSIDE_NOTIFIER_H
19+
20+#include <QQuickItem>
21+
22+#include <QQuickWindow>
23+#include <QPointer>
24+#include <QTimer>
25+
26+#include "UbuntuGesturesGlobal.h"
27+
28+/*
29+ Notifies when a point, mouse or touch, is pressed outside its area.
30+
31+ Only enable it while needed.
32+ */
33+class UBUNTUGESTURES_EXPORT PressedOutsideNotifier : public QQuickItem {
34+ Q_OBJECT
35+
36+public:
37+ PressedOutsideNotifier(QQuickItem * parent = nullptr);
38+
39+ // From QObject
40+ bool eventFilter(QObject *watched, QEvent *event) override;
41+
42+Q_SIGNALS:
43+ void pressedOutside();
44+
45+protected:
46+ void itemChange(ItemChange change, const ItemChangeData &value) override;
47+
48+private Q_SLOTS:
49+ void setupOrTearDownEventFiltering();
50+
51+private:
52+ void setupEventFiltering();
53+ void tearDownEventFiltering();
54+ void processFilteredTouchBegin(QTouchEvent *event);
55+
56+ QPointer<QQuickWindow> m_filteredWindow;
57+
58+ // Emits pressedOutside() signal on timeout
59+ QTimer m_signalEmissionTimer;
60+};
61+
62+#endif // PRESSED_OUTSIDE_NOTIFIER_H
plugins/Ubuntu/Gestures/plugin.cpp+2
@@ -18,6 +18,7 @@
18#include "AxisVelocityCalculator.h"
18#include "AxisVelocityCalculator.h"
19#include "Direction.h"
19#include "Direction.h"
20#include "DirectionalDragArea.h"
20#include "DirectionalDragArea.h"
21+#include "PressedOutsideNotifier.h"
21
22
22#include <qqml.h>
23#include <qqml.h>
23
24
@@ -32,4 +33,5 @@ void UbuntuGestureQmlPlugin::registerTypes(const char *uri)
32 qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
33 qmlRegisterSingletonType<Direction>(uri, 0, 1, "Direction", directionSingleton);
33 qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
34 qmlRegisterType<DirectionalDragArea>(uri, 0, 1, "DirectionalDragArea");
34 qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
35 qmlRegisterType<AxisVelocityCalculator>(uri, 0, 1, "AxisVelocityCalculator");
36+ qmlRegisterType<PressedOutsideNotifier>(uri, 0, 1, "PressedOutsideNotifier");
35}
37}
tests/plugins/Ubuntu/Gestures/CMakeLists.txt+1
@@ -45,5 +45,6 @@ macro(add_gesture_test CLASSNAME)
45endmacro(add_gesture_test)
45endmacro(add_gesture_test)
46
46
47add_gesture_ui_test(DirectionalDragArea)
47add_gesture_ui_test(DirectionalDragArea)
48+add_gesture_ui_test(PressedOutsideNotifier)
48add_gesture_test(Damper)
49add_gesture_test(Damper)
49add_gesture_test(AxisVelocityCalculator)
50add_gesture_test(AxisVelocityCalculator)
tests/plugins/Ubuntu/Gestures/tst_PressedOutsideNotifier.cppnew file+150
@@ -0,0 +1,150 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+#include "GestureTest.h"
18+
19+#include <PressedOutsideNotifier.h>
20+
21+#include <QQuickItem>
22+#include <QQuickView>
23+#include <QtTest>
24+
25+class tst_PressedOutsideNotifier: public GestureTest
26+{
27+ Q_OBJECT
28+public:
29+ tst_PressedOutsideNotifier();
30+private Q_SLOTS:
31+ void init(); // called right before each and every test function is executed
32+
33+ void touchOutsideAreaTriggersSignal();
34+ void touchInsideAreaHasNoEffect();
35+ void mousePressOutsideAreaTriggersSignal();
36+ void mousePresssInsideAreaHasNoEffect();
37+ void nothingHappensWhileDisabled();
38+
39+private:
40+ QQuickItem *m_blueRect;
41+ PressedOutsideNotifier *m_notifier;
42+};
43+
44+tst_PressedOutsideNotifier::tst_PressedOutsideNotifier()
45+ : GestureTest(QStringLiteral("tst_PressedOutsideNotifier.qml"))
46+{
47+}
48+
49+void tst_PressedOutsideNotifier::init()
50+{
51+ GestureTest::init();
52+
53+ m_blueRect = m_view->rootObject()->findChild<QQuickItem*>("blueRect");
54+ Q_ASSERT(m_blueRect != nullptr);
55+
56+ m_notifier =
57+ m_view->rootObject()->findChild<PressedOutsideNotifier*>("pressedOutsideNotifier");
58+ Q_ASSERT(m_notifier != nullptr);
59+}
60+
61+void tst_PressedOutsideNotifier::touchOutsideAreaTriggersSignal()
62+{
63+ // half-way towards the top-left corner of the blueRect.
64+ // Therefore, still outside it.
65+ QPoint touch0 = QPoint(m_blueRect->x() * 0.5, m_blueRect->y() * 0.5);
66+
67+ QSignalSpy pressedOutsideSpy(m_notifier, SIGNAL(pressedOutside()));
68+
69+ QTest::touchEvent(m_view, m_device)
70+ .press(0, touch0);
71+
72+ qApp->processEvents();
73+
74+ QCOMPARE(pressedOutsideSpy.count(), 1);
75+
76+ QTest::touchEvent(m_view, m_device)
77+ .release(0, touch0);
78+}
79+
80+void tst_PressedOutsideNotifier::touchInsideAreaHasNoEffect()
81+{
82+ QPoint touch0 = QPoint(m_blueRect->x() + m_blueRect->width()*0.5,
83+ m_blueRect->y() + m_blueRect->height()*0.5);
84+
85+ QSignalSpy pressedOutsideSpy(m_notifier, SIGNAL(pressedOutside()));
86+
87+ QTest::touchEvent(m_view, m_device)
88+ .press(0, touch0);
89+
90+ qApp->processEvents();
91+
92+ QCOMPARE(pressedOutsideSpy.count(), 0);
93+
94+ QTest::touchEvent(m_view, m_device)
95+ .release(0, touch0);
96+}
97+
98+void tst_PressedOutsideNotifier::mousePressOutsideAreaTriggersSignal()
99+{
100+ QPoint mousePos= QPoint(m_blueRect->x() * 0.5, m_blueRect->y() * 0.5);
101+
102+ QSignalSpy pressedOutsideSpy(m_notifier, SIGNAL(pressedOutside()));
103+
104+ QTest::mousePress(m_view, Qt::LeftButton, 0 /*modifiers*/, mousePos);
105+
106+ qApp->processEvents();
107+
108+ QCOMPARE(pressedOutsideSpy.count(), 1);
109+
110+ QTest::mouseRelease(m_view, Qt::LeftButton, 0 /*modifiers*/, mousePos);
111+}
112+
113+void tst_PressedOutsideNotifier::mousePresssInsideAreaHasNoEffect()
114+{
115+ QPoint mousePos = QPoint(m_blueRect->x() + m_blueRect->width()*0.5,
116+ m_blueRect->y() + m_blueRect->height()*0.5);
117+
118+ QSignalSpy pressedOutsideSpy(m_notifier, SIGNAL(pressedOutside()));
119+
120+ QTest::mousePress(m_view, Qt::LeftButton, 0 /*modifiers*/, mousePos);
121+
122+ qApp->processEvents();
123+
124+ QCOMPARE(pressedOutsideSpy.count(), 0);
125+
126+ QTest::mouseRelease(m_view, Qt::LeftButton, 0 /*modifiers*/, mousePos);
127+}
128+
129+void tst_PressedOutsideNotifier::nothingHappensWhileDisabled()
130+{
131+ QPoint touch0 = QPoint(m_blueRect->x() * 0.5, m_blueRect->y() * 0.5);
132+
133+ QSignalSpy pressedOutsideSpy(m_notifier, SIGNAL(pressedOutside()));
134+
135+ m_notifier->setEnabled(false);
136+
137+ QTest::touchEvent(m_view, m_device)
138+ .press(0, touch0);
139+
140+ qApp->processEvents();
141+
142+ QCOMPARE(pressedOutsideSpy.count(), 0);
143+
144+ QTest::touchEvent(m_view, m_device)
145+ .release(0, touch0);
146+}
147+
148+QTEST_MAIN(tst_PressedOutsideNotifier)
149+
150+#include "tst_PressedOutsideNotifier.moc"
tests/plugins/Ubuntu/Gestures/tst_PressedOutsideNotifier.qmlnew file+62
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import Ubuntu.Gestures 0.1
20+
21+/*
22+ NB: If you change positions or sizes here make sure
23+ tst_PressedOutsideNotifier.cpp is updated accordingly
24+ */
25+
26+Rectangle {
27+ width: units.gu(60)
28+ height: units.gu(60)
29+ color: "white"
30+
31+ Rectangle {
32+ id: blueRect
33+ objectName: "blueRect"
34+ color: "blue"
35+ x: units.gu(20)
36+ y: units.gu(20)
37+ width: units.gu(20)
38+ height: units.gu(20)
39+
40+ Timer {
41+ id: resetColorTimer
42+ interval: 300
43+ repeat: false
44+ onTriggered: {
45+ blueRect.color = "blue";
46+ }
47+ }
48+
49+ function blinkRed() {
50+ blueRect.color = "red";
51+ resetColorTimer.start();
52+ }
53+
54+ PressedOutsideNotifier {
55+ objectName: "pressedOutsideNotifier"
56+ anchors.fill: parent
57+ onPressedOutside: {
58+ blueRect.blinkRed();
59+ }
60+ }
61+ }
62+}
tests/qmltests/CMakeLists.txt+3−1
@@ -46,7 +46,9 @@ add_qml_test(Dash GenericPreview)
46add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
46add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
47add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
47add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
48 ${CMAKE_BINARY_DIR}/tests/mocks)
48 ${CMAKE_BINARY_DIR}/tests/mocks)
49−add_qml_test(Dash/Apps RunningApplicationsGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
49+add_qml_test(Dash/Apps RunningApplicationsGrid IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
50+ ${CMAKE_BINARY_DIR}/tests/mocks
51+ ${CMAKE_BINARY_DIR}/plugins)
50add_qml_test(Dash/Apps AppPreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
52add_qml_test(Dash/Apps AppPreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
51add_qml_test(Dash/Movie MoviePreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
53add_qml_test(Dash/Movie MoviePreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
52add_qml_test(Dash/Music MusicPreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
54add_qml_test(Dash/Music MusicPreview IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Dash/Apps/tst_RunningApplicationsGrid.qml+20
@@ -239,5 +239,25 @@ Item {
239 return findChild(runningApplicationsGrid, "runningAppTile Calendar")
239 return findChild(runningApplicationsGrid, "runningAppTile Calendar")
240 != undefined
240 != undefined
241 }
241 }
242+
243+ // While in termination mode, if you click outside any of the tiles, the
244+ // termination mode is disabled (i.e. we switch back to activation mode).
245+ function test_clickOutsideTilesDisablesTerminationMode() {
246+ runningApplicationsGrid.terminationModeEnabled = true
247+
248+ var calendarTile = findChild(runningApplicationsGrid, "runningAppTile Calendar")
249+ verify(calendarTile != undefined)
250+
251+ verify(runningApplicationsGrid.terminationModeEnabled);
252+
253+ // Click on the bottom right corner of the grid, where there's no
254+ // RunningApplicationTile lying around
255+ mouseClick(runningApplicationsGrid,
256+ runningApplicationsGrid.width - 1, runningApplicationsGrid.height - 1);
257+
258+ wait(0) // spin event loop to ensure that any pending signal emission went through
259+
260+ verify(!runningApplicationsGrid.terminationModeEnabled);
261+ }
242 }
262 }
243}
263}