diff --git a/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate+ViewerCreation.mm b/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate+ViewerCreation.mm index 88ed02bcd..be472138a 100644 --- a/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate+ViewerCreation.mm +++ b/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate+ViewerCreation.mm @@ -20,14 +20,9 @@ - (NCViewerView *)makeViewerWithFrame:(NSRect)frame - (NCViewerViewController *)makeViewerController { - using nc::core::ActionsShortcutsManager; - auto shortcuts = [](std::string_view _name) -> ActionsShortcutsManager::Shortcut { - auto sc = ActionsShortcutsManager::Instance().ShortcutsFromAction(_name).value(); - return sc.empty() ? ActionsShortcutsManager::Shortcut{} : sc.front(); - }; return [[NCViewerViewController alloc] initWithHistory:self.internalViewerHistory config:self.globalConfig - shortcutsProvider:shortcuts]; + shortcuts:nc::core::ActionsShortcutsManager::Instance()]; } @end diff --git a/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate.mm b/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate.mm index ce70754af..59320385c 100644 --- a/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate.mm +++ b/Source/NimbleCommander/NimbleCommander/Bootstrap/AppDelegate.mm @@ -301,7 +301,7 @@ - (void)wireMenuDelegates { // set up menu delegates. do this via DI to reduce links to AppDelegate in whole codebase auto item_for_action = [](const char *_action) -> NSMenuItem * { - const std::optional tag = nc::core::ActionsShortcutsManager::TagFromAction(_action); + const std::optional tag = nc::core::ActionsShortcutsManager::Instance().TagFromAction(_action); if( tag == std::nullopt ) return nil; return [NSApp.mainMenu itemWithTagHierarchical:*tag]; @@ -355,7 +355,9 @@ - (void)wireMenuDelegates - (void)updateMainMenuFeaturesByVersionAndState { // disable some features available in menu by configuration limitation - auto tag_from_lit = [](const char *s) { return nc::core::ActionsShortcutsManager::TagFromAction(s).value_or(-1); }; + auto tag_from_lit = [](const char *s) { + return nc::core::ActionsShortcutsManager::Instance().TagFromAction(s).value(); + }; auto current_menuitem = [&](const char *s) { return [NSApp.mainMenu itemWithTagHierarchical:tag_from_lit(s)]; }; auto hide = [&](const char *s) { auto item = current_menuitem(s); @@ -627,7 +629,7 @@ - (IBAction)OnMenuToggleAdminMode:(id) [[maybe_unused]] _sender - (BOOL)validateMenuItem:(NSMenuItem *)item { static const int admin_mode_tag = - nc::core::ActionsShortcutsManager::TagFromAction("menu.nimble_commander.toggle_admin_mode").value(); + nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.nimble_commander.toggle_admin_mode").value(); const long tag = item.tag; if( tag == admin_mode_tag ) { diff --git a/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.h b/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.h index df4e07b2c..c7c940f02 100644 --- a/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.h +++ b/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -22,59 +23,50 @@ class Config; namespace nc::core { -class ActionsShortcutsManager : nc::base::ObservableBase +class ActionsShortcutsManager : public nc::utility::ActionsShortcutsManager, private nc::base::ObservableBase { public: - // Shortcut represents a key and its modifiers that have to be pressed to trigger an action. - using Shortcut = nc::utility::ActionShortcut; - - // An ordered list of shortcuts. - // The relative order of the shortcuts must be preserved as it has semantic meaning for e.g. menus. - // Empty shortcuts should not be stored in such vectors. - // An inlined vector is used to avoid memory allocating for such tiny memory blocks. - using Shortcuts = absl::InlinedVector; - - // ActionTags represents a list of numberic action tags. - // Normally they are tiny, thus an inline vector is used to avoid memory allocation. - using ActionTags = absl::InlinedVector; - // Create a new shortcut manager which will use the provided config to store the overides. ActionsShortcutsManager(nc::config::Config &_config); + // Destructor. + virtual ~ActionsShortcutsManager(); + // A shared instance of a manager, it uses the GlobalConfig() as its data backend. static ActionsShortcutsManager &Instance(); // Returns a numeric tag that corresponds to the given action name. - static std::optional TagFromAction(std::string_view _action) noexcept; + std::optional TagFromAction(std::string_view _action) const noexcept override; // Returns an action name of the given numeric tag. - static std::optional ActionFromTag(int _tag) noexcept; + std::optional ActionFromTag(int _tag) const noexcept override; // Returns a shortcut assigned to the specified action. // Returns std::nullopt such action cannot be found. // Overrides have priority over the default shortcuts. - std::optional ShortcutsFromAction(std::string_view _action) const noexcept; + std::optional ShortcutsFromAction(std::string_view _action) const noexcept override; // Returns a shortcut assigned to the specified numeric action tag. // Returns std::nullopt such action cannot be found. // Overrides have priority over the default shortcuts. - std::optional ShortcutsFromTag(int _tag) const noexcept; + std::optional ShortcutsFromTag(int _tag) const noexcept override; // Returns a default shortcut for an action specified by its numeric tag. // Returns std::nullopt such action cannot be found. - std::optional DefaultShortcutsFromTag(int _tag) const noexcept; + std::optional DefaultShortcutsFromTag(int _tag) const noexcept override; // Returns an unordered list of numeric tags of actions that have the specified shortcut. // An optional domain parameter can be specified to filter the output by only leaving actions that have the // specified domain in their name. - std::optional ActionTagsFromShortcut(Shortcut _sc, std::string_view _in_domain = {}) const noexcept; + std::optional ActionTagsFromShortcut(Shortcut _sc, + std::string_view _in_domain = {}) const noexcept override; // Syntax sugar around ActionTagsFromShortCut(_sc, _in_domain) and find_first_of(_of_tags). // Returns the first tag from the specified set. // The order is not defined in case of ambiguities. std::optional FirstOfActionTagsFromShortcut(std::span _of_tags, Shortcut _sc, - std::string_view _in_domain = {}) const noexcept; + std::string_view _in_domain = {}) const noexcept override; // Removes any hotkeys overrides. void RevertToDefaults(); diff --git a/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.mm b/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.mm index 400342fb3..3ca14d82d 100644 --- a/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.mm +++ b/Source/NimbleCommander/NimbleCommander/Core/ActionsShortcutsManager.mm @@ -473,20 +473,22 @@ static constexpr auto make_array_n(T &&value) BuildShortcutUsageMap(); } +ActionsShortcutsManager::~ActionsShortcutsManager() = default; + ActionsShortcutsManager &ActionsShortcutsManager::Instance() { [[clang::no_destroy]] static ActionsShortcutsManager manager(GlobalConfig()); return manager; } -std::optional ActionsShortcutsManager::TagFromAction(std::string_view _action) noexcept +std::optional ActionsShortcutsManager::TagFromAction(std::string_view _action) const noexcept { if( const auto it = g_ActionToTag.find(_action); it != g_ActionToTag.end() ) return it->second; return std::nullopt; } -std::optional ActionsShortcutsManager::ActionFromTag(int _tag) noexcept +std::optional ActionsShortcutsManager::ActionFromTag(int _tag) const noexcept { if( const auto it = g_TagToAction.find(_tag); it != g_TagToAction.end() ) return std::string_view{it->second.data(), it->second.size()}; diff --git a/Source/NimbleCommander/NimbleCommander/Preferences/PreferencesWindowHotkeysTab.mm b/Source/NimbleCommander/NimbleCommander/Preferences/PreferencesWindowHotkeysTab.mm index fb0f6aa4a..8e2ce597f 100644 --- a/Source/NimbleCommander/NimbleCommander/Preferences/PreferencesWindowHotkeysTab.mm +++ b/Source/NimbleCommander/NimbleCommander/Preferences/PreferencesWindowHotkeysTab.mm @@ -426,7 +426,7 @@ - (IBAction)onHKChanged:(id)sender return; const int tag = static_cast(tf.tag); - const std::optional action = ActionsShortcutsManager::ActionFromTag(tag); + const std::optional action = ActionsShortcutsManager::Instance().ActionFromTag(tag); if( !action ) return; diff --git a/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelControllerActionsDispatcher.mm b/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelControllerActionsDispatcher.mm index 6fb4da3bf..3f9b82de9 100644 --- a/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelControllerActionsDispatcher.mm +++ b/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelControllerActionsDispatcher.mm @@ -51,14 +51,14 @@ - (int)bidForHandlingKeyDown:(NSEvent *)_event { using ASM = ActionsShortcutsManager; struct Tags { - int file_enter = ASM::TagFromAction("menu.file.enter").value(); - int file_open = ASM::TagFromAction("menu.file.open").value(); - int go_root = ASM::TagFromAction("panel.go_root").value(); - int go_home = ASM::TagFromAction("panel.go_home").value(); - int show_preview = ASM::TagFromAction("panel.show_preview").value(); - int go_into_folder = ASM::TagFromAction("panel.go_into_folder").value(); - int go_into_enclosing_folder = ASM::TagFromAction("panel.go_into_enclosing_folder").value(); - int show_context_menu = ASM::TagFromAction("panel.show_context_menu").value(); + int file_enter = ASM::Instance().TagFromAction("menu.file.enter").value(); + int file_open = ASM::Instance().TagFromAction("menu.file.open").value(); + int go_root = ASM::Instance().TagFromAction("panel.go_root").value(); + int go_home = ASM::Instance().TagFromAction("panel.go_home").value(); + int show_preview = ASM::Instance().TagFromAction("panel.show_preview").value(); + int go_into_folder = ASM::Instance().TagFromAction("panel.go_into_folder").value(); + int go_into_enclosing_folder = ASM::Instance().TagFromAction("panel.go_into_enclosing_folder").value(); + int show_context_menu = ASM::Instance().TagFromAction("panel.show_context_menu").value(); } static const tags; const std::optional event_action_tag = ASM::Instance().FirstOfActionTagsFromShortcut( @@ -76,7 +76,7 @@ - (int)bidForHandlingKeyDown:(NSEvent *)_event if( event_action_tag == tags.go_home ) { if( _handle ) { - static int tag = ActionsShortcutsManager::TagFromAction("menu.go.home").value(); + static int tag = ASM::Instance().TagFromAction("menu.go.home").value(); [[NSApp menu] performActionForItemWithTagHierarchical:tag]; } return view::BiddingPriority::High; @@ -84,7 +84,7 @@ - (int)bidForHandlingKeyDown:(NSEvent *)_event if( event_action_tag == tags.go_root ) { if( _handle ) { - static int tag = ActionsShortcutsManager::TagFromAction("menu.go.root").value(); + static int tag = ASM::Instance().TagFromAction("menu.go.root").value(); [[NSApp menu] performActionForItemWithTagHierarchical:tag]; } return view::BiddingPriority::High; @@ -92,7 +92,7 @@ - (int)bidForHandlingKeyDown:(NSEvent *)_event if( event_action_tag == tags.go_into_folder ) { if( _handle ) { - static int tag = ActionsShortcutsManager::TagFromAction("menu.go.into_folder").value(); + static int tag = ASM::Instance().TagFromAction("menu.go.into_folder").value(); [[NSApp menu] performActionForItemWithTagHierarchical:tag]; } return view::BiddingPriority::High; @@ -100,7 +100,7 @@ - (int)bidForHandlingKeyDown:(NSEvent *)_event if( event_action_tag == tags.go_into_enclosing_folder ) { if( _handle ) { - static int tag = ActionsShortcutsManager::TagFromAction("menu.go.enclosing_folder").value(); + static int tag = ASM::Instance().TagFromAction("menu.go.enclosing_folder").value(); [[NSApp menu] performActionForItemWithTagHierarchical:tag]; } return view::BiddingPriority::High; diff --git a/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelView.mm b/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelView.mm index f761a349c..957d3ad81 100644 --- a/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelView.mm +++ b/Source/NimbleCommander/NimbleCommander/States/FilePanels/PanelView.mm @@ -537,20 +537,20 @@ - (void)keyDown:(NSEvent *)event [self checkKeyboardModifierFlags:event.modifierFlags]; struct Tags { - int up = ASM::TagFromAction("panel.move_up").value(); - int down = ASM::TagFromAction("panel.move_down").value(); - int left = ASM::TagFromAction("panel.move_left").value(); - int right = ASM::TagFromAction("panel.move_right").value(); - int first = ASM::TagFromAction("panel.move_first").value(); - int last = ASM::TagFromAction("panel.move_last").value(); - int page_down = ASM::TagFromAction("panel.move_next_page").value(); - int page_up = ASM::TagFromAction("panel.move_prev_page").value(); - int invert_and_move = ASM::TagFromAction("panel.move_next_and_invert_selection").value(); - int invert = ASM::TagFromAction("panel.invert_item_selection").value(); - int scroll_down = ASM::TagFromAction("panel.scroll_next_page").value(); - int scroll_up = ASM::TagFromAction("panel.scroll_prev_page").value(); - int scroll_home = ASM::TagFromAction("panel.scroll_first").value(); - int scroll_end = ASM::TagFromAction("panel.scroll_last").value(); + int up = ASM::Instance().TagFromAction("panel.move_up").value(); + int down = ASM::Instance().TagFromAction("panel.move_down").value(); + int left = ASM::Instance().TagFromAction("panel.move_left").value(); + int right = ASM::Instance().TagFromAction("panel.move_right").value(); + int first = ASM::Instance().TagFromAction("panel.move_first").value(); + int last = ASM::Instance().TagFromAction("panel.move_last").value(); + int page_down = ASM::Instance().TagFromAction("panel.move_next_page").value(); + int page_up = ASM::Instance().TagFromAction("panel.move_prev_page").value(); + int invert_and_move = ASM::Instance().TagFromAction("panel.move_next_and_invert_selection").value(); + int invert = ASM::Instance().TagFromAction("panel.invert_item_selection").value(); + int scroll_down = ASM::Instance().TagFromAction("panel.scroll_next_page").value(); + int scroll_up = ASM::Instance().TagFromAction("panel.scroll_prev_page").value(); + int scroll_home = ASM::Instance().TagFromAction("panel.scroll_first").value(); + int scroll_end = ASM::Instance().TagFromAction("panel.scroll_last").value(); } static const tags; const auto event_data = ActionShortcut::EventData(event); diff --git a/Source/NimbleCommander/NimbleCommander/States/FilePanels/StateActionsDispatcher.mm b/Source/NimbleCommander/NimbleCommander/States/FilePanels/StateActionsDispatcher.mm index 8fadbe367..171472603 100644 --- a/Source/NimbleCommander/NimbleCommander/States/FilePanels/StateActionsDispatcher.mm +++ b/Source/NimbleCommander/NimbleCommander/States/FilePanels/StateActionsDispatcher.mm @@ -65,12 +65,12 @@ - (BOOL)performKeyEquivalent:(NSEvent *)_event { using ASM = nc::core::ActionsShortcutsManager; struct Tags { - int FocusLeft = ASM::TagFromAction("panel.focus_left_panel").value(); - int FocusRight = ASM::TagFromAction("panel.focus_right_panel").value(); - int MoveUp = ASM::TagFromAction("menu.view.panels_position.move_up").value(); - int MoveDown = ASM::TagFromAction("menu.view.panels_position.move_down").value(); - int Show = ASM::TagFromAction("menu.view.panels_position.showpanels").value(); - int FocusTerminal = ASM::TagFromAction("menu.view.panels_position.focusterminal").value(); + int FocusLeft = ASM::Instance().TagFromAction("panel.focus_left_panel").value(); + int FocusRight = ASM::Instance().TagFromAction("panel.focus_right_panel").value(); + int MoveUp = ASM::Instance().TagFromAction("menu.view.panels_position.move_up").value(); + int MoveDown = ASM::Instance().TagFromAction("menu.view.panels_position.move_down").value(); + int Show = ASM::Instance().TagFromAction("menu.view.panels_position.showpanels").value(); + int FocusTerminal = ASM::Instance().TagFromAction("menu.view.panels_position.focusterminal").value(); } static const tags; NSString *characters = _event.charactersIgnoringModifiers; diff --git a/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelMainSplitView.mm b/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelMainSplitView.mm index 100f2d1a7..0cd2d542a 100644 --- a/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelMainSplitView.mm +++ b/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelMainSplitView.mm @@ -259,8 +259,8 @@ - (BOOL)performKeyEquivalent:(NSEvent *)_event { using ASM = nc::core::ActionsShortcutsManager; struct Tags { - int move_left = ASM::TagFromAction("menu.view.panels_position.move_left").value(); - int move_right = ASM::TagFromAction("menu.view.panels_position.move_right").value(); + int move_left = ASM::Instance().TagFromAction("menu.view.panels_position.move_left").value(); + int move_right = ASM::Instance().TagFromAction("menu.view.panels_position.move_right").value(); } static const tags; const std::optional event_action_tag = ASM::Instance().FirstOfActionTagsFromShortcut( @@ -434,9 +434,9 @@ - (BOOL)validateUserInterfaceItem:(id)_item { using nc::core::ActionsShortcutsManager; static const int move_left_tag = - ActionsShortcutsManager::TagFromAction("menu.view.panels_position.move_left").value_or(-1); + ActionsShortcutsManager::Instance().TagFromAction("menu.view.panels_position.move_left").value(); static const int move_right_tag = - ActionsShortcutsManager::TagFromAction("menu.view.panels_position.move_right").value_or(-1); + ActionsShortcutsManager::Instance().TagFromAction("menu.view.panels_position.move_right").value(); const long item_tag = _item.tag; if( item_tag == move_left_tag ) { diff --git a/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelsTabbedHolder.mm b/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelsTabbedHolder.mm index cf8ade963..52ab1ef15 100644 --- a/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelsTabbedHolder.mm +++ b/Source/NimbleCommander/NimbleCommander/States/FilePanels/Views/FilePanelsTabbedHolder.mm @@ -224,18 +224,18 @@ - (BOOL)performKeyEquivalent:(NSEvent *)_event { using ASM = nc::core::ActionsShortcutsManager; struct Tags { - int prev = ASM::TagFromAction("panel.show_previous_tab").value(); - int next = ASM::TagFromAction("panel.show_next_tab").value(); - int t1 = ASM::TagFromAction("panel.show_tab_no_1").value(); - int t2 = ASM::TagFromAction("panel.show_tab_no_2").value(); - int t3 = ASM::TagFromAction("panel.show_tab_no_3").value(); - int t4 = ASM::TagFromAction("panel.show_tab_no_4").value(); - int t5 = ASM::TagFromAction("panel.show_tab_no_5").value(); - int t6 = ASM::TagFromAction("panel.show_tab_no_6").value(); - int t7 = ASM::TagFromAction("panel.show_tab_no_7").value(); - int t8 = ASM::TagFromAction("panel.show_tab_no_8").value(); - int t9 = ASM::TagFromAction("panel.show_tab_no_9").value(); - int t10 = ASM::TagFromAction("panel.show_tab_no_10").value(); + int prev = ASM::Instance().TagFromAction("panel.show_previous_tab").value(); + int next = ASM::Instance().TagFromAction("panel.show_next_tab").value(); + int t1 = ASM::Instance().TagFromAction("panel.show_tab_no_1").value(); + int t2 = ASM::Instance().TagFromAction("panel.show_tab_no_2").value(); + int t3 = ASM::Instance().TagFromAction("panel.show_tab_no_3").value(); + int t4 = ASM::Instance().TagFromAction("panel.show_tab_no_4").value(); + int t5 = ASM::Instance().TagFromAction("panel.show_tab_no_5").value(); + int t6 = ASM::Instance().TagFromAction("panel.show_tab_no_6").value(); + int t7 = ASM::Instance().TagFromAction("panel.show_tab_no_7").value(); + int t8 = ASM::Instance().TagFromAction("panel.show_tab_no_8").value(); + int t9 = ASM::Instance().TagFromAction("panel.show_tab_no_9").value(); + int t10 = ASM::Instance().TagFromAction("panel.show_tab_no_10").value(); } static const tags; const auto resp_view = nc::objc_cast(self.window.firstResponder); diff --git a/Source/NimbleCommander/NimbleCommander/States/InternalViewer/MainWindowInternalViewerState.mm b/Source/NimbleCommander/NimbleCommander/States/InternalViewer/MainWindowInternalViewerState.mm index 4bd006514..71866674c 100644 --- a/Source/NimbleCommander/NimbleCommander/States/InternalViewer/MainWindowInternalViewerState.mm +++ b/Source/NimbleCommander/NimbleCommander/States/InternalViewer/MainWindowInternalViewerState.mm @@ -121,7 +121,7 @@ - (IBAction)OnFileInternalBigViewCommand:(id)sender - (BOOL)validateMenuItem:(NSMenuItem *)item { const long tag = item.tag; - static const int close_tag = nc::core::ActionsShortcutsManager::TagFromAction("menu.file.close").value(); + static const int close_tag = nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.file.close").value(); if( tag == close_tag ) { item.title = NSLocalizedString(@"Close Viewer", "Menu item title for closing internal viewer state"); return true; diff --git a/Source/NimbleCommander/NimbleCommander/States/MainWindow.mm b/Source/NimbleCommander/NimbleCommander/States/MainWindow.mm index af673519b..68cb40ca5 100644 --- a/Source/NimbleCommander/NimbleCommander/States/MainWindow.mm +++ b/Source/NimbleCommander/NimbleCommander/States/MainWindow.mm @@ -96,14 +96,14 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item { const long tag = item.tag; - static const int close_tag = nc::core::ActionsShortcutsManager::TagFromAction("menu.file.close").value(); + static const int close_tag = nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.file.close").value(); if( tag == close_tag ) { item.title = g_CloseWindowTitle; return true; } static const int close_window_tag = - nc::core::ActionsShortcutsManager::TagFromAction("menu.file.close_window").value(); + nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.file.close_window").value(); if( tag == close_window_tag ) { item.hidden = true; return true; diff --git a/Source/NimbleCommander/NimbleCommander/States/MainWindowController.mm b/Source/NimbleCommander/NimbleCommander/States/MainWindowController.mm index 4fc6560c6..3f7028631 100644 --- a/Source/NimbleCommander/NimbleCommander/States/MainWindowController.mm +++ b/Source/NimbleCommander/NimbleCommander/States/MainWindowController.mm @@ -432,7 +432,7 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item const long tag = item.tag; static const int show_toolbal_tag = - nc::core::ActionsShortcutsManager::TagFromAction("menu.view.show_toolbar").value(); + nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.view.show_toolbar").value(); if( tag == show_toolbal_tag ) { item.title = self.toolbarVisible ? g_HideToolbarTitle : g_ShowToolbarTitle; return self.window.toolbar != nil; diff --git a/Source/NimbleCommander/NimbleCommander/States/Terminal/ShellState.mm b/Source/NimbleCommander/NimbleCommander/States/Terminal/ShellState.mm index dec985054..91c7a40eb 100644 --- a/Source/NimbleCommander/NimbleCommander/States/Terminal/ShellState.mm +++ b/Source/NimbleCommander/NimbleCommander/States/Terminal/ShellState.mm @@ -370,7 +370,7 @@ - (IBAction)OnShowTerminal:(id) [[maybe_unused]] _sender - (BOOL)validateMenuItem:(NSMenuItem *)item { static const int show_terminal_tag = - nc::core::ActionsShortcutsManager::TagFromAction("menu.view.show_terminal").value(); + nc::core::ActionsShortcutsManager::Instance().TagFromAction("menu.view.show_terminal").value(); const long tag = item.tag; if( tag == show_terminal_tag ) { item.title = NSLocalizedString(@"Hide Terminal", "Menu item title for hiding terminal"); diff --git a/Source/NimbleCommander/NimbleCommander/Tests/ActionsShortcutsManager_UT.cpp b/Source/NimbleCommander/NimbleCommander/Tests/ActionsShortcutsManager_UT.cpp index 3b74f4538..2da3ca37f 100644 --- a/Source/NimbleCommander/NimbleCommander/Tests/ActionsShortcutsManager_UT.cpp +++ b/Source/NimbleCommander/NimbleCommander/Tests/ActionsShortcutsManager_UT.cpp @@ -19,14 +19,18 @@ static const auto g_EmptyConfigJSON = R"({ TEST_CASE(PREFIX "TagFromAction") { - CHECK(ASM::TagFromAction("menu.edit.copy") == 12'000); // Valid query - CHECK(ASM::TagFromAction("menu.i.dont.exist") == std::nullopt); // Invalid query + ConfigImpl config{g_EmptyConfigJSON, std::make_shared("")}; + const ASM manager{config}; + CHECK(manager.TagFromAction("menu.edit.copy") == 12'000); // Valid query + CHECK(manager.TagFromAction("menu.i.dont.exist") == std::nullopt); // Invalid query } TEST_CASE(PREFIX "ActionFromTag") { - CHECK(ASM::ActionFromTag(12'000) == "menu.edit.copy"); // Valid query - CHECK(ASM::ActionFromTag(346'242) == std::nullopt); // Invalid query + ConfigImpl config{g_EmptyConfigJSON, std::make_shared("")}; + const ASM manager{config}; + CHECK(manager.ActionFromTag(12'000) == "menu.edit.copy"); // Valid query + CHECK(manager.ActionFromTag(346'242) == std::nullopt); // Invalid query } TEST_CASE(PREFIX "ShortCutFromAction") @@ -118,18 +122,18 @@ TEST_CASE(PREFIX "ActionTagsFromShortCut") auto tags = manager.ActionTagsFromShortcut(AS("⌘1")); REQUIRE(tags); REQUIRE(std::set(tags->begin(), tags->end()) == - std::set{ASM::TagFromAction("menu.go.quick_lists.parent_folders").value(), - ASM::TagFromAction("viewer.toggle_text").value()}); + std::set{manager.TagFromAction("menu.go.quick_lists.parent_folders").value(), + manager.TagFromAction("viewer.toggle_text").value()}); } SECTION("Shortcut used by two actions in different domains, specify first") { REQUIRE(manager.ActionTagsFromShortcut(AS("⌘1"), "menu.") == - ASM::ActionTags{ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()}); + ASM::ActionTags{manager.TagFromAction("menu.go.quick_lists.parent_folders").value()}); } SECTION("Shortcut used by two actions in different domains, specify second") { REQUIRE(manager.ActionTagsFromShortcut(AS("⌘1"), "viewer.") == - ASM::ActionTags{ASM::TagFromAction("viewer.toggle_text").value()}); + ASM::ActionTags{manager.TagFromAction("viewer.toggle_text").value()}); } SECTION("Shortcut is used by by two actions by default and one via override") { @@ -138,9 +142,9 @@ TEST_CASE(PREFIX "ActionTagsFromShortCut") REQUIRE(tags); REQUIRE(std::set(tags->begin(), tags->end()) == std::set{ - ASM::TagFromAction("menu.go.quick_lists.parent_folders").value(), - ASM::TagFromAction("viewer.toggle_text").value(), - ASM::TagFromAction("menu.window.zoom").value(), + manager.TagFromAction("menu.go.quick_lists.parent_folders").value(), + manager.TagFromAction("viewer.toggle_text").value(), + manager.TagFromAction("menu.window.zoom").value(), }); } SECTION("Shortcut is used by by two actions by default and one via override (multiple shortcuts)") @@ -150,9 +154,9 @@ TEST_CASE(PREFIX "ActionTagsFromShortCut") REQUIRE(tags); REQUIRE(std::set(tags->begin(), tags->end()) == std::set{ - ASM::TagFromAction("menu.go.quick_lists.parent_folders").value(), - ASM::TagFromAction("viewer.toggle_text").value(), - ASM::TagFromAction("menu.window.zoom").value(), + manager.TagFromAction("menu.go.quick_lists.parent_folders").value(), + manager.TagFromAction("viewer.toggle_text").value(), + manager.TagFromAction("menu.window.zoom").value(), }); } SECTION("After setting and removing the override its not reported as being used") @@ -171,25 +175,25 @@ TEST_CASE(PREFIX "FirstOfActionTagsFromShortCut") REQUIRE(manager.FirstOfActionTagsFromShortcut({}, AS("⌘1")) == std::nullopt); REQUIRE(manager.FirstOfActionTagsFromShortcut(std::initializer_list{346'242}, AS("⌘1")) == std::nullopt); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()}, - AS("⌘1")) == ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()); + std::initializer_list{manager.TagFromAction("menu.go.quick_lists.parent_folders").value()}, + AS("⌘1")) == manager.TagFromAction("menu.go.quick_lists.parent_folders").value()); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()}, + std::initializer_list{manager.TagFromAction("menu.go.quick_lists.parent_folders").value()}, AS("⌘1"), - "menu.") == ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()); + "menu.") == manager.TagFromAction("menu.go.quick_lists.parent_folders").value()); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("menu.go.quick_lists.parent_folders").value()}, + std::initializer_list{manager.TagFromAction("menu.go.quick_lists.parent_folders").value()}, AS("⌘1"), "viewer.") == std::nullopt); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("viewer.toggle_text").value()}, AS("⌘1")) == - ASM::TagFromAction("viewer.toggle_text").value()); + std::initializer_list{manager.TagFromAction("viewer.toggle_text").value()}, AS("⌘1")) == + manager.TagFromAction("viewer.toggle_text").value()); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("viewer.toggle_text").value()}, AS("⌘1"), "menu.") == + std::initializer_list{manager.TagFromAction("viewer.toggle_text").value()}, AS("⌘1"), "menu.") == std::nullopt); REQUIRE(manager.FirstOfActionTagsFromShortcut( - std::initializer_list{ASM::TagFromAction("viewer.toggle_text").value()}, AS("⌘1"), "viewer.") == - ASM::TagFromAction("viewer.toggle_text").value()); + std::initializer_list{manager.TagFromAction("viewer.toggle_text").value()}, AS("⌘1"), "viewer.") == + manager.TagFromAction("viewer.toggle_text").value()); } TEST_CASE(PREFIX "Configuration persistence") diff --git a/Source/Utility/Utility.xcodeproj/project.pbxproj b/Source/Utility/Utility.xcodeproj/project.pbxproj index 783b6b6be..32bbe355a 100644 --- a/Source/Utility/Utility.xcodeproj/project.pbxproj +++ b/Source/Utility/Utility.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ CFB062452590BB4E0095C748 /* SystemInformation_UT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFB062442590BB4E0095C748 /* SystemInformation_UT.mm */; }; CFB4B68F219944680097E4B4 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF6E64EA1F62721500BF986E /* AppKit.framework */; }; CFC41DAB257159630037677B /* VersionCompare.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFC41DAA257159630037677B /* VersionCompare.mm */; }; + CFC4C4062D208985006C8A0F /* ActionsShortcutsManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CFC4C4052D208985006C8A0F /* ActionsShortcutsManager.cpp */; }; CFC546302136DB4500D288A4 /* UnitTests_main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CFC5462E2136DB4100D288A4 /* UnitTests_main.cpp */; }; CFDAC82F2168E43600DEBA2A /* DiskUtility_UT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CFDAC82E2168E43600DEBA2A /* DiskUtility_UT.mm */; }; CFE3F1C422932CCF009D6AB4 /* ByteCountFormatter_UT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF614AA81F9D871D0005F2DB /* ByteCountFormatter_UT.mm */; }; @@ -209,6 +210,8 @@ CFB0F239215A07830088C18E /* FileMask_UT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FileMask_UT.cpp; path = tests/FileMask_UT.cpp; sourceTree = ""; }; CFC41DA92571594E0037677B /* VersionCompare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = VersionCompare.h; path = include/Utility/VersionCompare.h; sourceTree = ""; }; CFC41DAA257159630037677B /* VersionCompare.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = VersionCompare.mm; path = source/VersionCompare.mm; sourceTree = ""; }; + CFC4C4042D208969006C8A0F /* ActionsShortcutsManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ActionsShortcutsManager.h; path = include/Utility/ActionsShortcutsManager.h; sourceTree = ""; }; + CFC4C4052D208985006C8A0F /* ActionsShortcutsManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ActionsShortcutsManager.cpp; path = source/ActionsShortcutsManager.cpp; sourceTree = ""; }; CFC4F9711F0DEA3D0000B3EE /* SimpleComboBoxPersistentDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SimpleComboBoxPersistentDataSource.h; path = include/Utility/SimpleComboBoxPersistentDataSource.h; sourceTree = ""; }; CFC4F9731F0DEA4E0000B3EE /* SimpleComboBoxPersistentDataSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SimpleComboBoxPersistentDataSource.mm; path = source/SimpleComboBoxPersistentDataSource.mm; sourceTree = ""; }; CFC546272136DAC800D288A4 /* UtilityUT */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = UtilityUT; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -343,6 +346,7 @@ isa = PBXGroup; children = ( CF24E1F32288A9FB00C166FA /* ActionShortcut.h */, + CFC4C4042D208969006C8A0F /* ActionsShortcutsManager.h */, CF29FC5F2155D9A20040FF19 /* AdaptiveDateFormatting.h */, CFDAC2281DFBD8690039A104 /* BlinkScheduler.h */, CF308F2C213ABFAA00915730 /* BriefOnDiskStorage.h */, @@ -405,8 +409,8 @@ CF960E161C992EE7001D8B02 /* Source */ = { isa = PBXGroup; children = ( - CF371DAB2D1849FF0034EB3F /* NSEventModifierFlagsHolder.mm */, CF24E1F52288AA0500C166FA /* ActionShortcut.mm */, + CFC4C4052D208985006C8A0F /* ActionsShortcutsManager.cpp */, CF29FC612155D9AB0040FF19 /* AdaptiveDateFormatting.mm */, CFDAC22A1DFBD8740039A104 /* BlinkScheduler.cpp */, CF308F2E213ABFB300915730 /* BriefOnDiskStorage.cpp */, @@ -440,6 +444,7 @@ CFD62C171C9AECA50021EE7F /* NativeFSManager.mm */, CFE08B2623DBA7BC007E99B8 /* NativeFSManagerImpl.mm */, CF3ABD7723B8A76B00D1878B /* NativeFSManagerVolumeLookup.cpp */, + CF371DAB2D1849FF0034EB3F /* NSEventModifierFlagsHolder.mm */, CFD62C041C9A89BD0021EE7F /* NSMenu+Hierarchical.mm */, CFD62BD31C9A67A60021EE7F /* NSObject+MassObserving.mm */, CFD62BFA1C9A7A8D0021EE7F /* NSTimer+Tolerance.mm */, @@ -594,6 +599,7 @@ CF5D1DC72B52B9E900750174 /* Tags.cpp in Sources */, CF460151256125E50095FC73 /* NSObject+MassObserving.mm in Sources */, CF46013A256125E50095FC73 /* NativeFSManagerImpl.mm in Sources */, + CFC4C4062D208985006C8A0F /* ActionsShortcutsManager.cpp in Sources */, CF460152256125E50095FC73 /* ButtonWithTextColor.mm in Sources */, CF460141256125E50095FC73 /* MIMResponder.mm in Sources */, CF460138256125E50095FC73 /* UTI.cpp in Sources */, diff --git a/Source/Utility/include/Utility/ActionsShortcutsManager.h b/Source/Utility/include/Utility/ActionsShortcutsManager.h new file mode 100644 index 000000000..27c8909a7 --- /dev/null +++ b/Source/Utility/include/Utility/ActionsShortcutsManager.h @@ -0,0 +1,66 @@ +// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3. +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace nc::utility { + +class ActionsShortcutsManager +{ +public: + // Shortcut represents a key and its modifiers that have to be pressed to trigger an action. + using Shortcut = nc::utility::ActionShortcut; + + // An ordered list of shortcuts. + // The relative order of the shortcuts must be preserved as it has semantic meaning for e.g. menus. + // Empty shortcuts should not be stored in such vectors. + // An inlined vector is used to avoid memory allocating for such tiny memory blocks. + using Shortcuts = absl::InlinedVector; + + // ActionTags represents a list of numberic action tags. + // Normally they are tiny, thus an inline vector is used to avoid memory allocation. + using ActionTags = absl::InlinedVector; + + // Destructor. + virtual ~ActionsShortcutsManager() = default; + + // Returns a numeric tag that corresponds to the given action name. + virtual std::optional TagFromAction(std::string_view _action) const noexcept = 0; + + // Returns an action name of the given numeric tag. + virtual std::optional ActionFromTag(int _tag) const noexcept = 0; + + // Returns a shortcut assigned to the specified action. + // Returns std::nullopt such action cannot be found. + // Overrides have priority over the default shortcuts. + virtual std::optional ShortcutsFromAction(std::string_view _action) const noexcept = 0; + + // Returns a shortcut assigned to the specified numeric action tag. + // Returns std::nullopt such action cannot be found. + // Overrides have priority over the default shortcuts. + virtual std::optional ShortcutsFromTag(int _tag) const noexcept = 0; + + // Returns a default shortcut for an action specified by its numeric tag. + // Returns std::nullopt such action cannot be found. + virtual std::optional DefaultShortcutsFromTag(int _tag) const noexcept = 0; + + // Returns an unordered list of numeric tags of actions that have the specified shortcut. + // An optional domain parameter can be specified to filter the output by only leaving actions that have the + // specified domain in their name. + virtual std::optional ActionTagsFromShortcut(Shortcut _sc, + std::string_view _in_domain = {}) const noexcept = 0; + + // Syntax sugar around ActionTagsFromShortCut(_sc, _in_domain) and find_first_of(_of_tags). + // Returns the first tag from the specified set. + // The order is not defined in case of ambiguities. + virtual std::optional FirstOfActionTagsFromShortcut(std::span _of_tags, + Shortcut _sc, + std::string_view _in_domain = {}) const noexcept = 0; +}; + +} // namespace nc::utility diff --git a/Source/Utility/source/ActionsShortcutsManager.cpp b/Source/Utility/source/ActionsShortcutsManager.cpp new file mode 100644 index 000000000..acf3e28b6 --- /dev/null +++ b/Source/Utility/source/ActionsShortcutsManager.cpp @@ -0,0 +1,2 @@ +// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3. +#include diff --git a/Source/Viewer/include/Viewer/ViewerViewController.h b/Source/Viewer/include/Viewer/ViewerViewController.h index 3195b6f35..81513b3de 100644 --- a/Source/Viewer/include/Viewer/ViewerViewController.h +++ b/Source/Viewer/include/Viewer/ViewerViewController.h @@ -14,7 +14,8 @@ class History; }; namespace nc::utility { struct ActionShortcut; -} +class ActionsShortcutsManager; +} // namespace nc::utility @interface NCViewerViewController : NSResponder @@ -31,7 +32,7 @@ struct ActionShortcut; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithHistory:(nc::viewer::History &)_history config:(nc::config::Config &)_config - shortcutsProvider:(std::function)_shortcuts; + shortcuts:(const nc::utility::ActionsShortcutsManager &)_shortcuts; - (void)setFile:(std::string)path at:(VFSHostPtr)vfs; diff --git a/Source/Viewer/source/ViewerViewController.mm b/Source/Viewer/source/ViewerViewController.mm index f6f1484d7..3843db710 100644 --- a/Source/Viewer/source/ViewerViewController.mm +++ b/Source/Viewer/source/ViewerViewController.mm @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "History.h" #include #include "Internal.h" @@ -95,7 +95,7 @@ @implementation NCViewerViewController { nc::base::SerialQueue m_SearchInFileQueue; nc::viewer::History *m_History; nc::config::Config *m_Config; - std::function m_Shortcuts; + const nc::utility::ActionsShortcutsManager *m_Shortcuts; // UI NCViewerView *m_View; @@ -114,14 +114,14 @@ @implementation NCViewerViewController { - (instancetype)initWithHistory:(nc::viewer::History &)_history config:(nc::config::Config &)_config - shortcutsProvider:(std::function)_shortcuts + shortcuts:(const nc::utility::ActionsShortcutsManager &)_shortcuts { self = [super init]; if( self ) { Log::Debug("created new NCViewerViewController {}", nc::objc_bridge_cast(self)); m_History = &_history; m_Config = &_config; - m_Shortcuts = _shortcuts; + m_Shortcuts = &_shortcuts; m_AutomaticFileRefreshScheduled = false; __weak NCViewerViewController *weak_self = self; m_SearchInFileQueue.SetOnChange( @@ -603,25 +603,44 @@ - (void)onFileChanged - (BOOL)performKeyEquivalent:(NSEvent *)_event { - const auto event_hotkey = nc::utility::ActionShortcut(nc::utility::ActionShortcut::EventData(_event)); - const auto is = [&](std::string_view _action_name) { return m_Shortcuts(_action_name) == event_hotkey; }; - if( is("viewer.toggle_text") ) { + struct Tags { + int toggle_text; + int toggle_hex; + int toggle_preview; + int show_goto; + int refresh; + }; + static const Tags tags = [&] { + Tags t; + t.toggle_text = m_Shortcuts->TagFromAction("viewer.toggle_text").value(); + t.toggle_hex = m_Shortcuts->TagFromAction("viewer.toggle_hex").value(); + t.toggle_preview = m_Shortcuts->TagFromAction("viewer.toggle_preview").value(); + t.show_goto = m_Shortcuts->TagFromAction("viewer.show_goto").value(); + t.refresh = m_Shortcuts->TagFromAction("viewer.refresh").value(); + return t; + }(); + + const auto event_shortcut = nc::utility::ActionShortcut(nc::utility::ActionShortcut::EventData(_event)); + const std::optional event_action_tag = m_Shortcuts->FirstOfActionTagsFromShortcut( + {reinterpret_cast(&tags), sizeof(tags) / sizeof(int)}, event_shortcut, "viewer."); + + if( event_action_tag == tags.toggle_text ) { [m_View setMode:ViewMode::Text]; return true; } - if( is("viewer.toggle_hex") ) { + if( event_action_tag == tags.toggle_hex ) { [m_View setMode:ViewMode::Hex]; return true; } - if( is("viewer.toggle_preview") ) { + if( event_action_tag == tags.toggle_preview ) { [m_View setMode:ViewMode::Preview]; return true; } - if( is("viewer.show_goto") ) { + if( event_action_tag == tags.show_goto ) { [m_View.footer performFilePositionClick:self]; return true; } - if( is("viewer.refresh") ) { + if( event_action_tag == tags.refresh ) { [self onRefresh]; return true; }