Merge lp:~nick-dedekind/ubuntu-ui-toolkit/menus into lp:ubuntu-ui-toolkit/staging

Proposed by Nick Dedekind
Status: Superseded
Proposed branch: lp:~nick-dedekind/ubuntu-ui-toolkit/menus
Merge into: lp:ubuntu-ui-toolkit/staging
Prerequisite: lp:~nick-dedekind/ubuntu-ui-toolkit/exclusiveGroup
Diff against target: 1953 lines (+1773/-48)
13 files modified
src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro (+10/-2)
src/Ubuntu/UbuntuToolkit/menu.cpp (+669/-0)
src/Ubuntu/UbuntuToolkit/menu_p.h (+75/-0)
src/Ubuntu/UbuntuToolkit/menu_p_p.h (+95/-0)
src/Ubuntu/UbuntuToolkit/menubar.cpp (+339/-0)
src/Ubuntu/UbuntuToolkit/menubar_p.h (+65/-0)
src/Ubuntu/UbuntuToolkit/menubar_p_p.h (+78/-0)
src/Ubuntu/UbuntuToolkit/menugroup.cpp (+164/-0)
src/Ubuntu/UbuntuToolkit/menugroup_p.h (+60/-0)
src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp (+6/-3)
src/Ubuntu/UbuntuToolkit/ucaction.cpp (+62/-37)
src/Ubuntu/UbuntuToolkit/ucaction_p.h (+14/-6)
tests/unit/components/tst_menu.qml (+136/-0)
To merge this branch: bzr merge lp:~nick-dedekind/ubuntu-ui-toolkit/menus
Reviewer Review Type Date Requested Status
Tim Peeters Needs Resubmitting
ubuntu-sdk-build-bot continuous-integration Needs Fixing
Zsombor Egri Approve
PS Jenkins bot continuous-integration Pending
Review via email: mp+297922@code.launchpad.net

This proposal supersedes a proposal from 2016-06-07.

This proposal has been superseded by a proposal from 2016-08-25.

Commit message

API for MenuBar, Menus, MenuItem & MenuSeparator

To post a comment you must log in.
Revision history for this message
Zsombor Egri (zsombi) wrote :

Nice, clean code!!!

A section in UITK Gallery would be also nice to have...

One small hint: UC prefix is no longer mandated, as we have the namespace added.

review: Needs Fixing
Revision history for this message
Nick Dedekind (nick-dedekind) wrote :

> Nice, clean code!!!
>
> A section in UITK Gallery would be also nice to have...

This is only the menu "backend" which gets translated into QPA menus; there are no UI elements, so I don't think there's anything to show in the gallery yet.

>
> One small hint: UC prefix is no longer mandated, as we have the namespace
> added.

Removed the prefix.

Revision history for this message
Zsombor Egri (zsombi) wrote :

All clear! Thanks for this too!

review: Approve
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Zoltan Balogh (bzoltan) wrote :

I have manualy merged this branch to rev2077

Revision history for this message
Tim Peeters (tpeeters) wrote :

I reverted this in r2080 because it breaks compilation.

review: Needs Resubmitting

Unmerged revisions

1359. By Nick Dedekind

