From 8e85110b5eba0e7af2ccc3536ec943b6ed3bf446 Mon Sep 17 00:00:00 2001 From: Don Ho Date: Mon, 5 Dec 2022 23:32:59 +0100 Subject: [PATCH] Make macro menu organizable with submenu This PR allows users to add one level of submenu manually for macro command in shortcuts.xml, in order to better organize macro menu commands. For doing it, `FolderName="my menu 1"` attribut should be added in node. Only 1 level of submenu is allowed. Here is an example: ```xml ``` Fix #5349, close #12605 --- PowerEditor/src/Notepad_plus.cpp | 29 ++-- PowerEditor/src/Parameters.cpp | 150 +++++++++++++++++- PowerEditor/src/Parameters.h | 31 +++- .../WinControls/ContextMenu/ContextMenu.cpp | 1 + .../src/WinControls/Grid/ShortcutMapper.cpp | 64 +++++--- .../src/WinControls/shortcut/shortcut.cpp | 2 +- 6 files changed, 233 insertions(+), 44 deletions(-) diff --git a/PowerEditor/src/Notepad_plus.cpp b/PowerEditor/src/Notepad_plus.cpp index d0b8d52db..49a06d742 100644 --- a/PowerEditor/src/Notepad_plus.cpp +++ b/PowerEditor/src/Notepad_plus.cpp @@ -190,6 +190,8 @@ Notepad_plus::~Notepad_plus() delete _pFileBrowser; } + + LRESULT Notepad_plus::init(HWND hwnd) { NppParameters& nppParam = NppParameters::getInstance(); @@ -461,21 +463,15 @@ LRESULT Notepad_plus::init(HWND hwnd) setupColorSampleBitmapsOnMainMenuItems(); // Macro Menu - std::vector & macros = nppParam.getMacroList(); HMENU hMacroMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_MACRO); size_t const posBase = 6; - size_t nbMacro = macros.size(); - if (nbMacro >= 1) + size_t nbTopLevelItem = nppParam.getMacroMenuItems().getTopLevelItemNumber(); + if (nbTopLevelItem >= 1) ::InsertMenu(hMacroMenu, posBase - 1, MF_BYPOSITION, static_cast(-1), 0); - for (size_t i = 0; i < nbMacro; ++i) - ::InsertMenu(hMacroMenu, static_cast(posBase + i), MF_BYPOSITION, ID_MACRO + i, macros[i].toMenuItemString().c_str()); + DynamicMenu& macroMenuItems = nppParam.getMacroMenuItems(); + macroMenuItems.attach(hMacroMenu, posBase); - if (nbMacro >= 1) - { - ::InsertMenu(hMacroMenu, static_cast(posBase + nbMacro + 1), MF_BYPOSITION, static_cast(-1), 0); - ::InsertMenu(hMacroMenu, static_cast(posBase + nbMacro + 2), MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_MACRO, TEXT("Modify Shortcut/Delete Macro...")); - } // Run Menu std::vector & userCommands = nppParam.getUserCommandList(); @@ -5188,7 +5184,9 @@ bool Notepad_plus::addCurrentMacro() vector & theMacros = nppParams.getMacroList(); int nbMacro = static_cast(theMacros.size()); - + + DynamicMenu& macroMenu = nppParams.getMacroMenuItems(); + int nbTopLevelItem = macroMenu.getTopLevelItemNumber(); int cmdID = ID_MACRO + nbMacro; MacroShortcut ms(Shortcut(), _macro, cmdID); ms.init(_pPublicInterface->getHinst(), _pPublicInterface->getHSelf()); @@ -5197,22 +5195,23 @@ bool Notepad_plus::addCurrentMacro() { HMENU hMacroMenu = ::GetSubMenu(_mainMenuHandle, MENUINDEX_MACRO); int const posBase = 6; //separator at index 5 - if (nbMacro == 0) + if (nbTopLevelItem == 0) { ::InsertMenu(hMacroMenu, posBase-1, MF_BYPOSITION, static_cast(-1), 0); //no separator yet, add one // Insert the separator and modify/delete command - ::InsertMenu(hMacroMenu, posBase + nbMacro + 1, MF_BYPOSITION, static_cast(-1), 0); + ::InsertMenu(hMacroMenu, posBase + nbTopLevelItem + 1, MF_BYPOSITION, static_cast(-1), 0); NativeLangSpeaker *pNativeLangSpeaker = nppParams.getNativeLangSpeaker(); generic_string nativeLangShortcutMapperMacro = pNativeLangSpeaker->getNativeLangMenuString(IDM_SETTING_SHORTCUT_MAPPER_MACRO); if (nativeLangShortcutMapperMacro == TEXT("")) nativeLangShortcutMapperMacro = TEXT("Modify Shortcut/Delete Macro..."); - ::InsertMenu(hMacroMenu, posBase + nbMacro + 2, MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_MACRO, nativeLangShortcutMapperMacro.c_str()); + ::InsertMenu(hMacroMenu, posBase + nbTopLevelItem + 2, MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_MACRO, nativeLangShortcutMapperMacro.c_str()); } theMacros.push_back(ms); - ::InsertMenu(hMacroMenu, posBase + nbMacro, MF_BYPOSITION, cmdID, ms.toMenuItemString().c_str()); + macroMenu.push_back(MenuItemUnit(cmdID, ms.getName())); + ::InsertMenu(hMacroMenu, posBase + nbTopLevelItem, MF_BYPOSITION, cmdID, ms.toMenuItemString().c_str()); _accelerator.updateShortcuts(); nppParams.setShortcutDirty(); return true; diff --git a/PowerEditor/src/Parameters.cpp b/PowerEditor/src/Parameters.cpp index 72115d991..fbb64738b 100644 --- a/PowerEditor/src/Parameters.cpp +++ b/PowerEditor/src/Parameters.cpp @@ -711,6 +711,135 @@ generic_string ThemeSwitcher::getThemeFromXmlFileName(const TCHAR *xmlFullPath) return fn; } +int DynamicMenu::getTopLevelItemNumber() const +{ + int nb = 0; + generic_string previousFolderName; + for (const MenuItemUnit& i : _menuItems) + { + if (i._parentFolderName.empty()) + { + ++nb; + } + else + { + if (previousFolderName.empty()) + { + ++nb; + previousFolderName = i._parentFolderName; + } + else // previousFolderName is not empty + { + if (i._parentFolderName.empty()) + { + ++nb; + previousFolderName = i._parentFolderName; + } + else if (previousFolderName == i._parentFolderName) + { + // maintain the number and do nothinh + } + else + { + ++nb; + previousFolderName = i._parentFolderName; + } + } + } + } + + return nb; +} + +bool DynamicMenu::attach(HMENU hMenu, size_t posBase) +{ + if (!hMenu) return false; + + _hMenu = hMenu; + _posBase = posBase; + + return createMenu(); +} + +bool DynamicMenu::clearMenu() const +{ + if (!_hMenu) return false; + + int nbTopItem = getTopLevelItemNumber(); + for (int i = nbTopItem + 1; i >= 0 ; --i) + { + ::DeleteMenu(_hMenu, static_cast(_posBase) + i, MF_BYPOSITION); + } + + return true; +} + +bool DynamicMenu::createMenu() const +{ + if (!_hMenu) return false; + + bool lastIsSep = false; + HMENU hParentFolder = NULL; + generic_string currentParentFolderStr; + int j = 0; + + size_t nb = _menuItems.size(); + size_t i = 0; + for (; i < nb; ++i) + { + const MenuItemUnit& item = _menuItems[i]; + if (item._parentFolderName.empty()) + { + currentParentFolderStr.clear(); + hParentFolder = NULL; + j = 0; + } + else + { + if (item._parentFolderName != currentParentFolderStr) + { + currentParentFolderStr = item._parentFolderName; + hParentFolder = ::CreateMenu(); + j = 0; + + ::InsertMenu(_hMenu, static_cast(_posBase + i), MF_BYPOSITION | MF_POPUP, (UINT_PTR)hParentFolder, currentParentFolderStr.c_str()); + } + } + + unsigned int flag = MF_BYPOSITION | ((item._cmdID == 0) ? MF_SEPARATOR : 0); + if (hParentFolder) + { + ::InsertMenu(hParentFolder, j++, flag, item._cmdID, item._itemName.c_str()); + lastIsSep = false; + } + else if ((i == 0 || i == _menuItems.size() - 1) && item._cmdID == 0) + { + lastIsSep = true; + } + else if (item._cmdID != 0) + { + ::InsertMenu(_hMenu, static_cast(_posBase + i), flag, item._cmdID, item._itemName.c_str()); + lastIsSep = false; + } + else if (item._cmdID == 0 && !lastIsSep) + { + ::InsertMenu(_hMenu, static_cast(_posBase + i), flag, item._cmdID, item._itemName.c_str()); + lastIsSep = true; + } + else // last item is separator and current item is separator + { + lastIsSep = true; + } + } + + if (nb > 0) + { + ::InsertMenu(_hMenu, static_cast(_posBase + i), MF_BYPOSITION | MF_SEPARATOR, 0, nullptr); + ::InsertMenu(_hMenu, static_cast(_posBase + i + 2), MF_BYCOMMAND, IDM_SETTING_SHORTCUT_MAPPER_MACRO, TEXT("Modify Shortcut/Delete Macro...")); + } + + return true; +} winVer NppParameters::getWindowsVersion() { @@ -2611,13 +2740,18 @@ void NppParameters::feedMacros(TiXmlNode *node) childNode = childNode->NextSibling(TEXT("Macro")) ) { Shortcut sc; - if (getShortcuts(childNode, sc))// && sc.isValid()) + generic_string fdnm; + if (getShortcuts(childNode, sc, &fdnm)) { Macro macro; getActions(childNode, macro); int cmdID = ID_MACRO + static_cast(_macros.size()); + MacroShortcut ms(sc, macro, cmdID); _macros.push_back(ms); + + MenuItemUnit miu(cmdID, sc.getName(), fdnm); + _macroMenuItems.push_back(miu); } } } @@ -2797,7 +2931,7 @@ bool NppParameters::feedBlacklist(TiXmlNode *node) return true; } -bool NppParameters::getShortcuts(TiXmlNode *node, Shortcut & sc) +bool NppParameters::getShortcuts(TiXmlNode *node, Shortcut & sc, generic_string* folderName) { if (!node) return false; @@ -2825,6 +2959,12 @@ bool NppParameters::getShortcuts(TiXmlNode *node, Shortcut & sc) if (!keyStr) return false; + if (folderName) + { + const TCHAR* fn = (node->ToElement())->Attribute(TEXT("FolderName")); + *folderName = fn ? fn : L""; + } + sc = Shortcut(name, isCtrl, isAlt, isShift, static_cast(key)); return true; } @@ -3221,7 +3361,7 @@ void NppParameters::insertCmd(TiXmlNode *shortcutsRoot, const CommandShortcut & } -void NppParameters::insertMacro(TiXmlNode *macrosRoot, const MacroShortcut & macro) +void NppParameters::insertMacro(TiXmlNode *macrosRoot, const MacroShortcut & macro, const generic_string& folderName) { const KeyCombo & key = macro.getKeyCombo(); TiXmlNode *macroRoot = macrosRoot->InsertEndChild(TiXmlElement(TEXT("Macro"))); @@ -3230,6 +3370,8 @@ void NppParameters::insertMacro(TiXmlNode *macrosRoot, const MacroShortcut & mac macroRoot->ToElement()->SetAttribute(TEXT("Alt"), key._isAlt?TEXT("yes"):TEXT("no")); macroRoot->ToElement()->SetAttribute(TEXT("Shift"), key._isShift?TEXT("yes"):TEXT("no")); macroRoot->ToElement()->SetAttribute(TEXT("Key"), key._key); + if (!folderName.empty()) + macroRoot->ToElement()->SetAttribute(TEXT("FolderName"), folderName); for (size_t i = 0, len = macro._macro.size(); i < len ; ++i) { @@ -3442,7 +3584,7 @@ void NppParameters::writeShortcuts() for (size_t i = 0, len = _macros.size(); i < len ; ++i) { - insertMacro(macrosRoot, _macros[i]); + insertMacro(macrosRoot, _macros[i], _macroMenuItems.getItemFromIndex(i)._parentFolderName); } TiXmlNode *userCmdRoot = root->FirstChild(TEXT("UserDefinedCommands")); diff --git a/PowerEditor/src/Parameters.h b/PowerEditor/src/Parameters.h index 21849594c..86a24da3c 100644 --- a/PowerEditor/src/Parameters.h +++ b/PowerEditor/src/Parameters.h @@ -1331,6 +1331,31 @@ const int NB_LANG = 100; const int RECENTFILES_SHOWFULLPATH = -1; const int RECENTFILES_SHOWONLYFILENAME = 0; +class DynamicMenu final +{ +public: + bool attach(HMENU hMenu, size_t posBase); + bool createMenu() const; + bool clearMenu() const; + int getTopLevelItemNumber() const; + void push_back(const MenuItemUnit& m) { + _menuItems.push_back(m); + }; + + MenuItemUnit& getItemFromIndex(size_t i) { + return _menuItems[i]; + }; + + void erase(size_t i) { + _menuItems.erase(_menuItems.begin() + i); + } + +private: + std::vector _menuItems; + HMENU _hMenu = nullptr; + size_t _posBase = 0; +}; + class NppParameters final { private: @@ -1556,6 +1581,7 @@ public: std::vector& getContextMenuItems() { return _contextMenuItems; }; std::vector& getTabContextMenuItems() { return _tabContextMenuItems; }; + DynamicMenu& getMacroMenuItems() { return _macroMenuItems; }; bool hasCustomContextMenu() const {return !_contextMenuItems.empty();}; bool hasCustomTabContextMenu() const {return !_tabContextMenuItems.empty();}; @@ -1832,6 +1858,7 @@ private: std::vector _contextMenuItems; std::vector _tabContextMenuItems; + DynamicMenu _macroMenuItems; Session _session; generic_string _shortcutsPath; @@ -1936,12 +1963,12 @@ private: bool feedBlacklist(TiXmlNode *node); void getActions(TiXmlNode *node, Macro & macro); - bool getShortcuts(TiXmlNode *node, Shortcut & sc); + bool getShortcuts(TiXmlNode *node, Shortcut & sc, generic_string* folderName = nullptr); void writeStyle2Element(const Style & style2Write, Style & style2Sync, TiXmlElement *element); void insertUserLang2Tree(TiXmlNode *node, UserLangContainer *userLang); void insertCmd(TiXmlNode *cmdRoot, const CommandShortcut & cmd); - void insertMacro(TiXmlNode *macrosRoot, const MacroShortcut & macro); + void insertMacro(TiXmlNode *macrosRoot, const MacroShortcut & macro, const generic_string& folderName); void insertUserCmd(TiXmlNode *userCmdRoot, const UserCommand & userCmd); void insertScintKey(TiXmlNode *scintKeyRoot, const ScintillaKeyMap & scintKeyMap); void insertPluginCmd(TiXmlNode *pluginCmdRoot, const PluginCmdShortcut & pluginCmd); diff --git a/PowerEditor/src/WinControls/ContextMenu/ContextMenu.cpp b/PowerEditor/src/WinControls/ContextMenu/ContextMenu.cpp index 32c06d426..9cef76e87 100644 --- a/PowerEditor/src/WinControls/ContextMenu/ContextMenu.cpp +++ b/PowerEditor/src/WinControls/ContextMenu/ContextMenu.cpp @@ -101,6 +101,7 @@ void ContextMenu::create(HWND hParent, const std::vector & menuIte { lastIsSep = true; } + if (mainMenuHandle) { UINT s = ::GetMenuState(mainMenuHandle, item._cmdID, MF_BYCOMMAND); diff --git a/PowerEditor/src/WinControls/Grid/ShortcutMapper.cpp b/PowerEditor/src/WinControls/Grid/ShortcutMapper.cpp index 23a4d82a3..8a04c3f5a 100644 --- a/PowerEditor/src/WinControls/Grid/ShortcutMapper.cpp +++ b/PowerEditor/src/WinControls/Grid/ShortcutMapper.cpp @@ -951,7 +951,6 @@ intptr_t CALLBACK ShortcutMapper::run_dlgProc(UINT message, WPARAM wParam, LPARA int32_t posBase = 0; size_t nbElem = 0; HMENU hMenu = NULL; - int modifCmd = IDM_SETTING_SHORTCUT_MAPPER_RUN; switch(_currentState) { case STATE_MENU: @@ -967,6 +966,8 @@ intptr_t CALLBACK ShortcutMapper::run_dlgProc(UINT message, WPARAM wParam, LPARA vector::iterator it = theMacros.begin(); theMacros.erase(it + shortcutIndex); + + //save the current view _lastHomeRow[_currentState] = _babygrid.getHomeRow(); _lastCursorRow[_currentState] = _babygrid.getSelectedRow(); @@ -979,18 +980,38 @@ intptr_t CALLBACK ShortcutMapper::run_dlgProc(UINT message, WPARAM wParam, LPARA fillOutBabyGrid(); - // preparing to remove from menu - posBase = 6; - nbElem = theMacros.size(); - HMENU m = reinterpret_cast(::SendMessage(_hParent, NPPM_INTERNAL_GETMENU, 0, 0)); - hMenu = ::GetSubMenu(m, MENUINDEX_MACRO); + // clear all menu + DynamicMenu& macroMenu = nppParam.getMacroMenuItems(); + macroMenu.clearMenu(); - modifCmd = IDM_SETTING_SHORTCUT_MAPPER_MACRO; + // Erase the menu item + macroMenu.erase(shortcutIndex); + + + nbElem = theMacros.size(); for (size_t i = shortcutIndex; i < nbElem; ++i) //lower the IDs of the remaining items so there are no gaps { MacroShortcut ms = theMacros[i]; ms.setID(ms.getID() - 1); //shift all IDs theMacros[i] = ms; + + // Ajust menu items + MenuItemUnit& miu = macroMenu.getItemFromIndex(i); + miu._cmdID -= 1; //shift all IDs + } + // create from scratch according the new menu items structure + macroMenu.createMenu(); + + HMENU m = reinterpret_cast(::SendMessage(_hParent, NPPM_INTERNAL_GETMENU, 0, 0)); + hMenu = ::GetSubMenu(m, MENUINDEX_MACRO); + posBase = 6; + if (nbElem == 0) + { + ::RemoveMenu(hMenu, IDM_SETTING_SHORTCUT_MAPPER_MACRO, MF_BYCOMMAND); + + //remove separator + ::RemoveMenu(hMenu, posBase - 1, MF_BYPOSITION); + ::RemoveMenu(hMenu, posBase - 1, MF_BYPOSITION); } } break; @@ -1019,13 +1040,26 @@ intptr_t CALLBACK ShortcutMapper::run_dlgProc(UINT message, WPARAM wParam, LPARA HMENU m = reinterpret_cast(::SendMessage(_hParent, NPPM_INTERNAL_GETMENU, 0, 0)); hMenu = ::GetSubMenu(m, MENUINDEX_RUN); - modifCmd = IDM_SETTING_SHORTCUT_MAPPER_RUN; for (size_t i = shortcutIndex; i < nbElem; ++i) //lower the IDs of the remaining items so there are no gaps { UserCommand uc = theUserCmds[i]; uc.setID(uc.getID() - 1); //shift all IDs theUserCmds[i] = uc; } + + if (!hMenu) return FALSE; + + // All menu items are shifted up. So we delete the last item + ::RemoveMenu(hMenu, posBase + static_cast(nbElem), MF_BYPOSITION); + + if (nbElem == 0) + { + ::RemoveMenu(hMenu, IDM_SETTING_SHORTCUT_MAPPER_RUN, MF_BYCOMMAND); + + //remove separator + ::RemoveMenu(hMenu, posBase - 1, MF_BYPOSITION); + ::RemoveMenu(hMenu, posBase - 1, MF_BYPOSITION); + } } break; } @@ -1033,20 +1067,6 @@ intptr_t CALLBACK ShortcutMapper::run_dlgProc(UINT message, WPARAM wParam, LPARA // updateShortcuts() will update all menu item - the menu items will be shifted nppParam.getAccelerator()->updateShortcuts(); nppParam.setShortcutDirty(); - - if (!hMenu) return FALSE; - - // All menu items are shifted up. So we delete the last item - ::RemoveMenu(hMenu, posBase + static_cast(nbElem), MF_BYPOSITION); - - if (nbElem == 0) - { - ::RemoveMenu(hMenu, modifCmd, MF_BYCOMMAND); - - //remove separator - ::RemoveMenu(hMenu, posBase-1, MF_BYPOSITION); - ::RemoveMenu(hMenu, posBase-1, MF_BYPOSITION); - } } return TRUE; } diff --git a/PowerEditor/src/WinControls/shortcut/shortcut.cpp b/PowerEditor/src/WinControls/shortcut/shortcut.cpp index 536403e38..e48d5ea6c 100644 --- a/PowerEditor/src/WinControls/shortcut/shortcut.cpp +++ b/PowerEditor/src/WinControls/shortcut/shortcut.cpp @@ -546,7 +546,7 @@ void Accelerator::updateShortcuts() size_t nbPluginCmd = pluginCommands.size(); delete [] _pAccelArray; - _pAccelArray = new ACCEL[nbMenu + nbMacro+nbUserCmd + nbPluginCmd]; + _pAccelArray = new ACCEL[nbMenu + nbMacro + nbUserCmd + nbPluginCmd]; vector incrFindAcc; vector findReplaceAcc; int offset = 0;