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

Last change on this file since 1198 was 1198, checked in by Maciej Komosinski, 15 months ago

Added simple Python GUI for Framsticks library/server

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