[030aeb]: / dosma / gui / gui_utils / gui_utils.py

Download this file

204 lines (147 with data), 6.1 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# bool --> booltype, default, checkbox
# filepath (str) --> stringtype, default, load file
# str --> stringtype, default, textbox
# float/int -->
import inspect
import tkinter as tk
from tkinter import ttk
from dosma.cli import BASIC_TYPES
from dosma.gui.gui_utils.filedialog_reader import FileDialogReader
class TextWithVar(tk.Text):
"""A text widget that accepts a 'textvariable' option"""
def __init__(self, parent, *args, **kwargs):
try:
self._textvariable = kwargs.pop("textvariable")
except KeyError:
self._textvariable = None
tk.Text.__init__(self, parent, *args, **kwargs)
# if the variable has data in it, use it to initialize
# the widget
if self._textvariable is not None:
self.insert("1.0", self._textvariable.get())
# this defines an internal proxy which generates a
# virtual event whenever text is inserted or deleted
self.tk.eval(
"""
proc widget_proxy {widget widget_command args} {
# call the real tk widget command with the real args
set result [uplevel [linsert $args 0 $widget_command]]
# if the contents changed, generate an event we can bind to
if {([lindex $args 0] in {insert replace delete})} {
event generate $widget <<Change>> -when tail
}
# return the result from the real widget command
return $result
}
"""
)
# this replaces the underlying widget with the proxy
self.tk.eval(
"""
rename {widget} _{widget}
interp alias {{}} ::{widget} {{}} widget_proxy {widget} _{widget}
""".format(
widget=str(self)
)
)
# set up a binding to update the variable whenever
# the widget changes
self.bind("<<Change>>", self._on_widget_change)
# set up a trace to update the text widget when the
# variable changes
if self._textvariable is not None:
self._textvariable.trace("wu", self._on_var_change)
def _on_var_change(self, *args):
"""Change the text widget when the associated textvariable changes"""
# only change the widget if something actually
# changed, otherwise we'll get into an endless
# loop
text_current = self.get("1.0", "end-1c")
var_current = self._textvariable.get()
if text_current != var_current:
self.delete("1.0", "end")
self.insert("1.0", var_current)
def _on_widget_change(self, event=None):
"""Change the variable when the widget changes"""
if self._textvariable is not None:
self._textvariable.set(self.get("1.0", "end-1c"))
class Filepath(str):
pass
TYPE_CAST = {bool: tk.BooleanVar, str: tk.StringVar, int: tk.IntVar, float: tk.DoubleVar}
def contains_filepath_keywords(param_name: str):
fp_keywords = ["dir", "path", "directory", "file"]
for k in fp_keywords:
if k in param_name:
return True
return False
def convert_base_type_to_gui(param_name, param_type, param_default, root, **kwargs):
balloon = None
param_help = ""
if "balloon" in kwargs:
balloon = kwargs.get("balloon")
if "param_help" in kwargs:
param_help = kwargs.get("param_help")
assert param_type in BASIC_TYPES, "type %s not in BASIC_TYPES" % param_type
# add default value to param help
has_default = param_default is not inspect._empty and param_default is not None
type_var = TYPE_CAST[param_type]()
if has_default:
type_var.set(param_default)
is_filepath = (
(param_type is str) and (not has_default) and contains_filepath_keywords(param_name)
)
hbox = None
if is_filepath:
hbox = format_filepath_gui(root, param_name, type_var)
elif param_type is bool:
hbox = format_bool_gui(root, param_name, type_var)
elif param_type is str:
if "options" in kwargs:
hbox = format_list_gui(root, param_name, type_var, **kwargs)
else:
hbox = format_str_gui(root, param_name, type_var)
# TODO: Add suport for float and int values.
if hbox:
if balloon and param_help:
balloon.bind(hbox, param_help)
return type_var
def format_filepath_gui(root, label, type_var, **kwargs):
hbox = tk.Frame(root)
hbox.pack(side="top", anchor="nw")
_label = tk.Label(hbox, text="%s: " % label)
_label.pack(side="left", anchor="nw", padx=5)
t = tk.Label(hbox, textvariable=type_var)
t.pack(side="left", anchor="nw", padx=5)
fd = FileDialogReader(type_var)
fd_prompt = "Load %s" % label.lower()
f_action = fd.get_filepath
if "dir" in label.lower():
f_action = fd.get_dirpath
b = ttk.Button(root, text=fd_prompt, command=lambda: f_action(title=fd_prompt))
b.pack(anchor="nw", pady=1)
return hbox
def format_str_gui(root, label, type_var, **kwargs):
hbox = tk.Frame(root)
hbox.pack(side="top", anchor="nw")
_label = tk.Label(hbox, text="%s: " % label)
_label.pack(side="left", anchor="nw", padx=5)
t = TextWithVar(hbox, textvariable=type_var)
t.pack(side="left", anchor="nw", padx=5)
return hbox
def format_bool_gui(root, label, type_var, **kwargs):
hbox = tk.Frame(root)
hbox.pack(side="top", anchor="nw")
_label = tk.Label(hbox, text="%s: " % label)
_label.pack(side="left", anchor="nw", padx=5)
t = tk.Checkbutton(hbox, variable=type_var)
t.pack(side="left", anchor="nw", padx=5)
return hbox
def format_list_gui(root, label, type_var, **kwargs):
options = kwargs.get("options")
hbox = tk.Frame(root)
hbox.pack(side="top", anchor="nw")
_label = tk.Label(hbox, text="%s: " % label)
_label.pack(side="left", anchor="nw", padx=5)
t = tk.OptionMenu(hbox, type_var, *options)
t.pack(side="left", anchor="nw", padx=5)
return hbox