1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39
40 #include "qwindowsmenu.h"
41 #include "qwindowscontext.h"
42 #include "qwindowswindow.h"
43
44 #include <QtGui/qwindow.h>
45 #include <QtCore/qdebug.h>
46 #include <QtCore/qvariant.h>
47 #include <QtCore/qmetaobject.h>
48 #include <QtCore/qpointer.h>
49
50 #include <algorithm>
51
52 QT_BEGIN_NAMESPACE
53
54 /*!
55 \class QWindowsMenuBar
56 \brief Windows native menu bar
57
58 \list
59 \li \l{https://msdn.microsoft.com/de-de/library/windows/desktop/ms647553(v=vs.85).aspx#_win32_Menu_Creation_Functions},
60 \e{About Menus}
61 \endlist
62
63 \note The destruction order of the QWindowsMenu/Item/Bar instances is
64 arbitrary depending on whether the application is Qt Quick or
65 Qt Widgets, either the containers or the items might be deleted first.
66
67 \internal
68 */
69
70 static uint nextId = 1;
71
72 // Find a QPlatformMenu[Item]* in a vector of QWindowsMenu[Item], where
73 // QVector::indexOf() cannot be used since it wants a QWindowsMenu[Item]*
74 template <class Derived, class Needle>
indexOf(const QVector<Derived * > & v,const Needle * needle)75 static int indexOf(const QVector<Derived *> &v, const Needle *needle)
76 {
77 for (int i = 0, size = v.size(); i < size; ++i) {
78 if (v.at(i) == needle)
79 return i;
80 }
81 return -1;
82 }
83
84 // Helper for inserting a QPlatformMenu[Item]* into a vector of QWindowsMenu[Item].
85 template <class Derived, class Base>
insertBefore(QVector<Derived * > * v,Base * newItemIn,const Base * before=nullptr)86 static int insertBefore(QVector<Derived *> *v, Base *newItemIn, const Base *before = nullptr)
87 {
88 int index = before ? indexOf(*v, before) : -1;
89 if (index != -1) {
90 v->insert(index, static_cast<Derived *>(newItemIn));
91 } else {
92 index = v->size();
93 v->append(static_cast<Derived *>(newItemIn));
94 }
95 return index;
96 }
97
qStringToWChar(const QString & s)98 static inline const wchar_t *qStringToWChar(const QString &s)
99 {
100 return reinterpret_cast<const wchar_t *>(s.utf16());
101 }
102
103 // Traverse menu and return the item for which predicate
104 // "bool Function(QWindowsMenuItem *)" returns true
105 template <class Predicate>
traverseMenuItems(const QWindowsMenu * menu,Predicate p)106 static QWindowsMenuItem *traverseMenuItems(const QWindowsMenu *menu, Predicate p)
107 {
108 const QWindowsMenu::MenuItems &items = menu->menuItems();
109 for (QWindowsMenuItem *item : items) {
110 if (p(item))
111 return item;
112 if (item->subMenu()) {
113 if (QWindowsMenuItem *subMenuItem = traverseMenuItems(item->subMenu(), p))
114 return subMenuItem;
115 }
116 }
117 return nullptr;
118 }
119
120 // Traverse menu bar return the item for which predicate
121 // "bool Function(QWindowsMenuItem *)" returns true
122 template <class Predicate>
traverseMenuItems(const QWindowsMenuBar * menuBar,Predicate p)123 static QWindowsMenuItem *traverseMenuItems(const QWindowsMenuBar *menuBar, Predicate p)
124 {
125 const QWindowsMenuBar::Menus &menus = menuBar->menus();
126 for (QWindowsMenu *menu : menus) {
127 if (QWindowsMenuItem *item = traverseMenuItems(menu, p))
128 return item;
129 }
130 return nullptr;
131 }
132
133 template <class Menu /* Menu[Bar] */>
findMenuItemById(const Menu * menu,uint id)134 static QWindowsMenuItem *findMenuItemById(const Menu *menu, uint id)
135 {
136 return traverseMenuItems(menu, [id] (const QWindowsMenuItem *i) { return i->id() == id; });
137 }
138
139 // Traverse menu and return the menu for which predicate
140 // "bool Function(QWindowsMenu *)" returns true
141 template <class Predicate>
traverseMenus(const QWindowsMenu * menu,Predicate p)142 static QWindowsMenu *traverseMenus(const QWindowsMenu *menu, Predicate p)
143 {
144 const QWindowsMenu::MenuItems &items = menu->menuItems();
145 for (QWindowsMenuItem *item : items) {
146 if (QWindowsMenu *subMenu = item->subMenu()) {
147 if (p(subMenu))
148 return subMenu;
149 if (QWindowsMenu *menu = traverseMenus(subMenu, p))
150 return menu;
151 }
152 }
153 return nullptr;
154 }
155
156 // Traverse menu bar return the item for which
157 // function "bool Function(QWindowsMenu *)" returns true
158 template <class Predicate>
traverseMenus(const QWindowsMenuBar * menuBar,Predicate p)159 static QWindowsMenu *traverseMenus(const QWindowsMenuBar *menuBar, Predicate p)
160 {
161 const QWindowsMenuBar::Menus &menus = menuBar->menus();
162 for (QWindowsMenu *menu : menus) {
163 if (p(menu))
164 return menu;
165 if (QWindowsMenu *subMenu = traverseMenus(menu, p))
166 return subMenu;
167 }
168 return nullptr;
169 }
170
171 template <class Menu /* Menu[Bar] */>
findMenuByHandle(const Menu * menu,HMENU hMenu)172 static QWindowsMenu *findMenuByHandle(const Menu *menu, HMENU hMenu)
173 {
174 return traverseMenus(menu, [hMenu] (const QWindowsMenu *i) { return i->menuHandle() == hMenu; });
175 }
176
177 template <class MenuType>
findNextVisibleEntry(const QVector<MenuType * > & entries,int pos)178 static int findNextVisibleEntry(const QVector<MenuType *> &entries, int pos)
179 {
180 for (int i = pos, size = entries.size(); i < size; ++i) {
181 if (entries.at(i)->isVisible())
182 return i;
183 }
184 return -1;
185 }
186
menuItemInfoInit(MENUITEMINFO & menuItemInfo)187 static inline void menuItemInfoInit(MENUITEMINFO &menuItemInfo)
188 {
189 memset(&menuItemInfo, 0, sizeof(MENUITEMINFO));
190 menuItemInfo.cbSize = sizeof(MENUITEMINFO);
191 }
192
menuItemInfoSetText(MENUITEMINFO & menuItemInfo,const QString & text)193 static inline void menuItemInfoSetText(MENUITEMINFO &menuItemInfo, const QString &text)
194 {
195 menuItemInfoInit(menuItemInfo);
196 menuItemInfo.fMask = MIIM_STRING;
197 menuItemInfo.dwTypeData = const_cast<wchar_t *>(qStringToWChar(text));
198 menuItemInfo.cch = UINT(text.size());
199 }
200
menuItemState(HMENU hMenu,UINT uItem,BOOL fByPosition)201 static UINT menuItemState(HMENU hMenu, UINT uItem, BOOL fByPosition)
202 {
203 MENUITEMINFO menuItemInfo;
204 menuItemInfoInit(menuItemInfo);
205 menuItemInfo.fMask = MIIM_STATE;
206 return GetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo) == TRUE ? menuItemInfo.fState : 0;
207 }
208
menuItemSetState(HMENU hMenu,UINT uItem,BOOL fByPosition,UINT flags)209 static void menuItemSetState(HMENU hMenu, UINT uItem, BOOL fByPosition, UINT flags)
210 {
211 MENUITEMINFO menuItemInfo;
212 menuItemInfoInit(menuItemInfo);
213 menuItemInfo.fMask = MIIM_STATE;
214 menuItemInfo.fState = flags;
215 SetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo);
216 }
217
menuItemSetChangeState(HMENU hMenu,UINT uItem,BOOL fByPosition,bool value,UINT trueState,UINT falseState)218 static void menuItemSetChangeState(HMENU hMenu, UINT uItem, BOOL fByPosition,
219 bool value, UINT trueState, UINT falseState)
220 {
221 const UINT oldState = menuItemState(hMenu, uItem, fByPosition);
222 UINT newState = oldState;
223 if (value) {
224 newState |= trueState;
225 newState &= ~falseState;
226 } else {
227 newState &= ~trueState;
228 newState |= falseState;
229 }
230 if (oldState != newState)
231 menuItemSetState(hMenu, uItem, fByPosition, newState);
232 }
233
234 // ------------ QWindowsMenuItem
QWindowsMenuItem(QWindowsMenu * parentMenu)235 QWindowsMenuItem::QWindowsMenuItem(QWindowsMenu *parentMenu)
236 : m_parentMenu(parentMenu)
237 , m_id(0)
238 {
239 qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this)
240 << "parentMenu=" << parentMenu;
241 }
242
~QWindowsMenuItem()243 QWindowsMenuItem::~QWindowsMenuItem()
244 {
245 qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this);
246 removeFromMenu();
247 freeBitmap();
248 }
249
freeBitmap()250 void QWindowsMenuItem::freeBitmap()
251 {
252 if (m_hbitmap) {
253 DeleteObject(m_hbitmap);
254 m_hbitmap = nullptr;
255 }
256 }
257
setIcon(const QIcon & icon)258 void QWindowsMenuItem::setIcon(const QIcon &icon)
259 {
260 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this;
261 if (m_icon.cacheKey() == icon.cacheKey())
262 return;
263 m_icon = icon;
264 if (m_parentMenu != nullptr)
265 updateBitmap();
266 }
267
268 Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0);
269
updateBitmap()270 void QWindowsMenuItem::updateBitmap()
271 {
272 freeBitmap();
273 if (!m_icon.isNull()) {
274 const int size = m_iconSize ? m_iconSize : GetSystemMetrics(SM_CYMENUCHECK);
275 m_hbitmap = qt_pixmapToWinHBITMAP(m_icon.pixmap(QSize(size, size)), 1);
276 }
277 MENUITEMINFO itemInfo;
278 menuItemInfoInit(itemInfo);
279 itemInfo.fMask = MIIM_BITMAP;
280 itemInfo.hbmpItem = m_hbitmap;
281 SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &itemInfo);
282 }
283
setText(const QString & text)284 void QWindowsMenuItem::setText(const QString &text)
285 {
286 qCDebug(lcQpaMenus).nospace().noquote()
287 << __FUNCTION__ << "(\"" << text << "\") " << this;
288 if (m_text == text)
289 return;
290 m_text = text;
291 if (m_parentMenu != nullptr)
292 updateText();
293 }
294
updateText()295 void QWindowsMenuItem::updateText()
296 {
297 MENUITEMINFO menuItemInfo;
298 const QString &text = nativeText();
299 menuItemInfoSetText(menuItemInfo, text);
300 SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &menuItemInfo);
301 }
302
setMenu(QPlatformMenu * menuIn)303 void QWindowsMenuItem::setMenu(QPlatformMenu *menuIn)
304 {
305 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuIn << ')' << this;
306 if (menuIn == m_subMenu)
307 return;
308 const uint oldId = m_id;
309 if (menuIn != nullptr) { // Set submenu
310 m_subMenu = static_cast<QWindowsMenu *>(menuIn);
311 m_subMenu->setAsItemSubMenu(this);
312 m_id = m_subMenu->id();
313 if (m_parentMenu != nullptr) {
314 ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND | MF_POPUP,
315 m_id, qStringToWChar(m_text));
316 }
317 return;
318 }
319 // Clear submenu
320 m_subMenu = nullptr;
321 if (m_parentMenu != nullptr) {
322 m_id = nextId++;
323 ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND,
324 m_id, qStringToWChar(m_text));
325 } else {
326 m_id = 0;
327 }
328 }
329
setVisible(bool isVisible)330 void QWindowsMenuItem::setVisible(bool isVisible)
331 {
332 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isVisible << ')' << this;
333 if (m_visible == isVisible)
334 return;
335 m_visible = isVisible;
336 if (m_parentMenu == nullptr)
337 return;
338 // Windows menu items do not implement settable visibility, we need to work
339 // around by removing the item from the menu. It will be kept in the list.
340 if (isVisible)
341 insertIntoMenuHelper(m_parentMenu, false, m_parentMenu->menuItems().indexOf(this));
342 else
343 RemoveMenu(parentMenuHandle(), m_id, MF_BYCOMMAND);
344 }
345
setIsSeparator(bool isSeparator)346 void QWindowsMenuItem::setIsSeparator(bool isSeparator)
347 {
348 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isSeparator << ')' << this;
349 if (m_separator == isSeparator)
350 return;
351 m_separator = isSeparator;
352 if (m_parentMenu == nullptr)
353 return;
354 MENUITEMINFO menuItemInfo;
355 menuItemInfoInit(menuItemInfo);
356 menuItemInfo.fMask = MIIM_FTYPE;
357 menuItemInfo.fType = isSeparator ? MFT_SEPARATOR : MFT_STRING;
358 SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &menuItemInfo);
359 }
360
setCheckable(bool checkable)361 void QWindowsMenuItem::setCheckable(bool checkable)
362 {
363 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << checkable << ')' << this;
364 if (m_checkable == checkable)
365 return;
366 m_checkable = checkable;
367 if (m_parentMenu == nullptr)
368 return;
369 UINT state = menuItemState(parentMenuHandle(), m_id, FALSE);
370 if (m_checkable)
371 state |= m_checked ? MF_CHECKED : MF_UNCHECKED;
372 else
373 state &= ~(MF_CHECKED | MF_UNCHECKED);
374 menuItemSetState(parentMenuHandle(), m_id, FALSE, state);
375 }
376
setChecked(bool isChecked)377 void QWindowsMenuItem::setChecked(bool isChecked)
378 {
379 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isChecked << ')' << this;
380 if (m_checked == isChecked)
381 return;
382 m_checked = isChecked;
383 // Convenience: Allow to set checkable by calling setChecked(true) for
384 // Quick Controls 1
385 if (isChecked)
386 m_checkable = true;
387 if (m_parentMenu == nullptr || !m_checkable)
388 return;
389 menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_checked, MF_CHECKED, MF_UNCHECKED);
390 }
391
392 #if QT_CONFIG(shortcut)
setShortcut(const QKeySequence & shortcut)393 void QWindowsMenuItem::setShortcut(const QKeySequence &shortcut)
394 {
395 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << shortcut << ')' << this;
396 if (m_shortcut == shortcut)
397 return;
398 m_shortcut = shortcut;
399 if (m_parentMenu != nullptr)
400 updateText();
401 }
402 #endif
403
setEnabled(bool enabled)404 void QWindowsMenuItem::setEnabled(bool enabled)
405 {
406 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this;
407 if (m_enabled == enabled)
408 return;
409 m_enabled = enabled;
410 if (m_parentMenu != nullptr)
411 menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_enabled, MF_ENABLED, MF_GRAYED);
412 }
413
setIconSize(int size)414 void QWindowsMenuItem::setIconSize(int size)
415 {
416 if (m_iconSize == size)
417 return;
418 m_iconSize = size;
419 if (m_parentMenu != nullptr)
420 updateBitmap();
421 }
422
parentMenuHandle() const423 HMENU QWindowsMenuItem::parentMenuHandle() const
424 {
425 return m_parentMenu ? m_parentMenu->menuHandle() : nullptr;
426 }
427
state() const428 UINT QWindowsMenuItem::state() const
429 {
430 if (m_separator)
431 return MF_SEPARATOR;
432 UINT result = MF_STRING | (m_enabled ? MF_ENABLED : MF_GRAYED);
433 if (m_subMenu != nullptr)
434 result |= MF_POPUP;
435 if (m_checkable)
436 result |= m_checked ? MF_CHECKED : MF_UNCHECKED;
437 if (QGuiApplication::layoutDirection() == Qt::RightToLeft)
438 result |= MFT_RIGHTORDER;
439 return result;
440 }
441
nativeText() const442 QString QWindowsMenuItem::nativeText() const
443 {
444 QString result = m_text;
445 #if QT_CONFIG(shortcut)
446 if (!m_shortcut.isEmpty()) {
447 result += u'\t';
448 result += m_shortcut.toString(QKeySequence::NativeText);
449 }
450 #endif
451 return result;
452 }
453
insertIntoMenu(QWindowsMenu * menu,bool append,int index)454 void QWindowsMenuItem::insertIntoMenu(QWindowsMenu *menu, bool append, int index)
455 {
456 if (m_id == 0 && m_subMenu == nullptr)
457 m_id = nextId++;
458 insertIntoMenuHelper(menu, append, index);
459 m_parentMenu = menu;
460 }
461
insertIntoMenuHelper(QWindowsMenu * menu,bool append,int index)462 void QWindowsMenuItem::insertIntoMenuHelper(QWindowsMenu *menu, bool append, int index)
463 {
464 const QString &text = nativeText();
465
466 UINT_PTR idBefore = 0;
467 if (!append) {
468 // Skip over self (either newly inserted or when called from setVisible()
469 const int nextIndex = findNextVisibleEntry(menu->menuItems(), index + 1);
470 if (nextIndex != -1)
471 idBefore = menu->menuItems().at(nextIndex)->id();
472 }
473
474 if (idBefore)
475 InsertMenu(menu->menuHandle(), idBefore, state(), m_id, qStringToWChar(text));
476 else
477 AppendMenu(menu->menuHandle(), state(), m_id, qStringToWChar(text));
478
479 updateBitmap();
480 }
481
removeFromMenu()482 bool QWindowsMenuItem::removeFromMenu()
483 {
484 if (QWindowsMenu *parentMenu = m_parentMenu) {
485 m_parentMenu = nullptr;
486 RemoveMenu(parentMenu->menuHandle(), m_id, MF_BYCOMMAND);
487 parentMenu->notifyRemoved(this);
488 return true;
489 }
490 return false;
491 }
492
493 // ------------ QWindowsMenu
494
QWindowsMenu()495 QWindowsMenu::QWindowsMenu() : QWindowsMenu(nullptr, CreateMenu())
496 {
497 }
498
QWindowsMenu(QWindowsMenu * parentMenu,HMENU menu)499 QWindowsMenu::QWindowsMenu(QWindowsMenu *parentMenu, HMENU menu)
500 : m_parentMenu(parentMenu)
501 , m_hMenu(menu)
502 {
503 qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this)
504 << "parentMenu=" << parentMenu << "HMENU=" << m_hMenu;
505 }
506
~QWindowsMenu()507 QWindowsMenu::~QWindowsMenu()
508 {
509 qCDebug(lcQpaMenus).noquote().nospace() << __FUNCTION__
510 << " \"" <<m_text << "\", " << static_cast<const void *>(this);
511 for (int i = m_menuItems.size() - 1; i>= 0; --i)
512 m_menuItems.at(i)->removeFromMenu();
513 removeFromParent();
514 DestroyMenu(m_hMenu);
515 }
516
insertMenuItem(QPlatformMenuItem * menuItemIn,QPlatformMenuItem * before)517 void QWindowsMenu::insertMenuItem(QPlatformMenuItem *menuItemIn, QPlatformMenuItem *before)
518 {
519 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ", before=" << before << ')' << this;
520 auto *menuItem = static_cast<QWindowsMenuItem *>(menuItemIn);
521 const int index = insertBefore(&m_menuItems, menuItemIn, before);
522 const bool append = index == m_menuItems.size() - 1;
523 menuItem->insertIntoMenu(this, append, index);
524 }
525
removeMenuItem(QPlatformMenuItem * menuItemIn)526 void QWindowsMenu::removeMenuItem(QPlatformMenuItem *menuItemIn)
527 {
528 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ')' << this;
529 static_cast<QWindowsMenuItem *>(menuItemIn)->removeFromMenu();
530 }
531
setText(const QString & text)532 void QWindowsMenu::setText(const QString &text)
533 {
534 qCDebug(lcQpaMenus).nospace().noquote()
535 << __FUNCTION__ << "(\"" << text << "\") " << this;
536 if (m_text == text)
537 return;
538 m_text = text;
539 if (!m_visible)
540 return;
541 const HMENU ph = parentHandle();
542 if (ph == nullptr)
543 return;
544 MENUITEMINFO menuItemInfo;
545 menuItemInfoSetText(menuItemInfo, m_text);
546 SetMenuItemInfo(ph, id(), FALSE, &menuItemInfo);
547 }
548
setIcon(const QIcon & icon)549 void QWindowsMenu::setIcon(const QIcon &icon)
550 {
551 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this;
552 m_icon = icon;
553 }
554
setEnabled(bool enabled)555 void QWindowsMenu::setEnabled(bool enabled)
556 {
557 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this;
558 if (m_enabled == enabled)
559 return;
560 m_enabled = enabled;
561 if (!m_visible)
562 return;
563 if (const HMENU ph = parentHandle())
564 menuItemSetChangeState(ph, id(), FALSE, m_enabled, MF_ENABLED, MF_GRAYED);
565 }
566
itemForSubMenu(const QWindowsMenu * subMenu) const567 QWindowsMenuItem *QWindowsMenu::itemForSubMenu(const QWindowsMenu *subMenu) const
568 {
569 const auto it = std::find_if(m_menuItems.cbegin(), m_menuItems.cend(),
570 [subMenu] (const QWindowsMenuItem *i) { return i->subMenu() == subMenu; });
571 return it != m_menuItems.cend() ? *it : nullptr;
572 }
573
insertIntoMenuBar(QWindowsMenuBar * bar,bool append,int index)574 void QWindowsMenu::insertIntoMenuBar(QWindowsMenuBar *bar, bool append, int index)
575 {
576 UINT_PTR idBefore = 0;
577 if (!append) {
578 // Skip over self (either newly inserted or when called from setVisible()
579 const int nextIndex = findNextVisibleEntry(bar->menus(), index + 1);
580 if (nextIndex != -1)
581 idBefore = bar->menus().at(nextIndex)->id();
582 }
583 m_parentMenuBar = bar;
584 m_parentMenu = nullptr;
585 if (idBefore)
586 InsertMenu(bar->menuBarHandle(), idBefore, MF_POPUP | MF_BYCOMMAND, id(), qStringToWChar(m_text));
587 else
588 AppendMenu(bar->menuBarHandle(), MF_POPUP, id(), qStringToWChar(m_text));
589 }
590
removeFromParent()591 bool QWindowsMenu::removeFromParent()
592 {
593 if (QWindowsMenuBar *bar = m_parentMenuBar) {
594 m_parentMenuBar = nullptr;
595 bar->notifyRemoved(this);
596 return RemoveMenu(bar->menuBarHandle(), id(), MF_BYCOMMAND) == TRUE;
597 }
598 if (QWindowsMenu *menu = m_parentMenu) {
599 m_parentMenu = nullptr;
600 QWindowsMenuItem *item = menu->itemForSubMenu(this);
601 if (item)
602 item->setMenu(nullptr);
603 return item != nullptr;
604 }
605 return false;
606 }
607
setVisible(bool visible)608 void QWindowsMenu::setVisible(bool visible)
609 {
610 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << visible << ')' << this;
611 if (m_visible == visible)
612 return;
613 m_visible = visible;
614 const HMENU ph = parentHandle();
615 if (ph == nullptr)
616 return;
617 // Windows menus do not implement settable visibility, we need to work
618 // around by removing the menu from the parent. It will be kept in the list.
619 if (visible) {
620 if (m_parentMenuBar)
621 insertIntoMenuBar(m_parentMenuBar, false, m_parentMenuBar->menus().indexOf(this));
622 } else {
623 RemoveMenu(ph, id(), MF_BYCOMMAND);
624 }
625 if (m_parentMenuBar)
626 m_parentMenuBar->redraw();
627 }
628
menuItemAt(int position) const629 QPlatformMenuItem *QWindowsMenu::menuItemAt(int position) const
630 {
631 qCDebug(lcQpaMenus) << __FUNCTION__ << position;
632 return position >= 0 && position < m_menuItems.size()
633 ? m_menuItems.at(position) : nullptr;
634 }
635
menuItemForTag(quintptr tag) const636 QPlatformMenuItem *QWindowsMenu::menuItemForTag(quintptr tag) const
637 {
638 return traverseMenuItems(this, [tag] (const QPlatformMenuItem *i) { return i->tag() == tag; });
639 }
640
createMenuItem() const641 QPlatformMenuItem *QWindowsMenu::createMenuItem() const
642 {
643 QPlatformMenuItem *result = new QWindowsMenuItem;
644 qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result;
645 return result;
646 }
647
createSubMenu() const648 QPlatformMenu *QWindowsMenu::createSubMenu() const
649 {
650 QPlatformMenu *result = new QWindowsMenu;
651 qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result;
652 return result;
653 }
654
setAsItemSubMenu(QWindowsMenuItem * item)655 void QWindowsMenu::setAsItemSubMenu(QWindowsMenuItem *item)
656 {
657 m_parentMenu = item->parentMenu();
658 }
659
parentMenuHandle() const660 HMENU QWindowsMenu::parentMenuHandle() const
661 {
662 return m_parentMenu ? m_parentMenu->menuHandle() : nullptr;
663 }
664
parentMenuBarHandle() const665 HMENU QWindowsMenu::parentMenuBarHandle() const
666 {
667 return m_parentMenuBar ? m_parentMenuBar->menuBarHandle() : nullptr;
668 }
669
parentHandle() const670 HMENU QWindowsMenu::parentHandle() const
671 {
672 if (m_parentMenuBar)
673 return m_parentMenuBar->menuBarHandle();
674 if (m_parentMenu)
675 return m_parentMenu->menuHandle();
676 return nullptr;
677 }
678
679 // --------------- QWindowsPopupMenu
680
681 static QPointer<QWindowsPopupMenu> lastShownPopupMenu;
682
QWindowsPopupMenu()683 QWindowsPopupMenu::QWindowsPopupMenu() : QWindowsMenu(nullptr, CreatePopupMenu())
684 {
685 }
686
showPopup(const QWindow * parentWindow,const QRect & targetRect,const QPlatformMenuItem * item)687 void QWindowsPopupMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
688 const QPlatformMenuItem *item)
689 {
690 qCDebug(lcQpaMenus) << __FUNCTION__ << '>' << this << parentWindow << targetRect << item;
691 const auto *window = static_cast<const QWindowsBaseWindow *>(parentWindow->handle());
692 const QPoint globalPos = window->mapToGlobal(targetRect.topLeft());
693 trackPopupMenu(window->handle(), globalPos.x(), globalPos.y());
694 }
695
trackPopupMenu(HWND windowHandle,int x,int y)696 bool QWindowsPopupMenu::trackPopupMenu(HWND windowHandle, int x, int y)
697 {
698 lastShownPopupMenu = this;
699 // Emulate Show()/Hide() signals. Could be implemented by catching the
700 // WM_EXITMENULOOP, WM_ENTERMENULOOP messages; but they do not carry
701 // information telling which menu was opened.
702 emit aboutToShow();
703 const bool result =
704 TrackPopupMenu(menuHandle(),
705 QGuiApplication::layoutDirection() == Qt::RightToLeft ? UINT(TPM_RIGHTALIGN) : UINT(0),
706 x, y, 0, windowHandle, nullptr) == TRUE;
707 emit aboutToHide();
708 return result;
709 }
710
notifyTriggered(uint id)711 bool QWindowsPopupMenu::notifyTriggered(uint id)
712 {
713 QPlatformMenuItem *result = lastShownPopupMenu.isNull()
714 ? nullptr
715 : findMenuItemById(lastShownPopupMenu.data(), id);
716 if (result != nullptr) {
717 qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id;
718 emit result->activated();
719 }
720 lastShownPopupMenu = nullptr;
721 return result != nullptr;
722 }
723
notifyAboutToShow(HMENU hmenu)724 bool QWindowsPopupMenu::notifyAboutToShow(HMENU hmenu)
725 {
726 if (lastShownPopupMenu.isNull())
727 return false;
728 if (lastShownPopupMenu->menuHandle() == hmenu) {
729 emit lastShownPopupMenu->aboutToShow();
730 return true;
731 }
732 if (QWindowsMenu *menu = findMenuByHandle(lastShownPopupMenu.data(), hmenu)) {
733 emit menu->aboutToShow();
734 return true;
735 }
736 return false;
737 }
738
739 // --------------- QWindowsMenuBar
740
QWindowsMenuBar()741 QWindowsMenuBar::QWindowsMenuBar() : m_hMenuBar(CreateMenu())
742 {
743 qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this);
744 }
745
~QWindowsMenuBar()746 QWindowsMenuBar::~QWindowsMenuBar()
747 {
748 qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this);
749 for (int m = m_menus.size() - 1; m >= 0; --m)
750 m_menus.at(m)->removeFromParent();
751 removeFromWindow();
752 DestroyMenu(m_hMenuBar);
753 }
754
insertMenu(QPlatformMenu * menuIn,QPlatformMenu * before)755 void QWindowsMenuBar::insertMenu(QPlatformMenu *menuIn, QPlatformMenu *before)
756 {
757 qCDebug(lcQpaMenus) << __FUNCTION__ << menuIn << "before=" << before;
758 auto *menu = static_cast<QWindowsMenu *>(menuIn);
759 const int index = insertBefore(&m_menus, menuIn, before);
760 menu->insertIntoMenuBar(this, index == m_menus.size() - 1, index);
761 }
762
removeMenu(QPlatformMenu * menu)763 void QWindowsMenuBar::removeMenu(QPlatformMenu *menu)
764 {
765 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menu << ')' << this;
766 const int index = indexOf(m_menus, menu);
767 if (index != -1)
768 m_menus[index]->removeFromParent();
769 }
770
771 // When calling handleReparent() for a QWindow instances that does not have
772 // a platform window yet, set the menubar as dynamic property to be installed
773 // on platform window creation.
774 static const char menuBarPropertyName[] = "_q_windowsNativeMenuBar";
775
handleReparent(QWindow * newParentWindow)776 void QWindowsMenuBar::handleReparent(QWindow *newParentWindow)
777 {
778 qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << newParentWindow << ')' << this;
779 if (newParentWindow == nullptr) {
780 removeFromWindow();
781 return; // Happens during Quick Controls 1 property setup
782 }
783 if (QPlatformWindow *platWin = newParentWindow->handle())
784 install(static_cast<QWindowsWindow *>(platWin));
785 else // Store for later creation, see menuBarOf()
786 newParentWindow->setProperty(menuBarPropertyName, QVariant::fromValue<QObject *>(this));
787 }
788
menuBarOf(const QWindow * notYetCreatedWindow)789 QWindowsMenuBar *QWindowsMenuBar::menuBarOf(const QWindow *notYetCreatedWindow)
790 {
791 const QVariant menuBarV = notYetCreatedWindow->property(menuBarPropertyName);
792 return menuBarV.canConvert<QObject *>()
793 ? qobject_cast<QWindowsMenuBar *>(menuBarV.value<QObject *>()) : nullptr;
794 }
795
install(QWindowsWindow * window)796 void QWindowsMenuBar::install(QWindowsWindow *window)
797 {
798 const HWND hwnd = window->handle();
799 const BOOL result = SetMenu(hwnd, m_hMenuBar);
800 if (result) {
801 window->setMenuBar(this);
802 QWindowsContext::forceNcCalcSize(hwnd);
803 }
804 }
805
removeFromWindow()806 void QWindowsMenuBar::removeFromWindow()
807 {
808 if (QWindowsWindow *window = platformWindow()) {
809 const HWND hwnd = window->handle();
810 SetMenu(hwnd, nullptr);
811 window->setMenuBar(nullptr);
812 QWindowsContext::forceNcCalcSize(hwnd);
813 }
814 }
815
menuForTag(quintptr tag) const816 QPlatformMenu *QWindowsMenuBar::menuForTag(quintptr tag) const
817 {
818 return traverseMenus(this, [tag] (const QWindowsMenu *m) { return m->tag() == tag; });
819 }
820
createMenu() const821 QPlatformMenu *QWindowsMenuBar::createMenu() const
822 {
823 QPlatformMenu *result = new QWindowsMenu;
824 qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result;
825 return result;
826 }
827
notifyTriggered(uint id)828 bool QWindowsMenuBar::notifyTriggered(uint id)
829 {
830 QPlatformMenuItem *result = findMenuItemById(this, id);
831 if (result != nullptr) {
832 qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id;
833 emit result->activated();
834 }
835 return result != nullptr;
836 }
837
notifyAboutToShow(HMENU hmenu)838 bool QWindowsMenuBar::notifyAboutToShow(HMENU hmenu)
839 {
840 if (QWindowsMenu *menu = findMenuByHandle(this, hmenu)) {
841 emit menu->aboutToShow();
842 return true;
843 }
844 return false;
845 }
846
platformWindow() const847 QWindowsWindow *QWindowsMenuBar::platformWindow() const
848 {
849 if (const QWindowsContext *ctx = QWindowsContext::instance()) {
850 if (QWindowsWindow *w = ctx->findPlatformWindow(this))
851 return w;
852 }
853 return nullptr;
854 }
855
redraw() const856 void QWindowsMenuBar::redraw() const
857 {
858 if (const QWindowsWindow *window = platformWindow())
859 DrawMenuBar(window->handle());
860 }
861
862 #ifndef QT_NO_DEBUG_STREAM
863
864 template <class M> /* Menu[Item] */
formatTextSequence(QDebug & d,const QVector<M * > & v)865 static void formatTextSequence(QDebug &d, const QVector<M *> &v)
866 {
867 if (const int size = v.size()) {
868 d << '[' << size << "](";
869 for (int i = 0; i < size; ++i) {
870 if (i)
871 d << ", ";
872 if (!v.at(i)->isVisible())
873 d << "[hidden] ";
874 d << '"' << v.at(i)->text() << '"';
875 }
876 d << ')';
877 }
878 }
879
formatDebug(QDebug & d) const880 void QWindowsMenuItem::formatDebug(QDebug &d) const
881 {
882 if (m_separator)
883 d << "separator, ";
884 else
885 d << '"' << m_text << "\", ";
886 d << static_cast<const void *>(this);
887 if (m_parentMenu)
888 d << ", parentMenu=" << static_cast<const void *>(m_parentMenu);
889 if (m_subMenu)
890 d << ", subMenu=" << static_cast<const void *>(m_subMenu);
891 d << ", tag=" << Qt::showbase << Qt::hex
892 << tag() << Qt::noshowbase << Qt::dec << ", id=" << m_id;
893 #if QT_CONFIG(shortcut)
894 if (!m_shortcut.isEmpty())
895 d << ", shortcut=" << m_shortcut;
896 #endif
897 if (m_visible)
898 d << " [visible]";
899 if (m_enabled)
900 d << " [enabled]";
901 if (m_checkable)
902 d << ", checked=" << m_checked;
903 }
904
operator <<(QDebug d,const QPlatformMenuItem * i)905 QDebug operator<<(QDebug d, const QPlatformMenuItem *i)
906 {
907 QDebugStateSaver saver(d);
908 d.nospace();
909 d.noquote();
910 d << "QPlatformMenuItem(";
911 if (i)
912 static_cast<const QWindowsMenuItem *>(i)->formatDebug(d);
913 else
914 d << '0';
915 d << ')';
916 return d;
917 }
918
formatDebug(QDebug & d) const919 void QWindowsMenu::formatDebug(QDebug &d) const
920 {
921 d << '"' << m_text << "\", " << static_cast<const void *>(this)
922 << ", handle=" << m_hMenu;
923 if (m_parentMenuBar != nullptr)
924 d << " [on menubar]";
925 if (m_parentMenu != nullptr)
926 d << " [on menu]";
927 if (tag())
928 d << ", tag=" << Qt::showbase << Qt::hex << tag() << Qt::noshowbase << Qt::dec;
929 if (m_visible)
930 d << " [visible]";
931 if (m_enabled)
932 d << " [enabled]";
933 d <<' ';
934 formatTextSequence(d, m_menuItems);
935 }
936
formatDebug(QDebug & d) const937 void QWindowsMenuBar::formatDebug(QDebug &d) const
938 {
939 d << static_cast<const void *>(this) << ' ';
940 formatTextSequence(d, m_menus);
941 }
942
operator <<(QDebug d,const QPlatformMenu * m)943 QDebug operator<<(QDebug d, const QPlatformMenu *m)
944 {
945 QDebugStateSaver saver(d);
946 d.nospace();
947 d.noquote();
948 if (m) {
949 d << m->metaObject()->className() << '(';
950 static_cast<const QWindowsMenu *>(m)->formatDebug(d);
951 d << ')';
952 } else {
953 d << "QPlatformMenu(0)";
954 }
955 return d;
956 }
957
operator <<(QDebug d,const QPlatformMenuBar * mb)958 QDebug operator<<(QDebug d, const QPlatformMenuBar *mb)
959 {
960 QDebugStateSaver saver(d);
961 d.nospace();
962 d.noquote();
963 d << "QPlatformMenuBar(";
964 if (mb)
965 static_cast<const QWindowsMenuBar *>(mb)->formatDebug(d);
966 else
967 d << '0';
968 d << ')';
969 return d;
970 }
971
972 #endif // !QT_NO_DEBUG_STREAM
973
974 QT_END_NAMESPACE
975