Skip to content

Commit d37f675

Browse files
authored
Merge tooltip into master to replace Balloon with Tooltip (PR #51)
2 parents ff737ff + c729745 commit d37f675

File tree

9 files changed

+120
-43
lines changed

9 files changed

+120
-43
lines changed

AUTHORS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file contains a list of all the authors of widgets in this repository. Plea
88
* `ScrolledListbox`
99
* `FontChooser`, based on an idea by [Nelson Brochado](https://www.github.com/nbro)
1010
* `FontSelectFrame`
11-
* `Balloon`
11+
* `Tooltip`
1212
* `ItemsCanvas`
1313
* `TimeLine`
1414
- The Python Team

docs/source/authors.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ List of all the authors of widgets in this repository. Please note that this lis
1111
* :class:`~ttkwidgets.ScrolledListbox`
1212
* :class:`~ttkwidgets.font.FontChooser`, based on an idea by `Nelson Brochado <https://www.github.com/nbro>`_
1313
* :class:`~ttkwidgets.font.FontSelectFrame`
14-
* :class:`~ttkwidgets.frames.Balloon`
14+
* :class:`~ttkwidgets.frames.Tooltip`
1515
* :class:`~ttkwidgets.ItemsCanvas`
1616
* :class:`~ttkwidgets.TimeLine`
1717

docs/source/ttkwidgets/ttkwidgets.frames.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ ttkwidgets.frames
1111
:nosignatures:
1212
:toctree: ttkwidgets.frames
1313

14-
Balloon
14+
Tooltip
1515
ScrolledFrame
1616
ToggledFrame

docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst renamed to docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Tooltip.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
Balloon
1+
Tooltip
22
=======
33

44
.. currentmodule:: ttkwidgets.frames
55

6-
.. autoclass:: Balloon
6+
.. autoclass:: Tooltip
77
:show-inheritance:
88
:members:
99

10-
.. automethod:: __init__
10+
.. automethod:: __init__
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
# Copyright (c) RedFantom 2017
44
# For license see LICENSE
5-
from ttkwidgets.frames import Balloon
5+
from ttkwidgets.frames import Tooltip
66
import tkinter as tk
7-
from tkinter import ttk
87

98

109
window = tk.Tk()
1110
button = tk.Button(window, text="Button", command=window.destroy)
1211
button.pack()
13-
balloon = Balloon(button)
12+
balloon = Tooltip(button)
1413
window.mainloop()

tests/test_balloon.py

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
1-
# Copyright (c) RedFantom 2017
2-
# For license see LICENSE
3-
from ttkwidgets.frames import Balloon
1+
"""
2+
Author: RedFantom
3+
License: GNU GPLv3
4+
Source: This repository
5+
"""
6+
from ttkwidgets.frames import Tooltip
7+
from ttkwidgets.utilities import parse_geometry_string
48
from tests import BaseWidgetTest
59
import tkinter as tk
610
from time import sleep
711

812

9-
class TestBalloon(BaseWidgetTest):
13+
class TestTooltip(BaseWidgetTest):
1014
def test_balloon_init(self):
11-
balloon = Balloon(self.window)
15+
balloon = Tooltip(self.window)
1216
self.window.update()
1317

1418
def test_balloon_kwargs(self):
15-
balloon = Balloon(self.window, headertext="Help", text="This is a test for the Balloon widget.", width=300,
16-
timeout=2, background="white")
19+
balloon = Tooltip(self.window, headertext="Help", text="This is a test for the Tooltip widget.", width=300,
20+
timeout=2, background="white", showheader=True, offset=(20, 20), static=True)
1721
self.assertEqual(balloon.cget("headertext"), "Help")
18-
self.assertEqual(balloon.cget("text"), "This is a test for the Balloon widget.")
22+
self.assertEqual(balloon.cget("text"), "This is a test for the Tooltip widget.")
1923
self.assertEqual(balloon.cget("width"), 300)
2024
self.assertEqual(balloon.cget("timeout"), 2)
2125
self.assertEqual(balloon.cget("background"), "white")
2226

23-
balloon.config(headertext="New Help", text="This is another test for the Balloon widget.", width=400,
27+
balloon.config(headertext="New Help", text="This is another test for the Tooltip widget.", width=400,
2428
timeout=3, background="black")
2529
self.assertEqual(balloon["headertext"], "New Help")
26-
self.assertEqual(balloon["text"], "This is another test for the Balloon widget.")
30+
self.assertEqual(balloon["text"], "This is another test for the Tooltip widget.")
2731
self.assertEqual(balloon["width"], 400)
2832
self.assertEqual(balloon["timeout"], 3)
2933
self.assertEqual(balloon["background"], "black")
34+
self.assertEqual(balloon["showheader"], True)
35+
self.assertEqual(balloon["offset"], (20, 20))
36+
self.assertEqual(balloon["static"], True)
3037

3138
# Keys for the Frame widget
3239
balloon.configure(height=40)
@@ -38,8 +45,29 @@ def test_balloon_kwargs(self):
3845
for key in ["headertext", "text", "width", "timeout", "background"]:
3946
self.assertIn(key, balloon.keys())
4047

48+
balloon.config(showheader=False)
49+
balloon.show()
50+
self.assertFalse(balloon.header_label.winfo_viewable() == 1)
51+
52+
balloon.config(offset=(0, 0))
53+
balloon.show()
54+
self.window.update()
55+
x1, y1, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry())
56+
balloon.config(offset=(20, 20))
57+
balloon._on_leave(None)
58+
balloon.show()
59+
self.window.update()
60+
x2, y2, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry())
61+
self.assertTrue(x2 - x1 == 20 and y2 - y1 == 20)
62+
63+
balloon.config(static=False)
64+
balloon.show()
65+
self.window.update()
66+
x3, y3, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry())
67+
self.assertFalse(x2 == x3 or y2 == y3)
68+
4169
def test_balloon_show(self):
42-
balloon = Balloon(self.window)
70+
balloon = Tooltip(self.window)
4371
self.window.update()
4472
balloon.show()
4573
self.window.update()
@@ -51,7 +79,7 @@ def test_balloon_show(self):
5179
self.assertIs(balloon._toplevel, None)
5280

