New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

tkinter-tooltip

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tkinter-tooltip - pypi Package Compare versions

Comparing version
2.2.0
to
3.0.0
+8
.vscode/settings.json
{
"python.analysis.typeCheckingMode": "basic",
"python.testing.pytestArgs": [
"test"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
import time
import tkinter as tk
import tkinter.ttk as ttk
from itertools import product
from tkinter import font as tkFont
from tktooltip import ToolTip
def custom_font(frame, **kwargs):
return tkFont.Font(frame, **kwargs)
def main():
root = tk.Tk()
s = ttk.Style()
s.configure("custom.TButton", foreground="#208020", background="#fafafa")
# root.tk.call("source", "themes/sun-valley/sun-valley.tcl")
# root.tk.call("set_theme", "dark")
btn_list = []
for i, j in list(product(range(2), range(4))):
text = f"delay={i}s\n"
delay = i
if j >= 2:
follow = True
text += "follow tooltip: \u2714\n" # yes
else:
follow = False
text += "follow tooltip: \u274C\n" # no
if j % 2 == 0:
msg = time.asctime
text += "tooltip: Function"
else:
msg = f"Button at {str((i, j))}"
text += "tooltip: String"
btn_list.append(ttk.Button(root, text=text, style="custom.TButton"))
ToolTip(
btn_list[-1],
msg=msg,
follow=follow,
delay=delay,
# Parent frame arguments
parent_kwargs={"bg": "black", "padx": 5, "pady": 5},
# These are arguments to the tk.Message
fg="#202020",
bg="#fafafa",
padx=10,
pady=10,
font=custom_font(root, size=8, weight=tkFont.BOLD),
)
btn_list[-1].grid(row=i, column=j, sticky="nsew", ipadx=20, ipady=20)
root.mainloop()
if __name__ == "__main__":
main()
import tkinter as tk
from tktooltip import ToolTip # Assuming tooltip.py is in the same directory
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.iter = 0
# Create a label with a tooltip
self.label = tk.Label(self, text="Hover over me!")
self.label.pack(padx=10, pady=10)
self.tooltip = ToolTip(self.label, msg=f"Hello {self.iter}")
# Create a button that destroys the tooltip
self.destroy_b = tk.Button(
self, text="Destroy Tooltip", command=self.destroy_tooltip
)
self.destroy_b.pack(padx=10, pady=10)
self.create_b = tk.Button(
self, text="Create Tooltip", command=self.create_tooltip
)
self.create_b.pack(padx=10, pady=10)
def destroy_tooltip(self):
# Destroy the tooltip
if self.tooltip is not None:
self.tooltip.destroy()
self.tooltip = None
def create_tooltip(self):
# Create the tooltip
print(type(self.tooltip))
print(self.tooltip)
print("===========")
for c in list(self.children.values()):
print(c)
print("***********")
if self.tooltip is None:
self.iter += 1
self.tooltip = ToolTip(self.label, msg=f"Hello {self.iter}")
if __name__ == "__main__":
app = Application()
app.mainloop()
+3
-3

@@ -45,3 +45,3 @@ # For most projects, this workflow file will not need changing; you simply need

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:

@@ -57,3 +57,3 @@ languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

@@ -72,2 +72,2 @@ # ℹ️ Command-line programs to run using the OS shell.

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3

@@ -13,3 +13,3 @@ # See https://pre-commit.com for more information

- repo: https://github.com/PyCQA/flake8
rev: 6.1.0
rev: 7.0.0
hooks:

@@ -22,3 +22,3 @@ - id: flake8

- repo: https://github.com/pycqa/isort
rev: 5.13.0
rev: 5.13.2
hooks:

@@ -28,4 +28,4 @@ - id: isort

- repo: https://github.com/psf/black
rev: 23.11.0
rev: 24.1.1
hooks:
- id: black
import re
import tkinter as tk
from pathlib import Path
from tkinter import ttk

@@ -23,3 +24,3 @@

self.color = "0,0,0" # Default color to replace (it's black)
img = "tooltip_logo.png" # image name to loads
img = Path(__file__).parent / "tooltip_logo.png" # image name to loads
self.img = Image.open(img).resize((100, 100)) # Load and scale image

@@ -26,0 +27,0 @@ self.img_tk = ImageTk.PhotoImage(self.img) # Store to stop garbage collection

Metadata-Version: 2.1
Name: tkinter-tooltip
Version: 2.2.0
Version: 3.0.0
Summary: An easy and customisable ToolTip implementation for Tkinter

@@ -63,2 +63,3 @@ Home-page: https://github.com/gnikit/tkinter-tooltip

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
<!-- [![CodeFactor](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip/badge)](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip) -->

@@ -207,2 +208,9 @@

## Notes
- Certain options do not match great with each other, a good example is `follow`
and `delay` using small x/y offsets. This can cause the tooltip to appear
inside the widget. Hovering over the tooltip will cause it to disappear and
reappear, in a new position, potentially again inside the widget.
## Contributing

@@ -209,0 +217,0 @@

@@ -10,2 +10,3 @@ <div align="center">

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
<!-- [![CodeFactor](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip/badge)](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip) -->

@@ -154,2 +155,9 @@

## Notes
- Certain options do not match great with each other, a good example is `follow`
and `delay` using small x/y offsets. This can cause the tooltip to appear
inside the widget. Hovering over the tooltip will cause it to disappear and
reappear, in a new position, potentially again inside the widget.
## Contributing

@@ -156,0 +164,0 @@

@@ -6,3 +6,3 @@ import tkinter as tk

from tktooltip import ToolTip
from tktooltip import ToolTip, ToolTipStatus

@@ -67,2 +67,3 @@

(lambda: "Callable Tooltip"),
(lambda: ["Callable Tooltip 1", "Callable Tooltip 2"]),
"text",

@@ -77,8 +78,19 @@ (["text 1", "text 2"]),

tooltip = ToolTip(widget, msg=msg)
assert tooltip.status == "outside"
assert tooltip.status == ToolTipStatus.OUTSIDE
widget.event_generate("<Enter>")
assert tooltip.status == "inside"
assert tooltip.status == ToolTipStatus.INSIDE
tooltip._show()
assert tooltip.status == "visible"
assert tooltip.status == ToolTipStatus.VISIBLE
widget.event_generate("<Leave>")
assert tooltip.status == "outside"
assert tooltip.status == ToolTipStatus.OUTSIDE
def test_tooltip_destroy(widget: tk.Widget):
tooltip = ToolTip(widget, msg="Test")
widget.event_generate("<Enter>")
assert tooltip.status == ToolTipStatus.INSIDE
tooltip._show()
assert tooltip.status == ToolTipStatus.VISIBLE
tooltip.destroy()
print(tooltip.bindigs)
assert tooltip.bindigs == []
Metadata-Version: 2.1
Name: tkinter-tooltip
Version: 2.2.0
Version: 3.0.0
Summary: An easy and customisable ToolTip implementation for Tkinter

@@ -63,2 +63,3 @@ Home-page: https://github.com/gnikit/tkinter-tooltip

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
<!-- [![CodeFactor](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip/badge)](https://www.codefactor.io/repository/github/gnikit/tkinter-tooltip) -->

@@ -207,2 +208,9 @@

## Notes
- Certain options do not match great with each other, a good example is `follow`
and `delay` using small x/y offsets. This can cause the tooltip to appear
inside the widget. Hovering over the tooltip will cause it to disappear and
reappear, in a new position, potentially again inside the widget.
## Contributing

@@ -209,0 +217,0 @@

@@ -17,2 +17,3 @@ .coveragerc

.github/workflows/python-publish.yml
.vscode/settings.json
assets/animations/color-changer.gif

@@ -41,2 +42,4 @@ assets/animations/color-changer.mp4

examples/color_changer.py
examples/matrix.py
examples/tooltip_destroy.py
examples/tooltip_logo.png

@@ -51,4 +54,3 @@ test/test_tktooltip.py

tktooltip/_version.py
tktooltip/demo.py
tktooltip/tooltip.py
tktooltip/version.py

@@ -16,3 +16,3 @@ """

from .tooltip import ToolTip
from .tooltip import ToolTip, ToolTipStatus
from .version import __version__

@@ -22,3 +22,4 @@

"ToolTip",
"ToolTipStatus",
"__version__",
]

@@ -15,3 +15,3 @@ # file generated by setuptools_scm

__version__ = version = '2.2.0'
__version_tuple__ = version_tuple = (2, 2, 0)
__version__ = version = '3.0.0'
__version_tuple__ = version_tuple = (3, 0, 0)
"""
Module defining the ToolTip widget
"""
from __future__ import annotations

@@ -8,2 +9,4 @@

import tkinter as tk
from contextlib import suppress
from enum import Enum, auto
from typing import Any, Callable

@@ -15,2 +18,18 @@

class ToolTipStatus(Enum):
OUTSIDE = auto()
INSIDE = auto()
VISIBLE = auto()
class Binding:
def __init__(self, widget: tk.Widget, binding_name: str, functor: Callable) -> None:
self._widget = widget
self._name: str = binding_name
self._id: str = self._widget.bind(binding_name, functor, add="+")
def unbind(self) -> None:
self._widget.unbind(self._name, self._id)
class ToolTip(tk.Toplevel):

@@ -21,2 +40,6 @@ """

DEFAULT_PARENT_KWARGS = {"bg": "black", "padx": 1, "pady": 1}
DEFAULT_MESSAGE_KWARGS = {"aspect": 1000}
S_TO_MS = 1000
def __init__(

@@ -31,3 +54,3 @@ self,

y_offset: int = +10,
parent_kwargs: dict[Any, Any] = {"bg": "black", "padx": 1, "pady": 1},
parent_kwargs: dict | None = None,
**message_kwargs: Any,

@@ -65,3 +88,3 @@ ):

# otherwise in the `parent_kwargs`
tk.Toplevel.__init__(self, **parent_kwargs)
tk.Toplevel.__init__(self, **(parent_kwargs or self.DEFAULT_PARENT_KWARGS))
self.withdraw() # Hide initially in case there is a delay

@@ -72,14 +95,5 @@ # Disable ToolTip's title bar

# StringVar instance for msg string|function
self.msgVar = tk.StringVar()
# This can be a string or a function
if not (
callable(msg)
or (isinstance(msg, str))
or (isinstance(msg, list) and all(isinstance(m, str) for m in msg))
):
raise TypeError(
"Error: ToolTip `msg` must be a string, list of strings or string "
+ f"returning function instead `msg` of type {type(msg)} was input"
)
self.msg_var = tk.StringVar()
self.msg = msg
self._update_message()
self.delay = delay

@@ -91,13 +105,36 @@ self.follow = follow

# visibility status of the ToolTip inside|outside|visible
self.status = "outside"
self.status = ToolTipStatus.OUTSIDE
self.last_moved = 0
# use Message widget to host ToolTip
tk.Message(self, textvariable=self.msgVar, aspect=1000, **message_kwargs).grid()
# Add bindings to the widget without overriding the existing ones
self.widget.bind("<Enter>", self.on_enter, add="+")
self.widget.bind("<Leave>", self.on_leave, add="+")
self.widget.bind("<Motion>", self.on_enter, add="+")
self.widget.bind("<ButtonPress>", self.on_leave, add="+")
self.message_kwargs: dict = message_kwargs or self.DEFAULT_MESSAGE_KWARGS
self.message_widget = tk.Message(
self,
textvariable=self.msg_var,
**self.message_kwargs,
)
self.message_widget.grid()
self.bindigs = self._init_bindings()
def on_enter(self, event) -> None:
def _init_bindings(self) -> list[Binding]:
"""Initialize the bindings."""
bindings = [
Binding(self.widget, "<Enter>", self.on_enter),
Binding(self.widget, "<Leave>", self.on_leave),
Binding(self.widget, "<ButtonPress>", self.on_leave),
]
if self.follow:
bindings.append(
Binding(self.widget, "<Motion>", self._update_tooltip_coords)
)
return bindings
def destroy(self) -> None:
"""Destroy the ToolTip and unbind all the bindings."""
with suppress(tk.TclError):
for b in self.bindigs:
b.unbind()
self.bindigs.clear()
super().destroy()
def on_enter(self, event: tk.Event) -> None:
"""

@@ -107,26 +144,36 @@ Processes motion within the widget including entering and moving.

self.last_moved = time.time()
self.status = ToolTipStatus.INSIDE
self._update_tooltip_coords(event)
self.after(int(self.delay * self.S_TO_MS), self._show)
# Set the status as inside for the very first time
if self.status == "outside":
self.status = "inside"
# If the follow flag is not set, motion within the widget will
# make the ToolTip dissapear
if not self.follow:
self.status = "inside"
self.withdraw()
# Offsets the ToolTip using the coordinates od an event as an origin
self.geometry(f"+{event.x_root + self.x_offset}+{event.y_root + self.y_offset}")
# Time is integer and in milliseconds
self.after(int(self.delay * 1000), self._show)
def on_leave(self, event=None) -> None:
def on_leave(self, event: tk.Event | None = None) -> None:
"""
Hides the ToolTip.
"""
self.status = "outside"
self.status = ToolTipStatus.OUTSIDE
self.withdraw()
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}")
def _update_message(self) -> None:
"""Update the message displayed in the tooltip."""
if callable(self.msg):
msg = self.msg()
if isinstance(msg, list):
msg = "\n".join(msg)
elif isinstance(self.msg, str):
msg = self.msg
elif isinstance(self.msg, list):
msg = "\n".join(self.msg)
else:
raise TypeError(
f"ToolTip `msg` must be a string, list of strings, or a "
f"callable returning them, not {type(self.msg)}."
)
self.msg_var.set(msg)
def _show(self) -> None:

@@ -138,15 +185,10 @@ """

"""
if self.status == "inside" and time.time() - self.last_moved > self.delay:
self.status = "visible"
if (
self.status == ToolTipStatus.INSIDE
and time.time() - self.last_moved > self.delay
):
self.status = ToolTipStatus.VISIBLE
if self.status == "visible":
# Update the string with the latest function call
if callable(self.msg):
self.msgVar.set(self.msg())
# Update the string with the latest string
elif isinstance(self.msg, str):
self.msgVar.set(self.msg)
# Update the string with the latest list
elif isinstance(self.msg, list):
self.msgVar.set("\n".join(self.msg))
if self.status == ToolTipStatus.VISIBLE:
self._update_message()
self.deiconify()

@@ -157,2 +199,2 @@

# that in turn changes the `status` to outside
self.after(int(self.refresh * 1000), self._show)
self.after(int(self.refresh * self.S_TO_MS), self._show)
import time
import tkinter as tk
import tkinter.ttk as ttk
from itertools import product
from tkinter import font as tkFont
from tooltip import ToolTip
def custom_font(frame, **kwargs):
return tkFont.Font(frame, **kwargs)
def main():
root = tk.Tk()
s = ttk.Style()
s.configure("custom.TButton", foreground="#208020", background="#fafafa")
# root.tk.call("source", "themes/sun-valley/sun-valley.tcl")
# root.tk.call("set_theme", "dark")
btn_list = []
for i, j in list(product(range(2), range(4))):
text = f"delay={i}s\n"
delay = i
if j >= 2:
follow = True
text += "follow tooltip: \u2714\n" # yes
else:
follow = False
text += "follow tooltip: \u274C\n" # no
if j % 2 == 0:
msg = time.asctime
text += "tooltip: Function"
else:
msg = f"Button at {str((i, j))}"
text += "tooltip: String"
btn_list.append(ttk.Button(root, text=text, style="custom.TButton"))
ToolTip(
btn_list[-1],
msg=msg,
follow=follow,
delay=delay,
# Parent frame arguments
parent_kwargs={"bg": "black", "padx": 5, "pady": 5},
# These are arguments to the tk.Message
fg="#202020",
bg="#fafafa",
padx=10,
pady=10,
font=custom_font(root, size=8, weight=tkFont.BOLD),
)
btn_list[-1].grid(row=i, column=j, sticky="nsew", ipadx=20, ipady=20)
root.mainloop()
if __name__ == "__main__":
main()