From 4821e8982fbbd671b5e17efdeb83abadaee3e815 Mon Sep 17 00:00:00 2001 From: Yuhang Zhao Date: Fri, 20 Oct 2023 10:47:53 +0800 Subject: [PATCH] win: system menu: allow user remove menu items --- FramelessHelperConfig.cmake.in | 1 + examples/dialog/dialog.cpp | 9 ++ .../Core/framelesshelpercore_global.h | 10 ++ src/core/utils_win.cpp | 115 ++++++++++++++---- 4 files changed, 111 insertions(+), 24 deletions(-) diff --git a/FramelessHelperConfig.cmake.in b/FramelessHelperConfig.cmake.in index 2dd799d4..cb4e4873 100644 --- a/FramelessHelperConfig.cmake.in +++ b/FramelessHelperConfig.cmake.in @@ -40,6 +40,7 @@ foreach(_component ${@PROJECT_NAME@_FIND_COMPONENTS}) if(EXISTS "${__targets_file}") include("${__targets_file}") add_library(${__target} ALIAS @PROJECT_NAME@::${__target_full}) + list(APPEND _@PROJECT_NAME@_available_components ${_component}) else() set(@PROJECT_NAME@_FOUND FALSE) set(@PROJECT_NAME@_NOT_FOUND_MESSAGE "Can't find necessary configuration file for ${__target}, please make sure this component is built successfully and installed properly.") diff --git a/examples/dialog/dialog.cpp b/examples/dialog/dialog.cpp index 92159a7d..ba58ec47 100644 --- a/examples/dialog/dialog.cpp +++ b/examples/dialog/dialog.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "../shared/settings.h" extern template void Settings::set(const QString &, const QString &, const QRect &); @@ -134,6 +135,14 @@ void Dialog::setupUi() #if FRAMELESSHELPER_CONFIG(titlebar) FramelessWidgetsHelper *helper = FramelessWidgetsHelper::get(this); helper->setTitleBarWidget(titleBar); +# ifdef Q_OS_WINDOWS + FramelessWidgetsHelperPrivate *helperPriv = FramelessWidgetsHelperPrivate::get(helper); + helperPriv->setProperty(kSysMenuRemoveRestoreVar, true); + helperPriv->setProperty(kSysMenuRemoveSizeVar, true); + helperPriv->setProperty(kSysMenuRemoveMinimizeVar, true); + helperPriv->setProperty(kSysMenuRemoveMaximizeVar, true); + helperPriv->setProperty(kSysMenuRemoveSeparatorVar, true); +# endif # if (!defined(Q_OS_MACOS) && FRAMELESSHELPER_CONFIG(system_button)) helper->setSystemButton(titleBar->minimizeButton(), SystemButtonType::Minimize); helper->setSystemButton(titleBar->maximizeButton(), SystemButtonType::Maximize); diff --git a/include/FramelessHelper/Core/framelesshelpercore_global.h b/include/FramelessHelper/Core/framelesshelpercore_global.h index d943dfc9..9c7f06cf 100644 --- a/include/FramelessHelper/Core/framelesshelpercore_global.h +++ b/include/FramelessHelper/Core/framelesshelpercore_global.h @@ -395,9 +395,19 @@ Q_NAMESPACE_EXPORT(FRAMELESSHELPER_CORE_API) [[maybe_unused]] inline constexpr const char kDontOverrideCursorVar[] = "FRAMELESSHELPER_DONT_OVERRIDE_CURSOR"; [[maybe_unused]] inline constexpr const char kDontToggleMaximizeVar[] = "FRAMELESSHELPER_DONT_TOGGLE_MAXIMIZE"; +[[maybe_unused]] inline constexpr const char kSysMenuDisableMoveVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_MOVE"; +[[maybe_unused]] inline constexpr const char kSysMenuDisableSizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_SIZE"; [[maybe_unused]] inline constexpr const char kSysMenuDisableMinimizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_MINIMIZE"; [[maybe_unused]] inline constexpr const char kSysMenuDisableMaximizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_MAXIMIZE"; [[maybe_unused]] inline constexpr const char kSysMenuDisableRestoreVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_RESTORE"; +[[maybe_unused]] inline constexpr const char kSysMenuDisableCloseVar[] = "FRAMELESSHELPER_SYSTEM_MENU_DISABLE_CLOSE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveMoveVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_MOVE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveSizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_SIZE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveMinimizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_MINIMIZE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveMaximizeVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_MAXIMIZE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveRestoreVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_RESTORE"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveSeparatorVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_SEPARATOR"; +[[maybe_unused]] inline constexpr const char kSysMenuRemoveCloseVar[] = "FRAMELESSHELPER_SYSTEM_MENU_REMOVE_CLOSE"; enum class Option : quint8 { diff --git a/src/core/utils_win.cpp b/src/core/utils_win.cpp index 2622bd47..86e02e66 100644 --- a/src/core/utils_win.cpp +++ b/src/core/utils_win.cpp @@ -135,6 +135,9 @@ FRAMELESSHELPER_STRING_CONSTANT(EnableMenuItem) FRAMELESSHELPER_STRING_CONSTANT(SetMenuDefaultItem) FRAMELESSHELPER_STRING_CONSTANT(HiliteMenuItem) FRAMELESSHELPER_STRING_CONSTANT(TrackPopupMenu) +FRAMELESSHELPER_STRING_CONSTANT(DrawMenuBar) +FRAMELESSHELPER_STRING_CONSTANT(DeleteMenu) +FRAMELESSHELPER_STRING_CONSTANT(RemoveMenu) FRAMELESSHELPER_STRING_CONSTANT(ClientToScreen) FRAMELESSHELPER_STRING_CONSTANT(DwmEnableBlurBehindWindow) FRAMELESSHELPER_STRING_CONSTANT(SetWindowCompositionAttribute) @@ -1315,49 +1318,113 @@ bool Utils::showSystemMenu(const WId windowId, const QPoint &pos, const bool sel } // Tweak the menu items according to the current window status and user settings. + const bool disableClose = data->callbacks->getProperty(kSysMenuDisableCloseVar, false).toBool(); const bool disableRestore = data->callbacks->getProperty(kSysMenuDisableRestoreVar, false).toBool(); const bool disableMinimize = data->callbacks->getProperty(kSysMenuDisableMinimizeVar, false).toBool(); const bool disableMaximize = data->callbacks->getProperty(kSysMenuDisableMaximizeVar, false).toBool(); + const bool disableSize = data->callbacks->getProperty(kSysMenuDisableSizeVar, false).toBool(); + const bool disableMove = data->callbacks->getProperty(kSysMenuDisableMoveVar, false).toBool(); + const bool removeClose = data->callbacks->getProperty(kSysMenuRemoveCloseVar, false).toBool(); + const bool removeSeparator = data->callbacks->getProperty(kSysMenuRemoveSeparatorVar, false).toBool(); + const bool removeRestore = data->callbacks->getProperty(kSysMenuRemoveRestoreVar, false).toBool(); + const bool removeMinimize = data->callbacks->getProperty(kSysMenuRemoveMinimizeVar, false).toBool(); + const bool removeMaximize = data->callbacks->getProperty(kSysMenuRemoveMaximizeVar, false).toBool(); + const bool removeSize = data->callbacks->getProperty(kSysMenuRemoveSizeVar, false).toBool(); + const bool removeMove = data->callbacks->getProperty(kSysMenuRemoveMoveVar, false).toBool(); const bool maxOrFull = (IsMaximized(hWnd) || isFullScreen(windowId)); const bool fixedSize = data->callbacks->isWindowFixedSize(); - ::EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize && !disableRestore) ? MFS_ENABLED : MFS_DISABLED))); - // The first menu item should be selected by default if the menu is brought - // up by keyboard. I don't know how to pre-select a menu item but it seems - // highlight can do the job. However, there's an annoying issue if we do - // this manually: the highlighted menu item is really only highlighted, - // not selected, so even if the mouse cursor hovers on other menu items - // or the user navigates to other menu items through keyboard, the original - // highlight bar will not move accordingly, the OS will generate another - // highlight bar to indicate the current selected menu item, which will make - // the menu look kind of weird. Currently I don't know how to fix this issue. - ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE))); - ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | (!maxOrFull ? MFS_ENABLED : MFS_DISABLED))); - ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize && !(disableMinimize || disableMaximize)) ? MFS_ENABLED : MFS_DISABLED))); - ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | (disableMinimize ? MFS_DISABLED : MFS_ENABLED))); - ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize && !disableMaximize) ? MFS_ENABLED : MFS_DISABLED))); - ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | MFS_ENABLED)); + if (removeClose) { + if (::DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_CLOSE, (MF_BYCOMMAND | (disableClose ? MFS_DISABLED : MFS_ENABLED))); + } + if (removeSeparator) { + if (::DeleteMenu(hMenu, SC_SEPARATOR, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } + if (removeMaximize) { + if (::DeleteMenu(hMenu, SC_MAXIMIZE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_MAXIMIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize && !disableMaximize) ? MFS_ENABLED : MFS_DISABLED))); + } + if (removeRestore) { + if (::DeleteMenu(hMenu, SC_RESTORE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_RESTORE, (MF_BYCOMMAND | ((maxOrFull && !fixedSize && !disableRestore) ? MFS_ENABLED : MFS_DISABLED))); + // The first menu item should be selected by default if the menu is brought + // up by keyboard. I don't know how to pre-select a menu item but it seems + // highlight can do the job. However, there's an annoying issue if we do + // this manually: the highlighted menu item is really only highlighted, + // not selected, so even if the mouse cursor hovers on other menu items + // or the user navigates to other menu items through keyboard, the original + // highlight bar will not move accordingly, the OS will generate another + // highlight bar to indicate the current selected menu item, which will make + // the menu look kind of weird. Currently I don't know how to fix this issue. + ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | (selectFirstEntry ? MFS_HILITE : MFS_UNHILITE))); + } + if (removeMinimize) { + if (::DeleteMenu(hMenu, SC_MINIMIZE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_MINIMIZE, (MF_BYCOMMAND | (disableMinimize ? MFS_DISABLED : MFS_ENABLED))); + } + if (removeSize) { + if (::DeleteMenu(hMenu, SC_SIZE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_SIZE, (MF_BYCOMMAND | ((!maxOrFull && !fixedSize && !(disableSize || disableMinimize || disableMaximize)) ? MFS_ENABLED : MFS_DISABLED))); + } + if (removeMove) { + if (::DeleteMenu(hMenu, SC_MOVE, MF_BYCOMMAND) == FALSE) { + //WARNING << getSystemErrorMessage(kDeleteMenu); + } + } else { + ::EnableMenuItem(hMenu, SC_MOVE, (MF_BYCOMMAND | ((disableMove || maxOrFull) ? MFS_DISABLED : MFS_ENABLED))); + } // The default menu item will appear in bold font. There can only be one default // menu item per menu at most. Set the item ID to "UINT_MAX" (or simply "-1") // can clear the default item for the given menu. - UINT defaultItemId = UINT_MAX; + std::optional defaultItemId = std::nullopt; if (WindowsVersionHelper::isWin11OrGreater()) { if (maxOrFull) { - defaultItemId = SC_RESTORE; + if (!removeRestore) { + defaultItemId = SC_RESTORE; + } } else { - defaultItemId = SC_MAXIMIZE; + if (!removeMaximize) { + defaultItemId = SC_MAXIMIZE; + } } - } else { + } + if (!(defaultItemId.has_value() || removeClose)) { defaultItemId = SC_CLOSE; } - ::SetMenuDefaultItem(hMenu, defaultItemId, FALSE); + if (::SetMenuDefaultItem(hMenu, defaultItemId.value_or(UINT_MAX), FALSE) == FALSE) { + WARNING << getSystemErrorMessage(kSetMenuDefaultItem); + } + + if (::DrawMenuBar(hWnd) == FALSE) { + WARNING << getSystemErrorMessage(kDrawMenuBar); + } // Popup the system menu at the required position. const int result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), pos.x(), pos.y(), 0, hWnd, nullptr); - // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep - // highlighting until we unhighlight it manually. - ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | MFS_UNHILITE)); + if (!removeRestore) { + // Unhighlight the first menu item after the popup menu is closed, otherwise it will keep + // highlighting until we unhighlight it manually. + ::HiliteMenuItem(hWnd, hMenu, SC_RESTORE, (MF_BYCOMMAND | MFS_UNHILITE)); + } if (result == FALSE) { // The user canceled the menu, no need to continue.