Merge lp:~aacid/unity8/doubleClickMaximize into lp:unity8

Proposed by Albert Astals Cid
Status: Superseded
Proposed branch: lp:~aacid/unity8/doubleClickMaximize
Merge into: lp:unity8
Prerequisite: lp:~dandrader/unity8/simplifyWindowDecoration
Diff against target: 1463 lines (+829/-216)
16 files modified
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/expressionfiltermodel.cpp (+49/-0)
plugins/Utils/expressionfiltermodel.h (+42/-0)
plugins/Utils/plugin.cpp (+2/-0)
qml/ApplicationMenus/ApplicationMenusLimits.qml (+24/-0)
qml/ApplicationMenus/MenuBar.qml (+320/-168)
qml/ApplicationMenus/MenuItem.qml (+3/-8)
qml/ApplicationMenus/MenuNavigator.qml (+23/-0)
qml/ApplicationMenus/MenuPopup.qml (+118/-27)
qml/ApplicationMenus/qmldir (+1/-0)
qml/Panel/Panel.qml (+3/-1)
tests/mocks/Utils/CMakeLists.txt (+1/-0)
tests/mocks/Utils/plugin.cpp (+2/-0)
tests/qmltests/ApplicationMenus/tst_MenuBar.qml (+49/-8)
tests/qmltests/ApplicationMenus/tst_MenuPopup.qml (+14/-0)
tests/qmltests/Stage/tst_DesktopStage.qml (+177/-4)
To merge this branch: bzr merge lp:~aacid/unity8/doubleClickMaximize
Reviewer Review Type Date Requested Status
Unity8 CI Bot continuous-integration Approve
Daniel d'Andrada (community) Approve
Review via email: mp+315147@code.launchpad.net

This proposal supersedes a proposal from 2017-01-19.

This proposal has been superseded by a proposal from 2017-01-31.

Commit message

Make double click on the window decoration maximize

And fix test and add a new one

Description of the change

 * Are there any related MPs required for this MP to build/function as expected?
No

 * Did you perform an exploratory manual test run of your code change and any related functionality?
Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

 * If you changed the UI, has there been a design review?
N/A

To post a comment you must log in.
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

FAILED: Continuous integration, rev:2770
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2965/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3864
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2255
    UNSTABLE: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2255
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3892
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3737/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3737
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3737/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2965/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2770
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2972/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3874
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2265
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2265
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3902
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3747/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3747
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3747/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2972/rebuild

