__author__ = "Simon Nilsson; sronilsson@gmail.com"
import os.path
import platform
import threading
import tkinter
import tkinter as tk
import webbrowser
from copy import copy, deepcopy
from tkinter import *
from tkinter.filedialog import askdirectory, askopenfilename
from tkinter.ttk import Combobox
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
try:
from typing import Literal
except:
from typing_extensions import Literal
import numpy as np
import PIL.Image
from PIL import ImageTk
from simba.utils.checks import check_if_valid_img
from simba.utils.enums import Defaults, Formats, TkBinds
from simba.utils.lookups import get_icons_paths, get_tooltips
from simba.utils.read_write import get_fn_ext
MENU_ICONS = get_icons_paths()
TOOLTIPS = get_tooltips()
[docs]def onMousewheel(event, canvas):
try:
scrollSpeed = event.delta
if platform.system() == "Darwin":
scrollSpeed = event.delta
elif platform.system() == "Windows":
scrollSpeed = int(event.delta / 120)
canvas.yview_scroll(-1 * (scrollSpeed), "units")
except:
pass
[docs]def bindToMousewheel(event, canvas):
canvas.bind_all("<MouseWheel>", lambda event: onMousewheel(event, canvas))
[docs]def unbindToMousewheel(event, canvas):
canvas.unbind_all("<MouseWheel>")
class DropDownMenu(Frame):
"""
Legacy, use :func:`simba.ui.tkinter_functions.SimBADropDown`.
"""
def __init__(self,
parent=None,
dropdownLabel="",
choice_dict=None,
labelwidth="",
com=None,
val: Optional[Any] = None,
tooltip_key: Optional[str] = None,
**kw):
Frame.__init__(self, master=parent, **kw)
self.dropdownvar = StringVar()
self.lblName = Label(self, text=dropdownLabel, width=labelwidth, anchor=W, font=Formats.FONT_REGULAR.value)
self.lblName.grid(row=0, column=0)
if tooltip_key is not None and tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
self.choices = choice_dict
self.popupMenu = OptionMenu(self, self.dropdownvar, *self.choices, command=com)
self.popupMenu.grid(row=0, column=1)
if val is not None:
self.setChoices(val)
def getChoices(self):
return self.dropdownvar.get()
def setChoices(self, choice):
self.dropdownvar.set(choice)
def enable(self):
self.popupMenu.configure(state="normal")
def disable(self):
self.popupMenu.configure(state="disable")
[docs]class SimBAScaleBar(Frame):
def __init__(self,
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
label: Optional[str] = None,
label_width: Optional[int] = None,
orient: Literal['horizontal', 'vertical'] = HORIZONTAL,
length: int = 200,
value: Optional[int] = 95,
showvalue: bool = True,
label_clr: str = 'black',
lbl_font: tuple = Formats.FONT_REGULAR.value,
scale_font: tuple = Formats.FONT_REGULAR_ITALICS.value,
lbl_img: Optional[str] = None,
from_: int = -1,
resolution: int = 1,
to: int = 100,
tickinterval: Optional[int] = None,
troughcolor: Optional[str] = None,
activebackground: Optional[str] = None,
sliderrelief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = 'flat'):
super().__init__(master=parent)
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=0)
if lbl_img is not None:
self.columnconfigure(2, weight=0)
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)
self.lbl_lbl.grid(row=0, column=0, sticky=SW)
else:
self.lbl_lbl = None
self.scale = Scale(self,
from_=from_,
to=to,
orient=orient,
length=length,
font=scale_font,
sliderrelief=sliderrelief,
troughcolor=troughcolor,
tickinterval=tickinterval,
resolution=resolution,
showvalue=showvalue,
activebackground=activebackground)
if label is not None:
self.lbl = SimBALabel(parent=self, txt=label, font=lbl_font, txt_clr=label_clr, width=label_width)
self.lbl.grid(row=0, column=1, sticky=SW)
self.scale.grid(row=0, column=2, sticky=NW)
if value is not None:
self.set_value(value=value)
[docs] def set_value(self, value: int):
self.scale.set(value)
[docs] def get_value(self) -> Union[int, float]:
return self.scale.get()
[docs] def get(self) -> Union[int, float]:
## Alternative for ``get_value`` for legacy reasons.
return self.scale.get()
[docs]class Entry_Box(Frame):
def __init__(self,
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
fileDescription: Optional[str] = "",
labelwidth: Union[int, str] = None,
label_bg_clr: Optional[str] = None,
status: Literal["normal", "disabled", "readonly"] = 'normal',
validation: Optional[Union[Callable, str]] = None,
trace: Optional[Callable] = None,
entry_box_width: Union[int, str] = None,
entry_box_clr: Optional[str] = 'white',
img: Optional[str] = None,
value: Optional[Any] = None,
bg_clr: Optional[str] = None,
label_font: tuple = Formats.FONT_REGULAR.value,
entry_font: tuple = Formats.FONT_REGULAR.value,
tooltip_key: Optional[str] = None,
justify: Literal["left", "center", "right"] = 'left',
cmd: Optional[Callable] = None,
allow_blank: bool = False,
**kw):
super(Entry_Box, self).__init__(master=parent)
self.validation_methods = {"numeric": (self.register(form_validator_is_numeric), "%P", "%d")}
self.status = status if status is not None else NORMAL
self.labelname = fileDescription
Frame.__init__(self, master=parent, **kw)
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=0)
if img is not None:
self.columnconfigure(2, weight=0)
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)
self.entrybox_img_lbl.grid(row=0, column=0, sticky="w")
else:
self.entrybox_img_lbl = None
self.filePath = StringVar()
self.lblName = Label(self, text=fileDescription, width=labelwidth, anchor=W, font=label_font, bg=label_bg_clr)
self.lblName.grid(row=0, column=1)
if tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
if not entry_box_width:
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)
else:
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)
self.entPath.grid(row=0, column=2)
if value is not None:
self.entry_set(val=value)
if cmd is not None:
self.bind_combobox_keys()
self.cmd = cmd
self.allow_blank = allow_blank
if trace is not None:
self.filePath.trace_add('write', lambda *a: trace(self))
if bg_clr is not None: self.set_bg_clr(clr=bg_clr)
[docs] def bind_combobox_keys(self):
self.entPath.bind("<KeyRelease>", self.run_cmd)
@property
def entry_get(self):
return self.entPath.get()
[docs] def entry_set(self, val):
self.filePath.set(val)
[docs] def set_state(self, setstatus):
self.entPath.config(state=setstatus)
[docs] def destroy(self):
try:
if self.entrybox_img_lbl is not None:
self.entrybox_img_lbl.destroy()
self.lblName.destroy()
self.entPath.destroy()
except:
pass
[docs] def run_cmd(self, x):
self.cmd(self.entry_get.strip())
[docs] def set_bg_clr(self, clr: str):
self.entPath.configure(bg=clr)
[docs]class FolderSelect(Frame):
def __init__(self,
parent: Union[Frame, LabelFrame, Canvas, Toplevel],
folderDescription: Optional[str] = "",
color: Optional[str] = None,
label_bg_clr: Optional[str] = None,
font: Tuple = Formats.FONT_REGULAR.value,
title: Optional[str] = "",
lblwidth: Optional[int] = 0,
bg_clr: Optional[str] = 'white',
entry_width: Optional[int] = 20,
lbl_icon: Optional[str] = None,
initialdir: Optional[Union[str, os.PathLike]] = None,
tooltip_txt: Optional[str] = None,
tooltip_key: Optional[str] = None,
**kw):
self.title, self.initialdir = title, initialdir
self.color = color if color is not None else "black"
self.lblwidth = lblwidth if lblwidth is not None else 0
self.parent = parent
Frame.__init__(self, master=parent, **kw)
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse_small"]["icon_path"]))
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=0)
self.columnconfigure(2, weight=0)
if lbl_icon is not None:
self.columnconfigure(3, weight=0)
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)
self.lbl_icon.grid(row=0, column=0, sticky="w")
else:
self.lbl_icon = None
self.folderPath = StringVar()
self.lblName = Label(self, text=folderDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=font, bg=label_bg_clr)
self.lblName.grid(row=0, column=1, sticky=NW)
self.entPath = Label(self, textvariable=self.folderPath, relief=SUNKEN, font=font, bg=bg_clr, width=entry_width)
self.entPath.grid(row=0, column=2, sticky=NW)
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FOLDER_BTN_TEXT.value, font=font, cmd=self.setFolderPath, img=browse_icon)
self.btnFind.image = browse_icon
self.btnFind.grid(row=0, column=3, sticky=NW)
self.folderPath.set("No folder selected")
if tooltip_txt is not None and isinstance(tooltip_txt, str):
CreateToolTip(widget=self.lblName, text=tooltip_txt)
elif tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
[docs] def setFolderPath(self):
if self.initialdir is not None:
if not os.path.isdir(self.initialdir):
self.initialdir = None
else:
pass
folder_selected = askdirectory(title=str(self.title), parent=self.parent, initialdir=self.initialdir)
if folder_selected:
self.folderPath.set(folder_selected)
self.entPath.configure(width=len(folder_selected)+10)
else:
self.folderPath.set("No folder selected")
self.entPath.configure(width=len("No folder selected")+10)
@property
def folder_path(self):
return self.folderPath.get()
[docs] def set_folder_path(self, folder_path):
self.folderPath.set(folder_path)
self.entPath.configure(width=len(folder_path) + 10)
[docs]def CreateLabelFrameWithIcon(parent: Union[Toplevel, LabelFrame, Canvas, Frame],
header: str,
icon_name: str,
padx: Optional[int] = None,
pady: Optional[int] = None,
relief: str = 'solid',
width: Optional[int] = None,
bg: Optional[str] = None,
font: tuple = Formats.FONT_HEADER.value,
icon_link: Optional[Union[str, None]] = LabelFrame,
tooltip_key: Optional[str] = None):
if icon_name in MENU_ICONS.keys():
icon = PIL.Image.open(MENU_ICONS[icon_name]["icon_path"])
icon = ImageTk.PhotoImage(icon)
else:
icon = None
frm = Frame(parent, bg=bg)
label_image = Label(frm, image=icon, bg=bg)
label_image.image = icon
if icon_link:
label_image.bind("<Button-1>", lambda e: callback(icon_link))
label_image.grid(row=0, column=0)
label_text = Label(frm, text=header, font=font, bg=bg)
label_text.grid(row=0, column=1)
lbl_frm = LabelFrame(parent, labelwidget=frm, relief=relief, width=width, padx=padx or 0, pady=pady or 0)
if tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=label_text, text=TOOLTIPS[tooltip_key])
if bg is not None:
lbl_frm.configure(bg=bg)
return lbl_frm
[docs]def callback(url):
webbrowser.open_new(url)
[docs]def create_scalebar(
parent: Frame, name: str, min: int, max: int, cmd: object or None = None
):
scale = Scale(
parent,
from_=min,
to=max,
orient=HORIZONTAL,
length=200,
label=name,
command=cmd,
)
scale.set(0)
return scale
[docs]def SimbaCheckbox(parent: Union[Frame, Toplevel, LabelFrame, Canvas],
txt: str,
txt_clr: Optional[str] = 'black',
bg_clr: Optional[str] = None,
txt_img: Optional[str] = None,
txt_img_location: Literal['left', 'right', 'top', 'bottom'] = RIGHT,
font: Optional[Tuple[str, str, int]] = Formats.FONT_REGULAR.value,
val: Optional[bool] = False,
state: Literal["disabled", 'normal'] = NORMAL,
indicatoron: bool = True,
cmd: Optional[Callable] = None,
tooltip_txt: Optional[str] = None,
tooltip_key: Optional[str] = None):
var = BooleanVar(value=False)
if val: var.set(True)
if isinstance(txt_img, str):
txt_img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[txt_img]["icon_path"]))
if cmd is None:
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)
else:
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)
if txt_img is not None:
cb.image = txt_img
if state == DISABLED:
cb.configure(state=DISABLED)
if isinstance(tooltip_txt, str):
CreateToolTip(widget=cb, text=tooltip_txt)
elif isinstance(tooltip_key, str) and tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=cb, text=TOOLTIPS[tooltip_key])
return cb, var
[docs]def SimBALabel(parent: Union[Frame, Canvas, LabelFrame, Toplevel],
txt: str,
txt_clr: str = 'black',
bg_clr: Optional[str] = None,
hover_fg_clr: Optional[str] = None,
font: tuple = Formats.FONT_REGULAR.value,
hover_font: Optional[Tuple] = None,
relief: str = FLAT,
compound: Optional[Literal['left', 'right', 'top', 'bottom', 'center']] = 'left',
justify: Optional[str] = None,
link: Optional[str] = None,
width: Optional[int] = None,
padx: Optional[int] = None,
pady: Optional[int] = None,
cursor: Optional[str] = None,
img: Optional[str] = None,
anchor: Optional[str] = None,
tooltip_key: Optional[str] = None,
hover_img: Optional[np.ndarray] = None):
def _hover_enter(e):
w = e.widget
if hover_img is not None and check_if_valid_img(data=hover_img, raise_error=False):
arr = np.asarray(hover_img, dtype=np.uint8)
if arr.ndim == 3 and arr.shape[2] == 3:
arr = arr[:, :, ::-1]
pil_img = PIL.Image.fromarray(arr)
photo = ImageTk.PhotoImage(pil_img)
tw = Toplevel(w)
tw.wm_overrideredirect(True)
tw.attributes("-topmost", True)
tw.wm_geometry("+%d+%d" % (w.winfo_rootx() + w.winfo_width() + 4, w.winfo_rooty()))
frm = Frame(tw, relief="solid", bd=2, bg="#f0f0f0")
frm.pack(padx=2, pady=2)
lbl_hover = Label(frm, image=photo, bg="#f0f0f0")
lbl_hover.image = photo
lbl_hover.pack(padx=4, pady=(4, 2))
caption_lbl = Label(frm, text=txt, font=font, fg=txt_clr, bg=bg_clr)
caption_lbl.pack(pady=(0, 4))
tw.lift()
w._hover_toplevel = tw
elif hover_fg_clr is not None or hover_font is not None:
w.config(fg=hover_fg_clr, font=hover_font)
def _hover_leave(e):
w = e.widget
if getattr(w, "_hover_toplevel", None) is not None:
try:
w._hover_toplevel.destroy()
except tkinter.TclError:
pass
w._hover_toplevel = None
if hover_fg_clr is not None or hover_font is not None:
w.config(fg=txt_clr, bg=bg_clr, font=font)
def on_enter(e):
if hover_img is not None:
_hover_enter(e)
else:
e.widget.config(fg=hover_fg_clr, font=hover_font)
def on_leave(e):
if hover_img is not None:
_hover_leave(e)
else:
e.widget.config(fg=txt_clr, bg=bg_clr, font=font)
anchor = 'w' if anchor is None else anchor
if isinstance(img, str) and img in MENU_ICONS.keys():
img = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS[img]["icon_path"]))
else:
img = None
if img is not None and compound is None:
compound = 'left'
lbl = Label(parent,
text=txt,
font=font,
fg=txt_clr,
bg=bg_clr,
justify=justify,
relief=relief,
cursor=cursor,
anchor=anchor,
image=img,
compound=compound,
padx=padx,
pady=pady)
if img is not None:
lbl.image = img
if width is not None:
lbl.configure(width=width)
if link is not None:
lbl.bind("<Button-1>", lambda e: callback(link))
elif tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=lbl, text=TOOLTIPS[tooltip_key])
if hover_font is not None or hover_fg_clr is not None or hover_img is not None:
lbl.bind(TkBinds.ENTER.value, on_enter)
lbl.bind(TkBinds.LEAVE.value, on_leave)
return lbl
[docs]class SimBASeperator(Frame):
def __init__(self,
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
color: Optional[str] = 'black',
orient: Literal['horizontal', 'vertical'] = 'horizontal',
cursor: Optional[str] = None,
borderwidth: Optional[int] = None,
takefocus: Optional[int] = 0,
height: int = 1,
relief: Literal["raised", "sunken", "flat", "ridge", "solid", "groove"] = "flat"):
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)
style = tk.ttk.Style()
style_name = "SimBA.TSeparator"
style.configure(style_name,
background=color if color is not None else "black")
seperator = tk.ttk.Separator(self,
orient=orient,
style=style_name,
cursor=cursor,
takefocus=takefocus)
if orient == "horizontal":
seperator.pack(fill="x", expand=True)
else:
seperator.pack(fill="y", expand=True)
[docs]class SimBADropDown(Frame):
"""
Create a dropdown menu widget with optional searchable functionality.
This class creates a ttk.Combobox dropdown menu with a label, supporting both readonly and searchable modes.
When searchable mode is enabled, users can type to filter the dropdown options.
:param parent (Frame | Canvas | LabelFrame | Toplevel | Tk): The parent widget container.
:param dropdown_options (Iterable[Any] | List[Any] | Tuple[Any]): List of options to display in the dropdown menu.
:param label (str, optional): Text label displayed next to the dropdown. Default: None.
:param label_width (int, optional): Width of the label in characters. Default: None.
:param label_font (tuple, optional): Font tuple for the label. Default: Formats.FONT_REGULAR.value.
:param label_bg_clr (str, optional): Background color for the label. Default: None.
:param dropdown_font_size (int, optional): Font size for the dropdown text. Default: None.
:param justify (str): Text justification in the dropdown ('left', 'center', 'right'). Default: 'center'.
:param dropdown_width (int, optional): Width of the dropdown widget in characters. Default: None.
:param command (Callable, optional): Callback function to execute when an option is selected. Default: None.
:param value (Any, optional): Initial selected value for the dropdown. Default: None.
:param state (str, optional): Initial state of the dropdown ('normal', 'disabled'). Default: None.
:param searchable (bool): If True, allows typing to filter dropdown options. Default: False.
:param tooltip_txt (str, optional): Tooltip text to display on hover. Default: None.
:param tooltip_key (str, optional): Key for tooltip lookup in TOOLTIPS dictionary. For dictionary, see `simba.assets.lookups.tooptips.json`. Default: None.
:example:
>>> dropdown = SimBADropDown(parent=parent_frm, dropdown_options=['Option 1', 'Option 2', 'Option 3'], label='Select option:', searchable=True)
>>> selected = dropdown.get_value()
"""
def __init__(self,
parent: Union[Frame, Canvas, LabelFrame, Toplevel, Tk],
dropdown_options: Union[Iterable[Any], List[Any], Tuple[Any]],
label: Optional[str] = None,
label_width: Optional[int] = None,
label_font: tuple = Formats.FONT_REGULAR.value,
label_bg_clr: Optional[str] = None,
dropdown_font_size: Optional[int] = None,
justify: Literal['left', 'right', 'center'] = 'center',
img: Optional[str] = None,
dropdown_width: Optional[int] = None,
command: Callable = None,
value: Optional[Any] = None,
state: Optional[str] = None,
searchable: bool = False,
tooltip_txt: Optional[str] = None,
tooltip_key: Optional[str] = None):
super().__init__(master=parent)
self.dropdown_var = StringVar()
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=0)
if img is not None:
self.columnconfigure(2, weight=0)
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')
self.dropdown_img_lbl.grid(row=0, column=0, sticky="w")
else:
self.dropdown_img_lbl = None
self.dropdown_lbl = SimBALabel(parent=self, txt=label, txt_clr='black', bg_clr=label_bg_clr, font=label_font, width=label_width, anchor='w')
self.dropdown_lbl.grid(row=0, column=1, sticky=NW)
self.dropdown_options = dropdown_options
self.original_options = deepcopy(self.dropdown_options)
self.command, self.searchable = command, searchable
if dropdown_font_size is None:
drop_down_font = None
else:
drop_down_font = ("Poppins", dropdown_font_size)
self.combobox_state = 'normal' if searchable else "readonly"
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)
if searchable: self.bind_combobox_keys()
self.dropdown.grid(row=0, column=2, sticky="w")
if value is not None: self.set_value(value=value)
if command is not None:
self.command = command
self.dropdown.bind("<<ComboboxSelected>>", self.on_select)
if state == 'disabled':
self.disable()
if isinstance(tooltip_txt, str):
CreateToolTip(widget=self.dropdown_lbl, text=tooltip_txt)
elif tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=self.dropdown_lbl, text=TOOLTIPS[tooltip_key])
if img is not None:
CreateToolTip(widget=self.dropdown_img_lbl, text=TOOLTIPS[tooltip_key])
[docs] def set_value(self, value: Any):
self.dropdown_var.set(value)
[docs] def get_value(self):
return self.dropdown_var.get()
[docs] def enable(self):
self.dropdown.configure(state="normal")
[docs] def disable(self):
self.dropdown.configure(state="disabled")
[docs] def getChoices(self):
return self.dropdown_var.get()
[docs] def setChoices(self, choice):
self.dropdown_var.set(choice)
[docs] def on_select(self, event):
selected_value = self.dropdown_var.get()
self.command(selected_value)
if self.searchable: self.dropdown['values'] = self.original_options
[docs] def set_width(self, width: int):
self.dropdown.configure(width=width)
[docs] def change_options(self, values: List[str], set_index: Optional[int] = None, set_str: Optional[str] = None, auto_change_width: Optional[bool] = True):
self.dropdown_var.set('')
self.dropdown['values'] = values
if isinstance(set_index, int) and (0 <= set_index <= len(values) - 1):
self.dropdown_var.set(values[set_index])
elif (set_str is not None) and (set_str in values):
self.dropdown_var.set(set_str)
elif self.searchable and (set_str is not None):
self.dropdown_var.set(set_str)
else:
self.dropdown_var.set(values[0])
if auto_change_width: self.set_width(width=max(5, max(len(s) for s in values)))
if self.dropdown['values'] == ('',) or len(values) == 0 and not self.searchable:
self.disable()
else:
self.enable()
self.dropdown.configure(state='normal' if self.searchable else "readonly")
[docs] def bind_combobox_keys(self):
self.dropdown.bind("<KeyRelease>", self._on_key_release)
if self.searchable:
self.dropdown.bind("<Button-1>", lambda e: self._reset_to_all_options())
def _on_key_release(self, event):
if event.keysym in ['Up', 'Down', 'Left', 'Right', 'Tab', 'Return']:
return
try:
cursor_pos = self.dropdown.index(INSERT)
except:
cursor_pos = len(self.dropdown_var.get())
current_text = self.dropdown_var.get()
filtered_options = [x for x in self.original_options if current_text.lower() in x.lower()]
if current_text != '':
self.dropdown['values'] = filtered_options
# Restore the text and cursor position
self.dropdown_var.set(current_text)
self.dropdown.icursor(cursor_pos)
else:
self.dropdown['values'] = self.original_options
self.dropdown_var.set('')
self.dropdown.icursor(0)
def _reset_to_all_options(self):
if self.searchable and self.dropdown_var.get() == '':
self.dropdown['values'] = self.original_options
[docs]class FileSelect(Frame):
def __init__(self,
parent=None,
fileDescription="",
color=None,
title: Optional[str] = None,
lblwidth: Optional[int] = None,
file_types=None,
bg_clr: Optional[str] = 'white',
dropdown: Union[DropDownMenu, SimBADropDown] = None,
entry_width: Optional[int] = 20,
status: Optional[str] = None,
lbl_icon: Optional[str] = None,
font: Tuple = Formats.FONT_REGULAR.value,
initialdir: Optional[Union[str, os.PathLike]] = None,
initial_path: Optional[Union[str, os.PathLike]] = None,
tooltip_txt: Optional[str] = None,
tooltip_key: Optional[str] = None,
**kw):
self.title, self.dropdown, self.initialdir = title, dropdown, initialdir
self.file_type = file_types
self.color = color if color is not None else "black"
self.lblwidth = lblwidth if lblwidth is not None else 0
self.parent = parent
Frame.__init__(self, master=parent, **kw)
browse_icon = ImageTk.PhotoImage(image=PIL.Image.open(MENU_ICONS["browse"]["icon_path"]))
self.columnconfigure(0, weight=0)
self.columnconfigure(1, weight=0)
self.columnconfigure(2, weight=0)
if lbl_icon is not None:
self.columnconfigure(3, weight=0)
self.lbl_icon = SimBALabel(parent=self, txt='', txt_clr='black', font=font, width=None, anchor='w', img=lbl_icon, compound=None)
self.lbl_icon.grid(row=0, column=0, sticky="w")
else:
self.lbl_icon = None
self.filePath = StringVar()
self.lblName = Label(self, text=fileDescription, fg=str(self.color), width=str(self.lblwidth), anchor=W, font=Formats.FONT_REGULAR.value)
self.lblName.grid(row=0, column=1, sticky=W)
self.entPath = Label(self, textvariable=self.filePath, relief=SUNKEN, font=Formats.FONT_REGULAR.value, bg=bg_clr, width=entry_width)
self.entPath.grid(row=0, column=2)
self.btnFind = SimbaButton(parent=self, txt=Defaults.BROWSE_FILE_BTN_TEXT.value, font=font, cmd=self.setFilePath, img=browse_icon)
self.btnFind.grid(row=0, column=3)
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
if initial_path is not None:
self.filePath.set(initial_path)
if status is not None:
self.set_state(setstatus=status)
if tooltip_txt is not None and isinstance(tooltip_txt, str):
CreateToolTip(widget=self.lblName, text=tooltip_txt)
elif tooltip_key in TOOLTIPS.keys():
CreateToolTip(widget=self.lblName, text=TOOLTIPS[tooltip_key])
[docs] def setFilePath(self):
if self.initialdir is not None:
if not os.path.isdir(self.initialdir):
self.initialdir = None
else:
pass
if self.file_type:
file_selected = askopenfilename(title=self.title, parent=self.parent, filetypes=self.file_type, initialdir=self.initialdir)
else:
file_selected = askopenfilename(title=self.title, parent=self.parent, initialdir=self.initialdir)
if file_selected:
if self.dropdown is not None:
_, name, _ = get_fn_ext(filepath=file_selected)
self.dropdown.setChoices(name)
self.filePath.set(name)
else:
self.filePath.set(file_selected)
self.entPath.configure(width=len(file_selected)+10)
else:
self.filePath.set(Defaults.NO_FILE_SELECTED_TEXT.value)
self.entPath.configure(width=len(Defaults.NO_FILE_SELECTED_TEXT.value) + 10)
@property
def file_path(self):
return self.filePath.get()
[docs] def set_state(self, setstatus):
self.entPath.config(state=setstatus)
self.btnFind["state"] = setstatus