Merge lp:~nick-dedekind/ubuntu-ui-toolkit/menus into lp:ubuntu-ui-toolkit/staging
- menus
- Merge into staging
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 |
Related bugs: |
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
Description of the change
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.
Zsombor Egri (zsombi) wrote : | # |
All clear! Thanks for this too!
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
None: https:/
SUCCESS: https:/
SUCCESS: https:/
None: https:/
SUCCESS: https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
ubuntu-sdk-build-bot (ubuntu-sdk-build-bot) wrote : | # |
FAILED: Continuous integration, rev:1357
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Zoltan Balogh (bzoltan) wrote : | # |
I have manualy merged this branch to rev2077
Tim Peeters (tpeeters) wrote : | # |
I reverted this in r2080 because it breaks compilation.
Unmerged revisions
- 1359. By Nick Dedekind
-
fixed broken build
Preview Diff
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 | +} |
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.