Merge lp:~zsombi/ubuntu-ui-toolkit/properTriggerCall into lp:ubuntu-ui-toolkit/staging

Proposed by Zsombor Egri
Status: Merged
Approved by: Cris Dywan
Approved revision: 1755
Merged at revision: 1760
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/properTriggerCall
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 440 lines (+184/-32)
13 files modified
src/Ubuntu/Components/plugin/ucabstractbutton.cpp (+7/-3)
src/Ubuntu/Components/plugin/ucaction.cpp (+1/-1)
src/Ubuntu/Components/plugin/ucaction.h (+22/-0)
src/Ubuntu/Components/plugin/ucactionitem.cpp (+8/-14)
src/Ubuntu/Components/plugin/ucactionitem.h (+1/-0)
src/Ubuntu/Components/plugin/ucactionitem_p.h (+1/-3)
src/Ubuntu/Components/plugin/ucbottomedgehint.cpp (+2/-1)
src/Ubuntu/Components/plugin/uclistitem.cpp (+1/-1)
tests/unit/tst_components/tst_action.qml (+32/-5)
tests/unit_x11/tst_bottomedge/OverriddenHintTrigger.qml (+45/-0)
tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp (+16/-0)
tests/unit_x11/tst_bottomedge/tst_bottomedge.pro (+2/-1)
tests/unit_x11/tst_components/tst_abstractbutton13.qml (+46/-3)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/properTriggerCall
Reviewer Review Type Date Requested Status
Cris Dywan Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+280380@code.launchpad.net

Commit message

Invoke the overridden trigger() function for Action and ActionItem derivates.

To post a comment you must log in.
1755. By Zsombor Egri

Fix ListItem's action triggering

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Cris Dywan (kalikiana) wrote :

