From b30f6ce42c70698335eaf86c42f73703fbfe55c3 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Thu, 13 Jun 2019 17:59:07 -0400 Subject: [PATCH] Release 3.39, 1.39 --- PySimpleGUI.py | 4 +- PySimpleGUI27.py | 562 ++++++++++++++++++++++++----------------------- docs/index.md | 43 +++- readme.md | 43 +++- 4 files changed, 364 insertions(+), 288 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 6e521f28..edce3992 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -631,7 +631,7 @@ class Combo(Element): tooltip=None, readonly=False, font=None, visible=True): ''' Combo - :param values: list of values + :param values: :param default_value: :param size: :param auto_size_text: @@ -1682,7 +1682,7 @@ class Button(Element): pass elif self.BType == BUTTON_TYPE_SHOW_DEBUGGER: if self.ParentForm.DebuggerEnabled: - Debugger._build_floating_window('self normally goes here') + Debugger.debugger._build_floating_window() # show_debugger_window() if should_submit_window: diff --git a/PySimpleGUI27.py b/PySimpleGUI27.py index d6b0ac85..10ccc271 100644 --- a/PySimpleGUI27.py +++ b/PySimpleGUI27.py @@ -32,7 +32,6 @@ else: import types import datetime import time -import textwrap import pickle import calendar from random import randint @@ -66,7 +65,7 @@ def TimerStop(): g_time_end = time.time() g_time_delta = g_time_end - g_time_start - print(int(g_time_delta * 1000)) + print((g_time_delta * 1000)) """ @@ -410,7 +409,7 @@ class Element(object): self.TKRightClickMenu = None self.Widget = None # Set when creating window. Has the main tkinter widget for element - def RightClickMenuCallback(self, event): + def _RightClickMenuCallback(self, event): self.TKRightClickMenu.tk_popup(event.x_root, event.y_root, 0) self.TKRightClickMenu.grab_release() @@ -514,7 +513,7 @@ class Element(object): if self.ParentForm.CurrentlyRunningMainloop: self.ParentForm.TKroot.quit() - def KeyboardHandler(self, event): + def _KeyboardHandler(self, event): if self.Key is not None: self.ParentForm.LastButtonClicked = self.Key else: @@ -523,7 +522,7 @@ class Element(object): if self.ParentForm.CurrentlyRunningMainloop: self.ParentForm.TKroot.quit() - def ClickHandler(self, event): + def _ClickHandler(self, event): if self.Key is not None: self.ParentForm.LastButtonClicked = self.Key else: @@ -1696,7 +1695,7 @@ class Button(Element): pass elif self.BType == BUTTON_TYPE_SHOW_DEBUGGER: if self.ParentForm.DebuggerEnabled: - Debugger._build_floating_window('self normally goes here') + Debugger.debugger._build_floating_window() # show_debugger_window() if should_submit_window: @@ -2260,10 +2259,12 @@ class Graph(Element): def DeleteFigure(self, id): try: - del self.Images[id] self._TKCanvas2.delete(id) except: print('DeleteFigure - bad ID {}'.format(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, visible=None): if self._TKCanvas2 is None: @@ -2589,7 +2590,8 @@ class TabGroup(Element): self.ParentWindow = None self.SelectedTitleColor = selected_title_color self.Rows = [] - self.TKNotebook = None + self.TKNotebook = None # type: ttk.Notebook + self.Widget = None # type: ttk.Notebook self.TabCount = 0 self.BorderWidth = border_width self.Theme = theme @@ -2635,6 +2637,12 @@ class TabGroup(Element): return element.Key return None + def SelectTab(self, index): + try: + self.TKNotebook.select(index) + except Exception as e: + print('Exception Selecting Tab {}'.format(e)) + def __del__(self): for row in self.Rows: for element in row: @@ -3303,7 +3311,7 @@ class Table(Element): self.DisplayRowNumbers = display_row_numbers self.NumRows = num_rows if num_rows is not None else size[1] self.RowHeight = row_height - self.TKTreeview = None + self.TKTreeview = None # type: ttk.Treeview self.AlternatingRowColor = alternating_row_color self.VerticalScrollOnly = vertical_scroll_only self.HideVerticalScroll = hide_vertical_scroll @@ -3319,7 +3327,7 @@ class Table(Element): size=size, pad=pad, key=key, tooltip=tooltip, visible=visible) return - def Update(self, values=None, num_rows=None, visible=None): + def Update(self, values=None, num_rows=None, visible=None, select_rows=None): if values is not None: children = self.TKTreeview.get_children() for i in children: @@ -3341,6 +3349,9 @@ class Table(Element): self.TKTreeview.pack() 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) def treeview_selected(self, event): selections = self.TKTreeview.selection() @@ -3712,6 +3723,25 @@ class Window(object): 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) == list: + PopupError('Error creating layout', + 'Layout has a LIST instead of an ELEMENT', + 'This means you have a badly placed ]', + 'The offensive list is:', + element, + 'This list will be stripped from your layout' + ) + continue + elif callable(element): + PopupError('Error creating layout', + 'Layout has a FUNCTION instead of an ELEMENT', + 'This means you are missing () from your layout', + 'The offensive list is:', + element, + 'This item will be stripped from your layout' + ) + continue + element.Position = (CurrentRowNumber, i) element.ParentContainer = self CurrentRow.append(element) @@ -4196,10 +4226,12 @@ class Window(object): return def Disable(self): - self.TKroot.grab_set_global() + self.TKroot.attributes('-disabled', 1) + # self.TKroot.grab_set_global() def Enable(self): - self.TKroot.grab_release() + self.TKroot.attributes('-disabled', 0) + # self.TKroot.grab_release() def Hide(self): self._Hidden = True @@ -4277,12 +4309,18 @@ class Window(object): self.TKroot.unbind("") self.TKroot.unbind("") + def _callback_main_debugger_window_create_keystroke(self, event): + Debugger.debugger._build_main_debugger_window() + + def _callback_popout_window_create_keystroke(self, event): + Debugger.debugger._build_floating_window() + def EnableDebugger(self): - self.TKroot.bind('', Debugger._build_main_debugger_window) - # root.bind('', show_debugger_popout_window) - self.TKroot.bind('', Debugger._build_floating_window) + self.TKroot.bind('', self._callback_main_debugger_window_create_keystroke) + self.TKroot.bind('', self._callback_popout_window_create_keystroke) self.DebuggerEnabled = True + def DisableDebugger(self): self.TKroot.unbind("") self.TKroot.unbind("") @@ -5134,7 +5172,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKColFrame.bind('', element.RightClickMenuCallback) + element.TKColFrame.bind('', element._RightClickMenuCallback) # ------------------------- Pane element ------------------------- # if element_type == ELEM_TYPE_PANE: bd = element.BorderDepth if element.BorderDepth is not None else border_depth @@ -5213,7 +5251,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): tktext_label.configure(background=element.BackgroundColor) if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: tktext_label.configure(fg=element.TextColor) - tktext_label.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True) + tktext_label.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1]) if element.Visible is False: tktext_label.pack_forget() element.TKText = tktext_label @@ -5226,7 +5264,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - tktext_label.bind('', element.RightClickMenuCallback) + tktext_label.bind('', element._RightClickMenuCallback) # ------------------------- BUTTON element ------------------------- # elif element_type == ELEM_TYPE_BUTTON: element = element # type: Button @@ -5400,7 +5438,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): textvariable=element.TKStringVar, bd=border_depth, font=font, show=show, justify=justify) if element.ChangeSubmits: - element.TKEntry.bind('', element.KeyboardHandler) + element.TKEntry.bind('', element._KeyboardHandler) element.TKEntry.bind('', element.ReturnKeyHandler) if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: element.TKEntry.configure(background=element.BackgroundColor) @@ -5421,10 +5459,10 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKEntry.bind('', element.RightClickMenuCallback) + element.TKEntry.bind('', element._RightClickMenuCallback) # ------------------------- COMBOBOX element ------------------------- # elif element_type == ELEM_TYPE_INPUT_COMBO: - max_line_len = max([len(str(l)) for l in element.Values]) + max_line_len = max([len(str(l)) for l in element.Values]) if len(element.Values) else 0 if auto_size_text is False: width = element_size[0] else: @@ -5490,7 +5528,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if v == element.DefaultValue: element.TKCombo.current(i) break - else: + elif element.Values: element.TKCombo.current(0) if element.ChangeSubmits: element.TKCombo.bind('<>', element.ComboboxSelectHandler) @@ -5570,7 +5608,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKListbox.bind('', element.RightClickMenuCallback) + element.TKListbox.bind('', element._RightClickMenuCallback) # ------------------------- MULTILINE element ------------------------- # elif element_type == ELEM_TYPE_INPUT_MULTILINE: element = element # type: Multiline @@ -5589,7 +5627,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.Visible is False: element.TKText.pack_forget() if element.ChangeSubmits: - element.TKText.bind('', element.KeyboardHandler) + element.TKText.bind('', element._KeyboardHandler) if element.EnterSubmits: element.TKText.bind('', element.ReturnKeyHandler) if element.Focus is True or (toplevel_form.UseDefaultFocus and not focus_set): @@ -5606,7 +5644,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKText.bind('', element.RightClickMenuCallback) + element.TKText.bind('', element._RightClickMenuCallback) row_should_expand = True # ------------------------- CHECKBOX element ------------------------- # elif element_type == ELEM_TYPE_INPUT_CHECKBOX: @@ -5743,7 +5781,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element._TKOut.bind('', element.RightClickMenuCallback) + element._TKOut.bind('', element._RightClickMenuCallback) row_should_expand = True # ------------------------- IMAGE element ------------------------- # elif element_type == ELEM_TYPE_IMAGE: @@ -5781,13 +5819,13 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TooltipObject = ToolTip(element.tktext_label, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) if element.EnableEvents: - element.tktext_label.bind('', element.ClickHandler) + element.tktext_label.bind('', element._ClickHandler) if element.RightClickMenu or toplevel_form.RightClickMenu: menu = element.RightClickMenu or toplevel_form.RightClickMenu top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.tktext_label.bind('', element.RightClickMenuCallback) + element.tktext_label.bind('', element._RightClickMenuCallback) # ------------------------- Canvas element ------------------------- # elif element_type == ELEM_TYPE_CANVAS: width, height = element_size @@ -5809,7 +5847,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element._TKCanvas.bind('', element.RightClickMenuCallback) + element._TKCanvas.bind('', element._RightClickMenuCallback) # ------------------------- Graph element ------------------------- # elif element_type == ELEM_TYPE_GRAPH: element = element # type: Graph @@ -5843,7 +5881,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element._TKCanvas2.bind('', element.RightClickMenuCallback) + element._TKCanvas2.bind('', element._RightClickMenuCallback) # ------------------------- MENUBAR element ------------------------- # elif element_type == ELEM_TYPE_MENUBAR: element = element # type: MenuBar @@ -5896,7 +5934,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - labeled_frame.bind('', element.RightClickMenuCallback) + labeled_frame.bind('', element._RightClickMenuCallback) # ------------------------- Tab element ------------------------- # elif element_type == ELEM_TYPE_TAB: element.TKFrame = element.Widget = tk.Frame(form.TKNotebook) @@ -5931,10 +5969,10 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKFrame.bind('', element.RightClickMenuCallback) + element.TKFrame.bind('', element._RightClickMenuCallback) # ------------------------- TabGroup element ------------------------- # elif element_type == ELEM_TYPE_TAB_GROUP: - + element=element # type: TabGroup custom_style = str(element.Key) + 'customtab.TNotebook' style = tkinter.ttk.Style(tk_row_frame) if element.Theme is not None: @@ -6130,7 +6168,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKTreeview.bind('', element.RightClickMenuCallback) + element.TKTreeview.bind('', element._RightClickMenuCallback) # ------------------------- Tree element ------------------------- # elif element_type == ELEM_TYPE_TREE: element = element # type: Tree @@ -6216,7 +6254,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): top_menu = tk.Menu(toplevel_form.TKroot, tearoff=False) AddMenuItem(top_menu, menu[1], element) element.TKRightClickMenu = top_menu - element.TKTreeview.bind('', element.RightClickMenuCallback) + element.TKTreeview.bind('', element._RightClickMenuCallback) # ------------------------- Separator element ------------------------- # elif element_type == ELEM_TYPE_SEPARATOR: element = element # type: VerticalSeparator @@ -6350,9 +6388,11 @@ def StartupTK(my_flex_form): root = tk.Toplevel() if my_flex_form.DebuggerEnabled: - root.bind('', Debugger._build_main_debugger_window) - # root.bind('', show_debugger_popout_window) - root.bind('', Debugger._build_floating_window) + root.bind('', my_flex_form._callback_main_debugger_window_create_keystroke) + root.bind('', my_flex_form._callback_popout_window_create_keystroke) + + # root.bind('', Debugger._build_main_debugger_window) + # root.bind('', Debugger._build_floating_window) try: root.attributes('-alpha', 0) # hide window while building it. makes for smoother 'paint' except: @@ -8330,21 +8370,19 @@ WIDTH_VARIABLES = 23 WIDTH_RESULTS = 46 WIDTH_WATCHER_VARIABLES = 20 -WIDTH_WATCHER_RESULTS = 58 +WIDTH_WATCHER_RESULTS = 60 WIDTH_LOCALS = 80 -NUM_AUTO_WATCH = 13 +NUM_AUTO_WATCH = 9 +MAX_LINES_PER_RESULT_FLOATING = 4 +MAX_LINES_PER_RESULT_MAIN = 3 + +POPOUT_WINDOW_FONT = 'Sans 8' class Debugger(object): - watcher_window = None # type: Window - popout_window = None # type: Window - local_choices = {} - myrc = '' - custom_watch = '' - locals = {} - globals = {} - popout_choices = {} + + debugger = None # # ###### ## ## ## # # # # # ###### ##### # # #### #### ###### ##### @@ -8354,11 +8392,23 @@ class Debugger(object): # # # # # # ## # # # # # # # # # # # # # # # # # # # # # ###### ###### ##### #### #### #### ###### # # - # Includes the DUAL PANE! Don't forget REPL is there too! - def _build_main_debugger_window(self): - if Debugger.watcher_window: - return + def __init__(self): + self.watcher_window = None # type: Window + self.popout_window = None # type: Window + self.local_choices = {} + self.myrc = '' + self.custom_watch = '' + self.locals = {} + self.globals = {} + self.popout_choices = {} + + + def _build_main_debugger_window_callback(self, events): + self._build_main_debugger_window() + + # Includes the DUAL PANE (now 2 tabs)! Don't forget REPL is there too! + def _build_main_debugger_window(self, location=(None, None)): ChangeLookAndFeel(COLOR_SCHEME) def InVar(key1): @@ -8368,13 +8418,13 @@ class Debugger(object): B('Obj', key=key1 + 'OBJ_'), ] return row1 - variables_frame = [InVar('_VAR1_'), - InVar('_VAR2_'), - InVar('_VAR3_'), ] + variables_frame = [InVar('_VAR0_'), + InVar('_VAR1_'), + InVar('_VAR2_'), ] - interactive_frame = [[T('>>> ', size=(9, 1), justification='r'), In(size=(83, 1), key='_INTERACTIVE_'), - B('Go', bind_return_key=True, visible=False)], - [T('CODE >>> ', justification='r', size=(9, 1)), In(size=(83, 1), key='_CODE_')], + interactive_frame = [[T('>>> '), In(size=(83, 1), key='_REPL_', + tooltip='Type in any "expression" or "statement"\n and it will be disaplayed below.\nPress RETURN KEY instead of "Go"\nbutton for faster use'), + B('Go', bind_return_key=True, visible=True)], [Multiline(size=(93, 26), key='_OUTPUT_', autoscroll=True, do_not_clear=True)], ] autowatch_frame = [[Button('Choose Variables To Auto Watch', key='_LOCALS_'), @@ -8382,14 +8432,17 @@ class Debugger(object): Button('Show All Variables', key='_SHOW_ALL_'), Button('Locals', key='_ALL_LOCALS_'), Button('Globals', key='_GLOBALS_'), - Button('Popout', key='_POPOUT_')]] + \ - [ - [T('', size=(WIDTH_WATCHER_VARIABLES, 1), key='_WATCH%s_' % i), - T('', size=(WIDTH_WATCHER_RESULTS, 2), key='_WATCH%s_RESULT_' % i, - auto_size_text=True)] for i in range(1, NUM_AUTO_WATCH + 1)] + Button('Popout', key='_POPOUT_')]] + + var_layout = [] + for i in range(NUM_AUTO_WATCH): + var_layout.append([T('', size=(WIDTH_WATCHER_VARIABLES, 1), key='_WATCH%s_' % i), + T('', size=(WIDTH_WATCHER_RESULTS, MAX_LINES_PER_RESULT_MAIN), key='_WATCH%s_RESULT_' % i, + )]) col1 = [ - [Frame('Auto Watches', autowatch_frame, title_color='blue')] + # [Frame('Auto Watches', autowatch_frame+variable_values, title_color='blue')] + [Frame('Auto Watches', autowatch_frame+var_layout, title_color='blue')] ] col2 = [ @@ -8397,15 +8450,15 @@ class Debugger(object): [Frame('REPL-Light - Press Enter To Execute Commands', interactive_frame, title_color='blue'), ] ] - layout = [[Pane([Column(col1), Column(col2)], size=(700, 640), orientation='h', background_color='red', - show_handle=True, ), ], - [Button('', image_data=red_x, key='_EXIT_', button_color=None), - Text('Pull Red Line For REPL & Object Display Screen ---> ', size=(80, 1), justification='r')]] + # Tab based layout + layout = [[TabGroup([[Tab('Variables', col1), Tab('REPL & Watches', col2)]])], + [Button('', image_data=red_x, key='_EXIT_', button_color=None),]] - window = Window("I'm Watching You Debugger", layout, icon=PSGDebugLogo, margins=(0, 0)).Finalize() + # ------------------------------- Create main window ------------------------------- + window = Window("PySimpleGUI Debugger", layout, icon=PSGDebugLogo, margins=(0, 0), location=location).Finalize() window.Element('_VAR1_').SetFocus() - Debugger.watcher_window = window - ChangeLookAndFeel('SystemDefault') + self.watcher_window = window + ChangeLookAndFeel('SystemDefault') # set look and feel to default before exiting return window # # ####### # @@ -8417,122 +8470,119 @@ class Debugger(object): # # # # # # # ####### ## ###### # # # ####### #### #### # def _refresh_main_debugger_window(self, mylocals, myglobals): - if not Debugger.watcher_window: + if not self.watcher_window: # if there is no window setup, nothing to do return False - event, values = Debugger.watcher_window.Read(timeout=1) + event, values = self.watcher_window.Read(timeout=1) if event in (None, 'Exit', '_EXIT_'): # EXIT BUTTON / X BUTTON try: - Debugger.watcher_window.Close() - except: - pass - Debugger.watcher_window = None + self.watcher_window.Close() + except: pass + self.watcher_window = None return False - - cmd_interactive = values['_INTERACTIVE_'] - cmd_code = values['_CODE_'] - cmd = cmd_interactive or cmd_code - + # ------------------------------- Process events from REPL Tab ------------------------------- + cmd = values['_REPL_'] # get the REPL entered + # BUTTON - GO (NOTE - This button is invisible!!) if event == 'Go': # GO BUTTON - Debugger.watcher_window.Element('_INTERACTIVE_').Update('') - Debugger.watcher_window.Element('_CODE_').Update('') - Debugger.watcher_window.Element('_OUTPUT_').Update(">>> {}\n".format(cmd), append=True, autoscroll=True) - if cmd_interactive: - expression = """ {} = {} """.format(fullname(Debugger.myrc), cmd) - try: - exec(expression, myglobals, mylocals) - Debugger.watcher_window.Element('_OUTPUT_').Update('{}\n'.format(Debugger.myrc), append=True, - autoscroll=True) - - except Exception as e: - Debugger.watcher_window.Element('_OUTPUT_').Update('Exception {}\n'.format(e), append=True, - autoscroll=True) - else: - Debugger.watcher_window.Element('_CODE_').Update('') - Debugger.watcher_window.Element('_OUTPUT_').Update(">>> {}\n".format(cmd), append=True, autoscroll=True) - expression = """{}""".format(cmd) - try: - exec(expression, myglobals, mylocals) - Debugger.watcher_window.Element('_OUTPUT_').Update('{}\n'.format(cmd), append=True, autoscroll=True) - - except Exception as e: - Debugger.watcher_window.Element('_OUTPUT_').Update('Exception {}\n'.format(e), append=True, - autoscroll=True) - + self.watcher_window.Element('_REPL_').Update('') + self.watcher_window.Element('_OUTPUT_').Update(">>> {}\n".format(cmd), append=True, autoscroll=True) + # REMOVED ONLY IN 2.7 version!!! HAND EDITED! + + # if sys.version_info[0] >= 3: + # try: + # result = eval('{}'.format(cmd), myglobals, mylocals) + # except Exception as e: + # try: + # result = exec('{}'.format(cmd), myglobals, mylocals) + # except Exception as e: + # result = 'Exception {}\n'.format(e) + # else: + result = '' + self.watcher_window.Element('_OUTPUT_').Update('{}\n'.format(result), append=True, autoscroll=True) + # BUTTON - DETAIL elif event.endswith('_DETAIL_'): # DETAIL BUTTON var = values['_VAR{}_'.format(event[4])] - expression = """ {} = {} """.format(fullname(Debugger.myrc), var) try: - exec(expression, myglobals, mylocals) - PopupScrolled(str(values['_VAR{}_'.format(event[4])]) + '\n' + str(Debugger.myrc), title=var, - non_blocking=True) + result = str(eval(str(var), myglobals, mylocals)) except: - pass + result = '' + PopupScrolled(str(values['_VAR{}_'.format(event[4])]) + '\n' + result, title=var, non_blocking=True) + # BUTTON - OBJ elif event.endswith('_OBJ_'): # OBJECT BUTTON var = values['_VAR{}_'.format(event[4])] - expression = """ {} = {} """.format(fullname(Debugger.myrc), cmd) try: - exec(expression, myglobals, mylocals) - PopupScrolled(ObjToStringSingleObj(Debugger.myrc), title=var, non_blocking=True) - except: - pass + result = ObjToStringSingleObj(mylocals[var]) + except Exception as e: + result = '{}\nError showing object {}'.format(e, var) + PopupScrolled(str(var) + '\n' + str(result), title=var, non_blocking=True) + # ------------------------------- Process Watch Tab ------------------------------- + # BUTTON - Choose Locals to see elif event == '_LOCALS_': # Show all locals BUTTON self._choose_auto_watches(mylocals) + # BUTTON - Locals (quick popup) elif event == '_ALL_LOCALS_': self._display_all_vars(mylocals) + # BUTTON - Globals (quick popup) elif event == '_GLOBALS_': self._display_all_vars(myglobals) + # BUTTON - clear all elif event == 'Clear All Auto Watches': if PopupYesNo('Do you really want to clear all Auto-Watches?', 'Really Clear??') == 'Yes': - Debugger.local_choices = {} - Debugger.custom_watch = '' - # Debugger.watcher_window.Element('_CUSTOM_WATCH_').Update('') + self.local_choices = {} + self.custom_watch = '' + # BUTTON - Popout elif event == '_POPOUT_': - if not Debugger.popout_window: + if not self.popout_window: self._build_floating_window() + # BUTTON - Show All elif event == '_SHOW_ALL_': - for key in Debugger.locals: - Debugger.local_choices[key] = True if not key.startswith('_') else False + for key in self.locals: + self.local_choices[key] = not key.startswith('_') # -------------------- Process the manual "watch list" ------------------ - for i in range(1, 4): + for i in range(3): key = '_VAR{}_'.format(i) out_key = '_VAR{}_CHANGED_'.format(i) - Debugger.myrc = '' - if Debugger.watcher_window.Element(key): - if values[key]: - Debugger.watcher_window.Element(out_key).Update(values[key]) - else: - Debugger.watcher_window.Element(out_key).Update('') + self.myrc = '' + if self.watcher_window.Element(key): + var = values[key] + try: + result = eval(str(var), myglobals, mylocals) + except: + result = '' + self.watcher_window.Element(out_key).Update(str(result)) + else: + self.watcher_window.Element(out_key).Update('') # -------------------- Process the automatic "watch list" ------------------ - slot = 1 - for key in Debugger.local_choices: - if Debugger.local_choices[key] is True: - Debugger.watcher_window.Element('_WATCH{}_'.format(slot)).Update(key) + slot = 0 + for key in self.local_choices: + if key == '_CUSTOM_WATCH_': + continue + if self.local_choices[key]: + self.watcher_window.Element('_WATCH{}_'.format(slot)).Update(key) try: - Debugger.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update(mylocals[key]) + self.watcher_window.Element('_WATCH{}_RESULT_'.format(slot), silent_on_error=True).Update(mylocals[key]) except: - pass - # Debugger.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update('') + self.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update('') slot += 1 - if slot + int(not Debugger.custom_watch in (None, '')) >= NUM_AUTO_WATCH: - break - - if Debugger.custom_watch: - Debugger.watcher_window.Element('_WATCH{}_'.format(slot)).Update(Debugger.custom_watch) + if slot + int(not self.custom_watch in (None, '')) >= NUM_AUTO_WATCH: + break + # If a custom watch was set, display that value in the window + if self.custom_watch: + self.watcher_window.Element('_WATCH{}_'.format(slot)).Update(self.custom_watch) try: - Debugger.myrc = eval(Debugger.custom_watch, myglobals, mylocals) + self.myrc = eval(self.custom_watch, myglobals, mylocals) except: - Debugger.myrc = '' - Debugger.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update(Debugger.myrc) + self.myrc = '' + self.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update(self.myrc) slot += 1 + # blank out all of the slots not used (blank) + for i in range(slot, NUM_AUTO_WATCH): + self.watcher_window.Element('_WATCH{}_'.format(i)).Update('') + self.watcher_window.Element('_WATCH{}_RESULT_'.format(i)).Update('') - for i in range(slot, NUM_AUTO_WATCH + 1): - Debugger.watcher_window.Element('_WATCH{}_'.format(i)).Update('') - Debugger.watcher_window.Element('_WATCH{}_RESULT_'.format(i)).Update('') - - return True + return True # return indicating the window stayed open ###### # # # # #### ##### # # ##### # # # # # # ##### #### # # @@ -8549,6 +8599,7 @@ class Debugger(object): # # # # # # ##### # ####### # # # # ###### ##### # # # # # # # # # # # # # # # # # # # # # # ###### #### # # # #### # # ###### ###### # # # # # #### + # displays them into a single text box def _display_all_vars(self, dict): num_cols = 3 @@ -8573,21 +8624,21 @@ class Debugger(object): cur_col += 1 ScrolledTextBox(out_text, non_blocking=True) - ##### # # - # # # # #### #### #### ###### # # # ## ##### #### # # - # # # # # # # # # # # # # # # # # # # - # ###### # # # # #### ##### # # # # # # # ###### - # # # # # # # # # # # # ###### # # # # - # # # # # # # # # # # # # # # # # # # # # - ##### # # #### #### #### ###### ## ## # # # #### # # + ##### # # +# # # # #### #### #### ###### # # # ## ##### #### # # +# # # # # # # # # # # # # # # # # # # +# ###### # # # # #### ##### # # # # # # # ###### +# # # # # # # # # # # # ###### # # # # +# # # # # # # # # # # # # # # # # # # # # + ##### # # #### #### #### ###### ## ## # # # #### # # - # # # # - # # ## ##### # ## ##### # ###### #### # # # # # # - # # # # # # # # # # # # # # # # # # ## # - # # # # # # # # # ##### # ##### #### # # # # # # # - # # ###### ##### # ###### # # # # # # # # # # # # - # # # # # # # # # # # # # # # # # # # # ## - # # # # # # # # ##### ###### ###### #### ## ## # # # +# # # # +# # ## ##### # ## ##### # ###### #### # # # # # # +# # # # # # # # # # # # # # # # # # ## # +# # # # # # # # # ##### # ##### #### # # # # # # # + # # ###### ##### # ###### # # # # # # # # # # # # + # # # # # # # # # # # # # # # # # # # # ## + # # # # # # # # ##### ###### ###### #### ## ## # # # def _choose_auto_watches(self, my_locals): ChangeLookAndFeel(COLOR_SCHEME) @@ -8603,7 +8654,7 @@ class Debugger(object): sorted_dict[key] = my_locals[key] for key in sorted_dict: line.append(CB(key, key=key, size=(longest_line, 1), - default=Debugger.local_choices[key] if key in Debugger.local_choices else False)) + default=self.local_choices[key] if key in self.local_choices else False)) if cur_col + 1 == num_cols: cur_col = 0 layout.append(line) @@ -8614,7 +8665,7 @@ class Debugger(object): layout.append(line) layout += [ - [Text('Custom Watch'), Input(default_text=Debugger.custom_watch, size=(60, 1), key='_CUSTOM_WATCH_')]] + [Text('Custom Watch (any expression)'), Input(default_text=self.custom_watch, size=(40, 1), key='_CUSTOM_WATCH_')]] layout += [ [Ok(), Cancel(), Button('Clear All'), Button('Select [almost] All', key='_AUTO_SELECT_')]] @@ -8625,12 +8676,12 @@ class Debugger(object): if event in (None, 'Cancel'): break elif event == 'Ok': - Debugger.local_choices = values - Debugger.custom_watch = values['_CUSTOM_WATCH_'] + self.local_choices = values + self.custom_watch = values['_CUSTOM_WATCH_'] break elif event == 'Clear All': PopupQuickMessage('Cleared Auto Watches', auto_close=True, auto_close_duration=3, non_blocking=True, - text_color='red', font='ANY 18') + text_color='red', font='ANY 18') for key in sorted_dict: window.Element(key).Update(False) window.Element('_CUSTOM_WATCH_').Update('') @@ -8659,11 +8710,11 @@ class Debugger(object): # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # ## # # # # ## ## - ## ## # # # ##### #### # # + ## ## # # # ##### #### # # - def _build_floating_window(self): - if Debugger.popout_window: - Debugger.popout_window.Close() + def _build_floating_window(self, location=(None, None)): + if self.popout_window: # if floating window already exists, close it first + self.popout_window.Close() ChangeLookAndFeel('Topanga') num_cols = 2 width_var = 15 @@ -8671,19 +8722,19 @@ class Debugger(object): layout = [] line = [] col = 0 - Debugger.popout_choices = Debugger.local_choices if Debugger.local_choices != {} else {} - if Debugger.popout_choices == {}: - for key in sorted(Debugger.locals.keys()): - if not key.startswith('_'): - Debugger.popout_choices[key] = True + self.popout_choices = self.local_choices + if self.popout_choices == {}: # if nothing chosen, then choose all non-_ variables + for key in sorted(self.locals.keys()): + self.popout_choices[key] = not key.startswith('_') - width_var = max([len(key) for key in Debugger.popout_choices]) - for key in Debugger.popout_choices: - if Debugger.popout_choices[key] is True: - value = str(Debugger.locals.get(key)) - line += [Text(key, size=(width_var, 1), font='Sans 8'), Text(' = ', font='Sans 8'), - Text(value, key=key, size=(width_value, 1 if len(value) < width_value else 2), - font='Sans 8')] + width_var = max([len(key) for key in self.popout_choices]) + for key in self.popout_choices: + if self.popout_choices[key] is True: + value = str(self.locals.get(key)) + h = min(len(value)//width_value + 1, MAX_LINES_PER_RESULT_FLOATING) + line += [Text('{}'.format(key), size=(width_var, 1), font=POPOUT_WINDOW_FONT), + Text(' = ', font=POPOUT_WINDOW_FONT), + Text(value, key=key, size=(width_value, h), font=POPOUT_WINDOW_FONT)] if col + 1 < num_cols: line += [VerticalSeparator(), T(' ')] col += 1 @@ -8696,13 +8747,16 @@ class Debugger(object): layout = [[Column(layout), Column( [[Button('', key='_EXIT_', image_data=red_x, button_color=('#282923', '#282923'), border_width=0)]])]] - Debugger.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, ).Finalize() - screen_size = Debugger.popout_window.GetScreenDimensions() - Debugger.popout_window.Move(screen_size[0] - Debugger.popout_window.Size[0], 0) - Debugger.popout_window.SetAlpha(1) + 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, + right_click_menu=['&Right', ['Debugger::RightClick', 'Exit::RightClick']], location=location ).Finalize() + if location == (None, None): + screen_size = self.popout_window.GetScreenDimensions() + self.popout_window.Move(screen_size[0] - self.popout_window.Size[0], 0) + self.popout_window.SetAlpha(1) ChangeLookAndFeel('SystemDefault') + return True ###### # # ###### ###### ##### ###### #### # # @@ -8726,18 +8780,21 @@ class Debugger(object): # # # # # # # # # # # # # # # # # # # # # # # # # ## # # # # # # ## # # # # ## ## - ## ## # # # ##### #### # # + ## ## # # # ##### #### # # - def _refresh_floating_window(): - if not Debugger.popout_window: + def _refresh_floating_window(self): + if not self.popout_window: return - for key in Debugger.popout_choices: - if Debugger.popout_choices[key] is True: - Debugger.popout_window.Element(key).Update(Debugger.locals.get(key)) - event, values = Debugger.popout_window.Read(timeout=1) - if event in (None, '_EXIT_'): - Debugger.popout_window.Close() - Debugger.popout_window = None + for key in self.popout_choices: + if self.popout_choices[key] is True and key in self.locals: + if key is not None: + self.popout_window.Element(key, silent_on_error=True).Update(self.locals.get(key)) + event, values = self.popout_window.Read(timeout=1) + if event in (None, '_EXIT_', 'Exit::RightClick'): + self.popout_window.Close() + self.popout_window = None + elif event == 'Debugger::RightClick': + show_debugger_window() # 888 888 .d8888b. d8888 888 888 888 888 @@ -8759,123 +8816,68 @@ class Debugger(object): # 888 "Y88888 888 888 "Y8888P "Y888 888 "Y88P" 888 888 88888P' -# The *args are needed because sometimes this is called by tkinter and it sends in some parms of something -# Due to the BIND that happens to the key -'' -def show_debugger_window(*args): +def show_debugger_window(location=(None, None), *args): + if Debugger.debugger is None: + Debugger.debugger = Debugger() + debugger = Debugger.debugger frame = inspect.currentframe() prev_frame = inspect.currentframe().f_back # frame, *others = inspect.stack()[1] try: - Debugger.locals = frame.f_back.f_locals - Debugger.globals = frame.f_back.f_globals + debugger.locals = frame.f_back.f_locals + debugger.globals = frame.f_back.f_globals finally: del frame - if not Debugger.watcher_window: - Debugger.watcher_window = debugger._build_main_debugger_window() + if not debugger.watcher_window: + debugger.watcher_window = debugger._build_main_debugger_window(location=location) return True -# -# -# def show_debugger_window(*args): -# frame, *others = inspect.stack()[1] -# try: -# Debugger.locals = frame.f_back.f_locals -# Debugger.globals = frame.f_back.f_globals -# finally: -# del frame -# -# if not Debugger.watcher_window: -# Debugger.watcher_window = debugger._build_main_debugger_window() -# return True -# - -def show_debugger_popout_window(*args): +def show_debugger_popout_window(location=(None, None), *args): + if Debugger.debugger is None: + Debugger.debugger = Debugger() + debugger = Debugger.debugger frame = inspect.currentframe() prev_frame = inspect.currentframe().f_back # frame = inspect.getframeinfo(prev_frame) # frame, *others = inspect.stack()[1] try: - Debugger.locals = frame.f_back.f_locals - Debugger.globals = frame.f_back.f_globals + debugger.locals = frame.f_back.f_locals + debugger.globals = frame.f_back.f_globals finally: del frame - if Debugger.popout_window: - return - debugger._build_floating_window() - - -# -# def show_debugger_popout_window(*args): -# -# frame = inspect.currentframe() -# prev_frame = inspect.currentframe().f_back -# frame = inspect.getframeinfo(prev_frame) -# # frame, *others = inspect.stack()[1] -# try: -# Debugger.locals = frame.f_back.f_locals -# Debugger.globals = frame.f_back.f_globals -# finally: -# del frame -# if Debugger.popout_window: -# return -# if not Debugger.popout_window: -# Debugger.popout_window = debugger._build_floating_window() + if debugger.popout_window: + debugger.popout_window.Close() + debugger.popout_window = None + debugger._build_floating_window(location=location) def refresh_debugger(): + if Debugger.debugger is None: + Debugger.debugger = Debugger() + debugger = Debugger.debugger Window.read_call_from_debugger = True frame = inspect.currentframe() frame = inspect.currentframe().f_back # frame, *others = inspect.stack()[1] try: - Debugger.locals = frame.f_back.f_locals - Debugger.globals = frame.f_back.f_globals + debugger.locals = frame.f_back.f_locals + debugger.globals = frame.f_back.f_globals finally: del frame - Debugger._refresh_floating_window() if Debugger.popout_window else None - rc = debugger._refresh_main_debugger_window(Debugger.locals, Debugger.globals) if Debugger.watcher_window else False + debugger._refresh_floating_window() if debugger.popout_window else None + rc = debugger._refresh_main_debugger_window(debugger.locals, debugger.globals) if debugger.watcher_window else False Window.read_call_from_debugger = False return rc -# -# def refresh_debugger(): -# Window.read_call_from_debugger = True -# frame = inspect.currentframe() -# prev_frame = inspect.currentframe().f_back -# # frame, *others = inspect.stack()[1] -# try: -# Debugger.locals = frame.f_back.f_locals -# Debugger.globals = frame.f_back.f_globals -# finally: -# del frame -# Debugger._refresh_floating_window() if Debugger.popout_window else None -# rc = debugger._refresh_main_debugger_window(Debugger.locals, Debugger.globals) if Debugger.watcher_window else False -# Window.read_call_from_debugger = False -# return rc -def fullname(o): - # o.__module__ + "." + o.__class__.__qualname__ is an example in - # this context of H.L. Mencken's "neat, plausible, and wrong." - # Python makes no guarantees as to whether the __module__ special - # attribute is defined, so we take a more circumspect approach. - # Alas, the module name is explicitly excluded from __qualname__ - # in Python 3. - - module = o.__class__.__module__ - if module is None or module == str.__class__.__module__: - return o.__class__.__name__ # Avoid reporting __builtin__ - else: - return module + '.' + o.__class__.__name__ -debugger = Debugger() """ d8b diff --git a/docs/index.md b/docs/index.md index 26582547..9cc7ea0e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,10 +2,16 @@ ![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) [![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) tkinter + [![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) tkinter 2.7 + [![Downloads](https://pepy.tech/badge/pysimpleguiqt)](https://pepy.tech/project/pysimpleguiqt) Qt + [![Downloads](https://pepy.tech/badge/pysimpleguiwx)](https://pepy.tech/project/pysimpleguiWx) WxPython + [![Downloads](https://pepy.tech/badge/pysimpleguiweb)](https://pepy.tech/project/pysimpleguiWeb) Web (Remi) + + ![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) @@ -14,7 +20,11 @@ # PySimpleGUI - +* Create windows that look and operate _identically_ to those created directly with tkinter, Qt, WxPython, and Remi. +* Requires 1/2 to 1/5th the amount of code as underlying frameworks. +* For exampl, develop a working Qt application in 1/2 to 1/5th the number lines of code. +* The savings can be even greater depending on your application. +* One afternoon is all that is required to learn the PySimpleGUI concepts and APIs. ## Supports both Python 2.7 & 3 when using tkinter @@ -25,9 +35,9 @@ ## The *only* way to write both desktop and web based GUIs at the same time -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.38.0-red.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.39.0-red.svg?longCache=true&style=for-the-badge) -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.37.0-blue.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.39.0-blue.svg?longCache=true&style=for-the-badge) ![Python Version](https://img.shields.io/badge/PySimpleGUIQt_Version-0.31.0-orange.svg?longCache=true&style=for-the-badge) @@ -5011,6 +5021,33 @@ A combination of user requests, and needs of new `imwatchingyou` debugger * Option to "launch built-in debugger" from the test harness * Rememeber that the Debugger is still in this code! It may or may not be operational as it's one version back from the latest release of the `imwatchingyou` debugger code. This code needs to be integrated back in +## 3.39 PySimpleGUI & 1.39 PySimpleGUI27 13-June-2019 + +* Ported the imwatchingyou debugger code into PySimpleGUI code + * Replaced old debugger built-in code with the newer imwatchingyou version + * Required removing all of the 'sg.' before PySimpleGUI calls since not importing + * Dynamically create the debugger object when first call to `refresh` or `show` is made +* Started the procecss of renaming Class Methods that are private to start with _ +* Needed for the automatic documentation generation that's being worked on +* Fixed crash when clicking the Debug button +* Fixed bug in DeleteFigure. Needed to delete image separately +* Added more type hints +* New `TabGroup` method `SelectTab(index)` selects a `Tab` within a `TabGroup` +* New `Table.Update` parameter - `select_rows`. List of rows to select (0 is first) +* Error checking in `Window.Layout` provides error "hints" to the user + * Looks for badly placed ']' + * Looks for functions missing '()' + * Pops up a window warning user instead of crashing + * May have to revisit if the popups start getting in the way +* New implementations of `Window.Disable()` and `Window.Enable()` + * Previously did not work correctly at all + * Now using the "-disabled" attribute +* Allow Comboboxes to have empty starting values + * Was crashing + * Enables application to fill these in later + + + ### Upcoming Make suggestions people! Future release features diff --git a/readme.md b/readme.md index 26582547..9cc7ea0e 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,16 @@ ![pysimplegui_logo](https://user-images.githubusercontent.com/13696193/43165867-fe02e3b2-8f62-11e8-9fd0-cc7c86b11772.png) [![Downloads](http://pepy.tech/badge/pysimplegui)](http://pepy.tech/project/pysimplegui) tkinter + [![Downloads ](https://pepy.tech/badge/pysimplegui27)](https://pepy.tech/project/pysimplegui27) tkinter 2.7 + [![Downloads](https://pepy.tech/badge/pysimpleguiqt)](https://pepy.tech/project/pysimpleguiqt) Qt + [![Downloads](https://pepy.tech/badge/pysimpleguiwx)](https://pepy.tech/project/pysimpleguiWx) WxPython + [![Downloads](https://pepy.tech/badge/pysimpleguiweb)](https://pepy.tech/project/pysimpleguiWeb) Web (Remi) + + ![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) ![Awesome Meter](https://img.shields.io/badge/Awesome_meter-100-yellow.svg) ![Python Version](https://img.shields.io/badge/Python-2.7_3.x-yellow.svg) @@ -14,7 +20,11 @@ # PySimpleGUI - +* Create windows that look and operate _identically_ to those created directly with tkinter, Qt, WxPython, and Remi. +* Requires 1/2 to 1/5th the amount of code as underlying frameworks. +* For exampl, develop a working Qt application in 1/2 to 1/5th the number lines of code. +* The savings can be even greater depending on your application. +* One afternoon is all that is required to learn the PySimpleGUI concepts and APIs. ## Supports both Python 2.7 & 3 when using tkinter @@ -25,9 +35,9 @@ ## The *only* way to write both desktop and web based GUIs at the same time -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.38.0-red.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_3.x_Version-3.39.0-red.svg?longCache=true&style=for-the-badge) -![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.37.0-blue.svg?longCache=true&style=for-the-badge) +![Python Version](https://img.shields.io/badge/PySimpleGUI_For_Python_2.7_Version-1.39.0-blue.svg?longCache=true&style=for-the-badge) ![Python Version](https://img.shields.io/badge/PySimpleGUIQt_Version-0.31.0-orange.svg?longCache=true&style=for-the-badge) @@ -5011,6 +5021,33 @@ A combination of user requests, and needs of new `imwatchingyou` debugger * Option to "launch built-in debugger" from the test harness * Rememeber that the Debugger is still in this code! It may or may not be operational as it's one version back from the latest release of the `imwatchingyou` debugger code. This code needs to be integrated back in +## 3.39 PySimpleGUI & 1.39 PySimpleGUI27 13-June-2019 + +* Ported the imwatchingyou debugger code into PySimpleGUI code + * Replaced old debugger built-in code with the newer imwatchingyou version + * Required removing all of the 'sg.' before PySimpleGUI calls since not importing + * Dynamically create the debugger object when first call to `refresh` or `show` is made +* Started the procecss of renaming Class Methods that are private to start with _ +* Needed for the automatic documentation generation that's being worked on +* Fixed crash when clicking the Debug button +* Fixed bug in DeleteFigure. Needed to delete image separately +* Added more type hints +* New `TabGroup` method `SelectTab(index)` selects a `Tab` within a `TabGroup` +* New `Table.Update` parameter - `select_rows`. List of rows to select (0 is first) +* Error checking in `Window.Layout` provides error "hints" to the user + * Looks for badly placed ']' + * Looks for functions missing '()' + * Pops up a window warning user instead of crashing + * May have to revisit if the popups start getting in the way +* New implementations of `Window.Disable()` and `Window.Enable()` + * Previously did not work correctly at all + * Now using the "-disabled" attribute +* Allow Comboboxes to have empty starting values + * Was crashing + * Enables application to fill these in later + + + ### Upcoming Make suggestions people! Future release features