From: <sv...@op...> - 2024-11-15 21:53:03
|
Author: sagamusix Date: Fri Nov 15 22:52:54 2024 New Revision: 22192 URL: https://source.openmpt.org/browse/openmpt/?op=revision&rev=22192 Log: [Imp] Redesign keyboard config dialog. The list of shortcuts now shows currently assigned key choices so shortcuts no longer have to be clicked individually to figure out which keys they are assigned to. The error log has been replaced with a display for the currently selected key choice whether it conflicts or not. "Find by hotkey" is no longer a keyboard focus trap. Categories in keyboard contexts now have names in their headers. etc... Modified: trunk/OpenMPT/mptrack/CListCtrl.h trunk/OpenMPT/mptrack/CommandSet.cpp trunk/OpenMPT/mptrack/CommandSet.h trunk/OpenMPT/mptrack/InputHandler.cpp trunk/OpenMPT/mptrack/InputHandler.h trunk/OpenMPT/mptrack/KeyConfigDlg.cpp trunk/OpenMPT/mptrack/KeyConfigDlg.h trunk/OpenMPT/mptrack/WelcomeDialog.cpp trunk/OpenMPT/mptrack/mptrack.rc trunk/OpenMPT/mptrack/resource.h Modified: trunk/OpenMPT/mptrack/CListCtrl.h ============================================================================== --- trunk/OpenMPT/mptrack/CListCtrl.h Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/CListCtrl.h Fri Nov 15 22:52:54 2024 (r22192) @@ -25,6 +25,7 @@ int width = 0; UINT mask = 0; }; + void SetHeaders(const mpt::span<const Header> &header) { for(int i = 0; i < static_cast<int>(header.size()); i++) @@ -36,6 +37,15 @@ } } + void SetColumnWidths(const mpt::span<const Header> &header) + { + for(int i = 0; i < static_cast<int>(header.size()); i++) + { + if(int width = header[i].width; width > 0) + SetColumnWidth(i, HighDPISupport::ScalePixels(width, m_hWnd)); + } + } + void SetItemDataPtr(int item, void *value) { SetItemData(item, reinterpret_cast<DWORD_PTR>(value)); Modified: trunk/OpenMPT/mptrack/CommandSet.cpp ============================================================================== --- trunk/OpenMPT/mptrack/CommandSet.cpp Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/CommandSet.cpp Fri Nov 15 22:52:54 2024 (r22192) @@ -28,6 +28,11 @@ namespace { +constexpr CommandID ModifierCommands[] = +{ + kcSelect, kcCopySelect, kcChordModifier, kcSetSpacing +}; + constexpr std::tuple<InputTargetContext, CommandID, CommandID> NoteContexts[] = { {kCtxViewPatternsNote, kcVPStartNotes, kcVPStartNoteStops}, @@ -1465,42 +1470,39 @@ // Avoid duplicate if(mpt::contains(kcList, kc)) - { - return CString(); - } + return CString{}; // Check that this keycombination isn't already assigned (in this context), except for dummy keys CString report; if(auto conflictCmd = IsConflicting(kc, cmd, checkEventConflict); conflictCmd.first != kcNull) { - if (!overwrite) + if(!overwrite) { - return CString(); + return CString{}; } else { - if (IsCrossContextConflict(kc, conflictCmd.second)) - { - report += _T("The following commands may conflict:\r\n >") + GetCommandText(conflictCmd.first) + _T(" in ") + conflictCmd.second.GetContextText() + _T("\r\n >") + GetCommandText(cmd) + _T(" in ") + kc.GetContextText() + _T("\r\n\r\n"); - LOG_COMMANDSET(mpt::ToUnicode(report)); - } else - { - //if(!TrackerSettings::Instance().MiscAllowMultipleCommandsPerKey) - // Remove(conflictCmd.second, conflictCmd.first); - report += _T("The following commands in same context share the same key combination:\r\n >") + GetCommandText(conflictCmd.first) + _T(" in ") + conflictCmd.second.GetContextText() + _T("\r\n\r\n"); - LOG_COMMANDSET(mpt::ToUnicode(report)); - } + report = FormatConflict(kc, conflictCmd.first, conflictCmd.second); + LOG_COMMANDSET(mpt::ToUnicode(report)); } } kcList.insert((pos < 0) ? kcList.end() : (kcList.begin() + pos), kc); //enfore rules on CommandSet - report += EnforceAll(kc, cmd, true); + EnforceAll(kc, cmd, true); return report; } -std::pair<CommandID, KeyCombination> CCommandSet::IsConflicting(KeyCombination kc, CommandID cmd, bool checkEventConflict) const +CString CCommandSet::FormatConflict(KeyCombination kc, CommandID conflictCommand, KeyCombination conflictCombination) const +{ + if(IsCrossContextConflict(kc, conflictCombination)) + return _T("May conflict with ") + GetCommandText(conflictCommand) + _T(" in ") + conflictCombination.GetContextText(); + else + return _T("Conflicts with ") + GetCommandText(conflictCommand) + _T(" in same context"); +} + +std::pair<CommandID, KeyCombination> CCommandSet::IsConflicting(KeyCombination kc, CommandID cmd, bool checkEventConflict, bool checkSameCommand) const { if(m_commands[cmd].IsDummy()) // no need to search if we are adding a dummy key return {kcNull, KeyCombination()}; @@ -1511,7 +1513,7 @@ // such conflicts are errors. Cross-context conflicts only emit warnings. for(int curCmd = kcFirst; curCmd < kcNumCommands; curCmd++) { - if(m_commands[curCmd].IsDummy()) + if(m_commands[curCmd].IsDummy() || (!checkSameCommand && cmd == curCmd)) continue; for(auto &curKc : m_commands[curCmd].kcList) @@ -1531,19 +1533,18 @@ } -CString CCommandSet::Remove(int pos, CommandID cmd) +void CCommandSet::Remove(int pos, CommandID cmd) { - if (pos>=0 && (size_t)pos<m_commands[cmd].kcList.size()) + if(pos >= 0 && static_cast<size_t>(pos) < m_commands[cmd].kcList.size()) { - return Remove(m_commands[cmd].kcList[pos], cmd); + Remove(m_commands[cmd].kcList[pos], cmd); } LOG_COMMANDSET(U_("Failed to remove a key: keychoice out of range.")); - return _T(""); } -CString CCommandSet::Remove(KeyCombination kc, CommandID cmd) +void CCommandSet::Remove(KeyCombination kc, CommandID cmd) { auto &kcList = m_commands[cmd].kcList; auto index = std::find(kcList.begin(), kcList.end(), kc); @@ -1551,23 +1552,20 @@ { kcList.erase(index); LOG_COMMANDSET(U_("Removed a key")); - return EnforceAll(kc, cmd, false); + EnforceAll(kc, cmd, false); } else { LOG_COMMANDSET(U_("Failed to remove a key as it was not found")); - return CString(); } - } -CString CCommandSet::EnforceAll(KeyCombination inKc, CommandID inCmd, bool adding) +void CCommandSet::EnforceAll(KeyCombination inKc, CommandID inCmd, bool adding) { //World's biggest, most confusing method. :) //Needs refactoring. Maybe make lots of Rule subclasses, each with their own Enforce() method? KeyCombination curKc; // for looping through key combinations KeyCombination newKc; // for adding new key combinations - CString report; if(m_enforceRule[krAllowNavigationWithSelection]) { @@ -1996,15 +1994,13 @@ if (m_enforceRule[krCheckModifiers]) { // for all commands that must be modifiers - for (auto curCmd : { kcSelect, kcCopySelect, kcChordModifier, kcSetSpacing }) + for (auto curCmd : ModifierCommands) { //for all of this command's key combinations for (auto &kc : m_commands[curCmd].kcList) { - if ((!kc.Modifier()) || (kc.KeyCode()!=VK_SHIFT && kc.KeyCode()!=VK_CONTROL && kc.KeyCode()!=VK_MENU && kc.KeyCode()!=0 && - kc.KeyCode()!=VK_LWIN && kc.KeyCode()!=VK_RWIN )) // Feature: use Windows keys as modifier keys + if(!kc.IsModifierCombination()) { - report += _T("Error! ") + GetCommandText((CommandID)curCmd) + _T(" must be a modifier (shift/ctrl/alt), but is currently ") + inKc.GetKeyText() + _T("\r\n"); //replace with dummy kc.Modifier(ModShift); kc.KeyCode(0); @@ -2057,7 +2053,6 @@ } } */ - return report; } @@ -2117,10 +2112,10 @@ } -void CCommandSet::Copy(const CCommandSet *source) +void CCommandSet::Copy(const CCommandSet &source) { - m_oldSpecs = source->m_oldSpecs; - std::copy(std::begin(source->m_commands), std::end(source->m_commands), std::begin(m_commands)); + m_oldSpecs = source.m_oldSpecs; + std::copy(std::begin(source.m_commands), std::end(source.m_commands), std::begin(m_commands)); } @@ -2517,12 +2512,32 @@ } +bool KeyCombination::IsModifierCombination() const +{ + return Modifier() && + (KeyCode() == VK_SHIFT || KeyCode() == VK_CONTROL || KeyCode() == VK_MENU || KeyCode() == 0 + || KeyCode() == VK_LWIN || KeyCode() == VK_RWIN); // Feature: use Windows keys as modifier keys + +} + + CString CCommandSet::GetKeyTextFromCommand(CommandID c, UINT key) const { - if (key < m_commands[c].kcList.size()) + if(key < m_commands[c].kcList.size()) return m_commands[c].kcList[0].GetKeyText(); - else + if(key != uint32_max) return CString(); + CString keys; + bool addSeparator = false; + for(auto &item : m_commands[c].kcList) + { + if(addSeparator) + keys += _T("; "); + else + addSeparator = true; + keys += item.GetKeyText(); + } + return keys; } @@ -2687,4 +2702,11 @@ return m_isParentContext[kc1.Context()][kc2.Context()] || m_isParentContext[kc2.Context()][kc1.Context()]; } + +bool CCommandSet::MustBeModifierKey(CommandID id) +{ + return mpt::contains(ModifierCommands, id); +} + + OPENMPT_NAMESPACE_END Modified: trunk/OpenMPT/mptrack/CommandSet.h ============================================================================== --- trunk/OpenMPT/mptrack/CommandSet.h Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/CommandSet.h Fri Nov 15 22:52:54 2024 (r22192) @@ -1158,6 +1158,9 @@ } LPARAM AsLPARAM() const { return AsUint32(); } + // True if key combination only consists of modifier keys + bool IsModifierCombination() const; + // Key combination to string static CString GetContextText(InputTargetContext ctx); CString GetContextText() const { return GetContextText(Context()); } @@ -1233,7 +1236,7 @@ //util void SetupCommands(); void SetupContextHierarchy(); - CString EnforceAll(KeyCombination kc, CommandID cmd, bool adding); + void EnforceAll(KeyCombination kc, CommandID cmd, bool adding); CommandID FindCmd(uint32 uid) const; bool KeyCombinationConflict(KeyCombination kc1, KeyCombination kc2, bool checkEventConflict = true) const; @@ -1245,10 +1248,10 @@ // Population CString Add(KeyCombination kc, CommandID cmd, bool overwrite, int pos = -1, bool checkEventConflict = true); - CString Remove(KeyCombination kc, CommandID cmd); - CString Remove(int pos, CommandID cmd); + void Remove(KeyCombination kc, CommandID cmd); + void Remove(int pos, CommandID cmd); - std::pair<CommandID, KeyCombination> IsConflicting(KeyCombination kc, CommandID cmd, bool checkEventConflict = true) const; + std::pair<CommandID, KeyCombination> IsConflicting(KeyCombination kc, CommandID cmd, bool checkEventConflict = true, bool checkSameCommand = true) const; bool IsCrossContextConflict(KeyCombination kc1, KeyCombination kc2) const; // Tranformation @@ -1257,19 +1260,22 @@ // Communication KeyCombination GetKey(CommandID cmd, UINT key) const { return m_commands[cmd].kcList[key]; } - bool isHidden(UINT c) const { return m_commands[c].IsHidden(); } + bool IsHidden(UINT c) const { return m_commands[c].IsHidden(); } int GetKeyListSize(CommandID cmd) const { return (cmd != kcNull) ? static_cast<int>(m_commands[cmd].kcList.size()) : 0; } CString GetCommandText(CommandID cmd) const { return m_commands[cmd].Message; } - CString GetKeyTextFromCommand(CommandID c, UINT key) const; + CString GetKeyTextFromCommand(CommandID c, UINT key = uint32_max) const; + CString FormatConflict(KeyCombination kc, CommandID conflictCommand, KeyCombination conflictCombination) const; // Pululation ;) - void Copy(const CCommandSet *source); // Copy the contents of a commandset into this command set + void Copy(const CCommandSet &source); // Copy the contents of a commandset into this command set void GenKeyMap(KeyMap &km); // Generate a keymap from this command set bool SaveFile(const mpt::PathString &filename); bool LoadFile(const mpt::PathString &filename); bool LoadFile(std::istream &iStrm, const mpt::ustring &filenameDescription); void LoadDefaultKeymap(); + static bool MustBeModifierKey(CommandID id); + protected: const CModSpecifications *m_oldSpecs = nullptr; KeyCommand m_commands[kcNumCommands]; Modified: trunk/OpenMPT/mptrack/InputHandler.cpp ============================================================================== --- trunk/OpenMPT/mptrack/InputHandler.cpp Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/InputHandler.cpp Fri Nov 15 22:52:54 2024 (r22192) @@ -534,7 +534,7 @@ } -void CInputHandler::SetNewCommandSet(const CCommandSet *newSet) +void CInputHandler::SetNewCommandSet(const CCommandSet &newSet) { m_activeCommandSet->Copy(newSet); m_activeCommandSet->GenKeyMap(m_keyMap); Modified: trunk/OpenMPT/mptrack/InputHandler.h ============================================================================== --- trunk/OpenMPT/mptrack/InputHandler.h Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/InputHandler.h Fri Nov 15 22:52:54 2024 (r22192) @@ -68,7 +68,7 @@ CString GetKeyTextFromCommand(CommandID c, const TCHAR *prependText = nullptr) const; CString GetMenuText(UINT id) const; void UpdateMainMenu(); - void SetNewCommandSet(const CCommandSet *newSet); + void SetNewCommandSet(const CCommandSet &newSet); bool SetEffectLetters(const CModSpecifications &modSpecs); }; Modified: trunk/OpenMPT/mptrack/KeyConfigDlg.cpp ============================================================================== --- trunk/OpenMPT/mptrack/KeyConfigDlg.cpp Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/KeyConfigDlg.cpp Fri Nov 15 22:52:54 2024 (r22192) @@ -51,16 +51,14 @@ if(byte2 != 0) { SetKey(ModMidi, byte1); - if(!m_isDummy) - m_pOptKeyDlg->OnSetKeyChoice(); + m_pOptKeyDlg->OnSetKeyChoice(this); } break; case MIDIEvents::evNoteOn: case MIDIEvents::evNoteOff: SetKey(ModMidi, byte1 | 0x80); - if(!m_isDummy) - m_pOptKeyDlg->OnSetKeyChoice(); + m_pOptKeyDlg->OnSetKeyChoice(this); break; default: @@ -83,8 +81,8 @@ { //if a key has been released but custom edit box is empty, we have probably just //navigated into the box with TAB or SHIFT-TAB. No need to set keychoice. - if(code != 0 && !m_isDummy) - m_pOptKeyDlg->OnSetKeyChoice(); + if(code != 0) + m_pOptKeyDlg->OnSetKeyChoice(this); } } return CEdit::PreTranslateMessage(pMsg); @@ -118,6 +116,7 @@ //unlock the input handler CMainFrame::GetInputHandler()->Bypass(false); m_isFocussed = false; + m_pOptKeyDlg->OnCancelKeyChoice(this); } @@ -126,14 +125,21 @@ // //***************************************************************************************// -// Initialisation +static constexpr CListCtrlEx::Header KeyListHeaders[] = +{ + {_T("Shortcut"), 276, LVCFMT_LEFT}, + {_T("Assigned Keys"), 116, LVCFMT_LEFT}, +}; BEGIN_MESSAGE_MAP(COptionsKeyboard, CPropertyPage) - ON_NOTIFY(LVN_ITEMCHANGED, IDC_COMMAND_LIST, &COptionsKeyboard::OnCommandKeySelChanged) - + ON_WM_LBUTTONDBLCLK() + ON_WM_DESTROY() + ON_MESSAGE(WM_DPICHANGED_AFTERPARENT, &COptionsKeyboard::OnDPIChangedAfterParent) ON_LBN_SELCHANGE(IDC_CHOICECOMBO, &COptionsKeyboard::OnKeyChoiceSelect) ON_LBN_SELCHANGE(IDC_KEYCATEGORY, &COptionsKeyboard::OnCategorySelChanged) ON_EN_UPDATE(IDC_CHORDDETECTWAITTIME, &COptionsKeyboard::OnChordWaitTimeChanged) + ON_COMMAND(IDC_BUTTON3, &COptionsKeyboard::OnClearSearch) + ON_COMMAND(IDC_BUTTON2, &COptionsKeyboard::OnEnableFindHotKey) ON_COMMAND(IDC_BUTTON1, &COptionsKeyboard::OnListenForKeys) ON_COMMAND(IDC_DELETE, &COptionsKeyboard::OnDeleteKeyChoice) ON_COMMAND(IDC_RESTORE, &COptionsKeyboard::OnRestoreKeyChoice) @@ -144,15 +150,14 @@ ON_COMMAND(IDC_CHECKKEYUP, &COptionsKeyboard::OnCheck) ON_COMMAND(IDC_NOTESREPEAT, &COptionsKeyboard::OnNotesRepeat) ON_COMMAND(IDC_NONOTESREPEAT, &COptionsKeyboard::OnNoNotesRepeat) - ON_COMMAND(IDC_CLEARLOG, &COptionsKeyboard::OnClearLog) ON_COMMAND(IDC_RESTORE_KEYMAP, &COptionsKeyboard::OnRestoreDefaultKeymap) ON_EN_CHANGE(IDC_FIND, &COptionsKeyboard::OnSearchTermChanged) - ON_EN_CHANGE(IDC_FINDHOTKEY, &COptionsKeyboard::OnFindHotKey) ON_EN_SETFOCUS(IDC_FINDHOTKEY, &COptionsKeyboard::OnClearHotKey) - ON_WM_LBUTTONDBLCLK() - ON_WM_DESTROY() + ON_NOTIFY(LVN_ITEMCHANGED, IDC_COMMAND_LIST, &COptionsKeyboard::OnCommandKeySelChanged) + ON_NOTIFY(NM_DBLCLK, IDC_COMMAND_LIST, &COptionsKeyboard::OnListenForKeysFromList) END_MESSAGE_MAP() + void COptionsKeyboard::DoDataExchange(CDataExchange *pDX) { CPropertyPage::DoDataExchange(pDX); @@ -160,13 +165,14 @@ DDX_Control(pDX, IDC_COMMAND_LIST, m_lbnCommandKeys); DDX_Control(pDX, IDC_CHOICECOMBO, m_cmbKeyChoice); DDX_Control(pDX, IDC_CHORDDETECTWAITTIME, m_eChordWaitTime); - DDX_Control(pDX, IDC_KEYREPORT, m_eReport); DDX_Control(pDX, IDC_CUSTHOTKEY, m_eCustHotKey); DDX_Control(pDX, IDC_FINDHOTKEY, m_eFindHotKey); DDX_Control(pDX, IDC_CHECKKEYDOWN, m_bKeyDown); DDX_Control(pDX, IDC_CHECKKEYHOLD, m_bKeyHold); DDX_Control(pDX, IDC_CHECKKEYUP, m_bKeyUp); DDX_Control(pDX, IDC_FIND, m_eFind); + DDX_Control(pDX, IDC_STATIC1, m_warnIconCtl); + DDX_Control(pDX, IDC_KEYREPORT, m_warnText); } @@ -180,32 +186,30 @@ } - BOOL COptionsKeyboard::OnInitDialog() { CPropertyPage::OnInitDialog(); m_fullPathName = TrackerSettings::Instance().m_szKbdFile; m_localCmdSet = std::make_unique<CCommandSet>(); - m_localCmdSet->Copy(CMainFrame::GetInputHandler()->m_activeCommandSet.get()); + m_localCmdSet->Copy(*CMainFrame::GetInputHandler()->m_activeCommandSet); m_lbnCommandKeys.SetExtendedStyle(m_lbnCommandKeys.GetExtendedStyle() | LVS_EX_FULLROWSELECT); m_listGrouped = CListCtrlEx::EnableGroupView(m_lbnCommandKeys); + m_lbnCommandKeys.SetHeaders(KeyListHeaders); //Fill category combo and automatically selects first category DefineCommandCategories(); for(size_t c = 0; c < commandCategories.size(); c++) { - if(commandCategories[c].name && !commandCategories[c].commands.empty()) + if(commandCategories[c].name && !commandCategories[c].commandRanges.empty()) m_cmbCategory.SetItemData(m_cmbCategory.AddString(commandCategories[c].name), c); } m_cmbCategory.SetCurSel(0); UpdateDialog(); - m_eCustHotKey.SetParent(m_hWnd, IDC_CUSTHOTKEY, this); - m_eFindHotKey.SetParent(m_hWnd, IDC_FINDHOTKEY, this); - m_eReport.FmtLines(TRUE); - m_eReport.SetWindowText(_T("")); + m_eCustHotKey.SetOwner(*this); + m_eFindHotKey.SetOwner(*this); EnableKeyChoice(false); @@ -214,31 +218,38 @@ } -void CommandCategory::AddCommands(CommandID first, CommandID last, bool addSeparatorAtEnd) +LRESULT COptionsKeyboard::OnDPIChangedAfterParent(WPARAM, LPARAM) { - int count = last - first + 1, val = first; - commands.insert(commands.end(), count, kcNull); - std::generate(commands.end() - count, commands.end(), [&val] { return static_cast<CommandID>(val++); }); - if(addSeparatorAtEnd) - separators.push_back(last); + auto result = Default(); + + m_lbnCommandKeys.SetColumnWidths(KeyListHeaders); + + if(m_warnIcon) + { + DestroyIcon(m_warnIcon); + m_warnIcon = nullptr; + } + if(m_infoIcon) + { + DestroyIcon(m_infoIcon); + m_infoIcon = nullptr; + } + UpdateWarning(m_lastWarning); + + return result; } -// Filter commands: We only need user to see a select set off commands -// for each category void COptionsKeyboard::DefineCommandCategories() { { - CommandCategory newCat(_T("Global keys"), kCtxAllContexts); - - newCat.AddCommands(kcStartFile, kcEndFile, true); - newCat.AddCommands(kcStartPlayCommands, kcEndPlayCommands, true); - newCat.AddCommands(kcStartEditCommands, kcEndEditCommands, true); - newCat.AddCommands(kcStartView, kcEndView, true); - newCat.AddCommands(kcStartMisc, kcEndMisc, true); - newCat.commands.push_back(kcDummyShortcut); - - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T("Global"), kCtxAllContexts).commandRanges; + commands.emplace_back(kcStartFile, kcEndFile, _T("File")); + commands.emplace_back(kcStartPlayCommands, kcEndPlayCommands, _T("Player")); + commands.emplace_back(kcStartEditCommands, kcEndEditCommands, _T("Edit")); + commands.emplace_back(kcStartView, kcEndView, _T("View")); + commands.emplace_back(kcStartMisc, kcEndMisc, _T("Miscellaneous")); + commands.emplace_back(kcDummyShortcut, kcDummyShortcut, _T("")); } commandCategories.emplace_back(_T(" General [Top]"), kCtxCtrlGeneral); @@ -246,121 +257,97 @@ commandCategories.emplace_back(_T(" Pattern Editor [Top]"), kCtxCtrlPatterns); { - CommandCategory newCat(_T(" Pattern Editor - Order List"), kCtxCtrlOrderlist); - - newCat.AddCommands(kcStartOrderlistCommands, kcEndOrderlistCommands); - newCat.separators.push_back(kcEndOrderlistNavigation); - newCat.separators.push_back(kcEndOrderlistEdit); - newCat.separators.push_back(kcEndOrderlistNum); - - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Order List"), kCtxCtrlOrderlist).commandRanges; + commands.emplace_back(kcStartOrderlistEdit, kcEndOrderlistEdit, _T("Edit")); + commands.emplace_back(kcStartOrderlistNavigation, kcEndOrderlistNavigation, _T("Navigation")); + commands.emplace_back(kcStartOrderlistNum, kcEndOrderlistNum, _T("Pattern Entry")); + commands.emplace_back(kcStartOrderlistMisc, kcEndOrderlistMisc, _T("Miscellaneous")); } { - CommandCategory newCat(_T(" Pattern Editor - Quick Channel Settings"), kCtxChannelSettings); - newCat.AddCommands(kcStartChnSettingsCommands, kcEndChnSettingsCommands); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Quick Channel Settings"), kCtxChannelSettings).commandRanges; + commands.emplace_back(kcStartChnSettingsCommands, kcEndChnSettingsCommands, _T("")); } { - CommandCategory newCat(_T(" Pattern Editor - General"), kCtxViewPatterns); - - newCat.AddCommands(kcStartPlainNavigate, kcEndPlainNavigate, true); - newCat.AddCommands(kcStartJumpSnap, kcEndJumpSnap, true); - newCat.AddCommands(kcStartHomeEnd, kcEndHomeEnd, true); - newCat.AddCommands(kcStartGotoColumn, kcEndGotoColumn, true); - newCat.AddCommands(kcPrevPattern, kcNextSequence, true); - newCat.AddCommands(kcStartPatternScrolling, kcEndPatternScrolling, true); - newCat.AddCommands(kcStartSelect, kcEndSelect, true); - newCat.AddCommands(kcStartPatternClipboard, kcEndPatternClipboard, true); - newCat.AddCommands(kcClearRow, kcInsertWholeRowGlobal, true); - newCat.AddCommands(kcStartChannelKeys, kcEndChannelKeys, true); - newCat.AddCommands(kcBeginTranspose, kcEndTranspose, true); - newCat.AddCommands(kcStartPatternEditMisc, kcEndPatternEditMisc, true); - newCat.AddCommands(kcStartPatternMisc, kcEndPatternMisc, true); - - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - General"), kCtxViewPatterns).commandRanges; + commands.emplace_back(kcStartPlainNavigate, kcEndPlainNavigate, _T("Navigation")); + commands.emplace_back(kcStartJumpSnap, kcEndJumpSnap, _T("Jump")); + commands.emplace_back(kcStartHomeEnd, kcEndHomeEnd, _T("Go To")); + commands.emplace_back(kcStartGotoColumn, kcEndGotoColumn, _T("Go To Column")); + commands.emplace_back(kcPrevPattern, kcNextSequence, _T("Order List Navigation")); + commands.emplace_back(kcStartPatternScrolling, kcEndPatternScrolling, _T("Scrolling")); + commands.emplace_back(kcStartSelect, kcEndSelect, _T("Selection")); + commands.emplace_back(kcStartPatternClipboard, kcEndPatternClipboard, _T("Clipboard")); + commands.emplace_back(kcClearRow, kcInsertWholeRowGlobal, _T("Clear / Insert")); + commands.emplace_back(kcStartChannelKeys, kcEndChannelKeys, _T("Channels")); + commands.emplace_back(kcBeginTranspose, kcEndTranspose, _T("Transpose")); + commands.emplace_back(kcStartPatternEditMisc, kcEndPatternEditMisc, _T("Edit")); + commands.emplace_back(kcStartPatternMisc, kcEndPatternMisc, _T("Miscellaneous")); } { - CommandCategory newCat(_T(" Pattern Editor - Note Column"), kCtxViewPatternsNote); - - newCat.AddCommands(kcVPStartNotes, kcVPEndNotes, true); - newCat.AddCommands(kcSetOctave0, kcSetOctave9, true); - newCat.AddCommands(kcStartNoteMisc, kcEndNoteMisc); - - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Note Column"), kCtxViewPatternsNote).commandRanges; + commands.emplace_back(kcVPStartNotes, kcVPEndNotes, _T("Note Entry")); + commands.emplace_back(kcSetOctave0, kcSetOctave9, _T("Octave Entry")); + commands.emplace_back(kcStartNoteMisc, kcEndNoteMisc, _T("Miscellaneous")); } { - CommandCategory newCat(_T(" Pattern Editor - Instrument Column"), kCtxViewPatternsIns); - newCat.AddCommands(kcSetIns0, kcSetIns9); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Instrument Column"), kCtxViewPatternsIns).commandRanges; + commands.emplace_back(kcSetIns0, kcSetIns9, _T("Instrument Entry")); } { - CommandCategory newCat(_T(" Pattern Editor - Volume Column"), kCtxViewPatternsVol); - newCat.AddCommands(kcSetVolumeStart, kcSetVolumeEnd); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Volume Column"), kCtxViewPatternsVol).commandRanges; + commands.emplace_back(kcStartVolumeDigits, kcEndVolumeDigits, _T("Volume Entry")); + commands.emplace_back(kcStartVolumeCommands, kcEndVolumeCommands, _T("Volume Command Entry")); } { - CommandCategory newCat(_T(" Pattern Editor - Effect Column"), kCtxViewPatternsFX); - newCat.AddCommands(kcSetFXStart, kcSetFXEnd); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Effect Column"), kCtxViewPatternsFX).commandRanges; + commands.emplace_back(kcSetFXStart, kcSetFXEnd, _T("Effect Command Entry")); } { - CommandCategory newCat(_T(" Pattern Editor - Effect Parameter Column"), kCtxViewPatternsFXparam); - newCat.AddCommands(kcSetFXParam0, kcSetFXParamF); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Pattern Editor - Effect Parameter Column"), kCtxViewPatternsFXparam).commandRanges; + commands.emplace_back(kcSetFXParam0, kcSetFXParamF, _T("Parameter Digit Entry")); } - { - CommandCategory newCat(_T(" Sample [Top]"), kCtxCtrlSamples); - commandCategories.push_back(newCat); - } + commandCategories.emplace_back(_T(" Sample [Top]"), kCtxCtrlSamples); { - CommandCategory newCat(_T(" Sample Editor"), kCtxViewSamples); - - newCat.AddCommands(kcStartSampleEditing, kcEndSampleEditing, true); - newCat.AddCommands(kcStartSampleMisc, kcEndSampleMisc, true); - newCat.AddCommands(kcStartSampleCues, kcEndSampleCueGroup); - - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Sample Editor"), kCtxViewSamples).commandRanges; + commands.emplace_back(kcStartSampleEditing, kcEndSampleEditing, _T("Edit")); + commands.emplace_back(kcStartSampleMisc, kcEndSampleMisc, _T("Miscellaneous")); + commands.emplace_back(kcStartSampleCues, kcEndSampleCueGroup, _T("Sample Cues")); } { - CommandCategory newCat(_T(" Instrument Editor"), kCtxCtrlInstruments); - newCat.AddCommands(kcStartInstrumentCtrlMisc, kcEndInstrumentCtrlMisc); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Instrument Editor"), kCtxCtrlInstruments).commandRanges; + commands.emplace_back(kcStartInstrumentCtrlMisc, kcEndInstrumentCtrlMisc, _T("")); } { - CommandCategory newCat(_T(" Envelope Editor"), kCtxViewInstruments); - newCat.AddCommands(kcStartInstrumentMisc, kcEndInstrumentMisc); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Envelope Editor"), kCtxViewInstruments).commandRanges; + commands.emplace_back(kcStartInstrumentMisc, kcEndInstrumentMisc, _T("")); } commandCategories.emplace_back(_T(" Comments [Top]"), kCtxCtrlComments); { - CommandCategory newCat(_T(" Comments [Bottom]"), kCtxViewComments); - newCat.AddCommands(kcStartCommentsCommands, kcEndCommentsCommands); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Comments [Bottom]"), kCtxViewComments).commandRanges; + commands.emplace_back(kcStartCommentsCommands, kcEndCommentsCommands, _T("")); } { - CommandCategory newCat(_T(" Plugin Editor"), kCtxVSTGUI); - newCat.AddCommands(kcStartVSTGUICommands, kcEndVSTGUICommands); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Plugin Editor"), kCtxVSTGUI).commandRanges; + commands.emplace_back(kcStartVSTGUICommands, kcEndVSTGUICommands, _T("")); } { - CommandCategory newCat(_T(" Tree View"), kCtxViewTree); - newCat.AddCommands(kcStartTreeViewCommands, kcEndTreeViewCommands); - commandCategories.push_back(newCat); + auto &commands = commandCategories.emplace_back(_T(" Tree View"), kCtxViewTree).commandRanges; + commands.emplace_back(kcStartTreeViewCommands, kcEndTreeViewCommands, _T("")); } } @@ -429,16 +416,33 @@ } +void COptionsKeyboard::OnClearSearch() +{ + m_eFindHotKey.SetKey(ModNone, 0); + m_eFind.SetWindowText(_T("")); +} + + +void COptionsKeyboard::OnEnableFindHotKey() +{ + OnClearHotKey(); + GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_FINDHOTKEY_LABEL)->ShowWindow(SW_SHOW); + GetDlgItem(IDC_FINDHOTKEY)->ShowWindow(SW_SHOW); + GetDlgItem(IDC_FINDHOTKEY)->SetFocus(); +} + + void COptionsKeyboard::OnFindHotKey() { - if(IsLocked()) - return; - - if(m_eFindHotKey.code == 0) - { + GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW); + GetDlgItem(IDC_FINDHOTKEY_LABEL)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_FINDHOTKEY)->ShowWindow(SW_HIDE); + const bool hasKey = m_eFindHotKey.HasKey(); + if(!hasKey) UpdateCategory(); - } - UpdateShortcutList(m_eFindHotKey.code == 0 ? m_curCategory : -1); + UpdateShortcutList(hasKey ? - 1: m_curCategory); + m_lbnCommandKeys.SetFocus(); } @@ -483,7 +487,7 @@ m_eFind.GetWindowText(findString); findString.MakeLower(); - const bool searchByName = !findString.IsEmpty(), searchByKey = (m_eFindHotKey.code != 0); + const bool searchByName = !findString.IsEmpty(), searchByKey = m_eFindHotKey.HasKey(); const bool doSearch = (searchByName || searchByKey); int firstCat = category, lastCat = category; @@ -497,11 +501,9 @@ const auto curSelection = m_lbnCommandKeys.GetSelectionMark(); CommandID curCommand = (curSelection >= 0) ? static_cast<CommandID>(m_lbnCommandKeys.GetItemData(curSelection)) : kcNull; m_lbnCommandKeys.SetRedraw(FALSE); - m_lbnCommandKeys.DeleteColumn(0); + m_lbnCommandKeys.DeleteAllItems(); if(m_listGrouped) ListView_RemoveAllGroups(m_lbnCommandKeys); - m_lbnCommandKeys.InsertColumn(0, _T("")); - m_lbnCommandKeys.DeleteAllItems(); int currentGroup = -1; int itemID = -1; @@ -511,34 +513,35 @@ // When searching, we also add the category names to the list. bool addCategoryName = (firstCat != lastCat); - for(size_t cmd = 0; cmd < commandCategories[cat].commands.size(); cmd++) + for(const auto &range : commandCategories[cat].commandRanges) { - CommandID com = (CommandID)commandCategories[cat].commands[cmd]; - - CString cmdText = m_localCmdSet->GetCommandText(com); - bool addKey = true; - - if(searchByKey) + for(CommandID com = range.first; com <= range.last; com = static_cast<CommandID>(com + 1)) { - addKey = false; - int numChoices = m_localCmdSet->GetKeyListSize(com); - for(int choice = 0; choice < numChoices; choice++) + CString cmdText = m_localCmdSet->GetCommandText(com); + bool addKey = true; + + if(searchByKey) { - const KeyCombination &kc = m_localCmdSet->GetKey(com, choice); - if(kc.KeyCode() == m_eFindHotKey.code && kc.Modifier() == m_eFindHotKey.mod) + addKey = false; + int numChoices = m_localCmdSet->GetKeyListSize(com); + for(int choice = 0; choice < numChoices; choice++) { - addKey = true; - break; + const KeyCombination &kc = m_localCmdSet->GetKey(com, choice); + if(kc.KeyCode() == m_eFindHotKey.code && kc.Modifier() == m_eFindHotKey.mod) + { + addKey = true; + break; + } } } - } - if(searchByName && addKey) - { - addKey = (cmdText.MakeLower().Find(findString) >= 0); - } + if(searchByName && addKey) + { + addKey = (cmdText.MakeLower().Find(findString) >= 0); + } + + if(!addKey) + continue; - if(addKey) - { m_curCategory = cat; LVITEM lvi; @@ -553,16 +556,35 @@ lvi.iIndent = 0; lvi.iGroupId = 0; - if(!m_localCmdSet->isHidden(com)) + if(com == range.first && !doSearch) + { + if(m_listGrouped) + { + InsertGroup(range.name, ++currentGroup); + } else + { + lvi.iItem = ++itemID; + lvi.lParam = kcNull; + CString catName; + if(range.name.IsEmpty()) + catName = const_cast<TCHAR *>(_T("------------------------------------------------------")); + else + catName = _T("------ ") + range.name + _T(" ------"); + lvi.pszText = const_cast<TCHAR *>(catName.GetString()); + m_lbnCommandKeys.InsertItem(&lvi); + } + } + + if(!m_localCmdSet->IsHidden(com)) { if(doSearch && addCategoryName) { if(m_listGrouped) { - InsertGroup(commandCategories[cat].name.Trim(), ++currentGroup); + InsertGroup(CString{commandCategories[cat].name}.TrimLeft(), ++currentGroup); } else { - const CString catName = _T("------ ") + commandCategories[cat].name.Trim() + _T(" ------"); + const CString catName = _T("------ ") + CString{commandCategories[cat].name}.TrimLeft() + _T(" ------"); lvi.iItem = ++itemID; lvi.lParam = LPARAM(-1); lvi.pszText = const_cast<TCHAR *>(catName.GetString()); @@ -580,6 +602,7 @@ lvi.pszText = const_cast<TCHAR *>(text.GetString()); lvi.iGroupId = currentGroup; m_lbnCommandKeys.InsertItem(&lvi); + m_lbnCommandKeys.SetItemText(itemID, 1, m_localCmdSet->GetKeyTextFromCommand(com)); if(curCommand == com) { @@ -587,21 +610,6 @@ m_lbnCommandKeys.SetSelectionMark(itemID); } } - - if(commandCategories[cat].SeparatorAt(com)) - { - if(m_listGrouped) - { - InsertGroup(_T(""), ++currentGroup); - - } else - { - lvi.iItem = ++itemID; - lvi.lParam = LPARAM(-1); - lvi.pszText = const_cast<TCHAR *>(_T("------------------------------------------------------")); - m_lbnCommandKeys.InsertItem(&lvi); - } - } } } } @@ -610,7 +618,6 @@ { m_lbnCommandKeys.SetSelectionMark(0); } - m_lbnCommandKeys.SetColumnWidth(0, LVSCW_AUTOSIZE); m_lbnCommandKeys.SetRedraw(TRUE); OnCommandKeySelChanged(); } @@ -636,14 +643,15 @@ EnableKeyChoice(false); - BOOL enableButton = (cmd == kcNull) ? FALSE : TRUE; + BOOL enableButton = (cmd != kcNull) ? TRUE : FALSE; + BOOL enableCheckBoxes = (cmd != kcNull && m_localCmdSet->GetKeyListSize(cmd) > 0) ? TRUE : FALSE; GetDlgItem(IDC_BUTTON1)->EnableWindow(enableButton); GetDlgItem(IDC_DELETE)->EnableWindow(enableButton); GetDlgItem(IDC_RESTORE)->EnableWindow(enableButton); m_cmbKeyChoice.EnableWindow(enableButton); - m_bKeyDown.EnableWindow(enableButton); - m_bKeyHold.EnableWindow(enableButton); - m_bKeyUp.EnableWindow(enableButton); + m_bKeyDown.EnableWindow(enableCheckBoxes); + m_bKeyHold.EnableWindow(enableCheckBoxes); + m_bKeyUp.EnableWindow(enableCheckBoxes); //Separator if(cmd == kcNull) @@ -654,11 +662,16 @@ m_bKeyHold.SetCheck(BST_UNCHECKED); m_bKeyUp.SetCheck(BST_UNCHECKED); m_curCommand = kcNull; + + GetDlgItem(IDC_GROUPBOX_KEYSETUP)->SetWindowText(_T("&Key setup for selected command")); + UpdateWarning(); } //Fill "choice" list else if((cmd >= kcFirst && cmd != m_curCommand) || m_forceUpdate) // Have we changed command? { + GetDlgItem(IDC_GROUPBOX_KEYSETUP)->SetWindowText(_T("&Key setup for ") + m_localCmdSet->GetCommandText(cmd)); + m_forceUpdate = false; m_curCommand = cmd; @@ -681,6 +694,15 @@ } } + +void COptionsKeyboard::OnListenForKeysFromList(NMHDR *pNMHDR, LRESULT *) +{ + auto hdr = reinterpret_cast<const NMLISTVIEW *>(pNMHDR); + if(hdr->iSubItem == 1) + OnListenForKeys(); +} + + //Fills or clears key choice info void COptionsKeyboard::OnKeyChoiceSelect() { @@ -692,6 +714,7 @@ //If nothing there, clear if(cmd == kcNull || choice >= m_localCmdSet->GetKeyListSize(cmd) || choice < 0) { + UpdateWarning(); m_curKeyChoice = choice; m_forceUpdate = true; m_eCustHotKey.SetKey(ModNone, 0); @@ -712,6 +735,15 @@ m_bKeyDown.SetCheck((kc.EventType() & kKeyEventDown) ? BST_CHECKED : BST_UNCHECKED); m_bKeyHold.SetCheck((kc.EventType() & kKeyEventRepeat) ? BST_CHECKED : BST_UNCHECKED); m_bKeyUp.SetCheck((kc.EventType() & kKeyEventUp) ? BST_CHECKED : BST_UNCHECKED); + + if(auto conflictCmd = m_localCmdSet->IsConflicting(kc, cmd, true, false); conflictCmd.first != kcNull + && conflictCmd.first != cmd) + { + UpdateWarning(m_localCmdSet->FormatConflict(kc, conflictCmd.first, conflictCmd.second)); + } else + { + UpdateWarning(); + } } } @@ -759,7 +791,7 @@ // Restore current key combination choice for currently selected command. kc = ih->m_activeCommandSet->GetKey(cmd, m_curKeyChoice); m_localCmdSet->Remove(m_curKeyChoice, cmd); - m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice); + UpdateWarning(m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice)); } ForceUpdateGUI(); @@ -811,12 +843,32 @@ m_localCmdSet->Remove(m_curKeyChoice, cmd); ForceUpdateGUI(); - return; + UpdateWarning(); } -void COptionsKeyboard::OnSetKeyChoice() +void COptionsKeyboard::OnCancelKeyChoice(const CWnd *source) { + if(source == &m_eFindHotKey) + { + GetDlgItem(IDC_BUTTON2)->ShowWindow(SW_SHOW); + GetDlgItem(IDC_FINDHOTKEY_LABEL)->ShowWindow(SW_HIDE); + GetDlgItem(IDC_FINDHOTKEY)->ShowWindow(SW_HIDE); + } else + { + EnableKeyChoice(false); + } +} + + +void COptionsKeyboard::OnSetKeyChoice(const CWnd *source) +{ + if(source == &m_eFindHotKey) + { + OnFindHotKey(); + return; + } + EnableKeyChoice(false); CommandID cmd = m_curCommand; @@ -849,9 +901,13 @@ bool add = true; std::pair<CommandID, KeyCombination> conflictCmd; - if((conflictCmd = m_localCmdSet->IsConflicting(kc, cmd)).first != kcNull - && conflictCmd.first != cmd - && !m_localCmdSet->IsCrossContextConflict(kc, conflictCmd.second)) + if(CCommandSet::MustBeModifierKey(cmd) && !kc.IsModifierCombination()) + { + KeyCombination origKc = m_localCmdSet->GetKey(cmd, m_curKeyChoice); + m_eCustHotKey.SetKey(origKc.Modifier(), origKc.KeyCode()); + UpdateWarning(m_localCmdSet->GetCommandText(cmd) + _T(" must be a modifier (Shift/Ctrl/Alt), but you chose ") + kc.GetKeyText(), true); + add = false; + } else if((conflictCmd = m_localCmdSet->IsConflicting(kc, cmd)).first != kcNull && conflictCmd.first != cmd && !m_localCmdSet->IsCrossContextConflict(kc, conflictCmd.second)) { ConfirmAnswer delOld = Reporting::Confirm(_T("New shortcut (") + kc.GetKeyText() + _T(") has the same key combination as ") + m_localCmdSet->GetCommandText(conflictCmd.first) + _T(" in ") + conflictCmd.second.GetContextText() + _T(".\nDo you want to delete the other shortcut, only keeping the new one?"), _T("Shortcut Conflict"), true, false, this); if(delOld == cnfYes) @@ -871,25 +927,63 @@ } } } - + if(add) { - CString report, reportHistory; //process valid input m_localCmdSet->Remove(m_curKeyChoice, cmd); - report = m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice); - - //Update log - m_eReport.GetWindowText(reportHistory); - m_eReport.SetWindowText(report + reportHistory); + UpdateWarning(m_localCmdSet->Add(kc, cmd, true, m_curKeyChoice), true); ForceUpdateGUI(); } } +static HICON LoadScaledIcon(const TCHAR *resourceName, int iconSize) +{ + mpt::Library comctl32(mpt::LibraryPath::System(P_("Comctl32"))); + if(comctl32.IsValid()) + { + using PLOADICONWITHSCALEDOWN = HRESULT(WINAPI *)(HINSTANCE, PCWSTR, int, int, HICON *); + PLOADICONWITHSCALEDOWN LoadIconWithScaleDown = nullptr; + HICON icon = nullptr; + if(comctl32.Bind(LoadIconWithScaleDown, "LoadIconWithScaleDown")) + LoadIconWithScaleDown(NULL, MAKEINTRESOURCEW(reinterpret_cast<uintptr_t>(resourceName)), iconSize, iconSize, &icon); + if(icon) + return icon; + } + return static_cast<HICON>(::LoadImage(nullptr, resourceName, IMAGE_ICON, iconSize, iconSize, LR_SHARED)); +} + + +void COptionsKeyboard::UpdateWarning(CString text, bool notify) +{ + const int iconSize = HighDPISupport::ScalePixels(16, m_hWnd); + HICON icon = nullptr; + if(text.IsEmpty()) + { + m_warnText.SetWindowText(_T("No conflicts found.")); + if(!m_infoIcon) + m_infoIcon = LoadScaledIcon(IDI_INFORMATION, iconSize); + icon = m_infoIcon; + } else + { + if(notify) + ::MessageBeep(MB_ICONWARNING); + + m_warnText.SetWindowText(text); + if(!m_warnIcon) + m_warnIcon = LoadScaledIcon(IDI_EXCLAMATION, iconSize); + icon = m_warnIcon; + } + m_warnIconCtl.SetWindowPos(nullptr, 0, 0, iconSize, iconSize, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOMOVE | SWP_NOACTIVATE); + m_warnIconCtl.SetIcon(icon); + m_lastWarning = std::move(text); +} + + void COptionsKeyboard::OnOK() { - CMainFrame::GetInputHandler()->SetNewCommandSet(m_localCmdSet.get()); + CMainFrame::GetInputHandler()->SetNewCommandSet(*m_localCmdSet); CString cs; m_eChordWaitTime.GetWindowText(cs); @@ -901,8 +995,18 @@ void COptionsKeyboard::OnDestroy() { + if(m_warnIcon) + { + DestroyIcon(m_warnIcon); + m_warnIcon = nullptr; + } + if(m_infoIcon) + { + DestroyIcon(m_infoIcon); + m_infoIcon = nullptr; + } CPropertyPage::OnDestroy(); - m_localCmdSet = nullptr; + m_localCmdSet.reset(); } @@ -919,7 +1023,8 @@ m_fullPathName = dlg.GetFirstFile(); m_localCmdSet->LoadFile(m_fullPathName); - ForceUpdateGUI(); + ForceUpdateGUI(true); + UpdateWarning(); } @@ -952,7 +1057,7 @@ } -void COptionsKeyboard::ForceUpdateGUI() +void COptionsKeyboard::ForceUpdateGUI(bool updateAllKeys) { m_forceUpdate = true; // m_nCurKeyChoice and m_nCurHotKey haven't changed, yet we still want to update. int ntmpChoice = m_curKeyChoice; // next call will overwrite m_nCurKeyChoice @@ -960,13 +1065,19 @@ m_cmbKeyChoice.SetCurSel(ntmpChoice); // select fresh keychoice (thus restoring m_nCurKeyChoice) OnKeyChoiceSelect(); // update key data OnSettingsChanged(); // Enable "apply" button -} - -void COptionsKeyboard::OnClearLog() -{ - m_eReport.SetWindowText(_T("")); - ForceUpdateGUI(); + if(updateAllKeys) + { + const int numItems = m_lbnCommandKeys.GetItemCount(); + for(int i = 0; i < numItems; i++) + { + if(const auto cmd = static_cast<CommandID>(m_lbnCommandKeys.GetItemData(i)); cmd != kcNull) + m_lbnCommandKeys.SetItemText(i, 1, m_localCmdSet->GetKeyTextFromCommand(cmd)); + } + } else if(m_curCommand != kcNull) + { + m_lbnCommandKeys.SetItemText(m_lbnCommandKeys.GetSelectionMark(), 1, m_localCmdSet->GetKeyTextFromCommand(m_curCommand)); + } } @@ -975,7 +1086,7 @@ if(Reporting::Confirm("Discard all custom changes and restore default key configuration?", false, true, this) == cnfYes) { m_localCmdSet->LoadDefaultKeymap(); - ForceUpdateGUI(); + ForceUpdateGUI(true); } } @@ -984,9 +1095,11 @@ { for(size_t cat = 0; cat < commandCategories.size(); cat++) { - const auto &cmds = commandCategories[cat].commands; - if(mpt::contains(cmds, command)) - return static_cast<int>(cat); + for(const auto &range : commandCategories[cat].commandRanges) + { + if(mpt::is_in_range(command, range.first, range.last)) + return static_cast<int>(cat); + } } return -1; } Modified: trunk/OpenMPT/mptrack/KeyConfigDlg.h ============================================================================== --- trunk/OpenMPT/mptrack/KeyConfigDlg.h Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/KeyConfigDlg.h Fri Nov 15 22:52:54 2024 (r22192) @@ -11,31 +11,27 @@ #pragma once #include "openmpt/all/BuildSettings.hpp" +#include "CListCtrl.h" #include "InputHandler.h" OPENMPT_NAMESPACE_BEGIN class COptionsKeyboard; -// Might promote to class so we can add rules -// (eg automatically do note off stuff, generate chord keybindings from notes based just on modifier. -// Would need GUI rules too as options would be different for each category class CommandCategory { public: - CommandCategory(const TCHAR *n, InputTargetContext d) : name(n), id(d) { } + CommandCategory(const TCHAR *n, InputTargetContext ctx) : name{n}, id{ctx} {} - bool SeparatorAt(CommandID c) const + const CString name; + const InputTargetContext id; + + struct Range { - return mpt::contains(separators, c); - } - - void AddCommands(CommandID first, CommandID last, bool addSeparatorAtEnd = false); - - CString name; - InputTargetContext id; - std::vector<CommandID> separators; - std::vector<CommandID> commands; + const CommandID first, last; + const CString name; + }; + std::vector<Range> commandRanges; }; @@ -43,23 +39,16 @@ { protected: COptionsKeyboard *m_pOptKeyDlg = nullptr; - HWND m_hParent = nullptr; - UINT m_nCtrlId = 0; - bool m_isFocussed = false, m_isDummy = false; + bool m_isFocussed = false; bool m_bypassed = false; public: FlagSet<Modifiers> mod = ModNone; UINT code = 0; - explicit CCustEdit(bool dummyField) : m_isDummy(dummyField) { } - void SetParent(HWND h, UINT nID, COptionsKeyboard *pOKD) - { - m_hParent = h; - m_nCtrlId = nID; - m_pOptKeyDlg = pOKD; - } + void SetOwner(COptionsKeyboard &dlg) { m_pOptKeyDlg = &dlg; } void SetKey(FlagSet<Modifiers> mod, UINT code); + bool HasKey() const noexcept { return mod || code; } void Bypass(bool bypass) { m_bypassed = bypass; EnableWindow(bypass ? FALSE : TRUE); } bool IsBypassed() const { return m_bypassed; } @@ -80,15 +69,18 @@ protected: CListBox m_lbnHotKeys; - CListCtrl m_lbnCommandKeys; + CListCtrlEx m_lbnCommandKeys; CComboBox m_cmbKeyChoice; CComboBox m_cmbCategory; CButton m_bKeyDown, m_bKeyHold, m_bKeyUp; CButton m_bnReset; - CCustEdit m_eCustHotKey{false}, m_eFindHotKey{true}; + CCustEdit m_eCustHotKey, m_eFindHotKey; + CStatic m_warnIconCtl, m_warnText; CEdit m_eFind; - CEdit m_eReport, m_eChordWaitTime; + CEdit m_eChordWaitTime; + HICON m_infoIcon = nullptr, m_warnIcon = nullptr; + CString m_lastWarning; std::vector<CommandCategory> commandCategories; std::unique_ptr<CCommandSet> m_localCmdSet; mpt::PathString m_fullPathName; @@ -108,12 +100,13 @@ void DoDataExchange(CDataExchange* pDX) override; void DefineCommandCategories(); - void ForceUpdateGUI(); + void ForceUpdateGUI(bool updateAllKeys = false); void InsertGroup(const TCHAR *title, int groupId); void UpdateShortcutList(int category = -1); void UpdateCategory(); int GetCategoryFromCommandID(CommandID command) const; - void OnSetKeyChoice(); + void OnCancelKeyChoice(const CWnd *source); + void OnSetKeyChoice(const CWnd *source); void LockControls() { m_lockCount++; } void UnlockControls() { m_lockCount--; MPT_ASSERT(m_lockCount >= 0); } @@ -121,15 +114,19 @@ void EnableKeyChoice(bool enable); + void UpdateWarning(CString text = {}, bool notify = false); + + afx_msg LRESULT OnDPIChangedAfterParent(WPARAM, LPARAM); afx_msg void UpdateDialog(); afx_msg void OnKeyboardChanged(); afx_msg void OnKeyChoiceSelect(); afx_msg void OnCommandKeySelChanged(NMHDR *pNMHDR = nullptr, LRESULT *pResult = nullptr); + afx_msg void OnListenForKeysFromList(NMHDR *pNMHDR, LRESULT *pResult); afx_msg void OnCategorySelChanged(); afx_msg void OnSearchTermChanged(); afx_msg void OnChordWaitTimeChanged(); afx_msg void OnSettingsChanged() { SetModified(TRUE); } - afx_msg void OnCheck() { OnSetKeyChoice(); }; + afx_msg void OnCheck() { OnSetKeyChoice(&m_eCustHotKey); }; afx_msg void OnNotesRepeat(); afx_msg void OnNoNotesRepeat(); afx_msg void OnListenForKeys(); @@ -137,9 +134,10 @@ afx_msg void OnRestoreKeyChoice(); afx_msg void OnLoad(); afx_msg void OnSave(); - afx_msg void OnClearLog(); afx_msg void OnRestoreDefaultKeymap(); afx_msg void OnClearHotKey(); + afx_msg void OnClearSearch(); + afx_msg void OnEnableFindHotKey(); afx_msg void OnFindHotKey(); afx_msg void OnLButtonDblClk(UINT flags, CPoint point); afx_msg void OnDestroy(); Modified: trunk/OpenMPT/mptrack/WelcomeDialog.cpp ============================================================================== --- trunk/OpenMPT/mptrack/WelcomeDialog.cpp Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/WelcomeDialog.cpp Fri Nov 15 22:52:54 2024 (r22192) @@ -121,7 +121,7 @@ // As this is presented as the default, load it right now, even if the user closes the dialog through the close button auto cmdSet = std::make_unique<CCommandSet>(); cmdSet->LoadFile(GetFullKeyPath(keyFile)); - CMainFrame::GetInputHandler()->SetNewCommandSet(cmdSet.get()); + CMainFrame::GetInputHandler()->SetNewCommandSet(*cmdSet); } } combo->SetItemDataPtr(combo->AddString(_T("Impulse Tracker")), (void*)("US_mpt-it2_classic")); @@ -185,7 +185,7 @@ cmdSet->LoadFile(GetFullKeyPath(keyFile)); else cmdSet->LoadDefaultKeymap(); - CMainFrame::GetInputHandler()->SetNewCommandSet(cmdSet.get()); + CMainFrame::GetInputHandler()->SetNewCommandSet(*cmdSet); #if defined(MPT_ENABLE_UPDATE) if(runUpdates) Modified: trunk/OpenMPT/mptrack/mptrack.rc ============================================================================== --- trunk/OpenMPT/mptrack/mptrack.rc Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/mptrack.rc Fri Nov 15 22:52:54 2024 (r22192) @@ -1245,36 +1245,35 @@ CAPTION "Keyboard" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN - LTEXT "Select category:",IDC_STATIC,7,5,131,11 - COMBOBOX IDC_KEYCATEGORY,5,16,139,204,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "",IDC_COMMAND_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,5,32,139,226 - GROUPBOX "Key setup for selected command ",IDC_STATIC,150,6,132,84 - COMBOBOX IDC_CHOICECOMBO,156,18,78,51,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Key:",IDC_STATIC,156,38,16,8 - EDITTEXT IDC_CUSTHOTKEY,174,36,60,13,ES_AUTOHSCROLL | ES_READONLY - PUSHBUTTON "&Set",IDC_BUTTON1,240,36,37,13 - PUSHBUTTON "&Restore",IDC_RESTORE,240,54,37,13 - PUSHBUTTON "&Delete",IDC_DELETE,240,18,37,13 - CONTROL "On Key Down",IDC_CHECKKEYDOWN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,54,66,8 - CONTROL "On Key Hold",IDC_CHECKKEYHOLD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,66,66,8 - CONTROL "On Key Up",IDC_CHECKKEYUP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,156,78,66,8 - GROUPBOX "Misc",IDC_STATIC,150,96,132,40 - LTEXT "Repeat notes on hold?",IDC_STATIC,156,108,84,8 - PUSHBUTTON "Yes",IDC_NOTESREPEAT,240,108,18,9 - PUSHBUTTON "No",IDC_NONOTESREPEAT,258,108,18,9 - LTEXT "Chord detect interval (ms):",IDC_STATIC,156,122,94,8 - EDITTEXT IDC_CHORDDETECTWAITTIME,250,120,26,12,ES_AUTOHSCROLL | ES_NUMBER,WS_EX_RIGHT - GROUPBOX "Keyboard Mapping",IDC_STATIC,150,144,132,48 - PUSHBUTTON "&Import Keys...",IDC_LOAD,156,156,54,13 - PUSHBUTTON "&Export Keys...",IDC_SAVE,222,156,54,13 - PUSHBUTTON "Restore default &configuration",IDC_RESTORE_KEYMAP,156,174,120,12 - LTEXT "Error Log:",IDC_STATIC,150,198,78,8 - PUSHBUTTON "Clear &Log",IDC_CLEARLOG,239,196,42,12 - EDITTEXT IDC_KEYREPORT,150,210,132,66,ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY | WS_VSCROLL - EDITTEXT IDC_FIND,30,264,54,12,ES_AUTOHSCROLL - LTEXT "Find:",IDC_STATIC,6,266,24,8 - EDITTEXT IDC_FINDHOTKEY,108,264,36,12,ES_AUTOHSCROLL - LTEXT "Key:",IDC_STATIC,90,266,17,8 + LTEXT "Select c&ategory:",IDC_STATIC,7,8,83,8 + COMBOBOX IDC_KEYCATEGORY,90,6,192,204,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "",IDC_COMMAND_LIST,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP,5,24,277,126 + LTEXT "&Find by Name:",IDC_STATIC,6,158,54,8 + EDITTEXT IDC_FIND,60,156,102,12,ES_AUTOHSCROLL + PUSHBUTTON "Find by Hotkey...",IDC_BUTTON2,168,156,66,13 + LTEXT "Key:",IDC_FINDHOTKEY_LABEL,174,158,17,8,NOT WS_VISIBLE + EDITTEXT IDC_FINDHOTKEY,192,156,42,12,ES_AUTOHSCROLL | NOT WS_VISIBLE + PUSHBUTTON "C&lear",IDC_BUTTON3,240,156,44,12 + GROUPBOX "&Key setup for selected command",IDC_GROUPBOX_KEYSETUP,6,174,276,66 + COMBOBOX IDC_CHOICECOMBO,12,186,84,51,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Key:",IDC_STATIC,102,188,16,8 + EDITTEXT IDC_CUSTHOTKEY,120,186,72,13,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "&Set",IDC_BUTTON1,198,186,37,13 + PUSHBUTTON "&Restore",IDC_RESTORE,240,186,37,13 + PUSHBUTTON "&Delete Choice",IDC_DELETE,198,204,79,13 + CONTROL "On Key Down",IDC_CHECKKEYDOWN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,12,206,66,8 + CONTROL "On Key Hold",IDC_CHECKKEYHOLD,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,78,206,60,8 + CONTROL "On Key Up",IDC_CHECKKEYUP,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,138,206,54,8 + ICON "",IDC_STATIC1,12,223,20,20,SS_CENTERIMAGE | SS_REALSIZEIMAGE | NOT WS_VISIBLE + LTEXT "No conflicts found.",IDC_KEYREPORT,26,224,254,13 + LTEXT "Repeat notes on hold?",IDC_STATIC,6,248,84,8 + PUSHBUTTON "Yes",IDC_NOTESREPEAT,90,246,24,12 + PUSHBUTTON "No",IDC_NONOTESREPEAT,120,246,24,12 + LTEXT "Chord detect interval (ms):",IDC_STATIC,156,248,94,8 + EDITTEXT IDC_CHORDDETECTWAITTIME,252,246,30,12,ES_AUTOHSCROLL | ES_NUMBER + PUSHBUTTON "&Import Key Map...",IDC_LOAD,6,264,72,13 + PUSHBUTTON "&Export Key Map...",IDC_SAVE,84,264,72,13 + PUSHBUTTON "Restore default &configuration",IDC_RESTORE_KEYMAP,162,264,120,12 END IDD_OPTIONS_COLORS DIALOGEX 0, 0, 286, 282 Modified: trunk/OpenMPT/mptrack/resource.h ============================================================================== --- trunk/OpenMPT/mptrack/resource.h Fri Nov 15 22:21:01 2024 (r22191) +++ trunk/OpenMPT/mptrack/resource.h Fri Nov 15 22:52:54 2024 (r22192) @@ -658,8 +658,8 @@ #define IDC_SAVE 2102 #define IDC_LOAD 2103 #define IDC_KEYREPORT 2105 -#define IDC_BROWSEKEYCONF 2107 -#define IDC_CLEARLOG 2107 +#define IDC_GROUPBOX_KEYSETUP 2106 +#define IDC_FINDHOTKEY_LABEL 2107 #define IDC_NOTESREPEAT 2108 #define IDC_REMCHANSLIST 2108 #define IDC_NOTESREPEAT2 2109 |