Merge pull request #5387 from PySimpleGUI/Dev-latest

Output element - changed to be a subclass of the Multiline as part of…
This commit is contained in:
PySimpleGUI 2022-04-20 06:14:41 -04:00 committed by GitHub
commit 1646a15914
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 209 additions and 201 deletions

View File

@ -1,6 +1,6 @@
#!/usr/bin/python3 #!/usr/bin/python3
version = __version__ = "4.59.0.21 Released 5-Apr-2022" version = __version__ = "4.59.0.22 Released 5-Apr-2022"
_change_log = """ _change_log = """
Changelog since 4.59.0 released to PyPI on 5-Apr-2022 Changelog since 4.59.0 released to PyPI on 5-Apr-2022
@ -79,7 +79,10 @@ _change_log = """
irony - when you accidently leave debug prints in your debug print code irony - when you accidently leave debug prints in your debug print code
4.59.0.21 4.59.0.21
Additional exception handling needed for debug window closure at any point Additional exception handling needed for debug window closure at any point
4.59.0.22
"Ding-dong the Output's dead" - finally replaced the Output element with Multiline by subclassing Multiline
This solves a bunch of problems including duplication of funcationality using 2 different techniques.
Problem may be in backward compatibility if anyone is using internal Output element member variables, etc.
""" """
__version__ = version.split()[0] # For PEP 396 and PEP 345 __version__ = version.split()[0] # For PEP 396 and PEP 345
@ -4259,117 +4262,119 @@ class TKProgressBar():
return False return False
return True return True
#
# ---------------------------------------------------------------------- # # # ---------------------------------------------------------------------- #
# TKOutput # # # TKOutput #
# New Type of TK Widget that's a Text Widget in disguise # # # New Type of TK Widget that's a Text Widget in disguise #
# Note that it's inherited from the TKFrame class so that the # # # Note that it's inherited from the TKFrame class so that the #
# Scroll bar will span the length of the frame # # # Scroll bar will span the length of the frame #
# ---------------------------------------------------------------------- # # # ---------------------------------------------------------------------- #
class TKOutput(tk.Frame): # class TKOutput(tk.Frame):
""" # """
tkinter style class. Inherits Frame class from tkinter. Adds a tk.Text and a scrollbar together. # tkinter style class. Inherits Frame class from tkinter. Adds a tk.Text and a scrollbar together.
Note - This is NOT a user controlled class. Users should NOT be directly using it unless making an extention # Note - This is NOT a user controlled class. Users should NOT be directly using it unless making an extention
to PySimpleGUI by directly manipulating tkinter. # to PySimpleGUI by directly manipulating tkinter.
""" # """
#
def __init__(self, parent, width, height, bd, background_color=None, text_color=None, echo_stdout_stderr=False, font=None, pad=None): # def __init__(self, parent, width, height, bd, background_color=None, text_color=None, echo_stdout_stderr=False, font=None, pad=None):
""" # """
:param parent: The "Root" that the Widget will be in # :param parent: The "Root" that the Widget will be in
:type parent: tk.Tk | tk.Toplevel # :type parent: tk.Tk | tk.Toplevel
:param width: Width in characters # :param width: Width in characters
:type width: (int) # :type width: (int)
:param height: height in rows # :param height: height in rows
:type height: (int) # :type height: (int)
:param bd: Border Depth. How many pixels of border to show # :param bd: Border Depth. How many pixels of border to show
:type bd: (int) # :type bd: (int)
:param background_color: color of background # :param background_color: color of background
:type background_color: (str) # :type background_color: (str)
:param text_color: color of the text # :param text_color: color of the text
:type text_color: (str) # :type text_color: (str)
:param echo_stdout_stderr: If True then output to stdout will be output to this element AND also to the normal console location # :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) # :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 # :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) # :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) # :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 # :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int
""" # """
self.frame = tk.Frame(parent) # self.frame = tk.Frame(parent)
tk.Frame.__init__(self, self.frame) # tk.Frame.__init__(self, self.frame)
self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font) # self.output = tk.Text(self.frame, width=width, height=height, bd=bd, font=font)
if background_color and background_color != COLOR_SYSTEM_DEFAULT: # if background_color and background_color != COLOR_SYSTEM_DEFAULT:
self.output.configure(background=background_color) # self.output.configure(background=background_color)
self.frame.configure(background=background_color) # self.frame.configure(background=background_color)
if text_color and text_color != COLOR_SYSTEM_DEFAULT: # if text_color and text_color != COLOR_SYSTEM_DEFAULT:
self.output.configure(fg=text_color) # self.output.configure(fg=text_color)
self.output.configure(insertbackground=text_color) # self.output.configure(insertbackground=text_color)
self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview) # self.vsb = tk.Scrollbar(self.frame, orient="vertical", command=self.output.yview)
self.output.configure(yscrollcommand=self.vsb.set) # self.output.configure(yscrollcommand=self.vsb.set)
self.output.pack(side="left", fill="both", expand=True) # self.output.pack(side="left", fill="both", expand=True)
self.vsb.pack(side="left", fill="y", expand=False) # self.vsb.pack(side="left", fill="y", expand=False)
self.frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y') # self.frame.pack(side="left", padx=pad[0], pady=pad[1], expand=True, fill='y')
self.previous_stdout = sys.stdout # self.previous_stdout = sys.stdout
self.previous_stderr = sys.stderr # self.previous_stderr = sys.stderr
self.parent = parent # self.parent = parent
self.echo_stdout_stderr = echo_stdout_stderr # self.echo_stdout_stderr = echo_stdout_stderr
#
sys.stdout = self # sys.stdout = self
sys.stderr = self # sys.stderr = self
self.pack() # self.pack()
#
def write(self, txt): # def write(self, txt):
""" # """
Called by Python (not tkinter?) when stdout or stderr wants to write # Called by Python (not tkinter?) when stdout or stderr wants to write
Refreshes the window after the write so that the change is immediately displayed # Refreshes the window after the write so that the change is immediately displayed
#
:param txt: text of output # :param txt: text of output
:type txt: (str) # :type txt: (str)
""" # """
try: # try:
self.output.insert(tk.END, str(txt)) # self.output.insert(tk.END, str(txt))
self.output.see(tk.END) # self.output.see(tk.END)
self.parent.update() # self.parent.update()
except: # except:
pass # pass
#
try: # try:
if self.echo_stdout_stderr: # if self.echo_stdout_stderr:
self.previous_stdout.write(txt) # self.previous_stdout.write(txt)
except: # except:
pass # pass
#
def Close(self): # def Close(self):
""" # """
Called when wanting to restore the old stdout/stderr # Called when wanting to restore the old stdout/stderr
""" # """
sys.stdout = self.previous_stdout # sys.stdout = self.previous_stdout
sys.stderr = self.previous_stderr # sys.stderr = self.previous_stderr
#
def flush(self): # def flush(self):
""" # """
Flush parameter was passed into a print statement. # 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. # For now doing nothing. Not sure what action should be taken to ensure a flush happens regardless.
""" # """
try: # try:
if self.echo_stdout_stderr: # if self.echo_stdout_stderr:
self.previous_stdout.flush() # self.previous_stdout.flush()
except: # except:
pass # pass
#
def __del__(self): # def __del__(self):
""" # """
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
""" # """
sys.stdout = self.previous_stdout # sys.stdout = self.previous_stdout
sys.stderr = self.previous_stderr # sys.stderr = self.previous_stderr
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #
# Output # # Output #
# Routes stdout, stderr to a scrolled window # # Routes stdout, stderr to a scrolled window #
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #
class Output(Element): class Output(Multiline):
""" """
** NOTE - It's recommended to use Multiline Element instead **
Output Element - a multi-lined text area where stdout and stderr are re-routed to. Output Element - a multi-lined text area where stdout and stderr are re-routed to.
The Multiline Element is the superior and recommended method for showing the output of stdout. The Multiline Element is the superior and recommended method for showing the output of stdout.
@ -4378,6 +4383,9 @@ class Output(Element):
Of course, Output Element continues to operate and be backwards compatible, but you're missing out on Of course, Output Element continues to operate and be backwards compatible, but you're missing out on
features such as routing the cprint output to the element. features such as routing the cprint output to the element.
In Apr 2022, the Output Element was switched to be a subclass of the Multiline so that more code will be in common. Nowever
you will not get all of the parms unless you switch to the Multline Specifically
""" """
def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, echo_stdout_stderr=False, font=None, tooltip=None, def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, echo_stdout_stderr=False, font=None, tooltip=None,
@ -4417,103 +4425,103 @@ class Output(Element):
:type metadata: (Any) :type metadata: (Any)
""" """
self._TKOut = self.Widget = None # type: TKOutput # self._TKOut = self.Widget = None # type: TKOutput
bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR # bg = background_color if background_color else DEFAULT_INPUT_ELEMENTS_COLOR
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR # fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
self.RightClickMenu = right_click_menu # self.RightClickMenu = right_click_menu
key = key if key is not None else k # key = key if key is not None else k
self.echo_stdout_stderr = echo_stdout_stderr # self.echo_stdout_stderr = echo_stdout_stderr
sz = size if size != (None, None) else s # sz = size if size != (None, None) else s
pad = pad if pad is not None else p # pad = pad if pad is not None else p
self.expand_x = expand_x # self.expand_x = expand_x
self.expand_y = expand_y # self.expand_y = expand_y
super().__init__(ELEM_TYPE_OUTPUT, size=sz, background_color=bg, text_color=fg, pad=pad, font=font, super().__init__(size=size, s=s, background_color=background_color, text_color=text_color, pad=pad, p=p, echo_stdout_stderr=echo_stdout_stderr, font=font, tooltip=tooltip,
tooltip=tooltip, key=key, visible=visible, metadata=metadata) key=key, k=k, right_click_menu=right_click_menu, write_only=True, reroute_stdout=True, reroute_stderr=True, autoscroll=True, expand_x=expand_x, expand_y=expand_y, visible=visible, metadata=metadata)
#
# @property
# def tk_out(self):
# """
# Returns the TKOutput object used to create the element
#
# :return: The TKOutput object
# :rtype: (TKOutput)
# """
# if self._TKOut is None:
# print('*** Did you forget to call Finalize()? Your code should look something like: ***')
# print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
# return self._TKOut
#
# def update(self, value=None, visible=None):
# """
# Changes some of the settings for the Output 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: string that will replace current contents of the output area
# :type value: (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 value is not None:
# self._TKOut.output.delete('1.0', tk.END)
# self._TKOut.output.insert(tk.END, value)
# if visible is False:
# self._pack_forget_save_settings(self._TKOut.frame)
# elif visible is True:
# self._pack_restore_settings(self._TKOut.frame)
#
# if visible is not None:
# self._visible = visible
#
# def get(self):
# """
# Returns the current contents of the output. Similar to Get method other Elements
# :return: the current value of the output
# :rtype: (str)
# """
# return self._TKOut.output.get(1.0, tk.END)
#
# def expand(self, expand_x=False, expand_y=False, expand_row=True):
# """
# Causes the Element to expand to fill available space in the X and Y directions. Can specify which or both directions
#
# :param expand_x: If True Element will expand in the Horizontal directions
# :type expand_x: (bool)
# :param expand_y: If True Element will expand in the Vertical directions
# :type expand_y: (bool)
# """
#
# if expand_x and expand_y:
# fill = tk.BOTH
# elif expand_x:
# fill = tk.X
# elif expand_y:
# fill = tk.Y
# else:
# return
#
# self._TKOut.output.pack(expand=True, fill=fill)
# self._TKOut.frame.pack(expand=True, fill=fill)
# self.ParentRowFrame.pack(expand=expand_row, fill=fill)
#
# def __del__(self):
# """
# Delete this element. Normally Elements do not have their delete method specified, but for this one
# it's important that the underlying TKOut object get deleted so that the stdout will get restored properly
# """
# self._TKOut.__del__()
@property # TKOut = tk_out
def tk_out(self): # Update = update
""" # Get = get
Returns the TKOutput object used to create the element
:return: The TKOutput object
:rtype: (TKOutput)
"""
if self._TKOut is None:
print('*** Did you forget to call Finalize()? Your code should look something like: ***')
print('*** form = sg.Window("My Form").Layout(layout).Finalize() ***')
return self._TKOut
def update(self, value=None, visible=None):
"""
Changes some of the settings for the Output 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: string that will replace current contents of the output area
:type value: (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 value is not None:
self._TKOut.output.delete('1.0', tk.END)
self._TKOut.output.insert(tk.END, value)
if visible is False:
self._pack_forget_save_settings(self._TKOut.frame)
elif visible is True:
self._pack_restore_settings(self._TKOut.frame)
if visible is not None:
self._visible = visible
def get(self):
"""
Returns the current contents of the output. Similar to Get method other Elements
:return: the current value of the output
:rtype: (str)
"""
return self._TKOut.output.get(1.0, tk.END)
def expand(self, expand_x=False, expand_y=False, expand_row=True):
"""
Causes the Element to expand to fill available space in the X and Y directions. Can specify which or both directions
:param expand_x: If True Element will expand in the Horizontal directions
:type expand_x: (bool)
:param expand_y: If True Element will expand in the Vertical directions
:type expand_y: (bool)
"""
if expand_x and expand_y:
fill = tk.BOTH
elif expand_x:
fill = tk.X
elif expand_y:
fill = tk.Y
else:
return
self._TKOut.output.pack(expand=True, fill=fill)
self._TKOut.frame.pack(expand=True, fill=fill)
self.ParentRowFrame.pack(expand=expand_row, fill=fill)
def __del__(self):
"""
Delete this element. Normally Elements do not have their delete method specified, but for this one
it's important that the underlying TKOut object get deleted so that the stdout will get restored properly
"""
self._TKOut.__del__()
TKOut = tk_out
Update = update
Get = get
# ---------------------------------------------------------------------- # # ---------------------------------------------------------------------- #