on xenial help sign in
Application menus
90770d5Michał Sawicz
debian/control+3−3
@@ -29,7 +29,7 @@ Build-Depends: cmake,
29 libpam0g-dev,
29 libpam0g-dev,
30 libpay2-dev,
30 libpay2-dev,
31 libpulse-dev,
31 libpulse-dev,
32− libqmenumodel-dev (>= 0.2.10),
32+ libqmenumodel-dev (>= 0.2.11),
33 libqt5svg5-dev,
33 libqt5svg5-dev,
34 libqt5xmlpatterns5-dev,
34 libqt5xmlpatterns5-dev,
35 libsystemsettings-dev,
35 libsystemsettings-dev,
@@ -83,7 +83,7 @@ X-Ubuntu-Use-Langpack: yes
83
83
84Package: indicators-client
84Package: indicators-client
85Architecture: any
85Architecture: any
86−Depends: qmenumodel-qml (>= 0.2.10),
86+Depends: qmenumodel-qml (>= 0.2.11),
87 qml-module-ubuntu-components (>= 1.3.2030) | qml-module-ubuntu-components-gles (>= 1.3.2030),
87 qml-module-ubuntu-components (>= 1.3.2030) | qml-module-ubuntu-components-gles (>= 1.3.2030),
88 unity8 (= ${binary:Version}),
88 unity8 (= ${binary:Version}),
89 ${misc:Depends},
89 ${misc:Depends},
@@ -122,7 +122,7 @@ Depends: dmz-cursor-theme,
122 gsettings-desktop-schemas,
122 gsettings-desktop-schemas,
123 libcap2-bin,
123 libcap2-bin,
124 libglib2.0-bin,
124 libglib2.0-bin,
125− qmenumodel-qml (>= 0.2.10),
125+ qmenumodel-qml (>= 0.2.11),
126 qml-module-biometryd,
126 qml-module-biometryd,
127 qml-module-qt-labs-folderlistmodel,
127 qml-module-qt-labs-folderlistmodel,
128 qml-module-qt-labs-settings,
128 qml-module-qt-labs-settings,
debian/unity8.install+1
@@ -8,6 +8,7 @@ usr/bin/unity8
8usr/bin/unity8-dash
8usr/bin/unity8-dash
9usr/share/applications/unity8-dash.desktop
9usr/share/applications/unity8-dash.desktop
10usr/share/applications/unity8.desktop
10usr/share/applications/unity8.desktop
11+usr/share/unity8/ApplicationMenus
11usr/share/unity8/Greeter
12usr/share/unity8/Greeter
12usr/share/unity8/Launcher
13usr/share/unity8/Launcher
13usr/share/unity8/Panel
14usr/share/unity8/Panel
plugins/GlobalShortcut/globalshortcut.cpp+12−4
@@ -63,10 +63,18 @@ void GlobalShortcut::componentComplete()
63
63
64void GlobalShortcut::keyPressEvent(QKeyEvent * event)
64void GlobalShortcut::keyPressEvent(QKeyEvent * event)
65{
65{
66− Q_UNUSED(event)
67− if (m_active) {
66+ if (!m_active) return;
67+
68+ event->accept();
68− Q_EMIT triggered(m_shortcut.toString());
69+ Q_EMIT triggered(m_shortcut.toString());
69− }
70+}
71+
72+void GlobalShortcut::keyReleaseEvent(QKeyEvent * event)
73+{
74+ if (!m_active) return;
75+
76+ event->accept();
77+ Q_EMIT released(m_shortcut.toString());
70}
78}
71
79
72void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
80void GlobalShortcut::setupFilterOnWindow(QQuickWindow *window)
plugins/GlobalShortcut/globalshortcut.h+5
@@ -55,6 +55,7 @@ public:
55protected:
55protected:
56 void componentComplete() override;
56 void componentComplete() override;
57 void keyPressEvent(QKeyEvent * event) override;
57 void keyPressEvent(QKeyEvent * event) override;
58+ void keyReleaseEvent(QKeyEvent * event) override;
58
59
59Q_SIGNALS:
60Q_SIGNALS:
60 void shortcutChanged(const QVariant &shortcut);
61 void shortcutChanged(const QVariant &shortcut);
@@ -62,6 +63,10 @@ Q_SIGNALS:
62 * Emitted when a global keypress of @p shortcut is detected
63 * Emitted when a global keypress of @p shortcut is detected
63 */
64 */
64 void triggered(const QString &shortcut);
65 void triggered(const QString &shortcut);
66+ /**
67+ * Emitted when a global keypress of @p shortcut is released
68+ */
69+ void released(const QString &shortcut);
65 void activeChanged(bool active);
70 void activeChanged(bool active);
66
71
67private Q_SLOTS:
72private Q_SLOTS:
plugins/GlobalShortcut/globalshortcutregistry.cpp+14−4
@@ -73,20 +73,30 @@ bool GlobalShortcutRegistry::eventFilter(QObject *obj, QEvent *event)
73 Q_ASSERT(m_filteredWindow);
73 Q_ASSERT(m_filteredWindow);
74 Q_ASSERT(obj == static_cast<QObject*>(m_filteredWindow.data()));
74 Q_ASSERT(obj == static_cast<QObject*>(m_filteredWindow.data()));
75
75
76− if (event->type() == QEvent::KeyPress) {
76+ if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
77+
77 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
78 QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
79+
80+ // Make a copy of the event so we don't alter it for passing on.
81+ QKeyEvent eCopy(keyEvent->type(),
82+ keyEvent->key(),
83+ keyEvent->modifiers(),
84+ keyEvent->text(),
85+ keyEvent->isAutoRepeat(),
86+ keyEvent->count());
87+ eCopy.ignore();
88+
78 int seq = keyEvent->key() + keyEvent->modifiers();
89 int seq = keyEvent->key() + keyEvent->modifiers();
79 if (m_shortcuts.contains(seq)) {
90 if (m_shortcuts.contains(seq)) {
80 const auto shortcuts = m_shortcuts.value(seq);
91 const auto shortcuts = m_shortcuts.value(seq);
81 Q_FOREACH(const auto &shortcut, shortcuts) {
92 Q_FOREACH(const auto &shortcut, shortcuts) {
82 if (shortcut) {
93 if (shortcut) {
83− qApp->sendEvent(shortcut, keyEvent);
94+ qApp->sendEvent(shortcut, &eCopy);
84− event->accept();
85 }
95 }
86 }
96 }
87 }
97 }
88
98
89− return event->isAccepted();
99+ return eCopy.isAccepted();
90 }
100 }
91
101
92 return QObject::eventFilter(obj, event);
102 return QObject::eventFilter(obj, event);
plugins/Unity/Indicators/sharedunitymenumodel.cpp+4
@@ -81,12 +81,16 @@ void SharedUnityMenuModel::initialize()
81 }
81 }
82 } else {
82 } else {
83 QSharedPointer<UnityMenuModel> model = UnityMenuModelCache::singleton()->model(m_menuObjectPath);
83 QSharedPointer<UnityMenuModel> model = UnityMenuModelCache::singleton()->model(m_menuObjectPath);
84+
84 if (model != m_model) {
85 if (model != m_model) {
85 if (model->busName() != m_busName) model->setBusName(m_busName);
86 if (model->busName() != m_busName) model->setBusName(m_busName);
86 if (model->actions() != m_actions) model->setActions(m_actions);
87 if (model->actions() != m_actions) model->setActions(m_actions);
87
88
88 m_model = model;
89 m_model = model;
89 Q_EMIT modelChanged();
90 Q_EMIT modelChanged();
91+ } else if (m_model) {
92+ if (m_model->busName() != m_busName) m_model->setBusName(m_busName);
93+ if (m_model->actions() != m_actions) m_model->setActions(m_actions);
90 }
94 }
91 }
95 }
92}
96}
qml/ApplicationMenus/ApplicationMenuItemFactory.qmlnew file+135
@@ -0,0 +1,135 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Settings.Menus 0.1 as Menus
19+import Ubuntu.Settings.Components 0.1
20+import QMenuModel 0.1
21+import Utils 0.1 as Utils
22+import Ubuntu.Components 1.3
23+
24+Object {
25+ id: menuFactory
26+
27+ property string context
28+ property var rootModel: null
29+ property var menuModel: null
30+
31+ Component {
32+ id: applicationMenu;
33+
34+ ListItem {
35+ property QtObject menuData: null
36+ property int menuIndex: -1
37+
38+ height: layout.height
39+ enabled: menuData && menuData.sensitive || false
40+ divider.visible: false
41+
42+ onClicked: {
43+ menuModel.activate(menuIndex);
44+ }
45+
46+ Action {
47+ id: action
48+ text: menuData.label.replace("_", "&")
49+ }
50+
51+ ListItemLayout {
52+ id: layout
53+ title.text: action.text
54+
55+ Icon {
56+ source: menuData && menuData.icon || ""
57+ SlotsLayout.position: SlotsLayout.Leading
58+ height: units.gu(3)
59+ }
60+
61+ Label {
62+ text: menuData.shortcut
63+ visible: menuData.shortcut && QuickUtils.keyboardAttached
64+ SlotsLayout.position: SlotsLayout.Trailing
65+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
66+ theme.palette.disabled.backgroundSecondaryText
67+ }
68+ }
69+ }
70+ }
71+
72+ Component {
73+ id: submenu
74+
75+ ListItem {
76+ property QtObject menuData: null
77+ property int menuIndex: -1
78+
79+ height: layout.height
80+ enabled: menuData && menuData.sensitive || false
81+ divider.visible: false
82+
83+ onClicked: {
84+ menuModel.activate(menuIndex);
85+ }
86+
87+ Action {
88+ id: action
89+ text: menuData.label.replace("_", "&")
90+ }
91+
92+ ListItemLayout {
93+ id: layout
94+ title.text: action.text
95+
96+ Icon {
97+ source: menuData && menuData.icon || ""
98+ SlotsLayout.position: SlotsLayout.Leading
99+ height: units.gu(3)
100+ }
101+
102+ Icon {
103+ name: "toolkit_chevron-ltr_1gu"
104+ SlotsLayout.position: SlotsLayout.Trailing
105+ width: units.gu(2)
106+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
107+ theme.palette.disabled.backgroundSecondaryText
108+ }
109+ }
110+ }
111+ }
112+
113+ Component {
114+ id: applicationMenuSeparator;
115+
116+ Menus.SeparatorMenu {
117+ objectName: "separatorMenu"
118+ }
119+ }
120+
121+ function load(modelData) {
122+ if (modelData.isSeparator) {
123+ return applicationMenuSeparator;
124+ }
125+ if (modelData.isRadio) {
126+ }
127+ if (modelData.isCheck) {
128+
129+ }
130+ if (modelData.hasSubmenu) {
131+ return submenu;
132+ }
133+ return applicationMenu;
134+ }
135+}
qml/ApplicationMenus/MenuBar.qmlnew file+304
@@ -0,0 +1,304 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Utils 0.1
20+import Ubuntu.Components 1.3
21+import GlobalShortcut 1.0
22+
23+Item {
24+ id: root
25+ objectName: "menuBar"
26+
27+ property alias unityMenuModel: rowRepeater.model
28+
29+ readonly property bool valid: rowRepeater.count > 0
30+
31+ property bool enableKeyFilter: false
32+
33+ readonly property bool showRequested: d.longAltPressed || d.currentItem != null
34+
35+ implicitWidth: row.width
36+ height: parent.height
37+
38+ function select(index) {
39+ d.select(index);
40+ }
41+
42+ function dismiss() {
43+ d.dismissAll();
44+ }
45+
46+ GlobalShortcut {
47+ shortcut: Qt.Key_Alt|Qt.AltModifier
48+ active: enableKeyFilter
49+ onTriggered: d.startShortcutTimer()
50+ onReleased: d.stopSHortcutTimer()
51+ }
52+ // On an actual keyboard, the AltModifier is not supplied on release.
53+ GlobalShortcut {
54+ shortcut: Qt.Key_Alt
55+ active: enableKeyFilter
56+ onTriggered: d.startShortcutTimer()
57+ onReleased: d.stopSHortcutTimer()
58+ }
59+
60+ InverseMouseArea {
61+ acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
62+ anchors.fill: parent
63+ enabled: d.currentItem != null
64+ onPressed: d.dismissAll()
65+ }
66+
67+ Item {
68+ id: clippingItem
69+
70+ height: root.height
71+ width: root.width
72+ clip: true
73+
74+ Row {
75+ id: row
76+ spacing: units.gu(2)
77+ height: parent.height
78+
79+ ActionContext {
80+ id: menuBarContext
81+ objectName: "barContext"
82+ active: !d.currentItem && enableKeyFilter
83+ }
84+
85+ Repeater {
86+ id: rowRepeater
87+
88+ Item {
89+ id: visualItem
90+ objectName: root.objectName + "-item" + __ownIndex
91+
92+ readonly property int __ownIndex: index
93+ property Item __popup: null;
94+ property bool popupVisible: __popup && __popup.visible
95+
96+ implicitWidth: column.implicitWidth
97+ implicitHeight: row.height
98+ enabled: model.sensitive
99+
100+ function show() {
101+ if (!__popup) {
102+ __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
103+ // force the current item to be the newly popped up menu
104+ } else {
105+ __popup.show();
106+ }
107+ d.currentItem = visualItem;
108+ }
109+ function hide() {
110+ if (__popup) {
111+ __popup.hide();
112+
113+ if (d.currentItem === visualItem) {
114+ d.currentItem = null;
115+ }
116+ }
117+ }
118+ function dismiss() {
119+ if (__popup) {
120+ __popup.destroy();
121+ __popup = null;
122+
123+ if (d.currentItem === visualItem) {
124+ d.currentItem = null;
125+ }
126+ }
127+ }
128+
129+ Connections {
130+ target: d
131+ onDismissAll: visualItem.dismiss()
132+ }
133+
134+ Component {
135+ id: menuComponent
136+ MenuPopup {
137+ x: visualItem.x - units.gu(1)
138+ anchors.top: parent.bottom
139+ unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
140+
141+ Component.onCompleted: reset();
142+ }
143+ }
144+
145+ RowLayout {
146+ id: column
147+ spacing: units.gu(1)
148+ anchors {
149+ centerIn: parent
150+ }
151+
152+ Icon {
153+ Layout.preferredWidth: units.gu(2)
154+ Layout.preferredHeight: units.gu(2)
155+ Layout.alignment: Qt.AlignVCenter
156+
157+ visible: model.icon || false
158+ source: model.icon || ""
159+ }
160+
161+ ActionItem {
162+ id: actionItem
163+ width: _title.width
164+ height: _title.height
165+
166+ action: Action {
167+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
168+ text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
169+
170+ onTriggered: {
171+ visualItem.show();
172+ }
173+ }
174+
175+ Label {
176+ id: _title
177+ text: actionItem.text
178+ horizontalAlignment: Text.AlignLeft
179+ color: enabled ? "white" : "#5d5d5d"
180+ }
181+ }
182+ }
183+ } // Item ( delegate )
184+ } // Repeater
185+ } // Row
186+
187+ MouseArea {
188+ anchors.fill: parent
189+ hoverEnabled: d.currentItem
190+
191+ onEntered: {
192+ if (d.currentItem) {
193+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
194+ }
195+ }
196+ onPositionChanged: {
197+ if (d.currentItem) {
198+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
199+ }
200+ }
201+ onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
202+
203+ function updateCurrentItemFromPosition(point) {
204+ var pos = mapToItem(row, point.x, point.y);
205+
206+ if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
207+ d.hoveredItem = row.childAt(pos.x, pos.y);
208+ if (!d.hoveredItem || !d.hoveredItem.enabled)
209+ return false;
210+ if (d.currentItem != d.hoveredItem) {
211+ d.currentItem = d.hoveredItem;
212+ }
213+ }
214+ return true;
215+ }
216+ }
217+
218+ Rectangle {
219+ id: underline
220+ anchors {
221+ bottom: row.bottom
222+ }
223+ x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
224+ width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
225+ height: units.dp(4)
226+ color: UbuntuColors.orange
227+ visible: d.currentItem
228+ }
229+ }
230+
231+ MenuNavigator {
232+ id: d
233+ objectName: "d"
234+ itemView: rowRepeater
235+
236+ property Item currentItem: null
237+ property Item hoveredItem: null
238+ property Item prevCurrentItem: null
239+
240+ readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
241+
242+ property bool altPressed: false
243+ property bool longAltPressed: false
244+
245+ signal dismissAll()
246+
247+ onSelect: {
248+ var delegate = rowRepeater.itemAt(index);
249+ if (delegate) {
250+ d.currentItem = delegate;
251+ }
252+ }
253+
254+ onCurrentItemChanged: {
255+ if (prevCurrentItem && prevCurrentItem != currentItem) {
256+ if (currentItem) {
257+ prevCurrentItem.hide();
258+ } else {
259+ prevCurrentItem.dismiss();
260+ }
261+ }
262+
263+ if (currentItem) currentItem.show();
264+ prevCurrentItem = currentItem;
265+ }
266+
267+ function startShortcutTimer() {
268+ d.altPressed = true;
269+ menuBarShortcutTimer.start();
270+ }
271+
272+ function stopSHortcutTimer() {
273+ menuBarShortcutTimer.stop();
274+ d.altPressed = false;
275+ d.longAltPressed = false;
276+ }
277+ }
278+
279+ Timer {
280+ id: menuBarShortcutTimer
281+ interval: 200
282+ repeat: false
283+ onTriggered: {
284+ d.longAltPressed = true;
285+ }
286+ }
287+
288+ Keys.onEscapePressed: {
289+ d.dismissAll();
290+ event.accepted = true;
291+ }
292+
293+ Keys.onLeftPressed: {
294+ if (d.currentItem) {
295+ d.selectPrevious(d.currentIndex);
296+ }
297+ }
298+
299+ Keys.onRightPressed: {
300+ if (d.currentItem) {
301+ d.selectNext(d.currentIndex);
302+ }
303+ }
304+}
qml/ApplicationMenus/MenuItem.qmlnew file+150
@@ -0,0 +1,150 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import Ubuntu.Components 1.3
20+
21+ActionItem {
22+ id: root
23+ implicitHeight: units.gu(5)
24+ implicitWidth: requiredWidth
25+
26+ property var menuData: undefined
27+
28+ readonly property real requiredWidth: {
29+ var val = 0;
30+ val += units.gu(1) + flagGutter.width;
31+ if (iconSource != "") {
32+ val += units.gu(1) + icon.width
33+ }
34+ val += units.gu(1) + title.contentWidth;
35+ if (hasSubmenu) {
36+ val += units.gu(1) + chevronIcon.width;
37+ } else if (shortcut != undefined) {
38+ val += units.gu(3) + shortcutLabel.contentWidth;
39+ }
40+ return val + units.gu(1);
41+ }
42+
43+ readonly property bool hasSubmenu: menuData ? menuData.hasSubmenu : false
44+ readonly property bool _checked : action && action.checkable ? action.checked : false
45+
46+ enabled: menuData ? menuData.sensitive : false
47+
48+ action: Action {
49+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
50+ text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
51+ checkable: menuData.isCheck || menuData.isRadio
52+ checked: menuData.isToggled
53+ }
54+
55+ width: {
56+ if (!parent) return implicitWidth;
57+ if (parent.width > implicitWidth) return parent.width;
58+ return implicitWidth;
59+ }
60+
61+ Keys.onRightPressed: {
62+ if (hasSubmenu) {
63+ root.trigger();
64+ } else {
65+ event.accepted = false;
66+ }
67+ }
68+ Keys.onReturnPressed: {
69+ root.trigger();
70+ }
71+ Keys.onEnterPressed: {
72+ root.trigger();
73+ }
74+
75+ RowLayout {
76+ id: row
77+ spacing: units.gu(1)
78+ anchors.left: parent.left
79+ anchors.right: parent.right
80+ anchors.leftMargin: units.gu(1)
81+ anchors.rightMargin: units.gu(1)
82+ anchors.verticalCenter: parent.verticalCenter
83+
84+ Item {
85+ Layout.minimumWidth: units.gu(1.5)
86+ Layout.minimumHeight: units.gu(1.5)
87+
88+ Icon {
89+ id: flagGutter
90+ width: units.gu(1.5)
91+ height: units.gu(1.5)
92+ visible: _checked
93+ name: "tick"
94+ }
95+ }
96+
97+ Icon {
98+ id: icon
99+ width: units.gu(2)
100+ height: units.gu(2)
101+
102+ visible: root.iconSource != "" || false
103+ source: root.iconSource || ""
104+ }
105+
106+ RowLayout {
107+ spacing: units.gu(3)
108+
109+ Label {
110+ id: title
111+ elide: Text.ElideNone
112+ wrapMode: Text.NoWrap
113+ clip: true
114+ Layout.fillWidth: true
115+
116+ text: root.text ? root.text : ""
117+ }
118+
119+ Label {
120+ id: shortcutLabel
121+ elide: Text.ElideNone
122+ wrapMode: Text.NoWrap
123+ clip: true
124+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
125+ theme.palette.disabled.backgroundSecondaryText
126+
127+ visible: menuData.shortcut != undefined && !root.hasSubmenu && QuickUtils.keyboardAttached
128+ text: menuData.shortcut ? menuData.shortcut : ""
129+ }
130+ }
131+
132+ Icon {
133+ id: chevronIcon
134+ width: units.gu(2)
135+ height: units.gu(2)
136+ color: enabled ? theme.palette.normal.backgroundSecondaryText :
137+ theme.palette.disabled.backgroundSecondaryText
138+
139+ visible: root.hasSubmenu
140+ name: "chevron"
141+ }
142+ }
143+
144+ MouseArea {
145+ anchors.fill: parent
146+ onClicked: {
147+ root.trigger(action && action.checkable ? !action.checked : undefined);
148+ }
149+ }
150+}
qml/ApplicationMenus/MenuNavigator.qmlnew file+78
@@ -0,0 +1,78 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+
19+QtObject {
20+ property Item itemView: null
21+
22+ signal select(int index)
23+
24+ function selectNext(currentIndex) {
25+ var menu;
26+ var newIndex = 0;
27+ if (currentIndex === -1 && itemView.count > 0) {
28+ while (itemView.count > newIndex) {
29+ menu = itemView.itemAt(newIndex);
30+ if (!!menu["enabled"]) {
31+ select(newIndex);
32+ break;
33+ }
34+ newIndex++;
35+ }
36+ } else if (currentIndex !== -1 && itemView.count > 1) {
37+ var startIndex = (currentIndex + 1) % itemView.count;
38+ newIndex = startIndex;
39+ do {
40+ menu = itemView.itemAt(newIndex);
41+ if (!!menu["enabled"]) {
42+ select(newIndex);
43+ break;
44+ }
45+ newIndex = (newIndex + 1) % itemView.count;
46+ } while (newIndex !== startIndex)
47+ }
48+ }
49+
50+ function selectPrevious(currentIndex) {
51+ var menu;
52+ var newIndex = itemView.count-1;
53+ if (currentIndex === -1 && itemView.count > 0) {
54+ while (itemView.count > newIndex) {
55+ menu = itemView.itemAt(newIndex);
56+ if (!!menu["enabled"]) {
57+ select(newIndex);
58+ break;
59+ }
60+ newIndex--;
61+ }
62+ } else if (currentIndex !== -1 && itemView.count > 1) {
63+ var startIndex = currentIndex - 1;
64+ newIndex = startIndex;
65+ do {
66+ if (newIndex < 0) {
67+ newIndex = itemView.count - 1;
68+ }
69+ menu = itemView.itemAt(newIndex);
70+ if (!!menu["enabled"]) {
71+ select(newIndex);
72+ break;
73+ }
74+ newIndex--;
75+ } while (newIndex !== startIndex)
76+ }
77+ }
78+}
qml/ApplicationMenus/MenuPopup.qmlnew file+361
@@ -0,0 +1,361 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import QtQuick.Layouts 1.1
19+import QtQuick.Window 2.2
20+import Ubuntu.Components 1.3
21+import Ubuntu.Components.ListItems 1.3 as ListItems
22+import "../Components"
23+
24+UbuntuShape {
25+ id: root
26+ objectName: "menu"
27+ backgroundColor: theme.palette.normal.overlay
28+
29+ property alias unityMenuModel: listView.model
30+
31+ readonly property real __ajustedMinimumHeight: {
32+ if (listView.contentHeight > __minimumHeight) {
33+ return units.gu(30);
34+ }
35+ return Math.max(listView.contentHeight, units.gu(2));
36+ }
37+
38+ readonly property real __minimumWidth: units.gu(20)
39+ readonly property real __minimumHeight: units.gu(30)
40+ readonly property real __maximumWidth: Screen.width * 0.7
41+ readonly property real __maximumHeight: Screen.height * 0.7
42+
43+ function show() {
44+ visible = true;
45+ focusScope.forceActiveFocus();
46+ }
47+
48+ function hide() {
49+ visible = false;
50+ d.currentItem = null;
51+ }
52+
53+ function select(index) {
54+ d.select(index)
55+ }
56+
57+ function reset() {
58+ d.currentItem = null;
59+ dismiss();
60+ }
61+
62+ function dismiss() {
63+ d.dismissAll();
64+ }
65+
66+ implicitWidth: container.width
67+ implicitHeight: MathUtils.clamp(listView.contentHeight, __ajustedMinimumHeight, __maximumHeight)
68+
69+ MenuNavigator {
70+ id: d
71+ objectName: "d"
72+ itemView: listView
73+
74+ property Item currentItem: null
75+ property Item hoveredItem: null
76+ readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
77+
78+ signal dismissAll()
79+
80+ onCurrentItemChanged: {
81+ if (currentItem) {
82+ currentItem.item.forceActiveFocus();
83+ } else {
84+ hoveredItem = null;
85+ }
86+ }
87+
88+ onSelect: {
89+ currentItem = listView.itemAt(index);
90+ }
91+ }
92+
93+ Item {
94+ id: focusScope
95+ anchors.fill: parent
96+ focus: visible
97+
98+ Keys.onUpPressed: d.selectPrevious(d.currentIndex)
99+ Keys.onDownPressed: d.selectNext(d.currentIndex)
100+ Keys.onRightPressed: {
101+ // Don't let right keypresses fall through if the current item has a visible popup.
102+ if (!d.currentItem || !d.currentItem.popup || !d.currentItem.popup.visible) {
103+ event.accepted = false;
104+ }
105+ }
106+
107+ ColumnLayout {
108+ id: container
109+ objectName: "container"
110+
111+ width: listView.contentWidth
112+ height: parent.height
113+ spacing: 0
114+
115+ // FIXME use ListView.header - tried but was flaky with positionViewAtIndex.
116+ Item {
117+ Layout.fillWidth: true;
118+ Layout.maximumHeight: units.gu(3)
119+ Layout.minimumHeight: units.gu(3)
120+ visible: listView.contentHeight > root.height
121+ enabled: !listView.atYBeginning
122+
123+ Rectangle {
124+ color: enabled ? theme.palette.normal.overlayText :
125+ theme.palette.disabled.overlayText
126+ height: units.dp(1)
127+ anchors {
128+ bottom: parent.bottom
129+ left: parent.left
130+ right: parent.right
131+ }
132+ }
133+
134+ Icon {
135+ anchors.centerIn: parent
136+ width: units.gu(2)
137+ height: units.gu(2)
138+ name: "up"
139+ color: enabled ? theme.palette.normal.overlayText :
140+ theme.palette.disabled.overlayText
141+ }
142+
143+ MouseArea {
144+ anchors.fill: parent
145+ onPressed: {
146+ var index = listView.indexAt(0, listView.contentY);
147+ listView.positionViewAtIndex(index-1, ListView.Beginning);
148+ }
149+ }
150+ }
151+
152+ ListView {
153+ id: listView
154+ objectName: "listView"
155+ Layout.fillHeight: true
156+ Layout.fillWidth: true
157+ contentWidth: MathUtils.clamp(contentItem.childrenRect.width,
158+ __minimumWidth,
159+ __maximumWidth)
160+
161+ orientation: Qt.Vertical
162+ interactive: contentHeight > height
163+ clip: interactive
164+ highlightFollowsCurrentItem: false
165+
166+ highlight: Rectangle {
167+ color: "transparent"
168+ border.width: units.dp(1)
169+ border.color: UbuntuColors.orange
170+ z: 1
171+
172+ width: listView.width
173+ height: d.currentItem ? d.currentItem.height : 0
174+ y: d.currentItem ? d.currentItem.y : 0
175+ visible: d.currentItem
176+ }
177+
178+ function itemAt(index) {
179+ if (index > count || index < 0) return null;
180+ currentIndex = index;
181+ return currentItem;
182+ }
183+
184+ MouseArea {
185+ id: menuMouseArea
186+ anchors.fill: listView
187+ hoverEnabled: true
188+ propagateComposedEvents: true // propogate events so we send clicks to children.
189+ z: 1 // on top so we override any other hovers
190+ onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
191+ onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
192+
193+ function updateCurrentItemFromPosition(point) {
194+ var pos = mapToItem(listView.contentItem, point.x, point.y);
195+
196+ if (!d.hoveredItem || !d.currentItem ||
197+ !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
198+ d.hoveredItem = listView.itemAt(listView.indexAt(pos.x, pos.y));
199+ if (!d.hoveredItem || !d.hoveredItem.enabled)
200+ return false;
201+ d.currentItem = d.hoveredItem;
202+ }
203+ return true;
204+ }
205+ }
206+
207+ ActionContext {
208+ id: menuBarContext
209+ objectName: "menuContext"
210+ active: {
211+ if (!root.visible) return false;
212+ if (d.currentItem && d.currentItem.popup && d.currentItem.popup.visible) {
213+ return false;
214+ }
215+ return true;
216+ }
217+ }
218+
219+ delegate: Loader {
220+ id: loader
221+ objectName: root.objectName + "-item" + __ownIndex
222+
223+ property int __ownIndex: index
224+
225+ width: root.width
226+ enabled: model.isSeparator ? false : model.sensitive
227+
228+ sourceComponent: {
229+ if (model.isSeparator) {
230+ return separatorComponent;
231+ }
232+ return menuItemComponent;
233+ }
234+
235+ property Item popup: null
236+
237+ Component {
238+ id: menuItemComponent
239+ MenuItem {
240+ id: menuItem
241+ menuData: model
242+ objectName: loader.objectName + "-actionItem"
243+
244+ action.onTriggered: {
245+ d.currentItem = loader;
246+
247+ if (hasSubmenu) {
248+ if (!popup) {
249+ var model = root.unityMenuModel.submenu(__ownIndex);
250+ popup = submenuComponent.createObject(focusScope, {
251+ objectName: loader.objectName + "-",
252+ unityMenuModel: model,
253+ x: Qt.binding(function() { return root.width }),
254+ y: Qt.binding(function() { return loader.y })
255+ });
256+ } else if (popup) {
257+ popup.visible = true;
258+ }
259+ popup.retreat.connect(function() {
260+ popup.destroy();
261+ popup = null;
262+ menuItem.forceActiveFocus();
263+ })
264+ } else {
265+ root.unityMenuModel.activate(__ownIndex);
266+ }
267+ }
268+
269+ Connections {
270+ target: d
271+ onCurrentIndexChanged: {
272+ if (popup && d.currentIndex != __ownIndex) {
273+ popup.visible = false;
274+ }
275+ }
276+ onDismissAll: {
277+ if (popup) {
278+ popup.destroy();
279+ popup = null;
280+ }
281+ }
282+ }
283+ }
284+ }
285+
286+ Component {
287+ id: separatorComponent
288+ ListItems.ThinDivider {
289+ objectName: loader.objectName + "-separator"
290+ }
291+ }
292+ }
293+ } // ListView
294+
295+ // FIXME use ListView.footer - tried but was flaky with positionViewAtIndex.
296+ Item {
297+ Layout.fillWidth: true;
298+ Layout.maximumHeight: units.gu(3)
299+ Layout.minimumHeight: units.gu(3)
300+ visible: listView.contentHeight > root.height
301+ enabled: !listView.atYEnd
302+
303+ Rectangle {
304+ color: enabled ? theme.palette.normal.overlayText :
305+ theme.palette.disabled.overlayText
306+ height: units.dp(1)
307+ anchors {
308+ top: parent.top
309+ left: parent.left
310+ right: parent.right
311+ }
312+ }
313+
314+ Icon {
315+ anchors.centerIn: parent
316+ width: units.gu(2)
317+ height: units.gu(2)
318+ name: "down"
319+ color: enabled ? theme.palette.normal.overlayText :
320+ theme.palette.disabled.overlayText
321+ }
322+
323+ MouseArea {
324+ anchors.fill: parent
325+ onPressed: {
326+ var index = listView.indexAt(0, listView.contentY);
327+ listView.positionViewAtIndex(index+1, ListView.Beginning);
328+ }
329+ }
330+ }
331+ } // Column
332+
333+ Component {
334+ id: submenuComponent
335+ Loader {
336+ id: submenuLoader
337+ source: "MenuPopup.qml"
338+
339+ property var unityMenuModel: null
340+ signal retreat()
341+
342+ Binding {
343+ target: item
344+ property: "unityMenuModel"
345+ value: submenuLoader.unityMenuModel
346+ }
347+
348+ Binding {
349+ target: item
350+ property: "objectName"
351+ value: submenuLoader.objectName + "menu"
352+ }
353+
354+ Keys.onLeftPressed: retreat()
355+
356+ Component.onCompleted: item.select(0);
357+ onVisibleChanged: if (visible) { item.select(0); }
358+ }
359+ }
360+ }
361+}
qml/ApplicationMenus/RegisteredApplicationMenuModel.qmlnew file+56
@@ -0,0 +1,56 @@
1+/*
2+ * Copyright 2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Unity.Indicators 0.1
20+import Unity.ApplicationMenu 0.1
21+import Unity.Indicators 0.1 as Indicators
22+
23+Object {
24+ property string persistentSurfaceId
25+ readonly property alias model: sharedAppModel.model
26+
27+ Indicators.SharedUnityMenuModel {
28+ id: sharedAppModel
29+ property var menus: ApplicationMenuRegistry.getMenusForSurface(persistentSurfaceId)
30+ property var menuService: menus.length > 0 ? menus[0] : null
31+
32+ busName: menuService ? menuService.service : ""
33+ menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
34+ actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
35+ }
36+
37+ onPersistentSurfaceIdChanged: update()
38+
39+ function update() {
40+ sharedAppModel.menus = Qt.binding(function() { return ApplicationMenuRegistry.getMenusForSurface(persistentSurfaceId); });
41+ }
42+
43+ Connections {
44+ target: ApplicationMenuRegistry
45+ onSurfaceMenuRegistered: {
46+ if (surfaceId === persistentSurfaceId) {
47+ update();
48+ }
49+ }
50+ onSurfaceMenuUnregistered: {
51+ if (surfaceId === persistentSurfaceId) {
52+ update();
53+ }
54+ }
55+ }
56+}
qml/CMakeLists.txt+1
@@ -5,6 +5,7 @@ install(FILES ${QML_JS_FILES}
5 )
5 )
6
6
7set(QML_DIRS
7set(QML_DIRS
8+ ApplicationMenus
8 Components
9 Components
9 Dash
10 Dash
10 graphics
11 graphics
qml/Components/DragHandle.qml+1
@@ -67,6 +67,7 @@ SwipeArea {
67 SmoothedAnimation {
67 SmoothedAnimation {
68 id: hintingAnimation
68 id: hintingAnimation
69 target: hintingAnimation
69 target: hintingAnimation
70+ objectName: "hintingAnimation"
70 property: "targetValue"
71 property: "targetValue"
71 duration: 150
72 duration: 150
72 velocity: -1
73 velocity: -1
qml/Components/PanelState/PanelState.qml+6−3
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
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
5 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,8 @@ QtObject {
21 id: root
21 id: root
22
22
23 property string title: ""
23 property string title: ""
24− property bool buttonsVisible: false
24+ property bool decorationsVisible: false
25− property bool buttonsAlwaysVisible: false
25+ property bool decorationsAlwaysVisible: false
26 property bool closeButtonShown: true
26 property bool closeButtonShown: true
27 property bool dropShadow: false
27 property bool dropShadow: false
28 property int panelHeight: 0
28 property int panelHeight: 0
@@ -30,4 +30,7 @@ QtObject {
30 signal closeClicked()
30 signal closeClicked()
31 signal minimizeClicked()
31 signal minimizeClicked()
32 signal restoreClicked()
32 signal restoreClicked()
33+
34+ property string focusedPersistentSurfaceId: ""
35+ property bool focusedSurfaceMaximized: false
33}
36}
qml/Components/ScrollCalculator.qml+21−2
@@ -22,6 +22,7 @@ Item {
22
22
23 readonly property bool areaActive: lateralPosition >= 0
23 readonly property bool areaActive: lateralPosition >= 0
24 property real stopScrollThreshold: units.gu(2)
24 property real stopScrollThreshold: units.gu(2)
25+ property real progressThreshold: units.dp(4)
25 property int direction: Qt.LeftToRight
26 property int direction: Qt.LeftToRight
26 property real baseScrollAmount: units.dp(3)
27 property real baseScrollAmount: units.dp(3)
27 property real maximumScrollAmount: units.dp(8)
28 property real maximumScrollAmount: units.dp(8)
@@ -38,6 +39,11 @@ Item {
38 function handleEnter() {
39 function handleEnter() {
39 d.thresholdAreaX = -scrollArea.stopScrollThreshold;
40 d.thresholdAreaX = -scrollArea.stopScrollThreshold;
40 scrollTimer.restart();
41 scrollTimer.restart();
42+ d.init = true;
43+ d.passedProgressThreshold = false;
44+
45+ d.progression = 0;
46+ d.startingProgression = 0;
41 }
47 }
42
48
43 function handleExit() {
49 function handleExit() {
@@ -59,6 +65,10 @@ Item {
59 }
65 }
60
66
61 d.progression = lateralPosition / width;
67 d.progression = lateralPosition / width;
68+ if (d.init) {
69+ d.startingProgression = d.progression;
70+ d.init = false;
71+ }
62 }
72 }
63 }
73 }
64
74
@@ -68,14 +78,23 @@ Item {
68 repeat: true
78 repeat: true
69
79
70 onTriggered: {
80 onTriggered: {
81+ if (d.passedProgressThreshold ||
82+ Math.abs(d.progression - d.startingProgression) * scrollArea.width > scrollArea.progressThreshold) {
83+ d.passedProgressThreshold = true;
84+
71− var scrollAmount = scrollArea.baseScrollAmount + scrollArea.maximumScrollAmount * d.progression;
85+ var scrollAmount = scrollArea.baseScrollAmount + scrollArea.maximumScrollAmount * d.progression;
72− scrollArea.scroll(scrollAmount);
86+ scrollArea.scroll(scrollAmount);
87+ }
73 }
88 }
74 }
89 }
75
90
76 QtObject {
91 QtObject {
77 id: d
92 id: d
93+ property real startingProgression: 0
94+ property bool init: true
95+
78 property real progression: 0
96 property real progression: 0
79 property real thresholdAreaX: -scrollArea.stopScrollThreshold
97 property real thresholdAreaX: -scrollArea.stopScrollThreshold
98+ property bool passedProgressThreshold: false
80 }
99 }
81}
100}
qml/Components/WindowControlButtons.qml+21
@@ -46,6 +46,13 @@ Row {
46 onClicked: root.closeClicked()
46 onClicked: root.closeClicked()
47 visible: root.closeButtonShown
47 visible: root.closeButtonShown
48
48
49+ // We dont want touch events to fall through to parent,
50+ // otherwise the containsMouse will not work.
51+ MouseArea {
52+ anchors.fill: parent
53+ propagateComposedEvents: true
54+ }
55+
49 Rectangle {
56 Rectangle {
50 anchors.fill: parent
57 anchors.fill: parent
51 anchors.margins: windowIsMaximized ? units.dp(3) : 0
58 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -69,6 +76,13 @@ Row {
69 width: height
76 width: height
70 onClicked: root.minimizeClicked()
77 onClicked: root.minimizeClicked()
71
78
79+ // We dont want touch events to fall through to parent,
80+ // otherwise the containsMouse will not work.
81+ MouseArea {
82+ anchors.fill: parent
83+ propagateComposedEvents: true
84+ }
85+
72 Rectangle {
86 Rectangle {
73 anchors.fill: parent
87 anchors.fill: parent
74 anchors.margins: windowIsMaximized ? units.dp(3) : 0
88 anchors.margins: windowIsMaximized ? units.dp(3) : 0
@@ -102,6 +116,13 @@ Row {
102 }
116 }
103 }
117 }
104
118
119+ // We dont want touch events to fall through to parent,
120+ // otherwise the containsMouse will not work.
121+ MouseArea {
122+ anchors.fill: parent
123+ propagateComposedEvents: true
124+ }
125+
105 Rectangle {
126 Rectangle {
106 anchors.fill: parent
127 anchors.fill: parent
107 anchors.margins: windowIsMaximized ? units.dp(3) : 0
128 anchors.margins: windowIsMaximized ? units.dp(3) : 0
qml/OrientedShell.qml+6
@@ -98,6 +98,12 @@ Item {
98 deviceFilter: InputInfo.TouchScreen
98 deviceFilter: InputInfo.TouchScreen
99 }
99 }
100
100
101+ Binding {
102+ target: QuickUtils
103+ property: "keyboardAttached"
104+ value: keyboardsModel.count > 0
105+ }
106+
101 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
107 readonly property int pointerInputDevices: miceModel.count + touchPadModel.count
102 onPointerInputDevicesChanged: calculateUsageMode()
108 onPointerInputDevicesChanged: calculateUsageMode()
103
109
qml/Panel/FakePanelMenu.qmlnew file+26
@@ -0,0 +1,26 @@
1+/*
2+ * Copyright (C) 2013-2015 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.4
18+
19+QtObject {
20+ property list<QtObject> hides
21+ property QtObject model: null
22+ property bool expanded: false
23+
24+ signal hide()
25+ signal show()
26+}
qml/Panel/IndicatorPage.qmldeleted−188
@@ -1,188 +0,0 @@
1−/*
2− * Copyright 2013-2014 Canonical Ltd.
3− *
4− * This program is free software; you can redistribute it and/or modify
5− * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12− *
13− * You should have received a copy of the GNU Lesser General Public License
14− * along with this program. If not, see <http://www.gnu.org/licenses/>.
15− */
16−
17−import QtQuick 2.4
18−import Ubuntu.Components 1.3 as Components
19−import Unity.Indicators 0.1 as Indicators
20−import "../Components"
21−import "Indicators"
22−
23−IndicatorBase {
24− id: main
25−
26− //const
27− property string title: rootActionState.title || rootActionState.accessibleName // some indicators don't expose a title but only the accessible-desc
28− property alias highlightFollowsCurrentItem : mainMenu.highlightFollowsCurrentItem
29− readonly property alias factory: _factory
30−
31− Indicators.UnityMenuModelStack {
32− id: menuStack
33− head: main.menuModel
34−
35− property var rootMenu: null
36−
37− onTailChanged: {
38− if (!tail) {
39− rootMenu = null;
40− } else if (rootMenu != tail) {
41− if (tail.get(0, "type") === rootMenuType) {
42− rootMenu = menuStack.tail.submenu(0);
43− push(rootMenu, 0);
44− } else {
45− rootMenu = null;
46− }
47− }
48− }
49− }
50−
51− Connections {
52− target: menuStack.tail
53−
54− // fix async creation with signal from model before it's finished.
55− Component.onCompleted: update();
56− onRowsInserted: update();
57− onModelReset: update();
58−
59− function update() {
60− if (menuStack.rootMenu !== menuStack.tail && menuStack.tail.get(0, "type") === rootMenuType) {
61− menuStack.rootMenu = menuStack.tail.submenu(0);
62− menuStack.push(menuStack.rootMenu, 0);
63− }
64− }
65− }
66−
67− ListView {
68− id: mainMenu
69− objectName: "mainMenu"
70− model: menuStack.rootMenu
71−
72− anchors {
73− fill: parent
74− bottomMargin: Qt.inputMethod.visible ? (Qt.inputMethod.keyboardRectangle.height - main.anchors.bottomMargin) : 0
75−
76− Behavior on bottomMargin {
77− NumberAnimation {
78− duration: 175
79− easing.type: Easing.OutQuad
80− }
81− }
82− // TODO - does ever frame.
83− onBottomMarginChanged: {
84− mainMenu.positionViewAtIndex(mainMenu.currentIndex, ListView.End)
85− }
86− }
87−
88− // Don't load all the delegates (only max of 3 pages worth -1/0/+1)
89− cacheBuffer: Math.max(height * 3, units.gu(70))
90−
91− // Only allow flicking if the content doesn't fit on the page
92− interactive: contentHeight > height
93−
94− property int selectedIndex: -1
95− property bool blockCurrentIndexChange: false
96− // for count = 0
97− onCountChanged: {
98− if (count == 0 && selectedIndex != -1) {
99− selectedIndex = -1;
100− }
101− }
102− // for highlight following
103− onSelectedIndexChanged: {
104− if (currentIndex != selectedIndex) {
105− var blocked = blockCurrentIndexChange;
106− blockCurrentIndexChange = true;
107−
108− currentIndex = selectedIndex;
109−
110− blockCurrentIndexChange = blocked;
111− }
112− }
113− // for item addition/removal
114− onCurrentIndexChanged: {
115− if (!blockCurrentIndexChange) {
116− if (selectedIndex != -1 && selectedIndex != currentIndex) {
117− selectedIndex = currentIndex;
118− }
119− }
120− }
121−
122− Connections {
123− target: mainMenu.model ? mainMenu.model : null
124− onRowsAboutToBeRemoved: {
125− // track current item deletion.
126− if (mainMenu.selectedIndex >= first && mainMenu.selectedIndex <= last) {
127− mainMenu.selectedIndex = -1;
128− }
129− }
130− }
131−
132− delegate: Loader {
133− id: loader
134− objectName: "menuItem" + index
135− width: ListView.view.width
136− visible: status == Loader.Ready
137−
138− property int modelIndex: index
139− sourceComponent: factory.load(model, main.identifier)
140−
141− onLoaded: {
142− if (item.hasOwnProperty("selected")) {
143− item.selected = mainMenu.selectedIndex == index;
144− }
145− if (item.hasOwnProperty("menuSelected")) {
146− item.menuSelected.connect(function() { mainMenu.selectedIndex = index; });
147− }
148− if (item.hasOwnProperty("menuDeselected")) {
149− item.menuDeselected.connect(function() { mainMenu.selectedIndex = -1; });
150− }
151− if (item.hasOwnProperty("menuData")) {
152− item.menuData = Qt.binding(function() { return model; });
153− }
154− if (item.hasOwnProperty("menuIndex")) {
155− item.menuIndex = Qt.binding(function() { return modelIndex; });
156− }
157− }
158−
159− Binding {
160− target: item ? item : null
161− property: "objectName"
162− value: model.action
163− }
164−
165− // TODO: Fixes lp#1243146
166− // This is a workaround for a Qt bug. https://bugreports.qt-project.org/browse/QTBUG-34351
167− Connections {
168− target: mainMenu
169− onSelectedIndexChanged: {
170− if (loader.item && loader.item.hasOwnProperty("selected")) {
171− loader.item.selected = mainMenu.selectedIndex == index;
172− }
173− }
174− }
175− }
176− }
177−
178− MenuItemFactory {
179− id: _factory
180− rootModel: main.menuModel ? main.menuModel : null
181− menuModel: mainMenu.model ? mainMenu.model : null
182− }
183−
184− function reset()
185− {
186− mainMenu.positionViewAtBeginning();
187− }
188−}
qml/Panel/IndicatorItem.qml → qml/Panel/Indicators/IndicatorItem.qmlrenamed+2−1
@@ -18,7 +18,6 @@ import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Ubuntu.Settings.Components 0.1
19import Ubuntu.Settings.Components 0.1
20import QMenuModel 0.1
20import QMenuModel 0.1
21−import "Indicators"
22
21
23IndicatorDelegate {
22IndicatorDelegate {
24 id: root
23 id: root
@@ -157,6 +156,8 @@ IndicatorDelegate {
157 }
156 }
158
157
159 StateGroup {
158 StateGroup {
159+ objectName: "indicatorItemState"
160+
160 states: [
161 states: [
161 State {
162 State {
162 name: "minimised"
163 name: "minimised"
qml/Panel/Indicators/MenuItemFactory.qml → qml/Panel/Indicators/IndicatorMenuItemFactory.qmlrenamed+7−10
@@ -25,9 +25,10 @@ import Ubuntu.Components 1.3
25import Unity.Session 0.1
25import Unity.Session 0.1
26import Unity.Platform 1.0
26import Unity.Platform 1.0
27
27
28−Item {
28+Object {
29 id: menuFactory
29 id: menuFactory
30
30
31+ property string indicator
31 property var rootModel: null
32 property var rootModel: null
32 property var menuModel: null
33 property var menuModel: null
33
34
@@ -88,7 +89,7 @@ Item {
88 }
89 }
89 }
90 }
90
91
91− function getComponentForIndicatorEntryType(indicator, type) {
92+ function getComponentForIndicatorEntryType(type) {
92 var component = undefined;
93 var component = undefined;
93 var map = _userMap || _typeToComponent
94 var map = _userMap || _typeToComponent
94 var indicatorComponents = map[indicator];
95 var indicatorComponents = map[indicator];
@@ -112,7 +113,7 @@ Item {
112 return component
113 return component
113 }
114 }
114
115
115− function getComponentForIndicatorEntryAction(indicator, action) {
116+ function getComponentForIndicatorEntryAction(action) {
116 var component = undefined;
117 var component = undefined;
117 var indicatorFilter = _action_filter_map[indicator]
118 var indicatorFilter = _action_filter_map[indicator]
118
119
@@ -1082,17 +1083,13 @@ Item {
1082 }
1083 }
1083 }
1084 }
1084
1085
1085− function load(modelData, context) {
1086+ function load(modelData) {
1086− if (context && context.indexOf("fake-") == 0) {
1087− context = context.substring("fake-".length)
1088− }
1089−
1090− var component = getComponentForIndicatorEntryAction(context, modelData.action)
1087+ var component = getComponentForIndicatorEntryAction(modelData.action)
1091 if (component !== undefined) {
1088 if (component !== undefined) {
1092 return component
1089 return component
1093 }
1090 }
1094
1091
1095− component = getComponentForIndicatorEntryType(context, modelData.type)
1092+ component = getComponentForIndicatorEntryType(modelData.type)
1096 if (component !== undefined) {
1093 if (component !== undefined) {
1097 return component;
1094 return component;
1098 }
1095 }
qml/Panel/MenuContent.qml+7−10
@@ -25,8 +25,10 @@ import "Indicators"
25Rectangle {
25Rectangle {
26 id: content
26 id: content
27
27
28− property QtObject indicatorsModel: null
28+ property QtObject model: null
29 property int currentMenuIndex: -1
29 property int currentMenuIndex: -1
30+ property Component pageDelegate
31+
30 color: theme.palette.normal.background
32 color: theme.palette.normal.background
31
33
32 width: units.gu(40)
34 width: units.gu(40)
@@ -40,7 +42,7 @@ Rectangle {
40 id: listViewContent
42 id: listViewContent
41 objectName: "indicatorsContentListView"
43 objectName: "indicatorsContentListView"
42 anchors.fill: parent
44 anchors.fill: parent
43− model: content.indicatorsModel
45+ model: content.model
44
46
45 highlightFollowsCurrentItem: true
47 highlightFollowsCurrentItem: true
46 interactive: false
48 interactive: false
@@ -59,18 +61,13 @@ Rectangle {
59
61
60 width: ListView.view.width
62 width: ListView.view.width
61 height: ListView.view.height
63 height: ListView.view.height
62− objectName: identifier
63 asynchronous: true
64 asynchronous: true
64 visible: ListView.isCurrentItem
65 visible: ListView.isCurrentItem
65
66
66− sourceComponent: IndicatorPage {
67+ property var modelData: model
67− objectName: identifier + "-page"
68+ property var modelIndex: index
68
69
69− identifier: model.identifier
70+ sourceComponent: pageDelegate
70− busName: indicatorProperties.busName
71− actionsObjectPath: indicatorProperties.actionsObjectPath
72− menuObjectPath: indicatorProperties.menuObjectPath
73− }
74
71
75 onVisibleChanged: {
72 onVisibleChanged: {
76 // Reset the indicator states
73 // Reset the indicator states
qml/Panel/Panel.qml+300−96
@@ -16,91 +16,160 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19+import Ubuntu.Layouts 1.0
19import Unity.Application 0.1
20import Unity.Application 0.1
21+import Unity.Indicators 0.1
22+import Utils 0.1
23+import Unity.ApplicationMenu 0.1
24+
25+import QtQuick.Window 2.2
26+// for indicator-keyboard
27+import AccountsService 0.1
28+import Unity.InputInfo 0.1
29+
30+import "../ApplicationMenus"
20import "../Components"
31import "../Components"
21import "../Components/PanelState"
32import "../Components/PanelState"
22import ".."
33import ".."
34+import "Indicators"
23
35
24Item {
36Item {
25 id: root
37 id: root
26− readonly property real panelHeight: indicatorArea.y + d.indicatorHeight
38+ readonly property real panelHeight: panelArea.y + minimizedPanelHeight
39+
40+ property real minimizedPanelHeight: units.gu(3)
41+ property real expandedPanelHeight: units.gu(7)
42+ property real indicatorMenuWidth: width
43+ property real applicationMenuWidth: width
44+
45+ property alias applicationMenus: __applicationMenus
27 property alias indicators: __indicators
46 property alias indicators: __indicators
28− property alias callHint: __callHint
29 property bool fullscreenMode: false
47 property bool fullscreenMode: false
30− property real indicatorAreaShowProgress: 1.0
48+ property real panelAreaShowProgress: 1.0
31− property bool locked: false
49+ property bool greeterShown: false
50+
51+ property string mode: "staged"
32
52
33 MouseArea {
53 MouseArea {
54+ id: backMouseEater
34 anchors.fill: parent
55 anchors.fill: parent
35 anchors.topMargin: panelHeight
56 anchors.topMargin: panelHeight
36− visible: !indicators.fullyClosed
57+ visible: !indicators.fullyClosed || !applicationMenus.fullyClosed
37 enabled: visible
58 enabled: visible
38− onClicked: if (indicators.fullyOpened) indicators.hide();
39 hoverEnabled: true // should also eat hover events, otherwise they will pass through
59 hoverEnabled: true // should also eat hover events, otherwise they will pass through
60+
61+ onClicked: {
62+ __applicationMenus.hide();
63+ __indicators.hide();
64+ }
40 }
65 }
41
66
42 Binding {
67 Binding {
43 target: PanelState
68 target: PanelState
44 property: "panelHeight"
69 property: "panelHeight"
70+ value: minimizedPanelHeight
71+ }
72+
73+ RegisteredApplicationMenuModel {
74+ id: registeredMenuModel
75+ persistentSurfaceId: PanelState.focusedPersistentSurfaceId
76+ }
77+
78+ QtObject {
79+ id: d
80+
81+ property bool revealControls: !greeterShown &&
82+ !applicationMenus.shown &&
45− value: indicators.minimizedPanelHeight
83+ !indicators.shown &&
84+ (decorationMouseArea.containsMouse || menuBarLoader.menusRequested)
85+
86+ property bool showWindowDecorationControls: (revealControls && PanelState.decorationsVisible) ||
87+ PanelState.decorationsAlwaysVisible
88+
89+ property bool showPointerMenu: revealControls &&
90+ (PanelState.decorationsVisible || mode == "staged")
91+
92+ property bool enablePointerMenu: revealControls &&
93+ applicationMenus.available &&
94+ applicationMenus.model
95+
96+ property bool showTouchMenu: !greeterShown &&
97+ !showPointerMenu
98+
99+ property bool enableTouchMenus: showTouchMenu &&
100+ applicationMenus.available &&
101+ applicationMenus.model
46 }
102 }
47
103
48 Item {
104 Item {
49− id: indicatorArea
105+ id: panelArea
50− objectName: "indicatorArea"
106+ objectName: "panelArea"
51
107
52 anchors.fill: parent
108 anchors.fill: parent
53
109
54 transform: Translate {
110 transform: Translate {
55 y: indicators.state === "initial"
111 y: indicators.state === "initial"
56− ? (1.0 - indicatorAreaShowProgress) * -d.indicatorHeight
112+ ? (1.0 - panelAreaShowProgress) * - minimizedPanelHeight
57 : 0
113 : 0
58 }
114 }
59
115
60 BorderImage {
116 BorderImage {
61 id: indicatorsDropShadow
117 id: indicatorsDropShadow
62 anchors {
118 anchors {
63− fill: indicators
119+ fill: __indicators
64− leftMargin: -units.gu(1)
120+ margins: -units.gu(1)
121+ }
122+ visible: !__indicators.fullyClosed
123+ source: "graphics/rectangular_dropshadow.sci"
124+ }
125+
126+ BorderImage {
127+ id: appmenuDropShadow
128+ anchors {
129+ fill: __applicationMenus
65− bottomMargin: -units.gu(1)
130+ margins: -units.gu(1)
66 }
131 }
67− visible: !indicators.fullyClosed
132+ visible: !__applicationMenus.fullyClosed
68 source: "graphics/rectangular_dropshadow.sci"
133 source: "graphics/rectangular_dropshadow.sci"
69 }
134 }
70
135
71 BorderImage {
136 BorderImage {
72 id: panelDropShadow
137 id: panelDropShadow
73 anchors {
138 anchors {
74− fill: indicatorAreaBackground
139+ fill: panelAreaBackground
75 bottomMargin: -units.gu(1)
140 bottomMargin: -units.gu(1)
76 }
141 }
77− visible: PanelState.dropShadow && !callHint.visible
142+ visible: PanelState.dropShadow
78 source: "graphics/rectangular_dropshadow.sci"
143 source: "graphics/rectangular_dropshadow.sci"
79 }
144 }
80
145
81 Rectangle {
146 Rectangle {
82− id: indicatorAreaBackground
147+ id: panelAreaBackground
83 color: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
148 color: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
84 anchors {
149 anchors {
85 top: parent.top
150 top: parent.top
86 left: parent.left
151 left: parent.left
87 right: parent.right
152 right: parent.right
88 }
153 }
89− height: indicators.minimizedPanelHeight
154+ height: minimizedPanelHeight
90
155
91 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
156 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
92 }
157 }
93
158
94 MouseArea {
159 MouseArea {
160+ id: decorationMouseArea
95 objectName: "windowControlArea"
161 objectName: "windowControlArea"
96 anchors {
162 anchors {
97− top: parent.top
98 left: parent.left
163 left: parent.left
99− right: indicators.left
164+ right: parent.right
165+ }
166+ height: minimizedPanelHeight
167+ hoverEnabled: !__indicators.shown
168+ onClicked: {
169+ if (callHint.visible) {
170+ callHint.showLiveCall();
171+ }
100 }
172 }
101− height: indicators.minimizedPanelHeight
102− hoverEnabled: true
103− onClicked: if (callHint.visible) { callHint.showLiveCall(); }
104
173
105 onPressed: {
174 onPressed: {
106 if (!callHint.visible) {
175 if (!callHint.visible) {
@@ -109,29 +178,135 @@ Item {
109 }
178 }
110 }
179 }
111
180
181+ Row {
182+ anchors.fill: parent
183+ spacing: units.gu(2)
184+
112− // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
185+ // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
113− // cf. https://bugreports.qt.io/browse/QTBUG-32909
186+ // cf. https://bugreports.qt.io/browse/QTBUG-32909
114− WindowControlButtons {
187+ WindowControlButtons {
115− id: windowControlButtons
188+ id: windowControlButtons
116− objectName: "panelWindowControlButtons"
189+ objectName: "panelWindowControlButtons"
117− anchors {
190+ height: indicators.minimizedPanelHeight
191+ opacity: d.showWindowDecorationControls ? 1 : 0
192+ visible: opacity != 0
193+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
194+
195+ active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
196+ windowIsMaximized: true
197+ onCloseClicked: PanelState.closeClicked()
198+ onMinimizeClicked: PanelState.minimizeClicked()
199+ onMaximizeClicked: PanelState.restoreClicked()
200+ closeButtonShown: PanelState.closeButtonShown
201+ }
202+
203+ Loader {
204+ id: menuBarLoader
205+ height: parent.height
206+ enabled: d.enablePointerMenu
207+ opacity: d.showPointerMenu ? 1 : 0
208+ visible: opacity != 0
209+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
210+ active: __applicationMenus.model
211+
212+ property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
213+
214+ sourceComponent: MenuBar {
215+ id: bar
216+ objectName: "menuBar"
118− left: parent.left
217+ anchors.left: parent.left
119− top: parent.top
218+ anchors.margins: units.gu(1)
219+ height: menuBarLoader.height
220+ enableKeyFilter: valid && PanelState.decorationsVisible
221+ unityMenuModel: __applicationMenus.model
222+
223+ Connections {
224+ target: __applicationMenus
225+ onShownChanged: bar.dismiss();
226+ }
227+
228+ Connections {
229+ target: __indicators
230+ onShownChanged: bar.dismiss();
231+ }
232+ }
233+ }
234+ }
235+
236+ ActiveCallHint {
237+ id: callHint
238+ objectName: "callHint"
239+
240+ anchors.centerIn: parent
241+ height: minimizedPanelHeight
242+
243+ visible: active && indicators.state == "initial" && __applicationMenus.state == "initial"
244+ greeterShown: root.greeterShown
245+ }
246+ }
247+
248+ PanelMenu {
249+ id: __applicationMenus
250+
251+ model: registeredMenuModel.model
252+ width: root.applicationMenuWidth
253+ minimizedPanelHeight: root.minimizedPanelHeight
254+ expandedPanelHeight: root.expandedPanelHeight
255+ openedHeight: root.height
256+ alignment: Qt.AlignLeft
257+ enableHint: !callHint.active && !fullscreenMode
258+ showOnClick: false
259+ panelColor: panelAreaBackground.color
260+
261+ onShowTapped: {
262+ if (callHint.active) {
263+ callHint.showLiveCall();
264+ }
265+ }
266+
267+ showRowTitle: !expanded
268+ rowTitle: PanelState.title
269+ rowItemDelegate: ActionItem {
270+ id: actionItem
271+ property int ownIndex: index
272+ objectName: "appMenuItem"+index
273+
274+ width: _title.width + units.gu(2)
275+ height: parent.height
276+
277+ action: Action {
278+ text: model.label.replace("_", "&")
279+ }
280+
281+ Label {
282+ id: _title
283+ anchors.centerIn: parent
284+ text: actionItem.text
285+ horizontalAlignment: Text.AlignLeft
286+ color: enabled ? "white" : "#5d5d5d"
287+ }
288+ }
289+
290+ pageDelegate: PanelMenuPage {
291+ menuModel: __applicationMenus.model
292+ submenuIndex: modelIndex
293+
294+ factory: ApplicationMenuItemFactory {
295+ rootModel: __applicationMenus.model
120 }
296 }
121− height: indicators.minimizedPanelHeight
297+ }
298+
299+ enabled: d.enableTouchMenus
300+ opacity: d.showTouchMenu ? 1 : 0
301+ visible: opacity != 0
302+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
122
303
123− visible: ((PanelState.buttonsVisible && parent.containsMouse) || PanelState.buttonsAlwaysVisible)
304+ onEnabledChanged: {
124− && !root.locked && !callHint.visible
305+ if (!enabled) hide();
125− active: PanelState.buttonsVisible || PanelState.buttonsAlwaysVisible
126− windowIsMaximized: true
127− onCloseClicked: PanelState.closeClicked()
128− onMinimizeClicked: PanelState.minimizeClicked()
129− onMaximizeClicked: PanelState.restoreClicked()
130− closeButtonShown: PanelState.closeButtonShown
131 }
306 }
132 }
307 }
133
308
134− IndicatorsMenu {
309+ PanelMenu {
135 id: __indicators
310 id: __indicators
136 objectName: "indicators"
311 objectName: "indicators"
137
312
@@ -139,72 +314,93 @@ Item {
139 top: parent.top
314 top: parent.top
140 right: parent.right
315 right: parent.right
141 }
316 }
142−
317+ width: root.indicatorMenuWidth
143− shown: false
144− width: root.width - (windowControlButtons.visible ? windowControlButtons.width + titleLabel.width : 0)
145− minimizedPanelHeight: units.gu(3)
318+ minimizedPanelHeight: root.minimizedPanelHeight
146− expandedPanelHeight: units.gu(7)
319+ expandedPanelHeight: root.expandedPanelHeight
147 openedHeight: root.height
320 openedHeight: root.height
148
321
149− overFlowWidth: {
322+ overFlowWidth: root.width
150− if (callHint.visible) {
151− return Math.max(root.width - (callHint.width + units.gu(2)), 0)
152− }
153− return root.width
154− }
155 enableHint: !callHint.active && !fullscreenMode
323 enableHint: !callHint.active && !fullscreenMode
156 showOnClick: !callHint.visible
324 showOnClick: !callHint.visible
157− panelColor: indicatorAreaBackground.color
325+ panelColor: panelAreaBackground.color
158
326
159 onShowTapped: {
327 onShowTapped: {
160 if (callHint.active) {
328 if (callHint.active) {
161 callHint.showLiveCall();
329 callHint.showLiveCall();
162 }
330 }
163 }
331 }
164− }
165
332
166− Label {
333+ rowItemDelegate: IndicatorItem {
167− id: titleLabel
334+ id: indicatorItem
168− objectName: "windowDecorationTitle"
335+ objectName: identifier+"-panelItem"
169− anchors {
170− left: parent.left
171− right: __indicators.left
172− top: parent.top
173− leftMargin: units.gu(1)
174− rightMargin: units.gu(1)
175− topMargin: units.gu(0.5)
176− bottomMargin: units.gu(0.5)
177− }
178− color: "white"
179− height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
180− opacity: !windowControlButtons.visible && !root.locked && !callHint.visible ? 1 : 0
181− visible: opacity != 0
182− verticalAlignment: Text.AlignVCenter
183− fontSize: "medium"
184− font.weight: PanelState.buttonsVisible ? Font.Light : Font.Medium
185− text: PanelState.title
186− elide: Text.ElideRight
187− maximumLineCount: 1
188− Behavior on opacity { UbuntuNumberAnimation {} }
189− }
190
336
191− // TODO here would the Locally integrated menus come
337+ property int ownIndex: index
338+ property bool overflow: parent.width - x > __indicators.overFlowWidth
339+ property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
340+ // HACK for indicator-session
341+ readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
342+ // HACK for indicator-keyboard
343+ readonly property bool hideKeyboardIndicator: identifier == "indicator-keyboard" && (AccountsService.keymaps.length < 2 || keyboardsModel.count == 0)
192
344
193− ActiveCallHint {
345+ height: parent.height
194− id: __callHint
346+ expanded: indicators.expanded
195− anchors {
347+ selected: ListView.isCurrentItem
196− top: parent.top
348+
197− left: parent.left
349+ identifier: model.identifier
350+ busName: indicatorProperties.busName
351+ actionsObjectPath: indicatorProperties.actionsObjectPath
352+ menuObjectPath: indicatorProperties.menuObjectPath
353+
354+ opacity: hidden ? 0.0 : 1.0
355+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
356+
357+ width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
358+
359+ Behavior on width { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
360+ }
361+
362+ pageDelegate: PanelMenuPage {
363+ objectName: modelData.identifier + "-page"
364+ submenuIndex: 0
365+
366+ menuModel: delegate.menuModel
367+
368+ factory: IndicatorMenuItemFactory {
369+ indicator: {
370+ var context = modelData.identifier;
371+ if (context && context.indexOf("fake-") === 0) {
372+ context = context.substring("fake-".length)
373+ }
374+ return context;
375+ }
376+ rootModel: delegate.menuModel
377+ }
378+
379+ IndicatorDelegate {
380+ id: delegate
381+ busName: modelData.indicatorProperties.busName
382+ actionsObjectPath: modelData.indicatorProperties.actionsObjectPath
383+ menuObjectPath: modelData.indicatorProperties.menuObjectPath
384+ }
385+ }
386+
387+ enabled: !applicationMenus.expanded
388+ opacity: !applicationMenus.expanded ? 1 : 0
389+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
390+
391+ onEnabledChanged: {
392+ if (!enabled) hide();
198 }
393 }
199− height: indicators.minimizedPanelHeight
200− visible: active && indicators.state == "initial"
201 }
394 }
202 }
395 }
203
396
204− QtObject {
397+ InputDeviceModel {
205− id: d
398+ id: keyboardsModel
206− objectName: "panelPriv"
399+ deviceFilter: InputInfo.Keyboard
207− readonly property real indicatorHeight: indicators.minimizedPanelHeight
400+ }
401+
402+ IndicatorsLight {
403+ id: indicatorLights
208 }
404 }
209
405
210 states: [
406 states: [
@@ -212,7 +408,7 @@ Item {
212 name: "onscreen" //fully opaque and visible at top edge of screen
408 name: "onscreen" //fully opaque and visible at top edge of screen
213 when: !fullscreenMode
409 when: !fullscreenMode
214 PropertyChanges {
410 PropertyChanges {
215− target: indicatorArea;
411+ target: panelArea;
216 anchors.topMargin: 0
412 anchors.topMargin: 0
217 opacity: 1;
413 opacity: 1;
218 }
414 }
@@ -221,25 +417,33 @@ Item {
221 name: "offscreen" //pushed off screen
417 name: "offscreen" //pushed off screen
222 when: fullscreenMode
418 when: fullscreenMode
223 PropertyChanges {
419 PropertyChanges {
224− target: indicatorArea;
420+ target: panelArea;
421+ anchors.topMargin: {
225− anchors.topMargin: indicators.state === "initial" ? -d.indicatorHeight : 0
422+ if (indicators.state !== "initial") return 0;
423+ if (applicationMenus.state !== "initial") return 0;
424+ return -minimizedPanelHeight;
425+ }
226− opacity: indicators.fullyClosed ? 0.0 : 1.0
426+ opacity: indicators.fullyClosed && applicationMenus.fullyClosed ? 0.0 : 1.0
227 }
427 }
228 PropertyChanges {
428 PropertyChanges {
229 target: indicators.showDragHandle;
429 target: indicators.showDragHandle;
230 anchors.bottomMargin: -units.gu(1)
430 anchors.bottomMargin: -units.gu(1)
231 }
431 }
432+ PropertyChanges {
433+ target: applicationMenus.showDragHandle;
434+ anchors.bottomMargin: -units.gu(1)
435+ }
232 }
436 }
233 ]
437 ]
234
438
235 transitions: [
439 transitions: [
236 Transition {
440 Transition {
237 to: "onscreen"
441 to: "onscreen"
238− UbuntuNumberAnimation { target: indicatorArea; properties: "anchors.topMargin,opacity" }
442+ UbuntuNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
239 },
443 },
240 Transition {
444 Transition {
241 to: "offscreen"
445 to: "offscreen"
242− UbuntuNumberAnimation { target: indicatorArea; properties: "anchors.topMargin,opacity" }
446+ UbuntuNumberAnimation { target: panelArea; properties: "anchors.topMargin,opacity" }
243 }
447 }
244 ]
448 ]
245}
449}
qml/Panel/IndicatorsBar.qml → qml/Panel/PanelBar.qmlrenamed+69−36
@@ -22,13 +22,19 @@ Item {
22 id: root
22 id: root
23 property alias expanded: row.expanded
23 property alias expanded: row.expanded
24 property alias interactive: flickable.interactive
24 property alias interactive: flickable.interactive
25− property alias indicatorsModel: row.indicatorsModel
25+ property alias model: row.model
26 property alias unitProgress: row.unitProgress
26 property alias unitProgress: row.unitProgress
27 property alias enableLateralChanges: row.enableLateralChanges
27 property alias enableLateralChanges: row.enableLateralChanges
28 property alias overFlowWidth: row.overFlowWidth
28 property alias overFlowWidth: row.overFlowWidth
29 readonly property alias currentItemIndex: row.currentItemIndex
29 readonly property alias currentItemIndex: row.currentItemIndex
30 property real lateralPosition: -1
30 property real lateralPosition: -1
31− readonly property string currentIndicator: row.currentItem ? row.currentItem.identifier : ""
31+ property int alignment: Qt.AlignRight
32+
33+ property alias showRowTitle: row.showRowTitle
34+ property alias rowTitle: row.rowTitle
35+ property alias rowItemDelegate: row.delegate
36+
37+ implicitWidth: flickable.contentWidth
32
38
33 function selectItemAt(lateralPosition) {
39 function selectItemAt(lateralPosition) {
34 if (!expanded) {
40 if (!expanded) {
@@ -46,15 +52,21 @@ Item {
46 }
52 }
47
53
48 function addScrollOffset(scrollAmmout) {
54 function addScrollOffset(scrollAmmout) {
55+ if (root.alignment == Qt.AlignLeft) {
56+ scrollAmmout = -scrollAmmout;
57+ }
58+
49 if (scrollAmmout < 0) { // left scroll
59 if (scrollAmmout < 0) { // left scroll
50 if (flickable.contentX + flickable.width > row.width) return; // already off the left.
60 if (flickable.contentX + flickable.width > row.width) return; // already off the left.
51
61
52− if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the right
62+ if (flickable.contentX + flickable.width - scrollAmmout > row.width) { // going to be off the left
53 scrollAmmout = (flickable.contentX + flickable.width) - row.width;
63 scrollAmmout = (flickable.contentX + flickable.width) - row.width;
54 }
64 }
55 } else { // right scroll
65 } else { // right scroll
56 if (flickable.contentX < 0) return; // already off the right.
66 if (flickable.contentX < 0) return; // already off the right.
57− if (flickable.contentX - scrollAmmout < 0) scrollAmmout = flickable.contentX; // going to be off the right
67+ if (flickable.contentX - scrollAmmout < 0) { // going to be off the right
68+ scrollAmmout = flickable.contentX;
69+ }
58 }
70 }
59 d.scrollOffset = d.scrollOffset + scrollAmmout;
71 d.scrollOffset = d.scrollOffset + scrollAmmout;
60 }
72 }
@@ -62,14 +74,19 @@ Item {
62 QtObject {
74 QtObject {
63 id: d
75 id: d
64 property var initialItem
76 property var initialItem
65− // the non-expanded distance from row offset to center of initial item
77+ // the non-expanded distance from alignment edge to center of initial item
66− property real originalDistanceFromRight: -1
78+ property real originalDistanceFromEdge: -1
67
79
68− // calculate the distance from row offset to center of initial item
80+ // calculate the distance from row alignment edge edge to center of initial item
69− property real distanceFromRight: {
81+ property real distanceFromEdge: {
70− if (originalDistanceFromRight == -1) return 0;
82+ if (originalDistanceFromEdge == -1) return 0;
71 if (!initialItem) return 0;
83 if (!initialItem) return 0;
84+
85+ if (root.alignment == Qt.AlignLeft) {
86+ return initialItem.x - initialItem.width / 2;
87+ } else {
72− return row.width - initialItem.x - initialItem.width /2;
88+ return row.width - initialItem.x - initialItem.width / 2;
89+ }
73 }
90 }
74
91
75 // offset to the intially selected expanded item
92 // offset to the intially selected expanded item
@@ -82,7 +99,11 @@ Item {
82 onScrollOffsetChanged: root.lateralPositionChanged()
99 onScrollOffsetChanged: root.lateralPositionChanged()
83
100
84 onInitialItemChanged: {
101 onInitialItemChanged: {
102+ if (root.alignment == Qt.AlignLeft) {
103+ originalDistanceFromEdge = initialItem ? (initialItem.x - initialItem.width/2) : -1;
104+ } else {
85− originalDistanceFromRight = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
105+ originalDistanceFromEdge = initialItem ? (row.width - initialItem.x - initialItem.width/2) : -1;
106+ }
86 }
107 }
87
108
88 Behavior on alignmentAdjustment {
109 Behavior on alignmentAdjustment {
@@ -93,26 +114,38 @@ Item {
93 flickable.resetContentXComponents();
114 flickable.resetContentXComponents();
94
115
95 if (expanded && !flickable.moving) {
116 if (expanded && !flickable.moving) {
96− // gap between left and row?
97− if (flickable.contentX + flickable.width > row.width) {
98− // row width is less than flickable
99− if (row.width < flickable.width) {
100− d.alignmentAdjustment -= flickable.contentX;
101− } else {
102− d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
103− }
104
117
118+ if (root.alignment == Qt.AlignLeft) {
119+ // current item overlap on left
120+ if (row.currentItem && flickable.contentX > row.currentItem.x) {
121+ d.alignmentAdjustment -= (flickable.contentX - row.currentItem.x);
122+
123+ // current item overlap on right
124+ } else if (row.currentItem && flickable.contentX + flickable.width < row.currentItem.x + row.currentItem.width) {
125+ d.alignmentAdjustment += (row.currentItem.x + row.currentItem.width) - (flickable.contentX + flickable.width);
126+ }
127+ } else {
128+ // gap between left and row?
129+ if (flickable.contentX + flickable.width > row.width) {
130+ // row width is less than flickable
131+ if (row.width < flickable.width) {
132+ d.alignmentAdjustment -= flickable.contentX;
133+ } else {
134+ d.alignmentAdjustment -= ((flickable.contentX + flickable.width) - row.width);
135+ }
136+
105− // gap between right and row?
137+ // gap between right and row?
106− } else if (flickable.contentX < 0) {
138+ } else if (flickable.contentX < 0) {
107− d.alignmentAdjustment -= flickable.contentX;
139+ d.alignmentAdjustment -= flickable.contentX;
108
140
109− // current item overlap on left
141+ // current item overlap on left
110− } else if (row.currentItem && (flickable.contentX + flickable.width) < (row.width - row.currentItem.x)) {
142+ } else if (row.currentItem && (flickable.contentX + flickable.width) < (row.width - row.currentItem.x)) {
111− d.alignmentAdjustment += ((row.width - row.currentItem.x) - (flickable.contentX + flickable.width));
143+ d.alignmentAdjustment += ((row.width - row.currentItem.x) - (flickable.contentX + flickable.width));
112
144
113− // current item overlap on right
145+ // current item overlap on right
114− } else if (row.currentItem && flickable.contentX > (row.width - row.currentItem.x - row.currentItem.width)) {
146+ } else if (row.currentItem && flickable.contentX > (row.width - row.currentItem.x - row.currentItem.width)) {
115− d.alignmentAdjustment -= flickable.contentX - (row.width - row.currentItem.x - row.currentItem.width);
147+ d.alignmentAdjustment -= flickable.contentX - (row.width - row.currentItem.x - row.currentItem.width);
148+ }
116 }
149 }
117 }
150 }
118 }
151 }
@@ -140,7 +173,7 @@ Item {
140
173
141 // we rotate it because we want the Flickable to align its content item
174 // we rotate it because we want the Flickable to align its content item
142 // on the right instead of on the left
175 // on the right instead of on the left
143− rotation: 180
176+ rotation: root.alignment != Qt.AlignRight ? 0 : 180
144
177
145 anchors.fill: parent
178 anchors.fill: parent
146 contentWidth: row.width
179 contentWidth: row.width
@@ -161,16 +194,16 @@ Item {
161 }
194 }
162 }
195 }
163
196
164− IndicatorItemRow {
197+ PanelItemRow {
165 id: row
198 id: row
166− objectName: "indicatorItemRow"
199+ objectName: "panelItemRow"
167 anchors {
200 anchors {
168 top: parent.top
201 top: parent.top
169 bottom: parent.bottom
202 bottom: parent.bottom
170 }
203 }
171
204
172 // Compensate for the Flickable rotation (ie, counter-rotate)
205 // Compensate for the Flickable rotation (ie, counter-rotate)
173− rotation: 180
206+ rotation: root.alignment != Qt.AlignRight ? 0 : 180
174
207
175 lateralPosition: {
208 lateralPosition: {
176 if (root.lateralPosition == -1) return -1;
209 if (root.lateralPosition == -1) return -1;
@@ -230,15 +263,15 @@ Item {
230 target: d
263 target: d
231 rowOffset: {
264 rowOffset: {
232 if (!initialItem) return 0;
265 if (!initialItem) return 0;
233− if (distanceFromRight - initialItem.width <= 0) return 0;
266+ if (distanceFromEdge - initialItem.width <= 0) return 0;
234
267
235− var rowOffset = distanceFromRight - originalDistanceFromRight;
268+ var rowOffset = distanceFromEdge - originalDistanceFromEdge;
236 return rowOffset;
269 return rowOffset;
237 }
270 }
238 restoreEntryValues: false
271 restoreEntryValues: false
239 }
272 }
240− }
273+ },
241− , State {
274+ State {
242 name: "interactive"
275 name: "interactive"
243 when: expanded && interactive
276 when: expanded && interactive
244
277
qml/Panel/IndicatorItemRow.qml → qml/Panel/PanelItemRow.qmlrenamed+51−100
@@ -15,29 +15,29 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import QtQuick.Window 2.2
19import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
20−
19+import "../Components"
21−// for indicator-keyboard
22−import AccountsService 0.1
23−import Unity.InputInfo 0.1
24
20
25Item {
21Item {
26 id: root
22 id: root
27− width: row.width
23+ implicitWidth: showRowTitle && !expanded ? rowTitle != "" ? rowLabel.width : 0 : row.width
28− height: units.gu(3)
24+ implicitHeight: units.gu(3)
29
25
26+ property bool showRowTitle: false
27+ property alias rowTitle: rowLabel.text
30− property QtObject indicatorsModel: null
28+ property QtObject model: null
31 property real overFlowWidth: width
29 property real overFlowWidth: width
32 property bool expanded: false
30 property bool expanded: false
33− property var currentItem
31+ readonly property alias currentItem: row.currentItem
34− readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
32+ readonly property alias currentItemIndex: row.currentIndex
35
33
36 property real unitProgress: 0.0
34 property real unitProgress: 0.0
37 property real selectionChangeBuffer: units.gu(2)
35 property real selectionChangeBuffer: units.gu(2)
38 property bool enableLateralChanges: false
36 property bool enableLateralChanges: false
39 property color hightlightColor: "#ffffff"
37 property color hightlightColor: "#ffffff"
40
38
39+ property alias delegate: row.delegate
40+
41 property real lateralPosition: -1
41 property real lateralPosition: -1
42 onLateralPositionChanged: {
42 onLateralPositionChanged: {
43 updateItemFromLateralPosition();
43 updateItemFromLateralPosition();
@@ -86,21 +86,21 @@ Item {
86 }
86 }
87
87
88 function indicatorAt(x, y) {
88 function indicatorAt(x, y) {
89− var item = row.childAt(x, y);
89+ var item = row.itemAt(x, y);
90 return item && item.hasOwnProperty("ownIndex") ? item : null;
90 return item && item.hasOwnProperty("ownIndex") ? item : null;
91 }
91 }
92
92
93 function resetCurrentItem() {
93 function resetCurrentItem() {
94 d.firstItemSwitch = true;
94 d.firstItemSwitch = true;
95− d.previousItem = undefined;
95+ d.previousItem = null;
96− currentItem = undefined;
96+ row.currentIndex = -1;
97 }
97 }
98
98
99 function setCurrentItemIndex(index) {
99 function setCurrentItemIndex(index) {
100− for (var i = 0; i < row.children.length; i++) {
100+ for (var i = 0; i < row.contentItem.children.length; i++) {
101− var item = row.children[i];
101+ var item = row.contentItem.children[i];
102 if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
102 if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
103− if (currentItem !== item) currentItem = item;
103+ if (currentItem !== item) row.currentIndex = index;
104 break;
104 break;
105 }
105 }
106 }
106 }
@@ -109,18 +109,18 @@ Item {
109 function selectItemAt(lateralPosition) {
109 function selectItemAt(lateralPosition) {
110 var item = indicatorAt(lateralPosition, 0);
110 var item = indicatorAt(lateralPosition, 0);
111 if (item && item.opacity > 0) {
111 if (item && item.opacity > 0) {
112− currentItem = item;
112+ row.currentIndex = item.ownIndex;
113 } else {
113 } else {
114 // Select default item.
114 // Select default item.
115− var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
115+ var searchIndex = lateralPosition >= width ? row.count - 1 : 0;
116
116
117− for (var i = 0; i < row.children.length; i++) {
117+ for (var i = 0; i < row.contentItem.children.length; i++) {
118− if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
118+ if (row.contentItem.children[i].hasOwnProperty("ownIndex") && row.contentItem.children[i].ownIndex === searchIndex) {
119− item = row.children[i];
119+ item = row.contentItem.children[i];
120 break;
120 break;
121 }
121 }
122 }
122 }
123− if (currentItem !== item) currentItem = item;
123+ if (currentItem !== item) row.currentIndex = item ? item.ownIndex : -1;
124 }
124 }
125 }
125 }
126
126
@@ -131,11 +131,6 @@ Item {
131 property bool forceAlignmentAnimationDisabled: false
131 property bool forceAlignmentAnimationDisabled: false
132 }
132 }
133
133
134− InputDeviceModel {
135− id: keyboardsModel
136− deviceFilter: InputInfo.Keyboard
137− }
138−
139 onCurrentItemChanged: {
134 onCurrentItemChanged: {
140 if (d.previousItem) {
135 if (d.previousItem) {
141 d.firstItemSwitch = false;
136 d.firstItemSwitch = false;
@@ -143,8 +138,32 @@ Item {
143 d.previousItem = currentItem;
138 d.previousItem = currentItem;
144 }
139 }
145
140
146− Row {
141+ Label {
142+ id: rowLabel
143+ objectName: "panelTitle"
144+ anchors {
145+ left: parent.left
146+ leftMargin: units.gu(1)
147+ verticalCenter: parent.verticalCenter
148+ }
149+ width: implicitWidth + units.gu(2)
150+ elide: Text.ElideRight
151+ maximumLineCount: 1
152+ fontSize: "medium"
153+ font.weight: Font.Medium
154+ color: Theme.palette.selected.backgroundText
155+ opacity: showRowTitle ? 1 : 0
156+ visible: opacity != 0
157+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
158+ }
159+
160+ ListView {
147 id: row
161 id: row
162+ objectName: "panelRow"
163+ orientation: ListView.Horizontal
164+ model: root.model
165+ opacity: showRowTitle ? 0 : 1
166+ // dont set visible on basis of opacity; otherwise width will not be calculated correctly
148 anchors {
167 anchors {
149 top: parent.top
168 top: parent.top
150 bottom: parent.bottom
169 bottom: parent.bottom
@@ -153,7 +172,7 @@ Item {
153 // TODO: make this better
172 // TODO: make this better
154 // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
173 // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
155 // and adjust the highlight X immediately.
174 // and adjust the highlight X immediately.
156− width: implicitWidth
175+ width: childrenRect.width
157 Behavior on width {
176 Behavior on width {
158 SequentialAnimation {
177 SequentialAnimation {
159 ScriptAction {
178 ScriptAction {
@@ -166,75 +185,7 @@ Item {
166 }
185 }
167 }
186 }
168
187
169− Repeater {
170− id: repeater
171− model: indicatorsModel
172− visible: false
173−
174− onItemRemoved: {
175− // current item removed.
176− if (currentItem === item) {
177− var i = 0;
178− while (i < row.children.length) {
179− var childItem = row.children[i];
180− if (childItem !== item) {
181− setCurrentItemIndex(i);
182− break;
183− }
184− i++;
185− }
186− }
187− }
188−
189−
190− delegate: IndicatorItem {
191− id: indicatorItem
192− objectName: identifier+"-panelItem"
193−
194− property int ownIndex: index
195− property bool overflow: row.width - x > overFlowWidth
196− property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
197− // HACK for indicator-session
198− readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
199− // HACK for indicator-keyboard
200− readonly property bool hideKeyboardIndicator: identifier == "indicator-keyboard" && (AccountsService.keymaps.length < 2 || keyboardsModel.count == 0)
201−
202− height: row.height
203− expanded: root.expanded
204− selected: currentItem === this
205−
206− identifier: model.identifier
207− busName: indicatorProperties.busName
208− actionsObjectPath: indicatorProperties.actionsObjectPath
209− menuObjectPath: indicatorProperties.menuObjectPath
210−
211− opacity: hidden ? 0.0 : 1.0
212− Behavior on opacity {
213− NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
214− }
215−
216− width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
217−
218− Behavior on width {
219− NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
188+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
220− }
221−
222− Component.onDestruction: {
223− // current item removed.
224− if (currentItem === this) {
225− var i = 0;
226− while (i < row.children.length) {
227− var childItem = row.children[i];
228− if (childItem !== this) {
229− setCurrentItemIndex(i);
230− break;
231− }
232− i++;
233− }
234− }
235− }
236− }
237− }
238 }
189 }
239
190
240 Rectangle {
191 Rectangle {
@@ -244,7 +195,7 @@ Item {
244 anchors.bottom: row.bottom
195 anchors.bottom: row.bottom
245 height: units.dp(2)
196 height: units.dp(2)
246 color: root.hightlightColor
197 color: root.hightlightColor
247− visible: currentItem !== undefined
198+ visible: currentItem !== null
248 opacity: 0.0
199 opacity: 0.0
249
200
250 width: currentItem ? currentItem.width : 0
201 width: currentItem ? currentItem.width : 0
@@ -269,7 +220,7 @@ Item {
269
220
270 if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
221 if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
271 return 0;
222 return 0;
272− } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
223+ } else if (currentItem && currentItem.ownIndex === row.count-1 & distanceFromCenter > 0) {
273 return 0;
224 return 0;
274 }
225 }
275 return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
226 return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
qml/Panel/IndicatorsMenu.qml → qml/Panel/PanelMenu.qmlrenamed+31−19
@@ -22,22 +22,30 @@ import "Indicators"
22
22
23Showable {
23Showable {
24 id: root
24 id: root
25− property alias indicatorsModel: bar.indicatorsModel
25+ property alias model: bar.model
26 property alias showDragHandle: __showDragHandle
26 property alias showDragHandle: __showDragHandle
27 property alias hideDragHandle: __hideDragHandle
27 property alias hideDragHandle: __hideDragHandle
28 property alias overFlowWidth: bar.overFlowWidth
28 property alias overFlowWidth: bar.overFlowWidth
29 property alias verticalVelocityThreshold: yVelocityCalculator.velocityThreshold
29 property alias verticalVelocityThreshold: yVelocityCalculator.velocityThreshold
30− property alias currentIndicator: bar.currentIndicator
31 property int minimizedPanelHeight: units.gu(3)
30 property int minimizedPanelHeight: units.gu(3)
32 property int expandedPanelHeight: units.gu(7)
31 property int expandedPanelHeight: units.gu(7)
33 property real openedHeight: units.gu(71)
32 property real openedHeight: units.gu(71)
33+ property bool enableHint: true
34+ property bool showOnClick: true
35+ property color panelColor: theme.palette.normal.background
36+
37+ property alias alignment: bar.alignment
38+ property alias rowTitle: bar.rowTitle
39+ property alias showRowTitle: bar.showRowTitle
40+ property alias rowItemDelegate: bar.rowItemDelegate
41+ property alias pageDelegate: content.pageDelegate
42+
34 readonly property real unitProgress: Math.max(0, (height - minimizedPanelHeight) / (openedHeight - minimizedPanelHeight))
43 readonly property real unitProgress: Math.max(0, (height - minimizedPanelHeight) / (openedHeight - minimizedPanelHeight))
35 readonly property bool fullyOpened: unitProgress >= 1
44 readonly property bool fullyOpened: unitProgress >= 1
36 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
45 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
37 readonly property bool fullyClosed: unitProgress == 0
46 readonly property bool fullyClosed: unitProgress == 0
38− property bool enableHint: true
47+ readonly property alias expanded: bar.expanded
39− property bool showOnClick: true
48+ readonly property int barWidth: Math.min(bar.width, bar.implicitWidth)
40− property color panelColor: theme.palette.normal.background
41
49
42 signal showTapped()
50 signal showTapped()
43
51
@@ -67,21 +75,20 @@ Showable {
67 ScriptAction { script: root.height = Qt.binding( function(){ return root.minimizedPanelHeight; } ) }
75 ScriptAction { script: root.height = Qt.binding( function(){ return root.minimizedPanelHeight; } ) }
68 }
76 }
69
77
78+ shown: false
70 height: minimizedPanelHeight
79 height: minimizedPanelHeight
71−
72− onUnitProgressChanged: d.updateState()
73 clip: root.partiallyOpened
80 clip: root.partiallyOpened
74
81
75− IndicatorsLight {
82+ onUnitProgressChanged: d.updateState()
76− id: indicatorLights
77− }
78
83
79 // eater
84 // eater
80 MouseArea {
85 MouseArea {
81− anchors.fill: parent
86+ anchors.fill: content
82 hoverEnabled: true
87 hoverEnabled: true
83 acceptedButtons: Qt.AllButtons
88 acceptedButtons: Qt.AllButtons
84 onWheel: wheel.accepted = true;
89 onWheel: wheel.accepted = true;
90+ enabled: root.state != "initial"
91+ visible: content.visible
85 }
92 }
86
93
87 MenuContent {
94 MenuContent {
@@ -94,7 +101,7 @@ Showable {
94 top: bar.bottom
101 top: bar.bottom
95 }
102 }
96 height: openedHeight - bar.height - handle.height
103 height: openedHeight - bar.height - handle.height
97− indicatorsModel: root.indicatorsModel
104+ model: root.model
98 visible: root.unitProgress > 0
105 visible: root.unitProgress > 0
99 currentMenuIndex: bar.currentItemIndex
106 currentMenuIndex: bar.currentItemIndex
100 }
107 }
@@ -109,6 +116,7 @@ Showable {
109 }
116 }
110 height: units.gu(2)
117 height: units.gu(2)
111 active: d.activeDragHandle ? true : false
118 active: d.activeDragHandle ? true : false
119+ visible: !root.fullyClosed
112
120
113 //small shadow gradient at bottom of menu
121 //small shadow gradient at bottom of menu
114 Rectangle {
122 Rectangle {
@@ -129,9 +137,10 @@ Showable {
129 Rectangle {
137 Rectangle {
130 anchors.fill: bar
138 anchors.fill: bar
131 color: panelColor
139 color: panelColor
140+ visible: !root.fullyClosed
132 }
141 }
133
142
134− IndicatorsBar {
143+ PanelBar {
135 id: bar
144 id: bar
136 objectName: "indicatorsBar"
145 objectName: "indicatorsBar"
137
146
@@ -178,12 +187,14 @@ Showable {
178
187
179 MouseArea {
188 MouseArea {
180 anchors.bottom: parent.bottom
189 anchors.bottom: parent.bottom
181− anchors.left: parent.left
190+ anchors.left: alignment == Qt.AlignLeft ? parent.left : undefined
182− anchors.right: parent.right
191+ anchors.right: alignment == Qt.AlignRight ? parent.right : undefined
192+ width: root.barWidth // show handle should only cover panel items.
183 height: minimizedPanelHeight
193 height: minimizedPanelHeight
184 enabled: __showDragHandle.enabled && showOnClick
194 enabled: __showDragHandle.enabled && showOnClick
185 onClicked: {
195 onClicked: {
196+ var barPosition = mapToItem(bar, mouseX, mouseY);
186− bar.selectItemAt(mouseX)
197+ bar.selectItemAt(barPosition.x)
187 root.show()
198 root.show()
188 }
199 }
189 }
200 }
@@ -192,8 +203,9 @@ Showable {
192 id: __showDragHandle
203 id: __showDragHandle
193 objectName: "showDragHandle"
204 objectName: "showDragHandle"
194 anchors.bottom: parent.bottom
205 anchors.bottom: parent.bottom
195− anchors.left: parent.left
206+ anchors.left: alignment == Qt.AlignLeft ? parent.left : undefined
196− anchors.right: parent.right
207+ anchors.right: alignment == Qt.AlignRight ? parent.right : undefined
208+ width: root.barWidth // show handle should only cover panel items.
197 height: minimizedPanelHeight
209 height: minimizedPanelHeight
198 direction: Direction.Downwards
210 direction: Direction.Downwards
199 enabled: !root.shown && root.available
211 enabled: !root.shown && root.available
@@ -311,7 +323,7 @@ Showable {
311 script: {
323 script: {
312 yVelocityCalculator.reset();
324 yVelocityCalculator.reset();
313 // initial item selection
325 // initial item selection
314− if (!d.hasCommitted) bar.selectItemAt(d.activeDragHandle ? d.activeDragHandle.touchPosition.x : -1);
326+ if (!d.hasCommitted) bar.selectItemAt(d.rowMappedLateralPosition);
315 d.hasCommitted = false;
327 d.hasCommitted = false;
316 }
328 }
317 }
329 }
qml/Panel/PanelMenuPage.qmlnew file+225
@@ -0,0 +1,225 @@
1+/*
2+ * Copyright 2013-2016 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.4
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItems
20+import Unity.Indicators 0.1 as Indicators
21+import "../Components"
22+import "Indicators"
23+
24+PageStack {
25+ id: root
26+
27+ property var submenuIndex: undefined
28+ property QtObject menuModel: null
29+ property Component factory
30+
31+ Connections {
32+ id: dynamicChanges
33+ target: root.menuModel
34+ property bool ready: false
35+
36+ // fix async creation with signal from model before it's finished.
37+ onRowsInserted: {
38+ if (submenuIndex !== undefined && first <= submenuIndex) {
39+ reset(true);
40+ }
41+ }
42+ onRowsRemoved: {
43+ if (submenuIndex !== undefined && first <= submenuIndex) {
44+ reset(true);
45+ }
46+ }
47+ onModelReset: {
48+ if (root.submenuIndex !== undefined) {
49+ reset(true);
50+ }
51+ }
52+ }
53+
54+ Component.onCompleted: {
55+ reset(true);
56+ dynamicChanges.ready = true;
57+ }
58+
59+ function reset(clearModel) {
60+ if (clearModel) {
61+ clear();
62+ var model = submenuIndex == undefined ? menuModel : menuModel.submenu(submenuIndex)
63+ if (model) {
64+ push(pageComponent, { "menuModel": model });
65+ }
66+ } else {
67+ root.currentPage.reset();
68+ }
69+ }
70+
71+ Component {
72+ id: pageComponent
73+ Page {
74+ id: page
75+
76+ property alias menuModel: listView.model
77+ property alias title: backLabel.title
78+ property bool isSubmenu: false
79+
80+ function reset() {
81+ listView.positionViewAtBeginning();
82+ }
83+
84+ property QtObject factory: root.factory.createObject(page, { menuModel: page.menuModel } )
85+
86+ header: PageHeader {
87+ id: backLabel
88+ visible: page.isSubmenu
89+ leadingActionBar.actions: [
90+ Action {
91+ iconName: "back"
92+ text: "Back"
93+ onTriggered: {
94+ root.pop();
95+ }
96+ }
97+ ]
98+ }
99+
100+ ListView {
101+ id: listView
102+ objectName: "listView"
103+
104+ anchors {
105+ top: page.isSubmenu ? backLabel.bottom : parent.top
106+ left: parent.left
107+ right: parent.right
108+ bottom: parent.bottom
109+ bottomMargin: Qt.inputMethod.visible ? (Qt.inputMethod.keyboardRectangle.height - root.anchors.bottomMargin) : 0
110+
111+ Behavior on bottomMargin {
112+ NumberAnimation {
113+ duration: 175
114+ easing.type: Easing.OutQuad
115+ }
116+ }
117+ // TODO - does ever frame.
118+ onBottomMarginChanged: {
119+ listView.positionViewAtIndex(listView.currentIndex, ListView.End)
120+ }
121+ }
122+
123+ // Don't load all the delegates (only max of 3 pages worth -1/0/+1)
124+ cacheBuffer: Math.max(height * 3, units.gu(70))
125+
126+ // Only allow flicking if the content doesn't fit on the page
127+ interactive: contentHeight > height
128+
129+ property int selectedIndex: -1
130+ property bool blockCurrentIndexChange: false
131+ // for count = 0
132+ onCountChanged: {
133+ if (count == 0 && selectedIndex != -1) {
134+ selectedIndex = -1;
135+ }
136+ }
137+ // for highlight following
138+ onSelectedIndexChanged: {
139+ if (currentIndex != selectedIndex) {
140+ var blocked = blockCurrentIndexChange;
141+ blockCurrentIndexChange = true;
142+
143+ currentIndex = selectedIndex;
144+
145+ blockCurrentIndexChange = blocked;
146+ }
147+ }
148+ // for item addition/removal
149+ onCurrentIndexChanged: {
150+ if (!blockCurrentIndexChange) {
151+ if (selectedIndex != -1 && selectedIndex != currentIndex) {
152+ selectedIndex = currentIndex;
153+ }
154+ }
155+ }
156+
157+ Connections {
158+ target: listView.model ? listView.model : null
159+ onRowsAboutToBeRemoved: {
160+ // track current item deletion.
161+ if (listView.selectedIndex >= first && listView.selectedIndex <= last) {
162+ listView.selectedIndex = -1;
163+ }
164+ }
165+ }
166+
167+ delegate: Loader {
168+ id: loader
169+ objectName: "menuItem" + index
170+ width: ListView.view.width
171+ visible: status == Loader.Ready
172+
173+ property int modelIndex: index
174+ sourceComponent: page.factory.load(model)
175+
176+ onLoaded: {
177+ if (item.hasOwnProperty("selected")) {
178+ item.selected = listView.selectedIndex == index;
179+ }
180+ if (item.hasOwnProperty("menuSelected")) {
181+ item.menuSelected.connect(function() { listView.selectedIndex = index; });
182+ }
183+ if (item.hasOwnProperty("menuDeselected")) {
184+ item.menuDeselected.connect(function() { listView.selectedIndex = -1; });
185+ }
186+ if (item.hasOwnProperty("menuData")) {
187+ item.menuData = Qt.binding(function() { return model; });
188+ }
189+ if (item.hasOwnProperty("menuIndex")) {
190+ item.menuIndex = Qt.binding(function() { return modelIndex; });
191+ }
192+ if (item.hasOwnProperty("clicked")) {
193+ item.clicked.connect(function() {
194+ if (model.hasSubmenu) {
195+ root.push(pageComponent, {
196+ "isSubmenu": true,
197+ "title": model.label.replace(/_|&/, ""),
198+ "menuModel": page.menuModel.submenu(modelIndex)
199+ });
200+ }
201+ });
202+ }
203+ }
204+
205+ Binding {
206+ target: item ? item : null
207+ property: "objectName"
208+ value: model.action
209+ }
210+
211+ // TODO: Fixes lp#1243146
212+ // This is a workaround for a Qt bug. https://bugreports.qt-project.org/browse/QTBUG-34351
213+ Connections {
214+ target: listView
215+ onSelectedIndexChanged: {
216+ if (loader.item && loader.item.hasOwnProperty("selected")) {
217+ loader.item.selected = listView.selectedIndex == index;
218+ }
219+ }
220+ }
221+ }
222+ }
223+ }
224+ }
225+}
qml/Rotation/RotationStates.qml+2−2
@@ -273,13 +273,13 @@ StateGroup {
273 } }
273 } }
274 NumberAnimation {
274 NumberAnimation {
275 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
275 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
276− target: root.shell; property: "indicatorAreaShowProgress"
276+ target: root.shell; property: "panelAreaShowProgress"
277 from: 1.0; to: 0.0
277 from: 1.0; to: 0.0
278 }
278 }
279 ImmediateRotationAction { info: d; shell: root.shell }
279 ImmediateRotationAction { info: d; shell: root.shell }
280 NumberAnimation {
280 NumberAnimation {
281 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
281 duration: UbuntuAnimation.FastDuration; easing: UbuntuAnimation.StandardEasing
282− target: root.shell; property: "indicatorAreaShowProgress"
282+ target: root.shell; property: "panelAreaShowProgress"
283 from: 0.0; to: 1.0
283 from: 0.0; to: 1.0
284 }
284 }
285 ScriptAction { script: {
285 ScriptAction { script: {
qml/Shell.qml+20−10
@@ -57,7 +57,7 @@ StyledItem {
57 property Orientations orientations
57 property Orientations orientations
58 property real nativeWidth
58 property real nativeWidth
59 property real nativeHeight
59 property real nativeHeight
60− property alias indicatorAreaShowProgress: panel.indicatorAreaShowProgress
60+ property alias panelAreaShowProgress: panel.panelAreaShowProgress
61 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
61 property string usageScenario: "phone" // supported values: "phone", "tablet" or "desktop"
62 property string mode: "full-greeter"
62 property string mode: "full-greeter"
63 property alias oskEnabled: inputMethod.enabled
63 property alias oskEnabled: inputMethod.enabled
@@ -313,6 +313,11 @@ StyledItem {
313 altTabPressed: physicalKeysMapper.altTabPressed
313 altTabPressed: physicalKeysMapper.altTabPressed
314 oskEnabled: shell.oskEnabled
314 oskEnabled: shell.oskEnabled
315 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
315 spreadEnabled: tutorial.spreadEnabled && (!greeter || (!greeter.hasLockedApp && !greeter.shown))
316+
317+ onSpreadShownChanged: {
318+ panel.indicators.hide();
319+ panel.applicationMenus.hide();
320+ }
316 }
321 }
317 }
322 }
318
323
@@ -345,7 +350,7 @@ StyledItem {
345 Greeter {
350 Greeter {
346
351
347 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
352 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
348− hides: [launcher, panel.indicators]
353+ hides: [launcher, panel.indicators, panel.applicationMenus]
349 tabletMode: shell.usageScenario != "phone"
354 tabletMode: shell.usageScenario != "phone"
350 forcedUnlock: wizard.active || shell.mode === "full-shell"
355 forcedUnlock: wizard.active || shell.mode === "full-shell"
351 background: wallpaperResolver.cachedBackground
356 background: wallpaperResolver.cachedBackground
@@ -445,6 +450,13 @@ StyledItem {
445 id: panel
450 id: panel
446 objectName: "panel"
451 objectName: "panel"
447 anchors.fill: parent //because this draws indicator menus
452 anchors.fill: parent //because this draws indicator menus
453+
454+ mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
455+ minimizedPanelHeight: units.gu(3)
456+ expandedPanelHeight: units.gu(7)
457+ indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
458+ applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
459+
448 indicators {
460 indicators {
449 hides: [launcher]
461 hides: [launcher]
450 available: tutorial.panelEnabled
462 available: tutorial.panelEnabled
@@ -452,12 +464,8 @@ StyledItem {
452 && (!greeter || !greeter.hasLockedApp)
464 && (!greeter || !greeter.hasLockedApp)
453 && !shell.waitingOnGreeter
465 && !shell.waitingOnGreeter
454 && settings.enableIndicatorMenu
466 && settings.enableIndicatorMenu
455− width: parent.width > units.gu(60) ? units.gu(40) : parent.width
456
467
457− minimizedPanelHeight: units.gu(3)
458− expandedPanelHeight: units.gu(7)
459−
460− indicatorsModel: Indicators.IndicatorsModel {
468+ model: Indicators.IndicatorsModel {
461 // tablet and phone both use the same profile
469 // tablet and phone both use the same profile
462 // FIXME: use just "phone" for greeter too, but first fix
470 // FIXME: use just "phone" for greeter too, but first fix
463 // greeter app launching to either load the app inside the
471 // greeter app launching to either load the app inside the
@@ -469,8 +477,10 @@ StyledItem {
469 }
477 }
470 }
478 }
471
479
472− callHint {
480+ applicationMenus {
481+ hides: [launcher]
473− greeterShown: greeter.shown
482+ available: (!greeter || !greeter.shown)
483+ && !shell.waitingOnGreeter
474 }
484 }
475
485
476 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
486 readonly property bool focusedSurfaceIsFullscreen: topLevelSurfaceList.focusedWindow
@@ -478,7 +488,7 @@ StyledItem {
478 : false
488 : false
479 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
489 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
480 || greeter.hasLockedApp
490 || greeter.hasLockedApp
481− locked: greeter && greeter.active
491+ greeterShown: greeter && greeter.shown
482 }
492 }
483
493
484 Launcher {
494 Launcher {
qml/Stage/DecoratedWindow.qml+77−35
@@ -18,6 +18,9 @@ import QtQuick 2.4
18import Ubuntu.Components 1.3
18import Ubuntu.Components 1.3
19import Unity.Application 0.1
19import Unity.Application 0.1
20import "Spread/MathUtils.js" as MathUtils
20import "Spread/MathUtils.js" as MathUtils
21+import Unity.ApplicationMenu 0.1
22+import Unity.Indicators 0.1 as Indicators
23+import "../Components/PanelState"
21
24
22FocusScope {
25FocusScope {
23 id: root
26 id: root
@@ -147,46 +150,12 @@ FocusScope {
147 opacity: root.shadowOpacity
150 opacity: root.shadowOpacity
148 }
151 }
149
152
150− WindowDecoration {
151− id: decoration
152− closeButtonVisible: root.application.appId !== "unity8-dash"
153− objectName: "appWindowDecoration"
154− anchors { left: parent.left; top: parent.top; right: parent.right }
155− height: units.gu(3)
156− width: root.width
157− title: applicationWindow.title
158− opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
159−
160− Behavior on opacity { UbuntuNumberAnimation { } }
161−
162− onCloseClicked: root.closeClicked();
163− onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
164− onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
165− onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
166− onMinimizeClicked: root.minimizeClicked();
167− onPressed: root.decorationPressed();
168−
169− onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
170− onPositionChanged: moveHandler.handlePositionChanged(mouse)
171− onReleased: {
172− root.decorationReleased();
173− moveHandler.handleReleased();
174− }
175− }
176−
177− MoveHandler {
178− id: moveHandler
179− objectName: "moveHandler"
180− target: root.parent
181− buttonsWidth: decoration.buttonsWidth
182− }
183−
184 ApplicationWindow {
153 ApplicationWindow {
185 id: applicationWindow
154 id: applicationWindow
186 objectName: "appWindow"
155 objectName: "appWindow"
187− anchors.left: parent.left
188 anchors.top: parent.top
156 anchors.top: parent.top
189 anchors.topMargin: root.decorationHeight * Math.min(1, root.showDecoration)
157 anchors.topMargin: root.decorationHeight * Math.min(1, root.showDecoration)
158+ anchors.left: parent.left
190 width: implicitWidth
159 width: implicitWidth
191 height: implicitHeight
160 height: implicitHeight
192 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
161 requestedHeight: !counterRotate ? root.requestedHeight - d.requestedDecorationHeight : root.requestedWidth
@@ -227,6 +196,72 @@ FocusScope {
227 ]
196 ]
228 }
197 }
229
198
199+ MouseArea {
200+ anchors { left: parent.left; top: parent.top; right: parent.right }
201+ height: units.gu(3)
202+
203+ opacity: root.hasDecoration ? Math.min(1, root.showDecoration) : 0
204+
205+ Behavior on opacity { UbuntuNumberAnimation { } }
206+
207+ drag.target: Item {}
208+ drag.filterChildren: true
209+ drag.threshold: 0
210+
211+ onPressed: root.decorationPressed();
212+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
213+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
214+ onReleased: {
215+ root.decorationReleased();
216+ moveHandler.handleReleased();
217+ }
218+
219+ WindowDecoration {
220+ id: decoration
221+ closeButtonVisible: root.application.appId !== "unity8-dash"
222+ objectName: "appWindowDecoration"
223+ anchors.fill: parent
224+ title: applicationWindow.title
225+
226+ onCloseClicked: root.closeClicked();
227+ onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
228+ onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
229+ onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
230+ onMinimizeClicked: root.minimizeClicked();
231+
232+ enableMenus: {
233+ return active &&
234+ surface &&
235+ (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.decorationsVisible)
236+ }
237+ menu: sharedAppModel.model
238+
239+ Indicators.SharedUnityMenuModel {
240+ id: sharedAppModel
241+ property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
242+ property var menuService: menus.length > 0 ? menus[0] : undefined
243+
244+ busName: menuService ? menuService.service : ""
245+ menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
246+ actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
247+ }
248+
249+ Connections {
250+ target: ApplicationMenuRegistry
251+ onSurfaceMenuRegistered: {
252+ if (surface && surfaceId === surface.persistentId) {
253+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
254+ }
255+ }
256+ onSurfaceMenuUnregistered: {
257+ if (surface && surfaceId === surface.persistentId) {
258+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
259+ }
260+ }
261+ }
262+ }
263+ }
264+
230 MouseArea {
265 MouseArea {
231 anchors.fill: applicationWindow
266 anchors.fill: applicationWindow
232 acceptedButtons: Qt.LeftButton
267 acceptedButtons: Qt.LeftButton
@@ -257,6 +292,13 @@ FocusScope {
257 }
292 }
258 }
293 }
259
294
295+ MoveHandler {
296+ id: moveHandler
297+ objectName: "moveHandler"
298+ target: root.parent
299+ buttonsWidth: decoration.buttonsWidth
300+ }
301+
260 Rectangle {
302 Rectangle {
261 anchors.fill: parent
303 anchors.fill: parent
262 color: "black"
304 color: "black"
qml/Stage/Stage.qml+17−3
@@ -356,7 +356,7 @@ FocusScope {
356
356
357 Binding {
357 Binding {
358 target: PanelState
358 target: PanelState
359− property: "buttonsVisible"
359+ property: "decorationsVisible"
360 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
360 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
361 }
361 }
362
362
@@ -375,6 +375,20 @@ FocusScope {
375 when: priv.focusedAppDelegate
375 when: priv.focusedAppDelegate
376 }
376 }
377
377
378+ Binding {
379+ target: PanelState
380+ property: "focusedPersistentSurfaceId"
381+ value: {
382+ if (priv.focusedAppDelegate !== null) {
383+ if (priv.focusedAppDelegate.surface) {
384+ return priv.focusedAppDelegate.surface.persistentId;
385+ }
386+ }
387+ return "";
388+ }
389+ when: priv.focusedAppDelegate
390+ }
391+
378 Binding {
392 Binding {
379 target: PanelState
393 target: PanelState
380 property: "dropShadow"
394 property: "dropShadow"
@@ -389,7 +403,7 @@ FocusScope {
389
403
390 Component.onDestruction: {
404 Component.onDestruction: {
391 PanelState.title = "";
405 PanelState.title = "";
392− PanelState.buttonsVisible = false;
406+ PanelState.decorationsVisible = false;
393 PanelState.dropShadow = false;
407 PanelState.dropShadow = false;
394 }
408 }
395
409
@@ -1492,7 +1506,7 @@ FocusScope {
1492
1506
1493 Binding {
1507 Binding {
1494 target: PanelState
1508 target: PanelState
1495− property: "buttonsAlwaysVisible"
1509+ property: "decorationsAlwaysVisible"
1496 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1510 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
1497 }
1511 }
1498
1512
qml/Stage/WindowDecoration.qml+80−19
@@ -15,18 +15,23 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18+import QtQuick.Layouts 1.1
18import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20+import Unity.Application 0.1
19import "../Components"
21import "../Components"
22+import "../Components/PanelState"
23+import "../ApplicationMenus"
20
24
21MouseArea {
25MouseArea {
22 id: root
26 id: root
23− clip: true
24
27
25 property alias closeButtonVisible: buttons.closeButtonShown
28 property alias closeButtonVisible: buttons.closeButtonShown
26 property alias title: titleLabel.text
29 property alias title: titleLabel.text
27 property alias maximizeButtonShown: buttons.maximizeButtonShown
30 property alias maximizeButtonShown: buttons.maximizeButtonShown
28 property bool active: false
31 property bool active: false
29 property alias overlayShown: buttons.overlayShown
32 property alias overlayShown: buttons.overlayShown
33+ property var menu: undefined
34+ property bool enableMenus: true
30
35
31 readonly property real buttonsWidth: buttons.width + row.spacing
36 readonly property real buttonsWidth: buttons.width + row.spacing
32
37
@@ -39,6 +44,7 @@ MouseArea {
39 signal maximizeHorizontallyClicked()
44 signal maximizeHorizontallyClicked()
40 signal maximizeVerticallyClicked()
45 signal maximizeVerticallyClicked()
41
46
47+ onClicked: mouse.accepted = true // propogated event
42 onDoubleClicked: {
48 onDoubleClicked: {
43 if (mouse.button == Qt.LeftButton) {
49 if (mouse.button == Qt.LeftButton) {
44 root.maximizeClicked();
50 root.maximizeClicked();
@@ -48,21 +54,46 @@ MouseArea {
48 // do not let unhandled wheel event pass thru the decoration
54 // do not let unhandled wheel event pass thru the decoration
49 onWheel: wheel.accepted = true;
55 onWheel: wheel.accepted = true;
50
56
57+ QtObject {
58+ id: priv
59+ property var menuBar: menuBarLoader.item
60+
61+ property bool shouldShowMenus: root.enableMenus &&
62+ menuBar &&
63+ menuBar.valid &&
64+ (menuBar.showRequested || root.containsMouse)
65+ }
66+
67+ // We dont want touch events to fall through to parent,
68+ // otherwise the containsMouse will not work.
69+ MouseArea {
70+ anchors.fill: parent
71+ propagateComposedEvents: true
72+ }
73+
51 Rectangle {
74 Rectangle {
75+ id: background
52 anchors.fill: parent
76 anchors.fill: parent
53− anchors.bottomMargin: -radius
54 radius: units.gu(.5)
77 radius: units.gu(.5)
55 color: theme.palette.normal.background
78 color: theme.palette.normal.background
56 }
79 }
57
80
58− Row {
81+ Rectangle {
82+ anchors {
83+ bottom: background.bottom
84+ left: parent.left
85+ right: parent.right
86+ }
87+ height: background.radius
88+ color: theme.palette.normal.background
89+ }
90+
91+ RowLayout {
59 id: row
92 id: row
60 anchors {
93 anchors {
61 fill: parent
94 fill: parent
62 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
95 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
63 rightMargin: units.gu(1)
96 rightMargin: units.gu(1)
64− topMargin: units.gu(0.5)
65− bottomMargin: units.gu(0.5)
66 }
97 }
67 Behavior on anchors.leftMargin {
98 Behavior on anchors.leftMargin {
68 UbuntuNumberAnimation {}
99 UbuntuNumberAnimation {}
@@ -72,7 +103,12 @@ MouseArea {
72
103
73 WindowControlButtons {
104 WindowControlButtons {
74 id: buttons
105 id: buttons
106+ anchors {
75− height: parent.height
107+ top: parent.top
108+ bottom: parent.bottom
109+ topMargin: units.gu(0.5)
110+ bottomMargin: units.gu(0.5)
111+ }
76 active: root.active
112 active: root.active
77 onCloseClicked: root.closeClicked();
113 onCloseClicked: root.closeClicked();
78 onMinimizeClicked: root.minimizeClicked();
114 onMinimizeClicked: root.minimizeClicked();
@@ -81,19 +117,44 @@ MouseArea {
81 onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
117 onMaximizeVerticallyClicked: root.maximizeVerticallyClicked();
82 }
118 }
83
119
120+ Item {
121+ Layout.preferredHeight: parent.height
122+ Layout.fillWidth: true
123+
84− Label {
124+ Label {
85− id: titleLabel
125+ id: titleLabel
86− objectName: "windowDecorationTitle"
126+ objectName: "windowDecorationTitle"
87− color: root.active ? "white" : UbuntuColors.slate
127+ color: root.active ? "white" : UbuntuColors.slate
88− height: parent.height
128+ height: parent.height
89− width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin
129+ width: parent.width
90− verticalAlignment: Text.AlignVCenter
130+ verticalAlignment: Text.AlignVCenter
91− fontSize: "medium"
131+ fontSize: "medium"
92− font.weight: root.active ? Font.Light : Font.Medium
132+ font.weight: root.active ? Font.Light : Font.Medium
93− elide: Text.ElideRight
133+ elide: Text.ElideRight
94− opacity: overlayShown ? 0 : 1
134+ opacity: overlayShown || priv.shouldShowMenus ? 0 : 1
95− visible: opacity != 0
135+ visible: opacity != 0
136+ Behavior on opacity { UbuntuNumberAnimation {} }
137+ }
138+
139+ Loader {
140+ id: menuBarLoader
141+ objectName: "menuBarLoader"
142+ anchors.bottom: parent.bottom
143+ height: parent.height
144+ width: parent.width
145+ active: root.menu !== undefined
146+
147+ sourceComponent: MenuBar {
148+ id: menuBar
149+ height: menuBarLoader.height
150+ enableKeyFilter: valid && root.active && root.enableMenus
151+ unityMenuModel: root.menu
152+ }
153+
154+ opacity: !overlayShown && priv.shouldShowMenus ? 1 : 0
155+ visible: opacity == 1
96− Behavior on opacity { UbuntuNumberAnimation {} }
156+ Behavior on opacity { UbuntuNumberAnimation {} }
157+ }
97 }
158 }
98 }
159 }
99}
160}
src/main.cpp+2−2
@@ -27,8 +27,8 @@ int main(int argc, const char *argv[])
27{
27{
28 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
28 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
29
29
30− bool isMirServer = false;
30+ bool isMirServer = qgetenv("QT_QPA_PLATFORM") == "mirserver";
31− if (qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
31+ if (!isMirServer && qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
32 setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
32 setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
33 isMirServer = true;
33 isMirServer = true;
34 }
34 }
tests/mocks/QMenuModel/unitymenumodel.cpp+47−6
@@ -29,7 +29,9 @@ enum MenuRoles {
29 ActionStateRole,
29 ActionStateRole,
30 IsCheckRole,
30 IsCheckRole,
31 IsRadioRole,
31 IsRadioRole,
32− IsToggledRole
32+ IsToggledRole,
33+ ShortcutRole,
34+ HasSubmenuRole
33};
35};
34
36
35UnityMenuModel::UnityMenuModel(QObject *parent)
37UnityMenuModel::UnityMenuModel(QObject *parent)
@@ -141,7 +143,29 @@ int UnityMenuModel::columnCount(const QModelIndex&) const
141
143
142QVariant UnityMenuModel::data(const QModelIndex &index, int role) const
144QVariant UnityMenuModel::data(const QModelIndex &index, int role) const
143{
145{
144− return rowData(index.row())[roleNames()[role]];
146+ QVariantMap v = rowData(index.row());
147+ QString roleName = roleNames()[role];
148+
149+ if (v.contains(roleName)) return v[roleName];
150+
151+ // defaults
152+ switch (role) {
153+ case LabelRole: return QString();
154+ case SensitiveRole: return true;
155+ case IsSeparatorRole: return false;
156+ case IconRole: return QString();
157+ case TypeRole: return QString();
158+ case ExtendedAttributesRole: return QVariantMap();
159+ case ActionRole: return QString();
160+ case ActionStateRole: return QVariant();
161+ case IsCheckRole: return false;
162+ case IsRadioRole: return false;
163+ case IsToggledRole: return false;
164+ case ShortcutRole: return QString();
165+ case HasSubmenuRole: return subMenuData(index.row()).isValid();
166+ default: break;
167+ }
168+ return QVariant();
145}
169}
146
170
147QVariantMap UnityMenuModel::rowData(int row) const
171QVariantMap UnityMenuModel::rowData(int row) const
@@ -149,12 +173,14 @@ QVariantMap UnityMenuModel::rowData(int row) const
149 if (m_modelData.count() <= row) {
173 if (m_modelData.count() <= row) {
150 return QVariantMap();
174 return QVariantMap();
151 }
175 }
152− return m_modelData[row].toMap()["rowData"].toMap();
176+ QVariantMap vRow = m_modelData.value(row, QVariantMap()).toMap();
177+ return vRow["rowData"].toMap();
153}
178}
154
179
155QVariant UnityMenuModel::subMenuData(int row) const
180QVariant UnityMenuModel::subMenuData(int row) const
156{
181{
157− return m_modelData[row].toMap()["submenu"];
182+ QVariantMap v = m_modelData.value(row, QVariantMap()).toMap();
183+ return v.value("submenu", QVariant());
158}
184}
159
185
160QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex&) const
186QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex&) const
@@ -182,6 +208,8 @@ QHash<int, QByteArray> UnityMenuModel::roleNames() const
182 names[IsCheckRole] = "isCheck";
208 names[IsCheckRole] = "isCheck";
183 names[IsRadioRole] = "isRadio";
209 names[IsRadioRole] = "isRadio";
184 names[IsToggledRole] = "isToggled";
210 names[IsToggledRole] = "isToggled";
211+ names[ShortcutRole] = "shortcut";
212+ names[HasSubmenuRole] = "hasSubmenu";
185
213
186 return names;
214 return names;
187}
215}
@@ -201,6 +229,7 @@ QObject * UnityMenuModel::submenu(int position, QQmlComponent*)
201 UnityMenuModel*& model = submenus[position];
229 UnityMenuModel*& model = submenus[position];
202 if (!model) {
230 if (!model) {
203 model = new UnityMenuModel(this);
231 model = new UnityMenuModel(this);
232+ connect(model, &UnityMenuModel::activated, this, &UnityMenuModel::activated);
204 }
233 }
205 if (model->modelData() != submenuData) {
234 if (model->modelData() != submenuData) {
206 model->setModelData(submenuData);
235 model->setModelData(submenuData);
@@ -225,11 +254,23 @@ QVariant UnityMenuModel::get(int row, const QByteArray &role)
225 roles.insert(names[role], role);
254 roles.insert(names[role], role);
226 }
255 }
227
256
228− return this->data(this->index(row, 0), roles[role]);
257+ return data(index(row, 0), roles[role]);
229}
258}
230
259
231−void UnityMenuModel::activate(int, const QVariant&)
260+void UnityMenuModel::activate(int row, const QVariant&)
232{
261{
262+ QVariantMap vModelData = m_modelData.value(row, QVariantMap()).toMap();
263+ QVariantMap rd = vModelData["rowData"].toMap();
264+
265+ bool isCheckable = rd[roleNames()[IsCheckRole]].toBool() || rd[roleNames()[IsRadioRole]].toBool();
266+ if (isCheckable) {
267+ rd[roleNames()[IsToggledRole]] = !rd[roleNames()[IsToggledRole]].toBool();
268+ vModelData["rowData"] = rd;
269+ m_modelData[row] = vModelData;
270+
271+ dataChanged(index(row, 0), index(row, 0), QVector<int>() << IsToggledRole);
272+ }
273+ Q_EMIT activated(rd[roleNames()[ActionRole]].toString());
233}
274}
234
275
235void UnityMenuModel::changeState(int, const QVariant&)
276void UnityMenuModel::changeState(int, const QVariant&)
tests/mocks/QMenuModel/unitymenumodel.h+3−1
@@ -61,7 +61,7 @@ public:
61
61
62 QString nameOwner() const;
62 QString nameOwner() const;
63
63
64− int rowCount(const QModelIndex &parent = QModelIndex()) const override;
64+ Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
65 int columnCount(const QModelIndex &parent = QModelIndex()) const override;
65 int columnCount(const QModelIndex &parent = QModelIndex()) const override;
66 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
66 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
67 QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
67 QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
@@ -88,6 +88,8 @@ Q_SIGNALS:
88 // Internal mock usage
88 // Internal mock usage
89 void modelDataChanged();
89 void modelDataChanged();
90
90
91+ void activated(const QString& action);
92+
91private:
93private:
92 QVariantMap rowData(int row) const;
94 QVariantMap rowData(int row) const;
93 QVariant subMenuData(int row) const;
95 QVariant subMenuData(int row) const;
tests/mocks/Unity/Application/SurfaceManager.cpp+5−2
@@ -83,9 +83,11 @@ void SurfaceManager::registerSurface(MirSurface *surface)
83 this->onStateRequested(surface, state);
83 this->onStateRequested(surface, state);
84 });
84 });
85
85
86+ const QString persistentId = surface->persistentId();
86 connect(surface, &QObject::destroyed, this, [=]() {
87 connect(surface, &QObject::destroyed, this, [=]() {
87− this->onSurfaceDestroyed(surface);
88+ this->onSurfaceDestroyed(surface, persistentId);
88 });
89 });
90+
89}
91}
90
92
91void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
93void SurfaceManager::notifySurfaceCreated(unityapi::MirSurfaceInterface *surface)
@@ -223,7 +225,7 @@ void SurfaceManager::onStateRequested(MirSurface *surface, Mir::State state)
223 DEBUG_MSG("("<<surface<<","<<state<<") ended");
225 DEBUG_MSG("("<<surface<<","<<state<<") ended");
224}
226}
225
227
226−void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
228+void SurfaceManager::onSurfaceDestroyed(MirSurface *surface, const QString& persistentId)
227{
229{
228 m_surfaces.removeAll(surface);
230 m_surfaces.removeAll(surface);
229 if (m_focusedSurface == surface) {
231 if (m_focusedSurface == surface) {
@@ -237,6 +239,7 @@ void SurfaceManager::onSurfaceDestroyed(MirSurface *surface)
237 m_underModification = false;
239 m_underModification = false;
238 Q_EMIT modificationsEnded();
240 Q_EMIT modificationsEnded();
239 }
241 }
242+ Q_EMIT surfaceDestroyed(persistentId);
240}
243}
241
244
242void SurfaceManager::focusFirstAvailableSurface()
245void SurfaceManager::focusFirstAvailableSurface()
tests/mocks/Unity/Application/SurfaceManager.h+3−1
@@ -76,6 +76,8 @@ public Q_SLOTS:
76 void createInputMethodSurface();
76 void createInputMethodSurface();
77
77
78Q_SIGNALS:
78Q_SIGNALS:
79+ void surfaceDestroyed(const QString& persistentSurfaceId);
80+
79 void newSurfaceMinimumWidthChanged(int value);
81 void newSurfaceMinimumWidthChanged(int value);
80 void newSurfaceMaximumWidthChanged(int value);
82 void newSurfaceMaximumWidthChanged(int value);
81 void newSurfaceMinimumHeightChanged(int value);
83 void newSurfaceMinimumHeightChanged(int value);
@@ -85,7 +87,7 @@ Q_SIGNALS:
85
87
86private Q_SLOTS:
88private Q_SLOTS:
87 void onStateRequested(MirSurface *surface, Mir::State state);
89 void onStateRequested(MirSurface *surface, Mir::State state);
88− void onSurfaceDestroyed(MirSurface *surface);
90+ void onSurfaceDestroyed(MirSurface *surface, const QString& persistentId);
89
91
90private:
92private:
91 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
93 void doRaise(unity::shell::application::MirSurfaceInterface *surface);
tests/mocks/Unity/ApplicationMenu/mockapplicationmenuregistry.cpp+5
@@ -40,3 +40,8 @@ void MockApplicationMenuRegistry::RegisterSurfaceMenu(const QString &surface,
40 QDBusObjectPath(actionObjectPath),
40 QDBusObjectPath(actionObjectPath),
41 service);
41 service);
42}
42}
43+
44+void MockApplicationMenuRegistry::UnregisterSurfaceMenu(const QString &surfaceId, const QDBusObjectPath &menuObjectPath)
45+{
46+ ApplicationMenuRegistry::UnregisterSurfaceMenu(surfaceId, menuObjectPath);
47+}
tests/mocks/Unity/ApplicationMenu/mockapplicationmenuregistry.h+3
@@ -31,6 +31,9 @@ public:
31 const QString &actionObjectPath,
31 const QString &actionObjectPath,
32 const QString &service);
32 const QString &service);
33
33
34+ Q_INVOKABLE void UnregisterSurfaceMenu(const QString &surfaceId,
35+ const QDBusObjectPath &menuObjectPath);
36+
34private:
37private:
35 MockApplicationMenuRegistry(QObject *parent = 0);
38 MockApplicationMenuRegistry(QObject *parent = 0);
36};
39};
tests/mocks/Unity/Indicators/fakeindicatorsmodeldata.js+4−4
@@ -567,7 +567,7 @@ var fakeMenuData = {
567 "state": "Stopped"
567 "state": "Stopped"
568 },
568 },
569 "ext": {},
569 "ext": {},
570− "icon": "file:///usr/share/mediaplayer-app/mediaplayer-app.png",
570+ "icon": "image://theme/mediaplayer-app",
571 "isCheck": false,
571 "isCheck": false,
572 "isRadio": false,
572 "isRadio": false,
573 "isSeparator": false,
573 "isSeparator": false,
@@ -863,7 +863,7 @@ var fakeMenuData = {
863 "ext": {
863 "ext": {
864 "xCanonicalUid": 1003
864 "xCanonicalUid": 1003
865 },
865 },
866− "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
866+ "icon": "image://theme/webbrowser-app",
867 "isCheck": false,
867 "isCheck": false,
868 "isRadio": false,
868 "isRadio": false,
869 "isSeparator": false,
869 "isSeparator": false,
@@ -912,7 +912,7 @@ var fakeMenuData = {
912 "ext": {
912 "ext": {
913 "xCanonicalUid": 1002
913 "xCanonicalUid": 1002
914 },
914 },
915− "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
915+ "icon": "image://theme/webbrowser-app",
916 "isCheck": false,
916 "isCheck": false,
917 "isRadio": false,
917 "isRadio": false,
918 "isSeparator": false,
918 "isSeparator": false,
@@ -929,7 +929,7 @@ var fakeMenuData = {
929 "ext": {
929 "ext": {
930 "xCanonicalUid": 1001
930 "xCanonicalUid": 1001
931 },
931 },
932− "icon": "file:///usr/share/webbrowser-app/webbrowser-app.png",
932+ "icon": "image://theme/webbrowser-app",
933 "isCheck": false,
933 "isCheck": false,
934 "isRadio": false,
934 "isRadio": false,
935 "isSeparator": false,
935 "isSeparator": false,
tests/qmltests/ApplicationMenuDataLoader.qmlnew file+75
@@ -0,0 +1,75 @@
1+import QtQuick 2.4
2+import Ubuntu.Components 1.3
3+import Unity.Application 0.1
4+import Unity.ApplicationMenu 0.1
5+import Unity.Indicators 0.1 as Indicators
6+
7+Object {
8+
9+ property alias surfaceManager: sMgrHandler.target
10+
11+ Connections {
12+ id: sMgrHandler
13+ onSurfaceCreated: {
14+ var fakeMenuPath = "/" + surface.persistentId.replace(/\W+/g, "");
15+
16+ ApplicationMenuRegistry.RegisterSurfaceMenu(surface.persistentId, fakeMenuPath, fakeMenuPath, ":1");
17+ Indicators.UnityMenuModelCache.setCachedModelData(fakeMenuPath, generateTestData(4, 3, 2, 3, "menu"));
18+ }
19+ onSurfaceDestroyed: {
20+ ApplicationMenuRegistry.UnregisterSurfaceMenu(persistentSurfaceId, "/app");
21+ }
22+ }
23+
24+ function generateTestData(length, depth, submenuInterval, separatorInterval, prefix, root) {
25+ var data = [];
26+ if (root === undefined) root = true;
27+
28+ if (prefix === undefined) prefix = "menu"
29+
30+ for (var i = 0; i < length; i++) {
31+
32+ var menuCode = String.fromCharCode(i+65);
33+
34+ var isSeparator = !root && separatorInterval > 0 && ((i+1) % separatorInterval == 0);
35+ var row = {
36+ "rowData": { // 1
37+ "label": prefix + "&" + menuCode,
38+ "sensitive": true,
39+ "isSeparator": isSeparator,
40+ "icon": "",
41+ "ext": {},
42+ "action": prefix + menuCode,
43+ "actionState": {},
44+ "isCheck": false,
45+ "isRadio": false,
46+ "isToggled": false,
47+ "shortcut": ""
48+ }
49+ }
50+ var isSubmenu = root === undefined || root === true || (submenuInterval > 0 && ((i+1) % submenuInterval == 0));
51+ if (isSubmenu && !isSeparator && depth > 1) {
52+ row["submenu"] = generateTestData(length, depth-1, submenuInterval, separatorInterval, prefix + menuCode + ".", false);
53+ }
54+ data[i] = row;
55+ }
56+ return data;
57+ }
58+
59+ // Test Data
60+ property var singleCheckable: [{
61+ "rowData": { // 1
62+ "label": "checkable1",
63+ "sensitive": true,
64+ "isSeparator": false,
65+ "icon": "",
66+ "ext": {},
67+ "action": "checkable1",
68+ "actionState": {},
69+ "isCheck": true,
70+ "isRadio": false,
71+ "isToggled": false,
72+ "shortcut": "Alt+F"
73+ }
74+ }]
75+}
tests/qmltests/ApplicationMenus/tst_MenuBar.qmlnew file+183
@@ -0,0 +1,183 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import Ubuntu.Components.ListItems 1.3
21+import Unity.Application 0.1
22+import QMenuModel 0.1
23+import Unity.Test 0.1
24+import Utils 0.1
25+
26+import "../../../qml/ApplicationMenus"
27+import ".."
28+
29+Item {
30+ id: root
31+ width: units.gu(100)
32+ height: units.gu(50)
33+
34+ Component.onCompleted: {
35+ QuickUtils.keyboardAttached = true;
36+ theme.name = "Ubuntu.Components.Themes.SuruDark"
37+ }
38+
39+ Binding {
40+ target: MouseTouchAdaptor
41+ property: "enabled"
42+ value: false
43+ }
44+
45+ SurfaceManager { id: sMgr }
46+ ApplicationMenuDataLoader {
47+ id: appMenuData
48+ surfaceManager: sMgr
49+ }
50+
51+ Rectangle {
52+ anchors {
53+ left: parent.left
54+ right: parent.right
55+ top: parent.top
56+ margins: units.gu(1)
57+ }
58+ height: units.gu(3)
59+ color: "grey"
60+
61+ MenuBar {
62+ id: menuBar
63+ anchors.fill: parent
64+ enableKeyFilter: true
65+
66+ unityMenuModel: UnityMenuModel {
67+ id: menuBackend
68+ modelData: appMenuData.generateTestData(7,5,2,3,"menu")
69+ }
70+ }
71+ }
72+
73+ SignalSpy {
74+ id: activatedSpy
75+ target: menuBackend
76+ signalName: "activated"
77+ }
78+
79+ UnityTestCase {
80+ id: testCase
81+ name: "MenuBar"
82+ when: windowShown
83+
84+ function init() {
85+ menuBar.dismiss();
86+ menuBackend.modelData = appMenuData.generateTestData(5,5,2,3,"menu")
87+ activatedSpy.clear();
88+ }
89+
90+ function test_mouseNavigation() {
91+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
92+ wait(50) // wait for row to build
93+ var priv = findInvisibleChild(menuBar, "d");
94+
95+ var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
96+ var menuItem1 = findChild(menuBar, "menuBar-item1"); verify(menuItem1);
97+ var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
98+
99+ menuItem0.show();
100+ compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
101+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
102+
103+ mouseMove(menuItem1, menuItem1.width/2, menuItem1.height/2);
104+ tryCompare(priv, "currentItem", menuItem1, undefined, "CurrentItem should have moved to item 1");
105+ compare(menuItem1.popupVisible, true, "Popup should be visible");
106+
107+ mouseMove(menuItem2, menuItem2.width/2, menuItem2.height/2);
108+ tryCompare(priv, "currentItem", menuItem2, undefined, "CurrentItem should have moved to item 2");
109+ compare(menuItem2.popupVisible, true, "Popup should be visible");
110+
111+ mouseMove(menuItem0, menuItem0.width/2, menuItem0.height/2);
112+ tryCompare(priv, "currentItem", menuItem0, undefined, "CurrentItem should have moved to item 0");
113+ compare(menuItem0.popupVisible, true, "Popup should be visible");
114+ }
115+
116+ function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
118+ var priv = findInvisibleChild(menuBar, "d");
119+
120+ var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
121+ var menuItem1 = findChild(menuBar, "menuBar-item1"); verify(menuItem1);
122+ var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
123+
124+ menuItem0.show();
125+ compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
126+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
127+
128+ keyClick(Qt.Key_Right, Qt.NoModifier);
129+ compare(priv.currentItem, menuItem1, "CurrentItem should have moved to item 1");
130+ compare(menuItem1.popupVisible, true, "Popup should be visible");
131+
132+ keyClick(Qt.Key_Right, Qt.NoModifier);
133+ compare(priv.currentItem, menuItem2, "CurrentItem should have moved to item 2");
134+ compare(menuItem2.popupVisible, true, "Popup should be visible");
135+
136+ keyClick(Qt.Key_Right, Qt.NoModifier);
137+ compare(priv.currentItem, menuItem0, "CurrentItem should have moved back to item 0");
138+ compare(menuItem0.popupVisible, true, "Popup should be visible");
139+ }
140+
141+ function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
143+ var priv = findInvisibleChild(menuBar, "d");
144+
145+ var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
146+ var menuItem1 = findChild(menuBar, "menuBar-item1"); verify(menuItem1);
147+ var menuItem2 = findChild(menuBar, "menuBar-item2"); verify(menuItem2);
148+
149+ menuItem0.show();
150+ compare(priv.currentItem, menuItem0, "CurrentItem should be set to item 0");
151+ compare(priv.currentItem.popupVisible, true, "Popup should be visible");
152+
153+ keyClick(Qt.Key_Left, Qt.NoModifier);
154+ compare(priv.currentItem, menuItem2, "CurrentItem should have moved to item 2");
155+ compare(menuItem2.popupVisible, true, "Popup should be visible");
156+
157+ keyClick(Qt.Key_Left, Qt.NoModifier);
158+ compare(priv.currentItem, menuItem1, "CurrentItem should have moved to item 1");
159+ compare(menuItem1.popupVisible, true, "Popup should be visible");
160+
161+ keyClick(Qt.Key_Left, Qt.NoModifier);
162+ compare(priv.currentItem, menuItem0, "CurrentItem should have moved back to item 0");
163+ compare(menuItem0.popupVisible, true, "Popup should be visible");
164+ }
165+
166+ function test_mnemonics_data() {
167+ return [
168+ { tag: "a", expectedItem: "menuBar-item0" },
169+ { tag: "c", expectedItem: "menuBar-item2" },
170+ ]
171+ }
172+
173+ function test_mnemonics(data) {
174+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
175+ var priv = findInvisibleChild(menuBar, "d");
176+
177+ var menuItem = findChild(menuBar, data.expectedItem); verify(menuItem);
178+
179+ keyPress(data.tag, Qt.AltModifier, 100);
180+ tryCompare(priv, "currentItem", menuItem);
181+ }
182+ }
183+}
tests/qmltests/ApplicationMenus/tst_MenuPopup.qmlnew file+207
@@ -0,0 +1,207 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import QtTest 1.0
19+import Ubuntu.Components 1.3
20+import Ubuntu.Components.ListItems 1.3
21+import Unity.Application 0.1
22+import QMenuModel 0.1
23+import Unity.Test 0.1
24+import Utils 0.1
25+
26+import "../../../qml/ApplicationMenus"
27+import ".."
28+
29+Item {
30+ id: root
31+ width: Math.max(units.gu(100), page.width + units.gu(6))
32+ height: Math.max(units.gu(50), page.height + units.gu(6))
33+
34+ Binding {
35+ target: MouseTouchAdaptor
36+ property: "enabled"
37+ value: false
38+ }
39+
40+ ApplicationMenuDataLoader { id: appMenuData }
41+
42+ MenuPopup {
43+ id: page
44+
45+ anchors {
46+ left: parent.left
47+ top: parent.top
48+ leftMargin: units.gu(3)
49+ topMargin: units.gu(3)
50+ }
51+
52+ unityMenuModel: UnityMenuModel {
53+ id: menuBackend
54+ modelData: appMenuData.generateTestData(7,5,2,3,"menu")
55+ }
56+ }
57+
58+ SignalSpy {
59+ id: activatedSpy
60+ target: menuBackend
61+ signalName: "activated"
62+ }
63+
64+ UnityTestCase {
65+ id: testCase
66+ name: "MenuPopup"
67+ when: windowShown
68+
69+ function init() {
70+ page.show();
71+ }
72+
73+ function cleanup() {
74+ page.reset();
75+ wait(100); // let the page dismiss
76+ activatedSpy.clear();
77+ }
78+
79+ // visit and verify that all the backend menus have been created
80+ function recurseMenuConstruction(rows, menuPage) {
81+ for (var i = 0; i < rows.length; ++i) {
82+ var rowData = rows[i]["rowData"];
83+
84+ var menuItemName = menuPage.objectName +"-item"+i
85+
86+ var menuItem = findChild(menuPage, menuItemName); verify(menuItem);
87+
88+ var menuPriv = findInvisibleChild(menuPage, "d");
89+
90+ // recurse into submenu
91+ var submenu = rows[i]["submenu"];
92+ if (submenu) {
93+ mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
94+ tryCompare(menuPriv, "currentItem", menuItem);
95+
96+ tryCompareFunction(function() { return menuItem.popup !== null && menuItem.visible }, true);
97+
98+ var submenuPage = findChild(menuPage, menuItemName + "-menu"); verify(submenuPage);
99+
100+ recurseMenuConstruction(submenu, submenuPage);
101+ } else {
102+ mouseMove(menuItem, menuItem.width/2, menuItem.height/2);
103+ tryCompare(menuPriv, "currentItem", menuItem);
104+ }
105+ }
106+ }
107+
108+ function test_mouseNavigation_data() {
109+ return [
110+ { tag: "long", testData: appMenuData.generateTestData(4, 2, 1, 0, "menu", false) },
111+ { tag: "deep", testData: appMenuData.generateTestData(2, 4, 1, 0, "menu", false) }
112+ ]
113+ }
114+
115+ function test_mouseNavigation(data) {
116+ menuBackend.modelData = data.testData;
117+
118+ recurseMenuConstruction(data.testData, page);
119+ }
120+
121+ function test_checkableMenuTogglesOnClick() {
122+ menuBackend.modelData = appMenuData.singleCheckable;
123+
124+ var menuItem = findChild(page, "menu-item0-actionItem");
125+ verify(menuItem);
126+ compare(menuItem.action.checkable, true, "Menu item should be checkable");
127+ compare(menuItem.action.checked, false, "Menu item should not be checked");
128+
129+ mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
130+
131+ compare(menuItem.action.checked, true, "Checkable menu item should have toggled");
132+ }
133+
134+ function test_keyboardNavigation_DownKeySelectsAndOpensNextMenuItemAndRotates() {
135+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
136+
137+ var item0 = findChild(page, "menu-item0"); verify(item0);
138+ var item1 = findChild(page, "menu-item1"); verify(item1);
139+ var item2 = findChild(page, "menu-item2"); verify(item2);
140+
141+ var priv = findInvisibleChild(page, "d");
142+
143+ keyClick(Qt.Key_Down, Qt.NoModifier);
144+ compare(priv.currentItem, item0, "CurrentItem should have moved to item 0");
145+
146+ keyClick(Qt.Key_Down, Qt.NoModifier);
147+ compare(priv.currentItem, item1, "CurrentItem should have moved to item 1");
148+
149+ keyClick(Qt.Key_Down, Qt.NoModifier);
150+ compare(priv.currentItem, item2, "CurrentItem should have moved to item 2");
151+
152+ keyClick(Qt.Key_Down, Qt.NoModifier);
153+ compare(priv.currentItem, item0, "CurrentItem should have moved to item 0");
154+ }
155+
156+ function test_keyboardNavigation_UpKeySelectsAndOpensPreviousMenuItemAndRotates() {
157+ menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu",false);
158+
159+ var item0 = findChild(page, "menu-item0"); verify(item0);
160+ var item1 = findChild(page, "menu-item1"); verify(item1);
161+ var item2 = findChild(page, "menu-item2"); verify(item2);
162+
163+ var priv = findInvisibleChild(page, "d");
164+
165+ keyClick(Qt.Key_Down, Qt.NoModifier);
166+ compare(priv.currentItem, item0, "CurrentItem should have moved to item 2");
167+
168+ keyClick(Qt.Key_Down, Qt.NoModifier);
169+ compare(priv.currentItem, item1, "CurrentItem should have moved to item 1");
170+
171+ keyClick(Qt.Key_Down, Qt.NoModifier);
172+ compare(priv.currentItem, item2, "CurrentItem should have moved to item 0");
173+
174+ keyClick(Qt.Key_Down, Qt.NoModifier);
175+ compare(priv.currentItem, item0, "CurrentItem should have moved to item 2");
176+ }
177+
178+ function test_keyboardNavigation_RightKeyEntersSubMenu() {
179+ menuBackend.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
180+
181+ var menuItem = findChild(page, "menu-item0");
182+
183+ var priv = findInvisibleChild(page, "d");
184+ priv.currentItem = menuItem;
185+
186+ keyClick(Qt.Key_Right, Qt.NoModifier);
187+ tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
188+
189+ var submenu0 = findChild(page, "menu-item0-menu"); verify(submenu0);
190+ var submenu0item0 = findChild(submenu0, "menu-item0-menu-item0"); verify(submenu0item0);
191+
192+ var submenu0Priv = findInvisibleChild(submenu0, "d"); verify(submenu0Priv);
193+ compare(submenu0Priv.currentItem, submenu0item0, "First item of submenu should be selected");
194+ }
195+
196+ function test_keyboardNavigation_LeftKeyClosesSubMenu() {
197+ menuBackend.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
198+
199+ var menuItem = findChild(page, "menu-item0"); verify(menuItem);
200+ mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
201+ tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, true);
202+
203+ keyClick(Qt.Key_Left, Qt.NoModifier);
204+ tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
205+ }
206+ }
207+}
tests/qmltests/CMakeLists.txt+11−7
@@ -7,6 +7,9 @@ add_unity8_qmltest(. Shell)
7add_unity8_qmltest(. ShellWithPin)
7add_unity8_qmltest(. ShellWithPin)
8add_unity8_qmltest(. DeviceConfiguration)
8add_unity8_qmltest(. DeviceConfiguration)
9add_unity8_qmltest_data(. EdgeBarrierControls)
9add_unity8_qmltest_data(. EdgeBarrierControls)
10+add_unity8_qmltest_data(. ApplicationMenuDataLoader)
11+add_unity8_qmltest(ApplicationMenus MenuBar)
12+add_unity8_qmltest(ApplicationMenus MenuPopup)
10add_unity8_qmltest(Components Background)
13add_unity8_qmltest(Components Background)
11add_unity8_qmltest(Components Carousel)
14add_unity8_qmltest(Components Carousel)
12add_unity8_qmltest(Components Dialogs)
15add_unity8_qmltest(Components Dialogs)
@@ -85,16 +88,16 @@ add_unity8_qmltest(Notifications OptionToggle)
85add_unity8_qmltest(Notifications SwipeToAct)
88add_unity8_qmltest(Notifications SwipeToAct)
86add_unity8_qmltest_data(Notifications Notification)
89add_unity8_qmltest_data(Notifications Notification)
87add_unity8_qmltest(Panel ActiveCallHint)
90add_unity8_qmltest(Panel ActiveCallHint)
88−add_unity8_qmltest(Panel IndicatorItem)
91+add_unity8_qmltest(Panel PanelItemRow)
89−add_unity8_qmltest(Panel IndicatorItemRow)
90−add_unity8_qmltest(Panel IndicatorPage)
92+add_unity8_qmltest(Panel PanelMenuPage)
91−add_unity8_qmltest(Panel IndicatorsBar)
93+add_unity8_qmltest(Panel PanelBar)
92−add_unity8_qmltest(Panel IndicatorsMenu)
94+add_unity8_qmltest(Panel PanelMenu)
93add_unity8_qmltest(Panel MenuContent)
95add_unity8_qmltest(Panel MenuContent)
94add_unity8_qmltest(Panel Panel)
96add_unity8_qmltest(Panel Panel)
95−add_unity8_qmltest_data(Panel IndicatorTest)
97+add_unity8_qmltest_data(Panel PanelTest)
98+add_unity8_qmltest(Panel/Indicators IndicatorItem)
96add_unity8_qmltest(Panel/Indicators IndicatorsLight)
99add_unity8_qmltest(Panel/Indicators IndicatorsLight)
97−add_unity8_qmltest(Panel/Indicators MenuItemFactory)
100+add_unity8_qmltest(Panel/Indicators IndicatorMenuItemFactory)
98add_unity8_qmltest(Panel/Indicators MessageMenuItemFactory)
101add_unity8_qmltest(Panel/Indicators MessageMenuItemFactory)
99add_unity8_qmltest(Stage ApplicationWindow)
102add_unity8_qmltest(Stage ApplicationWindow)
100add_unity8_qmltest(Stage DesktopStage)
103add_unity8_qmltest(Stage DesktopStage)
@@ -104,6 +107,7 @@ add_unity8_qmltest(Stage TabletStage)
104add_unity8_qmltest(Stage WindowResizeArea)
107add_unity8_qmltest(Stage WindowResizeArea)
105add_unity8_qmltest(Stage Splash)
108add_unity8_qmltest(Stage Splash)
106add_unity8_qmltest(Stage DecoratedWindow)
109add_unity8_qmltest(Stage DecoratedWindow)
110+add_unity8_qmltest(Stage WindowDecoration)
107add_unity8_qmltest_data(Stage ApplicationCheckBox)
111add_unity8_qmltest_data(Stage ApplicationCheckBox)
108add_unity8_qmltest_data(Stage SizeHintField)
112add_unity8_qmltest_data(Stage SizeHintField)
109add_unity8_qmltest_data(Stage SurfaceManagerControls)
113add_unity8_qmltest_data(Stage SurfaceManagerControls)
tests/qmltests/Panel/tst_IndicatorItem.qml → tests/qmltests/Panel/Indicators/tst_IndicatorItem.qmlrenamed+1−1
@@ -19,7 +19,7 @@ import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
21import Unity.Test 0.1 as UT
21import Unity.Test 0.1 as UT
22−import "../../../qml/Panel"
22+import "../../../../qml/Panel/Indicators"
23
23
24Rectangle {
24Rectangle {
25 width: units.gu(80)
25 width: units.gu(80)
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml → tests/qmltests/Panel/Indicators/tst_IndicatorMenuItemFactory.qmlrenamed+2−2
@@ -28,7 +28,7 @@ Item {
28 width: units.gu(40)
28 width: units.gu(40)
29 height: units.gu(70)
29 height: units.gu(70)
30
30
31− MenuItemFactory {
31+ IndicatorMenuItemFactory {
32 id: factory
32 id: factory
33 menuModel: UnityMenuModel {}
33 menuModel: UnityMenuModel {}
34 }
34 }
@@ -51,7 +51,7 @@ Item {
51 }
51 }
52
52
53 UT.UnityTestCase {
53 UT.UnityTestCase {
54− name: "MenuItemFactory"
54+ name: "IndicatorMenuItemFactory"
55 when: windowShown
55 when: windowShown
56
56
57 property QtObject menuData: QtObject {
57 property QtObject menuData: QtObject {
tests/qmltests/Panel/IndicatorTest.qml → tests/qmltests/Panel/PanelTest.qmlrenamed−5
@@ -15,11 +15,6 @@
15 */
15 */
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18−import QtQuick.Layouts 1.1
19−import QtTest 1.0
20−import "../../../qml/Panel"
21−import Ubuntu.Components 1.3
22−import Unity.Test 0.1 as UT
23import Unity.Indicators 0.1 as Indicators
18import Unity.Indicators 0.1 as Indicators
24
19
25Rectangle {
20Rectangle {
tests/qmltests/Panel/tst_MenuContent.qml+14−2
@@ -20,7 +20,7 @@ import Unity.Test 0.1 as UT
20import "../../../qml/Panel"
20import "../../../qml/Panel"
21import Unity.Indicators 0.1 as Indicators
21import Unity.Indicators 0.1 as Indicators
22
22
23−IndicatorTest {
23+PanelTest {
24 id: root
24 id: root
25 width: units.gu(40)
25 width: units.gu(40)
26 height: units.gu(70)
26 height: units.gu(70)
@@ -31,8 +31,20 @@ IndicatorTest {
31
31
32 MenuContent {
32 MenuContent {
33 id: menuContent
33 id: menuContent
34− indicatorsModel: root.indicatorsModel
34+ model: root.indicatorsModel
35 height: parent.height - 50
35 height: parent.height - 50
36+
37+ pageDelegate: Rectangle {
38+
39+ function reset() {
40+ }
41+
42+ Binding {
43+ target: parent
44+ property: "objectName"
45+ value: modelData ? modelData.identifier : ""
46+ }
47+ }
36 }
48 }
37
49
38 Rectangle {
50 Rectangle {
tests/qmltests/Panel/tst_Panel.qml+384−103
@@ -19,26 +19,42 @@ import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20import Unity.Test 0.1
20import Unity.Test 0.1
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3 as ListItem
22import Unity.Application 0.1
23import Unity.Application 0.1
23−import Unity.Indicators 0.1 as Indicators
24+import QMenuModel 0.1
24import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
26+import AccountsService 0.1
27+import Unity.InputInfo 0.1
25import "../../../qml/Panel"
28import "../../../qml/Panel"
26import "../../../qml/Components/PanelState"
29import "../../../qml/Components/PanelState"
30+import "../Stage"
31+import ".."
27
32
28−IndicatorTest {
33+PanelTest {
29 id: root
34 id: root
30− width: units.gu(100)
35+ width: units.gu(120)
31 height: units.gu(71)
36 height: units.gu(71)
32 color: "black"
37 color: "black"
33
38
34− Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark"
39+ Binding {
40+ target: QuickUtils
41+ property: "keyboardAttached"
42+ value: keyboardAttached.checked
43+ }
35
44
36− SurfaceManager {}
45+ SurfaceManager { id: sMgr }
46+ ApplicationMenuDataLoader {
47+ id: appMenuData
48+ surfaceManager: sMgr
49+ }
37
50
38− Binding {
51+ Component.onCompleted: {
39− target: mouseEmulation
52+ theme.name = "Ubuntu.Components.Themes.SuruDark"
40− property: "checked"
53+ }
41− value: !windowControlsCB.checked
54+
55+ Rectangle {
56+ anchors.fill: parent
57+ color: "darkgrey"
42 }
58 }
43
59
44 RowLayout {
60 RowLayout {
@@ -61,12 +77,23 @@ IndicatorTest {
61 Panel {
77 Panel {
62 id: panel
78 id: panel
63 anchors.fill: parent
79 anchors.fill: parent
80+ mode: modeSelector.model[modeSelector.selectedIndex]
81+
82+ indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
83+ applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
84+
85+ applicationMenus {
86+ model: UnityMenuModel {
87+ modelData: appMenuData.generateTestData(5, 4, 2, 3, "menu")
88+ }
89+
64− indicators {
90+ hides: [ panel.indicators ]
65− width: parent.width > units.gu(60) ? units.gu(40) : parent.width
66− indicatorsModel: root.indicatorsModel
67 }
91 }
68
92
69− property real panelAndSeparatorHeight: panel.indicators.minimizedPanelHeight
93+ indicators {
94+ model: root.indicatorsModel
95+ hides: [ panel.applicationMenus ]
96+ }
70 }
97 }
71 }
98 }
72 }
99 }
@@ -75,6 +102,18 @@ IndicatorTest {
75 Layout.alignment: Qt.AlignTop
102 Layout.alignment: Qt.AlignTop
76 Layout.fillWidth: false
103 Layout.fillWidth: false
77
104
105+ ListItem.ItemSelector {
106+ id: modeSelector
107+ anchors { left: parent.left; right: parent.right }
108+ activeFocusOnPress: false
109+ text: "Mode"
110+ model: ["staged", "windowed" ]
111+ onSelectedIndexChanged: {
112+ panel.mode = model[selectedIndex];
113+ keyboardAttached.checked = panel.mode == "windowed"
114+ }
115+ }
116+
78 Button {
117 Button {
79 Layout.fillWidth: true
118 Layout.fillWidth: true
80 text: panel.indicators.shown ? "Hide" : "Show"
119 text: panel.indicators.shown ? "Hide" : "Show"
@@ -109,25 +148,22 @@ IndicatorTest {
109 Layout.fillWidth: true
148 Layout.fillWidth: true
110 CheckBox {
149 CheckBox {
111 id: windowControlsCB
150 id: windowControlsCB
112− onClicked: PanelState.buttonsVisible = checked
151+ onClicked: PanelState.decorationsVisible = checked
113 }
152 }
114 Label {
153 Label {
115− text: "Show window controls"
154+ text: "Show window decorations"
155+ color: "white"
116 }
156 }
117 }
157 }
118
158
119 RowLayout {
159 RowLayout {
120 Layout.fillWidth: true
160 Layout.fillWidth: true
121 CheckBox {
161 CheckBox {
122− onClicked: {
162+ onClicked: PanelState.title = checked ? "Fake window title" : ""
123− if (checked)
124− PanelState.title = "Fake window title"
125− else
126− PanelState.title = ""
127− }
128 }
163 }
129 Label {
164 Label {
130 text: "Show fake window title"
165 text: "Show fake window title"
166+ color: "white"
131 }
167 }
132 }
168 }
133
169
@@ -137,6 +173,12 @@ IndicatorTest {
137 color: "black"
173 color: "black"
138 }
174 }
139
175
176+ Rectangle {
177+ Layout.preferredHeight: units.dp(1);
178+ Layout.fillWidth: true;
179+ color: "black"
180+ }
181+
140 Repeater {
182 Repeater {
141 model: root.originalModelData
183 model: root.originalModelData
142 RowLayout {
184 RowLayout {
@@ -147,6 +189,7 @@ IndicatorTest {
147 Label {
189 Label {
148 Layout.fillWidth: true
190 Layout.fillWidth: true
149 text: modelData["identifier"]
191 text: modelData["identifier"]
192+ color: "white"
150 }
193 }
151
194
152 CheckBox {
195 CheckBox {
@@ -155,6 +198,7 @@ IndicatorTest {
155 }
198 }
156 Label {
199 Label {
157 text: "visible"
200 text: "visible"
201+ color: "white"
158 }
202 }
159 }
203 }
160 }
204 }
@@ -165,7 +209,22 @@ IndicatorTest {
165 color: "black"
209 color: "black"
166 }
210 }
167
211
168− MouseTouchEmulationCheckbox { id: mouseEmulation }
212+ MouseTouchEmulationCheckbox {
213+ id: mouseEmulation
214+ color: "white"
215+ checked: panel.mode == "staged"
216+ }
217+
218+ RowLayout {
219+ Layout.fillWidth: true
220+ CheckBox {
221+ id: keyboardAttached
222+ }
223+ Label {
224+ text: "Keyboard Attached"
225+ color: "white"
226+ }
227+ }
169 }
228 }
170 }
229 }
171
230
@@ -191,22 +250,31 @@ IndicatorTest {
191 }
250 }
192
251
193 function init() {
252 function init() {
253+ panel.mode = "staged";
254+ mouseEmulation.checked = true;
194 panel.fullscreenMode = false;
255 panel.fullscreenMode = false;
195 callManager.foregroundCall = null;
256 callManager.foregroundCall = null;
196
257
197− panel.indicators.hide();
258+ PanelState.title = "";
198− // Wait for animation to complete
259+ PanelState.decorationsVisible = false;
199− tryCompare(panel.indicators.hideAnimation, "running", false);
200
260
201 // Wait for the indicators to get into position.
261 // Wait for the indicators to get into position.
202 // (switches between normal and fullscreen modes are animated)
262 // (switches between normal and fullscreen modes are animated)
203− var indicatorArea = findChild(panel, "indicatorArea");
263+ var panelArea = findChild(panel, "panelArea");
204− tryCompare(indicatorArea, "y", 0);
264+ tryCompare(panelArea, "y", 0);
205
265
206 backgroundPressedSpy.clear();
266 backgroundPressedSpy.clear();
207 compare(backgroundPressedSpy.valid, true);
267 compare(backgroundPressedSpy.valid, true);
208 windowControlButtonsSpy.clear();
268 windowControlButtonsSpy.clear();
209 compare(windowControlButtonsSpy.valid, true);
269 compare(windowControlButtonsSpy.valid, true);
270+
271+ waitForRendering(panel);
272+ }
273+
274+ function cleanup() {
275+ panel.indicators.hide();
276+ panel.applicationMenus.hide();
277+ waitForAllAnimationToComplete("initial");
210 }
278 }
211
279
212 function get_indicator_item(index) {
280 function get_indicator_item(index) {
@@ -216,8 +284,28 @@ IndicatorTest {
216 return indicatorItem;
284 return indicatorItem;
217 }
285 }
218
286
287+ function waitForAllAnimationToComplete(state) {
288+
289+ waitUntilTransitionsEnd(panel);
290+ tryCompare(panel.indicators.hideAnimation, "running", false);
291+ tryCompare(panel.indicators.showAnimation, "running", false);
292+ tryCompare(panel.indicators, "state", state);
293+
294+ for (var i = 0; i < root.originalModelData.length; i++) {
295+ var indicatorItem = get_indicator_item(i);
296+
297+ var itemState = findInvisibleChild(indicatorItem, "indicatorItemState");
298+ verify(itemState !== null);
299+
300+ waitUntilTransitionsEnd(itemState);
301+ }
302+
303+ tryCompare(panel.applicationMenus.hideAnimation, "running", false);
304+ tryCompare(panel.applicationMenus, "state", state);
305+ }
306+
219 function pullDownIndicatorsMenu() {
307 function pullDownIndicatorsMenu() {
220− var showDragHandle = findChild(panel, "showDragHandle");
308+ var showDragHandle = findChild(panel.indicators, "showDragHandle");
221 touchFlick(showDragHandle,
309 touchFlick(showDragHandle,
222 showDragHandle.width / 2,
310 showDragHandle.width / 2,
223 showDragHandle.height / 2,
311 showDragHandle.height / 2,
@@ -226,16 +314,22 @@ IndicatorTest {
226 tryCompare(panel.indicators, "fullyOpened", true);
314 tryCompare(panel.indicators, "fullyOpened", true);
227 }
315 }
228
316
229− function test_drag_show_data() {
317+ function pullDownApplicationsMenu() {
318+ var showDragHandle = findChild(panel.applicationMenus, "showDragHandle");
319+ touchFlick(showDragHandle,
320+ showDragHandle.width / 2,
321+ showDragHandle.height / 2,
322+ showDragHandle.width / 2,
323+ showDragHandle.height / 2 + (showDragHandle.autoCompleteDragThreshold * 1.1));
324+ tryCompare(panel.applicationMenus, "fullyOpened", true);
325+ }
326+
327+ function test_drag_indicator_item_down_shows_menu_data() {
230 return [
328 return [
231− { tag: "pinned", fullscreen: false, call: null,
329+ { tag: "pinned", fullscreen: false, call: null, y: 0 },
232− indicatorY: 0 },
233− { tag: "fullscreen", fullscreen: true, call: null,
330+ { tag: "fullscreen", fullscreen: true, call: null, y: -panel.minimizedPanelHeight },
234− indicatorY: -panel.panelAndSeparatorHeight },
235− { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
331+ { tag: "pinned-callActive", fullscreen: false, call: phoneCall, y: 0},
236− indicatorY: 0},
237− { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
332+ { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall, y: -panel.minimizedPanelHeight }
238− indicatorY: -panel.panelAndSeparatorHeight }
239 ];
333 ];
240 }
334 }
241
335
@@ -243,24 +337,26 @@ IndicatorTest {
243 // indicators, first by running the hint animation, then after dragging down will
337 // indicators, first by running the hint animation, then after dragging down will
244 // expose more of the panel, binding it to the selected indicator and opening it's menu.
338 // expose more of the panel, binding it to the selected indicator and opening it's menu.
245 // Tested from first Y pixel to check for swipe from offscreen.
339 // Tested from first Y pixel to check for swipe from offscreen.
246− function test_drag_show(data) {
340+ function test_drag_indicator_item_down_shows_menu(data) {
247 panel.fullscreenMode = data.fullscreen;
341 panel.fullscreenMode = data.fullscreen;
248 callManager.foregroundCall = data.call;
342 callManager.foregroundCall = data.call;
249
343
250− var indicatorRow = findChild(panel.indicators, "indicatorItemRow");
344+ var panelRow = findChild(panel.indicators, "panelItemRow");
251− verify(indicatorRow !== null);
345+ verify(panelRow !== null);
252
346
253 var menuContent = findChild(panel.indicators, "menuContent");
347 var menuContent = findChild(panel.indicators, "menuContent");
254 verify(menuContent !== null);
348 verify(menuContent !== null);
255
349
256− var indicatorArea = findChild(panel, "indicatorArea");
350+ var panelArea = findChild(panel, "panelArea");
257− verify(indicatorArea !== null);
351+ verify(panelArea !== null);
258
352
259 // Wait for the indicators to get into position.
353 // Wait for the indicators to get into position.
260 // (switches between normal and fullscreen modes are animated)
354 // (switches between normal and fullscreen modes are animated)
261− tryCompareFunction(function() { return indicatorArea.y }, data.indicatorY);
355+ tryCompareFunction(function() { return panelArea.y }, data.y);
262
356
263 for (var i = 0; i < root.originalModelData.length; i++) {
357 for (var i = 0; i < root.originalModelData.length; i++) {
358+ waitForAllAnimationToComplete("initial");
359+
264 var indicatorItem = get_indicator_item(i);
360 var indicatorItem = get_indicator_item(i);
265
361
266 var startXPosition = panel.mapFromItem(indicatorItem, indicatorItem.width / 2, 0).x;
362 var startXPosition = panel.mapFromItem(indicatorItem, indicatorItem.width / 2, 0).x;
@@ -268,7 +364,7 @@ IndicatorTest {
268 touchFlick(panel,
364 touchFlick(panel,
269 startXPosition, 0,
365 startXPosition, 0,
270 startXPosition, panel.height,
366 startXPosition, panel.height,
271− true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
367+ true /* beginTouch */, false /* endTouch */, 1, 15);
272
368
273 // Indicators height should follow the drag, and therefore increase accordingly.
369 // Indicators height should follow the drag, and therefore increase accordingly.
274 // They should be at least half-way through the screen
370 // They should be at least half-way through the screen
@@ -278,105 +374,152 @@ IndicatorTest {
278
374
279 touchRelease(panel, startXPosition, panel.height);
375 touchRelease(panel, startXPosition, panel.height);
280
376
281− compare(indicatorRow.currentItemIndex, i, "Indicator item should be activated at position " + i);
377+ tryCompare(panelRow, "currentItemIndex", i, undefined, "Indicator item should be activated at position " + i);
282 compare(menuContent.currentMenuIndex, i, "Menu conetent should be activated for item at position " + i);
378 compare(menuContent.currentMenuIndex, i, "Menu conetent should be activated for item at position " + i);
283
379
284 // init for next indicatorItem
380 // init for next indicatorItem
285 panel.indicators.hide();
381 panel.indicators.hide();
286− tryCompare(panel.indicators.hideAnimation, "running", false);
287− tryCompare(panel.indicators, "state", "initial");
288 }
382 }
289 }
383 }
290
384
291− function test_drag_hide_data() {
385+ function test_drag_panel_handle_up_hides_menu_data() {
292 return [
386 return [
293− { tag: "pinned", fullscreen: false, call: null,
387+ { tag: "indicators-pinned", section: panel.indicators, fullscreen: false, call: null },
294− indicatorY: 0 },
295− { tag: "fullscreen", fullscreen: true, call: null,
388+ { tag: "indicators-fullscreen", section: panel.indicators, fullscreen: true, call: null },
296− indicatorY: -panel.panelAndSeparatorHeight },
389+ { tag: "indicators-pinned-callActive", section: panel.indicators, fullscreen: false, call: phoneCall },
297− { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
390+ { tag: "indicators-fullscreen-callActive", section: panel.indicators, fullscreen: true, call: phoneCall },
298− indicatorY: 0},
391+ { tag: "appMenus-pinned", section: panel.applicationMenus, fullscreen: false, call: null },
392+ { tag: "appMenus-fullscreen", section: panel.applicationMenus, fullscreen: true, call: null },
299− { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
393+ { tag: "appMenus-pinned-callActive", section: panel.applicationMenus, fullscreen: false, call: phoneCall },
300− indicatorY: -panel.panelAndSeparatorHeight }
394+ { tag: "appMenus-fullscreen-callActive", section: panel.applicationMenus, fullscreen: true, call: phoneCall }
301 ];
395 ];
302 }
396 }
303
397
304 // Dragging the shown indicators up from bottom of panel will hide the indicators
398 // Dragging the shown indicators up from bottom of panel will hide the indicators
305 // Tested from last Y pixel to check for swipe from offscreen.
399 // Tested from last Y pixel to check for swipe from offscreen.
306− function test_drag_hide(data) {
400+ function test_drag_panel_handle_up_hides_menu(data) {
307 panel.fullscreenMode = data.fullscreen;
401 panel.fullscreenMode = data.fullscreen;
308 callManager.foregroundCall = data.call;
402 callManager.foregroundCall = data.call;
309
403
310− var indicatorRow = findChild(panel.indicators, "indicatorItemRow");
404+ var panelRow = findChild(data.section, "panelItemRow");
311− verify(indicatorRow !== null);
405+ verify(panelRow !== null);
312
406
313− var menuContent = findChild(panel.indicators, "menuContent");
407+ var panelArea = findChild(panel, "panelArea");
314− verify(menuContent !== null);
408+ verify(panelArea !== null);
315−
316− var indicatorArea = findChild(panel, "indicatorArea");
317− verify(indicatorArea !== null);
318
409
319 // Wait for the indicators to get into position.
410 // Wait for the indicators to get into position.
320 // (switches between normal and fullscreen modes are animated)
411 // (switches between normal and fullscreen modes are animated)
321− tryCompareFunction(function() { return indicatorArea.y }, data.indicatorY);
412+ tryCompareFunction(function() { return panelArea.y }, data.fullscreen ? -panel.minimizedPanelHeight : 0);
322
413
323− panel.indicators.show();
414+ data.section.show();
324− tryCompare(panel.indicators.showAnimation, "running", false);
415+ tryCompare(data.section.showAnimation, "running", false);
325− tryCompare(panel.indicators, "unitProgress", 1);
416+ tryCompare(data.section, "unitProgress", 1);
326
417
327− touchFlick(panel.indicators,
418+ touchFlick(data.section,
328− panel.indicators.width / 2, panel.height,
419+ data.section.width / 2, panel.height,
329− panel.indicators.width / 2, 0,
420+ data.section.width / 2, 0,
330 true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
421 true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
331
422
332 // Indicators height should follow the drag, and therefore increase accordingly.
423 // Indicators height should follow the drag, and therefore increase accordingly.
333 // They should be at least half-way through the screen
424 // They should be at least half-way through the screen
334 tryCompareFunction(
425 tryCompareFunction(
335− function() {return panel.indicators.height <= panel.height * 0.5},
426+ function() {return data.section.height <= panel.height * 0.5},
336 true);
427 true);
337
428
338− touchRelease(panel.indicators, panel.indicators.width / 2, 0);
429+ touchRelease(data.section, data.section.width / 2, 0);
339
430
340− tryCompare(panel.indicators.hideAnimation, "running", true);
431+ tryCompare(data.section.hideAnimation, "running", true);
341− tryCompare(panel.indicators.hideAnimation, "running", false);
432+ tryCompare(data.section.hideAnimation, "running", false);
342− tryCompare(panel.indicators, "state", "initial");
433+ tryCompare(data.section, "state", "initial");
343 }
434 }
344
435
345 function test_hint_data() {
436 function test_hint_data() {
346 return [
437 return [
347− { tag: "normal", fullscreen: false, call: null, hintExpected: true},
438+ { tag: "indicators-normal", section: panel.indicators, fullscreen: false, call: null, hintExpected: true},
348− { tag: "fullscreen", fullscreen: true, call: null, hintExpected: false},
439+ { tag: "indicators-fullscreen", section: panel.indicators, fullscreen: true, call: null, hintExpected: false},
440+ { tag: "indicators-callActive", section: panel.indicators, fullscreen: false, call: phoneCall, hintExpected: false},
441+ { tag: "appMenus-normal", section: panel.applicationMenus, fullscreen: false, call: null, hintExpected: true},
442+ { tag: "appMenus-fullscreen", section: panel.applicationMenus, fullscreen: true, call: null, hintExpected: false},
349− { tag: "call hint", fullscreen: false, call: phoneCall, hintExpected: false},
443+ { tag: "appMenus-callActive", section: panel.applicationMenus, fullscreen: false, call: phoneCall, hintExpected: false},
350 ];
444 ];
351 }
445 }
352
446
353 function test_hint(data) {
447 function test_hint(data) {
448+ PanelState.title = "Fake Title"
354 panel.fullscreenMode = data.fullscreen;
449 panel.fullscreenMode = data.fullscreen;
355 callManager.foregroundCall = data.call;
450 callManager.foregroundCall = data.call;
356
451
357 if (data.fullscreen) {
452 if (data.fullscreen) {
358 // Wait for the indicators to get into position.
453 // Wait for the indicators to get into position.
359 // (switches between normal and fullscreen modes are animated)
454 // (switches between normal and fullscreen modes are animated)
360− var indicatorArea = findChild(panel, "indicatorArea");
455+ var panelArea = findChild(panel, "panelArea");
361− tryCompare(indicatorArea, "y", -panel.panelHeight);
456+ tryCompare(panelArea, "y", -panel.minimizedPanelHeight);
362 }
457 }
363
458
364− var indicatorItem = get_indicator_item(0);
365− var mappedPosition = root.mapFromItem(indicatorItem, indicatorItem.width / 2, indicatorItem.height / 2);
459+ var mappedPosition = root.mapFromItem(data.section,
460+ data.section.barWidth / 2, data.section.minimizedPanelHeight / 2);
461+
462+ touchPress(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
366
463
367− touchPress(panel, mappedPosition.x, panel.indicators.minimizedPanelHeight / 2);
464+ var showDragHandle = findChild(data.section, "showDragHandle")
465+ var hintingAnimation = findInvisibleChild(showDragHandle, "hintingAnimation");
466+ verify(hintingAnimation);
368
467
369− // Give some time for a hint animation to change things, if any
468+ compare(hintingAnimation.running, data.hintExpected)
370− wait(500);
469+ tryCompare(hintingAnimation, "running", false); // wait till animation completes
371
470
372 // no hint animation when fullscreen
471 // no hint animation when fullscreen
373− compare(panel.indicators.fullyClosed, !data.hintExpected, "Indicator should be fully closed");
374− compare(panel.indicators.partiallyOpened, data.hintExpected, "Indicator should be partialy opened");
472+ compare(data.section.partiallyOpened, data.hintExpected, "Indicator should be partialy opened");
375− compare(panel.indicators.fullyOpened, false, "Indicator should not be fully opened");
473+ compare(data.section.fullyOpened, false, "Indicator should not be fully opened");
376
474
377 touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
475 touchRelease(panel, mappedPosition.x, panel.minimizedPanelHeight / 2);
378 }
476 }
379
477
478+ function test_drag_applicationMenu_down_shows_menu_data() {
479+ return [
480+ { tag: "normal", fullscreen: false, call: null, hintExpected: true},
481+ { tag: "fullscreen", fullscreen: true, call: null, hintExpected: false},
482+ { tag: "callActive", fullscreen: false, call: phoneCall, hintExpected: false}
483+ ];
484+ }
485+
486+ // Dragging the application menu will gradually expose the
487+ // menus, first by running the hint animation, then after dragging down will
488+ // expose more of the panel. Releasing the touch will complete the show.
489+ function test_drag_applicationMenu_down_shows_menu(data) {
490+ PanelState.title = "Fake Title";
491+ panel.fullscreenMode = data.fullscreen;
492+ callManager.foregroundCall = data.call;
493+
494+ var panelRow = findChild(panel.indicators, "panelItemRow");
495+ verify(panelRow !== null);
496+
497+ var menuContent = findChild(panel.indicators, "menuContent");
498+ verify(menuContent !== null);
499+
500+ var panelArea = findChild(panel, "panelArea");
501+ verify(panelArea !== null);
502+
503+ // Wait for the indicators to get into position.
504+ // (switches between normal and fullscreen modes are animated)
505+ tryCompareFunction(function() { return panelArea.y }, data.fullscreen ? -panel.minimizedPanelHeight : 0);
506+
507+ touchFlick(panel,
508+ units.gu(1), 0,
509+ units.gu(1), panel.height,
510+ true /* beginTouch */, false /* endTouch */, units.gu(5), 15);
511+
512+ // Indicators height should follow the drag, and therefore increase accordingly.
513+ // They should be at least half-way through the screen
514+ tryCompareFunction(
515+ function() {return panel.applicationMenus.height >= panel.height * 0.5},
516+ true);
517+
518+ touchRelease(panel, units.gu(1), panel.height);
519+
520+ tryCompare(panel.applicationMenus, "fullyOpened", true)
521+ }
522+
380 /* Checks that no input reaches items behind the indicator bar.
523 /* Checks that no input reaches items behind the indicator bar.
381 Ie., the indicator bar should eat all input events that hit it.
524 Ie., the indicator bar should eat all input events that hit it.
382 */
525 */
@@ -385,20 +528,20 @@ IndicatorTest {
385 // that it doesn't have a "weak spot" from where taps pass through.
528 // that it doesn't have a "weak spot" from where taps pass through.
386 var numTaps = 5;
529 var numTaps = 5;
387 var stepLength = (panel.width / (numTaps + 1));
530 var stepLength = (panel.width / (numTaps + 1));
388− var tapY = panel.indicators.minimizedPanelHeight / 2;
531+ var tapY = panel.minimizedPanelHeight / 2;
389 for (var i = 1; i <= numTaps; ++i) {
532 for (var i = 1; i <= numTaps; ++i) {
390 tap(panel, stepLength * i, tapY);
533 tap(panel, stepLength * i, tapY);
391 tryCompare(panel.indicators, "fullyClosed", true);
534 tryCompare(panel.indicators, "fullyClosed", true);
392 }
535 }
393 }
536 }
394
537
395− function test_darkenedAreaEatsAllEvents() {
538+ function test_darkenedAreaEatsAllIndicatorEvents() {
396
539
397 // The center of the area not covered by the indicators menu
540 // The center of the area not covered by the indicators menu
398 // Ie, the visible darkened area behind the menu
541 // Ie, the visible darkened area behind the menu
399 var touchPosX = (panel.width - panel.indicators.width) / 2
542 var touchPosX = (panel.width - panel.indicators.width) / 2
400− var touchPosY = panel.indicators.minimizedPanelHeight +
543+ var touchPosY = panel.minimizedPanelHeight +
401− ((panel.height - panel.indicators.minimizedPanelHeight) / 2)
544+ ((panel.height - panel.minimizedPanelHeight) / 2)
402
545
403 // input goes through while the indicators menu is closed
546 // input goes through while the indicators menu is closed
404 tryCompare(panel.indicators, "fullyClosed", true);
547 tryCompare(panel.indicators, "fullyClosed", true);
@@ -434,6 +577,50 @@ IndicatorTest {
434 compare(backgroundPressedSpy.count, 2);
577 compare(backgroundPressedSpy.count, 2);
435 }
578 }
436
579
580+ function test_darkenedAreaEatsAllApplicationMenuEvents() {
581+ PanelState.title = "Fake Title"
582+
583+ // The center of the area not covered by the indicators menu
584+ // Ie, the visible darkened area behind the menu
585+ var touchPosX = panel.applicationMenus.width + (panel.width - panel.applicationMenus.width) / 2
586+ var touchPosY = panel.minimizedPanelHeight +
587+ ((panel.height - panel.minimizedPanelHeight) / 2)
588+
589+ // input goes through while the indicators menu is closed
590+ tryCompare(panel.applicationMenus, "fullyClosed", true);
591+ compare(backgroundPressedSpy.count, 0);
592+ tap(panel, touchPosX, touchPosY);
593+ compare(backgroundPressedSpy.count, 2);
594+
595+ pullDownApplicationsMenu();
596+
597+ // Darkened area eats input when the indicators menu is fully opened
598+ tap(panel, touchPosX, touchPosY);
599+ compare(backgroundPressedSpy.count, 2);
600+ backgroundPressedSpy.clear();
601+
602+ // And should continue to eat inpunt until the indicators menu is fully closed
603+ wait(10);
604+ while (!panel.applicationMenus.fullyClosed) {
605+ tap(panel, touchPosX, touchPosY);
606+
607+ // it could have got fully closed during the tap
608+ // so we have to double check here
609+ if (!panel.applicationMenus.fullyClosed) {
610+ compare(backgroundPressedSpy.count, 0);
611+ }
612+
613+ // let the animation go a bit further
614+ wait(50);
615+ }
616+
617+ // Now that's fully closed, input should go through again
618+ backgroundPressedSpy.clear();
619+ tap(panel, touchPosX, touchPosY);
620+ compare(backgroundPressedSpy.count, 2);
621+ }
622+
623+
437 /*
624 /*
438 Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1439318
625 Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1439318
439 When the panel is in fullscreen mode and the user taps near the top edge,
626 When the panel is in fullscreen mode and the user taps near the top edge,
@@ -441,14 +628,12 @@ IndicatorTest {
441 panel.
628 panel.
442 */
629 */
443 function test_tapNearTopEdgeWithPanelInFullscreenMode() {
630 function test_tapNearTopEdgeWithPanelInFullscreenMode() {
444− var indicatorArea = findChild(panel, "indicatorArea");
631+ var panelArea = findChild(panel, "panelArea");
445− verify(indicatorArea);
446− var panelPriv = findInvisibleChild(panel, "panelPriv");
447− verify(panelPriv);
632+ verify(panelArea);
448
633
449 panel.fullscreenMode = true;
634 panel.fullscreenMode = true;
450 // wait until if finishes hiding itself
635 // wait until if finishes hiding itself
451− tryCompare(indicatorArea, "y", -panelPriv.indicatorHeight);
636+ tryCompare(panelArea, "y", -panel.minimizedPanelHeight);
452
637
453 compare(panel.indicators.shown, false);
638 compare(panel.indicators.shown, false);
454 verify(panel.indicators.fullyClosed);
639 verify(panel.indicators.fullyClosed);
@@ -479,7 +664,7 @@ IndicatorTest {
479
664
480 mouseClick(panel.indicators,
665 mouseClick(panel.indicators,
481 panel.indicators.width / 2,
666 panel.indicators.width / 2,
482− panel.indicators.minimizedPanelHeight / 2);
667+ panel.minimizedPanelHeight / 2);
483
668
484 compare(panel.indicators.shown, false);
669 compare(panel.indicators.shown, false);
485 verify(panel.indicators.fullyClosed);
670 verify(panel.indicators.fullyClosed);
@@ -494,7 +679,7 @@ IndicatorTest {
494
679
495 mouseClick(panel.indicators,
680 mouseClick(panel.indicators,
496 panel.indicators.width / 2,
681 panel.indicators.width / 2,
497− panel.indicators.minimizedPanelHeight / 2);
682+ panel.minimizedPanelHeight / 2);
498
683
499 compare(panel.indicators.shown, true);
684 compare(panel.indicators.shown, true);
500 tryCompare(panel.indicators, "fullyOpened", true);
685 tryCompare(panel.indicators, "fullyOpened", true);
@@ -510,13 +695,109 @@ IndicatorTest {
510
695
511 // https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1611959
696 // https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1611959
512 function test_windowControlButtonsFittsLaw() {
697 function test_windowControlButtonsFittsLaw() {
513− var buttonsVisible = PanelState.buttonsVisible;
698+ panel.mode = "windowed";
699+ var windowControlArea = findChild(panel, "windowControlArea");
700+ verify(windowControlArea, "Window control area should have been created in windowed mode")
701+
514− PanelState.buttonsVisible = true;
702+ PanelState.decorationsVisible = true;
515 // click in very topleft corner and verify the close button got clicked too
703 // click in very topleft corner and verify the close button got clicked too
516 mouseMove(panel, 0, 0);
704 mouseMove(panel, 0, 0);
517 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
705 mouseClick(panel, 0, 0, undefined /*button*/, undefined /*modifiers*/, 100 /*short delay*/);
518 compare(windowControlButtonsSpy.count, 1);
706 compare(windowControlButtonsSpy.count, 1);
519− PanelState.buttonsVisible = buttonsVisible;
707+ }
708+
709+ function test_hidingKeyboardIndicator_data() {
710+ return [
711+ { tag: "No keyboard, no keymap", keyboard: false, keymaps: [], hidden: true },
712+ { tag: "No keyboard, one keymap", keyboard: false, keymaps: ["us"], hidden: true },
713+ { tag: "No keyboard, 2 keymaps", keyboard: false, keymaps: ["us", "cs"], hidden: true },
714+ { tag: "Keyboard, no keymap", keyboard: true, keymaps: [], hidden: true },
715+ { tag: "Keyboard, one keymap", keyboard: true, keymaps: ["us"], hidden: true },
716+ { tag: "Keyboard, 2 keymaps", keyboard: true, keymaps: ["us", "cs"], hidden: false }
717+ ];
718+ }
719+
720+ function test_hidingKeyboardIndicator(data) {
721+ var item = findChild(panel, "indicator-keyboard-panelItem");
722+ AccountsService.keymaps = data.keymaps;
723+ if (data.keyboard) {
724+ MockInputDeviceBackend.addMockDevice("/indicator_kbd0", InputInfo.Keyboard);
725+ } else {
726+ MockInputDeviceBackend.removeDevice("/indicator_kbd0");
727+ }
728+
729+ compare(item.hidden, data.hidden);
730+ }
731+
732+ function test_visibleIndicators_data() {
733+ return [
734+ { visible: [true, false, true, false, true, true, false, true] },
735+ { visible: [false, false, false, false, false, false, true, false] }
736+ ];
737+ }
738+
739+ function test_visibleIndicators(data) {
740+ for (var i = 0; i < data.visible.length; i++) {
741+ var visible = data.visible[i];
742+ root.setIndicatorVisible(i, visible);
743+
744+ var dataItem = findChild(panel, root.originalModelData[i]["identifier"] + "-panelItem");
745+ tryCompare(dataItem, "opacity", visible ? 1.0 : 0.0);
746+ tryCompareFunction(function() { return dataItem.width > 0.0; }, visible);
747+ }
748+
749+ panel.indicators.show();
750+ tryCompare(panel.indicators.showAnimation, "running", false);
751+ tryCompare(panel.indicators, "unitProgress", 1);
752+
753+ for (var i = 0; i < data.visible.length; i++) {
754+ root.setIndicatorVisible(i, data.visible[i]);
755+
756+ var dataItem = findChild(panel, root.originalModelData[i]["identifier"] + "-panelItem");
757+ tryCompareFunction(function() { return dataItem.opacity > 0.0; }, true);
758+ tryCompareFunction(function() { return dataItem.width > 0.0; }, true);
759+ }
760+ }
761+
762+ function test_stagedApplicationMenuBarShowOnMouseHover() {
763+ PanelState.title = "Fake Title";
764+ panel.mode = "staged";
765+ mouseEmulation.checked = false;
766+
767+ var appTitle = findChild(panel.applicationMenus, "panelTitle"); verify(appTitle);
768+ var appMenuRow = findChild(panel.applicationMenus, "panelRow"); verify(appMenuRow);
769+ var appMenuBar = findChild(panel, "menuBar"); verify(appMenuBar);
770+
771+ tryCompare(appTitle, "visible", true, undefined, "App title should be visible");
772+ tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should not be visible");
773+
774+ mouseMove(panel, panel.width/2, panel.panelHeight);
775+
776+ tryCompare(appTitle, "visible", false, undefined, "App title should not be visible on mouse hover");
777+ tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover");
778+ }
779+
780+ function test_windowedApplicationMenuShowOnMouseHoverWhenDecorationsShown() {
781+ PanelState.title = "Fake Title";
782+ panel.mode = "windowed";
783+ mouseEmulation.checked = false;
784+
785+ var appTitle = findChild(panel.applicationMenus, "panelTitle"); verify(appTitle);
786+ var appMenuRow = findChild(panel.applicationMenus, "panelRow"); verify(appMenuRow);
787+ var appMenuBar = findChild(panel, "menuBar"); verify(appMenuBar);
788+
789+ tryCompare(appTitle, "visible", true, undefined, "App title should be visible");
790+ tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should not be visible");
791+
792+ mouseMove(panel, panel.width/2, panel.panelHeight);
793+
794+ tryCompare(appTitle, "visible", true, undefined, "App title should still be visible on mouse hover when panel decorations are not visible");
795+ tryCompare(appMenuBar, "visible", false, undefined, "App menu bar should be visible on mouse hover when panel decorations are not visible");
796+
797+ PanelState.decorationsVisible = true;
798+
799+ tryCompare(appTitle, "visible", false, undefined, "App title should still be visible on mouse hover when panel decorations are visible");
800+ tryCompare(appMenuBar, "visible", true, undefined, "App menu bar should be visible on mouse hover when panel decorations not visible");
520 }
801 }
521 }
802 }
522}
803}
tests/qmltests/Panel/tst_IndicatorsBar.qml → tests/qmltests/Panel/tst_PanelBar.qmlrenamed+24−33
@@ -21,8 +21,9 @@ import "../../../qml/Panel"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
23import Unity.Indicators 0.1 as Indicators
23import Unity.Indicators 0.1 as Indicators
24+import "../../../qml/Panel/Indicators"
24
25
25−IndicatorTest {
26+PanelTest {
26 id: root
27 id: root
27 width: units.gu(100)
28 width: units.gu(100)
28 height: units.gu(40)
29 height: units.gu(40)
@@ -43,14 +44,31 @@ IndicatorTest {
43 anchors.fill: indicatorsBar
44 anchors.fill: indicatorsBar
44 }
45 }
45
46
46− IndicatorsBar {
47+ PanelBar {
47 id: indicatorsBar
48 id: indicatorsBar
48 height: expanded ? units.gu(7) : units.gu(3)
49 height: expanded ? units.gu(7) : units.gu(3)
49 width: units.gu(widthSlider.value)
50 width: units.gu(widthSlider.value)
50 anchors.centerIn: parent
51 anchors.centerIn: parent
51− indicatorsModel: root.indicatorsModel
52+ model: root.indicatorsModel
52 interactive: expanded && height === units.gu(7)
53 interactive: expanded && height === units.gu(7)
53
54
55+ rowItemDelegate: Item {
56+ property int ownIndex: index
57+ objectName: model.identifier + "-panelItem"
58+
59+ implicitWidth: indicatorsBar.expanded ? units.gu(5) : units.gu(3)
60+ height: parent.height
61+
62+ Rectangle {
63+ anchors {
64+ fill: parent
65+ margins: 2
66+ }
67+ color: "red"
68+ Label { anchors.centerIn: parent; text: ownIndex }
69+ }
70+ }
71+
54 Behavior on height {
72 Behavior on height {
55 NumberAnimation {
73 NumberAnimation {
56 id: heightAnimation
74 id: heightAnimation
@@ -171,7 +189,7 @@ IndicatorTest {
171 var dataItem = findChild(indicatorsBar, root.originalModelData[lastItemIndex]["identifier"] + "-panelItem");
189 var dataItem = findChild(indicatorsBar, root.originalModelData[lastItemIndex]["identifier"] + "-panelItem");
172 verify(dataItem !== null);
190 verify(dataItem !== null);
173
191
174− var row = findChild(indicatorsBar, "indicatorItemRow");
192+ var row = findChild(indicatorsBar, "panelItemRow");
175 // test will not work without these conditions
193 // test will not work without these conditions
176 verify(row.width >= indicatorsBar.width + dataItem.width);
194 verify(row.width >= indicatorsBar.width + dataItem.width);
177
195
@@ -198,36 +216,9 @@ IndicatorTest {
198 skip("Out of bounds");
216 skip("Out of bounds");
199 }
217 }
200 mouseClick(dataItem);
218 mouseClick(dataItem);
201− verify(dataItem.selected === true);
202− }
203−
204− function test_visibleIndicators_data() {
205− return [
206− { visible: [true, false, true, false, true, true, false, true] },
207− { visible: [false, false, false, false, false, false, true, false] }
208− ];
209− }
210−
211− function test_visibleIndicators(data) {
212− for (var i = 0; i < data.visible.length; i++) {
213− var visible = data.visible[i];
214− root.setIndicatorVisible(i, visible);
215
219
216− var dataItem = findChild(indicatorsBar, root.originalModelData[i]["identifier"] + "-panelItem");
220+ var row = findChild(indicatorsBar, "panelItemRow");
217− tryCompare(dataItem, "opacity", visible ? 1.0 : 0.0);
221+ compare(dataItem, row.currentItem, "Item should be selected");
218− tryCompareFunction(function() { return dataItem.width > 0.0; }, visible);
219− }
220−
221− indicatorsBar.expanded = true;
222− wait_for_expansion_to_settle();
223−
224− for (var i = 0; i < data.visible.length; i++) {
225− root.setIndicatorVisible(i, data.visible[i]);
226−
227− var dataItem = findChild(indicatorsBar, root.originalModelData[i]["identifier"] + "-panelItem");
228− tryCompareFunction(function() { return dataItem.opacity > 0.0; }, true);
229− tryCompareFunction(function() { return dataItem.width > 0.0; }, true);
230− }
231 }
222 }
232
223
233 // Rough test that resizing the IndicatorsBar has the items reposition correctly
224 // Rough test that resizing the IndicatorsBar has the items reposition correctly
tests/qmltests/Panel/tst_IndicatorItemRow.qml → tests/qmltests/Panel/tst_PanelItemRow.qmlrenamed+36−32
@@ -17,14 +17,13 @@
17import QtQuick 2.4
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import QtQuick.Layouts 1.1
19import QtTest 1.0
19import QtTest 1.0
20−import "../../../qml/Panel"
21import Ubuntu.Components 1.3
20import Ubuntu.Components 1.3
22import Unity.Test 0.1 as UT
21import Unity.Test 0.1 as UT
23−import Unity.Indicators 0.1 as Indicators
24import AccountsService 0.1
22import AccountsService 0.1
25import Unity.InputInfo 0.1
23import Unity.InputInfo 0.1
24+import "../../../qml/Panel"
26
25
27−IndicatorTest {
26+PanelTest {
28 id: root
27 id: root
29 width: units.gu(120)
28 width: units.gu(120)
30 height: units.gu(40)
29 height: units.gu(40)
@@ -45,11 +44,11 @@ IndicatorTest {
45 anchors.fill: indicatorsRow
44 anchors.fill: indicatorsRow
46 }
45 }
47
46
48− IndicatorItemRow {
47+ PanelItemRow {
49 id: indicatorsRow
48 id: indicatorsRow
50 height: expanded ? units.gu(7) : units.gu(3)
49 height: expanded ? units.gu(7) : units.gu(3)
51 anchors.centerIn: parent
50 anchors.centerIn: parent
52− indicatorsModel: root.indicatorsModel
51+ model: root.indicatorsModel
53 enableLateralChanges: ma.pressed
52 enableLateralChanges: ma.pressed
54
53
55 Behavior on height {
54 Behavior on height {
@@ -59,6 +58,23 @@ IndicatorTest {
59 }
58 }
60 }
59 }
61
60
61+ delegate: Item {
62+ property int ownIndex: index
63+ objectName: model.identifier + "-panelItem"
64+
65+ implicitWidth: indicatorsRow.expanded ? units.gu(6) : units.gu(3)
66+ height: parent.height
67+
68+ Rectangle {
69+ anchors {
70+ fill: parent
71+ margins: 2
72+ }
73+ color: "red"
74+ Label { anchors.centerIn: parent; text: ownIndex }
75+ }
76+ }
77+
62 MouseArea {
78 MouseArea {
63 id: ma
79 id: ma
64 anchors.fill: parent
80 anchors.fill: parent
@@ -116,7 +132,7 @@ IndicatorTest {
116 }
132 }
117
133
118 UT.UnityTestCase {
134 UT.UnityTestCase {
119− name: "IndicatorItemRow"
135+ name: "PanelItemRow"
120 when: windowShown
136 when: windowShown
121
137
122 function init() {
138 function init() {
@@ -161,25 +177,36 @@ IndicatorTest {
161 for (i = data.remove.length-1; i >= 0; i--) {
177 for (i = data.remove.length-1; i >= 0; i--) {
162 removeIndicator(data.remove[i]);
178 removeIndicator(data.remove[i]);
163 }
179 }
180+ wait(50);
164
181
165 // test removals
182 // test removals
166 for (i = 0; i < root.originalModelData.length; i++) {
183 for (i = 0; i < root.originalModelData.length; i++) {
167− item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem");
184+ item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem", 0);
185+ console.log(item, i, root.originalModelData[i]["identifier"])
168
186
169− verify(data.remove.indexOf(i) !== -1 ? (item === null) : (item !== null));
187+ if (data.remove.indexOf(i) !== -1) {
188+ verify(item === null, "removed item should not be present");
189+ } else {
190+ verify(item !== null, "items not removed item should still be present");
191+ }
170 }
192 }
193+ console.log("2")
171
194
172 // test insertion
195 // test insertion
173 for (i = 0; i < data.remove.length; i++) {
196 for (i = 0; i < data.remove.length; i++) {
174 insertIndicator(data.remove[i]);
197 insertIndicator(data.remove[i]);
175 }
198 }
176
199
200+ console.log("3")
201+
177 for (i = 0; i < root.originalModelData.length; i++) {
202 for (i = 0; i < root.originalModelData.length; i++) {
178− item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem");
203+ item = findChild(indicatorsRow, root.originalModelData[i]["identifier"] + "-panelItem", 100);
179 verify(item);
204 verify(item);
180
205
181 compare(item.ownIndex, i, "Item at incorrect index");
206 compare(item.ownIndex, i, "Item at incorrect index");
182 }
207 }
208+
209+ console.log("4")
183 }
210 }
184
211
185 function test_validCurrentItem_data() {
212 function test_validCurrentItem_data() {
@@ -287,28 +314,5 @@ IndicatorTest {
287 // should go back to 0
314 // should go back to 0
288 tryCompare(findChild(indicatorsRow, "highlight"), "highlightCenterOffset", 0);
315 tryCompare(findChild(indicatorsRow, "highlight"), "highlightCenterOffset", 0);
289 }
316 }
290−
291− function test_hidingKeyboardIndicator_data() {
292− return [
293− { tag: "No keyboard, no keymap", keyboard: false, keymaps: [], hidden: true },
294− { tag: "No keyboard, one keymap", keyboard: false, keymaps: ["us"], hidden: true },
295− { tag: "No keyboard, 2 keymaps", keyboard: false, keymaps: ["us", "cs"], hidden: true },
296− { tag: "Keyboard, no keymap", keyboard: true, keymaps: [], hidden: true },
297− { tag: "Keyboard, one keymap", keyboard: true, keymaps: ["us"], hidden: true },
298− { tag: "Keyboard, 2 keymaps", keyboard: true, keymaps: ["us", "cs"], hidden: false }
299− ];
300− }
301−
302− function test_hidingKeyboardIndicator(data) {
303− var item = findChild(indicatorsRow, "indicator-keyboard-panelItem");
304− AccountsService.keymaps = data.keymaps;
305− if (data.keyboard) {
306− MockInputDeviceBackend.addMockDevice("/indicator_kbd0", InputInfo.Keyboard);
307− } else {
308− MockInputDeviceBackend.removeDevice("/indicator_kbd0");
309− }
310−
311− compare(item.hidden, data.hidden);
312− }
313 }
317 }
314}
318}
tests/qmltests/Panel/tst_IndicatorsMenu.qml → tests/qmltests/Panel/tst_PanelMenu.qmlrenamed+32−14
@@ -20,9 +20,8 @@ import QtTest 1.0
20import "../../../qml/Panel"
20import "../../../qml/Panel"
21import Ubuntu.Components 1.3
21import Ubuntu.Components 1.3
22import Unity.Test 0.1 as UT
22import Unity.Test 0.1 as UT
23−import Unity.Indicators 0.1 as Indicators
24
23
25−IndicatorTest {
24+PanelTest {
26 id: root
25 id: root
27 width: units.gu(80)
26 width: units.gu(80)
28 height: units.gu(71)
27 height: units.gu(71)
@@ -48,7 +47,7 @@ IndicatorTest {
48 id: itemArea
47 id: itemArea
49 color: "blue"
48 color: "blue"
50
49
51− IndicatorsMenu {
50+ PanelMenu {
52 id: indicatorsMenu
51 id: indicatorsMenu
53 width: units.gu(40)
52 width: units.gu(40)
54 anchors {
53 anchors {
@@ -58,8 +57,25 @@ IndicatorTest {
58 minimizedPanelHeight: units.gu(3)
57 minimizedPanelHeight: units.gu(3)
59 expandedPanelHeight: units.gu(7)
58 expandedPanelHeight: units.gu(7)
60 openedHeight: parent.height
59 openedHeight: parent.height
61− indicatorsModel: root.indicatorsModel
60+ model: root.indicatorsModel
62 shown: false
61 shown: false
62+
63+ rowItemDelegate: Item {
64+ property int ownIndex: index
65+ objectName: model.identifier + "-panelItem"
66+
67+ implicitWidth: indicatorsMenu.expanded ? units.gu(5) : units.gu(3)
68+ height: parent.height
69+
70+ Rectangle {
71+ anchors {
72+ fill: parent
73+ margins: 2
74+ }
75+ color: "red"
76+ Label { anchors.centerIn: parent; text: ownIndex }
77+ }
78+ }
63 }
79 }
64 }
80 }
65
81
@@ -111,7 +127,7 @@ IndicatorTest {
111
127
112 UT.UnityTestCase {
128 UT.UnityTestCase {
113 id: testCase
129 id: testCase
114− name: "IndicatorsMenu"
130+ name: "PanelMenu"
115 when: windowShown
131 when: windowShown
116
132
117 function init() {
133 function init() {
@@ -206,8 +222,8 @@ IndicatorTest {
206 // tests swiping on an indicator item activates the correct item.
222 // tests swiping on an indicator item activates the correct item.
207 function test_swipeForCurrentItem()
223 function test_swipeForCurrentItem()
208 {
224 {
209− var indicatorItemRow = findChild(indicatorsMenu, "indicatorItemRow");
225+ var panelItemRow = findChild(indicatorsMenu, "panelItemRow");
210− verify(indicatorItemRow !== null);
226+ verify(panelItemRow !== null);
211
227
212 for (var i = 0; i < root.originalModelData.length; i++) {
228 for (var i = 0; i < root.originalModelData.length; i++) {
213 var indicatorItem = get_indicator_item(i);
229 var indicatorItem = get_indicator_item(i);
@@ -219,10 +235,10 @@ IndicatorTest {
219 mappedPosition.x, indicatorsMenu.openedHeight / 2,
235 mappedPosition.x, indicatorsMenu.openedHeight / 2,
220 true /* beginTouch */, false /* endTouch */);
236 true /* beginTouch */, false /* endTouch */);
221
237
222− tryCompare(indicatorItemRow, "currentItem", indicatorItem, undefined /*timeout, default */,
238+ tryCompare(panelItemRow, "currentItem", indicatorItem, undefined /*timeout, default */,
223 "Incorrect item activated at position " + i);
239 "Incorrect item activated at position " + i);
224
240
225− touchFlick(indicatorItemRow,
241+ touchFlick(panelItemRow,
226 mappedPosition.x, indicatorsMenu.openedHeight / 2,
242 mappedPosition.x, indicatorsMenu.openedHeight / 2,
227 mappedPosition.x, mappedPosition.y,
243 mappedPosition.x, mappedPosition.y,
228 false /* beginTouch */, true /* endTouch */);
244 false /* beginTouch */, true /* endTouch */);
@@ -239,8 +255,8 @@ IndicatorTest {
239 indicatorsMenu.verticalVelocityThreshold = 0;
255 indicatorsMenu.verticalVelocityThreshold = 0;
240 verify(root.originalModelData.length >= 2);
256 verify(root.originalModelData.length >= 2);
241
257
242− var indicatorItemRow = findChild(indicatorsMenu, "indicatorItemRow");
258+ var panelItemRow = findChild(indicatorsMenu, "panelItemRow");
243− verify(indicatorItemRow !== null);
259+ verify(panelItemRow !== null);
244
260
245 // Get the first indicator
261 // Get the first indicator
246 var firstItem = get_indicator_item(0);
262 var firstItem = get_indicator_item(0);
@@ -252,12 +268,14 @@ IndicatorTest {
252 firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
268 firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
253 true /* beginTouch */, false /* endTouch */);
269 true /* beginTouch */, false /* endTouch */);
254
270
255− tryCompare(indicatorItemRow, "currentItem", firstItem)
271+ tryCompare(panelItemRow, "currentItem", firstItem)
256
272
257 // next time position will have moved.
273 // next time position will have moved.
258 var nextItem = get_indicator_item(1);
274 var nextItem = get_indicator_item(1);
259 var nextItemMappedPositionX = firstItemMappedPosition.x + firstItem.width;
275 var nextItemMappedPositionX = firstItemMappedPosition.x + firstItem.width;
260
276
277+ firstItemMappedPosition = indicatorsMenu.mapFromItem(firstItem, firstItem.width/2, firstItem.height/2);
278+
261 // 1) Flick mouse down to bottom
279 // 1) Flick mouse down to bottom
262 touchFlick(indicatorsMenu,
280 touchFlick(indicatorsMenu,
263 firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
281 firstItemMappedPosition.x, indicatorsMenu.minimizedPanelHeight * 2,
@@ -265,10 +283,10 @@ IndicatorTest {
265 false /* beginTouch */, false /* endTouch */,
283 false /* beginTouch */, false /* endTouch */,
266 units.gu(50) /* speed */, 5 /* iterations */); // more samples needed for accurate velocity
284 units.gu(50) /* speed */, 5 /* iterations */); // more samples needed for accurate velocity
267
285
268− tryCompare(indicatorItemRow, "currentItem", firstItem, undefined /* timeout, default */,
286+ tryCompare(panelItemRow, "currentItem", firstItem, undefined /* timeout, default */,
269 "First indicator should still be the current item");
287 "First indicator should still be the current item");
270 // after waiting in the same spot with touch down, it should update to the next item.
288 // after waiting in the same spot with touch down, it should update to the next item.
271− tryCompare(indicatorItemRow, "currentItem", nextItem);
289+ tryCompare(panelItemRow, "currentItem", nextItem);
272
290
273 touchRelease(indicatorsMenu, nextItemMappedPositionX, indicatorsMenu.openedHeight / 3);
291 touchRelease(indicatorsMenu, nextItemMappedPositionX, indicatorsMenu.openedHeight / 3);
274 }
292 }
tests/qmltests/Panel/tst_IndicatorPage.qml → tests/qmltests/Panel/tst_PanelMenuPage.qmlrenamed+53−47
@@ -16,42 +16,46 @@
16
16
17import QtQuick 2.4
17import QtQuick 2.4
18import QtTest 1.0
18import QtTest 1.0
19+import Ubuntu.Components 1.3
19import Unity.Test 0.1 as UT
20import Unity.Test 0.1 as UT
20−import Unity.Indicators 0.1 as Indicators
21+import QMenuModel 0.1
21import Ubuntu.Settings.Menus 0.1 as Menus
22import Ubuntu.Settings.Menus 0.1 as Menus
22import "../../../qml/Panel"
23import "../../../qml/Panel"
24+import "../../../qml/Panel/Indicators"
23
25
24Item {
26Item {
25 id: testView
27 id: testView
26 width: units.gu(40)
28 width: units.gu(40)
27 height: units.gu(70)
29 height: units.gu(70)
28
30
29− IndicatorPage {
31+ UnityMenuModel {
32+ id: unityMenuModel
33+ modelData: fullMenuData
34+ }
35+
36+ PanelMenuPage {
30 id: page
37 id: page
31 anchors.fill: parent
38 anchors.fill: parent
32
39
33− identifier: "test-indicator"
40+ menuModel: unityMenuModel
34− busName: "com.caninical.indicator.test"
41+ submenuIndex: 0
35− actionsObjectPath: "/com/canonical/indicator/test"
42+
36− menuObjectPath: "/com/canonical/indicator/test"
43+ factory: Object {
44+ function load(model) {
45+ return standardMenuComponent;
46+ }
47+
48+ Component {
49+ id: standardMenuComponent
37
50
38− factory {
51+ Menus.StandardMenu {
39− _userMap: {
52+ signal menuSelected
40− "default": {
53+ signal menuDeselected
41− "com.canonical.indicator.test" : testMenu
42 }
54 }
43 }
55 }
44 }
56 }
45 }
57 }
46
58
47− Component {
48− id: testMenu
49− Menus.StandardMenu {
50− signal menuSelected
51− signal menuDeselected
52− }
53− }
54−
55 property var fullMenuData: [{
59 property var fullMenuData: [{
56 "rowData": { // 1
60 "rowData": { // 1
57 "label": "root",
61 "label": "root",
@@ -126,43 +130,43 @@ Item {
126 "submenu": []
130 "submenu": []
127 }]; // end row 1
131 }]; // end row 1
128
132
129− function initializeMenuData(data) {
130− Indicators.UnityMenuModelCache.setCachedModelData("/com/canonical/indicator/test",
131− data);
132− }
133−
134 UT.UnityTestCase {
133 UT.UnityTestCase {
135 name: "IndicatorPage"
134 name: "IndicatorPage"
136
135
137 function init() {
136 function init() {
138− initializeMenuData([]);
137+ page.submenuIndex = 0;
138+ unityMenuModel.modelData = [];
139+ tryCompareFunction(function() { return page.currentPage == null }, true);
139 }
140 }
140
141
141− function test_reloadData() {
142+ function test_loadData() {
142− var mainMenu = findChild(page, "mainMenu");
143+ unityMenuModel.modelData = fullMenuData;
143−
144− tryCompare(mainMenu, "count", 0);
145
144
146− initializeMenuData(fullMenuData);
145+ tryCompareFunction(function() { return page.currentPage != null }, true);
146+ var listView = findChild(page.currentPage, "listView", 50);
147+ verify(listView);
147− tryCompare(mainMenu, "count", 3);
148+ tryCompare(listView, "count", 3);
148
149
149− initializeMenuData([]);
150+ unityMenuModel.modelData = [];
150− tryCompare(mainMenu, "count", 0);
151+ tryCompareFunction(function() { return page.currentPage == null }, true);
151 }
152 }
152
153
153− function test_traverse_rootMenuType_data() {
154+ function test_traverse_submenuIndex_data() {
154− return [
155+ return [,
155− { tag: "Incorrect", rootMenuType: "com.canonical.indicator", expectedCount: 0},
156− { tag: "Correct", rootMenuType: "com.canonical.indicator.root", expectedCount: 3},
156+ { tag: "Correct", submenuIndex: 0, expectedCount: 3},
157+ { tag: "Incorrect", submenuIndex: 1, expectedCount: 0}
157 ]
158 ]
158 }
159 }
159
160
160− function test_traverse_rootMenuType(data) {
161+ function test_traverse_submenuIndex(data) {
161− page.rootMenuType = data.rootMenuType;
162+ page.submenuIndex = data.submenuIndex;
162− initializeMenuData(fullMenuData);
163+ unityMenuModel.modelData = fullMenuData;
163
164
165+ tryCompareFunction(function() { return page.currentPage != null }, data.expectedCount > 0);
166+ if (data.expectedCount > 0) {
164− var mainMenu = findChild(page, "mainMenu");
167+ var listView = findChild(page.currentPage, "listView", 50);
165− tryCompare(mainMenu, "count", data.expectedCount);
168+ tryCompare(listView, "count", data.expectedCount);
169+ }
166 }
170 }
167
171
168 function test_remove_selected_item_data() {
172 function test_remove_selected_item_data() {
@@ -173,20 +177,22 @@ Item {
173 }
177 }
174
178
175 function test_remove_selected_item(data) {
179 function test_remove_selected_item(data) {
180+ unityMenuModel.modelData = fullMenuData;
176− var mainMenu = findChild(page, "mainMenu");
181+ var listView = findChild(page.currentPage, "listView", 50);
177− initializeMenuData(fullMenuData);
178
182
179 var menuId = "menu"+data.remove
183 var menuId = "menu"+data.remove
180− var menu = findChild(page, menuId);
184+ var menu = findChild(listView, menuId);
185+ verify(menu);
181
186
182 menu.menuSelected();
187 menu.menuSelected();
183− compare(mainMenu.currentIndex, data.remove, "Incorrect index selected");
188+ compare(listView.currentIndex, data.remove, "Incorrect index selected");
184− mainMenu.model.removeRow(data.remove);
189+ listView.model.removeRow(data.remove);
185
190
186− compare(mainMenu.currentIndex, -1, "Current index should be reset after current item removal");
191+ compare(listView.currentIndex, -1, "Current index should be reset after current item removal");
187
192
188 // now make sure selecting a new menu works.
193 // now make sure selecting a new menu works.
189 var menu1 = findChild(page, "menu1");
194 var menu1 = findChild(page, "menu1");
195+ verify(menu1);
190 menu1.menuSelected();
196 menu1.menuSelected();
191 compare(menu1.selected, true, "Item not selected");
197 compare(menu1.selected, true, "Item not selected");
192 }
198 }
tests/qmltests/Stage/tst_DecoratedWindow.qml+40−5
@@ -19,10 +19,13 @@ import QtTest 1.0
19import Unity.Test 0.1 as UT
19import Unity.Test 0.1 as UT
20import ".."
20import ".."
21import "../../../qml/Components"
21import "../../../qml/Components"
22+import "../../../qml/Components/PanelState"
22import "../../../qml/Stage"
23import "../../../qml/Stage"
23import Ubuntu.Components 1.3
24import Ubuntu.Components 1.3
24import Ubuntu.Components.ListItems 1.3 as ListItem
25import Ubuntu.Components.ListItems 1.3 as ListItem
25import Unity.Application 0.1
26import Unity.Application 0.1
27+import Unity.ApplicationMenu 0.1
28+import Unity.Indicators 0.1 as Indicators
26
29
27Rectangle {
30Rectangle {
28 color: "red"
31 color: "red"
@@ -30,8 +33,25 @@ Rectangle {
30 width: fakeShell.shortestDimension + controls.width
33 width: fakeShell.shortestDimension + controls.width
31 height: fakeShell.longestDimension
34 height: fakeShell.longestDimension
32
35
36+ Component.onCompleted: {
37+ QuickUtils.keyboardAttached = true;
38+ theme.name = "Ubuntu.Components.Themes.SuruDark"
39+ }
40+
33 property QtObject fakeApplication: null
41 property QtObject fakeApplication: null
34
42
43+ Binding {
44+ target: UT.MouseTouchAdaptor
45+ property: "enabled"
46+ value: false
47+ }
48+
49+ SurfaceManager { id: sMgr }
50+ ApplicationMenuDataLoader {
51+ id: appMenuData
52+ surfaceManager: sMgr
53+ }
54+
35 Item {
55 Item {
36 id: fakeShell
56 id: fakeShell
37
57
@@ -49,6 +69,11 @@ Rectangle {
49
69
50 rotation: orientationAngle
70 rotation: orientationAngle
51
71
72+ WindowResizeArea {
73+ target: loader
74+ borderThickness: units.gu(2)
75+ }
76+
52 Loader {
77 Loader {
53 id: loader
78 id: loader
54 property int windowedX: units.gu(1)
79 property int windowedX: units.gu(1)
@@ -64,12 +89,21 @@ Rectangle {
64 property bool itemDestroyed: false
89 property bool itemDestroyed: false
65
90
66 sourceComponent: DecoratedWindow {
91 sourceComponent: DecoratedWindow {
92+ id: decoratedWindow
93+
67 anchors.fill: parent
94 anchors.fill: parent
68 application: fakeApplication
95 application: fakeApplication
69 requestedWidth: loader.windowedWidth
96 requestedWidth: loader.windowedWidth
70 requestedHeight: loader.windowedHeight
97 requestedHeight: loader.windowedHeight
98+ active: true
71 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
99 surface: fakeApplication && fakeApplication.surfaceList.count > 0 ? fakeApplication.surfaceList.get(0) : null
72
100
101+ Binding {
102+ target: PanelState
103+ property: "focusedPersistentSurfaceId"
104+ value: decoratedWindow.surface ? decoratedWindow.surface.persistentId : "x"
105+ }
106+
73 Component.onDestruction: {
107 Component.onDestruction: {
74 loader.itemDestroyed = true;
108 loader.itemDestroyed = true;
75 }
109 }
@@ -80,16 +114,12 @@ Rectangle {
80
114
81 Rectangle { anchors.fill: parent; color: "green"; opacity: 1 }
115 Rectangle { anchors.fill: parent; color: "green"; opacity: 1 }
82 }
116 }
83− WindowResizeArea {
84− target: loader
85− borderThickness: units.gu(2)
86− }
87 }
117 }
88
118
89
119
90 Rectangle {
120 Rectangle {
91 id: controls
121 id: controls
92− color: "white"
122+ color: theme.palette.normal.background
93 width: units.gu(30)
123 width: units.gu(30)
94 anchors {
124 anchors {
95 top: parent.top
125 top: parent.top
@@ -100,6 +130,11 @@ Rectangle {
100 Column {
130 Column {
101 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
131 anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
102 spacing: units.gu(1)
132 spacing: units.gu(1)
133+ Button {
134+ id: loadWidthDialerApp
135+ text: "Load with dialer-app"
136+ onClicked: { testCase.restartWithApp("dialer-app"); }
137+ }
103 Button {
138 Button {
104 id: loadWithWeatherApp
139 id: loadWithWeatherApp
105 text: "Load with ubuntu-weather-app"
140 text: "Load with ubuntu-weather-app"
tests/qmltests/Stage/tst_DesktopStage.qml+9−45
@@ -19,6 +19,8 @@ import QtTest 1.0
19import Ubuntu.Components 1.3
19import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.3
20import Ubuntu.Components.ListItems 1.3
21import Unity.Application 0.1
21import Unity.Application 0.1
22+import Unity.ApplicationMenu 0.1
23+import Unity.Indicators 0.1 as Indicators
22import Unity.Test 0.1
24import Unity.Test 0.1
23import Utils 0.1
25import Utils 0.1
24import WindowManager 1.0
26import WindowManager 1.0
@@ -42,6 +44,7 @@ Item {
42 }
44 }
43
45
44 Component.onCompleted: {
46 Component.onCompleted: {
47+ QuickUtils.keyboardAttached = true;
45 theme.name = "Ubuntu.Components.Themes.SuruDark";
48 theme.name = "Ubuntu.Components.Themes.SuruDark";
46 resetGeometry();
49 resetGeometry();
47 }
50 }
@@ -58,6 +61,11 @@ Item {
58 }
61 }
59
62
60 SurfaceManager { id: sMgr }
63 SurfaceManager { id: sMgr }
64+ ApplicationMenuDataLoader {
65+ id: appMenuData
66+ surfaceManager: sMgr
67+ }
68+
61 TopLevelWindowModel {
69 TopLevelWindowModel {
62 id: topSurfaceList
70 id: topSurfaceList
63 applicationManager: ApplicationManager
71 applicationManager: ApplicationManager
@@ -94,13 +102,6 @@ Item {
94 mode: "windowed"
102 mode: "windowed"
95 }
103 }
96 }
104 }
97−
98− MouseArea {
99− id: clickThroughTester
100− anchors.fill: stageLoader.item
101− acceptedButtons: Qt.AllButtons
102− hoverEnabled: true
103− }
104 }
105 }
105
106
106 Rectangle {
107 Rectangle {
@@ -152,11 +153,6 @@ Item {
152 }
153 }
153 }
154 }
154
155
155− SignalSpy {
156− id: mouseEaterSpy
157− target: clickThroughTester
158− }
159−
160 StageTestCase {
156 StageTestCase {
161 id: testCase
157 id: testCase
162 name: "DesktopStage"
158 name: "DesktopStage"
@@ -197,8 +193,6 @@ Item {
197
193
198 stageLoader.active = true;
194 stageLoader.active = true;
199 tryCompare(stageLoader, "status", Loader.Ready);
195 tryCompare(stageLoader, "status", Loader.Ready);
200−
201− mouseEaterSpy.clear();
202 }
196 }
203
197
204 function maximizeAppDelegate(appDelegate) {
198 function maximizeAppDelegate(appDelegate) {
@@ -709,43 +703,13 @@ Item {
709
703
710 var posBefore = Qt.point(appDelegate.x, appDelegate.y);
704 var posBefore = Qt.point(appDelegate.x, appDelegate.y);
711
705
712− mousePress(appDelegate, appDelegate.width / 2, units.gu(1), data.button);
706+ mouseDrag(appDelegate, appDelegate.width / 2, units.gu(1), 0, appDelegate.height / 2, data.button, Qt.NoModifier, 200)
713− mouseMove(appDelegate, appDelegate.width / 2, -units.gu(100), undefined /* delay */, data.button);
714
707
715 var posAfter = Qt.point(appDelegate.x, appDelegate.y);
708 var posAfter = Qt.point(appDelegate.x, appDelegate.y);
716
709
717 tryCompareFunction(function(){return posBefore == posAfter;}, data.button !== Qt.LeftButton ? true : false);
710 tryCompareFunction(function(){return posBefore == posAfter;}, data.button !== Qt.LeftButton ? true : false);
718 }
711 }
719
712
720− function test_eatWindowDecorationMouseEvents_data() {
721− return [
722− {tag: "left mouse click", signalName: "clicked", button: Qt.LeftButton },
723− {tag: "right mouse click", signalName: "clicked", button: Qt.RightButton },
724− {tag: "middle mouse click", signalName: "clicked", button: Qt.MiddleButton },
725− {tag: "mouse wheel", signalName: "wheel", button: Qt.MiddleButton },
726− {tag: "double click (RMB)", signalName: "doubleClicked", button: Qt.RightButton },
727− ]
728− }
729−
730− function test_eatWindowDecorationMouseEvents(data) {
731− var dialerAppDelegate = startApplication("dialer-app");
732− verify(dialerAppDelegate);
733− var decoration = findChild(dialerAppDelegate, "appWindowDecoration");
734− verify(decoration);
735−
736− mouseEaterSpy.signalName = data.signalName;
737− if (data.signalName === "wheel") {
738− mouseWheel(decoration, decoration.width/2, decoration.height/2, 20, 20);
739− } else if (data.signalName === "clicked") {
740− mouseClick(decoration, decoration.width/2, decoration.height/2, data.button);
741− } else {
742− mouseDoubleClick(decoration, decoration.width/2, decoration.height/2, data.button);
743− tryCompare(dialerAppDelegate, "maximized", false);
744− }
745−
746− tryCompare(mouseEaterSpy, "count", 0);
747− }
748−
749 // regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1627281
713 // regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1627281
750 function test_doubleTapToMaximizeWindow() {
714 function test_doubleTapToMaximizeWindow() {
751 var dialerAppDelegate = startApplication("dialer-app");
715 var dialerAppDelegate = startApplication("dialer-app");
tests/qmltests/Stage/tst_PhoneStage.qml+5
@@ -32,6 +32,11 @@ Item {
32 property var greeter: { fullyShown: true }
32 property var greeter: { fullyShown: true }
33
33
34 SurfaceManager { id: sMgr }
34 SurfaceManager { id: sMgr }
35+ ApplicationMenuDataLoader {
36+ id: appMenuData
37+ surfaceManager: sMgr
38+ }
39+
35 Stage {
40 Stage {
36 id: stage
41 id: stage
37 anchors { fill: parent; rightMargin: units.gu(30) }
42 anchors { fill: parent; rightMargin: units.gu(30) }
tests/qmltests/Stage/tst_TabletStage.qml+6
@@ -36,6 +36,11 @@ Rectangle {
36 property var greeter: { fullyShown: true }
36 property var greeter: { fullyShown: true }
37
37
38 SurfaceManager { id: sMgr }
38 SurfaceManager { id: sMgr }
39+ ApplicationMenuDataLoader {
40+ id: appMenuData
41+ surfaceManager: sMgr
42+ }
43+
39 Stage {
44 Stage {
40 id: stage
45 id: stage
41 anchors { fill: parent; rightMargin: units.gu(30) }
46 anchors { fill: parent; rightMargin: units.gu(30) }
@@ -587,6 +592,7 @@ Rectangle {
587
592
588 function test_loadSideStageByDraggingFromMainStage() {
593 function test_loadSideStageByDraggingFromMainStage() {
589 sideStage.showNow();
594 sideStage.showNow();
595+ print("sidestage now shown. launching browser")
590 var webbrowserSurfaceId = topSurfaceList.nextId;
596 var webbrowserSurfaceId = topSurfaceList.nextId;
591 webbrowserCheckBox.checked = true;
597 webbrowserCheckBox.checked = true;
592 waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
598 waitUntilAppSurfaceShowsUp(webbrowserSurfaceId);
tests/qmltests/Stage/tst_WindowDecoration.qmlnew file+244
@@ -0,0 +1,244 @@
1+/*
2+ * Copyright (C) 2016 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.4
18+import QtTest 1.0
19+import QtQuick.Layouts 1.1
20+import Ubuntu.Components 1.3
21+import Ubuntu.Components.ListItems 1.3
22+import Unity.Application 0.1
23+import Unity.Test 0.1
24+import Utils 0.1
25+import QMenuModel 0.1
26+
27+import ".."
28+import "../../../qml/Stage"
29+
30+Item {
31+ id: root
32+ width: units.gu(70)
33+ height: units.gu(50)
34+
35+ Component.onCompleted: {
36+ QuickUtils.keyboardAttached = true;
37+ theme.name = "Ubuntu.Components.Themes.SuruDark"
38+ }
39+
40+ Binding {
41+ target: MouseTouchAdaptor
42+ property: "enabled"
43+ value: false
44+ }
45+
46+ property QtObject application: QtObject {
47+ property string name: "webbrowser"
48+ }
49+
50+ SurfaceManager { id: sMgr }
51+ ApplicationMenuDataLoader {
52+ id: appMenuData
53+ surfaceManager: sMgr
54+ }
55+
56+ UnityMenuModel {
57+ id: menuBackend
58+ modelData: appMenuData.generateTestData(5, 3, 3, "menu")
59+ onActivated: log.text = "Activated " + action + "\n" + log.text
60+ }
61+
62+ Rectangle {
63+ anchors {
64+ left: parent.left
65+ right: parent.right
66+ bottom: parent.bottom
67+ margins: units.gu(1)
68+ }
69+ height: units.gu(20)
70+ border.width: 1
71+ border.color: "black"
72+
73+ TextArea {
74+ id: log
75+ anchors.fill: parent
76+ readOnly: true
77+ color: "black"
78+ }
79+ }
80+
81+ MouseArea {
82+ id: clickThroughTester
83+ anchors.fill: parent
84+ acceptedButtons: Qt.AllButtons
85+ hoverEnabled: true
86+ }
87+
88+ Loader {
89+ id: decorationLoader
90+ anchors { left: parent.left; right: parent.right; top: parent.top }
91+ anchors.margins: units.gu(1)
92+ height: units.gu(3)
93+
94+ property bool itemDestroyed: false
95+ sourceComponent: WindowDecoration {
96+ anchors.fill: parent
97+ title: "TestTitle - Doing something"
98+ active: true
99+ menu: menuBackend
100+
101+ onCloseClicked: { log.text = "Close\n" + log.text }
102+ onMinimizeClicked: { log.text = "Minimize\n" + log.text }
103+ onMaximizeClicked: { log.text = "Maximize\n" + log.text }
104+
105+ Component.onDestruction: {
106+ decorationLoader.itemDestroyed = true;
107+ }
108+ }
109+ }
110+
111+ SignalSpy {
112+ id: signalSpy
113+ target: decorationLoader.item
114+ }
115+
116+ SignalSpy {
117+ id: mouseEaterSpy
118+ target: clickThroughTester
119+ }
120+
121+ UnityTestCase {
122+ id: testCase
123+ name: "WindowDecoration"
124+ when: windowShown
125+
126+ property Item decoration: decorationLoader.status === Loader.Ready ? decorationLoader.item : null
127+
128+ function init() {
129+ tryCompareFunction(function() { return decoration !== null }, true);
130+ decoration.menu = menuBackend;
131+
132+ waitForRendering(decoration);
133+ }
134+
135+ function cleanup() {
136+ decorationLoader.itemDestroyed = false;
137+ decorationLoader.active = false;
138+
139+ tryCompare(decorationLoader, "status", Loader.Null);
140+ tryCompare(decorationLoader, "item", null);
141+ // Loader.status might be Loader.Null and Loader.item might be null but the Loader
142+ // actually took place. Likely because Loader waits until the next event loop
143+ // iteration to do its work. So to ensure the reload, we will wait until the
144+ // Shell instance gets destroyed.
145+ tryCompare(decorationLoader, "itemDestroyed", true);
146+
147+ decorationLoader.active = true;
148+ tryCompare(decorationLoader, "status", Loader.Ready);
149+
150+ signalSpy.clear();
151+ mouseEaterSpy.clear();
152+ }
153+
154+ function test_windowControlButtons_data() {
155+ return [ { tag: "close", controlName: "closeWindowButton", signal: "closeClicked"},
156+ { tag: "minimize", controlName: "minimizeWindowButton", signal: "minimizeClicked"},
157+ { tag: "maximize", controlName: "maximizeWindowButton", signal: "maximizeClicked"}];
158+ }
159+
160+ function test_windowControlButtons(data) {
161+ signalSpy.signalName = data.signal;
162+ var controlButton = findChild(decoration, data.controlName);
163+ verify(controlButton !== null);
164+
165+ mouseClick(controlButton, controlButton.width/2, controlButton.height/2);
166+ compare(signalSpy.count, 1);
167+ }
168+
169+ function test_titleRemainsWhenHoveringOnTitleBarWithNoMenu() {
170+ decoration.menu = undefined;
171+
172+ var menuLoader = findChild(decoration, "menuBarLoader");
173+ verify(menuLoader);
174+ mouseMove(menuLoader, menuLoader.width/2, menuLoader.height/2);
175+ wait(200);
176+
177+ var titleLabel = findChild(decoration, "windowDecorationTitle");
178+ verify(menuLoader);
179+
180+ compare(menuLoader.opacity, 0, "Menu should not show when present")
181+ compare(titleLabel.opacity, 1, "Title should always show when app menu not present")
182+ }
183+
184+ function test_menuShowsWhenHoveringOnTitleBar() {
185+ var menuLoader = findChild(decoration, "menuBarLoader");
186+ verify(menuLoader);
187+ mouseMove(menuLoader, menuLoader.width/2, menuLoader.height/2)
188+
189+ var titleLabel = findChild(decoration, "windowDecorationTitle");
190+ verify(menuLoader);
191+
192+ tryCompare(menuLoader, "opacity", 1);
193+ tryCompare(titleLabel, "opacity", 0);
194+
195+ mouseMove(menuLoader, menuLoader.width/2, menuLoader.height * 2);
196+
197+ tryCompare(menuLoader, "opacity", 0);
198+ tryCompare(titleLabel, "opacity", 1);
199+ }
200+
201+ function test_showMenuBarWithShortcutsOnLongAltPress() {
202+ var menuLoader = findChild(decoration, "menuBarLoader");
203+ verify(menuLoader);
204+
205+ var titleLabel = findChild(decoration, "windowDecorationTitle");
206+ verify(menuLoader);
207+
208+ var menuBar = findChild(decoration, "menuBar");
209+ verify(menuBar);
210+
211+ keyPress(Qt.Key_Alt, Qt.NoModifier);
212+ tryCompare(menuLoader, "opacity", 1);
213+ tryCompare(titleLabel, "opacity", 0);
214+
215+ keyRelease(Qt.Key_Alt, Qt.NoModifier);
216+ tryCompare(menuLoader, "opacity", 0);
217+ tryCompare(titleLabel, "opacity", 1);
218+ }
219+
220+ function test_eatMouseEvents_data() {
221+ return [
222+ {tag: "left mouse click", signalName: "clicked", button: Qt.LeftButton },
223+ {tag: "right mouse click", signalName: "clicked", button: Qt.RightButton },
224+ {tag: "middle mouse click", signalName: "clicked", button: Qt.MiddleButton },
225+ {tag: "mouse wheel", signalName: "wheel", button: Qt.MiddleButton },
226+ {tag: "double click (LMB)", signalName: "doubleClicked", button: Qt.LeftButton },
227+ {tag: "double click (RMB)", signalName: "doubleClicked", button: Qt.RightButton },
228+ ]
229+ }
230+
231+ function test_eatMouseEvents(data) {
232+ mouseEaterSpy.signalName = data.signalName;
233+ if (data.signalName === "wheel") {
234+ mouseWheel(decoration, decoration.width/2, decoration.height/2, 20, 20);
235+ } else if (data.signalName === "clicked") {
236+ mouseClick(decoration, decoration.width/2, decoration.height/2, data.button);
237+ } else {
238+ mouseDoubleClick(decoration, decoration.width/2, decoration.height/2, data.button);
239+ }
240+
241+ tryCompare(mouseEaterSpy, "count", 0);
242+ }
243+ }
244+}
tests/qmltests/tst_Shell.qml+34−5
@@ -24,6 +24,7 @@ import Ubuntu.Components 1.3
24import Ubuntu.Components.ListItems 1.3 as ListItem
24import Ubuntu.Components.ListItems 1.3 as ListItem
25import Ubuntu.Telephony 0.1 as Telephony
25import Ubuntu.Telephony 0.1 as Telephony
26import Unity.Application 0.1
26import Unity.Application 0.1
27+import Unity.ApplicationMenu 0.1
27import Unity.Connectivity 0.1
28import Unity.Connectivity 0.1
28import Unity.Indicators 0.1
29import Unity.Indicators 0.1
29import Unity.Notifications 1.0
30import Unity.Notifications 1.0
@@ -32,6 +33,7 @@ import Unity.Test 0.1
32import Powerd 0.1
33import Powerd 0.1
33import Wizard 0.1 as Wizard
34import Wizard 0.1 as Wizard
34import Utils 0.1
35import Utils 0.1
36+import Unity.Indicators 0.1 as Indicators
35
37
36import "../../qml"
38import "../../qml"
37import "../../qml/Components"
39import "../../qml/Components"
@@ -51,12 +53,18 @@ Rectangle {
51 shellLoader.active = true;
53 shellLoader.active = true;
52 }
54 }
53
55
56+ ApplicationMenuDataLoader {
57+ id: appMenuData
58+ }
59+
54 property var shell: shellLoader.item ? shellLoader.item : null
60 property var shell: shellLoader.item ? shellLoader.item : null
55 onShellChanged: {
61 onShellChanged: {
56 if (shell) {
62 if (shell) {
57 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
63 topLevelSurfaceList = testCase.findInvisibleChild(shell, "topLevelSurfaceList");
64+ appMenuData.surfaceManager = testCase.findInvisibleChild(shell, "surfaceManager");
58 } else {
65 } else {
59 topLevelSurfaceList = null;
66 topLevelSurfaceList = null;
67+ appMenuData.surfaceManager = null;
60 }
68 }
61 }
69 }
62
70
@@ -1878,21 +1886,21 @@ Rectangle {
1878 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1886 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
1879
1887
1880 tryCompare(appDelegate, "state", "normal");
1888 tryCompare(appDelegate, "state", "normal");
1881− tryCompare(PanelState, "buttonsVisible", false)
1889+ tryCompare(PanelState, "decorationsVisible", false)
1882
1890
1883 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1891 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1884 tryCompare(appDelegate, "state", "maximized");
1892 tryCompare(appDelegate, "state", "maximized");
1885− tryCompare(PanelState, "buttonsVisible", true)
1893+ tryCompare(PanelState, "decorationsVisible", true)
1886
1894
1887 ApplicationManager.stopApplication(application.appId);
1895 ApplicationManager.stopApplication(application.appId);
1888− tryCompare(PanelState, "buttonsVisible", false)
1896+ tryCompare(PanelState, "decorationsVisible", false)
1889
1897
1890 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1898 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
1891 // They won't be gone until those surface items are destroyed.
1899 // They won't be gone until those surface items are destroyed.
1892 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1900 tryCompareFunction(function() { return application.surfaceList.count }, 0);
1893
1901
1894 ApplicationManager.startApplication(application.appId);
1902 ApplicationManager.startApplication(application.appId);
1895− tryCompare(PanelState, "buttonsVisible", true)
1903+ tryCompare(PanelState, "decorationsVisible", true)
1896 }
1904 }
1897
1905
1898 function test_newAppHasValidGeometry() {
1906 function test_newAppHasValidGeometry() {
@@ -1913,6 +1921,7 @@ Rectangle {
1913 var appRepeater = findChild(shell, "appRepeater")
1921 var appRepeater = findChild(shell, "appRepeater")
1914 var appDelegate = appRepeater.itemAt(0);
1922 var appDelegate = appRepeater.itemAt(0);
1915 var panelButtons = findChild(shell, "panelWindowControlButtons")
1923 var panelButtons = findChild(shell, "panelWindowControlButtons")
1924+ verify(panelButtons)
1916
1925
1917 tryCompare(appDelegate, "state", "normal");
1926 tryCompare(appDelegate, "state", "normal");
1918 tryCompare(panelButtons, "visible", false);
1927 tryCompare(panelButtons, "visible", false);
@@ -2547,7 +2556,7 @@ Rectangle {
2547
2556
2548 tryCompare(appDelegate, "state", "maximized");
2557 tryCompare(appDelegate, "state", "maximized");
2549
2558
2550− mouseDrag(panel, panel.width/3, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
2559+ mouseDrag(panel, panel.width/2, panel.height/2, 0, shell.height/3, Qt.LeftButton, Qt.NoModifier, 500);
2551 tryCompare(appDelegate, "state", "restored");
2560 tryCompare(appDelegate, "state", "restored");
2552 }
2561 }
2553
2562
@@ -2744,5 +2753,25 @@ Rectangle {
2744 tryCompare(stage, "state", data.spreadEnabled ? "spread" : "staged");
2753 tryCompare(stage, "state", data.spreadEnabled ? "spread" : "staged");
2745 keyRelease(Qt.Key_W, Qt.MetaModifier)
2754 keyRelease(Qt.Key_W, Qt.MetaModifier)
2746 }
2755 }
2756+
2757+ function test_panelTitleShowsWhenGreeterNotShown_data() {
2758+ return [
2759+ {tag: "phone" },
2760+ {tag: "tablet" },
2761+ {tag: "desktop" }
2762+ ]
2763+ }
2764+
2765+ function test_panelTitleShowsWhenGreeterNotShown(data) {
2766+ loadShell(data.tag);
2767+
2768+ var panel = findChild(shell, "panel"); verify(panel);
2769+ var panelTitle = findChild(panel.applicationMenus, "panelTitle"); verify(panelTitle);
2770+ compare(panelTitle.visible, false, "Panel title should not be visible when greeter is shown");
2771+
2772+ swipeAwayGreeter();
2773+
2774+ tryCompare(panelTitle, "visible", true, undefined, "Panel title should be visible when greeter not shown");
2775+ }
2747 }
2776 }
2748}
2777}
tests/qmltests/tst_ShellWithPin.qml+3−1
@@ -474,7 +474,9 @@ Item {
474
474
475 ApplicationManager.requestFocusApplication("unity8-dash");
475 ApplicationManager.requestFocusApplication("unity8-dash");
476 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
476 tryCompare(ApplicationManager, "focusedApplicationId", "unity8-dash");
477+ var callHint = findChild(panel, "callHint");
477− tryCompare(panel.callHint, "visible", true);
478+ tryCompare(callHint, "visible", true);
479+ wait(10000)
478
480
479 // simulate a callHint press, the real thing requires dialer: url support
481 // simulate a callHint press, the real thing requires dialer: url support
480 ApplicationManager.requestFocusApplication("dialer-app");
482 ApplicationManager.requestFocusApplication("dialer-app");