5381
def test_balloon_events(self):
54-
balloon = Balloon(self.window, timeout=0.2)
82+
balloon = Tooltip(self.window, timeout=0.2)
5583
balloon._on_enter(None)
5684
self.window.update()
5785
sleep(0.3)
@@ -61,7 +89,6 @@ def test_balloon_events(self):
6189
self.assertIs(balloon._toplevel, None)
6290

6391
def test_balloon_events_noshow(self):
64-
balloon = Balloon(self.window)
92+
balloon = Tooltip(self.window)
6593
balloon._on_enter(None)
6694
balloon._on_leave(None)
67-

ttkwidgets/frames/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
# Available under the license found in LICENSE
33
from .scrolledframe import ScrolledFrame
44
from .toggledframe import ToggledFrame
5-
from .balloon import Balloon
5+
from .tooltip import Tooltip
Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,35 @@
1010
from ttkwidgets.utilities import get_assets_directory
1111

1212

13-
class Balloon(ttk.Frame):
13+
class Tooltip(ttk.Frame):
1414
"""Simple help hover balloon."""
1515

1616
def __init__(self, master=None, headertext="Help", text="Some great help is displayed here.", width=200, timeout=1,
17-
background="#fef9cd", **kwargs):
17+
background="#fef9cd", offset=(2, 2), showheader=True, static=False, **kwargs):
1818
"""
19-
Create a Balloon.
19+
Create a Tooltip
2020
21-
:param master: widget to bind the Balloon to
21+
:param master: widget to bind the Tooltip to
2222
:type master: widget
2323
:param headertext: text to show in window header
2424
:type headertext: str
2525
:param text: text to show as help text
2626
:type text: str
2727
:param width: width of the window
2828
:type width: int
29-
:param timeout: timeout in seconds to wait until the Balloon is shown
29+
:param timeout: timeout in seconds to wait until the Tooltip is shown
3030
:type timeout: float
31-
:param background: background color of the Balloon
31+
:param background: background color of the Tooltip
3232
:type background: str
33+
:param offset: The offset from the mouse position the Ballon shows up
34+
:type offset: Tuple[int, int]
35+
:param showheader: Whether to display the header with image
36+
:type showheader: bool
37+
:param static: Whether to display the tooltip with static
38+
position. When the position is set to static, the balloon
39+
will always appear an offset from the bottom right corner of
40+
the widget.
41+
:type static: bool
3342
:param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer
3443
"""
3544
ttk.Frame.__init__(self, master, **kwargs)
@@ -47,11 +56,21 @@ def __init__(self, master=None, headertext="Help", text="Some great help is disp
4756
self.__headertext = headertext
4857
self.__text = text
4958
self.__width = width
59+
self.__offset = offset
60+
self.__showheader = showheader
61+
self.__static = static
62+
5063
self.master = master
5164
self._id = None
5265
self._timeout = timeout
53-
self.master.bind("<Enter>", self._on_enter)
54-
self.master.bind("<Leave>", self._on_leave)
66+
67+
self._bind_to_master()
68+
69+
def _bind_to_master(self):
70+
"""Bind the Balloon widget to the master widget's events"""
71+
self.master.bind("<Enter>", self._on_enter, "add")
72+
self.master.bind("<Leave>", self._on_leave, "add")
73+
self.master.bind("<ButtonPress>", self._on_leave, "add")
5574

5675
def __getitem__(self, key):
5776
return self.cget(key)
@@ -62,7 +81,8 @@ def __setitem__(self, key, value):
6281
def _grid_widgets(self):
6382
"""Place the widgets in the Toplevel."""
6483
self._canvas.grid(sticky="nswe")
65-
self.header_label.grid(row=1, column=1, sticky="nswe", pady=5, padx=5)
84+
if self.__showheader is True:
85+
self.header_label.grid(row=1, column=1, sticky="nswe", pady=5, padx=5)
6686
self.text_label.grid(row=3, column=1, sticky="nswe", pady=6, padx=5)
6787

6888
def _on_enter(self, event):
@@ -80,9 +100,10 @@ def _on_leave(self, event):
80100

81101
def show(self):
82102
"""
83-
Create the Toplevel widget and its child widgets to show in the spot of the cursor.
103+
Create the Toplevel and its children to show near the cursor
84104
85-
This is the callback for the delayed :obj:`<Enter>` event (see :meth:`~Balloon._on_enter`).
105+
This is the callback for the delayed :obj:`<Enter>` event
106+
(see :meth:`~Tooltip._on_enter`).
86107
"""
87108
self._toplevel = tk.Toplevel(self.master)
88109
self._canvas = tk.Canvas(self._toplevel, background=self.__background)
@@ -93,11 +114,17 @@ def show(self):
93114
self._toplevel.attributes("-topmost", True)
94115
self._toplevel.overrideredirect(True)
95116
self._grid_widgets()
96-
x, y = self.master.winfo_pointerxy()
117+
if self.__static is True:
118+
x, y = self.master.winfo_rootx(), self.master.winfo_rooty()
119+
w, h = self.master.winfo_width(), self.master.winfo_height()
120+
x, y = x + w, y + h
121+
else:
122+
x, y = self.master.winfo_pointerxy()
97123
self._canvas.update()
98124
# Update the Geometry of the Toplevel to update its position and size
99-
self._toplevel.geometry("{0}x{1}+{2}+{3}".format(self._canvas.winfo_width(), self._canvas.winfo_height(),
100-
x + 2, y + 2))
125+
self._toplevel.geometry("{0}x{1}+{2}+{3}".format(
126+
self._canvas.winfo_width(), self._canvas.winfo_height(),
127+
x + self.__offset[0], y + self.__offset[1]))
101128

102129
def cget(self, key):
103130
"""
@@ -107,7 +134,8 @@ def cget(self, key):
107134
:type key: str
108135
:return: value of the option
109136
110-
To get the list of options for this widget, call the method :meth:`~Balloon.keys`.
137+
To get the list of options for this widget, call the method
138+
:meth:`~Tooltip.keys`.
111139
"""
112140
if key == "headertext":
113141
return self.__headertext
@@ -119,21 +147,31 @@ def cget(self, key):
119147
return self._timeout
120148
elif key == "background":
121149
return self.__background
150+
elif key == "offset":
151+
return self.__offset
152+
elif key == "showheader":
153+
return self.__showheader
154+
elif key == "static":
155+
return self.__static
122156
else:
123157
return ttk.Frame.cget(self, key)
124158

125159
def config(self, **kwargs):
126160
"""
127161
Configure resources of the widget.
128162
129-
To get the list of options for this widget, call the method :meth:`~Balloon.keys`.
130-
See :meth:`~Balloon.__init__` for a description of the widget specific option.
163+
To get the list of options for this widget, call the method
164+
:meth:`~Tooltip.keys`. See :meth:`~Tooltip.__init__` for a
165+
description of the widget specific option.
131166
"""
132167
self.__headertext = kwargs.pop("headertext", self.__headertext)
133168
self.__text = kwargs.pop("text", self.__text)
134169
self.__width = kwargs.pop("width", self.__width)
135170
self._timeout = kwargs.pop("timeout", self._timeout)
136171
self.__background = kwargs.pop("background", self.__background)
172+
self.__offset = kwargs.pop("offset", self.__offset)
173+
self.__showheader = kwargs.pop("showheader", self.__showheader)
174+
self.__static = kwargs.pop("static", self.__static)
137175
if self._toplevel:
138176
self._on_leave(None)
139177
self.show()
@@ -143,5 +181,5 @@ def config(self, **kwargs):
143181

144182
def keys(self):
145183
keys = ttk.Frame.keys(self)
146-
keys.extend(["headertext", "text", "width", "timeout", "background"])
184+
keys.extend(["headertext", "text", "width", "timeout", "background", "offset", "showheader", "static"])
147185
return keys

ttkwidgets/utilities.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# Copyright (c) RedFantom 2017
1+
"""
2+
Author: The ttkwidgets authors
3+
License: GNU GPLv3
4+
Source: The ttkwidgets repository
5+
"""
26
import os
37
from PIL import Image, ImageTk
48

@@ -9,3 +13,12 @@ def get_assets_directory():
913

1014
def open_icon(icon_name):
1115
return ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), icon_name)))
16+
17+
18+
def parse_geometry_string(string):
19+
"""Parse a Tkinter geometry string ('XxY+W+H') into a box tuple"""
20+
e = string.split("x")
21+
w = int(e[0])
22+
e = e[1].split("+")
23+
h, x, y = map(int, e)
24+
return x, y, w, h

0 commit comments

Comments
 (0)