source: framspy/gui/framsutils/framsProperty.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: 8.1 KB
Line 
1import tkinter as tk
2import tkinter.ttk as ttk
3from typing import Dict, List, Optional, Tuple
4from datetime import datetime
5from time import mktime
6from gui.widgets.ScrolledText import ScrolledText
7import re
8
9trans = {
10        "~": r"\~",
11        "\\": r"\\",
12        "\"": r"\"",
13        "\n": r"\n",
14        "\t": r"\t"}
15
16def encodeString(data: str) -> str:
17        return data.translate(data.maketrans(trans))
18
19datetimeformat = '%Y-%m-%d %H:%M:%S'
20
21class Property:
22        def __init__(self, Id: str = "", Name: str = "", Type: str = "", p: Dict = None) -> None:
23                self.p = {}
24                self.p["id"] = Id
25                self.p["name"] = Name
26                self.p["type"] = Type
27                self.p["flags"] = 0
28                self.p["help"] = ""
29                self.p["value"] = None
30                if p:
31                        self.p = {**self.p, **p}
32
33class PropertyCallback:
34        def __init__(self, widget: tk.Widget, Id: str, xtype: str) -> None:
35                self.widget: tk.Widget = widget
36                self.id = Id
37                self._changed = False
38                self.type = xtype[0]
39                self.subtype = None
40                if len(xtype) > 1:
41                        self.subtype = xtype[1]
42
43                if isinstance(self.widget, tk.Checkbutton):
44                        self.var = self.widget.variable
45                elif isinstance(self.widget, tk.Text):
46                        self.widget.edit_modified(False)
47                elif isinstance(self.widget, tk.Button):
48                        pass
49
50                self.initValue = self.value()
51
52        def changed(self) -> bool:
53                if isinstance(self.widget, tk.Text):
54                        self._changed = self.widget.edit_modified()
55                else:
56                        if self.initValue != self.value():
57                                self._changed = True
58                return self._changed
59
60        def restart(self) -> None:
61                self.initValue = self.value()
62                self._changed = False
63
64        def value(self) -> str:
65                if isinstance(self.widget, tk.Checkbutton):
66                        return self._checkButton()
67                elif isinstance(self.widget, ttk.Combobox):
68                        return self._combobox()
69                elif isinstance(self.widget, ttk.Spinbox):
70                        return self._spinbox()
71                elif isinstance(self.widget, tk.Text):
72                        return encodeString(self._text())
73                elif isinstance(self.widget, tk.Entry):
74                        return self._entry()
75                return None
76
77        def rawValue(self, translate: bool = True) -> str:
78                if self.type == 'd':
79                        if self.subtype == 'b':
80                                return int(self.value(), 2)
81                        elif self.subtype == 'c':
82                                return int(self.value(), 16)
83                        else:
84                                return self.value()
85                elif self.type == 'f':
86                        if self.subtype == 't':
87                                el = datetime.strptime(self.value(), datetimeformat)
88                                t = el.timetuple()
89                                return mktime(t)
90                        elif self.subtype == 'i':
91                                return float(self.value())
92                        else:
93                                return self.value()
94                elif self.type == 's':
95                        if not translate and isinstance(self.widget, tk.Text):
96                                return self._text()
97                        else:
98                                return self.value()
99                elif self.type == 'p':
100                        return self.value()
101
102        def updateValue(self, value: Property) -> None:
103                _, cb = propertyToTkinter(value, self.widget.master)
104
105                if isinstance(self.widget, tk.Checkbutton):
106                        if value.p["value"] == '1' or value.p["value"] == 1:
107                                self.widget.select()
108                        else:
109                                self.widget.deselect()
110                elif isinstance(self.widget, ttk.Combobox):
111                        if hasattr(self.widget, "mapvalues"):
112                                self.widget.set(list(self.widget.mapvalues.keys())[list(self.widget.mapvalues.values()).index(cb.value())])
113                        else:
114                                self.widget.set(cb.value())
115                elif isinstance(self.widget, ttk.Spinbox):
116                        self.widget.set(cb.value())
117                elif isinstance(self.widget, tk.Text):
118                        self.widget.config(state=tk.NORMAL)
119                        if len(self.value()) > 0:
120                                self.widget.delete("1.0", tk.END)
121                        self.widget.insert("1.0", cb._text()) #translates and escapes \\ as expected
122                        self.widget.edit_modified(False)
123                        self.widget.config(state=tk.DISABLED)
124                elif isinstance(self.widget, tk.Entry):
125                        self.widget.delete(0, tk.END)
126                        self.widget.insert(0, cb.value())
127
128        def _checkButton(self) -> str:
129                return "1" if self.var.get() else "0"
130
131        def _combobox(self) -> str:
132                if hasattr(self.widget, "mapvalues"):
133                        return self.widget.mapvalues.get(self.widget.get())
134                else:
135                        return self.widget.get()
136
137        def _spinbox(self) -> str:
138                return str(self.widget.get())
139
140        def _text(self) -> str:
141                return self.widget.get("1.0", "end").strip()
142
143        def _entry(self) -> str:
144                return self.widget.get().strip()
145
146def propertyToTkinter(prop: Property, master: tk.Widget) -> Optional[Tuple[tk.Widget, PropertyCallback]]:
147        global trans
148        widget = None
149        callback: PropertyCallback = None
150        t = prop.p["type"].split()
151        propType = t[0]
152
153        readonly = True if int(prop.p["flags"]) & 1 or int(prop.p["flags"]) & 16 else False
154        visible = False if int(prop.p["flags"]) & 32 else True
155
156        if not visible:
157                return None, None
158
159        class MinMaxDefault:
160                def __init__(self, minValue, maxValue, default = None) -> None:
161                        self.minValue = minValue
162                        self.maxValue = maxValue
163                        self.default = default
164
165        def minMaxDefault(prop_type: List) -> MinMaxDefault:
166                if len(prop_type) >= 4:
167                        return MinMaxDefault(prop_type[1], prop_type[2], prop_type[3])
168                elif len(prop_type) >= 3:
169                        return MinMaxDefault(prop_type[1], prop_type[2])
170                else:
171                        return MinMaxDefault(None, None)
172
173        minmax = minMaxDefault(t)
174
175        if propType[0] == 'd':
176                value = int(prop.p["value"])
177                if len(propType) > 1:
178                        if propType[1] == 'b':
179                                value = bin(value)
180                        elif propType[1] == 'c':
181                                value = hex(value)
182
183                if minmax.minValue == '0' and minmax.maxValue == '1' and prop.p["type"].find('~') == -1:
184                        var = tk.BooleanVar()
185                        widget = tk.Checkbutton(master=master, text='', onvalue=1, offvalue=0, var=var)
186                        if value == 1:
187                                widget.select()
188                        if readonly:
189                                widget['state'] = 'disabled'
190                        widget.variable = var
191                elif prop.p["type"].find('~') > 0:
192                        options = prop.p["type"].split('~')[1:]
193                        widget = ttk.Combobox(master=master, values=options, state="readonly")
194                        widget.mapvalues = {k:v for (v, k) in enumerate(options, int(t[1]))}
195                        widget.set(options[value])
196                else:
197                        minVal = 0 if minmax.minValue is None else minmax.minValue
198                        maxVal = 100 if minmax.maxValue is None else minmax.maxValue
199                        widget = ttk.Spinbox(master=master, from_=minVal, to=maxVal)
200                        widget.set(value)
201                        if readonly:
202                                widget["state"] = "disabled"
203        elif propType[0] == 'f':
204                value = None
205                var = None
206                if len(propType) > 1:
207                        if propType[1] == 't':
208                                timestamp = float(prop.p["value"])
209                                value = datetime.fromtimestamp(timestamp).strftime(datetimeformat)
210                        elif propType[1] == 'i':
211                                var = tk.StringVar()
212                                value = float(prop.p["value"])
213                else:
214                        var = tk.StringVar()
215                        value = float(prop.p["value"])
216
217                if var:
218                        def callback(*_):
219                                val = var.get()
220                                try:
221                                        v = float(val)
222                                        if minmax.minValue and v < float(minmax.minValue):
223                                                v = float(minmax.minValue)
224                                        elif minmax.maxValue and v > float(minmax.maxValue):
225                                                v = float(minmax.maxValue)
226                                        var.set(str(v))
227                                except ValueError:
228                                        pass
229
230                        def on_validate(string):
231                                regex = re.compile(r"(\+|\-)?[0-9.]*$")
232                                result = regex.match(string)
233                                return (string == ""
234                                                or (string.count('+') <= 1
235                                                        and string.count('-') <= 1
236                                                        and string.count(',') <= 1
237                                                        and result is not None
238                                                        and result.group(0) != ""))
239
240                        var.trace_add("write", callback)
241                        widget = tk.Entry(master=master, validate="key", textvariable=var)
242                        vcmd = (widget.register(on_validate), '%P')
243                        widget.config(validatecommand=vcmd)
244                else:
245                        widget = tk.Entry(master=master)
246                widget.insert(0, value)
247                if readonly:
248                        widget["state"] = "disabled"
249        elif propType[0] == 's':
250                if t[-1].find('~') >= 0:
251                        options = t[-1].split('~')[1:]
252                        widget = ttk.Combobox(master=master)
253                        widget["values"] = options
254                        widget.set(prop.p["value"])
255                else:
256                        multiline = True if len(t) > 1 and t[1] != '0' else False
257                        if multiline:
258                                widget = ScrolledText(master=master, height=1 + multiline * 3)
259                                widget.insert(tk.INSERT, prop.p["value"])
260                        else:
261                                widget = tk.Entry(master=master)
262                                widget.insert(0, prop.p["value"])
263                if readonly:
264                        widget["state"] = "disabled"
265        elif propType[0] == 'p':
266                widget = tk.Button(master=master, text=prop.p["name"], command=prop.p["value"])
267                callback = PropertyCallback(widget, prop.p["id"], propType)
268                widget.anchor('w')
269
270        if widget and propType[0] != 'p':
271                callback = PropertyCallback(widget, prop.p["id"], propType)
272                widget.anchor('w')
273
274        return widget, callback
Note: See TracBrowser for help on using the repository browser.