From 3296474c15aa49b8a7b6745edb51bd1a54953e38 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 May 2019 13:53:16 +0000 Subject: [PATCH] Rework tooltip implementation to track tip area. This fixes an elusive GTK issue where tooltips would be spuriously displayed, and makes tooltips behave nicer on Windows. Unfortunately the macOS code is unchanged as the macOS tooltip implementation seems seriously broken in ways I do not understand. --- src/platform/gui.h | 3 ++- src/platform/guigtk.cpp | 46 ++++++++++++++++++++++++++++++++++------- src/platform/guimac.mm | 6 +++--- src/platform/guiwin.cpp | 35 +++++++++++++++++-------------- src/textwin.cpp | 7 +++++-- src/toolbar.cpp | 41 ++++++++++++++++++++---------------- src/ui.h | 3 ++- 7 files changed, 93 insertions(+), 48 deletions(-) diff --git a/src/platform/gui.h b/src/platform/gui.h index c710868b..cef74f6d 100644 --- a/src/platform/gui.h +++ b/src/platform/gui.h @@ -253,7 +253,8 @@ public: virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0; virtual void SetCursor(Cursor cursor) = 0; - virtual void SetTooltip(const std::string &text) = 0; + virtual void SetTooltip(const std::string &text, double x, double y, + double width, double height) = 0; virtual bool IsEditorVisible() = 0; virtual void ShowEditor(double x, double y, double fontHeight, double minWidth, diff --git a/src/platform/guigtk.cpp b/src/platform/guigtk.cpp index a4dfb587..fbcc701a 100644 --- a/src/platform/guigtk.cpp +++ b/src/platform/guigtk.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "config.h" @@ -730,7 +731,10 @@ class GtkWindow : public Gtk::Window { Gtk::HBox _hbox; GtkEditorOverlay _editor_overlay; Gtk::VScrollbar _scrollbar; + bool _is_under_cursor = false; bool _is_fullscreen = false; + std::string _tooltip_text; + Gdk::Rectangle _tooltip_area; public: GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) { @@ -746,6 +750,10 @@ public: _scrollbar.get_adjustment()->signal_value_changed(). connect(sigc::mem_fun(this, &GtkWindow::on_scrollbar_value_changed)); + + get_gl_widget().set_has_tooltip(true); + get_gl_widget().signal_query_tooltip(). + connect(sigc::mem_fun(this, &GtkWindow::on_query_tooltip)); } bool is_full_screen() const { @@ -779,7 +787,34 @@ public: return _scrollbar; } + void set_tooltip(const std::string &text, const Gdk::Rectangle &rect) { + if(_tooltip_text != text) { + _tooltip_text = text; + _tooltip_area = rect; + get_gl_widget().trigger_tooltip_query(); + } + } + protected: + bool on_query_tooltip(int x, int y, bool keyboard_tooltip, + const Glib::RefPtr &tooltip) { + tooltip->set_text(_tooltip_text); + tooltip->set_tip_area(_tooltip_area); + return !_tooltip_text.empty() && (keyboard_tooltip || _is_under_cursor); + } + + bool on_enter_notify_event(GdkEventCrossing* gdk_event) override { + _is_under_cursor = true; + + return true; + } + + bool on_leave_notify_event(GdkEventCrossing* gdk_event) override { + _is_under_cursor = false; + + return true; + } + bool on_delete_event(GdkEventAny* gdk_event) override { if(_receiver->onClose) { _receiver->onClose(); @@ -795,7 +830,7 @@ protected: _receiver->onFullScreen(_is_fullscreen); } - return Gtk::Window::on_window_state_event(gdk_event); + return true; } void on_scrollbar_value_changed() { @@ -940,12 +975,9 @@ public: } } - void SetTooltip(const std::string &text) override { - if(text.empty()) { - gtkWindow.get_gl_widget().set_has_tooltip(false); - } else { - gtkWindow.get_gl_widget().set_tooltip_text(text); - } + void SetTooltip(const std::string &text, double x, double y, + double width, double height) override { + gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height }); } bool IsEditorVisible() override { diff --git a/src/platform/guimac.mm b/src/platform/guimac.mm index f6859639..6ed9d4c1 100644 --- a/src/platform/guimac.mm +++ b/src/platform/guimac.mm @@ -925,10 +925,10 @@ public: } } - void SetTooltip(const std::string &newText) override { + void SetTooltip(const std::string &newText, double x, double y, double w, double h) override { NSString *nsNewText = Wrap(newText); - if(![[ssView toolTip] isEqualToString:nsNewText]) { - [ssView setToolTip:nsNewText]; + if(![nsToolTip isEqualToString:nsNewText]) { + nsToolTip = nsNewText; NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager]; if(newText.empty()) { diff --git a/src/platform/guiwin.cpp b/src/platform/guiwin.cpp index ba0a3a1e..6914a8de 100644 --- a/src/platform/guiwin.cpp +++ b/src/platform/guiwin.cpp @@ -572,9 +572,8 @@ public: TOOLINFOW ti = {}; ti.cbSize = sizeof(ti); - ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS; + ti.uFlags = TTF_SUBCLASS; ti.hwnd = hWindow; - ti.uId = (UINT_PTR)hWindow; ti.lpszText = (LPWSTR)L""; sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti)); sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0)); @@ -1222,27 +1221,31 @@ public: sscheck(::SetCursor(hCursor)); } - void SetTooltip(const std::string &newText) override { - // The following SendMessage calls sometimes fail with ERROR_ACCESS_DENIED for - // no discernible reason, but only on wine. - if(newText.empty()) { - SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0); - SendMessageW(hTooltip, TTM_POP, 0, 0); - } else if(newText != tooltipText) { - tooltipText = newText; + void SetTooltip(const std::string &newText, double x, double y, + double width, double height) override { + if(newText == tooltipText) return; + tooltipText = newText; + + if(!newText.empty()) { + int pixelRatio = GetDevicePixelRatio(); + RECT toolRect; + toolRect.left = (int)(x * pixelRatio); + toolRect.top = (int)(y * pixelRatio); + toolRect.right = toolRect.left + (int)(width * pixelRatio); + toolRect.bottom = toolRect.top + (int)(height * pixelRatio); std::wstring newTextW = Widen(newText); TOOLINFOW ti = {}; ti.cbSize = sizeof(ti); - ti.uFlags = TTF_IDISHWND; ti.hwnd = hWindow; - ti.uId = (UINT_PTR)hWindow; + ti.rect = toolRect; ti.lpszText = &newTextW[0]; - SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti); - - SendMessageW(hTooltip, TTM_ACTIVATE, TRUE, 0); - SendMessageW(hTooltip, TTM_POPUP, 0, 0); + sscheck(SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti)); + sscheck(SendMessageW(hTooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti)); } + // The following SendMessage call sometimes fails with ERROR_ACCESS_DENIED for + // no discernible reason, but only on wine. + SendMessageW(hTooltip, TTM_ACTIVATE, !newText.empty(), 0); } bool IsEditorVisible() override { diff --git a/src/textwin.cpp b/src/textwin.cpp index 2a1eeb8a..4ae71d66 100644 --- a/src/textwin.cpp +++ b/src/textwin.cpp @@ -597,12 +597,15 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow hoveredButton = NULL; } + double hoveredX, hoveredY; for(Button *button : buttons) { if(how == PAINT) { button->Draw(uiCanvas, x, y, (button == hoveredButton)); } else if(mx > x - 2 && mx < x + 26 && my < y + 2 && my > y - 26) { hoveredButton = button; + hoveredX = x - 2; + hoveredY = y - 26; if(how == CLICK) { button->Click(); } @@ -613,9 +616,9 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow if(how != PAINT && hoveredButton != oldHovered) { if(hoveredButton == NULL) { - window->SetTooltip(""); + window->SetTooltip("", 0, 0, 0, 0); } else { - window->SetTooltip(hoveredButton->Tooltip()); + window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28); } window->Invalidate(); } diff --git a/src/toolbar.cpp b/src/toolbar.cpp index ca88d750..4ca478c2 100644 --- a/src/toolbar.cpp +++ b/src/toolbar.cpp @@ -87,7 +87,7 @@ static ToolIcon Toolbar[] = { }; void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) { - ToolbarDrawOrHitTest(0, 0, canvas, NULL); + ToolbarDrawOrHitTest(0, 0, canvas, NULL, NULL, NULL); } bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { @@ -97,11 +97,12 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { x += ((int)width/2); y += ((int)height/2); - Command hit; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit); + Command hitCommand; + int hitX, hitY; + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, &hitX, &hitY); - if(hit != toolbarHovered) { - toolbarHovered = hit; + if(hitCommand != toolbarHovered) { + toolbarHovered = hitCommand; Invalidate(); } @@ -119,7 +120,9 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { tooltip += ssprintf(" (%s)", accelDesc.c_str()); } - window->SetTooltip(tooltip); + window->SetTooltip(tooltip, hitX, hitY, 32, 32); + } else { + window->SetTooltip("", 0, 0, 0, 0); } return withinToolbar; @@ -132,16 +135,16 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) { x += ((int)width/2); y += ((int)height/2); - Command hit; - bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit); - if(hit != Command::NONE) { - SS.GW.ActivateCommand(hit); + Command hitCommand; + bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, NULL, NULL); + if(hitCommand != Command::NONE) { + SS.GW.ActivateCommand(hitCommand); } return withinToolbar; } -bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, - UiCanvas *canvas, Command *menuHit) +bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas, + Command *hitCommand, int *hitX, int *hitY) { double width, height; window->GetContentSize(&width, &height); @@ -154,9 +157,9 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, bool withinToolbar = (mx >= aleft && mx <= aright && my <= atop && my >= abot); - - // Initialize/clear menuHit. - if(menuHit) *menuHit = Command::NONE; + + // Initialize/clear hitCommand. + if(hitCommand) *hitCommand = Command::NONE; if(!canvas && !withinToolbar) { // This gets called every MouseMove event, so return quickly. @@ -203,17 +206,19 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, (pending.operation == Pending::COMMAND && pending.command == icon.command)) { // Highlight the hovered or pending item. - int boxhw = 15; + const int boxhw = 15; canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw, /*fillColor=*/{ 255, 255, 0, 75 }, /*outlineColor=*/{}); } } else { - int boxhw = 16; + const int boxhw = 16; if(mx < (x+boxhw) && mx > (x - boxhw) && my < (y+boxhw) && my > (y - boxhw)) { - if(menuHit) *menuHit = icon.command; + if(hitCommand) *hitCommand = icon.command; + if(hitX) *hitX = x - boxhw; + if(hitY) *hitY = height - (y + boxhw); } } diff --git a/src/ui.h b/src/ui.h index cb6f06a6..4fef169e 100644 --- a/src/ui.h +++ b/src/ui.h @@ -756,7 +756,8 @@ public: void ClearSuper(); // The toolbar, in toolbar.cpp - bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, Command *menuHit); + bool ToolbarDrawOrHitTest(int x, int y, UiCanvas *canvas, + Command *hitCommand, int *hitX, int *hitY); void ToolbarDraw(UiCanvas *canvas); bool ToolbarMouseMoved(int x, int y); bool ToolbarMouseDown(int x, int y);