initiated venv, installed FreeSimpleGUI and PySerial
This commit is contained in:
16576
.venv/lib/python3.12/site-packages/FreeSimpleGUI/__init__.py
Normal file
16576
.venv/lib/python3.12/site-packages/FreeSimpleGUI/__init__.py
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
140
.venv/lib/python3.12/site-packages/FreeSimpleGUI/_utils.py
Normal file
140
.venv/lib/python3.12/site-packages/FreeSimpleGUI/_utils.py
Normal 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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1055
.venv/lib/python3.12/site-packages/FreeSimpleGUI/elements/base.py
Normal file
1055
.venv/lib/python3.12/site-packages/FreeSimpleGUI/elements/base.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
@ -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]))
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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,
|
||||
)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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)
|
||||
697
.venv/lib/python3.12/site-packages/FreeSimpleGUI/elements/tab.py
Normal file
697
.venv/lib/python3.12/site-packages/FreeSimpleGUI/elements/tab.py
Normal 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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
358
.venv/lib/python3.12/site-packages/FreeSimpleGUI/tray.py
Normal file
358
.venv/lib/python3.12/site-packages/FreeSimpleGUI/tray.py
Normal 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
|
||||
2875
.venv/lib/python3.12/site-packages/FreeSimpleGUI/window.py
Normal file
2875
.venv/lib/python3.12/site-packages/FreeSimpleGUI/window.py
Normal file
File diff suppressed because it is too large
Load Diff
33
.venv/lib/python3.12/site-packages/_yaml/__init__.py
Normal file
33
.venv/lib/python3.12/site-packages/_yaml/__init__.py
Normal 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__ = ''
|
||||
Binary file not shown.
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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`
|
||||
|
||||

|
||||
|
||||
|
||||
-------------------------
|
||||
|
||||
|
||||
|
||||
## 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 |  |
|
||||
| FreeSimpleGUIQt |  |
|
||||
| FreeSimpleGUIWx |  |
|
||||
| FreeSimpleGUIWeb |  |
|
||||
|
||||
|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
|
||||
<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.
|
||||
|
||||

|
||||
|
||||
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
|
||||
@ -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.
|
||||
@ -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
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: setuptools (76.0.0)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -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
|
||||
@ -0,0 +1 @@
|
||||
FreeSimpleGUI
|
||||
@ -0,0 +1 @@
|
||||
pip
|
||||
@ -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.
|
||||
@ -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.
|
||||
|
||||
417
.venv/lib/python3.12/site-packages/future-1.0.0.dist-info/RECORD
Normal file
417
.venv/lib/python3.12/site-packages/future-1.0.0.dist-info/RECORD
Normal 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,,
|
||||
@ -0,0 +1,5 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.41.2)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py3-none-any
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
[console_scripts]
|
||||
futurize = libfuturize.main:main
|
||||
pasteurize = libpasteurize.main:main
|
||||
@ -0,0 +1,4 @@
|
||||
future
|
||||
libfuturize
|
||||
libpasteurize
|
||||
past
|
||||
92
.venv/lib/python3.12/site-packages/future/__init__.py
Normal file
92
.venv/lib/python3.12/site-packages/future/__init__.py
Normal 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__)
|
||||
Binary file not shown.
@ -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
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
Reference in New Issue
Block a user