@@ -24,12 +24,13 @@ Item {
2424 id: root
2525 objectName: "menuBar"
2626
27+ // set from outside
2728 property alias unityMenuModel: rowRepeater.model
28−
29− readonly property bool valid: rowRepeater.count > 0
30−
3129 property bool enableKeyFilter: false
30+ property real overflowWidth: width
3231
32+ // read from outside
33+ readonly property bool valid: rowRepeater.count > 0
3334 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
3435
3536 implicitWidth: row.width
@@ -75,197 +76,334 @@ Item {
7576 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
7677 anchors.fill: parent
7778 enabled: d.currentItem != null
78− hoverEnabled: enabled && d.currentItem.__popup != null
79+ hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
7980 onPressed: d.dismissAll()
8081 }
8182
82− Item {
83− id: clippingItem
84−
85− height: root.height
86− width: root.width
87− clip: true
88−
89− Row {
90− id: row
91− spacing: units.gu(2)
92− height: parent.height
83+ Row {
84+ id: row
85+ spacing: units.gu(2)
86+ height: parent.height
9387
94− ActionContext {
95− id: menuBarContext
96− objectName: "barContext"
97− active: !d.currentItem && enableKeyFilter
98− }
99−
100− Repeater {
101− id: rowRepeater
102−
103− Item {
104− id: visualItem
105− objectName: root.objectName + "-item" + __ownIndex
106−
107− readonly property int __ownIndex: index
108− property Item __popup: null;
109− property bool popupVisible: __popup && __popup.visible
88+ ActionContext {
89+ id: menuBarContext
90+ objectName: "barContext"
91+ active: !d.currentItem && enableKeyFilter
92+ }
11093
111− implicitWidth: column.implicitWidth
112− implicitHeight: row.height
113− enabled: model.sensitive
94+ Connections {
95+ target: root.unityMenuModel
96+ onModelReset: d.firstInvisibleIndex = undefined
97+ }
11498
115− function show() {
116− if (!__popup) {
117− __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
118− __popup.childActivated.connect(dismiss);
119− // force the current item to be the newly popped up menu
120− } else {
121− __popup.show();
122− }
123− d.currentItem = visualItem;
99+ Repeater {
100+ id: rowRepeater
101+
102+ onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
103+ onCountChanged: d.recalcFirstInvisibleIndex()
104+
105+ Item {
106+ id: visualItem
107+ objectName: root.objectName + "-item" + __ownIndex
108+
109+ readonly property int __ownIndex: index
110+ property Item __popup: null;
111+ readonly property bool popupVisible: __popup && __popup.visible
112+ readonly property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
113+ root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
114+
115+ implicitWidth: column.implicitWidth
116+ implicitHeight: row.height
117+ enabled: model.sensitive && shouldDisplay
118+ opacity: shouldDisplay ? 1 : 0
119+
120+ function show() {
121+ if (!__popup) {
122+ __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
123+ __popup.childActivated.connect(dismiss);
124+ // force the current item to be the newly popped up menu
125+ } else {
126+ __popup.show();
124127 }
125− function hide() {
126− if (__popup) {
127− __popup.hide();
128+ d.currentItem = visualItem;
129+ }
130+ function hide() {
131+ if (__popup) {
132+ __popup.hide();
128133
129− if (d.currentItem === visualItem) {
130− d.currentItem = null;
131− }
134+ if (d.currentItem === visualItem) {
135+ d.currentItem = null;
132136 }
133137 }
134− function dismiss() {
135− if (__popup) {
136− __popup.destroy();
137− __popup = null;
138+ }
139+ function dismiss() {
140+ if (__popup) {
141+ __popup.destroy();
142+ __popup = null;
138143
139− if (d.currentItem === visualItem) {
140− d.currentItem = null;
141− }
144+ if (d.currentItem === visualItem) {
145+ d.currentItem = null;
142146 }
143147 }
148+ }
149+
150+ onVisibleChanged: {
151+ if (!visible && __popup) dismiss();
152+ }
144153
145− Connections {
146− target: d
147− onDismissAll: visualItem.dismiss()
154+ onShouldDisplayChanged: {
155+ if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
156+ d.recalcFirstInvisibleIndex();
148157 }
158+ }
149159
150− Component {
151− id: menuComponent
152− MenuPopup {
153− desiredX: visualItem.x - units.gu(1)
154− desiredY: parent.height
155− unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
160+ Connections {
161+ target: d
162+ onDismissAll: visualItem.dismiss()
163+ }
156164
157− Component.onCompleted: reset();
158− }
165+ Component {
166+ id: menuComponent
167+ MenuPopup {
168+ desiredX: visualItem.x - units.gu(1)
169+ desiredY: parent.height
170+ unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
171+
172+ Component.onCompleted: reset();
159173 }
174+ }
160175
161− RowLayout {
162− id: column
163− spacing: units.gu(1)
164− anchors {
165− centerIn: parent
166− }
176+ RowLayout {
177+ id: column
178+ spacing: units.gu(1)
179+ anchors {
180+ centerIn: parent
181+ }
167182
168− Icon {
169− Layout.preferredWidth: units.gu(2)
170− Layout.preferredHeight: units.gu(2)
171− Layout.alignment: Qt.AlignVCenter
183+ Icon {
184+ Layout.preferredWidth: units.gu(2)
185+ Layout.preferredHeight: units.gu(2)
186+ Layout.alignment: Qt.AlignVCenter
172187
173− visible: model.icon || false
174− source: model.icon || ""
175− }
188+ visible: model.icon || false
189+ source: model.icon || ""
190+ }
176191
177− ActionItem {
178− id: actionItem
179− width: _title.width
180− height: _title.height
192+ ActionItem {
193+ id: actionItem
194+ width: _title.width
195+ height: _title.height
181196
182− action: Action {
183− // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
184− text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
197+ action: Action {
198+ enabled: visualItem.enabled
199+ // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
200+ text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
185201
186− onTriggered: {
187− visualItem.show();
188− }
202+ onTriggered: {
203+ visualItem.show();
189204 }
205+ }
190206
191− Label {
192− id: _title
193− text: actionItem.text
194− horizontalAlignment: Text.AlignLeft
195− color: enabled ? "white" : "#5d5d5d"
196− }
207+ Label {
208+ id: _title
209+ text: actionItem.text
210+ horizontalAlignment: Text.AlignLeft
211+ color: enabled ? "white" : "#5d5d5d"
197212 }
198213 }
199− } // Item ( delegate )
200− } // Repeater
201− } // Row
214+ }
215+ } // Item ( delegate )
216+ } // Repeater
217+ } // Row
202218
203− MouseArea {
204− anchors.fill: parent
205− hoverEnabled: d.currentItem
219+ MouseArea {
220+ anchors.fill: parent
221+ hoverEnabled: d.currentItem
206222
207− onEntered: {
208− if (d.currentItem) {
209− updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
223+ onEntered: {
224+ if (d.currentItem) {
225+ updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
226+ }
227+ }
228+ onPositionChanged: {
229+ if (d.currentItem) {
230+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
231+ }
232+ }
233+ onClicked: {
234+ var prevItem = d.currentItem;
235+ updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
236+ if (prevItem && d.currentItem == prevItem) {
237+ prevItem.hide();
238+ }
239+ }
240+
241+ function updateCurrentItemFromPosition(point) {
242+ var pos = mapToItem(row, point.x, point.y);
243+
244+ if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
245+ d.hoveredItem = row.childAt(pos.x, pos.y);
246+ if (!d.hoveredItem || !d.hoveredItem.enabled)
247+ return;
248+ if (d.currentItem != d.hoveredItem) {
249+ d.currentItem = d.hoveredItem;
210250 }
211251 }
212− onPositionChanged: {
213− if (d.currentItem) {
214− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
252+ }
253+ }
254+
255+ MouseArea {
256+ id: overflowButton
257+ objectName: "overflow"
258+
259+ hoverEnabled: d.currentItem
260+ onEntered: d.currentItem = this
261+ onPositionChanged: d.currentItem = this
262+ onClicked: d.currentItem = this
263+
264+ property Item __popup: null;
265+ readonly property bool popupVisible: __popup && __popup.visible
266+ readonly property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
267+
268+ visible: d.firstInvisibleIndex != undefined
269+ x: firstInvisibleItem ? firstInvisibleItem.x : 0
270+
271+ height: parent.height
272+ width: units.gu(4)
273+
274+ onVisibleChanged: {
275+ if (!visible && __popup) dismiss();
276+ }
277+
278+ Icon {
279+ id: icon
280+ width: units.gu(2)
281+ height: units.gu(2)
282+ anchors.centerIn: parent
283+ color: theme.palette.normal.overlayText
284+ name: "toolkit_chevron-down_2gu"
285+ }
286+
287+ function show() {
288+ if (!__popup) {
289+ __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
290+ __popup.childActivated.connect(dismiss);
291+ // force the current item to be the newly popped up menu
292+ } else {
293+ __popup.show();
294+ }
295+ d.currentItem = overflowButton;
296+ }
297+ function hide() {
298+ if (__popup) {
299+ __popup.hide();
300+
301+ if (d.currentItem === overflowButton) {
302+ d.currentItem = null;
215303 }
216304 }
217− onClicked: {
218− var prevItem = d.currentItem;
219− updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
220− if (prevItem && d.currentItem == prevItem) {
221− prevItem.hide();
305+ }
306+ function dismiss() {
307+ if (__popup) {
308+ __popup.destroy();
309+ __popup = null;
310+
311+ if (d.currentItem === overflowButton) {
312+ d.currentItem = null;
222313 }
223314 }
315+ }
224316
225− function updateCurrentItemFromPosition(point) {
226− var pos = mapToItem(row, point.x, point.y);
317+ Connections {
318+ target: d
319+ onDismissAll: overflowButton.dismiss()
320+ }
321+
322+ Component {
323+ id: overflowComponent
324+ MenuPopup {
325+ id: overflowPopup
326+ desiredX: overflowButton.x - units.gu(1)
327+ desiredY: parent.height
328+ unityMenuModel: overflowModel
329+
330+ ExpressionFilterModel {
331+ id: overflowModel
332+ sourceModel: root.unityMenuModel
333+ matchExpression: function(index) {
334+ if (d.firstInvisibleIndex === undefined) return false;
335+ return index >= d.firstInvisibleIndex;
336+ }
227337
228− if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
229− d.hoveredItem = row.childAt(pos.x, pos.y);
230− if (!d.hoveredItem || !d.hoveredItem.enabled)
231− return false;
232− if (d.currentItem != d.hoveredItem) {
233− d.currentItem = d.hoveredItem;
338+ function submenu(index) {
339+ return sourceModel.submenu(mapRowToSource(index));
234340 }
341+ function activate(index) {
342+ return sourceModel.activate(mapRowToSource(index));
343+ }
344+ }
345+
346+ Connections {
347+ target: d
348+ onFirstInvisibleIndexChanged: overflowModel.invalidate()
235349 }
236− return true;
237350 }
238351 }
352+ }
239353
240− Rectangle {
241− id: underline
242− anchors {
243− bottom: row.bottom
244− }
245− x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
246− width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
247− height: units.dp(4)
248− color: UbuntuColors.orange
249− visible: d.currentItem
354+ Rectangle {
355+ id: underline
356+ anchors {
357+ bottom: row.bottom
250358 }
359+ x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
360+ width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
361+ height: units.dp(4)
362+ color: UbuntuColors.orange
363+ visible: d.currentItem
251364 }
252365
253366 MenuNavigator {
254367 id: d
255368 objectName: "d"
256369 itemView: rowRepeater
370+ hasOverflow: overflowButton.visible
257371
258372 property Item currentItem: null
259373 property Item hoveredItem: null
260374 property Item prevCurrentItem: null
261−
262− readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
263−
264375 property bool altPressed: false
265376 property bool longAltPressed: false
377+ property var firstInvisibleIndex: undefined
378+
379+ readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
266380
267381 signal dismissAll()
268382
383+ function recalcFirstInvisibleIndexAdded(index, item) {
384+ if (firstInvisibleIndex === undefined) {
385+ if (!item.shouldDisplay) {
386+ firstInvisibleIndex = index;
387+ }
388+ } else if (index <= firstInvisibleIndex) {
389+ if (!item.shouldDisplay) {
390+ firstInvisibleIndex = index;
391+ } else {
392+ firstInvisibleIndex++;
393+ }
394+ }
395+ }
396+
397+ function recalcFirstInvisibleIndex() {
398+ for (var i = 0; i < rowRepeater.count; i++) {
399+ if (!rowRepeater.itemAt(i).shouldDisplay) {
400+ firstInvisibleIndex = i;
401+ return;
402+ }
403+ }
404+ firstInvisibleIndex = undefined;
405+ }
406+
269407 onSelect: {
270408 var delegate = rowRepeater.itemAt(index);
271409 if (delegate) {
@@ -273,6 +411,10 @@ Item {
273411 }
274412 }
275413
414+ onOverflow: {
415+ d.currentItem = overflowButton;
416+ }
417+
276418 onCurrentItemChanged: {
277419 if (prevCurrentItem && prevCurrentItem != currentItem) {
278420 if (currentItem) {