Source code for frontend.ui_dialogs.tooltip

"""Tooltip widget for Tkinter/ttk elements."""

from __future__ import annotations

import tkinter as tk

from config import get_env_from_schema
from frontend.theme import get_font


[docs] class ToolTip: """Hover tooltip for any Tkinter widget. Args: widget: The widget to attach the tooltip to. text: The tooltip text. delay: Delay in milliseconds before showing. """ def __init__( self, widget: tk.Widget, text: str, delay: int | None = None, ) -> None: self.widget = widget self.text = text self.delay = int(get_env_from_schema("UI_TOOLTIP_DELAY_MS")) if delay is None else delay self._tipwindow: tk.Toplevel | None = None self._id_after: str | None = None widget.bind("<Enter>", self._on_enter) widget.bind("<Leave>", self._on_leave) widget.bind("<Destroy>", self._on_destroy) def _on_enter(self, _event: tk.Event) -> None: # type: ignore[type-arg] self._id_after = self.widget.after(self.delay, self._show) def _on_leave(self, _event: tk.Event) -> None: # type: ignore[type-arg] if self._id_after: self.widget.after_cancel(self._id_after) self._id_after = None self._hide() def _show(self) -> None: if self._tipwindow: return if not self.text or not self.text.strip(): return x = self.widget.winfo_rootx() + 20 y = self.widget.winfo_rooty() + self.widget.winfo_height() + 5 tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(True) tw.wm_geometry(f"+{x}+{y}") tooltip_bg: str = get_env_from_schema("UI_BUTTON_BG") tooltip_fg: str = get_env_from_schema("UI_FOREGROUND") wraplength: int = get_env_from_schema("UI_TOOLTIP_WRAPLENGTH") padx: int = get_env_from_schema("UI_TOOLTIP_PADX") pady: int = get_env_from_schema("UI_TOOLTIP_PADY") base_font = get_font() base_size = ( int(base_font[1]) if len(base_font) > 1 else int(get_env_from_schema("UI_FONT_SIZE")) ) tooltip_font = (base_font[0], max(6, int(round(base_size * 0.5)))) label = tk.Label( tw, text=self.text, justify=tk.LEFT, background=tooltip_bg, foreground=tooltip_fg, relief=tk.SOLID, borderwidth=1, padx=padx, pady=pady, font=tooltip_font, wraplength=wraplength, ) label.pack() self._tipwindow = tw def _on_destroy(self, _event: tk.Event) -> None: # type: ignore[type-arg] if self._id_after: try: self.widget.after_cancel(self._id_after) except tk.TclError: pass self._id_after = None self._hide() def _hide(self) -> None: if self._tipwindow: self._tipwindow.destroy() self._tipwindow = None