Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ python_requires = >= 3.7
install_requires =
importlib-metadata; python_version < "3.8"
typing-extensions; python_version < "3.8"
screeninfo

[options.packages.find]
exclude =
Expand Down
28 changes: 28 additions & 0 deletions test/test_tktooltip.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import tkinter as tk
from typing import Callable
from unittest.mock import patch

import pytest
from screeninfo.common import Monitor

from tktooltip import ToolTip, ToolTipStatus

Expand Down Expand Up @@ -91,3 +93,29 @@ def test_tooltip_destroy(widget: tk.Widget):
tooltip.destroy()
print(tooltip.bindigs)
assert tooltip.bindigs == []


@pytest.fixture
def get_monitors():
return [
Monitor(x=0, y=0, width=1920, height=1080, name="DISPLAY1", is_primary=True),
Monitor(x=1920, y=0, width=1366, height=768, name="DISPLAY2", is_primary=False),
Monitor(
x=3286, y=0, width=1920, height=1080, name="DISPLAY3", is_primary=False
),
]


def test_tooltip_overflow(widget: tk.Widget, get_monitors: list[Monitor]):
with patch("screeninfo.get_monitors", return_value=get_monitors):
tooltip = ToolTip(widget, msg="Test\nTest")
widget.event_generate("<Enter>", rootx=3280, rooty=760)
tooltip._show()
assert (
tooltip.message_widget.winfo_rootx() + tooltip.message_widget.winfo_width()
< 3286
)
assert (
tooltip.message_widget.winfo_rooty() + tooltip.message_widget.winfo_height()
< 768
)
43 changes: 40 additions & 3 deletions tktooltip/tooltip.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from enum import Enum, auto
from typing import Any, Callable

import screeninfo

# This code is based on Tucker Beck's implementation licensed under an MIT License
# Original code: http://code.activestate.com/recipes/576688-tooltip-for-tkinter/

Expand Down Expand Up @@ -85,6 +87,8 @@ def __init__(
self.withdraw() # Hide initially in case there is a delay
# Disable ToolTip's title bar
self.overrideredirect(True)
# Mkae ToolTip topmost to display over taskbar
self.attributes("-topmost", True)

# StringVar instance for msg string|function
self.msg_var = tk.StringVar()
Expand All @@ -93,8 +97,9 @@ def __init__(
self.delay = delay
self.follow = follow
self.refresh = refresh
self.x_offset = x_offset
self.y_offset = y_offset
# ensure a minimum offset of 2 to avoid flickering
self.x_offset = x_offset if x_offset > 0 else 2
self.y_offset = y_offset if y_offset > 0 else 2
# visibility status of the ToolTip inside|outside|visible
self.status = ToolTipStatus.OUTSIDE
self.last_moved = 0.0
Expand All @@ -107,6 +112,7 @@ def __init__(
**self.message_kwargs,
)
self.message_widget.grid()
self.monitors = screeninfo.get_monitors()
self.bindigs = self._init_bindings()

def _init_bindings(self) -> list[Binding]:
Expand Down Expand Up @@ -150,7 +156,38 @@ def _update_tooltip_coords(self, event: tk.Event) -> None:
"""
Updates the ToolTip's position.
"""
self.geometry(f"+{event.x_root + self.x_offset}+{event.y_root + self.y_offset}")
w_left = event.x_root + self.x_offset
w_top = event.y_root + self.y_offset

w_right = w_left + self.message_widget.winfo_width()
w_bottom = w_top + self.message_widget.winfo_height()

for monitor in self.monitors:
# check if cursor is in monitor
if (
monitor.x <= event.x_root < monitor.x + monitor.width
and monitor.y <= event.y_root < monitor.y + monitor.height
):
# move tooltip left if it exceedes the monitor's right boundary
if w_right >= monitor.x + monitor.width:
w_left = (
monitor.x
+ monitor.width
- self.message_widget.winfo_width()
- 2
)

# flip tooltip if it exceedes the monitor's bottom boundary
if w_bottom >= monitor.y + monitor.height:
w_top = (
event.y_root
- self.y_offset
- self.message_widget.winfo_height()
)

break

self.geometry(f"+{w_left}+{w_top}")

def _update_message(self) -> None:
"""Update the message displayed in the tooltip."""
Expand Down
Loading