OpenCPN Partial API docs
Loading...
Searching...
No Matches
tooltip.cpp
1/***************************************************************************
2 * Copyright (C) 2025 by OpenCPN developer team *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18 **************************************************************************/
19
20#include "model/config_vars.h"
21
22#include "tooltip.h"
23#include "chcanv.h"
24#include "color_handler.h"
25#include "font_mgr.h"
26#include "ocpn_platform.h"
27#include "navutil.h"
28#include "top_frame.h"
29
30// Define timer event ID
31#define TOOLTIP_TIMER_ID 10002
32
33//----------------------------------------------------------------------------
34// Tooltip Implementation
35//----------------------------------------------------------------------------
36
37BEGIN_EVENT_TABLE(Tooltip, wxFrame)
38EVT_PAINT(Tooltip::OnPaint)
39EVT_TIMER(TOOLTIP_TIMER_ID, Tooltip::OnTimer)
40END_EVENT_TABLE()
41
42Tooltip::Tooltip(wxWindow *parent, TooltipCallback on_destroy)
43 : wxFrame(parent, wxID_ANY, "", wxPoint(0, 0), wxSize(1, 1),
44 wxNO_BORDER | wxFRAME_FLOAT_ON_PARENT | wxFRAME_NO_TASKBAR),
45 m_showTimer(this, TOOLTIP_TIMER_ID),
46 m_on_destroy(std::move(on_destroy)) {
47 m_pbm = nullptr;
48 m_hiviz = false;
49 m_showPending = false;
50
51 // Initialize colors using SetColorScheme to avoid duplicate code
52 SetColorScheme(GLOBAL_COLOR_SCHEME_RGB);
53
54 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
55 SetBackgroundColour(m_back_color);
56 Hide();
57}
58
59Tooltip::~Tooltip() {
60 m_on_destroy(this);
61 m_showTimer.Stop();
62 delete m_pbm;
63}
64
65bool Tooltip::Destroy() {
66 m_on_destroy(this);
67 return wxFrame::Destroy();
68}
69
70void Tooltip::SetString(const wxString &text) { m_string = text; }
71
72void Tooltip::SetAbsolutePosition(const wxPoint &pt) {
73 m_position = pt;
74 m_requestedPosition = pt;
75}
76
77void Tooltip::SetHiviz(bool hiviz) { m_hiviz = hiviz; }
78
79void Tooltip::SetColorScheme(ColorScheme cs) {
80 m_cs = cs;
81 m_back_color =
82 GetGlobalColor("DILG0"); // Background gets progressively darker
83 m_text_color = GetGlobalColor("DILG4"); // Text gets progressively lighter
84}
85
86void Tooltip::SetRelativePosition(const wxRect &windowRect, int offsetX,
87 int offsetY) {
88 // Calculate initial position to the right of the window
89 wxPoint pos;
90 pos.x = windowRect.x + windowRect.width + offsetX;
91 pos.y = windowRect.y + offsetY;
92
93 // Get tooltip size for boundary checking
94 wxSize tooltipSize = GetRenderedSize();
95 wxSize screenSize = wxGetDisplaySize();
96
97 // Convert to screen coordinates if needed
98 wxPoint screenPos = pos;
99 if (GetParent() && GetParent() != wxTheApp->GetTopWindow()) {
100 screenPos = GetParent()->ClientToScreen(pos);
101 }
102
103 // Adjust horizontal position if tooltip would go off screen
104 if (screenPos.x + tooltipSize.x > screenSize.x) {
105 // Try positioning to the left of the window instead
106 pos.x = windowRect.x - tooltipSize.x - offsetX;
107 if (GetParent() && GetParent() != wxTheApp->GetTopWindow()) {
108 screenPos = GetParent()->ClientToScreen(pos);
109 } else {
110 screenPos = pos;
111 }
112
113 // If still off screen, clamp to screen edge
114 if (screenPos.x < 0) {
115 screenPos.x = offsetX;
116 }
117 }
118
119 // Adjust vertical position if tooltip would go off screen
120 if (screenPos.y + tooltipSize.y > screenSize.y) {
121 // Try positioning above the window instead
122 pos.y = windowRect.y - tooltipSize.y - offsetY;
123 if (GetParent() && GetParent() != wxTheApp->GetTopWindow()) {
124 screenPos = GetParent()->ClientToScreen(pos);
125 } else {
126 screenPos = pos;
127 }
128
129 // If still off screen, clamp to screen edge
130 if (screenPos.y < 0) {
131 screenPos.y = offsetY;
132 }
133 }
134
135 // SetAbsolutePosition expects screen coordinates
136 SetAbsolutePosition(screenPos);
137}
138
140 if (m_string.IsEmpty()) {
141 return wxSize(0, 0);
142 }
143
144 wxScreenDC cdc;
145 double scaler = g_Platform->GetDisplayDIPMult(const_cast<Tooltip *>(this));
146
147 wxFont *plabelFont = FontMgr::Get().GetFont(_("ToolTips"));
148 wxFont sFont = plabelFont->Scaled(1.0 / scaler);
149
150 int w, h;
151 cdc.GetMultiLineTextExtent(m_string, &w, &h, nullptr, &sFont);
152 int sizeX = w + GetCharWidth() * 2;
153 int sizeY = h + GetCharHeight() / 2;
154
155 sizeX *= scaler;
156 sizeY *= scaler;
157
158 return wxSize(sizeX, sizeY);
159}
160
161void Tooltip::CreateBitmap() {
162 if (m_string.IsEmpty()) return;
163
164 wxScreenDC cdc;
165 double scaler = g_Platform->GetDisplayDIPMult(this);
166
167 wxFont *plabelFont = FontMgr::Get().GetFont(_("ToolTips"));
168 wxFont sFont = plabelFont->Scaled(1.0 / scaler);
169
170 int w, h;
171 cdc.GetMultiLineTextExtent(m_string, &w, &h, nullptr, &sFont);
172
173 m_size.x = w + GetCharWidth() * 2;
174 m_size.y = h + GetCharHeight() / 2;
175
176 m_size.x *= scaler;
177 m_size.y *= scaler;
178
179 wxMemoryDC mdc;
180
181 delete m_pbm;
182 m_pbm = new wxBitmap(m_size.x, m_size.y, -1);
183 mdc.SelectObject(*m_pbm);
184
185 wxPen pborder(m_text_color);
186 wxBrush bback(m_back_color);
187 mdc.SetPen(pborder);
188 mdc.SetBrush(bback);
189
190 // High visibility mode for night/dusk color schemes
191 if (m_hiviz) {
192 if ((m_cs == GLOBAL_COLOR_SCHEME_DUSK) ||
193 (m_cs == GLOBAL_COLOR_SCHEME_NIGHT)) {
194 wxBrush hv_back(wxColour(200, 200, 200));
195 mdc.SetBrush(hv_back);
196 }
197 }
198
199 mdc.DrawRectangle(0, 0, m_size.x, m_size.y);
200
201 // Draw the text
202 mdc.SetFont(sFont);
203 mdc.SetTextForeground(m_text_color);
204 mdc.SetTextBackground(m_back_color);
205
206 int offx = GetCharWidth();
207 int offy = GetCharHeight() / 4;
208 offx *= scaler;
209 offy *= scaler;
210 mdc.DrawText(m_string, offx, offy);
211
212 SetClientSize(m_size.x, m_size.y);
213}
214
216 CreateBitmap();
217 CalculateOptimalPosition();
218 SetSize(m_position.x, m_position.y, m_size.x, m_size.y);
219}
220
221void Tooltip::CalculateOptimalPosition() {
222 if (!GetParent()) return;
223
224 wxPoint screenPos = m_requestedPosition;
225
226 // Adjust position to keep tooltip on screen
227 wxSize tooltipSize = GetRenderedSize();
228 wxSize screenSize = wxGetDisplaySize();
229
230 if (screenPos.x + tooltipSize.x > screenSize.x) {
231 screenPos.x = screenSize.x - tooltipSize.x - 10;
232 }
233 if (screenPos.y + tooltipSize.y > screenSize.y) {
234 screenPos.y = screenSize.y - tooltipSize.y - 10;
235 }
236
237 if (screenPos.x < 0) screenPos.x = 10;
238 if (screenPos.y < 0) screenPos.y = 10;
239
240 m_position = screenPos;
241}
242
243void Tooltip::ShowTooltip(int delay_ms) {
244 if (m_string.IsEmpty()) return;
245
246 if (delay_ms > 0) {
247 m_showPending = true;
248 m_showTimer.Start(delay_ms, wxTIMER_ONE_SHOT);
249 } else {
250 SetBitmap();
251 Show();
252#ifndef __WXOSX__
253 if (top_frame::Get()) top_frame::Get()->Raise();
254#endif
255 }
256}
257
259 m_showTimer.Stop();
260 m_showPending = false;
261 Hide();
262}
263
264void Tooltip::OnPaint(wxPaintEvent &event) {
265 int width, height;
266 GetClientSize(&width, &height);
267 wxPaintDC dc(this);
268
269 if (m_string.Len() && m_pbm) {
270 wxMemoryDC mdc;
271 mdc.SelectObject(*m_pbm);
272 dc.Blit(0, 0, width, height, &mdc, 0, 0);
273 }
274}
275
276void Tooltip::OnTimer(wxTimerEvent &event) {
277 if (event.GetId() == TOOLTIP_TIMER_ID && m_showPending) {
278 m_showPending = false;
279 // Ensure we're still in a valid state before proceeding
280 if (!IsBeingDeleted()) {
281 SetBitmap();
282 Show();
283#ifndef __WXOSX__
284 if (top_frame::Get()) top_frame::Get()->Raise();
285#endif
286 }
287 }
288}
289
290//----------------------------------------------------------------------------
291// TooltipManager Implementation
292//----------------------------------------------------------------------------
293
294TooltipManager *TooltipManager::s_instance = nullptr;
295
296TooltipManager::TooltipManager()
297 : m_currentTooltip(nullptr),
298 m_currentParent(nullptr),
299 m_colorScheme(GLOBAL_COLOR_SCHEME_RGB),
300 m_enabled(true),
301 m_showDelay(500),
302 m_hideDelay(5000) {}
303
304TooltipManager::~TooltipManager() { CleanupTooltip(); }
305
307 if (!s_instance) {
308 s_instance = new TooltipManager();
309 }
310 return *s_instance;
311}
312
314 const wxString &text,
315 const wxPoint &position,
316 bool hiviz) {
317 if (!m_enabled || text.IsEmpty()) return;
318
319 // Hide any existing tooltip
320 HideTooltip();
321
322 // Create or reuse tooltip
323 m_currentTooltip = GetOrCreateTooltip(parent);
324 m_currentParent = parent;
325
326 // Configure tooltip
327 m_currentTooltip->SetString(text);
328 m_currentTooltip->SetAbsolutePosition(position);
329 m_currentTooltip->SetHiviz(hiviz);
330 m_currentTooltip->SetColorScheme(m_colorScheme);
331
332 // Show with delay
333 m_currentTooltip->ShowTooltip(m_showDelay);
334}
335
337 const wxString &text, bool hiviz) {
338 if (!window) return;
339
340 // Hide any existing tooltip
341 HideTooltip();
342
343 // Create or reuse tooltip
344 m_currentTooltip = GetOrCreateTooltip(window->GetParent());
345 m_currentParent = window->GetParent();
346
347 // Configure tooltip
348 m_currentTooltip->SetString(text);
349 m_currentTooltip->SetHiviz(hiviz);
350 m_currentTooltip->SetColorScheme(m_colorScheme);
351
352 // Use enhanced positioning with automatic boundary detection
353 wxRect windowRect = window->GetRect();
354 m_currentTooltip->SetRelativePosition(windowRect, 2, 0);
355
356 // Show with delay
357 m_currentTooltip->ShowTooltip(m_showDelay);
358}
359
361 if (m_currentTooltip) {
362 m_currentTooltip->HideTooltip();
363 }
364}
365
367 HideTooltip();
368 CleanupTooltip();
369}
370
371void TooltipManager::SetColorScheme(ColorScheme cs) {
372 m_colorScheme = cs;
373 if (m_currentTooltip) {
374 m_currentTooltip->SetColorScheme(cs);
375 }
376}
377
379 m_enabled = enable;
380 if (!enable) {
382 }
383}
384
386 return m_currentTooltip && m_currentTooltip->IsShown();
387}
388
389Tooltip *TooltipManager::GetOrCreateTooltip(wxWindow *parent) {
390 if (m_currentTooltip && m_currentParent == parent) {
391 return m_currentTooltip;
392 }
393
394 CleanupTooltip();
395
396 m_currentTooltip = new Tooltip(parent, [&](const Tooltip *t) {
397 if (t == m_currentTooltip) m_currentTooltip = nullptr;
398 });
399 m_currentParent = parent;
400
401 return m_currentTooltip;
402}
403void TooltipManager::CleanupTooltip() {
404 if (m_currentTooltip) {
405 m_currentTooltip->HideTooltip();
406 m_currentTooltip->Destroy();
407 m_currentTooltip = nullptr;
408 m_currentParent = nullptr;
409 }
410}
Generic Chart canvas base.
double GetDisplayDIPMult(wxWindow *win)
Get the display scaling factor for DPI-aware rendering.
wxFont * GetFont(const wxString &TextElement, int requested_font_size=0)
Get a font object for a UI element.
Definition font_mgr.cpp:193
Coordinates tooltip display across OpenCPN components.
Definition tooltip.h:140
void SetColorScheme(ColorScheme cs)
Set color scheme for all tooltips.
Definition tooltip.cpp:371
void ShowTooltipForWindow(wxWindow *window, const wxString &text, bool hiviz=false)
Show tooltip for a window using automatic positioning relative to the window.
Definition tooltip.cpp:336
bool IsShown() const
Check if a tooltip is currently shown.
Definition tooltip.cpp:385
void HideAllTooltips()
Hide all tooltips.
Definition tooltip.cpp:366
static TooltipManager & Get()
Get the singleton instance.
Definition tooltip.cpp:306
void HideTooltip()
Hide the current tooltip.
Definition tooltip.cpp:360
void ShowTooltipAtPosition(wxWindow *parent, const wxString &text, const wxPoint &position, bool hiviz=false)
Show tooltip at specified position in absolute screen coordinates (physical pixels).
Definition tooltip.cpp:313
void EnableTooltips(bool enable)
Enable or disable tooltip system.
Definition tooltip.cpp:378
Tooltip with color scheme support and high-visibility mode.
Definition tooltip.h:39
void HideTooltip()
Hide the tooltip immediately.
Definition tooltip.cpp:258
void SetRelativePosition(const wxRect &windowRect, int offsetX=2, int offsetY=0)
Position tooltip relative to a window rectangle with automatic screen boundary detection.
Definition tooltip.cpp:86
void SetAbsolutePosition(const wxPoint &pt)
Set the tooltip position in absolute screen coordinates (physical pixels).
Definition tooltip.cpp:72
void SetHiviz(bool hiviz)
Enable/disable high visibility mode.
Definition tooltip.cpp:77
void SetColorScheme(ColorScheme cs)
Set the color scheme for tooltip appearance.
Definition tooltip.cpp:79
void SetBitmap()
Create the tooltip bitmap.
Definition tooltip.cpp:215
void SetString(const wxString &text)
Set the tooltip text to display.
Definition tooltip.cpp:70
wxSize GetRenderedSize() const
Get the rendered size of the tooltip.
Definition tooltip.cpp:139
void ShowTooltip(int delay_ms=0)
Show the tooltip with optional delay in milliseconds.
Definition tooltip.cpp:243
Global color handling by name.
Global variables stored in configuration file.
Font list manager.
Utility functions.
OpenCPN Platform specific support utilities.
Abstract gFrame/MyFrame interface.