Serenity Operating System
at portability 752 lines 19 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_GWIDGET(Button) 55REGISTER_GWIDGET(CheckBox) 56REGISTER_GWIDGET(GroupBox) 57REGISTER_GWIDGET(Label) 58REGISTER_GWIDGET(RadioButton) 59REGISTER_GWIDGET(ScrollBar) 60REGISTER_GWIDGET(Slider) 61REGISTER_GWIDGET(SpinBox) 62REGISTER_GWIDGET(TextBox) 63REGISTER_GWIDGET(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 update(); 127 } 128 return Core::Object::child_event(event); 129} 130 131void Widget::set_relative_rect(const Gfx::Rect& a_rect) 132{ 133 // Get rid of negative width/height values. 134 Gfx::Rect rect = { 135 a_rect.x(), 136 a_rect.y(), 137 max(a_rect.width(), 0), 138 max(a_rect.height(), 0) 139 }; 140 141 if (rect == m_relative_rect) 142 return; 143 144 auto old_rect = m_relative_rect; 145 146 bool size_changed = m_relative_rect.size() != rect.size(); 147 m_relative_rect = rect; 148 149 if (size_changed) { 150 ResizeEvent resize_event(m_relative_rect.size(), rect.size()); 151 event(resize_event); 152 } 153 154 if (auto* parent = parent_widget()) 155 parent->update(old_rect); 156 update(); 157} 158 159void Widget::event(Core::Event& event) 160{ 161 switch (event.type()) { 162 case Event::Paint: 163 return handle_paint_event(static_cast<PaintEvent&>(event)); 164 case Event::Resize: 165 return handle_resize_event(static_cast<ResizeEvent&>(event)); 166 case Event::FocusIn: 167 return focusin_event(event); 168 case Event::FocusOut: 169 return focusout_event(event); 170 case Event::Show: 171 return show_event(static_cast<ShowEvent&>(event)); 172 case Event::Hide: 173 return hide_event(static_cast<HideEvent&>(event)); 174 case Event::KeyDown: 175 return keydown_event(static_cast<KeyEvent&>(event)); 176 case Event::KeyUp: 177 return keyup_event(static_cast<KeyEvent&>(event)); 178 case Event::MouseMove: 179 return mousemove_event(static_cast<MouseEvent&>(event)); 180 case Event::MouseDown: 181 return handle_mousedown_event(static_cast<MouseEvent&>(event)); 182 case Event::MouseDoubleClick: 183 return handle_mousedoubleclick_event(static_cast<MouseEvent&>(event)); 184 case Event::MouseUp: 185 return handle_mouseup_event(static_cast<MouseEvent&>(event)); 186 case Event::MouseWheel: 187 return mousewheel_event(static_cast<MouseEvent&>(event)); 188 case Event::DragMove: 189 return drag_move_event(static_cast<DragEvent&>(event)); 190 case Event::Drop: 191 return drop_event(static_cast<DropEvent&>(event)); 192 case Event::Enter: 193 return handle_enter_event(event); 194 case Event::Leave: 195 return handle_leave_event(event); 196 case Event::EnabledChange: 197 return change_event(static_cast<Event&>(event)); 198 default: 199 return Core::Object::event(event); 200 } 201} 202 203void Widget::handle_paint_event(PaintEvent& event) 204{ 205 ASSERT(is_visible()); 206 if (fill_with_background_color()) { 207 Painter painter(*this); 208 painter.fill_rect(event.rect(), palette().color(background_role())); 209 } else { 210#ifdef DEBUG_WIDGET_UNDERDRAW 211 // FIXME: This is a bit broken. 212 // If the widget is not opaque, let's not mess it up with debugging color. 213 Painter painter(*this); 214 painter.fill_rect(rect(), Color::Red); 215#endif 216 } 217 paint_event(event); 218 for_each_child_widget([&](auto& child) { 219 if (!child.is_visible()) 220 return IterationDecision::Continue; 221 if (child.relative_rect().intersects(event.rect())) { 222 PaintEvent local_event(event.rect().intersected(child.relative_rect()).translated(-child.relative_position())); 223 child.dispatch_event(local_event, this); 224 } 225 return IterationDecision::Continue; 226 }); 227 second_paint_event(event); 228} 229 230void Widget::set_layout(OwnPtr<Layout>&& layout) 231{ 232 if (m_layout) 233 m_layout->notify_disowned({}, *this); 234 m_layout = move(layout); 235 if (m_layout) { 236 m_layout->notify_adopted({}, *this); 237 do_layout(); 238 } else { 239 update(); 240 } 241} 242 243void Widget::do_layout() 244{ 245 for_each_child_widget([&](auto& child) { 246 child.do_layout(); 247 return IterationDecision::Continue; 248 }); 249 custom_layout(); 250 if (!m_layout) 251 return; 252 m_layout->run(*this); 253 did_layout(); 254 update(); 255} 256 257void Widget::notify_layout_changed(Badge<Layout>) 258{ 259 invalidate_layout(); 260} 261 262void Widget::handle_resize_event(ResizeEvent& event) 263{ 264 resize_event(event); 265 do_layout(); 266} 267 268void Widget::handle_mouseup_event(MouseEvent& event) 269{ 270 mouseup_event(event); 271} 272 273void Widget::handle_mousedown_event(MouseEvent& event) 274{ 275 if (accepts_focus()) 276 set_focus(true); 277 mousedown_event(event); 278 if (event.button() == MouseButton::Right) { 279 ContextMenuEvent c_event(event.position(), screen_relative_rect().location().translated(event.position())); 280 context_menu_event(c_event); 281 } 282} 283 284void Widget::handle_mousedoubleclick_event(MouseEvent& event) 285{ 286 doubleclick_event(event); 287} 288 289void Widget::handle_enter_event(Core::Event& event) 290{ 291 if (has_tooltip()) 292 Application::the().show_tooltip(m_tooltip, screen_relative_rect().center().translated(0, height() / 2)); 293 enter_event(event); 294} 295 296void Widget::handle_leave_event(Core::Event& event) 297{ 298 Application::the().hide_tooltip(); 299 leave_event(event); 300} 301 302void Widget::click_event(MouseEvent&) 303{ 304} 305 306void Widget::doubleclick_event(MouseEvent&) 307{ 308} 309 310void Widget::resize_event(ResizeEvent&) 311{ 312} 313 314void Widget::paint_event(PaintEvent&) 315{ 316} 317 318void Widget::second_paint_event(PaintEvent&) 319{ 320} 321 322void Widget::show_event(ShowEvent&) 323{ 324} 325 326void Widget::hide_event(HideEvent&) 327{ 328} 329 330void Widget::keydown_event(KeyEvent& event) 331{ 332 if (!event.alt() && !event.ctrl() && !event.logo() && event.key() == KeyCode::Key_Tab) { 333 if (event.shift()) 334 focus_previous_widget(); 335 else 336 focus_next_widget(); 337 event.accept(); 338 return; 339 } 340 event.ignore(); 341} 342 343void Widget::keyup_event(KeyEvent&) 344{ 345} 346 347void Widget::mousedown_event(MouseEvent&) 348{ 349} 350 351void Widget::mouseup_event(MouseEvent&) 352{ 353} 354 355void Widget::mousemove_event(MouseEvent&) 356{ 357} 358 359void Widget::mousewheel_event(MouseEvent&) 360{ 361} 362 363void Widget::context_menu_event(ContextMenuEvent&) 364{ 365} 366 367void Widget::focusin_event(Core::Event&) 368{ 369} 370 371void Widget::focusout_event(Core::Event&) 372{ 373} 374 375void Widget::enter_event(Core::Event&) 376{ 377} 378 379void Widget::leave_event(Core::Event&) 380{ 381} 382 383void Widget::change_event(Event&) 384{ 385} 386 387void Widget::drag_move_event(DragEvent& event) 388{ 389 dbg() << class_name() << "{" << this << "} DRAG MOVE position: " << event.position() << ", data_type: '" << event.data_type() << "'"; 390 event.ignore(); 391} 392 393void Widget::drop_event(DropEvent& event) 394{ 395 dbg() << class_name() << "{" << this << "} DROP position: " << event.position() << ", text: '" << event.text() << "'"; 396 event.ignore(); 397} 398 399void Widget::update() 400{ 401 if (rect().is_empty()) 402 return; 403 update(rect()); 404} 405 406void Widget::update(const Gfx::Rect& rect) 407{ 408 if (!is_visible()) 409 return; 410 411 if (!updates_enabled()) 412 return; 413 414 Window* window = m_window; 415 Widget* parent = parent_widget(); 416 while (parent) { 417 if (!parent->updates_enabled()) 418 return; 419 window = parent->m_window; 420 parent = parent->parent_widget(); 421 } 422 if (window) 423 window->update(rect.translated(window_relative_rect().location())); 424} 425 426Gfx::Rect Widget::window_relative_rect() const 427{ 428 auto rect = relative_rect(); 429 for (auto* parent = parent_widget(); parent; parent = parent->parent_widget()) { 430 rect.move_by(parent->relative_position()); 431 } 432 return rect; 433} 434 435Gfx::Rect Widget::screen_relative_rect() const 436{ 437 return window_relative_rect().translated(window()->position()); 438} 439 440Widget* Widget::child_at(const Gfx::Point& point) const 441{ 442 for (int i = children().size() - 1; i >= 0; --i) { 443 if (!Core::is<Widget>(children()[i])) 444 continue; 445 auto& child = Core::to<Widget>(children()[i]); 446 if (!child.is_visible()) 447 continue; 448 if (child.relative_rect().contains(point)) 449 return const_cast<Widget*>(&child); 450 } 451 return nullptr; 452} 453 454Widget::HitTestResult Widget::hit_test(const Gfx::Point& position, ShouldRespectGreediness should_respect_greediness) 455{ 456 if (should_respect_greediness == ShouldRespectGreediness::Yes && is_greedy_for_hits()) 457 return { this, position }; 458 if (auto* child = child_at(position)) 459 return child->hit_test(position - child->relative_position()); 460 return { this, position }; 461} 462 463void Widget::set_window(Window* window) 464{ 465 if (m_window == window) 466 return; 467 m_window = window; 468} 469 470bool Widget::is_focused() const 471{ 472 auto* win = window(); 473 if (!win) 474 return false; 475 if (!win->is_active()) 476 return false; 477 return win->focused_widget() == this; 478} 479 480void Widget::set_focus(bool focus) 481{ 482 auto* win = window(); 483 if (!win) 484 return; 485 if (focus) { 486 win->set_focused_widget(this); 487 } else { 488 if (win->focused_widget() == this) 489 win->set_focused_widget(nullptr); 490 } 491} 492 493void Widget::set_font(const Gfx::Font* font) 494{ 495 if (m_font.ptr() == font) 496 return; 497 498 if (!font) 499 m_font = Gfx::Font::default_font(); 500 else 501 m_font = *font; 502 503 did_change_font(); 504 update(); 505} 506 507void Widget::set_global_cursor_tracking(bool enabled) 508{ 509 auto* win = window(); 510 if (!win) 511 return; 512 win->set_global_cursor_tracking_widget(enabled ? this : nullptr); 513} 514 515bool Widget::global_cursor_tracking() const 516{ 517 auto* win = window(); 518 if (!win) 519 return false; 520 return win->global_cursor_tracking_widget() == this; 521} 522 523void Widget::set_preferred_size(const Gfx::Size& size) 524{ 525 if (m_preferred_size == size) 526 return; 527 m_preferred_size = size; 528 invalidate_layout(); 529} 530 531void Widget::set_size_policy(Orientation orientation, SizePolicy policy) 532{ 533 if (orientation == Orientation::Horizontal) 534 set_size_policy(policy, m_vertical_size_policy); 535 else 536 set_size_policy(m_horizontal_size_policy, policy); 537} 538 539void Widget::set_size_policy(SizePolicy horizontal_policy, SizePolicy vertical_policy) 540{ 541 if (m_horizontal_size_policy == horizontal_policy && m_vertical_size_policy == vertical_policy) 542 return; 543 m_horizontal_size_policy = horizontal_policy; 544 m_vertical_size_policy = vertical_policy; 545 invalidate_layout(); 546} 547 548void Widget::invalidate_layout() 549{ 550 if (window()) 551 window()->schedule_relayout(); 552} 553 554void Widget::set_visible(bool visible) 555{ 556 if (visible == m_visible) 557 return; 558 m_visible = visible; 559 if (auto* parent = parent_widget()) 560 parent->invalidate_layout(); 561 if (m_visible) 562 update(); 563 564 if (m_visible) { 565 ShowEvent e; 566 event(e); 567 } else { 568 HideEvent e; 569 event(e); 570 } 571} 572 573bool Widget::spans_entire_window_horizontally() const 574{ 575 auto* w = window(); 576 if (!w) 577 return false; 578 auto* main_widget = w->main_widget(); 579 if (!main_widget) 580 return false; 581 if (main_widget == this) 582 return true; 583 auto wrr = window_relative_rect(); 584 return wrr.left() == main_widget->rect().left() && wrr.right() == main_widget->rect().right(); 585} 586 587void Widget::set_enabled(bool enabled) 588{ 589 if (m_enabled == enabled) 590 return; 591 m_enabled = enabled; 592 Event e(Event::EnabledChange); 593 event(e); 594 update(); 595} 596 597void Widget::move_to_front() 598{ 599 auto* parent = parent_widget(); 600 if (!parent) 601 return; 602 if (parent->children().size() == 1) 603 return; 604 parent->children().remove_first_matching([this](auto& entry) { 605 return entry == this; 606 }); 607 parent->children().append(*this); 608 parent->update(); 609} 610 611void Widget::move_to_back() 612{ 613 auto* parent = parent_widget(); 614 if (!parent) 615 return; 616 if (parent->children().size() == 1) 617 return; 618 parent->children().remove_first_matching([this](auto& entry) { 619 return entry == this; 620 }); 621 parent->children().prepend(*this); 622 parent->update(); 623} 624 625bool Widget::is_frontmost() const 626{ 627 auto* parent = parent_widget(); 628 if (!parent) 629 return true; 630 return &parent->children().last() == this; 631} 632 633bool Widget::is_backmost() const 634{ 635 auto* parent = parent_widget(); 636 if (!parent) 637 return true; 638 return &parent->children().first() == this; 639} 640 641Action* Widget::action_for_key_event(const KeyEvent& event) 642{ 643 Shortcut shortcut(event.modifiers(), (KeyCode)event.key()); 644 Action* found_action = nullptr; 645 for_each_child_of_type<Action>([&](auto& action) { 646 if (action.shortcut() == shortcut) { 647 found_action = &action; 648 return IterationDecision::Break; 649 } 650 return IterationDecision::Continue; 651 }); 652 return found_action; 653} 654 655void Widget::set_updates_enabled(bool enabled) 656{ 657 if (m_updates_enabled == enabled) 658 return; 659 m_updates_enabled = enabled; 660 if (enabled) 661 update(); 662} 663 664void Widget::focus_previous_widget() 665{ 666 auto focusable_widgets = window()->focusable_widgets(); 667 for (int i = focusable_widgets.size() - 1; i >= 0; --i) { 668 if (focusable_widgets[i] != this) 669 continue; 670 if (i > 0) 671 focusable_widgets[i - 1]->set_focus(true); 672 else 673 focusable_widgets.last()->set_focus(true); 674 } 675} 676 677void Widget::focus_next_widget() 678{ 679 auto focusable_widgets = window()->focusable_widgets(); 680 for (size_t i = 0; i < focusable_widgets.size(); ++i) { 681 if (focusable_widgets[i] != this) 682 continue; 683 if (i < focusable_widgets.size() - 1) 684 focusable_widgets[i + 1]->set_focus(true); 685 else 686 focusable_widgets.first()->set_focus(true); 687 } 688} 689 690void Widget::set_backcolor(const StringView& color_string) 691{ 692 auto color = Color::from_string(color_string); 693 if (!color.has_value()) 694 return; 695 set_background_color(color.value()); 696} 697 698void Widget::set_forecolor(const StringView& color_string) 699{ 700 auto color = Color::from_string(color_string); 701 if (!color.has_value()) 702 return; 703 set_foreground_color(color.value()); 704} 705 706void Widget::save_to(AK::JsonObject& json) 707{ 708 json.set("relative_rect", relative_rect().to_string()); 709 json.set("fill_with_background_color", fill_with_background_color()); 710 json.set("tooltip", tooltip()); 711 json.set("visible", is_visible()); 712 json.set("focused", is_focused()); 713 json.set("enabled", is_enabled()); 714 json.set("background_color", background_color().to_string()); 715 json.set("foreground_color", foreground_color().to_string()); 716 json.set("preferred_size", preferred_size().to_string()); 717 json.set("size_policy", String::format("[%s,%s]", to_string(horizontal_size_policy()), to_string(vertical_size_policy()))); 718 Core::Object::save_to(json); 719} 720 721Vector<Widget*> Widget::child_widgets() const 722{ 723 Vector<Widget*> widgets; 724 widgets.ensure_capacity(children().size()); 725 for (auto& child : const_cast<Widget*>(this)->children()) { 726 if (child.is_widget()) 727 widgets.append(static_cast<Widget*>(&child)); 728 } 729 return widgets; 730} 731 732void Widget::set_palette(const Palette& palette) 733{ 734 m_palette = palette.impl(); 735} 736 737void Widget::set_background_role(ColorRole role) 738{ 739 m_background_role = role; 740} 741 742void Widget::set_foreground_role(ColorRole role) 743{ 744 m_foreground_role = role; 745} 746 747Gfx::Palette Widget::palette() const 748{ 749 return Gfx::Palette(*m_palette); 750} 751 752}