558 lines
23 KiB
Python
558 lines
23 KiB
Python
"""Selbst gezeichneter Windows-95-Fensterrahmen.
|
||
|
||
Windows erlaubt Tk nicht, die echte Titelleiste umzufärben. Deshalb wird sie ausgeblendet
|
||
(overrideredirect) und hier nachgebaut: blaue Titelleiste mit Icon und _ □ x, Verschieben,
|
||
Doppelklick = Maximieren, Größenänderung über den Rand und den Anfasser unten rechts,
|
||
graue Menüleiste.
|
||
|
||
Unter Windows ist das Hauptfenster trotzdem ein echtes Programmfenster (siehe nativ.py): Windows
|
||
übernimmt Verschieben, Größe ändern, Andocken am Bildschirmrand, Win+Pfeiltasten, Taskleiste und
|
||
Alt+Tab. Auf anderen Systemen (Entwicklung/Tests) macht Tk das selbst.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
import tkinter as tk
|
||
|
||
from provtool.gui import win95 as w
|
||
|
||
IS_WIN = sys.platform.startswith("win")
|
||
|
||
# --------------------------------------------------------------------------- Glyphen
|
||
|
||
GLYPHEN = {
|
||
"min": """
|
||
........
|
||
........
|
||
........
|
||
........
|
||
........
|
||
........
|
||
kkkkkk..
|
||
kkkkkk..""",
|
||
"max": """
|
||
kkkkkkkkk
|
||
kkkkkkkkk
|
||
k.......k
|
||
k.......k
|
||
k.......k
|
||
k.......k
|
||
k.......k
|
||
kkkkkkkkk""",
|
||
"restore": """
|
||
..kkkkkk
|
||
..kkkkkk
|
||
..k....k
|
||
kkkkkk.k
|
||
kkkkkk.k
|
||
k....kkk
|
||
k....k..
|
||
kkkkkk..""",
|
||
"close": """
|
||
kk....kk
|
||
.kk..kk.
|
||
..kkkk..
|
||
...kk...
|
||
..kkkk..
|
||
.kk..kk.
|
||
kk....kk""",
|
||
}
|
||
|
||
|
||
def glyph(master, name: str) -> tk.PhotoImage:
|
||
rows = [r.strip() for r in GLYPHEN[name].strip("\n").splitlines() if r.strip()]
|
||
breite = max(len(r) for r in rows)
|
||
img = tk.PhotoImage(master=master, width=breite, height=len(rows))
|
||
for y, row in enumerate(rows):
|
||
for x, ch in enumerate(row):
|
||
if ch != ".":
|
||
img.put(w.PALETTE[ch], (x, y))
|
||
return img
|
||
|
||
|
||
def griff_bild(master, groesse: int = 12) -> tk.PhotoImage:
|
||
"""Der typische schraffierte Größen-Anfasser unten rechts."""
|
||
img = tk.PhotoImage(master=master, width=groesse, height=groesse)
|
||
for y in range(groesse):
|
||
for x in range(groesse):
|
||
d = x + y - (groesse - 1)
|
||
if d >= 0:
|
||
farbe = (w.HILITE, w.SHADOW, w.SHADOW, None)[d % 4]
|
||
if farbe:
|
||
img.put(farbe, (x, y))
|
||
return img
|
||
|
||
|
||
# --------------------------------------------------------------------------- Win32-Hilfen
|
||
|
||
def _hwnd(fenster: tk.Misc) -> int:
|
||
import ctypes
|
||
return ctypes.windll.user32.GetParent(fenster.winfo_id())
|
||
|
||
|
||
def taskleiste_anzeigen(fenster: tk.Tk) -> None:
|
||
"""Ein overrideredirect-Fenster erscheint unter Windows sonst nicht in Taskleiste/Alt+Tab."""
|
||
if not IS_WIN:
|
||
return
|
||
try:
|
||
import ctypes
|
||
GWL_EXSTYLE, WS_EX_APPWINDOW, WS_EX_TOOLWINDOW = -20, 0x00040000, 0x00000080
|
||
hwnd = _hwnd(fenster)
|
||
stil = ctypes.windll.user32.GetWindowLongW(hwnd, GWL_EXSTYLE)
|
||
stil = (stil & ~WS_EX_TOOLWINDOW) | WS_EX_APPWINDOW
|
||
ctypes.windll.user32.SetWindowLongW(hwnd, GWL_EXSTYLE, stil)
|
||
fenster.wm_withdraw()
|
||
fenster.after(10, lambda: (fenster.wm_deiconify(), fenster.focus_force()))
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
def arbeitsbereich(fenster: tk.Misc) -> tuple[int, int, int, int]:
|
||
"""Bildschirmbereich ohne Taskleiste (des Monitors, auf dem das Fenster steht): x, y, breite, höhe."""
|
||
if IS_WIN:
|
||
try:
|
||
import ctypes
|
||
from ctypes import wintypes
|
||
|
||
class MONITORINFO(ctypes.Structure):
|
||
_fields_ = [("cbSize", wintypes.DWORD), ("rcMonitor", wintypes.RECT),
|
||
("rcWork", wintypes.RECT), ("dwFlags", wintypes.DWORD)]
|
||
|
||
mon = ctypes.windll.user32.MonitorFromWindow(_hwnd(fenster), 2) # MONITOR_DEFAULTTONEAREST
|
||
mi = MONITORINFO()
|
||
mi.cbSize = ctypes.sizeof(MONITORINFO)
|
||
if ctypes.windll.user32.GetMonitorInfoW(mon, ctypes.byref(mi)):
|
||
r = mi.rcWork
|
||
return r.left, r.top, r.right - r.left, r.bottom - r.top
|
||
except Exception:
|
||
pass
|
||
return 0, 0, fenster.winfo_screenwidth(), fenster.winfo_screenheight()
|
||
|
||
|
||
# --------------------------------------------------------------------------- Bausteine
|
||
|
||
class TitelKnopf(tk.Label):
|
||
def __init__(self, master, bild: tk.PhotoImage, command):
|
||
super().__init__(master, image=bild, bg=w.FACE, bd=2, relief="raised", width=14, height=12)
|
||
self.command = command
|
||
self.bind("<ButtonPress-1>", lambda e: self.configure(relief="sunken"))
|
||
self.bind("<ButtonRelease-1>", self._los)
|
||
|
||
def _los(self, e):
|
||
self.configure(relief="raised")
|
||
if 0 <= e.x < self.winfo_width() and 0 <= e.y < self.winfo_height():
|
||
self.command()
|
||
|
||
|
||
class Titelleiste(tk.Frame):
|
||
"""Blaue Win95-Titelleiste. knoepfe: Teilmenge von ("min", "max", "close")."""
|
||
|
||
def __init__(self, master, fenster: tk.Misc, titel: str, icon: tk.PhotoImage | None, knoepfe=("close",),
|
||
schliessen=None, minimieren=None, maximieren=None):
|
||
super().__init__(master, bg=w.NAVY, height=20)
|
||
self.fenster = fenster
|
||
self._bilder = {n: glyph(self, n) for n in ("min", "max", "restore", "close")}
|
||
if icon is not None:
|
||
tk.Label(self, image=icon, bg=w.NAVY).pack(side="left", padx=(2, 0))
|
||
self.titel_var = tk.StringVar(value=titel)
|
||
self.titel_lbl = tk.Label(self, textvariable=self.titel_var, bg=w.NAVY, fg=w.WHITE, font=w.FONT_BOLD,
|
||
anchor="w")
|
||
self.titel_lbl.pack(side="left", fill="x", expand=True, padx=3)
|
||
self.knoepfe: dict[str, TitelKnopf] = {}
|
||
if "close" in knoepfe:
|
||
self.knoepfe["close"] = TitelKnopf(self, self._bilder["close"], schliessen)
|
||
self.knoepfe["close"].pack(side="right", padx=(2, 2), pady=2)
|
||
if "max" in knoepfe:
|
||
self.knoepfe["max"] = TitelKnopf(self, self._bilder["max"], maximieren)
|
||
self.knoepfe["max"].pack(side="right", pady=2)
|
||
if "min" in knoepfe:
|
||
self.knoepfe["min"] = TitelKnopf(self, self._bilder["min"], minimieren)
|
||
self.knoepfe["min"].pack(side="right", pady=2)
|
||
self._maximieren = maximieren
|
||
for widget in (self, self.titel_lbl):
|
||
widget.bind("<ButtonPress-1>", self._start)
|
||
widget.bind("<B1-Motion>", self._ziehen)
|
||
if maximieren:
|
||
widget.bind("<Double-Button-1>", lambda e: maximieren())
|
||
self._ab = (0, 0)
|
||
self.ziehen_erlaubt = lambda: True
|
||
|
||
def set_titel(self, t: str):
|
||
self.titel_var.set(t)
|
||
|
||
def max_symbol(self, maximiert: bool):
|
||
if "max" in self.knoepfe:
|
||
self.knoepfe["max"].configure(image=self._bilder["restore" if maximiert else "max"])
|
||
|
||
def _start(self, e):
|
||
self._ab = (e.x_root - self.fenster.winfo_rootx(), e.y_root - self.fenster.winfo_rooty())
|
||
self._nativ = False
|
||
if NATIV_VERSCHIEBEN and IS_WIN and self.ziehen_erlaubt():
|
||
# Windows übernimmt das Ziehen: flüssig und mit Andocken am Bildschirmrand
|
||
from provtool.gui import win32
|
||
self._nativ = win32.nativ_verschieben(self.fenster)
|
||
if self._nativ:
|
||
return "break"
|
||
|
||
def _ziehen(self, e):
|
||
if self._nativ or not self.ziehen_erlaubt():
|
||
return
|
||
self.fenster.geometry(f"+{e.x_root - self._ab[0]}+{e.y_root - self._ab[1]}")
|
||
|
||
def blinken(self, mal: int = 3):
|
||
"""Titelleiste kurz grau/blau blinken lassen (wie Windows bei gesperrtem Fenster)."""
|
||
farben = [w.SHADOW, w.NAVY] * mal
|
||
|
||
def schritt(i=0):
|
||
if i >= len(farben) or not self.winfo_exists():
|
||
return
|
||
for widget in (self, self.titel_lbl):
|
||
widget.configure(bg=farben[i])
|
||
self.after(70, lambda: schritt(i + 1))
|
||
schritt()
|
||
|
||
|
||
NATIV_VERSCHIEBEN = True # Tests können das abschalten, um den Tk-Weg zu prüfen
|
||
NATIVER_RAHMEN = True # Windows: echtes Fenster mit unsichtbarem Rahmen (nativ.py)
|
||
|
||
|
||
# --------------------------------------------------------------------------- Hauptfenster
|
||
|
||
class Hauptrahmen:
|
||
"""Umgibt das Hauptfenster. Im klassischen Modus mit eigener Titelleiste, sonst nur ein Container."""
|
||
|
||
RAND = 6 # Pixel am Rand, an denen man die Größe ziehen kann (= sichtbarer Rahmen)
|
||
ECKE = 18 # so weit vom Eck entfernt gilt der Rand noch als Ecke (diagonal ziehen)
|
||
|
||
def __init__(self, root: tk.Tk, titel: str, icon: tk.PhotoImage, schliessen, klassisch: bool = True,
|
||
minbreite: int = 900, minhoehe: int = 520):
|
||
self.root, self.klassisch = root, klassisch
|
||
self.minbreite, self.minhoehe = minbreite, minhoehe
|
||
self.maximiert = False
|
||
self._normal_geo: str | None = None
|
||
self._schliessen = schliessen
|
||
self.nativ = None
|
||
root.minsize(minbreite, minhoehe)
|
||
if not klassisch:
|
||
self.aussen = tk.Frame(root, bg=w.FACE)
|
||
self.aussen.pack(fill="both", expand=True)
|
||
self.inhalt = self.aussen
|
||
self.titelleiste = None
|
||
root.sperrbereich = lambda: [self.inhalt] # type: ignore[attr-defined] # für modal.py
|
||
return
|
||
|
||
root.overrideredirect(True)
|
||
self.aussen = tk.Frame(root, bg=w.FACE, bd=2, relief="raised", padx=self.RAND - 2, pady=self.RAND - 2)
|
||
self.aussen.pack(fill="both", expand=True)
|
||
self.titelleiste = Titelleiste(self.aussen, root, titel, icon, ("min", "max", "close"),
|
||
schliessen=schliessen, minimieren=self.minimieren,
|
||
maximieren=self.umschalten)
|
||
self.titelleiste.pack(fill="x")
|
||
# Maximiert darf man nur nativ ziehen (Windows stellt das Fenster dabei wieder her)
|
||
self.titelleiste.ziehen_erlaubt = lambda: not self.maximiert or self.nativ_an
|
||
self.inhalt = tk.Frame(self.aussen, bg=w.FACE)
|
||
self.inhalt.pack(fill="both", expand=True)
|
||
# Gesperrt wird bei offenem Dialog nur der Inhalt – Titelleiste und Rand bleiben bedienbar
|
||
root.sperrbereich = lambda: [self.inhalt] # type: ignore[attr-defined]
|
||
|
||
self._rand: str = ""
|
||
self._resize_geplant = None
|
||
self.aussen.bind("<Motion>", self._rand_cursor)
|
||
# Mauszeiger zurücksetzen, sobald der Rand verlassen wird – Kindfenster erben sonst den
|
||
# Größenänderungs-Zeiger, wenn das letzte Motion-Ereignis am Rand verpasst wurde.
|
||
self.aussen.bind("<Leave>", self._cursor_zurueck)
|
||
self.inhalt.bind("<Enter>", self._cursor_zurueck)
|
||
self.titelleiste.bind("<Enter>", self._cursor_zurueck, add="+")
|
||
self.aussen.bind("<ButtonPress-1>", self._resize_start)
|
||
self.aussen.bind("<B1-Motion>", self._resize)
|
||
root.bind_all("<Alt-F4>", lambda e: self.alt_f4(e.widget))
|
||
|
||
if IS_WIN and NATIVER_RAHMEN:
|
||
from provtool.gui.nativ import NativerRahmen
|
||
self.nativ = NativerRahmen(root, self.RAND, self.ECKE)
|
||
root.bind("<Configure>", self._fenster_geaendert, add="+")
|
||
root.bind("<Map>", lambda e: e.widget is root and self._nativ_einrichten(), add="+")
|
||
self._nativ_einrichten() # möglichst schon vor dem ersten Anzeigen
|
||
root.after(50, self._fenster_einrichten)
|
||
|
||
# ---- natives Windows-Fenster
|
||
@property
|
||
def nativ_an(self) -> bool:
|
||
return self.nativ is not None and self.nativ.aktiv
|
||
|
||
def _nativ_einrichten(self) -> bool:
|
||
if self.nativ is None:
|
||
return False
|
||
if self.nativ.aktiv and not self.nativ.veraltet():
|
||
return True
|
||
try:
|
||
sichtbar = bool(self.root.winfo_viewable())
|
||
except tk.TclError:
|
||
sichtbar = True
|
||
if self.nativ.veraltet(): # Tk hat das äußere Fenster neu angelegt
|
||
ok = self.nativ.neu_einrichten()
|
||
else:
|
||
ok = self.nativ.einrichten()
|
||
if ok:
|
||
# Erst eingerichtet, als das Fenster schon zu sehen war -> Taskleiste muss es neu erfassen
|
||
self._taskleiste_neu = sichtbar
|
||
return ok
|
||
|
||
_taskleiste_neu = True
|
||
|
||
def _fenster_einrichten(self):
|
||
self._nativ_einrichten()
|
||
if self.nativ_an and not self._taskleiste_neu:
|
||
return # vor dem ersten Anzeigen eingerichtet: Windows kennt den Taskleisten-Stil schon
|
||
taskleiste_anzeigen(self.root)
|
||
|
||
def _fenster_geaendert(self, e):
|
||
"""Zustand nachführen – maximieren/wiederherstellen geht jetzt auch über Windows selbst
|
||
(Win+Hoch, an den oberen Rand ziehen, Doppelklick, Taskleiste)."""
|
||
if e.widget is not self.root or self.nativ is None:
|
||
return
|
||
if self.nativ.veraltet():
|
||
self._nativ_einrichten()
|
||
if not self.nativ_an:
|
||
return
|
||
try:
|
||
maximiert = self.nativ.maximiert()
|
||
if maximiert != self.maximiert:
|
||
self.maximiert = maximiert
|
||
self.titelleiste.max_symbol(maximiert)
|
||
self._cursor_zurueck()
|
||
if not maximiert and self.root.wm_state() == "normal":
|
||
self._normal_geo = self.root.geometry()
|
||
except Exception:
|
||
pass
|
||
|
||
def alt_f4(self, widget) -> str:
|
||
"""Alt+F4 wie unter Windows: schließt nur das Fenster, in dem man gerade ist."""
|
||
try:
|
||
# Ist ein modaler Dialog offen, gilt Alt+F4 immer ihm – egal wo der Tastaturfokus liegt
|
||
# (unter Windows liefert Tk Tastendrücke an das Fenster mit Fokus, das kann noch das
|
||
# Hauptfenster sein).
|
||
from provtool.gui import modal
|
||
sperre = self.root.grab_current() or modal.oberstes()
|
||
popup = self._offenes_popup()
|
||
if sperre is not None and sperre.winfo_toplevel() is not self.root:
|
||
fenster = sperre.winfo_toplevel()
|
||
elif popup is not None: # Sperre (noch) nicht gesetzt – trotzdem gilt Alt+F4 dem Popup
|
||
fenster = popup
|
||
else:
|
||
fenster = widget.winfo_toplevel() if isinstance(widget, tk.Misc) else self.root
|
||
except tk.TclError:
|
||
fenster = self.root
|
||
if fenster is self.root:
|
||
from provtool.gui import modal
|
||
offen = modal.oberstes()
|
||
if offen is not None: # Alt+F4 bei offenem Dialog schließt den Dialog
|
||
offen.abbrechen()
|
||
return "break"
|
||
self._schliessen()
|
||
elif hasattr(fenster, "abbrechen"):
|
||
fenster.abbrechen()
|
||
else:
|
||
fenster.destroy()
|
||
return "break"
|
||
|
||
def _offenes_popup(self):
|
||
"""Innerstes sichtbares Popup mit abbrechen() (Dialog, Rückfrage, Rundgang-Sprechblase)."""
|
||
gefunden = []
|
||
|
||
def suche(widget):
|
||
for kind in widget.winfo_children():
|
||
# Fensterstatus statt winfo_ismapped(): direkt nach dem Einblenden ist der Status schon
|
||
# „normal“, gezeichnet ist das Fenster aber evtl. noch nicht (langsamer PC, Wine).
|
||
if isinstance(kind, tk.Toplevel) and hasattr(kind, "abbrechen"):
|
||
try:
|
||
offen = kind.winfo_exists() and kind.wm_state() != "withdrawn"
|
||
except tk.TclError:
|
||
offen = False
|
||
if offen:
|
||
gefunden.append(kind)
|
||
suche(kind)
|
||
suche(self.root)
|
||
return gefunden[-1] if gefunden else None
|
||
|
||
# ---- öffentliche Helfer
|
||
def set_titel(self, text: str):
|
||
self.root.title(text)
|
||
if self.titelleiste:
|
||
self.titelleiste.set_titel(text)
|
||
|
||
def griff(self, master) -> tk.Label | None:
|
||
"""Anfasser für die Statusleiste (nur im klassischen Modus)."""
|
||
if not self.klassisch:
|
||
return None
|
||
self._griff_bild = griff_bild(master)
|
||
g = tk.Label(master, image=self._griff_bild, bg=w.FACE, cursor="sizing" if not IS_WIN else "size_nw_se")
|
||
g.bind("<ButtonPress-1>", self._griff_start)
|
||
g.bind("<B1-Motion>", self._resize)
|
||
return g
|
||
|
||
def _griff_start(self, e):
|
||
if self.nativ_an and not self.maximiert:
|
||
self.nativ.groesse_ziehen("se") # Windows zieht die Größe (flüssig)
|
||
return "break"
|
||
self._resize_start(e, "se")
|
||
|
||
def geometrie_setzen(self, geo: str | None, maximiert: bool = False):
|
||
self.root.update_idletasks()
|
||
if not geo or "+" not in geo:
|
||
breite, hoehe = 1200, 720
|
||
if geo and "x" in geo:
|
||
try:
|
||
breite, hoehe = (int(v) for v in geo.split("+")[0].split("x"))
|
||
except ValueError:
|
||
pass
|
||
x0, y0, bw, bh = arbeitsbereich(self.root)
|
||
breite, hoehe = min(breite, bw), min(hoehe, bh)
|
||
geo = f"{breite}x{hoehe}+{x0 + (bw - breite) // 2}+{y0 + (bh - hoehe) // 2}"
|
||
self.root.geometry(geo)
|
||
self._normal_geo = geo
|
||
self._nativ_einrichten()
|
||
if maximiert and self.klassisch:
|
||
self.root.after(100, self.maximieren)
|
||
elif maximiert: # normaler Windows-Rahmen: Windows maximiert selbst
|
||
try:
|
||
self.root.state("zoomed")
|
||
except tk.TclError:
|
||
self.root.attributes("-zoomed", True) # Linux/X11
|
||
|
||
def normale_geometrie(self) -> str:
|
||
return self._normal_geo if self.maximiert and self._normal_geo else self.root.geometry()
|
||
|
||
# ---- Fensterfunktionen
|
||
def minimieren(self):
|
||
if IS_WIN:
|
||
# Direkt über Windows: offene Dialoge (gehören dem Hauptfenster) werden mit aus- und
|
||
# wieder eingeblendet. NICHT overrideredirect umschalten – Tk baut dabei das Fenster neu
|
||
# und Windows würde die zugehörigen Dialoge zerstören.
|
||
from provtool.gui import win32
|
||
if win32.minimieren(self.root):
|
||
return
|
||
# Tk minimiert keine overrideredirect-Fenster -> kurz normalen Rahmen aktivieren
|
||
geo = self.root.geometry()
|
||
self.root.overrideredirect(False)
|
||
self.root.iconify()
|
||
|
||
def wieder_da(e):
|
||
if e.widget is self.root and self.root.state() == "normal":
|
||
self.root.unbind("<Map>")
|
||
self.root.overrideredirect(True)
|
||
self.root.geometry(geo)
|
||
taskleiste_anzeigen(self.root)
|
||
self.root.bind("<Map>", wieder_da)
|
||
|
||
def maximieren(self):
|
||
if self.maximiert:
|
||
return
|
||
if self.nativ_an:
|
||
from provtool.gui.nativ import SC_MAXIMIZE
|
||
self._normal_geo = self.root.geometry()
|
||
self.nativ.befehl(SC_MAXIMIZE)
|
||
self.maximiert = self.nativ.maximiert()
|
||
self.titelleiste.max_symbol(self.maximiert)
|
||
return
|
||
self._normal_geo = self.root.geometry()
|
||
x, y, bw, bh = arbeitsbereich(self.root)
|
||
self.root.geometry(f"{bw}x{bh}+{x}+{y}")
|
||
self.maximiert = True
|
||
self.titelleiste.max_symbol(True)
|
||
|
||
def wiederherstellen(self):
|
||
if not self.maximiert:
|
||
return
|
||
if self.nativ_an:
|
||
from provtool.gui.nativ import SC_RESTORE
|
||
self.nativ.befehl(SC_RESTORE)
|
||
self.maximiert = self.nativ.maximiert()
|
||
self.titelleiste.max_symbol(self.maximiert)
|
||
return
|
||
self.maximiert = False
|
||
if self._normal_geo:
|
||
self.root.geometry(self._normal_geo)
|
||
self.titelleiste.max_symbol(False)
|
||
|
||
def umschalten(self):
|
||
self.wiederherstellen() if self.maximiert else self.maximieren()
|
||
|
||
# ---- Größe ändern
|
||
def _welcher_rand(self, x, y) -> str:
|
||
bw, bh, r, k = self.aussen.winfo_width(), self.aussen.winfo_height(), self.RAND, self.ECKE
|
||
ns = "n" if y < r else "s" if y >= bh - r else ""
|
||
we = "w" if x < r else "e" if x >= bw - r else ""
|
||
# an einer Kante nahe am Eck -> Ecke (diagonal), wie bei Windows
|
||
if ns and not we:
|
||
we = "w" if x < k else "e" if x >= bw - k else ""
|
||
elif we and not ns:
|
||
ns = "n" if y < k else "s" if y >= bh - k else ""
|
||
return ns + we
|
||
|
||
def _cursor_zurueck(self, _e=None):
|
||
if self._rand_zeiger:
|
||
self._rand_zeiger = False
|
||
try:
|
||
self.aussen.configure(cursor="")
|
||
except tk.TclError:
|
||
pass
|
||
|
||
_rand_zeiger = False
|
||
|
||
CURSOR_WIN = {"n": "size_ns", "s": "size_ns", "e": "size_we", "w": "size_we",
|
||
"nw": "size_nw_se", "se": "size_nw_se", "ne": "size_ne_sw", "sw": "size_ne_sw"}
|
||
CURSOR_X11 = {"n": "top_side", "s": "bottom_side", "e": "right_side", "w": "left_side",
|
||
"nw": "top_left_corner", "se": "bottom_right_corner", "ne": "top_right_corner",
|
||
"sw": "bottom_left_corner"}
|
||
|
||
def _rand_cursor(self, e):
|
||
if self.maximiert:
|
||
self._cursor_zurueck()
|
||
return
|
||
rand = self._welcher_rand(e.x, e.y)
|
||
zeiger = (self.CURSOR_WIN if IS_WIN else self.CURSOR_X11).get(rand, "")
|
||
try:
|
||
self.aussen.configure(cursor=zeiger)
|
||
self._rand_zeiger = bool(zeiger)
|
||
except tk.TclError:
|
||
pass
|
||
|
||
def _resize_start(self, e, rand: str | None = None):
|
||
self._rand = rand or self._welcher_rand(e.x, e.y)
|
||
if self._rand and self.nativ_an and not self.maximiert:
|
||
# Den Zeiger zeigt Tk (weiß dann immer, welcher gilt); ziehen lässt Windows (flüssig,
|
||
# mit Andocken). Angestoßen per PostMessage – siehe nativ.py.
|
||
self.nativ.groesse_ziehen(self._rand)
|
||
self._rand = ""
|
||
return "break"
|
||
self._start = (e.x_root, e.y_root, self.root.winfo_x(), self.root.winfo_y(),
|
||
self.root.winfo_width(), self.root.winfo_height())
|
||
|
||
def _resize(self, e):
|
||
if not self._rand or self.maximiert:
|
||
return
|
||
sx, sy, x, y, bw, bh = self._start
|
||
dx, dy = e.x_root - sx, e.y_root - sy
|
||
if "e" in self._rand:
|
||
bw = max(self.minbreite, bw + dx)
|
||
if "s" in self._rand:
|
||
bh = max(self.minhoehe, bh + dy)
|
||
if "w" in self._rand:
|
||
neu = max(self.minbreite, bw - dx)
|
||
x, bw = x + (bw - neu), neu
|
||
if "n" in self._rand:
|
||
neu = max(self.minhoehe, bh - dy)
|
||
y, bh = y + (bh - neu), neu
|
||
# Mausbewegungen bündeln: nur die jeweils letzte Größe anwenden (flüssiger unter Windows)
|
||
self._resize_ziel = f"{bw}x{bh}+{x}+{y}"
|
||
if self._resize_geplant is None:
|
||
self._resize_geplant = self.root.after(15, self._resize_anwenden)
|
||
|
||
def _resize_anwenden(self):
|
||
self._resize_geplant = None
|
||
try:
|
||
self.root.geometry(self._resize_ziel)
|
||
except tk.TclError:
|
||
pass
|