DBC_Converter/venv/Lib/site-packages/ttkwidgets/hook.py
2025-01-03 23:49:59 +09:00

158 lines
6.1 KiB
Python

"""
Author: RedFantom
License: GNU GPLv3
Source: The ttkwidgets repository
This file provides a set of functions that can be used for adding
options to all classes that inherit from ``ttk.Widget``, so `ttk.Button``,
for example, but also every widget contained in this package.
When an option is changed, an updater function is called that the
developer creating the hook has to provide. This updater is called
after the widget has initialized if the option is set upon
initialization of the widget.
Default values may be specified as well. For more details, see
:meth:`hook_ttk_widgets` for more details. See :meth:`tooltip_updater`
for a practical implementation of a hook.
"""
try:
import Tkinter as tk
import ttk
except ImportError:
import tkinter as tk
from tkinter import ttk
def is_hooked(options):
# type: (dict) -> bool
"""Return whether ``ttk.Widget`` is hooked for any of the given options"""
for hookname in [hook for hook in dir(ttk.Widget) if hook.startswith("WidgetHook_")]:
hookoptions = getattr(ttk.Widget, hookname).defaults
if any(option in hookoptions for option in options):
return True
return hasattr(ttk.Widget, generate_hook_name(options))
def generate_hook_name(options):
# type: (dict) -> str
"""Generate a unique name for a hook given a set of options"""
return "WidgetHook_" + "_".join(sorted(options))
def hook_ttk_widgets(updater, options):
# type: (callable, dict) -> str
"""
Create a hook in either tk.Widget or ttk.Widget to support options
This function works by overriding the ``__init__``, ``configure``,
``config``, ``cget`` and ``keys`` functions of the ``ttk.Widget``
class. The original functions are stored safely inside a class
object created upon the ``ttk.Widget`` class, so that they can
still be executed when necessary.
Multiple hooks are allowed at the same time and a custom hook
overwriting any of the functions (as long as it is done properly)
does not cause any issues.
The order in which hooks are executed is not guaranteed as a stable
library feature.
:param updater: Function to call when an option in the given options
is changed. The function should support updating for all the
options given in the hook.
:type updater: (widget: ttk.Widget, option: str, value: Any) -> None
:param options: A dictionary of options where the keys are the
keyword argument names and the values are their respective
default values. A default value must be specified for every
option. All option names must be allowed in valid Python syntax.
:type options: Dict[str, Any]
:return: Name of the attribute created to store values
:rtype: str
"""
NULL = object()
assert len(options) > 0
# Create a unique name so that multiple hooks do not interfere
name = generate_hook_name(options)
# Check to see if the hook already exists
if hasattr(ttk.Widget, name): # Hook already exists, will be updated
if updater is not None:
raise RuntimeError("Invalid parameter: Updater may not be changed after hook creation")
getattr(ttk.Widget, name).defaults = options.copy()
return name
elif is_hooked(options):
raise RuntimeError("Invalid options: Cannot replace full hook with partial hook")
# Create a class with the original functions
class OriginalFunctions(object):
original_init = ttk.Widget.__init__
original_config = ttk.Widget.config
original_configure = ttk.Widget.configure
original_cget = ttk.Widget.cget
original_keys = ttk.Widget.keys
defaults = options
# Move the OriginalFunctions class to the target class
setattr(ttk.Widget, name, OriginalFunctions)
def setter(self, option, value):
"""Store an option on the embedded object and then call updater"""
current = getter(self, option)
if current != value:
setattr(getattr(self, name.lower()), option, value)
updater(self, option, value)
def getter(self, option):
"""Retrieve an option value from the embedded object"""
return getattr(getattr(self, name.lower()), option, NULL)
def __init__(self, *args):
"""Catch initialization and pop all the custom options"""
master, widget, widget_options = args
# Pop all the options, taking default values first
values = getattr(self, name).defaults.copy()
for (option, default) in options.items():
value = widget_options.pop(option, default)
values[option] = value
# Perform initialization of the widget
getattr(self, name).original_init(self, master, widget, widget_options)
# Create an instance object to store options on
setattr(self, name.lower(), OriginalFunctions())
# Set all the options only after widget init is complete
for option, value in values.items(): # updater only called after init is done
setter(self, option, value)
def configure(self, *args, **kwargs):
"""Catch configure to pop custom options and configure them"""
for widget_options in args + (kwargs,): # Loop over all sets of options available
if widget_options is None:
continue
for option, _ in options.items():
current = getter(self, option)
value = widget_options.pop(option, current)
setter(self, option, value)
return getattr(self, name).original_configure(self, *args, **kwargs)
def cget(self, key):
"""Return the value of a custom option if key is a custom option"""
if key in options:
return getter(self, key)
return getattr(self, name).original_cget(self, key)
def keys(self):
"""Return an updated list of keys with the custom options"""
keys = getattr(self, name).original_keys(self)
keys.extend(options.keys())
return keys
ttk.Widget.__init__ = __init__
ttk.Widget.configure = configure
ttk.Widget.config = configure
ttk.Widget.cget = cget
ttk.Widget.__getitem__ = cget
ttk.Widget.keys = keys
return name