Serenity Operating System
1/*
2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, the SerenityOS developers.
4 * Copyright (c) 2022, networkException <networkexception@serenityos.org>
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 */
8
9#include <AK/JsonObject.h>
10#include <LibCore/Timer.h>
11#include <LibGUI/AbstractButton.h>
12#include <LibGUI/Painter.h>
13#include <LibGUI/Window.h>
14#include <LibGfx/Palette.h>
15
16namespace GUI {
17
18AbstractButton::AbstractButton(String text)
19{
20 set_text(move(text));
21
22 set_focus_policy(GUI::FocusPolicy::StrongFocus);
23 set_background_role(Gfx::ColorRole::Button);
24 set_foreground_role(Gfx::ColorRole::ButtonText);
25
26 m_auto_repeat_timer = add<Core::Timer>();
27 m_auto_repeat_timer->on_timeout = [this] {
28 click();
29 };
30
31 // FIXME: Port JsonValue to the new String class.
32 register_property(
33 "text",
34 [this]() { return this->text().to_deprecated_string(); },
35 [this](auto& value) {
36 this->set_text(String::from_deprecated_string(value.to_deprecated_string()).release_value_but_fixme_should_propagate_errors());
37 return true;
38 });
39 REGISTER_BOOL_PROPERTY("checked", is_checked, set_checked);
40 REGISTER_BOOL_PROPERTY("checkable", is_checkable, set_checkable);
41 REGISTER_BOOL_PROPERTY("exclusive", is_exclusive, set_exclusive);
42}
43
44void AbstractButton::set_text(String text)
45{
46 if (m_text == text)
47 return;
48 m_text = move(text);
49 update();
50}
51
52void AbstractButton::set_checked(bool checked, AllowCallback allow_callback)
53{
54 if (m_checked == checked)
55 return;
56 m_checked = checked;
57
58 if (is_exclusive() && checked && parent_widget()) {
59 bool sibling_had_focus = false;
60 parent_widget()->for_each_child_of_type<AbstractButton>([&](auto& sibling) {
61 if (!sibling.is_exclusive())
62 return IterationDecision::Continue;
63 if (window() && window()->focused_widget() == &sibling)
64 sibling_had_focus = true;
65 if (!sibling.is_checked())
66 return IterationDecision::Continue;
67 sibling.m_checked = false;
68 sibling.update();
69 if (sibling.on_checked)
70 sibling.on_checked(false);
71 return IterationDecision::Continue;
72 });
73 m_checked = true;
74 if (sibling_had_focus)
75 set_focus(true);
76 }
77
78 if (is_exclusive() && parent_widget()) {
79 // In a group of exclusive checkable buttons, only the currently checked button is focusable.
80 parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& button) {
81 if (button.is_exclusive() && button.is_checkable())
82 button.set_focus_policy(button.is_checked() ? GUI::FocusPolicy::StrongFocus : GUI::FocusPolicy::NoFocus);
83 return IterationDecision::Continue;
84 });
85 }
86
87 update();
88 if (on_checked && allow_callback == AllowCallback::Yes)
89 on_checked(checked);
90}
91
92void AbstractButton::set_checkable(bool checkable)
93{
94 if (m_checkable == checkable)
95 return;
96 m_checkable = checkable;
97 update();
98}
99
100void AbstractButton::mousemove_event(MouseEvent& event)
101{
102 bool is_over = rect().contains(event.position());
103 m_hovered = is_over;
104 if (event.buttons() & m_pressed_mouse_button) {
105 bool being_pressed = is_over;
106 if (being_pressed != m_being_pressed) {
107 m_being_pressed = being_pressed;
108 if (m_auto_repeat_interval) {
109 if (!m_being_pressed)
110 m_auto_repeat_timer->stop();
111 else
112 m_auto_repeat_timer->start(m_auto_repeat_interval);
113 }
114 update();
115 }
116 }
117 Widget::mousemove_event(event);
118}
119
120void AbstractButton::mousedown_event(MouseEvent& event)
121{
122 if (event.button() & m_allowed_mouse_buttons_for_pressing) {
123 m_being_pressed = true;
124 m_pressed_mouse_button = event.button();
125 repaint();
126
127 if (m_auto_repeat_interval) {
128 click();
129 m_auto_repeat_timer->start(m_auto_repeat_interval);
130 }
131 event.accept();
132 }
133 Widget::mousedown_event(event);
134}
135
136void AbstractButton::mouseup_event(MouseEvent& event)
137{
138 if (event.button() == m_pressed_mouse_button && m_being_pressed) {
139 bool was_auto_repeating = m_auto_repeat_timer->is_active();
140 m_auto_repeat_timer->stop();
141 m_was_being_pressed = m_being_pressed;
142 ScopeGuard update_was_being_pressed { [this] { m_was_being_pressed = m_being_pressed; } };
143 m_being_pressed = false;
144 m_pressed_mouse_button = MouseButton::None;
145 if (!is_checkable() || is_checked())
146 repaint();
147 if (m_was_being_pressed && !was_auto_repeating) {
148 switch (event.button()) {
149 case MouseButton::Primary:
150 click(event.modifiers());
151 break;
152 case MouseButton::Middle:
153 middle_mouse_click(event.modifiers());
154 break;
155 default:
156 VERIFY_NOT_REACHED();
157 }
158 }
159 }
160 Widget::mouseup_event(event);
161}
162
163void AbstractButton::doubleclick_event(GUI::MouseEvent& event)
164{
165 double_click(event.modifiers());
166 Widget::doubleclick_event(event);
167}
168
169void AbstractButton::enter_event(Core::Event&)
170{
171 m_hovered = true;
172 update();
173}
174
175void AbstractButton::leave_event(Core::Event& event)
176{
177 m_hovered = false;
178 if (m_being_keyboard_pressed)
179 m_being_keyboard_pressed = m_being_pressed = false;
180 update();
181 event.accept();
182 Widget::leave_event(event);
183}
184
185void AbstractButton::focusout_event(GUI::FocusEvent& event)
186{
187 if (m_being_keyboard_pressed) {
188 m_being_pressed = m_being_keyboard_pressed = false;
189 event.accept();
190 update();
191 }
192 Widget::focusout_event(event);
193}
194
195void AbstractButton::keydown_event(KeyEvent& event)
196{
197 if (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space) {
198 m_being_pressed = m_being_keyboard_pressed = true;
199 update();
200 event.accept();
201 return;
202 } else if (m_being_pressed && event.key() == KeyCode::Key_Escape) {
203 m_being_pressed = m_being_keyboard_pressed = false;
204 update();
205 event.accept();
206 return;
207 }
208
209 // Arrow keys switch the currently checked option within an exclusive group of checkable buttons.
210 if (event.is_arrow_key() && !event.modifiers() && is_exclusive() && is_checkable() && parent_widget()) {
211 event.accept();
212 Vector<GUI::AbstractButton&> exclusive_siblings;
213 size_t this_index = 0;
214 parent_widget()->for_each_child_of_type<GUI::AbstractButton>([&](auto& sibling) {
215 if (&sibling == this) {
216 VERIFY(is_enabled());
217 this_index = exclusive_siblings.size();
218 }
219 if (sibling.is_exclusive() && sibling.is_checkable() && sibling.is_enabled())
220 exclusive_siblings.append(sibling);
221 return IterationDecision::Continue;
222 });
223 if (exclusive_siblings.size() <= 1)
224 return;
225 size_t new_checked_index;
226 if (event.key() == KeyCode::Key_Left || event.key() == KeyCode::Key_Up)
227 new_checked_index = this_index == 0 ? exclusive_siblings.size() - 1 : this_index - 1;
228 else
229 new_checked_index = this_index == exclusive_siblings.size() - 1 ? 0 : this_index + 1;
230 exclusive_siblings[new_checked_index].click();
231 return;
232 }
233 Widget::keydown_event(event);
234}
235
236void AbstractButton::keyup_event(KeyEvent& event)
237{
238 bool was_being_pressed = m_being_pressed;
239 if (was_being_pressed && (event.key() == KeyCode::Key_Return || event.key() == KeyCode::Key_Space)) {
240 m_being_pressed = m_being_keyboard_pressed = false;
241 click(event.modifiers());
242 update();
243 event.accept();
244 return;
245 }
246 Widget::keyup_event(event);
247}
248
249void AbstractButton::paint_text(Painter& painter, Gfx::IntRect const& rect, Gfx::Font const& font, Gfx::TextAlignment text_alignment, Gfx::TextWrapping text_wrapping)
250{
251 auto clipped_rect = rect.intersected(this->rect());
252
253 if (!is_enabled()) {
254 painter.draw_text(clipped_rect.translated(1, 1), text(), font, text_alignment, palette().disabled_text_back(), Gfx::TextElision::Right, text_wrapping);
255 painter.draw_text(clipped_rect, text(), font, text_alignment, palette().disabled_text_front(), Gfx::TextElision::Right, text_wrapping);
256 return;
257 }
258
259 if (text().is_empty())
260 return;
261 painter.draw_text(clipped_rect, text(), font, text_alignment, palette().color(foreground_role()), Gfx::TextElision::Right, text_wrapping);
262}
263
264void AbstractButton::change_event(Event& event)
265{
266 if (event.type() == Event::Type::EnabledChange) {
267 if (m_auto_repeat_timer->is_active())
268 m_auto_repeat_timer->stop();
269 if (!is_enabled()) {
270 bool was_being_pressed = m_being_pressed;
271 m_being_pressed = false;
272 if (was_being_pressed)
273 update();
274 }
275 }
276 Widget::change_event(event);
277}
278
279}