Eigene Kennungen ausblenden, Win95-Menüs, verschiebbares Hauptfenster bei offenem Dialog u. a.
Windows-Build / windows-exe (push) Canceled after 53m28s
Windows-Build / windows-exe (push) Canceled after 53m28s
Neu: - Eigene Kennungen (eigene Filiale) ausblenden: nicht angezeigt, nicht mitgerechnet, nicht exportiert; per Rechtsklick, Händler-Menü oder Import-Bericht; Händler-Zuordnung hat Vorrang - Menüs im Win95-Stil: wechseln beim Überfahren der Menüleiste, Tastatur, Kontextmenü gleich - Dialoge sperren nur den Inhalt: Hauptfenster bleibt verschiebbar/maximierbar; Dialoge haben eigenen Taskleisten-Eintrag; Klick auf gesperrten Inhalt holt den Dialog nach vorn (blinkt) - Trennlinie zwischen Händler- und Positionsliste sichtbar und ziehbar, Position wird gemerkt - Strg+Rücktaste / Strg+Entf löschen ganze Wörter in allen Eingabefeldern Behoben: - Gedrückte Knöpfe zeigten keine Beschriftung (activeForeground gehört in Tk zur Klasse Background) - Tipp des Tages: Text lief aus dem sichtbaren Bereich - Größenänderung: breiterer Griffbereich mit Ecken, Zeiger wird zuverlässig zurückgesetzt, flüssiger; Verschieben über Windows selbst (inkl. Andocken), Minimieren über Windows - Zeichen, die MS Sans Serif nicht hat (…, ▸, →, •), durch Win95-übliche ersetzt - Menü/Tooltip/Dialog: Größe+Position zusammen setzen (Tk ignoriert sonst die Position) - Suche reagiert erst nach kurzer Tipp-Pause (flüssigeres Tippen) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
69c2a108a9
commit
807a38622c
15 files changed
+1217
-187
No files matched your search
+124
-43
@@ -294,6 +294,9 @@ def apply(root: tk.Tk) -> None:
|
||||
o("*Background", FACE)
|
||||
o("*Foreground", DARK)
|
||||
o("*activeBackground", FACE)
|
||||
# Tk-Eigenheit: „activeForeground“ gehört zur Optionsklasse „Background“ – ohne diese Zeile
|
||||
# wäre die Schrift gedrückter Knöpfe grau auf grau (unsichtbar).
|
||||
o("*activeForeground", DARK)
|
||||
o("*highlightThickness", 0)
|
||||
o("*selectBackground", NAVY)
|
||||
o("*selectForeground", WHITE)
|
||||
@@ -322,6 +325,7 @@ def apply(root: tk.Tk) -> None:
|
||||
|
||||
from tkinter import font as tkfont
|
||||
zeile = max(18, tkfont.Font(root=root, font=FONT).metrics("linespace") + 5)
|
||||
wort_loeschen_einrichten(root)
|
||||
st = ttk.Style(root)
|
||||
st.theme_use("classic" if "classic" in st.theme_names() else "default")
|
||||
st.configure(".", background=FACE, foreground=DARK, font=FONT, troughcolor=LIGHT,
|
||||
@@ -341,6 +345,78 @@ def apply(root: tk.Tk) -> None:
|
||||
st.configure("Sash", sashthickness=6, background=FACE)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- Wort löschen
|
||||
|
||||
def _wortgrenze(text: str, pos: int, richtung: int) -> int:
|
||||
"""Position der nächsten Wortgrenze von pos aus (richtung -1 = links, +1 = rechts),
|
||||
wie Strg+Rücktaste / Strg+Entf unter Windows: erst Leerzeichen, dann das Wort."""
|
||||
if richtung < 0:
|
||||
i = pos
|
||||
while i > 0 and text[i - 1].isspace():
|
||||
i -= 1
|
||||
if i > 0 and text[i - 1].isalnum():
|
||||
while i > 0 and text[i - 1].isalnum():
|
||||
i -= 1
|
||||
elif i > 0:
|
||||
i -= 1 # Satzzeichen einzeln
|
||||
return i
|
||||
i = pos
|
||||
if i < len(text) and text[i].isalnum():
|
||||
while i < len(text) and text[i].isalnum():
|
||||
i += 1
|
||||
elif i < len(text):
|
||||
i += 1
|
||||
while i < len(text) and text[i].isspace():
|
||||
i += 1
|
||||
return i
|
||||
|
||||
|
||||
def _wort_weg(event, richtung: int):
|
||||
e = event.widget
|
||||
try:
|
||||
if str(e.cget("state")) in ("disabled", "readonly"):
|
||||
return "break"
|
||||
except tk.TclError:
|
||||
pass
|
||||
try:
|
||||
if e.selection_present():
|
||||
e.delete("sel.first", "sel.last")
|
||||
return "break"
|
||||
except (tk.TclError, AttributeError):
|
||||
pass
|
||||
text, pos = e.get(), e.index("insert")
|
||||
grenze = _wortgrenze(text, pos, richtung)
|
||||
if richtung < 0 and grenze < pos:
|
||||
e.delete(grenze, pos)
|
||||
elif richtung > 0 and grenze > pos:
|
||||
e.delete(pos, grenze)
|
||||
return "break"
|
||||
|
||||
|
||||
def _text_wort_weg(event, richtung: int):
|
||||
t = event.widget
|
||||
if str(t.cget("state")) == "disabled":
|
||||
return "break"
|
||||
if t.tag_ranges("sel"):
|
||||
t.delete("sel.first", "sel.last")
|
||||
return "break"
|
||||
if richtung < 0:
|
||||
t.delete("insert -1c wordstart" if t.get("insert -1c").isspace() else "insert wordstart", "insert")
|
||||
else:
|
||||
t.delete("insert", "insert wordend")
|
||||
return "break"
|
||||
|
||||
|
||||
def wort_loeschen_einrichten(root: tk.Misc):
|
||||
"""Strg+Rücktaste / Strg+Entf löschen das ganze Wort – in allen Eingabefeldern (Tk kann das
|
||||
unter Windows von Haus aus nicht, dort wird nur ein Zeichen gelöscht)."""
|
||||
for klasse in ("Entry", "TEntry", "TCombobox", "Spinbox", "TSpinbox"):
|
||||
root.bind_class(klasse, "<Control-BackSpace>", lambda e: _wort_weg(e, -1))
|
||||
root.bind_class(klasse, "<Control-Delete>", lambda e: _wort_weg(e, 1))
|
||||
root.bind_class("Text", "<Control-BackSpace>", lambda e: _text_wort_weg(e, -1))
|
||||
root.bind_class("Text", "<Control-Delete>", lambda e: _text_wort_weg(e, 1))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- Bausteine
|
||||
|
||||
class Sunken(tk.Frame):
|
||||
@@ -434,8 +510,12 @@ class Tooltip:
|
||||
y = self.widget.winfo_rooty() + self.widget.winfo_height() + 4
|
||||
self.tip = tw = tk.Toplevel(self.widget)
|
||||
tw.wm_overrideredirect(True)
|
||||
tw.wm_geometry(f"+{x}+{y}")
|
||||
tw.withdraw()
|
||||
tk.Label(tw, text=self.text, bg=INFO_BG, bd=1, relief="solid", padx=4, pady=1).pack()
|
||||
tw.update_idletasks()
|
||||
# Größe + Position zusammen (reine Position ignoriert Tk unter Windows bei rahmenlosen Fenstern)
|
||||
tw.wm_geometry(f"{tw.winfo_reqwidth()}x{tw.winfo_reqheight()}+{x}+{y}")
|
||||
tw.deiconify()
|
||||
|
||||
def _weg(self, _e=None):
|
||||
if self._after:
|
||||
@@ -502,19 +582,24 @@ KLASSISCH = False
|
||||
|
||||
|
||||
class Dialog(tk.Toplevel):
|
||||
"""Modaler Dialog im Win95-Stil."""
|
||||
"""Modaler Dialog im Win95-Stil.
|
||||
|
||||
Modal heißt hier: Der INHALT des aufrufenden Fensters ist gesperrt (siehe modal.py), dessen
|
||||
Titelleiste und Rand bleiben bedienbar. Unter Windows gehört der Dialog dem aufrufenden Fenster
|
||||
(bleibt darüber, kommt mit Alt+Tab nach vorn) und hat einen eigenen Taskleisten-Eintrag.
|
||||
"""
|
||||
|
||||
def __init__(self, master, titel: str, resizable: bool = False):
|
||||
super().__init__(master, bg=FACE)
|
||||
self.withdraw()
|
||||
self.title(titel)
|
||||
if not KLASSISCH:
|
||||
# Rahmenlose Dialoge bekommen ihren Besitzer über win32.Modal – mit transient() würde Tk
|
||||
# sie unter Windows als Kindfenster anlegen, die dann hinter anderen Programmen hängen.
|
||||
# Rahmenlose Dialoge bekommen ihren Besitzer über win32 – mit transient() würde Tk sie
|
||||
# unter Windows als Kindfenster anlegen, die dann hinter anderen Programmen hängen.
|
||||
self.transient(master.winfo_toplevel())
|
||||
self.resizable(resizable, resizable)
|
||||
self.result = None
|
||||
self._modal = None # Windows: Besitzer + Sperre des aufrufenden Fensters (siehe win32.Modal)
|
||||
self._sperre = None
|
||||
self.protocol("WM_DELETE_WINDOW", self.abbrechen)
|
||||
self.bind("<Escape>", lambda e: self.abbrechen())
|
||||
try:
|
||||
@@ -530,64 +615,60 @@ class Dialog(tk.Toplevel):
|
||||
self.titelleiste.pack(side="top", fill="x")
|
||||
|
||||
def zeigen(self, fokus=None):
|
||||
from provtool.gui import modal, win32
|
||||
if getattr(self._root(), "_provtool_schliesst", False):
|
||||
self.destroy() # Programm wird beendet – keine neuen Dialoge mehr
|
||||
return None
|
||||
self.update_idletasks()
|
||||
m = self.master.winfo_toplevel()
|
||||
self._aufrufer = m
|
||||
w, h = self.winfo_reqwidth(), self.winfo_reqheight()
|
||||
x = m.winfo_rootx() + max(0, (m.winfo_width() - w) // 2)
|
||||
y = m.winfo_rooty() + max(0, (m.winfo_height() - h) // 3)
|
||||
self.geometry(f"+{max(0, x)}+{max(0, y)}")
|
||||
self.geometry(f"{w}x{h}+{max(0, x)}+{max(0, y)}") # Größe + Position zusammen (siehe Tooltip)
|
||||
self.deiconify()
|
||||
self.lift()
|
||||
# Windows: Dialog gehört dem aufrufenden Fenster, das solange gesperrt ist → Klick aufs
|
||||
# Hauptfenster, Taskleiste und Alt+Tab holen automatisch den Dialog nach vorn.
|
||||
from provtool.gui.win32 import Modal
|
||||
self.update_idletasks()
|
||||
if KLASSISCH and win32.IS_WIN:
|
||||
# eigener Taskleisten-Eintrag; wird erst nach kurzem Aus-/Einblenden wirksam
|
||||
if win32.taskleisteneintrag(self):
|
||||
self.withdraw()
|
||||
self.deiconify()
|
||||
self.update_idletasks()
|
||||
win32.besitzer_setzen(self, m)
|
||||
self._sperre = modal.Sperre(m, self)
|
||||
self.lift()
|
||||
self.focus_force()
|
||||
self._modal = Modal(self, m)
|
||||
vorher = self.grab_current() # z. B. der Assistent, wenn hier eine Rückfrage aufgeht
|
||||
self._sperre_setzen()
|
||||
bind_id = None
|
||||
if KLASSISCH:
|
||||
# Ein rahmenloser Dialog darf nicht hinter dem Hauptfenster verschwinden
|
||||
bind_id = m.bind("<FocusIn>", lambda e: self.winfo_exists() and self.lift(), add="+")
|
||||
self.focus_force()
|
||||
(fokus or self).focus_set()
|
||||
self.wait_window()
|
||||
if bind_id:
|
||||
try:
|
||||
m.unbind("<FocusIn>", bind_id)
|
||||
except tk.TclError:
|
||||
pass
|
||||
# Tk gibt beim Schließen die Sperre frei, stellt die vorige aber nicht wieder her
|
||||
try:
|
||||
if vorher is not None and vorher.winfo_exists():
|
||||
vorher.grab_set()
|
||||
vorher.focus_force()
|
||||
except tk.TclError:
|
||||
pass
|
||||
return self.result
|
||||
|
||||
def _sperre_setzen(self, versuch: int = 0):
|
||||
"""Tk-Modalsperre setzen. Ist das Fenster noch nicht ganz sichtbar (langsamer PC, Wine),
|
||||
schlägt grab_set fehl – dann kurz danach erneut versuchen statt ohne Sperre weiterzumachen."""
|
||||
def nach_vorn(self, blinken: bool = False):
|
||||
"""Dialog nach vorn holen (z. B. nach Klick auf das gesperrte Hauptfenster)."""
|
||||
from provtool.gui import win32
|
||||
try:
|
||||
if self.winfo_exists():
|
||||
self.grab_set()
|
||||
self.lift()
|
||||
self.focus_force()
|
||||
win32.nach_vorn(self)
|
||||
except tk.TclError:
|
||||
if versuch < 50:
|
||||
self.after(20, lambda: self._sperre_setzen(versuch + 1))
|
||||
return
|
||||
if blinken:
|
||||
self.bell()
|
||||
win32.blinken(self)
|
||||
if self.titelleiste is not None:
|
||||
self.titelleiste.blinken()
|
||||
|
||||
def destroy(self):
|
||||
# Erst das aufrufende Fenster wieder freigeben, dann schließen – sonst aktiviert Windows
|
||||
# beim Schließen ein fremdes Programm statt unseres Fensters.
|
||||
modal = getattr(self, "_modal", None)
|
||||
if modal is not None:
|
||||
modal.beenden()
|
||||
self._modal = None
|
||||
sperre = getattr(self, "_sperre", None)
|
||||
if sperre is not None:
|
||||
sperre.aufheben()
|
||||
self._sperre = None
|
||||
aufrufer = getattr(self, "_aufrufer", None)
|
||||
super().destroy()
|
||||
try: # Fokus zurück an das aufrufende Fenster
|
||||
if aufrufer is not None and aufrufer.winfo_exists():
|
||||
aufrufer.focus_force()
|
||||
except tk.TclError:
|
||||
pass
|
||||
|
||||
def abbrechen(self):
|
||||
self.result = None
|
||||
|
||||
Reference in new issue
Block a user