diff options
author | Andreas Kling <awesomekling@gmail.com> | 2019-04-08 18:58:44 +0200 |
---|---|---|
committer | Andreas Kling <awesomekling@gmail.com> | 2019-04-08 18:58:44 +0200 |
commit | 7f2eeb0b35d07469815f8221c8f5e0d300899f58 (patch) | |
tree | 7dde32ea37170e8e0dbaa494676d442c889d89c4 | |
parent | 3e175c9a96977f14a8adcf1b5a021ccb48f3b194 (diff) | |
download | serenity-7f2eeb0b35d07469815f8221c8f5e0d300899f58.zip |
LibGUI+WindowServer: Add support for GWidget tooltips.
Any GWidget can have a tooltip and it will automatically pop up below the
center of the widget when hovered. GActions added to GToolBars will use
the action text() as their tooltip automagically. :^)
-rw-r--r-- | LibGUI/GApplication.cpp | 47 | ||||
-rw-r--r-- | LibGUI/GApplication.h | 6 | ||||
-rw-r--r-- | LibGUI/GLabel.h | 4 | ||||
-rw-r--r-- | LibGUI/GToolBar.cpp | 1 | ||||
-rw-r--r-- | LibGUI/GWidget.cpp | 23 | ||||
-rw-r--r-- | LibGUI/GWidget.h | 8 | ||||
-rw-r--r-- | LibGUI/GWindow.cpp | 10 | ||||
-rw-r--r-- | LibGUI/GWindowType.h | 1 | ||||
-rw-r--r-- | Servers/WindowServer/WSAPITypes.h | 1 | ||||
-rw-r--r-- | Servers/WindowServer/WSClientConnection.cpp | 48 | ||||
-rw-r--r-- | Servers/WindowServer/WSMessageLoop.cpp | 2 | ||||
-rw-r--r-- | Servers/WindowServer/WSWindow.cpp | 2 | ||||
-rw-r--r-- | Servers/WindowServer/WSWindowFrame.cpp | 5 | ||||
-rw-r--r-- | Servers/WindowServer/WSWindowManager.h | 4 | ||||
-rw-r--r-- | Servers/WindowServer/WSWindowType.h | 1 |
15 files changed, 136 insertions, 27 deletions
diff --git a/LibGUI/GApplication.cpp b/LibGUI/GApplication.cpp index b50549d2db..aace66ffad 100644 --- a/LibGUI/GApplication.cpp +++ b/LibGUI/GApplication.cpp @@ -2,6 +2,10 @@ #include <LibGUI/GEventLoop.h> #include <LibGUI/GMenuBar.h> #include <LibGUI/GAction.h> +#include <LibGUI/GWindow.h> +#include <LibGUI/GLabel.h> +#include <LibGUI/GPainter.h> +#include <WindowServer/WSAPITypes.h> static GApplication* s_the; @@ -65,3 +69,46 @@ GAction* GApplication::action_for_key_event(const GKeyEvent& event) return nullptr; return (*it).value; } + +class GApplication::TooltipWindow final : public GWindow { +public: + TooltipWindow() + { + set_title("Tooltip"); + set_window_type(GWindowType::Tooltip); + m_label = new GLabel; + m_label->set_background_color(Color::from_rgb(0xdac7b5)); + m_label->set_fill_with_background_color(true); + m_label->set_frame_thickness(1); + m_label->set_frame_shape(GFrame::Shape::Container); + m_label->set_frame_shadow(GFrame::Shadow::Plain); + set_main_widget(m_label); + } + + void set_tooltip(const String& tooltip) + { + // FIXME: Add some kind of GLabel auto-sizing feature. + int text_width = m_label->font().width(tooltip); + set_rect(100, 100, text_width + 10, m_label->font().glyph_height() + 8); + m_label->set_text(tooltip); + } + + GLabel* m_label { nullptr }; +}; + +void GApplication::show_tooltip(const String& tooltip, const Point& screen_location) +{ + if (!m_tooltip_window) { + m_tooltip_window = new TooltipWindow; + m_tooltip_window->set_double_buffering_enabled(false); + } + m_tooltip_window->set_tooltip(tooltip); + m_tooltip_window->move_to(screen_location); + m_tooltip_window->show(); +} + +void GApplication::hide_tooltip() +{ + if (m_tooltip_window) + m_tooltip_window->hide(); +} diff --git a/LibGUI/GApplication.h b/LibGUI/GApplication.h index 694f19e4d2..baa31ba40d 100644 --- a/LibGUI/GApplication.h +++ b/LibGUI/GApplication.h @@ -9,6 +9,7 @@ class GAction; class GKeyEvent; class GEventLoop; class GMenuBar; +class Point; class GApplication { public: @@ -25,8 +26,13 @@ public: void register_shortcut_action(Badge<GAction>, GAction&); void unregister_shortcut_action(Badge<GAction>, GAction&); + void show_tooltip(const String&, const Point& screen_location); + void hide_tooltip(); + private: OwnPtr<GEventLoop> m_event_loop; OwnPtr<GMenuBar> m_menubar; HashMap<GShortcut, GAction*> m_shortcut_actions; + class TooltipWindow; + TooltipWindow* m_tooltip_window { nullptr }; }; diff --git a/LibGUI/GLabel.h b/LibGUI/GLabel.h index 4e62b5c10f..56258145f6 100644 --- a/LibGUI/GLabel.h +++ b/LibGUI/GLabel.h @@ -7,8 +7,8 @@ class GraphicsBitmap; class GLabel final : public GFrame { public: - explicit GLabel(GWidget* parent); - GLabel(const String& text, GWidget* parent); + explicit GLabel(GWidget* parent = nullptr); + GLabel(const String& text, GWidget* parent = nullptr); virtual ~GLabel() override; String text() const { return m_text; } diff --git a/LibGUI/GToolBar.cpp b/LibGUI/GToolBar.cpp index 14e58e9d51..11939e9cd8 100644 --- a/LibGUI/GToolBar.cpp +++ b/LibGUI/GToolBar.cpp @@ -26,6 +26,7 @@ void GToolBar::add_action(Retained<GAction>&& action) item->action = move(action); auto* button = new GButton(this); + button->set_tooltip(item->action->text()); if (item->action->icon()) button->set_icon(item->action->icon()); else diff --git a/LibGUI/GWidget.cpp b/LibGUI/GWidget.cpp index 764037870d..b02bf059f2 100644 --- a/LibGUI/GWidget.cpp +++ b/LibGUI/GWidget.cpp @@ -6,6 +6,7 @@ #include <AK/Assertions.h> #include <SharedGraphics/GraphicsBitmap.h> #include <LibGUI/GPainter.h> +#include <LibGUI/GApplication.h> #include <unistd.h> @@ -79,9 +80,9 @@ void GWidget::event(GEvent& event) case GEvent::MouseUp: return handle_mouseup_event(static_cast<GMouseEvent&>(event)); case GEvent::Enter: - return enter_event(event); + return handle_enter_event(event); case GEvent::Leave: - return leave_event(event); + return handle_leave_event(event); default: return GObject::event(event); } @@ -177,6 +178,19 @@ void GWidget::handle_mousedown_event(GMouseEvent& event) mousedown_event(event); } +void GWidget::handle_enter_event(GEvent& event) +{ + if (has_tooltip()) + GApplication::the().show_tooltip(m_tooltip, screen_relative_rect().center().translated(0, height() / 2)); + enter_event(event); +} + +void GWidget::handle_leave_event(GEvent& event) +{ + GApplication::the().hide_tooltip(); + leave_event(event); +} + void GWidget::click_event(GMouseEvent&) { } @@ -261,6 +275,11 @@ Rect GWidget::window_relative_rect() const return rect; } +Rect GWidget::screen_relative_rect() const +{ + return window_relative_rect().translated(window()->position()); +} + GWidget::HitTestResult GWidget::hit_test(int x, int y) { // FIXME: Care about z-order. diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index 99b16857a4..ffeb546d71 100644 --- a/LibGUI/GWidget.h +++ b/LibGUI/GWidget.h @@ -34,6 +34,10 @@ public: Size preferred_size() const { return m_preferred_size; } void set_preferred_size(const Size&); + bool has_tooltip() const { return !m_tooltip.is_empty(); } + String tooltip() const { return m_tooltip; } + void set_tooltip(const String& tooltip) { m_tooltip = tooltip; } + virtual void event(GEvent&) override; virtual void paint_event(GPaintEvent&); virtual void resize_event(GResizeEvent&); @@ -56,6 +60,7 @@ public: Point relative_position() const { return m_relative_rect.location(); } Rect window_relative_rect() const; + Rect screen_relative_rect() const; int x() const { return m_relative_rect.x(); } int y() const { return m_relative_rect.y(); } @@ -149,6 +154,8 @@ private: void handle_resize_event(GResizeEvent&); void handle_mousedown_event(GMouseEvent&); void handle_mouseup_event(GMouseEvent&); + void handle_enter_event(GEvent&); + void handle_leave_event(GEvent&); void do_layout(); GWindow* m_window { nullptr }; @@ -158,6 +165,7 @@ private: Color m_background_color; Color m_foreground_color; RetainPtr<Font> m_font; + String m_tooltip; SizePolicy m_horizontal_size_policy { SizePolicy::Fill }; SizePolicy m_vertical_size_policy { SizePolicy::Fill }; diff --git a/LibGUI/GWindow.cpp b/LibGUI/GWindow.cpp index 973737ccbe..72a5b2fb16 100644 --- a/LibGUI/GWindow.cpp +++ b/LibGUI/GWindow.cpp @@ -83,7 +83,11 @@ void GWindow::hide() WSAPI_ClientMessage request; request.type = WSAPI_ClientMessage::Type::DestroyWindow; request.window_id = m_window_id; - GEventLoop::current().post_message_to_server(request); + GEventLoop::current().sync_request(request, WSAPI_ServerMessage::Type::DidDestroyWindow); + m_window_id = 0; + m_pending_paint_event_rects.clear(); + m_back_bitmap = nullptr; + m_front_bitmap = nullptr; } void GWindow::set_title(const String& title) @@ -139,6 +143,8 @@ void GWindow::set_rect(const Rect& a_rect) request.window_id = m_window_id; request.window.rect = a_rect; GEventLoop::current().post_message_to_server(request); + if (m_main_widget) + m_main_widget->resize(a_rect.size()); } void GWindow::set_window_type(GWindowType window_type) @@ -193,6 +199,8 @@ void GWindow::event(GEvent& event) } if (event.is_paint_event()) { + if (!m_window_id) + return; m_pending_paint_event_rects.clear(); if (!m_main_widget) return; diff --git a/LibGUI/GWindowType.h b/LibGUI/GWindowType.h index 18ae16fdec..d620889249 100644 --- a/LibGUI/GWindowType.h +++ b/LibGUI/GWindowType.h @@ -6,4 +6,5 @@ enum class GWindowType { Menu, WindowSwitcher, Taskbar, + Tooltip, }; diff --git a/Servers/WindowServer/WSAPITypes.h b/Servers/WindowServer/WSAPITypes.h index bcd836f8c7..c47ad1e8b5 100644 --- a/Servers/WindowServer/WSAPITypes.h +++ b/Servers/WindowServer/WSAPITypes.h @@ -26,6 +26,7 @@ enum WSAPI_WindowType { Menu, WindowSwitcher, Taskbar, + Tooltip, }; struct WSAPI_WindowBackingStoreInfo { diff --git a/Servers/WindowServer/WSClientConnection.cpp b/Servers/WindowServer/WSClientConnection.cpp index 202810ba66..ac9c8a5f1a 100644 --- a/Servers/WindowServer/WSClientConnection.cpp +++ b/Servers/WindowServer/WSClientConnection.cpp @@ -124,7 +124,7 @@ void WSClientConnection::handle_request(const WSAPIDestroyMenubarRequest& reques int menubar_id = request.menubar_id(); auto it = m_menubars.find(menubar_id); if (it == m_menubars.end()) { - post_error("Bad menubar ID"); + post_error("WSAPIDestroyMenubarRequest: Bad menubar ID"); return; } auto& menubar = *(*it).value; @@ -152,7 +152,7 @@ void WSClientConnection::handle_request(const WSAPIDestroyMenuRequest& request) int menu_id = static_cast<const WSAPIDestroyMenuRequest&>(request).menu_id(); auto it = m_menus.find(menu_id); if (it == m_menus.end()) { - post_error("Bad menu ID"); + post_error("WSAPIDestroyMenuRequest: Bad menu ID"); return; } auto& menu = *(*it).value; @@ -169,7 +169,7 @@ void WSClientConnection::handle_request(const WSAPISetApplicationMenubarRequest& int menubar_id = request.menubar_id(); auto it = m_menubars.find(menubar_id); if (it == m_menubars.end()) { - post_error("Bad menubar ID"); + post_error("WSAPISetApplicationMenubarRequest: Bad menubar ID"); return; } auto& menubar = *(*it).value; @@ -188,11 +188,11 @@ void WSClientConnection::handle_request(const WSAPIAddMenuToMenubarRequest& requ auto it = m_menubars.find(menubar_id); auto jt = m_menus.find(menu_id); if (it == m_menubars.end()) { - post_error("Bad menubar ID"); + post_error("WSAPIAddMenuToMenubarRequest: Bad menubar ID"); return; } if (jt == m_menus.end()) { - post_error("Bad menu ID"); + post_error("WSAPIAddMenuToMenubarRequest: Bad menu ID"); return; } auto& menubar = *(*it).value; @@ -211,7 +211,7 @@ void WSClientConnection::handle_request(const WSAPIAddMenuItemRequest& request) unsigned identifier = request.identifier(); auto it = m_menus.find(menu_id); if (it == m_menus.end()) { - post_error("Bad menu ID"); + post_error("WSAPIAddMenuItemRequest: Bad menu ID"); return; } auto& menu = *(*it).value; @@ -228,7 +228,7 @@ void WSClientConnection::handle_request(const WSAPIAddMenuSeparatorRequest& requ int menu_id = request.menu_id(); auto it = m_menus.find(menu_id); if (it == m_menus.end()) { - post_error("Bad menu ID"); + post_error("WSAPIAddMenuSeparatorRequest: Bad menu ID"); return; } auto& menu = *(*it).value; @@ -244,7 +244,7 @@ void WSClientConnection::handle_request(const WSAPISetWindowOpacityRequest& requ int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetWindowOpacityRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -276,7 +276,7 @@ void WSClientConnection::handle_request(const WSAPISetWindowTitleRequest& reques int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetWindowTitleRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -288,7 +288,7 @@ void WSClientConnection::handle_request(const WSAPIGetWindowTitleRequest& reques int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIGetWindowTitleRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -306,7 +306,7 @@ void WSClientConnection::handle_request(const WSAPISetWindowRectRequest& request int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetWindowRectRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -319,7 +319,7 @@ void WSClientConnection::handle_request(const WSAPIGetWindowRectRequest& request int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIGetWindowRectRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -334,7 +334,7 @@ void WSClientConnection::handle_request(const WSAPISetClipboardContentsRequest& { auto shared_buffer = SharedBuffer::create_from_shared_buffer_id(request.shared_buffer_id()); if (!shared_buffer) { - post_error("Bad shared buffer ID"); + post_error("WSAPISetClipboardContentsRequest: Bad shared buffer ID"); return; } WSClipboard::the().set_data(*shared_buffer, request.size()); @@ -392,12 +392,16 @@ void WSClientConnection::handle_request(const WSAPIDestroyWindowRequest& request int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIDestroyWindowRequest: Bad window ID"); return; } auto& window = *(*it).value; WSWindowManager::the().invalidate(window); m_windows.remove(it); + WSAPI_ServerMessage response; + response.type = WSAPI_ServerMessage::Type::DidDestroyWindow; + response.window_id = window.window_id(); + post_message(response); } void WSClientConnection::post_paint_request(const WSWindow& window, const Rect& rect) @@ -415,7 +419,7 @@ void WSClientConnection::handle_request(const WSAPIInvalidateRectRequest& reques int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIInvalidateRectRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -427,7 +431,7 @@ void WSClientConnection::handle_request(const WSAPIDidFinishPaintingNotification int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIDidFinishPaintingNotification: Bad window ID"); return; } auto& window = *(*it).value; @@ -446,7 +450,7 @@ void WSClientConnection::handle_request(const WSAPIGetWindowBackingStoreRequest& int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPIGetWindowBackingStoreRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -468,7 +472,7 @@ void WSClientConnection::handle_request(const WSAPISetWindowBackingStoreRequest& int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetWindowBackingStoreRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -500,7 +504,7 @@ void WSClientConnection::handle_request(const WSAPISetGlobalCursorTrackingReques int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetGlobalCursorTrackingRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -512,7 +516,7 @@ void WSClientConnection::handle_request(const WSAPISetWindowOverrideCursorReques int window_id = request.window_id(); auto it = m_windows.find(window_id); if (it == m_windows.end()) { - post_error("Bad window ID"); + post_error("WSAPISetWindowOverrideCursorRequest: Bad window ID"); return; } auto& window = *(*it).value; @@ -523,12 +527,12 @@ void WSClientConnection::handle_request(const WSWMAPISetActiveWindowRequest& req { auto* client = WSClientConnection::from_client_id(request.target_client_id()); if (!client) { - post_error("Bad client ID"); + post_error("WSWMAPISetActiveWindowRequest: Bad client ID"); return; } auto it = client->m_windows.find(request.target_window_id()); if (it == client->m_windows.end()) { - post_error("Bad window ID"); + post_error("WSWMAPISetActiveWindowRequest: Bad window ID"); return; } auto& window = *(*it).value; diff --git a/Servers/WindowServer/WSMessageLoop.cpp b/Servers/WindowServer/WSMessageLoop.cpp index 2f8aecf8c8..d354eb5e1a 100644 --- a/Servers/WindowServer/WSMessageLoop.cpp +++ b/Servers/WindowServer/WSMessageLoop.cpp @@ -260,6 +260,8 @@ static WSWindowType from_api(WSAPI_WindowType api_type) return WSWindowType::WindowSwitcher; case WSAPI_WindowType::Taskbar: return WSWindowType::Taskbar; + case WSAPI_WindowType::Tooltip: + return WSWindowType::Tooltip; default: ASSERT_NOT_REACHED(); } diff --git a/Servers/WindowServer/WSWindow.cpp b/Servers/WindowServer/WSWindow.cpp index 10d800ced5..40dd9b5925 100644 --- a/Servers/WindowServer/WSWindow.cpp +++ b/Servers/WindowServer/WSWindow.cpp @@ -107,6 +107,8 @@ static WSAPI_WindowType to_api(WSWindowType ws_type) return WSAPI_WindowType::WindowSwitcher; case WSWindowType::Taskbar: return WSAPI_WindowType::Taskbar; + case WSWindowType::Tooltip: + return WSAPI_WindowType::Tooltip; default: ASSERT_NOT_REACHED(); } diff --git a/Servers/WindowServer/WSWindowFrame.cpp b/Servers/WindowServer/WSWindowFrame.cpp index 5d33c6a09b..b782082937 100644 --- a/Servers/WindowServer/WSWindowFrame.cpp +++ b/Servers/WindowServer/WSWindowFrame.cpp @@ -119,6 +119,9 @@ void WSWindowFrame::paint(Painter& painter) if (m_window.type() == WSWindowType::Taskbar) return; + if (m_window.type() == WSWindowType::Tooltip) + return; + auto& window = m_window; auto titlebar_rect = title_bar_rect(); @@ -195,6 +198,8 @@ static Rect frame_rect_for_window_type(WSWindowType type, const Rect& rect) return rect; case WSWindowType::Taskbar: return rect; + case WSWindowType::Tooltip: + return rect; default: ASSERT_NOT_REACHED(); } diff --git a/Servers/WindowServer/WSWindowManager.h b/Servers/WindowServer/WSWindowManager.h index b38524b15d..9ad5058107 100644 --- a/Servers/WindowServer/WSWindowManager.h +++ b/Servers/WindowServer/WSWindowManager.h @@ -251,6 +251,8 @@ IterationDecision WSWindowManager::for_each_visible_window_from_back_to_front(Ca return IterationDecision::Abort; if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Taskbar, callback) == IterationDecision::Abort) return IterationDecision::Abort; + if (for_each_visible_window_of_type_from_back_to_front(WSWindowType::Tooltip, callback) == IterationDecision::Abort) + return IterationDecision::Abort; return for_each_visible_window_of_type_from_back_to_front(WSWindowType::WindowSwitcher, callback); } @@ -282,6 +284,8 @@ IterationDecision WSWindowManager::for_each_visible_window_from_front_to_back(Ca { if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Taskbar, callback) == IterationDecision::Abort) return IterationDecision::Abort; + if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Tooltip, callback) == IterationDecision::Abort) + return IterationDecision::Abort; if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Menu, callback) == IterationDecision::Abort) return IterationDecision::Abort; if (for_each_visible_window_of_type_from_front_to_back(WSWindowType::Normal, callback) == IterationDecision::Abort) diff --git a/Servers/WindowServer/WSWindowType.h b/Servers/WindowServer/WSWindowType.h index c45ee10b8d..897e81f74e 100644 --- a/Servers/WindowServer/WSWindowType.h +++ b/Servers/WindowServer/WSWindowType.h @@ -6,4 +6,5 @@ enum class WSWindowType { Menu, WindowSwitcher, Taskbar, + Tooltip, }; |