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

Proposed by Zsombor Egri
Status: Merged
Approved by: Zoltan Balogh
Approved revision: 1638
Merged at revision: 1638
Proposed branch: lp:~zsombi/ubuntu-ui-toolkit/blackbox
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 562 lines (+300/-135)
6 files modified
src/Ubuntu/Components/1.2/TextInputPopover.qml (+0/-7)
src/Ubuntu/Components/1.3/TextInputPopover.qml (+0/-6)
src/Ubuntu/Components/plugin/ucactionitem.cpp (+61/-36)
src/Ubuntu/Components/plugin/ucactionitem.h (+13/-4)
tests/resources/navigation/Blackbox.qml (+48/-0)
tests/unit_x11/tst_components/tst_actionitem.qml (+178/-82)
To merge this branch: bzr merge lp:~zsombi/ubuntu-ui-toolkit/blackbox
Reviewer Review Type Date Requested Status
Zoltan Balogh Approve
PS Jenkins bot continuous-integration Approve
Review via email: mp+271074@code.launchpad.net

Commit message

ActionItem visible and enabled internal bindings not broken when the property values are having new bindings or value assignments.

Description of the change

ActionItem visible and enabled internal bindings not broken when the property values are having new bindings or value assignments.

To post a comment you must log in.
Revision history for this message
Zsombor Egri (zsombi) wrote :

tst_actionitem.qml test had to be moved under X11 as visible property is not set on the dynamically created item.

1635. By Zsombor Egri

fix

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
1636. By Zsombor Egri

override visible and enabled properties with custom setters to capture QML binding caused updates

1637. By Zsombor Egri

remove text property override in TextInputPopovers as those already have text property

1638. By Zsombor Egri

staging sync

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Zoltan Balogh (bzoltan) wrote :