fixed broken build

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro'
2--- src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-22 16:02:17 +0000
3+++ src/Ubuntu/UbuntuToolkit/UbuntuToolkit.pro 2016-08-22 16:02:17 +0000
4@@ -44,7 +44,12 @@
5 mousetouchadaptor_p.h \
6 mousetouchadaptor_p_p.h \
7 propertychange_p.h \
8- ubuntutoolkitmodule.h
9+ ubuntutoolkitmodule.h \
10+ menu_p_p.h \
11+ menubar_p_p.h \
12+ menu_p.h \
13+ menubar_p.h \
14+ menugroup_p.h
15
16 SOURCES += \
17 colorutils.cpp \
18@@ -52,7 +57,10 @@
19 asyncloader.cpp \
20 mousetouchadaptor.cpp \
21 propertychange.cpp \
22- ubuntutoolkitmodule.cpp
23+ ubuntutoolkitmodule.cpp \
24+ menu.cpp \
25+ menubar.cpp \
26+ menugroup.cpp
27
28 HEADERS += \
29 uctheme_p.h \
30
31=== added file 'src/Ubuntu/UbuntuToolkit/menu.cpp'
32--- src/Ubuntu/UbuntuToolkit/menu.cpp 1970-01-01 00:00:00 +0000
33+++ src/Ubuntu/UbuntuToolkit/menu.cpp 2016-08-22 16:02:17 +0000
34@@ -0,0 +1,669 @@
35+/*
36+ * Copyright 2016 Canonical Ltd.
37+ *
38+ * This program is free software; you can redistribute it and/or modify
39+ * it under the terms of the GNU Lesser General Public License as published by
40+ * the Free Software Foundation; version 3.
41+ *
42+ * This program is distributed in the hope that it will be useful,
43+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
44+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45+ * GNU Lesser General Public License for more details.
46+ *
47+ * You should have received a copy of the GNU Lesser General Public License
48+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
49+ */
50+
51+#include "menu_p.h"
52+#include "menu_p_p.h"
53+#include "menubar_p.h"
54+#include "actionlist_p.h"
55+#include "menugroup_p.h"
56+
57+// Qt
58+#include <QtGui/qpa/qplatformtheme.h>
59+#include <QtGui/qpa/qplatformmenu.h>
60+#include <QQuickItem>
61+#include <private/qguiapplication_p.h>
62+#include <private/qquickitem_p.h>
63+#include <QPointer>
64+#include <functional>
65+
66+Q_LOGGING_CATEGORY(ucMenu, "ubuntu.components.Menu", QtMsgType::QtWarningMsg)
67+
68+UT_NAMESPACE_BEGIN
69+
70+namespace {
71+
72+QWindow* findWindowForObject(QObject* object)
73+{
74+ QObject* window = object;
75+ while (window && !window->isWindowType()) {
76+ window = window->parent();
77+ if (QQuickItem* item = qobject_cast<QQuickItem*>(window)) {
78+ window = item->window();
79+ }
80+ }
81+ return qobject_cast<QWindow*>(window);
82+}
83+
84+// recursively get the all the object from the menu group which are not lists or groups.
85+QObjectList getActionsFromMenuGroup(MenuGroup* menuGroup) {
86+
87+ QObjectList objectList;
88+
89+ Q_FOREACH(QObject* data, menuGroup->list()) {
90+
91+ if (auto actionList = qobject_cast<ActionList*>(data)) {
92+ Q_FOREACH(UCAction* action, actionList->list()) {
93+ objectList << action;
94+ }
95+ } else if (auto subMenuGroup = qobject_cast<MenuGroup*>(data)) {
96+ objectList << getActionsFromMenuGroup(subMenuGroup);
97+ } else {
98+ objectList << data;
99+ }
100+ }
101+ return objectList;
102+}
103+
104+// recursively get the first object from the menu group which is not a list or a group.
105+QObject* getFirstObject(MenuGroup* menuGroup) {
106+
107+ Q_FOREACH(QObject* data, menuGroup->list()) {
108+
109+ if (auto subMenuGroup = qobject_cast<MenuGroup*>(data)) {
110+ QObject* object = getFirstObject(subMenuGroup);
111+ if (object) { return object; }
112+ } else if (auto actionList = qobject_cast<ActionList*>(data)) {
113+ return actionList->list().count() > 0 ? actionList->list()[0] : 0;
114+ } else {
115+ return data;
116+ }
117+ }
118+ return Q_NULLPTR;
119+}
120+
121+}
122+
123+MenuPrivate::MenuPrivate(Menu *qq)
124+ : q_ptr(qq)
125+ , m_platformMenu(QGuiApplicationPrivate::platformTheme()->createPlatformMenu())
126+{
127+}
128+
129+MenuPrivate::~MenuPrivate()
130+{
131+ qDeleteAll(m_platformItems);
132+ m_platformItems.clear();
133+
134+ delete m_platformMenu;
135+ m_platformMenu = Q_NULLPTR;
136+}
137+
138+void MenuPrivate::insertObject(int index, QObject *o)
139+{
140+ Q_Q(Menu);
141+ if (!o) return;
142+ qCInfo(ucMenu).nospace() << "Menu::insertObject(index="<< index << ", object=" << o << ")";
143+
144+ if (!m_platformMenu) {
145+ m_data.insert(m_data.count() > index ? index : m_data.count(), o);
146+ return;
147+ }
148+
149+ // If the menus contains lists or groups, we need to alter the insertion index to account for them.
150+ int actualIndex = 0;
151+ bool insertSeparator = false;
152+ for (int i = 0; i < index && i < m_data.count(); i++) {
153+ QObject* data = m_data[i];
154+
155+ int dataObjectCount = 0;
156+ if (auto menuGroup = qobject_cast<MenuGroup*>(data)) {
157+ dataObjectCount = getActionsFromMenuGroup(menuGroup).count();
158+ } else if (auto list = qobject_cast<ActionList*>(data)) {
159+ dataObjectCount = list->list().count();
160+ } else {
161+ dataObjectCount = 1;
162+ }
163+ actualIndex += dataObjectCount;
164+ // insert a separator before the new item if there are previous items.
165+ insertSeparator |= dataObjectCount > 0;
166+
167+ // account for item separators if it's not the last data item.
168+ actualIndex += dataObjectCount > 0 && (i+1 < index && i+1 < m_data.count()) ? 1: 0;
169+ }
170+
171+ // need to make sure the item after the insertion index has a separator
172+ if (index < m_data.count()) {
173+ QObject* data = m_data.at(index);
174+ QObject* objectToSeparate(Q_NULLPTR);
175+
176+ if (auto menuGroup = qobject_cast<MenuGroup*>(data)) {
177+ objectToSeparate = getFirstObject(menuGroup);
178+ } else if (auto actionList = qobject_cast<ActionList*>(data)) {
179+ objectToSeparate = actionList->list().count() > 0 ? actionList->list()[0] : 0;
180+ } else {
181+ objectToSeparate = data;
182+ }
183+
184+ if (objectToSeparate && m_platformItems.contains(objectToSeparate)) {
185+ m_platformItems.value(objectToSeparate)->setSeparator();
186+ }
187+ }
188+
189+ m_data.insert(m_data.count() > index ? index : m_data.count(), o);
190+
191+ // if an object changes, we need to remove and re-add it.
192+ std::function<void()> refreshObject = [o, this]() {
193+ int index = m_data.indexOf(o);
194+ if (index >= 0) {
195+ removeObject(o);
196+ insertObject(index, o);
197+ }
198+ };
199+
200+ // Get All the menu item objects
201+ QObjectList objects;
202+ if (auto menuGroup = qobject_cast<MenuGroup*>(o)) {
203+ Q_FOREACH(QObject* menuGroupObject, getActionsFromMenuGroup(menuGroup)) {
204+ objects << menuGroupObject;
205+ }
206+ // connect to content changes
207+ QObject::connect(menuGroup, &MenuGroup::changed, q, refreshObject);
208+ } else if (auto actionList = qobject_cast<ActionList*>(o)) {
209+ Q_FOREACH(UCAction* action, actionList->list()) {
210+ objects << action;
211+ }
212+ // connect to content changes
213+ QObject::connect(actionList, &ActionList::added, q, refreshObject);
214+ QObject::connect(actionList, &ActionList::removed, q, refreshObject);
215+ } else {
216+ objects << o;
217+ }
218+
219+ Q_FOREACH(QObject* platformObject, objects) {
220+ // add to platform
221+ auto platformWrapper = new PlatformItemWrapper(platformObject, q);
222+ platformWrapper->insert(actualIndex++, insertSeparator);
223+ if (platformWrapper->hasSeparator()) { // we also inserted an separator, need to increment for next position.
224+ actualIndex++;
225+ }
226+ insertSeparator = false;
227+ m_platformItems[platformObject] = platformWrapper;
228+ // map the inserted item with the object which sources the platformItem info.
229+ if (!m_dataPlatformObjectMap.contains(o, platformObject)) {
230+ m_dataPlatformObjectMap.insertMulti(o, platformObject);
231+ }
232+
233+ QObject::connect(platformObject, &QObject::destroyed, q, [o, this](QObject* platformObject) {
234+ m_dataPlatformObjectMap.remove(o, platformObject);
235+
236+ if (m_platformItems.contains(platformObject)) {
237+ PlatformItemWrapper* wrapper = m_platformItems.take(platformObject);
238+ wrapper->remove();
239+ delete wrapper;
240+ }
241+ });
242+ }
243+}
244+
245+void MenuPrivate::removeObject(QObject *o)
246+{
247+ Q_Q(Menu);
248+ m_data.removeOne(o);
249+ qCInfo(ucMenu).nospace() << "Menu::removeObject(" << o << ")";
250+
251+ if (m_platformMenu) {
252+ if (auto menuGroup = qobject_cast<MenuGroup*>(o)) {
253+ // disconnect from content changes
254+ QObject::disconnect(menuGroup, &MenuGroup::changed, q, 0);
255+ } else if (ActionList* actionList = qobject_cast<ActionList*>(o)) {
256+ // disconnect from content changes
257+ QObject::disconnect(actionList, &ActionList::added, q, 0);
258+ QObject::disconnect(actionList, &ActionList::removed, q, 0);
259+ }
260+
261+ QList<QObject*> platformObjects = m_dataPlatformObjectMap.values(o);
262+ m_dataPlatformObjectMap.remove(o);
263+
264+ Q_FOREACH(QObject* platformObject, platformObjects) {
265+ // remove from platform.
266+ if (m_platformItems.contains(platformObject)) {
267+ PlatformItemWrapper* wrapper = m_platformItems.take(platformObject);
268+ wrapper->remove();
269+ delete wrapper;
270+ }
271+ }
272+ }
273+}
274+
275+void MenuPrivate::_q_updateEnabled()
276+{
277+ Q_Q(Menu);
278+
279+ bool isEnabled = q->isEnabled();
280+ if (m_platformMenu) { m_platformMenu->setEnabled(isEnabled); }
281+}
282+
283+void MenuPrivate::_q_updateText()
284+{
285+ Q_Q(Menu);
286+
287+ QString text = q->text();
288+ if (m_platformMenu) { m_platformMenu->setText(text); }
289+}
290+
291+void MenuPrivate::_q_updateIcon()
292+{
293+ Q_Q(Menu);
294+
295+ QIcon icon;
296+ if (!q->iconSource().isEmpty()) {
297+ icon = QIcon(q->iconSource().path());
298+ } else if (!q->iconName().isEmpty()) {
299+ icon = QIcon::fromTheme(q->iconName());
300+ }
301+
302+ if (m_platformMenu) { m_platformMenu->setIcon(icon); }
303+}
304+
305+void MenuPrivate::_q_updateVisible()
306+{
307+ Q_Q(Menu);
308+
309+ bool visible = q->visible();
310+ if (m_platformMenu) { m_platformMenu->setVisible(visible); }
311+}
312+
313+void MenuPrivate::data_append(QQmlListProperty<QObject> *prop, QObject *o)
314+{
315+ Menu *q = qobject_cast<Menu *>(prop->object);
316+ q->appendObject(o);
317+}
318+
319+int MenuPrivate::data_count(QQmlListProperty<QObject> *prop)
320+{
321+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
322+ return p->m_data.count();
323+}
324+
325+QObject *MenuPrivate::data_at(QQmlListProperty<QObject> *prop, int index)
326+{
327+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
328+ return p->m_data.value(index, Q_NULLPTR);
329+}
330+
331+void MenuPrivate::data_clear(QQmlListProperty<QObject> *prop)
332+{
333+ MenuPrivate *p = static_cast<MenuPrivate *>(prop->data);
334+ p->m_data.clear();
335+}
336+
337+/*!
338+ * \qmltype Menu
339+ * \inqmlmodule Ubuntu.Components
340+ * \ingroup ubuntu
341+ * \brief Menu defines a context menu or submenu structure of a MenuBar
342+ *
343+ * Example usage:
344+ * \qml
345+ * import QtQuick 2.4
346+ * import Ubuntu.Components 1.3
347+ * Menu {
348+ * text: "&File"
349+ *
350+ * MenuGroup {
351+ * Action {
352+ * text: "&New"
353+ * shortcut: "Ctrl+N"
354+ * }
355+ *
356+ * Action {
357+ * text: "&Open"
358+ * shortcut: "Ctrl+O"
359+ * }
360+ * }
361+ *
362+ * Menu {
363+ * text: "Recent Files"
364+ *
365+ * ActionList {
366+ * Action { text: "1.txt" }
367+ * Action { text: "2.txt" }
368+ * Action { text: "3.txt" }
369+ * }
370+ * }
371+ *
372+ * Action {
373+ * action: Action {
374+ * text: "E&xit"
375+ * shortcut: "Ctrl+X"
376+ * }
377+ * }
378+ * }
379+ * \endqml
380+ */
381+Menu::Menu(QObject *parent)
382+ : UCAction(parent)
383+ , d_ptr(new MenuPrivate(this))
384+{
385+ Q_D(Menu);
386+
387+ connect(this, SIGNAL(enabledChanged()), this, SLOT(_q_updateEnabled()));
388+ connect(this, SIGNAL(textChanged()), this, SLOT(_q_updateText()));
389+ connect(this, SIGNAL(iconNameChanged()), this, SLOT(_q_updateIcon()));
390+ connect(this, SIGNAL(iconSourceChanged()), this, SLOT(_q_updateIcon()));
391+ connect(this, SIGNAL(visibleChanged()), this, SLOT(_q_updateVisible()));
392+}
393+
394+Menu::~Menu()
395+{
396+}
397+
398+/*!
399+ * \qmlproperty list<Object> Menu::data
400+ * \default
401+ * List of objects representing menu items within the menu.
402+ *
403+ * Currently supports Menu, Action, AcionList & MenuGroup objects.
404+ * \note Item object which do not support platformItem will not be exported for native menus.
405+ */
406+QQmlListProperty<QObject> Menu::data()
407+{
408+ Q_D(Menu);
409+ return QQmlListProperty<QObject>(this, d,
410+ &MenuPrivate::data_append,
411+ &MenuPrivate::data_count,
412+ &MenuPrivate::data_at,
413+ &MenuPrivate::data_clear);
414+}
415+
416+/*!
417+ * \qmlmethod Menu::appendObject(object o)
418+ * Add a object tto the menu
419+ */
420+void Menu::appendObject(QObject *o)
421+{
422+ Q_D(Menu);
423+
424+ insertObject(d->m_data.count(), o);
425+}
426+
427+/*!
428+ * \qmlmethod Menu::insertObject(int index, object o)
429+ * Inserts an item at the index in the menu.
430+ *
431+ * Currently supports Menu, Action, AcionList & MenuGroup objects.
432+ * \note Item object which do not support platformItem will not be exported for native menus.
433+ */
434+void Menu::insertObject(int index, QObject *o)
435+{
436+ Q_D(Menu);
437+ d->insertObject(index, o);
438+}
439+
440+/*!
441+ * \qmlmethod Menu::removeObject(object o)
442+ * Removes the item from the menu.
443+ */
444+void Menu::removeObject(QObject *o)
445+{
446+ Q_D(Menu);
447+ qCInfo(ucMenu) << "Menu::removeObject" << o;
448+
449+ d->removeObject(o);
450+}
451+
452+QPlatformMenu* Menu::platformMenu() const
453+{
454+ Q_D(const Menu);
455+ return d->m_platformMenu;
456+}
457+
458+/*!
459+ * \qmlmethod Menu::show(point point)
460+ * Show the menu popup at the given point
461+ */
462+void Menu::show(const QPoint &point)
463+{
464+ Q_D(Menu);
465+ qCInfo(ucMenu, "Menu::popup(%s, point(%d,%d))", qPrintable(text()), point.x(), point.y());
466+
467+ if (d->m_platformMenu) {
468+ d->m_platformMenu->showPopup(findWindowForObject(this), QRect(point, QSize()), Q_NULLPTR);
469+ }
470+}
471+
472+/*!
473+ * \qmlmethod Menu::dismiss()
474+ * Dismiss and destroy the menu popup.
475+ */
476+void Menu::dismiss()
477+{
478+ Q_D(Menu);
479+ qCInfo(ucMenu, "Menu::dismiss(%s)", qPrintable(text()));
480+
481+ if (d->m_platformMenu) {
482+ d->m_platformMenu->dismiss();
483+ }
484+}
485+
486+
487+PlatformItemWrapper::PlatformItemWrapper(QObject *target, Menu* menu)
488+ : QObject(menu)
489+ , m_target(target)
490+ , m_menu(menu)
491+ , m_platformItem(menu->platformMenu() ? menu->platformMenu()->createMenuItem() : Q_NULLPTR)
492+ , m_platformItemSeparator(Q_NULLPTR)
493+ , m_inserted(false)
494+{
495+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
496+ if (m_platformItem) {
497+ m_platformItem->setMenu(menu->platformMenu());
498+ }
499+
500+ connect(menu, &Menu::visibleChanged, this, &PlatformItemWrapper::updateVisible);
501+ connect(menu, &Menu::textChanged, this, &PlatformItemWrapper::updateText);
502+ connect(menu, &Menu::enabledChanged, this, &PlatformItemWrapper::updateEnabled);
503+ connect(menu, &Menu::iconSourceChanged, this, &PlatformItemWrapper::updateIcon);
504+ connect(menu, &Menu::iconNameChanged, this, &PlatformItemWrapper::updateIcon);
505+
506+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
507+
508+ connect(action, &UCAction::visibleChanged, this, &PlatformItemWrapper::updateVisible);
509+ connect(action, &UCAction::textChanged, this, &PlatformItemWrapper::updateText);
510+ connect(action, &UCAction::enabledChanged, this, &PlatformItemWrapper::updateEnabled);
511+ connect(action, &UCAction::iconSourceChanged, this, &PlatformItemWrapper::updateIcon);
512+ connect(action, &UCAction::iconNameChanged, this, &PlatformItemWrapper::updateIcon);
513+ connect(action, &UCAction::shortcutChanged, this, &PlatformItemWrapper::updateShortcut);
514+ connect(action, &UCAction::checkableChanged, this, &PlatformItemWrapper::updateCheck);
515+ connect(action, &UCAction::toggled, this, &PlatformItemWrapper::updateCheck);
516+
517+ if (m_platformItem) {
518+ // Connect to activate (with inversion for checkables)
519+ connect(m_platformItem, &QPlatformMenuItem::activated, action, [action]() {
520+ action->trigger();
521+ });
522+ }
523+
524+ }
525+ syncPlatformItem();
526+}
527+
528+PlatformItemWrapper::~PlatformItemWrapper()
529+{
530+ if (m_inserted && m_menu && m_menu->platformMenu()) {
531+ m_menu->platformMenu()->removeMenuItem(m_platformItem);
532+ if (m_platformItemSeparator) {
533+ m_menu->platformMenu()->removeMenuItem(m_platformItemSeparator);
534+ delete m_platformItemSeparator;
535+ }
536+ }
537+ delete m_platformItem;
538+}
539+
540+void PlatformItemWrapper::insert(int index, bool withSeparator)
541+{
542+ if (m_inserted) return;
543+ qCInfo(ucMenu).nospace() << " PlatformItemWrapper::insert(menu=" << m_menu
544+ << ", index=" << index
545+ << ", object=" << m_target << ")";
546+
547+ auto platformMenu = m_menu->platformMenu();
548+ if (!platformMenu) return;
549+ if (!m_platformItem) return;
550+
551+ QPlatformMenuItem* before = platformMenu->menuItemAt(index);
552+ platformMenu->insertMenuItem(m_platformItem, before);
553+ m_inserted = true;
554+
555+ if (withSeparator) setSeparator();
556+}
557+
558+void PlatformItemWrapper::remove()
559+{
560+ if (!m_inserted) return;
561+ qCInfo(ucMenu).nospace() << " PlatformItemWrapper::remove(menu=" << m_menu
562+ << ", object=" << m_target << ")";
563+
564+ auto platformMenu = m_menu->platformMenu();
565+ if (!platformMenu) return;
566+ if (!m_platformItem) return;
567+
568+ platformMenu->removeMenuItem(m_platformItem);
569+ m_inserted = false;
570+
571+ if (m_platformItemSeparator) {
572+ qCInfo(ucMenu).nospace() << " PlatformItemWrapper::removeSeparator(menu=" << m_menu
573+ << ", object=" << m_target << ")";
574+ platformMenu->removeMenuItem(m_platformItemSeparator);
575+ delete m_platformItemSeparator;
576+ m_platformItemSeparator = Q_NULLPTR;
577+ }
578+}
579+
580+void PlatformItemWrapper::setSeparator()
581+{
582+ // already created
583+ if (m_platformItemSeparator) return;
584+ // not inserted yet.
585+ if (!m_inserted) return;
586+
587+ auto platformMenu = m_menu->platformMenu();
588+ if (!platformMenu) return;
589+
590+ // insert separator before?
591+ m_platformItemSeparator = platformMenu->createMenuItem();
592+ if (m_platformItemSeparator) {
593+ qCInfo(ucMenu).nospace() << " PlatformItemWrapper::setSeparator(menu=" << m_menu
594+ << ", object=" << m_target << ")";
595+
596+ m_platformItemSeparator->setIsSeparator(true);
597+ platformMenu->insertMenuItem(m_platformItemSeparator, m_platformItem);
598+ }
599+}
600+
601+void PlatformItemWrapper::updateVisible()
602+{
603+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
604+ if (m_platformItem) m_platformItem->setVisible(menu->visible());
605+ if (menu->platformMenu()) menu->platformMenu()->setVisible(menu->visible());
606+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
607+ if (m_platformItem) m_platformItem->setVisible(action->visible());
608+ }
609+}
610+
611+void PlatformItemWrapper::updateEnabled()
612+{
613+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
614+ if (m_platformItem) m_platformItem->setEnabled(menu->isEnabled());
615+ if (menu->platformMenu()) menu->platformMenu()->setEnabled(menu->isEnabled());
616+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
617+ if (m_platformItem) m_platformItem->setEnabled(action->isEnabled());
618+ }
619+}
620+
621+void PlatformItemWrapper::updateText()
622+{
623+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
624+ if (m_platformItem) m_platformItem->setText(menu->text());
625+ if (menu->platformMenu()) menu->platformMenu()->setText(menu->text());
626+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
627+ if (m_platformItem) m_platformItem->setText(action->text());
628+ }
629+}
630+
631+void PlatformItemWrapper::updateIcon()
632+{
633+ QIcon icon;
634+ if (Menu* menu = qobject_cast<Menu*>(m_target)) {
635+
636+ if (!menu->iconSource().isEmpty()) {
637+ icon = QIcon(menu->iconSource().path());
638+ } else if (!menu->iconName().isEmpty()) {
639+ icon = QIcon::fromTheme(menu->iconName());
640+ }
641+ if (menu->platformMenu()) menu->platformMenu()->setIcon(icon);
642+
643+ } else if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
644+
645+ if (!action->iconSource().isEmpty()) {
646+ icon = QIcon(action->iconSource().path());
647+ } else if (!action->iconName().isEmpty()) {
648+ icon = QIcon::fromTheme(action->iconName());
649+ }
650+ }
651+
652+ if (m_platformItem) { m_platformItem->setIcon(icon); }
653+}
654+
655+
656+inline QKeySequence sequenceFromVariant(const QVariant& variant)
657+{
658+ if (variant.type() == QVariant::Int) {
659+ return static_cast<QKeySequence::StandardKey>(variant.toInt());
660+ }
661+ if (variant.type() == QVariant::String) {
662+ return QKeySequence::fromString(variant.toString());
663+ }
664+ return QKeySequence();
665+}
666+
667+void PlatformItemWrapper::updateShortcut()
668+{
669+ if (!m_platformItem) return;
670+
671+ if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
672+ m_platformItem->setShortcut(sequenceFromVariant(action->shortcut()));
673+ }
674+}
675+
676+void PlatformItemWrapper::updateCheck()
677+{
678+ if (!m_platformItem) return;
679+
680+ if (UCAction* action = qobject_cast<UCAction*>(m_target)) {
681+ bool checkable = action->isCheckable();
682+ m_platformItem->setCheckable(checkable);
683+ m_platformItem->setChecked(checkable && action->isChecked());
684+ }
685+}
686+
687+void PlatformItemWrapper::syncPlatformItem()
688+{
689+ updateVisible();
690+ updateEnabled();
691+ updateText();
692+ updateIcon();
693+ updateShortcut();
694+ updateCheck();
695+
696+ if (m_menu->platformMenu() && m_platformItem) {
697+ m_menu->platformMenu()->syncMenuItem(m_platformItem);
698+ }
699+}
700+
701+UT_NAMESPACE_END
702+
703+#include "moc_menu_p.cpp"
704
705=== added file 'src/Ubuntu/UbuntuToolkit/menu_p.h'
706--- src/Ubuntu/UbuntuToolkit/menu_p.h 1970-01-01 00:00:00 +0000
707+++ src/Ubuntu/UbuntuToolkit/menu_p.h 2016-08-22 16:02:17 +0000
708@@ -0,0 +1,75 @@
709+/*
710+ * Copyright 2016 Canonical Ltd.
711+ *
712+ * This program is free software; you can redistribute it and/or modify
713+ * it under the terms of the GNU Lesser General Public License as published by
714+ * the Free Software Foundation; version 3.
715+ *
716+ * This program is distributed in the hope that it will be useful,
717+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
718+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
719+ * GNU Lesser General Public License for more details.
720+ *
721+ * You should have received a copy of the GNU Lesser General Public License
722+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
723+ *
724+ */
725+
726+#ifndef MENU_P_H
727+#define MENU_P_H
728+
729+#include <QQmlListProperty>
730+#include <QLoggingCategory>
731+#include <QPointer>
732+#include <ubuntutoolkitglobal.h>
733+
734+#include "ucaction_p.h"
735+
736+Q_DECLARE_LOGGING_CATEGORY(ucMenu);
737+
738+class QPlatformMenu;
739+class QPlatformMenuItem;
740+class QQuickItem;
741+
742+UT_NAMESPACE_BEGIN
743+
744+class MenuPrivate;
745+class MenuBar;
746+class UCAction;
747+class UBUNTUTOOLKIT_EXPORT Menu : public UCAction
748+{
749+ Q_OBJECT
750+
751+ Q_PROPERTY(QQmlListProperty<QObject> data READ data FINAL)
752+ Q_CLASSINFO("DefaultProperty", "data")
753+
754+public:
755+ explicit Menu(QObject *parent = 0);
756+ ~Menu();
757+
758+ QQmlListProperty<QObject> data();
759+
760+ Q_INVOKABLE void appendObject(QObject* obj);
761+ Q_INVOKABLE void insertObject(int index, QObject* obj);
762+ Q_INVOKABLE void removeObject(QObject* obj);
763+
764+ QPlatformMenu *platformMenu() const;
765+
766+public Q_SLOTS:
767+ void show(const QPoint& pt);
768+ void dismiss();
769+
770+private:
771+ Q_DISABLE_COPY(Menu)
772+ Q_DECLARE_PRIVATE(Menu)
773+ QScopedPointer<MenuPrivate> d_ptr;
774+
775+ Q_PRIVATE_SLOT(d_func(), void _q_updateEnabled())
776+ Q_PRIVATE_SLOT(d_func(), void _q_updateText())
777+ Q_PRIVATE_SLOT(d_func(), void _q_updateIcon())
778+ Q_PRIVATE_SLOT(d_func(), void _q_updateVisible())
779+};
780+
781+UT_NAMESPACE_END
782+
783+#endif // MENU_P_H
784
785=== added file 'src/Ubuntu/UbuntuToolkit/menu_p_p.h'
786--- src/Ubuntu/UbuntuToolkit/menu_p_p.h 1970-01-01 00:00:00 +0000
787+++ src/Ubuntu/UbuntuToolkit/menu_p_p.h 2016-08-22 16:02:17 +0000
788@@ -0,0 +1,95 @@
789+/*
790+ * Copyright 2016 Canonical Ltd.
791+ *
792+ * This program is free software; you can redistribute it and/or modify
793+ * it under the terms of the GNU Lesser General Public License as published by
794+ * the Free Software Foundation; version 3.
795+ *
796+ * This program is distributed in the hope that it will be useful,
797+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
798+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
799+ * GNU Lesser General Public License for more details.
800+ *
801+ * You should have received a copy of the GNU Lesser General Public License
802+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
803+ *
804+ */
805+
806+#ifndef MENU_P_P_H
807+#define MENU_P_P_H
808+
809+#include "menu_p.h"
810+#include <ubuntutoolkitglobal.h>
811+
812+class QObject;
813+class QQmlComponent;
814+
815+UT_NAMESPACE_BEGIN
816+
817+class UCAction;
818+class Menu;
819+class PlatformItemWrapper;
820+
821+class MenuPrivate
822+{
823+ Q_DECLARE_PUBLIC(Menu)
824+public:
825+ MenuPrivate(Menu *qq);
826+ virtual ~MenuPrivate();
827+
828+ void insertObject(int index, QObject *obj);
829+ void removeObject(QObject *obj);
830+
831+ void _q_updateEnabled();
832+ void _q_updateText();
833+ void _q_updateIcon();
834+ void _q_updateVisible();
835+
836+ static void data_append(QQmlListProperty<QObject> *prop, QObject *o);
837+ static int data_count(QQmlListProperty<QObject> *prop);
838+ static QObject *data_at(QQmlListProperty<QObject> *prop, int index);
839+ static void data_clear(QQmlListProperty<QObject> *prop);
840+
841+ Menu* q_ptr;
842+ QPlatformMenu* m_platformMenu;
843+ UCAction* m_action;
844+
845+ QHash<QObject*, PlatformItemWrapper*> m_platformItems;
846+ QMultiHash<QObject*, QObject*> m_dataPlatformObjectMap;
847+ QVector<QObject*> m_data;
848+};
849+
850+class PlatformItemWrapper : public QObject
851+{
852+ Q_OBJECT
853+public:
854+ PlatformItemWrapper(QObject *target, Menu* menu);
855+ ~PlatformItemWrapper();
856+
857+ void insert(int index, bool withSeparator);
858+ void remove();
859+ void setSeparator();
860+
861+ bool hasSeparator() const { return m_platformItemSeparator != Q_NULLPTR; }
862+
863+public Q_SLOTS:
864+ void updateVisible();
865+ void updateEnabled();
866+ void updateText();
867+ void updateIcon();
868+ void updateShortcut();
869+ void updateCheck();
870+
871+private:
872+ void syncPlatformItem();
873+
874+ QObject* m_target;
875+ QPointer<Menu> m_menu;
876+ QPlatformMenuItem* m_platformItem;
877+ QPlatformMenuItem* m_platformItemSeparator;
878+ bool m_inserted;
879+};
880+
881+UT_NAMESPACE_END
882+
883+#endif // MENU_P_P_H
884
885=== added file 'src/Ubuntu/UbuntuToolkit/menubar.cpp'
886--- src/Ubuntu/UbuntuToolkit/menubar.cpp 1970-01-01 00:00:00 +0000
887+++ src/Ubuntu/UbuntuToolkit/menubar.cpp 2016-08-22 16:02:17 +0000
888@@ -0,0 +1,339 @@
889+/*
890+ * Copyright 2016 Canonical Ltd.
891+ *
892+ * This program is free software; you can redistribute it and/or modify
893+ * it under the terms of the GNU Lesser General Public License as published by
894+ * the Free Software Foundation; version 3.
895+ *
896+ * This program is distributed in the hope that it will be useful,
897+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
898+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
899+ * GNU Lesser General Public License for more details.
900+ *
901+ * You should have received a copy of the GNU Lesser General Public License
902+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
903+ *
904+ */
905+
906+#include "menubar_p.h"
907+#include "menubar_p_p.h"
908+
909+// Qt
910+#include <QQuickItem>
911+#include <QQuickWindow>
912+#include <private/qguiapplication_p.h>
913+#include <QtGui/qpa/qplatformtheme.h>
914+#include <QtGui/qpa/qplatformmenu.h>
915+
916+UT_NAMESPACE_BEGIN
917+
918+MenuBarPrivate::MenuBarPrivate(MenuBar *qq)
919+ : q_ptr(qq)
920+{
921+ m_platformBar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar();
922+}
923+
924+MenuBarPrivate::~MenuBarPrivate()
925+{
926+ qDeleteAll(m_platformMenus);
927+ m_platformMenus.clear();
928+
929+ delete m_platformBar;
930+ m_platformBar = Q_NULLPTR;
931+}
932+
933+void MenuBarPrivate::insertMenu(int index, Menu* menu)
934+{
935+ Q_Q(MenuBar);
936+ Menu* prevMenu = m_menus.count() > index ? m_menus[index] : Q_NULLPTR;
937+
938+ m_menus.insert(index, menu);
939+
940+ // add to platform
941+ if (m_platformBar && menu->platformMenu()) {
942+ auto platformWrapper = new PlatformMenuWrapper(menu, q);
943+ platformWrapper->insert(prevMenu ? prevMenu->platformMenu() : Q_NULLPTR);
944+ m_platformMenus[menu] = platformWrapper;
945+
946+ QObject::connect(menu, &QObject::destroyed, q, [this](QObject* object) {
947+ if (m_platformMenus.contains(object)) {
948+ PlatformMenuWrapper* wrapper = m_platformMenus.take(object);
949+ wrapper->remove();
950+ delete wrapper;
951+ }
952+ });
953+ }
954+}
955+
956+void MenuBarPrivate::removeMenu(Menu *menu)
957+{
958+ m_menus.removeOne(menu);
959+
960+ if (m_platformBar) {
961+ if (m_platformMenus.contains(menu)) {
962+ PlatformMenuWrapper* wrapper = m_platformMenus.take(menu);
963+ wrapper->remove();
964+ delete wrapper;
965+ }
966+ }
967+}
968+
969+void MenuBarPrivate::menu_append(QQmlListProperty<Menu> *prop, Menu *o)
970+{
971+ MenuBarPrivate *q = static_cast<MenuBarPrivate *>(prop->data);
972+ // menubar is the menus parent
973+ o->setParent(prop->object);
974+ q->insertMenu(q->m_menus.count(), o);
975+}
976+
977+int MenuBarPrivate::menu_count(QQmlListProperty<Menu> *prop)
978+{
979+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
980+ return p->m_menus.count();
981+}
982+
983+Menu *MenuBarPrivate::menu_at(QQmlListProperty<Menu> *prop, int index)
984+{
985+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
986+ return p->m_menus.value(index);
987+}
988+
989+void MenuBarPrivate::menu_clear(QQmlListProperty<Menu> *prop)
990+{
991+ MenuBarPrivate *p = static_cast<MenuBarPrivate *>(prop->data);
992+ p->m_menus.clear();
993+}
994+
995+/*!
996+ * \qmltype MenuBar
997+ * \inqmlmodule Ubuntu.Components 1.3
998+ * \ingroup ubuntu
999+ * \brief MenuBar defines an application menu bar structure
1000+ *
1001+ * Example usage:
1002+ * \qml
1003+ * import QtQuick 2.4
1004+ * import Ubuntu.Components 1.3
1005+ * MainView {
1006+ * MenuBar {
1007+ * Menu {
1008+ * text: "_File"
1009+ *
1010+ * MenuItem {
1011+ * text: "_New"
1012+ * shortcut: "Ctrl+N"
1013+ * }
1014+ *
1015+ * MenuItem {
1016+ * text: "_Open"
1017+ * shortcut: "Ctrl+O"
1018+ * }
1019+ *
1020+ * MenuSeparator {}
1021+ *
1022+ * MenuItem {
1023+ * action: exitAction
1024+ * }
1025+ * }
1026+ *
1027+ * Menu {
1028+ * text: "_Edit"
1029+ *
1030+ * MenuItem {
1031+ * text: "_Undo"
1032+ * iconSource: "image://theme/undo"
1033+ * }
1034+ * }
1035+ * Menu {
1036+ * text: "_Window"
1037+ *
1038+ * MenuItem {
1039+ * text: "Fullscreen"
1040+ * checkable: true
1041+ * checked: false
1042+ * }
1043+ * }
1044+ * }
1045+ * Action {
1046+ * id: boundAction
1047+ * text: "E_xit"
1048+ * onTriggered: {
1049+ * Qt.quit();
1050+ * }
1051+ * }
1052+ * }
1053+ * \endqml
1054+ */
1055+MenuBar::MenuBar(QObject *parent)
1056+ : QObject(parent)
1057+ , d_ptr(new MenuBarPrivate(this))
1058+{
1059+}
1060+
1061+MenuBar::~MenuBar()
1062+{
1063+}
1064+
1065+/*!
1066+ * \qmlmethod void MenuBar::appendMenu(Menu menu)
1067+ * Append a Menu to the MenuBar
1068+ */
1069+void MenuBar::appendMenu(Menu *menu)
1070+{
1071+ Q_D(MenuBar);
1072+ insertMenu(d->m_menus.count(), menu);
1073+}
1074+
1075+/*!
1076+ * \qmlmethod void MenuBar::insertMenu(int index, Menu menu)
1077+ * Insert a Menu to the MenuBar at the specified position
1078+ */
1079+void MenuBar::insertMenu(int index, Menu *menu)
1080+{
1081+ Q_D(MenuBar);
1082+ if (!menu) return;
1083+
1084+ d->insertMenu(index, menu);
1085+ Q_EMIT menusChanged();
1086+}
1087+
1088+/*!
1089+ * \qmlmethod void MenuBar::removeMenu(Menu menu)
1090+ * Remove a Menu from the MenuBar
1091+ */
1092+void MenuBar::removeMenu(Menu *menu)
1093+{
1094+ Q_D(MenuBar);
1095+ if (!menu) return;
1096+
1097+ d->removeMenu(menu);
1098+ Q_EMIT menusChanged();
1099+}
1100+/*!
1101+ * \qmlproperty list<Menu> MenuBar::menus
1102+ * \default
1103+ * List of Menus in this MenuBar.
1104+ */
1105+QQmlListProperty<Menu> MenuBar::menus()
1106+{
1107+ Q_D(MenuBar);
1108+ return QQmlListProperty<Menu>(this, d,
1109+ &MenuBarPrivate::menu_append,
1110+ &MenuBarPrivate::menu_count,
1111+ &MenuBarPrivate::menu_at,
1112+ &MenuBarPrivate::menu_clear);
1113+}
1114+
1115+QPlatformMenuBar *MenuBar::platformMenuBar() const
1116+{
1117+ Q_D(const MenuBar);
1118+ return d->m_platformBar;
1119+}
1120+
1121+void MenuBar::classBegin()
1122+{
1123+}
1124+
1125+void MenuBar::componentComplete()
1126+{
1127+ Q_D(MenuBar);
1128+
1129+ auto parentItem = qobject_cast<QQuickItem*>(parent());
1130+ if (parentItem && d->m_platformBar) {
1131+ d->m_platformBar->handleReparent(parentItem->window());
1132+ }
1133+}
1134+
1135+
1136+PlatformMenuWrapper::PlatformMenuWrapper(Menu *target, MenuBar* bar)
1137+ : QObject(bar)
1138+ , m_bar(bar)
1139+ , m_target(target)
1140+ , m_inserted(false)
1141+{
1142+ connect(m_target, &Menu::visibleChanged, this, &PlatformMenuWrapper::updateVisible);
1143+ connect(m_target, &Menu::textChanged, this, &PlatformMenuWrapper::updateText);
1144+ connect(m_target, &Menu::enabledChanged, this, &PlatformMenuWrapper::updateEnabled);
1145+ connect(m_target, &Menu::iconSourceChanged, this, &PlatformMenuWrapper::updateIcon);
1146+ connect(m_target, &Menu::iconNameChanged, this, &PlatformMenuWrapper::updateIcon);
1147+
1148+ syncPlatformMenu();
1149+}
1150+
1151+PlatformMenuWrapper::~PlatformMenuWrapper()
1152+{
1153+ if (m_inserted && m_bar && m_bar->platformMenuBar()) {
1154+ if (m_target && m_target->platformMenu()) {
1155+ m_bar->platformMenuBar()->removeMenu(m_target->platformMenu());
1156+ }
1157+ }
1158+}
1159+
1160+void PlatformMenuWrapper::insert(QPlatformMenu *before)
1161+{
1162+ if (m_inserted) return;
1163+ qCInfo(ucMenu).nospace() << " PlatformMenuWrapper::insert(bar=" << m_bar
1164+ << ", before=" << before
1165+ << ", menu=" << m_target << ")";
1166+
1167+ auto platformBar = m_bar->platformMenuBar();
1168+ if (!platformBar) return;
1169+ auto platformMenu = m_target->platformMenu();
1170+ if (!platformMenu) return;
1171+
1172+ platformBar->insertMenu(platformMenu, before);
1173+ m_inserted = true;
1174+}
1175+
1176+void PlatformMenuWrapper::remove()
1177+{
1178+ if (!m_inserted) return;
1179+ qCInfo(ucMenu).nospace() << " PlatformMenuWrapper::remove(bar=" << m_bar
1180+ << ", menu=" << m_target << ")";
1181+
1182+ auto platformBar = m_bar->platformMenuBar();
1183+ if (!platformBar) return;
1184+ auto platformMenu = m_target->platformMenu();
1185+ if (!platformMenu) return;
1186+
1187+ platformBar->removeMenu(platformMenu);
1188+ m_inserted = false;
1189+}
1190+
1191+void PlatformMenuWrapper::updateVisible()
1192+{
1193+ if (m_target->platformMenu()) m_target->platformMenu()->setVisible(m_target->visible());
1194+}
1195+
1196+void PlatformMenuWrapper::updateEnabled()
1197+{
1198+ if (m_target->platformMenu()) m_target->platformMenu()->setEnabled(m_target->isEnabled());
1199+}
1200+
1201+void PlatformMenuWrapper::updateText()
1202+{
1203+ if (m_target->platformMenu()) m_target->platformMenu()->setText(m_target->text());
1204+}
1205+
1206+void PlatformMenuWrapper::updateIcon()
1207+{
1208+ QIcon icon;
1209+ if (!m_target->iconSource().isEmpty()) {
1210+ icon = QIcon(m_target->iconSource().path());
1211+ } else if (!m_target->iconName().isEmpty()) {
1212+ icon = QIcon::fromTheme(m_target->iconName());
1213+ }
1214+ if (m_target->platformMenu()) m_target->platformMenu()->setIcon(icon);
1215+}
1216+
1217+void PlatformMenuWrapper::syncPlatformMenu()
1218+{
1219+ updateVisible();
1220+ updateEnabled();
1221+ updateText();
1222+ updateIcon();
1223+}
1224+
1225+UT_NAMESPACE_END
1226+
1227+#include "moc_menubar_p.cpp"
1228
1229=== added file 'src/Ubuntu/UbuntuToolkit/menubar_p.h'
1230--- src/Ubuntu/UbuntuToolkit/menubar_p.h 1970-01-01 00:00:00 +0000
1231+++ src/Ubuntu/UbuntuToolkit/menubar_p.h 2016-08-22 16:02:17 +0000
1232@@ -0,0 +1,65 @@
1233+/*
1234+ * Copyright 2016 Canonical Ltd.
1235+ *
1236+ * This program is free software; you can redistribute it and/or modify
1237+ * it under the terms of the GNU Lesser General Public License as published by
1238+ * the Free Software Foundation; version 3.
1239+ *
1240+ * This program is distributed in the hope that it will be useful,
1241+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1242+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1243+ * GNU Lesser General Public License for more details.
1244+ *
1245+ * You should have received a copy of the GNU Lesser General Public License
1246+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1247+ *
1248+ */
1249+
1250+#ifndef MENUBAR_P_H
1251+#define MENUBAR_P_H
1252+
1253+#include "menu_p.h"
1254+#include <ubuntutoolkitglobal.h>
1255+
1256+#include <QQmlParserStatus>
1257+
1258+class QPlatformMenuBar;
1259+
1260+UT_NAMESPACE_BEGIN
1261+
1262+class MenuBarPrivate;
1263+class UBUNTUTOOLKIT_EXPORT MenuBar : public QObject, public QQmlParserStatus
1264+{
1265+ Q_OBJECT
1266+ Q_INTERFACES(QQmlParserStatus)
1267+
1268+ Q_PROPERTY(QQmlListProperty<UT_PREPEND_NAMESPACE(Menu)> menus READ menus NOTIFY menusChanged FINAL)
1269+ Q_CLASSINFO("DefaultProperty", "menus")
1270+
1271+public:
1272+ explicit MenuBar(QObject *parent = 0);
1273+ ~MenuBar();
1274+
1275+ Q_INVOKABLE void appendMenu(Menu *menu);
1276+ Q_INVOKABLE void insertMenu(int index, Menu *menu);
1277+ Q_INVOKABLE void removeMenu(Menu *menu);
1278+
1279+ QQmlListProperty<Menu> menus();
1280+
1281+ QPlatformMenuBar *platformMenuBar() const;
1282+
1283+ void classBegin() Q_DECL_OVERRIDE;
1284+ void componentComplete() Q_DECL_OVERRIDE;
1285+
1286+Q_SIGNALS:
1287+ void menusChanged();
1288+
1289+private:
1290+ Q_DISABLE_COPY(MenuBar)
1291+ Q_DECLARE_PRIVATE(MenuBar)
1292+ QScopedPointer<MenuBarPrivate> d_ptr;
1293+};
1294+
1295+UT_NAMESPACE_END
1296+
1297+#endif // MENUBAR_P_H
1298
1299=== added file 'src/Ubuntu/UbuntuToolkit/menubar_p_p.h'
1300--- src/Ubuntu/UbuntuToolkit/menubar_p_p.h 1970-01-01 00:00:00 +0000
1301+++ src/Ubuntu/UbuntuToolkit/menubar_p_p.h 2016-08-22 16:02:17 +0000
1302@@ -0,0 +1,78 @@
1303+/*
1304+ * Copyright 2016 Canonical Ltd.
1305+ *
1306+ * This program is free software; you can redistribute it and/or modify
1307+ * it under the terms of the GNU Lesser General Public License as published by
1308+ * the Free Software Foundation; version 3.
1309+ *
1310+ * This program is distributed in the hope that it will be useful,
1311+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1312+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1313+ * GNU Lesser General Public License for more details.
1314+ *
1315+ * You should have received a copy of the GNU Lesser General Public License
1316+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1317+ *
1318+ */
1319+
1320+#ifndef MENUBAR_P_P_H
1321+#define MENUBAR_P_P_H
1322+
1323+#include "menubar_p.h"
1324+#include <ubuntutoolkitglobal.h>
1325+
1326+class QPlatformMenuBar;
1327+
1328+UT_NAMESPACE_BEGIN
1329+
1330+class PlatformMenuWrapper;
1331+
1332+class MenuBarPrivate
1333+{
1334+ Q_DECLARE_PUBLIC(MenuBar)
1335+public:
1336+ MenuBarPrivate(MenuBar *qq);
1337+ ~MenuBarPrivate();
1338+
1339+ void insertMenu(int index, Menu *menu);
1340+ void removeMenu(Menu *menu);
1341+
1342+ static void menu_append(QQmlListProperty<Menu> *prop, Menu *o);
1343+ static int menu_count(QQmlListProperty<Menu> *prop);
1344+ static Menu *menu_at(QQmlListProperty<Menu> *prop, int index);
1345+ static void menu_clear(QQmlListProperty<Menu> *prop);
1346+
1347+ MenuBar* q_ptr;
1348+ QPlatformMenuBar* m_platformBar;
1349+ QVector<Menu*> m_menus;
1350+ QHash<QObject*, PlatformMenuWrapper*> m_platformMenus;
1351+};
1352+
1353+class PlatformMenuWrapper : public QObject
1354+{
1355+ Q_OBJECT
1356+public:
1357+ PlatformMenuWrapper(Menu *target, MenuBar *bar);
1358+ ~PlatformMenuWrapper();
1359+
1360+ void insert(QPlatformMenu *before);
1361+ void remove();
1362+
1363+public Q_SLOTS:
1364+ void updateVisible();
1365+ void updateEnabled();
1366+ void updateText();
1367+ void updateIcon();
1368+
1369+private:
1370+ void syncPlatformMenu();
1371+
1372+ QPointer<MenuBar> m_bar;
1373+ QPointer<Menu> m_target;
1374+ bool m_inserted;
1375+};
1376+
1377+UT_NAMESPACE_END
1378+
1379+#endif // MENUBAR_P_P_H
1380+
1381
1382=== added file 'src/Ubuntu/UbuntuToolkit/menugroup.cpp'
1383--- src/Ubuntu/UbuntuToolkit/menugroup.cpp 1970-01-01 00:00:00 +0000
1384+++ src/Ubuntu/UbuntuToolkit/menugroup.cpp 2016-08-22 16:02:17 +0000
1385@@ -0,0 +1,164 @@
1386+/*
1387+ * Copyright 2016 Canonical Ltd.
1388+ *
1389+ * This program is free software; you can redistribute it and/or modify
1390+ * it under the terms of the GNU Lesser General Public License as published by
1391+ * the Free Software Foundation; version 3.
1392+ *
1393+ * This program is distributed in the hope that it will be useful,
1394+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1395+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1396+ * GNU Lesser General Public License for more details.
1397+ *
1398+ * You should have received a copy of the GNU Lesser General Public License
1399+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1400+ */
1401+
1402+#include "menugroup_p.h"
1403+#include "actionlist_p.h"
1404+#include "ucaction_p.h"
1405+
1406+UT_NAMESPACE_BEGIN
1407+
1408+/*!
1409+ * \qmltype MenuGroup
1410+ * \inqmlmodule Ubuntu.Components
1411+ * \ingroup ubuntu
1412+ * \brief Logical list of items for a menu.
1413+ *
1414+ * Example usage:
1415+ * \qml
1416+ * import QtQuick 2.4
1417+ * import Ubuntu.Components 1.3
1418+ * Menu {
1419+ * text: "Edit"
1420+ *
1421+ * MenuGroup {
1422+ * Action { text: "Undo" }
1423+ * Action { text: "Redo" }
1424+ * }
1425+ *
1426+ * MenuGroup {
1427+ * Action { text: "Cut" }
1428+ * ActionList {
1429+ * Action { text: "Copy" }
1430+ * Action { text: "Paste" }
1431+ * }
1432+ * }
1433+ *
1434+ * MenuGroup {
1435+ * Action { text: "Select All" }
1436+ * }
1437+ * }
1438+ * \endqml
1439+ */
1440+/*!
1441+ * \qmlsignal MenuGroup::added(Object action)
1442+ * Signal called when a action is added to the list
1443+ */
1444+
1445+/*!
1446+ * \qmlsignal MenuGroup::removed(Object action)
1447+ * Signal called when a action is removed from the list
1448+ */
1449+/*!
1450+ * \qmlsignal MenuGroup::changed()
1451+ * Signal called when the contents of the group change,
1452+ * including child content changes (eg. \l ActionList child add/remove)
1453+ */
1454+MenuGroup::MenuGroup(QObject *parent)
1455+ : QObject(parent)
1456+{
1457+}
1458+
1459+/*!
1460+ * \qmlmethod MenuGroup::addObject(Object object)
1461+ * Adds an Object to the list programatically.
1462+ */
1463+void MenuGroup::addObject(QObject *object)
1464+{
1465+ if (m_data.contains(object)) {
1466+ return;
1467+ }
1468+ m_data.push_back(object);
1469+
1470+ if (auto childGroup = qobject_cast<MenuGroup*>(object)) {
1471+ connect(childGroup, &MenuGroup::changed, this, &MenuGroup::changed);
1472+ } else if (auto actionList = qobject_cast<ActionList*>(object)) {
1473+ connect(actionList, &ActionList::added, this, &MenuGroup::changed);
1474+ connect(actionList, &ActionList::removed, this, &MenuGroup::changed);
1475+ }
1476+
1477+ Q_EMIT added(object);
1478+ Q_EMIT changed();
1479+}
1480+
1481+/*!
1482+ * \qmlmethod MenuGroup::removeObject(Object object)
1483+ * Removes an object from the list programatically.
1484+ */
1485+void MenuGroup::removeObject(QObject *object)
1486+{
1487+ if (!object) {
1488+ return;
1489+ }
1490+ if (m_data.removeOne(object)) {
1491+ disconnect(object, 0, this, 0);
1492+ Q_EMIT removed(object);
1493+ Q_EMIT changed();
1494+ }
1495+}
1496+
1497+/*!
1498+
1499+ * \qmlproperty list<Object> MenuGroup::data
1500+ * \default
1501+ * List of Objects in this MenuGroup
1502+ * Note that when you set this property, the children of the MenuGroup will be ignored,
1503+ * so do not set the list and define children.
1504+ */
1505+QQmlListProperty<QObject> MenuGroup::data()
1506+{
1507+ return QQmlListProperty<QObject>(this, 0, MenuGroup::append, MenuGroup::count, MenuGroup::at, MenuGroup::clear);
1508+}
1509+
1510+const QVector<QObject*> &MenuGroup::list() const
1511+{
1512+ return m_data;
1513+}
1514+
1515+void MenuGroup::append(QQmlListProperty<QObject> *list, QObject *object)
1516+{
1517+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
1518+ if (menuGroup) {
1519+ menuGroup->addObject(object);
1520+ }
1521+}
1522+
1523+int MenuGroup::count(QQmlListProperty<QObject> *list)
1524+{
1525+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
1526+ if (menuGroup) {
1527+ return menuGroup->m_data.count();
1528+ }
1529+ return 0;
1530+}
1531+
1532+QObject* MenuGroup::at(QQmlListProperty<QObject> *list, int index)
1533+{
1534+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
1535+ if (menuGroup && index >=0 && menuGroup->m_data.count() > index) {
1536+ return menuGroup->m_data[index];
1537+ }
1538+ return Q_NULLPTR;
1539+}
1540+
1541+void MenuGroup::clear(QQmlListProperty<QObject> *list)
1542+{
1543+ MenuGroup *menuGroup = qobject_cast<MenuGroup*>(list->object);
1544+ if (menuGroup) {
1545+ menuGroup->m_data.clear();
1546+ }
1547+}
1548+
1549+UT_NAMESPACE_END
1550
1551=== added file 'src/Ubuntu/UbuntuToolkit/menugroup_p.h'
1552--- src/Ubuntu/UbuntuToolkit/menugroup_p.h 1970-01-01 00:00:00 +0000
1553+++ src/Ubuntu/UbuntuToolkit/menugroup_p.h 2016-08-22 16:02:17 +0000
1554@@ -0,0 +1,60 @@
1555+/*
1556+ * Copyright 2016 Canonical Ltd.
1557+ *
1558+ * This program is free software; you can redistribute it and/or modify
1559+ * it under the terms of the GNU Lesser General Public License as published by
1560+ * the Free Software Foundation; version 3.
1561+ *
1562+ * This program is distributed in the hope that it will be useful,
1563+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1564+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1565+ * GNU Lesser General Public License for more details.
1566+ *
1567+ * You should have received a copy of the GNU Lesser General Public License
1568+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1569+ */
1570+
1571+#ifndef MENUGROUP_P_H
1572+#define MENUGROUP_P_H
1573+
1574+#include <ubuntutoolkitglobal.h>
1575+
1576+#include <QObject>
1577+#include <QVector>
1578+#include <QQmlListProperty>
1579+
1580+UT_NAMESPACE_BEGIN
1581+
1582+class MenuGroup : public QObject
1583+{
1584+ Q_OBJECT
1585+ Q_PROPERTY(QQmlListProperty<QObject> data READ data)
1586+ Q_CLASSINFO("DefaultProperty", "data")
1587+public:
1588+ explicit MenuGroup(QObject *parent = 0);
1589+
1590+ QQmlListProperty<QObject> data();
1591+
1592+ const QVector<QObject*> &list() const;
1593+
1594+public Q_SLOTS:
1595+ void addObject(QObject *object);
1596+ void removeObject(QObject *object);
1597+
1598+Q_SIGNALS:
1599+ void added(QObject *object);
1600+ void removed(QObject *object);
1601+ void changed();
1602+
1603+protected:
1604+ QVector<QObject*> m_data;
1605+
1606+ static void append(QQmlListProperty<QObject> *list, QObject *action);
1607+ static int count(QQmlListProperty<QObject> *list);
1608+ static QObject* at(QQmlListProperty<QObject> *list, int);
1609+ static void clear(QQmlListProperty<QObject> *list);
1610+};
1611+
1612+UT_NAMESPACE_END
1613+
1614+#endif // MENUGROUP_P_H
1615
1616=== modified file 'src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp'
1617--- src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-22 16:02:17 +0000
1618+++ src/Ubuntu/UbuntuToolkit/ubuntutoolkitmodule.cpp 2016-08-22 16:02:17 +0000
1619@@ -91,6 +91,9 @@
1620 #include <privates/ucscrollbarutils_p.h>
1621 #include <actionlist_p.h>
1622 #include <exclusivegroup_p.h>
1623+#include <menu_p.h>
1624+#include <menubar_p.h>
1625+#include <menugroup_p.h>
1626
1627 // styles
1628 #include <ucbottomedgestyle_p.h>
1629@@ -414,9 +417,9 @@
1630
1631 void UbuntuLabsModule::defineModule(const char *uri)
1632 {
1633- Q_UNUSED(uri);
1634- // a fake component so we can have the module types file created
1635- qmlRegisterType<QObject>(uri, 1, 0, "ZiObject");
1636+ qmlRegisterType<Menu>(uri, 1, 0, "Menu");
1637+ qmlRegisterType<MenuBar>(uri, 1, 0, "MenuBar");
1638+ qmlRegisterType<MenuGroup>(uri, 1, 0, "MenuGroup");
1639 }
1640
1641 void UbuntuLabsModule::undefineModule()
1642
1643=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction.cpp'
1644--- src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-08-22 16:02:17 +0000
1645+++ src/Ubuntu/UbuntuToolkit/ucaction.cpp 2016-08-22 16:02:17 +0000
1646@@ -271,43 +271,6 @@
1647 * \endqml
1648 */
1649
1650-/*!
1651- * \qmlproperty enum Action::parameterType
1652- * Type of the parameter passed to \l trigger and \l triggered.
1653- * Type is an enumeration:
1654- * \list
1655- * \li \b Action.None: No paramater. (default)
1656- * \li \b Action.String: String parameter.
1657- * \li \b Action.Integer: Integer parameter.
1658- * \li \b Action.Bool: Boolean parameter.
1659- * \li \b Action.Real: Single precision floating point parameter.
1660- * \li \b Action.Object: The parameter is an object.
1661- * \endlist
1662- * \qml
1663- * Action {
1664- * id: action
1665- * parameterType: Action.String
1666- * onTriggered: {
1667- * // value arguments now contain strings
1668- * console.log(value);
1669- * }
1670- * Component.onCompleted: action.trigger("Hello World")
1671- * }
1672- * \endqml
1673- */
1674-
1675-/*!
1676- * \qmlproperty bool Action::enabled
1677- * If set to false the action can not be triggered. Components visualizing the
1678- * action migth either hide the action or make it insensitive. However visibility
1679- * can be controlled separately using the \l visible property.
1680- */
1681-
1682-/*!
1683- * \qmlproperty bool Action::visible
1684- * Specifies whether the action is visible to the user. Defaults to true.
1685- */
1686-
1687 UCAction::UCAction(QObject *parent)
1688 : QObject(parent)
1689 , m_exclusiveGroup(Q_NULLPTR)
1690@@ -461,6 +424,68 @@
1691 }
1692
1693 /*!
1694+ * \qmlproperty bool Action::visible
1695+ * Specifies whether the action is visible to the user. Defaults to true.
1696+ */
1697+void UCAction::setVisible(bool visible)
1698+{
1699+ if (m_visible == visible) {
1700+ return;
1701+ }
1702+ m_visible = visible;
1703+ Q_EMIT visibleChanged();
1704+}
1705+
1706+/*!
1707+ * \qmlproperty bool Action::enabled
1708+ * If set to false the action can not be triggered. Components visualizing the
1709+ * action migth either hide the action or make it insensitive. However visibility
1710+ * can be controlled separately using the \l visible property.
1711+ */
1712+void UCAction::setEnabled(bool enabled)
1713+{
1714+ if (m_enabled == enabled) {
1715+ return;
1716+ }
1717+ m_enabled = enabled;
1718+ Q_EMIT enabledChanged();
1719+
1720+}
1721+
1722+/*!
1723+ * \qmlproperty enum Action::parameterType
1724+ * Type of the parameter passed to \l trigger and \l triggered.
1725+ * Type is an enumeration:
1726+ * \list
1727+ * \li \b Action.None: No paramater. (default)
1728+ * \li \b Action.String: String parameter.
1729+ * \li \b Action.Integer: Integer parameter.
1730+ * \li \b Action.Bool: Boolean parameter.
1731+ * \li \b Action.Real: Single precision floating point parameter.
1732+ * \li \b Action.Object: The parameter is an object.
1733+ * \endlist
1734+ * \qml
1735+ * Action {
1736+ * id: action
1737+ * parameterType: Action.String
1738+ * onTriggered: {
1739+ * // value arguments now contain strings
1740+ * console.log(value);
1741+ * }
1742+ * Component.onCompleted: action.trigger("Hello World")
1743+ * }
1744+ * \endqml
1745+ */
1746+void UCAction::setParameterType(UCAction::Type type)
1747+{
1748+ if (m_parameterType == type) {
1749+ return;
1750+ }
1751+ m_parameterType = type;
1752+ Q_EMIT parameterTypeChanged();
1753+}
1754+
1755+/*!
1756 * \qmlproperty bool Action::checkable
1757 * \since Ubuntu.Components 1.3
1758 * Whether the action can be checked. Defaults to false.
1759
1760=== modified file 'src/Ubuntu/UbuntuToolkit/ucaction_p.h'
1761--- src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-08-22 16:02:17 +0000
1762+++ src/Ubuntu/UbuntuToolkit/ucaction_p.h 2016-08-22 16:02:17 +0000
1763@@ -62,23 +62,23 @@
1764 Q_ENUMS(Type)
1765 Q_PROPERTY(QString name MEMBER m_name WRITE setName NOTIFY nameChanged)
1766 Q_PROPERTY(QString text READ text WRITE setText RESET resetText NOTIFY textChanged)
1767- Q_PROPERTY(QString iconName MEMBER m_iconName WRITE setIconName NOTIFY iconNameChanged)
1768+ Q_PROPERTY(QString iconName READ iconName WRITE setIconName NOTIFY iconNameChanged)
1769 Q_PROPERTY(QString description MEMBER m_description NOTIFY descriptionChanged)
1770 Q_PROPERTY(QString keywords MEMBER m_keywords NOTIFY keywordsChanged)
1771- Q_PROPERTY(bool enabled MEMBER m_enabled NOTIFY enabledChanged)
1772- Q_PROPERTY(Type parameterType MEMBER m_parameterType NOTIFY parameterTypeChanged)
1773+ Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)
1774+ Q_PROPERTY(Type parameterType READ parameterType WRITE setParameterType NOTIFY parameterTypeChanged)
1775
1776 Q_PROPERTY(bool checkable READ isCheckable WRITE setCheckable NOTIFY checkableChanged REVISION 1)
1777 Q_PROPERTY(bool checked READ isChecked WRITE setChecked NOTIFY toggled REVISION 1)
1778 Q_PROPERTY(ExclusiveGroup* exclusiveGroup READ exclusiveGroup WRITE setExclusiveGroup NOTIFY exclusiveGroupChanged REVISION 1)
1779
1780 // Toolkit Actions API
1781- Q_PROPERTY(QUrl iconSource MEMBER m_iconSource WRITE setIconSource NOTIFY iconSourceChanged)
1782- Q_PROPERTY(bool visible MEMBER m_visible NOTIFY visibleChanged)
1783+ Q_PROPERTY(QUrl iconSource READ iconSource WRITE setIconSource NOTIFY iconSourceChanged)
1784+ Q_PROPERTY(bool visible READ visible WRITE setVisible NOTIFY visibleChanged)
1785 Q_PROPERTY(QQmlComponent *itemHint MEMBER m_itemHint WRITE setItemHint)
1786
1787 // QtQuickControls.Action
1788- Q_PROPERTY(QVariant shortcut MEMBER m_shortcut WRITE setShortcut RESET resetShortcut NOTIFY shortcutChanged REVISION 1)
1789+ Q_PROPERTY(QVariant shortcut READ shortcut WRITE setShortcut RESET resetShortcut NOTIFY shortcutChanged REVISION 1)
1790 public:
1791 enum Type {
1792 None,
1793@@ -112,11 +112,19 @@
1794 QString text();
1795 void setText(const QString &text);
1796 void resetText();
1797+ QString iconName() const { return m_iconName; }
1798 void setIconName(const QString &name);
1799+ QUrl iconSource() const { return m_iconSource; }
1800 void setIconSource(const QUrl &url);
1801 void setItemHint(QQmlComponent *);
1802+ QVariant shortcut() const { return m_shortcut; }
1803 void setShortcut(const QVariant&);
1804 void resetShortcut();
1805+ bool visible() const { return m_visible; }
1806+ void setVisible(bool visible);
1807+ void setEnabled(bool enabled);
1808+ void setParameterType(Type type);
1809+ Type parameterType() const { return m_parameterType; }
1810 void setCheckable(bool checkable);
1811 bool isCheckable() const { return m_checkable; }
1812 void setChecked(bool checked);
1813
1814=== added file 'tests/unit/components/tst_menu.qml'
1815--- tests/unit/components/tst_menu.qml 1970-01-01 00:00:00 +0000
1816+++ tests/unit/components/tst_menu.qml 2016-08-22 16:02:17 +0000
1817@@ -0,0 +1,136 @@
1818+/*
1819+ * Copyright 2016 Canonical Ltd.
1820+ *
1821+ * This program is free software; you can redistribute it and/or modify
1822+ * it under the terms of the GNU Lesser General Public License as published by
1823+ * the Free Software Foundation; version 3.
1824+ *
1825+ * This program is distributed in the hope that it will be useful,
1826+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1827+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1828+ * GNU Lesser General Public License for more details.
1829+ *
1830+ * You should have received a copy of the GNU Lesser General Public License
1831+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1832+ */
1833+
1834+import QtQuick 2.0
1835+import QtTest 1.0
1836+import Ubuntu.Components 1.3
1837+
1838+TestCase {
1839+ name: "MenuAPI"
1840+
1841+ TestUtil {
1842+ id: util
1843+ }
1844+
1845+ function test_menubar() {
1846+ compare(menuBar.menus.length, 2, "Incorrect number of menus in menubar")
1847+ compare(menuBar.menus[0], submenu1, "Incorrect element found at menu bar index")
1848+ compare(menuBar.menus[1], submenu2, "Incorrect element found at menu bar index")
1849+ }
1850+
1851+ function test_menu() {
1852+ compare(floatingMenu.data.length, 5, "Incorrect number of menu items in menu");
1853+ compare(floatingMenu.data[0], actionList, "Incorrect element found at menu index");
1854+ compare(floatingMenu.data[1], menuGroup, "Incorrect element found at menu index");
1855+ compare(floatingMenu.data[2], action1, "Incorrect element found at menu index");
1856+ compare(floatingMenu.data[3], action2, "Incorrect element found at menu index");
1857+ compare(floatingMenu.data[4], menu1, "Incorrect element found at menu index");
1858+ }
1859+
1860+ function test_menugroup() {
1861+ compare(group.data.length, 4, "Incorrect number of menu items in MenuGroup");
1862+ compare(group.data[0], groupAction, "Action not found at correct index in MenuGroup");
1863+ compare(group.data[1], groupList, "ActionList not found at correct index in MenuGroup");
1864+ compare(group.data[2], groupMenu, "Menu not found at correct index in MenuGroup");
1865+ compare(group.data[3], groupGroup, "MenuGroup not found at correct index in MenuGroup");
1866+ }
1867+
1868+ function test_dynamic_append() {
1869+ dynamicMenu.appendObject(floatingAction);
1870+ compare(dynamicMenu.data.length, 1, "Action not added to menu");
1871+
1872+ dynamicMenu.removeObject(floatingAction);
1873+ compare(dynamicMenu.data.length, 0, "Action not removed from menu");
1874+ }
1875+
1876+ function test_dynamic_action_list_append() {
1877+ dynamicMenu.appendObject(floatingList);
1878+ compare(dynamicMenu.data.length, 1, "ActionList not added to menu");
1879+
1880+ dynamicMenu.removeObject(floatingList);
1881+ compare(dynamicMenu.data.length, 0, "ActionList not removed from menu");
1882+ }
1883+
1884+ function test_dynamic_insert() {
1885+ dynamicMenu.insertObject(0, floatingAction);
1886+ dynamicMenu.insertObject(0, floatingList);
1887+ compare(dynamicMenu.data.length, 2, "ActionList not removed from menu");
1888+
1889+ compare(dynamicMenu.data[0], floatingList, "Object was not inserted at correct index");
1890+ compare(dynamicMenu.data[1], floatingAction, "Object was not inserted at correct index");
1891+
1892+ dynamicMenu.removeObject(floatingAction);
1893+ dynamicMenu.removeObject(floatingList);
1894+ compare(dynamicMenu.data.length, 0, "Menu should be empty");
1895+ }
1896+
1897+ MenuBar {
1898+ id: menuBar
1899+ Menu {
1900+ id: submenu1
1901+ }
1902+ Menu {
1903+ id: submenu2
1904+ }
1905+ }
1906+
1907+ Menu {
1908+ id: floatingMenu
1909+ ActionList {
1910+ id: actionList
1911+ }
1912+ MenuGroup {
1913+ id: menuGroup
1914+ }
1915+ Action {
1916+ id: action1
1917+ }
1918+ Action {
1919+ id: action2
1920+ }
1921+ Menu {
1922+ id: menu1
1923+ }
1924+ }
1925+
1926+ MenuGroup {
1927+ id: group
1928+ Action {
1929+ id: groupAction
1930+ }
1931+ ActionList {
1932+ id: groupList
1933+ }
1934+ Menu {
1935+ id: groupMenu
1936+ }
1937+ MenuGroup {
1938+ id: groupGroup
1939+ }
1940+ }
1941+
1942+ Menu {
1943+ id: dynamicMenu
1944+ }
1945+
1946+ Action {
1947+ id: floatingAction
1948+ }
1949+
1950+ ActionList {
1951+ id: floatingList
1952+ }
1953+}

Subscribers

People subscribed via source and target branches