Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/Assertions.h>
28#include <AK/JsonObject.h>
29#include <LibGUI/Action.h>
30#include <LibGUI/Application.h>
31#include <LibGUI/Button.h>
32#include <LibGUI/CheckBox.h>
33#include <LibGUI/Event.h>
34#include <LibGUI/GroupBox.h>
35#include <LibGUI/Label.h>
36#include <LibGUI/Layout.h>
37#include <LibGUI/Menu.h>
38#include <LibGUI/Painter.h>
39#include <LibGUI/RadioButton.h>
40#include <LibGUI/ScrollBar.h>
41#include <LibGUI/Slider.h>
42#include <LibGUI/SpinBox.h>
43#include <LibGUI/TextBox.h>
44#include <LibGUI/Widget.h>
45#include <LibGUI/Window.h>
46#include <LibGUI/WindowServerConnection.h>
47#include <LibGfx/Bitmap.h>
48#include <LibGfx/Font.h>
49#include <LibGfx/Palette.h>
50#include <unistd.h>
51
52namespace GUI {
53
54REGISTER_WIDGET(Button)
55REGISTER_WIDGET(CheckBox)
56REGISTER_WIDGET(GroupBox)
57REGISTER_WIDGET(Label)
58REGISTER_WIDGET(RadioButton)
59REGISTER_WIDGET(ScrollBar)
60REGISTER_WIDGET(Slider)
61REGISTER_WIDGET(SpinBox)
62REGISTER_WIDGET(TextBox)
63REGISTER_WIDGET(Widget)
64
65static HashMap<String, WidgetClassRegistration*>& widget_classes()
66{
67 static HashMap<String, WidgetClassRegistration*>* map;
68 if (!map)
69 map = new HashMap<String, WidgetClassRegistration*>;
70 return *map;
71}
72
73WidgetClassRegistration::WidgetClassRegistration(const String& class_name, Function<NonnullRefPtr<Widget>()> factory)
74 : m_class_name(class_name)
75 , m_factory(move(factory))
76{
77 widget_classes().set(class_name, this);
78}
79
80WidgetClassRegistration::~WidgetClassRegistration()
81{
82}
83
84void WidgetClassRegistration::for_each(Function<void(const WidgetClassRegistration&)> callback)
85{
86 for (auto& it : widget_classes()) {
87 callback(*it.value);
88 }
89}
90
91const WidgetClassRegistration* WidgetClassRegistration::find(const String& class_name)
92{
93 return widget_classes().get(class_name).value_or(nullptr);
94}
95
96Widget::Widget()
97 : Core::Object(nullptr, true)
98 , m_background_role(Gfx::ColorRole::Window)
99 , m_foreground_role(Gfx::ColorRole::WindowText)
100 , m_font(Gfx::Font::default_font())
101 , m_palette(Application::the().palette().impl())
102{
103}
104
105Widget::~Widget()
106{
107}
108
109void Widget::child_event(Core::ChildEvent& event)
110{
111 if (event.type() == Event::ChildAdded) {
112 if (event.child() && Core::is<Widget>(*event.child()) && layout()) {
113 if (event.insertion_before_child() && event.insertion_before_child()->is_widget())
114 layout()->insert_widget_before(Core::to<Widget>(*event.child()), Core::to<Widget>(*event.insertion_before_child()));
115 else
116 layout()->add_widget(Core::to<Widget>(*event.child()));
117 }
118 }
119 if (event.type() == Event::ChildRemoved) {
120 if (layout()) {
121 if (event.child() && Core::is<Widget>(*event.child()))
122 layout()->remove_widget(Core::to<Widget>(*event.child()));
123 else
124 invalidate_layout();
125 }
126 if (event.child() && Core::is<Widget>(*event.child()))
127 window()->did_remove_widget({}, Core::to<Widget>(*event.child()));
128 update();
129 }
130 return Core::Object::child_event(event);
131}
132
133void Widget::set_relative_rect(const Gfx::Rect& a_rect)
134{
135 // Get rid of negative width/height values.
136 Gfx::Rect rect = {
137 a_rect.x(),
138 a_rect.y(),
139 max(a_rect.width(), 0),
140 max(a_rect.height(), 0)
141 };
142
143 if (rect == m_relative_rect)
144 return;
145
146 auto old_rect = m_relative_rect;
147
148 bool size_changed = m_relative_rect.size() != rect.size();
149 m_relative_rect = rect;
150
151 if (size_changed) {
152 ResizeEvent resize_event(m_relative_rect.size(), rect.size());
153 event(resize_event);
154 }
155
156 if (auto* parent = parent_widget())
157 parent->update(old_rect);
158 update();
159}
160
161void Widget::event(Core::Event& event)
162{
163 if (!is_enabled()) {
164 switch (event.type()) {
165 case Event::MouseUp:
166 case Event::MouseDown:
167 case Event::MouseMove:
168 case Event::MouseWheel:
169 case Event::MouseDoubleClick:
170 case Event::KeyUp:
171 case Event::KeyDown:
172 return;
173 default:
174 break;
175 }
176 }
177
178 switch (event.type()) {
179 case Event::Paint:
180 return handle_paint_event(static_cast<PaintEvent&>(event));
181 case Event::Resize:
182 return handle_resize_event(static_cast<ResizeEvent&>(event));
183 case Event::FocusIn:
184 return focusin_event(event);
185 case Event::FocusOut:
186 return focusout_event(event);
187 case Event::Show:
188 return show_event(static_cast<ShowEvent&>(event));
189 case Event::Hide:
190 return hide_event(static_cast<HideEvent&>(event));
191 case Event::KeyDown:
192 return keydown_event(static_cast<KeyEvent&>(event));
193 case Event::KeyUp:
194 return keyup_event(static_cast<KeyEvent&>(event));
195 case Event::MouseMove:
196 return mousemove_event(static_cast<MouseEvent&>(event));
197 case Event::MouseDown:
198 return handle_mousedown_event(static_cast<MouseEvent&>(event));
199 case Event::MouseDoubleClick:
200 return handle_mousedoubleclick_event(static_cast<MouseEvent&>(event));
201 case Event::MouseUp:
202 return handle_mouseup_event(static_cast<MouseEvent&>(event));
203 case Event::MouseWheel:
204 return mousewheel_event(static_cast<MouseEvent&>(event));
205 case Event::DragMove:
206 return drag_move_event(static_cast<DragEvent&>(event));
207 case Event::Drop:
208 return drop_event(static_cast<DropEvent&>(event));
209 case Event::ThemeChange:
210 return theme_change_event(static_cast<ThemeChangeEvent&>(event));
211 case Event::Enter:
212 return handle_enter_event(event);
213 case Event::Leave:
214 return handle_leave_event(event);
215 case Event::EnabledChange:
216 return change_event(static_cast<Event&>(event));
217 default:
218 return Core::Object::event(event);
219 }
220}
221
222void Widget::handle_paint_event(PaintEvent& event)
223{
224 ASSERT(is_visible());
225 if (fill_with_background_color()) {
226 Painter painter(*this);
227 painter.fill_rect(event.rect(), palette().color(background_role()));
228 } else {
229#ifdef DEBUG_WIDGET_UNDERDRAW
230 // FIXME: This is a bit broken.
231 // If the widget is not opaque, let's not mess it up with debugging color.
232 Painter painter(*this);
233 painter.fill_rect(rect(), Color::Red);
234#endif
235 }
236 paint_event(event);
237 for_each_child_widget([&](auto& child) {
238 if (!child.is_visible())
239 return IterationDecision::Continue;
240 if (child.relative_rect().intersects(event.rect())) {
241 PaintEvent local_event(event.rect().intersected(child.relative_rect()).translated(-child.relative_position()));
242 child.dispatch_event(local_event, this);
243 }
244 return IterationDecision::Continue;
245 });
246 second_paint_event(event);
247
248 if (is_being_inspected()) {
249 Painter painter(*this);
250 painter.draw_rect(rect(), Color::Magenta);
251 }
252}
253
254void Widget::set_layout(NonnullRefPtr<Layout> layout)
255{
256 if (m_layout) {
257 m_layout->notify_disowned({}, *this);
258 m_layout->remove_from_parent();
259 }
260 m_layout = move(layout);
261 if (m_layout) {
262 add_child(*m_layout);
263 m_layout->notify_adopted({}, *this);
264 do_layout();
265 } else {
266 update();
267 }
268}
269
270void Widget::do_layout()
271{
272 for_each_child_widget([&](auto& child) {
273 child.do_layout();
274 return IterationDecision::Continue;
275 });
276 custom_layout();
277 if (!m_layout)
278 return;
279 m_layout->run(*this);
280 did_layout();
281 update();
282}
283
284void Widget::notify_layout_changed(Badge<Layout>)
285{
286 invalidate_layout();
287}
288
289void Widget::handle_resize_event(ResizeEvent& event)
290{
291 resize_event(event);
292 do_layout();
293}
294
295void Widget::handle_mouseup_event(MouseEvent& event)
296{
297 mouseup_event(event);
298}
299
300void Widget::handle_mousedown_event(MouseEvent& event)
301{
302 if (accepts_focus())
303 set_focus(true);
304 mousedown_event(event);
305 if (event.button() == MouseButton::Right) {
306 ContextMenuEvent c_event(event.position(), screen_relative_rect().location().translated(event.position()));
307 context_menu_event(c_event);
308 }
309}
310
311void Widget::handle_mousedoubleclick_event(MouseEvent& event)
312{
313 doubleclick_event(event);
314}
315
316void Widget::handle_enter_event(Core::Event& event)
317{
318 if (has_tooltip())
319 Application::the().show_tooltip(m_tooltip, screen_relative_rect().center().translated(0, height() / 2));
320 enter_event(event);
321}
322
323void Widget::handle_leave_event(Core::Event& event)
324{
325 Application::the().hide_tooltip();
326 leave_event(event);
327}
328
329void Widget::doubleclick_event(MouseEvent&)
330{
331}
332
333void Widget::resize_event(ResizeEvent&)
334{
335}
336
337void Widget::paint_event(PaintEvent&)
338{
339}
340
341void Widget::second_paint_event(PaintEvent&)
342{
343}
344
345void Widget::show_event(ShowEvent&)
346{
347}
348
349void Widget::hide_event(HideEvent&)
350{
351}
352
353void Widget::keydown_event(KeyEvent& event)
354{
355 if (!event.alt() && !event.ctrl() && !event.logo() && event.key() == KeyCode::Key_Tab) {
356 if (event.shift())
357 focus_previous_widget();
358 else
359 focus_next_widget();
360 event.accept();
361 return;
362 }
363 event.ignore();
364}
365
366void Widget::keyup_event(KeyEvent&)
367{
368}
369
370void Widget::mousedown_event(MouseEvent&)
371{
372}
373
374void Widget::mouseup_event(MouseEvent&)
375{
376}
377
378void Widget::mousemove_event(MouseEvent&)
379{
380}
381
382void Widget::mousewheel_event(MouseEvent&)
383{
384}
385
386void Widget::context_menu_event(ContextMenuEvent&)
387{
388}
389
390void Widget::focusin_event(Core::Event&)
391{
392}
393
394void Widget::focusout_event(Core::Event&)
395{
396}
397
398void Widget::enter_event(Core::Event&)
399{
400}
401
402void Widget::leave_event(Core::Event&)
403{
404}
405
406void Widget::change_event(Event&)
407{
408}
409
410void Widget::drag_move_event(DragEvent& event)
411{
412 dbg() << class_name() << "{" << this << "} DRAG MOVE position: " << event.position() << ", data_type: '" << event.data_type() << "'";
413 event.ignore();
414}
415
416void Widget::drop_event(DropEvent& event)
417{
418 dbg() << class_name() << "{" << this << "} DROP position: " << event.position() << ", text: '" << event.text() << "'";
419 event.ignore();
420}
421
422void Widget::theme_change_event(ThemeChangeEvent&)
423{
424}
425
426void Widget::update()
427{
428 if (rect().is_empty())
429 return;
430 update(rect());
431}
432
433void Widget::update(const Gfx::Rect& rect)
434{
435 if (!is_visible())
436 return;
437
438 if (!updates_enabled())
439 return;
440
441 Window* window = m_window;
442 Widget* parent = parent_widget();
443 while (parent) {
444 if (!parent->updates_enabled())
445 return;
446 window = parent->m_window;
447 parent = parent->parent_widget();
448 }
449 if (window)
450 window->update(rect.translated(window_relative_rect().location()));
451}
452
453Gfx::Rect Widget::window_relative_rect() const
454{
455 auto rect = relative_rect();
456 for (auto* parent = parent_widget(); parent; parent = parent->parent_widget()) {
457 rect.move_by(parent->relative_position());
458 }
459 return rect;
460}
461
462Gfx::Rect Widget::screen_relative_rect() const
463{
464 return window_relative_rect().translated(window()->position());
465}
466
467Widget* Widget::child_at(const Gfx::Point& point) const
468{
469 for (int i = children().size() - 1; i >= 0; --i) {
470 if (!Core::is<Widget>(children()[i]))
471 continue;
472 auto& child = Core::to<Widget>(children()[i]);
473 if (!child.is_visible())
474 continue;
475 if (child.relative_rect().contains(point))
476 return const_cast<Widget*>(&child);
477 }
478 return nullptr;
479}
480
481Widget::HitTestResult Widget::hit_test(const Gfx::Point& position, ShouldRespectGreediness should_respect_greediness)
482{
483 if (should_respect_greediness == ShouldRespectGreediness::Yes && is_greedy_for_hits())
484 return { this, position };
485 if (auto* child = child_at(position))
486 return child->hit_test(position - child->relative_position());
487 return { this, position };
488}
489
490void Widget::set_window(Window* window)
491{
492 if (m_window == window)
493 return;
494 m_window = window;
495}
496
497bool Widget::is_focused() const
498{
499 auto* win = window();
500 if (!win)
501 return false;
502 if (!win->is_active())
503 return false;
504 return win->focused_widget() == this;
505}
506
507void Widget::set_focus(bool focus)
508{
509 auto* win = window();
510 if (!win)
511 return;
512 if (focus) {
513 win->set_focused_widget(this);
514 } else {
515 if (win->focused_widget() == this)
516 win->set_focused_widget(nullptr);
517 }
518}
519
520void Widget::set_font(const Gfx::Font* font)
521{
522 if (m_font.ptr() == font)
523 return;
524
525 if (!font)
526 m_font = Gfx::Font::default_font();
527 else
528 m_font = *font;
529
530 did_change_font();
531 update();
532}
533
534void Widget::set_global_cursor_tracking(bool enabled)
535{
536 auto* win = window();
537 if (!win)
538 return;
539 win->set_global_cursor_tracking_widget(enabled ? this : nullptr);
540}
541
542bool Widget::global_cursor_tracking() const
543{
544 auto* win = window();
545 if (!win)
546 return false;
547 return win->global_cursor_tracking_widget() == this;
548}
549
550void Widget::set_preferred_size(const Gfx::Size& size)
551{
552 if (m_preferred_size == size)
553 return;
554 m_preferred_size = size;
555 invalidate_layout();
556}
557
558void Widget::set_size_policy(Orientation orientation, SizePolicy policy)
559{
560 if (orientation == Orientation::Horizontal)
561 set_size_policy(policy, m_vertical_size_policy);
562 else
563 set_size_policy(m_horizontal_size_policy, policy);
564}
565
566void Widget::set_size_policy(SizePolicy horizontal_policy, SizePolicy vertical_policy)
567{
568 if (m_horizontal_size_policy == horizontal_policy && m_vertical_size_policy == vertical_policy)
569 return;
570 m_horizontal_size_policy = horizontal_policy;
571 m_vertical_size_policy = vertical_policy;
572 invalidate_layout();
573}
574
575void Widget::invalidate_layout()
576{
577 if (window())
578 window()->schedule_relayout();
579}
580
581void Widget::set_visible(bool visible)
582{
583 if (visible == m_visible)
584 return;
585 m_visible = visible;
586 if (auto* parent = parent_widget())
587 parent->invalidate_layout();
588 if (m_visible)
589 update();
590
591 if (m_visible) {
592 ShowEvent e;
593 event(e);
594 } else {
595 HideEvent e;
596 event(e);
597 }
598}
599
600bool Widget::spans_entire_window_horizontally() const
601{
602 auto* w = window();
603 if (!w)
604 return false;
605 auto* main_widget = w->main_widget();
606 if (!main_widget)
607 return false;
608 if (main_widget == this)
609 return true;
610 auto wrr = window_relative_rect();
611 return wrr.left() == main_widget->rect().left() && wrr.right() == main_widget->rect().right();
612}
613
614void Widget::set_enabled(bool enabled)
615{
616 if (m_enabled == enabled)
617 return;
618 m_enabled = enabled;
619 Event e(Event::EnabledChange);
620 event(e);
621 update();
622}
623
624void Widget::move_to_front()
625{
626 auto* parent = parent_widget();
627 if (!parent)
628 return;
629 if (parent->children().size() == 1)
630 return;
631 parent->children().remove_first_matching([this](auto& entry) {
632 return entry == this;
633 });
634 parent->children().append(*this);
635 parent->update();
636}
637
638void Widget::move_to_back()
639{
640 auto* parent = parent_widget();
641 if (!parent)
642 return;
643 if (parent->children().size() == 1)
644 return;
645 parent->children().remove_first_matching([this](auto& entry) {
646 return entry == this;
647 });
648 parent->children().prepend(*this);
649 parent->update();
650}
651
652bool Widget::is_frontmost() const
653{
654 auto* parent = parent_widget();
655 if (!parent)
656 return true;
657 return &parent->children().last() == this;
658}
659
660bool Widget::is_backmost() const
661{
662 auto* parent = parent_widget();
663 if (!parent)
664 return true;
665 return &parent->children().first() == this;
666}
667
668Action* Widget::action_for_key_event(const KeyEvent& event)
669{
670 Shortcut shortcut(event.modifiers(), (KeyCode)event.key());
671 Action* found_action = nullptr;
672 for_each_child_of_type<Action>([&](auto& action) {
673 if (action.shortcut() == shortcut) {
674 found_action = &action;
675 return IterationDecision::Break;
676 }
677 return IterationDecision::Continue;
678 });
679 return found_action;
680}
681
682void Widget::set_updates_enabled(bool enabled)
683{
684 if (m_updates_enabled == enabled)
685 return;
686 m_updates_enabled = enabled;
687 if (enabled)
688 update();
689}
690
691void Widget::focus_previous_widget()
692{
693 auto focusable_widgets = window()->focusable_widgets();
694 for (int i = focusable_widgets.size() - 1; i >= 0; --i) {
695 if (focusable_widgets[i] != this)
696 continue;
697 if (i > 0)
698 focusable_widgets[i - 1]->set_focus(true);
699 else
700 focusable_widgets.last()->set_focus(true);
701 }
702}
703
704void Widget::focus_next_widget()
705{
706 auto focusable_widgets = window()->focusable_widgets();
707 for (size_t i = 0; i < focusable_widgets.size(); ++i) {
708 if (focusable_widgets[i] != this)
709 continue;
710 if (i < focusable_widgets.size() - 1)
711 focusable_widgets[i + 1]->set_focus(true);
712 else
713 focusable_widgets.first()->set_focus(true);
714 }
715}
716
717void Widget::set_backcolor(const StringView& color_string)
718{
719 auto color = Color::from_string(color_string);
720 if (!color.has_value())
721 return;
722 set_background_color(color.value());
723}
724
725void Widget::set_forecolor(const StringView& color_string)
726{
727 auto color = Color::from_string(color_string);
728 if (!color.has_value())
729 return;
730 set_foreground_color(color.value());
731}
732
733void Widget::save_to(AK::JsonObject& json)
734{
735 json.set("relative_rect", relative_rect().to_string());
736 json.set("fill_with_background_color", fill_with_background_color());
737 json.set("tooltip", tooltip());
738 json.set("visible", is_visible());
739 json.set("focused", is_focused());
740 json.set("enabled", is_enabled());
741 json.set("background_color", background_color().to_string());
742 json.set("foreground_color", foreground_color().to_string());
743 json.set("preferred_size", preferred_size().to_string());
744 json.set("size_policy", String::format("[%s,%s]", to_string(horizontal_size_policy()), to_string(vertical_size_policy())));
745 Core::Object::save_to(json);
746}
747
748bool Widget::set_property(const StringView& name, const JsonValue& value)
749{
750 if (name == "fill_with_background_color") {
751 set_fill_with_background_color(value.to_bool());
752 return true;
753 }
754 if (name == "tooltip") {
755 set_tooltip(value.to_string());
756 return true;
757 }
758 if (name == "enable") {
759 set_enabled(value.to_bool());
760 return true;
761 }
762 if (name == "focused") {
763 set_focus(value.to_bool());
764 return true;
765 }
766 if (name == "visible") {
767 set_visible(value.to_bool());
768 return true;
769 }
770 return Core::Object::set_property(name, value);
771}
772
773Vector<Widget*> Widget::child_widgets() const
774{
775 Vector<Widget*> widgets;
776 widgets.ensure_capacity(children().size());
777 for (auto& child : const_cast<Widget*>(this)->children()) {
778 if (child.is_widget())
779 widgets.append(static_cast<Widget*>(&child));
780 }
781 return widgets;
782}
783
784void Widget::set_palette(const Palette& palette)
785{
786 m_palette = palette.impl();
787}
788
789void Widget::set_background_role(ColorRole role)
790{
791 m_background_role = role;
792}
793
794void Widget::set_foreground_role(ColorRole role)
795{
796 m_foreground_role = role;
797}
798
799Gfx::Palette Widget::palette() const
800{
801 return Gfx::Palette(*m_palette);
802}
803
804void Widget::did_begin_inspection()
805{
806 update();
807}
808
809void Widget::did_end_inspection()
810{
811 update();
812}
813
814}