Source code for mytk.utils
import re
from threading import current_thread, main_thread
[docs]
def is_main_thread() -> bool:
"""Check whether the current thread is the main thread."""
return current_thread() == main_thread()
[docs]
def parse_geometry(geometry):
"""Parse a Tkinter geometry string into its size and offset components.
Args:
geometry (str | None): A geometry string such as "800x600",
"+100+200", "800x600+100+200", or None.
Returns:
tuple: (size_str, offset_str) where either element may be None.
Examples:
"800x600+100+200" -> ("800x600", "+100+200")
"800x600" -> ("800x600", None)
"+100+200" -> (None, "+100+200")
None -> (None, None)
"""
if not geometry:
return None, None
m = re.fullmatch(r"=?(\d+x\d+)?([+-]\d+[+-]\d+)?", geometry)
if m:
return m.group(1) or None, m.group(2) or None
return None, None
[docs]
def apply_window_position(widget, position, size_str=None):
"""Set only the +X+Y offset of a Tk or Toplevel window.
The window size is never modified. When *size_str* is provided the
offset is computed and applied immediately; otherwise the call is
deferred to the first idle tick (after the event loop has laid out all
content) and the window is briefly withdrawn to avoid a visible jump.
Args:
widget: A tk.Tk or tk.Toplevel instance.
position (str): One of "center", "top-left", "top-right",
"bottom-left", "bottom-right".
size_str (str | None): The "WxH" portion of a geometry string
(e.g. "800x600"). When None the size is measured from the
widget after layout.
Raises:
ValueError: If *position* is not a recognised name.
"""
valid = {"center", "top-left", "top-right", "bottom-left", "bottom-right"}
if position not in valid:
raise ValueError(f"Unknown position '{position}'. Choose from: {sorted(valid)}")
def _apply(w, h):
screen_w = widget.winfo_screenwidth()
screen_h = widget.winfo_screenheight()
offsets = {
"center": ((screen_w - w) // 2, (screen_h - h) // 2),
"top-left": (0, 0),
"top-right": (screen_w - w, 0),
"bottom-left": (0, screen_h - h),
"bottom-right": (screen_w - w, screen_h - h),
}
x, y = offsets[position]
widget.geometry(f"+{x}+{y}") # position only — never overrides size
if size_str:
w, h = map(int, size_str.split("x"))
widget.update_idletasks()
_apply(w, h)
else:
# Size not yet known; defer until after layout.
# Withdraw to avoid a visible jump to the default position.
originally_withdrawn = widget.state() == "withdrawn"
widget.withdraw()
def _deferred():
try:
if not widget.winfo_exists():
return
widget.update_idletasks()
w = widget.winfo_width()
h = widget.winfo_height()
if w <= 1: # not yet rendered, fall back to requested size
w = widget.winfo_reqwidth()
h = widget.winfo_reqheight()
_apply(w, h)
if not originally_withdrawn:
widget.deiconify()
except Exception:
return
widget.nametowidget('.').after_idle(_deferred)
[docs]
def package_app_script(filepath=None):
"""Bundle the utility module and an optional script into a single self-contained string."""
from inspect import currentframe, getframeinfo
frameinfo = getframeinfo(currentframe())
script = ""
with open(__file__, "r") as file:
lines = file.readlines()
embeddable_lines = lines[: frameinfo.lineno - 3]
script += "".join(embeddable_lines)
if filepath is not None:
with open(filepath, "r") as file:
lines = file.readlines()
embeddable_lines = [
line
for line in lines
if "from mytk import *" not in line and "app_script" not in line
]
script += "".join(embeddable_lines)
try:
import pyperclip
pyperclip.copy(script)
except ImportError:
pass