From 1f9247e6ce5e084210d04a9816c60f6313a9d944 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 13:48:02 -0400 Subject: [PATCH 01/14] Keyboard capture! You can now have a form return the keystokes. This is great for page-up page-down, etc. Returned as a string in the button field.. Specified in the FlexForm call. return_keyboard_events is the boolean parameter. --- PySimpleGUI.py | 48 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 685075f7..2ceb7ac3 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -102,11 +102,11 @@ class MyWindows(): def Decrement(self): self.NumOpenWindows -= 1 * (self.NumOpenWindows != 0) # decrement if not 0 - print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows)) + # print('---- DECREMENTING Num Open Windows = {} ---'.format(self.NumOpenWindows)) def Increment(self): self.NumOpenWindows += 1 - print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows)) + # print('++++ INCREMENTING Num Open Windows = {} ++++'.format(self.NumOpenWindows)) _my_windows = MyWindows() # terrible hack using globals... means need a class for collecing windows @@ -862,7 +862,7 @@ class FlexForm: ''' Display a user defined for and return the filled in data ''' - def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON): + def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, return_keyboard_events=False): self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS self.Title = title @@ -896,6 +896,8 @@ class FlexForm: self.LastButtonClicked = None self.UseDictionary = False self.UseDefaultFocus = False + self.ReturnKeyboardEvents = return_keyboard_events + self.LastKeyboardEvent = None # ------------------------- Add ONE Row to Form ------------------------- # def AddRow(self, *args): @@ -993,11 +995,18 @@ class FlexForm: if not self.Shown: self.Show() else: + InitializeResults(self) self.TKroot.mainloop() if self.RootNeedsDestroying: self.TKroot.destroy() _my_windows.Decrement() - return BuildResults(self, False, self) + # if self.ReturnValues[0] is not None: # keyboard events build their own return values + # return self.ReturnValues + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + return BuildResults(self, False, self) + else: + return self.ReturnValues + def ReadNonBlocking(self, Message=''): if self.TKrootDestroyed: @@ -1013,6 +1022,19 @@ class FlexForm: _my_windows.Decrement() return BuildResults(self, False, self) + def KeyboardCallback(self, event ): + print(".",) + self.LastButtonClicked = None + self.FormRemainedOpen = True + if event.char != '': + self.LastKeyboardEvent = event.char + else: + self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) + # self.LastKeyboardEvent = event + if not self.NonBlocking: + results = BuildResults(self, False, self) + self.TKroot.quit() + def _Close(self): try: @@ -1125,7 +1147,7 @@ def FileSaveAs(target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),), button_ # ------------------------- SAVE AS Element lazy function ------------------------- # def SaveAs(target=(ThisRow, -1), file_types=(("ALL Files", "*.*"),), button_text='Save As...', scale=(None, None), size=(None, None), auto_size_button=None, button_color=None, font=None): - return Button(BUTTON_TYPE_BROWSE_FILE, target, button_text=button_text, file_types=file_types, scale=scale, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font) + return Button(BUTTON_TYPE_SAVEAS_FILE, target, button_text=button_text, file_types=file_types, scale=scale, size=size, auto_size_button=auto_size_button, button_color=button_color, font=font) # ------------------------- SAVE BUTTON Element lazy function ------------------------- # def Save(button_text='Save', scale=(None, None), size=(None, None), auto_size_button=None, button_color=None, bind_return_key=True,font=None, focus=False): @@ -1244,7 +1266,7 @@ def BuildResultsForSubform(form, initialize_only, top_level_form): if not initialize_only: if element.Type == ELEM_TYPE_INPUT_TEXT: value=element.TKStringVar.get() - if not top_level_form.NonBlocking and not element.do_not_clear: + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: element.TKStringVar.set('') elif element.Type == ELEM_TYPE_INPUT_CHECKBOX: value = element.TKIntVar.get() @@ -1279,7 +1301,7 @@ def BuildResultsForSubform(form, initialize_only, top_level_form): elif element.Type == ELEM_TYPE_INPUT_MULTILINE: try: value=element.TKText.get(1.0, tk.END) - if not top_level_form.NonBlocking and not element.do_not_clear: + if not top_level_form.NonBlocking and not element.do_not_clear and not top_level_form.ReturnKeyboardEvents: element.TKText.delete('1.0', tk.END) except: value = None @@ -1292,6 +1314,10 @@ def BuildResultsForSubform(form, initialize_only, top_level_form): AddToReturnList(form, value) AddToReturnDictionary(top_level_form, element, value) + if form.ReturnKeyboardEvents and form.LastKeyboardEvent is not None: + button_pressed_text = form.LastKeyboardEvent + form.LastKeyboardEvent = None + try: form.ReturnValuesDictionary.pop(None, None) # clean up dictionary include None was included except: pass @@ -1765,6 +1791,8 @@ def StartupTK(my_flex_form): # root.bind('', MyFlexForm.DestroyedCallback()) ConvertFlexToTK(my_flex_form) my_flex_form.SetIcon(my_flex_form.WindowIcon) + if my_flex_form.ReturnKeyboardEvents: + root.bind("", my_flex_form.KeyboardCallback) if my_flex_form.AutoClose: duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration @@ -2571,11 +2599,15 @@ def ChangeLookAndFeel(index): sprint=ScrolledTextBox # Converts an object's contents into a nice printable string. Great for dumping debug data -def ObjToString_old(obj): +def ObjToStringSingleObj(obj): + if obj is None: + return 'None' return str(obj.__class__) + '\n' + '\n'.join( (repr(item) + ' = ' + repr(obj.__dict__[item]) for item in sorted(obj.__dict__))) def ObjToString(obj, extra=' '): + if obj is None: + return 'None' return str(obj.__class__) + '\n' + '\n'.join( (extra + (str(item) + ' = ' + (ObjToString(obj.__dict__[item], extra + ' ') if hasattr(obj.__dict__[item], '__dict__') else str( From aa2d31f24bc17ec68c5f58f075d76cdea0186103 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 15:27:12 -0400 Subject: [PATCH 02/14] Added non-blocking form keyboard binding If the form is a non-blocking form, when a key is pressed, the form will continuously return that key as being pressed until it is released. --- PySimpleGUI.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 2ceb7ac3..fc07064a 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1791,8 +1791,10 @@ def StartupTK(my_flex_form): # root.bind('', MyFlexForm.DestroyedCallback()) ConvertFlexToTK(my_flex_form) my_flex_form.SetIcon(my_flex_form.WindowIcon) - if my_flex_form.ReturnKeyboardEvents: + if my_flex_form.ReturnKeyboardEvents and not my_flex_form.NonBlocking: root.bind("", my_flex_form.KeyboardCallback) + elif my_flex_form.ReturnKeyboardEvents: + root.bind("", my_flex_form.KeyboardCallback) if my_flex_form.AutoClose: duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration From 51ea64ce07fbe552e63370f0ab324d99550a9ef1 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 15:51:29 -0400 Subject: [PATCH 03/14] Removed print --- PySimpleGUI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index fc07064a..dae19930 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1023,7 +1023,7 @@ class FlexForm: return BuildResults(self, False, self) def KeyboardCallback(self, event ): - print(".",) + # print(".",) self.LastButtonClicked = None self.FormRemainedOpen = True if event.char != '': From 9b190f5cee6dff31eb7682b297eaf25440ec3deb Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 18:43:33 -0400 Subject: [PATCH 04/14] Added page-up / page-down --- Demo_PDF_Viewer.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Demo_PDF_Viewer.py diff --git a/Demo_PDF_Viewer.py b/Demo_PDF_Viewer.py new file mode 100644 index 00000000..1e933ef8 --- /dev/null +++ b/Demo_PDF_Viewer.py @@ -0,0 +1,92 @@ +import sys +import fitz +import PySimpleGUI as sg + +try: + fname = sys.argv[1] +except: + fname = 'C:/Python/PycharmProjects/GooeyGUI/test.pdf' +doc = fitz.open(fname) +title = "PyMuPDF display of '%s' (%i pages)" % (fname, len(doc)) + +def get_page(pno, zoom = 0): + page = doc[pno] + r = page.rect + mp = r.tl + (r.br - r.tl) * 0.5 + mt = r.tl + (r.tr - r.tl) * 0.5 + ml = r.tl + (r.bl - r.tl) * 0.5 + mr = r.tr + (r.br - r.tr) * 0.5 + mb = r.bl + (r.br - r.bl) * 0.5 + mat = fitz.Matrix(2, 2) + if zoom == 1: + clip = fitz.Rect(r.tl, mp) + elif zoom == 4: + clip = fitz.Rect(mp, r.br) + elif zoom == 2: + clip = fitz.Rect(mt, mr) + elif zoom == 3: + clip = fitz.Rect(ml, mb) + if zoom == 0: + pix = page.getPixmap(alpha = False) + else: + pix = page.getPixmap(alpha = False, matrix = mat, clip = clip) + return pix.getPNGData() + +form = sg.FlexForm(title, return_keyboard_events=True) + +data = get_page(0) +image_elem = sg.Image(data=data) +layout = [ [image_elem], + [sg.ReadFormButton('Next'), + sg.ReadFormButton('Prev'), + sg.ReadFormButton('First'), + sg.ReadFormButton('Last'), + sg.ReadFormButton('Zoom-1'), + sg.ReadFormButton('Zoom-2'), + sg.ReadFormButton('Zoom-3'), + sg.ReadFormButton('Zoom-4'), + sg.Quit()] ] + +form.Layout(layout) + +i = 0 +oldzoom = 0 +while True: + button,value = form.Read() + zoom = 0 + if button in (None, 'Quit'): + break + if button in ("Next", 'Next:34'): + i += 1 + elif button in ("Prev", "Prior:33"): + i -= 1 + elif button == "First": + i = 0 + elif button == "Last": + i = -1 + elif button == "Zoom-1": + if oldzoom == 1: + zoom = oldzoom = 0 + else: + zoom = oldzoom = 1 + elif button == "Zoom-2": + if oldzoom == 2: + zoom = oldzoom = 0 + else: + zoom = oldzoom = 2 + elif button == "Zoom-3": + if oldzoom == 3: + zoom = oldzoom = 0 + else: + zoom = oldzoom = 3 + elif button == "Zoom-4": + if oldzoom == 4: + zoom = oldzoom = 0 + else: + zoom = oldzoom = 4 + try: + data = get_page(i, zoom) + except: + i = 0 + data = get_page(i, zoom) + image_elem.Update(data=data) From 88bdf72d8afa9f8ce2e0bba9282fb9c4cb1b791f Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 18:44:33 -0400 Subject: [PATCH 05/14] Removed a commment --- PySimpleGUI.py | 1 - 1 file changed, 1 deletion(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index dae19930..18edc84c 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1030,7 +1030,6 @@ class FlexForm: self.LastKeyboardEvent = event.char else: self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) - # self.LastKeyboardEvent = event if not self.NonBlocking: results = BuildResults(self, False, self) self.TKroot.quit() From e0deebea9ea513415428615b8a390c4734068cfc Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 19:00:02 -0400 Subject: [PATCH 06/14] Initial checkin of Keyboard Demos --- Demo_Keyboard.py | 26 ++++++++++++++++++++++++++ Demo_Keyboard_Realtime.py | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Demo_Keyboard.py create mode 100644 Demo_Keyboard_Realtime.py diff --git a/Demo_Keyboard.py b/Demo_Keyboard.py new file mode 100644 index 00000000..a6bbda99 --- /dev/null +++ b/Demo_Keyboard.py @@ -0,0 +1,26 @@ +import sys +import PySimpleGUI as sg + +# Recipe for getting keys, one at a time as they are released +# If want to use the space bar, then be sure and disable the "default focus" + +with sg.FlexForm('Realtime Keyboard Test', return_keyboard_events=True, use_default_focus=False) as form: + text_elem = sg.Text('', size=(12,1)) + layout = [[sg.Text('Press a key')], + [text_elem], + [sg.SimpleButton('OK')]] + + form.Layout(layout) + # ---===--- Loop taking in user input --- # + while True: + button, value = form.Read() + + if button == 'OK': + print(button, 'exiting') + break + if button is not None: + text_elem.Update(button) + elif value is None: + break + + diff --git a/Demo_Keyboard_Realtime.py b/Demo_Keyboard_Realtime.py new file mode 100644 index 00000000..bb1d145e --- /dev/null +++ b/Demo_Keyboard_Realtime.py @@ -0,0 +1,23 @@ +import PySimpleGUI as sg + +# Recipe for getting a continuous stream of keys when using a non-blocking form +# If want to use the space bar, then be sure and disable the "default focus" + +with sg.FlexForm('Realtime Keyboard Test', return_keyboard_events=True, use_default_focus=False) as form: + layout = [[sg.Text('Hold down a key')], + [sg.SimpleButton('OK')]] + + form.Layout(layout) + # ---===--- Loop taking in user input --- # + while True: + button, value = form.ReadNonBlocking() + + if button == 'OK': + print(button, value, 'exiting') + break + if button is not None: + print(button) + elif value is None: + break + + From d3d154b8708e27f1e9f1a22f1d301eabdf1e33a3 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 19:09:05 -0400 Subject: [PATCH 07/14] Cleaned up code --- Demo_PDF_Viewer.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/Demo_PDF_Viewer.py b/Demo_PDF_Viewer.py index 1e933ef8..85825bcc 100644 --- a/Demo_PDF_Viewer.py +++ b/Demo_PDF_Viewer.py @@ -49,8 +49,7 @@ layout = [ [image_elem], form.Layout(layout) -i = 0 -oldzoom = 0 +i = oldzoom = 0 while True: button,value = form.Read() zoom = 0 @@ -65,25 +64,13 @@ while True: elif button == "Last": i = -1 elif button == "Zoom-1": - if oldzoom == 1: - zoom = oldzoom = 0 - else: - zoom = oldzoom = 1 + zoom = oldzoom = 0 if oldzoom == 1 else 1 elif button == "Zoom-2": - if oldzoom == 2: - zoom = oldzoom = 0 - else: - zoom = oldzoom = 2 + zoom = oldzoom = 0 if oldzoom == 2 else 2 elif button == "Zoom-3": - if oldzoom == 3: - zoom = oldzoom = 0 - else: - zoom = oldzoom = 3 + zoom = oldzoom = 0 if oldzoom == 3 else 3 elif button == "Zoom-4": - if oldzoom == 4: - zoom = oldzoom = 0 - else: - zoom = oldzoom = 4 + zoom = oldzoom = 0 if oldzoom == 4 else 4 try: data = get_page(i, zoom) except: From 4667a2f3ffbc9f801f4767bd468d941266587017 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 20:47:24 -0400 Subject: [PATCH 08/14] New use_default_focus option for forms. --- Demo_Keyboard.py | 2 +- PySimpleGUI.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Demo_Keyboard.py b/Demo_Keyboard.py index a6bbda99..85f63b8a 100644 --- a/Demo_Keyboard.py +++ b/Demo_Keyboard.py @@ -20,7 +20,7 @@ with sg.FlexForm('Realtime Keyboard Test', return_keyboard_events=True, use_defa break if button is not None: text_elem.Update(button) - elif value is None: + else: break diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 18edc84c..eed9167c 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -862,7 +862,7 @@ class FlexForm: ''' Display a user defined for and return the filled in data ''' - def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, return_keyboard_events=False): + def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, return_keyboard_events=False, use_default_focus=True): self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS self.Title = title @@ -895,7 +895,7 @@ class FlexForm: self.DictionaryKeyCounter = 0 self.LastButtonClicked = None self.UseDictionary = False - self.UseDefaultFocus = False + self.UseDefaultFocus = use_default_focus self.ReturnKeyboardEvents = return_keyboard_events self.LastKeyboardEvent = None @@ -954,8 +954,10 @@ class FlexForm: except: pass - if not found_focus: + if not found_focus and self.UseDefaultFocus: self.UseDefaultFocus = True + else: + self.UseDefaultFocus = False # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## StartupTK(self) return self.ReturnValues From c482dee57e958231aa8492bba08071e5e91171d1 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 21:59:00 -0400 Subject: [PATCH 09/14] Update method for InputText element --- PySimpleGUI.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index eed9167c..751ef6de 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -236,6 +236,8 @@ class InputText(Element): super().__init__(ELEM_TYPE_INPUT_TEXT, scale=scale, size=size, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key) + def Update(self, new_value): + self.TKStringVar.set(new_value) def __del__(self): super().__del__() From 240a0a71e40d8fcb6d9a92157202d4d27c85bd3f Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Mon, 20 Aug 2018 23:45:09 -0400 Subject: [PATCH 10/14] Mouse scroll wheel! New PDF viewer demo --- Demo_PDF_Viewer.py | 220 +++++++++++++++++++++++++++++++++------------ PySimpleGUI.py | 13 ++- 2 files changed, 174 insertions(+), 59 deletions(-) diff --git a/Demo_PDF_Viewer.py b/Demo_PDF_Viewer.py index 85825bcc..ecd60c9c 100644 --- a/Demo_PDF_Viewer.py +++ b/Demo_PDF_Viewer.py @@ -1,79 +1,183 @@ +""" +@created: 2018-08-19 18:00:00 + +@author: (c) 2018 Jorj X. McKie + +Display a PyMuPDF Document using Tkinter +------------------------------------------------------------------------------- + +Dependencies: +------------- +PyMuPDF, PySimpleGUI > v2.9.0, Tkinter with Tk v8.6+, Python 3 + + +License: +-------- +GNU GPL V3+ + +Description +------------ +Read filename from command line and start display with page 1. +Pages can be directly jumped to, or buttons for paging can be used. +For experimental / demonstration purposes, we have included options to zoom +into the four page quadrants (top-left, bottom-right, etc.). + +We also interpret keyboard events to support paging by PageDown / PageUp +keys as if the resp. buttons were clicked. Similarly, we do not include +a 'Quit' button. Instead, the ESCAPE key can be used, or cancelling the form. + +To improve paging performance, we are not directly creating pixmaps from +pages, but instead from the fitz.DisplayList of the page. A display list +will be stored in a list and looked up by page number. This way, zooming +pixmaps and page re-visits will re-use a once-created display list. + +""" import sys import fitz import PySimpleGUI as sg +from binascii import hexlify -try: +if len(sys.argv) == 1: + rc, fname = sg.GetFileBox('PDF Browser', 'PDF file to open', file_types=(("PDF Files", "*.pdf"),)) + if rc is False: + sg.MsgBoxCancel('Cancelling') + exit(0) +else: fname = sys.argv[1] -except: - fname = 'C:/Python/PycharmProjects/GooeyGUI/test.pdf' + doc = fitz.open(fname) -title = "PyMuPDF display of '%s' (%i pages)" % (fname, len(doc)) +page_count = len(doc) -def get_page(pno, zoom = 0): - page = doc[pno] - r = page.rect - mp = r.tl + (r.br - r.tl) * 0.5 - mt = r.tl + (r.tr - r.tl) * 0.5 - ml = r.tl + (r.bl - r.tl) * 0.5 - mr = r.tr + (r.br - r.tr) * 0.5 - mb = r.bl + (r.br - r.bl) * 0.5 - mat = fitz.Matrix(2, 2) - if zoom == 1: +# storage for page display lists +dlist_tab = [None] * page_count + +title = "PyMuPDF display of '%s', pages: %i" % (fname, page_count) + + +def get_page(pno, zoom=0): + """Return a PNG image for a document page number. If zoom is other than 0, one of the 4 page quadrants are zoomed-in instead and the corresponding clip returned. + + """ + dlist = dlist_tab[pno] # get display list + if not dlist: # create if not yet there + dlist_tab[pno] = doc[pno].getDisplayList() + dlist = dlist_tab[pno] + r = dlist.rect # page rectangle + mp = r.tl + (r.br - r.tl) * 0.5 # rect middle point + mt = r.tl + (r.tr - r.tl) * 0.5 # middle of top edge + ml = r.tl + (r.bl - r.tl) * 0.5 # middle of left edge + mr = r.tr + (r.br - r.tr) * 0.5 # middle of right egde + mb = r.bl + (r.br - r.bl) * 0.5 # middle of bottom edge + mat = fitz.Matrix(2, 2) # zoom matrix + if zoom == 1: # top-left quadrant clip = fitz.Rect(r.tl, mp) - elif zoom == 4: + elif zoom == 4: # bot-right quadrant clip = fitz.Rect(mp, r.br) - elif zoom == 2: + elif zoom == 2: # top-right clip = fitz.Rect(mt, mr) - elif zoom == 3: + elif zoom == 3: # bot-left clip = fitz.Rect(ml, mb) - if zoom == 0: - pix = page.getPixmap(alpha = False) + if zoom == 0: # total page + pix = dlist.getPixmap(alpha=False) else: - pix = page.getPixmap(alpha = False, matrix = mat, clip = clip) - return pix.getPNGData() + pix = dlist.getPixmap(alpha=False, matrix=mat, clip=clip) + return pix.getPNGData() # return the PNG image -form = sg.FlexForm(title, return_keyboard_events=True) -data = get_page(0) +form = sg.FlexForm(title, return_keyboard_events=True, use_default_focus=False) + +cur_page = 0 +data = get_page(cur_page) # show page 1 for start image_elem = sg.Image(data=data) -layout = [ [image_elem], - [sg.ReadFormButton('Next'), - sg.ReadFormButton('Prev'), - sg.ReadFormButton('First'), - sg.ReadFormButton('Last'), - sg.ReadFormButton('Zoom-1'), - sg.ReadFormButton('Zoom-2'), - sg.ReadFormButton('Zoom-3'), - sg.ReadFormButton('Zoom-4'), - sg.Quit()] ] +goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True) + +layout = [ + [ + sg.ReadFormButton('Next'), + sg.ReadFormButton('Prev'), + sg.Text('Page:'), + goto, + ], + [ + sg.Text("Zoom:"), + sg.ReadFormButton('Top-L'), + sg.ReadFormButton('Top-R'), + sg.ReadFormButton('Bot-L'), + sg.ReadFormButton('Bot-R'), + ], + [image_elem], +] form.Layout(layout) +my_keys = ("Next", "Next:34", "Prev", "Prior:33", "Top-L", "Top-R", + "Bot-L", "Bot-R", "MouseWheel:Down", "MouseWheel:Up") +zoom_buttons = ("Top-L", "Top-R", "Bot-L", "Bot-R") + +old_page = 0 +old_zoom = 0 # used for zoom on/off +# the zoom buttons work in on/off mode. -i = oldzoom = 0 while True: - button,value = form.Read() + button, value = form.ReadNonBlocking() zoom = 0 - if button in (None, 'Quit'): + force_page = False + if button is None and value is None: break - if button in ("Next", 'Next:34'): - i += 1 - elif button in ("Prev", "Prior:33"): - i -= 1 - elif button == "First": - i = 0 - elif button == "Last": - i = -1 - elif button == "Zoom-1": - zoom = oldzoom = 0 if oldzoom == 1 else 1 - elif button == "Zoom-2": - zoom = oldzoom = 0 if oldzoom == 2 else 2 - elif button == "Zoom-3": - zoom = oldzoom = 0 if oldzoom == 3 else 3 - elif button == "Zoom-4": - zoom = oldzoom = 0 if oldzoom == 4 else 4 - try: - data = get_page(i, zoom) - except: - i = 0 - data = get_page(i, zoom) - image_elem.Update(data=data) + if button is None: + continue + + if button in ("Escape:27"): # this spares me a 'Quit' button! + break + # print("hex(button)", hexlify(button.encode())) + if button[0] == chr(13): # surprise: this is 'Enter'! + try: + cur_page = int(value[0]) - 1 # check if valid + while cur_page < 0: + cur_page += page_count + except: + cur_page = 0 # this guy's trying to fool me + goto.Update(str(cur_page + 1)) + # goto.TKStringVar.set(str(cur_page + 1)) + + elif button in ("Next", "Next:34", "MouseWheel:Down"): + cur_page += 1 + elif button in ("Prev", "Prior:33", "MouseWheel:Up"): + cur_page -= 1 + elif button == "Top-L": + zoom = 1 + elif button == "Top-R": + zoom = 2 + elif button == "Bot-L": + zoom = 3 + elif button == "Bot-R": + zoom = 4 + + # sanitize page number + if cur_page >= page_count: # wrap around + cur_page = 0 + while cur_page < 0: # we show conventional page numbers + cur_page += page_count + + # prevent creating same data again + if cur_page != old_page: + zoom = old_zoom = 0 + force_page = True + + if button in zoom_buttons: + if 0 < zoom == old_zoom: + zoom = 0 + force_page = True + + if zoom != old_zoom: + force_page = True + + if force_page: + data = get_page(cur_page, zoom) + image_elem.Update(data=data) + old_page = cur_page + old_zoom = zoom + + # update page number field + if button in my_keys or not value[0]: + goto.Update(str(cur_page + 1)) + # goto.TKStringVar.set(str(cur_page + 1)) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 751ef6de..c69c74d1 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1027,7 +1027,6 @@ class FlexForm: return BuildResults(self, False, self) def KeyboardCallback(self, event ): - # print(".",) self.LastButtonClicked = None self.FormRemainedOpen = True if event.char != '': @@ -1038,6 +1037,16 @@ class FlexForm: results = BuildResults(self, False, self) self.TKroot.quit() + def MouseWheelCallback(self, event ): + self.LastButtonClicked = None + self.FormRemainedOpen = True + # print(ObjToStringSingleObj(event)) + direction = 'Down' if event.delta < 0 else 'Up' + self.LastKeyboardEvent = 'MouseWheel:' + direction + if not self.NonBlocking: + results = BuildResults(self, False, self) + self.TKroot.quit() + def _Close(self): try: @@ -1796,8 +1805,10 @@ def StartupTK(my_flex_form): my_flex_form.SetIcon(my_flex_form.WindowIcon) if my_flex_form.ReturnKeyboardEvents and not my_flex_form.NonBlocking: root.bind("", my_flex_form.KeyboardCallback) + root.bind("", my_flex_form.MouseWheelCallback) elif my_flex_form.ReturnKeyboardEvents: root.bind("", my_flex_form.KeyboardCallback) + root.bind("", my_flex_form.MouseWheelCallback) if my_flex_form.AutoClose: duration = DEFAULT_AUTOCLOSE_TIME if my_flex_form.AutoCloseDuration is None else my_flex_form.AutoCloseDuration From 23123c532062400683784c80cb9295a6f6e62d73 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Tue, 21 Aug 2018 10:33:42 -0400 Subject: [PATCH 11/14] Fix for missing results on persistent form --- PySimpleGUI.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index c69c74d1..c26ae030 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -962,6 +962,9 @@ class FlexForm: self.UseDefaultFocus = False # -=-=-=-=-=-=-=-=- RUN the GUI -=-=-=-=-=-=-=-=- ## StartupTK(self) + # If a button or keyboard event happened but no results have been built, build the results + if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None: + return BuildResults(self, False, self) return self.ReturnValues # ------------------------- SetIcon - set the window's fav icon ------------------------- # From 1d61773df611124e26f3ae1cc5d2143799a2b619 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Tue, 21 Aug 2018 13:10:05 -0400 Subject: [PATCH 12/14] Option added to Image.UIpdate to create a new PhotoImage --- PySimpleGUI.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index c26ae030..fc1c59da 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -769,7 +769,10 @@ class Image(Element): if filename is not None: image = tk.PhotoImage(file=filename) elif data is not None: - image = tk.PhotoImage(data=data) + if type(data) is bytes: + image = tk.PhotoImage(data=data) + else: + image = data else: return self.tktext_label.configure(image=image) self.tktext_label.image = image From a4461313aef6fbc7b63a397902a9d7860f360151 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Tue, 21 Aug 2018 18:29:32 -0400 Subject: [PATCH 13/14] Added text justification setting to FlexForm --- PySimpleGUI.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/PySimpleGUI.py b/PySimpleGUI.py index fc1c59da..8cbed783 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -452,7 +452,7 @@ class Text(Element): ''' self.DisplayText = text self.TextColor = text_color if text_color else DEFAULT_TEXT_COLOR - self.Justification = justification if justification else DEFAULT_TEXT_JUSTIFICATION + self.Justification = justification if background_color is None: bg = DEFAULT_TEXT_ELEMENT_BACKGROUND_COLOR else: @@ -867,7 +867,7 @@ class FlexForm: ''' Display a user defined for and return the filled in data ''' - def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, return_keyboard_events=False, use_default_focus=True): + def __init__(self, title, default_element_size=(DEFAULT_ELEMENT_SIZE[0], DEFAULT_ELEMENT_SIZE[1]), auto_size_text=None, auto_size_buttons=None, scale=(None, None), location=(None, None), button_color=None, font=None, progress_bar_color=(None, None), background_color=None, is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, icon=DEFAULT_WINDOW_ICON, return_keyboard_events=False, use_default_focus=True, text_justification=None): self.AutoSizeText = auto_size_text if auto_size_text is not None else DEFAULT_AUTOSIZE_TEXT self.AutoSizeButtons = auto_size_buttons if auto_size_buttons is not None else DEFAULT_AUTOSIZE_BUTTONS self.Title = title @@ -903,6 +903,7 @@ class FlexForm: self.UseDefaultFocus = use_default_focus self.ReturnKeyboardEvents = return_keyboard_events self.LastKeyboardEvent = None + self.TextJustification = text_justification # ------------------------- Add ONE Row to Form ------------------------- # def AddRow(self, *args): @@ -1040,17 +1041,15 @@ class FlexForm: else: self.LastKeyboardEvent = str(event.keysym) + ':' + str(event.keycode) if not self.NonBlocking: - results = BuildResults(self, False, self) + BuildResults(self, False, self) self.TKroot.quit() def MouseWheelCallback(self, event ): self.LastButtonClicked = None self.FormRemainedOpen = True - # print(ObjToStringSingleObj(event)) - direction = 'Down' if event.delta < 0 else 'Up' - self.LastKeyboardEvent = 'MouseWheel:' + direction + self.LastKeyboardEvent = 'MouseWheel:' + 'Down' if event.delta < 0 else 'Up' if not self.NonBlocking: - results = BuildResults(self, False, self) + BuildResults(self, False, self) self.TKroot.quit() @@ -1059,7 +1058,7 @@ class FlexForm: self.TKroot.update() except: pass if not self.NonBlocking: - results = BuildResults(self, False, self) + BuildResults(self, False, self) if self.TKrootDestroyed: return None self.TKrootDestroyed = True @@ -1424,8 +1423,14 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): stringvar.set(display_text) if auto_size_text: width = 0 - justify = tk.LEFT if element.Justification == 'left' else tk.CENTER if element.Justification == 'center' else tk.RIGHT - anchor = tk.NW if element.Justification == 'left' else tk.N if element.Justification == 'center' else tk.NE + if element.Justification is not None: + justification = element.Justification + elif toplevel_form.TextJustification is not None: + justification = toplevel_form.TextJustification + else: + justification = DEFAULT_TEXT_JUSTIFICATION + justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT + anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE tktext_label = tk.Label(tk_row_frame, textvariable=stringvar, width=width, height=height, justify=justify, bd=border_depth) # tktext_label = tk.Label(tk_row_frame,anchor=tk.NW, text=display_text, width=width, height=height, justify=tk.LEFT, bd=border_depth) # Set wrap-length for text (in PIXELS) == PAIN IN THE ASS From 5f2740055e560ab83a0140ff516dcee3f0ec0f40 Mon Sep 17 00:00:00 2001 From: MikeTheWatchGuy Date: Tue, 21 Aug 2018 18:56:10 -0400 Subject: [PATCH 14/14] Updated readme with new FlexForm options --- docs/index.md | 61 ++++++++++++++++++++++++++++++++++++++++++++------- readme.md | 61 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 106 insertions(+), 16 deletions(-) diff --git a/docs/index.md b/docs/index.md index 392287c2..2efe1958 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,16 +6,28 @@ ![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) +[![Python Version](https://img.shields.io/badge/Python-3-brightgreen.svg)](https://www.python.org/downloads/) + # PySimpleGUI - (Ver 2.8) + (Ver 2.9) +Lots of documentation available in addition to this Readme File. [Formatted ReadTheDocs Version of this Readme](http://pysimplegui.readthedocs.io/) [COOKBOOK documentation now online!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) +[Brief Tutorial on PySimpleGUI](https://pysimplegui.readthedocs.io/en/latest/tutorial/) + +[See Wiki for latest news about development branch + new features](https://github.com/MikeTheWatchGuy/PySimpleGUI/wiki) + + Super-simple GUI to grasp... Powerfully customizable. +Create a custom GUI in 5 lines of code. + +Can create a custom GUI in 1 line of code if desired. + Note - ***Python3*** is required to run PySimpleGUI. It takes advantage of some Python3 features that do not translate well into Python2. Looking to take your Python code from the world of command lines and into the convenience of a GUI? Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? Into Machine Learning and are sick of the command line? Look no further, **you've found your GUI package**. @@ -27,6 +39,14 @@ Looking to take your Python code from the world of command lines and into the co ![snap0136](https://user-images.githubusercontent.com/13696193/43162494-33095ece-8f59-11e8-86de-b6d8bcc5a52f.jpg) +Or how about a ***custom GUI*** in 1 line of code? + + import PySimpleGUI as sg + + button, (filename,) = sg.FlexForm('Get filename example'). LayoutAndRead([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]) + +![simple](https://user-images.githubusercontent.com/13696193/44279378-2f891900-a21f-11e8-89d1-52d935a4f5f5.jpg) + Build beautiful customized forms that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve the real problems. Do you really want to plod through the mountains of code required to program tkinter? PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a **much** more friendly way. @@ -35,7 +55,8 @@ PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkin Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The is the same form as above, except shown on a Pi. -![raspberry pi](https://user-images.githubusercontent.com/13696193/43298356-9cfe9008-9123-11e8-9612-14649a2f6c7f.jpg) +![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) + In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this into any of your `for` loops and get a nice meter like this: @@ -48,6 +69,7 @@ You can build an async media player GUI with custom buttons in 30 lines of code. ![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) + ## Background I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? There are a number of 'easy to use' Python GUIs, but they're **very** limiting. PySimpleGUI takes the best of packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited. The primary difference between these and `PySimpleGUI` is that in addition to getting the simple Message Boxes you also get the ability to **make your own forms** that are highly customizeable. Don't like the standard Message Box? Then make your own! @@ -60,6 +82,8 @@ With a simple GUI, it becomes practical to "associate" .py files with the python The `PySimpleGUI` package is focused on the ***developer***. How can the desired result be achieved in as little and as simple code as possible? This was the mantra used to create PySimpleGUI. How can it be done is a Python-like way? +## Features + Features of PySimpleGUI include: Text Single Line Input @@ -89,6 +113,7 @@ The `PySimpleGUI` package is focused on the ***developer***. How can the desire Return values as dictionary Set focus Bind return key to buttons + Group widgets into a column and place into form anywhere An example of many widgets used on a single form. A little further down you'll find the TWENTY lines of code required to create this complex form. Try it if you don't believe it. Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... @@ -143,6 +168,7 @@ You will see a number of different styles of buttons, data entry fields, etc, in - A row is a list of elements - Return values are a list of button presses and input values. - Return values can also be represented as a dictionary +- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values ----- @@ -733,11 +759,19 @@ This is the definition of the FlexForm object: location=(None, None), button_color=None,Font=None, progress_bar_color=(None,None), + background_color=None is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - icon=DEFAULT_WINDOW_ICON): + icon=DEFAULT_WINDOW_ICON, + return_keyboard_events=False, + use_default_focus=True, + text_justification=None): + + + + Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `Form` values. @@ -748,11 +782,15 @@ Parameter Descriptions. You will find these same parameters specified for each location - (x,y) Location to place window in pixels button_color - Default color for buttons (foreground, background). Can be text or hex progress_bar_color - Foreground and background colors for progress bars + background_color - Color of the window background is_tabbed_form - Bool. If True then form is a tabbed form border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. auto_close - Bool. If True form will autoclose auto_close_duration - Duration in seconds before form closes icon - .ICO file that will appear on the Task Bar and end of Title Bar + return_keyboard_events - if True key presses are returned as buttons + use_default_focus - if True and no focus set, then automatically set a focus + text_justification - Justification to use for Text Elements in this form #### Window Location @@ -1111,7 +1149,7 @@ While it's possible to build forms using the Button Element directly, you should button_color=None, font=None) -Pre-made buttons include: +These Pre-made buttons are some of the most important elements of all because they are used so much. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. (hmmm Save already comes to mind). They include: OK Ok @@ -1600,8 +1638,14 @@ Valid values for the description string are: GreenMono BrownBlue BrightColors + NeutralBlue + Kayak + SandyBeach TealMono +To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. + +You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. **ObjToString** Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. @@ -1655,7 +1699,7 @@ A MikeTheWatchGuy production... entirely responsible for this code.... unless it | 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ | 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting | 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key -| 2.9.0 | Aug XX,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, +| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults ### Release Notes @@ -1670,14 +1714,15 @@ New debug printing capability. `sg.Print` Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. 2.7 Is the "feature complete" release. Pretty much all features are done and in the code + 2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) +2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. + ### Upcoming Make suggestions people! Future release features -Columns. How multiple columns would be specified in the SDK interface are still being designed. - Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. @@ -1738,7 +1783,7 @@ Here are the steps to run that application To run it: Python HowDoI.py -The pip command is all there is to the setup. +The pip command is all there is to the setup. The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see. diff --git a/readme.md b/readme.md index 392287c2..2efe1958 100644 --- a/readme.md +++ b/readme.md @@ -6,16 +6,28 @@ ![Documentation Status](https://readthedocs.org/projects/pysimplegui/badge/?version=latest) +[![Python Version](https://img.shields.io/badge/Python-3-brightgreen.svg)](https://www.python.org/downloads/) + # PySimpleGUI - (Ver 2.8) + (Ver 2.9) +Lots of documentation available in addition to this Readme File. [Formatted ReadTheDocs Version of this Readme](http://pysimplegui.readthedocs.io/) [COOKBOOK documentation now online!](https://pysimplegui.readthedocs.io/en/latest/cookbook/) +[Brief Tutorial on PySimpleGUI](https://pysimplegui.readthedocs.io/en/latest/tutorial/) + +[See Wiki for latest news about development branch + new features](https://github.com/MikeTheWatchGuy/PySimpleGUI/wiki) + + Super-simple GUI to grasp... Powerfully customizable. +Create a custom GUI in 5 lines of code. + +Can create a custom GUI in 1 line of code if desired. + Note - ***Python3*** is required to run PySimpleGUI. It takes advantage of some Python3 features that do not translate well into Python2. Looking to take your Python code from the world of command lines and into the convenience of a GUI? Have a Raspberry **Pi** with a touchscreen that's going to waste because you don't have the time to learn a GUI SDK? Into Machine Learning and are sick of the command line? Look no further, **you've found your GUI package**. @@ -27,6 +39,14 @@ Looking to take your Python code from the world of command lines and into the co ![snap0136](https://user-images.githubusercontent.com/13696193/43162494-33095ece-8f59-11e8-86de-b6d8bcc5a52f.jpg) +Or how about a ***custom GUI*** in 1 line of code? + + import PySimpleGUI as sg + + button, (filename,) = sg.FlexForm('Get filename example'). LayoutAndRead([[sg.Text('Filename')], [sg.Input(), sg.FileBrowse()], [sg.OK(), sg.Cancel()] ]) + +![simple](https://user-images.githubusercontent.com/13696193/44279378-2f891900-a21f-11e8-89d1-52d935a4f5f5.jpg) + Build beautiful customized forms that fit your specific problem. Let PySimpleGUI solve your GUI problem while you solve the real problems. Do you really want to plod through the mountains of code required to program tkinter? PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkinter, but you interact with them in a **much** more friendly way. @@ -35,7 +55,8 @@ PySimpleGUI wraps tkinter so that you get all the same widgets as you would tkin Perhaps you're looking for a way to interact with your **Raspberry Pi** in a more friendly way. The is the same form as above, except shown on a Pi. -![raspberry pi](https://user-images.githubusercontent.com/13696193/43298356-9cfe9008-9123-11e8-9612-14649a2f6c7f.jpg) +![raspberry pi everything demo](https://user-images.githubusercontent.com/13696193/44279694-5b58ce80-a220-11e8-9ab6-d6021f5a944f.jpg) + In addition to a primary GUI, you can add a Progress Meter to your code with ONE LINE of code. Slide this into any of your `for` loops and get a nice meter like this: @@ -48,6 +69,7 @@ You can build an async media player GUI with custom buttons in 30 lines of code. ![media file player](https://user-images.githubusercontent.com/13696193/43161977-9ee7cace-8f57-11e8-8ff8-3ea24b69dab9.jpg) + ## Background I was frustrated by having to deal with the dos prompt when I had a powerful Windows machine right in front of me. Why is it SO difficult to do even the simplest of input/output to a window in Python?? There are a number of 'easy to use' Python GUIs, but they're **very** limiting. PySimpleGUI takes the best of packages like `EasyGUI`and `WxSimpleGUI` , both really handy but limited. The primary difference between these and `PySimpleGUI` is that in addition to getting the simple Message Boxes you also get the ability to **make your own forms** that are highly customizeable. Don't like the standard Message Box? Then make your own! @@ -60,6 +82,8 @@ With a simple GUI, it becomes practical to "associate" .py files with the python The `PySimpleGUI` package is focused on the ***developer***. How can the desired result be achieved in as little and as simple code as possible? This was the mantra used to create PySimpleGUI. How can it be done is a Python-like way? +## Features + Features of PySimpleGUI include: Text Single Line Input @@ -89,6 +113,7 @@ The `PySimpleGUI` package is focused on the ***developer***. How can the desire Return values as dictionary Set focus Bind return key to buttons + Group widgets into a column and place into form anywhere An example of many widgets used on a single form. A little further down you'll find the TWENTY lines of code required to create this complex form. Try it if you don't believe it. Start Python, copy and paste the code below into the >>> prompt and hit enter. This will pop up... @@ -143,6 +168,7 @@ You will see a number of different styles of buttons, data entry fields, etc, in - A row is a list of elements - Return values are a list of button presses and input values. - Return values can also be represented as a dictionary +- The SDK calls collapse down into a single line of Python code that presents a custom GUI and returns values ----- @@ -733,11 +759,19 @@ This is the definition of the FlexForm object: location=(None, None), button_color=None,Font=None, progress_bar_color=(None,None), + background_color=None is_tabbed_form=False, border_depth=None, auto_close=False, auto_close_duration=DEFAULT_AUTOCLOSE_TIME, - icon=DEFAULT_WINDOW_ICON): + icon=DEFAULT_WINDOW_ICON, + return_keyboard_events=False, + use_default_focus=True, + text_justification=None): + + + + Parameter Descriptions. You will find these same parameters specified for each `Element` and some of them in `Row` specifications. The `Element` specified value will take precedence over the `Row` and `Form` values. @@ -748,11 +782,15 @@ Parameter Descriptions. You will find these same parameters specified for each location - (x,y) Location to place window in pixels button_color - Default color for buttons (foreground, background). Can be text or hex progress_bar_color - Foreground and background colors for progress bars + background_color - Color of the window background is_tabbed_form - Bool. If True then form is a tabbed form border_depth - Amount of 'bezel' to put on input boxes, buttons, etc. auto_close - Bool. If True form will autoclose auto_close_duration - Duration in seconds before form closes icon - .ICO file that will appear on the Task Bar and end of Title Bar + return_keyboard_events - if True key presses are returned as buttons + use_default_focus - if True and no focus set, then automatically set a focus + text_justification - Justification to use for Text Elements in this form #### Window Location @@ -1111,7 +1149,7 @@ While it's possible to build forms using the Button Element directly, you should button_color=None, font=None) -Pre-made buttons include: +These Pre-made buttons are some of the most important elements of all because they are used so much. If you find yourself needing to create a custom button often because it's not on this list, please post a request on GitHub. (hmmm Save already comes to mind). They include: OK Ok @@ -1600,8 +1638,14 @@ Valid values for the description string are: GreenMono BrownBlue BrightColors + NeutralBlue + Kayak + SandyBeach TealMono +To see the latest list of color choices, take a look at the bottom of the `PySimpleGUI.py` file where you'll find the `ChangLookAndFeel` function. + +You can also combine the `ChangeLookAndFeel` function with the `SetOptions` function to quickly modify one of the canned color schemes. Maybe you like the colors but was more depth to your bezels. You can dial in exactly what you want. **ObjToString** Ever wanted to easily display an objects contents easily? Use ObjToString to get a nicely formatted recursive walk of your objects. @@ -1655,7 +1699,7 @@ A MikeTheWatchGuy production... entirely responsible for this code.... unless it | 2.6.0 | July 27, 2018 - auto_size_button setting. License changed to LGPL 3+ | 2.7.0 | July 30, 2018 - realtime buttons, window_location default setting | 2.8.0 | Aug 9, 2018 - New None default option for Checkbox element, text color option for all elements, return values as a dictionary, setting focus, binding return key -| 2.9.0 | Aug XX,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, +| 2.9.0 | Aug 16,2018 - Screen flash fix, `do_not_clear` input field option, `autosize_text` defaults to `True` now, return values as ordered dict, removed text target from progress bar, rework of return values and initial return values, removed legacy Form.Refresh() method (replaced by Form.ReadNonBlockingForm()), COLUMN elements!!, colored text defaults ### Release Notes @@ -1670,14 +1714,15 @@ New debug printing capability. `sg.Print` Listboxes are still without scrollwheels. The mouse can drag to see more items. The mouse scrollwheel will also scroll the list and will `page up` and `page down` keys. 2.7 Is the "feature complete" release. Pretty much all features are done and in the code + 2.8 More text color controls. The caller has more control over things like the focus and what buttons should be clicked when enter key is pressed. Return values as a dictionary! (NICE addition) +2.9 COLUMNS! This is the biggest feature and had the biggest impact on the code base. It was a difficult feature to add, but it was worth it. Can now make even more layouts. Almost any layout is possible with this addition. + ### Upcoming Make suggestions people! Future release features -Columns. How multiple columns would be specified in the SDK interface are still being designed. - Port to other graphic engines. Hook up the front-end interface to a backend other than tkinter. Qt, WxPython, etc. @@ -1738,7 +1783,7 @@ Here are the steps to run that application To run it: Python HowDoI.py -The pip command is all there is to the setup. +The pip command is all there is to the setup. The way HowDoI works is that it uses your search term to look through stack overflow posts. It finds the best answer, gets the code from the answer, and presents it as a response. It gives you the correct answer OFTEN. It's a miracle that it work SO well. For Python questions, I simply start my query with 'Python'. Let's say you forgot how to reverse a list in Python. When you run HowDoI and ask this question, this is what you'll see.