From 2fcdad42621181475c9242aaf4e48d0ed0aad692 Mon Sep 17 00:00:00 2001 From: RedFantom Date: Tue, 26 Nov 2019 20:25:07 +0100 Subject: [PATCH 1/4] Improve Balloon widget for #39 --- tests/test_balloon.py | 35 +++++++++++++++++++--- ttkwidgets/frames/balloon.py | 57 ++++++++++++++++++++++++++++-------- ttkwidgets/utilities.py | 15 +++++++++- 3 files changed, 90 insertions(+), 17 deletions(-) diff --git a/tests/test_balloon.py b/tests/test_balloon.py index ff698243..7033a952 100644 --- a/tests/test_balloon.py +++ b/tests/test_balloon.py @@ -1,6 +1,10 @@ -# Copyright (c) RedFantom 2017 -# For license see LICENSE +""" +Author: RedFantom +License: GNU GPLv3 +Source: This repository +""" from ttkwidgets.frames import Balloon +from ttkwidgets.utilities import parse_geometry_string from tests import BaseWidgetTest import tkinter as tk from time import sleep @@ -13,7 +17,7 @@ def test_balloon_init(self): def test_balloon_kwargs(self): balloon = Balloon(self.window, headertext="Help", text="This is a test for the Balloon widget.", width=300, - timeout=2, background="white") + timeout=2, background="white", showheader=True, offset=(20, 20), static=True) self.assertEqual(balloon.cget("headertext"), "Help") self.assertEqual(balloon.cget("text"), "This is a test for the Balloon widget.") self.assertEqual(balloon.cget("width"), 300) @@ -27,6 +31,9 @@ def test_balloon_kwargs(self): self.assertEqual(balloon["width"], 400) self.assertEqual(balloon["timeout"], 3) self.assertEqual(balloon["background"], "black") + self.assertEqual(balloon["showheader"], True) + self.assertEqual(balloon["offset"], (20, 20)) + self.assertEqual(balloon["static"], True) # Keys for the Frame widget balloon.configure(height=40) @@ -38,6 +45,27 @@ def test_balloon_kwargs(self): for key in ["headertext", "text", "width", "timeout", "background"]: self.assertIn(key, balloon.keys()) + balloon.config(showheader=False) + balloon.show() + self.assertFalse(balloon.header_label.winfo_viewable() == 1) + + balloon.config(offset=(0, 0)) + balloon.show() + self.window.update() + x1, y1, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) + balloon.config(offset=(20, 20)) + balloon._on_leave(None) + balloon.show() + self.window.update() + x2, y2, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) + self.assertTrue(x2 - x1 == 20 and y2 - y1 == 20) + + balloon.config(static=False) + balloon.show() + self.window.update() + x3, y3, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) + self.assertFalse(x2 == x3 or y2 == y3) + def test_balloon_show(self): balloon = Balloon(self.window) self.window.update() @@ -64,4 +92,3 @@ def test_balloon_events_noshow(self): balloon = Balloon(self.window) balloon._on_enter(None) balloon._on_leave(None) - diff --git a/ttkwidgets/frames/balloon.py b/ttkwidgets/frames/balloon.py index e8093297..972b2ede 100644 --- a/ttkwidgets/frames/balloon.py +++ b/ttkwidgets/frames/balloon.py @@ -14,9 +14,9 @@ class Balloon(ttk.Frame): """Simple help hover balloon.""" def __init__(self, master=None, headertext="Help", text="Some great help is displayed here.", width=200, timeout=1, - background="#fef9cd", **kwargs): + background="#fef9cd", offset=(2, 2), showheader=True, static=False, **kwargs): """ - Create a Balloon. + Create a Balloon :param master: widget to bind the Balloon to :type master: widget @@ -30,6 +30,15 @@ def __init__(self, master=None, headertext="Help", text="Some great help is disp :type timeout: float :param background: background color of the Balloon :type background: str + :param offset: The offset from the mouse position the Ballon shows up + :type offset: Tuple[int, int] + :param showheader: Whether to display the header with image + :type showheader: bool + :param static: Whether to display the tooltip with static + position. When the position is set to static, the balloon + will always appear an offset from the bottom right corner of + the widget. + :type static: bool :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer """ ttk.Frame.__init__(self, master, **kwargs) @@ -47,11 +56,16 @@ def __init__(self, master=None, headertext="Help", text="Some great help is disp self.__headertext = headertext self.__text = text self.__width = width + self.__offset = offset + self.__showheader = showheader + self.__static = static + self.master = master self._id = None self._timeout = timeout self.master.bind("", self._on_enter) self.master.bind("", self._on_leave) + self.master.bind("", self._on_leave) def __getitem__(self, key): return self.cget(key) @@ -62,7 +76,8 @@ def __setitem__(self, key, value): def _grid_widgets(self): """Place the widgets in the Toplevel.""" self._canvas.grid(sticky="nswe") - self.header_label.grid(row=1, column=1, sticky="nswe", pady=5, padx=5) + if self.__showheader is True: + self.header_label.grid(row=1, column=1, sticky="nswe", pady=5, padx=5) self.text_label.grid(row=3, column=1, sticky="nswe", pady=6, padx=5) def _on_enter(self, event): @@ -80,9 +95,10 @@ def _on_leave(self, event): def show(self): """ - Create the Toplevel widget and its child widgets to show in the spot of the cursor. + Create the Toplevel and its children to show near the cursor - This is the callback for the delayed :obj:`` event (see :meth:`~Balloon._on_enter`). + This is the callback for the delayed :obj:`` event + (see :meth:`~Balloon._on_enter`). """ self._toplevel = tk.Toplevel(self.master) self._canvas = tk.Canvas(self._toplevel, background=self.__background) @@ -93,11 +109,17 @@ def show(self): self._toplevel.attributes("-topmost", True) self._toplevel.overrideredirect(True) self._grid_widgets() - x, y = self.master.winfo_pointerxy() + if self.__static is True: + x, y = self.master.winfo_rootx(), self.master.winfo_rooty() + w, h = self.master.winfo_width(), self.master.winfo_height() + x, y = x + w, y + h + else: + x, y = self.master.winfo_pointerxy() self._canvas.update() # Update the Geometry of the Toplevel to update its position and size - self._toplevel.geometry("{0}x{1}+{2}+{3}".format(self._canvas.winfo_width(), self._canvas.winfo_height(), - x + 2, y + 2)) + self._toplevel.geometry("{0}x{1}+{2}+{3}".format( + self._canvas.winfo_width(), self._canvas.winfo_height(), + x + self.__offset[0], y + self.__offset[1])) def cget(self, key): """ @@ -107,7 +129,8 @@ def cget(self, key): :type key: str :return: value of the option - To get the list of options for this widget, call the method :meth:`~Balloon.keys`. + To get the list of options for this widget, call the method + :meth:`~Balloon.keys`. """ if key == "headertext": return self.__headertext @@ -119,6 +142,12 @@ def cget(self, key): return self._timeout elif key == "background": return self.__background + elif key == "offset": + return self.__offset + elif key == "showheader": + return self.__showheader + elif key == "static": + return self.__static else: return ttk.Frame.cget(self, key) @@ -126,14 +155,18 @@ def config(self, **kwargs): """ Configure resources of the widget. - To get the list of options for this widget, call the method :meth:`~Balloon.keys`. - See :meth:`~Balloon.__init__` for a description of the widget specific option. + To get the list of options for this widget, call the method + :meth:`~Balloon.keys`. See :meth:`~Balloon.__init__` for a + description of the widget specific option. """ self.__headertext = kwargs.pop("headertext", self.__headertext) self.__text = kwargs.pop("text", self.__text) self.__width = kwargs.pop("width", self.__width) self._timeout = kwargs.pop("timeout", self._timeout) self.__background = kwargs.pop("background", self.__background) + self.__offset = kwargs.pop("offset", self.__offset) + self.__showheader = kwargs.pop("showheader", self.__showheader) + self.__static = kwargs.pop("static", self.__static) if self._toplevel: self._on_leave(None) self.show() @@ -143,5 +176,5 @@ def config(self, **kwargs): def keys(self): keys = ttk.Frame.keys(self) - keys.extend(["headertext", "text", "width", "timeout", "background"]) + keys.extend(["headertext", "text", "width", "timeout", "background", "offset", "showheader", "static"]) return keys diff --git a/ttkwidgets/utilities.py b/ttkwidgets/utilities.py index ad32dbe3..22bf5134 100644 --- a/ttkwidgets/utilities.py +++ b/ttkwidgets/utilities.py @@ -1,4 +1,8 @@ -# Copyright (c) RedFantom 2017 +""" +Author: The ttkwidgets authors +License: GNU GPLv3 +Source: The ttkwidgets repository +""" import os from PIL import Image, ImageTk @@ -9,3 +13,12 @@ def get_assets_directory(): def open_icon(icon_name): return ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), icon_name))) + + +def parse_geometry_string(string): + """Parse a Tkinter geometry string ('XxY+W+H') into a box tuple""" + e = string.split("x") + w = int(e[0]) + e = e[1].split("+") + h, x, y = map(int, e) + return x, y, w, h From 56ef26748424135785ff843e10f0a92728c6448b Mon Sep 17 00:00:00 2001 From: Dogeek Date: Fri, 29 Nov 2019 03:11:49 +0100 Subject: [PATCH 2/4] Rename Balloon widget to Tooltip --- AUTHORS.md | 2 +- docs/source/authors.rst | 2 +- docs/source/ttkwidgets/ttkwidgets.frames.rst | 2 +- .../ttkwidgets.frames.Balloon.rst | 4 ++-- ...{example_balloon.py => example_tooltip.py} | 5 ++--- tests/test_balloon.py | 20 +++++++++---------- ttkwidgets/frames/__init__.py | 2 +- ttkwidgets/frames/{balloon.py => tooltip.py} | 16 +++++++-------- 8 files changed, 26 insertions(+), 27 deletions(-) rename examples/{example_balloon.py => example_tooltip.py} (71%) rename ttkwidgets/frames/{balloon.py => tooltip.py} (94%) diff --git a/AUTHORS.md b/AUTHORS.md index f7d74299..5e60a2d5 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -8,7 +8,7 @@ This file contains a list of all the authors of widgets in this repository. Plea * `ScrolledListbox` * `FontChooser`, based on an idea by [Nelson Brochado](https://www.github.com/nbro) * `FontSelectFrame` - * `Balloon` + * `Tooltip` * `ItemsCanvas` * `TimeLine` - The Python Team diff --git a/docs/source/authors.rst b/docs/source/authors.rst index 3e53e915..49f8755d 100644 --- a/docs/source/authors.rst +++ b/docs/source/authors.rst @@ -11,7 +11,7 @@ List of all the authors of widgets in this repository. Please note that this lis * :class:`~ttkwidgets.ScrolledListbox` * :class:`~ttkwidgets.font.FontChooser`, based on an idea by `Nelson Brochado `_ * :class:`~ttkwidgets.font.FontSelectFrame` - * :class:`~ttkwidgets.frames.Balloon` + * :class:`~ttkwidgets.frames.Tooltip` * :class:`~ttkwidgets.ItemsCanvas` * :class:`~ttkwidgets.TimeLine` diff --git a/docs/source/ttkwidgets/ttkwidgets.frames.rst b/docs/source/ttkwidgets/ttkwidgets.frames.rst index af677973..27204c05 100644 --- a/docs/source/ttkwidgets/ttkwidgets.frames.rst +++ b/docs/source/ttkwidgets/ttkwidgets.frames.rst @@ -11,6 +11,6 @@ ttkwidgets.frames :nosignatures: :toctree: ttkwidgets.frames - Balloon + Tooltip ScrolledFrame ToggledFrame diff --git a/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst b/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst index 0987eb18..8a7a06fe 100644 --- a/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst +++ b/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst @@ -1,9 +1,9 @@ -Balloon +Tooltip ======= .. currentmodule:: ttkwidgets.frames -.. autoclass:: Balloon +.. autoclass:: Tooltip :show-inheritance: :members: diff --git a/examples/example_balloon.py b/examples/example_tooltip.py similarity index 71% rename from examples/example_balloon.py rename to examples/example_tooltip.py index ba6e0602..4f60c2d7 100644 --- a/examples/example_balloon.py +++ b/examples/example_tooltip.py @@ -2,13 +2,12 @@ # Copyright (c) RedFantom 2017 # For license see LICENSE -from ttkwidgets.frames import Balloon +from ttkwidgets.frames import Tooltip import tkinter as tk -from tkinter import ttk window = tk.Tk() button = tk.Button(window, text="Button", command=window.destroy) button.pack() -balloon = Balloon(button) +balloon = Tooltip(button) window.mainloop() diff --git a/tests/test_balloon.py b/tests/test_balloon.py index 7033a952..82bfe412 100644 --- a/tests/test_balloon.py +++ b/tests/test_balloon.py @@ -3,31 +3,31 @@ License: GNU GPLv3 Source: This repository """ -from ttkwidgets.frames import Balloon +from ttkwidgets.frames import Tooltip from ttkwidgets.utilities import parse_geometry_string from tests import BaseWidgetTest import tkinter as tk from time import sleep -class TestBalloon(BaseWidgetTest): +class TestTooltip(BaseWidgetTest): def test_balloon_init(self): - balloon = Balloon(self.window) + balloon = Tooltip(self.window) self.window.update() def test_balloon_kwargs(self): - balloon = Balloon(self.window, headertext="Help", text="This is a test for the Balloon widget.", width=300, + balloon = Tooltip(self.window, headertext="Help", text="This is a test for the Tooltip widget.", width=300, timeout=2, background="white", showheader=True, offset=(20, 20), static=True) self.assertEqual(balloon.cget("headertext"), "Help") - self.assertEqual(balloon.cget("text"), "This is a test for the Balloon widget.") + self.assertEqual(balloon.cget("text"), "This is a test for the Tooltip widget.") self.assertEqual(balloon.cget("width"), 300) self.assertEqual(balloon.cget("timeout"), 2) self.assertEqual(balloon.cget("background"), "white") - balloon.config(headertext="New Help", text="This is another test for the Balloon widget.", width=400, + balloon.config(headertext="New Help", text="This is another test for the Tooltip widget.", width=400, timeout=3, background="black") self.assertEqual(balloon["headertext"], "New Help") - self.assertEqual(balloon["text"], "This is another test for the Balloon widget.") + self.assertEqual(balloon["text"], "This is another test for the Tooltip widget.") self.assertEqual(balloon["width"], 400) self.assertEqual(balloon["timeout"], 3) self.assertEqual(balloon["background"], "black") @@ -67,7 +67,7 @@ def test_balloon_kwargs(self): self.assertFalse(x2 == x3 or y2 == y3) def test_balloon_show(self): - balloon = Balloon(self.window) + balloon = Tooltip(self.window) self.window.update() balloon.show() self.window.update() @@ -79,7 +79,7 @@ def test_balloon_show(self): self.assertIs(balloon._toplevel, None) def test_balloon_events(self): - balloon = Balloon(self.window, timeout=0.2) + balloon = Tooltip(self.window, timeout=0.2) balloon._on_enter(None) self.window.update() sleep(0.3) @@ -89,6 +89,6 @@ def test_balloon_events(self): self.assertIs(balloon._toplevel, None) def test_balloon_events_noshow(self): - balloon = Balloon(self.window) + balloon = Tooltip(self.window) balloon._on_enter(None) balloon._on_leave(None) diff --git a/ttkwidgets/frames/__init__.py b/ttkwidgets/frames/__init__.py index befc0fd6..30e27adf 100644 --- a/ttkwidgets/frames/__init__.py +++ b/ttkwidgets/frames/__init__.py @@ -2,4 +2,4 @@ # Available under the license found in LICENSE from .scrolledframe import ScrolledFrame from .toggledframe import ToggledFrame -from .balloon import Balloon +from .tooltip import Tooltip diff --git a/ttkwidgets/frames/balloon.py b/ttkwidgets/frames/tooltip.py similarity index 94% rename from ttkwidgets/frames/balloon.py rename to ttkwidgets/frames/tooltip.py index 972b2ede..7029b889 100644 --- a/ttkwidgets/frames/balloon.py +++ b/ttkwidgets/frames/tooltip.py @@ -10,15 +10,15 @@ from ttkwidgets.utilities import get_assets_directory -class Balloon(ttk.Frame): +class Tooltip(ttk.Frame): """Simple help hover balloon.""" def __init__(self, master=None, headertext="Help", text="Some great help is displayed here.", width=200, timeout=1, background="#fef9cd", offset=(2, 2), showheader=True, static=False, **kwargs): """ - Create a Balloon + Create a Tooltip - :param master: widget to bind the Balloon to + :param master: widget to bind the Tooltip to :type master: widget :param headertext: text to show in window header :type headertext: str @@ -26,9 +26,9 @@ def __init__(self, master=None, headertext="Help", text="Some great help is disp :type text: str :param width: width of the window :type width: int - :param timeout: timeout in seconds to wait until the Balloon is shown + :param timeout: timeout in seconds to wait until the Tooltip is shown :type timeout: float - :param background: background color of the Balloon + :param background: background color of the Tooltip :type background: str :param offset: The offset from the mouse position the Ballon shows up :type offset: Tuple[int, int] @@ -98,7 +98,7 @@ def show(self): Create the Toplevel and its children to show near the cursor This is the callback for the delayed :obj:`` event - (see :meth:`~Balloon._on_enter`). + (see :meth:`~Tooltip._on_enter`). """ self._toplevel = tk.Toplevel(self.master) self._canvas = tk.Canvas(self._toplevel, background=self.__background) @@ -130,7 +130,7 @@ def cget(self, key): :return: value of the option To get the list of options for this widget, call the method - :meth:`~Balloon.keys`. + :meth:`~Tooltip.keys`. """ if key == "headertext": return self.__headertext @@ -156,7 +156,7 @@ def config(self, **kwargs): Configure resources of the widget. To get the list of options for this widget, call the method - :meth:`~Balloon.keys`. See :meth:`~Balloon.__init__` for a + :meth:`~Tooltip.keys`. See :meth:`~Tooltip.__init__` for a description of the widget specific option. """ self.__headertext = kwargs.pop("headertext", self.__headertext) From b13c9d7edb983ec5ea5eb5c1ed3c10e773454643 Mon Sep 17 00:00:00 2001 From: RedFantom Date: Sat, 30 Nov 2019 11:09:01 +0100 Subject: [PATCH 3/4] Change method of binding to master widget for Balloon --- ttkwidgets/frames/tooltip.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ttkwidgets/frames/tooltip.py b/ttkwidgets/frames/tooltip.py index 7029b889..1e6c62d0 100644 --- a/ttkwidgets/frames/tooltip.py +++ b/ttkwidgets/frames/tooltip.py @@ -63,9 +63,14 @@ def __init__(self, master=None, headertext="Help", text="Some great help is disp self.master = master self._id = None self._timeout = timeout - self.master.bind("", self._on_enter) - self.master.bind("", self._on_leave) - self.master.bind("", self._on_leave) + + self._bind_to_master() + + def _bind_to_master(self): + """Bind the Balloon widget to the master widget's events""" + self.master.bind("", self._on_enter, "add") + self.master.bind("", self._on_leave, "add") + self.master.bind("", self._on_leave, "add") def __getitem__(self, key): return self.cget(key) From de40f71dde4b276717f980c961f4efffa82b02c6 Mon Sep 17 00:00:00 2001 From: RedFantom Date: Thu, 9 Jan 2020 11:44:33 +0100 Subject: [PATCH 4/4] Change Balloon to Tooltip for sphinx documentation --- ...widgets.frames.Balloon.rst => ttkwidgets.frames.Tooltip.rst} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename docs/source/ttkwidgets/ttkwidgets.frames/{ttkwidgets.frames.Balloon.rst => ttkwidgets.frames.Tooltip.rst} (80%) diff --git a/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst b/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Tooltip.rst similarity index 80% rename from docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst rename to docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Tooltip.rst index 8a7a06fe..03618841 100644 --- a/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Balloon.rst +++ b/docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Tooltip.rst @@ -7,4 +7,4 @@ Tooltip :show-inheritance: :members: - .. automethod:: __init__ \ No newline at end of file + .. automethod:: __init__