on xenial help sign in
menus
commita40eeb73f79022700443d6e1c00a86a735cbd009 authorNick Dedekind <nick.dedekind@canonical.com> dateThu 29 Sep 2016, 10:15 (10 years ago) parentd275869
24 files changed+1153−467
plugins/Utils/windowinputfilter.cpp+23−6 plugins/Utils/windowinputfilter.h+1 qml/CMakeLists.txt+1 qml/Components/PanelState/PanelState.qml+6−3 qml/Panel/IndicatorPage.qml+159−136 qml/Panel/Indicators/MenuItemFactory.qml+87−1 qml/Panel/MenuContent.qml+6−8 qml/Panel/Panel.qml+364−99 qml/Panel/IndicatorsBar.qml → qml/Panel/PanelBar.qml+11−5 qml/Panel/IndicatorItemRow.qml → qml/Panel/PanelItemRow.qml+65−93 qml/Panel/IndicatorsMenu.qml → qml/Panel/PanelMenu.qml+30−19 qml/Shell.qml+12−8 qml/Stages/DecoratedWindow.qml+71−32 qml/Stages/DesktopStage.qml+19−5 qml/Stages/PhoneStage.qml+40−1 qml/Stages/TabletStage.qml+13 qml/Stages/WindowDecoration.qml+78−19 src/main.cpp+2−2 tests/mocks/QMenuModel/unitymenumodel.cpp+47−6 tests/mocks/QMenuModel/unitymenumodel.h+3−1 tests/qmltests/CMakeLists.txt+4 tests/qmltests/Panel/tst_Panel.qml+76−19 tests/qmltests/Stages/tst_DesktopStage.qml+16 tests/qmltests/tst_Shell.qml+19−4
plugins/Utils/windowinputfilter.cpp+23−6
@@ -26,10 +26,14 @@ WindowInputFilter::WindowInputFilter(QQuickItem *parent)
2626{
2727 connect(this, &QQuickItem::windowChanged,
2828 this, &WindowInputFilter::setupFilterOnWindow);
29+ connect(this, &QQuickItem::enabledChanged,
30+ this, &WindowInputFilter::updateFilter);
2931}
3032
3133bool WindowInputFilter::eventFilter(QObject *watched, QEvent *event)
3234{
35+ if (!isEnabled()) return false;
36+
3337 Q_ASSERT(!m_filteredWindow.isNull());
3438 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
3539 Q_UNUSED(watched);
@@ -53,14 +57,27 @@ bool WindowInputFilter::eventFilter(QObject *watched, QEvent *event)
5357
5458void WindowInputFilter::setupFilterOnWindow(QQuickWindow *window)
5559{
56− if (!m_filteredWindow.isNull()) {
57− m_filteredWindow->removeEventFilter(this);
58− m_filteredWindow.clear();
60+ if (isEnabled()) {
61+ if (!m_filteredWindow.isNull()) {
62+ m_filteredWindow->removeEventFilter(this);
63+ m_filteredWindow.clear();
64+ }
65+
66+ if (window) {
67+ window->installEventFilter(this);
68+ }
5969 }
70+ m_filteredWindow = window;
71+}
6072
61− if (window) {
62− window->installEventFilter(this);
63− m_filteredWindow = window;
73+void WindowInputFilter::updateFilter()
74+{
75+ if (!m_filteredWindow.isNull()) {
76+ if (isEnabled()) {
77+ m_filteredWindow->installEventFilter(this);
78+ } else {
79+ m_filteredWindow->removeEventFilter(this);
80+ }
6481 }
6582}
6683
plugins/Utils/windowinputfilter.h+1
@@ -49,6 +49,7 @@ Q_SIGNALS:
4949
5050private Q_SLOTS:
5151 void setupFilterOnWindow(QQuickWindow *window);
52+ void updateFilter();
5253
5354private:
5455 QPointer<QQuickWindow> m_filteredWindow;
qml/CMakeLists.txt+1
@@ -5,6 +5,7 @@ install(FILES ${QML_JS_FILES}
55 )
66
77set(QML_DIRS
8+ ApplicationMenus
89 Components
910 Dash
1011 graphics
qml/Components/PanelState/PanelState.qml+6−3
@@ -1,5 +1,5 @@
11/*
2− * Copyright (C) 2014 Canonical, Ltd.
2+ * Copyright (C) 2014-2016 Canonical, Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -21,8 +21,8 @@ QtObject {
2121 id: root
2222
2323 property string title: ""
24− property bool buttonsVisible: false
25− property bool buttonsAlwaysVisible: false
24+ property bool decorationsVisible: false
25+ property bool decorationsAlwaysVisible: false
2626 property bool closeButtonShown: true
2727 property bool dropShadow: false
2828 property int panelHeight: 0
@@ -30,4 +30,7 @@ QtObject {
3030 signal closeClicked()
3131 signal minimizeClicked()
3232 signal restoreClicked()
33+
34+ property string focusedPersistentSurfaceId: ""
35+ property bool focusedSurfaceMaximized: false
3336}
qml/Panel/IndicatorPage.qml+159−136
@@ -15,174 +15,197 @@
1515 */
1616
1717import QtQuick 2.4
18−import Ubuntu.Components 1.3 as Components
18+import Ubuntu.Components 1.3
19+import Ubuntu.Components.ListItems 1.3 as ListItems
1920import Unity.Indicators 0.1 as Indicators
2021import "../Components"
2122import "Indicators"
2223
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− }
24+PageStack {
25+ id: root
26+
27+ property var submenuIndex: undefined
28+ property string context
29+ property QtObject menuModel: null
5030
5131 Connections {
52− target: menuStack.tail
32+ target: root.menuModel
5333
5434 // 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− }
35+ onRowsInserted: root.reset()
36+ onRowsRemoved: root.reset()
37+ onModelReset: root.reset()
6538 }
6639
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
40+ Component.onCompleted: {
41+ reset();
42+ }
7543
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− }
44+ function reset() {
45+ clear();
46+ var model = submenuIndex == undefined ? menuModel : menuModel.submenu(submenuIndex)
47+ push(pageComponent, { "menuModel": model });
48+ }
8749
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))
50+ Component {
51+ id: pageComponent
52+ Page {
53+ id: page
9054
91− // Only allow flicking if the content doesn't fit on the page
92− interactive: contentHeight > height
55+ property alias menuModel: mainMenu.model
56+ property alias title: backLabel.title
57+ property bool isSubmenu: false
9358
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;
59+ MenuItemFactory {
60+ id: factory
61+ context: root.context
62+ rootModel: root.menuModel ? root.menuModel : null
63+ menuModel: mainMenu.model ? mainMenu.model : null
10064 }
101− }
102− // for highlight following
103− onSelectedIndexChanged: {
104− if (currentIndex != selectedIndex) {
105− var blocked = blockCurrentIndexChange;
106− blockCurrentIndexChange = true;
107−
108− currentIndex = selectedIndex;
10965
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− }
66+ header: PageHeader {
67+ id: backLabel
68+ visible: page.isSubmenu
69+ leadingActionBar.actions: [
70+ Action {
71+ iconName: "back"
72+ text: "Back"
73+ onTriggered: {
74+ root.pop();
75+ }
76+ }
77+ ]
11978 }
120− }
12179
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;
80+ ListView {
81+ id: mainMenu
82+ objectName: "mainMenu"
83+
84+ anchors {
85+ top: page.isSubmenu ? backLabel.bottom : parent.top
86+ left: parent.left
87+ right: parent.right
88+ bottom: parent.bottom
89+ bottomMargin: Qt.inputMethod.visible ? (Qt.inputMethod.keyboardRectangle.height - root.anchors.bottomMargin) : 0
90+
91+ Behavior on bottomMargin {
92+ NumberAnimation {
93+ duration: 175
94+ easing.type: Easing.OutQuad
95+ }
96+ }
97+ // TODO - does ever frame.
98+ onBottomMarginChanged: {
99+ mainMenu.positionViewAtIndex(mainMenu.currentIndex, ListView.End)
100+ }
128101 }
129− }
130− }
131102
132− delegate: Loader {
133− id: loader
134− objectName: "menuItem" + index
135− width: ListView.view.width
136− visible: status == Loader.Ready
103+ // Don't load all the delegates (only max of 3 pages worth -1/0/+1)
104+ cacheBuffer: Math.max(height * 3, units.gu(70))
137105
138− property int modelIndex: index
139− sourceComponent: factory.load(model, main.identifier)
106+ // Only allow flicking if the content doesn't fit on the page
107+ interactive: contentHeight > height
140108
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; });
109+ property int selectedIndex: -1
110+ property bool blockCurrentIndexChange: false
111+ // for count = 0
112+ onCountChanged: {
113+ if (count == 0 && selectedIndex != -1) {
114+ selectedIndex = -1;
115+ }
147116 }
148− if (item.hasOwnProperty("menuDeselected")) {
149− item.menuDeselected.connect(function() { mainMenu.selectedIndex = -1; });
117+ // for highlight following
118+ onSelectedIndexChanged: {
119+ if (currentIndex != selectedIndex) {
120+ var blocked = blockCurrentIndexChange;
121+ blockCurrentIndexChange = true;
122+
123+ currentIndex = selectedIndex;
124+
125+ blockCurrentIndexChange = blocked;
126+ }
150127 }
151− if (item.hasOwnProperty("menuData")) {
152− item.menuData = Qt.binding(function() { return model; });
128+ // for item addition/removal
129+ onCurrentIndexChanged: {
130+ if (!blockCurrentIndexChange) {
131+ if (selectedIndex != -1 && selectedIndex != currentIndex) {
132+ selectedIndex = currentIndex;
133+ }
134+ }
153135 }
154− if (item.hasOwnProperty("menuIndex")) {
155− item.menuIndex = Qt.binding(function() { return modelIndex; });
136+
137+ Connections {
138+ target: mainMenu.model ? mainMenu.model : null
139+ onRowsAboutToBeRemoved: {
140+ // track current item deletion.
141+ if (mainMenu.selectedIndex >= first && mainMenu.selectedIndex <= last) {
142+ mainMenu.selectedIndex = -1;
143+ }
144+ }
156145 }
157− }
158146
159− Binding {
160− target: item ? item : null
161− property: "objectName"
162− value: model.action
163− }
147+ delegate: Loader {
148+ id: loader
149+ objectName: "menuItem" + index
150+ width: ListView.view.width
151+ visible: status == Loader.Ready
152+
153+ property int modelIndex: index
154+ sourceComponent: factory.load(model)
155+
156+ onLoaded: {
157+ if (item.hasOwnProperty("selected")) {
158+ item.selected = mainMenu.selectedIndex == index;
159+ }
160+ if (item.hasOwnProperty("menuSelected")) {
161+ item.menuSelected.connect(function() { mainMenu.selectedIndex = index; });
162+ }
163+ if (item.hasOwnProperty("menuDeselected")) {
164+ item.menuDeselected.connect(function() { mainMenu.selectedIndex = -1; });
165+ }
166+ if (item.hasOwnProperty("menuData")) {
167+ item.menuData = Qt.binding(function() { return model; });
168+ }
169+ if (item.hasOwnProperty("menuIndex")) {
170+ item.menuIndex = Qt.binding(function() { return modelIndex; });
171+ }
172+ if (item.hasOwnProperty("clicked")) {
173+ item.clicked.connect(function() {
174+ if (model.hasSubmenu) {
175+ root.push(pageComponent, {
176+ "isSubmenu": true,
177+ "title": model.label,
178+ "menuModel": page.menuModel.submenu(modelIndex)
179+ });
180+ }
181+ });
182+ }
183+ }
164184
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;
185+ Binding {
186+ target: item ? item : null
187+ property: "objectName"
188+ value: model.action
189+ }
190+
191+ // TODO: Fixes lp#1243146
192+ // This is a workaround for a Qt bug. https://bugreports.qt-project.org/browse/QTBUG-34351
193+ Connections {
194+ target: mainMenu
195+ onSelectedIndexChanged: {
196+ if (loader.item && loader.item.hasOwnProperty("selected")) {
197+ loader.item.selected = mainMenu.selectedIndex == index;
198+ }
199+ }
172200 }
173201 }
174202 }
175203 }
176204 }
177205
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− }
206+// function reset()
207+// {
208+// menuStack.reset();
209+// mainMenu.positionViewAtBeginning();
210+// }
188211}
qml/Panel/Indicators/MenuItemFactory.qml+87−1
@@ -28,6 +28,7 @@ import Unity.Platform 1.0
2828Item {
2929 id: menuFactory
3030
31+ property string context
3132 property var rootModel: null
3233 property var menuModel: null
3334
@@ -1041,7 +1042,92 @@ Item {
10411042 }
10421043 }
10431044
1044− function load(modelData, context) {
1045+ Component {
1046+ id: applicationMenu;
1047+
1048+ ListItem {
1049+ property QtObject menuData: null
1050+ property int menuIndex: -1
1051+
1052+ height: layout.height
1053+ enabled: menuData && menuData.sensitive || false
1054+ divider.visible: false
1055+
1056+ onClicked: {
1057+ menuModel.activate(menuIndex);
1058+ }
1059+
1060+ ListItemLayout {
1061+ id: layout
1062+ title.text: menuData && menuData.label || ""
1063+
1064+ Icon {
1065+ source: menuData && menuData.icon || ""
1066+ SlotsLayout.position: SlotsLayout.Leading
1067+ height: units.gu(3)
1068+ }
1069+
1070+ }
1071+ }
1072+ }
1073+
1074+ Component {
1075+ id: submenu
1076+
1077+ ListItem {
1078+ property QtObject menuData: null
1079+ property int menuIndex: -1
1080+
1081+ height: layout.height
1082+ enabled: menuData && menuData.sensitive || false
1083+ divider.visible: false
1084+
1085+ onClicked: {
1086+ menuModel.activate(menuIndex);
1087+ }
1088+
1089+ ListItemLayout {
1090+ id: layout
1091+ title.text: menuData && menuData.label || ""
1092+
1093+ Icon {
1094+ source: menuData && menuData.icon || ""
1095+ SlotsLayout.position: SlotsLayout.Leading
1096+ height: units.gu(3)
1097+ }
1098+
1099+ Icon {
1100+ name: "toolkit_chevron-ltr_1gu"
1101+ SlotsLayout.position: SlotsLayout.Trailing
1102+ width: units.gu(2)
1103+ }
1104+ }
1105+ }
1106+ }
1107+
1108+ Component {
1109+ id: applicationMenuSeparator;
1110+
1111+ ListItems.Divider {
1112+ }
1113+ }
1114+
1115+ function load(modelData) {
1116+ if (context == "application-menu") {
1117+ if (modelData.isSeparator) {
1118+ return applicationMenuSeparator;
1119+ }
1120+ if (modelData.isRadio) {
1121+ }
1122+ if (modelData.isCheck) {
1123+
1124+ }
1125+ if (modelData.hasSubmenu) {
1126+ return submenu;
1127+ }
1128+ return applicationMenu;
1129+ }
1130+
10451131 // tweak indicator-session items
10461132 if (context === "indicator-session") {
10471133 if ((modelData.action === "indicator.logout" || modelData.action === "indicator.suspend" || modelData.action === "indicator.hibernate" ||
qml/Panel/MenuContent.qml+6−8
@@ -27,6 +27,8 @@ Rectangle {
2727
2828 property QtObject indicatorsModel: null
2929 property int currentMenuIndex: -1
30+ property Component pageDelegate
31+
3032 color: theme.palette.normal.background
3133
3234 width: units.gu(40)
@@ -59,18 +61,14 @@ Rectangle {
5961
6062 width: ListView.view.width
6163 height: ListView.view.height
62− objectName: identifier
64+ objectName: item ? item.context : ""
6365 asynchronous: true
6466 visible: ListView.isCurrentItem
6567
66− sourceComponent: IndicatorPage {
67− objectName: identifier + "-page"
68+ property var modelData: model
69+ property var modelIndex: index
6870
69− identifier: model.identifier
70− busName: indicatorProperties.busName
71− actionsObjectPath: indicatorProperties.actionsObjectPath
72− menuObjectPath: indicatorProperties.menuObjectPath
73− }
71+ sourceComponent: pageDelegate
7472
7573 onVisibleChanged: {
7674 // Reset the indicator states
qml/Panel/Panel.qml+364−99
@@ -16,19 +16,40 @@
1616
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
19+import Ubuntu.Layouts 1.0
1920import 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"
2031import "../Components"
2132import "../Components/PanelState"
2233import ".."
34+import "Indicators"
2335
2436Item {
2537 id: root
26− readonly property real panelHeight: indicatorArea.y + d.indicatorHeight
38+ readonly property real panelHeight: indicatorArea.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: fakeApplicationMenus
2746 property alias indicators: __indicators
28− property alias callHint: __callHint
2947 property bool fullscreenMode: false
3048 property real indicatorAreaShowProgress: 1.0
3149 property bool locked: false
50+ property bool greeterShown: false
51+
52+ property string mode: "staged"
3253
3354 MouseArea {
3455 anchors.fill: parent
@@ -42,7 +63,17 @@ Item {
4263 Binding {
4364 target: PanelState
4465 property: "panelHeight"
45− value: indicators.minimizedPanelHeight
66+ value: minimizedPanelHeight
67+ }
68+
69+ FakePanelMenu {
70+ id: fakeApplicationMenus
71+ model: registeredMenuModel.model
72+ }
73+
74+ RegisteredApplicationMenuModel {
75+ id: registeredMenuModel
76+ persistentSurfaceId: PanelState.focusedPersistentSurfaceId
4677 }
4778
4879 Item {
@@ -53,25 +84,24 @@ Item {
5384
5485 transform: Translate {
5586 y: indicators.state === "initial"
56− ? (1.0 - indicatorAreaShowProgress) * -d.indicatorHeight
87+ ? (1.0 - indicatorAreaShowProgress) * - minimizedPanelHeight
5788 : 0
5889 }
5990
6091 BorderImage {
6192 id: indicatorsDropShadow
6293 anchors {
63− fill: indicators
64− leftMargin: -units.gu(1)
65− bottomMargin: -units.gu(1)
94+ fill: __indicators
95+ margins: -units.gu(1)
6696 }
67− visible: !indicators.fullyClosed
97+ visible: !__indicators.fullyClosed
6898 source: "graphics/rectangular_dropshadow.sci"
6999 }
70100
71101 BorderImage {
72102 id: panelDropShadow
73103 anchors {
74− fill: indicatorAreaBackground
104+ fill: panelAreaBackground
75105 bottomMargin: -units.gu(1)
76106 }
77107 visible: PanelState.dropShadow && !callHint.visible
@@ -79,59 +109,289 @@ Item {
79109 }
80110
81111 Rectangle {
82− id: indicatorAreaBackground
83− color: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
112+ id: panelAreaBackground
113+ color: theme.palette.normal.background
84114 anchors {
85115 top: parent.top
86116 left: parent.left
87117 right: parent.right
88118 }
89− height: indicators.minimizedPanelHeight
119+ height: minimizedPanelHeight
90120
91121 Behavior on color { ColorAnimation { duration: UbuntuAnimation.FastDuration } }
92122 }
93123
94− MouseArea {
95− objectName: "windowControlArea"
96− anchors {
97− top: parent.top
98− left: parent.left
99− right: indicators.left
100− }
101− height: indicators.minimizedPanelHeight
102− hoverEnabled: true
103− onClicked: if (callHint.visible) { callHint.showLiveCall(); }
124+ Loader {
125+ id: apppMenuLoader
126+ sourceComponent: mode == "staged" ? stagedLeftPanelComponent : windowedLeftPanelComponent
127+ width: parent.width
128+ height: minimizedPanelHeight
129+
130+ opacity: root.locked ? 0 : 1
131+ visible: opacity != 0
132+ Behavior on opacity { UbuntuNumberAnimation {} }
133+ }
134+
135+ Component {
136+ id: stagedLeftPanelComponent
137+
138+ MouseArea {
139+ id: decorationMouseArea
140+ objectName: "windowControlArea"
141+ anchors {
142+ left: parent.left
143+ right: parent.right
144+ }
145+ height: minimizedPanelHeight
146+ hoverEnabled: !__indicators.shown
147+ onClicked: {
148+ if (callHint.visible) {
149+ callHint.showLiveCall();
150+ }
151+ }
104152
105− onPressed: {
106− if (!callHint.visible) {
107− // let it fall through to the window decoration of the maximized window behind, if any
108− mouse.accepted = false;
153+ BorderImage {
154+ id: applicationMenusDropShadow
155+ anchors {
156+ fill: __applicationMenus
157+ margins: -units.gu(1)
158+ }
159+ visible: !__applicationMenus.fullyClosed
160+ source: "graphics/rectangular_dropshadow.sci"
161+ }
162+
163+ PanelMenu {
164+ id: __applicationMenus
165+
166+ hides: fakeApplicationMenus.hides
167+ model: fakeApplicationMenus.model
168+
169+ width: root.applicationMenuWidth
170+ minimizedPanelHeight: root.minimizedPanelHeight
171+ expandedPanelHeight: root.expandedPanelHeight
172+ openedHeight: root.height
173+ enableHint: !callHint.active && !fullscreenMode
174+ showOnClick: !callHint.visible
175+ panelColor: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
176+ alignment: Qt.AlignLeft
177+
178+ showRowTitle: !expanded
179+ rowTitle: PanelState.title
180+ rowItemDelegate: ActionItem {
181+ id: actionItem
182+ property int ownIndex: index
183+
184+ width: _title.width + units.gu(2)
185+ height: parent.height
186+
187+ action: Action {
188+ text: model.label.replace("_", "&")
189+ }
190+
191+ Label {
192+ id: _title
193+ anchors.centerIn: parent
194+ text: actionItem.text
195+ horizontalAlignment: Text.AlignLeft
196+ color: enabled ? "white" : "#5d5d5d"
197+ }
198+ }
199+
200+ pageDelegate: IndicatorPage {
201+ id: page
202+ context: "application-menu"
203+ menuModel: __applicationMenus.model
204+ submenuIndex: modelIndex
205+ }
206+
207+ enabled: !root.locked && model
208+ opacity: !indicators.expanded ? 1 : 0
209+ visible: opacity != 0
210+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
211+
212+ Binding {
213+ target: fakeApplicationMenus
214+ property: "expanded"
215+ value: __applicationMenus.expanded
216+ }
217+
218+ Connections {
219+ target: fakeApplicationMenus
220+ onHide: __applicationMenus.hide();
221+ onShow: __applicationMenus.show();
222+ }
223+ }
224+
225+ ActiveCallHint {
226+ id: callHint
227+ x: __applicationMenus.x + __applicationMenus.barWidth
228+ height: parent.height
229+
230+ visible: active && (indicators.state == "initial" && __applicationMenus.state == "initial")
231+ greeterShown: root.greeterShown
232+ }
233+
234+ Binding {
235+ target: __indicators
236+ property: "overFlowWidth"
237+ value: {
238+ var width = root.width;
239+ if (callHint.visible) {
240+ width -= callHint.width;
241+ }
242+ if (__applicationMenus.visible) {
243+ width -= __applicationMenus.barWidth;
244+ }
245+ return Math.max(width, 0);
246+ }
247+ }
248+ Binding {
249+ target: panelAreaBackground
250+ property: "color"
251+ value: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
252+ }
253+ Binding {
254+ target: __indicators
255+ property: "enableHint"
256+ value: !callHint.active && !__indicators.fullscreenMode
257+ }
258+ Binding {
259+ target: __indicators
260+ property: "showOnClick"
261+ value: !callHint.visible
262+ }
263+ Connections {
264+ target: __indicators
265+ onShowTapped: {
266+ if (callHint.active) {
267+ callHint.showLiveCall();
268+ }
269+ }
109270 }
110271 }
272+ }
111273
112− // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
113− // cf. https://bugreports.qt.io/browse/QTBUG-32909
114− WindowControlButtons {
115− id: windowControlButtons
116− objectName: "panelWindowControlButtons"
274+ Component {
275+ id: windowedLeftPanelComponent
276+
277+ MouseArea {
278+ id: decorationMouseArea
279+ objectName: "windowControlArea"
117280 anchors {
118281 left: parent.left
119− top: parent.top
120− }
121− height: indicators.minimizedPanelHeight
122−
123− visible: ((PanelState.buttonsVisible && parent.containsMouse) || PanelState.buttonsAlwaysVisible)
124− && !root.locked && !callHint.visible
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
282+ right: parent.right
283+ }
284+ height: minimizedPanelHeight
285+ hoverEnabled: !__indicators.shown
286+ onClicked: if (callHint.visible) { callHint.showLiveCall(); }
287+
288+ onPressed: {
289+ if (!callHint.visible) {
290+ // let it fall through to the window decoration of the maximized window behind, if any
291+ mouse.accepted = false;
292+ }
293+ }
294+
295+ property bool showWindowControls: (PanelState.decorationsVisible && (containsMouse || menuBarLoader.menusRequested)) || PanelState.decorationsAlwaysVisible
296+
297+ // WindowControlButtons inside the mouse area, otherwise QML doesn't grok nested hover events :/
298+ // cf. https://bugreports.qt.io/browse/QTBUG-32909
299+ WindowControlButtons {
300+ id: windowControlButtons
301+ objectName: "panelWindowControlButtons"
302+ anchors {
303+ left: parent.left
304+ top: parent.top
305+ }
306+ height: indicators.minimizedPanelHeight
307+ opacity: decorationMouseArea.showWindowControls ? 1 : 0
308+ visible: opacity != 0
309+ Behavior on opacity { UbuntuNumberAnimation {} }
310+
311+ active: PanelState.decorationsVisible || PanelState.decorationsAlwaysVisible
312+ windowIsMaximized: true
313+ onCloseClicked: PanelState.closeClicked()
314+ onMinimizeClicked: PanelState.minimizeClicked()
315+ onMaximizeClicked: PanelState.restoreClicked()
316+ closeButtonShown: PanelState.closeButtonShown
317+ }
318+
319+ Loader {
320+ id: menuBarLoader
321+ anchors {
322+ left: windowControlButtons.right
323+ leftMargin: units.gu(3)
324+ }
325+ height: parent.height
326+ opacity: windowControlButtons.opacity
327+ visible: opacity != 0
328+ active: fakeApplicationMenus.model
329+
330+ property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
331+
332+ sourceComponent: MenuBarLoader {
333+ height: menuBarLoader.height
334+ enableKeyFilter: valid && PanelState.decorationsVisible
335+ unityMenuModel: fakeApplicationMenus.model
336+ }
337+ }
338+
339+ Label {
340+ id: titleLabel
341+ objectName: "windowDecorationTitle"
342+ anchors {
343+ left: parent.left
344+ leftMargin: units.gu(1)
345+ verticalCenter: parent.verticalCenter
346+ }
347+ color: "white"
348+ opacity: !decorationMouseArea.showWindowControls ? 1 : 0
349+ visible: opacity != 0
350+ verticalAlignment: Text.AlignVCenter
351+ fontSize: "medium"
352+ font.weight: PanelState.decorationsVisible ? Font.Light : Font.Medium
353+ text: PanelState.title
354+ elide: Text.ElideRight
355+ maximumLineCount: 1
356+ Behavior on opacity { UbuntuNumberAnimation {} }
357+ }
358+
359+ ActiveCallHint {
360+ id: callHint
361+ anchors.centerIn: parent
362+ height: minimizedPanelHeight
363+
364+ visible: active && indicators.state == "initial"
365+ greeterShown: root.greeterShown
366+ }
367+
368+ Binding {
369+ target: panelAreaBackground
370+ property: "color"
371+ value: callHint.visible ? theme.palette.normal.positive : theme.palette.normal.background
372+ }
373+ Binding {
374+ target: __indicators
375+ property: "enableHint"
376+ value: !callHint.active && !__indicators.fullscreenMode
377+ }
378+ Binding {
379+ target: __indicators
380+ property: "showOnClick"
381+ value: !callHint.visible
382+ }
383+ Connections {
384+ target: __indicators
385+ onShowTapped: {
386+ if (callHint.active) {
387+ callHint.showLiveCall();
388+ }
389+ }
390+ }
131391 }
132392 }
133393
134− IndicatorsMenu {
394+ PanelMenu {
135395 id: __indicators
136396 objectName: "indicators"
137397
@@ -139,72 +399,77 @@ Item {
139399 top: parent.top
140400 right: parent.right
141401 }
142−
143− shown: false
144− width: root.width - (windowControlButtons.visible ? windowControlButtons.width + titleLabel.width : 0)
145− minimizedPanelHeight: units.gu(3)
146− expandedPanelHeight: units.gu(7)
402+ width: root.indicatorMenuWidth
403+ minimizedPanelHeight: root.minimizedPanelHeight
404+ expandedPanelHeight: root.expandedPanelHeight
147405 openedHeight: root.height
148406
149− overFlowWidth: {
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
156− showOnClick: !callHint.visible
157− panelColor: indicatorAreaBackground.color
407+ overFlowWidth: root.width
408+ enableHint: !fullscreenMode
409+ showOnClick: true
410+ panelColor: panelAreaBackground.color
158411
159− onShowTapped: {
160− if (callHint.active) {
161− callHint.showLiveCall();
162− }
412+ onShownChanged: {
413+ if (d.menuBar) d.menuBar.close();
163414 }
164− }
165415
166− Label {
167− id: titleLabel
168− objectName: "windowDecorationTitle"
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)
416+ rowItemDelegate: IndicatorItem {
417+ id: indicatorItem
418+ objectName: identifier+"-panelItem"
419+
420+ property int ownIndex: index
421+ property bool overflow: parent.width - x > __indicators.overFlowWidth
422+ property bool hidden: !expanded && (overflow || !indicatorVisible || hideSessionIndicator || hideKeyboardIndicator)
423+ // HACK for indicator-session
424+ readonly property bool hideSessionIndicator: identifier == "indicator-session" && Math.min(Screen.width, Screen.height) <= units.gu(60)
425+ // HACK for indicator-keyboard
426+ readonly property bool hideKeyboardIndicator: identifier == "indicator-keyboard" && (AccountsService.keymaps.length < 2 || keyboardsModel.count == 0)
427+
428+ height: parent.height
429+ expanded: indicators.expanded
430+ selected: ListView.isCurrentItem
431+
432+ identifier: model.identifier
433+ busName: indicatorProperties.busName
434+ actionsObjectPath: indicatorProperties.actionsObjectPath
435+ menuObjectPath: indicatorProperties.menuObjectPath
436+
437+ opacity: hidden ? 0.0 : 1.0
438+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
439+
440+ width: ((expanded || indicatorVisible) && !hideSessionIndicator && !hideKeyboardIndicator) ? implicitWidth : 0
441+
442+ Behavior on width { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
177443 }
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− }
190444
191− // TODO here would the Locally integrated menus come
445+ pageDelegate: IndicatorPage {
446+ id: page
447+ objectName: context + "-page"
448+ context: modelData.identifier
449+ submenuIndex: 0
192450
193− ActiveCallHint {
194− id: __callHint
195− anchors {
196− top: parent.top
197− left: parent.left
451+ menuModel: delegate.menuModel
452+ IndicatorDelegate {
453+ id: delegate
454+ busName: modelData.indicatorProperties.busName
455+ actionsObjectPath: modelData.indicatorProperties.actionsObjectPath
456+ menuObjectPath: modelData.indicatorProperties.menuObjectPath
457+ }
198458 }
199− height: indicators.minimizedPanelHeight
200− visible: active && indicators.state == "initial"
459+
460+ enabled: !applicationMenus.expanded
461+ opacity: !applicationMenus.expanded ? 1 : 0
462+ Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
201463 }
202464 }
203465
204− QtObject {
205− id: d
206− objectName: "panelPriv"
207− readonly property real indicatorHeight: indicators.minimizedPanelHeight
466+ InputDeviceModel {
467+ id: keyboardsModel
468+ deviceFilter: InputInfo.Keyboard
469+ }
470+
471+ IndicatorsLight {
472+ id: indicatorLights
208473 }
209474
210475 states: [
@@ -222,7 +487,7 @@ Item {
222487 when: fullscreenMode
223488 PropertyChanges {
224489 target: indicatorArea;
225− anchors.topMargin: indicators.state === "initial" ? -d.indicatorHeight : 0
490+ anchors.topMargin: indicators.state === "initial" ? - minimizedPanelHeight : 0
226491 opacity: indicators.fullyClosed ? 0.0 : 1.0
227492 }
228493 PropertyChanges {
qml/Panel/IndicatorsBar.qml → qml/Panel/PanelBar.qmlrenamed+11−5
@@ -28,7 +28,13 @@ Item {
2828 property alias overFlowWidth: row.overFlowWidth
2929 readonly property alias currentItemIndex: row.currentItemIndex
3030 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.itemDelegate
36+
37+ implicitWidth: flickable.contentWidth
3238
3339 function selectItemAt(lateralPosition) {
3440 if (!expanded) {
@@ -140,10 +146,10 @@ Item {
140146
141147 // we rotate it because we want the Flickable to align its content item
142148 // on the right instead of on the left
143− rotation: 180
149+ rotation: root.alignment != Qt.AlignRight ? 0 : 180
144150
145151 anchors.fill: parent
146− contentWidth: row.width
152+ contentWidth: row.implicitWidth
147153 contentX: d.combinedOffset
148154 interactive: false
149155
@@ -161,7 +167,7 @@ Item {
161167 }
162168 }
163169
164− IndicatorItemRow {
170+ PanelItemRow {
165171 id: row
166172 objectName: "indicatorItemRow"
167173 anchors {
@@ -170,7 +176,7 @@ Item {
170176 }
171177
172178 // Compensate for the Flickable rotation (ie, counter-rotate)
173− rotation: 180
179+ rotation: root.alignment != Qt.AlignRight ? 0 : 180
174180
175181 lateralPosition: {
176182 if (root.lateralPosition == -1) return -1;
qml/Panel/IndicatorItemRow.qml → qml/Panel/PanelItemRow.qmlrenamed+65−93
@@ -15,29 +15,28 @@
1515 */
1616
1717import QtQuick 2.4
18−import QtQuick.Window 2.2
1918import Ubuntu.Components 1.3
2019
21−// for indicator-keyboard
22−import AccountsService 0.1
23−import Unity.InputInfo 0.1
24−
2520Item {
2621 id: root
27− width: row.width
28− height: units.gu(3)
22+ implicitWidth: rowLabel.text !== "" && !expanded ? rowLabel.width : row.width
23+ implicitHeight: units.gu(3)
2924
25+ property bool showRowTitle: false
26+ property alias rowTitle: rowLabel.text
3027 property QtObject indicatorsModel: null
3128 property real overFlowWidth: width
3229 property bool expanded: false
33− property var currentItem
34− readonly property int currentItemIndex: currentItem ? currentItem.ownIndex : -1
30+ readonly property alias currentItem: row.currentItem
31+ readonly property alias currentItemIndex: row.currentIndex
3532
3633 property real unitProgress: 0.0
3734 property real selectionChangeBuffer: units.gu(2)
3835 property bool enableLateralChanges: false
3936 property color hightlightColor: "#ffffff"
4037
38+ property alias itemDelegate: row.delegate
39+
4140 property real lateralPosition: -1
4241 onLateralPositionChanged: {
4342 updateItemFromLateralPosition();
@@ -86,21 +85,21 @@ Item {
8685 }
8786
8887 function indicatorAt(x, y) {
89− var item = row.childAt(x, y);
88+ var item = row.itemAt(x, y);
9089 return item && item.hasOwnProperty("ownIndex") ? item : null;
9190 }
9291
9392 function resetCurrentItem() {
9493 d.firstItemSwitch = true;
95− d.previousItem = undefined;
96− currentItem = undefined;
94+ d.previousItem = null;
95+ row.currentIndex = -1;
9796 }
9897
9998 function setCurrentItemIndex(index) {
100− for (var i = 0; i < row.children.length; i++) {
101− var item = row.children[i];
99+ for (var i = 0; i < row.contentItem.children.length; i++) {
100+ var item = row.contentItem.children[i];
102101 if (item.hasOwnProperty("ownIndex") && item.ownIndex === index) {
103− if (currentItem !== item) currentItem = item;
102+ if (currentItem !== item) row.currentIndex = index;
104103 break;
105104 }
106105 }
@@ -109,18 +108,18 @@ Item {
109108 function selectItemAt(lateralPosition) {
110109 var item = indicatorAt(lateralPosition, 0);
111110 if (item && item.opacity > 0) {
112− currentItem = item;
111+ row.currentIndex = item.ownIndex;
113112 } else {
114113 // Select default item.
115− var searchIndex = lateralPosition > width ? repeater.count - 1 : 0;
114+ var searchIndex = lateralPosition >= width ? row.count - 1 : 0;
116115
117− for (var i = 0; i < row.children.length; i++) {
118− if (row.children[i].hasOwnProperty("ownIndex") && row.children[i].ownIndex === searchIndex) {
119− item = row.children[i];
116+ for (var i = 0; i < row.contentItem.children.length; i++) {
117+ if (row.contentItem.children[i].hasOwnProperty("ownIndex") && row.contentItem.children[i].ownIndex === searchIndex) {
118+ item = row.contentItem.children[i];
120119 break;
121120 }
122121 }
123− if (currentItem !== item) currentItem = item;
122+ if (currentItem !== item) row.currentIndex = item ? item.ownIndex : -1;
124123 }
125124 }
126125
@@ -131,11 +130,6 @@ Item {
131130 property bool forceAlignmentAnimationDisabled: false
132131 }
133132
134− InputDeviceModel {
135− id: keyboardsModel
136− deviceFilter: InputInfo.Keyboard
137− }
138−
139133 onCurrentItemChanged: {
140134 if (d.previousItem) {
141135 d.firstItemSwitch = false;
@@ -143,8 +137,28 @@ Item {
143137 d.previousItem = currentItem;
144138 }
145139
146− Row {
140+ Label {
141+ id: rowLabel
142+ anchors {
143+ left: parent.left
144+ leftMargin: units.gu(1)
145+ verticalCenter: parent.verticalCenter
146+ }
147+ width: implicitWidth + units.gu(2)
148+ elide: Text.ElideRight
149+ maximumLineCount: 1
150+ fontSize: "medium"
151+ font.weight: Font.Medium
152+ color: Theme.palette.selected.backgroundText
153+ opacity: showRowTitle ? 1 : 0
154+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
155+ }
156+
157+ ListView {
147158 id: row
159+ orientation: ListView.Horizontal
160+ model: indicatorsModel
161+ opacity: showRowTitle ? 0 : 1
148162 anchors {
149163 top: parent.top
150164 bottom: parent.bottom
@@ -153,7 +167,7 @@ Item {
153167 // TODO: make this better
154168 // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
155169 // and adjust the highlight X immediately.
156− width: implicitWidth
170+ width: childrenRect.width
157171 Behavior on width {
158172 SequentialAnimation {
159173 ScriptAction {
@@ -166,71 +180,29 @@ Item {
166180 }
167181 }
168182
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
183+ Behavior on opacity { NumberAnimation { duration: UbuntuAnimation.SnapDuration } }
184+ }
217185
218− Behavior on width {
219− NumberAnimation { duration: UbuntuAnimation.SnapDuration; easing: UbuntuAnimation.StandardEasing }
220− }
186+ ListView {
187+ orientation: ListView.Horizontal
188+ model: indicatorsModel
189+ anchors {
190+ top: parent.top
191+ bottom: parent.bottom
192+ }
193+ interactive: expanded
221194
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− }
195+ // TODO: make this better
196+ // when the width changes, the highlight will lag behind due to animation, so we need to disable the animation
197+ // and adjust the highlight X immediately.
198+ width: implicitWidth
199+ Behavior on width {
200+ SequentialAnimation {
201+ ScriptAction {
202+ script: {
203+ d.forceAlignmentAnimationDisabled = true;
204+ highlight.currentItemX = Qt.binding(function() { return currentItem ? currentItem.x : 0 });
205+ d.forceAlignmentAnimationDisabled = false;
234206 }
235207 }
236208 }
@@ -244,7 +216,7 @@ Item {
244216 anchors.bottom: row.bottom
245217 height: units.dp(2)
246218 color: root.hightlightColor
247− visible: currentItem !== undefined
219+ visible: currentItem !== null
248220 opacity: 0.0
249221
250222 width: currentItem ? currentItem.width : 0
@@ -269,7 +241,7 @@ Item {
269241
270242 if (currentItem && currentItem.ownIndex === 0 && distanceFromCenter < 0) {
271243 return 0;
272− } else if (currentItem && currentItem.ownIndex === repeater.count-1 & distanceFromCenter > 0) {
244+ } else if (currentItem && currentItem.ownIndex === row.count-1 & distanceFromCenter > 0) {
273245 return 0;
274246 }
275247 return (distanceFromCenter / (currentItem.width / 4)) * units.gu(1);
qml/Panel/IndicatorsMenu.qml → qml/Panel/PanelMenu.qmlrenamed+30−19
@@ -22,22 +22,30 @@ import "Indicators"
2222
2323Showable {
2424 id: root
25− property alias indicatorsModel: bar.indicatorsModel
25+ property alias model: bar.indicatorsModel
2626 property alias showDragHandle: __showDragHandle
2727 property alias hideDragHandle: __hideDragHandle
2828 property alias overFlowWidth: bar.overFlowWidth
2929 property alias verticalVelocityThreshold: yVelocityCalculator.velocityThreshold
30− property alias currentIndicator: bar.currentIndicator
3130 property int minimizedPanelHeight: units.gu(3)
3231 property int expandedPanelHeight: units.gu(7)
3332 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+
3443 readonly property real unitProgress: Math.max(0, (height - minimizedPanelHeight) / (openedHeight - minimizedPanelHeight))
3544 readonly property bool fullyOpened: unitProgress >= 1
3645 readonly property bool partiallyOpened: unitProgress > 0 && unitProgress < 1.0
3746 readonly property bool fullyClosed: unitProgress == 0
38− property bool enableHint: true
39− property bool showOnClick: true
40− property color panelColor: theme.palette.normal.background
47+ readonly property alias expanded: bar.expanded
48+ readonly property int barWidth: Math.min(bar.width, bar.implicitWidth)
4149
4250 signal showTapped()
4351
@@ -67,21 +75,19 @@ Showable {
6775 ScriptAction { script: root.height = Qt.binding( function(){ return root.minimizedPanelHeight; } ) }
6876 }
6977
78+ shown: false
7079 height: minimizedPanelHeight
71−
72− onUnitProgressChanged: d.updateState()
7380 clip: root.partiallyOpened
7481
75− IndicatorsLight {
76− id: indicatorLights
77− }
82+ onUnitProgressChanged: d.updateState()
7883
7984 // eater
8085 MouseArea {
81− anchors.fill: parent
86+ anchors.fill: content
8287 hoverEnabled: true
8388 acceptedButtons: Qt.AllButtons
8489 onWheel: wheel.accepted = true;
90+ enabled: root.state != "initial"
8591 }
8692
8793 MenuContent {
@@ -94,7 +100,7 @@ Showable {
94100 top: bar.bottom
95101 }
96102 height: openedHeight - bar.height - handle.height
97− indicatorsModel: root.indicatorsModel
103+ indicatorsModel: root.model
98104 visible: root.unitProgress > 0
99105 currentMenuIndex: bar.currentItemIndex
100106 }
@@ -109,6 +115,7 @@ Showable {
109115 }
110116 height: units.gu(2)
111117 active: d.activeDragHandle ? true : false
118+ visible: !root.fullyClosed
112119
113120 //small shadow gradient at bottom of menu
114121 Rectangle {
@@ -129,9 +136,10 @@ Showable {
129136 Rectangle {
130137 anchors.fill: bar
131138 color: panelColor
139+ visible: !root.fullyClosed
132140 }
133141
134− IndicatorsBar {
142+ PanelBar {
135143 id: bar
136144 objectName: "indicatorsBar"
137145
@@ -178,12 +186,14 @@ Showable {
178186
179187 MouseArea {
180188 anchors.bottom: parent.bottom
181− anchors.left: parent.left
182− anchors.right: parent.right
189+ anchors.left: alignment == Qt.AlignLeft ? parent.left : undefined
190+ anchors.right: alignment == Qt.AlignRight ? parent.right : undefined
191+ width: root.barWidth // show handle should only cover panel items.
183192 height: minimizedPanelHeight
184193 enabled: __showDragHandle.enabled && showOnClick
185194 onClicked: {
186− bar.selectItemAt(mouseX)
195+ var barPosition = mapToItem(bar, mouseX, mouseY);
196+ bar.selectItemAt(barPosition.x)
187197 root.show()
188198 }
189199 }
@@ -192,8 +202,9 @@ Showable {
192202 id: __showDragHandle
193203 objectName: "showDragHandle"
194204 anchors.bottom: parent.bottom
195− anchors.left: parent.left
196− anchors.right: parent.right
205+ anchors.left: alignment == Qt.AlignLeft ? parent.left : undefined
206+ anchors.right: alignment == Qt.AlignRight ? parent.right : undefined
207+ width: root.barWidth // show handle should only cover panel items.
197208 height: minimizedPanelHeight
198209 direction: Direction.Downwards
199210 enabled: !root.shown && root.available
@@ -311,7 +322,7 @@ Showable {
311322 script: {
312323 yVelocityCalculator.reset();
313324 // initial item selection
314− if (!d.hasCommitted) bar.selectItemAt(d.activeDragHandle ? d.activeDragHandle.touchPosition.x : -1);
325+ if (!d.hasCommitted) bar.selectItemAt(d.rowMappedLateralPosition);
315326 d.hasCommitted = false;
316327 }
317328 }
qml/Shell.qml+12−8
@@ -456,7 +456,7 @@ StyledItem {
456456 Greeter {
457457
458458 enabled: panel.indicators.fullyClosed // hides OSK when panel is open
459− hides: [launcher, panel.indicators]
459+ hides: [launcher, panel.indicators, panel.applicationMenus]
460460 tabletMode: shell.usageScenario != "phone"
461461 launcherOffset: launcher.progress
462462 forcedUnlock: wizard.active || shell.mode === "full-shell"
@@ -569,18 +569,21 @@ StyledItem {
569569 id: panel
570570 objectName: "panel"
571571 anchors.fill: parent //because this draws indicator menus
572+
573+ mode: shell.usageScenario == "desktop" ? "windowed" : "staged"
574+ minimizedPanelHeight: units.gu(3)
575+ expandedPanelHeight: units.gu(7)
576+ indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
577+ applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
578+
572579 indicators {
573580 hides: [launcher]
574581 available: tutorial.panelEnabled
575582 && ((!greeter || !greeter.locked) || AccountsService.enableIndicatorsWhileLocked)
576583 && (!greeter || !greeter.hasLockedApp)
577584 && !shell.waitingOnGreeter
578− width: parent.width > units.gu(60) ? units.gu(40) : parent.width
579585
580− minimizedPanelHeight: units.gu(3)
581− expandedPanelHeight: units.gu(7)
582−
583− indicatorsModel: Indicators.IndicatorsModel {
586+ model: Indicators.IndicatorsModel {
584587 // tablet and phone both use the same profile
585588 // FIXME: use just "phone" for greeter too, but first fix
586589 // greeter app launching to either load the app inside the
@@ -592,8 +595,8 @@ StyledItem {
592595 }
593596 }
594597
595− callHint {
596− greeterShown: greeter.shown
598+ applicationMenus {
599+ hides: [launcher]
597600 }
598601
599602 readonly property bool focusedSurfaceIsFullscreen: MirFocusController.focusedSurface
@@ -602,6 +605,7 @@ StyledItem {
602605 fullscreenMode: (focusedSurfaceIsFullscreen && !LightDMService.greeter.active && launcher.progress == 0)
603606 || greeter.hasLockedApp
604607 locked: greeter && greeter.active
608+ greeterShown: greeter && greeter.shown
605609 }
606610
607611 Launcher {
qml/Stages/DecoratedWindow.qml+71−32
@@ -17,6 +17,9 @@
1717import QtQuick 2.4
1818import Ubuntu.Components 1.3
1919import Unity.Application 0.1
20+import Unity.ApplicationMenu 0.1
21+import Unity.Indicators 0.1 as Indicators
22+import "../Components/PanelState"
2023
2124FocusScope {
2225 id: root
@@ -91,38 +94,6 @@ FocusScope {
9194 visible: !fullscreen
9295 }
9396
94− WindowDecoration {
95− id: decoration
96− target: root.parent
97− objectName: "appWindowDecoration"
98− anchors { left: parent.left; top: parent.top; right: parent.right }
99− height: units.gu(3)
100− width: root.width
101− title: applicationWindow.title
102− visible: root.decorationShown
103−
104− onCloseClicked: root.closeClicked();
105− onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
106− onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
107− onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
108− onMinimizeClicked: root.minimizeClicked();
109− onPressed: root.decorationPressed();
110−
111− onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
112− onPositionChanged: moveHandler.handlePositionChanged(mouse)
113− onReleased: {
114− root.decorationReleased();
115− moveHandler.handleReleased();
116− }
117− }
118−
119− MoveHandler {
120− id: moveHandler
121− objectName: "moveHandler"
122− target: root.parent
123− buttonsWidth: decoration.buttonsWidth
124− }
125−
12697 ApplicationWindow {
12798 id: applicationWindow
12899 objectName: "appWindow"
@@ -154,4 +125,72 @@ FocusScope {
154125 angle: rotationAngle
155126 }
156127 }
128+
129+ MouseArea {
130+ anchors { left: parent.left; top: parent.top; right: parent.right }
131+ height: units.gu(3)
132+
133+ drag.target: Item {}
134+ drag.filterChildren: true
135+ drag.threshold: 0
136+
137+ onPressed: root.decorationPressed();
138+ onPressedChanged: moveHandler.handlePressedChanged(pressed, pressedButtons, mouseX, mouseY)
139+ onPositionChanged: moveHandler.handlePositionChanged(mouse)
140+ onReleased: {
141+ root.decorationReleased();
142+ moveHandler.handleReleased();
143+ }
144+
145+ WindowDecoration {
146+ id: decoration
147+ target: root.parent
148+ objectName: "appWindowDecoration"
149+ anchors.fill: parent
150+ title: applicationWindow.title
151+ visible: root.decorationShown
152+
153+ onCloseClicked: root.closeClicked();
154+ onMaximizeClicked: { root.decorationPressed(); root.maximizeClicked(); }
155+ onMaximizeHorizontallyClicked: { root.decorationPressed(); root.maximizeHorizontallyClicked(); }
156+ onMaximizeVerticallyClicked: { root.decorationPressed(); root.maximizeVerticallyClicked(); }
157+ onMinimizeClicked: root.minimizeClicked();
158+
159+ enableMenus: active &&
160+ (PanelState.focusedPersistentSurfaceId !== surface.persistentId ||
161+ (PanelState.focusedPersistentSurfaceId === surface.persistentId && !PanelState.focusedSurfaceMaximized))
162+ menu: sharedAppModel.model
163+
164+ Indicators.SharedUnityMenuModel {
165+ id: sharedAppModel
166+ property var menus: surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : []
167+ property var menuService: menus.length > 0 ? menus[0] : null
168+
169+ busName: menuService ? menuService.service : ""
170+ menuObjectPath: menuService && menuService.menuPath ? menuService.menuPath : ""
171+ actions: menuService && menuService.actionPath ? { "unity": menuService.actionPath } : {}
172+ }
173+
174+ Connections {
175+ target: ApplicationMenuRegistry
176+ onSurfaceMenuRegistered: {
177+ if (surface && surfaceId === surface.persistentId) {
178+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
179+ }
180+ }
181+ onSurfaceMenuUnregistered: {
182+ if (surface && surfaceId === surface.persistentId) {
183+ sharedAppModel.menus = Qt.binding(function() { return surface ? ApplicationMenuRegistry.getMenusForSurface(surface.persistentId) : [] });
184+ }
185+ }
186+ }
187+ }
188+ }
189+
190+ MoveHandler {
191+ id: moveHandler
192+ objectName: "moveHandler"
193+ target: root.parent
194+ buttonsWidth: decoration.buttonsWidth
195+ }
157196}
qml/Stages/DesktopStage.qml+19−5
@@ -176,8 +176,8 @@ AbstractStage {
176176
177177 Binding {
178178 target: PanelState
179− property: "buttonsVisible"
180− value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus
179+ property: "decorationsVisible"
180+ value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
181181 && spread.state == ""
182182 }
183183
@@ -185,7 +185,7 @@ AbstractStage {
185185 target: PanelState
186186 property: "title"
187187 value: {
188− if (priv.focusedAppDelegate !== null && spread.state == "") {
188+ if (spread.state == "") {
189189 if (priv.focusedAppDelegate.maximized)
190190 return priv.focusedAppDelegate.title
191191 else
@@ -196,6 +196,20 @@ AbstractStage {
196196 when: priv.focusedAppDelegate
197197 }
198198
199+ Binding {
200+ target: PanelState
201+ property: "focusedPersistentSurfaceId"
202+ value: {
203+ if (priv.focusedAppDelegate !== null && spread.state == "") {
204+ if (priv.focusedAppDelegate.surface) {
205+ return priv.focusedAppDelegate.surface.persistentId;
206+ }
207+ }
208+ return "";
209+ }
210+ when: priv.focusedAppDelegate
211+ }
212+
199213 Binding {
200214 target: PanelState
201215 property: "dropShadow"
@@ -210,7 +224,7 @@ AbstractStage {
210224
211225 Component.onDestruction: {
212226 PanelState.title = "";
213− PanelState.buttonsVisible = false;
227+ PanelState.decorationsVisible = false;
214228 PanelState.dropShadow = false;
215229 }
216230
@@ -678,7 +692,7 @@ AbstractStage {
678692
679693 Binding {
680694 target: PanelState
681− property: "buttonsAlwaysVisible"
695+ property: "decorationsAlwaysVisible"
682696 value: appDelegate && appDelegate.maximized && touchControls.overlayShown
683697 }
684698
qml/Stages/PhoneStage.qml+40−1
@@ -22,6 +22,7 @@ import Unity.Session 0.1
2222import Utils 0.1
2323import Powerd 0.1
2424import "../Components"
25+import "../Components/PanelState"
2526
2627AbstractStage {
2728 id: root
@@ -57,6 +58,39 @@ AbstractStage {
5758 }
5859 }
5960
61+ Binding {
62+ target: PanelState
63+ property: "title"
64+ value: {
65+ if (priv.focusedAppDelegate !== null) {
66+ return priv.focusedAppDelegate.title
67+ }
68+ return ""
69+ }
70+ when: priv.focusedAppDelegate
71+ }
72+
73+ Binding {
74+ target: PanelState
75+ property: "focusedPersistentSurfaceId"
76+ value: {
77+ console.log("PLOP", priv.focusedAppDelegatem, priv.focusedAppDelegate.surface)
78+ if (priv.focusedAppDelegate && priv.fullyShowingFocusedApp) {
79+ if (priv.focusedAppDelegate.surface) {
80+ return priv.focusedAppDelegate.surface.persistentId;
81+ }
82+ }
83+ return "";
84+ }
85+ when: priv.focusedAppDelegate
86+ }
87+
88+ Binding {
89+ target: PanelState
90+ property: "decorationsVisible"
91+ value: priv.focusedAppDelegate && priv.fullyShowingFocusedApp
92+ }
93+
6094 FocusScope {
6195 focus: root.altTabPressed
6296
@@ -168,7 +202,7 @@ AbstractStage {
168202
169203 property bool focusedAppOrientationChangesEnabled: false
170204 readonly property int firstSpreadIndex: root.focusFirstApp ? 1 : 0
171− property var focusedAppDelegate
205+ property var focusedAppDelegate: null
172206 // NB! This may differ from applicationManager.focusedApplicationId if focusedAppDelegate
173207 // contains a screenshot instead of a surface.
174208 property string focusedAppId: focusedAppDelegate ? focusedAppDelegate.application.appId : ""
@@ -509,6 +543,11 @@ AbstractStage {
509543
510544 readonly property bool isDash: model.application.appId == "unity8-dash"
511545
546+ readonly property string title: {
547+ if (model.surface && model.surface.name !== "") return model.surface.name;
548+ return model.application.name;
549+ }
550+
512551 Component.onCompleted: {
513552 // NB: We're differentiating if this delegate was created in response to a new entry in the model
514553 // or if the Repeater is just populating itself with delegates to match the model it received.
qml/Stages/TabletStage.qml+13
@@ -21,6 +21,7 @@ import Unity.Application 0.1
2121import Utils 0.1
2222import Powerd 0.1
2323import "../Components"
24+import "../Components/PanelState"
2425
2526AbstractStage {
2627 id: root
@@ -311,6 +312,18 @@ AbstractStage {
311312 when: root.parent && !spreadRepeater.startingUp
312313 }
313314
315+ Binding {
316+ target: PanelState
317+ property: "title"
318+ value: {
319+ if (priv.focusedAppDelegate !== null) {
320+ return priv.focusedAppDelegate.title
321+ }
322+ return ""
323+ }
324+ when: priv.focusedAppDelegate
325+ }
326+
314327 Flickable {
315328 id: spreadView
316329 objectName: "spreadView"
qml/Stages/WindowDecoration.qml+78−19
@@ -15,18 +15,23 @@
1515 */
1616
1717import QtQuick 2.4
18+import QtQuick.Layouts 1.1
1819import Ubuntu.Components 1.3
20+import Unity.Application 0.1
1921import "../Components"
22+import "../Components/PanelState"
23+import "../ApplicationMenus"
2024
2125MouseArea {
2226 id: root
23− clip: true
2427
2528 property Item target // appDelegate
2629 property alias title: titleLabel.text
2730 property alias maximizeButtonShown: buttons.maximizeButtonShown
2831 property bool active: false
2932 property alias overlayShown: buttons.overlayShown
33+ property var menu: undefined
34+ property bool enableMenus: true
3035
3136 readonly property real buttonsWidth: buttons.width + row.spacing
3237
@@ -48,21 +53,41 @@ MouseArea {
4853 // do not let unhandled wheel event pass thru the decoration
4954 onWheel: wheel.accepted = true;
5055
56+ QtObject {
57+ id: priv
58+ property var menuBar: menuBarLoader.item
59+
60+ property bool enableMenus: root.enableMenus &&
61+ menuBar &&
62+ menuBar.valid
63+
64+ property bool shouldShowMenus : enableMenus &&
65+ (menuBar.showRequested || root.containsMouse)
66+ }
67+
5168 Rectangle {
69+ id: background
5270 anchors.fill: parent
53− anchors.bottomMargin: -radius
5471 radius: units.gu(.5)
5572 color: theme.palette.normal.background
5673 }
5774
58− Row {
75+ Rectangle {
76+ anchors {
77+ bottom: background.bottom
78+ left: parent.left
79+ right: parent.right
80+ }
81+ height: background.radius
82+ color: theme.palette.normal.background
83+ }
84+
85+ RowLayout {
5986 id: row
6087 anchors {
6188 fill: parent
6289 leftMargin: overlayShown ? units.gu(5) : units.gu(1)
6390 rightMargin: units.gu(1)
64− topMargin: units.gu(0.5)
65− bottomMargin: units.gu(0.5)
6691 }
6792 Behavior on anchors.leftMargin {
6893 UbuntuNumberAnimation {}
@@ -72,7 +97,12 @@ MouseArea {
7297
7398 WindowControlButtons {
7499 id: buttons
75− height: parent.height
100+ anchors {
101+ top: parent.top
102+ bottom: parent.bottom
103+ topMargin: units.gu(0.5)
104+ bottomMargin: units.gu(0.5)
105+ }
76106 active: root.active
77107 onCloseClicked: root.closeClicked();
78108 onMinimizeClicked: root.minimizeClicked();
@@ -82,19 +112,48 @@ MouseArea {
82112 closeButtonShown: root.target.application.appId !== "unity8-dash"
83113 }
84114
85− Label {
86− id: titleLabel
87− objectName: "windowDecorationTitle"
88− color: root.active ? "white" : UbuntuColors.slate
89− height: parent.height
90− width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin
91− verticalAlignment: Text.AlignVCenter
92− fontSize: "medium"
93− font.weight: root.active ? Font.Light : Font.Medium
94− elide: Text.ElideRight
95− opacity: overlayShown ? 0 : 1
96− visible: opacity != 0
97− Behavior on opacity { UbuntuNumberAnimation {} }
115+ Item {
116+ Layout.preferredHeight: parent.height
117+ Layout.fillWidth: true
118+
119+ Label {
120+ id: titleLabel
121+ objectName: "windowDecorationTitle"
122+ color: root.active ? "white" : UbuntuColors.slate
123+ height: parent.height
124+ width: parent.width
125+ verticalAlignment: Text.AlignVCenter
126+ fontSize: "medium"
127+ font.weight: root.active ? Font.Light : Font.Medium
128+ elide: Text.ElideRight
129+ opacity: overlayShown || priv.shouldShowMenus ? 0 : 1
130+ visible: opacity != 0
131+ Behavior on opacity { UbuntuNumberAnimation {} }
132+ }
133+
134+ Loader {
135+ id: menuBarLoader
136+ objectName: "menuBarLoader"
137+ anchors.bottom: parent.bottom
138+ height: parent.height
139+ width: parent.width
140+ sourceComponent: root.menu ? menuBarComponent : undefined
141+
142+ Component {
143+ id: menuBarComponent
144+ MenuBarLoader {
145+ id: menuBar
146+ height: menuBarLoader.height
147+ enableKeyFilter: priv.enableMenus
148+ unityMenuModel: root.menu
149+ }
150+ }
151+
152+ opacity: !overlayShown && priv.shouldShowMenus ? 1 : 0
153+ visible: opacity == 1
154+ Behavior on opacity { UbuntuNumberAnimation {} }
155+ }
98156 }
99157 }
158+
100159}
src/main.cpp+2−2
@@ -23,8 +23,8 @@ int main(int argc, const char *argv[])
2323{
2424 qSetMessagePattern("[%{time yyyy-MM-dd:hh:mm:ss.zzz}] %{if-category}%{category}: %{endif}%{message}");
2525
26− bool isMirServer = false;
27− if (qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
26+ bool isMirServer = qgetenv("QT_QPA_PLATFORM") == "mirserver";
27+ if (!isMirServer && qgetenv("QT_QPA_PLATFORM") == "ubuntumirclient") {
2828 setenv("QT_QPA_PLATFORM", "mirserver", 1 /* overwrite */);
2929 isMirServer = true;
3030 }
tests/mocks/QMenuModel/unitymenumodel.cpp+47−6
@@ -29,7 +29,9 @@ enum MenuRoles {
2929 ActionStateRole,
3030 IsCheckRole,
3131 IsRadioRole,
32− IsToggledRole
32+ IsToggledRole,
33+ ShortcutRole,
34+ HasSubmenuRole
3335};
3436
3537UnityMenuModel::UnityMenuModel(QObject *parent)
@@ -141,7 +143,29 @@ int UnityMenuModel::columnCount(const QModelIndex&) const
141143
142144QVariant UnityMenuModel::data(const QModelIndex &index, int role) const
143145{
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();
145169}
146170
147171QVariantMap UnityMenuModel::rowData(int row) const
@@ -149,12 +173,14 @@ QVariantMap UnityMenuModel::rowData(int row) const
149173 if (m_modelData.count() <= row) {
150174 return QVariantMap();
151175 }
152− return m_modelData[row].toMap()["rowData"].toMap();
176+ QVariantMap vRow = m_modelData.value(row, QVariantMap()).toMap();
177+ return vRow["rowData"].toMap();
153178}
154179
155180QVariant UnityMenuModel::subMenuData(int row) const
156181{
157− return m_modelData[row].toMap()["submenu"];
182+ QVariantMap v = m_modelData.value(row, QVariantMap()).toMap();
183+ return v.value("submenu", QVariant());
158184}
159185
160186QModelIndex UnityMenuModel::index(int row, int column, const QModelIndex&) const
@@ -182,6 +208,8 @@ QHash<int, QByteArray> UnityMenuModel::roleNames() const
182208 names[IsCheckRole] = "isCheck";
183209 names[IsRadioRole] = "isRadio";
184210 names[IsToggledRole] = "isToggled";
211+ names[ShortcutRole] = "shortcut";
212+ names[HasSubmenuRole] = "hasSubmenu";
185213
186214 return names;
187215}
@@ -201,6 +229,7 @@ QObject * UnityMenuModel::submenu(int position, QQmlComponent*)
201229 UnityMenuModel*& model = submenus[position];
202230 if (!model) {
203231 model = new UnityMenuModel(this);
232+ connect(model, &UnityMenuModel::activated, this, &UnityMenuModel::activated);
204233 }
205234 if (model->modelData() != submenuData) {
206235 model->setModelData(submenuData);
@@ -225,11 +254,23 @@ QVariant UnityMenuModel::get(int row, const QByteArray &role)
225254 roles.insert(names[role], role);
226255 }
227256
228− return this->data(this->index(row, 0), roles[role]);
257+ return data(index(row, 0), roles[role]);
229258}
230259
231−void UnityMenuModel::activate(int, const QVariant&)
260+void UnityMenuModel::activate(int row, const QVariant&)
232261{
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());
233274}
234275
235276void UnityMenuModel::changeState(int, const QVariant&)
tests/mocks/QMenuModel/unitymenumodel.h+3−1
@@ -61,7 +61,7 @@ public:
6161
6262 QString nameOwner() const;
6363
64− int rowCount(const QModelIndex &parent = QModelIndex()) const override;
64+ Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override;
6565 int columnCount(const QModelIndex &parent = QModelIndex()) const override;
6666 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
6767 QModelIndex index(int row, int column = 0, const QModelIndex &parent = QModelIndex()) const override;
@@ -88,6 +88,8 @@ Q_SIGNALS:
8888 // Internal mock usage
8989 void modelDataChanged();
9090
91+ void activated(const QString& action);
92+
9193private:
9294 QVariantMap rowData(int row) const;
9395 QVariant subMenuData(int row) const;
tests/qmltests/CMakeLists.txt+4
@@ -6,6 +6,8 @@ add_unity8_qmltest(. DisabledScreenNotice)
66add_unity8_qmltest(. Shell)
77add_unity8_qmltest(. ShellWithPin)
88add_unity8_qmltest(. DeviceConfiguration)
9+add_unity8_qmltest(ApplicationMenus MenuBar)
10+add_unity8_qmltest(ApplicationMenus MenuPage)
911add_unity8_qmltest(Components Background)
1012add_unity8_qmltest(Components Carousel)
1113add_unity8_qmltest(Components Dialogs)
@@ -87,12 +89,14 @@ add_unity8_qmltest(Panel/Indicators IndicatorsLight)
8789add_unity8_qmltest(Panel/Indicators MenuItemFactory)
8890add_unity8_qmltest(Panel/Indicators MessageMenuItemFactory)
8991add_unity8_qmltest(Stages ApplicationWindow)
92+add_unity8_qmltest(Stages DecoratedWindow)
9093add_unity8_qmltest(Stages DesktopStage)
9194add_unity8_qmltest(Stages PhoneStage)
9295add_unity8_qmltest(Stages SpreadDelegate)
9396add_unity8_qmltest(Stages SurfaceContainer)
9497add_unity8_qmltest(Stages TabletStage)
9598add_unity8_qmltest(Stages WindowResizeArea)
99+add_unity8_qmltest(Stages WindowDecoration)
96100add_unity8_qmltest(Stages Splash)
97101add_unity8_qmltest(Tutorial Tutorial)
98102add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
tests/qmltests/Panel/tst_Panel.qml+76−19
@@ -19,26 +19,42 @@ import QtQuick.Layouts 1.1
1919import QtTest 1.0
2020import Unity.Test 0.1
2121import Ubuntu.Components 1.3
22+import Ubuntu.Components.ListItems 1.3 as ListItem
2223import Unity.Application 0.1
24+import Unity.ApplicationMenu 0.1
2325import Unity.Indicators 0.1 as Indicators
2426import Ubuntu.Telephony 0.1 as Telephony
2527import "../../../qml/Panel"
2628import "../../../qml/Components/PanelState"
29+import "../Stages"
2730
2831IndicatorTest {
2932 id: root
30− width: units.gu(100)
33+ width: units.gu(120)
3134 height: units.gu(71)
3235 color: "black"
3336
34− Component.onCompleted: theme.name = "Ubuntu.Components.Themes.SuruDark"
35−
3637 Binding {
3738 target: mouseEmulation
3839 property: "checked"
3940 value: !windowControlsCB.checked
4041 }
4142
43+ DesktopMenuData { id: appMenuData }
44+
45+ Component.onCompleted: {
46+ QuickUtils.keyboardAttached = true;
47+ theme.name = "Ubuntu.Components.Themes.SuruDark"
48+
49+ ApplicationMenuRegistry.RegisterSurfaceMenu("dialerAppSurfaceId", "/dialerapp", "/dialerapp", ":2");
50+ Indicators.UnityMenuModelCache.setCachedModelData("/dialerapp", appMenuData.dialerData);
51+ }
52+
53+ Rectangle {
54+ anchors.fill: parent
55+ color: "darkgrey"
56+ }
57+
4258 RowLayout {
4359 anchors.fill: parent
4460 anchors.margins: units.gu(1)
@@ -59,12 +75,28 @@ IndicatorTest {
5975 Panel {
6076 id: panel
6177 anchors.fill: parent
78+ mode: "staged"
79+
80+ indicatorMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
81+ applicationMenuWidth: parent.width > units.gu(60) ? units.gu(40) : parent.width
82+
83+ applicationMenus {
84+ model: sharedAppModel.model
85+ hides: [ panel.indicators ]
86+ }
87+
6288 indicators {
63− width: parent.width > units.gu(60) ? units.gu(40) : parent.width
64− indicatorsModel: root.indicatorsModel
89+ model: root.indicatorsModel
90+ hides: [ panel.applicationMenus ]
6591 }
6692
67− property real panelAndSeparatorHeight: panel.indicators.minimizedPanelHeight
93+ Indicators.SharedUnityMenuModel {
94+ id: sharedAppModel
95+
96+ busName: ":2"
97+ menuObjectPath: "/dialerapp"
98+ actions: { "unity": "/dialerapp" }
99+ }
68100 }
69101 }
70102 }
@@ -73,6 +105,16 @@ IndicatorTest {
73105 Layout.alignment: Qt.AlignTop
74106 Layout.fillWidth: false
75107
108+ ListItem.ItemSelector {
109+ anchors { left: parent.left; right: parent.right }
110+ activeFocusOnPress: false
111+ text: "Mode"
112+ model: ["staged", "windowed" ]
113+ onSelectedIndexChanged: {
114+ panel.mode = model[selectedIndex];
115+ }
116+ }
117+
76118 Button {
77119 Layout.fillWidth: true
78120 text: panel.indicators.shown ? "Hide" : "Show"
@@ -107,25 +149,22 @@ IndicatorTest {
107149 Layout.fillWidth: true
108150 CheckBox {
109151 id: windowControlsCB
110− onClicked: PanelState.buttonsVisible = checked
152+ onClicked: PanelState.decorationsVisible = checked
111153 }
112154 Label {
113− text: "Show window controls"
155+ text: "Show window decorations"
156+ color: "white"
114157 }
115158 }
116159
117160 RowLayout {
118161 Layout.fillWidth: true
119162 CheckBox {
120− onClicked: {
121− if (checked)
122− PanelState.title = "Fake window title"
123− else
124− PanelState.title = ""
125− }
163+ onClicked: PanelState.title = checked ? "Fake window title" : ""
126164 }
127165 Label {
128166 text: "Show fake window title"
167+ color: "white"
129168 }
130169 }
131170
@@ -135,6 +174,19 @@ IndicatorTest {
135174 color: "black"
136175 }
137176
177+ ApplicationCheckBox {
178+ id: applicationCheckBox
179+ Layout.fillWidth: true
180+ appId: "dialer-app"
181+ onCheckedChanged: PanelState.focusedPersistentSurfaceId = checked ? "dialer-app" : "";
182+ }
183+
184+ Rectangle {
185+ Layout.preferredHeight: units.dp(1);
186+ Layout.fillWidth: true;
187+ color: "black"
188+ }
189+
138190 Repeater {
139191 model: root.originalModelData
140192 RowLayout {
@@ -145,6 +197,7 @@ IndicatorTest {
145197 Label {
146198 Layout.fillWidth: true
147199 text: modelData["identifier"]
200+ color: "white"
148201 }
149202
150203 CheckBox {
@@ -153,6 +206,7 @@ IndicatorTest {
153206 }
154207 Label {
155208 text: "visible"
209+ color: "white"
156210 }
157211 }
158212 }
@@ -163,7 +217,10 @@ IndicatorTest {
163217 color: "black"
164218 }
165219
166− MouseTouchEmulationCheckbox { id: mouseEmulation }
220+ MouseTouchEmulationCheckbox {
221+ id: mouseEmulation
222+ color: "white"
223+ }
167224 }
168225 }
169226
@@ -229,11 +286,11 @@ IndicatorTest {
229286 { tag: "pinned", fullscreen: false, call: null,
230287 indicatorY: 0 },
231288 { tag: "fullscreen", fullscreen: true, call: null,
232− indicatorY: -panel.panelAndSeparatorHeight },
289+ indicatorY: -panel.indicators.minimizedPanelHeight },
233290 { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
234291 indicatorY: 0},
235292 { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
236− indicatorY: -panel.panelAndSeparatorHeight }
293+ indicatorY: -panel.indicators.minimizedPanelHeight }
237294 ];
238295 }
239296
@@ -291,11 +348,11 @@ IndicatorTest {
291348 { tag: "pinned", fullscreen: false, call: null,
292349 indicatorY: 0 },
293350 { tag: "fullscreen", fullscreen: true, call: null,
294− indicatorY: -panel.panelAndSeparatorHeight },
351+ indicatorY: -panel.indicators.minimizedPanelHeight },
295352 { tag: "pinned-callActive", fullscreen: false, call: phoneCall,
296353 indicatorY: 0},
297354 { tag: "fullscreen-callActive", fullscreen: true, call: phoneCall,
298− indicatorY: -panel.panelAndSeparatorHeight }
355+ indicatorY: -panel.indicators.minimizedPanelHeight }
299356 ];
300357 }
301358
tests/qmltests/Stages/tst_DesktopStage.qml+16
@@ -19,6 +19,8 @@ import QtTest 1.0
1919import Ubuntu.Components 1.3
2020import Ubuntu.Components.ListItems 1.3
2121import Unity.Application 0.1
22+import Unity.ApplicationMenu 0.1
23+import Unity.Indicators 0.1 as Indicators
2224import Unity.Test 0.1
2325import WindowManager 0.1
2426import Utils 0.1
@@ -41,9 +43,23 @@ Item {
4143 value: false
4244 }
4345
46+ DesktopMenuData {
47+ id: appMenuData
48+ }
49+
4450 Component.onCompleted: {
51+ QuickUtils.keyboardAttached = true;
4552 theme.name = "Ubuntu.Components.Themes.SuruDark";
4653 resetGeometry();
54+
55+ ApplicationMenuRegistry.RegisterSurfaceMenu("DialerId", "/dialerapp", "/dialerapp", ":0");
56+ Indicators.UnityMenuModelCache.setCachedModelData("/dialerapp", appMenuData.dialerData);
57+
58+ ApplicationMenuRegistry.RegisterSurfaceMenu("CameraId", "/cameraapp", "/cameraapp", ":1");
59+ Indicators.UnityMenuModelCache.setCachedModelData("/cameraapp", appMenuData.cameraData);
60+
61+ ApplicationMenuRegistry.RegisterSurfaceMenu("GalleryId", "/galleryapp", "/galleryapp", ":2");
62+ Indicators.UnityMenuModelCache.setCachedModelData("/galleryapp", appMenuData.galleryData);
4763 }
4864
4965 function resetGeometry() {
tests/qmltests/tst_Shell.qml+19−4
@@ -24,6 +24,7 @@ import Ubuntu.Components 1.3
2424import Ubuntu.Components.ListItems 1.3 as ListItem
2525import Ubuntu.Telephony 0.1 as Telephony
2626import Unity.Application 0.1
27+import Unity.ApplicationMenu 0.1
2728import Unity.Connectivity 0.1
2829import Unity.Indicators 0.1
2930import Unity.Notifications 1.0
@@ -32,6 +33,7 @@ import Unity.Test 0.1
3233import Powerd 0.1
3334import Wizard 0.1 as Wizard
3435import Utils 0.1
36+import Unity.Indicators 0.1 as Indicators
3537
3638import "../../qml"
3739import "../../qml/Components"
@@ -49,6 +51,19 @@ Rectangle {
4951 LightDM.Greeter.mockMode = "single";
5052 LightDM.Users.mockMode = "single";
5153 shellLoader.active = true;
54+
55+ ApplicationMenuRegistry.RegisterSurfaceMenu("DialerId", "/dialerapp", "/dialerapp", ":0");
56+ Indicators.UnityMenuModelCache.setCachedModelData("/dialerapp", appMenuData.dialerData);
57+
58+ ApplicationMenuRegistry.RegisterSurfaceMenu("CameraId", "/cameraapp", "/cameraapp", ":1");
59+ Indicators.UnityMenuModelCache.setCachedModelData("/cameraapp", appMenuData.cameraData);
60+
61+ ApplicationMenuRegistry.RegisterSurfaceMenu("GalleryId", "/galleryapp", "/galleryapp", ":2");
62+ Indicators.UnityMenuModelCache.setCachedModelData("/galleryapp", appMenuData.galleryData);
63+ }
64+
65+ DesktopMenuData {
66+ id: appMenuData
5267 }
5368
5469 Item {
@@ -1997,21 +2012,21 @@ Rectangle {
19972012 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
19982013
19992014 tryCompare(appDelegate, "state", "normal");
2000− tryCompare(PanelState, "buttonsVisible", false)
2015+ tryCompare(PanelState, "decorationsVisible", false)
20012016
20022017 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
20032018 tryCompare(appDelegate, "state", "maximized");
2004− tryCompare(PanelState, "buttonsVisible", true)
2019+ tryCompare(PanelState, "decorationsVisible", true)
20052020
20062021 ApplicationManager.stopApplication(application.appId);
2007− tryCompare(PanelState, "buttonsVisible", false)
2022+ tryCompare(PanelState, "decorationsVisible", false)
20082023
20092024 // wait until all zombie surfaces are gone. As MirSurfaceItems hold references over them.
20102025 // They won't be gone until those surface items are destroyed.
20112026 tryCompareFunction(function() { return application.surfaceList.count }, 0);
20122027
20132028 ApplicationManager.startApplication(application.appId);
2014− tryCompare(PanelState, "buttonsVisible", true)
2029+ tryCompare(PanelState, "decorationsVisible", true)
20152030 }
20162031
20172032 function test_newAppHasValidGeometry() {