For the record, two things at first I was skeptical about, but after all make sense to me. Having the trigger be a separate template rather than implemented in the subclass - this is indeed needed to support a trigger() method overridden in both C++ and QML. And separating the clicked invocation - it would seem to be a good place to implement the trigger, but it really is equivalent to calling the both of them accordingly.
So, looks very good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Ubuntu/Components/plugin/ucabstractbutton.cpp'
2--- src/Ubuntu/Components/plugin/ucabstractbutton.cpp 2015-12-11 15:37:25 +0000
3+++ src/Ubuntu/Components/plugin/ucabstractbutton.cpp 2015-12-14 06:15:34 +0000
4@@ -17,6 +17,7 @@
5 #include "ucabstractbutton.h"
6 #include "ucabstractbutton_p.h"
7 #include "uchaptics.h"
8+#include "ucaction.h"
9 #include <QtQuick/private/qquickitem_p.h>
10 #include <QtQuick/private/qquickmousearea_p.h>
11 #include <QtQml/private/qqmlglobal_p.h>
12@@ -100,8 +101,6 @@
13 {
14 UCActionItemPrivate::completeComponentInitialization();
15 Q_Q(UCAbstractButton);
16- // connect to the right slot, using macros so we get the proper slot
17- QObject::connect(mouseArea, SIGNAL(clicked(QQuickMouseEvent*)), q, SLOT(trigger()));
18
19 // bind mouse area
20 QObject::connect(mouseArea, &QQuickMouseArea::pressedChanged, q, &UCAbstractButton::pressedChanged);
21@@ -139,6 +138,8 @@
22 // play haptics
23 HapticsProxy::instance().play(QVariant());
24 Q_EMIT q->clicked();
25+ // call the overridden QML trigger function
26+ invokeTrigger<UCAbstractButton>(q, QVariant());
27 }
28
29 // handle pressAndHold
30@@ -162,7 +163,10 @@
31 case Qt::Key_Return:
32 case Qt::Key_Space:
33 {
34- trigger();
35+ // trigger clicked signal first
36+ Q_EMIT clicked();
37+ // then invoke the overloaded trigger
38+ invokeTrigger<UCAbstractButton>(this, QVariant());
39 break;
40 }
41 }
42
43=== modified file 'src/Ubuntu/Components/plugin/ucaction.cpp'
44--- src/Ubuntu/Components/plugin/ucaction.cpp 2015-12-08 15:13:49 +0000
45+++ src/Ubuntu/Components/plugin/ucaction.cpp 2015-12-14 06:15:34 +0000
46@@ -316,7 +316,7 @@
47 }
48
49 // do not call trigger() directly but invoke, as it may get overridden in QML
50- metaObject()->invokeMethod(this, "trigger");
51+ invokeTrigger<UCAction>(this, QVariant());
52 return true;
53 }
54
55
56=== modified file 'src/Ubuntu/Components/plugin/ucaction.h'
57--- src/Ubuntu/Components/plugin/ucaction.h 2015-12-11 09:54:17 +0000
58+++ src/Ubuntu/Components/plugin/ucaction.h 2015-12-14 06:15:34 +0000
59@@ -21,6 +21,28 @@
60 #include <QtCore/QVariant>
61 #include <QtCore/QUrl>
62
63+// the function detects whether QML has an overridden trigger() slot available
64+// and invokes the one with the appropriate signature
65+template<class T>
66+inline void invokeTrigger(T *object, const QVariant &value)
67+{
68+ bool invoked = false;
69+ const QMetaObject *mo = object->metaObject();
70+ int offset = mo->methodOffset();
71+ int paramlessTriggerIndex = mo->indexOfSlot("trigger()") - offset;
72+ int paramTriggerIndex = mo->indexOfSlot("trigger(QVariant)") - offset;
73+
74+ /* if we have the parametered version, call that even if the value given is invalid */
75+ if (paramTriggerIndex >= 0) {
76+ invoked = QMetaObject::invokeMethod(object, "trigger", Q_ARG(QVariant, value));
77+ } else if (paramlessTriggerIndex >= 0) {
78+ invoked = QMetaObject::invokeMethod(object, "trigger");
79+ }
80+ if (!invoked) {
81+ object->trigger(value);
82+ }
83+}
84+
85 class QQmlComponent;
86 class UCAction : public QObject
87 {
88
89=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.cpp'
90--- src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-12-11 15:37:25 +0000
91+++ src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-12-14 06:15:34 +0000
92@@ -72,17 +72,6 @@
93 return QQmlPropertyPrivate::binding(property) != Q_NULLPTR;
94 }
95
96-void UCActionItemPrivate::completeComponentInitialization()
97-{
98- UCStyledItemBasePrivate::completeComponentInitialization();
99- // make sure we connect to the right signals, so we detach and re-attach actions
100- // to make sure the SLOT macro picks up the custom trigger() slot
101- if (action) {
102- attachAction(false);
103- attachAction(true);
104- }
105-}
106-
107 // update visible property
108 void UCActionItemPrivate::_q_visibleBinding()
109 {
110@@ -111,6 +100,12 @@
111 q_func()->setEnabled(enabled);
112 }
113
114+// invoke actions' overridden triger() function
115+void UCActionItemPrivate::_q_invokeActionTrigger(const QVariant &value)
116+{
117+ invokeTrigger<UCAction>(action, value);
118+}
119+
120 // setter called when bindings from QML set the value. Internal functions will
121 // all use the setVisible setter, so initialization and (re)parenting related
122 // visible alteration won't set the custom flag
123@@ -145,7 +140,7 @@
124 Q_Q(UCActionItem);
125 if (attach) {
126 QObject::connect(q, SIGNAL(triggered(QVariant)),
127- action, SLOT(trigger(QVariant)), Qt::DirectConnection);
128+ q, SLOT(_q_invokeActionTrigger(QVariant)), Qt::DirectConnection);
129 if (!(flags & CustomVisible)) {
130 QObject::connect(action, SIGNAL(visibleChanged()),
131 q, SLOT(_q_visibleBinding()), Qt::DirectConnection);
132@@ -168,7 +163,7 @@
133 }
134 } else {
135 QObject::disconnect(q, SIGNAL(triggered(QVariant)),
136- action, SLOT(trigger(QVariant)));
137+ q, SLOT(_q_invokeActionTrigger(QVariant)));
138 if (!(flags & CustomVisible)) {
139 QObject::disconnect(action, SIGNAL(visibleChanged()),
140 q, SLOT(_q_visibleBinding()));
141@@ -371,7 +366,6 @@
142 void UCActionItem::trigger(const QVariant &value)
143 {
144 if (isEnabled()) {
145- // FIXME: bug #1524234: invoke function from QMetaObject (zsombi)
146 Q_EMIT triggered(value);
147 }
148 }
149
150=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.h'
151--- src/Ubuntu/Components/plugin/ucactionitem.h 2015-12-11 10:28:46 +0000
152+++ src/Ubuntu/Components/plugin/ucactionitem.h 2015-12-14 06:15:34 +0000
153@@ -68,6 +68,7 @@
154 Q_DECLARE_PRIVATE(UCActionItem)
155 Q_PRIVATE_SLOT(d_func(), void _q_visibleBinding())
156 Q_PRIVATE_SLOT(d_func(), void _q_enabledBinding())
157+ Q_PRIVATE_SLOT(d_func(), void _q_invokeActionTrigger(const QVariant &value))
158 };
159
160 #endif // UCACTIONITEM_H
161
162=== modified file 'src/Ubuntu/Components/plugin/ucactionitem_p.h'
163--- src/Ubuntu/Components/plugin/ucactionitem_p.h 2015-12-11 10:28:46 +0000
164+++ src/Ubuntu/Components/plugin/ucactionitem_p.h 2015-12-14 06:15:34 +0000
165@@ -38,12 +38,10 @@
166 void updateProperties();
167 void attachAction(bool attach);
168
169- // overrides
170- void completeComponentInitialization() override;
171-
172 // private slots
173 void _q_visibleBinding();
174 void _q_enabledBinding();
175+ void _q_invokeActionTrigger(const QVariant &value);
176
177 enum {
178 CustomText = 0x01,
179
180=== modified file 'src/Ubuntu/Components/plugin/ucbottomedgehint.cpp'
181--- src/Ubuntu/Components/plugin/ucbottomedgehint.cpp 2015-12-11 12:19:28 +0000
182+++ src/Ubuntu/Components/plugin/ucbottomedgehint.cpp 2015-12-14 06:15:34 +0000
183@@ -21,6 +21,7 @@
184 #include "ucstyleditembase_p.h"
185 #include "quickutils.h"
186 #include "ucunits.h"
187+#include "ucaction.h"
188 #include "gestures/ucswipearea.h"
189 #include "propertychange_p.h"
190 #include <QtQml/private/qqmlproperty_p.h>
191@@ -46,7 +47,7 @@
192 Q_Q(UCBottomEdgeHint);
193 QObject::connect(q, &UCBottomEdgeHint::clicked, [=]() {
194 // make sure the overloaded trigger is called!
195- q->metaObject()->invokeMethod(q, "trigger", Q_ARG(QVariant, QVariant()));
196+ invokeTrigger<UCBottomEdgeHint>(q, QVariant());
197 });
198 /*
199 * we cannot use setStyleName as that will trigger style loading
200
201=== modified file 'src/Ubuntu/Components/plugin/uclistitem.cpp'
202--- src/Ubuntu/Components/plugin/uclistitem.cpp 2015-11-16 16:24:42 +0000
203+++ src/Ubuntu/Components/plugin/uclistitem.cpp 2015-12-14 06:15:34 +0000
204@@ -1223,7 +1223,7 @@
205 if (!swiped) {
206 Q_EMIT q->clicked();
207 if (mainAction) {
208- Q_EMIT mainAction->trigger(index());
209+ invokeTrigger<UCAction>(mainAction, index());
210 }
211 }
212 snapOut();
213
214=== modified file 'tests/unit/tst_components/tst_action.qml'
215--- tests/unit/tst_components/tst_action.qml 2015-08-25 11:31:29 +0000
216+++ tests/unit/tst_components/tst_action.qml 2015-12-14 06:15:34 +0000
217@@ -137,10 +137,25 @@
218 verify(!data.inactive.active, "Context deactivation error");
219 }
220
221- function test_overloaded_action_trigger() {
222- triggeredSignalSpy.target = suppressTrigger;
223- suppressTrigger.trigger();
224- compare(triggeredSignalSpy.count, 0, "Overloaded trigger should not trigger action");
225+ function test_overloaded_action_trigger_data() {
226+ return [
227+ {tag: "parametered override without parameter", action: suppressTrigger, invoked: true},
228+ {tag: "parametered override with parameter", action: suppressTrigger, value: 1, type: Action.Integer, invoked: true},
229+ {tag: "paremeterless override without parameter", action: override, invoked: true},
230+ {tag: "paremeterless override with parameter", action: override, value: 1, type: Action.Integer, invoked: true},
231+ ];
232+ }
233+ function test_overloaded_action_trigger(data) {
234+ data.action.invoked = false;
235+ data.action.parameterType = Action.None;
236+ testItem.action = data.action;
237+ if (data.value) {
238+ data.action.parameterType = data.type;
239+ testItem.trigger(data.value);
240+ } else {
241+ testItem.trigger(data.value);
242+ }
243+ compare(data.action.invoked, data.invoked);
244 }
245
246 Action {
247@@ -198,7 +213,19 @@
248
249 Action {
250 id: suppressTrigger
251- function trigger() {}
252+ property bool invoked: false
253+ // we must override the parametered version as Button connects to the parametered version
254+ function trigger(v) { invoked = true }
255+ }
256+
257+ Action {
258+ id: override
259+ property bool invoked: false
260+ function trigger() { invoked = true }
261+ }
262+
263+ ActionItem {
264+ id: testItem
265 }
266
267 }
268
269=== added file 'tests/unit_x11/tst_bottomedge/OverriddenHintTrigger.qml'
270--- tests/unit_x11/tst_bottomedge/OverriddenHintTrigger.qml 1970-01-01 00:00:00 +0000
271+++ tests/unit_x11/tst_bottomedge/OverriddenHintTrigger.qml 2015-12-14 06:15:34 +0000
272@@ -0,0 +1,45 @@
273+/*
274+ * Copyright 2015 Canonical Ltd.
275+ *
276+ * This program is free software; you can redistribute it and/or modify
277+ * it under the terms of the GNU Lesser General Public License as published by
278+ * the Free Software Foundation; version 3.
279+ *
280+ * This program is distributed in the hope that it will be useful,
281+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
282+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
283+ * GNU Lesser General Public License for more details.
284+ *
285+ * You should have received a copy of the GNU Lesser General Public License
286+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
287+ *
288+ */
289+
290+import QtQuick 2.4
291+import Ubuntu.Components 1.3
292+
293+Item {
294+ id: holder
295+ width: units.gu(40)
296+ height: units.gu(71)
297+
298+ Action {
299+ id: testAction
300+ objectName: "testAction"
301+ text: "Test"
302+ function trigger() {}
303+ }
304+
305+ BottomEdge {
306+ hint {
307+ action: testAction
308+ }
309+ objectName: "testItem"
310+ contentComponent: Rectangle {
311+ width: holder.width
312+ height: holder.height
313+ color: UbuntuColors.green
314+ }
315+ }
316+}
317+
318
319=== modified file 'tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp'
320--- tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp 2015-12-02 15:31:53 +0000
321+++ tests/unit_x11/tst_bottomedge/tst_bottomedge.cpp 2015-12-14 06:15:34 +0000
322@@ -185,6 +185,22 @@
323 QTRY_COMPARE_WITH_TIMEOUT(test->testItem()->status(), UCBottomEdge::Committed, 1000);
324 }
325
326+ void test_overridden_triggers_bug1524234()
327+ {
328+ QScopedPointer<BottomEdgeTestCase> test(new BottomEdgeTestCase("OverriddenHintTrigger.qml"));
329+ test->testItem()->hint()->setStatus(UCBottomEdgeHint::Locked);
330+ UCBottomEdgeHint *hint = test->testItem()->hint();
331+ UCAction *action = hint->action();
332+ QSignalSpy actionSpy(action, SIGNAL(triggered(QVariant)));
333+ QSignalSpy hintSpy(hint, SIGNAL(triggered(QVariant)));
334+
335+ QTest::mouseClick(test->testItem()->hint()->window(), Qt::LeftButton, 0, UbuntuTestCase::centerOf(hint, true).toPoint());
336+ QTRY_COMPARE_WITH_TIMEOUT(test->testItem()->status(), UCBottomEdge::Committed, 1000);
337+
338+ QCOMPARE(actionSpy.count(), 0);
339+ QCOMPARE(hintSpy.count(), 1);
340+ }
341+
342 void test_revealed_when_hint_threshold_passed_data()
343 {
344 QTest::addColumn<bool>("withMouse");
345
346=== modified file 'tests/unit_x11/tst_bottomedge/tst_bottomedge.pro'
347--- tests/unit_x11/tst_bottomedge/tst_bottomedge.pro 2015-12-02 15:31:53 +0000
348+++ tests/unit_x11/tst_bottomedge/tst_bottomedge.pro 2015-12-14 06:15:34 +0000
349@@ -21,4 +21,5 @@
350 AutoCollapseInPageHeader.qml \
351 AutoCollapseInPageWithPageHeader.qml \
352 LeanActiveRegionChange.qml \
353- UncoveredByRegion.qml
354+ UncoveredByRegion.qml \
355+ OverriddenHintTrigger.qml
356
357=== modified file 'tests/unit_x11/tst_components/tst_abstractbutton13.qml'
358--- tests/unit_x11/tst_components/tst_abstractbutton13.qml 2015-12-05 05:59:07 +0000
359+++ tests/unit_x11/tst_components/tst_abstractbutton13.qml 2015-12-14 06:15:34 +0000
360@@ -41,6 +41,18 @@
361 height: width
362 function trigger() {}
363 }
364+ AbstractButton {
365+ id: suppressTrigger2
366+ width: units.gu(10)
367+ height: width
368+ function trigger(v) {}
369+ }
370+ AbstractButton {
371+ id: suppressTrigger3
372+ width: units.gu(10)
373+ height: width
374+ function trigger(v) { triggered(v) }
375+ }
376 Loader {
377 id: loader
378 width: units.gu(10)
379@@ -64,6 +76,11 @@
380 onTriggered: triggerCount++
381 }
382
383+ Action {
384+ id: override
385+ function trigger() {}
386+ }
387+
388 SignalSpy {
389 id: signalSpy
390 target: absButton
391@@ -87,6 +104,7 @@
392
393 function cleanup() {
394 signalSpy.clear();
395+ signalSpy.target = absButton;
396 triggeredSpy.clear();
397 loader.click = false;
398 loader.longPress = false;
399@@ -104,13 +122,38 @@
400 absButton.action = null
401 }
402
403- function test_custom_trigger_function() {
404- suppressTrigger.action = action1;
405+ function test_custom_trigger_function_data() {
406+ return [
407+ {tag: "parameterless trigger", testItem: suppressTrigger},
408+ {tag: "parameted trigger", testItem: suppressTrigger2},
409+ ];
410+ }
411+ function test_custom_trigger_function(data) {
412+ data.testItem.action = action1;
413 triggeredSpy.target = action1;
414- mouseClick(suppressTrigger, centerOf(suppressTrigger).x, centerOf(suppressTrigger).y);
415+ mouseClick(data.testItem, centerOf(data.testItem).x, centerOf(data.testItem).y);
416 compare(triggeredSpy.count, 0, "Trigger should be overridden");
417 }
418
419+ function test_custom_trigger_overrides_triggered_bug1524234_data() {
420+ return [
421+ {tag: "parameterless trigger", testItem: suppressTrigger, action: action1, triggeredWatch: action1, triggeredCount: 0 },
422+ {tag: "parameted trigger", testItem: suppressTrigger2, action: action1, triggeredWatch: action1, triggeredCount: 0 },
423+ {tag: "button trigger fired", testItem: suppressTrigger3, action: action1, triggeredWatch: suppressTrigger3, triggeredCount: 1 },
424+ {tag: "action trigger fired", testItem: suppressTrigger3, action: action1, triggeredWatch: action1, triggeredCount: 1 },
425+ {tag: "action trigger overridden", testItem: suppressTrigger3, action: override, triggeredWatch: override, triggeredCount: 0 },
426+ ];
427+ }
428+ function test_custom_trigger_overrides_triggered_bug1524234(data) {
429+ data.testItem.action = data.action;
430+ triggeredSpy.target = data.triggeredWatch;
431+ signalSpy.target = data.testItem;
432+ data.testItem.forceActiveFocus();
433+ keyClick(Qt.Key_Space);
434+ signalSpy.wait(200);
435+ compare(triggeredSpy.count, data.triggeredCount);
436+ }
437+
438 // fixing bugs 1365471 and 1458028
439 function test_no_pressAndHold_connected_clicks_bug1365471_bug1458028() {
440 signalSpy.target = absButton;

Subscribers

People subscribed via source and target branches