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 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,

View File

@ -23,6 +23,7 @@
#include <gtkmm/messagedialog.h>
#include <gtkmm/scrollbar.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/tooltip.h>
#include <gtkmm/window.h>
#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<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 {
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 {

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

View File

@ -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 {

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);