Release 4.36.0

This commit is contained in:
PySimpleGUI 2021-03-14 22:14:30 -04:00
parent 968cd8af6c
commit 002718742a
1 changed files with 110 additions and 66 deletions

View File

@ -1,5 +1,5 @@
#!/usr/bin/python3 #!/usr/bin/python3
version = __version__ = "4.35.0.10 Unreleased\nUpdated debugger, Added checks for COLOR_SYSTEM_DEFAULT to several element update methods, changed GreenTan theme to use black instead of the COLOR_SYSTEM_DEFAULT setting, fix in button.update when subsample used, changed image update animation to start & stop at correct frame and added a return value for popup animated, bind event handling will add an item to a tuple rather than making an entirely new tuple (NOTE MAY BREAK SOME EXISTING APPLICATIONS...), theme will change gray to grey if needed, editor launcher - if spaces in the name then will auto add quotes if they don't already exist, scrollbar parm for Multiline element to enable turning off scrollbar, fix in execute_command_subprocess that fixed problem in 3.8, 3.9, 3.10., adding quotes in the exec subprocess func" version = __version__ = "4.36.0 Released 14-Mar-2021"
__version__ = version.split()[0] # For PEP 396 and PEP 345 __version__ = version.split()[0] # For PEP 396 and PEP 345
@ -13,33 +13,16 @@ except:
port = 'PySimpleGUI' port = 'PySimpleGUI'
# 888888ba .d88888b oo dP .88888. dP dP dP # 8""""8 8""""8 8""""8 8 8 8
# 88 `8b 88. "' 88 d8' `88 88 88 88 # 8 8 e e 8 e eeeeeee eeeee e eeee 8 " 8 8 8
# a88aaaa8P' dP dP `Y88888b. dP 88d8b.d8b. 88d888b. 88 .d8888b. 88 88 88 88 # 8eeee8 8 8 8eeeee 8 8 8 8 8 8 8 8 8e 8e 8 8e
# 88 88 88 `8b 88 88'`88'`88 88' `88 88 88ooood8 88 YP88 88 88 88 # 88 8eeee8 88 8e 8e 8 8 8eee8 8e 8eee 88 ee 88 8 88
# 88 88. .88 d8' .8P 88 88 88 88 88. .88 88 88. ... Y8. .88 Y8. .8P 88 # 88 88 e 88 88 88 8 8 88 88 88 88 8 88 8 88
# dP `8888P88 Y88888P dP dP dP dP 88Y888P' dP `88888P' `88888' `Y88888P' dP # 88 88 8eee88 88 88 8 8 88 88eee 88ee 88eee8 88ee8 88
# .88 88
# .88 88
# d8888P dP
# __ __
# / | / |
# $$ | ______ ______ ______ $$ |
# $$ | / \ / \ / \ $$ |
# $$ | /$$$$$$ |/$$$$$$ | $$$$$$ |$$ |
# $$ | $$ $$ |$$ | $$ | / $$ |$$ |
# $$ |_____ $$$$$$$$/ $$ \__$$ |/$$$$$$$ |$$ |
# $$ |$$ |$$ $$ |$$ $$ |$$ |
# $$$$$$$$/ $$$$$$$/ $$$$$$$ | $$$$$$$/ $$/
# / \__$$ |
# $$ $$/
# $$$$$$/
""" """
Copyright 2018, 2019, 2020, 2021 PySimpleGUI.org Copyright 2018, 2019, 2020, 2021 PySimpleGUI
Before getting into the details, let's talk about the high level goals of the PySimpleGUI project. Before getting into the details, let's talk about the high level goals of the PySimpleGUI project.
@ -1077,8 +1060,8 @@ class Element():
if isinstance(self.Key, str): if isinstance(self.Key, str):
key = self.Key + str(key_suffix) key = self.Key + str(key_suffix)
else: else:
# key = (self.Key, key_suffix) # old way (pre 2021) was to make a brand new tuple key = (self.Key, key_suffix) # old way (pre 2021) was to make a brand new tuple
key = self.Key + (key_suffix,) # if key is a tuple, add one more item # key = self.Key + (key_suffix,) # in 2021 tried this. It will break existing applications though - if key is a tuple, add one more item
else: else:
key = bind_string key = bind_string
@ -2821,8 +2804,16 @@ class Multiline(Element):
""" """
If this Widget is deleted, be sure and restore the old stdout, stderr If this Widget is deleted, be sure and restore the old stdout, stderr
""" """
self.restore_stdout() # These trys are here because found that if the init fails, then
self.restore_stderr() # the variables holding the old stdout won't exist and will get an error
try:
self.restore_stdout()
except Exception as e:
pass
try:
self.restore_stderr()
except:
pass
@ -2943,6 +2934,66 @@ class Text(Element):
""" """
return self.DisplayText return self.DisplayText
@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, to be measured
:type font: (str or Tuple[str, int] or None)
:param character: specifies a SINGLE CHARACTER character to measure
:type character: (str)
:return: Width in pixels of "A"
:rtype: (int)
"""
size = 0
try:
size = tkinter.font.Font(font=font).measure(character) # single character width
except Exception as e:
print('Error retrieving font information', 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, to be measured
:type font: (str or Tuple[str, int] or None)
:return: Height in pixels of "A"
:rtype: (int)
"""
size = 0
try:
size = tkinter.font.Font(font=font).metrics('linespace')
except Exception as e:
print('Error retrieving font information', 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.
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, to be measured
:type font: (str or Tuple[str, int] or None)
:param string: the string to measure
:type string: str
:return: Width in pixels of string
:rtype: (int)
"""
size = 0
try:
size = tkinter.font.Font(font=font).measure(string) # string's width
except Exception as e:
print('Error retrieving font information', e)
return size
Get = get Get = get
@ -17733,21 +17784,16 @@ def execute_command_subprocess(command, *args, wait=False, cwd=None):
:rtype: (subprocess.Popen) :rtype: (subprocess.Popen)
""" """
try: try:
if _running_linux(): if args is not None:
arg_string = '' expanded_args = ' '.join(args)
for arg in args: # print('executing subprocess command:',command, 'args:',expanded_args)
arg_string += ' ' + str(arg) if command[0] != '"' and ' ' in command:
sp = subprocess.Popen(str(command) + arg_string, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) command = '"'+command+'"'
# print('calling popen with:', command +' '+ expanded_args)
sp = subprocess.Popen(command +' '+ expanded_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
# sp = subprocess.Popen([command,], expanded_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
else: else:
if args is not None: sp = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
expanded_args = ' '.join(args)
# print('executing subprocess command:',command, 'args:',expanded_args)
if command[0] != '"' and ' ' in command:
command = '"'+command+'"'
sp = subprocess.Popen(command +' '+ expanded_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
# sp = subprocess.Popen([command,], expanded_args, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
else:
sp = subprocess.Popen([command, ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
if wait: if wait:
out, err = sp.communicate() out, err = sp.communicate()
if out: if out:
@ -17768,15 +17814,15 @@ def execute_py_file(pyfile, parms=None, cwd=None, interpreter_command=None, wait
2. global setting "-python command-" 2. global setting "-python command-"
3. the interpreter running running PySimpleGUI 3. the interpreter running running PySimpleGUI
:param pyfile: the file to run :param pyfile: the file to run
:type : (str) :type pyfile: (str)
:param parms: parameters to pass on the command line :param parms: parameters to pass on the command line
:type : :type parms: (str)
:param cwd: the working directory to use :param cwd: the working directory to use
:type : :type cwd: (str)
:param interpreter_command: the command used to invoke the Python interpreter :param interpreter_command: the command used to invoke the Python interpreter
:type : :type interpreter_command: (str)
:param wait: the working directory to use :param wait: the working directory to use
:type : :type wait: (bool)
:return: Popen object :return: Popen object
:rtype: (subprocess.Popen) | None :rtype: (subprocess.Popen) | None
""" """
@ -17814,7 +17860,7 @@ def execute_editor(file_to_edit, line_number=None):
:return: Popen object :return: Popen object
:rtype: (subprocess.Popen) | None :rtype: (subprocess.Popen) | None
""" """
if _running_windows() and file_to_edit is not None and len(file_to_edit) != 0 and file_to_edit[0] not in ('\"', "\'") and ' ' in file_to_edit: if file_to_edit is not None and len(file_to_edit) != 0 and file_to_edit[0] not in ('\"', "\'") and ' ' in file_to_edit:
file_to_edit = '"' + file_to_edit + '"' file_to_edit = '"' + file_to_edit + '"'
editor_program = pysimplegui_user_settings.get('-editor program-', None) editor_program = pysimplegui_user_settings.get('-editor program-', None)
@ -17934,8 +17980,8 @@ PSGDebugLogo = b'R0lGODlhMgAtAPcAAAAAADD/2akK/4yz0pSxyZWyy5u3zZ24zpW30pG52J250J+
red_x = b"R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw==" red_x = b"R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
COLOR_SCHEME = 'LightGreen' COLOR_SCHEME = 'dark grey 13'
DEBUGGER_POPOUT_THEME = 'dark grey 13'
WIDTH_VARIABLES = 23 WIDTH_VARIABLES = 23
WIDTH_RESULTS = 46 WIDTH_RESULTS = 46
@ -18008,17 +18054,16 @@ class _Debugger():
col1 = [ col1 = [
# [Frame('Auto Watches', autowatch_frame+variable_values, title_color='blue')] # [Frame('Auto Watches', autowatch_frame+variable_values, title_color='blue')]
[Frame('Auto Watches', autowatch_frame + var_layout, title_color='blue')] [Frame('Auto Watches', autowatch_frame + var_layout, title_color=theme_button_color()[0])]
] ]
col2 = [ col2 = [
[Frame('Variables or Expressions to Watch', variables_frame, title_color='blue'), ], [Frame('Variables or Expressions to Watch', variables_frame, title_color=theme_button_color()[0]), ],
[Frame('REPL-Light - Press Enter To Execute Commands', interactive_frame, title_color='blue'), ] [Frame('REPL-Light - Press Enter To Execute Commands', interactive_frame, title_color=theme_button_color()[0]), ]
] ]
# Tab based layout # Tab based layout
layout = [[TabGroup([[Tab('Variables', col1), Tab('REPL & Watches', col2)]])], layout = [[TabGroup([[Tab('Variables', col1), Tab('REPL & Watches', col2)]])]]
[Button('', image_data=red_x, key='_EXIT_', button_color=None), ]]
# ------------------------------- Create main window ------------------------------- # ------------------------------- Create main window -------------------------------
window = Window("PySimpleGUI Debugger", layout, icon=PSGDebugLogo, margins=(0, 0), location=location, keep_on_top=True) window = Window("PySimpleGUI Debugger", layout, icon=PSGDebugLogo, margins=(0, 0), location=location, keep_on_top=True)
@ -18044,9 +18089,9 @@ class _Debugger():
if not self.watcher_window: # if there is no window setup, nothing to do if not self.watcher_window: # if there is no window setup, nothing to do
return False return False
event, values = self.watcher_window.read(timeout=1) event, values = self.watcher_window.read(timeout=1)
if event in (None, 'Exit', '_EXIT_'): # EXIT BUTTON / X BUTTON if event in (None, 'Exit', '_EXIT_', '-EXIT-'): # EXIT BUTTON / X BUTTON
try: try:
self.watcher_window.Close() self.watcher_window.close()
except: except:
pass pass
self.watcher_window = None self.watcher_window = None
@ -18217,7 +18262,7 @@ class _Debugger():
# # # # # # # # ##### ###### ###### #### ## ## # # # # # # # # # # # ##### ###### ###### #### ## ## # # #
def _choose_auto_watches(self, my_locals): def _choose_auto_watches(self, my_locals):
ChangeLookAndFeel(COLOR_SCHEME) theme(COLOR_SCHEME)
num_cols = 3 num_cols = 3
output_text = '' output_text = ''
num_lines = 2 num_lines = 2
@ -18245,11 +18290,11 @@ class _Debugger():
layout += [ layout += [
[Ok(), Cancel(), Button('Clear All'), Button('Select [almost] All', key='_AUTO_SELECT_')]] [Ok(), Cancel(), Button('Clear All'), Button('Select [almost] All', key='_AUTO_SELECT_')]]
window = Window('All Locals', layout, icon=PSGDebugLogo).Finalize() window = Window('All Locals', layout, icon=PSGDebugLogo, finalize=True)
while True: # event loop while True: # event loop
event, values = window.read() event, values = window.read()
if event in (None, 'Cancel'): if event in (None, 'Cancel', '-EXIT-'):
break break
elif event == 'Ok': elif event == 'Ok':
self.local_choices = values self.local_choices = values
@ -18296,7 +18341,7 @@ class _Debugger():
""" """
if self.popout_window: # if floating window already exists, close it first if self.popout_window: # if floating window already exists, close it first
self.popout_window.Close() self.popout_window.Close()
ChangeLookAndFeel('Topanga') theme(DEBUGGER_POPOUT_THEME)
num_cols = 2 num_cols = 2
width_var = 15 width_var = 15
width_value = 30 width_value = 30
@ -18326,8 +18371,7 @@ class _Debugger():
col = 0 col = 0
if col != 0: if col != 0:
layout.append(line) layout.append(line)
layout = [[Column(layout), Column( layout = [[T(SYMBOL_X, enable_events=True, key='-EXIT-', font='_ 7')],[Column(layout)]]
[[Button('', key='_EXIT_', image_data=red_x, button_color=('#282923', '#282923'), border_width=0)]])]]
self.popout_window = Window('Floating', layout, alpha_channel=0, no_titlebar=True, grab_anywhere=True, self.popout_window = Window('Floating', layout, alpha_channel=0, no_titlebar=True, grab_anywhere=True,
element_padding=(0, 0), margins=(0, 0), keep_on_top=True, element_padding=(0, 0), margins=(0, 0), keep_on_top=True,
@ -18342,7 +18386,7 @@ class _Debugger():
self.popout_window.Move(screen_size[0] - self.popout_window.Size[0], 0) self.popout_window.Move(screen_size[0] - self.popout_window.Size[0], 0)
self.popout_window.SetAlpha(1) self.popout_window.SetAlpha(1)
ChangeLookAndFeel('SystemDefault') # ChangeLookAndFeel('SystemDefault')
return True return True
###### ######
@ -18377,7 +18421,7 @@ class _Debugger():
if key is not None and self.popout_window is not None: if key is not None and self.popout_window is not None:
self.popout_window.Element(key, silent_on_error=True).Update(self.locals.get(key)) self.popout_window.Element(key, silent_on_error=True).Update(self.locals.get(key))
event, values = self.popout_window.read(timeout=5) event, values = self.popout_window.read(timeout=5)
if event in (None, '_EXIT_', 'Exit::RightClick'): if event in (None, '_EXIT_', 'Exit::RightClick', '-EXIT-'):
self.popout_window.Close() self.popout_window.Close()
self.popout_window = None self.popout_window = None
elif event == 'Debugger::RightClick': elif event == 'Debugger::RightClick':