Permalink
Switch branches/tags
Nothing to show
Find file Copy path
6473 lines (5737 sloc) 292 KB
#!/usr/bin/python3
import sys
if sys.version_info[0] >= 3:
import tkinter as tk
from tkinter import filedialog
from tkinter.colorchooser import askcolor
from tkinter import ttk
import tkinter.scrolledtext as tkst
import tkinter.font
else:
import Tkinter as tk
import tkFileDialog
import ttk
import tkColorChooser
import tkFont
import ScrolledText
import types
import datetime
import textwrap
import pickle
import calendar
g_time_start = 0
g_time_end = 0
g_time_delta = 0
import time
def TimerStart():
global g_time_start
g_time_start = time.time()
def TimerStop():
global g_time_delta, g_time_end
g_time_end = time.time()
g_time_delta = g_time_end - g_time_start
print(g_time_delta)
"""
Welcome to the "core" PySimpleGUI code....
It's a mess.... really... it's a mess internally... it's the external-facing interfaces that
are not a mess. The Elements and the methods for them are well-designed.
PEP8 - this code is far far from PEP8 compliant.
It was written PRIOR to learning that PEP8 existed.
The variable and function naming in particular are not compliant. There is
liberal use of CamelVariableAndFunctionNames. If you've got a serious enough problem with this
that you'll pass on this package, then that's your right and I invite you to do so. However, if
perhaps you're a practical thinker where it's the results that matter, then you'll have no
trouble with this code base. There is consisency however.
I truly hope you get a lot of enjoyment out of using PySimpleGUI. It came from good intentions.
"""
# ----====----====----==== Constants the user CAN safely change ====----====----====----#
DEFAULT_WINDOW_ICON = 'default_icon.ico'
DEFAULT_ELEMENT_SIZE = (45, 1) # In CHARACTERS
DEFAULT_BUTTON_ELEMENT_SIZE = (10, 1) # In CHARACTERS
DEFAULT_MARGINS = (10, 5) # Margins for each LEFT/RIGHT margin is first term
DEFAULT_ELEMENT_PADDING = (5, 3) # Padding between elements (row, col) in pixels
DEFAULT_AUTOSIZE_TEXT = True
DEFAULT_AUTOSIZE_BUTTONS = True
DEFAULT_FONT = ("Helvetica", 10)
DEFAULT_TEXT_JUSTIFICATION = 'left'
DEFAULT_BORDER_WIDTH = 1
DEFAULT_AUTOCLOSE_TIME = 3 # time in seconds to show an autoclose form
DEFAULT_DEBUG_WINDOW_SIZE = (80, 20)
DEFAULT_WINDOW_LOCATION = (None, None)
MAX_SCROLLED_TEXT_BOX_HEIGHT = 50
DEFAULT_TOOLTIP_TIME = 400
#################### COLOR STUFF ####################
BLUES = ("#082567", "#0A37A3", "#00345B")
PURPLES = ("#480656", "#4F2398", "#380474")
GREENS = ("#01826B", "#40A860", "#96D2AB", "#00A949", "#003532")
YELLOWS = ("#F3FB62", "#F0F595")
TANS = ("#FFF9D5", "#F4EFCF", "#DDD8BA")
NICE_BUTTON_COLORS = ((GREENS[3], TANS[0]),
('#000000', '#FFFFFF'),
('#FFFFFF', '#000000'),
(YELLOWS[0], PURPLES[1]),
(YELLOWS[0], GREENS[3]),
(YELLOWS[0], BLUES[2]))
COLOR_SYSTEM_DEFAULT = '1234567890' # Colors should never be this long
if sys.platform == 'darwin':
DEFAULT_BUTTON_COLOR = COLOR_SYSTEM_DEFAULT # Foreground, Background (None, None) == System Default
OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = COLOR_SYSTEM_DEFAULT # Colors should never be this long
else:
DEFAULT_BUTTON_COLOR = ('white', BLUES[0]) # Foreground, Background (None, None) == System Default
OFFICIAL_PYSIMPLEGUI_BUTTON_COLOR = ('white', BLUES[0]) # Colors should never be this long
DEFAULT_ERROR_BUTTON_COLOR = ("#FFFFFF", "#FF0000")
DEFAULT_BACKGROUND_COLOR = None
DEFAULT_ELEMENT_BACKGROUND_COLOR = None
DEFAULT_ELEMENT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT
DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR = None
DEFAULT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT
DEFAULT_INPUT_ELEMENTS_COLOR = COLOR_SYSTEM_DEFAULT
DEFAULT_INPUT_TEXT_COLOR = COLOR_SYSTEM_DEFAULT
DEFAULT_SCROLLBAR_COLOR = None
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[0]) # (Text, Background) or (Color "on", Color) as a way to remember
# DEFAULT_BUTTON_COLOR = (GREENS[3], TANS[0]) # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], GREENS[4]) # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = ('white', 'black') # Foreground, Background (None, None) == System Default
# DEFAULT_BUTTON_COLOR = (YELLOWS[0], PURPLES[2]) # Foreground, Background (None, None) == System Default
# DEFAULT_PROGRESS_BAR_COLOR = (GREENS[2], GREENS[0]) # a nice green progress bar
# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[1], BLUES[1]) # a nice green progress bar
# DEFAULT_PROGRESS_BAR_COLOR = (BLUES[0], BLUES[0]) # a nice green progress bar
# DEFAULT_PROGRESS_BAR_COLOR = (PURPLES[1],PURPLES[0]) # a nice purple progress bar
# A transparent button is simply one that matches the background
TRANSPARENT_BUTTON = ('#F0F0F0', '#F0F0F0')
# --------------------------------------------------------------------------------
# Progress Bar Relief Choices
RELIEF_RAISED = 'raised'
RELIEF_SUNKEN = 'sunken'
RELIEF_FLAT = 'flat'
RELIEF_RIDGE = 'ridge'
RELIEF_GROOVE = 'groove'
RELIEF_SOLID = 'solid'
DEFAULT_PROGRESS_BAR_COLOR = (GREENS[0], '#D0D0D0') # a nice green progress bar
DEFAULT_PROGRESS_BAR_SIZE = (25, 20) # Size of Progress Bar (characters for length, pixels for width)
DEFAULT_PROGRESS_BAR_BORDER_WIDTH = 1
DEFAULT_PROGRESS_BAR_RELIEF = RELIEF_GROOVE
PROGRESS_BAR_STYLES = ('default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative')
DEFAULT_PROGRESS_BAR_STYLE = 'default'
DEFAULT_METER_ORIENTATION = 'Horizontal'
DEFAULT_SLIDER_ORIENTATION = 'vertical'
DEFAULT_SLIDER_BORDER_WIDTH = 1
DEFAULT_SLIDER_RELIEF = tk.FLAT
DEFAULT_FRAME_RELIEF = tk.GROOVE
DEFAULT_LISTBOX_SELECT_MODE = tk.SINGLE
SELECT_MODE_MULTIPLE = tk.MULTIPLE
LISTBOX_SELECT_MODE_MULTIPLE = 'multiple'
SELECT_MODE_BROWSE = tk.BROWSE
LISTBOX_SELECT_MODE_BROWSE = 'browse'
SELECT_MODE_EXTENDED = tk.EXTENDED
LISTBOX_SELECT_MODE_EXTENDED = 'extended'
SELECT_MODE_SINGLE = tk.SINGLE
LISTBOX_SELECT_MODE_SINGLE = 'single'
TABLE_SELECT_MODE_NONE = tk.NONE
TABLE_SELECT_MODE_BROWSE = tk.BROWSE
TABLE_SELECT_MODE_EXTENDED = tk.EXTENDED
DEFAULT_TABLE_SECECT_MODE = TABLE_SELECT_MODE_EXTENDED
TITLE_LOCATION_TOP = tk.N
TITLE_LOCATION_BOTTOM = tk.S
TITLE_LOCATION_LEFT = tk.W
TITLE_LOCATION_RIGHT = tk.E
TITLE_LOCATION_TOP_LEFT = tk.NW
TITLE_LOCATION_TOP_RIGHT = tk.NE
TITLE_LOCATION_BOTTOM_LEFT = tk.SW
TITLE_LOCATION_BOTTOM_RIGHT = tk.SE
THEME_DEFAULT = 'default'
THEME_WINNATIVE = 'winnative'
THEME_CLAM = 'clam'
THEME_ALT = 'alt'
THEME_CLASSIC = 'classic'
THEME_VISTA = 'vista'
THEME_XPNATIVE = 'xpnative'
# DEFAULT_METER_ORIENTATION = 'Vertical'
# ----====----====----==== Constants the user should NOT f-with ====----====----====----#
ThisRow = 555666777 # magic number
# DEFAULT_WINDOW_ICON = ''
MESSAGE_BOX_LINE_WIDTH = 60
# "Special" Key Values.. reserved
# Key representing a Read timeout
TIMEOUT_KEY = '__TIMEOUT__'
# Key indicating should not create any return values for element
WRITE_ONLY_KEY = '__WRITE ONLY__'
# a shameful global variable. This represents the top-level window information. Needed because opening a second window is different than opening the first.
class MyWindows():
def __init__(self):
self.NumOpenWindows = 0
self.user_defined_icon = None
self.hidden_master_root = None
def Decrement(self):
self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0
# print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows))
def Increment(self):
self.NumOpenWindows += 1
# print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows))
_my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows
# ====================================================================== #
# One-liner functions that are handy as f_ck #
# ====================================================================== #
def RGB(red, green, blue): return '#%02x%02x%02x' % (red, green, blue)
# ====================================================================== #
# Enums for types #
# ====================================================================== #
# ------------------------- Button types ------------------------- #
# todo Consider removing the Submit, Cancel types... they are just 'RETURN' type in reality
# uncomment this line and indent to go back to using Enums
# class ButtonType(Enum):
BUTTON_TYPE_BROWSE_FOLDER = 1
BUTTON_TYPE_BROWSE_FILE = 2
BUTTON_TYPE_BROWSE_FILES = 21
BUTTON_TYPE_SAVEAS_FILE = 3
BUTTON_TYPE_CLOSES_WIN = 5
BUTTON_TYPE_CLOSES_WIN_ONLY = 6
BUTTON_TYPE_READ_FORM = 7
BUTTON_TYPE_REALTIME = 9
BUTTON_TYPE_CALENDAR_CHOOSER = 30
BUTTON_TYPE_COLOR_CHOOSER = 40
# ------------------------- Element types ------------------------- #
# class ElementType(Enum):
ELEM_TYPE_TEXT = 'text'
ELEM_TYPE_INPUT_TEXT = 'input'
ELEM_TYPE_INPUT_COMBO = 'combo'
ELEM_TYPE_INPUT_OPTION_MENU = 'option menu'
ELEM_TYPE_INPUT_RADIO = 'radio'
ELEM_TYPE_INPUT_MULTILINE = 'multiline'
ELEM_TYPE_INPUT_CHECKBOX = 'checkbox'
ELEM_TYPE_INPUT_SPIN = 'spind'
ELEM_TYPE_BUTTON = 'button'
ELEM_TYPE_IMAGE = 'image'
ELEM_TYPE_CANVAS = 'canvas'
ELEM_TYPE_FRAME = 'frame'
ELEM_TYPE_GRAPH = 'graph'
ELEM_TYPE_TAB = 'tab'
ELEM_TYPE_TAB_GROUP = 'tabgroup'
ELEM_TYPE_INPUT_SLIDER = 'slider'
ELEM_TYPE_INPUT_LISTBOX = 'listbox'
ELEM_TYPE_OUTPUT = 'output'
ELEM_TYPE_COLUMN = 'column'
ELEM_TYPE_MENUBAR = 'menubar'
ELEM_TYPE_PROGRESS_BAR = 'progressbar'
ELEM_TYPE_BLANK = 'blank'
ELEM_TYPE_TABLE = 'table'
ELEM_TYPE_TREE = 'tree'
ELEM_TYPE_ERROR = 'error'
ELEM_TYPE_SEPARATOR = 'separator'
# ------------------------- Popup Buttons Types ------------------------- #
POPUP_BUTTONS_YES_NO = 1
POPUP_BUTTONS_CANCELLED = 2
POPUP_BUTTONS_ERROR = 3
POPUP_BUTTONS_OK_CANCEL = 4
POPUP_BUTTONS_OK = 0
POPUP_BUTTONS_NO_BUTTONS = 5
# ------------------------------------------------------------------------- #
# ToolTip used by the Elements #
# ------------------------------------------------------------------------- #
class ToolTip:
""" Create a tooltip for a given widget
(inspired by https://stackoverflow.com/a/36221216)
"""
def __init__(self, widget, text, timeout=DEFAULT_TOOLTIP_TIME):
self.widget = widget
self.text = text
self.timeout = timeout
# self.wraplength = wraplength if wraplength else widget.winfo_screenwidth() // 2
self.tipwindow = None
self.id = None
self.x = self.y = 0
self.widget.bind("<Enter>", self.enter)
self.widget.bind("<Leave>", self.leave)
self.widget.bind("<ButtonPress>", self.leave)
def enter(self, event=None):
self.schedule()
def leave(self, event=None):
self.unschedule()
self.hidetip()
def schedule(self):
self.unschedule()
self.id = self.widget.after(self.timeout, self.showtip)
def unschedule(self):
if self.id:
self.widget.after_cancel(self.id)
self.id = None
def showtip(self):
if self.tipwindow:
return
x = self.widget.winfo_rootx() + 20
y = self.widget.winfo_rooty() + self.widget.winfo_height() - 20
self.tipwindow = tk.Toplevel(self.widget)
self.tipwindow.wm_overrideredirect(True)
self.tipwindow.wm_geometry("+%d+%d" % (x, y))
label = ttk.Label(self.tipwindow, text=self.text, justify=tk.LEFT,
background="#ffffe0", relief=tk.SOLID, borderwidth=1)
label.pack()
def hidetip(self):
if self.tipwindow:
self.tipwindow.destroy()
self.tipwindow = None
# ---------------------------------------------------------------------- #
# Cascading structure.... Objects get larger #
# Button #
# Element #
# Row #
# Form #
# ---------------------------------------------------------------------- #
# ------------------------------------------------------------------------- #
# Element CLASS #
# ------------------------------------------------------------------------- #
class Element():
def __init__(self, type, size=(None, None), auto_size_text=None, font=None, background_color=None, text_color=None,
key=None, pad=None, tooltip=None):
self.Size = size
self.Type = type
self.AutoSizeText = auto_size_text
self.Pad = DEFAULT_ELEMENT_PADDING if pad is None else pad
self.Font = font
self.TKStringVar = None
self.TKIntVar = None
self.TKText = None
self.TKEntry = None
self.TKImage = None
self.ParentForm = None
self.ParentContainer = None # will be a Form, Column, or Frame element
self.TextInputDefault = None
self.Position = (0, 0) # Default position Row 0, Col 0
self.BackgroundColor = background_color if background_color is not None else DEFAULT_ELEMENT_BACKGROUND_COLOR
self.TextColor = text_color if text_color is not None else DEFAULT_ELEMENT_TEXT_COLOR
self.Key = key # dictionary key for return values
self.Tooltip = tooltip
self.TooltipObject = None
def FindReturnKeyBoundButton(self, form):
for row in form.Rows:
for element in row:
if element.Type == ELEM_TYPE_BUTTON:
if element.BindReturnKey:
return element
if element.Type == ELEM_TYPE_COLUMN:
rc = self.FindReturnKeyBoundButton(element)
if rc is not None:
return rc
if element.Type == ELEM_TYPE_FRAME:
rc = self.FindReturnKeyBoundButton(element)
if rc is not None:
return rc
if element.Type == ELEM_TYPE_TAB_GROUP:
rc = self.FindReturnKeyBoundButton(element)
if rc is not None:
return rc
if element.Type == ELEM_TYPE_TAB:
rc = self.FindReturnKeyBoundButton(element)
if rc is not None:
return rc
return None
def TextClickedHandler(self, event):
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = self.DisplayText
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def ReturnKeyHandler(self, event):
MyForm = self.ParentForm
button_element = self.FindReturnKeyBoundButton(MyForm)
if button_element is not None:
button_element.ButtonCallBack()
def ListboxSelectHandler(self, event):
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def ComboboxSelectHandler(self, event):
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def RadioHandler(self):
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def CheckboxHandler(self):
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def TabGroupSelectHandler(self, event):
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def KeyboardHandler(self, event):
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def __del__(self):
try:
self.TKStringVar.__del__()
except:
pass
try:
self.TKIntVar.__del__()
except:
pass
try:
self.TKText.__del__()
except:
pass
try:
self.TKEntry.__del__()
except:
pass
# ---------------------------------------------------------------------- #
# Input Class #
# ---------------------------------------------------------------------- #
class InputText(Element):
def __init__(self, default_text='', size=(None, None), disabled=False, password_char='',
justification=None, background_color=None, text_color=None, font=None, tooltip=None,
change_submits=False,
do_not_clear=False, key=None, focus=False, pad=None):
'''
Input a line of text Element
:param default_text: Default value to display
:param size: Size of field in characters
:param password_char: If non-blank, will display this character for every character typed
:param background_color: Color for Element. Text or RGB Hex
'''
self.DefaultText = default_text
self.PasswordCharacter = password_char
bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
self.Focus = focus
self.do_not_clear = do_not_clear
self.Justification = justification
self.Disabled = disabled
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_INPUT_TEXT, size=size, background_color=bg, text_color=fg, key=key, pad=pad,
font=font, tooltip=tooltip)
def Update(self, value=None, disabled=None):
if disabled is True:
self.TKEntry['state'] = 'disabled'
elif disabled is False:
self.TKEntry['state'] = 'normal'
if value is not None:
try:
self.TKStringVar.set(value)
except:
pass
self.DefaultText = value
def Get(self):
return self.TKStringVar.get()
def SetFocus(self):
try:
self.TKEntry.focus_set()
except:
pass
def __del__(self):
super().__del__()
# ------------------------- INPUT TEXT Element lazy functions ------------------------- #
In = InputText
Input = InputText
# ---------------------------------------------------------------------- #
# Combo #
# ---------------------------------------------------------------------- #
class InputCombo(Element):
def __init__(self, values, default_value=None, size=(None, None), auto_size_text=None, background_color=None,
text_color=None, change_submits=False, disabled=False, key=None, pad=None, tooltip=None,
readonly=False, font=None):
'''
Input Combo Box Element (also called Dropdown box)
:param values:
:param size: Size of field in characters
:param auto_size_text: True if should shrink field to fit the default text
:param background_color: Color for Element. Text or RGB Hex
'''
self.Values = values
self.DefaultValue = default_value
self.ChangeSubmits = change_submits
self.TKCombo = None
# self.InitializeAsDisabled = disabled
self.Disabled = disabled
self.Readonly = readonly
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
super().__init__(ELEM_TYPE_INPUT_COMBO, size=size, auto_size_text=auto_size_text, background_color=bg,
text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT)
def Update(self, value=None, values=None, set_to_index=None, disabled=None, readonly=None, font=None):
if values is not None:
try:
self.TKCombo['values'] = values
self.TKCombo.current(0)
except:
pass
self.Values = values
if value is not None:
for index, v in enumerate(self.Values):
if v == value:
try:
self.TKCombo.current(index)
except:
pass
self.DefaultValue = value
break
if set_to_index is not None:
try:
self.TKCombo.current(set_to_index)
self.DefaultValue = self.Values[set_to_index]
except:
pass
if disabled == True:
self.TKCombo['state'] = 'disable'
elif disabled == False:
self.TKCombo['state'] = 'enable'
if readonly is not None:
self.Readonly = readonly
if self.Readonly:
self.TKCombo['state'] = 'readonly'
if font is not None:
self.TKText.configure(font=font)
def __del__(self):
try:
self.TKCombo.__del__()
except:
pass
super().__del__()
# ------------------------- INPUT COMBO Element lazy functions ------------------------- #
Combo = InputCombo
DropDown = InputCombo
Drop = InputCombo
# ---------------------------------------------------------------------- #
# Option Menu #
# ---------------------------------------------------------------------- #
class OptionMenu(Element):
def __init__(self, values, default_value=None, size=(None, None), disabled=False, auto_size_text=None,
background_color=None, text_color=None, key=None, pad=None, tooltip=None):
'''
InputOptionMenu
:param values:
:param default_value:
:param size:
:param disabled:
:param auto_size_text:
:param background_color:
:param text_color:
:param key:
:param pad:
:param tooltip:
'''
self.Values = values
self.DefaultValue = default_value
self.TKOptionMenu = None
self.Disabled = disabled
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
super().__init__(ELEM_TYPE_INPUT_OPTION_MENU, size=size, auto_size_text=auto_size_text, background_color=bg,
text_color=fg, key=key, pad=pad, tooltip=tooltip)
def Update(self, value=None, values=None, disabled=None):
if values is not None:
self.Values = values
if self.Values is not None:
for index, v in enumerate(self.Values):
if v == value:
try:
self.TKStringVar.set(value)
except:
pass
self.DefaultValue = value
break
if disabled == True:
self.TKOptionMenu['state'] = 'disabled'
elif disabled == False:
self.TKOptionMenu['state'] = 'normal'
def __del__(self):
try:
self.TKOptionMenu.__del__()
except:
pass
super().__del__()
# ------------------------- OPTION MENU Element lazy functions ------------------------- #
InputOptionMenu = OptionMenu
# ---------------------------------------------------------------------- #
# Listbox #
# ---------------------------------------------------------------------- #
class Listbox(Element):
def __init__(self, values, default_values=None, select_mode=None, change_submits=False, bind_return_key=False,
size=(None, None), disabled=False, auto_size_text=None, font=None, background_color=None,
text_color=None, key=None, pad=None, tooltip=None):
'''
Listbox Element
:param values:
:param default_values:
:param select_mode:
:param change_submits:
:param bind_return_key:
:param size:
:param disabled:
:param auto_size_text:
:param font:
:param background_color:
:param text_color:
:param key:
:param pad:
:param tooltip:
'''
self.Values = values
self.DefaultValues = default_values
self.TKListbox = None
self.ChangeSubmits = change_submits
self.BindReturnKey = bind_return_key
self.Disabled = disabled
if select_mode == LISTBOX_SELECT_MODE_BROWSE:
self.SelectMode = SELECT_MODE_BROWSE
elif select_mode == LISTBOX_SELECT_MODE_EXTENDED:
self.SelectMode = SELECT_MODE_EXTENDED
elif select_mode == LISTBOX_SELECT_MODE_MULTIPLE:
self.SelectMode = SELECT_MODE_MULTIPLE
elif select_mode == LISTBOX_SELECT_MODE_SINGLE:
self.SelectMode = SELECT_MODE_SINGLE
else:
self.SelectMode = DEFAULT_LISTBOX_SELECT_MODE
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
super().__init__(ELEM_TYPE_INPUT_LISTBOX, size=size, auto_size_text=auto_size_text, font=font,
background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip)
def Update(self, values=None, disabled=None):
if disabled == True:
self.TKListbox.configure(state='disabled')
elif disabled == False:
self.TKListbox.configure(state='normal')
if values is not None:
self.TKListbox.delete(0, 'end')
for item in values:
self.TKListbox.insert(tk.END, item)
self.TKListbox.selection_set(0, 0)
self.Values = values
def SetValue(self, values):
for index, item in enumerate(self.Values):
try:
if item in values:
self.TKListbox.selection_set(index)
else:
self.TKListbox.selection_clear(index)
except:
pass
self.DefaultValues = values
def GetListValues(self):
return self.Values
def __del__(self):
try:
self.TKListBox.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Radio #
# ---------------------------------------------------------------------- #
class Radio(Element):
def __init__(self, text, group_id, default=False, disabled=False, size=(None, None), auto_size_text=None,
background_color=None, text_color=None, font=None, key=None, pad=None, tooltip=None, change_submits=False):
'''
Radio Button Element
:param text:
:param group_id:
:param default:
:param disabled:
:param size:
:param auto_size_text:
:param background_color:
:param text_color:
:param font:
:param key:
:param pad:
:param tooltip:
:param change_submits:
'''
self.InitialState = default
self.Text = text
self.TKRadio = None
self.GroupID = group_id
self.Value = None
self.Disabled = disabled
self.TextColor = text_color or DEFAULT_TEXT_COLOR
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_INPUT_RADIO, size=size, auto_size_text=auto_size_text, font=font,
background_color=background_color, text_color=self.TextColor, key=key, pad=pad,
tooltip=tooltip)
def Update(self, value=None, disabled=None):
location = EncodeRadioRowCol(self.Position[0], self.Position[1])
if value is not None:
try:
self.TKIntVar.set(location)
except:
pass
self.InitialState = value
if disabled == True:
self.TKRadio['state'] = 'disabled'
elif disabled == False:
self.TKRadio['state'] = 'normal'
def __del__(self):
try:
self.TKRadio.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Checkbox #
# ---------------------------------------------------------------------- #
class Checkbox(Element):
def __init__(self, text, default=False, size=(None, None), auto_size_text=None, font=None, background_color=None,
text_color=None, change_submits=False, disabled=False, key=None, pad=None, tooltip=None):
'''
Checkbox Element
:param text:
:param default:
:param size:
:param auto_size_text:
:param font:
:param background_color:
:param text_color:
:param change_submits:
:param disabled:
:param key:
:param pad:
:param tooltip:
'''
self.Text = text
self.InitialState = default
self.Value = None
self.TKCheckbutton = None
self.Disabled = disabled
self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_INPUT_CHECKBOX, size=size, auto_size_text=auto_size_text, font=font,
background_color=background_color, text_color=self.TextColor, key=key, pad=pad,
tooltip=tooltip)
def Get(self):
return self.TKIntVar.get()
def Update(self, value=None, disabled=None):
if value is not None:
try:
self.TKIntVar.set(value)
self.InitialState = value
except:
pass
if disabled == True:
self.TKCheckbutton.configure(state='disabled')
elif disabled == False:
self.TKCheckbutton.configure(state='normal')
def __del__(self):
super().__del__()
# ------------------------- CHECKBOX Element lazy functions ------------------------- #
CB = Checkbox
CBox = Checkbox
Check = Checkbox
# ---------------------------------------------------------------------- #
# Spin #
# ---------------------------------------------------------------------- #
class Spin(Element):
# Values = None
# TKSpinBox = None
def __init__(self, values, initial_value=None, disabled=False, change_submits=False, size=(None, None),
auto_size_text=None, font=None, background_color=None, text_color=None, key=None, pad=None,
tooltip=None):
'''
Spinner Element
:param values:
:param initial_value:
:param disabled:
:param change_submits:
:param size:
:param auto_size_text:
:param font:
:param background_color:
:param text_color:
:param key:
:param pad:
:param tooltip:
'''
self.Values = values
self.DefaultValue = initial_value
self.ChangeSubmits = change_submits
self.TKSpinBox = None
self.Disabled = disabled
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
super().__init__(ELEM_TYPE_INPUT_SPIN, size, auto_size_text, font=font, background_color=bg, text_color=fg,
key=key, pad=pad, tooltip=tooltip)
return
def Update(self, value=None, values=None, disabled=None):
if values != None:
old_value = self.TKStringVar.get()
self.Values = values
self.TKSpinBox.configure(values=values)
self.TKStringVar.set(old_value)
if value is not None:
try:
self.TKStringVar.set(value)
except:
pass
self.DefaultValue = value
if disabled == True:
self.TKSpinBox.configure(state='disabled')
elif disabled == False:
self.TKSpinBox.configure(state='normal')
def SpinChangedHandler(self, event):
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def __del__(self):
try:
self.TKSpinBox.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Multiline #
# ---------------------------------------------------------------------- #
class Multiline(Element):
def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, size=(None, None),
auto_size_text=None, background_color=None, text_color=None, change_submits=False, do_not_clear=False, key=None, focus=False,
font=None, pad=None, tooltip=None):
'''
Multiline Element
:param default_text:
:param enter_submits:
:param disabled:
:param autoscroll:
:param size:
:param auto_size_text:
:param background_color:
:param text_color:
:param do_not_clear:
:param key:
:param focus:
:param pad:
:param tooltip:
:param font:
'''
self.DefaultText = default_text
self.EnterSubmits = enter_submits
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
self.Focus = focus
self.do_not_clear = do_not_clear
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
self.Autoscroll = autoscroll
self.Disabled = disabled
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_INPUT_MULTILINE, size=size, auto_size_text=auto_size_text, background_color=bg,
text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT)
return
def Update(self, value=None, disabled=None, append=False, font=None, text_color=None, background_color=None):
if value is not None:
try:
if not append:
self.TKText.delete('1.0', tk.END)
self.TKText.insert(tk.END, value)
except:
pass
self.DefaultText = value
if self.Autoscroll:
self.TKText.see(tk.END)
if disabled == True:
self.TKText.configure(state='disabled')
elif disabled == False:
self.TKText.configure(state='normal')
if background_color is not None:
self.TKText.configure(background=background_color)
if text_color is not None:
self.TKText.configure(fg=text_color)
if font is not None:
self.TKText.configure(font=font)
def Get(self):
return self.TKText.get(1.0, tk.END)
def SetFocus(self):
try:
self.TKText.focus_set()
except:
pass
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Text #
# ---------------------------------------------------------------------- #
class Text(Element):
def __init__(self, text, size=(None, None), auto_size_text=None, click_submits=None, relief=None, font=None,
text_color=None, background_color=None, justification=None, pad=None, key=None, tooltip=None):
'''
Text Element
:param text:
:param size:
:param auto_size_text:
:param click_submits:
:param relief:
:param font:
:param text_color:
:param background_color:
:param justification:
:param pad:
:param key:
:param tooltip:
'''
self.DisplayText = text
self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR
self.Justification = justification
self.Relief = relief
self.ClickSubmits = click_submits
if background_color is None:
bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR
else:
bg = background_color
super().__init__(ELEM_TYPE_TEXT, size, auto_size_text, background_color=bg, font=font if font else DEFAULT_FONT,
text_color=self.TextColor, pad=pad, key=key, tooltip=tooltip)
return
def Update(self, value=None, background_color=None, text_color=None, font=None):
if value is not None:
self.DisplayText = value
stringvar = self.TKStringVar
stringvar.set(value)
if background_color is not None:
self.TKText.configure(background=background_color)
if text_color is not None:
self.TKText.configure(fg=text_color)
if font is not None:
self.TKText.configure(font=font)
def __del__(self):
super().__del__()
# ------------------------- Text Element lazy functions ------------------------- #
Txt = Text
T = Text
# ---------------------------------------------------------------------- #
# TKProgressBar #
# Emulate the TK ProgressBar using canvas and rectangles
# ---------------------------------------------------------------------- #
class TKProgressBar():
def __init__(self, root, max, length=400, width=DEFAULT_PROGRESS_BAR_SIZE[1], style=DEFAULT_PROGRESS_BAR_STYLE,
relief=DEFAULT_PROGRESS_BAR_RELIEF, border_width=DEFAULT_PROGRESS_BAR_BORDER_WIDTH,
orientation='horizontal', BarColor=(None, None), key=None):
self.Length = length
self.Width = width
self.Max = max
self.Orientation = orientation
self.Count = None
self.PriorCount = 0
if orientation[0].lower() == 'h':
s = ttk.Style()
s.theme_use(style)
if BarColor != COLOR_SYSTEM_DEFAULT:
s.configure(str(key) + "my.Horizontal.TProgressbar", background=BarColor[0], troughcolor=BarColor[1],
troughrelief=relief, borderwidth=border_width, thickness=width)
else:
s.configure(str(key) + "my.Horizontal.TProgressbar", troughrelief=relief, borderwidth=border_width,
thickness=width)
self.TKProgressBarForReal = ttk.Progressbar(root, maximum=self.Max,
style=str(key) + 'my.Horizontal.TProgressbar', length=length,
orient=tk.HORIZONTAL, mode='determinate')
else:
s = ttk.Style()
s.theme_use(style)
if BarColor != COLOR_SYSTEM_DEFAULT:
s.configure(str(length) + str(width) + "my.Vertical.TProgressbar", background=BarColor[0],
troughcolor=BarColor[1], troughrelief=relief, borderwidth=border_width, thickness=width)
else:
s.configure(str(length) + str(width) + "my.Vertical.TProgressbar", troughrelief=relief,
borderwidth=border_width, thickness=width)
self.TKProgressBarForReal = ttk.Progressbar(root, maximum=self.Max,
style=str(length) + str(width) + 'my.Vertical.TProgressbar',
length=length, orient=tk.VERTICAL, mode='determinate')
def Update(self, count=None, max=None):
if max is not None:
self.Max = max
try:
self.TKProgressBarForReal.config(maximum=max)
except:
return False
if count is not None and count > self.Max: return False
if count is not None:
try:
self.TKProgressBarForReal['value'] = count
except:
return False
return True
def __del__(self):
try:
self.TKProgressBarForReal.__del__()
except:
pass
# ---------------------------------------------------------------------- #
# TKOutput #
# New Type of TK Widget that's a Text Widget in disguise #
# Note that it's inherited from the TKFrame class so that the #
# Scroll bar will span the length of the frame #
# ---------------------------------------------------------------------- #
class TKOutput(tk.Frame):
def __init__(self, parent, width, height, bd, background_color=None, text_color=None, font=None, pad=None):
frame = tk.Frame(parent)
tk.Frame.__init__(self, frame)
self.output = tk.Text(frame, width=width, height=height, bd=bd, font=font)
if background_color and background_color != COLOR_SYSTEM_DEFAULT:
self.output.configure(background=background_color)
frame.configure(background=background_color)
if text_color and text_color != COLOR_SYSTEM_DEFAULT:
self.output.configure(fg=text_color)
self.vsb = tk.Scrollbar(frame, orient="vertical", command=self.output.yview)
self.output.configure(yscrollcommand=self.vsb.set)
self.output.pack(side="left", fill="both", expand=True)
self.vsb.pack(side="left", fill="y", expand=False)
frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y')
self.previous_stdout = sys.stdout
self.previous_stderr = sys.stderr
sys.stdout = self
sys.stderr = self
self.pack()
def write(self, txt):
try:
self.output.insert(tk.END, str(txt))
self.output.see(tk.END)
except:
pass
def Close(self):
sys.stdout = self.previous_stdout
sys.stderr = self.previous_stderr
def flush(self):
sys.stdout = self.previous_stdout
sys.stderr = self.previous_stderr
def __del__(self):
sys.stdout = self.previous_stdout
sys.stderr = self.previous_stderr
# ---------------------------------------------------------------------- #
# Output #
# Routes stdout, stderr to a scrolled window #
# ---------------------------------------------------------------------- #
class Output(Element):
def __init__(self, size=(None, None), background_color=None, text_color=None, pad=None, font=None, tooltip=None,
key=None):
'''
Output Element
:param size:
:param background_color:
:param text_color:
:param pad:
:param font:
:param tooltip:
:param key:
'''
self._TKOut = None
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
super().__init__(ELEM_TYPE_OUTPUT, size=size, background_color=bg, text_color=fg, pad=pad, font=font,
tooltip=tooltip, key=key)
@property
def TKOut(self):
if self._TKOut is None:
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
return self._TKOut
def Update(self, value=None):
if value is not None:
# try:
self._TKOut.output.delete('1.0', tk.END)
self._TKOut.output.insert(tk.END, value)
# except:
# pass
def __del__(self):
try:
self._TKOut.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Button Class #
# ---------------------------------------------------------------------- #
class Button(Element):
def __init__(self, button_text='', button_type=BUTTON_TYPE_READ_FORM, target=(None, None), tooltip=None,
file_types=(("ALL Files", "*.*"),), initial_folder=None, disabled=False, change_submits=False, image_filename=None, image_data=None, image_size=(None, None), image_subsample=None, border_width=None, size=(None, None), auto_size_button=None, button_color=None, font=None, bind_return_key=False, focus=False, pad=None, key=None):
'''
Button Element
:param button_text:
:param button_type:
:param target:
:param tooltip:
:param file_types:
:param initial_folder:
:param disabled:
:param image_filename:
:param image_size:
:param image_subsample:
:param border_width:
:param size:
:param auto_size_button:
:param button_color:
:param default_value:
:param font:
:param bind_return_key:
:param focus:
:param pad:
:param key:
'''
self.AutoSizeButton = auto_size_button
self.BType = button_type
self.FileTypes = file_types
self.TKButton = None
self.Target = target
self.ButtonText = button_text
self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR
self.ImageFilename = image_filename
self.ImageData = image_data
self.ImageSize = image_size
self.ImageSubsample = image_subsample
self.UserData = None
self.BorderWidth = border_width if border_width is not None else DEFAULT_BORDER_WIDTH
self.BindReturnKey = bind_return_key
self.Focus = focus
self.TKCal = None
self.CalendarCloseWhenChosen = None
self.DefaultDate_M_D_Y = (None, None, None)
self.InitialFolder = initial_folder
self.Disabled = disabled
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_BUTTON, size=size, font=font, pad=pad, key=key, tooltip=tooltip)
return
# Realtime button release callback
def ButtonReleaseCallBack(self, parm):
self.LastButtonClickedWasRealtime = False
self.ParentForm.LastButtonClicked = None
# Realtime button callback
def ButtonPressCallBack(self, parm):
self.ParentForm.LastButtonClickedWasRealtime = True
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = self.ButtonText
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick out of loop if read was called
# ------- Button Callback ------- #
def ButtonCallBack(self):
global _my_windows
# print('Button callback')
# print(f'Parent = {self.ParentForm} Position = {self.Position}')
# Buttons modify targets or return from the form
# If modifying target, get the element object at the target and modify its StrVar
target = self.Target
target_element = None
if target[0] == ThisRow:
target = [self.Position[0], target[1]]
if target[1] < 0:
target[1] = self.Position[1] + target[1]
strvar = None
should_submit_window = False
if target == (None, None):
strvar = self.TKStringVar
else:
if not isinstance(target, str):
if target[0] < 0:
target = [self.Position[0] + target[0], target[1]]
target_element = self.ParentContainer._GetElementAtLocation(target)
else:
target_element = self.ParentForm.FindElement(target)
try:
strvar = target_element.TKStringVar
except:
pass
try:
if target_element.ChangeSubmits:
should_submit_window = True
except:
pass
filetypes = (("ALL Files", "*.*"),) if self.FileTypes is None else self.FileTypes
if self.BType == BUTTON_TYPE_BROWSE_FOLDER:
folder_name = tk.filedialog.askdirectory(initialdir=self.InitialFolder) # show the 'get folder' dialog box
if folder_name != '':
try:
if sys.platform == 'win32':
folder_name = folder_name.replace("/", "\\")
strvar.set(folder_name)
self.TKStringVar.set(folder_name)
except:
pass
elif self.BType == BUTTON_TYPE_BROWSE_FILE:
if sys.platform == 'darwin':
file_name = tk.filedialog.askopenfilename(initialdir=self.InitialFolder) # show the 'get file' dialog box
else:
file_name = tk.filedialog.askopenfilename(filetypes=filetypes, initialdir=self.InitialFolder) # show the 'get file' dialog box
if file_name != '':
if sys.platform == 'win32':
file_name = file_name.replace("/", "\\")
strvar.set(file_name)
self.TKStringVar.set(file_name)
elif self.BType == BUTTON_TYPE_COLOR_CHOOSER:
color = tk.colorchooser.askcolor() # show the 'get file' dialog box
color = color[1] # save only the #RRGGBB portion
strvar.set(color)
self.TKStringVar.set(color)
elif self.BType == BUTTON_TYPE_BROWSE_FILES:
if sys.platform == 'darwin':
file_name = tk.filedialog.askopenfilenames(initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.askopenfilenames(filetypes=filetypes, initialdir=self.InitialFolder)
if file_name != '':
file_name = ';'.join(file_name)
if sys.platform == 'win32':
file_name = file_name.replace("/", "\\")
strvar.set(file_name)
self.TKStringVar.set(file_name)
elif self.BType == BUTTON_TYPE_SAVEAS_FILE:
if sys.platform == 'darwin':
file_name = tk.filedialog.asksaveasfilename(initialdir=self.InitialFolder) # show the 'get file' dialog box
else:
file_name = tk.filedialog.asksaveasfilename(filetypes=filetypes,
initialdir=self.InitialFolder) # show the 'get file' dialog box
if file_name != '':
if sys.platform == 'win32':
file_name = file_name.replace("/", "\\")
strvar.set(file_name)
self.TKStringVar.set(file_name)
elif self.BType == BUTTON_TYPE_CLOSES_WIN: # this is a return type button so GET RESULTS and destroy window
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = self.ButtonText
self.ParentForm.FormRemainedOpen = False
self.ParentForm._Close()
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
if self.ParentForm.NonBlocking:
self.ParentForm.TKroot.destroy()
_my_windows.Decrement()
elif self.BType == BUTTON_TYPE_READ_FORM: # LEAVE THE WINDOW OPEN!! DO NOT CLOSE
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = self.ButtonText
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop: # if this window is running the mainloop, kick out
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop
self.ParentForm._Close()
if self.ParentForm.NonBlocking:
self.ParentForm.TKroot.destroy()
_my_windows.Decrement()
elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window
should_submit_window = False
root = tk.Toplevel()
root.title('Calendar Chooser')
self.TKCal = TKCalendar(master=root, firstweekday=calendar.SUNDAY, target_element=target_element, close_when_chosen=self.CalendarCloseWhenChosen, default_date=self.DefaultDate_M_D_Y )
self.TKCal.pack(expand=1, fill='both')
root.update()
if should_submit_window:
self.ParentForm.LastButtonClicked = target_element.Key
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
return
def Update(self, text=None, button_color=(None, None), disabled=None, image_data=None, image_filename=None):
try:
if text is not None:
self.TKButton.configure(text=text)
self.ButtonText = text
if button_color != (None, None):
self.TKButton.config(foreground=button_color[0], background=button_color[1])
except:
return
if disabled == True:
self.TKButton['state'] = 'disabled'
elif disabled == False:
self.TKButton['state'] = 'normal'
if image_data is not None:
image = tk.PhotoImage(data=image_data)
width, height = image.width(), image.height()
self.TKButton.config(image=image, width=width, height=height)
self.TKButton.image = image
if image_filename is not None:
self.TKButton.config(highlightthickness=0)
photo = tk.PhotoImage(file=image_filename)
width, height = photo.width(), photo.height()
self.TKButton.config(image=photo, width=width, height=height)
self.TKButton.image = photo
def GetText(self):
return self.ButtonText
def __del__(self):
try:
self.TKButton.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# ProgreessBar #
# ---------------------------------------------------------------------- #
class ProgressBar(Element):
def __init__(self, max_value, orientation=None, size=(None, None), auto_size_text=None, bar_color=(None, None),
style=None, border_width=None, relief=None, key=None, pad=None):
'''
ProgressBar Element
:param max_value:
:param orientation:
:param size:
:param auto_size_text:
:param bar_color:
:param style:
:param border_width:
:param relief:
:param key:
:param pad:
'''
self.MaxValue = max_value
self.TKProgressBar = None
self.Cancelled = False
self.NotRunning = True
self.Orientation = orientation if orientation else DEFAULT_METER_ORIENTATION
self.BarColor = bar_color
self.BarStyle = style if style else DEFAULT_PROGRESS_BAR_STYLE
self.BorderWidth = border_width if border_width else DEFAULT_PROGRESS_BAR_BORDER_WIDTH
self.Relief = relief if relief else DEFAULT_PROGRESS_BAR_RELIEF
self.BarExpired = False
super().__init__(ELEM_TYPE_PROGRESS_BAR, size=size, auto_size_text=auto_size_text, key=key, pad=pad)
# returns False if update failed
def UpdateBar(self, current_count, max=None):
if self.ParentForm.TKrootDestroyed:
return False
self.TKProgressBar.Update(current_count, max=max)
try:
self.ParentForm.TKroot.update()
except:
_my_windows.Decrement()
return False
return True
def __del__(self):
try:
self.TKProgressBar.__del__()
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Image #
# ---------------------------------------------------------------------- #
class Image(Element):
def __init__(self, filename=None, data=None, background_color=None, size=(None, None), pad=None, key=None,
tooltip=None):
'''
Image Element
:param filename:
:param data:
:param background_color:
:param size:
:param pad:
:param key:
:param tooltip:
'''
self.Filename = filename
self.Data = data
self.tktext_label = None
self.BackgroundColor = background_color
if data is None and filename is None:
print('* Warning... no image specified in Image Element! *')
super().__init__(ELEM_TYPE_IMAGE, size=size, background_color=background_color, pad=pad, key=key,
tooltip=tooltip)
return
def Update(self, filename=None, data=None, size=(None,None)):
if filename is not None:
image = tk.PhotoImage(file=filename)
elif data is not None:
# if type(data) is bytes:
try:
image = tk.PhotoImage(data=data)
except:
return # an error likely means the window has closed so exit
# else:
# image = data
else:
return
width, height = size[0] or image.width(), size[1] or image.height()
self.tktext_label.configure(image=image, width=width, height=height)
self.tktext_label.image = image
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Canvas #
# ---------------------------------------------------------------------- #
class Canvas(Element):
def __init__(self, canvas=None, background_color=None, size=(None, None), pad=None, key=None, tooltip=None):
'''
Canvas Element
:param canvas:
:param background_color:
:param size:
:param pad:
:param key:
:param tooltip:
'''
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self._TKCanvas = canvas
super().__init__(ELEM_TYPE_CANVAS, background_color=background_color, size=size, pad=pad, key=key,
tooltip=tooltip)
return
@property
def TKCanvas(self):
if self._TKCanvas is None:
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
return self._TKCanvas
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Graph #
# ---------------------------------------------------------------------- #
class Graph(Element):
def __init__(self, canvas_size, graph_bottom_left, graph_top_right, background_color=None, pad=None, change_submits=False, drag_submits=False, key=None,
tooltip=None):
'''
Graph Element
:param canvas_size:
:param graph_bottom_left:
:param graph_top_right:
:param background_color:
:param pad:
:param key:
:param tooltip:
'''
self.CanvasSize = canvas_size
self.BottomLeft = graph_bottom_left
self.TopRight = graph_top_right
self._TKCanvas = None
self._TKCanvas2 = None
self.ChangeSubmits = change_submits
self.DragSubmits = drag_submits
self.ClickPosition = (None, None)
self.MouseButtonDown = False
super().__init__(ELEM_TYPE_GRAPH, background_color=background_color, size=canvas_size, pad=pad, key=key,
tooltip=tooltip)
return
def _convert_xy_to_canvas_xy(self, x_in, y_in):
if None in (x_in,y_in):
return None, None
scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0])
scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1])
new_x = 0 + scale_x * (x_in - self.BottomLeft[0])
new_y = self.CanvasSize[1] + scale_y * (y_in - self.BottomLeft[1])
return new_x, new_y
def _convert_canvas_xy_to_xy(self, x_in, y_in):
if None in (x_in,y_in):
return None, None
scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0])
scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1])
new_x = x_in/scale_x+self.BottomLeft[0]
new_y = (y_in - self.CanvasSize[1]) / scale_y + self.BottomLeft[1]
return int(new_x), int(new_y)
def DrawLine(self, point_from, point_to, color='black', width=1):
if point_from == (None, None):
return
converted_point_from = self._convert_xy_to_canvas_xy(point_from[0], point_from[1])
converted_point_to = self._convert_xy_to_canvas_xy(point_to[0], point_to[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_line(converted_point_from, converted_point_to, width=width, fill=color)
def DrawPoint(self, point, size=2, color='black'):
if point == (None, None):
return
converted_point = self._convert_xy_to_canvas_xy(point[0], point[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_oval(converted_point[0] - size, converted_point[1] - size,
converted_point[0] + size, converted_point[1] + size, fill=color,
outline=color)
def DrawCircle(self, center_location, radius, fill_color=None, line_color='black'):
if center_location == (None, None):
return
converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_oval(converted_point[0] - radius, converted_point[1] - radius,
converted_point[0] + radius, converted_point[1] + radius, fill=fill_color,
outline=line_color)
def DrawOval(self, top_left, bottom_right, fill_color=None, line_color=None):
converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1])
converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_oval(converted_top_left[0], converted_top_left[1], converted_bottom_right[0],
converted_bottom_right[1], fill=fill_color, outline=line_color)
def DrawArc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black'):
converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1])
converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1])
tkstyle = tk.PIESLICE if style is None else style
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_arc(converted_top_left[0], converted_top_left[1], converted_bottom_right[0],
converted_bottom_right[1], extent=extent, start=start_angle, style=tkstyle,
outline=arc_color)
def DrawRectangle(self, top_left, bottom_right, fill_color=None, line_color=None):
converted_top_left = self._convert_xy_to_canvas_xy(top_left[0], top_left[1])
converted_bottom_right = self._convert_xy_to_canvas_xy(bottom_right[0], bottom_right[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
return self._TKCanvas2.create_rectangle(converted_top_left[0], converted_top_left[1], converted_bottom_right[0],
converted_bottom_right[1], fill=fill_color, outline=line_color)
def DrawText(self, text, location, color='black', font=None, angle=0):
if location == (None, None):
return
converted_point = self._convert_xy_to_canvas_xy(location[0], location[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
text_id = self._TKCanvas2.create_text(converted_point[0], converted_point[1], text=text, font=font, fill=color,
angle=angle)
return text_id
def Erase(self):
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
self._TKCanvas2.delete('all')
def Update(self, background_color):
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
self._TKCanvas2.configure(background=background_color)
def Move(self, x_direction, y_direction):
zero_converted = self._convert_xy_to_canvas_xy(0, 0)
shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction)
shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1])
if self._TKCanvas2 is None:
print('*** WARNING - The Graph element has not been finalized and cannot be drawn upon ***')
print('Call Window.Finalize() prior to this operation')
return None
self._TKCanvas2.move('all', shift_amount[0], shift_amount[1])
def MoveFigure(self, figure, x_direction, y_direction):
zero_converted = self._convert_xy_to_canvas_xy(0, 0)
shift_converted = self._convert_xy_to_canvas_xy(x_direction, y_direction)
shift_amount = (shift_converted[0] - zero_converted[0], shift_converted[1] - zero_converted[1])
if figure is None:
print('*** WARNING - Your figure is None. It most likely means your did not Finalize your Window ***')
print('Call Window.Finalize() prior to all graph operations')
return None
self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1])
@property
def TKCanvas(self):
if self._TKCanvas2 is None:
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
return self._TKCanvas2
# Realtime button release callback
def ButtonReleaseCallBack(self, event):
self.ClickPosition = (None, None)
self.LastButtonClickedWasRealtime = not self.DragSubmits
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
if self.DragSubmits:
self.ParentForm.LastButtonClicked = None
self.MouseButtonDown = False
# Realtime button callback
def ButtonPressCallBack(self, event):
self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y)
self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick out of loop if read was called
self.MouseButtonDown = True
# Realtime button callback
def MotionCallBack(self, event):
if not self.MouseButtonDown:
return
self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y)
self.ParentForm.LastButtonClickedWasRealtime = self.DragSubmits
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick out of loop if read was called
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Frame #
# ---------------------------------------------------------------------- #
class Frame(Element):
def __init__(self, title, layout, title_color=None, background_color=None, title_location=None,
relief=DEFAULT_FRAME_RELIEF, size=(None, None), font=None, pad=None, border_width=None, key=None,
tooltip=None):
'''
Frame Element
:param title:
:param layout:
:param title_color:
:param background_color:
:param title_location:
:param relief:
:param size:
:param font:
:param pad:
:param border_width:
:param key:
:param tooltip:
'''
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.Rows = []
# self.ParentForm = None
self.TKFrame = None
self.Title = title
self.Relief = relief
self.TitleLocation = title_location
self.BorderWidth = border_width
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.Layout(layout)
super().__init__(ELEM_TYPE_FRAME, background_color=background_color, text_color=title_color, size=size,
font=font, pad=pad, key=key, tooltip=tooltip)
return
def AddRow(self, *args):
''' Parms are a variable number of Elements '''
NumRows = len(self.Rows) # number of existing rows is our row number
CurrentRowNumber = NumRows # this row's number
CurrentRow = [] # start with a blank row and build up
# ------------------------- Add the elements to a row ------------------------- #
for i, element in enumerate(args): # Loop through list of elements and add them to the row
element.Position = (CurrentRowNumber, i)
element.ParentContainer = self
CurrentRow.append(element)
if element.Key is not None:
self.UseDictionary = True
# ------------------------- Append the row to list of Rows ------------------------- #
self.Rows.append(CurrentRow)
def Layout(self, rows):
for row in rows:
self.AddRow(*row)
def _GetElementAtLocation(self, location):
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def __del__(self):
for row in self.Rows:
for element in row:
element.__del__()
super().__del__()
# ---------------------------------------------------------------------- #
# Separator #
# Routes stdout, stderr to a scrolled window #
# ---------------------------------------------------------------------- #
class VerticalSeparator(Element):
def __init__(self, pad=None):
'''
VerticalSeperator - A separator that spans only 1 row in a vertical fashion
:param pad:
'''
self.Orientation = 'vertical' # for now only vertical works
super().__init__(ELEM_TYPE_SEPARATOR, pad=pad)
def __del__(self):
super().__del__()
VSeperator = VerticalSeparator
VSep = VerticalSeparator
# ---------------------------------------------------------------------- #
# Tab #
# ---------------------------------------------------------------------- #
class Tab(Element):
def __init__(self, title, layout, title_color=None, background_color=None, font=None, pad=None, disabled=False,
border_width=None, key=None, tooltip=None):
'''
Tab Element
:param title:
:param layout:
:param title_color:
:param background_color:
:param font:
:param pad:
:param disabled:
:param border_width:
:param key:
:param tooltip:
'''
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.Rows = []
self.TKFrame = None
self.Title = title
self.BorderWidth = border_width
self.Disabled = disabled
self.ParentNotebook = None
self.TabID = None
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.Layout(layout)
super().__init__(ELEM_TYPE_TAB, background_color=background_color, text_color=title_color, font=font, pad=pad,
key=key, tooltip=tooltip)
return
def AddRow(self, *args):
''' Parms are a variable number of Elements '''
NumRows = len(self.Rows) # number of existing rows is our row number
CurrentRowNumber = NumRows # this row's number
CurrentRow = [] # start with a blank row and build up
# ------------------------- Add the elements to a row ------------------------- #
for i, element in enumerate(args): # Loop through list of elements and add them to the row
element.Position = (CurrentRowNumber, i)
element.ParentContainer = self
CurrentRow.append(element)
if element.Key is not None:
self.UseDictionary = True
# ------------------------- Append the row to list of Rows ------------------------- #
self.Rows.append(CurrentRow)
def Layout(self, rows):
for row in rows:
self.AddRow(*row)
return self
def Update(self, disabled=None): # TODO Disable / enable of tabs is not complete
if disabled is None:
return
self.Disabled = disabled
state = 'disabled' if disabled is True else 'normal'
self.ParentNotebook.tab(self.TabID, state=state)
return self
def _GetElementAtLocation(self, location):
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def __del__(self):
for row in self.Rows:
for element in row:
element.__del__()
super().__del__()
# ---------------------------------------------------------------------- #
# TabGroup #
# ---------------------------------------------------------------------- #
class TabGroup(Element):
def __init__(self, layout, tab_location=None, title_color=None, selected_title_color=None, background_color=None,
font=None, change_submits=False, pad=None, border_width=None, theme=None, key=None, tooltip=None):
'''
TabGroup Element
:param layout:
:param tab_location:
:param title_color:
:param selected_title_color:
:param background_color:
:param font:
:param change_submits:
:param pad:
:param border_width:
:param theme:
:param key:
:param tooltip:
'''
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.SelectedTitleColor = selected_title_color
self.Rows = []
self.TKNotebook = None
self.TabCount = 0
self.BorderWidth = border_width
self.Theme = theme
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.ChangeSubmits = change_submits
self.TabLocation = tab_location
self.Layout(layout)
super().__init__(ELEM_TYPE_TAB_GROUP, background_color=background_color, text_color=title_color, font=font,
pad=pad, key=key, tooltip=tooltip)
return
def AddRow(self, *args):
''' Parms are a variable number of Elements '''
NumRows = len(self.Rows) # number of existing rows is our row number
CurrentRowNumber = NumRows # this row's number
CurrentRow = [] # start with a blank row and build up
# ------------------------- Add the elements to a row ------------------------- #
for i, element in enumerate(args): # Loop through list of elements and add them to the row
element.Position = (CurrentRowNumber, i)
element.ParentContainer = self
CurrentRow.append(element)
if element.Key is not None:
self.UseDictionary = True
# ------------------------- Append the row to list of Rows ------------------------- #
self.Rows.append(CurrentRow)
def Layout(self, rows):
for row in rows:
self.AddRow(*row)
def _GetElementAtLocation(self, location):
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def FindKeyFromTabName(self, tab_name):
for row in self.Rows:
for element in row:
if element.Title == tab_name:
return element.Key
return None
def __del__(self):
for row in self.Rows:
for element in row:
element.__del__()
super().__del__()
# ---------------------------------------------------------------------- #
# Slider #
# ---------------------------------------------------------------------- #
class Slider(Element):
def __init__(self, range=(None, None), default_value=None, resolution=None, tick_interval=None, orientation=None,
border_width=None, relief=None, change_submits=False, disabled=False, size=(None, None), font=None,
background_color=None, text_color=None, key=None, pad=None, tooltip=None):
'''
Slider Element
:param range:
:param default_value:
:param resolution:
:param orientation:
:param border_width:
:param relief:
:param change_submits:
:param disabled:
:param size:
:param font:
:param background_color:
:param text_color:
:param key:
:param pad:
:param tooltip:
'''
self.TKScale = None
self.Range = (1, 10) if range == (None, None) else range
self.DefaultValue = self.Range[0] if default_value is None else default_value
self.Orientation = orientation if orientation else DEFAULT_SLIDER_ORIENTATION
self.BorderWidth = border_width if border_width else DEFAULT_SLIDER_BORDER_WIDTH
self.Relief = relief if relief else DEFAULT_SLIDER_RELIEF
self.Resolution = 1 if resolution is None else resolution
self.ChangeSubmits = change_submits
self.Disabled = disabled
self.TickInterval = tick_interval
temp_size = size
if temp_size == (None, None):
temp_size = (20, 20) if orientation.startswith('h') else (8, 20)
super().__init__(ELEM_TYPE_INPUT_SLIDER, size=temp_size, font=font, background_color=background_color,
text_color=text_color, key=key, pad=pad, tooltip=tooltip)
return
def Update(self, value=None, range=(None, None), disabled=None):
if value is not None:
try:
self.TKIntVar.set(value)
if range != (None, None):
self.TKScale.config(from_=range[0], to_=range[1])
except:
pass
self.DefaultValue = value
if disabled == True:
self.TKScale['state'] = 'disabled'
elif disabled == False:
self.TKScale['state'] = 'normal'
def SliderChangedHandler(self, event):
# first, get the results table built
# modify the Results table in the parent FlexForm object
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# TkScrollableFrame (Used by Column) #
# ---------------------------------------------------------------------- #
class TkScrollableFrame(tk.Frame):
def __init__(self, master, vertical_only, **kwargs):
tk.Frame.__init__(self, master, **kwargs)
# create a canvas object and a vertical scrollbar for scrolling it
self.vscrollbar = tk.Scrollbar(self, orient=tk.VERTICAL)
self.vscrollbar.pack(side='right', fill="y", expand="false")
if not vertical_only:
self.hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.hscrollbar.pack(side='bottom', fill="x", expand="false")
self.canvas = tk.Canvas(self, yscrollcommand=self.vscrollbar.set, xscrollcommand=self.hscrollbar.set)
else:
self.canvas = tk.Canvas(self, yscrollcommand=self.vscrollbar.set)
self.canvas.pack(side="left", fill="both", expand=True)
self.vscrollbar.config(command=self.canvas.yview)
if not vertical_only:
self.hscrollbar.config(command=self.canvas.xview)
# reset the view
self.canvas.xview_moveto(0)
self.canvas.yview_moveto(0)
# create a frame inside the canvas which will be scrolled with it
self.TKFrame = tk.Frame(self.canvas, **kwargs)
self.frame_id = self.canvas.create_window(0, 0, window=self.TKFrame, anchor="nw")
self.canvas.config(borderwidth=0, highlightthickness=0)
self.TKFrame.config(borderwidth=0, highlightthickness=0)
self.config(borderwidth=0, highlightthickness=0)
# scrollbar = tk.Scrollbar(frame)
# scrollbar.pack(side=tk.RIGHT, fill='y')
# scrollbar.config(command=treeview.yview)
# self.vscrollbar.config(command=self.TKFrame.yview)
# self.TKFrame.configure(yscrollcommand=self.vscrollbar.set)
self.bind('<Configure>', self.set_scrollregion)
self.bind_mouse_scroll(self.canvas, self.yscroll)
if not vertical_only:
self.bind_mouse_scroll(self.hscrollbar, self.xscroll)
self.bind_mouse_scroll(self.vscrollbar, self.yscroll)
def resize_frame(self, e):
self.canvas.itemconfig(self.frame_id, height=e.height, width=e.width)
def yscroll(self, event):
if event.num == 5 or event.delta < 0:
self.canvas.yview_scroll(1, "unit")
elif event.num == 4 or event.delta > 0:
self.canvas.yview_scroll(-1, "unit")
def xscroll(self, event):
if event.num == 5 or event.delta < 0:
self.canvas.xview_scroll(1, "unit")
elif event.num == 4 or event.delta > 0:
self.canvas.xview_scroll(-1, "unit")
def bind_mouse_scroll(self, parent, mode):
# ~~ Windows only
parent.bind("<MouseWheel>", mode)
# ~~ Unix only
parent.bind("<Button-4>", mode)
parent.bind("<Button-5>", mode)
def set_scrollregion(self, event=None):
""" Set the scroll region on the canvas"""
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
# ---------------------------------------------------------------------- #
# Column #
# ---------------------------------------------------------------------- #
class Column(Element):
def __init__(self, layout, background_color=None, size=(None, None), pad=None, scrollable=False, vertical_scroll_only=False, key=None):
'''
Column Element
:param layout:
:param background_color:
:param size:
:param pad:
:param scrollable:
:param key:
'''
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.Rows = []
self.TKFrame = None
self.Scrollable = scrollable
self.VerticalScrollOnly = vertical_scroll_only
# self.ImageFilename = image_filename
# self.ImageData = image_data
# self.ImageSize = image_size
# self.ImageSubsample = image_subsample
bg = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.Layout(layout)
super().__init__(ELEM_TYPE_COLUMN, background_color=background_color, size=size, pad=pad, key=key)
return
def AddRow(self, *args):
''' Parms are a variable number of Elements '''
NumRows = len(self.Rows) # number of existing rows is our row number
CurrentRowNumber = NumRows # this row's number
CurrentRow = [] # start with a blank row and build up
# ------------------------- Add the elements to a row ------------------------- #
for i, element in enumerate(args): # Loop through list of elements and add them to the row
element.Position = (CurrentRowNumber, i)
element.ParentContainer = self
CurrentRow.append(element)
if element.Key is not None:
self.UseDictionary = True
# ------------------------- Append the row to list of Rows ------------------------- #
self.Rows.append(CurrentRow)
def Layout(self, rows):
for row in rows:
self.AddRow(*row)
def _GetElementAtLocation(self, location):
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def __del__(self):
for row in self.Rows:
for element in row:
element.__del__()
try:
del (self.TKFrame)
except:
pass
super().__del__()
# ---------------------------------------------------------------------- #
# Calendar #
# ---------------------------------------------------------------------- #
class TKCalendar(ttk.Frame):
"""
This code was shamelessly lifted from moshekaplan's repository - moshekaplan/tkinter_components
"""
# XXX ToDo: cget and configure
datetime = calendar.datetime.datetime
timedelta = calendar.datetime.timedelta
def __init__(self, master=None, target_element=None, close_when_chosen=True, default_date=(None, None, None), **kw):
"""
WIDGET-SPECIFIC OPTIONS
locale, firstweekday, year, month, selectbackground,
selectforeground
"""
self._TargetElement = target_element
default_mon, default_day, default_year = default_date
# remove custom options from kw before initializating ttk.Frame
fwday = kw.pop('firstweekday', calendar.MONDAY)
year = kw.pop('year', default_year or self.datetime.now().year)
month = kw.pop('month', default_mon or self.datetime.now().month)
locale = kw.pop('locale', None)
sel_bg = kw.pop('selectbackground', '#ecffc4')
sel_fg = kw.pop('selectforeground', '#05640e')
self._date = self.datetime(year, month, default_day or 1)
self._selection = None # no date selected
self._master = master
self.close_when_chosen = close_when_chosen
ttk.Frame.__init__(self, master, **kw)
# instantiate proper calendar class
if locale is None:
self._cal = calendar.TextCalendar(fwday)
else:
self._cal = calendar.LocaleTextCalendar(fwday, locale)
self.__setup_styles() # creates custom styles
self.__place_widgets() # pack/grid used widgets
self.__config_calendar() # adjust calendar columns and setup tags
# configure a canvas, and proper bindings, for selecting dates
self.__setup_selection(sel_bg, sel_fg)
# store items ids, used for insertion later
self._items = [self._calendar.insert('', 'end', values='')
for _ in range(6)]
# insert dates in the currently empty calendar
self._build_calendar()
def __setitem__(self, item, value):
if item in ('year', 'month'):
raise AttributeError("attribute '%s' is not writeable" % item)
elif item == 'selectbackground':
self._canvas['background'] = value
elif item == 'selectforeground':
self._canvas.itemconfigure(self._canvas.text, item=value)
else:
ttk.Frame.__setitem__(self, item, value)
def __getitem__(self, item):
if item in ('year', 'month'):
return getattr(self._date, item)
elif item == 'selectbackground':
return self._canvas['background']
elif item == 'selectforeground':
return self._canvas.itemcget(self._canvas.text, 'fill')
else:
r = ttk.tclobjs_to_py({item: ttk.Frame.__getitem__(self, item)})
return r[item]
def __setup_styles(self):
# custom ttk styles
style = ttk.Style(self.master)
arrow_layout = lambda dir: (
[('Button.focus', {'children': [('Button.%sarrow' % dir, None)]})]
)
style.layout('L.TButton', arrow_layout('left'))
style.layout('R.TButton', arrow_layout('right'))
def __place_widgets(self):
# header frame and its widgets
hframe = ttk.Frame(self)
lbtn = ttk.Button(hframe, style='L.TButton', command=self._prev_month)
rbtn = ttk.Button(hframe, style='R.TButton', command=self._next_month)
self._header = ttk.Label(hframe, width=15, anchor='center')
# the calendar
self._calendar = ttk.Treeview(self, show='', selectmode='none', height=7)
# pack the widgets
hframe.pack(in_=self, side='top', pady=4, anchor='center')
lbtn.grid(in_=hframe)
self._header.grid(in_=hframe, column=1, row=0, padx=12)
rbtn.grid(in_=hframe, column=2, row=0)
self._calendar.pack(in_=self, expand=1, fill='both', side='bottom')
def __config_calendar(self):
cols = self._cal.formatweekheader(3).split()
self._calendar['columns'] = cols
self._calendar.tag_configure('header', background='grey90')
self._calendar.insert('', 'end', values=cols, tag='header')
# adjust its columns width
font = tkinter.font.Font()
maxwidth = max(font.measure(col) for col in cols)
for col in cols:
self._calendar.column(col, width=maxwidth, minwidth=maxwidth,
anchor='e')
def __setup_selection(self, sel_bg, sel_fg):
self._font = tkinter.font.Font()
self._canvas = canvas = tk.Canvas(self._calendar,
background=sel_bg, borderwidth=0, highlightthickness=0)
canvas.text = canvas.create_text(0, 0, fill=sel_fg, anchor='w')
canvas.bind('<ButtonPress-1>', lambda evt: canvas.place_forget())
self._calendar.bind('<Configure>', lambda evt: canvas.place_forget())
self._calendar.bind('<ButtonPress-1>', self._pressed)
def __minsize(self, evt):
width, height = self._calendar.master.geometry().split('x')
height = height[:height.index('+')]
self._calendar.master.minsize(width, height)
def _build_calendar(self):
year, month = self._date.year, self._date.month
# update header text (Month, YEAR)
header = self._cal.formatmonthname(year, month, 0)
self._header['text'] = header.title()
# update calendar shown dates
cal = self._cal.monthdayscalendar(year, month)
for indx, item in enumerate(self._items):
week = cal[indx] if indx < len(cal) else []
fmt_week = [('%02d' % day) if day else '' for day in week]
self._calendar.item(item, values=fmt_week)
def _show_selection(self, text, bbox):
"""Configure canvas for a new selection."""
x, y, width, height = bbox
textw = self._font.measure(text)
canvas = self._canvas
canvas.configure(width=width, height=height)
canvas.coords(canvas.text, width - textw, height / 2 - 1)
canvas.itemconfigure(canvas.text, text=text)
canvas.place(in_=self._calendar, x=x, y=y)
# Callbacks
def _pressed(self, evt):
"""Clicked somewhere in the calendar."""
x, y, widget = evt.x, evt.y, evt.widget
item = widget.identify_row(y)
column = widget.identify_column(x)
if not column or not item in self._items:
# clicked in the weekdays row or just outside the columns
return
item_values = widget.item(item)['values']
if not len(item_values): # row is empty for this month
return
text = item_values[int(column[1]) - 1]
if not text: # date is empty
return
bbox = widget.bbox(item, column)
if not bbox: # calendar not visible yet
return
# update and then show selection
text = '%02d' % text
self._selection = (text, item, column)
self._show_selection(text, bbox)
year, month = self._date.year, self._date.month
try:
self._TargetElement.Update(self.datetime(year, month, int(self._selection[0])))
if self._TargetElement.ChangeSubmits:
self._TargetElement.ParentForm.LastButtonClicked = self._TargetElement.Key
self._TargetElement.ParentForm.FormRemainedOpen = True
self._TargetElement.ParentForm.TKroot.quit() # kick the users out of the mainloop
except:
pass
if self.close_when_chosen:
self._master.destroy()
def _prev_month(self):
"""Updated calendar to show the previous month."""
self._canvas.place_forget()
self._date = self._date - self.timedelta(days=1)
self._date = self.datetime(self._date.year, self._date.month, 1)
self._build_calendar() # reconstuct calendar
def _next_month(self):
"""Update calendar to show the next month."""
self._canvas.place_forget()
year, month = self._date.year, self._date.month
self._date = self._date + self.timedelta(
days=calendar.monthrange(year, month)[1] + 1)
self._date = self.datetime(self._date.year, self._date.month, 1)
self._build_calendar() # reconstruct calendar
# Properties
@property
def selection(self):
"""Return a datetime representing the current selected date."""
if not self._selection:
return None
year, month = self._date.year, self._date.month
return self.datetime(year, month, int(self._selection[0]))
# ---------------------------------------------------------------------- #
# Menu #
# ---------------------------------------------------------------------- #
class Menu(Element):
def __init__(self, menu_definition, background_color=None, size=(None, None), tearoff=False, pad=None, key=None):
'''
Menu Element
:param menu_definition:
:param background_color:
:param size:
:param tearoff:
:param pad:
:param key:
'''
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.MenuDefinition = menu_definition
self.TKMenu = None
self.Tearoff = tearoff
super().__init__(ELEM_TYPE_MENUBAR, background_color=background_color, size=size, pad=pad, key=key)
return
def MenuItemChosenCallback(self, item_chosen):
# print('IN MENU ITEM CALLBACK', item_chosen)
self.ParentForm.LastButtonClicked = item_chosen
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Table #
# ---------------------------------------------------------------------- #
class Table(Element):
def __init__(self, values, headings=None, visible_column_map=None, col_widths=None, def_col_width=10,
auto_size_columns=True, max_col_width=20, select_mode=None, display_row_numbers=False, num_rows=None,
font=None, justification='right', text_color=None, background_color=None, alternating_row_color=None,
size=(None, None), change_submits=False, bind_return_key=False, pad=None, key=None, tooltip=None):
'''
Table Element
:param values:
:param headings:
:param visible_column_map:
:param col_widths:
:param def_col_width:
:param auto_size_columns:
:param max_col_width:
:param select_mode:
:param display_row_numbers:
:param font:
:param justification:
:param text_color:
:param background_color:
:param size:
:param pad:
:param key:
:param tooltip:
'''
self.Values = values
self.ColumnHeadings = headings
self.ColumnsToDisplay = visible_column_map
self.ColumnWidths = col_widths
self.MaxColumnWidth = max_col_width
self.DefaultColumnWidth = def_col_width
self.AutoSizeColumns = auto_size_columns
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.TextColor = text_color
self.Justification = justification
self.InitialState = None
self.SelectMode = select_mode
self.DisplayRowNumbers = display_row_numbers
self.NumRows = num_rows if num_rows is not None else size[1]
self.TKTreeview = None
self.AlternatingRowColor = alternating_row_color
self.SelectedRows = []
self.ChangeSubmits = change_submits
self.BindReturnKey = bind_return_key
self.StartingRowNumber = 0 # When displaying row numbers, where to start
self.RowHeaderText = 'Row'
super().__init__(ELEM_TYPE_TABLE, text_color=text_color, background_color=background_color, font=font,
size=size, pad=pad, key=key, tooltip=tooltip)
return
def Update(self, values=None):
if values is not None:
children = self.TKTreeview.get_children()
for i in children:
self.TKTreeview.detach(i)
self.TKTreeview.delete(i)
children = self.TKTreeview.get_children()
# self.TKTreeview.delete(*self.TKTreeview.get_children())
for i, value in enumerate(values):
if self.DisplayRowNumbers:
value = [i+self.StartingRowNumber] + value
id = self.TKTreeview.insert('', 'end', text=i, iid=i + 1, values=value, tag=i % 2)
if self.AlternatingRowColor is not None:
self.TKTreeview.tag_configure(1, background=self.AlternatingRowColor)
self.Values = values
self.SelectedRows = []
def treeview_selected(self, event):
selections = self.TKTreeview.selection()
self.SelectedRows = [int(x) - 1 for x in selections]
if self.ChangeSubmits:
MyForm = self.ParentForm
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def treeview_double_click(self, event):
selections = self.TKTreeview.selection()
self.SelectedRows = [int(x) - 1 for x in selections]
if self.BindReturnKey:
MyForm = self.ParentForm
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def __del__(self):
super().__del__()
# ---------------------------------------------------------------------- #
# Tree #
# ---------------------------------------------------------------------- #
class Tree(Element):
def __init__(self, data=None, headings=None, visible_column_map=None, col_widths=None, col0_width=10,
def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, show_expanded=False, change_submits=False, font=None,
justification='right', text_color=None, background_color=None, num_rows=None, pad=None, key=None,
tooltip=None):
'''
Tree Element
:param headings:
:param visible_column_map:
:param col_widths:
:param def_col_width:
:param auto_size_columns:
:param max_col_width:
:param select_mode:
:param font:
:param justification:
:param text_color:
:param background_color:
:param num_rows:
:param pad:
:param key:
:param tooltip:
'''
self.TreeData = data
self.ColumnHeadings = headings
self.ColumnsToDisplay = visible_column_map
self.ColumnWidths = col_widths
self.MaxColumnWidth = max_col_width
self.DefaultColumnWidth = def_col_width
self.AutoSizeColumns = auto_size_columns
self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR
self.TextColor = text_color
self.Justification = justification
self.InitialState = None
self.SelectMode = select_mode
self.ShowExpanded = show_expanded
self.NumRows = num_rows
self.Col0Width = col0_width
self.TKTreeview = None
self.SelectedRows = []
self.ChangeSubmits = change_submits
super().__init__(ELEM_TYPE_TREE, text_color=text_color, background_color=background_color, font=font, pad=pad,
key=key, tooltip=tooltip)
return
def treeview_selected(self, event):
selections = self.TKTreeview.selection()
self.SelectedRows = [x for x in selections]
if self.ChangeSubmits:
MyForm = self.ParentForm
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
if self.ParentForm.CurrentlyRunningMainloop:
self.ParentForm.TKroot.quit()
def add_treeview_data(self, node):
# print(f'Inserting {node.key} under parent {node.parent}')
if node.key != '':
self.TKTreeview.insert(node.parent, 'end', node.key, text=node.text, values=node.values,
open=self.ShowExpanded)
for node in node.children:
self.add_treeview_data(node)
def Update(self, values=None, key=None, value=None, text=None):
if values is not None:
children = self.TKTreeview.get_children()
for i in children:
self.TKTreeview.detach(i)
self.TKTreeview.delete(i)
children = self.TKTreeview.get_children()
self.TreeData = values
self.add_treeview_data(self.TreeData.root_node)
self.SelectedRows = []
if key is not None:
item = self.TKTreeview.item(key)
if value is not None:
self.TKTreeview.item(key, values=value)
if text is not None:
self.TKTreeview.item(key, text=text)
item = self.TKTreeview.item(key)
return self
def __del__(self):
super().__del__()
class TreeData(object):
class Node(object):
def __init__(self, parent, key, text, values):
self.parent = parent
self.children = []
self.key = key
self.text = text
self.values = values
def _Add(self, node):
self.children.append(node)
def __init__(self):
self.tree_dict = {}
self.root_node = self.Node("", "", 'root', [])
self.tree_dict[""] = self.root_node
def _AddNode(self, key, node):
self.tree_dict[key] = node
def Insert(self, parent, key, text, values):
node = self.Node(parent, key, text, values)
self.tree_dict[key] = node
parent_node = self.tree_dict[parent]
parent_node._Add(node)
def __repr__(self):
return self._NodeStr(self.root_node, 1)
def _NodeStr(self, node, level):
return '\n'.join(
[str(node.key) + ' : ' + str(node.text)] +
[' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children])
# ---------------------------------------------------------------------- #
# Error Element #
# ---------------------------------------------------------------------- #
class ErrorElement(Element):
def __init__(self, key=None):
'''
Error Element
:param key:
'''
self.Key = key
super().__init__(ELEM_TYPE_ERROR, key=key)
return
def Update(self, *args, **kwargs):
PopupError('Keyword error in Update',
'You need to stop this madness and check your spelling',
'Bad key = {}'.format(self.Key),
'Your bad line of code may resemble this:',
'window.FindElement("{}")'.format(self.Key))
return self
def Get(self):
return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!'
def __del__(self):
super().__del__()
# ------------------------------------------------------------------------- #
# Window CLASS #
# ------------------------------------------------------------------------- #
class Window:
def __init__(self, title, default_element_size=DEFAULT_ELEMENT_SIZE, default_button_element_size=(None, None),
auto_size_text=None, auto_size_buttons=None, location=(None, None), size=(None, None), button_color=None, font=None,
progress_bar_color=(None, None), background_color=None, border_depth=None, auto_close=False,
auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, force_toplevel=False,
alpha_channel=1, return_keyboard_events=False, use_default_focus=True, text_justification=None,
no_titlebar=False, grab_anywhere=False, keep_on_top=False, resizable=False, disable_close=False):
'''
Window
:param title:
:param default_element_size:
:param default_button_element_size:
:param auto_size_text:
:param auto_size_buttons:
:param location:
:param button_color:
:param font:
:param progress_bar_color:
:param background_color:
:param border_depth:
:param auto_close:
:param auto_close_duration:
:param icon:
:param force_toplevel:
:param return_keyboard_events:
:param use_default_focus:
:param text_justification:
:param no_titlebar:
:param grab_anywhere:
:param keep_on_top:
:param resizable:
'''
self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT
self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS
self.Title = title
self.Rows = [] # a list of ELEMENTS for this row
self.DefaultElementSize = default_element_size
self.DefaultButtonElementSize = default_button_element_size if default_button_element_size != (
None, None) else DEFAULT_BUTTON_ELEMENT_SIZE
self.Location = location
self.ButtonColor = button_color if button_color else DEFAULT_BUTTON_COLOR
self.BackgroundColor = background_color if background_color else DEFAULT_BACKGROUND_COLOR
self.ParentWindow = None
self.Font = font if font else DEFAULT_FONT
self.RadioDict = {}
self.BorderDepth = border_depth
self.WindowIcon = icon if icon is not None else _my_windows.user_defined_icon
self.AutoClose = auto_close
self.NonBlocking = False
self.TKroot = None
self.TKrootDestroyed = False
self.CurrentlyRunningMainloop = False
self.FormRemainedOpen = False
self.TKAfterID = None
self.ProgressBarColor = progress_bar_color
self.AutoCloseDuration = auto_close_duration
self.RootNeedsDestroying = False
self.Shown = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.LastButtonClicked = None
self.LastButtonClickedWasRealtime = False
self.UseDictionary = False
self.UseDefaultFocus = use_default_focus
self.ReturnKeyboardEvents = return_keyboard_events
self.LastKeyboardEvent = None
self.TextJustification = text_justification
self.NoTitleBar = no_titlebar
self.GrabAnywhere = grab_anywhere
self.KeepOnTop = keep_on_top
self.ForceTopLevel = force_toplevel
self.Resizable = resizable
self._AlphaChannel = alpha_channel
self.Timeout = None
self.TimeoutKey = '_timeout_'
self.TimerCancelled = False
self.DisableClose = disable_close
self._Hidden = False
self._Size = size
# ------------------------- Add ONE Row to Form ------------------------- #
def AddRow(self, *args):
''' Parms are a variable number of Elements '''
NumRows = len(self.Rows) # number of existing rows is our row number
CurrentRowNumber = NumRows # this row's number
CurrentRow = [] # start with a blank row and build up
# ------------------------- Add the elements to a row ------------------------- #
for i, element in enumerate(args): # Loop through list of elements and add them to the row
element.Position = (CurrentRowNumber, i)
element.ParentContainer = self
CurrentRow.append(element)
# ------------------------- Append the row to list of Rows ------------------------- #
self.Rows.append(CurrentRow)
# ------------------------- Add Multiple Rows to Form ------------------------- #
def AddRows(self, rows):
for row in rows:
self.AddRow(*row)
def Layout(self, rows):
self.AddRows(rows)
return self
def LayoutAndRead(self, rows, non_blocking=False):
self.AddRows(rows)
self.Show(non_blocking=non_blocking)
return self.ReturnValues
def LayoutAndShow(self, rows):
raise DeprecationWarning('LayoutAndShow is no longer supported... change your call to LayoutAndRead')
# ------------------------- ShowForm THIS IS IT! ------------------------- #
def Show(self, non_blocking=False):
self.Shown = True
# Compute num rows & num cols (it'll come in handy debugging)
self.NumRows = len(self.Rows)
self.NumCols = max(len(row) for row in self.Rows)
self.NonBlocking = non_blocking
# Search through entire form to see if any elements set the focus
# if not, then will set the focus to the first input element
found_focus = False
for row in self.Rows:
for element in row:
try:
if element.Focus:
found_focus = True
except:
pass
try:
if element.Key is not None:
self.UseDictionary = True
except:
pass
if not found_focus and self.UseDefaultFocus:
self.UseDefaultFocus = True
else:
self.UseDefaultFocus = False
# -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ##
StartupTK(self)
# If a button or keyboard event happened but no results have been built, build the results
if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None:
return BuildResults(self, False, self)
return self.ReturnValues
# ------------------------- SetIcon - set the window's fav icon ------------------------- #
def SetIcon(self, icon=None, pngbase64=None):
if pngbase64 != None:
img = tkinter.PhotoImage(data=pngbase64)
wicon = img
else:
wicon = icon
self.WindowIcon = wicon
try:
self.TKroot.iconbitmap(wicon)
except:
pass
def _GetElementAtLocation(self, location):
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def _GetDefaultElementSize(self):
return self.DefaultElementSize
def _AutoCloseAlarmCallback(self):
try:
window = self
if window:
if window.NonBlocking:
self.CloseNonBlockingForm()
else:
window._Close()
self.TKroot.quit()
self.RootNeedsDestroying = True
except:
pass
def _TimeoutAlarmCallback(self):
# first, get the results table built
# modify the Results table in the parent FlexForm object
# print('TIMEOUT CALLBACK')
if self.TimerCancelled:
# print('** timer was cancelled **')
return
self.LastButtonClicked = self.TimeoutKey
self.FormRemainedOpen = True
self.TKroot.quit() # kick the users out of the mainloop
def Read(self, timeout=None, timeout_key=TIMEOUT_KEY):
if timeout == 0: # timeout of zero runs the old readnonblocking
event, values = self.ReadNonBlocking()
if event is None:
event = timeout_key
if values is None:
event = None
return event, values # make event None if values was None and return
# Read with a timeout
self.Timeout = timeout
self.TimeoutKey = timeout_key
self.NonBlocking = False
if self.TKrootDestroyed:
return None, None
if not self.Shown:
self.Show()
else:
# if already have a button waiting, the return previously built results
if self.LastButtonClicked is not None and not self.LastButtonClickedWasRealtime:
# print(f'*** Found previous clicked saved {self.LastButtonClicked}')
results = BuildResults(self, False, self)
self.LastButtonClicked = None
return results
InitializeResults(self)
# if the last button clicked was realtime, emulate a read non-blocking
# the idea is to quickly return realtime buttons without any blocks until released
if self.LastButtonClickedWasRealtime:
# print(f'RTime down {self.LastButtonClicked}' )
try:
rc = self.TKroot.update()
except:
self.TKrootDestroyed = True
_my_windows.Decrement()
print('ROOT Destroyed')
results = BuildResults(self, False, self)
if results[0] != None and results[0] != timeout_key:
return results
else:
pass
# else:
# print("** REALTIME PROBLEM FOUND **", results)
# normal read blocking code....
if timeout != None:
self.TimerCancelled = False
self.TKAfterID = self.TKroot.after(timeout, self._TimeoutAlarmCallback)
self.CurrentlyRunningMainloop = True
# print(f'In main {self.Title}')
self.TKroot.protocol("WM_DESTROY_WINDOW", self.OnClosingCallback)
self.TKroot.protocol("WM_DELETE_WINDOW", self.OnClosingCallback)
self.TKroot.mainloop()
# print('Out main')
self.CurrentlyRunningMainloop = False
# if self.LastButtonClicked != TIMEOUT_KEY:
# print(f'Window {self.Title} Last button clicked = {self.LastButtonClicked}')
try:
self.TKroot.after_cancel(self.TKAfterID)
except:
pass
# print('** tkafter cancel failed **')
self.TimerCancelled = True
if self.RootNeedsDestroying:
print('*** DESTROYING LATE ***')
self.TKroot.destroy()
_my_windows.Decrement()
# if form was closed with X
if self.LastButtonClicked is None and self.LastKeyboardEvent is None and self.ReturnValues[0] is None:
_my_windows.Decrement()
# Determine return values
if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None:
results = BuildResults(self, False, self)
if not self.LastButtonClickedWasRealtime:
self.LastButtonClicked = None
return results
else:
return self.ReturnValues
def ReadNonBlocking(self):
if self.TKrootDestroyed:
try:
self.TKroot.quit()
self.TKroot.destroy()
except:
print('DESTROY FAILED')
return None, None
if not self.Shown:
self.Show(non_blocking=True)
try:
rc = self.TKroot.update()
except:
self.TKrootDestroyed = True
_my_windows.Decrement()
print("read failed")
# return None, None
if self.RootNeedsDestroying:
print('*** DESTROYING LATE ***')
self.TKroot.destroy()
_my_windows.Decrement()
return BuildResults(self, False, self)
def Finalize(self):
if self.TKrootDestroyed:
return self
if not self.Shown:
self.Show(non_blocking=True)
try:
rc = self.TKroot.update()
except:
self.TKrootDestroyed = True
_my_windows.Decrement()
# return None, None
return self
def Refresh(self):
if self.TKrootDestroyed:
return self
try:
rc = self.TKroot.update()
except:
pass
return self
def Fill(self, values_dict):
FillFormWithValues(self, values_dict)
return self
def FindElement(self, key):
element = _FindElementFromKeyInSubForm(self, key)
if element is None:
print('*** WARNING = FindElement did not find the key. Please check your key\'s spelling ***')
PopupError('Keyword error in FindElement Call',
'Bad key = {}'.format(key),
'Your bad line of code may resemble this:',
'window.FindElement("{}")'.format(key))
return ErrorElement(key=key)
return element
def FindElementWithFocus(self):
element = _FindElementWithFocusInSubForm(self)
return element
def SaveToDisk(self, filename):
try:
results = BuildResults(self, False, self)
with open(filename, 'wb') as sf:
pickle.dump(results[1], sf)
except:
print('*** Error saving form to disk ***')
def LoadFromDisk(self, filename):
try:
with open(filename, 'rb') as df:
self.Fill(pickle.load(df))
except:
print('*** Error loading form to disk ***')
def GetScreenDimensions(self):
if self.TKrootDestroyed:
return None, None
screen_width = self.TKroot.winfo_screenwidth() # get window info to move to middle of screen
screen_height = self.TKroot.winfo_screenheight()
return screen_width, screen_height
def Move(self, x, y):
try:
self.TKroot.geometry("+%s+%s" % (x, y))
except:
pass
def Minimize(self):
self.TKroot.iconify()
def StartMove(self, event):
try:
self.TKroot.x = event.x
self.TKroot.y = event.y
except:
pass
def StopMove(self, event):
try:
self.TKroot.x = None
self.TKroot.y = None
except:
pass
def OnMotion(self, event):
try:
deltax = event.x - self.TKroot.x
deltay = event.y - self.TKroot.y
x = self.TKroot.winfo_x() + deltax
y = self.TKroot.winfo_y() + deltay
self.TKroot.geometry("+%s+%s" % (x, y))
except:
pass
def _KeyboardCallback(self, event):
self.LastButtonClicked = None
self.FormRemainedOpen = True
if event.char != '':
self.LastKeyboardEvent = event.char
else:
self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode)
if not self.NonBlocking:
BuildResults(self, False, self)
if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit!
self.TKroot.quit()
def _MouseWheelCallback(self, event):
self.LastButtonClicked = None
self.FormRemainedOpen = True
self.LastKeyboardEvent = 'MouseWheel:Down' if event.delta < 0 else 'MouseWheel:Up'
if not self.NonBlocking:
BuildResults(self, False, self)
if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit!
self.TKroot.quit()
def _Close(self):
try:
self.TKroot.update()
except:
pass
if not self.NonBlocking:
BuildResults(self, False, self)
if self.TKrootDestroyed:
return None
self.TKrootDestroyed = True
self.RootNeedsDestroying = True
return None
def Close(self):
if self.TKrootDestroyed:
return
try:
self.TKroot.destroy()
_my_windows.Decrement()
except:
pass
CloseNonBlockingForm = Close
CloseNonBlocking = Close
# IT FINALLY WORKED! 29-Oct-2018 was the first time this damned thing got called
def OnClosingCallback(self):
global _my_windows
if self.DisableClose:
return
# print('Got closing callback')
if self.CurrentlyRunningMainloop: # quit if this is the current mainloop, otherwise don't quit!
self.TKroot.quit() # kick the users out of the mainloop
self.TKroot.destroy() # kick the users out of the mainloop
self.RootNeedsDestroying = True
else:
self.RootNeedsDestroying = True
self.TKrootDestroyed = True
return
def Disable(self):
self.TKroot.grab_set_global()
def Enable(self):
self.TKroot.grab_release()
def Hide(self):
self._Hidden = True
self.TKroot.withdraw()
def UnHide(self):
if self._Hidden:
self.TKroot.deiconify()
self._Hidden = False
def Disappear(self):
self.TKroot.attributes('-alpha', 0)
def Reappear(self):
self.TKroot.attributes('-alpha', 255)
def SetAlpha(self, alpha):
'''
Change the window's transparency
:param alpha: From 0 to 1 with 0 being completely transparent
:return:
'''
self._AlphaChannel = alpha
self.TKroot.attributes('-alpha', alpha)
@property
def AlphaChannel(self):
return self._AlphaChannel
@AlphaChannel.setter
def AlphaChannel(self, alpha):
self._AlphaChannel = alpha
self.TKroot.attributes('-alpha', alpha)
def BringToFront(self):
try:
self.TKroot.lift()
except:
pass
def CurrentLocation(self):
return int(self.TKroot.winfo_x()), int(self.TKroot.winfo_y())
@property
def Size(self):
win_width = self.TKroot.winfo_width()
win_height = self.TKroot.winfo_height()
return win_width, win_height
@Size.setter
def Size(self, size):
try:
self.TKroot.geometry("%sx%s" % (size[0], size[1]))
self.TKroot.update_idletasks()
except:
pass
def __enter__(self):
return self
def __exit__(self, *a):
self.__del__()
return False
def __del__(self):
for row in self.Rows:
for element in row:
element.__del__()
FlexForm = Window
# ################################################################################
# #######################################################################