TooltipManager
TooltipManager Overview
The TooltipManager library (Plots/Common/TooltipManager/) provides a shared tooltip lifecycle manager for all plot widgets. It encapsulates the dwell-timer pattern, hit-test delegation, and content display logic that would otherwise be duplicated across every OpenGL plot widget.
Problem
Every interactive plot widget needs tooltips that:
- Wait for stationarity — only fire after the cursor has been still for a dwell interval (e.g. 500 ms), avoiding expensive per-frame computation
- Hit test — determine what is under the cursor using spatial data structures (QuadTree, kd-tree, etc.)
- Generate content — produce text or rendered imagery (glyphs, mini-plots) describing the hovered element
- Suppress during interaction — disable tooltips while panning, zooming, or dragging
- Clean up on leave — stop timers and hide popups when the mouse exits the widget
Without a shared component, each widget re-implements the same QTimer + std::optional<QPoint> + onTooltipTimer() + leaveEvent() pattern with minor content variations.
Architecture
WhiskerToolbox/Plots/Common/TooltipManager/
├── CMakeLists.txt
├── PlotTooltipManager.hpp # Public API, callback types, data structs
└── PlotTooltipManager.cpp # Timer management, display logic, PixmapPopup
Key Types
| Type | Purpose |
|---|---|
PlotTooltipHit |
Result of a hit test — world coordinates + arbitrary std::any payload |
PlotTooltipContent |
Display content — plain text or QPixmap for rich tooltips |
TooltipHitTestFn |
Callback: QPoint → std::optional<PlotTooltipHit> |
TooltipTextFn |
Callback: PlotTooltipHit → QString |
TooltipContentFn |
Callback: PlotTooltipHit → PlotTooltipContent |
PlotTooltipManager |
QObject owning the dwell timer and display lifecycle |
Callback Design
The manager delegates two concerns to the consuming widget via callbacks:
Hit test provider (
TooltipHitTestFn) — Called when the dwell timer fires. Performs a spatial query at the pending screen position to determine if anything is under the cursor. Returnsstd::nullopton miss.Content provider (
TooltipContentFnorTooltipTextFn) — Called with the hit result to generate display content. For simple cases,setTextProvider()wraps a text callback. For rich tooltips (glyphs, mini-plots), usesetContentProvider()which can return aQPixmap.
Both callbacks are only invoked after the dwell timer fires, so expensive operations (QuadTree lookups, pixmap rendering) never run during rapid mouse movement.
Lifecycle
mouseMoveEvent → onMouseMove(pos, is_interacting)
│
├── if interacting: stop timer, hide tooltip
│
└── else: restart dwell timer with new position
│
▼ (after dwell_ms)
onDwellTimerFired()
│
hit_test_fn(pos) → hit?
│
content_fn(hit) → content?
│
display (QToolTip or PixmapPopup)
leaveEvent → onLeave() → stop timer, hide tooltip
Display Modes
- Text: Uses
QToolTip::showText()— zero allocation, native look - Pixmap: Uses a lightweight
PixmapPopupQWidget withQt::ToolTipwindow flags. Positioned near the cursor with screen-boundary clamping. Created lazily on first use.
Usage
Text Tooltip (EventPlotWidget example)
// Constructor
_tooltip_mgr = std::make_unique<PlotTooltipManager>(this);
_tooltip_mgr->setHitTestProvider(
[this](QPoint pos) -> std::optional<PlotTooltipHit> {
auto hit = findEventNear(pos);
if (!hit) return std::nullopt;
QPointF world = screenToWorld(pos);
PlotTooltipHit result;
result.world_x = static_cast<float>(world.x());
result.world_y = static_cast<float>(world.y());
result.user_data = *hit;
return result;
});
_tooltip_mgr->setTextProvider(
[](PlotTooltipHit const & hit) -> QString {
auto const & data =
std::any_cast<std::pair<int, std::string> const &>(hit.user_data);
return QString("Trial %1\nTime: %2 ms")
.arg(data.first + 1)
.arg(hit.world_x, 0, 'f', 1);
});
// mouseMoveEvent
_tooltip_mgr->onMouseMove(event->pos(), _is_panning);
// leaveEvent
_tooltip_mgr->onLeave();Rich Tooltip (future — mini-plot example)
_tooltip_mgr->setContentProvider(
[this](PlotTooltipHit const & hit) -> PlotTooltipContent {
PlotTooltipContent content;
content.pixmap = renderMiniRasterPlot(hit);
return content;
});Configuration
_tooltip_mgr->setDwellTime(300); // ms before tooltip appears
_tooltip_mgr->setEnabled(false); // disable tooltips
_tooltip_mgr->hide(); // dismiss immediately (e.g. on scene rebuild)Consuming Widgets
| Widget | Hit Test Source | Content Type | Status |
|---|---|---|---|
| EventPlotWidget | SceneHitTester (QuadTree) |
Text (trial + time) | Implemented |
| ScatterPlotWidget | SceneHitTester (QuadTree) |
Text (coordinates) | Planned |
| HeatmapWidget | Row index from Y coordinate | Pixmap (mini PSTH + raster) | Planned |
| LinePlotWidget | Time interpolation | Pixmap (distribution histogram) | Planned |
Dependencies
Qt6::Widgets,Qt6::Core,Qt6::Gui— forQTimer,QToolTip,QLabel,QPixmap- No dependency on
CorePlotting,DataManager, or any widget-specific code
See Also
- EventPlotWidget — First consumer of PlotTooltipManager
- HeatmapWidget Roadmap — Phase 8: Tooltip with stacked PSTH + raster
- ScatterPlotWidget Roadmap — Phase 8: Hover tooltip
- LinePlotWidget Roadmap — Phase 4: Distribution tooltips