Merge lp:~nick-dedekind/unity8/desktop_menus.gmenumodel into lp:unity8

Proposed by Nick Dedekind
Status: Work in progress
Proposed branch: lp:~nick-dedekind/unity8/desktop_menus.gmenumodel
Merge into: lp:unity8
Diff against target: 3885 lines (+3069/-96)
44 files modified
CMakeLists.txt (+1/-1)
debian/control (+2/-2)
plugins/Utils/CMakeLists.txt (+1/-0)
plugins/Utils/constants.cpp (+1/-0)
plugins/Utils/constants.h (+3/-0)
plugins/Utils/plugin.cpp (+2/-0)
plugins/Utils/shortcutaction.cpp (+122/-0)
plugins/Utils/shortcutaction.h (+44/-0)
plugins/Utils/windowkeysfilter.cpp (+11/-6)
plugins/Utils/windowkeysfilter.h (+1/-1)
qml/ApplicationMenus/AppMenuUtils.js (+107/-0)
qml/ApplicationMenus/MenuBar.qml (+245/-0)
qml/ApplicationMenus/MenuBarItem.qml (+56/-0)
qml/ApplicationMenus/MenuBase.qml (+116/-0)
qml/ApplicationMenus/MenuItemBase.qml (+32/-0)
qml/ApplicationMenus/MenuItemDelegate.qml (+49/-0)
qml/ApplicationMenus/MenuItemDelegateBase.qml (+82/-0)
qml/ApplicationMenus/MenuItemFactory.qml (+240/-0)
qml/ApplicationMenus/MenuPage.qml (+277/-0)
qml/CMakeLists.txt (+1/-0)
qml/Components/PanelState/PanelState.qml (+2/-1)
qml/Panel/Panel.qml (+91/-7)
qml/Stages/DecoratedWindow.qml (+50/-16)
qml/Stages/DesktopStage.qml (+10/-3)
qml/Stages/WindowDecoration.qml (+117/-45)
tests/imports/check_imports.py (+2/-1)
tests/mocks/QMenuModel/QMenuModel.qmltypes (+5/-1)
tests/mocks/QMenuModel/unitymenumodel.cpp (+25/-3)
tests/mocks/QMenuModel/unitymenumodel.h (+2/-0)
tests/mocks/Unity/Application/MirSurface.cpp (+10/-0)
tests/mocks/Unity/Application/MirSurface.h (+3/-0)
tests/mocks/Utils/CMakeLists.txt (+1/-0)
tests/mocks/Utils/constants.cpp (+7/-0)
tests/mocks/Utils/constants.h (+7/-0)
tests/mocks/Utils/plugin.cpp (+2/-0)
tests/qmltests/ApplicationMenus/tst_MenuBar.qml (+132/-0)
tests/qmltests/ApplicationMenus/tst_MenuPage.qml (+256/-0)
tests/qmltests/CMakeLists.txt (+3/-0)
tests/qmltests/Panel/tst_Panel.qml (+56/-4)
tests/qmltests/Stages/ApplicationCheckBox.qml (+4/-0)
tests/qmltests/Stages/DesktopMenuData.qml (+702/-0)
tests/qmltests/Stages/tst_DesktopStage.qml (+13/-1)
tests/qmltests/Stages/tst_WindowDecoration.qml (+163/-0)
tests/qmltests/tst_Shell.qml (+13/-4)
To merge this branch: bzr merge lp:~nick-dedekind/unity8/desktop_menus.gmenumodel
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Unity8 CI Bot continuous-integration Needs Fixing
Unity Team Pending
Review via email: mp+282727@code.launchpad.net

Commit message

Desktop Menus

Description of the change

Desktop Menus

To post a comment you must log in.
2110. By Nick Dedekind

updated control

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

FAILED: Continuous integration, rev:2109
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/90/
Executed test runs:

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

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

FAILED: Continuous integration, rev:2110
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/91/
Executed test runs:

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

review: Needs Fixing (continuous-integration)
2111. By Nick Dedekind

merged with trunk

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

FAILED: Continuous integration, rev:2111
https://unity8-jenkins.ubuntu.com/job/lp-unity8-1-ci/92/
Executed test runs:

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

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

Unmerged revisions

2111. By Nick Dedekind

merged with trunk

2110. By Nick Dedekind

updated control

2109. By Nick Dedekind

desktop menus

2108. By Launchpad Translations on behalf of unity-team

