diff options
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r-- | src/plugins/platforms/windows/qtwindowsglobal.h | 8 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowscontext.cpp | 27 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowscontext.h | 3 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowsintegration.cpp | 8 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowsintegration.h | 4 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowskeymapper.cpp | 6 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowsmenu.cpp | 962 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowsmenu.h | 243 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowstheme.cpp | 52 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowstheme.h | 7 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowswindow.cpp | 13 | ||||
-rw-r--r-- | src/plugins/platforms/windows/qwindowswindow.h | 6 | ||||
-rw-r--r-- | src/plugins/platforms/windows/windows.pri | 2 |
13 files changed, 1340 insertions, 1 deletions
diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h index e703b5d47e..34fe114d0e 100644 --- a/src/plugins/platforms/windows/qtwindowsglobal.h +++ b/src/plugins/platforms/windows/qtwindowsglobal.h @@ -121,6 +121,9 @@ enum WindowsEventType // Simplify event types EndSessionApplicationEvent = ApplicationEventFlag + 5, AppCommandEvent = ApplicationEventFlag + 6, DeviceChangeEvent = ApplicationEventFlag + 7, + MenuAboutToShowEvent = ApplicationEventFlag + 8, + AcceleratorCommandEvent = ApplicationEventFlag + 9, + MenuCommandEvent = ApplicationEventFlag + 10, InputMethodStartCompositionEvent = InputMethodEventFlag + 1, InputMethodCompositionEvent = InputMethodEventFlag + 2, InputMethodEndCompositionEvent = InputMethodEventFlag + 3, @@ -274,6 +277,11 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI return QtWindows::GestureEvent; case WM_DEVICECHANGE: return QtWindows::DeviceChangeEvent; + case WM_INITMENU: + case WM_INITMENUPOPUP: + return QtWindows::MenuAboutToShowEvent; + case WM_COMMAND: + return HIWORD(wParamIn) ? QtWindows::AcceleratorCommandEvent : QtWindows::MenuCommandEvent; case WM_DPICHANGED: return QtWindows::DpiChangedEvent; default: diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index f017929148..b6b51cd60a 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -44,6 +44,7 @@ #include "qwindowskeymapper.h" #include "qwindowsmousehandler.h" #include "qtwindowsglobal.h" +#include "qwindowsmenu.h" #include "qwindowsmime.h" #include "qwindowsinputcontext.h" #include "qwindowstabletsupport.h" @@ -90,6 +91,7 @@ Q_LOGGING_CATEGORY(lcQpaGl, "qt.qpa.gl") Q_LOGGING_CATEGORY(lcQpaMime, "qt.qpa.mime") Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods") Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs") +Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus") Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility") @@ -601,6 +603,15 @@ void QWindowsContext::removeWindow(HWND hwnd) } } +QWindowsWindow *QWindowsContext::findPlatformWindow(const QWindowsMenuBar *mb) const +{ + for (auto it = d->m_windows.cbegin(), end = d->m_windows.cend(); it != end; ++it) { + if ((*it)->menuBar() == mb) + return *it; + } + return nullptr; +} + QWindowsWindow *QWindowsContext::findPlatformWindow(HWND hwnd) const { return d->m_windows.value(hwnd); @@ -994,6 +1005,22 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message, case QtWindows::InputMethodKeyDownEvent: case QtWindows::AppCommandEvent: return sessionManagerInteractionBlocked() || d->m_keyMapper.translateKeyEvent(platformWindow->window(), hwnd, msg, result); + case QtWindows::MenuAboutToShowEvent: + if (sessionManagerInteractionBlocked()) + return true; + if (QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(wParam))) + return true; + if (platformWindow == nullptr || platformWindow->menuBar() == nullptr) + return false; + return platformWindow->menuBar()->notifyAboutToShow(reinterpret_cast<HMENU>(wParam)); + case QtWindows::MenuCommandEvent: + if (sessionManagerInteractionBlocked()) + return true; + if (QWindowsPopupMenu::notifyTriggered(LOWORD(wParam))) + return true; + if (platformWindow == nullptr || platformWindow->menuBar() == nullptr) + return false; + return platformWindow->menuBar()->notifyTriggered(LOWORD(wParam)); case QtWindows::MoveEvent: platformWindow->handleMoved(); return true; diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h index b50010321b..7f4a76af05 100644 --- a/src/plugins/platforms/windows/qwindowscontext.h +++ b/src/plugins/platforms/windows/qwindowscontext.h @@ -63,11 +63,13 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaGl) Q_DECLARE_LOGGING_CATEGORY(lcQpaMime) Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods) Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs) +Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus) Q_DECLARE_LOGGING_CATEGORY(lcQpaTablet) Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility) class QWindow; class QPlatformScreen; +class QWindowsMenuBar; class QWindowsScreenManager; class QWindowsTabletSupport; class QWindowsWindow; @@ -177,6 +179,7 @@ public: QWindowsWindow *findClosestPlatformWindow(HWND) const; QWindowsWindow *findPlatformWindow(HWND) const; + QWindowsWindow *findPlatformWindow(const QWindowsMenuBar *mb) const; QWindow *findWindow(HWND) const; QWindowsWindow *findPlatformWindowAt(HWND parent, const QPoint &screenPoint, unsigned cwex_flags) const; diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp index a049b8c40c..6c8fccf350 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.cpp +++ b/src/plugins/platforms/windows/qwindowsintegration.cpp @@ -42,6 +42,7 @@ #include "qwindowswindow.h" #include "qwindowscontext.h" #include "qwin10helpers.h" +#include "qwindowsmenu.h" #include "qwindowsopenglcontext.h" #include "qwindowsscreen.h" @@ -206,6 +207,10 @@ static inline unsigned parseOptions(const QStringList ¶mList, } else if (parseIntOption(param, QLatin1String("verbose"), 0, INT_MAX, &QWindowsContext::verbose) || parseIntOption(param, QLatin1String("tabletabsoluterange"), 0, INT_MAX, tabletAbsoluteRange) || parseIntOption(param, QLatin1String("dpiawareness"), QtWindows::ProcessDpiUnaware, QtWindows::ProcessPerMonitorDpiAware, dpiAwareness)) { + } else if (param == QLatin1String("menus=native")) { + options |= QWindowsIntegration::AlwaysUseNativeMenus; + } else if (param == QLatin1String("menus=none")) { + options |= QWindowsIntegration::NoNativeMenus; } else { qWarning() << "Unknown option" << param; } @@ -334,6 +339,9 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons QWindowsWindow *result = createPlatformWindowHelper(window, obtained); Q_ASSERT(result); + if (QWindowsMenuBar *menuBarToBeInstalled = QWindowsMenuBar::menuBarOf(window)) + menuBarToBeInstalled->install(result); + if (requested.flags != obtained.flags) window->setFlags(obtained.flags); // Trigger geometry change (unless it has a special state in which case setWindowState() diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h index fbdb341dab..11320332bb 100644 --- a/src/plugins/platforms/windows/qwindowsintegration.h +++ b/src/plugins/platforms/windows/qwindowsintegration.h @@ -64,7 +64,9 @@ public: DontPassOsMouseEventsSynthesizedFromTouch = 0x20, // Do not pass OS-generated mouse events from touch. // Keep in sync with QWindowsFontDatabase::FontOptions DontUseDirectWriteFonts = QWindowsFontDatabase::DontUseDirectWriteFonts, - DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts + DontUseColorFonts = QWindowsFontDatabase::DontUseColorFonts, + AlwaysUseNativeMenus = 0x100, + NoNativeMenus = 0x200 }; explicit QWindowsIntegration(const QStringList ¶mList); diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp index 24c2df86d4..2f459b70ec 100644 --- a/src/plugins/platforms/windows/qwindowskeymapper.cpp +++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp @@ -903,6 +903,12 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, const MSG &ms return true; } + // Enable Alt accelerators ("&File") on menus + if (msgType == WM_SYSKEYDOWN && (nModifiers & AltAny) != 0 && GetMenu(msg.hwnd) != nullptr) + return false; + if (msgType == WM_SYSKEYUP && nModifiers == 0 && GetMenu(msg.hwnd) != nullptr) + return false; + bool result = false; // handle Directionality changes (BiDi) with RTL extensions if (m_useRTLExtensions) { diff --git a/src/plugins/platforms/windows/qwindowsmenu.cpp b/src/plugins/platforms/windows/qwindowsmenu.cpp new file mode 100644 index 0000000000..4e1997c4f8 --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsmenu.cpp @@ -0,0 +1,962 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qwindowsmenu.h" +#include "qwindowscontext.h" +#include "qwindowswindow.h" + +#include <QtGui/qwindow.h> +#include <QtCore/qdebug.h> +#include <QtCore/qvariant.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qpointer.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +/*! + \class QWindowsMenuBar + \brief Windows native menu bar + + \list + \li \l{https://msdn.microsoft.com/de-de/library/windows/desktop/ms647553(v=vs.85).aspx#_win32_Menu_Creation_Functions}, + \e{About Menus} + \endlist + + \note The destruction order of the QWindowsMenu/Item/Bar instances is + arbitrary depending on whether the application is Qt Quick or + Qt Widgets, either the containers or the items might be deleted first. + + \internal + \ingroup qt-lighthouse-win +*/ + +static uint nextId = 1; + +// Find a QPlatformMenu[Item]* in a vector of QWindowsMenu[Item], where +// QVector::indexOf() cannot be used since it wants a QWindowsMenu[Item]* +template <class Derived, class Needle> +static int indexOf(const QVector<Derived *> &v, const Needle *needle) +{ + for (int i = 0, size = v.size(); i < size; ++i) { + if (v.at(i) == needle) + return i; + } + return -1; +} + +// Helper for inserting a QPlatformMenu[Item]* into a vector of QWindowsMenu[Item]. +template <class Derived, class Base> +static int insertBefore(QVector<Derived *> *v, Base *newItemIn, const Base *before = nullptr) +{ + int index = before ? indexOf(*v, before) : -1; + if (index != -1) { + v->insert(index, static_cast<Derived *>(newItemIn)); + } else { + index = v->size(); + v->append(static_cast<Derived *>(newItemIn)); + } + return index; +} + +static inline const wchar_t *qStringToWChar(const QString &s) +{ + return reinterpret_cast<const wchar_t *>(s.utf16()); +} + +// Traverse menu and return the item for which predicate +// "bool Function(QWindowsMenuItem *)" returns true +template <class Predicate> +static QWindowsMenuItem *traverseMenuItems(const QWindowsMenu *menu, Predicate p) +{ + const QWindowsMenu::MenuItems &items = menu->menuItems(); + for (QWindowsMenuItem *item : items) { + if (p(item)) + return item; + if (item->subMenu()) { + if (QWindowsMenuItem *subMenuItem = traverseMenuItems(item->subMenu(), p)) + return subMenuItem; + } + } + return nullptr; +} + +// Traverse menu bar return the item for which predicate +// "bool Function(QWindowsMenuItem *)" returns true +template <class Predicate> +static QWindowsMenuItem *traverseMenuItems(const QWindowsMenuBar *menuBar, Predicate p) +{ + const QWindowsMenuBar::Menus &menus = menuBar->menus(); + for (QWindowsMenu *menu : menus) { + if (QWindowsMenuItem *item = traverseMenuItems(menu, p)) + return item; + } + return nullptr; +} + +template <class Menu /* Menu[Bar] */> +static QWindowsMenuItem *findMenuItemById(const Menu *menu, uint id) +{ + return traverseMenuItems(menu, [id] (const QWindowsMenuItem *i) { return i->id() == id; }); +} + +// Traverse menu and return the menu for which predicate +// "bool Function(QWindowsMenu *)" returns true +template <class Predicate> +static QWindowsMenu *traverseMenus(const QWindowsMenu *menu, Predicate p) +{ + const QWindowsMenu::MenuItems &items = menu->menuItems(); + for (QWindowsMenuItem *item : items) { + if (QWindowsMenu *subMenu = item->subMenu()) { + if (p(subMenu)) + return subMenu; + if (QWindowsMenu *menu = traverseMenus(subMenu, p)) + return menu; + } + } + return nullptr; +} + +// Traverse menu bar return the item for which +// function "bool Function(QWindowsMenu *)" returns true +template <class Predicate> +static QWindowsMenu *traverseMenus(const QWindowsMenuBar *menuBar, Predicate p) +{ + const QWindowsMenuBar::Menus &menus = menuBar->menus(); + for (QWindowsMenu *menu : menus) { + if (p(menu)) + return menu; + if (QWindowsMenu *subMenu = traverseMenus(menu, p)) + return subMenu; + } + return nullptr; +} + +template <class Menu /* Menu[Bar] */> +static QWindowsMenu *findMenuByHandle(const Menu *menu, HMENU hMenu) +{ + return traverseMenus(menu, [hMenu] (const QWindowsMenu *i) { return i->menuHandle() == hMenu; }); +} + +template <class MenuType> +static int findNextVisibleEntry(const QVector<MenuType *> &entries, int pos) +{ + for (int i = pos, size = entries.size(); i < size; ++i) { + if (entries.at(i)->isVisible()) + return i; + } + return -1; +} + +static inline void menuItemInfoInit(MENUITEMINFO &menuItemInfo) +{ + memset(&menuItemInfo, 0, sizeof(MENUITEMINFO)); + menuItemInfo.cbSize = sizeof(MENUITEMINFO); +} + +static inline void menuItemInfoSetText(MENUITEMINFO &menuItemInfo, const QString &text) +{ + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STRING; + menuItemInfo.dwTypeData = const_cast<wchar_t *>(qStringToWChar(text)); + menuItemInfo.cch = UINT(text.size()); +} + +static UINT menuItemState(HMENU hMenu, UINT uItem, BOOL fByPosition) +{ + MENUITEMINFO menuItemInfo; + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STATE; + return GetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo) == TRUE ? menuItemInfo.fState : 0; +} + +static void menuItemSetState(HMENU hMenu, UINT uItem, BOOL fByPosition, UINT flags) +{ + MENUITEMINFO menuItemInfo; + menuItemInfoInit(menuItemInfo); + menuItemInfo.fMask = MIIM_STATE; + menuItemInfo.fState = flags; + SetMenuItemInfo(hMenu, uItem, fByPosition, &menuItemInfo); +} + +static void menuItemSetChangeState(HMENU hMenu, UINT uItem, BOOL fByPosition, + bool value, UINT trueState, UINT falseState) +{ + const UINT oldState = menuItemState(hMenu, uItem, fByPosition); + UINT newState = oldState; + if (value) { + newState |= trueState; + newState &= ~falseState; + } else { + newState &= ~trueState; + newState |= falseState; + } + if (oldState != newState) + menuItemSetState(hMenu, uItem, fByPosition, newState); +} + +// ------------ QWindowsMenuItem +QWindowsMenuItem::QWindowsMenuItem(QWindowsMenu *parentMenu) + : m_parentMenu(parentMenu) + , m_id(0) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this) + << "parentMenu=" << parentMenu; +} + +QWindowsMenuItem::~QWindowsMenuItem() +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); + removeFromMenu(); + freeBitmap(); +} + +void QWindowsMenuItem::freeBitmap() +{ + if (m_hbitmap) { + DeleteObject(m_hbitmap); + m_hbitmap = nullptr; + } +} + +void QWindowsMenuItem::setIcon(const QIcon &icon) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this; + if (m_icon.cacheKey() == icon.cacheKey()) + return; + m_icon = icon; + if (m_parentMenu != nullptr) + updateBitmap(); +} + +Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0); + +void QWindowsMenuItem::updateBitmap() +{ + freeBitmap(); + if (!m_icon.isNull()) { + const int size = m_iconSize ? m_iconSize : GetSystemMetrics(SM_CYMENUCHECK); + m_hbitmap = qt_pixmapToWinHBITMAP(m_icon.pixmap(QSize(size, size)), 1); + } + MENUITEMINFO itemInfo; + menuItemInfoInit(itemInfo); + itemInfo.fMask = MIIM_BITMAP; + itemInfo.hbmpItem = m_hbitmap; + SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &itemInfo); +} + +void QWindowsMenuItem::setText(const QString &text) +{ + qCDebug(lcQpaMenus).nospace().noquote() + << __FUNCTION__ << "(\"" << text << "\") " << this; + if (m_text == text) + return; + m_text = text; + if (m_parentMenu != nullptr) + updateText(); +} + +void QWindowsMenuItem::updateText() +{ + MENUITEMINFO menuItemInfo; + const QString &text = nativeText(); + menuItemInfoSetText(menuItemInfo, text); + SetMenuItemInfo(parentMenuHandle(), m_id, FALSE, &menuItemInfo); +} + +void QWindowsMenuItem::setMenu(QPlatformMenu *menuIn) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuIn << ')' << this; + if (menuIn == m_subMenu) + return; + const uint oldId = m_id; + if (menuIn != nullptr) { // Set submenu + m_subMenu = static_cast<QWindowsMenu *>(menuIn); + m_subMenu->setAsItemSubMenu(this); + m_id = m_subMenu->id(); + if (m_parentMenu != nullptr) { + ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND | MF_POPUP, + m_id, qStringToWChar(m_text)); + } + return; + } + // Clear submenu + m_subMenu = nullptr; + if (m_parentMenu != nullptr) { + m_id = nextId++; + ModifyMenu(m_parentMenu->menuHandle(), oldId, MF_BYCOMMAND, + m_id, qStringToWChar(m_text)); + } else { + m_id = 0; + } +} + +void QWindowsMenuItem::setVisible(bool isVisible) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isVisible << ')' << this; + if (m_visible == isVisible) + return; + m_visible = isVisible; + if (m_parentMenu == nullptr) + return; + // Windows menu items do not implement settable visibility, we need to work + // around by removing the item from the menu. It will be kept in the list. + if (isVisible) + insertIntoMenuHelper(m_parentMenu, false, m_parentMenu->menuItems().indexOf(this)); + else + RemoveMenu(parentMenuHandle(), m_id, MF_BYCOMMAND); +} + +void QWindowsMenuItem::setIsSeparator(bool isSeparator) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isSeparator << ')' << this; + if (m_separator == isSeparator) + return; + m_separator = isSeparator; +} + +void QWindowsMenuItem::setCheckable(bool checkable) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << checkable << ')' << this; + if (m_checkable == checkable) + return; + m_checkable = checkable; + if (m_parentMenu == nullptr) + return; + UINT state = menuItemState(parentMenuHandle(), m_id, FALSE); + if (m_checkable) + state |= m_checked ? MF_CHECKED : MF_UNCHECKED; + else + state &= ~(MF_CHECKED | MF_UNCHECKED); + menuItemSetState(parentMenuHandle(), m_id, FALSE, state); +} + +void QWindowsMenuItem::setChecked(bool isChecked) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << isChecked << ')' << this; + if (m_checked == isChecked) + return; + m_checked = isChecked; + // Convenience: Allow to set checkable by calling setChecked(true) for + // Quick Controls 1 + if (isChecked) + m_checkable = true; + if (m_parentMenu == nullptr || !m_checkable) + return; + menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_checked, MF_CHECKED, MF_UNCHECKED); +} + +void QWindowsMenuItem::setShortcut(const QKeySequence &shortcut) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << shortcut << ')' << this; + if (m_shortcut == shortcut) + return; + m_shortcut = shortcut; + if (m_parentMenu != nullptr) + updateText(); +} + +void QWindowsMenuItem::setEnabled(bool enabled) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this; + if (m_enabled == enabled) + return; + m_enabled = enabled; + if (m_parentMenu != nullptr) + menuItemSetChangeState(parentMenuHandle(), m_id, FALSE, m_enabled, MF_ENABLED, MF_GRAYED); +} + +void QWindowsMenuItem::setIconSize(int size) +{ + if (m_iconSize == size) + return; + m_iconSize = size; + if (m_parentMenu != nullptr) + updateBitmap(); +} + +HMENU QWindowsMenuItem::parentMenuHandle() const +{ + return m_parentMenu ? m_parentMenu->menuHandle() : nullptr; +} + +UINT QWindowsMenuItem::state() const +{ + if (m_separator) + return MF_SEPARATOR; + UINT result = MF_STRING | (m_enabled ? MF_ENABLED : MF_GRAYED); + if (m_subMenu != nullptr) + result |= MF_POPUP; + if (m_checkable) + result |= m_checked ? MF_CHECKED : MF_UNCHECKED; + if (QGuiApplication::layoutDirection() == Qt::RightToLeft) + result |= MFT_RIGHTORDER; + return result; +} + +QString QWindowsMenuItem::nativeText() const +{ + QString result = m_text; + if (!m_shortcut.isEmpty()) { + result += QLatin1Char('\t'); + result += m_shortcut.toString(QKeySequence::NativeText); + } + return result; +} + +void QWindowsMenuItem::insertIntoMenu(QWindowsMenu *menu, bool append, int index) +{ + if (m_id == 0 && m_subMenu == nullptr) + m_id = nextId++; + insertIntoMenuHelper(menu, append, index); + m_parentMenu = menu; +} + +void QWindowsMenuItem::insertIntoMenuHelper(QWindowsMenu *menu, bool append, int index) +{ + const QString &text = nativeText(); + + UINT_PTR idBefore = 0; + if (!append) { + // Skip over self (either newly inserted or when called from setVisible() + const int nextIndex = findNextVisibleEntry(menu->menuItems(), index + 1); + if (nextIndex != -1) + idBefore = menu->menuItems().at(nextIndex)->id(); + } + + if (idBefore) + InsertMenu(menu->menuHandle(), idBefore, state(), m_id, qStringToWChar(text)); + else + AppendMenu(menu->menuHandle(), state(), m_id, qStringToWChar(text)); + + updateBitmap(); +} + +bool QWindowsMenuItem::removeFromMenu() +{ + if (QWindowsMenu *parentMenu = m_parentMenu) { + m_parentMenu = nullptr; + RemoveMenu(parentMenu->menuHandle(), m_id, MF_BYCOMMAND); + parentMenu->notifyRemoved(this); + return true; + } + return false; +} + +// ------------ QWindowsMenu + +QWindowsMenu::QWindowsMenu() : QWindowsMenu(nullptr, CreateMenu()) +{ +} + +QWindowsMenu::QWindowsMenu(QWindowsMenu *parentMenu, HMENU menu) + : m_parentMenu(parentMenu) + , m_hMenu(menu) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this) + << "parentMenu=" << parentMenu << "HMENU=" << m_hMenu; +} + +QWindowsMenu::~QWindowsMenu() +{ + qCDebug(lcQpaMenus).noquote().nospace() << __FUNCTION__ + << " \"" <<m_text << "\", " << static_cast<const void *>(this); + for (int i = m_menuItems.size() - 1; i>= 0; --i) + m_menuItems.at(i)->removeFromMenu(); + removeFromParent(); + DestroyMenu(m_hMenu); +} + +void QWindowsMenu::insertMenuItem(QPlatformMenuItem *menuItemIn, QPlatformMenuItem *before) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ", before=" << before << ')' << this; + QWindowsMenuItem *menuItem = static_cast<QWindowsMenuItem *>(menuItemIn); + const int index = insertBefore(&m_menuItems, menuItemIn, before); + const bool append = index == m_menuItems.size() - 1; + menuItem->insertIntoMenu(this, append, index); +} + +void QWindowsMenu::removeMenuItem(QPlatformMenuItem *menuItemIn) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menuItemIn << ')' << this; + static_cast<QWindowsMenuItem *>(menuItemIn)->removeFromMenu(); +} + +void QWindowsMenu::setText(const QString &text) +{ + qCDebug(lcQpaMenus).nospace().noquote() + << __FUNCTION__ << "(\"" << text << "\") " << this; + if (m_text == text) + return; + m_text = text; + if (!m_visible) + return; + const HMENU ph = parentHandle(); + if (ph == nullptr) + return; + MENUITEMINFO menuItemInfo; + menuItemInfoSetText(menuItemInfo, m_text); + SetMenuItemInfo(ph, id(), FALSE, &menuItemInfo); +} + +void QWindowsMenu::setIcon(const QIcon &icon) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << icon << ')' << this; + m_icon = icon; +} + +void QWindowsMenu::setEnabled(bool enabled) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << enabled << ')' << this; + if (m_enabled == enabled) + return; + m_enabled = enabled; + if (!m_visible) + return; + if (const HMENU ph = parentHandle()) + menuItemSetChangeState(ph, id(), FALSE, m_enabled, MF_ENABLED, MF_GRAYED); +} + +QWindowsMenuItem *QWindowsMenu::itemForSubMenu(const QWindowsMenu *subMenu) const +{ + const auto it = std::find_if(m_menuItems.cbegin(), m_menuItems.cend(), + [subMenu] (const QWindowsMenuItem *i) { return i->subMenu() == subMenu; }); + return it != m_menuItems.cend() ? *it : nullptr; +} + +void QWindowsMenu::insertIntoMenuBar(QWindowsMenuBar *bar, bool append, int index) +{ + UINT_PTR idBefore = 0; + if (!append) { + // Skip over self (either newly inserted or when called from setVisible() + const int nextIndex = findNextVisibleEntry(bar->menus(), index + 1); + if (nextIndex != -1) + idBefore = bar->menus().at(nextIndex)->id(); + } + m_parentMenuBar = bar; + m_parentMenu = nullptr; + if (idBefore) + InsertMenu(bar->menuBarHandle(), idBefore, MF_POPUP | MF_BYCOMMAND, id(), qStringToWChar(m_text)); + else + AppendMenu(bar->menuBarHandle(), MF_POPUP, id(), qStringToWChar(m_text)); +} + +bool QWindowsMenu::removeFromParent() +{ + if (QWindowsMenuBar *bar = m_parentMenuBar) { + m_parentMenuBar = nullptr; + bar->notifyRemoved(this); + return RemoveMenu(bar->menuBarHandle(), id(), MF_BYCOMMAND) == TRUE; + } + if (QWindowsMenu *menu = m_parentMenu) { + m_parentMenu = nullptr; + QWindowsMenuItem *item = menu->itemForSubMenu(this); + if (item) + item->setMenu(nullptr); + return item != nullptr; + } + return false; +} + +void QWindowsMenu::setVisible(bool visible) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << visible << ')' << this; + if (m_visible == visible) + return; + m_visible = visible; + const HMENU ph = parentHandle(); + if (ph == nullptr) + return; + // Windows menus do not implement settable visibility, we need to work + // around by removing the menu from the parent. It will be kept in the list. + if (visible) { + if (m_parentMenuBar) + insertIntoMenuBar(m_parentMenuBar, false, m_parentMenuBar->menus().indexOf(this)); + } else { + RemoveMenu(ph, id(), MF_BYCOMMAND); + } + if (m_parentMenuBar) + m_parentMenuBar->redraw(); +} + +QPlatformMenuItem *QWindowsMenu::menuItemAt(int position) const +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << position; + return position >= 0 && position < m_menuItems.size() + ? m_menuItems.at(position) : nullptr; +} + +QPlatformMenuItem *QWindowsMenu::menuItemForTag(quintptr tag) const +{ + return traverseMenuItems(this, [tag] (const QPlatformMenuItem *i) { return i->tag() == tag; }); +} + +QPlatformMenuItem *QWindowsMenu::createMenuItem() const +{ + QPlatformMenuItem *result = new QWindowsMenuItem; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +QPlatformMenu *QWindowsMenu::createSubMenu() const +{ + QPlatformMenu *result = new QWindowsMenu; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +void QWindowsMenu::setAsItemSubMenu(QWindowsMenuItem *item) +{ + m_parentMenu = item->parentMenu(); +} + +HMENU QWindowsMenu::parentMenuHandle() const +{ + return m_parentMenu ? m_parentMenu->menuHandle() : nullptr; +} + +HMENU QWindowsMenu::parentMenuBarHandle() const +{ + return m_parentMenuBar ? m_parentMenuBar->menuBarHandle() : nullptr; +} + +HMENU QWindowsMenu::parentHandle() const +{ + if (m_parentMenuBar) + return m_parentMenuBar->menuBarHandle(); + if (m_parentMenu) + return m_parentMenu->menuHandle(); + return nullptr; +} + +// --------------- QWindowsPopupMenu + +static QPointer<QWindowsPopupMenu> lastShownPopupMenu; + +QWindowsPopupMenu::QWindowsPopupMenu() : QWindowsMenu(nullptr, CreatePopupMenu()) +{ +} + +void QWindowsPopupMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, + const QPlatformMenuItem *item) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '>' << this << parentWindow << targetRect << item; + const QWindowsBaseWindow *window = static_cast<const QWindowsBaseWindow *>(parentWindow->handle()); + const QPoint globalPos = window->mapToGlobal(targetRect.topLeft()); + trackPopupMenu(window->handle(), globalPos.x(), globalPos.y()); +} + +bool QWindowsPopupMenu::trackPopupMenu(HWND windowHandle, int x, int y) +{ + lastShownPopupMenu = this; + return TrackPopupMenu(menuHandle(), + QGuiApplication::layoutDirection() == Qt::RightToLeft ? UINT(TPM_RIGHTALIGN) : UINT(0), + x, y, 0, windowHandle, nullptr) == TRUE; +} + +bool QWindowsPopupMenu::notifyTriggered(uint id) +{ + QPlatformMenuItem *result = lastShownPopupMenu.isNull() + ? nullptr + : findMenuItemById(lastShownPopupMenu.data(), id); + if (result != nullptr) { + qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id; + emit result->activated(); + } + lastShownPopupMenu = nullptr; + return result != nullptr; +} + +bool QWindowsPopupMenu::notifyAboutToShow(HMENU hmenu) +{ + if (lastShownPopupMenu.isNull()) + return false; + if (lastShownPopupMenu->menuHandle() == hmenu) { + emit lastShownPopupMenu->aboutToShow(); + return true; + } + if (QWindowsMenu *menu = findMenuByHandle(lastShownPopupMenu.data(), hmenu)) { + emit menu->aboutToShow(); + return true; + } + return false; +} + +// --------------- QWindowsMenuBar + +QWindowsMenuBar::QWindowsMenuBar() : m_hMenuBar(CreateMenu()) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); +} + +QWindowsMenuBar::~QWindowsMenuBar() +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << static_cast<const void *>(this); + for (int m = m_menus.size() - 1; m >= 0; --m) + m_menus.at(m)->removeFromParent(); + removeFromWindow(); + DestroyMenu(m_hMenuBar); +} + +void QWindowsMenuBar::insertMenu(QPlatformMenu *menuIn, QPlatformMenu *before) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << menuIn << "before=" << before; + QWindowsMenu *menu = static_cast<QWindowsMenu *>(menuIn); + const int index = insertBefore(&m_menus, menuIn, before); + menu->insertIntoMenuBar(this, index == m_menus.size() - 1, index); +} + +void QWindowsMenuBar::removeMenu(QPlatformMenu *menu) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << menu << ')' << this; + const int index = indexOf(m_menus, menu); + if (index != -1) + m_menus[index]->removeFromParent(); +} + +// When calling handleReparent() for a QWindow instances that does not have +// a platform window yet, set the menubar as dynamic property to be installed +// on platform window creation. +static const char menuBarPropertyName[] = "_q_windowsNativeMenuBar"; + +void QWindowsMenuBar::handleReparent(QWindow *newParentWindow) +{ + qCDebug(lcQpaMenus) << __FUNCTION__ << '(' << newParentWindow << ')' << this; + if (newParentWindow == nullptr) { + removeFromWindow(); + return; // Happens during Quick Controls 1 property setup + } + if (QPlatformWindow *platWin = newParentWindow->handle()) + install(static_cast<QWindowsWindow *>(platWin)); + else // Store for later creation, see menuBarOf() + newParentWindow->setProperty(menuBarPropertyName, qVariantFromValue<QObject *>(this)); +} + +QWindowsMenuBar *QWindowsMenuBar::menuBarOf(const QWindow *notYetCreatedWindow) +{ + const QVariant menuBarV = notYetCreatedWindow->property(menuBarPropertyName); + return menuBarV.canConvert<QObject *>() + ? qobject_cast<QWindowsMenuBar *>(menuBarV.value<QObject *>()) : nullptr; +} + +static inline void forceNcCalcSize(HWND hwnd) +{ + // Force WM_NCCALCSIZE to adjust margin: Does not appear to work? + SetWindowPos(hwnd, 0, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); +} + +void QWindowsMenuBar::install(QWindowsWindow *window) +{ + const HWND hwnd = window->handle(); + const BOOL result = SetMenu(hwnd, m_hMenuBar); + if (result) { + window->setMenuBar(this); + forceNcCalcSize(hwnd); + } +} + +void QWindowsMenuBar::removeFromWindow() +{ + if (QWindowsWindow *window = platformWindow()) { + const HWND hwnd = window->handle(); + SetMenu(hwnd, nullptr); + window->setMenuBar(nullptr); + forceNcCalcSize(hwnd); + } +} + +QPlatformMenu *QWindowsMenuBar::menuForTag(quintptr tag) const +{ + return traverseMenus(this, [tag] (const QWindowsMenu *m) { return m->tag() == tag; }); +} + +QPlatformMenu *QWindowsMenuBar::createMenu() const +{ + QPlatformMenu *result = new QWindowsMenu; + qCDebug(lcQpaMenus) << __FUNCTION__ << this << "returns" << result; + return result; +} + +bool QWindowsMenuBar::notifyTriggered(uint id) +{ + QPlatformMenuItem *result = findMenuItemById(this, id); + if (result != nullptr) { + qCDebug(lcQpaMenus) << __FUNCTION__ << "id=" << id; + emit result->activated(); + } + return result != nullptr; +} + +bool QWindowsMenuBar::notifyAboutToShow(HMENU hmenu) +{ + if (QWindowsMenu *menu = findMenuByHandle(this, hmenu)) { + emit menu->aboutToShow(); + return true; + } + return false; +} + +QWindowsWindow *QWindowsMenuBar::platformWindow() const +{ + if (const QWindowsContext *ctx = QWindowsContext::instance()) { + if (QWindowsWindow *w = ctx->findPlatformWindow(this)) + return w; + } + return nullptr; +} + +void QWindowsMenuBar::redraw() const +{ + if (const QWindowsWindow *window = platformWindow()) + DrawMenuBar(window->handle()); +} + +#ifndef QT_NO_DEBUG_STREAM + +template <class M> /* Menu[Item] */ +static void formatTextSequence(QDebug &d, const QVector<M *> &v) +{ + if (const int size = v.size()) { + d << '[' << size << "]("; + for (int i = 0; i < size; ++i) { + if (i) + d << ", "; + if (!v.at(i)->isVisible()) + d << "[hidden] "; + d << '"' << v.at(i)->text() << '"'; + } + d << ')'; + } +} + +void QWindowsMenuItem::formatDebug(QDebug &d) const +{ + if (m_separator) + d << "separator, "; + else + d << '"' << m_text << "\", "; + d << static_cast<const void *>(this); + if (m_parentMenu) + d << ", parentMenu=" << static_cast<const void *>(m_parentMenu); + if (m_subMenu) + d << ", subMenu=" << static_cast<const void *>(m_subMenu); + d << ", tag=" << showbase << hex + << tag() << noshowbase << dec << ", id=" << m_id; + if (!m_shortcut.isEmpty()) + d << ", shortcut=" << m_shortcut; + if (m_visible) + d << " [visible]"; + if (m_enabled) + d << " [enabled]"; + if (m_checkable) + d << ", checked=" << m_checked; +} + +QDebug operator<<(QDebug d, const QPlatformMenuItem *i) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QPlatformMenuItem("; + if (i) + static_cast<const QWindowsMenuItem *>(i)->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +void QWindowsMenu::formatDebug(QDebug &d) const +{ + d << '"' << m_text << "\", " << static_cast<const void *>(this) + << ", handle=" << m_hMenu; + if (m_parentMenuBar != nullptr) + d << " [on menubar]"; + if (m_parentMenu != nullptr) + d << " [on menu]"; + if (tag()) + d << ", tag=" << showbase << hex << tag() << noshowbase << dec; + if (m_visible) + d << " [visible]"; + if (m_enabled) + d << " [enabled]"; + d <<' '; + formatTextSequence(d, m_menuItems); +} + +void QWindowsMenuBar::formatDebug(QDebug &d) const +{ + d << static_cast<const void *>(this) << ' '; + formatTextSequence(d, m_menus); +} + +QDebug operator<<(QDebug d, const QPlatformMenu *m) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + if (m) { + d << m->metaObject()->className() << '('; + static_cast<const QWindowsMenu *>(m)->formatDebug(d); + d << ')'; + } else { + d << "QPlatformMenu(0)"; + } + return d; +} + +QDebug operator<<(QDebug d, const QPlatformMenuBar *mb) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QPlatformMenuBar("; + if (mb) + static_cast<const QWindowsMenuBar *>(mb)->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsmenu.h b/src/plugins/platforms/windows/qwindowsmenu.h new file mode 100644 index 0000000000..d51a29676e --- /dev/null +++ b/src/plugins/platforms/windows/qwindowsmenu.h @@ -0,0 +1,243 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QWINDOWSMENU_H +#define QWINDOWSMENU_H + +#include "qtwindowsglobal.h" + +#include <qpa/qplatformmenu.h> + +#include <QtCore/QVector> +#include <QtCore/QPair> + +QT_BEGIN_NAMESPACE + +class QDebug; + +class QWindowsMenu; +class QWindowsMenuBar; +class QWindowsWindow; + +class QWindowsMenuItem : public QPlatformMenuItem +{ + Q_OBJECT +public: + explicit QWindowsMenuItem(QWindowsMenu *parentMenu = nullptr); + ~QWindowsMenuItem(); + + void setText(const QString &text) override; + void setIcon(const QIcon &icon) override; + void setMenu(QPlatformMenu *menu) override; + void setVisible(bool isVisible) override; + void setIsSeparator(bool isSeparator) override; + void setFont(const QFont &) override {} + void setRole(MenuRole) override {} + void setCheckable(bool checkable) override; + void setChecked(bool isChecked) override; +#ifndef QT_NO_SHORTCUT + void setShortcut(const QKeySequence& shortcut) override; +#endif + void setEnabled(bool enabled) override; + void setIconSize(int size) override; + + const QWindowsMenu *parentMenu() const { return m_parentMenu; } + QWindowsMenu *parentMenu() { return m_parentMenu; } + HMENU parentMenuHandle() const; + QWindowsMenu *subMenu() const { return m_subMenu; } + UINT_PTR id() const { return m_id; } + void setId(uint id) { m_id = id; } + UINT state() const; + QString text() const { return m_text; } + QString nativeText() const; + bool isVisible() const { return m_visible; } + + void insertIntoMenu(QWindowsMenu *menuItem, bool append, int index); + bool removeFromMenu(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + void updateBitmap(); + void freeBitmap(); + void updateText(); + void insertIntoMenuHelper(QWindowsMenu *menu, bool append, int index); + + QWindowsMenu *m_parentMenu = nullptr; + QWindowsMenu *m_subMenu = nullptr; + UINT_PTR m_id; // Windows Id sent as wParam with WM_COMMAND or submenu handle. + QString m_text; + QIcon m_icon; + HBITMAP m_hbitmap = nullptr; + int m_iconSize = 0; + bool m_separator = false; + bool m_visible = true; + bool m_checkable = false; + bool m_checked = false; + bool m_enabled = true; + QKeySequence m_shortcut; +}; + +class QWindowsMenu : public QPlatformMenu +{ + Q_OBJECT +public: + typedef QVector<QWindowsMenuItem *> MenuItems; + + QWindowsMenu(); + ~QWindowsMenu(); + + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; + void removeMenuItem(QPlatformMenuItem *menuItem) override; + void syncMenuItem(QPlatformMenuItem *) override {} + void syncSeparatorsCollapsible(bool) override {} + + void setText(const QString &text) override; + void setIcon(const QIcon &icon) override; + void setEnabled(bool enabled) override; + bool isEnabled() const override { return m_enabled; } + void setVisible(bool visible) override; + + QPlatformMenuItem *menuItemAt(int position) const override; + QPlatformMenuItem *menuItemForTag(quintptr tag) const override; + + QPlatformMenuItem *createMenuItem() const override; + QPlatformMenu *createSubMenu() const override; + + HMENU menuHandle() const { return m_hMenu; } + UINT_PTR id() const { return reinterpret_cast<UINT_PTR>(m_hMenu); } + QString text() const { return m_text; } + const MenuItems &menuItems() const { return m_menuItems; } + QWindowsMenuItem *itemForSubMenu(const QWindowsMenu *subMenu) const; + + const QWindowsMenuBar *parentMenuBar() const { return m_parentMenuBar; } + HMENU parentMenuBarHandle() const; + const QWindowsMenu *parentMenu() const { return m_parentMenu; } + void setAsItemSubMenu(QWindowsMenuItem *item); + void notifyRemoved(QWindowsMenuItem *item) { m_menuItems.removeOne(item); } + HMENU parentMenuHandle() const; + HMENU parentHandle() const; + bool isVisible() const { return m_visible; } + void insertIntoMenuBar(QWindowsMenuBar *bar, bool append, int index); + bool removeFromParent(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +protected: + explicit QWindowsMenu(QWindowsMenu *parentMenu, HMENU menu); + +private: + QWindowsMenuBar *m_parentMenuBar = nullptr; + QWindowsMenu *m_parentMenu = nullptr; + MenuItems m_menuItems; + HMENU m_hMenu = nullptr; + QString m_text; + QIcon m_icon; + bool m_visible = true; + bool m_enabled = true; +}; + +class QWindowsPopupMenu : public QWindowsMenu +{ + Q_OBJECT +public: + QWindowsPopupMenu(); + + static bool notifyTriggered(uint id); + static bool notifyAboutToShow(HMENU hmenu); + + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; + void dismiss() override {} + + bool trackPopupMenu(HWND windowHandle, int x, int y); +}; + +class QWindowsMenuBar : public QPlatformMenuBar +{ + Q_OBJECT +public: + typedef QVector<QWindowsMenu *> Menus; + + QWindowsMenuBar(); + ~QWindowsMenuBar(); + + void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override; + void removeMenu(QPlatformMenu *menu) override; + void syncMenu(QPlatformMenu *) override {} + void handleReparent(QWindow *newParentWindow) override; + + QPlatformMenu *menuForTag(quintptr tag) const override; + QPlatformMenu *createMenu() const override; + + HMENU menuBarHandle() const { return m_hMenuBar; } + const Menus &menus() const { return m_menus; } + bool notifyTriggered(uint id); + bool notifyAboutToShow(HMENU hmenu); + void notifyRemoved(QWindowsMenu *menu) { m_menus.removeOne(menu); } + void redraw() const; + + void install(QWindowsWindow *window); + + static QWindowsMenuBar *menuBarOf(const QWindow *notYetCreatedWindow); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + QWindowsWindow *platformWindow() const; + void removeFromWindow(); + + Menus m_menus; + HMENU m_hMenuBar = nullptr; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QPlatformMenuItem *); +QDebug operator<<(QDebug d, const QPlatformMenu *); +QDebug operator<<(QDebug d, const QPlatformMenuBar *); +#endif // !QT_NO_DEBUG_STREAM + +QT_END_NAMESPACE + +#endif // QWINDOWSMENU_H diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp index e1cbbc9311..5fe7c7c071 100644 --- a/src/plugins/platforms/windows/qwindowstheme.cpp +++ b/src/plugins/platforms/windows/qwindowstheme.cpp @@ -44,6 +44,7 @@ #endif #include "qwindowstheme.h" +#include "qwindowsmenu.h" #include "qwindowsdialoghelpers.h" #include "qwindowscontext.h" #include "qwindowsintegration.h" @@ -910,4 +911,55 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions)); } +static inline bool doUseNativeMenus() +{ + const unsigned options = QWindowsIntegration::instance()->options(); + if ((options & QWindowsIntegration::NoNativeMenus) != 0) + return false; + if ((options & QWindowsIntegration::AlwaysUseNativeMenus) != 0) + return true; + // "Auto" mode: For non-widget or Quick Controls 2 applications + if (!QCoreApplication::instance()->inherits("QApplication")) + return true; + const QWindowList &topLevels = QGuiApplication::topLevelWindows(); + for (const QWindow *t : topLevels) { + if (t->inherits("QQuickApplicationWindow")) + return true; + } + return false; +} + +bool QWindowsTheme::useNativeMenus() +{ + static const bool result = doUseNativeMenus(); + return result; +} + +QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + return QWindowsTheme::useNativeMenus() ? new QWindowsMenuItem : nullptr; +} + +QPlatformMenu *QWindowsTheme::createPlatformMenu() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + // We create a popup menu here, since it will likely be used as context + // menu. Submenus should be created the factory functions of + // QPlatformMenu/Bar. Note though that Quick Controls 1 will use this + // function for submenus as well, but this has been found to work. + return QWindowsTheme::useNativeMenus() ? new QWindowsPopupMenu : nullptr; +} + +QPlatformMenuBar *QWindowsTheme::createPlatformMenuBar() const +{ + qCDebug(lcQpaMenus) << __FUNCTION__; + return QWindowsTheme::useNativeMenus() ? new QWindowsMenuBar : nullptr; +} + +void QWindowsTheme::showPlatformMenuBar() +{ + qCDebug(lcQpaMenus) << __FUNCTION__; +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h index a3019ff6eb..7ce3023111 100644 --- a/src/plugins/platforms/windows/qwindowstheme.h +++ b/src/plugins/platforms/windows/qwindowstheme.h @@ -74,6 +74,13 @@ public: QList<QSize> availableFileIconSizes() const { return m_fileIconSizes; } + QPlatformMenuItem *createPlatformMenuItem() const override; + QPlatformMenu *createPlatformMenu() const override; + QPlatformMenuBar *createPlatformMenuBar() const override; + void showPlatformMenuBar() override; + + static bool useNativeMenus(); + static const char *name; private: diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index a3337509a7..79ae4c004b 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -42,6 +42,7 @@ #include "qwindowsdrag.h" #include "qwindowsscreen.h" #include "qwindowsintegration.h" +#include "qwindowsmenu.h" #include "qwindowsnativeinterface.h" #if QT_CONFIG(dynamicgl) # include "qwindowsglcontext.h" @@ -1016,6 +1017,8 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QMargins effectiveMargins = margins + customMargins; frameWidth = effectiveMargins.left() + geometry.width() + effectiveMargins.right(); frameHeight = effectiveMargins.top() + geometry.height() + effectiveMargins.bottom(); + if (QWindowsMenuBar::menuBarOf(w) != nullptr) + frameHeight += GetSystemMetrics(SM_CYMENU); const bool isDefaultPosition = !frameX && !frameY && w->isTopLevel(); if (!QWindowsGeometryHint::positionIncludesFrame(w) && !isDefaultPosition) { frameX -= effectiveMargins.left(); @@ -2446,6 +2449,16 @@ bool QWindowsWindow::isTopLevel() const return window()->isTopLevel() && !m_data.embedded; } +QWindowsMenuBar *QWindowsWindow::menuBar() const +{ + return m_menuBar.data(); +} + +void QWindowsWindow::setMenuBar(QWindowsMenuBar *mb) +{ + m_menuBar = mb; +} + /*! \brief Sets custom margins to be added to the default margins determined by the windows style in the handling of the WM_NCCALCSIZE message. diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 46236c10b5..540d0ff98c 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -41,6 +41,7 @@ #define QWINDOWSWINDOW_H #include <QtCore/qt_windows.h> +#include <QtCore/QPointer> #include "qwindowscursor.h" #include <qpa/qplatformwindow.h> @@ -53,6 +54,7 @@ QT_BEGIN_NAMESPACE class QWindowsOleDropTarget; +class QWindowsMenuBar; class QDebug; struct QWindowsGeometryHint @@ -270,6 +272,9 @@ public: HWND handle() const override { return m_data.hwnd; } bool isTopLevel() const override; + QWindowsMenuBar *menuBar() const; + void setMenuBar(QWindowsMenuBar *mb); + QMargins customMargins() const { return m_data.customMargins; } void setCustomMargins(const QMargins &m); @@ -342,6 +347,7 @@ private: void fireExpose(const QRegion ®ion, bool force=false); mutable QWindowsWindowData m_data; + QPointer<QWindowsMenuBar> m_menuBar; mutable unsigned m_flags = WithinCreate; HDC m_hdc = 0; Qt::WindowStates m_windowState = Qt::WindowNoState; diff --git a/src/plugins/platforms/windows/windows.pri b/src/plugins/platforms/windows/windows.pri index 7694824d20..a7166e90e4 100644 --- a/src/plugins/platforms/windows/windows.pri +++ b/src/plugins/platforms/windows/windows.pri @@ -22,6 +22,7 @@ SOURCES += \ $$PWD/qwindowscursor.cpp \ $$PWD/qwindowsinputcontext.cpp \ $$PWD/qwindowstheme.cpp \ + $$PWD/qwindowsmenu.cpp \ $$PWD/qwindowsdialoghelpers.cpp \ $$PWD/qwindowsservices.cpp \ $$PWD/qwindowsnativeinterface.cpp \ @@ -43,6 +44,7 @@ HEADERS += \ $$PWD/qwindowscursor.h \ $$PWD/qwindowsinputcontext.h \ $$PWD/qwindowstheme.h \ + $$PWD/qwindowsmenu.h \ $$PWD/qwindowsdialoghelpers.h \ $$PWD/qwindowsservices.h \ $$PWD/qwindowsnativeinterface.h \ |