Serenity Operating System
1/*
2 * Copyright (c) 2018-2023, Andreas Kling <kling@serenityos.org>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#include <LibGUI/Action.h>
8#include <LibGUI/ActionGroup.h>
9#include <LibGUI/Application.h>
10#include <LibGUI/Button.h>
11#include <LibGUI/MenuItem.h>
12#include <LibGUI/Window.h>
13
14namespace GUI {
15
16NonnullRefPtr<Action> Action::create(DeprecatedString text, Function<void(Action&)> callback, Core::Object* parent)
17{
18 return adopt_ref(*new Action(move(text), move(callback), parent));
19}
20
21NonnullRefPtr<Action> Action::create(DeprecatedString text, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> callback, Core::Object* parent)
22{
23 return adopt_ref(*new Action(move(text), move(icon), move(callback), parent));
24}
25
26NonnullRefPtr<Action> Action::create(DeprecatedString text, Shortcut const& shortcut, Function<void(Action&)> callback, Core::Object* parent)
27{
28 return adopt_ref(*new Action(move(text), shortcut, move(callback), parent));
29}
30
31NonnullRefPtr<Action> Action::create(DeprecatedString text, Shortcut const& shortcut, Shortcut const& alternate_shortcut, Function<void(Action&)> callback, Core::Object* parent)
32{
33 return adopt_ref(*new Action(move(text), shortcut, alternate_shortcut, move(callback), parent));
34}
35
36NonnullRefPtr<Action> Action::create(DeprecatedString text, Shortcut const& shortcut, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> callback, Core::Object* parent)
37{
38 return adopt_ref(*new Action(move(text), shortcut, Shortcut {}, move(icon), move(callback), parent));
39}
40
41NonnullRefPtr<Action> Action::create(DeprecatedString text, Shortcut const& shortcut, Shortcut const& alternate_shortcut, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> callback, Core::Object* parent)
42{
43 return adopt_ref(*new Action(move(text), shortcut, alternate_shortcut, move(icon), move(callback), parent));
44}
45
46NonnullRefPtr<Action> Action::create_checkable(DeprecatedString text, Function<void(Action&)> callback, Core::Object* parent)
47{
48 return adopt_ref(*new Action(move(text), move(callback), parent, true));
49}
50
51NonnullRefPtr<Action> Action::create_checkable(DeprecatedString text, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> callback, Core::Object* parent)
52{
53 return adopt_ref(*new Action(move(text), move(icon), move(callback), parent, true));
54}
55
56NonnullRefPtr<Action> Action::create_checkable(DeprecatedString text, Shortcut const& shortcut, Function<void(Action&)> callback, Core::Object* parent)
57{
58 return adopt_ref(*new Action(move(text), shortcut, move(callback), parent, true));
59}
60
61NonnullRefPtr<Action> Action::create_checkable(DeprecatedString text, Shortcut const& shortcut, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> callback, Core::Object* parent)
62{
63 return adopt_ref(*new Action(move(text), shortcut, Shortcut {}, move(icon), move(callback), parent, true));
64}
65
66ErrorOr<NonnullRefPtr<Action>> Action::try_create_checkable(DeprecatedString text, Shortcut const& shortcut, Function<void(Action&)> callback, Core::Object* parent)
67{
68 return adopt_nonnull_ref_or_enomem(new (nothrow) Action(move(text), shortcut, Shortcut {}, move(callback), parent, true));
69}
70
71RefPtr<Action> Action::find_action_for_shortcut(Core::Object& object, Shortcut const& shortcut)
72{
73 RefPtr<Action> found_action = nullptr;
74 object.for_each_child_of_type<Action>([&](auto& action) {
75 if (action.shortcut() == shortcut || action.alternate_shortcut() == shortcut) {
76 found_action = &action;
77 return IterationDecision::Break;
78 }
79 return IterationDecision::Continue;
80 });
81 return found_action;
82}
83
84Action::Action(DeprecatedString text, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
85 : Action(move(text), Shortcut {}, Shortcut {}, nullptr, move(on_activation_callback), parent, checkable)
86{
87}
88
89Action::Action(DeprecatedString text, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
90 : Action(move(text), Shortcut {}, Shortcut {}, move(icon), move(on_activation_callback), parent, checkable)
91{
92}
93
94Action::Action(DeprecatedString text, Shortcut const& shortcut, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
95 : Action(move(text), shortcut, Shortcut {}, nullptr, move(on_activation_callback), parent, checkable)
96{
97}
98
99Action::Action(DeprecatedString text, Shortcut const& shortcut, Shortcut const& alternate_shortcut, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
100 : Action(move(text), shortcut, alternate_shortcut, nullptr, move(on_activation_callback), parent, checkable)
101{
102}
103
104Action::Action(DeprecatedString text, Shortcut const& shortcut, Shortcut const& alternate_shortcut, RefPtr<Gfx::Bitmap const> icon, Function<void(Action&)> on_activation_callback, Core::Object* parent, bool checkable)
105 : Core::Object(parent)
106 , on_activation(move(on_activation_callback))
107 , m_text(move(text))
108 , m_icon(move(icon))
109 , m_shortcut(shortcut)
110 , m_alternate_shortcut(alternate_shortcut)
111 , m_checkable(checkable)
112{
113 if (parent && is<Widget>(*parent)) {
114 m_scope = ShortcutScope::WidgetLocal;
115 } else if (parent && is<Window>(*parent)) {
116 m_scope = ShortcutScope::WindowLocal;
117 } else {
118 m_scope = ShortcutScope::ApplicationGlobal;
119 if (auto* app = Application::the()) {
120 app->register_global_shortcut_action({}, *this);
121 }
122 }
123}
124
125Action::~Action()
126{
127 if (m_scope == ShortcutScope::ApplicationGlobal) {
128 if (auto* app = Application::the())
129 app->unregister_global_shortcut_action({}, *this);
130 }
131}
132
133void Action::process_event(Window& window, Event& event)
134{
135 if (is_enabled()) {
136 flash_menubar_menu(window);
137 activate();
138 event.accept();
139 return;
140 }
141 if (swallow_key_event_when_disabled()) {
142 event.accept();
143 return;
144 }
145
146 event.ignore();
147}
148
149void Action::activate(Core::Object* activator)
150{
151 if (!on_activation)
152 return;
153
154 if (activator)
155 m_activator = activator->make_weak_ptr();
156
157 if (is_checkable()) {
158 if (m_action_group) {
159 if (m_action_group->is_unchecking_allowed())
160 set_checked(!is_checked());
161 else
162 set_checked(true);
163 } else {
164 set_checked(!is_checked());
165 }
166 }
167
168 if (activator == nullptr) {
169 for_each_toolbar_button([](auto& button) {
170 button.mimic_pressed();
171 });
172 }
173
174 on_activation(*this);
175 m_activator = nullptr;
176}
177
178void Action::flash_menubar_menu(GUI::Window& window)
179{
180 for (auto& menu_item : m_menu_items)
181 window.flash_menubar_menu_for(*menu_item);
182}
183
184void Action::register_button(Badge<Button>, Button& button)
185{
186 m_buttons.set(&button);
187}
188
189void Action::unregister_button(Badge<Button>, Button& button)
190{
191 m_buttons.remove(&button);
192}
193
194void Action::register_menu_item(Badge<MenuItem>, MenuItem& menu_item)
195{
196 m_menu_items.set(&menu_item);
197}
198
199void Action::unregister_menu_item(Badge<MenuItem>, MenuItem& menu_item)
200{
201 m_menu_items.remove(&menu_item);
202}
203
204template<typename Callback>
205void Action::for_each_toolbar_button(Callback callback)
206{
207 for (auto& it : m_buttons)
208 callback(*it);
209}
210
211template<typename Callback>
212void Action::for_each_menu_item(Callback callback)
213{
214 for (auto& it : m_menu_items)
215 callback(*it);
216}
217
218void Action::set_enabled(bool enabled)
219{
220 if (m_enabled == enabled)
221 return;
222 m_enabled = enabled;
223 for_each_toolbar_button([enabled](auto& button) {
224 button.set_enabled(enabled);
225 });
226 for_each_menu_item([enabled](auto& item) {
227 item.set_enabled(enabled);
228 });
229}
230
231void Action::set_visible(bool visible)
232{
233 if (m_visible == visible)
234 return;
235 m_visible = visible;
236 for_each_toolbar_button([visible](auto& button) {
237 button.set_visible(visible);
238 });
239 for_each_menu_item([visible](auto& item) {
240 item.set_visible(visible);
241 });
242}
243
244void Action::set_checked(bool checked)
245{
246 if (m_checked == checked)
247 return;
248 m_checked = checked;
249
250 if (m_checked && m_action_group) {
251 m_action_group->for_each_action([this](auto& other_action) {
252 if (this == &other_action)
253 return IterationDecision::Continue;
254 if (other_action.is_checkable())
255 other_action.set_checked(false);
256 return IterationDecision::Continue;
257 });
258 }
259
260 for_each_toolbar_button([checked](auto& button) {
261 button.set_checked(checked);
262 });
263 for_each_menu_item([checked](MenuItem& item) {
264 item.set_checked(checked);
265 });
266}
267
268void Action::set_group(Badge<ActionGroup>, ActionGroup* group)
269{
270 m_action_group = AK::make_weak_ptr_if_nonnull(group);
271}
272
273void Action::set_icon(Gfx::Bitmap const* icon)
274{
275 if (m_icon == icon)
276 return;
277 m_icon = icon;
278 for_each_toolbar_button([icon](auto& button) {
279 button.set_icon(icon);
280 });
281 for_each_menu_item([](auto& menu_item) {
282 menu_item.update_from_action({});
283 });
284}
285
286void Action::set_text(DeprecatedString text)
287{
288 if (m_text == text)
289 return;
290 m_text = move(text);
291 for_each_toolbar_button([&](auto& button) {
292 button.set_text(String::from_deprecated_string(m_text).release_value_but_fixme_should_propagate_errors());
293 });
294 for_each_menu_item([&](auto& menu_item) {
295 menu_item.update_from_action({});
296 });
297}
298
299void Action::set_tooltip(DeprecatedString tooltip)
300{
301 if (m_tooltip == tooltip)
302 return;
303 m_tooltip = move(tooltip);
304 for_each_toolbar_button([&](auto& button) {
305 button.set_tooltip(*m_tooltip);
306 });
307 for_each_menu_item([&](auto& menu_item) {
308 menu_item.update_from_action({});
309 });
310}
311
312}