Launchpad automatic translations update.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2015-12-16 13:58:39 +0000
+++ CMakeLists.txt 2016-01-15 13:11:04 +0000
@@ -57,7 +57,7 @@
57find_package(Qt5Concurrent 5.4 REQUIRED)57find_package(Qt5Concurrent 5.4 REQUIRED)
58find_package(Qt5Sql 5.4 REQUIRED)58find_package(Qt5Sql 5.4 REQUIRED)
5959
60pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=12)60pkg_check_modules(APPLICATION_API REQUIRED unity-shell-application=13)
6161
62# Standard install paths62# Standard install paths
63include(GNUInstallDirs)63include(GNUInstallDirs)
6464
=== modified file 'debian/control'
--- debian/control 2016-01-11 17:37:16 +0000
+++ debian/control 2016-01-15 13:11:04 +0000
@@ -29,7 +29,7 @@
29 libqt5xmlpatterns5-dev,29 libqt5xmlpatterns5-dev,
30 libsystemsettings-dev,30 libsystemsettings-dev,
31 libudev-dev,31 libudev-dev,
32 libunity-api-dev (>= 7.105),32 libunity-api-dev (>= 7.106),
33 libusermetricsoutput1-dev,33 libusermetricsoutput1-dev,
34# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop34# Need those X11 libs touch emulation from mouse events in manual QML tests on a X11 desktop
35 libx11-dev[!armhf],35 libx11-dev[!armhf],
@@ -102,7 +102,7 @@
102 qml-module-qtquick-xmllistmodel,102 qml-module-qtquick-xmllistmodel,
103 qml-module-qtsysteminfo,103 qml-module-qtsysteminfo,
104 qtdeclarative5-gsettings1.0,104 qtdeclarative5-gsettings1.0,
105 qtdeclarative5-qtmir-plugin (>= 0.4.5),105 qtdeclarative5-qtmir-plugin (>= 0.4.7),
106 qtdeclarative5-ubuntu-telephony0.1,106 qtdeclarative5-ubuntu-telephony0.1,
107 qtdeclarative5-ubuntu-web-plugin,107 qtdeclarative5-ubuntu-web-plugin,
108 ubuntu-system-settings,108 ubuntu-system-settings,
109109
=== modified file 'plugins/Utils/CMakeLists.txt'
--- plugins/Utils/CMakeLists.txt 2015-10-28 10:32:47 +0000
+++ plugins/Utils/CMakeLists.txt 2016-01-15 13:11:04 +0000
@@ -24,6 +24,7 @@
24 easingcurve.cpp24 easingcurve.cpp
25 windowstatestorage.cpp25 windowstatestorage.cpp
26 timezoneFormatter.cpp26 timezoneFormatter.cpp
27 shortcutaction.cpp
27 plugin.cpp28 plugin.cpp
28 )29 )
2930
3031
=== modified file 'plugins/Utils/constants.cpp'
--- plugins/Utils/constants.cpp 2015-11-20 15:01:39 +0000
+++ plugins/Utils/constants.cpp 2016-01-15 13:11:04 +0000
@@ -24,4 +24,5 @@
24 } else {24 } else {
25 m_indicatorValueTimeout = 5000;25 m_indicatorValueTimeout = 5000;
26 }26 }
27 m_menuHoverOpenInterval = 250;
27}28}
2829
=== modified file 'plugins/Utils/constants.h'
--- plugins/Utils/constants.h 2015-03-11 08:07:31 +0000
+++ plugins/Utils/constants.h 2016-01-15 13:11:04 +0000
@@ -30,14 +30,17 @@
30{30{
31 Q_OBJECT31 Q_OBJECT
32 Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)32 Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)
33 Q_PROPERTY(int menuHoverOpenInterval READ menuHoverOpenInterval CONSTANT)
3334
34public:35public:
35 Constants(QObject *parent = 0);36 Constants(QObject *parent = 0);
3637
37 int indicatorValueTimeout() const { return m_indicatorValueTimeout; }38 int indicatorValueTimeout() const { return m_indicatorValueTimeout; }
39 int menuHoverOpenInterval() const { return m_menuHoverOpenInterval; }
3840
39private:41private:
40 int m_indicatorValueTimeout;42 int m_indicatorValueTimeout;
43 int m_menuHoverOpenInterval;
41};44};
4245
43#endif46#endif
4447
=== modified file 'plugins/Utils/plugin.cpp'
--- plugins/Utils/plugin.cpp 2015-10-26 16:47:52 +0000
+++ plugins/Utils/plugin.cpp 2016-01-15 13:11:04 +0000
@@ -36,6 +36,7 @@
36#include "constants.h"36#include "constants.h"
37#include "timezoneFormatter.h"37#include "timezoneFormatter.h"
38#include "applicationsfiltermodel.h"38#include "applicationsfiltermodel.h"
39#include "shortcutaction.h"
3940
40static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)41static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
41{42{
@@ -68,6 +69,7 @@
68 [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });69 [](QQmlEngine*, QJSEngine*) -> QObject* { return new TimezoneFormatter; });
69 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");70 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
70 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");71 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
72 qmlRegisterType<ShortcutAction>(uri, 0, 1, "ShortcutAction");
71}73}
7274
73void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)75void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
7476
=== added file 'plugins/Utils/shortcutaction.cpp'
--- plugins/Utils/shortcutaction.cpp 1970-01-01 00:00:00 +0000
+++ plugins/Utils/shortcutaction.cpp 2016-01-15 13:11:04 +0000
@@ -0,0 +1,122 @@
1#include "shortcutaction.h"
2
3#include <QQuickItem>
4#include <QQuickWindow>
5#include <QVariant>
6#include <QShortcutEvent>
7#include <private/qguiapplication_p.h>
8
9namespace {
10
11bool qShortcutContextMatcher(QObject *o, Qt::ShortcutContext context)
12{
13 if (!static_cast<ShortcutAction*>(o)->isEnabled())
14 return false;
15
16 switch (context) {
17 case Qt::ApplicationShortcut:
18 case Qt::WindowShortcut:
19 break;
20 case Qt::WidgetShortcut: {
21 QQuickItem* target = static_cast<ShortcutAction*>(o)->target();
22 if (target && target->hasActiveFocus()) {
23 return true;
24 }
25 return true;
26 }
27 case Qt::WidgetWithChildrenShortcut:
28 break;
29 }
30
31 return false;
32}
33
34}
35
36
37ShortcutAction::ShortcutAction(QObject* parent)
38 : QObject(parent)
39 , m_enabled(true)
40{
41}
42
43ShortcutAction::~ShortcutAction()
44{
45 setShortcut(QString());
46}
47
48QVariant ShortcutAction::shortcut() const
49{
50 return m_shortcut.toString(QKeySequence::NativeText);
51}
52
53void ShortcutAction::setShortcut(const QVariant &arg)
54{
55 QKeySequence sequence;
56 if (arg.type() == QVariant::Int)
57 sequence = QKeySequence(static_cast<QKeySequence::StandardKey>(arg.toInt()));
58 else
59 sequence = QKeySequence::fromString(arg.toString());
60
61 if (sequence == m_shortcut)
62 return;
63
64 if (!m_shortcut.isEmpty())
65 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(0, this, m_shortcut);
66
67 m_shortcut = sequence;
68
69 if (!m_shortcut.isEmpty()) {
70 Qt::ShortcutContext context = Qt::WidgetShortcut;
71 QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(this, m_shortcut, context, qShortcutContextMatcher);
72 }
73 Q_EMIT shortcutChanged(shortcut());
74}
75
76void ShortcutAction::setEnabled(bool e)
77{
78 if (e == m_enabled) return;
79 m_enabled = e;
80
81 Q_EMIT enabledChanged();
82}
83
84void ShortcutAction::setTarget(QQuickItem *target)
85{
86 if (target == m_target) return;
87 m_target = target;
88
89 Q_EMIT targetChanged();
90}
91
92bool ShortcutAction::event(QEvent *e)
93{
94 if (!m_enabled)
95 return false;
96
97 if (e->type() != QEvent::Shortcut)
98 return false;
99
100 QShortcutEvent *se = static_cast<QShortcutEvent *>(e);
101
102 Q_ASSERT_X(se->key() == m_shortcut,
103 "QQuickAction::event",
104 "Received shortcut event from incorrect shortcut");
105 if (se->isAmbiguous()) {
106 qWarning("QQuickAction::event: Ambiguous shortcut overload: %s", se->key().toString(QKeySequence::NativeText).toLatin1().constData());
107 return false;
108 }
109
110 trigger();
111
112 return true;
113}
114
115void ShortcutAction::trigger(QObject* source)
116{
117 if (!m_enabled)
118 return;
119
120 Q_EMIT triggered(source);
121}
122
0123
=== added file 'plugins/Utils/shortcutaction.h'
--- plugins/Utils/shortcutaction.h 1970-01-01 00:00:00 +0000
+++ plugins/Utils/shortcutaction.h 2016-01-15 13:11:04 +0000
@@ -0,0 +1,44 @@
1#ifndef SHORTCUTACTION_H
2#define SHORTCUTACTION_H
3
4#include <QObject>
5#include <QKeySequence>
6#include <QQuickItem>
7
8class ShortcutAction : public QObject
9{
10 Q_OBJECT
11 Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
12 Q_PROPERTY(QVariant shortcut READ shortcut WRITE setShortcut NOTIFY shortcutChanged)
13 Q_PROPERTY(QQuickItem* target READ target WRITE setTarget NOTIFY targetChanged)
14
15public:
16 ShortcutAction(QObject* parent = nullptr);
17 ~ShortcutAction();
18
19 QVariant shortcut() const;
20 void setShortcut(const QVariant &shortcut);
21
22 bool isEnabled() const { return m_enabled; }
23 void setEnabled(bool e);
24
25 QQuickItem* target() const { return m_target; }
26 void setTarget(QQuickItem* target);
27
28 bool event(QEvent *e) override;
29
30 void trigger(QObject* source = nullptr);
31
32Q_SIGNALS:
33 void targetChanged();
34 void enabledChanged();
35 void triggered(QObject* source = nullptr);
36 void shortcutChanged(QVariant shortcut);
37
38private:
39 QKeySequence m_shortcut;
40 bool m_enabled;
41 QQuickItem* m_target;
42};
43
44#endif // SHORTCUTACTION_H
045
=== modified file 'plugins/Utils/windowkeysfilter.cpp'
--- plugins/Utils/windowkeysfilter.cpp 2015-10-22 17:37:26 +0000
+++ plugins/Utils/windowkeysfilter.cpp 2016-01-15 13:11:04 +0000
@@ -24,12 +24,14 @@
24 : QQuickItem(parent),24 : QQuickItem(parent),
25 m_currentEventTimestamp(0)25 m_currentEventTimestamp(0)
26{26{
27 connect(this, &QQuickItem::windowChanged,27 connect(this, &QQuickItem::windowChanged, this, &WindowKeysFilter::setupFilter);
28 this, &WindowKeysFilter::setupFilterOnWindow);28 connect(this, &QQuickItem::enabledChanged, this, &WindowKeysFilter::setupFilter);
29}29}
3030
31bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)31bool WindowKeysFilter::eventFilter(QObject *watched, QEvent *event)
32{32{
33 if (!isEnabled()) return false;
34
33 Q_ASSERT(!m_filteredWindow.isNull());35 Q_ASSERT(!m_filteredWindow.isNull());
34 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));36 Q_ASSERT(watched == static_cast<QObject*>(m_filteredWindow.data()));
35 Q_UNUSED(watched);37 Q_UNUSED(watched);
@@ -53,16 +55,19 @@
53 }55 }
54}56}
5557
56void WindowKeysFilter::setupFilterOnWindow(QQuickWindow *window)58void WindowKeysFilter::setupFilter()
57{59{
60 if (!m_filteredWindow.isNull() == isEnabled() && m_filteredWindow.data() == window()) return;
61
58 if (!m_filteredWindow.isNull()) {62 if (!m_filteredWindow.isNull()) {
59 m_filteredWindow->removeEventFilter(this);63 m_filteredWindow->removeEventFilter(this);
60 m_filteredWindow.clear();64 m_filteredWindow.clear();
61 }65 }
6266
63 if (window) {67 QQuickWindow *wnd = window();
64 window->installEventFilter(this);68 if (wnd && isEnabled()) {
65 m_filteredWindow = window;69 wnd->installEventFilter(this);
70 m_filteredWindow = wnd;
66 }71 }
67}72}
6873
6974
=== modified file 'plugins/Utils/windowkeysfilter.h'
--- plugins/Utils/windowkeysfilter.h 2015-10-22 17:37:26 +0000
+++ plugins/Utils/windowkeysfilter.h 2016-01-15 13:11:04 +0000
@@ -48,7 +48,7 @@
48 void currentEventTimestampChanged();48 void currentEventTimestampChanged();
4949
50private Q_SLOTS:50private Q_SLOTS:
51 void setupFilterOnWindow(QQuickWindow *window);51 void setupFilter();
5252
53private:53private:
54 QPointer<QQuickWindow> m_filteredWindow;54 QPointer<QQuickWindow> m_filteredWindow;
5555
=== added directory 'qml/ApplicationMenus'
=== added file 'qml/ApplicationMenus/AppMenuUtils.js'
--- qml/ApplicationMenus/AppMenuUtils.js 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/AppMenuUtils.js 2016-01-15 13:11:04 +0000
@@ -0,0 +1,107 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17.pragma library
18
19String.prototype.htmlLabelFromMenuLabel = function (underline) {
20 var replacements = [
21 {
22 char: "_",
23 searchExpr: /_/g,
24 doubleReplacement: "_"
25 }, {
26 char: "&",
27 searchExpr: /&(?!amp;)/g,
28 doubleReplacement: "&amp;"
29 }
30 ];
31 var text = this;
32
33 var i = 0;
34 for (; i < replacements.length; i++) {
35 text = parseShortcutChararacter(text, underline, replacements[i]);
36 }
37 return text;
38}
39
40function parseShortcutChararacter(text, underline, replacement) {
41 text = text.replace(new RegExp(replacement.char + replacement.char, "g"), replacement.doubleReplacement);
42
43 if (!underline) {
44 return text.replace(replacement.searchExpr, "");
45 }
46
47 var original = text;
48 var output = "";
49 var current = 0
50 var next = text.search(replacement.searchExpr);
51 if (next === -1) return text;
52
53 while (next !== -1) {
54 output += text.slice(current, next);
55 current = next+1;
56
57 var rest = text.slice(current);
58 text = "<u>" + rest[0] + "</u>" + rest.slice(1);
59 next = text.search(replacement.searchExpr);
60 }
61 output += text;
62 return output;
63}
64
65String.prototype.actionKeyFromMenuLabel = function () {
66 var replacements = [
67 {
68 char: "_",
69 searchExpr: /_/g,
70 doubleReplacement: "_"
71 }, {
72 char: "&",
73 searchExpr: /&(?!amp;)/g,
74 doubleReplacement: "&amp;"
75 }
76 ];
77 var text = this;
78
79 var i = 0;
80 var replaced = false;
81 for (; i < replacements.length; i++) {
82 var actionKey = parseActionKeyChararacter(text, replacements[i]);
83 if (actionKey !== "") return actionKey;
84 }
85 return "";
86}
87
88function parseActionKeyChararacter(original, replacement) {
89 var text = original.replace(new RegExp(replacement.char + replacement.char, "g"), replacement.doubleReplacement);
90
91 var output = "";
92 var iter = 0;
93 var next = text.search(replacement.searchExpr);
94 if (next === -1) return "";
95
96 while (next !== -1) {
97 var char = text.slice(next+1, next+2);
98 if (char === "") break;
99
100 if (iter > 0) output += "+";
101 output += char;
102
103 text = text.slice(next+2);
104 next = text.search(replacement.searchExpr)
105 }
106 return output;
107}
0108
=== added file 'qml/ApplicationMenus/MenuBar.qml'
--- qml/ApplicationMenus/MenuBar.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuBar.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,245 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
20
21MenuBase {
22 id: root
23 objectName: "menuBar"
24
25 property var focusWindow: undefined
26 property alias menuModel: rootDelegate.menuModel
27 implicitWidth: listView.width
28 readonly property bool hasChildren: repeater.count > 0
29 submenuOffset: -units.gu(1)
30
31 function close() {
32 closePopup();
33 listView.selectedItem = undefined;
34 }
35
36 FocusScope {
37 id: scope
38 anchors {
39 left: parent.left
40 }
41 width: listView.width
42 height: parent.height
43
44 Keys.onLeftPressed: listView.selectPrevious()
45 Keys.onRightPressed: listView.selectNext()
46 Keys.onEscapePressed: {
47 focus = false;
48 if (focusWindow !== undefined) {
49 focusWindow.forceActiveFocus();
50 }
51 }
52
53 onFocusChanged: {
54 if (!focus && root.openItem) {
55 root.close();
56 }
57 }
58
59 InverseMouseArea {
60 enabled: root.openItem !== undefined
61 acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
62 anchors.fill: parent
63
64 onPressed: {
65 // stop the inverse mouse area stealing events from mouse areas under.
66 mouse.accepted = false;
67 root.close();
68 }
69 }
70
71 MenuItemDelegateBase {
72 id: rootDelegate
73 focusWindow: root.focusWindow
74 }
75
76 Row {
77 id: listView
78 anchors.left: parent.left
79 height: parent.height
80 spacing: units.gu(2)
81
82 property var selectedItem: undefined
83
84 function selectNext() {
85 var delegate;
86 var newIndex = 0;
87 if (listView.selectedItem === undefined && repeater.count > 0) {
88 while (repeater.count > newIndex) {
89 delegate = repeater.itemAt(newIndex++);
90 if (delegate.enabled) {
91 delegate.selected = true;
92 break;
93 }
94 }
95 } else if (listView.selectedItem !== undefined && repeater.count > 1) {
96 var startIndex = (listView.selectedItem.ownIndex + 1) % repeater.count;
97 newIndex = startIndex;
98 do {
99 delegate = repeater.itemAt(newIndex);
100 if (delegate.enabled) {
101 delegate.selected = true;
102 break;
103 }
104 newIndex = (newIndex + 1) % repeater.count;
105 } while (newIndex !== startIndex)
106 }
107 }
108
109 function selectPrevious() {
110 var delegate;
111 var newIndex = repeater.count-1;
112 if (listView.selectedItem === undefined && repeater.count > 0) {
113 while (repeater.count > newIndex) {
114 delegate = repeater.itemAt(newIndex--);
115 if (delegate.enabled) {
116 delegate.selected = true;
117 break;
118 }
119 }
120 } else if (listView.selectedItem !== undefined && repeater.count > 1) {
121 var startIndex = listView.selectedItem.ownIndex - 1;
122 newIndex = startIndex;
123 do {
124 if (newIndex < 0) newIndex = repeater.count - 1;
125 delegate = repeater.itemAt(newIndex--);
126 if (delegate.enabled) {
127 delegate.selected = true;
128 break;
129 }
130 } while (newIndex !== startIndex)
131 }
132 }
133
134 Repeater {
135 id: repeater
136 model: rootDelegate.submenuItems
137
138 MenuBarItem {
139 id: repeaterItem
140 height: listView.height
141
142 enableMnemonic: root.enableMnemonic
143 menuItemDelegate: model.delegate
144
145 property int modelIndex: index
146 property bool selected: false
147 enabled: model.delegate.sensitive && !model.delegate.isSeparator
148
149 Rectangle {
150 anchors {
151 left: parent.left
152 right: parent.right
153 bottom: parent.bottom
154 leftMargin: -units.gu(1)
155 rightMargin: -units.gu(1)
156 }
157 height: units.dp(4)
158 color: "#E95420"
159 visible: root.openItem == menuItem
160 }
161
162 MenuItemBase {
163 id: menuItem
164 objectName: root.objectName + "-menu" + index
165 property int ownIndex: index
166
167 anchors.fill: parent
168 delegate: model.delegate
169
170 mnemonicAction {
171 property string action: model.delegate.label.actionKeyFromMenuLabel()
172 enabled: enableMnemonic && repeaterItem.enabled
173 shortcut: action !== "" ? "Alt+" + action : ""
174
175 onTriggered: {
176 if (model.delegate.hasSubmenu) {
177 root.open(menuItem, true);
178 }
179 else {
180 model.delegate.activate();
181 }
182 }
183 }
184 }
185
186 MouseArea {
187 id: titleMouseArea
188 anchors.fill: parent
189 hoverEnabled: listView.selectedItem !== undefined
190
191 onEntered: {
192 if (listView.selectedItem !== undefined && !repeaterItem.selected) {
193 repeaterItem.selected = true;
194 }
195 }
196 onClicked: {
197 if (model.delegate.hasSubmenu) {
198 root.open(menuItem, true);
199 }
200 else if (root.enabled) {
201 model.delegate.activate()
202 }
203 }
204 }
205
206 onSelectedChanged: {
207 if (selected) {
208 listView.selectedItem = menuItem;
209 if (model.delegate.hasSubmenu && root.openItem !== menuItem) {
210 root.open(menuItem, true);
211 }
212 }
213 else if (listView.selectedItem === menuItem) {
214 listView.selectedItem = undefined;
215 if (root.openItem == menuItem) {
216 root.closePopup();
217 }
218 }
219 }
220
221 Connections {
222 target: listView
223 onSelectedItemChanged: {
224 if (repeaterItem.selected && (listView.selectedItem === undefined || listView.selectedItem !== menuItem)) {
225 repeaterItem.selected = false;
226 if (root.openItem == menuItem) {
227 root.closePopup();
228 }
229 }
230 }
231 }
232
233 Connections {
234 target: root
235 onOpenItemChanged: {
236 if (root.openItem == menuItem) {
237 repeaterItem.selected = true;
238 }
239 }
240 }
241 }
242 }
243 }
244 }
245}
0246
=== added file 'qml/ApplicationMenus/MenuBarItem.qml'
--- qml/ApplicationMenus/MenuBarItem.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuBarItem.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,56 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Settings.Menus 0.1 as Menus
19import Ubuntu.Components.ListItems 1.3 as ListItems
20import Ubuntu.Components 1.3
21import QtQuick.Layouts 1.1
22
23import "AppMenuUtils.js" as AppMenuUtils
24
25Item {
26 id: root
27 property var menuItemDelegate: undefined
28 property bool enableMnemonic: false
29
30 implicitWidth: column.implicitWidth
31 implicitHeight: column.height
32
33 RowLayout {
34 id: column
35 spacing: units.gu(1)
36 anchors {
37 centerIn: parent
38 }
39
40 Icon {
41 Layout.preferredWidth: units.gu(2)
42 Layout.preferredHeight: units.gu(2)
43 Layout.alignment: Qt.AlignVCenter
44
45 visible: menuItemDelegate && menuItemDelegate.icon
46 source: menuItemDelegate && menuItemDelegate.icon || ""
47 }
48
49 Label {
50 id: _title
51 text: menuItemDelegate.label.htmlLabelFromMenuLabel(enableMnemonic) || ""
52 horizontalAlignment: Text.AlignLeft
53 color: enabled ? "white" : "#5d5d5d"
54 }
55 }
56}
057
=== added file 'qml/ApplicationMenus/MenuBase.qml'
--- qml/ApplicationMenus/MenuBase.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuBase.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,116 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Utils 0.1
19
20Item {
21 id: root
22
23 property bool enableMnemonic: false
24 property bool opensVertically: true
25 readonly property alias openItem: d._openItem
26 property real submenuOffset: 0
27
28 function open(menuItem, focus, focusIndex) {
29 openDelayTimer.stop();
30
31 if (d._openItem !== menuItem && menuItem.delegate.hasSubmenu) {
32 d.closePopup();
33
34 var menuPageComponent = Qt.createComponent("MenuPage.qml");
35
36 if (menuPageComponent.status === Component.Ready) {
37 d._requestedOpenItem = menuItem;
38 d.popup = menuPageComponent.createObject(menuItem,
39 {
40 "objectName": root.objectName + "-subMenu" + menuItem.delegate.index,
41 "vertical": true,
42 "x": opensVertically ? submenuOffset : menuItem.width,
43 "y": opensVertically ? menuItem.height : submenuOffset,
44 "delegateModel": menuItem.delegate.submenuItems
45 });
46 } else if (menuPageComponent.status === Component.Error) {
47 console.log(menuPageComponent.errorString());
48 }
49 }
50 if (focus && d.popup) d.popup.forceActiveFocus();
51 if (focusIndex !== undefined && d.popup) d.popup.setSelectedItem(focusIndex);
52 }
53
54 function openWithDelay(menuItem, focus, index) {
55 openDelayTimer.stop();
56 if (d._openItem === menuItem) {
57 open(menuItem, focus, index);
58 } else {
59 d._requestedOpenItem = menuItem;
60 openDelayTimer.openFocus = focus;
61 openDelayTimer.openIndex = index;
62 openDelayTimer.restart();
63 }
64 }
65
66 function closePopup(focus) {
67 openDelayTimer.stop();
68 if (openItem !== undefined) {
69 d.closePopup();
70 }
71 if (focus) {
72 forceActiveFocus();
73 }
74 }
75
76 Timer {
77 id: openDelayTimer
78
79 property var openFocus
80 property var openIndex
81
82 interval: Constants.menuHoverOpenInterval
83 onTriggered: {
84 open(d._requestedOpenItem, openDelayTimer.openFocus, openDelayTimer.openIndex);
85 }
86 }
87
88 QtObject {
89 id: d
90 property var _requestedOpenItem: undefined
91 property var _openItem: undefined
92
93 property QtObject popup: null
94 onPopupChanged: {
95 d._openItem = popup !== null ? _requestedOpenItem : undefined
96 }
97
98 signal closePopup
99 }
100
101 Connections {
102 target: d
103 onClosePopup: {
104 if (d.popup) {
105 // recusive close to preserve focusing
106 d.popup.closePopup(false);
107 d.popup.visible = false;
108 d.popup.destroy();
109 d.popup = null;
110
111 d._openItem = undefined;
112 d._requestedOpenItem = undefined;
113 }
114 }
115 }
116}
0117
=== added file 'qml/ApplicationMenus/MenuItemBase.qml'
--- qml/ApplicationMenus/MenuItemBase.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuItemBase.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtQuick.Controls 1.3 as Controls
19
20// Item which represents visual location of a menu item
21// Also contains common convenience properties.
22Item {
23 id: root
24
25 property var delegate: undefined
26 property alias mnemonicAction: mnemonic
27
28 /* Quick action. eg "F" from "_File" in the label. Only available while bar/popup is opened */
29 Controls.Action {
30 id: mnemonic
31 }
32}
033
=== added file 'qml/ApplicationMenus/MenuItemDelegate.qml'
--- qml/ApplicationMenus/MenuItemDelegate.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuItemDelegate.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,49 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtQuick.Controls 1.3 as Controls
19import Utils 0.1 as UnityUtils
20
21MenuItemDelegateBase {
22 id: root
23 property var index: undefined
24 property var menuData: undefined
25
26 readonly property string label: menuData ? menuData.label : ""
27 readonly property string action: menuData ? menuData.action : ""
28 readonly property string icon: menuData ? menuData.icon : ""
29 readonly property var shortcut: menuData ? menuData.shortcut : undefined
30 readonly property bool sensitive: menuData ? menuData.sensitive : false
31 readonly property bool hasSubmenu: menuData ? menuData.hasSubmenu : false
32 readonly property bool isSeparator: menuData ? menuData.isSeparator : false
33 readonly property bool isCheck: menuData ? menuData.isCheck : false
34 readonly property bool isRadio: menuData ? menuData.isRadio : false
35 readonly property bool isToggled: menuData ? menuData.isToggled : false
36
37 signal activate()
38
39 UnityUtils.ShortcutAction {
40 shortcut: root.shortcut
41 target: focusWindow ? focusWindow : null
42
43 onTriggered: {
44 if (root.enabled) {
45 activate();
46 }
47 }
48 }
49}
050
=== added file 'qml/ApplicationMenus/MenuItemDelegateBase.qml'
--- qml/ApplicationMenus/MenuItemDelegateBase.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuItemDelegateBase.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,82 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Utils 0.1
19
20Item {
21 id: root
22 property alias menuModel: subMenuRepeater.model
23 readonly property alias submenuItems: submenuDelegates
24 property var focusWindow: undefined
25
26 ListModel {
27 id: submenuDelegates
28 }
29
30 Repeater {
31 id: subMenuRepeater
32
33 onItemAdded: {
34 submenuDelegates.insert(index, { delegate: item.item });
35 }
36 onItemRemoved: {
37 for (var i = 0; i < submenuDelegates.count; i++) {
38 if (item.item === submenuDelegates.get(i).delegate) {
39 submenuDelegates.remove(i, 1);
40 return;
41 }
42 }
43 }
44 onCountChanged: {
45 if (count == 0) {
46 submenuDelegates.clear();
47 }
48 }
49
50 delegate: Loader {
51 id: loader
52 source: "MenuItemDelegate.qml"
53
54 property int modelIndex: index
55
56 Binding {
57 target: loader.item
58 property: "index"
59 value: loader.modelIndex
60 }
61 Binding {
62 target: loader.item
63 property: "menuModel"
64 value: model.hasSubmenu ? menuModel.submenu(loader.modelIndex) : undefined
65 }
66 Binding {
67 target: loader.item
68 property: "menuData"
69 value: model
70 }
71 Binding {
72 target: loader.item
73 property: "focusWindow"
74 value: focusWindow
75 }
76 Connections {
77 target: loader.item
78 onActivate: menuModel.activate(loader.modelIndex)
79 }
80 }
81 }
82}
083
=== added file 'qml/ApplicationMenus/MenuItemFactory.qml'
--- qml/ApplicationMenus/MenuItemFactory.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuItemFactory.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,240 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import Ubuntu.Settings.Menus 0.1 as Menus
19import Ubuntu.Components.ListItems 1.3 as ListItems
20import Ubuntu.Components 1.3
21import QtQuick.Layouts 1.1
22
23import "AppMenuUtils.js" as AppMenuUtils
24
25Loader {
26 id: root
27
28 property bool showShortcut: true
29 property var menuItemDelegate: undefined
30
31 property string text: menuItemDelegate && menuItemDelegate.label || ""
32
33 sourceComponent: {
34 if (menuItemDelegate.isSeparator) {
35 return separatorMenu;
36 }
37 if (menuItemDelegate.hasSubmenu) {
38 return subMenu;
39 }
40 if (menuItemDelegate.isCheck || menuItemDelegate.isRadio) {
41 return checkableMenu;
42 }
43 return standardMenu;
44 }
45
46 Component {
47 id: separatorMenu
48
49 Item {
50 implicitHeight: units.dp(6)
51 anchors {
52 left: parent.left
53 right: parent.right
54 }
55
56 Rectangle {
57 height: units.dp(2)
58 color: "#5D5D5D"
59 anchors {
60 left: parent.left
61 right: parent.right
62 verticalCenter: parent.verticalCenter
63 }
64 }
65 }
66 }
67
68 Component {
69 id: standardMenu
70
71 Item {
72 implicitWidth: column.implicitWidth + units.gu(2)
73 implicitHeight: column.implicitHeight + units.gu(2)
74
75 RowLayout {
76 id: column
77 spacing: units.gu(0.5)
78 anchors {
79 left: parent.left
80 right: parent.right
81 top: parent.top
82 margins: units.gu(1)
83 }
84
85 Item {
86 Layout.preferredWidth: units.gu(1.5)
87 Layout.preferredHeight: units.gu(1.5)
88 }
89
90 Icon {
91 Layout.preferredWidth: units.gu(2)
92 Layout.preferredHeight: units.gu(2)
93 Layout.alignment: Qt.AlignVCenter
94
95 visible: menuItemDelegate && menuItemDelegate.icon || false
96 source: menuItemDelegate && menuItemDelegate.icon || ""
97 }
98
99 RowLayout {
100 Layout.fillWidth: true
101 spacing: units.gu(5)
102
103 Label {
104 text: menuItemDelegate ? menuItemDelegate.label.htmlLabelFromMenuLabel(showShortcut) : ""
105 Layout.fillWidth: true
106 horizontalAlignment: Text.AlignLeft
107 color: enabled ? "#FFFFFF" : "#888888"
108 textFormat: Text.StyledText
109 fontSize: "small"
110 }
111
112 Label {
113 text: menuItemDelegate && menuItemDelegate.shortcut !== undefined ? menuItemDelegate.shortcut : ""
114 Layout.alignment: Qt.AlignRight
115 color: "#5D5D5D"
116 fontSize: "small"
117 }
118 }
119 }
120 }
121 }
122
123 Component {
124 id: subMenu
125
126 Item {
127 implicitWidth: column.implicitWidth + units.gu(2)
128 implicitHeight: column.implicitHeight + units.gu(2)
129
130 RowLayout {
131 id: column
132 spacing: units.gu(0.5)
133 anchors {
134 left: parent.left
135 right: parent.right
136 top: parent.top
137 margins: units.gu(1)
138 }
139
140 Item {
141 Layout.preferredWidth: units.gu(1.5)
142 Layout.preferredHeight: units.gu(1.5)
143 }
144
145 Icon {
146 Layout.preferredWidth: units.gu(2)
147 Layout.preferredHeight: units.gu(2)
148 Layout.alignment: Qt.AlignVCenter
149
150 visible: menuItemDelegate && menuItemDelegate.icon || false
151 source: menuItemDelegate && menuItemDelegate.icon || ""
152 }
153
154 Label {
155 id: _title
156 text: menuItemDelegate && menuItemDelegate.label.htmlLabelFromMenuLabel(showShortcut) || ""
157 Layout.fillWidth: true
158 horizontalAlignment: Text.AlignLeft
159 color: enabled ? "#FFFFFF" : "#5D5D5D"
160 textFormat: Text.StyledText
161 fontSize: "small"
162 }
163
164 Icon {
165 id: chevron
166 visible: menuItemDelegate.hasSubmenu
167
168 Layout.preferredHeight: units.gu(1.5)
169 Layout.alignment: Qt.AlignVCenter
170 name: "chevron"
171 color: enabled ? "#FFFFFF" : "#5D5D5D"
172 }
173 }
174 }
175 }
176
177
178 Component {
179 id: checkableMenu
180
181 Item {
182 implicitWidth: column.implicitWidth + units.gu(2)
183 implicitHeight: column.height + units.gu(2)
184
185 RowLayout {
186 id: column
187 spacing: units.gu(0.5)
188 anchors {
189 left: parent.left
190 right: parent.right
191 top: parent.top
192 margins: units.gu(1)
193 }
194
195 Item {
196 Layout.preferredWidth: units.gu(1.5)
197 Layout.preferredHeight: units.gu(1.5)
198 Layout.alignment: Qt.AlignVCenter
199
200 Icon {
201 anchors.fill: parent
202 visible: menuItemDelegate && menuItemDelegate.isToggled
203 name: "tick"
204 color: enabled ? "#FFFFFF" : "#5D5D5D"
205 }
206 }
207
208 Icon {
209 Layout.preferredWidth: units.gu(2)
210 Layout.preferredHeight: units.gu(2)
211 Layout.alignment: Qt.AlignVCenter
212
213 visible: menuItemDelegate && menuItemDelegate.icon
214 source: menuItemDelegate && menuItemDelegate.icon || ""
215 }
216
217 RowLayout {
218 Layout.fillWidth: true
219 spacing: units.gu(5)
220
221 Label {
222 text: menuItemDelegate ? menuItemDelegate.label.htmlLabelFromMenuLabel(showShortcut) : ""
223 Layout.fillWidth: true
224 horizontalAlignment: Text.AlignLeft
225 color: enabled ? "#FFFFFF" : "#888888"
226 textFormat: Text.StyledText
227 fontSize: "small"
228 }
229
230 Label {
231 text: menuItemDelegate && menuItemDelegate.shortcut !== undefined ? menuItemDelegate.shortcut : ""
232 Layout.alignment: Qt.AlignRight
233 color: "#5D5D5D"
234 fontSize: "small"
235 }
236 }
237 }
238 }
239 }
240}
0241
=== added file 'qml/ApplicationMenus/MenuPage.qml'
--- qml/ApplicationMenus/MenuPage.qml 1970-01-01 00:00:00 +0000
+++ qml/ApplicationMenus/MenuPage.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,277 @@
1/*
2 * Copyright 2015 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtQuick.Layouts 1.1
19import Ubuntu.Components 1.3
20
21MenuBase {
22 id: root
23 objectName: "menuPage"
24
25 property bool vertical: false
26 property alias delegateModel: repeater.model
27
28 width: listView.width
29 height: listView.height
30 opensVertically: false
31 enableMnemonic: focus
32
33 Keys.onDownPressed: {
34 listView.selectNext();
35 event.accepted = true;
36 }
37 Keys.onUpPressed: {
38 listView.selectPrevious();
39 event.accepted = true;
40 }
41
42 Keys.onLeftPressed: {
43 if (root.openItem !== undefined) {
44 if (!focus) {
45 // If we don't have the focus, then a child must.
46 // Close the child and let our parent handle the left press
47 root.closePopup(true);
48 event.accepted = true;
49 return;
50 }
51 }
52 event.accepted = false;
53 }
54 Keys.onRightPressed: {
55 if (listView.selectedItem) {
56 if (listView.selectedItem.delegate.hasSubmenu) {
57 root.open(listView.selectedItem, true, 0);
58 event.accepted = true;
59 return;
60 }
61 }
62 event.accepted = false;
63 }
64 Keys.onReturnPressed: {
65 if (listView.selectedItem) {
66 if (listView.selectedItem.delegate.hasSubmenu) {
67 root.open(listView.selectedItem, true, 0);
68 event.accepted = true;
69 return;
70 } else {
71 listView.selectedItem.delegate.activate();
72 event.accepted = true;
73 }
74 }
75 }
76
77 function setSelectedItem(index) {
78 if (repeater.count <= index) return;
79 var item = repeater.itemAt(index);
80 item.selected = true;
81 }
82
83 BorderImage {
84 anchors {
85 fill: root
86 margins: -units.gu(1)
87 }
88 source: "../Stages/graphics/dropshadow2gu.sci"
89 opacity: 0.3
90 }
91
92 Rectangle {
93 anchors.fill: parent
94 color: "#292929"
95 }
96
97 ColumnLayout {
98 id: listView
99 objectName: root.objectName+"-ListView"
100 spacing: 0
101
102 property var selectedItem: undefined
103
104 function selectNext() {
105 var delegate;
106 var newIndex = 0;
107 if (listView.selectedItem === undefined && repeater.count > 0) {
108 while (repeater.count > newIndex) {
109 delegate = repeater.itemAt(newIndex++);
110 if (delegate.enabled) {
111 delegate.selected = true;
112 break;
113 }
114 }
115 } else if (listView.selectedItem !== undefined && repeater.count > 1) {
116 var startIndex = (listView.selectedItem.ownIndex + 1) % repeater.count;
117 newIndex = startIndex;
118 do {
119 delegate = repeater.itemAt(newIndex);
120 if (delegate.enabled) {
121 delegate.selected = true;
122 break;
123 }
124 newIndex = (newIndex + 1) % repeater.count;
125 } while (newIndex !== startIndex)
126 }
127 }
128
129 function selectPrevious() {
130 var delegate;
131 var newIndex = repeater.count-1;
132 if (listView.selectedItem === undefined && repeater.count > 0) {
133 while (repeater.count > newIndex) {
134 delegate = repeater.itemAt(newIndex--);
135 if (delegate.enabled) {
136 delegate.selected = true;
137 break;
138 }
139 }
140 } else if (listView.selectedItem !== undefined && repeater.count > 1) {
141 var startIndex = listView.selectedItem.ownIndex - 1;
142 newIndex = startIndex;
143 do {
144 if (newIndex < 0) newIndex = repeater.count - 1;
145 delegate = repeater.itemAt(newIndex--);
146 if (delegate.enabled) {
147 delegate.selected = true;
148 break;
149 }
150 } while (newIndex !== startIndex)
151 }
152 }
153
154 Repeater {
155 id: repeater
156
157 // This fixes the ordering issues with using repeater in Layouts.
158 onCountChanged: {
159 var i = 0;
160 for (; i < repeater.count; i++) {
161 var item = repeater.itemAt(i)
162 item.parent = null;
163 item.parent = listView;
164 }
165 }
166 onItemRemoved: {
167 if (item.menuItem === listView.selectedItem) {
168 if (root.openItem == item.menuItem) {
169 root.closePopup(true);
170 }
171 listView.selectedItem = undefined;
172 }
173 }
174
175 delegate: MenuItemFactory {
176 id: repeaterItem
177 property bool selected: false
178 enabled: model.delegate.sensitive && !model.delegate.isSeparator
179
180 menuItemDelegate: model.delegate
181 showShortcut: enableMnemonic
182
183 Layout.fillWidth: true
184 Layout.preferredHeight: repeaterItem.implicitHeight
185
186 Rectangle {
187 visible: repeaterItem.selected
188 anchors.fill: parent
189 gradient: UbuntuColors.orangeGradient
190 }
191
192 property alias menuItem: _menuItem
193
194 MenuItemBase {
195 id: _menuItem
196 objectName: root.objectName + "-menu" + index
197 property int ownIndex: index
198
199 anchors.fill: parent
200 delegate: model.delegate
201
202 mnemonicAction {
203 property string action: model.delegate.label.actionKeyFromMenuLabel()
204 enabled: enableMnemonic && repeaterItem.enabled
205 shortcut: action !== "" ? action : ""
206
207 onTriggered: {
208 if (model.delegate.hasSubmenu) {
209 root.open(menuItem, true, 0);
210 }
211 else {
212 model.delegate.activate();
213 }
214 }
215 }
216 }
217
218 MouseArea {
219 anchors.fill: parent
220 hoverEnabled: true
221 z: 100
222
223 onEntered: {
224 repeaterItem.selected = true;
225 }
226 onClicked: {
227 if (model.delegate.hasSubmenu) {
228 root.open(menuItem, true);
229 }
230 else if (root.enabled) {
231 model.delegate.activate()
232 }
233 }
234 }
235
236 onSelectedChanged: {
237 if (selected) {
238 listView.selectedItem = menuItem;
239 if (root.openItem !== menuItem) {
240 root.closePopup(true);
241 if (model.delegate.hasSubmenu) {
242 root.openWithDelay(menuItem, false);
243 }
244 }
245 }
246 else if (listView.selectedItem === menuItem) {
247 listView.selectedItem = undefined;
248 if (root.openItem == menuItem) {
249 root.closePopup(true);
250 }
251 }
252 }
253
254 Connections {
255 target: listView
256 onSelectedItemChanged: {
257 if (repeaterItem.selected && (listView.selectedItem === undefined || listView.selectedItem !== menuItem)) {
258 repeaterItem.selected = false;
259 if (root.openItem == menuItem) {
260 root.closePopup(true);
261 }
262 }
263 }
264 }
265
266 Connections {
267 target: root
268 onOpenItemChanged: {
269 if (root.openItem == menuItem) {
270 repeaterItem.selected = true;
271 }
272 }
273 }
274 }
275 }
276 }
277}
0278
=== modified file 'qml/CMakeLists.txt'
--- qml/CMakeLists.txt 2015-03-06 04:44:11 +0000
+++ qml/CMakeLists.txt 2016-01-15 13:11:04 +0000
@@ -5,6 +5,7 @@
5 )5 )
66
7set(QML_DIRS7set(QML_DIRS
8 ApplicationMenus
8 Components9 Components
9 Dash10 Dash
10 graphics11 graphics
1112
=== modified file 'qml/Components/PanelState/PanelState.qml'
--- qml/Components/PanelState/PanelState.qml 2015-11-20 13:06:14 +0000
+++ qml/Components/PanelState/PanelState.qml 2016-01-15 13:11:04 +0000
@@ -21,9 +21,10 @@
21 id: root21 id: root
2222
23 property string title: ""23 property string title: ""
24 property bool buttonsVisible: false24 property bool decorationsVisible: false
25 property bool dropShadow: false25 property bool dropShadow: false
26 property int panelHeight: units.gu(3)26 property int panelHeight: units.gu(3)
27 property string maximizedApplication: ""
2728
28 signal close()29 signal close()
29 signal minimize()30 signal minimize()
3031
=== modified file 'qml/Panel/Panel.qml'
--- qml/Panel/Panel.qml 2015-11-25 13:57:34 +0000
+++ qml/Panel/Panel.qml 2016-01-15 13:11:04 +0000
@@ -17,6 +17,9 @@
17import QtQuick 2.417import QtQuick 2.4
18import Ubuntu.Components 1.318import Ubuntu.Components 1.3
19import Unity.Application 0.119import Unity.Application 0.1
20import Unity.Indicators 0.1
21import Utils 0.1
22import "../ApplicationMenus"
20import "../Components"23import "../Components"
21import "../Components/PanelState"24import "../Components/PanelState"
22import ".."25import ".."
@@ -101,13 +104,14 @@
101 }104 }
102105
103 MouseArea {106 MouseArea {
107 id: decorationMouseArea
104 anchors {108 anchors {
105 top: parent.top109 top: parent.top
106 left: parent.left110 left: parent.left
107 right: indicators.left111 right: indicators.left
108 }112 }
109 height: indicators.minimizedPanelHeight113 height: indicators.minimizedPanelHeight
110 hoverEnabled: true114 hoverEnabled: !indicators.shown
111 onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()115 onClicked: callHint.visible ? callHint.showLiveCall() : PanelState.focusMaximizedApp()
112 onDoubleClicked: PanelState.maximize()116 onDoubleClicked: PanelState.maximize()
113117
@@ -124,12 +128,80 @@
124 bottomMargin: units.gu(0.5)128 bottomMargin: units.gu(0.5)
125 }129 }
126 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin130 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
127 visible: PanelState.buttonsVisible && parent.containsMouse && !root.locked && !callHint.visible131 visible: d.showDecorations
128 active: PanelState.buttonsVisible132 active: PanelState.decorationsVisible
129 onClose: PanelState.close()133 onClose: PanelState.close()
130 onMinimize: PanelState.minimize()134 onMinimize: PanelState.minimize()
131 onMaximize: PanelState.maximize()135 onMaximize: PanelState.maximize()
132 }136 }
137
138 WindowKeysFilter {
139 id: altFilter
140 property bool altPressed: false
141 property bool longAltPressed: false
142 enabled: d.enableMenus
143 Keys.onPressed: {
144 if (event.key === Qt.Key_Alt && !event.isAutoRepeat) {
145 altPressed = true;
146 longAltPressed = false;
147 menuBarShortcutTimer.start();
148 return;
149 }
150 event.accepted = false;
151 }
152 Keys.onReleased: {
153 if (event.key === Qt.Key_Alt) {
154 menuBarShortcutTimer.stop();
155 altPressed = false;
156 longAltPressed = false;
157 return;
158 }
159 event.accepted = false
160 }
161
162 Timer {
163 id: menuBarShortcutTimer
164 interval: 200
165 repeat: false
166 onTriggered: {
167 altFilter.longAltPressed = true;
168 }
169 }
170 }
171
172 Loader {
173 id: menuBarLoader
174 anchors {
175 left: windowControlButtons.right
176 leftMargin: units.gu(3)
177 top: parent.top
178 }
179 height: parent.height
180 visible: d.showDecorations
181
182 sourceComponent: PanelState.maximizedApplication ? menuBarComponent : undefined
183 Component {
184 id: menuBarComponent
185 MenuBar {
186 id: menuBar
187 height: menuBarLoader.height
188 enableMnemonic: altFilter.altPressed
189 enabled: d.enableMenus
190 menuModel: sharedAppModel.model
191
192 SharedUnityMenuModel {
193 id: sharedAppModel
194
195 property var application: ApplicationManager.findApplication(PanelState.maximizedApplication)
196 property var surface: application ? application.session ? application.session.lastSurface : null : null
197
198 busName: surface ? surface.dbusMenuName : ""
199 menuObjectPath: surface ? surface.dbusMenuObjectPath : ""
200 actions: surface && surface.dbusMenuObjectPath ? { "unity": surface.dbusMenuObjectPath } : {}
201 }
202 }
203 }
204 }
133 }205 }
134206
135 IndicatorsMenu {207 IndicatorsMenu {
@@ -162,6 +234,9 @@
162 callHint.showLiveCall();234 callHint.showLiveCall();
163 }235 }
164 }236 }
237 onShownChanged: {
238 if (d.menuBar) d.menuBar.close();
239 }
165 }240 }
166241
167 Label {242 Label {
@@ -174,17 +249,15 @@
174 topMargin: units.gu(0.5)249 topMargin: units.gu(0.5)
175 bottomMargin: units.gu(0.5)250 bottomMargin: units.gu(0.5)
176 }251 }
177 color: PanelState.buttonsVisible ? "#ffffff" : "#5d5d5d"252 color: PanelState.decorationsVisible ? "#ffffff" : "#5d5d5d"
178 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin253 height: indicators.minimizedPanelHeight - anchors.topMargin - anchors.bottomMargin
179 visible: !windowControlButtons.visible && !root.locked && !callHint.visible254 visible: !d.showDecorations
180 verticalAlignment: Text.AlignVCenter255 verticalAlignment: Text.AlignVCenter
181 fontSize: "medium"256 fontSize: "medium"
182 font.weight: Font.Normal257 font.weight: Font.Normal
183 text: PanelState.title258 text: PanelState.title
184 }259 }
185260
186 // TODO here would the Locally integrated menus come
187
188 ActiveCallHint {261 ActiveCallHint {
189 id: __callHint262 id: __callHint
190 anchors {263 anchors {
@@ -200,6 +273,17 @@
200 id: d273 id: d
201 objectName: "panelPriv"274 objectName: "panelPriv"
202 readonly property real indicatorHeight: indicators.minimizedPanelHeight275 readonly property real indicatorHeight: indicators.minimizedPanelHeight
276
277 property var menuBar: menuBarLoader.item
278
279 property bool enableMenus: PanelState.decorationsVisible && !root.locked && !callHint.visible &&
280 menuBar &&
281 menuBar.hasChildren
282
283 property bool showDecorations : PanelState.decorationsVisible && !root.locked && !callHint.visible &&
284 (altFilter.longAltPressed ||
285 (decorationMouseArea.hoverEnabled && decorationMouseArea.containsMouse) ||
286 (menuBar && menuBar.openItem !== undefined))
203 }287 }
204288
205 states: [289 states: [
206290
=== modified file 'qml/Stages/DecoratedWindow.qml'
--- qml/Stages/DecoratedWindow.qml 2015-11-23 15:09:45 +0000
+++ qml/Stages/DecoratedWindow.qml 2016-01-15 13:11:04 +0000
@@ -19,6 +19,11 @@
19import QtQuick 2.419import QtQuick 2.4
20import Ubuntu.Components 1.320import Ubuntu.Components 1.3
21import Unity.Application 0.121import Unity.Application 0.1
22import Unity.Indicators 0.1 as Indicators
23import GlobalShortcut 1.0
24import "../Components/PanelState"
25
26import QMenuModel 0.1 as MenuModel
2227
23FocusScope {28FocusScope {
24 id: root29 id: root
@@ -69,22 +74,6 @@
69 enabled: !fullscreen74 enabled: !fullscreen
70 }75 }
7176
72 WindowDecoration {
73 id: decoration
74 target: root.parent
75 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
76 anchors { left: parent.left; top: parent.top; right: parent.right }
77 height: units.gu(3)
78 width: root.width
79 title: window.title
80 visible: root.decorationShown
81
82 onClose: root.close();
83 onMaximize: { root.decorationPressed(); root.maximize(); }
84 onMinimize: root.minimize();
85 onPressed: root.decorationPressed();
86 }
87
88 ApplicationWindow {77 ApplicationWindow {
89 id: applicationWindow78 id: applicationWindow
90 objectName: application ? "appWindow_" + application.appId : "appWindow_null"79 objectName: application ? "appWindow_" + application.appId : "appWindow_null"
@@ -95,4 +84,49 @@
95 interactive: true84 interactive: true
96 focus: true85 focus: true
97 }86 }
87
88 MouseArea {
89 anchors { left: parent.left; top: parent.top; right: parent.right }
90 height: units.gu(3)
91
92 drag.target: root.parent
93 drag.filterChildren: true
94 drag.threshold: 0
95 drag.minimumY: PanelState.panelHeight
96 drag.onActiveChanged: {
97 Mir.cursorName = active ? "grabbing" : ""
98 }
99
100 // Parent is handling drag, this handles clicks.
101 MouseArea {
102 anchors.fill: parent
103 onPressed: root.decorationPressed()
104 onDoubleClicked: root.maximize()
105 }
106
107 WindowDecoration {
108 id: decoration
109 objectName: application ? "appWindowDecoration_" + application.appId : "appWindowDecoration_null"
110 anchors.fill: parent
111 appId: application ? application.appId : ""
112 title: window.title
113 visible: root.decorationShown
114
115 onClose: root.close();
116 onMaximize: { root.decorationPressed(); root.maximize(); }
117 onMinimize: root.minimize();
118
119 menu: sharedAppModel.model
120 target: applicationWindow
121
122 Indicators.SharedUnityMenuModel {
123 id: sharedAppModel
124 property var surface: application ? application.session ? application.session.lastSurface : null : null
125
126 busName: surface ? surface.dbusMenuName : ""
127 menuObjectPath: surface ? surface.dbusMenuObjectPath : ""
128 actions: surface && surface.dbusMenuObjectPath ? { "unity": surface.dbusMenuObjectPath } : {}
129 }
130 }
131 }
98}132}
99133
=== modified file 'qml/Stages/DesktopStage.qml'
--- qml/Stages/DesktopStage.qml 2016-01-08 13:29:03 +0000
+++ qml/Stages/DesktopStage.qml 2016-01-15 13:11:04 +0000
@@ -187,13 +187,20 @@
187187
188 Binding {188 Binding {
189 target: PanelState189 target: PanelState
190 property: "buttonsVisible"190 property: "decorationsVisible"
191 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized // FIXME for Locally integrated menus191 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
192 && spread.state == ""192 && spread.state == ""
193 }193 }
194194
195 Binding {195 Binding {
196 target: PanelState196 target: PanelState
197 property: "maximizedApplication"
198 value: priv.focusedAppDelegate !== null && priv.focusedAppDelegate.maximized
199 && spread.state == "" ? priv.focusedAppId : ""
200 }
201
202 Binding {
203 target: PanelState
197 property: "title"204 property: "title"
198 value: {205 value: {
199 if (priv.focusedAppDelegate !== null && spread.state == "") {206 if (priv.focusedAppDelegate !== null && spread.state == "") {
@@ -215,7 +222,7 @@
215222
216 Component.onDestruction: {223 Component.onDestruction: {
217 PanelState.title = "";224 PanelState.title = "";
218 PanelState.buttonsVisible = false;225 PanelState.decorationsVisible = false;
219 PanelState.dropShadow = false;226 PanelState.dropShadow = false;
220 }227 }
221228
222229
=== modified file 'qml/Stages/WindowDecoration.qml'
--- qml/Stages/WindowDecoration.qml 2015-11-25 13:57:34 +0000
+++ qml/Stages/WindowDecoration.qml 2016-01-15 13:11:04 +0000
@@ -15,90 +15,162 @@
15 */15 */
1616
17import QtQuick 2.417import QtQuick 2.4
18import QtQuick.Layouts 1.1
18import Unity.Application 0.1 // For Mir singleton19import Unity.Application 0.1 // For Mir singleton
19import Ubuntu.Components 1.320import Ubuntu.Components 1.3
21import Utils 0.1
20import "../Components"22import "../Components"
21import "../Components/PanelState"23import "../Components/PanelState"
24import "../ApplicationMenus"
2225
23MouseArea {26Item {
24 id: root27 id: root
25 clip: true
2628
27 property Item target29 property Item target
30 property string appId
28 property alias title: titleLabel.text31 property alias title: titleLabel.text
29 property bool active: false32 property bool active: false
30 hoverEnabled: true33 property var menu: undefined
3134
32 signal close()35 signal close()
33 signal minimize()36 signal minimize()
34 signal maximize()37 signal maximize()
3538
36 onDoubleClicked: root.maximize()
37
38 QtObject {39 QtObject {
39 id: priv40 id: priv
40 property real distanceX41 property real distanceX
41 property real distanceY42 property real distanceY
42 property bool dragging43 property bool dragging
43 }44
4445 property var menuBar: menuBarLoader.item
45 onPressedChanged: {46
46 if (pressed) {47 property bool enableMenus: root.active &&
47 var pos = mapToItem(root.target, mouseX, mouseY);48 (!PanelState.decorationsVisible || PanelState.maximizedApplication !== appId) &&
48 priv.distanceX = pos.x;49 menuBar &&
49 priv.distanceY = pos.y;50 menuBar.hasChildren
50 priv.dragging = true;51
51 } else {52 property bool shouldShowMenus : enableMenus &&
52 priv.dragging = false;53 (altFilter.longAltPressed || menuBarHover.containsMouse || menuBar.openItem !== undefined)
53 Mir.cursorName = "";54 }
54 }55
55 }56 WindowKeysFilter {
5657 id: altFilter
57 onPositionChanged: {58 property bool altPressed: false
58 if (priv.dragging) {59 property bool longAltPressed: false
59 Mir.cursorName = "grabbing";60 enabled: priv.enableMenus
60 var pos = mapToItem(root.target.parent, mouseX, mouseY);61 Keys.onPressed: {
61 root.target.x = pos.x - priv.distanceX;62 if (event.key === Qt.Key_Alt && !event.isAutoRepeat) {
62 root.target.y = Math.max(pos.y - priv.distanceY, PanelState.panelHeight);63 altPressed = true;
63 }64 longAltPressed = false;
64 }65 menuBarShortcutTimer.start();
6566 return;
66 Rectangle {67 }
67 anchors.fill: parent68 event.accepted = false;
68 anchors.bottomMargin: -radius69 }
70 Keys.onReleased: {
71 if (event.key === Qt.Key_Alt) {
72 menuBarShortcutTimer.stop();
73 altPressed = false;
74 longAltPressed = false;
75 return;
76 }
77 event.accepted = false
78 }
79
80 Timer {
81 id: menuBarShortcutTimer
82 interval: 200
83 repeat: false
84 onTriggered: {
85 altFilter.longAltPressed = true;
86 }
87 }
88 }
89
90 // non rounded for bottom of decoration
91 Rectangle {
92 anchors.fill: parent
93 anchors.topMargin: units.gu(.5)
94 color: "#292929"
95 }
96
97 // rounded for top of decoration
98 Rectangle {
99 anchors.fill: parent
69 radius: units.gu(.5)100 radius: units.gu(.5)
70 color: "#292929"101 color: "#292929"
71 }102 }
72103
73 Row {104 RowLayout {
74 anchors {105 anchors {
75 fill: parent106 fill: parent
76 leftMargin: units.gu(1)107 leftMargin: units.gu(1)
77 rightMargin: units.gu(1)108 rightMargin: units.gu(1)
78 topMargin: units.gu(0.5)
79 bottomMargin: units.gu(0.5)
80 }109 }
81 spacing: units.gu(3)110 spacing: units.gu(3)
82111
83 WindowControlButtons {112 WindowControlButtons {
84 id: buttons113 id: buttons
85 height: parent.height114 anchors {
115 top: parent.top
116 bottom: parent.bottom
117 topMargin: units.gu(0.5)
118 bottomMargin: units.gu(0.5)
119 }
86 active: root.active120 active: root.active
87 onClose: root.close();121 onClose: root.close();
88 onMinimize: root.minimize();122 onMinimize: root.minimize();
89 onMaximize: root.maximize();123 onMaximize: root.maximize();
90 }124 }
91125
92 Label {126 Item {
93 id: titleLabel127 Layout.preferredHeight: parent.height
94 objectName: "windowDecorationTitle"128 Layout.fillWidth: true
95 color: root.active ? "white" : "#5d5d5d"129
96 height: parent.height130 MouseArea {
97 width: parent.width - buttons.width - parent.anchors.rightMargin - parent.anchors.leftMargin131 id: menuBarHover
98 verticalAlignment: Text.AlignVCenter132 hoverEnabled: true
99 fontSize: "medium"133 anchors.fill: parent
100 font.weight: root.active ? Font.Light : Font.Normal134 onPressed: { mouse.accepted = false; } // just monitoring
101 elide: Text.ElideRight135 }
136
137 Label {
138 id: titleLabel
139 objectName: "windowDecorationTitle"
140 color: root.active ? "white" : "#5d5d5d"
141 height: parent.height
142 width: parent.width
143 verticalAlignment: Text.AlignVCenter
144 fontSize: "medium"
145 font.weight: root.active ? Font.Light : Font.Normal
146 elide: Text.ElideRight
147
148 opacity: priv.shouldShowMenus ? 0 : 1
149 Behavior on opacity { UbuntuNumberAnimation { } }
150 }
151
152 Loader {
153 id: menuBarLoader
154 objectName: "windowDecorationMenuBarLoader"
155 anchors.bottom: parent.bottom
156 height: parent.height
157 width: parent.width
158 sourceComponent: root.menu ? menuBarComponent : undefined
159 Component {
160 id: menuBarComponent
161 MenuBar {
162 id: menuBar
163 height: menuBarLoader.height
164 focusWindow: root.target
165 menuModel: root.menu
166 enableMnemonic: altFilter.altPressed
167 enabled: priv.enableMenus
168 }
169 }
170
171 opacity: priv.shouldShowMenus ? 1 : 0
172 Behavior on opacity { UbuntuNumberAnimation { } }
173 }
102 }174 }
103 }175 }
104}176}
105177
=== modified file 'tests/imports/check_imports.py'
--- tests/imports/check_imports.py 2015-07-15 15:13:18 +0000
+++ tests/imports/check_imports.py 2016-01-15 13:11:04 +0000
@@ -53,6 +53,7 @@
53quick_good_pat = re.compile(r'.*import QtQuick 2\.4.*$')53quick_good_pat = re.compile(r'.*import QtQuick 2\.4.*$')
54quick_layouts_good_pat = re.compile(r'.*import QtQuick.Layouts 1\.1.*$')54quick_layouts_good_pat = re.compile(r'.*import QtQuick.Layouts 1\.1.*$')
55quick_window_good_pat = re.compile(r'.*import QtQuick.Window 2\.2.*$')55quick_window_good_pat = re.compile(r'.*import QtQuick.Window 2\.2.*$')
56quick_controls_good_pat = re.compile(r'.*import QtQuick.Controls 1\.4.*$')
5657
57# Ubuntu Components patterns58# Ubuntu Components patterns
58ubuntu_components_pat = re.compile(r'.*import Ubuntu.Components.*')59ubuntu_components_pat = re.compile(r'.*import Ubuntu.Components.*')
@@ -111,7 +112,7 @@
111 for file in files:112 for file in files:
112 path = os.path.join(root, file)113 path = os.path.join(root, file)
113 if not (ignore and path.startswith(ignore)) and pat.match(file):114 if not (ignore and path.startswith(ignore)) and pat.match(file):
114 quick_good_pats = [quick_good_pat, quick_layouts_good_pat, quick_window_good_pat]115 quick_good_pats = [quick_good_pat, quick_layouts_good_pat, quick_window_good_pat, quick_controls_good_pat]
115 if scan_for_bad_import(path, quick_pat, quick_good_pats):116 if scan_for_bad_import(path, quick_pat, quick_good_pats):
116 found_bad_import = True117 found_bad_import = True
117 if scan_for_bad_import(path, ubuntu_components_pat, [ubuntu_good_components_pat]):118 if scan_for_bad_import(path, ubuntu_components_pat, [ubuntu_good_components_pat]):
118119
=== modified file 'tests/mocks/QMenuModel/QMenuModel.qmltypes'
--- tests/mocks/QMenuModel/QMenuModel.qmltypes 2015-02-13 09:01:16 +0000
+++ tests/mocks/QMenuModel/QMenuModel.qmltypes 2016-01-15 13:11:04 +0000
@@ -1,4 +1,4 @@
1import QtQuick.tooling 1.11import QtQuick.tooling 1.2
22
3// This file describes the plugin-supplied types contained in the library.3// This file describes the plugin-supplied types contained in the library.
4// It is used for QML tooling purposes only.4// It is used for QML tooling purposes only.
@@ -57,6 +57,10 @@
57 Property { name: "actionStateParser"; type: "ActionStateParser"; isPointer: true }57 Property { name: "actionStateParser"; type: "ActionStateParser"; isPointer: true }
58 Property { name: "nameOwner"; type: "string"; isReadonly: true }58 Property { name: "nameOwner"; type: "string"; isReadonly: true }
59 Property { name: "modelData"; type: "QVariant" }59 Property { name: "modelData"; type: "QVariant" }
60 Signal {
61 name: "activated"
62 Parameter { name: "action"; type: "string" }
63 }
60 Method {64 Method {
61 name: "insertRow"65 name: "insertRow"
62 Parameter { name: "row"; type: "int" }66 Parameter { name: "row"; type: "int" }
6367
=== modified file 'tests/mocks/QMenuModel/unitymenumodel.cpp'
--- tests/mocks/QMenuModel/unitymenumodel.cpp 2014-10-07 10:28:59 +0000
+++ tests/mocks/QMenuModel/unitymenumodel.cpp 2016-01-15 13:11:04 +0000
@@ -18,6 +18,8 @@
1818
19#include "unitymenumodel.h"19#include "unitymenumodel.h"
2020
21#include <QDebug>
22
21enum MenuRoles {23enum MenuRoles {
22 LabelRole = Qt::DisplayRole + 1,24 LabelRole = Qt::DisplayRole + 1,
23 SensitiveRole,25 SensitiveRole,
@@ -29,7 +31,9 @@
29 ActionStateRole,31 ActionStateRole,
30 IsCheckRole,32 IsCheckRole,
31 IsRadioRole,33 IsRadioRole,
32 IsToggledRole34 IsToggledRole,
35 ShortcutRole,
36 HasSubmenuRole
33};37};
3438
35UnityMenuModel::UnityMenuModel(QObject *parent)39UnityMenuModel::UnityMenuModel(QObject *parent)
@@ -149,7 +153,10 @@
149 if (m_modelData.count() <= row) {153 if (m_modelData.count() <= row) {
150 return QVariantMap();154 return QVariantMap();
151 }155 }
152 return m_modelData[row].toMap()["rowData"].toMap();156 QVariantMap vRow = m_modelData[row].toMap();
157 QVariantMap map = vRow["rowData"].toMap();
158 map["hasSubmenu"] = vRow.contains("submenu");
159 return map;
153}160}
154161
155QVariant UnityMenuModel::subMenuData(int row) const162QVariant UnityMenuModel::subMenuData(int row) const
@@ -182,6 +189,8 @@
182 names[IsCheckRole] = "isCheck";189 names[IsCheckRole] = "isCheck";
183 names[IsRadioRole] = "isRadio";190 names[IsRadioRole] = "isRadio";
184 names[IsToggledRole] = "isToggled";191 names[IsToggledRole] = "isToggled";
192 names[ShortcutRole] = "shortcut";
193 names[HasSubmenuRole] = "hasSubmenu";
185194
186 return names;195 return names;
187}196}
@@ -201,6 +210,7 @@
201 UnityMenuModel*& model = submenus[position];210 UnityMenuModel*& model = submenus[position];
202 if (!model) {211 if (!model) {
203 model = new UnityMenuModel(this);212 model = new UnityMenuModel(this);
213 connect(model, &UnityMenuModel::activated, this, &UnityMenuModel::activated);
204 }214 }
205 if (model->modelData() != submenuData) {215 if (model->modelData() != submenuData) {
206 model->setModelData(submenuData);216 model->setModelData(submenuData);
@@ -228,8 +238,20 @@
228 return this->data(this->index(row, 0), roles[role]);238 return this->data(this->index(row, 0), roles[role]);
229}239}
230240
231void UnityMenuModel::activate(int, const QVariant&)241void UnityMenuModel::activate(int row, const QVariant&)
232{242{
243 QVariantMap vModelData = m_modelData[row].toMap();
244 QVariantMap rd = vModelData["rowData"].toMap();
245
246 bool isCheckable = rd[roleNames()[IsCheckRole]].toBool() || rd[roleNames()[IsRadioRole]].toBool();
247 if (isCheckable) {
248 rd[roleNames()[IsToggledRole]] = !rd[roleNames()[IsToggledRole]].toBool();
249 vModelData["rowData"] = rd;
250 m_modelData[row] = vModelData;
251
252 dataChanged(index(row, 0), index(row, 0), QVector<int>() << IsToggledRole);
253 }
254 Q_EMIT activated(rd[roleNames()[ActionRole]].toString());
233}255}
234256
235void UnityMenuModel::changeState(int, const QVariant&)257void UnityMenuModel::changeState(int, const QVariant&)
236258
=== modified file 'tests/mocks/QMenuModel/unitymenumodel.h'
--- tests/mocks/QMenuModel/unitymenumodel.h 2015-04-30 09:31:51 +0000
+++ tests/mocks/QMenuModel/unitymenumodel.h 2016-01-15 13:11:04 +0000
@@ -88,6 +88,8 @@
88 // Internal mock usage88 // Internal mock usage
89 void modelDataChanged();89 void modelDataChanged();
9090
91 void activated(const QString& action);
92
91private:93private:
92 QVariantMap rowData(int row) const;94 QVariantMap rowData(int row) const;
93 QVariant subMenuData(int row) const;95 QVariant subMenuData(int row) const;
9496
=== modified file 'tests/mocks/Unity/Application/MirSurface.cpp'
--- tests/mocks/Unity/Application/MirSurface.cpp 2015-11-06 15:52:24 +0000
+++ tests/mocks/Unity/Application/MirSurface.cpp 2016-01-15 13:11:04 +0000
@@ -129,6 +129,16 @@
129 Q_EMIT orientationAngleChanged(angle);129 Q_EMIT orientationAngleChanged(angle);
130}130}
131131
132QString MirSurface::dbusMenuName() const
133{
134 return name();
135}
136
137QString MirSurface::dbusMenuObjectPath() const
138{
139 return QString("/%1").arg(name());
140}
141
132142
133143
134void MirSurface::registerView(qintptr viewId)144void MirSurface::registerView(qintptr viewId)
135145
=== modified file 'tests/mocks/Unity/Application/MirSurface.h'
--- tests/mocks/Unity/Application/MirSurface.h 2015-11-06 15:52:24 +0000
+++ tests/mocks/Unity/Application/MirSurface.h 2016-01-15 13:11:04 +0000
@@ -66,6 +66,9 @@
66 Mir::OrientationAngle orientationAngle() const override;66 Mir::OrientationAngle orientationAngle() const override;
67 void setOrientationAngle(Mir::OrientationAngle) override;67 void setOrientationAngle(Mir::OrientationAngle) override;
6868
69 QString dbusMenuName() const override;
70 QString dbusMenuObjectPath() const override;
71
69 ////72 ////
70 // API for tests73 // API for tests
7174
7275
=== modified file 'tests/mocks/Utils/CMakeLists.txt'
--- tests/mocks/Utils/CMakeLists.txt 2015-10-28 10:32:47 +0000
+++ tests/mocks/Utils/CMakeLists.txt 2016-01-15 13:11:04 +0000
@@ -19,6 +19,7 @@
19 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp19 ${CMAKE_SOURCE_DIR}/plugins/Utils/easingcurve.cpp
20 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp20 ${CMAKE_SOURCE_DIR}/plugins/Utils/inputwatcher.cpp
21 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp21 ${CMAKE_SOURCE_DIR}/plugins/Utils/applicationsfiltermodel.cpp
22 ${CMAKE_SOURCE_DIR}/plugins/Utils/shortcutaction.cpp
22 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h23 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
23 constants.cpp24 constants.cpp
24 plugin.cpp25 plugin.cpp
2526
=== modified file 'tests/mocks/Utils/constants.cpp'
--- tests/mocks/Utils/constants.cpp 2015-04-15 14:52:01 +0000
+++ tests/mocks/Utils/constants.cpp 2016-01-15 13:11:04 +0000
@@ -20,4 +20,11 @@
20 : QObject(parent)20 : QObject(parent)
21{21{
22 m_indicatorValueTimeout = 5000;22 m_indicatorValueTimeout = 5000;
23 m_menuHoverOpenInterval = 250;
24}
25
26void Constants::setMenuHoverOpenInterval(int menuHoverOpenInterval)
27{
28 m_menuHoverOpenInterval = menuHoverOpenInterval;
29 Q_EMIT menuHoverOpenIntervalChanged();
23}30}
2431
=== modified file 'tests/mocks/Utils/constants.h'
--- tests/mocks/Utils/constants.h 2015-04-15 14:52:01 +0000
+++ tests/mocks/Utils/constants.h 2016-01-15 13:11:04 +0000
@@ -30,14 +30,21 @@
30{30{
31 Q_OBJECT31 Q_OBJECT
32 Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)32 Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)
33 Q_PROPERTY(int menuHoverOpenInterval READ menuHoverOpenInterval WRITE setMenuHoverOpenInterval NOTIFY menuHoverOpenIntervalChanged)
3334
34public:35public:
35 Constants(QObject *parent = 0);36 Constants(QObject *parent = 0);
3637
37 int indicatorValueTimeout() const { return m_indicatorValueTimeout; }38 int indicatorValueTimeout() const { return m_indicatorValueTimeout; }
39 int menuHoverOpenInterval() const { return m_menuHoverOpenInterval; }
40 void setMenuHoverOpenInterval(int menuHoverOpenInterval);
41
42Q_SIGNALS:
43 void menuHoverOpenIntervalChanged();
3844
39private:45private:
40 int m_indicatorValueTimeout;46 int m_indicatorValueTimeout;
47 int m_menuHoverOpenInterval;
41};48};
4249
43#endif50#endif
4451
=== modified file 'tests/mocks/Utils/plugin.cpp'
--- tests/mocks/Utils/plugin.cpp 2015-10-28 10:32:47 +0000
+++ tests/mocks/Utils/plugin.cpp 2016-01-15 13:11:04 +0000
@@ -36,6 +36,7 @@
36#include <windowscreenshotprovider.h>36#include <windowscreenshotprovider.h>
37#include <easingcurve.h>37#include <easingcurve.h>
38#include <applicationsfiltermodel.h>38#include <applicationsfiltermodel.h>
39#include <shortcutaction.h>
3940
40static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)41static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
41{42{
@@ -65,6 +66,7 @@
65 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);66 qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
66 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");67 qmlRegisterType<ActiveFocusLogger>(uri, 0, 1, "ActiveFocusLogger");
67 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");68 qmlRegisterType<ApplicationsFilterModel>(uri, 0, 1, "ApplicationsFilterModel");
69 qmlRegisterType<ShortcutAction>(uri, 0, 1, "ShortcutAction");
68}70}
6971
70void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)72void FakeUtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
7173
=== added directory 'tests/qmltests/ApplicationMenus'
=== added file 'tests/qmltests/ApplicationMenus/tst_MenuBar.qml'
--- tests/qmltests/ApplicationMenus/tst_MenuBar.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/ApplicationMenus/tst_MenuBar.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,132 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtTest 1.0
19import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.3
21import Unity.Application 0.1
22import QMenuModel 0.1
23import Unity.Test 0.1
24import Utils 0.1
25
26import "../../../qml/ApplicationMenus"
27import "../Stages"
28
29Item {
30 id: root
31 width: units.gu(100)
32 height: units.gu(50)
33
34 Component.onCompleted: {
35 Constants.menuHoverOpenInterval = 1;
36 }
37
38 DesktopMenuData { id: desktopMenuData }
39
40 Rectangle {
41 anchors {
42 left: parent.left
43 right: parent.right
44 top: parent.top
45 margins: units.gu(1)
46 }
47 height: units.gu(3)
48 color: "grey"
49
50 MenuBar {
51 id: menuBar
52 anchors.fill: parent
53
54 focusWindow: root
55 enableMnemonic: true
56 menuModel: UnityMenuModel {
57 id: menuBackend
58 modelData: desktopMenuData.testData
59 }
60 }
61 }
62
63 SignalSpy {
64 id: activatedSpy
65 target: menuBackend
66 signalName: "activated"
67 }
68
69 UnityTestCase {
70 id: testCase
71 name: "MenuPage"
72 when: windowShown
73
74 property bool clickNavigate: true
75
76 function init() {
77 menuBar.closePopup();
78 menuBackend.modelData = desktopMenuData.generateTestData(3, 2, 0);
79 activatedSpy.clear();
80 }
81
82 function test_mnemonics_data() {
83 return [
84 { tag: "a" },
85 { tag: "b" },
86 ]
87 }
88
89 function test_mnemonics(data) {
90 menuBackend.modelData = desktopMenuData.generateTestData(3, 2, 0);
91
92 keyPress(data.tag, Qt.AltModifier, 100);
93 tryCompareFunction(function() { return menuBar.openItem !== undefined; }, true);
94 }
95
96 function test_navigateRight(data) {
97 var menuItem0 = findChild(menuBar, "menuBar-menu0"); verify(menuItem0);
98 var menuItem1 = findChild(menuBar, "menuBar-menu1"); verify(menuItem1);
99 var menuItem2 = findChild(menuBar, "menuBar-menu2"); verify(menuItem2);
100
101 menuBar.open(menuItem0, true);
102 compare(menuBar.openItem, menuItem0);
103
104 keyClick(Qt.Key_Right, Qt.NoModifier);
105 compare(menuBar.openItem, menuItem1);
106
107 keyClick(Qt.Key_Right, Qt.NoModifier);
108 compare(menuBar.openItem, menuItem2);
109
110 keyClick(Qt.Key_Right, Qt.NoModifier);
111 compare(menuBar.openItem, menuItem0);
112 }
113
114 function test_navigateLeft(data) {
115 var menuItem0 = findChild(menuBar, "menuBar-menu0"); verify(menuItem0);
116 var menuItem1 = findChild(menuBar, "menuBar-menu1"); verify(menuItem1);
117 var menuItem2 = findChild(menuBar, "menuBar-menu2"); verify(menuItem2);
118
119 menuBar.open(menuItem0, true);
120 compare(menuBar.openItem, menuItem0);
121
122 keyClick(Qt.Key_Left, Qt.NoModifier);
123 compare(menuBar.openItem, menuItem2);
124
125 keyClick(Qt.Key_Left, Qt.NoModifier);
126 compare(menuBar.openItem, menuItem1);
127
128 keyClick(Qt.Key_Left, Qt.NoModifier);
129 compare(menuBar.openItem, menuItem0);
130 }
131 }
132}
0133
=== added file 'tests/qmltests/ApplicationMenus/tst_MenuPage.qml'
--- tests/qmltests/ApplicationMenus/tst_MenuPage.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/ApplicationMenus/tst_MenuPage.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,256 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtTest 1.0
19import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.3
21import Unity.Application 0.1
22import QMenuModel 0.1
23import Unity.Test 0.1
24import Utils 0.1
25
26import "../../../qml/ApplicationMenus"
27import "../Stages"
28
29Item {
30 id: root
31 width: Math.max(units.gu(100), page.width + units.gu(6))
32 height: Math.max(units.gu(50), page.height + units.gu(6))
33
34 Component.onCompleted: {
35 Constants.menuHoverOpenInterval = 1;
36 }
37
38 DesktopMenuData { id: desktopMenuData }
39
40 Keys.onEscapePressed: {
41 page.closePopup(true);
42 }
43
44 MenuPage {
45 id: page
46 focus: true
47
48 anchors {
49 left: parent.left
50 top: parent.top
51 leftMargin: units.gu(3)
52 topMargin: units.gu(3)
53 }
54
55 delegateModel: rootDelegate.submenuItems
56 MenuItemDelegateBase {
57 id: rootDelegate
58 menuModel: UnityMenuModel {
59 id: menuBackend
60 modelData: desktopMenuData.generateTestData(3, 3, 0);
61 }
62 }
63 }
64
65 SignalSpy {
66 id: activatedSpy
67 target: menuBackend
68 signalName: "activated"
69 }
70
71 UnityTestCase {
72 id: testCase
73 name: "MenuPage"
74 when: windowShown
75
76 property bool clickNavigate: true
77
78 function init() {
79 page.closePopup(true);
80 menuBackend.modelData = desktopMenuData.generateTestData(3, 3, 0);
81 activatedSpy.clear();
82 }
83
84 // visit and verify that all the backend menus have been created
85 function recurseMenuConstruction(rows, menuPageName) {
86 for (var i = 0; i < rows.length; ++i) {
87 var rowData = rows[i]["rowData"];
88
89 var menuPage = findChild(page, menuPageName);
90 verify(menuPage);
91 var menuItem = findChild(menuPage, menuPageName+"-menu"+i);
92 verify(menuItem);
93
94 // recurse into submenu
95 var submenu = rows[i]["submenu"];
96 if (submenu) {
97 if (clickNavigate) {
98 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
99 } else {
100 mouseMove(menuItem, menuItem.width/2, menuItem.height/2);
101 }
102 tryCompare(menuPage, "openItem", menuItem);
103 recurseMenuConstruction(submenu, menuPageName+"-subMenu"+i);
104 }
105 }
106 }
107
108 function test_hoverNavigation_data() {
109 return [
110 { tag: "long", testData: desktopMenuData.generateTestData(4, 2, 0) },
111 { tag: "deep", testData: desktopMenuData.generateTestData(2, 4, 0) }
112 ]
113 }
114
115 function test_hoverNavigation(data) {
116 clickNavigate = false;
117 menuBackend.modelData = data.testData;
118
119 recurseMenuConstruction(data.testData, "menuPage");
120 }
121
122 function test_clickNavigation_data() {
123 return [
124 { tag: "long", testData: desktopMenuData.generateTestData(4, 2, 0) },
125 { tag: "deep", testData: desktopMenuData.generateTestData(2, 4, 0) }
126 ]
127 }
128
129 function test_clickNavigation(data) {
130 clickNavigate = true;
131 menuBackend.modelData = data.testData;
132
133 recurseMenuConstruction(data.testData, "menuPage");
134 }
135
136 function test_checkableMenuTogglesOnClick() {
137 menuBackend.modelData = desktopMenuData.singleCheckable;
138
139 var menuItem = findChild(page, "menuPage-menu0");
140 verify(menuItem);
141 verify(menuItem.delegate.isCheck);
142 verify(menuItem.delegate.isToggled === false);
143
144 mouseClick(menuItem, menuItem.width/2, menuItem.height/2);
145
146 compare(menuItem.delegate.isToggled, true, "Checkable menu should have toggled");
147 }
148
149 function test_keyboardNavigation_DownKeySelectsAndOpensNextMenuItemAndRotates() {
150 menuBackend.modelData = desktopMenuData.generateTestData(4, 2, 3);
151 var listView = findChild(page, "menuPage-ListView");
152 verify(listView);
153
154 var menuItem0 = findChild(page, "menuPage-menu0"); verify(menuItem0);
155 var menuItem1 = findChild(page, "menuPage-menu1"); verify(menuItem1);
156 var menuItem2 = findChild(page, "menuPage-menu2"); verify(menuItem2);
157 verify(menuItem2.delegate.isSeparator);
158 var menuItem3 = findChild(page, "menuPage-menu3"); verify(menuItem3);
159
160 keyClick(Qt.Key_Down, Qt.NoModifier, 100);
161 compare(listView.selectedItem, menuItem0);
162 tryCompare(page, "openItem", menuItem0);
163
164 keyClick(Qt.Key_Down, Qt.NoModifier, 100);
165 compare(listView.selectedItem, menuItem1);
166 tryCompare(page, "openItem", menuItem1);
167
168 // Skip separator
169
170 keyClick(Qt.Key_Down, Qt.NoModifier, 100);
171 compare(listView.selectedItem, menuItem3);
172 tryCompare(page, "openItem", menuItem3);
173
174 keyClick(Qt.Key_Down, Qt.NoModifier, 100);
175 compare(listView.selectedItem, menuItem0);
176 tryCompare(page, "openItem", menuItem0);
177 }
178
179 function test_keyboardNavigation_UpKeySelectsAndOpensPreviousMenuItemAndRotates() {
180 menuBackend.modelData = desktopMenuData.generateTestData(4, 2, 3);
181 var listView = findChild(page, "menuPage-ListView");
182 verify(listView);
183
184 var menuItem0 = findChild(page, "menuPage-menu0"); verify(menuItem0);
185 var menuItem1 = findChild(page, "menuPage-menu1"); verify(menuItem1);
186 var menuItem2 = findChild(page, "menuPage-menu2"); verify(menuItem2);
187 verify(menuItem2.delegate.isSeparator);
188 var menuItem3 = findChild(page, "menuPage-menu3"); verify(menuItem3);
189
190 keyClick(Qt.Key_Up, Qt.NoModifier, 100);
191 compare(listView.selectedItem, menuItem3);
192 tryCompare(page, "openItem", menuItem3);
193
194 // Skip separator
195
196 keyClick(Qt.Key_Up, Qt.NoModifier, 100);
197 compare(listView.selectedItem, menuItem1);
198 tryCompare(page, "openItem", menuItem1);
199
200 keyClick(Qt.Key_Up, Qt.NoModifier, 100);
201 compare(listView.selectedItem, menuItem0);
202 tryCompare(page, "openItem", menuItem0);
203
204 keyClick(Qt.Key_Up, Qt.NoModifier, 100);
205 compare(listView.selectedItem, menuItem3);
206 tryCompare(page, "openItem", menuItem3);
207 }
208
209 function test_keyboardNavigation_RightKeyEntersSubMenu() {
210 menuBackend.modelData = desktopMenuData.generateTestData(2, 2, 0);
211
212 var menuItem = findChild(page, "menuPage-menu0");
213 verify(menuItem);
214 page.open(menuItem, true);
215 compare(page.openItem, menuItem);
216
217 var submenu = findChild(page, "menuPage-subMenu0");
218 verify(submenu);
219 var listView = findChild(page, "menuPage-subMenu0-ListView");
220 verify(listView);
221 menuItem = findChild(page, "menuPage-subMenu0-menu0");
222 verify(menuItem);
223
224 keyClick(Qt.Key_Right, Qt.NoModifier);
225 compare(listView.selectedItem, menuItem);
226 }
227
228 function test_keyboardNavigation_LeftKeyClosesSubMenu() {
229 menuBackend.modelData = desktopMenuData.generateTestData(2, 2, 0);
230
231 var menuItem = findChild(page, "menuPage-menu0");
232 verify(menuItem);
233 page.open(menuItem, true, 0); // quick open & select item 0
234
235 compare(page.openItem, menuItem);
236 keyClick(Qt.Key_Left, Qt.NoModifier);
237 compare(page.openItem, undefined);
238 }
239
240 function test_mnemonics() {
241 var menuItem0 = findChild(page, "menuPage-menu0"); verify(menuItem0);
242
243 keyClick(Qt.Key_A, Qt.NoModifier);
244 tryCompare(page, "openItem", menuItem0);
245
246 var submenu0 = findChild(menuItem0, "menuPage-subMenu0"); verify(submenu0);
247 var submenu0_menuItem1 = findChild(submenu0, "menuPage-subMenu0-menu1"); verify(submenu0_menuItem1);
248
249 keyClick(Qt.Key_B, Qt.NoModifier);
250 tryCompare(submenu0, "openItem", submenu0_menuItem1);
251
252 keyClick(Qt.Key_B, Qt.NoModifier);
253 compare(activatedSpy.signalArguments, [{ "0": "menuA.B.B" }], "Activate should have been emmited once");
254 }
255 }
256}
0257
=== modified file 'tests/qmltests/CMakeLists.txt'
--- tests/qmltests/CMakeLists.txt 2016-01-11 17:37:50 +0000
+++ tests/qmltests/CMakeLists.txt 2016-01-15 13:11:04 +0000
@@ -5,6 +5,8 @@
5add_unity8_qmltest(. DisabledScreenNotice)5add_unity8_qmltest(. DisabledScreenNotice)
6add_unity8_qmltest(. Shell LIGHTDM)6add_unity8_qmltest(. Shell LIGHTDM)
7add_unity8_qmltest(. ShellWithPin LIGHTDM)7add_unity8_qmltest(. ShellWithPin LIGHTDM)
8add_unity8_qmltest(ApplicationMenus MenuBar)
9add_unity8_qmltest(ApplicationMenus MenuPage)
8add_unity8_qmltest(Components Background)10add_unity8_qmltest(Components Background)
9add_unity8_qmltest(Components Carousel)11add_unity8_qmltest(Components Carousel)
10add_unity8_qmltest(Components Dialogs LIGHTDM)12add_unity8_qmltest(Components Dialogs LIGHTDM)
@@ -85,6 +87,7 @@
85add_unity8_qmltest(Stages SessionContainer)87add_unity8_qmltest(Stages SessionContainer)
86add_unity8_qmltest(Stages TabletStage)88add_unity8_qmltest(Stages TabletStage)
87add_unity8_qmltest(Stages WindowResizeArea)89add_unity8_qmltest(Stages WindowResizeArea)
90add_unity8_qmltest(Stages WindowDecoration)
88add_unity8_qmltest(Stages Splash)91add_unity8_qmltest(Stages Splash)
89add_unity8_qmltest(Tutorial Tutorial LIGHTDM)92add_unity8_qmltest(Tutorial Tutorial LIGHTDM)
90add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")93add_unity8_qmltest(Wizard Wizard ENVIRONMENT "OXIDE_NO_SANDBOX=1")
9194
=== modified file 'tests/qmltests/Panel/tst_Panel.qml'
--- tests/qmltests/Panel/tst_Panel.qml 2015-11-18 15:12:56 +0000
+++ tests/qmltests/Panel/tst_Panel.qml 2016-01-15 13:11:04 +0000
@@ -24,10 +24,11 @@
24import Ubuntu.Telephony 0.1 as Telephony24import Ubuntu.Telephony 0.1 as Telephony
25import "../../../qml/Panel"25import "../../../qml/Panel"
26import "../../../qml/Components/PanelState"26import "../../../qml/Components/PanelState"
27import "../Stages"
2728
28IndicatorTest {29IndicatorTest {
29 id: root30 id: root
30 width: units.gu(100)31 width: units.gu(120)
31 height: units.gu(71)32 height: units.gu(71)
32 color: "white"33 color: "white"
3334
@@ -37,6 +38,17 @@
37 value: !windowControlsCB.checked38 value: !windowControlsCB.checked
38 }39 }
3940
41 DesktopMenuData { id: appMenuData }
42
43 Component.onCompleted: {
44 Indicators.UnityMenuModelCache.setCachedModelData("/dialer-app", appMenuData.dialerData);
45 }
46
47 Rectangle {
48 anchors.fill: parent
49 color: "darkgrey"
50 }
51
40 RowLayout {52 RowLayout {
41 anchors.fill: parent53 anchors.fill: parent
42 anchors.margins: units.gu(1)54 anchors.margins: units.gu(1)
@@ -105,10 +117,11 @@
105 Layout.fillWidth: true117 Layout.fillWidth: true
106 CheckBox {118 CheckBox {
107 id: windowControlsCB119 id: windowControlsCB
108 onClicked: PanelState.buttonsVisible = checked120 onClicked: PanelState.decorationsVisible = checked
109 }121 }
110 Label {122 Label {
111 text: "Show window controls"123 text: "Show window decorations"
124 color: "white"
112 }125 }
113 }126 }
114127
@@ -124,6 +137,7 @@
124 }137 }
125 Label {138 Label {
126 text: "Show fake window title"139 text: "Show fake window title"
140 color: "white"
127 }141 }
128 }142 }
129143
@@ -133,6 +147,19 @@
133 color: "black"147 color: "black"
134 }148 }
135149
150 ApplicationCheckBox {
151 id: applicationCheckBox
152 Layout.fillWidth: true
153 appId: "dialer-app"
154 onTriggered: PanelState.maximizedApplication = checked ? "dialer-app" : "";
155 }
156
157 Rectangle {
158 Layout.preferredHeight: units.dp(1);
159 Layout.fillWidth: true;
160 color: "black"
161 }
162
136 Repeater {163 Repeater {
137 model: root.originalModelData164 model: root.originalModelData
138 RowLayout {165 RowLayout {
@@ -143,6 +170,7 @@
143 Label {170 Label {
144 Layout.fillWidth: true171 Layout.fillWidth: true
145 text: modelData["identifier"]172 text: modelData["identifier"]
173 color: "white"
146 }174 }
147175
148 CheckBox {176 CheckBox {
@@ -151,6 +179,7 @@
151 }179 }
152 Label {180 Label {
153 text: "visible"181 text: "visible"
182 color: "white"
154 }183 }
155 }184 }
156 }185 }
@@ -161,7 +190,10 @@
161 color: "black"190 color: "black"
162 }191 }
163192
164 MouseTouchEmulationCheckbox { id: mouseEmulation }193 MouseTouchEmulationCheckbox {
194 id: mouseEmulation
195 color: "white"
196 }
165 }197 }
166 }198 }
167199
@@ -492,5 +524,25 @@
492 compare(panel.indicators.shown, false);524 compare(panel.indicators.shown, false);
493 tryCompare(panel.indicators, "fullyClosed", true);525 tryCompare(panel.indicators, "fullyClosed", true);
494 }526 }
527
528 function test_showsDecorations() {
529 compare(panel.indicators.shown, false);
530 verify(panel.indicators.fullyClosed);
531
532 mouseClick(panel.indicators,
533 panel.indicators.width / 2,
534 panel.indicators.minimizedPanelHeight / 2);
535
536 compare(panel.indicators.shown, true);
537 tryCompare(panel.indicators, "fullyOpened", true);
538
539 var handle = findChild(panel.indicators, "handle");
540 verify(handle);
541
542 mouseClick(handle);
543
544 compare(panel.indicators.shown, false);
545 tryCompare(panel.indicators, "fullyClosed", true);
546 }
495 }547 }
496}548}
497549
=== modified file 'tests/qmltests/Stages/ApplicationCheckBox.qml'
--- tests/qmltests/Stages/ApplicationCheckBox.qml 2015-08-11 11:41:08 +0000
+++ tests/qmltests/Stages/ApplicationCheckBox.qml 2016-01-15 13:11:04 +0000
@@ -24,6 +24,8 @@
24 property string appId24 property string appId
25 property bool checked: false25 property bool checked: false
2626
27 signal triggered
28
27 enabled: appId !== "unity8-dash"29 enabled: appId !== "unity8-dash"
2830
29 onCheckedChanged: {31 onCheckedChanged: {
@@ -35,6 +37,7 @@
35 } else {37 } else {
36 ApplicationManager.stopApplication(root.appId);38 ApplicationManager.stopApplication(root.appId);
37 }39 }
40 root.triggered();
38 d.bindGuard = false;41 d.bindGuard = false;
39 }42 }
4043
@@ -69,6 +72,7 @@
69 } else {72 } else {
70 ApplicationManager.stopApplication(root.appId);73 ApplicationManager.stopApplication(root.appId);
71 }74 }
75 root.triggered();
72 d.bindGuard = false;76 d.bindGuard = false;
73 }77 }
74 onCheckedChanged: {78 onCheckedChanged: {
7579
=== added file 'tests/qmltests/Stages/DesktopMenuData.qml'
--- tests/qmltests/Stages/DesktopMenuData.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/DesktopMenuData.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,702 @@
1import QtQuick 2.4
2
3QtObject {
4
5 property var dialerData: [{
6 "rowData": { // 1.1
7 "label": "_dialer1",
8 "sensitive": true,
9 "isSeparator": false,
10 "icon": "",
11 "type": "com.canonical.indicator.test",
12 "ext": {},
13 "action": "dialer1",
14 "actionState": {},
15 "isCheck": false,
16 "isRadio": false,
17 "isToggled": false,
18 "shortcut": "Alt+F"
19 },
20 "submenu": [{
21 "rowData": { // 1.1
22 "label": "menu1.1",
23 "sensitive": true,
24 "isSeparator": false,
25 "icon": "",
26 "type": "com.canonical.indicator.test",
27 "ext": {},
28 "action": "menu1.1",
29 "actionState": {},
30 "isCheck": false,
31 "isRadio": false,
32 "isToggled": false,
33 "shortcut": "Alt+0"
34 }}, {
35 "rowData": { // 1.2
36 "label": "menu1.2",
37 "sensitive": true,
38 "isSeparator": false,
39 "icon": "",
40 "type": "com.canonical.indicator.test",
41 "ext": {},
42 "action": "menu1.2",
43 "actionState": {},
44 "isCheck": false,
45 "isRadio": false,
46 "isToggled": false,
47 "shortcut": "Alt+1"
48 },
49 "submenu": [{
50 "rowData": { // 1.2.1
51 "label": "menu1.2.1",
52 "sensitive": true,
53 "isSeparator": false,
54 "icon": "",
55 "type": "com.canonical.indicator.test",
56 "ext": {},
57 "action": "menu1.2.1",
58 "actionState": {},
59 "isCheck": false,
60 "isRadio": false,
61 "isToggled": false,
62 "shortcut": ""
63 }}, {
64 "rowData": { // 1.2.2
65 "label": "menu1.2.2",
66 "sensitive": true,
67 "isSeparator": false,
68 "icon": "",
69 "type": "com.canonical.indicator.test",
70 "ext": {},
71 "action": "menu1.2.2",
72 "actionState": {},
73 "isCheck": false,
74 "isRadio": false,
75 "isToggled": false,
76 "shortcut": ""
77 }}, {
78 "rowData": { // 1.2.3
79 "label": "",
80 "sensitive": false,
81 "isSeparator": true,
82 "icon": "",
83 "type": "",
84 "ext": {},
85 "action": "",
86 "actionState": {},
87 "isCheck": false,
88 "isRadio": false,
89 "isToggled": false,
90 "shortcut": ""
91 }}, {
92 "rowData": { // row 1.2.4
93 "label": "menu1.2.4",
94 "sensitive": true,
95 "isSeparator": false,
96 "icon": "",
97 "type": "com.canonical.indicator.test",
98 "ext": {},
99 "action": "menu1.2.4",
100 "actionState": {},
101 "isCheck": false,
102 "isRadio": false,
103 "isToggled": true,
104 "shortcut": ""
105 }}
106 ]}, {
107 "rowData": { // 1.3
108 "label": "",
109 "sensitive": false,
110 "isSeparator": true,
111 "icon": "",
112 "type": "",
113 "ext": {},
114 "action": "",
115 "actionState": {},
116 "isCheck": false,
117 "isRadio": false,
118 "isToggled": false,
119 "shortcut": ""
120 }}, {
121 "rowData": { // row 1.4
122 "label": "menu1.4",
123 "sensitive": true,
124 "isSeparator": false,
125 "icon": "",
126 "type": "com.canonical.indicator.test",
127 "ext": {},
128 "action": "menu1.4",
129 "actionState": {},
130 "isCheck": true,
131 "isRadio": false,
132 "isToggled": true,
133 "shortcut": "Alt+2"
134 }}
135 ]}, {
136 "rowData": { // 2
137 "label": "d_ialer2",
138 "sensitive": true,
139 "isSeparator": false,
140 "icon": "",
141 "type": "com.canonical.indicator.test",
142 "ext": {},
143 "action": "dialer2",
144 "actionState": {},
145 "isCheck": false,
146 "isRadio": false,
147 "isToggled": false,
148 "shortcut": "Alt+E"
149 },
150 "submenu": [{
151 "rowData": { // 2.1
152 "label": "menu2.1",
153 "sensitive": true,
154 "isSeparator": false,
155 "icon": "",
156 "type": "com.canonical.indicator.test",
157 "ext": {},
158 "action": "menu2.1",
159 "actionState": {},
160 "isCheck": false,
161 "isRadio": false,
162 "isToggled": false,
163 "shortcut": ""
164 }}
165 ]}, {
166 "rowData": { // row 3
167 "label": "di_aler3",
168 "sensitive": true,
169 "isSeparator": false,
170 "icon": "",
171 "type": "com.canonical.indicator.test",
172 "ext": {},
173 "action": "dialer3",
174 "actionState": {},
175 "isCheck": false,
176 "isRadio": false,
177 "isToggled": false,
178 "shortcut": ""
179 },
180 "submenu": [{
181 "rowData": { // 3.1
182 "label": "menu3.1",
183 "sensitive": true,
184 "isSeparator": false,
185 "icon": "",
186 "type": "com.canonical.indicator.test",
187 "ext": {},
188 "action": "menu3.1",
189 "actionState": {},
190 "isCheck": false,
191 "isRadio": false,
192 "isToggled": false,
193 "shortcut": ""
194 }}
195 ]}
196 ]
197
198 property var cameraData: [{
199 "rowData": { // 1.1
200 "label": "camera1",
201 "sensitive": true,
202 "isSeparator": false,
203 "icon": "",
204 "type": "com.canonical.indicator.test",
205 "ext": {},
206 "action": "camera1",
207 "actionState": {},
208 "isCheck": false,
209 "isRadio": false,
210 "isToggled": false,
211 "shortcut": ""
212 },
213 "submenu": [{
214 "rowData": { // 1.1
215 "label": "menu1.1",
216 "sensitive": true,
217 "isSeparator": false,
218 "icon": "",
219 "type": "com.canonical.indicator.test",
220 "ext": {},
221 "action": "menu1.1",
222 "actionState": {},
223 "isCheck": false,
224 "isRadio": false,
225 "isToggled": false,
226 "shortcut": ""
227 }}, {
228 "rowData": { // 1.2
229 "label": "menu1.2",
230 "sensitive": true,
231 "isSeparator": false,
232 "icon": "",
233 "type": "com.canonical.indicator.test",
234 "ext": {},
235 "action": "menu1.2",
236 "actionState": {},
237 "isCheck": false,
238 "isRadio": false,
239 "isToggled": false,
240 "shortcut": ""
241 }}, {
242 "rowData": { // row 1.2
243 "label": "menu1.2",
244 "sensitive": true,
245 "isSeparator": false,
246 "icon": "",
247 "type": "com.canonical.indicator.test",
248 "ext": {},
249 "action": "menu1.2",
250 "actionState": {},
251 "isCheck": false,
252 "isRadio": false,
253 "isToggled": false,
254 "shortcut": ""
255 }}
256 ]}, {
257 "rowData": { // 2
258 "label": "camera2",
259 "sensitive": true,
260 "isSeparator": false,
261 "icon": "",
262 "type": "com.canonical.indicator.test",
263 "ext": {},
264 "action": "camera2",
265 "actionState": {},
266 "isCheck": false,
267 "isRadio": false,
268 "isToggled": false,
269 "shortcut": ""
270 }}
271 ]
272
273 property var galleryData: [{
274 "rowData": { // 1.1
275 "label": "gallery1",
276 "sensitive": true,
277 "isSeparator": false,
278 "icon": "",
279 "type": "com.canonical.indicator.test",
280 "ext": {},
281 "action": "gallery1",
282 "actionState": {},
283 "isCheck": false,
284 "isRadio": false,
285 "isToggled": false,
286 "shortcut": ""
287 },
288 "submenu": [{
289 "rowData": { // 1.1
290 "label": "menu0",
291 "sensitive": true,
292 "isSeparator": false,
293 "icon": "",
294 "type": "com.canonical.indicator.test",
295 "ext": {},
296 "action": "menu0",
297 "actionState": {},
298 "isCheck": false,
299 "isRadio": false,
300 "isToggled": false,
301 "shortcut": ""
302 }}, {
303 "rowData": { // 1.2
304 "label": "menu1",
305 "sensitive": true,
306 "isSeparator": false,
307 "icon": "",
308 "type": "com.canonical.indicator.test",
309 "ext": {},
310 "action": "menu1",
311 "actionState": {},
312 "isCheck": false,
313 "isRadio": false,
314 "isToggled": false,
315 "shortcut": ""
316 }}, {
317 "rowData": { // 1.2
318 "label": "",
319 "sensitive": false,
320 "isSeparator": true,
321 "icon": "",
322 "type": "",
323 "ext": {},
324 "action": "",
325 "actionState": {},
326 "isCheck": false,
327 "isRadio": false,
328 "isToggled": false,
329 "shortcut": ""
330 }}, {
331 "rowData": { // row 1.2
332 "label": "menu2",
333 "sensitive": true,
334 "isSeparator": false,
335 "icon": "",
336 "type": "com.canonical.indicator.test",
337 "ext": {},
338 "action": "menu2",
339 "actionState": {},
340 "isCheck": false,
341 "isRadio": false,
342 "isToggled": false,
343 "shortcut": ""
344 }}
345 ]}, {
346 "rowData": { // 2
347 "label": "gallery2",
348 "sensitive": true,
349 "isSeparator": false,
350 "icon": "",
351 "type": "com.canonical.indicator.test",
352 "ext": {},
353 "action": "gallery2",
354 "actionState": {},
355 "isCheck": false,
356 "isRadio": false,
357 "isToggled": false,
358 "shortcut": ""
359 }}, {
360 "rowData": { // row 2
361 "label": "gallery3",
362 "sensitive": true,
363 "isSeparator": false,
364 "icon": "",
365 "type": "com.canonical.indicator.test",
366 "ext": {},
367 "action": "gallery3",
368 "actionState": {},
369 "isCheck": false,
370 "isRadio": false,
371 "isToggled": false,
372 "shortcut": ""
373 }}
374 ]
375
376 property var testData: [{
377 "rowData": { // 1
378 "label": "_menu1",
379 "sensitive": true,
380 "isSeparator": false,
381 "icon": "",
382 "type": "com.canonical.indicator.test",
383 "ext": {},
384 "action": "menu1",
385 "actionState": {},
386 "isCheck": false,
387 "isRadio": false,
388 "isToggled": false,
389 "shortcut": "Alt+F"
390 },
391 "submenu": [{
392 "rowData": { // 1.1
393 "label": "menu1.1",
394 "sensitive": true,
395 "isSeparator": false,
396 "icon": "",
397 "type": "com.canonical.indicator.test",
398 "ext": {},
399 "action": "menu1.1",
400 "actionState": {},
401 "isCheck": false,
402 "isRadio": false,
403 "isToggled": false,
404 "shortcut": "Alt+0"
405 }}, {
406 "rowData": { // 1.2
407 "label": "m_enu1.2",
408 "sensitive": true,
409 "isSeparator": false,
410 "icon": "",
411 "type": "com.canonical.indicator.test",
412 "ext": {},
413 "action": "menu1.2",
414 "actionState": {},
415 "isCheck": false,
416 "isRadio": false,
417 "isToggled": false,
418 "shortcut": "Alt+1"
419 },
420 "submenu": [{
421 "rowData": { // 1.2.1
422 "label": "menu1.2.1",
423 "sensitive": true,
424 "isSeparator": false,
425 "icon": "",
426 "type": "com.canonical.indicator.test",
427 "ext": {},
428 "action": "menu1.2.1",
429 "actionState": {},
430 "isCheck": false,
431 "isRadio": false,
432 "isToggled": false,
433 "shortcut": ""
434 }}, {
435 "rowData": { // 1.2.2
436 "label": "men_u1.2.2",
437 "sensitive": true,
438 "isSeparator": false,
439 "icon": "",
440 "type": "com.canonical.indicator.test",
441 "ext": {},
442 "action": "menu1.2.2",
443 "actionState": {},
444 "isCheck": false,
445 "isRadio": false,
446 "isToggled": false,
447 "shortcut": ""
448 }}, {
449 "rowData": { // 1.2.3
450 "label": "",
451 "sensitive": false,
452 "isSeparator": true,
453 "icon": "",
454 "type": "",
455 "ext": {},
456 "action": "",
457 "actionState": {},
458 "isCheck": false,
459 "isRadio": false,
460 "isToggled": false,
461 "shortcut": ""
462 }}, {
463 "rowData": { // row 1.2.4
464 "label": "menu1.2.4",
465 "sensitive": true,
466 "isSeparator": false,
467 "icon": "",
468 "type": "com.canonical.indicator.test",
469 "ext": {},
470 "action": "menu1.2.4",
471 "actionState": {},
472 "isCheck": false,
473 "isRadio": false,
474 "isToggled": true,
475 "shortcut": ""
476 }}
477 ]}, {
478 "rowData": { // 1.3
479 "label": "",
480 "sensitive": false,
481 "isSeparator": true,
482 "icon": "",
483 "type": "",
484 "ext": {},
485 "action": "",
486 "actionState": {},
487 "isCheck": false,
488 "isRadio": false,
489 "isToggled": false,
490 "shortcut": ""
491 }}, {
492 "rowData": { // row 1.4
493 "label": "menu1.4",
494 "sensitive": true,
495 "isSeparator": false,
496 "icon": "",
497 "type": "com.canonical.indicator.test",
498 "ext": {},
499 "action": "menu1.4",
500 "actionState": {},
501 "isCheck": true,
502 "isRadio": false,
503 "isToggled": true,
504 "shortcut": "Alt+2"
505 }}
506 ]}, {
507 "rowData": { // 2
508 "label": "menu2",
509 "sensitive": true,
510 "isSeparator": false,
511 "icon": "",
512 "type": "com.canonical.indicator.test",
513 "ext": {},
514 "action": "menu2",
515 "actionState": {},
516 "isCheck": false,
517 "isRadio": false,
518 "isToggled": false,
519 "shortcut": "Alt+E"
520 },
521 "submenu": [{
522 "rowData": { // 2.1
523 "label": "menu2.1",
524 "sensitive": true,
525 "isSeparator": false,
526 "icon": "",
527 "type": "com.canonical.indicator.test",
528 "ext": {},
529 "action": "menu2.1",
530 "actionState": {},
531 "isCheck": false,
532 "isRadio": false,
533 "isToggled": false,
534 "shortcut": ""
535 }}
536 ]}, {
537 "rowData": { // row 3
538 "label": "me_nu3",
539 "sensitive": true,
540 "isSeparator": false,
541 "icon": "",
542 "type": "com.canonical.indicator.test",
543 "ext": {},
544 "action": "dialer3",
545 "actionState": {},
546 "isCheck": false,
547 "isRadio": false,
548 "isToggled": false,
549 "shortcut": ""
550 },
551 "submenu": [{
552 "rowData": { // 3.1
553 "label": "menu3.1",
554 "sensitive": true,
555 "isSeparator": false,
556 "icon": "",
557 "type": "com.canonical.indicator.test",
558 "ext": {},
559 "action": "menu3.1",
560 "actionState": {},
561 "isCheck": false,
562 "isRadio": false,
563 "isToggled": false,
564 "shortcut": ""
565 }}
566 ]}
567 ]
568
569 property var deepTestData: [{
570 "rowData": { // 1
571 "label": "_menu1",
572 "sensitive": true,
573 "isSeparator": false,
574 "icon": "",
575 "type": "com.canonical.indicator.test",
576 "ext": {},
577 "action": "menu1",
578 "actionState": {},
579 "isCheck": false,
580 "isRadio": false,
581 "isToggled": false,
582 "shortcut": "Alt+F"
583 },
584 "submenu": [{
585 "rowData": { // 1.1
586 "label": "menu1.1",
587 "sensitive": true,
588 "isSeparator": false,
589 "icon": "",
590 "type": "com.canonical.indicator.test",
591 "ext": {},
592 "action": "menu1.1",
593 "actionState": {},
594 "isCheck": false,
595 "isRadio": false,
596 "isToggled": false,
597 "shortcut": ""
598 },
599 "submenu": [{
600 "rowData": { // 1.1.1
601 "label": "menu1.1.1",
602 "sensitive": true,
603 "isSeparator": false,
604 "icon": "",
605 "type": "com.canonical.indicator.test",
606 "ext": {},
607 "action": "menu1.1.1",
608 "actionState": {},
609 "isCheck": false,
610 "isRadio": false,
611 "isToggled": false,
612 "shortcut": ""
613 },
614 "submenu": [{
615 "rowData": { // 1.1.1
616 "label": "menu1.1.1.1",
617 "sensitive": true,
618 "isSeparator": false,
619 "icon": "",
620 "type": "com.canonical.indicator.test",
621 "ext": {},
622 "action": "menu1.1.1.1",
623 "actionState": {},
624 "isCheck": false,
625 "isRadio": false,
626 "isToggled": false,
627 "shortcut": ""
628 },
629 "submenu": [{
630 "rowData": { // 1.1.1.1
631 "label": "menu1.1.1.1.1",
632 "sensitive": true,
633 "isSeparator": false,
634 "icon": "",
635 "type": "com.canonical.indicator.test",
636 "ext": {},
637 "action": "menu1.1.1.1.1",
638 "actionState": {},
639 "isCheck": false,
640 "isRadio": false,
641 "isToggled": false,
642 "shortcut": ""
643 }}
644 ]}
645 ]}
646 ]}
647 ]}
648 ]
649
650 property var singleCheckable: [{
651 "rowData": { // 1
652 "label": "checkable1",
653 "sensitive": true,
654 "isSeparator": false,
655 "icon": "",
656 "type": "com.canonical.indicator.test",
657 "ext": {},
658 "action": "checkable1",
659 "actionState": {},
660 "isCheck": true,
661 "isRadio": false,
662 "isToggled": false,
663 "shortcut": "Alt+F"
664 }
665 }]
666
667 function generateTestData(length, depth, separatorInterval, prefix) {
668 var data = [];
669
670 if (prefix === undefined) prefix = "menu"
671
672 for (var i = 0; i < length; i++) {
673
674 var menuCode = String.fromCharCode(i+65);
675
676 var isSeparator = separatorInterval > 0 && ((i+1) % separatorInterval == 0);
677 var row = {
678 "rowData": { // 1
679 "label": prefix + "&" + menuCode,
680 "sensitive": true,
681 "isSeparator": isSeparator,
682 "icon": "",
683 "type": "com.canonical.indicator.test",
684 "ext": {},
685 "action": prefix + menuCode,
686 "actionState": {},
687 "isCheck": false,
688 "isRadio": false,
689 "isToggled": false,
690 "shortcut": ""
691 }
692 }
693 if (!isSeparator && depth > 1) {
694 var submenu = generateTestData(length, depth-1, separatorInterval, prefix + menuCode + ".");
695 row["submenu"] = submenu;
696 }
697 data[i] = row;
698 }
699 return data;
700 }
701}
702
0703
=== modified file 'tests/qmltests/Stages/tst_DesktopStage.qml'
--- tests/qmltests/Stages/tst_DesktopStage.qml 2016-01-07 14:00:21 +0000
+++ tests/qmltests/Stages/tst_DesktopStage.qml 2016-01-15 13:11:04 +0000
@@ -19,8 +19,10 @@
19import Ubuntu.Components 1.319import Ubuntu.Components 1.3
20import Ubuntu.Components.ListItems 1.320import Ubuntu.Components.ListItems 1.3
21import Unity.Application 0.121import Unity.Application 0.1
22import Unity.Indicators 0.1 as Indicators
22import Unity.Test 0.123import Unity.Test 0.1
23import Utils 0.124import Utils 0.1
25import QMenuModel 0.1
2426
25import ".." // For EdgeBarrierControls27import ".." // For EdgeBarrierControls
26import "../../../qml/Stages"28import "../../../qml/Stages"
@@ -40,7 +42,17 @@
40 value: false42 value: false
41 }43 }
4244
43 Component.onCompleted: resetGeometry()45 DesktopMenuData {
46 id: appMenuData
47 }
48
49 Component.onCompleted: {
50 resetGeometry();
51
52 Indicators.UnityMenuModelCache.setCachedModelData("/dialer-app", appMenuData.dialerData);
53 Indicators.UnityMenuModelCache.setCachedModelData("/camera-app", appMenuData.cameraData);
54 Indicators.UnityMenuModelCache.setCachedModelData("/gallery-app", appMenuData.galleryData);
55 }
4456
45 function resetGeometry() {57 function resetGeometry() {
46 // ensures apps which are tested decorations are in view.58 // ensures apps which are tested decorations are in view.
4759
=== added file 'tests/qmltests/Stages/tst_WindowDecoration.qml'
--- tests/qmltests/Stages/tst_WindowDecoration.qml 1970-01-01 00:00:00 +0000
+++ tests/qmltests/Stages/tst_WindowDecoration.qml 2016-01-15 13:11:04 +0000
@@ -0,0 +1,163 @@
1/*
2 * Copyright (C) 2016 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18import QtTest 1.0
19import QtQuick.Layouts 1.1
20import Ubuntu.Components 1.3
21import Ubuntu.Components.ListItems 1.3
22import Unity.Application 0.1
23import Unity.Test 0.1
24import Utils 0.1
25import QMenuModel 0.1
26
27import "../../../qml/Stages"
28
29Item {
30 id: root
31 width: units.gu(70)
32 height: units.gu(50)
33
34 Binding {
35 target: MouseTouchAdaptor
36 property: "enabled"
37 value: false
38 }
39
40 DesktopMenuData { id: desktopMenuData }
41
42 Rectangle {
43 anchors {
44 left: parent.left
45 right: parent.right
46 bottom: parent.bottom
47 margins: units.gu(1)
48 }
49 height: units.gu(20)
50 border.width: 1
51 border.color: "black"
52
53 TextArea {
54 id: log
55 anchors.fill: parent
56 readOnly: true
57 }
58 }
59
60 WindowDecoration {
61 id: decoration
62 anchors { left: parent.left; right: parent.right; top: parent.top }
63 anchors.margins: units.gu(1)
64 title: "TestTitle - Doing something"
65 active: true
66 height: units.gu(3)
67 menu: menuBackend
68 UnityMenuModel {
69 id: menuBackend
70 modelData: desktopMenuData.testData
71 onActivated: log.text = "Activated " + action + "\n" + log.text
72 }
73
74 onClose: log.text = "Close\n" + log.text
75 onMinimize: log.text = "Minimize\n" + log.text
76 onMaximize: log.text = "Maximize\n" + log.text
77 }
78
79 SignalSpy {
80 id: signalSpy
81 target: decoration
82 }
83
84 UnityTestCase {
85 id: testCase
86 name: "MenuPage"
87 when: windowShown
88
89 function init() {
90 decoration.menu = menuBackend;
91 signalSpy.clear();
92 }
93
94 function test_windowControlButtons_data() {
95 return [ { tag: "close", controlName: "closeWindowButton", signal: "close"},
96 { tag: "minimize", controlName: "minimizeWindowButton", signal: "minimize"},
97 { tag: "maximize", controlName: "maximizeWindowButton", signal: "maximize"}];
98 }
99
100 function test_windowControlButtons(data) {
101 signalSpy.signalName = data.signal;
102 var controlButton = findChild(decoration, data.controlName);
103 verify(controlButton !== null);
104
105 mouseClick(controlButton, controlButton.width/2, controlButton.height/2);
106 compare(signalSpy.count, 1);
107 }
108
109 function test_titleRemainsWhenHoveringOnTitleBarWithNoMenu() {
110 decoration.menu = undefined;
111
112 var menuLoader = findChild(decoration, "windowDecorationMenuBarLoader");
113 verify(menuLoader);
114 mouseMove(menuLoader, menuLoader.width/2, menuLoader.height/2);
115 wait(200);
116
117 var titleLabel = findChild(decoration, "windowDecorationTitle");
118 verify(menuLoader);
119
120 compare(menuLoader.opacity, 0, "Menu should not show when present")
121 compare(titleLabel.opacity, 1, "Title should always show when app menu not present")
122 }
123
124 function test_menuShowsWhenHoveringOnTitleBar() {
125 var menuLoader = findChild(decoration, "windowDecorationMenuBarLoader");
126 verify(menuLoader);
127 mouseMove(menuLoader, menuLoader.width/2, menuLoader.height/2)
128
129 var titleLabel = findChild(decoration, "windowDecorationTitle");
130 verify(menuLoader);
131
132 tryCompare(menuLoader, "opacity", 1);
133 tryCompare(titleLabel, "opacity", 0);
134
135 mouseMove(menuLoader, menuLoader.width/2, menuLoader.height * 2);
136
137 tryCompare(menuLoader, "opacity", 0);
138 tryCompare(titleLabel, "opacity", 1);
139 }
140
141 function test_showMenuBarWithShortcutsOnLongAltPress() {
142 var menuLoader = findChild(decoration, "windowDecorationMenuBarLoader");
143 verify(menuLoader);
144
145 var titleLabel = findChild(decoration, "windowDecorationTitle");
146 verify(menuLoader);
147
148 var menuBar = findChild(decoration, "menuBar");
149 verify(menuBar);
150 verify(menuBar.enableMnemonic === false, "Menubar should not show shortcuts")
151
152 keyPress(Qt.Key_Alt, Qt.NoModifier);
153 tryCompare(menuLoader, "opacity", 1);
154 tryCompare(titleLabel, "opacity", 0);
155 compare(menuBar.enableMnemonic, true, "Menubar should show shortcuts when long alt pressed");
156
157 keyRelease(Qt.Key_Alt, Qt.NoModifier);
158 tryCompare(menuLoader, "opacity", 0);
159 tryCompare(titleLabel, "opacity", 1);
160 compare(menuBar.enableMnemonic, false, "Menubar should not show shortcuts after long alt pressed");
161 }
162 }
163}
0164
=== modified file 'tests/qmltests/tst_Shell.qml'
--- tests/qmltests/tst_Shell.qml 2015-12-08 15:37:51 +0000
+++ tests/qmltests/tst_Shell.qml 2016-01-15 13:11:04 +0000
@@ -30,6 +30,7 @@
30import Powerd 0.130import Powerd 0.1
31import Wizard 0.1 as Wizard31import Wizard 0.1 as Wizard
32import Utils 0.132import Utils 0.1
33import Unity.Indicators 0.1 as Indicators
3334
34import "../../qml"35import "../../qml"
35import "../../qml/Components"36import "../../qml/Components"
@@ -47,6 +48,14 @@
47 LightDM.Greeter.mockMode = "single";48 LightDM.Greeter.mockMode = "single";
48 LightDM.Users.mockMode = "single";49 LightDM.Users.mockMode = "single";
49 shellLoader.active = true;50 shellLoader.active = true;
51
52 Indicators.UnityMenuModelCache.setCachedModelData("/dialer-app", appMenuData.dialerData);
53 Indicators.UnityMenuModelCache.setCachedModelData("/camera-app", appMenuData.cameraData);
54 Indicators.UnityMenuModelCache.setCachedModelData("/gallery-app", appMenuData.galleryData);
55 }
56
57 DesktopMenuData {
58 id: appMenuData
50 }59 }
5160
52 Item {61 Item {
@@ -1752,17 +1761,17 @@
1752 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");1761 var maximizeButton = findChild(appDelegate, "maximizeWindowButton");
17531762
1754 tryCompare(appDelegate, "state", "normal");1763 tryCompare(appDelegate, "state", "normal");
1755 tryCompare(PanelState, "buttonsVisible", false)1764 tryCompare(PanelState, "decorationsVisible", false)
17561765
1757 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);1766 mouseClick(maximizeButton, maximizeButton.width / 2, maximizeButton.height / 2);
1758 tryCompare(appDelegate, "state", "maximized");1767 tryCompare(appDelegate, "state", "maximized");
1759 tryCompare(PanelState, "buttonsVisible", true)1768 tryCompare(PanelState, "decorationsVisible", true)
17601769
1761 ApplicationManager.stopApplication(appId);1770 ApplicationManager.stopApplication(appId);
1762 tryCompare(PanelState, "buttonsVisible", false)1771 tryCompare(PanelState, "decorationsVisible", false)
17631772
1764 ApplicationManager.startApplication(appId);1773 ApplicationManager.startApplication(appId);
1765 tryCompare(PanelState, "buttonsVisible", true)1774 tryCompare(PanelState, "decorationsVisible", true)
1766 }1775 }
17671776
1768 function test_newAppHasValidGeometry() {1777 function test_newAppHasValidGeometry() {

Subscribers

People subscribed via source and target branches