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