Added debugger class for dashboard and other launcher util classes.
This commit is contained in:
680
ptlinux/tk_chrome.py
Normal file
680
ptlinux/tk_chrome.py
Normal file
@@ -0,0 +1,680 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Literal
|
||||
|
||||
from .desktop_identity import APPLICATION_WM_CLASS, APPLICATION_WM_INSTANCE
|
||||
|
||||
|
||||
DialogKind = Literal["info", "success", "warning", "error", "question"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DialogPalette:
|
||||
"""Colors and glyph used by one launcher dialog intent."""
|
||||
|
||||
accent: str
|
||||
accent_soft: str
|
||||
glyph: str
|
||||
|
||||
|
||||
_DIALOG_PALETTES: dict[str, DialogPalette] = {
|
||||
"info": DialogPalette("#d98927", "#3a2819", "i"),
|
||||
"success": DialogPalette("#55b878", "#173122", "✓"),
|
||||
"warning": DialogPalette("#e0a248", "#3b2a18", "!"),
|
||||
"error": DialogPalette("#cf604c", "#3a1d19", "×"),
|
||||
"question": DialogPalette("#d98927", "#3a2819", "?"),
|
||||
}
|
||||
|
||||
|
||||
def dialog_palette(kind: DialogKind) -> DialogPalette:
|
||||
"""Return the stable visual palette for a themed launcher dialog."""
|
||||
|
||||
try:
|
||||
return _DIALOG_PALETTES[kind]
|
||||
except KeyError as error:
|
||||
raise ValueError(f"unsupported launcher dialog kind: {kind}") from error
|
||||
|
||||
|
||||
def square_icon_layout(width: int, height: int) -> tuple[int, int, int]:
|
||||
"""Return square canvas size and centered offsets for a window icon."""
|
||||
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError("icon dimensions must be positive")
|
||||
size = max(width, height)
|
||||
return size, (size - width) // 2, (size - height) // 2
|
||||
|
||||
|
||||
def icon_subsample_factor(width: int, height: int, max_dimension: int) -> int:
|
||||
"""Return the integer Tk subsample needed to fit an image within a square."""
|
||||
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError("icon dimensions must be positive")
|
||||
if max_dimension <= 0:
|
||||
raise ValueError("maximum icon dimension must be positive")
|
||||
largest_dimension = max(width, height)
|
||||
return max(1, (largest_dimension + max_dimension - 1) // max_dimension)
|
||||
|
||||
|
||||
def load_square_window_icon(master: Any, path: Path) -> Any:
|
||||
"""Load a PNG into a square transparent Tk image for desktop shells."""
|
||||
|
||||
import tkinter as tk
|
||||
|
||||
source = tk.PhotoImage(master=master, file=str(path))
|
||||
size, offset_x, offset_y = square_icon_layout(source.width(), source.height())
|
||||
if source.width() == source.height():
|
||||
return source
|
||||
icon = tk.PhotoImage(master=master, width=size, height=size)
|
||||
icon.tk.call(
|
||||
str(icon),
|
||||
"copy",
|
||||
str(source),
|
||||
"-to",
|
||||
offset_x,
|
||||
offset_y,
|
||||
)
|
||||
return icon
|
||||
|
||||
|
||||
def centered_window_geometry(
|
||||
width: int,
|
||||
height: int,
|
||||
*,
|
||||
parent_x: int,
|
||||
parent_y: int,
|
||||
parent_width: int,
|
||||
parent_height: int,
|
||||
) -> str:
|
||||
"""Return Tk geometry centered over a parent window."""
|
||||
|
||||
if width <= 0 or height <= 0:
|
||||
raise ValueError("window dimensions must be positive")
|
||||
x = parent_x + (max(1, parent_width) - width) // 2
|
||||
y = parent_y + (max(1, parent_height) - height) // 2
|
||||
return f"{width}x{height}{_geometry_offset(x)}{_geometry_offset(y)}"
|
||||
|
||||
|
||||
def dragged_window_geometry(
|
||||
pointer_x: int,
|
||||
pointer_y: int,
|
||||
*,
|
||||
offset_x: int,
|
||||
offset_y: int,
|
||||
) -> str:
|
||||
"""Return the Tk position for a pointer-driven custom title bar."""
|
||||
|
||||
return (
|
||||
f"{_geometry_offset(pointer_x - offset_x)}"
|
||||
f"{_geometry_offset(pointer_y - offset_y)}"
|
||||
)
|
||||
|
||||
|
||||
def _geometry_offset(value: int) -> str:
|
||||
return f"+{value}" if value >= 0 else str(value)
|
||||
|
||||
|
||||
def _bind_window_drag(handle: Any, *widgets: Any) -> None:
|
||||
position = [0, 0]
|
||||
|
||||
def begin(event: Any) -> None:
|
||||
position[0] = int(event.x_root) - int(handle.winfo_x())
|
||||
position[1] = int(event.y_root) - int(handle.winfo_y())
|
||||
|
||||
def move(event: Any) -> None:
|
||||
handle.geometry(
|
||||
dragged_window_geometry(
|
||||
int(event.x_root),
|
||||
int(event.y_root),
|
||||
offset_x=position[0],
|
||||
offset_y=position[1],
|
||||
)
|
||||
)
|
||||
|
||||
for widget in widgets:
|
||||
widget.bind("<ButtonPress-1>", begin, add="+")
|
||||
widget.bind("<B1-Motion>", move, add="+")
|
||||
|
||||
|
||||
class DashboardChrome:
|
||||
"""Controller for one frameless launcher window and its controls."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root: Any,
|
||||
content: Any,
|
||||
close_button: Any,
|
||||
minimize_button: Any,
|
||||
*,
|
||||
managed: bool,
|
||||
identity_configured: bool,
|
||||
allow_close: bool,
|
||||
) -> None:
|
||||
self.root = root
|
||||
self.content = content
|
||||
self.managed = managed
|
||||
self.identity_configured = identity_configured
|
||||
self._close_button = close_button
|
||||
self._minimize_button = minimize_button
|
||||
self._allow_close = allow_close
|
||||
self._close_command: Callable[[], None] = root.destroy
|
||||
if close_button is not None:
|
||||
close_button.configure(command=self.close)
|
||||
if minimize_button is not None:
|
||||
minimize_button.configure(command=self.minimize)
|
||||
root.bind("<Alt-F4>", self._close_from_event, add="+")
|
||||
|
||||
def set_close_command(self, command: Callable[[], None]) -> None:
|
||||
self._close_command = command
|
||||
|
||||
def close(self) -> None:
|
||||
if self._allow_close:
|
||||
self._close_command()
|
||||
|
||||
def minimize(self) -> None:
|
||||
"""Ask the window manager to iconify the managed dashboard."""
|
||||
|
||||
import tkinter as tk
|
||||
|
||||
try:
|
||||
self.root.iconify()
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def _close_from_event(self, _event: Any) -> str:
|
||||
self.close()
|
||||
return "break"
|
||||
|
||||
|
||||
|
||||
def _configure_x11_window(
|
||||
root: Any,
|
||||
*,
|
||||
resource_name: str,
|
||||
resource_class: str,
|
||||
) -> tuple[bool, bool]:
|
||||
"""Hide the X11 frame and give desktop shells a stable window identity."""
|
||||
|
||||
import ctypes.util
|
||||
|
||||
library_path = ctypes.util.find_library("X11")
|
||||
if library_path is None:
|
||||
return False, False
|
||||
try:
|
||||
x11 = ctypes.CDLL(library_path)
|
||||
except OSError:
|
||||
return False, False
|
||||
|
||||
class XClassHint(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("res_name", ctypes.c_char_p),
|
||||
("res_class", ctypes.c_char_p),
|
||||
]
|
||||
|
||||
x11.XOpenDisplay.argtypes = [ctypes.c_char_p]
|
||||
x11.XOpenDisplay.restype = ctypes.c_void_p
|
||||
x11.XQueryTree.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
ctypes.POINTER(ctypes.c_ulong),
|
||||
ctypes.POINTER(ctypes.POINTER(ctypes.c_ulong)),
|
||||
ctypes.POINTER(ctypes.c_uint),
|
||||
]
|
||||
x11.XQueryTree.restype = ctypes.c_int
|
||||
x11.XInternAtom.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int]
|
||||
x11.XInternAtom.restype = ctypes.c_ulong
|
||||
x11.XChangeProperty.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
ctypes.c_ulong,
|
||||
ctypes.c_ulong,
|
||||
ctypes.c_int,
|
||||
ctypes.c_int,
|
||||
ctypes.POINTER(ctypes.c_ubyte),
|
||||
ctypes.c_int,
|
||||
]
|
||||
x11.XChangeProperty.restype = ctypes.c_int
|
||||
x11.XSetClassHint.argtypes = [
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_ulong,
|
||||
ctypes.POINTER(XClassHint),
|
||||
]
|
||||
x11.XSetClassHint.restype = ctypes.c_int
|
||||
x11.XFlush.argtypes = [ctypes.c_void_p]
|
||||
x11.XFlush.restype = ctypes.c_int
|
||||
x11.XFree.argtypes = [ctypes.c_void_p]
|
||||
x11.XFree.restype = ctypes.c_int
|
||||
x11.XCloseDisplay.argtypes = [ctypes.c_void_p]
|
||||
x11.XCloseDisplay.restype = ctypes.c_int
|
||||
root.update_idletasks()
|
||||
display = x11.XOpenDisplay(None)
|
||||
if display is None:
|
||||
return False, False
|
||||
children = ctypes.POINTER(ctypes.c_ulong)()
|
||||
try:
|
||||
root_window = ctypes.c_ulong()
|
||||
wrapper_window = ctypes.c_ulong()
|
||||
child_count = ctypes.c_uint()
|
||||
queried = x11.XQueryTree(
|
||||
display,
|
||||
root.winfo_id(),
|
||||
ctypes.byref(root_window),
|
||||
ctypes.byref(wrapper_window),
|
||||
ctypes.byref(children),
|
||||
ctypes.byref(child_count),
|
||||
)
|
||||
if not queried or not wrapper_window.value:
|
||||
return False, False
|
||||
atom = x11.XInternAtom(display, b"_MOTIF_WM_HINTS", 0)
|
||||
if not atom:
|
||||
return False, False
|
||||
motif_hints_decorations = 1 << 1
|
||||
hints = (ctypes.c_ulong * 5)(motif_hints_decorations, 0, 0, 0, 0)
|
||||
changed = x11.XChangeProperty(
|
||||
display,
|
||||
wrapper_window.value,
|
||||
atom,
|
||||
atom,
|
||||
32,
|
||||
0,
|
||||
ctypes.cast(hints, ctypes.POINTER(ctypes.c_ubyte)),
|
||||
len(hints),
|
||||
)
|
||||
identity = XClassHint(
|
||||
res_name=resource_name.encode("utf-8"),
|
||||
res_class=resource_class.encode("utf-8"),
|
||||
)
|
||||
identity_changed = x11.XSetClassHint(
|
||||
display,
|
||||
wrapper_window.value,
|
||||
ctypes.byref(identity),
|
||||
)
|
||||
x11.XFlush(display)
|
||||
return bool(changed), bool(identity_changed)
|
||||
finally:
|
||||
if children:
|
||||
x11.XFree(children)
|
||||
x11.XCloseDisplay(display)
|
||||
|
||||
|
||||
def create_window_chrome(
|
||||
root: Any,
|
||||
*,
|
||||
title: str,
|
||||
subtitle: str,
|
||||
show_title_bar: bool = True,
|
||||
show_close: bool = True,
|
||||
show_minimize: bool = False,
|
||||
allow_close: bool = True,
|
||||
) -> DashboardChrome:
|
||||
"""Install polished managed chrome around one launcher-owned Tk window."""
|
||||
|
||||
import tkinter as tk
|
||||
|
||||
managed, identity_configured = _configure_x11_window(
|
||||
root,
|
||||
resource_name=APPLICATION_WM_INSTANCE,
|
||||
resource_class=APPLICATION_WM_CLASS,
|
||||
)
|
||||
if not managed:
|
||||
root.overrideredirect(True)
|
||||
border = tk.Frame(
|
||||
root,
|
||||
background="#6a482d",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
border.pack(fill="both", expand=True)
|
||||
surface = tk.Frame(
|
||||
border,
|
||||
background="#0c0908",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
surface.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
title_bar = tk.Frame(
|
||||
surface,
|
||||
background="#17100d",
|
||||
height=32,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
cursor="fleur",
|
||||
)
|
||||
if show_title_bar:
|
||||
title_bar.pack(fill="x")
|
||||
title_bar.pack_propagate(False)
|
||||
tk_accent = tk.Frame(title_bar, background="#d98927", width=3)
|
||||
tk_accent.pack(side="left", fill="y")
|
||||
mark = tk.Canvas(
|
||||
title_bar,
|
||||
width=22,
|
||||
height=22,
|
||||
background="#17100d",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
cursor="fleur",
|
||||
)
|
||||
mark.create_oval(3, 3, 19, 19, outline="#d98927", width=1)
|
||||
mark.create_text(11, 11, text="PT", fill="#f5d18b", font=("Georgia", 6, "bold"))
|
||||
mark.pack(side="left", padx=(9, 7))
|
||||
identity = tk.Frame(title_bar, background="#17100d", cursor="fleur")
|
||||
identity.pack(side="left", fill="y")
|
||||
title_label = tk.Label(
|
||||
identity,
|
||||
text=title,
|
||||
background="#17100d",
|
||||
foreground="#f2dfbd",
|
||||
font=("Sans", 8, "bold"),
|
||||
cursor="fleur",
|
||||
)
|
||||
title_label.pack(side="left", pady=(0, 1))
|
||||
subtitle_label = tk.Label(
|
||||
identity,
|
||||
text=f" · {subtitle}",
|
||||
background="#17100d",
|
||||
foreground="#8f7b69",
|
||||
font=("Sans", 7),
|
||||
cursor="fleur",
|
||||
)
|
||||
subtitle_label.pack(side="left", pady=(0, 1))
|
||||
close_button: Any = None
|
||||
if show_close:
|
||||
close_button = tk.Button(
|
||||
title_bar,
|
||||
text="×",
|
||||
background="#17100d",
|
||||
foreground="#c9b7a4",
|
||||
activebackground="#a63f2f",
|
||||
activeforeground="#ffffff",
|
||||
font=("Sans", 12),
|
||||
width=4,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
cursor="hand2",
|
||||
takefocus=False,
|
||||
)
|
||||
close_button.pack(side="right", fill="y")
|
||||
minimize_button: Any = None
|
||||
if show_minimize:
|
||||
minimize_button = tk.Button(
|
||||
title_bar,
|
||||
text="—",
|
||||
background="#17100d",
|
||||
foreground="#c9b7a4",
|
||||
activebackground="#312219",
|
||||
activeforeground="#ffffff",
|
||||
font=("Sans", 10, "bold"),
|
||||
width=4,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
cursor="hand2",
|
||||
takefocus=False,
|
||||
)
|
||||
minimize_button.pack(side="right", fill="y")
|
||||
content = tk.Frame(
|
||||
surface,
|
||||
background="#0c0908",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
content.pack(fill="both", expand=True)
|
||||
if show_title_bar:
|
||||
_bind_window_drag(root, title_bar, mark, identity, title_label, subtitle_label)
|
||||
return DashboardChrome(
|
||||
root,
|
||||
content,
|
||||
close_button,
|
||||
minimize_button,
|
||||
managed=managed,
|
||||
identity_configured=identity_configured,
|
||||
allow_close=allow_close,
|
||||
)
|
||||
|
||||
|
||||
def create_dashboard_chrome(
|
||||
root: Any,
|
||||
*,
|
||||
title: str,
|
||||
subtitle: str,
|
||||
) -> DashboardChrome:
|
||||
"""Install the draggable frame and full controls used by the dashboard."""
|
||||
|
||||
return create_window_chrome(
|
||||
root,
|
||||
title=title,
|
||||
subtitle=subtitle,
|
||||
show_close=True,
|
||||
show_minimize=True,
|
||||
allow_close=True,
|
||||
)
|
||||
|
||||
|
||||
def show_themed_dialog(
|
||||
parent: Any,
|
||||
*,
|
||||
title: str,
|
||||
message: str,
|
||||
kind: DialogKind = "info",
|
||||
confirm: bool = False,
|
||||
accept_text: str = "OK",
|
||||
cancel_text: str = "CANCEL",
|
||||
window_icon: Any | None = None,
|
||||
) -> bool:
|
||||
"""Show one frameless modal dialog matching the native launcher palette."""
|
||||
|
||||
import tkinter as tk
|
||||
|
||||
palette = dialog_palette(kind)
|
||||
dialog = tk.Toplevel(parent)
|
||||
dialog.withdraw()
|
||||
dialog.title(title)
|
||||
dialog.configure(background=palette.accent)
|
||||
dialog.resizable(False, False)
|
||||
dialog.overrideredirect(True)
|
||||
if window_icon is not None:
|
||||
try:
|
||||
dialog.iconphoto(False, window_icon)
|
||||
except tk.TclError:
|
||||
pass
|
||||
try:
|
||||
dialog.transient(parent)
|
||||
except tk.TclError:
|
||||
pass
|
||||
result = False
|
||||
|
||||
def finish(value: bool) -> None:
|
||||
nonlocal result
|
||||
result = value
|
||||
try:
|
||||
dialog.grab_release()
|
||||
except tk.TclError:
|
||||
pass
|
||||
dialog.destroy()
|
||||
|
||||
border = tk.Frame(dialog, background=palette.accent)
|
||||
border.pack(fill="both", expand=True)
|
||||
surface = tk.Frame(border, background="#191311")
|
||||
surface.pack(fill="both", expand=True, padx=1, pady=1)
|
||||
header = tk.Frame(surface, background="#24170f", height=66)
|
||||
header.pack(fill="x")
|
||||
header.pack_propagate(False)
|
||||
tk_accent = tk.Frame(header, background=palette.accent, width=5)
|
||||
tk_accent.pack(side="left", fill="y")
|
||||
header_copy = tk.Frame(header, background="#24170f")
|
||||
header_copy.pack(side="left", fill="both", expand=True, padx=16, pady=10)
|
||||
eyebrow = tk.Label(
|
||||
header_copy,
|
||||
text="PRISTONTALE EU · LINUX LAUNCHER",
|
||||
background="#24170f",
|
||||
foreground=palette.accent,
|
||||
font=("Sans", 7, "bold"),
|
||||
anchor="w",
|
||||
)
|
||||
eyebrow.pack(fill="x")
|
||||
title_label = tk.Label(
|
||||
header_copy,
|
||||
text=title,
|
||||
background="#24170f",
|
||||
foreground="#fff0d2",
|
||||
font=("Georgia", 15, "bold"),
|
||||
anchor="w",
|
||||
)
|
||||
title_label.pack(fill="x", pady=(1, 0))
|
||||
close_button = tk.Button(
|
||||
header,
|
||||
text="×",
|
||||
command=lambda: finish(False),
|
||||
background="#24170f",
|
||||
foreground="#c9b7a4",
|
||||
activebackground="#a63f2f",
|
||||
activeforeground="#ffffff",
|
||||
font=("Sans", 12),
|
||||
width=4,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
cursor="hand2",
|
||||
takefocus=False,
|
||||
)
|
||||
close_button.pack(side="right", fill="y")
|
||||
body = tk.Frame(surface, background="#191311", padx=20, pady=20)
|
||||
body.pack(fill="both", expand=True)
|
||||
modal_icon: Any | None = None
|
||||
if window_icon is not None:
|
||||
try:
|
||||
factor = icon_subsample_factor(
|
||||
window_icon.width(),
|
||||
window_icon.height(),
|
||||
46,
|
||||
)
|
||||
modal_icon = window_icon.subsample(factor, factor)
|
||||
except (tk.TclError, ValueError):
|
||||
modal_icon = None
|
||||
if modal_icon is not None:
|
||||
icon_host = tk.Frame(
|
||||
body,
|
||||
width=46,
|
||||
height=46,
|
||||
background="#191311",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
icon_host.pack(side="left", anchor="n", padx=(0, 16))
|
||||
icon_host.pack_propagate(False)
|
||||
icon = tk.Label(
|
||||
icon_host,
|
||||
image=modal_icon,
|
||||
background="#191311",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
icon.place(relx=0.5, rely=0.5, anchor="center")
|
||||
else:
|
||||
icon = tk.Canvas(
|
||||
body,
|
||||
width=46,
|
||||
height=46,
|
||||
background="#191311",
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
)
|
||||
icon.create_oval(
|
||||
3,
|
||||
3,
|
||||
43,
|
||||
43,
|
||||
fill=palette.accent_soft,
|
||||
outline=palette.accent,
|
||||
width=2,
|
||||
)
|
||||
icon.create_text(
|
||||
23,
|
||||
23,
|
||||
text=palette.glyph,
|
||||
fill=palette.accent,
|
||||
font=("Sans", 17, "bold"),
|
||||
)
|
||||
icon.pack(side="left", anchor="n", padx=(0, 16))
|
||||
message_label = tk.Label(
|
||||
body,
|
||||
text=message,
|
||||
background="#191311",
|
||||
foreground="#ded1c4",
|
||||
font=("Sans", 10),
|
||||
justify="left",
|
||||
anchor="w",
|
||||
wraplength=420,
|
||||
)
|
||||
message_label.pack(side="left", fill="both", expand=True, anchor="n")
|
||||
footer = tk.Frame(surface, background="#100d0c", padx=18, pady=12)
|
||||
footer.pack(fill="x")
|
||||
|
||||
def button(
|
||||
container: Any,
|
||||
*,
|
||||
label: str,
|
||||
command: Callable[[], None],
|
||||
primary: bool,
|
||||
) -> Any:
|
||||
background = palette.accent if primary else "#2a201c"
|
||||
foreground = "#17100d" if primary else "#ead8c2"
|
||||
active_background = "#efae57" if primary else "#3a2b25"
|
||||
return tk.Button(
|
||||
container,
|
||||
text=label,
|
||||
command=command,
|
||||
background=background,
|
||||
foreground=foreground,
|
||||
activebackground=active_background,
|
||||
activeforeground="#ffffff" if not primary else "#17100d",
|
||||
font=("Sans", 8, "bold"),
|
||||
padx=16,
|
||||
pady=7,
|
||||
borderwidth=0,
|
||||
highlightthickness=0,
|
||||
relief="flat",
|
||||
cursor="hand2",
|
||||
)
|
||||
|
||||
accept_button = button(
|
||||
footer,
|
||||
label=accept_text,
|
||||
command=lambda: finish(True),
|
||||
primary=True,
|
||||
)
|
||||
accept_button.pack(side="right")
|
||||
if confirm:
|
||||
cancel_button = button(
|
||||
footer,
|
||||
label=cancel_text,
|
||||
command=lambda: finish(False),
|
||||
primary=False,
|
||||
)
|
||||
cancel_button.pack(side="right", padx=(0, 8))
|
||||
_bind_window_drag(dialog, header, header_copy, eyebrow, title_label)
|
||||
dialog.bind("<Escape>", lambda _event: finish(False))
|
||||
dialog.bind("<Return>", lambda _event: finish(True))
|
||||
parent.update_idletasks()
|
||||
dialog.update_idletasks()
|
||||
width = 560
|
||||
height = max(218, dialog.winfo_reqheight())
|
||||
dialog.geometry(
|
||||
centered_window_geometry(
|
||||
width,
|
||||
height,
|
||||
parent_x=parent.winfo_rootx(),
|
||||
parent_y=parent.winfo_rooty(),
|
||||
parent_width=parent.winfo_width(),
|
||||
parent_height=parent.winfo_height(),
|
||||
)
|
||||
)
|
||||
dialog.deiconify()
|
||||
dialog.lift()
|
||||
dialog.grab_set()
|
||||
accept_button.focus_set()
|
||||
parent.wait_window(dialog)
|
||||
return result
|
||||
Reference in New Issue
Block a user