review: Approve (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

With kate, after I double click to maximize. the cursor stays with the "closed hand" shape, as if I were dragging something.

In "make tryDesktopStage", when you double click on unity8-dash then move the mouse over the maximized tittle bar, things go crazy. Might be another symptom of the same bug. Some unresolved mouse press+release pair in some MouseArea.

Revision history for this message
Albert Astals Cid (aacid) wrote :

> In "make tryDesktopStage", when you double click on unity8-dash then move the mouse over the
> maximized tittle bar, things go crazy.

Interestingly doesn't seem to happen in tryShell

lp:~aacid/unity8/doubleClickMaximize updated
2771. By Albert Astals Cid

Dfiferent way of making double click work on window decoration

Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2771
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2994/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3898
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2284
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2284
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3926
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3771/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3771
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3771/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2994/rebuild

review: Approve (continuous-integration)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

In test_hideMaximizeButtonWhenSizeConstrained():

"""
mouseMove(deco, deco.width - units.gu(1), deco.height/2);
"""

I think it deserves a comment on why are you clicking specifically near the right corner of the window title (ie, explain that you're evading the menu bar because double-clicking there doens't trigger maximization).

lp:~aacid/unity8/doubleClickMaximize updated
2772. By Albert Astals Cid

comment++

2773. By Albert Astals Cid

i'm tyopeing moar than usual

Revision history for this message
Daniel d'Andrada (dandrader) wrote :

* Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

* Did CI run pass? If not, please explain why.
Yes.

review: Approve
Revision history for this message
Unity8 CI Bot (unity8-ci-bot) wrote :

PASSED: Continuous integration, rev:2772
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2997/
Executed test runs:
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build/3901
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=xenial+overlay,testname=qmluitests.sh/2287
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/test-0-autopkgtest/label=amd64,release=zesty,testname=qmluitests.sh/2287
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-0-fetch/3929
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=amd64,release=zesty/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=armhf,release=zesty/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=xenial+overlay/3774/artifact/output/*zip*/output.zip
    SUCCESS: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3774
        deb: https://unity8-jenkins.ubuntu.com/job/build-2-binpkg/arch=i386,release=zesty/3774/artifact/output/*zip*/output.zip

Click here to trigger a rebuild:
https://unity8-jenkins.ubuntu.com/job/lp-unity8-ci/2997/rebuild

review: Approve (continuous-integration)
lp:~aacid/unity8/doubleClickMaximize updated
2774. By Albert Astals Cid

Merge ~nick-dedekind/unity8/menu.overflow

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'plugins/Utils/CMakeLists.txt'
--- plugins/Utils/CMakeLists.txt 2016-12-21 10:20:36 +0000
+++ plugins/Utils/CMakeLists.txt 2017-01-31 14:13:11 +0000
@@ -35,6 +35,7 @@
35 globalfunctions.cpp35 globalfunctions.cpp
36 URLDispatcher.cpp36 URLDispatcher.cpp
37 tabfocusfence.cpp37 tabfocusfence.cpp
38 expressionfiltermodel.cpp
38 plugin.cpp39 plugin.cpp
39 )40 )
4041
4142
=== added file 'plugins/Utils/expressionfiltermodel.cpp'
--- plugins/Utils/expressionfiltermodel.cpp 1970-01-01 00:00:00 +0000
+++ plugins/Utils/expressionfiltermodel.cpp 2017-01-31 14:13:11 +0000
@@ -0,0 +1,49 @@
1/*
2 * Copyright (C) 2017 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#include "expressionfiltermodel.h"
18
19ExpressionFilterModel::ExpressionFilterModel(QObject *parent)
20 : UnitySortFilterProxyModelQML(parent)
21{
22}
23
24QJSValue ExpressionFilterModel::matchExpression() const
25{
26 return m_matchExpression;
27}
28
29void ExpressionFilterModel::setMatchExpression(const QJSValue &value)
30{
31 m_matchExpression = value;
32 invalidateFilter();
33}
34
35bool
36ExpressionFilterModel::filterAcceptsRow(int sourceRow,
37 const QModelIndex &sourceParent) const
38{
39 if (m_matchExpression.isCallable()) {
40 QJSValueList args;
41 args << sourceRow;
42 QJSValue ret = m_matchExpression.call(args);
43 if (ret.isBool()) {
44 return ret.toBool();
45 }
46 }
47
48 return UnitySortFilterProxyModelQML::filterAcceptsRow(sourceRow, sourceParent);
49}
050
=== added file 'plugins/Utils/expressionfiltermodel.h'
--- plugins/Utils/expressionfiltermodel.h 1970-01-01 00:00:00 +0000
+++ plugins/Utils/expressionfiltermodel.h 2017-01-31 14:13:11 +0000
@@ -0,0 +1,42 @@
1/*
2 * Copyright (C) 2017 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#ifndef EXPRESSIONFILTERMODEL_H
18#define EXPRESSIONFILTERMODEL_H
19
20#include "unitysortfilterproxymodelqml.h"
21#include <QJSValue>
22
23class ExpressionFilterModel : public UnitySortFilterProxyModelQML
24{
25 Q_OBJECT
26 Q_PROPERTY(QJSValue matchExpression READ matchExpression WRITE setMatchExpression NOTIFY matchExpressionChanged)
27public:
28 explicit ExpressionFilterModel(QObject *parent = 0);
29
30 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
31
32 QJSValue matchExpression() const;
33 void setMatchExpression(const QJSValue& value);
34
35Q_SIGNALS:
36 void matchExpressionChanged();
37
38private:
39 mutable QJSValue m_matchExpression;
40};
41
42#endif // EXPRESSIONFILTERMODEL_H
043
=== modified file 'plugins/Utils/plugin.cpp'
--- plugins/Utils/plugin.cpp 2017-01-03 12:16:00 +0000
+++ plugins/Utils/plugin.cpp 2017-01-31 14:13:11 +0000
@@ -41,6 +41,7 @@
41#include "URLDispatcher.h"41#include "URLDispatcher.h"
42#include "appdrawerproxymodel.h"42#include "appdrawerproxymodel.h"
43#include "tabfocusfence.h"43#include "tabfocusfence.h"
44#include "expressionfiltermodel.h"
4445
45static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)46static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
46{47{
@@ -86,4 +87,5 @@
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");87 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");88 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");89 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90 qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
89}91}
9092
=== added file 'qml/ApplicationMenus/ApplicationMenusLimits.qml'
--- qml/ApplicationMenus/ApplicationMenusLimits.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/ApplicationMenusLimits.qml 2017-01-31 14:13:11 +0000
@@ -0,0 +1,24 @@
1/*
2 * Copyright (C) 2017 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
17pragma Singleton
18import QtQuick 2.4
19import QtQuick.Window 2.2
20
21QtObject {
22 property real screenWidth: Screen.width
23 property real screenHeight: Screen.height
24}
025
=== modified file 'qml/ApplicationMenus/MenuBar.qml'
--- qml/ApplicationMenus/MenuBar.qml 2017-01-17 09:45:22 +0000
+++ qml/ApplicationMenus/MenuBar.qml 2017-01-31 14:13:11 +0000
@@ -24,12 +24,13 @@
24 id: root24 id: root
25 objectName: "menuBar"25 objectName: "menuBar"
2626
27 // set from outside
27 property alias unityMenuModel: rowRepeater.model28 property alias unityMenuModel: rowRepeater.model
29 property bool enableKeyFilter: false
30 property real overflowWidth: width
2831
32 // read from outside
29 readonly property bool valid: rowRepeater.count > 033 readonly property bool valid: rowRepeater.count > 0
30
31 property bool enableKeyFilter: false
32
33 readonly property bool showRequested: d.longAltPressed || d.currentItem != null34 readonly property bool showRequested: d.longAltPressed || d.currentItem != null
3435
35 implicitWidth: row.width36 implicitWidth: row.width
@@ -75,189 +76,336 @@
75 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton76 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
76 anchors.fill: parent77 anchors.fill: parent
77 enabled: d.currentItem != null78 enabled: d.currentItem != null
79 hoverEnabled: enabled && d.currentItem && d.currentItem.__popup != null
78 onPressed: d.dismissAll()80 onPressed: d.dismissAll()
79 }81 }
8082
81 Item {83 Row {
82 id: clippingItem84 id: row
8385 spacing: units.gu(2)
84 height: root.height86 height: parent.height
85 width: root.width87
86 clip: true88 ActionContext {
8789 id: menuBarContext
88 Row {90 objectName: "barContext"
89 id: row91 active: !d.currentItem && enableKeyFilter
90 spacing: units.gu(2)92 }
91 height: parent.height93
9294 Connections {
93 ActionContext {95 target: root.unityMenuModel
94 id: menuBarContext96 onModelReset: d.firstInvisibleIndex = undefined
95 objectName: "barContext"97 }
96 active: !d.currentItem && enableKeyFilter98
97 }99 Repeater {
98100 id: rowRepeater
99 Repeater {101
100 id: rowRepeater102 onItemAdded: d.recalcFirstInvisibleIndexAdded(index, item)
101103 onCountChanged: d.recalcFirstInvisibleIndex()
102 Item {104
103 id: visualItem105 Item {
104 objectName: root.objectName + "-item" + __ownIndex106 id: visualItem
105107 objectName: root.objectName + "-item" + __ownIndex
106 readonly property int __ownIndex: index108
107 property Item __popup: null;109 readonly property int __ownIndex: index
108 property bool popupVisible: __popup && __popup.visible110 property Item __popup: null;
109111 property bool popupVisible: __popup && __popup.visible
110 implicitWidth: column.implicitWidth112 property bool shouldDisplay: x + width + ((__ownIndex < rowRepeater.count-1) ? units.gu(2) : 0) <
111 implicitHeight: row.height113 root.overflowWidth - ((__ownIndex < rowRepeater.count-1) ? overflowButton.width : 0)
112 enabled: model.sensitive114
113115 implicitWidth: column.implicitWidth
114 function show() {116 implicitHeight: row.height
115 if (!__popup) {117 enabled: model.sensitive && shouldDisplay
116 __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });118 opacity: shouldDisplay ? 1 : 0
117 // force the current item to be the newly popped up menu119
118 } else {120 function show() {
119 __popup.show();121 if (!__popup) {
120 }122 __popup = menuComponent.createObject(root, { objectName: visualItem.objectName + "-menu" });
121 d.currentItem = visualItem;123 __popup.childActivated.connect(dismiss);
122 }124 // force the current item to be the newly popped up menu
123 function hide() {125 } else {
124 if (__popup) {126 __popup.show();
125 __popup.hide();127 }
126128 d.currentItem = visualItem;
127 if (d.currentItem === visualItem) {129 }
128 d.currentItem = null;130 function hide() {
129 }131 if (__popup) {
130 }132 __popup.hide();
131 }133
132 function dismiss() {134 if (d.currentItem === visualItem) {
133 if (__popup) {135 d.currentItem = null;
134 __popup.destroy();136 }
135 __popup = null;137 }
136138 }
137 if (d.currentItem === visualItem) {139 function dismiss() {
138 d.currentItem = null;140 if (__popup) {
139 }141 __popup.destroy();
140 }142 __popup = null;
141 }143
142144 if (d.currentItem === visualItem) {
143 Connections {145 d.currentItem = null;
144 target: d146 }
145 onDismissAll: visualItem.dismiss()147 }
146 }148 }
147149
148 Component {150 onVisibleChanged: {
149 id: menuComponent151 if (!visible && __popup) dismiss();
150 MenuPopup {152 }
151 x: visualItem.x - units.gu(1)153
152 anchors.top: parent.bottom154 Component.onCompleted: {
153 unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)155 shouldDisplayChanged.connect(function() {
154156 if ((!shouldDisplay && d.firstInvisibleIndex == undefined) || __ownIndex <= d.firstInvisibleIndex) {
155 Component.onCompleted: reset();157 d.recalcFirstInvisibleIndex();
156 }158 }
157 }159 });
158160 }
159 RowLayout {161
160 id: column162 Connections {
161 spacing: units.gu(1)163 target: d
162 anchors {164 onDismissAll: visualItem.dismiss()
163 centerIn: parent165 }
164 }166
165167 Component {
166 Icon {168 id: menuComponent
167 Layout.preferredWidth: units.gu(2)169 MenuPopup {
168 Layout.preferredHeight: units.gu(2)170 desiredX: visualItem.x - units.gu(1)
169 Layout.alignment: Qt.AlignVCenter171 desiredY: parent.height
170172 unityMenuModel: root.unityMenuModel.submenu(visualItem.__ownIndex)
171 visible: model.icon || false173
172 source: model.icon || ""174 Component.onCompleted: reset();
173 }175 }
174176 }
175 ActionItem {177
176 id: actionItem178 RowLayout {
177 width: _title.width179 id: column
178 height: _title.height180 spacing: units.gu(1)
179181 anchors {
180 action: Action {182 centerIn: parent
181 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic183 }
182 text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")184
183185 Icon {
184 onTriggered: {186 Layout.preferredWidth: units.gu(2)
185 visualItem.show();187 Layout.preferredHeight: units.gu(2)
186 }188 Layout.alignment: Qt.AlignVCenter
187 }189
188190 visible: model.icon || false
189 Label {191 source: model.icon || ""
190 id: _title192 }
191 text: actionItem.text193
192 horizontalAlignment: Text.AlignLeft194 ActionItem {
193 color: enabled ? "white" : "#5d5d5d"195 id: actionItem
194 }196 width: _title.width
195 }197 height: _title.height
196 }198
197 } // Item ( delegate )199 action: Action {
198 } // Repeater200 enabled: visualItem.enabled
199 } // Row201 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
200202 text: model.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
201 MouseArea {203
202 anchors.fill: parent204 onTriggered: {
203 hoverEnabled: d.currentItem205 visualItem.show();
204206 }
205 onEntered: {207 }
206 if (d.currentItem) {208
207 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))209 Label {
208 }210 id: _title
209 }211 text: actionItem.text
210 onPositionChanged: {212 horizontalAlignment: Text.AlignLeft
211 if (d.currentItem) {213 color: enabled ? "white" : "#5d5d5d"
212 updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))214 }
213 }215 }
214 }216 }
215 onClicked: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))217 } // Item ( delegate )
216218 } // Repeater
217 function updateCurrentItemFromPosition(point) {219 } // Row
218 var pos = mapToItem(row, point.x, point.y);220
219221 MouseArea {
220 if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {222 anchors.fill: row
221 d.hoveredItem = row.childAt(pos.x, pos.y);223 hoverEnabled: d.currentItem
222 if (!d.hoveredItem || !d.hoveredItem.enabled)224
223 return false;225 onEntered: {
224 if (d.currentItem != d.hoveredItem) {226 if (d.currentItem) {
225 d.currentItem = d.hoveredItem;227 updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
226 }228 }
227 }229 }
228 return true;230 onPositionChanged: {
229 }231 if (d.currentItem) {
230 }232 updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
231233 }
232 Rectangle {234 }
233 id: underline235 onClicked: {
234 anchors {236 var prevItem = d.currentItem;
235 bottom: row.bottom237 updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
236 }238 if (prevItem && d.currentItem == prevItem) {
237 x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0239 prevItem.hide();
238 width: d.currentItem ? d.currentItem.width + units.gu(2) : 0240 }
239 height: units.dp(4)241 }
240 color: UbuntuColors.orange242
241 visible: d.currentItem243 function updateCurrentItemFromPosition(point) {
242 }244 var pos = mapToItem(row, point.x, point.y);
245
246 if (!d.hoveredItem || !d.currentItem || !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
247 d.hoveredItem = row.childAt(pos.x, pos.y);
248 if (!d.hoveredItem || !d.hoveredItem.enabled)
249 return;
250 if (d.currentItem != d.hoveredItem) {
251 d.currentItem = d.hoveredItem;
252 }
253 }
254 }
255 }
256
257 MouseArea {
258 id: overflowButton
259 objectName: "overflow"
260
261 hoverEnabled: d.currentItem
262 onEntered: d.currentItem = this
263 onPositionChanged: d.currentItem = this
264 onClicked: d.currentItem = this
265
266 property Item __popup: null;
267 property bool popupVisible: __popup && __popup.visible
268 property Item firstInvisibleItem: d.firstInvisibleIndex !== undefined ? rowRepeater.itemAt(d.firstInvisibleIndex) : null
269
270 visible: d.firstInvisibleIndex != undefined
271 x: firstInvisibleItem ? firstInvisibleItem.x : 0
272
273 height: parent.height
274 width: units.gu(4)
275
276 onVisibleChanged: {
277 if (!visible && __popup) dismiss();
278 }
279
280 Icon {
281 id: icon
282 width: units.gu(2)
283 height: units.gu(2)
284 anchors.centerIn: parent
285 color: theme.palette.normal.overlayText
286 name: "toolkit_chevron-down_2gu"
287 }
288
289 function show() {
290 if (!__popup) {
291 __popup = overflowComponent.createObject(root, { objectName: overflowButton.objectName + "-menu" });
292 __popup.childActivated.connect(dismiss);
293 // force the current item to be the newly popped up menu
294 } else {
295 __popup.show();
296 }
297 d.currentItem = overflowButton;
298 }
299 function hide() {
300 if (__popup) {
301 __popup.hide();
302
303 if (d.currentItem === overflowButton) {
304 d.currentItem = null;
305 }
306 }
307 }
308 function dismiss() {
309 if (__popup) {
310 __popup.destroy();
311 __popup = null;
312
313 if (d.currentItem === overflowButton) {
314 d.currentItem = null;
315 }
316 }
317 }
318
319 Connections {
320 target: d
321 onDismissAll: overflowButton.dismiss()
322 }
323
324 Component {
325 id: overflowComponent
326 MenuPopup {
327 id: overflowPopup
328 desiredX: overflowButton.x - units.gu(1)
329 desiredY: parent.height
330 unityMenuModel: overflowModel
331
332 ExpressionFilterModel {
333 id: overflowModel
334 sourceModel: root.unityMenuModel
335 matchExpression: function(index) {
336 if (d.firstInvisibleIndex === undefined) return false;
337 return index >= d.firstInvisibleIndex;
338 }
339
340 function submenu(index) {
341 return sourceModel.submenu(mapRowToSource(index));
342 }
343 function activate(index) {
344 return sourceModel.activate(mapRowToSource(index));
345 }
346 }
347
348 Connections {
349 target: d
350 onFirstInvisibleIndexChanged: overflowModel.invalidate()
351 }
352 }
353 }
354 }
355
356 Rectangle {
357 id: underline
358 anchors {
359 bottom: row.bottom
360 }
361 x: d.currentItem ? row.x + d.currentItem.x - units.gu(1) : 0
362 width: d.currentItem ? d.currentItem.width + units.gu(2) : 0
363 height: units.dp(4)
364 color: UbuntuColors.orange
365 visible: d.currentItem
243 }366 }
244367
245 MenuNavigator {368 MenuNavigator {
246 id: d369 id: d
247 objectName: "d"370 objectName: "d"
248 itemView: rowRepeater371 itemView: rowRepeater
372 hasOverflow: overflowButton.visible
249373
250 property Item currentItem: null374 property Item currentItem: null
251 property Item hoveredItem: null375 property Item hoveredItem: null
252 property Item prevCurrentItem: null376 property Item prevCurrentItem: null
253
254 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
255
256 property bool altPressed: false377 property bool altPressed: false
257 property bool longAltPressed: false378 property bool longAltPressed: false
379 property var firstInvisibleIndex: undefined
380
381 readonly property int currentIndex: currentItem && currentItem.hasOwnProperty("__ownIndex") ? currentItem.__ownIndex : -1
258382
259 signal dismissAll()383 signal dismissAll()
260384
385 function recalcFirstInvisibleIndexAdded(index, item) {
386 if (firstInvisibleIndex === undefined) {
387 if (!item.shouldDisplay) {
388 firstInvisibleIndex = index;
389 }
390 } else if (index <= firstInvisibleIndex) {
391 if (!item.shouldDisplay) {
392 firstInvisibleIndex = index;
393 } else {
394 firstInvisibleIndex++;
395 }
396 }
397 }
398
399 function recalcFirstInvisibleIndex() {
400 for (var i = 0; i < rowRepeater.count; i++) {
401 if (!rowRepeater.itemAt(i).shouldDisplay) {
402 firstInvisibleIndex = i;
403 return;
404 }
405 }
406 firstInvisibleIndex = undefined;
407 }
408
261 onSelect: {409 onSelect: {
262 var delegate = rowRepeater.itemAt(index);410 var delegate = rowRepeater.itemAt(index);
263 if (delegate) {411 if (delegate) {
@@ -265,6 +413,10 @@
265 }413 }
266 }414 }
267415
416 onOverflow: {
417 d.currentItem = overflowButton;
418 }
419
268 onCurrentItemChanged: {420 onCurrentItemChanged: {
269 if (prevCurrentItem && prevCurrentItem != currentItem) {421 if (prevCurrentItem && prevCurrentItem != currentItem) {
270 if (currentItem) {422 if (currentItem) {
271423
=== modified file 'qml/ApplicationMenus/MenuItem.qml'
--- qml/ApplicationMenus/MenuItem.qml 2016-12-13 09:56:20 +0000
+++ qml/ApplicationMenus/MenuItem.qml 2017-01-31 14:13:11 +0000
@@ -46,6 +46,8 @@
46 enabled: menuData ? menuData.sensitive : false46 enabled: menuData ? menuData.sensitive : false
4747
48 action: Action {48 action: Action {
49 enabled: root.enabled
50
49 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic51 // FIXME - SDK Action:text modifies menu text with html underline for mnemonic
50 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")52 text: menuData.label.replace("_", "&").replace("<u>", "&").replace("</u>", "")
51 checkable: menuData.isCheck || menuData.isRadio53 checkable: menuData.isCheck || menuData.isRadio
@@ -137,14 +139,7 @@
137 theme.palette.disabled.backgroundSecondaryText139 theme.palette.disabled.backgroundSecondaryText
138140
139 visible: root.hasSubmenu141 visible: root.hasSubmenu
140 name: "chevron"142 name: "toolkit_chevron-ltr_2gu"
141 }
142 }
143
144 MouseArea {
145 anchors.fill: parent
146 onClicked: {
147 root.trigger(action && action.checkable ? !action.checked : undefined);
148 }143 }
149 }144 }
150}145}
151146
=== modified file 'qml/ApplicationMenus/MenuNavigator.qml'
--- qml/ApplicationMenus/MenuNavigator.qml 2016-11-28 13:44:30 +0000
+++ qml/ApplicationMenus/MenuNavigator.qml 2017-01-31 14:13:11 +0000
@@ -18,8 +18,10 @@
1818
19QtObject {19QtObject {
20 property Item itemView: null20 property Item itemView: null
21 property bool hasOverflow: false
2122
22 signal select(int index)23 signal select(int index)
24 signal overflow()
2325
24 function selectNext(currentIndex) {26 function selectNext(currentIndex) {
25 var menu;27 var menu;
@@ -32,6 +34,11 @@
32 break;34 break;
33 }35 }
34 newIndex++;36 newIndex++;
37
38 if (hasOverflow && newIndex === itemView.count) {
39 overflow()
40 break;
41 }
35 }42 }
36 } else if (currentIndex !== -1 && itemView.count > 1) {43 } else if (currentIndex !== -1 && itemView.count > 1) {
37 var startIndex = (currentIndex + 1) % itemView.count;44 var startIndex = (currentIndex + 1) % itemView.count;
@@ -42,6 +49,12 @@
42 select(newIndex);49 select(newIndex);
43 break;50 break;
44 }51 }
52
53 if (hasOverflow && newIndex + 1 === itemView.count) {
54 overflow()
55 break;
56 }
57
45 newIndex = (newIndex + 1) % itemView.count;58 newIndex = (newIndex + 1) % itemView.count;
46 } while (newIndex !== startIndex)59 } while (newIndex !== startIndex)
47 }60 }
@@ -58,12 +71,21 @@
58 break;71 break;
59 }72 }
60 newIndex--;73 newIndex--;
74
75 if (hasOverflow && newIndex < 0 ) {
76 overflow();
77 break;
78 }
61 }79 }
62 } else if (currentIndex !== -1 && itemView.count > 1) {80 } else if (currentIndex !== -1 && itemView.count > 1) {
63 var startIndex = currentIndex - 1;81 var startIndex = currentIndex - 1;
64 newIndex = startIndex;82 newIndex = startIndex;
65 do {83 do {
66 if (newIndex < 0) {84 if (newIndex < 0) {
85 if (hasOverflow) {
86 overflow();
87 break;
88 }
67 newIndex = itemView.count - 1;89 newIndex = itemView.count - 1;
68 }90 }
69 menu = itemView.itemAt(newIndex);91 menu = itemView.itemAt(newIndex);
@@ -72,6 +94,7 @@
72 break;94 break;
73 }95 }
74 newIndex--;96 newIndex--;
97
75 } while (newIndex !== startIndex)98 } while (newIndex !== startIndex)
76 }99 }
77 }100 }
78101
=== modified file 'qml/ApplicationMenus/MenuPopup.qml'
--- qml/ApplicationMenus/MenuPopup.qml 2017-01-18 12:08:05 +0000
+++ qml/ApplicationMenus/MenuPopup.qml 2017-01-31 14:13:11 +0000
@@ -16,16 +16,50 @@
1616
17import QtQuick 2.417import QtQuick 2.4
18import QtQuick.Layouts 1.118import QtQuick.Layouts 1.1
19import QtQuick.Window 2.2
20import Ubuntu.Components 1.319import Ubuntu.Components 1.3
21import Ubuntu.Components.ListItems 1.3 as ListItems20import Ubuntu.Components.ListItems 1.3 as ListItems
22import "../Components"21import "../Components"
22import "."
2323
24UbuntuShape {24UbuntuShape {
25 id: root25 id: root
26 objectName: "menu"26 objectName: "menu"
27 backgroundColor: theme.palette.normal.overlay27 backgroundColor: theme.palette.normal.overlay
2828
29 signal childActivated()
30
31 // true for submenus that need to show on the other side of their parent
32 // if they don't fit when growing right
33 property bool substractWidth: false
34
35 property real desiredX
36 x: {
37 var dummy = visible; // force recalc when shown/hidden
38 var parentTopLeft = parent.mapToItem(null, 0, 0);
39 var farX = ApplicationMenusLimits.screenWidth;
40 if (parentTopLeft.x + width + desiredX <= farX) {
41 return desiredX;
42 } else {
43 if (substractWidth) {
44 return -width;
45 } else {
46 return farX - parentTopLeft.x - width;
47 }
48 }
49 }
50
51 property real desiredY
52 y: {
53 var dummy = visible; // force recalc when shown/hidden
54 var parentTopLeft = parent.mapToItem(null, 0, 0);
55 var bottomY = ApplicationMenusLimits.screenHeight;
56 if (parentTopLeft.y + height + desiredY <= bottomY) {
57 return desiredY;
58 } else {
59 return bottomY - parentTopLeft.y - height;
60 }
61 }
62
29 property alias unityMenuModel: repeater.model63 property alias unityMenuModel: repeater.model
3064
31 function show() {65 function show() {
@@ -64,9 +98,9 @@
64 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -198 readonly property int currentIndex: currentItem ? currentItem.__ownIndex : -1
6599
66 property real __minimumWidth: units.gu(20)100 property real __minimumWidth: units.gu(20)
67 property real __maximumWidth: Screen.width * 0.7101 property real __maximumWidth: ApplicationMenusLimits.screenWidth * 0.7
68 property real __minimumHeight: units.gu(2)102 property real __minimumHeight: units.gu(2)
69 property real __maximumHeight: Screen.height * 0.7103 property real __maximumHeight: ApplicationMenusLimits.screenHeight
70104
71 signal dismissAll()105 signal dismissAll()
72106
@@ -76,6 +110,8 @@
76 } else {110 } else {
77 hoveredItem = null;111 hoveredItem = null;
78 }112 }
113
114 submenuHoverTimer.stop();
79 }115 }
80116
81 onSelect: {117 onSelect: {
@@ -147,8 +183,20 @@
147 }183 }
148184
149 MouseArea {185 MouseArea {
186 id: previousMA
150 anchors.fill: parent187 anchors.fill: parent
151 onPressed: {188 hoverEnabled: enabled
189 onPressed: progress()
190
191 Timer {
192 running: previousMA.containsMouse && !listView.atYBeginning
193 interval: 1000
194 repeat: true
195 onTriggered: previousMA.progress()
196 }
197
198 function progress() {
199 console.log("progress!")
152 var item = menuColumn.childAt(0, listView.contentY);200 var item = menuColumn.childAt(0, listView.contentY);
153 if (item) {201 if (item) {
154 var previousItem = item;202 var previousItem = item;
@@ -176,10 +224,15 @@
176 contentHeight: menuColumn.height224 contentHeight: menuColumn.height
177 interactive: height < contentHeight225 interactive: height < contentHeight
178226
227 Timer {
228 id: submenuHoverTimer
229 interval: 225 // GTK MENU_POPUP_DELAY, Qt SH_Menu_SubMenuPopupDelay in QCommonStyle is 256
230 onTriggered: d.currentItem.item.trigger();
231 }
232
179 MouseArea {233 MouseArea {
180 anchors.fill: parent234 anchors.fill: parent
181 hoverEnabled: true235 hoverEnabled: true
182 propagateComposedEvents: true // propogate events so we send clicks to children.
183 z: 1 // on top so we override any other hovers236 z: 1 // on top so we override any other hovers
184 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))237 onEntered: updateCurrentItemFromPosition(Qt.point(mouseX, mouseY))
185 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))238 onPositionChanged: updateCurrentItemFromPosition(Qt.point(mouse.x, mouse.y))
@@ -189,12 +242,25 @@
189242
190 if (!d.hoveredItem || !d.currentItem ||243 if (!d.hoveredItem || !d.currentItem ||
191 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {244 !d.hoveredItem.contains(Qt.point(pos.x - d.currentItem.x, pos.y - d.currentItem.y))) {
245 submenuHoverTimer.stop();
246
192 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)247 d.hoveredItem = menuColumn.childAt(pos.x, pos.y)
193 if (!d.hoveredItem || !d.hoveredItem.enabled)248 if (!d.hoveredItem || !d.hoveredItem.enabled)
194 return false;249 return;
195 d.currentItem = d.hoveredItem;250 d.currentItem = d.hoveredItem;
196 }251
197 return true;252 if (!d.currentItem.__isSeparator && d.currentItem.item.hasSubmenu && d.currentItem.item.enabled) {
253 submenuHoverTimer.start();
254 }
255 }
256 }
257
258 onClicked: {
259 var pos = mapToItem(listView.contentItem, mouse.x, mouse.y);
260 var clickedItem = menuColumn.childAt(pos.x, pos.y);
261 if (clickedItem.enabled && !clickedItem.__isSeparator) {
262 clickedItem.item.trigger();
263 }
198 }264 }
199 }265 }
200266
@@ -249,6 +315,8 @@
249 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)315 width: MathUtils.clamp(implicitWidth, d.__minimumWidth, d.__maximumWidth)
250316
251 action.onTriggered: {317 action.onTriggered: {
318 submenuHoverTimer.stop();
319
252 d.currentItem = loader;320 d.currentItem = loader;
253321
254 if (hasSubmenu) {322 if (hasSubmenu) {
@@ -257,22 +325,29 @@
257 popup = submenuComponent.createObject(focusScope, {325 popup = submenuComponent.createObject(focusScope, {
258 objectName: loader.objectName + "-",326 objectName: loader.objectName + "-",
259 unityMenuModel: model,327 unityMenuModel: model,
260 x: Qt.binding(function() { return root.width }),328 substractWidth: true,
261 y: Qt.binding(function() {329 desiredX: Qt.binding(function() { return root.width }),
330 desiredY: Qt.binding(function() {
262 var dummy = listView.contentY; // force a recalc on contentY change.331 var dummy = listView.contentY; // force a recalc on contentY change.
263 return mapToItem(container, 0, y).y;332 return mapToItem(container, 0, y).y;
264 })333 })
265 });334 });
335 popup.retreat.connect(function() {
336 popup.destroy();
337 popup = null;
338 menuItem.forceActiveFocus();
339 });
340 popup.childActivated.connect(function() {
341 popup.destroy();
342 popup = null;
343 root.childActivated();
344 });
266 } else if (popup) {345 } else if (popup) {
267 popup.visible = true;346 popup.visible = true;
268 }347 }
269 popup.retreat.connect(function() {
270 popup.destroy();
271 popup = null;
272 menuItem.forceActiveFocus();
273 })
274 } else {348 } else {
275 root.unityMenuModel.activate(__ownIndex);349 root.unityMenuModel.activate(__ownIndex);
350 root.childActivated();
276 }351 }
277 }352 }
278353
@@ -349,8 +424,19 @@
349 }424 }
350425
351 MouseArea {426 MouseArea {
427 id: nextMA
352 anchors.fill: parent428 anchors.fill: parent
353 onPressed: {429 hoverEnabled: enabled
430 onPressed: progress()
431
432 Timer {
433 running: nextMA.containsMouse && !listView.atYEnd
434 interval: 1000
435 repeat: true
436 onTriggered: nextMA.progress()
437 }
438
439 function progress() {
354 var item = menuColumn.childAt(0, listView.contentY + listView.height);440 var item = menuColumn.childAt(0, listView.contentY + listView.height);
355 if (item) {441 if (item) {
356 var nextItem = item;442 var nextItem = item;
@@ -375,23 +461,28 @@
375 id: submenuLoader461 id: submenuLoader
376 source: "MenuPopup.qml"462 source: "MenuPopup.qml"
377463
464 property real desiredX
465 property real desiredY
466 property bool substractWidth
378 property var unityMenuModel: null467 property var unityMenuModel: null
379 signal retreat()468 signal retreat()
380469 signal childActivated()
381 Binding {470
382 target: item471 onLoaded: {
383 property: "unityMenuModel"472 item.unityMenuModel = Qt.binding(function() { return submenuLoader.unityMenuModel; });
384 value: submenuLoader.unityMenuModel473 item.objectName = Qt.binding(function() { return submenuLoader.objectName + "menu"; });
385 }474 item.desiredX = Qt.binding(function() { return submenuLoader.desiredX; });
386475 item.desiredY = Qt.binding(function() { return submenuLoader.desiredY; });
387 Binding {476 item.substractWidth = Qt.binding(function() { return submenuLoader.substractWidth; });
388 target: item
389 property: "objectName"
390 value: submenuLoader.objectName + "menu"
391 }477 }
392478
393 Keys.onLeftPressed: retreat()479 Keys.onLeftPressed: retreat()
394480
481 Connections {
482 target: item
483 onChildActivated: childActivated();
484 }
485
395 Component.onCompleted: item.select(0);486 Component.onCompleted: item.select(0);
396 onVisibleChanged: if (visible) { item.select(0); }487 onVisibleChanged: if (visible) { item.select(0); }
397 }488 }
398489
=== added file 'qml/ApplicationMenus/qmldir'
--- qml/ApplicationMenus/qmldir 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/qmldir 2017-01-31 14:13:11 +0000
@@ -0,0 +1,1 @@
1singleton ApplicationMenusLimits 0.1 ApplicationMenusLimits.qml
02
=== modified file 'qml/Panel/Panel.qml'
--- qml/Panel/Panel.qml 2017-01-09 14:10:17 +0000
+++ qml/Panel/Panel.qml 2017-01-31 14:13:11 +0000
@@ -209,6 +209,8 @@
209 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }209 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
210 active: __applicationMenus.model210 active: __applicationMenus.model
211211
212 width: parent.width - windowControlButtons.width - units.gu(2) - __indicators.barWidth
213
212 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false214 property bool menusRequested: menuBarLoader.item ? menuBarLoader.item.showRequested : false
213215
214 sourceComponent: MenuBar {216 sourceComponent: MenuBar {
@@ -385,7 +387,7 @@
385 }387 }
386388
387 enabled: !applicationMenus.expanded389 enabled: !applicationMenus.expanded
388 opacity: !applicationMenus.expanded ? 1 : 0390 opacity: !callHint.visible && !applicationMenus.expanded ? 1 : 0
389 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }391 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.SnapDuration } }
390392
391 onEnabledChanged: {393 onEnabledChanged: {
392394
=== modified file 'tests/mocks/Utils/CMakeLists.txt'
--- tests/mocks/Utils/CMakeLists.txt 2016-12-21 10:20:36 +0000
+++ tests/mocks/Utils/CMakeLists.txt 2017-01-31 14:13:11 +0000
@@ -26,6 +26,7 @@
26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp26 ${CMAKE_SOURCE_DIR}/plugins/Utils/globalfunctions.cpp
27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp27 ${CMAKE_SOURCE_DIR}/plugins/Utils/appdrawerproxymodel.cpp
28 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp28 ${CMAKE_SOURCE_DIR}/plugins/Utils/tabfocusfence.cpp
29 ${CMAKE_SOURCE_DIR}/plugins/Utils/expressionfiltermodel.cpp
29 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
30 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
31 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h32 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/MirSurfaceInterface.h
3233
=== modified file 'tests/mocks/Utils/plugin.cpp'
--- tests/mocks/Utils/plugin.cpp 2017-01-03 12:16:00 +0000
+++ tests/mocks/Utils/plugin.cpp 2017-01-31 14:13:11 +0000
@@ -42,6 +42,7 @@
42#include <globalfunctions.h>42#include <globalfunctions.h>
43#include <appdrawerproxymodel.h>43#include <appdrawerproxymodel.h>
44#include <tabfocusfence.h>44#include <tabfocusfence.h>
45#include <expressionfiltermodel.h>
4546
46static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)47static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
47{48{
@@ -86,4 +87,5 @@
86 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");87 qmlRegisterType<URLDispatcher>(uri, 0, 1, "URLDispatcher");
87 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");88 qmlRegisterType<AppDrawerProxyModel>(uri, 0, 1, "AppDrawerProxyModel");
88 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");89 qmlRegisterType<TabFocusFenceItem>(uri, 0, 1, "TabFocusFence");
90 qmlRegisterType<ExpressionFilterModel>(uri, 0, 1, "ExpressionFilterModel");
89}91}
9092
=== modified file 'tests/qmltests/ApplicationMenus/tst_MenuBar.qml'
--- tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-24 07:41:35 +0000
+++ tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2017-01-31 14:13:11 +0000
@@ -28,8 +28,8 @@
2828
29Item {29Item {
30 id: root30 id: root
31 width: units.gu(100)31 width: units.gu(120)
32 height: units.gu(50)32 height: units.gu(70)
3333
34 Component.onCompleted: {34 Component.onCompleted: {
35 QuickUtils.keyboardAttached = true;35 QuickUtils.keyboardAttached = true;
@@ -51,11 +51,11 @@
51 Rectangle {51 Rectangle {
52 anchors {52 anchors {
53 left: parent.left53 left: parent.left
54 right: parent.right
55 top: parent.top54 top: parent.top
56 margins: units.gu(1)55 margins: units.gu(1)
57 }56 }
58 height: units.gu(3)57 height: units.gu(3)
58 width: parent.width * 2/3
59 color: "grey"59 color: "grey"
6060
61 MenuBar {61 MenuBar {
@@ -65,7 +65,7 @@
6565
66 unityMenuModel: UnityMenuModel {66 unityMenuModel: UnityMenuModel {
67 id: menuBackend67 id: menuBackend
68 modelData: appMenuData.generateTestData(17,5,2,3)68 modelData: appMenuData.generateTestData(10,5,2,3)
69 }69 }
70 }70 }
71 }71 }
@@ -83,12 +83,12 @@
8383
84 function init() {84 function init() {
85 menuBar.dismiss();85 menuBar.dismiss();
86 menuBackend.modelData = appMenuData.generateTestData(5,5,2,3)86 menuBackend.modelData = appMenuData.generateTestData(5,5,2,3, "menu")
87 activatedSpy.clear();87 activatedSpy.clear();
88 }88 }
8989
90 function test_mouseNavigation() {90 function test_mouseNavigation() {
91 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);91 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
92 wait(50) // wait for row to build92 wait(50) // wait for row to build
93 var priv = findInvisibleChild(menuBar, "d");93 var priv = findInvisibleChild(menuBar, "d");
9494
@@ -114,7 +114,7 @@
114 }114 }
115115
116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {116 function test_keyboardNavigation_RightKeySelectsNextMenuItem(data) {
117 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);117 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
118 var priv = findInvisibleChild(menuBar, "d");118 var priv = findInvisibleChild(menuBar, "d");
119119
120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);120 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -139,7 +139,7 @@
139 }139 }
140140
141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {141 function test_keyboardNavigation_LeftKeySelectsPreviousMenuItem(data) {
142 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0);142 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0, "menu");
143 var priv = findInvisibleChild(menuBar, "d");143 var priv = findInvisibleChild(menuBar, "d");
144144
145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);145 var menuItem0 = findChild(menuBar, "menuBar-item0"); verify(menuItem0);
@@ -192,5 +192,46 @@
192 keyClick(Qt.Key_F10, Qt.AltModifier);192 keyClick(Qt.Key_F10, Qt.AltModifier);
193 compare(priv.currentItem, menuItem1, "First enabled item should be opened");193 compare(priv.currentItem, menuItem1, "First enabled item should be opened");
194 }194 }
195
196 function test_clickOpenMenuClosesMenu() {
197 menuBackend.modelData = appMenuData.generateTestData(3,3,0,0,"menu");
198 var priv = findInvisibleChild(menuBar, "d");
199
200 var menuItem = findChild(menuBar, "menuBar-item0");
201 waitForRendering(menuItem);
202 mouseClick(menuItem);
203 compare(priv.currentItem, menuItem, "CurrentItem should be set to item 0");
204 compare(priv.currentItem.popupVisible, true, "Popup should be visible");
205
206 waitForRendering(menuItem);
207 mouseClick(menuItem);
208 compare(priv.currentItem, null, "CurrentItem should be null");
209 }
210
211 function test_overfow() {
212 menuBackend.modelData = appMenuData.generateTestData(5,2,0,0,"menu");
213
214 var overflow = findChild(menuBar, "overflow");
215 compare(overflow.visible, false, "Overflow should not be visible");
216
217 var menu = { "rowData": { "label": "Short" } };
218 tryCompareFunction(function() {
219 menuBackend.insertRow(0, menu);
220 wait(1);
221 if (overflow.visible) {
222 return true;
223 }
224 return false;
225 }, true);
226
227 tryCompareFunction(function() {
228 menuBackend.removeRow(0);
229 wait(1);
230 if (!overflow.visible) {
231 return true;
232 }
233 return false;
234 }, true);
235 }
195 }236 }
196}237}
197238
=== modified file 'tests/qmltests/ApplicationMenus/tst_MenuPopup.qml'
--- tests/qmltests/ApplicationMenus/tst_MenuPopup.qml 2017-01-18 12:08:14 +0000
+++ tests/qmltests/ApplicationMenus/tst_MenuPopup.qml 2017-01-31 14:13:11 +0000
@@ -223,6 +223,20 @@
223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);223 tryCompareFunction(function() { return menuItem.popup !== null && menuItem.popup.visible }, false);
224 }224 }
225225
226 function test_mouseHoverOpensSubMenu() {
227 menu.unityMenuModel.modelData = appMenuData.generateTestData(3,3,1,0,"menu",false);
228
229 var menuItem = findChild(menu, "menu-item0");
230
231 var priv = findInvisibleChild(menu, "d");
232 priv.currentItem = menuItem;
233
234 mouseMove(menuItem, menuItem.width/2, menuItem.height/2);
235 verify(!menuItem.popup);
236
237 tryCompareFunction(function() { return menuItem.popup != null; }, true);
238 }
239
226 function test_differentSizes() {240 function test_differentSizes() {
227 var differentSizesMenu = [{241 var differentSizesMenu = [{
228 "rowData": { "label": "Short" }}, {242 "rowData": { "label": "Short" }}, {
229243
=== modified file 'tests/qmltests/Stage/tst_DesktopStage.qml'
--- tests/qmltests/Stage/tst_DesktopStage.qml 2017-01-10 14:44:29 +0000
+++ tests/qmltests/Stage/tst_DesktopStage.qml 2017-01-31 14:13:11 +0000
@@ -29,6 +29,7 @@
29import "../../../qml/Stage"29import "../../../qml/Stage"
30import "../../../qml/Components"30import "../../../qml/Components"
31import "../../../qml/Components/PanelState"31import "../../../qml/Components/PanelState"
32import "../../../qml/ApplicationMenus"
3233
33Item {34Item {
34 id: root35 id: root
@@ -44,6 +45,8 @@
44 }45 }
4546
46 Component.onCompleted: {47 Component.onCompleted: {
48 ApplicationMenusLimits.screenWidth = Qt.binding( function() { return stageLoader.width; } );
49 ApplicationMenusLimits.screenHeight = Qt.binding( function() { return stageLoader.height; } );
47 QuickUtils.keyboardAttached = true;50 QuickUtils.keyboardAttached = true;
48 theme.name = "Ubuntu.Components.Themes.SuruDark";51 theme.name = "Ubuntu.Components.Themes.SuruDark";
49 resetGeometry();52 resetGeometry();
@@ -695,10 +698,18 @@
695 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);698 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
696 var deco = findChild(dialerDelegate, "appWindowDecoration");699 var deco = findChild(dialerDelegate, "appWindowDecoration");
697 verify(deco);700 verify(deco);
698 tryCompare(deco, "maximizeButtonShown", false);701 // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
699 mouseDoubleClick(deco);702 mouseMove(deco, deco.width - units.gu(1), deco.height/2);
700 var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);703 var menuBarLoader = findChild(deco, "menuBarLoader");
701 tryCompareFunction(function(){ return sizeBefore; }, sizeAfter);704 tryCompare(menuBarLoader.item, "visible", true);
705 mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2)
706 expectFail("", "Double click should not maximize in a size restricted window");
707 tryCompareFunction(function() {
708 var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
709 return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
710 },
711 true
712 );
702713
703 // remove restrictions, the maximize button should again be visible714 // remove restrictions, the maximize button should again be visible
704 dialerDelegate.surface.setMaximumWidth(0);715 dialerDelegate.surface.setMaximumWidth(0);
@@ -706,6 +717,29 @@
706 tryCompare(dialerMaximizeButton, "visible", true);717 tryCompare(dialerMaximizeButton, "visible", true);
707 }718 }
708719
720 function test_doubleClickMaximizes() {
721 var dialerDelegate = startApplication("dialer-app");
722
723 var dialerMaximizeButton = findChild(dialerDelegate, "maximizeWindowButton");
724 tryCompare(dialerMaximizeButton, "visible", true);
725
726 // try double clicking the decoration, should maximize it
727 var sizeBefore = Qt.size(dialerDelegate.width, dialerDelegate.height);
728 var deco = findChild(dialerDelegate, "appWindowDecoration");
729 verify(deco);
730 // deco.width - units.gu(1) to make sure we're outside the "menu" area of the decoration
731 mouseMove(deco, deco.width - units.gu(1), deco.height/2);
732 var menuBarLoader = findChild(deco, "menuBarLoader");
733 tryCompare(menuBarLoader.item, "visible", true);
734 mouseDoubleClick(deco, deco.width - units.gu(1), deco.height/2);
735 tryCompareFunction(function() {
736 var sizeAfter = Qt.size(dialerDelegate.width, dialerDelegate.height);
737 return sizeAfter.width > sizeBefore.width && sizeAfter.height > sizeBefore.height;
738 },
739 true
740 );
741 }
742
709 function test_canMoveWindowWithLeftMouseButtonOnly_data() {743 function test_canMoveWindowWithLeftMouseButtonOnly_data() {
710 return [744 return [
711 {tag: "left mouse button", button: Qt.LeftButton },745 {tag: "left mouse button", button: Qt.LeftButton },
@@ -830,5 +864,144 @@
830 mouseRelease(decoration);864 mouseRelease(decoration);
831 tryCompare(Mir, "cursorName", "");865 tryCompare(Mir, "cursorName", "");
832 }866 }
867
868 function test_menuPositioning_data() {
869 return [
870 {tag: "good",
871 windowPosition: Qt.point(units.gu(10), units.gu(10))
872 },
873 {tag: "collides right",
874 windowPosition: Qt.point(units.gu(100), units.gu(10)),
875 minimumXDifference: units.gu(8)
876 },
877 {tag: "collides bottom",
878 windowPosition: Qt.point(units.gu(10), units.gu(80)),
879 minimumYDifference: units.gu(7)
880 },
881 ]
882 }
883
884 function test_menuPositioning(data) {
885 var appDelegate = startApplication("dialer-app");
886 appDelegate.windowedX = data.windowPosition.x;
887 appDelegate.windowedY = data.windowPosition.y;
888
889 var menuItem = findChild(appDelegate, "menuBar-item3");
890 menuItem.show();
891
892 var menu = findChild(appDelegate, "menuBar-item3-menu");
893 tryCompare(menu, "visible", true);
894
895 var normalPositioningX = menuItem.x - units.gu(1);
896 var normalPositioningY = menuItem.height;
897
898 // We do this fuzzy checking because otherwise we would be duplicating the code
899 // that calculates the coordinates and any bug it may have, what we want is really
900 // to check that on collision with the border the menu is shifted substantially
901 if (data.minimumXDifference) {
902 verify(menu.x < normalPositioningX - data.minimumXDifference);
903 } else {
904 compare(menu.x, normalPositioningX);
905 }
906
907 if (data.minimumYDifference) {
908 verify(menu.y < normalPositioningY - data.minimumYDifference);
909 } else {
910 compare(menu.y, normalPositioningY);
911 }
912 }
913
914 function test_submenuPositioning_data() {
915 return [
916 {tag: "good",
917 windowPosition: Qt.point(units.gu(10), units.gu(10))
918 },
919 {tag: "collides right",
920 windowPosition: Qt.point(units.gu(100), units.gu(10)),
921 minimumXDifference: units.gu(35)
922 },
923 {tag: "collides bottom",
924 windowPosition: Qt.point(units.gu(10), units.gu(80)),
925 minimumYDifference: units.gu(8)
926 },
927 ]
928 }
929
930 function test_submenuPositioning(data) {
931 var appDelegate = startApplication("dialer-app");
932 appDelegate.windowedX = data.windowPosition.x;
933 appDelegate.windowedY = data.windowPosition.y;
934
935 var menuItem = findChild(appDelegate, "menuBar-item3");
936 menuItem.show();
937
938 var menu = findChild(appDelegate, "menuBar-item3-menu");
939 menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
940 tryCompare(menuItem, "visible", true);
941 mouseMove(menuItem);
942 mouseClick(menuItem);
943
944 menu = findChild(appDelegate, "menuBar-item3-menu-item3-menu");
945
946 var normalPositioningX = menuItem.width;
947 var normalPositioningY = menuItem.parent.y;
948
949 // We do this fuzzy checking because otherwise we would be duplicating the code
950 // that calculates the coordinates and any bug it may have, what we want is really
951 // to check that on collision with the border the menu is shifted substantially
952 if (data.minimumXDifference) {
953 verify(menu.x < normalPositioningX - data.minimumXDifference);
954 } else {
955 compare(menu.x, normalPositioningX);
956 }
957
958 if (data.minimumYDifference) {
959 verify(menu.y < normalPositioningY - data.minimumYDifference);
960 } else {
961 compare(menu.y, normalPositioningY);
962 }
963 }
964
965 function test_menuDoubleClickNoMaximizeWindowBehind() {
966 var appDelegate1 = startApplication("dialer-app");
967 var appDelegate2 = startApplication("gmail-webapp");
968
969 // Open menu
970 var menuItem = findChild(appDelegate2, "menuBar-item3");
971 menuItem.show();
972 var menu = findChild(appDelegate2, "menuBar-item3-menu");
973 menuItem = findChild(menu, "menuBar-item3-menu-item3-actionItem");
974 tryCompare(menuItem, "visible", true);
975
976 // Place the other application window decoration under the menu
977 var pos = menuItem.mapToItem(null, menuItem.width / 2, menuItem.height / 2);
978 appDelegate1.windowedX = pos.x - appDelegate1.width / 2;
979 appDelegate1.windowedY = pos.y - units.gu(1);
980
981 var previousWindowState = appDelegate1.windowState;
982
983 mouseMove(menuItem);
984 mouseDoubleClickSequence(menuItem);
985
986 expectFail("", "Double clicking a menu should not change the window below");
987 tryCompareFunction(function() { return appDelegate1.windowState != previousWindowState; }, true);
988 }
989
990 function test_openMenuEatsHoverOutsideIt() {
991 var appDelegate = startApplication("gmail-webapp");
992
993 var wd = findChild(appDelegate, "appWindowDecoration");
994 var closeButton = findChild(wd, "closeWindowButton");
995
996 // Open menu
997 var menuItem = findChild(appDelegate, "menuBar-item3");
998 menuItem.show();
999 var menu = findChild(appDelegate, "menuBar-item3-menu");
1000 tryCompare(menu, "visible", true);
1001
1002 mouseMove(closeButton, closeButton.width/2, closeButton.height/2);
1003 expectFail("", "Hovering the window controls should be ignored when the menu is open");
1004 tryCompare(closeButton, "containsMouse", true);
1005 }
833 }1006 }
834}1007}

Subscribers

People subscribed via source and target branches