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.
pull/434/head
whitequark 2019-05-23 13:53:16 +00:00
parent f6484c78e7
commit 3296474c15
7 changed files with 93 additions and 48 deletions

View File

@ -253,7 +253,8 @@ public:
virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0; virtual void ThawPosition(SettingsRef settings, const std::string &key) = 0;
virtual void SetCursor(Cursor cursor) = 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 bool IsEditorVisible() = 0;
virtual void ShowEditor(double x, double y, double fontHeight, double minWidth, virtual void ShowEditor(double x, double y, double fontHeight, double minWidth,

View File

@ -23,6 +23,7 @@
#include <gtkmm/messagedialog.h> #include <gtkmm/messagedialog.h>
#include <gtkmm/scrollbar.h> #include <gtkmm/scrollbar.h>
#include <gtkmm/separatormenuitem.h> #include <gtkmm/separatormenuitem.h>
#include <gtkmm/tooltip.h>
#include <gtkmm/window.h> #include <gtkmm/window.h>
#include "config.h" #include "config.h"
@ -730,7 +731,10 @@ class GtkWindow : public Gtk::Window {
Gtk::HBox _hbox; Gtk::HBox _hbox;
GtkEditorOverlay _editor_overlay; GtkEditorOverlay _editor_overlay;
Gtk::VScrollbar _scrollbar; Gtk::VScrollbar _scrollbar;
bool _is_under_cursor = false;
bool _is_fullscreen = false; bool _is_fullscreen = false;
std::string _tooltip_text;
Gdk::Rectangle _tooltip_area;
public: public:
GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) { GtkWindow(Platform::Window *receiver) : _receiver(receiver), _editor_overlay(receiver) {
@ -746,6 +750,10 @@ public:
_scrollbar.get_adjustment()->signal_value_changed(). _scrollbar.get_adjustment()->signal_value_changed().
connect(sigc::mem_fun(this, &GtkWindow::on_scrollbar_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 { bool is_full_screen() const {
@ -779,7 +787,34 @@ public:
return _scrollbar; 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: protected:
bool on_query_tooltip(int x, int y, bool keyboard_tooltip,
const Glib::RefPtr<Gtk::Tooltip> &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 { bool on_delete_event(GdkEventAny* gdk_event) override {
if(_receiver->onClose) { if(_receiver->onClose) {
_receiver->onClose(); _receiver->onClose();
@ -795,7 +830,7 @@ protected:
_receiver->onFullScreen(_is_fullscreen); _receiver->onFullScreen(_is_fullscreen);
} }
return Gtk::Window::on_window_state_event(gdk_event); return true;
} }
void on_scrollbar_value_changed() { void on_scrollbar_value_changed() {
@ -940,12 +975,9 @@ public:
} }
} }
void SetTooltip(const std::string &text) override { void SetTooltip(const std::string &text, double x, double y,
if(text.empty()) { double width, double height) override {
gtkWindow.get_gl_widget().set_has_tooltip(false); gtkWindow.set_tooltip(text, { (int)x, (int)y, (int)width, (int)height });
} else {
gtkWindow.get_gl_widget().set_tooltip_text(text);
}
} }
bool IsEditorVisible() override { bool IsEditorVisible() override {

View File

@ -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); NSString *nsNewText = Wrap(newText);
if(![[ssView toolTip] isEqualToString:nsNewText]) { if(![nsToolTip isEqualToString:nsNewText]) {
[ssView setToolTip:nsNewText]; nsToolTip = nsNewText;
NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager]; NSToolTipManager *nsToolTipManager = [NSToolTipManager sharedToolTipManager];
if(newText.empty()) { if(newText.empty()) {

View File

@ -572,9 +572,8 @@ public:
TOOLINFOW ti = {}; TOOLINFOW ti = {};
ti.cbSize = sizeof(ti); ti.cbSize = sizeof(ti);
ti.uFlags = TTF_IDISHWND|TTF_SUBCLASS; ti.uFlags = TTF_SUBCLASS;
ti.hwnd = hWindow; ti.hwnd = hWindow;
ti.uId = (UINT_PTR)hWindow;
ti.lpszText = (LPWSTR)L""; ti.lpszText = (LPWSTR)L"";
sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti)); sscheck(SendMessageW(hTooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti));
sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0)); sscheck(SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0));
@ -1222,27 +1221,31 @@ public:
sscheck(::SetCursor(hCursor)); sscheck(::SetCursor(hCursor));
} }
void SetTooltip(const std::string &newText) override { void SetTooltip(const std::string &newText, double x, double y,
// The following SendMessage calls sometimes fail with ERROR_ACCESS_DENIED for double width, double height) override {
// no discernible reason, but only on wine. if(newText == tooltipText) return;
if(newText.empty()) {
SendMessageW(hTooltip, TTM_ACTIVATE, FALSE, 0);
SendMessageW(hTooltip, TTM_POP, 0, 0);
} else if(newText != tooltipText) {
tooltipText = newText; 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); std::wstring newTextW = Widen(newText);
TOOLINFOW ti = {}; TOOLINFOW ti = {};
ti.cbSize = sizeof(ti); ti.cbSize = sizeof(ti);
ti.uFlags = TTF_IDISHWND;
ti.hwnd = hWindow; ti.hwnd = hWindow;
ti.uId = (UINT_PTR)hWindow; ti.rect = toolRect;
ti.lpszText = &newTextW[0]; ti.lpszText = &newTextW[0];
SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti); sscheck(SendMessageW(hTooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti));
sscheck(SendMessageW(hTooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti));
SendMessageW(hTooltip, TTM_ACTIVATE, TRUE, 0);
SendMessageW(hTooltip, TTM_POPUP, 0, 0);
} }
// 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 { bool IsEditorVisible() override {

View File

@ -597,12 +597,15 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
hoveredButton = NULL; hoveredButton = NULL;
} }
double hoveredX, hoveredY;
for(Button *button : buttons) { for(Button *button : buttons) {
if(how == PAINT) { if(how == PAINT) {
button->Draw(uiCanvas, x, y, (button == hoveredButton)); button->Draw(uiCanvas, x, y, (button == hoveredButton));
} else if(mx > x - 2 && mx < x + 26 && } else if(mx > x - 2 && mx < x + 26 &&
my < y + 2 && my > y - 26) { my < y + 2 && my > y - 26) {
hoveredButton = button; hoveredButton = button;
hoveredX = x - 2;
hoveredY = y - 26;
if(how == CLICK) { if(how == CLICK) {
button->Click(); button->Click();
} }
@ -613,9 +616,9 @@ void TextWindow::DrawOrHitTestIcons(UiCanvas *uiCanvas, TextWindow::DrawOrHitHow
if(how != PAINT && hoveredButton != oldHovered) { if(how != PAINT && hoveredButton != oldHovered) {
if(hoveredButton == NULL) { if(hoveredButton == NULL) {
window->SetTooltip(""); window->SetTooltip("", 0, 0, 0, 0);
} else { } else {
window->SetTooltip(hoveredButton->Tooltip()); window->SetTooltip(hoveredButton->Tooltip(), hoveredX, hoveredY, 28, 28);
} }
window->Invalidate(); window->Invalidate();
} }

View File

@ -87,7 +87,7 @@ static ToolIcon Toolbar[] = {
}; };
void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) { void GraphicsWindow::ToolbarDraw(UiCanvas *canvas) {
ToolbarDrawOrHitTest(0, 0, canvas, NULL); ToolbarDrawOrHitTest(0, 0, canvas, NULL, NULL, NULL);
} }
bool GraphicsWindow::ToolbarMouseMoved(int x, int y) { bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
@ -97,11 +97,12 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
x += ((int)width/2); x += ((int)width/2);
y += ((int)height/2); y += ((int)height/2);
Command hit; Command hitCommand;
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit); int hitX, hitY;
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, &hitX, &hitY);
if(hit != toolbarHovered) { if(hitCommand != toolbarHovered) {
toolbarHovered = hit; toolbarHovered = hitCommand;
Invalidate(); Invalidate();
} }
@ -119,7 +120,9 @@ bool GraphicsWindow::ToolbarMouseMoved(int x, int y) {
tooltip += ssprintf(" (%s)", accelDesc.c_str()); 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; return withinToolbar;
@ -132,16 +135,16 @@ bool GraphicsWindow::ToolbarMouseDown(int x, int y) {
x += ((int)width/2); x += ((int)width/2);
y += ((int)height/2); y += ((int)height/2);
Command hit; Command hitCommand;
bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hit); bool withinToolbar = ToolbarDrawOrHitTest(x, y, NULL, &hitCommand, NULL, NULL);
if(hit != Command::NONE) { if(hitCommand != Command::NONE) {
SS.GW.ActivateCommand(hit); SS.GW.ActivateCommand(hitCommand);
} }
return withinToolbar; return withinToolbar;
} }
bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my, UiCanvas *canvas,
UiCanvas *canvas, Command *menuHit) Command *hitCommand, int *hitX, int *hitY)
{ {
double width, height; double width, height;
window->GetContentSize(&width, &height); window->GetContentSize(&width, &height);
@ -155,8 +158,8 @@ bool GraphicsWindow::ToolbarDrawOrHitTest(int mx, int my,
bool withinToolbar = bool withinToolbar =
(mx >= aleft && mx <= aright && my <= atop && my >= abot); (mx >= aleft && mx <= aright && my <= atop && my >= abot);
// Initialize/clear menuHit. // Initialize/clear hitCommand.
if(menuHit) *menuHit = Command::NONE; if(hitCommand) *hitCommand = Command::NONE;
if(!canvas && !withinToolbar) { if(!canvas && !withinToolbar) {
// This gets called every MouseMove event, so return quickly. // 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.operation == Pending::COMMAND &&
pending.command == icon.command)) { pending.command == icon.command)) {
// Highlight the hovered or pending item. // Highlight the hovered or pending item.
int boxhw = 15; const int boxhw = 15;
canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw, canvas->DrawRect(x+boxhw, x-boxhw, y+boxhw, y-boxhw,
/*fillColor=*/{ 255, 255, 0, 75 }, /*fillColor=*/{ 255, 255, 0, 75 },
/*outlineColor=*/{}); /*outlineColor=*/{});
} }
} else { } else {
int boxhw = 16; const int boxhw = 16;
if(mx < (x+boxhw) && mx > (x - boxhw) && if(mx < (x+boxhw) && mx > (x - boxhw) &&
my < (y+boxhw) && my > (y - 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);
} }
} }

View File

@ -756,7 +756,8 @@ public:
void ClearSuper(); void ClearSuper();
// The toolbar, in toolbar.cpp // 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); void ToolbarDraw(UiCanvas *canvas);
bool ToolbarMouseMoved(int x, int y); bool ToolbarMouseMoved(int x, int y);
bool ToolbarMouseDown(int x, int y); bool ToolbarMouseDown(int x, int y);