1 | import tkinter as tk
|
---|
2 | import tkinter.ttk as ttk
|
---|
3 | import tkinter.messagebox as messagebox
|
---|
4 | from typing import Dict, List, Callable, Tuple
|
---|
5 | from gui.framsutils.framsProperty import Property, PropertyCallback, propertyToTkinter
|
---|
6 | from gui.widgets.ToolTip import CreateToolTip
|
---|
7 | from gui.framsutils.FramsInterface import FramsInterface, InterfaceType
|
---|
8 |
|
---|
9 | '''
|
---|
10 | BUG: 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.
|
---|
11 | SOLUTION: open window only when the semaphore is released.
|
---|
12 | '''
|
---|
13 | class 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() |
---|