Serenity Operating System
at hosted 814 lines 21 kB view raw
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}