Does the job

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/1.2/TextInputPopover.qml'
2--- src/Ubuntu/Components/1.2/TextInputPopover.qml 2015-08-19 06:55:11 +0000
3+++ src/Ubuntu/Components/1.2/TextInputPopover.qml 2015-09-16 05:29:55 +0000
4@@ -83,13 +83,6 @@
5 model: actions.length
6 AbstractButton {
7 id: button
8- /*
9- Workaround for autopilot used in the text input's context menu to access
10- action.text so we can get the proper button by text, action being not
11- accessible. https://bugs.launchpad.net/autopilot/+bug/1334599
12- */
13- // FIXME: AbstractButton has text property, which is getting the action.text, so no need to override!
14- property string text: action.text
15 width: Math.max(units.gu(5), implicitWidth) + units.gu(2)
16 height: units.gu(6)
17 action: actions[modelData]
18
19=== modified file 'src/Ubuntu/Components/1.3/TextInputPopover.qml'
20--- src/Ubuntu/Components/1.3/TextInputPopover.qml 2015-07-02 23:33:22 +0000
21+++ src/Ubuntu/Components/1.3/TextInputPopover.qml 2015-09-16 05:29:55 +0000
22@@ -83,12 +83,6 @@
23 model: actions.length
24 AbstractButton {
25 id: button
26- /*
27- Workaround for autopilot used in the text input's context menu to access
28- action.text so we can get the proper button by text, action being not
29- accessible. https://bugs.launchpad.net/autopilot/+bug/1334599
30- */
31- property string text: action.text
32 width: Math.max(units.gu(5), implicitWidth) + units.gu(2)
33 height: units.gu(6)
34 action: actions[modelData]
35
36=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.cpp'
37--- src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-08-25 11:31:29 +0000
38+++ src/Ubuntu/Components/plugin/ucactionitem.cpp 2015-09-16 05:29:55 +0000
39@@ -16,6 +16,10 @@
40
41 #include "ucactionitem.h"
42 #include "ucaction.h"
43+#include "ucstyleditembase_p.h"
44+#define foreach Q_FOREACH
45+#include <QtQml/private/qqmlbinding_p.h>
46+#undef foreach
47
48 /*!
49 * \qmltype ActionItem
50@@ -40,8 +44,14 @@
51 , m_action(Q_NULLPTR)
52 , m_flags(0)
53 {
54- connect(this, &UCActionItem::visibleChanged, this, &UCActionItem::_q_visibleChanged);
55- connect(this, &UCActionItem::enabledChanged, this, &UCActionItem::_q_enabledChanged);
56+ connect(this, &UCActionItem::enabledChanged, this, &UCActionItem::enabledChanged2);
57+ connect(this, &UCActionItem::visibleChanged, this, &UCActionItem::visibleChanged2);
58+}
59+
60+bool UCActionItem::hasBindingOnProperty(const QString &name)
61+{
62+ QQmlProperty property(this, name, qmlContext(this));
63+ return QQmlPropertyPrivate::binding(property) != Q_NULLPTR;
64 }
65
66 void UCActionItem::componentComplete()
67@@ -55,40 +65,47 @@
68 }
69 }
70
71-void UCActionItem::_q_visibleChanged()
72-{
73- m_flags |= CustomVisible;
74- disconnect(this, &UCActionItem::visibleChanged, this, &UCActionItem::_q_visibleChanged);
75-}
76-
77-void UCActionItem::_q_enabledChanged()
78-{
79- m_flags |= CustomEnabled;
80- disconnect(this, &UCActionItem::enabledChanged, this, &UCActionItem::_q_enabledChanged);
81-}
82-
83 // update visible property
84-void UCActionItem::_q_updateVisible()
85+void UCActionItem::_q_visibleBinding()
86 {
87+ if (m_flags & CustomVisible) {
88+ return;
89+ }
90+ if (hasBindingOnProperty(QStringLiteral("visible"))) {
91+ m_flags |= CustomEnabled;
92+ return;
93+ }
94 bool visible = m_action ? m_action->m_visible : true;
95 setVisible(visible);
96- // reset flag and reconnect signal handler disconnected by the
97- m_flags &= ~CustomVisible;
98- if (m_action) {
99- connect(this, &UCActionItem::visibleChanged, this, &UCActionItem::_q_visibleChanged);
100- }
101 }
102
103 // update enabled property
104-void UCActionItem::_q_updateEnabled()
105+void UCActionItem::_q_enabledBinding()
106 {
107+ if (m_flags & CustomEnabled) {
108+ return;
109+ }
110+ if (hasBindingOnProperty(QStringLiteral("enabled"))) {
111+ m_flags |= CustomEnabled;
112+ return;
113+ }
114 bool enabled = m_action ? m_action->m_enabled : true;
115 setEnabled(enabled);
116- // reset flag and reconnect signal handler disconnected by the
117- m_flags &= ~CustomEnabled;
118- if (m_action) {
119- connect(this, &UCActionItem::enabledChanged, this, &UCActionItem::_q_enabledChanged);
120- }
121+}
122+
123+// setter called when bindings from QML set the value. Internal functions will
124+// all use the setVisible setter, so initialization and (re)parenting related
125+// visible alteration won't set the custom flag
126+void UCActionItem::setVisible2(bool visible)
127+{
128+ // set the custom flag and forward the value to the original proepry setter
129+ m_flags |= CustomVisible;
130+ setVisible(visible);
131+}
132+void UCActionItem::setEnabled2(bool enabled)
133+{
134+ m_flags |= CustomEnabled;
135+ setEnabled(enabled);
136 }
137
138 void UCActionItem::updateProperties()
139@@ -109,10 +126,14 @@
140 if (attach) {
141 connect(this, SIGNAL(triggered(QVariant)),
142 m_action, SLOT(trigger(QVariant)), Qt::DirectConnection);
143- connect(m_action, &UCAction::visibleChanged,
144- this, &UCActionItem::_q_updateVisible, Qt::DirectConnection);
145- connect(m_action, &UCAction::enabledChanged,
146- this, &UCActionItem::_q_updateEnabled, Qt::DirectConnection);
147+ if (!(m_flags & CustomVisible)) {
148+ connect(m_action, &UCAction::visibleChanged,
149+ this, &UCActionItem::_q_visibleBinding, Qt::DirectConnection);
150+ }
151+ if (!(m_flags & CustomEnabled)) {
152+ connect(m_action, &UCAction::enabledChanged,
153+ this, &UCActionItem::_q_enabledBinding, Qt::DirectConnection);
154+ }
155 if (!(m_flags & CustomText)) {
156 connect(m_action, &UCAction::textChanged,
157 this, &UCActionItem::textChanged, Qt::DirectConnection);
158@@ -128,10 +149,14 @@
159 } else {
160 disconnect(this, SIGNAL(triggered(QVariant)),
161 m_action, SLOT(trigger(QVariant)));
162- disconnect(m_action, &UCAction::visibleChanged,
163- this, &UCActionItem::_q_updateVisible);
164- disconnect(m_action, &UCAction::enabledChanged,
165- this, &UCActionItem::_q_updateEnabled);
166+ if (!(m_flags & CustomVisible)) {
167+ disconnect(m_action, &UCAction::visibleChanged,
168+ this, &UCActionItem::_q_visibleBinding);
169+ }
170+ if (!(m_flags & CustomEnabled)) {
171+ disconnect(m_action, &UCAction::enabledChanged,
172+ this, &UCActionItem::_q_enabledBinding);
173+ }
174 if (!(m_flags & CustomText)) {
175 disconnect(m_action, &UCAction::textChanged,
176 this, &UCActionItem::textChanged);
177@@ -167,8 +192,8 @@
178 if (m_action) {
179 attachAction(true);
180 }
181- _q_updateVisible();
182- _q_updateEnabled();
183+ _q_visibleBinding();
184+ _q_enabledBinding();
185 updateProperties();
186 }
187
188
189=== modified file 'src/Ubuntu/Components/plugin/ucactionitem.h'
190--- src/Ubuntu/Components/plugin/ucactionitem.h 2015-08-25 11:31:29 +0000
191+++ src/Ubuntu/Components/plugin/ucactionitem.h 2015-09-16 05:29:55 +0000
192@@ -26,6 +26,10 @@
193 Q_PROPERTY(QString text READ text WRITE setText RESET resetText NOTIFY textChanged)
194 Q_PROPERTY(QUrl iconSource READ iconSource WRITE setIconSource RESET resetIconSource NOTIFY iconSourceChanged)
195 Q_PROPERTY(QString iconName READ iconName WRITE setIconName RESET resetIconName NOTIFY iconNameChanged)
196+
197+ // overrides
198+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled2 NOTIFY enabledChanged2)
199+ Q_PROPERTY(bool visible READ isVisible WRITE setVisible2 NOTIFY visibleChanged2 FINAL)
200 public:
201 explicit UCActionItem(QQuickItem *parent = 0);
202
203@@ -40,6 +44,9 @@
204 void setIconName(const QString &iconName);
205 void resetIconName();
206
207+ void setVisible2(bool visible);
208+ void setEnabled2(bool enabled);
209+
210 Q_SIGNALS:
211 void actionChanged();
212 void textChanged();
213@@ -47,14 +54,15 @@
214 void iconNameChanged();
215 void triggered(const QVariant &value);
216
217+ void enabledChanged2();
218+ void visibleChanged2();
219+
220 public Q_SLOTS:
221 void trigger(const QVariant &value = QVariant());
222
223 protected Q_SLOTS:
224- void _q_visibleChanged();
225- void _q_enabledChanged();
226- void _q_updateVisible();
227- void _q_updateEnabled();
228+ void _q_visibleBinding();
229+ void _q_enabledBinding();
230
231 protected:
232 enum {
233@@ -72,6 +80,7 @@
234
235 void componentComplete();
236
237+ bool hasBindingOnProperty(const QString &name);
238 void updateProperties();
239 void attachAction(bool attach);
240 };
241
242=== added file 'tests/resources/navigation/Blackbox.qml'
243--- tests/resources/navigation/Blackbox.qml 1970-01-01 00:00:00 +0000
244+++ tests/resources/navigation/Blackbox.qml 2015-09-16 05:29:55 +0000
245@@ -0,0 +1,48 @@
246+/*
247+ * Copyright 2015 Canonical Ltd.
248+ *
249+ * This program is free software; you can redistribute it and/or modify
250+ * it under the terms of the GNU Lesser General Public License as published by
251+ * the Free Software Foundation; version 3.
252+ *
253+ * This program is distributed in the hope that it will be useful,
254+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
255+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
256+ * GNU Lesser General Public License for more details.
257+ *
258+ * You should have received a copy of the GNU Lesser General Public License
259+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
260+ */
261+
262+import QtQuick 2.4
263+import Ubuntu.Components 1.2
264+
265+MainView {
266+ width: units.gu(40)
267+ height: units.gu(60)
268+ Component.onCompleted: pageStack.push(page1)
269+
270+ PageStack {
271+ id: pageStack
272+ }
273+
274+ Page {
275+ id: page1
276+ title: "one"
277+ Button {
278+ anchors.centerIn: parent
279+ text: "next"
280+ onClicked: pageStack.push(page2)
281+ }
282+ }
283+ Page {
284+ id: page2
285+ title: "two"
286+ visible: false
287+
288+ head.backAction: Action {
289+ iconName: "back"
290+ onTriggered: pageStack.pop()
291+ }
292+ }
293+}
294
295=== renamed file 'tests/unit/tst_components/tst_actionitem.qml' => 'tests/unit_x11/tst_components/tst_actionitem.qml'
296--- tests/unit/tst_components/tst_actionitem.qml 2015-08-18 16:58:41 +0000
297+++ tests/unit_x11/tst_components/tst_actionitem.qml 2015-09-16 05:29:55 +0000
298@@ -18,86 +18,182 @@
299 import QtTest 1.0
300 import Ubuntu.Components 1.1
301
302-TestCase {
303- name: "ActionItemAPI"
304-
305- SignalSpy {
306- id: triggerSpy
307- target: action1
308- signalName: "triggered"
309- }
310-
311- function initTestCase() {
312- compare(item1.action, null, "action is null by default")
313- compare(item1.text, "", "text is empty string set by default")
314- compare(item1.iconSource, "", "iconSource is empty string by default")
315- compare(item1.iconName, "", "iconSource is empty string by default")
316- }
317-
318- function cleanup() {
319- item1.action = null;
320- triggerSpy.clear();
321- }
322-
323- function test_action() {
324- compare(item1.action, null,"Action is null by default")
325- item1.action = action1
326- compare(item1.action, action1, "Action can be set")
327- compare(item1.text, action1.text, "text is automatically set to action text")
328- compare(item1.iconSource, action1.iconSource, "iconSource is automatically set to action iconSource")
329- item1.triggered(null)
330- triggerSpy.wait(400);
331- }
332-
333- // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
334- // not update the text
335- function test_text() {
336- compare(item1.text, "", "text is empty string by default")
337- var newText = "new text"
338- item1.text = newText
339- compare(item1.text, newText, "text can be set")
340- item1.text = ""
341- compare(item1.text, "", "text can be unset")
342- }
343-
344- // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
345- // will not update the iconSource
346- function test_iconSource() {
347- compare(item1.iconSource, "", "iconSource is empty string by default")
348- var newIconSource = Qt.resolvedUrl("../../../examples/ubuntu-ui-toolkit-gallery/small_avatar.png")
349- item1.iconSource = newIconSource
350- compare(item1.iconSource, newIconSource, "iconSource can be set")
351- item1.iconSource = ""
352- compare(item1.iconSource, "", "iconSource can be unset")
353- }
354-
355- // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
356- // will not update the iconName
357- function test_iconName() {
358- compare(item1.iconName, "", "iconName is empty string by default")
359- var newIconName = "compose"
360- item1.iconName = newIconName
361- compare(item1.iconName, newIconName, "iconName can be set")
362- item1.iconName = ""
363- compare(item1.iconName, "", "iconName can be unset")
364- }
365-
366- function test_signal_triggered() {
367- signalSpy.signalName = "triggered";
368- compare(signalSpy.valid,true,"triggered signal exists")
369- }
370-
371- ActionItem {
372- id: item1
373- SignalSpy {
374- id: signalSpy
375- target: parent
376- }
377- }
378-
379- Action {
380- id: action1
381- text: "actionText"
382- iconSource: "imageURL"
383- }
384+Item {
385+ id: main
386+ width: units.gu(20)
387+ height: units.gu(20)
388+
389+ property bool customVisible: true
390+ property bool customEnabled: true
391+
392+ ActionItem {
393+ id: item1
394+ SignalSpy {
395+ id: signalSpy
396+ target: parent
397+ }
398+ }
399+
400+ Component {
401+ id: dynamicItem
402+ ActionItem {
403+ action: action1
404+ }
405+ }
406+ Component {
407+ id: dynamicItem2
408+ ActionItem {
409+ action: action1
410+ visible: customVisible
411+ enabled: customEnabled
412+ }
413+ }
414+
415+ Action {
416+ id: action1
417+ objectName: "action1"
418+ text: "actionText"
419+ iconSource: "imageURL"
420+ }
421+ Action {
422+ id: action2
423+ objectName: "action2"
424+ }
425+
426+ Loader {
427+ id: loader
428+ asynchronous: false
429+ }
430+
431+ TestCase {
432+ id: testCase
433+ when: windowShown
434+ name: "ActionItemAPI"
435+
436+ SignalSpy {
437+ id: triggerSpy
438+ target: action1
439+ signalName: "triggered"
440+ }
441+
442+ function initTestCase() {
443+ compare(item1.action, null, "action is null by default")
444+ compare(item1.text, "", "text is empty string set by default")
445+ compare(item1.iconSource, "", "iconSource is empty string by default")
446+ compare(item1.iconName, "", "iconSource is empty string by default")
447+ }
448+
449+ function cleanup() {
450+ loader.sourceComponent = null;
451+ item1.action = null;
452+ action1.visible = true;
453+ action1.enabled = true;
454+ action2.visible = true;
455+ action2.enabled = true;
456+ main.customEnabled = true;
457+ main.customVisible = true;
458+ triggerSpy.clear();
459+ }
460+
461+ function test_action() {
462+ compare(item1.action, null,"Action is null by default")
463+ item1.action = action1
464+ compare(item1.action, action1, "Action can be set")
465+ compare(item1.text, action1.text, "text is automatically set to action text")
466+ compare(item1.iconSource, action1.iconSource, "iconSource is automatically set to action iconSource")
467+ item1.triggered(null)
468+ triggerSpy.wait(400);
469+ }
470+
471+ // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
472+ // not update the text
473+ function test_text() {
474+ compare(item1.text, "", "text is empty string by default")
475+ var newText = "new text"
476+ item1.text = newText
477+ compare(item1.text, newText, "text can be set")
478+ item1.text = ""
479+ compare(item1.text, "", "text can be unset")
480+ }
481+
482+ // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
483+ // will not update the iconSource
484+ function test_iconSource() {
485+ compare(item1.iconSource, "", "iconSource is empty string by default")
486+ var newIconSource = Qt.resolvedUrl("../../../examples/ubuntu-ui-toolkit-gallery/small_avatar.png")
487+ item1.iconSource = newIconSource
488+ compare(item1.iconSource, newIconSource, "iconSource can be set")
489+ item1.iconSource = ""
490+ compare(item1.iconSource, "", "iconSource can be unset")
491+ }
492+
493+ // NOTE: This test must be run AFTER test_action(), otherwise setting the action will
494+ // will not update the iconName
495+ function test_iconName() {
496+ compare(item1.iconName, "", "iconName is empty string by default")
497+ var newIconName = "compose"
498+ item1.iconName = newIconName
499+ compare(item1.iconName, newIconName, "iconName can be set")
500+ item1.iconName = ""
501+ compare(item1.iconName, "", "iconName can be unset")
502+ }
503+
504+ function test_signal_triggered() {
505+ signalSpy.signalName = "triggered";
506+ compare(signalSpy.valid,true,"triggered signal exists")
507+ }
508+
509+ function test_default_bindings_visible_enabled_data() {
510+ return [
511+ {tag: "visible", property: "visible"},
512+ {tag: "enabled", property: "enabled"},
513+ ];
514+ }
515+ function test_default_bindings_visible_enabled(data) {
516+ item1.action = action1;
517+ action1[data.property] = false;
518+ compare(item1[data.property], action1[data.property], "The item1 and action1 '" + data.property + "' value differs");
519+ }
520+
521+ function test_custom_bindings_visible_enabled_bug1495408_data() {
522+ return [
523+ {tag: "visible", component: dynamicItem, property: "visible"},
524+ {tag: "enabled", component: dynamicItem, property: "enabled"},
525+ {tag: "visible binding", component: dynamicItem2, property: "visible", customProperty: "customVisible"},
526+ {tag: "enabled binding", component: dynamicItem2, property: "enabled", customProperty: "customEnabled"},
527+ ];
528+ }
529+ function test_custom_bindings_visible_enabled_bug1495408(data) {
530+ loader.sourceComponent = data.component;
531+ var item = loader.item;
532+ compare(item[data.property], action1[data.property], "The item and action1 '" + data.property + "' value differs");
533+ if (data.customProperty) {
534+ main[data.customProperty] = false;
535+ } else {
536+ item[data.property] = false;
537+ }
538+ // change the action so the internal bindings are updated
539+ item.action = action2;
540+ expectFail(data.tag, "default binding must be broken");
541+ compare(item[data.property], item.action[data.property], "The item's and action's '" + data.property + "' value is the same");
542+ }
543+ function test_custom_bindings_visible_enabled_reparenting_bug1495408_data() {
544+ return [
545+ {tag: "visible binding", component: dynamicItem2, property: "visible"},
546+ {tag: "enabled binding", component: dynamicItem2, property: "enabled"},
547+ ];
548+ }
549+ function test_custom_bindings_visible_enabled_reparenting_bug1495408(data) {
550+ loader.sourceComponent = dynamicItem2;
551+ var item = loader.item;
552+ compare(item[data.property], item.action[data.property], "The item and item.action '" + data.property + "' value differs");
553+ // then reparent
554+ item.parent = item1;
555+ // change the action property
556+
557+ item.action[data.property] = false;
558+ expectFail(data.tag, "default binding must be broken");
559+ compare(item[data.property], item.action[data.property], "The item and action2 '" + data.property + "' value is the same");
560+ }
561+ }
562 }

Subscribers

People subscribed via source and target branches