simba-uw-tf-dev 4.7.8__py3-none-any.whl → 4.7.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of simba-uw-tf-dev might be problematic. Click here for more details.
- simba/SimBA.py +1 -1
- simba/assets/lookups/tooptips.json +21 -1
- simba/ui/blob_tracker_ui.py +25 -26
- simba/ui/pop_ups/batch_preprocess_pop_up.py +1 -1
- simba/ui/pop_ups/initialize_blob_tracking_pop_up.py +2 -2
- simba/ui/pop_ups/video_processing_pop_up.py +2 -2
- simba/ui/tkinter_functions.py +1091 -1091
- simba/utils/read_write.py +1 -1
- simba/video_processors/batch_process_menus.py +5 -2
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/METADATA +1 -1
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/RECORD +15 -15
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/LICENSE +0 -0
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/WHEEL +0 -0
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/entry_points.txt +0 -0
- {simba_uw_tf_dev-4.7.8.dist-info → simba_uw_tf_dev-4.7.9.dist-info}/top_level.txt +0 -0
simba/ui/tkinter_functions.py
CHANGED
|
@@ -1,1091 +1,1091 @@
|
|
|
1
|
-
__author__ = "Simon Nilsson; sronilsson@gmail.com"
|
|
2
|
-
|
|
3
|
-
import os.path
|
|
4
|
-
import platform
|
|
5
|
-
import threading
|
|
6
|
-
import tkinter
|
|
7
|
-
import tkinter as tk
|
|
8
|
-
import webbrowser
|
|
9
|
-
from copy import copy, deepcopy
|
|
10
|
-
from tkinter import *
|
|
11
|
-
from tkinter.filedialog import askdirectory, askopenfilename
|
|
12
|
-
from tkinter.ttk import Combobox
|
|
13
|
-
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
|
14
|
-
|
|
15
|
-
try:
|
|
16
|
-
from typing import Literal
|
|
17
|
-
except:
|
|
18
|
-
from typing_extensions import Literal
|
|
19
|
-
|
|
20
|
-
import numpy as np
|
|
21
|
-
import PIL.Image
|
|
22
|
-
from PIL import ImageTk
|
|
23
|
-
|
|
24
|
-
from simba.utils.
|
|
25
|
-
from simba.utils.
|
|
26
|
-
from simba.utils.
|
|
27
|
-
from simba.utils.
|
|
28
|
-
|
|
29
|
-
MENU_ICONS = get_icons_paths()
|
|
30
|
-
TOOLTIPS = get_tooltips()
|
|
31
|
-
|
|
32
|
-
def onMousewheel(event, canvas):
|
|
33
|
-
try:
|
|
34
|
-
scrollSpeed = event.delta
|
|
35
|
-
if platform.system() == "Darwin":
|
|
36
|
-
scrollSpeed = event.delta
|
|
37
|
-
elif platform.system() == "Windows":
|
|
38
|
-
scrollSpeed = int(event.delta / 120)
|
|
39
|
-
canvas.yview_scroll(-1 * (scrollSpeed), "units")
|
|
40
|
-
except:
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def bindToMousewheel(event, canvas):
|
|
45
|
-
canvas.bind_all("<MouseWheel>", lambda event: onMousewheel(event, canvas))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def unbindToMousewheel(event, canvas):
|
|
49
|
-
canvas.unbind_all("<MouseWheel>")
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def onFrameConfigure(canvas):
|
|
53
|
-
"""Reset the scroll region to encompass the inner frame"""
|
|
54
|
-
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def on_mouse_scroll(event, canvas):
|
|
58
|
-
if event.delta:
|
|
59
|
-
canvas.yview_scroll(-1 * (event.delta // 120), "units")
|
|
60
|
-
elif event.num == 4:
|
|
61
|
-
canvas.yview_scroll(-1, "units")
|
|
62
|
-
elif event.num == 5:
|
|
63
|
-
canvas.yview_scroll(1, "units")
|
|
64
|
-
|
|
65
|
-
def hxtScrollbar(master):
|
|
66
|
-
canvas = tk.Canvas(master, borderwidth=0, bg="#f0f0f0", relief="flat")
|
|
67
|
-
frame = tk.Frame(canvas, bg="#f0f0f0", bd=0)
|
|
68
|
-
|
|
69
|
-
vsb = tk.Scrollbar(master, orient="vertical", command=canvas.yview, bg="#cccccc", width=20)
|
|
70
|
-
hsb = tk.Scrollbar(master, orient="horizontal", command=canvas.xview, bg="#cccccc")
|
|
71
|
-
|
|
72
|
-
canvas.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
|
|
73
|
-
|
|
74
|
-
canvas.pack(side="left", fill="both", expand=True, padx=0, pady=0)
|
|
75
|
-
vsb.pack(side="right", fill="y", padx=(0, 0))
|
|
76
|
-
hsb.pack(side="bottom", fill="x", pady=(0, 0))
|
|
77
|
-
|
|
78
|
-
canvas.create_window((0, 0), window=frame, anchor="nw")
|
|
79
|
-
|
|
80
|
-
def on_frame_configure(event):
|
|
81
|
-
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
82
|
-
|
|
83
|
-
frame.bind("<Configure>", on_frame_configure)
|
|
84
|
-
|
|
85
|
-
def _on_mousewheel(event):
|
|
86
|
-
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
|
87
|
-
|
|
88
|
-
def _on_key(event):
|
|
89
|
-
if event.keysym == "Up":
|
|
90
|
-
canvas.yview_scroll(-1, "units")
|
|
91
|
-
elif event.keysym == "Down":
|
|
92
|
-
canvas.yview_scroll(1, "units")
|
|
93
|
-
elif event.keysym == "Page_Up":
|
|
94
|
-
canvas.yview_scroll(-1, "pages")
|
|
95
|
-
elif event.keysym == "Page_Down":
|
|
96
|
-
canvas.yview_scroll(1, "pages")
|
|
97
|
-
|
|
98
|
-
# Focus & bindings when mouse enters
|
|
99
|
-
def _on_enter(_):
|
|
100
|
-
frame.focus_set()
|
|
101
|
-
frame.bind_all("<MouseWheel>", _on_mousewheel)
|
|
102
|
-
frame.bind_all("<Key>", _on_key)
|
|
103
|
-
|
|
104
|
-
# Clean up when mouse leaves
|
|
105
|
-
# def _on_leave(_):
|
|
106
|
-
# frame.unbind_all("<MouseWheel>")
|
|
107
|
-
# frame.unbind_all("<Key>")
|
|
108
|
-
|
|
109
|
-
frame.bind("<Enter>", _on_enter)
|
|
110
|
-
#frame.bind("<Leave>", _on_leave)
|
|
111
|
-
|
|
112
|
-
return frame
|
|
113
|
-
|
|
114
|
-
def form_validator_is_numeric(inStr, acttyp):
|
|
115
|
-
if acttyp == "1": # insert
|
|
116
|
-
if not inStr.isdigit():
|
|
117
|
-
return False
|
|
118
|
-
return True
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
class DropDownMenu(Frame):
|
|
122
|
-
"""
|
|
123
|
-
Legacy, use :func:`simba.ui.tkinter_functions.SimBADropDown`.
|
|
124
|
-
"""
|
|
125
|
-
def __init__(self,
|
|
126
|
-
parent=None,
|
|
127
|
-
dropdownLabel="",
|
|
128
|
-
choice_dict=None,
|
|
129
|
-
labelwidth="",
|
|
130
|
-
com=None,
|
|
131
|
-
val: Optional[Any] = None,
|
|
132
|
-
**kw):
|
|
133
|
-
|
|
134
|
-
Frame.__init__(self, master=parent, **kw)
|
|
135
|
-
self.dropdownvar = StringVar()
|
|
136
|
-
self.lblName = Label(self, text=dropdownLabel, width=labelwidth, anchor=W, font=Formats.FONT_REGULAR.value)
|
|
137
|
-
self.lblName.grid(row=0, column=0)
|
|
138
|
-
self.choices = choice_dict
|
|
139
|
-
self.popupMenu = OptionMenu(self, self.dropdownvar, *self.choices, command=com)
|
|
140
|
-
self.popupMenu.grid(row=0, column=1)
|
|
141
|
-
if val is not None:
|
|
142
|
-
self.setChoices(val)
|
|
143
|
-
|
|
144
|
-
def getChoices(self):
|
|
145
|
-
return self.dropdownvar.get()
|
|
146
|
-
|
|
147
|
-
def setChoices(self, choice):
|
|
148
|
-
self.dropdownvar.set(choice)
|
|
149
|
-
|
|
150
|
-
def enable(self):
|
|
151
|
-
self.popupMenu.configure(state="normal")
|
|
152
|
-
|
|
153
|
-
def disable(self):
|
|
154
|
-
self.popupMenu.configure(state="disable")
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
class SimBAScaleBar(Frame):
|
|
158
|
-
def __init__(self,
|
|
159
|
-
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
160
|
-
label: Optional[str] = None,
|
|
161
|
-
label_width: Optional[int] = None,
|
|
162
|
-
orient: Literal['horizontal', 'vertical'] = HORIZONTAL,
|
|
163
|
-
length: int = 200,
|
|
164
|
-
value: Optional[int] = 95,
|
|
165
|
-
showvalue: bool = True,
|
|
166
|
-
label_clr: str = 'black',
|
|
167
|
-
lbl_font: tuple = Formats.FONT_REGULAR.value,
|
|
168
|
-
scale_font: tuple = Formats.FONT_REGULAR_ITALICS.value,
|
|
169
|
-
lbl_img: Optional[str] = None,
|
|
170
|
-
from_: int = -1,
|
|
171
|
-
resolution: int = 1,
|
|
172
|
-
to: int = 100,
|
|
173
|
-
tickinterval: Optional[int] = None,
|
|
174
|
-
troughcolor: Optional[str] = None,
|
|
175
|
-
activebackground: Optional[str] = None,
|
|
176
|
-
sliderrelief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = 'flat'):
|
|
177
|
-
|
|
178
|
-
super().__init__(master=parent)
|
|
179
|
-
self.columnconfigure(0, weight=0)
|
|
180
|
-
self.columnconfigure(1, weight=0)
|
|
181
|
-
if lbl_img is not None:
|
|
182
|
-
self.columnconfigure(2, weight=0)
|
|
183
|
-
self.lbl_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=None, font=lbl_font, width=None, anchor='w', img=lbl_img, compound=None)
|
|
184
|
-
self.lbl_lbl.grid(row=0, column=0, sticky=SW)
|
|
185
|
-
else:
|
|
186
|
-
self.lbl_lbl = None
|
|
187
|
-
|
|
188
|
-
self.scale = Scale(self,
|
|
189
|
-
from_=from_,
|
|
190
|
-
to=to,
|
|
191
|
-
orient=orient,
|
|
192
|
-
length=length,
|
|
193
|
-
font=scale_font,
|
|
194
|
-
sliderrelief=sliderrelief,
|
|
195
|
-
troughcolor=troughcolor,
|
|
196
|
-
tickinterval=tickinterval,
|
|
197
|
-
resolution=resolution,
|
|
198
|
-
showvalue=showvalue,
|
|
199
|
-
activebackground=activebackground)
|
|
200
|
-
|
|
201
|
-
if label is not None:
|
|
202
|
-
self.lbl = SimBALabel(parent=self, txt=label, font=lbl_font, txt_clr=label_clr, width=label_width)
|
|
203
|
-
self.lbl.grid(row=0, column=1, sticky=SW)
|
|
204
|
-
|
|
205
|
-
self.scale.grid(row=0, column=2, sticky=NW)
|
|
206
|
-
if value is not None:
|
|
207
|
-
self.set_value(value=value)
|
|
208
|
-
|
|
209
|
-
def set_value(self, value: int):
|
|
210
|
-
self.scale.set(value)
|
|
211
|
-
|
|
212
|
-
def get_value(self) -> Union[int, float]:
|
|
213
|
-
return self.scale.get()
|
|
214
|
-
|
|
215
|
-
def get(self) -> Union[int, float]:
|
|
216
|
-
## Alternative for ``get_value`` for legacy reasons.
|
|
217
|
-
return self.scale.get()
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
class Entry_Box(Frame):
|
|
223
|
-
def __init__(self,
|
|
224
|
-
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
225
|
-
fileDescription: Optional[str] = "",
|
|
226
|
-
labelwidth: Union[int, str] = None,
|
|
227
|
-
label_bg_clr: Optional[str] = None,
|
|
228
|
-
status: Literal["normal", "disabled", "readonly"] = 'normal',
|
|
229
|
-
validation: Optional[Union[Callable, str]] = None,
|
|
230
|
-
entry_box_width: Union[int, str] = None,
|
|
231
|
-
entry_box_clr: Optional[str] = None,
|
|
232
|
-
img: Optional[str] = None,
|
|
233
|
-
value: Optional[Any] = None,
|
|
234
|
-
label_font: tuple = Formats.FONT_REGULAR.value,
|
|
235
|
-
entry_font: tuple = Formats.FONT_REGULAR.value,
|
|
236
|
-
tooltip_key: Optional[str] = None,
|
|
237
|
-
justify: Literal["left", "center", "right"] = 'left',
|
|
238
|
-
cmd: Optional[Callable] = None,
|
|
239
|
-
**kw):
|
|
240
|
-
|
|
241
|
-
super(Entry_Box, self).__init__(master=parent)
|
|
242
|
-
self.validation_methods = {"numeric": (self.register(form_validator_is_numeric), "%P", "%d")}
|
|
243
|
-
self.status = status if status is not None else NORMAL
|
|
244
|
-
self.labelname = fileDescription
|
|
245
|
-
Frame.__init__(self, master=parent, **kw)
|
|
246
|
-
self.columnconfigure(0, weight=0)
|
|
247
|
-
self.columnconfigure(1, weight=0)
|
|
248
|
-
if img is not None:
|
|
249
|
-
self.columnconfigure(2, weight=0)
|
|
250
|
-
self.entrybox_img_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=None, anchor='w', img=img, compound=None)
|
|
251
|
-
self.entrybox_img_lbl.grid(row=0, column=0, sticky="w")
|
|
252
|
-
else:
|
|
253
|
-
self.entrybox_img_lbl = None
|
|
254
|
-
self.filePath = StringVar()
|
|
255
|
-
self.lblName = Label(self, text=fileDescription, width=labelwidth, anchor=W, font=label_font, bg=label_bg_clr)
|
|
256
|
-
self.lblName.grid(row=0, column=1)
|
|
257
|
-
if tooltip_key in TOOLTIPS.keys():
|
|
258
|
-
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
259
|
-
if not entry_box_width:
|
|
260
|
-
self.entPath = Entry(self, textvariable=self.filePath, state=self.status, validate="key", validatecommand=self.validation_methods.get(validation, None), font=entry_font, justify=justify, bg=entry_box_clr)
|
|
261
|
-
else:
|
|
262
|
-
self.entPath = Entry(self, textvariable=self.filePath, state=self.status, width=entry_box_width, validate="key", font=entry_font, justify=justify, validatecommand=self.validation_methods.get(validation, None), bg=entry_box_clr)
|
|
263
|
-
self.entPath.grid(row=0, column=2)
|
|
264
|
-
if value is not None:
|
|
265
|
-
self.entry_set(val=value)
|
|
266
|
-
if cmd is not None:
|
|
267
|
-
self.bind_combobox_keys()
|
|
268
|
-
self.cmd = cmd
|
|
269
|
-
|
|
270
|
-
def bind_combobox_keys(self):
|
|
271
|
-
self.entPath.bind("<KeyRelease>", self.run_cmd)
|
|
272
|
-
|
|
273
|
-
@property
|
|
274
|
-
def entry_get(self):
|
|
275
|
-
return self.entPath.get()
|
|
276
|
-
|
|
277
|
-
def entry_set(self, val):
|
|
278
|
-
self.filePath.set(val)
|
|
279
|
-
|
|
280
|
-
def set_state(self, setstatus):
|
|
281
|
-
self.entPath.config(state=setstatus)
|
|
282
|
-
|
|
283
|
-
def destroy(self):
|
|
284
|
-
try:
|
|
285
|
-
if self.entrybox_img_lbl is not None:
|
|
286
|
-
self.entrybox_img_lbl.destroy()
|
|
287
|
-
self.lblName.destroy()
|
|
288
|
-
self.entPath.destroy()
|
|
289
|
-
except:
|
|
290
|
-
pass
|
|
291
|
-
|
|
292
|
-
def run_cmd(self, x):
|
|
293
|
-
self.cmd(self.entry_get.strip())
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
class FolderSelect(Frame):
|
|
298
|
-
def __init__(self,
|
|
299
|
-
parent: Union[Frame, LabelFrame, Canvas, Toplevel],
|
|
300
|
-
folderDescription: Optional[str] = "",
|
|
301
|
-
color: Optional[str] = None,
|
|
302
|
-
label_bg_clr: Optional[str] = None,
|
|
303
|
-
font: Tuple = Formats.FONT_REGULAR.value,
|
|
304
|
-
title: Optional[str] = "",
|
|
305
|
-
lblwidth: Optional[int] = 0,
|
|
306
|
-
bg_clr: Optional[str] = 'white',
|
|
307
|
-
entry_width: Optional[int] = 20,
|
|
308
|
-
lbl_icon: Optional[str] = None,
|
|
309
|
-
initialdir: Optional[Union[str, os.PathLike]] = None,
|
|
310
|
-
tooltip_txt: Optional[str] = None,
|
|
311
|
-
tooltip_key: Optional[str] = None,
|
|
312
|
-
**kw):
|
|
313
|
-
|
|
314
|
-
self.title, self.initialdir = title, initialdir
|
|
315
|
-
self.color = color if color is not None else "black"
|
|
316
|
-
self.lblwidth = lblwidth if lblwidth is not None else 0
|
|
317
|
-
self.parent = parent
|
|
318
|
-
Frame.__init__(self, master=parent, **kw)
|
|
319
|
-
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse_small"]["icon_path"]))
|
|
320
|
-
self.columnconfigure(0, weight=0)
|
|
321
|
-
self.columnconfigure(1, weight=0)
|
|
322
|
-
self.columnconfigure(2, weight=0)
|
|
323
|
-
if lbl_icon is not None:
|
|
324
|
-
self.columnconfigure(3, weight=0)
|
|
325
|
-
self.lbl_icon = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=font, width=None, anchor='w', img=lbl_icon, compound=None)
|
|
326
|
-
self.lbl_icon.grid(row=0, column=0, sticky="w")
|
|
327
|
-
else:
|
|
328
|
-
self.lbl_icon = None
|
|
329
|
-
self.folderPath = StringVar()
|
|
330
|
-
self.lblName = Label(self, text=folderDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=font, bg=label_bg_clr)
|
|
331
|
-
self.lblName.grid(row=0, column=1, sticky=NW)
|
|
332
|
-
self.entPath = Label(self, textvariable=self.folderPath, relief=SUNKEN, font=font, bg=bg_clr, width=entry_width)
|
|
333
|
-
self.entPath.grid(row=0, column=2, sticky=NW)
|
|
334
|
-
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FOLDER_BTN_TEXT.value, font=font, cmd=self.setFolderPath, img=browse_icon)
|
|
335
|
-
self.btnFind.image = browse_icon
|
|
336
|
-
self.btnFind.grid(row=0, column=3, sticky=NW)
|
|
337
|
-
self.folderPath.set("No folder selected")
|
|
338
|
-
if tooltip_txt is not None and isinstance(tooltip_txt, str):
|
|
339
|
-
CreateToolTip(widget=self.lblName, text=tooltip_txt)
|
|
340
|
-
elif tooltip_key in TOOLTIPS.keys():
|
|
341
|
-
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
342
|
-
|
|
343
|
-
def setFolderPath(self):
|
|
344
|
-
if self.initialdir is not None:
|
|
345
|
-
if not os.path.isdir(self.initialdir):
|
|
346
|
-
self.initialdir = None
|
|
347
|
-
else:
|
|
348
|
-
pass
|
|
349
|
-
folder_selected = askdirectory(title=str(self.title), parent=self.parent, initialdir=self.initialdir)
|
|
350
|
-
if folder_selected:
|
|
351
|
-
self.folderPath.set(folder_selected)
|
|
352
|
-
self.entPath.configure(width=len(folder_selected)+10)
|
|
353
|
-
else:
|
|
354
|
-
self.folderPath.set("No folder selected")
|
|
355
|
-
self.entPath.configure(width=len("No folder selected")+10)
|
|
356
|
-
|
|
357
|
-
@property
|
|
358
|
-
def folder_path(self):
|
|
359
|
-
return self.folderPath.get()
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
class ToolTip(object):
|
|
363
|
-
|
|
364
|
-
def __init__(self, widget):
|
|
365
|
-
self.widget = widget
|
|
366
|
-
self.tipwindow = None
|
|
367
|
-
self.id = None
|
|
368
|
-
self.x = self.y = 0
|
|
369
|
-
|
|
370
|
-
def showtip(self, text):
|
|
371
|
-
"Display text in tooltip window"
|
|
372
|
-
self.text = text
|
|
373
|
-
if self.tipwindow or not self.text:
|
|
374
|
-
return
|
|
375
|
-
x, y, cx, cy = self.widget.bbox("insert")
|
|
376
|
-
x = x + self.widget.winfo_rootx() + 20
|
|
377
|
-
y = y + cy + self.widget.winfo_rooty() + 20
|
|
378
|
-
self.tipwindow = tw = Toplevel(self.widget)
|
|
379
|
-
tw.wm_overrideredirect(1)
|
|
380
|
-
tw.wm_geometry("+%d+%d" % (x, y))
|
|
381
|
-
label = Label(tw, text=self.text, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, font=Formats.FONT_REGULAR.value)
|
|
382
|
-
label.pack(ipadx=1)
|
|
383
|
-
|
|
384
|
-
def hidetip(self):
|
|
385
|
-
tw = self.tipwindow
|
|
386
|
-
self.tipwindow = None
|
|
387
|
-
if tw:
|
|
388
|
-
tw.destroy()
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
def CreateToolTip(widget, text):
|
|
392
|
-
toolTip = ToolTip(widget)
|
|
393
|
-
def enter(event):
|
|
394
|
-
toolTip.showtip(text)
|
|
395
|
-
|
|
396
|
-
def leave(event):
|
|
397
|
-
toolTip.hidetip()
|
|
398
|
-
|
|
399
|
-
widget.bind("<Enter>", enter)
|
|
400
|
-
widget.bind("<Leave>", leave)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
def CreateLabelFrameWithIcon(parent: Union[Toplevel, LabelFrame, Canvas, Frame],
|
|
404
|
-
header: str,
|
|
405
|
-
icon_name: str,
|
|
406
|
-
padx: Optional[int] = None,
|
|
407
|
-
pady: Optional[int] = None,
|
|
408
|
-
relief: str = 'solid',
|
|
409
|
-
width: Optional[int] = None,
|
|
410
|
-
bg: Optional[str] = None,
|
|
411
|
-
font: tuple = Formats.FONT_HEADER.value,
|
|
412
|
-
icon_link: Optional[Union[str, None]] = LabelFrame,
|
|
413
|
-
tooltip_key: Optional[str] = None):
|
|
414
|
-
|
|
415
|
-
if icon_name in MENU_ICONS.keys():
|
|
416
|
-
icon = PIL.Image.open(MENU_ICONS[icon_name]["icon_path"])
|
|
417
|
-
icon = ImageTk.PhotoImage(icon)
|
|
418
|
-
else:
|
|
419
|
-
icon = None
|
|
420
|
-
frm = Frame(parent, bg=bg)
|
|
421
|
-
|
|
422
|
-
label_image = Label(frm, image=icon, bg=bg)
|
|
423
|
-
label_image.image = icon
|
|
424
|
-
if icon_link:
|
|
425
|
-
label_image.bind("<Button-1>", lambda e: callback(icon_link))
|
|
426
|
-
label_image.grid(row=0, column=0)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
label_text = Label(frm, text=header, font=font, bg=bg)
|
|
430
|
-
label_text.grid(row=0, column=1)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
lbl_frm = LabelFrame(parent, labelwidget=frm, relief=relief, width=width, padx=padx or 0, pady=pady or 0)
|
|
435
|
-
|
|
436
|
-
if tooltip_key in TOOLTIPS.keys():
|
|
437
|
-
CreateToolTip(widget=label_text, text=TOOLTIPS[tooltip_key])
|
|
438
|
-
|
|
439
|
-
if bg is not None:
|
|
440
|
-
lbl_frm.configure(bg=bg)
|
|
441
|
-
return lbl_frm
|
|
442
|
-
|
|
443
|
-
def callback(url):
|
|
444
|
-
webbrowser.open_new(url)
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
def create_scalebar(
|
|
448
|
-
parent: Frame, name: str, min: int, max: int, cmd: object or None = None
|
|
449
|
-
):
|
|
450
|
-
|
|
451
|
-
scale = Scale(
|
|
452
|
-
parent,
|
|
453
|
-
from_=min,
|
|
454
|
-
to=max,
|
|
455
|
-
orient=HORIZONTAL,
|
|
456
|
-
length=200,
|
|
457
|
-
label=name,
|
|
458
|
-
command=cmd,
|
|
459
|
-
)
|
|
460
|
-
scale.set(0)
|
|
461
|
-
return scale
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
class TwoOptionQuestionPopUp(object):
|
|
465
|
-
"""
|
|
466
|
-
Helpe to create a two-option question tkinter pop up window (e.g., YES/NO).
|
|
467
|
-
|
|
468
|
-
:parameter str question: Question to present to the user. E.g., ``DO YOU WANT TO PROCEED?``.
|
|
469
|
-
:parameter str option_one: The first user option. E.g., ``YES``.
|
|
470
|
-
:parameter str option_one: The second user option. E.g., ``NO``.
|
|
471
|
-
:parameter str title: The window titel in the top banner of the pop-up. E.g., ``A QUESTION FOR YOU!``.
|
|
472
|
-
:parameter Optional[str] link: If not None, then a link to documentation presenting background info about the user choices.
|
|
473
|
-
"""
|
|
474
|
-
|
|
475
|
-
def __init__(self,
|
|
476
|
-
question: str,
|
|
477
|
-
option_one: str,
|
|
478
|
-
option_two: str,
|
|
479
|
-
title: str,
|
|
480
|
-
link: Optional[str] = None):
|
|
481
|
-
|
|
482
|
-
self.main_frm = Toplevel()
|
|
483
|
-
self.main_frm.geometry("600x200")
|
|
484
|
-
self.main_frm.title(title)
|
|
485
|
-
menu_icons = get_menu_icons()
|
|
486
|
-
self.main_frm.iconphoto(False, menu_icons['question_mark']["img"])
|
|
487
|
-
|
|
488
|
-
#img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS['question_mark']["icon_path"]))
|
|
489
|
-
|
|
490
|
-
question_frm = Frame(self.main_frm)
|
|
491
|
-
question_frm.pack(expand=True, fill="both")
|
|
492
|
-
Label(question_frm, text=question, font=Formats.LABELFRAME_HEADER_FORMAT.value).pack()
|
|
493
|
-
|
|
494
|
-
button_one = SimbaButton(parent=question_frm, txt=option_one, txt_clr="blue", bg_clr="lightgrey", img='check_blue', cmd=self.run, cmd_kwargs={'selected_option': lambda: option_one}, font=Formats.FONT_LARGE.value, hover_font=Formats.FONT_LARGE_BOLD.value)
|
|
495
|
-
button_two = SimbaButton(parent=question_frm, txt=option_two, txt_clr="red", bg_clr="lightgrey", img='close', cmd=self.run, cmd_kwargs={'selected_option': lambda: option_two}, font=Formats.FONT_LARGE.value, hover_font=Formats.FONT_LARGE_BOLD.value)
|
|
496
|
-
if link:
|
|
497
|
-
link_lbl = Label(question_frm, text="Click here for more information.", cursor="hand2", fg="blue")
|
|
498
|
-
link_lbl.bind("<Button-1>", lambda e: callback(link))
|
|
499
|
-
link_lbl.place(relx=0.5, rely=0.30, anchor=CENTER)
|
|
500
|
-
button_one.place(relx=0.5, rely=0.50, anchor=CENTER)
|
|
501
|
-
button_two.place(relx=0.5, rely=0.80, anchor=CENTER)
|
|
502
|
-
self.main_frm.wait_window()
|
|
503
|
-
|
|
504
|
-
def run(self, selected_option):
|
|
505
|
-
self.selected_option = selected_option
|
|
506
|
-
self.main_frm.destroy()
|
|
507
|
-
|
|
508
|
-
def SimbaButton(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
509
|
-
txt: str,
|
|
510
|
-
txt_clr: Optional[str] = 'black',
|
|
511
|
-
bg_clr: Optional[str] = '#f0f0f0',
|
|
512
|
-
active_bg_clr: Optional[str] = None,
|
|
513
|
-
hover_bg_clr: Optional[str] = Formats.BTN_HOVER_CLR.value,
|
|
514
|
-
hover_font: Optional[Tuple] = Formats.FONT_REGULAR_BOLD.value,
|
|
515
|
-
font: Optional[Tuple] = Formats.FONT_REGULAR.value,
|
|
516
|
-
width: Optional[int] = None,
|
|
517
|
-
height: Optional[int] = None,
|
|
518
|
-
compound: Optional[str] = 'left',
|
|
519
|
-
img: Optional[Union[ImageTk.PhotoImage, str]] = None,
|
|
520
|
-
cmd: Optional[Callable] = None,
|
|
521
|
-
cmd_kwargs: Optional[Dict[Any, Any]] = None,
|
|
522
|
-
enabled: Optional[bool] = True,
|
|
523
|
-
anchor: str = 'w',
|
|
524
|
-
thread: Optional[bool] = False,
|
|
525
|
-
tooltip_txt: Optional[str] = None) -> Button:
|
|
526
|
-
def on_enter(e):
|
|
527
|
-
e.widget.config(bg=hover_bg_clr, font=hover_font)
|
|
528
|
-
|
|
529
|
-
def on_leave(e):
|
|
530
|
-
e.widget.config(bg=bg_clr, font=font)
|
|
531
|
-
|
|
532
|
-
# if hover_font != font:
|
|
533
|
-
# hover_font = copy(font)
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
if isinstance(img, str):
|
|
538
|
-
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
539
|
-
|
|
540
|
-
if cmd_kwargs is None:
|
|
541
|
-
cmd_kwargs = {}
|
|
542
|
-
|
|
543
|
-
def execute_command():
|
|
544
|
-
if cmd:
|
|
545
|
-
evaluated_kwargs = {k: (v() if callable(v) else v) for k, v in cmd_kwargs.items()}
|
|
546
|
-
cmd(**evaluated_kwargs)
|
|
547
|
-
|
|
548
|
-
if cmd is not None:
|
|
549
|
-
if thread:
|
|
550
|
-
command = lambda: threading.Thread(target=execute_command).start()
|
|
551
|
-
else:
|
|
552
|
-
command = execute_command
|
|
553
|
-
else:
|
|
554
|
-
command = None
|
|
555
|
-
|
|
556
|
-
btn = Button(master=parent,
|
|
557
|
-
text=txt,
|
|
558
|
-
compound=compound,
|
|
559
|
-
image=img,
|
|
560
|
-
relief=RAISED,
|
|
561
|
-
fg=txt_clr,
|
|
562
|
-
activebackground=active_bg_clr,
|
|
563
|
-
font=font,
|
|
564
|
-
bg=bg_clr,
|
|
565
|
-
anchor=anchor,
|
|
566
|
-
command=command)
|
|
567
|
-
|
|
568
|
-
if img is not None:
|
|
569
|
-
btn.image = img
|
|
570
|
-
if width is not None:
|
|
571
|
-
btn.config(width=width)
|
|
572
|
-
if height is not None:
|
|
573
|
-
btn.config(height=height)
|
|
574
|
-
if not enabled:
|
|
575
|
-
btn.config(state=DISABLED)
|
|
576
|
-
|
|
577
|
-
if hover_bg_clr is not None:
|
|
578
|
-
btn.bind("<Enter>", on_enter)
|
|
579
|
-
btn.bind("<Leave>", on_leave)
|
|
580
|
-
|
|
581
|
-
if tooltip_txt is not None:
|
|
582
|
-
CreateToolTip(widget=btn, text=tooltip_txt)
|
|
583
|
-
|
|
584
|
-
return btn
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def SimbaCheckbox(parent: Union[Frame, Toplevel, LabelFrame, Canvas],
|
|
588
|
-
txt: str,
|
|
589
|
-
txt_clr: Optional[str] = 'black',
|
|
590
|
-
bg_clr: Optional[str] = None,
|
|
591
|
-
txt_img: Optional[str] = None,
|
|
592
|
-
txt_img_location: Literal['left', 'right', 'top', 'bottom'] = RIGHT,
|
|
593
|
-
font: Optional[Tuple[str, str, int]] = Formats.FONT_REGULAR.value,
|
|
594
|
-
val: Optional[bool] = False,
|
|
595
|
-
state: Literal["disabled", 'normal'] = NORMAL,
|
|
596
|
-
indicatoron: bool = True,
|
|
597
|
-
cmd: Optional[Callable] = None,
|
|
598
|
-
tooltip_txt: Optional[str] = None,
|
|
599
|
-
tooltip_key: Optional[str] = None):
|
|
600
|
-
|
|
601
|
-
var = BooleanVar(value=False)
|
|
602
|
-
if val: var.set(True)
|
|
603
|
-
if isinstance(txt_img, str):
|
|
604
|
-
txt_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[txt_img]["icon_path"]))
|
|
605
|
-
if cmd is None:
|
|
606
|
-
cb = Checkbutton(master=parent, font=font, fg=txt_clr, image=txt_img, text=txt, compound=txt_img_location, variable=var, indicatoron=indicatoron, bg=bg_clr)
|
|
607
|
-
else:
|
|
608
|
-
cb = Checkbutton(master=parent, font=font, fg=txt_clr, image=txt_img, text=txt, compound=txt_img_location, variable=var, command=cmd, indicatoron=indicatoron, bg=bg_clr)
|
|
609
|
-
if txt_img is not None:
|
|
610
|
-
cb.image = txt_img
|
|
611
|
-
if state == DISABLED:
|
|
612
|
-
cb.configure(state=DISABLED)
|
|
613
|
-
|
|
614
|
-
if isinstance(tooltip_txt, str):
|
|
615
|
-
CreateToolTip(widget=cb, text=tooltip_txt)
|
|
616
|
-
elif isinstance(tooltip_key, str) and tooltip_key in TOOLTIPS.keys():
|
|
617
|
-
CreateToolTip(widget=cb, text=TOOLTIPS[tooltip_key])
|
|
618
|
-
|
|
619
|
-
return cb, var
|
|
620
|
-
|
|
621
|
-
def SimBALabel(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
622
|
-
txt: str,
|
|
623
|
-
txt_clr: str = 'black',
|
|
624
|
-
bg_clr: Optional[str] = None,
|
|
625
|
-
hover_fg_clr: Optional[str] = None,
|
|
626
|
-
font: tuple = Formats.FONT_REGULAR.value,
|
|
627
|
-
hover_font: Optional[Tuple] = None,
|
|
628
|
-
relief: str = FLAT,
|
|
629
|
-
compound: Optional[Literal['left', 'right', 'top', 'bottom', 'center']] = 'left',
|
|
630
|
-
justify: Optional[str] = None,
|
|
631
|
-
link: Optional[str] = None,
|
|
632
|
-
width: Optional[int] = None,
|
|
633
|
-
padx: Optional[int] = None,
|
|
634
|
-
pady: Optional[int] = None,
|
|
635
|
-
cursor: Optional[str] = None,
|
|
636
|
-
img: Optional[str] = None,
|
|
637
|
-
anchor: Optional[str] = None,
|
|
638
|
-
tooltip_key: Optional[str] = None,
|
|
639
|
-
hover_img: Optional[np.ndarray] = None):
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
def _hover_enter(e):
|
|
643
|
-
w = e.widget
|
|
644
|
-
if hover_img is not None and check_if_valid_img(data=hover_img, raise_error=False):
|
|
645
|
-
arr = np.asarray(hover_img, dtype=np.uint8)
|
|
646
|
-
if arr.ndim == 3 and arr.shape[2] == 3:
|
|
647
|
-
arr = arr[:, :, ::-1]
|
|
648
|
-
pil_img = PIL.Image.fromarray(arr)
|
|
649
|
-
photo = ImageTk.PhotoImage(pil_img)
|
|
650
|
-
tw = Toplevel(w)
|
|
651
|
-
tw.wm_overrideredirect(True)
|
|
652
|
-
tw.attributes("-topmost", True)
|
|
653
|
-
tw.wm_geometry("+%d+%d" % (w.winfo_rootx() + w.winfo_width() + 4, w.winfo_rooty()))
|
|
654
|
-
frm = Frame(tw, relief="solid", bd=2, bg="#f0f0f0")
|
|
655
|
-
frm.pack(padx=2, pady=2)
|
|
656
|
-
lbl_hover = Label(frm, image=photo, bg="#f0f0f0")
|
|
657
|
-
lbl_hover.image = photo
|
|
658
|
-
lbl_hover.pack(padx=4, pady=(4, 2))
|
|
659
|
-
caption_lbl = Label(frm, text=txt, font=font, fg=txt_clr, bg=bg_clr)
|
|
660
|
-
caption_lbl.pack(pady=(0, 4))
|
|
661
|
-
tw.lift()
|
|
662
|
-
w._hover_toplevel = tw
|
|
663
|
-
elif hover_fg_clr is not None or hover_font is not None:
|
|
664
|
-
w.config(fg=hover_fg_clr, font=hover_font)
|
|
665
|
-
|
|
666
|
-
def _hover_leave(e):
|
|
667
|
-
w = e.widget
|
|
668
|
-
if getattr(w, "_hover_toplevel", None) is not None:
|
|
669
|
-
try:
|
|
670
|
-
w._hover_toplevel.destroy()
|
|
671
|
-
except tkinter.TclError:
|
|
672
|
-
pass
|
|
673
|
-
w._hover_toplevel = None
|
|
674
|
-
if hover_fg_clr is not None or hover_font is not None:
|
|
675
|
-
w.config(fg=txt_clr, bg=bg_clr, font=font)
|
|
676
|
-
|
|
677
|
-
def on_enter(e):
|
|
678
|
-
if hover_img is not None:
|
|
679
|
-
_hover_enter(e)
|
|
680
|
-
else:
|
|
681
|
-
e.widget.config(fg=hover_fg_clr, font=hover_font)
|
|
682
|
-
|
|
683
|
-
def on_leave(e):
|
|
684
|
-
if hover_img is not None:
|
|
685
|
-
_hover_leave(e)
|
|
686
|
-
else:
|
|
687
|
-
e.widget.config(fg=txt_clr, bg=bg_clr, font=font)
|
|
688
|
-
|
|
689
|
-
anchor = 'w' if anchor is None else anchor
|
|
690
|
-
if isinstance(img, str) and img in MENU_ICONS.keys():
|
|
691
|
-
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
692
|
-
else:
|
|
693
|
-
img = None
|
|
694
|
-
if img is not None and compound is None:
|
|
695
|
-
compound = 'left'
|
|
696
|
-
|
|
697
|
-
lbl = Label(parent,
|
|
698
|
-
text=txt,
|
|
699
|
-
font=font,
|
|
700
|
-
fg=txt_clr,
|
|
701
|
-
bg=bg_clr,
|
|
702
|
-
justify=justify,
|
|
703
|
-
relief=relief,
|
|
704
|
-
cursor=cursor,
|
|
705
|
-
anchor=anchor,
|
|
706
|
-
image=img,
|
|
707
|
-
compound=compound,
|
|
708
|
-
padx=padx,
|
|
709
|
-
pady=pady)
|
|
710
|
-
|
|
711
|
-
if img is not None:
|
|
712
|
-
lbl.image = img
|
|
713
|
-
|
|
714
|
-
if width is not None:
|
|
715
|
-
lbl.configure(width=width)
|
|
716
|
-
|
|
717
|
-
if link is not None:
|
|
718
|
-
lbl.bind("<Button-1>", lambda e: callback(link))
|
|
719
|
-
|
|
720
|
-
elif tooltip_key in TOOLTIPS.keys():
|
|
721
|
-
CreateToolTip(widget=lbl, text=TOOLTIPS[tooltip_key])
|
|
722
|
-
|
|
723
|
-
if hover_font is not None or hover_fg_clr is not None or hover_img is not None:
|
|
724
|
-
lbl.bind(TkBinds.ENTER.value, on_enter)
|
|
725
|
-
lbl.bind(TkBinds.LEAVE.value, on_leave)
|
|
726
|
-
|
|
727
|
-
return lbl
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
def get_menu_icons():
|
|
731
|
-
menu_icons = copy(MENU_ICONS)
|
|
732
|
-
for k in menu_icons.keys():
|
|
733
|
-
menu_icons[k]["img"] = ImageTk.PhotoImage(image=PIL.Image.open(os.path.join(os.path.dirname(__file__), menu_icons[k]["icon_path"])))
|
|
734
|
-
return menu_icons
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
class SimBASeperator(Frame):
|
|
739
|
-
|
|
740
|
-
def __init__(self,
|
|
741
|
-
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
742
|
-
color: Optional[str] = 'black',
|
|
743
|
-
orient: Literal['horizontal', 'vertical'] = 'horizontal',
|
|
744
|
-
cursor: Optional[str] = None,
|
|
745
|
-
borderwidth: Optional[int] = None,
|
|
746
|
-
takefocus: Optional[int] = 0,
|
|
747
|
-
height: int = 1,
|
|
748
|
-
relief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = "flat"):
|
|
749
|
-
|
|
750
|
-
super().__init__(master=parent, height=height, bg=color if color is not None else "black", relief=relief, bd=borderwidth if borderwidth is not None else None)
|
|
751
|
-
style = tk.ttk.Style()
|
|
752
|
-
style_name = "SimBA.TSeparator"
|
|
753
|
-
|
|
754
|
-
style.configure(style_name,
|
|
755
|
-
background=color if color is not None else "black")
|
|
756
|
-
|
|
757
|
-
seperator = tk.ttk.Separator(self,
|
|
758
|
-
orient=orient,
|
|
759
|
-
style=style_name,
|
|
760
|
-
cursor=cursor,
|
|
761
|
-
takefocus=takefocus)
|
|
762
|
-
|
|
763
|
-
if orient == "horizontal":
|
|
764
|
-
seperator.pack(fill="x", expand=True)
|
|
765
|
-
else:
|
|
766
|
-
seperator.pack(fill="y", expand=True)
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
class SimBADropDown(Frame):
|
|
770
|
-
"""
|
|
771
|
-
Create a dropdown menu widget with optional searchable functionality.
|
|
772
|
-
|
|
773
|
-
This class creates a ttk.Combobox dropdown menu with a label, supporting both readonly and searchable modes.
|
|
774
|
-
When searchable mode is enabled, users can type to filter the dropdown options.
|
|
775
|
-
|
|
776
|
-
:param parent (Frame | Canvas | LabelFrame | Toplevel | Tk): The parent widget container.
|
|
777
|
-
:param dropdown_options (Iterable[Any] | List[Any] | Tuple[Any]): List of options to display in the dropdown menu.
|
|
778
|
-
:param label (str, optional): Text label displayed next to the dropdown. Default: None.
|
|
779
|
-
:param label_width (int, optional): Width of the label in characters. Default: None.
|
|
780
|
-
:param label_font (tuple, optional): Font tuple for the label. Default: Formats.FONT_REGULAR.value.
|
|
781
|
-
:param label_bg_clr (str, optional): Background color for the label. Default: None.
|
|
782
|
-
:param dropdown_font_size (int, optional): Font size for the dropdown text. Default: None.
|
|
783
|
-
:param justify (str): Text justification in the dropdown ('left', 'center', 'right'). Default: 'center'.
|
|
784
|
-
:param dropdown_width (int, optional): Width of the dropdown widget in characters. Default: None.
|
|
785
|
-
:param command (Callable, optional): Callback function to execute when an option is selected. Default: None.
|
|
786
|
-
:param value (Any, optional): Initial selected value for the dropdown. Default: None.
|
|
787
|
-
:param state (str, optional): Initial state of the dropdown ('normal', 'disabled'). Default: None.
|
|
788
|
-
:param searchable (bool): If True, allows typing to filter dropdown options. Default: False.
|
|
789
|
-
:param tooltip_txt (str, optional): Tooltip text to display on hover. Default: None.
|
|
790
|
-
:param tooltip_key (str, optional): Key for tooltip lookup in TOOLTIPS dictionary. For dictionary, see `simba.assets.lookups.tooptips.json`. Default: None.
|
|
791
|
-
|
|
792
|
-
:example:
|
|
793
|
-
>>> dropdown = SimBADropDown(parent=parent_frm, dropdown_options=['Option 1', 'Option 2', 'Option 3'], label='Select option:', searchable=True)
|
|
794
|
-
>>> selected = dropdown.get_value()
|
|
795
|
-
"""
|
|
796
|
-
def __init__(self,
|
|
797
|
-
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
798
|
-
dropdown_options: Union[Iterable[Any], List[Any], Tuple[Any]],
|
|
799
|
-
label: Optional[str] = None,
|
|
800
|
-
label_width: Optional[int] = None,
|
|
801
|
-
label_font: tuple = Formats.FONT_REGULAR.value,
|
|
802
|
-
label_bg_clr: Optional[str] = None,
|
|
803
|
-
dropdown_font_size: Optional[int] = None,
|
|
804
|
-
justify: Literal['left', 'right', 'center'] = 'center',
|
|
805
|
-
img: Optional[str] = None,
|
|
806
|
-
dropdown_width: Optional[int] = None,
|
|
807
|
-
command: Callable = None,
|
|
808
|
-
value: Optional[Any] = None,
|
|
809
|
-
state: Optional[str] = None,
|
|
810
|
-
searchable: bool = False,
|
|
811
|
-
tooltip_txt: Optional[str] = None,
|
|
812
|
-
tooltip_key: Optional[str] = None):
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
super().__init__(master=parent)
|
|
816
|
-
self.dropdown_var = StringVar()
|
|
817
|
-
self.columnconfigure(0, weight=0)
|
|
818
|
-
self.columnconfigure(1, weight=0)
|
|
819
|
-
if img is not None:
|
|
820
|
-
self.columnconfigure(2, weight=0)
|
|
821
|
-
self.dropdown_img_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=None, anchor='w', img=img, compound='left')
|
|
822
|
-
self.dropdown_img_lbl.grid(row=0, column=0, sticky="w")
|
|
823
|
-
else:
|
|
824
|
-
self.dropdown_img_lbl = None
|
|
825
|
-
self.dropdown_lbl = SimBALabel(parent=self, txt=label, txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=label_width, anchor='w')
|
|
826
|
-
self.dropdown_lbl.grid(row=0, column=1, sticky=NW)
|
|
827
|
-
self.dropdown_options = dropdown_options
|
|
828
|
-
self.original_options = deepcopy(self.dropdown_options)
|
|
829
|
-
self.command, self.searchable = command, searchable
|
|
830
|
-
if dropdown_font_size is None:
|
|
831
|
-
drop_down_font = None
|
|
832
|
-
else:
|
|
833
|
-
drop_down_font = ("Poppins", dropdown_font_size)
|
|
834
|
-
self.combobox_state = 'normal' if searchable else "readonly"
|
|
835
|
-
self.dropdown = Combobox(self, textvariable=self.dropdown_var, font=drop_down_font, values=self.dropdown_options, state=self.combobox_state, width=dropdown_width, justify=justify)
|
|
836
|
-
if searchable: self.bind_combobox_keys()
|
|
837
|
-
self.dropdown.grid(row=0, column=2, sticky="w")
|
|
838
|
-
if value is not None: self.set_value(value=value)
|
|
839
|
-
if command is not None:
|
|
840
|
-
self.command = command
|
|
841
|
-
self.dropdown.bind("<<ComboboxSelected>>", self.on_select)
|
|
842
|
-
if state == 'disabled':
|
|
843
|
-
self.disable()
|
|
844
|
-
if isinstance(tooltip_txt, str):
|
|
845
|
-
CreateToolTip(widget=self.dropdown_lbl, text=tooltip_txt)
|
|
846
|
-
elif tooltip_key in TOOLTIPS.keys():
|
|
847
|
-
CreateToolTip(widget=self.dropdown_lbl, text=TOOLTIPS[tooltip_key])
|
|
848
|
-
if img is not None:
|
|
849
|
-
CreateToolTip(widget=self.dropdown_img_lbl, text=TOOLTIPS[tooltip_key])
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
def set_value(self, value: Any):
|
|
853
|
-
self.dropdown_var.set(value)
|
|
854
|
-
|
|
855
|
-
def get_value(self):
|
|
856
|
-
return self.dropdown_var.get()
|
|
857
|
-
|
|
858
|
-
def enable(self):
|
|
859
|
-
self.dropdown.configure(state="normal")
|
|
860
|
-
|
|
861
|
-
def disable(self):
|
|
862
|
-
self.dropdown.configure(state="disabled")
|
|
863
|
-
|
|
864
|
-
def getChoices(self):
|
|
865
|
-
return self.dropdown_var.get()
|
|
866
|
-
|
|
867
|
-
def setChoices(self, choice):
|
|
868
|
-
self.dropdown_var.set(choice)
|
|
869
|
-
|
|
870
|
-
def on_select(self, event):
|
|
871
|
-
selected_value = self.dropdown_var.get()
|
|
872
|
-
self.command(selected_value)
|
|
873
|
-
if self.searchable: self.dropdown['values'] = self.original_options
|
|
874
|
-
|
|
875
|
-
def set_width(self, width: int):
|
|
876
|
-
self.dropdown.configure(width=width)
|
|
877
|
-
|
|
878
|
-
def change_options(self, values: List[str], set_index: Optional[int] = None, set_str: Optional[str] = None, auto_change_width: Optional[bool] = True):
|
|
879
|
-
self.dropdown_var.set('')
|
|
880
|
-
self.dropdown['values'] = values
|
|
881
|
-
if isinstance(set_index, int) and (0 <= set_index <= len(values) - 1):
|
|
882
|
-
self.dropdown_var.set(values[set_index])
|
|
883
|
-
elif (set_str is not None) and (set_str in values):
|
|
884
|
-
self.dropdown_var.set(set_str)
|
|
885
|
-
elif self.searchable and (set_str is not None):
|
|
886
|
-
self.dropdown_var.set(set_str)
|
|
887
|
-
else:
|
|
888
|
-
self.dropdown_var.set(values[0])
|
|
889
|
-
if auto_change_width: self.set_width(width=max(5, max(len(s) for s in values)))
|
|
890
|
-
if self.dropdown['values'] == ('',) or len(values) == 0 and not self.searchable:
|
|
891
|
-
self.disable()
|
|
892
|
-
else:
|
|
893
|
-
self.enable()
|
|
894
|
-
self.dropdown.configure(state='normal' if self.searchable else "readonly")
|
|
895
|
-
|
|
896
|
-
def bind_combobox_keys(self):
|
|
897
|
-
self.dropdown.bind("<KeyRelease>", self._on_key_release)
|
|
898
|
-
if self.searchable:
|
|
899
|
-
self.dropdown.bind("<Button-1>", lambda e: self._reset_to_all_options())
|
|
900
|
-
|
|
901
|
-
def _on_key_release(self, event):
|
|
902
|
-
if event.keysym in ['Up', 'Down', 'Left', 'Right', 'Tab', 'Return']:
|
|
903
|
-
return
|
|
904
|
-
try:
|
|
905
|
-
cursor_pos = self.dropdown.index(INSERT)
|
|
906
|
-
except:
|
|
907
|
-
cursor_pos = len(self.dropdown_var.get())
|
|
908
|
-
current_text = self.dropdown_var.get()
|
|
909
|
-
filtered_options = [x for x in self.original_options if current_text.lower() in x.lower()]
|
|
910
|
-
if current_text != '':
|
|
911
|
-
self.dropdown['values'] = filtered_options
|
|
912
|
-
# Restore the text and cursor position
|
|
913
|
-
self.dropdown_var.set(current_text)
|
|
914
|
-
self.dropdown.icursor(cursor_pos)
|
|
915
|
-
else:
|
|
916
|
-
self.dropdown['values'] = self.original_options
|
|
917
|
-
self.dropdown_var.set('')
|
|
918
|
-
self.dropdown.icursor(0)
|
|
919
|
-
|
|
920
|
-
def _reset_to_all_options(self):
|
|
921
|
-
if self.searchable and self.dropdown_var.get() == '':
|
|
922
|
-
self.dropdown['values'] = self.original_options
|
|
923
|
-
|
|
924
|
-
class DropDownMenu(Frame):
|
|
925
|
-
|
|
926
|
-
"""
|
|
927
|
-
Legacy, use :func:`simba.ui.tkinter_functions.SimBADropDown`.
|
|
928
|
-
"""
|
|
929
|
-
def __init__(self,
|
|
930
|
-
parent=None,
|
|
931
|
-
dropdownLabel="",
|
|
932
|
-
choice_dict=None,
|
|
933
|
-
labelwidth="",
|
|
934
|
-
com=None,
|
|
935
|
-
val: Optional[Any] = None,
|
|
936
|
-
**kw):
|
|
937
|
-
Frame.__init__(self, master=parent, **kw)
|
|
938
|
-
self.dropdownvar = StringVar()
|
|
939
|
-
self.lblName = Label(self, text=dropdownLabel, width=labelwidth, anchor=W, font=Formats.FONT_REGULAR.value)
|
|
940
|
-
self.lblName.grid(row=0, column=0)
|
|
941
|
-
self.choices = choice_dict
|
|
942
|
-
self.popupMenu = OptionMenu(self, self.dropdownvar, *self.choices, command=com)
|
|
943
|
-
self.popupMenu.grid(row=0, column=1)
|
|
944
|
-
if val is not None:
|
|
945
|
-
self.setChoices(val)
|
|
946
|
-
def getChoices(self):
|
|
947
|
-
return self.dropdownvar.get()
|
|
948
|
-
def setChoices(self, choice):
|
|
949
|
-
self.dropdownvar.set(choice)
|
|
950
|
-
def enable(self):
|
|
951
|
-
self.popupMenu.configure(state="normal")
|
|
952
|
-
def disable(self):
|
|
953
|
-
self.popupMenu.configure(state="disable")
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
class FileSelect(Frame):
|
|
958
|
-
def __init__(self,
|
|
959
|
-
parent=None,
|
|
960
|
-
fileDescription="",
|
|
961
|
-
color=None,
|
|
962
|
-
title: Optional[str] = None,
|
|
963
|
-
lblwidth: Optional[int] = None,
|
|
964
|
-
file_types=None,
|
|
965
|
-
bg_clr: Optional[str] = 'white',
|
|
966
|
-
dropdown: Union[DropDownMenu, SimBADropDown] = None,
|
|
967
|
-
entry_width: Optional[int] = 20,
|
|
968
|
-
status: Optional[str] = None,
|
|
969
|
-
lbl_icon: Optional[str] = None,
|
|
970
|
-
font: Tuple = Formats.FONT_REGULAR.value,
|
|
971
|
-
initialdir: Optional[Union[str, os.PathLike]] = None,
|
|
972
|
-
initial_path: Optional[Union[str, os.PathLike]] = None,
|
|
973
|
-
tooltip_txt: Optional[str] = None,
|
|
974
|
-
tooltip_key: Optional[str] = None,
|
|
975
|
-
**kw):
|
|
976
|
-
|
|
977
|
-
self.title, self.dropdown, self.initialdir = title, dropdown, initialdir
|
|
978
|
-
self.file_type = file_types
|
|
979
|
-
self.color = color if color is not None else "black"
|
|
980
|
-
self.lblwidth = lblwidth if lblwidth is not None else 0
|
|
981
|
-
self.parent = parent
|
|
982
|
-
Frame.__init__(self, master=parent, **kw)
|
|
983
|
-
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse"]["icon_path"]))
|
|
984
|
-
self.columnconfigure(0, weight=0)
|
|
985
|
-
self.columnconfigure(1, weight=0)
|
|
986
|
-
self.columnconfigure(2, weight=0)
|
|
987
|
-
if lbl_icon is not None:
|
|
988
|
-
self.columnconfigure(3, weight=0)
|
|
989
|
-
self.lbl_icon = SimBALabel(parent=self, txt='', txt_clr='black', font=font, width=None, anchor='w', img=lbl_icon, compound=None)
|
|
990
|
-
self.lbl_icon.grid(row=0, column=0, sticky="w")
|
|
991
|
-
else:
|
|
992
|
-
self.lbl_icon = None
|
|
993
|
-
self.filePath = StringVar()
|
|
994
|
-
self.lblName = Label(self, text=fileDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=Formats.FONT_REGULAR.value)
|
|
995
|
-
self.lblName.grid(row=0, column=1, sticky=W)
|
|
996
|
-
self.entPath = Label(self, textvariable=self.filePath, relief=SUNKEN, font=Formats.FONT_REGULAR.value, bg=bg_clr, width=entry_width)
|
|
997
|
-
self.entPath.grid(row=0, column=2)
|
|
998
|
-
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FILE_BTN_TEXT.value, font=font, cmd=self.setFilePath, img=browse_icon)
|
|
999
|
-
self.btnFind.grid(row=0, column=3)
|
|
1000
|
-
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
|
|
1001
|
-
if initial_path is not None:
|
|
1002
|
-
self.filePath.set(initial_path)
|
|
1003
|
-
if status is not None:
|
|
1004
|
-
self.set_state(setstatus=status)
|
|
1005
|
-
if tooltip_txt is not None and isinstance(tooltip_txt, str):
|
|
1006
|
-
CreateToolTip(widget=self.lblName, text=tooltip_txt)
|
|
1007
|
-
elif tooltip_key in TOOLTIPS.keys():
|
|
1008
|
-
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
1009
|
-
|
|
1010
|
-
def setFilePath(self):
|
|
1011
|
-
if self.initialdir is not None:
|
|
1012
|
-
if not os.path.isdir(self.initialdir):
|
|
1013
|
-
self.initialdir = None
|
|
1014
|
-
else:
|
|
1015
|
-
pass
|
|
1016
|
-
|
|
1017
|
-
if self.file_type:
|
|
1018
|
-
file_selected = askopenfilename(title=self.title, parent=self.parent, filetypes=self.file_type, initialdir=self.initialdir)
|
|
1019
|
-
else:
|
|
1020
|
-
file_selected = askopenfilename(title=self.title, parent=self.parent, initialdir=self.initialdir)
|
|
1021
|
-
if file_selected:
|
|
1022
|
-
if self.dropdown is not None:
|
|
1023
|
-
_, name, _ = get_fn_ext(filepath=file_selected)
|
|
1024
|
-
self.dropdown.setChoices(name)
|
|
1025
|
-
self.filePath.set(name)
|
|
1026
|
-
else:
|
|
1027
|
-
self.filePath.set(file_selected)
|
|
1028
|
-
self.entPath.configure(width=len(file_selected)+10)
|
|
1029
|
-
|
|
1030
|
-
else:
|
|
1031
|
-
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
|
|
1032
|
-
self.entPath.configure(width=len(Defaults.NO_FILE_SELECTED_TEXT.value) + 10)
|
|
1033
|
-
|
|
1034
|
-
@property
|
|
1035
|
-
def file_path(self):
|
|
1036
|
-
return self.filePath.get()
|
|
1037
|
-
|
|
1038
|
-
def set_state(self, setstatus):
|
|
1039
|
-
self.entPath.config(state=setstatus)
|
|
1040
|
-
self.btnFind["state"] = setstatus
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
def SimBARadioButton(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
1044
|
-
txt: str,
|
|
1045
|
-
variable: Union[BooleanVar, StringVar],
|
|
1046
|
-
txt_clr: Optional[str] = 'black',
|
|
1047
|
-
font: Optional[Tuple] = Formats.FONT_REGULAR.value,
|
|
1048
|
-
compound: Optional[str] = 'left',
|
|
1049
|
-
img: Optional[Union[ImageTk.PhotoImage, str]] = None,
|
|
1050
|
-
enabled: Optional[bool] = True,
|
|
1051
|
-
tooltip_txt: Optional[str] = None,
|
|
1052
|
-
value: bool = False,
|
|
1053
|
-
cmd: Optional[Callable] = None,
|
|
1054
|
-
cmd_kwargs: Optional[Dict[Any, Any]] = None) -> Radiobutton:
|
|
1055
|
-
|
|
1056
|
-
if isinstance(img, str):
|
|
1057
|
-
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
1058
|
-
|
|
1059
|
-
if cmd_kwargs is None:
|
|
1060
|
-
cmd_kwargs = {}
|
|
1061
|
-
|
|
1062
|
-
def execute_command():
|
|
1063
|
-
if cmd:
|
|
1064
|
-
evaluated_kwargs = {k: (v() if callable(v) else v) for k, v in cmd_kwargs.items()}
|
|
1065
|
-
cmd(**evaluated_kwargs)
|
|
1066
|
-
|
|
1067
|
-
if cmd is not None:
|
|
1068
|
-
command = execute_command
|
|
1069
|
-
else:
|
|
1070
|
-
command = None
|
|
1071
|
-
|
|
1072
|
-
btn = Radiobutton(parent,
|
|
1073
|
-
text=txt,
|
|
1074
|
-
font=font,
|
|
1075
|
-
image=img,
|
|
1076
|
-
fg=txt_clr,
|
|
1077
|
-
variable=variable,
|
|
1078
|
-
value=value,
|
|
1079
|
-
compound=compound,
|
|
1080
|
-
command=command)
|
|
1081
|
-
|
|
1082
|
-
if img is not None:
|
|
1083
|
-
btn.image = img
|
|
1084
|
-
|
|
1085
|
-
if not enabled:
|
|
1086
|
-
btn.config(state=DISABLED)
|
|
1087
|
-
|
|
1088
|
-
if tooltip_txt is not None:
|
|
1089
|
-
CreateToolTip(widget=btn, text=tooltip_txt)
|
|
1090
|
-
|
|
1091
|
-
return btn
|
|
1
|
+
__author__ = "Simon Nilsson; sronilsson@gmail.com"
|
|
2
|
+
|
|
3
|
+
import os.path
|
|
4
|
+
import platform
|
|
5
|
+
import threading
|
|
6
|
+
import tkinter
|
|
7
|
+
import tkinter as tk
|
|
8
|
+
import webbrowser
|
|
9
|
+
from copy import copy, deepcopy
|
|
10
|
+
from tkinter import *
|
|
11
|
+
from tkinter.filedialog import askdirectory, askopenfilename
|
|
12
|
+
from tkinter.ttk import Combobox
|
|
13
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from typing import Literal
|
|
17
|
+
except:
|
|
18
|
+
from typing_extensions import Literal
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
import PIL.Image
|
|
22
|
+
from PIL import ImageTk
|
|
23
|
+
|
|
24
|
+
from simba.utils.checks import check_if_valid_img
|
|
25
|
+
from simba.utils.enums import Defaults, Formats, TkBinds
|
|
26
|
+
from simba.utils.lookups import get_icons_paths, get_tooltips
|
|
27
|
+
from simba.utils.read_write import get_fn_ext
|
|
28
|
+
|
|
29
|
+
MENU_ICONS = get_icons_paths()
|
|
30
|
+
TOOLTIPS = get_tooltips()
|
|
31
|
+
|
|
32
|
+
def onMousewheel(event, canvas):
|
|
33
|
+
try:
|
|
34
|
+
scrollSpeed = event.delta
|
|
35
|
+
if platform.system() == "Darwin":
|
|
36
|
+
scrollSpeed = event.delta
|
|
37
|
+
elif platform.system() == "Windows":
|
|
38
|
+
scrollSpeed = int(event.delta / 120)
|
|
39
|
+
canvas.yview_scroll(-1 * (scrollSpeed), "units")
|
|
40
|
+
except:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def bindToMousewheel(event, canvas):
|
|
45
|
+
canvas.bind_all("<MouseWheel>", lambda event: onMousewheel(event, canvas))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def unbindToMousewheel(event, canvas):
|
|
49
|
+
canvas.unbind_all("<MouseWheel>")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def onFrameConfigure(canvas):
|
|
53
|
+
"""Reset the scroll region to encompass the inner frame"""
|
|
54
|
+
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def on_mouse_scroll(event, canvas):
|
|
58
|
+
if event.delta:
|
|
59
|
+
canvas.yview_scroll(-1 * (event.delta // 120), "units")
|
|
60
|
+
elif event.num == 4:
|
|
61
|
+
canvas.yview_scroll(-1, "units")
|
|
62
|
+
elif event.num == 5:
|
|
63
|
+
canvas.yview_scroll(1, "units")
|
|
64
|
+
|
|
65
|
+
def hxtScrollbar(master):
|
|
66
|
+
canvas = tk.Canvas(master, borderwidth=0, bg="#f0f0f0", relief="flat")
|
|
67
|
+
frame = tk.Frame(canvas, bg="#f0f0f0", bd=0)
|
|
68
|
+
|
|
69
|
+
vsb = tk.Scrollbar(master, orient="vertical", command=canvas.yview, bg="#cccccc", width=20)
|
|
70
|
+
hsb = tk.Scrollbar(master, orient="horizontal", command=canvas.xview, bg="#cccccc")
|
|
71
|
+
|
|
72
|
+
canvas.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)
|
|
73
|
+
|
|
74
|
+
canvas.pack(side="left", fill="both", expand=True, padx=0, pady=0)
|
|
75
|
+
vsb.pack(side="right", fill="y", padx=(0, 0))
|
|
76
|
+
hsb.pack(side="bottom", fill="x", pady=(0, 0))
|
|
77
|
+
|
|
78
|
+
canvas.create_window((0, 0), window=frame, anchor="nw")
|
|
79
|
+
|
|
80
|
+
def on_frame_configure(event):
|
|
81
|
+
canvas.configure(scrollregion=canvas.bbox("all"))
|
|
82
|
+
|
|
83
|
+
frame.bind("<Configure>", on_frame_configure)
|
|
84
|
+
|
|
85
|
+
def _on_mousewheel(event):
|
|
86
|
+
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
|
87
|
+
|
|
88
|
+
def _on_key(event):
|
|
89
|
+
if event.keysym == "Up":
|
|
90
|
+
canvas.yview_scroll(-1, "units")
|
|
91
|
+
elif event.keysym == "Down":
|
|
92
|
+
canvas.yview_scroll(1, "units")
|
|
93
|
+
elif event.keysym == "Page_Up":
|
|
94
|
+
canvas.yview_scroll(-1, "pages")
|
|
95
|
+
elif event.keysym == "Page_Down":
|
|
96
|
+
canvas.yview_scroll(1, "pages")
|
|
97
|
+
|
|
98
|
+
# Focus & bindings when mouse enters
|
|
99
|
+
def _on_enter(_):
|
|
100
|
+
frame.focus_set()
|
|
101
|
+
frame.bind_all("<MouseWheel>", _on_mousewheel)
|
|
102
|
+
frame.bind_all("<Key>", _on_key)
|
|
103
|
+
|
|
104
|
+
# Clean up when mouse leaves
|
|
105
|
+
# def _on_leave(_):
|
|
106
|
+
# frame.unbind_all("<MouseWheel>")
|
|
107
|
+
# frame.unbind_all("<Key>")
|
|
108
|
+
|
|
109
|
+
frame.bind("<Enter>", _on_enter)
|
|
110
|
+
#frame.bind("<Leave>", _on_leave)
|
|
111
|
+
|
|
112
|
+
return frame
|
|
113
|
+
|
|
114
|
+
def form_validator_is_numeric(inStr, acttyp):
|
|
115
|
+
if acttyp == "1": # insert
|
|
116
|
+
if not inStr.isdigit():
|
|
117
|
+
return False
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class DropDownMenu(Frame):
|
|
122
|
+
"""
|
|
123
|
+
Legacy, use :func:`simba.ui.tkinter_functions.SimBADropDown`.
|
|
124
|
+
"""
|
|
125
|
+
def __init__(self,
|
|
126
|
+
parent=None,
|
|
127
|
+
dropdownLabel="",
|
|
128
|
+
choice_dict=None,
|
|
129
|
+
labelwidth="",
|
|
130
|
+
com=None,
|
|
131
|
+
val: Optional[Any] = None,
|
|
132
|
+
**kw):
|
|
133
|
+
|
|
134
|
+
Frame.__init__(self, master=parent, **kw)
|
|
135
|
+
self.dropdownvar = StringVar()
|
|
136
|
+
self.lblName = Label(self, text=dropdownLabel, width=labelwidth, anchor=W, font=Formats.FONT_REGULAR.value)
|
|
137
|
+
self.lblName.grid(row=0, column=0)
|
|
138
|
+
self.choices = choice_dict
|
|
139
|
+
self.popupMenu = OptionMenu(self, self.dropdownvar, *self.choices, command=com)
|
|
140
|
+
self.popupMenu.grid(row=0, column=1)
|
|
141
|
+
if val is not None:
|
|
142
|
+
self.setChoices(val)
|
|
143
|
+
|
|
144
|
+
def getChoices(self):
|
|
145
|
+
return self.dropdownvar.get()
|
|
146
|
+
|
|
147
|
+
def setChoices(self, choice):
|
|
148
|
+
self.dropdownvar.set(choice)
|
|
149
|
+
|
|
150
|
+
def enable(self):
|
|
151
|
+
self.popupMenu.configure(state="normal")
|
|
152
|
+
|
|
153
|
+
def disable(self):
|
|
154
|
+
self.popupMenu.configure(state="disable")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class SimBAScaleBar(Frame):
|
|
158
|
+
def __init__(self,
|
|
159
|
+
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
160
|
+
label: Optional[str] = None,
|
|
161
|
+
label_width: Optional[int] = None,
|
|
162
|
+
orient: Literal['horizontal', 'vertical'] = HORIZONTAL,
|
|
163
|
+
length: int = 200,
|
|
164
|
+
value: Optional[int] = 95,
|
|
165
|
+
showvalue: bool = True,
|
|
166
|
+
label_clr: str = 'black',
|
|
167
|
+
lbl_font: tuple = Formats.FONT_REGULAR.value,
|
|
168
|
+
scale_font: tuple = Formats.FONT_REGULAR_ITALICS.value,
|
|
169
|
+
lbl_img: Optional[str] = None,
|
|
170
|
+
from_: int = -1,
|
|
171
|
+
resolution: int = 1,
|
|
172
|
+
to: int = 100,
|
|
173
|
+
tickinterval: Optional[int] = None,
|
|
174
|
+
troughcolor: Optional[str] = None,
|
|
175
|
+
activebackground: Optional[str] = None,
|
|
176
|
+
sliderrelief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = 'flat'):
|
|
177
|
+
|
|
178
|
+
super().__init__(master=parent)
|
|
179
|
+
self.columnconfigure(0, weight=0)
|
|
180
|
+
self.columnconfigure(1, weight=0)
|
|
181
|
+
if lbl_img is not None:
|
|
182
|
+
self.columnconfigure(2, weight=0)
|
|
183
|
+
self.lbl_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=None, font=lbl_font, width=None, anchor='w', img=lbl_img, compound=None)
|
|
184
|
+
self.lbl_lbl.grid(row=0, column=0, sticky=SW)
|
|
185
|
+
else:
|
|
186
|
+
self.lbl_lbl = None
|
|
187
|
+
|
|
188
|
+
self.scale = Scale(self,
|
|
189
|
+
from_=from_,
|
|
190
|
+
to=to,
|
|
191
|
+
orient=orient,
|
|
192
|
+
length=length,
|
|
193
|
+
font=scale_font,
|
|
194
|
+
sliderrelief=sliderrelief,
|
|
195
|
+
troughcolor=troughcolor,
|
|
196
|
+
tickinterval=tickinterval,
|
|
197
|
+
resolution=resolution,
|
|
198
|
+
showvalue=showvalue,
|
|
199
|
+
activebackground=activebackground)
|
|
200
|
+
|
|
201
|
+
if label is not None:
|
|
202
|
+
self.lbl = SimBALabel(parent=self, txt=label, font=lbl_font, txt_clr=label_clr, width=label_width)
|
|
203
|
+
self.lbl.grid(row=0, column=1, sticky=SW)
|
|
204
|
+
|
|
205
|
+
self.scale.grid(row=0, column=2, sticky=NW)
|
|
206
|
+
if value is not None:
|
|
207
|
+
self.set_value(value=value)
|
|
208
|
+
|
|
209
|
+
def set_value(self, value: int):
|
|
210
|
+
self.scale.set(value)
|
|
211
|
+
|
|
212
|
+
def get_value(self) -> Union[int, float]:
|
|
213
|
+
return self.scale.get()
|
|
214
|
+
|
|
215
|
+
def get(self) -> Union[int, float]:
|
|
216
|
+
## Alternative for ``get_value`` for legacy reasons.
|
|
217
|
+
return self.scale.get()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class Entry_Box(Frame):
|
|
223
|
+
def __init__(self,
|
|
224
|
+
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
225
|
+
fileDescription: Optional[str] = "",
|
|
226
|
+
labelwidth: Union[int, str] = None,
|
|
227
|
+
label_bg_clr: Optional[str] = None,
|
|
228
|
+
status: Literal["normal", "disabled", "readonly"] = 'normal',
|
|
229
|
+
validation: Optional[Union[Callable, str]] = None,
|
|
230
|
+
entry_box_width: Union[int, str] = None,
|
|
231
|
+
entry_box_clr: Optional[str] = None,
|
|
232
|
+
img: Optional[str] = None,
|
|
233
|
+
value: Optional[Any] = None,
|
|
234
|
+
label_font: tuple = Formats.FONT_REGULAR.value,
|
|
235
|
+
entry_font: tuple = Formats.FONT_REGULAR.value,
|
|
236
|
+
tooltip_key: Optional[str] = None,
|
|
237
|
+
justify: Literal["left", "center", "right"] = 'left',
|
|
238
|
+
cmd: Optional[Callable] = None,
|
|
239
|
+
**kw):
|
|
240
|
+
|
|
241
|
+
super(Entry_Box, self).__init__(master=parent)
|
|
242
|
+
self.validation_methods = {"numeric": (self.register(form_validator_is_numeric), "%P", "%d")}
|
|
243
|
+
self.status = status if status is not None else NORMAL
|
|
244
|
+
self.labelname = fileDescription
|
|
245
|
+
Frame.__init__(self, master=parent, **kw)
|
|
246
|
+
self.columnconfigure(0, weight=0)
|
|
247
|
+
self.columnconfigure(1, weight=0)
|
|
248
|
+
if img is not None:
|
|
249
|
+
self.columnconfigure(2, weight=0)
|
|
250
|
+
self.entrybox_img_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=None, anchor='w', img=img, compound=None)
|
|
251
|
+
self.entrybox_img_lbl.grid(row=0, column=0, sticky="w")
|
|
252
|
+
else:
|
|
253
|
+
self.entrybox_img_lbl = None
|
|
254
|
+
self.filePath = StringVar()
|
|
255
|
+
self.lblName = Label(self, text=fileDescription, width=labelwidth, anchor=W, font=label_font, bg=label_bg_clr)
|
|
256
|
+
self.lblName.grid(row=0, column=1)
|
|
257
|
+
if tooltip_key in TOOLTIPS.keys():
|
|
258
|
+
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
259
|
+
if not entry_box_width:
|
|
260
|
+
self.entPath = Entry(self, textvariable=self.filePath, state=self.status, validate="key", validatecommand=self.validation_methods.get(validation, None), font=entry_font, justify=justify, bg=entry_box_clr)
|
|
261
|
+
else:
|
|
262
|
+
self.entPath = Entry(self, textvariable=self.filePath, state=self.status, width=entry_box_width, validate="key", font=entry_font, justify=justify, validatecommand=self.validation_methods.get(validation, None), bg=entry_box_clr)
|
|
263
|
+
self.entPath.grid(row=0, column=2)
|
|
264
|
+
if value is not None:
|
|
265
|
+
self.entry_set(val=value)
|
|
266
|
+
if cmd is not None:
|
|
267
|
+
self.bind_combobox_keys()
|
|
268
|
+
self.cmd = cmd
|
|
269
|
+
|
|
270
|
+
def bind_combobox_keys(self):
|
|
271
|
+
self.entPath.bind("<KeyRelease>", self.run_cmd)
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def entry_get(self):
|
|
275
|
+
return self.entPath.get()
|
|
276
|
+
|
|
277
|
+
def entry_set(self, val):
|
|
278
|
+
self.filePath.set(val)
|
|
279
|
+
|
|
280
|
+
def set_state(self, setstatus):
|
|
281
|
+
self.entPath.config(state=setstatus)
|
|
282
|
+
|
|
283
|
+
def destroy(self):
|
|
284
|
+
try:
|
|
285
|
+
if self.entrybox_img_lbl is not None:
|
|
286
|
+
self.entrybox_img_lbl.destroy()
|
|
287
|
+
self.lblName.destroy()
|
|
288
|
+
self.entPath.destroy()
|
|
289
|
+
except:
|
|
290
|
+
pass
|
|
291
|
+
|
|
292
|
+
def run_cmd(self, x):
|
|
293
|
+
self.cmd(self.entry_get.strip())
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class FolderSelect(Frame):
|
|
298
|
+
def __init__(self,
|
|
299
|
+
parent: Union[Frame, LabelFrame, Canvas, Toplevel],
|
|
300
|
+
folderDescription: Optional[str] = "",
|
|
301
|
+
color: Optional[str] = None,
|
|
302
|
+
label_bg_clr: Optional[str] = None,
|
|
303
|
+
font: Tuple = Formats.FONT_REGULAR.value,
|
|
304
|
+
title: Optional[str] = "",
|
|
305
|
+
lblwidth: Optional[int] = 0,
|
|
306
|
+
bg_clr: Optional[str] = 'white',
|
|
307
|
+
entry_width: Optional[int] = 20,
|
|
308
|
+
lbl_icon: Optional[str] = None,
|
|
309
|
+
initialdir: Optional[Union[str, os.PathLike]] = None,
|
|
310
|
+
tooltip_txt: Optional[str] = None,
|
|
311
|
+
tooltip_key: Optional[str] = None,
|
|
312
|
+
**kw):
|
|
313
|
+
|
|
314
|
+
self.title, self.initialdir = title, initialdir
|
|
315
|
+
self.color = color if color is not None else "black"
|
|
316
|
+
self.lblwidth = lblwidth if lblwidth is not None else 0
|
|
317
|
+
self.parent = parent
|
|
318
|
+
Frame.__init__(self, master=parent, **kw)
|
|
319
|
+
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse_small"]["icon_path"]))
|
|
320
|
+
self.columnconfigure(0, weight=0)
|
|
321
|
+
self.columnconfigure(1, weight=0)
|
|
322
|
+
self.columnconfigure(2, weight=0)
|
|
323
|
+
if lbl_icon is not None:
|
|
324
|
+
self.columnconfigure(3, weight=0)
|
|
325
|
+
self.lbl_icon = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=font, width=None, anchor='w', img=lbl_icon, compound=None)
|
|
326
|
+
self.lbl_icon.grid(row=0, column=0, sticky="w")
|
|
327
|
+
else:
|
|
328
|
+
self.lbl_icon = None
|
|
329
|
+
self.folderPath = StringVar()
|
|
330
|
+
self.lblName = Label(self, text=folderDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=font, bg=label_bg_clr)
|
|
331
|
+
self.lblName.grid(row=0, column=1, sticky=NW)
|
|
332
|
+
self.entPath = Label(self, textvariable=self.folderPath, relief=SUNKEN, font=font, bg=bg_clr, width=entry_width)
|
|
333
|
+
self.entPath.grid(row=0, column=2, sticky=NW)
|
|
334
|
+
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FOLDER_BTN_TEXT.value, font=font, cmd=self.setFolderPath, img=browse_icon)
|
|
335
|
+
self.btnFind.image = browse_icon
|
|
336
|
+
self.btnFind.grid(row=0, column=3, sticky=NW)
|
|
337
|
+
self.folderPath.set("No folder selected")
|
|
338
|
+
if tooltip_txt is not None and isinstance(tooltip_txt, str):
|
|
339
|
+
CreateToolTip(widget=self.lblName, text=tooltip_txt)
|
|
340
|
+
elif tooltip_key in TOOLTIPS.keys():
|
|
341
|
+
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
342
|
+
|
|
343
|
+
def setFolderPath(self):
|
|
344
|
+
if self.initialdir is not None:
|
|
345
|
+
if not os.path.isdir(self.initialdir):
|
|
346
|
+
self.initialdir = None
|
|
347
|
+
else:
|
|
348
|
+
pass
|
|
349
|
+
folder_selected = askdirectory(title=str(self.title), parent=self.parent, initialdir=self.initialdir)
|
|
350
|
+
if folder_selected:
|
|
351
|
+
self.folderPath.set(folder_selected)
|
|
352
|
+
self.entPath.configure(width=len(folder_selected)+10)
|
|
353
|
+
else:
|
|
354
|
+
self.folderPath.set("No folder selected")
|
|
355
|
+
self.entPath.configure(width=len("No folder selected")+10)
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def folder_path(self):
|
|
359
|
+
return self.folderPath.get()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class ToolTip(object):
|
|
363
|
+
|
|
364
|
+
def __init__(self, widget):
|
|
365
|
+
self.widget = widget
|
|
366
|
+
self.tipwindow = None
|
|
367
|
+
self.id = None
|
|
368
|
+
self.x = self.y = 0
|
|
369
|
+
|
|
370
|
+
def showtip(self, text):
|
|
371
|
+
"Display text in tooltip window"
|
|
372
|
+
self.text = text
|
|
373
|
+
if self.tipwindow or not self.text:
|
|
374
|
+
return
|
|
375
|
+
x, y, cx, cy = self.widget.bbox("insert")
|
|
376
|
+
x = x + self.widget.winfo_rootx() + 20
|
|
377
|
+
y = y + cy + self.widget.winfo_rooty() + 20
|
|
378
|
+
self.tipwindow = tw = Toplevel(self.widget)
|
|
379
|
+
tw.wm_overrideredirect(1)
|
|
380
|
+
tw.wm_geometry("+%d+%d" % (x, y))
|
|
381
|
+
label = Label(tw, text=self.text, justify=LEFT, background="#ffffe0", relief=SOLID, borderwidth=1, font=Formats.FONT_REGULAR.value)
|
|
382
|
+
label.pack(ipadx=1)
|
|
383
|
+
|
|
384
|
+
def hidetip(self):
|
|
385
|
+
tw = self.tipwindow
|
|
386
|
+
self.tipwindow = None
|
|
387
|
+
if tw:
|
|
388
|
+
tw.destroy()
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def CreateToolTip(widget, text):
|
|
392
|
+
toolTip = ToolTip(widget)
|
|
393
|
+
def enter(event):
|
|
394
|
+
toolTip.showtip(text)
|
|
395
|
+
|
|
396
|
+
def leave(event):
|
|
397
|
+
toolTip.hidetip()
|
|
398
|
+
|
|
399
|
+
widget.bind("<Enter>", enter)
|
|
400
|
+
widget.bind("<Leave>", leave)
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def CreateLabelFrameWithIcon(parent: Union[Toplevel, LabelFrame, Canvas, Frame],
|
|
404
|
+
header: str,
|
|
405
|
+
icon_name: str,
|
|
406
|
+
padx: Optional[int] = None,
|
|
407
|
+
pady: Optional[int] = None,
|
|
408
|
+
relief: str = 'solid',
|
|
409
|
+
width: Optional[int] = None,
|
|
410
|
+
bg: Optional[str] = None,
|
|
411
|
+
font: tuple = Formats.FONT_HEADER.value,
|
|
412
|
+
icon_link: Optional[Union[str, None]] = LabelFrame,
|
|
413
|
+
tooltip_key: Optional[str] = None):
|
|
414
|
+
|
|
415
|
+
if icon_name in MENU_ICONS.keys():
|
|
416
|
+
icon = PIL.Image.open(MENU_ICONS[icon_name]["icon_path"])
|
|
417
|
+
icon = ImageTk.PhotoImage(icon)
|
|
418
|
+
else:
|
|
419
|
+
icon = None
|
|
420
|
+
frm = Frame(parent, bg=bg)
|
|
421
|
+
|
|
422
|
+
label_image = Label(frm, image=icon, bg=bg)
|
|
423
|
+
label_image.image = icon
|
|
424
|
+
if icon_link:
|
|
425
|
+
label_image.bind("<Button-1>", lambda e: callback(icon_link))
|
|
426
|
+
label_image.grid(row=0, column=0)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
label_text = Label(frm, text=header, font=font, bg=bg)
|
|
430
|
+
label_text.grid(row=0, column=1)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
lbl_frm = LabelFrame(parent, labelwidget=frm, relief=relief, width=width, padx=padx or 0, pady=pady or 0)
|
|
435
|
+
|
|
436
|
+
if tooltip_key in TOOLTIPS.keys():
|
|
437
|
+
CreateToolTip(widget=label_text, text=TOOLTIPS[tooltip_key])
|
|
438
|
+
|
|
439
|
+
if bg is not None:
|
|
440
|
+
lbl_frm.configure(bg=bg)
|
|
441
|
+
return lbl_frm
|
|
442
|
+
|
|
443
|
+
def callback(url):
|
|
444
|
+
webbrowser.open_new(url)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def create_scalebar(
|
|
448
|
+
parent: Frame, name: str, min: int, max: int, cmd: object or None = None
|
|
449
|
+
):
|
|
450
|
+
|
|
451
|
+
scale = Scale(
|
|
452
|
+
parent,
|
|
453
|
+
from_=min,
|
|
454
|
+
to=max,
|
|
455
|
+
orient=HORIZONTAL,
|
|
456
|
+
length=200,
|
|
457
|
+
label=name,
|
|
458
|
+
command=cmd,
|
|
459
|
+
)
|
|
460
|
+
scale.set(0)
|
|
461
|
+
return scale
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class TwoOptionQuestionPopUp(object):
|
|
465
|
+
"""
|
|
466
|
+
Helpe to create a two-option question tkinter pop up window (e.g., YES/NO).
|
|
467
|
+
|
|
468
|
+
:parameter str question: Question to present to the user. E.g., ``DO YOU WANT TO PROCEED?``.
|
|
469
|
+
:parameter str option_one: The first user option. E.g., ``YES``.
|
|
470
|
+
:parameter str option_one: The second user option. E.g., ``NO``.
|
|
471
|
+
:parameter str title: The window titel in the top banner of the pop-up. E.g., ``A QUESTION FOR YOU!``.
|
|
472
|
+
:parameter Optional[str] link: If not None, then a link to documentation presenting background info about the user choices.
|
|
473
|
+
"""
|
|
474
|
+
|
|
475
|
+
def __init__(self,
|
|
476
|
+
question: str,
|
|
477
|
+
option_one: str,
|
|
478
|
+
option_two: str,
|
|
479
|
+
title: str,
|
|
480
|
+
link: Optional[str] = None):
|
|
481
|
+
|
|
482
|
+
self.main_frm = Toplevel()
|
|
483
|
+
self.main_frm.geometry("600x200")
|
|
484
|
+
self.main_frm.title(title)
|
|
485
|
+
menu_icons = get_menu_icons()
|
|
486
|
+
self.main_frm.iconphoto(False, menu_icons['question_mark']["img"])
|
|
487
|
+
|
|
488
|
+
#img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS['question_mark']["icon_path"]))
|
|
489
|
+
|
|
490
|
+
question_frm = Frame(self.main_frm)
|
|
491
|
+
question_frm.pack(expand=True, fill="both")
|
|
492
|
+
Label(question_frm, text=question, font=Formats.LABELFRAME_HEADER_FORMAT.value).pack()
|
|
493
|
+
|
|
494
|
+
button_one = SimbaButton(parent=question_frm, txt=option_one, txt_clr="blue", bg_clr="lightgrey", img='check_blue', cmd=self.run, cmd_kwargs={'selected_option': lambda: option_one}, font=Formats.FONT_LARGE.value, hover_font=Formats.FONT_LARGE_BOLD.value)
|
|
495
|
+
button_two = SimbaButton(parent=question_frm, txt=option_two, txt_clr="red", bg_clr="lightgrey", img='close', cmd=self.run, cmd_kwargs={'selected_option': lambda: option_two}, font=Formats.FONT_LARGE.value, hover_font=Formats.FONT_LARGE_BOLD.value)
|
|
496
|
+
if link:
|
|
497
|
+
link_lbl = Label(question_frm, text="Click here for more information.", cursor="hand2", fg="blue")
|
|
498
|
+
link_lbl.bind("<Button-1>", lambda e: callback(link))
|
|
499
|
+
link_lbl.place(relx=0.5, rely=0.30, anchor=CENTER)
|
|
500
|
+
button_one.place(relx=0.5, rely=0.50, anchor=CENTER)
|
|
501
|
+
button_two.place(relx=0.5, rely=0.80, anchor=CENTER)
|
|
502
|
+
self.main_frm.wait_window()
|
|
503
|
+
|
|
504
|
+
def run(self, selected_option):
|
|
505
|
+
self.selected_option = selected_option
|
|
506
|
+
self.main_frm.destroy()
|
|
507
|
+
|
|
508
|
+
def SimbaButton(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
509
|
+
txt: str,
|
|
510
|
+
txt_clr: Optional[str] = 'black',
|
|
511
|
+
bg_clr: Optional[str] = '#f0f0f0',
|
|
512
|
+
active_bg_clr: Optional[str] = None,
|
|
513
|
+
hover_bg_clr: Optional[str] = Formats.BTN_HOVER_CLR.value,
|
|
514
|
+
hover_font: Optional[Tuple] = Formats.FONT_REGULAR_BOLD.value,
|
|
515
|
+
font: Optional[Tuple] = Formats.FONT_REGULAR.value,
|
|
516
|
+
width: Optional[int] = None,
|
|
517
|
+
height: Optional[int] = None,
|
|
518
|
+
compound: Optional[str] = 'left',
|
|
519
|
+
img: Optional[Union[ImageTk.PhotoImage, str]] = None,
|
|
520
|
+
cmd: Optional[Callable] = None,
|
|
521
|
+
cmd_kwargs: Optional[Dict[Any, Any]] = None,
|
|
522
|
+
enabled: Optional[bool] = True,
|
|
523
|
+
anchor: str = 'w',
|
|
524
|
+
thread: Optional[bool] = False,
|
|
525
|
+
tooltip_txt: Optional[str] = None) -> Button:
|
|
526
|
+
def on_enter(e):
|
|
527
|
+
e.widget.config(bg=hover_bg_clr, font=hover_font)
|
|
528
|
+
|
|
529
|
+
def on_leave(e):
|
|
530
|
+
e.widget.config(bg=bg_clr, font=font)
|
|
531
|
+
|
|
532
|
+
# if hover_font != font:
|
|
533
|
+
# hover_font = copy(font)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
if isinstance(img, str):
|
|
538
|
+
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
539
|
+
|
|
540
|
+
if cmd_kwargs is None:
|
|
541
|
+
cmd_kwargs = {}
|
|
542
|
+
|
|
543
|
+
def execute_command():
|
|
544
|
+
if cmd:
|
|
545
|
+
evaluated_kwargs = {k: (v() if callable(v) else v) for k, v in cmd_kwargs.items()}
|
|
546
|
+
cmd(**evaluated_kwargs)
|
|
547
|
+
|
|
548
|
+
if cmd is not None:
|
|
549
|
+
if thread:
|
|
550
|
+
command = lambda: threading.Thread(target=execute_command).start()
|
|
551
|
+
else:
|
|
552
|
+
command = execute_command
|
|
553
|
+
else:
|
|
554
|
+
command = None
|
|
555
|
+
|
|
556
|
+
btn = Button(master=parent,
|
|
557
|
+
text=txt,
|
|
558
|
+
compound=compound,
|
|
559
|
+
image=img,
|
|
560
|
+
relief=RAISED,
|
|
561
|
+
fg=txt_clr,
|
|
562
|
+
activebackground=active_bg_clr,
|
|
563
|
+
font=font,
|
|
564
|
+
bg=bg_clr,
|
|
565
|
+
anchor=anchor,
|
|
566
|
+
command=command)
|
|
567
|
+
|
|
568
|
+
if img is not None:
|
|
569
|
+
btn.image = img
|
|
570
|
+
if width is not None:
|
|
571
|
+
btn.config(width=width)
|
|
572
|
+
if height is not None:
|
|
573
|
+
btn.config(height=height)
|
|
574
|
+
if not enabled:
|
|
575
|
+
btn.config(state=DISABLED)
|
|
576
|
+
|
|
577
|
+
if hover_bg_clr is not None:
|
|
578
|
+
btn.bind("<Enter>", on_enter)
|
|
579
|
+
btn.bind("<Leave>", on_leave)
|
|
580
|
+
|
|
581
|
+
if tooltip_txt is not None:
|
|
582
|
+
CreateToolTip(widget=btn, text=tooltip_txt)
|
|
583
|
+
|
|
584
|
+
return btn
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def SimbaCheckbox(parent: Union[Frame, Toplevel, LabelFrame, Canvas],
|
|
588
|
+
txt: str,
|
|
589
|
+
txt_clr: Optional[str] = 'black',
|
|
590
|
+
bg_clr: Optional[str] = None,
|
|
591
|
+
txt_img: Optional[str] = None,
|
|
592
|
+
txt_img_location: Literal['left', 'right', 'top', 'bottom'] = RIGHT,
|
|
593
|
+
font: Optional[Tuple[str, str, int]] = Formats.FONT_REGULAR.value,
|
|
594
|
+
val: Optional[bool] = False,
|
|
595
|
+
state: Literal["disabled", 'normal'] = NORMAL,
|
|
596
|
+
indicatoron: bool = True,
|
|
597
|
+
cmd: Optional[Callable] = None,
|
|
598
|
+
tooltip_txt: Optional[str] = None,
|
|
599
|
+
tooltip_key: Optional[str] = None):
|
|
600
|
+
|
|
601
|
+
var = BooleanVar(value=False)
|
|
602
|
+
if val: var.set(True)
|
|
603
|
+
if isinstance(txt_img, str):
|
|
604
|
+
txt_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[txt_img]["icon_path"]))
|
|
605
|
+
if cmd is None:
|
|
606
|
+
cb = Checkbutton(master=parent, font=font, fg=txt_clr, image=txt_img, text=txt, compound=txt_img_location, variable=var, indicatoron=indicatoron, bg=bg_clr)
|
|
607
|
+
else:
|
|
608
|
+
cb = Checkbutton(master=parent, font=font, fg=txt_clr, image=txt_img, text=txt, compound=txt_img_location, variable=var, command=cmd, indicatoron=indicatoron, bg=bg_clr)
|
|
609
|
+
if txt_img is not None:
|
|
610
|
+
cb.image = txt_img
|
|
611
|
+
if state == DISABLED:
|
|
612
|
+
cb.configure(state=DISABLED)
|
|
613
|
+
|
|
614
|
+
if isinstance(tooltip_txt, str):
|
|
615
|
+
CreateToolTip(widget=cb, text=tooltip_txt)
|
|
616
|
+
elif isinstance(tooltip_key, str) and tooltip_key in TOOLTIPS.keys():
|
|
617
|
+
CreateToolTip(widget=cb, text=TOOLTIPS[tooltip_key])
|
|
618
|
+
|
|
619
|
+
return cb, var
|
|
620
|
+
|
|
621
|
+
def SimBALabel(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
622
|
+
txt: str,
|
|
623
|
+
txt_clr: str = 'black',
|
|
624
|
+
bg_clr: Optional[str] = None,
|
|
625
|
+
hover_fg_clr: Optional[str] = None,
|
|
626
|
+
font: tuple = Formats.FONT_REGULAR.value,
|
|
627
|
+
hover_font: Optional[Tuple] = None,
|
|
628
|
+
relief: str = FLAT,
|
|
629
|
+
compound: Optional[Literal['left', 'right', 'top', 'bottom', 'center']] = 'left',
|
|
630
|
+
justify: Optional[str] = None,
|
|
631
|
+
link: Optional[str] = None,
|
|
632
|
+
width: Optional[int] = None,
|
|
633
|
+
padx: Optional[int] = None,
|
|
634
|
+
pady: Optional[int] = None,
|
|
635
|
+
cursor: Optional[str] = None,
|
|
636
|
+
img: Optional[str] = None,
|
|
637
|
+
anchor: Optional[str] = None,
|
|
638
|
+
tooltip_key: Optional[str] = None,
|
|
639
|
+
hover_img: Optional[np.ndarray] = None):
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _hover_enter(e):
|
|
643
|
+
w = e.widget
|
|
644
|
+
if hover_img is not None and check_if_valid_img(data=hover_img, raise_error=False):
|
|
645
|
+
arr = np.asarray(hover_img, dtype=np.uint8)
|
|
646
|
+
if arr.ndim == 3 and arr.shape[2] == 3:
|
|
647
|
+
arr = arr[:, :, ::-1]
|
|
648
|
+
pil_img = PIL.Image.fromarray(arr)
|
|
649
|
+
photo = ImageTk.PhotoImage(pil_img)
|
|
650
|
+
tw = Toplevel(w)
|
|
651
|
+
tw.wm_overrideredirect(True)
|
|
652
|
+
tw.attributes("-topmost", True)
|
|
653
|
+
tw.wm_geometry("+%d+%d" % (w.winfo_rootx() + w.winfo_width() + 4, w.winfo_rooty()))
|
|
654
|
+
frm = Frame(tw, relief="solid", bd=2, bg="#f0f0f0")
|
|
655
|
+
frm.pack(padx=2, pady=2)
|
|
656
|
+
lbl_hover = Label(frm, image=photo, bg="#f0f0f0")
|
|
657
|
+
lbl_hover.image = photo
|
|
658
|
+
lbl_hover.pack(padx=4, pady=(4, 2))
|
|
659
|
+
caption_lbl = Label(frm, text=txt, font=font, fg=txt_clr, bg=bg_clr)
|
|
660
|
+
caption_lbl.pack(pady=(0, 4))
|
|
661
|
+
tw.lift()
|
|
662
|
+
w._hover_toplevel = tw
|
|
663
|
+
elif hover_fg_clr is not None or hover_font is not None:
|
|
664
|
+
w.config(fg=hover_fg_clr, font=hover_font)
|
|
665
|
+
|
|
666
|
+
def _hover_leave(e):
|
|
667
|
+
w = e.widget
|
|
668
|
+
if getattr(w, "_hover_toplevel", None) is not None:
|
|
669
|
+
try:
|
|
670
|
+
w._hover_toplevel.destroy()
|
|
671
|
+
except tkinter.TclError:
|
|
672
|
+
pass
|
|
673
|
+
w._hover_toplevel = None
|
|
674
|
+
if hover_fg_clr is not None or hover_font is not None:
|
|
675
|
+
w.config(fg=txt_clr, bg=bg_clr, font=font)
|
|
676
|
+
|
|
677
|
+
def on_enter(e):
|
|
678
|
+
if hover_img is not None:
|
|
679
|
+
_hover_enter(e)
|
|
680
|
+
else:
|
|
681
|
+
e.widget.config(fg=hover_fg_clr, font=hover_font)
|
|
682
|
+
|
|
683
|
+
def on_leave(e):
|
|
684
|
+
if hover_img is not None:
|
|
685
|
+
_hover_leave(e)
|
|
686
|
+
else:
|
|
687
|
+
e.widget.config(fg=txt_clr, bg=bg_clr, font=font)
|
|
688
|
+
|
|
689
|
+
anchor = 'w' if anchor is None else anchor
|
|
690
|
+
if isinstance(img, str) and img in MENU_ICONS.keys():
|
|
691
|
+
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
692
|
+
else:
|
|
693
|
+
img = None
|
|
694
|
+
if img is not None and compound is None:
|
|
695
|
+
compound = 'left'
|
|
696
|
+
|
|
697
|
+
lbl = Label(parent,
|
|
698
|
+
text=txt,
|
|
699
|
+
font=font,
|
|
700
|
+
fg=txt_clr,
|
|
701
|
+
bg=bg_clr,
|
|
702
|
+
justify=justify,
|
|
703
|
+
relief=relief,
|
|
704
|
+
cursor=cursor,
|
|
705
|
+
anchor=anchor,
|
|
706
|
+
image=img,
|
|
707
|
+
compound=compound,
|
|
708
|
+
padx=padx,
|
|
709
|
+
pady=pady)
|
|
710
|
+
|
|
711
|
+
if img is not None:
|
|
712
|
+
lbl.image = img
|
|
713
|
+
|
|
714
|
+
if width is not None:
|
|
715
|
+
lbl.configure(width=width)
|
|
716
|
+
|
|
717
|
+
if link is not None:
|
|
718
|
+
lbl.bind("<Button-1>", lambda e: callback(link))
|
|
719
|
+
|
|
720
|
+
elif tooltip_key in TOOLTIPS.keys():
|
|
721
|
+
CreateToolTip(widget=lbl, text=TOOLTIPS[tooltip_key])
|
|
722
|
+
|
|
723
|
+
if hover_font is not None or hover_fg_clr is not None or hover_img is not None:
|
|
724
|
+
lbl.bind(TkBinds.ENTER.value, on_enter)
|
|
725
|
+
lbl.bind(TkBinds.LEAVE.value, on_leave)
|
|
726
|
+
|
|
727
|
+
return lbl
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def get_menu_icons():
|
|
731
|
+
menu_icons = copy(MENU_ICONS)
|
|
732
|
+
for k in menu_icons.keys():
|
|
733
|
+
menu_icons[k]["img"] = ImageTk.PhotoImage(image=PIL.Image.open(os.path.join(os.path.dirname(__file__), menu_icons[k]["icon_path"])))
|
|
734
|
+
return menu_icons
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class SimBASeperator(Frame):
|
|
739
|
+
|
|
740
|
+
def __init__(self,
|
|
741
|
+
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
742
|
+
color: Optional[str] = 'black',
|
|
743
|
+
orient: Literal['horizontal', 'vertical'] = 'horizontal',
|
|
744
|
+
cursor: Optional[str] = None,
|
|
745
|
+
borderwidth: Optional[int] = None,
|
|
746
|
+
takefocus: Optional[int] = 0,
|
|
747
|
+
height: int = 1,
|
|
748
|
+
relief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = "flat"):
|
|
749
|
+
|
|
750
|
+
super().__init__(master=parent, height=height, bg=color if color is not None else "black", relief=relief, bd=borderwidth if borderwidth is not None else None)
|
|
751
|
+
style = tk.ttk.Style()
|
|
752
|
+
style_name = "SimBA.TSeparator"
|
|
753
|
+
|
|
754
|
+
style.configure(style_name,
|
|
755
|
+
background=color if color is not None else "black")
|
|
756
|
+
|
|
757
|
+
seperator = tk.ttk.Separator(self,
|
|
758
|
+
orient=orient,
|
|
759
|
+
style=style_name,
|
|
760
|
+
cursor=cursor,
|
|
761
|
+
takefocus=takefocus)
|
|
762
|
+
|
|
763
|
+
if orient == "horizontal":
|
|
764
|
+
seperator.pack(fill="x", expand=True)
|
|
765
|
+
else:
|
|
766
|
+
seperator.pack(fill="y", expand=True)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
class SimBADropDown(Frame):
|
|
770
|
+
"""
|
|
771
|
+
Create a dropdown menu widget with optional searchable functionality.
|
|
772
|
+
|
|
773
|
+
This class creates a ttk.Combobox dropdown menu with a label, supporting both readonly and searchable modes.
|
|
774
|
+
When searchable mode is enabled, users can type to filter the dropdown options.
|
|
775
|
+
|
|
776
|
+
:param parent (Frame | Canvas | LabelFrame | Toplevel | Tk): The parent widget container.
|
|
777
|
+
:param dropdown_options (Iterable[Any] | List[Any] | Tuple[Any]): List of options to display in the dropdown menu.
|
|
778
|
+
:param label (str, optional): Text label displayed next to the dropdown. Default: None.
|
|
779
|
+
:param label_width (int, optional): Width of the label in characters. Default: None.
|
|
780
|
+
:param label_font (tuple, optional): Font tuple for the label. Default: Formats.FONT_REGULAR.value.
|
|
781
|
+
:param label_bg_clr (str, optional): Background color for the label. Default: None.
|
|
782
|
+
:param dropdown_font_size (int, optional): Font size for the dropdown text. Default: None.
|
|
783
|
+
:param justify (str): Text justification in the dropdown ('left', 'center', 'right'). Default: 'center'.
|
|
784
|
+
:param dropdown_width (int, optional): Width of the dropdown widget in characters. Default: None.
|
|
785
|
+
:param command (Callable, optional): Callback function to execute when an option is selected. Default: None.
|
|
786
|
+
:param value (Any, optional): Initial selected value for the dropdown. Default: None.
|
|
787
|
+
:param state (str, optional): Initial state of the dropdown ('normal', 'disabled'). Default: None.
|
|
788
|
+
:param searchable (bool): If True, allows typing to filter dropdown options. Default: False.
|
|
789
|
+
:param tooltip_txt (str, optional): Tooltip text to display on hover. Default: None.
|
|
790
|
+
:param tooltip_key (str, optional): Key for tooltip lookup in TOOLTIPS dictionary. For dictionary, see `simba.assets.lookups.tooptips.json`. Default: None.
|
|
791
|
+
|
|
792
|
+
:example:
|
|
793
|
+
>>> dropdown = SimBADropDown(parent=parent_frm, dropdown_options=['Option 1', 'Option 2', 'Option 3'], label='Select option:', searchable=True)
|
|
794
|
+
>>> selected = dropdown.get_value()
|
|
795
|
+
"""
|
|
796
|
+
def __init__(self,
|
|
797
|
+
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
|
|
798
|
+
dropdown_options: Union[Iterable[Any], List[Any], Tuple[Any]],
|
|
799
|
+
label: Optional[str] = None,
|
|
800
|
+
label_width: Optional[int] = None,
|
|
801
|
+
label_font: tuple = Formats.FONT_REGULAR.value,
|
|
802
|
+
label_bg_clr: Optional[str] = None,
|
|
803
|
+
dropdown_font_size: Optional[int] = None,
|
|
804
|
+
justify: Literal['left', 'right', 'center'] = 'center',
|
|
805
|
+
img: Optional[str] = None,
|
|
806
|
+
dropdown_width: Optional[int] = None,
|
|
807
|
+
command: Callable = None,
|
|
808
|
+
value: Optional[Any] = None,
|
|
809
|
+
state: Optional[str] = None,
|
|
810
|
+
searchable: bool = False,
|
|
811
|
+
tooltip_txt: Optional[str] = None,
|
|
812
|
+
tooltip_key: Optional[str] = None):
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
super().__init__(master=parent)
|
|
816
|
+
self.dropdown_var = StringVar()
|
|
817
|
+
self.columnconfigure(0, weight=0)
|
|
818
|
+
self.columnconfigure(1, weight=0)
|
|
819
|
+
if img is not None:
|
|
820
|
+
self.columnconfigure(2, weight=0)
|
|
821
|
+
self.dropdown_img_lbl = SimBALabel(parent=self, txt='', txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=None, anchor='w', img=img, compound='left')
|
|
822
|
+
self.dropdown_img_lbl.grid(row=0, column=0, sticky="w")
|
|
823
|
+
else:
|
|
824
|
+
self.dropdown_img_lbl = None
|
|
825
|
+
self.dropdown_lbl = SimBALabel(parent=self, txt=label, txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=label_width, anchor='w')
|
|
826
|
+
self.dropdown_lbl.grid(row=0, column=1, sticky=NW)
|
|
827
|
+
self.dropdown_options = dropdown_options
|
|
828
|
+
self.original_options = deepcopy(self.dropdown_options)
|
|
829
|
+
self.command, self.searchable = command, searchable
|
|
830
|
+
if dropdown_font_size is None:
|
|
831
|
+
drop_down_font = None
|
|
832
|
+
else:
|
|
833
|
+
drop_down_font = ("Poppins", dropdown_font_size)
|
|
834
|
+
self.combobox_state = 'normal' if searchable else "readonly"
|
|
835
|
+
self.dropdown = Combobox(self, textvariable=self.dropdown_var, font=drop_down_font, values=self.dropdown_options, state=self.combobox_state, width=dropdown_width, justify=justify)
|
|
836
|
+
if searchable: self.bind_combobox_keys()
|
|
837
|
+
self.dropdown.grid(row=0, column=2, sticky="w")
|
|
838
|
+
if value is not None: self.set_value(value=value)
|
|
839
|
+
if command is not None:
|
|
840
|
+
self.command = command
|
|
841
|
+
self.dropdown.bind("<<ComboboxSelected>>", self.on_select)
|
|
842
|
+
if state == 'disabled':
|
|
843
|
+
self.disable()
|
|
844
|
+
if isinstance(tooltip_txt, str):
|
|
845
|
+
CreateToolTip(widget=self.dropdown_lbl, text=tooltip_txt)
|
|
846
|
+
elif tooltip_key in TOOLTIPS.keys():
|
|
847
|
+
CreateToolTip(widget=self.dropdown_lbl, text=TOOLTIPS[tooltip_key])
|
|
848
|
+
if img is not None:
|
|
849
|
+
CreateToolTip(widget=self.dropdown_img_lbl, text=TOOLTIPS[tooltip_key])
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def set_value(self, value: Any):
|
|
853
|
+
self.dropdown_var.set(value)
|
|
854
|
+
|
|
855
|
+
def get_value(self):
|
|
856
|
+
return self.dropdown_var.get()
|
|
857
|
+
|
|
858
|
+
def enable(self):
|
|
859
|
+
self.dropdown.configure(state="normal")
|
|
860
|
+
|
|
861
|
+
def disable(self):
|
|
862
|
+
self.dropdown.configure(state="disabled")
|
|
863
|
+
|
|
864
|
+
def getChoices(self):
|
|
865
|
+
return self.dropdown_var.get()
|
|
866
|
+
|
|
867
|
+
def setChoices(self, choice):
|
|
868
|
+
self.dropdown_var.set(choice)
|
|
869
|
+
|
|
870
|
+
def on_select(self, event):
|
|
871
|
+
selected_value = self.dropdown_var.get()
|
|
872
|
+
self.command(selected_value)
|
|
873
|
+
if self.searchable: self.dropdown['values'] = self.original_options
|
|
874
|
+
|
|
875
|
+
def set_width(self, width: int):
|
|
876
|
+
self.dropdown.configure(width=width)
|
|
877
|
+
|
|
878
|
+
def change_options(self, values: List[str], set_index: Optional[int] = None, set_str: Optional[str] = None, auto_change_width: Optional[bool] = True):
|
|
879
|
+
self.dropdown_var.set('')
|
|
880
|
+
self.dropdown['values'] = values
|
|
881
|
+
if isinstance(set_index, int) and (0 <= set_index <= len(values) - 1):
|
|
882
|
+
self.dropdown_var.set(values[set_index])
|
|
883
|
+
elif (set_str is not None) and (set_str in values):
|
|
884
|
+
self.dropdown_var.set(set_str)
|
|
885
|
+
elif self.searchable and (set_str is not None):
|
|
886
|
+
self.dropdown_var.set(set_str)
|
|
887
|
+
else:
|
|
888
|
+
self.dropdown_var.set(values[0])
|
|
889
|
+
if auto_change_width: self.set_width(width=max(5, max(len(s) for s in values)))
|
|
890
|
+
if self.dropdown['values'] == ('',) or len(values) == 0 and not self.searchable:
|
|
891
|
+
self.disable()
|
|
892
|
+
else:
|
|
893
|
+
self.enable()
|
|
894
|
+
self.dropdown.configure(state='normal' if self.searchable else "readonly")
|
|
895
|
+
|
|
896
|
+
def bind_combobox_keys(self):
|
|
897
|
+
self.dropdown.bind("<KeyRelease>", self._on_key_release)
|
|
898
|
+
if self.searchable:
|
|
899
|
+
self.dropdown.bind("<Button-1>", lambda e: self._reset_to_all_options())
|
|
900
|
+
|
|
901
|
+
def _on_key_release(self, event):
|
|
902
|
+
if event.keysym in ['Up', 'Down', 'Left', 'Right', 'Tab', 'Return']:
|
|
903
|
+
return
|
|
904
|
+
try:
|
|
905
|
+
cursor_pos = self.dropdown.index(INSERT)
|
|
906
|
+
except:
|
|
907
|
+
cursor_pos = len(self.dropdown_var.get())
|
|
908
|
+
current_text = self.dropdown_var.get()
|
|
909
|
+
filtered_options = [x for x in self.original_options if current_text.lower() in x.lower()]
|
|
910
|
+
if current_text != '':
|
|
911
|
+
self.dropdown['values'] = filtered_options
|
|
912
|
+
# Restore the text and cursor position
|
|
913
|
+
self.dropdown_var.set(current_text)
|
|
914
|
+
self.dropdown.icursor(cursor_pos)
|
|
915
|
+
else:
|
|
916
|
+
self.dropdown['values'] = self.original_options
|
|
917
|
+
self.dropdown_var.set('')
|
|
918
|
+
self.dropdown.icursor(0)
|
|
919
|
+
|
|
920
|
+
def _reset_to_all_options(self):
|
|
921
|
+
if self.searchable and self.dropdown_var.get() == '':
|
|
922
|
+
self.dropdown['values'] = self.original_options
|
|
923
|
+
|
|
924
|
+
class DropDownMenu(Frame):
|
|
925
|
+
|
|
926
|
+
"""
|
|
927
|
+
Legacy, use :func:`simba.ui.tkinter_functions.SimBADropDown`.
|
|
928
|
+
"""
|
|
929
|
+
def __init__(self,
|
|
930
|
+
parent=None,
|
|
931
|
+
dropdownLabel="",
|
|
932
|
+
choice_dict=None,
|
|
933
|
+
labelwidth="",
|
|
934
|
+
com=None,
|
|
935
|
+
val: Optional[Any] = None,
|
|
936
|
+
**kw):
|
|
937
|
+
Frame.__init__(self, master=parent, **kw)
|
|
938
|
+
self.dropdownvar = StringVar()
|
|
939
|
+
self.lblName = Label(self, text=dropdownLabel, width=labelwidth, anchor=W, font=Formats.FONT_REGULAR.value)
|
|
940
|
+
self.lblName.grid(row=0, column=0)
|
|
941
|
+
self.choices = choice_dict
|
|
942
|
+
self.popupMenu = OptionMenu(self, self.dropdownvar, *self.choices, command=com)
|
|
943
|
+
self.popupMenu.grid(row=0, column=1)
|
|
944
|
+
if val is not None:
|
|
945
|
+
self.setChoices(val)
|
|
946
|
+
def getChoices(self):
|
|
947
|
+
return self.dropdownvar.get()
|
|
948
|
+
def setChoices(self, choice):
|
|
949
|
+
self.dropdownvar.set(choice)
|
|
950
|
+
def enable(self):
|
|
951
|
+
self.popupMenu.configure(state="normal")
|
|
952
|
+
def disable(self):
|
|
953
|
+
self.popupMenu.configure(state="disable")
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
class FileSelect(Frame):
|
|
958
|
+
def __init__(self,
|
|
959
|
+
parent=None,
|
|
960
|
+
fileDescription="",
|
|
961
|
+
color=None,
|
|
962
|
+
title: Optional[str] = None,
|
|
963
|
+
lblwidth: Optional[int] = None,
|
|
964
|
+
file_types=None,
|
|
965
|
+
bg_clr: Optional[str] = 'white',
|
|
966
|
+
dropdown: Union[DropDownMenu, SimBADropDown] = None,
|
|
967
|
+
entry_width: Optional[int] = 20,
|
|
968
|
+
status: Optional[str] = None,
|
|
969
|
+
lbl_icon: Optional[str] = None,
|
|
970
|
+
font: Tuple = Formats.FONT_REGULAR.value,
|
|
971
|
+
initialdir: Optional[Union[str, os.PathLike]] = None,
|
|
972
|
+
initial_path: Optional[Union[str, os.PathLike]] = None,
|
|
973
|
+
tooltip_txt: Optional[str] = None,
|
|
974
|
+
tooltip_key: Optional[str] = None,
|
|
975
|
+
**kw):
|
|
976
|
+
|
|
977
|
+
self.title, self.dropdown, self.initialdir = title, dropdown, initialdir
|
|
978
|
+
self.file_type = file_types
|
|
979
|
+
self.color = color if color is not None else "black"
|
|
980
|
+
self.lblwidth = lblwidth if lblwidth is not None else 0
|
|
981
|
+
self.parent = parent
|
|
982
|
+
Frame.__init__(self, master=parent, **kw)
|
|
983
|
+
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse"]["icon_path"]))
|
|
984
|
+
self.columnconfigure(0, weight=0)
|
|
985
|
+
self.columnconfigure(1, weight=0)
|
|
986
|
+
self.columnconfigure(2, weight=0)
|
|
987
|
+
if lbl_icon is not None:
|
|
988
|
+
self.columnconfigure(3, weight=0)
|
|
989
|
+
self.lbl_icon = SimBALabel(parent=self, txt='', txt_clr='black', font=font, width=None, anchor='w', img=lbl_icon, compound=None)
|
|
990
|
+
self.lbl_icon.grid(row=0, column=0, sticky="w")
|
|
991
|
+
else:
|
|
992
|
+
self.lbl_icon = None
|
|
993
|
+
self.filePath = StringVar()
|
|
994
|
+
self.lblName = Label(self, text=fileDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=Formats.FONT_REGULAR.value)
|
|
995
|
+
self.lblName.grid(row=0, column=1, sticky=W)
|
|
996
|
+
self.entPath = Label(self, textvariable=self.filePath, relief=SUNKEN, font=Formats.FONT_REGULAR.value, bg=bg_clr, width=entry_width)
|
|
997
|
+
self.entPath.grid(row=0, column=2)
|
|
998
|
+
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FILE_BTN_TEXT.value, font=font, cmd=self.setFilePath, img=browse_icon)
|
|
999
|
+
self.btnFind.grid(row=0, column=3)
|
|
1000
|
+
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
|
|
1001
|
+
if initial_path is not None:
|
|
1002
|
+
self.filePath.set(initial_path)
|
|
1003
|
+
if status is not None:
|
|
1004
|
+
self.set_state(setstatus=status)
|
|
1005
|
+
if tooltip_txt is not None and isinstance(tooltip_txt, str):
|
|
1006
|
+
CreateToolTip(widget=self.lblName, text=tooltip_txt)
|
|
1007
|
+
elif tooltip_key in TOOLTIPS.keys():
|
|
1008
|
+
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
|
|
1009
|
+
|
|
1010
|
+
def setFilePath(self):
|
|
1011
|
+
if self.initialdir is not None:
|
|
1012
|
+
if not os.path.isdir(self.initialdir):
|
|
1013
|
+
self.initialdir = None
|
|
1014
|
+
else:
|
|
1015
|
+
pass
|
|
1016
|
+
|
|
1017
|
+
if self.file_type:
|
|
1018
|
+
file_selected = askopenfilename(title=self.title, parent=self.parent, filetypes=self.file_type, initialdir=self.initialdir)
|
|
1019
|
+
else:
|
|
1020
|
+
file_selected = askopenfilename(title=self.title, parent=self.parent, initialdir=self.initialdir)
|
|
1021
|
+
if file_selected:
|
|
1022
|
+
if self.dropdown is not None:
|
|
1023
|
+
_, name, _ = get_fn_ext(filepath=file_selected)
|
|
1024
|
+
self.dropdown.setChoices(name)
|
|
1025
|
+
self.filePath.set(name)
|
|
1026
|
+
else:
|
|
1027
|
+
self.filePath.set(file_selected)
|
|
1028
|
+
self.entPath.configure(width=len(file_selected)+10)
|
|
1029
|
+
|
|
1030
|
+
else:
|
|
1031
|
+
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
|
|
1032
|
+
self.entPath.configure(width=len(Defaults.NO_FILE_SELECTED_TEXT.value) + 10)
|
|
1033
|
+
|
|
1034
|
+
@property
|
|
1035
|
+
def file_path(self):
|
|
1036
|
+
return self.filePath.get()
|
|
1037
|
+
|
|
1038
|
+
def set_state(self, setstatus):
|
|
1039
|
+
self.entPath.config(state=setstatus)
|
|
1040
|
+
self.btnFind["state"] = setstatus
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def SimBARadioButton(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
|
|
1044
|
+
txt: str,
|
|
1045
|
+
variable: Union[BooleanVar, StringVar],
|
|
1046
|
+
txt_clr: Optional[str] = 'black',
|
|
1047
|
+
font: Optional[Tuple] = Formats.FONT_REGULAR.value,
|
|
1048
|
+
compound: Optional[str] = 'left',
|
|
1049
|
+
img: Optional[Union[ImageTk.PhotoImage, str]] = None,
|
|
1050
|
+
enabled: Optional[bool] = True,
|
|
1051
|
+
tooltip_txt: Optional[str] = None,
|
|
1052
|
+
value: bool = False,
|
|
1053
|
+
cmd: Optional[Callable] = None,
|
|
1054
|
+
cmd_kwargs: Optional[Dict[Any, Any]] = None) -> Radiobutton:
|
|
1055
|
+
|
|
1056
|
+
if isinstance(img, str):
|
|
1057
|
+
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
|
|
1058
|
+
|
|
1059
|
+
if cmd_kwargs is None:
|
|
1060
|
+
cmd_kwargs = {}
|
|
1061
|
+
|
|
1062
|
+
def execute_command():
|
|
1063
|
+
if cmd:
|
|
1064
|
+
evaluated_kwargs = {k: (v() if callable(v) else v) for k, v in cmd_kwargs.items()}
|
|
1065
|
+
cmd(**evaluated_kwargs)
|
|
1066
|
+
|
|
1067
|
+
if cmd is not None:
|
|
1068
|
+
command = execute_command
|
|
1069
|
+
else:
|
|
1070
|
+
command = None
|
|
1071
|
+
|
|
1072
|
+
btn = Radiobutton(parent,
|
|
1073
|
+
text=txt,
|
|
1074
|
+
font=font,
|
|
1075
|
+
image=img,
|
|
1076
|
+
fg=txt_clr,
|
|
1077
|
+
variable=variable,
|
|
1078
|
+
value=value,
|
|
1079
|
+
compound=compound,
|
|
1080
|
+
command=command)
|
|
1081
|
+
|
|
1082
|
+
if img is not None:
|
|
1083
|
+
btn.image = img
|
|
1084
|
+
|
|
1085
|
+
if not enabled:
|
|
1086
|
+
btn.config(state=DISABLED)
|
|
1087
|
+
|
|
1088
|
+
if tooltip_txt is not None:
|
|
1089
|
+
CreateToolTip(widget=btn, text=tooltip_txt)
|
|
1090
|
+
|
|
1091
|
+
return btn
|