From fcc3745b0286c68b932ec91cce6e5ede767ca122 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 20 Sep 2019 20:37:31 +0200 Subject: [PATCH] LibCore+LibGUI+WindowServer: Make events bubble up through ancestors With this patch, CEvents no longer stop at the target object, but will bubble up the ancestor chain as long as CEvent::is_accepted() is false. To the set accepted flag, call CEvent::accept(). To clear the accepted flag, call CEvent::ignore(). Events start out in the accepted state, so if you want them to bubble up, you have to call ignore() on them. Using this mechanism, we now ignore non-tabbing keydown events in GWidget, causing them to bubble up through the widget's ancestors. :^) --- Libraries/LibCore/CEvent.h | 5 +++++ Libraries/LibCore/CEventLoop.cpp | 2 +- Libraries/LibCore/CObject.cpp | 21 +++++++++++++++++++++ Libraries/LibCore/CObject.h | 4 ++++ Libraries/LibGUI/GAbstractButton.cpp | 5 ++++- Libraries/LibGUI/GWidget.cpp | 5 ++++- Libraries/LibGUI/GWindow.cpp | 18 +++++++++--------- Servers/WindowServer/WSWindow.cpp | 3 ++- Servers/WindowServer/WSWindowManager.cpp | 8 ++++---- 9 files changed, 54 insertions(+), 17 deletions(-) diff --git a/Libraries/LibCore/CEvent.h b/Libraries/LibCore/CEvent.h index 18470e841f..84b34d09af 100644 --- a/Libraries/LibCore/CEvent.h +++ b/Libraries/LibCore/CEvent.h @@ -31,8 +31,13 @@ public: unsigned type() const { return m_type; } + bool is_accepted() const { return m_accepted; } + void accept() { m_accepted = true; } + void ignore() { m_accepted = false; } + private: unsigned m_type { Type::Invalid }; + bool m_accepted { true }; }; class CDeferredInvocationEvent : public CEvent { diff --git a/Libraries/LibCore/CEventLoop.cpp b/Libraries/LibCore/CEventLoop.cpp index e39f2a06fc..729d2b0f31 100644 --- a/Libraries/LibCore/CEventLoop.cpp +++ b/Libraries/LibCore/CEventLoop.cpp @@ -245,7 +245,7 @@ void CEventLoop::pump(WaitMode mode) #endif static_cast(event).m_invokee(*receiver); } else { - receiver->event(event); + receiver->dispatch_event(event); } if (m_exit_requested) { diff --git a/Libraries/LibCore/CObject.cpp b/Libraries/LibCore/CObject.cpp index 2d9d4f505b..6d5bab5b8d 100644 --- a/Libraries/LibCore/CObject.cpp +++ b/Libraries/LibCore/CObject.cpp @@ -134,3 +134,24 @@ void CObject::save_to(JsonObject& json) json.set("name", name()); json.set("parent", String::format("%p", parent())); } + +bool CObject::is_ancestor_of(const CObject& other) const +{ + if (&other == this) + return false; + for (auto* ancestor = other.parent(); ancestor; ancestor = ancestor->parent()) { + if (ancestor == this) + return true; + } + return false; +} + +void CObject::dispatch_event(CEvent& e, CObject* stay_within) +{ + ASSERT(!stay_within || stay_within == this || stay_within->is_ancestor_of(*this)); + auto* target = this; + do { + target->event(e); + target = target->parent(); + } while (target && target != stay_within && !e.is_accepted()); +} diff --git a/Libraries/LibCore/CObject.h b/Libraries/LibCore/CObject.h index 4327085d93..bfe9a14f00 100644 --- a/Libraries/LibCore/CObject.h +++ b/Libraries/LibCore/CObject.h @@ -48,6 +48,8 @@ public: template void for_each_child_of_type(Callback callback); + bool is_ancestor_of(const CObject&) const; + CObject* parent() { return m_parent; } const CObject* parent() const { return m_parent; } @@ -71,6 +73,8 @@ public: static IntrusiveList& all_objects(); + void dispatch_event(CEvent&, CObject* stay_within = nullptr); + protected: explicit CObject(CObject* parent = nullptr, bool is_widget = false); diff --git a/Libraries/LibGUI/GAbstractButton.cpp b/Libraries/LibGUI/GAbstractButton.cpp index 150d9b75e9..f0f3901f91 100644 --- a/Libraries/LibGUI/GAbstractButton.cpp +++ b/Libraries/LibGUI/GAbstractButton.cpp @@ -134,8 +134,11 @@ void GAbstractButton::leave_event(CEvent&) void GAbstractButton::keydown_event(GKeyEvent& event) { - if (event.key() == KeyCode::Key_Return) + if (event.key() == KeyCode::Key_Return) { click(); + event.accept(); + return; + } GWidget::keydown_event(event); } diff --git a/Libraries/LibGUI/GWidget.cpp b/Libraries/LibGUI/GWidget.cpp index 6d84f2f12e..66ff1eed53 100644 --- a/Libraries/LibGUI/GWidget.cpp +++ b/Libraries/LibGUI/GWidget.cpp @@ -122,7 +122,7 @@ void GWidget::handle_paint_event(GPaintEvent& event) return IterationDecision::Continue; if (child.relative_rect().intersects(event.rect())) { GPaintEvent local_event(event.rect().intersected(child.relative_rect()).translated(-child.relative_position())); - child.event(local_event); + child.dispatch_event(local_event, this); } return IterationDecision::Continue; }); @@ -231,7 +231,10 @@ void GWidget::keydown_event(GKeyEvent& event) focus_previous_widget(); else focus_next_widget(); + event.accept(); + return; } + event.ignore(); } void GWidget::keyup_event(GKeyEvent&) diff --git a/Libraries/LibGUI/GWindow.cpp b/Libraries/LibGUI/GWindow.cpp index 997b587e78..ce29cd52e4 100644 --- a/Libraries/LibGUI/GWindow.cpp +++ b/Libraries/LibGUI/GWindow.cpp @@ -191,14 +191,14 @@ void GWindow::event(CEvent& event) auto window_relative_rect = m_global_cursor_tracking_widget->window_relative_rect(); Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() }; auto local_event = make((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta()); - m_global_cursor_tracking_widget->event(*local_event); + m_global_cursor_tracking_widget->dispatch_event(*local_event, this); return; } if (m_automatic_cursor_tracking_widget) { auto window_relative_rect = m_automatic_cursor_tracking_widget->window_relative_rect(); Point local_point { mouse_event.x() - window_relative_rect.x(), mouse_event.y() - window_relative_rect.y() }; auto local_event = make((GEvent::Type)event.type(), local_point, mouse_event.buttons(), mouse_event.button(), mouse_event.modifiers(), mouse_event.wheel_delta()); - m_automatic_cursor_tracking_widget->event(*local_event); + m_automatic_cursor_tracking_widget->dispatch_event(*local_event, this); if (mouse_event.buttons() == 0) m_automatic_cursor_tracking_widget = nullptr; return; @@ -212,7 +212,7 @@ void GWindow::event(CEvent& event) if (mouse_event.buttons() != 0 && !m_automatic_cursor_tracking_widget) m_automatic_cursor_tracking_widget = result.widget->make_weak_ptr(); if (result.widget != m_global_cursor_tracking_widget.ptr()) - return result.widget->event(*local_event); + return result.widget->dispatch_event(*local_event, this); return; } @@ -241,7 +241,7 @@ void GWindow::event(CEvent& event) } for (auto& rect : rects) - m_main_widget->event(*make(rect)); + m_main_widget->dispatch_event(*make(rect), this); paint_keybinds(); @@ -290,9 +290,9 @@ void GWindow::event(CEvent& event) if (found_widget != m_keyboard_activation_targets.end()) { m_keybind_mode = false; auto event = make(GEvent::MouseDown, Point(), 0, GMouseButton::Left, 0, 0); - found_widget->value->event(*event); + found_widget->value->dispatch_event(*event, this); event = make(GEvent::MouseUp, Point(), 0, GMouseButton::Left, 0, 0); - found_widget->value->event(*event); + found_widget->value->dispatch_event(*event, this); } else if (m_entered_keybind.length() >= m_max_keybind_length) { m_keybind_mode = false; } @@ -300,9 +300,9 @@ void GWindow::event(CEvent& event) } } else { if (m_focused_widget) - return m_focused_widget->event(event); + return m_focused_widget->dispatch_event(event, this); if (m_main_widget) - return m_main_widget->event(event); + return m_main_widget->dispatch_event(event, this); } return; } @@ -315,7 +315,7 @@ void GWindow::event(CEvent& event) m_is_active = event.type() == GEvent::WindowBecameActive; if (m_main_widget) - m_main_widget->event(event); + m_main_widget->dispatch_event(event, this); if (m_focused_widget) m_focused_widget->update(); return; diff --git a/Servers/WindowServer/WSWindow.cpp b/Servers/WindowServer/WSWindow.cpp index 18a235a1d6..4042203bf5 100644 --- a/Servers/WindowServer/WSWindow.cpp +++ b/Servers/WindowServer/WSWindow.cpp @@ -178,7 +178,8 @@ void WSWindow::event(CEvent& event) { if (!m_client) { ASSERT(parent()); - return parent()->event(event); + event.ignore(); + return; } if (is_blocked_by_modal_window()) diff --git a/Servers/WindowServer/WSWindowManager.cpp b/Servers/WindowServer/WSWindowManager.cpp index 465eeaec1d..839e87dcdc 100644 --- a/Servers/WindowServer/WSWindowManager.cpp +++ b/Servers/WindowServer/WSWindowManager.cpp @@ -664,11 +664,11 @@ void WSWindowManager::process_event_for_doubleclick(WSWindow& window, WSMouseEve void WSWindowManager::deliver_mouse_event(WSWindow& window, WSMouseEvent& event) { - window.event(event); + window.dispatch_event(event); if (event.type() == WSEvent::MouseUp) { process_event_for_doubleclick(window, event); if (event.type() == WSEvent::MouseDoubleClick) - window.event(event); + window.dispatch_event(event); } } @@ -703,7 +703,7 @@ void WSWindowManager::process_mouse_event(WSMouseEvent& event, WSWindow*& hovere // FIXME: Now that the menubar has a dedicated window, is this special-casing really necessary? if (!active_window_is_modal() && menubar_rect().contains(event.position())) { - m_menu_manager.event(event); + m_menu_manager.dispatch_event(event); return; } @@ -915,7 +915,7 @@ void WSWindowManager::event(CEvent& event) return; } if (m_active_window) - return m_active_window->event(event); + return m_active_window->dispatch_event(event); return; }