source: framspy/gui/widgets/propertyWindow.py @ 1202

Last change on this file since 1202 was 1202, checked in by Maciej Komosinski, 14 months ago

Introduced common utility functions for setting window sizes; untangled some repeated code

File size: 7.1 KB
Line 
1import tkinter as tk
2import tkinter.ttk as ttk
3import tkinter.messagebox as messagebox
4from typing import Dict, List, Callable, Tuple
5from gui.framsutils.framsProperty import Property, PropertyCallback, propertyToTkinter
6from gui.framsutils.FramsInterface import FramsInterface, InterfaceType
7from gui.widgets.ToolTip import CreateToolTip
8from gui.utils import windowHideAndMaximize, windowShowAndSetGeometry
9
10'''
11BUG: when you open this window during the creature loading for render (commThread in glFrame) and modify the list of creatures (with buttons) or parts (with editing genotype), it can get stuck on the semaphore or throw an exception when exiting the window and returning to creature loading.
12SOLUTION: open window only when the semaphore is released.
13'''
14class PropertyWindow(tk.Toplevel):
15    def __init__(self, parent, title: str, posX, dataCallback: Callable[[None], List[Property]], propUpdateCallback: Callable[[str, str], None], getError: Callable[[None], str or None], frams: FramsInterface, semaphore = None, onUpdate: Callable[[None], None] = None):
16        super().__init__(parent)
17        self.parent = parent
18        #self.transient(parent)
19        self.protocol("WM_DELETE_WINDOW", self._dismiss)
20        self.title(title)
21
22        self.propUpdateCallback: Callable[[str, str], None] = propUpdateCallback
23        self.dataCallback: Callable[[None], List[Property]] = dataCallback
24        self.getError: Callable([None], str or None) = getError
25        self.onUpdate: Callable([None], None) = onUpdate
26        self.frams: FramsInterface = frams
27
28        #MAIN SECTION
29        frame_main = tk.Frame(master=self)
30        notebook_notebook = ttk.Notebook(frame_main)
31        frames: Dict[str, tk.Frame] = {}
32        frames_idx: Dict[str, int] = {}
33        data = self.dataCallback()
34
35        self.callbacks: List[PropertyCallback] = []
36        self.semaphore = semaphore
37
38        for prop in data:
39            if "group" not in prop.p:
40                prop.p["group"] = "other"
41               
42            if prop.p["group"] not in frames:
43                frames[prop.p["group"]] = tk.Frame(master=frame_main)
44                frames_idx[prop.p["group"]] = 0
45
46                frames[prop.p["group"]].columnconfigure(0, weight=0, minsize=0)
47                frames[prop.p["group"]].columnconfigure(1, weight=1, minsize=0)
48                notebook_notebook.add(frames[prop.p["group"]], text=prop.p["group"])
49
50            widget, callback = propertyToTkinter(prop, frames[prop.p["group"]])
51            if widget:
52                label = tk.Label(master=frames[prop.p["group"]], text=prop.p["name"], anchor='w')
53                if prop.p["type"][0] == 'p':
54                    label.text = ""
55                label.grid(row=frames_idx[prop.p["group"]], column=0, sticky="NSEW")
56                if issubclass(type(widget), tk.Checkbutton):
57                    widget.grid(row=frames_idx[prop.p["group"]], column=1, sticky="NSW")
58                else:
59                    widget.grid(row=frames_idx[prop.p["group"]], column=1, sticky="NSEW")
60                if callback:
61                    self.callbacks.append(callback)
62                if prop.p["help"] != "":
63                    CreateToolTip(label, prop.p["help"])
64                    CreateToolTip(widget, prop.p["help"])
65                if issubclass(type(widget), tk.Text) and prop.p["type"][0] == 's':
66                    frames[prop.p["group"]].rowconfigure(frames_idx[prop.p["group"]], weight=1, minsize=0)
67                else:
68                    frames[prop.p["group"]].rowconfigure(frames_idx[prop.p["group"]], weight=0, minsize=0)
69                frames_idx[prop.p["group"]] += 1
70
71        notebook_notebook.grid(row=0, column=0, sticky="NSEW") #sometimes it freezes here
72        frame_main.columnconfigure(0, weight=1, minsize=0)
73        frame_main.rowconfigure(0, weight=1, minsize=0)
74
75        #CONTROL BUTTONS
76        frame_buttons = tk.Frame(master=frame_main)
77        self.button_refresh = tk.Button(master=frame_buttons, text="Refresh", command=self.refreshButtonCommand)
78        self.button_cancel = tk.Button(master=frame_buttons, text="Cancel", command=self.cancelButtonCommand)
79        self.button_apply = tk.Button(master=frame_buttons, text="Apply", command=self.applyButtonCommand)
80        self.button_ok = tk.Button(master=frame_buttons, text="OK", command=self.okButtonCommand)
81        self.button_refresh.grid(row=0, column=0, sticky="E")
82        self.button_cancel.grid(row=0, column=1, sticky="E")
83        self.button_apply.grid(row=0, column=2, sticky="E")
84        self.button_ok.grid(row=0, column=3, sticky="E")
85
86        frame_buttons.columnconfigure(0, weight=1, minsize=0)
87        frame_buttons.columnconfigure(1, weight=1, minsize=0)
88        frame_buttons.columnconfigure(2, weight=1, minsize=0)
89        frame_buttons.columnconfigure(3, weight=1, minsize=0)
90        frame_buttons.rowconfigure(0, weight=1, minsize=0)
91        frame_buttons.grid(row=1, column=0, sticky="SE")
92        frame_main.rowconfigure(1, weight=0, minsize=0)
93
94        frame_main.grid(row=0, column=0, sticky="NSEW")
95
96        self.columnconfigure(0, weight=1, minsize=0)
97        self.rowconfigure(0, weight=1, minsize=0)
98
99        def parseGeometryString(geometry: str) -> Tuple[int, int, int, int]:
100            widthxrest = geometry.split('x')
101            heightxy = widthxrest[1].split('+')
102            maxwidth = int(widthxrest[0])
103            maxheight = int(heightxy[0])
104            x = int(heightxy[1])
105            y = int(heightxy[2])
106            return maxwidth, maxheight, x, y
107
108        self.update_idletasks()
109
110        width, height, x, y = parseGeometryString(self.winfo_geometry())
111
112        windowHideAndMaximize(self)
113        rootx = self.winfo_rootx()
114
115        maxWidth, maxHeight, x, y = parseGeometryString(self.winfo_geometry())
116        x = (maxWidth - width) / 2
117        y = (maxHeight - height) / 2
118
119        windowShowAndSetGeometry(self, "+%d+%d" % (int(rootx + x), int(y)))
120        self.maxsize(maxWidth, maxHeight)
121
122        self.grab_set()
123
124    def refreshButtonCommand(self):
125        data = self.dataCallback()
126        for d in data:
127            prop = next((x for x in self.callbacks if d.p["id"] == x.id), None)
128            if prop:
129                prop.updateValue(d)
130
131    def cancelButtonCommand(self):
132        self._dismiss()
133
134    def applyButtonCommand(self):
135        if self.propUpdateCallback:
136            updated = False
137            with self.semaphore:
138                for c in self.callbacks:
139                    if c.changed():
140                        self.propUpdateCallback(c.id, c.rawValue(self.frams.interfaceType == InterfaceType.SOCKET))
141                        updated = True
142                        error = self.getError()
143                        if error:
144                            messagebox.showerror("Framsticks error", error)
145                self.refreshButtonCommand()
146                if updated and self.onUpdate:
147                    self.onUpdate()
148   
149    def okButtonCommand(self):
150        self.applyButtonCommand()
151        self.cancelButtonCommand()
152
153    def _dismiss(self):
154        self.grab_release()
155        self.destroy()
Note: See TracBrowser for help on using the repository browser.