From 5b47b0d8673734883f753caae3fa52a64db27e59 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 2 Feb 2020 01:57:57 +0100 Subject: [PATCH] LibGUI: Make GAction scoped to its CObject parent (widget or window) Unparented GActions are still parented to the application like before, making them globally available. This makes it possible to have actions that work whenever a specific window is active, no matter which widget is currently focused. :^) --- Libraries/LibCore/CObject.h | 1 + Libraries/LibGUI/GAction.cpp | 85 ++++++++++---------- Libraries/LibGUI/GAction.h | 73 +++++++++-------- Libraries/LibGUI/GWidget.cpp | 24 +++--- Libraries/LibGUI/GWidget.h | 5 -- Libraries/LibGUI/GWindow.cpp | 15 ++++ Libraries/LibGUI/GWindow.h | 10 +++ Libraries/LibGUI/GWindowServerConnection.cpp | 26 +++--- 8 files changed, 130 insertions(+), 109 deletions(-) diff --git a/Libraries/LibCore/CObject.h b/Libraries/LibCore/CObject.h index a98bfc9edc..df4ad4b815 100644 --- a/Libraries/LibCore/CObject.h +++ b/Libraries/LibCore/CObject.h @@ -115,6 +115,7 @@ public: void deferred_invoke(Function); bool is_widget() const { return m_widget; } + virtual bool is_action() const { return false; } virtual bool is_window() const { return false; } virtual void save_to(AK::JsonObject&); diff --git a/Libraries/LibGUI/GAction.cpp b/Libraries/LibGUI/GAction.cpp index 452f20eaeb..86468761a6 100644 --- a/Libraries/LibGUI/GAction.cpp +++ b/Libraries/LibGUI/GAction.cpp @@ -32,54 +32,54 @@ namespace GCommonActions { -NonnullRefPtr make_open_action(Function callback, GWidget* widget) +NonnullRefPtr make_open_action(Function callback, CObject* parent) { - return GAction::create("Open...", { Mod_Ctrl, Key_O }, GraphicsBitmap::load_from_file("/res/icons/16x16/open.png"), move(callback), widget); + return GAction::create("Open...", { Mod_Ctrl, Key_O }, GraphicsBitmap::load_from_file("/res/icons/16x16/open.png"), move(callback), parent); } -NonnullRefPtr make_move_to_front_action(Function callback, GWidget* widget) +NonnullRefPtr make_move_to_front_action(Function callback, CObject* parent) { - return GAction::create("Move to front", { Mod_Ctrl | Mod_Shift, Key_Up }, GraphicsBitmap::load_from_file("/res/icons/16x16/move-to-front.png"), move(callback), widget); + return GAction::create("Move to front", { Mod_Ctrl | Mod_Shift, Key_Up }, GraphicsBitmap::load_from_file("/res/icons/16x16/move-to-front.png"), move(callback), parent); } -NonnullRefPtr make_move_to_back_action(Function callback, GWidget* widget) +NonnullRefPtr make_move_to_back_action(Function callback, CObject* parent) { - return GAction::create("Move to back", { Mod_Ctrl | Mod_Shift, Key_Down }, GraphicsBitmap::load_from_file("/res/icons/16x16/move-to-back.png"), move(callback), widget); + return GAction::create("Move to back", { Mod_Ctrl | Mod_Shift, Key_Down }, GraphicsBitmap::load_from_file("/res/icons/16x16/move-to-back.png"), move(callback), parent); } -NonnullRefPtr make_undo_action(Function callback, GWidget* widget) +NonnullRefPtr make_undo_action(Function callback, CObject* parent) { - return GAction::create("Undo", { Mod_Ctrl, Key_Z }, GraphicsBitmap::load_from_file("/res/icons/16x16/undo.png"), move(callback), widget); + return GAction::create("Undo", { Mod_Ctrl, Key_Z }, GraphicsBitmap::load_from_file("/res/icons/16x16/undo.png"), move(callback), parent); } -NonnullRefPtr make_redo_action(Function callback, GWidget* widget) +NonnullRefPtr make_redo_action(Function callback, CObject* parent) { - return GAction::create("Redo", { Mod_Ctrl, Key_Y }, GraphicsBitmap::load_from_file("/res/icons/16x16/redo.png"), move(callback), widget); + return GAction::create("Redo", { Mod_Ctrl, Key_Y }, GraphicsBitmap::load_from_file("/res/icons/16x16/redo.png"), move(callback), parent); } -NonnullRefPtr make_delete_action(Function callback, GWidget* widget) +NonnullRefPtr make_delete_action(Function callback, CObject* parent) { - return GAction::create("Delete", { Mod_None, Key_Delete }, GraphicsBitmap::load_from_file("/res/icons/16x16/delete.png"), move(callback), widget); + return GAction::create("Delete", { Mod_None, Key_Delete }, GraphicsBitmap::load_from_file("/res/icons/16x16/delete.png"), move(callback), parent); } -NonnullRefPtr make_cut_action(Function callback, GWidget* widget) +NonnullRefPtr make_cut_action(Function callback, CObject* parent) { - return GAction::create("Cut", { Mod_Ctrl, Key_X }, GraphicsBitmap::load_from_file("/res/icons/cut16.png"), move(callback), widget); + return GAction::create("Cut", { Mod_Ctrl, Key_X }, GraphicsBitmap::load_from_file("/res/icons/cut16.png"), move(callback), parent); } -NonnullRefPtr make_copy_action(Function callback, GWidget* widget) +NonnullRefPtr make_copy_action(Function callback, CObject* parent) { - return GAction::create("Copy", { Mod_Ctrl, Key_C }, GraphicsBitmap::load_from_file("/res/icons/16x16/edit-copy.png"), move(callback), widget); + return GAction::create("Copy", { Mod_Ctrl, Key_C }, GraphicsBitmap::load_from_file("/res/icons/16x16/edit-copy.png"), move(callback), parent); } -NonnullRefPtr make_paste_action(Function callback, GWidget* widget) +NonnullRefPtr make_paste_action(Function callback, CObject* parent) { - return GAction::create("Paste", { Mod_Ctrl, Key_V }, GraphicsBitmap::load_from_file("/res/icons/paste16.png"), move(callback), widget); + return GAction::create("Paste", { Mod_Ctrl, Key_V }, GraphicsBitmap::load_from_file("/res/icons/paste16.png"), move(callback), parent); } -NonnullRefPtr make_fullscreen_action(Function callback, GWidget* widget) +NonnullRefPtr make_fullscreen_action(Function callback, CObject* parent) { - return GAction::create("Fullscreen", { Mod_None, Key_F11 }, move(callback), widget); + return GAction::create("Fullscreen", { Mod_None, Key_F11 }, move(callback), parent); } NonnullRefPtr make_quit_action(Function callback) @@ -87,58 +87,59 @@ NonnullRefPtr make_quit_action(Function callback) return GAction::create("Quit", { Mod_Alt, Key_F4 }, move(callback)); } -NonnullRefPtr make_go_back_action(Function callback, GWidget* widget) +NonnullRefPtr make_go_back_action(Function callback, CObject* parent) { - return GAction::create("Go back", { Mod_Alt, Key_Left }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-back.png"), move(callback), widget); + return GAction::create("Go back", { Mod_Alt, Key_Left }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-back.png"), move(callback), parent); } -NonnullRefPtr make_go_forward_action(Function callback, GWidget* widget) +NonnullRefPtr make_go_forward_action(Function callback, CObject* parent) { - return GAction::create("Go forward", { Mod_Alt, Key_Right }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-forward.png"), move(callback), widget); + return GAction::create("Go forward", { Mod_Alt, Key_Right }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-forward.png"), move(callback), parent); } -NonnullRefPtr make_go_home_action(Function callback, GWidget* widget) +NonnullRefPtr make_go_home_action(Function callback, CObject* parent) { - return GAction::create("Go home", { Mod_Alt, Key_Home }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-home.png"), move(callback), widget); + return GAction::create("Go home", { Mod_Alt, Key_Home }, GraphicsBitmap::load_from_file("/res/icons/16x16/go-home.png"), move(callback), parent); } -NonnullRefPtr make_reload_action(Function callback, GWidget* widget) +NonnullRefPtr make_reload_action(Function callback, CObject* parent) { - return GAction::create("Reload", { Mod_Ctrl, Key_R }, GraphicsBitmap::load_from_file("/res/icons/16x16/reload.png"), move(callback), widget); + return GAction::create("Reload", { Mod_Ctrl, Key_R }, GraphicsBitmap::load_from_file("/res/icons/16x16/reload.png"), move(callback), parent); } } -GAction::GAction(const StringView& text, Function on_activation_callback, GWidget* widget) - : on_activation(move(on_activation_callback)) +GAction::GAction(const StringView& text, Function on_activation_callback, CObject* parent) + : CObject(parent) + , on_activation(move(on_activation_callback)) , m_text(text) - , m_widget(widget ? widget->make_weak_ptr() : nullptr) { } -GAction::GAction(const StringView& text, RefPtr&& icon, Function on_activation_callback, GWidget* widget) - : on_activation(move(on_activation_callback)) +GAction::GAction(const StringView& text, RefPtr&& icon, Function on_activation_callback, CObject* parent) + : CObject(parent) + , on_activation(move(on_activation_callback)) , m_text(text) , m_icon(move(icon)) - , m_widget(widget ? widget->make_weak_ptr() : nullptr) { } -GAction::GAction(const StringView& text, const GShortcut& shortcut, Function on_activation_callback, GWidget* widget) - : GAction(text, shortcut, nullptr, move(on_activation_callback), widget) +GAction::GAction(const StringView& text, const GShortcut& shortcut, Function on_activation_callback, CObject* parent) + : GAction(text, shortcut, nullptr, move(on_activation_callback), parent) { } -GAction::GAction(const StringView& text, const GShortcut& shortcut, RefPtr&& icon, Function on_activation_callback, GWidget* widget) - : on_activation(move(on_activation_callback)) +GAction::GAction(const StringView& text, const GShortcut& shortcut, RefPtr&& icon, Function on_activation_callback, CObject* parent) + : CObject(parent) + , on_activation(move(on_activation_callback)) , m_text(text) , m_icon(move(icon)) , m_shortcut(shortcut) - , m_widget(widget ? widget->make_weak_ptr() : nullptr) { - if (m_widget) { + if (parent && is(*parent)) { m_scope = ShortcutScope::WidgetLocal; - m_widget->register_local_shortcut_action({}, *this); + } else if (parent && is(*parent)) { + m_scope = ShortcutScope::WindowLocal; } else { m_scope = ShortcutScope::ApplicationGlobal; GApplication::the().register_global_shortcut_action({}, *this); @@ -149,8 +150,6 @@ GAction::~GAction() { if (m_shortcut.is_valid() && m_scope == ShortcutScope::ApplicationGlobal) GApplication::the().unregister_global_shortcut_action({}, *this); - if (m_widget && m_scope == ShortcutScope::WidgetLocal) - m_widget->unregister_local_shortcut_action({}, *this); } void GAction::activate(CObject* activator) diff --git a/Libraries/LibGUI/GAction.h b/Libraries/LibGUI/GAction.h index c8190d0a40..31827f66d0 100644 --- a/Libraries/LibGUI/GAction.h +++ b/Libraries/LibGUI/GAction.h @@ -42,53 +42,51 @@ class GAction; class GActionGroup; class GButton; class GMenuItem; -class GWidget; namespace GCommonActions { -NonnullRefPtr make_open_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_undo_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_redo_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_cut_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_copy_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_paste_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_delete_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_move_to_front_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_move_to_back_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_fullscreen_action(Function, GWidget* widget = nullptr); +NonnullRefPtr make_open_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_undo_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_redo_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_cut_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_copy_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_paste_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_delete_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_move_to_front_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_move_to_back_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_fullscreen_action(Function, CObject* parent = nullptr); NonnullRefPtr make_quit_action(Function); -NonnullRefPtr make_go_back_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_go_forward_action(Function, GWidget* widget = nullptr); -NonnullRefPtr make_go_home_action(Function callback, GWidget* widget = nullptr); -NonnullRefPtr make_reload_action(Function, GWidget* widget = nullptr); +NonnullRefPtr make_go_back_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_go_forward_action(Function, CObject* parent = nullptr); +NonnullRefPtr make_go_home_action(Function callback, CObject* parent = nullptr); +NonnullRefPtr make_reload_action(Function, CObject* parent = nullptr); }; -class GAction : public RefCounted - , public Weakable { +class GAction final : public CObject { + C_OBJECT(GAction) public: enum class ShortcutScope { None, - ApplicationGlobal, WidgetLocal, + WindowLocal, + ApplicationGlobal, }; - static NonnullRefPtr create(const StringView& text, Function callback, GWidget* widget = nullptr) + static NonnullRefPtr create(const StringView& text, Function callback, CObject* parent = nullptr) { - return adopt(*new GAction(text, move(callback), widget)); + return adopt(*new GAction(text, move(callback), parent)); } - static NonnullRefPtr create(const StringView& text, RefPtr&& icon, Function callback, GWidget* widget = nullptr) + static NonnullRefPtr create(const StringView& text, RefPtr&& icon, Function callback, CObject* parent = nullptr) { - return adopt(*new GAction(text, move(icon), move(callback), widget)); + return adopt(*new GAction(text, move(icon), move(callback), parent)); } - static NonnullRefPtr create(const StringView& text, const GShortcut& shortcut, Function callback, GWidget* widget = nullptr) + static NonnullRefPtr create(const StringView& text, const GShortcut& shortcut, Function callback, CObject* parent = nullptr) { - return adopt(*new GAction(text, shortcut, move(callback), widget)); + return adopt(*new GAction(text, shortcut, move(callback), parent)); } - static NonnullRefPtr create(const StringView& text, const GShortcut& shortcut, RefPtr&& icon, Function callback, GWidget* widget = nullptr) + static NonnullRefPtr create(const StringView& text, const GShortcut& shortcut, RefPtr&& icon, Function callback, CObject* parent = nullptr) { - return adopt(*new GAction(text, shortcut, move(icon), move(callback), widget)); + return adopt(*new GAction(text, shortcut, move(icon), move(callback), parent)); } - ~GAction(); - GWidget* widget() { return m_widget.ptr(); } - const GWidget* widget() const { return m_widget.ptr(); } + virtual ~GAction() override; String text() const { return m_text; } GShortcut shortcut() const { return m_shortcut; } @@ -124,10 +122,12 @@ public: void set_group(Badge, GActionGroup*); private: - GAction(const StringView& text, Function = nullptr, GWidget* = nullptr); - GAction(const StringView& text, const GShortcut&, Function = nullptr, GWidget* = nullptr); - GAction(const StringView& text, const GShortcut&, RefPtr&& icon, Function = nullptr, GWidget* = nullptr); - GAction(const StringView& text, RefPtr&& icon, Function = nullptr, GWidget* = nullptr); + virtual bool is_action() const override { return true; } + + GAction(const StringView& text, Function = nullptr, CObject* = nullptr); + GAction(const StringView& text, const GShortcut&, Function = nullptr, CObject* = nullptr); + GAction(const StringView& text, const GShortcut&, RefPtr&& icon, Function = nullptr, CObject* = nullptr); + GAction(const StringView& text, RefPtr&& icon, Function = nullptr, CObject* = nullptr); template void for_each_toolbar_button(Callback); @@ -144,7 +144,12 @@ private: HashTable m_buttons; HashTable m_menu_items; - WeakPtr m_widget; WeakPtr m_action_group; WeakPtr m_activator; }; + +template<> +inline bool is(const CObject& object) +{ + return object.is_action(); +} diff --git a/Libraries/LibGUI/GWidget.cpp b/Libraries/LibGUI/GWidget.cpp index a16ea0ce82..0ee5a83356 100644 --- a/Libraries/LibGUI/GWidget.cpp +++ b/Libraries/LibGUI/GWidget.cpp @@ -626,20 +626,16 @@ bool GWidget::is_backmost() const GAction* GWidget::action_for_key_event(const GKeyEvent& event) { - auto it = m_local_shortcut_actions.find(GShortcut(event.modifiers(), (KeyCode)event.key())); - if (it == m_local_shortcut_actions.end()) - return nullptr; - return (*it).value; -} - -void GWidget::register_local_shortcut_action(Badge, GAction& action) -{ - m_local_shortcut_actions.set(action.shortcut(), &action); -} - -void GWidget::unregister_local_shortcut_action(Badge, GAction& action) -{ - m_local_shortcut_actions.remove(action.shortcut()); + GShortcut shortcut(event.modifiers(), (KeyCode)event.key()); + GAction* found_action = nullptr; + for_each_child_of_type([&] (auto& action) { + if (action.shortcut() == shortcut) { + found_action = &action; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return found_action; } void GWidget::set_updates_enabled(bool enabled) diff --git a/Libraries/LibGUI/GWidget.h b/Libraries/LibGUI/GWidget.h index 7e0488a57b..f9a3921564 100644 --- a/Libraries/LibGUI/GWidget.h +++ b/Libraries/LibGUI/GWidget.h @@ -241,9 +241,6 @@ public: GAction* action_for_key_event(const GKeyEvent&); - void register_local_shortcut_action(Badge, GAction&); - void unregister_local_shortcut_action(Badge, GAction&); - template void for_each_child_widget(Callback callback) { @@ -325,8 +322,6 @@ private: bool m_layout_dirty { false }; bool m_updates_enabled { true }; - HashMap m_local_shortcut_actions; - NonnullRefPtr m_palette; }; diff --git a/Libraries/LibGUI/GWindow.cpp b/Libraries/LibGUI/GWindow.cpp index f91a790c62..4bab839163 100644 --- a/Libraries/LibGUI/GWindow.cpp +++ b/Libraries/LibGUI/GWindow.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -633,3 +634,17 @@ void GWindow::notify_state_changed(Badge, bool minimize } } } + +GAction* GWindow::action_for_key_event(const GKeyEvent& event) +{ + GShortcut shortcut(event.modifiers(), (KeyCode)event.key()); + GAction* found_action = nullptr; + for_each_child_of_type([&](auto& action) { + if (action.shortcut() == shortcut) { + found_action = &action; + return IterationDecision::Break; + } + return IterationDecision::Continue; + }); + return found_action; +} diff --git a/Libraries/LibGUI/GWindow.h b/Libraries/LibGUI/GWindow.h index 92f5de9c5d..ae47f1efe8 100644 --- a/Libraries/LibGUI/GWindow.h +++ b/Libraries/LibGUI/GWindow.h @@ -35,6 +35,8 @@ #include #include +class GAction; +class GKeyEvent; class GWMEvent; class GWidget; class GWindowServerConnection; @@ -170,6 +172,8 @@ public: virtual bool is_visible_for_timer_purposes() const override { return m_visible_for_timer_purposes; } + GAction* action_for_key_event(const GKeyEvent&); + protected: GWindow(CObject* parent = nullptr); virtual void wm_event(GWMEvent&); @@ -210,3 +214,9 @@ private: bool m_layout_pending { false }; bool m_visible_for_timer_purposes { true }; }; + +template<> +inline bool is(const CObject& object) +{ + return object.is_window(); +} diff --git a/Libraries/LibGUI/GWindowServerConnection.cpp b/Libraries/LibGUI/GWindowServerConnection.cpp index 808b4be0a9..c8ac8c775f 100644 --- a/Libraries/LibGUI/GWindowServerConnection.cpp +++ b/Libraries/LibGUI/GWindowServerConnection.cpp @@ -136,20 +136,20 @@ void GWindowServerConnection::handle(const WindowClient::KeyDown& message) key_event->m_text = String(&ch, 1); } - if (auto* focused_widget = window->focused_widget()) { - if (auto* action = focused_widget->action_for_key_event(*key_event)) { - if (action->is_enabled()) { - action->activate(); - return; - } - } - } + GAction* action = nullptr; - if (auto* action = GApplication::the().action_for_key_event(*key_event)) { - if (action->is_enabled()) { - action->activate(); - return; - } + if (auto* focused_widget = window->focused_widget()) + action = focused_widget->action_for_key_event(*key_event); + + if (!action) + action = window->action_for_key_event(*key_event); + + if (!action) + action = GApplication::the().action_for_key_event(*key_event); + + if (action && action->is_enabled()) { + action->activate(); + return; } CEventLoop::current().post_event(*window, move(key_event)); }