initiated venv, installed FreeSimpleGUI and PySerial

This commit is contained in:
2025-10-22 17:20:59 +03:00
parent 434f9c0df3
commit f921bec1c3
1510 changed files with 218990 additions and 27193 deletions

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,140 @@
from __future__ import annotations
import inspect
import sys
import traceback
def _create_error_message():
"""
Creates an error message containing the filename and line number of the users
code that made the call into PySimpleGUI
:return: Error string to display with file, line number, and line of code
:rtype: str
"""
called_func = inspect.stack()[1].function
trace_details = traceback.format_stack()
error_message = ''
file_info_pysimplegui = trace_details[-1].split(',')[0]
for line in reversed(trace_details):
if line.split(',')[0] != file_info_pysimplegui:
error_message = line
break
if error_message != '':
error_parts = error_message.split(', ')
if len(error_parts) < 4:
error_message = error_parts[0] + '\n' + error_parts[1] + '\n' + ''.join(error_parts[2:])
return 'The PySimpleGUI internal reporting function is ' + called_func + '\n' + 'The error originated from:\n' + error_message
def _error_popup_with_traceback(title, *args, emoji=None):
if SUPPRESS_ERROR_POPUPS:
return
trace_details = traceback.format_stack()
error_message = ''
file_info_pysimplegui = None
for line in reversed(trace_details):
if __file__ not in line:
file_info_pysimplegui = line.split(',')[0]
error_message = line
break
if file_info_pysimplegui is None:
_error_popup_with_code(title, None, None, 'Did not find your traceback info', *args, emoji=emoji)
return
error_parts = None
if error_message != '':
error_parts = error_message.split(', ')
if len(error_parts) < 4:
error_message = error_parts[0] + '\n' + error_parts[1] + '\n' + ''.join(error_parts[2:])
if error_parts is None:
print('*** Error popup attempted but unable to parse error details ***')
print(trace_details)
return
filename = error_parts[0][error_parts[0].index('File ') + 5 :]
line_num = error_parts[1][error_parts[1].index('line ') + 5 :]
_error_popup_with_code(title, filename, line_num, error_message, *args, emoji=emoji)
def _error_popup_with_code(title, filename, line_num, *args, emoji=None):
"""
Makes the error popup window
:param title: The title that will be shown in the popup's titlebar and in the first line of the window
:type title: str
:param filename: The filename to show.. may not be the filename that actually encountered the exception!
:type filename: str
:param line_num: Line number within file with the error
:type line_num: int | str
:param args: A variable number of lines of messages
:type args: *Any
:param emoji: An optional BASE64 Encoded image to shows in the error window
:type emoji: bytes
"""
editor_filename = execute_get_editor()
emoji_data = emoji if emoji is not None else _random_error_emoji()
layout = [[Text('ERROR'), Text(title)], [Image(data=emoji_data)]]
lines = []
for msg in args:
if isinstance(msg, Exception):
lines += [[f'Additional Exception info pased in by PySimpleGUI or user: Error type is: {type(msg).__name__}']]
lines += [[f'In file {__file__} Line number {msg.__traceback__.tb_lineno}']]
lines += [[f'{msg}']]
else:
lines += [str(msg).split('\n')]
max_line_len = 0
for line in lines:
max_line_len = max(max_line_len, max([len(s) for s in line]))
layout += [[Text(''.join(line), size=(min(max_line_len, 90), None))] for line in lines]
layout += [
[
Button('Close'),
Button('Take me to error', disabled=True if not editor_filename else False),
Button('Kill Application', button_color='white on red'),
]
]
if not editor_filename:
layout += [[Text('Configure editor in the Global settings to enable "Take me to error" feature')]]
window = Window(title, layout, keep_on_top=True)
while True:
event, values = window.read()
if event in ('Close', WIN_CLOSED):
break
if event == 'Kill Application':
window.close()
popup_quick_message(
'KILLING APP! BYE!',
font='_ 18',
keep_on_top=True,
text_color='white',
background_color='red',
non_blocking=False,
)
sys.exit()
if event == 'Take me to error' and filename is not None and line_num is not None:
execute_editor(filename, line_num)
window.close()
def _exit_mainloop(exiting_window):
if exiting_window == Window._window_running_mainloop or Window._root_running_mainloop == Window.hidden_master_root:
Window._window_that_exited = exiting_window
if Window._root_running_mainloop is not None:
Window._root_running_mainloop.quit()
# print('** Exited window mainloop **')
from FreeSimpleGUI import _random_error_emoji
from FreeSimpleGUI import execute_editor
from FreeSimpleGUI import execute_get_editor
from FreeSimpleGUI import popup_quick_message
from FreeSimpleGUI import SUPPRESS_ERROR_POPUPS
from FreeSimpleGUI import WIN_CLOSED
from FreeSimpleGUI.elements.button import Button
from FreeSimpleGUI.elements.image import Image
from FreeSimpleGUI.elements.text import Text
from FreeSimpleGUI.window import Window

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,923 @@
from __future__ import annotations
import copy
import tkinter as tk
import warnings
from tkinter import ttk
import FreeSimpleGUI
from FreeSimpleGUI import BROWSE_FILES_DELIMITER
from FreeSimpleGUI import BUTTON_DISABLED_MEANS_IGNORE
from FreeSimpleGUI import BUTTON_TYPE_BROWSE_FILE
from FreeSimpleGUI import BUTTON_TYPE_BROWSE_FILES
from FreeSimpleGUI import BUTTON_TYPE_BROWSE_FOLDER
from FreeSimpleGUI import BUTTON_TYPE_CALENDAR_CHOOSER
from FreeSimpleGUI import BUTTON_TYPE_CLOSES_WIN
from FreeSimpleGUI import BUTTON_TYPE_CLOSES_WIN_ONLY
from FreeSimpleGUI import BUTTON_TYPE_COLOR_CHOOSER
from FreeSimpleGUI import BUTTON_TYPE_READ_FORM
from FreeSimpleGUI import BUTTON_TYPE_SAVEAS_FILE
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_BUTTON
from FreeSimpleGUI import ELEM_TYPE_BUTTONMENU
from FreeSimpleGUI import FILE_TYPES_ALL_FILES
from FreeSimpleGUI import MENU_SHORTCUT_CHARACTER
from FreeSimpleGUI import running_mac
from FreeSimpleGUI import theme_background_color
from FreeSimpleGUI import theme_button_color
from FreeSimpleGUI import theme_input_background_color
from FreeSimpleGUI import theme_input_text_color
from FreeSimpleGUI import ThisRow
from FreeSimpleGUI.elements.base import Element
from FreeSimpleGUI.elements.helpers import AddMenuItem
from FreeSimpleGUI.elements.helpers import button_color_to_tuple
class Button(Element):
"""
Button Element - Defines all possible buttons. The shortcuts such as Submit, FileBrowse, ... each create a Button
"""
def __init__(
self,
button_text='',
button_type=BUTTON_TYPE_READ_FORM,
target=(None, None),
tooltip=None,
file_types=FILE_TYPES_ALL_FILES,
initial_folder=None,
default_extension='',
disabled=False,
change_submits=False,
enable_events=False,
image_filename=None,
image_data=None,
image_size=(None, None),
image_subsample=None,
image_zoom=None,
image_source=None,
border_width=None,
size=(None, None),
s=(None, None),
auto_size_button=None,
button_color=None,
disabled_button_color=None,
highlight_colors=None,
mouseover_colors=(None, None),
use_ttk_buttons=None,
font=None,
bind_return_key=False,
focus=False,
pad=None,
p=None,
key=None,
k=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param button_text: Text to be displayed on the button
:type button_text: (str)
:param button_type: You should NOT be setting this directly. ONLY the shortcut functions set this
:type button_type: (int)
:param target: key or (row,col) target for the button. Note that -1 for column means 1 element to the left of this one. The constant ThisRow is used to indicate the current row. The Button itself is a valid target for some types of button
:type target: str | (int, int)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param file_types: the filetypes that will be used to match files. To indicate all files: (("ALL Files", "*.* *"),).
:type file_types: Tuple[(str, str), ...]
:param initial_folder: starting path for folders and files
:type initial_folder: (str)
:param default_extension: If no extension entered by user, add this to filename (only used in saveas dialogs)
:type default_extension: (str)
:param disabled: If True button will be created disabled. If BUTTON_DISABLED_MEANS_IGNORE then the button will be ignored rather than disabled using tkinter
:type disabled: (bool | str)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. If this button is a target, should it generate an event when filled in
:type enable_events: (bool)
:param image_source: Image to place on button. Use INSTEAD of the image_filename and image_data. Unifies these into 1 easier to use parm
:type image_source: (str | bytes)
:param image_filename: image filename if there is a button image. GIFs and PNGs only.
:type image_filename: (str)
:param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data
:type image_data: bytes | str
:param image_size: Size of the image in pixels (width, height)
:type image_size: (int, int)
:param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type image_subsample: (int)
:param image_zoom: amount to increase the size of the image. 2=twice size, 3=3 times, etc
:type image_zoom: (int)
:param border_width: width of border around button in pixels
:type border_width: (int)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int | None, int | None) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int | None, int | None) | (None, None) | int
:param auto_size_button: if True the button size is sized to fit the text
:type auto_size_button: (bool)
:param button_color: Color of button. default is from theme or the window. Easy to remember which is which if you say "ON" between colors. "red" on "green". Normally a tuple, but can be a simplified-button-color-string "foreground on background". Can be a single color if want to set only the background.
:type button_color: (str, str) | str
:param disabled_button_color: colors to use when button is disabled (text, background). Use None for a color if don't want to change. Only ttk buttons support both text and background colors. tk buttons only support changing text color
:type disabled_button_color: (str, str) | str
:param highlight_colors: colors to use when button has focus (has focus, does not have focus). None will use colors based on theme. Only used by Linux and only for non-TTK button
:type highlight_colors: (str, str)
:param mouseover_colors: Important difference between Linux & Windows! Linux - Colors when mouse moved over button. Windows - colors when button is pressed. The default is to switch the text and background colors (an inverse effect)
:type mouseover_colors: (str, str) | str
:param use_ttk_buttons: True = use ttk buttons. False = do not use ttk buttons. None (Default) = use ttk buttons only if on a Mac and not with button images
:type use_ttk_buttons: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param bind_return_key: If True then pressing the return key in an Input or Multiline Element will cause this button to appear to be clicked (generates event with this button's key
:type bind_return_key: (bool)
:param focus: if True, initial focus will be put on this button
:type focus: (bool)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.AutoSizeButton = auto_size_button
self.BType = button_type
if file_types is not None and len(file_types) == 2 and isinstance(file_types[0], str) and isinstance(file_types[1], str):
warnings.warn(
'file_types parameter not correctly specified. This parameter is a LIST of TUPLES. You have passed (str,str) rather than ((str, str),). Fixing it for you this time.\nchanging {} to {}\nPlease correct your code'.format(file_types, ((file_types[0], file_types[1]),)),
UserWarning,
)
file_types = ((file_types[0], file_types[1]),)
self.FileTypes = file_types
self.Widget = self.TKButton = None # type: tk.Button
self.Target = target
self.ButtonText = str(button_text)
self.RightClickMenu = right_click_menu
# Button colors can be a tuple (text, background) or a string with format "text on background"
self.ButtonColor = button_color_to_tuple(button_color)
self.DisabledButtonColor = button_color_to_tuple(disabled_button_color) if disabled_button_color is not None else (None, None)
if image_source is not None:
if isinstance(image_source, bytes):
image_data = image_source
elif isinstance(image_source, str):
image_filename = image_source
self.ImageFilename = image_filename
self.ImageData = image_data
self.ImageSize = image_size
self.ImageSubsample = image_subsample
self.zoom = int(image_zoom) if image_zoom is not None else None
self.UserData = None
self.BorderWidth = border_width if border_width is not None else FreeSimpleGUI.DEFAULT_BORDER_WIDTH
self.BindReturnKey = bind_return_key
self.Focus = focus
self.TKCal = None
self.calendar_default_date_M_D_Y = (None, None, None)
self.calendar_close_when_chosen = False
self.calendar_locale = None
self.calendar_format = None
self.calendar_location = (None, None)
self.calendar_no_titlebar = True
self.calendar_begin_at_sunday_plus = 0
self.calendar_month_names = None
self.calendar_day_abbreviations = None
self.calendar_title = ''
self.calendar_selection = ''
self.default_button = None
self.InitialFolder = initial_folder
self.DefaultExtension = default_extension
self.Disabled = disabled
self.ChangeSubmits = change_submits or enable_events
self.UseTtkButtons = use_ttk_buttons
self._files_delimiter = BROWSE_FILES_DELIMITER # used by the file browse button. used when multiple files are selected by user
if use_ttk_buttons is None and running_mac():
self.UseTtkButtons = True
if key is None and k is None:
_key = self.ButtonText
if FreeSimpleGUI.DEFAULT_USE_BUTTON_SHORTCUTS is True:
pos = _key.find(MENU_SHORTCUT_CHARACTER)
if pos != -1:
if pos < len(MENU_SHORTCUT_CHARACTER) or _key[pos - len(MENU_SHORTCUT_CHARACTER)] != '\\':
_key = _key[:pos] + _key[pos + len(MENU_SHORTCUT_CHARACTER) :]
else:
_key = _key.replace('\\' + MENU_SHORTCUT_CHARACTER, MENU_SHORTCUT_CHARACTER)
else:
_key = key if key is not None else k
if highlight_colors is not None:
self.HighlightColors = highlight_colors
else:
self.HighlightColors = self._compute_highlight_colors()
if mouseover_colors != (None, None):
self.MouseOverColors = button_color_to_tuple(mouseover_colors)
elif button_color is not None:
self.MouseOverColors = (self.ButtonColor[1], self.ButtonColor[0])
else:
self.MouseOverColors = (theme_button_color()[1], theme_button_color()[0])
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
sz = size if size != (None, None) else s
super().__init__(ELEM_TYPE_BUTTON, size=sz, font=font, pad=pad, key=_key, tooltip=tooltip, visible=visible, metadata=metadata)
return
def _compute_highlight_colors(self):
"""
Determines the color to use to indicate the button has focus. This setting is only used by Linux.
:return: Pair of colors. (Highlight, Highlight Background)
:rtype: (str, str)
"""
highlight_color = highlight_background = COLOR_SYSTEM_DEFAULT
if self.ButtonColor != COLOR_SYSTEM_DEFAULT and theme_background_color() != COLOR_SYSTEM_DEFAULT:
highlight_background = theme_background_color()
if self.ButtonColor != COLOR_SYSTEM_DEFAULT and self.ButtonColor[0] != COLOR_SYSTEM_DEFAULT:
if self.ButtonColor[0] != theme_background_color():
highlight_color = self.ButtonColor[0]
else:
highlight_color = 'red'
return (highlight_color, highlight_background)
# Realtime button release callback
def ButtonReleaseCallBack(self, parm):
"""
Not a user callable function. Called by tkinter when a "realtime" button is released
:param parm: the event info from tkinter
:type parm:
"""
self.LastButtonClickedWasRealtime = False
self.ParentForm.LastButtonClicked = None
# Realtime button callback
def ButtonPressCallBack(self, parm):
"""
Not a user callable method. Callback called by tkinter when a "realtime" button is pressed
:param parm: Event info passed in by tkinter
:type parm:
"""
self.ParentForm.LastButtonClickedWasRealtime = True
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = self.ButtonText
_exit_mainloop(self.ParentForm)
def _find_target(self):
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:
# Need a try-block because if the target is not hashable, the "in" test will raise exception
try:
if target in self.ParentForm.AllKeysDict:
target_element = self.ParentForm.AllKeysDict[target]
except:
pass
# if target not found or the above try got exception, then keep looking....
if target_element is None:
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.find_element(target)
try:
strvar = target_element.TKStringVar
except:
pass
try:
if target_element.ChangeSubmits:
should_submit_window = True
except:
pass
return target_element, strvar, should_submit_window
# ------- Button Callback ------- #
def ButtonCallBack(self):
"""
Not user callable! Called by tkinter when a button is clicked. This is where all the fun begins!
"""
if self.Disabled == BUTTON_DISABLED_MEANS_IGNORE:
return
target_element, strvar, should_submit_window = self._find_target()
filetypes = FILE_TYPES_ALL_FILES if self.FileTypes is None else self.FileTypes
if self.BType == BUTTON_TYPE_BROWSE_FOLDER:
if running_mac(): # macs don't like seeing the parent window (go firgure)
folder_name = tk.filedialog.askdirectory(initialdir=self.InitialFolder) # show the 'get folder' dialog box
else:
folder_name = tk.filedialog.askdirectory(initialdir=self.InitialFolder, parent=self.ParentForm.TKroot) # show the 'get folder' dialog box
if folder_name:
try:
strvar.set(folder_name)
self.TKStringVar.set(folder_name)
except:
pass
else: # if "cancel" button clicked, don't generate an event
should_submit_window = False
elif self.BType == BUTTON_TYPE_BROWSE_FILE:
if running_mac():
# Workaround for the "*.*" issue on Mac
is_all = [(x, y) for (x, y) in filetypes if all(ch in '* .' for ch in y)]
if not len(set(filetypes)) > 1 and (len(is_all) != 0 or filetypes == FILE_TYPES_ALL_FILES):
file_name = tk.filedialog.askopenfilename(initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.askopenfilename(initialdir=self.InitialFolder, filetypes=filetypes) # show the 'get file' dialog box
else:
file_name = tk.filedialog.askopenfilename(filetypes=filetypes, initialdir=self.InitialFolder, parent=self.ParentForm.TKroot) # show the 'get file' dialog box
if file_name:
strvar.set(file_name)
self.TKStringVar.set(file_name)
else: # if "cancel" button clicked, don't generate an event
should_submit_window = False
elif self.BType == BUTTON_TYPE_COLOR_CHOOSER:
color = tk.colorchooser.askcolor(parent=self.ParentForm.TKroot, color=self.default_color) # show the 'get file' dialog box
color = color[1] # save only the #RRGGBB portion
if color is not None:
strvar.set(color)
self.TKStringVar.set(color)
elif self.BType == BUTTON_TYPE_BROWSE_FILES:
if running_mac():
# Workaround for the "*.*" issue on Mac
is_all = [(x, y) for (x, y) in filetypes if all(ch in '* .' for ch in y)]
if not len(set(filetypes)) > 1 and (len(is_all) != 0 or filetypes == FILE_TYPES_ALL_FILES):
file_name = tk.filedialog.askopenfilenames(initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.askopenfilenames(filetypes=filetypes, initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.askopenfilenames(filetypes=filetypes, initialdir=self.InitialFolder, parent=self.ParentForm.TKroot)
if file_name:
file_name = self._files_delimiter.join(file_name) # normally a ';'
strvar.set(file_name)
self.TKStringVar.set(file_name)
else: # if "cancel" button clicked, don't generate an event
should_submit_window = False
elif self.BType == BUTTON_TYPE_SAVEAS_FILE:
# show the 'get file' dialog box
if running_mac():
# Workaround for the "*.*" issue on Mac
is_all = [(x, y) for (x, y) in filetypes if all(ch in '* .' for ch in y)]
if not len(set(filetypes)) > 1 and (len(is_all) != 0 or filetypes == FILE_TYPES_ALL_FILES):
file_name = tk.filedialog.asksaveasfilename(defaultextension=self.DefaultExtension, initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.asksaveasfilename(filetypes=filetypes, defaultextension=self.DefaultExtension, initialdir=self.InitialFolder)
else:
file_name = tk.filedialog.asksaveasfilename(
filetypes=filetypes,
defaultextension=self.DefaultExtension,
initialdir=self.InitialFolder,
parent=self.ParentForm.TKroot,
)
if file_name:
strvar.set(file_name)
self.TKStringVar.set(file_name)
else: # if "cancel" button clicked, don't generate an event
should_submit_window = False
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()
_exit_mainloop(self.ParentForm)
if self.ParentForm.NonBlocking:
self.ParentForm.TKroot.destroy()
Window._DecrementOpenCount()
elif self.BType == BUTTON_TYPE_READ_FORM: # LEAVE THE WINDOW OPEN!! DO NOT CLOSE
# This is a PLAIN BUTTON
# 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
_exit_mainloop(self.ParentForm)
elif self.BType == BUTTON_TYPE_CLOSES_WIN_ONLY: # special kind of button that does not exit main loop
self.ParentForm._Close(without_event=True)
self.ParentForm.TKroot.destroy() # close the window with tkinter
Window._DecrementOpenCount()
elif self.BType == BUTTON_TYPE_CALENDAR_CHOOSER: # this is a return type button so GET RESULTS and destroy window
# ------------ new chooser code -------------
self.ParentForm.LastButtonClicked = self.Key # key should have been generated already if not set by user
self.ParentForm.FormRemainedOpen = True
should_submit_window = False
_exit_mainloop(self.ParentForm)
# elif self.BType == BUTTON_TYPE_SHOW_DEBUGGER:
# **** DEPRICATED *****
# if self.ParentForm.DebuggerEnabled:
# show_debugger_popout_window()
if should_submit_window:
self.ParentForm.LastButtonClicked = target_element.Key
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
return
def update(
self,
text=None,
button_color=(None, None),
disabled=None,
image_source=None,
image_data=None,
image_filename=None,
visible=None,
image_subsample=None,
image_zoom=None,
disabled_button_color=(None, None),
image_size=None,
):
"""
Changes some of the settings for the Button Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param text: sets button text
:type text: (str)
:param button_color: Color of button. default is from theme or the window. Easy to remember which is which if you say "ON" between colors. "red" on "green". Normally a tuple, but can be a simplified-button-color-string "foreground on background". Can be a single color if want to set only the background.
:type button_color: (str, str) | str
:param disabled: True/False to enable/disable at the GUI level. Use BUTTON_DISABLED_MEANS_IGNORE to ignore clicks (won't change colors)
:type disabled: (bool | str)
:param image_source: Image to place on button. Use INSTEAD of the image_filename and image_data. Unifies these into 1 easier to use parm
:type image_source: (str | bytes)
:param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data
:type image_data: bytes | str
:param image_filename: image filename if there is a button image. GIFs and PNGs only.
:type image_filename: (str)
:param disabled_button_color: colors to use when button is disabled (text, background). Use None for a color if don't want to change. Only ttk buttons support both text and background colors. tk buttons only support changing text color
:type disabled_button_color: (str, str)
:param visible: control visibility of element
:type visible: (bool)
:param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type image_subsample: (int)
:param image_zoom: amount to increase the size of the image. 2=twice size, 3=3 times, etc
:type image_zoom: (int)
:param image_size: Size of the image in pixels (width, height)
:type image_size: (int, int)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Button.update - The window was closed')
return
if image_source is not None:
if isinstance(image_source, bytes):
image_data = image_source
elif isinstance(image_source, str):
image_filename = image_source
if self.UseTtkButtons:
style_name = self.ttk_style_name # created when made initial window (in the pack)
# style_name = str(self.Key) + 'custombutton.TButton'
button_style = ttk.Style()
if text is not None:
btext = text
if FreeSimpleGUI.DEFAULT_USE_BUTTON_SHORTCUTS is True:
pos = btext.find(MENU_SHORTCUT_CHARACTER)
if pos != -1:
if pos < len(MENU_SHORTCUT_CHARACTER) or btext[pos - len(MENU_SHORTCUT_CHARACTER)] != '\\':
btext = btext[:pos] + btext[pos + len(MENU_SHORTCUT_CHARACTER) :]
else:
btext = btext.replace('\\' + MENU_SHORTCUT_CHARACTER, MENU_SHORTCUT_CHARACTER)
pos = -1
if pos != -1:
self.TKButton.config(underline=pos)
self.TKButton.configure(text=btext)
self.ButtonText = text
if button_color != (None, None) and button_color != COLOR_SYSTEM_DEFAULT:
bc = button_color_to_tuple(button_color, self.ButtonColor)
if self.UseTtkButtons:
if bc[0] not in (None, COLOR_SYSTEM_DEFAULT):
button_style.configure(style_name, foreground=bc[0])
if bc[1] not in (None, COLOR_SYSTEM_DEFAULT):
button_style.configure(style_name, background=bc[1])
else:
if bc[0] not in (None, COLOR_SYSTEM_DEFAULT):
self.TKButton.config(foreground=bc[0], activebackground=bc[0])
if bc[1] not in (None, COLOR_SYSTEM_DEFAULT):
self.TKButton.config(background=bc[1], activeforeground=bc[1])
self.ButtonColor = bc
if disabled is True:
self.TKButton['state'] = 'disabled'
elif disabled is False:
self.TKButton['state'] = 'normal'
elif disabled == BUTTON_DISABLED_MEANS_IGNORE:
self.TKButton['state'] = 'normal'
self.Disabled = disabled if disabled is not None else self.Disabled
if image_data is not None:
image = tk.PhotoImage(data=image_data)
if image_subsample:
image = image.subsample(image_subsample)
if image_zoom is not None:
image = image.zoom(int(image_zoom))
if image_size is not None:
width, height = image_size
else:
width, height = image.width(), image.height()
if self.UseTtkButtons:
button_style.configure(style_name, image=image, width=width, height=height)
else:
self.TKButton.config(image=image, width=width, height=height)
self.TKButton.image = image
if image_filename is not None:
image = tk.PhotoImage(file=image_filename)
if image_subsample:
image = image.subsample(image_subsample)
if image_zoom is not None:
image = image.zoom(int(image_zoom))
if image_size is not None:
width, height = image_size
else:
width, height = image.width(), image.height()
if self.UseTtkButtons:
button_style.configure(style_name, image=image, width=width, height=height)
else:
self.TKButton.config(highlightthickness=0, image=image, width=width, height=height)
self.TKButton.image = image
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if disabled_button_color != (None, None) and disabled_button_color != COLOR_SYSTEM_DEFAULT:
if not self.UseTtkButtons:
self.TKButton['disabledforeground'] = disabled_button_color[0]
else:
if disabled_button_color[0] is not None:
button_style.map(style_name, foreground=[('disabled', disabled_button_color[0])])
if disabled_button_color[1] is not None:
button_style.map(style_name, background=[('disabled', disabled_button_color[1])])
self.DisabledButtonColor = (
disabled_button_color[0] if disabled_button_color[0] is not None else self.DisabledButtonColor[0],
disabled_button_color[1] if disabled_button_color[1] is not None else self.DisabledButtonColor[1],
)
if visible is not None:
self._visible = visible
def get_text(self):
"""
Returns the current text shown on a button
:return: The text currently displayed on the button
:rtype: (str)
"""
return self.ButtonText
def click(self):
"""
Generates a click of the button as if the user clicked the button
Calls the tkinter invoke method for the button
"""
try:
self.TKButton.invoke()
except:
print('Exception clicking button')
Click = click
GetText = get_text
Update = update
class ButtonMenu(Element):
"""
The Button Menu Element. Creates a button that when clicked will show a menu similar to right click menu
"""
def __init__(
self,
button_text,
menu_def,
tooltip=None,
disabled=False,
image_source=None,
image_filename=None,
image_data=None,
image_size=(None, None),
image_subsample=None,
image_zoom=None,
border_width=None,
size=(None, None),
s=(None, None),
auto_size_button=None,
button_color=None,
text_color=None,
background_color=None,
disabled_text_color=None,
font=None,
item_font=None,
pad=None,
p=None,
expand_x=False,
expand_y=False,
key=None,
k=None,
tearoff=False,
visible=True,
metadata=None,
):
"""
:param button_text: Text to be displayed on the button
:type button_text: (str)
:param menu_def: A list of lists of Menu items to show when this element is clicked. See docs for format as they are the same for all menu types
:type menu_def: List[List[str]]
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param disabled: If True button will be created disabled
:type disabled: (bool)
:param image_source: Image to place on button. Use INSTEAD of the image_filename and image_data. Unifies these into 1 easier to use parm
:type image_source: (str | bytes)
:param image_filename: image filename if there is a button image. GIFs and PNGs only.
:type image_filename: (str)
:param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data
:type image_data: bytes | str
:param image_size: Size of the image in pixels (width, height)
:type image_size: (int, int)
:param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type image_subsample: (int)
:param image_zoom: amount to increase the size of the image. 2=twice size, 3=3 times, etc
:type image_zoom: (int)
:param border_width: width of border around button in pixels
:type border_width: (int)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_button: if True the button size is sized to fit the text
:type auto_size_button: (bool)
:param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green"
:type button_color: (str, str) | str
:param background_color: color of the background
:type background_color: (str)
:param text_color: element's text color. Can be in #RRGGBB format or a color name "black"
:type text_color: (str)
:param disabled_text_color: color to use for text when item is disabled. Can be in #RRGGBB format or a color name "black"
:type disabled_text_color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param item_font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike, for the menu items
:type item_font: (str or (str, int[, str]) or None)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tearoff: Determines if menus should allow them to be torn off
:type tearoff: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.MenuDefinition = copy.deepcopy(menu_def)
self.AutoSizeButton = auto_size_button
self.ButtonText = button_text
self.ButtonColor = button_color_to_tuple(button_color)
self.BackgroundColor = background_color if background_color is not None else theme_input_background_color()
self.TextColor = text_color if text_color is not None else theme_input_text_color()
self.DisabledTextColor = disabled_text_color if disabled_text_color is not None else COLOR_SYSTEM_DEFAULT
self.ItemFont = item_font
self.BorderWidth = border_width if border_width is not None else FreeSimpleGUI.DEFAULT_BORDER_WIDTH
if image_source is not None:
if isinstance(image_source, str):
image_filename = image_source
elif isinstance(image_source, bytes):
image_data = image_source
else:
warnings.warn(f'ButtonMenu element - image_source is not a valid type: {type(image_source)}', UserWarning)
self.ImageFilename = image_filename
self.ImageData = image_data
self.ImageSize = image_size
self.ImageSubsample = image_subsample
self.zoom = int(image_zoom) if image_zoom is not None else None
self.Disabled = disabled
self.IsButtonMenu = True
self.MenuItemChosen = None
self.Widget = self.TKButtonMenu = None # type: tk.Menubutton
self.TKMenu = None # type: tk.Menu
self.part_of_custom_menubar = False
self.custom_menubar_key = None
# self.temp_size = size if size != (NONE, NONE) else
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_BUTTONMENU,
size=sz,
font=font,
pad=pad,
key=key,
tooltip=tooltip,
text_color=self.TextColor,
background_color=self.BackgroundColor,
visible=visible,
metadata=metadata,
)
self.Tearoff = tearoff
def _MenuItemChosenCallback(self, item_chosen): # ButtonMenu Menu Item Chosen Callback
"""
Not a user callable function. Called by tkinter when an item is chosen from the menu.
:param item_chosen: The menu item chosen.
:type item_chosen: (str)
"""
# print('IN MENU ITEM CALLBACK', item_chosen)
self.MenuItemChosen = item_chosen
self.ParentForm.LastButtonClicked = self.Key
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def update(
self,
menu_definition=None,
visible=None,
image_source=None,
image_size=(None, None),
image_subsample=None,
image_zoom=None,
button_text=None,
button_color=None,
):
"""
Changes some of the settings for the ButtonMenu Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param menu_definition: (New menu definition (in menu definition format)
:type menu_definition: List[List]
:param visible: control visibility of element
:type visible: (bool)
:param image_source: new image if image is to be changed. Can be a filename or a base64 encoded byte-string
:type image_source: (str | bytes)
:param image_size: Size of the image in pixels (width, height)
:type image_size: (int, int)
:param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type image_subsample: (int)
:param image_zoom: amount to increase the size of the image. 2=twice size, 3=3 times, etc
:type image_zoom: (int)
:param button_text: Text to be shown on the button
:type button_text: (str)
:param button_color: Normally a tuple, but can be a simplified-button-color-string "foreground on background". Can be a single color if want to set only the background.
:type button_color: (str, str) | str
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in ButtonMenu.update - The window was closed')
return
if menu_definition is not None:
self.MenuDefinition = copy.deepcopy(menu_definition)
top_menu = self.TKMenu = tk.Menu(self.TKButtonMenu, tearoff=self.Tearoff, font=self.ItemFont, tearoffcommand=self._tearoff_menu_callback)
if self.BackgroundColor not in (COLOR_SYSTEM_DEFAULT, None):
top_menu.config(bg=self.BackgroundColor)
if self.TextColor not in (COLOR_SYSTEM_DEFAULT, None):
top_menu.config(fg=self.TextColor)
if self.DisabledTextColor not in (COLOR_SYSTEM_DEFAULT, None):
top_menu.config(disabledforeground=self.DisabledTextColor)
if self.ItemFont is not None:
top_menu.config(font=self.ItemFont)
AddMenuItem(self.TKMenu, self.MenuDefinition[1], self)
self.TKButtonMenu.configure(menu=self.TKMenu)
if image_source is not None:
filename = data = None
if image_source is not None:
if isinstance(image_source, bytes):
data = image_source
elif isinstance(image_source, str):
filename = image_source
else:
warnings.warn(
f'ButtonMenu element - image_source is not a valid type: {type(image_source)}',
UserWarning,
)
image = None
if filename is not None:
image = tk.PhotoImage(file=filename)
if image_subsample is not None:
image = image.subsample(image_subsample)
if image_zoom is not None:
image = image.zoom(int(image_zoom))
elif data is not None:
# if type(data) is bytes:
try:
image = tk.PhotoImage(data=data)
if image_subsample is not None:
image = image.subsample(image_subsample)
if image_zoom is not None:
image = image.zoom(int(image_zoom))
except Exception:
image = data
if image is not None:
if type(image) is not bytes:
width, height = (
image_size[0] if image_size[0] is not None else image.width(),
image_size[1] if image_size[1] is not None else image.height(),
)
else:
width, height = image_size
self.TKButtonMenu.config(image=image, compound=tk.CENTER, width=width, height=height)
self.TKButtonMenu.image = image
if button_text is not None:
self.TKButtonMenu.configure(text=button_text)
self.ButtonText = button_text
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
if button_color != (None, None) and button_color != COLOR_SYSTEM_DEFAULT:
bc = button_color_to_tuple(button_color, self.ButtonColor)
if bc[0] not in (None, COLOR_SYSTEM_DEFAULT):
self.TKButtonMenu.config(foreground=bc[0], activeforeground=bc[0])
if bc[1] not in (None, COLOR_SYSTEM_DEFAULT):
self.TKButtonMenu.config(background=bc[1], activebackground=bc[1])
self.ButtonColor = bc
def click(self):
"""
Generates a click of the button as if the user clicked the button
Calls the tkinter invoke method for the button
"""
try:
self.TKMenu.invoke(1)
except:
print('Exception clicking button')
Update = update
Click = click
from FreeSimpleGUI._utils import _error_popup_with_traceback, _exit_mainloop
from FreeSimpleGUI.window import Window

View File

@ -0,0 +1,222 @@
from __future__ import annotations
import calendar
import tkinter as tk
import tkinter.font
from tkinter import ttk
class TKCalendar(ttk.Frame):
"""
This code was shamelessly lifted from moshekaplan's repository - moshekaplan/tkinter_components
NONE of this code is user callable. Stay away!
"""
# 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.format = kw.pop('format')
if self.format is None:
self.format = '%Y-%m-%d %H:%M:%S'
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)
def arrow_layout(dir):
return [('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 item not 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
now = self.datetime.now()
try:
self._TargetElement.Update(self.datetime(year, month, int(self._selection[0]), now.hour, now.minute, now.second).strftime(self.format))
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):
if not self._selection:
return None
year, month = self._date.year, self._date.month
return self.datetime(year, month, int(self._selection[0]))

View File

@ -0,0 +1,122 @@
from __future__ import annotations
import FreeSimpleGUI
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_CANVAS
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Canvas(Element):
def __init__(
self,
canvas=None,
background_color=None,
size=(None, None),
s=(None, None),
pad=None,
p=None,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
border_width=0,
metadata=None,
):
"""
:param canvas: Your own tk.Canvas if you already created it. Leave blank to create a Canvas
:type canvas: (tk.Canvas)
:param background_color: color of background
:type background_color: (str)
:param size: (width in char, height in rows) size in pixels to make canvas
:type size: (int,int) | (None, None)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param border_width: width of border around element in pixels. Not normally used with Canvas element
:type border_width: (int)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.BackgroundColor = background_color if background_color is not None else FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self._TKCanvas = self.Widget = canvas
self.RightClickMenu = right_click_menu
self.BorderWidth = border_width
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_CANVAS,
background_color=background_color,
size=sz,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def update(self, background_color=None, visible=None):
"""
:param background_color: color of background
:type background_color: (str)
:param visible: set visibility state of the element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Canvas.update - The window was closed')
return
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self._TKCanvas.configure(background=background_color)
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
@property
def tk_canvas(self):
"""
Returns the underlying tkiner Canvas widget
:return: The tkinter canvas widget
:rtype: (tk.Canvas)
"""
if self._TKCanvas is None:
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
print('*** window = sg.Window("My Form", layout, finalize=True) ***')
return self._TKCanvas
TKCanvas = tk_canvas

View File

@ -0,0 +1,240 @@
from __future__ import annotations
import tkinter as tk # noqa
from FreeSimpleGUI import _hex_to_hsl
from FreeSimpleGUI import _hsl_to_rgb
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_INPUT_CHECKBOX
from FreeSimpleGUI import Element
from FreeSimpleGUI import rgb
from FreeSimpleGUI import theme_background_color
from FreeSimpleGUI import theme_text_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Checkbox(Element):
"""
Checkbox Element - Displays a checkbox and text next to it
"""
def __init__(
self,
text,
default=False,
size=(None, None),
s=(None, None),
auto_size_text=None,
font=None,
background_color=None,
text_color=None,
checkbox_color=None,
highlight_thickness=1,
change_submits=False,
enable_events=False,
disabled=False,
key=None,
k=None,
pad=None,
p=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param text: Text to display next to checkbox
:type text: (str)
:param default: Set to True if you want this checkbox initially checked
:type default: (bool)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: if True will size the element to match the length of the text
:type auto_size_text: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param checkbox_color: color of background of the box that has the check mark in it. The checkmark is the same color as the text
:type checkbox_color: (str)
:param highlight_thickness: thickness of border around checkbox when gets focus
:type highlight_thickness: (int)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Checkbox events happen when an item changes
:type enable_events: (bool)
:param disabled: set disable state
:type disabled: (bool)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.Text = text
self.InitialState = bool(default)
self.Value = None
self.TKCheckbutton = self.Widget = None # type: tk.Checkbutton
self.Disabled = disabled
self.TextColor = text_color if text_color else theme_text_color()
self.RightClickMenu = right_click_menu
self.highlight_thickness = highlight_thickness
# ---- compute color of circle background ---
if checkbox_color is None:
try: # something in here will fail if a color is not specified in Hex
text_hsl = _hex_to_hsl(self.TextColor)
background_hsl = _hex_to_hsl(background_color if background_color else theme_background_color())
l_delta = abs(text_hsl[2] - background_hsl[2]) / 10
if text_hsl[2] > background_hsl[2]: # if the text is "lighter" than the background then make background darker
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] - l_delta)
else:
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] + l_delta)
self.CheckboxBackgroundColor = rgb(*bg_rbg)
except:
self.CheckboxBackgroundColor = background_color if background_color else theme_background_color()
else:
self.CheckboxBackgroundColor = checkbox_color
self.ChangeSubmits = change_submits or enable_events
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_INPUT_CHECKBOX,
size=sz,
auto_size_text=auto_size_text,
font=font,
background_color=background_color,
text_color=self.TextColor,
key=key,
pad=pad,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
def get(self):
# type: (Checkbox) -> bool
"""
Return the current state of this checkbox
:return: Current state of checkbox
:rtype: (bool)
"""
return self.TKIntVar.get() != 0
def update(
self,
value=None,
text=None,
background_color=None,
text_color=None,
checkbox_color=None,
disabled=None,
visible=None,
):
"""
Changes some of the settings for the Checkbox Element. Must call `Window.Read` or `Window.Finalize` prior.
Note that changing visibility may cause element to change locations when made visible after invisible
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: if True checks the checkbox, False clears it
:type value: (bool)
:param text: Text to display next to checkbox
:type text: (str)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text. Note this also changes the color of the checkmark
:type text_color: (str)
:param disabled: disable or enable element
:type disabled: (bool)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Checkbox.update - The window was closed')
return
if value is not None:
value = bool(value)
try:
self.TKIntVar.set(value)
self.InitialState = value
except:
print('Checkbox update failed')
if disabled is True:
self.TKCheckbutton.configure(state='disabled')
elif disabled is False:
self.TKCheckbutton.configure(state='normal')
self.Disabled = disabled if disabled is not None else self.Disabled
if text is not None:
self.Text = str(text)
self.TKCheckbutton.configure(text=self.Text)
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKCheckbutton.configure(background=background_color)
self.BackgroundColor = background_color
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKCheckbutton.configure(fg=text_color)
self.TextColor = text_color
# Color the checkbox itself
if checkbox_color not in (None, COLOR_SYSTEM_DEFAULT):
self.CheckboxBackgroundColor = checkbox_color
self.TKCheckbutton.configure(selectcolor=self.CheckboxBackgroundColor) # The background of the checkbox
elif text_color or background_color:
if self.CheckboxBackgroundColor is not None and self.TextColor is not None and self.BackgroundColor is not None and self.TextColor.startswith('#') and self.BackgroundColor.startswith('#'):
# ---- compute color of checkbox background ---
text_hsl = _hex_to_hsl(self.TextColor)
background_hsl = _hex_to_hsl(self.BackgroundColor if self.BackgroundColor else theme_background_color())
l_delta = abs(text_hsl[2] - background_hsl[2]) / 10
if text_hsl[2] > background_hsl[2]: # if the text is "lighter" than the background then make background darker
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] - l_delta)
else:
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] + l_delta)
self.CheckboxBackgroundColor = rgb(*bg_rbg)
self.TKCheckbutton.configure(selectcolor=self.CheckboxBackgroundColor) # The background of the checkbox
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
Get = get
Update = update

View File

@ -0,0 +1,442 @@
from __future__ import annotations
import tkinter as tk
import warnings
import FreeSimpleGUI
from FreeSimpleGUI import _make_ttk_scrollbar
from FreeSimpleGUI import _random_error_emoji
from FreeSimpleGUI import ELEM_TYPE_COLUMN
from FreeSimpleGUI import popup_error
from FreeSimpleGUI import VarHolder
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.elements.base import Element
class TkFixedFrame(tk.Frame):
"""
A tkinter frame that is used with Column Elements that do not have a scrollbar
"""
def __init__(self, master, **kwargs):
"""
:param master: The parent widget
:type master: (tk.Widget)
:param **kwargs: The keyword args
:type **kwargs:
"""
tk.Frame.__init__(self, master, **kwargs)
self.canvas = tk.Canvas(self)
self.canvas.pack(side='left', fill='both', expand=True)
# 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)
class TkScrollableFrame(tk.Frame):
"""
A frame with one or two scrollbars. Used to make Columns with scrollbars
"""
def __init__(self, master, vertical_only, element, window, **kwargs):
"""
:param master: The parent widget
:type master: (tk.Widget)
:param vertical_only: if True the only a vertical scrollbar will be shown
:type vertical_only: (bool)
:param element: The element containing this object
:type element: (Column)
"""
tk.Frame.__init__(self, master, **kwargs)
# create a canvas object and a vertical scrollbar for scrolling it
self.canvas = tk.Canvas(self)
element.Widget = self.canvas
# Okay, we're gonna make a list. Containing the y-min, x-min, y-max, and x-max of the frame
element.element_frame = self
_make_ttk_scrollbar(element, 'v', window)
# element.vsb = tk.Scrollbar(self, orient=tk.VERTICAL)
element.vsb.pack(side='right', fill='y', expand='false')
if not vertical_only:
_make_ttk_scrollbar(element, 'h', window)
# self.hscrollbar = tk.Scrollbar(self, orient=tk.HORIZONTAL)
element.hsb.pack(side='bottom', fill='x', expand='false')
self.canvas.config(xscrollcommand=element.hsb.set)
self.canvas.config(yscrollcommand=element.vsb.set)
self.canvas.pack(side='left', fill='both', expand=True)
element.vsb.config(command=self.canvas.yview)
if not vertical_only:
element.hsb.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)
# Canvas can be: master, canvas, TKFrame
self.unhookMouseWheel(None)
self.canvas.bind('<Enter>', self.hookMouseWheel)
self.canvas.bind('<Leave>', self.unhookMouseWheel)
self.bind('<Configure>', self.set_scrollregion)
def hookMouseWheel(self, e):
# print("enter")
VarHolder.canvas_holder = self.canvas
self.canvas.bind_all('<4>', self.yscroll, add='+')
self.canvas.bind_all('<5>', self.yscroll, add='+')
self.canvas.bind_all('<MouseWheel>', self.yscroll, add='+')
self.canvas.bind_all('<Shift-MouseWheel>', self.xscroll, add='+')
# Chr0nic
def unhookMouseWheel(self, e):
# print("leave")
VarHolder.canvas_holder = None
self.canvas.unbind_all('<4>')
self.canvas.unbind_all('<5>')
self.canvas.unbind_all('<MouseWheel>')
self.canvas.unbind_all('<Shift-MouseWheel>')
def resize_frame(self, e):
self.canvas.itemconfig(self.frame_id, height=e.height, width=e.width)
def yscroll(self, event):
if self.canvas.yview() == (0.0, 1.0):
return
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'))
class Column(Element):
"""
A container element that is used to create a layout within your window's layout
"""
def __init__(
self,
layout,
background_color=None,
size=(None, None),
s=(None, None),
size_subsample_width=1,
size_subsample_height=2,
pad=None,
p=None,
scrollable=False,
vertical_scroll_only=False,
right_click_menu=None,
key=None,
k=None,
visible=True,
justification=None,
element_justification=None,
vertical_alignment=None,
grab=None,
expand_x=None,
expand_y=None,
metadata=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
):
"""
:param layout: Layout that will be shown in the Column container
:type layout: List[List[Element]]
:param background_color: color of background of entire Column
:type background_color: (str)
:param size: (width, height) size in pixels (doesn't work quite right, sometimes only 1 dimension is set by tkinter. Use a Sizer Element to help set sizes
:type size: (int | None, int | None)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int | None, int | None)
:param size_subsample_width: Determines the size of a scrollable column width based on 1/size_subsample * required size. 1 = match the contents exactly, 2 = 1/2 contents size, 3 = 1/3. Can be a fraction to make larger than required.
:type size_subsample_width: (float)
:param size_subsample_height: Determines the size of a scrollable height based on 1/size_subsample * required size. 1 = match the contents exactly, 2 = 1/2 contents size, 3 = 1/3. Can be a fraction to make larger than required..
:type size_subsample_height: (float)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param scrollable: if True then scrollbars will be added to the column. If you update the contents of a scrollable column, be sure and call Column.contents_changed also
:type scrollable: (bool)
:param vertical_scroll_only: if True then no horizontal scrollbar will be shown if a scrollable column
:type vertical_scroll_only: (bool)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param visible: set visibility state of the element
:type visible: (bool)
:param justification: set justification for the Column itself. Note entire row containing the Column will be affected
:type justification: (str)
:param element_justification: All elements inside the Column will have this justification 'left', 'right', 'center' are valid values
:type element_justification: (str)
:param vertical_alignment: Place the column at the 'top', 'center', 'bottom' of the row (can also use t,c,r). Defaults to no setting (tkinter decides)
:type vertical_alignment: (str)
:param grab: If True can grab this element and move the window around. Default is False
:type grab: (bool)
:param expand_x: If True the column will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the column will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
"""
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.ParentPanedWindow = None
self.Rows = []
self.TKFrame = None
self.TKColFrame = None # type: tk.Frame
self.Scrollable = scrollable
self.VerticalScrollOnly = vertical_scroll_only
self.RightClickMenu = right_click_menu
bg = background_color if background_color is not None else FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.ContainerElemementNumber = Window._GetAContainerNumber()
self.ElementJustification = element_justification
self.Justification = justification
self.VerticalAlignment = vertical_alignment
key = key if key is not None else k
self.Grab = grab
self.expand_x = expand_x
self.expand_y = expand_y
self.Layout(layout)
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.size_subsample_width = size_subsample_width
self.size_subsample_height = size_subsample_height
super().__init__(
ELEM_TYPE_COLUMN,
background_color=bg,
size=sz,
pad=pad,
key=key,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)
return
def add_row(self, *args):
"""
Not recommended user call. Used to add rows of Elements to the Column Element.
:param *args: The list of elements for this row
:type *args: List[Element]
"""
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
if type(element) is list:
popup_error(
'Error creating Column layout',
'Layout has a LIST instead of an ELEMENT',
'This sometimes means you have a badly placed ]',
'The offensive list is:',
element,
'This list will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
elif callable(element) and not isinstance(element, Element):
popup_error(
'Error creating Column layout',
'Layout has a FUNCTION instead of an ELEMENT',
'This likely means you are missing () from your layout',
'The offensive list is:',
element,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
if element.ParentContainer is not None:
warnings.warn(
'*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***',
UserWarning,
)
popup_error(
'Error creating Column layout',
'The layout specified has already been used',
'You MUST start witha "clean", unused layout every time you create a window',
'The offensive Element = ',
element,
'and has a key = ',
element.Key,
'This item will be stripped from your layout',
'Hint - try printing your layout and matching the IDs "print(layout)"',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
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):
"""
Can use like the Window.Layout method, but it's better to use the layout parameter when creating
:param rows: The rows of Elements
:type rows: List[List[Element]]
:return: Used for chaining
:rtype: (Column)
"""
for row in rows:
try:
iter(row)
except TypeError:
popup_error(
'Error creating Column layout',
'Your row is not an iterable (e.g. a list)',
f'Instead of a list, the type found was {type(row)}',
'The offensive row = ',
row,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
self.AddRow(*row)
return self
def _GetElementAtLocation(self, location):
"""
Not user callable. Used to find the Element at a row, col position within the layout
:param location: (row, column) position of the element to find in layout
:type location: (int, int)
:return: The element found at the location
:rtype: (Element)
"""
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def update(self, visible=None):
"""
Changes some of the settings for the Column Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Column.update - The window was closed')
return
if visible is False:
if self.TKColFrame:
self._pack_forget_save_settings()
if self.ParentPanedWindow:
self.ParentPanedWindow.remove(self.TKColFrame)
elif visible is True:
if self.TKColFrame:
self._pack_restore_settings()
if self.ParentPanedWindow:
self.ParentPanedWindow.add(self.TKColFrame)
if visible is not None:
self._visible = visible
def contents_changed(self):
"""
When a scrollable column has part of its layout changed by making elements visible or invisible or the
layout is extended for the Column, then this method needs to be called so that the new scroll area
is computed to match the new contents.
"""
self.TKColFrame.canvas.config(scrollregion=self.TKColFrame.canvas.bbox('all'))
AddRow = add_row
Layout = layout
Update = update
from FreeSimpleGUI.window import Window

View File

@ -0,0 +1,329 @@
from __future__ import annotations
import tkinter as tk
import tkinter.font
from tkinter import ttk
import FreeSimpleGUI
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_INPUT_COMBO
from FreeSimpleGUI import Element
from FreeSimpleGUI import theme_button_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Combo(Element):
"""
ComboBox Element - A combination of a single-line input and a drop-down menu. User can type in their own value or choose from list.
"""
def __init__(
self,
values,
default_value=None,
size=(None, None),
s=(None, None),
auto_size_text=None,
background_color=None,
text_color=None,
button_background_color=None,
button_arrow_color=None,
bind_return_key=False,
change_submits=False,
enable_events=False,
enable_per_char_events=None,
disabled=False,
key=None,
k=None,
pad=None,
p=None,
expand_x=False,
expand_y=False,
tooltip=None,
readonly=False,
font=None,
visible=True,
metadata=None,
):
"""
:param values: values to choose. While displayed as text, the items returned are what the caller supplied, not text
:type values: List[Any] or Tuple[Any]
:param default_value: Choice to be displayed as initial value. Must match one of values variable contents
:type default_value: (Any)
:param size: width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list. If an Int is passed rather than a tuple, then height is auto-set to 1 and width is value of the int
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: True if element should be the same size as the contents
:type auto_size_text: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param button_background_color: The color of the background of the button on the combo box
:type button_background_color: (str)
:param button_arrow_color: The color of the arrow on the button on the combo box
:type button_arrow_color: (str)
:param bind_return_key: If True, then the return key will cause a the Combo to generate an event when return key is pressed
:type bind_return_key: (bool)
:param change_submits: DEPRICATED DO NOT USE. Use `enable_events` instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Combo event is when a choice is made
:type enable_events: (bool)
:param enable_per_char_events: Enables generation of events for every character that's input. This is like the Input element's events
:type enable_per_char_events: (bool)
:param disabled: set disable state for element
:type disabled: (bool)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param tooltip: text that will appear when mouse hovers over this element
:type tooltip: (str)
:param readonly: make element readonly (user can't change). True means user cannot change
:type readonly: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.Values = values
self.DefaultValue = default_value
self.ChangeSubmits = change_submits or enable_events
self.Widget = self.TKCombo = None # type: ttk.Combobox
self.Disabled = disabled
self.Readonly = readonly
self.BindReturnKey = bind_return_key
bg = background_color if background_color else FreeSimpleGUI.DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else FreeSimpleGUI.DEFAULT_INPUT_TEXT_COLOR
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
if button_background_color is None:
self.button_background_color = theme_button_color()[1]
else:
self.button_background_color = button_background_color
if button_arrow_color is None:
self.button_arrow_color = theme_button_color()[0]
else:
self.button_arrow_color = button_arrow_color
self.enable_per_char_events = enable_per_char_events
super().__init__(
ELEM_TYPE_INPUT_COMBO,
size=sz,
auto_size_text=auto_size_text,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
tooltip=tooltip,
font=font or FreeSimpleGUI.DEFAULT_FONT,
visible=visible,
metadata=metadata,
)
def update(
self,
value=None,
values=None,
set_to_index=None,
disabled=None,
readonly=None,
font=None,
visible=None,
size=(None, None),
select=None,
text_color=None,
background_color=None,
):
"""
Changes some of the settings for the Combo Element. Must call `Window.Read` or `Window.Finalize` prior.
Note that the state can be in 3 states only.... enabled, disabled, readonly even
though more combinations are available. The easy way to remember is that if you
change the readonly parameter then you are enabling the element.
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: change which value is current selected based on new list of previous list of choices
:type value: (Any)
:param values: change list of choices
:type values: List[Any]
:param set_to_index: change selection to a particular choice starting with index = 0
:type set_to_index: (int)
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param readonly: if True make element readonly (user cannot change any choices). Enables the element if either choice are made.
:type readonly: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param visible: control visibility of element
:type visible: (bool)
:param size: width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list
:type size: (int, int)
:param select: if True, then the text will be selected, if False then selection will be cleared
:type select: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
"""
if size != (None, None):
if isinstance(size, int):
size = (size, 1)
if isinstance(size, tuple) and len(size) == 1:
size = (size[0], 1)
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Combo.update - The window was closed')
return
if values is not None:
try:
self.TKCombo['values'] = values
# self.TKCombo.current(0) # don't set any value if a new set of values was made
except:
pass
self.Values = values
if value is None:
self.TKCombo.set('')
if size == (None, None):
max_line_len = max([len(str(line)) for line in self.Values]) if len(self.Values) else 0
if self.AutoSizeText is False:
width = self.Size[0]
else:
width = max_line_len + 1
self.TKCombo.configure(width=width)
else:
self.TKCombo.configure(height=size[1])
self.TKCombo.configure(width=size[0])
if value is not None:
if value not in self.Values:
self.TKCombo.set(value)
else:
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 readonly:
self.Readonly = True
self.TKCombo['state'] = 'readonly'
elif readonly is False:
self.Readonly = False
self.TKCombo['state'] = 'enable'
if disabled is True:
self.TKCombo['state'] = 'disable'
elif disabled is False and self.Readonly is True:
self.TKCombo['state'] = 'readonly'
elif disabled is False and self.Readonly is False:
self.TKCombo['state'] = 'enable'
self.Disabled = disabled if disabled is not None else self.Disabled
combostyle = self.ttk_style
style_name = self.ttk_style_name
if text_color is not None:
combostyle.configure(style_name, foreground=text_color)
combostyle.configure(style_name, selectforeground=text_color)
combostyle.configure(style_name, insertcolor=text_color)
combostyle.map(style_name, fieldforeground=[('readonly', text_color)])
self.TextColor = text_color
if background_color is not None:
combostyle.configure(style_name, selectbackground=background_color)
combostyle.map(style_name, fieldbackground=[('readonly', background_color)])
combostyle.configure(style_name, fieldbackground=background_color)
self.BackgroundColor = background_color
if self.Readonly is True:
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
combostyle.configure(style_name, selectforeground=text_color)
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
combostyle.configure(style_name, selectbackground=background_color)
if font is not None:
self.Font = font
self.TKCombo.configure(font=font)
self._dropdown_newfont = tkinter.font.Font(font=font)
self.ParentRowFrame.option_add('*TCombobox*Listbox*Font', self._dropdown_newfont)
# make tcl call to deal with colors for the drop-down formatting
try:
if self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT) and self.TextColor not in (
None,
COLOR_SYSTEM_DEFAULT,
):
self.Widget.tk.eval(
'[ttk::combobox::PopdownWindow {}].f.l configure -foreground {} -background {} -selectforeground {} -selectbackground {} -font {}'.format(
self.Widget,
self.TextColor,
self.BackgroundColor,
self.BackgroundColor,
self.TextColor,
self._dropdown_newfont,
)
)
except Exception:
pass # going to let this one slide
if visible is False:
self._pack_forget_save_settings()
# self.TKCombo.pack_forget()
elif visible is True:
self._pack_restore_settings()
# self.TKCombo.pack(padx=self.pad_used[0], pady=self.pad_used[1])
if visible is not None:
self._visible = visible
if select is True:
self.TKCombo.select_range(0, tk.END)
elif select is False:
self.TKCombo.select_clear()
def get(self):
"""
Returns the current (right now) value of the Combo. DO NOT USE THIS AS THE NORMAL WAY OF READING A COMBO!
You should be using values from your call to window.read instead. Know what you're doing if you use it.
:return: Returns the value of what is currently chosen
:rtype: Any | None
"""
try:
if self.TKCombo.current() == -1: # if the current value was not in the original list
value = self.TKCombo.get() # then get the value typed in by user
else:
value = self.Values[self.TKCombo.current()] # get value from original list given index
except:
value = None # only would happen if user closes window
return value
Get = get
Update = update

View File

@ -0,0 +1,47 @@
from __future__ import annotations
from FreeSimpleGUI import ELEM_TYPE_ERROR
from FreeSimpleGUI.elements.base import Element
class ErrorElement(Element):
"""
A "dummy Element" that is returned when there are error conditions, like trying to find an element that's invalid
"""
def __init__(self, key=None, metadata=None):
"""
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key:
"""
self.Key = key
super().__init__(ELEM_TYPE_ERROR, key=key, metadata=metadata)
def update(self, silent_on_error=True, *args, **kwargs):
"""
Update method for the Error Element, an element that should not be directly used by developer
:param silent_on_error: if False, then a Popup window will be shown
:type silent_on_error: (bool)
:param *args: meant to "soak up" any normal parameters passed in
:type *args: (Any)
:param **kwargs: meant to "soak up" any keyword parameters that were passed in
:type **kwargs: (Any)
:return: returns 'self' so call can be chained
:rtype: (ErrorElement)
"""
print('** Your update is being ignored because you supplied a bad key earlier **')
return self
def get(self):
"""
One of the method names found in other Elements. Used here to return an error string in case it's called
:return: A warning text string.
:rtype: (str)
"""
return 'This is NOT a valid Element!\nSTOP trying to do things with it or I will have to crash at some point!'
Get = get
Update = update

View File

@ -0,0 +1,273 @@
from __future__ import annotations
import tkinter as tk
import warnings
import FreeSimpleGUI
from FreeSimpleGUI import _random_error_emoji
from FreeSimpleGUI import ELEM_TYPE_FRAME
from FreeSimpleGUI import Element
from FreeSimpleGUI import popup_error
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.window import Window
class Frame(Element):
"""
A Frame Element that contains other Elements. Encloses with a line around elements and a text label.
"""
def __init__(
self,
title,
layout,
title_color=None,
background_color=None,
title_location=None,
relief=FreeSimpleGUI.DEFAULT_FRAME_RELIEF,
size=(None, None),
s=(None, None),
font=None,
pad=None,
p=None,
border_width=None,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
grab=None,
visible=True,
element_justification='left',
vertical_alignment=None,
metadata=None,
):
"""
:param title: text that is displayed as the Frame's "label" or title
:type title: (str)
:param layout: The layout to put inside the Frame
:type layout: List[List[Elements]]
:param title_color: color of the title text
:type title_color: (str)
:param background_color: background color of the Frame
:type background_color: (str)
:param title_location: location to place the text title. Choices include: TITLE_LOCATION_TOP TITLE_LOCATION_BOTTOM TITLE_LOCATION_LEFT TITLE_LOCATION_RIGHT TITLE_LOCATION_TOP_LEFT TITLE_LOCATION_TOP_RIGHT TITLE_LOCATION_BOTTOM_LEFT TITLE_LOCATION_BOTTOM_RIGHT
:type title_location: (enum)
:param relief: relief style. Values are same as other elements with reliefs. Choices include RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
:type relief: (enum)
:param size: (width, height) Sets an initial hard-coded size for the Frame. This used to be a problem, but was fixed in 4.53.0 and works better than Columns when using the size paramter
:type size: (int, int)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param font: specifies the font family, size, etc. for the TITLE. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param border_width: width of border around element in pixels
:type border_width: (int)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param grab: If True can grab this element and move the window around. Default is False
:type grab: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param element_justification: All elements inside the Frame will have this justification 'left', 'right', 'center' are valid values
:type element_justification: (str)
:param vertical_alignment: Place the Frame at the 'top', 'center', 'bottom' of the row (can also use t,c,r). Defaults to no setting (tkinter decides)
:type vertical_alignment: (str)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
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 FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.RightClickMenu = right_click_menu
self.ContainerElemementNumber = Window._GetAContainerNumber()
self.ElementJustification = element_justification
self.VerticalAlignment = vertical_alignment
self.Widget = None # type: tk.LabelFrame
self.Grab = grab
self.Layout(layout)
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_FRAME,
background_color=background_color,
text_color=title_color,
size=sz,
font=font,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def add_row(self, *args):
"""
Not recommended user call. Used to add rows of Elements to the Frame Element.
:param *args: The list of elements for this row
:type *args: List[Element]
"""
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
if type(element) is list:
popup_error(
'Error creating Frame layout',
'Layout has a LIST instead of an ELEMENT',
'This sometimes means you have a badly placed ]',
'The offensive list is:',
element,
'This list will be stripped from your layout',
keep_on_top=True,
)
continue
elif callable(element) and not isinstance(element, Element):
popup_error(
'Error creating Frame layout',
'Layout has a FUNCTION instead of an ELEMENT',
'This likely means you are missing () from your layout',
'The offensive list is:',
element,
'This item will be stripped from your layout',
keep_on_top=True,
)
continue
if element.ParentContainer is not None:
warnings.warn(
'*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***',
UserWarning,
)
_error_popup_with_traceback(
'Error creating Frame layout',
'The layout specified has already been used',
'You MUST start witha "clean", unused layout every time you create a window',
'The offensive Element = ',
element,
'and has a key = ',
element.Key,
'This item will be stripped from your layout',
'Hint - try printing your layout and matching the IDs "print(layout)"',
)
continue
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):
"""
Can use like the Window.Layout method, but it's better to use the layout parameter when creating
:param rows: The rows of Elements
:type rows: List[List[Element]]
:return: Used for chaining
:rtype: (Frame)
"""
for row in rows:
try:
iter(row)
except TypeError:
popup_error(
'Error creating Frame layout',
'Your row is not an iterable (e.g. a list)',
f'Instead of a list, the type found was {type(row)}',
'The offensive row = ',
row,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
self.AddRow(*row)
return self
def _GetElementAtLocation(self, location):
"""
Not user callable. Used to find the Element at a row, col position within the layout
:param location: (row, column) position of the element to find in layout
:type location: (int, int)
:return: (Element) The element found at the location
:rtype: (Element)
"""
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def update(self, value=None, visible=None):
"""
Changes some of the settings for the Frame Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: New text value to show on frame
:type value: (Any)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Frame.update - The window was closed')
return
if visible is False:
self._pack_forget_save_settings()
# self.TKFrame.pack_forget()
elif visible is True:
self._pack_restore_settings()
# self.TKFrame.pack(padx=self.pad_used[0], pady=self.pad_used[1])
if value is not None:
self.TKFrame.config(text=str(value))
if visible is not None:
self._visible = visible
AddRow = add_row
Layout = layout
Update = update

View File

@ -0,0 +1,817 @@
from __future__ import annotations
import tkinter as tk
from math import floor
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_GRAPH
from FreeSimpleGUI import Element
from FreeSimpleGUI import TEXT_LOCATION_CENTER
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
class Graph(Element):
"""
Creates an area for you to draw on. The MAGICAL property this Element has is that you interact
with the element using your own coordinate system. This is an important point!! YOU define where the location
is for (0,0). Want (0,0) to be in the middle of the graph like a math 4-quadrant graph? No problem! Set your
lower left corner to be (-100,-100) and your upper right to be (100,100) and you've got yourself a graph with
(0,0) at the center.
One of THE coolest of the Elements.
You can also use float values. To do so, be sure and set the float_values parameter.
Mouse click and drag events are possible and return the (x,y) coordinates of the mouse
Drawing primitives return an "id" that is referenced when you want to operation on that item (e.g. to erase it)
"""
def __init__(
self,
canvas_size,
graph_bottom_left,
graph_top_right,
background_color=None,
pad=None,
p=None,
change_submits=False,
drag_submits=False,
enable_events=False,
motion_events=False,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
float_values=False,
border_width=0,
metadata=None,
):
"""
:param canvas_size: size of the canvas area in pixels
:type canvas_size: (int, int)
:param graph_bottom_left: (x,y) The bottoms left corner of your coordinate system
:type graph_bottom_left: (int, int)
:param graph_top_right: (x,y) The top right corner of your coordinate system
:type graph_top_right: (int, int)
:param background_color: background color of the drawing area
:type background_color: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead
:type change_submits: (bool)
:param drag_submits: if True and Events are enabled for the Graph, will report Events any time the mouse moves while button down. When the mouse button is released, you'll get an event = graph key + '+UP' (if key is a string.. if not a string, it'll be made into a tuple)
:type drag_submits: (bool)
:param enable_events: If True then clicks on the Graph are immediately reported as an event. Use this instead of change_submits
:type enable_events: (bool)
:param motion_events: If True then if no button is down and the mouse is moved, an event is generated with key = graph key + '+MOVE' (if key is a string, it not a string then a tuple is returned)
:type motion_events: (bool)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element (Default = True)
:type visible: (bool)
:param float_values: If True x,y coordinates are returned as floats, not ints
:type float_values: (bool)
:param border_width: width of border around element in pixels. Not normally used for Graph Elements
:type border_width: (int)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.CanvasSize = canvas_size
self.BottomLeft = graph_bottom_left
self.TopRight = graph_top_right
# self._TKCanvas = None # type: tk.Canvas
self._TKCanvas2 = self.Widget = None # type: tk.Canvas
self.ChangeSubmits = change_submits or enable_events
self.DragSubmits = drag_submits
self.ClickPosition = (None, None)
self.MouseButtonDown = False
self.Images = {}
self.RightClickMenu = right_click_menu
self.FloatValues = float_values
self.BorderWidth = border_width
key = key if key is not None else k
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.motion_events = motion_events
super().__init__(
ELEM_TYPE_GRAPH,
background_color=background_color,
size=canvas_size,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def _convert_xy_to_canvas_xy(self, x_in, y_in):
"""
Not user callable. Used to convert user's coordinates into the ones used by tkinter
:param x_in: The x coordinate to convert
:type x_in: int | float
:param y_in: The y coordinate to convert
:type y_in: int | float
:return: (int, int) The converted canvas coordinates
:rtype: (int, int)
"""
if None in (x_in, y_in):
return None, None
try:
scale_x = (self.CanvasSize[0] - 0) / (self.TopRight[0] - self.BottomLeft[0])
scale_y = (0 - self.CanvasSize[1]) / (self.TopRight[1] - self.BottomLeft[1])
except:
scale_x = scale_y = 0
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):
"""
Not user callable. Used to convert tkinter Canvas coords into user's coordinates
:param x_in: The x coordinate in canvas coordinates
:type x_in: (int)
:param y_in: (int) The y coordinate in canvas coordinates
:type y_in:
:return: The converted USER coordinates
:rtype: (int, int) | Tuple[float, float]
"""
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]
if self.FloatValues:
return new_x, new_y
else:
return floor(new_x), floor(new_y)
def draw_line(self, point_from, point_to, color='black', width=1):
"""
Draws a line from one point to another point using USER'S coordinates. Can set the color and width of line
:param point_from: Starting point for line
:type point_from: (int, int) | Tuple[float, float]
:param point_to: Ending point for line
:type point_to: (int, int) | Tuple[float, float]
:param color: Color of the line
:type color: (str)
:param width: width of line in pixels
:type width: (int)
:return: id returned from tktiner or None if user closed the window. id is used when you
:rtype: int | None
"""
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
try: # in case window was closed with an X
id = self._TKCanvas2.create_line(converted_point_from, converted_point_to, width=width, fill=color)
except:
id = None
return id
def draw_lines(self, points, color='black', width=1):
"""
Draw a series of lines given list of points
:param points: list of points that define the polygon
:type points: List[(int, int) | Tuple[float, float]]
:param color: Color of the line
:type color: (str)
:param width: width of line in pixels
:type width: (int)
:return: id returned from tktiner or None if user closed the window. id is used when you
:rtype: int | None
"""
converted_points = [self._convert_xy_to_canvas_xy(point[0], point[1]) for point in points]
try: # in case window was closed with an X
id = self._TKCanvas2.create_line(*converted_points, width=width, fill=color)
except:
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')
id = None
return id
def draw_point(self, point, size=2, color='black'):
"""
Draws a "dot" at the point you specify using the USER'S coordinate system
:param point: Center location using USER'S coordinate system
:type point: (int, int) | Tuple[float, float]
:param size: Radius? (Or is it the diameter?) in user's coordinate values.
:type size: int | float
:param color: color of the point to draw
:type color: (str)
:return: id returned from tkinter that you'll need if you want to manipulate the point
:rtype: int | None
"""
if point == (None, None):
return
converted_point = self._convert_xy_to_canvas_xy(point[0], point[1])
size_converted = self._convert_xy_to_canvas_xy(point[0] + size, point[1])
size = size_converted[0] - converted_point[0]
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
try: # needed in case window was closed with an X
point1 = converted_point[0] - size // 2, converted_point[1] - size // 2
point2 = converted_point[0] + size // 2, converted_point[1] + size // 2
id = self._TKCanvas2.create_oval(point1[0], point1[1], point2[0], point2[1], width=0, fill=color, outline=color)
except:
id = None
return id
def draw_circle(self, center_location, radius, fill_color=None, line_color='black', line_width=1):
"""
Draws a circle, cenetered at the location provided. Can set the fill and outline colors
:param center_location: Center location using USER'S coordinate system
:type center_location: (int, int) | Tuple[float, float]
:param radius: Radius in user's coordinate values.
:type radius: int | float
:param fill_color: color of the point to draw
:type fill_color: (str)
:param line_color: color of the outer line that goes around the circle (sorry, can't set thickness)
:type line_color: (str)
:param line_width: width of the line around the circle, the outline, in pixels
:type line_width: (int)
:return: id returned from tkinter that you'll need if you want to manipulate the circle
:rtype: int | None
"""
if center_location == (None, None):
return
converted_point = self._convert_xy_to_canvas_xy(center_location[0], center_location[1])
radius_converted = self._convert_xy_to_canvas_xy(center_location[0] + radius, center_location[1])
radius = radius_converted[0] - converted_point[0]
# radius = radius_converted[1]-5
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
try: # needed in case the window was closed with an X
id = self._TKCanvas2.create_oval(
int(converted_point[0]) - int(radius),
int(converted_point[1]) - int(radius),
int(converted_point[0]) + int(radius),
int(converted_point[1]) + int(radius),
fill=fill_color,
outline=line_color,
width=line_width,
)
except:
id = None
return id
def draw_oval(self, top_left, bottom_right, fill_color=None, line_color=None, line_width=1):
"""
Draws an oval based on coordinates in user coordinate system. Provide the location of a "bounding rectangle"
:param top_left: the top left point of bounding rectangle
:type top_left: (int, int) | Tuple[float, float]
:param bottom_right: the bottom right point of bounding rectangle
:type bottom_right: (int, int) | Tuple[float, float]
:param fill_color: color of the interrior
:type fill_color: (str)
:param line_color: color of outline of oval
:type line_color: (str)
:param line_width: width of the line around the oval, the outline, in pixels
:type line_width: (int)
:return: id returned from tkinter that you'll need if you want to manipulate the oval
:rtype: int | 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
try: # in case windows close with X
id = 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,
width=line_width,
)
except:
id = None
return id
def draw_arc(self, top_left, bottom_right, extent, start_angle, style=None, arc_color='black', line_width=1, fill_color=None):
"""
Draws different types of arcs. Uses a "bounding box" to define location
:param top_left: the top left point of bounding rectangle
:type top_left: (int, int) | Tuple[float, float]
:param bottom_right: the bottom right point of bounding rectangle
:type bottom_right: (int, int) | Tuple[float, float]
:param extent: Andle to end drawing. Used in conjunction with start_angle
:type extent: (float)
:param start_angle: Angle to begin drawing. Used in conjunction with extent
:type start_angle: (float)
:param style: Valid choices are One of these Style strings- 'pieslice', 'chord', 'arc', 'first', 'last', 'butt', 'projecting', 'round', 'bevel', 'miter'
:type style: (str)
:param arc_color: color to draw arc with
:type arc_color: (str)
:param fill_color: color to fill the area
:type fill_color: (str)
:return: id returned from tkinter that you'll need if you want to manipulate the arc
:rtype: int | 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])
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
try: # in case closed with X
id = 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,
width=line_width,
fill=fill_color,
)
except Exception as e:
print('Error encountered drawing arc.', e)
id = None
return id
def draw_rectangle(self, top_left, bottom_right, fill_color=None, line_color=None, line_width=None):
"""
Draw a rectangle given 2 points. Can control the line and fill colors
:param top_left: the top left point of rectangle
:type top_left: (int, int) | Tuple[float, float]
:param bottom_right: the bottom right point of rectangle
:type bottom_right: (int, int) | Tuple[float, float]
:param fill_color: color of the interior
:type fill_color: (str)
:param line_color: color of outline
:type line_color: (str)
:param line_width: width of the line in pixels
:type line_width: (int)
:return: int | None id returned from tkinter that you'll need if you want to manipulate the rectangle
:rtype: int | 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
if line_width is None:
line_width = 1
try: # in case closed with X
id = 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,
width=line_width,
)
except:
id = None
return id
def draw_polygon(self, points, fill_color=None, line_color=None, line_width=None):
"""
Draw a polygon given list of points
:param points: list of points that define the polygon
:type points: List[(int, int) | Tuple[float, float]]
:param fill_color: color of the interior
:type fill_color: (str)
:param line_color: color of outline
:type line_color: (str)
:param line_width: width of the line in pixels
:type line_width: (int)
:return: id returned from tkinter that you'll need if you want to manipulate the rectangle
:rtype: int | None
"""
converted_points = [self._convert_xy_to_canvas_xy(point[0], point[1]) for point in points]
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
try: # in case closed with X
id = self._TKCanvas2.create_polygon(converted_points, fill=fill_color, outline=line_color, width=line_width)
except:
id = None
return id
def draw_text(self, text, location, color='black', font=None, angle=0, text_location=TEXT_LOCATION_CENTER):
"""
Draw some text on your graph. This is how you label graph number lines for example
:param text: text to display
:type text: (Any)
:param location: location to place first letter
:type location: (int, int) | Tuple[float, float]
:param color: text color
:type color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param angle: Angle 0 to 360 to draw the text. Zero represents horizontal text
:type angle: (float)
:param text_location: "anchor" location for the text. Values start with TEXT_LOCATION_
:type text_location: (enum)
:return: id returned from tkinter that you'll need if you want to manipulate the text
:rtype: int | None
"""
text = str(text)
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
try: # in case closed with X
id = self._TKCanvas2.create_text(
converted_point[0],
converted_point[1],
text=text,
font=font,
fill=color,
angle=angle,
anchor=text_location,
)
except:
id = None
return id
def draw_image(self, filename=None, data=None, location=(None, None)):
"""
Places an image onto your canvas. It's a really important method for this element as it enables so much
:param filename: if image is in a file, path and filename for the image. (GIF and PNG only!)
:type filename: (str)
:param data: if image is in Base64 format or raw? format then use instead of filename
:type data: str | bytes
:param location: the (x,y) location to place image's top left corner
:type location: (int, int) | Tuple[float, float]
:return: id returned from tkinter that you'll need if you want to manipulate the image
:rtype: int | None
"""
if location == (None, None):
return
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 None # an error likely means the window has closed so exit
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
try: # in case closed with X
id = self._TKCanvas2.create_image(converted_point, image=image, anchor=tk.NW)
self.Images[id] = image
except:
id = None
return id
def erase(self):
"""
Erase the Graph - Removes all figures previously "drawn" using the Graph methods (e.g. DrawText)
"""
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.Images = {}
try: # in case window was closed with X
self._TKCanvas2.delete('all')
except:
pass
def delete_figure(self, id):
"""
Remove from the Graph the figure represented by id. The id is given to you anytime you call a drawing primitive
:param id: the id returned to you when calling one of the drawing methods
:type id: (int)
"""
try:
self._TKCanvas2.delete(id)
except:
print(f'DeleteFigure - bad ID {id}')
try:
del self.Images[id] # in case was an image. If wasn't an image, then will get exception
except:
pass
def update(self, background_color=None, visible=None):
"""
Changes some of the settings for the Graph Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param background_color: color of background
:type background_color: ???
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Graph.update - The window was closed')
return
if background_color is not None and background_color != COLOR_SYSTEM_DEFAULT:
self._TKCanvas2.configure(background=background_color)
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
def move(self, x_direction, y_direction):
"""
Moves the entire drawing area (the canvas) by some delta from the current position. Units are indicated in your coordinate system indicated number of ticks in your coordinate system
:param x_direction: how far to move in the "X" direction in your coordinates
:type x_direction: int | float
:param y_direction: how far to move in the "Y" direction in your coordinates
:type y_direction: int | float
"""
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 move_figure(self, figure, x_direction, y_direction):
"""
Moves a previously drawn figure using a "delta" from current position
:param figure: Previously obtained figure-id. These are returned from all Draw methods
:type figure: (id)
:param x_direction: delta to apply to position in the X direction
:type x_direction: int | float
:param y_direction: delta to apply to position in the Y direction
:type y_direction: int | float
"""
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('* move_figure warning - your figure is None *')
return None
self._TKCanvas2.move(figure, shift_amount[0], shift_amount[1])
def relocate_figure(self, figure, x, y):
"""
Move a previously made figure to an arbitrary (x,y) location. This differs from the Move methods because it
uses absolute coordinates versus relative for Move
:param figure: Previously obtained figure-id. These are returned from all Draw methods
:type figure: (id)
:param x: location on X axis (in user coords) to move the upper left corner of the figure
:type x: int | float
:param y: location on Y axis (in user coords) to move the upper left corner of the figure
:type y: int | float
"""
# zero_converted = self._convert_xy_to_canvas_xy(0, 0)
shift_converted = self._convert_xy_to_canvas_xy(x, y)
# 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
xy = self._TKCanvas2.coords(figure)
self._TKCanvas2.move(figure, shift_converted[0] - xy[0], shift_converted[1] - xy[1])
def send_figure_to_back(self, figure):
"""
Changes Z-order of figures on the Graph. Sends the indicated figure to the back of all other drawn figures
:param figure: value returned by tkinter when creating the figure / drawing
:type figure: (int)
"""
self.TKCanvas.tag_lower(figure) # move figure to the "bottom" of all other figure
def bring_figure_to_front(self, figure):
"""
Changes Z-order of figures on the Graph. Brings the indicated figure to the front of all other drawn figures
:param figure: value returned by tkinter when creating the figure / drawing
:type figure: (int)
"""
self.TKCanvas.tag_raise(figure) # move figure to the "top" of all other figures
def get_figures_at_location(self, location):
"""
Returns a list of figures located at a particular x,y location within the Graph
:param location: point to check
:type location: (int, int) | Tuple[float, float]
:return: a list of previously drawn "Figures" (returned from the drawing primitives)
:rtype: List[int]
"""
x, y = self._convert_xy_to_canvas_xy(location[0], location[1])
ids = self.TKCanvas.find_overlapping(x, y, x, y)
return ids
def get_bounding_box(self, figure):
"""
Given a figure, returns the upper left and lower right bounding box coordinates
:param figure: a previously drawing figure
:type figure: object
:return: upper left x, upper left y, lower right x, lower right y
:rtype: Tuple[int, int, int, int] | Tuple[float, float, float, float]
"""
box = self.TKCanvas.bbox(figure)
top_left = self._convert_canvas_xy_to_xy(box[0], box[1])
bottom_right = self._convert_canvas_xy_to_xy(box[2], box[3])
return top_left, bottom_right
def change_coordinates(self, graph_bottom_left, graph_top_right):
"""
Changes the corrdinate system to a new one. The same 2 points in space are used to define the coorinate
system - the bottom left and the top right values of your graph.
:param graph_bottom_left: The bottoms left corner of your coordinate system
:type graph_bottom_left: (int, int) (x,y)
:param graph_top_right: The top right corner of your coordinate system
:type graph_top_right: (int, int) (x,y)
"""
self.BottomLeft = graph_bottom_left
self.TopRight = graph_top_right
@property
def tk_canvas(self):
"""
Returns the underlying tkiner Canvas widget
:return: The tkinter canvas widget
:rtype: (tk.Canvas)
"""
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
# button release callback
def button_release_call_back(self, event):
"""
Not a user callable method. Used to get Graph click events. Called by tkinter when button is released
:param event: (event) event info from tkinter. Note not used in this method
:type event:
"""
if not self.DragSubmits:
return # only report mouse up for drag operations
self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y)
self.ParentForm.LastButtonClickedWasRealtime = False
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = '__GRAPH__' # need to put something rather than None
_exit_mainloop(self.ParentForm)
if isinstance(self.ParentForm.LastButtonClicked, str):
self.ParentForm.LastButtonClicked = self.ParentForm.LastButtonClicked + '+UP'
else:
self.ParentForm.LastButtonClicked = (self.ParentForm.LastButtonClicked, '+UP')
self.MouseButtonDown = False
# button callback
def button_press_call_back(self, event):
"""
Not a user callable method. Used to get Graph click events. Called by tkinter when button is released
:param event: (event) event info from tkinter. Contains the x and y coordinates of a click
:type 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
_exit_mainloop(self.ParentForm)
self.MouseButtonDown = True
def _update_position_for_returned_values(self, event):
"""
Updates the variable that's used when the values dictionary is returned from a window read.
Not called by the user. It's called from another method/function that tkinter calledback
:param event: (event) event info from tkinter. Contains the x and y coordinates of a click
:type event:
"""
"""
Updates the variable that's used when the values dictionary is returned from a window read.
Not called by the user. It's called from another method/function that tkinter calledback
:param event: (event) event info from tkinter. Contains the x and y coordinates of a click
:type event:
"""
self.ClickPosition = self._convert_canvas_xy_to_xy(event.x, event.y)
# button callback
def motion_call_back(self, event):
"""
Not a user callable method. Used to get Graph mouse motion events. Called by tkinter when mouse moved
:param event: (event) event info from tkinter. Contains the x and y coordinates of a mouse
:type event:
"""
if not self.MouseButtonDown and not self.motion_events:
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.motion_events and not self.MouseButtonDown:
if isinstance(self.ParentForm.LastButtonClicked, str):
self.ParentForm.LastButtonClicked = self.ParentForm.LastButtonClicked + '+MOVE'
else:
self.ParentForm.LastButtonClicked = (self.ParentForm.LastButtonClicked, '+MOVE')
_exit_mainloop(self.ParentForm)
BringFigureToFront = bring_figure_to_front
ButtonPressCallBack = button_press_call_back
ButtonReleaseCallBack = button_release_call_back
DeleteFigure = delete_figure
DrawArc = draw_arc
DrawCircle = draw_circle
DrawImage = draw_image
DrawLine = draw_line
DrawOval = draw_oval
DrawPoint = draw_point
DrawPolygon = draw_polygon
DrawLines = draw_lines
DrawRectangle = draw_rectangle
DrawText = draw_text
GetFiguresAtLocation = get_figures_at_location
GetBoundingBox = get_bounding_box
Erase = erase
MotionCallBack = motion_call_back
Move = move
MoveFigure = move_figure
RelocateFigure = relocate_figure
SendFigureToBack = send_figure_to_back
TKCanvas = tk_canvas
Update = update

View File

@ -0,0 +1,199 @@
from __future__ import annotations
import tkinter as tk
def AddMenuItem(top_menu, sub_menu_info, element, is_sub_menu=False, skip=False, right_click_menu=False):
"""
Only to be used internally. Not user callable
:param top_menu: ???
:type top_menu: ???
:param sub_menu_info: ???
:type sub_menu_info:
:param element: ???
:type element: idk_yetReally
:param is_sub_menu: (Default = False)
:type is_sub_menu: (bool)
:param skip: (Default = False)
:type skip: (bool)
"""
return_val = None
if type(sub_menu_info) is str:
if not is_sub_menu and not skip:
pos = sub_menu_info.find(MENU_SHORTCUT_CHARACTER)
if pos != -1:
if pos < len(MENU_SHORTCUT_CHARACTER) or sub_menu_info[pos - len(MENU_SHORTCUT_CHARACTER)] != '\\':
sub_menu_info = sub_menu_info[:pos] + sub_menu_info[pos + len(MENU_SHORTCUT_CHARACTER) :]
if sub_menu_info == '---':
top_menu.add('separator')
else:
try:
item_without_key = sub_menu_info[: sub_menu_info.index(MENU_KEY_SEPARATOR)]
except:
item_without_key = sub_menu_info
if item_without_key[0] == MENU_DISABLED_CHARACTER:
top_menu.add_command(
label=item_without_key[len(MENU_DISABLED_CHARACTER) :],
underline=pos - 1,
command=lambda: element._MenuItemChosenCallback(sub_menu_info),
)
top_menu.entryconfig(item_without_key[len(MENU_DISABLED_CHARACTER) :], state='disabled')
else:
top_menu.add_command(
label=item_without_key,
underline=pos,
command=lambda: element._MenuItemChosenCallback(sub_menu_info),
)
else:
i = 0
while i < (len(sub_menu_info)):
item = sub_menu_info[i]
if i != len(sub_menu_info) - 1:
if type(sub_menu_info[i + 1]) is list:
new_menu = tk.Menu(top_menu, tearoff=element.Tearoff)
# if a right click menu, then get styling from the top-level window
if right_click_menu:
window = element.ParentForm
if window.right_click_menu_background_color not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(bg=window.right_click_menu_background_color)
new_menu.config(activeforeground=window.right_click_menu_background_color)
if window.right_click_menu_text_color not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(fg=window.right_click_menu_text_color)
new_menu.config(activebackground=window.right_click_menu_text_color)
if window.right_click_menu_disabled_text_color not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(disabledforeground=window.right_click_menu_disabled_text_color)
if window.right_click_menu_font is not None:
new_menu.config(font=window.right_click_menu_font)
else:
if element.Font is not None:
new_menu.config(font=element.Font)
if element.BackgroundColor not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(bg=element.BackgroundColor)
new_menu.config(activeforeground=element.BackgroundColor)
if element.TextColor not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(fg=element.TextColor)
new_menu.config(activebackground=element.TextColor)
if element.DisabledTextColor not in (COLOR_SYSTEM_DEFAULT, None):
new_menu.config(disabledforeground=element.DisabledTextColor)
if element.ItemFont is not None:
new_menu.config(font=element.ItemFont)
return_val = new_menu
pos = sub_menu_info[i].find(MENU_SHORTCUT_CHARACTER)
if pos != -1:
if pos < len(MENU_SHORTCUT_CHARACTER) or sub_menu_info[i][pos - len(MENU_SHORTCUT_CHARACTER)] != '\\':
sub_menu_info[i] = sub_menu_info[i][:pos] + sub_menu_info[i][pos + len(MENU_SHORTCUT_CHARACTER) :]
if sub_menu_info[i][0] == MENU_DISABLED_CHARACTER:
top_menu.add_cascade(
label=sub_menu_info[i][len(MENU_DISABLED_CHARACTER) :],
menu=new_menu,
underline=pos,
state='disabled',
)
else:
top_menu.add_cascade(label=sub_menu_info[i], menu=new_menu, underline=pos)
AddMenuItem(new_menu, sub_menu_info[i + 1], element, is_sub_menu=True, right_click_menu=right_click_menu)
i += 1 # skip the next one
else:
AddMenuItem(top_menu, item, element, right_click_menu=right_click_menu)
else:
AddMenuItem(top_menu, item, element, right_click_menu=right_click_menu)
i += 1
return return_val
def button_color_to_tuple(color_tuple_or_string, default=(None, None)):
"""
Convert a color tuple or color string into 2 components and returns them as a tuple
(Text Color, Button Background Color)
If None is passed in as the first parameter, then the theme's button color is
returned
:param color_tuple_or_string: Button color - tuple or a simplied color string with word "on" between color
:type color_tuple_or_string: str | (str, str)
:param default: The 2 colors to use if there is a problem. Otherwise defaults to the theme's button color
:type default: (str, str)
:return: (str | (str, str)
:rtype: str | (str, str)
"""
if default == (None, None):
color_tuple = _simplified_dual_color_to_tuple(color_tuple_or_string, default=theme_button_color())
elif color_tuple_or_string == COLOR_SYSTEM_DEFAULT:
color_tuple = (COLOR_SYSTEM_DEFAULT, COLOR_SYSTEM_DEFAULT)
else:
color_tuple = _simplified_dual_color_to_tuple(color_tuple_or_string, default=default)
return color_tuple
def _simplified_dual_color_to_tuple(color_tuple_or_string, default=(None, None)):
"""
Convert a color tuple or color string into 2 components and returns them as a tuple
(Text Color, Button Background Color)
If None is passed in as the first parameter, theme_
:param color_tuple_or_string: Button color - tuple or a simplied color string with word "on" between color
:type color_tuple_or_string: str | (str, str} | (None, None)
:param default: The 2 colors to use if there is a problem. Otherwise defaults to the theme's button color
:type default: (str, str)
:return: (str | (str, str)
:rtype: str | (str, str)
"""
if color_tuple_or_string is None or color_tuple_or_string == (None, None):
color_tuple_or_string = default
if color_tuple_or_string == COLOR_SYSTEM_DEFAULT:
return (COLOR_SYSTEM_DEFAULT, COLOR_SYSTEM_DEFAULT)
text_color = background_color = COLOR_SYSTEM_DEFAULT
try:
if isinstance(color_tuple_or_string, (tuple, list)):
if len(color_tuple_or_string) >= 2:
text_color = color_tuple_or_string[0] or default[0]
background_color = color_tuple_or_string[1] or default[1]
elif len(color_tuple_or_string) == 1:
background_color = color_tuple_or_string[0] or default[1]
elif isinstance(color_tuple_or_string, str):
color_tuple_or_string = color_tuple_or_string.lower()
split_colors = color_tuple_or_string.split(' on ')
if len(split_colors) >= 2:
text_color = split_colors[0].strip() or default[0]
background_color = split_colors[1].strip() or default[1]
elif len(split_colors) == 1:
split_colors = color_tuple_or_string.split('on')
if len(split_colors) == 1:
text_color, background_color = default[0], split_colors[0].strip()
else:
split_colors = split_colors[0].strip(), split_colors[1].strip()
text_color = split_colors[0] or default[0]
background_color = split_colors[1] or default[1]
# text_color, background_color = color_tuple_or_string, default[1]
else:
text_color, background_color = default
else:
if not SUPPRESS_ERROR_POPUPS:
_error_popup_with_traceback('** Badly formatted dual-color... not a tuple nor string **', color_tuple_or_string)
else:
print('** Badly formatted dual-color... not a tuple nor string **', color_tuple_or_string)
text_color, background_color = default
except Exception as e:
if not SUPPRESS_ERROR_POPUPS:
_error_popup_with_traceback('** Badly formatted button color **', color_tuple_or_string, e)
else:
print('** Badly formatted button color... not a tuple nor string **', color_tuple_or_string, e)
text_color, background_color = default
if isinstance(text_color, int):
text_color = '#%06X' % text_color
if isinstance(background_color, int):
background_color = '#%06X' % background_color
# print('converted button color', color_tuple_or_string, 'to', (text_color, background_color))
return (text_color, background_color)
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import MENU_DISABLED_CHARACTER
from FreeSimpleGUI import MENU_KEY_SEPARATOR
from FreeSimpleGUI import MENU_SHORTCUT_CHARACTER
from FreeSimpleGUI import SUPPRESS_ERROR_POPUPS
from FreeSimpleGUI import theme_button_color

View File

@ -0,0 +1,319 @@
from __future__ import annotations
import time
import tkinter as tk
import warnings
from FreeSimpleGUI import ELEM_TYPE_IMAGE
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.elements.base import Element
class Image(Element):
"""
Image Element - show an image in the window. Should be a GIF or a PNG only
"""
def __init__(
self,
source=None,
filename=None,
data=None,
background_color=None,
size=(None, None),
s=(None, None),
pad=None,
p=None,
key=None,
k=None,
tooltip=None,
subsample=None,
zoom=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
enable_events=False,
metadata=None,
):
"""
:param source: A filename or a base64 bytes. Will automatically detect the type and fill in filename or data for you.
:type source: str | bytes | None
:param filename: image filename if there is a button image. GIFs and PNGs only.
:type filename: str | None
:param data: Raw or Base64 representation of the image to put on button. Choose either filename or data
:type data: bytes | str | None
:param background_color: color of background
:type background_color:
:param size: (width, height) size of image in pixels
:type size: (int, int)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type subsample: (int)
:param zoom: amount to increase the size of the image.
:type zoom: (int)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param enable_events: Turns on the element specific events. For an Image element, the event is "image clicked"
:type enable_events: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
if source is not None:
if isinstance(source, bytes):
data = source
elif isinstance(source, str):
filename = source
else:
warnings.warn(f'Image element - source is not a valid type: {type(source)}', UserWarning)
self.Filename = filename
self.Data = data
self.Widget = self.tktext_label = None # type: tk.Label
self.BackgroundColor = background_color
if data is None and filename is None:
self.Filename = ''
self.EnableEvents = enable_events
self.RightClickMenu = right_click_menu
self.AnimatedFrames = None
self.CurrentFrameNumber = 0
self.TotalAnimatedFrames = 0
self.LastFrameTime = 0
self.ImageSubsample = subsample
self.zoom = int(zoom) if zoom is not None else None
self.Source = filename if filename is not None else data
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_IMAGE,
size=sz,
background_color=background_color,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def update(self, source=None, filename=None, data=None, size=(None, None), subsample=None, zoom=None, visible=None):
"""
Changes some of the settings for the Image Element. Must call `Window.Read` or `Window.Finalize` prior.
To clear an image that's been displayed, call with NONE of the options set. A blank update call will
delete the previously shown image.
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param source: A filename or a base64 bytes. Will automatically detect the type and fill in filename or data for you.
:type source: str | bytes | None
:param filename: filename to the new image to display.
:type filename: (str)
:param data: Base64 encoded string OR a tk.PhotoImage object
:type data: str | tkPhotoImage
:param size: (width, height) size of image in pixels
:type size: Tuple[int,int]
:param subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type subsample: (int)
:param zoom: amount to increase the size of the image
:type zoom: (int)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Image.update - The window was closed')
return
if source is not None:
if isinstance(source, bytes):
data = source
elif isinstance(source, str):
filename = source
else:
warnings.warn(f'Image element - source is not a valid type: {type(source)}', UserWarning)
image = None
if filename is not None:
try:
image = tk.PhotoImage(file=filename)
if subsample is not None:
image = image.subsample(subsample)
if zoom is not None:
image = image.zoom(int(zoom))
except Exception as e:
_error_popup_with_traceback('Exception updating Image element', e)
elif data is not None:
# if type(data) is bytes:
try:
image = tk.PhotoImage(data=data)
if subsample is not None:
image = image.subsample(subsample)
if zoom is not None:
image = image.zoom(int(zoom))
except Exception:
image = data
# return # an error likely means the window has closed so exit
if image is not None:
self.tktext_label.configure(image='') # clear previous image
if self.tktext_label.image is not None:
del self.tktext_label.image
if type(image) is not bytes:
width, height = (
size[0] if size[0] is not None else image.width(),
size[1] if size[1] is not None else image.height(),
)
else:
width, height = size
try: # sometimes crashes if user closed with X
self.tktext_label.configure(image=image, width=width, height=height)
except Exception as e:
_error_popup_with_traceback('Exception updating Image element', e)
self.tktext_label.image = image
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
# if everything is set to None, then delete the image
if filename is None and image is None and visible is None and size == (None, None):
# Using a try because the image may have been previously deleted and don't want an error if that's happened
try:
self.tktext_label.configure(image='', width=1, height=1, bd=0)
self.tktext_label.image = None
except:
pass
if visible is not None:
self._visible = visible
def update_animation(self, source, time_between_frames=0):
"""
Show an Animated GIF. Call the function as often as you like. The function will determine when to show the next frame and will automatically advance to the next frame at the right time.
NOTE - does NOT perform a sleep call to delay
:param source: Filename or Base64 encoded string containing Animated GIF
:type source: str | bytes | None
:param time_between_frames: Number of milliseconds to wait between showing frames
:type time_between_frames: (int)
"""
if self.Source != source:
self.AnimatedFrames = None
self.Source = source
if self.AnimatedFrames is None:
self.TotalAnimatedFrames = 0
self.AnimatedFrames = []
# Load up to 1000 frames of animation. stops when a bad frame is returns by tkinter
for i in range(1000):
if type(source) is not bytes:
try:
self.AnimatedFrames.append(tk.PhotoImage(file=source, format='gif -index %i' % (i)))
except Exception:
break
else:
try:
self.AnimatedFrames.append(tk.PhotoImage(data=source, format='gif -index %i' % (i)))
except Exception:
break
self.TotalAnimatedFrames = len(self.AnimatedFrames)
self.LastFrameTime = time.time()
self.CurrentFrameNumber = -1 # start at -1 because it is incremented before every frame is shown
# show the frame
now = time.time()
if time_between_frames:
if (now - self.LastFrameTime) * 1000 > time_between_frames:
self.LastFrameTime = now
self.CurrentFrameNumber = (self.CurrentFrameNumber + 1) % self.TotalAnimatedFrames
else: # don't reshow the frame again if not time for new frame
return
else:
self.CurrentFrameNumber = (self.CurrentFrameNumber + 1) % self.TotalAnimatedFrames
image = self.AnimatedFrames[self.CurrentFrameNumber]
try: # needed in case the window was closed with an "X"
self.tktext_label.configure(image=image, width=image.width(), heigh=image.height())
except Exception as e:
print('Exception in update_animation', e)
def update_animation_no_buffering(self, source, time_between_frames=0):
"""
Show an Animated GIF. Call the function as often as you like. The function will determine when to show the next frame and will automatically advance to the next frame at the right time.
NOTE - does NOT perform a sleep call to delay
:param source: Filename or Base64 encoded string containing Animated GIF
:type source: str | bytes
:param time_between_frames: Number of milliseconds to wait between showing frames
:type time_between_frames: (int)
"""
if self.Source != source:
self.AnimatedFrames = None
self.Source = source
self.frame_num = 0
now = time.time()
if time_between_frames:
if (now - self.LastFrameTime) * 1000 > time_between_frames:
self.LastFrameTime = now
else: # don't reshow the frame again if not time for new frame
return
# read a frame
while True:
if type(source) is not bytes:
try:
self.image = tk.PhotoImage(file=source, format='gif -index %i' % (self.frame_num))
self.frame_num += 1
except:
self.frame_num = 0
else:
try:
self.image = tk.PhotoImage(data=source, format='gif -index %i' % (self.frame_num))
self.frame_num += 1
except:
self.frame_num = 0
if self.frame_num:
break
try: # needed in case the window was closed with an "X"
self.tktext_label.configure(image=self.image, width=self.image.width(), heigh=self.image.height())
except:
pass
Update = update
UpdateAnimation = update_animation

View File

@ -0,0 +1,301 @@
from __future__ import annotations
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_INPUT_TEXT
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Input(Element):
"""
Display a single text input field. Based on the tkinter Widget `Entry`
"""
def __init__(
self,
default_text='',
size=(None, None),
s=(None, None),
disabled=False,
password_char='',
justification=None,
background_color=None,
text_color=None,
font=None,
tooltip=None,
border_width=None,
change_submits=False,
enable_events=False,
do_not_clear=True,
key=None,
k=None,
focus=False,
pad=None,
p=None,
use_readonly_for_disable=True,
readonly=False,
disabled_readonly_background_color=None,
disabled_readonly_text_color=None,
selected_text_color=None,
selected_background_color=None,
expand_x=False,
expand_y=False,
right_click_menu=None,
visible=True,
metadata=None,
):
"""
:param default_text: Text initially shown in the input box as a default value(Default value = ''). Will automatically be converted to string
:type default_text: (Any)
:param size: w=characters-wide, h=rows-high. If an int is supplied rather than a tuple, then a tuple is created width=int supplied and heigh=1
:type size: (int, int) | (int, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param disabled: set disable state for element (Default = False)
:type disabled: (bool)
:param password_char: Password character if this is a password field (Default value = '')
:type password_char: (char)
:param justification: justification for data display. Valid choices - left, right, center
:type justification: (str)
:param background_color: color of background in one of the color formats
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param font: specifies the font family, size. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param border_width: width of border around element in pixels
:type border_width: (int)
:param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead
:type change_submits: (bool)
:param enable_events: If True then changes to this element are immediately reported as an event. Use this instead of change_submits (Default = False)
:type enable_events: (bool)
:param do_not_clear: If False then the field will be set to blank after ANY event (button, any event) (Default = True)
:type do_not_clear: (bool)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param focus: Determines if initial focus should go to this element.
:type focus: (bool)
:param pad: Amount of padding to put around element. Normally (horizontal pixels, vertical pixels) but can be split apart further into ((horizontal left, horizontal right), (vertical above, vertical below)). If int is given, then converted to tuple (int, int) with the value provided duplicated
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param use_readonly_for_disable: If True (the default) tkinter state set to 'readonly'. Otherwise state set to 'disabled'
:type use_readonly_for_disable: (bool)
:param readonly: If True tkinter state set to 'readonly'. Use this in place of use_readonly_for_disable as another way of achieving readonly. Note cannot set BOTH readonly and disabled as tkinter only supplies a single flag
:type readonly: (bool)
:param disabled_readonly_background_color: If state is set to readonly or disabled, the color to use for the background
:type disabled_readonly_background_color: (str)
:param disabled_readonly_text_color: If state is set to readonly or disabled, the color to use for the text
:type disabled_readonly_text_color: (str)
:param selected_text_color: Color of text when it is selected (using mouse or control+A, etc)
:type selected_text_color: (str)
:param selected_background_color: Color of background when it is selected (using mouse or control+A, etc)
:type selected_background_color: (str)
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param visible: set visibility state of the element (Default = True)
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.DefaultText = default_text if default_text is not None else ''
self.PasswordCharacter = password_char
bg = background_color if background_color is not None else FreeSimpleGUI.DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else FreeSimpleGUI.DEFAULT_INPUT_TEXT_COLOR
self.selected_text_color = selected_text_color
self.selected_background_color = selected_background_color
self.Focus = focus
self.do_not_clear = do_not_clear
self.Justification = justification
self.Disabled = disabled
self.ChangeSubmits = change_submits or enable_events
self.RightClickMenu = right_click_menu
self.UseReadonlyForDisable = use_readonly_for_disable
self.disabled_readonly_background_color = disabled_readonly_background_color
self.disabled_readonly_text_color = disabled_readonly_text_color
self.ReadOnly = readonly
self.BorderWidth = border_width if border_width is not None else FreeSimpleGUI.DEFAULT_BORDER_WIDTH
self.TKEntry = self.Widget = None # type: tk.Entry
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_INPUT_TEXT,
size=sz,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
font=font,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
def update(
self,
value=None,
disabled=None,
select=None,
visible=None,
text_color=None,
background_color=None,
font=None,
move_cursor_to='end',
password_char=None,
paste=None,
readonly=None,
):
"""
Changes some of the settings for the Input Element. Must call `Window.Read` or `Window.Finalize` prior.
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: new text to display as default text in Input field
:type value: (str)
:param disabled: disable or enable state of the element (sets Entry Widget to readonly or normal)
:type disabled: (bool)
:param select: if True, then the text will be selected
:type select: (bool)
:param visible: change visibility of element
:type visible: (bool)
:param text_color: change color of text being typed
:type text_color: (str)
:param background_color: change color of the background
:type background_color: (str)
:param font: specifies the font family, size. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param move_cursor_to: Moves the cursor to a particular offset. Defaults to 'end'
:type move_cursor_to: int | str
:param password_char: Password character if this is a password field
:type password_char: str
:param paste: If True "Pastes" the value into the element rather than replacing the entire element. If anything is selected it is replaced. The text is inserted at the current cursor location.
:type paste: bool
:param readonly: if True make element readonly (user cannot change any choices). Enables the element if either choice are made.
:type readonly: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Input.update - The window was closed')
return
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKEntry.configure(background=background_color)
self.BackgroundColor = background_color
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKEntry.configure(fg=text_color)
self.TextColor = text_color
if disabled is True:
if self.UseReadonlyForDisable:
if self.disabled_readonly_text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKEntry.configure(fg=self.disabled_readonly_text_color)
self.TKEntry['state'] = 'readonly'
else:
if self.TextColor not in (None, COLOR_SYSTEM_DEFAULT):
self.TKEntry.configure(fg=self.TextColor)
self.TKEntry['state'] = 'disabled'
self.Disabled = True
elif disabled is False:
self.TKEntry['state'] = 'normal'
if self.TextColor not in (None, COLOR_SYSTEM_DEFAULT):
self.TKEntry.configure(fg=self.TextColor)
self.Disabled = False
if readonly is True:
self.TKEntry['state'] = 'readonly'
elif readonly is False:
self.TKEntry['state'] = 'normal'
if value is not None:
if paste is not True:
try:
self.TKStringVar.set(value)
except:
pass
self.DefaultText = value
if paste is True:
try:
self.TKEntry.delete('sel.first', 'sel.last')
except:
pass
self.TKEntry.insert('insert', value)
if move_cursor_to == 'end':
self.TKEntry.icursor(tk.END)
elif move_cursor_to is not None:
self.TKEntry.icursor(move_cursor_to)
if select:
self.TKEntry.select_range(0, 'end')
if visible is False:
self._pack_forget_save_settings()
# self.TKEntry.pack_forget()
elif visible is True:
self._pack_restore_settings()
# self.TKEntry.pack(padx=self.pad_used[0], pady=self.pad_used[1])
# self.TKEntry.pack(padx=self.pad_used[0], pady=self.pad_used[1], in_=self.ParentRowFrame)
if visible is not None:
self._visible = visible
if password_char is not None:
self.TKEntry.configure(show=password_char)
self.PasswordCharacter = password_char
if font is not None:
self.TKEntry.configure(font=font)
def set_ibeam_color(self, ibeam_color=None):
"""
Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by
many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term
ibeam is used in this case.
:param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted
:type ibeam_color: (str)
"""
if not self._widget_was_created():
return
if ibeam_color is not None:
try:
self.Widget.config(insertbackground=ibeam_color)
except Exception:
_error_popup_with_traceback(
'Error setting I-Beam color in set_ibeam_color',
'The element has a key:',
self.Key,
'The color passed in was:',
ibeam_color,
)
def get(self):
"""
Read and return the current value of the input element. Must call `Window.Read` or `Window.Finalize` prior
:return: current value of Input field or '' if error encountered
:rtype: (str)
"""
try:
text = self.TKStringVar.get()
except:
text = ''
return text
Get = get
Update = update

View File

@ -0,0 +1,392 @@
from __future__ import annotations
import tkinter as tk
import warnings
from typing import Any # noqa
from typing import List # noqa
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_INPUT_LISTBOX
from FreeSimpleGUI import Element
from FreeSimpleGUI import LISTBOX_SELECT_MODE_BROWSE
from FreeSimpleGUI import LISTBOX_SELECT_MODE_EXTENDED
from FreeSimpleGUI import LISTBOX_SELECT_MODE_MULTIPLE
from FreeSimpleGUI import LISTBOX_SELECT_MODE_SINGLE
from FreeSimpleGUI import SELECT_MODE_BROWSE
from FreeSimpleGUI import SELECT_MODE_EXTENDED
from FreeSimpleGUI import SELECT_MODE_MULTIPLE
from FreeSimpleGUI import SELECT_MODE_SINGLE
from FreeSimpleGUI import theme_input_background_color
from FreeSimpleGUI import theme_input_text_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Listbox(Element):
"""
A List Box. Provide a list of values for the user to choose one or more of. Returns a list of selected rows
when a window.read() is executed.
"""
def __init__(
self,
values,
default_values=None,
select_mode=None,
change_submits=False,
enable_events=False,
bind_return_key=False,
size=(None, None),
s=(None, None),
disabled=False,
justification=None,
auto_size_text=None,
font=None,
no_scrollbar=False,
horizontal_scroll=False,
background_color=None,
text_color=None,
highlight_background_color=None,
highlight_text_color=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
key=None,
k=None,
pad=None,
p=None,
tooltip=None,
expand_x=False,
expand_y=False,
right_click_menu=None,
visible=True,
metadata=None,
):
"""
:param values: list of values to display. Can be any type including mixed types as long as they have __str__ method
:type values: List[Any] or Tuple[Any]
:param default_values: which values should be initially selected
:type default_values: List[Any]
:param select_mode: Select modes are used to determine if only 1 item can be selected or multiple and how they can be selected. Valid choices begin with "LISTBOX_SELECT_MODE_" and include: LISTBOX_SELECT_MODE_SINGLE LISTBOX_SELECT_MODE_MULTIPLE LISTBOX_SELECT_MODE_BROWSE LISTBOX_SELECT_MODE_EXTENDED
:type select_mode: [enum]
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Listbox generates events when an item is clicked
:type enable_events: (bool)
:param bind_return_key: If True, then the return key will cause a the Listbox to generate an event when return key is pressed
:type bind_return_key: (bool)
:param size: w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (int, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param disabled: set disable state for element
:type disabled: (bool)
:param justification: justification for items in listbox. Valid choices - left, right, center. Default is left. NOTE - on some older versions of tkinter, not available
:type justification: (str)
:param auto_size_text: True if element should be the same size as the contents
:type auto_size_text: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param no_scrollbar: Controls if a scrollbar should be shown. If True, no scrollbar will be shown
:type no_scrollbar: (bool)
:param horizontal_scroll: Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical
:type horizontal_scroll: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param highlight_background_color: color of the background when an item is selected. Defaults to normal text color (a reverse look)
:type highlight_background_color: (str)
:param highlight_text_color: color of the text when an item is selected. Defaults to the normal background color (a rerverse look)
:type highlight_text_color: (str)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
if values is None:
_error_popup_with_traceback(
'Error in your Listbox definition - The values parameter cannot be None',
'Use an empty list if you want no values in your Listbox',
)
self.Values = values
self.DefaultValues = default_values
self.TKListbox = None
self.ChangeSubmits = change_submits or enable_events
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 = FreeSimpleGUI.DEFAULT_LISTBOX_SELECT_MODE
bg = background_color if background_color is not None else theme_input_background_color()
fg = text_color if text_color is not None else theme_input_text_color()
self.HighlightBackgroundColor = highlight_background_color if highlight_background_color is not None else fg
self.HighlightTextColor = highlight_text_color if highlight_text_color is not None else bg
self.RightClickMenu = right_click_menu
self.vsb = None # type: tk.Scrollbar or None
self.hsb = None # type: tk.Scrollbar | None
self.TKListbox = self.Widget = None # type: tk.Listbox
self.element_frame = None # type: tk.Frame
self.NoScrollbar = no_scrollbar
self.HorizontalScroll = horizontal_scroll
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.justification = justification
super().__init__(
ELEM_TYPE_INPUT_LISTBOX,
size=sz,
auto_size_text=auto_size_text,
font=font,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
tooltip=tooltip,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)
def update(self, values=None, disabled=None, set_to_index=None, scroll_to_index=None, select_mode=None, visible=None):
"""
Changes some of the settings for the Listbox Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param values: new list of choices to be shown to user
:type values: List[Any]
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param set_to_index: highlights the item(s) indicated. If parm is an int one entry will be set. If is a list, then each entry in list is highlighted
:type set_to_index: int | list | tuple
:param scroll_to_index: scroll the listbox so that this index is the first shown
:type scroll_to_index: (int)
:param select_mode: changes the select mode according to tkinter's listbox widget
:type select_mode: (str)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Listbox.update - The window was closed')
return
if disabled is True:
self.TKListbox.configure(state='disabled')
elif disabled is False:
self.TKListbox.configure(state='normal')
self.Disabled = disabled if disabled is not None else self.Disabled
if values is not None:
self.TKListbox.delete(0, 'end')
for item in list(values):
self.TKListbox.insert(tk.END, item)
# self.TKListbox.selection_set(0, 0)
self.Values = list(values)
if set_to_index is not None:
self.TKListbox.selection_clear(0, len(self.Values)) # clear all listbox selections
if type(set_to_index) in (tuple, list):
for i in set_to_index:
try:
self.TKListbox.selection_set(i, i)
except:
warnings.warn(f'* Listbox Update selection_set failed with index {set_to_index}*')
else:
try:
self.TKListbox.selection_set(set_to_index, set_to_index)
except:
warnings.warn(f'* Listbox Update selection_set failed with index {set_to_index}*')
if visible is False:
self._pack_forget_save_settings(self.element_frame)
elif visible is True:
self._pack_restore_settings(self.element_frame)
if scroll_to_index is not None and len(self.Values):
self.TKListbox.yview_moveto(scroll_to_index / len(self.Values))
if select_mode is not None:
try:
self.TKListbox.config(selectmode=select_mode)
except:
print('Listbox.update error trying to change mode to: ', select_mode)
if visible is not None:
self._visible = visible
def set_value(self, values):
"""
Set listbox highlighted choices
:param values: new values to choose based on previously set values
:type values: List[Any] | Tuple[Any]
"""
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 get_list_values(self):
# type: (Listbox) -> List[Any]
"""
Returns list of Values provided by the user in the user's format
:return: List of values. Can be any / mixed types -> []
:rtype: List[Any]
"""
return self.Values
def get_indexes(self):
"""
Returns the items currently selected as a list of indexes
:return: A list of offsets into values that is currently selected
:rtype: List[int]
"""
return self.TKListbox.curselection()
def get(self):
"""
Returns the list of items currently selected in this listbox. It should be identical
to the value you would receive when performing a window.read() call.
:return: The list of currently selected items. The actual items are returned, not the indexes
:rtype: List[Any]
"""
try:
items = self.TKListbox.curselection()
value = [self.Values[int(item)] for item in items]
except:
value = []
return value
def select_index(self, index, highlight_text_color=None, highlight_background_color=None):
"""
Selects an index while providing capability to setting the selected color for the index to specific text/background color
:param index: specifies which item to change. index starts at 0 and goes to length of values list minus one
:type index: (int)
:param highlight_text_color: color of the text when this item is selected.
:type highlight_text_color: (str)
:param highlight_background_color: color of the background when this item is selected
:type highlight_background_color: (str)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Listbox.select_item - The window was closed')
return
if index >= len(self.Values):
_error_popup_with_traceback('Index {} is out of range for Listbox.select_index. Max allowed index is {}.'.format(index, len(self.Values) - 1))
return
self.TKListbox.selection_set(index, index)
if highlight_text_color is not None:
self.widget.itemconfig(index, selectforeground=highlight_text_color)
if highlight_background_color is not None:
self.widget.itemconfig(index, selectbackground=highlight_background_color)
def set_index_color(self, index, text_color=None, background_color=None, highlight_text_color=None, highlight_background_color=None):
"""
Sets the color of a specific item without selecting it
:param index: specifies which item to change. index starts at 0 and goes to length of values list minus one
:type index: (int)
:param text_color: color of the text for this item
:type text_color: (str)
:param background_color: color of the background for this item
:type background_color: (str)
:param highlight_text_color: color of the text when this item is selected.
:type highlight_text_color: (str)
:param highlight_background_color: color of the background when this item is selected
:type highlight_background_color: (str)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Listbox.set_item_color - The window was closed')
return
if index >= len(self.Values):
_error_popup_with_traceback('Index {} is out of range for Listbox.set_index_color. Max allowed index is {}.'.format(index, len(self.Values) - 1))
return
if text_color is not None:
self.widget.itemconfig(index, fg=text_color)
if background_color is not None:
self.widget.itemconfig(index, bg=background_color)
if highlight_text_color is not None:
self.widget.itemconfig(index, selectforeground=highlight_text_color)
if highlight_background_color is not None:
self.widget.itemconfig(index, selectbackground=highlight_background_color)
GetIndexes = get_indexes
GetListValues = get_list_values
SetValue = set_value
Update = update

View File

@ -0,0 +1,192 @@
from __future__ import annotations
import copy
import tkinter as tk
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_MENUBAR
from FreeSimpleGUI import MENU_DISABLED_CHARACTER
from FreeSimpleGUI import MENU_SHORTCUT_CHARACTER
from FreeSimpleGUI import theme_input_background_color
from FreeSimpleGUI import theme_input_text_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
from FreeSimpleGUI.elements.base import Element
from FreeSimpleGUI.elements.helpers import AddMenuItem
class Menu(Element):
"""
Menu Element is the Element that provides a Menu Bar that goes across the top of the window, just below titlebar.
Here is an example layout. The "&" are shortcut keys ALT+key.
Is a List of - "Item String" + List
Where Item String is what will be displayed on the Menubar itself.
The List that follows the item represents the items that are shown then Menu item is clicked
Notice how an "entry" in a mennu can be a list which means it branches out and shows another menu, etc. (resursive)
menu_def = [['&File', ['!&Open', '&Save::savekey', '---', '&Properties', 'E&xit']],
['!&Edit', ['!&Paste', ['Special', 'Normal', ], 'Undo'], ],
['&Debugger', ['Popout', 'Launch Debugger']],
['&Toolbar', ['Command &1', 'Command &2', 'Command &3', 'Command &4']],
['&Help', '&About...'], ]
Important Note! The colors, font, look of the Menubar itself cannot be changed, only the menus shown AFTER clicking the menubar
can be changed. If you want to change the style/colors the Menubar, then you will have to use the MenubarCustom element.
Finally, "keys" can be added to entries so make them unique. The "Save" entry has a key associated with it. You
can see it has a "::" which signifies the beginning of a key. The user will not see the key portion when the
menu is shown. The key portion is returned as part of the event.
"""
def __init__(
self,
menu_definition,
background_color=None,
text_color=None,
disabled_text_color=None,
size=(None, None),
s=(None, None),
tearoff=False,
font=None,
pad=None,
p=None,
key=None,
k=None,
visible=True,
metadata=None,
):
"""
:param menu_definition: The Menu definition specified using lists (docs explain the format)
:type menu_definition: List[List[Tuple[str, List[str]]]
:param background_color: color of the background of menus, NOT the Menubar
:type background_color: (str)
:param text_color: text color for menus, NOT the Menubar. Can be in #RRGGBB format or a color name "black".
:type text_color: (str)
:param disabled_text_color: color to use for text when item in submenu, not the menubar itself, is disabled. Can be in #RRGGBB format or a color name "black"
:type disabled_text_color: (str)
:param size: Not used in the tkinter port
:type size: (int, int)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None)
:param tearoff: if True, then can tear the menu off from the window ans use as a floating window. Very cool effect
:type tearoff: (bool)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param font: specifies the font family, size, etc. of submenus. Does NOT apply to the Menubar itself. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.BackgroundColor = background_color if background_color is not None else theme_input_background_color()
self.TextColor = text_color if text_color is not None else theme_input_text_color()
self.DisabledTextColor = disabled_text_color if disabled_text_color is not None else COLOR_SYSTEM_DEFAULT
self.MenuDefinition = copy.deepcopy(menu_definition)
self.Widget = self.TKMenu = None # type: tk.Menu
self.MenuItemChosen = None
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
super().__init__(
ELEM_TYPE_MENUBAR,
background_color=self.BackgroundColor,
text_color=self.TextColor,
size=sz,
pad=pad,
key=key,
visible=visible,
font=font,
metadata=metadata,
)
# super().__init__(ELEM_TYPE_MENUBAR, background_color=COLOR_SYSTEM_DEFAULT, text_color=COLOR_SYSTEM_DEFAULT, size=sz, pad=pad, key=key, visible=visible, font=None, metadata=metadata)
self.Tearoff = tearoff
return
def _MenuItemChosenCallback(self, item_chosen): # Menu Menu Item Chosen Callback
"""
Not user callable. Called when some end-point on the menu (an item) has been clicked. Send the information back to the application as an event. Before event can be sent
:param item_chosen: the text that was clicked on / chosen from the menu
:type item_chosen: (str)
"""
# print('IN MENU ITEM CALLBACK', item_chosen)
self.MenuItemChosen = item_chosen
self.ParentForm.LastButtonClicked = item_chosen
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def update(self, menu_definition=None, visible=None):
"""
Update a menubar - can change the menu definition and visibility. The entire menu has to be specified
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param menu_definition: The menu definition list
:type menu_definition: List[List[Tuple[str, List[str]]]
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Menu.update - The window was closed')
return
if menu_definition is not None:
self.MenuDefinition = copy.deepcopy(menu_definition)
if self.TKMenu is None: # if no menu exists, make one
self.TKMenu = tk.Menu(self.ParentForm.TKroot, tearoff=self.Tearoff, tearoffcommand=self._tearoff_menu_callback) # create the menubar
menubar = self.TKMenu
# Delete all the menu items (assuming 10000 should be a high enough number to cover them all)
menubar.delete(0, 10000)
self.Widget = self.TKMenu # same the new menu so user can access to extend PySimpleGUI
for menu_entry in self.MenuDefinition:
baritem = tk.Menu(menubar, tearoff=self.Tearoff, tearoffcommand=self._tearoff_menu_callback)
if self.BackgroundColor not in (COLOR_SYSTEM_DEFAULT, None):
baritem.config(bg=self.BackgroundColor)
if self.TextColor not in (COLOR_SYSTEM_DEFAULT, None):
baritem.config(fg=self.TextColor)
if self.DisabledTextColor not in (COLOR_SYSTEM_DEFAULT, None):
baritem.config(disabledforeground=self.DisabledTextColor)
if self.Font is not None:
baritem.config(font=self.Font)
if self.Font is not None:
baritem.config(font=self.Font)
pos = menu_entry[0].find(MENU_SHORTCUT_CHARACTER)
# print(pos)
if pos != -1:
if pos == 0 or menu_entry[0][pos - len(MENU_SHORTCUT_CHARACTER)] != '\\':
menu_entry[0] = menu_entry[0][:pos] + menu_entry[0][pos + len(MENU_SHORTCUT_CHARACTER) :]
if menu_entry[0][0] == MENU_DISABLED_CHARACTER:
menubar.add_cascade(label=menu_entry[0][len(MENU_DISABLED_CHARACTER) :], menu=baritem, underline=pos)
menubar.entryconfig(menu_entry[0][len(MENU_DISABLED_CHARACTER) :], state='disabled')
else:
menubar.add_cascade(label=menu_entry[0], menu=baritem, underline=pos)
if len(menu_entry) > 1:
AddMenuItem(baritem, menu_entry[1], self)
if visible is False:
self.ParentForm.TKroot.configure(menu=[]) # this will cause the menubar to disappear
elif self.TKMenu is not None:
self.ParentForm.TKroot.configure(menu=self.TKMenu)
if visible is not None:
self._visible = visible
Update = update

View File

@ -0,0 +1,685 @@
from __future__ import annotations
import sys
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import _print_to_element
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_INPUT_MULTILINE
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.elements.base import Element
from FreeSimpleGUI.window import Window
class Multiline(Element):
"""
Multiline Element - Display and/or read multiple lines of text. This is both an input and output element.
Other PySimpleGUI ports have a separate MultilineInput and MultilineOutput elements. May want to split this
one up in the future too.
"""
def __init__(
self,
default_text='',
enter_submits=False,
disabled=False,
autoscroll=False,
autoscroll_only_at_bottom=False,
border_width=None,
size=(None, None),
s=(None, None),
auto_size_text=None,
background_color=None,
text_color=None,
selected_text_color=None,
selected_background_color=None,
horizontal_scroll=False,
change_submits=False,
enable_events=False,
do_not_clear=True,
key=None,
k=None,
write_only=False,
auto_refresh=False,
reroute_stdout=False,
reroute_stderr=False,
reroute_cprint=False,
echo_stdout_stderr=False,
focus=False,
font=None,
pad=None,
p=None,
tooltip=None,
justification=None,
no_scrollbar=False,
wrap_lines=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
expand_x=False,
expand_y=False,
rstrip=True,
right_click_menu=None,
visible=True,
metadata=None,
):
"""
:param default_text: Initial text to show
:type default_text: (Any)
:param enter_submits: if True, the Window.read call will return is enter key is pressed in this element
:type enter_submits: (bool)
:param disabled: set disable state
:type disabled: (bool)
:param autoscroll: If True the contents of the element will automatically scroll as more data added to the end
:type autoscroll: (bool)
:param autoscroll_only_at_bottom: If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline
:type autoscroll_only_at_bottom: (bool)
:param border_width: width of border around element in pixels
:type border_width: (int)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: if True will size the element to match the length of the text
:type auto_size_text: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param selected_text_color: Color of text when it is selected (using mouse or control+A, etc)
:type selected_text_color: (str)
:param selected_background_color: Color of background when it is selected (using mouse or control+A, etc)
:type selected_background_color: (str)
:param horizontal_scroll: Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical
:type horizontal_scroll: (bool)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: If True then any key press that happens when the element has focus will generate an event.
:type enable_events: (bool)
:param do_not_clear: if False the element will be cleared any time the Window.read call returns
:type do_not_clear: (bool)
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param write_only: If True then no entry will be added to the values dictionary when the window is read
:type write_only: bool
:param auto_refresh: If True then anytime the element is updated, the window will be refreshed so that the change is immediately displayed
:type auto_refresh: (bool)
:param reroute_stdout: If True then all output to stdout will be output to this element
:type reroute_stdout: (bool)
:param reroute_stderr: If True then all output to stderr will be output to this element
:type reroute_stderr: (bool)
:param reroute_cprint: If True your cprint calls will output to this element. It's the same as you calling cprint_set_output_destination
:type reroute_cprint: (bool)
:param echo_stdout_stderr: If True then output to stdout and stderr will be output to this element AND also to the normal console location
:type echo_stdout_stderr: (bool)
:param focus: if True initial focus will go to this element
:type focus: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param justification: text justification. left, right, center. Can use single characters l, r, c.
:type justification: (str)
:param no_scrollbar: If False then a vertical scrollbar will be shown (the default)
:type no_scrollbar: (bool)
:param wrap_lines: If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping.
:type wrap_lines: (bool)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param rstrip: If True the value returned in will have whitespace stripped from the right side
:type rstrip: (bool)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.DefaultText = str(default_text)
self.EnterSubmits = enter_submits
bg = background_color if background_color else FreeSimpleGUI.DEFAULT_INPUT_ELEMENTS_COLOR
self.Focus = focus
self.do_not_clear = do_not_clear
fg = text_color if text_color is not None else FreeSimpleGUI.DEFAULT_INPUT_TEXT_COLOR
self.selected_text_color = selected_text_color
self.selected_background_color = selected_background_color
self.Autoscroll = autoscroll
self.Disabled = disabled
self.ChangeSubmits = change_submits or enable_events
self.RightClickMenu = right_click_menu
self.BorderWidth = border_width if border_width is not None else FreeSimpleGUI.DEFAULT_BORDER_WIDTH
self.TagCounter = 0
self.TKText = self.Widget = None # type: tk.Text
self.element_frame = None # type: tk.Frame
self.HorizontalScroll = horizontal_scroll
self.tags = set()
self.WriteOnly = write_only
self.AutoRefresh = auto_refresh
key = key if key is not None else k
self.reroute_cprint = reroute_cprint
self.echo_stdout_stderr = echo_stdout_stderr
self.Justification = 'left' if justification is None else justification
self.justification_tag = self.just_center_tag = self.just_left_tag = self.just_right_tag = None
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.rstrip = rstrip
self.wrap_lines = wrap_lines
self.reroute_stdout = reroute_stdout
self.reroute_stderr = reroute_stderr
self.no_scrollbar = no_scrollbar
self.hscrollbar = None # The horizontal scrollbar
self.auto_scroll_only_at_bottom = autoscroll_only_at_bottom
sz = size if size != (None, None) else s
super().__init__(
ELEM_TYPE_INPUT_MULTILINE,
size=sz,
auto_size_text=auto_size_text,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
tooltip=tooltip,
font=font or FreeSimpleGUI.DEFAULT_FONT,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)
return
def update(
self,
value=None,
disabled=None,
append=False,
font=None,
text_color=None,
background_color=None,
text_color_for_value=None,
background_color_for_value=None,
visible=None,
autoscroll=None,
justification=None,
font_for_value=None,
):
"""
Changes some of the settings for the Multiline Element. Must call `Window.read` or set finalize=True when creating window.
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: new text to display
:type value: (Any)
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param append: if True then new value will be added onto the end of the current value. if False then contents will be replaced.
:type append: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike for the entire element
:type font: (str or (str, int[, str]) or None)
:param text_color: color of the text
:type text_color: (str)
:param background_color: color of background
:type background_color: (str)
:param text_color_for_value: color of the new text being added (the value paramter)
:type text_color_for_value: (str)
:param background_color_for_value: color of the new background of the text being added (the value paramter)
:type background_color_for_value: (str)
:param visible: set visibility state of the element
:type visible: (bool)
:param autoscroll: if True then contents of element are scrolled down when new text is added to the end
:type autoscroll: (bool)
:param justification: text justification. left, right, center. Can use single characters l, r, c. Sets only for this value, not entire element
:type justification: (str)
:param font_for_value: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike for the value being updated
:type font_for_value: str | (str, int)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
# _error_popup_with_traceback('Error in Multiline.update - The window was closed')
return
if autoscroll is not None:
self.Autoscroll = autoscroll
current_scroll_position = self.TKText.yview()[1]
if justification is not None:
if justification.startswith('l'):
just_tag = 'left'
if justification.startswith('r'):
just_tag = 'right'
if justification.startswith('c'):
just_tag = 'center'
else:
just_tag = self.justification_tag
tag = None
if value is not None:
value = str(value)
if background_color_for_value is not None or text_color_for_value is not None or font_for_value is not None:
try:
tag = 'Multiline(' + str(text_color_for_value) + ',' + str(background_color_for_value) + ',' + str(font_for_value) + ')'
if tag not in self.tags:
self.tags.add(tag)
if background_color_for_value is not None:
self.TKText.tag_configure(tag, background=background_color_for_value)
if text_color_for_value is not None:
self.TKText.tag_configure(tag, foreground=text_color_for_value)
if font_for_value is not None:
self.TKText.tag_configure(tag, font=font_for_value)
except Exception as e:
print('* Multiline.update - bad color likely specified:', e)
if self.Disabled:
self.TKText.configure(state='normal')
try:
if not append:
self.TKText.delete('1.0', tk.END)
if tag is not None or just_tag is not None:
self.TKText.insert(tk.END, value, (just_tag, tag))
else:
self.TKText.insert(tk.END, value)
except Exception as e:
print('* Error setting multiline *', e)
if self.Disabled:
self.TKText.configure(state='disabled')
self.DefaultText = value
if self.Autoscroll:
if not self.auto_scroll_only_at_bottom or (self.auto_scroll_only_at_bottom and current_scroll_position == 1.0):
self.TKText.see(tk.END)
if disabled is True:
self.TKText.configure(state='disabled')
elif disabled is False:
self.TKText.configure(state='normal')
self.Disabled = disabled if disabled is not None else self.Disabled
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(background=background_color)
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(fg=text_color)
if font is not None:
self.TKText.configure(font=font)
if visible is False:
self._pack_forget_save_settings(alternate_widget=self.element_frame)
elif visible is True:
self._pack_restore_settings(alternate_widget=self.element_frame)
if self.AutoRefresh and self.ParentForm:
try: # in case the window was destroyed
self.ParentForm.refresh()
except:
pass
if visible is not None:
self._visible = visible
def get(self):
"""
Return current contents of the Multiline Element
:return: current contents of the Multiline Element (used as an input type of Multiline
:rtype: (str)
"""
value = str(self.TKText.get(1.0, tk.END))
if self.rstrip:
return value.rstrip()
return value
def print(
self,
*args,
end=None,
sep=None,
text_color=None,
background_color=None,
justification=None,
font=None,
colors=None,
t=None,
b=None,
c=None,
autoscroll=True,
):
"""
Print like Python normally prints except route the output to a multiline element and also add colors if desired
colors -(str, str) or str. A combined text/background color definition in a single parameter
There are also "aliases" for text_color, background_color and colors (t, b, c)
t - An alias for color of the text (makes for shorter calls)
b - An alias for the background_color parameter
c - (str, str) - "shorthand" way of specifying color. (foreground, backgrouned)
c - str - can also be a string of the format "foreground on background" ("white on red")
With the aliases it's possible to write the same print but in more compact ways:
cprint('This will print white text on red background', c=('white', 'red'))
cprint('This will print white text on red background', c='white on red')
cprint('This will print white text on red background', text_color='white', background_color='red')
cprint('This will print white text on red background', t='white', b='red')
:param args: The arguments to print
:type args: (Any)
:param end: The end char to use just like print uses
:type end: (str)
:param sep: The separation character like print uses
:type sep: (str)
:param text_color: The color of the text
:type text_color: (str)
:param background_color: The background color of the line
:type background_color: (str)
:param justification: text justification. left, right, center. Can use single characters l, r, c. Sets only for this value, not entire element
:type justification: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike for the args being printed
:type font: (str or (str, int[, str]) or None)
:param colors: Either a tuple or a string that has both the text and background colors. Or just the text color
:type colors: (str) or (str, str)
:param t: Color of the text
:type t: (str)
:param b: The background color of the line
:type b: (str)
:param c: Either a tuple or a string that has both the text and background colors or just tex color (same as the color parm)
:type c: (str) or (str, str)
:param autoscroll: If True the contents of the element will automatically scroll as more data added to the end
:type autoscroll: (bool)
"""
kw_text_color = text_color or t
kw_background_color = background_color or b
dual_color = colors or c
try:
if isinstance(dual_color, tuple):
kw_text_color = dual_color[0]
kw_background_color = dual_color[1]
elif isinstance(dual_color, str):
if ' on ' in dual_color: # if has "on" in the string, then have both text and background
kw_text_color = dual_color.split(' on ')[0]
kw_background_color = dual_color.split(' on ')[1]
else: # if no "on" then assume the color string is just the text color
kw_text_color = dual_color
except Exception as e:
print('* multiline print warning * you messed up with color formatting', e)
_print_to_element(
self,
*args,
end=end,
sep=sep,
text_color=kw_text_color,
background_color=kw_background_color,
justification=justification,
autoscroll=autoscroll,
font=font,
)
def reroute_stdout_to_here(self):
"""
Sends stdout (prints) to this element
"""
# if nothing on the stack, then need to save the very first stdout
if len(Window._rerouted_stdout_stack) == 0:
Window._original_stdout = sys.stdout
Window._rerouted_stdout_stack.insert(0, (self.ParentForm, self))
sys.stdout = self
def reroute_stderr_to_here(self):
"""
Sends stderr to this element
"""
if len(Window._rerouted_stderr_stack) == 0:
Window._original_stderr = sys.stderr
Window._rerouted_stderr_stack.insert(0, (self.ParentForm, self))
sys.stderr = self
def restore_stdout(self):
"""
Restore a previously re-reouted stdout back to the original destination
"""
Window._restore_stdout()
def restore_stderr(self):
"""
Restore a previously re-reouted stderr back to the original destination
"""
Window._restore_stderr()
def write(self, txt):
"""
Called by Python (not tkinter?) when stdout or stderr wants to write
:param txt: text of output
:type txt: (str)
"""
try:
self.update(txt, append=True)
# if need to echo, then send the same text to the destinatoin that isn't thesame as this one
if self.echo_stdout_stderr:
if sys.stdout != self:
sys.stdout.write(txt)
elif sys.stderr != self:
sys.stderr.write(txt)
except:
pass
def flush(self):
"""
Flush parameter was passed into a print statement.
For now doing nothing. Not sure what action should be taken to ensure a flush happens regardless.
"""
# try:
# self.previous_stdout.flush()
# except:
# pass
return
def set_ibeam_color(self, ibeam_color=None):
"""
Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by
many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term
ibeam is used in this case.
:param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted
:type ibeam_color: (str)
"""
if not self._widget_was_created():
return
if ibeam_color is not None:
try:
self.Widget.config(insertbackground=ibeam_color)
except Exception:
_error_popup_with_traceback(
'Error setting I-Beam color in set_ibeam_color',
'The element has a key:',
self.Key,
'The color passed in was:',
ibeam_color,
)
def __del__(self):
"""
AT ONE TIME --- If this Widget is deleted, be sure and restore the old stdout, stderr
Now the restore is done differently. Do not want to RELY on Python to call this method
in order for stdout and stderr to be restored. Instead explicit restores are called.
"""
return
Get = get
Update = update
class Output(Multiline):
"""
Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted.
The Output Element is now based on the Multiline Element. When you make an Output Element, you're
creating a Multiline Element with some specific settings set:
auto_refresh = True
auto_scroll = True
reroute_stdout = True
reroute_stderr = True
reroute_cprint = True
write_only = True
If you choose to use a Multiline element to replace an Output element, be sure an turn on the write_only paramter in the Multiline
so that an item is not included in the values dictionary on every window.read call
"""
def __init__(
self,
size=(None, None),
s=(None, None),
background_color=None,
text_color=None,
pad=None,
p=None,
autoscroll_only_at_bottom=False,
echo_stdout_stderr=False,
font=None,
tooltip=None,
key=None,
k=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
wrap_lines=None,
horizontal_scroll=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
):
"""
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param autoscroll_only_at_bottom: If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline
:type autoscroll_only_at_bottom: (bool)
:param echo_stdout_stderr: If True then output to stdout will be output to this element AND also to the normal console location
:type echo_stdout_stderr: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
:param wrap_lines: If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping.
:type wrap_lines: (bool)
:param horizontal_scroll: Controls if a horizontal scrollbar should be shown. If True, then line wrapping will be off by default
:type horizontal_scroll: (bool)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
"""
super().__init__(
size=size,
s=s,
background_color=background_color,
autoscroll_only_at_bottom=autoscroll_only_at_bottom,
text_color=text_color,
pad=pad,
p=p,
echo_stdout_stderr=echo_stdout_stderr,
font=font,
tooltip=tooltip,
wrap_lines=wrap_lines,
horizontal_scroll=horizontal_scroll,
key=key,
k=k,
right_click_menu=right_click_menu,
write_only=True,
reroute_stdout=True,
reroute_stderr=True,
reroute_cprint=True,
autoscroll=True,
auto_refresh=True,
expand_x=expand_x,
expand_y=expand_y,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)

View File

@ -0,0 +1,168 @@
from __future__ import annotations
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_INPUT_OPTION_MENU
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
class OptionMenu(Element):
"""
Option Menu is an Element available ONLY on the tkinter port of PySimpleGUI. It is a widget that is unique
to tkinter. However, it looks much like a ComboBox. Instead of an arrow to click to pull down the list of
choices, another little graphic is shown on the widget to indicate where you click. After clicking to activate,
it looks like a Combo Box that you scroll to select a choice.
"""
def __init__(
self,
values,
default_value=None,
size=(None, None),
s=(None, None),
disabled=False,
auto_size_text=None,
expand_x=False,
expand_y=False,
background_color=None,
text_color=None,
key=None,
k=None,
pad=None,
p=None,
tooltip=None,
visible=True,
metadata=None,
):
"""
:param values: Values to be displayed
:type values: List[Any] or Tuple[Any]
:param default_value: the value to choose by default
:type default_value: (Any)
:param size: (width, height) size in characters (wide), height is ignored and present to be consistent with other elements
:type size: (int, int) (width, UNUSED)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param disabled: control enabled / disabled
:type disabled: (bool)
:param auto_size_text: True if size of Element should match the contents of the items
:type auto_size_text: (bool)
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param tooltip: text that will appear when mouse hovers over this element
:type tooltip: (str)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.Values = values
self.DefaultValue = default_value
self.Widget = self.TKOptionMenu = None # type: tk.OptionMenu
self.Disabled = disabled
bg = background_color if background_color else FreeSimpleGUI.DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else FreeSimpleGUI.DEFAULT_INPUT_TEXT_COLOR
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_INPUT_OPTION_MENU,
size=sz,
auto_size_text=auto_size_text,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
def update(self, value=None, values=None, disabled=None, visible=None, size=(None, None)):
"""
Changes some of the settings for the OptionMenu Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: the value to choose by default
:type value: (Any)
:param values: Values to be displayed
:type values: List[Any]
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param visible: control visibility of element
:type visible: (bool)
:param size: (width, height) size in characters (wide), height is ignored and present to be consistent with other elements
:type size: (int, int) (width, UNUSED)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in OptionMenu.update - The window was closed')
return
if values is not None:
self.Values = values
self.TKOptionMenu['menu'].delete(0, 'end')
# Insert list of new options (tk._setit hooks them up to var)
# self.TKStringVar.set(self.Values[0])
for new_value in self.Values:
self.TKOptionMenu['menu'].add_command(label=new_value, command=tk._setit(self.TKStringVar, new_value))
if value is None:
self.TKStringVar.set('')
if size == (None, None):
max_line_len = max([len(str(line)) for line in self.Values]) if len(self.Values) else 0
if self.AutoSizeText is False:
width = self.Size[0]
else:
width = max_line_len + 1
self.TKOptionMenu.configure(width=width)
else:
self.TKOptionMenu.configure(width=size[0])
if value is not None:
self.DefaultValue = value
self.TKStringVar.set(value)
if disabled is True:
self.TKOptionMenu['state'] = 'disabled'
elif disabled is False:
self.TKOptionMenu['state'] = 'normal'
self.Disabled = disabled if disabled is not None else self.Disabled
if visible is False:
self._pack_forget_save_settings()
# self.TKOptionMenu.pack_forget()
elif visible is True:
self._pack_restore_settings()
# self.TKOptionMenu.pack(padx=self.pad_used[0], pady=self.pad_used[1])
if visible is not None:
self._visible = visible
Update = update

View File

@ -0,0 +1,127 @@
from __future__ import annotations
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_PANE
from FreeSimpleGUI import Element
from FreeSimpleGUI import RELIEF_RAISED
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Pane(Element):
"""
A sliding Pane that is unique to tkinter. Uses Columns to create individual panes
"""
def __init__(
self,
pane_list,
background_color=None,
size=(None, None),
s=(None, None),
pad=None,
p=None,
orientation='vertical',
show_handle=True,
relief=RELIEF_RAISED,
handle_size=None,
border_width=None,
key=None,
k=None,
expand_x=None,
expand_y=None,
visible=True,
metadata=None,
):
"""
:param pane_list: Must be a list of Column Elements. Each Column supplied becomes one pane that's shown
:type pane_list: List[Column] | Tuple[Column]
:param background_color: color of background
:type background_color: (str)
:param size: (width, height) w=characters-wide, h=rows-high How much room to reserve for the Pane
:type size: (int, int)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param orientation: 'horizontal' or 'vertical' or ('h' or 'v'). Direction the Pane should slide
:type orientation: (str)
:param show_handle: if True, the handle is drawn that makes it easier to grab and slide
:type show_handle: (bool)
:param relief: relief style. Values are same as other elements that use relief values. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
:type relief: (enum)
:param handle_size: Size of the handle in pixels
:type handle_size: (int)
:param border_width: width of border around element in pixels
:type border_width: (int)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param expand_x: If True the column will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the column will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.Rows = []
self.TKFrame = None
self.PanedWindow = None
self.Orientation = orientation
self.PaneList = pane_list
self.ShowHandle = show_handle
self.Relief = relief
self.HandleSize = handle_size or 8
self.BorderDepth = border_width
bg = background_color if background_color is not None else FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.Rows = [pane_list]
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(ELEM_TYPE_PANE, background_color=bg, size=sz, pad=pad, key=key, visible=visible, metadata=metadata)
return
def update(self, visible=None):
"""
Changes some of the settings for the Pane Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Pane.update - The window was closed')
return
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
Update = update

View File

@ -0,0 +1,304 @@
from __future__ import annotations
import tkinter as tk
from tkinter import ttk
import FreeSimpleGUI
from FreeSimpleGUI import _change_ttk_theme
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_PROGRESS_BAR
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.elements.helpers import _simplified_dual_color_to_tuple
from FreeSimpleGUI.window import Window
class TKProgressBar:
uniqueness_counter = 0
def __init__(
self,
root,
max,
length=400,
width=FreeSimpleGUI.DEFAULT_PROGRESS_BAR_SIZE[1],
ttk_theme=FreeSimpleGUI.DEFAULT_TTK_THEME,
style_name='',
relief=FreeSimpleGUI.DEFAULT_PROGRESS_BAR_RELIEF,
border_width=FreeSimpleGUI.DEFAULT_PROGRESS_BAR_BORDER_WIDTH,
orientation='horizontal',
BarColor=(None, None),
key=None,
):
"""
:param root: The root window bar is to be shown in
:type root: tk.Tk | tk.TopLevel
:param max: Maximum value the bar will be measuring
:type max: (int)
:param length: length in pixels of the bar
:type length: (int)
:param width: width in pixels of the bar
:type width: (int)
:param style_name: Progress bar style to use. Set in the packer function
:type style_name: (str)
:param ttk_theme: Progress bar style defined as one of these 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative'
:type ttk_theme: (str)
:param relief: relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID` (Default value = DEFAULT_PROGRESS_BAR_RELIEF)
:type relief: (str)
:param border_width: The amount of pixels that go around the outside of the bar
:type border_width: (int)
:param orientation: 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical')
:type orientation: (str)
:param BarColor: The 2 colors that make up a progress bar. One is the background, the other is the bar
:type BarColor: (str, str)
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
"""
self.Length = length
self.Width = width
self.Max = max
self.Orientation = orientation
self.Count = None
self.PriorCount = 0
self.style_name = style_name
TKProgressBar.uniqueness_counter += 1
if orientation.lower().startswith('h'):
s = ttk.Style()
_change_ttk_theme(s, ttk_theme)
if BarColor != COLOR_SYSTEM_DEFAULT and BarColor[0] != COLOR_SYSTEM_DEFAULT:
s.configure(
self.style_name,
background=BarColor[0],
troughcolor=BarColor[1],
troughrelief=relief,
borderwidth=border_width,
thickness=width,
)
else:
s.configure(self.style_name, troughrelief=relief, borderwidth=border_width, thickness=width)
self.TKProgressBarForReal = ttk.Progressbar(root, maximum=self.Max, style=self.style_name, length=length, orient=tk.HORIZONTAL, mode='determinate')
else:
s = ttk.Style()
_change_ttk_theme(s, ttk_theme)
if BarColor != COLOR_SYSTEM_DEFAULT and BarColor[0] != COLOR_SYSTEM_DEFAULT:
s.configure(
self.style_name,
background=BarColor[0],
troughcolor=BarColor[1],
troughrelief=relief,
borderwidth=border_width,
thickness=width,
)
else:
s.configure(self.style_name, troughrelief=relief, borderwidth=border_width, thickness=width)
self.TKProgressBarForReal = ttk.Progressbar(root, maximum=self.Max, style=self.style_name, length=length, orient=tk.VERTICAL, mode='determinate')
def Update(self, count=None, max=None):
"""
Update the current value of the bar and/or update the maximum value the bar can reach
:param count: current value
:type count: (int)
:param max: the maximum value
:type max: (int)
"""
if max is not None:
self.Max = max
try:
self.TKProgressBarForReal.config(maximum=max)
except:
return False
if count is not None:
try:
self.TKProgressBarForReal['value'] = count
except:
return False
return True
class ProgressBar(Element):
"""
Progress Bar Element - Displays a colored bar that is shaded as progress of some operation is made
"""
def __init__(
self,
max_value,
orientation=None,
size=(None, None),
s=(None, None),
size_px=(None, None),
auto_size_text=None,
bar_color=None,
style=None,
border_width=None,
relief=None,
key=None,
k=None,
pad=None,
p=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param max_value: max value of progressbar
:type max_value: (int)
:param orientation: 'horizontal' or 'vertical'
:type orientation: (str)
:param size: Size of the bar. If horizontal (chars long, pixels wide), vert (chars high, pixels wide). Vert height measured using horizontal chars units.
:type size: (int, int) | (int, None)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None)
:param size_px: Size in pixels (length, width). Will be used in place of size parm if specified
:type size_px: (int, int) | (None, None)
:param auto_size_text: Not sure why this is here
:type auto_size_text: (bool)
:param bar_color: The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background.
:type bar_color: (str, str) or str
:param style: Progress bar style defined as one of these 'default', 'winnative', 'clam', 'alt', 'classic', 'vista', 'xpnative'
:type style: (str)
:param border_width: The amount of pixels that go around the outside of the bar
:type border_width: (int)
:param relief: relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID` (Default value = DEFAULT_PROGRESS_BAR_RELIEF)
:type relief: (str)
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.MaxValue = max_value
self.TKProgressBar = None # type: TKProgressBar
self.Cancelled = False
self.NotRunning = True
self.Orientation = orientation if orientation else FreeSimpleGUI.DEFAULT_METER_ORIENTATION
self.RightClickMenu = right_click_menu
# Progress Bar colors can be a tuple (text, background) or a string with format "bar on background" - examples "red on white" or ("red", "white")
if bar_color is None:
bar_color = FreeSimpleGUI.DEFAULT_PROGRESS_BAR_COLOR
else:
bar_color = _simplified_dual_color_to_tuple(bar_color, default=FreeSimpleGUI.DEFAULT_PROGRESS_BAR_COLOR)
self.BarColor = bar_color # should be a tuple at this point
self.BarStyle = style if style else FreeSimpleGUI.DEFAULT_TTK_THEME
self.BorderWidth = border_width if border_width else FreeSimpleGUI.DEFAULT_PROGRESS_BAR_BORDER_WIDTH
self.Relief = relief if relief else FreeSimpleGUI.DEFAULT_PROGRESS_BAR_RELIEF
self.BarExpired = False
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.size_px = size_px
super().__init__(
ELEM_TYPE_PROGRESS_BAR,
size=sz,
auto_size_text=auto_size_text,
key=key,
pad=pad,
visible=visible,
metadata=metadata,
)
# returns False if update failed
def update_bar(self, current_count, max=None):
"""
DEPRECATED BUT STILL USABLE - has been combined with the normal ProgressBar.update method.
Change what the bar shows by changing the current count and optionally the max count
:param current_count: sets the current value
:type current_count: (int)
:param max: changes the max value
:type max: (int)
"""
if self.ParentForm.TKrootDestroyed:
return False
self.TKProgressBar.Update(current_count, max=max)
try:
self.ParentForm.TKroot.update()
except:
Window._DecrementOpenCount()
# _my_windows.Decrement()
return False
return True
def update(self, current_count=None, max=None, bar_color=None, visible=None):
"""
Changes some of the settings for the ProgressBar Element. Must call `Window.Read` or `Window.Finalize` prior
Now has the ability to modify the count so that the update_bar method is not longer needed separately
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param current_count: sets the current value
:type current_count: (int)
:param max: changes the max value
:type max: (int)
:param bar_color: The 2 colors that make up a progress bar. Easy to remember which is which if you say "ON" between colors. "red" on "green".
:type bar_color: (str, str) or str
:param visible: control visibility of element
:type visible: (bool)
:return: Returns True if update was OK. False means something wrong with window or it was closed
:rtype: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return False
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in ProgressBar.update - The window was closed')
return
if self.ParentForm.TKrootDestroyed:
return False
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
if bar_color is not None:
bar_color = _simplified_dual_color_to_tuple(bar_color, default=FreeSimpleGUI.DEFAULT_PROGRESS_BAR_COLOR)
self.BarColor = bar_color
style = ttk.Style()
style.configure(self.ttk_style_name, background=bar_color[0], troughcolor=bar_color[1])
if current_count is not None:
self.TKProgressBar.Update(current_count, max=max)
try:
self.ParentForm.TKroot.update()
except:
return False
return True
Update = update
UpdateBar = update_bar

View File

@ -0,0 +1,252 @@
from __future__ import annotations
import tkinter as tk # noqa
from FreeSimpleGUI import _hex_to_hsl
from FreeSimpleGUI import _hsl_to_rgb
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_INPUT_RADIO
from FreeSimpleGUI import Element
from FreeSimpleGUI import rgb
from FreeSimpleGUI import theme_background_color
from FreeSimpleGUI import theme_text_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
class Radio(Element):
"""
Radio Button Element - Used in a group of other Radio Elements to provide user with ability to select only
1 choice in a list of choices.
"""
def __init__(
self,
text,
group_id,
default=False,
disabled=False,
size=(None, None),
s=(None, None),
auto_size_text=None,
background_color=None,
text_color=None,
circle_color=None,
font=None,
key=None,
k=None,
pad=None,
p=None,
tooltip=None,
change_submits=False,
enable_events=False,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param text: Text to display next to button
:type text: (str)
:param group_id: Groups together multiple Radio Buttons. Any type works
:type group_id: (Any)
:param default: Set to True for the one element of the group you want initially selected
:type default: (bool)
:param disabled: set disable state
:type disabled: (bool)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: if True will size the element to match the length of the text
:type auto_size_text: (bool)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param circle_color: color of background of the circle that has the dot selection indicator in it
:type circle_color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Radio Button events happen when an item is selected
:type enable_events: (bool)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.InitialState = default
self.Text = text
self.Widget = self.TKRadio = None # type: tk.Radiobutton
self.GroupID = group_id
self.Value = None
self.Disabled = disabled
self.TextColor = text_color if text_color else theme_text_color()
self.RightClickMenu = right_click_menu
if circle_color is None:
# ---- compute color of circle background ---
try: # something in here will fail if a color is not specified in Hex
text_hsl = _hex_to_hsl(self.TextColor)
background_hsl = _hex_to_hsl(background_color if background_color else theme_background_color())
l_delta = abs(text_hsl[2] - background_hsl[2]) / 10
if text_hsl[2] > background_hsl[2]: # if the text is "lighter" than the background then make background darker
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] - l_delta)
else:
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] + l_delta)
self.CircleBackgroundColor = rgb(*bg_rbg)
except:
self.CircleBackgroundColor = background_color if background_color else theme_background_color()
else:
self.CircleBackgroundColor = circle_color
self.ChangeSubmits = change_submits or enable_events
self.EncodedRadioValue = None
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_INPUT_RADIO,
size=sz,
auto_size_text=auto_size_text,
font=font,
background_color=background_color,
text_color=self.TextColor,
key=key,
pad=pad,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
def update(
self,
value=None,
text=None,
background_color=None,
text_color=None,
circle_color=None,
disabled=None,
visible=None,
):
"""
Changes some of the settings for the Radio Button Element. Must call `Window.read` or `Window.finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: if True change to selected and set others in group to unselected
:type value: (bool)
:param text: Text to display next to radio button
:type text: (str)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text. Note this also changes the color of the selection dot
:type text_color: (str)
:param circle_color: color of background of the circle that has the dot selection indicator in it
:type circle_color: (str)
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Radio.update - The window was closed')
return
if value is not None:
try:
if value is True:
self.TKIntVar.set(self.EncodedRadioValue)
elif value is False:
if self.TKIntVar.get() == self.EncodedRadioValue:
self.TKIntVar.set(0)
except:
print('Error updating Radio')
self.InitialState = value
if text is not None:
self.Text = str(text)
self.TKRadio.configure(text=self.Text)
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKRadio.configure(background=background_color)
self.BackgroundColor = background_color
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKRadio.configure(fg=text_color)
self.TextColor = text_color
if circle_color not in (None, COLOR_SYSTEM_DEFAULT):
self.CircleBackgroundColor = circle_color
self.TKRadio.configure(selectcolor=self.CircleBackgroundColor) # The background of the radio button
elif text_color or background_color:
if self.TextColor not in (None, COLOR_SYSTEM_DEFAULT) and self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT) and self.TextColor.startswith('#') and self.BackgroundColor.startswith('#'):
# ---- compute color of circle background ---
text_hsl = _hex_to_hsl(self.TextColor)
background_hsl = _hex_to_hsl(self.BackgroundColor if self.BackgroundColor else theme_background_color())
l_delta = abs(text_hsl[2] - background_hsl[2]) / 10
if text_hsl[2] > background_hsl[2]: # if the text is "lighter" than the background then make background darker
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] - l_delta)
else:
bg_rbg = _hsl_to_rgb(background_hsl[0], background_hsl[1], background_hsl[2] + l_delta)
self.CircleBackgroundColor = rgb(*bg_rbg)
self.TKRadio.configure(selectcolor=self.CircleBackgroundColor) # The background of the checkbox
if disabled is True:
self.TKRadio['state'] = 'disabled'
elif disabled is False:
self.TKRadio['state'] = 'normal'
self.Disabled = disabled if disabled is not None else self.Disabled
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
def reset_group(self):
"""
Sets all Radio Buttons in the group to not selected
"""
self.TKIntVar.set(0)
def get(self):
# type: (Radio) -> bool
"""
A snapshot of the value of Radio Button -> (bool)
:return: True if this radio button is selected
:rtype: (bool)
"""
return self.TKIntVar.get() == self.EncodedRadioValue
Get = get
ResetGroup = reset_group
Update = update

View File

@ -0,0 +1,62 @@
from __future__ import annotations
from FreeSimpleGUI import ELEM_TYPE_SEPARATOR
from FreeSimpleGUI import Element
from FreeSimpleGUI import theme_text_color
class VerticalSeparator(Element):
"""
Vertical Separator Element draws a vertical line at the given location. It will span 1 "row". Usually paired with
Column Element if extra height is needed
"""
def __init__(self, color=None, pad=None, p=None, key=None, k=None):
"""
:param color: Color of the line. Defaults to theme's text color. Can be name or #RRGGBB format
:type color: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
"""
key = key if key is not None else k
pad = pad if pad is not None else p
self.expand_x = None
self.expand_y = None
self.Orientation = 'vertical' # for now only vertical works
self.color = color if color is not None else theme_text_color()
super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, key=key)
class HorizontalSeparator(Element):
"""
Horizontal Separator Element draws a Horizontal line at the given location.
"""
def __init__(self, color=None, pad=None, p=None, key=None, k=None):
"""
:param color: Color of the line. Defaults to theme's text color. Can be name or #RRGGBB format
:type color: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
"""
self.Orientation = 'horizontal' # for now only vertical works
self.color = color if color is not None else theme_text_color()
self.expand_x = True
self.expand_y = None
key = key if key is not None else k
pad = pad if pad is not None else p
super().__init__(ELEM_TYPE_SEPARATOR, pad=pad, key=key)

View File

@ -0,0 +1,34 @@
from __future__ import annotations
from FreeSimpleGUI import ELEM_TYPE_SIZEGRIP
from FreeSimpleGUI import Element
from FreeSimpleGUI import theme_background_color
class Sizegrip(Element):
"""
Sizegrip element will be added to the bottom right corner of your window.
It should be placed on the last row of your window along with any other elements on that row.
The color will match the theme's background color.
"""
def __init__(self, background_color=None, pad=None, p=(0, 0), key=None, k=None):
"""
Sizegrip Element
:param background_color: color to use for the background of the grip
:type background_color: str
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
"""
bg = background_color if background_color is not None else theme_background_color()
pad = pad if pad is not None else p
key = key if key is not None else k
super().__init__(ELEM_TYPE_SIZEGRIP, background_color=bg, key=key, pad=pad)

View File

@ -0,0 +1,199 @@
from __future__ import annotations
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_INPUT_SLIDER
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
class Slider(Element):
"""
A slider, horizontal or vertical
"""
def __init__(
self,
range=(None, None),
default_value=None,
resolution=None,
tick_interval=None,
orientation=None,
disable_number_display=False,
border_width=None,
relief=None,
change_submits=False,
enable_events=False,
disabled=False,
size=(None, None),
s=(None, None),
font=None,
background_color=None,
text_color=None,
trough_color=None,
key=None,
k=None,
pad=None,
p=None,
expand_x=False,
expand_y=False,
tooltip=None,
visible=True,
metadata=None,
):
"""
:param range: slider's range (min value, max value)
:type range: (int, int) | Tuple[float, float]
:param default_value: starting value for the slider
:type default_value: int | float
:param resolution: the smallest amount the slider can be moved
:type resolution: int | float
:param tick_interval: how often a visible tick should be shown next to slider
:type tick_interval: int | float
:param orientation: 'horizontal' or 'vertical' ('h' or 'v' also work)
:type orientation: (str)
:param disable_number_display: if True no number will be displayed by the Slider Element
:type disable_number_display: (bool)
:param border_width: width of border around element in pixels
:type border_width: (int)
:param relief: relief style. Use constants - RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
:type relief: str | None
:param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead
:type change_submits: (bool)
:param enable_events: If True then moving the slider will generate an Event
:type enable_events: (bool)
:param disabled: set disable state for element
:type disabled: (bool)
:param size: (l=length chars/rows, w=width pixels)
:type size: (int, int)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param background_color: color of slider's background
:type background_color: (str)
:param text_color: color of the slider's text
:type text_color: (str)
:param trough_color: color of the slider's trough
:type trough_color: (str)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.TKScale = self.Widget = None # type: tk.Scale
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 FreeSimpleGUI.DEFAULT_SLIDER_ORIENTATION
self.BorderWidth = border_width if border_width else FreeSimpleGUI.DEFAULT_SLIDER_BORDER_WIDTH
self.Relief = relief if relief else FreeSimpleGUI.DEFAULT_SLIDER_RELIEF
self.Resolution = 1 if resolution is None else resolution
self.ChangeSubmits = change_submits or enable_events
self.Disabled = disabled
self.TickInterval = tick_interval
self.DisableNumericDisplay = disable_number_display
self.TroughColor = trough_color or FreeSimpleGUI.DEFAULT_SCROLLBAR_COLOR
sz = size if size != (None, None) else s
temp_size = sz
if temp_size == (None, None):
temp_size = (20, 20) if self.Orientation.startswith('h') else (8, 20)
key = key if key is not None else k
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
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,
visible=visible,
metadata=metadata,
)
return
def update(self, value=None, range=(None, None), disabled=None, visible=None):
"""
Changes some of the settings for the Slider Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: sets current slider value
:type value: int | float
:param range: Sets a new range for slider
:type range: (int, int) | Tuple[float, float
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Slider.update - The window was closed')
return
if range != (None, None):
self.TKScale.config(from_=range[0], to_=range[1])
if value is not None:
try:
self.TKIntVar.set(value)
except:
pass
self.DefaultValue = value
if disabled is True:
self.TKScale['state'] = 'disabled'
elif disabled is False:
self.TKScale['state'] = 'normal'
self.Disabled = disabled if disabled is not None else self.Disabled
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
def _SliderChangedHandler(self, event):
"""
Not user callable. Callback function for when slider is moved.
:param event: (event) the event data provided by tkinter. Unknown format. Not used.
:type event:
"""
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
Update = update

View File

@ -0,0 +1,252 @@
from __future__ import annotations
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_INPUT_SPIN
from FreeSimpleGUI import Element
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
class Spin(Element):
"""
A spinner with up/down buttons and a single line of text. Choose 1 values from list
"""
def __init__(
self,
values,
initial_value=None,
disabled=False,
change_submits=False,
enable_events=False,
readonly=False,
size=(None, None),
s=(None, None),
auto_size_text=None,
bind_return_key=None,
font=None,
background_color=None,
text_color=None,
key=None,
k=None,
pad=None,
p=None,
wrap=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param values: List of valid values
:type values: Tuple[Any] or List[Any]
:param initial_value: Initial item to show in window. Choose from list of values supplied
:type initial_value: (Any)
:param disabled: set disable state
:type disabled: (bool)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Spin events happen when an item changes
:type enable_events: (bool)
:param readonly: If True, then users cannot type in values. Only values from the values list are allowed.
:type readonly: (bool)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (None, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: if True will size the element to match the length of the text
:type auto_size_text: (bool)
:param bind_return_key: If True, then the return key will cause a the element to generate an event when return key is pressed
:type bind_return_key: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param key: Used with window.find_element and with return values to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param wrap: Determines if the values should "Wrap". Default is False. If True, when reaching last value, will continue back to the first value.
:type wrap: (bool)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.Values = values
self.DefaultValue = initial_value
self.ChangeSubmits = change_submits or enable_events
self.TKSpinBox = self.Widget = None # type: tk.Spinbox
self.Disabled = disabled
self.Readonly = readonly
self.RightClickMenu = right_click_menu
self.BindReturnKey = bind_return_key
self.wrap = wrap
bg = background_color if background_color else FreeSimpleGUI.DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else FreeSimpleGUI.DEFAULT_INPUT_TEXT_COLOR
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_INPUT_SPIN,
size=sz,
auto_size_text=auto_size_text,
font=font,
background_color=bg,
text_color=fg,
key=key,
pad=pad,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def update(self, value=None, values=None, disabled=None, readonly=None, visible=None):
"""
Changes some of the settings for the Spin Element. Must call `Window.Read` or `Window.Finalize` prior
Note that the state can be in 3 states only.... enabled, disabled, readonly even
though more combinations are available. The easy way to remember is that if you
change the readonly parameter then you are enabling the element.
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: set the current value from list of choices
:type value: (Any)
:param values: set available choices
:type values: List[Any]
:param disabled: disable. Note disabled and readonly cannot be mixed. It must be one OR the other
:type disabled: (bool)
:param readonly: make element readonly. Note disabled and readonly cannot be mixed. It must be one OR the other
:type readonly: (bool)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Spin.update - The window was closed')
return
if values is not 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)
self.DefaultValue = value
except:
pass
if readonly is True:
self.Readonly = True
self.TKSpinBox['state'] = 'readonly'
elif readonly is False:
self.Readonly = False
self.TKSpinBox['state'] = 'normal'
if disabled is True:
self.TKSpinBox['state'] = 'disable'
elif disabled is False:
if self.Readonly:
self.TKSpinBox['state'] = 'readonly'
else:
self.TKSpinBox['state'] = 'normal'
self.Disabled = disabled if disabled is not None else self.Disabled
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
def _SpinChangedHandler(self, event):
"""
Callback function. Used internally only. Called by tkinter when Spinbox Widget changes. Results in Window.Read() call returning
:param event: passed in from tkinter
:type event:
"""
# first, get the results table built
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
# if self.ParentForm.CurrentlyRunningMainloop:
# Window._window_that_exited = self.ParentForm
# self.ParentForm.TKroot.quit() # kick the users out of the mainloop
def set_ibeam_color(self, ibeam_color=None):
"""
Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by
many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term
ibeam is used in this case.
:param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted
:type ibeam_color: (str)
"""
if not self._widget_was_created():
return
if ibeam_color is not None:
try:
self.Widget.config(insertbackground=ibeam_color)
except Exception:
_error_popup_with_traceback(
'Error setting I-Beam color in set_ibeam_color',
'The element has a key:',
self.Key,
'The color passed in was:',
ibeam_color,
)
def get(self):
"""
Return the current chosen value showing in spinbox.
This value will be the same as what was provided as list of choices. If list items are ints, then the
item returned will be an int (not a string)
:return: The currently visible entry
:rtype: (Any)
"""
value = self.TKStringVar.get()
for v in self.Values:
if str(v) == value:
value = v
break
return value
Get = get
Update = update

View File

@ -0,0 +1,165 @@
from __future__ import annotations
import tkinter as tk
import FreeSimpleGUI
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_STATUSBAR
from FreeSimpleGUI import Element
from FreeSimpleGUI import RELIEF_SUNKEN
from FreeSimpleGUI._utils import _error_popup_with_traceback
class StatusBar(Element):
"""
A StatusBar Element creates the sunken text-filled strip at the bottom. Many Windows programs have this line
"""
def __init__(
self,
text,
size=(None, None),
s=(None, None),
auto_size_text=None,
click_submits=None,
enable_events=False,
relief=RELIEF_SUNKEN,
font=None,
text_color=None,
background_color=None,
justification=None,
pad=None,
p=None,
key=None,
k=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
tooltip=None,
visible=True,
metadata=None,
):
"""
:param text: Text that is to be displayed in the widget
:type text: (str)
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (int, None) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (None, None) | int
:param auto_size_text: True if size should fit the text length
:type auto_size_text: (bool)
:param click_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type click_submits: (bool)
:param enable_events: Turns on the element specific events. StatusBar events occur when the bar is clicked
:type enable_events: (bool)
:param relief: relief style. Values are same as progress meter relief values. Can be a constant or a string: `RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID`
:type relief: (enum)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param text_color: color of the text
:type text_color: (str)
:param background_color: color of background
:type background_color: (str)
:param justification: how string should be aligned within space provided by size. Valid choices = `left`, `right`, `center`
:type justification: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.DisplayText = text
self.TextColor = text_color if text_color else FreeSimpleGUI.DEFAULT_TEXT_COLOR
self.Justification = justification
self.Relief = relief
self.ClickSubmits = click_submits or enable_events
if background_color is None:
bg = FreeSimpleGUI.DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR
else:
bg = background_color
self.TKText = self.Widget = None # type: tk.Label
key = key if key is not None else k
self.RightClickMenu = right_click_menu
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_STATUSBAR,
size=sz,
auto_size_text=auto_size_text,
background_color=bg,
font=font or FreeSimpleGUI.DEFAULT_FONT,
text_color=self.TextColor,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def update(self, value=None, background_color=None, text_color=None, font=None, visible=None):
"""
Changes some of the settings for the Status Bar Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: new text to show
:type value: (str)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param visible: set visibility state of the element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in StatusBar.update - The window was closed')
return
if value is not None:
self.DisplayText = value
stringvar = self.TKStringVar
stringvar.set(value)
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(background=background_color)
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(fg=text_color)
if font is not None:
self.TKText.configure(font=font)
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
Update = update

View File

@ -0,0 +1,26 @@
from __future__ import annotations
from FreeSimpleGUI.elements.text import Text
def Push(background_color=None):
"""
Acts like a Stretch element found in the Qt port.
Used in a Horizontal fashion. Placing one on each side of an element will enter the element.
Place one to the left and the element to the right will be right justified. See VStretch for vertical type
:param background_color: color of background may be needed because of how this is implemented
:type background_color: (str)
:return: (Text)
"""
return Text(font='_ 1', background_color=background_color, pad=(0, 0), expand_x=True)
def VPush(background_color=None):
"""
Acts like a Stretch element found in the Qt port.
Used in a Vertical fashion.
:param background_color: color of background may be needed because of how this is implemented
:type background_color: (str)
:return: (Text)
"""
return Text(font='_ 1', background_color=background_color, pad=(0, 0), expand_y=True)

View File

@ -0,0 +1,697 @@
from __future__ import annotations
import tkinter as tk
import warnings
from tkinter import ttk
import FreeSimpleGUI
from FreeSimpleGUI import _add_right_click_menu
from FreeSimpleGUI import _random_error_emoji
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_TAB
from FreeSimpleGUI import ELEM_TYPE_TAB_GROUP
from FreeSimpleGUI import Element
from FreeSimpleGUI import LOOK_AND_FEEL_TABLE
from FreeSimpleGUI import PackFormIntoFrame
from FreeSimpleGUI import popup_error
from FreeSimpleGUI import popup_error_with_traceback
from FreeSimpleGUI import ToolTip
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.window import Window
class Tab(Element):
"""
Tab Element is another "Container" element that holds a layout and displays a tab with text. Used with TabGroup only
Tabs are never placed directly into a layout. They are always "Contained" in a TabGroup layout
"""
def __init__(
self,
title,
layout,
title_color=None,
background_color=None,
font=None,
pad=None,
p=None,
disabled=False,
border_width=None,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
element_justification='left',
image_source=None,
image_subsample=None,
image_zoom=None,
metadata=None,
):
"""
:param title: text to show on the tab
:type title: (str)
:param layout: The element layout that will be shown in the tab
:type layout: List[List[Element]]
:param title_color: color of the tab text (note not currently working on tkinter)
:type title_color: (str)
:param background_color: color of background of the entire layout
:type background_color: (str)
:param font: NOT USED in the tkinter port
:type font: (str or (str, int[, str]) or None)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param disabled: If True button will be created disabled
:type disabled: (bool)
:param border_width: NOT USED in tkinter port
:type border_width: (int)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param element_justification: All elements inside the Tab will have this justification 'left', 'right', 'center' are valid values
:type element_justification: (str)
:param image_source: A filename or a base64 bytes of an image to place on the Tab
:type image_source: str | bytes | None
:param image_subsample: amount to reduce the size of the image. Divides the size by this number. 2=1/2, 3=1/3, 4=1/4, etc
:type image_subsample: (int)
:param image_zoom: amount to increase the size of the image. 2=twice size, 3=3 times, etc
:type image_zoom: (int)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
filename = data = None
if image_source is not None:
if isinstance(image_source, bytes):
data = image_source
elif isinstance(image_source, str):
filename = image_source
else:
warnings.warn(f'Image element - source is not a valid type: {type(image_source)}', UserWarning)
self.Filename = filename
self.Data = data
self.ImageSubsample = image_subsample
self.zoom = int(image_zoom) if image_zoom is not None else None
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.Rows = []
self.TKFrame = None
self.Widget = None # type: tk.Frame
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 FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.RightClickMenu = right_click_menu
self.ContainerElemementNumber = Window._GetAContainerNumber()
self.ElementJustification = element_justification
key = key if key is not None else k
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.Layout(layout)
super().__init__(
ELEM_TYPE_TAB,
background_color=background_color,
text_color=title_color,
font=font,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def add_row(self, *args):
"""
Not recommended use call. Used to add rows of Elements to the Frame Element.
:param *args: The list of elements for this row
:type *args: List[Element]
"""
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
if type(element) is list:
popup_error_with_traceback(
'Error creating Tab layout',
'Layout has a LIST instead of an ELEMENT',
'This sometimes means you have a badly placed ]',
'The offensive list is:',
element,
'This list will be stripped from your layout',
)
continue
elif callable(element) and not isinstance(element, Element):
popup_error_with_traceback(
'Error creating Tab layout',
'Layout has a FUNCTION instead of an ELEMENT',
'This likely means you are missing () from your layout',
'The offensive list is:',
element,
'This item will be stripped from your layout',
)
continue
if element.ParentContainer is not None:
warnings.warn(
'*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***',
UserWarning,
)
popup_error_with_traceback(
'Error creating Tab layout',
'The layout specified has already been used',
'You MUST start witha "clean", unused layout every time you create a window',
'The offensive Element = ',
element,
'and has a key = ',
element.Key,
'This item will be stripped from your layout',
'Hint - try printing your layout and matching the IDs "print(layout)"',
)
continue
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):
"""
Not user callable. Use layout parameter instead. Creates the layout using the supplied rows of Elements
:param rows: List[List[Element]] The list of rows
:type rows: List[List[Element]]
:return: (Tab) used for chaining
:rtype:
"""
for row in rows:
try:
iter(row)
except TypeError:
popup_error(
'Error creating Tab layout',
'Your row is not an iterable (e.g. a list)',
f'Instead of a list, the type found was {type(row)}',
'The offensive row = ',
row,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
self.AddRow(*row)
return self
def update(self, title=None, disabled=None, visible=None):
"""
Changes some of the settings for the Tab Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param title: tab title
:type title: (str)
:param disabled: disable or enable state of the element
:type disabled: (bool)
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Tab.update - The window was closed')
return
state = 'normal'
if disabled is not None:
self.Disabled = disabled
if disabled:
state = 'disabled'
if visible is False:
state = 'hidden'
if visible is not None:
self._visible = visible
self.ParentNotebook.tab(self.TabID, state=state)
if title is not None:
self.Title = str(title)
self.ParentNotebook.tab(self.TabID, text=self.Title)
return self
def _GetElementAtLocation(self, location):
"""
Not user callable. Used to find the Element at a row, col position within the layout
:param location: (row, column) position of the element to find in layout
:type location: (int, int)
:return: The element found at the location
:rtype: (Element)
"""
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def select(self):
"""
Create a tkinter event that mimics user clicking on a tab. Must have called window.Finalize / Read first!
"""
# Use a try in case the window has been destoyed
try:
self.ParentNotebook.select(self.TabID)
except Exception as e:
print(f'Exception Selecting Tab {e}')
AddRow = add_row
Layout = layout
Select = select
Update = update
class TabGroup(Element):
"""
TabGroup Element groups together your tabs into the group of tabs you see displayed in your window
"""
def __init__(
self,
layout,
tab_location=None,
title_color=None,
tab_background_color=None,
selected_title_color=None,
selected_background_color=None,
background_color=None,
focus_color=None,
font=None,
change_submits=False,
enable_events=False,
pad=None,
p=None,
border_width=None,
tab_border_width=None,
theme=None,
key=None,
k=None,
size=(None, None),
s=(None, None),
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param layout: Layout of Tabs. Different than normal layouts. ALL Tabs should be on first row
:type layout: List[List[Tab]]
:param tab_location: location that tabs will be displayed. Choices are left, right, top, bottom, lefttop, leftbottom, righttop, rightbottom, bottomleft, bottomright, topleft, topright
:type tab_location: (str)
:param title_color: color of text on tabs
:type title_color: (str)
:param tab_background_color: color of all tabs that are not selected
:type tab_background_color: (str)
:param selected_title_color: color of tab text when it is selected
:type selected_title_color: (str)
:param selected_background_color: color of tab when it is selected
:type selected_background_color: (str)
:param background_color: color of background area that tabs are located on
:type background_color: (str)
:param focus_color: color of focus indicator on the tabs
:type focus_color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param change_submits: * DEPRICATED DO NOT USE. Use `enable_events` instead
:type change_submits: (bool)
:param enable_events: If True then switching tabs will generate an Event
:type enable_events: (bool)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param border_width: width of border around element in pixels
:type border_width: (int)
:param tab_border_width: width of border around the tabs
:type tab_border_width: (int)
:param theme: DEPRICATED - You can only specify themes using set options or when window is created. It's not possible to do it on an element basis
:type theme: (enum)
:param key: Value that uniquely identifies this element from all other elements. Used when Finding an element or in return values. Must be unique to the window
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param size: (width, height) w=pixels-wide, h=pixels-high. Either item in tuple can be None to indicate use the computed value and set only 1 direction
:type size: (int|None, int|None)
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int|None, int|None)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: DEPRECATED - Should you need to control visiblity for the TabGroup as a whole, place it into a Column element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.UseDictionary = False
self.ReturnValues = None
self.ReturnValuesList = []
self.ReturnValuesDictionary = {}
self.DictionaryKeyCounter = 0
self.ParentWindow = None
self.SelectedTitleColor = selected_title_color if selected_title_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['TEXT']
self.SelectedBackgroundColor = selected_background_color if selected_background_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['BACKGROUND']
title_color = title_color if title_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['TEXT_INPUT']
self.TabBackgroundColor = tab_background_color if tab_background_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['INPUT']
self.Rows = []
self.TKNotebook = None # type: ttk.Notebook
self.Widget = None # type: ttk.Notebook
self.tab_index_to_key = {} # has a list of the tabs in the notebook and their associated key
self.TabCount = 0
self.BorderWidth = border_width
self.BackgroundColor = background_color if background_color is not None else FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.ChangeSubmits = change_submits or enable_events
self.TabLocation = tab_location
self.ElementJustification = 'left'
self.RightClickMenu = right_click_menu
self.TabBorderWidth = tab_border_width
self.FocusColor = focus_color
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
self.Layout(layout)
super().__init__(
ELEM_TYPE_TAB_GROUP,
size=sz,
background_color=background_color,
text_color=title_color,
font=font,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
return
def add_row(self, *args):
"""
Not recommended user call. Used to add rows of Elements to the Frame Element.
:param *args: The list of elements for this row
:type *args: List[Element]
"""
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
if type(element) is list:
popup_error(
'Error creating Tab layout',
'Layout has a LIST instead of an ELEMENT',
'This sometimes means you have a badly placed ]',
'The offensive list is:',
element,
'This list will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
elif callable(element) and not isinstance(element, Element):
popup_error(
'Error creating Tab layout',
'Layout has a FUNCTION instead of an ELEMENT',
'This likely means you are missing () from your layout',
'The offensive list is:',
element,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
if element.ParentContainer is not None:
warnings.warn(
'*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***',
UserWarning,
)
popup_error(
'Error creating Tab layout',
'The layout specified has already been used',
'You MUST start witha "clean", unused layout every time you create a window',
'The offensive Element = ',
element,
'and has a key = ',
element.Key,
'This item will be stripped from your layout',
'Hint - try printing your layout and matching the IDs "print(layout)"',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
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):
"""
Can use like the Window.Layout method, but it's better to use the layout parameter when creating
:param rows: The rows of Elements
:type rows: List[List[Element]]
:return: Used for chaining
:rtype: (Frame)
"""
for row in rows:
try:
iter(row)
except TypeError:
popup_error(
'Error creating Tab layout',
'Your row is not an iterable (e.g. a list)',
f'Instead of a list, the type found was {type(row)}',
'The offensive row = ',
row,
'This item will be stripped from your layout',
keep_on_top=True,
image=_random_error_emoji(),
)
continue
self.AddRow(*row)
return self
def _GetElementAtLocation(self, location):
"""
Not user callable. Used to find the Element at a row, col position within the layout
:param location: (row, column) position of the element to find in layout
:type location: (int, int)
:return: The element found at the location
:rtype: (Element)
"""
(row_num, col_num) = location
row = self.Rows[row_num]
element = row[col_num]
return element
def find_key_from_tab_name(self, tab_name):
"""
Searches through the layout to find the key that matches the text on the tab. Implies names should be unique
:param tab_name: name of a tab
:type tab_name: str
:return: Returns the key or None if no key found
:rtype: key | None
"""
for row in self.Rows:
for element in row:
if element.Title == tab_name:
return element.Key
return None
def find_currently_active_tab_key(self):
"""
Returns the key for the currently active tab in this TabGroup
:return: Returns the key or None of no key found
:rtype: key | None
"""
try:
current_index = self.TKNotebook.index('current')
key = self.tab_index_to_key.get(current_index, None)
except:
key = None
return key
def get(self):
"""
Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on
the tab if no key is defined. Returns None if an error occurs.
Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you
are using this method correctly?
:return: The key of the currently selected tab or None if there is an error
:rtype: Any | None
"""
try:
current_index = self.TKNotebook.index('current')
key = self.tab_index_to_key.get(current_index, None)
except:
key = None
return key
def add_tab(self, tab_element):
"""
Add a new tab to an existing TabGroup
This call was written so that tabs can be added at runtime as your user performs operations.
Your Window should already be created and finalized.
:param tab_element: A Tab Element that has a layout in it
:type tab_element: Tab
"""
self.add_row(tab_element)
tab_element.TKFrame = tab_element.Widget = tk.Frame(self.TKNotebook)
form = self.ParentForm
form._BuildKeyDictForWindow(form, tab_element, form.AllKeysDict)
form.AllKeysDict[tab_element.Key] = tab_element
# Pack the tab's layout into the tab. NOTE - This does NOT pack the Tab itself... for that see below...
PackFormIntoFrame(tab_element, tab_element.TKFrame, self.ParentForm)
# - This is below - Perform the same operation that is performed when a Tab is packed into the window.
# If there's an image in the tab, then do the imagey-stuff
# ------------------- start of imagey-stuff -------------------
try:
if tab_element.Filename is not None:
photo = tk.PhotoImage(file=tab_element.Filename)
elif tab_element.Data is not None:
photo = tk.PhotoImage(data=tab_element.Data)
else:
photo = None
if tab_element.ImageSubsample and photo is not None:
photo = photo.subsample(tab_element.ImageSubsample)
# print('*ERROR laying out form.... Image Element has no image specified*')
except Exception as e:
photo = None
_error_popup_with_traceback(
'Your Window has an Tab Element with an IMAGE problem',
'The traceback will show you the Window with the problem layout',
f'Look in this Window\'s layout for an Image tab_element that has a key of {tab_element.Key}',
'The error occuring is:',
e,
)
tab_element.photo = photo
# add the label
if photo is not None:
width, height = photo.width(), photo.height()
tab_element.tktext_label = tk.Label(tab_element.ParentRowFrame, image=photo, width=width, height=height, bd=0)
else:
tab_element.tktext_label = tk.Label(tab_element.ParentRowFrame, bd=0)
# ------------------- end of imagey-stuff -------------------
state = 'normal'
if tab_element.Disabled:
state = 'disabled'
if tab_element.visible is False:
state = 'hidden'
if photo is not None:
self.TKNotebook.add(tab_element.TKFrame, text=tab_element.Title, compound=tk.LEFT, state=state, image=photo)
else:
self.TKNotebook.add(tab_element.TKFrame, text=tab_element.Title, state=state)
tab_element.ParentNotebook = self.TKNotebook
tab_element.TabID = self.TabCount
tab_element.ParentForm = self.ParentForm
self.TabCount += 1
if tab_element.BackgroundColor != COLOR_SYSTEM_DEFAULT and tab_element.BackgroundColor is not None:
tab_element.TKFrame.configure(
background=tab_element.BackgroundColor,
highlightbackground=tab_element.BackgroundColor,
highlightcolor=tab_element.BackgroundColor,
)
if tab_element.BorderWidth is not None:
tab_element.TKFrame.configure(borderwidth=tab_element.BorderWidth)
if tab_element.Tooltip is not None:
tab_element.TooltipObject = ToolTip(tab_element.TKFrame, text=tab_element.Tooltip, timeout=FreeSimpleGUI.DEFAULT_TOOLTIP_TIME)
_add_right_click_menu(tab_element, form)
def update(self, visible=None):
"""
Enables changing the visibility
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in TabGroup.update - The window was closed')
return
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
AddRow = add_row
FindKeyFromTabName = find_key_from_tab_name
Get = get
Layout = layout

View File

@ -0,0 +1,463 @@
from __future__ import annotations
import warnings
from tkinter import ttk
import FreeSimpleGUI
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_TABLE
from FreeSimpleGUI import Element
from FreeSimpleGUI import LOOK_AND_FEEL_TABLE
from FreeSimpleGUI import obj_to_string_single_obj
from FreeSimpleGUI import running_mac
from FreeSimpleGUI import TABLE_CLICKED_INDICATOR
from FreeSimpleGUI import theme_button_color
from FreeSimpleGUI._utils import _create_error_message
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
class Table(Element):
def __init__(
self,
values,
headings=None,
visible_column_map=None,
col_widths=None,
cols_justification=None,
def_col_width=10,
auto_size_columns=True,
max_col_width=20,
select_mode=None,
display_row_numbers=False,
starting_row_number=0,
num_rows=None,
row_height=None,
font=None,
justification='right',
text_color=None,
background_color=None,
alternating_row_color=None,
selected_row_colors=(None, None),
header_text_color=None,
header_background_color=None,
header_font=None,
header_border_width=None,
header_relief=None,
row_colors=None,
vertical_scroll_only=True,
hide_vertical_scroll=False,
border_width=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
size=(None, None),
s=(None, None),
change_submits=False,
enable_events=False,
enable_click_events=False,
right_click_selects=False,
bind_return_key=False,
pad=None,
p=None,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param values: Your table data represented as a 2-dimensions table... a list of rows, with each row representing a row in your table.
:type values: List[List[str | int | float]]
:param headings: The headings to show on the top line
:type headings: List[str]
:param visible_column_map: One entry for each column. False indicates the column is not shown
:type visible_column_map: List[bool]
:param col_widths: Number of characters that each column will occupy
:type col_widths: List[int]
:param cols_justification: Justification for EACH column. Is a list of strings with the value 'l', 'r', 'c' that indicates how the column will be justified. Either no columns should be set, or have to have one for every colun
:type cols_justification: List[str] or Tuple[str] or None
:param def_col_width: Default column width in characters
:type def_col_width: (int)
:param auto_size_columns: if True columns will be sized automatically
:type auto_size_columns: (bool)
:param max_col_width: Maximum width for all columns in characters
:type max_col_width: (int)
:param select_mode: Select Mode. Valid values start with "TABLE_SELECT_MODE_". Valid values are: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED
:type select_mode: (enum)
:param display_row_numbers: if True, the first column of the table will be the row #
:type display_row_numbers: (bool)
:param starting_row_number: The row number to use for the first row. All following rows will be based on this starting value. Default is 0.
:type starting_row_number: (int)
:param num_rows: The number of rows of the table to display at a time
:type num_rows: (int)
:param row_height: height of a single row in pixels
:type row_height: (int)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param justification: 'left', 'right', 'center' are valid choices
:type justification: (str)
:param text_color: color of the text
:type text_color: (str)
:param background_color: color of background
:type background_color: (str)
:param alternating_row_color: if set then every other row will have this color in the background.
:type alternating_row_color: (str)
:param selected_row_colors: Sets the text color and background color for a selected row. Same format as button colors - tuple ('red', 'yellow') or string 'red on yellow'. Defaults to theme's button color
:type selected_row_colors: str or (str, str)
:param header_text_color: sets the text color for the header
:type header_text_color: (str)
:param header_background_color: sets the background color for the header
:type header_background_color: (str)
:param header_font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type header_font: (str or (str, int[, str]) or None)
:param header_border_width: Border width for the header portion
:type header_border_width: (int | None)
:param header_relief: Relief style for the header. Values are same as other elements that use relief. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
:type header_relief: (str | None)
:param row_colors: list of tuples of (row, background color) OR (row, foreground color, background color). Sets the colors of listed rows to the color(s) provided (note the optional foreground color)
:type row_colors: List[Tuple[int, str] | Tuple[Int, str, str]]
:param vertical_scroll_only: if True only the vertical scrollbar will be visible
:type vertical_scroll_only: (bool)
:param hide_vertical_scroll: if True vertical scrollbar will be hidden
:type hide_vertical_scroll: (bool)
:param border_width: Border width/depth in pixels
:type border_width: (int)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
:param size: DO NOT USE! Use num_rows instead
:type size: (int, int)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Table events happen when row is clicked
:type enable_events: (bool)
:param enable_click_events: Turns on the element click events that will give you (row, col) click data when the table is clicked
:type enable_click_events: (bool)
:param right_click_selects: If True, then right clicking a row will select that row if multiple rows are not currently selected
:type right_click_selects: (bool)
:param bind_return_key: if True, pressing return key will cause event coming from Table, ALSO a left button double click will generate an event if this parameter is True
:type bind_return_key: (bool)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.Values = values
self.ColumnHeadings = headings
self.ColumnsToDisplay = visible_column_map
self.ColumnWidths = col_widths
self.cols_justification = cols_justification
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 FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.TextColor = text_color
self.HeaderTextColor = header_text_color if header_text_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['TEXT_INPUT']
self.HeaderBackgroundColor = header_background_color if header_background_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['INPUT']
self.HeaderFont = header_font
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.RowHeight = row_height
self.Widget = self.TKTreeview = None # type: ttk.Treeview
self.AlternatingRowColor = alternating_row_color
self.VerticalScrollOnly = vertical_scroll_only
self.HideVerticalScroll = hide_vertical_scroll
self.SelectedRows = []
self.ChangeSubmits = change_submits or enable_events
self.BindReturnKey = bind_return_key
self.StartingRowNumber = starting_row_number # When displaying row numbers, where to start
self.RowHeaderText = 'Row'
self.enable_click_events = enable_click_events
self.right_click_selects = right_click_selects
self.last_clicked_position = (None, None)
self.HeaderBorderWidth = header_border_width
self.BorderWidth = border_width
self.HeaderRelief = header_relief
self.table_ttk_style_name = None # the ttk style name for the Table itself
if selected_row_colors == (None, None):
# selected_row_colors = DEFAULT_TABLE_AND_TREE_SELECTED_ROW_COLORS
selected_row_colors = theme_button_color()
else:
try:
if isinstance(selected_row_colors, str):
selected_row_colors = selected_row_colors.split(' on ')
except Exception as e:
print('* Table Element Warning * you messed up with color formatting of Selected Row Color', e)
self.SelectedRowColors = selected_row_colors
self.RightClickMenu = right_click_menu
self.RowColors = row_colors
self.tree_ids = [] # ids returned when inserting items into table - will use to delete colors
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_TABLE,
text_color=text_color,
background_color=background_color,
font=font,
size=sz,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)
return
def update(self, values=None, num_rows=None, visible=None, select_rows=None, alternating_row_color=None, row_colors=None):
"""
Changes some of the settings for the Table Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param values: A new 2-dimensional table to show
:type values: List[List[str | int | float]]
:param num_rows: How many rows to display at a time
:type num_rows: (int)
:param visible: if True then will be visible
:type visible: (bool)
:param select_rows: List of rows to select as if user did
:type select_rows: List[int]
:param alternating_row_color: the color to make every other row
:type alternating_row_color: (str)
:param row_colors: list of tuples of (row, background color) OR (row, foreground color, background color). Changes the colors of listed rows to the color(s) provided (note the optional foreground color)
:type row_colors: List[Tuple[int, str] | Tuple[Int, str, str]]
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Table.update - The window was closed')
return
if values is not None:
for id in self.tree_ids:
self.TKTreeview.item(id, tags=())
if self.BackgroundColor is not None and self.BackgroundColor != COLOR_SYSTEM_DEFAULT:
self.TKTreeview.tag_configure(id, background=self.BackgroundColor)
else:
self.TKTreeview.tag_configure(id, background='#FFFFFF', foreground='#000000')
if self.TextColor is not None and self.TextColor != COLOR_SYSTEM_DEFAULT:
self.TKTreeview.tag_configure(id, foreground=self.TextColor)
else:
self.TKTreeview.tag_configure(id, foreground='#000000')
children = self.TKTreeview.get_children()
for i in children:
self.TKTreeview.detach(i)
self.TKTreeview.delete(i)
children = self.TKTreeview.get_children()
self.tree_ids = []
for i, value in enumerate(values):
if self.DisplayRowNumbers:
value = [i + self.StartingRowNumber] + value
id = self.TKTreeview.insert('', 'end', text=value, iid=i + 1, values=value, tag=i)
if self.BackgroundColor is not None and self.BackgroundColor != COLOR_SYSTEM_DEFAULT:
self.TKTreeview.tag_configure(id, background=self.BackgroundColor)
else:
self.TKTreeview.tag_configure(id, background='#FFFFFF')
self.tree_ids.append(id)
self.Values = values
self.SelectedRows = []
if visible is False:
self._pack_forget_save_settings(self.element_frame)
elif visible is True:
self._pack_restore_settings(self.element_frame)
if num_rows is not None:
self.TKTreeview.config(height=num_rows)
if select_rows is not None:
rows_to_select = [i + 1 for i in select_rows]
self.TKTreeview.selection_set(rows_to_select)
if alternating_row_color is not None: # alternating colors
self.AlternatingRowColor = alternating_row_color
if self.AlternatingRowColor is not None:
for row in range(0, len(self.Values), 2):
self.TKTreeview.tag_configure(row, background=self.AlternatingRowColor)
if row_colors is not None: # individual row colors
self.RowColors = row_colors
for row_def in self.RowColors:
if len(row_def) == 2: # only background is specified
self.TKTreeview.tag_configure(row_def[0], background=row_def[1])
else:
self.TKTreeview.tag_configure(row_def[0], background=row_def[2], foreground=row_def[1])
if visible is not None:
self._visible = visible
def _treeview_selected(self, event):
"""
Not user callable. Callback function that is called when something is selected from Table.
Stores the selected rows in Element as they are being selected. If events enabled, then returns from Read
:param event: event information from tkinter
:type event: (unknown)
"""
# print('**-- in treeview selected --**')
selections = self.TKTreeview.selection()
self.SelectedRows = [int(x) - 1 for x in selections]
if self.ChangeSubmits:
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def _treeview_double_click(self, event):
"""
Not user callable. Callback function that is called when something is selected from Table.
Stores the selected rows in Element as they are being selected. If events enabled, then returns from Read
:param event: event information from tkinter
:type event: (unknown)
"""
selections = self.TKTreeview.selection()
self.SelectedRows = [int(x) - 1 for x in selections]
if self.BindReturnKey: # Signifies BOTH a return key AND a double click
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def _table_clicked(self, event):
"""
Not user callable. Callback function that is called a click happens on a table.
Stores the selected rows in Element as they are being selected. If events enabled, then returns from Read
:param event: event information from tkinter
:type event: (unknown)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
# popup(obj_to_string_single_obj(event))
try:
region = self.Widget.identify('region', event.x, event.y)
if region == 'heading':
row = -1
elif region == 'cell':
row = int(self.Widget.identify_row(event.y)) - 1
elif region == 'separator':
row = None
else:
row = None
col_identified = self.Widget.identify_column(event.x)
if col_identified: # Sometimes tkinter returns a value of '' which would cause an error if cast to an int
column = int(self.Widget.identify_column(event.x)[1:]) - 1 - int(self.DisplayRowNumbers is True)
else:
column = None
except Exception as e:
warnings.warn(f'Error getting table click data for table with key= {self.Key}\nError: {e}', UserWarning)
if not FreeSimpleGUI.SUPPRESS_ERROR_POPUPS:
_error_popup_with_traceback(
f'Unable to complete operation getting the clicked event for table with key {self.Key}',
_create_error_message(),
e,
'Event data:',
obj_to_string_single_obj(event),
)
row = column = None
self.last_clicked_position = (row, column)
# update the rows being selected if appropriate
self.ParentForm.TKroot.update()
# self.TKTreeview.()
selections = self.TKTreeview.selection()
if self.right_click_selects and len(selections) <= 1:
if (event.num == 3 and not running_mac()) or (event.num == 2 and running_mac()):
if row != -1 and row is not None:
selections = [row + 1]
self.TKTreeview.selection_set(selections)
# print(selections)
self.SelectedRows = [int(x) - 1 for x in selections]
# print('The new selected rows = ', self.SelectedRows, 'selections =', selections)
if self.enable_click_events is True:
if self.Key is not None:
self.ParentForm.LastButtonClicked = (self.Key, TABLE_CLICKED_INDICATOR, (row, column))
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def get(self):
"""
Get the selected rows using tktiner's selection method. Returns a list of the selected rows.
:return: a list of the index of the selected rows (a list of ints)
:rtype: List[int]
"""
selections = self.TKTreeview.selection()
selected_rows = [int(x) - 1 for x in selections]
return selected_rows
def get_last_clicked_position(self):
"""
Returns a tuple with the row and column of the cell that was last clicked.
Headers will have a row == -1 and the Row Number Column (if present) will have a column == -1
:return: The (row,col) position of the last cell clicked in the table
:rtype: (int | None, int | None)
"""
return self.last_clicked_position
Update = update
Get = get

View File

@ -0,0 +1,413 @@
from __future__ import annotations
import tkinter.font
import FreeSimpleGUI
from FreeSimpleGUI import _get_hidden_master_root
from FreeSimpleGUI import COLOR_SYSTEM_DEFAULT
from FreeSimpleGUI import ELEM_TYPE_TEXT
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI.elements.base import Element
class Text(Element):
"""
Text - Display some text in the window. Usually this means a single line of text. However, the text can also be multiple lines. If multi-lined there are no scroll bars.
"""
def __init__(
self,
text='',
size=(None, None),
s=(None, None),
auto_size_text=None,
click_submits=False,
enable_events=False,
relief=None,
font=None,
text_color=None,
background_color=None,
border_width=None,
justification=None,
pad=None,
p=None,
key=None,
k=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
grab=None,
tooltip=None,
visible=True,
metadata=None,
):
"""
:param text: The text to display. Can include /n to achieve multiple lines. Will convert (optional) parameter into a string
:type text: Any
:param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1
:type size: (int, int) | (int, None) | (None, None) | (int, ) | int
:param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used
:type s: (int, int) | (int, None) | (None, None) | (int, ) | int
:param auto_size_text: if True size of the Text Element will be sized to fit the string provided in 'text' parm
:type auto_size_text: (bool)
:param click_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type click_submits: (bool)
:param enable_events: Turns on the element specific events. Text events happen when the text is clicked
:type enable_events: (bool)
:param relief: relief style around the text. Values are same as progress meter relief values. Should be a constant that is defined at starting with RELIEF - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type relief: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param text_color: color of the text
:type text_color: (str)
:param background_color: color of background
:type background_color: (str)
:param border_width: number of pixels for the border (if using a relief)
:type border_width: (int)
:param justification: how string should be aligned within space provided by size. Valid choices = `left`, `right`, `center`
:type justification: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str or int or tuple or object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[ List[str] | str ]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param grab: If True can grab this element and move the window around. Default is False
:type grab: (bool)
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.DisplayText = str(text)
self.TextColor = text_color if text_color else FreeSimpleGUI.DEFAULT_TEXT_COLOR
self.Justification = justification
self.Relief = relief
self.ClickSubmits = click_submits or enable_events
if background_color is None:
bg = FreeSimpleGUI.DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR
else:
bg = background_color
self.RightClickMenu = right_click_menu
self.TKRightClickMenu = None
self.BorderWidth = border_width
self.Grab = grab
key = key if key is not None else k
sz = size if size != (None, None) else s
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_TEXT,
auto_size_text=auto_size_text,
size=sz,
background_color=bg,
font=font if font else FreeSimpleGUI.DEFAULT_FONT,
text_color=self.TextColor,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
)
def update(self, value=None, background_color=None, text_color=None, font=None, visible=None):
"""
Changes some of the settings for the Text Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param value: new text to show
:type value: (Any)
:param background_color: color of background
:type background_color: (str)
:param text_color: color of the text
:type text_color: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param visible: set visibility state of the element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Text.update - The window was closed')
return
if value is not None:
self.DisplayText = str(value)
self.TKStringVar.set(str(value))
if background_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(background=background_color)
if text_color not in (None, COLOR_SYSTEM_DEFAULT):
self.TKText.configure(fg=text_color)
if font is not None:
self.TKText.configure(font=font)
if visible is False:
self._pack_forget_save_settings()
elif visible is True:
self._pack_restore_settings()
if visible is not None:
self._visible = visible
def get(self):
"""
Gets the current value of the displayed text
:return: The current value
:rtype: (str)
"""
try:
text = self.TKStringVar.get()
except:
text = ''
return text
@classmethod
def fonts_installed_list(cls):
"""
Returns a list of strings that tkinter reports as the installed fonts
:return: List of the installed font names
:rtype: List[str]
"""
# A window must exist before can perform this operation. Create the hidden master root if it doesn't exist
_get_hidden_master_root()
fonts = list(tkinter.font.families())
fonts.sort()
return fonts
@classmethod
def char_width_in_pixels(cls, font, character='W'):
"""
Get the with of the character "W" in pixels for the font being passed in or
the character of your choosing if "W" is not a good representative character.
Cannot be used until a window has been created.
If an error occurs, 0 will be returned
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike, to be measured
:type font: (str or (str, int[, str]) or None)
:param character: specifies a SINGLE CHARACTER character to measure
:type character: (str)
:return: Width in pixels of "A"
:rtype: (int)
"""
# A window must exist before can perform this operation. Create the hidden master root if it doesn't exist
_get_hidden_master_root()
size = 0
try:
size = tkinter.font.Font(font=font).measure(character) # single character width
except Exception as e:
_error_popup_with_traceback('Exception retrieving char width in pixels', e)
return size
@classmethod
def char_height_in_pixels(cls, font):
"""
Get the height of a string if using the supplied font in pixels.
Cannot be used until a window has been created.
If an error occurs, 0 will be returned
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike, to be measured
:type font: (str or (str, int[, str]) or None)
:return: Height in pixels of "A"
:rtype: (int)
"""
# A window must exist before can perform this operation. Create the hidden master root if it doesn't exist
_get_hidden_master_root()
size = 0
try:
size = tkinter.font.Font(font=font).metrics('linespace')
except Exception as e:
_error_popup_with_traceback('Exception retrieving char height in pixels', e)
return size
@classmethod
def string_width_in_pixels(cls, font, string):
"""
Get the with of the supplied string in pixels for the font being passed in.
If an error occurs, 0 will be returned
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike, to be measured
:type font: (str or (str, int[, str]) or None)
:param string: the string to measure
:type string: str
:return: Width in pixels of string
:rtype: (int)
"""
# A window must exist before can perform this operation. Create the hidden master root if it doesn't exist
_get_hidden_master_root()
size = 0
try:
size = tkinter.font.Font(font=font).measure(string) # string's width
except Exception as e:
_error_popup_with_traceback('Exception retrieving string width in pixels', e)
return size
def _print_to_element(
self,
*args,
end=None,
sep=None,
text_color=None,
background_color=None,
autoscroll=None,
justification=None,
font=None,
append=None,
):
"""
Print like Python normally prints except route the output to a multiline element and also add colors if desired
:param multiline_element: The multiline element to be output to
:type multiline_element: (Multiline)
:param args: The arguments to print
:type args: List[Any]
:param end: The end char to use just like print uses
:type end: (str)
:param sep: The separation character like print uses
:type sep: (str)
:param text_color: color of the text
:type text_color: (str)
:param background_color: The background color of the line
:type background_color: (str)
:param autoscroll: If True (the default), the element will scroll to bottom after updating
:type autoscroll: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike for the value being updated
:type font: str | (str, int)
"""
end_str = str(end) if end is not None else '\n'
sep_str = str(sep) if sep is not None else ' '
outstring = ''
num_args = len(args)
for i, arg in enumerate(args):
outstring += str(arg)
if i != num_args - 1:
outstring += sep_str
outstring += end_str
if append:
outstring = self.get() + outstring
self.update(outstring, text_color=text_color, background_color=background_color, font=font)
try: # if the element is set to autorefresh, then refresh the parent window
if self.AutoRefresh:
self.ParentForm.refresh()
except:
pass
def print(
self,
*args,
end=None,
sep=None,
text_color=None,
background_color=None,
justification=None,
font=None,
colors=None,
t=None,
b=None,
c=None,
autoscroll=True,
append=True,
):
"""
Print like Python normally prints except route the output to a multiline element and also add colors if desired
colors -(str, str) or str. A combined text/background color definition in a single parameter
There are also "aliases" for text_color, background_color and colors (t, b, c)
t - An alias for color of the text (makes for shorter calls)
b - An alias for the background_color parameter
c - (str, str) - "shorthand" way of specifying color. (foreground, backgrouned)
c - str - can also be a string of the format "foreground on background" ("white on red")
With the aliases it's possible to write the same print but in more compact ways:
cprint('This will print white text on red background', c=('white', 'red'))
cprint('This will print white text on red background', c='white on red')
cprint('This will print white text on red background', text_color='white', background_color='red')
cprint('This will print white text on red background', t='white', b='red')
:param args: The arguments to print
:type args: (Any)
:param end: The end char to use just like print uses
:type end: (str)
:param sep: The separation character like print uses
:type sep: (str)
:param text_color: The color of the text
:type text_color: (str)
:param background_color: The background color of the line
:type background_color: (str)
:param justification: text justification. left, right, center. Can use single characters l, r, c. Sets only for this value, not entire element
:type justification: (str)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike for the args being printed
:type font: (str or (str, int[, str]) or None)
:param colors: Either a tuple or a string that has both the text and background colors. Or just the text color
:type colors: (str) or (str, str)
:param t: Color of the text
:type t: (str)
:param b: The background color of the line
:type b: (str)
:param c: Either a tuple or a string that has both the text and background colors or just tex color (same as the color parm)
:type c: (str) or (str, str)
:param autoscroll: If True the contents of the element will automatically scroll as more data added to the end
:type autoscroll: (bool)
"""
kw_text_color = text_color or t
kw_background_color = background_color or b
dual_color = colors or c
try:
if isinstance(dual_color, tuple):
kw_text_color = dual_color[0]
kw_background_color = dual_color[1]
elif isinstance(dual_color, str):
if ' on ' in dual_color: # if has "on" in the string, then have both text and background
kw_text_color = dual_color.split(' on ')[0]
kw_background_color = dual_color.split(' on ')[1]
else: # if no "on" then assume the color string is just the text color
kw_text_color = dual_color
except Exception as e:
print('* multiline print warning * you messed up with color formatting', e)
self._print_to_element(
*args,
end=end,
sep=sep,
text_color=kw_text_color,
background_color=kw_background_color,
justification=justification,
autoscroll=autoscroll,
font=font,
append=append,
)
Get = get
Update = update

View File

@ -0,0 +1,493 @@
from __future__ import annotations
import tkinter as tk
from tkinter import ttk
from typing import Any
from typing import Dict
from typing import List
import FreeSimpleGUI
from FreeSimpleGUI import ELEM_TYPE_TREE
from FreeSimpleGUI import Element
from FreeSimpleGUI import LOOK_AND_FEEL_TABLE
from FreeSimpleGUI import theme_button_color
from FreeSimpleGUI._utils import _error_popup_with_traceback
from FreeSimpleGUI._utils import _exit_mainloop
class Tree(Element):
"""
Tree Element - Presents data in a tree-like manner, much like a file/folder browser. Uses the TreeData class
to hold the user's data and pass to the element for display.
"""
def __init__(
self,
data=None,
headings=None,
visible_column_map=None,
col_widths=None,
col0_width=10,
col0_heading='',
def_col_width=10,
auto_size_columns=True,
max_col_width=20,
select_mode=None,
show_expanded=False,
change_submits=False,
enable_events=False,
click_toggles_select=None,
font=None,
justification='right',
text_color=None,
border_width=None,
background_color=None,
selected_row_colors=(None, None),
header_text_color=None,
header_background_color=None,
header_font=None,
header_border_width=None,
header_relief=None,
num_rows=None,
sbar_trough_color=None,
sbar_background_color=None,
sbar_arrow_color=None,
sbar_width=None,
sbar_arrow_width=None,
sbar_frame_color=None,
sbar_relief=None,
row_height=None,
vertical_scroll_only=True,
hide_vertical_scroll=False,
pad=None,
p=None,
key=None,
k=None,
tooltip=None,
right_click_menu=None,
expand_x=False,
expand_y=False,
visible=True,
metadata=None,
):
"""
:param data: The data represented using a PySimpleGUI provided TreeData class
:type data: (TreeData)
:param headings: List of individual headings for each column
:type headings: List[str]
:param visible_column_map: Determines if a column should be visible. If left empty, all columns will be shown
:type visible_column_map: List[bool]
:param col_widths: List of column widths so that individual column widths can be controlled
:type col_widths: List[int]
:param col0_width: Size of Column 0 which is where the row numbers will be optionally shown
:type col0_width: (int)
:param col0_heading: Text to be shown in the header for the left-most column
:type col0_heading: (str)
:param def_col_width: default column width
:type def_col_width: (int)
:param auto_size_columns: if True, the size of a column is determined using the contents of the column
:type auto_size_columns: (bool)
:param max_col_width: the maximum size a column can be
:type max_col_width: (int)
:param select_mode: Use same values as found on Table Element. Valid values include: TABLE_SELECT_MODE_NONE TABLE_SELECT_MODE_BROWSE TABLE_SELECT_MODE_EXTENDED
:type select_mode: (enum)
:param show_expanded: if True then the tree will be initially shown with all nodes completely expanded
:type show_expanded: (bool)
:param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead
:type change_submits: (bool)
:param enable_events: Turns on the element specific events. Tree events happen when row is clicked
:type enable_events: (bool)
:param click_toggles_select: If True then clicking a row will cause the selection for that row to toggle between selected and deselected
:type click_toggles_select: (bool)
:param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type font: (str or (str, int[, str]) or None)
:param justification: 'left', 'right', 'center' are valid choices
:type justification: (str)
:param text_color: color of the text
:type text_color: (str)
:param border_width: Border width/depth in pixels
:type border_width: (int)
:param background_color: color of background
:type background_color: (str)
:param selected_row_colors: Sets the text color and background color for a selected row. Same format as button colors - tuple ('red', 'yellow') or string 'red on yellow'. Defaults to theme's button color
:type selected_row_colors: str or (str, str)
:param header_text_color: sets the text color for the header
:type header_text_color: (str)
:param header_background_color: sets the background color for the header
:type header_background_color: (str)
:param header_font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike
:type header_font: (str or (str, int[, str]) or None)
:param header_border_width: Border width for the header portion
:type header_border_width: (int | None)
:param header_relief: Relief style for the header. Values are same as other elements that use relief. RELIEF_RAISED RELIEF_SUNKEN RELIEF_FLAT RELIEF_RIDGE RELIEF_GROOVE RELIEF_SOLID
:type header_relief: (str | None)
:param num_rows: The number of rows of the table to display at a time
:type num_rows: (int)
:param row_height: height of a single row in pixels
:type row_height: (int)
:param vertical_scroll_only: if True only the vertical scrollbar will be visible
:type vertical_scroll_only: (bool)
:param hide_vertical_scroll: if True vertical scrollbar will be hidden
:type hide_vertical_scroll: (bool)
:param sbar_trough_color: Scrollbar color of the trough
:type sbar_trough_color: (str)
:param sbar_background_color: Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over
:type sbar_background_color: (str)
:param sbar_arrow_color: Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over
:type sbar_arrow_color: (str)
:param sbar_width: Scrollbar width in pixels
:type sbar_width: (int)
:param sbar_arrow_width: Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar
:type sbar_arrow_width: (int)
:param sbar_frame_color: Scrollbar Color of frame around scrollbar (available only on some ttk themes)
:type sbar_frame_color: (str)
:param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID
:type sbar_relief: (str)
:param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int)
:type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used
:type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
:param key: Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element
:type key: str | int | tuple | object
:param k: Same as the Key. You can use either k or key. Which ever is set will be used.
:type k: str | int | tuple | object
:param tooltip: text, that will appear when mouse hovers over the element
:type tooltip: (str)
:param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format.
:type right_click_menu: List[List[str] | str]]
:param expand_x: If True the element will automatically expand in the X direction to fill available space
:type expand_x: (bool)
:param expand_y: If True the element will automatically expand in the Y direction to fill available space
:type expand_y: (bool)
:param visible: set visibility state of the element
:type visible: (bool)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self.image_dict = {}
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 FreeSimpleGUI.DEFAULT_BACKGROUND_COLOR
self.TextColor = text_color
self.HeaderTextColor = header_text_color if header_text_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['TEXT_INPUT']
self.HeaderBackgroundColor = header_background_color if header_background_color is not None else LOOK_AND_FEEL_TABLE[FreeSimpleGUI.CURRENT_LOOK_AND_FEEL]['INPUT']
self.HeaderBorderWidth = header_border_width
self.BorderWidth = border_width
self.HeaderRelief = header_relief
self.click_toggles_select = click_toggles_select
if selected_row_colors == (None, None):
# selected_row_colors = DEFAULT_TABLE_AND_TREE_SELECTED_ROW_COLORS
selected_row_colors = theme_button_color()
else:
try:
if isinstance(selected_row_colors, str):
selected_row_colors = selected_row_colors.split(' on ')
except Exception as e:
print('* Table Element Warning * you messed up with color formatting of Selected Row Color', e)
self.SelectedRowColors = selected_row_colors
self.HeaderFont = header_font
self.Justification = justification
self.InitialState = None
self.SelectMode = select_mode
self.ShowExpanded = show_expanded
self.NumRows = num_rows
self.Col0Width = col0_width
self.col0_heading = col0_heading
self.TKTreeview = None # type: ttk.Treeview
self.element_frame = None # type: tk.Frame
self.VerticalScrollOnly = vertical_scroll_only
self.HideVerticalScroll = hide_vertical_scroll
self.SelectedRows = []
self.ChangeSubmits = change_submits or enable_events
self.RightClickMenu = right_click_menu
self.RowHeight = row_height
self.IconList = {}
self.IdToKey = {'': ''}
self.KeyToID = {'': ''}
key = key if key is not None else k
pad = pad if pad is not None else p
self.expand_x = expand_x
self.expand_y = expand_y
super().__init__(
ELEM_TYPE_TREE,
text_color=text_color,
background_color=background_color,
font=font,
pad=pad,
key=key,
tooltip=tooltip,
visible=visible,
metadata=metadata,
sbar_trough_color=sbar_trough_color,
sbar_background_color=sbar_background_color,
sbar_arrow_color=sbar_arrow_color,
sbar_width=sbar_width,
sbar_arrow_width=sbar_arrow_width,
sbar_frame_color=sbar_frame_color,
sbar_relief=sbar_relief,
)
return
def _treeview_selected(self, event):
"""
Not a user function. Callback function that happens when an item is selected from the tree. In this
method, it saves away the reported selections so they can be properly returned.
:param event: An event parameter passed in by tkinter. Not used
:type event: (Any)
"""
selections = self.TKTreeview.selection()
selected_rows = [self.IdToKey[x] for x in selections]
if self.click_toggles_select:
if set(self.SelectedRows) == set(selected_rows):
for item in selections:
self.TKTreeview.selection_remove(item)
selections = []
self.SelectedRows = [self.IdToKey[x] for x in selections]
if self.ChangeSubmits:
if self.Key is not None:
self.ParentForm.LastButtonClicked = self.Key
else:
self.ParentForm.LastButtonClicked = ''
self.ParentForm.FormRemainedOpen = True
_exit_mainloop(self.ParentForm)
def add_treeview_data(self, node):
"""
Not a user function. Recursive method that inserts tree data into the tkinter treeview widget.
:param node: The node to insert. Will insert all nodes from starting point downward, recursively
:type node: (TreeData)
"""
if node.key != '':
if node.icon:
try:
if node.icon not in self.image_dict:
if type(node.icon) is bytes:
photo = tk.PhotoImage(data=node.icon)
else:
photo = tk.PhotoImage(file=node.icon)
self.image_dict[node.icon] = photo
else:
photo = self.image_dict.get(node.icon)
node.photo = photo
id = self.TKTreeview.insert(
self.KeyToID[node.parent],
'end',
iid=None,
text=node.text,
values=node.values,
open=self.ShowExpanded,
image=node.photo,
)
self.IdToKey[id] = node.key
self.KeyToID[node.key] = id
except:
self.photo = None
else:
id = self.TKTreeview.insert(
self.KeyToID[node.parent],
'end',
iid=None,
text=node.text,
values=node.values,
open=self.ShowExpanded,
)
self.IdToKey[id] = node.key
self.KeyToID[node.key] = id
for node in node.children:
self.add_treeview_data(node)
def update(self, values=None, key=None, value=None, text=None, icon=None, visible=None):
"""
Changes some of the settings for the Tree Element. Must call `Window.Read` or `Window.Finalize` prior
Changes will not be visible in your window until you call window.read or window.refresh.
If you change visibility, your element may MOVE. If you want it to remain stationary, use the "layout helper"
function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there
when made visible.
:param values: Representation of the tree
:type values: (TreeData)
:param key: identifies a particular item in tree to update
:type key: str | int | tuple | object
:param value: sets the node identified by key to a particular value
:type value: (Any)
:param text: sets the node identified by key to this string
:type text: (str)
:param icon: can be either a base64 icon or a filename for the icon
:type icon: bytes | str
:param visible: control visibility of element
:type visible: (bool)
"""
if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow
return
if self._this_elements_window_closed():
_error_popup_with_traceback('Error in Tree.update - The window was closed')
return
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.IdToKey = {'': ''}
self.KeyToID = {'': ''}
self.add_treeview_data(self.TreeData.root_node)
self.SelectedRows = []
if key is not None:
for id in self.IdToKey.keys():
if key == self.IdToKey[id]:
break
else:
id = None
print('** Key not found **')
else:
id = None
if id:
# item = self.TKTreeview.item(id)
if value is not None:
self.TKTreeview.item(id, values=value)
if text is not None:
self.TKTreeview.item(id, text=text)
if icon is not None:
try:
if type(icon) is bytes:
photo = tk.PhotoImage(data=icon)
else:
photo = tk.PhotoImage(file=icon)
self.TKTreeview.item(id, image=photo)
self.IconList[key] = photo # save so that it's not deleted (save reference)
except:
pass
# item = self.TKTreeview.item(id)
if visible is False:
self._pack_forget_save_settings(self.element_frame)
elif visible is True:
self._pack_restore_settings(self.element_frame)
if visible is not None:
self._visible = visible
return self
Update = update
class TreeData:
"""
Class that user fills in to represent their tree data. It's a very simple tree representation with a root "Node"
with possibly one or more children "Nodes". Each Node contains a key, text to display, list of values to display
and an icon. The entire tree is built using a single method, Insert. Nothing else is required to make the tree.
"""
class Node:
"""
Contains information about the individual node in the tree
"""
def __init__(self, parent, key, text, values, icon=None):
"""
Represents a node within the TreeData class
:param parent: The parent Node
:type parent: (TreeData.Node)
:param key: Used to uniquely identify this node
:type key: str | int | tuple | object
:param text: The text that is displayed at this node's location
:type text: (str)
:param values: The list of values that are displayed at this node
:type values: List[Any]
:param icon: just a icon
:type icon: str | bytes
"""
self.parent = parent # type: TreeData.Node
self.children = [] # type: List[TreeData.Node]
self.key = key # type: str
self.text = text # type: str
self.values = values # type: List[Any]
self.icon = icon # type: str | bytes
def _Add(self, node):
self.children.append(node)
def __init__(self):
"""
Instantiate the object, initializes the Tree Data, creates a root node for you
"""
self.tree_dict = {} # type: Dict[str, TreeData.Node]
self.root_node = self.Node('', '', 'root', [], None) # The root node
self.tree_dict[''] = self.root_node # Start the tree out with the root node
def _AddNode(self, key, node):
"""
Adds a node to tree dictionary (not user callable)
:param key: Uniquely identifies this Node
:type key: (str)
:param node: Node being added
:type node: (TreeData.Node)
"""
self.tree_dict[key] = node
def insert(self, parent, key, text, values, icon=None):
"""
Inserts a node into the tree. This is how user builds their tree, by Inserting Nodes
This is the ONLY user callable method in the TreeData class
:param parent: the parent Node
:type parent: (Node)
:param key: Used to uniquely identify this node
:type key: str | int | tuple | object
:param text: The text that is displayed at this node's location
:type text: (str)
:param values: The list of values that are displayed at this node
:type values: List[Any]
:param icon: icon
:type icon: str | bytes
"""
node = self.Node(parent, key, text, values, icon)
self.tree_dict[key] = node
parent_node = self.tree_dict[parent]
parent_node._Add(node)
def __repr__(self):
"""
Converts the TreeData into a printable version, nicely formatted
:return: (str) A formatted, text version of the TreeData
:rtype:
"""
return self._NodeStr(self.root_node, 1)
def _NodeStr(self, node, level):
"""
Does the magic of converting the TreeData into a nicely formatted string version
:param node: The node to begin printing the tree
:type node: (TreeData.Node)
:param level: The indentation level for string formatting
:type level: (int)
"""
return '\n'.join([str(node.key) + ' : ' + str(node.text) + ' [ ' + ', '.join([str(v) for v in node.values]) + ' ]'] + [' ' * 4 * level + self._NodeStr(child, level + 1) for child in node.children])
Insert = insert

View File

@ -0,0 +1,358 @@
from __future__ import annotations
import textwrap
import tkinter as tk
from FreeSimpleGUI import EVENT_SYSTEM_TRAY_ICON_ACTIVATED # = '__ACTIVATED__'
from FreeSimpleGUI import EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED # = '__DOUBLE_CLICKED__'
from FreeSimpleGUI import EVENT_SYSTEM_TRAY_MESSAGE_CLICKED # = '__MESSAGE_CLICKED__'
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_DISPLAY_DURATION_IN_MILLISECONDS # = 3000 # how long to display the window
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_FADE_IN_DURATION # = 1000 # how long to fade in / fade out the window
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_MAX_LINE_LENGTH # = 50
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_TEXT_COLOR # = '#ffffff'
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_WIN_COLOR # = '#282828'
from FreeSimpleGUI import SYSTEM_TRAY_WIN_MARGINS # = 160, 60 # from right edge of screen, from bottom of screen
from FreeSimpleGUI import TEXT_LOCATION_TOP_LEFT
from FreeSimpleGUI import TIMEOUT_KEY
from FreeSimpleGUI.elements.graph import Graph
from FreeSimpleGUI.elements.helpers import AddMenuItem
from FreeSimpleGUI.elements.image import Image
from FreeSimpleGUI.window import Window
_tray_icon_error = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAADlAAAA5QGP5Zs8AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAIpQTFRF////20lt30Bg30pg4FJc409g4FBe4E9f4U9f4U9g4U9f4E9g31Bf4E9f4E9f4E9f4E9f4E9f4FFh4Vdm4lhn42Bv5GNx5W575nJ/6HqH6HyI6YCM6YGM6YGN6oaR8Kev9MPI9cbM9snO9s3R+Nfb+dzg+d/i++vt/O7v/fb3/vj5//z8//7+////KofnuQAAABF0Uk5TAAcIGBktSYSXmMHI2uPy8/XVqDFbAAAA8UlEQVQ4y4VT15LCMBBTQkgPYem9d9D//x4P2I7vILN68kj2WtsAhyDO8rKuyzyLA3wjSnvi0Eujf3KY9OUP+kno651CvlB0Gr1byQ9UXff+py5SmRhhIS0oPj4SaUUCAJHxP9+tLb/ezU0uEYDUsCc+l5/T8smTIVMgsPXZkvepiMj0Tm5txQLENu7gSF7HIuMreRxYNkbmHI0u5Hk4PJOXkSMz5I3nyY08HMjbpOFylF5WswdJPmYeVaL28968yNfGZ2r9gvqFalJNUy2UWmq1Wa7di/3Kxl3tF1671YHRR04dWn3s9cXRV09f3vb1fwPD7z9j1WgeRgAAAABJRU5ErkJggg=='
_tray_icon_success = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAEKAAABCgEWpLzLAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAHJQTFRF////ZsxmbbZJYL9gZrtVar9VZsJcbMRYaMZVasFYaL9XbMFbasRZaMFZacRXa8NYasFaasJaasFZasJaasNZasNYasJYasJZasJZasJZasJZasJZasJYasJZasJZasJZasJZasJaasJZasJZasJZasJZ2IAizQAAACV0Uk5TAAUHCA8YGRobHSwtPEJJUVtghJeYrbDByNjZ2tvj6vLz9fb3/CyrN0oAAADnSURBVDjLjZPbWoUgFIQnbNPBIgNKiwwo5v1fsQvMvUXI5oqPf4DFOgCrhLKjC8GNVgnsJY3nKm9kgTsduVHU3SU/TdxpOp15P7OiuV/PVzk5L3d0ExuachyaTWkAkLFtiBKAqZHPh/yuAYSv8R7XE0l6AVXnwBNJUsE2+GMOzWL8k3OEW7a/q5wOIS9e7t5qnGExvF5Bvlc4w/LEM4Abt+d0S5BpAHD7seMcf7+ZHfclp10TlYZc2y2nOqc6OwruxUWx0rDjNJtyp6HkUW4bJn0VWdf/a7nDpj1u++PBOR694+Ftj/8PKNdnDLn/V8YAAAAASUVORK5CYII='
_tray_icon_halt = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANswNuMPDO8HBO8FCe0HCu4IBu4IB+oLDeoLDu8JC+wKCu4JDO4LDOwKEe4OEO4OEeUQDewQDe0QDucVEuYcG+ccHOsQFuwWHe4fH/EGAvMEBfMFBvAHBPMGBfEGBvYCAfYDAvcDA/cDBPcDBfUDBvYEAPYEAfYEAvYEA/QGAPQGAfQGAvYEBPUEBvYFB/QGBPQGBfQHB/EFCvIHCPMHCfIHC/IFDfMHDPQGCPQGCfQGCvEIBPIIBfAIB/UIB/QICPYICfoBAPoBAfoBAvsBA/kCAPkCAfkCAvkCA/oBBPkCBPkCBfkCBvgCB/gEAPkEAfgEAvkEA/gGAfkGAvkEBPgEBfkEBv0AAP0AAfwAAvwAA/wCAPwCAfwCAvwCA/wABP0ABfwCBfwEAPwFA/ASD/ESFPAUEvAUE/EXFvAdH+kbIOobIeofIfEfIOcmKOohIukgJOggJesiKuwiKewoLe0tLO0oMOQ3OO43Oew4OfAhIPAhIfAiIPEiI+dDRe9ES+lQTOdSWupSUOhTUehSV+hUVu1QUO1RUe1SV+tTWe5SWOxXWOpYV+pZWelYXexaW+xaXO9aX+lZYeNhYOxjZ+lna+psbOttbehsbupscepucuxtcuxucep3fet7e+p/ffB6gOmKiu2Iie2Sk+2Qle2QluySlOyTleuYmvKFivCOjgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIxGNZsAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACVElEQVQ4T22S93PTMBhADQdl791SSsuRARTKKHsn+STZBptAi6zIacous+w9yyxl7z1T1h8ptHLhrrzLD5+/987R2XZElZ/39tZsbGg42NdvF4pqcGMs4XEcozAB/oQeu6wGr5fkAZcKOUIIRgQXR723wgaXt/NSgcwlO1r3oARkATfhbmNMMCnlMZdz5J8RN9fVhglS5JA/pJUOJiYXoShCkz/flheDvpzlBCBmya5KcDG1sMSB+r/VQtG+YoFXlwN0Us4yeBXujPmWCOqNlVwX5zHntLH5iQ420YiqX9pqTZFSCrBGBc+InBUDAsbwLRlMC40fGJT8YLRwfnhY3v6/AUtDc9m5z0tRJBOAvHUaFchdY6+zDzEghHv1tUnrNCaIOw84Q2WQmkeO/Xopj1xFBREFr8ZZjuRhA++PEB+t05ggwBucpbH8i/n5C1ZU0EEEmRZnSMxoIYcarKigA0Cb1zpHAyZnGj21xqICAA9dcvo4UgEdZ41FBZSTzEOn30f6QeE3Vhl0gLN+2RGDzZPMHLHKoAO3MFy+ix4sDxFlvMXfrdNgFezy7qrXPaaJg0u27j5nneKrCjJ4pf4e3m4DVMcjNNNKxWnpo6jtnfnkunExB4GbuGKk5FNanpB1nJCjCsThJPAAJ8lVdSF5sSrklM2ZqmYdiC40G7Dfnhp57ZsQz6c3hylEO6ZoZQJxqiVgbhoQK3T6AIgU4rbjxthAPF6NAwAOAcS+ixlp/WBFJRDi0fj2RtcjWRwif8Qdu/w3EKLcu3/YslnrZzwo24UQQvwFCrp/iM1NnHwAAAAASUVORK5CYII='
_tray_icon_notallowed = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAPcICPcLC/cMDPcQEPcSEvcXF/cYGPcaGvcbG/ccHPgxMfgyMvg0NPg5Ofg6Ovg7O/hBQfhCQvlFRflGRvljY/pkZPplZfpnZ/p2dgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMgEwNYAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABE0lEQVQ4T4WT65bDIAiExWbbtN0m3Uua+P4P6g4jGtN4NvNL4DuCCC5WWobe++uwmEmtwNxJUTebcwWCt5jJBwsYcKf3NE4hTOOJxj1FEnBTz4NH6qH2jUcCGr/QLLpkQgHe/6VWJXVqFgBB4yI/KVCkBCoFgPrPHw0CWbwCL8RibBFwzQDQH62/QeAtHQBeADUIDbkF/UnmnkB1ixtERrN3xCgyuF5kMntHTCJXh2vyv+wIdMhvgTeCQJ0C2hBMgSKfZlM1wSLXZ5oqgs8sjSpaCQ2VVlfKhLU6fdZGSvyWz9JMb+NE4jt/Nwfm0yJZSkBpYDg7TcJGrjm0Z7jK0B6P/fHiHK8e9Pp/eSmuf1+vf4x/ralnCN9IrncAAAAASUVORK5CYII='
_tray_icon_stop = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANsAANsBAdsCAtwEBNwFBdwHB9wICNwLC90MDN0NDd0PD90REd0SEt4TE94UFN4WFt4XF94ZGeAjI+AlJeEnJ+EpKeEqKuErK+EsLOEuLuIvL+IyMuIzM+M1NeM2NuM3N+M6OuM8POQ9PeQ+PuQ/P+RAQOVISOVJSeVKSuZLS+ZOTuZQUOZRUedSUudVVehbW+lhYeljY+poaOtvb+twcOtxcetzc+t0dOx3d+x4eOx6eu19fe1+fu2AgO2Cgu6EhO6Ghu6Hh+6IiO6Jie+Kiu+Li++MjO+Nje+Oju+QkPCUlPCVlfKgoPKkpPKlpfKmpvOrq/SurvSxsfSysvW4uPW6uvW7u/W8vPa9vfa+vvbAwPbCwvfExPfFxffGxvfHx/fIyPfJyffKyvjLy/jNzfjQ0PjR0fnS0vnU1PnY2Pvg4Pvi4vvj4/vl5fvm5vzo6Pzr6/3u7v3v7/3x8f3z8/309P719f729v739/74+P75+f76+v77+//8/P/9/f/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPHCyoUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABnUlEQVQ4T33S50PTQBgG8D6lzLbsIUv2kD0FFWTvPWTvISDIUBGV1ecvj+8luZTR9P1wSe755XK5O4+hK4gn5bc7DcMBz/InQoMXeVjY4FXuCAtEyLUwQcTcFgq45JYQ4JqbwhMtV8IjeUJDjQ+5paqCyG9srEsGgoUlpeXpIjxA1nfyi2+Jqmo7Q9JeV+ODerpvBQTM8/ySzQ3t+xxoL7h7nJve5jd85M7wJq9McHaT8o6TwBTfIIfHQGzoAZ/YiSTSq8D5dSDQVqFADrJ5KFMLPaKLHQiQMQoscClezdgCB4CXD/jM90izR8g85UaKA3YAn4AejhV189acA5LX+DVOg00gnvfoVX/BRQsgbplNGqzLusgIffx1tDchiyRgdRbVHNdgRRZHQD9H1asm+PMzYyYMtoBU/sYgRxxgrmGtBRL/cnf5RL4zzCEHZF2QE14LoOWf6B9vMcJBG/iBxKo8dVtYnyStv6yuUq7FLfmqTzbLEOFest1GNGEemCjCPnKuwjm0LsLMbRBJWLkGr4WdO+Cl0HkYPBc6N4z//HcQqVwcOuIAAAAASUVORK5CYII='
_tray_icon_exclamation = b'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAN0zM900NN01Nd02Nt03N944ON45Od46Ot47O98/P99BQd9CQt9DQ+FPT+JSUuJTU+JUVOJVVeJWVuNbW+ReXuVjY+Zra+dxceh4eOl7e+l8fOl+ful/f+qBgeqCguqDg+qFheuJieuLi+yPj+yQkO2Wlu+cnO+hofGqqvGtrfre3vrf3/ri4vvn5/75+f76+v/+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMQ8SQkAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABJElEQVQ4T4WS63KCMBBGsyBai62X0otY0aq90ZZa3v/dtpvsJwTijOfXt7tnILOJYY9tNonjNCtQOlqhuKKG0RrNVjgkmIHBHgMId+h7zHSiwg2a9FNVVYScupETmjkd67o+CWpYwft+R6CpCgeUlq5AOyf45+8JsRUKFI6eQLkI3n5CIREBUekLxGaLpATCymRISiAszARJCYSxiZGUQKDLQoqgnPnFhUPOTWeRoZD3FvVZlmVHkE2OEM9iV71GVoZDBGUpAg9QWN5/jx+Ilsi9hz0q4VHOWD+hEF70yc1QEr1a4Q0F0S3eJDfLuv8T4QEFXduZE1rj+et7g6hzCDxF08N+X4DAu+6lUSTnc5wE5tx73ckSTV8QVoux3N88Rykw/wP3i+vwPKk17AAAAABJRU5ErkJggg=='
_tray_icon_none = None
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_ICON_INFORMATION
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_ICON_WARNING
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_ICON_CRITICAL
from FreeSimpleGUI import SYSTEM_TRAY_MESSAGE_ICON_NOICON
class SystemTray:
"""
A "Simulated System Tray" that duplicates the API calls available to PySimpleGUIWx and PySimpleGUIQt users.
All of the functionality works. The icon is displayed ABOVE the system tray rather than inside of it.
"""
def __init__(self, menu=None, filename=None, data=None, data_base64=None, tooltip=None, metadata=None):
"""
SystemTray - create an icon in the system tray
:param menu: Menu definition. Example - ['UNUSED', ['My', 'Simple', '---', 'Menu', 'Exit']]
:type menu: List[List[List[str] or str]]
:param filename: filename for icon
:type filename: (str)
:param data: in-ram image for icon (same as data_base64 parm)
:type data: (bytes)
:param data_base64: base-64 data for icon
:type data_base64: (bytes)
:param tooltip: tooltip string
:type tooltip: (str)
:param metadata: User metadata that can be set to ANYTHING
:type metadata: (Any)
"""
self._metadata = None
self.Menu = menu
self.TrayIcon = None
self.Shown = False
self.MenuItemChosen = TIMEOUT_KEY
self.metadata = metadata
self.last_message_event = None
screen_size = Window.get_screen_size()
if filename:
image_elem = Image(filename=filename, background_color='red', enable_events=True, tooltip=tooltip, key='-IMAGE-')
elif data_base64:
image_elem = Image(data=data_base64, background_color='red', enable_events=True, tooltip=tooltip, key='-IMAGE-')
elif data:
image_elem = Image(data=data, background_color='red', enable_events=True, tooltip=tooltip, key='-IMAGE-')
else:
image_elem = Image(background_color='red', enable_events=True, tooltip=tooltip, key='-IMAGE-')
layout = [
[image_elem],
]
self.window = Window(
'Window Title',
layout,
element_padding=(0, 0),
margins=(0, 0),
grab_anywhere=True,
no_titlebar=True,
transparent_color='red',
keep_on_top=True,
right_click_menu=menu,
location=(screen_size[0] - 100, screen_size[1] - 100),
finalize=True,
)
self.window['-IMAGE-'].bind('<Double-Button-1>', '+DOUBLE_CLICK')
@property
def metadata(self):
"""
Metadata is an SystemTray property that you can use at any time to hold any value
:return: the current metadata value
:rtype: (Any)
"""
return self._metadata
@metadata.setter
def metadata(self, value):
"""
Metadata is an SystemTray property that you can use at any time to hold any value
:param value: Anything you want it to be
:type value: (Any)
"""
self._metadata = value
def read(self, timeout=None):
"""
Reads the context menu
:param timeout: Optional. Any value other than None indicates a non-blocking read
:type timeout:
:return:
:rtype:
"""
if self.last_message_event != TIMEOUT_KEY and self.last_message_event is not None:
event = self.last_message_event
self.last_message_event = None
return event
event, values = self.window.read(timeout=timeout)
if event.endswith('DOUBLE_CLICK'):
return EVENT_SYSTEM_TRAY_ICON_DOUBLE_CLICKED
elif event == '-IMAGE-':
return EVENT_SYSTEM_TRAY_ICON_ACTIVATED
return event
def hide(self):
"""
Hides the icon
"""
self.window.hide()
def un_hide(self):
"""
Restores a previously hidden icon
"""
self.window.un_hide()
def show_message(
self,
title,
message,
filename=None,
data=None,
data_base64=None,
messageicon=None,
time=(SYSTEM_TRAY_MESSAGE_FADE_IN_DURATION, SYSTEM_TRAY_MESSAGE_DISPLAY_DURATION_IN_MILLISECONDS),
):
"""
Shows a balloon above icon in system tray
:param title: Title shown in balloon
:type title: str
:param message: Message to be displayed
:type message: str
:param filename: Optional icon filename
:type filename: str
:param data: Optional in-ram icon
:type data: b''
:param data_base64: Optional base64 icon
:type data_base64: b''
:param time: Amount of time to display message in milliseconds. If tuple, first item is fade in/out duration
:type time: int | (int, int)
:return: The event that happened during the display such as user clicked on message
:rtype: Any
"""
if isinstance(time, tuple):
fade_duration, display_duration = time
else:
fade_duration = SYSTEM_TRAY_MESSAGE_FADE_IN_DURATION
display_duration = time
user_icon = data_base64 or filename or data or messageicon
event = self.notify(title, message, icon=user_icon, fade_in_duration=fade_duration, display_duration_in_ms=display_duration)
self.last_message_event = event
return event
def close(self):
"""
Close the system tray window
"""
self.window.close()
def update(
self,
menu=None,
tooltip=None,
filename=None,
data=None,
data_base64=None,
):
"""
Updates the menu, tooltip or icon
:param menu: menu defintion
:type menu: ???
:param tooltip: string representing tooltip
:type tooltip: ???
:param filename: icon filename
:type filename: ???
:param data: icon raw image
:type data: ???
:param data_base64: icon base 64 image
:type data_base64: ???
"""
# Menu
if menu is not None:
top_menu = tk.Menu(self.window.TKroot, tearoff=False)
AddMenuItem(top_menu, menu[1], self.window['-IMAGE-'])
self.window['-IMAGE-'].TKRightClickMenu = top_menu
if filename:
self.window['-IMAGE-'].update(filename=filename)
elif data_base64:
self.window['-IMAGE-'].update(data=data_base64)
elif data:
self.window['-IMAGE-'].update(data=data)
if tooltip:
self.window['-IMAGE-'].set_tooltip(tooltip)
@classmethod
def notify(
cls,
title,
message,
icon=_tray_icon_success,
display_duration_in_ms=SYSTEM_TRAY_MESSAGE_DISPLAY_DURATION_IN_MILLISECONDS,
fade_in_duration=SYSTEM_TRAY_MESSAGE_FADE_IN_DURATION,
alpha=0.9,
location=None,
):
"""
Displays a "notification window", usually in the bottom right corner of your display. Has an icon, a title, and a message
The window will slowly fade in and out if desired. Clicking on the window will cause it to move through the end the current "phase". For example, if the window was fading in and it was clicked, then it would immediately stop fading in and instead be fully visible. It's a way for the user to quickly dismiss the window.
:param title: Text to be shown at the top of the window in a larger font
:type title: (str)
:param message: Text message that makes up the majority of the window
:type message: (str)
:param icon: A base64 encoded PNG/GIF image or PNG/GIF filename that will be displayed in the window
:type icon: bytes | str
:param display_duration_in_ms: Number of milliseconds to show the window
:type display_duration_in_ms: (int)
:param fade_in_duration: Number of milliseconds to fade window in and out
:type fade_in_duration: (int)
:param alpha: Alpha channel. 0 - invisible 1 - fully visible
:type alpha: (float)
:param location: Location on the screen to display the window
:type location: (int, int)
:return: (int) reason for returning
:rtype: (int)
"""
messages = message.split('\n')
full_msg = ''
for m in messages:
m_wrap = textwrap.fill(m, SYSTEM_TRAY_MESSAGE_MAX_LINE_LENGTH)
full_msg += m_wrap + '\n'
message = full_msg[:-1]
win_msg_lines = message.count('\n') + 1
screen_res_x, screen_res_y = Window.get_screen_size()
win_margin = SYSTEM_TRAY_WIN_MARGINS # distance from screen edges
win_width, win_height = 364, 66 + (14.8 * win_msg_lines)
layout = [
[
Graph(
canvas_size=(win_width, win_height),
graph_bottom_left=(0, win_height),
graph_top_right=(win_width, 0),
key='-GRAPH-',
background_color=SYSTEM_TRAY_MESSAGE_WIN_COLOR,
enable_events=True,
)
]
]
win_location = location if location is not None else (screen_res_x - win_width - win_margin[0], screen_res_y - win_height - win_margin[1])
window = Window(
title,
layout,
background_color=SYSTEM_TRAY_MESSAGE_WIN_COLOR,
no_titlebar=True,
location=win_location,
keep_on_top=True,
alpha_channel=0,
margins=(0, 0),
element_padding=(0, 0),
grab_anywhere=True,
finalize=True,
)
window['-GRAPH-'].draw_rectangle(
(win_width, win_height),
(-win_width, -win_height),
fill_color=SYSTEM_TRAY_MESSAGE_WIN_COLOR,
line_color=SYSTEM_TRAY_MESSAGE_WIN_COLOR,
)
if type(icon) is bytes:
window['-GRAPH-'].draw_image(data=icon, location=(20, 20))
elif icon is not None:
window['-GRAPH-'].draw_image(filename=icon, location=(20, 20))
window['-GRAPH-'].draw_text(
title,
location=(64, 20),
color=SYSTEM_TRAY_MESSAGE_TEXT_COLOR,
font=('Helvetica', 12, 'bold'),
text_location=TEXT_LOCATION_TOP_LEFT,
)
window['-GRAPH-'].draw_text(
message,
location=(64, 44),
color=SYSTEM_TRAY_MESSAGE_TEXT_COLOR,
font=('Helvetica', 9),
text_location=TEXT_LOCATION_TOP_LEFT,
)
window['-GRAPH-'].set_cursor('hand2')
if fade_in_duration:
for i in range(1, int(alpha * 100)): # fade in
window.set_alpha(i / 100)
event, values = window.read(timeout=fade_in_duration // 100)
if event != TIMEOUT_KEY:
window.set_alpha(1)
break
if event != TIMEOUT_KEY:
window.close()
return EVENT_SYSTEM_TRAY_MESSAGE_CLICKED if event == '-GRAPH-' else event
event, values = window(timeout=display_duration_in_ms)
if event == TIMEOUT_KEY:
for i in range(int(alpha * 100), 1, -1): # fade out
window.set_alpha(i / 100)
event, values = window.read(timeout=fade_in_duration // 100)
if event != TIMEOUT_KEY:
break
else:
window.set_alpha(alpha)
event, values = window(timeout=display_duration_in_ms)
window.close()
return EVENT_SYSTEM_TRAY_MESSAGE_CLICKED if event == '-GRAPH-' else event
Close = close
Hide = hide
Read = read
ShowMessage = show_message
UnHide = un_hide
Update = update

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,33 @@
# This is a stub package designed to roughly emulate the _yaml
# extension module, which previously existed as a standalone module
# and has been moved into the `yaml` package namespace.
# It does not perfectly mimic its old counterpart, but should get
# close enough for anyone who's relying on it even when they shouldn't.
import yaml
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
# to tread carefully when poking at it here (it may not have the attributes we expect)
if not getattr(yaml, '__with_libyaml__', False):
from sys import version_info
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
raise exc("No module named '_yaml'")
else:
from yaml._yaml import *
import warnings
warnings.warn(
'The _yaml extension module is now located at yaml._yaml'
' and its location is subject to change. To use the'
' LibYAML-based parser and emitter, import from `yaml`:'
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
DeprecationWarning
)
del warnings
# Don't `del yaml` here because yaml is actually an existing
# namespace member of _yaml.
__name__ = '_yaml'
# If the module is top-level (i.e. not a part of any specific package)
# then the attribute should be set to ''.
# https://docs.python.org/3.8/library/types.html
__package__ = ''

View File

@ -0,0 +1,848 @@
Metadata-Version: 2.2
Name: FreeSimpleGUI
Version: 5.2.0.post1
Summary: The free-forever Python GUI framework.
Author-email: Spencer Phillip Young <spencer.young@spyoung.com>
Maintainer-email: Spencer Phillip Young <spencer.young@spyoung.com>
License: GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
Project-URL: Repository, https://github.com/spyoungtech/FreeSimpleGui
Project-URL: Documentation, https://freesimplegui.readthedocs.io/en/latest/
Keywords: PySimpleGui,fork,GUI,UI,tkinter,Qt,WxPython,Remi,wrapper,simple,easy,beginner,novice,student,graphics,progressbar,progressmeter
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)
Classifier: Topic :: Multimedia :: Graphics
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
License-File: NOTICE
# FreeSimpleGUI
The free-forever Python Simple GUI software.
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/freesimplegui.png">
</p>
```bash
pip install FreeSimpleGUI
```
To migrate from PySimpleGUI:
```diff
- import PySimpleGUI as sg
+ import FreeSimpleGUI as sg
```
### Support
If you encounter any issues or have any questions, please feel welcome to [open an issue](https://github.com/spyoungtech/FreeSimpleGUI/issues/new).
Documentation for FreeSimpleGUI is available at: https://freesimplegui.readthedocs.io/en/latest/. FreeSimpleGUI.org is still in progress.
### Contributions
Contributions are welcome! Contributions can be made via pull request. Ideally, please try to make sure there is an open [issue](https://github.com/spyoungtech/FreeSimpleGUI/issues) associated with your pull request first or create one if necessary.
--------------------------
# What Is FreeSimpleGUI ❓
FreeSimpleGUI is a Python package that enables Python programmers of all levels to create GUIs. You specify your GUI window using a "layout" which contains widgets (they're called "Elements" in FreeSimpleGUI). Your layout is used to create a window using one of the 4 supported frameworks to display and interact with your window. Supported frameworks include tkinter, Qt, WxPython, or Remi. The term "wrapper" is sometimes used for these kinds of packages.
Your FreeSimpleGUI code is simpler and shorter than writing directly using the underlying framework because FreeSimpleGUI implements much of the "boilerplate code" for you. Additionally, interfaces are simplified to require as little code as possible to get the desired result. Depending on the program and framework used, a FreeSimpleGUI program may require 1/2 to 1/10th amount of code to create an identical window using one of the frameworks directly.
While the goal is to encapsulate/hide the specific objects and code used by the GUI framework you are running on top of, if needed you can access the frameworks' dependent widgets and windows directly. If a setting or feature is not yet exposed or accessible using the FreeSimpleGUI APIs, you are not walled off from the framework. You can expand capabilities without directly modifying the FreeSimpleGUI package itself.
## Example 1 - The One-Shot Window
This type of program is called a "one-shot" window because the window is displayed one time, the values collected, and then it is closed. It doesn't remain open for a long time like you would in a Word Processor.
### Anatomy of a Simple FreeSimpleGUI Program
There are 5 sections to a FreeSimpleGUI program
```python
import FreeSimpleGUI as sg # Part 1 - The import
# Define the window's contents
layout = [ [sg.Text("What's your name?")], # Part 2 - The Layout
[sg.Input()],
[sg.Button('Ok')] ]
# Create the window
window = sg.Window('Window Title', layout) # Part 3 - Window Defintion
# Display and interact with the Window
event, values = window.read() # Part 4 - Event loop or Window.read call
# Do something with the information gathered
print('Hello', values[0], "! Thanks for trying FreeSimpleGUI")
# Finish up by removing from the screen
window.close() # Part 5 - Close the Window
```
The code produces this window
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-tkinter.jpg">
</p>
<hr>
## Example 2 - Interactive Window
In this example, our window will remain on the screen until the user closes the window or clicks the Quit button. The main difference between the one-shot window you saw earlier and an interactive window is the addition of an "Event Loop". The Event Loop reads events and inputs from your window. The heart of your application lives in the event loop.
```python
import FreeSimpleGUI as sg
# Define the window's contents
layout = [[sg.Text("What's your name?")],
[sg.Input(key='-INPUT-')],
[sg.Text(size=(40,1), key='-OUTPUT-')],
[sg.Button('Ok'), sg.Button('Quit')]]
# Create the window
window = sg.Window('Window Title', layout)
# Display and interact with the Window using an Event Loop
while True:
event, values = window.read()
# See if user wants to quit or window was closed
if event == sg.WINDOW_CLOSED or event == 'Quit':
break
# Output a message to the window
window['-OUTPUT-'].update('Hello ' + values['-INPUT-'] + "! Thanks for trying FreeSimpleGUI")
# Finish up by removing from the screen
window.close()
```
This is the window that Example 2 produces.
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Example2-1.jpg">
</p>
And here's what it looks like after you enter a value into the Input field and click the Ok button.
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Example2-2.jpg">
</p>
Let's take a quick look at some of the differences between this example and the one-shot window.
First, you'll notice differences in the layout. Two changes in particular are important. One is the addition of the `key` parameter to the `Input` element and one of the `Text` elements. A `key` is like a name for an element. Or, in Python terms, it's like a dictionary key. The `Input` element's key will be used as a dictionary key later in the code.
Another difference is the addition of this `Text` element:
```python
[sg.Text(size=(40,1), key='-OUTPUT-')],
```
There are 2 parameters, the `key` we already covered. The `size` parameter defines the size of the element in characters. In this case, we're indicating that this `Text` element is 40 characters wide, by 1 character high. Notice that there is no text string specified which means it'll be blank. You can easily see this blank row in the window that's created.
We also added a button, "Quit".
The Event Loop has our familiar `window.read()` call.
Following the read is this if statement:
```python
if event == sg.WINDOW_CLOSED or event == 'Quit':
break
```
This code is checking to see if the user closed the window by clicking the "X" or if they clicked the "Quit" button. If either of these happens, then the code will break out of the event loop.
If the window wasn't closed nor the Quit button clicked, then execution continues. The only thing that could have happened is the user clicked the "Ok" button. The last statement in the Event Loop is this one:
```python
window['-OUTPUT-'].update('Hello ' + values['-INPUT-'] + "! Thanks for trying FreeSimpleGUI")
```
This statement updates the `Text` element that has the key `-OUTPUT-` with a string. `window['-OUTPUT-']` finds the element with the key `-OUTPUT-`. That key belongs to our blank `Text` element. Once that element is returned from the lookup, then its `update` method is called. Nearly all elements have an `update` method. This method is used to change the value of the element or to change some configuration of the element.
If we wanted the text to be yellow, then that can be accomplished by adding a `text_color` parameter to the `update` method so that it reads:
```python
window['-OUTPUT-'].update('Hello ' + values['-INPUT-'] + "! Thanks for trying FreeSimpleGUI",
text_color='yellow')
```
After adding the `text_color` parameter, this is our new resulting window:
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Example2-3.jpg">
</p>
The parameters available for each element are documented in both the [call reference documentation](http://calls.FreeSimpleGUI.org) as well as the docstrings. FreeSimpleGUI has extensive documentation to help you understand all of the options available to you. If you lookup the `update` method for the `Text` element, you'll find this definition for the call:
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/TextUpdate.jpg">
</p>
As you can see several things can be changed for a `Text` element. The call reference documentation is a valuable resource that will make programming in FreeSimpleGUI, uhm, simple.
<hr>
## Jump Start! Get the Demo Programs & Demo Browser 🔎
The over 300 Demo Programs will give you a jump-start and provide many design patterns for you to learn how to use FreeSimpleGUI and how to integrate FreeSimpleGUI with other packages. By far the best way to experience these demos is using the Demo Browser. This tool enables you to search, edit and run the Demo Programs.
To get them installed quickly along with the Demo Browser, use `pip` to install `psgdemos`:
`python -m pip install psgdemos`
or if you're in Linux, Mac, etc, that uses `python3` instead of `python` to launch Python:
`python3 -m pip install psgdemos`
Once installed, launch the demo browser by typing `psgdemos` from the command line"
`psgdemos`
![SNAG-1543](https://user-images.githubusercontent.com/46163555/151877440-85ad9239-3219-4711-8cdf-9abc1501f05a.jpg)
-------------------------
## Layouts Are Funny LOL! 😆
Your window's layout is a "list of lists" (LOL). Windows are broken down into "rows". Each row in your window becomes a list in your layout. Concatenate together all of the lists and you've got a layout...a list of lists.
Here is the same layout as before with an extra `Text` element added to each row so that you can more easily see how rows are defined:
```python
layout = [ [sg.Text('Row 1'), sg.Text("What's your name?")],
[sg.Text('Row 2'), sg.Input()],
[sg.Text('Row 3'), sg.Button('Ok')] ]
```
Each row of this layout is a list of elements that will be displayed on that row in your window.
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/layout-with-rows.jpg">
</p>
Using lists to define your GUI has some huge advantages over how GUI programming is done using other frameworks. For example, you can use Python's list comprehension to create a grid of buttons in a single line of code.
These 3 lines of code:
```python
import FreeSimpleGUI as sg
layout = [[sg.Button(f'{row}, {col}') for col in range(4)] for row in range(4)]
event, values = sg.Window('List Comprehensions', layout).read(close=True)
```
produces this window which has a 4 x 4 grid of buttons:
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/4x4grid.jpg">
</p>
Recall how "fun" is one of the goals of the project. It's fun to directly apply Python's powerful basic capabilities to GUI problems. Instead of pages of code to create a GUI, it's a few (or often 1) lines of code.
## Collapsing Code
It's possible to condense a window's code down to a single line of code. The layout definition, window creation, display, and data collection can all be written in this line of code:
```python
event, values = sg.Window('Window Title', [[sg.Text("What's your name?")],[sg.Input()],[sg.Button('Ok')]]).read(close=True)
```
<p align="center">
<img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-tkinter.jpg">
</p>
The same window is shown and returns the same values as the example showing the sections of a FreeSimpleGUI program. Being able to do so much with so little enables you to quickly and easily add GUIs to your Python code. If you want to display some data and get a choice from your user, it can be done in a line of code instead of a page of code.
By using short-hand aliases, you can save even more space in your code by using fewer characters. All of the Elements have one or more shorter names that can be used. For example, the `Text` element can be written simply as `T`. The `Input` element can be written as `I` and the `Button` as `B`. Your single-line window code thus becomes:
```python
event, values = sg.Window('Window Title', [[sg.T("What's your name?")],[sg.I()],[sg.B('Ok')]]).read(close=True)
```
### Code Portability
FreeSimpleGUI is currently capable of running on 4 Python GUI Frameworks. The framework to use is specified using the import statement. Change the import and you'll change the underlying GUI framework. For some programs, no other changes are needed than the import statement to run on a different GUI framework. In the example above, changing the import from `FreeSimpleGUI` to `FreeSimpleGUIQt`, `FreeSimpleGUIWx`, `FreeSimpleGUIWeb` will change the framework.
| Import Statement | Resulting Window |
|--|--|
| FreeSimpleGUI | ![](https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-tkinter.jpg) |
| FreeSimpleGUIQt | ![](https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-Qt.jpg) |
| FreeSimpleGUIWx | ![](https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-WxPython.jpg) |
| FreeSimpleGUIWeb | ![](https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ex1-Remi.jpg) |
Porting GUI code from one framework to another (e.g. moving your code from tkinter to Qt) usually requires a rewrite of your code. FreeSimpleGUI is designed to enable you to have easy movement between the frameworks. Sometimes some changes are required of you, but the goal is to have highly portable code with minimal changes.
Some features, like a System Tray Icon, are not available on all of the ports. The System Tray Icon feature is available on the Qt and WxPython ports. A simulated version is available on tkinter. There is no support for a System Tray icon in the FreeSimpleGUIWeb port.
## Runtime Environments
| Environment | Supported |
|--|--|
| Python | Python 3.4+ |
| Operating Systems | Windows, Linux, Mac |
| Hardware | Desktop PCs, Laptops, Raspberry Pi, Android devices running PyDroid3 |
| Online | repli.it, Trinket.com (both run tkinter in a browser) |
| GUI Frameworks | tkinter, pyside2, WxPython, Remi |
## Integrations
Among the more than 200 "Demo Programs", you'll find examples of how to integrate many popular Python packages into your GUI.
Want to embed a Matplotlib drawing into your window? No problem, copy the demo code and instantly have a Matplotlib drawing of your dreams into your GUI.
These packages and more are ready for you to put into your GUI as there are demo programs or a demo repo available for each:
Package | Description |
|--|--|
Matplotlib | Many types of graphs and plots |
OpenCV | Computer Vision (often used for AI) |
VLC | Video playback |
pymunk | Physics engine|
psutil | System environment statistics |
prawn | Reddit API |
json | FreeSimpleGUI wraps a special API to store "User Settings" |
weather | Integrates with several weather APIs to make weather apps |
mido | MIDI playback |
beautiful soup | Web Scraping (GitHub issue watcher example) |
<hr>
# Installing 💾
Two common ways of installing FreeSimpleGUI:
1. pip to install from PyPI
2. Download the file FreeSimpleGUI.py and place in your application's folder
### Pip Installing & Upgrading
The current suggested way of invoking the `pip` command is by running it as a module using Python. Previously the command `pip` or `pip3` was directly onto a command-line / shell. The suggested way
Initial install for Windows:
`python -m pip install FreeSimpleGUI`
Initial install for Linux and MacOS:
`python3 -m pip install FreeSimpleGUI`
To upgrade using `pip`, you simply add 2 parameters to the line `--upgrade --no-cache-dir`.
Upgrade installation on Windows:
`python -m pip install --upgrade --no-cache-dir FreeSimpleGUI`
Upgrade for Linux and MacOS:
`python3 -m pip install --upgrade --no-cache-dir FreeSimpleGUI`
### Single File Installing
FreeSimpleGUI was created as a single .py file so that it would be very easy for you to install it, even on systems that are not connected to the internet like a Raspberry Pi. It's as simple as placing the FreeSimpleGUI.py file into the same folder as your application that imports it. Python will use your local copy when performing the import.
When installing using just the .py file, you can get it from either PyPI or if you want to run the most recent unreleased version then you'll download it from GitHub.
To install from PyPI, download either the wheel or the .gz file and unzip the file. If you rename the .whl file to .zip you can open it just like any normal zip file. You will find the FreeSimpleGUI.py file in one of the folders. Copy this file to your application's folder and you're done.
The PyPI link for the tkinter version of FreeSimpleGUI is:
https://pypi.org/project/FreeSimpleGUI/#files
The GitHub repo's latest version can be found here:
https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/FreeSimpleGUI.py
Now some of you are thinking, "yea, but, wait, having a single huge source file is a terrible idea". And, yea, *sometimes* it can be a terrible idea. In this case, the benefits greatly outweighed the downside. Lots of concepts in computer science are tradeoffs or subjective. As much as some would like it to be, not everything is black and white. Many times the answer to a question is "it depends".
## Galleries 🎨
Work on a more formal gallery of user-submitted GUIs as well as those found on GitHub is underway but as of this writing it's not complete. There are currently 2 places you can go to see some screenshots in a centralized way. Hopefully, a Wiki or other mechanism can be released soon to do justice to the awesome creations people are making.
### User Submitted Gallery
The first is a [user submitted screenshots issue](https://github.com/spyoungtech/FreeSimpleGUI/issues/10) located on the GitHub. It's an informal way for people to show off what they've made. It's not ideal, but it was a start.
### Massive Scraped GitHub Images
The second is a [massive gallery of over 3,000 images](https://www.dropbox.com/sh/g67ms0darox0i2p/AAAMrkIM6C64nwHLDkboCWnaa?dl=0) scraped from 1,000 projects on GitHub that are reportedly using FreeSimpleGUI. It's not been hand-filtered and there are plenty of old screenshots that were used in the early documentation. But, you may find something in there that sparks your imagination.
<hr>
# Uses for FreeSimpleGUI 🔨
The following sections showcase a fraction of the uses for FreeSimpleGUI. There are over 1,000 projects on GitHub alone that use FreeSimpleGUI. It's truly amazing how possibilities have opened up for so many people. Many users have spoken about previously attempting to create a GUI in Python and failing, but finally achieving their dreams when they tried FreeSimpleGUI.
## Your First GUI
Of course one of the best uses of FreeSimpleGUI is getting you into making GUIs for your Python projects. You can start as small as requesting a filename. For this, you only need to make a single call to one of the "high-level functions" called `popup`. There are all kinds of popups, some collect information.
`popup` on itself makes a window to display information. You can pass multiple parameters just like a print. If you want to get information, then you will call functions that start with `popup_get_` such as `popup_get_filename`.
Adding a single line to get a filename instead of specifying a filename on the command line can transform your program into one that "normal people" will feel comfortable using.
```python
import FreeSimpleGUI as sg
filename = sg.popup_get_file('Enter the file you wish to process')
sg.popup('You entered', filename)
```
This code will display 2 popup windows. One to get the filename, which can be browsed to or pasted into the input box.
<p align="center">
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/First_GUI1.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/First_GUI1.jpg" alt="img" width="400px"></a>
</p>
The other window will output what is collected.
<p align="center">
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/First_GUI2.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/First_GUI2.jpg" alt="img" width="175px"></a>
</p>
<br>
## Rainmeter-Style Windows
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/RainmeterStyleWidgets.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/RainmeterStyleWidgets.jpg" alt="img" align="right" width="500px"></a>
The default settings for GUI frameworks don't tend to produce the nicest looking windows. However, with some attention to detail, you can do several things to make windows look attractive. FreeSimpleGUI makes it easier to manipulate colors and features like removing the title bar. The result is windows that don't look like your typical tkinter windows.
Here is an example of how you can create windows that don't look like your typical tkinter in windows. In this example, the windows have their titlebars removed. The result is windows that look much like those found when using Rainmeter, a desktop widget program.
<br><br>
You can easily set the transparency of a window as well. Here are more examples of desktop widgets in the same Rainmeter style. Some are dim appearing because they are semi-transparent.
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/semi-transparent.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/semi-transparent.jpg" alt="img" align="right" width="500px"></a>
Both of these effects; removing the titlebar and making a window semi-transparent, are achieved by setting 2 parameters when creating the window. This is an example of how FreeSimpleGUI enables easy access to features. And because FreeSimpleGUI code is portable across the GUI frameworks, these same parameters work for the other ports such as Qt.
Changing the Window creation call in Example 1 to this line of code produces a similar semi-transparent window:
```python
window = sg.Window('My window', layout, no_titlebar=True, alpha_channel=0.5)
```
## Games
While not specifically written as a game development SDK, FreeSimpleGUI makes the development of some games quite easy.
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Chess.png"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Chess.png" alt="img" align="right" width="500px"></a>
This Chess program not only plays chess, but it integrates with the Stockfish chess-playing AI.
<br><br><br><br><br><br><br><br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Minesweeper.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Minesweeper.gif" alt="img" align="right" width="500px"></a>
Several variants of Minesweeper have been released by users.
<br><br><br><br>
<br><br><br><br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/minesweeper_israel_dryer.png"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/minesweeper_israel_dryer.png" alt="img" align="right" width="500px"></a>
<br><br><br><br><br><br><br><br><br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Solitaire.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Solitaire.gif" alt="img" align="right" width="500px"></a>
<br><br>
Card games work well with FreeSimpleGUI as manipulating images is simple when using the FreeSimpleGUI `Graph` element.
While not specifically written as a game development SDK, FreeSimpleGUI makes development of some games quite easy.
<br><br>
<br><br>
<br><br><br>
## Media Capture and Playback
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/OpenCV.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/OpenCV.jpg" alt="img" align="right" width="400px"></a>
Capturing and displaying video from your webcam in a GUI is 4 lines of FreeSimpleGUI code. Even more impressive is that these 4 lines of code work with the tkinter, Qt, and Web ports. You can display your webcam, in realtime, in a browser using the same code that displays the image using tkinter.
Media playback, audio and video, can also be achieved using the VLC player. A demo application is provided to you so that you have a working example to start from. Everything you see in this readme is available to you as a starting point for your own creations.
<br><br><br><br><br>
<br><br><br><br><br>
<br><br>
## Artificial Intelligence
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/YOLO_GIF.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/YOLO_GIF.gif" alt="img" align="right" width="500px"></a>
AI and Python have long been a recognized superpower when the two are paired together. What's often missing however is a way for users to interact with these AI algorithms familiarly, using a GUI.
These YOLO demos are a great example of how a GUI can make a tremendous difference in interacting with AI algorithms. Notice two sliders at the bottom of these windows. These 2 sliders change a couple of the parameters used by the YOLO algorithm.
If you were tuning your YOLO demo using only the command line, you would need to set the parameters, once, when you launch the application, see how they perform, stop the application, change the parameters, and finally restart the application with the new parameters.
<br><br><br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/YOLO%20Object%20Detection.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/YOLO%20Object%20Detection.jpg" alt="img" align="right" width="500px"></a>
Contrast those steps against what can be done using a GUI. A GUI enables you to modify these parameters in real-time. You can immediately get feedback on how they are affecting the algorithm.
<br><br><br><br><br>
<br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Colorizer.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Colorizer.jpg" alt="img" align="right" width="500px"></a>
There are SO many AI programs that have been published that are command-line driven. This in itself isn't a huge hurdle, but it's enough of a "pain in the ass" to type/paste the filename you want to colorize on the command line, run the program, then open the resulting output file in a file viewer.
GUIs have the power to **change the user experience**, to fill the "GUI Gap". With this colorizer example, the user only needs to supply a folder full of images, and then click on an image to both colorize and display the result.
The program/algorithm to do the colorization was freely available, ready to use. What was missing is the ease of use that a GUI could bring.
<hr>
## Graphing
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/CPU%20Cores%20Dashboard%202.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/CPU%20Cores%20Dashboard%202.gif" alt="img" align="right" width="500px"></a>
Displaying and interacting with data in a GUI is simple with FreeSimpleGUI. You have several options.
You can use the built-in drawing/graphing capabilities to produce custom graphs. This CPU usage monitor uses the `Graph` element
<br><br>
<br><br>
<br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Matplotlib.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Matplotlib.jpg" alt="img" align="right" width="500px"></a>
Matplotlib is a popular choice with Python users. FreeSimpleGUI can enable you to embed Matplotlib graphs directly into your GUI window. You can even embed the interactive controls into your window if you want to retain the Matplotlib interactive features.
<br><br>
<br><br>
<br><br>
<br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Matplotlib2.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Matplotlib2.jpg" alt="img" align="right" width="500px"></a>
Using FreeSimpleGUI's color themes, you can produce graphs that are a notch above default graphs that most people create in Matplotlib.
<br><br>
<br><br>
<br><br>
<br><br>
<br><br>
<br><br>
<br><br>
<hr>
## Front-ends
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/JumpCutter.png"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/JumpCutter.png" alt="img" align="right" width="500px"></a>
The "GUI Gap" mentioned earlier can be easily solved using FreeSimpleGUI. You don't even need to have the source code to the program you wish to add a GUI onto. A "front-end" GUI is one that collects information that is then passed to a command-line application.
Front-end GUIs are a fantastic way for a programmer to distribute an application that users were reluctant to use previously because they didn't feel comfortable using a command-line interface. These GUIs are your only choice for command-line programs that you don't have access to the source code for.
This example is a front-end for a program called "Jump Cutter". The parameters are collected via the GUI, a command-line is constructed using those parameters, and then the command is executed with the output from the command-line program being routed to the GUI interface. In this example, you can see in yellow the command that was executed.
<br><br>
<hr>
## Raspberry Pi
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Raspberry%20Pi.jpg"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Raspberry%20Pi.jpg" alt="img" align="right" width="500px"></a>
Because FreeSimpleGUI is compatible back to Python 3.4, it is capable of creating a GUI for your Raspberry Pi projects. It works particularly well when paired with a touchscreen. You can also use FreeSimpleGUIWeb to control your Pi if it doesn't have a monitor attached.
<br><br>
<br><br>
<br><br>
<br><br><br>
<hr>
## Easy Access to Advanced Features
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Customized%20Titlebar.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Customized%20Titlebar.gif" alt="img" align="right" width="500px"></a>
Because it's very easy to access many of the underlying GUI frameworks' features, it's possible to piece together capabilities to create applications that look nothing like those produced using the GUI framework directly.
For example, it's not possible to change the color/look-and-feel of a titlebar using tkinter or the other GUI packages, but with FreeSimpleGUI it's easy to create windows that appear as if they have a custom titlebar.
<br><br><br>
<a href="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Desktop%20Bouncing%20Balls.gif"><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/Desktop%20Bouncing%20Balls.gif" alt="img" align="right" width="500px"></a>
Unbelievably, this window is using tkinter to achieve what appears to be something like a screensaver.
On windows, tkinter can completely remove the background from your application. Once again, FreeSimpleGUI makes accessing these capabilities trivial. Creating a transparent window requires adding a single parameter to the call that creates your `Window`. One parameter change can result in a simple application with this effect:
You can interact with everything on your desktop, clicking through a full-screen window.
<hr>
# Themes
Tired of the default grey GUIs? FreeSimpleGUI makes it trivial for your window to look nice by making a single call to the `theme` function. There are over 150 different color themes available for you to choose:
<p align="center">
<a href=""><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/ThemePreview.jpg" alt="img" width="900px"></a>
</p>
With most GUI frameworks, you must specify the color for every widget you create. FreeSimpleGUI takes this chore from you and will automatically color the Elements to match your chosen theme.
To use a theme, call the `theme` function with the name of the theme before creating your window. You can add spaces for readability. To set the theme to "Dark Grey 9":
```python
import FreeSimpleGUI as sg
sg.theme('dark grey 9')
```
This single line of code changes the window's appearance entirely:
<p align="center">
<a href=""><img src="https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_readme/DarkGrey.jpg" alt="img" width="400px"></a>
</p>
The theme changed colors of the background, text, input background, input text, and button colors. In other GUI packages, to change color schemes like this, you would need to specify the colors of each widget individually, requiring numerous changes to your code.
<hr>
# Distribution
Want to share your FreeSimpleGUI program with friends and family that don't have Python installed on their computer? Try the GUI front-end for PyInstaller that you'll find in the [psgcompiler](https://github.com/FreeSimpleGUI/psgcompiler) project.
![](https://raw.githubusercontent.com/FreeSimpleGUI/psgcompiler/main/screenshot_for_readme/psgcompiler_screenshot.jpg?token=ALAGMY3Z33WHFX3RTFXEZ73BTEUPO)
<hr>
# Support 💪
Your first stop should be the [documentation](http://www.FreeSimpleGUI.org) and [demo programs](http://Demos.FreeSimpleGUI.org).
Be sure and install the Demo Browser (instructions in the Cookbook) so that you can search and run the 100s of demo programs.
![](https://raw.githubusercontent.com/spyoungtech/FreeSimpleGUI/main/images/for_cookbook/Project_Browser_Main_Window_Explained.jpg)
If you still have a question or need help... no problem... help is available to you, at no cost. Simply [file an Issue](http://Issues.FreeSimpleGUI.org) on the FreeSimpleGUI GitHub repo and you'll get help.
Nearly all software companies have a form that accompanies bug reports. It's not a bad trade... fill in the form, get free software support. This information helps get you an answer efficiently.
In addition to requesting information such as the version numbers of FreeSimpleGUI and underlying GUI frameworks, you're also given a checklist of items that may help you solve your problem.
***Please fill in the form.*** It may feel pointless to you. It may feel painful, despite it taking just a moment. It helps get you a solution faster. If it wasn't useful and necessary information to help you get a speedy reply and fix, you wouldn't be asked to fill it out. "Help me help you".
© Copyright 2024, FreeSimpleGUI authors
© Copyright 2021, 2022 PySimpleGui

View File

@ -0,0 +1,7 @@
This project was forked from the LGPL3-licensed PySimpleGUI project. All code from PySimpleGUI is copyrighted
by the original author(s). Subsequent modifications (effective April 3, 2024) are the Copyright of FreeSimpleGUI (Spencer Phillip Young)
and are distributed under the same license terms of the LGPL3, as found in license.txt
'PySimpleGUI' is a trademark registered in the United States to Satisfice Labs LLC
FreeSimpleGUI has no official association with PySimpleGUI or Satisfice Labs LLC and no such associations are expressed or implied.

View File

@ -0,0 +1,85 @@
../../../bin/fsghelp,sha256=WVFO3I2DYgoxWAHBEgcSQXIZ_KkXXTcjMU4b6aObKmI,275
../../../bin/fsgissue,sha256=HcdFxoTCDKeSOI7yvG2FFpJ8TdzjUOmgaJ9VaR0vXr8,293
../../../bin/fsgmain,sha256=TSjOMEwNTTznQcT4lT_Zx2erk6dDw0j_ImbdCawqMuc,283
../../../bin/fsgsettings,sha256=Ut1JQGEyy0TJziK_hiT_nSe2-lRfe3BfuKq_eGCm14g,313
../../../bin/fsgver,sha256=jDoq8mbztYzKuVt7LRq-5xMEieHBXIO3_qC_9ZQQ3QQ,287
FreeSimpleGUI/__init__.py,sha256=5L5sypf3BgVEsQNmaJBJ3akB3Z6PDhpxJkkiuhWpJ54,1452977
FreeSimpleGUI/__pycache__/__init__.cpython-312.pyc,,
FreeSimpleGUI/__pycache__/_utils.cpython-312.pyc,,
FreeSimpleGUI/__pycache__/themes.cpython-312.pyc,,
FreeSimpleGUI/__pycache__/tray.cpython-312.pyc,,
FreeSimpleGUI/__pycache__/window.cpython-312.pyc,,
FreeSimpleGUI/_utils.py,sha256=tLe7TSFJ0G0XYSpeR6LrasbGS-ah2LZGx-IZPrSc3NY,5477
FreeSimpleGUI/elements/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
FreeSimpleGUI/elements/__pycache__/__init__.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/base.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/button.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/calendar.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/canvas.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/checkbox.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/column.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/combo.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/error.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/frame.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/graph.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/helpers.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/image.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/input.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/list_box.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/menu.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/multiline.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/option_menu.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/pane.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/progress_bar.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/radio.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/separator.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/sizegrip.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/slider.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/spin.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/status_bar.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/stretch.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/tab.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/table.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/text.cpython-312.pyc,,
FreeSimpleGUI/elements/__pycache__/tree.cpython-312.pyc,,
FreeSimpleGUI/elements/base.py,sha256=hH3TwHxzwvwwkULL3dYKkvFQuCIZEGfX5DI5JEH6H7Y,46495
FreeSimpleGUI/elements/button.py,sha256=qVdeds04ZN1rd37I9haNcZZLtVfz45I2qQWWCZU2cn4,48509
FreeSimpleGUI/elements/calendar.py,sha256=AkWR_rmDTLy566xjSgGze0edulx34fnvblWfb3RNaoI,8893
FreeSimpleGUI/elements/canvas.py,sha256=I8gCLO7PJu3qJdxFuoCKX6rXflZk6r-SZm8O6effnWA,5418
FreeSimpleGUI/elements/checkbox.py,sha256=43da7_rt6W8r_b21o9m6meC4EPBEqoeZEOS4EkrF8FY,11716
FreeSimpleGUI/elements/column.py,sha256=2YUC4fjzgFd3rDlb2fW-H9nhMaaRGNWAkx_XK2k0Wms,20545
FreeSimpleGUI/elements/combo.py,sha256=Y6nycCUr-qb4c9s7rY7e0knBLuQRZP3LUNldYkFLLZA,15917
FreeSimpleGUI/elements/error.py,sha256=FhHL-U1iZxrr8dKWfSyShGHLZVj4h90lsXYbPhIFvFU,1745
FreeSimpleGUI/elements/frame.py,sha256=RWamrZ3APSFppOMge99XfjWF2LyCsWHjehlZCjyFihw,12864
FreeSimpleGUI/elements/graph.py,sha256=3rPauIfFWksCUEBPFuhDXlL563SbezV67olGn7-p5UI,38466
FreeSimpleGUI/elements/helpers.py,sha256=e5jJlOHZmYYd4PE93fjXVsk4xWgSYfpEKMwKSjN6VBs,10533
FreeSimpleGUI/elements/image.py,sha256=L7UOKv7PsdBonPHXRIouMhR1SfIXKElaHOZ0BdXLeqI,14477
FreeSimpleGUI/elements/input.py,sha256=SN-g4AIa190BVOAKsBq9gxLQV522BU7RulA0h0yj8a4,15929
FreeSimpleGUI/elements/list_box.py,sha256=9wBVu1BCA4yxmfgAdR_5VIJmMLvtLJvge2ALDtVMBzg,20343
FreeSimpleGUI/elements/menu.py,sha256=fCmpvheZ-fwfhECm66l-3v7JtuQ0EGJu7KOT7aVPFRo,10768
FreeSimpleGUI/elements/multiline.py,sha256=saGIJy45zW-YN_YC7R4qIQg6lAMC7ZPzUQGCOzRPxkM,35824
FreeSimpleGUI/elements/option_menu.py,sha256=znW33j0LtRgawM7sZaUSWCCF6GP398rXq738-tIu3Uo,7787
FreeSimpleGUI/elements/pane.py,sha256=yjKOipla75YAmNftALMaDVxQjnnQAF47owHpd7W9tjM,5920
FreeSimpleGUI/elements/progress_bar.py,sha256=JQpiZSJBqfpX9wocyJCWJHDX1s61cZy1z9o9u9jj5f4,14151
FreeSimpleGUI/elements/radio.py,sha256=01wlFAZXAJgZuq34_cOi5URdS1ysFUYqZN5YWI8fDHk,11989
FreeSimpleGUI/elements/separator.py,sha256=fXaF1EQ7VyxGhmgNf_W-21XiNi2MNuVtxrMnz0xv6Xw,3540
FreeSimpleGUI/elements/sizegrip.py,sha256=2FCYMUhYksvktdNmRh-bcijajHGsW5g3DjGSz3An9yo,1888
FreeSimpleGUI/elements/slider.py,sha256=NsHujhKNn4fLxZ7Gj4TPm9j17mdWhFrgFE0IFrqviG0,9372
FreeSimpleGUI/elements/spin.py,sha256=MwNe4eqsy2aadige8-aOLQt6fVXLyRu_UjQqa9i8u2w,11301
FreeSimpleGUI/elements/status_bar.py,sha256=QCPfogXGqRDux68OeYIss0MKhCMju3HhXri7vq5jvUY,8045
FreeSimpleGUI/elements/stretch.py,sha256=AFKy526Me7OBDZeo8bHNul_vyTpNqFdmQ4I5Zt2ZbRY,1052
FreeSimpleGUI/elements/tab.py,sha256=zCzjrErEd2aguK0GwOQzyFiZ7gHAOmUkG4Zdqcv0wlY,31730
FreeSimpleGUI/elements/table.py,sha256=bNbw33HZF1PlYnpeFdDWFXjjS1gLsyOvBXJVqlni0SU,25103
FreeSimpleGUI/elements/text.py,sha256=e-qaRxeoKimmKBS8PtP7TQCI0am9lCshK9gVxEBkQ-A,19040
FreeSimpleGUI/elements/tree.py,sha256=JsiQQCqiwlMXHCSFk2XOA9J1JJHrR-HryToZ4AKl-4Y,23233
FreeSimpleGUI/themes.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
FreeSimpleGUI/tray.py,sha256=4DLBhNlqvsxOnXCLk0J1RNgk802c843Db02IFWI2pfA,23472
FreeSimpleGUI/window.py,sha256=-Mt8LcGot0YgnuU8NUdz7U1Vt8zGnex1d6vXhuQzQRg,129275
freesimplegui-5.2.0.post1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
freesimplegui-5.2.0.post1.dist-info/METADATA,sha256=_rNuGjuOrbRIdjZSk4NIwiOIkOfYxY_87TG_rqX1M9Y,45947
freesimplegui-5.2.0.post1.dist-info/NOTICE,sha256=0T7Snn55kaZeRxflGnNf7aWVPKSB33AyJJATGJjr1s4,551
freesimplegui-5.2.0.post1.dist-info/RECORD,,
freesimplegui-5.2.0.post1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
freesimplegui-5.2.0.post1.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
freesimplegui-5.2.0.post1.dist-info/entry_points.txt,sha256=p_sAKeadMfAS9Hj--aOW1UYMePpGQfTEbLshdivGLQY,250
freesimplegui-5.2.0.post1.dist-info/top_level.txt,sha256=fBOHNraAo4iLHn0M0-LnQ_smTGdoLwgBXZ96a-7zXks,14

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: setuptools (76.0.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,6 @@
[console_scripts]
fsghelp = FreeSimpleGUI:main_sdk_help
fsgissue = FreeSimpleGUI:main_open_github_issue
fsgmain = FreeSimpleGUI:_main_entry_point
fsgsettings = FreeSimpleGUI:main_global_pysimplegui_settings
fsgver = FreeSimpleGUI:main_get_debug_data

View File

@ -0,0 +1,19 @@
Copyright (c) 2013-2024 Python Charmers, Australia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,112 @@
Metadata-Version: 2.1
Name: future
Version: 1.0.0
Summary: Clean single-source support for Python 3 and 2
Home-page: https://python-future.org
Author: Ed Schofield
Author-email: ed@pythoncharmers.com
License: MIT
Project-URL: Source, https://github.com/PythonCharmers/python-future
Keywords: future past python3 migration futurize backport six 2to3 modernize pasteurize 3to2
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved
Classifier: License :: OSI Approved :: MIT License
Classifier: Development Status :: 6 - Mature
Classifier: Intended Audience :: Developers
Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*
License-File: LICENSE.txt
future: Easy, safe support for Python 2/3 compatibility
=======================================================
``future`` is the missing compatibility layer between Python 2 and Python
3. It allows you to use a single, clean Python 3.x-compatible codebase to
support both Python 2 and Python 3 with minimal overhead.
It is designed to be used as follows::
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import (
bytes, dict, int, list, object, range, str,
ascii, chr, hex, input, next, oct, open,
pow, round, super,
filter, map, zip)
followed by predominantly standard, idiomatic Python 3 code that then runs
similarly on Python 2.6/2.7 and Python 3.3+.
The imports have no effect on Python 3. On Python 2, they shadow the
corresponding builtins, which normally have different semantics on Python 3
versus 2, to provide their Python 3 semantics.
Standard library reorganization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``future`` supports the standard library reorganization (PEP 3108) through the
following Py3 interfaces:
>>> # Top-level packages with Py3 names provided on Py2:
>>> import html.parser
>>> import queue
>>> import tkinter.dialog
>>> import xmlrpc.client
>>> # etc.
>>> # Aliases provided for extensions to existing Py2 module names:
>>> from future.standard_library import install_aliases
>>> install_aliases()
>>> from collections import Counter, OrderedDict # backported to Py2.6
>>> from collections import UserDict, UserList, UserString
>>> import urllib.request
>>> from itertools import filterfalse, zip_longest
>>> from subprocess import getoutput, getstatusoutput
Automatic conversion
--------------------
An included script called `futurize
<https://python-future.org/automatic_conversion.html>`_ aids in converting
code (from either Python 2 or Python 3) to code compatible with both
platforms. It is similar to ``python-modernize`` but goes further in
providing Python 3 compatibility through the use of the backported types
and builtin functions in ``future``.
Documentation
-------------
See: https://python-future.org
Credits
-------
:Author: Ed Schofield, Jordan M. Adler, et al
:Sponsor: Python Charmers: https://pythoncharmers.com
:Others: See docs/credits.rst or https://python-future.org/credits.html
Licensing
---------
Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.

View File

@ -0,0 +1,417 @@
../../../bin/futurize,sha256=wZkVTZPjh9pqgHWdXiY4PtF41jJC6xYhZZS7VHKW6mg,260
../../../bin/pasteurize,sha256=SFA2lYIeT5_RFK7CVmRbuXsBjTOhZjppZ8RQD4ReB-Y,262
future-1.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
future-1.0.0.dist-info/LICENSE.txt,sha256=vroEEDw6n_-Si1nHfV--sDo-S9DXrD0eH4mUoh4w7js,1075
future-1.0.0.dist-info/METADATA,sha256=MNNaUKMRbZAmVYq5Am_-1NtYZ1ViECpSa8VsQjXbkT4,3959
future-1.0.0.dist-info/RECORD,,
future-1.0.0.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
future-1.0.0.dist-info/entry_points.txt,sha256=U9LtP60KSNXoj58mzV5TbotBF371gTWzrKrzJIH80Kw,88
future-1.0.0.dist-info/top_level.txt,sha256=DT0C3az2gb-uJaj-fs0h4WwHYlJVDp0EvLdud1y5Zyw,38
future/__init__.py,sha256=T9PNLu6ycmVtpETLxRurmufuRAaosICWmWdAExZb5a8,2938
future/__pycache__/__init__.cpython-312.pyc,,
future/backports/__init__.py,sha256=5QXvQ_jc5Xx6p4dSaHnZXPZazBEunKDKhbUjxZ0XD1I,530
future/backports/__pycache__/__init__.cpython-312.pyc,,
future/backports/__pycache__/_markupbase.cpython-312.pyc,,
future/backports/__pycache__/datetime.cpython-312.pyc,,
future/backports/__pycache__/misc.cpython-312.pyc,,
future/backports/__pycache__/socket.cpython-312.pyc,,
future/backports/__pycache__/socketserver.cpython-312.pyc,,
future/backports/__pycache__/total_ordering.cpython-312.pyc,,
future/backports/_markupbase.py,sha256=MDPTCykLq4J7Aea3PvYotATEE0CG4R_SjlxfJaLXTJM,16215
future/backports/datetime.py,sha256=jITCStolfadhCEhejFd99wCo59mBDF0Ruj8l7QcG7Ms,75553
future/backports/email/__init__.py,sha256=eH3AJr3FkuBy_D6yS1V2K76Q2CQ93y2zmAMWmn8FbHI,2269
future/backports/email/__pycache__/__init__.cpython-312.pyc,,
future/backports/email/__pycache__/_encoded_words.cpython-312.pyc,,
future/backports/email/__pycache__/_header_value_parser.cpython-312.pyc,,
future/backports/email/__pycache__/_parseaddr.cpython-312.pyc,,
future/backports/email/__pycache__/_policybase.cpython-312.pyc,,
future/backports/email/__pycache__/base64mime.cpython-312.pyc,,
future/backports/email/__pycache__/charset.cpython-312.pyc,,
future/backports/email/__pycache__/encoders.cpython-312.pyc,,
future/backports/email/__pycache__/errors.cpython-312.pyc,,
future/backports/email/__pycache__/feedparser.cpython-312.pyc,,
future/backports/email/__pycache__/generator.cpython-312.pyc,,
future/backports/email/__pycache__/header.cpython-312.pyc,,
future/backports/email/__pycache__/headerregistry.cpython-312.pyc,,
future/backports/email/__pycache__/iterators.cpython-312.pyc,,
future/backports/email/__pycache__/message.cpython-312.pyc,,
future/backports/email/__pycache__/parser.cpython-312.pyc,,
future/backports/email/__pycache__/policy.cpython-312.pyc,,
future/backports/email/__pycache__/quoprimime.cpython-312.pyc,,
future/backports/email/__pycache__/utils.cpython-312.pyc,,
future/backports/email/_encoded_words.py,sha256=m1vTRfxAQdg4VyWO7PF-1ih1mmq97V-BPyHHkuEwSME,8443
future/backports/email/_header_value_parser.py,sha256=GmSdr5PpG3xzedMiElSJOsQ6IwE3Tl5SNwp4m6ZT4aE,104692
future/backports/email/_parseaddr.py,sha256=KewEnos0YDM-SYX503z7E1MmVbG5VRaKjxjcl0Ipjbs,17389
future/backports/email/_policybase.py,sha256=2lJD9xouiz4uHvWGQ6j1nwlwWVQGwwzpy5JZoeQqhUc,14647
future/backports/email/base64mime.py,sha256=gXZFxh66jk6D2UqAmjRbmmyhOXbGUWZmFcdVOIolaYE,3761
future/backports/email/charset.py,sha256=CfE4iV2zAq6MQC0CHXHLnwTNW71zmhNITbzOcfxE4vY,17439
future/backports/email/encoders.py,sha256=Nn4Pcx1rOdRgoSIzB6T5RWHl5zxClbf32wgE6D0tUt8,2800
future/backports/email/errors.py,sha256=tRX8PP5g7mk2bAxL1jTCYrbfhD2gPZFNrh4_GJRM8OQ,3680
future/backports/email/feedparser.py,sha256=bvmhb4cdY-ipextPK2K2sDgMsNvTspmuQfYyCxc4zSc,22736
future/backports/email/generator.py,sha256=lpaLhZHneguvZ2QgRu7Figkjb7zmY28AGhj9iZTdI7s,19520
future/backports/email/header.py,sha256=uBHbNKO-yx5I9KBflernJpyy3fX4gImCB1QE7ICApLs,24448
future/backports/email/headerregistry.py,sha256=ZPbvLKXD0NMLSU4jXlVHfGyGcLMrFm-GQVURu_XHj88,20637
future/backports/email/iterators.py,sha256=kMRYFGy3SVVpo7HG7JJr2ZAlOoaX6CVPzKYwDSvLfV0,2348
future/backports/email/message.py,sha256=I6WW5cZDza7uwLOGJSvsDhGZC9K_Q570Lk2gt_vDUXM,35237
future/backports/email/mime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/backports/email/mime/__pycache__/__init__.cpython-312.pyc,,
future/backports/email/mime/__pycache__/application.cpython-312.pyc,,
future/backports/email/mime/__pycache__/audio.cpython-312.pyc,,
future/backports/email/mime/__pycache__/base.cpython-312.pyc,,
future/backports/email/mime/__pycache__/image.cpython-312.pyc,,
future/backports/email/mime/__pycache__/message.cpython-312.pyc,,
future/backports/email/mime/__pycache__/multipart.cpython-312.pyc,,
future/backports/email/mime/__pycache__/nonmultipart.cpython-312.pyc,,
future/backports/email/mime/__pycache__/text.cpython-312.pyc,,
future/backports/email/mime/application.py,sha256=m-5a4mSxu2E32XAImnp9x9eMVX5Vme2iNgn2dMMNyss,1401
future/backports/email/mime/audio.py,sha256=2ognalFRadcsUYQYMUZbjv5i1xJbFhQN643doMuI7M4,2815
future/backports/email/mime/base.py,sha256=wV3ClQyMsOqmkXSXbk_wd_zPoPTvBx8kAIzq3rdM4lE,875
future/backports/email/mime/image.py,sha256=DpQk1sB-IMmO43AF4uadsXyf_y5TdEzJLfyhqR48bIw,1907
future/backports/email/mime/message.py,sha256=pFsMhXW07aRjsLq1peO847PApWFAl28-Z2Z7BP1Dn74,1429
future/backports/email/mime/multipart.py,sha256=j4Lf_sJmuwTbfgdQ6R35_t1_ha2DynJBJDvpjwbNObE,1699
future/backports/email/mime/nonmultipart.py,sha256=Ciba1Z8d2yLDDpxgDJuk3Bb-TqcpE9HCd8KfbW5vgl4,832
future/backports/email/mime/text.py,sha256=zV98BjoR4S_nX8c47x43LnsnifeGhIfNGwSAh575bs0,1552
future/backports/email/parser.py,sha256=NpTjmvjv6YDH6eImMJEYiIn_K7qe9-pPz3DmzTdMZUU,5310
future/backports/email/policy.py,sha256=gpcbhVRXuCohkK6MUqopTs1lv4E4-ZVUO6OVncoGEJE,8823
future/backports/email/quoprimime.py,sha256=w93W5XgdFpyGaDqDBJrnXF_v_npH5r20WuAxmrAzyQg,10923
future/backports/email/utils.py,sha256=vpfN0E8UjNbNw-2NFBQGCo4TNgrghMsqzpEYW5C_fBs,14270
future/backports/html/__init__.py,sha256=FKwqFtWMCoGNkhU97OPnR1fZSh6etAKfN1FU1KvXcV8,924
future/backports/html/__pycache__/__init__.cpython-312.pyc,,
future/backports/html/__pycache__/entities.cpython-312.pyc,,
future/backports/html/__pycache__/parser.cpython-312.pyc,,
future/backports/html/entities.py,sha256=kzoRnQyGk_3DgoucHLhL5QL1pglK9nvmxhPIGZFDTnc,75428
future/backports/html/parser.py,sha256=G2tUObvbHSotNt06JLY-BP1swaZNfDYFd_ENWDjPmRg,19770
future/backports/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/backports/http/__pycache__/__init__.cpython-312.pyc,,
future/backports/http/__pycache__/client.cpython-312.pyc,,
future/backports/http/__pycache__/cookiejar.cpython-312.pyc,,
future/backports/http/__pycache__/cookies.cpython-312.pyc,,
future/backports/http/__pycache__/server.cpython-312.pyc,,
future/backports/http/client.py,sha256=76EbhEZOtvdHFcU-jrjivoff13oQ9IMbdkZEdf5kQzQ,47602
future/backports/http/cookiejar.py,sha256=jqb27uvv8wB2mJm6kF9aC0w7B03nO6rzQ0_CF35yArg,76608
future/backports/http/cookies.py,sha256=DsyDUGDEbCXAA9Jq6suswSc76uSZqUu39adDDNj8XGw,21581
future/backports/http/server.py,sha256=1CaMxgzHf9lYhmTJyE7topgjRIlIn9cnjgw8YEvwJV4,45523
future/backports/misc.py,sha256=EGnCVRmU-_7xrzss1rqqCqwqlQVywaPAxxLogBeNpw4,33063
future/backports/socket.py,sha256=DH1V6IjKPpJ0tln8bYvxvQ7qnvZG-UoQtMA5yVleHiU,15663
future/backports/socketserver.py,sha256=Twvyk5FqVnOeiNcbVsyMDPTF1mNlkKfyofG7tKxTdD8,24286
future/backports/test/__init__.py,sha256=9dXxIZnkI095YfHC-XIaVF6d31GjeY1Ag8TEzcFgepM,264
future/backports/test/__pycache__/__init__.cpython-312.pyc,,
future/backports/test/__pycache__/pystone.cpython-312.pyc,,
future/backports/test/__pycache__/ssl_servers.cpython-312.pyc,,
future/backports/test/__pycache__/support.cpython-312.pyc,,
future/backports/test/badcert.pem,sha256=JioQeRZkHH8hGsWJjAF3U1zQvcWqhyzG6IOEJpTY9SE,1928
future/backports/test/badkey.pem,sha256=gaBK9px_gG7DmrLKxfD6f6i-toAmARBTVfs-YGFRQF0,2162
future/backports/test/dh512.pem,sha256=dUTsjtLbK-femrorUrTGF8qvLjhTiT_n4Uo5V6u__Gs,402
future/backports/test/https_svn_python_org_root.pem,sha256=wOB3Onnc62Iu9kEFd8GcHhd_suucYjpJNA3jyfHeJWA,2569
future/backports/test/keycert.passwd.pem,sha256=ZBfnVLpbBtAOf_2gCdiQ-yrBHmRsNzSf8VC3UpQZIjg,1830
future/backports/test/keycert.pem,sha256=xPXi5idPcQVbrhgxBqF2TNGm6sSZ2aLVVEt6DWzplL8,1783
future/backports/test/keycert2.pem,sha256=DB46FEAYv8BWwQJ-5RzC696FxPN7CON-Qsi-R4poJgc,1795
future/backports/test/nokia.pem,sha256=s00x0uPDSaa5DHJ_CwzlVhg3OVdJ47f4zgqQdd0SAfQ,1923
future/backports/test/nullbytecert.pem,sha256=NFRYWhmP_qT3jGfVjR6-iaC-EQdhIFjiXtTLN5ZPKnE,5435
future/backports/test/nullcert.pem,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/backports/test/pystone.py,sha256=fvyoJ_tVovTNaxbJmdJMwr9F6SngY-U4ibULnd_wUqA,7427
future/backports/test/sha256.pem,sha256=3wB-GQqEc7jq-PYwYAQaPbtTvvr7stk_DVmZxFgehfA,8344
future/backports/test/ssl_cert.pem,sha256=M607jJNeIeHG9BlTf_jaQkPJI4nOxSJPn-zmEAaW43M,867
future/backports/test/ssl_key.passwd.pem,sha256=I_WH4sBw9Vs9Z-BvmuXY0aw8tx8avv6rm5UL4S_pP00,963
future/backports/test/ssl_key.pem,sha256=VKGU-R3UYaZpVTXl7chWl4vEYEDeob69SfvRTQ8aq_4,916
future/backports/test/ssl_servers.py,sha256=-pd7HMZljuZfFRAbCAiAP_2G04orITJFj-S9ddr6o84,7209
future/backports/test/support.py,sha256=oTQ09QrLcbmFZXhMGqPz3VrYZddgxpJGEJPQhwfiG2k,69620
future/backports/total_ordering.py,sha256=O3M57_IisQ-zW5hW20uxkfk4fTGsr0EF2tAKx3BksQo,1929
future/backports/urllib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/backports/urllib/__pycache__/__init__.cpython-312.pyc,,
future/backports/urllib/__pycache__/error.cpython-312.pyc,,
future/backports/urllib/__pycache__/parse.cpython-312.pyc,,
future/backports/urllib/__pycache__/request.cpython-312.pyc,,
future/backports/urllib/__pycache__/response.cpython-312.pyc,,
future/backports/urllib/__pycache__/robotparser.cpython-312.pyc,,
future/backports/urllib/error.py,sha256=ktikuK9ag4lS4f8Z0k5p1F11qF40N2AiOtjbXiF97ew,2715
future/backports/urllib/parse.py,sha256=67avrYqV1UK7i_22goRUrvJ8SffzjRdTja9wzq_ynXY,35792
future/backports/urllib/request.py,sha256=aR9ZMzfhV1C2Qk3wFsGvkwxqtdPTdsJVGRt5DUCwgJ8,96276
future/backports/urllib/response.py,sha256=ooQyswwbb-9N6IVi1Kwjss1aR-Kvm8ZNezoyVEonp8c,3180
future/backports/urllib/robotparser.py,sha256=pnAGTbKhdbCq_9yMZp7m8hj5q_NJpyQX6oQIZuYcnkw,6865
future/backports/xmlrpc/__init__.py,sha256=h61ciVTdVvu8oEUXv4dHf_Tc5XUXDH3RKB1-8fQhSsg,38
future/backports/xmlrpc/__pycache__/__init__.cpython-312.pyc,,
future/backports/xmlrpc/__pycache__/client.cpython-312.pyc,,
future/backports/xmlrpc/__pycache__/server.cpython-312.pyc,,
future/backports/xmlrpc/client.py,sha256=TIHPztKRlrStphmO_PfYOQxsy2xugzWKz77STC1OZ1U,48175
future/backports/xmlrpc/server.py,sha256=W_RW5hgYbNV2LGbnvngzm7akacRdK-XFY-Cy2HL-qsY,37285
future/builtins/__init__.py,sha256=rDAHzkhfXHSaye72FgzQz-HPN3yYBu-VXSs5PUJGA6o,1688
future/builtins/__pycache__/__init__.cpython-312.pyc,,
future/builtins/__pycache__/disabled.cpython-312.pyc,,
future/builtins/__pycache__/iterators.cpython-312.pyc,,
future/builtins/__pycache__/misc.cpython-312.pyc,,
future/builtins/__pycache__/new_min_max.cpython-312.pyc,,
future/builtins/__pycache__/newnext.cpython-312.pyc,,
future/builtins/__pycache__/newround.cpython-312.pyc,,
future/builtins/__pycache__/newsuper.cpython-312.pyc,,
future/builtins/disabled.py,sha256=Ysq74bsmwntpq7dzkwTAD7IHKrkXy66vJlPshVwgVBI,2109
future/builtins/iterators.py,sha256=l1Zawm2x82oqOuGGtCZRE76Ej98sMlHQwu9fZLK5RrA,1396
future/builtins/misc.py,sha256=hctlKKWUyN0Eoodxg4ySQHEqARTukOLR4L5K5c6PW9k,4550
future/builtins/new_min_max.py,sha256=7qQ4iiG4GDgRzjPzzzmg9pdby35Mtt6xNOOsyqHnIGY,1757
future/builtins/newnext.py,sha256=oxXB8baXqJv29YG40aCS9UXk9zObyoOjya8BJ7NdBJM,2009
future/builtins/newround.py,sha256=7YTWjBgfIAvSEl7hLCWgemhjqdKtzohbO18yMErKz4E,3190
future/builtins/newsuper.py,sha256=3Ygqq-8l3wh9gNvGbW5nAiTYT5WxxgSKN6RhNj7qi74,3849
future/moves/__init__.py,sha256=MsAW69Xp_fqUo4xODufcKM6AZf-ozHaz44WPZdsDFJA,220
future/moves/__pycache__/__init__.cpython-312.pyc,,
future/moves/__pycache__/_dummy_thread.cpython-312.pyc,,
future/moves/__pycache__/_markupbase.cpython-312.pyc,,
future/moves/__pycache__/_thread.cpython-312.pyc,,
future/moves/__pycache__/builtins.cpython-312.pyc,,
future/moves/__pycache__/collections.cpython-312.pyc,,
future/moves/__pycache__/configparser.cpython-312.pyc,,
future/moves/__pycache__/copyreg.cpython-312.pyc,,
future/moves/__pycache__/itertools.cpython-312.pyc,,
future/moves/__pycache__/multiprocessing.cpython-312.pyc,,
future/moves/__pycache__/pickle.cpython-312.pyc,,
future/moves/__pycache__/queue.cpython-312.pyc,,
future/moves/__pycache__/reprlib.cpython-312.pyc,,
future/moves/__pycache__/socketserver.cpython-312.pyc,,
future/moves/__pycache__/subprocess.cpython-312.pyc,,
future/moves/__pycache__/sys.cpython-312.pyc,,
future/moves/__pycache__/winreg.cpython-312.pyc,,
future/moves/_dummy_thread.py,sha256=ULUtLk1Luw9I1h-YPitnU3gqCbvNPoKC28N_Bk8jkR8,348
future/moves/_markupbase.py,sha256=W9wh_Gu3jDAMIhVBV1ZnCkJwYLHRk_v_su_HLALBkZQ,171
future/moves/_thread.py,sha256=rwY7L4BZMFPlrp_i6T2Un4_iKYwnrXJ-yV6FJZN8YDo,163
future/moves/builtins.py,sha256=4sjjKiylecJeL9da_RaBZjdymX2jtMs84oA9lCqb4Ug,281
future/moves/collections.py,sha256=OKQ-TfUgms_2bnZRn4hrclLDoiN2i-HSWcjs3BC2iY8,417
future/moves/configparser.py,sha256=TNy226uCbljjU-DjAVo7j7Effbj5zxXvDh0SdXehbzk,146
future/moves/copyreg.py,sha256=Y3UjLXIMSOxZggXtvZucE9yv4tkKZtVan45z8eix4sU,438
future/moves/dbm/__init__.py,sha256=_VkvQHC2UcIgZFPRroiX_P0Fs7HNqS_69flR0-oq2B8,488
future/moves/dbm/__pycache__/__init__.cpython-312.pyc,,
future/moves/dbm/__pycache__/dumb.cpython-312.pyc,,
future/moves/dbm/__pycache__/gnu.cpython-312.pyc,,
future/moves/dbm/__pycache__/ndbm.cpython-312.pyc,,
future/moves/dbm/dumb.py,sha256=HKdjjtO3EyP9EKi1Hgxh_eUU6yCQ0fBX9NN3n-zb8JE,166
future/moves/dbm/gnu.py,sha256=XoCSEpZ2QaOgo2h1m80GW7NUgj_b93BKtbcuwgtnaKo,162
future/moves/dbm/ndbm.py,sha256=OFnreyo_1YHDBl5YUm9gCzKlN1MHgWbfSQAZVls2jaM,162
future/moves/html/__init__.py,sha256=BSUFSHxXf2kGvHozlnrB1nn6bPE6p4PpN3DwA_Z5geo,1016
future/moves/html/__pycache__/__init__.cpython-312.pyc,,
future/moves/html/__pycache__/entities.cpython-312.pyc,,
future/moves/html/__pycache__/parser.cpython-312.pyc,,
future/moves/html/entities.py,sha256=lVvchdjK_RzRj759eg4RMvGWHfgBbj0tKGOoZ8dbRyY,177
future/moves/html/parser.py,sha256=V2XpHLKLCxQum3N9xlO3IUccAD7BIykZMqdEcWET3vY,167
future/moves/http/__init__.py,sha256=Mx1v_Tcks4udHCtDM8q2xnYUiQ01gD7EpPyeQwsP3-Q,71
future/moves/http/__pycache__/__init__.cpython-312.pyc,,
future/moves/http/__pycache__/client.cpython-312.pyc,,
future/moves/http/__pycache__/cookiejar.cpython-312.pyc,,
future/moves/http/__pycache__/cookies.cpython-312.pyc,,
future/moves/http/__pycache__/server.cpython-312.pyc,,
future/moves/http/client.py,sha256=hqEBq7GDXZidd1AscKnSyjSoMcuj8rERqGTmD7VheDQ,165
future/moves/http/cookiejar.py,sha256=Frr9ZZCg-145ymy0VGpiPJhvBEpJtVqRBYPaKhgT1Z4,173
future/moves/http/cookies.py,sha256=PPrHa1_oDbu3D_BhJGc6PvMgY1KoxyYq1jqeJwEcMvE,233
future/moves/http/server.py,sha256=8YQlSCShjAsB5rr5foVvZgp3IzwYFvTmGZCHhBSDtaI,606
future/moves/itertools.py,sha256=PVxFHRlBQl9ElS0cuGFPcUtj53eHX7Z1DmggzGfgQ6c,158
future/moves/multiprocessing.py,sha256=4L37igVf2NwBhXqmCHRA3slZ7lJeiQLzZdrGSGOOZ08,191
future/moves/pickle.py,sha256=r8j9skzfE8ZCeHyh_OB-WucOkRTIHN7zpRM7l7V3qS4,229
future/moves/queue.py,sha256=uxvLCChF-zxWWgrY1a_wxt8rp2jILdwO4PrnkBW6VTE,160
future/moves/reprlib.py,sha256=Nt5sUgMQ3jeVIukqSHOvB0UIsl6Y5t-mmT_13mpZmiY,161
future/moves/socketserver.py,sha256=v8ZLurDxHOgsubYm1iefjlpnnJQcx2VuRUGt9FCJB9k,174
future/moves/subprocess.py,sha256=oqRSMfFZkxM4MXkt3oD5N6eBwmmJ6rQ9KPhvSQKT_hM,251
future/moves/sys.py,sha256=HOMRX4Loim75FMbWawd3oEwuGNJR-ClMREEFkVpBsRs,132
future/moves/test/__init__.py,sha256=yB9F-fDQpzu1v8cBoKgIrL2ScUNqjlkqEztYrGVCQ-0,110
future/moves/test/__pycache__/__init__.cpython-312.pyc,,
future/moves/test/__pycache__/support.cpython-312.pyc,,
future/moves/test/support.py,sha256=TG5h0FVGwyJGtKQEXMhWtD4G9WZagHrMI_CeL9NlZYc,484
future/moves/tkinter/__init__.py,sha256=jV9vDx3wRl0bsoclU8oSe-5SqHQ3YpCbStmqtXnq1p4,620
future/moves/tkinter/__pycache__/__init__.cpython-312.pyc,,
future/moves/tkinter/__pycache__/colorchooser.cpython-312.pyc,,
future/moves/tkinter/__pycache__/commondialog.cpython-312.pyc,,
future/moves/tkinter/__pycache__/constants.cpython-312.pyc,,
future/moves/tkinter/__pycache__/dialog.cpython-312.pyc,,
future/moves/tkinter/__pycache__/dnd.cpython-312.pyc,,
future/moves/tkinter/__pycache__/filedialog.cpython-312.pyc,,
future/moves/tkinter/__pycache__/font.cpython-312.pyc,,
future/moves/tkinter/__pycache__/messagebox.cpython-312.pyc,,
future/moves/tkinter/__pycache__/scrolledtext.cpython-312.pyc,,
future/moves/tkinter/__pycache__/simpledialog.cpython-312.pyc,,
future/moves/tkinter/__pycache__/tix.cpython-312.pyc,,
future/moves/tkinter/__pycache__/ttk.cpython-312.pyc,,
future/moves/tkinter/colorchooser.py,sha256=kprlmpRtvDbW5Gq43H1mi2KmNJ2kuzLQOba0a5EwDkU,333
future/moves/tkinter/commondialog.py,sha256=mdUbq1IZqOGaSA7_8R367IukDCsMfzXiVHrTQQpp7Z0,333
future/moves/tkinter/constants.py,sha256=0qRUrZLRPdVxueABL9KTzzEWEsk6xM1rOjxK6OHxXtA,324
future/moves/tkinter/dialog.py,sha256=ksp-zvs-_A90P9RNHS8S27f1k8f48zG2Bel2jwZV5y0,311
future/moves/tkinter/dnd.py,sha256=C_Ah0Urnyf2XKE5u-oP6mWi16RzMSXgMA1uhBSAwKY8,306
future/moves/tkinter/filedialog.py,sha256=yNr30k-hDY1aMJHNsKqRqHqOOlzYKCubfQ3HjY1ZlrE,534
future/moves/tkinter/font.py,sha256=TXarflhJRxqepaRNSDw6JFUVGz5P1T1C4_uF9VRqj3w,309
future/moves/tkinter/messagebox.py,sha256=WJt4t83kLmr_UnpCWFuLoyazZr3wAUOEl6ADn3osoEA,327
future/moves/tkinter/scrolledtext.py,sha256=DRzN8aBAlDBUo1B2KDHzdpRSzXBfH4rOOz0iuHXbQcg,329
future/moves/tkinter/simpledialog.py,sha256=6MhuVhZCJV4XfPpPSUWKlDLLGEi0Y2ZlGQ9TbsmJFL0,329
future/moves/tkinter/tix.py,sha256=aNeOfbWSGmcN69UmEGf4tJ-QIxLT6SU5ynzm1iWgepA,302
future/moves/tkinter/ttk.py,sha256=rRrJpDjcP2gjQNukECu4F026P-CkW-3Ca2tN6Oia-Fw,302
future/moves/urllib/__init__.py,sha256=yB9F-fDQpzu1v8cBoKgIrL2ScUNqjlkqEztYrGVCQ-0,110
future/moves/urllib/__pycache__/__init__.cpython-312.pyc,,
future/moves/urllib/__pycache__/error.cpython-312.pyc,,
future/moves/urllib/__pycache__/parse.cpython-312.pyc,,
future/moves/urllib/__pycache__/request.cpython-312.pyc,,
future/moves/urllib/__pycache__/response.cpython-312.pyc,,
future/moves/urllib/__pycache__/robotparser.cpython-312.pyc,,
future/moves/urllib/error.py,sha256=gfrKzv-6W5OjzNIfjvJaQkxABRLym2KwjfKFXSdDB60,479
future/moves/urllib/parse.py,sha256=xLLUMIIB5MreCdYzRZ5zIRWrhTRCoMO8RZEH4WPFQDY,1045
future/moves/urllib/request.py,sha256=ttIzq60PwjRyrLQUGdAtfYvs4fziVwvcLe2Kw-hvE0g,3496
future/moves/urllib/response.py,sha256=ZEZML0FpbB--GIeBFPvSzbtlVJ6EsR4tCI4qB7D8sFQ,342
future/moves/urllib/robotparser.py,sha256=j24p6dMNzUpGZtT3BQxwRoE-F88iWmBpKgu0tRV61FQ,179
future/moves/winreg.py,sha256=2zNAG59QI7vFlCj7kqDh0JrAYTpexOnI55PEAIjYhqo,163
future/moves/xmlrpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/moves/xmlrpc/__pycache__/__init__.cpython-312.pyc,,
future/moves/xmlrpc/__pycache__/client.cpython-312.pyc,,
future/moves/xmlrpc/__pycache__/server.cpython-312.pyc,,
future/moves/xmlrpc/client.py,sha256=2PfnL5IbKVwdKP7C8B1OUviEtuBObwoH4pAPfvHIvQc,143
future/moves/xmlrpc/server.py,sha256=ESDXdpUgTKyeFmCDSnJmBp8zONjJklsRJOvy4OtaALc,143
future/standard_library/__init__.py,sha256=Nwbaqikyeh77wSiro-BHNjSsCmSmuLGAe91d4c5q_QE,28065
future/standard_library/__pycache__/__init__.cpython-312.pyc,,
future/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
future/tests/__pycache__/__init__.cpython-312.pyc,,
future/tests/__pycache__/base.cpython-312.pyc,,
future/tests/base.py,sha256=7LTAKHJgUxOwmffD1kgcErVt2VouKcldPnq4iruqk_k,19956
future/types/__init__.py,sha256=5fBxWqf_OTQ8jZ7k2TS34rFH14togeR488F4zBHIQ-s,6831
future/types/__pycache__/__init__.cpython-312.pyc,,
future/types/__pycache__/newbytes.cpython-312.pyc,,
future/types/__pycache__/newdict.cpython-312.pyc,,
future/types/__pycache__/newint.cpython-312.pyc,,
future/types/__pycache__/newlist.cpython-312.pyc,,
future/types/__pycache__/newmemoryview.cpython-312.pyc,,
future/types/__pycache__/newobject.cpython-312.pyc,,
future/types/__pycache__/newopen.cpython-312.pyc,,
future/types/__pycache__/newrange.cpython-312.pyc,,
future/types/__pycache__/newstr.cpython-312.pyc,,
future/types/newbytes.py,sha256=D_kNDD9sbNJir2cUxxePiAuw2OW5irxVnu55uHmuK9E,16303
future/types/newdict.py,sha256=go-Lbl2MRWZJJRlwTAUlJNJRkg986YYeV0jCqEUEFNc,2011
future/types/newint.py,sha256=HH90HS2Y1ApS02LDpKzqt9V1Lwtp6tktMIYjavZUIh8,13406
future/types/newlist.py,sha256=-H5-fXodd-UQgTFnZBJdwE68CrgIL_jViYdv4w7q2rU,2284
future/types/newmemoryview.py,sha256=LnARgiKqQ2zLwwDZ3owu1atoonPQkOneWMfxJCwB4_o,712
future/types/newobject.py,sha256=AX_n8GwlDR2IY-xIwZCvu0Olj_Ca2aS57nlTihnFr-I,3358
future/types/newopen.py,sha256=lcRNHWZ1UjEn_0_XKis1ZA5U6l-4c-CHlC0WX1sY4NI,810
future/types/newrange.py,sha256=fcCL1imqqH-lqWsY9Lnml9d-WbJOtXrayAUPoUbM7Ck,5296
future/types/newstr.py,sha256=e0brkurI0IK--4ToQEO4Cz1FECZav4CyUGMKxlrcmK4,15758
future/utils/__init__.py,sha256=Er_tUl6bS4xp7_M1Z3hZrgM9hAGrRUvCAdcHDRgSOdE,21960
future/utils/__pycache__/__init__.cpython-312.pyc,,
future/utils/__pycache__/surrogateescape.cpython-312.pyc,,
future/utils/surrogateescape.py,sha256=7u4V4XlW83P5YSAJS2f92YUF8vsWthsiTnmAshOJL_M,6097
libfuturize/__init__.py,sha256=CZA_KgvTQOPAY1_MrlJeQ6eMh2Eei4_KIv4JuyAkpfw,31
libfuturize/__pycache__/__init__.cpython-312.pyc,,
libfuturize/__pycache__/fixer_util.cpython-312.pyc,,
libfuturize/__pycache__/main.cpython-312.pyc,,
libfuturize/fixer_util.py,sha256=hOmX8XLnicGJ6RGwlUxslhuhzhPc0cZimlylFQAeDOo,17357
libfuturize/fixes/__init__.py,sha256=5KEpUnjVsFCCsr_-zrikvJbLf9zslEJnFTH_5pBc33I,5236
libfuturize/fixes/__pycache__/__init__.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_UserDict.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_absolute_import.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_add__future__imports_except_unicode_literals.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_basestring.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_bytes.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_cmp.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_division.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_division_safe.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_execfile.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_future_builtins.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_future_standard_library.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_future_standard_library_urllib.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_input.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_metaclass.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_next_call.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_object.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_oldstr_wrap.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_order___future__imports.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_print.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_print_with_import.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_raise.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_remove_old__future__imports.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_unicode_keep_u.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_unicode_literals_import.cpython-312.pyc,,
libfuturize/fixes/__pycache__/fix_xrange_with_import.cpython-312.pyc,,
libfuturize/fixes/fix_UserDict.py,sha256=jL4jXnGaUQTkG8RKfGXbU_HVTkB3MWZMQwUkqMAjB6I,3840
libfuturize/fixes/fix_absolute_import.py,sha256=vkrF2FyQR5lSz2WmdqywzkEJVTC0eq4gh_REWBKHh7w,3140
libfuturize/fixes/fix_add__future__imports_except_unicode_literals.py,sha256=Fr219VAzR8KWXc2_bfiqLl10EgxAWjL6cI3Mowt--VU,662
libfuturize/fixes/fix_basestring.py,sha256=bHkKuMzhr5FMXwjXlMOjsod4S3rQkVdbzhoWV4-tl3Y,394
libfuturize/fixes/fix_bytes.py,sha256=AhzOJes6EnPwgzboDjvURANbWKqciG6ZGaYW07PYQK8,685
libfuturize/fixes/fix_cmp.py,sha256=Blq_Z0IGkYiKS83QzZ5wUgpJyZfQiZoEsWJ1VPyXgFY,701
libfuturize/fixes/fix_division.py,sha256=gnrAi7stquiVUyi_De1H8q--43iQaSUX0CjnOmQ6O2w,228
libfuturize/fixes/fix_division_safe.py,sha256=oz407p0Woc2EKw7jZHUL4CpDs81FFpekRum58NKsNp4,3631
libfuturize/fixes/fix_execfile.py,sha256=I5AcJ6vPZ7i70TChaq9inxqnZ4C04-yJyfAItGa8E3c,921
libfuturize/fixes/fix_future_builtins.py,sha256=QBCRpD9XA7tbtfP4wmOF2DXquB4lq-eupkQj-QAxp0s,2027
libfuturize/fixes/fix_future_standard_library.py,sha256=FVtflFt38efHe_SEX6k3m6IYAtKWjA4rAPZrlCv6yA0,733
libfuturize/fixes/fix_future_standard_library_urllib.py,sha256=Rf81XcAXA-vwNvrhskf5sLExbR--Wkr5fiUcMYGAKzs,1001
libfuturize/fixes/fix_input.py,sha256=bhaPNtMrZNbjWIDQCR7Iue5BxBj4rf0RJQ9_jiwvb-s,687
libfuturize/fixes/fix_metaclass.py,sha256=_CS1NDXYM-Mh6xpogLK_GtYx3rUUptu1-Z0Rx3lC9eQ,9570
libfuturize/fixes/fix_next_call.py,sha256=01STG86Av9o5QcpQDJ6UbPhvxt9kKrkatiPeddXRgvA,3158
libfuturize/fixes/fix_object.py,sha256=qalFIjn0VTWXG5sGOOoCvO65omjX5_9d40SUpwUjBdw,407
libfuturize/fixes/fix_oldstr_wrap.py,sha256=UCR6Q2l-pVqJSrRTnQAWMlaqBoX7oX1VpG_w6Q0XcyY,1214
libfuturize/fixes/fix_order___future__imports.py,sha256=ACUCw5NEGWvj6XA9rNj8BYha3ktxLvkM5Ssh5cyV644,829
libfuturize/fixes/fix_print.py,sha256=nbJdv5DbxtWzJIRIQ0tr7FfGkMkHScJTLzvpxv_hSNw,3881
libfuturize/fixes/fix_print_with_import.py,sha256=hVWn70Q1DPMUiHMyEqgUx-6sM1AylLj78v9pMc4LFw8,735
libfuturize/fixes/fix_raise.py,sha256=CkjqiSvHHD-enaLxYMkH-Nsi92NGShFLWd3fG-exmI4,3904
libfuturize/fixes/fix_remove_old__future__imports.py,sha256=j4EC1KEVgXhuQAqhYHnAruUjW6uczPjV_fTCSOLMuAw,851
libfuturize/fixes/fix_unicode_keep_u.py,sha256=M8fcFxHeFnWVOKoQRpkMsnpd9qmUFubI2oFhO4ZPk7A,779
libfuturize/fixes/fix_unicode_literals_import.py,sha256=wq-hb-9Yx3Az4ol-ylXZJPEDZ81EaPZeIy5VvpA0CEY,367
libfuturize/fixes/fix_xrange_with_import.py,sha256=f074qStjMz3OtLjt1bKKZSxQnRbbb7HzEbqHt9wgqdw,479
libfuturize/main.py,sha256=feICmcv0dzWhutvwz0unnIVxusbSlQZFDaxObkHebs8,13733
libpasteurize/__init__.py,sha256=CZA_KgvTQOPAY1_MrlJeQ6eMh2Eei4_KIv4JuyAkpfw,31
libpasteurize/__pycache__/__init__.cpython-312.pyc,,
libpasteurize/__pycache__/main.cpython-312.pyc,,
libpasteurize/fixes/__init__.py,sha256=ccdv-2MGjQMbq8XuEZBndHmbzGRrZnabksjXZLUv044,3719
libpasteurize/fixes/__pycache__/__init__.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/feature_base.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_add_all__future__imports.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_add_all_future_builtins.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_add_future_standard_library_import.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_annotations.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_division.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_features.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_fullargspec.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_future_builtins.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_getcwd.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_imports.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_imports2.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_kwargs.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_memoryview.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_metaclass.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_newstyle.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_next.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_printfunction.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_raise.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_raise_.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_throw.cpython-312.pyc,,
libpasteurize/fixes/__pycache__/fix_unpacking.cpython-312.pyc,,
libpasteurize/fixes/feature_base.py,sha256=v7yLjBDBUPeNUc-YHGGlIsJDOQzFAM4Vo0RN5F1JHVU,1723
libpasteurize/fixes/fix_add_all__future__imports.py,sha256=mHet1LgbHn9GfgCYGNZXKo-rseDWreAvUcAjZwdgeTE,676
libpasteurize/fixes/fix_add_all_future_builtins.py,sha256=scfkY-Sz5j0yDtLYls2ENOcqEMPVxeDm9gFYYPINPB8,1269
libpasteurize/fixes/fix_add_future_standard_library_import.py,sha256=thTRbkBzy_SJjZ0bJteTp0sBTx8Wr69xFakH4styf7Y,663
libpasteurize/fixes/fix_annotations.py,sha256=VT_AorKY9AYWYZUZ17_CeUrJlEA7VGkwVLDQlwD1Bxo,1581
libpasteurize/fixes/fix_division.py,sha256=_TD_c5KniAYqEm11O7NJF0v2WEhYSNkRGcKG_94ZOas,904
libpasteurize/fixes/fix_features.py,sha256=NZn0n34_MYZpLNwyP1Tf51hOiN58Rg7A8tA9pK1S8-c,2675
libpasteurize/fixes/fix_fullargspec.py,sha256=VlZuIU6QNrClmRuvC4mtLICL3yMCi-RcGCnS9fD4b-Q,438
libpasteurize/fixes/fix_future_builtins.py,sha256=SlCK9I9u05m19Lr1wxlJxF8toZ5yu0yXBeDLxUN9_fw,1450
libpasteurize/fixes/fix_getcwd.py,sha256=uebvTvFboLqsROFCwdnzoP6ThziM0skz9TDXHoJcFsQ,873
libpasteurize/fixes/fix_imports.py,sha256=KH4Q-qMzsuN5VcfE1ZGS337yHhxbgrmLoRtpHtr2A94,5026
libpasteurize/fixes/fix_imports2.py,sha256=bs2V5Yv0v_8xLx-lNj9kNEAK2dLYXUXkZ2hxECg01CU,8580
libpasteurize/fixes/fix_kwargs.py,sha256=NB_Ap8YJk-9ncoJRbOiPY_VMIigFgVB8m8AuY29DDhE,5991
libpasteurize/fixes/fix_memoryview.py,sha256=Fwayx_ezpr22tbJ0-QrKdJ-FZTpU-m7y78l1h_N4xxc,551
libpasteurize/fixes/fix_metaclass.py,sha256=IcE2KjaDG8jUR3FYXECzOC_cr2pr5r95W1NTbMrK8Wc,3260
libpasteurize/fixes/fix_newstyle.py,sha256=78sazKOHm9DUoMyW4VdvQpMXZhicbXzorVPRhBpSUrM,888
libpasteurize/fixes/fix_next.py,sha256=VHqcyORRNVqKJ51jJ1OkhwxHuXRgp8qaldyqcMvA4J0,1233
libpasteurize/fixes/fix_printfunction.py,sha256=NDIfqVmUJBG3H9E6nrnN0cWZK8ch9pL4F-nMexdsa38,401
libpasteurize/fixes/fix_raise.py,sha256=zQ_AcMsGmCbtKMgrxZGcHLYNscw6tqXFvHQxgqtNbU8,1099
libpasteurize/fixes/fix_raise_.py,sha256=9STp633frUfYASjYzqhwxx_MXePNmMhfJClowRj8FLY,1225
libpasteurize/fixes/fix_throw.py,sha256=_ZREVre-WttUvk4sWjrqUNqm9Q1uFaATECN0_-PXKbk,835
libpasteurize/fixes/fix_unpacking.py,sha256=xZqxMYHgdeuIkermtY-evisvcKlGCPi5vg5t5pt-XCY,6041
libpasteurize/main.py,sha256=dVHYTQQeJonuOFDNrenJZl-rKHgOQKRMPP1OqnJogWQ,8186
past/__init__.py,sha256=2DxcQt5zgPH-e-TSDS2l7hI94A9eG7pPgD-V5FgH084,2892
past/__pycache__/__init__.cpython-312.pyc,,
past/builtins/__init__.py,sha256=7j_4OsUlN6q2eKr14do7mRQ1GwXRoXAMUR0A1fJpAls,1805
past/builtins/__pycache__/__init__.cpython-312.pyc,,
past/builtins/__pycache__/misc.cpython-312.pyc,,
past/builtins/__pycache__/noniterators.cpython-312.pyc,,
past/builtins/misc.py,sha256=I76Mpx_3wnHpJg7Ub9SZOBRqEFo02YgimZJpfoq17_0,5598
past/builtins/noniterators.py,sha256=LtdELnd7KyYdXg7GkW25cgkEPUC0ggZ5AYMtDe9N95I,9370
past/translation/__init__.py,sha256=oTtrOHD8ToM9c9RXat_BhjKhN33N7_Vg4HGS0if-UbU,14914
past/translation/__pycache__/__init__.cpython-312.pyc,,
past/types/__init__.py,sha256=RyJlgqg9uJ8oF-kJT9QlfhfdmhiMh3fShmtvd2CQycY,879
past/types/__pycache__/__init__.cpython-312.pyc,,
past/types/__pycache__/basestring.cpython-312.pyc,,
past/types/__pycache__/olddict.cpython-312.pyc,,
past/types/__pycache__/oldstr.cpython-312.pyc,,
past/types/basestring.py,sha256=lO66aHgOV02vka6kosnR6GWK0iNC0G28Nugb1MP69-E,774
past/types/olddict.py,sha256=0YtffZ55VY6AyQ_rwu4DZ4vcRsp6dz-dQzczeyN8hLk,2721
past/types/oldstr.py,sha256=JuF8VBBI4OGSgZ3PyhU6LxSAiTfEWzdHUx0Hwg13WSY,4333
past/utils/__init__.py,sha256=e8l1sOfdiDJ3dkckBWLNWvC1ahC5BX5haHC2TGdNgA8,2633
past/utils/__pycache__/__init__.cpython-312.pyc,,

View File

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.41.2)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -0,0 +1,3 @@
[console_scripts]
futurize = libfuturize.main:main
pasteurize = libpasteurize.main:main

View File

@ -0,0 +1,4 @@
future
libfuturize
libpasteurize
past

View File

@ -0,0 +1,92 @@
"""
future: Easy, safe support for Python 2/3 compatibility
=======================================================
``future`` is the missing compatibility layer between Python 2 and Python
3. It allows you to use a single, clean Python 3.x-compatible codebase to
support both Python 2 and Python 3 with minimal overhead.
It is designed to be used as follows::
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import (
bytes, dict, int, list, object, range, str,
ascii, chr, hex, input, next, oct, open,
pow, round, super,
filter, map, zip)
followed by predominantly standard, idiomatic Python 3 code that then runs
similarly on Python 2.6/2.7 and Python 3.3+.
The imports have no effect on Python 3. On Python 2, they shadow the
corresponding builtins, which normally have different semantics on Python 3
versus 2, to provide their Python 3 semantics.
Standard library reorganization
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``future`` supports the standard library reorganization (PEP 3108) through the
following Py3 interfaces:
>>> # Top-level packages with Py3 names provided on Py2:
>>> import html.parser
>>> import queue
>>> import tkinter.dialog
>>> import xmlrpc.client
>>> # etc.
>>> # Aliases provided for extensions to existing Py2 module names:
>>> from future.standard_library import install_aliases
>>> install_aliases()
>>> from collections import Counter, OrderedDict # backported to Py2.6
>>> from collections import UserDict, UserList, UserString
>>> import urllib.request
>>> from itertools import filterfalse, zip_longest
>>> from subprocess import getoutput, getstatusoutput
Automatic conversion
--------------------
An included script called `futurize
<https://python-future.org/automatic_conversion.html>`_ aids in converting
code (from either Python 2 or Python 3) to code compatible with both
platforms. It is similar to ``python-modernize`` but goes further in
providing Python 3 compatibility through the use of the backported types
and builtin functions in ``future``.
Documentation
-------------
See: https://python-future.org
Credits
-------
:Author: Ed Schofield, Jordan M. Adler, et al
:Sponsor: Python Charmers: https://pythoncharmers.com
:Others: See docs/credits.rst or https://python-future.org/credits.html
Licensing
---------
Copyright 2013-2024 Python Charmers, Australia.
The software is distributed under an MIT licence. See LICENSE.txt.
"""
__title__ = 'future'
__author__ = 'Ed Schofield'
__license__ = 'MIT'
__copyright__ = 'Copyright 2013-2024 Python Charmers (https://pythoncharmers.com)'
__ver_major__ = 1
__ver_minor__ = 0
__ver_patch__ = 0
__ver_sub__ = ''
__version__ = "%d.%d.%d%s" % (__ver_major__, __ver_minor__,
__ver_patch__, __ver_sub__)

View File

@ -0,0 +1,26 @@
"""
future.backports package
"""
from __future__ import absolute_import
import sys
__future_module__ = True
from future.standard_library import import_top_level_modules
if sys.version_info[0] >= 3:
import_top_level_modules()
from .misc import (ceil,
OrderedDict,
Counter,
ChainMap,
check_output,
count,
recursive_repr,
_count_elements,
cmp_to_key
)

View File

@ -0,0 +1,422 @@
"""Shared support for scanning document type declarations in HTML and XHTML.
Backported for python-future from Python 3.3. Reason: ParserBase is an
old-style class in the Python 2.7 source of markupbase.py, which I suspect
might be the cause of sporadic unit-test failures on travis-ci.org with
test_htmlparser.py. The test failures look like this:
======================================================================
ERROR: test_attr_entity_replacement (future.tests.test_htmlparser.AttributesStrictTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 661, in test_attr_entity_replacement
[("starttag", "a", [("b", "&><\"'")])])
File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 93, in _run_check
collector = self.get_collector()
File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 617, in get_collector
return EventCollector(strict=True)
File "/home/travis/build/edschofield/python-future/future/tests/test_htmlparser.py", line 27, in __init__
html.parser.HTMLParser.__init__(self, *args, **kw)
File "/home/travis/build/edschofield/python-future/future/backports/html/parser.py", line 135, in __init__
self.reset()
File "/home/travis/build/edschofield/python-future/future/backports/html/parser.py", line 143, in reset
_markupbase.ParserBase.reset(self)
TypeError: unbound method reset() must be called with ParserBase instance as first argument (got EventCollector instance instead)
This module is used as a foundation for the html.parser module. It has no
documented public API and should not be used directly.
"""
import re
_declname_match = re.compile(r'[a-zA-Z][-_.a-zA-Z0-9]*\s*').match
_declstringlit_match = re.compile(r'(\'[^\']*\'|"[^"]*")\s*').match
_commentclose = re.compile(r'--\s*>')
_markedsectionclose = re.compile(r']\s*]\s*>')
# An analysis of the MS-Word extensions is available at
# http://www.planetpublish.com/xmlarena/xap/Thursday/WordtoXML.pdf
_msmarkedsectionclose = re.compile(r']\s*>')
del re
class ParserBase(object):
"""Parser base class which provides some common support methods used
by the SGML/HTML and XHTML parsers."""
def __init__(self):
if self.__class__ is ParserBase:
raise RuntimeError(
"_markupbase.ParserBase must be subclassed")
def error(self, message):
raise NotImplementedError(
"subclasses of ParserBase must override error()")
def reset(self):
self.lineno = 1
self.offset = 0
def getpos(self):
"""Return current line number and offset."""
return self.lineno, self.offset
# Internal -- update line number and offset. This should be
# called for each piece of data exactly once, in order -- in other
# words the concatenation of all the input strings to this
# function should be exactly the entire input.
def updatepos(self, i, j):
if i >= j:
return j
rawdata = self.rawdata
nlines = rawdata.count("\n", i, j)
if nlines:
self.lineno = self.lineno + nlines
pos = rawdata.rindex("\n", i, j) # Should not fail
self.offset = j-(pos+1)
else:
self.offset = self.offset + j-i
return j
_decl_otherchars = ''
# Internal -- parse declaration (for use by subclasses).
def parse_declaration(self, i):
# This is some sort of declaration; in "HTML as
# deployed," this should only be the document type
# declaration ("<!DOCTYPE html...>").
# ISO 8879:1986, however, has more complex
# declaration syntax for elements in <!...>, including:
# --comment--
# [marked section]
# name in the following list: ENTITY, DOCTYPE, ELEMENT,
# ATTLIST, NOTATION, SHORTREF, USEMAP,
# LINKTYPE, LINK, IDLINK, USELINK, SYSTEM
rawdata = self.rawdata
j = i + 2
assert rawdata[i:j] == "<!", "unexpected call to parse_declaration"
if rawdata[j:j+1] == ">":
# the empty comment <!>
return j + 1
if rawdata[j:j+1] in ("-", ""):
# Start of comment followed by buffer boundary,
# or just a buffer boundary.
return -1
# A simple, practical version could look like: ((name|stringlit) S*) + '>'
n = len(rawdata)
if rawdata[j:j+2] == '--': #comment
# Locate --.*-- as the body of the comment
return self.parse_comment(i)
elif rawdata[j] == '[': #marked section
# Locate [statusWord [...arbitrary SGML...]] as the body of the marked section
# Where statusWord is one of TEMP, CDATA, IGNORE, INCLUDE, RCDATA
# Note that this is extended by Microsoft Office "Save as Web" function
# to include [if...] and [endif].
return self.parse_marked_section(i)
else: #all other declaration elements
decltype, j = self._scan_name(j, i)
if j < 0:
return j
if decltype == "doctype":
self._decl_otherchars = ''
while j < n:
c = rawdata[j]
if c == ">":
# end of declaration syntax
data = rawdata[i+2:j]
if decltype == "doctype":
self.handle_decl(data)
else:
# According to the HTML5 specs sections "8.2.4.44 Bogus
# comment state" and "8.2.4.45 Markup declaration open
# state", a comment token should be emitted.
# Calling unknown_decl provides more flexibility though.
self.unknown_decl(data)
return j + 1
if c in "\"'":
m = _declstringlit_match(rawdata, j)
if not m:
return -1 # incomplete
j = m.end()
elif c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ":
name, j = self._scan_name(j, i)
elif c in self._decl_otherchars:
j = j + 1
elif c == "[":
# this could be handled in a separate doctype parser
if decltype == "doctype":
j = self._parse_doctype_subset(j + 1, i)
elif decltype in set(["attlist", "linktype", "link", "element"]):
# must tolerate []'d groups in a content model in an element declaration
# also in data attribute specifications of attlist declaration
# also link type declaration subsets in linktype declarations
# also link attribute specification lists in link declarations
self.error("unsupported '[' char in %s declaration" % decltype)
else:
self.error("unexpected '[' char in declaration")
else:
self.error(
"unexpected %r char in declaration" % rawdata[j])
if j < 0:
return j
return -1 # incomplete
# Internal -- parse a marked section
# Override this to handle MS-word extension syntax <![if word]>content<![endif]>
def parse_marked_section(self, i, report=1):
rawdata= self.rawdata
assert rawdata[i:i+3] == '<![', "unexpected call to parse_marked_section()"
sectName, j = self._scan_name( i+3, i )
if j < 0:
return j
if sectName in set(["temp", "cdata", "ignore", "include", "rcdata"]):
# look for standard ]]> ending
match= _markedsectionclose.search(rawdata, i+3)
elif sectName in set(["if", "else", "endif"]):
# look for MS Office ]> ending
match= _msmarkedsectionclose.search(rawdata, i+3)
else:
self.error('unknown status keyword %r in marked section' % rawdata[i+3:j])
if not match:
return -1
if report:
j = match.start(0)
self.unknown_decl(rawdata[i+3: j])
return match.end(0)
# Internal -- parse comment, return length or -1 if not terminated
def parse_comment(self, i, report=1):
rawdata = self.rawdata
if rawdata[i:i+4] != '<!--':
self.error('unexpected call to parse_comment()')
match = _commentclose.search(rawdata, i+4)
if not match:
return -1
if report:
j = match.start(0)
self.handle_comment(rawdata[i+4: j])
return match.end(0)
# Internal -- scan past the internal subset in a <!DOCTYPE declaration,
# returning the index just past any whitespace following the trailing ']'.
def _parse_doctype_subset(self, i, declstartpos):
rawdata = self.rawdata
n = len(rawdata)
j = i
while j < n:
c = rawdata[j]
if c == "<":
s = rawdata[j:j+2]
if s == "<":
# end of buffer; incomplete
return -1
if s != "<!":
self.updatepos(declstartpos, j + 1)
self.error("unexpected char in internal subset (in %r)" % s)
if (j + 2) == n:
# end of buffer; incomplete
return -1
if (j + 4) > n:
# end of buffer; incomplete
return -1
if rawdata[j:j+4] == "<!--":
j = self.parse_comment(j, report=0)
if j < 0:
return j
continue
name, j = self._scan_name(j + 2, declstartpos)
if j == -1:
return -1
if name not in set(["attlist", "element", "entity", "notation"]):
self.updatepos(declstartpos, j + 2)
self.error(
"unknown declaration %r in internal subset" % name)
# handle the individual names
meth = getattr(self, "_parse_doctype_" + name)
j = meth(j, declstartpos)
if j < 0:
return j
elif c == "%":
# parameter entity reference
if (j + 1) == n:
# end of buffer; incomplete
return -1
s, j = self._scan_name(j + 1, declstartpos)
if j < 0:
return j
if rawdata[j] == ";":
j = j + 1
elif c == "]":
j = j + 1
while j < n and rawdata[j].isspace():
j = j + 1
if j < n:
if rawdata[j] == ">":
return j
self.updatepos(declstartpos, j)
self.error("unexpected char after internal subset")
else:
return -1
elif c.isspace():
j = j + 1
else:
self.updatepos(declstartpos, j)
self.error("unexpected char %r in internal subset" % c)
# end of buffer reached
return -1
# Internal -- scan past <!ELEMENT declarations
def _parse_doctype_element(self, i, declstartpos):
name, j = self._scan_name(i, declstartpos)
if j == -1:
return -1
# style content model; just skip until '>'
rawdata = self.rawdata
if '>' in rawdata[j:]:
return rawdata.find(">", j) + 1
return -1
# Internal -- scan past <!ATTLIST declarations
def _parse_doctype_attlist(self, i, declstartpos):
rawdata = self.rawdata
name, j = self._scan_name(i, declstartpos)
c = rawdata[j:j+1]
if c == "":
return -1
if c == ">":
return j + 1
while 1:
# scan a series of attribute descriptions; simplified:
# name type [value] [#constraint]
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
c = rawdata[j:j+1]
if c == "":
return -1
if c == "(":
# an enumerated type; look for ')'
if ")" in rawdata[j:]:
j = rawdata.find(")", j) + 1
else:
return -1
while rawdata[j:j+1].isspace():
j = j + 1
if not rawdata[j:]:
# end of buffer, incomplete
return -1
else:
name, j = self._scan_name(j, declstartpos)
c = rawdata[j:j+1]
if not c:
return -1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if m:
j = m.end()
else:
return -1
c = rawdata[j:j+1]
if not c:
return -1
if c == "#":
if rawdata[j:] == "#":
# end of buffer
return -1
name, j = self._scan_name(j + 1, declstartpos)
if j < 0:
return j
c = rawdata[j:j+1]
if not c:
return -1
if c == '>':
# all done
return j + 1
# Internal -- scan past <!NOTATION declarations
def _parse_doctype_notation(self, i, declstartpos):
name, j = self._scan_name(i, declstartpos)
if j < 0:
return j
rawdata = self.rawdata
while 1:
c = rawdata[j:j+1]
if not c:
# end of buffer; incomplete
return -1
if c == '>':
return j + 1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if not m:
return -1
j = m.end()
else:
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
# Internal -- scan past <!ENTITY declarations
def _parse_doctype_entity(self, i, declstartpos):
rawdata = self.rawdata
if rawdata[i:i+1] == "%":
j = i + 1
while 1:
c = rawdata[j:j+1]
if not c:
return -1
if c.isspace():
j = j + 1
else:
break
else:
j = i
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
while 1:
c = self.rawdata[j:j+1]
if not c:
return -1
if c in "'\"":
m = _declstringlit_match(rawdata, j)
if m:
j = m.end()
else:
return -1 # incomplete
elif c == ">":
return j + 1
else:
name, j = self._scan_name(j, declstartpos)
if j < 0:
return j
# Internal -- scan a name token and the new position and the token, or
# return -1 if we've reached the end of the buffer.
def _scan_name(self, i, declstartpos):
rawdata = self.rawdata
n = len(rawdata)
if i == n:
return None, -1
m = _declname_match(rawdata, i)
if m:
s = m.group()
name = s.strip()
if (i + len(s)) == n:
return None, -1 # end of buffer
return name.lower(), m.end()
else:
self.updatepos(declstartpos, i)
self.error("expected name token at %r"
% rawdata[declstartpos:declstartpos+20])
# To be overridden -- handlers for unknown objects
def unknown_decl(self, data):
pass

Some files were not shown because too many files have changed in this diff Show More