Skip to content

Commit

Permalink
LibGUI: Make GAction scoped to its CObject parent (widget or window)
Browse files Browse the repository at this point in the history
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. :^)
  • Loading branch information
awesomekling committed Feb 2, 2020
1 parent 6ab9dc4 commit 5b47b0d
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 109 deletions.
1 change: 1 addition & 0 deletions Libraries/LibCore/CObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ class CObject
void deferred_invoke(Function<void(CObject&)>);

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&);
Expand Down
85 changes: 42 additions & 43 deletions Libraries/LibGUI/GAction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,113 +32,114 @@

namespace GCommonActions {

NonnullRefPtr<GAction> make_open_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_open_action(Function<void(GAction&)> 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<GAction> make_move_to_front_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_move_to_front_action(Function<void(GAction&)> 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<GAction> make_move_to_back_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_move_to_back_action(Function<void(GAction&)> 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<GAction> make_undo_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_undo_action(Function<void(GAction&)> 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<GAction> make_redo_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_redo_action(Function<void(GAction&)> 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<GAction> make_delete_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_delete_action(Function<void(GAction&)> 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<GAction> make_cut_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_cut_action(Function<void(GAction&)> 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<GAction> make_copy_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_copy_action(Function<void(GAction&)> 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<GAction> make_paste_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_paste_action(Function<void(GAction&)> 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<GAction> make_fullscreen_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_fullscreen_action(Function<void(GAction&)> 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<GAction> make_quit_action(Function<void(GAction&)> callback)
{
return GAction::create("Quit", { Mod_Alt, Key_F4 }, move(callback));
}

NonnullRefPtr<GAction> make_go_back_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_go_back_action(Function<void(GAction&)> 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<GAction> make_go_forward_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_go_forward_action(Function<void(GAction&)> 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<GAction> make_go_home_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_go_home_action(Function<void(GAction&)> 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<GAction> make_reload_action(Function<void(GAction&)> callback, GWidget* widget)
NonnullRefPtr<GAction> make_reload_action(Function<void(GAction&)> 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<void(GAction&)> on_activation_callback, GWidget* widget)
: on_activation(move(on_activation_callback))
GAction::GAction(const StringView& text, Function<void(GAction&)> 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<GraphicsBitmap>&& icon, Function<void(GAction&)> on_activation_callback, GWidget* widget)
: on_activation(move(on_activation_callback))
GAction::GAction(const StringView& text, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> 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<void(GAction&)> on_activation_callback, GWidget* widget)
: GAction(text, shortcut, nullptr, move(on_activation_callback), widget)
GAction::GAction(const StringView& text, const GShortcut& shortcut, Function<void(GAction&)> on_activation_callback, CObject* parent)
: GAction(text, shortcut, nullptr, move(on_activation_callback), parent)
{
}

GAction::GAction(const StringView& text, const GShortcut& shortcut, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> on_activation_callback, GWidget* widget)
: on_activation(move(on_activation_callback))
GAction::GAction(const StringView& text, const GShortcut& shortcut, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> 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<GWidget>(*parent)) {
m_scope = ShortcutScope::WidgetLocal;
m_widget->register_local_shortcut_action({}, *this);
} else if (parent && is<GWindow>(*parent)) {
m_scope = ShortcutScope::WindowLocal;
} else {
m_scope = ShortcutScope::ApplicationGlobal;
GApplication::the().register_global_shortcut_action({}, *this);
Expand All @@ -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)
Expand Down
73 changes: 39 additions & 34 deletions Libraries/LibGUI/GAction.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,53 +42,51 @@ class GAction;
class GActionGroup;
class GButton;
class GMenuItem;
class GWidget;

namespace GCommonActions {
NonnullRefPtr<GAction> make_open_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_undo_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_redo_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_cut_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_copy_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_paste_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_delete_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_move_to_front_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_move_to_back_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_fullscreen_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_open_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_undo_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_redo_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_cut_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_copy_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_paste_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_delete_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_move_to_front_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_move_to_back_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_fullscreen_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_quit_action(Function<void(GAction&)>);
NonnullRefPtr<GAction> make_go_back_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_go_forward_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_go_home_action(Function<void(GAction&)> callback, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_reload_action(Function<void(GAction&)>, GWidget* widget = nullptr);
NonnullRefPtr<GAction> make_go_back_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_go_forward_action(Function<void(GAction&)>, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_go_home_action(Function<void(GAction&)> callback, CObject* parent = nullptr);
NonnullRefPtr<GAction> make_reload_action(Function<void(GAction&)>, CObject* parent = nullptr);
};

class GAction : public RefCounted<GAction>
, public Weakable<GAction> {
class GAction final : public CObject {
C_OBJECT(GAction)
public:
enum class ShortcutScope {
None,
ApplicationGlobal,
WidgetLocal,
WindowLocal,
ApplicationGlobal,
};
static NonnullRefPtr<GAction> create(const StringView& text, Function<void(GAction&)> callback, GWidget* widget = nullptr)
static NonnullRefPtr<GAction> create(const StringView& text, Function<void(GAction&)> callback, CObject* parent = nullptr)
{
return adopt(*new GAction(text, move(callback), widget));
return adopt(*new GAction(text, move(callback), parent));
}
static NonnullRefPtr<GAction> create(const StringView& text, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> callback, GWidget* widget = nullptr)
static NonnullRefPtr<GAction> create(const StringView& text, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> 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<GAction> create(const StringView& text, const GShortcut& shortcut, Function<void(GAction&)> callback, GWidget* widget = nullptr)
static NonnullRefPtr<GAction> create(const StringView& text, const GShortcut& shortcut, Function<void(GAction&)> callback, CObject* parent = nullptr)
{
return adopt(*new GAction(text, shortcut, move(callback), widget));
return adopt(*new GAction(text, shortcut, move(callback), parent));
}
static NonnullRefPtr<GAction> create(const StringView& text, const GShortcut& shortcut, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> callback, GWidget* widget = nullptr)
static NonnullRefPtr<GAction> create(const StringView& text, const GShortcut& shortcut, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> 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; }
Expand Down Expand Up @@ -124,10 +122,12 @@ class GAction : public RefCounted<GAction>
void set_group(Badge<GActionGroup>, GActionGroup*);

private:
GAction(const StringView& text, Function<void(GAction&)> = nullptr, GWidget* = nullptr);
GAction(const StringView& text, const GShortcut&, Function<void(GAction&)> = nullptr, GWidget* = nullptr);
GAction(const StringView& text, const GShortcut&, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> = nullptr, GWidget* = nullptr);
GAction(const StringView& text, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> = nullptr, GWidget* = nullptr);
virtual bool is_action() const override { return true; }

GAction(const StringView& text, Function<void(GAction&)> = nullptr, CObject* = nullptr);
GAction(const StringView& text, const GShortcut&, Function<void(GAction&)> = nullptr, CObject* = nullptr);
GAction(const StringView& text, const GShortcut&, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> = nullptr, CObject* = nullptr);
GAction(const StringView& text, RefPtr<GraphicsBitmap>&& icon, Function<void(GAction&)> = nullptr, CObject* = nullptr);

template<typename Callback>
void for_each_toolbar_button(Callback);
Expand All @@ -144,7 +144,12 @@ class GAction : public RefCounted<GAction>

HashTable<GButton*> m_buttons;
HashTable<GMenuItem*> m_menu_items;
WeakPtr<GWidget> m_widget;
WeakPtr<GActionGroup> m_action_group;
WeakPtr<CObject> m_activator;
};

template<>
inline bool is<GAction>(const CObject& object)
{
return object.is_action();
}
24 changes: 10 additions & 14 deletions Libraries/LibGUI/GWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>, GAction& action)
{
m_local_shortcut_actions.set(action.shortcut(), &action);
}

void GWidget::unregister_local_shortcut_action(Badge<GAction>, 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<GAction>([&] (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)
Expand Down
5 changes: 0 additions & 5 deletions Libraries/LibGUI/GWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,6 @@ class GWidget : public CObject {

GAction* action_for_key_event(const GKeyEvent&);

void register_local_shortcut_action(Badge<GAction>, GAction&);
void unregister_local_shortcut_action(Badge<GAction>, GAction&);

template<typename Callback>
void for_each_child_widget(Callback callback)
{
Expand Down Expand Up @@ -325,8 +322,6 @@ class GWidget : public CObject {
bool m_layout_dirty { false };
bool m_updates_enabled { true };

HashMap<GShortcut, GAction*> m_local_shortcut_actions;

NonnullRefPtr<PaletteImpl> m_palette;
};

Expand Down
15 changes: 15 additions & 0 deletions Libraries/LibGUI/GWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <LibC/stdlib.h>
#include <LibC/unistd.h>
#include <LibDraw/GraphicsBitmap.h>
#include <LibGUI/GAction.h>
#include <LibGUI/GApplication.h>
#include <LibGUI/GEvent.h>
#include <LibGUI/GPainter.h>
Expand Down Expand Up @@ -633,3 +634,17 @@ void GWindow::notify_state_changed(Badge<GWindowServerConnection>, 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<GAction>([&](auto& action) {
if (action.shortcut() == shortcut) {
found_action = &action;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
return found_action;
}
10 changes: 10 additions & 0 deletions Libraries/LibGUI/GWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
#include <LibDraw/Rect.h>
#include <LibGUI/GWindowType.h>

class GAction;
class GKeyEvent;
class GWMEvent;
class GWidget;
class GWindowServerConnection;
Expand Down Expand Up @@ -170,6 +172,8 @@ class GWindow : public CObject {

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&);
Expand Down Expand Up @@ -210,3 +214,9 @@ class GWindow : public CObject {
bool m_layout_pending { false };
bool m_visible_for_timer_purposes { true };
};

template<>
inline bool is<GWindow>(const CObject& object)
{
return object.is_window();
}
Loading

0 comments on commit 5b47b0d

Please sign in to comment.