Skip to content

Commit

Permalink
Improve Balloon widget for #39
Browse files Browse the repository at this point in the history
  • Loading branch information
RedFantom committed Nov 26, 2019
1 parent 347f1a9 commit 8cb64e0
Showing 1 changed file with 46 additions and 9 deletions.
55 changes: 46 additions & 9 deletions ttkwidgets/frames/balloon.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
import tkinter as tk
from PIL import Image, ImageTk
import os
from ttkwidgets.utilities import get_assets_directory
from ttkwidgets.utilities import get_assets_directory, parse_geometry_string
from typing import Any, Dict, List, Tuple, Union


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):
# type: (Union[tk.Widget, None], str, str, int, int, str, Tuple[int, int])->None
"""
Create a Balloon.
Create a Balloon
:param master: widget to bind the Balloon to
:type master: widget
Expand All @@ -34,6 +36,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)
Expand All @@ -51,29 +62,39 @@ 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("<Enter>", self._on_enter)
self.master.bind("<Leave>", self._on_leave)
self.master.bind("<ButtonPress>", self._on_leave)

def __getitem__(self, key):
# type: (Any) -> Any
return self.cget(key)

def __setitem__(self, key, value):
self.configure(**{key: value})

def _grid_widgets(self):
# type: () -> None
"""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):
# type: (tk.Event) -> None
"""Creates a delayed callback for the :obj:`<Enter>` event."""
self._id = self.master.after(int(self._timeout * 1000), func=self.show)

def _on_leave(self, event):
# type: (tk.Event) -> None
"""Callback for the :obj:`<Leave>` event to destroy the Toplevel."""
if self._toplevel:
self._toplevel.destroy()
Expand All @@ -83,10 +104,12 @@ def _on_leave(self, event):
self._id = None

def show(self):
# type: () -> None
"""
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:`<Enter>` event (see :meth:`~Balloon._on_enter`).
This is the callback for the delayed :obj:`<Enter>` event
(see :meth:`~Balloon._on_enter`).
"""
self._toplevel = tk.Toplevel(self.master)
self._canvas = tk.Canvas(self._toplevel, background=self.__background)
Expand All @@ -97,13 +120,18 @@ 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()
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):
# type: (str) -> Any
"""
Query widget option.
Expand All @@ -123,10 +151,15 @@ 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
else:
return ttk.Frame.cget(self, key)

def config(self, **kwargs):
# type: (Dict[str, Any]) -> None
"""
Configure resources of the widget.
Expand All @@ -138,6 +171,9 @@ def config(self, **kwargs):
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()
Expand All @@ -146,6 +182,7 @@ def config(self, **kwargs):
configure = config

def keys(self):
# type: () -> List[str]
keys = ttk.Frame.keys(self)
keys.extend(["headertext", "text", "width", "timeout", "background"])
return keys

0 comments on commit 8cb64e0

Please sign in to comment.