diff --git a/DemoPrograms old/ButtonClick.wav b/DemoPrograms old/ButtonClick.wav
new file mode 100644
index 00000000..f774f70e
Binary files /dev/null and b/DemoPrograms old/ButtonClick.wav differ
diff --git a/DemoPrograms/Color-Guide.png b/DemoPrograms old/Color-Guide.png
similarity index 100%
rename from DemoPrograms/Color-Guide.png
rename to DemoPrograms old/Color-Guide.png
diff --git a/DemoPrograms/Color-names.png b/DemoPrograms old/Color-names.png
similarity index 100%
rename from DemoPrograms/Color-names.png
rename to DemoPrograms old/Color-names.png
diff --git a/DemoPrograms old/Demo_All_Widgets.py b/DemoPrograms old/Demo_All_Widgets.py
new file mode 100644
index 00000000..51d70237
--- /dev/null
+++ b/DemoPrograms old/Demo_All_Widgets.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+sg.ChangeLookAndFeel('GreenTan')
+
+# ------ Menu Definition ------ #
+menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']],
+ ['&Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
+ ['&Help', '&About...'], ]
+
+# ------ Column Definition ------ #
+column1 = [[sg.Text('Column 1', background_color='lightblue', justification='center', size=(10, 1))],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]]
+
+layout = [
+ [sg.Menu(menu_def, tearoff=True)],
+ [sg.Text('(Almost) All widgets in one Window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)],
+ [sg.Text('Here is some text.... and a place to enter text')],
+ [sg.InputText('This is my text')],
+ [sg.Frame(layout=[
+ [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)],
+ [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')],
+ [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)),
+ sg.Multiline(default_text='A second multi-line', size=(35, 3))],
+ [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
+ sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)],
+ [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
+ [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)),
+ sg.Frame('Labelled Group',[[
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, tick_interval=25),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
+ sg.Column(column1, background_color='lightblue')]])],
+ [sg.Text('_' * 80)],
+ [sg.Text('Choose A Folder', size=(35, 1))],
+ [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'),
+ sg.InputText('Default Folder'), sg.FolderBrowse()],
+ [sg.Submit(tooltip='Click to submit this form'), sg.Cancel()]]
+
+window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout)
+event, values = window.Read()
+
+sg.Popup('Title',
+ 'The results of the window.',
+ 'The button clicked was "{}"'.format(event),
+ 'The values are', values)
+
+
diff --git a/DemoPrograms old/Demo_Animated_GIFs.py b/DemoPrograms old/Demo_Animated_GIFs.py
new file mode 100644
index 00000000..e6f02868
--- /dev/null
+++ b/DemoPrograms old/Demo_Animated_GIFs.py
@@ -0,0 +1,65 @@
+import PySimpleGUI as sg
+
+"""
+ Demo_Animated_GIFs.py
+
+ Shows how to:
+ * Use PopupAnimated
+ * Use animated GIF in a custom window layout
+ * Store your GIFs in base64 format inside this source file (copy and paste into your source file)
+
+ The first image that uses PopupAnimated will stop after a few seconds on its own.
+ The remaining images are shown 1 at a time. To move on to the next image, click the current image.
+ If you want to exit before reaching the final image, right click the image and choose 'exit'
+"""
+
+# ---------------------------- Base 64 GIFs ----------------------------
+
+line_boxes = b'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'
+
+
+line_bubbles = b'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'
+
+ring_black_dots = b'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'
+
+ring_gray_segments = b'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'
+
+
+ring_lines = b'R0lGODlh2ADYAKIHAPj4+ODg4MnJyaysrIuLi2NjYzk5Of///yH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0NFQTZFMUU5QzBDMTFFMkFFNDdDRTVDRTJCRUM3RTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0NFQTZFMUY5QzBDMTFFMkFFNDdDRTVDRTJCRUM3RTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQ0VBNkUxQzlDMEMxMUUyQUU0N0NFNUNFMkJFQzdFMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQ0VBNkUxRDlDMEMxMUUyQUU0N0NFNUNFMkJFQzdFMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDP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'
+
+
+red_dots_ring = b'R0lGODlhkAHIALMPAM0/QMQfIdRcXtpxc9+EhOahofbf3/vv7+KTlOy4ucIYGv/+/vHNze/Dw8MbHf///yH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjMzOEMxNzM2RTAzMTFFNjg4Mzk5NzdDMkE1QzlDRjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjMzOEMxNzQ2RTAzMTFFNjg4Mzk5NzdDMkE1QzlDRjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MzM4QzE3MTZFMDMxMUU2ODgzOTk3N0MyQTVDOUNGMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MzM4QzE3MjZFMDMxMUU2ODgzOTk3N0MyQTVDOUNGMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsNvBgkIyAUGC8TNLAsJAgDT1AACBczO2iQG0tXf1gzb4x8M4OfTDeTrGQbo7+Ls8hIL3u/gAtnz6wn37wn7Yh1ogGCAwQINDoiw5+9bvoCtDhQQQLFiRWwf3DVEZwDiKgMD/yyKpDigY4d+G88V8Jiq28iXAxRyQJDyHAKWpw6EfAlTXwaaNb/dxFlqIk+eADcADUptKFFRB45KlZkBJdNpK5+KaiD1aLx2V6mZ1AoKQVeeWTPUC/uQLKidZ0U6rRo2LQcDBQjoRZDQrSK4cSsS4LA2aFsNCyYGWMw4AAACY/0SAhxYwFywQdVtcNe4M2MEPiUDMlqZYlIODWqepuu5dYCYogVxLU0x8gYGDM8J0KyhgevfA2IHikp7QOgNEnM3pQr29+/LwvdEK736w8CCBvky1zDAuXPb0fMsoCyVwPEZBrw7Hxy+D8i4sHMQUP8bwPb2eHR2JXC/BgD6v/GGn/8e0PA0QALn0XAAgM8N6McCxiBAAAIJLNMDAwy6xp6DsWCYoWcbcviKhx82FqKIEZXYmV0otiKAiox91WIrCMDoWIIzdnGAAQb098SCMEKXIxd46WUkAQhSUWOJ9g35RWJHRkmAjD+++GF1TmZxgIRSRollE5wxKGSWVizAZZdRUulEmOqxSCYWCaCJpo9LHDCfc7u9ycUCcqL55ZoE/NeZAEnquQUDfaKJhTEJJNCXoV3EmaiUdEIaSl6TRgmepaNgmqmRm3J66adHVipqJw2QauSpOalKgJusjkpqqLF6Yuenf9b6iQGZgqbrKbz2idGvOXl6JAIM4EhsKAcwkED/AQjRuuy01FZr7bXYZqvtttx26+234IYr7rjklmvuueimq+4rBjQAbQEJJLsuJAccg8y9yKg57yF44esvhcru+4dE//4roMCE2Fuwv9IivMeWC/+bq8N7EBTxv6ZSfIfCF9/bsMZ2FNAxwyCPNjK+H5c8B8cjp6xyHAycfG/AL8sB8ckH15yHxR0XkLHOcrBcsMtAwwFNxMoUDUi//ibws9J1NMsAAz16scABWNMMtR1X8+j1AVpvHUfXXpcdtthu7Fj22mejrQaEa6/9tNtqqB3313TfYffdVecdNd94+00H3IDPLfgKV2dtxN5xt334Cc02Krm8QxAet+GPm8Co/+ScE10D2WVjnjk3nJfeqOc1YK2646OPUK/pprPe+hYNwG66vrOTAY3tseeexua8Sy46BV0r7vsSwAfvNAk7Tu08A8Mff0PywQ9vwPPYoy79Da8r3yjrUmP/fPTbz1C797hvJr74spcvQ/fVixD++s6T7z4Mzgav/QPz0w/9/URYQP5gZyERXM9/ztsfAN93Psk9ynUIrN8CF+e19k2gf/Sz3wSZcEAEWnCDTMAg9jQIQuT5T4El/FEHJZhCMqjNeC2MoQxnSMMa2vCGOMyhDnfIwx768IdADKIQh0jEIhrxiEhMohKXyMQmOvGJUIyiFKdIxSpa8YpYzKIWt8jFLg168YtgDKMYx0jGF0QAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExZUGBQQCywQFBgvG0ScGysvW1gQG0tshBtff1w3c4xve4OcCDOTrFebo59rs7NXv4ATytAsG+9Aj7vXg4uFrtaABgQEIESJ4FqIAQHQFBrailrCiQoEd6D28dk+iqgYW/0MOyPZhIzqPqQyIFEnggAeT51CeWnBwZUgE/TbABCfTFEibIjFm0AizY89RNYFajMjB4c5lTI+GWqCUZc4M/2AKldpJZdWQLjkQfUjgKteuX8F2yPpwK9YCAwDIFUAggdmzg7ymTRiWQwOYCTyolEu4sIDAeAtR3YvQaIe/D6NuSFC4cuEBfRMHQsB4AOIPKt8NcHuhgOXTczNr9qM3LWkNCxhwtjYAQYO7GBqg3j0A92o9nNNKHnGguG8MB3Yr//y7D8WqLXeYVo5awPHmdp7bjK5jAfXl2P8cCC6ygOobBr7vdhyeD7KkdV/XmK7esoD2goqf30GgPmr8tfTnn/9lANKCwIAEFigLZQgSdp+CsaTXoFwIQCiLABMCoI6FsDCI4AAckqFPAwkkwABDVCyAIYIbhvhFQQjEKCMCzlQhoX8VuvjFAQXM6KNt1ynBgH9l6egFjz/+KA4V3nxXQJBGTtFjkj/Kt8QC9Fk2WpReGEBlkk9ewQBcjSWwH5dYJPBlklaiKcqaSS7pZirjwekjc3OaUqedMuKZJykL8DmjnH+aMqWgbRbKiWyC4qToTI22KIY++zz6hJd2hhkGNQIE4GkAABwGpaU/MPqleZsO8OmqnwJgF6lJYJrkq2AUwOqtnwpwJqxAxKZmjAUkkGgVtuJqLAC78npJAsY2G4D/dcp2BYCzzQ4X7SWqUttsstc+YoC2zubYrSUIgNssAONe0qm5xg6b7iHTsosroe9CIq+xftbbyL245qvvIvHy+ym9/zKyrsCeulvwZgh7iu7C3jYcgLgQN5KtwNxWTEh6AlursSLM3gvtxzfoN+oVxZqLLMk16JNAATAX0IDCUpqrK8s0HPByzDzfJoZKzrp6Ms4l6Mzz0cEOzaQyq4ZKK9ExLLAz0jzTXAWlKEI9AzJUH/201otM3XXMVoOtB49jH02w2YWgnXbMa7M9iNtvyyx3IljWDbOkdxcidtpl910H129/LTghDdQd+OF0SA0443iPibSwkC9ygAElJjCz/9KVd+75EAuELvrniIhueuikG3L66akPsjrrrQfyOuyx+zG76bX/cfvoufexO+e9t/F78EEscPk+BwBPw+3EA2EAAw1EHz0DGXe3evM/GCD99tEvPgPq2PugPffcVx9+GrGRzz0Dyp+/qfrkm+9+GePDL73383NRv/0zm2D8PgZIXv6YxL/7FQ2ACJTfAIewP/spkALHQyAAH7jAHxSkgOwbAdYkCMD2VXAHDSRf4CLIQeR90Akjgl/WulFCCZ4QhSHcXAla6MIXwhCBHpQADRFoQzaQsIQU7GEWNljCHAqxCj9M4BF9CMQlCi+CAnSiFKdIxSpa8YpYzKIWt8jFLj168YtgDKMYx0jGMprxjGhMoxrXyMY2uvGNcIyjHOdIxzra8Y54zKMe98jHPvrxj4AMpCAHSchCGvKQZ4kAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExZcHDMkHxswrCwkEA9LSBAkLzdgjDNHT3dIN2eEdDd7l0gni6RcM5u3g6vAL3O3ly/DpCfTtBfeyyAkAGdgTMU+ft2v9Wh2ARqChwwQDPRww2M5AQlYNHGrUaPEDO4rl/9BdTJVxo0kCHTt8BNlN5EhTB06eRICQw0qW516eYihzo8sNE3FOS6lTVMyeJ2tuKMhSaVFQ25CaZOAhn1B+T0fxlOrwHQd5QiNm/bSVKwGvNnH+lNigrQGnYw2V5Ur1g1WDBeBmOFBAgN+/AgjUjWsoqtmGRMcZtPahAeDHfhGIJQzoAILDBGiK4GuuwGQNfSFDHvCZcp+5PdGGWGAAYIK3IUKLHq3X9B7LZvPyMDC7N1bbgAyYLV1jQO/eg4H7MXBZpucevI/PRqA80DOTCBrUtiFbOuTt1fGwbssA9g/j3kUnDi8r/WzV7GO5Fw0//qv5kOvbb0UAP2Di+6nSHf9+Ac4SnX+/FQhLf/6tpyAYCxxwAHhOHODfWg96wVoCBXRYQAMOQuGYewRQmKEVC3mo4ocmKsFAepqd+AVfK67IWBXCHXejjF3QWOOK+kHBAALo+VVNizxO0cCPPwIIhYQTJhnGAkz+mJyUqhhQZY0YYnkKA1vW6CUrYIap4piraGmmh2iq4qOZQbYZCodrhihnKGqGueOdppS5pZNatOZhA4DyKYSfXBaKYl8ANOooAANcaegRByy5IgNIVtHAo5w6StqkS7BmgAFRjlFAp6g2aieomSCQaqoCrMpqJZu+CmumszZygK22dpkrJafyCuuvmggg7KuyEsvIrsemmqD/spLU2mynA0BbSQLTolqttZNgmy2n23IbibTfNhquuI8wW26jz6LbiLHrApCsu4UEW64A9Eai7re+5psIuc0KgKu/gbg6bawES2KvsPMmvMIBBpRn3hgAa6uowyhU2tbGIA48BV/wgispxjAYwPHJmJohaIeEkmyDxidzPLLLizAQc8wX01zZzTHPrLMhJvN8ssc/6xG00BsTXTQeRyOt3dKKNI200lDXsYDTbmGxEJF+DYCAz1XDYLPTORshXABop522AHuGHcPVSDeMRAFq1622AGW7PQLMMU8cxQID2C042vLqPcN4MudNhACDNx6A3IaXACXVQtDt+OAAUB65/8qXO07d5noE3nnjioNexgKjO96u6XEkkHrj+LJeBwGvNy57HaLXbrfmt3PBuO52Q967GL8Dr3bpw3eRu/FoJx+H5cwHAIDzcBwQPdqfU48DxKOOirwKxRsvvPYmiNp999+jwED055I/w/nwk+rD8q8DkL77EsUPP+8lLAAA8HHC3wr0Fz8fGOB/r+uXAJ1BQPj9YAHhaxwAArjAFJivgeO7wAI2WAJsOY4A96vgVzDYvRNECEqlGkFGGkU4ATxHhDUg4ajKh8LJwfBjJETeCWsoIf7dEAcX1B/VeFjDH/6tgVTbIRFTaMQmRAh+TBSBEonowyYCkYMomCIPq2jFj2gtUUJddIMWbRjGNoyRi2WcwhTRmEYqbJCNbYyjHOdIxzra8Y54zKMe98jHPvrxj4AMpCAHSchCGvKQiEykIhfJyEY68pGQjKQkJ0nJSlrykpjMpCY3yclOevKToAylKEdJylKask0RAAAh+QQJAAAPACwAAAAAkAHIAAAE//DJSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx7kGCQXMCQbI0CYGBQTV1gQIz9HbHgnX39UJC9zkGN7g4Anl6xMG6O8M7OvU7+AI4/Lb7vXo8fmuBxgsa8YAX4hz/L6p+8eKAbOHELWBoJfwGgKGqxxC3FjA3weKFf+rXcSISiPHjRI7gAw5kmSpAydjGuSAMCSBhS5JmYwJ0SOHfTYJ+MwZaiBPiDg7IAhKYCZRUEdPggBacejTT1E5HgxZwOlVT1mRiqiJrqsIZQUQIOjo9esgo1mTfmCwtF9bDe4G6N2rV5xbQzujpgSxQGBaBAkKhlhQgK/jvYP//mEcVm4Oxo8zD4gsuc+0rAd8JNCcuWlnQZ95cr5xgLRmy6f5wDyZILSPxq4z2449GW2BBM7u4sit2SpvWa2JP4Z9HBYD5Y9bNp/VALpj6dNjPbe+F3v2V8m5D2D+nRUB8ZvLzxotXrj6VAvEG3/f0Lp3+l8WGNh/wP0T9rkRsBv/fl8EBNyBwWFRnWsFDEhgF8ogiGAD/jUBU2YIzPdgFgdI6CGFWkSYmIMbcrGAhyiuViIqIqJ4IIgrtiKQixKSGOMpDdAooYo3jpKjjgfy2GMoMwJZ25CqtKhjhUh2cqKRGjYZipIoMillJ0WiaOOVpFCZQANbejENAQIIMABiYXJJhH4MtGlAf2U8B8CcdM4pgF9qloKAAHX2aaeQeWKCgJ+EAiAAoIFSUkChhQqQZqKTGMAno4TeB2klBFDaKKKXMmKApowW0CkmCYBa6ACjXjKoqYRameohA7BK6KOvJhKrrH3SWushq+JKp6u7vuUrnagGG8mnwwIgqrGRZOrr/6HMHjuprJZGq8iisjpqrSS9agrtttxOuym4lMhJ6J3AknuCfga4CacZY5Z55pHq2hBQm/gyYEC69Q5yb7758tvvZAAXzOnAqBVcsMAI79Guwvnq2jAgEAMs8cR+VBwxxos8rPHFHO/xb8UMH3HAfiWHXEJhH2Nx4q10CpCNyjewrPC+V5RKKQIg07yyx/i+S0VrrEbp8woHnPxmykHEh6vRR9cBs6wHR+2Gzs9avccCyc5JntZwYD1ssWDbMfWwPZddRtd0fq22GgewPeeyb8eBLNt01/1G3HLnrXcbXMvt9t9miDts1YRb+OZEbAvAdOIjLCC55CnAJEAACmQeAP8AM2/Ad7J+Qz7DAkmXnnTJrWGe+eqrOzDAo9g++7joH5huOwkNqM767pp/fYDhpjZAu7223x5C7rwnv3roFHzOKvPDu0B68aYDy4Duyif/taTPR1/D9NSf/gHX2ZcfQJoHdHsu1N5XHn7priJQ/vxkZ2CA+nbi2f7o74vfwQLYmx/vHMApEYFpfzXrn9A2kAABzq9aCBSCAj8wAAeWDwARVAL4iucqAFgwewHIoAapB6wAfpB1aRMhDsDHrxNmD3EqfIIJXaiAFMaQCR6kIetCeEMvVFCHq8NgD7vQQCBmDoJDnAIAjXi+JHZBfkCsnxOzQD4aNnGKXDDADMvnAOFpYbEL1/tgAAb3xSl8yoEBYF8ZlYiALSogAPdYoxheBgDMbW4ADZKjHvfIxz768Y+ADKQgB0nIQhrykIhMpCIXychGOvKRkIykJCdJyUpa8pKYzKQmN8nJTnryk6AMpShHScpSmvKUiowAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHuwsHC8jNKQcNBQjTBQkHztghDNLT3dMNzNniGQne5tMJ4ePrEgzn7wns7Afc7+YG8uMN9vD5sgcGAqobUY9ftwID/amClqBhwwbXRCww+C6iwlQLGDjc2JBBQg4H/yiew3cxlUaOHBmEmCjSm8WSpQ6gnPmyQ0GRH2GGajATpUoQ+1oiKKDTlMyeKHNmoCeUZNFRBpCirMkhKMV4T0lFlbqR6oYF5QwizKqVa1eJYeF59aBsLVlCYM02VMohasECTkPIRECgL4ECHt8i4mn2ZwllBpaRAOu3cV8EeQULOsqVro0FBRxrJhBZMqCTSDvryLxZs1vPe0CjFJ2DQenNY1FPVp0g8I8Fr0sblh2oreIgrnNrJso7VwLhmy0Xd0UaeePTy1s1d94XevRV06lbv57qOHW/3GsZ+N4Xa/hZfL+zPs9qPHXz7GU1cI5AeXxV3l8j2H7/CmIGDCS2hf8B6WmWTn9jHADgggAaYJ8TC0R1kDUIjmEAgxjaVmEsCmbI4HobpnKhhwzyF2IoGZH44YmudKhigyy24uKLIMYoSoovBmgjKzkCaOKOnMxI4oNAejIiiT8WuUmESCop45EBErkFQFE6GcUCWEqZBT0ECOClAAMgoKGVpIA1wJdoeskZmWV2meabA+zGZicTvWmnlzXOSUkCd945gJZ6MnLAmX3aCV+gl/BZqJ1/IpqJm4u+maejiyxAaKRpHkppJAdgauimlXTqaaagUmLpqGhqWqojkKI66aqGKIpqo7ByeqmnqtbKiKyY0qorJHV6+uqvheAWaZzEmsrrm2smG2r/ZmiGOaazNWB5wG9pUOkgtTpcG1BA2HI7CUDffhuuuI+QW665gKILiLrrguvuIxHGW26S8/YBr7345rvHvvH2628e9dor78CLAFxuuwgTbLCAWmRUwMQUNpwDYuueS8V4AHTsMQACHGjxZd4mxrARE32scscCyDnyH52uLDMAub6cR8wzy9yAzX8QkHPOAvOMBgM/54yA0HsMUHTOwyKdxgJL51yz024QHbXMBFBtRwFXyyyA1nVw3bXKX4M9h9hje1y22XE0kPbHA7AtxwFve0yc3HAoXbfLeFchEzcJDOv223H37R+fASSueAACxLaB3mM3bbgRDAiw+OWJA7Dz/wYGpH335FIkgPnoiX+OgdVRE3Ay6D+ITjrppl9gAOQ5O876E52//jrfFYAlwMzN3g7FApbrTjoAD0pMwAADEBC48FO4bjzs0KMxwPSvC7B69cNjr7vk3E/BgPevTx1+FtKTf/nR54eRvvqKs9/+F+/DH4D883cxvv2Lm58/FQvg3+LA978mXE+AaysgF+pHvtgpMAvFgx/yHuiF3MGPdxS8QgIAoD4HZhB9HJweADz4wSwYIIKka1kJx5CAA15uACJb4Rgy4hAGBE2GOMyhDnfIwx768IdADKIQh0jEIhrxiEhMohKXyMQmOvGJUIyiFKdIxSpa8YpYzKIWt8jFLg568YtgDKMYx0jGMpYwAgAh+QQJAAAPACwAAAAAkAHIAAAE//DJSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJyh8HBgwMBwvL0yEGCQXY2AkG0tTeGAsN2ePYDd3f6BLi5OQJ5+nUBuzzDPDf1/Ps7/bIB/nzBvi9avaMAbcS8v6Rqydw1QIDDSJKbHBQBAOFCxuqWsBgoseKIP8SYsTGUOMpiB4/ivA3EltAk6YOpEzJYF8HfCNtwgSFcubEAyJEKiy5c1RHnxNfhlj3z13RUkeRRlQKYgFOdgmAPiXVUyrVqhfZ1dxaSqbUiFpJPCwIcsSCAwWhkU3E8exYIBwLINjLN+vcQ11npvWxQC/fw3u//g0UOGmQwogjI1C8+I8zj3KDJJAseXBlQG8NiI4mxABnyQk+89p8OrJn1bVaS6YMO9YB2ZGJ1p51G/dh3btjLfD9O7gtw8RpG2/VgPhenctb9fbdIHotBr4LQLfeinXr19xhNedcAHz4LwvSb6dywPveAnfPi2kmevT6KaENmJffhX79+vfxl4r/f/8BKKBwBSa434GnEJigaAEyKIqDD0YoISgUJmjhhZ5kWOCGHHLy0IP1LRjihCRCeKJDKZq4Yij5/efii6K8dcCNINJII0esFeCXjqhYRcAARBY5wGRAloWAkUwOQABwSXJywJBNNglllJgMV+WWymFJCQNbbomAl5wsGWaVXZL5yAFnbpmampeA2WaTY8JpSQJzNkmAnXfmyeSefH7pp5F1BioJm4MS+aahkpg5aJqMIiKnn4VGComWfkJq6SFT5nnlpozcduaToFYiZJVIlmoJj+/9qOqrx6inHqyXyCorrZTYaiuukei6K6+P+PorsIwIeyuxxRqbHrLJGsts/7PCPgvtsNIqEq0WeRVJQAGaVtvCsVkUJsC45JJLQLfe3sFmueySW126gazb7ryLwsvHAgPMq68An9pLRwH76jtAjv6mcUDA+9ZbcB0NIKwvoAvbgYDD+hIcMQsyFTDktvHxkC/F7aJ7McYEBGDyySYDgMCMMHwMcrn9jvxCASjXjLLCN7j88rgxy7wCvjYHbXKlN0y8M7ks+3yCAEI3TXQNCRw97gBK01By003jPMPBUhdQtQwGYC120ioYvTPZX4cwgNhYP72BPASQOwC3zBytddopHMA21gBYaAABAAQuuOADdMvAywhYjDcHCeyNdc8PJDD45IN73cHhDmu3+P8LVzsetOUaIED56IG7fcHfAd+9OQpMex40xBhITjrpoHMgj84EuLp6C627XjPVGRgw+/CQU3AjabvH0LvvJ8NuAeDDkw588kR0zvzQGRwQ/fAiU08DzdefrHoD289eu/c/hB2+yfuJXv7o06MPxPK+O1/BAO+PLoD8QjCwfpf4y9/k9se/IKyNeeezQAAFKDgCFvAHB/PdwDbgPgYGLn4P7EEEHSeAAJHPgoFLYAZ3wKa9ESBC2gMhALo3whgcrmmFA0EFBYjBFqZPY+Yqz0oEYEEW2rAJH8yf6n54hQLkz3REzELDtjfEJGLhNrNbmRPJIBNHIUB3U8yiFrfIxS4/evGLYAyjGMdIxjKa8YxoTKMa18jGNrrxjXCMoxznSMc62vGOeMyjHvfIxz768Y+ADKQgB0nIQhrykIjsQAQAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8w3BwYMDQwGB83WHAvRDdvcDAvX4BXZ3OTb3uHo2uXk5+jWB+vx1e7NBvHrDPSyC/wo6vfc9Lla8MyAQWomAK4TyGrBwYcGv434p5ChKocQIZKwp9CcxVQF/zMenBcCXscGJD+WEpmRBEV8ElWSwsjyIIlxAGPKHEWzpoESOMu12znTp00TB9QxOKCT6CijBp0WC8kyJZCkCbI2MNBUKiGfVn0saFCgrNmyP70a6vkwbI8DCc7KLZBWbSGqCIUsiDtXbl27vBj0nZugK2BbCwb3/Xv4lgHFcxs01iUY8tkEk3NVtlwWc+Zbmzl7/lzrAGezkknb4suZsepYj0Ubfv2KrGW3tF/thYw7N6zQqHv7huVQ29LhZ/gpR66L4IHnz2czHwi9OtPps5xbh4593/bq0ruf+g5e/Cvy0MObJ4U++vpW2r+rfy8K/Xz6oeKnx6/beT/+AAZojP97Aq7yGAEIEoBAA/cVmIlpCUaooGsOdnIAAhJmmFqFniyAYYYacuhJAiCWKJyIkRxQYomjoXhJAyuCiECDLipSQIwgnlgjIx/iGCGFOz7So48IAhlkIzcSmaCORyICo5IK0tgkISpC2eKUNcBTAAIIFDAUGCQqySSWKSQgQABophkAAASMOYWHRG5IZgwGnKnmnWjOCMaFOF45pwsJAIDnoAEI4GYUEMqYz58xJEDoowAcGsWBCS4oKaMgGCDoo4QOQAaBmMKwwKacEupnqIEUUCqnAEiJah2krjroqa/yYYCsnApQqyCq4kooALsGgoCvj7oarBsDEEuokcfWQYD/soNe2uwaw0J7p7HTquGotWkCm+0eB3Cbpqff7mEnt4tSUZC05XrAgLi6TiHYAPTSS0AC7LarwbnKpvuEivUGXK+c+qJwQKy4FiCFAQI3TK/CBaugKbEESAGwww3TGrEIE8uqZxQIYIxxvhtTsMCzrBLsBAMiY4xAySpciHAAAxRGRQEtjwzzCs9Iw5UVC+SMsb87y8Gw0A1DXLQOYxEgAABQC0DAlzocjXTASi99Q6BQd+21AETjYPXVD2vtDAFep+11AdiiEDTZA5tdwwJPq203m22fEDLcA5Astwdo33131jc0wHfFf8vAteB3h03DyXA7nrgKozIuuADYFpdX/6ZkEz45C4tbbrfkGMDltACoCzBAAYcaLjQBeX++QeCi2414Bw2krrvuNrubM9uyw1D75R4gsPvxqMP+gekNI0B18DEPL3gHxiOPvPIfONRAVsdBD8PB0tvNAQPWl6+x9z6AH37aG9Bdfvl+o//C+mnHm0Hu71vvufw/DEB/17fDwOnydzxy8U8IBfgf1FRWAQLC74BCUB/9wnMAB1qPWRDEAQL+d74HVNCCxyNdBnFAt/UNQD0fBKHuMDhCG1RQeobqgAp3F78WpuCFojth8WaIOgPa8AceslzvOkA+Hnbwhzgwjf+8VjNXuW+GNURiC/bjkhkeUYpUMJMFP4ZFL15oMX9c7KIXilg+BoqRC9moXuruFcUzNoGKboyjHOdIxzra8Y54zKMe98jHPvrxj4AMpCAHSchCGvKQiEykIhfJyEY68pGQjKQkJ0nJSlrykpjMpCY3yclOevKTgIwAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8w4CwsHBwvN1B4LBgzZ2QbT1d4W19riDAff5g/h4+Ld59TY6uIG7dQL8Ors88n19uLl+a7QouEbcYBfv3+sAkZbOBDEPoPkEKpSuJAhiYcGG0ocVbFjtBLv//jJ23iKokdpFyFqJAnqZMeVHdKp88eylMuKMGOG3JazZieTHnvGtOiz5E2BRYsdpUmEaNJEN4tca5CgaoKITw8BRTnkAFWrYEdmNfSsbJEDYNNWFTu211e1YJm2zYUWbloGc3kxsKtWaF5Xb/lWlfuXVmDBhAvLOsw3seIwBhoUSNDAcRQDgsH6fXyFwYAACkKLBoDAcpO6mfFyhixAtGvXAQpsPrI3s+nVVhKAfs07tIDbSRYwjov7SwEHvZMrAAAcST27zYtHaYBceXIAs40U/NqAm/QuB3ZbT47geyoC4607iG4e0wLx6XuXb08qQXzrAOiTQn9fOVv9nrTWX/9yDQAYCgADJleAgaAgmCBvCzIY4IO8FShhJwNQ+Np/F2JSgIaiBZBdh4wcUJ2GA5DYiYAacqiiJQacmGCKL3LCX4IBuFijJSze54CFYMjE3o42LNDjeAEkEIZwBTTpZAE6EskDAvBdp9oXBzypZQENjChlDAZ8dp1skG25ZZdfCrFAAgQMIIAAAyDg3ZJmmhllmpQwUKeZXuKZSAJ7bnmnn49kGaiWVxJaiaGHOgmkopQw2iiXkFoiaaOJVioJoJNCqWklenba56eBLNDpoKS2kCUBAgAAgAAIMDDqEwY0imaqNBwwgKu89gpAArOeFuituMqQgK/IujrAkFIwKWj/sTQUkOy0AKB6hZDBQtvBsdROy6y2eRjQLbUEgJvIruNOm6m5gYib7rQ0sjsIAu9S+628caBbL7Lr4svHvtNG6O8fACc738B+FIzswQj/q3CvAje8h74P9yuxHdw+jN3FfBygMQDlcswHARpbi0RBVnUnMgwHtFpwxFCgRcDMNBMg58ouMFDwANnyYAACNQdNALA4s6Bzvb9NYYDQTBNdtAoGuNwtAj3vcADQTAtt8dMjmCq1rwOYfASbWTN9L9cZYEYzAgmcLcQBZWf9KNp1MBA30zDTPQfZdwet9w9TUTbnD3z3TfPfPiPw9aulEW6434g7ozi1ApDJg92Pz5x3/+Qx6PouAVUTlPnMSnJOwwIUjws6DwWM7rbpH9AL8OY1wP146bCDuXi6Se/QgOFU5y4DyQoz/IFXjt77e9wFvC78BkY+LEB2Pw9g/fUDELB12lgLTezzOX9ssqnYl2994yHsVQACCHDpPPgbSKvx3BiQb7756MOPhPwP465BAvcLoPH0RwT+Kcx/GAhTAAO4PQL24GgPQxUCFhjAkDmwKbt71/SgR8EFvu+CLUhdvSyYtg4GkH4g9AEE9yUAVAHQhOajXQp5QLx9DfACL4Qh9mQ4Qx20bF/L2pYOy8fDHuYgau8KmwcUOETrodCIPIDbuPLHAdQ10XofhOIKPLM4AX8QQGwTKMAVSajFt4WqAFgRga6aCMYyRiGHJiyiG68gRhMGb45hgOP9LIfHMGTpfjfrYxmuYZXBCfKQiEykIhfJyEY68pGQjKQkJ0nJSlrykpjMpCY3yclOevKToAylKEdJylKa8pSoTKUqV8nKVrrylbCMpSxnScta2vKWTIgAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBwsPExcbHyMnKy8zNzk0LB9IHz9Ua0QbZ2QcL1t4TC9ri2d3f1gfj4tzmz+Hp6uzO6O/a1PHM8/QG9vfK+fT8+iFzpy+gwGP/xq07mIxgOoMMjWFTBzGixGnSylncyLGjx48g/2vlWxhS2AEGKFMyIFmy10mVKg1obKlrAcybFWnWMnDz5kydtWz2hJkTKKyXQ1MaMHoLaVIGS5mKfKpUqi2qKIta1bKgAYIBAgQMKBD1Ck+qP7duOUAAQIC3cN8KSHBFaFKtaqs0cBu379sBeJc4JZrXCwK/iN8CKDslHM7CXQokngwg8JJoGSF3YTC58wDNqAR07kwXNKkGozsLME1qQOrODFiL4vsacQHZoAzUnvwZtyfOuxGv9t0JePC+w4lvMn4cbm/lmhY074sAOifR0982sL4pQfYAANJyp3SA9vHq4zVJbl45/SbXx2O713QAe+3b8zcdgD8awHYxjiWQQP8DK+U3RALm+TUAY18c0ICAEArIgHgG6tDVAOYJQACDXxgQ4YcDUljhDvNYlsUBIIIo34iaPJjihyayuAiKL364ooyVeFjjhzhewsCOPPZYyY9AQihkjkUaeeQkNBZ545KQuAhkjFAS0mSNT1Y5g4MSitiFji824KWWKhxQgAAApKnmAP+NcWWEE5I5QwJoqmlnmguSEeCABcoZwwJt3Slomln6aUiggw5aqKGCIJhoogJQyagd9T36KH6TDlKApY9GmukgdXI6aGmf/lGeqIkSUCogDKAK6ap/OOrqncnBuoess6pZq615tJqrnc/xmsepv6aJqbB5YFisf8jusVf/sQKM2Swcyuba5rR4GPArAdImYVMCBSCAQAENSIotB8+iClgVBoQr7rviinnuCg2E+ui6VDAA777iJtDtvB2w1am/7PJrMMEAp9AuWGlqmIC5RCzgrsH7cpjwHPpSzO+xF9ORgMYGQ9zxGBOD/K7FI7thMr+LpizDtwTEnECcQJS8Msouv7BAAmCF5bNY1/Jgs8ki5xzCfj8nHRYBRaeQ8crj/mt0CQb0rHTS+JIItbgtT53CAlZfnTS3PTwNcgFSey0CnWKLHXQOH5/dtNoaIN321QOk7YLZ+xYwN90Z1Hu32F1j4GABMROAwMMkHA5vATQD7sKZg19Nagc7J665/8x6g1Oi5DKEXbnPqnpwAAKbp47A36DHIProGnqwAOqpq9556zW8PnrpHCRQ+++X4y4EArAnzbHhvyfPuvD0Fv9z4RI0kDzwzA9Rn/NiWYb49KpXPwTlxQefAfe/ey+E3aPn7QH5tZsvRNXpx8h+6u6/r/vPeX6w/fyK138++EpDWwikxz8CiM9/PXDQVwYwlnKNgC0FXB4Ci+C7+R1wgliYHfsQcDsMMsFM3PObB8NAwNrJa4RhcJziyCVBFLrwhTCMoQxnSMMa2vCGOMyhDnfIwx768IdADKIQh0jEIhrxiEhMohKXyMQmOvGJUIyiFKdIxSpa8YpYzKIWt8jFLgV6EYkRAAAh+QQJAAAPACwAAAAAkAHIAAAE//DJSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7y9vr/AwcLDxMXGx8jJysvMzc4qBgUEAtQECQfP2RYMAgHe398DBicL5eYL2rIHA+Dt4AToI+fz6a8H3e75AQLxIPP/9Vjd00dwQD8P/+gFVMWOIMECIRKeW5iKgcOL4z5InEjRFL6L+v8I+Nt4sKMoAyAvYkNI0mSpAikdJtDY0uWohjHzIRiZ0CapjznbieTJ0acooEHDkShpNBTSpAGGNsVFAGo7iFNxJbAKjkFWXAcAcA0AgOlXWTihYu2x4ICBtwYOmD2rB6VVACt5uIXLdy5dPFWhzuyxl2/fv3+eXtzJ1rDjvIj3LEi7+Edhx3Aj/ykg1iGAwT4uY86ouc8BAp3bASgAmfBovqUBHWggbQCBAgz86hCNOfauBa/ftvZtZq9uKcENHCfOpcE0ANABCGCdBfjo4cy/cIvOPToC7FGsG5abfQyC7uihCyBdpe1e8uXFnE9Pn338VQno6xcA/j4pA/oFKJX/f6igFqB+9hE4SlgH6reWgqQw0OB+EJrC2YT09VfhJgZiiF6CG3bSoYfcgRjiJvORyJ2GJ16Sn4rRCdBiKAzCCMCAM3YygI0AeJXjJw3YyM+PoIyIoY9EenIPiaAlqaQAGD7opCcLGImeAA2Y4R6LU/5ggJXSUUeGWww0YCYDynWJxAEMFOBmAmmWYYCZdNKZm5qjzFnnnnfi+ckCZe65p4l+XqKnoHX2WegmgSJaJ5eLQuKooJBG6sike1ZqKSONYqrppoocOqmioFYCKKYNEFoqJKIKSuqqlbRq53KwvmBAAggQcFsDn15Bpp1x1noDAwNQY6yx352xpbA5LFDA/7HQGpsls5BUGe21AiBJLSMIYIutqtsKwo231xoUriLFknuttucSYoC62DLWbiEJwIstrfPi0a290YKbbx7T8Astu//6EbDAxhJcMB/PImxsrwvD0YDD1AwQcSD3UCzlxXzsi7C/HNPxLsIbh7zHuPaae4V4EJsswsTwDtAyEWwm4OabwbrsArHkEjDzELfeLHQBDeCrswgLJJDusQgoHEWbQw+dgNFHi8CmmTlXcUDUXDtd9R20cR31z18rK3bXZe8RzdlDT5s2HmuzfbPbb1t2ANkuxC030XX7EA0BAwRuDcg0bL23m173LcMBCATu+ONi+mDz4XgrLsKXj2cueP/lJ+h9Nt2W04C55pr7/EPYZ18T+g2Nk056yTgAmjrnq3dArOuuEy5D0EMzQHvtHBSAu+tNhkBmXOQYwMDyvgNvw2TDk45jB7IjYL31uFHtPBAGRO/6p1tfLz72v28/Q/fea65p+OOPH7n5SaCf/uOVOtv+/QVoD3+z8z8OjwcMuJ8AE7c/IACufwOA3QUKIED86a+ANrhd/wg4AcY18H7lg+AKqtQ/BODLABe8n+40iAP5Rc90AAxh+yhIwh1IEHcEYOEEQKhC8Y2whTcYHekQcEML1tB6GcShChbgnMw17YEM/GH+hJgE8TxwAgH8oQyZeIUkhnCJVBwD+xr4vixdhiFpDVSdF80QDfe9aoxkaEtcnojGNrrxjXCMoxznSMc62vGOeMyjHvfIxz768Y+ADKQgB0nIQhrykIhMpCIXychGOvKRkIykJCdJyUpa8pKYzKQmN8nJTnrSGREAACH5BAkAAA8ALAAAAACQAcgAAAT/8MlJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vL2+v8DBQwYIAgDHAgQMwswwCQIB0dLSAAkLzdgnBwPT3dMCB9niIQcA3ufRAAbj7Bvl6PAA4e30Fdzw8AL1twcG8yIJ8AlMsC/WgQIDjikcYA0ENIH5CrpagEChRYsEOxiAKHCZRFUH/4xdHAlgwLUNCDjiI/AxVUiSMMFteKjynL6WphaIhEnSpAZzNc8BwGmqIk+eGTEADdptKNFR5Y4ePXlhKVNqT0cVkHq0QQaaV6PdzAoqIVeYLDGkDCstLdlPZ3mOtbCRbTSvbz1FjUtSA1imAKjm3WSAL0wNAdkmHbxpr2GLG+4xFSCYsabHF+deeBdUnmVPBDArdJuhcE11nz0lEH3MozvJ+GSm7rSANeUPz+I1HLGgd+XZglZjdv2BQTGFyRr89rDggHPnvYEXCm24AJDmz7Mvl85HJ18C229gz/48PHc9B6hLLWDexnjy0M8PEh6TuA/45NvLx7OgAYGdyTCg3/8N+Gm3HzAFlnfgL+/BN+CCsTRoIIRrLGBAAgUU0MA/WUh4wIMUckGMACSWKABDIC7R24QhnvGMiTCeuM4W0bWIxn8x5mifja8Uk+OPM/L4SgM/FumTkK3oVGSRiyGZCpFL/jiAk6z4GGWOHFJpypVF4qXlKVz+aN2XYIYZ45hklmJmjE2mGQqOa5IYpJuivBjnbXRCdacACORZSgF3zuknKAsMsGabg34SUpgIpJhoJQYYGiV7j+YEqJQ7VjpKfwiRSEABgo6BnT9ZanpKPwakqqqjpl6CqqqwstrqJK/CGuusolho666l4rqJrrva6isotQab6rCKGisssrQpCyv/s8066w+0nRS7q6zUKmLtqtnqFSy23bagYBqjxhduDwckgMAA7A6AgHLnZrJAAu3Wyy4BocYLyQHr2uuvl/pCsgAB/hY8AMABN4KQwQX3mvAh2zBcMJoPK0KvxA1XvAjBGPubqcaCFNqxv4iCHEjEI9dLscmDoJwyuyuzHIjIL7Nbssx+9Ftzvjj70UDNA4DXMyE0p3zz0Hww8LLQSBNyccc8N+3H0wYT4HAUzTGg9bRS20CMwbtdYUADZJfdgIBd23BAAwUggEABDFwdBQNm171h2oWMbXfd4OLdRn971x2133joHbjZhANC9+Fm9504GoszTrbjj5sRueSUV66C/4VkM2BA5ioYjrnmP8xLwOmovwv6CYBLPjjpNBCD+uynIyD3DZcHvjrsIRhA++8E2H5d7nXfzjsM/AL/e6PDB2788S8koDzwCKMbuee7Q+9B8tPTzrz2cTDQPfCvZ3AA3QkkEDf4Vkg/Pu0fa7B2hvRnmMDz7Bvh/vuoV+9O/QDMUPnyV4T98Y8A/jNfABeIPwIGQXwHPF38LoChBQLwaA4kQnoiyLQOHMSCAWxgBn1gwPFhsAIMAGEAJzhCIWzwfd/zANtUWL8TthAI/nnfAClQQRra74ZMyOH0dkiBGfqwADYE4g8OArz7jSCFR4SbEpuwtvSpT4QV+OARsThFLGwYUYUJ7GIYtKhCLorxCmRcIBHPuIX5BdCJbFzD+ay4vjja8Y54zKMe98jHPvrxj4AMpCAHSchCGvKQiEykIhfJyEY68pGQjKQkJ0nJSlrykpjMpCY3yclOevKToAylKEdJylKa8pSoTGUfIgAAIfkECQAADwAsAAAAAJAByAAABP/wyUmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wMFFBwzFB8LIMwYIAAABzwEACAbJ1SoHBM7Q288Ex9bgIgba3OUADOHpHePl7c/n6vEXB+Tu5t/y+QT2/AT5tMQKCGSA7wM7fvbQ/XvFYECzh80IUPuAACE/BAtbYYPIMWJBDfX/LHIDkHHVAQEdUw74OC+kyG0sS4o6mbLmyg0MXiaUeSpbzZoYNeTU2U4hz1EMfiqdiGEoUW5Gj4ZyqLSmvwz0nnKLKXUTvao1BSwAqRUaya6hkoKtyfRCxbIBgnY4YKCYgbFoFxVYWzOBhoNao2IwkC2AgsMKAgxIgDevob18O/rVsE/rVawEEGvWDKCB48eRO3rWkJUoAK4Scm5ejZhA48+A1IZ+2HawS3vwMqhmzXvAa9h9FqCcDUDsutvmBFcwYJi388vA/ficLZfDxn6oJQBwzl2B8uh5xs0WkP3CspDSvlco0J07gN/g8zALXaAEMWMeFmxv73x0/D0LULWW/2s4MMAfdwP818cBAipFIA6ZHchbAPApaMcC84VVQIUzCCChc7VZiAdhwz0kAALlxbDfh6upJ+KFBjSQQAN3+bAii5r59+IrN+J4WIg7ruKhj5qlGCQpCBCJ2FlHumKAkodB1+QXCzBQAAFYIpAAkE30yCKXU2bBAAEDlGlmmShK0YCSAoRJJZlnxlmmi0kMyWIAYLpJxQJwyiknnUfQg+Nkem5RgJ+I3gTFOB9WVygWBiSKqKNNnMRfAIQ+mgUCkiKaZxIJeImYYkZq6kSAnfqZaRRjOpNYcQWUaqoTkaYqJ6WzktKQrXFKmauuvPb6Kyq1Blumr8OGgqqxA9SXrP8pfQYL6LObNMDsg9SSEm2q02arSbGp4uotKOAmigCH436yjKSMpZsKA5yeSUCs7rZiwL2y1qvvAgf02y+6+obi78AHABxwJwQTbPDBmfCb8MAMj/IwwRHPNLG/C1dMycUYa/wJx/16/InDF4v88cUZmyzJxCmrLAnJ/7osM1p1NUBjvjM3Ai+WPM+Lc86HHHBlz0R3CzQiByBA9NIE6Hj0I0MzTfSnTxcyptRLn1t1IxhizbTRW/tBmNdLOxt2IleTTfTZijSg9tJsJ+L22z3HjUjadItr9x/Y0I2l2XsTEvXbVAeux9hvb2i4IXOr/fPidCwwuNSFQ74H3kTTu8X/ApxbrsMBCSjNcwEMtMwEXcXYZbrnKTi8+hIL1JX67K+zXofss9NuOyH35T575bvf7rvvtQffRpXD5/648Wwgn3zqyzOvhvPPEyS92NXjdz0QsTcg0IzA19B78jVu74PQAqX/ffQtxF49++ajYID69AsUvgzuDw9//CbMX3/9+1NB/lJngADybwSS+1/9VvUDknXugDvwnwLpdz8IjiEBE6yf00TAr+JZsAgZXCAJupeAEtLIgx8EQgjrN4IDyKiEMCyhAVOYgxXSTwSgi6EOEzBDGtoAgzYsAAM3sIAX7jCGKPRhDqwUxPsZ4Ig7rKASeZBAG67OiFA04RSdwMQQoN5vAVncYRK3aIMuKhBsE8hhGGHYQzLGwIwUROAakejGJlQJiEIsXQmwmMUN1rEMT5yjFP94hSLOcYyElIIaodjGRDrBhUdsQCMdeaoYwfCElHxDBzPJyU568pOgDKUoR0nKUprylKhMpSpXycpWuvKVsIylLGdJy1ra8pa4zKUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685mAiAAAOw=='
+
+blue_dots = b'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'
+
+ring_blue = b'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HGieCTBLWavP5OjocYKR////HI/jXLPc////PJTMVG6EdLTsRJrMBEF1rNb4VK33////+vz8////NFp8NpjaVF5oVJ7EJE9mBHDIdLLE////////OH60O2FxPJLcFCAnHFqBeb/5PKboJEBMHILU////4O/8VLTuDI70lJyh////////RKrkPHKM////////DEJopKq0////rLrMvMjQbK7EfKbMVIiYfKTEVIKMfJKsv8LEhNDlxNLgpMLcpNL8VHaU////PHaXXGZs////lLXR////////PEJM////JDI6vNLkFFaMfIeRHDhGfHp8vNvxNIzJpKKkXI60KYDAi9/5PFhgPGd4lM78j5OUVFhYZK7MVI68XJWmfH+H1ODwXJ7U1NLUaLrc////HE5wHG6oT2Z8dLrRdK7gVJjYBE6E////aLrsb3qE8vb4////V67gLH6sFEJk////GmKYaJq0ebrsPI68////////KEhk////LHKo////////LGKQTKbUOKb4PGKEBFaYrLK8XLrkfLLcVLr4lKKspLLEXIKkfI6cHD5MDIjqDEp0FGaglMjwbLLIbMnxTGqENFhkLI7ZDDxcrMbc1NfcHFR9DFycHIfWbMLp////THyMXKbkLDhIVJOsPE5cHDE6HEhkJGiWXGp0fLTkTJe/XJ+8LFFiDHHBHCAkLEBI////zNHUrM7sXHKE/v7+CP4A/wkcSLCgwYFztrjh4qyBw4cxuAzbMuegxYsYMXrp5KaBM2rfRIka8q3kt0RDEn2j0yAMh05eMsqcKXBXAmcEQn4j0KBOgJ9Af2qb8kZaonRv1rjZRbNpwV0dJ4ia0DOAz6A/c2QNoDXHlF5FeoUYxtTpTC+lGkglcBVrUK1c4+bomiMGkSchusU0e3HOR1EE3GLtKpcr3blz17zKsqYiX4OMGgyZ0BZrnRh0YJxMxJlIrmYx0CFGPKK0Am+hUDwe6MWNM1ENBNchUGQIjDxhprx4oe3FlDBvnhQh0kx06TIjypSJg8pTrb1md3GhNqTyzwYwEkmbMoVDgk5zdv558aJiTqdu7OKgU0AElRXl8Mvgs1fPVtmm0jVbr5OuSJ4pWFRAUwVYxOEJKqigA98DZTxQTw8k3CeTF1zAMIFbeSTyXykS0rRLNxp44k09+DxgoonEQAhdRrnQ8c1b2qSTzhTDrMjXLvTgMwYFGpz4gAvEyGDETIw4kwhQWk2hYRgCrlYQK6EQQ0EoLvz4ozvJLJJRBQ0k4hNcSsbQTIdO1lSPBhT4UqULVbrTBBQXefFGETHINUURLtlYJmtGyMIjm2xyQ0gaegrUDRfSwJWDNnhysGdGfarSBKDQPJKEJAftoqQ2hgXwSh7skPkoQV6kkowqLkCjKjSEBKOCQf7DSJPHXFwp8EoIoo5K0C7JyCADNBcAG4wehTx1J10vFBFHk7pilMI5hshzwbQX6NHGqwO5QUUMo1GRxTDNzlRIMIZMe8sAbSRhAmtrFDHaFLTEkWu4uwazjjwDnDsABszs1UkYT4z2hBXd0DuTJG1goO8AM7ShiEBYQBDCXCPEEe+8Bg+0SyZ6zDDAxwOogYRA4hBx3AhWZFFLxjMJswEpWsSshRoz/DOHJ83Eh0oozLJ8UQrbYDBACVqUsAETUKCgwHvKjWBGHD7PZI4asZRgNROxAELPE3HAF8oYrUQt0zUbbGC11fcgYYs3JTLYAzGYio2RCUyYXYITJfjQiP4ngjBoYg+hYCI3RpiMM8rdd98DRigU+GiPLI4NfhAPJYziBN5OwBHP1ye6QAE+GMutghPh3H35L+No0IOVD1BwQKGS/+NFCQt0cLkT2XSAjztWuiDMObBL7oUTC9yOwCZOaOAOoC6ockDoYo9evBMIINBBB8oz704wcMZuECtO6EN99ZvAkQaqbEIjQxOCe19QIJlXX70Sx6SyfKqVXup+QdU4gUP1RzgCPGYxCBkEY1XyeAQo9kcQfyhBCQFEwBGUoIlFPEIewLoAtILBwIFkAx4nkGAEozGHYDyCWrdYxwpS0EF+IAAeAYzhEVjxjySs4xYXOJc8SCGMDkYBAf4nCKAFAggHgQhDDyvQ1wowkAnoGcwLSgjhEYY4RF0IRBEzwBfISGGOuHnPDkcIogXGGEAHCMQLzBDax7RgDi46sVm7gAcb2DBFMioBOiZghjlkNrNYHMJ7mgjjGIMwRguYgiAqWAHMilYCJqihDywcXDmOQMdBEvII2BrIITZQtbOR4R4zeGOZdtGOI4jAAkEg5BitWBAVmCMcfThbCcLxi0YEb1ReEIMF2EDIVF4ykwQxARmqhrlx+KADGYjaBwqJSl8GwQ4W8cIz7jGO2znBmMi85Wq8oAsqOjOVlQgeD35xD8RdrgPFu4IoZ7ILMfTSlwAIAgAsUI6MWAOd1v68nD7oV89RuaIdqkxlPAFA0GrMJAPZyMb4EHA5HGwCAdhYp0F2sQpUNpOgGA3CB2jiBXBsogPyk58S/peNakj0H7uwwwlSiUqMurQA2tzVLD4KwJCCEAFKiAI/aMKPD+QjoC516Tti+pRZKAEeIpQhEIMowHnYARE8UMF4VMADRNgBEitVZVCDCtPHeOGHSkgqFQM4RzrW8Z3OtORWXfoBol7ED0BEwFgLWccp2pWZal2rSw36KB744AgSxGsvm9lMZ+o1qEvoJy5VKkSLsvSb8pTnYTNqCrc6RQWrkOsUIRvZyQaVE91jmQpMAQ9mClSyngVAPgQQWrF5ARG6gCRHPDs72Xx8ABGWpRcU/KALMbQjHxYgqAVE0I4CcOITPHhUQAAAIfkECQkA/wAsAAAAAEAAQACHBBEdFIzkBFCNjI6UTE5UVI2fCm66Bi9MXLLUJE5klMr0////JDE5RKPcVKLEdMnnNIq8DD5gBB8zzNTYRHB8BH/hJF6EBFmfbLjUbKLMb3V8JHS43Ov3jLLUFCAmNJXcBHDIbIykBDBWRH+cbL7c////BEBxrK+3HFqBJEBKOmBqKZfpLHqs////BBYkBGSyBCI8bLPqTK/nRJrMzODw9PX4FC9CZKrBlL/h////HE9xZL/p////////XG58FIHRJILEOUFJJIzY////NG+MbK3AbJe4HG6pxMzURoyxHEFXFicrPJvXB3bLFBocFDZMZLXZJGiSVIWW////////////////ZKC0gtHqTHefdIGP6/T5FjpU////JEZUfLXnKZLfjKK0OGqQBChG////Gne6SHeIfIyYBTZb////JFd3FSo2XLrpDCY0pLLEfMDUWK7cPGd3DBggHDA7pMLcTKj0GYbZJIbRPHakfK3UwNvvRJ3W////VGFs////JDhAvMbOpKSkrMzoRGZ0VHqcPH2fjJqskrjZ////vNbslKe3XJ7U3NrcfKbITIKsNldi+vz8hMrkJ2KR7O7vdKrYfJevLHapPJLE////////////ZJqsVGp8VIKofL70HEZk////LFZkXKbE1NrkBF6nbKa8TIac////NIKsdLr0TJ7E1Ob0LIbENEZUPHaURJK4////3Ob0fIaUxOL8qNL0WLXoLHKoNI3JbMLpGVyP////////////////VJbU////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bGQjncFdBgzgu1WDBBK04IU2rOG+qW2DHKixXzn4t73EiEdz81UPN8iiD+cgRxskS401BKEPWjgBZ/oUEAAh+QQJCQD/ACwAAAAAQABAAIcEER0UjOQEUI2MjpRMTlRUjZ8KbroGL0xUtOgsT1mUyvT///8kMTlEo9xUosQMPmR0yecEHzM0irxEb3zM1NgEf+EkXoRsuNQEWZ5woMRsdXwkeLTc6/eMstQUICYEcMgEMFY0lNpEf5xsjqxsvtRUoeIEQHGsr7ckP0g0XmwUXJD///8seqwEFyQkl+kEZLIUL0JMr+cEIjzM4PBss+n09fj///88lsQUeMgcUHJkv+k8pegUgdFsrb8kgsSMwvQ0PkxUmrw0b4xcqsxccHwWJytRgakUOEwcbqnEzNQHdss8m9cUGhwkjdhktdgcQVckaJIWOVT///////////8bZZ////9UlKxkn7GC0epGjLH////r8/krR1CMorQsV2partc4apAEKEZ8tOj///8VKjYad7pEdowFNltUhZZ8wdf///9EndYpkt5UYWxcuumkucx8jZy8xc48aHgMGCAcMDtMqPQbhtt8q9T///88dqTB2+////8MEhT///8kOECkpKSkwtyszOhEaHRUepx8oLw8fZ+MmqyTuNn///+81uyUp7fc2tz///98gYY2V2L6/PyEyuQnYpHs7u////98lqxUqvQsdqlapsj///////////9Uanx8vvQcRmR8psz///9IdoTU2uQEXqdsprxMhpw0gqzU5vRwuvQkWn////80RlQ8dpRMhrT////c5vREkrjE4vyo0vQscqg0jclswukkQlj///////9UltT///83nekEOWYMhuRveoQsfrREquwMNkystsRkprh8hpT///9MpNdcob8UQF58yOAMIClMcnx0uc2UrsRMgJJ0ipxcoNgcXIw0fKT////U3ul0s+ccgs2Uwec8b4ocN0Qsjdf///9clKeUoKkMKD3////ExshceJBMVlxkutx8uulMnsQsXniUmpwMXpx0pswsWnjk5ugcitxctug8i7fU1tQMgd4MWpwseLjk7vk8ltR0wNj///88YWpEmswcesREpeQshMI8QkhclsRkq8f+/v4I/gD/CRxIsKDBgfNkjSkRQpsPa9Z87KOVq1qgeQczaty4EdKeam3aGANTj9kFf0MwYfLX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qqz///8Eedbc3eBMlLQ0UFx3v9f///8YXIwUMEH///83NzkHDxT///8caJ6E1+0LHylso88sZpQcj+MUSWwJMEtcrdF8yOHc6PRkmqxxe4T///88oPT///88lMx4s+ZMl78EQXWs1vhUtOxUnsR0ssT///////98v/b///8kQFBUXmgkT2YEcMj///////9UboQ4frQ7YXEUICZEquT6/Pw0Wnz////g7/x8e38MjvSUnKH///////////+kqrQMQmj///8slOI8pulUgoxUrdwEgORclaasusy8ytRsrsR8psxUiJh8pMT///+/wsSE0OXE0uA8coykwtyk0vz///88dpdcZmyUtc7///88QkwwcJr///8kMjcUVowcN0a82vFUuvdcntQ0jMmkoqRcjrQpgMCL3/n///88WGA8Z3hUmryUzvx8ipePk5RUWFhUjrzU4PBUq/fU0tRoutz///9kuuQcbqgcTnD///90utBPZnx0ruQEToT////y9vkcWoQsfqz///8UQmT///8aYppxgpH///9cuuQ8jrz///////8oSGT///////////8sYpQscqhMptQpjtk3mtxcxvw4pvg8YoRUpswEVpissrx8stx8goyUoqykssRcgqS80uQcPkxsuvxUmNAMiOwMSXQUZqCUyPBssshsyfM0WGQMPFysxtzU19wch9cMXJxswuccVX0sOEj///9MfIxUk6w8TlwcMTokaJYcSGTk6vBctOBcn7wsQEgsUWIMccFcanQcICRMretcruwMftzM0dSszuz+/v4I/gD/CRxIsKDBgXD8Xdm2yZMGDfcepkE1aBGcgxgzatTYBtOVZcuwQAMHLgkXLO3a/fnTzoYjI0osJGqzsaZNgbfA6GEGwQc0K+buORjqoIVRo97gOeKjCBmyQrduSi2YE4aTJ04+jCjDtQzRoUdbRLtANoYSDGrSFZI39WabbzDmPdnDb4SOrVwdeC3agm+0vxdqDRicTk0HWJBots0IhYo+RGx0SL5bBi/RvkcBkxU8OEsWWGqIPUu1+CAjGIiAcdPhR7Jdrvc2BcNCz4LtP4RcwjtX68LgAZ5PnCBjb5e10gPbaNtzpIGf56zt8iMBARyXYLk0HNh+IGmypeyU/sQAngWP8BPjenDIoLjtLVWZEMl53trPCHNOZClwyOUbtgq3tNEGC1BgA8kfQADhBgaipJPFeU00cQID2aAR1VS36ONCOAHQ15oK88gCFC2Y3JSCBemkI4oaTEAYYTbZzHKhTW2oMkE4fnRInwJPWPHBNzNKdQsksIBGxgkRItkEB+/M0t5G2mTCYYcdqsAKBCr4EORitxQyjj09jBNhEwg0EUQQvtjECBtHBODmc0s8wcYypCFXUAo0xGOPmGQigMAlCNyxERwNfCLHmwHEyYwyW9qJExq7MMBBn36+g8AOGbXhTHOHdrgEInps8KSjBLWRAQcMUIoEAkG4M+pA/qRk0oybHXKDyAwbkLpRBk2kigASwAJbzUG3FNqpm+iwocqrupaKRhAy+BnsGkGwVRAYE+hDqx9soKNHo81SRcwlQQQbLDVUGUurHEcsUUG4NZXz569IUEABEmuwQFACzThzbLJgwGtTKQi8U+/BFJiSnLpuorYEuAJTdYnB9RJxbyWKVeDMBMe64Mw3EdtUDRJB3EuBxRQcIpA2za3bLsQhlxrEGvWebK8IAhVKawD67KNNzDZJgcAa9hJhcSX/wLHxseHo8S7QG/WDLxIWG01EKlpkkonLy0Bt08wnW00EDmCE0wCtVLiyj9c14QOs2ERIoY+hb27BBshsazTH/r1wj+HMJzu7MEOJeWd0CN9GAwDAEA20+eYTS1xUOEapEEF14kSs0cCUHSKiAsyFs2C51YpTsPmbfiDCDbOT/9NG0UQorvjpOabOD+uTt1F17LJvXnvqn7d+EAv2AsA7AKYjoqMfT6ggufAE7VC17ADkQ8URtevgih6EQz/QIRZTD0AlqihPXx8KJOA9QaaQLnsBZS9B3wxOrL2+QI8kTn0cjOizB3Tm8IE57ieQEpxMfJyAwwyAQR8dCPBp3utH+MSHqQY8oTWSUYYCrnA/KehPdvkQyBXQIT/JBPB23rvFGj6oOJz9AxN7UAYGRwCBTeBNeHNwn+xU5jo9XPAu/iNYhiDMAbqQsaAELKxee77RB2aM4IllEMQmpiE8ahABBOIDgAAIwoIl+OA1I/CEIO4BQbatImxZtNZArtDEynAFC1zwRBGbdQt1THB/BrnFB1iBF65YYBOowF2z2jCG6VGPCGokyDcUoICuOEADFtBAErwmgqJlUWEHaYMVhqiXoXjCAq9IgiCR0wYpwO54i8NdBT4wxq/kIgqvQMUcMVRIS4qvHBtBwSa4sBejaCAKk0hGCsJVDnXULImcsAktgsGDy7TgFbk5B1QcdYtS1ItoOgSAC2vSBixgIRiYMco5bEAIePQCErOkSjUMRrOwobIAoyTILUjAA3CGJRrw/mAHBoBgiSjg0ib98EW51mDAUxoPAK5qSxu2kYZ2+IUs0VACO9BAg26k4xrVCAQUAtSGW+wgENVAgxj8VIIStNOdvINnadqQhGS0wxt/GQtncCEKtNAAFuYZ05jO9I6enhSlxhNBPDWyCCMQwhFjCQxnPAOLDohDHKHogT0YwIB6iEEGlqLXMd1JgWHpCgppmAQhetGb3wQHQieYVISkRS97Fa1okvjnICFhBEVgABedOauSxuSnvgLLZG69lynSORUWFAIIZ6EBcNAqIb5qtWZ/RYIvEhmyW1QDHrhQg2HMs1dK0etX9AqCKSgLtTYkIgo0YAJx7GEPYmSDA5fgJMCZzgQsGUjhAUONmTwAEQU0uCEe45gUB8RADHxQwxpQ0FVAAAAh+QQJCQD/ACwAAAAAQABAAIcEBQgEhOqMiowMRGg8rvRMVFwPY6REgpwEIz4knO+EzuRsr8YkQ1QEZLFEm8wkJiTIztQkhswEMlRkx/cETIYqerIEEhpku+EQdMRkm6nc7flEbXk0YnwMkPZ8hIwkUmwUIywki9ZZwPwppvwENFysxtwEVpQYGBx0vdJUsur///8UhNiUqrwUUnxMouwEdM9sbmwscpwEGiTE4vx00/wcRFs0mtyE3Pc0ksw8TlwcMjn09fU0U1wUV4cEKkQkSlxMeIccLDMUa680grR0yuhpp7x0uvRUuOwEetQEDBMEivVEZmx0tsxUpswWPlxUkagcO06MmqQKSnocbqREhqQEFSZkwew0boQ0ldcEWKCsqqw0eqTc3txMtPD///8UUoQ0fqwcjuc8p+r///8citlEpuT///80ovFUfoQMiuxUhJIpovpsr+YEbcFcmqwkOD/U1NgEUpQofcEcdLTr9fv///+s2vz///+M4/w0XXAEK07///8MDg9UlrCkpKgMg+GsusyQvNz///+80OD///88PkT////AwMQ0MjT///8kWITE2uwkluyk0PCUttTU5PSUlpyUprSUy/c8doz6/PwcSnDs7uz///9Usvx0tuxUirRUpux8enz///////85irxUosS83vxkorhEtvxQWmQkisxswuT///94ipxMlrzM5vw4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i7GELEQcihDKF1rQA4pYBCOjCggAIfkECQkA/wAsAAAAAEAAQACHBAkO////BEiAjIqM////TFBUTIac////CSc7jMr8RKDXJCYoK0hR////xMbMJJ71bLTJ////ZMf5KonK0eb5ZqCyJHe3BBYjBFiabImc////DDNLB3bLNFdfbLXsKI3YNGqEFBka////bHB0SJLI////pMrsBDxpFCcvzNrk7vDxBHnXFFmMF0lqBBktKFh+ZMDvJJfqKXapJDhD////dNL8////UpSyNJ/nrKqs3N3gZKrESHqMNzc5FIffUKzc////dMnpHDM6VKfsBw8UVLTnhNjyBG/EdMDXOJPK4efpBF2mZJmnKmiUcXuEFCw/FHjCd7Pm////NFp8NJjc////R2Z3rNb4////BCA3JEFOPKL3XLrkVIeXWZ+8JE9nfLLcNIa8dKbUVG6ETJbAHC40////BX7b4O/8HGaXBC5MDB4kFCAnHCgx+vz8F094////HJDk////////FHC0JG6kfHt/lJuf////////OH60DBgdMmByPJLcSq7upKq0rLrMvMrUNHigVH6M////VF5ofL/4v8LEhNHmxNLgpMLcpNL8////LH60PHCIlLXOGWCZPEJMbKLQbKrcJDI4XJanvNrxXI60////pKKk////VJjQFILUNHGYlM78fIqXj5OUVFdZVI/BXKvOBHLN1ODw1NLU////ZLrn////GX7MWKbMF26s////HDpKBDZgDF6cKJLiDEJo8vb5////PKbp////TIKcit75////cYKR////dLrMPH6c////RIqwKEhk////////////////////XJ7U////BIHm////LGKUXIKk////DI70LI7Mb3Z8ebrpOGKArLK8fIKMlKKspLLEvNLkbLr5HDNFDEh4lMjwTKDQLJ/2bMjwDFmUFDNFPGZ0TKr0rMbcDDxf1NfcHFaEbMLnLJbkLDhIfNTy////bKu8HIfZPE5cfMjhXKbkXLLcDG64HHe6PJrYDCEuLEJMLFFhPIi0fKXHXGp0HCAkLHCkPHaUzNHUrM7s/v7+CP4A/wkcSLCgwYFKAGUYNAXYuA0bvrlqZ+WTNCUHM2rcuNFNogzKWnyx16EdAwZaZrhSSekJijUznDhww7GmTYEq8IEoBw7ciy+yXr1SQ5RoFgRZjqJAwQbmABU3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LCcwLHxWmdCAwmhNe1oZkEMYwpiCNxJhUkxJohbVmIEuDuALaDhgD80Qww+w4QNYeEAYZ/CGNzzAB9jySgO2yMMPdHGPLdRjC0c4wjGwUQ1BmO5JAQEAIfkECQkA/wAsAAAAAEAAQACHBAkO////jIqMBEiA////TFBQDGisRIuvBCdEhM7lK0hRRJ/XJCYsBGi4xMbMKJ72arLIZMj8BzZb////0eb5JHe3CxcbBFCPLFdsZKCybIacRG55BHbMbLPpJIzaFBkaFCczcXR3NGeCUJSy////BDllpMrsRKbkBHnX7vDyJlh9zNrkJJjqHFJsdNL4BB40BFyiZMHz////FJL0JDdE////THyMLHWprKqs////NJ7mFDZJ3N3g////FEhsdMHZ////Z6vC////////hNPq////BxAU4efpCx8pHGCSFD9ZWbPgVK7cN5PMOGFxdLTs////VKfsODg8////JFBpGHK0dLbMbJKsVGyA////rNb4BX7cPGKEOZnZVJrBdsns////O4e0fKXHOJLYBj9r+vz8KGCA////4O/8V730lJyjVF5oVJ68////OH60////HHe3////////XJWkBGGu////////////bLrWKZLfpKq0er/5PHSP////GU5x////rLrMvMjQVIugVIKMfJKsv8LEJDE6////pMLc////////bKPPdK7clMjwXGZsKH60////lLXOvNLkFFaMfIaRfHp8vNvxNIrHpKKk////VFdXPFhgFEJkj5OUPEJEbKrc1ODwNDI01NLUZLrkFFqERJrM////hNr3////VGZ8VKbMGX7ICE6EDC5ETKbY////////b3qETK7s8vb4////DGKkPKbs////fM7kerrqdLrMaJq0JG6kPI68////jMr8KEhk////GVWCTJPPFInl////FGKk////////BEF1LGKQTGJ8////////DI70LI7MNqb2////rLK8lKKsXJ7UXJqspLLEPHqcXIKklM78fI6cbLr4VJjQDEd0FGehDCg5TKDMbMfuNFpsTGqELI7cHCkxPGd3rMbcTKrv1Nfc////fNX0DFyYbMLoHJPoLDhINHOcPE5c////HDdFHElkfMDUjNXmHDE6HD5RXKvPfLLkXKrkLFBgfLLcXGp0/v7+CP4A/wkcSLCgwYFHcMTSJAXJBwAAjHyQoikW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aYlTtOQUVDhDAuJRjGfY057P4AMtzqYRawYhHsqop0SV8YxiHOEF+tDHLhIhCgzgwDE4wIAoErELfZzhBUcohjXOIFGJ8iEI8OwKJ6iQCnKMo6US7UY8pmHMbiSACBL9AhGS0I0gHGEaR3gGUHFKhCOkggrjNIgjEoANdLT0CxIlAiL4cNRpePWr0yBHB15w03piFasBeAE2EuBLFmGgG+goBiIC8IW61pWuZ+3DOBKQgHEs4ayAvesXEuAFdHTjlE/iRCISUAsvzNWukLWrBjQQ2com4Ai1WIY4OWUGNCyjFvZAx18jS1nJfqG0de0DAeyRWTSkEVhmmFQEKQgwA3vYABFLKC1lJ2vXPpjABjPwAgGWMYnX1osTLkADKRABXB7YwxgPqEUtHmAMe/BgBrclBRpEEdUxmYEOaAgCIhBhgvKaYLlBQAMdECuggAAAIfkECQkA/wAsAAAAAEAAQACHBAkOBIfvjIqMBEZ8////TFBQR42xBGasBCZDhMvfRJ7UJCYsKkhS////xMbMZ7PQKZ/1////ZMb4KInK0eb5Z6GxJHe3BBYjRG16bIecBFicJFZ0bLPpFCcxBnbLJI3cbHV8JGaMSJLERKTkFBkZkazBpMrsBDll7vDxzNrkKVd/BHjX////dNL8ZMHyF0lqRHuRKXapFJL0////ZKzE////BCpMBBktrKqsJDhCNJ7kGzA43N3gVbPjTJK0dMfhBw8Ub7/cGzhIGYfZh9ToVKzc////4efp////JGeUVKfsVIiYBl6jdLTsdIKR////rNb4F4LUNzc5VLv0CXC6PJbMLIC4VGyATJi+Bj9rPGKENJjcFD5UBEyHfLLcfKXHNFZcPJLc+vz8////KV+ABn/bBCA3NKH3////////4O/8TGJ8FGagDDBEV6HBKVBjPIasFHbEJ5LhFCAmGU5wGY7olJyhHCkxfHp8pKq0er/5VICQZK7M////////////rLrMvMjQPGZxfJKsOHic////v8LE////M3GYpMLclMjw////////PHCIbKPPVFhalLXROWBxJDE8vNLkfIaRWI6wvNrxXGZs////pKKkSazuHGGSPEJEFHK0VJjQFEJkj5OUjMr8VI68XJSk1ODwdK7c1NLURHKCZLrkZ5qx////dLrIGG6q////F37KfL7UbLrY////DB4kcHqE8vb4TIKc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2uTu8PEEXKQ5WWEkl+kEHjQcUmz///900vhkwfP///8UlPQkN0QUNkksdan///////9Eeoysqqzc3eD///90wdlnq8L///8UP1n///+E0+p0x+cFftwcYJIHDxQUSGzh5ukLHypZs+AkUGlUrtw3k8xkmqwmWH1xe4Ss1vj///84ODz///8YcrR0tsx4s+ZcrOg5mdkUgdccaJ40hrxUboR8pcc4ktj///8GP2v///9UmsD////////g7/xXvfRUXmhUnsAEcMcsfrT///8cd7cUICZclaRMrun6/Pz///80XHQkQUz///8pYIJ8e3+Um59sutYqkt8cKTGkqrR8v/Y8dZGsusy8xsxUi6BUgoz///////84frS/wsQkMTf///+kwtz///////90rtxcZmyUtc680NwUVoxso8y82vE0isekoqT///9UWFgUQmR8ipePk5Q8QkRsqtzU4PDU0tREdoxkuuQXWoREmsz///8ajuKE2vcZTnFPZnxUpsz///80MjQIToQMLkRMptz////////y9vk8Xmz///88puz///////9xgpF0usz///8kbqQ8jrz///84YniUzvz///8nSWT///9EcIgZVYJMk88UieX///////////////8EQXX///8YesgMjvAsjsw2pvaEzuSssrxcntRcmqxQrvh8goyUoqykssQ8epxcgqRsuvxUmNAMRnQUZ6EMKDlMoMxsx+4sjtw8Z3esxtxUqO7U19wMXJz///981fNswugckuQsOEgcN0U0c5w8Tlz///9MfIx8wNQcMTocPlGM1OZ8yOQcSGQsUGBcq898stwkaJg8h7JcanQMcMAcICQsQkz+/v4I/gD/CRxIsKDBgUnEfRlmqEIuN24qsAGjDUuJMwczaty4UQ4kSfnysdMBpySJk2p+/IBwT1QzcFgmyeFIs6ZAFV9E3Ak26E8uLiuCrmhAlOiaZU5Q3QKlZU8CWDajFsRpilcwEaEuaL0gNGjRBmvCht3SBF6WPX5USLUp51E9DeGgDJi7lSvXoUOLiuXAAQUKLUSWtOg1c61GHrx+xXqRLNncAXW74tUblq9fFEW0PDsxC6Nhg9Yw2WMiZkTjx5AjcRkpCI60DRvUkNFFaVnly0VyLyHSQsrngXIyBGmHKYJpMadX3dHRkx0Xbgaic7PCRhcZCBC8bVnjN3fuFudm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pdaq1OQMTmyhHeAzFg3ekUD2pbB7lTWrwg5wL80qwRnD0mgGNHo6lUtAQot6xCtlxaba1f5KCSRYRgLSdQmrXWIADGCB7/4QuuYGTmocS2sTBlsCTFw0VpMAxDAq4bskouG7aKCbHYxQCSMYIV/TAAJt+zUPOUACDrdgwDk2JgMjLMMTc1jHPDYVEAAh+QQJCQD/ACwAAAAAQABAAIcEER0MiuQEToSMjpRMTlRMiqQEb8NMsuwGL0wkTmQnb5yUyvQkMThMn8xstM4MP2EEHzMEXqQEf+HM1NgyjckkXoBsorREcYFsxuwkgMBvdXwsmuSMstRsjKTf7PgEMFdywNlUoeIUICb///////9EgpwEQHGsr7c0UF4kQEwEFyQsdagcYJA8YGpss+osjdlMkrxhtNgUL0Jcqs8bgMkEcszM4PAEIjyUv+H09fg5QUk0fqw8m9lcu+pamK4sV2psq7tsl7gXOlQUOEwcTm/EzNRcorwcQFdcbnwYKCsUGhwMZqQHN1n///////////////8qSFP///8ahtj///98yN50gY8aeLr///84dqQ0aoh8tec8pOckl+qMorQ0cZE8jLgkZozr9Pn///8EeNZcipx8usxEepR8jJgEWJw0WGQkerQZZ6AWgNcEKEYaWIAWKTf///8MEhVsutYkhsykssR8wdQESH+8xc4MGB86Z3lctegcMD2kwtx0qtjB2+9UYWykpKQijdqszOhEanRUepx8pshcpOBclsSMmqySuNm81uyUp7f////c2txUgpT///9cntRJeIp90Otcrtz6/PwkOEAsYpTs7u88nuxklqR8l698vvRUg6xUkqT///9Mptz///////9EkrwcRmAMdsRMptQMJjTU2uRMhpx0uvTU5vQ0RlRkprzc5vR8hpTE4vwkkuSo0vRUanz///8kUG8EYa3///8kdbf///9UltT///8nWHb///////9QqvQkSGAUecf///8MhuRseoQMNkystsR8uu98kqSkutD///88XnwMcbxUoMZ0tcQMHygsX3h0x+QsgsiUrsR0ipxMgZf///90s+lkrszU3uk8gKREnNJkvOb///9knrR8gIZEptyUoKc8coxEirD///8Md8xMd58MWpQ8WmQMJzwMSXfExsh8rdRceJBMVlxckqQ0mtw8apQserB0uswshsKUmpzk5ugM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/0mSyaTK7AQ8aRQnL8za5O7w8f///xdJav///yhYfmTA7yl2qQQZLSSX6////yQ4QXTS/P///1KUsjSe5qyqrNzd4BSH32SqxEh6jITY8lCs3P///3TI6DqTyihplFSn7Dc3OQcPFARvxHPA1wRdpv///xwzPv///zRafOTo6HSz6XGCkf///6zW+DeZ2f///////1Sz6BR4wv///xaC1AQgN0dmd1SHl1mfvCRPZzSGvHylx1RuhP////r8/BRPeP///yRBTiRupFy65JScoxxmlwQuTHyy3Dh+tAweJBQgJ0SazBwuNASA5Hm/+RyQ5P///////xRwtODv/P///wwYHjJgcjyS3KSqtBwpMRRgnKy6zLzI0Dx2kFR+jHySrFReaL/CxITR5sTS4HSu3KTC3KTS/P///2qkzFSX1Cx+tDxwiJS1zjxCTCQyPLzS5HyHkXx6fFyWp7za8VyOtP///6SipP///zRxmJTO/I+TlFRYWFSOvFyrzgRyzXx/h9Tg8NTS1P///2S63P///2iatBh+zFimzBdurDx+nP///xw6SgQ2YAxenGa66ieS4v///296hAxCaPL2+P///zym6f///0yCnIre9////3m67P///3S6zP///0SKsChIZP///////////////1ye1ChikP///////zR6pFyCpP///wyO9CyOzDhigDym/KyyvJSirKSyxHyOnAxIeJTI8Eyi1Cyf9WzI8AxZlBQzRTxmdEyq9KzG3Aw8X9TX3GzC5////yw4SHzT8Tyg5ByI3GyrvDxOXHzJ5Fym5AxuuHy05Fyy3Bx3uAwhLixRYTyItFxqdBxRfCxCTCxxoRwgJEyWwAx+3BxhlMzR1KzO7P7+/gj+AP8JHEiwoMGBeKTMIbINx4OHD7ZNszJnEZ6DGDNq1DgG0y8ZOKxI8OHjUBBu3GqQ9FEDzbRtczCN2UizpkBcc6agI3doli4ZO+QEGEo0ALQHBCT0qkFgWwJcNqMWxPWOFjx2P2jJ2cpVaACvRYc+eNFLwoM5UKXWHJPtww8kcPZ1nbv1a9ii0Kb5kLDN00y1GfHwgQO3gxzDiOnOvTsUWgACPlxeBGzQxCoIrDpo3rx53xRt8IKwO0T63AsruugwNoqmxrZFlAeOSTQBwoQjo3J3GNVhHzok7ODpmkJnR5YAO+jg0DXr0DkrDxg/8EFg1l+1uByV4HHniPfco9L+iUKibUKWdtkw4cE1ZgwuPJiywaNDy4rJbXehtQSXNiouffp04d2AR1zBCgR8rJJIBTZV4AE06LDDzjZgDfWCBPxFNYYjw+iThDvuJOEdPRAIsYoi/fnnCR3oHIKGY0Wh8QI4123UCD03iCjiEe500cUd76QIGE47vMCOVnYF8AIas9SkCAyXfChlK13Q80gpsRlUAS26HIKDUF5xMw1sGlVwxiWyJKFmEndY8IgjQmZ5ExG0HIIOV1+dI8NkB43RBhdnrJlEK5dQ0EiNchI0xhO0sPMlVzIEgQ6iA0XSiSsYYLDmJWc0kuhGHtSpFVfggJPNQSqowUWmrB7QTCj+lH6qKBERysUVOzLECYYgQwyQ6QD48HJGnLJO9QE8qBy2FQ7w/FKQCvz04Kuv3XDxB4PFblTKPkhMoWwH5NCSIhggKDPAtLyCkW1NvwiBRGL1nSrQGMqEce653fTAD7HrTpUFBOgkhsQHfyHiwh73DgCJMpH0W1M2E7y7GTpTYCKQKnuQcS8ZPdjDr8ME4XKZEJtlgYoHArmwxS23DHCLCy6oAnJNTwghim6jeBDNP06wMM8AJ5wwwDdkpDAzTRVcEcxuvCkQDR6AeMFC0Cfc8o09R9e0ijq34TYBHP4QMs8tVLPggjNZ05QMgAN2IMo7PXAQy9wneOFFNWlvlM3+I6wQyIMjTawz99xSB5K3RpjEo86OSfAQDQsLrBGL5OucoMPhgbmjTojeOfIILMSsIfka84ijAuYYqZCEBXdwfkM8G5ghuuiCx4r6GKu3ouMwsMMy+xqR24457hbo/iE98XgDCwJraAH8Gh8fjksSlxifxDBnwEJD86LLjiXqBjkhC5prDkPBOks4r4UWsHhjOPgFUfJHJpqqmUkbW9CwvvPeeIM3/ASJxBAOwCoMHCAUlUABAva3hjiEAYAEyUQnBFHAA4CBGgiQx/4QMAlYQHAg0eoVBgaghmYowgnyQEEM9hcHBPTjgynoxiC64SsMDIEfF6GBClcYAxSgAAr+H1SFMiCRsB7gQyC2eIMWYsBELcTBG9Gb2RjIAAmE1RASMvuHA2LgBiYyEQXySAMAI2EPSGDjXi5QBiLmNYk3XOACXkTBEqLYL1zYYw9NaNm5FnadT+jhjYB8gxsqAT5C2GNlQBsAC/agroGowA16MMIFjBCDCyjRFJhLwS324IWgAU0aZDgdQaAgyVJewA0hMAcdP4ULF3iBA1Q7ATZcQIjw5SOSpoRkIYQnqzEMQhwcEEcsOWAPURYkDaU0gjIv8EdbZC0DG1gHCwYXCxZ4oWF9koQRAKDMZf5xBLyMzRgysIZ1mGFyg+NAE4Q3jkhys5tG+GMhVumfMGzgHrL+m10sFnCLy2lkE9wEgEC7GUlzvFBW4yCGN+7hu9+xABbWqMkIBErRgUbyAqSgJ0ZwcQ0ELIEG3mDe7GABiwzYZAwFqGhAlRlJP6RBowPBRTVo4NHtaWGBzVNeGMIZspSqdKDxNMIbbHFQmvQDCmD0xiRQsD6cakF5W4DpP3Dh059uE55+qMcnDKED9oxBBTowRBrqsYQYyMMPfmDq/vbXv6hSZgwT/SlQg7rMbgISjnp4AwreoEE4rnB/S0AAFHiqEWTKtaIEjaceFhuCELjhDX8EpBeZmMI19AGhRTgsRa8KT1NK8q6VXCEY1+HPXn5im5pVKWft+tk3PhYF1SBJrFpUYInUInal8HyjHh4LBWOCTAUCyIdtN5vbSE7iE77N2hgMUQ8GaPaq7zTCJEbgANk6TAc5GEEBihAC1ObVHKCAQh9Km6WAAAA7MGJrRXo5Y2lXNnVmU0JEVzFsWjk4YnBTejVFT1Mvbm5iRHBCSDJBNE5ONjFRSXU2QzdKNElOS3RHaFgwemlKbg=='
+
+
+
+bar_striped = b'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'
+
+# list of all of the base64 GIFs
+gifs = [ring_blue, red_dots_ring, ring_black_dots, ring_gray_segments, ring_lines, blue_dots, red_dots_ring, bar_striped, line_boxes, line_bubbles]
+
+# first show how to use PopupAnimated using built-in GIF image
+for i in range(100000):
+ sg.PopupAnimated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', time_between_frames=100)
+sg.PopupAnimated(None) # close all Animated Popups
+
+# Next demo is to show how to create custom windows with animations
+layout = [[sg.Image(data=gifs[0], enable_events=True, background_color='white', key='_IMAGE_', right_click_menu=['UNUSED', 'Exit'])],]
+
+window = sg.Window('My new window', no_titlebar=True, grab_anywhere=True, keep_on_top=True, background_color='white', alpha_channel=.8, margins=(0,0)).Layout(layout)
+
+offset = 0
+gif = gifs[0]
+while True: # Event Loop
+ event, values = window.Read(timeout=10) # loop every 10 ms to show that the 100 ms value below is used for animation
+ if event in (None, 'Exit', 'Cancel'):
+ break
+
+ elif event == '_IMAGE_': # if clicked on the image
+ offset += (offset < len(gifs)-1) # add 1 until the last one
+ gif = gifs[offset] # get a new gif image
+ # update the animation in the window
+ window.Element('_IMAGE_').UpdateAnimation(gif, time_between_frames=100)
diff --git a/DemoPrograms/Demo_Graph_Element_Bar_Chart.py b/DemoPrograms old/Demo_Bar_Chart.py
similarity index 100%
rename from DemoPrograms/Demo_Graph_Element_Bar_Chart.py
rename to DemoPrograms old/Demo_Bar_Chart.py
diff --git a/DemoPrograms old/Demo_Base64_Image_Encoder.py b/DemoPrograms old/Demo_Base64_Image_Encoder.py
new file mode 100644
index 00000000..bf7a6b44
--- /dev/null
+++ b/DemoPrograms old/Demo_Base64_Image_Encoder.py
@@ -0,0 +1,40 @@
+import PySimpleGUI as sg
+import os
+import base64
+
+'''
+ Base64 Encoder - encodes a folder of PNG files and creates a .py file with definitions
+'''
+
+OUTPUT_FILENAME = 'output.py'
+
+def main():
+ # folder = r'C:\Python\PycharmProjects\GooeyGUI\Uno Cards'
+ folder=''
+ folder = sg.PopupGetFolder('Source folder for images\nImages will be encoded and results saved to %s'%OUTPUT_FILENAME,
+ title='Base64 Encoder',
+ default_path=folder, initial_folder=folder )
+
+ if folder is None or folder == '':
+ sg.PopupCancel('Cancelled - No valid folder entered')
+ return
+ try:
+ namesonly = [f for f in os.listdir(folder) if f.endswith('.png') or f.endswith('.ico') or f.endswith('.gif')]
+ except:
+ sg.PopupCancel('Cancelled - No valid folder entered')
+ return
+
+ outfile = open(os.path.join(folder, OUTPUT_FILENAME), 'w')
+
+ for i, file in enumerate(namesonly):
+ contents = open(os.path.join(folder, file), 'rb').read()
+ encoded = base64.b64encode(contents)
+ outfile.write('\n{} = {}\n\n'.format(file[:file.index(".")], encoded))
+ sg.OneLineProgressMeter('Base64 Encoding', i+1, len(namesonly),key='_METER_')
+
+ outfile.close()
+ sg.Popup('Completed!', 'Encoded %s files'%(i+1))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Borderless_Window.py b/DemoPrograms old/Demo_Borderless_Window.py
new file mode 100644
index 00000000..38a4a287
--- /dev/null
+++ b/DemoPrograms old/Demo_Borderless_Window.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+Turn off padding in order to get a really tight looking layout.
+"""
+
+sg.ChangeLookAndFeel('Dark')
+sg.SetOptions(element_padding=(0, 0))
+
+layout = [[sg.T('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User 2'), size=(20, 1)),
+ sg.T('0', size=(8, 1))],
+ [sg.T('Customer:', pad=((3, 0), 0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20, 1)),
+ sg.T('1', size=(8, 1))],
+ [sg.T('Notes:', pad=((3, 0), 0)), sg.In(size=(44, 1), background_color='white', text_color='black')],
+ [sg.Button('Start', button_color=('white', 'black')),
+ sg.Button('Stop', button_color=('gray50', 'black')),
+ sg.Button('Reset', button_color=('white', '#9B0023')),
+ sg.Button('Submit', button_color=('gray60', 'springgreen4')),
+ sg.Button('Exit', button_color=('white', '#00406B'))]]
+
+window = sg.Window("Borderless Window",
+ default_element_size=(12, 1),
+ text_justification='r',
+ auto_size_text=False,
+ auto_size_buttons=False,
+ no_titlebar=True,
+ grab_anywhere=True,
+ default_button_element_size=(12, 1))
+
+window.Layout(layout)
+
+while True:
+ event, values = window.Read()
+ if event is None or event == 'Exit':
+ break
+
+
diff --git a/DemoPrograms old/Demo_Button_Click.py b/DemoPrograms old/Demo_Button_Click.py
new file mode 100644
index 00000000..84f16376
--- /dev/null
+++ b/DemoPrograms old/Demo_Button_Click.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+if not sys.platform.startswith('win'):
+ sg.PopupError('Sorry, you gotta be on Windows')
+ sys.exit()
+import winsound
+
+
+# sg.ChangeLookAndFeel('Dark')
+# sg.SetOptions(element_padding=(0,0))
+
+layout = [
+ [sg.Button('Start', button_color=('white', 'black'), key='start'),
+ sg.Button('Stop', button_color=('white', 'black'), key='stop'),
+ sg.Button('Reset', button_color=('white', 'firebrick3'), key='reset'),
+ sg.Button('Submit', button_color=('white', 'springgreen4'), key='submit')]
+ ]
+
+window = sg.Window("Button Click", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False, default_button_element_size=(12,1), use_default_focus=False).Layout(layout).Finalize()
+
+window.FindElement('submit').Update(disabled=True)
+
+recording = have_data = False
+while True:
+ event, values = window.Read(timeout=100)
+ if event is None:
+ sys.exit(69)
+ winsound.PlaySound("ButtonClick.wav", 1) if event != sg.TIMEOUT_KEY else None
diff --git a/DemoPrograms old/Demo_Button_Func_Calls.py b/DemoPrograms old/Demo_Button_Func_Calls.py
new file mode 100644
index 00000000..c4d62a71
--- /dev/null
+++ b/DemoPrograms old/Demo_Button_Func_Calls.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+
+"""
+Demo Button Function Calls
+Typically GUI packages in Python (tkinter, Qt, WxPython, etc) will call a user's function
+when a button is clicked. This "Callback" model versus "Message Passing" model is a fundamental
+difference between PySimpleGUI and all other GUI.
+
+There are NO BUTTON CALLBACKS in the PySimpleGUI Architecture
+
+It is quite easy to simulate these callbacks however. The way to do this is to add the calls
+to your Event Loop
+"""
+
+def callback_function1():
+ sg.Popup('In Callback Function 1')
+ print('In the callback function 1')
+
+def callback_function2():
+ sg.Popup('In Callback Function 2')
+ print('In the callback function 2')
+
+layout = [ [sg.Text('Demo of Button Callbacks')],
+ [sg.Button('Button 1'), sg.Button('Button 2')] ]
+
+window = sg.Window('Button Callback Simulation').Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None:
+ break
+ elif event == 'Button 1':
+ callback_function1() # call the "Callback" function
+ elif event == 'Button 2':
+ callback_function2() # call the "Callback" function
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Button_States.py b/DemoPrograms old/Demo_Button_States.py
new file mode 100644
index 00000000..0b442699
--- /dev/null
+++ b/DemoPrograms old/Demo_Button_States.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+"""
+Demonstrates using a "tight" layout with a Dark theme.
+Shows how button states can be controlled by a user application. The program manages the disabled/enabled
+states for buttons and changes the text color to show greyed-out (disabled) buttons
+"""
+
+sg.ChangeLookAndFeel('Dark')
+sg.SetOptions(element_padding=(0,0))
+
+layout = [[sg.T('User:', pad=((3,0),0)), sg.OptionMenu(values = ('User 1', 'User 2'), size=(20,1)), sg.T('0', size=(8,1))],
+ [sg.T('Customer:', pad=((3,0),0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20,1)), sg.T('1', size=(8,1))],
+ [sg.T('Notes:', pad=((3,0),0)), sg.In(size=(44,1), background_color='white', text_color='black')],
+ [sg.Button('Start', button_color=('white', 'black'), key='_Start_'),
+ sg.Button('Stop', button_color=('white', 'black'), key='_Stop_'),
+ sg.Button('Reset', button_color=('white', 'firebrick3'), key='_Reset_'),
+ sg.Button('Submit', button_color=('white', 'springgreen4'), key='_Submit_')]]
+
+window = sg.Window("Time Tracker", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False,
+ default_button_element_size=(12,1)).Layout(layout).Finalize()
+
+
+for key, state in {'_Start_': False, '_Stop_': True, '_Reset_': True, '_Submit_': True}.items():
+ window.FindElement(key).Update(disabled=state)
+
+recording = have_data = False
+while True:
+ event, values = window.Read()
+ print(event)
+ if event is None:
+ sys.exit(69)
+ if event == '_Start_':
+ for key, state in {'_Start_':True, '_Stop_':False, '_Reset_':False, '_Submit_':True}.items():
+ window.FindElement(key).Update(disabled=state)
+ recording = True
+ elif event == '_Stop_' and recording:
+ [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':False, '_Submit_':False}.items()]
+ recording = False
+ have_data = True
+ elif event == '_Reset_':
+ [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':True}.items()]
+ recording = False
+ have_data = False
+ elif event == '_Submit_' and have_data:
+ [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':False}.items()]
+ recording = False
diff --git a/DemoPrograms old/Demo_Button_Toggle.py b/DemoPrograms old/Demo_Button_Toggle.py
new file mode 100644
index 00000000..0d923be6
--- /dev/null
+++ b/DemoPrograms old/Demo_Button_Toggle.py
@@ -0,0 +1,39 @@
+import PySimpleGUI as sg
+
+"""
+ Toggle Button Demo
+ The background color of the button toggles between on and off
+ Two versions are present... a simple button that changes text and a graphical one
+ A HUGE thank you to the PySimpleGUI community memeber that donated his time and skill in creating the buttons!
+ The text of the button toggles between Off and On
+"""
+def main():
+ layout = [[sg.Text('A toggle button example')],
+ [sg.T('A graphical version'), sg.B('', image_data=toggle_btn_off, key='_TOGGLE_GRAPHIC_', button_color=sg.COLOR_SYSTEM_DEFAULT,border_width=0),],
+ [sg.Button('On', size=(3,1), button_color=('white', 'green'), key='_B_'), sg.Button('Exit')]]
+
+ window = sg.Window('Toggle Button Example', layout)
+
+ down = True
+ graphic_off = True
+ while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event in (None, 'Exit'):
+ break
+ elif event == '_B_': # if the normal button that changes color and text
+ down = not down
+ window.Element('_B_').Update(('Off','On')[down], button_color=(('white', ('red', 'green')[down])))
+ elif event == '_TOGGLE_GRAPHIC_': # if the graphical button that changes images
+ graphic_off = not graphic_off
+ window.Element('_TOGGLE_GRAPHIC_').Update(image_data=(toggle_btn_on, toggle_btn_off)[graphic_off])
+
+ window.Close()
+
+
+toggle_btn_off = b'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'
+
+
+toggle_btn_on = b'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'
+
+main()
diff --git a/DemoPrograms old/Demo_Buttons_Mac.py b/DemoPrograms old/Demo_Buttons_Mac.py
new file mode 100644
index 00000000..b605923b
--- /dev/null
+++ b/DemoPrograms old/Demo_Buttons_Mac.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+import sys
+import time
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+def show_win():
+ sg.SetOptions(border_width=0, margins=(0,0), element_padding=(5,3))
+
+
+ frame_layout = [ [sg.Button('', image_data=mac_red, button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='_exit_'),
+ sg.Button('', image_data=mac_orange, button_color=('white', sg.COLOR_SYSTEM_DEFAULT)),
+ sg.Button('', image_data=mac_green, button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='_minimize_'),
+ sg.Text(' '*40)],]
+
+ layout = [[sg.Frame('',frame_layout)],
+ [sg.T('')],
+ [ sg.Text(' My Mac-alike window', size=(25,2)) ],]
+
+ window = sg.Window('My new window',
+ no_titlebar=True,
+ grab_anywhere=True,
+ alpha_channel=0,
+ ).Layout(layout).Finalize()
+
+ for i in range(100):
+ window.SetAlpha(i/100)
+ time.sleep(.01)
+
+ while True: # Event Loop
+ event, values = window.Read()
+ if event is None or event == '_exit_':
+ break
+ if event == '_minimize_':
+ # window.Minimize() # cannot minimize a window with no titlebar
+ pass
+ print(event, values)
+
+
+
+mac_red = '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'
+
+mac_green = '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'
+
+mac_orange = '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'
+
+
+show_win()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Buttons_Nice_Graphics.py b/DemoPrograms old/Demo_Buttons_Nice_Graphics.py
new file mode 100644
index 00000000..2a53a919
--- /dev/null
+++ b/DemoPrograms old/Demo_Buttons_Nice_Graphics.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+import io
+from PIL import Image
+import base64
+
+"""
+Shows some fancy button graphics with the help of PIL
+
+Usually when you create Base64 buttons to embed in your PySimpleGUI code, you make the exactly the correct size. Resizing isn't an option when
+ using the tkinter version of PySimpleGUI (except for crude "Scaling")
+
+The PIL code resizes the button images prior to creating the sg.Button
+"""
+
+
+DEF_BUTTON_COLOR =('white', 'black')
+
+def resize_base64_image(image64, size):
+ '''
+ May not be the original purpose, but this code is being used to resize an image for use with PySimpleGUI (tkinter) button graphics
+ :param image64: (str) The Base64 image
+ :param size: Tuple[int, int] Size to make the image in pixels (width, height)
+ :return: (str) A new Base64 image
+ '''
+ image_file = io.BytesIO(base64.b64decode(image64))
+ img = Image.open(image_file)
+ img.thumbnail(size, Image.ANTIALIAS)
+ bio = io.BytesIO()
+ img.save(bio, format='PNG')
+ imgbytes = bio.getvalue()
+ return imgbytes
+
+
+def GraphicButton(text, key, image_data, color=DEF_BUTTON_COLOR, size=(100,50)):
+ '''
+ A user defined element. Use this function inside of your layouts as if it were a Button element (it IS a Button Element)
+ Only 3 parameters are required.
+
+ :param text: (str) Text you want to display on the button
+ :param key: (Any) The key for the button
+ :param image_data: (str) The Base64 image to use on the button
+ :param color: Tuple[str, str] Button color
+ :param size: Tuple[int, int] Size of the button to display in pixels (width, height)
+ :return: (PySimpleGUI.Button) A button with a resized Base64 image applied to it
+ '''
+ return sg.Button(text, image_data=resize_base64_image(image_data, size), button_color=color, font='Any 15', pad=(0, 0), key=key, border_width=0)
+
+def ShowMeTheButtons():
+
+ sg.ChangeLookAndFeel('Black')
+
+ frame_layout = [ [sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45,3))],
+ [sg.Text('All of these buttons are part of the code itself', size=(45,2))],
+ [GraphicButton('Next', '-NEXT-', button64),
+ GraphicButton('Submit', '-SUBMIT-', red_pill64),
+ GraphicButton('OK', '-OK-', green_pill64),
+ GraphicButton('Exit', '-EXIT-', orange64)],]
+
+ layout = [[sg.Frame('Nice Buttons', frame_layout, font=('any 18'), background_color='black')]]
+
+ window = sg.Window('Demo of Nice Looking Buttons', layout,
+ grab_anywhere=True,
+ keep_on_top=True,
+ no_titlebar=True,
+ use_default_focus=False,
+ font='any 15',
+ background_color='black')
+
+ # ---===--- The event loop --- #
+ while True:
+ event, values = window.Read()
+ print(event)
+ if event in ('-EXIT-', None): # Exit button or X
+ break
+
+if __name__ == '__main__':
+
+ # To convert your images, use the PySimpleGUI program - Demo_Base64_Image_Encoder.py located on the GitHub (http://www.PySimpleGUI.com)
+
+ orange64 = 'iVBORw0KGgoAAAANSUhEUgAAAiIAAADLCAMAAABkvgh7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANtvJ99sId5tIt5uI91tJNxuJNxuJttvKN90NN91Ntt3PN52ONx3Otx3PNt4Pdp4Ptx4PepfD+pfEOtgD+piD+tkD+ttD+dnFOdoE+doFOVoFuNqG+JrHOFrHuFsHuRpGORpGuRqGORqGupgEOpiEOpkEOlmEOhnEupoEOpoEupqFettEetsF+tuFOxqFetrGOtsGetvHOxrGOxsGOxtGuxuG+xuHexxFutwH+xxGOxyGOxyGuxxHuBsIOJvJ+NvKORvKONwKeNwKuJxLONyLeJyLuRwKeRwKuRxLOtxIexyIexzJOx0JOx1Ju11KOx2KOx2Kux5Iux4Kux5Le1/LeFyMOFzMuB0NOB1Nux7MOx8MOx8Mu19NO1+NO1+Ntd7RdV8RtN+S9J/TdF/TtV9SNR9Stl5QNl6Qdh6Qth7RK+Tfa+Tfq6Ufr+JZb+JZr+KZ7uMbr6KaL6LaryMbLyNb7yOb7WPdLuNcLqOcLmOcrmPdLOQd7SQdrKReLKRerGSerCTfLCTfrmQde2AN+2AOO2BOu2COu6DPO6EPe6EPsWDV8OFXcOFXsOGXcOGXsaDWMSEWsqCVs+AUM2AUs6CU82CVMqDWMmEWtCAT9CAUMKHYMCIYsCIZMCKZu6FQO6GQe6GQu6HRO6IRe6JRu6KR+6KSO+LSu+MS++MTO+OTe+OTu+OUO+RUu+TVfCNTvCOTvCPUPCQUfCQUvCSU/CRVPCSVPCSVvCUV/CUWPCVWvGWWvGXXPGYXvCWYPGaYfGcYvGcZPKdZvKeZ/GdavKeaPKebfKebvKgavKhbPKibfGibvKjcPGkcfKmdPKiePOod/OpePSpePSqevSrfPSsfPSsfvSqg/SugfWwgvWwhPWyhfWyhvWxi/Wyj/W0ifW2jfa4j/a1kPW2kva2lfW2lvW4kPa6kfa7kva5lvW7lPa8lPW8lve+l/a4mva5nPa6nPa7nva+mPa+mva/nPa8oPa9ovbAm/bAnfXAnvbCofbEo/XGp/bGqPbIqQAAAC/NnaUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAAJXElEQVR4Xu3de3zVdR3H8Xb9/sJh60LlOTtubdIZEyckSdedMzkdZyRGIePmZdqki8s0KllJmtGCTAXRlUAyrireUeZga2JRkooYRWR0JbXMLsIqZ5fT7/L5nRvj9/md0/fhw/Px/fyHP+D33fb4vPj+LvsxXpMA8IREgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgJF7Ih1Xf3bVsuu/AYXu+mUrL+u8iqbqIcdErvhme0V8SjQCIpwWe+0nli+k2R5NTolc0jYzqpQBcigV+UDltZ57SQ6JfLqyRRXRyiCIis7u7qAhj8B3Ip1tLdg/pFLR0Z+hOR/JbyIrWxGIZCp289E2Ep+J3HzEFqLKy0qhUJUfeUkZnf8lGnYWX4l0tDfTOg5VEqoJNNTXh6EgmaMLBKuaymmcJFLRSfPO5CuRtgitYisPBehDQUFrqC3O2EtUxRdo4Bn8JHJhlNawlNZh85Aj2JQeiRp9JY08nY9Euk+nFUwq1ECLgww1ZTRaS+R8mnk6PpEF8dTDkNIgLQxiHFdNw7U0d9PU07CJdLw+tRUVpy5Cxo19+bz9lYI+n5cNfdhcjWGZf2jsCTTJcDiUmnBR/HKaewqbSHfqQqSJrkLGvuuctZu3jOgBD1u99B7Fg6a+Vwrrk8kNfRX+0XE2+qg527adsc38M723d01yK3lbqhH1SZp7CpfIolnJw4udQk44ddPOn/3q6b+M6K8+/c2vF+jXVw29X/ALI7N+Z3jome+f/QZ7pOG6VCMtl9Dkk7hElifvd0uPs1cb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'
+
+ green_pill64 = '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'
+
+ red_pill64 = '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'
+
+ button64 = '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'
+
+ ShowMeTheButtons()
+
diff --git a/DemoPrograms old/Demo_Calendar.py b/DemoPrograms old/Demo_Calendar.py
new file mode 100644
index 00000000..79a5533c
--- /dev/null
+++ b/DemoPrograms old/Demo_Calendar.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[sg.T('Calendar Test')],
+ [sg.In('', size=(20,1), key='input')],
+ [sg.CalendarButton('Choose Date', target='input', key='date')],
+ [sg.Ok(key=1)]]
+
+window = sg.Window('Calendar', grab_anywhere=False).Layout(layout)
+event,values = window.Read()
+sg.Popup(values['input'])
diff --git a/DemoPrograms old/Demo_Canvas.py b/DemoPrograms old/Demo_Canvas.py
new file mode 100644
index 00000000..60773758
--- /dev/null
+++ b/DemoPrograms old/Demo_Canvas.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [
+ [sg.Canvas(size=(150, 150), background_color='red', key='canvas')],
+ [sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue')]
+ ]
+
+window = sg.Window('Canvas test').Layout(layout).Finalize()
+
+cir = window.FindElement('canvas').TKCanvas.create_oval(50, 50, 100, 100)
+
+while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ if event in ('Blue', 'Red'):
+ window.FindElement('canvas').TKCanvas.itemconfig(cir, fill=event)
diff --git a/DemoPrograms old/Demo_Change_Look_And_Feel_Browser.py b/DemoPrograms old/Demo_Change_Look_And_Feel_Browser.py
new file mode 100644
index 00000000..823e66f5
--- /dev/null
+++ b/DemoPrograms old/Demo_Change_Look_And_Feel_Browser.py
@@ -0,0 +1,25 @@
+import PySimpleGUI as sg
+
+"""
+ Allows you to "browse" through the look and feel settings. Click on one and you'll see a
+ Popup window using the color scheme you chose. It's a simply little program that demonstrates
+ how snappy a GUI can feel if you enable an element's events rather than waiting on a button click.
+ In this program, as soon as a listbox entry is clicked, the read returns.
+"""
+
+sg.ChangeLookAndFeel('GreenTan')
+
+layout = [ [sg.Text('Look and Feel Browser')],
+ [sg.Text('Click a look and feel color to see demo window')],
+ [sg.Listbox(values=sg.list_of_look_and_feel_values(), size=(20,12), key='-LIST-', enable_events=True)],
+ [sg.Button('Show Window'), sg.Button('Exit')] ]
+
+window = sg.Window('Look and Feel Browser', layout)
+
+while True: # Event Loop
+ event, values = window.read()
+ if event in (None, 'Exit'):
+ break
+ sg.change_look_and_feel(values['-LIST-'][0])
+ sg.popup_get_text('This is {}'.format(values['-LIST-'][0]))
+window.close()
diff --git a/DemoPrograms old/Demo_Change_Submits_InputText.py b/DemoPrograms old/Demo_Change_Submits_InputText.py
new file mode 100644
index 00000000..215b46b2
--- /dev/null
+++ b/DemoPrograms old/Demo_Change_Submits_InputText.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+ Demonstrates the new change_submits parameter for inputtext elements
+ It ONLY submits when a button changes the field, not normal user input
+ Be careful on persistent forms to not clear the input
+"""
+layout = [[ sg.Text('Test of reading input field') ],
+ [sg.T('This input is normal'), sg.In()],
+ [sg.T('This input change submits'), sg.In(change_submits=True)],
+ [sg.T('This multiline input change submits'), sg.Multiline(change_submits=True, do_not_clear=True)],
+ [sg.T('This input is normal'), sg.In(), sg.FileBrowse()],
+ [sg.T('File Browse submits'), sg.In(change_submits=True,
+ do_not_clear=True,
+ key='_in1_'), sg.FileBrowse()],
+ [sg.T('Color Chooser submits'), sg.In(change_submits=True,
+ do_not_clear=True,
+ key='_in2_'), sg.ColorChooserButton('Color...', target=(sg.ThisRow, -1))],
+ [sg.T('Folder Browse submits'), sg.In(change_submits=True,
+ do_not_clear=True,
+ key='_in3_'), sg.FolderBrowse()],
+ [sg.T('Calendar Chooser submits'), sg.In(change_submits=True,
+ do_not_clear=True,
+ key='_in4_'), sg.CalendarButton('Date...', target=(sg.ThisRow, -1))],
+ [sg.T('Disabled input submits'), sg.In(change_submits=True,
+ do_not_clear=True,
+ disabled=True,
+ key='_in5'), sg.FileBrowse()],
+ [sg.T('This input clears after submit'),sg.In(change_submits=True,
+ key='_in6_'), sg.FileBrowse()],
+ [ sg.Button('Read')]]
+
+window = sg.Window('Demonstration of InputText with change_submits',
+ auto_size_text=False,
+ default_element_size=(22,1),
+ text_justification='right',
+ ).Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event is None:
+ break
diff --git a/DemoPrograms old/Demo_Chat.py b/DemoPrograms old/Demo_Chat.py
new file mode 100644
index 00000000..c4ab0dd3
--- /dev/null
+++ b/DemoPrograms old/Demo_Chat.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+'''
+A chat window. Add call to your send-routine, print the response and you're done
+
+To see this program RUN on the web go here:
+https://repl.it/@PySimpleGUI/Chat-Application-Demo
+
+Note that the size of the display on repl.it is smaller than most, so the sizes of the
+Multiline and Output text areas were reduced in the online version. Nothing else was changed
+'''
+
+sg.ChangeLookAndFeel('GreenTan') # give our window a spiffy set of colors
+
+layout = [ [sg.Text('Your output will go here', size=(40, 1))],
+ [sg.Output(size=(127, 30), font=('Helvetica 10'))],
+ [sg.Multiline(size=(85, 5), enter_submits=True, key='query'),
+ sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
+
+window = sg.Window('Chat window',
+ default_element_size=(30, 2),
+ font=('Helvetica',' 13'),
+ default_button_element_size=(8,2)).Layout(layout)
+
+# ---===--- Loop taking in user input and using it --- #
+while True:
+ event, value = window.Read()
+ if event == 'SEND':
+ query = value['query'].rstrip()
+ # EXECUTE YOUR COMMAND HERE
+ print('The command you entered was {}'.format(query))
+ elif event in (None, 'EXIT'): # quit if exit button or X
+ break
+sys.exit(69)
+
diff --git a/DemoPrograms old/Demo_Chat_With_History.py b/DemoPrograms old/Demo_Chat_With_History.py
new file mode 100644
index 00000000..1330caa1
--- /dev/null
+++ b/DemoPrograms old/Demo_Chat_With_History.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+'''
+A chatbot with history
+Scroll up and down through prior commands using the arrow keys
+Special keyboard keys:
+ Up arrow - scroll up in commands
+ Down arrow - scroll down in commands
+ Escape - clear current command
+ Control C - exit form
+'''
+
+def ChatBotWithHistory():
+ # ------- Make a new Window ------- #
+ sg.ChangeLookAndFeel('GreenTan') # give our form a spiffy set of colors
+
+ layout = [[sg.Text('Your output will go here', size=(40, 1))],
+ [sg.Output(size=(127, 30), font=('Helvetica 10'))],
+ [sg.T('Command History'), sg.T('', size=(20,3), key='history')],
+ [sg.Multiline(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
+ sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
+
+ window = sg.Window('Chat window with history', default_element_size=(30, 2), font=('Helvetica',' 13'), default_button_element_size=(8,2), return_keyboard_events=True).Layout(layout)
+
+ # ---===--- Loop taking in user input and using it --- #
+ command_history = []
+ history_offset = 0
+ while True:
+ (event, value) = window.Read()
+ if event == 'SEND':
+ query = value['query'].rstrip()
+ # EXECUTE YOUR COMMAND HERE
+ print('The command you entered was {}'.format(query))
+ command_history.append(query)
+ history_offset = len(command_history)-1
+ window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear
+ window.FindElement('history').Update('\n'.join(command_history[-3:]))
+ elif event in (None, 'EXIT'): # quit if exit event or X
+ break
+ elif 'Up' in event and len(command_history):
+ command = command_history[history_offset]
+ history_offset -= 1 * (history_offset > 0) # decrement is not zero
+ window.FindElement('query').Update(command)
+ elif 'Down' in event and len(command_history):
+ history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list
+ command = command_history[history_offset]
+ window.FindElement('query').Update(command)
+ elif 'Escape' in event:
+ window.FindElement('query').Update('')
+
+ sys.exit(69)
+
+
+ChatBotWithHistory()
diff --git a/DemoPrograms old/Demo_Chatterbot.py b/DemoPrograms old/Demo_Chatterbot.py
new file mode 100644
index 00000000..a80d08c3
--- /dev/null
+++ b/DemoPrograms old/Demo_Chatterbot.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+from chatterbot import ChatBot
+import chatterbot.utils
+
+
+'''
+Demo_Chatterbot.py
+A GUI wrapped arouind the Chatterbot package.
+The GUI is used to show progress bars during the training process and
+to collect user input that is sent to the chatbot. The reply is displayed in the GUI window
+'''
+
+# Create the 'Trainer GUI'
+# The Trainer GUI consists of a lot of progress bars stacked on top of each other
+sg.ChangeLookAndFeel('GreenTan')
+# sg.DebugWin()
+MAX_PROG_BARS = 20 # number of training sessions
+bars = []
+texts = []
+training_layout = [[sg.T('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))], ]
+for i in range(MAX_PROG_BARS):
+ bars.append(sg.ProgressBar(100, size=(30, 4)))
+ texts.append(sg.T(' ' * 20, size=(20, 1), justification='right'))
+ training_layout += [[texts[i], bars[i]],] # add a single row
+
+training_window = sg.Window('Training').Layout(training_layout)
+current_bar = 0
+
+# callback function for training runs
+def print_progress_bar(description, iteration_counter, total_items, progress_bar_length=20):
+ global current_bar
+ global bars
+ global texts
+ global training_window
+ # update the window and the bars
+ button, values = training_window.Read(timeout=0)
+ if button is None: # if user closed the window on us, exit
+ sys.exit(69)
+ if bars[current_bar].UpdateBar(iteration_counter, max=total_items) is False:
+ sys.exit(69)
+ texts[current_bar].Update(description) # show the training dataset name
+ if iteration_counter == total_items:
+ current_bar += 1
+
+# redefine the chatbot text based progress bar with a graphical one
+chatterbot.utils.print_progress_bar = print_progress_bar
+
+chatbot = ChatBot('Ron Obvious', trainer='chatterbot.trainers.ChatterBotCorpusTrainer')
+
+# Train based on the english corpus
+chatbot.train("chatterbot.corpus.english")
+
+################# GUI #################
+
+layout = [[sg.Output(size=(80, 20))],
+ [sg.Multiline(size=(70, 5), enter_submits=True),
+ sg.Button('SEND', bind_return_key=True), sg.Button('EXIT')]]
+
+window = sg.Window('Chat Window', auto_size_text=True, default_element_size=(30, 2)).Layout(layout)
+
+# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
+while True:
+ event, (value,) = window.Read()
+ if event != 'SEND':
+ break
+ string = value.rstrip()
+ print(' '+string)
+ # send the user input to chatbot to get a response
+ response = chatbot.get_response(value.rstrip())
+ print(response)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Chatterbot_With_TTS.py b/DemoPrograms old/Demo_Chatterbot_With_TTS.py
new file mode 100644
index 00000000..565c89c1
--- /dev/null
+++ b/DemoPrograms old/Demo_Chatterbot_With_TTS.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+from chatterbot import ChatBot
+import chatterbot.utils
+
+from gtts import gTTS
+from pygame import mixer
+import time
+import os
+
+'''
+Demo_Chatterbot.py
+A GUI wrapped arouind the Chatterbot package.
+The GUI is used to show progress bars during the training process and
+to collect user input that is sent to the chatbot. The reply is displayed in the GUI window
+'''
+
+# Create the 'Trainer GUI'
+# The Trainer GUI consists of a lot of progress bars stacked on top of each other
+sg.ChangeLookAndFeel('NeutralBlue')
+# sg.DebugWin()
+MAX_PROG_BARS = 20 # number of training sessions
+bars = []
+texts = []
+training_layout = [[sg.T('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))], ]
+for i in range(MAX_PROG_BARS):
+ bars.append(sg.ProgressBar(100, size=(30, 4)))
+ texts.append(sg.T(' ' * 20, size=(20, 1), justification='right'))
+ training_layout += [[texts[i], bars[i]],] # add a single row
+
+training_window = sg.Window('Training').Layout(training_layout)
+current_bar = 0
+
+# callback function for training runs
+def print_progress_bar(description, iteration_counter, total_items, progress_bar_length=20):
+ global current_bar
+ global bars
+ global texts
+ global training_window
+ # update the window and the bars
+ button, values = training_window.Read(timeout=0)
+ if button is None: # if user closed the window on us, exit
+ sys.exit(69)
+ if bars[current_bar].UpdateBar(iteration_counter, max=total_items) is False:
+ sys.exit(69)
+ texts[current_bar].Update(description) # show the training dataset name
+ if iteration_counter == total_items:
+ current_bar += 1
+
+def speak(text):
+ global i
+ tts = gTTS(text=text, lang='en',slow=False)
+ tts.save('speech{}.mp3'.format(i%2))
+ # playback the speech
+ mixer.music.load('speech{}.mp3'.format(i%2))
+ mixer.music.play()
+ # wait for playback to end
+ while mixer.music.get_busy():
+ time.sleep(.1)
+ mixer.stop()
+ i += 1
+
+i = 0
+mixer.init()
+
+# redefine the chatbot text based progress bar with a graphical one
+chatterbot.utils.print_progress_bar = print_progress_bar
+
+chatbot = ChatBot('Ron Obvious', trainer='chatterbot.trainers.ChatterBotCorpusTrainer')
+
+# Train based on the english corpus
+chatbot.train("chatterbot.corpus.english")
+
+################# GUI #################
+
+layout = [[sg.Output(size=(80, 20))],
+ [sg.Multiline(size=(70, 5), enter_submits=True),
+ sg.Button('SEND', bind_return_key=True), sg.Button('EXIT')]]
+
+window = sg.Window('Chat Window', auto_size_text=True, default_element_size=(30, 2)).Layout(layout)
+
+# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
+while True:
+ event, (value,) = window.Read()
+ if event != 'SEND':
+ break
+ string = value.rstrip()
+ print(' '+string)
+ # send the user input to chatbot to get a response
+ response = chatbot.get_response(value.rstrip())
+ print(response)
+ speak(str(response))
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Color.py b/DemoPrograms old/Demo_Color.py
new file mode 100644
index 00000000..ead1245f
--- /dev/null
+++ b/DemoPrograms old/Demo_Color.py
@@ -0,0 +1,1728 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+MY_WINDOW_ICON = 'E:\\TheRealMyDocs\\Icons\\The Planets\\jupiter.ico'
+reverse = {}
+colorhex = {}
+
+colors = {
+ "abbey" : ( 76, 79, 86),
+ "acadia" : ( 27, 20, 4),
+ "acapulco" : (124, 176, 161),
+ "aero blue" : (201, 255, 229),
+ "affair" : (113, 70, 147),
+ "akaroa" : (212, 196, 168),
+ "alabaster" : (250, 250, 250),
+ "albescent white" : (245, 233, 211),
+ "algae green" : (147, 223, 184),
+ "alice blue" : (240, 248, 255),
+ "alizarin crimson" : (227, 38, 54),
+ "allports" : ( 0, 118, 163),
+ "almond" : (238, 217, 196),
+ "almond frost" : (144, 123, 113),
+ "alpine" : (175, 143, 44),
+ "alto" : (219, 219, 219),
+ "aluminium" : (169, 172, 182),
+ "amaranth" : (229, 43, 80),
+ "amazon" : ( 59, 122, 87),
+ "amber" : (255, 191, 0),
+ "americano" : (135, 117, 110),
+ "amethyst" : (153, 102, 204),
+ "amethyst smoke" : (163, 151, 180),
+ "amour" : (249, 234, 243),
+ "amulet" : (123, 159, 128),
+ "anakiwa" : (157, 229, 255),
+ "antique brass" : (200, 138, 101),
+ "antique bronze" : (112, 74, 7),
+ "anzac" : (224, 182, 70),
+ "apache" : (223, 190, 111),
+ "apple" : ( 79, 168, 61),
+ "apple blossom" : (175, 77, 67),
+ "apple green" : (226, 243, 236),
+ "apricot" : (235, 147, 115),
+ "apricot peach" : (251, 206, 177),
+ "apricot white" : (255, 254, 236),
+ "aqua deep" : ( 1, 75, 67),
+ "aqua forest" : ( 95, 167, 119),
+ "aqua haze" : (237, 245, 245),
+ "aqua island" : (161, 218, 215),
+ "aqua spring" : (234, 249, 245),
+ "aqua squeeze" : (232, 245, 242),
+ "aquamarine" : (127, 255, 212),
+ "aquamarine blue" : (113, 217, 226),
+ "arapawa" : ( 17, 12, 108),
+ "armadillo" : ( 67, 62, 55),
+ "arrowtown" : (148, 135, 113),
+ "ash" : (198, 195, 181),
+ "asparagus" : (123, 160, 91),
+ "asphalt" : ( 19, 10, 6),
+ "astra" : (250, 234, 185),
+ "astral" : ( 50, 125, 160),
+ "astronaut" : ( 40, 58, 119),
+ "astronaut blue" : ( 1, 62, 98),
+ "athens gray" : (238, 240, 243),
+ "aths special" : (236, 235, 206),
+ "atlantis" : (151, 205, 45),
+ "atoll" : ( 10, 111, 117),
+ "atomic tangerine" : (255, 153, 102),
+ "au chico" : (151, 96, 93),
+ "aubergine" : ( 59, 9, 16),
+ "australian mint" : (245, 255, 190),
+ "avocado" : (136, 141, 101),
+ "axolotl" : ( 78, 102, 73),
+ "azalea" : (247, 200, 218),
+ "aztec" : ( 13, 28, 25),
+ "azure" : ( 49, 91, 161),
+ "azure radiance" : ( 0, 127, 255),
+ "baby blue" : (224, 255, 255),
+ "bahama blue" : ( 2, 99, 149),
+ "bahia" : (165, 203, 12),
+ "baja white" : (255, 248, 209),
+ "bali hai" : (133, 159, 175),
+ "baltic sea" : ( 42, 38, 48),
+ "bamboo" : (218, 99, 4),
+ "banana mania" : (251, 231, 178),
+ "bandicoot" : (133, 132, 112),
+ "barberry" : (222, 215, 23),
+ "barley corn" : (166, 139, 91),
+ "barley white" : (255, 244, 206),
+ "barossa" : ( 68, 1, 45),
+ "bastille" : ( 41, 33, 48),
+ "battleship gray" : (130, 143, 114),
+ "bay leaf" : (125, 169, 141),
+ "bay of many" : ( 39, 58, 129),
+ "bazaar" : (152, 119, 123),
+ "bean " : ( 61, 12, 2),
+ "beauty bush" : (238, 193, 190),
+ "beaver" : (146, 111, 91),
+ "beeswax" : (254, 242, 199),
+ "beige" : (245, 245, 220),
+ "bermuda" : (125, 216, 198),
+ "bermuda gray" : (107, 139, 162),
+ "beryl green" : (222, 229, 192),
+ "bianca" : (252, 251, 243),
+ "big stone" : ( 22, 42, 64),
+ "bilbao" : ( 50, 124, 20),
+ "biloba flower" : (178, 161, 234),
+ "birch" : ( 55, 48, 33),
+ "bird flower" : (212, 205, 22),
+ "biscay" : ( 27, 49, 98),
+ "bismark" : ( 73, 113, 131),
+ "bison hide" : (193, 183, 164),
+ "bistre" : ( 61, 43, 31),
+ "bitter" : (134, 137, 116),
+ "bitter lemon" : (202, 224, 13),
+ "bittersweet" : (254, 111, 94),
+ "bizarre" : (238, 222, 218),
+ "black" : ( 0, 0, 0),
+ "black bean" : ( 8, 25, 16),
+ "black forest" : ( 11, 19, 4),
+ "black haze" : (246, 247, 247),
+ "black marlin" : ( 62, 44, 28),
+ "black olive" : ( 36, 46, 22),
+ "black pearl" : ( 4, 19, 34),
+ "black rock" : ( 13, 3, 50),
+ "black rose" : (103, 3, 45),
+ "black russian" : ( 10, 0, 28),
+ "black squeeze" : (242, 250, 250),
+ "black white" : (255, 254, 246),
+ "blackberry" : ( 77, 1, 53),
+ "blackcurrant" : ( 50, 41, 58),
+ "blaze orange" : (255, 102, 0),
+ "bleach white" : (254, 243, 216),
+ "bleached cedar" : ( 44, 33, 51),
+ "blizzard blue" : (163, 227, 237),
+ "blossom" : (220, 180, 188),
+ "blue" : ( 0, 0, 255),
+ "blue bayoux" : ( 73, 102, 121),
+ "blue bell" : (153, 153, 204),
+ "blue chalk" : (241, 233, 255),
+ "blue charcoal" : ( 1, 13, 26),
+ "blue chill" : ( 12, 137, 144),
+ "blue diamond" : ( 56, 4, 116),
+ "blue dianne" : ( 32, 72, 82),
+ "blue gem" : ( 44, 14, 140),
+ "blue haze" : (191, 190, 216),
+ "blue lagoon" : ( 1, 121, 135),
+ "blue marguerite" : (118, 102, 198),
+ "blue ribbon" : ( 0, 102, 255),
+ "blue romance" : (210, 246, 222),
+ "blue smoke" : (116, 136, 129),
+ "blue stone" : ( 1, 97, 98),
+ "blue violet" : (100, 86, 183),
+ "blue whale" : ( 4, 46, 76),
+ "blue zodiac" : ( 19, 38, 77),
+ "blumine" : ( 24, 88, 122),
+ "blush" : (180, 70, 104),
+ "blush pink" : (255, 111, 255),
+ "bombay" : (175, 177, 184),
+ "bon jour" : (229, 224, 225),
+ "bondi blue" : ( 0, 149, 182),
+ "bone" : (228, 209, 192),
+ "bordeaux" : ( 92, 1, 32),
+ "bossanova" : ( 78, 42, 90),
+ "boston blue" : ( 59, 145, 180),
+ "botticelli" : (199, 221, 229),
+ "bottle green" : ( 9, 54, 36),
+ "boulder" : (122, 122, 122),
+ "bouquet" : (174, 128, 158),
+ "bourbon" : (186, 111, 30),
+ "bracken" : ( 74, 42, 4),
+ "brandy" : (222, 193, 150),
+ "brandy punch" : (205, 132, 41),
+ "brandy rose" : (187, 137, 131),
+ "breaker bay" : ( 93, 161, 159),
+ "brick red" : (198, 45, 66),
+ "bridal heath" : (255, 250, 244),
+ "bridesmaid" : (254, 240, 236),
+ "bright gray" : ( 60, 65, 81),
+ "bright green" : (102, 255, 0),
+ "bright red" : (177, 0, 0),
+ "bright sun" : (254, 211, 60),
+ "bright turquoise" : ( 8, 232, 222),
+ "brilliant rose" : (246, 83, 166),
+ "brink pink" : (251, 96, 127),
+ "bronco" : (171, 161, 150),
+ "bronze" : ( 63, 33, 9),
+ "bronze olive" : ( 78, 66, 12),
+ "bronzetone" : ( 77, 64, 15),
+ "broom" : (255, 236, 19),
+ "brown" : (150, 75, 0),
+ "brown bramble" : ( 89, 40, 4),
+ "brown derby" : ( 73, 38, 21),
+ "brown pod" : ( 64, 24, 1),
+ "brown rust" : (175, 89, 62),
+ "brown tumbleweed" : ( 55, 41, 14),
+ "bubbles" : (231, 254, 255),
+ "buccaneer" : ( 98, 47, 48),
+ "bud" : (168, 174, 156),
+ "buddha gold" : (193, 160, 4),
+ "buff" : (240, 220, 130),
+ "bulgarian rose" : ( 72, 6, 7),
+ "bull shot" : (134, 77, 30),
+ "bunker" : ( 13, 17, 23),
+ "bunting" : ( 21, 31, 76),
+ "burgundy" : (144, 0, 32),
+ "burnham" : ( 0, 46, 32),
+ "burning orange" : (255, 112, 52),
+ "burning sand" : (217, 147, 118),
+ "burnt maroon" : ( 66, 3, 3),
+ "burnt orange" : (204, 85, 0),
+ "burnt sienna" : (233, 116, 81),
+ "burnt umber" : (138, 51, 36),
+ "bush" : ( 13, 46, 28),
+ "buttercup" : (243, 173, 22),
+ "buttered rum" : (161, 117, 13),
+ "butterfly bush" : ( 98, 78, 154),
+ "buttermilk" : (255, 241, 181),
+ "buttery white" : (255, 252, 234),
+ "cab sav" : ( 77, 10, 24),
+ "cabaret" : (217, 73, 114),
+ "cabbage pont" : ( 63, 76, 58),
+ "cactus" : ( 88, 113, 86),
+ "cadet blue" : (169, 178, 195),
+ "cadillac" : (176, 76, 106),
+ "cafe royale" : (111, 68, 12),
+ "calico" : (224, 192, 149),
+ "california" : (254, 157, 4),
+ "calypso" : ( 49, 114, 141),
+ "camarone" : ( 0, 88, 26),
+ "camelot" : (137, 52, 86),
+ "cameo" : (217, 185, 155),
+ "camouflage" : ( 60, 57, 16),
+ "camouflage green" : (120, 134, 107),
+ "can can" : (213, 145, 164),
+ "canary" : (243, 251, 98),
+ "candlelight" : (252, 217, 23),
+ "candy corn" : (251, 236, 93),
+ "cannon black" : ( 37, 23, 6),
+ "cannon pink" : (137, 67, 103),
+ "cape cod" : ( 60, 68, 67),
+ "cape honey" : (254, 229, 172),
+ "cape palliser" : (162, 102, 69),
+ "caper" : (220, 237, 180),
+ "caramel" : (255, 221, 175),
+ "cararra" : (238, 238, 232),
+ "cardin green" : ( 1, 54, 28),
+ "cardinal" : (196, 30, 58),
+ "cardinal pink" : (140, 5, 94),
+ "careys pink" : (210, 158, 170),
+ "caribbean green" : ( 0, 204, 153),
+ "carissma" : (234, 136, 168),
+ "carla" : (243, 255, 216),
+ "carmine" : (150, 0, 24),
+ "carnaby tan" : ( 92, 46, 1),
+ "carnation" : (249, 90, 97),
+ "carnation pink" : (255, 166, 201),
+ "carousel pink" : (249, 224, 237),
+ "carrot orange" : (237, 145, 33),
+ "casablanca" : (248, 184, 83),
+ "casal" : ( 47, 97, 104),
+ "cascade" : (139, 169, 165),
+ "cashmere" : (230, 190, 165),
+ "casper" : (173, 190, 209),
+ "castro" : ( 82, 0, 31),
+ "catalina blue" : ( 6, 42, 120),
+ "catskill white" : (238, 246, 247),
+ "cavern pink" : (227, 190, 190),
+ "cedar" : ( 62, 28, 20),
+ "cedar wood finish" : (113, 26, 0),
+ "celadon" : (172, 225, 175),
+ "celery" : (184, 194, 93),
+ "celeste" : (209, 210, 202),
+ "cello" : ( 30, 56, 91),
+ "celtic" : ( 22, 50, 34),
+ "cement" : (141, 118, 98),
+ "ceramic" : (252, 255, 249),
+ "cerise" : (218, 50, 135),
+ "cerise red" : (222, 49, 99),
+ "cerulean" : ( 2, 164, 211),
+ "cerulean blue" : ( 42, 82, 190),
+ "chablis" : (255, 244, 243),
+ "chalet green" : ( 81, 110, 61),
+ "chalky" : (238, 215, 148),
+ "chambray" : ( 53, 78, 140),
+ "chamois" : (237, 220, 177),
+ "champagne" : (250, 236, 204),
+ "chantilly" : (248, 195, 223),
+ "charade" : ( 41, 41, 55),
+ "chardon" : (255, 243, 241),
+ "chardonnay" : (255, 205, 140),
+ "charlotte" : (186, 238, 249),
+ "charm" : (212, 116, 148),
+ "chartreuse" : (127, 255, 0),
+ "chartreuse yellow" : (223, 255, 0),
+ "chateau green" : ( 64, 168, 96),
+ "chatelle" : (189, 179, 199),
+ "chathams blue" : ( 23, 85, 121),
+ "chelsea cucumber" : (131, 170, 93),
+ "chelsea gem" : (158, 83, 2),
+ "chenin" : (223, 205, 111),
+ "cherokee" : (252, 218, 152),
+ "cherry pie" : ( 42, 3, 89),
+ "cherrywood" : (101, 26, 20),
+ "cherub" : (248, 217, 233),
+ "chestnut" : (185, 78, 72),
+ "chestnut rose" : (205, 92, 92),
+ "chetwode blue" : (133, 129, 217),
+ "chicago" : ( 93, 92, 88),
+ "chiffon" : (241, 255, 200),
+ "chilean fire" : (247, 119, 3),
+ "chilean heath" : (255, 253, 230),
+ "china ivory" : (252, 255, 231),
+ "chino" : (206, 199, 167),
+ "chinook" : (168, 227, 189),
+ "chocolate" : ( 55, 2, 2),
+ "christalle" : ( 51, 3, 107),
+ "christi" : (103, 167, 18),
+ "christine" : (231, 115, 10),
+ "chrome white" : (232, 241, 212),
+ "cinder" : ( 14, 14, 24),
+ "cinderella" : (253, 225, 220),
+ "cinnabar" : (227, 66, 52),
+ "cinnamon" : (123, 63, 0),
+ "cioccolato" : ( 85, 40, 12),
+ "citrine white" : (250, 247, 214),
+ "citron" : (158, 169, 31),
+ "citrus" : (161, 197, 10),
+ "clairvoyant" : ( 72, 6, 86),
+ "clam shell" : (212, 182, 175),
+ "claret" : (127, 23, 52),
+ "classic rose" : (251, 204, 231),
+ "clay ash" : (189, 200, 179),
+ "clay creek" : (138, 131, 96),
+ "clear day" : (233, 255, 253),
+ "clementine" : (233, 110, 0),
+ "clinker" : ( 55, 29, 9),
+ "cloud" : (199, 196, 191),
+ "cloud burst" : ( 32, 46, 84),
+ "cloudy" : (172, 165, 159),
+ "clover" : ( 56, 73, 16),
+ "cobalt" : ( 0, 71, 171),
+ "cocoa bean" : ( 72, 28, 28),
+ "cocoa brown" : ( 48, 31, 30),
+ "coconut cream" : (248, 247, 220),
+ "cod gray" : ( 11, 11, 11),
+ "coffee" : (112, 101, 85),
+ "coffee bean" : ( 42, 20, 14),
+ "cognac" : (159, 56, 29),
+ "cola" : ( 63, 37, 0),
+ "cold purple" : (171, 160, 217),
+ "cold turkey" : (206, 186, 186),
+ "colonial white" : (255, 237, 188),
+ "comet" : ( 92, 93, 117),
+ "como" : ( 81, 124, 102),
+ "conch" : (201, 217, 210),
+ "concord" : (124, 123, 122),
+ "concrete" : (242, 242, 242),
+ "confetti" : (233, 215, 90),
+ "congo brown" : ( 89, 55, 55),
+ "congress blue" : ( 2, 71, 142),
+ "conifer" : (172, 221, 77),
+ "contessa" : (198, 114, 107),
+ "copper" : (184, 115, 51),
+ "copper canyon" : (126, 58, 21),
+ "copper rose" : (153, 102, 102),
+ "copper rust" : (148, 71, 71),
+ "copperfield" : (218, 138, 103),
+ "coral" : (255, 127, 80),
+ "coral red" : (255, 64, 64),
+ "coral reef" : (199, 188, 162),
+ "coral tree" : (168, 107, 107),
+ "corduroy" : ( 96, 110, 104),
+ "coriander" : (196, 208, 176),
+ "cork" : ( 64, 41, 29),
+ "corn" : (231, 191, 5),
+ "corn field" : (248, 250, 205),
+ "corn harvest" : (139, 107, 11),
+ "cornflower" : (147, 204, 234),
+ "cornflower blue" : (100, 149, 237),
+ "cornflower lilac" : (255, 176, 172),
+ "corvette" : (250, 211, 162),
+ "cosmic" : (118, 57, 93),
+ "cosmos" : (255, 216, 217),
+ "costa del sol" : ( 97, 93, 48),
+ "cotton candy" : (255, 183, 213),
+ "cotton seed" : (194, 189, 182),
+ "county green" : ( 1, 55, 26),
+ "cowboy" : ( 77, 40, 45),
+ "crail" : (185, 81, 64),
+ "cranberry" : (219, 80, 121),
+ "crater brown" : ( 70, 36, 37),
+ "cream" : (255, 253, 208),
+ "cream brulee" : (255, 229, 160),
+ "cream can" : (245, 200, 92),
+ "creole" : ( 30, 15, 4),
+ "crete" : (115, 120, 41),
+ "crimson" : (220, 20, 60),
+ "crocodile" : (115, 109, 88),
+ "crown of thorns" : (119, 31, 31),
+ "crowshead" : ( 28, 18, 8),
+ "cruise" : (181, 236, 223),
+ "crusoe" : ( 0, 72, 22),
+ "crusta" : (253, 123, 51),
+ "cumin" : (146, 67, 33),
+ "cumulus" : (253, 255, 213),
+ "cupid" : (251, 190, 218),
+ "curious blue" : ( 37, 150, 209),
+ "cutty sark" : ( 80, 118, 114),
+ "cyan / aqua" : ( 0, 255, 255),
+ "cyprus" : ( 0, 62, 64),
+ "daintree" : ( 1, 39, 49),
+ "dairy cream" : (249, 228, 188),
+ "daisy bush" : ( 79, 35, 152),
+ "dallas" : (110, 75, 38),
+ "dandelion" : (254, 216, 93),
+ "danube" : ( 96, 147, 209),
+ "dark blue" : ( 0, 0, 200),
+ "dark burgundy" : (119, 15, 5),
+ "dark ebony" : ( 60, 32, 5),
+ "dark fern" : ( 10, 72, 13),
+ "dark tan" : (102, 16, 16),
+ "dawn" : (166, 162, 154),
+ "dawn pink" : (243, 233, 229),
+ "de york" : (122, 196, 136),
+ "deco" : (210, 218, 151),
+ "deep blue" : ( 34, 8, 120),
+ "deep blush" : (228, 118, 152),
+ "deep bronze" : ( 74, 48, 4),
+ "deep cerulean" : ( 0, 123, 167),
+ "deep cove" : ( 5, 16, 64),
+ "deep fir" : ( 0, 41, 0),
+ "deep forest green" : ( 24, 45, 9),
+ "deep koamaru" : ( 27, 18, 123),
+ "deep oak" : ( 65, 32, 16),
+ "deep sapphire" : ( 8, 37, 103),
+ "deep sea" : ( 1, 130, 107),
+ "deep sea green" : ( 9, 88, 89),
+ "deep teal" : ( 0, 53, 50),
+ "del rio" : (176, 154, 149),
+ "dell" : ( 57, 100, 19),
+ "delta" : (164, 164, 157),
+ "deluge" : (117, 99, 168),
+ "denim" : ( 21, 96, 189),
+ "derby" : (255, 238, 216),
+ "desert" : (174, 96, 32),
+ "desert sand" : (237, 201, 175),
+ "desert storm" : (248, 248, 247),
+ "dew" : (234, 255, 254),
+ "di serria" : (219, 153, 94),
+ "diesel" : ( 19, 0, 0),
+ "dingley" : ( 93, 119, 71),
+ "disco" : (135, 21, 80),
+ "dixie" : (226, 148, 24),
+ "dodger blue" : ( 30, 144, 255),
+ "dolly" : (249, 255, 139),
+ "dolphin" : (100, 96, 119),
+ "domino" : (142, 119, 94),
+ "don juan" : ( 93, 76, 81),
+ "donkey brown" : (166, 146, 121),
+ "dorado" : (107, 87, 85),
+ "double colonial white" : (238, 227, 173),
+ "double pearl lusta" : (252, 244, 208),
+ "double spanish white" : (230, 215, 185),
+ "dove gray" : (109, 108, 108),
+ "downriver" : ( 9, 34, 86),
+ "downy" : (111, 208, 197),
+ "driftwood" : (175, 135, 81),
+ "drover" : (253, 247, 173),
+ "dull lavender" : (168, 153, 230),
+ "dune" : ( 56, 53, 51),
+ "dust storm" : (229, 204, 201),
+ "dusty gray" : (168, 152, 155),
+ "eagle" : (182, 186, 164),
+ "earls green" : (201, 185, 59),
+ "early dawn" : (255, 249, 230),
+ "east bay" : ( 65, 76, 125),
+ "east side" : (172, 145, 206),
+ "eastern blue" : ( 30, 154, 176),
+ "ebb" : (233, 227, 227),
+ "ebony" : ( 12, 11, 29),
+ "ebony clay" : ( 38, 40, 59),
+ "eclipse" : ( 49, 28, 23),
+ "ecru white" : (245, 243, 229),
+ "ecstasy" : (250, 120, 20),
+ "eden" : ( 16, 88, 82),
+ "edgewater" : (200, 227, 215),
+ "edward" : (162, 174, 171),
+ "egg sour" : (255, 244, 221),
+ "egg white" : (255, 239, 193),
+ "eggplant" : ( 97, 64, 81),
+ "el paso" : ( 30, 23, 8),
+ "el salva" : (143, 62, 51),
+ "electric lime" : (204, 255, 0),
+ "electric violet" : (139, 0, 255),
+ "elephant" : ( 18, 52, 71),
+ "elf green" : ( 8, 131, 112),
+ "elm" : ( 28, 124, 125),
+ "emerald" : ( 80, 200, 120),
+ "eminence" : (108, 48, 130),
+ "emperor" : ( 81, 70, 73),
+ "empress" : (129, 115, 119),
+ "endeavour" : ( 0, 86, 167),
+ "energy yellow" : (248, 221, 92),
+ "english holly" : ( 2, 45, 21),
+ "english walnut" : ( 62, 43, 35),
+ "envy" : (139, 166, 144),
+ "equator" : (225, 188, 100),
+ "espresso" : ( 97, 39, 24),
+ "eternity" : ( 33, 26, 14),
+ "eucalyptus" : ( 39, 138, 91),
+ "eunry" : (207, 163, 157),
+ "evening sea" : ( 2, 78, 70),
+ "everglade" : ( 28, 64, 46),
+ "faded jade" : ( 66, 121, 119),
+ "fair pink" : (255, 239, 236),
+ "falcon" : (127, 98, 109),
+ "fall green" : (236, 235, 189),
+ "falu red" : (128, 24, 24),
+ "fantasy" : (250, 243, 240),
+ "fedora" : (121, 106, 120),
+ "feijoa" : (159, 221, 140),
+ "fern" : ( 99, 183, 108),
+ "fern frond" : (101, 114, 32),
+ "fern green" : ( 79, 121, 66),
+ "ferra" : (112, 79, 80),
+ "festival" : (251, 233, 108),
+ "feta" : (240, 252, 234),
+ "fiery orange" : (179, 82, 19),
+ "finch" : ( 98, 102, 73),
+ "finlandia" : ( 85, 109, 86),
+ "finn" : (105, 45, 84),
+ "fiord" : ( 64, 81, 105),
+ "fire" : (170, 66, 3),
+ "fire bush" : (232, 153, 40),
+ "firefly" : ( 14, 42, 48),
+ "flame pea" : (218, 91, 56),
+ "flamenco" : (255, 125, 7),
+ "flamingo" : (242, 85, 42),
+ "flax" : (238, 220, 130),
+ "flax smoke" : (123, 130, 101),
+ "flesh" : (255, 203, 164),
+ "flint" : (111, 106, 97),
+ "flirt" : (162, 0, 109),
+ "flush mahogany" : (202, 52, 53),
+ "flush orange" : (255, 127, 0),
+ "foam" : (216, 252, 250),
+ "fog" : (215, 208, 255),
+ "foggy gray" : (203, 202, 182),
+ "forest green" : ( 34, 139, 34),
+ "forget me not" : (255, 241, 238),
+ "fountain blue" : ( 86, 180, 190),
+ "frangipani" : (255, 222, 179),
+ "french gray" : (189, 189, 198),
+ "french lilac" : (236, 199, 238),
+ "french pass" : (189, 237, 253),
+ "french rose" : (246, 74, 138),
+ "fresh eggplant" : (153, 0, 102),
+ "friar gray" : (128, 126, 121),
+ "fringy flower" : (177, 226, 193),
+ "froly" : (245, 117, 132),
+ "frost" : (237, 245, 221),
+ "frosted mint" : (219, 255, 248),
+ "frostee" : (228, 246, 231),
+ "fruit salad" : ( 79, 157, 93),
+ "fuchsia blue" : (122, 88, 193),
+ "fuchsia pink" : (193, 84, 193),
+ "fuego" : (190, 222, 13),
+ "fuel yellow" : (236, 169, 39),
+ "fun blue" : ( 25, 89, 168),
+ "fun green" : ( 1, 109, 57),
+ "fuscous gray" : ( 84, 83, 77),
+ "fuzzy wuzzy brown" : (196, 86, 85),
+ "gable green" : ( 22, 53, 49),
+ "gallery" : (239, 239, 239),
+ "galliano" : (220, 178, 12),
+ "gamboge" : (228, 155, 15),
+ "geebung" : (209, 143, 27),
+ "genoa" : ( 21, 115, 107),
+ "geraldine" : (251, 137, 137),
+ "geyser" : (212, 223, 226),
+ "ghost" : (199, 201, 213),
+ "gigas" : ( 82, 60, 148),
+ "gimblet" : (184, 181, 106),
+ "gin" : (232, 242, 235),
+ "gin fizz" : (255, 249, 226),
+ "givry" : (248, 228, 191),
+ "glacier" : (128, 179, 196),
+ "glade green" : ( 97, 132, 95),
+ "go ben" : (114, 109, 78),
+ "goblin" : ( 61, 125, 82),
+ "gold" : (255, 215, 0),
+ "gold drop" : (241, 130, 0),
+ "gold sand" : (230, 190, 138),
+ "gold tips" : (222, 186, 19),
+ "golden bell" : (226, 137, 19),
+ "golden dream" : (240, 213, 45),
+ "golden fizz" : (245, 251, 61),
+ "golden glow" : (253, 226, 149),
+ "golden grass" : (218, 165, 32),
+ "golden sand" : (240, 219, 125),
+ "golden tainoi" : (255, 204, 92),
+ "goldenrod" : (252, 214, 103),
+ "gondola" : ( 38, 20, 20),
+ "gordons green" : ( 11, 17, 7),
+ "gorse" : (255, 241, 79),
+ "gossamer" : ( 6, 155, 129),
+ "gossip" : (210, 248, 176),
+ "gothic" : (109, 146, 161),
+ "governor bay" : ( 47, 60, 179),
+ "grain brown" : (228, 213, 183),
+ "grandis" : (255, 211, 140),
+ "granite green" : (141, 137, 116),
+ "granny apple" : (213, 246, 227),
+ "granny smith" : (132, 160, 160),
+ "granny smith apple" : (157, 224, 147),
+ "grape" : ( 56, 26, 81),
+ "graphite" : ( 37, 22, 7),
+ "gravel" : ( 74, 68, 75),
+ "gray" : (128, 128, 128),
+ "gray asparagus" : ( 70, 89, 69),
+ "gray chateau" : (162, 170, 179),
+ "gray nickel" : (195, 195, 189),
+ "gray nurse" : (231, 236, 230),
+ "gray olive" : (169, 164, 145),
+ "gray suit" : (193, 190, 205),
+ "green" : ( 0, 255, 0),
+ "green haze" : ( 1, 163, 104),
+ "green house" : ( 36, 80, 15),
+ "green kelp" : ( 37, 49, 28),
+ "green leaf" : ( 67, 106, 13),
+ "green mist" : (203, 211, 176),
+ "green pea" : ( 29, 97, 66),
+ "green smoke" : (164, 175, 110),
+ "green spring" : (184, 193, 177),
+ "green vogue" : ( 3, 43, 82),
+ "green waterloo" : ( 16, 20, 5),
+ "green white" : (232, 235, 224),
+ "green yellow" : (173, 255, 47),
+ "grenadier" : (213, 70, 0),
+ "guardsman red" : (186, 1, 1),
+ "gulf blue" : ( 5, 22, 87),
+ "gulf stream" : (128, 179, 174),
+ "gull gray" : (157, 172, 183),
+ "gum leaf" : (182, 211, 191),
+ "gumbo" : (124, 161, 166),
+ "gun powder" : ( 65, 66, 87),
+ "gunsmoke" : (130, 134, 133),
+ "gurkha" : (154, 149, 119),
+ "hacienda" : (152, 129, 27),
+ "hairy heath" : (107, 42, 20),
+ "haiti" : ( 27, 16, 53),
+ "half baked" : (133, 196, 204),
+ "half colonial white" : (253, 246, 211),
+ "half dutch white" : (254, 247, 222),
+ "half spanish white" : (254, 244, 219),
+ "half and half" : (255, 254, 225),
+ "hampton" : (229, 216, 175),
+ "harlequin" : ( 63, 255, 0),
+ "harp" : (230, 242, 234),
+ "harvest gold" : (224, 185, 116),
+ "havelock blue" : ( 85, 144, 217),
+ "hawaiian tan" : (157, 86, 22),
+ "hawkes blue" : (212, 226, 252),
+ "heath" : ( 84, 16, 18),
+ "heather" : (183, 195, 208),
+ "heathered gray" : (182, 176, 149),
+ "heavy metal" : ( 43, 50, 40),
+ "heliotrope" : (223, 115, 255),
+ "hemlock" : ( 94, 93, 59),
+ "hemp" : (144, 120, 116),
+ "hibiscus" : (182, 49, 108),
+ "highland" : (111, 142, 99),
+ "hillary" : (172, 165, 134),
+ "himalaya" : (106, 93, 27),
+ "hint of green" : (230, 255, 233),
+ "hint of red" : (251, 249, 249),
+ "hint of yellow" : (250, 253, 228),
+ "hippie blue" : ( 88, 154, 175),
+ "hippie green" : ( 83, 130, 75),
+ "hippie pink" : (174, 69, 96),
+ "hit gray" : (161, 173, 181),
+ "hit pink" : (255, 171, 129),
+ "hokey pokey" : (200, 165, 40),
+ "hoki" : (101, 134, 159),
+ "holly" : ( 1, 29, 19),
+ "hollywood cerise" : (244, 0, 161),
+ "honey flower" : ( 79, 28, 112),
+ "honeysuckle" : (237, 252, 132),
+ "hopbush" : (208, 109, 161),
+ "horizon" : ( 90, 135, 160),
+ "horses neck" : ( 96, 73, 19),
+ "hot cinnamon" : (210, 105, 30),
+ "hot pink" : (255, 105, 180),
+ "hot toddy" : (179, 128, 7),
+ "humming bird" : (207, 249, 243),
+ "hunter green" : ( 22, 29, 16),
+ "hurricane" : (135, 124, 123),
+ "husk" : (183, 164, 88),
+ "ice cold" : (177, 244, 231),
+ "iceberg" : (218, 244, 240),
+ "illusion" : (246, 164, 201),
+ "inch worm" : (176, 227, 19),
+ "indian khaki" : (195, 176, 145),
+ "indian tan" : ( 77, 30, 1),
+ "indigo" : ( 79, 105, 198),
+ "indochine" : (194, 107, 3),
+ "international klein blue" : ( 0, 47, 167),
+ "international orange" : (255, 79, 0),
+ "irish coffee" : ( 95, 61, 38),
+ "iroko" : ( 67, 49, 32),
+ "iron" : (212, 215, 217),
+ "ironside gray" : (103, 102, 98),
+ "ironstone" : (134, 72, 60),
+ "island spice" : (255, 252, 238),
+ "ivory" : (255, 255, 240),
+ "jacaranda" : ( 46, 3, 41),
+ "jacarta" : ( 58, 42, 106),
+ "jacko bean" : ( 46, 25, 5),
+ "jacksons purple" : ( 32, 32, 141),
+ "jade" : ( 0, 168, 107),
+ "jaffa" : (239, 134, 63),
+ "jagged ice" : (194, 232, 229),
+ "jagger" : ( 53, 14, 87),
+ "jaguar" : ( 8, 1, 16),
+ "jambalaya" : ( 91, 48, 19),
+ "janna" : (244, 235, 211),
+ "japanese laurel" : ( 10, 105, 6),
+ "japanese maple" : (120, 1, 9),
+ "japonica" : (216, 124, 99),
+ "java" : ( 31, 194, 194),
+ "jazzberry jam" : (165, 11, 94),
+ "jelly bean" : ( 41, 123, 154),
+ "jet stream" : (181, 210, 206),
+ "jewel" : ( 18, 107, 64),
+ "jon" : ( 59, 31, 31),
+ "jonquil" : (238, 255, 154),
+ "jordy blue" : (138, 185, 241),
+ "judge gray" : ( 84, 67, 51),
+ "jumbo" : (124, 123, 130),
+ "jungle green" : ( 41, 171, 135),
+ "jungle mist" : (180, 207, 211),
+ "juniper" : (109, 146, 146),
+ "just right" : (236, 205, 185),
+ "kabul" : ( 94, 72, 62),
+ "kaitoke green" : ( 0, 70, 32),
+ "kangaroo" : (198, 200, 189),
+ "karaka" : ( 30, 22, 9),
+ "karry" : (255, 234, 212),
+ "kashmir blue" : ( 80, 112, 150),
+ "kelp" : ( 69, 73, 54),
+ "kenyan copper" : (124, 28, 5),
+ "keppel" : ( 58, 176, 158),
+ "key lime pie" : (191, 201, 33),
+ "khaki" : (240, 230, 140),
+ "kidnapper" : (225, 234, 212),
+ "kilamanjaro" : ( 36, 12, 2),
+ "killarney" : ( 58, 106, 71),
+ "kimberly" : (115, 108, 159),
+ "kingfisher daisy" : ( 62, 4, 128),
+ "kobi" : (231, 159, 196),
+ "kokoda" : (110, 109, 87),
+ "korma" : (143, 75, 14),
+ "koromiko" : (255, 189, 95),
+ "kournikova" : (255, 231, 114),
+ "kumera" : (136, 98, 33),
+ "la palma" : ( 54, 135, 22),
+ "la rioja" : (179, 193, 16),
+ "las palmas" : (198, 230, 16),
+ "laser" : (200, 181, 104),
+ "laser lemon" : (255, 255, 102),
+ "laurel" : (116, 147, 120),
+ "lavender" : (181, 126, 220),
+ "lavender gray" : (189, 187, 215),
+ "lavender magenta" : (238, 130, 238),
+ "lavender pink" : (251, 174, 210),
+ "lavender purple" : (150, 123, 182),
+ "lavender rose" : (251, 160, 227),
+ "lavender blush" : (255, 240, 245),
+ "leather" : (150, 112, 89),
+ "lemon" : (253, 233, 16),
+ "lemon chiffon" : (255, 250, 205),
+ "lemon ginger" : (172, 158, 34),
+ "lemon grass" : (155, 158, 143),
+ "light apricot" : (253, 213, 177),
+ "light orchid" : (226, 156, 210),
+ "light wisteria" : (201, 160, 220),
+ "lightning yellow" : (252, 192, 30),
+ "lilac" : (200, 162, 200),
+ "lilac bush" : (152, 116, 211),
+ "lily" : (200, 170, 191),
+ "lily white" : (231, 248, 255),
+ "lima" : (118, 189, 23),
+ "lime" : (191, 255, 0),
+ "limeade" : (111, 157, 2),
+ "limed ash" : (116, 125, 99),
+ "limed oak" : (172, 138, 86),
+ "limed spruce" : ( 57, 72, 81),
+ "linen" : (250, 240, 230),
+ "link water" : (217, 228, 245),
+ "lipstick" : (171, 5, 99),
+ "lisbon brown" : ( 66, 57, 33),
+ "livid brown" : ( 77, 40, 46),
+ "loafer" : (238, 244, 222),
+ "loblolly" : (189, 201, 206),
+ "lochinvar" : ( 44, 140, 132),
+ "lochmara" : ( 0, 126, 199),
+ "locust" : (168, 175, 142),
+ "log cabin" : ( 36, 42, 29),
+ "logan" : (170, 169, 205),
+ "lola" : (223, 207, 219),
+ "london hue" : (190, 166, 195),
+ "lonestar" : (109, 1, 1),
+ "lotus" : (134, 60, 60),
+ "loulou" : ( 70, 11, 65),
+ "lucky" : (175, 159, 28),
+ "lucky point" : ( 26, 26, 104),
+ "lunar green" : ( 60, 73, 58),
+ "luxor gold" : (167, 136, 44),
+ "lynch" : (105, 126, 154),
+ "mabel" : (217, 247, 255),
+ "macaroni and cheese" : (255, 185, 123),
+ "madang" : (183, 240, 190),
+ "madison" : ( 9, 37, 93),
+ "madras" : ( 63, 48, 2),
+ "magenta / fuchsia" : (255, 0, 255),
+ "magic mint" : (170, 240, 209),
+ "magnolia" : (248, 244, 255),
+ "mahogany" : ( 78, 6, 6),
+ "mai tai" : (176, 102, 8),
+ "maize" : (245, 213, 160),
+ "makara" : (137, 125, 109),
+ "mako" : ( 68, 73, 84),
+ "malachite" : ( 11, 218, 81),
+ "malibu" : (125, 200, 247),
+ "mallard" : ( 35, 52, 24),
+ "malta" : (189, 178, 161),
+ "mamba" : (142, 129, 144),
+ "manatee" : (141, 144, 161),
+ "mandalay" : (173, 120, 27),
+ "mandy" : (226, 84, 101),
+ "mandys pink" : (242, 195, 178),
+ "mango tango" : (231, 114, 0),
+ "manhattan" : (245, 201, 153),
+ "mantis" : (116, 195, 101),
+ "mantle" : (139, 156, 144),
+ "manz" : (238, 239, 120),
+ "mardi gras" : ( 53, 0, 54),
+ "marigold" : (185, 141, 40),
+ "marigold yellow" : (251, 232, 112),
+ "mariner" : ( 40, 106, 205),
+ "maroon" : (128, 0, 0),
+ "maroon flush" : (195, 33, 72),
+ "maroon oak" : ( 82, 12, 23),
+ "marshland" : ( 11, 15, 8),
+ "martini" : (175, 160, 158),
+ "martinique" : ( 54, 48, 80),
+ "marzipan" : (248, 219, 157),
+ "masala" : ( 64, 59, 56),
+ "matisse" : ( 27, 101, 157),
+ "matrix" : (176, 93, 84),
+ "matterhorn" : ( 78, 59, 65),
+ "mauve" : (224, 176, 255),
+ "mauvelous" : (240, 145, 169),
+ "maverick" : (216, 194, 213),
+ "medium carmine" : (175, 64, 53),
+ "medium purple" : (147, 112, 219),
+ "medium red violet" : (187, 51, 133),
+ "melanie" : (228, 194, 213),
+ "melanzane" : ( 48, 5, 41),
+ "melon" : (254, 186, 173),
+ "melrose" : (199, 193, 255),
+ "mercury" : (229, 229, 229),
+ "merino" : (246, 240, 230),
+ "merlin" : ( 65, 60, 55),
+ "merlot" : (131, 25, 35),
+ "metallic bronze" : ( 73, 55, 27),
+ "metallic copper" : (113, 41, 29),
+ "meteor" : (208, 125, 18),
+ "meteorite" : ( 60, 31, 118),
+ "mexican red" : (167, 37, 37),
+ "mid gray" : ( 95, 95, 110),
+ "midnight" : ( 1, 22, 53),
+ "midnight blue" : ( 0, 51, 102),
+ "midnight moss" : ( 4, 16, 4),
+ "mikado" : ( 45, 37, 16),
+ "milan" : (250, 255, 164),
+ "milano red" : (184, 17, 4),
+ "milk punch" : (255, 246, 212),
+ "millbrook" : ( 89, 68, 51),
+ "mimosa" : (248, 253, 211),
+ "mindaro" : (227, 249, 136),
+ "mine shaft" : ( 50, 50, 50),
+ "mineral green" : ( 63, 93, 83),
+ "ming" : ( 54, 116, 125),
+ "minsk" : ( 63, 48, 127),
+ "mint green" : (152, 255, 152),
+ "mint julep" : (241, 238, 193),
+ "mint tulip" : (196, 244, 235),
+ "mirage" : ( 22, 25, 40),
+ "mischka" : (209, 210, 221),
+ "mist gray" : (196, 196, 188),
+ "mobster" : (127, 117, 137),
+ "moccaccino" : (110, 29, 20),
+ "mocha" : (120, 45, 25),
+ "mojo" : (192, 71, 55),
+ "mona lisa" : (255, 161, 148),
+ "monarch" : (139, 7, 35),
+ "mondo" : ( 74, 60, 48),
+ "mongoose" : (181, 162, 127),
+ "monsoon" : (138, 131, 137),
+ "monte carlo" : (131, 208, 198),
+ "monza" : (199, 3, 30),
+ "moody blue" : (127, 118, 211),
+ "moon glow" : (252, 254, 218),
+ "moon mist" : (220, 221, 204),
+ "moon raker" : (214, 206, 246),
+ "morning glory" : (158, 222, 224),
+ "morocco brown" : ( 68, 29, 0),
+ "mortar" : ( 80, 67, 81),
+ "mosque" : ( 3, 106, 110),
+ "moss green" : (173, 223, 173),
+ "mountain meadow" : ( 26, 179, 133),
+ "mountain mist" : (149, 147, 150),
+ "mountbatten pink" : (153, 122, 141),
+ "muddy waters" : (183, 142, 92),
+ "muesli" : (170, 139, 91),
+ "mulberry" : (197, 75, 140),
+ "mulberry wood" : ( 92, 5, 54),
+ "mule fawn" : (140, 71, 47),
+ "mulled wine" : ( 78, 69, 98),
+ "mustard" : (255, 219, 88),
+ "my pink" : (214, 145, 136),
+ "my sin" : (255, 179, 31),
+ "mystic" : (226, 235, 237),
+ "nandor" : ( 75, 93, 82),
+ "napa" : (172, 164, 148),
+ "narvik" : (237, 249, 241),
+ "natural gray" : (139, 134, 128),
+ "navajo white" : (255, 222, 173),
+ "navy blue" : ( 0, 0, 128),
+ "nebula" : (203, 219, 214),
+ "negroni" : (255, 226, 197),
+ "neon carrot" : (255, 153, 51),
+ "nepal" : (142, 171, 193),
+ "neptune" : (124, 183, 187),
+ "nero" : ( 20, 6, 0),
+ "nevada" : (100, 110, 117),
+ "new orleans" : (243, 214, 157),
+ "new york pink" : (215, 131, 127),
+ "niagara" : ( 6, 161, 137),
+ "night rider" : ( 31, 18, 15),
+ "night shadz" : (170, 55, 90),
+ "nile blue" : ( 25, 55, 81),
+ "nobel" : (183, 177, 177),
+ "nomad" : (186, 177, 162),
+ "norway" : (168, 189, 159),
+ "nugget" : (197, 153, 34),
+ "nutmeg" : (129, 66, 44),
+ "nutmeg wood finish" : (104, 54, 0),
+ "oasis" : (254, 239, 206),
+ "observatory" : ( 2, 134, 111),
+ "ocean green" : ( 65, 170, 120),
+ "ochre" : (204, 119, 34),
+ "off green" : (230, 248, 243),
+ "off yellow" : (254, 249, 227),
+ "oil" : ( 40, 30, 21),
+ "old brick" : (144, 30, 30),
+ "old copper" : (114, 74, 47),
+ "old gold" : (207, 181, 59),
+ "old lace" : (253, 245, 230),
+ "old lavender" : (121, 104, 120),
+ "old rose" : (192, 128, 129),
+ "olive" : (128, 128, 0),
+ "olive drab" : (107, 142, 35),
+ "olive green" : (181, 179, 92),
+ "olive haze" : (139, 132, 112),
+ "olivetone" : (113, 110, 16),
+ "olivine" : (154, 185, 115),
+ "onahau" : (205, 244, 255),
+ "onion" : ( 47, 39, 14),
+ "opal" : (169, 198, 194),
+ "opium" : (142, 111, 112),
+ "oracle" : ( 55, 116, 117),
+ "orange" : (255, 104, 31),
+ "orange peel" : (255, 160, 0),
+ "orange roughy" : (196, 87, 25),
+ "orange white" : (254, 252, 237),
+ "orchid" : (218, 112, 214),
+ "orchid white" : (255, 253, 243),
+ "oregon" : (155, 71, 3),
+ "orient" : ( 1, 94, 133),
+ "oriental pink" : (198, 145, 145),
+ "orinoco" : (243, 251, 212),
+ "oslo gray" : (135, 141, 145),
+ "ottoman" : (233, 248, 237),
+ "outer space" : ( 45, 56, 58),
+ "outrageous orange" : (255, 96, 55),
+ "oxford blue" : ( 56, 69, 85),
+ "oxley" : (119, 158, 134),
+ "oyster bay" : (218, 250, 255),
+ "oyster pink" : (233, 206, 205),
+ "paarl" : (166, 85, 41),
+ "pablo" : (119, 111, 97),
+ "pacific blue" : ( 0, 157, 196),
+ "pacifika" : (119, 129, 32),
+ "paco" : ( 65, 31, 16),
+ "padua" : (173, 230, 196),
+ "pale canary" : (255, 255, 153),
+ "pale leaf" : (192, 211, 185),
+ "pale oyster" : (152, 141, 119),
+ "pale prim" : (253, 254, 184),
+ "pale rose" : (255, 225, 242),
+ "pale sky" : (110, 119, 131),
+ "pale slate" : (195, 191, 193),
+ "palm green" : ( 9, 35, 15),
+ "palm leaf" : ( 25, 51, 14),
+ "pampas" : (244, 242, 238),
+ "panache" : (234, 246, 238),
+ "pancho" : (237, 205, 171),
+ "papaya whip" : (255, 239, 213),
+ "paprika" : (141, 2, 38),
+ "paradiso" : ( 49, 125, 130),
+ "parchment" : (241, 233, 210),
+ "paris daisy" : (255, 244, 110),
+ "paris m" : ( 38, 5, 106),
+ "paris white" : (202, 220, 212),
+ "parsley" : ( 19, 79, 25),
+ "pastel green" : (119, 221, 119),
+ "pastel pink" : (255, 209, 220),
+ "patina" : ( 99, 154, 143),
+ "pattens blue" : (222, 245, 255),
+ "paua" : ( 38, 3, 104),
+ "pavlova" : (215, 196, 152),
+ "peach" : (255, 229, 180),
+ "peach cream" : (255, 240, 219),
+ "peach orange" : (255, 204, 153),
+ "peach schnapps" : (255, 220, 214),
+ "peach yellow" : (250, 223, 173),
+ "peanut" : (120, 47, 22),
+ "pear" : (209, 226, 49),
+ "pearl bush" : (232, 224, 213),
+ "pearl lusta" : (252, 244, 220),
+ "peat" : (113, 107, 86),
+ "pelorous" : ( 62, 171, 191),
+ "peppermint" : (227, 245, 225),
+ "perano" : (169, 190, 242),
+ "perfume" : (208, 190, 248),
+ "periglacial blue" : (225, 230, 214),
+ "periwinkle" : (204, 204, 255),
+ "periwinkle gray" : (195, 205, 230),
+ "persian blue" : ( 28, 57, 187),
+ "persian green" : ( 0, 166, 147),
+ "persian indigo" : ( 50, 18, 122),
+ "persian pink" : (247, 127, 190),
+ "persian plum" : (112, 28, 28),
+ "persian red" : (204, 51, 51),
+ "persian rose" : (254, 40, 162),
+ "persimmon" : (255, 107, 83),
+ "peru tan" : (127, 58, 2),
+ "pesto" : (124, 118, 49),
+ "petite orchid" : (219, 150, 144),
+ "pewter" : (150, 168, 161),
+ "pharlap" : (163, 128, 123),
+ "picasso" : (255, 243, 157),
+ "pickled bean" : (110, 72, 38),
+ "pickled bluewood" : ( 49, 68, 89),
+ "picton blue" : ( 69, 177, 232),
+ "pig pink" : (253, 215, 228),
+ "pigeon post" : (175, 189, 217),
+ "pigment indigo" : ( 75, 0, 130),
+ "pine cone" : (109, 94, 84),
+ "pine glade" : (199, 205, 144),
+ "pine green" : ( 1, 121, 111),
+ "pine tree" : ( 23, 31, 4),
+ "pink" : (255, 192, 203),
+ "pink flamingo" : (255, 102, 255),
+ "pink flare" : (225, 192, 200),
+ "pink lace" : (255, 221, 244),
+ "pink lady" : (255, 241, 216),
+ "pink salmon" : (255, 145, 164),
+ "pink swan" : (190, 181, 183),
+ "piper" : (201, 99, 35),
+ "pipi" : (254, 244, 204),
+ "pippin" : (255, 225, 223),
+ "pirate gold" : (186, 127, 3),
+ "pistachio" : (157, 194, 9),
+ "pixie green" : (192, 216, 182),
+ "pizazz" : (255, 144, 0),
+ "pizza" : (201, 148, 21),
+ "plantation" : ( 39, 80, 75),
+ "plum" : (132, 49, 121),
+ "pohutukawa" : (143, 2, 28),
+ "polar" : (229, 249, 246),
+ "polo blue" : (141, 168, 204),
+ "pomegranate" : (243, 71, 35),
+ "pompadour" : (102, 0, 69),
+ "porcelain" : (239, 242, 243),
+ "porsche" : (234, 174, 105),
+ "port gore" : ( 37, 31, 79),
+ "portafino" : (255, 255, 180),
+ "portage" : (139, 159, 238),
+ "portica" : (249, 230, 99),
+ "pot pourri" : (245, 231, 226),
+ "potters clay" : (140, 87, 56),
+ "powder ash" : (188, 201, 194),
+ "powder blue" : (176, 224, 230),
+ "prairie sand" : (154, 56, 32),
+ "prelude" : (208, 192, 229),
+ "prim" : (240, 226, 236),
+ "primrose" : (237, 234, 153),
+ "provincial pink" : (254, 245, 241),
+ "prussian blue" : ( 0, 49, 83),
+ "puce" : (204, 136, 153),
+ "pueblo" : (125, 44, 20),
+ "puerto rico" : ( 63, 193, 170),
+ "pumice" : (194, 202, 196),
+ "pumpkin" : (255, 117, 24),
+ "pumpkin skin" : (177, 97, 11),
+ "punch" : (220, 67, 51),
+ "punga" : ( 77, 61, 20),
+ "purple" : (102, 0, 153),
+ "purple heart" : (101, 45, 193),
+ "purple mountain's majesty" : (150, 120, 182),
+ "purple pizzazz" : (255, 0, 204),
+ "putty" : (231, 205, 140),
+ "quarter pearl lusta" : (255, 253, 244),
+ "quarter spanish white" : (247, 242, 225),
+ "quicksand" : (189, 151, 142),
+ "quill gray" : (214, 214, 209),
+ "quincy" : ( 98, 63, 45),
+ "racing green" : ( 12, 25, 17),
+ "radical red" : (255, 53, 94),
+ "raffia" : (234, 218, 184),
+ "rainee" : (185, 200, 172),
+ "rajah" : (247, 182, 104),
+ "rangitoto" : ( 46, 50, 34),
+ "rangoon green" : ( 28, 30, 19),
+ "raven" : (114, 123, 137),
+ "raw sienna" : (210, 125, 70),
+ "raw umber" : (115, 74, 18),
+ "razzle dazzle rose" : (255, 51, 204),
+ "razzmatazz" : (227, 11, 92),
+ "rebel" : ( 60, 18, 6),
+ "red" : (255, 0, 0),
+ "red beech" : (123, 56, 1),
+ "red berry" : (142, 0, 0),
+ "red damask" : (218, 106, 65),
+ "red devil" : (134, 1, 17),
+ "red orange" : (255, 63, 52),
+ "red oxide" : (110, 9, 2),
+ "red ribbon" : (237, 10, 63),
+ "red robin" : (128, 52, 31),
+ "red stage" : (208, 95, 4),
+ "red violet" : (199, 21, 133),
+ "redwood" : ( 93, 30, 15),
+ "reef" : (201, 255, 162),
+ "reef gold" : (159, 130, 28),
+ "regal blue" : ( 1, 63, 106),
+ "regent gray" : (134, 148, 159),
+ "regent st blue" : (170, 214, 230),
+ "remy" : (254, 235, 243),
+ "reno sand" : (168, 101, 21),
+ "resolution blue" : ( 0, 35, 135),
+ "revolver" : ( 44, 22, 50),
+ "rhino" : ( 46, 63, 98),
+ "rice cake" : (255, 254, 240),
+ "rice flower" : (238, 255, 226),
+ "rich gold" : (168, 83, 7),
+ "rio grande" : (187, 208, 9),
+ "ripe lemon" : (244, 216, 28),
+ "ripe plum" : ( 65, 0, 86),
+ "riptide" : (139, 230, 216),
+ "river bed" : ( 67, 76, 89),
+ "rob roy" : (234, 198, 116),
+ "robin's egg blue" : ( 0, 204, 204),
+ "rock" : ( 77, 56, 51),
+ "rock blue" : (158, 177, 205),
+ "rock spray" : (186, 69, 12),
+ "rodeo dust" : (201, 178, 155),
+ "rolling stone" : (116, 125, 131),
+ "roman" : (222, 99, 96),
+ "roman coffee" : (121, 93, 76),
+ "romance" : (255, 254, 253),
+ "romantic" : (255, 210, 183),
+ "ronchi" : (236, 197, 78),
+ "roof terracotta" : (166, 47, 32),
+ "rope" : (142, 77, 30),
+ "rose" : (255, 0, 127),
+ "rose bud" : (251, 178, 163),
+ "rose bud cherry" : (128, 11, 71),
+ "rose fog" : (231, 188, 180),
+ "rose white" : (255, 246, 245),
+ "rose of sharon" : (191, 85, 0),
+ "rosewood" : (101, 0, 11),
+ "roti" : (198, 168, 75),
+ "rouge" : (162, 59, 108),
+ "royal blue" : ( 65, 105, 225),
+ "royal heath" : (171, 52, 114),
+ "royal purple" : (107, 63, 160),
+ "rum" : (121, 105, 137),
+ "rum swizzle" : (249, 248, 228),
+ "russet" : (128, 70, 27),
+ "russett" : (117, 90, 87),
+ "rust" : (183, 65, 14),
+ "rustic red" : ( 72, 4, 4),
+ "rusty nail" : (134, 86, 10),
+ "saddle" : ( 76, 48, 36),
+ "saddle brown" : ( 88, 52, 1),
+ "saffron" : (244, 196, 48),
+ "saffron mango" : (249, 191, 88),
+ "sage" : (158, 165, 135),
+ "sahara" : (183, 162, 20),
+ "sahara sand" : (241, 231, 136),
+ "sail" : (184, 224, 249),
+ "salem" : ( 9, 127, 75),
+ "salmon" : (255, 140, 105),
+ "salomie" : (254, 219, 141),
+ "salt box" : (104, 94, 110),
+ "saltpan" : (241, 247, 242),
+ "sambuca" : ( 58, 32, 16),
+ "san felix" : ( 11, 98, 7),
+ "san juan" : ( 48, 75, 106),
+ "san marino" : ( 69, 108, 172),
+ "sand dune" : (130, 111, 101),
+ "sandal" : (170, 141, 111),
+ "sandrift" : (171, 145, 122),
+ "sandstone" : (121, 109, 98),
+ "sandwisp" : (245, 231, 162),
+ "sandy beach" : (255, 234, 200),
+ "sandy brown" : (244, 164, 96),
+ "sangria" : (146, 0, 10),
+ "sanguine brown" : (141, 61, 56),
+ "santa fe" : (177, 109, 82),
+ "santas gray" : (159, 160, 177),
+ "sapling" : (222, 212, 164),
+ "sapphire" : ( 47, 81, 158),
+ "saratoga" : ( 85, 91, 16),
+ "satin linen" : (230, 228, 212),
+ "sauvignon" : (255, 245, 243),
+ "sazerac" : (255, 244, 224),
+ "scampi" : (103, 95, 166),
+ "scandal" : (207, 250, 244),
+ "scarlet" : (255, 36, 0),
+ "scarlet gum" : ( 67, 21, 96),
+ "scarlett" : (149, 0, 21),
+ "scarpa flow" : ( 88, 85, 98),
+ "schist" : (169, 180, 151),
+ "school bus yellow" : (255, 216, 0),
+ "schooner" : (139, 132, 126),
+ "science blue" : ( 0, 102, 204),
+ "scooter" : ( 46, 191, 212),
+ "scorpion" : (105, 95, 98),
+ "scotch mist" : (255, 251, 220),
+ "screamin' green" : (102, 255, 102),
+ "sea buckthorn" : (251, 161, 41),
+ "sea green" : ( 46, 139, 87),
+ "sea mist" : (197, 219, 202),
+ "sea nymph" : (120, 163, 156),
+ "sea pink" : (237, 152, 158),
+ "seagull" : (128, 204, 234),
+ "seance" : (115, 30, 143),
+ "seashell" : (241, 241, 241),
+ "seashell peach" : (255, 245, 238),
+ "seaweed" : ( 27, 47, 17),
+ "selago" : (240, 238, 253),
+ "selective yellow" : (255, 186, 0),
+ "sepia" : (112, 66, 20),
+ "sepia black" : ( 43, 2, 2),
+ "sepia skin" : (158, 91, 64),
+ "serenade" : (255, 244, 232),
+ "shadow" : (131, 112, 80),
+ "shadow green" : (154, 194, 184),
+ "shady lady" : (170, 165, 169),
+ "shakespeare" : ( 78, 171, 209),
+ "shalimar" : (251, 255, 186),
+ "shamrock" : ( 51, 204, 153),
+ "shark" : ( 37, 39, 44),
+ "sherpa blue" : ( 0, 73, 80),
+ "sherwood green" : ( 2, 64, 44),
+ "shilo" : (232, 185, 179),
+ "shingle fawn" : (107, 78, 49),
+ "ship cove" : (120, 139, 186),
+ "ship gray" : ( 62, 58, 68),
+ "shiraz" : (178, 9, 49),
+ "shocking" : (226, 146, 192),
+ "shocking pink" : (252, 15, 192),
+ "shuttle gray" : ( 95, 102, 114),
+ "siam" : (100, 106, 84),
+ "sidecar" : (243, 231, 187),
+ "silk" : (189, 177, 168),
+ "silver" : (192, 192, 192),
+ "silver chalice" : (172, 172, 172),
+ "silver rust" : (201, 192, 187),
+ "silver sand" : (191, 193, 194),
+ "silver tree" : (102, 181, 143),
+ "sinbad" : (159, 215, 211),
+ "siren" : (122, 1, 58),
+ "sirocco" : (113, 128, 128),
+ "sisal" : (211, 203, 186),
+ "skeptic" : (202, 230, 218),
+ "sky blue" : (118, 215, 234),
+ "slate gray" : (112, 128, 144),
+ "smalt" : ( 0, 51, 153),
+ "smalt blue" : ( 81, 128, 143),
+ "smoky" : ( 96, 91, 115),
+ "snow drift" : (247, 250, 247),
+ "snow flurry" : (228, 255, 209),
+ "snowy mint" : (214, 255, 219),
+ "snuff" : (226, 216, 237),
+ "soapstone" : (255, 251, 249),
+ "soft amber" : (209, 198, 180),
+ "soft peach" : (245, 237, 239),
+ "solid pink" : (137, 56, 67),
+ "solitaire" : (254, 248, 226),
+ "solitude" : (234, 246, 255),
+ "sorbus" : (253, 124, 7),
+ "sorrell brown" : (206, 185, 143),
+ "soya bean" : (106, 96, 81),
+ "spanish green" : (129, 152, 133),
+ "spectra" : ( 47, 90, 87),
+ "spice" : (106, 68, 46),
+ "spicy mix" : (136, 83, 66),
+ "spicy mustard" : (116, 100, 13),
+ "spicy pink" : (129, 110, 113),
+ "spindle" : (182, 209, 234),
+ "spray" : (121, 222, 236),
+ "spring green" : ( 0, 255, 127),
+ "spring leaves" : ( 87, 131, 99),
+ "spring rain" : (172, 203, 177),
+ "spring sun" : (246, 255, 220),
+ "spring wood" : (248, 246, 241),
+ "sprout" : (193, 215, 176),
+ "spun pearl" : (170, 171, 183),
+ "squirrel" : (143, 129, 118),
+ "st tropaz" : ( 45, 86, 155),
+ "stack" : (138, 143, 138),
+ "star dust" : (159, 159, 156),
+ "stark white" : (229, 215, 189),
+ "starship" : (236, 242, 69),
+ "steel blue" : ( 70, 130, 180),
+ "steel gray" : ( 38, 35, 53),
+ "stiletto" : (156, 51, 54),
+ "stonewall" : (146, 133, 115),
+ "storm dust" : (100, 100, 99),
+ "storm gray" : (113, 116, 134),
+ "stratos" : ( 0, 7, 65),
+ "straw" : (212, 191, 141),
+ "strikemaster" : (149, 99, 135),
+ "stromboli" : ( 50, 93, 82),
+ "studio" : (113, 74, 178),
+ "submarine" : (186, 199, 201),
+ "sugar cane" : (249, 255, 246),
+ "sulu" : (193, 240, 124),
+ "summer green" : (150, 187, 171),
+ "sun" : (251, 172, 19),
+ "sundance" : (201, 179, 91),
+ "sundown" : (255, 177, 179),
+ "sunflower" : (228, 212, 34),
+ "sunglo" : (225, 104, 101),
+ "sunglow" : (255, 204, 51),
+ "sunset orange" : (254, 76, 64),
+ "sunshade" : (255, 158, 44),
+ "supernova" : (255, 201, 1),
+ "surf" : (187, 215, 193),
+ "surf crest" : (207, 229, 210),
+ "surfie green" : ( 12, 122, 121),
+ "sushi" : (135, 171, 57),
+ "suva gray" : (136, 131, 135),
+ "swamp" : ( 0, 27, 28),
+ "swamp green" : (172, 183, 142),
+ "swans down" : (220, 240, 234),
+ "sweet corn" : (251, 234, 140),
+ "sweet pink" : (253, 159, 162),
+ "swirl" : (211, 205, 197),
+ "swiss coffee" : (221, 214, 213),
+ "sycamore" : (144, 141, 57),
+ "tabasco" : (160, 39, 18),
+ "tacao" : (237, 179, 129),
+ "tacha" : (214, 197, 98),
+ "tahiti gold" : (233, 124, 7),
+ "tahuna sands" : (238, 240, 200),
+ "tall poppy" : (179, 45, 41),
+ "tallow" : (168, 165, 137),
+ "tamarillo" : (153, 22, 19),
+ "tamarind" : ( 52, 21, 21),
+ "tan" : (210, 180, 140),
+ "tan hide" : (250, 157, 90),
+ "tana" : (217, 220, 193),
+ "tangaroa" : ( 3, 22, 60),
+ "tangerine" : (242, 133, 0),
+ "tango" : (237, 122, 28),
+ "tapa" : (123, 120, 116),
+ "tapestry" : (176, 94, 129),
+ "tara" : (225, 246, 232),
+ "tarawera" : ( 7, 58, 80),
+ "tasman" : (207, 220, 207),
+ "taupe" : ( 72, 60, 50),
+ "taupe gray" : (179, 175, 149),
+ "tawny port" : (105, 37, 69),
+ "te papa green" : ( 30, 67, 60),
+ "tea" : (193, 186, 176),
+ "tea green" : (208, 240, 192),
+ "teak" : (177, 148, 97),
+ "teal" : ( 0, 128, 128),
+ "teal blue" : ( 4, 66, 89),
+ "temptress" : ( 59, 0, 11),
+ "tenn" : (205, 87, 0),
+ "tequila" : (255, 230, 199),
+ "terracotta" : (226, 114, 91),
+ "texas" : (248, 249, 156),
+ "texas rose" : (255, 181, 85),
+ "thatch" : (182, 157, 152),
+ "thatch green" : ( 64, 61, 25),
+ "thistle" : (216, 191, 216),
+ "thistle green" : (204, 202, 168),
+ "thunder" : ( 51, 41, 47),
+ "thunderbird" : (192, 43, 24),
+ "tia maria" : (193, 68, 14),
+ "tiara" : (195, 209, 209),
+ "tiber" : ( 6, 53, 55),
+ "tickle me pink" : (252, 128, 165),
+ "tidal" : (241, 255, 173),
+ "tide" : (191, 184, 176),
+ "timber green" : ( 22, 50, 44),
+ "timberwolf" : (217, 214, 207),
+ "titan white" : (240, 238, 255),
+ "toast" : (154, 110, 97),
+ "tobacco brown" : (113, 93, 71),
+ "toledo" : ( 58, 0, 32),
+ "tolopea" : ( 27, 2, 69),
+ "tom thumb" : ( 63, 88, 59),
+ "tonys pink" : (231, 159, 140),
+ "topaz" : (124, 119, 138),
+ "torch red" : (253, 14, 53),
+ "torea bay" : ( 15, 45, 158),
+ "tory blue" : ( 20, 80, 170),
+ "tosca" : (141, 63, 63),
+ "totem pole" : (153, 27, 7),
+ "tower gray" : (169, 189, 191),
+ "tradewind" : ( 95, 179, 172),
+ "tranquil" : (230, 255, 255),
+ "travertine" : (255, 253, 232),
+ "tree poppy" : (252, 156, 29),
+ "treehouse" : ( 59, 40, 32),
+ "trendy green" : (124, 136, 26),
+ "trendy pink" : (140, 100, 149),
+ "trinidad" : (230, 78, 3),
+ "tropical blue" : (195, 221, 249),
+ "tropical rain forest" : ( 0, 117, 94),
+ "trout" : ( 74, 78, 90),
+ "true v" : (138, 115, 214),
+ "tuatara" : ( 54, 53, 52),
+ "tuft bush" : (255, 221, 205),
+ "tulip tree" : (234, 179, 59),
+ "tumbleweed" : (222, 166, 129),
+ "tuna" : ( 53, 53, 66),
+ "tundora" : ( 74, 66, 68),
+ "turbo" : (250, 230, 0),
+ "turkish rose" : (181, 114, 129),
+ "turmeric" : (202, 187, 72),
+ "turquoise" : ( 48, 213, 200),
+ "turquoise blue" : (108, 218, 231),
+ "turtle green" : ( 42, 56, 11),
+ "tuscany" : (189, 94, 46),
+ "tusk" : (238, 243, 195),
+ "tussock" : (197, 153, 75),
+ "tutu" : (255, 241, 249),
+ "twilight" : (228, 207, 222),
+ "twilight blue" : (238, 253, 255),
+ "twine" : (194, 149, 93),
+ "tyrian purple" : (102, 2, 60),
+ "ultramarine" : ( 18, 10, 143),
+ "valencia" : (216, 68, 55),
+ "valentino" : ( 53, 14, 66),
+ "valhalla" : ( 43, 25, 79),
+ "van cleef" : ( 73, 23, 12),
+ "vanilla" : (209, 190, 168),
+ "vanilla ice" : (243, 217, 223),
+ "varden" : (255, 246, 223),
+ "venetian red" : (114, 1, 15),
+ "venice blue" : ( 5, 89, 137),
+ "venus" : (146, 133, 144),
+ "verdigris" : ( 93, 94, 55),
+ "verdun green" : ( 73, 84, 0),
+ "vermilion" : (255, 77, 0),
+ "vesuvius" : (177, 74, 11),
+ "victoria" : ( 83, 68, 145),
+ "vida loca" : ( 84, 144, 25),
+ "viking" : (100, 204, 219),
+ "vin rouge" : (152, 61, 97),
+ "viola" : (203, 143, 169),
+ "violent violet" : ( 41, 12, 94),
+ "violet" : ( 36, 10, 64),
+ "violet eggplant" : (153, 17, 153),
+ "violet red" : (247, 70, 138),
+ "viridian" : ( 64, 130, 109),
+ "viridian green" : (103, 137, 117),
+ "vis vis" : (255, 239, 161),
+ "vista blue" : (143, 214, 180),
+ "vista white" : (252, 248, 247),
+ "vivid tangerine" : (255, 153, 128),
+ "vivid violet" : (128, 55, 144),
+ "voodoo" : ( 83, 52, 85),
+ "vulcan" : ( 16, 18, 29),
+ "wafer" : (222, 203, 198),
+ "waikawa gray" : ( 90, 110, 156),
+ "waiouru" : ( 54, 60, 13),
+ "walnut" : (119, 63, 26),
+ "wasabi" : (120, 138, 37),
+ "water leaf" : (161, 233, 222),
+ "watercourse" : ( 5, 111, 87),
+ "waterloo " : (123, 124, 148),
+ "wattle" : (220, 215, 71),
+ "watusi" : (255, 221, 207),
+ "wax flower" : (255, 192, 168),
+ "we peep" : (247, 219, 230),
+ "web orange" : (255, 165, 0),
+ "wedgewood" : ( 78, 127, 158),
+ "well read" : (180, 51, 50),
+ "west coast" : ( 98, 81, 25),
+ "west side" : (255, 145, 15),
+ "westar" : (220, 217, 210),
+ "wewak" : (241, 155, 171),
+ "wheat" : (245, 222, 179),
+ "wheatfield" : (243, 237, 207),
+ "whiskey" : (213, 154, 111),
+ "whisper" : (247, 245, 250),
+ "white" : (255, 255, 255),
+ "white ice" : (221, 249, 241),
+ "white lilac" : (248, 247, 252),
+ "white linen" : (248, 240, 232),
+ "white pointer" : (254, 248, 255),
+ "white rock" : (234, 232, 212),
+ "wild blue yonder" : (122, 137, 184),
+ "wild rice" : (236, 224, 144),
+ "wild sand" : (244, 244, 244),
+ "wild strawberry" : (255, 51, 153),
+ "wild watermelon" : (253, 91, 120),
+ "wild willow" : (185, 196, 106),
+ "william" : ( 58, 104, 108),
+ "willow brook" : (223, 236, 218),
+ "willow grove" : (101, 116, 93),
+ "windsor" : ( 60, 8, 120),
+ "wine berry" : ( 89, 29, 53),
+ "winter hazel" : (213, 209, 149),
+ "wisp pink" : (254, 244, 248),
+ "wisteria" : (151, 113, 181),
+ "wistful" : (164, 166, 211),
+ "witch haze" : (255, 252, 153),
+ "wood bark" : ( 38, 17, 5),
+ "woodland" : ( 77, 83, 40),
+ "woodrush" : ( 48, 42, 15),
+ "woodsmoke" : ( 12, 13, 15),
+ "woody brown" : ( 72, 49, 49),
+ "xanadu" : (115, 134, 120),
+ "yellow" : (255, 255, 0),
+ "yellow green" : (197, 225, 122),
+ "yellow metal" : (113, 99, 56),
+ "yellow orange" : (255, 174, 66),
+ "yellow sea" : (254, 169, 4),
+ "your pink" : (255, 195, 192),
+ "yukon gold" : (123, 102, 8),
+ "yuma" : (206, 194, 145),
+ "zambezi" : (104, 85, 88),
+ "zanah" : (218, 236, 214),
+ "zest" : (229, 132, 27),
+ "zeus" : ( 41, 35, 25),
+ "ziggurat" : (191, 219, 226),
+ "zinnwaldite" : (235, 194, 175),
+ "zircon" : (244, 248, 255),
+ "zombie" : (228, 214, 155),
+ "zorba" : (165, 155, 145),
+ "zuccini" : ( 4, 64, 34),
+ "zumthor" : (237, 246, 255)}
+
+def build_reverse_dict():
+ global reverse
+ global colorhex
+ global colors
+ for color in colors:
+ rgb = colors[color]
+ hex = '#%02X%02X%02X' % (rgb)
+ reverse[hex] = color
+ colorhex[color] = hex
+ return
+
+
+def get_complementary_hex(color):
+ # strip the # from the beginning
+ color = color[1:]
+ # convert the string into hex
+ color = int(color, 16)
+ # invert the three bytes
+ # as good as substracting each of RGB component by 255(FF)
+ comp_color = 0xFFFFFF ^ color
+ # convert the color back to hex by prefixing a #
+ comp_color = "#%06X" % comp_color
+ # return the result
+ return comp_color
+
+def get_complementary_rgb(red, green, blue):
+ color_string = '#%02X%02X%02X' % (red, green, blue)
+ # strip the # from the beginning
+ color = color_string[1:]
+ # convert the string into hex
+ color = int(color, 16)
+ # invert the three bytes
+ # as good as substracting each of RGB component by 255(FF)
+ comp_color = 0xFFFFFF ^ color
+ # convert the color back to hex by prefixing a #
+ comp_color = "#%06X" % comp_color
+ # return the result
+ return comp_color
+
+def get_name_from_hex(hex):
+ global reverse
+ global colorhex
+ global colors
+
+ hex = hex.upper()
+ try:
+ name = reverse[hex]
+ except:
+ name = 'No Hex For Name'
+ return name
+
+def get_hex_from_name(name):
+ global reverse
+ global colorhex
+ global colors
+
+ name = name.lower()
+ try:
+ hex = colorhex[name]
+ except:
+ hex = '#000000'
+ return hex
+
+def show_all_colors_on_buttons():
+ global reverse
+ global colorhex
+ global colors
+ window = sg.Window('Colors on Buttons Demo', default_element_size=(3, 1), location=(0, 0), icon=MY_WINDOW_ICON, font=("Helvetica", 7))
+ row = []
+ row_len = 20
+ for i, c in enumerate(colors):
+ hex = get_hex_from_name(c)
+ button1 = sg.CButton(button_text=c, button_color=(get_complementary_hex(hex), hex), size=(8, 1))
+ button2 = sg.CButton(button_text=c, button_color=(hex, get_complementary_hex(hex)), size=(8, 1))
+ row.append(button1)
+ row.append(button2)
+ if (i+1) % row_len == 0:
+ window.AddRow(*row)
+ row = []
+ if row != []:
+ window.AddRow(*row)
+ window.Show()
+
+
+GoodColors = [('#0e6251', sg.RGB(255, 246, 122)),
+ ('white', sg.RGB(0, 74, 60)),
+ (sg.RGB(0, 210, 124), sg.RGB(0, 74, 60)),
+ (sg.RGB(0, 210, 87), sg.RGB(0, 74, 60)),
+ (sg.RGB(0, 164, 73), sg.RGB(0, 74, 60)),
+ (sg.RGB(0, 74, 60), sg.RGB(0, 74, 60)),
+ ]
+
+
+def main():
+ global colors
+ global reverse
+
+ build_reverse_dict()
+ list_of_colors = [c for c in colors]
+ printable = '\n'.join(map(str, list_of_colors))
+ # show_all_colors_on_buttons()
+ sg.SetOptions(element_padding=(0,0))
+ while True:
+ # ------- Form show ------- #
+ layout = [[sg.Text('Find color')],
+ [sg.Text('Demonstration of colors')],
+ [sg.Text('Enter a color name in text or hex #RRGGBB format')],
+ [sg.InputText(key='hex')],
+ [sg.Listbox(list_of_colors, size=(20, 30), bind_return_key=True, key='listbox'), sg.T('Or choose from list')],
+ [sg.Submit(), sg.Button('Many buttons', button_color=('white', '#0e6251'), key='Many buttons'), sg.ColorChooserButton( 'Chooser', target=(3,0), key='Chooser'), sg.Quit(),],
+ ]
+ # [g.Multiline(DefaultText=str(printable), Size=(30,20))]]
+ event, values = sg.Window('Color Demo', auto_size_buttons=False).Layout(layout).Read()
+
+ # ------- OUTPUT results portion ------- #
+ if event == 'Quit' or event is None:
+ exit(0)
+ elif event == 'Many buttons':
+ show_all_colors_on_buttons()
+
+ drop_down_value = values['listbox']
+ hex_input = values['hex']
+ if hex_input == '' and len(drop_down_value) == 0:
+ continue
+
+ if len(hex_input) != 0:
+ if hex_input[0] == '#':
+ color_hex = hex_input.upper()
+ color_name = get_name_from_hex(hex_input)
+ else:
+ color_name = hex_input
+ color_hex = get_hex_from_name(color_name)
+ elif drop_down_value is not None and len(drop_down_value) != 0:
+ color_name = drop_down_value[0]
+ color_hex = get_hex_from_name(color_name)
+
+ complementary_hex = get_complementary_hex(color_hex)
+ complementary_color = get_name_from_hex(complementary_hex)
+
+ layout = [[sg.Text('That color and it\'s compliment are shown on these buttons. This form auto-closes')],
+ [sg.CloseButton(button_text=color_name, button_color=(color_hex, complementary_hex))],
+ [sg.CloseButton(button_text=complementary_hex + ' ' + complementary_color, button_color=(complementary_hex , color_hex), size=(30, 1))],
+ ]
+ sg.Window('Color demo', default_element_size=(100, 1), auto_size_text=True, auto_close=True, auto_close_duration=5, icon=MY_WINDOW_ICON).Layout(layout).Read()
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Color_Names.py b/DemoPrograms old/Demo_Color_Names.py
new file mode 100644
index 00000000..aa080f30
--- /dev/null
+++ b/DemoPrograms old/Demo_Color_Names.py
@@ -0,0 +1,709 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+
+"""
+
+ Shows a big chart of colors... give it a few seconds to create it
+ Once large window is shown, you can click on any color and another window will popup
+ showing both white and black text on that color
+ Uses TOOLTIPS to show the hex values for the colors. Hover over a color and a tooltip will show you the RGB
+ You will find the list of tkinter colors here:
+ http://www.tcl.tk/man/tcl8.5/TkCmd/colors.htm
+
+"""
+
+color_map = {
+ 'alice blue': '#F0F8FF',
+ 'AliceBlue': '#F0F8FF',
+ 'antique white': '#FAEBD7',
+ 'AntiqueWhite': '#FAEBD7',
+ 'AntiqueWhite1': '#FFEFDB',
+ 'AntiqueWhite2': '#EEDFCC',
+ 'AntiqueWhite3': '#CDC0B0',
+ 'AntiqueWhite4': '#8B8378',
+ 'aquamarine': '#7FFFD4',
+ 'aquamarine1': '#7FFFD4',
+ 'aquamarine2': '#76EEC6',
+ 'aquamarine3': '#66CDAA',
+ 'aquamarine4': '#458B74',
+ 'azure': '#F0FFFF',
+ 'azure1': '#F0FFFF',
+ 'azure2': '#E0EEEE',
+ 'azure3': '#C1CDCD',
+ 'azure4': '#838B8B',
+ 'beige': '#F5F5DC',
+ 'bisque': '#FFE4C4',
+ 'bisque1': '#FFE4C4',
+ 'bisque2': '#EED5B7',
+ 'bisque3': '#CDB79E',
+ 'bisque4': '#8B7D6B',
+ 'black': '#000000',
+ 'blanched almond': '#FFEBCD',
+ 'BlanchedAlmond': '#FFEBCD',
+ 'blue': '#0000FF',
+ 'blue violet': '#8A2BE2',
+ 'blue1': '#0000FF',
+ 'blue2': '#0000EE',
+ 'blue3': '#0000CD',
+ 'blue4': '#00008B',
+ 'BlueViolet': '#8A2BE2',
+ 'brown': '#A52A2A',
+ 'brown1': '#FF4040',
+ 'brown2': '#EE3B3B',
+ 'brown3': '#CD3333',
+ 'brown4': '#8B2323',
+ 'burlywood': '#DEB887',
+ 'burlywood1': '#FFD39B',
+ 'burlywood2': '#EEC591',
+ 'burlywood3': '#CDAA7D',
+ 'burlywood4': '#8B7355',
+ 'cadet blue': '#5F9EA0',
+ 'CadetBlue': '#5F9EA0',
+ 'CadetBlue1': '#98F5FF',
+ 'CadetBlue2': '#8EE5EE',
+ 'CadetBlue3': '#7AC5CD',
+ 'CadetBlue4': '#53868B',
+ 'chartreuse': '#7FFF00',
+ 'chartreuse1': '#7FFF00',
+ 'chartreuse2': '#76EE00',
+ 'chartreuse3': '#66CD00',
+ 'chartreuse4': '#458B00',
+ 'chocolate': '#D2691E',
+ 'chocolate1': '#FF7F24',
+ 'chocolate2': '#EE7621',
+ 'chocolate3': '#CD661D',
+ 'chocolate4': '#8B4513',
+ 'coral': '#FF7F50',
+ 'coral1': '#FF7256',
+ 'coral2': '#EE6A50',
+ 'coral3': '#CD5B45',
+ 'coral4': '#8B3E2F',
+ 'cornflower blue': '#6495ED',
+ 'CornflowerBlue': '#6495ED',
+ 'cornsilk': '#FFF8DC',
+ 'cornsilk1': '#FFF8DC',
+ 'cornsilk2': '#EEE8CD',
+ 'cornsilk3': '#CDC8B1',
+ 'cornsilk4': '#8B8878',
+ 'cyan': '#00FFFF',
+ 'cyan1': '#00FFFF',
+ 'cyan2': '#00EEEE',
+ 'cyan3': '#00CDCD',
+ 'cyan4': '#008B8B',
+ 'dark blue': '#00008B',
+ 'dark cyan': '#008B8B',
+ 'dark goldenrod': '#B8860B',
+ 'dark gray': '#A9A9A9',
+ 'dark green': '#006400',
+ 'dark grey': '#A9A9A9',
+ 'dark khaki': '#BDB76B',
+ 'dark magenta': '#8B008B',
+ 'dark olive green': '#556B2F',
+ 'dark orange': '#FF8C00',
+ 'dark orchid': '#9932CC',
+ 'dark red': '#8B0000',
+ 'dark salmon': '#E9967A',
+ 'dark sea green': '#8FBC8F',
+ 'dark slate blue': '#483D8B',
+ 'dark slate gray': '#2F4F4F',
+ 'dark slate grey': '#2F4F4F',
+ 'dark turquoise': '#00CED1',
+ 'dark violet': '#9400D3',
+ 'DarkBlue': '#00008B',
+ 'DarkCyan': '#008B8B',
+ 'DarkGoldenrod': '#B8860B',
+ 'DarkGoldenrod1': '#FFB90F',
+ 'DarkGoldenrod2': '#EEAD0E',
+ 'DarkGoldenrod3': '#CD950C',
+ 'DarkGoldenrod4': '#8B6508',
+ 'DarkGray': '#A9A9A9',
+ 'DarkGreen': '#006400',
+ 'DarkGrey': '#A9A9A9',
+ 'DarkKhaki': '#BDB76B',
+ 'DarkMagenta': '#8B008B',
+ 'DarkOliveGreen': '#556B2F',
+ 'DarkOliveGreen1': '#CAFF70',
+ 'DarkOliveGreen2': '#BCEE68',
+ 'DarkOliveGreen3': '#A2CD5A',
+ 'DarkOliveGreen4': '#6E8B3D',
+ 'DarkOrange': '#FF8C00',
+ 'DarkOrange1': '#FF7F00',
+ 'DarkOrange2': '#EE7600',
+ 'DarkOrange3': '#CD6600',
+ 'DarkOrange4': '#8B4500',
+ 'DarkOrchid': '#9932CC',
+ 'DarkOrchid1': '#BF3EFF',
+ 'DarkOrchid2': '#B23AEE',
+ 'DarkOrchid3': '#9A32CD',
+ 'DarkOrchid4': '#68228B',
+ 'DarkRed': '#8B0000',
+ 'DarkSalmon': '#E9967A',
+ 'DarkSeaGreen': '#8FBC8F',
+ 'DarkSeaGreen1': '#C1FFC1',
+ 'DarkSeaGreen2': '#B4EEB4',
+ 'DarkSeaGreen3': '#9BCD9B',
+ 'DarkSeaGreen4': '#698B69',
+ 'DarkSlateBlue': '#483D8B',
+ 'DarkSlateGray': '#2F4F4F',
+ 'DarkSlateGray1': '#97FFFF',
+ 'DarkSlateGray2': '#8DEEEE',
+ 'DarkSlateGray3': '#79CDCD',
+ 'DarkSlateGray4': '#528B8B',
+ 'DarkSlateGrey': '#2F4F4F',
+ 'DarkTurquoise': '#00CED1',
+ 'DarkViolet': '#9400D3',
+ 'deep pink': '#FF1493',
+ 'deep sky blue': '#00BFFF',
+ 'DeepPink': '#FF1493',
+ 'DeepPink1': '#FF1493',
+ 'DeepPink2': '#EE1289',
+ 'DeepPink3': '#CD1076',
+ 'DeepPink4': '#8B0A50',
+ 'DeepSkyBlue': '#00BFFF',
+ 'DeepSkyBlue1': '#00BFFF',
+ 'DeepSkyBlue2': '#00B2EE',
+ 'DeepSkyBlue3': '#009ACD',
+ 'DeepSkyBlue4': '#00688B',
+ 'dim gray': '#696969',
+ 'dim grey': '#696969',
+ 'DimGray': '#696969',
+ 'DimGrey': '#696969',
+ 'dodger blue': '#1E90FF',
+ 'DodgerBlue': '#1E90FF',
+ 'DodgerBlue1': '#1E90FF',
+ 'DodgerBlue2': '#1C86EE',
+ 'DodgerBlue3': '#1874CD',
+ 'DodgerBlue4': '#104E8B',
+ 'firebrick': '#B22222',
+ 'firebrick1': '#FF3030',
+ 'firebrick2': '#EE2C2C',
+ 'firebrick3': '#CD2626',
+ 'firebrick4': '#8B1A1A',
+ 'floral white': '#FFFAF0',
+ 'FloralWhite': '#FFFAF0',
+ 'forest green': '#228B22',
+ 'ForestGreen': '#228B22',
+ 'gainsboro': '#DCDCDC',
+ 'ghost white': '#F8F8FF',
+ 'GhostWhite': '#F8F8FF',
+ 'gold': '#FFD700',
+ 'gold1': '#FFD700',
+ 'gold2': '#EEC900',
+ 'gold3': '#CDAD00',
+ 'gold4': '#8B7500',
+ 'goldenrod': '#DAA520',
+ 'goldenrod1': '#FFC125',
+ 'goldenrod2': '#EEB422',
+ 'goldenrod3': '#CD9B1D',
+ 'goldenrod4': '#8B6914',
+ 'green': '#00FF00',
+ 'green yellow': '#ADFF2F',
+ 'green1': '#00FF00',
+ 'green2': '#00EE00',
+ 'green3': '#00CD00',
+ 'green4': '#008B00',
+ 'GreenYellow': '#ADFF2F',
+ 'grey': '#BEBEBE',
+ 'grey0': '#000000',
+ 'grey1': '#030303',
+ 'grey2': '#050505',
+ 'grey3': '#080808',
+ 'grey4': '#0A0A0A',
+ 'grey5': '#0D0D0D',
+ 'grey6': '#0F0F0F',
+ 'grey7': '#121212',
+ 'grey8': '#141414',
+ 'grey9': '#171717',
+ 'grey10': '#1A1A1A',
+ 'grey11': '#1C1C1C',
+ 'grey12': '#1F1F1F',
+ 'grey13': '#212121',
+ 'grey14': '#242424',
+ 'grey15': '#262626',
+ 'grey16': '#292929',
+ 'grey17': '#2B2B2B',
+ 'grey18': '#2E2E2E',
+ 'grey19': '#303030',
+ 'grey20': '#333333',
+ 'grey21': '#363636',
+ 'grey22': '#383838',
+ 'grey23': '#3B3B3B',
+ 'grey24': '#3D3D3D',
+ 'grey25': '#404040',
+ 'grey26': '#424242',
+ 'grey27': '#454545',
+ 'grey28': '#474747',
+ 'grey29': '#4A4A4A',
+ 'grey30': '#4D4D4D',
+ 'grey31': '#4F4F4F',
+ 'grey32': '#525252',
+ 'grey33': '#545454',
+ 'grey34': '#575757',
+ 'grey35': '#595959',
+ 'grey36': '#5C5C5C',
+ 'grey37': '#5E5E5E',
+ 'grey38': '#616161',
+ 'grey39': '#636363',
+ 'grey40': '#666666',
+ 'grey41': '#696969',
+ 'grey42': '#6B6B6B',
+ 'grey43': '#6E6E6E',
+ 'grey44': '#707070',
+ 'grey45': '#737373',
+ 'grey46': '#757575',
+ 'grey47': '#787878',
+ 'grey48': '#7A7A7A',
+ 'grey49': '#7D7D7D',
+ 'grey50': '#7F7F7F',
+ 'grey51': '#828282',
+ 'grey52': '#858585',
+ 'grey53': '#878787',
+ 'grey54': '#8A8A8A',
+ 'grey55': '#8C8C8C',
+ 'grey56': '#8F8F8F',
+ 'grey57': '#919191',
+ 'grey58': '#949494',
+ 'grey59': '#969696',
+ 'grey60': '#999999',
+ 'grey61': '#9C9C9C',
+ 'grey62': '#9E9E9E',
+ 'grey63': '#A1A1A1',
+ 'grey64': '#A3A3A3',
+ 'grey65': '#A6A6A6',
+ 'grey66': '#A8A8A8',
+ 'grey67': '#ABABAB',
+ 'grey68': '#ADADAD',
+ 'grey69': '#B0B0B0',
+ 'grey70': '#B3B3B3',
+ 'grey71': '#B5B5B5',
+ 'grey72': '#B8B8B8',
+ 'grey73': '#BABABA',
+ 'grey74': '#BDBDBD',
+ 'grey75': '#BFBFBF',
+ 'grey76': '#C2C2C2',
+ 'grey77': '#C4C4C4',
+ 'grey78': '#C7C7C7',
+ 'grey79': '#C9C9C9',
+ 'grey80': '#CCCCCC',
+ 'grey81': '#CFCFCF',
+ 'grey82': '#D1D1D1',
+ 'grey83': '#D4D4D4',
+ 'grey84': '#D6D6D6',
+ 'grey85': '#D9D9D9',
+ 'grey86': '#DBDBDB',
+ 'grey87': '#DEDEDE',
+ 'grey88': '#E0E0E0',
+ 'grey89': '#E3E3E3',
+ 'grey90': '#E5E5E5',
+ 'grey91': '#E8E8E8',
+ 'grey92': '#EBEBEB',
+ 'grey93': '#EDEDED',
+ 'grey94': '#F0F0F0',
+ 'grey95': '#F2F2F2',
+ 'grey96': '#F5F5F5',
+ 'grey97': '#F7F7F7',
+ 'grey98': '#FAFAFA',
+ 'grey99': '#FCFCFC',
+ 'grey100': '#FFFFFF',
+ 'honeydew': '#F0FFF0',
+ 'honeydew1': '#F0FFF0',
+ 'honeydew2': '#E0EEE0',
+ 'honeydew3': '#C1CDC1',
+ 'honeydew4': '#838B83',
+ 'hot pink': '#FF69B4',
+ 'HotPink': '#FF69B4',
+ 'HotPink1': '#FF6EB4',
+ 'HotPink2': '#EE6AA7',
+ 'HotPink3': '#CD6090',
+ 'HotPink4': '#8B3A62',
+ 'indian red': '#CD5C5C',
+ 'IndianRed': '#CD5C5C',
+ 'IndianRed1': '#FF6A6A',
+ 'IndianRed2': '#EE6363',
+ 'IndianRed3': '#CD5555',
+ 'IndianRed4': '#8B3A3A',
+ 'ivory': '#FFFFF0',
+ 'ivory1': '#FFFFF0',
+ 'ivory2': '#EEEEE0',
+ 'ivory3': '#CDCDC1',
+ 'ivory4': '#8B8B83',
+ 'khaki': '#F0E68C',
+ 'khaki1': '#FFF68F',
+ 'khaki2': '#EEE685',
+ 'khaki3': '#CDC673',
+ 'khaki4': '#8B864E',
+ 'lavender': '#E6E6FA',
+ 'lavender blush': '#FFF0F5',
+ 'LavenderBlush': '#FFF0F5',
+ 'LavenderBlush1': '#FFF0F5',
+ 'LavenderBlush2': '#EEE0E5',
+ 'LavenderBlush3': '#CDC1C5',
+ 'LavenderBlush4': '#8B8386',
+ 'lawn green': '#7CFC00',
+ 'LawnGreen': '#7CFC00',
+ 'lemon chiffon': '#FFFACD',
+ 'LemonChiffon': '#FFFACD',
+ 'LemonChiffon1': '#FFFACD',
+ 'LemonChiffon2': '#EEE9BF',
+ 'LemonChiffon3': '#CDC9A5',
+ 'LemonChiffon4': '#8B8970',
+ 'light blue': '#ADD8E6',
+ 'light coral': '#F08080',
+ 'light cyan': '#E0FFFF',
+ 'light goldenrod': '#EEDD82',
+ 'light goldenrod yellow': '#FAFAD2',
+ 'light gray': '#D3D3D3',
+ 'light green': '#90EE90',
+ 'light grey': '#D3D3D3',
+ 'light pink': '#FFB6C1',
+ 'light salmon': '#FFA07A',
+ 'light sea green': '#20B2AA',
+ 'light sky blue': '#87CEFA',
+ 'light slate blue': '#8470FF',
+ 'light slate gray': '#778899',
+ 'light slate grey': '#778899',
+ 'light steel blue': '#B0C4DE',
+ 'light yellow': '#FFFFE0',
+ 'LightBlue': '#ADD8E6',
+ 'LightBlue1': '#BFEFFF',
+ 'LightBlue2': '#B2DFEE',
+ 'LightBlue3': '#9AC0CD',
+ 'LightBlue4': '#68838B',
+ 'LightCoral': '#F08080',
+ 'LightCyan': '#E0FFFF',
+ 'LightCyan1': '#E0FFFF',
+ 'LightCyan2': '#D1EEEE',
+ 'LightCyan3': '#B4CDCD',
+ 'LightCyan4': '#7A8B8B',
+ 'LightGoldenrod': '#EEDD82',
+ 'LightGoldenrod1': '#FFEC8B',
+ 'LightGoldenrod2': '#EEDC82',
+ 'LightGoldenrod3': '#CDBE70',
+ 'LightGoldenrod4': '#8B814C',
+ 'LightGoldenrodYellow': '#FAFAD2',
+ 'LightGray': '#D3D3D3',
+ 'LightGreen': '#90EE90',
+ 'LightGrey': '#D3D3D3',
+ 'LightPink': '#FFB6C1',
+ 'LightPink1': '#FFAEB9',
+ 'LightPink2': '#EEA2AD',
+ 'LightPink3': '#CD8C95',
+ 'LightPink4': '#8B5F65',
+ 'LightSalmon': '#FFA07A',
+ 'LightSalmon1': '#FFA07A',
+ 'LightSalmon2': '#EE9572',
+ 'LightSalmon3': '#CD8162',
+ 'LightSalmon4': '#8B5742',
+ 'LightSeaGreen': '#20B2AA',
+ 'LightSkyBlue': '#87CEFA',
+ 'LightSkyBlue1': '#B0E2FF',
+ 'LightSkyBlue2': '#A4D3EE',
+ 'LightSkyBlue3': '#8DB6CD',
+ 'LightSkyBlue4': '#607B8B',
+ 'LightSlateBlue': '#8470FF',
+ 'LightSlateGray': '#778899',
+ 'LightSlateGrey': '#778899',
+ 'LightSteelBlue': '#B0C4DE',
+ 'LightSteelBlue1': '#CAE1FF',
+ 'LightSteelBlue2': '#BCD2EE',
+ 'LightSteelBlue3': '#A2B5CD',
+ 'LightSteelBlue4': '#6E7B8B',
+ 'LightYellow': '#FFFFE0',
+ 'LightYellow1': '#FFFFE0',
+ 'LightYellow2': '#EEEED1',
+ 'LightYellow3': '#CDCDB4',
+ 'LightYellow4': '#8B8B7A',
+ 'lime green': '#32CD32',
+ 'LimeGreen': '#32CD32',
+ 'linen': '#FAF0E6',
+ 'magenta': '#FF00FF',
+ 'magenta1': '#FF00FF',
+ 'magenta2': '#EE00EE',
+ 'magenta3': '#CD00CD',
+ 'magenta4': '#8B008B',
+ 'maroon': '#B03060',
+ 'maroon1': '#FF34B3',
+ 'maroon2': '#EE30A7',
+ 'maroon3': '#CD2990',
+ 'maroon4': '#8B1C62',
+ 'medium aquamarine': '#66CDAA',
+ 'medium blue': '#0000CD',
+ 'medium orchid': '#BA55D3',
+ 'medium purple': '#9370DB',
+ 'medium sea green': '#3CB371',
+ 'medium slate blue': '#7B68EE',
+ 'medium spring green': '#00FA9A',
+ 'medium turquoise': '#48D1CC',
+ 'medium violet red': '#C71585',
+ 'MediumAquamarine': '#66CDAA',
+ 'MediumBlue': '#0000CD',
+ 'MediumOrchid': '#BA55D3',
+ 'MediumOrchid1': '#E066FF',
+ 'MediumOrchid2': '#D15FEE',
+ 'MediumOrchid3': '#B452CD',
+ 'MediumOrchid4': '#7A378B',
+ 'MediumPurple': '#9370DB',
+ 'MediumPurple1': '#AB82FF',
+ 'MediumPurple2': '#9F79EE',
+ 'MediumPurple3': '#8968CD',
+ 'MediumPurple4': '#5D478B',
+ 'MediumSeaGreen': '#3CB371',
+ 'MediumSlateBlue': '#7B68EE',
+ 'MediumSpringGreen': '#00FA9A',
+ 'MediumTurquoise': '#48D1CC',
+ 'MediumVioletRed': '#C71585',
+ 'midnight blue': '#191970',
+ 'MidnightBlue': '#191970',
+ 'mint cream': '#F5FFFA',
+ 'MintCream': '#F5FFFA',
+ 'misty rose': '#FFE4E1',
+ 'MistyRose': '#FFE4E1',
+ 'MistyRose1': '#FFE4E1',
+ 'MistyRose2': '#EED5D2',
+ 'MistyRose3': '#CDB7B5',
+ 'MistyRose4': '#8B7D7B',
+ 'moccasin': '#FFE4B5',
+ 'navajo white': '#FFDEAD',
+ 'NavajoWhite': '#FFDEAD',
+ 'NavajoWhite1': '#FFDEAD',
+ 'NavajoWhite2': '#EECFA1',
+ 'NavajoWhite3': '#CDB38B',
+ 'NavajoWhite4': '#8B795E',
+ 'navy': '#000080',
+ 'navy blue': '#000080',
+ 'NavyBlue': '#000080',
+ 'old lace': '#FDF5E6',
+ 'OldLace': '#FDF5E6',
+ 'olive drab': '#6B8E23',
+ 'OliveDrab': '#6B8E23',
+ 'OliveDrab1': '#C0FF3E',
+ 'OliveDrab2': '#B3EE3A',
+ 'OliveDrab3': '#9ACD32',
+ 'OliveDrab4': '#698B22',
+ 'orange': '#FFA500',
+ 'orange red': '#FF4500',
+ 'orange1': '#FFA500',
+ 'orange2': '#EE9A00',
+ 'orange3': '#CD8500',
+ 'orange4': '#8B5A00',
+ 'OrangeRed': '#FF4500',
+ 'OrangeRed1': '#FF4500',
+ 'OrangeRed2': '#EE4000',
+ 'OrangeRed3': '#CD3700',
+ 'OrangeRed4': '#8B2500',
+ 'orchid': '#DA70D6',
+ 'orchid1': '#FF83FA',
+ 'orchid2': '#EE7AE9',
+ 'orchid3': '#CD69C9',
+ 'orchid4': '#8B4789',
+ 'pale goldenrod': '#EEE8AA',
+ 'pale green': '#98FB98',
+ 'pale turquoise': '#AFEEEE',
+ 'pale violet red': '#DB7093',
+ 'PaleGoldenrod': '#EEE8AA',
+ 'PaleGreen': '#98FB98',
+ 'PaleGreen1': '#9AFF9A',
+ 'PaleGreen2': '#90EE90',
+ 'PaleGreen3': '#7CCD7C',
+ 'PaleGreen4': '#548B54',
+ 'PaleTurquoise': '#AFEEEE',
+ 'PaleTurquoise1': '#BBFFFF',
+ 'PaleTurquoise2': '#AEEEEE',
+ 'PaleTurquoise3': '#96CDCD',
+ 'PaleTurquoise4': '#668B8B',
+ 'PaleVioletRed': '#DB7093',
+ 'PaleVioletRed1': '#FF82AB',
+ 'PaleVioletRed2': '#EE799F',
+ 'PaleVioletRed3': '#CD687F',
+ 'PaleVioletRed4': '#8B475D',
+ 'papaya whip': '#FFEFD5',
+ 'PapayaWhip': '#FFEFD5',
+ 'peach puff': '#FFDAB9',
+ 'PeachPuff': '#FFDAB9',
+ 'PeachPuff1': '#FFDAB9',
+ 'PeachPuff2': '#EECBAD',
+ 'PeachPuff3': '#CDAF95',
+ 'PeachPuff4': '#8B7765',
+ 'peru': '#CD853F',
+ 'pink': '#FFC0CB',
+ 'pink1': '#FFB5C5',
+ 'pink2': '#EEA9B8',
+ 'pink3': '#CD919E',
+ 'pink4': '#8B636C',
+ 'plum': '#DDA0DD',
+ 'plum1': '#FFBBFF',
+ 'plum2': '#EEAEEE',
+ 'plum3': '#CD96CD',
+ 'plum4': '#8B668B',
+ 'powder blue': '#B0E0E6',
+ 'PowderBlue': '#B0E0E6',
+ 'purple': '#A020F0',
+ 'purple1': '#9B30FF',
+ 'purple2': '#912CEE',
+ 'purple3': '#7D26CD',
+ 'purple4': '#551A8B',
+ 'red': '#FF0000',
+ 'red1': '#FF0000',
+ 'red2': '#EE0000',
+ 'red3': '#CD0000',
+ 'red4': '#8B0000',
+ 'rosy brown': '#BC8F8F',
+ 'RosyBrown': '#BC8F8F',
+ 'RosyBrown1': '#FFC1C1',
+ 'RosyBrown2': '#EEB4B4',
+ 'RosyBrown3': '#CD9B9B',
+ 'RosyBrown4': '#8B6969',
+ 'royal blue': '#4169E1',
+ 'RoyalBlue': '#4169E1',
+ 'RoyalBlue1': '#4876FF',
+ 'RoyalBlue2': '#436EEE',
+ 'RoyalBlue3': '#3A5FCD',
+ 'RoyalBlue4': '#27408B',
+ 'saddle brown': '#8B4513',
+ 'SaddleBrown': '#8B4513',
+ 'salmon': '#FA8072',
+ 'salmon1': '#FF8C69',
+ 'salmon2': '#EE8262',
+ 'salmon3': '#CD7054',
+ 'salmon4': '#8B4C39',
+ 'sandy brown': '#F4A460',
+ 'SandyBrown': '#F4A460',
+ 'sea green': '#2E8B57',
+ 'SeaGreen': '#2E8B57',
+ 'SeaGreen1': '#54FF9F',
+ 'SeaGreen2': '#4EEE94',
+ 'SeaGreen3': '#43CD80',
+ 'SeaGreen4': '#2E8B57',
+ 'seashell': '#FFF5EE',
+ 'seashell1': '#FFF5EE',
+ 'seashell2': '#EEE5DE',
+ 'seashell3': '#CDC5BF',
+ 'seashell4': '#8B8682',
+ 'sienna': '#A0522D',
+ 'sienna1': '#FF8247',
+ 'sienna2': '#EE7942',
+ 'sienna3': '#CD6839',
+ 'sienna4': '#8B4726',
+ 'sky blue': '#87CEEB',
+ 'SkyBlue': '#87CEEB',
+ 'SkyBlue1': '#87CEFF',
+ 'SkyBlue2': '#7EC0EE',
+ 'SkyBlue3': '#6CA6CD',
+ 'SkyBlue4': '#4A708B',
+ 'slate blue': '#6A5ACD',
+ 'slate gray': '#708090',
+ 'slate grey': '#708090',
+ 'SlateBlue': '#6A5ACD',
+ 'SlateBlue1': '#836FFF',
+ 'SlateBlue2': '#7A67EE',
+ 'SlateBlue3': '#6959CD',
+ 'SlateBlue4': '#473C8B',
+ 'SlateGray': '#708090',
+ 'SlateGray1': '#C6E2FF',
+ 'SlateGray2': '#B9D3EE',
+ 'SlateGray3': '#9FB6CD',
+ 'SlateGray4': '#6C7B8B',
+ 'SlateGrey': '#708090',
+ 'snow': '#FFFAFA',
+ 'snow1': '#FFFAFA',
+ 'snow2': '#EEE9E9',
+ 'snow3': '#CDC9C9',
+ 'snow4': '#8B8989',
+ 'spring green': '#00FF7F',
+ 'SpringGreen': '#00FF7F',
+ 'SpringGreen1': '#00FF7F',
+ 'SpringGreen2': '#00EE76',
+ 'SpringGreen3': '#00CD66',
+ 'SpringGreen4': '#008B45',
+ 'steel blue': '#4682B4',
+ 'SteelBlue': '#4682B4',
+ 'SteelBlue1': '#63B8FF',
+ 'SteelBlue2': '#5CACEE',
+ 'SteelBlue3': '#4F94CD',
+ 'SteelBlue4': '#36648B',
+ 'tan': '#D2B48C',
+ 'tan1': '#FFA54F',
+ 'tan2': '#EE9A49',
+ 'tan3': '#CD853F',
+ 'tan4': '#8B5A2B',
+ 'thistle': '#D8BFD8',
+ 'thistle1': '#FFE1FF',
+ 'thistle2': '#EED2EE',
+ 'thistle3': '#CDB5CD',
+ 'thistle4': '#8B7B8B',
+ 'tomato': '#FF6347',
+ 'tomato1': '#FF6347',
+ 'tomato2': '#EE5C42',
+ 'tomato3': '#CD4F39',
+ 'tomato4': '#8B3626',
+ 'turquoise': '#40E0D0',
+ 'turquoise1': '#00F5FF',
+ 'turquoise2': '#00E5EE',
+ 'turquoise3': '#00C5CD',
+ 'turquoise4': '#00868B',
+ 'violet': '#EE82EE',
+ 'violet red': '#D02090',
+ 'VioletRed': '#D02090',
+ 'VioletRed1': '#FF3E96',
+ 'VioletRed2': '#EE3A8C',
+ 'VioletRed3': '#CD3278',
+ 'VioletRed4': '#8B2252',
+ 'wheat': '#F5DEB3',
+ 'wheat1': '#FFE7BA',
+ 'wheat2': '#EED8AE',
+ 'wheat3': '#CDBA96',
+ 'wheat4': '#8B7E66',
+ 'white': '#FFFFFF',
+ 'white smoke': '#F5F5F5',
+ 'WhiteSmoke': '#F5F5F5',
+ 'yellow': '#FFFF00',
+ 'yellow green': '#9ACD32',
+ 'yellow1': '#FFFF00',
+ 'yellow2': '#EEEE00',
+ 'yellow3': '#CDCD00',
+ 'yellow4': '#8B8B00',
+ 'YellowGreen': '#9ACD32',
+}
+
+
+sg.SetOptions(button_element_size=(12,1), element_padding=(0,0), auto_size_buttons=False, border_width=1, tooltip_time=100)
+
+#start layout with the tittle
+layout = [[sg.Text('Hover mouse to see RGB value, click for white & black text',
+ text_color='blue',
+ font='Any 15',
+ relief=sg.RELIEF_SUNKEN,
+ justification='center',
+ size=(100,1),
+ background_color='light green',
+ pad=(0,(0,20))),]]
+
+# -- Create primary color viewer window --
+color_list = [key for key in color_map]
+for rows in range(40):
+
+ row = []
+ for i in range(12):
+ try:
+ color = color_list[rows+40*i]
+ row.append(sg.Button(color, button_color=('black', color), key=color, tooltip=color_map[color]))
+ except:
+ pass
+ layout.append(row)
+
+
+window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(layout)
+
+# -- Event loop --
+while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ # -- Create a secondary window that shows white and black text on chosen color
+ layout2 =[[sg.DummyButton(event, button_color=('white', event), tooltip=color_map[event]), sg.DummyButton(event, button_color=('black', event), tooltip=color_map[event])] ]
+ sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).Read(timeout=0)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Color_Names_Smaller_List.py b/DemoPrograms old/Demo_Color_Names_Smaller_List.py
new file mode 100644
index 00000000..e6911dcd
--- /dev/null
+++ b/DemoPrograms old/Demo_Color_Names_Smaller_List.py
@@ -0,0 +1,132 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+ Color names courtesy of Big Daddy's Wiki-Python
+ http://www.wikipython.com/tkinter-ttk-tix/summary-information/colors/
+
+ Shows a big chart of colors... give it a few seconds to create it
+ Once large window is shown, you can click on any color and another window will popup
+ showing both white and black text on that color
+"""
+
+
+
+COLORS = ['snow', 'ghost white', 'white smoke', 'gainsboro', 'floral white', 'old lace',
+ 'linen', 'antique white', 'papaya whip', 'blanched almond', 'bisque', 'peach puff',
+ 'navajo white', 'lemon chiffon', 'mint cream', 'azure', 'alice blue', 'lavender',
+ 'lavender blush', 'misty rose', 'dark slate gray', 'dim gray', 'slate gray',
+ 'light slate gray', 'gray', 'light gray', 'midnight blue', 'navy', 'cornflower blue', 'dark slate blue',
+ 'slate blue', 'medium slate blue', 'light slate blue', 'medium blue', 'royal blue', 'blue',
+ 'dodger blue', 'deep sky blue', 'sky blue', 'light sky blue', 'steel blue', 'light steel blue',
+ 'light blue', 'powder blue', 'pale turquoise', 'dark turquoise', 'medium turquoise', 'turquoise',
+ 'cyan', 'light cyan', 'cadet blue', 'medium aquamarine', 'aquamarine', 'dark green', 'dark olive green',
+ 'dark sea green', 'sea green', 'medium sea green', 'light sea green', 'pale green', 'spring green',
+ 'lawn green', 'medium spring green', 'green yellow', 'lime green', 'yellow green',
+ 'forest green', 'olive drab', 'dark khaki', 'khaki', 'pale goldenrod', 'light goldenrod yellow',
+ 'light yellow', 'yellow', 'gold', 'light goldenrod', 'goldenrod', 'dark goldenrod', 'rosy brown',
+ 'indian red', 'saddle brown', 'sandy brown',
+ 'dark salmon', 'salmon', 'light salmon', 'orange', 'dark orange',
+ 'coral', 'light coral', 'tomato', 'orange red', 'red', 'hot pink', 'deep pink', 'pink', 'light pink',
+ 'pale violet red', 'maroon', 'medium violet red', 'violet red',
+ 'medium orchid', 'dark orchid', 'dark violet', 'blue violet', 'purple', 'medium purple',
+ 'thistle', 'snow2', 'snow3',
+ 'snow4', 'seashell2', 'seashell3', 'seashell4', 'AntiqueWhite1', 'AntiqueWhite2',
+ 'AntiqueWhite3', 'AntiqueWhite4', 'bisque2', 'bisque3', 'bisque4', 'PeachPuff2',
+ 'PeachPuff3', 'PeachPuff4', 'NavajoWhite2', 'NavajoWhite3', 'NavajoWhite4',
+ 'LemonChiffon2', 'LemonChiffon3', 'LemonChiffon4', 'cornsilk2', 'cornsilk3',
+ 'cornsilk4', 'ivory2', 'ivory3', 'ivory4', 'honeydew2', 'honeydew3', 'honeydew4',
+ 'LavenderBlush2', 'LavenderBlush3', 'LavenderBlush4', 'MistyRose2', 'MistyRose3',
+ 'MistyRose4', 'azure2', 'azure3', 'azure4', 'SlateBlue1', 'SlateBlue2', 'SlateBlue3',
+ 'SlateBlue4', 'RoyalBlue1', 'RoyalBlue2', 'RoyalBlue3', 'RoyalBlue4', 'blue2', 'blue4',
+ 'DodgerBlue2', 'DodgerBlue3', 'DodgerBlue4', 'SteelBlue1', 'SteelBlue2',
+ 'SteelBlue3', 'SteelBlue4', 'DeepSkyBlue2', 'DeepSkyBlue3', 'DeepSkyBlue4',
+ 'SkyBlue1', 'SkyBlue2', 'SkyBlue3', 'SkyBlue4', 'LightSkyBlue1', 'LightSkyBlue2',
+ 'LightSkyBlue3', 'LightSkyBlue4', 'Slategray1', 'Slategray2', 'Slategray3',
+ 'Slategray4', 'LightSteelBlue1', 'LightSteelBlue2', 'LightSteelBlue3',
+ 'LightSteelBlue4', 'LightBlue1', 'LightBlue2', 'LightBlue3', 'LightBlue4',
+ 'LightCyan2', 'LightCyan3', 'LightCyan4', 'PaleTurquoise1', 'PaleTurquoise2',
+ 'PaleTurquoise3', 'PaleTurquoise4', 'CadetBlue1', 'CadetBlue2', 'CadetBlue3',
+ 'CadetBlue4', 'turquoise1', 'turquoise2', 'turquoise3', 'turquoise4', 'cyan2', 'cyan3',
+ 'cyan4', 'DarkSlategray1', 'DarkSlategray2', 'DarkSlategray3', 'DarkSlategray4',
+ 'aquamarine2', 'aquamarine4', 'DarkSeaGreen1', 'DarkSeaGreen2', 'DarkSeaGreen3',
+ 'DarkSeaGreen4', 'SeaGreen1', 'SeaGreen2', 'SeaGreen3', 'PaleGreen1', 'PaleGreen2',
+ 'PaleGreen3', 'PaleGreen4', 'SpringGreen2', 'SpringGreen3', 'SpringGreen4',
+ 'green2', 'green3', 'green4', 'chartreuse2', 'chartreuse3', 'chartreuse4',
+ 'OliveDrab1', 'OliveDrab2', 'OliveDrab4', 'DarkOliveGreen1', 'DarkOliveGreen2',
+ 'DarkOliveGreen3', 'DarkOliveGreen4', 'khaki1', 'khaki2', 'khaki3', 'khaki4',
+ 'LightGoldenrod1', 'LightGoldenrod2', 'LightGoldenrod3', 'LightGoldenrod4',
+ 'LightYellow2', 'LightYellow3', 'LightYellow4', 'yellow2', 'yellow3', 'yellow4',
+ 'gold2', 'gold3', 'gold4', 'goldenrod1', 'goldenrod2', 'goldenrod3', 'goldenrod4',
+ 'DarkGoldenrod1', 'DarkGoldenrod2', 'DarkGoldenrod3', 'DarkGoldenrod4',
+ 'RosyBrown1', 'RosyBrown2', 'RosyBrown3', 'RosyBrown4', 'IndianRed1', 'IndianRed2',
+ 'IndianRed3', 'IndianRed4', 'sienna1', 'sienna2', 'sienna3', 'sienna4', 'burlywood1',
+ 'burlywood2', 'burlywood3', 'burlywood4', 'wheat1', 'wheat2', 'wheat3', 'wheat4', 'tan1',
+ 'tan2', 'tan4', 'chocolate1', 'chocolate2', 'chocolate3', 'firebrick1', 'firebrick2',
+ 'firebrick3', 'firebrick4', 'brown1', 'brown2', 'brown3', 'brown4', 'salmon1', 'salmon2',
+ 'salmon3', 'salmon4', 'LightSalmon2', 'LightSalmon3', 'LightSalmon4', 'orange2',
+ 'orange3', 'orange4', 'DarkOrange1', 'DarkOrange2', 'DarkOrange3', 'DarkOrange4',
+ 'coral1', 'coral2', 'coral3', 'coral4', 'tomato2', 'tomato3', 'tomato4', 'OrangeRed2',
+ 'OrangeRed3', 'OrangeRed4', 'red2', 'red3', 'red4', 'DeepPink2', 'DeepPink3', 'DeepPink4',
+ 'HotPink1', 'HotPink2', 'HotPink3', 'HotPink4', 'pink1', 'pink2', 'pink3', 'pink4',
+ 'LightPink1', 'LightPink2', 'LightPink3', 'LightPink4', 'PaleVioletRed1',
+ 'PaleVioletRed2', 'PaleVioletRed3', 'PaleVioletRed4', 'maroon1', 'maroon2',
+ 'maroon3', 'maroon4', 'VioletRed1', 'VioletRed2', 'VioletRed3', 'VioletRed4',
+ 'magenta2', 'magenta3', 'magenta4', 'orchid1', 'orchid2', 'orchid3', 'orchid4', 'plum1',
+ 'plum2', 'plum3', 'plum4', 'MediumOrchid1', 'MediumOrchid2', 'MediumOrchid3',
+ 'MediumOrchid4', 'DarkOrchid1', 'DarkOrchid2', 'DarkOrchid3', 'DarkOrchid4',
+ 'purple1', 'purple2', 'purple3', 'purple4', 'MediumPurple1', 'MediumPurple2',
+ 'MediumPurple3', 'MediumPurple4', 'thistle1', 'thistle2', 'thistle3', 'thistle4',
+ 'grey1', 'grey2', 'grey3', 'grey4', 'grey5', 'grey6', 'grey7', 'grey8', 'grey9', 'grey10',
+ 'grey11', 'grey12', 'grey13', 'grey14', 'grey15', 'grey16', 'grey17', 'grey18', 'grey19',
+ 'grey20', 'grey21', 'grey22', 'grey23', 'grey24', 'grey25', 'grey26', 'grey27', 'grey28',
+ 'grey29', 'grey30', 'grey31', 'grey32', 'grey33', 'grey34', 'grey35', 'grey36', 'grey37',
+ 'grey38', 'grey39', 'grey40', 'grey42', 'grey43', 'grey44', 'grey45', 'grey46', 'grey47',
+ 'grey48', 'grey49', 'grey50', 'grey51', 'grey52', 'grey53', 'grey54', 'grey55', 'grey56',
+ 'grey57', 'grey58', 'grey59', 'grey60', 'grey61', 'grey62', 'grey63', 'grey64', 'grey65',
+ 'grey66', 'grey67', 'grey68', 'grey69', 'grey70', 'grey71', 'grey72', 'grey73', 'grey74',
+ 'grey75', 'grey76', 'grey77', 'grey78', 'grey79', 'grey80', 'grey81', 'grey82', 'grey83',
+ 'grey84', 'grey85', 'grey86', 'grey87', 'grey88', 'grey89', 'grey90', 'grey91', 'grey92',
+ 'grey93', 'grey94', 'grey95', 'grey97', 'grey98', 'grey99']
+
+
+
+
+sg.SetOptions(button_element_size=(12,1), element_padding=(0,0), auto_size_buttons=False, border_width=0)
+
+layout = [[sg.Text('Click on a color square to see both white and black text on that color', text_color='blue', font='Any 15')]]
+row = []
+layout = []
+# -- Create primary color viewer window --
+for rows in range(40):
+
+ row = []
+ for i in range(12):
+ try:
+ color = COLORS[rows+40*i]
+ row.append(sg.Button(color, button_color=('black', color), key=color))
+ except:
+ pass
+ layout.append(row)
+
+
+# for i, color in enumerate(COLORS):
+# row.append(sg.Button(color, button_color=('black', color), key=color))
+# if (i+1) % 12 == 0:
+# layout.append(row)
+# row = []
+
+window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(layout)
+
+# -- Event loop --
+while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ # -- Create a secondary window that shows white and black text on chosen color
+ layout2 =[[sg.DummyButton(event, button_color=('white', event)), sg.DummyButton(event, button_color=('black', event))]]
+ sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).Read(timeout=0)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Column_And_Frames.py b/DemoPrograms old/Demo_Column_And_Frames.py
new file mode 100644
index 00000000..2c6f64ea
--- /dev/null
+++ b/DemoPrograms old/Demo_Column_And_Frames.py
@@ -0,0 +1,48 @@
+import PySimpleGUI as sg
+# this one long import has the effect of making the code more compact as there is no 'sg.' prefix required for Elements
+from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, Check, Button, B, Btn, ButtonMenu, Canvas, Column, Col, Combo, Frame, Graph, Image, InputText, Input, In, Listbox, LBox, Menu, Multiline, ML, MLine, OptionMenu, Output, Pane, ProgressBar, Radio, Slider, Spin, StatusBar, Tab, TabGroup, Table, Text, Txt, T, Tree, TreeData, VerticalSeparator, Window, Sizer
+
+"""
+ Demo Columns and Frames
+ Demonstrates using mixture of Column and Frame elements to create a nice window layout.
+ A couple of the concepts shown here include:
+ * Using Columns and Frames with specific sizes on them
+ * Importing all required classes so that "sg." is not required on any objects. This makes the code more compact and readable
+
+ There are 3 columns. Two are side by side at the top and the third is along the bottom
+"""
+
+sg.change_look_and_feel('GreenTan')
+
+col2 = Column([[Frame('Accounts:', [[Column([[Listbox(['Account '+str(i) for i in range(1,16)], key='-ACCT-LIST-', size=(15,20)),]],size=(150,400))]])]], pad=(0,0))
+
+col1 = Column([
+ # Categories frame
+ [Frame('Categories:', [[Radio('Websites', 'radio1', default=True, key='-WEBSITES-', size=(10, 1)),
+ Radio('Software', 'radio1',key='-SOFTWARE-', size=(10, 1))]],)],
+ # Information frame
+ [Frame('Information:', [[Column([[Text('Account:')],
+ [Input(key='-ACCOUNT-IN-', size=(19, 1))],
+ [Text('User Id:')],
+ [Input(key='-USERID-IN-', size=(19, 1)), Button('Copy', key='-USERID-')],
+ [Text('Password:')],
+ [Input(key='-PW-IN-', size=(19, 1)), Button('Copy', key='-PASS-')],
+ [Text('Location:')],
+ [Input(key='-LOC-IN-', size=(19, 1)), Button('Copy', key='-LOC')],
+ [Text('Notes:')],
+ [Multiline(key='-NOTES-', size=(25, 5))],
+ ], size=(235,350),pad=(0,0))]])],], pad=(0,0))
+
+col3 = Column([[Frame('Actions:', [[Column([[Button('Save'), Button('Clear'), Button('Delete'),]], size=(450,45), pad=(0,0))]])]], pad=(0,0))
+
+layout = [ [col1, col2],
+ [col3]]
+
+window = Window('Passwords', layout)
+
+while True:
+ event, values = window.read()
+ print(event, values)
+ if event is None:
+ break
+window.close()
diff --git a/DemoPrograms old/Demo_Columns.py b/DemoPrograms old/Demo_Columns.py
new file mode 100644
index 00000000..481751cb
--- /dev/null
+++ b/DemoPrograms old/Demo_Columns.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+sg.ChangeLookAndFeel('BlueMono')
+
+# Column layout
+col = [[sg.Text('col Row 1', text_color='white', background_color='blue')],
+ [sg.Text('col Row 2', text_color='white', background_color='blue'), sg.Input('col input 1')],
+ [sg.Text('col Row 3', text_color='white', background_color='blue'), sg.Input('col input 2')]]
+# Window layout
+layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item 3'),
+ select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE, size=(20, 3)),
+ sg.Column(col, background_color='blue')],
+ [sg.Input('Last input')],
+ [sg.OK()]]
+
+# Display the window and get values
+event, values = sg.Window('Compact 1-line form with column').Layout(layout).Read()
+
+sg.Popup(event, values, line_width=200)
+
diff --git a/DemoPrograms old/Demo_Compact_Layouts_Element_Renaming.py b/DemoPrograms old/Demo_Compact_Layouts_Element_Renaming.py
new file mode 100644
index 00000000..589375a3
--- /dev/null
+++ b/DemoPrograms old/Demo_Compact_Layouts_Element_Renaming.py
@@ -0,0 +1,47 @@
+import PySimpleGUI as sg
+# Import the elements individually to save space
+from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, Check, Button, B, Btn, ButtonMenu,BMenu, Canvas, Column, Col, Combo, DropDown, Drop, DD, Frame, Graph, Image, InputText, Input, In, I, Listbox, LBox, LB, Menu, Multiline, ML, MLine, OptionMenu, Output, Pane, ProgressBar, Prog, PBar, Radio, R, Rad, Sizer, Slider, Spin, StatusBar, Tab, TabGroup, Table, Text, Txt, T, Tree, TreeData, VerticalSeparator, Window, Print
+
+
+"""
+ Demo - Compact Layouts and Element Renaming
+
+ Some layouts contain many many elements such that space becomes a premium. For experienced PySimpleGUI
+ programmers, there is little additional knowledge to be gained by writing
+ sg.Text('My text')
+ rather than using one of the shortcuts such as
+ sg.T('My text')
+ However, even with shortcut usage, you continue to have the package prefix of
+ sg.
+ That's 3 characters per element that are added to your layout!
+ The very long import statement st the top can be copied into your code to give you the ability to write
+ T('My text')
+
+ If you don't want to use that very-long import or perhaps want to use your own shortcut names, you can easily
+ create your shortcut by simple assignment:
+ T = sg.Text
+ This enables you to use T just as if you imported the Class T from PySimpleGUI. You could develop your own
+ template that you copy and paste at the top of all of your PySimpleGUI programs. Or perhaps perform an import
+ of those assignments from a .py file you create.
+
+ Note that you may lose docstrings in PyCharm using these shortcuts. You can still see the parameters when pressing
+ Control+P, but the Control+Q doesn't bring up the full list of parms and their descriptions. Looking for a fix
+ for this.
+
+ PLEASE OH PLEASE OH PLEASE NEVER EVER EVER do this:
+ from PySimpleGUI import *
+ There is a bot scanning GitHub for this statement. If found in your code, a squad of assassins will be dispatched
+ from the PySimpleGUI headquarters and you will be hunted down and forced to change your code.
+"""
+
+# A user created shortcut....
+# Suppose this user's layout contains many Multiline Elements. It could be advantageous to have a single letter
+# shortcut version for Multiline
+M = sg.Multiline
+
+# This layout uses the user defined "M" element as well as the PySimpleGUI Button shortcut, B.
+layout = [[M(size=(30,3))],
+ [B('OK')]]
+
+event, values = Window('Shortcuts', layout).read()
+sg.popup_scrolled(event, values)
diff --git a/DemoPrograms old/Demo_Compare_Files.py b/DemoPrograms old/Demo_Compare_Files.py
new file mode 100644
index 00000000..77ab262d
--- /dev/null
+++ b/DemoPrograms old/Demo_Compare_Files.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+# sg.SetOptions(button_color=sg.COLOR_SYSTEM_DEFAULT)
+
+def GetFilesToCompare():
+ form_rows = [[sg.Text('Enter 2 files to comare')],
+ [sg.Text('File 1', size=(15, 1)), sg.InputText(key='file1'), sg.FileBrowse()],
+ [sg.Text('File 2', size=(15, 1)), sg.InputText(key='file2'), sg.FileBrowse(target='file2')],
+ [sg.Submit(), sg.Cancel()]]
+
+ window = sg.Window('File Compare')
+ event, values = window.Layout(form_rows).Read()
+ return event, values
+
+def main():
+ button, values = GetFilesToCompare()
+ f1 = values['file1']
+ f2 = values['file2']
+
+ if any((button != 'Submit', f1 =='', f2 == '')):
+ sg.PopupError('Operation cancelled')
+ sys.exit(69)
+
+ # --- This portion of the code is not GUI related ---
+ with open(f1, 'rb') as file1:
+ with open(f2, 'rb') as file2:
+ a = file1.read()
+ b = file2.read()
+
+ for i, x in enumerate(a):
+ if x != b[i]:
+ sg.Popup('Compare results for files', f1, f2, '**** Mismatch at offset {} ****'.format(i))
+ break
+ else:
+ if len(a) == len(b):
+ sg.Popup('**** The files are IDENTICAL ****')
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Conways_Game_of_Life.py b/DemoPrograms old/Demo_Conways_Game_of_Life.py
new file mode 100644
index 00000000..ece57f65
--- /dev/null
+++ b/DemoPrograms old/Demo_Conways_Game_of_Life.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+
+# John Conway's "Game of Life" using a GUI.
+# Copyright (C) 2018 PySimpleGUI.org
+# GUI provided by PySimpleGUI.
+# Core game engine provied by Christian Jacobs
+
+# An implementation of Conway's Game of Life in Python.
+
+# Copyright (C) 2013 Christian Jacobs.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+import numpy
+import PySimpleGUI as sg # Take your pick! Tkinter
+# import PySimpleGUIWeb as sg # Or the Web! (Remi!)
+
+BOX_SIZE = 15
+
+class GameOfLife:
+
+ def __init__(self, N=20, T=200):
+ """ Set up Conway's Game of Life. """
+ # Here we create two grids to hold the old and new configurations.
+ # This assumes an N*N grid of points.
+ # Each point is either alive or dead, represented by integer values of 1 and 0, respectively.
+ self.N = N
+ self.old_grid = numpy.zeros(N * N, dtype='i').reshape(N, N)
+ self.new_grid = numpy.zeros(N * N, dtype='i').reshape(N, N)
+ self.T = T # The maximum number of generations
+
+ # Set up a random initial configuration for the grid.
+ for i in range(0, self.N):
+ for j in range(0, self.N):
+ self.old_grid[i][j] = 0
+ self.init_graphics()
+ self.manual_board_setup()
+
+ def live_neighbours(self, i, j):
+ """ Count the number of live neighbours around point (i, j). """
+ s = 0 # The total number of live neighbours.
+ # Loop over all the neighbours.
+ for x in [i - 1, i, i + 1]:
+ for y in [j - 1, j, j + 1]:
+ if (x == i and y == j):
+ continue # Skip the current point itself - we only want to count the neighbours!
+ if (x != self.N and y != self.N):
+ s += self.old_grid[x][y]
+ # The remaining branches handle the case where the neighbour is off the end of the grid.
+ # In this case, we loop back round such that the grid becomes a "toroidal array".
+ elif (x == self.N and y != self.N):
+ s += self.old_grid[0][y]
+ elif (x != self.N and y == self.N):
+ s += self.old_grid[x][0]
+ else:
+ s += self.old_grid[0][0]
+ return s
+
+ def play(self):
+ """ Play Conway's Game of Life. """
+
+ # Write the initial configuration to file.
+
+ self.t = 1 # Current time level
+ while self.t <= self.T: # Evolve!
+ # print( "At time level %d" % t)
+
+ # Loop over each cell of the grid and apply Conway's rules.
+ for i in range(self.N):
+ for j in range(self.N):
+ live = self.live_neighbours(i, j)
+ if (self.old_grid[i][j] == 1 and live < 2):
+ self.new_grid[i][j] = 0 # Dead from starvation.
+ elif (self.old_grid[i][j] == 1 and (live == 2 or live == 3)):
+ self.new_grid[i][j] = 1 # Continue living.
+ elif (self.old_grid[i][j] == 1 and live > 3):
+ self.new_grid[i][j] = 0 # Dead from overcrowding.
+ elif (self.old_grid[i][j] == 0 and live == 3):
+ self.new_grid[i][j] = 1 # Alive from reproduction.
+
+ # Output the new configuration.
+
+ # The new configuration becomes the old configuration for the next generation.
+ self.old_grid = self.new_grid.copy()
+ self.draw_board()
+ # Move on to the next time level
+ self.t += 1
+
+ def init_graphics(self):
+ self.graph = sg.Graph((600, 600), (0, 0), (450, 450), key='_GRAPH_', change_submits=True, drag_submits=False, background_color='lightblue')
+ layout = [
+ [sg.Text('Game of Life ', font='ANY 15'), sg.Text('', key='_OUTPUT_', size=(30,1), font='ANY 15')],
+ [self.graph],
+ [sg.Button('Go!', key='_DONE_'),
+ sg.Text(' Delay (ms)') , sg.Slider([0,800], orientation='h', key='_SLIDER_', enable_events=True, size=(15,15)), sg.T('', size=(3,1), key='_S1_OUT_'),
+ sg.Text(' Num Generations'), sg.Slider([0, 20000],default_value=4000, orientation='h',size=(15,15),enable_events=True, key='_SLIDER2_'), sg.T('', size=(3,1), key='_S2_OUT_')]
+ ]
+
+ self.window = sg.Window('Window Title', ).Layout(layout).Finalize()
+ event, values = self.window.Read(timeout=0)
+ self.delay = values['_SLIDER_']
+ self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
+ self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
+
+
+ def draw_board(self):
+ BOX_SIZE = 15
+ self.graph.Erase()
+ for i in range(self.N):
+ for j in range(self.N):
+ if self.old_grid[i][j]:
+ self.graph.DrawRectangle((i * BOX_SIZE, j * BOX_SIZE),
+ (i * BOX_SIZE + BOX_SIZE, j * (BOX_SIZE) + BOX_SIZE),
+ line_color='black', fill_color='yellow')
+ event, values = self.window.Read(timeout=self.delay)
+ if event in (None, '_DONE_'):
+ exit()
+ self.delay = values['_SLIDER_']
+ self.T = int(values['_SLIDER2_'])
+ self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
+ self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
+ self.window.Element('_OUTPUT_').Update('Generation {}'.format(self.t))
+
+
+ def manual_board_setup(self):
+ ids = []
+ for i in range(self.N):
+ ids.append([])
+ for j in range(self.N):
+ ids[i].append(0)
+ while True: # Event Loop
+ event, values = self.window.Read()
+ if event is None or event == '_DONE_':
+ break
+ self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
+ self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
+ mouse = values['_GRAPH_']
+
+ if event == '_GRAPH_':
+ if mouse == (None, None):
+ continue
+ box_x = mouse[0] // BOX_SIZE
+ box_y = mouse[1] // BOX_SIZE
+ if self.old_grid[box_x][box_y] == 1:
+ id = ids[box_x][box_y]
+ self.graph.DeleteFigure(id)
+ self.old_grid[box_x][box_y] = 0
+ else:
+ id = self.graph.DrawRectangle((box_x * BOX_SIZE, box_y * BOX_SIZE),
+ (box_x * BOX_SIZE + BOX_SIZE, box_y * (BOX_SIZE) + BOX_SIZE),
+ line_color='black', fill_color='yellow')
+ ids[box_x][box_y] = id
+ self.old_grid[box_x][box_y] = 1
+ self.window.Element('_DONE_').Update(text='Exit')
+
+if (__name__ == "__main__"):
+ game = GameOfLife(N=35, T=200)
+ game.play()
+ game.window.Close()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Crossword_Puzzle.py b/DemoPrograms old/Demo_Crossword_Puzzle.py
new file mode 100644
index 00000000..62dd7b4e
--- /dev/null
+++ b/DemoPrograms old/Demo_Crossword_Puzzle.py
@@ -0,0 +1,54 @@
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+ # import PySimpleGUIWeb as sg # take your pick of ports. Runs on both
+else:
+ import PySimpleGUI27 as sg
+import random
+import string
+
+"""
+ Demo application to show how to draw rectangles and letters on a Graph Element
+ This demo mocks up a crossword puzzle board
+ It will place a letter where you click on the puzzle
+"""
+
+
+BOX_SIZE = 25
+
+layout = [
+ [sg.Text('Crossword Puzzle Using PySimpleGUI'), sg.Text('', key='_OUTPUT_')],
+ [sg.Graph((800,800), (0,450), (450,0), key='_GRAPH_', change_submits=True, drag_submits=False)],
+ [sg.Button('Show'), sg.Button('Exit')]
+ ]
+
+window = sg.Window('Window Title', ).Layout(layout).Finalize()
+
+g = window.FindElement('_GRAPH_')
+
+for row in range(16):
+ for col in range(16):
+ if random.randint(0,100) > 10:
+ g.DrawRectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black')
+ else:
+ g.DrawRectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black', fill_color='black')
+
+ g.DrawText('{}'.format(row * 6 + col + 1), (col * BOX_SIZE + 10, row * BOX_SIZE + 8))
+
+while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event is None or event == 'Exit':
+ break
+ mouse = values['_GRAPH_']
+
+ if event == '_GRAPH_':
+ if mouse == (None, None):
+ continue
+ box_x = mouse[0]//BOX_SIZE
+ box_y = mouse[1]//BOX_SIZE
+ letter_location = (box_x * BOX_SIZE + 18, box_y * BOX_SIZE + 17)
+ print(box_x, box_y)
+ g.DrawText('{}'.format(random.choice(string.ascii_uppercase)), letter_location, font='Courier 25')
+
+window.Close()
diff --git a/DemoPrograms old/Demo_DOC_Viewer_PIL.py b/DemoPrograms old/Demo_DOC_Viewer_PIL.py
new file mode 100644
index 00000000..50c6f0c8
--- /dev/null
+++ b/DemoPrograms old/Demo_DOC_Viewer_PIL.py
@@ -0,0 +1,242 @@
+"""
+@created: 2018-08-19 18:00:00
+@author: (c) 2018 Jorj X. McKie
+Display a PyMuPDF Document using Tkinter
+-------------------------------------------------------------------------------
+Dependencies:
+-------------
+PyMuPDF, PySimpleGUI (requires Python 3), Tkinter, PIL
+License:
+--------
+GNU GPL V3+
+Description
+------------
+Get filename and start displaying page 1. Please note that all file types
+of MuPDF are supported (including EPUB e-books and HTML files for example).
+Pages can be directly jumped to, or buttons can be used for paging.
+
+This version contains enhancements:
+* Use of PIL improves response times by a factor 3 or more.
+* Zooming is now flexible: only one button serves as a toggle. Arrow keys can
+ be used for moving the window when zooming.
+
+We also interpret keyboard events (PageDown / PageUp) and mouse wheel actions
+to support paging as if a button was clicked. Similarly, we do not include
+a 'Quit' button. Instead, the ESCAPE key can be used, or cancelling the window.
+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 sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import tkinter as tk
+from PIL import Image, ImageTk
+import time
+
+if len(sys.argv) == 1:
+ fname = sg.PopupGetFile('Document Browser', 'Document file to open', no_window=True,
+ file_types = (
+ ("PDF Files", "*.pdf"),
+ ("XPS Files", "*.*xps"),
+ ("Epub Files", "*.epub"),
+ ("Fiction Books", "*.fb2"),
+ ("Comic Books", "*.cbz"),
+ ("HTML", "*.htm*")
+ # add more document types here
+ )
+ )
+else:
+ fname = sys.argv[1]
+
+if not fname:
+ sg.Popup("Cancelling:", "No filename supplied")
+ raise SystemExit("Cancelled: no filename supplied")
+
+doc = fitz.open(fname)
+page_count = len(doc)
+
+# used for response time statistics only
+fitz_img_time = 0.0
+tk_img_time = 0.0
+img_count = 1
+
+# allocate 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 = False, max_size = None, first = False):
+ """Return a PNG image for a document page number.
+ """
+ dlist = dlist_tab[pno] # get display list of page number
+ if not dlist: # create if not yet there
+ dlist_tab[pno] = doc[pno].getDisplayList()
+ dlist = dlist_tab[pno]
+ r = dlist.rect # the page rectangle
+ clip = r
+ # ensure image fits screen:
+ # exploit, but do not exceed width or height
+ zoom_0 = 1
+ if max_size:
+ zoom_0 = min(1, max_size[0] / r.width, max_size[1] / r.height)
+ if zoom_0 == 1:
+ zoom_0 = min(max_size[0] / r.width, max_size[1] / r.height)
+ mat_0 = fitz.Matrix(zoom_0, zoom_0)
+
+ if not zoom: # show total page
+ pix = dlist.getPixmap(matrix = mat_0, alpha=False)
+ else:
+ mp = r.tl + (r.br - r.tl) * 0.5 # page rect center
+ w2 = r.width / 2
+ h2 = r.height / 2
+ clip = r * 0.5
+ tl = zoom[0] # old top-left
+ tl.x += zoom[1] * (w2 / 2)
+ tl.x = max(0, tl.x)
+ tl.x = min(w2, tl.x)
+ tl.y += zoom[2] * (h2 / 2)
+ tl.y = max(0, tl.y)
+ tl.y = min(h2, tl.y)
+ clip = fitz.Rect(tl, tl.x + w2, tl.y + h2)
+
+ mat = mat_0 * fitz.Matrix(2, 2) # zoom matrix
+ pix = dlist.getPixmap(alpha=False, matrix=mat, clip=clip)
+
+ if first: # first call: tkinter still inactive
+ img = pix.getPNGData() # so use fitz png output
+ else: # else take tk photo image
+ pilimg = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
+ img = ImageTk.PhotoImage(pilimg)
+
+ return img, clip.tl # return image, clip position
+
+
+root = tk.Tk()
+max_width = root.winfo_screenwidth() - 20
+max_height = root.winfo_screenheight() - 135
+max_size = (max_width, max_height)
+root.destroy()
+del root
+
+window = sg.Window(title, return_keyboard_events = True,
+ location = (0,0), use_default_focus = False, no_titlebar=False)
+
+cur_page = 0
+data, clip_pos = get_page(cur_page,
+ zoom = False,
+ max_size = max_size,
+ first = True)
+
+image_elem = sg.Image(data = data)
+
+goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True,
+ key = "PageNumber")
+
+layout = [
+ [
+ sg.ReadButton('Next'),
+ sg.ReadButton('Prev'),
+ sg.Text('Page:'),
+ goto,
+ sg.Text('(%i)' % page_count),
+ sg.ReadButton('Zoom'),
+ sg.Text('(toggle on/off, use arrows to navigate while zooming)'),
+ ],
+ [image_elem],
+]
+
+window.Layout(layout)
+
+# now define the buttons / events we want to handle
+enter_buttons = [chr(13), "Return:13"]
+quit_buttons = ["Escape:27", chr(27)]
+next_buttons = ["Next", "Next:34", "MouseWheel:Down"]
+prev_buttons = ["Prev", "Prior:33", "MouseWheel:Up"]
+Up = "Up:38"
+Left = "Left:37"
+Right = "Right:39"
+Down = "Down:40"
+zoom_buttons = ["Zoom", Up, Down, Left, Right]
+
+# all the buttons we will handle
+my_keys = enter_buttons + next_buttons + prev_buttons + zoom_buttons
+
+# old page store and zoom toggle
+old_page = 0
+old_zoom = False
+
+while True:
+ event, value = window.Read()
+ if event is None and (value is None or value['PageNumber'] is None):
+ break
+ if event in quit_buttons:
+ break
+
+ zoom_pressed = False
+ zoom = False
+
+ if event in enter_buttons:
+ try:
+ cur_page = int(value['PageNumber']) - 1 # check if valid
+ while cur_page < 0:
+ cur_page += page_count
+ except:
+ cur_page = 0 # this guy's trying to fool me
+
+ elif event in next_buttons:
+ cur_page += 1
+ elif event in prev_buttons:
+ cur_page -= 1
+ elif event == Up:
+ zoom = (clip_pos, 0, -1)
+ elif event == Down:
+ zoom = (clip_pos, 0, 1)
+ elif event == Left:
+ zoom = (clip_pos, -1, 0)
+ elif event == Right:
+ zoom = (clip_pos, 1, 0)
+ elif event == "Zoom":
+ zoom_pressed = True
+ zoom = (clip_pos, 0, 0)
+
+ # sanitize page number
+ if cur_page >= page_count: # wrap around
+ cur_page = 0
+ while cur_page < 0: # pages > 0 look nicer
+ cur_page += page_count
+
+ if zoom_pressed and old_zoom:
+ zoom = zoom_pressed = old_zoom = False
+
+ t0 = time.perf_counter()
+ data, clip_pos = get_page(cur_page, zoom = zoom, max_size = max_size,
+ first = False)
+ t1 = time.perf_counter()
+ image_elem.Update(data = data)
+ t2 = time.perf_counter()
+ fitz_img_time += t1 - t0
+ tk_img_time += t2 - t1
+ img_count += 1
+ old_page = cur_page
+ old_zoom = zoom_pressed or zoom
+
+ # update page number field
+ if event in my_keys:
+ goto.Update(str(cur_page + 1))
+
+
+# print some response time statistics
+if img_count > 0:
+ print("response times for '%s'" % doc.name)
+ print("%.4f" % (fitz_img_time/img_count), "sec fitz avg. image time")
+ print("%.4f" % (tk_img_time/img_count), "sec tk avg. image time")
+ print("%.4f" % ((fitz_img_time + tk_img_time)/img_count), "sec avg. total time")
+ print(img_count, "images read")
+ print(page_count, "pages")
diff --git a/DemoPrograms old/Demo_Debugger_Built_Into_PSG.py b/DemoPrograms old/Demo_Debugger_Built_Into_PSG.py
new file mode 100644
index 00000000..22e4b9ae
--- /dev/null
+++ b/DemoPrograms old/Demo_Debugger_Built_Into_PSG.py
@@ -0,0 +1,55 @@
+import PySimpleGUI as sg
+# import imwatchingyou # Not needed because using the one inside PySimpleGUI.py code itself
+
+"""
+ Demo program that shows you how to integrate the PySimpleGUI Debugger
+ into your program.
+ This particular program is a GUI based program simply to make it easier for you to interact and change
+ things.
+
+ In this example, the debugger is not started initiallly. You click the "Debug" button to launch it
+ There are THREE steps, and they are copy and pastes.
+ 1. At the top of your app to debug add
+ import imwatchingyou
+ 2. When you want to show a debug window, call one of two functions:
+ imwatchingyou.show_debug_window()
+ imwatchingyou.show_popout_window()
+ 3. You must find a location in your code to "refresh" the debugger. Some loop that's executed often.
+ In this loop add this call:
+ imwatchingyou.refresh()
+"""
+
+layout = [
+ [sg.T('A typical PSG application')],
+ [sg.In(key='_IN_')],
+ [sg.T(' ', key='_OUT_', size=(45,1))],
+ [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
+ [sg.Radio('a',1, key='_R1_'), sg.Radio('b',1, key='_R2_'), sg.Radio('c',1, key='_R3_')],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6,3), key='_COMBO_')],
+ [sg.Output(size=(50,6))],
+ [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Button('Popout'), sg.Button('Debugger'), sg.Debug(key='Debug')],
+ ]
+
+window = sg.Window('This is your Application Window', layout, debugger_enabled=False)
+
+counter = 0
+timeout = 100
+
+# Note that you can launch the debugger windows right away, without waiting for user input
+sg.show_debugger_popout_window()
+
+while True: # Your Event Loop
+ event, values = window.Read(timeout=timeout)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Enable':
+ window.EnableDebugger()
+ elif event == 'Popout':
+ sg.show_debugger_popout_window() # replaces old popout with a new one, possibly with new variables`
+ elif event == 'Debugger':
+ sg.show_debugger_window()
+ counter += 1
+ # to prove window is operating, show the input in another area in the window.
+ window.Element('_OUT_').Update(values['_IN_'])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Debugger_Button.py b/DemoPrograms old/Demo_Debugger_Button.py
new file mode 100644
index 00000000..b2b482d6
--- /dev/null
+++ b/DemoPrograms old/Demo_Debugger_Button.py
@@ -0,0 +1,48 @@
+import PySimpleGUI as sg
+# import imwatchingyou # STEP 1
+
+"""
+ Demo program that shows you how to integrate the PySimpleGUI Debugger
+ into your program.
+ This particular program is a GUI based program simply to make it easier for you to interact and change
+ things.
+
+ In this example, the debugger is not started initiallly. You click the "Debug" button to launch it
+ There are THREE steps, and they are copy and pastes.
+ 1. At the top of your app to debug add
+ import imwatchingyou
+ 2. When you want to show a debug window, call one of two functions:
+ imwatchingyou.show_debug_window()
+ imwatchingyou.show_popout_window()
+ 3. You must find a location in your code to "refresh" the debugger. Some loop that's executed often.
+ In this loop add this call:
+ imwatchingyou.refresh()
+"""
+
+layout = [
+ [sg.T('A typical PSG application')],
+ [sg.In(key='_IN_')],
+ [sg.T(' ', key='_OUT_', size=(45,1))],
+ [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
+ [sg.Radio('a',1, key='_R1_'), sg.Radio('b',1, key='_R2_'), sg.Radio('c',1, key='_R3_')],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6,3), key='_COMBO_')],
+ [sg.Output(size=(50,6))],
+ [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Debug(key='Debug')],
+ ]
+
+window = sg.Window('This is your Application Window', layout, debugger_enabled=False)
+
+counter = 0
+timeout = 100
+
+while True: # Your Event Loop
+ event, values = window.Read(timeout=timeout)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Enable':
+ window.EnableDebugger()
+ counter += 1
+ # to prove window is operating, show the input in another area in the window.
+ window.Element('_OUT_').Update(values['_IN_'])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Debugger_ImWatchingYou.py b/DemoPrograms old/Demo_Debugger_ImWatchingYou.py
new file mode 100644
index 00000000..12239a7e
--- /dev/null
+++ b/DemoPrograms old/Demo_Debugger_ImWatchingYou.py
@@ -0,0 +1,54 @@
+import PySimpleGUI as sg
+import imwatchingyou # STEP 1
+
+"""
+ Demo program that shows you how to integrate the PySimpleGUI Debugger
+ into your program.
+ This particular program is a GUI based program simply to make it easier for you to interact and change
+ things.
+
+ In this example, the debugger is not started initiallly. You click the "Debug" button to launch it
+ There are THREE steps, and they are copy and pastes.
+ 1. At the top of your app to debug add
+ import imwatchingyou
+ 2. When you want to show a debug window, call one of two functions:
+ imwatchingyou.show_debug_window()
+ imwatchingyou.show_popout_window()
+ 3. You must find a location in your code to "refresh" the debugger. Some loop that's executed often.
+ In this loop add this call:
+ imwatchingyou.refresh()
+"""
+
+layout = [
+ [sg.T('A typical PSG application')],
+ [sg.In(key='_IN_')],
+ [sg.T(' ', key='_OUT_', size=(30, 1))],
+ [sg.Radio('a', 1, key='_R1_'), sg.Radio('b', 1, key='_R2_'), sg.Radio('c', 1, key='_R3_')],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6, 3), key='_COMBO_')],
+ [sg.Output(size=(50, 6))],
+ [sg.Ok(), sg.Exit(), sg.Button('Debug'), sg.Button('Popout')],
+]
+
+window = sg.Window('This is your Application Window', layout)
+
+counter = 0
+timeout = 100
+
+while True: # Your Event Loop
+ event, values = window.Read(timeout=timeout)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Ok':
+ print('You clicked Ok.... this is where print output goes')
+ elif event == 'Debug':
+ imwatchingyou.show_debugger_window() # STEP 2
+ elif event == 'Popout':
+ imwatchingyou.show_debugger_popout_window() # STEP 2
+ counter += 1
+ # to prove window is operating, show the input in another area in the window.
+ window.Element('_OUT_').Update(values['_IN_'])
+
+ # don't worry about the "state" of things, just call this function "frequently"
+ imwatchingyou.refresh_debugger() # STEP 3 - refresh debugger
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows.py b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows.py
new file mode 100644
index 00000000..22b8cf55
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows.py
@@ -0,0 +1,59 @@
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+ Demo - Running 2 windows with both being active at the same time
+ Three important things to note about this design patter:
+ 1. The layout for window 2 is inside of the while loop, just before the call to window2=sg.Window
+ 2. The read calls have timeout values of 100 and 0. You can change the 100 to whatever interval you wish
+ but must keep the second window's timeout at 0
+ 3. There is a safeguard to stop from launching multiple copies of window2. Only 1 window2 is visible at a time
+"""
+
+# Window 1 layout
+layout = [
+ [sg.Text('This is the FIRST WINDOW'), sg.Text(' ', key='_OUTPUT_')],
+ [sg.Text('')],
+ [sg.Button('Launch 2nd Window'),sg.Button('Popup'), sg.Button('Exit')]
+ ]
+
+window = sg.Window('Window Title', location=(800,600)).Layout(layout)
+win2_active = False
+i=0
+while True: # Event Loop
+ event, values = window.Read(timeout=100)
+ if event != sg.TIMEOUT_KEY:
+ print(i, event, values)
+
+ if event is None or event == 'Exit':
+ break
+ elif event == 'Popup':
+ sg.Popup('This is a BLOCKING popup','all windows remain inactive while popup active')
+ i+=1
+ if event == 'Launch 2nd Window' and not win2_active: # only run if not already showing a window2
+ win2_active = True
+ # window 2 layout - note - must be "new" every time a window is created
+ layout2 = [
+ [sg.Text('The second window'), sg.Text('', key='_OUTPUT_')],
+ [sg.Input(do_not_clear=True, key='_IN_')],
+ [sg.Button('Show'), sg.Button('Exit')]
+ ]
+ window2 = sg.Window('Second Window').Layout(layout2)
+ # Read window 2's events. Must use timeout of 0
+ if win2_active:
+ # print("reading 2")
+ event, values = window2.Read(timeout=100)
+ # print("win2 ", event)
+ if event != sg.TIMEOUT_KEY:
+ print("win2 ", event)
+ if event == 'Exit' or event is None:
+ # print("Closing window 2", event)
+ win2_active = False
+ window2.Close()
+ if event == 'Show':
+ sg.Popup('You entered ', values['_IN_'])
+
+window.Close()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows1.py b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows1.py
new file mode 100644
index 00000000..f2328e33
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows1.py
@@ -0,0 +1,35 @@
+"""
+ PySimpleGUI The Complete Course
+ Lesson 7 - Multiple Windows
+"""
+import PySimpleGUI as sg
+
+# Design pattern 1 - First window does not remain active
+
+layout = [[ sg.Text('Window 1'),],
+ [sg.Input(do_not_clear=True)],
+ [sg.Text('', size=(20,1), key='_OUTPUT_')],
+ [sg.Button('Launch 2')]]
+
+win1 = sg.Window('Window 1').Layout(layout)
+win2_active=False
+while True:
+ ev1, vals1 = win1.Read(timeout=100)
+ if ev1 is None:
+ break
+ win1.FindElement('_OUTPUT_').Update(vals1[0])
+
+ if ev1 == 'Launch 2' and not win2_active:
+ win2_active = True
+ win1.Hide()
+ layout2 = [[sg.Text('Window 2')],
+ [sg.Button('Exit')]]
+
+ win2 = sg.Window('Window 2').Layout(layout2)
+ while True:
+ ev2, vals2 = win2.Read()
+ if ev2 is None or ev2 == 'Exit':
+ win2.Close()
+ win2_active = False
+ win1.UnHide()
+ break
diff --git a/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows2.py b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows2.py
new file mode 100644
index 00000000..41bdad76
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows2.py
@@ -0,0 +1,34 @@
+"""
+ PySimpleGUI The Complete Course
+ Lesson 7 - Multiple Windows
+"""
+import PySimpleGUI as sg
+
+# Design pattern 2 - First window remains active
+
+layout = [[ sg.Text('Window 1'),],
+ [sg.Input(do_not_clear=True)],
+ [sg.Text('', size=(20,1), key='_OUTPUT_')],
+ [sg.Button('Launch 2'), sg.Button('Exit')]]
+
+win1 = sg.Window('Window 1').Layout(layout)
+
+win2_active = False
+while True:
+ ev1, vals1 = win1.Read(timeout=100)
+ win1.FindElement('_OUTPUT_').Update(vals1[0])
+ if ev1 is None or ev1 == 'Exit':
+ break
+
+ if not win2_active and ev1 == 'Launch 2':
+ win2_active = True
+ layout2 = [[sg.Text('Window 2')],
+ [sg.Button('Exit')]]
+
+ win2 = sg.Window('Window 2').Layout(layout2)
+
+ if win2_active:
+ ev2, vals2 = win2.Read(timeout=100)
+ if ev2 is None or ev2 == 'Exit':
+ win2_active = False
+ win2.Close()
diff --git a/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows3.py b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows3.py
new file mode 100644
index 00000000..b5500cec
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Pattern_Multiple_Windows3.py
@@ -0,0 +1,48 @@
+import PySimpleGUI as sg
+
+layout = [[sg.Text('Window 1'), ],
+ [sg.Input(do_not_clear=True)],
+ [sg.Text('',size=(20,1), key='_OUTPUT_')],
+ [sg.Button('Next >'), sg.Button('Exit')]]
+
+win1 = sg.Window('Window 1').Layout(layout)
+
+win3_active = win2_active = False
+while True:
+ if not win2_active:
+ ev1, vals1 = win1.Read()
+ if ev1 is None or ev1 == 'Exit':
+ break
+ win1.FindElement('_OUTPUT_').Update(vals1[0])
+
+ if not win2_active and ev1 == 'Next >':
+ win2_active = True
+ win1.Hide()
+ layout2 = [[sg.Text('Window 2')],
+ [sg.Button('< Prev'), sg.Button('Next >')]]
+
+ win2 = sg.Window('Window 2').Layout(layout2)
+
+ if win2_active:
+ ev2, vals2 = win2.Read()
+ if ev2 in (None, 'Exit', '< Prev'):
+ win2_active = False
+ win2.Close()
+ win1.UnHide()
+ elif ev2 == 'Next >':
+ win3_active = True
+ win2_active = False
+ win2.Hide()
+ layout3 = [[sg.Text('Window 3')],
+ [sg.Button('< Prev'), sg.Button('Exit')]]
+ win3 = sg.Window('Window 3').Layout(layout3)
+
+ if win3_active:
+ ev3, vals3 = win3.Read()
+ if ev3 == '< Prev':
+ win3.Close()
+ win3_active = False
+ win2_active = True
+ win2.UnHide()
+ elif ev3 in (None, 'Exit'):
+ break
diff --git a/DemoPrograms old/Demo_Design_Pattern_Persistent_Window.py b/DemoPrograms old/Demo_Design_Pattern_Persistent_Window.py
new file mode 100644
index 00000000..7d137458
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Pattern_Persistent_Window.py
@@ -0,0 +1,24 @@
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [
+ [sg.Text('Your typed chars appear here:'), sg.Text('', size=(20,1), key='-OUTPUT-')],
+ [sg.Input(do_not_clear=True, key='-IN-')],
+ [sg.Button('Show'), sg.Button('Exit')]
+ ]
+
+window = sg.Window('Window Title').Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event is None or event == 'Exit':
+ break
+ if event == 'Show':
+ # change the "output" element to be the value of "input" element
+ window.FindElement('-OUTPUT-').Update(values['-IN-'])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Design_Patterns.py b/DemoPrograms old/Demo_Design_Patterns.py
new file mode 100644
index 00000000..64b39661
--- /dev/null
+++ b/DemoPrograms old/Demo_Design_Patterns.py
@@ -0,0 +1,54 @@
+"""
+When creating a new PySimpleGUI program from scratch, start here.
+These are the accepted design patterns that cover the two primary use cases
+
+1. A "One Shot" window
+2. A persistent window that stays open after button clicks (uses an event loop)
+3. A persistent window that need to perform Update of an element before the window.read
+"""
+# ---------------------------------#
+# DESIGN PATTERN 1 - Simple Window #
+# ---------------------------------#
+import PySimpleGUI as sg
+
+layout = [[ sg.Text('My Oneshot') ],
+ [ sg.Button('OK') ]]
+
+window = sg.Window('My Oneshot', layout)
+event, values = window.read()
+window.close()
+
+
+# -------------------------------------#
+# DESIGN PATTERN 2 - Persistent Window #
+# -------------------------------------#
+import PySimpleGUI as sg
+
+layout = [[ sg.Text('My layout') ],
+ [ sg.Button('OK'), sg.Button('Cancel') ]]
+
+window = sg.Window('Design Pattern 2', layout)
+
+while True: # Event Loop
+ event, values = window.read()
+ if event in (None, 'Cancel'):
+ break
+window.close()
+
+# ------------------------------------------------------------------#
+# DESIGN PATTERN 3 - Persistent Window with "early update" required #
+# ------------------------------------------------------------------#
+import PySimpleGUI as sg
+
+layout = [[ sg.Text('My layout', key='-TEXT-KEY-') ],
+ [ sg.Button('OK'), sg.Button('Cancel') ]]
+
+window = sg.Window('Design Pattern 3', layout, finalize=True)
+
+window['-TEXT-KEY-'].Update('NEW Text') # Change the text field. Finalize allows us to do this
+
+while True: # Event Loop
+ event, values = window.read()
+ if event in (None, 'Cancel'):
+ break
+window.close()
diff --git a/DemoPrograms old/Demo_Desktop_Floating_Toolbar.py b/DemoPrograms old/Demo_Desktop_Floating_Toolbar.py
new file mode 100644
index 00000000..785ae428
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Floating_Toolbar.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import subprocess
+import os
+
+
+"""
+ Demo_Toolbar - A floating toolbar with quick launcher
+
+ One cool PySimpleGUI demo. Shows borderless windows, grab_anywhere, tight button layout
+ You can setup a specific program to launch when a button is clicked, or use the
+ Combobox to select a .py file found in the root folder, and run that file.
+
+"""
+
+ROOT_PATH = './'
+
+def Launcher():
+
+ # def print(line):
+ # window.FindElement('output').Update(line)
+
+ sg.ChangeLookAndFeel('Dark')
+
+ namesonly = [f for f in os.listdir(ROOT_PATH) if f.endswith('.py') ]
+
+ if len(namesonly) == 0:
+ namesonly = ['test 1', 'test 2', 'test 3']
+
+ sg.SetOptions(element_padding=(0,0), button_element_size=(12,1), auto_size_buttons=False)
+
+ layout = [[sg.Combo(values=namesonly, size=(35,30), key='demofile'),
+ sg.Button('Run', button_color=('white', '#00168B')),
+ sg.Button('Program 1'),
+ sg.Button('Program 2'),
+ sg.Button('Program 3', button_color=('white', '#35008B')),
+ sg.Button('EXIT', button_color=('white','firebrick3'))],
+ [sg.T('', text_color='white', size=(50,1), key='output')]]
+
+ window = sg.Window('Floating Toolbar', no_titlebar=True, grab_anywhere=True, keep_on_top=True).Layout(layout)
+
+
+ # ---===--- Loop taking in user input and executing appropriate program --- #
+ while True:
+ (event, values) = window.Read()
+ if event == 'EXIT' or event is None:
+ break # exit button clicked
+ if event == 'Program 1':
+ print('Run your program 1 here!')
+ elif event == 'Program 2':
+ print('Run your program 2 here!')
+ elif event == 'Run':
+ file = values['demofile']
+ print('Launching %s'%file)
+ ExecuteCommandSubprocess('python', os.path.join(ROOT_PATH, file))
+ else:
+ print(event)
+
+def ExecuteCommandSubprocess(command, *args, wait=False):
+ try:
+ if sys.platform == 'linux':
+ arg_string = ''
+ arg_string = ' '.join([str(arg) for arg in args])
+ # for arg in args:
+ # arg_string += ' ' + str(arg)
+ print('python3 ' + arg_string)
+ sp = subprocess.Popen(['python3 ', arg_string ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ else:
+ arg_string = ' '.join([str(arg) for arg in args])
+ sp = subprocess.Popen([command, arg_string], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ # sp = subprocess.Popen([command, list(args)], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ if wait:
+ out, err = sp.communicate()
+ if out:
+ print(out.decode("utf-8"))
+ if err:
+ print(err.decode("utf-8"))
+ except: pass
+
+
+
+if __name__ == '__main__':
+ Launcher()
+
diff --git a/DemoPrograms old/Demo_Desktop_Widget_CPU_Dashboard.py b/DemoPrograms old/Demo_Desktop_Widget_CPU_Dashboard.py
new file mode 100644
index 00000000..0512f4d6
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_CPU_Dashboard.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import psutil
+
+"""
+ Desktop floating widget - CPU Cores
+ Uses psutil to display:
+ CPU usage on each individual core
+ Information is updated once a second and is shown as an area graph that scrolls
+"""
+
+GRAPH_WIDTH = 120 # each individual graph size in pixels
+GRAPH_HEIGHT = 40
+TRANSPARENCY = .8 # how transparent the window looks. 0 = invisible, 1 = normal window
+NUM_COLS = 4
+POLL_FREQUENCY = 500 # how often to update graphs in milliseconds
+
+colors = ('#23a0a0', '#56d856', '#be45be', '#5681d8', '#d34545', '#BE7C29')
+
+# DashGraph does the drawing of each graph
+class DashGraph(object):
+ def __init__(self, graph_elem, text_elem, starting_count, color):
+ self.graph_current_item = 0
+ self.graph_elem = graph_elem
+ self.text_elem = text_elem
+ self.prev_value = starting_count
+ self.max_sent = 1
+ self.color = color
+
+ def graph_percentage_abs(self, value):
+ self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, value), color=self.color)
+ if self.graph_current_item >= GRAPH_WIDTH:
+ self.graph_elem.Move(-1,0)
+ else:
+ self.graph_current_item += 1
+
+ def text_display(self, text):
+ self.text_elem.Update(text)
+
+def main():
+ # A couple of "Uber Elements" that combine several elements and enable bulk edits
+ def Txt(text, **kwargs):
+ return(sg.Text(text, font=('Helvetica 8'), **kwargs))
+
+ def GraphColumn(name, key):
+ col = sg.Column([[Txt(name, key=key+'_TXT_'), ],
+ [sg.Graph((GRAPH_WIDTH, GRAPH_HEIGHT), (0, 0), (GRAPH_WIDTH, 100), background_color='black',
+ key=key+'_GRAPH_')]], pad=(2, 2))
+ return col
+
+
+ num_cores = len(psutil.cpu_percent(percpu=True)) # get the number of cores in the CPU
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0), margins=(1,1), border_width=0)
+
+ # ---------------- Create Layout ----------------
+ layout = [[ sg.Button('', image_data=red_x, button_color=('black', 'black'), key='Exit', tooltip='Closes window'),
+ sg.Text(' CPU Core Usage')] ]
+
+ # add on the graphs
+ for rows in range(num_cores//NUM_COLS+1):
+ row = []
+ for cols in range(min(num_cores-rows*NUM_COLS, NUM_COLS)):
+ row.append(GraphColumn('CPU '+str(rows*NUM_COLS+cols), '_CPU_'+str(rows*NUM_COLS+cols)))
+ layout.append(row)
+
+ # ---------------- Create Window ----------------
+ window = sg.Window('PSG System Dashboard',
+ keep_on_top=True,
+ auto_size_buttons=False,
+ grab_anywhere=True,
+ no_titlebar=True,
+ default_button_element_size=(12, 1),
+ return_keyboard_events=True,
+ alpha_channel=TRANSPARENCY,
+ use_default_focus=False,
+ ).Layout(layout).Finalize()
+
+ # setup graphs & initial values
+ graphs = []
+ for i in range(num_cores):
+ graphs.append(DashGraph(window.FindElement('_CPU_'+str(i)+'_GRAPH_'),
+ window.FindElement('_CPU_'+str(i) + '_TXT_'),
+ 0, colors[i%6]))
+
+ # ---------------- main loop ----------------
+ while (True):
+ # --------- Read and update window once every Polling Frequency --------
+ event, values = window.Read(timeout=POLL_FREQUENCY)
+ if event in (None, 'Exit'): # Be nice and give an exit
+ break
+ # read CPU for each core
+ stats = psutil.cpu_percent(percpu=True)
+ # Update each graph
+ for i in range(num_cores):
+ graphs[i].graph_percentage_abs(stats[i])
+ graphs[i].text_display('{} CPU {:2.0f}'.format(i, stats[i]))
+ window.Close()
+
+if __name__ == "__main__":
+ # the clever Red X graphic
+ red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+
+ main()
+ sys.exit(69)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Desktop_Widget_CPU_Graph.py b/DemoPrograms old/Demo_Desktop_Widget_CPU_Graph.py
new file mode 100644
index 00000000..801cd61e
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_CPU_Graph.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import time
+import random
+import psutil
+from threading import Thread
+
+
+STEP_SIZE=3
+SAMPLES = 300
+SAMPLE_MAX = 500
+CANVAS_SIZE = (300,200)
+
+
+g_interval = .25
+g_cpu_percent = 0
+g_procs = None
+g_exit = False
+
+def CPU_thread(args):
+ global g_interval, g_cpu_percent, g_procs, g_exit
+
+ while not g_exit:
+ try:
+ g_cpu_percent = psutil.cpu_percent(interval=g_interval)
+ g_procs = psutil.process_iter()
+ except:
+ pass
+
+def main():
+ global g_exit, g_response_time
+ # start ping measurement thread
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0))
+
+ layout = [ [sg.Quit( button_color=('white','black')), sg.T('', pad=((100,0),0), font='Any 15', key='output')],
+ [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,SAMPLE_MAX),background_color='black', key='graph')],]
+
+ window = sg.Window('CPU Graph', grab_anywhere=True, keep_on_top=True, background_color='black', no_titlebar=True, use_default_focus=False).Layout(layout)
+
+ graph = window.FindElement('graph')
+ output = window.FindElement('output')
+ # start cpu measurement thread
+ thread = Thread(target=CPU_thread,args=(None,))
+ thread.start()
+
+ last_cpu = i = 0
+ prev_x, prev_y = 0, 0
+ while True: # the Event Loop
+ time.sleep(.5)
+ event, values = window.Read(timeout=0)
+ if event == 'Quit' or event is None: # always give ths user a way out
+ break
+ # do CPU measurement and graph it
+ current_cpu = int(g_cpu_percent*10)
+ if current_cpu == last_cpu:
+ continue
+ output.Update(current_cpu/10) # show current cpu usage at top
+ if current_cpu > SAMPLE_MAX:
+ current_cpu = SAMPLE_MAX
+ new_x, new_y = i, current_cpu
+ if i >= SAMPLES:
+ graph.Move(-STEP_SIZE,0) # shift graph over if full of data
+ prev_x = prev_x - STEP_SIZE
+ graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
+ prev_x, prev_y = new_x, new_y
+ i += STEP_SIZE if i < SAMPLES else 0
+ last_cpu = current_cpu
+
+ g_exit = True
+ window.Close()
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization.py b/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization.py
new file mode 100644
index 00000000..39aefb5d
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import psutil
+import time
+from threading import Thread
+import operator
+
+
+"""
+ PSUTIL Desktop Widget
+ Creates a floating CPU utilization window that is always on top of other windows
+ You move it by grabbing anywhere on the window
+ Good example of how to do a non-blocking, polling program using PySimpleGUI
+ Use the spinner to adjust the number of seconds between readings of the CPU utilizaiton
+
+ NOTE - you will get a warning message printed when you exit using exit button.
+ It will look something like:
+ invalid command name "1616802625480StopMove"
+"""
+
+# globale used to communicate with thread.. yea yea... it's working fine
+g_interval = 1
+g_cpu_percent = 0
+g_procs = None
+g_exit = False
+
+def CPU_thread(args):
+ global g_interval, g_cpu_percent, g_procs, g_exit
+
+ while not g_exit:
+ try:
+ g_cpu_percent = psutil.cpu_percent(interval=g_interval)
+ g_procs = psutil.process_iter()
+ except:
+ pass
+
+
+def main():
+ global g_interval, g_procs, g_exit
+
+ # ---------------- Create Form ----------------
+ sg.ChangeLookAndFeel('Black')
+ layout = [[sg.Text('', size=(8,1), font=('Helvetica', 20),text_color=sg.YELLOWS[0],
+ justification='center', key='text')],
+ [sg.Text('', size=(30, 8), font=('Courier New', 12),text_color='white', justification='left', key='processes')],
+ [sg.Exit(button_color=('white', 'firebrick4'), pad=((15,0), 0), size=(9,1)),
+ sg.Spin([x+1 for x in range(10)], 3, key='spin')],]
+
+ window = sg.Window('CPU Utilization',
+ no_titlebar=True,
+ keep_on_top=True,
+ alpha_channel=.8,
+ grab_anywhere=True).Layout(layout)
+
+ # start cpu measurement thread
+ thread = Thread(target=CPU_thread,args=(None,))
+ thread.start()
+ timeout_value = 1 # make first read really quick
+ g_interval = 1
+ # ---------------- main loop ----------------
+ while (True):
+ # --------- Read and update window --------
+ event, values = window.Read(timeout=timeout_value, timeout_key='Timeout')
+ # --------- Do Button Operations --------
+ if event is None or event == 'Exit':
+ break
+
+ timeout_value = int(values['spin']) * 1000
+
+ cpu_percent = g_cpu_percent
+ display_string = ''
+ if g_procs:
+ # --------- Create list of top % CPU porocesses --------
+ try:
+ top = {proc.name() : proc.cpu_percent() for proc in g_procs}
+ except: pass
+
+
+ top_sorted = sorted(top.items(), key=operator.itemgetter(1), reverse=True)
+ if top_sorted:
+ top_sorted.pop(0)
+ display_string = ''
+ for proc, cpu in top_sorted:
+ display_string += '{:2.2f} {}\n'.format(cpu/10, proc)
+
+ # --------- Display timer and proceses in window --------
+ window.FindElement('text').Update('CPU {}'.format(cpu_percent))
+ window.FindElement('processes').Update(display_string)
+
+ g_exit = True
+ thread.join()
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization_Simple.py b/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization_Simple.py
new file mode 100644
index 00000000..f5c61b32
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_CPU_Utilization_Simple.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import psutil
+
+# ---------------- Create Form ----------------
+sg.ChangeLookAndFeel('Black')
+
+layout = [[sg.Text('CPU Utilization')],
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='_text_')],
+ [sg.Exit(button_color=('white', 'firebrick4'), pad=((15, 0), 0), size=(9,1)),
+ sg.Spin([x + 1 for x in range(10)], 3, key='_spin_')]]
+
+# Layout the rows of the Window
+window = sg.Window('CPU Meter',
+ no_titlebar=True,
+ keep_on_top=True,
+ grab_anywhere=True).Layout(layout).Finalize()
+
+# ---------------- main loop ----------------
+interval = 10 # For the first one, make it quick
+while (True):
+ # --------- Read and update window --------
+ event, values = window.Read(timeout=interval)
+ # --------- Do Button Operations --------
+ if event is None or event == 'Exit':
+ break
+
+ interval = int(values['_spin_'])*1000
+
+
+
+ cpu_percent = psutil.cpu_percent(interval=1)
+
+ # --------- Display timer in window --------
+
+ window.FindElement('_text_').Update(f'CPU {cpu_percent:02.0f}%')
+
+# Broke out of main loop. Close the window.
+window.CloseNonBlocking()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Desktop_Widget_Email_Notification.py b/DemoPrograms old/Demo_Desktop_Widget_Email_Notification.py
new file mode 100644
index 00000000..de4264d8
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_Email_Notification.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+import sys
+
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+ sg.PopupError('Sorry, at the moment this program only runs on Python 3')
+ sys.exit()
+
+import email
+import imaplib
+from datetime import datetime
+import calendar
+
+IMAP_SERVER_GMAIL = 'imap.gmail.com' # gmail server address
+IMAP_SERVER_HOTMAIL = 'imap-mail.outlook.com' # hotmail server address
+
+################# Change these to match your email setup ################
+LOGIN_EMAIL = 'you@mail.com'
+LOGIN_PASSWORD = 'your email password'
+IMAP_SERVER = IMAP_SERVER_GMAIL # change to match your email service
+
+MAX_EMAILS = 10
+
+
+def gui():
+ sg.ChangeLookAndFeel('Topanga')
+
+ sg.SetOptions(border_width=0, margins=(0, 0), element_padding=(4, 0))
+
+ layout = [[sg.T('Email New Mail Notification' + 48 * ' '),
+ sg.Button('', image_data=refresh, button_color=('#282923', '#282923'), key='_refresh_',
+ tooltip='Refreshes Email'),
+ sg.Button('', image_data=red_x, button_color=('#282923', '#282923'), key='_quit_',
+ tooltip='Closes window')],
+ [sg.T('', key='_status_', size=(25, 1))], ]
+
+ for i in range(MAX_EMAILS):
+ layout.append([sg.T('', size=(20, 1), key='{}date'.format(i), font='Sans 8'),
+ sg.T('', size=(45, 1), font='Sans 8', key='{}from'.format(i))])
+
+ window = sg.Window('',
+ no_titlebar=True,
+ grab_anywhere=True,
+ keep_on_top=True,
+ alpha_channel=0,
+ ).Layout(layout).Finalize()
+
+ # move the window to the upper right corner of the screen
+ w, h = window.GetScreenDimensions()
+ window.Move(w - 410, 0)
+ window.SetAlpha(.9)
+ window.Refresh()
+ status_elem = window.FindElement('_status_')
+ # The Event Loop
+ while True:
+ status_elem.Update('Reading...')
+ window.Refresh()
+ read_mail(window)
+ status_elem.Update('')
+ event, values = window.Read(timeout=30 * 1000) # return every 30 seconds
+ if event == '_quit_':
+ break
+
+
+def read_mail(window):
+ """
+ Reads late emails from IMAP server and displays them in the Window
+ :param window: window to display emails in
+ :return:
+ """
+ mail = imaplib.IMAP4_SSL(IMAP_SERVER)
+
+ (retcode, capabilities) = mail.login(LOGIN_EMAIL, LOGIN_PASSWORD)
+ mail.list()
+ typ, data = mail.select('Inbox')
+ n = 0
+ now = datetime.now()
+ # get messages from today
+ search_string = '(SENTON {}-{}-{})'.format(now.day, calendar.month_abbr[now.month], now.year)
+ (retcode, messages) = mail.search(None, search_string)
+ if retcode == 'OK':
+ msg_list = messages[0].split() # message numbers are separated by spaces, turn into list
+ msg_list.sort(reverse=True) # sort messages descending
+ for n, message in enumerate(msg_list):
+ if n >= MAX_EMAILS:
+ break
+ from_elem = window.FindElement('{}from'.format(n))
+ date_elem = window.FindElement('{}date'.format(n))
+ from_elem.Update('') # erase them so you know they're changing
+ date_elem.Update('')
+ window.Refresh()
+ typ, data = mail.fetch(message, '(RFC822)')
+ for response_part in data:
+ if isinstance(response_part, tuple):
+ original = email.message_from_bytes(response_part[1])
+ date_str = original['Date'][:22]
+ from_elem.Update(original['From'])
+ date_elem.Update(date_str)
+ window.Refresh() # make the window changes show up right away
+
+
+red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+
+refresh = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACb0lEQVR4XpVTXUiTbxT/vePdFuF0BTFW9ufVvMlu+iACka6CQY1gQVdtEmTMpSKzzJT/RTdCRHhT4F0Us8LGVqlo1lZaFslWQWBkN+tDkpSpbfNz797T8zy6DbUbf/Dbec7vfOycMwa0DBJjM7Ko72mBtz+KplCS6Ronf3NNxNZBt2qv4dJzL0uKwGRqU/6zHDqyd1dBk32/xMnfXOMxkVPXXYlVSLjykk4fKIb/4zgUSxEO7zRBKd4Bjm/jU9ys8f2fJoCFhRiWl6pw6+Qw0BymhlfT5Lg/xmycHA++ktL+nsRqrUOrdpBpH6hhKC7yhObti0CgKUTu0KTgcd8X4j4aB2bYvj7UPqkQrO/1cU25ESV3eJJO8LzLIQ11/CYXn5Grf4KqGF19E3Ts9iixe2QPm0dtt5PtP6NcHxF5ZVfDhIbeqMQ6E0hcI4ec327jah513T4YDM5TR/dh8vc0hkfHUxI2gwuPKyDLb2wV5cIdePuZZGwWmQxSSyqICFBVyKgJJkFaQW4Hna4THQ4X/gUiD2+QXEwjNZsASJvTgWgMqoY95WWw7raAJdjheeTEeniCTqgZu2IxswnSmGI3gEZjMiQpAMocTC2nJcm4hU9gRjp9E+6Ajb07wKFpHqRVOzKqedFUhOX4HyRnEwSjMQCB8/4IqnxU2DYiaGnsIe7n2UlK61MWe0dbW18Ijdfk/wuy7IXeEEvEvmM+kcRM4XYYSkohW62ChtIS/NKbWGwO8z9+Anp9TNSsQU2wEtVdEZy5o7Gfi7Z5ewj/vxbkPs51kYhVP4zAw3I3IN+ohSVFcfZeEs67Gid/c03E1uEv5QpTFzvZK5EAAAAASUVORK5CYII='
+
+gui()
diff --git a/DemoPrograms old/Demo_Desktop_Widget_Timer.py b/DemoPrograms old/Demo_Desktop_Widget_Timer.py
new file mode 100644
index 00000000..31b24875
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_Timer.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+import time
+
+"""
+ Timer Desktop Widget Creates a floating timer that is always on top of other windows You move it by grabbing anywhere on the window Good example of how to do a non-blocking, polling program using PySimpleGUI
+ Something like this can be used to poll hardware when running on a Pi
+
+ While the timer ticks are being generated by PySimpleGUI's "timeout" mechanism, the actual value
+ of the timer that is displayed comes from the system timer, time.time(). This guarantees an
+ accurate time value is displayed regardless of the accuracy of the PySimpleGUI timer tick. If
+ this design were not used, then the time value displayed would slowly drift by the amount of time
+ it takes to execute the PySimpleGUI read and update calls (not good!)
+
+"""
+
+def time_as_int():
+ return int(round(time.time() * 100))
+
+# ---------------- Create Form ----------------
+sg.ChangeLookAndFeel('Black')
+
+layout = [[sg.Text('')],
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')],
+ [sg.Button('Pause', key='-RUN-PAUSE-', button_color=('white', '#001480')),
+ sg.Button('Reset', button_color=('white', '#007339'), key='-RESET-'),
+ sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]]
+
+window = sg.Window('Running Timer', layout, no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True, element_padding=(0,0))
+
+# ---------------- main loop ----------------
+current_time, paused_time, paused = 0, 0, False
+start_time = time_as_int()
+while (True):
+ # --------- Read and update window --------
+ if not paused:
+ event, values = window.Read(timeout=10)
+ current_time = time_as_int() - start_time
+ else:
+ event, values = window.Read()
+ # --------- Do Button Operations --------
+ if event in (None, 'Exit'): # ALWAYS give a way out of program
+ break
+ if event == '-RESET-':
+ paused_time = start_time = time_as_int()
+ current_time = 0
+ elif event == '-RUN-PAUSE-':
+ paused = not paused
+ if paused:
+ paused_time = time_as_int()
+ else:
+ start_time = start_time + time_as_int() - paused_time
+ window['-RUN-PAUSE-'].Update('Run' if paused else 'Pause') # Change button's text
+
+ # --------- Display timer in window --------
+ window['text'].Update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60,
+ (current_time // 100) % 60,
+ current_time % 100))
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Desktop_Widget_psutil_Dashboard.py b/DemoPrograms old/Demo_Desktop_Widget_psutil_Dashboard.py
new file mode 100644
index 00000000..3d1df605
--- /dev/null
+++ b/DemoPrograms old/Demo_Desktop_Widget_psutil_Dashboard.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import psutil
+
+"""
+ Desktop floating widget - System status dashboard
+ Uses psutil to display:
+ Network I/O
+ Disk I/O
+ CPU Used
+ Mem Used
+ Information is updated once a second and is shown as an area graph that scrolls
+"""
+
+GRAPH_WIDTH = 120 # each individual graph size in pixels
+GRAPH_HEIGHT = 40
+ALPHA = .7
+
+class DashGraph(object):
+ def __init__(self, graph_elem, starting_count, color):
+ self.graph_current_item = 0
+ self.graph_elem = graph_elem
+ self.prev_value = starting_count
+ self.max_sent = 1
+ self.color = color
+
+ def graph_value(self, current_value):
+ delta = current_value - self.prev_value
+ self.prev_value = current_value
+ self.max_sent = max(self.max_sent, delta)
+ percent_sent = 100 * delta / self.max_sent
+ self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, percent_sent), color=self.color)
+ if self.graph_current_item >= GRAPH_WIDTH:
+ self.graph_elem.Move(-1,0)
+ else:
+ self.graph_current_item += 1
+ return delta
+
+ def graph_percentage_abs(self, value):
+ self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, value), color=self.color)
+ if self.graph_current_item >= GRAPH_WIDTH:
+ self.graph_elem.Move(-1,0)
+ else:
+ self.graph_current_item += 1
+
+
+def human_size(bytes, units=[' bytes','KB','MB','GB','TB', 'PB', 'EB']):
+ """ Returns a human readable string reprentation of bytes"""
+ return str(bytes) + units[0] if bytes < 1024 else human_size(bytes>>10, units[1:])
+
+def main():
+ # Make the layout less cluttered and allow bulk-changes to text formatting
+ def Txt(text, **kwargs):
+ return(sg.Text(text, font=('Helvetica 8'), **kwargs))
+ # Update a Text Element
+ def Txt_Update(window, key, value):
+ window.FindElement(key).Update(value)
+
+ # ---------------- Create Window ----------------
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0), margins=(1,1), border_width=0)
+
+ def GraphColumn(name, key):
+ col = sg.Column([[Txt(name, key=key+'TXT_'), ],
+ [sg.Graph((GRAPH_WIDTH, GRAPH_HEIGHT), (0, 0), (GRAPH_WIDTH, 100), background_color='black',
+ key=key+'GRAPH_')]], pad=(2, 2))
+ return col
+
+ layout = [[sg.Text('System Status Dashboard'+' '*18), sg.Button('', image_data=red_x, button_color=('black', 'black'), key='Exit', tooltip='Closes window')],
+ [GraphColumn('Net Out', '_NET_OUT_'),
+ GraphColumn('Net In', '_NET_IN_')],
+ [GraphColumn('Disk Read', '_DISK_READ_'),
+ GraphColumn('Disk Write', '_DISK_WRITE_')],
+ [GraphColumn('CPU Usage', '_CPU_'),
+ GraphColumn('Memory Usage', '_MEM_')],]
+
+ window = sg.Window('PSG System Dashboard',
+ keep_on_top=True,
+ auto_size_buttons=False,
+ grab_anywhere=True,
+ no_titlebar=True,
+ default_button_element_size=(12, 1),
+ return_keyboard_events=True,
+ alpha_channel=ALPHA,
+ use_default_focus=False,
+ ).Layout(layout).Finalize()
+
+ # setup graphs & initial values
+ netio = psutil.net_io_counters()
+ net_graph_in = DashGraph(window.FindElement('_NET_IN_GRAPH_'), netio.bytes_recv, '#23a0a0')
+ net_graph_out = DashGraph(window.FindElement('_NET_OUT_GRAPH_'), netio.bytes_sent, '#56d856')
+
+
+ diskio = psutil.disk_io_counters()
+ disk_graph_write = DashGraph(window.FindElement('_DISK_WRITE_GRAPH_'), diskio.write_bytes, '#be45be')
+ disk_graph_read = DashGraph(window.FindElement('_DISK_READ_GRAPH_'), diskio.read_bytes, '#5681d8')
+
+ cpu_usage_graph = DashGraph(window.FindElement('_CPU_GRAPH_'), 0, '#d34545')
+ mem_usage_graph = DashGraph(window.FindElement('_MEM_GRAPH_'), 0, '#BE7C29')
+
+ print(psutil.cpu_percent(percpu=True))
+ # ---------------- main loop ----------------
+ while (True):
+ # --------- Read and update window once a second--------
+ event, values = window.Read(timeout=1000)
+ if event in (None, 'Exit'): # Be nice and give an exit, expecially since there is no titlebar
+ break
+ # ----- Network Graphs -----
+ netio = psutil.net_io_counters()
+ write_bytes = net_graph_out.graph_value(netio.bytes_sent)
+ read_bytes = net_graph_in.graph_value(netio.bytes_recv)
+ Txt_Update(window, '_NET_OUT_TXT_', 'Net out {}'.format(human_size(write_bytes)))
+ Txt_Update(window, '_NET_IN_TXT_', 'Net In {}'.format(human_size(read_bytes)))
+ # ----- Disk Graphs -----
+ diskio = psutil.disk_io_counters()
+ write_bytes = disk_graph_write.graph_value(diskio.write_bytes)
+ read_bytes = disk_graph_read.graph_value(diskio.read_bytes)
+ Txt_Update(window, '_DISK_WRITE_TXT_', 'Disk Write {}'.format(human_size(write_bytes)))
+ Txt_Update(window, '_DISK_READ_TXT_', 'Disk Read {}'.format(human_size(read_bytes)))
+ # ----- CPU Graph -----
+ cpu = psutil.cpu_percent(0)
+ cpu_usage_graph.graph_percentage_abs(cpu)
+ Txt_Update(window, '_CPU_TXT_', '{0:2.0f}% CPU Used'.format(cpu))
+ # ----- Memory Graph -----
+ mem_used = psutil.virtual_memory().percent
+ mem_usage_graph.graph_percentage_abs(mem_used)
+ Txt_Update(window, '_MEM_TXT_', '{}% Memory Used'.format(mem_used))
+
+if __name__ == "__main__":
+ # the clever Red X graphic
+ red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+
+ main()
+ sys.exit(69)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Disable_Elements.py b/DemoPrograms old/Demo_Disable_Elements.py
new file mode 100644
index 00000000..4f4f7778
--- /dev/null
+++ b/DemoPrograms old/Demo_Disable_Elements.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+sg.ChangeLookAndFeel('Dark')
+sg.SetOptions(element_padding=(0, 0))
+
+layout = [
+ [sg.T('Notes:', pad=((3, 0), 0)), sg.In(size=(44, 1), background_color='white', text_color='black', key='notes')],
+ [sg.T('Output:', pad=((3, 0), 0)), sg.T('', size=(44, 1), text_color='white', key='output')],
+ [sg.CBox('Checkbox:', default=True, pad=((3, 0), 0), disabled=True, key='cbox'), sg.Listbox((1,2,3,4),size=(8,3),disabled=True, key='listbox'),
+ sg.Radio('Radio 1', default=True, group_id='1', disabled=True, key='radio1'), sg.Radio('Radio 2', default=False, group_id='1', disabled=True, key='radio2')],
+ [sg.Spin((1,2,3,4),1,disabled=True, key='spin'), sg.OptionMenu((1,2,3,4),disabled=True, key='option'), sg.Combo(values=(1,2,3,4),disabled=True,key='combo')],
+ [sg.Multiline('Multiline', size=(20,3),disabled=True, key='multi')],
+ [sg.Slider((1,10), size=(20,20), orientation='h', disabled=True, key='slider')],
+ [sg.Button('Enable', button_color=('white', 'black')),
+ sg.Button('Disable', button_color=('white', 'black')),
+ sg.Button('Reset', button_color=('white', '#9B0023'), key='reset'),
+ sg.Button('Values', button_color=('white', 'springgreen4')),
+ sg.Button('Exit', disabled=True, button_color=('white', '#00406B'), key='exit')]]
+
+window = sg.Window("Disable Elements Demo", default_element_size=(12, 1), text_justification='r', auto_size_text=False,
+ auto_size_buttons=False, keep_on_top=True, grab_anywhere=False,
+ default_button_element_size=(12, 1)).Layout(layout).Finalize()
+
+key_list = 'cbox', 'listbox', 'radio1', 'radio2', 'spin', 'option', 'combo', 'reset', 'notes', 'multi', 'slider', 'exit'
+
+for key in key_list: window.FindElement(key).Update(disabled=True) # don't do this kind of for-loop
+
+while True:
+ event, values = window.Read()
+ if event in (None, 'exit'):
+ break
+ elif event == 'Disable':
+ for key in key_list: window.FindElement(key).Update(disabled=True)
+ elif event == 'Enable':
+ for key in key_list: window.FindElement(key).Update(disabled=False)
+ elif event == 'Values':
+ sg.Popup(values, keep_on_top=True)
+
+sys.exit(0)
diff --git a/DemoPrograms old/Demo_DuplicateFileFinder.py b/DemoPrograms old/Demo_DuplicateFileFinder.py
new file mode 100644
index 00000000..7e7045c6
--- /dev/null
+++ b/DemoPrograms old/Demo_DuplicateFileFinder.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import hashlib
+import os
+
+
+# ====____====____==== FUNCTION DeDuplicate_folder(path) ====____====____==== #
+# Function to de-duplicate the folder passed in #
+# --------------------------------------------------------------------------- #
+def FindDuplicatesFilesInFolder(path):
+ shatab = []
+ total = 0
+ small_count, dup_count, error_count = 0,0,0
+ pngdir = path
+ if not os.path.exists(path):
+ sg.Popup('Duplicate Finder', '** Folder doesn\'t exist***', path)
+ return
+ pngfiles = os.listdir(pngdir)
+ total_files = len(pngfiles)
+ for idx, f in enumerate(pngfiles):
+ if not sg.OneLineProgressMeter('Counting Duplicates', idx + 1, total_files, 'Counting Duplicate Files'):
+ break
+ total += 1
+ fname = os.path.join(pngdir, f)
+ if os.path.isdir(fname):
+ continue
+ x = open(fname, "rb").read()
+
+ m = hashlib.sha256()
+ m.update(x)
+ f_sha = m.digest()
+ if f_sha in shatab:
+ # uncomment next line to remove duplicate files
+ # os.remove(fname)
+ dup_count += 1
+ # sg.Print(f'Duplicate file - {f}') # cannot current use sg.Print with Progress Meter
+ continue
+ shatab.append(f_sha)
+
+ msg = '{} Files processed\n {} Duplicates found'.format(total_files, dup_count)
+ sg.Popup('Duplicate Finder Ended', msg)
+
+# ====____====____==== Pseudo-MAIN program ====____====____==== #
+# This is our main-alike piece of code #
+# + Starts up the GUI #
+# + Gets values from GUI #
+# + Runs DeDupe_folder based on GUI inputs #
+# ------------------------------------------------------------- #
+if __name__ == '__main__':
+
+ source_folder = None
+ source_folder = sg.PopupGetFolder('Duplicate Finder - Count number of duplicate files', 'Enter path to folder you wish to find duplicates in')
+ if source_folder is not None:
+ FindDuplicatesFilesInFolder(source_folder)
+ else:
+ sg.PopupCancel('Cancelling', '*** Cancelling ***')
+ exit(0)
diff --git a/DemoPrograms old/Demo_EXE_Maker.py b/DemoPrograms old/Demo_EXE_Maker.py
new file mode 100644
index 00000000..9d751c6f
--- /dev/null
+++ b/DemoPrograms old/Demo_EXE_Maker.py
@@ -0,0 +1,75 @@
+import PySimpleGUI as sg
+import subprocess
+from shutil import copyfile
+import shutil
+import os
+
+def Launcher():
+ sg.ChangeLookAndFeel('LightGreen')
+
+ layout = [[sg.T('PyInstaller EXE Creator', font='Any 15')],
+ [sg.T('Source Python File'), sg.In(key='_sourcefile_', size=(45,1)), sg.FileBrowse(file_types=(("Python Files", "*.py"),))],
+ [sg.T('Icon File'), sg.In(key='_iconfile_', size=(45,1)), sg.FileBrowse(file_types=(("Icon Files", "*.ico"),))],
+ [sg.Frame('Output', font='Any 15',layout= [[sg.Output(size=(65, 15), font='Courier 10')]])],
+ [sg.ReadFormButton('Make EXE',bind_return_key=True),
+ sg.SimpleButton('Quit', button_color=('white','firebrick3')),]]
+
+ window = sg.Window('PySimpleGUI EXE Maker',
+ auto_size_text=False,
+ auto_size_buttons=False,
+ default_element_size=(20,1,),
+ text_justification='right')
+
+ window.Layout(layout)
+
+ # ---===--- Loop taking in user input --- #
+ while True:
+ (button, values) = window.Read()
+ if button in ('Quit', None):
+ break # exit button clicked
+
+ source_file = values['_sourcefile_']
+ icon_file = values['_iconfile_']
+
+ icon_option = '-i "{}"'.format(icon_file) if icon_file else ''
+ source_path, source_filename = os.path.split(source_file)
+ workpath_option = '--workpath "{}"'.format(source_path)
+ dispath_option = '--distpath "{}"'.format(source_path)
+ specpath_option = '--specpath "{}"'.format(source_path)
+ folder_to_remove = os.path.join(source_path,source_filename[:-3])
+ file_to_remove = os.path.join(source_path, source_filename[:-3]+'.spec')
+ command_line = 'pyinstaller -wF "{}" {} {} {} {}'.format(source_file, icon_option, workpath_option, dispath_option, specpath_option)
+
+ if button == 'Make EXE':
+ try:
+ print(command_line)
+ print('Making EXE... this will take a while.. the program has NOT locked up...')
+ window.Refresh()
+ # print('Running command {}'.format(command_line))
+ runCommand(command_line)
+ shutil.rmtree(folder_to_remove)
+ os.remove(file_to_remove)
+ print('**** DONE ****')
+ except:
+ sg.PopupError('Something went wrong')
+
+
+def runCommand(cmd, timeout=None):
+ """ run shell command
+
+ @param cmd: command to execute
+ @param timeout: timeout for command execution
+
+ @return: (return code from command, command output)
+ """
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = ''
+
+ out, err = p.communicate()
+ p.wait(timeout)
+
+ return (out, err)
+
+if __name__ == '__main__':
+ Launcher()
+
diff --git a/DemoPrograms old/Demo_Event_Callback_Simulation.py b/DemoPrograms old/Demo_Event_Callback_Simulation.py
new file mode 100644
index 00000000..d3642368
--- /dev/null
+++ b/DemoPrograms old/Demo_Event_Callback_Simulation.py
@@ -0,0 +1,53 @@
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+# import PySimpleGUIWx as sg
+
+'''
+ Event Callback Simulation
+
+ This design pattern simulates callbacks for events.
+ This is NOT the "normal" way things work in PySimpleGUI and is an architecture that is actively discouraged
+ Unlike tkinter, Qt, etc, PySimpleGUI does not utilize callback
+ functions as a mechanism for communicating when button presses or other events happen.
+ BUT, should you want to quickly convert some existing code that does use callback functions, then this
+ is one way to do a "quick and dirty" port to PySimpleGUI.
+'''
+
+# The callback functions
+# These callbacks all display a message in a non-blocking way and immediately return
+def button1(event, values):
+ sg.popup_quick_message('Button 1 callback', background_color='red', text_color='white')
+
+def button2(event, values):
+ sg.popup_quick_message('Button 2 callback', background_color='green', text_color='white')
+
+def catch_all(event, values):
+ sg.popup_quick_message(f'An unplanned event = "{event}" happend', background_color='blue', text_color='white', auto_close_duration=6)
+
+# Lookup dictionary that maps event to function to call. In this case, only 2 event have defined callbacks
+func_dict = {'1':button1, '2':button2}
+
+# Layout the design of the GUI
+layout = [[sg.Text('Please click a button')],
+ [sg.Button('1'), sg.Button('2'), sg.Button('Not defined', key='-MY-KEY-'), sg.Quit()]]
+
+# Show the Window to the user
+window = sg.Window('Button callback example', layout)
+
+# Event loop. Read buttons, make callbacks
+while True:
+ # Read the Window
+ event, values = window.read()
+ # Lookup event in function dictionary and call the function, passing in the event and values variables
+ try:
+ func_dict[event](event, values) # Call function with event and values
+ except:
+ catch_all(event, values)
+ # See if should close the window
+ if event in ('Quit', None): # normally this is done IMMEDIATELY after the read
+ break
+
+window.close()
+
+# All done!
+sg.popup_auto_close('Done... this window auto closes')
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Fill_Form.py b/DemoPrograms old/Demo_Fill_Form.py
new file mode 100644
index 00000000..08e24929
--- /dev/null
+++ b/DemoPrograms old/Demo_Fill_Form.py
@@ -0,0 +1,66 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+def Everything():
+ sg.ChangeLookAndFeel('TanBlue')
+
+ column1 = [
+ [sg.Text('Column 1', background_color=sg.DEFAULT_BACKGROUND_COLOR, justification='center', size=(10, 1))],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1', key='spin1')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2', key='spin2')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3', key='spin3')]]
+
+ layout = [
+ [sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))],
+ [sg.Text('Here is some text.... and a place to enter text')],
+ [sg.InputText('This is my text', key='in1', do_not_clear=True)],
+ [sg.Checkbox('Checkbox', key='cb1'), sg.Checkbox('My second checkbox!', key='cb2', default=True)],
+ [sg.Radio('My first Radio! ', "RADIO1", key='rad1', default=True),
+ sg.Radio('My second Radio!', "RADIO1", key='rad2')],
+ [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3),
+ key='multi1', do_not_clear=True),
+ sg.Multiline(default_text='A second multi-line', size=(35, 3), key='multi2', do_not_clear=True)],
+ [sg.InputCombo(('Combobox 1', 'Combobox 2'), key='combo', size=(20, 1)),
+ sg.Slider(range=(1, 100), orientation='h', size=(34, 20), key='slide1', default_value=85)],
+ [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'), key='optionmenu')],
+ [sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3), key='listbox'),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, key='slide2', ),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75, key='slide3', ),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10, key='slide4'),
+ sg.Column(column1, background_color='gray34')],
+ [sg.Text('_' * 80)],
+ [sg.Text('Choose A Folder', size=(35, 1))],
+ [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'),
+ sg.InputText('Default Folder', key='folder', do_not_clear=True), sg.FolderBrowse()],
+ [sg.Button('Exit'),
+ sg.Text(' ' * 40), sg.Button('SaveSettings'), sg.Button('LoadSettings')]
+ ]
+
+ window = sg.Window('Form Fill Demonstration', default_element_size=(40, 1), grab_anywhere=False)
+ # button, values = window.LayoutAndRead(layout, non_blocking=True)
+ window.Layout(layout)
+
+ while True:
+ event, values = window.Read()
+
+ if event == 'SaveSettings':
+ filename = sg.PopupGetFile('Save Settings', save_as=True, no_window=True)
+ window.SaveToDisk(filename)
+ # save(values)
+ elif event == 'LoadSettings':
+ filename = sg.PopupGetFile('Load Settings', no_window=True)
+ window.LoadFromDisk(filename)
+ # load(form)
+ elif event in ('Exit', None):
+ break
+
+ # window.CloseNonBlocking()
+
+
+if __name__ == '__main__':
+ Everything()
diff --git a/DemoPrograms old/Demo_Floating_Toolbar.py b/DemoPrograms old/Demo_Floating_Toolbar.py
new file mode 100644
index 00000000..14312f6a
--- /dev/null
+++ b/DemoPrograms old/Demo_Floating_Toolbar.py
@@ -0,0 +1,44 @@
+import PySimpleGUI as sg
+import sys
+
+button_names = ('close', 'cookbook', 'cpu', 'github', 'pysimplegui', 'run', 'storage', 'timer')
+
+house64='iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsSAAALEgHS3X78AAAHPklEQVRYhbVXbUxb1xl+zjn30/a9/gBsbBwCBhPAUD4W2pClSZM0TemkdZPaSf0RTfszTZv2o1qzqmqiaL82salSqzZptVVqqmRV1dEssERKxJKxLAWajEYkAcxXyoBg4xgcY8AY23c/+EgwNiTRdqTz557zPOd5n/Oe95wLPGFzOp24fPp0yeTJk4cbjxzJelIe9qTA5uPHt7mHho6HOzsP1RQUWODxnO/o6Pj/C3A6naT5/ffLC9raWqZbW2v8t29GEz7/d3dXVuY56us7W69cmX1EHqaqKn1sAWffe6+ipK/vROjChaq+WNj/r2wWN44FEvAHamtcLhtfW3uuo7NT24xHVVUKPIYDzrw80vzuu1WuixdbQufPV3SJC747VcxUWC1ZvtFoRPX6tMX+wR27PJ6CLbt3d3zV1WWy2+0HZVn2APAkEgmPKIqeeDzeAwDhcFgLh8MaeVQB//j445qSrq4TU2fO1HlF+L07BGN5hVmXnWXG4PA4+q/OTVb1RwSjwSRZGxqaLm3deq7z+vU/B4NBjIyMwOfzQVEU+Hw+AgD19fUCAGwqwJmXR08dO1brampqjly7Zuu26/3j35GNNdutOqvVAV4QEA6H0D8wgr7u6OS29oCgSxCj7eWXvyB7snLjCDwLAiSTSe3YB20/avv3aNPD/NxmAk4dPbq9pLX1w3BHh23IrPMH6lW1vMyks+XmQxBEAIDRlI2iIoATJqw9kaS/sDt4P3b27A90d2yJql83EMIzxGILcYGniVT+jAKcDgc99dZbT7tOnGgO9/dn9RZb/f5nzeo2t1lPIGM6GAUlUbBlDxl4WA1GcAcEW2+27LddGiXz7cPqrd9fROXPDkC2GMAYv8q/sgUZBZw6fLi+5PPPj0d6e7NHnNm+qX1Wtdht0muLAj7rVhB0fR81VgLc/AKXTK/ioIuHe/5LFG6NgeMmbTdn4r6szrvM195vIAkN24+8AkYfLNfe3h5bEp4aud3Omo8e3eVubPzrgtdb4PU4fYHvbVFLn3LobblOxKJJdMyWwPXiL/F8XQV6brQjWv8r1D9VBvdsJ7Jy9JBlCXorMYyJmsBGZjA74ENo0IeEq7T5Srf3FrBBHWh5++09ZZ9+eiI2MpL/baHdH/yhS813Z+lzrHmQJD1mQrNIjvXBEf4G/NAFZEXvYCfrRtn9v0MI3oZozYUo6cDxFIZsEWOLiLDAQnR+2Cd7bPkm8759Z77u6oqtqwNOu51refPNvaWNjWcWx8edAzUu3/QrJWphuV2fk+OEJCsglGFuZhYtoTJ0lh2BuXwvvvrPLD6SfwHOtReFiUEYFApKOciyAlEUoOZJwj2zMq0N309GbvWU1VosTxcfOPB1y+XLgXA4rK0K+Nsbbzxfefr0B/GJCceoy+EPveZRHEUWgyXLAUlWQAkDIQxzMzO4Iz+Dssrt2FkkYnzgNsxFz+ClIh7ucBsgLM2jlFtyggKKhTP4CD+FiYg26x1wlypKhfm555qv3bgRZc7cXP7c668frHznnb/EJybsQ3Vuf/hQteIssRnMFgcknRGEstWemI0gSXR4oWARXHQEJVNXUesQ4Ex8C8PkNSQU0+pcSjmIsgJe4GByykooxzgd9wYQ6ekrrTEa64v377/OXqiutv387t0/LHq928bcW3wzP9mu5BRY9EazDZLOuBr5SudFEYViAPpIP5RwP7IMGrIXvJAjXkDgoEnGNfMp5SCIOhCahDFHNAQ5YSoxGsLcwFDRnoaGEDcej09M7NrVNDo+VBR8tcJcVmzT6/QWyDpT2uPJ61RAp0IDoAFIpowTkHX1lTEeJrMTjPlRup/Y2+ZjI4XDscG7VmszAYAd5eXGaHCi7seH6n7TsK9ip6LawPO6tAI+OfklAvem0o4BwEsv7oHH404zoiESnsS9YAD+hfzjv/vtJ38cDoZ6OQDo6Om5D6D1NY3+lOMFUMaDPlS1Hm6Dff2IT42D0vVjszEgUFedEct4AYwTUOyqvnm1b+AGkFIJCWVLi9Olnq7xjEAQCWiaayyhLXOkxWqgjANlHAh5AF4jgFIGxjhQxoNkiIJjFJLIAWStAgJgUUsuJV8GLGU82EYCVqhWsjddY5RCFrjU9UEIEI1vhNWWEjQ1oHSLEMqBMCG9AEZhkLl1W0AAROPxzFhNA8j6xMkgYGMHjBIPgaWQEWBuESCEpsdq2hrrNxGQ2QGOMQgcA5ey/j99KtR44H/hwOY5oOpEiPxash1kAdMzfEYHNE0D8KhbwLiNTwFPwLO1L+98I0FykS47sB5LNDziFhAsO5DpKFHIAoOQ8pIgBJB4BkJpWqz2OElIM0QBLOWAQeIgpiAJAFlkICSTA4+RhNjAAUYpZJGDlLIFhBBIPIOWoRI+hgNk+T7P8F4lFJIkQxHXk0nCIuYJTYsl0ECWk5DQB8/zTf8LUluScAguUG0mvv73bz6exuOHJKwUwg8/+lNk5et/AVSZbsni/k4yAAAAAElFTkSuQmCC'
+
+cpu64='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'
+
+timer64='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'
+
+close64 = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEQ0lEQVR42r2XW2wbRRSG/1177TgkdkyoS4shaaWogVIKRAXUVn4BgRBEIRBSkSK1lAakPhTxABJSK6BEtAoXCUHEWwWi4oEXUAVvRUASSBuJliAh5QJp6hrspoGQi69r73LO7Npu6kvsBGek0ezOrvf79szsmbG0D2iwAN8DaMQaFA0YHQFaLwCX6TQuHQAuNtjR2PawD05LZeFzKeC7b/txPoLxU8Aj1BVkAf1wqw/uejeU9RsASaqYQGp+Dv8EAvjgdD9OAg9S14gQOPKED1XNWyv7+lT0VArxiVH0fCUEOqjr3JoKcImN/pYW2EOnQyUJTESBJkdpgGkV8Cj/owDDdx59A8Mf92FT+GpR+KSlBrt6ehE6+hL0pLp6AYbvfusE5FontFgUZ989UVAiDU+X0OsvQ0/EVy4g4MeOQ3a6Mn38wKHet3MkrofzZJMsFlzpeRVaeLF8ASPsb8Javy7nDXRVxdA7x7FpIZQXnrlP0yDJMoKvHVpZBKq23Qv3M8/nzQt6PIah93qhRxaLwvPNhbLmgGP7Drg694mHlVqKwcsWEBItD8DVvleM6WrhRQXUwBSsnpthvclDY++BZLdnflS9YxecrZ2QFGVZePDIYcq5yWuGK47k39NIzlCdDkHxNuYXiJzrz/xIrr4BFpdbfAFyTS1CSi1uf7IDrqeeheyoLihxubsD2sI8UuEFaItUKfen5mahRcLZl7nft7xAvjIQs+GFP2cLCmjRCL5p3oDN6nzR56xIYDl4ORJlCwyqDnT7Z5aFL5G4w4vN8dnVCwymatA9daVkeCkSJQv8qDtxcDKYF86AwKEuSDYbvB+doq/DlnMPJ6uvmzfmSJQk0E9D+OLVcEG4f38bwgNnxLmz9Wl4+z6HZLXm3JuYHMfE7i0ri8Ck3Y3Hx4L0lvYl8Et7H0Xk7NJ7Xe1d8H74GX2/2YyZmv8XY3euo4SUXJkAFyvtEbdc+CsDn2r3Ifrrz3nHvW7Pftzy/kmxdhSCly2Qlmj66Xf88dB2qP6LRme+jauuo67rIDyvHMN4i1esmvlK6QIUTrEISbKxDnDlPkk2BK6VIDhXXaddP6Vk0H6A9wSUn0WKFn2lCgiYbDEmFVXJYjWOuU1LcHudgAASSLS0FnD4dV4TksYxNEOqsMDwgAAxELToSFZFfGaiVWzGNV6MWM4Uyc5OE8wQCr2AqwmxIuoJowX3k5CjZSd6vvxhqcBj921Fc2g8C2Mwzf5sax7zNZZjSdkcCg6/EEgacAYzlLZvRk1kW7rm39iELwZHsgLPATN311rqb7trG+65dT2FXTEg4o1NoDinZKOYQ8ICFo4ADwMJpEwBDrnKIU+YMqZQ0pAbC4QwODwCf0Rd/BQ4IATagM46oI+CeiNPPVS40EDF6M/pJ78Ap+n0PL8Cp7sGs9asgQSFDLxBmKJ6STKBVSbcZsa10gKcJHi/Hv0PWqbBbaFH/AEAAAAASUVORK5CYII='
+
+
+def main():
+
+ toolbar_buttons = [[sg.Button('', image_data=close64,button_color=('white', 'black'), pad=(0,0), key='-CLOSE-'),
+ sg.Button('', image_data=timer64, button_color=('white', 'black'), pad=(0, 0), key='-TIMER-'),
+ sg.Button('', image_data=house64, button_color=('white', 'black'), pad=(0, 0), key='-HOUSE-'),
+ sg.Button('', image_data=cpu64, button_color=('white', 'black'), pad=(0,0), key='-CPU-'),]]
+
+ # layout = toolbar_buttons
+ layout = [[sg.Column( toolbar_buttons, background_color='black')]]
+
+ window = sg.Window('Toolbar', layout, no_titlebar=True, grab_anywhere=True, background_color='black', margins=(0,0))
+
+ # ---===--- Loop taking in user input --- #
+ while True:
+ button, value = window.read()
+ print(button)
+ if button == '-CLOSE-' or button is None:
+ break # exit button clicked
+ elif button == '-TIMER-':
+ print('Timer Button') # add your call to launch a timer program
+ elif button == '-CPU-':
+ print('CPU Button') # add your call to launch a CPU measuring utility
+ elif button == '-HOUSE-':
+ print('Home Button')
+
+ window.close()
+
+if __name__ == '__main__':
+ main()
+
diff --git a/DemoPrograms old/Demo_Font_Previewer.py b/DemoPrograms old/Demo_Font_Previewer.py
new file mode 100644
index 00000000..1c7eeb63
--- /dev/null
+++ b/DemoPrograms old/Demo_Font_Previewer.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+from tkinter import font
+import tkinter
+root = tkinter.Tk()
+fonts = list(font.families())
+fonts.sort()
+root.destroy()
+
+sg.ChangeLookAndFeel('Black')
+
+layout = [[ sg.Text('My Text Element',
+ size=(20,1),
+ auto_size_text=False,
+ click_submits=True,
+ relief=sg.RELIEF_GROOVE,
+ font = 'Courier` 25',
+ text_color='#FF0000',
+ background_color='white',
+ justification='center',
+ pad=(5,3),
+ key='_text_',
+ tooltip='This is a text element',
+ ) ],
+ [sg.Listbox(fonts, size=(30,20), change_submits=True, key='_list_')],
+ [sg.Input(key='_in_')],
+ [ sg.Button('Read', bind_return_key=True), sg.Exit()]]
+
+window = sg.Window('My new window',
+ # grab_anywhere=True,
+ # force_toplevel=True,
+ ).Layout(layout)
+
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None or event == 'Exit':
+ break
+ text_elem = window.FindElement('_text_')
+ print(event, values)
+ if values['_in_'] != '':
+ text_elem.Update(font=values['_in_'])
+ else:
+ text_elem.Update(font=(values['_list_'][0], 25))
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Font_Sizer.py b/DemoPrograms old/Demo_Font_Sizer.py
new file mode 100644
index 00000000..7050160e
--- /dev/null
+++ b/DemoPrograms old/Demo_Font_Sizer.py
@@ -0,0 +1,30 @@
+
+# Testing async form, see if can have a slider
+# that adjusts the size of text displayed
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+fontSize = 12
+layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'),
+ sg.Slider(range=(6,172), orientation='h', size=(10,20), change_submits=True, key='slider', font=('Helvetica 20')), sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]]
+sz = fontSize
+window = sg.Window("Font size selector", grab_anywhere=False)
+window.Layout(layout)
+while True:
+ event, values= window.Read()
+ if event is None or event == 'Quit':
+ break
+ sz_spin = int(values['spin'])
+ sz_slider = int(values['slider'])
+ sz = sz_spin if sz_spin != fontSize else sz_slider
+ if sz != fontSize:
+ fontSize = sz
+ font = "Helvetica " + str(fontSize)
+ window.FindElement('text').Update(font=font)
+ window.FindElement('slider').Update(sz)
+ window.FindElement('spin').Update(sz)
+
+print("Done.")
diff --git a/DemoPrograms old/Demo_Font_String.py b/DemoPrograms old/Demo_Font_String.py
new file mode 100644
index 00000000..b97c0f67
--- /dev/null
+++ b/DemoPrograms old/Demo_Font_String.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[sg.Text('This is my sample text',size=(20,1), key='_text_') ],
+ [sg.CB('Bold', key='_bold_', change_submits=True),
+ sg.CB('Italics', key='_italics_', change_submits=True),
+ sg.CB('Underline', key='_underline_', change_submits=True)],
+ [sg.Slider((6,50), default_value=12, size=(14,20), orientation='h', key='_slider_', change_submits=True),
+ sg.Text('Font size')],
+ [sg.Text('Font string = '), sg.Text('', size=(25,1), key='_fontstring_')],
+ [ sg.Button('Exit')]]
+
+window = sg.Window('Font string builder').Layout(layout)
+
+text_elem = window.FindElement('_text_')
+while True: # Event Loop
+ event, values = window.Read()
+ if event in (None, 'Exit'):
+ break
+ font_string = 'Helvitica '
+ font_string += str(values['_slider_'])
+ if values['_bold_']:
+ font_string += ' bold'
+ if values['_italics_']:
+ font_string += ' italic'
+ if values['_underline_']:
+ font_string += ' underline'
+ text_elem.Update(font=font_string)
+ window.FindElement('_fontstring_').Update('"'+font_string+'"')
+ print(event, values)
diff --git a/DemoPrograms old/Demo_GoodColors.py b/DemoPrograms old/Demo_GoodColors.py
new file mode 100644
index 00000000..8ea1ba9c
--- /dev/null
+++ b/DemoPrograms old/Demo_GoodColors.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+def main():
+ # ------- Make a new Window ------- #
+ window = sg.Window('GoodColors', auto_size_text=True, default_element_size=(30,2))
+ window.AddRow(sg.Text('Having trouble picking good colors? Try one of the colors defined by PySimpleGUI'))
+ window.AddRow(sg.Text('Here come the good colors as defined by PySimpleGUI'))
+
+ #===== Show some nice BLUE colors with yellow text ===== ===== ===== ===== ===== ===== =====#
+ text_color = sg.YELLOWS[0]
+ buttons = (sg.Button('BLUES[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.BLUES))
+ window.AddRow(sg.T('Button Colors Using PySimpleGUI.BLUES'))
+ window.AddRow(*buttons)
+ window.AddRow(sg.Text('_' * 100, size=(65, 1)))
+
+ #===== Show some nice PURPLE colors with yellow text ===== ===== ===== ===== ===== ===== =====#
+ buttons = (sg.Button('PURPLES[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.PURPLES))
+ window.AddRow(sg.T('Button Colors Using PySimpleGUI.PURPLES'))
+ window.AddRow(*buttons)
+ window.AddRow(sg.Text('_' * 100, size=(65, 1)))
+
+ #===== Show some nice GREEN colors with yellow text ===== ===== ===== ===== ===== ===== =====#
+ buttons = (sg.Button('GREENS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.GREENS))
+ window.AddRow(sg.T('Button Colors Using PySimpleGUI.GREENS'))
+ window.AddRow(*buttons)
+ window.AddRow(sg.Text('_' * 100, size=(65, 1)))
+
+ #===== Show some nice TAN colors with yellow text ===== ===== ===== ===== ===== ===== =====#
+ text_color = sg.GREENS[0] # let's use GREEN text on the tan
+ buttons = (sg.Button('TANS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.TANS))
+ window.AddRow(sg.T('Button Colors Using PySimpleGUI.TANS'))
+ window.AddRow(*buttons)
+ window.AddRow(sg.Text('_' * 100, size=(65, 1)))
+
+ #===== Show some nice YELLOWS colors with black text ===== ===== ===== ===== ===== ===== =====#
+ text_color = 'black' # let's use black text on the tan
+ buttons = (sg.Button('YELLOWS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.YELLOWS))
+ window.AddRow(sg.T('Button Colors Using PySimpleGUI.YELLOWS'))
+ window.AddRow(*buttons)
+ window.AddRow(sg.Text('_' * 100, size=(65, 1)))
+
+
+ #===== Add a click me button for fun and SHOW the window ===== ===== ===== ===== ===== ===== =====#
+ window.AddRow(sg.Button('Click ME!'))
+ event, values = window.Read() # show it!
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Google_TTS.py b/DemoPrograms old/Demo_Google_TTS.py
new file mode 100644
index 00000000..ee8557bb
--- /dev/null
+++ b/DemoPrograms old/Demo_Google_TTS.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+from gtts import gTTS
+from pygame import mixer
+import time
+import os
+
+'''
+ Simple demonstration of using Google Text to Speech
+ Get a multi-line string
+ Convert to speech
+ Play back the speech
+
+ Note that there are 2 temp files created. The program tries to delete them but will fail on one of them
+'''
+
+layout = [[sg.Text('What would you like me to say?')],
+ [sg.Multiline(size=(60,10), enter_submits=True)],
+ [sg.Button('Speak', bind_return_key=True), sg.Exit()]]
+
+window = sg.Window('Google Text to Speech').Layout(layout)
+
+i = 0
+mixer.init()
+while True:
+ event, values = window.Read()
+ if event is None or event == 'Exit':
+ break
+ # Get the text and convert to mp3 file
+ tts = gTTS(text=values[0], lang='en',slow=False)
+ tts.save('speech{}.mp3'.format(i%2))
+ # playback the speech
+ mixer.music.load('speech{}.mp3'.format(i%2))
+ mixer.music.play()
+ # wait for playback to end
+ while mixer.music.get_busy():
+ time.sleep(.1)
+ mixer.stop()
+ i += 1
+
+# try to remove the temp files. You'll likely be left with 1 to clean up
+try:
+ os.remove('speech0.mp3')
+except:
+ pass
+try:
+ os.remove('speech1.mp3')
+except:
+ pass
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Graph_Ball_Game.py b/DemoPrograms old/Demo_Graph_Ball_Game.py
new file mode 100644
index 00000000..e1c3c642
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Ball_Game.py
@@ -0,0 +1,97 @@
+# import PySimpleGUIWeb as sg
+import PySimpleGUI as sg
+import pymunk
+import random
+import socket
+
+"""
+ Demo that shows integrating PySimpleGUI with the pymunk library. This combination
+ of PySimpleGUI and pymunk could be used to build games.
+ Note this exact same demo runs with PySimpleGUIWeb by changing the import statement
+"""
+
+class Ball():
+ def __init__(self, x, y, r, graph_elem, *args, **kwargs):
+ mass = 10
+ self.body = pymunk.Body(mass,
+ pymunk.moment_for_circle(mass, 0, r, (0, 0))) # Create a Body with mass and moment
+ self.body.position = x, y
+ self.shape = pymunk.Circle(self.body, r, offset=(0, 0)) # Create a box shape and attach to body
+ self.shape.elasticity = 0.99999
+ self.shape.friction = 0.8
+ self.gui_circle_figure = None
+ self.graph_elem = graph_elem
+
+ def move(self):
+ self.graph_elem.RelocateFigure(self.gui_circle_figure, self.body.position[0], ball.body.position[1])
+
+
+
+class Playfield():
+ def __init__(self, graph_elem):
+ self.space = pymunk.Space()
+ self.space.gravity = 0, 200
+ self.add_wall((0, 400), (600, 400)) # ground
+ self.add_wall((0, 0), (0, 600)) # Left side
+ self.add_wall((600, 0), (600, 400)) # right side
+ self.arena_balls = [] # type: [] Ball
+ self.graph_elem = graph_elem # type: sg.Graph
+
+
+ def add_wall(self, pt_from, pt_to):
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ ground_shape = pymunk.Segment(body, pt_from, pt_to, 0.0)
+ ground_shape.friction = 0.8
+ ground_shape.elasticity = .99
+ self.space.add(ground_shape)
+
+ def add_random_balls(self):
+ for i in range(1, 200):
+ x = random.randint(0, 600)
+ y = random.randint(0, 400)
+ r = random.randint(1, 10)
+ self.add_ball(x,y,r)
+
+ def add_ball(self, x, y, r, fill_color='black', line_color='red'):
+ ball = Ball(x, y, r, self.graph_elem)
+ self.arena_balls.append(ball)
+ area.space.add(ball.body, ball.shape)
+ ball.gui_circle_figure = self.graph_elem.DrawCircle((x, y), r, fill_color=fill_color, line_color=line_color)
+ return ball
+
+ def shoot_a_ball(self, x, y, r, vector=(-10, 0), fill_color='black', line_color='red'):
+ ball = self.add_ball(x,y,r, fill_color=fill_color, line_color=line_color )
+ # ball.shape.surface_velocity=10
+ ball.body.apply_impulse_at_local_point(100*pymunk.Vec2d(vector))
+
+# ------------------- Build and show the GUI Window -------------------
+graph_elem = sg.Graph((600, 400), (0, 400), (600, 0), enable_events=True, key='_GRAPH_', background_color='lightblue')
+
+layout = [[sg.Text('Ball Test'), sg.T('My IP {}'.format(socket.gethostbyname(socket.gethostname())))],
+ [graph_elem],
+ [sg.B('Kick'), sg.B('Player 1 Shoot', size=(15,2)),sg.B('Player 2 Shoot', size=(15,2)), sg.Button('Exit')]]
+
+window = sg.Window('Window Title', layout, disable_close=True)
+
+area = Playfield(graph_elem)
+# area.add_random_balls()
+
+# ------------------- GUI Event Loop -------------------
+while True: # Event Loop
+ event, values = window.Read(timeout=10)
+ # print(event, values)
+ if event in (None, 'Exit'):
+ break
+ area.space.step(0.01)
+
+ if event == 'Player 2 Shoot':
+ area.shoot_a_ball(555, 200, 5, (-10,0), fill_color='green', line_color='green')
+ elif event == 'Player 1 Shoot':
+ area.shoot_a_ball(10, 200, 5, (10,0))
+
+ for ball in area.arena_balls:
+ if event == 'Kick':
+ ball.body.position = ball.body.position[0], ball.body.position[1]-random.randint(1,200)
+ ball.move()
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Graph_Drag_Rectangle.py b/DemoPrograms old/Demo_Graph_Drag_Rectangle.py
new file mode 100644
index 00000000..b23a795f
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Drag_Rectangle.py
@@ -0,0 +1,53 @@
+import PySimpleGUI as sg
+
+"""
+ Demo - Drag a rectangle to draw it
+
+ This demo shows how to use a Graph Element to (optionally) display an image and then use the
+ mouse to "drag a rectangle". This is sometimes called a rubber band and is an operation you
+ see in things like editors
+"""
+
+
+# image_file = r'Color-names.png'
+image_file = None # image is optional
+
+layout = [[sg.Graph(
+ canvas_size=(400, 400),
+ graph_bottom_left=(0, 400),
+ graph_top_right=(400, 0),
+ key="-GRAPH-",
+ change_submits=True, # mouse click events
+ drag_submits=True),],
+ [sg.Text("", key="info", size=(60, 1))]]
+
+window = sg.Window("draw rect on image", layout, finalize=True)
+# get the graph element for ease of use later
+graph = window["-GRAPH-"] # type: sg.Graph
+
+graph.DrawImage(image_file, location=(0,0)) if image_file else None
+dragging = False
+start_point = end_point = prior_rect = None
+
+while True:
+ event, values = window.Read()
+ if event is None:
+ break # exit
+ if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse
+ x, y = values["-GRAPH-"]
+ if not dragging:
+ start_point = (x, y)
+ dragging = True
+ else:
+ end_point = (x, y)
+ if prior_rect:
+ graph.DeleteFigure(prior_rect)
+ if None not in (start_point, end_point):
+ prior_rect = graph.DrawRectangle(start_point, end_point, line_color='red')
+ elif event.endswith('+UP'): # The drawing has ended because mouse up
+ info = window.Element("info")
+ info.Update(value=f"grabbed rectangle from {start_point} to {end_point}")
+ start_point, end_point = None, None # enable grabbing a new rect
+ dragging = False
+ else:
+ print("unhandled event", event, values)
diff --git a/DemoPrograms old/Demo_Graph_Drawing.py b/DemoPrograms old/Demo_Graph_Drawing.py
new file mode 100644
index 00000000..8c11ced1
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Drawing.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0,0), graph_top_right=(400, 400), background_color='red', key='graph')],
+ [sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue'), sg.Button('Move')]]
+
+window = sg.Window('Graph test').Layout(layout).Finalize()
+
+graph = window.FindElement('graph')
+circle =graph .DrawCircle((75,75), 25, fill_color='black',line_color='white')
+point = graph.DrawPoint((75,75), 10, color='green')
+oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' )
+rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' )
+line = graph.DrawLine((0,0), (100,100))
+arc = graph.DrawArc((0,0), (400,400), 160, 10, style='arc' ,arc_color='blue')
+while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ if event in ('Blue', 'Red'):
+ graph.TKCanvas.itemconfig(circle, fill=event)
+ elif event == 'Move':
+ graph.MoveFigure(point, 10,10)
+ graph.MoveFigure(circle, 10,10)
+ graph.MoveFigure(oval, 10,10)
+ graph.MoveFigure(rectangle, 10,10)
+ graph.MoveFigure(arc, 10,10)
diff --git a/DemoPrograms old/Demo_Graph_Element.py b/DemoPrograms old/Demo_Graph_Element.py
new file mode 100644
index 00000000..67f28ac5
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Element.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import ping
+from threading import Thread
+import time
+
+
+STEP_SIZE=1
+SAMPLES = 1000
+CANVAS_SIZE = (1000,500)
+
+# globale used to communicate with thread.. yea yea... it's working fine
+g_exit = False
+g_response_time = None
+def ping_thread(args):
+ global g_exit, g_response_time
+ while not g_exit:
+ g_response_time = ping.quiet_ping('google.com', timeout=1000)
+
+def main():
+ global g_exit, g_response_time
+
+ # start ping measurement thread
+ thread = Thread(target=ping_thread, args=(None,))
+ thread.start()
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0))
+
+ layout = [ [sg.T('Ping times to Google.com', font='Any 12'), sg.Quit(pad=((100,0), 0), button_color=('white', 'black'))],
+ [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,500),background_color='black', key='graph')],]
+
+ window = sg.Window('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False).Layout(layout)
+
+ graph = window.FindElement('graph')
+
+ prev_response_time = None
+ i=0
+ prev_x, prev_y = 0, 0
+ while True:
+ event, values = window.Read(timeout=200)
+ if event == 'Quit' or event is None:
+ break
+ if g_response_time is None or prev_response_time == g_response_time:
+ continue
+ new_x, new_y = i, g_response_time[0]
+ prev_response_time = g_response_time
+ if i >= SAMPLES:
+ graph.Move(-STEP_SIZE,0)
+ prev_x = prev_x - STEP_SIZE
+ graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
+ # window.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+ prev_x, prev_y = new_x, new_y
+ i += STEP_SIZE if i < SAMPLES else 0
+
+ # tell thread we're done. wait for thread to exit
+ g_exit = True
+ thread.join()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Graph_Element_Bar_Chart.py b/DemoPrograms old/Demo_Graph_Element_Bar_Chart.py
new file mode 100644
index 00000000..276e4471
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Element_Bar_Chart.py
@@ -0,0 +1,29 @@
+import PySimpleGUI as sg
+import random
+
+BAR_WIDTH = 50
+BAR_SPACING = 75
+EDGE_OFFSET = 3
+GRAPH_SIZE = (500,500)
+DATA_SIZE = (500,500)
+
+graph = sg.Graph(GRAPH_SIZE, (0,0), DATA_SIZE)
+
+layout = [[sg.Text('Bar graphs using PySimpleGUI')],
+ [graph],
+ [sg.Button('OK')]]
+
+window = sg.Window('Window Title', layout)
+
+while True:
+ event, values = window.Read()
+ graph.Erase()
+ if event is None:
+ break
+
+ for i in range(7):
+ graph_value = random.randint(0, 400)
+ graph.DrawRectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
+ bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='blue')
+ graph.DrawText(text=graph_value, location=(i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))
+window.Close()
diff --git a/DemoPrograms old/Demo_Graph_Element_Sine_Wave.py b/DemoPrograms old/Demo_Graph_Element_Sine_Wave.py
new file mode 100644
index 00000000..dc2b7305
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Element_Sine_Wave.py
@@ -0,0 +1,51 @@
+# import PySimpleGUIWeb as sg
+# import PySimpleGUIQt as sg
+import PySimpleGUI as sg
+import math
+
+SIZE_X = 200
+SIZE_Y = 100
+NUMBER_MARKER_FREQUENCY = 25
+
+def draw_axis():
+ graph.draw_line((-SIZE_X,0), (SIZE_X, 0)) # axis lines
+ graph.draw_line((0,-SIZE_Y), (0,SIZE_Y))
+
+ for x in range(-SIZE_X, SIZE_X+1, NUMBER_MARKER_FREQUENCY):
+ graph.draw_line((x,-3), (x,3)) # tick marks
+ if x != 0:
+ graph.draw_text( str(x), (x,-10), color='green', font='Algerian 15') # numeric labels
+
+ for y in range(-SIZE_Y, SIZE_Y+1, NUMBER_MARKER_FREQUENCY):
+ graph.draw_line((-3,y), (3,y))
+ if y != 0:
+ graph.draw_text( str(y), (-10,y), color='blue')
+
+# Create the graph that will be put into the window
+graph = sg.Graph(canvas_size=(400, 400),
+ graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
+ graph_top_right=(SIZE_X+5, SIZE_Y+5),
+ background_color='white',
+ key='graph')
+# Window layout
+layout = [[sg.Text('Example of Using Math with a Graph', justification='center', size=(50,1), relief=sg.RELIEF_SUNKEN)],
+ [graph],
+ [sg.Text('y = sin(x / x2 * x1)', font='Algerian 18')],
+ [sg.Text('x1'),sg.Slider((0,200), orientation='h', enable_events=True,key='_SLIDER_')],
+ [sg.Text('x2'),sg.Slider((1,200), orientation='h', enable_events=True,key='_SLIDER2_')]]
+
+window = sg.Window('Graph of Sine Function', layout)
+
+while True:
+ event, values = window.read()
+ if event is None:
+ break
+ graph.erase()
+ draw_axis()
+ prev_x = prev_y = None
+ for x in range(-SIZE_X,SIZE_X):
+ y = math.sin(x/int(values['_SLIDER2_']))*int(values['_SLIDER_'])
+ if prev_x is not None:
+ graph.draw_line((prev_x, prev_y), (x,y), color='red')
+ prev_x, prev_y = x, y
+
diff --git a/DemoPrograms old/Demo_Graph_Noise.py b/DemoPrograms old/Demo_Graph_Noise.py
new file mode 100644
index 00000000..c1893514
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_Noise.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import random
+import sys
+
+STEP_SIZE=1
+SAMPLES = 300
+SAMPLE_MAX = 300
+CANVAS_SIZE = (300,300)
+
+
+def main():
+ global g_exit, g_response_time
+
+ layout = [[sg.T('Enter width, height of graph')],
+ [sg.In(size=(6, 1)), sg.In(size=(6, 1))],
+ [sg.Ok(), sg.Cancel()]]
+
+ window = sg.Window('Enter graph size').Layout(layout)
+ b,v = window.Read()
+ if b is None or b == 'Cancel':
+ sys.exit(69)
+ w, h = int(v[0]), int(v[1])
+ CANVAS_SIZE = (w,h)
+
+ # start ping measurement thread
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0))
+
+ layout = [ [sg.Button('Quit', button_color=('white','black'))],
+ [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,SAMPLE_MAX),background_color='black', key='graph')],]
+
+ window = sg.Window('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False).Layout(layout).Finalize()
+ graph = window.FindElement('graph')
+
+ prev_response_time = None
+ i=0
+ prev_x, prev_y = 0, 0
+ graph_value = 250
+ while True:
+ # time.sleep(.2)
+ event, values = window.Read(timeout=0)
+ if event == 'Quit' or event is None:
+ break
+ graph_offset = random.randint(-10, 10)
+ graph_value = graph_value + graph_offset
+ if graph_value > SAMPLE_MAX:
+ graph_value = SAMPLE_MAX
+ if graph_value < 0:
+ graph_value = 0
+ new_x, new_y = i, graph_value
+ prev_value = graph_value
+ if i >= SAMPLES:
+ graph.Move(-STEP_SIZE,0)
+ prev_x = prev_x - STEP_SIZE
+ graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
+ # window.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+ prev_x, prev_y = new_x, new_y
+ i += STEP_SIZE if i < SAMPLES else 0
+
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Graph__Element.py b/DemoPrograms old/Demo_Graph__Element.py
new file mode 100644
index 00000000..174ce1fb
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph__Element.py
@@ -0,0 +1,66 @@
+import ping
+from threading import Thread
+import time
+import PySimpleGUI as sg
+
+
+STEP_SIZE=1
+SAMPLES = 6000
+CANVAS_SIZE = (6000,500)
+
+# globale used to communicate with thread.. yea yea... it's working fine
+g_exit = False
+g_response_time = None
+def ping_thread(args):
+ global g_exit, g_response_time
+ while not g_exit:
+ g_response_time = ping.quiet_ping('google.com', timeout=1000)
+
+def main():
+ global g_exit, g_response_time
+
+ # start ping measurement thread
+ thread = Thread(target=ping_thread, args=(None,))
+ thread.start()
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(element_padding=(0,0))
+
+ layout = [ [sg.T('Ping times to Google.com', font='Any 12'), sg.Quit(pad=((100,0), 0), button_color=('white', 'black'))],
+ [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,500),background_color='black', key='graph')],]
+
+ form = sg.FlexForm('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False)
+ form.Layout(layout)
+
+ form.Finalize()
+ graph = form.FindElement('graph')
+
+ prev_response_time = None
+ i=0
+ prev_x, prev_y = 0, 0
+ while True:
+ time.sleep(.2)
+
+ button, values = form.ReadNonBlocking()
+ if button == 'Quit' or values is None:
+ break
+ if g_response_time is None or prev_response_time == g_response_time:
+ continue
+ new_x, new_y = i, g_response_time[0]
+ prev_response_time = g_response_time
+ if i >= SAMPLES:
+ graph.Move(-STEP_SIZE,0)
+ prev_x = prev_x - STEP_SIZE
+ graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
+ # form.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+ prev_x, prev_y = new_x, new_y
+ i += STEP_SIZE if i < SAMPLES else 0
+
+ # tell thread we're done. wait for thread to exit
+ g_exit = True
+ thread.join()
+
+
+if __name__ == '__main__':
+ main()
+ exit(69)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Graph_pymunk_2D_Graphics.py b/DemoPrograms old/Demo_Graph_pymunk_2D_Graphics.py
new file mode 100644
index 00000000..9dcd8e9c
--- /dev/null
+++ b/DemoPrograms old/Demo_Graph_pymunk_2D_Graphics.py
@@ -0,0 +1,77 @@
+import PySimpleGUIWeb as sg
+# import PySimpleGUI as sg
+import pymunk
+import random
+import socket
+
+"""
+ Demo that shows integrating PySimpleGUI with the pymunk library. This combination
+ of PySimpleGUI and pymunk could be used to build games.
+ Note this exact same demo runs with PySimpleGUIWeb by changing the import statement
+"""
+
+class Ball():
+ def __init__(self, x, y, r, *args, **kwargs):
+ mass = 10
+ self.body = pymunk.Body(mass,
+ pymunk.moment_for_circle(mass, 0, r, (0, 0))) # Create a Body with mass and moment
+ self.body.position = x, y
+ self.shape = pymunk.Circle(self.body, r, offset=(0, 0)) # Create a box shape and attach to body
+ self.shape.elasticity = 0.99999
+ self.shape.friction = 0.8
+ self.gui_circle_figure = None
+
+class Playfield():
+ def __init__(self):
+ self.space = pymunk.Space()
+ self.space.gravity = 0, 200
+ self.add_wall((0, 400), (600, 400)) # ground
+ self.add_wall((0, 0), (0, 600)) # Left side
+ self.add_wall((600, 0), (600, 400)) # right side
+
+ def add_wall(self, pt_from, pt_to):
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ ground_shape = pymunk.Segment(body, pt_from, pt_to, 0.0)
+ ground_shape.friction = 0.8
+ ground_shape.elasticity = .99
+ self.space.add(ground_shape)
+
+ def add_balls(self):
+ self.arena_balls = []
+ for i in range(1, 200):
+ x = random.randint(0, 600)
+ y = random.randint(0, 400)
+ r = random.randint(1, 10)
+ ball = Ball(x, y, r)
+ self.arena_balls.append(ball)
+ area.space.add(ball.body, ball.shape)
+ ball.gui_circle_figure = graph_elem.DrawCircle((x, y), r, fill_color='black', line_color='red')
+
+
+# ------------------- Build and show the GUI Window -------------------
+graph_elem = sg.Graph((600, 400), (0, 400), (600, 0), enable_events=True, key='_GRAPH_', background_color='lightblue')
+
+layout = [[sg.Text('Ball Test'), sg.T('My IP {}'.format(socket.gethostbyname(socket.gethostname())))],
+ [graph_elem],
+ # [sg.Up(), sg.Down()],
+ [sg.B('Kick'), sg.Button('Exit')]]
+
+window = sg.Window('Window Title', layout, ).Finalize()
+
+area = Playfield()
+area.add_balls()
+
+# ------------------- GUI Event Loop -------------------
+while True: # Event Loop
+ event, values = window.Read(timeout=0)
+ # print(event, values)
+ if event in (None, 'Exit'):
+ break
+ area.space.step(0.02)
+
+ for ball in area.arena_balls:
+ if event == 'Kick':
+ ball.body.position = ball.body.position[0], ball.body.position[1]-random.randint(1,200)
+ graph_elem.RelocateFigure(ball.gui_circle_figure, ball.body.position[0], ball.body.position[1])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Hello_World.py b/DemoPrograms old/Demo_Hello_World.py
new file mode 100644
index 00000000..eb7ee429
--- /dev/null
+++ b/DemoPrograms old/Demo_Hello_World.py
@@ -0,0 +1,15 @@
+import PySimpleGUI as sg
+
+"""
+ Oh yes, the classic "Hello World". The problem is that you
+ can do it so many ways using PySimpleGUI
+"""
+
+sg.PopupNoButtons('Hello World') # the single line
+
+sg.Window('Hello world', [[sg.Text('Hello World')]]).Read() # single line using a real window
+
+# This is a "Traditional" PySimpleGUI window code. First make a layout, then a window, the read it
+layout = [[sg.Text('Hello World')]]
+window = sg.Window('Hello world', layout)
+event, values = window.Read()
diff --git a/DemoPrograms old/Demo_HowDoI.py b/DemoPrograms old/Demo_HowDoI.py
new file mode 100644
index 00000000..38d73bc0
--- /dev/null
+++ b/DemoPrograms old/Demo_HowDoI.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUIQt as sg
+else:
+ import PySimpleGUI27 as sg
+import subprocess
+
+
+# Test this command in a dos window if you are having trouble.
+HOW_DO_I_COMMAND = 'python -m howdoi.howdoi'
+
+# if you want an icon on your taskbar for this gui, then change this line of code to point to the ICO file
+DEFAULT_ICON = 'E:\\TheRealMyDocs\\Icons\\QuestionMark.ico'
+
+def HowDoI():
+ '''
+ Make and show a window (PySimpleGUI form) that takes user input and sends to the HowDoI web oracle
+ Excellent example of 2 GUI concepts
+ 1. Output Element that will show text in a scrolled window
+ 2. Non-Window-Closing Buttons - These buttons will cause the form to return with the form's values, but doesn't close the form
+ :return: never returns
+ '''
+ # ------- Make a new Window ------- #
+ sg.ChangeLookAndFeel('GreenTan') # give our form a spiffy set of colors
+
+ layout = [
+ [sg.Text('Ask and your answer will appear here....', size=(40, 1))],
+ [sg.Output(size=(120, 30), font=('Helvetica 10'))],
+ [ sg.Spin(values=(1, 2, 3, 4), initial_value=1, size=(2, 1), key='Num Answers', font='Helvetica 15'),
+ sg.Text('Num Answers',font='Helvetica 15'), sg.Checkbox('Display Full Text', key='full text', font='Helvetica 15'),
+ sg.T('Command History', font='Helvetica 15'), sg.T('', size=(40,3), text_color=sg.BLUES[0], key='history')],
+ [sg.Multiline(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
+ sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]
+ ]
+
+ window = sg.Window('How Do I ??', default_element_size=(30, 2), icon=DEFAULT_ICON, font=('Helvetica',' 13'), default_button_element_size=(8,2), return_keyboard_events=True, no_titlebar=True, grab_anywhere=True)
+ window.Layout(layout)
+ # ---===--- Loop taking in user input and using it to query HowDoI --- #
+ command_history = []
+ history_offset = 0
+ while True:
+ event, values = window.Read()
+ if event == 'SEND':
+ query = values['query'].rstrip()
+ # print(query)
+ QueryHowDoI(query, values['Num Answers'], values['full text']) # send the string to HowDoI
+ command_history.append(query)
+ history_offset = len(command_history)-1
+ window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear
+ window.FindElement('history').Update('\n'.join(command_history[-3:]))
+ elif event == None or event == 'EXIT': # if exit button or closed using X
+ break
+ elif 'Up' in event and len(command_history): # scroll back in history
+ command = command_history[history_offset]
+ history_offset -= 1 * (history_offset > 0) # decrement is not zero
+ window.FindElement('query').Update(command)
+ elif 'Down' in event and len(command_history): # scroll forward in history
+ history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list
+ command = command_history[history_offset]
+ window.FindElement('query').Update(command)
+ elif 'Escape' in event: # clear currently line
+ window.FindElement('query').Update('')
+
+
+def QueryHowDoI(Query, num_answers, full_text):
+ '''
+ Kicks off a subprocess to send the 'Query' to HowDoI
+ Prints the result, which in this program will route to a gooeyGUI window
+ :param Query: text english question to ask the HowDoI web engine
+ :return: nothing
+ '''
+ howdoi_command = HOW_DO_I_COMMAND
+ full_text_option = ' -a' if full_text else ''
+ t = subprocess.Popen(howdoi_command + ' \"'+ Query + '\" -n ' + str(num_answers)+full_text_option, stdout=subprocess.PIPE)
+ (output, err) = t.communicate()
+ print('{:^88}'.format(Query.rstrip()))
+ print('_'*60)
+ print(output.decode("utf-8") )
+ exit_code = t.wait()
+
+if __name__ == '__main__':
+ HowDoI()
+
diff --git a/DemoPrograms/Demo_IP_Address_Entry.py b/DemoPrograms old/Demo_IP_Address_Entry.py
similarity index 100%
rename from DemoPrograms/Demo_IP_Address_Entry.py
rename to DemoPrograms old/Demo_IP_Address_Entry.py
diff --git a/DemoPrograms old/Demo_Img_Viewer.py b/DemoPrograms old/Demo_Img_Viewer.py
new file mode 100644
index 00000000..c4fa408c
--- /dev/null
+++ b/DemoPrograms old/Demo_Img_Viewer.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import os
+from PIL import Image, ImageTk
+import io
+"""
+Simple Image Browser based on PySimpleGUI
+--------------------------------------------
+There are some improvements compared to the PNG browser of the repository:
+1. Paging is cyclic, i.e. automatically wraps around if file index is outside
+2. Supports all file types that are valid PIL images
+3. Limits the maximum form size to the physical screen
+4. When selecting an image from the listbox, subsequent paging uses its index
+5. Paging performance improved significantly because of using PIL
+
+Dependecies
+------------
+Python v3
+PIL
+"""
+# Get the folder containin:g the images from the user
+folder = sg.PopupGetFolder('Image folder to open', default_path='')
+if not folder:
+ sg.PopupCancel('Cancelling')
+ raise SystemExit()
+
+# PIL supported image types
+img_types = (".png", ".jpg", "jpeg", ".tiff", ".bmp")
+
+# get list of files in folder
+flist0 = os.listdir(folder)
+
+# create sub list of image files (no sub folders, no wrong file types)
+fnames = [f for f in flist0 if os.path.isfile(os.path.join(folder,f)) and f.lower().endswith(img_types)]
+
+num_files = len(fnames) # number of iamges found
+if num_files == 0:
+ sg.Popup('No files in folder')
+ raise SystemExit()
+
+del flist0 # no longer needed
+
+#------------------------------------------------------------------------------
+# use PIL to read data of one image
+#------------------------------------------------------------------------------
+def get_img_data(f, maxsize = (1200, 850), first = False):
+ """Generate image data using PIL
+ """
+ img = Image.open(f)
+ img.thumbnail(maxsize)
+ if first: # tkinter is inactive the first time
+ bio = io.BytesIO()
+ img.save(bio, format = "PNG")
+ del img
+ return bio.getvalue()
+ return ImageTk.PhotoImage(img)
+#------------------------------------------------------------------------------
+
+
+# create the form that also returns keyboard events
+window = sg.Window('Image Browser', return_keyboard_events=True,
+ location=(0, 0), use_default_focus=False)
+
+# make these 2 elements outside the layout as we want to "update" them later
+# initialize to the first file in the list
+filename = os.path.join(folder, fnames[0]) # name of first file in list
+image_elem = sg.Image(data = get_img_data(filename, first = True))
+filename_display_elem = sg.Text(filename, size=(80, 3))
+file_num_display_elem = sg.Text('File 1 of {}'.format(num_files), size=(15,1))
+
+# define layout, show and read the form
+col = [[filename_display_elem],
+ [image_elem]]
+
+col_files = [[sg.Listbox(values = fnames, change_submits=True, size=(60, 30), key='listbox')],
+ [sg.Button('Next', size=(8,2)), sg.Button('Prev',
+ size=(8,2)), file_num_display_elem]]
+
+layout = [[sg.Column(col_files), sg.Column(col)]]
+
+window.Layout(layout) # Shows form on screen
+
+# loop reading the user input and displaying image, filename
+i=0
+while True:
+ # read the form
+ event, values = window.Read()
+ print(event, values)
+ # perform button and keyboard operations
+ if event is None:
+ break
+ elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34'):
+ i += 1
+ if i >= num_files:
+ i -= num_files
+ filename = os.path.join(folder, fnames[i])
+ elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33'):
+ i -= 1
+ if i < 0:
+ i = num_files + i
+ filename = os.path.join(folder, fnames[i])
+ elif event == 'listbox': # something from the listbox
+ f = values["listbox"][0] # selected filename
+ filename = os.path.join(folder, f) # read this file
+ i = fnames.index(f) # update running index
+ else:
+ filename = os.path.join(folder, fnames[i])
+
+ # update window with new image
+ image_elem.Update(data=get_img_data(filename))
+ # update window with filename
+ filename_display_elem.Update(filename)
+ # update page display
+ file_num_display_elem.Update('File {} of {}'.format(i+1, num_files))
+
+
diff --git a/DemoPrograms old/Demo_Input_Auto_Complete.py b/DemoPrograms old/Demo_Input_Auto_Complete.py
new file mode 100644
index 00000000..e963dad0
--- /dev/null
+++ b/DemoPrograms old/Demo_Input_Auto_Complete.py
@@ -0,0 +1,90 @@
+import sys
+import re
+QT = True
+if QT:
+ import PySimpleGUIQt as sg
+else:
+ import PySimpleGUI as sg
+
+def autocomplete_popup_show(text_list ):
+ autocomplete_popup_layout = [[sg.Listbox(values=text_list,
+ size=(100,20*len(text_list)) if QT else (15, len(text_list)),
+ change_submits=True,
+ bind_return_key=True,
+ auto_size_text=True,
+ key='_FLOATING_LISTBOX_', enable_events=True)]]
+
+ autocomplete_popup = sg.Window("Borderless Window",
+ default_element_size=(12, 1),
+ auto_size_text=False,
+ auto_size_buttons=False,
+ no_titlebar=True,
+ grab_anywhere=True,
+ return_keyboard_events=True,
+ keep_on_top=True,
+ background_color='black',
+ location=(1320,622),
+ default_button_element_size=(12, 1))
+
+ window = autocomplete_popup.Layout(autocomplete_popup_layout).Finalize()
+ return window
+
+
+def predict_text(input, lista):
+ pattern = re.compile('.*' + input + '.*')
+ return [w for w in lista if re.match(pattern, w)]
+
+choices = ['ABC' + str(i) for i in range(30)] # dummy data
+
+layout = [ [sg.Text('Your typed chars appear here:')],
+ [sg.In(key='_INPUT_', size=(10,1), do_not_clear=True)],
+ [sg.Button('Show'), sg.Button('Exit')],]
+
+window = sg.Window('Window Title', return_keyboard_events=True).Layout(layout)
+
+sel_item = -1
+skip_event = False
+while True: # Event Loop
+ event, values = window.Read(timeout=500)
+ if event is None or event == 'Exit':
+ break
+ if event != sg.TIMEOUT_KEY:
+ # print(f'ev1 {event}')
+ in_val = values['_INPUT_']
+ prediction_list = predict_text(str(in_val), choices)
+ if prediction_list:
+ try:
+ fwindow.Close()
+ except: pass
+ fwindow = autocomplete_popup_show(prediction_list)
+ list_elem = fwindow.Element('_FLOATING_LISTBOX_')
+ if event == '_COMBO_':
+ sg.Popup('Chose', values['_COMBO_'])
+ if event.startswith('Down') or event.startswith('special 16777237'):
+ sel_item = sel_item + (sel_item0)
+ list_elem.Update(set_to_index=sel_item)
+ skip_event = True
+ if event == '\r' or event.startswith('special 16777220'):
+ chosen = vals2['_FLOATING_LISTBOX_']
+ window.Element('_INPUT_').Update(vals2['_FLOATING_LISTBOX_'][0], select=True)
+ fwindow.Close()
+ sel_item = -1
+ if event.startswith('Escape') or event.startswith('special 16777216'):
+ window.Element('_INPUT_').Update('')
+
+ try:
+ ev2, vals2 = fwindow.Read(timeout=10)
+ if ev2 == '_FLOATING_LISTBOX_' and skip_event and QT:
+ skip_event = False
+ elif ev2 != sg.TIMEOUT_KEY and ev2 is not None:
+ # print(f'ev2 {ev2}')
+ fwindow.Close()
+ window.Element('_INPUT_').Update(vals2['_FLOATING_LISTBOX_'][0], select=True)
+ sel_item = -1
+ fwindow = None
+ except: pass
+window.Close()
diff --git a/DemoPrograms old/Demo_Input_Validation.py b/DemoPrograms old/Demo_Input_Validation.py
new file mode 100644
index 00000000..0a963f0b
--- /dev/null
+++ b/DemoPrograms old/Demo_Input_Validation.py
@@ -0,0 +1,25 @@
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+ Simple field validation
+ Input field should only accept digits.
+ If non-digit entered, it is deleted from the field
+"""
+
+layout = [[sg.Text('Enter digits:')],
+ [sg.Input(do_not_clear=True, enable_events=True, key='_INPUT_')],
+ [sg.Button('Ok', key='_OK_'),sg.Button('Exit')]]
+
+window = sg.Window('Window Title').Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event in (None, 'Exit'):
+ break
+ if len(values['_INPUT_']) and values['_INPUT_'][-1] not in ('0123456789'): # if last char entered not a digit
+ window.Element('_INPUT_').Update(values['_INPUT_'][:-1]) # delete last char from input
+window.Close()
diff --git a/DemoPrograms old/Demo_Invisible_Elements.py b/DemoPrograms old/Demo_Invisible_Elements.py
new file mode 100644
index 00000000..df98d4bd
--- /dev/null
+++ b/DemoPrograms old/Demo_Invisible_Elements.py
@@ -0,0 +1,27 @@
+import PySimpleGUI as sg
+
+"""
+ Demonstrates that using a Column Element to make groups of Elements appear and disappear
+ will cause the layout of the elements in the column to remain as they were. If each individual element
+ were made invisible and then visible, then tkinter puts EACH ELEMENT on a separate row when it is made
+ visible again. This means a row of 6 elements will become a column of 6 elements if you make each of them
+ visible one at a time.
+
+"""
+
+layout = [[sg.Column([[sg.Text('My Window')],[sg.Input(key='_IN_'), sg.B('My button', key='_OUT_')]], key='_COL_')],
+ [sg.Button('Invisible'), sg.B('Visible'), sg.Button('Exit')]]
+
+window = sg.Window('Window Title', layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event in (None, 'Exit'):
+ break
+ if event == 'Invisible':
+ window.Elem('_COL_').Update(visible=False)
+ elif event == 'Visible':
+ window.Elem('_COL_').Update(visible=True)
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Keyboard.py b/DemoPrograms old/Demo_Keyboard.py
new file mode 100644
index 00000000..6fd21931
--- /dev/null
+++ b/DemoPrograms old/Demo_Keyboard.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 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"
+
+layout = [[sg.Text("Press a key or scroll mouse")],
+ [sg.Text("", size=(18,1), key='text')],
+ [sg.Button("OK", key='OK')]]
+
+window = sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False).Layout(layout)
+
+# ---===--- Loop taking in user input --- #
+while True:
+ event, values = window.Read()
+ text_elem = window.FindElement('text')
+ if event in ("OK", None):
+ print(event, "exiting")
+ break
+ if len(event) == 1:
+ text_elem.Update(value='%s - %s' % (event, ord(event)))
+ if event is not None:
+ text_elem.Update(event)
+
+
diff --git a/DemoPrograms old/Demo_Keyboard_ENTER_Presses_Button.py b/DemoPrograms old/Demo_Keyboard_ENTER_Presses_Button.py
new file mode 100644
index 00000000..de2c2a6b
--- /dev/null
+++ b/DemoPrograms old/Demo_Keyboard_ENTER_Presses_Button.py
@@ -0,0 +1,46 @@
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+
+"""
+ tkinter and Qt do not "activate" buttons by pressing the ENTER key with the button highlighted / in focus
+ This demo will enable the application to click on a button if the button has focus (is highlighted) and the
+ user presses the ENTER key.
+ NOTE that the SPACE BAR works correctly out of the box with both tkinter and Qt. If a button has focus and
+ you press the space bar, then tkinter and Qt will both consider that a button click. But not so with the ENTER
+ key.
+
+ The solution is for your program to read the keyboard presses and act upon those directly. It's trivial logic
+ in the end:
+ 1. Get a key press
+ 2. See if the key is the ENTER key
+ 3. Find the Element that currently has focus
+ 4. Click the Button if the Element with focus is a button
+
+"""
+
+QT_ENTER_KEY1 = 'special 16777220'
+QT_ENTER_KEY2 = 'special 16777221'
+
+layout = [ [sg.T('Test of Enter Key use')],
+ [sg.In(key='_IN_')],
+ [sg.Button('Button 1', key='_1_')],
+ [sg.Button('Button 2', key='_2_')],
+ [sg.Button('Button 3', key='_3_')], ]
+
+window = sg.Window('My new window', layout,
+ return_keyboard_events=True)
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None:
+ break
+ if event in ('\r', QT_ENTER_KEY1, QT_ENTER_KEY2): # Check for ENTER key
+ elem = window.FindElementWithFocus() # go find element with Focus
+ if elem is not None and elem.Type == sg.ELEM_TYPE_BUTTON: # if it's a button element, click it
+ elem.Click()
+ # check for buttons that have been clicked
+ elif event == '_1_':
+ print('Button 1 clicked')
+ elif event == '_2_':
+ print('Button 2 clicked')
+ elif event == '_3_':
+ print('Button 3 clicked')
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Keyboard_Realtime.py b/DemoPrograms old/Demo_Keyboard_Realtime.py
new file mode 100644
index 00000000..cc50054e
--- /dev/null
+++ b/DemoPrograms old/Demo_Keyboard_Realtime.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[sg.Text("Hold down a key")],
+ [sg.Button("OK")]]
+
+window = sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False).Layout(layout)
+
+while True:
+ event, values = window.Read(timeout=0)
+
+ if event == "OK":
+ print(event, values, "exiting")
+ break
+ if event is not sg.TIMEOUT_KEY:
+ if len(event) == 1:
+ print('%s - %s' % (event, ord(event)))
+ else:
+ print(event)
+ elif event is None:
+ break
diff --git a/DemoPrograms old/Demo_Keypad.py b/DemoPrograms old/Demo_Keypad.py
new file mode 100644
index 00000000..31194fbc
--- /dev/null
+++ b/DemoPrograms old/Demo_Keypad.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+# Demonstrates a number of PySimpleGUI features including:
+# Default element size
+# auto_size_buttons
+# Button
+# Dictionary return values
+# Update of elements in form (Text, Input)
+# do_not_clear of Input elements
+
+
+
+layout = [[sg.Text('Enter Your Passcode')],
+ [sg.Input(size=(10, 1), do_not_clear=True, key='input')],
+ [sg.Button('1'), sg.Button('2'), sg.Button('3')],
+ [sg.Button('4'), sg.Button('5'), sg.Button('6')],
+ [sg.Button('7'), sg.Button('8'), sg.Button('9')],
+ [sg.Button('Submit'), sg.Button('0'), sg.Button('Clear')],
+ [sg.Text('', size=(15, 1), font=('Helvetica', 18), text_color='red', key='out')],
+ ]
+
+window = sg.Window('Keypad', default_button_element_size=(5, 2), auto_size_buttons=False, grab_anywhere=False).Layout(layout)
+
+# Loop forever reading the form's values, updating the Input field
+keys_entered = ''
+while True:
+ event, values = window.Read() # read the form
+ if event is None: # if the X button clicked, just exit
+ break
+ if event == 'Clear': # clear keys if clear button
+ keys_entered = ''
+ elif event in '1234567890':
+ keys_entered = values['input'] # get what's been entered so far
+ keys_entered += event # add the new digit
+ elif event == 'Submit':
+ keys_entered = values['input']
+ window.FindElement('out').Update(keys_entered) # output the final string
+
+ window.FindElement('input').Update(keys_entered) # change the form to reflect current key string
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_LED_Clock_Weather.py b/DemoPrograms old/Demo_LED_Clock_Weather.py
new file mode 100644
index 00000000..cde7115e
--- /dev/null
+++ b/DemoPrograms old/Demo_LED_Clock_Weather.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+ sg.PopupError('This program uses Base64 images which are not supported in Python 2.7')
+ sys.exit()
+import datetime
+import calendar
+import forecastio
+
+##### CHANGE these settings to match your location... check Google Maps #####
+MY_LOCATION_LAT = 35.000000
+MY_LOCATION_LON = -79.000000
+##### You need a free dark-sky key. You get 1000 calls a month for free #####
+DARKSKY_KEY = "INSERT YOUR DARKSKY KEY HERE!" # *** INSERT YOUR DARKSKY KEY HERE **
+
+NUM_COLS = 5 # Changes number of days in forecast
+
+
+class GUI():
+ def __init__(self):
+ self.api_key = DARKSKY_KEY
+ self.lat = MY_LOCATION_LAT
+ self.lng = MY_LOCATION_LON
+ self.blink_count = 0
+
+ sg.SetOptions(border_width=0, text_color='white', background_color='black', text_element_background_color='black')
+
+ # Create clock layout
+ clock = [
+ [sg.T('', pad=((120,0),0)),
+ sg.Image(data=ledblank[22:], key='_hour1_'),
+ sg.Image(data=ledblank[22:], key='_hour2_'),
+ sg.Image(data=ledblank[22:], key='_colon_'),
+ sg.Image(data=ledblank[22:], key='_min1_'),
+ sg.Image(data=ledblank[22:], key='_min2_')], ]
+
+ # Create the weather columns layout
+ weather_cols = []
+ for i in range(NUM_COLS):
+ weather_cols.append(
+ [[sg.T('', size=(4, 1), font='Any 20', justification='center', key='_DAY_' + str(i)), ],
+ [sg.Image(data=w1[22:], background_color='black', key='_icon_'+str(i), pad=((4, 0), 3)), ],
+ [sg.T('--', size=(3, 1), justification='center', font='Any 20', key='_high_' + str(i), pad=((10, 0), 3))],
+ [sg.T('--', size=(3, 1), justification='center', font='Any 20', key='_low_' + str(i), pad=((10, 0), 3))]])
+
+ # Create the overall layout
+ layout = [[sg.Column(clock, background_color='black')],
+ [sg.Column(weather_cols[x], background_color='black') for x in range(NUM_COLS)],
+
+ [sg.Button('Exit', button_color=('black', 'black'),
+ image_data=orangeround[22:], tooltip='close window', pad=((450,0),(10,0)))]]
+
+ # Create the window
+ self.window = sg.Window('My new window',
+ background_color='black',
+ grab_anywhere=True,
+ use_default_focus=False,
+ no_titlebar=True,
+ alpha_channel=.8, # set an alpha channel if want transparent
+ ).Layout(layout).Finalize()
+
+ self.colon_elem = self.window.FindElement('_colon_')
+ self.hour1 = self.window.FindElement('_hour1_')
+ self.hour2 = self.window.FindElement('_hour2_')
+ self.min1 = self.window.FindElement('_min1_')
+ self.min2 = self.window.FindElement('_min2_')
+
+
+ def update_clock(self):
+ # update the clock
+ now = datetime.datetime.now()
+ real_hour = now.hour - 12 if now.hour > 12 else now.hour
+ hour1_digit = led_digits[real_hour // 10]
+ self.hour1.Update(data=hour1_digit[22:])
+ self.hour2.Update(data=led_digits[real_hour % 10][22:])
+ self.min2.Update(data=led_digits[int(now.minute) % 10][22:])
+ self.min1.Update(data=led_digits[int(now.minute) // 10][22:])
+ # Blink the :
+ if self.blink_count % 2:
+ self.colon_elem.Update(data=ledcolon[22:])
+ else:
+ self.colon_elem.Update(data=ledblank[22:])
+ self.blink_count += 1
+
+ def update_weather(self):
+ forecast = forecastio.load_forecast(self.api_key, self.lat, self.lng)
+ daily = forecast.daily()
+ today_weekday = datetime.datetime.today().weekday()
+
+ max_temps = []
+ min_temps = []
+ daily_icons = []
+ for daily_data in daily.data:
+ daily_icons.append(daily_data.d['icon'])
+ max_temps.append(int(daily_data.d['temperatureMax']))
+ min_temps.append(int(daily_data.d['temperatureMin']))
+
+ for i in range(NUM_COLS):
+ day_element = self.window.FindElement('_DAY_' + str(i))
+ max_element = self.window.FindElement('_high_' + str(i))
+ min_element = self.window.FindElement('_low_' + str(i))
+ icon_element = self.window.FindElement('_icon_' + str(i))
+ day_element.Update(calendar.day_abbr[(today_weekday + i) % 7])
+ max_element.Update(max_temps[i])
+ min_element.Update(min_temps[i])
+ icon_element.Update(data=weather_icon_dict[daily_icons[i]][22:])
+
+
+def led_clock():
+
+ # Get the GUI object that is used to update the window
+ gui = GUI()
+
+ # ---------- EVENT LOOP ----------
+ last_update_time = 0
+ while True:
+ # Wake up once a second to update the clock and weather
+ event, values = gui.window.Read(timeout=1000)
+ if event in (None, 'Exit'):
+ break
+ # update clock
+ gui.update_clock()
+ # update weather once ever 6 hours
+ now = datetime.datetime.now()
+ if last_update_time == 0 or (now-last_update_time).seconds >= 60*60*6:
+ print('*** Updating Weather ***')
+ last_update_time = now
+ gui.update_weather()
+
+
+led0 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEb0lEQVRoge2Zz4scRRTHP/WqunsDSQjiBvFHSBC8eRJRFAXFgCIYRVBPgrkoiBgU9U/w4iEgHjwEBT2IKHiIiBfBg4oB8SJ4CgHjIXpSE5Ls9HR9PUz1ZGa6dnZ2dmdnlXzh0TPV1a+/9erVq/eqHSB2GWzZBHK4TmpW7EpSIdfokuwE4jrv12QDk40LhNElNmapluFNZtws8bdEsQCCrb69zvEL0FP3DQLk0nUf6P6y1A8hSKAeKG6jNEmnQG8Xhe5cWZE5p5ZHy3mM1GHQz2Z6YGVF33ovgeqkZDukn65vVpVeDUFfr6wIM9k0UgdBa6DfvNc9VaWvErFeGuW80h8Z3Imi0PGqkkCny1KYyY+Q6qw+A64AtzUNn0k8GwI9M56oayLzxxABzjleCgFvxqleb3DDddd5NiT4dL01Rr6oa54pS9aKgqMSl+YgFoG9wFtm7JE42evRkyjJh54sqRZ9YFXi07rmLjMuJEWbXY0B+Ac4Dpysa2qmD6xDavSFnsEoVyVWm4bzEg35gLcRKYA7wuDXRsE561M28R8GVptFYQ5t/yYTj6aSagPnJKlJDNfsJtD2n3Uw3fdnVsNOo0PKwdKJ/YcstSDM71ObeHhR6FpqN04fsHS/2lGfmhU76lNbcvRF5eez7pkbOnpcr+McaPe03KCV6TdsvApccY4KqIESOGvG7yOb6bzT+6MEZvgYqRlkIRdTojypc5iGtnnysbLUFTMJ9Kv3OhLCIGV1Tm5O8c4JMz1fluon3WdC0KrZWDre2kc5Yk+GoJ+KQoeKQjg3mdjPJQGE9zpeFPq+KHRDCDlC6hSjMJjT6BzmPcSIYqSZc8omUQDRDOccagZaJ3V3/NcYJHT3ec8nzrHfORqzbXP02jke854PzSicoyHvo52pu9d7/ZlKq++81w1FIcw6Zt6MeBBmejQEXUw+ddp7VdN8qm280XtdSISupjrtTAja5/3At+Zw8uCccE5Hi0I9M8UR3aeqSjg3VvcNZ6U14T7nOJCWf8lgvu9uGm4fqXO1SWmfe9iMIkb6XItFtwA4N+bYHVfRxBy3NeCsSf809JOOUf2tk4+2zey/25HSWEZHTuvST/Jy9l86qRyWTuq6pWaFMqt6+aQybUsnlcPSSf3/LLUdxUTOqXN6px4vAsPD17bjVg7NQtpmRvfX4YY9K6kmdTjrPefSg3FiR58FbUV0JkZwDpOm6lj3g5HSzfPe82JR8EFdc8iMNTZvqQjsBz4GXi5L3kunw7C+W4xlnYdDGFYyfzinB8tS36Skr2H614Rp0j77elXpRDrYF+jzVJhkk7xRhgG45BzPlSWvNQ0PNQ29dK+ZU8Qg939nbY3LMfJGOinuzxLRA3DZjKfKkhdi5FjTcJVryd5WUQPv1zXnvOfdEDiwTr+xHP1ICHp6zx59lKasv4UpmyrO6ZWq0uNV1Skcxuq+9s9BMx6Jkb+20UKjiAzy/55zfAmdkJAtRrONC8KGX0ZHsQgL5ZCrvHfSKDNjV27I/wIpihClbBNQxAAAAABJRU5ErkJggg=='
+
+led1 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAACD0lEQVRoge2Yv27UQBCHv9kkRCAhXgAkSh6AmoKCFFBENLwBFQ0PQIPES0BJky4NDR0lJT28A4ICQc7nH4V3FccZ+9zcrAv/pJVP3jntp525+XMGiIUp1QbwtELN1Qo1V1WhLK+hqkEVIC8fVYFKdDB3gLvW3ZUN9qsAmRlnKfEoQ6WBTShQCxybcW7Gk7blt2N3GAVkGegmcA6ctC0AB45tyE2VgL5txifgROLfhH0YFMBH4DGwYdpFIVBtft5XlwA8l/UVGujtbhMgGMrL3p7W2rcrlor2DtV32dHM7+wdSiOfp7TG1FytUHO1Qnllpko7bL3n3N4pJE+VFvhLftf09n/Kz16KXO/NJJBAr8wEKF23i4GxvDDTh5T0OqUxII2NXntRiavGDJM4pOtChwqfZhrgDfDCjA3jnUOY+zDT2xxTf0Gnea9KTJVD36UkgS5ALWgDeugEe+jg8DTPegnY0k009/JetbF92wMoEFu73rmHB/oVOUCuXbS2zrvqUF6RrgIlLmPKy9zrTXlaJNQi3bc8KGmBUCwgphYzOPRVeqdd/1OFQJVDvuVa1y8tcgaHEKhy7EvgsxlHXE401dxXysqFxHO6UevGhH1YTCkf9kfiFPiaXdk4tuG/vgT8Ap4B31PilmMXOmIVHdAF+wMzGuCHdAWkClQ5mJHDq2X0/q0MVe2mplS99nlaJNR/DW4BrSasyZkAAAAASUVORK5CYII='
+
+led2 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADmElEQVRoge2Zz4scRRiGn7d6NlHJQaKoKHgwxGgiIpiTegrI4kEUkdwEQcRF8pdI0INIrorkoqB48SB4EhQUvSoIIkjixaiExdnNTtfrYaqyPZmamZ6e7GQC88JHM71dVU999ev7tgSYFVO41QAlraHaaiWheqWXSrYMxQnt39LVVwLolT64T+Jem22gOgAQM5w3hyV+8bhPisN3GPhU4qTNf5M+WgBIwCFgS+KqxKU4Oohj3ssvHpL4UuLJGKm5OR7LDQl4NQQeBp4A3oiRCqjT38dWX3btJZszwA8SFbCXCnW1AfteerGqOCLxboz00/A1F1ZxS4gMPXMlRjaB7yQ2UsGqgwWGU0DACxKPxMiHdT0GkzVxutSpsn9sNiU+riqetulP6skM9YA303Q4b7MD3DEvFAw9JmDb5pU07l3UA/rAOeC8zd6MhmcurDwPSJV1UZ7gpwrzpxNUrvQ6XAflcqXdu6S5tqCuW39zK2ijlTyQ11BttYZqq6VCSe3W39pTbbWSUK139EWSidzz0jFTOiVaQYVUuO3ZdaNyuRxlNM/RuUOXDBSBu4GjDOOseT3WA/4CvgbeSnA5fPl3QhlPspCexyX/KjmC++DdOa0PNnhL8supHoM/l3xIMmCNtl0GqtLz8RD8u2SD61RZF8sgWyH4tRB8MQRXZaAyVAaS5O8T0G6quKvVqQ6DXwrBgDfKQONQecjOSH5P8mnJlxue6go1SEDvSw6pnQlAo1AZ6JkQfCVV8kEIfqyqfDkEW3KU7DmtTs93koemwBjw9dWXV9mDEl/YHAV2gbdj5M4QOC1xXOoUpwu4BvwUY+uVO+KlU8kb2e0Gb4MfyPNsRi+n2YwhG/dUVt7omvl8c2+qGu/nVT37E6CweZaOkyZIvQBUW63kgbyGaqvbG+qgJ3dTt7enlqk1VFu1guoam3dVq8Qh/wMW9pOILhLtzr+pUANgA/hR4k8Am7gAFOyHSJ2g6gbQuRD4LEbuSfHUvNlMBI4AH0lciJEeww5P00g8dVLyIIW/P4NPhOBvQlg4ccj2eoo+e9NjqlGoEyl0/U3yoyH4q0biMFjA9nKnJJ9NWcwUsFGopyT/EYKPVZU/SUA7qcJFbS8BXgNvJo+FaVA5TD0m+bmq8sU0ZDkLuVmWQ+wdyc9PABu5xco/7geeZZhSH8R9Xw3cBfwt8a09dpVWvFrjxpdLVvG6Vizn/DEreodc0koeyP8DbP/uIBO2okgAAAAASUVORK5CYII='
+
+led3 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADoUlEQVRoge2ZzascRRTFf6d7TCLBaALiImAIRoObJzqu3JiVCzH/QEACLlyI4sdCiArqwp2gG/8BcavgB7gRsgquRRBc+lwEBTExEMzLdB8XVTWv01090zPznPcgc6FopvtO1al7q+qee0uAOWBS7DeAnGxADZUDCWrU92EdaE1+l6nn/dokB2CUUzguccLmJlD+D0Aq4BBQS/zurk267pM4AnwFbAH/ZJVWB1QB54GbwF/cabGO9UqJSuJUXfMDcAao2Zs15jjgBHgWeA44DFwiTHwS9TpjVTalzW8S54Cfo9JOnN2y7XYEdAN4WuIJ4H2CJ/omMG2KzxKM5OOSr4ANruJz0VbH5zXwGcmvN769G8cbNTB0lksycQWUNn9LvAB8ATwK/Jsz7wxx1K+AF4ELwIexnyNxrLmWarcCXEgzdea1ZIV3onV2YjP4vYyl5k66js9VFnraSY8r2EX0WwgG7va+k3eoJACTmVq7stbYV2YOypwcyIB894Jqho8hcvdaalHZgBoqG1BDpRNmJOHWyZuYw9At3ZaSwBLqzLfcGT8o9pXxz7lOh0g1Y7BcDpAF1bRWmuWx2JYBNgL+BL6XuGBP+RrA1dz4ZCyoRDFsaomzwNc2pwm0eBE3JtePgOcljgLfxAl/ArzVAyJLzErJFIXH4KstWrssHTb4PPhl8Gct+t1q3ZdJ8RD4p9jZrdj5sm3SaFux/8M9oLJHggmp1k5R8ApwjZCrrUr0SuAl4KTEm8At+s+krqUkSwoulPwM+PoKLkz/uQg+1+Dnr0XuX3Q9lV/o090nUQEPAw8SKO2i59UI+APYsvmOsIMr4B7gIvA5u7s86fcCgpicxpx/e0EwTVAT4CMJ7OngAKfTuC39uVKz2ome1k1KHJr93M7oD65dLHuaN/+bW9RD3+27bEANlQ2oobJWUEPD1MZSQ2XjvlxUyL0bFGYSF1qWT5WEuJcb7Ebm3VxLNYPpsuXqFIh/zBTNFk6xSqCSeMTmYylcXbA4W6iB+4APbN4APiVUh2cN3mGdsFtHPwn+lb2po++An5J8SZp+e5UhdXQ71L0l7ge+BR4jpFYFy1MYE5jmZZuxxAPA2+SLsx1QhUQtcW9d8yXwZAS0F5dGE0JCe8VmDJwCTsyYxG5qVRQ+Kvnyii6b58pt8FjyWe5M62gnDunHQ8AYuM7e3/eJQIGPAdvAL3Qz5H29GU27uA0gX+BgfXfIuY2z73fIOTmQLOE/hw5ngBNVOEQAAAAASUVORK5CYII='
+
+led4 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADdElEQVRoge2Zu4tkRRSHv3OqukdFxd0FUQRdxESMFgQDBWFBQ/P1LzBREHSNzAwNRPwbVkMDQUMVdIKNfIAYimAgsivjY3fsufUzmKrZftSdvn3t7tvC/OBw+z6q6runHufUbQPEjsmHBqjpDKqrzqC6qgq1TdJaW9X21XZjzXIgtVyf0f3uXAyBBIQtAF0ajYhmC/cFyPLxwRD0/Xisp0MQoJivr9MiCDO9HqO+Go+F2Un72WahHghBN0PQgbueyWCjNQKVul4JQTLT5zEKM/lpUOdD0K8xSqDfzfScmQCFNQCF7KHX3CWQQF+MRsuhLoSgGxlKoD/MdDlGAfMFVzLPQFdz3Ye5/i+7eOpCCLoRggSa5IIH7nrcvTdY8fKV3GWTqbprUAuzz7gzJQPHM+S+lDif0sn9VVXKPJL9ryX1VKFq5+vIb8qatOzFqlC1Qn081LeOzlDbVDWabAqqa71xoaA2mB1XwklN/5/UZWgtQm2y+zqq7qkNgfVeEnZBOwm1sCQox6Y2TcfGrirP1+qtXVuAcmb7vqTF0zGwllefpvJ8Sa81BeqVtWsB6hbwpxn3SkyAMfCDGb+Z8URKOKsH5wAcANeBf8wYSxwCe8DNlkk1m4iBLrvrwEwCfWemSzHq63w+ATUrWsmd3nPXCyHo73z+jbseypltNUefB3vRXfsx6vnRSPs56WtyZX2slH0/Rr0co/Zj1MM5cZwDWoQqYA7CXZ9loNug9B/tVgZ7KwThrj3quX91IiXyQEyJqynxszt7QFN7g47WAHcB10LgW+AaMDKjob6onp7sg550108xSu5q3KUVrZT5MEY9Ox7rl7yb+dRdd1e60Eoftinkt3w0BJ5yZ9LyZqfJgb8Al/g4Jc6lxO3suY9i5ErT4NLMUtNpe1QZjJ2t7LLfyB46nBr4+3k3Y7PPL1cZY31jUgCOuLOITq91TT5Od1knqALWVzZ3XKbBA3KnT0Hbliqxb3ioyrXBoXaz+yrXzqBq2kmoszFVU80rg0PVdjXlt1We26hKwz+agdlMQB7MU01u6JOUeNXsJEcbFAry/lHig5R40529HPMGH1MNx7nSuxJvx+Os6aiy7+ucT61LDRAl3jk64mIIPNbyda93mtvXSup7D+il/DfI3P21fCLvpc7/921TagEY1FNtGjz21bSTUP8C1hqqTm9IJgsAAAAASUVORK5CYII='
+
+led5 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADyUlEQVRoge2Zv4tcVRTHP99zZ6NEggg2iYgWpghKEGNlYaGNYifBgAGbNFaCkCJ/haAiiGDAgOkFKxtttrGKFlaCbJFlwVVEo+5m5t2vxdy3+3bmzcybmXVnA/uFw5v35r37Pu/cX+fcK8AcM8WqAdp0AtVVxxKqN+mPo6I14z1NLdfaLx6hWj0VwLMS/9iYIeRhawCcAbYkfrXHHFF70CrHsxG+npIN3gX3wYNDtB2wwXcifDalA+8uv/cBE1ABNyXuSPwlcbOqyMVbh+GxqrznB4nXgC2bkMje99OB9ly/dAB8mDODnHlHIoBcCsxLWL8ArUfwisSWTYIDQGNQo3C3bE5LvJUSSSKV/2IBA1gDvpV4Hfg9572aGa2BiQ0dYBf4NGfeS4nLEXxs829LIbOUgdPA9zZXgb9zJgoQjPf0ieNUDdcHPqoqrqXEuVL4YE6oWgPApaflKfdNhWr2gvMSVBX3l4Cqa2DWGNgKJWnYaWk0/sb5or2wbfTuDNWm5lf+36P91N63Kh3LKOHBgTqpvhaNQ0lDW6FOqq+rjqWnOo/o9QTaDEXm1ULTTP2AGscaJjXgps3w0xQjZXaCEoDNn+V8wDAwQ2IdeBx4jP04qKvM8KO2Jf6w9yLZWc8cCN7PSP4mwgbfB7+Rkm+U8zqRmMd2S6Lwk+SnSqIQjfe22MELNdjDEf46wlcjfEOywVUpfBGrn/05whckA+51harB1sBIfrukWjvgvKTVHluXjGRN9lg7bUhOkk9F+PMC1i9fvIj1C9AG+MUIf5KSX4qYVJXtUCpfApgIfxZhS64ke07L5XhX8nOSb5fmsBnhF1rA2tcSJDwSDq9JXIqg1/ZAB/WAX2w+sHnTZhd4CNiUuAj8NpK6T+sFe20slca5iNVeOM8wbW+2sz74mZH7Oo3oZjiupFk3TlA9Lpn9tN3s18Jced+o5h00azWrZHSKaptnH5woYdU6geqqlUNVjK9NrByqLT5bOVSbTqC66sihDn19atF1qXru63Usp5On6kXTRZeq6zlzA/hRQkyfR2d6KgGVxPUIruTMva5f0pCBU8AmcC2CL3PmQtlimfZMq/VKnP5+yWQyyycO30m+mJI3SvR5D/zkSDzVDiXtAb1bgOp0aZk9mTpx+CrCz0d4G7wr+VwJIKdCpQJ0OSVn6VCARjeLvpB8KcJ3I/z0LKgoQFfqwH/JamuzQTneivDLvZ6fKFB1ojJxv/FV4BFgh8XD4GmqgEcZ7tVsjyQNq94E3Ru3mhBT95CPYp2qTiiaWrmn2nQsJ+T/AHYfURS2h8j3AAAAAElFTkSuQmCC'
+
+led6 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEBUlEQVRoge2ZP4gdVRTGf9+Z3c1qZJFUChJWLBQigmLQiJYhIkQLwcLWwkYrGy1s0sRC0gQUIljYWCRaaKONJoKNIoJKwEgaBcEuJuwmu/vefBZz7+68P/N23p91X7EfHGbmzsy935x777nfuSPAzBlivwkMwwGptphLUgvDCv8vpmVDueibfQMF+4BtT2UyhyUeBP61Kfao0RK4B7gO3Gl4xtkOgY8WhT+RbPAaeGPGtg42+EKEkVxvv2ZY6WJJ8jcRfjrCFxKxTqpkFpbr+qAojGTV2m4kheRrEb4l+VhR+FyEnb6wM6VtJkLnJCM5mgn1kpLk3xKRG5KPR/i95LHuFB4q0/Fs6rJiNCEPhIQ8uFdsrticjGBD4nXgJuOHizyoz0ucLUsWbLrZHQ1Q8hQGQuKqxMNlSYdqaq5LHAeuAodoji27YQsIu9X7g8HT1TcE0AHutllNpDpAd0JSxRjvDpBS7Tx3Vad2T0yGcTzcQ2q3Rrenxh5jLhfkHlKyJ+6eWWLQU97v5XgIqbnz1DSza5aY/4E+Lzgg1RY9EX23iB273B+FcVaDgWWmft5NRPJDJZOrhNwlbYj1kNoCbkpgs0klVf4Erkk8YLPYstJ+CPhbYiOtGG3qMOBIx2OS/0pK8XfJj0T4UlKft0ckBE12O9X1qeQiJQp1pQsNcrif2GMpgXiiKHwxyeNp5HB+91KElyNcgKOZUC8pKn3sJaok4sOah8opLXvsTNLpi6N1+qCnjkr+WPLjki/XUq1JCeXU6mKEn4zwR5KP9HXlUFL55n0R/il12ffg1Qh/G2FL7kr2mJbf+SzCq0XhX9NHXpa80kBsYDJ8J/GczR1gGfhR4hWJ+6WJ4pSoZvUtmy9tHqrV/XkEL9tDE4qdDBl8nZ1cbSudP5/uF81jYFQOZ8CvJu9v1gb+D2l89XuqJ07Vg6PZCXi5bJINj4Iq8cgernu7KRAPRPRRi2GX8VMs9R09pKwfc7kg7yuppklzQKotDjzVFgeeaou58FR/EG1Faha7C3lJaZOBD/0NUq8ogKV0PYlKyF+dG6ovM2OTKtPNfyR+AUgbqONmM3kX8GdgDThMJWUKmj+wkdQCcEPixQjeKkueklhjst3hFeAL4KUIvirLVnVs65lF8B9J66yDT0T4fNJB0/x5yPrp3Qifiti+vpL0VJbh2XpI1/v7lMRp4I2yZDOVdSc0U3XZmbLkUZvTRaXM3LBBF/0XAl4oCk4A75QlG1RdqSktgE3gfZsjNq9FcJe07YyB7st2L/jZovDbM8j1mqwEW/KbEX4mokrndkscliVOJnHfZvqOC1PNvAL4muFKdoBUMPkmxrhoamvofsNe/RHtR5Pen4dfxgOYS5XwH7eZhqnTFNxUAAAAAElFTkSuQmCC'
+
+led7 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAACtElEQVRoge2ZwWoUQRCGv+ruFRPx4iOIGPEuETyLGDH4AqII4tN49KQg+AB6jAjiOeBFg5gHEA+KHiQmJpme38P0hN3ZSZysm949zA9NQ2/t9LfdtV1V0waIOZObNUCbeqiumkuocNgHuWjF+D/NWsayylI/DHHoSi2ZUUgUTH/VDNgHzgK/zPgijazOGJQDSmDZjOdAlIhTBiuARWATuG/GjsQPRrdNzeZSfy8ERTMJVII0hRZTv2mmJee0Brqe5vNDLjYGBSikftVMuwlqPz100raXgDa81+XBQK+9l0BXukIBGqT+pnPadu6/VqxeoXXQee/1Lj1PoKsNqEMdHSpnDMBaWXInBJ46R6DyOTvqiw2VwAKwIfGwLHkRI9fS8wct9kdCAQf/vjdFwUXvCUx2hhjwR+ItHAAdNvk/oaD6pQ7Yi5HdCYEEnAEupDF/hH0nqBrMON62NaFKIHaw7wwF0zn6u/youQzIPVRX9VBd1UN1VQ/VVVmh6lDTNj6srFCO0UCsofGm3YlLVDFvG1i3KvoVaUxm/E5janznxJul/pSZXqU0OIIemAmzg7qgZssCNQLmnF6GoEdmI4XKkF3eYtSlFp0DiSCx32KTTXUuVQCPgVWqtLgtC822fYAw05NUS26Z6XYIbVuY0Z/M9Cw5+S5VebXjnC41fOtY6fCkqh13UeJGrLL0ulQ7XZacG7KDzD7VLBxqiGYxkd3R2yZsFhN9QO6qHqqreqiu6qG6aj6gbPT4zF44dEneshcOYxPa+Bur7IXDR1dNORKEZ7l9AHfNeO89A6oMtE3ZoJQm+xkjKxIfzFpfV2eFgiqf8sD3smRF4nMaj7PevpjAvgK3nOOb9yw0bGZ23+epAJdDYAv4VBQHN2gzvYRsu4CEGZ/o9VHRPKlmfl3bpvmIfQ3NJdRfFS/ZxKoPEs0AAAAASUVORK5CYII='
+
+led8 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEi0lEQVRoge2ZT4gcRRTGf+9Vd/VkIRjxIqggJF5E8JSDF/EQjKAICflDQjAnUdGD6EEQDHrRmwdBD6IIweQkiiIevOTgKSAiiiAiBDxpomhiJLvTVf08TPVkZqdmMz272Z2FfPCoobrr1dev/r1vSgBjwaBbTSCHW6RmxUKSKnKVkuxmw8ivMlld35LZjCU5ra8JUgD3qyJNQ5+bM74R2AGsqPJL02RJGGCSyru9t5NVZf+JmIEFsGYDrQYzsL9U7diOHXavc2P9AzYMhEvlURH2x8iBsuSaKi69KRtgxmAS/y3CwbLkZAg8FuNY/5AZncvAsRA41DQ86T1XRFAgAM06LKTOflflibLkuRjZX9f8I/klZYAVqXy2qsxSiM+UpT3ivV1KQ9mk+q7WtvvNOXuoquwL54bPToiM9Z9+57EMHK9ryqLgcFnyoRlLZkS6bRcGlMCfwDPO8XoIPBojy0BvSpsJUjLyoAYOh0C/LNltxs5EqisUuAp8mQjVieg0TI0UXJ9896lCCFyNsfP+1U5wBfaM+G26kBodmpZAYzbxrCuxBmaO8pqRGnUK04+FWTHrR01sCZtx5t0IC5klbI9ILSSpjYZM+b0WNi1Syvihe6N3Fw6bOqcWbvi6YKYdvT2nlO47envEqMjAj417yPmbIJWbZC4lYs2cpNrjKec7NzITpK6YgQiNGcJgxZwz43Yz7mSQQXZFAfwhwjlV9oRw/WAW4V8RMMsLh6GJ2HtFYZayzTe9tyPeDwVEAIsdLKQM85qqPV5V9s5I1vmW94bImGhgICImdZ+J8EFR0Ae+UeVMv4+kr5lnIbTtVkQ4WhQcEuFi0/ByjEO/uTajLM2DoWoPem8rqhY2QGq17a+o2p6qMkTMr5JWUyOlQCPCKeeIqlw04/0QhonePGgnuQAnvOcBMy4Bb4eAmmWz0LEoIWKnynIw7iL2Qlna096bqVqjatbRRtsc895e837gW9WeL8tWfOYj5Rikq8erijN1TZ3kdAG8UpZ8JcI9ZvTprmYq4IIZJ0R4NQTqtLILVfaL8HWMw/7HItXqrpeS7qtHVs557w3nsuN/I5OR8ufkL3Jdvj+VZPuaui83d9ovaCX8PDDye1yuv0k1k5HR7U4c10Fq1M8YMqS2R+qy2YiLFKl2kuQE6pZHKre9zERqs68ktjxSOdwiNSu2L6mNmuhr/VE2ipnUjKajZ56sc6hmpnRmKUcf628th+2XnY8RzBARrKM1IgN5pcq3xYDW6hRlNaZGKjD49/Z0UXBOle+ahmWRucWqA153jiXgQAgspzoykZpKqgd8UpacLQo+XVlhqWnWLRxO9/scLAp2Ose+GEFktki1Oc/nzvGuCJ8tL7NkRpiTUIsGuMOMszFy1Hs88HCMBJ2cQRM1twHfO8cbzvFxXbPLjHqdhNqO+sBdTcNHdc2LZcmvRcHOKe+PJe/7ej070uvZhSQa5736mGYxlT+q2sFez/YWxYR4GJNYrRDdC+w24zIz7hkdIAxuMnYBP6nyQ+a+b/vcjLbC8WbDyO/yWVJbjYU8kP8HKX/sOnQ6GT4AAAAASUVORK5CYII='
+
+led9 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEYklEQVRoge2ZT2gkRRTGf+91VXeQJcp6kAVBEFn/ggh7ET0IggjBGBNW1+Bpc1E8uDfPIgqC4OLNu6IL4oJ48+RR0LMXvQgii4d1E4PZdFc9D1M9yWRqJjPpbGbAfFBM0l3d7+tXr+q9r0oAY86gsyaQwympSTGXpFzuoqR2p2HkZ5kcvN6SOYkpOcrWECmAh1XxMbIDFHeATAQqIKjyS4zZPgaYpN9zInapquymqhlYDRaPsTVgBrYlYm+UpT1YlgP2AesHeuuRJTPWQ2CtLLklgks95RiaJTvbqqyWJa/EyEXVAfuQmX03gZeaho0QWPaev0RQoEluP2prkrFbIiyXJZdCYLVp+Nvy0WuAufT7WnKvgV0vCnumLO2PojBL7rcjtPa5G6r2bFnaV973771dVQP209957AArIbAAvO49n4lwFqiZbrkweuvOlggbIlwJgbUQ2AEWRjwzRKp1pk8EXgyBWoRHzThDbyimRQFsAdeAtRCoc4bHkWqDrA1KgIdiRMz4xyy/hkwCVc471yc57uOGSDmR3mjvQ2BvBsL0q70kEmFEUB9KKmew6yrfPjdpoh3uJyeR9cZjiNTsKZ16anLMZZE37Kl5HL55wGlMTYrhvJiJqTZPdfmCtq46iFyWGJmQc50iI4r6CZGrDDTjhH6/1tAmgAhmRkgkf1DlbIzcZ0ZzRDI3gO9FeJJegifZ2RwnHGiLdxH7uCzNRMzArjpny2VpO0lINGBhitakyrMWsVXv7aOy7FWdIvap94bIgGhIPIZ1n6nySVGwKMJ3wLW6xpsNlC/ToH0uAutVxfNmRDPeDAGJMRsOB1n26+XHqsq2i6IvjbpKqwD2r6o9UVWGqpUHpNVIT7XecqrUIrxTFFyta+IxeEpF2PCe+4EmRj5sGpTRs3KgFUVhImIKhnP2lvdmqhZVzTq0y1VlV1olI2LvOme0dsZ5SpM4jGlWFCIEVS6IcK8IzZTeMqAEfhfhZeCD3V3qdM+LcNF7vt7dpWBvVmZFRdw3TYMZzoyfY8QmrLGzUOXzJBw0kcWMc8kJ+z90nNLZI0a31VzoKeQmfdT+4cmJiYlItdO56/bQ0YXDHGAiUp1i6bB3Z67N3FNzSSqHmZOaS0/NjNTYDJCZRP9fT/UxoaY8UVIxM1QTCYccNNXsXVFkPHWk4VMgivRyn8iRWrsV8GP6sLDfwKQJuc3iDmhUWSoK3gd20p7ntDB6xx7vqbLoPet1zU56/yj/D1R9qrq3r61qz3lv20nJdN1H31K1FxYW7Bvn+vcuO3f4PrrR24xtRLjgHNebhrtinLrizL33TIx8efs2K95TqrIUY78K3Y8hUgXQiPCIc3wbAvfEyC7dT7ME2AXOmvFF07DiPQ80DYvjjkFo5Y6qPe6c/dlxyA4byt+Kwl5dWLCnve+FzSjh0P7zlCrnY2ST3snDcVZTQu8k427gVxF+yhwYdNmv6IxR+/PZJUE5mc0zIy9EZ+qpUZh5lZDDfw9J5SDpQQEIAAAAAElFTkSuQmCC'
+
+ledcolon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAABdklEQVRoge2YQU7DMBBFn9MEVVBYcADEmiVH4BScD3EL7gIcAYEEFSQdFrZRCJGYeBa1qnlSZSuVJz8/k/EkARAqo9m3gDlclBYXpcVFaXFRWlyUFhelpUpRrWVxAFaTYwIMlqApbnX9VJFT+UpOgRugA3ZE195C4EHE7JYs/TVpvAJ5BxGQPo1PTSObEASQUBA7xS/nE/hI8+yMEJ20YBK1G81zwnfWoKXr85MxAF9p3o/+24tT45N2k/EYOLIoovDpy069AvfAOdGxDngR+cmzUg6nTv0XoJ85toQqnapyQ3ZRWlyUFhelpUpR5oo+1xFYq3GVFd3Uo18Cd8CGuN+tgcem4VaErUjxFZtErYFr4ITY8K2AM2ILsy0JnDD36Pnk49a4mh49B2qtQUvX5zzZ8TdnVqVBR5jWD/x2C6Ko6av8UkwloQUuiImd32J64Bnb94TDqVNj5u7/9JYupUqnqtyQXZQWF6XFRWlxUVpclBYXpeUbS+1qOYf8HRgAAAAASUVORK5CYII='
+
+ledblank = 'data:image/png;base64,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/GMzLdsAADpEaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxOC0xMC0yMFQxNjowOToyNS0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE4LTEwLTIwVDE2OjM2OjMyLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOC0xMC0yMFQxNjozNjozMi0wNDowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgICAgPHBob3Rvc2hvcDpDb2xvck1vZGU+MzwvcGhvdG9zaG9wOkNvbG9yTW9kZT4KICAgICAgICAgPHBob3Rvc2hvcDpJQ0NQcm9maWxlPnNSR0IgSUVDNjE5NjYtMi4xPC9waG90b3Nob3A6SUNDUHJvZmlsZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDoxYTc5NDQ5ZC05ZmM0LWZiNGItOTNjYy0xNTcyMjU1MGU5OTk8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkMGIzZGFmNS1kNGE3LTExZTgtOGI3ZC1jYmQxMWFlODBkMGE8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDowZDlkYjE1OS1lYThmLWQxNDEtYWE4MS04YTY4NzFkNTI4YTc8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MGQ5ZGIxNTktZWE4Zi1kMTQxLWFhODEtOGE2ODcxZDUyOGE3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE4LTEwLTIwVDE2OjA5OjI1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjFhNzk0NDlkLTlmYzQtZmI0Yi05M2NjLTE1NzIyNTUwZTk5OTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxOC0xMC0yMFQxNjozNjozMi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjMxNDAwMDAvMTAwMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjMxNDAwMDAvMTAwMDA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+Mzc8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NjA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PvnJsBAAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAEZJREFUeNrszkEBACAMAKHT/p1nDPeABJxqWua2kJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSU1G8PAAD//wMAGdkBdyV2S1QAAAAASUVORK5CYII='
+
+w1 = 'data:image/png;base64,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'
+
+w2 = 'data:image/png;base64,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'
+
+w3 = 'data:image/png;base64,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'
+
+w4 = 'data:image/png;base64,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'
+
+w5 = 'data:image/png;base64,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'
+
+orangeround = 'data:image/png;base64,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'
+
+weather_icon_dict = {'clear-day': w1, 'clear-night': w1, 'rain': w3, 'snow': w3, 'sleet': w3, 'wind': w3, 'fog': w3,
+ 'cloudy': w4, 'partly-cloudy-day': w5, 'partly-cloudy-night': w5}
+
+led_digits = [led0, led1, led2, led3, led4, led5, led6, led7, led8, led9]
+
+if __name__ == '__main__':
+ led_clock()
diff --git a/DemoPrograms old/Demo_LED_Indicators.py b/DemoPrograms old/Demo_LED_Indicators.py
new file mode 100644
index 00000000..790b3cb8
--- /dev/null
+++ b/DemoPrograms old/Demo_LED_Indicators.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import sys
+
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import time
+import random
+
+"""
+ Demo program showing how to create your own "LED Indicators"
+ The LEDIndicator function acts like a new Element that is directly placed in a window's layout
+ After the Window is created, use the SetLED function to access the LED and set the color
+
+"""
+
+
+def LEDIndicator(key=None, radius=30):
+ return sg.Graph(canvas_size=(radius, radius),
+ graph_bottom_left=(-radius, -radius),
+ graph_top_right=(radius, radius),
+ pad=(0, 0), key=key)
+
+def SetLED(window, key, color):
+ graph = window.FindElement(key)
+ graph.Erase()
+ graph.DrawCircle((0, 0), 12, fill_color=color, line_color=color)
+
+
+layout = [[sg.Text('My LED Status Indicators', size=(20,1))],
+ [sg.Text('CPU Use'), LEDIndicator('_cpu_')],
+ [sg.Text('RAM'), LEDIndicator('_ram_')],
+ [sg.Text('Temperature'), LEDIndicator('_temp_')],
+ [sg.Text('Server 1'), LEDIndicator('_server1_')],
+ [sg.Button('Exit')]]
+
+window = sg.Window('My new window', default_element_size=(12, 1), auto_size_text=False).Layout(layout).Finalize()
+
+i = 0
+while True: # Event Loop
+ event, value = window.Read(timeout=400)
+ if event == 'Exit' or event is None:
+ break
+ if value is None:
+ break
+ i += 1
+ SetLED(window, '_cpu_', 'green' if random.randint(1, 10) > 5 else 'red')
+ SetLED(window, '_ram_', 'green' if random.randint(1, 10) > 5 else 'red')
+ SetLED(window, '_temp_', 'green' if random.randint(1, 10) > 5 else 'red')
+ SetLED(window, '_server1_', 'green' if random.randint(1, 10) > 5 else 'red')
diff --git a/DemoPrograms old/Demo_Layout_Generation.py b/DemoPrograms old/Demo_Layout_Generation.py
new file mode 100644
index 00000000..201a0ce0
--- /dev/null
+++ b/DemoPrograms old/Demo_Layout_Generation.py
@@ -0,0 +1,289 @@
+import PySimpleGUI as sg
+
+"""
+ PySimpleGUI is designed & authored in Python to take full advantage the awesome Python constructs & capabilities.
+ Layouts are represented as lists to PySimpleGUI. Lists are fundamental in Python and have a number of powerful
+ capabilities that PySimpleGUI exploits.
+
+ Many times PySimpleGUI programs can benefit from using CODE to GENERATE your layouts. This Demo illustrates
+ a number of ways of "building" a layout. Some work on 3.5 and up. Some are basic and show concatenation of rows
+ to build up a layout. Some utilize generators.
+
+ These 8 "Constructs" or Design Patterns demonstrate numerous ways of "generating" or building your layouts
+ 0 - A simple list comprehension to build a row of buttons
+ 1 - A simple list comprehension to build a column of buttons
+ 2 - Concatenation of rows within a layout
+ 3 - Concatenation of 2 complete layouts [[ ]] + [[ ]] = [[ ]]
+ 4 - Concatenation of elements to form a single row [ [] + [] + [] ] = [[ ]]
+ 5 - Questionnaire - Using a double list comprehension to build both rows and columns in a single line of code
+ 6 - Questionnaire - Unwinding the comprehensions into 2 for loops instead
+ 7 - Using the * operator to unpack generated items onto a single row
+ 8 - Multiple Choice Test - a practical use showing list comprehension and concatenated layout
+"""
+
+"""
+ Construct #0 - List comprehension to generate a row of Buttons
+
+ Comprehensions are super-powers of Python. In this example we're using a comprehension to create 4 buttons that
+ are all on the same row.
+"""
+
+
+def layout0():
+ layout = [[sg.Button(i) for i in range(4)]] # A list of buttons is created
+
+ window = sg.Window('Generated Layouts', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+"""
+ Construct #1 - List comprehension to generate a Column of Buttons
+
+ More list super-power, this time used to build a series of buttons doing DOWN the window instead of across
+
+"""
+
+def layout1():
+ layout = [[sg.Button(i)] for i in range(4)] # a List of lists of buttons. Notice the ] after Button
+
+ window = sg.Window('Generated Layouts', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct #2 - List comprehension to generate a row of Buttons and concatenation of more lines of elements
+
+ Comprehensions are super-powers of Python. In this example we're using a comprehension to create 4 buttons that
+ are all on the same row, just like the previous example.
+ However, here, we want to not just have a row of buttons, we want have an OK button at the bottom.
+ To do this, you "add" the rest of the GUI layout onto the end of the generated part.
+
+ Note - you can't end the layout line after the +. If you wanted to put the OK button on the next line, you need
+ to add a \ at the end of the first line.
+ See next Construct on how to not use a \ that also results in a VISUALLY similar to a norma layout
+"""
+
+def layout2():
+ layout = [[sg.Button(i) for i in range(4)]] + [[sg.OK()]] # if want to split, can't add newline after + to do it
+
+ window = sg.Window('Generated Layouts', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct # 3 - Adding together what appears to be 2 layouts
+
+ Same as layout2, except that the OK button is put on another line without using a \ so that the layout appears to
+ look like a normal, multiline layout without a \ at the end
+
+ Also shown is the OLD tried and true way, using layout.append. You will see the append technique in many of the
+ Demo programs and probably elsewhere. Hoping to remove these and instead use this more explicit method of +=.
+
+ Using the + operator, as you've already seen, can be used in the middle of the layout. A call to append you cannot
+ use this way because it modifies the layout list directly.
+"""
+
+def layout3():
+ # in terms of formatting, the layout to the RIGHT of the = sign looks like a 2-line GUI (ignore the layout +=
+ layout = [[sg.Button(i) for i in range(4)]]
+ layout += [[sg.OK()]] # this row is better than, but is the same as
+ layout.append([sg.Cancel()]) # .. this row in that they both add a new ROW with a button on it
+
+ window = sg.Window('Generated Layouts', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct 4 - Using + to place Elements on the same row
+
+ If you want to put elements on the same row, you can simply add them together. All that is happening is that the
+ items in one list are added to the items in another. That's true for all these contructs using +
+"""
+
+def layout4():
+ layout = [[sg.Text('Enter some info')] + [sg.Input()] + [sg.Exit()]]
+
+ window = sg.Window('Generated Layouts', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct #5 - Simple "concatenation" of layouts
+ Layouts are lists of lists. Some of the examples and demo programs use a .append method to add rows to layouts.
+ These will soono be replaced with this new technique. It's so simple that I don't know why it took so long to
+ find it.
+ This layout uses list comprehensions heavily, and even uses 2 of them. So, if you find them confusing, skip down
+ to the next Construct and you'll see the same layout built except for loops are used rather than comprehensions
+
+ The next 3 examples all use this same window that is layed out like this:
+ Each row is:
+ Text1, Text2, Radio1, Radio2, Radio3, Radio4, Radio5
+ Text1, Text2, Radio1, Radio2, Radio3, Radio4, Radio5
+ ...
+
+ It shows, in particular, this handy bit of layout building, a += to add on additional rows.
+ layout = [[stuff on row 1], [stuff on row 2]]
+ layout += [[stuff on row 3]]
+
+ Works as long as the things you are adding together look like this [[ ]] (the famous double bracket layouts of PSG)
+"""
+
+def layout5():
+ questions = ('Managing your day-to-day life', 'Coping with problems in your life?', 'Concentrating?',
+ 'Get along with people in your family?', 'Get along with people outside your family?',
+ 'Get along well in social situations?', 'Feel close to another person',
+ 'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
+
+ layout = [[sg.T(qnum + 1, size=(2, 2)), sg.T(q, size=(30, 2))] + [sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, col)) for col in range(5)] for qnum, q in enumerate(questions)]
+ layout += [[sg.OK()]]
+
+ window = sg.Window('Computed Layout Questionnaire', layout)
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct #6 - Computed layout without using list comprehensions
+ This layout is identical to Contruct #5. The difference is that rather than use list comprehensions, this code
+ uses for loops. Perhaps if you're a beginner this version makes more sense?
+
+ In this example we start with a "blank layout" [[ ]] and add onto it.
+
+ Works as long as the things you are adding together look like this [[ ]] (the famous double bracket layouts of PSG)
+"""
+
+
+def layout6():
+ questions = ('Managing your day-to-day life', 'Coping with problems in your life?', 'Concentrating?',
+ 'Get along with people in your family?', 'Get along with people outside your family?',
+ 'Get along well in social situations?', 'Feel close to another person',
+ 'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
+
+ layout = [[]]
+ for qnum, question in enumerate(questions): # loop through questions
+ row_layout = [sg.T(qnum + 1, size=(2, 2)), sg.T(question, size=(30, 2))] # rows start with # and question
+ for radio_num in range(5): # loop through 5 radio buttons and add to row
+ row_layout += [sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, radio_num))]
+ layout += [row_layout] # after row is completed layout, tack it onto the end of final layout
+
+ layout += [[sg.OK()]] # and finally, add a row to the bottom that has an OK button
+
+ window = sg.Window('Computed Layout Questionnaire', layout)
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+
+"""
+ Construct #7 - * operator and list comprehensions
+ Using the * operator from inside the layout
+ List comprehension inside the layout
+ Addition of rows to layouts
+ All in a single variable assignment
+
+ NOTE - this particular code, using the * operator, will not work on Python 2 and think it was added in Python 3.5
+
+ This code shows a bunch of questions with Radio Button choices
+
+ There is a double-loop comprehension used. One that loops through the questions (rows) and the other loops through
+ the Radio Button choices.
+ Thus each row is:
+ Text1, Text2, Radio1, Radio2, Radio3, Radio4, Radio5
+ Text1, Text2, Radio1, Radio2, Radio3, Radio4, Radio5
+ Text1, Text2, Radio1, Radio2, Radio3, Radio4, Radio5
+
+ What the * operator is doing in these cases is expanding the list they are in front of into a SERIES of items
+ from the list... one after another, as if they are separated with comma. It's a way of "unpacking" from within
+ a statement.
+
+ The result is a beautifully compact way to make a layout, still using a layout variable, that consists of a
+ variable number of rows and a variable number of columns in each row.
+"""
+
+def layout7():
+ questions = ('Managing your day-to-day life', 'Coping with problems in your life?', 'Concentrating?',
+ 'Get along with people in your family?', 'Get along with people outside your family?',
+ 'Get along well in social situations?', 'Feel close to another person',
+ 'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
+
+ layout = [[*[sg.T(qnum + 1, size=(2, 2)), sg.T(q, size=(30, 2))], # These are the question # and the question text
+ *[sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, col)) for col in range(5)]] for qnum, q in enumerate(questions)] + [[sg.OK()]] # finally add an OK button at the very bottom by using the '+' operator
+
+ window = sg.Window('Questionnaire', layout)
+
+ event, values = window.Read()
+
+ print(event, values)
+ window.Close()
+
+
+"""
+ Construct #8 - Computed layout using list comprehension and concatenation
+ This layout shows one practical use, a multiple choice test. It's been left parse as to focus on the generation
+ part of the program. For example, default keys are used on the Radio elements. In reality you would likely
+ use a tuple of the question number and the answer number.
+
+ In this example we start with a "Header" Text element and build from there.
+"""
+
+def layout8():
+ # The questions and answers
+ q_and_a = [
+ ['1. What is the thing that makes light in our solar system', ['A. The Moon', 'B. Jupiter', 'C. I dunno']],
+ ['2. What is Pluto', ['A. The 9th planet', 'B. A dwarf-planet', 'C. The 8th planet', 'D. Goofies pet dog']],
+ ['3. When did man step foot on the moon', ['A. 1969', 'B. 1960', 'C. 1970', 'D. 1869']], ]
+
+ layout = [[sg.Text('Astronomy Quiz #1', font='ANY 15', size=(30, 2))]] # make Header larger
+
+ # "generate" the layout for the window based on the Question and Answer information
+ for qa in q_and_a:
+ q = qa[0]
+ a_list = qa[1]
+ layout += [[sg.Text(q)]] + [[sg.Radio(a, group_id=q)] for a in a_list] + [[sg.Text('_' * 50)]]
+
+ layout += [[sg.Button('Submit Answers', key='SUBMIT')]]
+
+ window = sg.Window('Multiple Choice Test', layout)
+
+ while True: # Event Loop
+ event, values = window.read()
+ if event in (None, 'SUBMIT'):
+ break
+ sg.popup('The answers submitted were', values)
+ window.close()
+
+
+# ------------------------- Call each of the Constructs -------------------------
+
+layout0()
+layout1()
+layout2()
+layout3()
+layout4()
+layout5()
+layout6()
+layout7()
+layout8()
diff --git a/DemoPrograms old/Demo_Listbox_Search_Filter.py b/DemoPrograms old/Demo_Listbox_Search_Filter.py
new file mode 100644
index 00000000..88f4ce45
--- /dev/null
+++ b/DemoPrograms old/Demo_Listbox_Search_Filter.py
@@ -0,0 +1,27 @@
+import PySimpleGUI as sg
+
+names = ['Roberta', 'Kylie', 'Jenny', 'Helen',
+ 'Andrea', 'Meredith','Deborah','Pauline',
+ 'Belinda', 'Wendy']
+
+layout = [ [sg.Text('Listbox with search')],
+ [sg.Input(do_not_clear=True, size=(20,1),enable_events=True, key='_INPUT_')],
+ [sg.Listbox(names, size=(20,4), enable_events=True, key='_LIST_')],
+ [sg.Button('Chrome'), sg.Button('Exit')]]
+
+window = sg.Window('Listbox with Search').Layout(layout)
+# Event Loop
+while True:
+ event, values = window.Read()
+ if event is None or event == 'Exit': # always check for closed window
+ break
+ if values['_INPUT_'] != '': # if a keystroke entered in search field
+ search = values['_INPUT_']
+ new_values = [x for x in names if search in x] # do the filtering
+ window.Element('_LIST_').Update(new_values) # display in the listbox
+ else:
+ window.Element('_LIST_').Update(names) # display original unfiltered list
+ if event == '_LIST_' and len(values['_LIST_']): # if a list item is chosen
+ sg.Popup('Selected ', values['_LIST_'])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_MIDI_Player.py b/DemoPrograms old/Demo_MIDI_Player.py
new file mode 100644
index 00000000..11b4445c
--- /dev/null
+++ b/DemoPrograms old/Demo_MIDI_Player.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import os
+import mido
+import time
+import sys
+
+PLAYER_COMMAND_NONE = 0
+PLAYER_COMMAND_EXIT = 1
+PLAYER_COMMAND_PAUSE = 2
+PLAYER_COMMAND_NEXT = 3
+PLAYER_COMMAND_RESTART_SONG = 4
+
+# ---------------------------------------------------------------------- #
+# PlayerGUI CLASS #
+# ---------------------------------------------------------------------- #
+class PlayerGUI():
+ '''
+ Class implementing GUI for both initial screen but the player itself
+ '''
+
+ def __init__(self):
+ self.Window = None
+ self.TextElem = None
+ self.PortList = mido.get_output_names() # use to get the list of midi ports
+ self.PortList = self.PortList[::-1] # reverse the list so the last one is first
+
+ # ---------------------------------------------------------------------- #
+ # PlayerChooseSongGUI #
+ # Show a GUI get to the file to playback #
+ # ---------------------------------------------------------------------- #
+ def PlayerChooseSongGUI(self):
+
+ # ---------------------- DEFINION OF CHOOSE WHAT TO PLAY GUI ----------------------------
+
+ layout = [[sg.Text('MIDI File Player', font=("Helvetica", 15), size=(20, 1), text_color='green')],
+ [sg.Text('File Selection', font=("Helvetica", 15), size=(20, 1))],
+ [sg.Text('Single File Playback', justification='right'), sg.InputText(size=(65, 1), key='midifile'), sg.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
+ [sg.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'), sg.InputText(size=(65, 1), key='folder'), sg.FolderBrowse(size=(10, 1))],
+ [sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
+ [sg.Text('Choose MIDI Output Device', size=(22, 1)),
+ sg.Listbox(values=self.PortList, size=(30, len(self.PortList) + 1), key='device')],
+ [sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
+ [sg.SimpleButton('PLAY', size=(12, 2), button_color=('red', 'white'), font=("Helvetica", 15), bind_return_key=True), sg.Text(' ' * 2, size=(4, 1)), sg.Cancel(size=(8, 2), font=("Helvetica", 15))]]
+
+ window = sg.Window('MIDI File Player', auto_size_text=False, default_element_size=(30, 1), font=("Helvetica", 12)).Layout(layout)
+ self.Window = window
+ return window.Read()
+
+
+ def PlayerPlaybackGUIStart(self, NumFiles=1):
+ # ------- Make a new FlexForm ------- #
+
+ image_pause = './ButtonGraphics/Pause.png'
+ image_restart = './ButtonGraphics/Restart.png'
+ image_next = './ButtonGraphics/Next.png'
+ image_exit = './ButtonGraphics/Exit.png'
+
+ self.TextElem = sg.T('Song loading....', size=(70, 5 + NumFiles), font=("Helvetica", 14), auto_size_text=False)
+ self.SliderElem = sg.Slider(range=(1, 100), size=(50, 8), orientation='h', text_color='#f0f0f0')
+ layout = [
+ [sg.T('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
+ [self.TextElem],
+ [self.SliderElem],
+ [sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
+ image_filename=image_pause, image_size=(50,50), image_subsample=2, border_width=0, key='PAUSE'), sg.T(' '),
+ sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
+ image_filename=image_next, image_size=(50,50), image_subsample=2, border_width=0, key='NEXT'), sg.T(' '),
+ sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
+ image_filename=image_restart, image_size=(50,50), image_subsample=2, border_width=0, key='Restart Song'), sg.T(' '),
+ sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
+ image_filename=image_exit, image_size=(50,50), image_subsample=2, border_width=0,key='EXIT')]
+ ]
+
+ window = sg.Window('MIDI File Player', default_element_size=(30, 1), font=("Helvetica", 25)).Layout(layout).Finalize()
+ self.Window = window
+
+
+
+ # ------------------------------------------------------------------------- #
+ # PlayerPlaybackGUIUpdate #
+ # Refresh the GUI for the main playback interface (must call periodically #
+ # ------------------------------------------------------------------------- #
+ def PlayerPlaybackGUIUpdate(self, DisplayString):
+ window = self.Window
+ if 'window' not in locals() or window is None: # if the widnow has been destoyed don't mess with it
+ return PLAYER_COMMAND_EXIT
+ self.TextElem.Update(DisplayString)
+ event, (values) = window.Read(timeout=0)
+ if event is None:
+ return PLAYER_COMMAND_EXIT
+ if event == 'PAUSE':
+ return PLAYER_COMMAND_PAUSE
+ elif event == 'EXIT':
+ return PLAYER_COMMAND_EXIT
+ elif event == 'NEXT':
+ return PLAYER_COMMAND_NEXT
+ elif event == 'Restart Song':
+ return PLAYER_COMMAND_RESTART_SONG
+ return PLAYER_COMMAND_NONE
+
+
+# ---------------------------------------------------------------------- #
+# MAIN - our main program... this is it #
+# Runs the GUI to get the file / path to play #
+# Decodes the MIDI-Video into a MID file #
+# Plays the decoded MIDI file #
+# ---------------------------------------------------------------------- #
+def main():
+ def GetCurrentTime():
+ '''
+ Get the current system time in milliseconds
+ :return: milliseconds
+ '''
+ return int(round(time.time() * 1000))
+
+
+ pback = PlayerGUI()
+
+ button, values = pback.PlayerChooseSongGUI()
+ if button != 'PLAY':
+ sg.PopupCancel('Cancelled...\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
+ sys.exit(69)
+ if values['device']:
+ midi_port = values['device'][0]
+ else:
+ sg.PopupCancel('No devices found\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
+
+ batch_folder = values['folder']
+ midi_filename = values['midifile']
+ # ------ Build list of files to play --------------------------------------------------------- #
+ if batch_folder:
+ filelist = os.listdir(batch_folder)
+ filelist = [batch_folder+'/'+f for f in filelist if f.endswith(('.mid', '.MID'))]
+ filetitles = [os.path.basename(f) for f in filelist]
+ elif midi_filename: # an individual filename
+ filelist = [midi_filename,]
+ filetitles = [os.path.basename(midi_filename),]
+ else:
+ sg.PopupError('*** Error - No MIDI files specified ***')
+ sys.exit(666)
+
+ # ------ LOOP THROUGH MULTIPLE FILES --------------------------------------------------------- #
+ pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <=10 else 10)
+ port = None
+ # Loop through the files in the filelist
+ for now_playing_number, current_midi_filename in enumerate(filelist):
+ display_string = 'Playing Local File...\n{} of {}\n{}'.format(now_playing_number+1, len(filelist), current_midi_filename)
+ midi_title = filetitles[now_playing_number]
+ # --------------------------------- REFRESH THE GUI ----------------------------------------- #
+ pback.PlayerPlaybackGUIUpdate(display_string)
+
+ # ---===--- Output Filename is .MID --- #
+ midi_filename = current_midi_filename
+
+ # --------------------------------- MIDI - STARTS HERE ----------------------------------------- #
+ if not port: # if the midi output port not opened yet, then open it
+ port = mido.open_output(midi_port if midi_port else None)
+
+ try:
+ mid = mido.MidiFile(filename=midi_filename)
+ except:
+ print('****** Exception trying to play MidiFile filename = {}***************'.format(midi_filename))
+ sg.PopupError('Exception trying to play MIDI file:', midi_filename, 'Skipping file')
+ continue
+
+ # Build list of data contained in MIDI File using only track 0
+ midi_length_in_seconds = mid.length
+ display_file_list = '>> ' + '\n'.join([f for i, f in enumerate(filetitles[now_playing_number:]) if i < 10])
+ paused = cancelled = next_file = False
+ ######################### Loop through MIDI Messages ###########################
+ while(True):
+ start_playback_time = GetCurrentTime()
+ port.reset()
+
+ for midi_msg_number, msg in enumerate(mid.play()):
+ #################### GUI - read values ##################
+ if not midi_msg_number % 4: # update the GUI every 4 MIDI messages
+ t = (GetCurrentTime() - start_playback_time)//1000
+ display_midi_len = '{:02d}:{:02d}'.format(*divmod(int(midi_length_in_seconds),60))
+ display_string = 'Now Playing {} of {}\n{}\n {:02d}:{:02d} of {}\nPlaylist:'.\
+ format(now_playing_number+1, len(filelist), midi_title, *divmod(t, 60), display_midi_len)
+ # display list of next 10 files to be played.
+ pback.SliderElem.Update(t, range=(1,midi_length_in_seconds))
+ rc = pback.PlayerPlaybackGUIUpdate(display_string + '\n' + display_file_list)
+ else: # fake rest of code as if GUI did nothing
+ rc = PLAYER_COMMAND_NONE
+ if paused:
+ rc = PLAYER_COMMAND_NONE
+ while rc == PLAYER_COMMAND_NONE: # TIGHT-ASS loop waiting on a GUI command
+ rc = pback.PlayerPlaybackGUIUpdate(display_string)
+ time.sleep(.25)
+
+ ####################################### MIDI send data ##################################
+ port.send(msg)
+
+ # ------- Execute GUI Commands after sending MIDI data ------- #
+ if rc == PLAYER_COMMAND_EXIT:
+ cancelled = True
+ break
+ elif rc == PLAYER_COMMAND_PAUSE:
+ paused = not paused
+ port.reset()
+ elif rc == PLAYER_COMMAND_NEXT:
+ next_file = True
+ break
+ elif rc == PLAYER_COMMAND_RESTART_SONG:
+ break
+
+ if cancelled or next_file:
+ break
+ #------- DONE playing the song ------- #
+ port.reset() # reset the midi port when done with the song
+
+ if cancelled:
+ break
+
+# ---------------------------------------------------------------------- #
+# LAUNCH POINT -- program starts and ends here #
+# ---------------------------------------------------------------------- #
+if __name__ == '__main__':
+ main()
+
diff --git a/DemoPrograms old/Demo_Machine_Learning.py b/DemoPrograms old/Demo_Machine_Learning.py
new file mode 100644
index 00000000..0b5881f1
--- /dev/null
+++ b/DemoPrograms old/Demo_Machine_Learning.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+def MachineLearningGUI():
+ sg.SetOptions(text_justification='right')
+
+ flags = [[sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))],
+ [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)],
+ [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))],
+ [sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))],
+ [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)],
+ [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))],]
+
+ loss_functions = [[sg.Radio('Cross-Entropy', 'loss', size=(12, 1)), sg.Radio('Logistic', 'loss', default=True, size=(12, 1))],
+ [sg.Radio('Hinge', 'loss', size=(12, 1)), sg.Radio('Huber', 'loss', size=(12, 1))],
+ [sg.Radio('Kullerback', 'loss', size=(12, 1)), sg.Radio('MAE(L1)', 'loss', size=(12, 1))],
+ [sg.Radio('MSE(L2)', 'loss', size=(12, 1)), sg.Radio('MB(L0)', 'loss', size=(12, 1))],]
+
+ command_line_parms = [[sg.Text('Passes', size=(8, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1)),
+ sg.Text('Steps', size=(8, 1), pad=((7,3))), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1))],
+ [sg.Text('ooa', size=(8, 1)), sg.In(default_text='6', size=(8, 1)), sg.Text('nn', size=(8, 1)),
+ sg.In(default_text='10', size=(10, 1))],
+ [sg.Text('q', size=(8, 1)), sg.In(default_text='ff', size=(8, 1)), sg.Text('ngram', size=(8, 1)),
+ sg.In(default_text='5', size=(10, 1))],
+ [sg.Text('l', size=(8, 1)), sg.In(default_text='0.4', size=(8, 1)), sg.Text('Layers', size=(8, 1)),
+ sg.Drop(values=('BatchNorm', 'other'), auto_size_text=True)],]
+
+ layout = [[sg.Frame('Command Line Parameteres', command_line_parms, title_color='green', font='Any 12')],
+ [sg.Frame('Flags', flags, font='Any 12', title_color='blue')],
+ [sg.Frame('Loss Functions', loss_functions, font='Any 12', title_color='red')],
+ [sg.Submit(), sg.Cancel()]]
+
+ window = sg.Window('Machine Learning Front End', font=("Helvetica", 12)).Layout(layout)
+ button, values = window.Read()
+ sg.SetOptions(text_justification='left')
+
+ print(button, values)
+
+def CustomMeter():
+ # layout the form
+ layout = [[sg.Text('A custom progress meter')],
+ [sg.ProgressBar(1000, orientation='h', size=(20,20), key='progress')],
+ [sg.Cancel()]]
+
+ # create the form`
+ window = sg.Window('Custom Progress Meter').Layout(layout)
+ progress_bar = window.FindElement('progress')
+ # loop that would normally do something useful
+ for i in range(1000):
+ # check to see if the cancel button was clicked and exit loop if clicked
+ event, values = window.Read(timeout=0, timeout_key='timeout')
+ if event == 'Cancel' or event == None:
+ break
+ # update bar with loop value +1 so that bar eventually reaches the maximum
+ progress_bar.UpdateBar(i+1)
+ # done with loop... need to destroy the window as it's still open
+ window.CloseNonBlocking()
+
+if __name__ == '__main__':
+ CustomMeter()
+ MachineLearningGUI()
diff --git a/DemoPrograms old/Demo_Matplotlib.py b/DemoPrograms old/Demo_Matplotlib.py
new file mode 100644
index 00000000..63164465
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+
+import matplotlib
+matplotlib.use('TkAgg')
+
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+
+"""
+Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
+
+Basic steps are:
+ * Create a Canvas Element
+ * Layout form
+ * Display form (NON BLOCKING)
+ * Draw plots onto convas
+ * Display form (BLOCKING)
+
+ Based on information from: https://matplotlib.org/3.1.0/gallery/user_interfaces/embedding_in_tk_sgskip.html
+ (Thank you Em-Bo & dirck)
+"""
+
+
+#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+from matplotlib.ticker import NullFormatter # useful for `logit` scale
+
+# Fixing random state for reproducibility
+np.random.seed(19680801)
+
+# make up some data in the interval ]0, 1[
+y = np.random.normal(loc=0.5, scale=0.4, size=1000)
+y = y[(y > 0) & (y < 1)]
+y.sort()
+x = np.arange(len(y))
+
+# plot with various axes scales
+plt.figure(1)
+
+# linear
+plt.subplot(221)
+plt.plot(x, y)
+plt.yscale('linear')
+plt.title('linear')
+plt.grid(True)
+
+
+# log
+plt.subplot(222)
+plt.plot(x, y)
+plt.yscale('log')
+plt.title('log')
+plt.grid(True)
+
+
+# symmetric log
+plt.subplot(223)
+plt.plot(x, y - y.mean())
+plt.yscale('symlog', linthreshy=0.01)
+plt.title('symlog')
+plt.grid(True)
+
+# logit
+plt.subplot(224)
+plt.plot(x, y)
+plt.yscale('logit')
+plt.title('logit')
+plt.grid(True)
+plt.gca().yaxis.set_minor_formatter(NullFormatter())
+plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
+ wspace=0.35)
+fig = plt.gcf() # if using Pyplot then get the figure from the plot
+figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
+
+#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
+
+#------------------------------- Beginning of Matplotlib helper code -----------------------
+
+
+def draw_figure(canvas, figure, loc=(0, 0)):
+ figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
+ figure_canvas_agg.draw()
+ figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
+ return figure_canvas_agg
+#------------------------------- Beginning of GUI CODE -------------------------------
+
+# define the window layout
+layout = [[sg.Text('Plot test', font='Any 18')],
+ [sg.Canvas(size=(figure_w, figure_h), key='canvas')],
+ [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
+
+# create the form and show it without the plot
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
+
+# add the plot to the window
+fig_canvas_agg = draw_figure(window['canvas'].TKCanvas, fig)
+
+event, values = window.read()
diff --git a/DemoPrograms old/Demo_Matplotlib_Animated.py b/DemoPrograms old/Demo_Matplotlib_Animated.py
new file mode 100644
index 00000000..da28c03c
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Animated.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+
+from random import randint
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg
+from matplotlib.figure import Figure
+
+def draw_figure(canvas, figure, loc=(0, 0)):
+ figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
+ figure_canvas_agg.draw()
+ figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
+ return figure_canvas_agg
+
+def main():
+
+ NUM_DATAPOINTS = 10000
+ # define the form layout
+ layout = [[sg.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')],
+ [sg.Canvas(size=(640, 480), key='-CANVAS-')],
+ [sg.Text('Progress through the data')],
+ [sg.Slider(range=(0, NUM_DATAPOINTS), size=(60, 10), orientation='h', key='-SLIDER-')],
+ [sg.Text('Number of data points to display on screen')],
+ [sg.Slider(range=(10, 500), default_value=40, size=(40, 10), orientation='h', key='-SLIDER-DATAPOINTS-')],
+ [sg.Button('Exit', size=(10, 1), pad=((280, 0), 3), font='Helvetica 14')]]
+
+ # create the form and show it without the plot
+ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
+
+ canvas_elem = window.FindElement('-CANVAS-')
+ slider_elem = window.FindElement('-SLIDER-')
+ canvas = canvas_elem.TKCanvas
+
+ # draw the initial plot in the window
+ fig = Figure()
+ ax = fig.add_subplot(111)
+ ax.set_xlabel("X axis")
+ ax.set_ylabel("Y axis")
+ ax.grid()
+ fig_agg = draw_figure(canvas, fig)
+ # make a bunch of random data points
+ dpts = [randint(0, 10) for x in range(NUM_DATAPOINTS)]
+
+ for i in range(len(dpts)):
+ event, values = window.Read(timeout=10)
+ if event in ('Exit', None):
+ exit(69)
+ slider_elem.Update(i) # slider shows "progress" through the data points
+ ax.cla() # clear the subplot
+ ax.grid() # draw the grid
+ data_points = int(values['-SLIDER-DATAPOINTS-']) # draw this many data points (on next line)
+ ax.plot(range(data_points), dpts[i:i+data_points], color='purple')
+ fig_agg.draw()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Matplotlib_Animated_Scatter.py b/DemoPrograms old/Demo_Matplotlib_Animated_Scatter.py
new file mode 100644
index 00000000..21df8cdf
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Animated_Scatter.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+
+import PySimpleGUI as sg
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+import matplotlib.pyplot as plt
+from numpy.random import rand
+
+def draw_figure(canvas, figure):
+ figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
+ figure_canvas_agg.draw()
+ figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
+ return figure_canvas_agg
+
+
+
+
+def main():
+ # define the form layout
+ layout = [[sg.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')],
+ [sg.Canvas(size=(640, 480), key='-CANVAS-')],
+ [sg.Button('Exit', size=(10, 2), pad=((280, 0), 3), font='Helvetica 14')]]
+
+ # create the form and show it without the plot
+ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
+
+ canvas_elem = window.FindElement('-CANVAS-')
+ canvas = canvas_elem.TKCanvas
+ # draw the intitial scatter plot
+ fig, ax = plt.subplots()
+ ax.grid(True)
+ fig_agg = draw_figure(canvas, fig)
+
+ while True:
+ event, values = window.Read(timeout=10)
+ if event in ('Exit', None):
+ exit(69)
+
+ ax.cla()
+ ax.grid(True)
+ for color in ['red', 'green', 'blue']:
+ n = 750
+ x, y = rand(2, n)
+ scale = 200.0 * rand(n)
+ ax.scatter(x, y, c=color, s=scale, label=color, alpha=0.3, edgecolors='none')
+ ax.legend()
+ fig_agg.draw()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Matplotlib_Browser.py b/DemoPrograms old/Demo_Matplotlib_Browser.py
new file mode 100644
index 00000000..a05242b0
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Browser.py
@@ -0,0 +1,878 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+import matplotlib
+matplotlib.use('TkAgg')
+import inspect
+
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+
+"""
+Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
+
+Basic steps are:
+ * Create a Canvas Element
+ * Layout form
+ * Display form (NON BLOCKING)
+ * Draw plots onto convas
+ * Display form (BLOCKING)
+
+Each plotting function, complete with imports, was copied directly from Matplot examples page
+"""
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+def PyplotSimple():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # evenly sampled time .2 intervals
+ t = np.arange(0., 5., 0.2) # go from 0 to 5 using .2 intervals
+
+ # red dashes, blue squares and green triangles
+ plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
+
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def PyplotHistogram():
+ """
+ =============================================================
+ Demo of the histogram (hist) function with multiple data sets
+ =============================================================
+
+ Plot histogram with multiple sample sets and demonstrate:
+
+ * Use of legend with multiple sample sets
+ * Stacked bars
+ * Step curve with no fill
+ * Data sets of different sample sizes
+
+ Selecting different bin counts and sizes can significantly affect the
+ shape of a histogram. The Astropy docs have a great section on how to
+ select these parameters:
+ http://docs.astropy.org/en/stable/visualization/histogram.html
+ """
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ np.random.seed(0)
+
+ n_bins = 10
+ x = np.random.randn(1000, 3)
+
+ fig, axes = plt.subplots(nrows=2, ncols=2)
+ ax0, ax1, ax2, ax3 = axes.flatten()
+
+ colors = ['red', 'tan', 'lime']
+ ax0.hist(x, n_bins, normed=1, histtype='bar', color=colors, label=colors)
+ ax0.legend(prop={'size': 10})
+ ax0.set_title('bars with legend')
+
+ ax1.hist(x, n_bins, normed=1, histtype='bar', stacked=True)
+ ax1.set_title('stacked bar')
+
+ ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False)
+ ax2.set_title('stack step (unfilled)')
+
+ # Make a multiple-histogram of data-sets with different length.
+ x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]]
+ ax3.hist(x_multi, n_bins, histtype='bar')
+ ax3.set_title('different sample sizes')
+
+ fig.tight_layout()
+ return fig
+
+def PyplotArtistBoxPlots():
+ """
+ =========================================
+ Demo of artist customization in box plots
+ =========================================
+
+ This example demonstrates how to use the various kwargs
+ to fully customize box plots. The first figure demonstrates
+ how to remove and add individual components (note that the
+ mean is the only value not shown by default). The second
+ figure demonstrates how the styles of the artists can
+ be customized. It also demonstrates how to set the limit
+ of the whiskers to specific percentiles (lower right axes)
+
+ A good general reference on boxplots and their history can be found
+ here: http://vita.had.co.nz/papers/boxplots.pdf
+
+ """
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # fake data
+ np.random.seed(937)
+ data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
+ labels = list('ABCD')
+ fs = 10 # fontsize
+
+ # demonstrate how to toggle the display of different elements:
+ fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6, 6), sharey=True)
+ axes[0, 0].boxplot(data, labels=labels)
+ axes[0, 0].set_title('Default', fontsize=fs)
+
+ axes[0, 1].boxplot(data, labels=labels, showmeans=True)
+ axes[0, 1].set_title('showmeans=True', fontsize=fs)
+
+ axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True)
+ axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs)
+
+ axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False)
+ tufte_title = 'Tufte Style \n(showbox=False,\nshowcaps=False)'
+ axes[1, 0].set_title(tufte_title, fontsize=fs)
+
+ axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000)
+ axes[1, 1].set_title('notch=True,\nbootstrap=10000', fontsize=fs)
+
+ axes[1, 2].boxplot(data, labels=labels, showfliers=False)
+ axes[1, 2].set_title('showfliers=False', fontsize=fs)
+
+ for ax in axes.flatten():
+ ax.set_yscale('log')
+ ax.set_yticklabels([])
+
+ fig.subplots_adjust(hspace=0.4)
+ return fig
+
+def ArtistBoxplot2():
+
+ # fake data
+ np.random.seed(937)
+ data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
+ labels = list('ABCD')
+ fs = 10 # fontsize
+
+ # demonstrate how to customize the display different elements:
+ boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod')
+ flierprops = dict(marker='o', markerfacecolor='green', markersize=12,
+ linestyle='none')
+ medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick')
+ meanpointprops = dict(marker='D', markeredgecolor='black',
+ markerfacecolor='firebrick')
+ meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple')
+
+ fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6, 6), sharey=True)
+ axes[0, 0].boxplot(data, boxprops=boxprops)
+ axes[0, 0].set_title('Custom boxprops', fontsize=fs)
+
+ axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops)
+ axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs)
+
+ axes[0, 2].boxplot(data, whis='range')
+ axes[0, 2].set_title('whis="range"', fontsize=fs)
+
+ axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False,
+ showmeans=True)
+ axes[1, 0].set_title('Custom mean\nas point', fontsize=fs)
+
+ axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True,
+ showmeans=True)
+ axes[1, 1].set_title('Custom mean\nas line', fontsize=fs)
+
+ axes[1, 2].boxplot(data, whis=[15, 85])
+ axes[1, 2].set_title('whis=[15, 85]\n#percentiles', fontsize=fs)
+
+ for ax in axes.flatten():
+ ax.set_yscale('log')
+ ax.set_yticklabels([])
+
+ fig.suptitle("I never said they'd be pretty")
+ fig.subplots_adjust(hspace=0.4)
+ return fig
+
+def PyplotScatterWithLegend():
+ import matplotlib.pyplot as plt
+ from numpy.random import rand
+
+ fig, ax = plt.subplots()
+ for color in ['red', 'green', 'blue']:
+ n = 750
+ x, y = rand(2, n)
+ scale = 200.0 * rand(n)
+ ax.scatter(x, y, c=color, s=scale, label=color,
+ alpha=0.3, edgecolors='none')
+
+ ax.legend()
+ ax.grid(True)
+ return fig
+
+def PyplotLineStyles():
+ """
+ ==========
+ Linestyles
+ ==========
+
+ This examples showcases different linestyles copying those of Tikz/PGF.
+ """
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from collections import OrderedDict
+ from matplotlib.transforms import blended_transform_factory
+
+ linestyles = OrderedDict(
+ [('solid', (0, ())),
+ ('loosely dotted', (0, (1, 10))),
+ ('dotted', (0, (1, 5))),
+ ('densely dotted', (0, (1, 1))),
+
+ ('loosely dashed', (0, (5, 10))),
+ ('dashed', (0, (5, 5))),
+ ('densely dashed', (0, (5, 1))),
+
+ ('loosely dashdotted', (0, (3, 10, 1, 10))),
+ ('dashdotted', (0, (3, 5, 1, 5))),
+ ('densely dashdotted', (0, (3, 1, 1, 1))),
+
+ ('loosely dashdotdotted', (0, (3, 10, 1, 10, 1, 10))),
+ ('dashdotdotted', (0, (3, 5, 1, 5, 1, 5))),
+ ('densely dashdotdotted', (0, (3, 1, 1, 1, 1, 1)))])
+
+ plt.figure(figsize=(10, 6))
+ ax = plt.subplot(1, 1, 1)
+
+ X, Y = np.linspace(0, 100, 10), np.zeros(10)
+ for i, (name, linestyle) in enumerate(linestyles.items()):
+ ax.plot(X, Y + i, linestyle=linestyle, linewidth=1.5, color='black')
+
+ ax.set_ylim(-0.5, len(linestyles) - 0.5)
+ plt.yticks(np.arange(len(linestyles)), linestyles.keys())
+ plt.xticks([])
+
+ # For each line style, add a text annotation with a small offset from
+ # the reference point (0 in Axes coords, y tick value in Data coords).
+ reference_transform = blended_transform_factory(ax.transAxes, ax.transData)
+ for i, (name, linestyle) in enumerate(linestyles.items()):
+ ax.annotate(str(linestyle), xy=(0.0, i), xycoords=reference_transform,
+ xytext=(-6, -12), textcoords='offset points', color="blue",
+ fontsize=8, ha="right", family="monospace")
+
+ plt.tight_layout()
+ return plt.gcf()
+
+def PyplotLinePolyCollection():
+ import matplotlib.pyplot as plt
+ from matplotlib import collections, colors, transforms
+ import numpy as np
+
+ nverts = 50
+ npts = 100
+
+ # Make some spirals
+ r = np.arange(nverts)
+ theta = np.linspace(0, 2 * np.pi, nverts)
+ xx = r * np.sin(theta)
+ yy = r * np.cos(theta)
+ spiral = np.column_stack([xx, yy])
+
+ # Fixing random state for reproducibility
+ rs = np.random.RandomState(19680801)
+
+ # Make some offsets
+ xyo = rs.randn(npts, 2)
+
+ # Make a list of colors cycling through the default series.
+ colors = [colors.to_rgba(c)
+ for c in plt.rcParams['axes.prop_cycle'].by_key()['color']]
+
+ fig, axes = plt.subplots(2, 2)
+ fig.subplots_adjust(top=0.92, left=0.07, right=0.97,
+ hspace=0.3, wspace=0.3)
+ ((ax1, ax2), (ax3, ax4)) = axes # unpack the axes
+
+ col = collections.LineCollection([spiral], offsets=xyo,
+ transOffset=ax1.transData)
+ trans = fig.dpi_scale_trans + transforms.Affine2D().scale(1.0 / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ # Note: the first argument to the collection initializer
+ # must be a list of sequences of x,y tuples; we have only
+ # one sequence, but we still have to put it in a list.
+ ax1.add_collection(col, autolim=True)
+ # autolim=True enables autoscaling. For collections with
+ # offsets like this, it is neither efficient nor accurate,
+ # but it is good enough to generate a plot that you can use
+ # as a starting point. If you know beforehand the range of
+ # x and y that you want to show, it is better to set them
+ # explicitly, leave out the autolim kwarg (or set it to False),
+ # and omit the 'ax1.autoscale_view()' call below.
+
+ # Make a transform for the line segments such that their size is
+ # given in points:
+ col.set_color(colors)
+
+ ax1.autoscale_view() # See comment above, after ax1.add_collection.
+ ax1.set_title('LineCollection using offsets')
+
+ # The same data as above, but fill the curves.
+ col = collections.PolyCollection([spiral], offsets=xyo,
+ transOffset=ax2.transData)
+ trans = transforms.Affine2D().scale(fig.dpi / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ ax2.add_collection(col, autolim=True)
+ col.set_color(colors)
+
+ ax2.autoscale_view()
+ ax2.set_title('PolyCollection using offsets')
+
+ # 7-sided regular polygons
+
+ col = collections.RegularPolyCollection(
+ 7, sizes=np.abs(xx) * 10.0, offsets=xyo, transOffset=ax3.transData)
+ trans = transforms.Affine2D().scale(fig.dpi / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ ax3.add_collection(col, autolim=True)
+ col.set_color(colors)
+ ax3.autoscale_view()
+ ax3.set_title('RegularPolyCollection using offsets')
+
+ # Simulate a series of ocean current profiles, successively
+ # offset by 0.1 m/s so that they form what is sometimes called
+ # a "waterfall" plot or a "stagger" plot.
+
+ nverts = 60
+ ncurves = 20
+ offs = (0.1, 0.0)
+
+ yy = np.linspace(0, 2 * np.pi, nverts)
+ ym = np.max(yy)
+ xx = (0.2 + (ym - yy) / ym) ** 2 * np.cos(yy - 0.4) * 0.5
+ segs = []
+ for i in range(ncurves):
+ xxx = xx + 0.02 * rs.randn(nverts)
+ curve = np.column_stack([xxx, yy * 100])
+ segs.append(curve)
+
+ col = collections.LineCollection(segs, offsets=offs)
+ ax4.add_collection(col, autolim=True)
+ col.set_color(colors)
+ ax4.autoscale_view()
+ ax4.set_title('Successive data offsets')
+ ax4.set_xlabel('Zonal velocity component (m/s)')
+ ax4.set_ylabel('Depth (m)')
+ # Reverse the y-axis so depth increases downward
+ ax4.set_ylim(ax4.get_ylim()[::-1])
+ return fig
+
+def PyplotGGPlotSytleSheet():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ plt.style.use('ggplot')
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ fig, axes = plt.subplots(ncols=2, nrows=2)
+ ax1, ax2, ax3, ax4 = axes.ravel()
+
+ # scatter plot (Note: `plt.scatter` doesn't use default colors)
+ x, y = np.random.normal(size=(2, 200))
+ ax1.plot(x, y, 'o')
+
+ # sinusoidal lines with colors from default color cycle
+ L = 2 * np.pi
+ x = np.linspace(0, L)
+ ncolors = len(plt.rcParams['axes.prop_cycle'])
+ shift = np.linspace(0, L, ncolors, endpoint=False)
+ for s in shift:
+ ax2.plot(x, np.sin(x + s), '-')
+ ax2.margins(0)
+
+ # bar graphs
+ x = np.arange(5)
+ y1, y2 = np.random.randint(1, 25, size=(2, 5))
+ width = 0.25
+ ax3.bar(x, y1, width)
+ ax3.bar(x + width, y2, width,
+ color=list(plt.rcParams['axes.prop_cycle'])[2]['color'])
+ ax3.set_xticks(x + width)
+ ax3.set_xticklabels(['a', 'b', 'c', 'd', 'e'])
+
+ # circles with colors from default color cycle
+ for i, color in enumerate(plt.rcParams['axes.prop_cycle']):
+ xy = np.random.normal(size=2)
+ ax4.add_patch(plt.Circle(xy, radius=0.3, color=color['color']))
+ ax4.axis('equal')
+ ax4.margins(0)
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def PyplotBoxPlot():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ # fake up some data
+ spread = np.random.rand(50) * 100
+ center = np.ones(25) * 50
+ flier_high = np.random.rand(10) * 100 + 100
+ flier_low = np.random.rand(10) * -100
+ data = np.concatenate((spread, center, flier_high, flier_low), 0)
+ fig1, ax1 = plt.subplots()
+ ax1.set_title('Basic Plot')
+ ax1.boxplot(data)
+ return fig1
+
+def PyplotRadarChart():
+ import numpy as np
+
+ import matplotlib.pyplot as plt
+ from matplotlib.path import Path
+ from matplotlib.spines import Spine
+ from matplotlib.projections.polar import PolarAxes
+ from matplotlib.projections import register_projection
+
+ def radar_factory(num_vars, frame='circle'):
+ """Create a radar chart with `num_vars` axes.
+
+ This function creates a RadarAxes projection and registers it.
+
+ Parameters
+ ----------
+ num_vars : int
+ Number of variables for radar chart.
+ frame : {'circle' | 'polygon'}
+ Shape of frame surrounding axes.
+
+ """
+ # calculate evenly-spaced axis angles
+ theta = np.linspace(0, 2 * np.pi, num_vars, endpoint=False)
+
+ def draw_poly_patch(self):
+ # rotate theta such that the first axis is at the top
+ verts = unit_poly_verts(theta + np.pi / 2)
+ return plt.Polygon(verts, closed=True, edgecolor='k')
+
+ def draw_circle_patch(self):
+ # unit circle centered on (0.5, 0.5)
+ return plt.Circle((0.5, 0.5), 0.5)
+
+ patch_dict = {'polygon': draw_poly_patch, 'circle': draw_circle_patch}
+ if frame not in patch_dict:
+ raise ValueError('unknown value for `frame`: %s' % frame)
+
+ class RadarAxes(PolarAxes):
+
+ name = 'radar'
+ # use 1 line segment to connect specified points
+ RESOLUTION = 1
+ # define draw_frame method
+ draw_patch = patch_dict[frame]
+
+ def __init__(self, *args, **kwargs):
+ super(RadarAxes, self).__init__(*args, **kwargs)
+ # rotate plot such that the first axis is at the top
+ self.set_theta_zero_location('N')
+
+ def fill(self, *args, **kwargs):
+ """Override fill so that line is closed by default"""
+ closed = kwargs.pop('closed', True)
+ return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
+
+ def plot(self, *args, **kwargs):
+ """Override plot so that line is closed by default"""
+ lines = super(RadarAxes, self).plot(*args, **kwargs)
+ for line in lines:
+ self._close_line(line)
+
+ def _close_line(self, line):
+ x, y = line.get_data()
+ # FIXME: markers at x[0], y[0] get doubled-up
+ if x[0] != x[-1]:
+ x = np.concatenate((x, [x[0]]))
+ y = np.concatenate((y, [y[0]]))
+ line.set_data(x, y)
+
+ def set_varlabels(self, labels):
+ self.set_thetagrids(np.degrees(theta), labels)
+
+ def _gen_axes_patch(self):
+ return self.draw_patch()
+
+ def _gen_axes_spines(self):
+ if frame == 'circle':
+ return PolarAxes._gen_axes_spines(self)
+ # The following is a hack to get the spines (i.e. the axes frame)
+ # to draw correctly for a polygon frame.
+
+ # spine_type must be 'left', 'right', 'top', 'bottom', or `circle`.
+ spine_type = 'circle'
+ verts = unit_poly_verts(theta + np.pi / 2)
+ # close off polygon by repeating first vertex
+ verts.append(verts[0])
+ path = Path(verts)
+
+ spine = Spine(self, spine_type, path)
+ spine.set_transform(self.transAxes)
+ return {'polar': spine}
+
+ register_projection(RadarAxes)
+ return theta
+
+ def unit_poly_verts(theta):
+ """Return vertices of polygon for subplot axes.
+
+ This polygon is circumscribed by a unit circle centered at (0.5, 0.5)
+ """
+ x0, y0, r = [0.5] * 3
+ verts = [(r * np.cos(t) + x0, r * np.sin(t) + y0) for t in theta]
+ return verts
+
+ def example_data():
+ # The following data is from the Denver Aerosol Sources and Health study.
+ # See doi:10.1016/j.atmosenv.2008.12.017
+ #
+ # The data are pollution source profile estimates for five modeled
+ # pollution sources (e.g., cars, wood-burning, etc) that emit 7-9 chemical
+ # species. The radar charts are experimented with here to see if we can
+ # nicely visualize how the modeled source profiles change across four
+ # scenarios:
+ # 1) No gas-phase species present, just seven particulate counts on
+ # Sulfate
+ # Nitrate
+ # Elemental Carbon (EC)
+ # Organic Carbon fraction 1 (OC)
+ # Organic Carbon fraction 2 (OC2)
+ # Organic Carbon fraction 3 (OC3)
+ # Pyrolized Organic Carbon (OP)
+ # 2)Inclusion of gas-phase specie carbon monoxide (CO)
+ # 3)Inclusion of gas-phase specie ozone (O3).
+ # 4)Inclusion of both gas-phase species is present...
+ data = [
+ ['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OC3', 'OP', 'CO', 'O3'],
+ ('Basecase', [
+ [0.88, 0.01, 0.03, 0.03, 0.00, 0.06, 0.01, 0.00, 0.00],
+ [0.07, 0.95, 0.04, 0.05, 0.00, 0.02, 0.01, 0.00, 0.00],
+ [0.01, 0.02, 0.85, 0.19, 0.05, 0.10, 0.00, 0.00, 0.00],
+ [0.02, 0.01, 0.07, 0.01, 0.21, 0.12, 0.98, 0.00, 0.00],
+ [0.01, 0.01, 0.02, 0.71, 0.74, 0.70, 0.00, 0.00, 0.00]]),
+ ('With CO', [
+ [0.88, 0.02, 0.02, 0.02, 0.00, 0.05, 0.00, 0.05, 0.00],
+ [0.08, 0.94, 0.04, 0.02, 0.00, 0.01, 0.12, 0.04, 0.00],
+ [0.01, 0.01, 0.79, 0.10, 0.00, 0.05, 0.00, 0.31, 0.00],
+ [0.00, 0.02, 0.03, 0.38, 0.31, 0.31, 0.00, 0.59, 0.00],
+ [0.02, 0.02, 0.11, 0.47, 0.69, 0.58, 0.88, 0.00, 0.00]]),
+ ('With O3', [
+ [0.89, 0.01, 0.07, 0.00, 0.00, 0.05, 0.00, 0.00, 0.03],
+ [0.07, 0.95, 0.05, 0.04, 0.00, 0.02, 0.12, 0.00, 0.00],
+ [0.01, 0.02, 0.86, 0.27, 0.16, 0.19, 0.00, 0.00, 0.00],
+ [0.01, 0.03, 0.00, 0.32, 0.29, 0.27, 0.00, 0.00, 0.95],
+ [0.02, 0.00, 0.03, 0.37, 0.56, 0.47, 0.87, 0.00, 0.00]]),
+ ('CO & O3', [
+ [0.87, 0.01, 0.08, 0.00, 0.00, 0.04, 0.00, 0.00, 0.01],
+ [0.09, 0.95, 0.02, 0.03, 0.00, 0.01, 0.13, 0.06, 0.00],
+ [0.01, 0.02, 0.71, 0.24, 0.13, 0.16, 0.00, 0.50, 0.00],
+ [0.01, 0.03, 0.00, 0.28, 0.24, 0.23, 0.00, 0.44, 0.88],
+ [0.02, 0.00, 0.18, 0.45, 0.64, 0.55, 0.86, 0.00, 0.16]])
+ ]
+ return data
+
+ N = 9
+ theta = radar_factory(N, frame='polygon')
+
+ data = example_data()
+ spoke_labels = data.pop(0)
+
+ fig, axes = plt.subplots(figsize=(9, 9), nrows=2, ncols=2,
+ subplot_kw=dict(projection='radar'))
+ fig.subplots_adjust(wspace=0.25, hspace=0.20, top=0.85, bottom=0.05)
+
+ colors = ['b', 'r', 'g', 'm', 'y']
+ # Plot the four cases from the example data on separate axes
+ for ax, (title, case_data) in zip(axes.flatten(), data):
+ ax.set_rgrids([0.2, 0.4, 0.6, 0.8])
+ ax.set_title(title, weight='bold', size='medium', position=(0.5, 1.1),
+ horizontalalignment='center', verticalalignment='center')
+ for d, color in zip(case_data, colors):
+ ax.plot(theta, d, color=color)
+ ax.fill(theta, d, facecolor=color, alpha=0.25)
+ ax.set_varlabels(spoke_labels)
+
+ # add legend relative to top-left plot
+ ax = axes[0, 0]
+ labels = ('Factor 1', 'Factor 2', 'Factor 3', 'Factor 4', 'Factor 5')
+ legend = ax.legend(labels, loc=(0.9, .95),
+ labelspacing=0.1, fontsize='small')
+
+ fig.text(0.5, 0.965, '5-Factor Solution Profiles Across Four Scenarios',
+ horizontalalignment='center', color='black', weight='bold',
+ size='large')
+ return fig
+
+def DifferentScales():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # Create some mock data
+ t = np.arange(0.01, 10.0, 0.01)
+ data1 = np.exp(t)
+ data2 = np.sin(2 * np.pi * t)
+
+ fig, ax1 = plt.subplots()
+
+ color = 'tab:red'
+ ax1.set_xlabel('time (s)')
+ ax1.set_ylabel('exp', color=color)
+ ax1.plot(t, data1, color=color)
+ ax1.tick_params(axis='y', labelcolor=color)
+
+ ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
+
+ color = 'tab:blue'
+ ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
+ ax2.plot(t, data2, color=color)
+ ax2.tick_params(axis='y', labelcolor=color)
+
+ fig.tight_layout() # otherwise the right y-label is slightly clipped
+ return fig
+
+def ExploringNormalizations():
+ import matplotlib.pyplot as plt
+ import matplotlib.colors as mcolors
+ import numpy as np
+ from numpy.random import multivariate_normal
+
+ data = np.vstack([
+ multivariate_normal([10, 10], [[3, 2], [2, 3]], size=100000),
+ multivariate_normal([30, 20], [[2, 3], [1, 3]], size=1000)
+ ])
+
+ gammas = [0.8, 0.5, 0.3]
+
+ fig, axes = plt.subplots(nrows=2, ncols=2)
+
+ axes[0, 0].set_title('Linear normalization')
+ axes[0, 0].hist2d(data[:, 0], data[:, 1], bins=100)
+
+ for ax, gamma in zip(axes.flat[1:], gammas):
+ ax.set_title(r'Power law $(\gamma=%1.1f)$' % gamma)
+ ax.hist2d(data[:, 0], data[:, 1],
+ bins=100, norm=mcolors.PowerNorm(gamma))
+
+ fig.tight_layout()
+ return fig
+
+def PyplotFormatstr():
+
+ def f(t):
+ return np.exp(-t) * np.cos(2*np.pi*t)
+
+ t1 = np.arange(0.0, 5.0, 0.1)
+ t2 = np.arange(0.0, 5.0, 0.02)
+
+ plt.figure(1)
+ plt.subplot(211)
+ plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')
+
+ plt.subplot(212)
+ plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def UnicodeMinus():
+ import numpy as np
+ import matplotlib
+ import matplotlib.pyplot as plt
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ matplotlib.rcParams['axes.unicode_minus'] = False
+ fig, ax = plt.subplots()
+ ax.plot(10 * np.random.randn(100), 10 * np.random.randn(100), 'o')
+ ax.set_title('Using hyphen instead of Unicode minus')
+ return fig
+
+def Subplot3d():
+ from mpl_toolkits.mplot3d.axes3d import Axes3D
+ from matplotlib import cm
+ # from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
+ import matplotlib.pyplot as plt
+ import numpy as np
+
+ fig = plt.figure()
+
+ ax = fig.add_subplot(1, 2, 1, projection='3d')
+ X = np.arange(-5, 5, 0.25)
+ Y = np.arange(-5, 5, 0.25)
+ X, Y = np.meshgrid(X, Y)
+ R = np.sqrt(X ** 2 + Y ** 2)
+ Z = np.sin(R)
+ surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet,
+ linewidth=0, antialiased=False)
+ ax.set_zlim3d(-1.01, 1.01)
+
+ # ax.w_zaxis.set_major_locator(LinearLocator(10))
+ # ax.w_zaxis.set_major_formatter(FormatStrFormatter('%.03f'))
+
+ fig.colorbar(surf, shrink=0.5, aspect=5)
+
+ from mpl_toolkits.mplot3d.axes3d import get_test_data
+ ax = fig.add_subplot(1, 2, 2, projection='3d')
+ X, Y, Z = get_test_data(0.05)
+ ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
+ return fig
+
+def PyplotScales():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ from matplotlib.ticker import NullFormatter # useful for `logit` scale
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ # make up some data in the interval ]0, 1[
+ y = np.random.normal(loc=0.5, scale=0.4, size=1000)
+ y = y[(y > 0) & (y < 1)]
+ y.sort()
+ x = np.arange(len(y))
+
+ # plot with various axes scales
+ plt.figure(1)
+
+ # linear
+ plt.subplot(221)
+ plt.plot(x, y)
+ plt.yscale('linear')
+ plt.title('linear')
+ plt.grid(True)
+
+ # log
+ plt.subplot(222)
+ plt.plot(x, y)
+ plt.yscale('log')
+ plt.title('log')
+ plt.grid(True)
+
+ # symmetric log
+ plt.subplot(223)
+ plt.plot(x, y - y.mean())
+ plt.yscale('symlog', linthreshy=0.01)
+ plt.title('symlog')
+ plt.grid(True)
+
+ # logit
+ plt.subplot(224)
+ plt.plot(x, y)
+ plt.yscale('logit')
+ plt.title('logit')
+ plt.grid(True)
+ # Format the minor tick labels of the y-axis into empty strings with
+ # `NullFormatter`, to avoid cumbering the axis with too many labels.
+ plt.gca().yaxis.set_minor_formatter(NullFormatter())
+ # Adjust the subplot layout, because the logit one may take more space
+ # than usual, due to y-tick labels like "1 - 10^{-3}"
+ plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
+ wspace=0.35)
+ return plt.gcf()
+
+
+def AxesGrid():
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from mpl_toolkits.axes_grid1.axes_rgb import RGBAxes
+
+ def get_demo_image():
+ # prepare image
+ delta = 0.5
+
+ extent = (-3, 4, -4, 3)
+ x = np.arange(-3.0, 4.001, delta)
+ y = np.arange(-4.0, 3.001, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-X ** 2 - Y ** 2)
+ Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
+ Z = (Z1 - Z2) * 2
+
+ return Z, extent
+
+ def get_rgb():
+ Z, extent = get_demo_image()
+
+ Z[Z < 0] = 0.
+ Z = Z / Z.max()
+
+ R = Z[:13, :13]
+ G = Z[2:, 2:]
+ B = Z[:13, 2:]
+
+ return R, G, B
+
+ fig = plt.figure(1)
+ ax = RGBAxes(fig, [0.1, 0.1, 0.8, 0.8])
+
+ r, g, b = get_rgb()
+ kwargs = dict(origin="lower", interpolation="nearest")
+ ax.imshow_rgb(r, g, b, **kwargs)
+
+ ax.RGB.set_xlim(0., 9.5)
+ ax.RGB.set_ylim(0.9, 10.6)
+
+ plt.draw()
+ return plt.gcf()
+
+
+
+# The magic function that makes it possible.... glues together tkinter and pyplot using Canvas Widget
+def draw_figure(canvas, figure):
+ figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
+ figure_canvas_agg.draw()
+ figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
+ return figure_canvas_agg
+
+def delete_figure_agg(figure_agg):
+ figure_agg.get_tk_widget().forget()
+ plt.close('all')
+
+# -------------------------------- GUI Starts Here -------------------------------#
+# fig = your figure you want to display. Assumption is that 'fig' holds the #
+# information to display. #
+# --------------------------------------------------------------------------------#
+
+fig_dict = {'Pyplot Simple':PyplotSimple, 'Pyplot Formatstr':PyplotFormatstr,'PyPlot Three':Subplot3d,
+ 'Unicode Minus': UnicodeMinus, 'Pyplot Scales' : PyplotScales, 'Axes Grid' : AxesGrid,
+ 'Exploring Normalizations' : ExploringNormalizations, 'Different Scales' : DifferentScales,
+ 'Pyplot Box Plot' : PyplotBoxPlot, 'Pyplot ggplot Style Sheet' : PyplotGGPlotSytleSheet,
+ 'Pyplot Line Poly Collection' : PyplotLinePolyCollection, 'Pyplot Line Styles' : PyplotLineStyles,
+ 'Pyplot Scatter With Legend' :PyplotScatterWithLegend, 'Artist Customized Box Plots' : PyplotArtistBoxPlots,
+ 'Artist Customized Box Plots 2' : ArtistBoxplot2, 'Pyplot Histogram' : PyplotHistogram}
+
+sg.ChangeLookAndFeel('LightGreen')
+
+figure_w, figure_h = 650, 650
+# define the form layout
+listbox_values = list(fig_dict)
+col_listbox = [[sg.Listbox(values=listbox_values, enable_events=True, size=(28, len(listbox_values)), key='-LISTBOX-')],
+ [sg.T(' ' * 12), sg.Exit(size=(5, 2))]]
+
+layout = [[sg.Text('Matplotlib Plot Test', font=('current 18'))],
+ [sg.Column(col_listbox, pad=(5, (3, 330))), sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-') ,
+ sg.Multiline(size=(70, 35), pad=(5, (3, 90)), key='-MULTILINE-')],]
+
+# create the form and show it without the plot
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, grab_anywhere=False, finalize=True)
+figure_agg = None
+# The GUI Event Loop
+while True:
+ event, values = window.read()
+ # print(event, values) # helps greatly when debugging
+ if event in (None, 'Exit'): # if user closed window or clicked Exit button
+ break
+ if figure_agg:
+ # ** IMPORTANT ** Clean up previous drawing before drawing again
+ delete_figure_agg(figure_agg)
+ choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
+ func = fig_dict[choice] # get function to call from the dictionary
+ window['-MULTILINE-'].Update(inspect.getsource(func)) # show source code to function in multiline
+ fig = func() # call function to get the figure
+ figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Matplotlib_Browser_Paned.py b/DemoPrograms old/Demo_Matplotlib_Browser_Paned.py
new file mode 100644
index 00000000..3a61374d
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Browser_Paned.py
@@ -0,0 +1,889 @@
+#!/usr/bin/env python
+#!/usr/bin/env python
+import PySimpleGUI as sg
+import matplotlib
+matplotlib.use('TkAgg')
+import inspect
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+
+"""
+Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
+
+Basic steps are:
+ * Create a Canvas Element
+ * Layout form
+ * Display form (NON BLOCKING)
+ * Draw plots onto convas
+ * Display form (BLOCKING)
+"""
+
+
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+
+def PyplotSimple():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # evenly sampled time at 200ms intervals
+ t = np.arange(0., 5., 0.2)
+
+ # red dashes, blue squares and green triangles
+ plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
+
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def PyplotHistogram():
+ """
+ =============================================================
+ Demo of the histogram (hist) function with multiple data sets
+ =============================================================
+
+ Plot histogram with multiple sample sets and demonstrate:
+
+ * Use of legend with multiple sample sets
+ * Stacked bars
+ * Step curve with no fill
+ * Data sets of different sample sizes
+
+ Selecting different bin counts and sizes can significantly affect the
+ shape of a histogram. The Astropy docs have a great section on how to
+ select these parameters:
+ http://docs.astropy.org/en/stable/visualization/histogram.html
+ """
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ np.random.seed(0)
+
+ n_bins = 10
+ x = np.random.randn(1000, 3)
+
+ fig, axes = plt.subplots(nrows=2, ncols=2)
+ ax0, ax1, ax2, ax3 = axes.flatten()
+
+ colors = ['red', 'tan', 'lime']
+ ax0.hist(x, n_bins, normed=1, histtype='bar', color=colors, label=colors)
+ ax0.legend(prop={'size': 10})
+ ax0.set_title('bars with legend')
+
+ ax1.hist(x, n_bins, normed=1, histtype='bar', stacked=True)
+ ax1.set_title('stacked bar')
+
+ ax2.hist(x, n_bins, histtype='step', stacked=True, fill=False)
+ ax2.set_title('stack step (unfilled)')
+
+ # Make a multiple-histogram of data-sets with different length.
+ x_multi = [np.random.randn(n) for n in [10000, 5000, 2000]]
+ ax3.hist(x_multi, n_bins, histtype='bar')
+ ax3.set_title('different sample sizes')
+
+ fig.tight_layout()
+ return fig
+
+def PyplotArtistBoxPlots():
+ """
+ =========================================
+ Demo of artist customization in box plots
+ =========================================
+
+ This example demonstrates how to use the various kwargs
+ to fully customize box plots. The first figure demonstrates
+ how to remove and add individual components (note that the
+ mean is the only value not shown by default). The second
+ figure demonstrates how the styles of the artists can
+ be customized. It also demonstrates how to set the limit
+ of the whiskers to specific percentiles (lower right axes)
+
+ A good general reference on boxplots and their history can be found
+ here: http://vita.had.co.nz/papers/boxplots.pdf
+
+ """
+
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # fake data
+ np.random.seed(937)
+ data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
+ labels = list('ABCD')
+ fs = 10 # fontsize
+
+ # demonstrate how to toggle the display of different elements:
+ fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6, 6), sharey=True)
+ axes[0, 0].boxplot(data, labels=labels)
+ axes[0, 0].set_title('Default', fontsize=fs)
+
+ axes[0, 1].boxplot(data, labels=labels, showmeans=True)
+ axes[0, 1].set_title('showmeans=True', fontsize=fs)
+
+ axes[0, 2].boxplot(data, labels=labels, showmeans=True, meanline=True)
+ axes[0, 2].set_title('showmeans=True,\nmeanline=True', fontsize=fs)
+
+ axes[1, 0].boxplot(data, labels=labels, showbox=False, showcaps=False)
+ tufte_title = 'Tufte Style \n(showbox=False,\nshowcaps=False)'
+ axes[1, 0].set_title(tufte_title, fontsize=fs)
+
+ axes[1, 1].boxplot(data, labels=labels, notch=True, bootstrap=10000)
+ axes[1, 1].set_title('notch=True,\nbootstrap=10000', fontsize=fs)
+
+ axes[1, 2].boxplot(data, labels=labels, showfliers=False)
+ axes[1, 2].set_title('showfliers=False', fontsize=fs)
+
+ for ax in axes.flatten():
+ ax.set_yscale('log')
+ ax.set_yticklabels([])
+
+ fig.subplots_adjust(hspace=0.4)
+ return fig
+
+def ArtistBoxplot2():
+
+ # fake data
+ np.random.seed(937)
+ data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
+ labels = list('ABCD')
+ fs = 10 # fontsize
+
+ # demonstrate how to customize the display different elements:
+ boxprops = dict(linestyle='--', linewidth=3, color='darkgoldenrod')
+ flierprops = dict(marker='o', markerfacecolor='green', markersize=12,
+ linestyle='none')
+ medianprops = dict(linestyle='-.', linewidth=2.5, color='firebrick')
+ meanpointprops = dict(marker='D', markeredgecolor='black',
+ markerfacecolor='firebrick')
+ meanlineprops = dict(linestyle='--', linewidth=2.5, color='purple')
+
+ fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(6, 6), sharey=True)
+ axes[0, 0].boxplot(data, boxprops=boxprops)
+ axes[0, 0].set_title('Custom boxprops', fontsize=fs)
+
+ axes[0, 1].boxplot(data, flierprops=flierprops, medianprops=medianprops)
+ axes[0, 1].set_title('Custom medianprops\nand flierprops', fontsize=fs)
+
+ axes[0, 2].boxplot(data, whis='range')
+ axes[0, 2].set_title('whis="range"', fontsize=fs)
+
+ axes[1, 0].boxplot(data, meanprops=meanpointprops, meanline=False,
+ showmeans=True)
+ axes[1, 0].set_title('Custom mean\nas point', fontsize=fs)
+
+ axes[1, 1].boxplot(data, meanprops=meanlineprops, meanline=True,
+ showmeans=True)
+ axes[1, 1].set_title('Custom mean\nas line', fontsize=fs)
+
+ axes[1, 2].boxplot(data, whis=[15, 85])
+ axes[1, 2].set_title('whis=[15, 85]\n#percentiles', fontsize=fs)
+
+ for ax in axes.flatten():
+ ax.set_yscale('log')
+ ax.set_yticklabels([])
+
+ fig.suptitle("I never said they'd be pretty")
+ fig.subplots_adjust(hspace=0.4)
+ return fig
+
+def PyplotScatterWithLegend():
+ import matplotlib.pyplot as plt
+ from numpy.random import rand
+
+ fig, ax = plt.subplots()
+ for color in ['red', 'green', 'blue']:
+ n = 750
+ x, y = rand(2, n)
+ scale = 200.0 * rand(n)
+ ax.scatter(x, y, c=color, s=scale, label=color,
+ alpha=0.3, edgecolors='none')
+
+ ax.legend()
+ ax.grid(True)
+ return fig
+
+def PyplotLineStyles():
+ """
+ ==========
+ Linestyles
+ ==========
+
+ This examples showcases different linestyles copying those of Tikz/PGF.
+ """
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from collections import OrderedDict
+ from matplotlib.transforms import blended_transform_factory
+
+ linestyles = OrderedDict(
+ [('solid', (0, ())),
+ ('loosely dotted', (0, (1, 10))),
+ ('dotted', (0, (1, 5))),
+ ('densely dotted', (0, (1, 1))),
+
+ ('loosely dashed', (0, (5, 10))),
+ ('dashed', (0, (5, 5))),
+ ('densely dashed', (0, (5, 1))),
+
+ ('loosely dashdotted', (0, (3, 10, 1, 10))),
+ ('dashdotted', (0, (3, 5, 1, 5))),
+ ('densely dashdotted', (0, (3, 1, 1, 1))),
+
+ ('loosely dashdotdotted', (0, (3, 10, 1, 10, 1, 10))),
+ ('dashdotdotted', (0, (3, 5, 1, 5, 1, 5))),
+ ('densely dashdotdotted', (0, (3, 1, 1, 1, 1, 1)))])
+
+ plt.figure(figsize=(10, 6))
+ ax = plt.subplot(1, 1, 1)
+
+ X, Y = np.linspace(0, 100, 10), np.zeros(10)
+ for i, (name, linestyle) in enumerate(linestyles.items()):
+ ax.plot(X, Y + i, linestyle=linestyle, linewidth=1.5, color='black')
+
+ ax.set_ylim(-0.5, len(linestyles) - 0.5)
+ plt.yticks(np.arange(len(linestyles)), linestyles.keys())
+ plt.xticks([])
+
+ # For each line style, add a text annotation with a small offset from
+ # the reference point (0 in Axes coords, y tick value in Data coords).
+ reference_transform = blended_transform_factory(ax.transAxes, ax.transData)
+ for i, (name, linestyle) in enumerate(linestyles.items()):
+ ax.annotate(str(linestyle), xy=(0.0, i), xycoords=reference_transform,
+ xytext=(-6, -12), textcoords='offset points', color="blue",
+ fontsize=8, ha="right", family="monospace")
+
+ plt.tight_layout()
+ return plt.gcf()
+
+def PyplotLinePolyCollection():
+ import matplotlib.pyplot as plt
+ from matplotlib import collections, colors, transforms
+ import numpy as np
+
+ nverts = 50
+ npts = 100
+
+ # Make some spirals
+ r = np.arange(nverts)
+ theta = np.linspace(0, 2 * np.pi, nverts)
+ xx = r * np.sin(theta)
+ yy = r * np.cos(theta)
+ spiral = np.column_stack([xx, yy])
+
+ # Fixing random state for reproducibility
+ rs = np.random.RandomState(19680801)
+
+ # Make some offsets
+ xyo = rs.randn(npts, 2)
+
+ # Make a list of colors cycling through the default series.
+ colors = [colors.to_rgba(c)
+ for c in plt.rcParams['axes.prop_cycle'].by_key()['color']]
+
+ fig, axes = plt.subplots(2, 2)
+ fig.subplots_adjust(top=0.92, left=0.07, right=0.97,
+ hspace=0.3, wspace=0.3)
+ ((ax1, ax2), (ax3, ax4)) = axes # unpack the axes
+
+ col = collections.LineCollection([spiral], offsets=xyo,
+ transOffset=ax1.transData)
+ trans = fig.dpi_scale_trans + transforms.Affine2D().scale(1.0 / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ # Note: the first argument to the collection initializer
+ # must be a list of sequences of x,y tuples; we have only
+ # one sequence, but we still have to put it in a list.
+ ax1.add_collection(col, autolim=True)
+ # autolim=True enables autoscaling. For collections with
+ # offsets like this, it is neither efficient nor accurate,
+ # but it is good enough to generate a plot that you can use
+ # as a starting point. If you know beforehand the range of
+ # x and y that you want to show, it is better to set them
+ # explicitly, leave out the autolim kwarg (or set it to False),
+ # and omit the 'ax1.autoscale_view()' call below.
+
+ # Make a transform for the line segments such that their size is
+ # given in points:
+ col.set_color(colors)
+
+ ax1.autoscale_view() # See comment above, after ax1.add_collection.
+ ax1.set_title('LineCollection using offsets')
+
+ # The same data as above, but fill the curves.
+ col = collections.PolyCollection([spiral], offsets=xyo,
+ transOffset=ax2.transData)
+ trans = transforms.Affine2D().scale(fig.dpi / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ ax2.add_collection(col, autolim=True)
+ col.set_color(colors)
+
+ ax2.autoscale_view()
+ ax2.set_title('PolyCollection using offsets')
+
+ # 7-sided regular polygons
+
+ col = collections.RegularPolyCollection(
+ 7, sizes=np.abs(xx) * 10.0, offsets=xyo, transOffset=ax3.transData)
+ trans = transforms.Affine2D().scale(fig.dpi / 72.0)
+ col.set_transform(trans) # the points to pixels transform
+ ax3.add_collection(col, autolim=True)
+ col.set_color(colors)
+ ax3.autoscale_view()
+ ax3.set_title('RegularPolyCollection using offsets')
+
+ # Simulate a series of ocean current profiles, successively
+ # offset by 0.1 m/s so that they form what is sometimes called
+ # a "waterfall" plot or a "stagger" plot.
+
+ nverts = 60
+ ncurves = 20
+ offs = (0.1, 0.0)
+
+ yy = np.linspace(0, 2 * np.pi, nverts)
+ ym = np.max(yy)
+ xx = (0.2 + (ym - yy) / ym) ** 2 * np.cos(yy - 0.4) * 0.5
+ segs = []
+ for i in range(ncurves):
+ xxx = xx + 0.02 * rs.randn(nverts)
+ curve = np.column_stack([xxx, yy * 100])
+ segs.append(curve)
+
+ col = collections.LineCollection(segs, offsets=offs)
+ ax4.add_collection(col, autolim=True)
+ col.set_color(colors)
+ ax4.autoscale_view()
+ ax4.set_title('Successive data offsets')
+ ax4.set_xlabel('Zonal velocity component (m/s)')
+ ax4.set_ylabel('Depth (m)')
+ # Reverse the y-axis so depth increases downward
+ ax4.set_ylim(ax4.get_ylim()[::-1])
+ return fig
+
+def PyplotGGPlotSytleSheet():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ plt.style.use('ggplot')
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ fig, axes = plt.subplots(ncols=2, nrows=2)
+ ax1, ax2, ax3, ax4 = axes.ravel()
+
+ # scatter plot (Note: `plt.scatter` doesn't use default colors)
+ x, y = np.random.normal(size=(2, 200))
+ ax1.plot(x, y, 'o')
+
+ # sinusoidal lines with colors from default color cycle
+ L = 2 * np.pi
+ x = np.linspace(0, L)
+ ncolors = len(plt.rcParams['axes.prop_cycle'])
+ shift = np.linspace(0, L, ncolors, endpoint=False)
+ for s in shift:
+ ax2.plot(x, np.sin(x + s), '-')
+ ax2.margins(0)
+
+ # bar graphs
+ x = np.arange(5)
+ y1, y2 = np.random.randint(1, 25, size=(2, 5))
+ width = 0.25
+ ax3.bar(x, y1, width)
+ ax3.bar(x + width, y2, width,
+ color=list(plt.rcParams['axes.prop_cycle'])[2]['color'])
+ ax3.set_xticks(x + width)
+ ax3.set_xticklabels(['a', 'b', 'c', 'd', 'e'])
+
+ # circles with colors from default color cycle
+ for i, color in enumerate(plt.rcParams['axes.prop_cycle']):
+ xy = np.random.normal(size=2)
+ ax4.add_patch(plt.Circle(xy, radius=0.3, color=color['color']))
+ ax4.axis('equal')
+ ax4.margins(0)
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def PyplotBoxPlot():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ # fake up some data
+ spread = np.random.rand(50) * 100
+ center = np.ones(25) * 50
+ flier_high = np.random.rand(10) * 100 + 100
+ flier_low = np.random.rand(10) * -100
+ data = np.concatenate((spread, center, flier_high, flier_low), 0)
+ fig1, ax1 = plt.subplots()
+ ax1.set_title('Basic Plot')
+ ax1.boxplot(data)
+ return fig1
+
+def PyplotRadarChart():
+ import numpy as np
+
+ import matplotlib.pyplot as plt
+ from matplotlib.path import Path
+ from matplotlib.spines import Spine
+ from matplotlib.projections.polar import PolarAxes
+ from matplotlib.projections import register_projection
+
+ def radar_factory(num_vars, frame='circle'):
+ """Create a radar chart with `num_vars` axes.
+
+ This function creates a RadarAxes projection and registers it.
+
+ Parameters
+ ----------
+ num_vars : int
+ Number of variables for radar chart.
+ frame : {'circle' | 'polygon'}
+ Shape of frame surrounding axes.
+
+ """
+ # calculate evenly-spaced axis angles
+ theta = np.linspace(0, 2 * np.pi, num_vars, endpoint=False)
+
+ def draw_poly_patch(self):
+ # rotate theta such that the first axis is at the top
+ verts = unit_poly_verts(theta + np.pi / 2)
+ return plt.Polygon(verts, closed=True, edgecolor='k')
+
+ def draw_circle_patch(self):
+ # unit circle centered on (0.5, 0.5)
+ return plt.Circle((0.5, 0.5), 0.5)
+
+ patch_dict = {'polygon': draw_poly_patch, 'circle': draw_circle_patch}
+ if frame not in patch_dict:
+ raise ValueError('unknown value for `frame`: %s' % frame)
+
+ class RadarAxes(PolarAxes):
+
+ name = 'radar'
+ # use 1 line segment to connect specified points
+ RESOLUTION = 1
+ # define draw_frame method
+ draw_patch = patch_dict[frame]
+
+ def __init__(self, *args, **kwargs):
+ super(RadarAxes, self).__init__(*args, **kwargs)
+ # rotate plot such that the first axis is at the top
+ self.set_theta_zero_location('N')
+
+ def fill(self, *args, **kwargs):
+ """Override fill so that line is closed by default"""
+ closed = kwargs.pop('closed', True)
+ return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
+
+ def plot(self, *args, **kwargs):
+ """Override plot so that line is closed by default"""
+ lines = super(RadarAxes, self).plot(*args, **kwargs)
+ for line in lines:
+ self._close_line(line)
+
+ def _close_line(self, line):
+ x, y = line.get_data()
+ # FIXME: markers at x[0], y[0] get doubled-up
+ if x[0] != x[-1]:
+ x = np.concatenate((x, [x[0]]))
+ y = np.concatenate((y, [y[0]]))
+ line.set_data(x, y)
+
+ def set_varlabels(self, labels):
+ self.set_thetagrids(np.degrees(theta), labels)
+
+ def _gen_axes_patch(self):
+ return self.draw_patch()
+
+ def _gen_axes_spines(self):
+ if frame == 'circle':
+ return PolarAxes._gen_axes_spines(self)
+ # The following is a hack to get the spines (i.e. the axes frame)
+ # to draw correctly for a polygon frame.
+
+ # spine_type must be 'left', 'right', 'top', 'bottom', or `circle`.
+ spine_type = 'circle'
+ verts = unit_poly_verts(theta + np.pi / 2)
+ # close off polygon by repeating first vertex
+ verts.append(verts[0])
+ path = Path(verts)
+
+ spine = Spine(self, spine_type, path)
+ spine.set_transform(self.transAxes)
+ return {'polar': spine}
+
+ register_projection(RadarAxes)
+ return theta
+
+ def unit_poly_verts(theta):
+ """Return vertices of polygon for subplot axes.
+
+ This polygon is circumscribed by a unit circle centered at (0.5, 0.5)
+ """
+ x0, y0, r = [0.5] * 3
+ verts = [(r * np.cos(t) + x0, r * np.sin(t) + y0) for t in theta]
+ return verts
+
+ def example_data():
+ # The following data is from the Denver Aerosol Sources and Health study.
+ # See doi:10.1016/j.atmosenv.2008.12.017
+ #
+ # The data are pollution source profile estimates for five modeled
+ # pollution sources (e.g., cars, wood-burning, etc) that emit 7-9 chemical
+ # species. The radar charts are experimented with here to see if we can
+ # nicely visualize how the modeled source profiles change across four
+ # scenarios:
+ # 1) No gas-phase species present, just seven particulate counts on
+ # Sulfate
+ # Nitrate
+ # Elemental Carbon (EC)
+ # Organic Carbon fraction 1 (OC)
+ # Organic Carbon fraction 2 (OC2)
+ # Organic Carbon fraction 3 (OC3)
+ # Pyrolized Organic Carbon (OP)
+ # 2)Inclusion of gas-phase specie carbon monoxide (CO)
+ # 3)Inclusion of gas-phase specie ozone (O3).
+ # 4)Inclusion of both gas-phase species is present...
+ data = [
+ ['Sulfate', 'Nitrate', 'EC', 'OC1', 'OC2', 'OC3', 'OP', 'CO', 'O3'],
+ ('Basecase', [
+ [0.88, 0.01, 0.03, 0.03, 0.00, 0.06, 0.01, 0.00, 0.00],
+ [0.07, 0.95, 0.04, 0.05, 0.00, 0.02, 0.01, 0.00, 0.00],
+ [0.01, 0.02, 0.85, 0.19, 0.05, 0.10, 0.00, 0.00, 0.00],
+ [0.02, 0.01, 0.07, 0.01, 0.21, 0.12, 0.98, 0.00, 0.00],
+ [0.01, 0.01, 0.02, 0.71, 0.74, 0.70, 0.00, 0.00, 0.00]]),
+ ('With CO', [
+ [0.88, 0.02, 0.02, 0.02, 0.00, 0.05, 0.00, 0.05, 0.00],
+ [0.08, 0.94, 0.04, 0.02, 0.00, 0.01, 0.12, 0.04, 0.00],
+ [0.01, 0.01, 0.79, 0.10, 0.00, 0.05, 0.00, 0.31, 0.00],
+ [0.00, 0.02, 0.03, 0.38, 0.31, 0.31, 0.00, 0.59, 0.00],
+ [0.02, 0.02, 0.11, 0.47, 0.69, 0.58, 0.88, 0.00, 0.00]]),
+ ('With O3', [
+ [0.89, 0.01, 0.07, 0.00, 0.00, 0.05, 0.00, 0.00, 0.03],
+ [0.07, 0.95, 0.05, 0.04, 0.00, 0.02, 0.12, 0.00, 0.00],
+ [0.01, 0.02, 0.86, 0.27, 0.16, 0.19, 0.00, 0.00, 0.00],
+ [0.01, 0.03, 0.00, 0.32, 0.29, 0.27, 0.00, 0.00, 0.95],
+ [0.02, 0.00, 0.03, 0.37, 0.56, 0.47, 0.87, 0.00, 0.00]]),
+ ('CO & O3', [
+ [0.87, 0.01, 0.08, 0.00, 0.00, 0.04, 0.00, 0.00, 0.01],
+ [0.09, 0.95, 0.02, 0.03, 0.00, 0.01, 0.13, 0.06, 0.00],
+ [0.01, 0.02, 0.71, 0.24, 0.13, 0.16, 0.00, 0.50, 0.00],
+ [0.01, 0.03, 0.00, 0.28, 0.24, 0.23, 0.00, 0.44, 0.88],
+ [0.02, 0.00, 0.18, 0.45, 0.64, 0.55, 0.86, 0.00, 0.16]])
+ ]
+ return data
+
+ N = 9
+ theta = radar_factory(N, frame='polygon')
+
+ data = example_data()
+ spoke_labels = data.pop(0)
+
+ fig, axes = plt.subplots(figsize=(9, 9), nrows=2, ncols=2,
+ subplot_kw=dict(projection='radar'))
+ fig.subplots_adjust(wspace=0.25, hspace=0.20, top=0.85, bottom=0.05)
+
+ colors = ['b', 'r', 'g', 'm', 'y']
+ # Plot the four cases from the example data on separate axes
+ for ax, (title, case_data) in zip(axes.flatten(), data):
+ ax.set_rgrids([0.2, 0.4, 0.6, 0.8])
+ ax.set_title(title, weight='bold', size='medium', position=(0.5, 1.1),
+ horizontalalignment='center', verticalalignment='center')
+ for d, color in zip(case_data, colors):
+ ax.plot(theta, d, color=color)
+ ax.fill(theta, d, facecolor=color, alpha=0.25)
+ ax.set_varlabels(spoke_labels)
+
+ # add legend relative to top-left plot
+ ax = axes[0, 0]
+ labels = ('Factor 1', 'Factor 2', 'Factor 3', 'Factor 4', 'Factor 5')
+ legend = ax.legend(labels, loc=(0.9, .95),
+ labelspacing=0.1, fontsize='small')
+
+ fig.text(0.5, 0.965, '5-Factor Solution Profiles Across Four Scenarios',
+ horizontalalignment='center', color='black', weight='bold',
+ size='large')
+ return fig
+
+def DifferentScales():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ # Create some mock data
+ t = np.arange(0.01, 10.0, 0.01)
+ data1 = np.exp(t)
+ data2 = np.sin(2 * np.pi * t)
+
+ fig, ax1 = plt.subplots()
+
+ color = 'tab:red'
+ ax1.set_xlabel('time (s)')
+ ax1.set_ylabel('exp', color=color)
+ ax1.plot(t, data1, color=color)
+ ax1.tick_params(axis='y', labelcolor=color)
+
+ ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
+
+ color = 'tab:blue'
+ ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
+ ax2.plot(t, data2, color=color)
+ ax2.tick_params(axis='y', labelcolor=color)
+
+ fig.tight_layout() # otherwise the right y-label is slightly clipped
+ return fig
+
+def ExploringNormalizations():
+ import matplotlib.pyplot as plt
+ import matplotlib.colors as mcolors
+ import numpy as np
+ from numpy.random import multivariate_normal
+
+ data = np.vstack([
+ multivariate_normal([10, 10], [[3, 2], [2, 3]], size=100000),
+ multivariate_normal([30, 20], [[2, 3], [1, 3]], size=1000)
+ ])
+
+ gammas = [0.8, 0.5, 0.3]
+
+ fig, axes = plt.subplots(nrows=2, ncols=2)
+
+ axes[0, 0].set_title('Linear normalization')
+ axes[0, 0].hist2d(data[:, 0], data[:, 1], bins=100)
+
+ for ax, gamma in zip(axes.flat[1:], gammas):
+ ax.set_title(r'Power law $(\gamma=%1.1f)$' % gamma)
+ ax.hist2d(data[:, 0], data[:, 1],
+ bins=100, norm=mcolors.PowerNorm(gamma))
+
+ fig.tight_layout()
+ return fig
+
+def PyplotFormatstr():
+
+ def f(t):
+ return np.exp(-t) * np.cos(2*np.pi*t)
+
+ t1 = np.arange(0.0, 5.0, 0.1)
+ t2 = np.arange(0.0, 5.0, 0.02)
+
+ plt.figure(1)
+ plt.subplot(211)
+ plt.plot(t1, f(t1), 'bo', t2, f(t2), 'k')
+
+ plt.subplot(212)
+ plt.plot(t2, np.cos(2*np.pi*t2), 'r--')
+ fig = plt.gcf() # get the figure to show
+ return fig
+
+def UnicodeMinus():
+ import numpy as np
+ import matplotlib
+ import matplotlib.pyplot as plt
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ matplotlib.rcParams['axes.unicode_minus'] = False
+ fig, ax = plt.subplots()
+ ax.plot(10 * np.random.randn(100), 10 * np.random.randn(100), 'o')
+ ax.set_title('Using hyphen instead of Unicode minus')
+ return fig
+
+def Subplot3d():
+ from mpl_toolkits.mplot3d.axes3d import Axes3D
+ from matplotlib import cm
+ # from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
+ import matplotlib.pyplot as plt
+ import numpy as np
+
+ fig = plt.figure()
+
+ ax = fig.add_subplot(1, 2, 1, projection='3d')
+ X = np.arange(-5, 5, 0.25)
+ Y = np.arange(-5, 5, 0.25)
+ X, Y = np.meshgrid(X, Y)
+ R = np.sqrt(X ** 2 + Y ** 2)
+ Z = np.sin(R)
+ surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet,
+ linewidth=0, antialiased=False)
+ ax.set_zlim3d(-1.01, 1.01)
+
+ # ax.w_zaxis.set_major_locator(LinearLocator(10))
+ # ax.w_zaxis.set_major_formatter(FormatStrFormatter('%.03f'))
+
+ fig.colorbar(surf, shrink=0.5, aspect=5)
+
+ from mpl_toolkits.mplot3d.axes3d import get_test_data
+ ax = fig.add_subplot(1, 2, 2, projection='3d')
+ X, Y, Z = get_test_data(0.05)
+ ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
+ return fig
+
+def PyplotScales():
+ import numpy as np
+ import matplotlib.pyplot as plt
+
+ from matplotlib.ticker import NullFormatter # useful for `logit` scale
+
+ # Fixing random state for reproducibility
+ np.random.seed(19680801)
+
+ # make up some data in the interval ]0, 1[
+ y = np.random.normal(loc=0.5, scale=0.4, size=1000)
+ y = y[(y > 0) & (y < 1)]
+ y.sort()
+ x = np.arange(len(y))
+
+ # plot with various axes scales
+ plt.figure(1)
+
+ # linear
+ plt.subplot(221)
+ plt.plot(x, y)
+ plt.yscale('linear')
+ plt.title('linear')
+ plt.grid(True)
+
+ # log
+ plt.subplot(222)
+ plt.plot(x, y)
+ plt.yscale('log')
+ plt.title('log')
+ plt.grid(True)
+
+ # symmetric log
+ plt.subplot(223)
+ plt.plot(x, y - y.mean())
+ plt.yscale('symlog', linthreshy=0.01)
+ plt.title('symlog')
+ plt.grid(True)
+
+ # logit
+ plt.subplot(224)
+ plt.plot(x, y)
+ plt.yscale('logit')
+ plt.title('logit')
+ plt.grid(True)
+ # Format the minor tick labels of the y-axis into empty strings with
+ # `NullFormatter`, to avoid cumbering the axis with too many labels.
+ plt.gca().yaxis.set_minor_formatter(NullFormatter())
+ # Adjust the subplot layout, because the logit one may take more space
+ # than usual, due to y-tick labels like "1 - 10^{-3}"
+ plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
+ wspace=0.35)
+ return plt.gcf()
+
+
+def AxesGrid():
+ import numpy as np
+ import matplotlib.pyplot as plt
+ from mpl_toolkits.axes_grid1.axes_rgb import RGBAxes
+
+ def get_demo_image():
+ # prepare image
+ delta = 0.5
+
+ extent = (-3, 4, -4, 3)
+ x = np.arange(-3.0, 4.001, delta)
+ y = np.arange(-4.0, 3.001, delta)
+ X, Y = np.meshgrid(x, y)
+ Z1 = np.exp(-X ** 2 - Y ** 2)
+ Z2 = np.exp(-(X - 1) ** 2 - (Y - 1) ** 2)
+ Z = (Z1 - Z2) * 2
+
+ return Z, extent
+
+ def get_rgb():
+ Z, extent = get_demo_image()
+
+ Z[Z < 0] = 0.
+ Z = Z / Z.max()
+
+ R = Z[:13, :13]
+ G = Z[2:, 2:]
+ B = Z[:13, 2:]
+
+ return R, G, B
+
+ fig = plt.figure(1)
+ ax = RGBAxes(fig, [0.1, 0.1, 0.8, 0.8])
+
+ r, g, b = get_rgb()
+ kwargs = dict(origin="lower", interpolation="nearest")
+ ax.imshow_rgb(r, g, b, **kwargs)
+
+ ax.RGB.set_xlim(0., 9.5)
+ ax.RGB.set_ylim(0.9, 10.6)
+
+ plt.draw()
+ return plt.gcf()
+
+# The magic function that makes it possible.... glues together tkinter and pyplot using Canvas Widget
+def draw_figure(canvas, figure):
+ figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
+ figure_canvas_agg.draw()
+ figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
+ return figure_canvas_agg
+
+def delete_figure_agg(figure_agg):
+ figure_agg.get_tk_widget().forget()
+ plt.close('all')
+
+
+# -------------------------------- GUI Starts Here -------------------------------#
+# fig = your figure you want to display. Assumption is that 'fig' holds the #
+# information to display. #
+# --------------------------------------------------------------------------------#
+
+# print(inspect.getsource(PyplotSimple))
+
+
+fig_dict = {'Pyplot Simple':PyplotSimple, 'Pyplot Formatstr':PyplotFormatstr,'PyPlot Three':Subplot3d,
+ 'Unicode Minus': UnicodeMinus, 'Pyplot Scales' : PyplotScales, 'Axes Grid' : AxesGrid,
+ 'Exploring Normalizations' : ExploringNormalizations, 'Different Scales' : DifferentScales,
+ 'Pyplot Box Plot' : PyplotBoxPlot, 'Pyplot ggplot Style Sheet' : PyplotGGPlotSytleSheet,
+ 'Pyplot Line Poly Collection' : PyplotLinePolyCollection, 'Pyplot Line Styles' : PyplotLineStyles,
+ 'Pyplot Scatter With Legend' :PyplotScatterWithLegend, 'Artist Customized Box Plots' : PyplotArtistBoxPlots,
+ 'Artist Customized Box Plots 2' : ArtistBoxplot2, 'Pyplot Histogram' : PyplotHistogram}
+
+
+sg.ChangeLookAndFeel('LightGreen')
+figure_w, figure_h = 650, 650
+# define the form layout
+listbox_values = list(fig_dict)
+col_listbox = [[sg.Listbox(values=listbox_values, change_submits=True, size=(28, len(listbox_values)), key='-LISTBOX-')],
+ [sg.T(' ' * 12), sg.Exit(size=(5, 2))]]
+
+col_multiline = sg.Column([[sg.Multiline(size=(70, 35), key='-MULTILINE-')]])
+col_canvas = sg.Column([[ sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-')]])
+col_instructions = sg.Column([[sg.Pane([col_canvas, col_multiline], size=(800,600))],
+ [sg.Text('Grab square above and slide upwards to view source code for graph')]])
+
+layout = [[sg.Text('Matplotlib Plot Test', font=('ANY 18'))],
+ [sg.Column(col_listbox), col_instructions],]
+
+# create the form and show it without the plot
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',layout, resizable=True, finalize=True)
+
+canvas_elem = window.FindElement('-CANVAS-')
+multiline_elem= window.FindElement('-MULTILINE-')
+figure_agg = None
+
+while True:
+ event, values = window.Read()
+ if event in (None, 'Exit'):
+ break
+
+ if figure_agg:
+ # ** IMPORTANT ** Clean up previous drawing before drawing again
+ delete_figure_agg(figure_agg)
+ choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
+ func = fig_dict[choice] # get function to call from the dictionary
+ window['-MULTILINE-'].Update(inspect.getsource(func)) # show source code to function in multiline
+ fig = func() # call function to get the figure
+ figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
+
diff --git a/DemoPrograms old/Demo_Matplotlib_Ping_Graph.py b/DemoPrograms old/Demo_Matplotlib_Ping_Graph.py
new file mode 100644
index 00000000..b5687502
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Ping_Graph.py
@@ -0,0 +1,674 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import matplotlib.pyplot as plt
+from matplotlib.backends.backend_tkagg import FigureCanvasAgg
+import matplotlib.backends.tkagg as tkagg
+import tkinter as tk
+
+
+"""
+A graph of time to ping Google.com
+Demonstrates Matploylib used in an animated way.
+
+Note this file contains a copy of ping.py. It is contained in the first part of this file
+
+"""
+
+
+"""
+ A pure python ping implementation using raw sockets.
+
+ (This is Python 3 port of https://github.com/jedie/python-ping)
+ (Tested and working with python 2.7, should work with 2.6+)
+
+ Note that ICMP messages can only be sent from processes running as root
+ (in Windows, you must run this script as 'Administrator').
+
+ Derived from ping.c distributed in Linux's netkit. That code is
+ copyright (c) 1989 by The Regents of the University of California.
+ That code is in turn derived from code written by Mike Muuss of the
+ US Army Ballistic Research Laboratory in December, 1983 and
+ placed in the public domain. They have my thanks.
+
+ Bugs are naturally mine. I'd be glad to hear about them. There are
+ certainly word - size dependencies here.
+
+ Copyright (c) Matthew Dixon Cowles, .
+ Distributable under the terms of the GNU General Public License
+ version 2. Provided with no warranties of any sort.
+
+ Original Version from Matthew Dixon Cowles:
+ -> ftp://ftp.visi.com/users/mdc/ping.py
+
+ Rewrite by Jens Diemer:
+ -> http://www.python-forum.de/post-69122.html#69122
+
+ Rewrite by George Notaras:
+ -> http://www.g-loaded.eu/2009/10/30/python-ping/
+
+ Enhancements by Martin Falatic:
+ -> http://www.falatic.com/index.php/39/pinging-with-python
+
+ Enhancements and fixes by Georgi Kolev:
+ -> http://github.com/jedie/python-ping/
+
+ Bug fix by Andrejs Rozitis:
+ -> http://github.com/rozitis/python-ping/
+
+ Revision history
+ ~~~~~~~~~~~~~~~~
+ May 1, 2014
+ -----------
+ Little modifications by Mohammad Emami
+ - Added Python 3 support. For now this project will just support
+ python 3.x
+ - Tested with python 3.3
+ - version was upped to 0.6
+
+ March 19, 2013
+ --------------
+ * Fixing bug to prevent divide by 0 during run-time.
+
+ January 26, 2012
+ ----------------
+ * Fixing BUG #4 - competability with python 2.x [tested with 2.7]
+ - Packet data building is different for 2.x and 3.x.
+ 'cose of the string/bytes difference.
+ * Fixing BUG #10 - the multiple resolv issue.
+ - When pinging domain names insted of hosts (for exmaple google.com)
+ you can get different IP every time you try to resolv it, we should
+ resolv the host only once and stick to that IP.
+ * Fixing BUGs #3 #10 - Doing hostname resolv only once.
+ * Fixing BUG #14 - Removing all 'global' stuff.
+ - You should not use globul! Its bad for you...and its not thread safe!
+ * Fix - forcing the use of different times on linux/windows for
+ more accurate mesurments. (time.time - linux/ time.clock - windows)
+ * Adding quiet_ping function - This way we'll be able to use this script
+ as external lib.
+ * Changing default timeout to 3s. (1second is not enought)
+ * Switching data syze to packet size. It's easyer for the user to ignore the
+ fact that the packet headr is 8b and the datasize 64 will make packet with
+ size 72.
+
+ October 12, 2011
+ --------------
+ Merged updates from the main project
+ -> https://github.com/jedie/python-ping
+
+ September 12, 2011
+ --------------
+ Bugfixes + cleanup by Jens Diemer
+ Tested with Ubuntu + Windows 7
+
+ September 6, 2011
+ --------------
+ Cleanup by Martin Falatic. Restored lost comments and docs. Improved
+ functionality: constant time between pings, internal times consistently
+ use milliseconds. Clarified annotations (e.g., in the checksum routine).
+ Using unsigned data in IP & ICMP header pack/unpack unless otherwise
+ necessary. Signal handling. Ping-style output formatting and stats.
+
+ August 3, 2011
+ --------------
+ Ported to py3k by Zach Ware. Mostly done by 2to3; also minor changes to
+ deal with bytes vs. string changes (no more ord() in checksum() because
+ >source_string< is actually bytes, added .encode() to data in
+ send_one_ping()). That's about it.
+
+ March 11, 2010
+ --------------
+ changes by Samuel Stauffer:
+ - replaced time.clock with default_timer which is set to
+ time.clock on windows and time.time on other systems.
+
+ November 8, 2009
+ ----------------
+ Improved compatibility with GNU/Linux systems.
+
+ Fixes by:
+ * George Notaras -- http://www.g-loaded.eu
+ Reported by:
+ * Chris Hallman -- http://cdhallman.blogspot.com
+
+ Changes in this release:
+ - Re-use time.time() instead of time.clock(). The 2007 implementation
+ worked only under Microsoft Windows. Failed on GNU/Linux.
+ time.clock() behaves differently under the two OSes[1].
+
+ [1] http://docs.python.org/library/time.html#time.clock
+
+ May 30, 2007
+ ------------
+ little rewrite by Jens Diemer:
+ - change socket asterisk import to a normal import
+ - replace time.time() with time.clock()
+ - delete "return None" (or change to "return" only)
+ - in checksum() rename "str" to "source_string"
+
+ December 4, 2000
+ ----------------
+ Changed the struct.pack() calls to pack the checksum and ID as
+ unsigned. My thanks to Jerome Poincheval for the fix.
+
+ November 22, 1997
+ -----------------
+ Initial hack. Doesn't do much, but rather than try to guess
+ what features I (or others) will want in the future, I've only
+ put in what I need now.
+
+ December 16, 1997
+ -----------------
+ For some reason, the checksum bytes are in the wrong order when
+ this is run under Solaris 2.X for SPARC but it works right under
+ Linux x86. Since I don't know just what's wrong, I'll swap the
+ bytes always and then do an htons().
+
+ ===========================================================================
+ IP header info from RFC791
+ -> http://tools.ietf.org/html/rfc791)
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| IHL |Type of Service| Total Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |Flags| Fragment Offset |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Time to Live | Protocol | Header Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Destination Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ ===========================================================================
+ ICMP Echo / Echo Reply Message header info from RFC792
+ -> http://tools.ietf.org/html/rfc792
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identifier | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ...
+ +-+-+-+-+-
+
+ ===========================================================================
+ ICMP parameter info:
+ -> http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xml
+
+ ===========================================================================
+ An example of ping's typical output:
+
+ PING heise.de (193.99.144.80): 56 data bytes
+ 64 bytes from 193.99.144.80: icmp_seq=0 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=1 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=2 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=3 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=4 ttl=240 time=127 ms
+
+ ----heise.de PING Statistics----
+ 5 packets transmitted, 5 packets received, 0.0% packet loss
+ round-trip (ms) min/avg/max/med = 126/127/127/127
+
+ ===========================================================================
+"""
+
+# =============================================================================#
+import argparse
+import os, sys, socket, struct, select, time, signal
+
+__description__ = 'A pure python ICMP ping implementation using raw sockets.'
+
+if sys.platform == "win32":
+ # On Windows, the best timer is time.clock()
+ default_timer = time.clock
+else:
+ # On most other platforms the best timer is time.time()
+ default_timer = time.time
+
+NUM_PACKETS = 3
+PACKET_SIZE = 64
+WAIT_TIMEOUT = 3.0
+
+# =============================================================================#
+# ICMP parameters
+
+ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
+ICMP_ECHO = 8 # Echo request (per RFC792)
+ICMP_MAX_RECV = 2048 # Max size of incoming buffer
+
+MAX_SLEEP = 1000
+
+
+class MyStats:
+ thisIP = "0.0.0.0"
+ pktsSent = 0
+ pktsRcvd = 0
+ minTime = 999999999
+ maxTime = 0
+ totTime = 0
+ avrgTime = 0
+ fracLoss = 1.0
+
+
+myStats = MyStats # NOT Used globally anymore.
+
+
+# =============================================================================#
+def checksum(source_string):
+ """
+ A port of the functionality of in_cksum() from ping.c
+ Ideally this would act on the string as a series of 16-bit ints (host
+ packed), but this works.
+ Network data is big-endian, hosts are typically little-endian
+ """
+ countTo = (int(len(source_string) / 2)) * 2
+ sum = 0
+ count = 0
+
+ # Handle bytes in pairs (decoding as short ints)
+ loByte = 0
+ hiByte = 0
+ while count < countTo:
+ if (sys.byteorder == "little"):
+ loByte = source_string[count]
+ hiByte = source_string[count + 1]
+ else:
+ loByte = source_string[count + 1]
+ hiByte = source_string[count]
+ try: # For Python3
+ sum = sum + (hiByte * 256 + loByte)
+ except: # For Python2
+ sum = sum + (ord(hiByte) * 256 + ord(loByte))
+ count += 2
+
+ # Handle last byte if applicable (odd-number of bytes)
+ # Endianness should be irrelevant in this case
+ if countTo < len(source_string): # Check for odd length
+ loByte = source_string[len(source_string) - 1]
+ try: # For Python3
+ sum += loByte
+ except: # For Python2
+ sum += ord(loByte)
+
+ sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
+ # uses signed ints, but overflow is unlikely in ping)
+
+ sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
+ sum += (sum >> 16) # Add carry from above (if any)
+ answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ answer = socket.htons(answer)
+
+ return answer
+
+
+# =============================================================================#
+def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=False):
+ """
+ Returns either the delay (in ms) or None on timeout.
+ """
+ delay = None
+
+ try: # One could use UDP here, but it's obscure
+ mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ except socket.error as e:
+ print("failed. (socket error: '%s')" % e.args[1])
+ raise # raise the original error
+
+ my_ID = os.getpid() & 0xFFFF
+
+ sentTime = send_one_ping(mySocket, destIP, my_ID, mySeqNumber, packet_size)
+ if sentTime == None:
+ mySocket.close()
+ return delay
+
+ myStats.pktsSent += 1
+
+ recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout)
+
+ mySocket.close()
+
+ if recvTime:
+ delay = (recvTime - sentTime) * 1000
+ if not quiet:
+ print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % (
+ dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay)
+ )
+ myStats.pktsRcvd += 1
+ myStats.totTime += delay
+ if myStats.minTime > delay:
+ myStats.minTime = delay
+ if myStats.maxTime < delay:
+ myStats.maxTime = delay
+ else:
+ delay = None
+ print("Request timed out.")
+
+ return delay
+
+
+# =============================================================================#
+def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
+ """
+ Send one ping to the given >destIP<.
+ """
+ # destIP = socket.gethostbyname(destIP)
+
+ # Header is type (8), code (8), checksum (16), id (16), sequence (16)
+ # (packet_size - 8) - Remove header size from packet size
+ myChecksum = 0
+
+ # Make a dummy heder with a 0 checksum.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ padBytes = []
+ startVal = 0x42
+ # 'cose of the string/byte changes in python 2/3 we have
+ # to build the data differnely for different version
+ # or it will make packets with unexpected size.
+ if sys.version[:1] == '2':
+ bytes = struct.calcsize("d")
+ data = ((packet_size - 8) - bytes) * "Q"
+ data = struct.pack("d", default_timer()) + data
+ else:
+ for i in range(startVal, startVal + (packet_size - 8)):
+ padBytes += [(i & 0xff)] # Keep chars in the 0-255 range
+ # data = bytes(padBytes)
+ data = bytearray(padBytes)
+
+ # Calculate the checksum on the data and the dummy header.
+ myChecksum = checksum(header + data) # Checksum is in network order
+
+ # Now that we have the right checksum, we put that in. It's just easier
+ # to make up a new header than to stuff it into the dummy.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ packet = header + data
+
+ sendTime = default_timer()
+
+ try:
+ mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP
+ except socket.error as e:
+ print("General failure (%s)" % (e.args[1]))
+ return
+
+ return sendTime
+
+
+# =============================================================================#
+def receive_one_ping(mySocket, myID, timeout):
+ """
+ Receive the ping from the socket. Timeout = in ms
+ """
+ timeLeft = timeout / 1000
+
+ while True: # Loop while waiting for packet or timeout
+ startedSelect = default_timer()
+ whatReady = select.select([mySocket], [], [], timeLeft)
+ howLongInSelect = (default_timer() - startedSelect)
+ if whatReady[0] == []: # Timeout
+ return None, 0, 0, 0, 0
+
+ timeReceived = default_timer()
+
+ recPacket, addr = mySocket.recvfrom(ICMP_MAX_RECV)
+
+ ipHeader = recPacket[:20]
+ iphVersion, iphTypeOfSvc, iphLength, \
+ iphID, iphFlags, iphTTL, iphProtocol, \
+ iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
+ "!BBHHHBBHII", ipHeader
+ )
+
+ icmpHeader = recPacket[20:28]
+ icmpType, icmpCode, icmpChecksum, \
+ icmpPacketID, icmpSeqNumber = struct.unpack(
+ "!BBHHH", icmpHeader
+ )
+
+ if icmpPacketID == myID: # Our packet
+ dataSize = len(recPacket) - 28
+ # print (len(recPacket.encode()))
+ return timeReceived, (dataSize + 8), iphSrcIP, icmpSeqNumber, iphTTL
+
+ timeLeft = timeLeft - howLongInSelect
+ if timeLeft <= 0:
+ return None, 0, 0, 0, 0
+
+
+# =============================================================================#
+def dump_stats(myStats):
+ """
+ Show stats when pings are done
+ """
+ print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP))
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent
+
+ print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
+ myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss
+ ))
+
+ if myStats.pktsRcvd > 0:
+ print("round-trip (ms) min/avg/max = %d/%0.1f/%d" % (
+ myStats.minTime, myStats.totTime / myStats.pktsRcvd, myStats.maxTime
+ ))
+
+ print("")
+ return
+
+
+# =============================================================================#
+def signal_handler(signum, frame):
+ """
+ Handle exit via signals
+ """
+ dump_stats()
+ print("\n(Terminated with signal %d)\n" % (signum))
+ sys.exit(0)
+
+
+# =============================================================================#
+def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Send >count< ping to >destIP< with the given >timeout< and display
+ the result.
+ """
+ signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl-C
+ if hasattr(signal, "SIGBREAK"):
+ # Handle Ctrl-Break e.g. under Windows
+ signal.signal(signal.SIGBREAK, signal_handler)
+
+ myStats = MyStats() # Reset the stats
+
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ print("\nPYTHON PING %s (%s): %d data bytes" % (hostname, destIP, packet_size))
+ except socket.gaierror as e:
+ print("\nPYTHON PING: Unknown host: %s (%s)" % (hostname, e.args[1]))
+ print()
+ return
+
+ myStats.thisIP = destIP
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay) / 1000)
+
+ dump_stats(myStats)
+
+#=============================================================================#
+def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Same as verbose_ping, but the results are returned as tuple
+ """
+ myStats = MyStats() # Reset the stats
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ except socket.gaierror as e:
+ return 0,0,0,0
+
+ myStats.thisIP = destIP
+
+ # This will send packet that we dont care about 0.5 seconds before it starts
+ # acrutally pinging. This is needed in big MAN/LAN networks where you sometimes
+ # loose the first packet. (while the switches find the way... :/ )
+ if path_finder:
+ fakeStats = MyStats()
+ do_one(fakeStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+ time.sleep(0.5)
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay)/1000)
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd)/myStats.pktsSent
+ if myStats.pktsRcvd > 0:
+ myStats.avrgTime = myStats.totTime / myStats.pktsRcvd
+
+ # return tuple(max_rtt, min_rtt, avrg_rtt, percent_lost)
+ return myStats.maxTime, myStats.minTime, myStats.avrgTime, myStats.fracLoss
+
+# =============================================================================#
+
+
+
+#================================================================================
+# Globals
+# These are needed because callback functions are used.
+# Need to retain state across calls
+#================================================================================
+SIZE=(320,240)
+
+class MyGlobals:
+ axis_pings = None
+ ping_x_array = []
+ ping_y_array = []
+
+g_my_globals = MyGlobals()
+
+#================================================================================
+# Performs *** PING! ***
+#================================================================================
+def run_a_ping_and_graph():
+ global g_my_globals # graphs are global so that can be retained across multiple calls to this callback
+
+ #===================== Do the ping =====================#
+ response = quiet_ping('google.com',timeout=1000)
+ if response[0] == 0:
+ ping_time = 1000
+ else:
+ ping_time = response[0]
+ #===================== Store current ping in historical array =====================#
+ g_my_globals.ping_x_array.append(len(g_my_globals.ping_x_array))
+ g_my_globals.ping_y_array.append(ping_time)
+ # ===================== Only graph last 100 items =====================#
+ if len(g_my_globals.ping_x_array) > 100:
+ x_array = g_my_globals.ping_x_array[-100:]
+ y_array = g_my_globals.ping_y_array[-100:]
+ else:
+ x_array = g_my_globals.ping_x_array
+ y_array = g_my_globals.ping_y_array
+
+ # ===================== Call graphinc functions =====================#
+ g_my_globals.axis_ping.clear() # clear before graphing
+ set_chart_labels()
+ g_my_globals.axis_ping.plot(x_array,y_array) # graph the ping values
+
+#================================================================================
+# Function: Set graph titles and Axis labels
+# Sets the text for the subplots
+# Have to do this in 2 places... initially when creating and when updating
+# So, putting into a function so don't have to duplicate code
+#================================================================================
+def set_chart_labels():
+ global g_my_globals
+
+ g_my_globals.axis_ping.set_xlabel('Time', fontsize=8)
+ g_my_globals.axis_ping.set_ylabel('Ping (ms)', fontsize=8)
+ g_my_globals.axis_ping.set_title('Current Ping Duration', fontsize = 8)
+
+def draw(fig, canvas):
+ # Magic code that draws the figure onto the Canvas Element's canvas
+ figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
+ figure_w, figure_h = int(figure_w), int(figure_h)
+ photo = tk.PhotoImage(master=canvas, width=figure_w, height=figure_h)
+ canvas.create_image(SIZE[0] / 2, SIZE[1] / 2, image=photo)
+ figure_canvas_agg = FigureCanvasAgg(fig)
+ figure_canvas_agg.draw()
+ tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
+ return photo
+
+#================================================================================
+# Function: MAIN
+#================================================================================
+def main():
+ global g_my_globals
+
+ # define the form layout
+ layout = [[ sg.Canvas(size=SIZE, background_color='white',key='canvas') , sg.Button('Exit', pad=(0, (210, 0)))]]
+
+ # create the form and show it without the plot
+ window = sg.Window('Ping Graph', background_color='white', grab_anywhere=True).Layout(layout).Finalize()
+
+ canvas_elem = window.FindElement('canvas')
+ canvas = canvas_elem.TKCanvas
+
+ fig = plt.figure(figsize=(3.1, 2.25), tight_layout={'pad':0})
+ g_my_globals.axis_ping = fig.add_subplot(1,1,1)
+ plt.rcParams['xtick.labelsize'] = 8
+ plt.rcParams['ytick.labelsize'] = 8
+ set_chart_labels()
+ plt.tight_layout()
+
+ while True:
+ event, values = window.Read(timeout=0)
+ if event in ('Exit', None):
+ exit(0)
+
+ run_a_ping_and_graph()
+ photo = draw(fig, canvas)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Matplotlib_Ping_Graph_Large.py b/DemoPrograms old/Demo_Matplotlib_Ping_Graph_Large.py
new file mode 100644
index 00000000..38f7cf60
--- /dev/null
+++ b/DemoPrograms old/Demo_Matplotlib_Ping_Graph_Large.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import matplotlib.pyplot as plt
+import ping
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg
+import matplotlib.backends.tkagg as tkagg
+import tkinter as tk
+
+#================================================================================
+# Globals
+# These are needed because callback functions are used.
+# Need to retain state across calls
+#================================================================================
+class MyGlobals:
+ axis_pings = None
+ ping_x_array = []
+ ping_y_array = []
+
+g_my_globals = MyGlobals()
+
+#================================================================================
+# Performs *** PING! ***
+#================================================================================
+def run_a_ping_and_graph():
+ global g_my_globals # graphs are global so that can be retained across multiple calls to this callback
+
+ #===================== Do the ping =====================#
+ response = ping.quiet_ping('google.com',timeout=1000)
+ if response[0] == 0:
+ ping_time = 1000
+ else:
+ ping_time = response[0]
+ #===================== Store current ping in historical array =====================#
+ g_my_globals.ping_x_array.append(len(g_my_globals.ping_x_array))
+ g_my_globals.ping_y_array.append(ping_time)
+ # ===================== Only graph last 100 items =====================#
+ if len(g_my_globals.ping_x_array) > 100:
+ x_array = g_my_globals.ping_x_array[-100:]
+ y_array = g_my_globals.ping_y_array[-100:]
+ else:
+ x_array = g_my_globals.ping_x_array
+ y_array = g_my_globals.ping_y_array
+
+ # ===================== Call graphinc functions =====================#
+ g_my_globals.axis_ping.clear() # clear before graphing
+ g_my_globals.axis_ping.plot(x_array,y_array) # graph the ping values
+
+#================================================================================
+# Function: Set graph titles and Axis labels
+# Sets the text for the subplots
+# Have to do this in 2 places... initially when creating and when updating
+# So, putting into a function so don't have to duplicate code
+#================================================================================
+def set_chart_labels():
+ global g_my_globals
+
+ g_my_globals.axis_ping.set_xlabel('Time')
+ g_my_globals.axis_ping.set_ylabel('Ping (ms)')
+ g_my_globals.axis_ping.set_title('Current Ping Duration', fontsize = 12)
+
+def draw(fig, canvas):
+ # Magic code that draws the figure onto the Canvas Element's canvas
+ figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
+ figure_w, figure_h = int(figure_w), int(figure_h)
+ photo = tk.PhotoImage(master=canvas, width=figure_w, height=figure_h)
+ canvas.create_image(640 / 2, 480 / 2, image=photo)
+ figure_canvas_agg = FigureCanvasAgg(fig)
+ figure_canvas_agg.draw()
+ tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
+ return photo
+
+#================================================================================
+# Function: MAIN
+#================================================================================
+def main():
+ global g_my_globals
+
+ # define the form layout
+ layout = [[sg.Text('Animated Ping', size=(40, 1), justification='center', font='Helvetica 20')],
+ [sg.Canvas(size=(640, 480), key='canvas')],
+ [sg.Button('Exit', size=(10, 2), pad=((280, 0), 3), font='Helvetica 14')]]
+
+ # create the form and show it without the plot
+ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize()
+
+ canvas_elem = window.FindElement('canvas')
+ canvas = canvas_elem.TKCanvas
+
+ fig = plt.figure()
+ g_my_globals.axis_ping = fig.add_subplot(1,1,1)
+ set_chart_labels()
+ plt.tight_layout()
+
+ while True:
+ event, values = window.Read(timeout=0)
+ if event in ('Exit', None):
+ break
+
+ run_a_ping_and_graph()
+ photo = draw(fig, canvas)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms old/Demo_Media_Player.py b/DemoPrograms old/Demo_Media_Player.py
new file mode 100644
index 00000000..74101bb0
--- /dev/null
+++ b/DemoPrograms old/Demo_Media_Player.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+ # import PySimpleGUIQt as sg # portable to QT
+else:
+ import PySimpleGUI27 as sg
+
+#
+# An Async Demonstration of a media player
+# Uses button images for a super snazzy look
+# See how it looks here:
+# https://user-images.githubusercontent.com/13696193/43159403-45c9726e-8f50-11e8-9da0-0d272e20c579.jpg
+#
+def MediaPlayerGUI():
+ background = '#F0F0F0'
+ # Set the backgrounds the same as the background on the buttons
+ sg.SetOptions(background_color=background, element_background_color=background)
+ # Images are located in a subfolder in the Demo Media Player.py folder
+ image_pause = './ButtonGraphics/Pause.png'
+ image_restart = './ButtonGraphics/Restart.png'
+ image_next = './ButtonGraphics/Next.png'
+ image_exit = './ButtonGraphics/Exit.png'
+
+ # A text element that will be changed to display messages in the GUI
+
+ ImageButton = lambda image_filename, key:sg.Button('', button_color=(background,background), image_filename=image_filename, image_size=(50, 50), image_subsample=2, border_width=0, key=key)
+
+ # define layout of the rows
+ layout= [[sg.Text('Media File Player', font=("Helvetica", 25))],
+ [sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')],
+ [ImageButton(image_restart, key='Restart Song'), sg.Text(' ' * 2),
+ ImageButton(image_pause, key='Pause'),
+ sg.Text(' ' * 2),
+ ImageButton(image_next, key='Next'),
+ sg.Text(' ' * 2),
+ sg.Text(' ' * 2),ImageButton(image_exit, key='Exit')],
+ ]
+
+ # Open a form, note that context manager can't be used generally speaking for async forms
+ window = sg.Window('Media File Player', auto_size_text=True, default_element_size=(20, 1),
+ font=("Helvetica", 25)).Layout(layout)
+ # Our event loop
+ while(True):
+ event, values = window.Read(timeout=100) # Poll every 100 ms
+ if event == 'Exit' or event is None:
+ break
+ # If a button was pressed, display it on the GUI by updating the text element
+ if event != sg.TIMEOUT_KEY:
+ window.FindElement('output').Update(event)
+
+MediaPlayerGUI()
+
diff --git a/DemoPrograms old/Demo_Menu_With_Toolbar.py b/DemoPrograms old/Demo_Menu_With_Toolbar.py
new file mode 100644
index 00000000..9a9afa94
--- /dev/null
+++ b/DemoPrograms old/Demo_Menu_With_Toolbar.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+
+def ShowMeTheButtons():
+ # ------ Menu Definition ------ #
+ menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit' ]],
+ ['&Edit', ['&Paste', ['Special', 'Normal',], 'Undo'],],
+ ['&Toolbar', ['---', 'Command &1', 'Command &2', '---', 'Command &3', 'Command &4']],
+ ['&Help', '&About...'],]
+
+ sg.SetOptions(auto_size_buttons=True, margins=(0,0), button_color=sg.COLOR_SYSTEM_DEFAULT)
+
+ toolbar_buttons = [[sg.Button('', image_data=close64[22:],button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0,0), key='_close_'),
+ sg.Button('', image_data=timer64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_timer_'),
+ sg.Button('', image_data=house64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_house_'),
+ sg.Button('', image_data=cpu64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_cpu_'),
+ sg.Button('', image_data=camera64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_camera_'),
+ sg.Button('', image_data=checkmark64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_checkmark_'),
+ sg.Button('', image_data=cookbook64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_cookbook_'),
+ sg.Button('', image_data=download64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_download_'),
+ sg.Button('', image_data=github64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_github_'),
+ sg.Button('', image_data=psg64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_psg_'),
+ sg.Button('', image_data=run64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_run_'),
+ sg.Button('', image_data=storage64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_storage_'),
+ ]]
+
+ # layout = toolbar_buttons
+ # ------ GUI Defintion ------ #
+ layout = [ [sg.Menu(menu_def, )],
+ [sg.Frame('', toolbar_buttons,title_color='white', background_color=sg.COLOR_SYSTEM_DEFAULT, pad=(0,0))],
+ [sg.Text('', size=(20,8))],
+ [sg.Text('Status Bar', relief=sg.RELIEF_SUNKEN, size=(55, 1), pad=(0, 3), key='_status_')]
+ ]
+
+ window = sg.Window('Toolbar').Layout(layout)
+
+ # ---===--- Loop taking in user input --- #
+ while True:
+ button, value = window.Read()
+ print(button)
+ if button in ('_close_', 'Exit') or button is None:
+ break # exit button clicked
+ elif button == '_timer_':
+ pass # add your call to launch a timer program
+ elif button == '_cpu_':
+ pass # add your call to launch a CPU measuring utility
+
+
+
+if __name__ == '__main__':
+ house64 = 'data:image/png;base64,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'
+
+ timer64 = 'data:image/png;base64,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'
+
+ close64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAE30lEQVRIiZ2VXYgdRRqGn6+quvucM/85iRoTNevMBJFEWY0GFQTBC1HBlaz/jMpoFFfXBdmFvdiLvRIEFRHFGBXMjUQhF/6Bol6sSNaIruCNir/R/Dlx5iRzck736e6qby/6JDlx9CIWFN10Ue/7vW+9X7XcDn8bryWPL2vERkNQQPj9Q72K7F3s7Hxb9bZ98L0bj91jt1y23kxNTxIEGUQ/aTYR6WW9cud/Prx01zf7/7FP5EHXHG7Y6bVTpBPLMSegCWKEEMKvkihgjEWDP+FbEjxTa1bjv9l/CsIKF3ypHhUDSFGACCKC956iKKjV6/hfkCjgUNK0TW1oCA3h+EJk8UUBYFCsQaSyRajArUWLnEONcTrT68nTLtZaEKmmMTiUlsREGy9HO0dgcL1y6lgtZrAsEYFexhwxq2buYfru+1mcOo+828UYg4rgUH7OSkY3zbDq1lkaV1yFP9TqEyy18jiBCMF7DjYmOOu+hxifnCSKItZuvp/F6fPJ05TEwE+dHhN33MfpGy4iFAVjf7qF8etvBV9y1IilBApGIMt6TExOM372JKqKqhLFMdOz93Jk6jx+bHVoztzLyj9eiHqP2Gq7O3UlGAuq1RwYDlUwhoChMdSAz3ZxaEeD8T/fBggaAnGtxpqZWdKFBSbOPLMCCQGJItJPdrHw4lOYRgNsBM6dSCDGErIuodtGkhoyPEr68U5svcbI1ZsQY0CV2vAw9ZGRKjEiSBTR/fQjDm9/AddcjqoSul182kYHVDhJauRffUH7wD7ilatxzVOwI6PM7XiJLO2x4rob0CgGVTSEKigidD94j/ltW9Dg0b0/4BfmyQ8ewKUdWLZ6wCIB9SXFXJvQ+hLkc6QeEznHf199jY1rpjh1w0ZUFTGm7z18/tSj2Hffor5shKLdhhJCADMcw7IlKRIkAqkJRIa4LPl6d5c/PPJkBd5vpArcArD+ue101l1Md08bFxuIBUlOyOUggUIAVIl94Kv5wKqtz7L+7r/0bRHEmApcFbwnHhljw6tv0b3kEtK5gDWmj/GbfQAWZbdaztjyPOfP3oN6D8GDCO133uDAvx9CyxKsRX1JMjbBBa+8Rnbl5RSpR35RfXUGfVLnYGFBcTfdwLo77yLkPYy14CLa773JngfuoNy7QOh2WPnw09WVkufUm8s598G/s+eT9wmBJZ1m+sVTFNBc4Wi8vJ3v//kAJk7AOhbf3MGezTfjWwuYCcv8s1s58K+/okWOxDGdjz5g7+YZtKRSoL+igCp5FKVntGk48sTTzDWb1C+4mB833wgETD2CELBjEfNbtyAjo4xdcz27N11L6B5GGoZQhN+26KiSoII9LebnJx9BkggzNIQkyfEdItiRQGvbM7S2bQHJMGN1NO8ds2dQhBORYBCjAFEE1kFSw0QxuAiTJCAGce64vz4gviTkOTJcErIMMRbyDIxg7bHTFnc47clcmpdj43VkeBRJEkytgdTqSL2OiRMkSRDroH9t4EtCUaBZhmYpIUurZ9pFfVnuX+w62xfjeq3D3/6vbifXrT1XkzgWdREmipA4RlwMUYRY21cg/X+lJ5gSbIHGOVovCHmOCSX7DrbMx599icIhVI2cA5c5mC1gbGnITm4oqAOr0PoOXs9g51HAGiITyCDByXDp4KuiaoESmP8/YC0Y5GajmEsAAAAASUVORK5CYII='
+
+ psg64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAABlbkiVhkyZikyFjmShjkyhlly1mli5nlylnmS5olyppnC5qmi5rmzBpmDBpmTFqmDFqmTFqmjNrmzFrnDNsmzJsnDJtnTFunjNvnzRsmTRtmzVunTVunjVvnzZwnjlxny5toDFvozJwoTNzpjVwoDRzpDRzqDd4rThyojp0ozl1pTt2pjt3pz11ozt4qDl4qTp6rTl6rzx4qD55qTx5qj97qz17rT58rD98rT9+rz5+sEB3pEF9rkB+r0F+sEF/sT2AtD6AtT6Btk+Aqk2CrEOBs0GBtEKCtEGCtUSBskWBs0SCtEWDtUaEtkWFt0aGt0KFuUCEukOGu0eFuEWHu0eJvEiGuUiHukiIuUiIukmJu0uKvEyKvFCAp1CCqlODrVKCrl2Kr1OEs1KGsFWGsFaIsVaPvFqKsl6NsmKNsWeTuG6YvHadvlySwV6UxF+YxW+bxW6dxnKewHGex3SdwHSfx3egwXGizHmgwHqkxnyjxHio0f/RMf7QMv/TMf/SMv3SNf7UOv7UO//UPP/UPf/UPv/VP//WPP/XPv/VQ//WQv7XQ//WRP/XRf/WR//YRv/YSP/YSf/YSv/ZS//aS//ZTf/aTP7aTf7aTv7bT//cT//bUP/cUP7cUv/cU/7cVf/eVf/fVv/eV/7dWP/eWP/eWf/fWv/fW//fYvzcaf/hW//gXP/gXv/gX//hYP/hYf/hY//iYP/jY//gZf/iZP7iZf/jZv/iZ//lZv/jaf/kav7ka//maf/ma//kbf/lbv7mbP/mbv/mb//id//mcP/ncv/nc//ld//ndv/meP/ocf/ocv/oc//odP/odf/odv/peP/pff/qfY+11ZSzz5G41qC81aW/1P/jgf/qiv/qjv7qoMnZ5szb587d6eDm2+fo1+7v3e/x3vXw1fHx3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJblQd8AAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAABcQAAAXEAEYYRHbAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAABzUlEQVQoU2P4jwNAJZIEuLJA9M2u/iNgAaiEELOgIFPc//+r6puaalvAQmAJdg4pPj4h5oT/2+raWtqaGmAS/PxC/IJAGdbEB7saW5pb20AyQAkFNiFhUSEgkGZNfjizua29u70XJJHr8j+eV0RGVkRMTJb56u2mvt7eSR0gCT2gPsbMGzbi8hJyPDl3OidPnDRlwhagRHbG/zTXe5WSqqqqmpzXb/VMmz5jztSVIDtSWFLvl3Jrampq8ZY8WThj1tx586ZCXFV9t1xRR1tbR6Lw0f6ZC+YvWDAb6tz/xUom+rrGymWPD8xaunjZ0oUgMZBEsYqZqampWsnTY/PWLF+xZhFIHCRRpW5raWFhUPT/3IJ1a9euW/H//5oTYAlDezs7Kwvv//+XbN6wcev6//+3/z8FltDwcrC3N8/7v3rHtu07Nv3/vxVo0CWQhJGPm5ubdf7/TXt279699//JnTA70j38fH19wv//33b00OGj+w6fPXz5KMRVTiH+/gHuFf//7zl+5szZs2fO7YPo+H/FOSIyPMqz5v//g+dAMocvQCX+XwsMjYmNdgSy9p0/d/bgRZAYWOL//4LgoDAwY+++02AaJoEJcEj8/w8A4UqG4COjF7gAAAAASUVORK5CYII='
+
+ cpu64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAFi0lEQVRIiZ1WS2wbRRj+/5l92O6u7WycTW03xLEd6uBUjVO1hfbSByLQEwhxoIhbCwcuvApUiEOFhAQVEiAOvK4gqoojSEhtgUNApaJKG5qkdWpa1JfcZL3teu3sZmeGQ9ZVGhIJ8Z/+2Vn93//45psBWMMqlcoXxWLxEACAaZpju3btOkkIoZRSsnv37hOmaY4BABSLxUOVSuXzteJIq32klMqyLBuqqhoAAKqqGpTSpKIoSUQESZK6OnuKohiKohiUUpkxtvivWCvWBABEOp1+sr+//5V169ZtnJub+6FUKh3Rdf3hVqv1l6Zp5Ww2ezASifQ0Go3fhoaGjsZisYdardaM4zjTiEgAQHQC4j0HkQghAAC4oiiJRCKxBQBIs9m8oOt6iRASa7VaVwEAYrFYP+e85TjOpXg8PiyE4LZtn/F93wYAgogghOD3AYS+UFW1q1AovGoYxp4wGxIEgS2EEIQQCQCAcx7gkslCCB8AwLbt07Va7SPP8xqdWPdmIElSxDTNfZZlncrn828MDg6+VavVPvF9fy4Wi/X19fUdWJHMfSaEYJlMZgwRpVqtdtQwjD31ev2HIAgWpJGRkS8VRTEMw9g9OTm5v7u7+9GpqamXq9XqxwAAmzZt+oBzjpzzYC0QIQRDRJpIJLanUqmdw8PDX1mW9ZPv+5bkOM5FVVVTiURia1i24rruDQCAUqn09sDAwCHGGEdEadnwlgOJZT5BRMIYc5rN5iXP8+ax0y9N04qc84Vt27aduHjx4uuEED46Ovo95xxEOH1ExKWEhQh9DPe4JEl0fn7+14mJiecQUWo2m7MAgNQ0zb3d3d3bhoaGjrTb7Wld1x/p6uoa2bBhw4uyLGsAEFBKKSIi51xQSjFcIiICIQRDAhDXdWue502Vy+X3hRALqqr2SoODg2/KsmzE4/GNlNJ1nPOF9evXPxYEAbiue7lWq72rKIphmub+GzdufBeNRg1d14cZYx4hhBJClFQqNRbOQlBKo8lkcms+n48vLi5a0vj4+OOKoiTT6fQzjuNcJYRIQRCALMswOzv7LSEk0tPT85TjOBeCIKi12+1rtm3/ruv6FgDgAMB7e3vHgiAAQgh1HOfquXPnXr958+Zx3/dtshopltp7nyEiUtd1rxuG8URfX99B13Un2+32rKIo3ZzztRgMdOfOnT/mcrkX+vv79zcajVOapm3XNC3HGINoNNpnWdZJz/P+TiQSOzRNK6bT6WcjkUh/q9WaQUTIZrMHEFEjhECz2fzL9/2ZkZGRz0zT3JfNZp+WqtXq+5FIJJXL5V5kjLVDdgDnnMVisYFyufxVSFHgnO9gjDFElIvF4jth34ExxgCAIiIyxtq2bZ+5cuXK5wsLC3NSvV4/BQDCsqw/hBBBLpeTO+WF/KdhC0TIHAoAIggCjogYMnjpEBAi27Z96ezZsy90aCoVCoXXVFVNZbPZ/TMzMy9xzr1ljSdhYLHicN0DCkFYWKFnGMamUqn06fXr17/xPG9e0nV9Y6jnWqiAPCydrTm5laxY+pcCABdCcEqprmnag4qiWNLExMTBZWI3Ho/Hd2Qymb1CCBpm+V8AQJZluHPnzum5ubnx8+fPH+iI3apync/nX04mk9vDXihCiMX/K9drXTjJZDK5FRHJ3bt3/9R1/cH/e+Esb0FnkKK3t3ff5s2bv+7p6Rm7devWsXK5/GGhUDjsOM5kNBp9oFKpfKNp2kC9Xv9xdHT0eCaTed513fPhlYmd4CsBOiDQarVmu7q6KpZl/XLt2rVjQggvHo8PTE9PH242m1PpdPrRy5cvf3L79u2fo9GoyRi7U61W3wsDL5fv1V8VjLFF3/ct3/ctAADP86wgCBq+7zcAABljtud5FgCA7/uWLMvWai8KAIB/ACsf4Gh+DNwbAAAAAElFTkSuQmCC'
+
+ camera64 = 'data:image/png;base64,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'
+
+ checkmark64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAD2ElEQVRIibWVe4hUVRzHP+feuTOz82gfrqwu5gM0zTQpS1yfoGQPyc3IjOwlYRElPcDICEP6Q6i/IvojCCIpTA0tSlSqJSvbTLZE0dbWxz4nXWdnxrk7M/feOY/+sHyEa7pt57/z45zP53x/B84R/B/jzeNLgeXADDHE4IeBV2Ihpo6LK1rzNqEhAW9oGw18UhelYeWUGHFL0tx5lqPZyBAI3mi9o8YRm16cWTlsVFLQVwjY2+4SSI2W+j8KXj+ybmwytO69xjo7Wyqzr8sldbaE60ksIdBlhTVo+KuHXppY5azftKzeNsbQkurntOuTKQQobYhFbCgPNsGaA5NDWm94ZlYV7fmAX3pcenIlTucDAqlJRENUxcJQLgwiwfMtYcpq4503JMJjq8M0d+XpyBRJnfUpBpJwyKYqFqbCcSCQg0gQyCeq4qHp90yr5Pd0kY6+ImnXJ1CaeDhEdSJCTSJKzLEHLXhu4oQEuWKZ79uzZAoX2hKPhOn+I6DtuEdfLriC4NE9L4CYhzEP8dH84Hz9kT0NBHLqvMlJmo5nyBQDylITj4RwM5rmw70orcEA0AL8Q/DgN8OBr/DltL8q64G1F52+obomwr6US7boE0hNhRPiVIdHx7H+EvA2sJ0tC3/+e8uFS27c/SS+7ElGrGkbnp5EfV0UArmGxt0Lzq/x5YzKWocz/T4FXyGEINvj0XE410QgJ7Fl4dqL4ecS3PVlJYgdllKzx04ZxqolY8h4mkm315JPl+z+nP8Bd++4hZ2LM/hyuokLCr7Eti28TJnOA5ndGLOUnYtLl+u2YMHnJ4BxY2bWsWj2SA72eoBBG4PnBvy2qwvpq81gVjhJp1Q7q9axLIFVMqSaz3ytfLWEpsbLwgFs6pc1o/R9+e7+eK9joSMWvjR4gSLA4FSGKLS7UyirUmRkbJFTG0VI6N17+oR0/bl8d/+A8HMJAG7bPB7BTmGL8TVz64mMiKGNQSuN0hqvq59CS59Kzq2zo8MrcH/s1V6qMIf9y5uvBL8gALj54xpgG5aYH589klB9BdoYjDY0XJ9k9HURPj2aRZ/ycL/tfouDK17+N/ilAoAbP6wAsRGLB8INI7BGJUAYLGEhLAtLCApfnDymc95NtD4eDMC8ZNiXzNKfSdLbt5K8N6o68nNMwoHqKCAwlkVwKI06ln2MtpWtVwMHBnjspHyNQO1Xe7pRbTmUEchCGbk/laKsdl0tfGBB51OKQM0hUD/ppk7kkTTy11NQku/TuUpdi+DKn/7wdyuAHzDcii0Uykwg/ezJoRMAVL9TCWwFjpJdvfpa4AB/Akx4zQw8GDagAAAAAElFTkSuQmCC'
+
+ cookbook64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsSAAALEgHS3X78AAADMUlEQVQ4jY1UQUgCWxS9782M5hQzle3NTRGEQdBipCKKECoEg4JaJAqCmyBsYauEaNOqjQtXLSKINuFiKMiwIMtF1iLK2kWRZLUoDRxo5s37ixfSt4+/u7xzz51z7r3nwc7Ojt1uBwCEEPwtWKXL5eIvLy+HhoYIIYIgCIJAKa0Do5RyHPfy8nJycnJ1dcUjhJxOZygUOj09zefzFoulDp5SihCKRqPLy8vJZBI4jgOAo6Mjj8cDABjj/6WdTqdDoRAAfJeyFn8MQohhGADAY4xFUSyVSpIkAYBpmgih+soRQmxm2GazbW5u7u7ujoyMKIrCmP+ePMdxv9nhSqXi8/lmZmb29vay2Syrs1gs8EM/QogQQgipBWOMOzs7397eWlpabDYbAMiyHAwGu7u7mQTWzu/3R6PRxsZG+HERvNVqjcVix8fHfX19Nzc3T09PHo+HUjo1NVUulx8fHwFgbW0tEolQSguFwtbWVpU/rlQqs7Ozc3NzqqrmcjmXy9Xe3m61WgcGBubn5wGgo6NjYWEBAEql0t3dHQBUx8ljjNva2orFYnNzM8/zBwcHFoslGo329/cXCgUA6OnpwRh/fHwsLS3lcjm2qm9wQ0NDPB7f398fHBx8eHjIZrOqqhaLRUmSwuFwPB53OBw+ny+dTn9+ftYujed5AEilUhMTE9U9saTX66WUJhKJmv0dHh4Gg0FgF4YxJoQwANNjGIaiKLFYbHp62ul0Li4umqb5H5crSVIymQwEAolEwu12s6SiKNfX15OTkwDgcDguLi4ikUgVUv0zCIJgs9lUVWWlrP3q6qrf72dfAaCrq2tjY0OW5RowTynVNM1qteq6XqW9srJiGAZCSNd1hNDt7W04HGZm+NeFiaKYTCa3t7fHx8fdbjez+9fXV7UR87Cu66Zp1oI1TQsEAl6vN51Os9smhCCEfpbWmMw0TZbBpmm+v7+3traWy2VKKdP825I/M7Isi6IIAFxTU9P6+nomk+nt7X19fX1+fsYY1/ez0+k8Pz+/v7/nMMblcnl4eDifz5+dnWmaVgfGolQq2e32sbGx7wcok8mMjo7C396wVCpFKSWE/ANWXYLwO0+V8wAAAABJRU5ErkJggg=='
+
+ download64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAEl0lEQVRIia2WS2hdZRDHf/Odc+7NvbZpYky1Gk2rEbRa26pt8UWLitYHIqigCD426kIQxCfWhYu68U0RBBERXCq+WrXoorYIFWpbEY0lpk0NTWKtuUlzn+d834yLm5smqQspzurMcM785j+c+eYTTtUcESAzvhIAm/+azHacA4FodszAVNFTrUPm+Q6iecmUyJkEna5OiDCCXCtvJ2cnV+Ep+9R7/VIfQhmeryKeySywok+SSMMKMwqAihDXPIcPDDMURUQCgiPWjJCck6x87ZXXV3cXu3XTO5tkYOvAHbnIfZipTpghLdAMIEngi1cXvtlzwfrHpG0x5ismzsvo0E9D9z7z++M799s2EcSm67OUkAs5cpbzkkoMtPtAAzdXQ9zqjHkt1Ol5SHofx0KWYRUxrdiS3FlLtzz51wd7+v2OQl7qHnPtorUXS3ZxPRUKUT5x4mTDWu559LbCNS+9X9v025Duc4KoYdMAA7A4Mk92EMp/JFIZwR/rx9dL1teVdC2/Qe8yzQg+pS0JvLUzx3hjioPVQamGGlcu47KNq6qrPj+fsd+GeAEYA2SmRQiCNSJKP1Ad3IVaG0nnlWRxKqkkVlYxJxGZwhmFIo34U/fh0Hv4v6YYrY+ihYtkorDUNj+298GPvzv6ZRrkMzA/oyCXh9rEMOOHfiLfcx+5zhXkOnppswxEpJHVxdTjs0CycDHy9XcMlwc5a0E3EoTconOls/dyBsb6lYRLY4m/9T6blDgi8oHw3rPx83fesubl4oVPWFvXBUKoQzqB92Xitpite77n/k/epaN7AZO1CTIROtZ14fJC6ccS9ndGUhRLK0Eum1h2YGpH5eFfD47sjluzcFo+f+vp655F03alNhZhASMjloA1qtzedzab125kiw2QLhHaQ0zIFM2MztUdkBcqx1Lp+0o59NGRP49OVQs0Z3d6nEyMUMP8OGgVtAJaA19CagP4xn4e6DPuPhox1V9HTRFr/h9mRmWkwbJtGSsHK4xXq4cQGQDCDABM0ClEy6DlJiA9DLV90BgktirFzhrPXX0mT6Y9lAaqkAhRItRKGT3bjetTYd2aYM7JYcwm5wwaAP44hDyQYukokg5jliICZoFIoNjZ4Ol1HdhueOPgCLlFjt7twvo63HwztGuipml20lEBBlrGfBXzR5BsDGjOPBrAAkJKRKBwuuepNUXyP5/HN7tKXFGvcuMGY/3qhAO/NLCTJ7kFmIT0OPgjmAhiYKYIASFgGoCUyAILu+o8ckng0jSwsF1YuzxP0hYwm3tizwIIpKPQOIY4BXUYCiiYYWSIKYYHMoRAV1fKTddFxJKQOA/mmW9zFWRjoCmYw6R1lrcg2kxgAfCIeRxKMa+YBSw0Vc7fOScAZuAnMXWYE8yaIUFBDFSbS8sCgscsayZWD3jMAmhT7b8CnDPIeZw6RGTOLmwWFRALMA3BZvkamoBcwM3Zh7MA9Yb5I3v/YKoKTlr9sROKZVrlTGDWsylmkMTGxCQ4h0ObGaT1aRJzHsbtwJJmWSet0/9kIpB69gPbgersJA4oMm/pn6JlQI1/uWX87/YP06p9rkZQnAYAAAAASUVORK5CYII='
+
+ github64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAADAwMDQ0NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhyjGAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABzSURBVChTbYxRFoAgDMPQ+98Z1zbIeJqPbU3RMRfDECqyGpjMg6ivT6NBbKTw5WySq0jKt/sHrXiJ8PwpAAVIgQGkwABSYAApMIAUGEAalFmK9UJ24dC1i7qdj6IO5F+xnxfLu0jS0c7kqxd3Dk+JY8/5AKFrLuM7mfCAAAAAAElFTkSuQmCC'
+
+
+ run64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAszD0iAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABqSURBVChTpY5JDsAwCMTy/09TMGvFpVF9aAZPRHpkcXC7OIodPg0uCjPq+MwCrWRGKkiIvLyTqzw3aqoI73eqUNAoXBXlg4zudxF+NONfPIVvbSZPgww5oW0Vz8T4Lgbt/xbjia+rahR5AEYEg4vdzh2JAAAAAElFTkSuQmCC'
+
+ storage64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAABwcHDQ0NDg4ODw8PFxcXGRkZGhoaGxsbHh4eIyMjJSUlJiYmJycnKCgoMTExMjIyNTU1NjY2Nzc3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAouNksgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdQAAA3UAe+RuhUAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAC5SURBVChTfZLbDsMgDEPpbb3TDv7/W7PYuAztYUeqhO2QAGowkXIMIeYkaSU4QsNBi4GcyhNINpTglmq4GWSphvy/ldkuLXZ4HmAxy3NmFJaA4guKGCwsjClfV05+fWdhYBtFw+amB292aygW3M7fsPTwjmadZkCvHEtWaAYTViBqVwgTA3tJVnB6D/xhaimItDhjMBvlhtFsaIafnEtOaAY/twAw/eslK70CbX8obUvgJNw9Jv0+Zh8D4s5+VAm/LwAAAABJRU5ErkJggg=='
+
+ ShowMeTheButtons()
+
diff --git a/DemoPrograms old/Demo_Menus.py b/DemoPrograms old/Demo_Menus.py
new file mode 100644
index 00000000..92235224
--- /dev/null
+++ b/DemoPrograms old/Demo_Menus.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+"""
+ Demonstration of MENUS!
+ How do menus work? Like buttons is how.
+ Check out the variable menu_def for a hint on how to
+ define menus
+"""
+def second_window():
+
+ layout = [[sg.Text('The second form is small \nHere to show that opening a window using a window works')],
+ [sg.OK()]]
+
+ window = sg.Window('Second Form', layout)
+ event, values = window.read()
+ window.close()
+
+def test_menus():
+
+
+ sg.change_look_and_feel('LightGreen')
+ sg.set_options(element_padding=(0, 0))
+
+ # ------ Menu Definition ------ #
+ menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit' ]],
+ ['&Edit', ['&Paste', ['Special', 'Normal',], 'Undo'],],
+ ['&Toolbar', ['---', 'Command &1', 'Command &2', '---', 'Command &3', 'Command &4']],
+ ['&Help', '&About...'],]
+
+ # ------ GUI Defintion ------ #
+ layout = [
+ [sg.Menu(menu_def, tearoff=False, pad=(20,1))],
+ [sg.Output(size=(60,20))],
+ ]
+
+ window = sg.Window("Windows-like program",
+ layout,
+ default_element_size=(12, 1),
+ auto_size_text=False,
+ auto_size_buttons=False,
+ default_button_element_size=(12, 1))
+
+ # ------ Loop & Process button menu choices ------ #
+ while True:
+ event, values = window.read()
+ if event is None or event == 'Exit':
+ return
+ print('Event = ', event)
+ # ------ Process menu choices ------ #
+ if event == 'About...':
+ window.disappear()
+ sg.popup('About this program','Version 1.0', 'PySimpleGUI rocks...', grab_anywhere=True)
+ window.reappear()
+ elif event == 'Open':
+ filename = sg.popup_get_file('file to open', no_window=True)
+ print(filename)
+ elif event == 'Properties':
+ second_window()
+
+test_menus()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Multiple_Windows_Experimental.py b/DemoPrograms old/Demo_Multiple_Windows_Experimental.py
new file mode 100644
index 00000000..a5902d60
--- /dev/null
+++ b/DemoPrograms old/Demo_Multiple_Windows_Experimental.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout1 = [[ sg.Text('Window 1') ],
+ [sg.Input(do_not_clear=True)],
+ [ sg.Button('Read')]]
+
+window1 = sg.Window('My new window', location=(800,500)).Layout(layout1)
+
+
+layout2 = [[ sg.Text('Window 2') ],
+ [sg.Input(do_not_clear=True)],
+ [ sg.Button('Read')]]
+
+window2 = sg.Window('My new window', location=(800, 625), return_keyboard_events=True).Layout(layout2)
+
+
+layout3 = [[ sg.Text('Window 3') ],
+ [sg.Input(do_not_clear=False)],
+ [ sg.Button('Read')]]
+
+window3 = sg.Window('My new window', location=(800,750), return_keyboard_events=True).Layout(layout3)
+
+
+while True: # Event Loop
+ event, values = window1.Read(timeout=50)
+ if event is None:
+ break
+ elif event != '__timeout__':
+ print(event, values)
+
+ event, values = window2.Read(timeout=0)
+ if event is None:
+ break
+ elif event != '__timeout__':
+ print(event, values)
+
+ event, values = window3.Read(timeout=0)
+ if event is None:
+ break
+ elif event != '__timeout__':
+ print(event, values)
diff --git a/DemoPrograms old/Demo_Multithreaded_Logging.py b/DemoPrograms old/Demo_Multithreaded_Logging.py
new file mode 100644
index 00000000..b5f0deb6
--- /dev/null
+++ b/DemoPrograms old/Demo_Multithreaded_Logging.py
@@ -0,0 +1,98 @@
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUIQt as sg
+else:
+ import PySimpleGUI27 as sg
+
+import queue
+import logging
+import threading
+import time
+
+"""
+ This code originated in this project:
+ https://github.com/john144/MultiThreading
+ Thanks to John for writing this in the early days of PySimpleGUI
+ Demo program showing one way that a threaded application can function with PySimpleGUI
+ Events are sent from the ThreadedApp thread to the main thread, the GUI, by using a queue
+"""
+
+logger = logging.getLogger('mymain')
+
+
+def externalFunction():
+ logger.info('Hello from external app')
+ logger.info('External app sleeping 5 seconds')
+ time.sleep(5)
+ logger.info('External app waking up and exiting')
+
+
+class ThreadedApp(threading.Thread):
+ def __init__(self):
+ super().__init__()
+ self._stop_event = threading.Event()
+
+ def run(self):
+ externalFunction()
+
+ def stop(self):
+ self._stop_event.set()
+
+
+class QueueHandler(logging.Handler):
+ def __init__(self, log_queue):
+ super().__init__()
+ self.log_queue = log_queue
+
+ def emit(self, record):
+ self.log_queue.put(record)
+
+
+def main():
+ window = sg.FlexForm('Log window', default_element_size=(30, 2), font=('Helvetica', ' 10'), default_button_element_size=(8, 2), return_keyboard_events=True)
+
+ layout = \
+ [
+ [sg.Multiline(size=(50, 15), key='Log')],
+ [sg.Button('Start', bind_return_key=True, key='_START_'), sg.Button('Exit')]
+ ]
+
+ window.Layout(layout).Read(timeout=0)
+ appStarted = False
+
+ # Setup logging and start app
+ logging.basicConfig(level=logging.DEBUG)
+ log_queue = queue.Queue()
+ queue_handler = QueueHandler(log_queue)
+ logger.addHandler(queue_handler)
+ threadedApp = ThreadedApp()
+
+ # Loop taking in user input and querying queue
+ while True:
+ # Wake every 100ms and look for work
+ event, values = window.Read(timeout=100)
+
+ if event == '_START_':
+ if appStarted is False:
+ threadedApp.start()
+ logger.debug('App started')
+ window.FindElement('_START_').Update(disabled=True)
+ appStarted = True
+ elif event in (None, 'Exit'):
+ break
+
+ # Poll queue
+ try:
+ record = log_queue.get(block=False)
+ except queue.Empty:
+ pass
+ else:
+ msg = queue_handler.format(record)
+ window.FindElement('Log').Update(msg+'\n', append=True)
+
+ window.Close()
+ exit()
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Multithreaded_Long_Tasks.py b/DemoPrograms old/Demo_Multithreaded_Long_Tasks.py
new file mode 100644
index 00000000..f74bee00
--- /dev/null
+++ b/DemoPrograms old/Demo_Multithreaded_Long_Tasks.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python3
+
+import queue
+import threading
+import time
+
+# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
+# To try something other than tkinter version, just comment out the first import and uncomment the one you want
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+# import PySimpleGUIWx as sg
+# import PySimpleGUIWeb as sg
+
+"""
+ DESIGN PATTERN - Multithreaded Long Tasks GUI
+ Presents one method for running long-running operations in a PySimpleGUI environment.
+ The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
+ The "long work" is contained in the thread that is being started.
+
+ A queue.Queue is used by the threads to communicate with main GUI code
+ The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
+ a Window is created, and an event loop is executed.
+ What's different is that within this otherwise normal PySimpleGUI Event Loop, there is a check for items
+ in the Queue. If there are items found, process them by making GUI changes, and continue.
+
+ This design pattern works for all of the flavors of PySimpleGUI including the Web and also repl.it
+ You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
+"""
+
+
+def long_operation_thread(seconds, gui_queue):
+ """
+ A worker thread that communicates with the GUI through a queue
+ This thread can block for as long as it wants and the GUI will not be affected
+ :param seconds: (int) How long to sleep, the ultimate blocking call
+ :param gui_queue: (queue.Queue) Queue to communicate back to GUI that task is completed
+ :return:
+ """
+ print('Starting thread - will sleep for {} seconds'.format(seconds))
+ time.sleep(seconds) # sleep for a while
+ gui_queue.put('** Done **') # put a message into queue for GUI
+
+
+###### ## ## ####
+## ## ## ## ##
+## ## ## ##
+## #### ## ## ##
+## ## ## ## ##
+## ## ## ## ##
+###### ####### ####
+
+def the_gui():
+ """
+ Starts and executes the GUI
+ Reads data from a Queue and displays the data to the window
+ Returns when the user exits / closes the window
+ """
+
+ gui_queue = queue.Queue() # queue used to communicate between the gui and the threads
+
+ layout = [[sg.Text('Long task to perform example')],
+ [sg.Output(size=(70, 12))],
+ [sg.Text('Number of seconds your task will take'),sg.Input(key='_SECONDS_', size=(5,1)), sg.Button('Do Long Task', bind_return_key=True)],
+ [sg.Button('Click Me'), sg.Button('Exit')], ]
+
+ window = sg.Window('Multithreaded Window').Layout(layout)
+
+ # --------------------- EVENT LOOP ---------------------
+ while True:
+ event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
+ if event is None or event == 'Exit':
+ break
+ elif event.startswith('Do'):
+ try:
+ seconds = int(values['_SECONDS_'])
+ print('Starting thread to do long work....sending value of {} seconds'.format(seconds))
+ threading.Thread(target=long_operation_thread, args=(seconds , gui_queue,), daemon=True).start()
+ except Exception as e:
+ print('Error starting work thread. Did you input a valid # of seconds? You entered: %s' % values['_SECONDS_'])
+ elif event == 'Click Me':
+ print('Your GUI is alive and well')
+ # --------------- Check for incoming messages from threads ---------------
+ try:
+ message = gui_queue.get_nowait()
+ except queue.Empty: # get_nowait() will get exception when Queue is empty
+ message = None # break from the loop if no more messages are queued up
+
+ # if message received from queue, display the message in the Window
+ if message:
+ print('Got a message back from the thread: ', message)
+
+ # if user exits the window, then close the window and exit the GUI func
+ window.Close()
+
+
+## ## ### #### ## ##
+### ### ## ## ## ### ##
+#### #### ## ## ## #### ##
+## ### ## ## ## ## ## ## ##
+## ## ######### ## ## ####
+## ## ## ## ## ## ###
+## ## ## ## #### ## ##
+
+if __name__ == '__main__':
+ the_gui()
+ print('Exiting Program')
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Multithreaded_Queued.py b/DemoPrograms old/Demo_Multithreaded_Queued.py
new file mode 100644
index 00000000..5e397925
--- /dev/null
+++ b/DemoPrograms old/Demo_Multithreaded_Queued.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python3
+
+# Rather than importing individual classes such as threading.Thread or queue.Queue, this
+# program is doing a simple import and then indicating the package name when the functions
+# are called. This seemed like a great way for the reader of the code to get an understanding
+# as to exactly which package is being used. It's purely for educational and explicitness purposes
+import queue
+import threading
+import time
+import itertools
+
+# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
+# To try something other than tkinter version, just comment out the first import and uncomment the one you want
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+# import PySimpleGUIWx as sg
+# import PySimpleGUIWeb as sg
+
+"""
+ DESIGN PATTERN - Multithreaded GUI
+ One method for running multiple threads in a PySimpleGUI environment.
+ The PySimpleGUI code, and thus the underlying GUI framework, runs as the primary, main thread
+ Other parts of the software are implemented as threads
+
+ A queue.Queue is used by the worker threads to communicate with code that calls PySimpleGUI directly.
+ The PySimpleGUI code is structured just like a typical PySimpleGUI program. A layout defined,
+ a Window is created, and an event loop is executed.
+ What's different is that within this otherwise normal PySimpleGUI Event Loop, there is a check for items
+ in the Queue. If there are items found, process them by making GUI changes, and continue.
+
+ This design pattern works for all of the flavors of PySimpleGUI including the Web and also repl.it
+ You'll find a repl.it version here: https://repl.it/@PySimpleGUI/Async-With-Queue-Communicationspy
+"""
+
+
+######## ## ## ######## ######## ### ########
+ ## ## ## ## ## ## ## ## ## ##
+ ## ## ## ## ## ## ## ## ## ##
+ ## ######### ######## ###### ## ## ## ##
+ ## ## ## ## ## ## ######### ## ##
+ ## ## ## ## ## ## ## ## ## ##
+ ## ## ## ## ## ######## ## ## ########
+
+def worker_thread(thread_name, run_freq, gui_queue):
+ """
+ A worker thrread that communicates with the GUI
+ These threads can call functions that block withouth affecting the GUI (a good thing)
+ Note that this function is the code started as each thread. All threads are identical in this way
+ :param thread_name: Text name used for displaying info
+ :param run_freq: How often the thread should run in milliseconds
+ :param gui_queue: Queue used to communicate with the GUI
+ :return:
+ """
+ print('Starting thread - {} that runs every {} ms'.format(thread_name, run_freq))
+ for i in itertools.count(): # loop forever, keeping count in i as it loops
+ time.sleep(run_freq/1000) # sleep for a while
+ gui_queue.put('{} - {}'.format(thread_name, i)) # put a message into queue for GUI
+
+ ###### ## ## ####
+## ## ## ## ##
+## ## ## ##
+## #### ## ## ##
+## ## ## ## ##
+## ## ## ## ##
+ ###### ####### ####
+
+def the_gui(gui_queue):
+ """
+ Starts and executes the GUI
+ Reads data from a Queue and displays the data to the window
+ Returns when the user exits / closes the window
+ (that means it does NOT return until the user exits the window)
+ :param gui_queue: Queue the GUI should read from
+ :return:
+ """
+ layout = [ [sg.Text('Multithreaded Window Example')],
+ [sg.Text('', size=(15,1), key='_OUTPUT_')],
+ [sg.Output(size=(40,6))],
+ [sg.Button('Exit')],]
+
+ window = sg.Window('Multithreaded Window').Layout(layout)
+ # --------------------- EVENT LOOP ---------------------
+ while True:
+ event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
+ if event is None or event == 'Exit':
+ break
+ #--------------- Loop through all messages coming in from threads ---------------
+ while True: # loop executes until runs out of messages in Queue
+ try: # see if something has been posted to Queue
+ message = gui_queue.get_nowait()
+ except queue.Empty: # get_nowait() will get exception when Queue is empty
+ break # break from the loop if no more messages are queued up
+ # if message received from queue, display the message in the Window
+ if message:
+ window.Element('_OUTPUT_').Update(message)
+ window.Refresh() # do a refresh because could be showing multiple messages before next Read
+ print(message)
+ # if user exits the window, then close the window and exit the GUI func
+ window.Close()
+
+
+## ## ### #### ## ##
+### ### ## ## ## ### ##
+#### #### ## ## ## #### ##
+## ### ## ## ## ## ## ## ##
+## ## ######### ## ## ####
+## ## ## ## ## ## ###
+## ## ## ## #### ## ##
+
+if __name__ == '__main__':
+ #-- Create a Queue to communicate with GUI --
+ gui_queue = queue.Queue() # queue used to communicate between the gui and the threads
+ #-- Start worker threads, one runs twice as often as the other
+ threading.Thread(target=worker_thread, args=('Thread 1', 500, gui_queue,), daemon=True).start()
+ threading.Thread(target=worker_thread, args=('Thread 2', 200, gui_queue,), daemon=True).start()
+ threading.Thread(target=worker_thread, args=('Thread 3', 1000, gui_queue,), daemon=True).start()
+ #-- Start the GUI passing in the Queue --
+ the_gui(gui_queue)
+ print('Exiting Program')
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Nice_Buttons.py b/DemoPrograms old/Demo_Nice_Buttons.py
new file mode 100644
index 00000000..2640cd48
--- /dev/null
+++ b/DemoPrograms old/Demo_Nice_Buttons.py
@@ -0,0 +1,74 @@
+import PySimpleGUI as sg
+import os
+import io
+from PIL import Image, ImageDraw, ImageTk, ImageFont
+import base64
+import subprocess
+import sys
+
+
+def image_file_to_bytes(image64, size):
+ image_file = io.BytesIO(base64.b64decode(image64))
+ img = Image.open(image_file)
+ img.thumbnail(size, Image.ANTIALIAS)
+ bio = io.BytesIO()
+ img.save(bio, format='PNG')
+ imgbytes = bio.getvalue()
+ return imgbytes
+
+
+
+def ShowMeTheButtons():
+ bcolor = ('black', 'black')
+ wcolor = ('white', 'black')
+
+ sg.ChangeLookAndFeel('Black')
+ sg.SetOptions(auto_size_buttons=True, border_width=0, button_color=sg.COLOR_SYSTEM_DEFAULT)
+
+ toolbar_buttons = [ [sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45,3))],
+ [sg.Text('All of these buttons are part of the code itself', size=(45,2))],
+
+ [sg.RButton('Next', image_data=image_file_to_bytes(button64, (100,50)),button_color=wcolor, font='Any 15', pad=(0,0), key='_close_'),
+ # [sg.RButton('Exit', image_data=image_file_to_bytes(black64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),],
+ sg.RButton('Submit', image_data=image_file_to_bytes(red_pill64, (100,50)),button_color=wcolor, font='Any 15', pad=(0,0), key='_close_'),
+ sg.RButton('OK', image_data=image_file_to_bytes(green_pill64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),
+ sg.RButton('Exit', image_data=image_file_to_bytes(orange64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),],
+ ]
+
+ # layout = toolbar_buttons
+ layout = [[sg.Frame('Nice Buttons', toolbar_buttons, font=('any 18'), background_color='black')]]
+
+ window = sg.Window('Demo of Nice Looking Buttons',
+ no_titlebar=False,
+ grab_anywhere=True,
+ keep_on_top=True,
+ use_default_focus=False,
+ font='any 15',
+ background_color='black').Layout(layout).Finalize()
+
+ # ---===--- Loop taking in user input --- #
+ while True:
+ button, value = window.Read()
+ print(button)
+ if button == '_close_' or button is None:
+ break # exit button clicked
+
+if __name__ == '__main__':
+
+ # To convert your PNG into Base 64:
+ # Go to https://www.base64-image.de/
+ # Drag and drop your PNG image onto the webpage
+ # Choose "Copy image"
+ # Create a string variable name to hold your image
+ # Paste data from webpage as a string
+ # Delete the "header" stuff - up to the data portion (data:image/png;base64,)
+ orange64 = 'iVBORw0KGgoAAAANSUhEUgAAAiIAAADLCAMAAABkvgh7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANtvJ99sId5tIt5uI91tJNxuJNxuJttvKN90NN91Ntt3PN52ONx3Otx3PNt4Pdp4Ptx4PepfD+pfEOtgD+piD+tkD+ttD+dnFOdoE+doFOVoFuNqG+JrHOFrHuFsHuRpGORpGuRqGORqGupgEOpiEOpkEOlmEOhnEupoEOpoEupqFettEetsF+tuFOxqFetrGOtsGetvHOxrGOxsGOxtGuxuG+xuHexxFutwH+xxGOxyGOxyGuxxHuBsIOJvJ+NvKORvKONwKeNwKuJxLONyLeJyLuRwKeRwKuRxLOtxIexyIexzJOx0JOx1Ju11KOx2KOx2Kux5Iux4Kux5Le1/LeFyMOFzMuB0NOB1Nux7MOx8MOx8Mu19NO1+NO1+Ntd7RdV8RtN+S9J/TdF/TtV9SNR9Stl5QNl6Qdh6Qth7RK+Tfa+Tfq6Ufr+JZb+JZr+KZ7uMbr6KaL6LaryMbLyNb7yOb7WPdLuNcLqOcLmOcrmPdLOQd7SQdrKReLKRerGSerCTfLCTfrmQde2AN+2AOO2BOu2COu6DPO6EPe6EPsWDV8OFXcOFXsOGXcOGXsaDWMSEWsqCVs+AUM2AUs6CU82CVMqDWMmEWtCAT9CAUMKHYMCIYsCIZMCKZu6FQO6GQe6GQu6HRO6IRe6JRu6KR+6KSO+LSu+MS++MTO+OTe+OTu+OUO+RUu+TVfCNTvCOTvCPUPCQUfCQUvCSU/CRVPCSVPCSVvCUV/CUWPCVWvGWWvGXXPGYXvCWYPGaYfGcYvGcZPKdZvKeZ/GdavKeaPKebfKebvKgavKhbPKibfGibvKjcPGkcfKmdPKiePOod/OpePSpePSqevSrfPSsfPSsfvSqg/SugfWwgvWwhPWyhfWyhvWxi/Wyj/W0ifW2jfa4j/a1kPW2kva2lfW2lvW4kPa6kfa7kva5lvW7lPa8lPW8lve+l/a4mva5nPa6nPa7nva+mPa+mva/nPa8oPa9ovbAm/bAnfXAnvbCofbEo/XGp/bGqPbIqQAAAC/NnaUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAAJXElEQVR4Xu3de3zVdR3H8Xb9/sJh60LlOTtubdIZEyckSdedMzkdZyRGIePmZdqki8s0KllJmtGCTAXRlUAyrireUeZga2JRkooYRWR0JbXMLsIqZ5fT7/L5nRvj9/md0/fhw/Px/fyHP+D33fb4vPj+LvsxXpMA8IREgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgJF7Ih1Xf3bVsuu/AYXu+mUrL+u8iqbqIcdErvhme0V8SjQCIpwWe+0nli+k2R5NTolc0jYzqpQBcigV+UDltZ57SQ6JfLqyRRXRyiCIis7u7qAhj8B3Ip1tLdg/pFLR0Z+hOR/JbyIrWxGIZCp289E2Ep+J3HzEFqLKy0qhUJUfeUkZnf8lGnYWX4l0tDfTOg5VEqoJNNTXh6EgmaMLBKuaymmcJFLRSfPO5CuRtgitYisPBehDQUFrqC3O2EtUxRdo4Bn8JHJhlNawlNZh85Aj2JQeiRp9JY08nY9Euk+nFUwq1ECLgww1ZTRaS+R8mnk6PpEF8dTDkNIgLQxiHFdNw7U0d9PU07CJdLw+tRUVpy5Cxo19+bz9lYI+n5cNfdhcjWGZf2jsCTTJcDiUmnBR/HKaewqbSHfqQqSJrkLGvuuctZu3jOgBD1u99B7Fg6a+Vwrrk8kNfRX+0XE2+qg527adsc38M723d01yK3lbqhH1SZp7CpfIolnJw4udQk44ddPOn/3q6b+M6K8+/c2vF+jXVw29X/ALI7N+Z3jome+f/QZ7pOG6VCMtl9Dkk7hElifvd0uPs1cb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'
+
+ green_pill64 = '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'
+
+ red_pill64 = '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'
+
+ button64 = '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'
+
+ ShowMeTheButtons()
+
diff --git a/DemoPrograms old/Demo_NonBlocking_Form.py b/DemoPrograms old/Demo_NonBlocking_Form.py
new file mode 100644
index 00000000..f3561f7c
--- /dev/null
+++ b/DemoPrograms old/Demo_NonBlocking_Form.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import time
+
+# Window that doen't block
+# good for applications with an loop that polls hardware
+def StatusOutputExample():
+ # Create a text element that will be updated with status information on the GUI itself
+ # Create the rows
+ layout = [[sg.Text('Non-blocking GUI with updates')],
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='output')],
+ [sg.Button('LED On'), sg.Button('LED Off'), sg.Button('Quit')]]
+ # Layout the rows of the Window and perform a read. Indicate the Window is non-blocking!
+ window = sg.Window('Running Timer', auto_size_text=True).Layout(layout)
+
+ #
+ # Some place later in your code...
+ # You need to perform a Read on your window every now and then or
+ # else it won't refresh.
+ #
+ # your program's main loop
+ i=0
+ while (True):
+ # This is the code that reads and updates your window
+ event, values = window.Read(timeout=10)
+ window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100))
+ if event in ('Quit', None):
+ break
+ if event == 'LED On':
+ print('Turning on the LED')
+ elif event == 'LED Off':
+ print('Turning off the LED')
+
+ i += 1
+ # Your code begins here
+
+ # Broke out of main loop. Close the window.
+ window.Close()
+
+
+def RemoteControlExample():
+
+ layout = [[sg.Text('Robotics Remote Control')],
+ [sg.T(' '*10), sg.RealtimeButton('Forward')],
+ [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')],
+ [sg.T(' '*10), sg.RealtimeButton('Reverse')],
+ [sg.T('')],
+ [sg.Quit(button_color=('black', 'orange'))]
+ ]
+
+ window = sg.Window('Robotics Remote Control', auto_size_text=True).Layout(layout).Finalize()
+
+ #
+ # Some place later in your code...
+ # You need to perform a ReadNonBlocking on your window every now and then or
+ # else it won't refresh.
+ #
+ # your program's main loop
+ while (True):
+ # This is the code that reads and updates your window
+ event, values = window.Read(timeout=0, timeout_key='timeout')
+ if event != 'timeout':
+ print(event)
+ if event in ('Quit', None):
+ break
+
+ window.Close()
+
+
+def main():
+ RemoteControlExample()
+ StatusOutputExample()
+ sg.Popup('End of non-blocking demonstration')
+
+
+if __name__ == '__main__':
+
+ main()
diff --git a/DemoPrograms old/Demo_Notification_Window_Alpha_Channel.py b/DemoPrograms old/Demo_Notification_Window_Alpha_Channel.py
new file mode 100644
index 00000000..ea645eae
--- /dev/null
+++ b/DemoPrograms old/Demo_Notification_Window_Alpha_Channel.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+import sys
+import time
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+# Demonstrates a notification window that's partially transparent
+# The window slowly fades-in
+# Includes a small red-X button to close the window
+# Base 64 encoded button is in-lined to avoid reading a file
+# Free online encoder - https://www.base64-image.de/
+red_x ="R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+
+
+sg.ChangeLookAndFeel('Topanga')
+sg.SetOptions(border_width=0, margins=(0,0))
+bcolor=('black', '#282923')
+
+sg.SetOptions(border_width=0, margins=(0,0))
+
+layout = [[sg.T('Notification'+' '*14),
+ sg.CloseButton('', image_data=red_x, button_color=('#282923', '#282923'))],
+ [sg.T('')],
+ [sg.T('You have 6 new emails')],]
+
+window = sg.Window('',
+ no_titlebar=True,
+ grab_anywhere=True,
+ keep_on_top=True,
+ alpha_channel=0
+ ).Layout(layout).Finalize()
+
+# Classy fade-in
+for i in range(1, 75, 2):
+ window.AlphaChannel = float(i)/100
+ time.sleep(.01)
+
+event, values = window.Read()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_OpenCV.py b/DemoPrograms old/Demo_OpenCV.py
new file mode 100644
index 00000000..194980af
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import cv2 as cv
+from PIL import Image
+import io
+from sys import exit as exit
+
+"""
+Demo program to open and play a file using OpenCV
+It's main purpose is to show you:
+1. How to get a frame at a time from a video file using OpenCV
+2. How to display an image in a PySimpleGUI Window
+
+For added fun, you can reposition the video using the slider.
+"""
+
+def main():
+ # ---===--- Get the filename --- #
+ filename = sg.PopupGetFile('Filename to play')
+ if filename is None:
+ exit(69)
+ vidFile = cv.VideoCapture(filename)
+ # ---===--- Get some Stats --- #
+ num_frames = vidFile.get(cv.CAP_PROP_FRAME_COUNT)
+ fps = vidFile.get(cv.CAP_PROP_FPS)
+
+ sg.ChangeLookAndFeel('Black')
+
+ # ---===--- define the window layout --- #
+ layout = [[sg.Text('OpenCV Demo', size=(15, 1), font='Helvetica 20')],
+ [sg.Image(filename='', key='_image_')],
+ [sg.Slider(range=(0, num_frames), size=(60, 10), orientation='h', key='_slider_')],
+ [sg.Button('Exit', size=(7, 1), pad=((600, 0), 3), font='Helvetica 14')]]
+
+ # create the window and show it without the plot
+ window = sg.Window('Demo Application - OpenCV Integration', no_titlebar=False, location=(0,0)).Layout(layout)
+
+ image_elem = window.Element('_image_') # locate the elements we'll be updating. Does the search only 1 time
+ slider_elem = window.Element('_slider_')
+
+ # ---===--- LOOP through video file by frame --- #
+ cur_frame = 0
+ while vidFile.isOpened():
+ event, values = window.Read(timeout=0)
+ if event in ('Exit', None):
+ exit(69)
+ ret, frame = vidFile.read()
+ if not ret: # if out of data stop looping
+ break
+ if int(values['_slider_']) != cur_frame-1: # if someone moved the slider manually, the jump to that frame
+ cur_frame = int(values['_slider_'])
+ vidFile.set(cv.CAP_PROP_POS_FRAMES, cur_frame)
+ slider_elem.Update(cur_frame)
+ cur_frame += 1
+
+ imgbytes = cv.imencode('.png', frame)[1].tobytes() # ditto
+ image_elem.Update(data=imgbytes)
+
+"""
+ # This was another way updates were being done, but seems slower than the above
+ img = Image.fromarray(frame) # create PIL image from frame
+ bio = io.BytesIO() # a binary memory resident stream
+ img.save(bio, format= 'PNG') # save image as png to it
+ imgbytes = bio.getvalue() # this can be used by OpenCV hopefully
+ image_elem.Update(data=imgbytes)
+"""
+
+main()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_OpenCV_4_Line_Program.py b/DemoPrograms old/Demo_OpenCV_4_Line_Program.py
new file mode 100644
index 00000000..6e032cf3
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_4_Line_Program.py
@@ -0,0 +1,4 @@
+import cv2, PySimpleGUI as sg
+window, cap = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')],], location=(800,400)), cv2.VideoCapture(0)
+while window(timeout=20)[0] is not None:
+ window['image'](data=cv2.imencode('.png', cap.read()[1])[1].tobytes())
diff --git a/DemoPrograms old/Demo_OpenCV_7_Line_Program.py b/DemoPrograms old/Demo_OpenCV_7_Line_Program.py
new file mode 100644
index 00000000..b1cc0ff7
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_7_Line_Program.py
@@ -0,0 +1,13 @@
+import cv2, PySimpleGUI as sg
+window = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')],], location=(800,400))
+cap = cv2.VideoCapture(0) # Setup the camera as a capture device
+while True: # The PSG "Event Loop"
+ event, values = window.Read(timeout=20, timeout_key='timeout') # get events for the window with 20ms max wait
+ if event is None: break # if user closed window, quit
+ window.FindElement('image').Update(data=cv2.imencode('.png', cap.read()[1])[1].tobytes()) # Update image in window
+
+"""
+Putting the comment at the bottom so that you can see that the code is indeed 7 lines long. And, there is nothing
+done out of the ordinary to make it 7 lines long. There are no ; for example. OK, so the if statement is on one line
+but that's the only place that you would traditionally see one more line. So, call it 8 if you want.
+"""
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_OpenCV_Draw_On_Webcam_Image.py b/DemoPrograms old/Demo_OpenCV_Draw_On_Webcam_Image.py
new file mode 100644
index 00000000..46a1cdb4
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_Draw_On_Webcam_Image.py
@@ -0,0 +1,38 @@
+import PySimpleGUI as sg
+import cv2
+
+"""
+ Demonstration of how to use a GRAPH ELEMENT to draw a webcam stream using OpenCV and PySimpleGUI.
+ Additionally, the thing this demo is really showcasing, is the ability to draw over the top of this
+ webcam stream, as it's being displayed. To "Draw" simply move your mouse over the image, left click and hold, and
+ then drag your mouse. You'll see a series of red circles on top of your image.
+ CURRENTLY ONLY WORKS WITH PySimpleGUI, NOT any of the other ports at this time.
+"""
+
+def main():
+ layout = [[sg.Graph((600,450),(0,450), (600,0), key='_GRAPH_', enable_events=True, drag_submits=True)],]
+
+ window = sg.Window('Demo Application - OpenCV Integration', layout)
+
+ graph_elem = window.Element('_GRAPH_') # type: sg.Graph
+
+ id = None
+ # ---===--- Event LOOP Read and display frames, operate the GUI --- #
+ cap = cv2.VideoCapture(0)
+ while True:
+ event, values = window.Read(timeout=0)
+ if event in ('Exit', None):
+ break
+
+ ret, frame = cap.read()
+ imgbytes=cv2.imencode('.png', frame)[1].tobytes()
+ if id:
+ graph_elem.DeleteFigure(id) # delete previous image
+ id = graph_elem.DrawImage(data=imgbytes, location=(0,0)) # draw new image
+ graph_elem.TKCanvas.tag_lower(id) # move image to the "bottom" of all other drawings
+
+ if event == '_GRAPH_':
+ graph_elem.DrawCircle(values['_GRAPH_'], 5, fill_color='red', line_color='red')
+ window.Close()
+
+main()
diff --git a/DemoPrograms old/Demo_OpenCV_Simple_GUI.py b/DemoPrograms old/Demo_OpenCV_Simple_GUI.py
new file mode 100644
index 00000000..7399dc96
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_Simple_GUI.py
@@ -0,0 +1,88 @@
+import sys
+
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import cv2
+import numpy as np
+from sys import exit as exit
+
+"""
+Demo program that displays a webcam using OpenCV and applies some very basic image functions
+
+- functions from top to bottom -
+none: no processing
+threshold: simple b/w-threshold on the luma channel, slider sets the threshold value
+canny: edge finding with canny, sliders set the two threshold values for the function => edge sensitivity
+contour: colour finding in the frame, first slider sets the hue for the colour to find, second the minimum saturation
+ for the object. Found objects are drawn with a red contour.
+blur: simple Gaussian blur, slider sets the sigma, i.e. the amount of blur smear
+hue: moves the image hue values by the amount selected on the slider
+enhance: applies local contrast enhancement on the luma channel to make the image fancier - slider controls fanciness.
+"""
+
+
+def main():
+ sg.ChangeLookAndFeel('LightGreen')
+
+ # define the window layout
+ layout = [[sg.Text('OpenCV Demo', size=(40, 1), justification='center')],
+ [sg.Image(filename='', key='image')],
+ [sg.Radio('None', 'Radio', True, size=(10, 1))],
+ [sg.Radio('threshold', 'Radio', size=(10, 1), key='thresh'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(40, 15), key='thresh_slider')],
+ [sg.Radio('canny', 'Radio', size=(10, 1), key='canny'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_a'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_b')],
+ [sg.Radio('contour', 'Radio', size=(10, 1), key='contour'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='contour_slider'),
+ sg.Slider((0, 255), 80, 1, orientation='h', size=(20, 15), key='base_slider')],
+ [sg.Radio('blur', 'Radio', size=(10, 1), key='blur'),
+ sg.Slider((1, 11), 1, 1, orientation='h', size=(40, 15), key='blur_slider')],
+ [sg.Radio('hue', 'Radio', size=(10, 1), key='hue'),
+ sg.Slider((0, 225), 0, 1, orientation='h', size=(40, 15), key='hue_slider')],
+ [sg.Radio('enhance', 'Radio', size=(10, 1), key='enhance'),
+ sg.Slider((1, 255), 128, 1, orientation='h', size=(40, 15), key='enhance_slider')],
+ [sg.Button('Exit', size=(10, 1))]]
+
+ # create the window and show it without the plot
+ window = sg.Window('Demo Application - OpenCV Integration',
+ location=(800, 400))
+ window.Layout(layout).Finalize()
+
+ cap = cv2.VideoCapture(0)
+ while True:
+ event, values = window.Read(timeout=0, timeout_key='timeout')
+ if event == 'Exit' or event is None:
+ sys.exit(0)
+ ret, frame = cap.read()
+ if values['thresh']:
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)[:, :, 0]
+ _, frame = cv2.threshold(frame, values['thresh_slider'], 255, cv2.THRESH_BINARY)
+ if values['canny']:
+ frame = cv2.Canny(frame, values['canny_slider_a'], values['canny_slider_b'])
+ if values['blur']:
+ frame = cv2.GaussianBlur(frame, (21, 21), values['blur_slider'])
+ if values['hue']:
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
+ frame[:, :, 0] += values['hue_slider']
+ frame = cv2.cvtColor(frame, cv2.COLOR_HSV2BGR)
+ if values['enhance']:
+ enh_val = values['enhance_slider'] / 40
+ clahe = cv2.createCLAHE(clipLimit=enh_val, tileGridSize=(8, 8))
+ lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
+ lab[:, :, 0] = clahe.apply(lab[:, :, 0])
+ frame = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
+ if values['contour']:
+ hue = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
+ hue = cv2.GaussianBlur(hue, (21, 21), 1)
+ hue = cv2.inRange(hue, np.array([values['contour_slider'], values['base_slider'], 40]),
+ np.array([values['contour_slider'] + 30, 255, 220]))
+ _, cnts, _ = cv2.findContours(hue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
+ cv2.drawContours(frame, cnts, -1, (0, 0, 255), 2)
+ imgbytes = cv2.imencode('.png', frame)[1].tobytes() # ditto
+ window.FindElement('image').Update(data=imgbytes)
+
+
+main()
diff --git a/DemoPrograms old/Demo_OpenCV_Webcam.py b/DemoPrograms old/Demo_OpenCV_Webcam.py
new file mode 100644
index 00000000..287f485a
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_Webcam.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+import cv2
+import numpy as np
+import sys
+from sys import exit as exit
+
+"""
+Demo program that displays a webcam using OpenCV
+"""
+def main():
+
+ sg.ChangeLookAndFeel('Black')
+
+ # define the window layout
+ layout = [[sg.Text('OpenCV Demo', size=(40, 1), justification='center', font='Helvetica 20')],
+ [sg.Image(filename='', key='image')],
+ [sg.Button('Record', size=(10, 1), font='Helvetica 14'),
+ sg.Button('Stop', size=(10, 1), font='Any 14'),
+ sg.Button('Exit', size=(10, 1), font='Helvetica 14'),]]
+
+ # create the window and show it without the plot
+ window = sg.Window('Demo Application - OpenCV Integration', layout,
+ location=(800,400))
+
+ # ---===--- Event LOOP Read and display frames, operate the GUI --- #
+ cap = cv2.VideoCapture(0)
+ recording = False
+ while True:
+ event, values = window.Read(timeout=20)
+ if event == 'Exit' or event is None:
+ sys.exit(0)
+ elif event == 'Record':
+ recording = True
+ elif event == 'Stop':
+ recording = False
+ img = np.full((480, 640),255)
+ imgbytes=cv2.imencode('.png', img)[1].tobytes() #this is faster, shorter and needs less includes
+ window.FindElement('image').Update(data=imgbytes)
+
+ if recording:
+ ret, frame = cap.read()
+ imgbytes=cv2.imencode('.png', frame)[1].tobytes() #ditto
+ window.FindElement('image').Update(data=imgbytes)
+
+main()
+exit()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_OpenCV_Webcam_ASCII.py b/DemoPrograms old/Demo_OpenCV_Webcam_ASCII.py
new file mode 100644
index 00000000..61232d00
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_Webcam_ASCII.py
@@ -0,0 +1,67 @@
+from PIL import Image
+import numpy as np
+import PySimpleGUI as sg; font_size=6; USING_QT=False
+# import PySimpleGUIQt as sg; font_size=8; USING_QT=True # if using, be sure and use the second layout that is commented out
+# import PySimpleGUIWeb as sg; font_size=12; USING_QT=False # yes, it runs in a webpage too! Not as good as tkinter but works
+import cv2
+
+"""
+ Interesting program that shows your webcam's image as ASCII text. Runs in realtime, producing a stream of
+ images so that it is actually animated ASCII text. Wild stuff that came about from a post on Reddit of all
+ places. The software bits that turn the image into ASCII text were shamelessly taken from this gist:
+ https://gist.github.com/cdiener/10491632
+ Brilliant work to have pulled off so much with so little Numpy
+ What's remarkable about this program is that the animation is created by updating individual Text Elements going
+ down the window, one line at a time, every time through the loop. That's 48 lines of text every time. Rough
+ timing shows an animation of more than 10 fps when running any of the PySimpleGUI ports.
+ Also added onto this are a spinner and a slider. They do essentially the same thing, enable a pair of parameters
+ to be modified on the fly.
+
+ You need PySimpleGUI installed as well as OpenCV. Both are easily installed via pip:
+ pip install PySimpleGUI
+ pip install opencv-python
+
+ On Linux / Mac use pip3 instead of pip
+"""
+
+# The magic bits that make the ASCII stuff work shamelessly taken from https://gist.github.com/cdiener/10491632
+chars = np.asarray(list(' .,:;irsXA253hMHGS#9B&@'))
+SC, GCF, WCF = .1, 1, 7/4
+
+sg.ChangeLookAndFeel('Black') # make it look cool
+
+# define the window layout
+NUM_LINES = 48 # number of lines of text elements. Depends on cameras image size and the variable SC (scaller)
+if USING_QT:
+ layout = [[sg.T(i, size_px=(800, 12), font=('Courier', font_size), key='_OUT_' + str(i))] for i in range(NUM_LINES)]
+else:
+ layout = [[sg.T(i,size=(120,1), font=('Courier', font_size), pad=(0,0), key='_OUT_'+str(i))] for i in range(NUM_LINES)]
+
+layout += [[ sg.Button('Exit', size=(5,1)),
+ sg.T('GCF', size=(4,1)), sg.Spin([round(i,2) for i in np.arange(0.1,20.0,0.1)], initial_value=1, key='_SPIN_GCF_', size=(5,1)),
+ sg.T('WCF', size=(4,1)), sg.Slider((1,4), resolution=.05, default_value=1.75, orientation='h', key='_SLIDER_WCF_', size=(15,15))]]
+
+# create the window and show it without the plot
+window = sg.Window('Demo Application - OpenCV Integration', layout, location=(800,400), font='Any 18')
+
+# ---===--- Event LOOP Read and display frames, operate the GUI --- #
+cap = cv2.VideoCapture(0) # Setup the OpenCV capture device (webcam)
+while True:
+ event, values = window.Read(timeout=0)
+ if event in ('Exit', None):
+ break
+ ret, frame = cap.read() # Read image from capture device (camera)
+
+ img = Image.fromarray(frame) # create PIL image from frame
+ GCF = float(values['_SPIN_GCF_'])
+ WCF = values['_SLIDER_WCF_']
+ # More magic that coverts the image to ascii
+ S = (round(img.size[0] * SC * WCF), round(img.size[1] * SC))
+ img = np.sum(np.asarray(img.resize(S)), axis=2)
+ img -= img.min()
+ img = (1.0 - img / img.max()) ** GCF * (chars.size - 1)
+
+ # "Draw" the image in the window, one line of text at a time!
+ for i, r in enumerate(chars[img.astype(int)]):
+ window.Element('_OUT_'+str(i)).Update("".join(r))
+window.Close()
diff --git a/DemoPrograms old/Demo_OpenCV_Webcam_Minimal.py b/DemoPrograms old/Demo_OpenCV_Webcam_Minimal.py
new file mode 100644
index 00000000..3d08fa03
--- /dev/null
+++ b/DemoPrograms old/Demo_OpenCV_Webcam_Minimal.py
@@ -0,0 +1,31 @@
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg
+# import PySimpleGUIWeb as sg # has a known flicker problem that's being worked
+import cv2
+
+"""
+ Demo of using OpenCV to show your webcam in a GUI window.
+ This demo will run on tkinter, Qt, and Web(Remi). The web version flickers at the moment though
+ To exit, right click and choose exit. If on Qt, you'll have to kill the program as there are no right click menus
+ in PySimpleGUIQt (yet).
+"""
+
+sg.ChangeLookAndFeel('Black')
+
+# define the window layout
+layout = [[sg.Image(filename='', key='_IMAGE_', tooltip='Right click for exit menu')],]
+
+# create the window and show it without the plot
+window = sg.Window('Demo Application - OpenCV Integration', layout, location=(800,400),
+ no_titlebar=True, grab_anywhere=True,
+ right_click_menu=['&Right', ['E&xit']], ) # if trying Qt, you will need to remove this right click menu
+
+# ---===--- Event LOOP Read and display frames, operate the GUI --- #
+cap = cv2.VideoCapture(0) # Setup the OpenCV capture device (webcam)
+while True:
+ event, values = window.Read(timeout=20, timeout_key='timeout')
+ if event in ('Exit', None):
+ break
+ ret, frame = cap.read() # Read image from capture device (camera)
+ imgbytes=cv2.imencode('.png', frame)[1].tobytes() # Convert the image to PNG Bytes
+ window.FindElement('_IMAGE_').Update(data=imgbytes) # Change the Image Element to show the new image
diff --git a/DemoPrograms old/Demo_PDF_Viewer.py b/DemoPrograms old/Demo_PDF_Viewer.py
new file mode 100644
index 00000000..48671c6d
--- /dev/null
+++ b/DemoPrograms old/Demo_PDF_Viewer.py
@@ -0,0 +1,187 @@
+"""
+@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 window.
+
+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
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+from sys import exit as exit
+from binascii import hexlify
+
+sg.ChangeLookAndFeel('GreenTan')
+
+if len(sys.argv) == 1:
+ fname = sg.PopupGetFile('PDF Browser', 'PDF file to open', file_types=(("PDF Files", "*.pdf"),))
+ if fname is None:
+ sg.PopupCancel('Cancelling')
+ exit(0)
+else:
+ fname = sys.argv[1]
+
+doc = fitz.open(fname)
+page_count = len(doc)
+
+# 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: # bot-right quadrant
+ clip = fitz.Rect(mp, r.br)
+ elif zoom == 2: # top-right
+ clip = fitz.Rect(mt, mr)
+ elif zoom == 3: # bot-left
+ clip = fitz.Rect(ml, mb)
+ if zoom == 0: # total page
+ pix = dlist.getPixmap(alpha=False)
+ else:
+ pix = dlist.getPixmap(alpha=False, matrix=mat, clip=clip)
+ return pix.getPNGData() # return the PNG image
+
+
+window = sg.Window(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)
+goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True)
+
+layout = [
+ [
+ sg.Button('Prev'),
+ sg.Button('Next'),
+ sg.Text('Page:'),
+ goto,
+ ],
+ [
+ sg.Text("Zoom:"),
+ sg.Button('Top-L'),
+ sg.Button('Top-R'),
+ sg.Button('Bot-L'),
+ sg.Button('Bot-R'),
+ ],
+ [image_elem],
+]
+
+window.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.
+
+while True:
+ event, values = window.Read(timeout=100)
+ zoom = 0
+ force_page = False
+ if event is None:
+ break
+
+ if event in ("Escape:27",): # this spares me a 'Quit' button!
+ break
+ # print("hex(button)", hexlify(button.encode()))
+ if event[0] == chr(13): # surprise: this is 'Enter'!
+ try:
+ cur_page = int(values[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 event in ("Next", "Next:34", "MouseWheel:Down"):
+ cur_page += 1
+ elif event in ("Prev", "Prior:33", "MouseWheel:Up"):
+ cur_page -= 1
+ elif event == "Top-L":
+ zoom = 1
+ elif event == "Top-R":
+ zoom = 2
+ elif event == "Bot-L":
+ zoom = 3
+ elif event == "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 event 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 event in my_keys or not values[0]:
+ goto.Update(str(cur_page + 1))
+ # goto.TKStringVar.set(str(cur_page + 1))
diff --git a/DemoPrograms old/Demo_PNG_Thumbnail_Viewer.py b/DemoPrograms old/Demo_PNG_Thumbnail_Viewer.py
new file mode 100644
index 00000000..b11a71bd
--- /dev/null
+++ b/DemoPrograms old/Demo_PNG_Thumbnail_Viewer.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import os
+from sys import exit as exit
+from PIL import Image
+import io
+import numpy as np
+
+thumbnails = {}
+
+ROWS = 8
+COLUMNS = 8
+sg.SetOptions(border_width=0)
+# Get the folder containing the images from the user
+# folder = 'A:/TEMP/pdfs'
+folder = sg.PopupGetFolder('Image folder to open')
+if folder is None:
+ sg.PopupCancel('Cancelling')
+ exit(0)
+def image_file_to_bytes(filename, size):
+ try:
+ image = Image.open(filename)
+ image.thumbnail(size, Image.ANTIALIAS)
+ bio = io.BytesIO() # a binary memory resident stream
+ image.save(bio, format='PNG') # save image as png to it
+ imgbytes = bio.getvalue()
+ except:
+ imgbytes = None
+ return imgbytes
+
+def set_image_to_blank(key):
+ img = Image.new('RGB', (100, 100), (255, 255, 255))
+ img.thumbnail((1, 1), Image.ANTIALIAS)
+ bio = io.BytesIO()
+ img.save(bio, format='PNG')
+ imgbytes = bio.getvalue()
+ window.FindElement(key).Update(image_data=imgbytes)
+
+
+
+# get list of PNG files in folder
+png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f]
+filenames_only = [f for f in os.listdir(folder) if '.png' in f]
+
+if len(png_files) == 0:
+ sg.Popup('No PNG images in folder')
+ exit(0)
+
+# define menu layout
+menu = [['&File', ['&Open Folder', 'E&xit']], ['&Help', ['&About',]]]
+
+buttons = []
+for display_index in range(ROWS):
+ row = []
+ for j in range(COLUMNS):
+ row.append(sg.Button('',border_width=0,button_color=sg.COLOR_SYSTEM_DEFAULT, key=(display_index, j)))
+ buttons.append(row)
+
+col_buttons = [[]]
+
+# define layout, show and read the window
+col = [[sg.Text(png_files[0], size=(80, 3), key='filename')],
+ [sg.Image(data=image_file_to_bytes(png_files[0], (500,500)), key='image')],]
+
+layout = [[sg.Menu(menu)], [sg.Column(buttons), sg.Column([[sg.Slider((len(png_files),0),default_value=0,size=(38,20),orientation='v', key='_slider_', change_submits=True)]]), sg.Column(col)]]
+window = sg.Window('Image Browser',
+ return_keyboard_events=True,
+ use_default_focus=False ).Layout(layout).Finalize()
+
+# -------========= Event Loop =========--------
+display_index=0
+while True:
+ for x in range(ROWS): # update thumbnails
+ for y in range(COLUMNS):
+ cur_index = display_index + (x * 4) + y
+ if cur_index < len(png_files):
+ filename = png_files[cur_index]
+ if filename not in thumbnails:
+ imgbytes = image_file_to_bytes(filename, (100,100))
+ thumbnails[filename] = imgbytes
+ else:
+ imgbytes = thumbnails[filename]
+ button_elem = window.FindElement(key=(x,y))
+ button_elem.Update(image_data=imgbytes)
+ else:
+ set_image_to_blank((x,y))
+
+ event, values = window.Read()
+ display_index = values['_slider_']
+ # --------------------- Button & Keyboard ---------------------
+ if event in (None, 'Exit'):
+ break
+ elif event in ('MouseWheel:Down', 'Down:40',) and display_index < len(png_files)-1:
+ display_index += 4
+ elif event in ('MouseWheel:Up', 'Up:38',) and display_index > 0:
+ display_index -= 4
+ elif event in ('Prior:33', 'Prev'):
+ display_index -= 16
+ elif event in ('Next:34', 'Next'):
+ display_index += 16
+
+ window.FindElement('_slider_').Update(display_index)
+ # ----------------- Menu choices -----------------
+ if event == 'Open Folder':
+ newfolder = sg.PopupGetFolder('New folder', no_window=True)
+ if newfolder is None:
+ continue
+ folder = newfolder
+ png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f]
+ filenames_only = [f for f in os.listdir(folder) if '.png' in f]
+ display_index = 0
+ thumbnail = {}
+ for j in range(ROWS):
+ for i in range(COLUMNS):
+ set_image_to_blank((i,j))
+ elif event == 'About':
+ sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!')
+ elif type(event) is tuple:
+ x, y = event
+ image_index = display_index + (x * 4) + y
+ if image_index < len(png_files):
+ filename = png_files[image_index]
+ imgbytes = image_file_to_bytes(filename, (500, 500))
+ window.FindElement('image').Update(data=imgbytes)
+ window.FindElement('filename').Update(filename)
+
diff --git a/DemoPrograms old/Demo_PNG_Viewer.py b/DemoPrograms old/Demo_PNG_Viewer.py
new file mode 100644
index 00000000..4fba5b1a
--- /dev/null
+++ b/DemoPrograms old/Demo_PNG_Viewer.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import os
+from sys import exit as exit
+
+# Simple Image Browser based on PySimpleGUI
+
+# Get the folder containing the images from the user
+folder = sg.PopupGetFolder('Image folder to open')
+if folder is None:
+ sg.PopupCancel('Cancelling')
+ exit(0)
+
+# get list of PNG files in folder
+png_files = [folder + '\\' + f for f in os.listdir(folder) if '.png' in f]
+filenames_only = [f for f in os.listdir(folder) if '.png' in f]
+
+if len(png_files) == 0:
+ sg.Popup('No PNG images in folder')
+ exit(0)
+
+
+# define menu layout
+menu = [['File', ['Open Folder', 'Exit']], ['Help', ['About',]]]
+
+# define layout, show and read the window
+col = [[sg.Text(png_files[0], size=(80, 3), key='filename')],
+ [sg.Image(filename=png_files[0], key='image')],
+ [sg.Button('Next', size=(8,2)), sg.Button('Prev', size=(8,2)),
+ sg.Text('File 1 of {}'.format(len(png_files)), size=(15,1), key='filenum')]]
+
+col_files = [[sg.Listbox(values=filenames_only, size=(60,30), key='listbox')],
+ [sg.Button('Read')]]
+layout = [[sg.Menu(menu)], [sg.Column(col_files), sg.Column(col)]]
+window = sg.Window('Image Browser', return_keyboard_events=True, location=(0,0), use_default_focus=False ).Layout(layout)
+
+# loop reading the user input and displaying image, filename
+i=0
+while True:
+
+ event, values = window.Read()
+ # --------------------- Button & Keyboard ---------------------
+ if event is None:
+ break
+ elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34') and i < len(png_files)-1:
+ i += 1
+ elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33') and i > 0:
+ i -= 1
+ elif event == 'Exit':
+ exit(69)
+
+ filename = folder + '/' + values['listbox'][0] if event == 'Read' else png_files[i]
+
+ # ----------------- Menu choices -----------------
+ if event == 'Open Folder':
+ newfolder = sg.PopupGetFolder('New folder', no_window=True)
+ if newfolder is None:
+ continue
+ folder = newfolder
+ png_files = [folder + '/' + f for f in os.listdir(folder) if '.png' in f]
+ filenames_only = [f for f in os.listdir(folder) if '.png' in f]
+ window.FindElement('listbox').Update(values=filenames_only)
+ window.Refresh()
+ i = 0
+ elif event == 'About':
+ sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!')
+
+ # update window with new image
+ window.FindElement('image').Update(filename=filename)
+ # update window with filename
+ window.FindElement('filename').Update(filename)
+ # update page display
+ window.FindElement('filenum').Update('File {} of {}'.format(i+1, len(png_files)))
+
diff --git a/DemoPrograms old/Demo_Paned_Window.py b/DemoPrograms old/Demo_Paned_Window.py
new file mode 100644
index 00000000..0bc884ac
--- /dev/null
+++ b/DemoPrograms old/Demo_Paned_Window.py
@@ -0,0 +1,45 @@
+import PySimpleGUI as sg
+
+sg.ChangeLookAndFeel('GreenTan')
+
+col1 = sg.Column([[sg.Text('in pane1', text_color='blue')],
+ [sg.T('Pane1')],
+ [sg.T('Pane1')],
+ ])
+col2 = sg.Column([[sg.Text('in pane2', text_color='red')],
+ [sg.T('Pane2')],
+ [sg.Input(key='_IN2_', do_not_clear=True)],
+ [sg.T('Pane2')],
+ [sg.T('Pane2')],
+ ], key='_COL2_', visible=False)
+col3 = sg.Column([[sg.Text('in pane 4', text_color='green')],
+ [sg.In(key='_IN3_', enable_events=True, do_not_clear=True)],
+ ], key='_COL3_', visible=False)
+col4 = sg.Column([[sg.Text('Column 4', text_color='firebrick')],
+ [sg.In()],
+ ], key='_COL4_')
+col5 = sg.Column([[sg.Frame('Frame', [[sg.Text('Column 5', text_color='purple')],
+ [sg.In()],
+ ])]])
+
+layout = [ [sg.Text('Click'), sg.Text('', key='_OUTPUT_')],
+ [sg.Button('Remove'), sg.Button('Add')],
+ [sg.Pane([col5, sg.Column([[sg.Pane([col1, col2, col4], handle_size=15, orientation='v', background_color='red', show_handle=True, visible=True, key='_PANE_', border_width=0, relief=sg.RELIEF_GROOVE),]]),col3 ], orientation='h', background_color=None, size=(160,160), relief=sg.RELIEF_RAISED, border_width=0)]
+ ]
+
+window = sg.Window('Window Title', default_element_size=(15,1), resizable=True, border_depth=5).Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ print(event, values)
+ if event is None or event == 'Exit':
+ break
+ if event == 'Remove':
+ window.Element('_COL2_').Update(visible=False)
+ window.Element('_COL3_').Update(visible=False)
+ elif event == 'Add':
+ window.Element('_COL2_').Update(visible=True)
+ window.Element('_COL3_').Update(visible=True)
+ window.Element('_IN2_').Update(values['_IN3_'])
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Password_Login.py b/DemoPrograms old/Demo_Password_Login.py
new file mode 100644
index 00000000..1f7abfbd
--- /dev/null
+++ b/DemoPrograms old/Demo_Password_Login.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import hashlib
+from sys import exit as exit
+
+"""
+ Create a secure login for your scripts without having to include your password
+ in the program. Create an SHA1 hash code for your password using the GUI. Paste into variable in final program
+ 1. Choose a password
+ 2. Generate a hash code for your chosen password by running program and entering 'gui' as the password
+ 3. Type password into the GUI
+ 4. Copy and paste hash code from GUI into variable named login_password_hash
+ 5. Run program again and test your login!
+ 6. Are you paying attention? The first person that can post an issue on GitHub with the
+ matching password to the hash code in this example gets a $5 PayPal payment
+"""
+
+# Use this GUI to get your password's hash code
+def HashGeneratorGUI():
+ layout = [[sg.T('Password Hash Generator', size=(30,1), font='Any 15')],
+ [sg.T('Password'), sg.In(key='password')],
+ [sg.T('SHA Hash'), sg.In('', size=(40,1), key='hash')],
+ ]
+
+ window = sg.Window('SHA Generator', auto_size_text=False, default_element_size=(10,1),
+ text_justification='r', return_keyboard_events=True, grab_anywhere=False).Layout(layout)
+
+ while True:
+ event, values = window.Read()
+ if event is None:
+ exit(69)
+
+ password = values['password']
+ try:
+ password_utf = password.encode('utf-8')
+ sha1hash = hashlib.sha1()
+ sha1hash.update(password_utf)
+ password_hash = sha1hash.hexdigest()
+ window.FindElement('hash').Update(password_hash)
+ except:
+ pass
+
+# ----------------------------- Paste this code into your program / script -----------------------------
+# determine if a password matches the secret password by comparing SHA1 hash codes
+def PasswordMatches(password, hash):
+ password_utf = password.encode('utf-8')
+ sha1hash = hashlib.sha1()
+ sha1hash.update(password_utf)
+ password_hash = sha1hash.hexdigest()
+ return password_hash == hash
+
+
+login_password_hash = 'e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4'
+password = sg.PopupGetText('Password', password_char='*')
+if password == 'gui': # Remove when pasting into your program
+ HashGeneratorGUI() # Remove when pasting into your program
+ exit(69) # Remove when pasting into your program
+if PasswordMatches(password, login_password_hash):
+ print('Login SUCCESSFUL')
+else:
+ print('Login FAILED!!')
diff --git a/DemoPrograms old/Demo_Pi_LEDs.py b/DemoPrograms old/Demo_Pi_LEDs.py
new file mode 100644
index 00000000..c82b9583
--- /dev/null
+++ b/DemoPrograms old/Demo_Pi_LEDs.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+# GUI for switching an LED on and off to GPIO14
+
+# GPIO and time library:
+import time
+
+if sys.platform == 'win32':
+ from random import randint
+
+ class GPIO():
+ LOW = 0
+ HIGH = 1
+ BCM = OUT = 0
+ current_value = 0
+ @classmethod
+ def setmode(self, mode):
+ return
+ @classmethod
+ def setup(self, arg1, arg2):
+ return
+ @classmethod
+ def output(self, port, value):
+ self.current_value = value
+ @classmethod
+ def input(self, port):
+ return self.current_value
+else:
+ import RPi.GPIO as GPIO
+
+# determine that GPIO numbers are used:
+GPIO.setmode(GPIO.BCM)
+GPIO.setup(14, GPIO.OUT)
+
+def SwitchLED():
+ varLedStatus = GPIO.input(14)
+ if varLedStatus == 0:
+ GPIO.output(14, GPIO.HIGH)
+ return "LED is switched ON"
+ else:
+ GPIO.output(14, GPIO.LOW)
+ return "LED is switched OFF"
+
+
+def FlashLED():
+ for i in range(5):
+ GPIO.output(14, GPIO.HIGH)
+ time.sleep(0.5)
+ GPIO.output(14, GPIO.LOW)
+ time.sleep(0.5)
+
+layout = [[sg.T('Raspberry Pi LEDs')],
+ [sg.T('', size=(20, 1), key='output')],
+ [sg.Button('Switch LED')],
+ [sg.Button('Flash LED')],
+ [sg.Exit()]]
+
+window = sg.Window('Raspberry Pi GUI', layout, grab_anywhere=False)
+
+while True:
+ event, values = window.Read()
+ if event in (None, 'Exit'):
+ break
+
+ if event == 'Switch LED':
+ window.FindElement('output').Update(SwitchLED())
+ elif event == 'Flash LED':
+ window.FindElement('output').Update('LED is Flashing')
+ window.Refresh()
+ FlashLED()
+ window.FindElement('output').Update('')
+
+window.Close()
+sg.Popup('Done... exiting')
diff --git a/DemoPrograms old/Demo_Pi_Robotics.py b/DemoPrograms old/Demo_Pi_Robotics.py
new file mode 100644
index 00000000..0c2ea577
--- /dev/null
+++ b/DemoPrograms old/Demo_Pi_Robotics.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+# Robotics design pattern
+# Uses Realtime Buttons to simulate the controls for a robot
+# Rather than sending a single click when a button is clicked, Realtime Buttons
+# send button presses continuously while the button is pressed down.
+# Two examples, one using fancy graphics, one plain.
+
+def RemoteControlExample():
+ # Make a form, but don't use context manager
+ sg.SetOptions(element_padding=(0,0))
+ back ='#eeeeee'
+ image_forward = 'ButtonGraphics/RobotForward.png'
+ image_backward = 'ButtonGraphics/RobotBack.png'
+ image_left = 'ButtonGraphics/RobotLeft.png'
+ image_right = 'ButtonGraphics/RobotRight.png'
+
+ sg.SetOptions(border_width=0, button_color=('black', back), background_color=back, element_background_color=back, text_element_background_color=back)
+
+ layout = [[sg.Text('Robotics Remote Control')],
+ [sg.T('', justification='center', size=(19,1), key='status')],
+ [ sg.RealtimeButton('', key='Forward', image_filename=image_forward, pad=((50,0),0))],
+ [ sg.RealtimeButton('', key='Left', image_filename=image_left),
+ sg.RealtimeButton('', key='Right', image_filename=image_right, pad=((50,0), 0))],
+ [ sg.RealtimeButton('', key='Reverse', image_filename=image_backward, pad=((50,0),0))],
+ [sg.T('')],
+ [sg.Quit(button_color=('black', 'orange'))]]
+
+ window = sg.Window('Robotics Remote Control', auto_size_text=True, grab_anywhere=False).Layout(layout)
+
+ #
+ # Some place later in your code...
+ # You need to perform a ReadNonBlocking on your form every now and then or
+ # else it won't refresh.
+ #
+ # your program's main loop
+ while (True):
+ # This is the code that reads and updates your window
+ event, values = window.Read(timeout=0, timeout_key='timeout')
+ if event is not None:
+ window.FindElement('status').Update(event)
+ elif event != 'timeout':
+ window.FindElement('status').Update('')
+ # if user clicked quit button OR closed the form using the X, then break out of loop
+ if event == 'Quit' or values is None:
+ break
+
+ window.Close()
+
+
+def RemoteControlExample_NoGraphics():
+ # Make a form, but don't use context manager
+
+ layout = [[sg.Text('Robotics Remote Control', justification='center')],
+ [sg.T('', justification='center', size=(19,1), key='status')],
+ [sg.T(' '*8), sg.RealtimeButton('Forward')],
+ [ sg.RealtimeButton('Left'), sg.T(' '), sg.RealtimeButton('Right')],
+ [sg.T(' '*8), sg.RealtimeButton('Reverse')],
+ [sg.T('')],
+ [sg.Quit(button_color=('black', 'orange'))]]
+ # Display form to user
+ window = sg.Window('Robotics Remote Control', auto_size_text=True, grab_anywhere=False).Layout(layout)
+
+ #
+ # Some place later in your code...
+ # You need to perform a Read on your form every now and then or
+ # else it won't refresh.
+ # Notice how the timeout is 100ms. You don't have to use a timeout = 0 for all of your hardware
+ # applications. Leave some CPU for other threads or for your GUI. The longer you are in the GUI, the more
+ # responsive the GUI itself will be Match your timeout with your hardware's capabilities
+ #
+ # your program's main loop
+ while (True):
+ # This is the code that reads and updates your window
+ event, values = window.Read(timeout=100, timeout_key='timeout')
+ # print(event, values)
+ if event != 'timeout':
+ window.FindElement('status').Update(event)
+ else:
+ window.FindElement('status').Update('')
+ # if user clicked quit button OR closed the form using the X, then break out of loop
+ if event in (None, 'Quit'):
+ break
+
+ window.Close()
+
+# ------------------------------------- main -------------------------------------
+def main():
+ RemoteControlExample_NoGraphics()
+ # Uncomment to get the fancy graphics version. Be sure and download the button images!
+ RemoteControlExample()
+ # sg.Popup('End of non-blocking demonstration')
+
+if __name__ == '__main__':
+
+ main()
diff --git a/DemoPrograms old/Demo_Ping_Line_Graph.py b/DemoPrograms old/Demo_Ping_Line_Graph.py
new file mode 100644
index 00000000..d7e8f182
--- /dev/null
+++ b/DemoPrograms old/Demo_Ping_Line_Graph.py
@@ -0,0 +1,665 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+from threading import Thread
+import time
+from sys import exit as exit
+
+# !/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+ A pure python ping implementation using raw sockets.
+
+ (This is Python 3 port of https://github.com/jedie/python-ping)
+ (Tested and working with python 2.7, should work with 2.6+)
+
+ Note that ICMP messages can only be sent from processes running as root
+ (in Windows, you must run this script as 'Administrator').
+
+ Derived from ping.c distributed in Linux's netkit. That code is
+ copyright (c) 1989 by The Regents of the University of California.
+ That code is in turn derived from code written by Mike Muuss of the
+ US Army Ballistic Research Laboratory in December, 1983 and
+ placed in the public domain. They have my thanks.
+
+ Bugs are naturally mine. I'd be glad to hear about them. There are
+ certainly word - size dependencies here.
+
+ Copyright (c) Matthew Dixon Cowles, .
+ Distributable under the terms of the GNU General Public License
+ version 2. Provided with no warranties of any sort.
+
+ Original Version from Matthew Dixon Cowles:
+ -> ftp://ftp.visi.com/users/mdc/ping.py
+
+ Rewrite by Jens Diemer:
+ -> http://www.python-forum.de/post-69122.html#69122
+
+ Rewrite by George Notaras:
+ -> http://www.g-loaded.eu/2009/10/30/python-ping/
+
+ Enhancements by Martin Falatic:
+ -> http://www.falatic.com/index.php/39/pinging-with-python
+
+ Enhancements and fixes by Georgi Kolev:
+ -> http://github.com/jedie/python-ping/
+
+ Bug fix by Andrejs Rozitis:
+ -> http://github.com/rozitis/python-ping/
+
+ Revision history
+ ~~~~~~~~~~~~~~~~
+ May 1, 2014
+ -----------
+ Little modifications by Mohammad Emami
+ - Added Python 3 support. For now this project will just support
+ python 3.x
+ - Tested with python 3.3
+ - version was upped to 0.6
+
+ March 19, 2013
+ --------------
+ * Fixing bug to prevent divide by 0 during run-time.
+
+ January 26, 2012
+ ----------------
+ * Fixing BUG #4 - competability with python 2.x [tested with 2.7]
+ - Packet data building is different for 2.x and 3.x.
+ 'cose of the string/bytes difference.
+ * Fixing BUG #10 - the multiple resolv issue.
+ - When pinging domain names insted of hosts (for exmaple google.com)
+ you can get different IP every time you try to resolv it, we should
+ resolv the host only once and stick to that IP.
+ * Fixing BUGs #3 #10 - Doing hostname resolv only once.
+ * Fixing BUG #14 - Removing all 'global' stuff.
+ - You should not use globul! Its bad for you...and its not thread safe!
+ * Fix - forcing the use of different times on linux/windows for
+ more accurate mesurments. (time.time - linux/ time.clock - windows)
+ * Adding quiet_ping function - This way we'll be able to use this script
+ as external lib.
+ * Changing default timeout to 3s. (1second is not enought)
+ * Switching data syze to packet size. It's easyer for the user to ignore the
+ fact that the packet headr is 8b and the datasize 64 will make packet with
+ size 72.
+
+ October 12, 2011
+ --------------
+ Merged updates from the main project
+ -> https://github.com/jedie/python-ping
+
+ September 12, 2011
+ --------------
+ Bugfixes + cleanup by Jens Diemer
+ Tested with Ubuntu + Windows 7
+
+ September 6, 2011
+ --------------
+ Cleanup by Martin Falatic. Restored lost comments and docs. Improved
+ functionality: constant time between pings, internal times consistently
+ use milliseconds. Clarified annotations (e.g., in the checksum routine).
+ Using unsigned data in IP & ICMP header pack/unpack unless otherwise
+ necessary. Signal handling. Ping-style output formatting and stats.
+
+ August 3, 2011
+ --------------
+ Ported to py3k by Zach Ware. Mostly done by 2to3; also minor changes to
+ deal with bytes vs. string changes (no more ord() in checksum() because
+ >source_string< is actually bytes, added .encode() to data in
+ send_one_ping()). That's about it.
+
+ March 11, 2010
+ --------------
+ changes by Samuel Stauffer:
+ - replaced time.clock with default_timer which is set to
+ time.clock on windows and time.time on other systems.
+
+ November 8, 2009
+ ----------------
+ Improved compatibility with GNU/Linux systems.
+
+ Fixes by:
+ * George Notaras -- http://www.g-loaded.eu
+ Reported by:
+ * Chris Hallman -- http://cdhallman.blogspot.com
+
+ Changes in this release:
+ - Re-use time.time() instead of time.clock(). The 2007 implementation
+ worked only under Microsoft Windows. Failed on GNU/Linux.
+ time.clock() behaves differently under the two OSes[1].
+
+ [1] http://docs.python.org/library/time.html#time.clock
+
+ May 30, 2007
+ ------------
+ little rewrite by Jens Diemer:
+ - change socket asterisk import to a normal import
+ - replace time.time() with time.clock()
+ - delete "return None" (or change to "return" only)
+ - in checksum() rename "str" to "source_string"
+
+ December 4, 2000
+ ----------------
+ Changed the struct.pack() calls to pack the checksum and ID as
+ unsigned. My thanks to Jerome Poincheval for the fix.
+
+ November 22, 1997
+ -----------------
+ Initial hack. Doesn't do much, but rather than try to guess
+ what features I (or others) will want in the future, I've only
+ put in what I need now.
+
+ December 16, 1997
+ -----------------
+ For some reason, the checksum bytes are in the wrong order when
+ this is run under Solaris 2.X for SPARC but it works right under
+ Linux x86. Since I don't know just what's wrong, I'll swap the
+ bytes always and then do an htons().
+
+ ===========================================================================
+ IP header info from RFC791
+ -> http://tools.ietf.org/html/rfc791)
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| IHL |Type of Service| Total Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |Flags| Fragment Offset |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Time to Live | Protocol | Header Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Destination Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ ===========================================================================
+ ICMP Echo / Echo Reply Message header info from RFC792
+ -> http://tools.ietf.org/html/rfc792
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identifier | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ...
+ +-+-+-+-+-
+
+ ===========================================================================
+ ICMP parameter info:
+ -> http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xml
+
+ ===========================================================================
+ An example of ping's typical output:
+
+ PING heise.de (193.99.144.80): 56 data bytes
+ 64 bytes from 193.99.144.80: icmp_seq=0 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=1 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=2 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=3 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=4 ttl=240 time=127 ms
+
+ ----heise.de PING Statistics----
+ 5 packets transmitted, 5 packets received, 0.0% packet loss
+ round-trip (ms) min/avg/max/med = 126/127/127/127
+
+ ===========================================================================
+"""
+
+# =============================================================================#
+import argparse
+import os, sys, socket, struct, select, time, signal
+
+__description__ = 'A pure python ICMP ping implementation using raw sockets.'
+
+if sys.platform == "win32":
+ # On Windows, the best timer is time.clock()
+ default_timer = time.clock
+else:
+ # On most other platforms the best timer is time.time()
+ default_timer = time.time
+
+NUM_PACKETS = 3
+PACKET_SIZE = 64
+WAIT_TIMEOUT = 3.0
+
+# =============================================================================#
+# ICMP parameters
+
+ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
+ICMP_ECHO = 8 # Echo request (per RFC792)
+ICMP_MAX_RECV = 2048 # Max size of incoming buffer
+
+MAX_SLEEP = 1000
+
+
+class MyStats:
+ thisIP = "0.0.0.0"
+ pktsSent = 0
+ pktsRcvd = 0
+ minTime = 999999999
+ maxTime = 0
+ totTime = 0
+ avrgTime = 0
+ fracLoss = 1.0
+
+
+myStats = MyStats # NOT Used globally anymore.
+
+
+# =============================================================================#
+def checksum(source_string):
+ """
+ A port of the functionality of in_cksum() from ping.c
+ Ideally this would act on the string as a series of 16-bit ints (host
+ packed), but this works.
+ Network data is big-endian, hosts are typically little-endian
+ """
+ countTo = (int(len(source_string) / 2)) * 2
+ sum = 0
+ count = 0
+
+ # Handle bytes in pairs (decoding as short ints)
+ loByte = 0
+ hiByte = 0
+ while count < countTo:
+ if (sys.byteorder == "little"):
+ loByte = source_string[count]
+ hiByte = source_string[count + 1]
+ else:
+ loByte = source_string[count + 1]
+ hiByte = source_string[count]
+ try: # For Python3
+ sum = sum + (hiByte * 256 + loByte)
+ except: # For Python2
+ sum = sum + (ord(hiByte) * 256 + ord(loByte))
+ count += 2
+
+ # Handle last byte if applicable (odd-number of bytes)
+ # Endianness should be irrelevant in this case
+ if countTo < len(source_string): # Check for odd length
+ loByte = source_string[len(source_string) - 1]
+ try: # For Python3
+ sum += loByte
+ except: # For Python2
+ sum += ord(loByte)
+
+ sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
+ # uses signed ints, but overflow is unlikely in ping)
+
+ sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
+ sum += (sum >> 16) # Add carry from above (if any)
+ answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ answer = socket.htons(answer)
+
+ return answer
+
+
+# =============================================================================#
+def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=False):
+ """
+ Returns either the delay (in ms) or None on timeout.
+ """
+ delay = None
+
+ try: # One could use UDP here, but it's obscure
+ mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ except socket.error as e:
+ print("failed. (socket error: '%s')" % e.args[1])
+ raise # raise the original error
+
+ my_ID = os.getpid() & 0xFFFF
+
+ sentTime = send_one_ping(mySocket, destIP, my_ID, mySeqNumber, packet_size)
+ if sentTime == None:
+ mySocket.close()
+ return delay
+
+ myStats.pktsSent += 1
+
+ recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout)
+
+ mySocket.close()
+
+ if recvTime:
+ delay = (recvTime - sentTime) * 1000
+ if not quiet:
+ print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % (
+ dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay)
+ )
+ myStats.pktsRcvd += 1
+ myStats.totTime += delay
+ if myStats.minTime > delay:
+ myStats.minTime = delay
+ if myStats.maxTime < delay:
+ myStats.maxTime = delay
+ else:
+ delay = None
+ print("Request timed out.")
+
+ return delay
+
+
+# =============================================================================#
+def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
+ """
+ Send one ping to the given >destIP<.
+ """
+ # destIP = socket.gethostbyname(destIP)
+
+ # Header is type (8), code (8), checksum (16), id (16), sequence (16)
+ # (packet_size - 8) - Remove header size from packet size
+ myChecksum = 0
+
+ # Make a dummy heder with a 0 checksum.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ padBytes = []
+ startVal = 0x42
+ # 'cose of the string/byte changes in python 2/3 we have
+ # to build the data differnely for different version
+ # or it will make packets with unexpected size.
+ if sys.version[:1] == '2':
+ bytes = struct.calcsize("d")
+ data = ((packet_size - 8) - bytes) * "Q"
+ data = struct.pack("d", default_timer()) + data
+ else:
+ for i in range(startVal, startVal + (packet_size - 8)):
+ padBytes += [(i & 0xff)] # Keep chars in the 0-255 range
+ # data = bytes(padBytes)
+ data = bytearray(padBytes)
+
+ # Calculate the checksum on the data and the dummy header.
+ myChecksum = checksum(header + data) # Checksum is in network order
+
+ # Now that we have the right checksum, we put that in. It's just easier
+ # to make up a new header than to stuff it into the dummy.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ packet = header + data
+
+ sendTime = default_timer()
+
+ try:
+ mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP
+ except socket.error as e:
+ print("General failure (%s)" % (e.args[1]))
+ return
+
+ return sendTime
+
+
+# =============================================================================#
+def receive_one_ping(mySocket, myID, timeout):
+ """
+ Receive the ping from the socket. Timeout = in ms
+ """
+ timeLeft = timeout / 1000
+
+ while True: # Loop while waiting for packet or timeout
+ startedSelect = default_timer()
+ whatReady = select.select([mySocket], [], [], timeLeft)
+ howLongInSelect = (default_timer() - startedSelect)
+ if whatReady[0] == []: # Timeout
+ return None, 0, 0, 0, 0
+
+ timeReceived = default_timer()
+
+ recPacket, addr = mySocket.recvfrom(ICMP_MAX_RECV)
+
+ ipHeader = recPacket[:20]
+ iphVersion, iphTypeOfSvc, iphLength, \
+ iphID, iphFlags, iphTTL, iphProtocol, \
+ iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
+ "!BBHHHBBHII", ipHeader
+ )
+
+ icmpHeader = recPacket[20:28]
+ icmpType, icmpCode, icmpChecksum, \
+ icmpPacketID, icmpSeqNumber = struct.unpack(
+ "!BBHHH", icmpHeader
+ )
+
+ if icmpPacketID == myID: # Our packet
+ dataSize = len(recPacket) - 28
+ # print (len(recPacket.encode()))
+ return timeReceived, (dataSize + 8), iphSrcIP, icmpSeqNumber, iphTTL
+
+ timeLeft = timeLeft - howLongInSelect
+ if timeLeft <= 0:
+ return None, 0, 0, 0, 0
+
+
+# =============================================================================#
+def dump_stats(myStats):
+ """
+ Show stats when pings are done
+ """
+ print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP))
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent
+
+ print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
+ myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss
+ ))
+
+ if myStats.pktsRcvd > 0:
+ print("round-trip (ms) min/avg/max = %d/%0.1f/%d" % (
+ myStats.minTime, myStats.totTime / myStats.pktsRcvd, myStats.maxTime
+ ))
+
+ print("")
+ return
+
+
+# =============================================================================#
+def signal_handler(signum, frame):
+ """
+ Handle exit via signals
+ """
+ dump_stats()
+ print("\n(Terminated with signal %d)\n" % (signum))
+ sys.exit(0)
+
+
+# =============================================================================#
+def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Send >count< ping to >destIP< with the given >timeout< and display
+ the result.
+ """
+ signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl-C
+ if hasattr(signal, "SIGBREAK"):
+ # Handle Ctrl-Break e.g. under Windows
+ signal.signal(signal.SIGBREAK, signal_handler)
+
+ myStats = MyStats() # Reset the stats
+
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ print("\nPYTHON PING %s (%s): %d data bytes" % (hostname, destIP, packet_size))
+ except socket.gaierror as e:
+ print("\nPYTHON PING: Unknown host: %s (%s)" % (hostname, e.args[1]))
+ print()
+ return
+
+ myStats.thisIP = destIP
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay) / 1000)
+
+ dump_stats(myStats)
+
+
+# =============================================================================#
+def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Same as verbose_ping, but the results are returned as tuple
+ """
+ myStats = MyStats() # Reset the stats
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ except socket.gaierror as e:
+ return False
+
+ myStats.thisIP = destIP
+
+ # This will send packet that we dont care about 0.5 seconds before it starts
+ # acrutally pinging. This is needed in big MAN/LAN networks where you sometimes
+ # loose the first packet. (while the switches find the way... :/ )
+ if path_finder:
+ fakeStats = MyStats()
+ do_one(fakeStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+ time.sleep(0.5)
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay) / 1000)
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent
+ if myStats.pktsRcvd > 0:
+ myStats.avrgTime = myStats.totTime / myStats.pktsRcvd
+
+ # return tuple(max_rtt, min_rtt, avrg_rtt, percent_lost)
+ return myStats.maxTime, myStats.minTime, myStats.avrgTime, myStats.fracLoss
+
+
+# =============================================================================#
+def main():
+ parser = argparse.ArgumentParser(description=__description__)
+ parser.add_argument('-q', '--quiet', action='store_true',
+ help='quiet output')
+ parser.add_argument('-c', '--count', type=int, default=NUM_PACKETS,
+ help=('number of packets to be sent '
+ '(default: %(default)s)'))
+ parser.add_argument('-W', '--timeout', type=float, default=WAIT_TIMEOUT,
+ help=('time to wait for a response in seoncds '
+ '(default: %(default)s)'))
+ parser.add_argument('-s', '--packet-size', type=int, default=PACKET_SIZE,
+ help=('number of data bytes to be sent '
+ '(default: %(default)s)'))
+ parser.add_argument('destination')
+ # args = parser.parse_args()
+
+ ping = verbose_ping
+ # if args.quiet:
+ # ping = quiet_ping
+ ping('Google.com', timeout=1000)
+ # ping(args.destination, timeout=args.timeout*1000, count=args.count,
+ # packet_size=args.packet_size)
+
+
+# set coordinate system
+canvas_right = 300
+canvas_left = 0
+canvas_top = 0
+canvas_bottom = 300
+# define the coordinates you'll use for your graph
+x_right = 100
+x_left = 0
+y_bottom = 0
+y_top = 500
+
+# globale used to communicate with thread.. yea yea... it's working fine
+g_exit = False
+g_response_time = None
+
+def ping_thread(args):
+ global g_exit, g_response_time
+
+ while not g_exit:
+ g_response_time = quiet_ping('google.com', timeout=1000)
+
+
+def convert_xy_to_canvas_xy(x_in,y_in):
+ scale_x = (canvas_right - canvas_left) / (x_right - x_left)
+ scale_y = (canvas_top - canvas_bottom) / (y_top - y_bottom)
+ new_x = canvas_left + scale_x * (x_in - x_left)
+ new_y = canvas_bottom + scale_y * (y_in - y_bottom)
+ return new_x, new_y
+
+
+
+# start ping measurement thread
+thread = Thread(target=ping_thread, args=(None,))
+thread.start()
+
+layout = [ [sg.T('Ping times to Google.com', font='Any 18')],
+ [sg.Canvas(size=(canvas_right, canvas_bottom), background_color='white', key='canvas')],
+ [sg.Quit()] ]
+
+window = sg.Window('Ping Times To Google.com', grab_anywhere=True).Layout(layout).Finalize()
+
+canvas = window.FindElement('canvas').TKCanvas
+
+prev_response_time = None
+i=0
+prev_x, prev_y = canvas_left, canvas_bottom
+while True:
+ time.sleep(.2)
+
+ event, values = window.Read(timeout=0)
+ if event == 'Quit' or event is None:
+ break
+
+ if g_response_time is None or prev_response_time == g_response_time:
+ continue
+ try:
+ new_x, new_y = convert_xy_to_canvas_xy(i, g_response_time[0])
+ except: continue
+
+ prev_response_time = g_response_time
+ canvas.create_line(prev_x, prev_y, new_x, new_y, width=1, fill='black')
+ prev_x, prev_y = new_x, new_y
+ if i >= x_right:
+ i = 0
+ prev_x = prev_y = last_x = last_y = 0
+ canvas.delete('all')
+ else: i += 1
+
+# tell thread we're done. wait for thread to exit
+g_exit = True
+thread.join()
+
+
+exit(69)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Pong.py b/DemoPrograms old/Demo_Pong.py
new file mode 100644
index 00000000..df4111e4
--- /dev/null
+++ b/DemoPrograms old/Demo_Pong.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import random
+import time
+from sys import exit as exit
+
+
+"""
+ Pong code supplied by Daniel Young (Neonzz)
+ Modified. Original code: https://www.pygame.org/project/3649/5739
+"""
+
+class Ball:
+ def __init__(self, canvas, bat, bat2, color):
+ self.canvas = canvas
+ self.bat = bat
+ self.bat2 = bat2
+ self.playerScore = 0
+ self.player1Score = 0
+ self.drawP1 = None
+ self.drawP = None
+ self.id = self.canvas.create_oval(10, 10, 35, 35, fill=color)
+ self.canvas.move(self.id, 327, 220)
+ self.canvas_height = self.canvas.winfo_height()
+ self.canvas_width = self.canvas.winfo_width()
+ self.x = random.choice([-2.5, 2.5])
+ self.y = -2.5
+
+ def checkwin(self):
+ winner = None
+ if self.playerScore >= 10:
+ winner = 'Player left wins'
+ if self.player1Score >= 10:
+ winner = 'Player Right'
+ return winner
+
+
+ def updatep(self, val):
+ self.canvas.delete(self.drawP)
+ self.drawP = self.canvas.create_text(170, 50, font=('freesansbold.ttf', 40), text=str(val), fill='white')
+
+ def updatep1(self, val):
+ self.canvas.delete(self.drawP1)
+ self.drawP1 = self.canvas.create_text(550, 50, font=('freesansbold.ttf', 40), text=str(val), fill='white')
+
+ def hit_bat(self, pos):
+ bat_pos = self.canvas.coords(self.bat.id)
+ if pos[2] >= bat_pos[0] and pos[0] <= bat_pos[2]:
+ if pos[3] >= bat_pos[1] and pos[3] <= bat_pos[3]:
+ return True
+ return False
+
+ def hit_bat2(self, pos):
+ bat_pos = self.canvas.coords(self.bat2.id)
+ if pos[2] >= bat_pos[0] and pos[0] <= bat_pos[2]:
+ if pos[3] >= bat_pos[1] and pos[3] <= bat_pos[3]:
+ return True
+ return False
+
+ def draw(self):
+ self.canvas.move(self.id, self.x, self.y)
+ pos = self.canvas.coords(self.id)
+ if pos[1] <= 0:
+ self.y = 4
+ if pos[3] >= self.canvas_height:
+ self.y = -4
+ if pos[0] <= 0:
+ self.player1Score += 1
+ self.canvas.move(self.id, 327, 220)
+ self.x = 4
+ self.updatep1(self.player1Score)
+ if pos[2] >= self.canvas_width:
+ self.playerScore += 1
+ self.canvas.move(self.id, -327, -220)
+ self.x = -4
+ self.updatep(self.playerScore)
+ if self.hit_bat(pos):
+ self.x = 4
+ if self.hit_bat2(pos):
+ self.x = -4
+
+
+class pongbat():
+ def __init__(self, canvas, color):
+ self.canvas = canvas
+ self.id = self.canvas.create_rectangle(40, 200, 25, 310, fill=color)
+ self.canvas_height = self.canvas.winfo_height()
+ self.canvas_width = self.canvas.winfo_width()
+ self.y = 0
+
+ def up(self, evt):
+ self.y = -5
+
+ def down(self, evt):
+ self.y = 5
+
+ def draw(self):
+ self.canvas.move(self.id, 0, self.y)
+ pos = self.canvas.coords(self.id)
+ if pos[1] <= 0:
+ self.y = 0
+ if pos[3] >= 400:
+ self.y = 0
+
+
+class pongbat2():
+ def __init__(self, canvas, color):
+ self.canvas = canvas
+ self.id = self.canvas.create_rectangle(680, 200, 660, 310, fill=color)
+ self.canvas_height = self.canvas.winfo_height()
+ self.canvas_width = self.canvas.winfo_width()
+ self.y = 0
+
+ def up(self, evt):
+ self.y = -5
+
+ def down(self, evt):
+ self.y = 5
+
+ def draw(self):
+ self.canvas.move(self.id, 0, self.y)
+ pos = self.canvas.coords(self.id)
+ if pos[1] <= 0:
+ self.y = 0
+ if pos[3] >= 400:
+ self.y = 0
+
+
+def pong():
+ # ------------- Define GUI layout -------------
+ layout = [[sg.Canvas(size=(700, 400), background_color='black', key='canvas')],
+ [sg.T(''), sg.Button('Quit')]]
+ # ------------- Create window -------------
+ window = sg.Window('The Classic Game of Pong', return_keyboard_events=True).Layout(layout).Finalize()
+ # window.Finalize() # TODO Replace with call to window.Finalize once code released
+
+ # ------------- Get the tkinter Canvas we're drawing on -------------
+ canvas = window.FindElement('canvas').TKCanvas
+
+ # ------------- Create line down center, the bats and ball -------------
+ canvas.create_line(350, 0, 350, 400, fill='white')
+ bat1 = pongbat(canvas, 'white')
+ bat2 = pongbat2(canvas, 'white')
+ ball1 = Ball(canvas, bat1, bat2, 'green')
+
+ # ------------- Event Loop -------------
+ while True:
+ # ------------- Draw ball and bats -------------
+ ball1.draw()
+ bat1.draw()
+ bat2.draw()
+
+ # ------------- Read the form, get keypresses -------------
+ event, values = window.Read(timeout=0)
+ # ------------- If quit -------------
+ if event is None or event == 'Quit':
+ exit(69)
+ # ------------- Keypresses -------------
+ if event is not None:
+ if event.startswith('Up'):
+ bat2.up(2)
+ elif event.startswith('Down'):
+ bat2.down(2)
+ elif event == 'w':
+ bat1.up(1)
+ elif event == 's':
+ bat1.down(1)
+
+ if ball1.checkwin():
+ sg.Popup('Game Over', ball1.checkwin() + ' won!!')
+ break
+
+
+ # ------------- Bottom of loop, delay between animations -------------
+ # time.sleep(.01)
+ canvas.after(10)
+
+if __name__ == '__main__':
+ pong()
+
+
+
diff --git a/DemoPrograms old/Demo_Pong_Multiple_Platforms.py b/DemoPrograms old/Demo_Pong_Multiple_Platforms.py
new file mode 100644
index 00000000..2e88127c
--- /dev/null
+++ b/DemoPrograms old/Demo_Pong_Multiple_Platforms.py
@@ -0,0 +1,172 @@
+# !/usr/bin/env python
+# Based on work by - Siddharth Natamai
+# At the moment, this source file runs on TWO of the 4 PySimpleGUI ports with a third one coming soon (Qt).
+# import PySimpleGUIQt as sg # not quite working on Qt yet... needs Graph.Relocate fixed first
+# import PySimpleGUIWeb as sg
+import PySimpleGUI as sg
+import random
+
+GAMEPLAY_SIZE = (700, 400)
+BAT_SIZE = (20, 110)
+STARTING_BALL_POSITION = (327, 200)
+player_1_Starting_Score = 0
+player_2_Starting_Score = 0
+BALL_RADIUS = 12
+# BACKGROUND_COLOR = 'lightblue' # if running on PySimpleGUIWeb
+BACKGROUND_COLOR = 'black'
+# BALL_COLOR = 'black' # if running on PySimpleGUIWeb
+BALL_COLOR = 'green1'
+num_rounds = 0
+while num_rounds == 0:
+ try:
+ num_rounds = int(sg.PopupGetText('How many rounds would you like to play?'))
+ except Exception as e:
+ num_rounds = 0
+
+
+class Ball:
+ def __init__(self, graph, bat_1, bat_2, colour):
+ self.graph = graph # type: sg.Graph
+ self.bat_1 = bat_1
+ self.bat_2 = bat_2
+ self.player_1_Score = player_1_Starting_Score
+ self.player_2_Score = player_2_Starting_Score
+ self.draw_P1 = None
+ self.draw_P2 = None
+ self.id = self.graph.DrawCircle(STARTING_BALL_POSITION, BALL_RADIUS, line_color=colour, fill_color=colour)
+ self.curx, self.cury = STARTING_BALL_POSITION
+ # self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ self.x = random.choice([-2.5, 2.5])
+ self.y = -2.5
+
+ def win_loss_check(self):
+ winner = None
+ if self.player_1_Score >= num_rounds:
+ winner = 'Player Right Wins'
+ if self.player_2_Score >= num_rounds:
+ winner = 'Player Left Wins'
+ return winner
+
+ def update_player1_score(self, val):
+ self.graph.DeleteFigure(self.draw_P1)
+ self.draw_P1 = self.graph.DrawText(str(val), (170, 50), font=('Courier 60'), color='white')
+
+ def update_player2_score(self, val):
+ self.graph.DeleteFigure(self.draw_P2)
+ self.draw_P2 = self.graph.DrawText(str(val), (550, 50), font=('courier 40'), color='white')
+
+ def hit_bat(self, pos):
+ bat_pos = (self.bat_1.curx, self.bat_1.cury)
+ if pos[0] >= bat_pos[0] and pos[0] <= bat_pos[0]+BAT_SIZE[0]:
+ if bat_pos[1] <= pos[1] <= bat_pos[1]+BAT_SIZE[1]:
+ return True
+ return False
+
+ def hit_bat2(self, pos):
+ bat_pos = (self.bat_2.curx, self.bat_2.cury)
+ if pos[0] >= bat_pos[0] and pos[0] <= bat_pos[0]+BAT_SIZE[0]:
+ if bat_pos[1] <= pos[1] <= bat_pos[1]+BAT_SIZE[1]:
+ return True
+ return False
+
+
+ def draw(self):
+ self.curx += self.x
+ self.cury += self.y
+ self.graph.RelocateFigure(self.id, self.curx, self.cury)
+ if self.cury <= 0: # see if hit top or bottom of play area. If so, reverse y direction
+ self.y = 4
+ self.cury = 0
+ if self.cury >= GAMEPLAY_SIZE[1]-BALL_RADIUS/2:
+ self.y = -4
+ self.cury = GAMEPLAY_SIZE[1]-BALL_RADIUS/2
+ if self.curx <= 0: # see if beyond player
+ self.player_1_Score += 1
+ self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ self.x = 4
+ self.update_player2_score(self.player_1_Score)
+ self.curx, self.cury = STARTING_BALL_POSITION
+ if self.curx >= GAMEPLAY_SIZE[0]:
+ self.player_2_Score += 1
+ self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ self.x = -4
+ self.update_player1_score(self.player_2_Score)
+ self.curx, self.cury = STARTING_BALL_POSITION
+ if self.hit_bat((self.curx, self.cury)):
+ self.x = 4
+ if self.hit_bat2((self.curx, self.cury)):
+ self.x = -4
+
+
+class PongBat():
+ def __init__(self, graph:sg.Graph, colour, x, width=BAT_SIZE[0], height=BAT_SIZE[1]):
+ self.graph = graph
+ self.id = graph.DrawRectangle((x - width / 2, 200), (x + width / 2, 200 + height), fill_color=colour)
+ self.y = 0
+ self.x = x
+ self.curx = x
+ self.cury = height/2
+
+ def up(self, amount):
+ self.y = -amount
+
+ def down(self, amount):
+ self.y = amount
+
+ @property
+ def curr_pos(self):
+ pos = self.cury
+ return pos
+
+ def draw(self):
+ self.graph.RelocateFigure(self.id, self.curx, self.cury)
+ if self.cury + self.y + BAT_SIZE[1] <= GAMEPLAY_SIZE[1] and self.cury + self.y + BAT_SIZE[1] >= 0:
+ self.cury += self.y
+ if self.cury <= 0:
+ self.cury = 0
+ self.y = 0
+ if self.cury >= GAMEPLAY_SIZE[1]:
+ self.cury = GAMEPLAY_SIZE[1]
+ self.y = 0
+
+
+def pong():
+ layout = [[sg.Graph(GAMEPLAY_SIZE, (0,GAMEPLAY_SIZE[1]), (GAMEPLAY_SIZE[0],0), background_color=BACKGROUND_COLOR, key='_GRAPH_')],
+ [sg.T(''), sg.Button('Exit'), sg.T('Speed'), sg.Slider((0,20),default_value=10, orientation='h', enable_events=True, key='_SPEED_')]]
+
+ window = sg.Window('Pong', layout, return_keyboard_events=True).Finalize()
+
+ graph_elem = window.FindElement('_GRAPH_') # type: sg.Graph
+
+ bat_1 = PongBat(graph_elem, 'red', 30)
+ bat_2 = PongBat(graph_elem, 'blue', 670)
+
+ ball_1 = Ball(graph_elem, bat_1, bat_2, 'green1')
+ sleep_time = 10
+
+ while True:
+ ball_1.draw()
+ bat_1.draw()
+ bat_2.draw()
+
+ event, values = window.Read(timeout=sleep_time) # type: str, str
+ if event is None or event == 'Exit':
+ break
+ elif event.startswith('Up') or event.endswith('Up'):
+ bat_2.up(5)
+ elif event.startswith('Down') or event.endswith('Down'):
+ bat_2.down(5)
+ elif event == 'w':
+ bat_1.up(5)
+ elif event == 's':
+ bat_1.down(5)
+ elif event == '_SPEED_':
+ sleep_time = int(values['_SPEED_'])
+
+ if ball_1.win_loss_check():
+ sg.Popup('Game Over', ball_1.win_loss_check() + ' won!!')
+ break
+ window.Close()
+
+if __name__ == '__main__':
+ pong()
diff --git a/DemoPrograms old/Demo_Popup_Custom.py b/DemoPrograms old/Demo_Popup_Custom.py
new file mode 100644
index 00000000..718adb8f
--- /dev/null
+++ b/DemoPrograms old/Demo_Popup_Custom.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+'''
+ Use this code as a starting point for creating your own Popup functions.
+ Rather than creating a long list of Popup high-level API calls, PySimpleGUI provides
+ you with the tools to easily create your own. If you need more than what the standard PopupGetText and
+ other calls provide, then it's time for you to graduate into making your own windows. Or, maybe you need
+ another window that pops-up over your primary window. Whatever the need, don't hesitate to dive in
+ and create your own Popup call.
+
+ This example is for a DropDown / Combobox Popup. You provide it with a title, a message and the list
+ of values to choose from. It mimics the return values of existing Popup calls (None if nothing was input)
+'''
+
+
+def PopupDropDown(title, text, values):
+ window = sg.Window(title).Layout([[sg.Text(text)],
+ [sg.DropDown(values, key='_DROP_')],
+ [sg.OK(), sg.Cancel()]])
+ event, values = window.Read()
+ return None if event != 'OK' else values['_DROP_']
+
+
+# ----------------------- Calling your PopupDropDown function -----------------------
+
+values = ['choice {}'.format(x) for x in range(30)]
+
+print(PopupDropDown('My Title', 'Please make a selection', values))
diff --git a/DemoPrograms old/Demo_Popups.py b/DemoPrograms old/Demo_Popups.py
new file mode 100644
index 00000000..a2435a82
--- /dev/null
+++ b/DemoPrograms old/Demo_Popups.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+from PySimpleGUI import Print as print
+
+
+print('test')
+sg.PopupGetFile('Get file', save_as=True,file_types=(("ALL Files", "*.jpg"),))
+
+# Here, have some windows on me....
+[sg.PopupNoWait('No-wait Popup', location=(500+100*x,500)) for x in range(10)]
+
+answer = sg.PopupYesNo('Do not worry about all those open windows... they will disappear at the end', 'Are you OK with that?')
+
+if answer == 'No':
+ sg.PopupCancel('OK, we will destroy those windows as soon as you close this window')
+ sys.exit()
+
+sg.PopupNonBlocking('Your answer was',answer, location=(1000,600))
+
+text = sg.PopupGetText('This is a call to PopopGetText', location=(1000,200))
+sg.PopupGetFile('Get file')
+sg.PopupGetFolder('Get folder')
+
+
+sg.Popup('Simple popup')
+
+sg.PopupNoTitlebar('No titlebar')
+sg.PopupNoBorder('No border')
+sg.PopupNoFrame('No frame')
+sg.PopupCancel('Cancel')
+sg.PopupOKCancel('OK Cancel')
+sg.PopupAutoClose('Autoclose')
diff --git a/DemoPrograms old/Demo_Progress_Meters.py b/DemoPrograms old/Demo_Progress_Meters.py
new file mode 100644
index 00000000..0ffc4c8e
--- /dev/null
+++ b/DemoPrograms old/Demo_Progress_Meters.py
@@ -0,0 +1,198 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+from time import sleep
+
+
+"""
+ Demonstration of simple and multiple OneLineProgressMeter's as well as the Progress Meter Element
+
+ There are 4 demos
+ 1. Manually updated progress bar
+ 2. Custom progress bar built into your window, updated in a loop
+ 3. OneLineProgressMeters, nested meters showing how 2 can be run at the same time.
+ 4. An "iterable" style progress meter - a wrapper for OneLineProgressMeters
+
+ If the software determined that a meter should be cancelled early,
+ calling OneLineProgresMeterCancel(key) will cancel the meter with the matching key
+"""
+
+
+
+"""
+ The simple case is that you want to add a single meter to your code. The one-line solution.
+ This demo function shows 3 different OneLineProgressMeter tests
+ 1. A horizontal with red and white bar colors
+ 2. A vertical bar with default colors
+ 3. A test showing 2 running at the same time
+"""
+
+def demo_one_line_progress_meter():
+ # Display a progress meter. Allow user to break out of loop using cancel button
+ for i in range(10000):
+ if not sg.OneLineProgressMeter('My 1-line progress meter', i+1, 10000, 'meter key','MY MESSAGE1', 'MY MESSAGE 2', orientation='h', bar_color=('white', 'red')):
+ print('Hit the break')
+ break
+ for i in range(10000):
+ if not sg.OneLineProgressMeter('My 1-line progress meter', i+1, 10000, 'meter key', 'MY MESSAGE1', 'MY MESSAGE 2',orientation='v' ):
+ print('Hit the break')
+ break
+
+ layout = [
+ [sg.T('One-Line Progress Meter Demo', font=('Any 18'))],
+ [sg.T('Outer Loop Count', size=(15,1), justification='r'), sg.In(default_text='100', size=(5,1), key='CountOuter', do_not_clear=True),
+ sg.T('Delay'), sg.In(default_text='10', key='TimeOuter', size=(5,1), do_not_clear=True), sg.T('ms')],
+ [sg.T('Inner Loop Count', size=(15,1), justification='r'), sg.In(default_text='100', size=(5,1), key='CountInner', do_not_clear=True) ,
+ sg.T('Delay'), sg.In(default_text='10', key='TimeInner', size=(5,1), do_not_clear=True), sg.T('ms')],
+ [sg.Button('Show', pad=((0,0), 3), bind_return_key=True), sg.T('me the meters!')]
+ ]
+
+ window = sg.Window('One-Line Progress Meter Demo').Layout(layout)
+
+ while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ if event == 'Show':
+ max_outer = int(values['CountOuter'])
+ max_inner = int(values['CountInner'])
+ delay_inner = int(values['TimeInner'])
+ delay_outer = int(values['TimeOuter'])
+ for i in range(max_outer):
+ if not sg.OneLineProgressMeter('Outer Loop', i+1, max_outer, 'outer'):
+ break
+ sleep(delay_outer/1000)
+ for j in range(max_inner):
+ if not sg.OneLineProgressMeter('Inner Loop', j+1, max_inner, 'inner'):
+ break
+ sleep(delay_inner/1000)
+
+
+'''
+ Manually Updated Test
+ Here is an example for when you want to "sprinkle" progress bar updates in multiple
+ places within your source code and you're not running an event loop.
+ Note that UpdateBar is special compared to other Update methods. It also refreshes
+ the containing window and checks for window closure events
+ The sleep calls are here only for demonstration purposes. You should NOT be adding
+ these kinds of sleeps to a GUI based program normally.
+'''
+
+def manually_updated_meter_test():
+ # layout the form
+ layout = [[sg.Text('This meter is manually updated 4 times')],
+ [sg.ProgressBar(max_value=10, orientation='h', size=(20,20), key='progress')]]
+
+ # create the form`
+ window = sg.Window('Custom Progress Meter', layout).Finalize() # must finalize since not running an event loop
+
+ progress_bar = window.FindElement('progress') # Get the element to make updating easier
+
+ # -------------------- Your Program Code --------------------
+ # Spot #1 to indicate progress
+ progress_bar.UpdateBar(1) # show 10% complete
+ sleep(2)
+
+ # more of your code.... perhaps pages and pages of code.
+ # Spot #2 to indicate progress
+ progress_bar.UpdateBar(2) # show 20% complete
+ sleep(2)
+
+ # more of your code.... perhaps pages and pages of code.
+ # Spot #3 to indicate progress
+ progress_bar.UpdateBar(6) # show 60% complete
+ sleep(2)
+
+ # more of your code.... perhaps pages and pages of code.
+ # Spot #4 to indicate progress
+ progress_bar.UpdateBar(9) # show 90% complete
+ sleep(2)
+ window.Close()
+
+
+'''
+ This function shows how to create a custom window with a custom progress bar and then
+ how to update the bar to indicate progress is being made
+'''
+
+def custom_meter_example():
+ # layout the form
+ layout = [[sg.Text('A typical custom progress meter')],
+ [sg.ProgressBar(1, orientation='h', size=(20,20), key='progress')],
+ [sg.Cancel()]]
+
+ # create the form`
+ window = sg.Window('Custom Progress Meter').Layout(layout)
+ progress_bar = window.FindElement('progress')
+ # loop that would normally do something useful
+ for i in range(10000):
+ # check to see if the cancel button was clicked and exit loop if clicked
+ event, values = window.Read(timeout=0)
+ if event == 'Cancel' or event == None:
+ break
+ # update bar with loop value +1 so that bar eventually reaches the maximum
+ progress_bar.UpdateBar(i+1, 10000)
+ # done with loop... need to destroy the window as it's still open
+ window.Close()
+
+
+'''
+ A Wrapper for OneLineProgressMeter that combines your iterable with a progress meter into a single interface. Two functions are provided
+ progess_bar - The basic wrapper
+ progress_bar_range - A "convienence function" if you wanted to specify like a range
+'''
+
+
+def progress_bar(key, iterable, *args, title='', **kwargs):
+ """
+ Takes your iterable and adds a progress meter onto it
+ :param key: Progress Meter key
+ :param iterable: your iterable
+ :param args: To be shown in one line progress meter
+ :param title: Title shown in meter window
+ :param kwargs: Other arguments to pass to OneLineProgressMeter
+ :return:
+ """
+ sg.OneLineProgressMeter(title, 0, len(iterable), key, *args, **kwargs)
+ for i, val in enumerate(iterable):
+ yield val
+ if not sg.OneLineProgressMeter(title, i+1, len(iterable), key, *args, **kwargs):
+ break
+
+
+def progress_bar_range(key, start, stop=None, step=1, *args, **kwargs):
+ """
+ Acts like the range() function but with a progress meter built-into it
+ :param key: progess meter's key
+ :param start: low end of the range
+ :param stop: Uppder end of range
+ :param step:
+ :param args:
+ :param kwargs:
+ :return:
+ """
+ return progress_bar(key, range(start, stop, step), *args, **kwargs)
+
+
+# -------------------- Demo Usage --------------------
+def demo_iterable_progress_bar():
+ my_list = list(range(1000)) # start with a list of 100 integers as the user's list
+
+ # first form takes an iterable and a key and will return a value from your iterable
+ # and bump the progress meter at the same time
+ for value in progress_bar('bar1', my_list, ):
+ # do something useful with value, a value from your list.
+ print(value)
+
+ # Since the progress_bar is an iterator, you can use it within a list comprehension
+ my_list = [x for x in progress_bar('bar1', my_list)]
+
+
+
+demo_iterable_progress_bar()
+manually_updated_meter_test()
+custom_meter_example()
+demo_one_line_progress_meter()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_PyGame_Integration.py b/DemoPrograms old/Demo_PyGame_Integration.py
new file mode 100644
index 00000000..dae260ff
--- /dev/null
+++ b/DemoPrograms old/Demo_PyGame_Integration.py
@@ -0,0 +1,39 @@
+import pygame
+import PySimpleGUI as sg
+import os
+
+"""
+ Demo of integrating PyGame with PySimpleGUI, the tkinter version
+ A similar technique may be possible with WxPython
+ Only works on windows from what I've read
+"""
+# --------------------- PySimpleGUI window layout and creation --------------------
+layout = [[sg.T('Test of PySimpleGUI with PyGame')],
+ [sg.Graph((500,500), (0,0), (500,500), background_color='lightblue', key='_GRAPH_' )],
+ [sg.B('Draw'), sg.Exit()]]
+
+window = sg.Window('PySimpleGUI + PyGame', layout).Finalize()
+graph = window.Element('_GRAPH_')
+
+# -------------- Magic code to integrate PyGame with tkinter -------
+embed = graph.TKCanvas
+os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
+os.environ['SDL_VIDEODRIVER'] = 'windib'
+
+# ----------------------------- PyGame Code -----------------------------
+
+screen = pygame.display.set_mode((500,500))
+screen.fill(pygame.Color(255,255,255))
+
+pygame.display.init()
+pygame.display.update()
+
+while True:
+ event, values = window.Read(timeout=10)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Draw':
+ pygame.draw.circle(screen, (0, 0, 0), (250, 250), 125)
+ pygame.display.update()
+
+window.Close()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_PyGame_Snake_Game.py b/DemoPrograms old/Demo_PyGame_Snake_Game.py
new file mode 100644
index 00000000..6d1485e7
--- /dev/null
+++ b/DemoPrograms old/Demo_PyGame_Snake_Game.py
@@ -0,0 +1,138 @@
+import pygame
+import PySimpleGUI as sg
+import os
+
+"""
+ Demo - Simple Snake Game using PyGame and PySimpleGUI
+ This demo may not be fully functional in terms of getting the coordinate
+ systems right or other problems due to a lack of understanding of PyGame
+ The purpose of the demo is to show one way of adding a PyGame window into your PySimpleGUI window
+ Note, you must click on the game area in order for PyGame to get keyboard strokes, etc.
+ Tried using set_focus to switch to the PyGame canvas but still needed to click on game area
+"""
+
+# --- Globals ---
+# Colors
+BLACK = (0, 0, 0)
+WHITE = (255, 255, 255)
+
+# Set the width and height of each snake segment
+segment_width = 15
+segment_height = 15
+# Margin between each segment
+segment_margin = 3
+
+# Set initial speed
+x_change = segment_width + segment_margin
+y_change = 0
+
+
+class Segment(pygame.sprite.Sprite):
+ """ Class to represent one segment of the snake. """
+ # -- Methods
+ # Constructor function
+ def __init__(self, x, y):
+ # Call the parent's constructor
+ super().__init__()
+
+ # Set height, width
+ self.image = pygame.Surface([segment_width, segment_height])
+ self.image.fill(WHITE)
+
+ # Make our top-left corner the passed-in location.
+ self.rect = self.image.get_rect()
+ self.rect.x = x
+ self.rect.y = y
+
+# --------------------------- GUI Setup & Create Window -------------------------------
+
+layout = [[sg.T('Snake Game - PySimpleGUI + PyGame')],
+ [sg.Graph((800,600), (0,0), (800,600), background_color='lightblue', key='_GRAPH_')],
+ [sg.Exit()]]
+
+window = sg.Window('Snake Game using PySimpleGUI and PyGame', layout).Finalize()
+
+# ------------------------ Do the magic that integrates PyGame and Graph Element ------------------
+graph = window.Element('_GRAPH_') # type: sg.Graph
+embed = graph.TKCanvas
+os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
+os.environ['SDL_VIDEODRIVER'] = 'windib'
+
+# ----------------------------- PyGame Code -----------------------------
+# Call this function so the Pygame library can initialize itself
+# pygame.init()
+screen = pygame.display.set_mode((800,600))
+screen.fill(pygame.Color(255,255,255))
+
+pygame.display.init()
+pygame.display.update()
+
+# Set the title of the window
+pygame.display.set_caption('Snake Example')
+
+allspriteslist = pygame.sprite.Group()
+
+# Create an initial snake
+snake_segments = []
+for i in range(15):
+ x = 250 - (segment_width + segment_margin) * i
+ y = 30
+ segment = Segment(x, y)
+ snake_segments.append(segment)
+ allspriteslist.add(segment)
+
+clock = pygame.time.Clock()
+
+while True:
+ event, values = window.Read(timeout=10)
+ if event in (None, 'Exit'):
+ break
+ pygame.display.update()
+
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ break
+ # Set the speed based on the key pressed
+ # We want the speed to be enough that we move a full
+ # segment, plus the margin.
+ if event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_LEFT:
+ x_change = (segment_width + segment_margin) * -1
+ y_change = 0
+ if event.key == pygame.K_RIGHT:
+ x_change = (segment_width + segment_margin)
+ y_change = 0
+ if event.key == pygame.K_UP:
+ x_change = 0
+ y_change = (segment_height + segment_margin) * -1
+ if event.key == pygame.K_DOWN:
+ x_change = 0
+ y_change = (segment_height + segment_margin)
+
+ # Get rid of last segment of the snake
+ # .pop() command removes last item in list
+ old_segment = snake_segments.pop()
+ allspriteslist.remove(old_segment)
+
+ # Figure out where new segment will be
+ x = snake_segments[0].rect.x + x_change
+ y = snake_segments[0].rect.y + y_change
+ segment = Segment(x, y)
+
+ # Insert new segment into the list
+ snake_segments.insert(0, segment)
+ allspriteslist.add(segment)
+
+ # -- Draw everything
+ # Clear screen
+ screen.fill(BLACK)
+
+ allspriteslist.draw(screen)
+
+ # Flip screen
+ pygame.display.flip()
+
+ # Pause
+ clock.tick(5)
+
+window.Close()
diff --git a/DemoPrograms old/Demo_Pyplot_Bar_Chart.py b/DemoPrograms old/Demo_Pyplot_Bar_Chart.py
new file mode 100644
index 00000000..34265b3d
--- /dev/null
+++ b/DemoPrograms old/Demo_Pyplot_Bar_Chart.py
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+import sys
+
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import matplotlib
+
+matplotlib.use('TkAgg')
+from matplotlib.backends.backend_tkagg import FigureCanvasAgg
+import matplotlib.backends.tkagg as tkagg
+import tkinter as Tk
+
+"""
+Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
+
+Paste your Pyplot code into the section marked below.
+
+Do all of your plotting as you normally would, but do NOT call plt.show().
+Stop just short of calling plt.show() and let the GUI do the rest.
+
+The remainder of the program will convert your plot and display it in the GUI.
+If you want to change the GUI, make changes to the GUI portion marked below.
+
+"""
+
+# ------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+values_to_plot = (20, 35, 30, 35, 27)
+ind = np.arange(len(values_to_plot))
+width = 0.4
+
+p1 = plt.bar(ind, values_to_plot, width)
+
+plt.ylabel('Y-Axis Values')
+plt.title('Plot Title')
+plt.xticks(ind, ('Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5'))
+plt.yticks(np.arange(0, 81, 10))
+plt.legend((p1[0],), ('Data Group 1',))
+
+
+# ------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
+
+# ------------------------------- Beginning of Matplotlib helper code -----------------------
+
+
+def draw_figure(canvas, figure, loc=(0, 0)):
+ """ Draw a matplotlib figure onto a Tk canvas
+
+ loc: location of top-left corner of figure on canvas in pixels.
+
+ Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
+ """
+ figure_canvas_agg = FigureCanvasAgg(figure)
+ figure_canvas_agg.draw()
+ figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
+ figure_w, figure_h = int(figure_w), int(figure_h)
+ photo = Tk.PhotoImage(master=canvas, width=figure_w, height=figure_h)
+ canvas.create_image(loc[0] + figure_w / 2, loc[1] + figure_h / 2, image=photo)
+ tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
+ return photo
+
+
+# ------------------------------- Beginning of GUI CODE -------------------------------
+
+fig = plt.gcf() # if using Pyplot then get the figure from the plot
+figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
+
+# define the window layout
+layout = [[sg.Text('Plot test', font='Any 18')],
+ [sg.Canvas(size=(figure_w, figure_h), key='canvas')],
+ [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
+
+# create the form and show it without the plot
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=True).Layout(
+ layout).Finalize()
+
+# add the plot to the window
+fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
+
+# show it all again and get buttons
+event, values = window.Read()
diff --git a/DemoPrograms old/Demo_Pyploy_Bar_Chart2.py b/DemoPrograms old/Demo_Pyploy_Bar_Chart2.py
new file mode 100644
index 00000000..fa02b35f
--- /dev/null
+++ b/DemoPrograms old/Demo_Pyploy_Bar_Chart2.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import matplotlib
+matplotlib.use('TkAgg')
+from matplotlib.backends.backend_tkagg import FigureCanvasAgg
+import matplotlib.backends.tkagg as tkagg
+import tkinter as Tk
+
+"""
+Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
+
+Paste your Pyplot code into the section marked below.
+
+Do all of your plotting as you normally would, but do NOT call plt.show().
+Stop just short of calling plt.show() and let the GUI do the rest.
+
+The remainder of the program will convert your plot and display it in the GUI.
+If you want to change the GUI, make changes to the GUI portion marked below.
+
+"""
+
+#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
+import matplotlib.pyplot as plt
+import numpy as np
+label = ['Adventure', 'Action', 'Drama', 'Comedy', 'Thriller/Suspense', 'Horror', 'Romantic Comedy', 'Musical',
+ 'Documentary', 'Black Comedy', 'Western', 'Concert/Performance', 'Multiple Genres', 'Reality']
+no_movies = [941, 854, 4595, 2125, 942, 509, 548, 149, 1952, 161, 64, 61, 35, 5]
+
+index = np.arange(len(label))
+plt.bar(index, no_movies)
+plt.xlabel('Genre', fontsize=5)
+plt.ylabel('No of Movies', fontsize=5)
+plt.xticks(index, label, fontsize=5, rotation=30)
+plt.title('Market Share for Each Genre 1995-2017')
+
+#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
+
+#------------------------------- Beginning of Matplotlib helper code -----------------------
+
+
+def draw_figure(canvas, figure, loc=(0, 0)):
+ """ Draw a matplotlib figure onto a Tk canvas
+
+ loc: location of top-left corner of figure on canvas in pixels.
+
+ Inspired by matplotlib source: lib/matplotlib/backends/backend_tkagg.py
+ """
+ figure_canvas_agg = FigureCanvasAgg(figure)
+ figure_canvas_agg.draw()
+ figure_x, figure_y, figure_w, figure_h = figure.bbox.bounds
+ figure_w, figure_h = int(figure_w), int(figure_h)
+ photo = Tk.PhotoImage(master=canvas, width=figure_w, height=figure_h)
+ canvas.create_image(loc[0] + figure_w/2, loc[1] + figure_h/2, image=photo)
+ tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
+ return photo
+
+#------------------------------- Beginning of GUI CODE -------------------------------
+
+fig = plt.gcf() # if using Pyplot then get the figure from the plot
+figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
+
+# define the window layout
+layout = [[sg.Text('Plot test', font='Any 18')],
+ [sg.Canvas(size=(figure_w, figure_h), key='canvas')],
+ [sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
+
+# create the form and show it without the plot
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=True).Layout(layout).Finalize()
+
+# add the plot to the window
+fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
+
+# show it all again and get buttons
+event, values = window.Read()
diff --git a/DemoPrograms old/Demo_Script_Launcher.py b/DemoPrograms old/Demo_Script_Launcher.py
new file mode 100644
index 00000000..7dff6a4f
--- /dev/null
+++ b/DemoPrograms old/Demo_Script_Launcher.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import glob
+import ntpath
+import subprocess
+
+LOCATION_OF_YOUR_SCRIPTS = ''
+
+# Execute the command. Will not see the output from the command until it completes.
+def execute_command_blocking(command, *args):
+ expanded_args = []
+ for a in args:
+ expanded_args.append(a)
+ # expanded_args += a
+ try:
+ sp = subprocess.Popen([command,expanded_args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ out, err = sp.communicate()
+ if out:
+ print(out.decode("utf-8"))
+ if err:
+ print(err.decode("utf-8"))
+ except:
+ out = ''
+ return out
+
+# Executes command and immediately returns. Will not see anything the script outputs
+def execute_command_nonblocking(command, *args):
+ expanded_args = []
+ for a in args:
+ expanded_args += a
+ try:
+ sp = subprocess.Popen([command,expanded_args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except: pass
+
+def Launcher2():
+ sg.ChangeLookAndFeel('GreenTan')
+ window = sg.Window('Script launcher')
+
+ filelist = glob.glob(LOCATION_OF_YOUR_SCRIPTS+'*.py')
+ namesonly = []
+ for file in filelist:
+ namesonly.append(ntpath.basename(file))
+
+ layout = [
+ [sg.Listbox(values=namesonly, size=(30, 19), select_mode=sg.SELECT_MODE_EXTENDED, key='demolist'), sg.Output(size=(88, 20), font='Courier 10')],
+ [sg.Checkbox('Wait for program to complete', default=False, key='wait')],
+ [sg.Button('Run'), sg.Button('Shortcut 1'), sg.Button('Fav Program'), sg.Button('EXIT')],
+ ]
+
+ window.Layout(layout)
+
+ # ---===--- Loop taking in user input --- #
+ while True:
+ event, values = window.Read()
+ if event in ('EXIT', None):
+ break # exit button clicked
+ if event in ('Shortcut 1', 'Fav Program'):
+ print('Quickly launch your favorite programs using these shortcuts')
+ print('Or copy files to your github folder. Or anything else you type on the command line')
+ # copyfile(source, dest)
+ elif event == 'Run':
+ for index, file in enumerate(values['demolist']):
+ print('Launching %s'%file)
+ window.Refresh() # make the print appear immediately
+ if values['wait']:
+ execute_command_blocking(LOCATION_OF_YOUR_SCRIPTS + file)
+ else:
+ execute_command_nonblocking(LOCATION_OF_YOUR_SCRIPTS + file)
+
+
+if __name__ == '__main__':
+ Launcher2()
+
diff --git a/DemoPrograms old/Demo_Script_Launcher_Realtime_Output.py b/DemoPrograms old/Demo_Script_Launcher_Realtime_Output.py
new file mode 100644
index 00000000..12b54c6b
--- /dev/null
+++ b/DemoPrograms old/Demo_Script_Launcher_Realtime_Output.py
@@ -0,0 +1,48 @@
+import subprocess
+import sys
+import PySimpleGUI as sg
+
+"""
+ Demo Program - Realtime output of a shell command in the window
+ Shows how you can run a long-running subprocess and have the output
+ be displayed in realtime in the window.
+"""
+
+def main():
+ layout = [ [sg.Text('Enter the command you wish to run')],
+ [sg.Input(key='_IN_')],
+ [sg.Output(size=(60,15))],
+ [sg.Button('Run'), sg.Button('Exit')] ]
+
+ window = sg.Window('Realtime Shell Command Output', layout)
+
+ while True: # Event Loop
+ event, values = window.Read()
+ # print(event, values)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Run':
+ runCommand(cmd=values['_IN_'], window=window)
+ window.Close()
+
+
+def runCommand(cmd, timeout=None, window=None):
+ """ run shell command
+ @param cmd: command to execute
+ @param timeout: timeout for command execution
+ @param window: the PySimpleGUI window that the output is going to (needed to do refresh on)
+ @return: (return code from command, command output)
+ """
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = ''
+ for line in p.stdout:
+ line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip()
+ output += line
+ print(line)
+ window.Refresh() if window else None # yes, a 1-line if, so shoot me
+
+ retval = p.wait(timeout)
+ return (retval, output)
+
+
+main()
diff --git a/DemoPrograms old/Demo_Script_Parameters.py b/DemoPrograms old/Demo_Script_Parameters.py
new file mode 100644
index 00000000..82fdda64
--- /dev/null
+++ b/DemoPrograms old/Demo_Script_Parameters.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+
+'''
+Quickly add a GUI to your script!
+
+This simple script shows a 1-line-GUI addition to a typical Python command line script.
+
+Previously this script accepted 1 parameter on the command line. When executed, that
+parameter is read into the variable fname.
+
+The 1-line-GUI shows a form that allows the user to browse to find the filename. The GUI
+stores the result in the variable fname, just like the command line parsing did.
+'''
+
+if len(sys.argv) == 1:
+ event, (fname,) = sg.Window('My Script').Layout([[sg.T('Document to open')],
+ [sg.In(), sg.FileBrowse()],
+ [sg.CloseButton('Open'), sg.CloseButton('Cancel')]]).Read()
+else:
+ fname = sys.argv[1]
+
+if not fname:
+ sg.Popup("Cancel", "No filename supplied")
+ raise SystemExit("Cancelling: no filename supplied")
diff --git a/DemoPrograms old/Demo_Sort_Visualizer.py b/DemoPrograms old/Demo_Sort_Visualizer.py
new file mode 100644
index 00000000..1f07a1b9
--- /dev/null
+++ b/DemoPrograms old/Demo_Sort_Visualizer.py
@@ -0,0 +1,59 @@
+import PySimpleGUI as sg
+import random
+# ------- Sort visualizer. Displays bar chart representing list items -------
+BAR_SPACING, BAR_WIDTH, EDGE_OFFSET = 11, 10, 3
+DATA_SIZE = GRAPH_SIZE = (700,500) # width, height of the graph portion
+
+def bubble_sort(arr):
+ def swap(i, j):
+ arr[i], arr[j] = arr[j], arr[i]
+ n = len(arr)
+ swapped = True
+ x = -1
+ while swapped:
+ swapped = False
+ x = x + 1
+ for i in range(1, n - x):
+ if arr[i - 1] > arr[i]:
+ swap(i - 1, i)
+ swapped = True
+ yield arr
+
+def draw_bars(graph, items): # draws all the bars for all values across screen
+ # type: (sg.Graph, List)->None
+ for i, item in enumerate(items):
+ graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, item),
+ bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='#76506d')
+
+def main():
+ sg.change_look_and_feel('LightGreen')
+ # Make list to sort
+ num_bars = DATA_SIZE[0]//(BAR_WIDTH+1)
+ list_to_sort = [DATA_SIZE[1]//num_bars*i for i in range(1,num_bars)]
+ random.shuffle(list_to_sort)
+
+ # define window layout
+ graph = sg.Graph(GRAPH_SIZE, (0,0), DATA_SIZE)
+ layout = [[graph],
+ [sg.T('Speed Faster'), sg.Slider((0,20), orientation='h', default_value=10, key='-SPEED-'), sg.T('Slower')]]
+
+ window = sg.Window('Sort Demonstration', layout, finalize=True)
+ draw_bars(graph, list_to_sort) # draw the initial window's bars
+
+ sg.popup('Click OK to begin Bubblesort') # Wait for user to start it up
+ bsort = bubble_sort(list_to_sort) # get an iterator for the sort
+ timeout=10 # start with 10ms delays between draws
+ while True: # ----- The event loop -----
+ event, values = window.read(timeout=timeout)
+ if event is None:
+ break
+ try:
+ partially_sorted_list = bsort.__next__()
+ except:
+ sg.popup('Sorting done!')
+ break
+ graph.Erase()
+ draw_bars(graph, partially_sorted_list)
+ timeout = int(values['-SPEED-'])
+ window.close()
+main()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Spinner_Compound_Element.py b/DemoPrograms old/Demo_Spinner_Compound_Element.py
new file mode 100644
index 00000000..dc9bb246
--- /dev/null
+++ b/DemoPrograms old/Demo_Spinner_Compound_Element.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+"""
+ Demo of how to combine elements into your own custom element
+"""
+
+sg.SetOptions(element_padding=(0,0))
+# sg.ChangeLookAndFeel('Dark')
+# --- Define our "Big-Button-Spinner" compound element. Has 2 buttons and an input field --- #
+NewSpinner = [sg.Button('-', size=(2,1), font='Any 12'),
+ sg.In('0', size=(2,1), font='Any 14', justification='r', key='spin'),
+ sg.Button('+', size=(2,1), font='Any 12')]
+# --- Define Window --- #
+layout = [
+ [sg.Text('Spinner simulation')],
+ NewSpinner,
+ [sg.T('')],
+ [sg.Ok()]
+ ]
+
+window = sg.Window('Spinner simulation').Layout(layout)
+
+# --- Event Loop --- #
+counter = 0
+while True:
+ event, values = window.Read()
+
+ if event == 'Ok' or event is None: # be nice to your user, always have an exit from your form
+ break
+
+ # --- do spinner stuff --- #
+ counter += 1 if event == '+' else -1 if event == '-' else 0
+ window.FindElement('spin').Update(counter)
diff --git a/DemoPrograms old/Demo_Stdout.py b/DemoPrograms old/Demo_Stdout.py
new file mode 100644
index 00000000..1ad90090
--- /dev/null
+++ b/DemoPrograms old/Demo_Stdout.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+"""
+ Demo program that reroutes stdout and stderr.
+ Type something in the input box and click Print
+ Whatever you typed is "printed" using a standard print statement
+ Use the Output Element in your window layout to reroute stdout
+ You will see the output of the print in the Output Element in the center of the window
+
+"""
+
+
+layout = [
+ [sg.Text('Type something in input field and click print')],
+ [sg.In()],
+ [sg.Output()],
+ [sg.Button('Print')]
+ ]
+
+window = sg.Window('Reroute stdout').Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None:
+ break
+ print('You typed: ', values[0])
diff --git a/DemoPrograms old/Demo_Super_Simple_Form.py b/DemoPrograms old/Demo_Super_Simple_Form.py
new file mode 100644
index 00000000..48bf949a
--- /dev/null
+++ b/DemoPrograms old/Demo_Super_Simple_Form.py
@@ -0,0 +1,11 @@
+import PySimpleGUI as sg
+
+layout = [[sg.Text('Please enter your Name, Address, Phone')],
+ [sg.Text('Name', size=(10,1)), sg.InputText(key='-NAME-')],
+ [sg.Text('Address', size=(10,1)), sg.InputText(key='-ADDRESS-')],
+ [sg.Text('Phone', size=(10,1)), sg.InputText(key='-PHONE-')],
+ [sg.Button('Submit'), sg.Button('Cancel')]]
+
+window = sg.Window('Simple Data Entry Window', layout)
+event, values = window.read()
+print(event, values['-NAME-'], values['-ADDRESS-'], values['-PHONE-'])
diff --git a/DemoPrograms old/Demo_Table_CSV.py b/DemoPrograms old/Demo_Table_CSV.py
new file mode 100644
index 00000000..0662e83d
--- /dev/null
+++ b/DemoPrograms old/Demo_Table_CSV.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import csv
+
+def table_example():
+ filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),))
+ # --- populate table with file contents --- #
+ if filename == '':
+ sys.exit(69)
+ data = []
+ header_list = []
+ button = sg.PopupYesNo('Does this file have column names already?')
+ if filename is not None:
+ with open(filename, "r") as infile:
+ reader = csv.reader(infile)
+ if button == 'Yes':
+ header_list = next(reader)
+ try:
+ data = list(reader) # read everything else into a list of rows
+ if button == 'No':
+ header_list = ['column' + str(x) for x in range(len(data[0]))]
+ except:
+ sg.PopupError('Error reading file')
+ sys.exit(69)
+ sg.SetOptions(element_padding=(0, 0))
+
+ layout = [[sg.Table(values=data,
+ headings=header_list,
+ max_col_width=25,
+ auto_size_columns=True,
+ justification='right',
+ alternating_row_color='lightblue',
+ num_rows=min(len(data), 20))]]
+
+
+ window = sg.Window('Table', grab_anywhere=False).Layout(layout)
+ event, values = window.Read()
+
+ sys.exit(69)
+
+table_example()
diff --git a/DemoPrograms old/Demo_Table_Element.py b/DemoPrograms old/Demo_Table_Element.py
new file mode 100644
index 00000000..4a2eac26
--- /dev/null
+++ b/DemoPrograms old/Demo_Table_Element.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+import sys
+
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import random
+import string
+
+
+# ------------------ Create a fake table ------------------
+class Fake():
+ @classmethod
+ def word(self):
+ return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
+
+ @classmethod
+ def number(self, max=1000):
+ return random.randint(0,max)
+
+
+def make_table(num_rows, num_cols):
+ data = [[j for j in range(num_cols)] for i in range(num_rows)]
+ data[0] = [Fake.word() for _ in range(num_cols)]
+ for i in range(1, num_rows):
+ data[i] = [Fake.word(), *[Fake.number() for i in range(num_cols - 1)]]
+ return data
+
+data = make_table(num_rows=15, num_cols=6)
+# sg.SetOptions(element_padding=(0,0))
+headings = [data[0][x] for x in range(len(data[0]))]
+
+layout = [[sg.Table(values=data[1:][:], headings=headings, max_col_width=25, background_color='lightblue',
+ auto_size_columns=True, display_row_numbers=True, justification='right', num_rows=20, alternating_row_color='blue', key='_table_', tooltip='This is a table')],
+ [sg.Button('Read'), sg.Button('Double'), sg.Button('Update')],
+ [sg.T('Read = read which rows are selected')],[sg.T('Double = double the amount of data in the table')]]
+
+window = sg.Window('Table', grab_anywhere=False, resizable=True).Layout(layout)
+
+while True:
+ event, values = window.Read()
+ print(event, values)
+ if event is None:
+ break
+ if event == 'Double':
+ for i in range(len(data)):
+ data.append(data[i])
+ window.FindElement('_table_').Update(values = data)
+ elif event == 'Update':
+ window.FindElement('_table_').Update( row_colors=((8,'white', 'red'), (9,'black')))
+
+ # sg.Popup(event, values)
+ # print(event, values)
+window.Close()
+sys.exit(69)
diff --git a/DemoPrograms old/Demo_Table_Pandas.py b/DemoPrograms old/Demo_Table_Pandas.py
new file mode 100644
index 00000000..bc192046
--- /dev/null
+++ b/DemoPrograms old/Demo_Table_Pandas.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import pandas as pd
+
+
+def table_example():
+ sg.SetOptions(auto_size_buttons=True)
+ filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files", "*.csv"),))
+ # --- populate table with file contents --- #
+ if filename == '':
+ sys.exit(69)
+ data = []
+ header_list = []
+ button = sg.PopupYesNo('Does this file have column names already?')
+ if filename is not None:
+ try:
+ df = pd.read_csv(filename, sep=',', engine='python', header=None) # Header=None means you directly pass the columns names to the dataframe
+ data = df.values.tolist() # read everything else into a list of rows
+ if button == 'Yes': # Press if you named your columns in the csv
+ header_list = df.iloc[0].tolist() # Uses the first row (which should be column names) as columns names
+ data = df[1:].values.tolist() # Drops the first row in the table (otherwise the header names and the first row will be the same)
+ elif button == 'No': # Press if you didn't name the columns in the csv
+ header_list = ['column' + str(x) for x in range(len(data[0]))] # Creates columns names for each column ('column0', 'column1', etc)
+ except:
+ sg.PopupError('Error reading file')
+ sys.exit(69)
+
+ layout = [[sg.Table(values=data, headings=header_list, display_row_numbers=True,
+ auto_size_columns=False, num_rows=min(25,len(data)))]]
+
+ window = sg.Window('Table', grab_anywhere=False)
+ event, values = window.Layout(layout).Read()
+
+ sys.exit(69)
+
+table_example()
diff --git a/DemoPrograms old/Demo_Table_Simulation.py b/DemoPrograms old/Demo_Table_Simulation.py
new file mode 100644
index 00000000..32018c5a
--- /dev/null
+++ b/DemoPrograms old/Demo_Table_Simulation.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import csv
+
+
+def TableSimulation():
+ """
+ Display data in a table format
+ """
+ sg.SetOptions(element_padding=(0,0))
+
+ menu_def = [['File', ['Open', 'Save', 'Exit']],
+ ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],],
+ ['Help', 'About...'],]
+
+ columm_layout = [[]]
+
+ MAX_ROWS = 20
+ MAX_COL = 10
+ for i in range(MAX_ROWS):
+ inputs = [sg.T('{}'.format(i), size=(4,1), justification='right')] + [sg.In(size=(10, 1), pad=(1, 1), justification='right', key=(i,j), do_not_clear=True) for j in range(MAX_COL)]
+ columm_layout.append(inputs)
+
+ layout = [ [sg.Menu(menu_def)],
+ [sg.T('Table Using Combos and Input Elements', font='Any 18')],
+ [sg.T('Type in a row, column and value. The form will update the values in realtime as you type'),
+ sg.In(key='inputrow', justification='right', size=(8,1), pad=(1,1), do_not_clear=True),
+ sg.In(key='inputcol', size=(8,1), pad=(1,1), justification='right', do_not_clear=True),
+ sg.In(key='value', size=(8,1), pad=(1,1), justification='right', do_not_clear=True)],
+ [sg.Column(columm_layout, size=(800,600), scrollable=True)] ]
+
+ window = sg.Window('Table', return_keyboard_events=True).Layout(layout)
+
+ while True:
+ event, values = window.Read()
+ # --- Process buttons --- #
+ if event is None or event == 'Exit':
+ break
+ elif event == 'About...':
+ sg.Popup('Demo of table capabilities')
+ elif event == 'Open':
+ filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),))
+ # --- populate table with file contents --- #
+ if filename is not None:
+ with open(filename, "r") as infile:
+ reader = csv.reader(infile)
+ try:
+ data = list(reader) # read everything else into a list of rows
+ except:
+ sg.PopupError('Error reading file')
+ continue
+ # clear the table
+ [window.FindElement((i,j)).Update('') for j in range(MAX_COL) for i in range(MAX_ROWS)]
+
+ for i, row in enumerate(data):
+ for j, item in enumerate(row):
+ location = (i,j)
+ try: # try the best we can at reading and filling the table
+ target_element = window.FindElement(location)
+ new_value = item
+ if target_element is not None and new_value != '':
+ target_element.Update(new_value)
+ except:
+ pass
+
+ # if a valid table location entered, change that location's value
+ try:
+ location = (int(values['inputrow']), int(values['inputcol']))
+ target_element = window.FindElement(location)
+ new_value = values['value']
+ if target_element is not None and new_value != '':
+ target_element.Update(new_value)
+ except:
+ pass
+
+TableSimulation()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Tabs.py b/DemoPrograms old/Demo_Tabs.py
new file mode 100644
index 00000000..733cb334
--- /dev/null
+++ b/DemoPrograms old/Demo_Tabs.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+sg.SetOptions(background_color='cornsilk4', element_background_color='cornsilk2', input_elements_background_color='cornsilk2')
+
+tab1_layout = [[sg.T('This is inside tab 1', background_color='darkslateblue', text_color='white')],
+ [sg.In(key='_in0_')]]
+
+tab2_layout = [[sg.T('This is inside tab 2', background_color='tan1')],
+ [sg.In(key='_in2_')]]
+
+
+tab3_layout = [[sg.T('This is inside tab 3')],
+ [sg.In(key='_in2_')]]
+
+tab4_layout = [[sg.T('This is inside tab 4', background_color='darkseagreen')],
+ [sg.In(key='_in3_')]]
+
+tab5_layout = [[sg.T('This is inside tab 5')],
+ [sg.In(key='_in4_')]]
+
+
+layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='_mykey_'),
+ sg.Tab('Tab 2', tab2_layout, background_color='tan1'),
+ sg.Tab('Tab 3', tab3_layout)]],
+ key='_group2_', title_color='red',
+ selected_title_color='green', tab_location='right'),
+ sg.TabGroup([[sg.Tab('Tab 4', tab4_layout,background_color='darkseagreen', key='_mykey_'),
+ sg.Tab('Tab 5', tab5_layout)]], key='_group1_', tab_location='top', selected_title_color='purple')],
+ [sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='_mykey_'),
+ sg.Tab('Tab 2', tab2_layout, background_color='tan1'),
+ sg.Tab('Tab 3', tab3_layout)]],
+ key='_group3_', title_color='red',
+ selected_title_color='green', tab_location='left'),
+ sg.TabGroup([[sg.Tab('Tab 4', tab4_layout,background_color='darkseagreen', key='_mykey_'),
+ sg.Tab('Tab 5', tab5_layout)]], key='_group4_', tab_location='bottom', selected_title_color='purple')],
+ [sg.Button('Read')]]
+
+window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout)
+
+
+while True:
+ event, values = window.Read()
+ sg.PopupNonBlocking(event, values)
+ print(event, values)
+ if event is None: # always, always give a way out!
+ break
diff --git a/DemoPrograms old/Demo_Tabs_Nested.py b/DemoPrograms old/Demo_Tabs_Nested.py
new file mode 100644
index 00000000..8868cb53
--- /dev/null
+++ b/DemoPrograms old/Demo_Tabs_Nested.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+sg.ChangeLookAndFeel('GreenTan')
+tab2_layout = [[sg.T('This is inside tab 2')],
+ [sg.T('Tabs can be anywhere now!')]]
+
+tab1_layout = [[sg.T('Type something here and click button'), sg.In(key='in')]]
+
+tab3_layout = [[sg.T('This is inside tab 3')]]
+tab4_layout = [[sg.T('This is inside tab 4')]]
+
+tab_layout = [[sg.T('This is inside of a tab')]]
+tab_group = sg.TabGroup([[sg.Tab('Tab 7', tab_layout), sg.Tab('Tab 8', tab_layout)]])
+
+tab5_layout = [[sg.T('Watch this window')],
+ [sg.Output(size=(40,5))]]
+tab6_layout = [[sg.T('This is inside tab 6')],
+ [sg.T('How about a second row of stuff in tab 6?'), tab_group]]
+
+layout = [[sg.T('My Window!')], [sg.Frame('A Frame', layout=
+ [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]]), sg.TabGroup([[sg.Tab('Tab3', tab3_layout), sg.Tab('Tab 4', tab4_layout)]])]])],
+ [sg.T('This text is on a row with a column'),sg.Column(layout=[[sg.T('In a column')],
+ [sg.TabGroup([[sg.Tab('Tab 5', tab5_layout), sg.Tab('Tab 6', tab6_layout)]])],
+ [sg.Button('Click me')]])],]
+
+window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout).Finalize()
+
+print('Are there enough tabs for you?')
+
+while True:
+ event, values = window.Read()
+ print(event, values)
+ if event is None: # always, always give a way out!
+ break
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Tabs_Simple.py b/DemoPrograms old/Demo_Tabs_Simple.py
new file mode 100644
index 00000000..d666c326
--- /dev/null
+++ b/DemoPrograms old/Demo_Tabs_Simple.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+tab1_layout = [[sg.T('Tab 1')],
+ [sg.T('Put your layout in here')],
+ [sg.T('Input something'),sg.In(key='_IN0_')]]
+
+tab2_layout = [[sg.T('Tab2')]]
+
+
+layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]], key='_TABGROUP_')],
+ [sg.Button('Read')]]
+
+window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout)
+
+
+while True:
+ event, values = window.Read()
+ sg.PopupNonBlocking('button = %s' % event, 'Values dictionary', values)
+ if event is None: # always, always give a way out!
+ break
diff --git a/DemoPrograms old/Demo_Template.py b/DemoPrograms old/Demo_Template.py
new file mode 100644
index 00000000..581bf934
--- /dev/null
+++ b/DemoPrograms old/Demo_Template.py
@@ -0,0 +1,39 @@
+#choose one of these are your starting point. Copy, paste, have fun
+
+# ---------------------------------#
+# DESIGN PATTERN 1 - Simple Window #
+# ---------------------------------#
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[ sg.Text('My layout') ],
+ [ sg.CloseButton('Next Window')]]
+
+window = sg.Window('My window').Layout(layout)
+event, values = window.Read()
+
+
+# --------------------------------------------------#
+# DESIGN PATTERN 2 - Persistent Window (stays open) #
+# --------------------------------------------------#
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[ sg.Text('My Window') ],
+ [ sg.Button('Read The Window')]]
+
+window = sg.Window('My Window Title').Layout(layout)
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None: # if window closed with X
+ break
+ print(event, values)
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Threaded_Work.py b/DemoPrograms old/Demo_Threaded_Work.py
new file mode 100644
index 00000000..e4dd3b9b
--- /dev/null
+++ b/DemoPrograms old/Demo_Threaded_Work.py
@@ -0,0 +1,102 @@
+#!/usr/bin/python3
+import queue
+import threading
+import time
+import PySimpleGUI as sg
+
+"""
+ You want to look for 3 points in this code, marked with comment "LOCATION X".
+ 1. Where you put your call that takes a long time
+ 2. Where the trigger to make the call takes place in the event loop
+ 3. Where the completion of the call is indicated in the event loop
+
+ Demo on how to add a long-running item to your PySimpleGUI Event Loop
+ If you want to do something that takes a long time, and you do it in the
+ main event loop, you'll quickly begin to see messages from windows that your
+ program has hung, asking if you want to kill it.
+
+ The problem is not that your problem is hung, the problem is that you are
+ not calling Read or Refresh often enough.
+
+ One way through this, shown here, is to put your long work into a thread that
+ is spun off, allowed to work, and then gets back to the GUI when it's done working
+ on that task.
+
+ Every time you start up one of these long-running functions, you'll give it an "ID".
+ When the function completes, it will send to the GUI Event Loop a message with
+ the format:
+ work_id ::: done
+ This makes it easy to parse out your original work ID
+
+ You can hard code these IDs to make your code more readable. For example, maybe
+ you have a function named "update_user_list()". You can call the work ID "user list".
+ Then check for the message coming back later from the work task to see if it starts
+ with "user list". If so, then that long-running task is over.
+
+"""
+
+# ############################# User callable CPU intensive code #############################
+# Put your long running code inside this "wrapper"
+# NEVER make calls to PySimpleGUI from this thread (or any thread)!
+# Create one of these functions for EVERY long-running call you want to make
+def long_function_wrapper(work_id, gui_queue):
+ # LOCATION 1
+ # this is our "long running function call"
+ time.sleep(5) # sleep for a while as a simulation of a long-running computation
+ # at the end of the work, before exiting, send a message back to the GUI indicating end
+ gui_queue.put('{} ::: done'.format(work_id))
+ # at this point, the thread exits
+ return
+
+
+############################# Begin GUI code #############################
+def the_gui():
+ gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code
+
+ layout = [[sg.Text('Multithreaded Work Example')],
+ [sg.Text('Click Go to start a long-running function call')],
+ [sg.Text('', size=(25, 1), key='_OUTPUT_')],
+ [sg.Text('', size=(25, 1), key='_OUTPUT2_')],
+ [sg.Graph((10,10),(0,0),(10,10),background_color='black',key=i) for i in range(20)],
+ [sg.Button('Go'), sg.Button('Popup'), sg.Button('Exit')], ]
+
+ window = sg.Window('Multithreaded Window').Layout(layout)
+ # --------------------- EVENT LOOP ---------------------
+ work_id = 0
+ while True:
+ event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
+ if event is None or event == 'Exit':
+ break
+ if event == 'Go': # clicking "Go" starts a long running work item by starting thread
+ window.Element('_OUTPUT_').Update('Starting long work %s'%work_id)
+ window.Element(work_id).Update(background_color='red')
+ # LOCATION 2
+ # STARTING long run by starting a thread
+ thread_id = threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True)
+ thread_id.start()
+ work_id = work_id+1 if work_id < 19 else 0
+ # --------------- Read next message coming in from threads ---------------
+ try:
+ message = gui_queue.get_nowait() # see if something has been posted to Queue
+ except queue.Empty: # get_nowait() will get exception when Queue is empty
+ message = None # nothing in queue so do nothing
+
+ # if message received from queue, then some work was completed
+ if message is not None:
+ # LOCATION 3
+ # this is the place you would execute code at ENDING of long running task
+ # You can check the completed_work_id variable to see exactly which long-running function completed
+ completed_work_id = int(message[:message.index(' :::')])
+ window.Element('_OUTPUT2_').Update('Complete Work ID "{}"'.format(completed_work_id))
+ window.Element(completed_work_id).Update(background_color='green')
+
+ if event == 'Popup':
+ sg.Popup('This is a popup showing that the GUI is running')
+ # if user exits the window, then close the window and exit the GUI func
+ window.Close()
+
+############################# Main #############################
+
+if __name__ == '__main__':
+ the_gui()
+ print('Exiting Program')
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Timer.py b/DemoPrograms old/Demo_Timer.py
new file mode 100644
index 00000000..4bf3d386
--- /dev/null
+++ b/DemoPrograms old/Demo_Timer.py
@@ -0,0 +1,44 @@
+import PySimpleGUI as sg
+import time
+
+# form that doen't block
+# good for applications with an loop that polls hardware
+def Timer():
+ sg.ChangeLookAndFeel('Dark')
+ sg.SetOptions(element_padding=(0,0))
+ # Make a form, but don't use context manager
+ window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False)
+ # Create a text element that will be updated with status information on the GUI itself
+ # Create the rows
+ form_rows = [[sg.Text('')],
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')],
+ [sg.Button('Pause', key='-RUN-PAUSE-'), sg.Button('Reset'), sg.Exit(button_color=('white','firebrick4'))]]
+ # Layout the rows of the form and perform a read. Indicate the form is non-blocking!
+ window.Layout(form_rows)
+ #
+ # your program's main loop
+ i = 0
+ paused = False
+ start_time = int(round(time.time() * 100))
+ while (True):
+ # This is the code that reads and updates your window
+ button, values = window.read(timeout=0)
+ window.FindElement('text').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100))
+
+ if values is None or button == 'Exit':
+ break
+
+ if button == 'Reset':
+ i=0
+ elif button == '-RUN-PAUSE-':
+ paused = not paused
+ window['-RUN-PAUSE-'].Update('Run' if paused else 'Pause')
+
+ if not paused:
+ i += 1
+
+
+ # Broke out of main loop. Close the window.
+ window.close()
+
+Timer()
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Touch_Keyboard.py b/DemoPrograms old/Demo_Touch_Keyboard.py
new file mode 100644
index 00000000..350eab31
--- /dev/null
+++ b/DemoPrograms old/Demo_Touch_Keyboard.py
@@ -0,0 +1,103 @@
+import PySimpleGUI as sg
+# import PySimpleGUIQt as sg # 100% portable to Qt by changing to this import
+
+
+class keyboard():
+ def __init__(self, location=(None, None), font=('Arial', 16)):
+ self.font = font
+ numberRow = '1234567890'
+ topRow = 'QWERTYUIOP'
+ midRow = 'ASDFGHJKL'
+ bottomRow = 'ZXCVBNM'
+ keyboard_layout = [[sg.Button(c, key=c, size=(4, 2), font=self.font) for c in numberRow] + [
+ sg.Button('⌫', key='back', size=(4, 2), font=self.font),
+ sg.Button('Esc', key='close', size=(4, 2), font=self.font)],
+ [sg.T(' ' * 4)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ topRow] + [sg.Stretch()],
+ [sg.T(' ' * 11)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ midRow] + [sg.Stretch()],
+ [sg.T(' ' * 18)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ bottomRow] + [sg.Stretch()]]
+
+ self.window = sg.Window('keyboard',
+ grab_anywhere=True,
+ keep_on_top=True,
+ alpha_channel=0,
+ no_titlebar=True,
+ element_padding=(0,0),
+ location=location
+ ).Layout(keyboard_layout).Finalize()
+ self.hide()
+
+ def _keyboardhandler(self):
+ if self.event is not None:
+ if self.event == 'close':
+ self.hide()
+ elif len(self.event) == 1:
+ self.focus.Update(self.focus.Get() + self.event)
+ elif self.event == 'back':
+ Text = self.focus.Get()
+ if len(Text) > 0:
+ Text = Text[:-1]
+ self.focus.Update(Text)
+
+ def hide(self):
+ self.visible = False
+ self.window.Disappear()
+
+ def show(self):
+ self.visible = True
+ self.window.Reappear()
+
+ def togglevis(self):
+ if self.visible:
+ self.hide()
+ else:
+ self.show()
+
+ def update(self, focus):
+ self.event, _ = self.window.Read(timeout=0)
+ if focus is not None:
+ self.focus = focus
+ self._keyboardhandler()
+
+ def close(self):
+ self.window.Close()
+
+
+class GUI():
+ def __init__(self):
+ layout = [[sg.Text('Enter Text')],
+ [sg.Input(size=(17, 1), key='input1', do_not_clear=True)],
+ [sg.InputText(size=(17, 1), key='input2', do_not_clear=True)],
+ [sg.Button('on-screen keyboard', key='keyboard')],
+ [sg.Button('close', key='close')]]
+
+ self.mainWindow = sg.Window('On-screen test',
+ grab_anywhere=True,
+ no_titlebar=False,
+ ).Layout(layout).Finalize()
+ location = self.mainWindow.CurrentLocation()
+ location = location[0]-200, location[1]+200
+ self.keyboard = keyboard(location)
+ self.focus = None
+
+ def run(self):
+ while True:
+ cur_focus = self.mainWindow.FindElementWithFocus()
+ if cur_focus is not None:
+ self.focus = cur_focus
+ event, values = self.mainWindow.Read(timeout=200, timeout_key='timeout')
+ if self.focus is not None:
+ self.keyboard.update(self.focus)
+ if event == 'keyboard':
+ self.keyboard.togglevis()
+ elif event == 'close' or event is None:
+ break
+ self.keyboard.close()
+ self.mainWindow.Close()
+
+
+if __name__ == '__main__':
+ app = GUI()
+ app.run()
diff --git a/DemoPrograms old/Demo_Tree_Element.py b/DemoPrograms old/Demo_Tree_Element.py
new file mode 100644
index 00000000..e83ec3e8
--- /dev/null
+++ b/DemoPrograms old/Demo_Tree_Element.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+import sys
+import os
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+
+"""
+ A PySimpleGUI or PySimpleGUIQt demo program that will display a folder heirarchy with icons for the folders and files.
+ Note that if you are scanning a large folder then tkinter will eventually complain abouit too many bitmaps and crash
+ Getting events back from clicks on the entries works for PySimpleGUI, but appears to not be implemented in PySimpleGUIQt
+ If you need tree events using PySimpleGUIQt then post an Issue on the GitHub http://www.PySimpleGUI.com
+"""
+
+# Base64 versions of images of a folder and a file. PNG files (may not work with PySimpleGUI27, swap with GIFs)
+
+folder_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABnUlEQVQ4y8WSv2rUQRSFv7vZgJFFsQg2EkWb4AvEJ8hqKVilSmFn3iNvIAp21oIW9haihBRKiqwElMVsIJjNrprsOr/5dyzml3UhEQIWHhjmcpn7zblw4B9lJ8Xag9mlmQb3AJzX3tOX8Tngzg349q7t5xcfzpKGhOFHnjx+9qLTzW8wsmFTL2Gzk7Y2O/k9kCbtwUZbV+Zvo8Md3PALrjoiqsKSR9ljpAJpwOsNtlfXfRvoNU8Arr/NsVo0ry5z4dZN5hoGqEzYDChBOoKwS/vSq0XW3y5NAI/uN1cvLqzQur4MCpBGEEd1PQDfQ74HYR+LfeQOAOYAmgAmbly+dgfid5CHPIKqC74L8RDyGPIYy7+QQjFWa7ICsQ8SpB/IfcJSDVMAJUwJkYDMNOEPIBxA/gnuMyYPijXAI3lMse7FGnIKsIuqrxgRSeXOoYZUCI8pIKW/OHA7kD2YYcpAKgM5ABXk4qSsdJaDOMCsgTIYAlL5TQFTyUIZDmev0N/bnwqnylEBQS45UKnHx/lUlFvA3fo+jwR8ALb47/oNma38cuqiJ9AAAAAASUVORK5CYII='
+
+file_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABU0lEQVQ4y52TzStEURiHn/ecc6XG54JSdlMkNhYWsiILS0lsJaUsLW2Mv8CfIDtr2VtbY4GUEvmIZnKbZsY977Uwt2HcyW1+dTZvt6fn9557BGB+aaNQKBR2ifkbgWR+cX13ubO1svz++niVTA1ArDHDg91UahHFsMxbKWycYsjze4muTsP64vT43v7hSf/A0FgdjQPQWAmco68nB+T+SFSqNUQgcIbN1bn8Z3RwvL22MAvcu8TACFgrpMVZ4aUYcn77BMDkxGgemAGOHIBXxRjBWZMKoCPA2h6qEUSRR2MF6GxUUMUaIUgBCNTnAcm3H2G5YQfgvccYIXAtDH7FoKq/AaqKlbrBj2trFVXfBPAea4SOIIsBeN9kkCwxsNkAqRWy7+B7Z00G3xVc2wZeMSI4S7sVYkSk5Z/4PyBWROqvox3A28PN2cjUwinQC9QyckKALxj4kv2auK0xAAAAAElFTkSuQmCC'
+
+starting_path = sg.PopupGetFolder('Folder to display')
+
+if not starting_path:
+ sys.exit()
+
+treedata = sg.TreeData()
+
+def add_files_in_folder(parent, dirname):
+ files = os.listdir(dirname)
+ for f in files:
+ fullname = os.path.join(dirname,f)
+ if os.path.isdir(fullname): # if it's a folder, add folder and recurse
+ treedata.Insert(parent, fullname, f, values=[], icon=folder_icon)
+ add_files_in_folder(fullname, fullname)
+ else:
+
+ treedata.Insert(parent, fullname, f, values=[os.stat(fullname).st_size], icon=file_icon)
+
+add_files_in_folder('', starting_path)
+
+
+layout = [[ sg.Text('File and folder browser Test') ],
+ [ sg.Tree(data=treedata,
+ headings=['Size', ],
+ auto_size_columns=True,
+ num_rows=20,
+ col0_width=30,
+ key='_TREE_',
+ show_expanded=False,
+ enable_events=True),
+ ],
+ [ sg.Button('Ok'), sg.Button('Cancel')]]
+
+window = sg.Window('Tree Element Test').Layout(layout)
+
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event in (None, 'Cancel'):
+ break
+ print(event, values)
+
diff --git a/DemoPrograms old/Demo_Turtle.py b/DemoPrograms old/Demo_Turtle.py
new file mode 100644
index 00000000..dcc2b0b2
--- /dev/null
+++ b/DemoPrograms old/Demo_Turtle.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+import turtle
+
+"""
+ Demo showing how to integrate drawing on a Canvas using Turtle with PySimpleGUI
+ The patern to follow:
+ Create Window & Finalize
+ Get the tkinter Canvas from the Canvas element
+ Draw on the tkinter Canvas using turtle commands.
+ Results are shown on the canvas immiedately after button press / drawing command
+"""
+
+
+layout = [[ sg.Text('My layout') ],
+ [sg.Canvas(size=(800,800), key='_canvas_')],
+ [ sg.Button('F'), sg.Button('B'), sg.Button('L'), sg.Button('R')],
+ [sg.Button('Spiral'), sg.Button('Inside Out'), sg.Button('Circles')]]
+
+window = sg.Window('My new window').Layout(layout).Finalize()
+
+canvas = window.FindElement('_canvas_').TKCanvas
+
+t = turtle.RawTurtle(canvas)
+t.pencolor("#ff0000") # Red
+
+t.penup()
+t.pendown()
+
+while True: # Event Loop
+ event, values = window.Read()
+ if event is None:
+ break
+
+ if event == 'F':
+ t.forward(100)
+ elif event == 'B':
+ t.back(100)
+ elif event == 'L':
+ t.left(90)
+ elif event == 'R':
+ t.right(90)
+ elif event == 'Spiral':
+ canvas.config(bg='light green')
+ t.color("blue")
+ def sqrfunc(size):
+ for i in range(4):
+ t.fd(size)
+ t.left(90)
+ size = size - 5
+ sqrfunc(146)
+ sqrfunc(126)
+ sqrfunc(106)
+ sqrfunc(86)
+ sqrfunc(66)
+ sqrfunc(46)
+ sqrfunc(26)
+ elif event == 'Inside Out':
+ canvas.config(bg = "light green")
+ t.color("blue")
+ def sqrfunc(size):
+ for i in range(4):
+ t.fd(size)
+ t.left(90)
+ size = size + 5
+ sqrfunc(6)
+ sqrfunc(26)
+ sqrfunc(46)
+ sqrfunc(66)
+ sqrfunc(86)
+ sqrfunc(106)
+ sqrfunc(126)
+ sqrfunc(146)
+ elif event == 'Circles':
+ t.speed(0)
+ for i in range(400):
+ t.circle(2 * i*.25)
+ t.circle(-2 * i*.25)
+ t.left(i)
diff --git a/DemoPrograms/Demo_Uno_Card_Game.py b/DemoPrograms old/Demo_Uno_Card_Game.py
similarity index 100%
rename from DemoPrograms/Demo_Uno_Card_Game.py
rename to DemoPrograms old/Demo_Uno_Card_Game.py
diff --git a/DemoPrograms old/Demo_Window_Disappear.py b/DemoPrograms old/Demo_Window_Disappear.py
new file mode 100644
index 00000000..603fdb0d
--- /dev/null
+++ b/DemoPrograms old/Demo_Window_Disappear.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+
+layout = [[ sg.Text('My Window') ],
+ [ sg.Button('Disappear')]]
+
+window = sg.Window('My window').Layout(layout)
+
+while True:
+ event, values = window.Read()
+ if event is None:
+ break
+ if event == 'Disappear':
+ window.Disappear()
+ sg.Popup('Click OK to make window reappear')
+ window.Reappear()
+
diff --git a/DemoPrograms old/Demo_YouTube_Intro.py b/DemoPrograms old/Demo_YouTube_Intro.py
new file mode 100644
index 00000000..88c2690f
--- /dev/null
+++ b/DemoPrograms old/Demo_YouTube_Intro.py
@@ -0,0 +1,11 @@
+import PySimpleGUI as sg
+
+layout = [[sg.Text('What is your name?')],
+ [sg.InputText()],
+ [sg.Button('Ok')]]
+
+window = sg.Window('Title of Window').Layout(layout)
+
+event, values = window.Read()
+
+sg.Popup('Hello {}'.format(values[0]))
\ No newline at end of file
diff --git a/DemoPrograms old/Demo_Youtube-dl_Frontend.py b/DemoPrograms old/Demo_Youtube-dl_Frontend.py
new file mode 100644
index 00000000..c2503b85
--- /dev/null
+++ b/DemoPrograms old/Demo_Youtube-dl_Frontend.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import subprocess
+
+"""
+Simple wrapper for youtube-dl.exe.
+Paste the youtube link into the GUI. The GUI link is queried when you click Get List.
+Get List will populate the pulldown list with the language options available for the video.
+Choose the language to download and click Download
+"""
+
+def DownloadSubtitlesGUI():
+ sg.ChangeLookAndFeel('Dark')
+
+ combobox = sg.InputCombo(values=['',], size=(10,1), key='lang')
+ layout = [
+ [sg.Text('Subtitle Grabber', size=(40, 1), font=('Any 15'))],
+ [sg.T('YouTube Link'),sg.In(default_text='',size=(60,1), key='link', do_not_clear=True) ],
+ [sg.Output(size=(90,20), font='Courier 12')],
+ [sg.Button('Get List')],
+ [sg.T('Language Code'), combobox, sg.Button('Download')],
+ [sg.Button('Exit', button_color=('white', 'firebrick3'))]
+ ]
+
+ window = sg.Window('Subtitle Grabber launcher', text_justification='r', default_element_size=(15,1), font=('Any 14')).Layout(layout)
+
+ # ---===--- Loop taking in user input and using it to query HowDoI --- #
+ while True:
+ event, values = window.Read()
+ if event in ('Exit', None):
+ break # exit button clicked
+ link = values['link']
+ if event == 'Get List':
+ print('Getting list of subtitles....')
+ window.Refresh()
+ command = [f'C:\\Python\\Anaconda3\\Scripts\\youtube-dl.exe --list-subs {link}',]
+ output = ExecuteCommandSubprocess(command, wait=True, quiet=True)
+ lang_list = [o[:5].rstrip() for o in output.split('\n') if 'vtt' in o]
+ lang_list = sorted(lang_list)
+ combobox.Update(values=lang_list)
+ print('Done')
+
+ elif event == 'Download':
+ lang = values['lang'] or 'en'
+ print(f'Downloading subtitle for {lang}...')
+ window.Refresh()
+ command = [f'C:\\Python\\Anaconda3\\Scripts\\youtube-dl.exe --sub-lang {lang} --write-sub {link}',]
+ print(ExecuteCommandSubprocess(command, wait=True, quiet=False))
+ print('Done')
+
+
+def ExecuteCommandSubprocess(command, wait=False, quiet=True, *args):
+ try:
+ sp = subprocess.Popen([command,*args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ if wait:
+ out, err = sp.communicate()
+ if not quiet:
+ if out:
+ print(out.decode("utf-8"))
+ if err:
+ print(err.decode("utf-8"))
+ except Exception as e:
+ print('Exception encountered running command ', e)
+ return ''
+
+ return (out.decode('utf-8'))
+
+
+if __name__ == '__main__':
+ DownloadSubtitlesGUI()
+
diff --git a/DemoPrograms old/Demo_psutil_Kill_Processes.py b/DemoPrograms old/Demo_psutil_Kill_Processes.py
new file mode 100644
index 00000000..95be7052
--- /dev/null
+++ b/DemoPrograms old/Demo_psutil_Kill_Processes.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+import sys
+import sys
+if sys.version_info[0] >= 3:
+ import PySimpleGUI as sg
+else:
+ import PySimpleGUI27 as sg
+import os
+import signal
+import psutil
+import operator
+
+CONFIRM_KILLS = False
+
+"""
+ Utility to show running processes, CPU usage and provides way to kill processes.
+ Based on psutil package that is easily installed using pip
+"""
+
+def kill_proc_tree(pid, sig=signal.SIGTERM, include_parent=True,
+ timeout=None, on_terminate=None):
+ """Kill a process tree (including grandchildren) with signal
+ "sig" and return a (gone, still_alive) tuple.
+ "on_terminate", if specified, is a callabck function which is
+ called as soon as a child terminates.
+ """
+ if pid == os.getpid():
+ raise RuntimeError("I refuse to kill myself")
+ parent = psutil.Process(pid)
+ children = parent.children(recursive=True)
+ if include_parent:
+ children.append(parent)
+ for p in children:
+ p.send_signal(sig)
+ gone, alive = psutil.wait_procs(children, timeout=timeout,
+ callback=on_terminate)
+ return (gone, alive)
+
+
+def show_list_by_name(window):
+ psutil.cpu_percent(interval=.1)
+ procs = psutil.process_iter()
+ all_procs = [[proc.cpu_percent(), proc.name(), proc.pid] for proc in procs]
+ sorted_by_cpu_procs = sorted(all_procs, key=operator.itemgetter(1), reverse=False)
+ display_list = []
+ for process in sorted_by_cpu_procs:
+ display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0] / 10, process[1]))
+ window.FindElement('_processes_').Update(display_list)
+ return display_list
+
+def main():
+
+ # ---------------- Create Form ----------------
+ # sg.ChangeLookAndFeel('Topanga')
+
+ layout = [[sg.Text('Process Killer - Choose one or more processes',
+ size=(45,1), font=('Helvetica', 15), text_color='red')],
+ [sg.Listbox(values=[' '], size=(50, 30), select_mode=sg.SELECT_MODE_EXTENDED, font=('Courier', 12), key='_processes_')],
+ [sg.Text('Click refresh once or twice.. once for list, second to get CPU usage')],
+ [sg.T('Filter by typing name', font='ANY 14'), sg.In(size=(15,1), font='any 14', key='_filter_')],
+ [sg.Button('Sort by Name', ),
+ sg.Button('Sort by % CPU', button_color=('white', 'DarkOrange2')),
+ sg.Button('Kill', button_color=('white','red'), bind_return_key=True),
+ sg.Exit(button_color=('white', 'sea green'))]]
+
+ window = sg.Window('Process Killer',
+ keep_on_top=True,
+ auto_size_buttons=False,
+ default_button_element_size=(12,1),
+ return_keyboard_events=True,
+ ).Layout(layout).Finalize()
+
+
+ display_list = show_list_by_name(window)
+ # ---------------- main loop ----------------
+ while (True):
+ # --------- Read and update window --------
+ event, values = window.Read()
+ if event is None or event == 'Exit':
+ break
+
+ # skip mouse, control key and shift key events entirely
+ if 'Mouse' in event or 'Control' in event or 'Shift' in event:
+ continue
+
+ # --------- Do Button Operations --------
+ if event == 'Sort by Name':
+ display_list = show_list_by_name(window)
+ # psutil.cpu_percent(interval=.1)
+ # procs = psutil.process_iter()
+ # all_procs = [[proc.cpu_percent(), proc.name(), proc.pid] for proc in procs]
+ # sorted_by_cpu_procs = sorted(all_procs, key=operator.itemgetter(1), reverse=False)
+ # display_list = []
+ # for process in sorted_by_cpu_procs:
+ # display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0]/10, process[1]))
+ # window.FindElement('_processes_').Update(display_list)
+ elif event == 'Kill':
+ processes_to_kill = values['_processes_']
+ for proc in processes_to_kill:
+ pid = int(proc[0:5])
+ # if sg.PopupYesNo('About to kill {} {}'.format(pid, proc[12:]), keep_on_top=True) == 'Yes':
+ try:
+ kill_proc_tree(pid=pid)
+ except:
+ sg.PopupNoWait('Error killing process', auto_close_duration=1, auto_close=True)
+ elif event == 'Sort by % CPU':
+ psutil.cpu_percent(interval=.1)
+ procs = psutil.process_iter()
+ all_procs = [[proc.cpu_percent(), proc.name(), proc.pid] for proc in procs]
+ sorted_by_cpu_procs = sorted(all_procs, key=operator.itemgetter(0), reverse=True)
+ display_list = []
+ for process in sorted_by_cpu_procs:
+ display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0]/10, process[1]))
+ window.FindElement('_processes_').Update(display_list)
+ else: # was a typed character
+ if display_list is not None:
+ new_output = []
+ for line in display_list:
+ if values['_filter_'] in line.lower():
+ new_output.append(line)
+ window.FindElement('_processes_').Update(new_output)
+
+
+if __name__ == "__main__":
+ main()
+ sys.exit(0)
\ No newline at end of file
diff --git a/DemoPrograms old/TutorialCPUUtilization.py b/DemoPrograms old/TutorialCPUUtilization.py
new file mode 100644
index 00000000..003115d7
--- /dev/null
+++ b/DemoPrograms old/TutorialCPUUtilization.py
@@ -0,0 +1,18 @@
+import PySimpleGUI as sg
+import psutil
+
+layout = [ [sg.Text('CPU Utilization')] ,
+ [sg.Text('', size=(8,2), font='Helvetica 20', justification='center', key='_text_')],
+ [sg.Exit()]]
+
+window = sg.Window('CPU Meter').Layout(layout)
+
+while True:
+ button, values = window.ReadNonBlocking()
+
+ if button == 'Exit' or values is None:
+ break
+
+ cpu_percent = psutil.cpu_percent(interval=1)
+
+ window.FindElement('_text_').Update(f'CPU {cpu_percent:02.0f}%')
diff --git a/DemoPrograms old/default_icon.ico b/DemoPrograms old/default_icon.ico
new file mode 100644
index 00000000..1a41525e
Binary files /dev/null and b/DemoPrograms old/default_icon.ico differ
diff --git a/DemoPrograms old/ping.py b/DemoPrograms old/ping.py
new file mode 100644
index 00000000..3df8635d
--- /dev/null
+++ b/DemoPrograms old/ping.py
@@ -0,0 +1,572 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+ A pure python ping implementation using raw sockets.
+
+ (This is Python 3 port of https://github.com/jedie/python-ping)
+ (Tested and working with python 2.7, should work with 2.6+)
+
+ Note that ICMP messages can only be sent from processes running as root
+ (in Windows, you must run this script as 'Administrator').
+
+ Derived from ping.c distributed in Linux's netkit. That code is
+ copyright (c) 1989 by The Regents of the University of California.
+ That code is in turn derived from code written by Mike Muuss of the
+ US Army Ballistic Research Laboratory in December, 1983 and
+ placed in the public domain. They have my thanks.
+
+ Bugs are naturally mine. I'd be glad to hear about them. There are
+ certainly word - size dependencies here.
+
+ Copyright (c) Matthew Dixon Cowles, .
+ Distributable under the terms of the GNU General Public License
+ version 2. Provided with no warranties of any sort.
+
+ Original Version from Matthew Dixon Cowles:
+ -> ftp://ftp.visi.com/users/mdc/ping.py
+
+ Rewrite by Jens Diemer:
+ -> http://www.python-forum.de/post-69122.html#69122
+
+ Rewrite by George Notaras:
+ -> http://www.g-loaded.eu/2009/10/30/python-ping/
+
+ Enhancements by Martin Falatic:
+ -> http://www.falatic.com/index.php/39/pinging-with-python
+
+ Enhancements and fixes by Georgi Kolev:
+ -> http://github.com/jedie/python-ping/
+
+ Bug fix by Andrejs Rozitis:
+ -> http://github.com/rozitis/python-ping/
+
+ Revision history
+ ~~~~~~~~~~~~~~~~
+ May 1, 2014
+ -----------
+ Little modifications by Mohammad Emami
+ - Added Python 3 support. For now this project will just support
+ python 3.x
+ - Tested with python 3.3
+ - version was upped to 0.6
+
+ March 19, 2013
+ --------------
+ * Fixing bug to prevent divide by 0 during run-time.
+
+ January 26, 2012
+ ----------------
+ * Fixing BUG #4 - competability with python 2.x [tested with 2.7]
+ - Packet data building is different for 2.x and 3.x.
+ 'cose of the string/bytes difference.
+ * Fixing BUG #10 - the multiple resolv issue.
+ - When pinging domain names insted of hosts (for exmaple google.com)
+ you can get different IP every time you try to resolv it, we should
+ resolv the host only once and stick to that IP.
+ * Fixing BUGs #3 #10 - Doing hostname resolv only once.
+ * Fixing BUG #14 - Removing all 'global' stuff.
+ - You should not use globul! Its bad for you...and its not thread safe!
+ * Fix - forcing the use of different times on linux/windows for
+ more accurate mesurments. (time.time - linux/ time.clock - windows)
+ * Adding quiet_ping function - This way we'll be able to use this script
+ as external lib.
+ * Changing default timeout to 3s. (1second is not enought)
+ * Switching data syze to packet size. It's easyer for the user to ignore the
+ fact that the packet headr is 8b and the datasize 64 will make packet with
+ size 72.
+
+ October 12, 2011
+ --------------
+ Merged updates from the main project
+ -> https://github.com/jedie/python-ping
+
+ September 12, 2011
+ --------------
+ Bugfixes + cleanup by Jens Diemer
+ Tested with Ubuntu + Windows 7
+
+ September 6, 2011
+ --------------
+ Cleanup by Martin Falatic. Restored lost comments and docs. Improved
+ functionality: constant time between pings, internal times consistently
+ use milliseconds. Clarified annotations (e.g., in the checksum routine).
+ Using unsigned data in IP & ICMP header pack/unpack unless otherwise
+ necessary. Signal handling. Ping-style output formatting and stats.
+
+ August 3, 2011
+ --------------
+ Ported to py3k by Zach Ware. Mostly done by 2to3; also minor changes to
+ deal with bytes vs. string changes (no more ord() in checksum() because
+ >source_string< is actually bytes, added .encode() to data in
+ send_one_ping()). That's about it.
+
+ March 11, 2010
+ --------------
+ changes by Samuel Stauffer:
+ - replaced time.clock with default_timer which is set to
+ time.clock on windows and time.time on other systems.
+
+ November 8, 2009
+ ----------------
+ Improved compatibility with GNU/Linux systems.
+
+ Fixes by:
+ * George Notaras -- http://www.g-loaded.eu
+ Reported by:
+ * Chris Hallman -- http://cdhallman.blogspot.com
+
+ Changes in this release:
+ - Re-use time.time() instead of time.clock(). The 2007 implementation
+ worked only under Microsoft Windows. Failed on GNU/Linux.
+ time.clock() behaves differently under the two OSes[1].
+
+ [1] http://docs.python.org/library/time.html#time.clock
+
+ May 30, 2007
+ ------------
+ little rewrite by Jens Diemer:
+ - change socket asterisk import to a normal import
+ - replace time.time() with time.clock()
+ - delete "return None" (or change to "return" only)
+ - in checksum() rename "str" to "source_string"
+
+ December 4, 2000
+ ----------------
+ Changed the struct.pack() calls to pack the checksum and ID as
+ unsigned. My thanks to Jerome Poincheval for the fix.
+
+ November 22, 1997
+ -----------------
+ Initial hack. Doesn't do much, but rather than try to guess
+ what features I (or others) will want in the future, I've only
+ put in what I need now.
+
+ December 16, 1997
+ -----------------
+ For some reason, the checksum bytes are in the wrong order when
+ this is run under Solaris 2.X for SPARC but it works right under
+ Linux x86. Since I don't know just what's wrong, I'll swap the
+ bytes always and then do an htons().
+
+ ===========================================================================
+ IP header info from RFC791
+ -> http://tools.ietf.org/html/rfc791)
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Version| IHL |Type of Service| Total Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identification |Flags| Fragment Offset |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Time to Live | Protocol | Header Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Source Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Destination Address |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options | Padding |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ ===========================================================================
+ ICMP Echo / Echo Reply Message header info from RFC792
+ -> http://tools.ietf.org/html/rfc792
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identifier | Sequence Number |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Data ...
+ +-+-+-+-+-
+
+ ===========================================================================
+ ICMP parameter info:
+ -> http://www.iana.org/assignments/icmp-parameters/icmp-parameters.xml
+
+ ===========================================================================
+ An example of ping's typical output:
+
+ PING heise.de (193.99.144.80): 56 data bytes
+ 64 bytes from 193.99.144.80: icmp_seq=0 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=1 ttl=240 time=127 ms
+ 64 bytes from 193.99.144.80: icmp_seq=2 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=3 ttl=240 time=126 ms
+ 64 bytes from 193.99.144.80: icmp_seq=4 ttl=240 time=127 ms
+
+ ----heise.de PING Statistics----
+ 5 packets transmitted, 5 packets received, 0.0% packet loss
+ round-trip (ms) min/avg/max/med = 126/127/127/127
+
+ ===========================================================================
+"""
+
+#=============================================================================#
+import argparse
+import os, sys, socket, struct, select, time, signal
+
+__description__ = 'A pure python ICMP ping implementation using raw sockets.'
+
+if sys.platform == "win32":
+ # On Windows, the best timer is time.clock()
+ default_timer = time.clock
+else:
+ # On most other platforms the best timer is time.time()
+ default_timer = time.time
+
+NUM_PACKETS = 3
+PACKET_SIZE = 64
+WAIT_TIMEOUT = 3.0
+
+#=============================================================================#
+# ICMP parameters
+
+ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
+ICMP_ECHO = 8 # Echo request (per RFC792)
+ICMP_MAX_RECV = 2048 # Max size of incoming buffer
+
+MAX_SLEEP = 1000
+
+class MyStats:
+ thisIP = "0.0.0.0"
+ pktsSent = 0
+ pktsRcvd = 0
+ minTime = 999999999
+ maxTime = 0
+ totTime = 0
+ avrgTime = 0
+ fracLoss = 1.0
+
+myStats = MyStats # NOT Used globally anymore.
+
+#=============================================================================#
+def checksum(source_string):
+ """
+ A port of the functionality of in_cksum() from ping.c
+ Ideally this would act on the string as a series of 16-bit ints (host
+ packed), but this works.
+ Network data is big-endian, hosts are typically little-endian
+ """
+ countTo = (int(len(source_string)/2))*2
+ sum = 0
+ count = 0
+
+ # Handle bytes in pairs (decoding as short ints)
+ loByte = 0
+ hiByte = 0
+ while count < countTo:
+ if (sys.byteorder == "little"):
+ loByte = source_string[count]
+ hiByte = source_string[count + 1]
+ else:
+ loByte = source_string[count + 1]
+ hiByte = source_string[count]
+ try: # For Python3
+ sum = sum + (hiByte * 256 + loByte)
+ except: # For Python2
+ sum = sum + (ord(hiByte) * 256 + ord(loByte))
+ count += 2
+
+ # Handle last byte if applicable (odd-number of bytes)
+ # Endianness should be irrelevant in this case
+ if countTo < len(source_string): # Check for odd length
+ loByte = source_string[len(source_string)-1]
+ try: # For Python3
+ sum += loByte
+ except: # For Python2
+ sum += ord(loByte)
+
+ sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
+ # uses signed ints, but overflow is unlikely in ping)
+
+ sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
+ sum += (sum >> 16) # Add carry from above (if any)
+ answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ answer = socket.htons(answer)
+
+ return answer
+
+#=============================================================================#
+def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet = False):
+ """
+ Returns either the delay (in ms) or None on timeout.
+ """
+ delay = None
+
+ try: # One could use UDP here, but it's obscure
+ mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ except socket.error as e:
+ print("failed. (socket error: '%s')" % e.args[1])
+ raise # raise the original error
+
+ my_ID = os.getpid() & 0xFFFF
+
+ sentTime = send_one_ping(mySocket, destIP, my_ID, mySeqNumber, packet_size)
+ if sentTime == None:
+ mySocket.close()
+ return delay
+
+ myStats.pktsSent += 1
+
+ recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout)
+
+ mySocket.close()
+
+ if recvTime:
+ delay = (recvTime-sentTime)*1000
+ if not quiet:
+ print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % (
+ dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay)
+ )
+ myStats.pktsRcvd += 1
+ myStats.totTime += delay
+ if myStats.minTime > delay:
+ myStats.minTime = delay
+ if myStats.maxTime < delay:
+ myStats.maxTime = delay
+ else:
+ delay = None
+ print("Request timed out.")
+
+ return delay
+
+#=============================================================================#
+def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
+ """
+ Send one ping to the given >destIP<.
+ """
+ #destIP = socket.gethostbyname(destIP)
+
+ # Header is type (8), code (8), checksum (16), id (16), sequence (16)
+ # (packet_size - 8) - Remove header size from packet size
+ myChecksum = 0
+
+ # Make a dummy heder with a 0 checksum.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ padBytes = []
+ startVal = 0x42
+ # 'cose of the string/byte changes in python 2/3 we have
+ # to build the data differnely for different version
+ # or it will make packets with unexpected size.
+ if sys.version[:1] == '2':
+ bytes = struct.calcsize("d")
+ data = ((packet_size - 8) - bytes) * "Q"
+ data = struct.pack("d", default_timer()) + data
+ else:
+ for i in range(startVal, startVal + (packet_size-8)):
+ padBytes += [(i & 0xff)] # Keep chars in the 0-255 range
+ #data = bytes(padBytes)
+ data = bytearray(padBytes)
+
+
+ # Calculate the checksum on the data and the dummy header.
+ myChecksum = checksum(header + data) # Checksum is in network order
+
+ # Now that we have the right checksum, we put that in. It's just easier
+ # to make up a new header than to stuff it into the dummy.
+ header = struct.pack(
+ "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
+ )
+
+ packet = header + data
+
+ sendTime = default_timer()
+
+ try:
+ mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP
+ except socket.error as e:
+ print("General failure (%s)" % (e.args[1]))
+ return
+
+ return sendTime
+
+#=============================================================================#
+def receive_one_ping(mySocket, myID, timeout):
+ """
+ Receive the ping from the socket. Timeout = in ms
+ """
+ timeLeft = timeout/1000
+
+ while True: # Loop while waiting for packet or timeout
+ startedSelect = default_timer()
+ whatReady = select.select([mySocket], [], [], timeLeft)
+ howLongInSelect = (default_timer() - startedSelect)
+ if whatReady[0] == []: # Timeout
+ return None, 0, 0, 0, 0
+
+ timeReceived = default_timer()
+
+ recPacket, addr = mySocket.recvfrom(ICMP_MAX_RECV)
+
+ ipHeader = recPacket[:20]
+ iphVersion, iphTypeOfSvc, iphLength, \
+ iphID, iphFlags, iphTTL, iphProtocol, \
+ iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
+ "!BBHHHBBHII", ipHeader
+ )
+
+ icmpHeader = recPacket[20:28]
+ icmpType, icmpCode, icmpChecksum, \
+ icmpPacketID, icmpSeqNumber = struct.unpack(
+ "!BBHHH", icmpHeader
+ )
+
+ if icmpPacketID == myID: # Our packet
+ dataSize = len(recPacket) - 28
+ #print (len(recPacket.encode()))
+ return timeReceived, (dataSize+8), iphSrcIP, icmpSeqNumber, iphTTL
+
+ timeLeft = timeLeft - howLongInSelect
+ if timeLeft <= 0:
+ return None, 0, 0, 0, 0
+
+#=============================================================================#
+def dump_stats(myStats):
+ """
+ Show stats when pings are done
+ """
+ print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP))
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd)/myStats.pktsSent
+
+ print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
+ myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss
+ ))
+
+ if myStats.pktsRcvd > 0:
+ print("round-trip (ms) min/avg/max = %d/%0.1f/%d" % (
+ myStats.minTime, myStats.totTime/myStats.pktsRcvd, myStats.maxTime
+ ))
+
+ print("")
+ return
+
+#=============================================================================#
+def signal_handler(signum, frame):
+ """
+ Handle exit via signals
+ """
+ dump_stats()
+ print("\n(Terminated with signal %d)\n" % (signum))
+ sys.exit(0)
+
+#=============================================================================#
+def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Send >count< ping to >destIP< with the given >timeout< and display
+ the result.
+ """
+ signal.signal(signal.SIGINT, signal_handler) # Handle Ctrl-C
+ if hasattr(signal, "SIGBREAK"):
+ # Handle Ctrl-Break e.g. under Windows
+ signal.signal(signal.SIGBREAK, signal_handler)
+
+ myStats = MyStats() # Reset the stats
+
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ print("\nPYTHON PING %s (%s): %d data bytes" % (hostname, destIP, packet_size))
+ except socket.gaierror as e:
+ print("\nPYTHON PING: Unknown host: %s (%s)" % (hostname, e.args[1]))
+ print()
+ return
+
+ myStats.thisIP = destIP
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay)/1000)
+
+ dump_stats(myStats)
+
+#=============================================================================#
+def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
+ packet_size=PACKET_SIZE, path_finder=False):
+ """
+ Same as verbose_ping, but the results are returned as tuple
+ """
+ myStats = MyStats() # Reset the stats
+ mySeqNumber = 0 # Starting value
+
+ try:
+ destIP = socket.gethostbyname(hostname)
+ except socket.gaierror as e:
+ return False
+
+ myStats.thisIP = destIP
+
+ # This will send packet that we dont care about 0.5 seconds before it starts
+ # acrutally pinging. This is needed in big MAN/LAN networks where you sometimes
+ # loose the first packet. (while the switches find the way... :/ )
+ if path_finder:
+ fakeStats = MyStats()
+ do_one(fakeStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+ time.sleep(0.5)
+
+ for i in range(count):
+ delay = do_one(myStats, destIP, hostname, timeout,
+ mySeqNumber, packet_size, quiet=True)
+
+ if delay == None:
+ delay = 0
+
+ mySeqNumber += 1
+
+ # Pause for the remainder of the MAX_SLEEP period (if applicable)
+ if (MAX_SLEEP > delay):
+ time.sleep((MAX_SLEEP - delay)/1000)
+
+ if myStats.pktsSent > 0:
+ myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd)/myStats.pktsSent
+ if myStats.pktsRcvd > 0:
+ myStats.avrgTime = myStats.totTime / myStats.pktsRcvd
+
+ # return tuple(max_rtt, min_rtt, avrg_rtt, percent_lost)
+ return myStats.maxTime, myStats.minTime, myStats.avrgTime, myStats.fracLoss
+
+#=============================================================================#
+def main():
+
+ parser = argparse.ArgumentParser(description=__description__)
+ parser.add_argument('-q', '--quiet', action='store_true',
+ help='quiet output')
+ parser.add_argument('-c', '--count', type=int, default=NUM_PACKETS,
+ help=('number of packets to be sent '
+ '(default: %(default)s)'))
+ parser.add_argument('-W', '--timeout', type=float, default=WAIT_TIMEOUT,
+ help=('time to wait for a response in seoncds '
+ '(default: %(default)s)'))
+ parser.add_argument('-s', '--packet-size', type=int, default=PACKET_SIZE,
+ help=('number of data bytes to be sent '
+ '(default: %(default)s)'))
+ parser. add_argument('destination')
+ # args = parser.parse_args()
+
+ ping = verbose_ping
+ # if args.quiet:
+ # ping = quiet_ping
+ ping('Google.com', timeout=1000)
+ # ping(args.destination, timeout=args.timeout*1000, count=args.count,
+ # packet_size=args.packet_size)
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms/Demo_All_Widgets.py b/DemoPrograms/Demo_All_Widgets.py
index 51d70237..6ffdf590 100644
--- a/DemoPrograms/Demo_All_Widgets.py
+++ b/DemoPrograms/Demo_All_Widgets.py
@@ -1,11 +1,11 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+'''
+Example of (almost) all widgets, that you can use in PySimpleGUI.
+'''
-sg.ChangeLookAndFeel('GreenTan')
+import PySimpleGUI as sg
+
+sg.change_look_and_feel('GreenTan')
# ------ Menu Definition ------ #
menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']],
@@ -14,41 +14,49 @@ menu_def = [['&File', ['&Open', '&Save', 'E&xit', 'Properties']],
# ------ Column Definition ------ #
column1 = [[sg.Text('Column 1', background_color='lightblue', justification='center', size=(10, 1))],
- [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1')],
- [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'),
+ initial_value='Spin Box 1')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'),
+ initial_value='Spin Box 2')],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3')]]
layout = [
[sg.Menu(menu_def, tearoff=True)],
- [sg.Text('(Almost) All widgets in one Window!', size=(30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)],
+ [sg.Text('(Almost) All widgets in one Window!', size=(
+ 30, 1), justification='center', font=("Helvetica", 25), relief=sg.RELIEF_RIDGE)],
[sg.Text('Here is some text.... and a place to enter text')],
[sg.InputText('This is my text')],
[sg.Frame(layout=[
- [sg.Checkbox('Checkbox', size=(10,1)), sg.Checkbox('My second checkbox!', default=True)],
- [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10,1)), sg.Radio('My second Radio!', "RADIO1")]], title='Options',title_color='red', relief=sg.RELIEF_SUNKEN, tooltip='Use these to set flags')],
- [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3)),
- sg.Multiline(default_text='A second multi-line', size=(35, 3))],
- [sg.InputCombo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
+ [sg.CBox('Checkbox', size=(10, 1)),
+ sg.CBox('My second checkbox!', default=True)],
+ [sg.Radio('My first Radio! ', "RADIO1", default=True, size=(10, 1)),
+ sg.Radio('My second Radio!', "RADIO1")]], title='Options',
+ title_color='red',
+ relief=sg.RELIEF_SUNKEN,
+ tooltip='Use these to set flags')],
+ [sg.MLine(default_text='This is the default Text should you decide not to type anything', size=(35, 3)),
+ sg.MLine(default_text='A second multi-line', size=(35, 3))],
+ [sg.Combo(('Combobox 1', 'Combobox 2'), size=(20, 1)),
sg.Slider(range=(1, 100), orientation='h', size=(34, 20), default_value=85)],
- [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
+ [sg.OptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'))],
[sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3)),
- sg.Frame('Labelled Group',[[
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, tick_interval=25),
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
- sg.Column(column1, background_color='lightblue')]])],
+ sg.Frame('Labelled Group', [[
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, tick_interval=25),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75),
+ sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10),
+ sg.Col(column1, background_color='lightblue')]])
+ ],
[sg.Text('_' * 80)],
[sg.Text('Choose A Folder', size=(35, 1))],
- [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'),
+ [sg.Text('Your Folder', size=(15, 1), justification='right'),
sg.InputText('Default Folder'), sg.FolderBrowse()],
[sg.Submit(tooltip='Click to submit this form'), sg.Cancel()]]
-window = sg.Window('Everything bagel', default_element_size=(40, 1), grab_anywhere=False).Layout(layout)
-event, values = window.Read()
+window = sg.Window('Everything bagel', layout,
+ default_element_size=(40, 1), grab_anywhere=False)
-sg.Popup('Title',
+event, values = window.read()
+sg.popup('Title',
'The results of the window.',
'The button clicked was "{}"'.format(event),
'The values are', values)
-
-
diff --git a/DemoPrograms/Demo_Animated_GIFs.py b/DemoPrograms/Demo_Animated_GIFs.py
index e6f02868..6e082df4 100644
--- a/DemoPrograms/Demo_Animated_GIFs.py
+++ b/DemoPrograms/Demo_Animated_GIFs.py
@@ -4,11 +4,11 @@ import PySimpleGUI as sg
Demo_Animated_GIFs.py
Shows how to:
- * Use PopupAnimated
+ * Use popup_animated
* Use animated GIF in a custom window layout
* Store your GIFs in base64 format inside this source file (copy and paste into your source file)
- The first image that uses PopupAnimated will stop after a few seconds on its own.
+ The first image that uses popup_animated will stop after a few seconds on its own.
The remaining images are shown 1 at a time. To move on to the next image, click the current image.
If you want to exit before reaching the final image, right click the image and choose 'exit'
"""
@@ -16,50 +16,44 @@ import PySimpleGUI as sg
# ---------------------------- Base 64 GIFs ----------------------------
line_boxes = b'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'
-
-
line_bubbles = b'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'
-
ring_black_dots = b'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'
-
ring_gray_segments = b'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'
-
-
ring_lines = b'R0lGODlh2ADYAKIHAPj4+ODg4MnJyaysrIuLi2NjYzk5Of///yH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0NFQTZFMUU5QzBDMTFFMkFFNDdDRTVDRTJCRUM3RTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0NFQTZFMUY5QzBDMTFFMkFFNDdDRTVDRTJCRUM3RTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQ0VBNkUxQzlDMEMxMUUyQUU0N0NFNUNFMkJFQzdFMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQ0VBNkUxRDlDMEMxMUUyQUU0N0NFNUNFMkJFQzdFMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZBQa9IAEGAw4GwA0DBgDBFsPIDMfGBgHNFQDH1dHQCs/M1gwEBA6/xdsNBAYFDgHfu9gGAsnqDdIM5Q0AAdq8ygXuB7ydWwCP3wF97YIB+DVum7wF9hQIiKgAIcBdE6mFIxZt/92CdA0r7rs4KwABkgwZCCjwEMLKlgf3OdBHUlW6fw2eGdRgMd++nasWqtuZzhwHhD5HxhKKk6CAmhcASGWAMKEspkBJVIXaiinXD/oEWK0lFKZWpa6UDSCpT8XUpFk/KVNndkZYmaYC/BpKI6zYr6BWHjsJA+FfwKEADPhV122Av7AeIz47GdwuvELeevpVgMCAuH33CRgNajFndQRAt/A7WqxqTI8JcPZI4/HodpU1Bfhsg6bl38CDC89SoLi64sVxtF5OupPs4+oMhKzBfPnw69izaweyu/EL36d2yy4+XYaAAa3HykWOnHeNsOjRiwU1Hn3u1aLRg2o7RPN2Xf+73QcCeK4oVtxrJexmHysArFTcWoV195mAmAhQH4UiNDgAeghuYhoB3hV2nnukDAAiWyGGoJ5I8xUoW4cbKAahLQDIRtgJMj5Flo0YRrXhjEvxSJWOHRB40Ig9dlJjZxeZmCIF55mlIZCtLHbjAiYVACMEMu6UIyyKkSSOUQGlJsFuBo04E5G2mHTlAU4ysKFKIFK14ZZgymaWOAaJQ5VnDSAZjIVv1vimnwSZCFCXvRi6k4VkHoDoAhbupGCSqOiXTJ3hlKdmoE/qYuhFk1bkGaZtlqqAqnC+eV13DrB6HqrBzPnfrbjmquuuvPbq66/ABivssMQWa+yxyCar7LI/zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01mvvvfjmq+++bSQAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZBQW9IAEFAw4GBg4DBgDBFgHHxsgNxwHNFQDHzAzQDM/L1g0EBA4EBsXb0gvK4+HsuwAFBgIN69PpCvHuCgLcvMoF2hR4C3ig3wFsBqotQKhPFzxzDeIpVGDQW7hszfglZFDu/xw+YAvK6UM471YAAgQVdOxWoCQEAS05GgDZjYDLVv9Sepu4QSPPg8caroL366cyjxtENvD2DdZDgAwACEiZAcAAggibxiIKNQVTqq+egg2BsGstojdJaBx7agAxqlJVTHUAAEAAtp3+xbxhN4CAn6KG5QPsom+Au6rc5sNrou7hw4xDWf2VdoVUyLD8RiZRdzO4XYeJ1AX1qwCBAYRrOPabGtMAAqV/EWjdoq+Av39pY/ILW7aNw7lHnwqA2oZdz5+TK1/OfEnv2KZx+L1N/a+n57GFyphe3Xrz7+DDi/9BvDKM46h499b+ojrmUK+zF1fNnbruSrBPz91h+zYou/+iITeeK5qdh1hY8d1HAncCciJVfled516Du+WHEg1SUUdhJW6Jo+AKfg3gHSkC2ATXhxwcGBVrYXmIwnQbkgKAOBeeIJWIMUpGY47XCIDjLDOKg9V+HKC3kI9EuhJkjQuUaN4F5TVwY5KsvMakQC52YFWEUfmIomRcMvDaTcTRViYDSNKlYi0nXemkmEhFudAA8/USZFqvEXRaN3Qu9WMvJYZ5kJBijkTnkHU6lOUCJ6W1J5qJHnQooJW9ZlRDcnbz5Vl5hoOUpIJ+d9KnB7yWTKTNWVXZo3zy2EyfA8Yq66y01mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LI3zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01ttGAgAh+QQJCgAHACwAAAAA2ADYAAAD/3i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8GQUFvSABBQMOBgYOAwUAwRYBBsANx8YGAc0VAAXV0sgMzwbM1wwExQ0ExNwNAwYEDgTtvNkFAurs6eMG5QsC073KywwAQAunoJ8CgdsWINS3Sx7DA9qsLTB44Fm0BesABuOnUf/BOobnGKyDd/AYvVsBBkgcV4BkxQIrHwiAie+iQgInXf0jqGAYzQ7fYh5A6JKVPAJCP3oY2QDhz1fyOg4VwFMDAKretIGbBeCcVBMIt9Lq+qvqCKdmY5HNWYKjUFXKBpgF8JYE1qZMS/3DiYPfsaKiAnhFSmPYMQNsTc38JRdG18ME0pYC8C/xinXsJKMKcNcFZcviQgeo20MAaUzn3pnmkVJrN08CvP4id7rFzMOHa2O6OmCwDdyjNX/irHsFgOOhkytfzjxL6pbQAcsYTb26p3fRW0qPQdy09+LNw4sfT77w6hp0hXvi/e6dje4BkIeKjf2dSht0R38HP6n9gM7/ORynH3+S0EWEfOU1BCAL6cFC2TsEhnCVaeqFclV7CxrHmWnxpZKSfRWiQJcAFJ7SG204jHgeKbFlaGAKCCpEHVS9RYjBhCGq8mBjJ0yYIY3k5MiBikKSsiNPDXaQ5EEk/qhjbzzu858HxDXVZJGgxBblQSgqKcCWQ1X5Cmdm9cYWZbWR6Q2JDixZS0pbfhnTl95MqRCJNrryYFJbDsDQZw1siGUrX25JWZ9/fokknr10dR8DXybmZwNy4uVkLStKKelDGzow2nKHVjVpQP8NqsuhyTx0gKLloZmqp5eWx2aCtNZq66245qrrrrz26uuvwAYr7LDEFmvsscgmq+yyO8w26+yz0EYr7bTUVmvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstuvuu/DGK++89NZr7734tpEAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZBAW9IAEFAg4FBg4DBQDBFsPADQXQDMcBzRUA0szUyAzP29cLAwMOv+TU0woCBgTJ7bzZBdYMAgXvC8cNBAbF9AbpuuoRAHcgHkFpDOLNU5DNwDl45qLJ4+btX4MB/wju+sZA/9nDAwTuHVAmsmE/WwEGaARZ4GMAAicf1FsIkp0DAARosiKpUaGHYQZ0AhXJCqc9jcpiasD4seCxZbGMDkwoYCUGAFW9PbVa9NfUFA2h0pLKFUTYsq6M6hyxTmyrATBXAlhLImsDAExNCYSJo54Bm6Ze2htAtwXQf0pLCfCqEgbOvw7RisJqrzAKjOwkl8KqmQRey+GazSUiALSlkAQG2NWR8pjFT4tRhyRsYzFkyE07YYU724brf7RPBShdO2fo48iTK8/CW3ZqHLeje2ouO7eM37cBLt/Ovbv3HcNNr1iceLKAcb1rZA8O6nzq2atn2I5OdBPccQE6v2gNGRQA/f82BCDed1HFx8J/AIayG3stzCVggpzslpqBKzj4IISYpDQOhS5YmN8p5+GHg4UYUnIehaOBJZeDr+DFoGcP3uIihyHMVVqJk43TGFgCfijLjATNhSND/yUU3pAR6qhRSgNGEN5dPSKpyYk9jYOkjT0NJ2UmnDlwIlVodbnAk1BuCUpK8Z1HkwAxkcmQlqKp2cB5BLFJlVJY+cgLmkGqVmdMWMVnY5MtWtnAcEqpdihxVOmpi4BeMrqAnd5IukCKx+2mEaWXsmnmLHgldl6kn/5oqTqJxUigTOWt6uqrsMYq66y01mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LI0zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYrbwgJAAAh+QQJCgAHACwAAAAA2ADYAAAD/3i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8GQQEvSAABAMOBQUOAwQAwRYBBcANx8YFAc0VwwXMDNMMz9rXDQPF4gUC0sgMAtDJ5LvDBOfqBe4K3QsE5g3r6bwCv9sUAIAW8MC9AwOrMRhooJ6ubPLwKVxw8Fs5cMGeLWMwQP8fvmgKOtZLGLFWgAEFQ9LzRsBahADxGOQDuWCYS1fKNrJs6eHbTYHHaLICkDNlx5IaOgpliPEVPJ0CA6TMAEBAwYRNYREFqILpVKe/oJbA+lWrsp8kBBjIuuqfVQcA0JaQCndAQ1P/fsmVsc7AXVMnfw3Y2wKmX4+o8hIri2LYYZRD8xJGoZag1rcviE4O1ywuEQGbLSkbh1lH4MOgBOQUDLnGvwJ+H4uKq1qwDdh+iYXuVHW3CtW+OQsfTrw4k3HEko/DgTs26k7IoxNj7jx2P+PYs2vf3qO3DbeoaI9bXqM5veCUVI8nzbjFv+p/PY0XUDrHyeagALQ3vZ+7rLj//ZFg02XjoBeCXWsF+Elc49GVGQGxtXZKAOrVB8MzsSElykkDgIYDggYIFQpwU3mWgocNEBWiU6opqIFaBhh4SlUdukhVbDaWQqOFY8Um42yq1QegB/pdhVuO+QWZEoU/LhQAWgD4CMtJFtKIpJMO1pSbVlkuQN9PVbU35AJPyjXgLVXVx6Q3Ja1Z05NXRjbYPvXRh2UDZcapCoVC0leQnW8KCWcvaS6JopdIUZhSb3qi8qQDbiKa4qFvElconZN2iV2aDgDKZqO2eIdpipr6R2aTpqaq6qqsturqq7DGKuustNZq66245qrrrrz26uuvwAYr7LDEFmvsscgmq+yyLcw26+yz0EYr7bTUVmvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstltBAgAh+QQJCgAHACwAAAAA2ADYAAAD/3i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8GQQEvSAABAIOvw4CBADBFgHHDQXADQQFAcwVw8rT0gsA0cvXDALFDQPE0NwKAgUDDgPtvNnW4gTwC88L5uQL6+m6zgPAKcgm8EA0BsMKFExobxcAfdvmKcB3IMBBcd+YOdPG7//cvXTJGnorsK9WAAEFFUBcEGCAxAfOXg5g5+DhS1YhU8rzYLFag5ENWT18lzJZSQ3rjibk+OphvYUBUmYAgBJhgYyyhgZUsfQmLKdbT4xkSmuoVxEWya4SMKDqz7MjotacGTQU23dwYay7WjdUy3dhZTi7SlLV37ZSuc7km7gUVbx6DbBrjIoqZbEC8oZjBkCzjsygAGe+LNgcYVAnAb9zS2NwAcmS+27qfPedjauSn1aWW6Ol583Agwsf7qS26qMzcBuALdkT2+dt2+J4zVxyAeLYs2vfDqTzbxXPd49rixwGdQP1vldK/Xwc6RWDly+n+WncON46nL6+/gnAexz/AajHXVP4tTCMgKHQxloLAixHAIKcKDgaDMPIF5gpVNn33wnOzFceahrisI6DppxU4EAbcrDgQAMYINspGaaYQYMGyAjjfTZe4I1kEJJC24klADBfjwmG2I1/Hjy0EHU59odjAyZ6QAB6UO5HZCcm6jRhBwEsd5RFVBIoVZTdAMnATCXt50BMuFBVYIAvBXhmc90s96IttKUUYEFy8rNcOcs1uUqWCJ3E50tCGoDoa/60aSiUfbJ002tB0ShoKkDuCSmg/J3ZKGeaMhCpAl3WyB1ta3rl5amZekXNleGMOuCstNZq66245qrrrrz26uuvwAYr7LDEFmvsscgmq+yyOsw26+yz0EYr7bTUVmvttdhmq+223Hbr7bfghivuuOSWa+656Kar7rrstuvuu/DGK++89NZr771qJAAAIfkECQoABwAsAAAAANgA2AAAA/94utz+MMpJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vBkDA70gAAMCDgQEDgIEAMEWAQTADcfGBAHNFcPL0sgMAMfM1wwCxQ2/5AvT4tDJ0bvDA9YMz+0K6QsDBOcKAQXcvAK/wCnwNkDgAXsHvGlboFCfrncO8RlE+MyfAmULewUIqE7/3y919LwViGcLgACDCszJKyhhI8kD+OgNhAcLIMtuEj0A6PcyIYECMleZ5CiOmAdlMkVmdDX05sAAKDMAgNrt59JXEKOSUKqV6S+nJRRelTW05wh+Y1O5jAqgqwiqDUwCNbWRmFkZygrMNdX0JA2RPFXVJeb2xDC90AqLmvrrbgoBesGymqo4rADH4a61JRIA8yWA4+DqyIYYVADQxMZVVvFMr+u9oKZuBG3jJ2K/pyjbcJm5t+/fwLOMG04cBwEDr117Gkes+TjjyKMXMGAxuPXr2LPz0F0DoGdPsovXmI6YZqjTxC+vTtFaOuxOwzuvd0Eaeez5NURrf6ifxbDv/6VMpR4MAhhAHX6chDfgCyIZKBkpAi4owzMGFuCQKOj1NwNkDtIlnwObPcbWAO8JdRqCFxRYAIoBnqahCDshB2Aqsr04QowjzSKgfm2xyBBYOPrYSY0ofdjBANTFRd6Mo3TWn2xCHhCAgT3FWJ2JUTkpjgEXimMhA8cVAGI1uEAJZpIL2HcPmgMZ2GWZJzZAJQMGbmlAOchFiYqZC0xpgEF1MjQnQ9NdeYtJ/YUp550MTCdTgX/2EmKjBsgU6JpilhNUMwAMmiajfRqoZy2QOnDpp2/6NmVQp6aU434PTAfrrLTWauutuOaq66689urrr8AGK+ywxBZr7LHIJqvssjrMNuvss9BGK+201FZr7bXYZqvtttx26+234IYr7rjklmvuueimq+667Lbr7rvwxivvvPTWa++9KCQAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZAwO9IAADAQ6/DgIDAMEWw8ANx9AEy8wUztQLAwQNAATK1Q0BAsjE0M8LAQTjDQLruwDJ2AoBA+4K0QsCBMUM6du99ATIu8bAG4Nh0w5qs6cLXjkGv+ThO9DtnAJ934IRzPfw/57FdPYQ8rMFT97FdgcFjHxATx5GB8NWshInkFtED90SKlQHy2HNfh036GOYU+creO0GAjCZAUCAgdoyxvLJlETRqq6Q/jSBUCoth1hB+AtrStxTmGTFMoXHsxTNrTX8GTSlFS4MegTyynTbTmXaEgjz2j3lNCkMfd7+knKqeAS8veAiLyUSAHKmvpUbuxA3oEBeUJX7tjsbV5tnz3NBLX3LMEZez/U0Z2Jsg57lyLhz694dhTVmHAQKCB/u2ZNZlchvvwh+evg/3tCjS5/ug3aNZMo9rTabfQVzzypFheZOmoY/4gUscqqcWTYLccyfd5o8pDz1hvRdxOzp1D4LAf/CGZXKdpnB0M1wXpXSX4EzpDNca6Gw598MAAqnXiiMrXUhCYMNk95RBhgAYQgVuldWiAaY2EwBBhSgoigAoPjiisLNaJyMCtk4zEDDdacgjgUZIJ8GnQ0JwHA2akJAiCYFEOKIFjhpgExHGrBhKvowFaJ8AEJ5QJcFCQdTULQIEOJKS8onZjZCHsSil7XEaGUDZzIQYj8tQtNikqcMwCSeKdppwEF1LnDkh7zIyZCfQ94ZpHpmuthLaguwqJ6jbA55wETgyCkTpgrIySctUjoAqgIs+shMAIgKakwBqvK25n201mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LI7zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01mvvvfiykQAAIfkECQoABwAsAAAAANgA2AAAA/94utz+MMpJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vBkCAr0gAAIBDgMDDgEDAMEWw8ANAsjRy80Vw9UMA9ALAMfM1gwBxQ0BxNTl2w6/vc/gC+bcCsfU5PD0vfHvB+4M0gzesikIaG/Xs4IHpO1T143hPYG8+sE7t4DdPXkEcQEIsE//gbmCwzo2GFZQmryBFF197CiRQ0CWx06u2vir40cPJkceg9iKJkeAAERi2LjPGwGermgKEDoiINJXPpkK2yk1qbmqHpQ9RRVSZFAVP0fmLDWOGNYWyggQkBlKaVgZRo8iLOXzbYtharedBbXxKowAapfCCrq3qd1wiAcWtnF407hxX3dgU0uA70dikBkfo3yU7SbCmOfCGEBZL9fGf1MmXs26tesoj2OPw8G5tifZsXGQLkCAd+9pr4MLH048h4C1Nn6JDqWsgIHnBmyo5b1tuSYC0KETsI52d+/AoKAXGMAdrrTvoGYPQV28FuDFIbyVp4vdAPAWAqjD5wSgvgEC//t94E0BBAqGSn7PFTAfCgPy5tkn/j34AmAE3icKdkgNo4KBAB1noSoAPCdhVrwFSJZzBZh4TW8p1hKiAS2m0F+JswSAIkhbOcMhPyyq2MmLMS6AoQcDjFfOdz5ugp2C5Ty3YAQ2MgnQb7C854BzlVVUgIT5FYReOk+qIsBz+wy5AIHa8AZQgc0A4JyFzhXknD9ophnkLgPAuE8AejLw3JoGFEVlRCI2kGeWZ0bHwKBa3pkLPova1wCMhqpp6Idtxjmpot30ORyfBThAaQMEhpmPkZsaI2V7pCLK6quwxirrrLTWauutuOaq66689urrr8AGK+ywxBZr7LHIJqvssjTMNuvss9BGK+201FZr7bXYZqvtttx26+234IYr7rjklmvuueimq+667Lbr7rvwxiuvsgkAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZAgK9IAACAQ6/DgECAMEWw8ANx9ADy8wUztQLAgMNAAPK1Q0BxeHE0M8LyefZ47sAydgKyewK0dnlDAED273DAfDXDLwxGDZtoLZ5utzdswev3oFu6g7k+xYMILqFBxzmm9dt/wDCWe7gxcM4TCQ3ivQcKigJS5w/bu88dBTZMeIqhS/xodywkZu+na3cifsHwCSGkAb1GWWFc6mIjgVpCc1poqZTVwqvyvSmldRQk0VVUF1A0Caopjjy/Tw1dSwMqB5Vte1KguBPup+m4hURgABXWEX3hkAKrjA5Im43GVhcIO4Ou35BDVhM2UABsy+SDfBLwC9mTfkKUC5gY7Nfj4IxCSDwuUVMw7Bjy549pbJtHKY7697HSbRty7h1C+dNu7jx48hxrG7NQh6q0KNtDAfaabJt1jbyCe/MvNJoxzoI6gYlDnHq5KvynZeJkSmBxcRZrG68PlM3ygTqc+imm/ooAf++FfCRC6F1NiAo71nWXXOdXWbKe1EZpAJ1w0TmSgCiLchTAfndgqFl+k0AAAEchvjJhwWYKCKJHcqC4j8RbsASWSyqmMmLDUDowQCNcVMjLO+lGI5lB1qAoYA+9vhKXx+JRgA+Dkaw2jw8PulTka8IACIDJBJXAGkLVDnQl1jWAoBo8Yk2z5dQgplNicFMJiQ6Wy4g2phzrtQlP2o2ICYDwHGpJDpf2kiKPg7oyACbDABoZaPxVXOmASIFSmiexQVgqZ0GOEAmcmdGymhALaLXAIempqrqqqy26uqrsMYq66y01mrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LIszDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuwemwAAIfkECQoABwAsAAAAANgA2AAAA/94utz+MMpJq7046827/2AojmRpnmiqrmzrvnAsz3Rt33iu73zv/8CgcEgsGo/IpHLJbDqf0Kh0Sq1ar9isdsvter/gsHhMLpvP6LR6zW673/C4fE6v2+/4vH7P7/v/gIGCg4SFhoeIiYqLjI2Oj5CRkpOUlZaXmJmam5ydnp+goaKjpKWmp6ipqqusra6vsLGys7S1tre4ubq7vBkBAb0gAL8OAgIOAQIAwRbDwA3JyMrMFcPTDMYNAMbL1AzDyAHdC9EMyc/m6LrO4wrb6gfZ5gLw28e97N+/7eXu3N/G4OXKN6+dPH/3yP0LRtAfP3XnAF6zJYCAwF/qhrVzoHH/XsIFHV8RMFCg3j4P9jZuGyBQFQADMFUS6xAR4ICJrl7CzAhgY4ae7ezhfKXTgE8SK4fCKno0hNCmRAsY+Ij0JlRTIwn47HeiJ8cALLHCNDAAR7IBYU0JGFuSxkqrqgawvWpiGFp6dEMFGDkVBliWeUkJGBB4REhviOcREQeKbVod1tCW/TRA6lgDBKjGeCv58Sewlkna6IwX1V7NMA4nXs26tesooS8XwNG5tqfYbGkPILC79+TXwIMLH55jMOoXNU2B5Su6hm/AogjELmDRxlneBHgfx2SZd2EX9nqDAjfEK/FdYL87pbeU7/YTezOr5wSgMkzCqStmVzpKQPeW/y3Ul51no8hF0nsuLJeZKXIptY0KjGlTEYKkAFBAARRyEN98plhIEofNDAiiKAFcWMCIIWaHoicengjQitsEtZtWsZR44UbS/bZBRToeAEB2NIp0IzQXAmjBXi5+I94re/l0IQHmLBjBXurMyBF+t/iXpAKV6ZgdA11+k52RtlhYQI9FMnDhN9Q14F+QvFS25QE2trMmSNIFtR8+aWLTppqzgYkhkXDqgpYD0lF15wL+9RhPhriYCc+i7lC3oi02OkCpAtKRidhpmgaKTaHnLfBlqaimquqqrLbq6quwxirrrLTWauutuOaq66689urrr8AGK+ywxBZr7LHIJqvssivMNuvss9BGK+201FZr7bXYZqvtttx26+234IYr7rjklmvuueimq+66xSYAACH5BAkKAAcALAAAAADYANgAAAP/eLrc/jDKSau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru987//AoHBILBqPyKRyyWw6n9CodEqtWq/YrHbL7Xq/4LB4TC6bz+i0es1uu9/wuHxOr9vv+Lx+z+/7/4CBgoOEhYaHiImKi4yNjo+QkZKTlJWWl5iZmpucnZ6foKGio6SlpqeoqaqrrK2ur7CxsrO0tba3uLm6u7wZAQG9IAEGAw6/DgABAMEWAgYGxsANAsrMFQDP0gvHDMnV1gwEBA4FxA3cC97RvdjmDAMGBefaCgECy93UwfAG+PXP/g6gO+AtoDd6utoVY/BMAAN73SA+pBZwlzMD9MqN20bv/xe9ZPdwCSCA8IDGhwYcRkgW0F5JlrAIxKt4sSQGkBUBCND3CkC5fg1kbuRgL6fLiqwC/DQ4AOlNAAZ3hpTlM57NESC/zWpXwGkInTu99iyn0oROraxkNkVW9gRUZC5N8UuJAyzPUgJ+FrjKwu5UVHMJiHVrbydaVABk0n1R+PCqAGtfwARH+VxbIG8//SQwgO8MkAIGXN404OezAgRGw7ArWrRnTAAGKI5nY6drx6Igq5aMu7Lv38CDNzH97DSO0AOSK99tqUC558+G1ghNvTVz4diza98ug7qNoqggE3gub/ry3ppKx4uH+vUKncqVu6fkHHVkHSCVgxqIeTB3Wf+Q+VeCTgKSkphz15kAmWgFfhJbffe1oBNnnTW4SV7OkUTDhMnNl159CbawoGhyFRChAjqp4FgyJLoSwHghEsWZhYhlSONNyt1ooI0qxDbjVhl+9NcGk6Eo24ms+IRaRaUtxMFIl/mIpCqlafiQczo+JA5CPsY4SoAOjOekQFZCABk9I42J4pC05CXYOwW0JQ4DaXazJTMHXjYePXNu06efU95SpUFLMoCanW+mI5uXY+01jYkNHPpOaueIkyWVo5Wmp3QH1DkNo7YcWJGk6Vi6nVKcHjBemGVil9hopC4Q2qXgyPbfrbjmquuuvPbq66/ABivssMQWa+yxyCar7LI/zDbr7LPQRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnopqvuuuy26+678MYr77z01mvvvfjmq+++KCQAADs='
-
-
red_dots_ring = b'R0lGODlhkAHIALMPAM0/QMQfIdRcXtpxc9+EhOahofbf3/vv7+KTlOy4ucIYGv/+/vHNze/Dw8MbHf///yH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjMzOEMxNzM2RTAzMTFFNjg4Mzk5NzdDMkE1QzlDRjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjMzOEMxNzQ2RTAzMTFFNjg4Mzk5NzdDMkE1QzlDRjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MzM4QzE3MTZFMDMxMUU2ODgzOTk3N0MyQTVDOUNGMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MzM4QzE3MjZFMDMxMUU2ODgzOTk3N0MyQTVDOUNGMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t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'
-
blue_dots = b'R0lGODlhgACAAMYAAAQ+dISivMTS3ERylKS6zOTq7GSKrCRahPT29JSuxLTG1HyatNTe5FyCpDxqlAxKfJSqxHSWtIyqvFSCpKzC1Ozy9CxijPz+/Mza5Ex6nKy+zOzu9HSStPz6/LzO3Nzm7BxSfAxGdIymvMzW5Ex2nOTu9GySrPT6/Jy2zLzK3ISevNzi7GSGpERulDRijARCdISmvMTW3ERynKS+zOTq9GyOrCxejPT2/JyyxLTK1HyetNTi7FyGpDxulBROfHSatIyqxKy+1BxShDRmjP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQJCQBEACwAAAAAgACAAAAH/oBEgoOEhYaHiImKRBc3FSUFHysrOysfBSUVNxeLnZ6foKGinRcdjhuRlKo7kRuaHZyjsrO0tYcdjx+Sq6qTupkdtsLDxIimFQWTO5WVDB8My70Fr8XV1rIXj8nRzc/eK86Vvwix1+bnho3bvM7h4M/glZMFm+j2540bkszv7u0fO7xBC7iB3L2DxTroYyew4b+Avj5sOIGwoq0bJVT98wdQIzxo4ehZHDkKV8duGx2e3OerQjCSMBc1KiHpYb+OKTeKK2Ew5rBSJzqcuDFUKCxPuNalRIlTpTNLLj0BFUr0htFyI019GJEjhQYNXUd8QPBS0YkNq5yqZPpPIsVF/scq0ChAtwCNCtQqlqqAgYKGGQQIoAhMYMYMBSNcYi10owDTxzdt+mP19lApBBX0sWR2KZPVxeZO9AUceHBpwoUpYKhs6MS2tTfZqlxRgHUhXDR18ZJn6QMwe7gEaEBN2LRg1Bo8RG3t+J3spbH7Ub6FOdlucM4n0c5r7UIJBagHGz9enLCCEqAbe+T4vGk4SbXTIaChix9kaJQu0ehZLBv40+UZN555FSzWWDfttddOPPER0sF8ymgU3Uaq0AMaLY2kACB54QVIXgr1DOJafgyQ6J43933ToCAXIKAPP9DZxAxB/NnSAQYdelgecYRhUBYRZ52IYHYcveeMbz8q/sQSexN+JIlbxFRAAYdUHieglcdRUIGDFewjUGRqMQXVj/OdBGZODcnD0zAdjKCjeDwOSNwIZbVIE5hDopkST+WYJBuCapUIzQfLYSglaTzqiCWcqfFnUnYJEnnSNHVitOSJgjYl5iUhztLBCsMtiuWOiR6nwQ4/NoapppJ2QxsCXF4KXUAl0mriU4T+OMoJMWyYKA7lATuYsKgJ8GOQtwY6G6GsIctZkU7OeuREtZygoYfAAktqqablwFqLC0m26p41usbWmU69s8qKstwwpZyKwikvahTASoidycbY1CU1EoHAaw+h2KpzDq1JywmhzisnvKgJS4FtjGhT07gN/tFVIHMKkqgxrSqJRMsNoXJLpcJUagAxEQ8isx6rR07TryDqwSiwnmF6PMsJ74raIcM7PpxIUrpcKk5v05wsSAdCo4tmnuzuem2cVYrK6GDe/ixUZrpxPG0FQV0Ic0ZLm5jnzPAZ/UkHAui8c6mJxqArdZnVRVcJrrxsSAd3ikuhvriW8HYoJzCAqKKnkQznqX+nI9QFJyDQ+FWenIBMQJFqrVautTQypa+jso3aDBrYbU6LqYj7Z6u+iQ5Km4gOC7Xhx81AZ1ZdMnQ6k2MOI2WVjHqO3JYkjUjz8AI1TQvrha8NOwoz+AhTkKum+E/uPyGQA9S+f56C6ujYOTG0/tJLxL0oF2yggGlXLi+YAgV4fRDpAMFmuiUFjD/KBQUo0Hr2gR3Wvk8s+lckxgatcbivFrjwgAbSJ7XCCKBQAEwZwJS1HbLYAwEYAM/giGMY0GHAXgAshORyYwlJBa1o78PFCPySPOZpADEFOiBMLoMASEAqaK7omkUetAOu5OAvXYnBCi4WQrhgZgOQkBteLDhDoziOLGS5gAyLeDSjNO4EQdEhFbfIxS56ESFTGUpRtPjF7lixKpAryQ0woAERwCACERCBCJpnlTJWIy5zqctduEMKvM1gASYIZA0GaYIamIADC5jBBo5ix1kARWK76cwrpngDBfyAA4IsZCY1/mmCH1DgBo30lFx0c52I/OZnJZAAJg3JSk22kpURkMAiQ7k6yVnHPthxxzz4OIgLrCAAgXSlMF+pSQ7oYAVTpGU2ciMzyXDGLv0qnw6ISc1hBlMHG0imHRtBgwgR0CEV6lQjRJDJaprTkCYQQadoWYiZbAZPA6NRLHBWTmuac5MUSBwtnaW0QLWEInhbwD0H6soa/IAG7DyEqlYms7Hxw0Igs6dE78mBGehzm7ULn54IpokSAJKgIA3kAoiYUG5uJkyo+0cmPFDPkN7TWAk9msoSFC210KYAKOCASyfKSQJc1IsnuBNkHkM2ftAPmDsFKRBAGdMONOdZ8mOZQALy/oOk8tSQCzDbF2PWz73Z9AMtvepVI6BVLx4IntAaXkDCatVXkjWmKHvqwGRU1F5UVaw7zSpcz0q8FD0GBm3l6VLheoJuOlRp/cxpYKtpAp/CFW/fiyrx6MdSvOKVAzCNqS0td7v4Mck3NPioZe/ZSZKyUx2yIhuTOMKTG8xAp6OVKAeC8NMvPmq1qgUTVMhSggjE1pwRQChcB6GqSBGPATwJRgcowNbY5nO4IloIx2hqpCctp0UBWOVvNQkE08I1H5E97k0KUo4LhHa76FwADbRZRvgNNZ66qN9iLrADHWAytsZEJnTTIbFmSjW+dlMIELQL0ggAgVr7vVt1uqqi/gowERHu+sEhScuBCHwywWbJRdB0G1+u9XEDBABkWBGpSEZi2DIPquEExSERrpm4E7jAERCyywEYAIEAiantiY92RLlhIoc6VtwZrXICKe4YFEBh3BPTeOQmO/nJYUQjGUMZ5TG+eIc38IAIeNAABzigATwQgQcebEc8ym2PddQL3gIwgCG4+c1udsAQZBCAAly5iI+ExDsDgolJHuQGCehBnIcg50LD2QE9SABTuSg5+uwCl6aE4DWcygNDw/nNliY0D2gQZOBgppvXyRRvppHm7mAgA3LGNKFXfWhMZwAD7B2dNkI9s2fuJ9ZSoQEJVH3pVafa124mwf9iwk2G/ugNHsywUH8QwANWAzvTmV41D9ZZEVMIVVKZKhIlMGG/T5wAArwON7Sd7QAgdLoYyDK2Z2vaDEvMchgF6MGvDZ3qXzt70HJuwQeep5Q0RYez9Fs0AmFQb17PW9X2jjMMzi2MR1mOb0XFXOY2IGhWJ9zil6a3m3uQTb1YyiP+5Vt+lH08Cti74AcHdq8L/dw+ZlHKd77FTPt5rpRsh+EiDMCgd87zk6t81TqoLS4oYIAh2OAFALCACwxQr9oG1ZuSXQl7kFQtFiC851fPOKZ5YLZSfIAHDwAA0gFAdrGT/QE8+EDMYdZv3IqXNmVdxA0GkHKst9rSvyaB2W7AgRCM/t3sf/97CCIAvFs8VVlprbXxACdovIv73gYntANOhjQXlP3vZA884F2g9kOYq0nfdCinanGDiu+87vgmN5x7IHAWCUAIZs987GdfdrH7IAbpkeux50ehxYPiBLtO+cEvrno5y8A2F/gA7GVfe80jPfA++ICB1vFw1SprOh+zuuohr3Gex5nr97qBDWb//MvX3vxlt4BpP3/c68PD92fTweN/3urUu1kFrOmACdDP//I7X+wckH8v4m/gg23eQHUHowA+t3I/l3As9yMrEAKxV37Md36Yd3n7JiIqs3tCIinU40gUd2/dV38NyHF9YgD8R36AZ37+9wIGUCf/YibH/hYpqYNrdxMABceAjld/DrBwvVQCL/B/tJeC5yd2HSdT4eUkGuUNkjYLHyADV0d8KKdqPZCBR4MDY4d5WbiCsud/gJcAWFFcc2V9rgJ/otABQBBtzxZu5GZuhHACPNB8QyiHRWiB4DcISqIxG4UmUBF3n5ANEzCFWmd/hTYBpnUDQzCHXriFixh7LvAtNRRZADddBMMn/bECUDh8GHdoFmB8O2AgPtCFc1iBdXh5IYB8MZgsz2Ei8mVqqAZ5GIdyr5YeFyiKFLiCjSh2EAM/E2Ncl1A/OIcUNFBp9ieC30cDlJeLdViLRfh8lOcIAwR673AJDhaMoHADQCBvj1dv/j0wWBAWihaIiywojlnoAyB0N8iwYUPSG9Toh7ZQCiUQADKQg5hGZ8joPifgAl64jKUYjknXdSmjGcnSYuRgg8dTATkQADwQiA4wATwQADngYXARh3K4j414i2LXAEInOaggN3TjYAYpDFNRFVm0diKUABjpj/3IhWAoFYtzRWQxZftVPkFoiyqojJn3At71ZOSDgivJjONIdiwQkjw5CCsQhMp4kXIYAjtQlLZwAhFQikAZlC9gAtbolCxSAQewj+Q4hEh3ADuJlaFwARgAjqTIj3/3AAxAlGLJIh7gA//HlbLnAwLAlm3JIh+QiFPJhUknfXcZJSZQkzgZhCZwJ45/+Y7JxwN+14xiFwJpZ2SHuWwbgAI8YAFbCQAHYAE8QACtaEeBAAAh+QQJCQBCACwAAAAAgACAAIYEPnSEorzE0txEcpTk6uykusxkiqwkWoT09vS0xtR8mrSUrsTU3uQ8apQMSnxcgqSUqsR0lrSMqrzs8vSswtQsYoz8/vzM2uRUgqTs7vSsvsx0krT8+vy8ztzc5uwcUnwMRnSMprzM1uRMepzk7vRskqz0+vy8ytyEnrzc4uxEbpRkhqQ0YowEQnSEprzE1txMdpzk6vSkvsxsjqwsXoz09vy0ytR8nrScssTU4uw8bpQUTnxchqR0mrSMqsSsvtQcUoQ0Zoz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/oBCgoOEhYaHiImKQhY1EzkCCRQFBRQJAjkTNRaLnZ6foKGinRYcEyIJBTKUrJQaFCITHJyjtba3uIcmFxSrrDiUwJQyMgkXNbnJysuIpgIardHSlB2yzNfYtRYkqcHercLCBT8JMbTZ6OmGFhPd0wXiv94JE+fq99iNJ97h7+8nm/AJZMbhgj9w8OCtiifjgomBEHNNmJQwWjx/wihMiMhxFAcR0i4CwyGDpMlpIjh0XElqoq+K3w6STCiDAgJ7LHGVMsHBRA2fPWd54pACGkyLJV8elJFDJamgPoHyxAmRQ40LGkK4iBAhRIiGNZwqMvFi2kil38SpLfBCbCKr/hMmxCBAl0CMuGEjliIhQ0GJvzMCl5hRYoMCGRmEIjKxb+2vlzPfRbbxEFEpRyQIeEiRgzNnDwRIaFJ8r0aCHhsAD1a9ukQPCsgWUwwJGe3RVrDfTiDhYbPnzp1TpOgt2m02DiQkpCbMfHVz5hEkJNZlVK3ttLW95TbUcwIB4cA9M0jBwEPwFARGo7OQIsBf5/Cfr95wIwXVGkYfy4spmZWMyoSwk9lv4nlQXmcMAAdaDDflk8EN8kUY33s3ZICTCZNcpJBM/FFCAYCCNBIDgQgaaB55Jo4HHAEBLdNICKpJKCNhJYTQoiA12ABOUvt1+A5lhFiVwWbhlWckigci/slZaA0qg2GME8rIGgVucSBAMGjFo2VIrHRgjwlDFmjkiTmkiGKZ4w03nTIkKCDlm87N0EMMQeZg0UFcRsOUWzV8V2SKgI45Zg6dsagMBxpEqaiUG8ggFju9YIQQf+JodA47ZCJ5oqDmBQqeB9boNEEPi5YqXwkK1DNIQTtqqOE0MrxwaQ0xEKlpiUmOlyugJDRpi5XLmSosYQK4NdGGk97ZoQwkBDiBrYNqOqiBn42JXqi2mFDAsNyWUIBbu+SpLJcNgYicb2gGuqm6u3pAgnGimAAjnPQ650NsIea4kLjJDmODqoOYMCKu65Zo8JkI50AAiKOY4Fe9ECvAMAcZ/szW423fUEACTn3+qSu71B4pKHoMi1JDBBBzG0HJFsSQgC+vLqtxySb4Wa2JByeZLqcKlxyKCShzW28E+DrbQZYXAyNDBxvrQgDBO4N8IM4oGnpLDQ8LbarEiVw1yXX+1eRQ108rGe2uIq873sK4yKs1vfcmUsoEL0TKX00vkGACVQHbfLa6OQPaMy4cbJsynN7Cy90jL9jQS002vJAJ34WAudnUKlLN6eabeZCBz6CYIACUh8e3QbGeWGCBCay3zrrqnpjwrNogB25weoqDwk7WpZ/aA8ACNaKZmJpLDSgBvv5aQLC9O7fBD7mnY8rlBd+K8K4KY4sLCRG8HeWc/iv1Wf3ftguH/DIcUEC691SuZPnNnMff+ecu1hAA8977ALxeNYTJs7Qg0xvltBED3mkNVTGIHj4sgAAS3Axq1zsRaJKnDAvk4Aap0Rp97JOTEO1mePLjVGj2R5AM+AB/b4qAD+jXQUFwAAHeOZjtQDMBBCgwGTWgAKk2IKXCRGA7LSSE7HjTOU4NpzcZmADorlGKDBTAL+szDGJIE8QQvbCBIASU52pIRXyYwiA+uN8GXOCDAsTihkF84QQykJm6kCCJNmRJKaBSg7DsbYBVHMROTIAAPvJkKnkMpCAHSUh17KQnP7FjFwtZQTpKZZFDqUEHQsCDBzSgAQ/gQQg6/hBHRqLPEXKpi13wgkY9IicAAwiCKlepygYEYQABSCAePamInXzQVgoaYVhmKYQaLEAHrQyCK4fJygboYAEIoGUtZFcrdBFIOMTRHncIwANisnKV1hQmDxKozE/AZUQkIg/CzKeedVxgBK7EpjDXWUxsjuACvFSmgGzmsU3lkkE4aRkM1HnNdabTn6qEAQHiyUgREeh/I1vRjRjIA3YCNJvZXCcPbtTNINWANwXClfWOpLBe0cIEEOCnSCHq0Ab4oJSEfN9BcWams6lJJRwggA7+Scx0/tOhwXSlCjxQUUPUjHhoI5PghsOiRrjApvykqTpv2koXoDSQ04Mf4ABo/jBQXVQF7QymVplaU1XqwEI9ZcRFoZUp+Wm0TOnRwFZLes2ILrV9PTWF32onLQCihwQ3YOdNlbpXgAbzBk8Nogkc+JupRitdVaVmUrOa037SlAdLJGRM61nPEFLLAwNQql9x+lCtBgEGkR1kxzZq2Kl2xgNI7adeObvWS4ZWkH2SqmWDOqa+knSpbF2lDoqmzJoFblq/pV0KUslUz7qVra4EbVhjC8G0WbZEDe2sZxubWlZCNqwciAHBDltagnlAAZzlq2qRSkwUvBaqGA3uYSNYnt544AeNna50WdsAuFZUdmVDaEvp6i4SYLW6x0XuOnVAQloKj0jFwx5VUdQr/vvlFreabatTw+rCZ7E3ZLTbLgEycBPMtna8EBamDnhKYRxpZrud0m+SQMMTIXDAB9Wdb3FbedISC+J9UZsttcrEQnZggLyODfElMVDgijbCf8ULLoKYdKkcZLad1pxxQJtiYz0i4MSkbW5vkJfPC8DgkiDu7CVHIAKCenKetNNvNPmGnGrOd7zbPG83GZgBP5XWSOnpJCJq4IOZtvWhxoxblRMxxN4M50+dc5c0D7GXAMx0xsaMJewGbZkrDmhkxEHATcxsChsEgAc/bgAGeBAAG+SF0rGDIRtF+UYuamN1VolKUMyM6kP2sY88gSSqd81rXh8yKoqkdU5+nchZ/rPEFBSYQQUqAIAW0CAIBrBJYFcCl1DW5S7lHEgpPMCDHQDg2+D+dgsA4AAeeEDXw4ZLZnD5GV2imxkIiAAIwj3ueocbACCIwEYEaQoigueZRyzOPTjgARY0W9z3pvfBg3DuKnYHnOFB0Divdep8CAAI41a4vTN+7x3IqoXzDOfZ7knBZFjAAzvIuL0TfvCNA2AHHhA2MwxKKKCm2TNFnfkEDH5wcLuc4/cedwWKvMCx2jyoEfdcyX9VAo6vPOE/R/gG5HwNirH7b1iHphKXkYJ5A73lYGe5yltAYo5cmXgoRnqhePsrA6i852IHO9DHbYBp52J6lU2yXcu0aFFs/qMFX486yxEu7haAVdsNxOVQzUqe03qUcDh4+9d9LveeO30B8ST2I+MZVS1jHVBVW1O2eAD1yg/e8uC+7iLUuOprk7ITOKarfhU0nHe1jQUuj7vgKd8CFiyxFKo2dNLdPUDf2hyCv00QydrmbY2H/fRPJ/cS+23oTJ0nmtEbrYKQDsAxUY9tV3v74KMe+Jb7TI3fEfnE01NxIc7VucYzkpEGdwsTON300Id74Vl25d5EPII5kDl2UXKjxTlpVzAmQh7gZws14G2Th3C7B4HgtgPJFCBnd1BKgibigXMUJQTGh1gZlTALVh4LuEw8F4GlV34AUAEgwkC88SdJ9zE6/sNjycNc3Rd/GVaCHkF641d5cwd348YD4BImK2WAmnJELORCGPVcpGUtnkN1uoADPnh6z6d/mEcIF3iAIQQcvRIk3vFAZ7J4Ysh3dhcgJAB4KYh/YVdvLQA8ndeEeueE2HJgGYhhnnczHCZzhcABBqCGaUh54LYC5/BCL3hhWrgpRHUjeJdmxuNdfTcKKYCGvKeGTzduIJADzoIujXhh3qc9P4ViVSU/6FGBycABTad/uhd04lYCQ0hW6jWGSGguQyKGaRM4w7F1FTQBByCBuTeJCHcA+2N8sweAdbU2LZh4UlUm21ctN/N4LnIBzdeDKkhuDIATV7Z9jKhkZ0J//laWRSNjWVyWDx2Qcn5YiS8nAFTxU9+Ig5yzfHvYf2QFjs6YDx4QBH8IiCsYc4dwjQmTYBl2PdxoUd5RVkK1YnlWhqAwAU0XfZXYAiVAij5VNgcIigumg16oGe5FMNBEQ1BoC6rDbSAQfQcHAuY2afv4HUb4eVinN3LDeobGKVtkQ3rokRMgAytQAQcwbgdQASsgA0QXJEvIjuOTgE/YCd1RZ6wWF0sXPEGhaX3EQCY5Fl+ojEnmj2eDO6kDFaxjQ4BEaQzkjVLThAy2lL0GCm+oYDozVGiClWWZCxc1gtl4hOfTlnc3izkWlnLYkXS5DsjIhNhYHvO4lx7Zf8RoP1YTNJOCaYEYWZjqQhdkmZihABdYNow0pGeQWYoDKXyakmjpoZeXWUtHKXxaBEfv9pnaAEPrhiJEJRqP2UKBAAAh+QQJCQBDACwAAAAAgACAAIYEPnSEorzE0txEcpTk6uykusxkiqwkWoT09vS0xtR8mrSUrsTU3uRcgqQ8apQMSnyUqsR0lrSMqrxUgqTs8vSswtQsYoz8/vzM2uRMepzs7vSsvsx0krT8+vy8ztzc5uwcUnwMRnSMprzM1uRMdpzk7vRskqz0+vy8ytyEnrzc4uxkhqREbpQ0YowEQnSEprzE1txEcpzk6vSkvsxsjqwsXoz09vy0ytR8nrScssTU4uxchqQ8bpQUTnx0mrSMqsSsvtQcUoQ0Zoz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/oBDgoOEhYaHiImKQxc2FDAFLwEmJgEvBTAUNheLnZ6foKGinRcdJTMKHCY0q6usHAozGh2co7a3uLmHNgkKlKzArcI+FTa6x8jJiKYSrcHPziYcErPK1te2FyoBqtHQ0BE4KrXY5eaGFxo4397Rzjga5Ofz140iv+3sNMEmP5v0AJWdqIBPXzt3FToEXKirhAKDEJ3R8CGDocVRHTbk22iQwwyFF0MuukDBB8eT0EwooCBPZK5SJzqcsDFTJi1PHQSoishzFQcBIEfanFkzZsuFHWx4ELGjgQMHDXaI8IAgaKITBVBq5VfgRKcOFD5guIFiwwayIz5UZViqRIAB/kLiyo3rQEiMAARuIrIhoadfGv4SlaKAIcEMIAVyFFhcYMaMBCMo6J1nYwEPukLqap7rgMcCY3t9+dWq0uteDBUYq16sePGMChhMm+tAYMfmuXJvZ94hw+qgExFG94wgmxBYARtUt269usAGD5LLXcCQoW7uzNg5586A4aiN4FvDRwBN6IKMBMqbs17fOIGMo7rMk7iOG7t1+3FJEGh5QnR4ngoUxwgF6LG3GnPqJcCSMhcgsEN2+OmmG3Y7/DOIDfcIh1JggzSCgnqJgbheayhYeMwJENCnooQQOvCDVVj9pxUHXRk3wgwGIricgYzNEFsyBPBw32bW3QchZnWx/vCBcQIUJCM7QBFCQWoiqpcDjs0BQQEyHbxQJH1DXmckXS8ERZJ/GkLDgQ8LCtLBCOzpOCKWrOHI3Ai+jZLOZdmN2SduRMbFQzyDdJDVkxsBYeaUdKbX3JWJNepaBQjAB0oHFRj5ZZj41adZQoQ4lGY7EVTUoQqIHZgepI+yN4MOeYZyQgCY1Wqrpp1ihwOMBI0aDAeg/iZAnMphKWmI6kWJywkriHmrs4DmtkNxjXCDaDAvmDiEDR9aKemOj2J5g4Ci2DAAp89qd9t9JAjYgQzrXKtSb4UMVOVyxzLGqmIVkCvrZeuueCSYmTlA7gU64NCNX6qkMI4hNiSHrGKs/oKIb3Mz+AuKDXzWii6SLc7FA3nGafDDTltF8EMJsZ4gsbf68hhzjxp/csJ8nIbpZ8h1xVCzDRWYxEFHJkRQzFVU1lkxglam128uNjQb8sCB2krXtIJ1oEEBvqAMjCqxzGLptjcgW6WqIo6bSwc4CJyrdiDHlULNhRJWwA/ccPDCDwVEFmshORUL7sRomw0D3Z2ckACunuY65qd/FwKTTDPZINMFYxNygg75nm32aq9G7smeR1YNt+ODZm5NI0DMcLHMn/NYQZu4dBDAl40HDLcDZVr05reFB38lDKp/8kEMzu686XU8LHnRlPo2KifsBVRQQjId/DBhhCq2+GJI/idg0KrnBv6IDEkTLB9t3JpNQDtbNpQNO3PTL3bD+/GpgLzOf3JmQc86KJ450pE0R42PMdYTYCimU52B/WlT3FHgAGVQAToxjWk9sh7ibvEu28StdFeTwQYBQhIPHKt+i/FACUaICxv8QEgCKxIPOOQSiKGmMZ7zEQtfYooAxAB3ubmLCCUYkMHAoIIGfA0GVkhEXYDlBgHYQfocMIEdBOB+OxRJI3QAgxtU8DU3gIEOKJBFBtmEJjSxSRO1SLkTuPGNmKuhHOdIxzrasUNDQaMa73iOyRHFckaxBVgqQAMLWAAALqiBEAxAKdHxkYOOoIAMCEBJAsiAAppwZHk6/vCBHfQAAKAMJShdAIAH7OADk3nkLUrhiBIQ4AMq0EEsY/kBApQgk8VDQARCIEpS+lKUAAhBBLakytpRoAQfgOUsZSlLFaggmbcUHSdbgMhRArOX1RQCKospCplI0pnMnCUDVMCADzRTBQTI5CEuIIAgkBKbv3wnMHtAPG5+giSuXKY4P1BOWTKAmbWUQaUk94EevPOX16xmPAHQgw+s0Y6NkEEsw1nOipqTnPzk5ywJYCKSULOaoVyoPIFJSgvgz57lsQEy92nRfmLUpQDVwECH0AETyBOh1xSpNTlQxjpqDZYUzag/M+rSijrzA2Sk6Qd4CVJrLjShB3WB81Bq/ggEEIClF22pUF9qTllytBEGaOpISarQplbTAJrkI1guOlStDrWtGaWlJgjggoM6tawJteYoXUAoqgqiQSsFqEXh6lZyyvIDJUDAAkY61pCWFaE3XcBDRbJWrBI2q0SNazkp2QC8Pjavjm0q1vw6hBNoQJk6yOxguUrY1D6TABZgbE4/e1OntqCnLrFqUFerVd7CkgH/LKhePwva2obyAbilLAEuC9e3spaWGTUuWXUqVoUmNyS6pWVRt7rd7vbzA5D1bF7t2ksXXPciCJDoZXurWq5a9JPjpW1ocdoDBJC2tMt1L3dVy9bMNvOQ1I3vcEFqgfP6Lr+phelzu7vf/g90Fqryra5dd5DWOpqWv+zNcGaTuVjiCnis8pTsfU9Agas617cnxuwzXVnX6Xr4rk49KTc7YNXf+le/Gk5tYm0QVpwW16w3XcFkXbLWBPM3tSl27gdk2gEdMFW+Pg4vIqd63yFYdZ+E1a6WE+xMAsSEpjYFLTyxWU0TVJiPJE5mc3HM32dqwDQkOQCUgWxWABxAxlRt0GkxumYNlzOx5JgOfH8MYkQ+gAFDnmODXtnnI2MUnTPtkAcMGmEYg7IHAkg0HfH5SjYbeZybvSV8LvABIcRXti6wgEOrnAgalxjJfH5uatMZ6UNQwKZPhbELTGBfVi+CxK9MpkaNfNQl/id1JKTeQQikC8oQnDKOvlZEKRCAgHzq19hVkSBJZrACCxyAlAewwApmgOdoLwMBGjhmJW2Z7lqDYnLURsAJGgRtc7+bcvKW9x7tze9++ztrMfnjvlHqRz0GMiRJweQkK3lJddoz4ZJcd8MtxxZvnhaoy6zlLS2n6Xp405UYB6gtcQkQsMhA2Po8JzSjc0cSnxy1+ix2NOeRcBPrE9QTdSY6HS7HmoPz5rHuMs9Xp262Ylmzh0Wsuy/CaaP3WeQC7bjkbGDz3RL1uejUlkUimvIFX52ZX2WQDfZs9bdy97CADkkjAotVUO93nDqw5dI5SPa2Yzizz2S5RS78c7de/p23bj7zIlRq9yTHOrgcxe7JmSvrG9eSZMY0up/vLlfBH4MkGGew5jGbWr2vUqU2ZrDhK3rYsBfRBicvvOqNOmutd7PEq7/7dzGazrQWvCipbHVYLMtmo2p155avlwYynuPDazS4K9YkxBdOyYlrku+C9TSf95v8XJwgv77dPEx/69WasbLoKdc4yRFxfcYrWPv8jPt5qa7dGxe2t65lQGJ1j0yYh1Pmni8E+z8te0e7FJ3nVX7G935vx3211GuAA2w/R1FBt3MUZwgCiGK9h2HqBzU2J4HF11Jehg7qdnPuB3VLR3XhtF79tV1wlXjLgn2YxV4kaFEbaBz7h2UD/jhssWR6v3GB7td/g2VOL3gL+zds+9VobtWDjIBuIcdSXgd3cicPIjiBjJdlRIgRK9VbW+Z/F+VmxfFTHriDvmdOGhV4mkN2QZiEjodYwac5r8Z50qdhSGUV6SV5OuhfXkUya2WF/GeC5EROtfcSoDeBXYiHfxZpRRZ739VS4NSGHVJtmdd/Lah0mlZZeGhkkmh8cad3a7eIq/VpMxiI5ACJBbh5b0UA1XAMNZZi6GdRkGYcu2d+W8Vl/Ad8hBCDQtiKXJWKXCKGVJh9QrVkWbhS+iVYjIhRZhiGyZSBlMeLyXCJfuh/S6g5JraLk2eIMEWEehZ6K/iJGcVEDFKK/rEGjCkGaS2RXQSIgV5oVFG4aF44i9t1gFJXHlbFaL94bYh1Ute3hXEoelFIUx24hplFSeW2No6Ag394hbQGH02oi0FlihqVjzSFALA3iYVVSxQwdwJxTCinX88kkZFTj6wYjWzFkG5ikcq0jhmZTgY2OjRWba9UiMKWbgd3COJYjnFIgiBJU652cfyFbfVWcuimAa60bi75fKfFf6OXg+U0jItgcT9JSSXQbu0oCvCWbwOnCJW1iWoYibU0ikIRE/kWcLnHaoBVjmQ4eSqQdv+2NrvnkeiXf2dpCzXWfkVpggfYlseghYB4jXAFhnTJh/XndmqZUTL1lP+GjsA4QI4XxY57eT7gR45GNnKCSZeupoLS2I8TeYaJqQgkVn+YmEwZ6ZKXiQ3TppI2ZmTY9pWfeT4OSUnFCEviR5F0FAgAIfkECQkAQwAsAAAAAIAAgACGBD50hKK8xNLcRHKU5OrspLrMZIqsJFqE9Pb0tMbUfJq0lK7E1N7kXIKkPGqUDEp8lKrEdJa0jKq8VIKk7PL0rMLULGKM/P78zNrkTHqc7O70rL7MdJK0/Pr8vM7c3ObsHFJ8DEZ0jKa8zNbkTHac5O70bJKs9Pr8vMrchJ683OLsZIakRG6UNGKMBEJ0hKa8xNbcRHKc5Or0pL7MbI6sLF6M9Pb8tMrUfJ60nLLE1OLsXIakPG6UFE58dJq0jKrErL7UHFKENGaM////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/6AQ4KDhIWGh4iJikMXNhQ3AQ0TDkINDQE3FDYXi52en6Chop0XHTIBA5RCqkKrQjEBBB2co7W2t7iHNgs8qqy/qwM/NrnFxseIHQQ7rK2uz86rOzIdyNbXtRcYGb7QzpTNDhkYtNjm54YXMiTR367h0CQy5ej11xcIO+/ez+DulDs22RuI7ASEdq38NUvozcGPagQj4iLQa183i9H8CRnwQaLHUR1ewOOXEZqvFxA/qlR0QQMPhgsVNnQnhIUGeitxlTrR4YQNnz1neepQISbJfwxhVkjJMqhPoDxxRuxAoQINCxYAuKghxEAFBEwRnUgBs6TMkiYt4Ah7yCeGDf4iXkSIIELEDAw22KIr9WFHDwCAAwN2AeDBjg9CxTIzi7BsUl8OdpxIVKrEBgUmMtPYbIKGCQ4KZmhIXA9BhBCCCasWDCBEBAqJOgxwjHHkKgvNYkxGZCOBj8ydgwv3bMJHBWLoOnxooXUw69TNhSBuq/Go44v+eOwu1EGDBM3BPYsP3zmChNHmLggIQhj66vase8CQauPlY4xoLyZ0sH3QBRU4REDeeAQKxwEOKkiVywUf9NDeas819x4APXyA0wns6NdOdUapolshLeEw4IgFiofDTchcQAFzzQU2IXysEWYBBfTYsMKG+CVFmysGICdIIyKQKGSBJgyjYC0dmP4AH4TPvegcB/11gMM+jWlUHZVCrEXICRUM6eV4Jix1jAqowSjhmRE+6EJHg5yQAGNXwiOnmIOUoECJXw5Igw8yGNOBAQ+2mOaZMBJmQEotVdQYlh0mxAOKgnQwQ554FsjBDHqFckEJLpjpZITODeYCpEN0EEBtCHUTE24vbKciZpVSKpwCNOLSQQ6BmukioS0uuQA9H8RQJZaMucJDn4N0IICssYrnmQCZfnLCDk3yCmqvgUlGSAc/nJUqsb48tGUBzOZpQgHRenJCCxMO+umuLrTQn4qTIHUlbQ5MUOsgNvzQbLmeDYPLCX+5h+a1TBbWHyMqCLuQvTjGoANONv7ACvCQCiwsig2BguqkpxJqrE0GqOLoyzhSnRDBvyyTF4HGoZywpLUICyoqzKas8LC3q0QmA84cXNzsy7jY8Jeuzr2bdGA9IJBIv73c+44DPPwAm1gWt8xyxgOzqHS1IANgAcw/dlBCADGo+g0sPx85xAlBam2uwLd0QK3HvBYqKGE7pCvICY8EsMMk+e6ACQVkb0uu3P+e6/ciJ+SQ97UH2/yrJ6X0ZMNPeUWl7rJCk8gBtDpxquvHBkvowr4DUZV16MT5wLotf9LsLnSBreC2OZLCTh4HQDzuiQqdGtzu0s6FoINKGgjIOIkRIFtMkgnfjrsLJgh/jZu+e8YBnf7FqHjA0scj39wBs0ekYtyhmxCAQMdoUzDeYRfGwO71qPM64wrMc48HDrLdwQjTAwHgzx4X0AEOgsasoKVgYun5gBDAFqPAWMBCOdmWBn4QNK1F4AfoQQcFlJQwJnXKBE7LoCG4FAEGVipoxvHRXhi0gxBUjzAhOMwFDuiRUmhgBgpwYXiChoMCjIaH8aPADFZggQMQ5gAWWMEM0qfCQ1AFAwX4QQA4wIEX/KAAI6AiQXbSAQSY8QT42GEVMecUn3ROjWuMoxznSMcxOoVzQUFiHUVBxqd0jjSh6IAjKCADAhiSADKggCa0t8dPCPIDI7jBDYCwgRugYAQfAAsodv5CAQ184AMq0EEoQ/kBApRgkXpsZDquWIEZzKAAsIxlAWaQgDACMhmE/CQoRclLFfjyk6dkpCoLcQIMtFKWBcgBMmdZAQwkrlSOIMAoe8kAFVRTB6L0JQEWOUxQUMUDG0CmMmM5zlhuwAMU0IuKSkCAD/RSlAxw5wfiaU1RAhMBqdzjphIgzmWSM5kFAEICSoCTRkjzndacp0KrGc94ZpMA8OsmIlrCT1iW858W9WcCWNcIT04zoQ5NqA4UCk97lgCfEj1EI1CQ0X76E6MoQGmpPErNhdq0oTZVwQfSmdIVYnGZF20pRmF5l8n05KCjxCk83SnSpjJgpBDtaSGq4v5SWZYzBzMIKiyBQCNHMHWpOA3rTRVKSp5KlREjGKpVX7pWWI7AESXYJSlDSlem1vWpOoVoPjOoomMCtZ9YDWwyX0nUCmiAnUkdq1jFOtKEblOYKuyACgh7VXJmlbCErepdpDlNmy71s05NqE5LANkMngAGbAXqK7Hqz3F6wK6edSpswTpPbKqAAM+kow1YCtCMBna1mRXqMhPw0YaCVLFjBWU1cStVBFQgtX91KWtnmdjFIlep9Zwnc3vagXBWlrWVrSpGaatUkprXoWPVwXZTaoNwLjO40U3tXOW52OMaF7vLze0cbfDc3gpWuFodZzlnoNzYhnSk2EXwPOdaAv79yrEDFYVub9sqzgroEqxzPS55YatTDTg4jsqyalYtG99+zsAD7PzsdRU7352WNicn0AFwXxpeChMVAxRoZ3XPO1vZxvOke80JPoAQXAFD96LKnEEFSuBVDTu5rrTVqVml2oERrJatSE4tDHhig3bW9K4rZsBJzzoIqpIYwCVeMie6U+Cw2he5Un5xFYtZYhqL2Jk/QkBcY6tg0NaVAjIlMz5ucGa1anWWmSgHPry8Y/zCtpRi7GlL+qtW4bZUzSBCgCHT++Z43jPIc1SHX228VlcuGWfRhC1DSSrabWqSzFakgAdGjGVZnpigigBcOz+p07rqFNIfprIxiyzUV/4WlRSC1DOjcfpJDSDulrA2RAeoAoNR21rJGGiwHsvYSXYesgTOfnW0P9EIHcDgBq1U8g1goAPEZSMoCDhBvPM4bj725AT4zjcaQV3vfvtb0neECrT1GfA/8ttWgyzkIRPJzWEKUpEKNyTD8yIRTnpSrrws5SnzcnBscJKdGCelKVHZulzyGqG//EAw6Qg4GfD6o9n89cqTE02YW/PmttVmwyNbc5uvOubbpLjHKQByhKKXnnj9tArXyegv2zXjiAx0/Lrs85s6+bYR/UgjZFB1MF8zlHpNkQ1oumMV09WkUlefDeJa3IU+ecHqBfIx2Nx261o3zio5gUcbTd8F4/63w3I2xNr57uceP7WUMowIAly+YdpmHLuI9xMFvtrpMO/SxT2cfGIb/2YVT9kWjWC7o6HsY3uGfYw2cDnhV29cqGYdJDlm/YobG8/HspEnfqR3J6gi19GzWKy3/fwo9N5Zuxve741V+eMeTshDIlKRQs812RFcebNzWPkDI4Dj385YUtbztokrBVx1/E6Nk1ws2u98Uwu/WPUGWxcH5XT326/cMeNyz75EucyFTwiqK9j41gVa4Jd97GdgrIZT2UUAKcQdupZ/xdVUOhd9W5J+jrZhAOh+RYNUFVh5Xrde/kF0SMV5eAVVMpB2Q9BlRsdpfeZjkXcLJ0CBqiZ/K/7mgdAUgrJFXtN0em2igQYIgOaFgbfgf3B3gDFoge7kgfgwfdXFfWAnd/wCgwGofjhIg6JgNm3mdtT3f0SYTR/gYRoUcozldkf3SyH0NzS1hdwXW8AUeG+TYyNVgL4nVpg3CItHeT7IWKIUVckyeTKohaTHULZ3C6F3hXb3dkvlhKUyeU4HZn6Yf3PICHrWe7PHgSpnglWoeaTXZ2joUIHICGsniQkmhjH4Y4HGe2nodbNFAGWIC5p2fKgIfAq4LZongqyWhTnViSdog3Foh2QVi35yhvW1iyPlhcnCdlJoeUuFfW1yccZnhPTUhe/HEp9IiVHIAKYkdS84X34YZv7rh4QI4FHCWHnaliKtCFqPd3y3lXaaRosbeHQLRoOLJk/bN3+fpIAdlw6atmw9qFDAREXZuIjIqGFUSBWIVX1vZkiRlgsPx4P3xWrBZ4komIY1BYcDKG0IEHsr2H2QZokFQXS65EvAV4/8t4Oyd4dfRYV7iH+yBUoiGY3vZkYg547N9mxus44iZXmvWEqJM20XeXHJFW5wRBBlpAGH5Xyq+GydoHd994ejGIPKqAg90W3OB26Ado/vxhNnBBaes3uax4JMaY6ltIqUAW/yhnsDJ1X4EFfUt4vGpwKI+G91g4k42ZTxNJJwOQqtqI03aIiltIB3aSvgeHzzSFZdyFiGf/kjn+h3c4lTGsCRh6kp5Thf9BhSfWmVj8l0V1h4CjZylvmYpXKRUCiKOOVqhumZK+SRJ3dcLNmF7maa91BGylZgCsaaYNGZrkkI+JBju/aMI+eYVRQIACH5BAkJAEMALAAAAACAAIAAhgQ+dISivMTS3ERylKS6zOTq7GSKrCRahPT29JSuxLTG1NTe5FyCpHyatDxqlAxKfJSqxIyqvFSCpKzC1Ozy9HSWtCxijPz+/Mza5Ex6nKy+zOzu9Pz6/LzO3Nzm7BxSfAxGdIymvMzW5Ex2nOTu9HSStPT6/Jy2zLzK3Nzi7GSGpISevERulDRijARCdISmvMTW3ERynKS+zOTq9GySrCxejPT2/JyyxLTK1NTi7FyGpHyetDxulBROfIyqxHSatKy+1BxShDRmjP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf+gEOCg4SFhoeIiYpDFzYbMgYWFgAuNRYGMhs2F4udnp+goaKdFxweOg8AqqusDzoeHJyjs7S1toc2FSCsLpS+vb0gFRS3xcbHiKYtqsCszqsuLbDI1NWzFwJBvby/3dsuPTCy1uTlhhcePd++z87fPR7j5vPVFxTL7Mze3c4WFPL0At7iQGMbv3bcmpUwIbDhrRS7DOprRrGdCxceHGocxcHAOokI9/kywGGjSVIkLrrb1+zZNxcbAJ6sVcoEBxM2cN6M5YnDjY8hoR38lkDmoZo3c9rYaTQgBxsUKMwoQLXAjKhLO5nQ4ZJlPqH6AOhguIgDgg4hdDBw4ICBjhD+HRCUbFiTwgYPHlLk0KvXQwESFJY2HWJDyMGgFfP1EoIgUakCAQYIcSCksmXKMQIU4EmPg1S8efeKTkEaL+C5h0z0SOi169cHZA8hSCDZ8uTbth3wSGBj3lMKBfiOXpCCeI69pAsERk3IxrqgItlRjE2IQwEVl3FXpkx5uxADm8nZI1HAw+i9C8x7SF98r2kEMp0LrdiSW1hmLqgLuoAhQ/fu3uVmmxAZYDAYLY0Ed15x6zVIXHrpIVfAJoWo9hVCiRnUUg+NEXJBASMEyJ13AI5I2QgzHChKI3cJx2CEDObQIHrukQBfc/jQd1hYIKliAXUXILDWgP9lV+RlOlD+aAwHLQ7n4JMQPpmCBxQwtxV0E11Inw5WQmBkgNqZiJsDPjBniw0k8BUljGyaF2N7fvU2CAcJ6FjfSj2qUhQhHrAAZonZDRjgABkV45mb6EGp6JqhUYnaBSnlmeWd9Lnwz5wvADimgCSGWdkLZo7SCAl5RZlom4me6t6E41xgQGsWXbiKCuMwycNtR44IZqC3xRCTLZ4p+OKMwzIaYal+VUlICirhuWOlhQpiwgSaascprrxWNkGooZiwgXCLEtvmgzJOSYKVFWSJoazM0MAcBztg26mm9H7pwA7cgmJCAae6aWqxxJa7wF4FAEnBAazd6dUBlw5iAnZfCgjorpb+GSAnLTYoqGi/4zoosI2F8LeapAq3sgBACAyga6dE/invCPqJsm+qAHfcL5wFdOhhB+pEN6kvPQggkwm3sjxvy4JyF3MoGasprr/hclzwUR4Y5ixCFsQjGw/Vrizx0ds5sLS+/L5p7NNnpzc1IhQURKk3LtCgsyEmSOY1tl0nbVkMY3/StIzGPd0vxzOufRQ6OoBQMgAgvHLBYAhAHPHXYHM3FrCk1txX4IzSPOUGfQ9ijwwqWHBALwdYoIIMDSsCr7xgU7zrCqFrBZyMNJ9NeJSOeoIUAsCbEOTjnpigAOWUi0miA9vaMiqyaa83bO4e2KgiMhdsYPfX9SJtGQ/+rdNyqNmIzih4jMrlW44JARzZMqDwT/ZC7Z8gUB71HZ+dQs4meVAb3nrL1mR4EK2BNGlR0wOYjEBnEg744G7K89R/ynSM59UsXGz6y402Yg8JFKlaIiKSAzIQvlsE6X5vEs352JOz65XjAimIgQQ9BbsYaI0aJ0Rh5wBmmhJysD/W4hW9RmggcvxGWGtK4P4osMGZCIIDM1CBmNxnIh3MgH62MAEFSIUX0jBqSslS30ko4AOucYpePPDBxcxRCuCRx3zpwcsGKGATFzrkcTMIQAzY8r7MXNGOS0LABjZAnqoUYI42cWLxKICDAOjAgw6QAAMCgAM6nuR3CDCBXOr+qMhPPM4mSjFBHQHZyVKa8pSoxOFOcKITTqZSPKtUClNm8RupGPIqy3llNZ6CARmEIAQVqMAvZYCBrHjyJnbp4nn8AhjB6NJ5HCCBDBpAgxLQ4JrYrGYDMsEZ131GmS4qTfWU9UyMTYCa2aSBAa65Tmz+YAJrNMQRXYSe4ugFOUs0Zjk/Ec0IVCCd7VSnNbNZgghsgFvjQaGTAFcq91SviftUBAx38E92DlSg2DRACdpZgh2kACAJoqfm/rU/JUUUEZDaQUYHqtFqarSl6aTBDn61H0eExmmoEleNIHpS0dkgBDFtaUBX+lJshkBJTLrp7jCYl971tEITIOpFN0r+VZhe05otbd5NhDU48uVORhN6aiEKgM6LCnSoUrVqBUjQCAog6oLRY4+MyCnWCwCBnWe96EupGlN1XpUAUMmcClHl1RetyqQntUdFralXxmZzr3u1aANIQB6cMrVzyEqfWDkgAJbm9awY7WtoadCB4IDrX4RlE4PMJcZUmoAABE3nVPMa2dASAGqohRr+1nOc/WHRlD+16DohK1AVVDOojQ2AiyAE13CVyrdiRQA6sWnW4z62BCq4aHYxWgHLxnVjcFqA4U5qgn8WlbhCra5VrUuDpTL0vXB8Ug7GG1EOmLW6A93udldaXGxuTj3GGqm4oPtU6Qo3r8YNamitaoD+BkDvfAyFUYQ3R4LfljK4sUUuRvPbWBoEADSn2lwCd8geDzDwqRyALXqrylIOW3e/JbgBeVJ1WSj916k9NUFnZathGuzXxwAtQQeAc1PMpha3DyWlIu2BzvNSdb8/ZjBjf6AJt454hUieEl1RDFvrVpe6PfZxCWQgl4y9NbffXQDIxDoIElBzuMXFKnaF22EaVOCgQ0iqgL+r5dY+0wQaGKhjRYvN7aZXA2QJEhf/9d7CpoeJSjZlkAKA1/Qamq8xLUEAmnjCM4O3TcmK9Ck/RM06BznONGhAAUBqv/KQWFHvETUqOZCCUktZo/uFaQkacLJDHBG3V16iXNicCG/+RcDFYS4oTRGhxfJ08WlgVI6FCzyBCjT2x8ctgQbmhtKnIOCN/ZIjHbtJbMcwaZrs3XADCBATQJrFLoWkCgnmOOxyh8IzIiBACAJQAk2HgAAi2DIofqdJm7jS3qHApFzkQm6EO/zhEJdoLFvZ8GcihZVLObhGajmVquBSn898igdEgAMcAEEDOECBCDxQb4HU5S5K7ctfliPrF3oGAxOQgQwIwPOeE0AGCgh4xa3hmRmARqTiPI1rcb5zn5+A50/nuQwmgIFpcwQqXFWTPXubnFx20jMd0IDPCRB1so+dABoYsp+vsUWFOo2h7QGj9ZZMAgWMvexlN7vZgaCAFNX+42/LtTFJWTWT7Nkd6k7ved6j/nQF+JAmNg18Ttm005pDHgWIz7zm8+5zFPC0Fnp2Upob1GeTmAADd0c83lVv9qcTc+2eQJN3detosMZTIBSYgObPzvmz81wDxDCUW0Vf4yj1ReD0uEC+Fd96vbP+6dA3uwxEAHvHoOnBXT3ywKZEeJfnfue9Z73vzz6Bz98bOLNfqqkyi/yjTDzjQ5dnCpr+fN43P/pRl0EKqp+abw02auRDLk11Lp3AcbeEFX5mAjDAfGN3A2bngON3dgLAf3RTNmiGZIzWUATmfoFVHvTETDSXCDaAeYkXfgTggCj4gM2HA1ZXCH/zafJVMx/+w23VsUVdhHRy137SonvR13MOiH8RSH402C0WiIFok2UD4xdDiEymtSAP4iZdB3IOI3aJF4FAeIUEUH5ngkRYpjvFQl+M0HaSp1MyNwOfZwNi14MneH9BeHaIlkVFqH5GyCj09RRZp33SIxrdNwg2oHs+B4H2h4VAOAEt2Bwawzm0d4GmMl5B0iSSd2XGoUHyYAIooIZY2IZjx4KY82DMBTj/lVvIYWLUEXrp5yAxIj2fYyUCwIC+J4hsSAATmEW3k4gweDY4NgQIYHTEN3q9FVZzkgNUyIYmGIT6R4Ehc33NxVygyCBz90TDl37TY2PH9yi5t3vM54r4JwPbZnn+eWZlhAVfRzZfdGVBhPN/T0g4zZhnIkB/2YiJYzd9xigbOoRkcshCczM+5ZhAnhhheaFZg1CN4YeNzYd2wWcoBxRgiqgeJ/ZEmaNAnwiAcUSADrOO9eeOUPd6FYSMo4dakkgI+/Jf/JhmKcSICECCgSiMeicDnseNHmI/tPd/1LM/PGU/+fhqNMZcdbgBfqiGmPh0E/B4ztNqRkg4PTQ0d1h8RzhfMfMhCkB/KJmNGqAAq2ZEWOddSqQc5pcx2RcwjmaKYNiNYed8bSgDajcPWsRFeQEw0aaDQ/CRNYmUetg3HCACGqBz9XcCOqeNGBCPK2IW3+ZqAAYaiBR/uBjNh3B5QV/5RPYgAnbnlFKnARqAAf/AkqBgFoMUb1QxmFpxF/xIPW8ZkX5mFikAAyjQlECHAyKwAIlkEpikSbNUgFYWgFhGM2CFZ6SwE8BjFjdBPA6naAAGiTWWAukYcaDnjYd5NmxJnKHgkiDZlTnFP8ppkMjimcCZitFZDM8jPRv5JBtgfte5Ii5pjjH4L0pImd+ZUNjnVQ0yc+b5nXmGALezZ+vJRHzpnsVmg+DEO2CESPZpDW30l9PZICZGn+3Zny0JHM5WYjPnnaUUCAAh+QQJCQBDACwAAAAAgACAAIYEPnSEorzE0txEcpTk6uykusxkiqwkWoT09vS0xtR8mrSUrsTU3uRcgqQ8apQMSnyUqsR0lrSMqrxUgqTs8vSswtQsYoz8/vzM2uRMepzs7vSsvsx0krT8+vy8ztzc5uwcUnwMRnSMprzM1uRMdpzk7vRskqz0+vy8ytyEnrzc4uxkhqREbpQ0YowEQnSEprzE1txEcpzk6vSkvsxsjqwsXoz09vy0ytR8nrScssTU4uxchqQ8bpQUTnx0mrSMqsSsvtQcUoQ0Zoz///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/oBDgoOEhYaHiImKQxc2FCUEHyoqOiofBCUUNheLnZ6foKGinRcdjhqRlKo6kRqaHZyjsrO0tYcdjx+Sq6qTupkdtsLDxIimFASTOpWVDB8My70Er8XV1rIXj8nRzc/eKs6Vvwix1+bnho3bvM7h4M/glZMEm+j2540akszv7u0fOrxBC6iB3L2DxTroYyew4b+Avj5oOIGwoi0bJVT98wdQIzxo4ehZHDkKV8duGx2e3OeLQjCSMBc1KiHpYb+OKTeKK2Ew5rBSJzqcsDFUKCxPuNalRIlTpTNLLj0BFUrUhtFyI49RkEGgKwEZFKgtOqFhlVOVTP9JpLgIVwUa/hYsAHBRQ4iBCgheIgRKQR9LZpcyWcVayAYBpohv2vTHiu2hUh92PABAuTJlFwAe7Phw9B4uGbr+9rL0AViiE9vQ3kyrUgUBx4YQcAiBea7t2pZDRKBg79g6fgzAUZLnWmwh1N1YL13dr/GtDy0q45Z+27YQzuayQTrpMeBTaKR5Eh5iGHjT5MyXv04nIIjty9Uty+8BY7wtdbyUM3ddj5Bhjzk1lNhGPBVygQogUCdfdZg1OFcPH9g3Sz6iLQaQN83s1JMgyCl2nofLObPeIBcgIBduDk63oGUWUCBhSQuZF2CAUOk1BFn7YAjieTo684uNJ3CgoHwpMngbB7DV/oIRgD1+yNglNhBiUpMcocdRSBrYqIILKMa34pcufDCMSTKeRWNAUQlSIk07zvhRQ6+V04EBtaloZJEpumCAjdhgVBOVTjYFHpRyUsBSlTsChqVeF5TAJXUOfinpXBq8iBQyTF55FkQipinIf94BaqVA8yAgZQ51LojnndUtYGknZJnl5qjBQSNJaUAu5Oaut1IA2wk7WFbkpJK6sEOSonRooaY2cVrJiGoi4BdigaYknn9CvEckq6vW1gKyoYCqWogOcVogISWm5k+iqlyyIYc9QKotsSu6EAK4oCgrqIDpfWMrAaYaqM2f5P6DiYux2Qlft6zO5QK+n4Dar3KA/jIArZQIIBNqqBZO8+4gNqwKwMgkl2yyyXVC7Im+FDOr0sXHIaPLJByRJpGviXQQL8rcNmxbDwHPIm5wG78zIFowo9sBAghst5ouruT1IgLRRXry1SdPZ4HKnXTAZk6UwMNxk/Ks1XXGGkDiFQFRv3pjsCQzzPDIDu7AZ7IaV8uvmx94qghQJ5yAgOBCXeD2EB0sYDXWjNvpai0zEWzmxO9cu5ejqcrtZZ4I12KS0aBvTLbR09zdmwHzAqD66qyzrvAKh3eCQCS07ksjwDBtqfmwRoagAzEK5Qj2shiaTVIHJijcuuvLz2WC6X1+bea4mHy8FwUH9Gz1sAd0/tPs/h8qGqhr1ld0AQY7N8/88i48wEDsoKRLu4es/eJ9TBd40IPmRgLQgwDwC4VvMrUfjwXwHB34QLbUl7q5WCBC9jhBLmbmIdKUzieIoEDyRMYgEwQNHaVg2nbC4Q2o+aozGDTQBSJDm9bhJgSbMZxFlqaBtK2NbSdMISkoMIMVWOAAmDmABVYwg/tZZCpMI1xQDoi/pZUoiSWSoQ6nSMUqWhEmUxlKUZZ4RXwYRYtW4WJJHLGVtYHFOF0Eng088IIdNMABDmjADkTggbzETyh9mRk/AnKwwaQRcl7DwQDgKIRCGtIBQohBAAiAwpxtJTT5mUR4/PZHUdjgB4M0pBAQ/olITTqABwuIUs4c8RuNCCca80BjJbtGgB10spCv3KQsYWkXRj5mYHs0mr/EUZryrTIdGMhAJ2MZy1kaMgMYIAx+cjk8HfGHiVW8gAxIoElZvpKTtMQmCWRQDgoxpEo90hA0dZgNV9KymsSsJix30J/gfXN6/6jRLxFxAgikE5vWzCY+HfCDYAillBYaVYagNM9DEIAH5xymOvdpzAFEyBHc4dWZ+ga9SnbgBddMaD4P6UlZvsARbBKfQEHXDnHQY5xY1ABCM2pMjrL0kDwoASQyBc/iveOCBUVcBRR6z5bOEp+bBEIyZDU6RNXqViWoaBdPkIKF+nSjLcUmDmwX/qjaDcQ1XLMiAgyQz4wC1ZhfFcIOvtmms9QEqzklzwCc6lSFdrSTMaCpRMP5jKRZdKUbxWY6oZpNiomOhIAVkF3/iIBiPjWs2TykA8IW2I2AKD1ozWkHYqDXxBazshwtZAwkh6ixiY9ozyhBVquIgBV0tK3qzOsmJxAalDC2XxayxETS2gEcaDSsmD2kBRywWwVAIjk1dWyoKJrWE+z0nG9NLUM5WcRUsKZoRvWG5Qp6AQ2s1bLJvWw1YwpR2Fb1JvJMK+ICgNvDgtWaHzXF/I4m0XOJdwgfiEFis3tamGLHnc3kVXjfW8/bKvenP+1ntGjCL+gGigK+/GU2JvDV/twid5MOmID30hXRcTXlEkYU74Hky9f5bjQGOlDm7CJRsIaMA6VUPF8GNrnbDl8WmfbxjaD6VRw7vvcWJTAnb3nq0k3uQAYqk2AkZtYkC+Lsxoq4JA/KC9MfiPJvpmja/NRyMzEiGRGlKEEA5AtUTiqSm/BbWl/U1pUSRE2pVz6ODW4QgB0wOMINCMANMiwVowxucFdJcygAV5XAwQLFeg60oCuZxarkeZ6F3mIjZ0hGrnjljFb5pSkwMAMRiCACEbD0DDAQafPh0S/5CcwrAG2NLM9AASbggAlowOpVp1oBM8gSqbv2SD3yIiKmsaINKoBqE7iaBr4GtqtN4IMK/jzZHANk5imJM41Op9BrEojAr6cN7GqvOgISyFJ2cMmQUMUjPAneiwpwIG1rm5vavuYADlQwa3RJrEztKGB/sFgCHKD73PheNQ4qVQxvlgm4jLHEdI9oAxHc++DnNoGT2+1OeNd0vxYxbrARTnFgVwDNoFjSc0nqJD4euyIEUEC+R37wCJRgTIZy+FzDRsl7XAAIFY85sDkwA4zLxE/eHWlJLXFS81FA2jKPuQLoDIqk0LRlNu2UzRMdxkUbQwCqJrnU0S0Am5+mLJ+1sGttJXCMmwQDN0DBBjYQ9hF8wMaKOEEBgj51YRfA6vQ8TM7Z5azIPgYXGEjADIBQgBwU/uDvBZjBDBIwApe8yAYSaDvbaeBkJalrb5DXSU3ca4gTYKACgM/83/3+9xlUAAMQ64DI2S700Z5G7i7TFFPi4a5bUEAAG8g85zmv+QJswAMtJ08EFM97E0TA9IiQWEDLmpakNSoBgKd97Wff+QRws/K7J/3UI/DxZKGeqsQLEMxwgXzNM7/2mk+AEW2A6t63XQHAP8TQGKs3v16sRCjYfN/BP3/Z1x8F876Rwc1f8cbTwmucJTYCSC5lM1uD0AEYQH/2l3zgt2mw0QFrx38kZwJvVwtCFhBWVTEm5jcUgHn1V3/K13czoIBAwBuDcAICMHESSG0cUHWQg3MSRTnO/jBdHTACI6iA8seAfsd8MzACjEIB5Sd96EZsRFd0hsJxIlVUAIFTjNCBOfiBIrh5Nxh44IcXcrJ2QohvHAAEcKcI4FM7SPcUuHOAOhB79Ed7OTCF3pd5M6ADNlICo7eC1hYBMgA8MeIU3kVCBigIHQADC/iBaciAUPiBLniCFZCF02YCF0cMkVNWAdIM1YMVJxB/IZh8agh+gZh5NwAbjRAAUYeIP1CEo0Bh6CFSHEc+hIEAHiiImfiElriGFZAkHSADOCCHJqAAMtCFUjFiVFU/pZFhJ2CGlSiIrliMM4AsF6ADOKBqbKdqKRBiyEZKBGQTNWYfCMB3a5iGw1iM/vV3jLegAT/wiW0XAT+QVBE0QbuQEkZWUcZFjJjojpUYi4mwaz4gjuimahFgbAcRQlJWPD5SZU43CAhwA1DYioOIg3+3iYlQChpQAArAjOamarAmazMkLTa0Nq4QFF0jAN43gsOIhqz4dx7gNkMBA0DwA57IAS/wAwVQeLrIiHZ2Z4c2FmUohQq4jbXXhktnFESxRVJ0ZSWyik/4ke74dxUgioOGFDBwgwZZlMvXefWRlMLQgcqHhlMIkjl5clJpCwh4iU/5joAHelt5H2v2igd5lgUwZ+2WlNUllE1ZkJlXASWwllIpTRXglTjZeXKZfmMpExTgAUy5hq7oAaLVLpfWYHmYd5U5GXhiaZilhgswcJfLJ3ifJ1p06Zgk4gg64AEV4IGe5wE6YJmVFAgAIfkECQkARAAsAAAAAIAAgACGBD50hKK8xNLcRHKUpLrM5OrsZIqsJFqE9Pb0nLLEtMbUfJq01N7kXIKkPGqUDEp8jKq8dJa0VIKkrMLU7PL0LGKM/P78zNrkTHqcrL7M7O70dJK0/Pr8vM7c3ObsHFJ8lKrEDEZ0jKa8zNbkTHac5O70bJKs9Pr8vMrchJ683OLsZIakRG6UNGKMBEJ0hKa8xNbcRHKcpL7M5Or0bI6sLF6M9Pb8pLbMtMrUfJ601OLsXIakPG6UFE58jKrEdJq0rL7UHFKElK7ENGaM////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/6ARIKDhIWGh4iJikQWNhQlBR4qKjoqHgUlFDYWi52en6Chop0WHI4akZSqOpEamhyco7KztLWHHI8ekquqk7qZHLbCw8SIphQFkzqVlQweDMu9Ba/F1dayFo/J0c3P3irOlb8Isdfm54aN27zO4eDP4JWTBZvo9ueNGpLM7+7tHjq8QQuogdy9g8U46GMnsOG/gL48aDiBsKItGyVU/fMHUCM8aOHoWRw5ClfHbhsdntzni0IwkjAXNSoh6WG/jik3iithMOawUic4nLAxVCgsT7jWpUSJU6UzSy49ARVK1IbRciOPUZhRoGuBGRSoLTqhYZVTlUz/SaS4SCtXr/5gxSIESkEfS2aXMlnFWshGAaaAb9r0x4rtoVII6uriFRDTK77mcM3QdbeXJQ/AEp3YhvZmWpUqChg2hIvmYn7yLmdGd2wdPwbgKKWeZvXQ5m6fl3ruV/hW4mSMY7ubJ7daNkgnPQZ8Ck11T0J+XzfFvVu36HQIJicP3LzxjOfE1AXPuTt0PehKzw72lpZnIQvZlWmsLlAVPci08lVevxznTvC3CTadgLo5c90g+km3njvMEAReLQrtpx5oHkQ1CFn7sEfgdBo688tLgkSo3DvcfSTJWsRg5BF/JLYIzSU2EGJShzRSx1FIGoBIRHwKTmiTCu4JY1KPOZ1FSYUgwv5H04ZFtiOgaOUMKSBu6sWDpC0z1UQjRzY+I859g5g04o0N4YVjkggs2V9aLjb1FIz4gZLUilueBZGBFgri15ELDrjRPAjISIEuua0E23J8fnMlLWSZ1aSfH0GEmY4YMunjiRSMVqmZbpqomw4eTlRLgH0Wilc8oY0Gn12AQZoST1gFiJKlfz7FzIGz7Nlfn2S2c2eQCCLAmT8b8nPJg7p6VmObuz4DrCykdlrqTVYWEOh72mhZYEOOQRYteYiGe5ZItCTL4qNp4Srjb2t2hueDggibaJePLkXuLNEW2qtK6hZyAjKLtXmZRJkmwgFL9drpYr+i6ArPmm3uG9Jo7/5xgECaqTykiysIHIUIRiWGs2ytl1AsCgdLgvuwux1ZImpbiWkAiVcFcBwnIZWS2WVgAW1scij//uVqmZYuKpNQJ5yAgNJCWXCzoITSh24/4Eyj4yhZ0uoppLBWBF/GlybcNYSDMjvvyABZnVXZEHM5Jns1Xz2LsEPr+421MKno5LY6z/OzLCI2u3edIr+cFavzEiigy39jjZG2YeOECbwHKQl5kZx2M/ZPdFOXucDWPm3P15dLW19jlNfydSSfffYLBaLfQ3rU9LJ3SeiROZLe4MNNk3pWutPpdmgUdGzPv6btktJlavtkWy6n3XTiJQXLbjHGtntIcFCxj4QY9v7Lb+8xQhZrIDPNNWcqt/O+mY9+CTbHNNXFTHPP/idAWZD00kGd0P39AAygAAd4jakMpSj2I6A5DFiVqwDOEVuhWVxqo8Bq4GICNKhABQDgghoMwQATMB7+hKKYyjRGL+OrIDYOtoMHAOCFMHyhCwDwgB14IIXG2AplghOR1ahQFhSIQAhmyMEiEjGGIYgABRTRmvHEJhrEoeAPP3GwIcDwiFc0YhGHcMPDZAs1JPpGc8bxvx9aQABBKKIMtRjDNvYABnwRDxjJ8yMwTVERFvBAD4iIxSzO8I9G7IEHyqEfhnCpQ/8p4wCz0QIsArKPbYxhBWAXooUQ6VJQWd8dif5wgg2sEZKP1OIjN0ARobiGPjurVow2aQgVDDGGoYykLGU4SEckJ2FFOlKeWMkIA3wykqEMphENkBg1Jc5tN/rSeVhpgRK4wJFsnOUsXYCJ3UVOZO9oHis5kIBowlKU4HykEIDDKWT66R2WKIEmK3iCBnwTkNKMJwAkYLpU9ioaDPuhDawIyXD685EVMKTWUlKTVPFyRz34ZT/l6UbhXdM/z8inCm3QT2FaVJQuMFX2ALLRh0i0ghwQJkPl6YKz0dEh1TEoLxGQ0DZe9KVFfEDpPEe0XfGpBI1TIAL4qcaRxtMFFaDMrBDFq6Yw7qAccKcf/8nUF+4AElTC5em8Yf60TZ5ACPD06TRfKASAObRtQ9vcJi2ggWcqlKmx5CA1bSk1SDElkwcVhAV8uVCfHnEFQfHLLQmnnmcd1ANmhalIixgCHXBCRCdNGFzjKohOahWYVzTBSyxXU7R1o3iKFGA2DiBYcKrxAJSUa+ccak7qZXaRF2jpY/n4AAbEUViR4FtDyMjYdHRgj2jt6Qx7IAD8tMZN9CGeCGv7Hg9YcaRHDOrTgrZDGjGvesRFBAJMEFh/qtUE18KjKbDHlI2p77R3LIUHdvBKWbogBDZ0mpxiNrOuwK9464xuOigggxVUgLMuOEAFViCD0IpifkrrHw7lezT/Xexi8FEvgRfM4P4GV6x/DUxgeI1yQKtIGHgeGAEOUJCBDGx4BB4YrgpN0YEX7KABDnBAA3Yggg6I2HoUuMAEMiADAtj4xgSQgQJG4BLwoqMUJchBDFI8hCIb2QFDiEEACjDga5xAxjW2cQKkjOMcT+ACOc2bEFiA5CMPoctH5oEQVnkOXAggAzeocpWnfOMMdGCXPuFAAXYA5i8b2c5gRrIBmLzAEigAx2wGtKBzrIAZ+Dg8F8BAnr185zoPAQMXODQisqGANBMg0JdWc5XTrAD/ksQCMyABo+1cZCR32dR2JoGhi9EIFGh6zTfG9I1RsEyLNKIBpC71qHPd6B3UGkIXiLWwqSzlKP5n+sYywDJMOACBRev61Kf+Mqq/7IP4hoICE3j1mo2NaTZPYIkk8QALdI3nUU972kMYgAeGwYERDPvYU05AlGUtbxyPwNrW4MALnE1uXvMa1S/At0ywbext5/jS3C74lGUQQkmHgqw8IPe5vbzoOjuABxqQNAdUkAFiH9vjxR60jWWgA4EzEIFNJs0E+E1qR7u85RMQuGZg8Gp51zvW3H61APBtigvIQAQiiEAEgJ5sKWomB7lG97Mpfuc7ByDLY3F1oGWN83e/GweNA7IMFmCCDZiABmD/etcXIIMc3ewEK1g603ddblTvAOqKsEG21WzzGt/841XP9AQaZwMFRP7ABICngdgHL3gT/GACZDbECYYs8aZP3NFNJwHcNdNxeBdc5CDHsQx+phAIBF7sgg896L8eAQjkyDYWV/uuUc36L0/+Y5U3uNXxjvfNp0MFOfD66EXPe7FvIAcqgIwNeBBtxyfd3453wOttM3eQU532VN/7ezSQg8/3/vqDN0EOMu6vAbS83Gxv/bRjsHzFo6DbMrj786FvY6zLyAYi2L38sR92H9TaBmnvt9qV3nRdGyDxJSEA2kZ3s4dpHRArE2B987eAohdzOJMC3/dyTNd6RpYDMocIJ6AD84ZwHrd+tEdyOlICC0B/DCh/EVACMjIB5nZ8kNdo0uaA+YEAzf6XYx74cc/3bVEiAyVIgr23ATKQJBrgffrnb+LHazzgaYAzApdndR4YaMomVxTAdTy4g4O3AP7FAQHweKrXf8/mAAE3DNjGZvXWbbAGcjKAgggiAFQ4haEneDtHCB4QA/qXekV4ZzwwA8TAAT6Xd5pWg0/YWASwhoJoAgRAKUKwdshnfKZWbeFhAzjQh5hngzKAA552AvHHhoJIA/ZHCNmAYsbXduB3ZBKAhKqjAXNHdfSmZhNQAnxxAlKYiTu4ABRjATogh/zHhW0XA4ZlDaA2ATKwgQSoijhFGhGAicZoAhFgMhaQaBUHihYXA5G2QBTQAUuYeQvXAaxoG8UIi/5TmIy3MAN0houK6ABP5XBtIWMH54EM94eGYAM/wI2xyHc+wGVFmGc84APgNjq4AAO+CIwjNwEwgFNnd4nwSH+bOGkoEwBDVgEu5wBKNgP+YxGmoAMwgAP9OAE4AAM6QIqKF4jHOIWEiG//ggMBsAMSkGISsAMBQInlFx4UljQwqWBjIQBe95EMuAFvSAr6YwoHlDQpx0vZ8Io22XuGx5EOJieBWJC7twFAcIFHiQgiOJQmiIdPCSEJKJWitwEwWJWzkA0EKZUmEAC/xpX/NQNCyY0LsGpkqTo6kHsKyIMb8HvBt5ZCogE+UJNrGAE+cHp0OQxy9wN4iX1eFwGI1yeXrKYQBLAAusd7Xkd2ZmeYxuEIAnADAZACX5cCAUAAAqABYylAgQAAIfkECQkAQwAsAAAAAIAAgACGBD50hKK8xNLcRHKUZIqspLrM5OrsJFqE9Pb0XIKkfJq0tMbUlK7EPGqUDEp81N7klKrEVH6kdJa0jKq8rMLU7PL0LGKM/P78zNrkTHqcdJK0rL7M7O70/Pr8vM7cHFJ8DEZ0jKa8zNbkTHacbJKs5O709Pr8ZIakhJ68vMrcRG6U3ObsNGKMBEJ0hKa8xNbcRHKcbI6spL7M5Or0LF6M9Pb8XIakfJ60tMrUnLLEPG6UFE583OLsVIKkdJq0jKrErL7UHFKENGaM////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/6AQ4KDhIWGh4iJikMXNRUlBis8PA8rKwYlFTUXi52en6Chop0XHY4ckZQrlQ8zKxyaHZyjtLW2t4cdj5aTrKuSPJaZHbjFxseIphUGvb6srKoGscjU1bQXj8yVv77cq8EGMwiz1uXmho3a3s6Szqozm+fy5o0ckuu/vfgPlRzx8wCPdbCnil3Bdgi3BatgIqBDXDVKHGRXSZXFiqxK1HjIcZSufcB8XUz4qwKxjigXNSpB0iA3gxVXaCSXElcpEx1M1NCZU5YnXepETgTpbIZJTzdz7qzRk+ZDZRVmGJgarsK0RSY4BKPYkug9mScVQZVK1ejVgDcr2GtHKVompv5OCdUwwLUuUUoPNCYq5QgS2wduY8W1pssVL4PBZB5FZIKut5F2Xc5omGxXyMeJh8lTFvQuD2lMD5mYwXVo5FXbKBfirK/upM9nqWHze7qbzHGG5orE6JXrqsnoEBiujfpSCdyy57Ylzg+YgX+DlO/u7XWFakGNZrSm/q0V9GL17tndmq+5zO+jYVIvrXdQ+PEVC3bzN5jWwK68pzszwOH6/fLrgcRDCf4RdNpI3rxy3S0RmZYPgBZx054gJlRgCXNeLRTWEMoJmB9CB4ljzEeldQchOyssxkhEIKKIYSbkYONhiyeWtOE1LDoo1Ie8zTTIRzoG2EoJMbJ4II8Pnv5X3ydADaXPds3FF1OKG86VIIa+TDjER11B6eE2KtKSFXkwRYZif4T8h2WCYBGSFX75BBlTPwuK0tia3eyW10bumcDSa3gq6SZpmCFZF0YEMujYdHLiEwxwaQonZIJGOXWno4HuectoTuLpS52zWTLpDJUawql+cI5nnaKlBRpMnVtWeBh1MxAIawcX0ojnJKvacqmhk+YFqyC6sCQqiqJmMuwQvz6WaaK2NLhjsLn2eghflg2l2CZLShtnppJo6dGf1PpWwo25VFhCCaSSuq4my/74J7jsQFsLkPTmU+uSafakkwkA48TvIBUyUy4++96ykrMH+4hWDa5IebCgt/7gC+CaMg1cjcX5EmlMg/hMygOkHEF8cDck43IfsKcJgy5Ab148aZvGXIAASyx7RaDG1ix8MsU1G5kqSDDWlJ3MxBUtm6RDs1NrBTyfEypC3D0dtSjKHEurrTUR0oiFFx4oE06bWdaVqIrFi1KxomqbGUMAYcuuo9uacDVaysytbSZQ361yROu2W+u6ZHdNCuDstvsuwCldcBPADAHsuOGfOJ5TwAFPTvnmnHfuOUpJ/cuUwJ/T4+9STdlXwwoi4JDCBhu4LsIKCLxcesWOREVVVbGppAsGFGwgQwHEF1+ADAuIYJLftxeSll/LBQZXJyYAPzzxOWBv/PEUYKB28/5iRcWLk2/bTmwFAmyw/frZF7+BB2GC3wlrcr4G2ssXlLCA8e3z7//xC5gB8z43m86YyTjIcU8F9qe94vVvfcZbANTkpxKINeog3YFHkVLQwA4WoH8gLF4KvkNBr9WAIBOjD7EwwD4IfvB6EPReCXNhIAHp6kFo0gUFPOjAD24vhMSjQAVmmJtIlAgwvJlIJcTRCBHA8IfFk0EOpMg/GE5RBOZrHokCNBLypMgRO/QhD6dYxR4STwYUSGAJF9aqy6inOZnAgPpcSEY6llEGDxhgSrjkoOXAKRiPmoEH/vfAOravjhB8QRY/9ybyMecgBtjfAx1oxSeaUYw4+F7nIP72xqatoxKWfOEl2RfKAlBAk5xrTKeodY9RThGRYpykGM+Iys11YFEMw5IqSNlCCE4ye6ckIocIFZ8gUYcHwyukFX3Jy+PVknK6+ZlBGIi968lSlrMsXiaF6S1UbQNDgxSlHUf5vxc803AdmBfSZkYaKPKwl7OUAQ/O2TWgeFNkMuEABUp5SWwaD40TJCIbQTIpZTkRiNn8X0LNKczz5apRdhmbDl2oUDrKQAZDbKggTPYhdpKtemZE6CwnKQMZapRZa8lZRKHVCBxQ9KUNxEFANXqBRoLLYYzQZzU7iND+UcBjJ8UORz1Zr5liZwb7TCZM//lTehIwWxiy2rUq4P4BS/b0jB4AalDTJKtcnaZWhUsG8I730ouW1KnyUxfakCUMe+1FFy/Y5yjR+AKG6BF8cptVN5SluZ/UgAcvwEFSKYCDF/AAblutXF8S5663OLWmmIvsXRv6uMiSLrGYzaxm34oT0aVujafjyWU7Ygo5hsAFEpBACEJQ0tDIT4cxsIAFANACGgiBAGlcpGzSKQMFkOC3MQguCWJAAg0oQAYc8InnSrECGzgAANCNLnRbAAAH2GAFypVHDRbgAw0Ad7jfBS8JfEABPnUOARIAAW2nu17qShcEEsioOdI5Ae8S977gxe99JTCB5NpyBSxYr3QHzF7qCgG75bgADwLw2/78Oli/4NXADXgwWfAIIAjuba+GMyzdHbygwujgwA0gTOIHN/gGHABxLS6wgh24l8MEpq6M17uDFahYqCH4bol3TFwShICEaKnAbAW8YSLHGAAWMOqmKKBjE+84vBTQrTVMoAEiwzi6M54xezWAVkOUQAFPDnN+Y+CDGaCEB+plb4GNPOAMt6AFKxjRBpxM5ydrQAZSRsYFCKBlNhfZzewlQJ73UgEf1PnQECaBApQstRKkWc0aJjCkX9yCFFdMAPZFtKaJKwAphw51oxVLDiBt5CxbGcvTZcBkTVCATbuaBAXQrU48EAIbJKABDUiADULggdpRLwGoBrSkT61mG/50mVk5FrOy8/sD86KjAzMIwACE0AAhWPva1YZBAAyQXVMJgc0vLvKkUS0EBGzKt8tOtwKWVQMGTPva1I43vBugAwY4uxA12EGwh43qcUfXAceugQTS7WoJ3GoGNqh2vBVu7WozvNoE4LZorvxnAQt7w8c2wcBdvWwJ3JsRGMgAw+cNb3lfOwMYiEsNTE3xfl98ui0IOLo5juh1O28GI8D2whtucofHewQCLIQJ9N3miksX0Bnegbl9lWyah7nZJry1yXXOc4WPvAE2+A4CAtxnfmsZxu61wLE70GqCixnWGzIBBEruc6vrvO0N/0HabXD0inM43IC2waBFI4Amm/79wRroNCFWoIKd87znJaf6AOL8IwZ0fdiPJ7WqFVaBmf890T6YaQdcMPKpH37nD7e2C8KSvzev2dRF/zptW8BorBUg05fPrwaAQHoO6KDqbyc523UOA0tjhwB+rnvkOXwCFZdAAk53cpnTRIGrT93nuP+8taNMCB6YnuWSRv16QcD4YnSAycmHMPUJdgPDR5/qn2/7DdIuAa9H+si0JcHeFdGIAMA++T8wqglOkHuSh97tiUcAzoYNB4B9qfd+AHAArbdiM2B5NKdoM3AjJpBzztd5Foh7CgcDC3IBGEB0whd87OUAeVQNF/AAN+BdNCdhFJYbbleB/UdtFuB8Ov4AKh7gYsFngACwAwJwY87DAT9wf2EmAT+AJobQAbfnf7pHdS14bTrwcYywAt8mbtmHZDamXRRgaBrwZMUlAeWVCDUAA9DXf2EIgIc3AstSASRgevv2dSSwdPJQChxQAL4FhMV1XMm1JPv3gtIXeuknBMa2FyxmAyDQcgAAAtfVVwDxOwXwA/ZHAj/wAwWgPLJWfi4Yfc43dSjwPdggAydgAQXYAgdgASeAUTwYCqGjE6PTbYnwfWQoffIWhlXXAOPnO3aDALY4DnZTih1xAbb3ir7Ih3qoAwu4WaDQAQEAi5dofrE4esRYDCsAA+gHfRfYeTpgZs2oMj/gi7oHi26gJ3fXaBMV0AP/h20xeH4N1wPD+I2VwwPQeIlWV46gBwMrqI42EXJjeHgNAI8NBwMmRY8qg3CtqI0ORwBu5Y8Q8QMqsIT+pwP5Z5C7VQIBAIZI2ADaNhm66JArQQEB0AMRYG0R0AM38FNA1jmBAAA7'
-
ring_blue = b'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HGieCTBLWavP5OjocYKR////HI/jXLPc////PJTMVG6EdLTsRJrMBEF1rNb4VK33////+vz8////NFp8NpjaVF5oVJ7EJE9mBHDIdLLE////////OH60O2FxPJLcFCAnHFqBeb/5PKboJEBMHILU////4O/8VLTuDI70lJyh////////RKrkPHKM////////DEJopKq0////rLrMvMjQbK7EfKbMVIiYfKTEVIKMfJKsv8LEhNDlxNLgpMLcpNL8VHaU////PHaXXGZs////lLXR////////PEJM////JDI6vNLkFFaMfIeRHDhGfHp8vNvxNIzJpKKkXI60KYDAi9/5PFhgPGd4lM78j5OUVFhYZK7MVI68XJWmfH+H1ODwXJ7U1NLUaLrc////HE5wHG6oT2Z8dLrRdK7gVJjYBE6E////aLrsb3qE8vb4////V67gLH6sFEJk////GmKYaJq0ebrsPI68////////KEhk////LHKo////////LGKQTKbUOKb4PGKEBFaYrLK8XLrkfLLcVLr4lKKspLLEXIKkfI6cHD5MDIjqDEp0FGaglMjwbLLIbMnxTGqENFhkLI7ZDDxcrMbc1NfcHFR9DFycHIfWbMLp////THyMXKbkLDhIVJOsPE5cHDE6HEhkJGiWXGp0fLTkTJe/XJ+8LFFiDHHBHCAkLEBI////zNHUrM7sXHKE/v7+CP4A/wkcSLCgwYFztrjh4qyBw4cxuAzbMuegxYsYMXrp5KaBM2rfRIka8q3kt0RDEn2j0yAMh05eMsqcKXBXAmcEQn4j0KBOgJ9Af2qb8kZaonRv1rjZRbNpwV0dJ4ia0DOAz6A/c2QNoDXHlF5FeoUYxtTpTC+lGkglcBVrUK1c4+bomiMGkSchusU0e3HOR1EE3GLtKpcr3blz17zKsqYiX4OMGgyZ0BZrnRh0YJxMxJlIrmYx0CFGPKK0Am+hUDwe6MWNM1ENBNchUGQIjDxhprx4oe3FlDBvnhQh0kx06TIjypSJg8pTrb1md3GhNqTyzwYwEkmbMoVDgk5zdv558aJiTqdu7OKgU0AElRXl8Mvgs1fPVtmm0jVbr5OuSJ4pWFRAUwVYxOEJKqigA98DZTxQTw8k3CeTF1zAMIFbeSTyXykS0rRLNxp44k09+DxgoonEQAhdRrnQ8c1b2qSTzhTDrMjXLvTgMwYFGpz4gAvEyGDETIw4kwhQWk2hYRgCrlYQK6EQQ0EoLvz4ozvJLJJRBQ0k4hNcSsbQTIdO1lSPBhT4UqULVbrTBBQXefFGETHINUURLtlYJmtGyMIjm2xyQ0gaegrUDRfSwJWDNnhysGdGfarSBKDQPJKEJAftoqQ2hgXwSh7skPkoQV6kkowqLkCjKjSEBKOCQf7DSJPHXFwp8EoIoo5K0C7JyCADNBcAG4wehTx1J10vFBFHk7pilMI5hshzwbQX6NHGqwO5QUUMo1GRxTDNzlRIMIZMe8sAbSRhAmtrFDHaFLTEkWu4uwazjjwDnDsABszs1UkYT4z2hBXd0DuTJG1goO8AM7ShiEBYQBDCXCPEEe+8Bg+0SyZ6zDDAxwOogYRA4hBx3AhWZFFLxjMJswEpWsSshRoz/DOHJ83Eh0oozLJ8UQrbYDBACVqUsAETUKCgwHvKjWBGHD7PZI4asZRgNROxAELPE3HAF8oYrUQt0zUbbGC11fcgYYs3JTLYAzGYio2RCUyYXYITJfjQiP4ngjBoYg+hYCI3RpiMM8rdd98DRigU+GiPLI4NfhAPJYziBN5OwBHP1ye6QAE+GMutghPh3H35L+No0IOVD1BwQKGS/+NFCQt0cLkT2XSAjztWuiDMObBL7oUTC9yOwCZOaOAOoC6ockDoYo9evBMIINBBB8oz704wcMZuECtO6EN99ZvAkQaqbEIjQxOCe19QIJlXX70Sx6SyfKqVXup+QdU4gUP1RzgCPGYxCBkEY1XyeAQo9kcQfyhBCQFEwBGUoIlFPEIewLoAtILBwIFkAx4nkGAEozGHYDyCWrdYxwpS0EF+IAAeAYzhEVjxjySs4xYXOJc8SCGMDkYBAf4nCKAFAggHgQhDDyvQ1wowkAnoGcwLSgjhEYY4RF0IRBEzwBfISGGOuHnPDkcIogXGGEAHCMQLzBDax7RgDi46sVm7gAcb2DBFMioBOiZghjlkNrNYHMJ7mgjjGIMwRguYgiAqWAHMilYCJqihDywcXDmOQMdBEvII2BrIITZQtbOR4R4zeGOZdtGOI4jAAkEg5BitWBAVmCMcfThbCcLxi0YEb1ReEIMF2EDIVF4ykwQxARmqhrlx+KADGYjaBwqJSl8GwQ4W8cIz7jGO2znBmMi85Wq8oAsqOjOVlQgeD35xD8RdrgPFu4IoZ7ILMfTSlwAIAgAsUI6MWAOd1v68nD7oV89RuaIdqkxlPAFA0GrMJAPZyMb4EHA5HGwCAdhYp0F2sQpUNpOgGA3CB2jiBXBsogPyk58S/peNakj0H7uwwwlSiUqMurQA2tzVLD4KwJCCEAFKiAI/aMKPD+QjoC516Tti+pRZKAEeIpQhEIMowHnYARE8UMF4VMADRNgBEitVZVCDCtPHeOGHSkgqFQM4RzrW8Z3OtORWXfoBol7ED0BEwFgLWccp2pWZal2rSw36KB744AgSxGsvm9lMZ+o1qEvoJy5VKkSLsvSb8pTnYTNqCrc6RQWrkOsUIRvZyQaVE91jmQpMAQ9mClSyngVAPgQQWrF5ARG6gCRHPDs72Xx8ABGWpRcU/KALMbQjHxYgqAVE0I4CcOITPHhUQAAAIfkECQkA/wAsAAAAAEAAQACHBBEdFIzkBFCNjI6UTE5UVI2fCm66Bi9MXLLUJE5klMr0////JDE5RKPcVKLEdMnnNIq8DD5gBB8zzNTYRHB8BH/hJF6EBFmfbLjUbKLMb3V8JHS43Ov3jLLUFCAmNJXcBHDIbIykBDBWRH+cbL7c////BEBxrK+3HFqBJEBKOmBqKZfpLHqs////BBYkBGSyBCI8bLPqTK/nRJrMzODw9PX4FC9CZKrBlL/h////HE9xZL/p////////XG58FIHRJILEOUFJJIzY////NG+MbK3AbJe4HG6pxMzURoyxHEFXFicrPJvXB3bLFBocFDZMZLXZJGiSVIWW////////////////ZKC0gtHqTHefdIGP6/T5FjpU////JEZUfLXnKZLfjKK0OGqQBChG////Gne6SHeIfIyYBTZb////JFd3FSo2XLrpDCY0pLLEfMDUWK7cPGd3DBggHDA7pMLcTKj0GYbZJIbRPHakfK3UwNvvRJ3W////VGFs////JDhAvMbOpKSkrMzoRGZ0VHqcPH2fjJqskrjZ////vNbslKe3XJ7U3NrcfKbITIKsNldi+vz8hMrkJ2KR7O7vdKrYfJevLHapPJLE////////////ZJqsVGp8VIKofL70HEZk////LFZkXKbE1NrkBF6nbKa8TIac////NIKsdLr0TJ7E1Ob0LIbENEZUPHaURJK4////3Ob0fIaUxOL8qNL0WLXoLHKoNI3JbMLpGVyP////////////////VJbU////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bGQjncFdBgzgu1WDBBK04IU2rOG+qW2DHKixXzn4t73EiEdz81UPN8iiD+cgRxskS401BKEPWjgBZ/oUEAAh+QQJCQD/ACwAAAAAQABAAIcEER0UjOQEUI2MjpRMTlRUjZ8KbroGL0xUtOgsT1mUyvT///8kMTlEo9xUosQMPmR0yecEHzM0irxEb3zM1NgEf+EkXoRsuNQEWZ5woMRsdXwkeLTc6/eMstQUICYEcMgEMFY0lNpEf5xsjqxsvtRUoeIEQHGsr7ckP0g0XmwUXJD///8seqwEFyQkl+kEZLIUL0JMr+cEIjzM4PBss+n09fj///88lsQUeMgcUHJkv+k8pegUgdFsrb8kgsSMwvQ0PkxUmrw0b4xcqsxccHwWJytRgakUOEwcbqnEzNQHdss8m9cUGhwkjdhktdgcQVckaJIWOVT///////////8bZZ////9UlKxkn7GC0epGjLH////r8/krR1CMorQsV2partc4apAEKEZ8tOj///8VKjYad7pEdowFNltUhZZ8wdf///9EndYpkt5UYWxcuumkucx8jZy8xc48aHgMGCAcMDtMqPQbhtt8q9T///88dqTB2+////8MEhT///8kOECkpKSkwtyszOhEaHRUepx8oLw8fZ+MmqyTuNn///+81uyUp7fc2tz///98gYY2V2L6/PyEyuQnYpHs7u////98lqxUqvQsdqlapsj///////////9Uanx8vvQcRmR8psz///9IdoTU2uQEXqdsprxMhpw0gqzU5vRwuvQkWn////80RlQ8dpRMhrT////c5vREkrjE4vyo0vQscqg0jclswukkQlj///////9UltT///83nekEOWYMhuRveoQsfrREquwMNkystsRkprh8hpT///9MpNdcob8UQF58yOAMIClMcnx0uc2UrsRMgJJ0ipxcoNgcXIw0fKT////U3ul0s+ccgs2Uwec8b4ocN0Qsjdf///9clKeUoKkMKD3////ExshceJBMVlxkutx8uulMnsQsXniUmpwMXpx0pswsWnjk5ugcitxctug8i7fU1tQMgd4MWpwseLjk7vk8ltR0wNj///88YWpEmswcesREpeQshMI8QkhclsRkq8f+/v4I/gD/CRxIsKDBgfNkjSkRQpsPa9Z87KOVq1qgeQczaty4EdKeam3aGANTj9kFf0MwYfLXY1gQLRvkpdsDiaPNmwK5/GizY1wyMEuaKFHyoegHAy9eGEDySwsWLZcudeCCs2pBLp3aIMgCpkmFoUONHn3xIanZUUgMcTOEpBBVqzchKXDhswG8r1/DEj1aluzZUaMwYICSxlAVRDXhauSwK0aWBhUigw0r1q/ZpIADCx7sLIyQV4oPynKRrF2vyHjBwtOGzwHLHj1IHbvijgUSDJoxCNgtIMyZaHBCD4SEaleWXaiV5IXHxgkzTDf25TNjoDqSDaZacbtieDNvASom/qQakRguJDsIIARAnZcdGDXlHkK7pogDF0iQanBQhKgfkirSFOAMFN8JYIIAc5hjRA3mAaNDLXcl14sT5IRgTTUz4DRDBlVA4YwI0ex24IHmmHMGgzid94YO7EXGhhpL3HFNeVVxgUgVknRmoIEmmKDOF4TQuBEqCNTSIjxOOHHHGELCVcMnAkwwQQ4j9vhFDs/cJMsuEJx2WgXs1GOMNqcIZ9AMklhwRio99sjLF54EtxEHLmQRwJdgJhOCMU2a+U8NRuQwB5Um8FLoI8hQoxEkO0CwC57sJNNGNX36+Q8kIwjqSaG8oPFAAvdUqkAMb7AHDwQh0GApR5ne84AJ/mjE6skTixzERZ294DmOMSVUuqpAkBDiSQq8dAoCCF14MolBnegADHv6OKGNr7/mpE4CCcSKBghooBDHVXWy10skd5RZLUeimIDosceiEMWyA/3wxg7sObEEOefeVAkyCbArRjDZHDIcrqiFeQe1+QpUgyddPAGCGBCjYEtisezQTr1LKJDwTYs80IUYD4sBAwxJCERDLS6gxoMaB29sUw3IdBEFxBBn44tALkSSXAPGqOqyTc8cgQLNMmRjyz/z7FKqckrU0kSGP3NEwQFAQCyDDGV4s44sMeygXAXwqGFN1Dc9kU0wV8sgRhEnkDOOV1+1MQQ0ZNsUTjYwXB1B/gRlPGNJMncNZQw+19TNUTcH/BGBDHuX4UYIyeSlBBja7GH4RnJ4o/jeERTRRRv1MK2EE9ZwcLlGr8iweQQtlFFHG+NMVg0Pb51u0CQy1ME46xF40AQYFXxAVOgI1w2JDAzw3oIHEfwO1gfM4FA82TXkrrwydDThxF7QGzB91JN0rgzrLdAhgzYXCF+UP/mYbrtBr3ROfgvKMBACM+p/gMk+irxvkByda4EAyweEEjCDe7BwR+H8R5BzKKMIAwQAHdxQjSFYwygSCALdGDgQTnjAAwMsny8EgQ9aFOUFOBiGGTg4kDpAUIAAkOAJ5uGDchigL8NAAtQYSIEWvDCG/jFcxz+00YOylMUd7kgHC32hDA8AoAUxpAMDBJIOB+TDLGbAAhK+l68alMEDdIBiDPugAYHsQQKwUEpSrrABRDCwG8sTIxDBAaxfDOMyG+AGElB0uhrUAYxy7AMDyoMIWPwCM6MowCzw8D5ixBGIMRwAQbhghn5gBgNIKEAVdlg3CtABjJAEQB/gNRA8aIEFmcGAIQwxCz5GrQZ/aGIoAeAIg3Ahj4DZTBqE0AouhgYSbvgkAIZJTCaQkiCIkIYhNoMBFaShCiMgmy8eScxhSvIgkDBFAW6jGwxEIw0qIM/GIDFNUFYTAEDw1SuQkIZu7sYCzojGgrrohuUp45yi/nwHRwIBBRHohjepCEUqJKGoX1EACMujAz4BcI6bZEAIrPiOCcKTAgFUwpcGqQExItDEe+KzjHFpBSuEsKMRzWEQOchBM1yJkxp0ow69A+FCCcBFSIjAMyXtUQ7ukQJP5GAEFMAJBRxRht4VIYz47AMBjnkTQEkCGzxqkwly8IhHeOIBngjHIuTwihrgpwaMkEM3iJANjpahCCCUYzUJwNIUjSAVg5JqsXjBr0ck6wjBoJkY1CYGb5TBdUVQRgSr2QcAaKCtVoGDoNjkpmJtCw1ReEIXuoCCyv7hsgxw4fhaEIEnnrMPdGjoqk4RhlT0dK7bYtcBIHYAb3ijr7vbhFsI5ahQIDDiXJBoRg6+8AVkaMtfIbOa3pQXwfIt7xwYvckkKuEJbHlCtXpNG+fmN0A6WJcYTE0YFwrxhShMFhl5Fe5weRcBOmBPinHI7s8gkYQReOII2cgGCrJRh5GdtQhoBWMEGOCLJCS3WuuAQxw4kYAnwMAbMvCG0dzwjBMU1E8BAQAh+QQJCQD/ACwAAAAAQABAAIcECQ4Eh++MiowESID///9MUVT///8MZ6kKJzmMyvxJn9EkJigpR1Aon/YEaLjExsxktNT///////9kxvcMFhvR5vlkoLAEUI9EcX5viZwkd7Zss+wRNkwsV2////8kidQUGRr///9sc3cEdsxIksYWJyv///8EPGmkyuzM2uQUh+H////u8PEEXKNkwe3///8klOUUVIQkOEQ0n+knWX3///9Efpz///+sqqz///8Eedbc3eBMlLQ0UFx3v9f///8YXIwUMEH///83NzkHDxT///8caJ6E1+0LHylso88sZpQcj+MUSWwJMEtcrdF8yOHc6PRkmqxxe4T///88oPT///88lMx4s+ZMl78EQXWs1vhUtOxUnsR0ssT///////98v/b///8kQFBUXmgkT2YEcMj///////9UboQ4frQ7YXEUICZEquT6/Pw0Wnz////g7/x8e38MjvSUnKH///////////+kqrQMQmj///8slOI8pulUgoxUrdwEgORclaasusy8ytRsrsR8psxUiJh8pMT///+/wsSE0OXE0uA8coykwtyk0vz///88dpdcZmyUtc7///88QkwwcJr///8kMjcUVowcN0a82vFUuvdcntQ0jMmkoqRcjrQpgMCL3/n///88WGA8Z3hUmryUzvx8ipePk5RUWFhUjrzU4PBUq/fU0tRoutz///9kuuQcbqgcTnD///90utBPZnx0ruQEToT////y9vkcWoQsfqz///8UQmT///8aYppxgpH///9cuuQ8jrz///////8oSGT///////////8sYpQscqhMptQpjtk3mtxcxvw4pvg8YoRUpswEVpissrx8stx8goyUoqykssRcgqS80uQcPkxsuvxUmNAMiOwMSXQUZqCUyPBssshsyfM0WGQMPFysxtzU19wch9cMXJxswuccVX0sOEj///9MfIxUk6w8TlwcMTokaJYcSGTk6vBctOBcn7wsQEgsUWIMccFcanQcICRMretcruwMftzM0dSszuz+/v4I/gD/CRxIsKDBgXD8Xdm2yZMGDfcepkE1aBGcgxgzatTYBtOVZcuwQAMHLgkXLO3a/fnTzoYjI0osJGqzsaZNgbfA6GEGwQc0K+buORjqoIVRo97gOeKjCBmyQrduSi2YE4aTJ04+jCjDtQzRoUdbRLtANoYSDGrSFZI39WabbzDmPdnDb4SOrVwdeC3agm+0vxdqDRicTk0HWJBots0IhYo+RGx0SL5bBi/RvkcBkxU8OEsWWGqIPUu1+CAjGIiAcdPhR7Jdrvc2BcNCz4LtP4RcwjtX68LgAZ5PnCBjb5e10gPbaNtzpIGf56zt8iMBARyXYLk0HNh+IGmypeyU/sQAngWP8BPjenDIoLjtLVWZEMl53trPCHNOZClwyOUbtgq3tNEGC1BgA8kfQADhBgaipJPFeU00cQID2aAR1VS36ONCOAHQ15oK88gCFC2Y3JSCBemkI4oaTEAYYTbZzHKhTW2oMkE4fnRInwJPWPHBNzNKdQsksIBGxgkRItkEB+/M0t5G2mTCYYcdqsAKBCr4EORitxQyjj09jBNhEwg0EUQQvtjECBtHBODmc0s8wcYypCFXUAo0xGOPmGQigMAlCNyxERwNfCLHmwHEyYwyW9qJExq7MMBBn36+g8AOGbXhTHOHdrgEInps8KSjBLWRAQcMUIoEAkG4M+pA/qRk0oybHXKDyAwbkLpRBk2kigASwAJbzUG3FNqpm+iwocqrupaKRhAy+BnsGkGwVRAYE+hDqx9soKNHo81SRcwlQQQbLDVUGUurHEcsUUG4NZXz569IUEABEmuwQFACzThzbLJgwGtTKQi8U+/BFJiSnLpuorYEuAJTdYnB9RJxbyWKVeDMBMe64Mw3EdtUDRJB3EuBxRQcIpA2za3bLsQhlxrEGvWebK8IAhVKawD67KNNzDZJgcAa9hJhcSX/wLHxseHo8S7QG/WDLxIWG01EKlpkkonLy0Bt08wnW00EDmCE0wCtVLiyj9c14QOs2ERIoY+hb27BBshsazTH/r1wj+HMJzu7MEOJeWd0CN9GAwDAEA20+eYTS1xUOEapEEF14kSs0cCUHSKiAsyFs2C51YpTsPmbfiDCDbOT/9NG0UQorvjpOabOD+uTt1F17LJvXnvqn7d+EAv2AsA7AKYjoqMfT6ggufAE7VC17ADkQ8URtevgih6EQz/QIRZTD0AlqihPXx8KJOA9QaaQLnsBZS9B3wxOrL2+QI8kTn0cjOizB3Tm8IE57ieQEpxMfJyAwwyAQR8dCPBp3utH+MSHqQY8oTWSUYYCrnA/KehPdvkQyBXQIT/JBPB23rvFGj6oOJz9AxN7UAYGRwCBTeBNeHNwn+xU5jo9XPAu/iNYhiDMAbqQsaAELKxee77RB2aM4IllEMQmpiE8ahABBOIDgAAIwoIl+OA1I/CEIO4BQbatImxZtNZArtDEynAFC1zwRBGbdQt1THB/BrnFB1iBF65YYBOowF2z2jCG6VGPCGokyDcUoICuOEADFtBAErwmgqJlUWEHaYMVhqiXoXjCAq9IgiCR0wYpwO54i8NdBT4wxq/kIgqvQMUcMVRIS4qvHBtBwSa4sBejaCAKk0hGCsJVDnXULImcsAktgsGDy7TgFbk5B1QcdYtS1ItoOgSAC2vSBixgIRiYMco5bEAIePQCErOkSjUMRrOwobIAoyTILUjAA3CGJRrw/mAHBoBgiSjg0ib98EW51mDAUxoPAK5qSxu2kYZ2+IUs0VACO9BAg26k4xrVCAQUAtSGW+wgENVAgxj8VIIStNOdvINnadqQhGS0wxt/GQtncCEKtNAAFuYZ05jO9I6enhSlxhNBPDWyCCMQwhFjCQxnPAOLDohDHKHogT0YwIB6iEEGlqLXMd1JgWHpCgppmAQhetGb3wQHQieYVISkRS97Fa1okvjnICFhBEVgABedOauSxuSnvgLLZG69lynSORUWFAIIZ6EBcNAqIb5qtWZ/RYIvEhmyW1QDHrhQg2HMs1dK0etX9AqCKSgLtTYkIgo0YAJx7GEPYmSDA5fgJMCZzgQsGUjhAUONmTwAEQU0uCEe45gUB8RADHxQwxpQ0FVAAAAh+QQJCQD/ACwAAAAAQABAAIcEBQgEhOqMiowMRGg8rvRMVFwPY6REgpwEIz4knO+EzuRsr8YkQ1QEZLFEm8wkJiTIztQkhswEMlRkx/cETIYqerIEEhpku+EQdMRkm6nc7flEbXk0YnwMkPZ8hIwkUmwUIywki9ZZwPwppvwENFysxtwEVpQYGBx0vdJUsur///8UhNiUqrwUUnxMouwEdM9sbmwscpwEGiTE4vx00/wcRFs0mtyE3Pc0ksw8TlwcMjn09fU0U1wUV4cEKkQkSlxMeIccLDMUa680grR0yuhpp7x0uvRUuOwEetQEDBMEivVEZmx0tsxUpswWPlxUkagcO06MmqQKSnocbqREhqQEFSZkwew0boQ0ldcEWKCsqqw0eqTc3txMtPD///8UUoQ0fqwcjuc8p+r///8citlEpuT///80ovFUfoQMiuxUhJIpovpsr+YEbcFcmqwkOD/U1NgEUpQofcEcdLTr9fv///+s2vz///+M4/w0XXAEK07///8MDg9UlrCkpKgMg+GsusyQvNz///+80OD///88PkT////AwMQ0MjT///8kWITE2uwkluyk0PCUttTU5PSUlpyUprSUy/c8doz6/PwcSnDs7uz///9Usvx0tuxUirRUpux8enz///////85irxUosS83vxkorhEtvxQWmQkisxswuT///94ipxMlrzM5vw4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i7GELEQcihDKF1rQA4pYBCOjCggAIfkECQkA/wAsAAAAAEAAQACHBAkO////BEiAjIqM////TFBUTIac////CSc7jMr8RKDXJCYoK0hR////xMbMJJ71bLTJ////ZMf5KonK0eb5ZqCyJHe3BBYjBFiabImc////DDNLB3bLNFdfbLXsKI3YNGqEFBka////bHB0SJLI////pMrsBDxpFCcvzNrk7vDxBHnXFFmMF0lqBBktKFh+ZMDvJJfqKXapJDhD////dNL8////UpSyNJ/nrKqs3N3gZKrESHqMNzc5FIffUKzc////dMnpHDM6VKfsBw8UVLTnhNjyBG/EdMDXOJPK4efpBF2mZJmnKmiUcXuEFCw/FHjCd7Pm////NFp8NJjc////R2Z3rNb4////BCA3JEFOPKL3XLrkVIeXWZ+8JE9nfLLcNIa8dKbUVG6ETJbAHC40////BX7b4O/8HGaXBC5MDB4kFCAnHCgx+vz8F094////HJDk////////FHC0JG6kfHt/lJuf////////OH60DBgdMmByPJLcSq7upKq0rLrMvMrUNHigVH6M////VF5ofL/4v8LEhNHmxNLgpMLcpNL8////LH60PHCIlLXOGWCZPEJMbKLQbKrcJDI4XJanvNrxXI60////pKKk////VJjQFILUNHGYlM78fIqXj5OUVFdZVI/BXKvOBHLN1ODw1NLU////ZLrn////GX7MWKbMF26s////HDpKBDZgDF6cKJLiDEJo8vb5////PKbp////TIKcit75////cYKR////dLrMPH6c////RIqwKEhk////////////////////XJ7U////BIHm////LGKUXIKk////DI70LI7Mb3Z8ebrpOGKArLK8fIKMlKKspLLEvNLkbLr5HDNFDEh4lMjwTKDQLJ/2bMjwDFmUFDNFPGZ0TKr0rMbcDDxf1NfcHFaEbMLnLJbkLDhIfNTy////bKu8HIfZPE5cfMjhXKbkXLLcDG64HHe6PJrYDCEuLEJMLFFhPIi0fKXHXGp0HCAkLHCkPHaUzNHUrM7s/v7+CP4A/wkcSLCgwYFKAGUYNAXYuA0bvrlqZ+WTNCUHM2rcuNFNogzKWnyx16EdAwZaZrhSSekJijUznDhww7GmTYEq8IEoBw7ciy+yXr1SQ5RoFgRZjqJAwQbmABU3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LCcwLHxWmdCAwmhNe1oZkEMYwpiCNxJhUkxJohbVmIEuDuALaDhgD80Qww+w4QNYeEAYZ/CGNzzAB9jySgO2yMMPdHGPLdRjC0c4wjGwUQ1BmO5JAQEAIfkECQkA/wAsAAAAAEAAQACHBAkO////jIqMBEiA////TFBQDGisRIuvBCdEhM7lK0hRRJ/XJCYsBGi4xMbMKJ72arLIZMj8BzZb////0eb5JHe3CxcbBFCPLFdsZKCybIacRG55BHbMbLPpJIzaFBkaFCczcXR3NGeCUJSy////BDllpMrsRKbkBHnX7vDyJlh9zNrkJJjqHFJsdNL4BB40BFyiZMHz////FJL0JDdE////THyMLHWprKqs////NJ7mFDZJ3N3g////FEhsdMHZ////Z6vC////////hNPq////BxAU4efpCx8pHGCSFD9ZWbPgVK7cN5PMOGFxdLTs////VKfsODg8////JFBpGHK0dLbMbJKsVGyA////rNb4BX7cPGKEOZnZVJrBdsns////O4e0fKXHOJLYBj9r+vz8KGCA////4O/8V730lJyjVF5oVJ68////OH60////HHe3////////XJWkBGGu////////////bLrWKZLfpKq0er/5PHSP////GU5x////rLrMvMjQVIugVIKMfJKsv8LEJDE6////pMLc////////bKPPdK7clMjwXGZsKH60////lLXOvNLkFFaMfIaRfHp8vNvxNIrHpKKk////VFdXPFhgFEJkj5OUPEJEbKrc1ODwNDI01NLUZLrkFFqERJrM////hNr3////VGZ8VKbMGX7ICE6EDC5ETKbY////////b3qETK7s8vb4////DGKkPKbs////fM7kerrqdLrMaJq0JG6kPI68////jMr8KEhk////GVWCTJPPFInl////FGKk////////BEF1LGKQTGJ8////////DI70LI7MNqb2////rLK8lKKsXJ7UXJqspLLEPHqcXIKklM78fI6cbLr4VJjQDEd0FGehDCg5TKDMbMfuNFpsTGqELI7cHCkxPGd3rMbcTKrv1Nfc////fNX0DFyYbMLoHJPoLDhINHOcPE5c////HDdFHElkfMDUjNXmHDE6HD5RXKvPfLLkXKrkLFBgfLLcXGp0/v7+CP4A/wkcSLCgwYFHcMTSJAXJBwAAjHyQoikW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aYlTtOQUVDhDAuJRjGfY057P4AMtzqYRawYhHsqop0SV8YxiHOEF+tDHLhIhCgzgwDE4wIAoErELfZzhBUcohjXOIFGJ8iEI8OwKJ6iQCnKMo6US7UY8pmHMbiSACBL9AhGS0I0gHGEaR3gGUHFKhCOkggrjNIgjEoANdLT0CxIlAiL4cNRpePWr0yBHB15w03piFasBeAE2EuBLFmGgG+goBiIC8IW61pWuZ+3DOBKQgHEs4ayAvesXEuAFdHTjlE/iRCISUAsvzNWukLWrBjQQ2com4Ai1WIY4OWUGNCyjFvZAx18jS1nJfqG0de0DAeyRWTSkEVhmmFQEKQgwA3vYABFLKC1lJ2vXPpjABjPwAgGWMYnX1osTLkADKRABXB7YwxgPqEUtHmAMe/BgBrclBRpEEdUxmYEOaAgCIhBhgvKaYLlBQAMdECuggAAAIfkECQkA/wAsAAAAAEAAQACHBAkOBIfvjIqMBEZ8////TFBQR42xBGasBCZDhMvfRJ7UJCYsKkhS////xMbMZ7PQKZ/1////ZMb4KInK0eb5Z6GxJHe3BBYjRG16bIecBFicJFZ0bLPpFCcxBnbLJI3cbHV8JGaMSJLERKTkFBkZkazBpMrsBDll7vDxzNrkKVd/BHjX////dNL8ZMHyF0lqRHuRKXapFJL0////ZKzE////BCpMBBktrKqsJDhCNJ7kGzA43N3gVbPjTJK0dMfhBw8Ub7/cGzhIGYfZh9ToVKzc////4efp////JGeUVKfsVIiYBl6jdLTsdIKR////rNb4F4LUNzc5VLv0CXC6PJbMLIC4VGyATJi+Bj9rPGKENJjcFD5UBEyHfLLcfKXHNFZcPJLc+vz8////KV+ABn/bBCA3NKH3////////4O/8TGJ8FGagDDBEV6HBKVBjPIasFHbEJ5LhFCAmGU5wGY7olJyhHCkxfHp8pKq0er/5VICQZK7M////////////rLrMvMjQPGZxfJKsOHic////v8LE////M3GYpMLclMjw////////PHCIbKPPVFhalLXROWBxJDE8vNLkfIaRWI6wvNrxXGZs////pKKkSazuHGGSPEJEFHK0VJjQFEJkj5OUjMr8VI68XJSk1ODwdK7c1NLURHKCZLrkZ5qx////dLrIGG6q////F37KfL7UbLrY////DB4kcHqE8vb4TIKc////hNr0////errq////PKb0VGZ8////J0lkVLTu////FInn////BGKu////////////BEJ1J2GPBIHmXJ7UGlJ+XIKkNGqELHKsSKbcLKb0DD5cLI7MLH6srLK8lKKspLLElM78fI6cbLr4NFp0DIbkDEZsDCg7bMjyDBgeTGqEDFqcLFdsLI7YdHN3rMbc1NfcfNT0bMLoTHuMHJTpbKy+LDhIPKHffMjeXKzQPE5cXKrkfLLkfH+HXLzuXGp0PJrcHD9SDCAuPJ/xVF5sHGidHHe4HCAkbKrc/v7+CP4A/wkcSLCgwYFHcMx6JGUOEAAAgPST8mgWjiMHM2rcuFGMIXILIIocCaSkSSCSyDkQw7GlS4EoBIQcSZPmyW/fgOQQgOKlz4Io8DysSfThyQslb8z5NodSz58uxYAiQbSqUZNIL2gFohRfBzssoWrkIaWqVaxAsqbVeuOGV3zweIg9mGmoWYglceKUhfPGtws3AANue8NMh8N55g4UQ+4uXrRp1/pVOgcf4cKFzZjZ0c1JWKi0Hjk2eeeSHUPmUNASg4KHITv1hCg9jE+zbTMIduwARwt0AbMm58wy9dKUkzbddtxBoJk5AgRChKzp/VLM77N37FD/SauakG45dv48Hw9dCLjPHBvbFXnhL6Xtc2lh69YmRzcENvLbuHcvg8u6dj2EFBDsEKdYQeb4IsQ9bejXIANcWMOROTlFdNMF+sB34EC0XCEEhDacEOIJDHwiV0ZicHKSSbJcAAJ6GxIkRgZtgCjiCdG8oQWMA9mxYlp/gRAjRxlwAUY0JyR5gi8vlHAQCkuVlJVf+vA4pIxXfAKGkklGQsdTBM2SU1aAzcGOhlcaRIsKb7yhZBYviDMIUEtpZeccNxiY5kYpcKPNC1kEmkUzy4D5DzYk2MmVUpTs6dIgdAhyzKTH0EGGkwKJIckcbAF2xw5oOqrmMpFscMwAqGKATFgOfIMPW/43dIBPNaK6VMIyp6A6QBfIkDGJQLPMYQZhZtzRhpW1FiTGMo1s0MWzXZySikA5dNDWsN104ESyLqVCRiPQdgEDIv8c4dWwmu2Aj57capRCF3s8q0EXiOxzBDX4dHNbdO26tEk6ScyrwSaIlOPEDrd1I8QV/baUCiKIaCBxOLV8wYt4uCHQxg60NrwRJEnUIrHEBogCjxAZQ9dGIB5vNAkbS4yswRJW3JMDeQyS0rJGR4QzChNAM2FADNHhlx8DbYS68z+0MJEKG0APY4AFbdyjnw1II7uzGEyMwsowYBvQSdX5tXECPCdo3bIYw1Tw9QFS88MFAyGG+EYbhi7NYf7bnRzgNxb8+OKL2SHCk8WJehOkBhUVUHGA455YoY2bSr7xCcuJE2QJP40/fsA741yx5YhvNJk5QZBYQcPjVAzDhyeDvIFkki9ow8zpAykjAhZU9L7CO/IAQgeggXITyTK4C+QKDdJQ4QEVQ2BhwhFxBnpMFoLQkQLuFMSxiivPrxDGOGr8o4IgWUw6ABlkzHk6B1XQ4PwKVDwwjkCpRILqqbguo3S/YvDAA6qwAg+sYAUc8IdAJrEBMqCqCwNoxCYgkTlFTOAVBzxgGNphiUyR4RTh+tcm/pcsWkThAUUw4AGDMI7PlIBQ4YJBCL6gt1yM4xVRyKAciqAHgqBgE/4w6ILANrGHcOisZRRYQRCesYIylGEFqJCDhhzRCGQIUYiEgEEMSJgmWoyjCEFwYhmSIQd65MIgKECGyGS2BEKIQG1DEoMStpCAD4xxjOiQ4kEgEQNCjIwJbBiFMxzRLw58wB3tGGMyktEOTSgCRXBYAhs0ELQYeM0RcJyLGDgwBHf0YJGLrMMP8qE1UuxjFMOIGhOsUAELeIKLoFGCHNxBD1AmIwA/gAAFOJKIGBgAbMBkJRyssMsrUUAOOiDCL0AZgGTQYxfXcIkj4PBLv/mNHzTgnTxguRFa6GEbPSDCCG5Jzl9MIRvVEYUPDOC53lFBGeuQBj8Uwc2C0OIaH/7YwjncIYcANNOfmpCAJjLJod2JoHWte94ESkGDCbiiCcVsCQVyEYwtSECZ/rylP38h0HpyyBNVcMPznuc8D1ThFQ9QgCuGEA9FWEINqxEDLdRgCUXEow7b0IQ3iECPOmjUnwGQwBQ04VEZ+WMC63AFFejngaauoArvSAAsilCFOkQhGYoMhjra8YsfEKEFmggGUIEqg3PsIhsE1YgJXOGPMBhQhSrcxhbA6A4iJMCuRMgrLTWhjp+OVRO3gEA0h6SGMCgAFhMoYAYP+ESsbqMO6oisWMk5VqBCoAVTOEP50iQGC8aDA3I44CIVactF/rOylm2BBASb1p+gIBcfKGTCD0YQjBWMlpl+rawMALtaPRRVLN7cQjuC8ANNbGEbzBzrLWUAjSm0AKzQCMVvDyQGS2RjCxD4hTfO0QJv0OO7LpDAOW6BWWicIRsUmG6a1ACFXChBBxCIr3zPoAk9QGGzGwoIACH5BAkJAP8ALAAAAABAAEAAhwQRHRSK5ARQjYyOlExOVFSMoARvxAcvTFyu1CROZJTK9P///yQxOTyl5nSyxFSiyQw+YQQeNERwfjyKuARZn8zU2AR/4SSCxCxed2x5goyy1Nzr9wQwV3TA2nCgxFy76RQgJmyKnER/mSRzqTSU2wRAcARksKyvtymX6BxgkBRwuCQ/SRQvQDRfdAQWJBRRfFSh4v///wQiPBSAzP///1iv3czg8HTC5Dk/Sf///0yw6ZS/4fT1+BQ2TCeN2ECXygRyzFxufDR+p0SGrP///1SWsMTM1Eyl13Sz6SpIU////xxAVWy822yXuCR6tP///xQaHBRYiP///zRvjGSjuRU6VHvH2myizBQoMwRqvP///yqS34yitFRgbEl4hydjkHSCkOz0+f///xwwOzRqhAQoRjRYYwY2WyR0uCRZef///////2ywwAwmNKSyxHyQoFSEkP///wdqtLzFzhR6yDxoeFSq1hyGzKTC3EyZxGSYqMHb7wwpPwwSFQRWlAx2xKSkpESSuKzM6ERpdGyquFR6nIyarHS3x4y23LzW7JSnt9za3Hx+hP///3yozHTJ6QR41fr8/Eyp9Gy69CQ4Qf///+zu73ygvFSCqHyWrCqGyP///////4TK3FRqfFSSpBxGYWy20kSq7FyqzARep9Ta5HR+jFS27NTm9DRGVDyGrHy67Nzm9HSq2CRqnMTi/KjS9Hyu2FSW1P///yRJXUx4nDye7AQ5ZWy08P///1BWXBxqoBqG3Aw2TDSKyCxmhKy2xEyq4GzC5GSqxEyeyP///xyN5AxQiAxvukSk31ykwBQ/XAwgKwyA1yyBvpSuxHzA0XSMpAxipBxvrCw+Sf///wwYHxxQdFyg2ByByNTe6Xy+9Bw3RkyErFyYrHy06BxZgTxxhxwqL5SgqXyEjyxZcsTGyBx5vEyOrFx4kJS41gx6zBxmlKS6zGzC7Dx+nCx6tDxqlJSanAxenOTm5yxqj2Sx0SxPX9TW1HR2fOTu+mS87EyAlix0qQxCbP7+/gj+AP8JHEiwoMGB9E6Y6oIDC4gI1piNSdUFzAl6BzNq3Lgxkrl8DAAAsAaFGQiTzLBgEcdCHJ82K8AYicSxpk2BlgaE7GONpwuRP11EEBohgow2Y7iNYUHLkKWbUAtaYtSzTx+RQF1oHcq1qFEZMsqMWcGtypunUW1GkgfCKtasALYS9fq1jF27PVasWKaIZlqNi3AAuPo2q9y5X8GWCWuXw4FeSZa00Pb3IKDBhbFqFWoSyxhxY0IvPcDnLocyHFJzoHUPlJvKAyPlw5w5rjUQDpm1KQo2LB8W3PRy64Va9RkOEMyAiuY3LQ9dtDOjjDAmyLg5i8JEimRpkZFxQZb+HOCWhFYP1bfS3yqXoBAP5wSiv71tDYvMmxWiLauS5B6EM2eot145Xrx3UyTQEVYYCNaMMY6BUfGgCChLmEGLgCWUUE45tTTHUQbyidQHFBGQA2FlPGQCwT1m+JPhixhg0IRNlymIlTXWUFMBbAaVgsE1LYDyYobxgNMOR6X0lBkU1nRxIo8D8VAIKHVck6EAJQggATiUZRQJDjaKBIULGXgIJUGRNPHCIOBkmWUUXsRj5kDyhDmSNRmcyZGaErwgAJYC/FKPOgdZ0lZhzDDTxZx6oolJGhIc8+ef/LAThkFUFRYRNU82ahAP9ZARzqQCsPPOJVLhaBgWzOzoKZL+x3jxxZ9++MHPLpcONIA1WsXlQqLkvGrTJezwI0CtfuzyjgaxMWCNr0KBMEanwhoUSQoi1OMHBdwWAI9f5rjA62YnjVOtTep8UcC23ArRTyICZTDmZhFgwQK15xbEgzQFuMItKfxeIRAlIMhlEhj52uRBPwVQQMrDRcDzDz2/blWvDK4mvJEN0uhBigkfq+KEPicwY01X4nCjsU27fNIPyCagMYEgYDxUlAttYBHEyjXJMoQqJgSNTBGtdCFOV22wYC7PG6kDTxFBm5AFFbIkIc5cLLQxB9MbJTINFUFnkYUyvnzmlQxj8LEI1xptIAchcoidxQPOqJQYJWXgy3b+GG+rEHcWxFyQ9N1lMMr2P5GYQMg5WRhgQB7ZJN1bGSsUfvhBkWTBxjmOG0BMNiyMwZgMKxygN9dhIOMAHZ0/oEkVlDBG+QGsXG4QK8gcggwQvNvhwz1437VCD1vbTtAe2TjAO+/2kOBJ8HYtUYUixhO0gyb2GMA7JKHA8AY3BxTXAy3pVD8QNsQ8AAQkQKxjDxJucHPeAam1Zr5A2TS/PiQ+HAELPb1YQnE44J+M2c4GdIDGDNgHiWRsQR//uEcSjEMLWrzBfLj4gT0gwUHubUEgIVgBBDgAoOS84HQJiwQd7EGMDjZjH7gQiBGWsYTjpKccS3iG8RTgA2g0g4P+FmiALfYgkEgkwAwCAkULroHCakViBvY4AhAh0YEtNIcLtFiGekqwRFQd7hs+sAIvIGEBC2zhFDsgiCVAgcQS3KIEVDpGKdiGinV0IBlkJOM+tpCrgTThHi94Y4Yw0IJ6NFFPPOhfB/JoARR8YBUGCQOQhiSAQaQBE4bzVCRgsIVOGMMCZLSAMPh4kGfcIw1XKsELvBCFGWkMF3ewAh7LGEQdKMBL8ajDC9wkAHB4ARxNyCSPIoGEWNqBlhYIwCNskUltgGNUWfrTF/iRggIcEiqbNAY0ToFMCzwCBajgSDvAUQdASRMOruhHl/SECh+QwAo1ACUtP9AAWNikCb/+IAOpBFDNd8zDEdf0EhKaYQdZdlMHp4ihWjAxhSlMqlbzEEEB+rGLHQQ0NgrgxRY6cAMfdFMU+5CEMPXVjSmEY1vsokA/PlGAEaigFTa4iQ2QMAMf2KMTR+imBXTwAZE6pxv9EEFKHWYCIejBG0JQwTmwsYNEbIAH2+HBBvawgxpkYwa2YAI8eaHTD5zCp3+JhAfqUQBpOOxhHzPB0wihjEA4IxsqWF777kACYuDCCkwgRjN0aoxHiGISI+XIDlxRACE8LGomiJscnBEIKjjgEJA9BDQmGwo72OIOoQxlGUXxTXvqiRVCEII3RpBWqcnNAI37wwyywdofdpCRtESTwSN0YIsNvCoS6kBDIIqABtOe1nHLe+0UkYkCd3wABQoI7F/C4AjdUsEXKhBb54K7PyDSMgANeMRxt9HHfIUBEc7whTKU8QNNrCO4woVEM1AQDGEIowHIhSDXIrGHVmhCEz8YRSjsgQA7+NcOH2DCI/YRDBSQABevUG6+9CGIb8CABD7wwR0ivAUSwGAVsJAvlAICACH5BAkJAP8ALAAAAABAAEAAhwQRHRSK5ARQjYyOlExOVFSMoARvxAcvTFyu1CROZP///5TK9CQxOTyl5lSiyXSyxAw+YURwfszU2AQeNDyKuARZnwR/4W91fCleeSSCxNzr9wQwV3CgxIyy1HTA2myOrFy87ER/mRQgJiR0rjSU2wRAcBRvtwRksRxgkCQ/SDRfdKyvtySW6BQvQFSh4gQWJBRRfCRQbBSBzAQiPMzg8PT1+HTC5BRkolSavFiv3Uyw6TlBSRQ2TDyWzCeN2ARyzMHL1FxufDR+p4zC9ESGrP///////1SXsUyl1////3ez5xxAVRU5VGyq3G6Zumy82yR6tP///yRIWRQaHBRYiP///zRvjGSjuSZkkevz+X7I2v///4yitFRgbEx3nxQoM////wRqvDRqhBwwOyRZeQQoRiqS3jRYYwY2Wyx0qRR5xFSp1sHb7////2ywwEl4hwwmNKSyxHyNnFSEkP///zxoeByGzP///0ycyGSYqHyBhgwpPwwSFQRWlAx2xFyqzL/FyqSkpESSuKTC3KzM6ERpdGyquFR6nIyarHS3x4y23LzW7JSnt9za3P///3TI6AR41fr8/Eyp9Gy69CQ4Qf///+zu73ygvFSCqXypz3yWrCqGyP///1RqfFSSpBxGYGy20kSq7NTa5ARepyxmhAhqtNTm9Bxqp1S27DRGVDyGrJTG7Hy67Nzm9CRqnMTi/KjS9Hyu3CRCWFSW1Dye7AQ5Zf///2y29FBWXG96hBiG3Aw2TDSKyKy2xEyq4GzC5GSqxHyGkP///xyN5AxQiAxvukSk31ykwBQ/XAwgKwx/1yyBvpSuxHzA0XSKnBxwrAxipP///1yg2AwYHxxQdByAx9Te6Xy+9Bw4R0SXxJTB50yFrFyXqnSo0CxIUhxZgjxxh5SgqRwqLyxZchx5vEyOrFx4kJS41wx6zBxmlKS6zGzC7Dx+nCx6tDxqlJSanAxenOTm5yxqj2Sx0SxPX9TW1OTu+WS86kyAlgxCbCw+Sf///yxScBxknFyWxP7+/gj+AP8JHEiwoMGB8Vbo6eJtTItkcFowoddJTpx4BzNq3LgxEqBclF4kS/ZFnLgvLVqMoUQpBSUmPBI4AxKJo82bAi0NYDBtiohk05JNGDp0xowySA/wWOJNypIYXLLgnFrQkh4RL0RMe8F1wguiE4yWOZq0zIZdS85I+aRJKtWbkd6J4DNtGgAAXL+CNUoW6Ya/f2tt+DSOHzVmNd9qbLQDgN27eb1KLnq07wazG9BsqMX5kwoy7lopPhjoBR8+d/HinQwn4sqWKWIvYaIZDWfOJXKPU/FN3eiBkS4AQJ0ab1eSJ7/s2YO0zAGlTKTQoycFwu1auUvAKPTNSeK3NXD+DS8eOZk4iNiChANkrUakSJZaAWJk7hMEKWfOfMqeW0AJcGJsUwN4BIxXHF7J/NRCLhLgJMEH+XimAgzZ+SeAGO4QMSBOkYhHXGpf/TRGOBtSFQkz1FBTBwYWWmgFONt8x5FwH6aWYDLBlDhaDZfkU0chFAogpADgWMHBTYEYWJxW+jT4W0HWYEHGGygMKUAfIbgyCEeinHYgAMm80IWOTw5UAyZUUClkH31UUMApomUUyQ413jXFC7nIWCZBkTjxzRxVslnBDQWooudA73zp2DS57MmREyjMAU+bFVQghBDnHGTJFHXS1cWhjvKJiTwFwFNpBaNwM4JbBOmhqAj++pAZqkGRPBNCCKiOMsoIRMRS1TR1TjGBk7NuZA08BaSB6gkn4LAqQQN8WVcwxd6UiSvcjHKCtiMIoghwDNQpQguyVmtQDc8UIASzzB6zTGKAKOlYMuGYe9M5I3DDbhgU8MKGQBfUmNUX5dpbUCQmcNPOCWGEUcwxTQgUbnEjNWqwTd2oggfDDQOzyT/xOAZZVsNebBMNpRhSTMMGZLOJPSuoltcXY5h80zNXLBOGAQZkkA0sudwZmQhB2GyTNIIIwrMB6PyhRBd3GjdSvUZvdA4vx+xswA+guKCPCKu98EUygFS90SLkuMHzDz/MQ4I4WHX1xQSNmK2RBn4k4gf+2z8g4EOCAHj1gjgzFGx3Fnmr8QMkP6zhQ1BdTTDGBKDa/U8kBiRSDeOQrGHHBCJMNsYMldsdyQ+J2AHJ6o6LEDpRoxtuNuLNyLA4JDn4MMbcsJdRt+UGaYBOM2qsjrsZqfA+1Bh7lA18QWxU04zxkPxCQhfiEDVDSlQ/P9AQJDyxuAWQPOKCM1/MENYMB2BjjvcESYPEGqtboMw9rKygnFh7pLAE/AOpxhN6QD4LmEEHsIhHRMRShhQcgFjPo4EutKALSFjAAr5ggT3+IQsGjAUp2MCGMwCohB48wYIWtJ5AcoGN5pRhF95AhuwuFgllPMEYFrRAANZxC4EAISX+LpQCExjhvVX4QAvKuKAFGtCAVwgkErJIgXP+wgRvfGKG5oqELuaRAyVa4BEs+A4isLELsxxgA/RAhiaAxwozUFCJLADBEAhiCR6kYANn3AAEzgABUdjNFMrwgC+8uA4WsEogzpDFLgCDBulgAIuOqoEP1mADL8bxGgbJAlMyc5szUOMQpQtVJFxAAi0Mw4tgPORAGLGET2zGNvlQwSc+YLNbDEMLDfBiKHSwADnVgR7Wwc0n6gCDD4TyN5G4RTW0sAYvDuMRtChdI/JxhtvkhhpAwgQkpzJKH3igi6hkgQY4EgfCYCc734gAGeRBg1mZwgelbKYXQRAKWNzkAwn+GAd/SkCFCLiDGJnYppyUgI41aIEWXrQAKlAxCQ55YRz6LIGFiAGON2ABBecQKHC0UQ0zeOAJPsjhBUMBAkkckyA1eCgGJCpRIWEBH/hIxw040M6b0EAJyvDBPLSABJFeUAclPWlBaoAPDNRBSC0VEqnmkIYbPMMf51iEBtwTiRpogA3akEY11NADNzTDFzJAoRJBgAqTjqZPZIgADKwkqDRsIw+eIEI7nlEKjhmgGNXYBB7m0Yx5GEMZ9VNiAB7RgFsIVSPn+MYb5DEkNlEKGrw6whUMQVk3POABiQCGA3oQ1tsVcIlgtKejWpEGMeADBYLKla60dYJSPIMc5FChw97YxrnAWoAFNtABLUxRrEh0wBXswMcpKrXafTVMa1ujrfHIx4J7zIMFCzgsVSxxiVOwQ13QYC2zwnCCpSX3dqtTRgO+yYJrqNJcWehAGtJQjiOoAgrF4Bhy+YYOHxjjCfNogBmGcF4asqEb7RgBBXBwhWM4AA8IdsAa5gGKeSDBDGZQwiukay57EOIBs+DFMqrB4U1sggQuUAIsNringAAAIfkECQkA/wAsAAAAAEAAQACHBAgOBIfvjIqMBEZ8////TFBQR42xBGasBCZDhMvhRJ7UJCYo////Z7PQKkhSxMbMLJ7kBjZbZMf5JIjMZ6Gx0eb5JHe2BBYjRG16bIecBFib////JFZ0FCcxJGaMbL/eJI3cbHB0R5LBFBkaRKTkBDll////pMrszNrkdNL47vDxBHjXKXapNFZcfLLc////EJL0F0lq////ZKzEBBktVrPjJDhCRHyU////GzA4rKqsBClMNJ7kGzhIdMfh////3N3g////BxAUKFiAGYfZh9ToVKzc////4efpcXuETJi+VKfsF4LUVLv0BG/DbLXsZJioBl6j////rNb4ODg8FD5UVIeXPIas////Bn7bBCA3////BEyHVF5odKbUVG6EPJLcBj5r////////////JF6G4O/8FGagV6HBKVBj////FHbEHCkxKJLhFCAm+vz8N5ja////OJ/wGY7ofHt/VJjQFHK0lJyhDDBEbLrcpKq0eLPmFE93ZK7M////BDBWrLrMvMbMPGZxVH6M////NFp0OHicv8LEJDA4xNLgpMLclMjwpNL8OJDI////PHCIXGZsLH60lLXOKW+kfL/4vNDcbKLMvNrxXI60pKKkVFdZSazuHGGSFEJkfIqXj5OUPEJEjMr8VI/BHJPpXJSkBHLNNIrH1ODwdK7c1NLURHKC////T2Z8GW6s////F37KNDI0LI7M////DB4k8vb5OF5sdLrMTIKcfM7k////hNr0cYKR////////PKb0////OmJ4J0lkKZ/2////VLXv////FInn////////BGKuBIHmBEJ1XJ7UNGqEXIKk////SKbcDD5cb3Z8LH6sebrprLK8bLr5fIKMlKKspLLElM78DIbkDEZsDCg7bMfvLInEDBgfTGqEDFqcLFdsLI7YrMbc1NfcfNT0bKy+LDhITHuMPKHffMjcXKzQPE5cXKrkXLzuDHG8HD9SDCEvfKXHXGp0LGCBHGedHHi5HCAkHFB0bKrcDDJMzNHUrM7s/v7+CP4A/wkcSLCgwYFIqu364q5HD378qgQbos4TICQHM2rcuPHNgyQ25nVjk6OHjR7yUjpw1yINh072oCR6w7GmTYEqPtkA56ZDB5FatCBAsOOP0T8losUYBwwYB3v0aN2cWlDFtVk8Z2mhQSOoUKJFjUYoUSJMmGUx7KlqxomeCqo339xxI2QEjQt4uwYdSvRP0Qhjy55dNoBLGXWPOEmiCVcjEFBCwAnBm9fr175HyZZdRrgwFy4ebhkyhLGxwUxCUk+mzHUrgm54UDpYmaZlmhjcOA/wzEWDBkMGJikyPfCNNACqU7PWMq9kDjx4+OH5AzhaJ33jBAmyx+dz794aOP5hYmGJMVxampALUc/aDZsO3Xp80XPoFK03b1QAqYTNGQcuaqnCyWe++ZYMKVeIYN5NtBSAnHqrXUADG/PgsUs/N6EAxRBlqHIDJwVqEEUUBoggglRxpQfAiqrh5cY8PWCDIlW0SMKJB1YYIo6IyfRogBJ1LLhRCCuyiJyLNHjyFnH/vEGPODdgckYUPR5wgIn72JRJkSymRsMI6aTCZEGnTMICKRZQeYATB6BhygkcmbNeketdAM4FXcw45kC0iDIJFJEks6YTTvQxQWkHvUEFl+rdmYSQewr0hiWtUBDJmgeU8go6cEAq0CeMHimENJFuZAk+FOBDKKEKKLDIQf4qjMBoauBo4mmpktYRiSXxEFpKKQ1MoKdAdISaWjrD4loQLZEogYYTv5bSRjt7VBUqAHdiqOxGFcSDzjfQrrBCHiDoKYCxQlyzbU0ufNPAr+KSY8Srki4QKjgdJLvuspuCUYq4K/jQBmOHoHvHvjUt8o0tAGcBzToVCETkrG7oizBBb7zSABziZmHMB08IZC+XqSVxcU2oKNDAClm0LPA/SKCr7ckaVfAKO1msoIwym/Bghg7GskFzTUzkAUfLykBADCPHkSwEJENz9I4RRmSx8zbeUKIinUIcHLVGi8Dxgc47n7MELLNecMjXGl0yRwI775wCD/k4LQQQbGdkBv4TRWyzcwDeCDMni3cumfeyWSQwhzIBKAOPMCRje8GtbNOiTAKjMB7A405LZjHbbyhTROYBbC4MOA/SeoHhh/O5TRFzNG56PuvVfiferRNkxuvGlA64MOnU7iU4a+c+0CVtFCF7ACn4oonwdoLjtfH/hMIDLpoHoMsmdCSH1zz1UC/QO8TAozkMKVCih/cXuJGD+P/M4cMmvgvTBCMxKycEDW7QMHPrFTDG6HzXBF+Y4R+IkAxl5uGGXVCPGpvwge+YJwyBSEOBeWEDHih3MloYY36+Q581BPKAyeyPKz/BRu4WAQFdTHATm4hYkxAxi7zQoDk9+BzCaDEHbxBjgv66EIZ5PgGOu7SGDd3wxOEowQPY1U8CoSCIChjIla4gwAbdQAHbKrANXNDPdykQxgEJsov+6UULeMhBMHSIK1q0oQa4mKAwJOANg6jAJ60Rykm+wMFIvWEJTWzD8sKoL2zMogN7IYoD8JABmj0BBEXwRfY20QRtZOQN7ugAAhLJDwfwIwN9NM0b9jAHdhAjezDQhS8oB4Ru5IAvROmBA6qgDjbC5Y9EYIf5sncOMXJED64cSliq4I5ODMEcuKoAOeDAjhr8rXQS8AUjbLKLNO7gmkaJRkvC4JYx0YIaWTACO0gQt8YRowkjtMkbWOGQsBilBC2oRQw4IAlbGoQWi/6YADls4QMQxI1xm5DAJkLJJ3bKQyyaiYEggKEPPlhCizapACpeMYE+sAMaOftn+TZhT4LQIhzycMA7yWIWDmAAA/YADSYkUQkk3OcNtEDCJRbBDHysoRF9sIURXkG2v8GjCQMVZQaqkIZoaIYzyzDMIwahDkN4YEojqhI+IiGCGdiiDwrgqbiUoTP5+cIaBNVINogaDMF0xjMeeMQtMAEFUkCBAhRABzoooIRGrAFeW9XZOlLQhmlGCggMLUQMdOMZDYBHA+I4QytaYYcDxGNQeN1qG7yxCTmMsVRvwIY+7AEMPvDmsCKikqAG5Su8tiEPNWiDNsJKFRXQY7MYKGFDdwxboKhaiU2lZZnYUkuJjhLHtR4ogyHUMQlO7IhHo3VCMqA1AZVtDAy9BV0iLOEBDxjiFgbAhAG2W6I6tGMGllDAN8ixh0uwdl1IUAQ9RDENCywWH1M1RR32cILLMikgACH5BAkJAP8ALAAAAABAAEAAhwQFCQSE6YyKjAxCZESy+UxUXP///wxipAQkP////4TK3ESazARlsiRDUSQmJGTK/CSCyAQyVMjO1Gy00GSgtARMhiR4uAQSGtzt+TxkcQSO/Eym1GnC6URtdAhyxAU0XHyEjBQkKyxSZCSK1Cqm/BgYHKzG3ARWlDByoJSqvAsaI2xubBSC1BdSfESt7ESSvDSb3zROVMTi/BRDX3TW/BQ1SvT19XfA1AR0z1TA/CRKXITY9DyVzHyy3GSuzBwsMlSz6gQqRFyasBRutHTM7jSDtBSP8ER6lBc+XBRXhxQ6VAQMFCFllDRijDSU2FSr2QSK9YyapP///0SCpAlqsTR9sQQVJuv1+////2SWpDRTXP///6yqrAUcMP///+Dg4AR83XTS/BSW/ApKd////yQ3Pmy18WeswhRShBiK5Bw0QXzA/FSy/FmiwVh8nCyi9NTU2ARSlCR+wThshFSg4GTG+SSU5qza/AVYnGx8h1eTqjyi4BpJb5Te9P///1y67Ky6zJC83LzK3KSkqP///////zw+RBxyrMDAxDSo+ZTL9zQyNDxyiMTa7KTQ8P///5S21IzK/CRWhNTk9He57FSKr5SWnBxqoCxumJSmtPr8/FSEkuzu7SROaXx6fP///zSKxER+mLze/FBaZCyKzP///////8zm/P///////3S6zP///9/m7CQ+RmS66AReqJTC7HyWrP///yid9v///0ul62y+/BSE4AQsUP///1yu7ARtwQQ8bVySvP///0xyfBQeItza3ITS6GzS/Gx2fGyu5ESKtFSm7Dx2mAyF5ky28gwkNSyGxxwmMEyt5EyWvDyc2Mze7BxFWSxLVIza8Vy143zN6jyErByP7Ex5hwwOEAx+1Cw6TCyW5HR6fzym8MzW3CRtnyxabLzS5AyO7AxWhHzS8ByW8gxSiSx+vGzG7FRSVBRln0ycyCxETAwzUQxOhCx6tAwUGuTs9CyO2RyDzzxKXHzY+wx2zAwsQxxqrBw9UTyS3Fyu3JSdqP7+/gj+AP8JHEiwoMGBrADFctNEUosWSZI0iVcplqN5BzNq3LhRkwQQnZBIEyEug8kmc7Id2bRpCrJD8Yo10sSxpk2BV6LoqCFtmg5pAwbw4jWmaIWjeNihuJalUpV0ga7cnFqQE4gaalqpefeh64ehQ48ejXMCD55Xrw4Zo/ACQg+pVG1q8lfjh5p8uPLi6gp2DFGxceKYRfuKARVmZ9pBgEUzrsZgMUL8yIegct4IXvvyAiz4bGEGDHbtIjVhAT8Mjg8OWhYiBIIuli9/ndGp5Jxf2VQeQYHpAGHDu6js8tBuwghHqQdq8tYlhIousCsHwRVh3zQtIvjwKTqmArwkTDD+hdKj59oh0KJ34cAxQhUMM43j2hilAhh06JUR4GrlTpoSPm6kMA4rNmiiiQ2sjANJL5dcco0QQqQTmmjrbePKE7XEd5MNBcijghVW3FdZVjUgAUIjN0Vzhj7ptEEBBOqtBwYY/VST4VSadKjCBSDel08ZNeyTiQ2OaRKIBYi1sZ6MYABho4YbESOPPEuAGGIXk+UDApTy9YBPGxPUg8OMM1YDhBk2DbIElTz2KJk74CRn0CTMgHIDKWSCEQAH3yC3UTBULnFBlSGGsMwoRMppkCZ0QHADD2QGkIw1s0yikSaGLKFplTw6lweXigqkSTEj3ADDjAEEcI45e4D6jyX+AGw6qBX25REqRxPQc4MdeqaqjDKKHMRJCbFuaoUKKozi6q2i0gGNAslsk2oAO8wCF0HeAFCspsdykyizl45QjSvTBvBGHWtUpU2sxV6AbJzgcjRJMgrAUK451hIkgLbbXiAPCPHaRAkM1qQKBRQk1BGJcg5ou0Sx8vzwbcAa2ZDGDS4YTO0bjSHCb7sXWEKxTYrAYA4UARysDAEyCLTCx5pqE8LEI2ekSRrWfJMyFGLQYItADfOr6QXe1GyTGS6oszMU97zxzxcfbyuB0TWdgg015OycQyJXcBH1Eto0Q7VN2FiTyMFQJJLDHdnCvMQoY9dkBhB/HKxBz2us8/X+EiLHvVEk35iz9A7KLLI3In5vJIodO6DN9DfECq3pF4lrhIERfZBj9zAkrAszAJxUntEV5FBzjt0PkBB1rBcsm7gmAZiuuQZ1qL63637DbrrdtXsueeiiG2QDFLtDoUHqkUtOefAFXd6HEZu/YbjbiDNP0OJYo31PInq73bf1AkWyh+DGQ7EDG20LfQHc4AtkZg5o4+111Nr80L5AZbuANglrQy35oFMD3ynSYDqtJQI1QROaNooGPkp8o2Boo4HT/vEyycmMZq9LAxEydjBy+EwgHvtfyKynCDsIwwg7IwDLRJXATUUMg2O7GQeqYTCmzSI+IJDcmi4AMNFRohv+JzTYLBRGEE6s62FDew4cKncKejljWlAgQr4I4gmHuVAF3vKbDejxhBuUa4jpqooKtiUoZCkrhsdwgjV4Na0wTLEgsJIVj4ChAltRrRj1UAA0ehUAFwDLZpkqlhUu0JwufGpko6rHDZyBqgAYIQyteow8BjWrY/1ABR2AoZxsQAdFPiFSASBCN1CzETXNqk1YCsE04HUrOpFCFXQYE6r+0CebEMNfhLpPlmKhyQ31wAPPUAUPZDkjZ5zpJppYh7+sdB81lCEfQuplxY4khzOcAQJLmtEGnjQVDnmImdD5USvegYRYBLAm0SiGPg7RhjO8IEay3AAQbkSV+SDrPtH+QUAN3NEAJbwDQCkQBCuuYCAEKagX+jhAFfRAgWcMIT0yeoIzaiHNSxGjC/bBj3RwUQMdaEELnejEAPwCmCSEAxmVyIIeikCFCcWIBROAQQ9wx5HVGCqf+snLB2hTkg78wqfZYMkmQhEOdqAFPelxgnH8FCrILOMHy4gNdfgylJG2oBxJQEdgTvAb9DCgHj4wjaXANZe63CWnEcDMV4ZC0rGcoCyEaakF2tCGxdDUMVapQQ1aUYMI7GWtbBVLBQIzmFdQoQhteIEc3jI2TuikBg2YhjRmAJawbGawcXjFJRaqh3hA5Vpx8whIlDASLWRAHE1I7RyQEYqhvgQFZ5gWSfvmoZBsNOSqEGECCipxBhNgJFQBAQAh+QQJCQD/ACwAAAAAQABAAIcECA7///+MiowESH////9MUVEMaKxEi68EJ0OExtQrSFFEoNgkJigEaLgonvfExsxqssgHNltkyPz////R5vkkdrcLFxsEUI9JcXlviZwsV2xkoLIEdsxstewkjNkUJzMUGRpsc3c0Z4IEOGVQlLKkyuz///9MquwEedfM2uTu8PEEXKQ5WWEkl+kEHjQcUmz///900vhkwfP///8UlPQkN0QUNkksdan///////9Eeoysqqzc3eD///90wdlnq8L///8UP1n///+E0+p0x+cFftwcYJIHDxQUSGzh5ukLHypZs+AkUGlUrtw3k8xkmqwmWH1xe4Ss1vj///84ODz///8YcrR0tsx4s+ZcrOg5mdkUgdccaJ40hrxUboR8pcc4ktj///8GP2v///9UmsD////////g7/xXvfRUXmhUnsAEcMcsfrT///8cd7cUICZclaRMrun6/Pz///80XHQkQUz///8pYIJ8e3+Um59sutYqkt8cKTGkqrR8v/Y8dZGsusy8xsxUi6BUgoz///////84frS/wsQkMTf///+kwtz///////90rtxcZmyUtc680NwUVoxso8y82vE0isekoqT///9UWFgUQmR8ipePk5Q8QkRsqtzU4PDU0tREdoxkuuQXWoREmsz///8ajuKE2vcZTnFPZnxUpsz///80MjQIToQMLkRMptz////////y9vk8Xmz///88puz///////9xgpF0usz///8kbqQ8jrz///84YniUzvz///8nSWT///9EcIgZVYJMk88UieX///////////////8EQXX///8YesgMjvAsjsw2pvaEzuSssrxcntRcmqxQrvh8goyUoqykssQ8epxcgqRsuvxUmNAMRnQUZ6EMKDlMoMxsx+4sjtw8Z3esxtxUqO7U19wMXJz///981fNswugckuQsOEgcN0U0c5w8Tlz///9MfIx8wNQcMTocPlGM1OZ8yOQcSGQsUGBcq898stwkaJg8h7JcanQMcMAcICQsQkz+/v4I/gD/CRxIsKDBgUnEfRlmqEIuN24qsAGjDUuJMwczaty4UQ4kSfnysdMBpySJk2p+/IBwT1QzcFgmyeFIs6ZAFV9E3Ak26E8uLiuCrmhAlOiaZU5Q3QKlZU8CWDajFsRpilcwEaEuaL0gNGjRBmvCht3SBF6WPX5USLUp51E9DeGgDJi7lSvXoUOLiuXAAQUKLUSWtOg1c61GHrx+xXqRLNncAXW74tUblq9fFEW0PDsxC6Nhg9Yw2WMiZkTjx5AjcRkpCI60DRvUkNFFaVnly0VyLyHSQsrngXIyBGmHKYJpMadX3dHRkx0Xbgaic7PCRhcZCBC8bVnjN3fuFudm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pdaq1OQMTmyhHeAzFg3ekUD2pbB7lTWrwg5wL80qwRnD0mgGNHo6lUtAQot6xCtlxaba1f5KCSRYRgLSdQmrXWIADGCB7/4QuuYGTmocS2sTBlsCTFw0VpMAxDAq4bskouG7aKCbHYxQCSMYIV/TAAJt+zUPOUACDrdgwDk2JgMjLMMTc1jHPDYVEAAh+QQJCQD/ACwAAAAAQABAAIcEER0MiuQEToSMjpRMTlRMiqQEb8NMsuwGL0wkTmQnb5yUyvQkMThMn8xstM4MP2EEHzMEXqQEf+HM1NgyjckkXoBsorREcYFsxuwkgMBvdXwsmuSMstRsjKTf7PgEMFdywNlUoeIUICb///////9EgpwEQHGsr7c0UF4kQEwEFyQsdagcYJA8YGpss+osjdlMkrxhtNgUL0Jcqs8bgMkEcszM4PAEIjyUv+H09fg5QUk0fqw8m9lcu+pamK4sV2psq7tsl7gXOlQUOEwcTm/EzNRcorwcQFdcbnwYKCsUGhwMZqQHN1n///////////////8qSFP///8ahtj///98yN50gY8aeLr///84dqQ0aoh8tec8pOckl+qMorQ0cZE8jLgkZozr9Pn///8EeNZcipx8usxEepR8jJgEWJw0WGQkerQZZ6AWgNcEKEYaWIAWKTf///8MEhVsutYkhsykssR8wdQESH+8xc4MGB86Z3lctegcMD2kwtx0qtjB2+9UYWykpKQijdqszOhEanRUepx8pshcpOBclsSMmqySuNm81uyUp7f////c2txUgpT///9cntRJeIp90Otcrtz6/PwkOEAsYpTs7u88nuxklqR8l698vvRUg6xUkqT///9Mptz///////9EkrwcRmAMdsRMptQMJjTU2uRMhpx0uvTU5vQ0RlRkprzc5vR8hpTE4vwkkuSo0vRUanz///8kUG8EYa3///8kdbf///9UltT///8nWHb///////9QqvQkSGAUecf///8MhuRseoQMNkystsR8uu98kqSkutD///88XnwMcbxUoMZ0tcQMHygsX3h0x+QsgsiUrsR0ipxMgZf///90s+lkrszU3uk8gKREnNJkvOb///9knrR8gIZEptyUoKc8coxEirD///8Md8xMd58MWpQ8WmQMJzwMSXfExsh8rdRceJBMVlxckqQ0mtw8apQserB0uswshsKUmpzk5ugM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/0mSyaTK7AQ8aRQnL8za5O7w8f///xdJav///yhYfmTA7yl2qQQZLSSX6////yQ4QXTS/P///1KUsjSe5qyqrNzd4BSH32SqxEh6jITY8lCs3P///3TI6DqTyihplFSn7Dc3OQcPFARvxHPA1wRdpv///xwzPv///zRafOTo6HSz6XGCkf///6zW+DeZ2f///////1Sz6BR4wv///xaC1AQgN0dmd1SHl1mfvCRPZzSGvHylx1RuhP////r8/BRPeP///yRBTiRupFy65JScoxxmlwQuTHyy3Dh+tAweJBQgJ0SazBwuNASA5Hm/+RyQ5P///////xRwtODv/P///wwYHjJgcjyS3KSqtBwpMRRgnKy6zLzI0Dx2kFR+jHySrFReaL/CxITR5sTS4HSu3KTC3KTS/P///2qkzFSX1Cx+tDxwiJS1zjxCTCQyPLzS5HyHkXx6fFyWp7za8VyOtP///6SipP///zRxmJTO/I+TlFRYWFSOvFyrzgRyzXx/h9Tg8NTS1P///2S63P///2iatBh+zFimzBdurDx+nP///xw6SgQ2YAxenGa66ieS4v///296hAxCaPL2+P///zym6f///0yCnIre9////3m67P///3S6zP///0SKsChIZP///////////////1ye1ChikP///////zR6pFyCpP///wyO9CyOzDhigDym/KyyvJSirKSyxHyOnAxIeJTI8Eyi1Cyf9WzI8AxZlBQzRTxmdEyq9KzG3Aw8X9TX3GzC5////yw4SHzT8Tyg5ByI3GyrvDxOXHzJ5Fym5AxuuHy05Fyy3Bx3uAwhLixRYTyItFxqdBxRfCxCTCxxoRwgJEyWwAx+3BxhlMzR1KzO7P7+/gj+AP8JHEiwoMGBeKTMIbINx4OHD7ZNszJnEZ6DGDNq1DgG0y8ZOKxI8OHjUBBu3GqQ9FEDzbRtczCN2UizpkBcc6agI3doli4ZO+QEGEo0ALQHBCT0qkFgWwJcNqMWxPWOFjx2P2jJ2cpVaACvRYc+eNFLwoM5UKXWHJPtww8kcPZ1nbv1a9ii0Kb5kLDN00y1GfHwgQO3gxzDiOnOvTsUWgACPlxeBGzQxCoIrDpo3rx53xRt8IKwO0T63AsruugwNoqmxrZFlAeOSTQBwoQjo3J3GNVhHzok7ODpmkJnR5YAO+jg0DXr0DkrDxg/8EFg1l+1uByV4HHniPfco9L+iUKibUKWdtkw4cE1ZgwuPJiywaNDy4rJbXehtQSXNiouffp04d2AR1zBCgR8rJJIBTZV4AE06LDDzjZgDfWCBPxFNYYjw+iThDvuJOEdPRAIsYoi/fnnCR3oHIKGY0Wh8QI4123UCD03iCjiEe500cUd76QIGE47vMCOVnYF8AIas9SkCAyXfChlK13Q80gpsRlUAS26HIKDUF5xMw1sGlVwxiWyJKFmEndY8IgjQmZ5ExG0HIIOV1+dI8NkB43RBhdnrJlEK5dQ0EiNchI0xhO0sPMlVzIEgQ6iA0XSiSsYYLDmJWc0kuhGHtSpFVfggJPNQSqowUWmrB7QTCj+lH6qKBERysUVOzLECYYgQwyQ6QD48HJGnLJO9QE8qBy2FQ7w/FKQCvz04Kuv3XDxB4PFblTKPkhMoWwH5NCSIhggKDPAtLyCkW1NvwiBRGL1nSrQGMqEce653fTAD7HrTpUFBOgkhsQHfyHiwh73DgCJMpH0W1M2E7y7GTpTYCKQKnuQcS8ZPdjDr8ME4XKZEJtlgYoHArmwxS23DHCLCy6oAnJNTwghim6jeBDNP06wMM8AJ5wwwDdkpDAzTRVcEcxuvCkQDR6AeMFC0Cfc8o09R9e0ijq34TYBHP4QMs8tVLPggjNZ05QMgAN2IMo7PXAQy9wneOFFNWlvlM3+I6wQyIMjTawz99xSB5K3RpjEo86OSfAQDQsLrBGL5OucoMPhgbmjTojeOfIILMSsIfka84ijAuYYqZCEBXdwfkM8G5ghuuiCx4r6GKu3ouMwsMMy+xqR24457hbo/iE98XgDCwJraAH8Gh8fjksSlxifxDBnwEJD86LLjiXqBjkhC5prDkPBOks4r4UWsHhjOPgFUfJHJpqqmUkbW9CwvvPeeIM3/ASJxBAOwCoMHCAUlUABAva3hjiEAYAEyUQnBFHAA4CBGgiQx/4QMAlYQHAg0eoVBgaghmYowgnyQEEM9hcHBPTjgynoxiC64SsMDIEfF6GBClcYAxSgAAr+H1SFMiCRsB7gQyC2eIMWYsBELcTBG9Gb2RjIAAmE1RASMvuHA2LgBiYyEQXySAMAI2EPSGDjXi5QBiLmNYk3XOACXkTBEqLYL1zYYw9NaNm5FnadT+jhjYB8gxsqAT5C2GNlQBsAC/agroGowA16MMIFjBCDCyjRFJhLwS324IWgAU0aZDgdQaAgyVJewA0hMAcdP4ULF3iBA1Q7ATZcQIjw5SOSpoRkIYQnqzEMQhwcEEcsOWAPURYkDaU0gjIv8EdbZC0DG1gHCwYXCxZ4oWF9koQRAKDMZf5xBLyMzRgysIZ1mGFyg+NAE4Q3jkhys5tG+GMhVumfMGzgHrL+m10sFnCLy2lkE9wEgEC7GUlzvFBW4yCGN+7hu9+xABbWqMkIBErRgUbyAqSgJ0ZwcQ0ELIEG3mDe7GABiwzYZAwFqGhAlRlJP6RBowPBRTVo4NHtaWGBzVNeGMIZspSqdKDxNMIbbHFQmvQDCmD0xiRQsD6cakF5W4DpP3Dh059uE55+qMcnDKED9oxBBTowRBrqsYQYyMMPfmDq/vbXv6hSZgwT/SlQg7rMbgISjnp4AwreoEE4rnB/S0AAFHiqEWTKtaIEjaceFhuCELjhDX8EpBeZmMI19AGhRTgsRa8KT1NK8q6VXCEY1+HPXn5im5pVKWft+tk3PhYF1SBJrFpUYInUInal8HyjHh4LBWOCTAUCyIdtN5vbSE7iE77N2hgMUQ8GaPaq7zTCJEbgANk6TAc5GEEBihAC1ObVHKCAQh9Km6WAAAA7MGJrRXo5Y2lXNnVmU0JEVzFsWjk4YnBTejVFT1Mvbm5iRHBCSDJBNE5ONjFRSXU2QzdKNElOS3RHaFgwemlKbg=='
-
-
-
bar_striped = b'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'
# list of all of the base64 GIFs
gifs = [ring_blue, red_dots_ring, ring_black_dots, ring_gray_segments, ring_lines, blue_dots, red_dots_ring, bar_striped, line_boxes, line_bubbles]
-# first show how to use PopupAnimated using built-in GIF image
+# first show how to use popup_animated using built-in GIF image
for i in range(100000):
- sg.PopupAnimated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', time_between_frames=100)
-sg.PopupAnimated(None) # close all Animated Popups
+ sg.popup_animated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', time_between_frames=100)
+sg.popup_animated(None) # close all Animated Popups
# Next demo is to show how to create custom windows with animations
-layout = [[sg.Image(data=gifs[0], enable_events=True, background_color='white', key='_IMAGE_', right_click_menu=['UNUSED', 'Exit'])],]
+layout = [[sg.Image(data=gifs[0], enable_events=True, background_color='white', key='-IMAGE-', right_click_menu=['UNUSED', 'Exit'])],]
+
+window = sg.Window('My new window', layout,
+ no_titlebar=True,
+ grab_anywhere=True,
+ keep_on_top=True,
+ background_color='white',
+ alpha_channel=.8,
+ margins=(0,0))
-window = sg.Window('My new window', no_titlebar=True, grab_anywhere=True, keep_on_top=True, background_color='white', alpha_channel=.8, margins=(0,0)).Layout(layout)
offset = 0
gif = gifs[0]
while True: # Event Loop
- event, values = window.Read(timeout=10) # loop every 10 ms to show that the 100 ms value below is used for animation
+ event, values = window.read(timeout=10) # loop every 10 ms to show that the 100 ms value below is used for animation
if event in (None, 'Exit', 'Cancel'):
break
- elif event == '_IMAGE_': # if clicked on the image
+ elif event == '-IMAGE-': # if clicked on the image
offset += (offset < len(gifs)-1) # add 1 until the last one
gif = gifs[offset] # get a new gif image
# update the animation in the window
- window.Element('_IMAGE_').UpdateAnimation(gif, time_between_frames=100)
+ window['-IMAGE-'].update_animation(gif, time_between_frames=100)
diff --git a/DemoPrograms/Demo_Bar_Chart.py b/DemoPrograms/Demo_Bar_Chart.py
index 276e4471..5dbf58c8 100644
--- a/DemoPrograms/Demo_Bar_Chart.py
+++ b/DemoPrograms/Demo_Bar_Chart.py
@@ -1,6 +1,24 @@
import PySimpleGUI as sg
import random
+# Bars drawing in PySimpleGUI
+#
+# .--.
+# | |
+# .--.| |.--.
+# | || || |
+# | || || |
+# | || || |
+# .--.| || || |
+# .--.| || || || |.--.
+# | || || || || || |
+# | || || || || || |
+# .--.| || || || || || |.--.
+# | || || || || || || || |.--.
+# | || || || || || || || || |
+# '--''--''--''--''--''--''--''--''--'
+
+
BAR_WIDTH = 50
BAR_SPACING = 75
EDGE_OFFSET = 3
@@ -9,21 +27,25 @@ DATA_SIZE = (500,500)
graph = sg.Graph(GRAPH_SIZE, (0,0), DATA_SIZE)
-layout = [[sg.Text('Bar graphs using PySimpleGUI')],
+layout = [[sg.Text('Labelled Bar graphs using PySimpleGUI')],
[graph],
- [sg.Button('OK')]]
+ [sg.Button('OK'), sg.T('Click to display more data'), sg.Exit()]]
window = sg.Window('Window Title', layout)
while True:
- event, values = window.Read()
- graph.Erase()
- if event is None:
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
+ graph.erase()
for i in range(7):
graph_value = random.randint(0, 400)
- graph.DrawRectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
+ graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, graph_value),
bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='blue')
- graph.DrawText(text=graph_value, location=(i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))
-window.Close()
+ graph.draw_text(text=graph_value, location=(i*BAR_SPACING+EDGE_OFFSET+25, graph_value+10))
+window.close()
+# del window
+
+while True:
+ pass
diff --git a/DemoPrograms/Demo_Base64_Image_Encoder.py b/DemoPrograms/Demo_Base64_Image_Encoder.py
index bf7a6b44..6ad4dc68 100644
--- a/DemoPrograms/Demo_Base64_Image_Encoder.py
+++ b/DemoPrograms/Demo_Base64_Image_Encoder.py
@@ -1,27 +1,27 @@
+# Base64 Encoder - encodes a folder of PNG files and creates a .py file with definitions
import PySimpleGUI as sg
import os
import base64
'''
- Base64 Encoder - encodes a folder of PNG files and creates a .py file with definitions
+ Make base64 images
+ input: folder with .png .ico .gif 's
+ output: output.py file with variables
'''
-OUTPUT_FILENAME = 'output.py'
-
def main():
- # folder = r'C:\Python\PycharmProjects\GooeyGUI\Uno Cards'
- folder=''
- folder = sg.PopupGetFolder('Source folder for images\nImages will be encoded and results saved to %s'%OUTPUT_FILENAME,
- title='Base64 Encoder',
- default_path=folder, initial_folder=folder )
+ OUTPUT_FILENAME = 'output.py'
- if folder is None or folder == '':
- sg.PopupCancel('Cancelled - No valid folder entered')
+ folder = sg.popup_get_folder('Source folder for images\nImages will be encoded and results saved to %s'%OUTPUT_FILENAME,
+ title='Base64 Encoder')
+
+ if not folder:
+ sg.popup_cancel('Cancelled - No valid folder entered')
return
try:
namesonly = [f for f in os.listdir(folder) if f.endswith('.png') or f.endswith('.ico') or f.endswith('.gif')]
except:
- sg.PopupCancel('Cancelled - No valid folder entered')
+ sg.popup_cancel('Cancelled - No valid folder entered')
return
outfile = open(os.path.join(folder, OUTPUT_FILENAME), 'w')
@@ -29,12 +29,11 @@ def main():
for i, file in enumerate(namesonly):
contents = open(os.path.join(folder, file), 'rb').read()
encoded = base64.b64encode(contents)
- outfile.write('\n{} = {}\n\n'.format(file[:file.index(".")], encoded))
- sg.OneLineProgressMeter('Base64 Encoding', i+1, len(namesonly),key='_METER_')
+ outfile.write('\n{} = {}'.format(file[:file.index(".")], encoded))
+ sg.OneLineProgressMeter('Base64 Encoding', i+1, len(namesonly), key='-METER-')
outfile.close()
- sg.Popup('Completed!', 'Encoded %s files'%(i+1))
-
+ sg.popup('Completed!', 'Encoded %s files'%(i+1))
if __name__ == '__main__':
- main()
+ main()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Borderless_Window.py b/DemoPrograms/Demo_Borderless_Window.py
index 38a4a287..c330d22b 100644
--- a/DemoPrograms/Demo_Borderless_Window.py
+++ b/DemoPrograms/Demo_Borderless_Window.py
@@ -1,22 +1,17 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-"""
-Turn off padding in order to get a really tight looking layout.
-"""
+# Turn off padding in order to get a really tight looking layout.
-sg.ChangeLookAndFeel('Dark')
-sg.SetOptions(element_padding=(0, 0))
+sg.change_look_and_feel('Dark')
+sg.set_options(element_padding=(0, 0))
-layout = [[sg.T('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User 2'), size=(20, 1)),
- sg.T('0', size=(8, 1))],
- [sg.T('Customer:', pad=((3, 0), 0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20, 1)),
- sg.T('1', size=(8, 1))],
- [sg.T('Notes:', pad=((3, 0), 0)), sg.In(size=(44, 1), background_color='white', text_color='black')],
+layout = [[sg.Text('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User 2'), size=(20, 1)),
+ sg.Text('0', size=(8, 1))],
+ [sg.Text('Customer:', pad=((3, 0), 0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20, 1)),
+ sg.Text('1', size=(8, 1))],
+ [sg.Text('Notes:', pad=((3, 0), 0)),
+ sg.Input(size=(44, 1), background_color='white', text_color='black')],
[sg.Button('Start', button_color=('white', 'black')),
sg.Button('Stop', button_color=('gray50', 'black')),
sg.Button('Reset', button_color=('white', '#9B0023')),
@@ -24,6 +19,7 @@ layout = [[sg.T('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User
sg.Button('Exit', button_color=('white', '#00406B'))]]
window = sg.Window("Borderless Window",
+ layout,
default_element_size=(12, 1),
text_justification='r',
auto_size_text=False,
@@ -32,11 +28,7 @@ window = sg.Window("Borderless Window",
grab_anywhere=True,
default_button_element_size=(12, 1))
-window.Layout(layout)
-
while True:
- event, values = window.Read()
- if event is None or event == 'Exit':
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
-
-
diff --git a/DemoPrograms/Demo_Button_Click.py b/DemoPrograms/Demo_Button_Click.py
index 84f16376..56c68d2a 100644
--- a/DemoPrograms/Demo_Button_Click.py
+++ b/DemoPrograms/Demo_Button_Click.py
@@ -1,33 +1,37 @@
#!/usr/bin/env python
+import winsound
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
if not sys.platform.startswith('win'):
- sg.PopupError('Sorry, you gotta be on Windows')
- sys.exit()
-import winsound
+ sg.popup_error('Sorry, you gotta be on Windows')
+ sys.exit(0)
-# sg.ChangeLookAndFeel('Dark')
-# sg.SetOptions(element_padding=(0,0))
+# sg.change_look_and_feel('Dark')
+# sg.set_options(element_padding=(0,0))
layout = [
- [sg.Button('Start', button_color=('white', 'black'), key='start'),
- sg.Button('Stop', button_color=('white', 'black'), key='stop'),
- sg.Button('Reset', button_color=('white', 'firebrick3'), key='reset'),
- sg.Button('Submit', button_color=('white', 'springgreen4'), key='submit')]
- ]
+ [sg.Button('Start', button_color=('white', 'black'), key='start'),
+ sg.Button('Stop', button_color=('white', 'black'), key='stop'),
+ sg.Button('Reset', button_color=('white', 'firebrick3'), key='reset'),
+ sg.Button('Submit', button_color=('white', 'springgreen4'), key='submit')]
+]
-window = sg.Window("Button Click", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False, default_button_element_size=(12,1), use_default_focus=False).Layout(layout).Finalize()
+window = sg.Window("Button Click", layout, finalize=True,
+ default_element_size=(12, 1), text_justification='r',
+ auto_size_text=False, auto_size_buttons=False,
+ default_button_element_size=(12, 1), use_default_focus=False )
-window.FindElement('submit').Update(disabled=True)
+window['submit'].update(disabled=True)
recording = have_data = False
while True:
- event, values = window.Read(timeout=100)
+ event, values = window.read(timeout=100)
if event is None:
- sys.exit(69)
- winsound.PlaySound("ButtonClick.wav", 1) if event != sg.TIMEOUT_KEY else None
+ break
+ if event != sg.TIMEOUT_KEY:
+ winsound.PlaySound("ButtonClick.wav", 1)
+
+window.close()
diff --git a/DemoPrograms/Demo_Button_Func_Calls.py b/DemoPrograms/Demo_Button_Func_Calls.py
index c4d62a71..af25aa9a 100644
--- a/DemoPrograms/Demo_Button_Func_Calls.py
+++ b/DemoPrograms/Demo_Button_Func_Calls.py
@@ -1,10 +1,6 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
"""
Demo Button Function Calls
@@ -18,21 +14,24 @@ It is quite easy to simulate these callbacks however. The way to do this is to
to your Event Loop
"""
+
def callback_function1():
- sg.Popup('In Callback Function 1')
+ sg.popup('In Callback Function 1')
print('In the callback function 1')
+
def callback_function2():
- sg.Popup('In Callback Function 2')
+ sg.popup('In Callback Function 2')
print('In the callback function 2')
-layout = [ [sg.Text('Demo of Button Callbacks')],
- [sg.Button('Button 1'), sg.Button('Button 2')] ]
-window = sg.Window('Button Callback Simulation').Layout(layout)
+layout = [[sg.Text('Demo of Button Callbacks')],
+ [sg.Button('Button 1'), sg.Button('Button 2')]]
+
+window = sg.Window('Button Callback Simulation', layout)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
elif event == 'Button 1':
@@ -40,4 +39,4 @@ while True: # Event Loop
elif event == 'Button 2':
callback_function2() # call the "Callback" function
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Button_States.py b/DemoPrograms/Demo_Button_States.py
index 0b442699..2af9d969 100644
--- a/DemoPrograms/Demo_Button_States.py
+++ b/DemoPrograms/Demo_Button_States.py
@@ -1,51 +1,60 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
"""
Demonstrates using a "tight" layout with a Dark theme.
Shows how button states can be controlled by a user application. The program manages the disabled/enabled
states for buttons and changes the text color to show greyed-out (disabled) buttons
"""
-sg.ChangeLookAndFeel('Dark')
-sg.SetOptions(element_padding=(0,0))
+sg.change_look_and_feel('Dark')
+sg.set_options(element_padding=(0, 0))
-layout = [[sg.T('User:', pad=((3,0),0)), sg.OptionMenu(values = ('User 1', 'User 2'), size=(20,1)), sg.T('0', size=(8,1))],
- [sg.T('Customer:', pad=((3,0),0)), sg.OptionMenu(values=('Customer 1', 'Customer 2'), size=(20,1)), sg.T('1', size=(8,1))],
- [sg.T('Notes:', pad=((3,0),0)), sg.In(size=(44,1), background_color='white', text_color='black')],
- [sg.Button('Start', button_color=('white', 'black'), key='_Start_'),
- sg.Button('Stop', button_color=('white', 'black'), key='_Stop_'),
- sg.Button('Reset', button_color=('white', 'firebrick3'), key='_Reset_'),
- sg.Button('Submit', button_color=('white', 'springgreen4'), key='_Submit_')]]
+layout = [[sg.Text('User:', pad=((3, 0), 0)), sg.OptionMenu(values=('User 1', 'User 2'), size=(20, 1)), sg.Text('0', size=(8, 1))],
+ [sg.Text('Customer:', pad=((3, 0), 0)), sg.OptionMenu(
+ values=('Customer 1', 'Customer 2'), size=(20, 1)), sg.Text('1', size=(8, 1))],
+ [sg.Text('Notes:', pad=((3, 0), 0)), sg.Input(size=(44, 1),
+ background_color='white', text_color='black')],
+ [sg.Button('Start', button_color=('white', 'black'), key='-Start-'),
+ sg.Button('Stop', button_color=('white', 'black'), key='-Stop-'),
+ sg.Button('Reset', button_color=('white', 'firebrick3'), key='-Reset-'),
+ sg.Button('Submit', button_color=('white', 'springgreen4'), key='-Submit-')]]
-window = sg.Window("Time Tracker", default_element_size=(12,1), text_justification='r', auto_size_text=False, auto_size_buttons=False,
- default_button_element_size=(12,1)).Layout(layout).Finalize()
+window = sg.Window("Time Tracker", layout,
+ default_element_size=(12, 1),
+ text_justification='r',
+ auto_size_text=False,
+ auto_size_buttons=False,
+ default_button_element_size=(12, 1),
+ finalize=True)
-for key, state in {'_Start_': False, '_Stop_': True, '_Reset_': True, '_Submit_': True}.items():
- window.FindElement(key).Update(disabled=state)
+for key, state in {'-Start-': False, '-Stop-': True, '-Reset-': True, '-Submit-': True}.items():
+ window[key].update(disabled=state)
recording = have_data = False
while True:
- event, values = window.Read()
+ event, values = window.read()
print(event)
if event is None:
- sys.exit(69)
- if event == '_Start_':
- for key, state in {'_Start_':True, '_Stop_':False, '_Reset_':False, '_Submit_':True}.items():
- window.FindElement(key).Update(disabled=state)
+ break
+ if event == '-Start-':
+ for key, state in {'-Start-': True, '-Stop-': False, '-Reset-': False, '-Submit-': True}.items():
+ window[key].update(disabled=state)
recording = True
- elif event == '_Stop_' and recording:
- [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':False, '_Submit_':False}.items()]
+ elif event == '-Stop-' and recording:
+ [window[key].update(disabled=value) for key, value in {
+ '-Start-': False, '-Stop-': True, '-Reset-': False, '-Submit-': False}.items()]
recording = False
have_data = True
- elif event == '_Reset_':
- [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':True}.items()]
+ elif event == '-Reset-':
+ [window[key].update(disabled=value) for key, value in {
+ '-Start-': False, '-Stop-': True, '-Reset-': True, '-Submit-': True}.items()]
recording = False
have_data = False
- elif event == '_Submit_' and have_data:
- [window.FindElement(key).Update(disabled=value) for key,value in {'_Start_':False, '_Stop_':True, '_Reset_':True, '_Submit_':False}.items()]
+ elif event == '-Submit-' and have_data:
+ [window[key].update(disabled=value) for key, value in {
+ '-Start-': False, '-Stop-': True, '-Reset-': True, '-Submit-': False}.items()]
recording = False
+
+window.close()
diff --git a/DemoPrograms/Demo_Button_Toggle.py b/DemoPrograms/Demo_Button_Toggle.py
index 0d923be6..1d35482a 100644
--- a/DemoPrograms/Demo_Button_Toggle.py
+++ b/DemoPrograms/Demo_Button_Toggle.py
@@ -7,33 +7,39 @@ import PySimpleGUI as sg
A HUGE thank you to the PySimpleGUI community memeber that donated his time and skill in creating the buttons!
The text of the button toggles between Off and On
"""
-def main():
- layout = [[sg.Text('A toggle button example')],
- [sg.T('A graphical version'), sg.B('', image_data=toggle_btn_off, key='_TOGGLE_GRAPHIC_', button_color=sg.COLOR_SYSTEM_DEFAULT,border_width=0),],
- [sg.Button('On', size=(3,1), button_color=('white', 'green'), key='_B_'), sg.Button('Exit')]]
-
- window = sg.Window('Toggle Button Example', layout)
-
- down = True
- graphic_off = True
- while True: # Event Loop
- event, values = window.Read()
- print(event, values)
- if event in (None, 'Exit'):
- break
- elif event == '_B_': # if the normal button that changes color and text
- down = not down
- window.Element('_B_').Update(('Off','On')[down], button_color=(('white', ('red', 'green')[down])))
- elif event == '_TOGGLE_GRAPHIC_': # if the graphical button that changes images
- graphic_off = not graphic_off
- window.Element('_TOGGLE_GRAPHIC_').Update(image_data=(toggle_btn_on, toggle_btn_off)[graphic_off])
-
- window.Close()
-
toggle_btn_off = b'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'
-
-
toggle_btn_on = b'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'
-main()
+layout = [[sg.Text('A toggle button example')],
+ [
+ sg.Text('A graphical version'),
+ sg.Button('', image_data=toggle_btn_off,
+ key='-TOGGLE-GRAPHIC-',
+ button_color=sg.COLOR_SYSTEM_DEFAULT,
+ border_width=0)
+],
+ [sg.Button('On', size=(3, 1),
+ button_color=('white', 'green'),
+ key='-B-'),
+ sg.Button('Exit')]]
+
+window = sg.Window('Toggle Button Example', layout)
+
+down = True
+graphic_off = True
+while True: # Event Loop
+ event, values = window.read()
+ print(event, values)
+ if event in (None, 'Exit'):
+ break
+ elif event == '-B-': # if the normal button that changes color and text
+ down = not down
+ window['-B-'].update(('Off', 'On')[down],
+ button_color=(('white', ('red', 'green')[down])))
+ elif event == '-TOGGLE-GRAPHIC-': # if the graphical button that changes images
+ graphic_off = not graphic_off
+ window['-TOGGLE-GRAPHIC-'].update(image_data=(
+ toggle_btn_on, toggle_btn_off)[graphic_off])
+
+window.close()
diff --git a/DemoPrograms/Demo_Buttons_Mac.py b/DemoPrograms/Demo_Buttons_Mac.py
index b605923b..90908f6c 100644
--- a/DemoPrograms/Demo_Buttons_Mac.py
+++ b/DemoPrograms/Demo_Buttons_Mac.py
@@ -1,50 +1,44 @@
#!/usr/bin/env python
import sys
import time
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
def show_win():
- sg.SetOptions(border_width=0, margins=(0,0), element_padding=(5,3))
+ sg.set_options(border_width=0, margins=(0, 0), element_padding=(5, 3))
+ frame_layout = [
+ [sg.Button('', image_data=mac_red,
+ button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='-exit-'),
+ sg.Button('', image_data=mac_orange,
+ button_color=('white', sg.COLOR_SYSTEM_DEFAULT)),
+ sg.Button('', image_data=mac_green,
+ button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='-minimize-'),
+ sg.Text(' '*40)], ]
- frame_layout = [ [sg.Button('', image_data=mac_red, button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='_exit_'),
- sg.Button('', image_data=mac_orange, button_color=('white', sg.COLOR_SYSTEM_DEFAULT)),
- sg.Button('', image_data=mac_green, button_color=('white', sg.COLOR_SYSTEM_DEFAULT), key='_minimize_'),
- sg.Text(' '*40)],]
+ layout = [[sg.Frame('', frame_layout)],
+ [sg.Text('')],
+ [sg.Text('My Mac-alike window', size=(25, 2))], ]
- layout = [[sg.Frame('',frame_layout)],
- [sg.T('')],
- [ sg.Text(' My Mac-alike window', size=(25,2)) ],]
-
- window = sg.Window('My new window',
- no_titlebar=True,
- grab_anywhere=True,
- alpha_channel=0,
- ).Layout(layout).Finalize()
+ window = sg.Window('My new window', layout,
+ no_titlebar=True, grab_anywhere=True,
+ alpha_channel=0, finalize=True)
for i in range(100):
- window.SetAlpha(i/100)
+ window.eet_alpha(i/100)
time.sleep(.01)
while True: # Event Loop
- event, values = window.Read()
- if event is None or event == '_exit_':
+ event, values = window.read()
+ if event is None or event == '-exit-':
break
- if event == '_minimize_':
+ if event == '-minimize-':
# window.Minimize() # cannot minimize a window with no titlebar
pass
print(event, values)
-
mac_red = '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'
-
mac_green = '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'
-
mac_orange = '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'
-
-
-show_win()
\ No newline at end of file
+show_win()
diff --git a/DemoPrograms/Demo_Buttons_Nice_Graphics.py b/DemoPrograms/Demo_Buttons_Nice_Graphics.py
index 2a53a919..300c9c50 100644
--- a/DemoPrograms/Demo_Buttons_Nice_Graphics.py
+++ b/DemoPrograms/Demo_Buttons_Nice_Graphics.py
@@ -10,11 +10,12 @@ Shows some fancy button graphics with the help of PIL
Usually when you create Base64 buttons to embed in your PySimpleGUI code, you make the exactly the correct size. Resizing isn't an option when
using the tkinter version of PySimpleGUI (except for crude "Scaling")
-The PIL code resizes the button images prior to creating the sg.Button
+The PIL code resizes the button images prior to creating the sg.B
"""
-DEF_BUTTON_COLOR =('white', 'black')
+DEF_BUTTON_COLOR = ('white', 'black')
+
def resize_base64_image(image64, size):
'''
@@ -32,7 +33,7 @@ def resize_base64_image(image64, size):
return imgbytes
-def GraphicButton(text, key, image_data, color=DEF_BUTTON_COLOR, size=(100,50)):
+def GraphicButton(text, key, image_data, color=DEF_BUTTON_COLOR, size=(100, 50)):
'''
A user defined element. Use this function inside of your layouts as if it were a Button element (it IS a Button Element)
Only 3 parameters are required.
@@ -46,18 +47,21 @@ def GraphicButton(text, key, image_data, color=DEF_BUTTON_COLOR, size=(100,50)):
'''
return sg.Button(text, image_data=resize_base64_image(image_data, size), button_color=color, font='Any 15', pad=(0, 0), key=key, border_width=0)
+
def ShowMeTheButtons():
- sg.ChangeLookAndFeel('Black')
+ sg.change_look_and_feel('Black')
- frame_layout = [ [sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45,3))],
- [sg.Text('All of these buttons are part of the code itself', size=(45,2))],
- [GraphicButton('Next', '-NEXT-', button64),
- GraphicButton('Submit', '-SUBMIT-', red_pill64),
- GraphicButton('OK', '-OK-', green_pill64),
- GraphicButton('Exit', '-EXIT-', orange64)],]
+ frame_layout = [[sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45, 3))],
+ [sg.Text(
+ 'All of these buttons are part of the code itself', size=(45, 2))],
+ [GraphicButton('Next', '-NEXT-', button64),
+ GraphicButton('Submit', '-SUBMIT-', red_pill64),
+ GraphicButton('OK', '-OK-', green_pill64),
+ GraphicButton('Exit', '-EXIT-', orange64)], ]
- layout = [[sg.Frame('Nice Buttons', frame_layout, font=('any 18'), background_color='black')]]
+ layout = [[sg.Frame('Nice Buttons', frame_layout, font=(
+ 'any 18'), background_color='black')]]
window = sg.Window('Demo of Nice Looking Buttons', layout,
grab_anywhere=True,
@@ -69,22 +73,18 @@ def ShowMeTheButtons():
# ---===--- The event loop --- #
while True:
- event, values = window.Read()
+ event, values = window.read()
print(event)
if event in ('-EXIT-', None): # Exit button or X
break
+
if __name__ == '__main__':
# To convert your images, use the PySimpleGUI program - Demo_Base64_Image_Encoder.py located on the GitHub (http://www.PySimpleGUI.com)
orange64 = 'iVBORw0KGgoAAAANSUhEUgAAAiIAAADLCAMAAABkvgh7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANtvJ99sId5tIt5uI91tJNxuJNxuJttvKN90NN91Ntt3PN52ONx3Otx3PNt4Pdp4Ptx4PepfD+pfEOtgD+piD+tkD+ttD+dnFOdoE+doFOVoFuNqG+JrHOFrHuFsHuRpGORpGuRqGORqGupgEOpiEOpkEOlmEOhnEupoEOpoEupqFettEetsF+tuFOxqFetrGOtsGetvHOxrGOxsGOxtGuxuG+xuHexxFutwH+xxGOxyGOxyGuxxHuBsIOJvJ+NvKORvKONwKeNwKuJxLONyLeJyLuRwKeRwKuRxLOtxIexyIexzJOx0JOx1Ju11KOx2KOx2Kux5Iux4Kux5Le1/LeFyMOFzMuB0NOB1Nux7MOx8MOx8Mu19NO1+NO1+Ntd7RdV8RtN+S9J/TdF/TtV9SNR9Stl5QNl6Qdh6Qth7RK+Tfa+Tfq6Ufr+JZb+JZr+KZ7uMbr6KaL6LaryMbLyNb7yOb7WPdLuNcLqOcLmOcrmPdLOQd7SQdrKReLKRerGSerCTfLCTfrmQde2AN+2AOO2BOu2COu6DPO6EPe6EPsWDV8OFXcOFXsOGXcOGXsaDWMSEWsqCVs+AUM2AUs6CU82CVMqDWMmEWtCAT9CAUMKHYMCIYsCIZMCKZu6FQO6GQe6GQu6HRO6IRe6JRu6KR+6KSO+LSu+MS++MTO+OTe+OTu+OUO+RUu+TVfCNTvCOTvCPUPCQUfCQUvCSU/CRVPCSVPCSVvCUV/CUWPCVWvGWWvGXXPGYXvCWYPGaYfGcYvGcZPKdZvKeZ/GdavKeaPKebfKebvKgavKhbPKibfGibvKjcPGkcfKmdPKiePOod/OpePSpePSqevSrfPSsfPSsfvSqg/SugfWwgvWwhPWyhfWyhvWxi/Wyj/W0ifW2jfa4j/a1kPW2kva2lfW2lvW4kPa6kfa7kva5lvW7lPa8lPW8lve+l/a4mva5nPa6nPa7nva+mPa+mva/nPa8oPa9ovbAm/bAnfXAnvbCofbEo/XGp/bGqPbIqQAAAC/NnaUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAAJXElEQVR4Xu3de3zVdR3H8Xb9/sJh60LlOTtubdIZEyckSdedMzkdZyRGIePmZdqki8s0KllJmtGCTAXRlUAyrireUeZga2JRkooYRWR0JbXMLsIqZ5fT7/L5nRvj9/md0/fhw/Px/fyHP+D33fb4vPj+LvsxXpMA8IREgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgJF7Ih1Xf3bVsuu/AYXu+mUrL+u8iqbqIcdErvhme0V8SjQCIpwWe+0nli+k2R5NTolc0jYzqpQBcigV+UDltZ57SQ6JfLqyRRXRyiCIis7u7qAhj8B3Ip1tLdg/pFLR0Z+hOR/JbyIrWxGIZCp289E2Ep+J3HzEFqLKy0qhUJUfeUkZnf8lGnYWX4l0tDfTOg5VEqoJNNTXh6EgmaMLBKuaymmcJFLRSfPO5CuRtgitYisPBehDQUFrqC3O2EtUxRdo4Bn8JHJhlNawlNZh85Aj2JQeiRp9JY08nY9Euk+nFUwq1ECLgww1ZTRaS+R8mnk6PpEF8dTDkNIgLQxiHFdNw7U0d9PU07CJdLw+tRUVpy5Cxo19+bz9lYI+n5cNfdhcjWGZf2jsCTTJcDiUmnBR/HKaewqbSHfqQqSJrkLGvuuctZu3jOgBD1u99B7Fg6a+Vwrrk8kNfRX+0XE2+qg527adsc38M723d01yK3lbqhH1SZp7CpfIolnJw4udQk44ddPOn/3q6b+M6K8+/c2vF+jXVw29X/ALI7N+Z3jome+f/QZ7pOG6VCMtl9Dkk7hElifvd0uPs1cb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'
-
green_pill64 = 'iVBORw0KGgoAAAANSUhEUgAAAYEAAACCCAYAAACgunQ+AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAl2cEFnAAABgQAAAIIAb/yLCQAAGGZJREFUeNrt3XuUXWV9N/Dvb+99bnPOZK7JTBIIF3MBAgElEiIBjEWwVl/f6iIKWhRae9GFlxaKttQQQWyL0db61sWqS1kWqw2vdlWrVqzINZogQtKQGHIDcs/cMnOu++y9n1//mImNM3ufOWdu5yT5ftaKK5x9e05c6/me57KfR1QVRER0ZrLqXQAiIqofhgAR0RmMIUBEdAZz6l2AevrIbkkM+sggQBo2kvUuDxHNrMBBIeYg99C5GFScmQOkcroODP/pAUkN5LDQsrAQgoWqWAjFYgg6IGiBQSsEAgCOJBKOxOP1LjMRzSzX5PMKY0b+swQgD8UggIMAdquF3ZZitwmwO17Ayw9erl69yzzVTpsQeP+vpMOxcI0BVoniGgCLIZCk1dxc77IR0amtZLJZKDwItgB4SgVPB0Vs/Poyzde7bJN1yoaAQOTW3bhGDK43wHUCXMhf9EQ0E0ZCwUDwSwgesw2++09LdGu9yzURp1wIfHCnLAuANQq8SwTz+UufiOqtZLJZVeywBBvUxoavvkb317tM1TolQmDNdok3O7hdgdsBtLPiJ6JGVQqyQ7DwM6P4znmL8ZW1UDP5u06fhg6B23bLbBj8CRQfBNDCyp+IThUjXUb7AXwxl8HXN5ylxXqXKUxDhsBt2+Rs4+CjorglbqfbLVj2RO+lBnCH9H//DCqCssL4gPGAwBv5u1/vb01EM812ACsGWDGBHQMsB7ATgmSrIDFr+E88IxieRzgxJZPNAuiH4stw8eDXLtPj9f7eJ2uoELhlq6SdGD6pij9J2s3tE7lHOavIHTHIHVWU+g1Kxxvn+xHRqSnVLmjqtJDutpCZK3AStaeCa/J5o2ZAgfuKF+ErG6BBvb8X0CAhIBB5/3bcCOBeAHNr6fZRAwztN8gfMxjYG8CcdrN4iajRJFoEzfMsNM+3kOmyamoplEw2qwbbRHDnQ0v1mXp/l7qHwG0vykVG8WUIXlt15a9A7qjB4MsGg/tZ8RNR/TgpQes5FlrOtZFqrz4NSiY7BMW3RfAXX1uqR+pV/rqFwDqItXc7PgaDv6y268d4wLH/9tH/UjAl4+0igq6WBYg5ccTtBGJOAnEngbiTxKQ6AYmoQSn8wEPZd+EFLsq+i7JfwmChDwU3O+m7J1oEHUtstJ5vQ6pcma1ksgcE+PBDS/W79fgXqUsI3PqidBuDByFYXc2vf6+g6N9pMLAnmNAAbiKWwrmzL8S5sy9Ea7oTbenZaE13Ip1sYVVPRACAwATozR7G8XwPBvI9ONi/Bwf79qJn6CAUtdWTTkrQsdhC20IbVmz880smm1Xga6UsPrFh5czOIprxELjlRbkBBl9OWpnzxjvX+EDPtgD9uwxq+f8gFU/jorOuwDmdS7CgcwlamiY0xkxEBGMCvNq3C4cHXsaLBzbhUP++qkNBbKDjAgudF47fMiiZXA7ADsvg1ocu1Rdn6vvNWAgIRG7Zgs+q4MNJK5OpdK4aoH+XQe+O6n/5d7WcjQvnL8fC7mXoajkb7M4houngegX86tAvsffoNuw49AtUU4c6KUHXMguzzh6/j6gU5IYg+KOvL9NvzcT3mZEQWLNd4kkP/wjg3eMFQP6o4uiWAOXc+OVKxFK4aP7rccmCq9DdsmAm/r2IiH6tUM7ixf2bsHX/M+jLjj+229Qp6LrURqKl8o9UN8hlVbDu65fq+un+DtMeAu/bJLOsBL6hwBsrBYDxgSPPB8geHH/EN51owdVL3oYL5i+HVDv6QkQ0jV4+th1bXn0Ge49tG/fcttdYmHOxXbHDomRyOQG+UtqFOzbcOH3vFExrCPz+8zKvrPg2BBdXCoDSgOLwcwG8QuWytKe7sPz8N2FR92Ws/ImoIfVmD+HZvf+FvUe3VRw7SLULui+3EUtFJ8FIEHw/ZXDbg5drYTrKO20h8P7N0hE4eCppZS6MPEmBgb0Gfb8KKu7pk4w1YdWSt+M1c5ZB2NVPRKeA/twxPLb9EfRmD0WeY8WArmU2MnOjf9SOBMHmtll46xcXqjvV5ZyWEHj/ZukIbPzIkcQljsRC1/fXADj0nI9ib/TzLbFw4fwr8Lpz34i4nZjychIRTScFsPfYNmze8ygK5ej3EFoWWJh9cfQSaSWTy4niR02Cm6Z6d7MpD4F3PCPN6Ti+H7MSK6ICICgDh5/z4Q5GP7sjMxfXLX03krH0lJaPiGimGfXx/CtPYtuBn0Wek+m2MGdZ9FTSksnlFNiwZDk+OJXLU09pCKz5maTiDr6jglVRYwBefvz+/wvmLcdlC66BJRNePJSIqOEcHNiDn+/+IVw//H2wVLug+7VO5AtmJwaLH16uH5+qMk1ZCAhEbn4Wj0BxQ9IODwB3SHH4F37k3P+Ek8IVr7ke3S3nTtX3IyJqKMVyDpv2/Cd6sgdDjztJwfwVNuyIlUpLQS4nwL0PX6F/OxXlmbIQuHmTfAzAvVEBUM4pDj/nRy721pxswxsW/Q6a4tw3hohOb6qKLa8+iX2920OPx9KCecsrtAiC3JBl4Z0Pv15/MtmyTEkI3LxJVsHge0kn0xp23C8qDv/SRxAxrt2e7sLrz3szYg4Hf4nozLHr6AvYefi50GOJWYKu1zqRW2qV/NwrloVVD6/QA5Mpw6RD4OZN0qUGTyftzMKw44GrOPJ8AL8U/pzO5vm4/JzVnPdPRGekQwN7sPVA+LYCyVbBnGVO5GCxa3I/9WfhLRsu0vJEn+9MpvBrHhHbmo8NKTuzMOydCDXAsa0BAldD5/d3ZuZh2dmrEGiABtlkh4hoRs1pWYCl6mP7oU1jjrmDit4Xfcy+OLyqVoPXO4O4D8CfT/T5k2oJ3LRRPqKKz0SNA/TtDFDoCZ/J1JLqxLKzVsGa+PbBRESnjQP9u7CnZ2vosdZzbTSfFd4cKPm5rNh4yzdX6saJPHfCIXDTk3I2bDybsDJdYcdzRwyO7w0PgKZ480gATKohQkR0Wnm1bwcODOwee0CA2UttJGZFzBgyuefnDGHlF3+79jeKJ1wLq2B9QtJdYSFSzikGXzahiyPZloNFXZfBwMCYCXdjERGddua2no+sexyDxd4xx/p3BZhzScQmNQaLj2bwZwDur/WZE2oJrHlS/o8F/EvCTo95nVcNcHRrEDkT6LzOi9HaNHv6/zWJiE5BgfHwq8PPohxSicabBbMviugWMvn+QHHl/79Gd9XyvJpbAjc8Kum2BL6UsNPpsMHgoVcNTBmRA8HNyVYE3BmeiCjSOZ0XYM+xrWNWIfVyikKPoqlzbAWblHS7q/l/APCWWp5Vcwi0JPAhBdrD2g9eXpHv0dBuoJidQGfzPPgTn8lERHRGiDkJtGe60Zc/PObY0AGDRKuNsCFVVaxa84S8ecO1+uNqn1VTCKx5XDIC3B7aClBg6BWNXOp5dvN8BOrXtFcwEdGZqi09G0OlXvijek40ALIHDFrOGdstlLDS6ZLJfwLA9IQAFH+oQHvYMEKhR+GVwlsBTfFmJGKpMV+GiIiitaW70JMb+0JwsV+R6lDE0iEVrsGKNY/Jmza8SR+r5hlVh8Atj0pabXwk6YxtBWgA5I+ayFZAS6oDAWcCERHVJBlLIeEkUQ5KY47lDinaFo6tdBNWOu2a/CcBTG0IFGz8gQCdoa2APh1e3TokBJJOE1QUHscCiIhq1pRoRrk4NgS8osIdUsSbx1a8arByzY/l2g1v1ifGu39VIbBunVi4Ch8OGwtQAxT7TOSGyYl4E2cDERFNkG3HYNsOgpA1+As9ingmvDVQ0vyHAExNCGy9Etdainlhg7ql/uFWQFhXkGPFIALOCCIimoSEk0TRy4353C8pyjlFPHxs4Pp3PipzvnO9Hqt076pCQATvi1vptIbMCCoORLcCbNvhYDAR0SSJZUXWs8U+RawppDVgN7WUgsK7AfxDpXuPGwLv+K40Owm8PawVUM4qNIgsG6AKnwPCRESTZosFE7K1sF9UBGXAHrOchIgo3ovJhoATx7ugaAobEHazGpkAAoGvHt8LICKaAirRx9whg1T72PcGVHHx7/5ALvu3t+oLUddW0x30nrA1gow/nEAS3QyAgnsEEBFNlaj61sspUu1jP0/Y6bQr+fcAeCHqnhVD4IZHJd1ksEJD1isq57RCPxAREc0UEwBeQeGkxlbKxuC3Kl1bMQSaPawKBLGwLh0vzxAgImoUXl7hJMdWymJwwZofSPeGt+qRsOsqhoAPrE5aTenRy01rMNwdJMIUICJqBL4LhG0NELebMsWg8EYA3wq7rmIIiGJ1WCvAd8FWABFRA1EDGA+hq4taBqtRawis+aHMhmJpWAgYt9KAMBER1UPgAqHbtiveGHVNZAi4Hq5IWMlU2NRQ3wNbAkREDcYvhw8OK3DW2/9D5n/vbXpw9LFK3UFLBJYVtmIoAmYAEVGj0TJC381KWE1NRa+wBEANIWCwJOzlhMAHE4CIqAEphqeLSsi0fhEsQcjy0pEhoIrFYYmiATgeQETUoCLraIPFYedHtwQUi8PGA9Tw/QAiokZlAkBCBodVsSTs/NAQeOe/yRwArWHHojaPISKi+lOjiKikF4V9GBoCgcH8uJ0KnRmkyu4gIqJGpWa4nh7zuWLuunVirV37m0uRhoaAH6A5ZlWo6hkCRESNK3z1Ztm0EBkAQyd/GBoCImiOWgKarQAiosYlQHgIKGDH0YxqQsAEyIStHIrIriYiImoEKuHdQXGrqalgCpnRn9fWEuB4ABFR44voybECNI/+LDQE1FRoCRARUUPTiLraANW1BKBIRFb4bAkQETW2iPrbNkiM/iw0BCyEr0sNcA8BIqJGF1V/Gx0bD07EiYXQJBGwJUBE1OhM+MeWojj6s6gxgSzs8JuwIUBE1NiievPVQnb0Z+HdQYKsRswOqrwXGRER1ZVGDQyrqldlCGiALMJmB4EtASKiRqa//p/fVA5KpaDalkBgI6ecHUREdOrRyLWDEC8hN/rz8BDwcLxsFYtxO5UKu5FEtBKIiKi+VIGIvWDK3/tDLYz+PDQEbAevqA8NfWHMMASIiBpW1Cqign1hp4eGwI9+T/Nv+aocgI7diYb7CRARNS4NNLQlIAYvhZ0fOdfHKHZqaAgoXxgjImpQJghvCRhgZ9j5lbaXfCksTUwAtgSIiBqUCRD+okCtLQFV7AwdXPA4TZSIqBFpgNC3hV2/WJxIS2BHySsWE85vzhBSBYwPWLF6f10iIjpZ4EW8KGag8UyNIeDH8QunjCKAMdNEAw+w4/X+ukREdLKgHLWRAJ7/wXt1KOxQZAj89ANauu5B2aiKt40+5ruKeIZ9QkREjcR3w1sCgcFPo66puBKQpXgcISEQuCN/YQ4QETWEwBsZExjF9YtFW/B41HUVQ0CBx1y/WIrbyeToA4ErcFJMASKiRhAUNXwfAQO31cXGqOsqvvu76ii2iGIAJ15DPulPOc+9JomIGkU5bxBWVwP4+YaPazHquootgbVr1Vz3j/JDKG4bfczLK9DBJSSIiOrNLylMeeznqsYo8P1K1467O4AafMPV0s1hXULlvCIxi11CRET1VM5p6IBw2SvnPeBfK107bghc04fHn2rHflhYNOYBQ4pkC0OAiKheVIFyVqO2E/vRk7drT6Xrxw2BtWvVrP6SfMNR3DP6mFdU+K7CSTIIiIjqwR3U4aUiRikHpRIM/nm866vaLNJSPFz2SnfFneSYF8eKvYrmsxkCREQzToFin4laK6iv8zj+c7xbiGp1s3xW/708GneSbw471na+xdYAEdEMKx1XZA+Z0GOeX3rgsY/qn493j6q3jRdgfdkvXT1mgBhAoUcxawFDgIhoxihQ6AlvBbhBaUgsfLGa21TdEgCA1X8nT8fs5FVhxzoW23CSVd+KiIgmoTigGNof3gpw/dKXnvi43l7NfapuCQCAGNxfNqVvx52xrYHsQYO2hXxpgIhoumkA5A6HtwLKfinrKD5X7b1qagkIRK5dj2fiTnJl2PGWBRZSHewWIiKaTkP7DQq94XV3OSh9+fE/1Q9Ve6+aWgIK1dUi97te6ZGw1sDQQYNEqw2rprsSEVG1vLwi3xMeAK5fKgjwQC33q6klcMK1n5P/iNvJ3wk7luoQtJ7PbiEioimnQO92A68wtt4u+6USBOufuEPvruWWE/rN7is+ql7pDXEn2Tb6WKFXkWpXJNvYLURENJWGDprIxTsN8HLrLNxf6z0n1BIAgKv/Vu4SxT1h3UKWA8y+hLOFiIimintc0bsjfDZQ2S+V1MLvPnWnjvty2GgT7r2fW8QXDifxHlW9bPSxwAP6dwaYs8zhKqNERJMUuIq+XUHoInFe4LoKfGciAQBMoiUAANd8Vq6C4CcxO5EIO57uttC+yK7TPxsR0alPFTi2xR9eJC6EF7hHEoILfnyXDk7k/pMKAQC4+rNyNwR3RwVB+yIbzXPZHCAimoieHcHwm8EhPM8twcaap+7S7030/pOezHldGff/OIYrjZjftsQaU9v3vxTAsoF0F4OAiKgWA3sCFI5FBMBwN9AXnp5EAADjbC9ZjbVr1ZQ8fCAIvH2hW5sp0LcjQLGP21ESEVVr8BWDoVcjtowc/vN0zMOnJvucSXcHnbDyXllhC/4r5iQyoQ+ygO7LHE4dJSIax9ABg76dQeRxz3dfLftYsfkePTLZZ01ZCADA1Z+RG1Xxz1HjA5YNzFnmINXJICAiCjP4ikH/S9EBUA7cbEzxW4//lT47Fc+b0hAAgDfcK3cIcF9UEIgAnUttzJrPMQIiol9ToHdngMFXTOQpnu/m1MKNG++e2HTQMFMeAgBw1Tq5D4I7ooIAGJ41xFVHiYgANcCxLQFyRyoEQOAWBbj16U/pv9Zw63FNSwgIRK5chy+J4vdjTnQQNM+z0HWpzRfKiOiMZXzg4GYfpYHoutjzXVcVt/9snf7TVD9/WkIAGA6CFffgIUvx7kpBEG8WdL/ORmIWxwmI6MySP6o4+oKPoBx9jue7LoCPblynD05HGaYtBICRFsFa3CuKOyoFgdjA7KU2Ws9jk4CITn9qgN7tAQb2mIrneb6bBfDHGz+t/zJdZZnWEDhh5afkjxVYH3cSTZXOy3QL5i53uB8BEZ22/BJw8Oc+Sscr172e7/aI4KaN6/Qn01meGQkBALhyrbwDBg/FnURrpfMsB+hYYqNtkcWxAiI6bQTl4V//x/eFbwt5Mi9wXzbA/930ad0y3eWasRAAgBV3y3IRfFdgdTp2LFbp3HhG0HWpjUw3xwqI6BSmwPGXDY5tCyr2/QNA2XddAfYZ4PrN9+n+mSjejIYAAFz9SWkrC/4fBO+MVxgnOCHdJei8wEbTHIYBEZ1CdHgv4N4dBu7Q+PWs57tFCNb7Pfj0Lx5Ub6aKOeMhcMLKv5Q/MMADMTveWs35qQ4LnReMvGTGPCCiBqUBMLAvQN/OAOVcFZV/UC4DOAKDWzd9Vh+b6fLWLQQA4HV3ybyYjX8HsCxmx+PVXJOYJWhbaKPtfBtWrJoriIimn19S9O0I0L8rgAmqu2YkAB6VBN7787U6VI9y1zUEAGD1OnHyRXwQgrWOFZsjIlX9zhcBMnMttJ5rYdbZNmcUEdGM80uKwVcMBvbVtlLySOW/D4o7N//15JaCnqy6h8AJ1/yFzC4G+LQl+IBjx2vbnXgkEJrnWkh3WUi1C7uMiGjKqQEKPQa5Iwb5I4r8MVPT9UaDIAiCrACfa0ph/U/v0VK9v1PDhMAJr7tDznEsPATBG6rtIhrzpezhJSlSHRYSrYLELEGiRTjllIiqpgFQGlC4Qwp30CB/TFE4ajDRKtMLyi4Uj4iDj226X/vq/f1OaLgQOOHKT8iqwOBOADfE7Pi4s4iqEc8Mh4EdA6yYwIoBtjPyd3YnEZ1xAg8wnsL4v/n3Yr/CL05N3ej55ZwC3xQL65/9G91Z7+88WsOGwAlX3inLAsUdELwrZsebJn9HIqLpFRjfN8YMieCr5QB/98Ln9WC9yxSl4UPghOUfkwXq4H0iuBXAgol2FRERTRcvKLuq2CTAN+MGGzZ+XvvrXabxnDIhcLLL75DLBbhJFDeqoJuBQET1MjLT5yUFvmkbfGvz53VvvctUi1MyBE624k5Z6StWW4prVbASQIKhQETTZaTSHxLgSVU8oRYef+4B3Vrvck3UKR8CJ1u9TpxCHitNgBsUOA/AJSJYpIDFYCCiWnlBuQyFr4IXBdgOxS8tG08++wC26GlSeZ5WIRD6BdeJdXke56mPRSK4WIBWBWYBaIYgA6AZQFoUtb2bQESnPCMoWIKcKnJQZC0gbwTHLeCgJ3hJA+za8gUcOl0q/DCnfQgQEVE0vj5FRHQGYwgQEZ3BGAJERGcwhgAR0RnsfwCReA5ROFftiwAAADt0RVh0Y29tbWVudABFZGl0ZWQgYnkgUGF1bCBTaGVybWFuIGZvciBXUENsaXBhcnQsIFB1YmxpYyBEb21haW40zfqqAAAAJXRFWHRjcmVhdGUtZGF0ZQAyMDA4LTEwLTE0VDE2OjQwOjIxLTA0OjAwPdgB9gAAACV0RVh0bW9kaWZ5LWRhdGUAMjAwOC0xMC0xNFQxNjo0MDoyMS0wNDowMGJpd8IAAAAASUVORK5CYII='
-
red_pill64 = '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'
-
button64 = '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'
-
ShowMeTheButtons()
-
diff --git a/DemoPrograms/Demo_Calendar.py b/DemoPrograms/Demo_Calendar.py
index 79a5533c..5d1d6e55 100644
--- a/DemoPrograms/Demo_Calendar.py
+++ b/DemoPrograms/Demo_Calendar.py
@@ -1,15 +1,13 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-layout = [[sg.T('Calendar Test')],
- [sg.In('', size=(20,1), key='input')],
+
+layout = [[sg.Text('Calendar Test')],
+ [sg.Input('', size=(20, 1), key='input')],
[sg.CalendarButton('Choose Date', target='input', key='date')],
[sg.Ok(key=1)]]
-window = sg.Window('Calendar', grab_anywhere=False).Layout(layout)
-event,values = window.Read()
-sg.Popup(values['input'])
+window = sg.Window('Calendar', layout, grab_anywhere=False)
+event, values = window.read()
+sg.popup(values['input'])
+windowclose()
diff --git a/DemoPrograms/Demo_Canvas.py b/DemoPrograms/Demo_Canvas.py
index 60773758..74b63a06 100644
--- a/DemoPrograms/Demo_Canvas.py
+++ b/DemoPrograms/Demo_Canvas.py
@@ -1,22 +1,18 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
layout = [
- [sg.Canvas(size=(150, 150), background_color='red', key='canvas')],
- [sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue')]
- ]
+ [sg.Canvas(size=(150, 150), background_color='red', key='canvas')],
+ [sg.Text('Change circle color to:'), sg.Button('Red'), sg.Button('Blue')]
+]
-window = sg.Window('Canvas test').Layout(layout).Finalize()
+window = sg.Window('Canvas test', layout, finalize=True)
-cir = window.FindElement('canvas').TKCanvas.create_oval(50, 50, 100, 100)
+cir = window['canvas'].TKCanvas.create_oval(50, 50, 100, 100)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event in ('Blue', 'Red'):
- window.FindElement('canvas').TKCanvas.itemconfig(cir, fill=event)
+ window['canvas'].TKCanvas.itemconfig(cir, fill=event)
diff --git a/DemoPrograms/Demo_Change_Look_And_Feel_Browser.py b/DemoPrograms/Demo_Change_Look_And_Feel_Browser.py
index 823e66f5..4a10e440 100644
--- a/DemoPrograms/Demo_Change_Look_And_Feel_Browser.py
+++ b/DemoPrograms/Demo_Change_Look_And_Feel_Browser.py
@@ -7,12 +7,13 @@ import PySimpleGUI as sg
In this program, as soon as a listbox entry is clicked, the read returns.
"""
-sg.ChangeLookAndFeel('GreenTan')
+sg.change_look_and_feel('GreenTan')
-layout = [ [sg.Text('Look and Feel Browser')],
- [sg.Text('Click a look and feel color to see demo window')],
- [sg.Listbox(values=sg.list_of_look_and_feel_values(), size=(20,12), key='-LIST-', enable_events=True)],
- [sg.Button('Show Window'), sg.Button('Exit')] ]
+layout = [[sg.Text('Look and Feel Browser')],
+ [sg.Text('Click a look and feel color to see demo window')],
+ [sg.Listbox(values=sg.list_of_look_and_feel_values(),
+ size=(20, 12), key='-LIST-', enable_events=True)],
+ [sg.Button('Show Window'), sg.Button('Exit')]]
window = sg.Window('Look and Feel Browser', layout)
@@ -22,4 +23,5 @@ while True: # Event Loop
break
sg.change_look_and_feel(values['-LIST-'][0])
sg.popup_get_text('This is {}'.format(values['-LIST-'][0]))
+
window.close()
diff --git a/DemoPrograms/Demo_Change_Submits_InputText.py b/DemoPrograms/Demo_Change_Submits_InputText.py
index 215b46b2..f64914c0 100644
--- a/DemoPrograms/Demo_Change_Submits_InputText.py
+++ b/DemoPrograms/Demo_Change_Submits_InputText.py
@@ -1,48 +1,46 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
"""
Demonstrates the new change_submits parameter for inputtext elements
It ONLY submits when a button changes the field, not normal user input
Be careful on persistent forms to not clear the input
"""
-layout = [[ sg.Text('Test of reading input field') ],
- [sg.T('This input is normal'), sg.In()],
- [sg.T('This input change submits'), sg.In(change_submits=True)],
- [sg.T('This multiline input change submits'), sg.Multiline(change_submits=True, do_not_clear=True)],
- [sg.T('This input is normal'), sg.In(), sg.FileBrowse()],
- [sg.T('File Browse submits'), sg.In(change_submits=True,
- do_not_clear=True,
- key='_in1_'), sg.FileBrowse()],
- [sg.T('Color Chooser submits'), sg.In(change_submits=True,
- do_not_clear=True,
- key='_in2_'), sg.ColorChooserButton('Color...', target=(sg.ThisRow, -1))],
- [sg.T('Folder Browse submits'), sg.In(change_submits=True,
- do_not_clear=True,
- key='_in3_'), sg.FolderBrowse()],
- [sg.T('Calendar Chooser submits'), sg.In(change_submits=True,
- do_not_clear=True,
- key='_in4_'), sg.CalendarButton('Date...', target=(sg.ThisRow, -1))],
- [sg.T('Disabled input submits'), sg.In(change_submits=True,
- do_not_clear=True,
- disabled=True,
- key='_in5'), sg.FileBrowse()],
- [sg.T('This input clears after submit'),sg.In(change_submits=True,
- key='_in6_'), sg.FileBrowse()],
- [ sg.Button('Read')]]
+layout = [[sg.Text('Test of reading input field')],
+ [sg.Text('This input is normal'), sg.Input()],
+ [sg.Text('This input change submits'),
+ sg.Input(change_submits=True)],
+ [sg.Text('This multiline input change submits'),
+ sg.ML('', change_submits=True)],
+ [sg.Text('This input is normal'),
+ sg.Input(), sg.FileBrowse()],
+ [sg.Text('File Browse submits'),
+ sg.Input(change_submits=True,
+ key='-in1-'), sg.FileBrowse()],
+ [sg.Text('Color Chooser submits'),
+ sg.Input(change_submits=True,
+ key='-in2-'), sg.ColorChooserButton('Color...', target=(sg.ThisRow, -1))],
+ [sg.Text('Folder Browse submits'),
+ sg.Input(change_submits=True,
+ key='-in3-'), sg.FolderBrowse()],
+ [sg.Text('Calendar Chooser submits'),
+ sg.Input(change_submits=True,
+ key='-in4-'), sg.CalendarButton('Date...', target=(sg.ThisRow, -1))],
+ [sg.Text('Disabled input submits'),
+ sg.Input(change_submits=True,
+ disabled=True,
+ key='_in5'), sg.FileBrowse()],
+ [sg.Text('This input clears after submit'),
+ sg.Input(change_submits=True, key='-in6-'), sg.FileBrowse()],
+ [sg.Button('Read')]]
window = sg.Window('Demonstration of InputText with change_submits',
- auto_size_text=False,
- default_element_size=(22,1),
- text_justification='right',
- ).Layout(layout)
+ layout, auto_size_text=False, default_element_size=(22, 1),
+ text_justification='right')
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
if event is None:
break
+
+window.close()
diff --git a/DemoPrograms/Demo_Chat.py b/DemoPrograms/Demo_Chat.py
index c4ab0dd3..9758e0df 100644
--- a/DemoPrograms/Demo_Chat.py
+++ b/DemoPrograms/Demo_Chat.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
'''
A chat window. Add call to your send-routine, print the response and you're done
@@ -15,27 +11,27 @@ Note that the size of the display on repl.it is smaller than most, so the sizes
Multiline and Output text areas were reduced in the online version. Nothing else was changed
'''
-sg.ChangeLookAndFeel('GreenTan') # give our window a spiffy set of colors
+# give our window a spiffy set of colors
+sg.change_look_and_feel('GreenTan')
-layout = [ [sg.Text('Your output will go here', size=(40, 1))],
- [sg.Output(size=(127, 30), font=('Helvetica 10'))],
- [sg.Multiline(size=(85, 5), enter_submits=True, key='query'),
- sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
- sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
+layout = [[sg.Text('Your output will go here', size=(40, 1))],
+ [sg.Output(size=(127, 30), font=('Helvetica 10'))],
+ [sg.MLine(size=(85, 5), enter_submits=True, key='query'),
+ sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
window = sg.Window('Chat window',
+ layout,
default_element_size=(30, 2),
- font=('Helvetica',' 13'),
- default_button_element_size=(8,2)).Layout(layout)
+ font=('Helvetica', ' 13'),
+ default_button_element_size=(8, 2))
# ---===--- Loop taking in user input and using it --- #
while True:
- event, value = window.Read()
+ event, value = window.read()
if event == 'SEND':
query = value['query'].rstrip()
# EXECUTE YOUR COMMAND HERE
print('The command you entered was {}'.format(query))
elif event in (None, 'EXIT'): # quit if exit button or X
break
-sys.exit(69)
-
diff --git a/DemoPrograms/Demo_Chat_With_History.py b/DemoPrograms/Demo_Chat_With_History.py
index 1330caa1..cd24a692 100644
--- a/DemoPrograms/Demo_Chat_With_History.py
+++ b/DemoPrograms/Demo_Chat_With_History.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
'''
A chatbot with history
@@ -15,46 +11,60 @@ Special keyboard keys:
Control C - exit form
'''
+
def ChatBotWithHistory():
# ------- Make a new Window ------- #
- sg.ChangeLookAndFeel('GreenTan') # give our form a spiffy set of colors
+ # give our form a spiffy set of colors
+ sg.change_look_and_feel('GreenTan')
- layout = [[sg.Text('Your output will go here', size=(40, 1))],
- [sg.Output(size=(127, 30), font=('Helvetica 10'))],
- [sg.T('Command History'), sg.T('', size=(20,3), key='history')],
- [sg.Multiline(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
- sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
- sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
+ layout = [[sg.Text('Your output will go here', size=(40, 1))],
+ [sg.Output(size=(127, 30), font=('Helvetica 10'))],
+ [sg.Text('Command History'),
+ sg.Text('', size=(20, 3), key='history')],
+ [sg.ML(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
+ sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]]
- window = sg.Window('Chat window with history', default_element_size=(30, 2), font=('Helvetica',' 13'), default_button_element_size=(8,2), return_keyboard_events=True).Layout(layout)
+ window = sg.Window('Chat window with history', layout,
+ default_element_size=(30, 2),
+ font=('Helvetica', ' 13'),
+ default_button_element_size=(8, 2),
+ return_keyboard_events=True)
# ---===--- Loop taking in user input and using it --- #
command_history = []
history_offset = 0
+
while True:
- (event, value) = window.Read()
+ event, value = window.read()
+
if event == 'SEND':
query = value['query'].rstrip()
# EXECUTE YOUR COMMAND HERE
print('The command you entered was {}'.format(query))
command_history.append(query)
history_offset = len(command_history)-1
- window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear
- window.FindElement('history').Update('\n'.join(command_history[-3:]))
+ # manually clear input because keyboard events blocks clear
+ window['query'].update('')
+ window['history'].update('\n'.join(command_history[-3:]))
+
elif event in (None, 'EXIT'): # quit if exit event or X
break
+
elif 'Up' in event and len(command_history):
command = command_history[history_offset]
- history_offset -= 1 * (history_offset > 0) # decrement is not zero
- window.FindElement('query').Update(command)
+ # decrement is not zero
+ history_offset -= 1 * (history_offset > 0)
+ window['query'].update(command)
+
elif 'Down' in event and len(command_history):
- history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list
+ # increment up to end of list
+ history_offset += 1 * (history_offset < len(command_history)-1)
command = command_history[history_offset]
- window.FindElement('query').Update(command)
+ window['query'].update(command)
+
elif 'Escape' in event:
- window.FindElement('query').Update('')
-
- sys.exit(69)
+ window['query'].update('')
ChatBotWithHistory()
diff --git a/DemoPrograms/Demo_Chatterbot.py b/DemoPrograms/Demo_Chatterbot.py
index a80d08c3..2a80fa95 100644
--- a/DemoPrograms/Demo_Chatterbot.py
+++ b/DemoPrograms/Demo_Chatterbot.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
from chatterbot import ChatBot
import chatterbot.utils
@@ -18,36 +13,40 @@ to collect user input that is sent to the chatbot. The reply is displayed in th
# Create the 'Trainer GUI'
# The Trainer GUI consists of a lot of progress bars stacked on top of each other
-sg.ChangeLookAndFeel('GreenTan')
+sg.change_look_and_feel('GreenTan')
# sg.DebugWin()
MAX_PROG_BARS = 20 # number of training sessions
bars = []
texts = []
-training_layout = [[sg.T('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))], ]
+training_layout = [[sg.Text('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))]]
for i in range(MAX_PROG_BARS):
bars.append(sg.ProgressBar(100, size=(30, 4)))
- texts.append(sg.T(' ' * 20, size=(20, 1), justification='right'))
- training_layout += [[texts[i], bars[i]],] # add a single row
+ texts.append(sg.Text(' ' * 20, size=(20, 1), justification='right'))
+ training_layout += [[texts[i], bars[i]], ] # add a single row
-training_window = sg.Window('Training').Layout(training_layout)
+training_window = sg.Window('Training', training_layout)
current_bar = 0
# callback function for training runs
+
+
def print_progress_bar(description, iteration_counter, total_items, progress_bar_length=20):
global current_bar
global bars
global texts
global training_window
# update the window and the bars
- button, values = training_window.Read(timeout=0)
+ button, values = training_window.read(timeout=0)
if button is None: # if user closed the window on us, exit
- sys.exit(69)
+ return
if bars[current_bar].UpdateBar(iteration_counter, max=total_items) is False:
- sys.exit(69)
- texts[current_bar].Update(description) # show the training dataset name
+ return
+ # show the training dataset name
+ texts[current_bar].update(description)
if iteration_counter == total_items:
current_bar += 1
+
# redefine the chatbot text based progress bar with a graphical one
chatterbot.utils.print_progress_bar = print_progress_bar
@@ -59,18 +58,19 @@ chatbot.train("chatterbot.corpus.english")
################# GUI #################
layout = [[sg.Output(size=(80, 20))],
- [sg.Multiline(size=(70, 5), enter_submits=True),
+ [sg.MLine(size=(70, 5), enter_submits=True),
sg.Button('SEND', bind_return_key=True), sg.Button('EXIT')]]
-window = sg.Window('Chat Window', auto_size_text=True, default_element_size=(30, 2)).Layout(layout)
+window = sg.Window('Chat Window', layout,
+ default_element_size=(30, 2))
# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
while True:
- event, (value,) = window.Read()
+ event, (value,) = window.read()
if event != 'SEND':
break
string = value.rstrip()
- print(' '+string)
+ print(' ' + string)
# send the user input to chatbot to get a response
response = chatbot.get_response(value.rstrip())
- print(response)
\ No newline at end of file
+ print(response)
diff --git a/DemoPrograms/Demo_Chatterbot_With_TTS.py b/DemoPrograms/Demo_Chatterbot_With_TTS.py
index 565c89c1..c650e840 100644
--- a/DemoPrograms/Demo_Chatterbot_With_TTS.py
+++ b/DemoPrograms/Demo_Chatterbot_With_TTS.py
@@ -1,13 +1,7 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
from chatterbot import ChatBot
import chatterbot.utils
-
from gtts import gTTS
from pygame import mixer
import time
@@ -22,18 +16,18 @@ to collect user input that is sent to the chatbot. The reply is displayed in th
# Create the 'Trainer GUI'
# The Trainer GUI consists of a lot of progress bars stacked on top of each other
-sg.ChangeLookAndFeel('NeutralBlue')
+sg.change_look_and_feel('NeutralBlue')
# sg.DebugWin()
MAX_PROG_BARS = 20 # number of training sessions
bars = []
texts = []
-training_layout = [[sg.T('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))], ]
+training_layout = [[sg.Text('TRAINING PROGRESS', size=(20, 1), font=('Helvetica', 17))], ]
for i in range(MAX_PROG_BARS):
bars.append(sg.ProgressBar(100, size=(30, 4)))
- texts.append(sg.T(' ' * 20, size=(20, 1), justification='right'))
+ texts.append(sg.Text(' ' * 20, size=(20, 1), justification='right'))
training_layout += [[texts[i], bars[i]],] # add a single row
-training_window = sg.Window('Training').Layout(training_layout)
+training_window = sg.Window('Training', training_layout)
current_bar = 0
# callback function for training runs
@@ -43,12 +37,12 @@ def print_progress_bar(description, iteration_counter, total_items, progress_bar
global texts
global training_window
# update the window and the bars
- button, values = training_window.Read(timeout=0)
+ button, values = training_window.read(timeout=0)
if button is None: # if user closed the window on us, exit
- sys.exit(69)
- if bars[current_bar].UpdateBar(iteration_counter, max=total_items) is False:
- sys.exit(69)
- texts[current_bar].Update(description) # show the training dataset name
+ return
+ if bars[current_bar].update_bar(iteration_counter, max=total_items) is False:
+ return
+ texts[current_bar].update(description) # show the training dataset name
if iteration_counter == total_items:
current_bar += 1
@@ -79,14 +73,14 @@ chatbot.train("chatterbot.corpus.english")
################# GUI #################
layout = [[sg.Output(size=(80, 20))],
- [sg.Multiline(size=(70, 5), enter_submits=True),
+ [sg.MLine(size=(70, 5), enter_submits=True),
sg.Button('SEND', bind_return_key=True), sg.Button('EXIT')]]
-window = sg.Window('Chat Window', auto_size_text=True, default_element_size=(30, 2)).Layout(layout)
+window = sg.Window('Chat Window', layout, default_element_size=(30, 2))
# ---===--- Loop taking in user input and using it to query HowDoI web oracle --- #
while True:
- event, (value,) = window.Read()
+ event, (value,) = window.read()
if event != 'SEND':
break
string = value.rstrip()
@@ -94,4 +88,6 @@ while True:
# send the user input to chatbot to get a response
response = chatbot.get_response(value.rstrip())
print(response)
- speak(str(response))
\ No newline at end of file
+ speak(str(response))
+
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Color.py b/DemoPrograms/Demo_Color.py
index ead1245f..e4382307 100644
--- a/DemoPrograms/Demo_Color.py
+++ b/DemoPrograms/Demo_Color.py
@@ -1,16 +1,12 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-MY_WINDOW_ICON = 'E:\\TheRealMyDocs\\Icons\\The Planets\\jupiter.ico'
reverse = {}
colorhex = {}
colors = {
- "abbey" : ( 76, 79, 86),
+ "abbey" : ( 76, 79, 86),
"acadia" : ( 27, 20, 4),
"acapulco" : (124, 176, 161),
"aero blue" : (201, 255, 229),
@@ -1583,9 +1579,9 @@ def build_reverse_dict():
global colors
for color in colors:
rgb = colors[color]
- hex = '#%02X%02X%02X' % (rgb)
- reverse[hex] = color
- colorhex[color] = hex
+ hex_val = '#%02X%02X%02X' % (rgb)
+ reverse[hex_val] = color
+ colorhex[color] = hex_val
return
@@ -1621,9 +1617,9 @@ def get_name_from_hex(hex):
global colorhex
global colors
- hex = hex.upper()
+ hex_val = hex.upper()
try:
- name = reverse[hex]
+ name = reverse[hex_val]
except:
name = 'No Hex For Name'
return name
@@ -1635,22 +1631,22 @@ def get_hex_from_name(name):
name = name.lower()
try:
- hex = colorhex[name]
+ hex_val = colorhex[name]
except:
- hex = '#000000'
- return hex
+ hex_val = '#000000'
+ return hex_val
def show_all_colors_on_buttons():
global reverse
global colorhex
global colors
- window = sg.Window('Colors on Buttons Demo', default_element_size=(3, 1), location=(0, 0), icon=MY_WINDOW_ICON, font=("Helvetica", 7))
+ window = sg.Window('Colors on Buttons Demo', default_element_size=(3, 1), location=(0, 0), font=("Helvetica", 7))
row = []
row_len = 20
for i, c in enumerate(colors):
- hex = get_hex_from_name(c)
- button1 = sg.CButton(button_text=c, button_color=(get_complementary_hex(hex), hex), size=(8, 1))
- button2 = sg.CButton(button_text=c, button_color=(hex, get_complementary_hex(hex)), size=(8, 1))
+ hex_val = get_hex_from_name(c)
+ button1 = sg.CButton(button_text=c, button_color=(get_complementary_hex(hex_val), hex_val), size=(8, 1))
+ button2 = sg.CButton(button_text=c, button_color=(hex_val, get_complementary_hex(hex_val)), size=(8, 1))
row.append(button1)
row.append(button2)
if (i+1) % row_len == 0:
@@ -1678,18 +1674,18 @@ def main():
list_of_colors = [c for c in colors]
printable = '\n'.join(map(str, list_of_colors))
# show_all_colors_on_buttons()
- sg.SetOptions(element_padding=(0,0))
+ sg.set_options(element_padding=(0,0))
while True:
# ------- Form show ------- #
layout = [[sg.Text('Find color')],
[sg.Text('Demonstration of colors')],
[sg.Text('Enter a color name in text or hex #RRGGBB format')],
[sg.InputText(key='hex')],
- [sg.Listbox(list_of_colors, size=(20, 30), bind_return_key=True, key='listbox'), sg.T('Or choose from list')],
+ [sg.Listbox(list_of_colors, size=(20, 30), bind_return_key=True, key='listbox'), sg.Text('Or choose from list')],
[sg.Submit(), sg.Button('Many buttons', button_color=('white', '#0e6251'), key='Many buttons'), sg.ColorChooserButton( 'Chooser', target=(3,0), key='Chooser'), sg.Quit(),],
]
# [g.Multiline(DefaultText=str(printable), Size=(30,20))]]
- event, values = sg.Window('Color Demo', auto_size_buttons=False).Layout(layout).Read()
+ event, values = sg.Window('Color Demo', layout, auto_size_buttons=False).read()
# ------- OUTPUT results portion ------- #
if event == 'Quit' or event is None:
@@ -1720,7 +1716,8 @@ def main():
[sg.CloseButton(button_text=color_name, button_color=(color_hex, complementary_hex))],
[sg.CloseButton(button_text=complementary_hex + ' ' + complementary_color, button_color=(complementary_hex , color_hex), size=(30, 1))],
]
- sg.Window('Color demo', default_element_size=(100, 1), auto_size_text=True, auto_close=True, auto_close_duration=5, icon=MY_WINDOW_ICON).Layout(layout).Read()
+ sg.Window('Color demo', layout, default_element_size=(100, 1),
+ auto_close=True, auto_close_duration=5).read()
diff --git a/DemoPrograms/Demo_Color_Names.py b/DemoPrograms/Demo_Color_Names.py
index aa080f30..39f40962 100644
--- a/DemoPrograms/Demo_Color_Names.py
+++ b/DemoPrograms/Demo_Color_Names.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
"""
@@ -671,17 +666,18 @@ color_map = {
}
-sg.SetOptions(button_element_size=(12,1), element_padding=(0,0), auto_size_buttons=False, border_width=1, tooltip_time=100)
+sg.set_options(button_element_size=(12, 1),
+ element_padding=(0, 0),
+ auto_size_buttons=False,
+ border_width=1, tooltip_time=100)
-#start layout with the tittle
-layout = [[sg.Text('Hover mouse to see RGB value, click for white & black text',
- text_color='blue',
- font='Any 15',
- relief=sg.RELIEF_SUNKEN,
- justification='center',
- size=(100,1),
- background_color='light green',
- pad=(0,(0,20))),]]
+# start layout with the tittle
+layout = [
+ [sg.Text('Hover mouse to see RGB value, click for white & black text',
+ justification='center',
+ text_color='blue', background_color='light green',
+ size=(100, 1), pad=(0, (0, 20)))]
+]
# -- Create primary color viewer window --
color_list = [key for key in color_map]
@@ -691,19 +687,25 @@ for rows in range(40):
for i in range(12):
try:
color = color_list[rows+40*i]
- row.append(sg.Button(color, button_color=('black', color), key=color, tooltip=color_map[color]))
+ row.append(sg.Button(color, button_color=('black', color),
+ key=color, tooltip=color_map[color]))
except:
pass
layout.append(row)
-window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(layout)
+window = sg.Window('Color Viewer', layout, grab_anywhere=False, font=('any 9'))
# -- Event loop --
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
# -- Create a secondary window that shows white and black text on chosen color
- layout2 =[[sg.DummyButton(event, button_color=('white', event), tooltip=color_map[event]), sg.DummyButton(event, button_color=('black', event), tooltip=color_map[event])] ]
- sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).Read(timeout=0)
\ No newline at end of file
+ layout2 = [[
+ sg.DummyButton(event, button_color=(
+ 'white', event), tooltip=color_map[event]),
+ sg.DummyButton(event, button_color=('black', event), tooltip=color_map[event])
+ ]]
+ sg.Window('Buttons with white and black text',
+ layout2, keep_on_top=True).read(timeout=0)
diff --git a/DemoPrograms/Demo_Color_Names_Smaller_List.py b/DemoPrograms/Demo_Color_Names_Smaller_List.py
index e6911dcd..12f86376 100644
--- a/DemoPrograms/Demo_Color_Names_Smaller_List.py
+++ b/DemoPrograms/Demo_Color_Names_Smaller_List.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
"""
Color names courtesy of Big Daddy's Wiki-Python
@@ -15,7 +11,6 @@ else:
"""
-
COLORS = ['snow', 'ghost white', 'white smoke', 'gainsboro', 'floral white', 'old lace',
'linen', 'antique white', 'papaya whip', 'blanched almond', 'bisque', 'peach puff',
'navajo white', 'lemon chiffon', 'mint cream', 'azure', 'alice blue', 'lavender',
@@ -93,17 +88,18 @@ COLORS = ['snow', 'ghost white', 'white smoke', 'gainsboro', 'floral white', 'ol
'grey84', 'grey85', 'grey86', 'grey87', 'grey88', 'grey89', 'grey90', 'grey91', 'grey92',
'grey93', 'grey94', 'grey95', 'grey97', 'grey98', 'grey99']
+sg.set_options(button_element_size=(12, 1),
+ element_padding=(0, 0),
+ auto_size_buttons=False,
+ border_width=0)
-
-
-sg.SetOptions(button_element_size=(12,1), element_padding=(0,0), auto_size_buttons=False, border_width=0)
-
-layout = [[sg.Text('Click on a color square to see both white and black text on that color', text_color='blue', font='Any 15')]]
+layout = [[sg.Text('Click on a color square to see both white and black text on that color',
+ text_color='blue', font='Any 15')]]
row = []
layout = []
+
# -- Create primary color viewer window --
for rows in range(40):
-
row = []
for i in range(12):
try:
@@ -113,20 +109,16 @@ for rows in range(40):
pass
layout.append(row)
-
-# for i, color in enumerate(COLORS):
-# row.append(sg.Button(color, button_color=('black', color), key=color))
-# if (i+1) % 12 == 0:
-# layout.append(row)
-# row = []
-
-window = sg.Window('Color Viewer', grab_anywhere=False, font=('any 9')).Layout(layout)
+window = sg.Window('Color Viewer', layout, grab_anywhere=False, font=('any 9'))
# -- Event loop --
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
# -- Create a secondary window that shows white and black text on chosen color
- layout2 =[[sg.DummyButton(event, button_color=('white', event)), sg.DummyButton(event, button_color=('black', event))]]
- sg.Window('Buttons with white and black text', keep_on_top=True).Layout(layout2).Read(timeout=0)
\ No newline at end of file
+ layout2 = [[sg.DummyButton(event, button_color=('white', event)),
+ sg.DummyButton(event, button_color=('black', event))]]
+ sg.Window('Buttons with white and black text', layout2, keep_on_top=True).read(timeout=0)
+
+window.close()
diff --git a/DemoPrograms/Demo_Column_And_Frames.py b/DemoPrograms/Demo_Column_And_Frames.py
index 2c6f64ea..3b6c8d79 100644
--- a/DemoPrograms/Demo_Column_And_Frames.py
+++ b/DemoPrograms/Demo_Column_And_Frames.py
@@ -1,6 +1,16 @@
-import PySimpleGUI as sg
# this one long import has the effect of making the code more compact as there is no 'sg.' prefix required for Elements
-from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, Check, Button, B, Btn, ButtonMenu, Canvas, Column, Col, Combo, Frame, Graph, Image, InputText, Input, In, Listbox, LBox, Menu, Multiline, ML, MLine, OptionMenu, Output, Pane, ProgressBar, Radio, Slider, Spin, StatusBar, Tab, TabGroup, Table, Text, Txt, T, Tree, TreeData, VerticalSeparator, Window, Sizer
+import PySimpleGUI as sg
+from PySimpleGUI import InputCombo, Combo, Multiline, ML,
+ MLine, Checkbox, CB, Check,
+ Button, B, Btn, ButtonMenu,
+ Canvas, Column, Col, Combo,
+ Frame, Graph, Image, InputText,
+ Input, In, Listbox, LBox, Menu,
+ Multiline, ML, MLine, OptionMenu,
+ Output, Pane, ProgressBar, Radio,
+ Slider, Spin, StatusBar, Tab,
+ TabGroup, Table, Text, Txt, T,
+ Tree, TreeData, VerticalSeparator, Window, Sizer
"""
Demo Columns and Frames
@@ -14,29 +24,33 @@ from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, C
sg.change_look_and_feel('GreenTan')
-col2 = Column([[Frame('Accounts:', [[Column([[Listbox(['Account '+str(i) for i in range(1,16)], key='-ACCT-LIST-', size=(15,20)),]],size=(150,400))]])]], pad=(0,0))
+col2 = Column([[Frame('Accounts:', [[Column([[Listbox(['Account '+str(i) for i in range(1, 16)],
+ key='-ACCT-LIST-', size=(15, 20)), ]], size=(150, 400))]])]], pad=(0, 0))
col1 = Column([
# Categories frame
[Frame('Categories:', [[Radio('Websites', 'radio1', default=True, key='-WEBSITES-', size=(10, 1)),
- Radio('Software', 'radio1',key='-SOFTWARE-', size=(10, 1))]],)],
+ Radio('Software', 'radio1', key='-SOFTWARE-', size=(10, 1))]],)],
# Information frame
[Frame('Information:', [[Column([[Text('Account:')],
- [Input(key='-ACCOUNT-IN-', size=(19, 1))],
- [Text('User Id:')],
- [Input(key='-USERID-IN-', size=(19, 1)), Button('Copy', key='-USERID-')],
- [Text('Password:')],
- [Input(key='-PW-IN-', size=(19, 1)), Button('Copy', key='-PASS-')],
- [Text('Location:')],
- [Input(key='-LOC-IN-', size=(19, 1)), Button('Copy', key='-LOC')],
- [Text('Notes:')],
- [Multiline(key='-NOTES-', size=(25, 5))],
- ], size=(235,350),pad=(0,0))]])],], pad=(0,0))
+ [Input(key='-ACCOUNT-IN-', size=(19, 1))],
+ [Text('User Id:')],
+ [Input(key='-USERID-IN-', size=(19, 1)),
+ Button('Copy', key='-USERID-')],
+ [Text('Password:')],
+ [Input(key='-PW-IN-', size=(19, 1)),
+ Button('Copy', key='-PASS-')],
+ [Text('Location:')],
+ [Input(key='-LOC-IN-', size=(19, 1)),
+ Button('Copy', key='-LOC')],
+ [Text('Notes:')],
+ [Multiline(key='-NOTES-', size=(25, 5))],
+ ], size=(235, 350), pad=(0, 0))]])], ], pad=(0, 0))
-col3 = Column([[Frame('Actions:', [[Column([[Button('Save'), Button('Clear'), Button('Delete'),]], size=(450,45), pad=(0,0))]])]], pad=(0,0))
+col3 = Column([[Frame('Actions:', [[Column([[Button('Save'), Button(
+ 'Clear'), Button('Delete'), ]], size=(450, 45), pad=(0, 0))]])]], pad=(0, 0))
-layout = [ [col1, col2],
- [col3]]
+layout = [[col1, col2], [col3]]
window = Window('Passwords', layout)
@@ -45,4 +59,5 @@ while True:
print(event, values)
if event is None:
break
+
window.close()
diff --git a/DemoPrograms/Demo_Columns.py b/DemoPrograms/Demo_Columns.py
index 481751cb..d4489d06 100644
--- a/DemoPrograms/Demo_Columns.py
+++ b/DemoPrograms/Demo_Columns.py
@@ -1,25 +1,28 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-sg.ChangeLookAndFeel('BlueMono')
+'''
+ Usage of Column Element
+'''
+sg.change_look_and_feel('BlueMono')
+
+css = {'text_color': 'white', 'background_color': 'blue'}
# Column layout
-col = [[sg.Text('col Row 1', text_color='white', background_color='blue')],
- [sg.Text('col Row 2', text_color='white', background_color='blue'), sg.Input('col input 1')],
- [sg.Text('col Row 3', text_color='white', background_color='blue'), sg.Input('col input 2')]]
+col = [[sg.Text('col Row 1', **css)],
+ [sg.Text('col Row 2', **css), sg.Input('col input 1')],
+ [sg.Text('col Row 3', **css), sg.Input('col input 2')]]
# Window layout
layout = [[sg.Listbox(values=('Listbox Item 1', 'Listbox Item 2', 'Listbox Item 3'),
select_mode=sg.LISTBOX_SELECT_MODE_MULTIPLE, size=(20, 3)),
- sg.Column(col, background_color='blue')],
+ sg.Col(col, background_color='blue')],
[sg.Input('Last input')],
[sg.OK()]]
# Display the window and get values
-event, values = sg.Window('Compact 1-line form with column').Layout(layout).Read()
+window = sg.Window('Compact 1-line form with column', layout)
+event, values = window.read()
-sg.Popup(event, values, line_width=200)
+sg.popup(event, values, line_width=200)
+window.close()
diff --git a/DemoPrograms/Demo_Compact_Layouts_Element_Renaming.py b/DemoPrograms/Demo_Compact_Layouts_Element_Renaming.py
index 589375a3..4bebce3e 100644
--- a/DemoPrograms/Demo_Compact_Layouts_Element_Renaming.py
+++ b/DemoPrograms/Demo_Compact_Layouts_Element_Renaming.py
@@ -1,7 +1,4 @@
import PySimpleGUI as sg
-# Import the elements individually to save space
-from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, Check, Button, B, Btn, ButtonMenu,BMenu, Canvas, Column, Col, Combo, DropDown, Drop, DD, Frame, Graph, Image, InputText, Input, In, I, Listbox, LBox, LB, Menu, Multiline, ML, MLine, OptionMenu, Output, Pane, ProgressBar, Prog, PBar, Radio, R, Rad, Sizer, Slider, Spin, StatusBar, Tab, TabGroup, Table, Text, Txt, T, Tree, TreeData, VerticalSeparator, Window, Print
-
"""
Demo - Compact Layouts and Element Renaming
@@ -10,7 +7,7 @@ from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, C
programmers, there is little additional knowledge to be gained by writing
sg.Text('My text')
rather than using one of the shortcuts such as
- sg.T('My text')
+ sg.Text('My text')
However, even with shortcut usage, you continue to have the package prefix of
sg.
That's 3 characters per element that are added to your layout!
@@ -19,7 +16,7 @@ from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, C
If you don't want to use that very-long import or perhaps want to use your own shortcut names, you can easily
create your shortcut by simple assignment:
- T = sg.Text
+ T = sg.T
This enables you to use T just as if you imported the Class T from PySimpleGUI. You could develop your own
template that you copy and paste at the top of all of your PySimpleGUI programs. Or perhaps perform an import
of those assignments from a .py file you create.
@@ -37,11 +34,14 @@ from PySimpleGUI import InputCombo, Combo, Multiline, ML, MLine, Checkbox, CB, C
# A user created shortcut....
# Suppose this user's layout contains many Multiline Elements. It could be advantageous to have a single letter
# shortcut version for Multiline
-M = sg.Multiline
+M = sg.MLine
+B = sg.B
# This layout uses the user defined "M" element as well as the PySimpleGUI Button shortcut, B.
-layout = [[M(size=(30,3))],
+layout = [[M(size=(30, 3))],
[B('OK')]]
-event, values = Window('Shortcuts', layout).read()
+window = sg.Window('Shortcuts', layout).read()
+event, values = window.read()
sg.popup_scrolled(event, values)
+window.close()
diff --git a/DemoPrograms/Demo_Compare_Files.py b/DemoPrograms/Demo_Compare_Files.py
index 77ab262d..a5769877 100644
--- a/DemoPrograms/Demo_Compare_Files.py
+++ b/DemoPrograms/Demo_Compare_Files.py
@@ -1,30 +1,32 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-# sg.SetOptions(button_color=sg.COLOR_SYSTEM_DEFAULT)
+'''
+ Simple "diff" in PySimpleGUI
+'''
def GetFilesToCompare():
form_rows = [[sg.Text('Enter 2 files to comare')],
- [sg.Text('File 1', size=(15, 1)), sg.InputText(key='file1'), sg.FileBrowse()],
- [sg.Text('File 2', size=(15, 1)), sg.InputText(key='file2'), sg.FileBrowse(target='file2')],
+ [sg.Text('File 1', size=(15, 1)),
+ sg.InputText(key='-file1-'), sg.FileBrowse()],
+ [sg.Text('File 2', size=(15, 1)), sg.InputText(key='-file2-'),
+ sg.FileBrowse(target='file2')],
[sg.Submit(), sg.Cancel()]]
- window = sg.Window('File Compare')
- event, values = window.Layout(form_rows).Read()
+ window = sg.Window('File Compare', form_rows)
+ event, values = window.read()
+ window.close()
return event, values
-def main():
- button, values = GetFilesToCompare()
- f1 = values['file1']
- f2 = values['file2']
- if any((button != 'Submit', f1 =='', f2 == '')):
- sg.PopupError('Operation cancelled')
- sys.exit(69)
+def main():
+
+ button, values = GetFilesToCompare()
+ f1, f2 = values['-file1-'], values['-file2-']
+
+ if any((button != 'Submit', f1 == '', f2 == '')):
+ sg.popup_error('Operation cancelled')
+ return
# --- This portion of the code is not GUI related ---
with open(f1, 'rb') as file1:
@@ -34,11 +36,12 @@ def main():
for i, x in enumerate(a):
if x != b[i]:
- sg.Popup('Compare results for files', f1, f2, '**** Mismatch at offset {} ****'.format(i))
+ sg.popup('Compare results for files', f1, f2,
+ '**** Mismatch at offset {} ****'.format(i))
break
else:
if len(a) == len(b):
- sg.Popup('**** The files are IDENTICAL ****')
+ sg.popup('**** The files are IDENTICAL ****')
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Conways_Game_of_Life.py b/DemoPrograms/Demo_Conways_Game_of_Life.py
index ece57f65..cd100e9d 100644
--- a/DemoPrograms/Demo_Conways_Game_of_Life.py
+++ b/DemoPrograms/Demo_Conways_Game_of_Life.py
@@ -23,11 +23,11 @@
# along with this program. If not, see .
import numpy
-import PySimpleGUI as sg # Take your pick! Tkinter
-# import PySimpleGUIWeb as sg # Or the Web! (Remi!)
+import PySimpleGUI as sg
BOX_SIZE = 15
+
class GameOfLife:
def __init__(self, N=20, T=200):
@@ -98,40 +98,56 @@ class GameOfLife:
self.t += 1
def init_graphics(self):
- self.graph = sg.Graph((600, 600), (0, 0), (450, 450), key='_GRAPH_', change_submits=True, drag_submits=False, background_color='lightblue')
+ self.graph = sg.Graph((600, 600), (0, 0), (450, 450),
+ key='-GRAPH-',
+ change_submits=True,
+ drag_submits=False,
+ background_color='lightblue')
layout = [
- [sg.Text('Game of Life ', font='ANY 15'), sg.Text('', key='_OUTPUT_', size=(30,1), font='ANY 15')],
+ [sg.Text('Game of Life', font='ANY 15'),
+ sg.Text('', key='-OUTPUT-', size=(30, 1), font='ANY 15')],
[self.graph],
- [sg.Button('Go!', key='_DONE_'),
- sg.Text(' Delay (ms)') , sg.Slider([0,800], orientation='h', key='_SLIDER_', enable_events=True, size=(15,15)), sg.T('', size=(3,1), key='_S1_OUT_'),
- sg.Text(' Num Generations'), sg.Slider([0, 20000],default_value=4000, orientation='h',size=(15,15),enable_events=True, key='_SLIDER2_'), sg.T('', size=(3,1), key='_S2_OUT_')]
+ [sg.Button('Go!', key='-DONE-'),
+ sg.Text(' Delay (ms)'),
+ sg.Slider([0, 800],
+ orientation='h',
+ key='-SLIDER-',
+ enable_events=True,
+ size=(15, 15)),
+ sg.Text('', size=(3, 1), key='-S1-OUT-'),
+ sg.Text(' Num Generations'), sg.Slider([0, 20000],
+ default_value=4000,
+ orientation='h',
+ size=(15, 15),
+ enable_events=True,
+ key='-SLIDER2-'),
+ sg.Text('', size=(3, 1), key='-S2-OUT-')]
]
- self.window = sg.Window('Window Title', ).Layout(layout).Finalize()
- event, values = self.window.Read(timeout=0)
- self.delay = values['_SLIDER_']
- self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
- self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
-
+ self.window = sg.Window('Window Title', layout, finalize=True)
+ event, values = self.window.read(timeout=0)
+ self.delay = values['-SLIDER-']
+ self.window['-S1-OUT-'].update(values['-SLIDER-'])
+ self.window['-S2-OUT-'].update(values['-SLIDER2-'])
def draw_board(self):
BOX_SIZE = 15
- self.graph.Erase()
+ self.graph.erase()
for i in range(self.N):
for j in range(self.N):
if self.old_grid[i][j]:
- self.graph.DrawRectangle((i * BOX_SIZE, j * BOX_SIZE),
- (i * BOX_SIZE + BOX_SIZE, j * (BOX_SIZE) + BOX_SIZE),
- line_color='black', fill_color='yellow')
- event, values = self.window.Read(timeout=self.delay)
- if event in (None, '_DONE_'):
- exit()
- self.delay = values['_SLIDER_']
- self.T = int(values['_SLIDER2_'])
- self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
- self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
- self.window.Element('_OUTPUT_').Update('Generation {}'.format(self.t))
-
+ self.graph.draw_rectangle((i * BOX_SIZE, j * BOX_SIZE),
+ (i * BOX_SIZE + BOX_SIZE,
+ j * (BOX_SIZE) + BOX_SIZE),
+ line_color='black', fill_color='yellow')
+ event, values = self.window.read(timeout=self.delay)
+ if event in (None, '-DONE-'):
+ return
+ self.delay = values['-SLIDER-']
+ self.T = int(values['-SLIDER2-'])
+ self.window['-S1-OUT-'].update(values['-SLIDER-'])
+ self.window['-S2-OUT-'].update(values['-SLIDER2-'])
+ self.window['-OUTPUT-'].update('Generation {}'.format(self.t))
def manual_board_setup(self):
ids = []
@@ -140,31 +156,33 @@ class GameOfLife:
for j in range(self.N):
ids[i].append(0)
while True: # Event Loop
- event, values = self.window.Read()
- if event is None or event == '_DONE_':
+ event, values = self.window.read()
+ if event is None or event == '-DONE-':
break
- self.window.Element('_S1_OUT_').Update(values['_SLIDER_'])
- self.window.Element('_S2_OUT_').Update(values['_SLIDER2_'])
- mouse = values['_GRAPH_']
+ self.window['-S1-OUT-'].update(values['-SLIDER-'])
+ self.window['-S2-OUT-'].update(values['-SLIDER2-'])
+ mouse = values['-GRAPH-']
- if event == '_GRAPH_':
+ if event == '-GRAPH-':
if mouse == (None, None):
continue
box_x = mouse[0] // BOX_SIZE
box_y = mouse[1] // BOX_SIZE
if self.old_grid[box_x][box_y] == 1:
- id = ids[box_x][box_y]
- self.graph.DeleteFigure(id)
+ id_val = ids[box_x][box_y]
+ self.graph.delete_figure(id_val)
self.old_grid[box_x][box_y] = 0
else:
- id = self.graph.DrawRectangle((box_x * BOX_SIZE, box_y * BOX_SIZE),
- (box_x * BOX_SIZE + BOX_SIZE, box_y * (BOX_SIZE) + BOX_SIZE),
- line_color='black', fill_color='yellow')
- ids[box_x][box_y] = id
+ id_val = self.graph.draw_rectangle((box_x * BOX_SIZE, box_y * BOX_SIZE),
+ (box_x * BOX_SIZE + BOX_SIZE,
+ box_y * (BOX_SIZE) + BOX_SIZE),
+ line_color='black', fill_color='yellow')
+ ids[box_x][box_y] = id_val
self.old_grid[box_x][box_y] = 1
- self.window.Element('_DONE_').Update(text='Exit')
+ self.window['-DONE-'].update(text='Exit')
+
if (__name__ == "__main__"):
game = GameOfLife(N=35, T=200)
game.play()
- game.window.Close()
\ No newline at end of file
+ game.window.close()
diff --git a/DemoPrograms/Demo_Crossword_Puzzle.py b/DemoPrograms/Demo_Crossword_Puzzle.py
index 62dd7b4e..c01d78e4 100644
--- a/DemoPrograms/Demo_Crossword_Puzzle.py
+++ b/DemoPrograms/Demo_Crossword_Puzzle.py
@@ -1,9 +1,4 @@
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
- # import PySimpleGUIWeb as sg # take your pick of ports. Runs on both
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import random
import string
@@ -17,38 +12,41 @@ import string
BOX_SIZE = 25
layout = [
- [sg.Text('Crossword Puzzle Using PySimpleGUI'), sg.Text('', key='_OUTPUT_')],
- [sg.Graph((800,800), (0,450), (450,0), key='_GRAPH_', change_submits=True, drag_submits=False)],
- [sg.Button('Show'), sg.Button('Exit')]
- ]
+ [sg.Text('Crossword Puzzle Using PySimpleGUI'), sg.Text('', key='-OUTPUT-')],
+ [sg.Graph((800, 800), (0, 450), (450, 0), key='-GRAPH-',
+ change_submits=True, drag_submits=False)],
+ [sg.Button('Show'), sg.Button('Exit')]
+]
-window = sg.Window('Window Title', ).Layout(layout).Finalize()
+window = sg.Window('Window Title', layout, finalize=True)
-g = window.FindElement('_GRAPH_')
+g = window['-GRAPH-']
for row in range(16):
for col in range(16):
- if random.randint(0,100) > 10:
- g.DrawRectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black')
+ if random.randint(0, 100) > 10:
+ g.draw_rectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black')
else:
- g.DrawRectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black', fill_color='black')
+ g.draw_rectangle((col * BOX_SIZE + 5, row * BOX_SIZE + 3), (col * BOX_SIZE + BOX_SIZE + 5, row * BOX_SIZE + BOX_SIZE + 3), line_color='black', fill_color='black')
- g.DrawText('{}'.format(row * 6 + col + 1), (col * BOX_SIZE + 10, row * BOX_SIZE + 8))
+ g.draw_text('{}'.format(row * 6 + col + 1),
+ (col * BOX_SIZE + 10, row * BOX_SIZE + 8))
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
- mouse = values['_GRAPH_']
+ mouse = values['-GRAPH-']
- if event == '_GRAPH_':
+ if event == '-GRAPH-':
if mouse == (None, None):
continue
box_x = mouse[0]//BOX_SIZE
box_y = mouse[1]//BOX_SIZE
letter_location = (box_x * BOX_SIZE + 18, box_y * BOX_SIZE + 17)
print(box_x, box_y)
- g.DrawText('{}'.format(random.choice(string.ascii_uppercase)), letter_location, font='Courier 25')
+ g.draw_text('{}'.format(random.choice(string.ascii_uppercase)),
+ letter_location, font='Courier 25')
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_DOC_Viewer_PIL.py b/DemoPrograms/Demo_DOC_Viewer_PIL.py
index 50c6f0c8..ae24bad5 100644
--- a/DemoPrograms/Demo_DOC_Viewer_PIL.py
+++ b/DemoPrograms/Demo_DOC_Viewer_PIL.py
@@ -32,31 +32,30 @@ pixmaps and page re-visits will re-use a once-created display list.
import sys
import fitz
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
import tkinter as tk
from PIL import Image, ImageTk
import time
if len(sys.argv) == 1:
- fname = sg.PopupGetFile('Document Browser', 'Document file to open', no_window=True,
- file_types = (
- ("PDF Files", "*.pdf"),
- ("XPS Files", "*.*xps"),
- ("Epub Files", "*.epub"),
- ("Fiction Books", "*.fb2"),
- ("Comic Books", "*.cbz"),
- ("HTML", "*.htm*")
- # add more document types here
- )
- )
+ fname = sg.popup_get_file('Document Browser', 'Document file to open',
+ no_window=True,
+ file_types=(
+ ("PDF Files", "*.pdf"),
+ ("XPS Files", "*.*xps"),
+ ("Epub Files", "*.epub"),
+ ("Fiction Books", "*.fb2"),
+ ("Comic Books", "*.cbz"),
+ ("HTML", "*.htm*")
+ # add more document types here
+ )
+ )
else:
fname = sys.argv[1]
if not fname:
- sg.Popup("Cancelling:", "No filename supplied")
+ sg.popup("Cancelling:", "No filename supplied")
raise SystemExit("Cancelled: no filename supplied")
doc = fitz.open(fname)
@@ -64,15 +63,16 @@ page_count = len(doc)
# used for response time statistics only
fitz_img_time = 0.0
-tk_img_time = 0.0
-img_count = 1
+tk_img_time = 0.0
+img_count = 1
# allocate 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 = False, max_size = None, first = False):
+
+def get_page(pno, zoom=False, max_size=None, first=False):
"""Return a PNG image for a document page number.
"""
dlist = dlist_tab[pno] # get display list of page number
@@ -91,7 +91,7 @@ def get_page(pno, zoom = False, max_size = None, first = False):
mat_0 = fitz.Matrix(zoom_0, zoom_0)
if not zoom: # show total page
- pix = dlist.getPixmap(matrix = mat_0, alpha=False)
+ pix = dlist.getPixmap(matrix=mat_0, alpha=False)
else:
mp = r.tl + (r.br - r.tl) * 0.5 # page rect center
w2 = r.width / 2
@@ -125,22 +125,15 @@ max_size = (max_width, max_height)
root.destroy()
del root
-window = sg.Window(title, return_keyboard_events = True,
- location = (0,0), use_default_focus = False, no_titlebar=False)
cur_page = 0
-data, clip_pos = get_page(cur_page,
- zoom = False,
- max_size = max_size,
- first = True)
+data, clip_pos = get_page(cur_page, zoom=False, max_size=max_size, first=True)
-image_elem = sg.Image(data = data)
+image_elem = sg.Image(data=data)
-goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True,
- key = "PageNumber")
+goto = sg.InputText(str(cur_page + 1), size=(5, 1), key="-PageNumber-")
-layout = [
- [
+layout = [[
sg.ReadButton('Next'),
sg.ReadButton('Prev'),
sg.Text('Page:'),
@@ -152,17 +145,18 @@ layout = [
[image_elem],
]
-window.Layout(layout)
+window = sg.Window(title, layout, return_keyboard_events=True,
+ location=(0, 0), use_default_focus=False, no_titlebar=False)
# now define the buttons / events we want to handle
enter_buttons = [chr(13), "Return:13"]
quit_buttons = ["Escape:27", chr(27)]
next_buttons = ["Next", "Next:34", "MouseWheel:Down"]
prev_buttons = ["Prev", "Prior:33", "MouseWheel:Up"]
-Up = "Up:38"
-Left = "Left:37"
+Up = "Up:38"
+Left = "Left:37"
Right = "Right:39"
-Down = "Down:40"
+Down = "Down:40"
zoom_buttons = ["Zoom", Up, Down, Left, Right]
# all the buttons we will handle
@@ -173,8 +167,8 @@ old_page = 0
old_zoom = False
while True:
- event, value = window.Read()
- if event is None and (value is None or value['PageNumber'] is None):
+ event, value = window.read()
+ if event is None and (value is None or value['-PageNumber-'] is None):
break
if event in quit_buttons:
break
@@ -184,7 +178,7 @@ while True:
if event in enter_buttons:
try:
- cur_page = int(value['PageNumber']) - 1 # check if valid
+ cur_page = int(value['-PageNumber-']) - 1 # check if valid
while cur_page < 0:
cur_page += page_count
except:
@@ -216,21 +210,22 @@ while True:
zoom = zoom_pressed = old_zoom = False
t0 = time.perf_counter()
- data, clip_pos = get_page(cur_page, zoom = zoom, max_size = max_size,
- first = False)
+ data, clip_pos = get_page(cur_page, zoom=zoom, max_size=max_size,
+ first=False)
t1 = time.perf_counter()
- image_elem.Update(data = data)
+ image_elem.update(data=data)
t2 = time.perf_counter()
fitz_img_time += t1 - t0
- tk_img_time += t2 - t1
- img_count += 1
+ tk_img_time += t2 - t1
+ img_count += 1
old_page = cur_page
old_zoom = zoom_pressed or zoom
# update page number field
if event in my_keys:
- goto.Update(str(cur_page + 1))
+ goto.update(str(cur_page + 1))
+window.close()
# print some response time statistics
if img_count > 0:
diff --git a/DemoPrograms/Demo_Debugger_Built_Into_PSG.py b/DemoPrograms/Demo_Debugger_Built_Into_PSG.py
index 22e4b9ae..79764b3c 100644
--- a/DemoPrograms/Demo_Debugger_Built_Into_PSG.py
+++ b/DemoPrograms/Demo_Debugger_Built_Into_PSG.py
@@ -1,5 +1,4 @@
import PySimpleGUI as sg
-# import imwatchingyou # Not needed because using the one inside PySimpleGUI.py code itself
"""
Demo program that shows you how to integrate the PySimpleGUI Debugger
@@ -20,36 +19,38 @@ import PySimpleGUI as sg
"""
layout = [
- [sg.T('A typical PSG application')],
- [sg.In(key='_IN_')],
- [sg.T(' ', key='_OUT_', size=(45,1))],
- [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
- [sg.Radio('a',1, key='_R1_'), sg.Radio('b',1, key='_R2_'), sg.Radio('c',1, key='_R3_')],
- [sg.Combo(['c1', 'c2', 'c3'], size=(6,3), key='_COMBO_')],
- [sg.Output(size=(50,6))],
- [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Button('Popout'), sg.Button('Debugger'), sg.Debug(key='Debug')],
- ]
+ [sg.Text('A typical PSG application')],
+ [sg.Input(key='-IN-')],
+ [sg.Text(' ', key='-OUT-', size=(45, 1))],
+ [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
+ [sg.Radio('a', 1, key='-R1-'), sg.Radio('b', 1, key='-R2-'),
+ sg.Radio('c', 1, key='-R3-')],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6, 3), key='-COMBO-')],
+ [sg.Output(size=(50, 6))],
+ [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Button('Popout'),
+ sg.Button('Debugger'), sg.Debug(key='Debug')],
+]
-window = sg.Window('This is your Application Window', layout, debugger_enabled=False)
+window = sg.Window('This is your Application Window',
+ layout, debugger_enabled=False)
counter = 0
-timeout = 100
-
# Note that you can launch the debugger windows right away, without waiting for user input
sg.show_debugger_popout_window()
while True: # Your Event Loop
- event, values = window.Read(timeout=timeout)
+ event, values = window.read(timeout=100)
if event in (None, 'Exit'):
break
elif event == 'Enable':
- window.EnableDebugger()
+ window.enable_debugger()
elif event == 'Popout':
- sg.show_debugger_popout_window() # replaces old popout with a new one, possibly with new variables`
+ # replaces old popout with a new one, possibly with new variables`
+ sg.show_debugger_popout_window()
elif event == 'Debugger':
sg.show_debugger_window()
counter += 1
# to prove window is operating, show the input in another area in the window.
- window.Element('_OUT_').Update(values['_IN_'])
+ window['-OUT-'].update(values['-IN-'])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Debugger_Button.py b/DemoPrograms/Demo_Debugger_Button.py
index b2b482d6..2f56ec43 100644
--- a/DemoPrograms/Demo_Debugger_Button.py
+++ b/DemoPrograms/Demo_Debugger_Button.py
@@ -20,29 +20,29 @@ import PySimpleGUI as sg
"""
layout = [
- [sg.T('A typical PSG application')],
- [sg.In(key='_IN_')],
- [sg.T(' ', key='_OUT_', size=(45,1))],
- [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
- [sg.Radio('a',1, key='_R1_'), sg.Radio('b',1, key='_R2_'), sg.Radio('c',1, key='_R3_')],
- [sg.Combo(['c1', 'c2', 'c3'], size=(6,3), key='_COMBO_')],
- [sg.Output(size=(50,6))],
- [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Debug(key='Debug')],
- ]
-
-window = sg.Window('This is your Application Window', layout, debugger_enabled=False)
+ [sg.Text('A typical PSG application')],
+ [sg.Input(key='-IN-')],
+ [sg.Text(' ', key='-OUT-', size=(45, 1))],
+ [sg.CBox('Checkbox 1'), sg.CBox('Checkbox 2')],
+ [sg.Radio('a', 1, key='-R1-'), sg.Radio('b', 1, key='-R2-'),
+ sg.Radio('c', 1, key='-R3-')],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6, 3), key='-COMBO-')],
+ [sg.Output(size=(50, 6))],
+ [sg.Ok(), sg.Exit(), sg.Button('Enable'), sg.Debug(key='Debug')],
+]
+window = sg.Window('This is your Application Window',
+ layout, debugger_enabled=False)
counter = 0
-timeout = 100
while True: # Your Event Loop
- event, values = window.Read(timeout=timeout)
+ event, values = window.read(timeout=100)
if event in (None, 'Exit'):
break
elif event == 'Enable':
- window.EnableDebugger()
+ window.enable_debugger()
counter += 1
# to prove window is operating, show the input in another area in the window.
- window.Element('_OUT_').Update(values['_IN_'])
+ window['-OUT-'].update(values['-IN-'])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Debugger_ImWatchingYou.py b/DemoPrograms/Demo_Debugger_ImWatchingYou.py
index 12239a7e..ce59c1df 100644
--- a/DemoPrograms/Demo_Debugger_ImWatchingYou.py
+++ b/DemoPrograms/Demo_Debugger_ImWatchingYou.py
@@ -20,11 +20,15 @@ import imwatchingyou # STEP 1
"""
layout = [
- [sg.T('A typical PSG application')],
- [sg.In(key='_IN_')],
- [sg.T(' ', key='_OUT_', size=(30, 1))],
- [sg.Radio('a', 1, key='_R1_'), sg.Radio('b', 1, key='_R2_'), sg.Radio('c', 1, key='_R3_')],
- [sg.Combo(['c1', 'c2', 'c3'], size=(6, 3), key='_COMBO_')],
+ [sg.Text('A typical PSG application')],
+ [sg.Input(key='-IN-')],
+ [sg.Text(' ', key='-OUT-', size=(30, 1))],
+ [
+ sg.Rad('a', 1, key='-R1-'),
+ sg.Rad('b', 1, key='-R2-'),
+ sg.Rad('c', 1, key='-R3-')
+ ],
+ [sg.Combo(['c1', 'c2', 'c3'], size=(6, 3), key='-COMBO-')],
[sg.Output(size=(50, 6))],
[sg.Ok(), sg.Exit(), sg.Button('Debug'), sg.Button('Popout')],
]
@@ -32,10 +36,9 @@ layout = [
window = sg.Window('This is your Application Window', layout)
counter = 0
-timeout = 100
while True: # Your Event Loop
- event, values = window.Read(timeout=timeout)
+ event, values = window.read(timeout=100)
if event in (None, 'Exit'):
break
elif event == 'Ok':
@@ -46,9 +49,9 @@ while True: # Your Event Loop
imwatchingyou.show_debugger_popout_window() # STEP 2
counter += 1
# to prove window is operating, show the input in another area in the window.
- window.Element('_OUT_').Update(values['_IN_'])
+ window['-OUT-'].update(values['-IN-'])
# don't worry about the "state" of things, just call this function "frequently"
imwatchingyou.refresh_debugger() # STEP 3 - refresh debugger
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py
index 22b8cf55..57da54ca 100644
--- a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py
+++ b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows.py
@@ -1,9 +1,4 @@
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
"""
Demo - Running 2 windows with both being active at the same time
Three important things to note about this design patter:
@@ -15,45 +10,44 @@ else:
# Window 1 layout
layout = [
- [sg.Text('This is the FIRST WINDOW'), sg.Text(' ', key='_OUTPUT_')],
+ [sg.Text('This is the FIRST WINDOW'), sg.Text(' ', key='-OUTPUT-')],
[sg.Text('')],
- [sg.Button('Launch 2nd Window'),sg.Button('Popup'), sg.Button('Exit')]
+ [sg.Button('Launch 2nd Window'), sg.Button('Popup'), sg.Button('Exit')]
]
-window = sg.Window('Window Title', location=(800,600)).Layout(layout)
+window = sg.Window('Window Title', layout, location=(800,600))
win2_active = False
i=0
while True: # Event Loop
- event, values = window.Read(timeout=100)
+ event, values = window.read(timeout=100)
if event != sg.TIMEOUT_KEY:
print(i, event, values)
-
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
elif event == 'Popup':
- sg.Popup('This is a BLOCKING popup','all windows remain inactive while popup active')
+ sg.popup('This is a BLOCKING popup','all windows remain inactive while popup active')
i+=1
if event == 'Launch 2nd Window' and not win2_active: # only run if not already showing a window2
win2_active = True
# window 2 layout - note - must be "new" every time a window is created
layout2 = [
- [sg.Text('The second window'), sg.Text('', key='_OUTPUT_')],
- [sg.Input(do_not_clear=True, key='_IN_')],
+ [sg.Text('The second window'), sg.Text('', key='-OUTPUT-')],
+ [sg.Input('', key='-IN-')],
[sg.Button('Show'), sg.Button('Exit')]
]
- window2 = sg.Window('Second Window').Layout(layout2)
+ window2 = sg.Window('Second Window', layout2)
# Read window 2's events. Must use timeout of 0
if win2_active:
# print("reading 2")
- event, values = window2.Read(timeout=100)
+ event, values = window2.read(timeout=100)
# print("win2 ", event)
if event != sg.TIMEOUT_KEY:
print("win2 ", event)
if event == 'Exit' or event is None:
# print("Closing window 2", event)
win2_active = False
- window2.Close()
+ window2.close()
if event == 'Show':
- sg.Popup('You entered ', values['_IN_'])
+ sg.popup('You entered ', values['-IN-'])
-window.Close()
\ No newline at end of file
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py
index f2328e33..030d2d9e 100644
--- a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py
+++ b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py
@@ -1,35 +1,39 @@
+import PySimpleGUI as sg
"""
PySimpleGUI The Complete Course
Lesson 7 - Multiple Windows
+ 1-lvl nested window
"""
-import PySimpleGUI as sg
# Design pattern 1 - First window does not remain active
layout = [[ sg.Text('Window 1'),],
- [sg.Input(do_not_clear=True)],
- [sg.Text('', size=(20,1), key='_OUTPUT_')],
+ [sg.Input()],
+ [sg.Text('', size=(20,1), key='-OUTPUT-')],
[sg.Button('Launch 2')]]
-win1 = sg.Window('Window 1').Layout(layout)
-win2_active=False
-while True:
- ev1, vals1 = win1.Read(timeout=100)
- if ev1 is None:
- break
- win1.FindElement('_OUTPUT_').Update(vals1[0])
+window1 = sg.Window('Window 1', layout)
+window2_active=False
- if ev1 == 'Launch 2' and not win2_active:
- win2_active = True
- win1.Hide()
+while True:
+ event1, values1 = window1.read(timeout=100)
+ if event1 is None:
+ break
+ window1['-OUTPUT-'].update(values1[0])
+
+ if event1 == 'Launch 2' and not window2_active:
+ window2_active = True
+ window1.hide()
layout2 = [[sg.Text('Window 2')],
[sg.Button('Exit')]]
- win2 = sg.Window('Window 2').Layout(layout2)
+ window2 = sg.Window('Window 2', layout2)
while True:
- ev2, vals2 = win2.Read()
+ ev2, vals2 = window2.read()
if ev2 is None or ev2 == 'Exit':
- win2.Close()
- win2_active = False
- win1.UnHide()
+ window2.close()
+ window2_active = False
+ window1.un_hide()
break
+window1.close()
+
diff --git a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py
index 41bdad76..1cca3dc1 100644
--- a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py
+++ b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows2.py
@@ -1,34 +1,37 @@
-"""
- PySimpleGUI The Complete Course
- Lesson 7 - Multiple Windows
-"""
import PySimpleGUI as sg
+"""
+ PySimpleGUI The Complete Course
+ Lesson 7
+ Multiple Independent Windows
+"""
# Design pattern 2 - First window remains active
layout = [[ sg.Text('Window 1'),],
- [sg.Input(do_not_clear=True)],
- [sg.Text('', size=(20,1), key='_OUTPUT_')],
+ [sg.Input('')],
+ [sg.Text('', size=(20,1), key='-OUTPUT-')],
[sg.Button('Launch 2'), sg.Button('Exit')]]
-win1 = sg.Window('Window 1').Layout(layout)
+window1 = sg.Window('Window 1', layout)
-win2_active = False
+window2_active = False
while True:
- ev1, vals1 = win1.Read(timeout=100)
- win1.FindElement('_OUTPUT_').Update(vals1[0])
- if ev1 is None or ev1 == 'Exit':
+ event1, values1 = window1.read(timeout=100)
+ window1['-OUTPUT-'].update(values1[0])
+ if event1 is None or event1 == 'Exit':
break
- if not win2_active and ev1 == 'Launch 2':
- win2_active = True
+ if not window2_active and event1 == 'Launch 2':
+ window2_active = True
layout2 = [[sg.Text('Window 2')],
[sg.Button('Exit')]]
- win2 = sg.Window('Window 2').Layout(layout2)
+ window2 = sg.Window('Window 2', layout2)
- if win2_active:
- ev2, vals2 = win2.Read(timeout=100)
+ if window2_active:
+ ev2, vals2 = window2.read(timeout=100)
if ev2 is None or ev2 == 'Exit':
- win2_active = False
- win2.Close()
+ window2_active = False
+ window2.close()
+
+window1.close()
diff --git a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows3.py b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows3.py
index b5500cec..c19b2425 100644
--- a/DemoPrograms/Demo_Design_Pattern_Multiple_Windows3.py
+++ b/DemoPrograms/Demo_Design_Pattern_Multiple_Windows3.py
@@ -1,48 +1,54 @@
import PySimpleGUI as sg
+'''
+ Example of wizard-like PySimpleGUI windows
+'''
+
layout = [[sg.Text('Window 1'), ],
- [sg.Input(do_not_clear=True)],
- [sg.Text('',size=(20,1), key='_OUTPUT_')],
+ [sg.Input('')],
+ [sg.Text('',size=(20,1), key='-OUTPUT-')],
[sg.Button('Next >'), sg.Button('Exit')]]
-win1 = sg.Window('Window 1').Layout(layout)
+window = sg.Window('Window 1', layout)
-win3_active = win2_active = False
+window3_active = window2_active = False
while True:
- if not win2_active:
- ev1, vals1 = win1.Read()
- if ev1 is None or ev1 == 'Exit':
+ if not window2_active:
+ event1, values1 = window.read()
+ if event1 is None or event1 == 'Exit':
break
- win1.FindElement('_OUTPUT_').Update(vals1[0])
+ window['-OUTPUT-'].update(values1[0])
- if not win2_active and ev1 == 'Next >':
- win2_active = True
- win1.Hide()
+ if not window2_active and event1 == 'Next >':
+ window2_active = True
+ window.hide()
layout2 = [[sg.Text('Window 2')],
[sg.Button('< Prev'), sg.Button('Next >')]]
- win2 = sg.Window('Window 2').Layout(layout2)
+ window2 = sg.Window('Window 2', layout2)
- if win2_active:
- ev2, vals2 = win2.Read()
- if ev2 in (None, 'Exit', '< Prev'):
- win2_active = False
- win2.Close()
- win1.UnHide()
- elif ev2 == 'Next >':
- win3_active = True
- win2_active = False
- win2.Hide()
+ if window2_active:
+ event2 = window2.read()[0]
+ if event2 in (None, 'Exit', '< Prev'):
+ window2_active = False
+ window2.close()
+ window.un_hide()
+ elif event2 == 'Next >':
+ window3_active = True
+ window2_active = False
+ window2.hide()
layout3 = [[sg.Text('Window 3')],
[sg.Button('< Prev'), sg.Button('Exit')]]
- win3 = sg.Window('Window 3').Layout(layout3)
+ window3 = sg.Window('Window 3', layout3)
- if win3_active:
- ev3, vals3 = win3.Read()
+ if window3_active:
+ ev3, vals3 = window3.read()
if ev3 == '< Prev':
- win3.Close()
- win3_active = False
- win2_active = True
- win2.UnHide()
+ window3.close()
+ window3_active = False
+ window2_active = True
+ window2.un_hide()
elif ev3 in (None, 'Exit'):
break
+
+window.close()
diff --git a/DemoPrograms/Demo_Design_Pattern_Persistent_Window.py b/DemoPrograms/Demo_Design_Pattern_Persistent_Window.py
index 7d137458..45fa657d 100644
--- a/DemoPrograms/Demo_Design_Pattern_Persistent_Window.py
+++ b/DemoPrograms/Demo_Design_Pattern_Persistent_Window.py
@@ -1,24 +1,21 @@
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-layout = [
- [sg.Text('Your typed chars appear here:'), sg.Text('', size=(20,1), key='-OUTPUT-')],
- [sg.Input(do_not_clear=True, key='-IN-')],
- [sg.Button('Show'), sg.Button('Exit')]
- ]
+layout = [
+ [sg.Text('Your typed chars appear here:'),
+ sg.Text(size=(20, 1), key='-OUTPUT-')],
+ [sg.Input('', key='-IN-')],
+ [sg.Button('Show'), sg.Button('Exit')]
+]
-window = sg.Window('Window Title').Layout(layout)
+window = sg.Window('Window Title', layout)
-while True: # Event Loop
- event, values = window.Read()
+while True:
+ event, values = window.read()
print(event, values)
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
if event == 'Show':
- # change the "output" element to be the value of "input" element
- window.FindElement('-OUTPUT-').Update(values['-IN-'])
+ # change the "output" element to be the value of "input" element
+ window['-OUTPUT-'].update(values['-IN-'])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Design_Patterns.py b/DemoPrograms/Demo_Design_Patterns.py
index 64b39661..70cb7719 100644
--- a/DemoPrograms/Demo_Design_Patterns.py
+++ b/DemoPrograms/Demo_Design_Patterns.py
@@ -1,15 +1,15 @@
+import PySimpleGUI as sg
"""
When creating a new PySimpleGUI program from scratch, start here.
These are the accepted design patterns that cover the two primary use cases
1. A "One Shot" window
2. A persistent window that stays open after button clicks (uses an event loop)
-3. A persistent window that need to perform Update of an element before the window.read
+3. A persistent window that need to perform update of an element before the window.read
"""
# ---------------------------------#
# DESIGN PATTERN 1 - Simple Window #
# ---------------------------------#
-import PySimpleGUI as sg
layout = [[ sg.Text('My Oneshot') ],
[ sg.Button('OK') ]]
@@ -22,7 +22,6 @@ window.close()
# -------------------------------------#
# DESIGN PATTERN 2 - Persistent Window #
# -------------------------------------#
-import PySimpleGUI as sg
layout = [[ sg.Text('My layout') ],
[ sg.Button('OK'), sg.Button('Cancel') ]]
@@ -38,14 +37,13 @@ window.close()
# ------------------------------------------------------------------#
# DESIGN PATTERN 3 - Persistent Window with "early update" required #
# ------------------------------------------------------------------#
-import PySimpleGUI as sg
layout = [[ sg.Text('My layout', key='-TEXT-KEY-') ],
[ sg.Button('OK'), sg.Button('Cancel') ]]
window = sg.Window('Design Pattern 3', layout, finalize=True)
-window['-TEXT-KEY-'].Update('NEW Text') # Change the text field. Finalize allows us to do this
+window['-TEXT-KEY-'].update('NEW Text') # Change the text field. Finalize allows us to do this
while True: # Event Loop
event, values = window.read()
diff --git a/DemoPrograms/Demo_Desktop_Floating_Toolbar.py b/DemoPrograms/Demo_Desktop_Floating_Toolbar.py
index 785ae428..261bcbaa 100644
--- a/DemoPrograms/Demo_Desktop_Floating_Toolbar.py
+++ b/DemoPrograms/Demo_Desktop_Floating_Toolbar.py
@@ -1,10 +1,6 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import subprocess
import os
@@ -20,34 +16,36 @@ import os
ROOT_PATH = './'
+
def Launcher():
- # def print(line):
- # window.FindElement('output').Update(line)
+ sg.change_look_and_feel('Dark')
- sg.ChangeLookAndFeel('Dark')
-
- namesonly = [f for f in os.listdir(ROOT_PATH) if f.endswith('.py') ]
+ namesonly = [f for f in os.listdir(ROOT_PATH) if f.endswith('.py')]
if len(namesonly) == 0:
namesonly = ['test 1', 'test 2', 'test 3']
- sg.SetOptions(element_padding=(0,0), button_element_size=(12,1), auto_size_buttons=False)
+ sg.set_options(element_padding=(0, 0),
+ button_element_size=(12, 1), auto_size_buttons=False)
- layout = [[sg.Combo(values=namesonly, size=(35,30), key='demofile'),
- sg.Button('Run', button_color=('white', '#00168B')),
- sg.Button('Program 1'),
- sg.Button('Program 2'),
- sg.Button('Program 3', button_color=('white', '#35008B')),
- sg.Button('EXIT', button_color=('white','firebrick3'))],
- [sg.T('', text_color='white', size=(50,1), key='output')]]
-
- window = sg.Window('Floating Toolbar', no_titlebar=True, grab_anywhere=True, keep_on_top=True).Layout(layout)
+ layout = [[sg.Combo(values=namesonly, size=(35, 30), key='demofile'),
+ sg.Button('Run', button_color=('white', '#00168B')),
+ sg.Button('Program 1'),
+ sg.Button('Program 2'),
+ sg.Button('Program 3', button_color=('white', '#35008B')),
+ sg.Button('EXIT', button_color=('white', 'firebrick3'))],
+ [sg.Text('', text_color='white', size=(50, 1), key='output')]]
+ window = sg.Window('Floating Toolbar',
+ layout,
+ no_titlebar=True,
+ grab_anywhere=True,
+ keep_on_top=True)
# ---===--- Loop taking in user input and executing appropriate program --- #
while True:
- (event, values) = window.Read()
+ event, values = window.read()
if event == 'EXIT' or event is None:
break # exit button clicked
if event == 'Program 1':
@@ -56,11 +54,14 @@ def Launcher():
print('Run your program 2 here!')
elif event == 'Run':
file = values['demofile']
- print('Launching %s'%file)
+ print('Launching %s' % file)
ExecuteCommandSubprocess('python', os.path.join(ROOT_PATH, file))
else:
print(event)
+ window.close()
+
+
def ExecuteCommandSubprocess(command, *args, wait=False):
try:
if sys.platform == 'linux':
@@ -69,10 +70,16 @@ def ExecuteCommandSubprocess(command, *args, wait=False):
# for arg in args:
# arg_string += ' ' + str(arg)
print('python3 ' + arg_string)
- sp = subprocess.Popen(['python3 ', arg_string ], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ sp = subprocess.Popen(['python3 ', arg_string],
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
else:
arg_string = ' '.join([str(arg) for arg in args])
- sp = subprocess.Popen([command, arg_string], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ sp = subprocess.Popen([command, arg_string],
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
# sp = subprocess.Popen([command, list(args)], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if wait:
@@ -81,10 +88,9 @@ def ExecuteCommandSubprocess(command, *args, wait=False):
print(out.decode("utf-8"))
if err:
print(err.decode("utf-8"))
- except: pass
-
+ except:
+ pass
if __name__ == '__main__':
Launcher()
-
diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py
index 0512f4d6..cf587477 100644
--- a/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py
+++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import psutil
"""
@@ -32,14 +28,17 @@ class DashGraph(object):
self.color = color
def graph_percentage_abs(self, value):
- self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, value), color=self.color)
+ self.graph_elem.draw_line(
+ (self.graph_current_item, 0),
+ (self.graph_current_item, value),
+ color=self.color)
if self.graph_current_item >= GRAPH_WIDTH:
- self.graph_elem.Move(-1,0)
+ self.graph_elem.move(-1,0)
else:
self.graph_current_item += 1
def text_display(self, text):
- self.text_elem.Update(text)
+ self.text_elem.update(text)
def main():
# A couple of "Uber Elements" that combine several elements and enable bulk edits
@@ -47,7 +46,7 @@ def main():
return(sg.Text(text, font=('Helvetica 8'), **kwargs))
def GraphColumn(name, key):
- col = sg.Column([[Txt(name, key=key+'_TXT_'), ],
+ col = sg.Col([[Txt(name, key=key+'_TXT_'), ],
[sg.Graph((GRAPH_WIDTH, GRAPH_HEIGHT), (0, 0), (GRAPH_WIDTH, 100), background_color='black',
key=key+'_GRAPH_')]], pad=(2, 2))
return col
@@ -55,10 +54,11 @@ def main():
num_cores = len(psutil.cpu_percent(percpu=True)) # get the number of cores in the CPU
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0), margins=(1,1), border_width=0)
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0,0), margins=(1,1), border_width=0)
- # ---------------- Create Layout ----------------
+ # the clever Red X graphic
+ red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
layout = [[ sg.Button('', image_data=red_x, button_color=('black', 'black'), key='Exit', tooltip='Closes window'),
sg.Text(' CPU Core Usage')] ]
@@ -70,7 +70,7 @@ def main():
layout.append(row)
# ---------------- Create Window ----------------
- window = sg.Window('PSG System Dashboard',
+ window = sg.Window('PSG System Dashboard', layout,
keep_on_top=True,
auto_size_buttons=False,
grab_anywhere=True,
@@ -79,32 +79,31 @@ def main():
return_keyboard_events=True,
alpha_channel=TRANSPARENCY,
use_default_focus=False,
- ).Layout(layout).Finalize()
+ finalize=True)
# setup graphs & initial values
graphs = []
for i in range(num_cores):
- graphs.append(DashGraph(window.FindElement('_CPU_'+str(i)+'_GRAPH_'),
- window.FindElement('_CPU_'+str(i) + '_TXT_'),
+ graphs.append(DashGraph(window['_CPU_'+str(i)+'_GRAPH_'],
+ window['_CPU_'+str(i) + '_TXT_'],
0, colors[i%6]))
# ---------------- main loop ----------------
- while (True):
+ while True :
# --------- Read and update window once every Polling Frequency --------
- event, values = window.Read(timeout=POLL_FREQUENCY)
+ event, values = window.read(timeout=POLL_FREQUENCY)
if event in (None, 'Exit'): # Be nice and give an exit
break
# read CPU for each core
stats = psutil.cpu_percent(percpu=True)
- # Update each graph
+
+ # update each graph
for i in range(num_cores):
graphs[i].graph_percentage_abs(stats[i])
graphs[i].text_display('{} CPU {:2.0f}'.format(i, stats[i]))
- window.Close()
+
+ window.close()
if __name__ == "__main__":
- # the clever Red X graphic
- red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
main()
- sys.exit(69)
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Graph.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Graph.py
index 801cd61e..ee7bb377 100644
--- a/DemoPrograms/Demo_Desktop_Widget_CPU_Graph.py
+++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Graph.py
@@ -1,20 +1,15 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import time
import random
import psutil
from threading import Thread
-STEP_SIZE=3
+STEP_SIZE = 3
SAMPLES = 300
SAMPLE_MAX = 500
-CANVAS_SIZE = (300,200)
+CANVAS_SIZE = (300, 200)
g_interval = .25
@@ -22,6 +17,7 @@ g_cpu_percent = 0
g_procs = None
g_exit = False
+
def CPU_thread(args):
global g_interval, g_cpu_percent, g_procs, g_exit
@@ -32,48 +28,63 @@ def CPU_thread(args):
except:
pass
+
def main():
global g_exit, g_response_time
# start ping measurement thread
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0))
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0, 0))
- layout = [ [sg.Quit( button_color=('white','black')), sg.T('', pad=((100,0),0), font='Any 15', key='output')],
- [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,SAMPLE_MAX),background_color='black', key='graph')],]
+ layout = [
+ [sg.Quit(button_color=('white', 'black')),
+ sg.Text('', pad=((100, 0), 0), font='Any 15', key='output')],
+ [sg.Graph(CANVAS_SIZE, (0, 0), (SAMPLES, SAMPLE_MAX),
+ background_color='black', key='graph')]
+ ]
- window = sg.Window('CPU Graph', grab_anywhere=True, keep_on_top=True, background_color='black', no_titlebar=True, use_default_focus=False).Layout(layout)
+ window = sg.Window('CPU Graph', layout,
+ grab_anywhere=True, keep_on_top=True,
+ background_color='black', no_titlebar=True,
+ use_default_focus=False)
- graph = window.FindElement('graph')
- output = window.FindElement('output')
+ graph = window['graph']
+ output = window['output']
# start cpu measurement thread
- thread = Thread(target=CPU_thread,args=(None,))
+ thread = Thread(target=CPU_thread, args=(None,))
thread.start()
last_cpu = i = 0
prev_x, prev_y = 0, 0
while True: # the Event Loop
time.sleep(.5)
- event, values = window.Read(timeout=0)
- if event == 'Quit' or event is None: # always give ths user a way out
+ event, values = window.read(timeout=0)
+ if event in ('Quit', None): # always give ths user a way out
break
# do CPU measurement and graph it
current_cpu = int(g_cpu_percent*10)
+
if current_cpu == last_cpu:
continue
- output.Update(current_cpu/10) # show current cpu usage at top
+ # show current cpu usage at top
+ output.update(current_cpu/10)
+
if current_cpu > SAMPLE_MAX:
current_cpu = SAMPLE_MAX
new_x, new_y = i, current_cpu
+
if i >= SAMPLES:
- graph.Move(-STEP_SIZE,0) # shift graph over if full of data
+ # shift graph over if full of data
+ graph.move(-STEP_SIZE, 0)
prev_x = prev_x - STEP_SIZE
- graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
+ graph.draw_line((prev_x, prev_y), (new_x, new_y), color='white')
prev_x, prev_y = new_x, new_y
i += STEP_SIZE if i < SAMPLES else 0
last_cpu = current_cpu
g_exit = True
- window.Close()
+ window.close()
+
+
if __name__ == '__main__':
main()
diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py
index 39aefb5d..8ea6d4c2 100644
--- a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py
+++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import psutil
import time
from threading import Thread
@@ -29,6 +24,7 @@ g_cpu_percent = 0
g_procs = None
g_exit = False
+
def CPU_thread(args):
global g_interval, g_cpu_percent, g_procs, g_exit
@@ -44,30 +40,30 @@ def main():
global g_interval, g_procs, g_exit
# ---------------- Create Form ----------------
- sg.ChangeLookAndFeel('Black')
- layout = [[sg.Text('', size=(8,1), font=('Helvetica', 20),text_color=sg.YELLOWS[0],
- justification='center', key='text')],
- [sg.Text('', size=(30, 8), font=('Courier New', 12),text_color='white', justification='left', key='processes')],
- [sg.Exit(button_color=('white', 'firebrick4'), pad=((15,0), 0), size=(9,1)),
- sg.Spin([x+1 for x in range(10)], 3, key='spin')],]
+ sg.change_look_and_feel('Black')
+ layout = [
+ [sg.Text('', size=(8, 1), font=('Helvetica', 20),
+ text_color=sg.YELLOWS[0], justification='center', key='text')],
+ [sg.Text('', size=(30, 8), font=('Courier New', 12),
+ text_color='white', justification='left', key='processes')],
+ [sg.Exit(button_color=('white', 'firebrick4'), pad=((15, 0), 0), size=(9, 1)),
+ sg.Spin([x+1 for x in range(10)], 3, key='spin')]
+ ]
- window = sg.Window('CPU Utilization',
- no_titlebar=True,
- keep_on_top=True,
- alpha_channel=.8,
- grab_anywhere=True).Layout(layout)
+ window = sg.Window('CPU Utilization', layout
+ no_titlebar=True, keep_on_top=True, alpha_channel=.8, grab_anywhere=True)
# start cpu measurement thread
- thread = Thread(target=CPU_thread,args=(None,))
+ thread = Thread(target=CPU_thread, args=(None,))
thread.start()
timeout_value = 1 # make first read really quick
g_interval = 1
# ---------------- main loop ----------------
- while (True):
+ while True:
# --------- Read and update window --------
- event, values = window.Read(timeout=timeout_value, timeout_key='Timeout')
+ event, values = window.read(timeout=timeout_value, timeout_key='Timeout')
# --------- Do Button Operations --------
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
timeout_value = int(values['spin']) * 1000
@@ -77,11 +73,12 @@ def main():
if g_procs:
# --------- Create list of top % CPU porocesses --------
try:
- top = {proc.name() : proc.cpu_percent() for proc in g_procs}
- except: pass
+ top = {proc.name(): proc.cpu_percent() for proc in g_procs}
+ except:
+ pass
-
- top_sorted = sorted(top.items(), key=operator.itemgetter(1), reverse=True)
+ top_sorted = sorted(
+ top.items(), key=operator.itemgetter(1), reverse=True)
if top_sorted:
top_sorted.pop(0)
display_string = ''
@@ -89,11 +86,14 @@ def main():
display_string += '{:2.2f} {}\n'.format(cpu/10, proc)
# --------- Display timer and proceses in window --------
- window.FindElement('text').Update('CPU {}'.format(cpu_percent))
- window.FindElement('processes').Update(display_string)
+ window['text'].update('CPU ' + str(cpu_percent))
+ window['processes'].update(display_string)
g_exit = True
thread.join()
+ window.close()
+
+
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization_Simple.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization_Simple.py
index f5c61b32..b143ce41 100644
--- a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization_Simple.py
+++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization_Simple.py
@@ -1,45 +1,41 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import psutil
+# Yet another usage of CPU data
+
# ---------------- Create Form ----------------
-sg.ChangeLookAndFeel('Black')
+sg.change_look_and_feel('Black')
layout = [[sg.Text('CPU Utilization')],
- [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='_text_')],
- [sg.Exit(button_color=('white', 'firebrick4'), pad=((15, 0), 0), size=(9,1)),
- sg.Spin([x + 1 for x in range(10)], 3, key='_spin_')]]
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20),
+ justification='center', key='-text-')],
+ [sg.Exit(button_color=('white', 'firebrick4'), pad=((15, 0), 0), size=(9, 1)),
+ sg.Spin([x + 1 for x in range(10)], 3, key='-spin-')]]
# Layout the rows of the Window
window = sg.Window('CPU Meter',
+ layout,
no_titlebar=True,
keep_on_top=True,
- grab_anywhere=True).Layout(layout).Finalize()
+ grab_anywhere=True, finalize=True)
# ---------------- main loop ----------------
interval = 10 # For the first one, make it quick
-while (True):
+while True:
# --------- Read and update window --------
- event, values = window.Read(timeout=interval)
+ event, values = window.read(timeout=interval)
# --------- Do Button Operations --------
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
- interval = int(values['_spin_'])*1000
-
-
+ interval = int(values['-spin-'])*1000
cpu_percent = psutil.cpu_percent(interval=1)
# --------- Display timer in window --------
- window.FindElement('_text_').Update(f'CPU {cpu_percent:02.0f}%')
+ window['-text-'].update(f'CPU {cpu_percent:02.0f}%')
# Broke out of main loop. Close the window.
-window.CloseNonBlocking()
\ No newline at end of file
+window.CloseNonBlocking()
diff --git a/DemoPrograms/Demo_Desktop_Widget_Email_Notification.py b/DemoPrograms/Demo_Desktop_Widget_Email_Notification.py
index de4264d8..037f0972 100644
--- a/DemoPrograms/Demo_Desktop_Widget_Email_Notification.py
+++ b/DemoPrograms/Demo_Desktop_Widget_Email_Notification.py
@@ -1,66 +1,67 @@
#!/usr/bin/env python
-import sys
-
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
- sg.PopupError('Sorry, at the moment this program only runs on Python 3')
- sys.exit()
-
+import PySimpleGUI as sg
import email
import imaplib
from datetime import datetime
import calendar
+'''
+ Usage of Notification in PSG
+'''
+
IMAP_SERVER_GMAIL = 'imap.gmail.com' # gmail server address
IMAP_SERVER_HOTMAIL = 'imap-mail.outlook.com' # hotmail server address
-
################# Change these to match your email setup ################
LOGIN_EMAIL = 'you@mail.com'
LOGIN_PASSWORD = 'your email password'
-IMAP_SERVER = IMAP_SERVER_GMAIL # change to match your email service
-
+# change to match your email service
+IMAP_SERVER = IMAP_SERVER_GMAIL
MAX_EMAILS = 10
def gui():
- sg.ChangeLookAndFeel('Topanga')
-
- sg.SetOptions(border_width=0, margins=(0, 0), element_padding=(4, 0))
-
- layout = [[sg.T('Email New Mail Notification' + 48 * ' '),
- sg.Button('', image_data=refresh, button_color=('#282923', '#282923'), key='_refresh_',
- tooltip='Refreshes Email'),
- sg.Button('', image_data=red_x, button_color=('#282923', '#282923'), key='_quit_',
- tooltip='Closes window')],
- [sg.T('', key='_status_', size=(25, 1))], ]
+ sg.change_look_and_feel('Topanga')
+ sg.set_options(border_width=0, margins=(0, 0), element_padding=(4, 0))
+ color = ('#282923', '#282923')
+ layout = [[sg.Text('Email New Mail Notification' + 48 * ' '),
+ sg.Button('', image_data=refresh,
+ button_color=color,
+ key='-refresh-',
+ tooltip='refreshes Email'),
+ sg.Button('', image_data=red_x,
+ button_color=color,
+ key='-quit-',
+ tooltip='Closes window')],
+ [sg.Text('', key='-status-', size=(25, 1))], ]
for i in range(MAX_EMAILS):
- layout.append([sg.T('', size=(20, 1), key='{}date'.format(i), font='Sans 8'),
- sg.T('', size=(45, 1), font='Sans 8', key='{}from'.format(i))])
+ layout.append([sg.Text('', size=(20, 1), key='{}date'.format(i), font='Sans 8'),
+ sg.Text('', size=(45, 1), font='Sans 8', key='{}from'.format(i))])
window = sg.Window('',
+ layout,
no_titlebar=True,
grab_anywhere=True,
keep_on_top=True,
alpha_channel=0,
- ).Layout(layout).Finalize()
+ finalize=True)
# move the window to the upper right corner of the screen
- w, h = window.GetScreenDimensions()
- window.Move(w - 410, 0)
- window.SetAlpha(.9)
- window.Refresh()
- status_elem = window.FindElement('_status_')
- # The Event Loop
+ w, h = window.get_screen_dimensions()
+ window.move(w - 410, 0)
+ window.set_alpha(.9)
+ window.refresh()
+
+ status_elem = window['-status-']
+
while True:
- status_elem.Update('Reading...')
- window.Refresh()
+ status_elem.update('Reading...')
+ window.refresh()
read_mail(window)
- status_elem.Update('')
- event, values = window.Read(timeout=30 * 1000) # return every 30 seconds
- if event == '_quit_':
+ status_elem.update('')
+ # return every 30 seconds
+ event, values = window.read(timeout=30 * 1000)
+ if event == '-quit-':
break
@@ -78,31 +79,35 @@ def read_mail(window):
n = 0
now = datetime.now()
# get messages from today
- search_string = '(SENTON {}-{}-{})'.format(now.day, calendar.month_abbr[now.month], now.year)
+ search_string = '(SENTON {}-{}-{})'.format(now.day,
+ calendar.month_abbr[now.month], now.year)
(retcode, messages) = mail.search(None, search_string)
if retcode == 'OK':
- msg_list = messages[0].split() # message numbers are separated by spaces, turn into list
+ # message numbers are separated by spaces, turn into list
+ msg_list = messages[0].split()
msg_list.sort(reverse=True) # sort messages descending
for n, message in enumerate(msg_list):
if n >= MAX_EMAILS:
break
- from_elem = window.FindElement('{}from'.format(n))
- date_elem = window.FindElement('{}date'.format(n))
- from_elem.Update('') # erase them so you know they're changing
- date_elem.Update('')
- window.Refresh()
+
+ from_elem = window['{}from'.format(n)]
+ date_elem = window['{}date'.format(n)]
+ from_elem.update('') # erase them so you know they're changing
+ date_elem.update('')
+ window.refresh()
+
typ, data = mail.fetch(message, '(RFC822)')
for response_part in data:
if isinstance(response_part, tuple):
+
original = email.message_from_bytes(response_part[1])
date_str = original['Date'][:22]
- from_elem.Update(original['From'])
- date_elem.Update(date_str)
- window.Refresh() # make the window changes show up right away
+
+ from_elem.update(original['From'])
+ date_elem.update(date_str)
+ window.refresh() # make the window changes show up right away
red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
-
refresh = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACb0lEQVR4XpVTXUiTbxT/vePdFuF0BTFW9ufVvMlu+iACka6CQY1gQVdtEmTMpSKzzJT/RTdCRHhT4F0Us8LGVqlo1lZaFslWQWBkN+tDkpSpbfNz797T8zy6DbUbf/Dbec7vfOycMwa0DBJjM7Ko72mBtz+KplCS6Ronf3NNxNZBt2qv4dJzL0uKwGRqU/6zHDqyd1dBk32/xMnfXOMxkVPXXYlVSLjykk4fKIb/4zgUSxEO7zRBKd4Bjm/jU9ys8f2fJoCFhRiWl6pw6+Qw0BymhlfT5Lg/xmycHA++ktL+nsRqrUOrdpBpH6hhKC7yhObti0CgKUTu0KTgcd8X4j4aB2bYvj7UPqkQrO/1cU25ESV3eJJO8LzLIQ11/CYXn5Grf4KqGF19E3Ts9iixe2QPm0dtt5PtP6NcHxF5ZVfDhIbeqMQ6E0hcI4ec327jah513T4YDM5TR/dh8vc0hkfHUxI2gwuPKyDLb2wV5cIdePuZZGwWmQxSSyqICFBVyKgJJkFaQW4Hna4THQ4X/gUiD2+QXEwjNZsASJvTgWgMqoY95WWw7raAJdjheeTEeniCTqgZu2IxswnSmGI3gEZjMiQpAMocTC2nJcm4hU9gRjp9E+6Ajb07wKFpHqRVOzKqedFUhOX4HyRnEwSjMQCB8/4IqnxU2DYiaGnsIe7n2UlK61MWe0dbW18Ijdfk/wuy7IXeEEvEvmM+kcRM4XYYSkohW62ChtIS/NKbWGwO8z9+Anp9TNSsQU2wEtVdEZy5o7Gfi7Z5ewj/vxbkPs51kYhVP4zAw3I3IN+ohSVFcfZeEs67Gid/c03E1uEv5QpTFzvZK5EAAAAASUVORK5CYII='
-
gui()
diff --git a/DemoPrograms/Demo_Desktop_Widget_Timer.py b/DemoPrograms/Demo_Desktop_Widget_Timer.py
index 31b24875..d8edf544 100644
--- a/DemoPrograms/Demo_Desktop_Widget_Timer.py
+++ b/DemoPrograms/Demo_Desktop_Widget_Timer.py
@@ -14,30 +14,38 @@ import time
"""
+
def time_as_int():
return int(round(time.time() * 100))
+
# ---------------- Create Form ----------------
-sg.ChangeLookAndFeel('Black')
+sg.change_look_and_feel('Black')
layout = [[sg.Text('')],
- [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')],
- [sg.Button('Pause', key='-RUN-PAUSE-', button_color=('white', '#001480')),
- sg.Button('Reset', button_color=('white', '#007339'), key='-RESET-'),
- sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]]
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20),
+ justification='center', key='text')],
+ [sg.Button('Pause', key='-RUN-PAUSE-', button_color=('white', '#001480')),
+ sg.Button('Reset', button_color=('white', '#007339'), key='-RESET-'),
+ sg.Exit(button_color=('white', 'firebrick4'), key='Exit')]]
-window = sg.Window('Running Timer', layout, no_titlebar=True, auto_size_buttons=False, keep_on_top=True, grab_anywhere=True, element_padding=(0,0))
+window = sg.Window('Running Timer', layout,
+ no_titlebar=True,
+ auto_size_buttons=False,
+ keep_on_top=True,
+ grab_anywhere=True,
+ element_padding=(0, 0))
-# ---------------- main loop ----------------
current_time, paused_time, paused = 0, 0, False
start_time = time_as_int()
-while (True):
+
+while True:
# --------- Read and update window --------
if not paused:
- event, values = window.Read(timeout=10)
+ event, values = window.read(timeout=10)
current_time = time_as_int() - start_time
else:
- event, values = window.Read()
+ event, values = window.read()
# --------- Do Button Operations --------
if event in (None, 'Exit'): # ALWAYS give a way out of program
break
@@ -50,10 +58,11 @@ while (True):
paused_time = time_as_int()
else:
start_time = start_time + time_as_int() - paused_time
- window['-RUN-PAUSE-'].Update('Run' if paused else 'Pause') # Change button's text
+ # Change button's text
+ window['-RUN-PAUSE-'].update('Run' if paused else 'Pause')
# --------- Display timer in window --------
- window['text'].Update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60,
- (current_time // 100) % 60,
- current_time % 100))
-window.close()
\ No newline at end of file
+ window['text'].update('{:02d}:{:02d}.{:02d}'.format((current_time // 100) // 60,
+ (current_time // 100) % 60,
+ current_time % 100))
+window.close()
diff --git a/DemoPrograms/Demo_Desktop_Widget_psutil_Dashboard.py b/DemoPrograms/Demo_Desktop_Widget_psutil_Dashboard.py
index 3d1df605..e34bdf8d 100644
--- a/DemoPrograms/Demo_Desktop_Widget_psutil_Dashboard.py
+++ b/DemoPrograms/Demo_Desktop_Widget_psutil_Dashboard.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import psutil
"""
@@ -20,6 +16,7 @@ GRAPH_WIDTH = 120 # each individual graph size in pixels
GRAPH_HEIGHT = 40
ALPHA = .7
+
class DashGraph(object):
def __init__(self, graph_elem, starting_count, color):
self.graph_current_item = 0
@@ -33,106 +30,118 @@ class DashGraph(object):
self.prev_value = current_value
self.max_sent = max(self.max_sent, delta)
percent_sent = 100 * delta / self.max_sent
- self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, percent_sent), color=self.color)
+ self.graph_elem.draw_line((self.graph_current_item, 0),
+ (self.graph_current_item, percent_sent),
+ color=self.color)
if self.graph_current_item >= GRAPH_WIDTH:
- self.graph_elem.Move(-1,0)
+ self.graph_elem.move(-1, 0)
else:
self.graph_current_item += 1
return delta
def graph_percentage_abs(self, value):
- self.graph_elem.DrawLine((self.graph_current_item, 0), (self.graph_current_item, value), color=self.color)
+ self.graph_elem.draw_line((self.graph_current_item, 0),
+ (self.graph_current_item, value),
+ color=self.color)
if self.graph_current_item >= GRAPH_WIDTH:
- self.graph_elem.Move(-1,0)
+ self.graph_elem.move(-1, 0)
else:
self.graph_current_item += 1
-def human_size(bytes, units=[' bytes','KB','MB','GB','TB', 'PB', 'EB']):
+def human_size(bytes, units=[' bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']):
""" Returns a human readable string reprentation of bytes"""
- return str(bytes) + units[0] if bytes < 1024 else human_size(bytes>>10, units[1:])
+ return str(bytes) + units[0] if bytes < 1024 else human_size(bytes >> 10, units[1:])
+
def main():
- # Make the layout less cluttered and allow bulk-changes to text formatting
- def Txt(text, **kwargs):
- return(sg.Text(text, font=('Helvetica 8'), **kwargs))
- # Update a Text Element
- def Txt_Update(window, key, value):
- window.FindElement(key).Update(value)
-
# ---------------- Create Window ----------------
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0), margins=(1,1), border_width=0)
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0, 0), margins=(1, 1), border_width=0)
def GraphColumn(name, key):
- col = sg.Column([[Txt(name, key=key+'TXT_'), ],
- [sg.Graph((GRAPH_WIDTH, GRAPH_HEIGHT), (0, 0), (GRAPH_WIDTH, 100), background_color='black',
- key=key+'GRAPH_')]], pad=(2, 2))
- return col
+ layout = [
+ [sg.Text(name, font=('Helvetica 8'), key=key+'TXT_')],
+ [sg.Graph((GRAPH_WIDTH, GRAPH_HEIGHT),
+ (0, 0),
+ (GRAPH_WIDTH, 100),
+ background_color='black',
+ key=key+'GRAPH_')]]
+ return sg.Col(layout, pad=(2, 2))
- layout = [[sg.Text('System Status Dashboard'+' '*18), sg.Button('', image_data=red_x, button_color=('black', 'black'), key='Exit', tooltip='Closes window')],
- [GraphColumn('Net Out', '_NET_OUT_'),
- GraphColumn('Net In', '_NET_IN_')],
- [GraphColumn('Disk Read', '_DISK_READ_'),
- GraphColumn('Disk Write', '_DISK_WRITE_')],
- [GraphColumn('CPU Usage', '_CPU_'),
- GraphColumn('Memory Usage', '_MEM_')],]
+ red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+ layout = [
+ [sg.Text('System Status Dashboard'+' '*18),
+ sg.Button('',
+ image_data=red_x,
+ button_color=('black', 'black'),
+ key='Exit',
+ tooltip='Closes window')],
+ [GraphColumn('Net Out', '_NET_OUT_'),
+ GraphColumn('Net In', '_NET_IN_')],
+ [GraphColumn('Disk Read', '_DISK_READ_'),
+ GraphColumn('Disk Write', '_DISK_WRITE_')],
+ [GraphColumn('CPU Usage', '_CPU_'),
+ GraphColumn('Memory Usage', '_MEM_')], ]
- window = sg.Window('PSG System Dashboard',
- keep_on_top=True,
- auto_size_buttons=False,
- grab_anywhere=True,
- no_titlebar=True,
- default_button_element_size=(12, 1),
- return_keyboard_events=True,
- alpha_channel=ALPHA,
- use_default_focus=False,
- ).Layout(layout).Finalize()
+ window = sg.Window('PSG System Dashboard', layout,
+ keep_on_top=True, auto_size_buttons=False,
+ grab_anywhere=True, no_titlebar=True,
+ default_button_element_size=(12, 1),
+ return_keyboard_events=True, alpha_channel=ALPHA,
+ use_default_focus=False, finalize=True)
# setup graphs & initial values
netio = psutil.net_io_counters()
- net_graph_in = DashGraph(window.FindElement('_NET_IN_GRAPH_'), netio.bytes_recv, '#23a0a0')
- net_graph_out = DashGraph(window.FindElement('_NET_OUT_GRAPH_'), netio.bytes_sent, '#56d856')
-
+ net_in = window['_NET_IN_GRAPH_']
+ net_graph_in = DashGraph(net_in,
+ netio.bytes_recv, '#23a0a0')
+ net_out = window['_NET_OUT_GRAPH_']
+ net_graph_out = DashGraph(net_out,
+ netio.bytes_sent, '#56d856')
diskio = psutil.disk_io_counters()
- disk_graph_write = DashGraph(window.FindElement('_DISK_WRITE_GRAPH_'), diskio.write_bytes, '#be45be')
- disk_graph_read = DashGraph(window.FindElement('_DISK_READ_GRAPH_'), diskio.read_bytes, '#5681d8')
+ disk_graph_write = DashGraph(
+ window['_DISK_WRITE_GRAPH_'], diskio.write_bytes, '#be45be')
+ disk_graph_read = DashGraph(
+ window['_DISK_READ_GRAPH_'], diskio.read_bytes, '#5681d8')
- cpu_usage_graph = DashGraph(window.FindElement('_CPU_GRAPH_'), 0, '#d34545')
- mem_usage_graph = DashGraph(window.FindElement('_MEM_GRAPH_'), 0, '#BE7C29')
+ cpu_usage_graph = DashGraph(window['_CPU_GRAPH_'], 0, '#d34545')
+ mem_usage_graph = DashGraph(window['_MEM_GRAPH_'], 0, '#BE7C29')
print(psutil.cpu_percent(percpu=True))
# ---------------- main loop ----------------
- while (True):
+ while True :
# --------- Read and update window once a second--------
- event, values = window.Read(timeout=1000)
- if event in (None, 'Exit'): # Be nice and give an exit, expecially since there is no titlebar
+ event, values = window.read(timeout=1000)
+ # Be nice and give an exit, expecially since there is no titlebar
+ if event in (None, 'Exit'):
break
# ----- Network Graphs -----
netio = psutil.net_io_counters()
write_bytes = net_graph_out.graph_value(netio.bytes_sent)
read_bytes = net_graph_in.graph_value(netio.bytes_recv)
- Txt_Update(window, '_NET_OUT_TXT_', 'Net out {}'.format(human_size(write_bytes)))
- Txt_Update(window, '_NET_IN_TXT_', 'Net In {}'.format(human_size(read_bytes)))
+ window['_NET_OUT_TXT_'].update(
+ 'Net out {}'.format(human_size(write_bytes)))
+ window['_NET_IN_TXT_'].update(
+ 'Net In {}'.format(human_size(read_bytes)))
# ----- Disk Graphs -----
diskio = psutil.disk_io_counters()
write_bytes = disk_graph_write.graph_value(diskio.write_bytes)
read_bytes = disk_graph_read.graph_value(diskio.read_bytes)
- Txt_Update(window, '_DISK_WRITE_TXT_', 'Disk Write {}'.format(human_size(write_bytes)))
- Txt_Update(window, '_DISK_READ_TXT_', 'Disk Read {}'.format(human_size(read_bytes)))
+ window['_DISK_WRITE_TXT_'].update(
+ 'Disk Write {}'.format(human_size(write_bytes)))
+ window['_DISK_READ_TXT_'].update(
+ 'Disk Read {}'.format(human_size(read_bytes)))
# ----- CPU Graph -----
cpu = psutil.cpu_percent(0)
cpu_usage_graph.graph_percentage_abs(cpu)
- Txt_Update(window, '_CPU_TXT_', '{0:2.0f}% CPU Used'.format(cpu))
+ window['_CPU_TXT_'].update('{0:2.0f}% CPU Used'.format(cpu))
# ----- Memory Graph -----
mem_used = psutil.virtual_memory().percent
mem_usage_graph.graph_percentage_abs(mem_used)
- Txt_Update(window, '_MEM_TXT_', '{}% Memory Used'.format(mem_used))
+ window['_MEM_TXT_'].update('{}% Memory Used'.format(mem_used))
+
if __name__ == "__main__":
- # the clever Red X graphic
- red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
-
main()
- sys.exit(69)
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Disable_Elements.py b/DemoPrograms/Demo_Disable_Elements.py
index 4f4f7778..97a6ad40 100644
--- a/DemoPrograms/Demo_Disable_Elements.py
+++ b/DemoPrograms/Demo_Disable_Elements.py
@@ -1,45 +1,66 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-sg.ChangeLookAndFeel('Dark')
-sg.SetOptions(element_padding=(0, 0))
+'''
+ Usage of Disable elements
+'''
+
+sg.change_look_and_feel('Dark')
+sg.set_options(element_padding=(0, 0))
layout = [
- [sg.T('Notes:', pad=((3, 0), 0)), sg.In(size=(44, 1), background_color='white', text_color='black', key='notes')],
- [sg.T('Output:', pad=((3, 0), 0)), sg.T('', size=(44, 1), text_color='white', key='output')],
- [sg.CBox('Checkbox:', default=True, pad=((3, 0), 0), disabled=True, key='cbox'), sg.Listbox((1,2,3,4),size=(8,3),disabled=True, key='listbox'),
- sg.Radio('Radio 1', default=True, group_id='1', disabled=True, key='radio1'), sg.Radio('Radio 2', default=False, group_id='1', disabled=True, key='radio2')],
- [sg.Spin((1,2,3,4),1,disabled=True, key='spin'), sg.OptionMenu((1,2,3,4),disabled=True, key='option'), sg.Combo(values=(1,2,3,4),disabled=True,key='combo')],
- [sg.Multiline('Multiline', size=(20,3),disabled=True, key='multi')],
- [sg.Slider((1,10), size=(20,20), orientation='h', disabled=True, key='slider')],
- [sg.Button('Enable', button_color=('white', 'black')),
- sg.Button('Disable', button_color=('white', 'black')),
- sg.Button('Reset', button_color=('white', '#9B0023'), key='reset'),
- sg.Button('Values', button_color=('white', 'springgreen4')),
- sg.Button('Exit', disabled=True, button_color=('white', '#00406B'), key='exit')]]
+ [ sg.Text('Notes:', pad=((3, 0), 0)),
+ sg.Input(size=(44, 1), background_color='white', text_color='black', key='notes')],
-window = sg.Window("Disable Elements Demo", default_element_size=(12, 1), text_justification='r', auto_size_text=False,
- auto_size_buttons=False, keep_on_top=True, grab_anywhere=False,
- default_button_element_size=(12, 1)).Layout(layout).Finalize()
+ [ sg.Text('Output:', pad=((3, 0), 0)),
+ sg.Text('', size=(44, 1), text_color='white', key='output')],
+
+ [sg.CBox('Checkbox:', default=True, pad=((3, 0), 0), disabled=True, key='cbox'),
+ sg.Listbox((1, 2, 3, 4), size=(8, 3), disabled=True, key='listbox'),
+ sg.Radio('Radio 1', default=True, group_id='1', disabled=True, key='radio1'),
+ sg.Radio('Radio 2', default=False, group_id='1', disabled=True, key='radio2')],
+
+ [sg.Spin((1, 2, 3, 4), 1, disabled=True, key='spin'),
+ sg.OptionMenu((1, 2, 3, 4), disabled=True, key='option'),
+ sg.Combo(values=(1, 2, 3, 4), disabled=True, key='combo')],
+
+ [sg.ML('Multiline', size=(20, 3), disabled=True, key='multi')],
+
+ [sg.Slider((1, 10), size=(20, 20), orientation='h', disabled=True, key='slider')],
+
+ [sg.Button('Enable', button_color=('white', 'black')),
+ sg.Button('Disable', button_color=('white', 'black')),
+ sg.Button('Reset', button_color=('white', '#9B0023'), key='reset'),
+ sg.Button('Values', button_color=('white', 'springgreen4')),
+ sg.Button('Exit', disabled=True, button_color=('white', '#00406B'), key='exit')]]
+
+window = sg.Window("Disable Elements Demo", layout,
+ default_element_size=(12, 1),
+ text_justification='r',
+ auto_size_text=False,
+ auto_size_buttons=False,
+ keep_on_top=True,
+ grab_anywhere=False,
+ default_button_element_size=(12, 1),
+ finalize=True)
key_list = 'cbox', 'listbox', 'radio1', 'radio2', 'spin', 'option', 'combo', 'reset', 'notes', 'multi', 'slider', 'exit'
-for key in key_list: window.FindElement(key).Update(disabled=True) # don't do this kind of for-loop
+# don't do this kind of for-loop
+for key in key_list:
+ window[key].update(disabled=True)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event in (None, 'exit'):
break
elif event == 'Disable':
- for key in key_list: window.FindElement(key).Update(disabled=True)
+ for key in key_list:
+ window[key].update(disabled=True)
elif event == 'Enable':
- for key in key_list: window.FindElement(key).Update(disabled=False)
+ for key in key_list:
+ window[key].update(disabled=False)
elif event == 'Values':
- sg.Popup(values, keep_on_top=True)
+ sg.popup(values, keep_on_top=True)
-sys.exit(0)
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_DuplicateFileFinder.py b/DemoPrograms/Demo_DuplicateFileFinder.py
index 7e7045c6..9ec42577 100644
--- a/DemoPrograms/Demo_DuplicateFileFinder.py
+++ b/DemoPrograms/Demo_DuplicateFileFinder.py
@@ -1,29 +1,30 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import hashlib
import os
+'''
+ Find dups with PySimpleGUI
+'''
# ====____====____==== FUNCTION DeDuplicate_folder(path) ====____====____==== #
# Function to de-duplicate the folder passed in #
# --------------------------------------------------------------------------- #
def FindDuplicatesFilesInFolder(path):
shatab = []
- total = 0
- small_count, dup_count, error_count = 0,0,0
+ total, small_count, dup_count, error_count = [0]*4
pngdir = path
if not os.path.exists(path):
- sg.Popup('Duplicate Finder', '** Folder doesn\'t exist***', path)
+ sg.popup('Duplicate Finder', '** Folder doesn\'t exist***', path)
return
pngfiles = os.listdir(pngdir)
total_files = len(pngfiles)
+
for idx, f in enumerate(pngfiles):
- if not sg.OneLineProgressMeter('Counting Duplicates', idx + 1, total_files, 'Counting Duplicate Files'):
+ if not sg.one_line_progress_meter('Counting Duplicates',
+ idx + 1,
+ total_files,
+ 'Counting Duplicate Files'):
break
total += 1
fname = os.path.join(pngdir, f)
@@ -42,8 +43,10 @@ def FindDuplicatesFilesInFolder(path):
continue
shatab.append(f_sha)
- msg = '{} Files processed\n {} Duplicates found'.format(total_files, dup_count)
- sg.Popup('Duplicate Finder Ended', msg)
+ msg = '{} Files processed\n {} Duplicates found'.format(
+ total_files, dup_count)
+ sg.popup('Duplicate Finder Ended', msg)
+
# ====____====____==== Pseudo-MAIN program ====____====____==== #
# This is our main-alike piece of code #
@@ -54,9 +57,10 @@ def FindDuplicatesFilesInFolder(path):
if __name__ == '__main__':
source_folder = None
- source_folder = sg.PopupGetFolder('Duplicate Finder - Count number of duplicate files', 'Enter path to folder you wish to find duplicates in')
+ source_folder = sg.popup_get_folder(
+ 'Duplicate Finder - Count number of duplicate files', 'Enter path to folder you wish to find duplicates in')
if source_folder is not None:
FindDuplicatesFilesInFolder(source_folder)
else:
- sg.PopupCancel('Cancelling', '*** Cancelling ***')
+ sg.popup_cancel('Cancelling', '*** Cancelling ***')
exit(0)
diff --git a/DemoPrograms/Demo_EXE_Maker.py b/DemoPrograms/Demo_EXE_Maker.py
index 9d751c6f..14fd0942 100644
--- a/DemoPrograms/Demo_EXE_Maker.py
+++ b/DemoPrograms/Demo_EXE_Maker.py
@@ -4,72 +4,79 @@ from shutil import copyfile
import shutil
import os
-def Launcher():
- sg.ChangeLookAndFeel('LightGreen')
+'''
+ Make a "Windows os" executable with PyInstaller
+'''
- layout = [[sg.T('PyInstaller EXE Creator', font='Any 15')],
- [sg.T('Source Python File'), sg.In(key='_sourcefile_', size=(45,1)), sg.FileBrowse(file_types=(("Python Files", "*.py"),))],
- [sg.T('Icon File'), sg.In(key='_iconfile_', size=(45,1)), sg.FileBrowse(file_types=(("Icon Files", "*.ico"),))],
- [sg.Frame('Output', font='Any 15',layout= [[sg.Output(size=(65, 15), font='Courier 10')]])],
- [sg.ReadFormButton('Make EXE',bind_return_key=True),
- sg.SimpleButton('Quit', button_color=('white','firebrick3')),]]
+def Launcher():
+ sg.change_look_and_feel('LightGreen')
+
+ layout = [[sg.Text('PyInstaller EXE Creator', font='Any 15')],
+ [sg.Text('Source Python File'), sg.Input(key='-sourcefile-', size=(45, 1)),
+ sg.FileBrowse(file_types=(("Python Files", "*.py"),))],
+ [sg.Text('Icon File'), sg.Input(key='-iconfile-', size=(45, 1)),
+ sg.FileBrowse(file_types=(("Icon Files", "*.ico"),))],
+ [sg.Frame('Output', font='Any 15', layout=[
+ [sg.Output(size=(65, 15), font='Courier 10')]])],
+ [sg.ReadFormButton('Make EXE', bind_return_key=True),
+ sg.SimpleButton('Quit', button_color=('white', 'firebrick3')), ]]
window = sg.Window('PySimpleGUI EXE Maker',
+ layout,
auto_size_text=False,
auto_size_buttons=False,
- default_element_size=(20,1,),
+ default_element_size=(20, 1,),
text_justification='right')
- window.Layout(layout)
-
# ---===--- Loop taking in user input --- #
while True:
- (button, values) = window.Read()
- if button in ('Quit', None):
- break # exit button clicked
- source_file = values['_sourcefile_']
- icon_file = values['_iconfile_']
+ event, values = window.read()
+ if event in ('Exit', 'Quit', None):
+ break
+
+ source_file = values['-sourcefile-']
+ icon_file = values['-iconfile-']
icon_option = '-i "{}"'.format(icon_file) if icon_file else ''
source_path, source_filename = os.path.split(source_file)
workpath_option = '--workpath "{}"'.format(source_path)
dispath_option = '--distpath "{}"'.format(source_path)
specpath_option = '--specpath "{}"'.format(source_path)
- folder_to_remove = os.path.join(source_path,source_filename[:-3])
- file_to_remove = os.path.join(source_path, source_filename[:-3]+'.spec')
- command_line = 'pyinstaller -wF "{}" {} {} {} {}'.format(source_file, icon_option, workpath_option, dispath_option, specpath_option)
+ folder_to_remove = os.path.join(source_path, source_filename[:-3])
+ file_to_remove = os.path.join(
+ source_path, source_filename[:-3]+'.spec')
+ command_line = 'pyinstaller -wF "{}" {} {} {} {}'.format(
+ source_file, icon_option, workpath_option, dispath_option, specpath_option)
- if button == 'Make EXE':
+ if event == 'Make EXE':
try:
print(command_line)
- print('Making EXE... this will take a while.. the program has NOT locked up...')
- window.Refresh()
+ print(
+ 'Making EXE...the program has NOT locked up...')
+ window.refresh()
# print('Running command {}'.format(command_line))
runCommand(command_line)
shutil.rmtree(folder_to_remove)
os.remove(file_to_remove)
print('**** DONE ****')
except:
- sg.PopupError('Something went wrong')
+ sg.popup_error('Something went wrong')
def runCommand(cmd, timeout=None):
- """ run shell command
+ """
+ run shell command
- @param cmd: command to execute
- @param timeout: timeout for command execution
+ @param cmd: command to execute
+ @param timeout: timeout for command execution
- @return: (return code from command, command output)
- """
+ @return: (return code from command, command output)
+ """
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- output = ''
-
out, err = p.communicate()
p.wait(timeout)
-
return (out, err)
if __name__ == '__main__':
Launcher()
-
diff --git a/DemoPrograms/Demo_Event_Callback_Simulation.py b/DemoPrograms/Demo_Event_Callback_Simulation.py
index d3642368..a79f3718 100644
--- a/DemoPrograms/Demo_Event_Callback_Simulation.py
+++ b/DemoPrograms/Demo_Event_Callback_Simulation.py
@@ -1,6 +1,4 @@
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
-# import PySimpleGUIWx as sg
'''
Event Callback Simulation
@@ -15,17 +13,28 @@ import PySimpleGUI as sg
# The callback functions
# These callbacks all display a message in a non-blocking way and immediately return
+
+
def button1(event, values):
- sg.popup_quick_message('Button 1 callback', background_color='red', text_color='white')
+ sg.popup_quick_message('Button 1 callback',
+ background_color='red',
+ text_color='white')
+
def button2(event, values):
- sg.popup_quick_message('Button 2 callback', background_color='green', text_color='white')
+ sg.popup_quick_message('Button 2 callback',
+ background_color='green',
+ text_color='white')
+
def catch_all(event, values):
- sg.popup_quick_message(f'An unplanned event = "{event}" happend', background_color='blue', text_color='white', auto_close_duration=6)
+ sg.popup_quick_message(f'An unplanned event = "{event}" happend',
+ background_color='blue',
+ text_color='white', auto_close_duration=6)
+
# Lookup dictionary that maps event to function to call. In this case, only 2 event have defined callbacks
-func_dict = {'1':button1, '2':button2}
+func_dict = {'1': button1, '2': button2}
# Layout the design of the GUI
layout = [[sg.Text('Please click a button')],
@@ -50,4 +59,4 @@ while True:
window.close()
# All done!
-sg.popup_auto_close('Done... this window auto closes')
\ No newline at end of file
+sg.popup_auto_close('Done... this window auto closes')
diff --git a/DemoPrograms/Demo_Fill_Form.py b/DemoPrograms/Demo_Fill_Form.py
index 08e24929..fe497e98 100644
--- a/DemoPrograms/Demo_Fill_Form.py
+++ b/DemoPrograms/Demo_Fill_Form.py
@@ -1,66 +1,77 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-def Everything():
- sg.ChangeLookAndFeel('TanBlue')
+'''
+ Example of GUI
+'''
+
+def main():
+ sg.change_look_and_feel('TanBlue')
column1 = [
- [sg.Text('Column 1', background_color=sg.DEFAULT_BACKGROUND_COLOR, justification='center', size=(10, 1))],
- [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 1', key='spin1')],
- [sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 2', key='spin2')],
+ [sg.Text('Column 1', background_color=sg.DEFAULT_BACKGROUND_COLOR,
+ justification='center', size=(10, 1))],
+ [sg.Spin(values=('Spin Box 1', '2', '3'),
+ initial_value='Spin Box 1', key='spin1')],
+ [sg.Spin(values=('Spin Box 1', '2', '3'),
+ initial_value='Spin Box 2', key='spin2')],
[sg.Spin(values=('Spin Box 1', '2', '3'), initial_value='Spin Box 3', key='spin3')]]
layout = [
[sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))],
[sg.Text('Here is some text.... and a place to enter text')],
- [sg.InputText('This is my text', key='in1', do_not_clear=True)],
- [sg.Checkbox('Checkbox', key='cb1'), sg.Checkbox('My second checkbox!', key='cb2', default=True)],
+ [sg.InputText('This is my text', key='in1')],
+ [sg.CBox('Checkbox', key='cb1'), sg.CBox(
+ 'My second checkbox!', key='cb2', default=True)],
[sg.Radio('My first Radio! ', "RADIO1", key='rad1', default=True),
sg.Radio('My second Radio!', "RADIO1", key='rad2')],
- [sg.Multiline(default_text='This is the default Text should you decide not to type anything', size=(35, 3),
- key='multi1', do_not_clear=True),
- sg.Multiline(default_text='A second multi-line', size=(35, 3), key='multi2', do_not_clear=True)],
- [sg.InputCombo(('Combobox 1', 'Combobox 2'), key='combo', size=(20, 1)),
+ [sg.MLine(default_text='This is the default Text should you decide not to type anything', size=(35, 3),
+ key='multi1'),
+ sg.MLine(default_text='A second multi-line', size=(35, 3), key='multi2')],
+ [sg.Combo(('Combobox 1', 'Combobox 2'), key='combo', size=(20, 1)),
sg.Slider(range=(1, 100), orientation='h', size=(34, 20), key='slide1', default_value=85)],
- [sg.InputOptionMenu(('Menu Option 1', 'Menu Option 2', 'Menu Option 3'), key='optionmenu')],
+ [sg.OptionMenu(('Menu Option 1', 'Menu Option 2',
+ 'Menu Option 3'), key='optionmenu')],
[sg.Listbox(values=('Listbox 1', 'Listbox 2', 'Listbox 3'), size=(30, 3), key='listbox'),
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=25, key='slide2', ),
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=75, key='slide3', ),
- sg.Slider(range=(1, 100), orientation='v', size=(5, 20), default_value=10, key='slide4'),
- sg.Column(column1, background_color='gray34')],
+ sg.Slider(range=(1, 100),
+ orientation='v',
+ size=(5, 20),
+ default_value=25, key='slide2', ),
+ sg.Slider(range=(1, 100),
+ orientation='v',
+ size=(5, 20),
+ default_value=75, key='slide3', ),
+ sg.Slider(range=(1, 100),
+ orientation='v',
+ size=(5, 20),
+ default_value=10, key='slide4'),
+ sg.Col(column1, background_color='gray34')],
[sg.Text('_' * 80)],
[sg.Text('Choose A Folder', size=(35, 1))],
- [sg.Text('Your Folder', size=(15, 1), auto_size_text=False, justification='right'),
- sg.InputText('Default Folder', key='folder', do_not_clear=True), sg.FolderBrowse()],
+ [sg.Text('Your Folder', size=(15, 1), justification='right'),
+ sg.InputText('Default Folder', key='folder'), sg.FolderBrowse()],
[sg.Button('Exit'),
sg.Text(' ' * 40), sg.Button('SaveSettings'), sg.Button('LoadSettings')]
]
- window = sg.Window('Form Fill Demonstration', default_element_size=(40, 1), grab_anywhere=False)
- # button, values = window.LayoutAndRead(layout, non_blocking=True)
- window.Layout(layout)
+ window = sg.Window('Form Fill Demonstration', layout, default_element_size=(40, 1), grab_anywhere=False)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event == 'SaveSettings':
- filename = sg.PopupGetFile('Save Settings', save_as=True, no_window=True)
+ filename = sg.popup_get_file('Save Settings', save_as=True, no_window=True)
window.SaveToDisk(filename)
# save(values)
elif event == 'LoadSettings':
- filename = sg.PopupGetFile('Load Settings', no_window=True)
+ filename = sg.popup_get_file('Load Settings', no_window=True)
window.LoadFromDisk(filename)
# load(form)
elif event in ('Exit', None):
break
- # window.CloseNonBlocking()
+ window.close()
if __name__ == '__main__':
- Everything()
+ main()
diff --git a/DemoPrograms/Demo_Floating_Toolbar.py b/DemoPrograms/Demo_Floating_Toolbar.py
index 14312f6a..162685ef 100644
--- a/DemoPrograms/Demo_Floating_Toolbar.py
+++ b/DemoPrograms/Demo_Floating_Toolbar.py
@@ -1,28 +1,34 @@
import PySimpleGUI as sg
import sys
-button_names = ('close', 'cookbook', 'cpu', 'github', 'pysimplegui', 'run', 'storage', 'timer')
-
-house64='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'
-
-cpu64='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'
-
-timer64='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'
+'''
+ Example of borderless floating toolbar.
+'''
+button_names = ('close', 'cookbook', 'cpu', 'github',
+ 'pysimplegui', 'run', 'storage', 'timer')
+house64 = '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'
+cpu64 = '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'
+timer64 = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsSAAALEgHS3X78AAAJDUlEQVRYhbWWe2xT1x3Hv/fht6+feTiJm6TYCUnaLYUmJFmb0pWu0NKmYhtQxoaKCmKjRe1aVRVV/xh/dFPfj0mZNFUr3TSKIHQCOtYVSkehzCEkJORpJ8GJY8eO7Xvt2L7O9bV97/5Iy3iEdK3YT7rS0e/e8/t+zvmee84hcJOj/nu31zQ23LkxFAxaWC5WYC8rHQDgPXnq9Mcsx6Wu/Z66meLVTkfxbbU1O/oHBo8Mjbg/8IyNd9TW1g46nc5ilYJew3Kx/rm5OfFmal6OhoY7y3bt/OWftvx8s2qh9y++8PyD69c9+ti1+Zs2AzRFN1lMRu7SpK+nra3NVFuztH3z5s3y8RMn3ABQbLNFCFl+YGjEfeb/AsAw+mVT/oDIxWLee1pbf1dZWbHDarVuanv44erKysqp9/d+cMloND7lDwQ6ruxH3iwAAKlqp0N8+623msxm049NJhOCwWmc/OzEYw+uWf2Q1WKhrGbTzLWd6O+i1NzcTNlsNoYgCCkYDKZcLpfEMMxgZUXF1nSaf5Cm6dJ0mod7eBjfr7+j57U33txnLytd5qyqGsAnn343gBUrVuieeOKJlqmpqXV1dXXFhYWFhlwuJwUCgdnm5uaJlpbmI2Nu96X+vr4VdbffjlGPG/lcDhqt7o9yPjdV7XRs9YyNH7q2LvFNwi+//HLNpk2bfuL1el/geZ6RJAn5fB6iKCKTySCfz0MQBPA8D5VKFRi42FeaSiaIrCiivKIiqNNq3xgZGSnr6x94xTM2fp0FNwRoaWnB9u3b766pqWkXRbEmGo0q3G43RkaGQRIkjEYTQADpdBoAUFRUBJqmkckIYKNRtN5996sfffTRxe6enlEAg/7ANL+QzoIWNDc3EwcPHnxubGzsRY7jzF1dXfB4faioq8cjv9oNvbUIFEWDJAiQkJDmIvBccCE8OY5cLg/GYMSw27NBq2f+7Q9Mn1u+fLnh6NGPt3V1nXs2Fo+fevvtd54LBoPpG87Ae++9d7/D4TgkCIKho6MDKosNP3j0ZygvL4dBo4KSIiCkEpBlQM0wkGUgm81hOhDASOfn8I8OQxRF0DQ9abPZNhRYrVtEUdyq1Wi06TQf1OmZzY9v3fo5sMA+sGfPnhWNjY3vx+Pxko6DHVh61wO4b8PjsJs0QCaNnEKDQIRDmBeRysmIxpOQaQ1CAR90ahWqljWBYYwI+cbBp1KmSCT8kEatrpFlyTo40I+xMc9cU3OLd9++D88uCNDe3v5SIpH40cmTJwmF2YYf/nQLbEYtYpEIhse9CLGzyGQEMAYjFAoFkpEQ2JkAaJpGYVk5aJqCucgGiHOIBAPguJjB4x5h0nwqYbFYhpY3rHjqr/s+/JvH4xGvWwN79+6tmZiY2MGyLBHkEnhk+zYUqglEQ0F4QiwonRmEnEdBsQ0EAFKSYLulHEkuClKWQJEEKGLe2DJnLYRUEix7ApRCGdux86mWJ5/c6X/l9TfTV2petROGw+GHs9kscb6rC433rUFJUQF4ngcrypgYugiapmAtsgGShBQbQZINg5Ak6HU6lFXcCgoySFlCMsZBp2dQU78Mer0ekiRZ9u/fX9LTc+Eq8asA1q1bZ2hsbLw/l8shFo/DcUczrCYDxi55MdR9DnZHNb449Gec/fgg2MAkKBJgjAbMRkNQ0BQUJOBzD6LPdRpZgUdJaSnKKp24dckSGI1GHDt2bP1CC/6yBaIoWjKZjGVmZgaWIhsMJhNIALqSSlSZi8AYzSi7pQJ/efUluLvPYsuzL0GjVkNJkTCZzaBJAuVLHMhmSqHVaEAC0GjUsBYUQqVSIZFIFC0EQF4BYBRF0Tg7OwtjoQ1UXsR0cBoCn4Reb4BOq4W1sAjbdv8WZmshXvv1Npz/16cosFqh+Mp7vU4LlUKBcGAKQiqBdCIOlVoDmqahUCgW0v8vgCRJVDabpURRBK1UIptOYWygDzMTYxD5JCgCIAnAUlCAXzy9GzZ7Ob74+6HLeZokQBEEhHQKQZ8XoalJcJGZRcWvsoCiqKQkSUmappFJ82AshVh272qks/I1IvMQu1//w3yOIi/nSQKw2+2ovMUOigAokkBg3INMJgNBEBYHUCgUCVEUE2q1GlwwBDGbg0pBgyLkq8RJAlAQgNpguCr/9UNfAUsSgIKmkc/nIctyZlELWJYNC4LQTRAEUskEOL8XBGSwQR/YaR+EVAIUCShJYv5/J3HZ+/k2EGcjCAV8SHBRQMqDT8QxOuoBy7JobW39x6IALpdLDofDnyQSCej1elwavIBIYBKTwwOYGO5HPBKEgpgf1fxIv2qT821IEob6ejA+PIQ4x2JksB9cNAKWZeHz+fKrVq36bFELACAcDh93Op1fplKpuyaHL8K+pAqtq9eCJIAUF8WEZwhLnFVQKJUgya+mHTK4cAhSTkTrPfdCp9OAIoBYNILj//wEvb290tq1a9t37dp13V0AuOYscLlcMJlMPMMwD/B8SpWeZVFRVQutRouJ0WGEAz5YrQXQ63WQ81nQBAE5n0N351nkxQwMBgaMXoesIKD3Qg/OdXbC6/V68/n8bwYGBgLfCAAAarV6dOXKlfLk5OR9qUSCmOPCMJpMkHI53OpwoLi0FHPJWZw8dhjh6QBq6upQXV0NnVaLqYlL0Gk1GOzvx9GjR3D69Om59evX7zxz5sxxv9+/kP71ANPT0/lgMHhh5cqVt/n9/qUcGyWSbBgOhxOFJaXQqFRQ0hQyc2kweh3sdjtIAlAraOg0Gnx5+gucPfslTp06Ja5atar98OHDv+/s7JQXVMciV7L6+npm48aNT3d3d78gy7LeaDSiqqoKlY4qFJeUwlpgBUWSSM7OIjOXBhuNYGhoCL29vQiFQqG2trbnOzo69p8/fz53I41FAQCgoaFBuWfPng0HDhx4OhgMNuh0OhQXF8NgMMBisUCtVoPneYTDYfj9fvh8PixduvQIy7LtsVjsU5fLdcOR/08AX8czzzxDxmKxtmw2uyaXy92RyWQMgiAwkiTJSqVyVqVSxfR6vctkMh159913z3xzxW8J8HU0NTWRAOyJRMKQTCYZgiBko9E4azabY9lsNuRyub5NOQDAfwBU9w9d4+VBlQAAAABJRU5ErkJggg=='
close64 = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEQ0lEQVR42r2XW2wbRRSG/1177TgkdkyoS4shaaWogVIKRAXUVn4BgRBEIRBSkSK1lAakPhTxABJSK6BEtAoXCUHEWwWi4oEXUAVvRUASSBuJliAh5QJp6hrspoGQi69r73LO7Npu6kvsBGek0ezOrvf79szsmbG0D2iwAN8DaMQaFA0YHQFaLwCX6TQuHQAuNtjR2PawD05LZeFzKeC7b/txPoLxU8Aj1BVkAf1wqw/uejeU9RsASaqYQGp+Dv8EAvjgdD9OAg9S14gQOPKED1XNWyv7+lT0VArxiVH0fCUEOqjr3JoKcImN/pYW2EOnQyUJTESBJkdpgGkV8Cj/owDDdx59A8Mf92FT+GpR+KSlBrt6ehE6+hL0pLp6AYbvfusE5FontFgUZ989UVAiDU+X0OsvQ0/EVy4g4MeOQ3a6Mn38wKHet3MkrofzZJMsFlzpeRVaeLF8ASPsb8Javy7nDXRVxdA7x7FpIZQXnrlP0yDJMoKvHVpZBKq23Qv3M8/nzQt6PIah93qhRxaLwvPNhbLmgGP7Drg694mHlVqKwcsWEBItD8DVvleM6WrhRQXUwBSsnpthvclDY++BZLdnflS9YxecrZ2QFGVZePDIYcq5yWuGK47k39NIzlCdDkHxNuYXiJzrz/xIrr4BFpdbfAFyTS1CSi1uf7IDrqeeheyoLihxubsD2sI8UuEFaItUKfen5mahRcLZl7nft7xAvjIQs+GFP2cLCmjRCL5p3oDN6nzR56xIYDl4ORJlCwyqDnT7Z5aFL5G4w4vN8dnVCwymatA9daVkeCkSJQv8qDtxcDKYF86AwKEuSDYbvB+doq/DlnMPJ6uvmzfmSJQk0E9D+OLVcEG4f38bwgNnxLmz9Wl4+z6HZLXm3JuYHMfE7i0ri8Ck3Y3Hx4L0lvYl8Et7H0Xk7NJ7Xe1d8H74GX2/2YyZmv8XY3euo4SUXJkAFyvtEbdc+CsDn2r3Ifrrz3nHvW7Pftzy/kmxdhSCly2Qlmj66Xf88dB2qP6LRme+jauuo67rIDyvHMN4i1esmvlK6QIUTrEISbKxDnDlPkk2BK6VIDhXXaddP6Vk0H6A9wSUn0WKFn2lCgiYbDEmFVXJYjWOuU1LcHudgAASSLS0FnD4dV4TksYxNEOqsMDwgAAxELToSFZFfGaiVWzGNV6MWM4Uyc5OE8wQCr2AqwmxIuoJowX3k5CjZSd6vvxhqcBj921Fc2g8C2Mwzf5sax7zNZZjSdkcCg6/EEgacAYzlLZvRk1kW7rm39iELwZHsgLPATN311rqb7trG+65dT2FXTEg4o1NoDinZKOYQ8ICFo4ADwMJpEwBDrnKIU+YMqZQ0pAbC4QwODwCf0Rd/BQ4IATagM46oI+CeiNPPVS40EDF6M/pJ78Ap+n0PL8Cp7sGs9asgQSFDLxBmKJ6STKBVSbcZsa10gKcJHi/Hv0PWqbBbaFH/AEAAAAASUVORK5CYII='
def main():
+ btn_css = {
+ 'button_color': ('white', 'black'),
+ 'pad': (0, 0),
+ }
- toolbar_buttons = [[sg.Button('', image_data=close64,button_color=('white', 'black'), pad=(0,0), key='-CLOSE-'),
- sg.Button('', image_data=timer64, button_color=('white', 'black'), pad=(0, 0), key='-TIMER-'),
- sg.Button('', image_data=house64, button_color=('white', 'black'), pad=(0, 0), key='-HOUSE-'),
- sg.Button('', image_data=cpu64, button_color=('white', 'black'), pad=(0,0), key='-CPU-'),]]
+ toolbar_buttons = [[sg.Button(image_data=close64, **btn_css, key='-CLOSE-'),
+ sg.Button(image_data=timer64, **btn_css, key='-TIMER-'),
+ sg.Button(image_data=house64, **btn_css, key='-HOUSE-'),
+ sg.Button(image_data=cpu64, **btn_css, key='-CPU-'), ]]
# layout = toolbar_buttons
- layout = [[sg.Column( toolbar_buttons, background_color='black')]]
+ layout = [[sg.Col(toolbar_buttons, background_color='black')]]
- window = sg.Window('Toolbar', layout, no_titlebar=True, grab_anywhere=True, background_color='black', margins=(0,0))
+ window = sg.Window('Toolbar', layout, no_titlebar=True,
+ grab_anywhere=True, background_color='black', margins=(0, 0))
# ---===--- Loop taking in user input --- #
while True:
@@ -31,14 +37,16 @@ def main():
if button == '-CLOSE-' or button is None:
break # exit button clicked
elif button == '-TIMER-':
- print('Timer Button') # add your call to launch a timer program
+ # add your call to launch a timer program
+ print('Timer Button')
elif button == '-CPU-':
- print('CPU Button') # add your call to launch a CPU measuring utility
+ # add your call to launch a CPU measuring utility
+ print('CPU Button')
elif button == '-HOUSE-':
print('Home Button')
window.close()
+
if __name__ == '__main__':
main()
-
diff --git a/DemoPrograms/Demo_Font_Previewer.py b/DemoPrograms/Demo_Font_Previewer.py
index 1c7eeb63..db0e2d4e 100644
--- a/DemoPrograms/Demo_Font_Previewer.py
+++ b/DemoPrograms/Demo_Font_Previewer.py
@@ -1,10 +1,6 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
from tkinter import font
import tkinter
root = tkinter.Tk()
@@ -12,38 +8,38 @@ fonts = list(font.families())
fonts.sort()
root.destroy()
-sg.ChangeLookAndFeel('Black')
+'''
+ Showing fonts in PSG / tk
+'''
-layout = [[ sg.Text('My Text Element',
- size=(20,1),
- auto_size_text=False,
- click_submits=True,
- relief=sg.RELIEF_GROOVE,
- font = 'Courier` 25',
- text_color='#FF0000',
- background_color='white',
- justification='center',
- pad=(5,3),
- key='_text_',
- tooltip='This is a text element',
- ) ],
- [sg.Listbox(fonts, size=(30,20), change_submits=True, key='_list_')],
- [sg.Input(key='_in_')],
- [ sg.Button('Read', bind_return_key=True), sg.Exit()]]
+sg.change_look_and_feel('Black')
-window = sg.Window('My new window',
- # grab_anywhere=True,
- # force_toplevel=True,
- ).Layout(layout)
+layout = [[sg.Text('My Text Element',
+ size=(20, 1),
+ click_submits=True,
+ relief=sg.RELIEF_GROOVE,
+ font='Courier` 25',
+ text_color='#FF0000',
+ background_color='white',
+ justification='center',
+ pad=(5, 3),
+ key='-text-',
+ tooltip='This is a text element',
+ )],
+ [sg.Listbox(fonts, size=(30, 20), change_submits=True, key='-list-')],
+ [sg.Input(key='-in-')],
+ [sg.Button('Read', bind_return_key=True), sg.Exit()]]
+window = sg.Window('My new window', layout)
while True: # Event Loop
- event, values = window.Read()
- if event is None or event == 'Exit':
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
- text_elem = window.FindElement('_text_')
+ text_elem = window['-text-']
print(event, values)
- if values['_in_'] != '':
- text_elem.Update(font=values['_in_'])
+ if values['-in-'] != '':
+ text_elem.update(font=values['-in-'])
else:
- text_elem.Update(font=(values['_list_'][0], 25))
\ No newline at end of file
+ text_elem.update(font=(values['-list-'][0], 25))
+window.close()
diff --git a/DemoPrograms/Demo_Font_Sizer.py b/DemoPrograms/Demo_Font_Sizer.py
index 7050160e..84b56b57 100644
--- a/DemoPrograms/Demo_Font_Sizer.py
+++ b/DemoPrograms/Demo_Font_Sizer.py
@@ -1,30 +1,34 @@
+import PySimpleGUI as sg
# Testing async form, see if can have a slider
# that adjusts the size of text displayed
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
fontSize = 12
-layout = [[sg.Spin([sz for sz in range(6, 172)], font=('Helvetica 20'), initial_value=fontSize, change_submits=True, key='spin'),
- sg.Slider(range=(6,172), orientation='h', size=(10,20), change_submits=True, key='slider', font=('Helvetica 20')), sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]]
+layout = [
+ [sg.Spin([sz for sz in range(6, 172)],
+ font=('Helvetica 20'),
+ initial_value=fontSize,
+ change_submits=True, key='spin'),
+ sg.Slider(range=(6, 172), orientation='h', size=(10, 20), change_submits=True, key='slider',
+ font=('Helvetica 20')),
+ sg.Text("Aa", size=(2, 1), font="Helvetica " + str(fontSize), key='text')]
+]
sz = fontSize
-window = sg.Window("Font size selector", grab_anywhere=False)
-window.Layout(layout)
+window = sg.Window("Font size selector", layout, grab_anywhere=False)
while True:
- event, values= window.Read()
+ event, values = window.read()
if event is None or event == 'Quit':
break
sz_spin = int(values['spin'])
sz_slider = int(values['slider'])
+
sz = sz_spin if sz_spin != fontSize else sz_slider
+
if sz != fontSize:
fontSize = sz
font = "Helvetica " + str(fontSize)
- window.FindElement('text').Update(font=font)
- window.FindElement('slider').Update(sz)
- window.FindElement('spin').Update(sz)
+ window['text'].update(font=font)
+ window['slider'].update(sz)
+ window['spin'].update(sz)
-print("Done.")
+window.close()
diff --git a/DemoPrograms/Demo_Font_String.py b/DemoPrograms/Demo_Font_String.py
index b97c0f67..831839a0 100644
--- a/DemoPrograms/Demo_Font_String.py
+++ b/DemoPrograms/Demo_Font_String.py
@@ -1,34 +1,37 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-layout = [[sg.Text('This is my sample text',size=(20,1), key='_text_') ],
- [sg.CB('Bold', key='_bold_', change_submits=True),
- sg.CB('Italics', key='_italics_', change_submits=True),
- sg.CB('Underline', key='_underline_', change_submits=True)],
- [sg.Slider((6,50), default_value=12, size=(14,20), orientation='h', key='_slider_', change_submits=True),
+'''
+ App that shows "how fonts work in PySimpleGUI".
+'''
+
+layout = [[sg.Text('This is my sample text', size=(20, 1), key='-text-')],
+ [sg.CB('Bold', key='-bold-', change_submits=True),
+ sg.CB('Italics', key='-italics-', change_submits=True),
+ sg.CB('Underline', key='-underline-', change_submits=True)],
+ [sg.Slider((6, 50), default_value=12, size=(14, 20),
+ orientation='h', key='-slider-', change_submits=True),
sg.Text('Font size')],
- [sg.Text('Font string = '), sg.Text('', size=(25,1), key='_fontstring_')],
- [ sg.Button('Exit')]]
+ [sg.Text('Font string = '), sg.Text('', size=(25, 1), key='-fontstring-')],
+ [sg.Button('Exit')]]
-window = sg.Window('Font string builder').Layout(layout)
+window = sg.Window('Font string builder', layout)
-text_elem = window.FindElement('_text_')
+text_elem = window['-text-']
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event in (None, 'Exit'):
break
font_string = 'Helvitica '
- font_string += str(values['_slider_'])
- if values['_bold_']:
+ font_string += str(values['-slider-'])
+ if values['-bold-']:
font_string += ' bold'
- if values['_italics_']:
+ if values['-italics-']:
font_string += ' italic'
- if values['_underline_']:
+ if values['-underline-']:
font_string += ' underline'
- text_elem.Update(font=font_string)
- window.FindElement('_fontstring_').Update('"'+font_string+'"')
+ text_elem.update(font=int(font_string.split(' ')[1].split('.')[0]))
+ window['-fontstring-'].update('"'+font_string+'"')
print(event, values)
+
+window.close()
diff --git a/DemoPrograms/Demo_GoodColors.py b/DemoPrograms/Demo_GoodColors.py
index 8ea1ba9c..7132eca7 100644
--- a/DemoPrograms/Demo_GoodColors.py
+++ b/DemoPrograms/Demo_GoodColors.py
@@ -1,53 +1,58 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
+
+# Example of colors in PSG
+
def main():
# ------- Make a new Window ------- #
- window = sg.Window('GoodColors', auto_size_text=True, default_element_size=(30,2))
- window.AddRow(sg.Text('Having trouble picking good colors? Try one of the colors defined by PySimpleGUI'))
+ window = sg.Window('GoodColors', default_element_size=(30, 2))
+ window.AddRow(sg.Text('Having trouble picking good colors? Try this'))
window.AddRow(sg.Text('Here come the good colors as defined by PySimpleGUI'))
#===== Show some nice BLUE colors with yellow text ===== ===== ===== ===== ===== ===== =====#
text_color = sg.YELLOWS[0]
- buttons = (sg.Button('BLUES[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.BLUES))
- window.AddRow(sg.T('Button Colors Using PySimpleGUI.BLUES'))
+ buttons = (sg.Button('BLUES[{}]\n{}'.format(j, c), button_color=(
+ text_color, c), size=(10, 2)) for j, c in enumerate(sg.BLUES))
+ window.AddRow(sg.Text('Button Colors Using PySimpleGUI.BLUES'))
window.AddRow(*buttons)
window.AddRow(sg.Text('_' * 100, size=(65, 1)))
#===== Show some nice PURPLE colors with yellow text ===== ===== ===== ===== ===== ===== =====#
- buttons = (sg.Button('PURPLES[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.PURPLES))
- window.AddRow(sg.T('Button Colors Using PySimpleGUI.PURPLES'))
+ buttons = (sg.Button('PURPLES[{}]\n{}'.format(j, c), button_color=(
+ text_color, c), size=(10, 2)) for j, c in enumerate(sg.PURPLES))
+ window.AddRow(sg.Text('Button Colors Using PySimpleGUI.PURPLES'))
window.AddRow(*buttons)
window.AddRow(sg.Text('_' * 100, size=(65, 1)))
#===== Show some nice GREEN colors with yellow text ===== ===== ===== ===== ===== ===== =====#
- buttons = (sg.Button('GREENS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.GREENS))
- window.AddRow(sg.T('Button Colors Using PySimpleGUI.GREENS'))
+ buttons = (sg.Button('GREENS[{}]\n{}'.format(j, c), button_color=(
+ text_color, c), size=(10, 2)) for j, c in enumerate(sg.GREENS))
+ window.AddRow(sg.Text('Button Colors Using PySimpleGUI.GREENS'))
window.AddRow(*buttons)
window.AddRow(sg.Text('_' * 100, size=(65, 1)))
#===== Show some nice TAN colors with yellow text ===== ===== ===== ===== ===== ===== =====#
text_color = sg.GREENS[0] # let's use GREEN text on the tan
- buttons = (sg.Button('TANS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.TANS))
- window.AddRow(sg.T('Button Colors Using PySimpleGUI.TANS'))
+ buttons = (sg.Button('TANS[{}]\n{}'.format(j, c), button_color=(
+ text_color, c), size=(10, 2)) for j, c in enumerate(sg.TANS))
+ window.AddRow(sg.Text('Button Colors Using PySimpleGUI.TANS'))
window.AddRow(*buttons)
window.AddRow(sg.Text('_' * 100, size=(65, 1)))
#===== Show some nice YELLOWS colors with black text ===== ===== ===== ===== ===== ===== =====#
text_color = 'black' # let's use black text on the tan
- buttons = (sg.Button('YELLOWS[{}]\n{}'.format(j, c), button_color=(text_color, c), size=(10,2)) for j, c in enumerate(sg.YELLOWS))
- window.AddRow(sg.T('Button Colors Using PySimpleGUI.YELLOWS'))
+ buttons = (sg.Button('YELLOWS[{}]\n{}'.format(j, c), button_color=(
+ text_color, c), size=(10, 2)) for j, c in enumerate(sg.YELLOWS))
+ window.AddRow(sg.Text('Button Colors Using PySimpleGUI.YELLOWS'))
window.AddRow(*buttons)
window.AddRow(sg.Text('_' * 100, size=(65, 1)))
-
#===== Add a click me button for fun and SHOW the window ===== ===== ===== ===== ===== ===== =====#
window.AddRow(sg.Button('Click ME!'))
- event, values = window.Read() # show it!
+ event, values = window.read()
+
+ window.close()
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_GoodColors_2.py b/DemoPrograms/Demo_GoodColors_2.py
new file mode 100644
index 00000000..5da9f0a0
--- /dev/null
+++ b/DemoPrograms/Demo_GoodColors_2.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+import PySimpleGUI as sg
+
+'''
+ Example of colors in PySimpleGUI
+'''
+
+def main():
+ s10 = (10, 2)
+
+ window = sg.Window('GoodColors', [
+ [sg.Text('Having trouble picking good colors? Try this')],
+ [sg.Text('Here come the good colors as defined by PySimpleGUI')],
+ [sg.Text('Button Colors Using PySimpleGUI.BLUES')],
+
+ [*[sg.Button('BLUES[{}]\n{}'.format(j, c), button_color=(sg.YELLOWS[0], c), size=s10)
+ for j, c in enumerate(sg.BLUES)]],
+ [sg.Text('_' * 100, size=(65, 1))],
+ [sg.Text('Button Colors Using PySimpleGUI.PURPLES')],
+
+ [*[sg.Button('PURPLES[{}]\n{}'.format(j, c), button_color=(sg.YELLOWS[0], c), size=s10)
+ for j, c in enumerate(sg.PURPLES)]],
+ [sg.Text('_' * 100, size=(65, 1))],
+ [sg.Text('Button Colors Using PySimpleGUI.GREENS')],
+
+ [*[sg.Button('GREENS[{}]\n{}'.format(j, c), button_color=(sg.YELLOWS[0], c), size=s10)
+ for j, c in enumerate(sg.GREENS)]],
+ [sg.Text('_' * 100, size=(65, 1))],
+ [sg.Text('Button Colors Using PySimpleGUI.TANS')],
+
+ [*[sg.Button('TANS[{}]\n{}'.format(j, c), button_color=(sg.GREENS[0], c), size=s10)
+ for j, c in enumerate(sg.TANS)]],
+ [sg.Text('_' * 100, size=(65, 1))],
+ [sg.Text('Button Colors Using PySimpleGUI.YELLOWS')],
+
+ [*[sg.Button('YELLOWS[{}]\n{}'.format(j, c), button_color=('black', c), size=s10)
+ for j, c in enumerate(sg.YELLOWS)]],
+
+ [sg.Text('_' * 100, size=(65, 1))],
+ [sg.Button('Click ME!')]
+ ], default_element_size=(30, 2))
+
+ event, values = window.read()
+ window.close()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms/Demo_Google_TTS.py b/DemoPrograms/Demo_Google_TTS.py
index ee8557bb..279d066c 100644
--- a/DemoPrograms/Demo_Google_TTS.py
+++ b/DemoPrograms/Demo_Google_TTS.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
from gtts import gTTS
from pygame import mixer
import time
@@ -19,16 +15,16 @@ import os
'''
layout = [[sg.Text('What would you like me to say?')],
- [sg.Multiline(size=(60,10), enter_submits=True)],
+ [sg.MLine(size=(60,10), enter_submits=True)],
[sg.Button('Speak', bind_return_key=True), sg.Exit()]]
-window = sg.Window('Google Text to Speech').Layout(layout)
+window = sg.Window('Google Text to Speech', layout)
i = 0
mixer.init()
while True:
- event, values = window.Read()
- if event is None or event == 'Exit':
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
# Get the text and convert to mp3 file
tts = gTTS(text=values[0], lang='en',slow=False)
@@ -42,6 +38,8 @@ while True:
mixer.stop()
i += 1
+window.close()
+
# try to remove the temp files. You'll likely be left with 1 to clean up
try:
os.remove('speech0.mp3')
diff --git a/DemoPrograms/Demo_Graph_Ball_Game.py b/DemoPrograms/Demo_Graph_Ball_Game.py
index e1c3c642..5dca7ef8 100644
--- a/DemoPrograms/Demo_Graph_Ball_Game.py
+++ b/DemoPrograms/Demo_Graph_Ball_Game.py
@@ -10,21 +10,24 @@ import socket
Note this exact same demo runs with PySimpleGUIWeb by changing the import statement
"""
+
class Ball():
def __init__(self, x, y, r, graph_elem, *args, **kwargs):
mass = 10
- self.body = pymunk.Body(mass,
- pymunk.moment_for_circle(mass, 0, r, (0, 0))) # Create a Body with mass and moment
+ # Create a Body with mass and moment
+ self.body = pymunk.Body(
+ mass, pymunk.moment_for_circle(mass, 0, r, (0, 0)))
self.body.position = x, y
- self.shape = pymunk.Circle(self.body, r, offset=(0, 0)) # Create a box shape and attach to body
+ # Create a box shape and attach to body
+ self.shape = pymunk.Circle(self.body, r, offset=(0, 0))
self.shape.elasticity = 0.99999
self.shape.friction = 0.8
self.gui_circle_figure = None
self.graph_elem = graph_elem
def move(self):
- self.graph_elem.RelocateFigure(self.gui_circle_figure, self.body.position[0], ball.body.position[1])
-
+ self.graph_elem.RelocateFigure(
+ self.gui_circle_figure, self.body.position[0], ball.body.position[1])
class Playfield():
@@ -37,9 +40,8 @@ class Playfield():
self.arena_balls = [] # type: [] Ball
self.graph_elem = graph_elem # type: sg.Graph
-
def add_wall(self, pt_from, pt_to):
- body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
ground_shape = pymunk.Segment(body, pt_from, pt_to, 0.0)
ground_shape.friction = 0.8
ground_shape.elasticity = .99
@@ -50,26 +52,35 @@ class Playfield():
x = random.randint(0, 600)
y = random.randint(0, 400)
r = random.randint(1, 10)
- self.add_ball(x,y,r)
+ self.add_ball(x, y, r)
def add_ball(self, x, y, r, fill_color='black', line_color='red'):
ball = Ball(x, y, r, self.graph_elem)
self.arena_balls.append(ball)
area.space.add(ball.body, ball.shape)
- ball.gui_circle_figure = self.graph_elem.DrawCircle((x, y), r, fill_color=fill_color, line_color=line_color)
+ ball.gui_circle_figure = self.graph_elem.draw_circle(
+ (x, y), r, fill_color=fill_color, line_color=line_color)
return ball
def shoot_a_ball(self, x, y, r, vector=(-10, 0), fill_color='black', line_color='red'):
- ball = self.add_ball(x,y,r, fill_color=fill_color, line_color=line_color )
+ ball = self.add_ball(
+ x, y, r, fill_color=fill_color, line_color=line_color)
# ball.shape.surface_velocity=10
ball.body.apply_impulse_at_local_point(100*pymunk.Vec2d(vector))
-# ------------------- Build and show the GUI Window -------------------
-graph_elem = sg.Graph((600, 400), (0, 400), (600, 0), enable_events=True, key='_GRAPH_', background_color='lightblue')
-layout = [[sg.Text('Ball Test'), sg.T('My IP {}'.format(socket.gethostbyname(socket.gethostname())))],
+# ------------------- Build and show the GUI Window -------------------
+graph_elem = sg.Graph((600, 400), (0, 400), (600, 0),
+ enable_events=True,
+ key='-GRAPH-',
+ background_color='lightblue')
+
+hostname = socket.gethostbyname(socket.gethostname())
+layout = [[sg.Text('Ball Test'), sg.Text('My IP {}'.format(hostname))],
[graph_elem],
- [sg.B('Kick'), sg.B('Player 1 Shoot', size=(15,2)),sg.B('Player 2 Shoot', size=(15,2)), sg.Button('Exit')]]
+ [sg.Button('Kick'), sg.Button('Player 1 Shoot', size=(15, 2)),
+ sg.Button('Player 2 Shoot', size=(15, 2)), sg.Button('Exit')]
+ ]
window = sg.Window('Window Title', layout, disable_close=True)
@@ -78,20 +89,23 @@ area = Playfield(graph_elem)
# ------------------- GUI Event Loop -------------------
while True: # Event Loop
- event, values = window.Read(timeout=10)
+ event, values = window.read(timeout=10)
# print(event, values)
if event in (None, 'Exit'):
break
+
area.space.step(0.01)
if event == 'Player 2 Shoot':
- area.shoot_a_ball(555, 200, 5, (-10,0), fill_color='green', line_color='green')
+ area.shoot_a_ball(555, 200, 5, (-10, 0),
+ fill_color='green', line_color='green')
elif event == 'Player 1 Shoot':
- area.shoot_a_ball(10, 200, 5, (10,0))
+ area.shoot_a_ball(10, 200, 5, (10, 0))
for ball in area.arena_balls:
if event == 'Kick':
- ball.body.position = ball.body.position[0], ball.body.position[1]-random.randint(1,200)
+ pos = ball.body.position[0], ball.body.position[1]-random.randint(1, 200)
+ ball.body.position = pos
ball.move()
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Graph_Drag_Rectangle.py b/DemoPrograms/Demo_Graph_Drag_Rectangle.py
index b23a795f..dd4854d2 100644
--- a/DemoPrograms/Demo_Graph_Drag_Rectangle.py
+++ b/DemoPrograms/Demo_Graph_Drag_Rectangle.py
@@ -19,20 +19,22 @@ layout = [[sg.Graph(
key="-GRAPH-",
change_submits=True, # mouse click events
drag_submits=True),],
- [sg.Text("", key="info", size=(60, 1))]]
+ [sg.Text(key='info', size=(60, 1))]]
window = sg.Window("draw rect on image", layout, finalize=True)
# get the graph element for ease of use later
graph = window["-GRAPH-"] # type: sg.Graph
-graph.DrawImage(image_file, location=(0,0)) if image_file else None
+graph.draw_image(image_file, location=(0,0)) if image_file else None
dragging = False
start_point = end_point = prior_rect = None
while True:
- event, values = window.Read()
+ event, values = window.read()
+
if event is None:
break # exit
+
if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse
x, y = values["-GRAPH-"]
if not dragging:
@@ -41,13 +43,15 @@ while True:
else:
end_point = (x, y)
if prior_rect:
- graph.DeleteFigure(prior_rect)
+ graph.delete_figure(prior_rect)
if None not in (start_point, end_point):
- prior_rect = graph.DrawRectangle(start_point, end_point, line_color='red')
+ prior_rect = graph.draw_rectangle(start_point, end_point, line_color='red')
+
elif event.endswith('+UP'): # The drawing has ended because mouse up
- info = window.Element("info")
- info.Update(value=f"grabbed rectangle from {start_point} to {end_point}")
+ info = window["info"]
+ info.update(value=f"grabbed rectangle from {start_point} to {end_point}")
start_point, end_point = None, None # enable grabbing a new rect
dragging = False
+
else:
print("unhandled event", event, values)
diff --git a/DemoPrograms/Demo_Graph_Drawing.py b/DemoPrograms/Demo_Graph_Drawing.py
index 8c11ced1..d0f90bee 100644
--- a/DemoPrograms/Demo_Graph_Drawing.py
+++ b/DemoPrograms/Demo_Graph_Drawing.py
@@ -1,31 +1,31 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-layout = [[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0,0), graph_top_right=(400, 400), background_color='red', key='graph')],
- [sg.T('Change circle color to:'), sg.Button('Red'), sg.Button('Blue'), sg.Button('Move')]]
+# Usage of Graph element.
-window = sg.Window('Graph test').Layout(layout).Finalize()
+layout = [[sg.Graph(canvas_size=(400, 400), graph_bottom_left=(0, 0), graph_top_right=(400, 400), background_color='red', key='graph')],
+ [sg.Text('Change circle color to:'), sg.Button('Red'), sg.Button('Blue'), sg.Button('Move')]]
-graph = window.FindElement('graph')
-circle =graph .DrawCircle((75,75), 25, fill_color='black',line_color='white')
-point = graph.DrawPoint((75,75), 10, color='green')
-oval = graph.DrawOval((25,300), (100,280), fill_color='purple', line_color='purple' )
-rectangle = graph.DrawRectangle((25,300), (100,280), line_color='purple' )
-line = graph.DrawLine((0,0), (100,100))
-arc = graph.DrawArc((0,0), (400,400), 160, 10, style='arc' ,arc_color='blue')
+window = sg.Window('Graph test', layout, finalize=True)
+
+graph = window['graph']
+circle = graph.draw_circle((75, 75), 25, fill_color='black', line_color='white')
+point = graph.draw_point((75, 75), 10, color='green')
+oval = graph.draw_oval((25, 300), (100, 280), fill_color='purple', line_color='purple')
+rectangle = graph.draw_rectangle((25, 300), (100, 280), line_color='purple')
+line = graph.draw_line((0, 0), (100, 100))
+arc = graph.draw_arc((0, 0), (400, 400), 160, 10, style='arc', arc_color='blue')
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event in ('Blue', 'Red'):
graph.TKCanvas.itemconfig(circle, fill=event)
elif event == 'Move':
- graph.MoveFigure(point, 10,10)
- graph.MoveFigure(circle, 10,10)
- graph.MoveFigure(oval, 10,10)
- graph.MoveFigure(rectangle, 10,10)
- graph.MoveFigure(arc, 10,10)
+ graph.MoveFigure(point, 10, 10)
+ graph.MoveFigure(circle, 10, 10)
+ graph.MoveFigure(oval, 10, 10)
+ graph.MoveFigure(rectangle, 10, 10)
+ graph.MoveFigure(arc, 10, 10)
+
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Graph_Element.py b/DemoPrograms/Demo_Graph_Element.py
index 67f28ac5..906fa439 100644
--- a/DemoPrograms/Demo_Graph_Element.py
+++ b/DemoPrograms/Demo_Graph_Element.py
@@ -1,27 +1,25 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import ping
from threading import Thread
import time
-STEP_SIZE=1
+STEP_SIZE = 1
SAMPLES = 1000
-CANVAS_SIZE = (1000,500)
+CANVAS_SIZE = (1000, 500)
# globale used to communicate with thread.. yea yea... it's working fine
g_exit = False
g_response_time = None
+
+
def ping_thread(args):
global g_exit, g_response_time
while not g_exit:
g_response_time = ping.quiet_ping('google.com', timeout=1000)
+
def main():
global g_exit, g_response_time
@@ -29,21 +27,27 @@ def main():
thread = Thread(target=ping_thread, args=(None,))
thread.start()
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0))
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0, 0))
- layout = [ [sg.T('Ping times to Google.com', font='Any 12'), sg.Quit(pad=((100,0), 0), button_color=('white', 'black'))],
- [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,500),background_color='black', key='graph')],]
+ layout = [
+ [sg.Text('Ping times to Google.com', font='Any 12'),
+ sg.Quit(pad=((100, 0), 0), button_color=('white', 'black'))],
+ [sg.Graph(CANVAS_SIZE, (0, 0), (SAMPLES, 500),
+ background_color='black', key='graph')]
+ ]
- window = sg.Window('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False).Layout(layout)
-
- graph = window.FindElement('graph')
+ window = sg.Window('Canvas test', layout,
+ grab_anywhere=True, background_color='black',
+ no_titlebar=False, use_default_focus=False)
+ graph = window['graph']
prev_response_time = None
- i=0
- prev_x, prev_y = 0, 0
+ i = 0
+ prev_x, prev_y = 0, 0
+
while True:
- event, values = window.Read(timeout=200)
+ event, values = window.read(timeout=200)
if event == 'Quit' or event is None:
break
if g_response_time is None or prev_response_time == g_response_time:
@@ -51,10 +55,10 @@ def main():
new_x, new_y = i, g_response_time[0]
prev_response_time = g_response_time
if i >= SAMPLES:
- graph.Move(-STEP_SIZE,0)
+ graph.move(-STEP_SIZE, 0)
prev_x = prev_x - STEP_SIZE
- graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
- # window.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+ graph.draw_line((prev_x, prev_y), (new_x, new_y), color='white')
+ # window['graph'].draw_point((new_x, new_y), color='red')
prev_x, prev_y = new_x, new_y
i += STEP_SIZE if i < SAMPLES else 0
@@ -62,6 +66,8 @@ def main():
g_exit = True
thread.join()
+ window.close()
+
if __name__ == '__main__':
main()
diff --git a/DemoPrograms/Demo_Graph_Element_Sine_Wave.py b/DemoPrograms/Demo_Graph_Element_Sine_Wave.py
index dc2b7305..c69072fd 100644
--- a/DemoPrograms/Demo_Graph_Element_Sine_Wave.py
+++ b/DemoPrograms/Demo_Graph_Element_Sine_Wave.py
@@ -1,38 +1,42 @@
-# import PySimpleGUIWeb as sg
-# import PySimpleGUIQt as sg
import PySimpleGUI as sg
import math
+# Yet another usage of Graph element.
+
SIZE_X = 200
SIZE_Y = 100
NUMBER_MARKER_FREQUENCY = 25
+
def draw_axis():
- graph.draw_line((-SIZE_X,0), (SIZE_X, 0)) # axis lines
- graph.draw_line((0,-SIZE_Y), (0,SIZE_Y))
+ graph.draw_line((-SIZE_X, 0), (SIZE_X, 0)) # axis lines
+ graph.draw_line((0, -SIZE_Y), (0, SIZE_Y))
for x in range(-SIZE_X, SIZE_X+1, NUMBER_MARKER_FREQUENCY):
- graph.draw_line((x,-3), (x,3)) # tick marks
+ graph.draw_line((x, -3), (x, 3)) # tick marks
if x != 0:
- graph.draw_text( str(x), (x,-10), color='green', font='Algerian 15') # numeric labels
+ # numeric labels
+ graph.draw_text(str(x), (x, -10), color='green')
for y in range(-SIZE_Y, SIZE_Y+1, NUMBER_MARKER_FREQUENCY):
- graph.draw_line((-3,y), (3,y))
+ graph.draw_line((-3, y), (3, y))
if y != 0:
- graph.draw_text( str(y), (-10,y), color='blue')
+ graph.draw_text(str(y), (-10, y), color='blue')
+
# Create the graph that will be put into the window
graph = sg.Graph(canvas_size=(400, 400),
- graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
- graph_top_right=(SIZE_X+5, SIZE_Y+5),
- background_color='white',
- key='graph')
+ graph_bottom_left=(-(SIZE_X+5), -(SIZE_Y+5)),
+ graph_top_right=(SIZE_X+5, SIZE_Y+5),
+ background_color='white',
+ key='graph')
# Window layout
-layout = [[sg.Text('Example of Using Math with a Graph', justification='center', size=(50,1), relief=sg.RELIEF_SUNKEN)],
+layout = [[sg.Text('Example of Using Math with a Graph', justification='center', size=(50, 1), relief=sg.RELIEF_SUNKEN)],
[graph],
- [sg.Text('y = sin(x / x2 * x1)', font='Algerian 18')],
- [sg.Text('x1'),sg.Slider((0,200), orientation='h', enable_events=True,key='_SLIDER_')],
- [sg.Text('x2'),sg.Slider((1,200), orientation='h', enable_events=True,key='_SLIDER2_')]]
+ [sg.Text('y = sin(x / x2 * x1)', font='COURIER 18')],
+ [sg.Text('x1'), sg.Slider((0, 200), orientation='h',
+ enable_events=True, key='-SLIDER-')],
+ [sg.Text('x2'), sg.Slider((1, 200), orientation='h', enable_events=True, key='-SLIDER2-')]]
window = sg.Window('Graph of Sine Function', layout)
@@ -43,9 +47,11 @@ while True:
graph.erase()
draw_axis()
prev_x = prev_y = None
- for x in range(-SIZE_X,SIZE_X):
- y = math.sin(x/int(values['_SLIDER2_']))*int(values['_SLIDER_'])
+
+ for x in range(-SIZE_X, SIZE_X):
+ y = math.sin(x/int(values['-SLIDER2-']))*int(values['-SLIDER-'])
if prev_x is not None:
- graph.draw_line((prev_x, prev_y), (x,y), color='red')
+ graph.draw_line((prev_x, prev_y), (x, y), color='red')
prev_x, prev_y = x, y
+window.close()
diff --git a/DemoPrograms/Demo_Graph_Noise.py b/DemoPrograms/Demo_Graph_Noise.py
index c1893514..6161c2d6 100644
--- a/DemoPrograms/Demo_Graph_Noise.py
+++ b/DemoPrograms/Demo_Graph_Noise.py
@@ -1,69 +1,76 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import random
import sys
-STEP_SIZE=1
+'''
+ Example of random line in Graph element.
+'''
+
+STEP_SIZE = 1
SAMPLES = 300
SAMPLE_MAX = 300
-CANVAS_SIZE = (300,300)
+CANVAS_SIZE = (300, 300)
def main():
global g_exit, g_response_time
- layout = [[sg.T('Enter width, height of graph')],
- [sg.In(size=(6, 1)), sg.In(size=(6, 1))],
+ layout = [[sg.Text('Enter width, height of graph')],
+ [sg.Input(size=(6, 1), key='w'), sg.Input(size=(6, 1), key='h')],
[sg.Ok(), sg.Cancel()]]
- window = sg.Window('Enter graph size').Layout(layout)
- b,v = window.Read()
- if b is None or b == 'Cancel':
- sys.exit(69)
- w, h = int(v[0]), int(v[1])
- CANVAS_SIZE = (w,h)
+ window = sg.Window('Enter graph size', layout)
+ event, values = window.read()
+ if event is None or event == 'Cancel':
+ return
+
+ CANVAS_SIZE = int(values['w']), int(values['h'])
+ window.close()
# start ping measurement thread
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0))
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0, 0))
- layout = [ [sg.Button('Quit', button_color=('white','black'))],
- [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,SAMPLE_MAX),background_color='black', key='graph')],]
+ layout = [[sg.Button('Quit', button_color=('white', 'black'))],
+ [sg.Graph(CANVAS_SIZE, (0, 0), (SAMPLES, SAMPLE_MAX)
+ background_color='black', key='graph')], ]
- window = sg.Window('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False).Layout(layout).Finalize()
- graph = window.FindElement('graph')
+ window = sg.Window('Canvas test', layout, grab_anywhere=True,
+ background_color='black', no_titlebar=False,
+ use_default_focus=False, finalize=True)
+ graph = window['graph']
prev_response_time = None
- i=0
+ i = 0
prev_x, prev_y = 0, 0
graph_value = 250
while True:
- # time.sleep(.2)
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event == 'Quit' or event is None:
break
+
graph_offset = random.randint(-10, 10)
graph_value = graph_value + graph_offset
+
if graph_value > SAMPLE_MAX:
graph_value = SAMPLE_MAX
if graph_value < 0:
graph_value = 0
+
new_x, new_y = i, graph_value
prev_value = graph_value
+
if i >= SAMPLES:
- graph.Move(-STEP_SIZE,0)
+ graph.move(-STEP_SIZE, 0)
prev_x = prev_x - STEP_SIZE
- graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
- # window.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+
+ graph.draw_line((prev_x, prev_y), (new_x, new_y), color='white')
prev_x, prev_y = new_x, new_y
i += STEP_SIZE if i < SAMPLES else 0
+ window.close()
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Graph__Element.py b/DemoPrograms/Demo_Graph__Element.py
index 174ce1fb..1a40ef37 100644
--- a/DemoPrograms/Demo_Graph__Element.py
+++ b/DemoPrograms/Demo_Graph__Element.py
@@ -1,21 +1,25 @@
-import ping
-from threading import Thread
-import time
import PySimpleGUI as sg
+from threading import Thread
+import ping
+import time
+# Yet another usage of Graph element.
-STEP_SIZE=1
+STEP_SIZE = 1
SAMPLES = 6000
-CANVAS_SIZE = (6000,500)
+CANVAS_SIZE = (6000, 500)
# globale used to communicate with thread.. yea yea... it's working fine
g_exit = False
g_response_time = None
+
+
def ping_thread(args):
global g_exit, g_response_time
while not g_exit:
g_response_time = ping.quiet_ping('google.com', timeout=1000)
+
def main():
global g_exit, g_response_time
@@ -23,21 +27,22 @@ def main():
thread = Thread(target=ping_thread, args=(None,))
thread.start()
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(element_padding=(0,0))
+ sg.change_look_and_feel('Black')
+ sg.set_options(element_padding=(0, 0))
- layout = [ [sg.T('Ping times to Google.com', font='Any 12'), sg.Quit(pad=((100,0), 0), button_color=('white', 'black'))],
- [sg.Graph(CANVAS_SIZE, (0,0), (SAMPLES,500),background_color='black', key='graph')],]
+ layout = [[sg.Text('Ping times to Google.com', font='Any 12'),
+ sg.Quit(pad=((100, 0), 0), button_color=('white', 'black'))],
+ [sg.Graph(CANVAS_SIZE, (0, 0), (SAMPLES, 500),
+ background_color='black', key='graph')]
+ ]
- form = sg.FlexForm('Canvas test', grab_anywhere=True, background_color='black', no_titlebar=False, use_default_focus=False)
- form.Layout(layout)
-
- form.Finalize()
- graph = form.FindElement('graph')
+ form = sg.FlexForm('Canvas test', layout, grab_anywhere=True, background_color='black',
+ no_titlebar=False, use_default_focus=False, finalize=True)
+ graph = form['graph']
prev_response_time = None
- i=0
- prev_x, prev_y = 0, 0
+ i = 0
+ prev_x, prev_y = 0, 0
while True:
time.sleep(.2)
@@ -49,10 +54,10 @@ def main():
new_x, new_y = i, g_response_time[0]
prev_response_time = g_response_time
if i >= SAMPLES:
- graph.Move(-STEP_SIZE,0)
+ graph.move(-STEP_SIZE, 0)
prev_x = prev_x - STEP_SIZE
- graph.DrawLine((prev_x, prev_y), (new_x, new_y), color='white')
- # form.FindElement('graph').DrawPoint((new_x, new_y), color='red')
+ graph.draw_line((prev_x, prev_y), (new_x, new_y), color='white')
+ # form['graph'].draw_point((new_x, new_y), color='red')
prev_x, prev_y = new_x, new_y
i += STEP_SIZE if i < SAMPLES else 0
@@ -63,4 +68,4 @@ def main():
if __name__ == '__main__':
main()
- exit(69)
\ No newline at end of file
+
diff --git a/DemoPrograms/Demo_Graph_pymunk_2D_Graphics.py b/DemoPrograms/Demo_Graph_pymunk_2D_Graphics.py
index 9dcd8e9c..625095c6 100644
--- a/DemoPrograms/Demo_Graph_pymunk_2D_Graphics.py
+++ b/DemoPrograms/Demo_Graph_pymunk_2D_Graphics.py
@@ -1,26 +1,32 @@
import PySimpleGUIWeb as sg
-# import PySimpleGUI as sg
import pymunk
import random
import socket
+# Yet another usage of Graph element and physics from pymunk.
+
+
"""
Demo that shows integrating PySimpleGUI with the pymunk library. This combination
of PySimpleGUI and pymunk could be used to build games.
Note this exact same demo runs with PySimpleGUIWeb by changing the import statement
"""
+
class Ball():
def __init__(self, x, y, r, *args, **kwargs):
mass = 10
- self.body = pymunk.Body(mass,
- pymunk.moment_for_circle(mass, 0, r, (0, 0))) # Create a Body with mass and moment
+ # Create a Body with mass and moment
+ self.body = pymunk.Body(
+ mass, pymunk.moment_for_circle(mass, 0, r, (0, 0)))
self.body.position = x, y
- self.shape = pymunk.Circle(self.body, r, offset=(0, 0)) # Create a box shape and attach to body
+ # Create a box shape and attach to body
+ self.shape = pymunk.Circle(self.body, r, offset=(0, 0))
self.shape.elasticity = 0.99999
self.shape.friction = 0.8
self.gui_circle_figure = None
+
class Playfield():
def __init__(self):
self.space = pymunk.Space()
@@ -30,7 +36,7 @@ class Playfield():
self.add_wall((600, 0), (600, 400)) # right side
def add_wall(self, pt_from, pt_to):
- body = pymunk.Body(body_type=pymunk.Body.STATIC)
+ body = pymunk.Body(body_type=pymunk.Body.STATIC)
ground_shape = pymunk.Segment(body, pt_from, pt_to, 0.0)
ground_shape.friction = 0.8
ground_shape.elasticity = .99
@@ -45,25 +51,28 @@ class Playfield():
ball = Ball(x, y, r)
self.arena_balls.append(ball)
area.space.add(ball.body, ball.shape)
- ball.gui_circle_figure = graph_elem.DrawCircle((x, y), r, fill_color='black', line_color='red')
+ ball.gui_circle_figure = graph_elem.draw_circle(
+ (x, y), r, fill_color='black', line_color='red')
# ------------------- Build and show the GUI Window -------------------
-graph_elem = sg.Graph((600, 400), (0, 400), (600, 0), enable_events=True, key='_GRAPH_', background_color='lightblue')
+graph_elem = sg.Graph((600, 400), (0, 400), (600, 0),
+ enable_events=True, key='-GRAPH-', background_color='lightblue')
-layout = [[sg.Text('Ball Test'), sg.T('My IP {}'.format(socket.gethostbyname(socket.gethostname())))],
+hostname = socket.gethostbyname(socket.gethostname())
+layout = [[sg.Text('Ball Test'), sg.Text('My IP '+hostname)],
[graph_elem],
# [sg.Up(), sg.Down()],
- [sg.B('Kick'), sg.Button('Exit')]]
+ [sg.Button('Kick'), sg.Button('Exit')]]
-window = sg.Window('Window Title', layout, ).Finalize()
+window = sg.Window('Window Title', layout, finalize=True)
area = Playfield()
area.add_balls()
# ------------------- GUI Event Loop -------------------
while True: # Event Loop
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
# print(event, values)
if event in (None, 'Exit'):
break
@@ -71,7 +80,9 @@ while True: # Event Loop
for ball in area.arena_balls:
if event == 'Kick':
- ball.body.position = ball.body.position[0], ball.body.position[1]-random.randint(1,200)
- graph_elem.RelocateFigure(ball.gui_circle_figure, ball.body.position[0], ball.body.position[1])
+ ball.body.position = ball.body.position[0], ball.body.position[1]-random.randint(
+ 1, 200)
+ graph_elem.RelocateFigure(
+ ball.gui_circle_figure, ball.body.position[0], ball.body.position[1])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Hello_World.py b/DemoPrograms/Demo_Hello_World.py
index eb7ee429..ae48f477 100644
--- a/DemoPrograms/Demo_Hello_World.py
+++ b/DemoPrograms/Demo_Hello_World.py
@@ -1,15 +1,18 @@
import PySimpleGUI as sg
"""
- Oh yes, the classic "Hello World". The problem is that you
+ Oh yes, the classic "Hello World". The problem is that you
can do it so many ways using PySimpleGUI
"""
-sg.PopupNoButtons('Hello World') # the single line
+sg.popup_no_buttons('Hello World') # the single line
-sg.Window('Hello world', [[sg.Text('Hello World')]]).Read() # single line using a real window
+# single line using a real window
+sg.Window('Hello world', [[sg.Text('Hello World')]]).read()
# This is a "Traditional" PySimpleGUI window code. First make a layout, then a window, the read it
layout = [[sg.Text('Hello World')]]
window = sg.Window('Hello world', layout)
-event, values = window.Read()
+event, values = window.read()
+
+window.close()
diff --git a/DemoPrograms/Demo_HowDoI.py b/DemoPrograms/Demo_HowDoI.py
index 38d73bc0..679126c7 100644
--- a/DemoPrograms/Demo_HowDoI.py
+++ b/DemoPrograms/Demo_HowDoI.py
@@ -1,17 +1,15 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUIQt as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import subprocess
+'''
+ Famouns howdoi command in PSG
+'''
+
# Test this command in a dos window if you are having trouble.
-HOW_DO_I_COMMAND = 'python -m howdoi.howdoi'
+HOW_DO_I_COMMAND = 'python -m howdoi.howdoi'
-# if you want an icon on your taskbar for this gui, then change this line of code to point to the ICO file
-DEFAULT_ICON = 'E:\\TheRealMyDocs\\Icons\\QuestionMark.ico'
def HowDoI():
'''
@@ -22,46 +20,59 @@ def HowDoI():
:return: never returns
'''
# ------- Make a new Window ------- #
- sg.ChangeLookAndFeel('GreenTan') # give our form a spiffy set of colors
+ # give our form a spiffy set of colors
+ sg.change_look_and_feel('GreenTan')
- layout = [
- [sg.Text('Ask and your answer will appear here....', size=(40, 1))],
- [sg.Output(size=(120, 30), font=('Helvetica 10'))],
- [ sg.Spin(values=(1, 2, 3, 4), initial_value=1, size=(2, 1), key='Num Answers', font='Helvetica 15'),
- sg.Text('Num Answers',font='Helvetica 15'), sg.Checkbox('Display Full Text', key='full text', font='Helvetica 15'),
- sg.T('Command History', font='Helvetica 15'), sg.T('', size=(40,3), text_color=sg.BLUES[0], key='history')],
- [sg.Multiline(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
- sg.Button('SEND', button_color=(sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
- sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]
- ]
+ layout = [
+ [sg.Text('Ask and your answer will appear here....', size=(40, 1))],
+ [sg.Output(size=(120, 30), font=('Helvetica 10'))],
+ [sg.Spin(values=(1, 2, 3, 4), initial_value=1, size=(2, 1), key='Num Answers', font='Helvetica 15'),
+ sg.Text('Num Answers', font='Helvetica 15'), sg.CBox(
+ 'Display Full Text', key='full text', font='Helvetica 15'),
+ sg.Text('Command History', font='Helvetica 15'), sg.Text('', size=(40, 3), text_color=sg.BLUES[0], key='history')],
+ [sg.MLine(size=(85, 5), enter_submits=True, key='query', do_not_clear=False),
+ sg.Button('SEND', button_color=(
+ sg.YELLOWS[0], sg.BLUES[0]), bind_return_key=True),
+ sg.Button('EXIT', button_color=(sg.YELLOWS[0], sg.GREENS[0]))]
+ ]
- window = sg.Window('How Do I ??', default_element_size=(30, 2), icon=DEFAULT_ICON, font=('Helvetica',' 13'), default_button_element_size=(8,2), return_keyboard_events=True, no_titlebar=True, grab_anywhere=True)
- window.Layout(layout)
+ window = sg.Window('How Do I ??', layout, default_element_size=(30, 2),
+ font=('Helvetica', ' 13'),
+ default_button_element_size=(8, 2),
+ return_keyboard_events=True, no_titlebar=True,
+ grab_anywhere=True)
# ---===--- Loop taking in user input and using it to query HowDoI --- #
command_history = []
history_offset = 0
while True:
- event, values = window.Read()
+ event, values = window.read()
if event == 'SEND':
query = values['query'].rstrip()
# print(query)
- QueryHowDoI(query, values['Num Answers'], values['full text']) # send the string to HowDoI
+ # send the string to HowDoI
+ QueryHowDoI(query, values['Num Answers'], values['full text'])
command_history.append(query)
history_offset = len(command_history)-1
- window.FindElement('query').Update('') # manually clear input because keyboard events blocks clear
- window.FindElement('history').Update('\n'.join(command_history[-3:]))
+ # manually clear input because keyboard events blocks clear
+ window['query'].update('')
+ window['history'].update('\n'.join(command_history[-3:]))
elif event == None or event == 'EXIT': # if exit button or closed using X
break
- elif 'Up' in event and len(command_history): # scroll back in history
+ # scroll back in history
+ elif 'Up' in event and len(command_history):
command = command_history[history_offset]
- history_offset -= 1 * (history_offset > 0) # decrement is not zero
- window.FindElement('query').Update(command)
- elif 'Down' in event and len(command_history): # scroll forward in history
- history_offset += 1 * (history_offset < len(command_history)-1) # increment up to end of list
+ # decrement is not zero
+ history_offset -= 1 * (history_offset > 0)
+ window['query'].update(command)
+ # scroll forward in history
+ elif 'Down' in event and len(command_history):
+ # increment up to end of list
+ history_offset += 1 * (history_offset < len(command_history)-1)
command = command_history[history_offset]
- window.FindElement('query').Update(command)
+ window['query'].update(command)
elif 'Escape' in event: # clear currently line
- window.FindElement('query').Update('')
+ window['query'].update('')
+ window.close()
def QueryHowDoI(Query, num_answers, full_text):
@@ -71,15 +82,15 @@ def QueryHowDoI(Query, num_answers, full_text):
:param Query: text english question to ask the HowDoI web engine
:return: nothing
'''
- howdoi_command = HOW_DO_I_COMMAND
full_text_option = ' -a' if full_text else ''
- t = subprocess.Popen(howdoi_command + ' \"'+ Query + '\" -n ' + str(num_answers)+full_text_option, stdout=subprocess.PIPE)
+ t = subprocess.Popen(HOW_DO_I_COMMAND + ' \"' + Query + '\" -n ' +
+ str(num_answers)+full_text_option, stdout=subprocess.PIPE)
(output, err) = t.communicate()
print('{:^88}'.format(Query.rstrip()))
print('_'*60)
- print(output.decode("utf-8") )
+ print(output.decode("utf-8"))
exit_code = t.wait()
+
if __name__ == '__main__':
HowDoI()
-
diff --git a/DemoPrograms/Demo_Img_Viewer.py b/DemoPrograms/Demo_Img_Viewer.py
index c4fa408c..3143aa28 100644
--- a/DemoPrograms/Demo_Img_Viewer.py
+++ b/DemoPrograms/Demo_Img_Viewer.py
@@ -1,12 +1,9 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import os
from PIL import Image, ImageTk
import io
+
"""
Simple Image Browser based on PySimpleGUI
--------------------------------------------
@@ -19,13 +16,14 @@ There are some improvements compared to the PNG browser of the repository:
Dependecies
------------
-Python v3
+Python3
PIL
"""
+
# Get the folder containin:g the images from the user
-folder = sg.PopupGetFolder('Image folder to open', default_path='')
+folder = sg.popup_get_folder('Image folder to open', default_path='')
if not folder:
- sg.PopupCancel('Cancelling')
+ sg.popup_cancel('Cancelling')
raise SystemExit()
# PIL supported image types
@@ -35,60 +33,59 @@ img_types = (".png", ".jpg", "jpeg", ".tiff", ".bmp")
flist0 = os.listdir(folder)
# create sub list of image files (no sub folders, no wrong file types)
-fnames = [f for f in flist0 if os.path.isfile(os.path.join(folder,f)) and f.lower().endswith(img_types)]
+fnames = [f for f in flist0 if os.path.isfile(
+ os.path.join(folder, f)) and f.lower().endswith(img_types)]
num_files = len(fnames) # number of iamges found
if num_files == 0:
- sg.Popup('No files in folder')
+ sg.popup('No files in folder')
raise SystemExit()
del flist0 # no longer needed
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
# use PIL to read data of one image
-#------------------------------------------------------------------------------
-def get_img_data(f, maxsize = (1200, 850), first = False):
+# ------------------------------------------------------------------------------
+
+
+def get_img_data(f, maxsize=(1200, 850), first=False):
"""Generate image data using PIL
"""
img = Image.open(f)
img.thumbnail(maxsize)
if first: # tkinter is inactive the first time
bio = io.BytesIO()
- img.save(bio, format = "PNG")
+ img.save(bio, format="PNG")
del img
return bio.getvalue()
return ImageTk.PhotoImage(img)
-#------------------------------------------------------------------------------
+# ------------------------------------------------------------------------------
-# create the form that also returns keyboard events
-window = sg.Window('Image Browser', return_keyboard_events=True,
- location=(0, 0), use_default_focus=False)
-
# make these 2 elements outside the layout as we want to "update" them later
# initialize to the first file in the list
filename = os.path.join(folder, fnames[0]) # name of first file in list
-image_elem = sg.Image(data = get_img_data(filename, first = True))
+image_elem = sg.Image(data=get_img_data(filename, first=True))
filename_display_elem = sg.Text(filename, size=(80, 3))
-file_num_display_elem = sg.Text('File 1 of {}'.format(num_files), size=(15,1))
+file_num_display_elem = sg.Text('File 1 of {}'.format(num_files), size=(15, 1))
# define layout, show and read the form
col = [[filename_display_elem],
- [image_elem]]
+ [image_elem]]
-col_files = [[sg.Listbox(values = fnames, change_submits=True, size=(60, 30), key='listbox')],
- [sg.Button('Next', size=(8,2)), sg.Button('Prev',
- size=(8,2)), file_num_display_elem]]
+col_files = [[sg.Listbox(values=fnames, change_submits=True, size=(60, 30), key='listbox')],
+ [sg.Button('Next', size=(8, 2)), sg.Button('Prev', size=(8, 2)), file_num_display_elem]]
-layout = [[sg.Column(col_files), sg.Column(col)]]
+layout = [[sg.Col(col_files), sg.Col(col)]]
-window.Layout(layout) # Shows form on screen
+window = sg.Window('Image Browser', layout, return_keyboard_events=True,
+ location=(0, 0), use_default_focus=False)
# loop reading the user input and displaying image, filename
-i=0
+i = 0
while True:
# read the form
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
# perform button and keyboard operations
if event is None:
@@ -111,10 +108,10 @@ while True:
filename = os.path.join(folder, fnames[i])
# update window with new image
- image_elem.Update(data=get_img_data(filename))
+ image_elem.update(data=get_img_data(filename))
# update window with filename
- filename_display_elem.Update(filename)
+ filename_display_elem.update(filename)
# update page display
- file_num_display_elem.Update('File {} of {}'.format(i+1, num_files))
-
+ file_num_display_elem.update('File {} of {}'.format(i+1, num_files))
+window.close()
diff --git a/DemoPrograms/Demo_Input_Auto_Complete.py b/DemoPrograms/Demo_Input_Auto_Complete.py
index e963dad0..9f392e01 100644
--- a/DemoPrograms/Demo_Input_Auto_Complete.py
+++ b/DemoPrograms/Demo_Input_Auto_Complete.py
@@ -1,32 +1,30 @@
-import sys
+import PySimpleGUI as sg
import re
-QT = True
-if QT:
- import PySimpleGUIQt as sg
-else:
- import PySimpleGUI as sg
-def autocomplete_popup_show(text_list ):
- autocomplete_popup_layout = [[sg.Listbox(values=text_list,
- size=(100,20*len(text_list)) if QT else (15, len(text_list)),
- change_submits=True,
- bind_return_key=True,
- auto_size_text=True,
- key='_FLOATING_LISTBOX_', enable_events=True)]]
+'''
+ Exampel of Input element features.
+'''
+
+def autocomplete_popup_show(text_list):
+ autocomplete_popup_layout = [
+ [sg.Listbox(values=text_list,
+ size=(15, len(text_list)),
+ change_submits=True, bind_return_key=True,
+ key='-FLOATING-LISTBOX-', enable_events=True)
+ ]
+ ]
autocomplete_popup = sg.Window("Borderless Window",
+ autocomplete_popup_layout,
default_element_size=(12, 1),
- auto_size_text=False,
- auto_size_buttons=False,
- no_titlebar=True,
- grab_anywhere=True,
+ auto_size_text=False, keep_on_top=True,
+ no_titlebar=True, grab_anywhere=True,
return_keyboard_events=True,
- keep_on_top=True,
+ auto_size_buttons=False,
background_color='black',
- location=(1320,622),
- default_button_element_size=(12, 1))
+ default_button_element_size=(12, 1),
+ location=(1320, 622), finalize=True)
- window = autocomplete_popup.Layout(autocomplete_popup_layout).Finalize()
return window
@@ -34,57 +32,69 @@ def predict_text(input, lista):
pattern = re.compile('.*' + input + '.*')
return [w for w in lista if re.match(pattern, w)]
+
choices = ['ABC' + str(i) for i in range(30)] # dummy data
-layout = [ [sg.Text('Your typed chars appear here:')],
- [sg.In(key='_INPUT_', size=(10,1), do_not_clear=True)],
- [sg.Button('Show'), sg.Button('Exit')],]
+layout = [[sg.Text('Your typed chars appear here:')],
+ [sg.Input(key='-INPUT-', size=(10, 1))],
+ [sg.Button('Show'), sg.Button('Exit')], ]
-window = sg.Window('Window Title', return_keyboard_events=True).Layout(layout)
+window = sg.Window('Window Title', layout, return_keyboard_events=True)
sel_item = -1
skip_event = False
while True: # Event Loop
- event, values = window.Read(timeout=500)
- if event is None or event == 'Exit':
+ event, values = window.read(timeout=500)
+
+ if event in (None, 'Exit'):
break
+
if event != sg.TIMEOUT_KEY:
- # print(f'ev1 {event}')
- in_val = values['_INPUT_']
+ # print(f'event1 {event}')
+ in_val = values['-INPUT-']
prediction_list = predict_text(str(in_val), choices)
if prediction_list:
try:
- fwindow.Close()
- except: pass
+ fwindow.close()
+ except:
+ pass
fwindow = autocomplete_popup_show(prediction_list)
- list_elem = fwindow.Element('_FLOATING_LISTBOX_')
+ list_elem = fwindow['-FLOATING-LISTBOX-']
if event == '_COMBO_':
- sg.Popup('Chose', values['_COMBO_'])
+ sg.popup('Chose', values['_COMBO_'])
+
if event.startswith('Down') or event.startswith('special 16777237'):
- sel_item = sel_item + (sel_item0)
- list_elem.Update(set_to_index=sel_item)
+ sel_item = sel_item - (sel_item > 0)
+ list_elem.update(set_to_index=sel_item)
skip_event = True
+
if event == '\r' or event.startswith('special 16777220'):
- chosen = vals2['_FLOATING_LISTBOX_']
- window.Element('_INPUT_').Update(vals2['_FLOATING_LISTBOX_'][0], select=True)
- fwindow.Close()
+ chosen = values2['-FLOATING-LISTBOX-']
+ window['-INPUT-'].update(values2['-FLOATING-LISTBOX-']
+ [0], select=True)
+ fwindow.close()
sel_item = -1
+
if event.startswith('Escape') or event.startswith('special 16777216'):
- window.Element('_INPUT_').Update('')
+ window['-INPUT-'].update('')
try:
- ev2, vals2 = fwindow.Read(timeout=10)
- if ev2 == '_FLOATING_LISTBOX_' and skip_event and QT:
- skip_event = False
- elif ev2 != sg.TIMEOUT_KEY and ev2 is not None:
- # print(f'ev2 {ev2}')
- fwindow.Close()
- window.Element('_INPUT_').Update(vals2['_FLOATING_LISTBOX_'][0], select=True)
+ event2, values2 = fwindow.read(timeout=10)
+ # if event2 == '-FLOATING-LISTBOX-' and skip_event and QT:
+ # skip_event = False
+ if event2 != sg.TIMEOUT_KEY and event2 is not None:
+ # print(f'event2 {event2}')
+ fwindow.close()
+ window['-INPUT-'].update(values2['-FLOATING-LISTBOX-']
+ [0], select=True)
sel_item = -1
fwindow = None
- except: pass
-window.Close()
+ except:
+ pass
+
+window.close()
diff --git a/DemoPrograms/Demo_Input_Validation.py b/DemoPrograms/Demo_Input_Validation.py
index 0a963f0b..4376ed69 100644
--- a/DemoPrograms/Demo_Input_Validation.py
+++ b/DemoPrograms/Demo_Input_Validation.py
@@ -1,8 +1,4 @@
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
"""
Simple field validation
@@ -11,15 +7,18 @@ else:
"""
layout = [[sg.Text('Enter digits:')],
- [sg.Input(do_not_clear=True, enable_events=True, key='_INPUT_')],
- [sg.Button('Ok', key='_OK_'),sg.Button('Exit')]]
+ [sg.Input('', enable_events=True, key='-INPUT-')],
+ [sg.Button('Ok', key='-OK-'), sg.Button('Exit')]]
-window = sg.Window('Window Title').Layout(layout)
+window = sg.Window('Window Title', layout)
while True: # Event Loop
- event, values = window.Read()
- if event in (None, 'Exit'):
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
- if len(values['_INPUT_']) and values['_INPUT_'][-1] not in ('0123456789'): # if last char entered not a digit
- window.Element('_INPUT_').Update(values['_INPUT_'][:-1]) # delete last char from input
-window.Close()
+ # if last char entered not a digit
+ if len(values['-INPUT-']) and values['-INPUT-'][-1] not in ('0123456789'):
+ # delete last char from input
+ window['-INPUT-'].update(values['-INPUT-'][:-1])
+
+window.close()
diff --git a/DemoPrograms/Demo_Invisible_Elements.py b/DemoPrograms/Demo_Invisible_Elements.py
index df98d4bd..4d5c5668 100644
--- a/DemoPrograms/Demo_Invisible_Elements.py
+++ b/DemoPrograms/Demo_Invisible_Elements.py
@@ -9,19 +9,19 @@ import PySimpleGUI as sg
"""
-layout = [[sg.Column([[sg.Text('My Window')],[sg.Input(key='_IN_'), sg.B('My button', key='_OUT_')]], key='_COL_')],
- [sg.Button('Invisible'), sg.B('Visible'), sg.Button('Exit')]]
+layout = [[sg.Col([[sg.Text('My Window')], [sg.Input(key='-IN-'), sg.Button('My button', key='-OUT-')]], key='-COL-')],
+ [sg.Button('Invisible'), sg.Button('Visible'), sg.Button('Exit')]]
window = sg.Window('Window Title', layout)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
if event in (None, 'Exit'):
break
if event == 'Invisible':
- window.Elem('_COL_').Update(visible=False)
+ window['-COL-'].update(visible=False)
elif event == 'Visible':
- window.Elem('_COL_').Update(visible=True)
+ window['-COL-'].update(visible=True)
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Keyboard.py b/DemoPrograms/Demo_Keyboard.py
index 6fd21931..f1acb3be 100644
--- a/DemoPrograms/Demo_Keyboard.py
+++ b/DemoPrograms/Demo_Keyboard.py
@@ -1,29 +1,28 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+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"
layout = [[sg.Text("Press a key or scroll mouse")],
- [sg.Text("", size=(18,1), key='text')],
+ [sg.Text("", size=(18, 1), key='text')],
[sg.Button("OK", key='OK')]]
-window = sg.Window("Keyboard Test", return_keyboard_events=True, use_default_focus=False).Layout(layout)
+window = sg.Window("Keyboard Test", layout,
+ return_keyboard_events=True, use_default_focus=False)
# ---===--- Loop taking in user input --- #
while True:
- event, values = window.Read()
- text_elem = window.FindElement('text')
+ event, values = window.read()
+ text_elem = window['text']
if event in ("OK", None):
print(event, "exiting")
break
if len(event) == 1:
- text_elem.Update(value='%s - %s' % (event, ord(event)))
+ text_elem.update(value='%s - %s' % (event, ord(event)))
if event is not None:
- text_elem.Update(event)
+ text_elem.update(event)
+window.close()
diff --git a/DemoPrograms/Demo_Keyboard_ENTER_Presses_Button.py b/DemoPrograms/Demo_Keyboard_ENTER_Presses_Button.py
index de2c2a6b..afbc91b6 100644
--- a/DemoPrograms/Demo_Keyboard_ENTER_Presses_Button.py
+++ b/DemoPrograms/Demo_Keyboard_ENTER_Presses_Button.py
@@ -1,6 +1,4 @@
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
-
"""
tkinter and Qt do not "activate" buttons by pressing the ENTER key with the button highlighted / in focus
This demo will enable the application to click on a button if the button has focus (is highlighted) and the
@@ -18,29 +16,32 @@ import PySimpleGUI as sg
"""
-QT_ENTER_KEY1 = 'special 16777220'
-QT_ENTER_KEY2 = 'special 16777221'
+QT_ENTER_KEY1 = 'special 16777220'
+QT_ENTER_KEY2 = 'special 16777221'
-layout = [ [sg.T('Test of Enter Key use')],
- [sg.In(key='_IN_')],
- [sg.Button('Button 1', key='_1_')],
- [sg.Button('Button 2', key='_2_')],
- [sg.Button('Button 3', key='_3_')], ]
+layout = [[sg.Text('Test of Enter Key use')],
+ [sg.Input(key='-IN-')],
+ [sg.Button('Button 1', key='-1-')],
+ [sg.Button('Button 2', key='-2-')],
+ [sg.Button('Button 3', key='-3-')], ]
window = sg.Window('My new window', layout,
return_keyboard_events=True)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event in ('\r', QT_ENTER_KEY1, QT_ENTER_KEY2): # Check for ENTER key
- elem = window.FindElementWithFocus() # go find element with Focus
+ # go find element with Focus
+ elem = window.find_element_with_focus()
if elem is not None and elem.Type == sg.ELEM_TYPE_BUTTON: # if it's a button element, click it
elem.Click()
# check for buttons that have been clicked
- elif event == '_1_':
+ elif event == '-1-':
print('Button 1 clicked')
- elif event == '_2_':
+ elif event == '-2-':
print('Button 2 clicked')
- elif event == '_3_':
- print('Button 3 clicked')
\ No newline at end of file
+ elif event == '-3-':
+ print('Button 3 clicked')
+
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Keyboard_Realtime.py b/DemoPrograms/Demo_Keyboard_Realtime.py
index cc50054e..bc362784 100644
--- a/DemoPrograms/Demo_Keyboard_Realtime.py
+++ b/DemoPrograms/Demo_Keyboard_Realtime.py
@@ -1,17 +1,14 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
layout = [[sg.Text("Hold down a key")],
[sg.Button("OK")]]
-window = sg.Window("Realtime Keyboard Test", return_keyboard_events=True, use_default_focus=False).Layout(layout)
+window = sg.Window("Realtime Keyboard Test", layout, return_keyboard_events=True,
+ use_default_focus=False)
while True:
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event == "OK":
print(event, values, "exiting")
@@ -23,3 +20,5 @@ while True:
print(event)
elif event is None:
break
+
+window.close()
diff --git a/DemoPrograms/Demo_Keypad.py b/DemoPrograms/Demo_Keypad.py
index 31194fbc..a48a7848 100644
--- a/DemoPrograms/Demo_Keypad.py
+++ b/DemoPrograms/Demo_Keypad.py
@@ -1,35 +1,34 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
# Demonstrates a number of PySimpleGUI features including:
# Default element size
# auto_size_buttons
# Button
# Dictionary return values
-# Update of elements in form (Text, Input)
-# do_not_clear of Input elements
-
+# update of elements in form (Text, Input)
layout = [[sg.Text('Enter Your Passcode')],
- [sg.Input(size=(10, 1), do_not_clear=True, key='input')],
+ [sg.Input('', size=(10, 1), key='input')],
[sg.Button('1'), sg.Button('2'), sg.Button('3')],
[sg.Button('4'), sg.Button('5'), sg.Button('6')],
[sg.Button('7'), sg.Button('8'), sg.Button('9')],
[sg.Button('Submit'), sg.Button('0'), sg.Button('Clear')],
- [sg.Text('', size=(15, 1), font=('Helvetica', 18), text_color='red', key='out')],
+ [sg.Text('', size=(15, 1), font=('Helvetica', 18),
+ text_color='red', key='out')],
]
-window = sg.Window('Keypad', default_button_element_size=(5, 2), auto_size_buttons=False, grab_anywhere=False).Layout(layout)
+window = sg.Window('Keypad', layout,
+ default_button_element_size=(5, 2),
+ auto_size_buttons=False,
+ grab_anywhere=False)
# Loop forever reading the form's values, updating the Input field
keys_entered = ''
while True:
- event, values = window.Read() # read the form
+ event, values = window.read() # read the form
if event is None: # if the X button clicked, just exit
break
if event == 'Clear': # clear keys if clear button
@@ -39,6 +38,8 @@ while True:
keys_entered += event # add the new digit
elif event == 'Submit':
keys_entered = values['input']
- window.FindElement('out').Update(keys_entered) # output the final string
+ window['out'].update(keys_entered) # output the final string
- window.FindElement('input').Update(keys_entered) # change the form to reflect current key string
\ No newline at end of file
+ # change the form to reflect current key string
+ window['input'].update(keys_entered)
+window.close()
diff --git a/DemoPrograms/Demo_LED_Clock_Weather.py b/DemoPrograms/Demo_LED_Clock_Weather.py
index cde7115e..9b9ff698 100644
--- a/DemoPrograms/Demo_LED_Clock_Weather.py
+++ b/DemoPrograms/Demo_LED_Clock_Weather.py
@@ -1,22 +1,28 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
- sg.PopupError('This program uses Base64 images which are not supported in Python 2.7')
- sys.exit()
+import PySimpleGUI as sg
import datetime
import calendar
import forecastio
-##### CHANGE these settings to match your location... check Google Maps #####
-MY_LOCATION_LAT = 35.000000
-MY_LOCATION_LON = -79.000000
-##### You need a free dark-sky key. You get 1000 calls a month for free #####
-DARKSKY_KEY = "INSERT YOUR DARKSKY KEY HERE!" # *** INSERT YOUR DARKSKY KEY HERE **
+'''
+ Example of a weather App, using:
+ - DARKSKY
+ - google maps coordinates
+'''
-NUM_COLS = 5 # Changes number of days in forecast
+
+
+##### CHANGE these settings to match your location... check Google Maps #####
+MY_LOCATION_LAT = 35.0
+MY_LOCATION_LON = -79.0
+
+##### You need a free dark-sky key. You get 1000 calls a month for free #####
+# *** INSERT YOUR DARKSKY KEY HERE **
+DARKSKY_KEY = "INSERT YOUR DARKSKY KEY HERE!"
+
+
+# Changes number of days in forecast
+NUM_COLS = 5
class GUI():
@@ -26,63 +32,66 @@ class GUI():
self.lng = MY_LOCATION_LON
self.blink_count = 0
- sg.SetOptions(border_width=0, text_color='white', background_color='black', text_element_background_color='black')
+ sg.set_options(border_width=0, text_color='white',
+ background_color='black', text_element_background_color='black')
# Create clock layout
clock = [
- [sg.T('', pad=((120,0),0)),
- sg.Image(data=ledblank[22:], key='_hour1_'),
- sg.Image(data=ledblank[22:], key='_hour2_'),
- sg.Image(data=ledblank[22:], key='_colon_'),
- sg.Image(data=ledblank[22:], key='_min1_'),
- sg.Image(data=ledblank[22:], key='_min2_')], ]
+ [sg.Text('', pad=((120, 0), 0)),
+ sg.Image(data=ledblank[22:], key='-hour1-'),
+ sg.Image(data=ledblank[22:], key='-hour2-'),
+ sg.Image(data=ledblank[22:], key='-colon-'),
+ sg.Image(data=ledblank[22:], key='-min1-'),
+ sg.Image(data=ledblank[22:], key='-min2-')], ]
# Create the weather columns layout
weather_cols = []
for i in range(NUM_COLS):
weather_cols.append(
- [[sg.T('', size=(4, 1), font='Any 20', justification='center', key='_DAY_' + str(i)), ],
- [sg.Image(data=w1[22:], background_color='black', key='_icon_'+str(i), pad=((4, 0), 3)), ],
- [sg.T('--', size=(3, 1), justification='center', font='Any 20', key='_high_' + str(i), pad=((10, 0), 3))],
- [sg.T('--', size=(3, 1), justification='center', font='Any 20', key='_low_' + str(i), pad=((10, 0), 3))]])
+ [[sg.Text('', size=(4, 1), font='Any 20',
+ justification='center', key='-DAY-' + str(i)), ],
+ [sg.Image(data=w1[22:], background_color='black',
+ key='-icon-'+str(i), pad=((4, 0), 3)), ],
+ [sg.Text('--', size=(3, 1), justification='center',
+ font='Any 20', key='-high-' + str(i), pad=((10, 0), 3))],
+ [sg.Text('--', size=(3, 1), justification='center',
+ font='Any 20', key='-low-' + str(i), pad=((10, 0), 3))]])
# Create the overall layout
- layout = [[sg.Column(clock, background_color='black')],
- [sg.Column(weather_cols[x], background_color='black') for x in range(NUM_COLS)],
+ layout = [[sg.Col(clock, background_color='black')],
+ [sg.Col(weather_cols[x], background_color='black')
+ for x in range(NUM_COLS)],
[sg.Button('Exit', button_color=('black', 'black'),
- image_data=orangeround[22:], tooltip='close window', pad=((450,0),(10,0)))]]
+ image_data=orangeround[22:],
+ tooltip='close window', pad=((450, 0), (10, 0)))]]
- # Create the window
- self.window = sg.Window('My new window',
- background_color='black',
- grab_anywhere=True,
- use_default_focus=False,
- no_titlebar=True,
- alpha_channel=.8, # set an alpha channel if want transparent
- ).Layout(layout).Finalize()
-
- self.colon_elem = self.window.FindElement('_colon_')
- self.hour1 = self.window.FindElement('_hour1_')
- self.hour2 = self.window.FindElement('_hour2_')
- self.min1 = self.window.FindElement('_min1_')
- self.min2 = self.window.FindElement('_min2_')
+ # Create the window
+ self.window = sg.Window('My new window', layout, finalize=True,
+ background_color='black', grab_anywhere=True,
+ use_default_focus=False, no_titlebar=True,
+ alpha_channel=.8)
+ self.colon_elem = self.window['-colon-']
+ self.hour1 = self.window['-hour1-']
+ self.hour2 = self.window['-hour2-']
+ self.min1 = self.window['-min1-']
+ self.min2 = self.window['-min2-']
def update_clock(self):
# update the clock
now = datetime.datetime.now()
real_hour = now.hour - 12 if now.hour > 12 else now.hour
hour1_digit = led_digits[real_hour // 10]
- self.hour1.Update(data=hour1_digit[22:])
- self.hour2.Update(data=led_digits[real_hour % 10][22:])
- self.min2.Update(data=led_digits[int(now.minute) % 10][22:])
- self.min1.Update(data=led_digits[int(now.minute) // 10][22:])
+ self.hour1.update(data=hour1_digit[22:])
+ self.hour2.update(data=led_digits[real_hour % 10][22:])
+ self.min2.update(data=led_digits[int(now.minute) % 10][22:])
+ self.min1.update(data=led_digits[int(now.minute) // 10][22:])
# Blink the :
if self.blink_count % 2:
- self.colon_elem.Update(data=ledcolon[22:])
+ self.colon_elem.update(data=ledcolon[22:])
else:
- self.colon_elem.Update(data=ledblank[22:])
+ self.colon_elem.update(data=ledblank[22:])
self.blink_count += 1
def update_weather(self):
@@ -99,14 +108,14 @@ class GUI():
min_temps.append(int(daily_data.d['temperatureMin']))
for i in range(NUM_COLS):
- day_element = self.window.FindElement('_DAY_' + str(i))
- max_element = self.window.FindElement('_high_' + str(i))
- min_element = self.window.FindElement('_low_' + str(i))
- icon_element = self.window.FindElement('_icon_' + str(i))
- day_element.Update(calendar.day_abbr[(today_weekday + i) % 7])
- max_element.Update(max_temps[i])
- min_element.Update(min_temps[i])
- icon_element.Update(data=weather_icon_dict[daily_icons[i]][22:])
+ day_element = self.window['-DAY-' + str(i)]
+ max_element = self.window['-high-' + str(i)]
+ min_element = self.window['-low-' + str(i)]
+ icon_element = self.window['-icon-' + str(i)]
+ day_element.update(calendar.day_abbr[(today_weekday + i) % 7])
+ max_element.update(max_temps[i])
+ min_element.update(min_temps[i])
+ icon_element.update(data=weather_icon_dict[daily_icons[i]][22:])
def led_clock():
@@ -118,7 +127,7 @@ def led_clock():
last_update_time = 0
while True:
# Wake up once a second to update the clock and weather
- event, values = gui.window.Read(timeout=1000)
+ event, values = gui.window.read(timeout=1000)
if event in (None, 'Exit'):
break
# update clock
@@ -132,45 +141,25 @@ def led_clock():
led0 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEb0lEQVRoge2Zz4scRRTHP/WqunsDSQjiBvFHSBC8eRJRFAXFgCIYRVBPgrkoiBgU9U/w4iEgHjwEBT2IKHiIiBfBg4oB8SJ4CgHjIXpSE5Ls9HR9PUz1ZGa6dnZ2dmdnlXzh0TPV1a+/9erVq/eqHSB2GWzZBHK4TmpW7EpSIdfokuwE4jrv12QDk40LhNElNmapluFNZtws8bdEsQCCrb69zvEL0FP3DQLk0nUf6P6y1A8hSKAeKG6jNEmnQG8Xhe5cWZE5p5ZHy3mM1GHQz2Z6YGVF33ovgeqkZDukn65vVpVeDUFfr6wIM9k0UgdBa6DfvNc9VaWvErFeGuW80h8Z3Imi0PGqkkCny1KYyY+Q6qw+A64AtzUNn0k8GwI9M56oayLzxxABzjleCgFvxqleb3DDddd5NiT4dL01Rr6oa54pS9aKgqMSl+YgFoG9wFtm7JE42evRkyjJh54sqRZ9YFXi07rmLjMuJEWbXY0B+Ac4Dpysa2qmD6xDavSFnsEoVyVWm4bzEg35gLcRKYA7wuDXRsE561M28R8GVptFYQ5t/yYTj6aSagPnJKlJDNfsJtD2n3Uw3fdnVsNOo0PKwdKJ/YcstSDM71ObeHhR6FpqN04fsHS/2lGfmhU76lNbcvRF5eez7pkbOnpcr+McaPe03KCV6TdsvApccY4KqIESOGvG7yOb6bzT+6MEZvgYqRlkIRdTojypc5iGtnnysbLUFTMJ9Kv3OhLCIGV1Tm5O8c4JMz1fluon3WdC0KrZWDre2kc5Yk+GoJ+KQoeKQjg3mdjPJQGE9zpeFPq+KHRDCDlC6hSjMJjT6BzmPcSIYqSZc8omUQDRDOccagZaJ3V3/NcYJHT3ec8nzrHfORqzbXP02jke854PzSicoyHvo52pu9d7/ZlKq++81w1FIcw6Zt6MeBBmejQEXUw+ddp7VdN8qm280XtdSISupjrtTAja5/3At+Zw8uCccE5Hi0I9M8UR3aeqSjg3VvcNZ6U14T7nOJCWf8lgvu9uGm4fqXO1SWmfe9iMIkb6XItFtwA4N+bYHVfRxBy3NeCsSf809JOOUf2tk4+2zey/25HSWEZHTuvST/Jy9l86qRyWTuq6pWaFMqt6+aQybUsnlcPSSf3/LLUdxUTOqXN6px4vAsPD17bjVg7NQtpmRvfX4YY9K6kmdTjrPefSg3FiR58FbUV0JkZwDpOm6lj3g5HSzfPe82JR8EFdc8iMNTZvqQjsBz4GXi5L3kunw7C+W4xlnYdDGFYyfzinB8tS36Skr2H614Rp0j77elXpRDrYF+jzVJhkk7xRhgG45BzPlSWvNQ0PNQ29dK+ZU8Qg939nbY3LMfJGOinuzxLRA3DZjKfKkhdi5FjTcJVryd5WUQPv1zXnvOfdEDiwTr+xHP1ICHp6zx59lKasv4UpmyrO6ZWq0uNV1Skcxuq+9s9BMx6Jkb+20UKjiAzy/55zfAmdkJAtRrONC8KGX0ZHsQgL5ZCrvHfSKDNjV27I/wIpihClbBNQxAAAAABJRU5ErkJggg=='
-
led1 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAACD0lEQVRoge2Yv27UQBCHv9kkRCAhXgAkSh6AmoKCFFBENLwBFQ0PQIPES0BJky4NDR0lJT28A4ICQc7nH4V3FccZ+9zcrAv/pJVP3jntp525+XMGiIUp1QbwtELN1Qo1V1WhLK+hqkEVIC8fVYFKdDB3gLvW3ZUN9qsAmRlnKfEoQ6WBTShQCxybcW7Gk7blt2N3GAVkGegmcA6ctC0AB45tyE2VgL5txifgROLfhH0YFMBH4DGwYdpFIVBtft5XlwA8l/UVGujtbhMgGMrL3p7W2rcrlor2DtV32dHM7+wdSiOfp7TG1FytUHO1Qnllpko7bL3n3N4pJE+VFvhLftf09n/Kz16KXO/NJJBAr8wEKF23i4GxvDDTh5T0OqUxII2NXntRiavGDJM4pOtChwqfZhrgDfDCjA3jnUOY+zDT2xxTf0Gnea9KTJVD36UkgS5ALWgDeugEe+jg8DTPegnY0k009/JetbF92wMoEFu73rmHB/oVOUCuXbS2zrvqUF6RrgIlLmPKy9zrTXlaJNQi3bc8KGmBUCwgphYzOPRVeqdd/1OFQJVDvuVa1y8tcgaHEKhy7EvgsxlHXE401dxXysqFxHO6UevGhH1YTCkf9kfiFPiaXdk4tuG/vgT8Ap4B31PilmMXOmIVHdAF+wMzGuCHdAWkClQ5mJHDq2X0/q0MVe2mplS99nlaJNR/DW4BrSasyZkAAAAASUVORK5CYII='
-
led2 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADmElEQVRoge2Zz4scRRiGn7d6NlHJQaKoKHgwxGgiIpiTegrI4kEUkdwEQcRF8pdI0INIrorkoqB48SB4EhQUvSoIIkjixaiExdnNTtfrYaqyPZmamZ6e7GQC88JHM71dVU999ev7tgSYFVO41QAlraHaaiWheqWXSrYMxQnt39LVVwLolT64T+Jem22gOgAQM5w3hyV+8bhPisN3GPhU4qTNf5M+WgBIwCFgS+KqxKU4Oohj3ssvHpL4UuLJGKm5OR7LDQl4NQQeBp4A3oiRCqjT38dWX3btJZszwA8SFbCXCnW1AfteerGqOCLxboz00/A1F1ZxS4gMPXMlRjaB7yQ2UsGqgwWGU0DACxKPxMiHdT0GkzVxutSpsn9sNiU+riqetulP6skM9YA303Q4b7MD3DEvFAw9JmDb5pU07l3UA/rAOeC8zd6MhmcurDwPSJV1UZ7gpwrzpxNUrvQ6XAflcqXdu6S5tqCuW39zK2ijlTyQ11BttYZqq6VCSe3W39pTbbWSUK139EWSidzz0jFTOiVaQYVUuO3ZdaNyuRxlNM/RuUOXDBSBu4GjDOOseT3WA/4CvgbeSnA5fPl3QhlPspCexyX/KjmC++DdOa0PNnhL8supHoM/l3xIMmCNtl0GqtLz8RD8u2SD61RZF8sgWyH4tRB8MQRXZaAyVAaS5O8T0G6quKvVqQ6DXwrBgDfKQONQecjOSH5P8mnJlxue6go1SEDvSw6pnQlAo1AZ6JkQfCVV8kEIfqyqfDkEW3KU7DmtTs93koemwBjw9dWXV9mDEl/YHAV2gbdj5M4QOC1xXOoUpwu4BvwUY+uVO+KlU8kb2e0Gb4MfyPNsRi+n2YwhG/dUVt7omvl8c2+qGu/nVT37E6CweZaOkyZIvQBUW63kgbyGaqvbG+qgJ3dTt7enlqk1VFu1guoam3dVq8Qh/wMW9pOILhLtzr+pUANgA/hR4k8Am7gAFOyHSJ2g6gbQuRD4LEbuSfHUvNlMBI4AH0lciJEeww5P00g8dVLyIIW/P4NPhOBvQlg4ccj2eoo+e9NjqlGoEyl0/U3yoyH4q0biMFjA9nKnJJ9NWcwUsFGopyT/EYKPVZU/SUA7qcJFbS8BXgNvJo+FaVA5TD0m+bmq8sU0ZDkLuVmWQ+wdyc9PABu5xco/7geeZZhSH8R9Xw3cBfwt8a09dpVWvFrjxpdLVvG6Vizn/DEreodc0koeyP8DbP/uIBO2okgAAAAASUVORK5CYII='
-
led3 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADoUlEQVRoge2ZzascRRTFf6d7TCLBaALiImAIRoObJzqu3JiVCzH/QEACLlyI4sdCiArqwp2gG/8BcavgB7gRsgquRRBc+lwEBTExEMzLdB8XVTWv01090zPznPcgc6FopvtO1al7q+qee0uAOWBS7DeAnGxADZUDCWrU92EdaE1+l6nn/dokB2CUUzguccLmJlD+D0Aq4BBQS/zurk267pM4AnwFbAH/ZJVWB1QB54GbwF/cabGO9UqJSuJUXfMDcAao2Zs15jjgBHgWeA44DFwiTHwS9TpjVTalzW8S54Cfo9JOnN2y7XYEdAN4WuIJ4H2CJ/omMG2KzxKM5OOSr4ANruJz0VbH5zXwGcmvN769G8cbNTB0lksycQWUNn9LvAB8ATwK/Jsz7wxx1K+AF4ELwIexnyNxrLmWarcCXEgzdea1ZIV3onV2YjP4vYyl5k66js9VFnraSY8r2EX0WwgG7va+k3eoJACTmVq7stbYV2YOypwcyIB894Jqho8hcvdaalHZgBoqG1BDpRNmJOHWyZuYw9At3ZaSwBLqzLfcGT8o9pXxz7lOh0g1Y7BcDpAF1bRWmuWx2JYBNgL+BL6XuGBP+RrA1dz4ZCyoRDFsaomzwNc2pwm0eBE3JtePgOcljgLfxAl/ArzVAyJLzErJFIXH4KstWrssHTb4PPhl8Gct+t1q3ZdJ8RD4p9jZrdj5sm3SaFux/8M9oLJHggmp1k5R8ApwjZCrrUr0SuAl4KTEm8At+s+krqUkSwoulPwM+PoKLkz/uQg+1+Dnr0XuX3Q9lV/o090nUQEPAw8SKO2i59UI+APYsvmOsIMr4B7gIvA5u7s86fcCgpicxpx/e0EwTVAT4CMJ7OngAKfTuC39uVKz2ome1k1KHJr93M7oD65dLHuaN/+bW9RD3+27bEANlQ2oobJWUEPD1MZSQ2XjvlxUyL0bFGYSF1qWT5WEuJcb7Ebm3VxLNYPpsuXqFIh/zBTNFk6xSqCSeMTmYylcXbA4W6iB+4APbN4APiVUh2cN3mGdsFtHPwn+lb2po++An5J8SZp+e5UhdXQ71L0l7ge+BR4jpFYFy1MYE5jmZZuxxAPA2+SLsx1QhUQtcW9d8yXwZAS0F5dGE0JCe8VmDJwCTsyYxG5qVRQ+Kvnyii6b58pt8FjyWe5M62gnDunHQ8AYuM7e3/eJQIGPAdvAL3Qz5H29GU27uA0gX+BgfXfIuY2z73fIOTmQLOE/hw5ngBNVOEQAAAAASUVORK5CYII='
-
led4 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADdElEQVRoge2Zu4tkRRSHv3OqukdFxd0FUQRdxESMFgQDBWFBQ/P1LzBREHSNzAwNRPwbVkMDQUMVdIKNfIAYimAgsivjY3fsufUzmKrZftSdvn3t7tvC/OBw+z6q6runHufUbQPEjsmHBqjpDKqrzqC6qgq1TdJaW9X21XZjzXIgtVyf0f3uXAyBBIQtAF0ajYhmC/cFyPLxwRD0/Xisp0MQoJivr9MiCDO9HqO+Go+F2Un72WahHghBN0PQgbueyWCjNQKVul4JQTLT5zEKM/lpUOdD0K8xSqDfzfScmQCFNQCF7KHX3CWQQF+MRsuhLoSgGxlKoD/MdDlGAfMFVzLPQFdz3Ye5/i+7eOpCCLoRggSa5IIH7nrcvTdY8fKV3GWTqbprUAuzz7gzJQPHM+S+lDif0sn9VVXKPJL9ryX1VKFq5+vIb8qatOzFqlC1Qn081LeOzlDbVDWabAqqa71xoaA2mB1XwklN/5/UZWgtQm2y+zqq7qkNgfVeEnZBOwm1sCQox6Y2TcfGrirP1+qtXVuAcmb7vqTF0zGwllefpvJ8Sa81BeqVtWsB6hbwpxn3SkyAMfCDGb+Z8URKOKsH5wAcANeBf8wYSxwCe8DNlkk1m4iBLrvrwEwCfWemSzHq63w+ATUrWsmd3nPXCyHo73z+jbseypltNUefB3vRXfsx6vnRSPs56WtyZX2slH0/Rr0co/Zj1MM5cZwDWoQqYA7CXZ9loNug9B/tVgZ7KwThrj3quX91IiXyQEyJqynxszt7QFN7g47WAHcB10LgW+AaMDKjob6onp7sg550108xSu5q3KUVrZT5MEY9Ox7rl7yb+dRdd1e60Eoftinkt3w0BJ5yZ9LyZqfJgb8Al/g4Jc6lxO3suY9i5ErT4NLMUtNpe1QZjJ2t7LLfyB46nBr4+3k3Y7PPL1cZY31jUgCOuLOITq91TT5Od1knqALWVzZ3XKbBA3KnT0Hbliqxb3ioyrXBoXaz+yrXzqBq2kmoszFVU80rg0PVdjXlt1We26hKwz+agdlMQB7MU01u6JOUeNXsJEcbFAry/lHig5R40529HPMGH1MNx7nSuxJvx+Os6aiy7+ucT61LDRAl3jk64mIIPNbyda93mtvXSup7D+il/DfI3P21fCLvpc7/921TagEY1FNtGjz21bSTUP8C1hqqTm9IJgsAAAAASUVORK5CYII='
-
led5 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAADyUlEQVRoge2Zv4tcVRTHP99zZ6NEggg2iYgWpghKEGNlYaGNYifBgAGbNFaCkCJ/haAiiGDAgOkFKxtttrGKFlaCbJFlwVVEo+5m5t2vxdy3+3bmzcybmXVnA/uFw5v35r37Pu/cX+fcK8AcM8WqAdp0AtVVxxKqN+mPo6I14z1NLdfaLx6hWj0VwLMS/9iYIeRhawCcAbYkfrXHHFF70CrHsxG+npIN3gX3wYNDtB2wwXcifDalA+8uv/cBE1ABNyXuSPwlcbOqyMVbh+GxqrznB4nXgC2bkMje99OB9ly/dAB8mDODnHlHIoBcCsxLWL8ArUfwisSWTYIDQGNQo3C3bE5LvJUSSSKV/2IBA1gDvpV4Hfg9572aGa2BiQ0dYBf4NGfeS4nLEXxs829LIbOUgdPA9zZXgb9zJgoQjPf0ieNUDdcHPqoqrqXEuVL4YE6oWgPApaflKfdNhWr2gvMSVBX3l4Cqa2DWGNgKJWnYaWk0/sb5or2wbfTuDNWm5lf+36P91N63Kh3LKOHBgTqpvhaNQ0lDW6FOqq+rjqWnOo/o9QTaDEXm1ULTTP2AGscaJjXgps3w0xQjZXaCEoDNn+V8wDAwQ2IdeBx4jP04qKvM8KO2Jf6w9yLZWc8cCN7PSP4mwgbfB7+Rkm+U8zqRmMd2S6Lwk+SnSqIQjfe22MELNdjDEf46wlcjfEOywVUpfBGrn/05whckA+51harB1sBIfrukWjvgvKTVHluXjGRN9lg7bUhOkk9F+PMC1i9fvIj1C9AG+MUIf5KSX4qYVJXtUCpfApgIfxZhS64ke07L5XhX8nOSb5fmsBnhF1rA2tcSJDwSDq9JXIqg1/ZAB/WAX2w+sHnTZhd4CNiUuAj8NpK6T+sFe20slca5iNVeOM8wbW+2sz74mZH7Oo3oZjiupFk3TlA9Lpn9tN3s18Jced+o5h00azWrZHSKaptnH5woYdU6geqqlUNVjK9NrByqLT5bOVSbTqC66sihDn19atF1qXru63Usp5On6kXTRZeq6zlzA/hRQkyfR2d6KgGVxPUIruTMva5f0pCBU8AmcC2CL3PmQtlimfZMq/VKnP5+yWQyyycO30m+mJI3SvR5D/zkSDzVDiXtAb1bgOp0aZk9mTpx+CrCz0d4G7wr+VwJIKdCpQJ0OSVn6VCARjeLvpB8KcJ3I/z0LKgoQFfqwH/JamuzQTneivDLvZ6fKFB1ojJxv/FV4BFgh8XD4GmqgEcZ7tVsjyQNq94E3Ru3mhBT95CPYp2qTiiaWrmn2nQsJ+T/AHYfURS2h8j3AAAAAElFTkSuQmCC'
-
led6 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEBUlEQVRoge2ZP4gdVRTGf9+Z3c1qZJFUChJWLBQigmLQiJYhIkQLwcLWwkYrGy1s0sRC0gQUIljYWCRaaKONJoKNIoJKwEgaBcEuJuwmu/vefBZz7+68P/N23p91X7EfHGbmzsy935x777nfuSPAzBlivwkMwwGptphLUgvDCv8vpmVDueibfQMF+4BtT2UyhyUeBP61Kfao0RK4B7gO3Gl4xtkOgY8WhT+RbPAaeGPGtg42+EKEkVxvv2ZY6WJJ8jcRfjrCFxKxTqpkFpbr+qAojGTV2m4kheRrEb4l+VhR+FyEnb6wM6VtJkLnJCM5mgn1kpLk3xKRG5KPR/i95LHuFB4q0/Fs6rJiNCEPhIQ8uFdsrticjGBD4nXgJuOHizyoz0ucLUsWbLrZHQ1Q8hQGQuKqxMNlSYdqaq5LHAeuAodoji27YQsIu9X7g8HT1TcE0AHutllNpDpAd0JSxRjvDpBS7Tx3Vad2T0yGcTzcQ2q3Rrenxh5jLhfkHlKyJ+6eWWLQU97v5XgIqbnz1DSza5aY/4E+Lzgg1RY9EX23iB273B+FcVaDgWWmft5NRPJDJZOrhNwlbYj1kNoCbkpgs0klVf4Erkk8YLPYstJ+CPhbYiOtGG3qMOBIx2OS/0pK8XfJj0T4UlKft0ckBE12O9X1qeQiJQp1pQsNcrif2GMpgXiiKHwxyeNp5HB+91KElyNcgKOZUC8pKn3sJaok4sOah8opLXvsTNLpi6N1+qCnjkr+WPLjki/XUq1JCeXU6mKEn4zwR5KP9HXlUFL55n0R/il12ffg1Qh/G2FL7kr2mJbf+SzCq0XhX9NHXpa80kBsYDJ8J/GczR1gGfhR4hWJ+6WJ4pSoZvUtmy9tHqrV/XkEL9tDE4qdDBl8nZ1cbSudP5/uF81jYFQOZ8CvJu9v1gb+D2l89XuqJ07Vg6PZCXi5bJINj4Iq8cgernu7KRAPRPRRi2GX8VMs9R09pKwfc7kg7yuppklzQKotDjzVFgeeaou58FR/EG1Faha7C3lJaZOBD/0NUq8ogKV0PYlKyF+dG6ovM2OTKtPNfyR+AUgbqONmM3kX8GdgDThMJWUKmj+wkdQCcEPixQjeKkueklhjst3hFeAL4KUIvirLVnVs65lF8B9J66yDT0T4fNJB0/x5yPrp3Qifiti+vpL0VJbh2XpI1/v7lMRp4I2yZDOVdSc0U3XZmbLkUZvTRaXM3LBBF/0XAl4oCk4A75QlG1RdqSktgE3gfZsjNq9FcJe07YyB7st2L/jZovDbM8j1mqwEW/KbEX4mokrndkscliVOJnHfZvqOC1PNvAL4muFKdoBUMPkmxrhoamvofsNe/RHtR5Pen4dfxgOYS5XwH7eZhqnTFNxUAAAAAElFTkSuQmCC'
-
led7 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAACtElEQVRoge2ZwWoUQRCGv+ruFRPx4iOIGPEuETyLGDH4AqII4tN49KQg+AB6jAjiOeBFg5gHEA+KHiQmJpme38P0hN3ZSZysm949zA9NQ2/t9LfdtV1V0waIOZObNUCbeqiumkuocNgHuWjF+D/NWsayylI/DHHoSi2ZUUgUTH/VDNgHzgK/zPgijazOGJQDSmDZjOdAlIhTBiuARWATuG/GjsQPRrdNzeZSfy8ERTMJVII0hRZTv2mmJee0Brqe5vNDLjYGBSikftVMuwlqPz100raXgDa81+XBQK+9l0BXukIBGqT+pnPadu6/VqxeoXXQee/1Lj1PoKsNqEMdHSpnDMBaWXInBJ46R6DyOTvqiw2VwAKwIfGwLHkRI9fS8wct9kdCAQf/vjdFwUXvCUx2hhjwR+ItHAAdNvk/oaD6pQ7Yi5HdCYEEnAEupDF/hH0nqBrMON62NaFKIHaw7wwF0zn6u/youQzIPVRX9VBd1UN1VQ/VVVmh6lDTNj6srFCO0UCsofGm3YlLVDFvG1i3KvoVaUxm/E5janznxJul/pSZXqU0OIIemAmzg7qgZssCNQLmnF6GoEdmI4XKkF3eYtSlFp0DiSCx32KTTXUuVQCPgVWqtLgtC822fYAw05NUS26Z6XYIbVuY0Z/M9Cw5+S5VebXjnC41fOtY6fCkqh13UeJGrLL0ulQ7XZacG7KDzD7VLBxqiGYxkd3R2yZsFhN9QO6qHqqreqiu6qG6aj6gbPT4zF44dEneshcOYxPa+Bur7IXDR1dNORKEZ7l9AHfNeO89A6oMtE3ZoJQm+xkjKxIfzFpfV2eFgiqf8sD3smRF4nMaj7PevpjAvgK3nOOb9yw0bGZ23+epAJdDYAv4VBQHN2gzvYRsu4CEGZ/o9VHRPKlmfl3bpvmIfQ3NJdRfFS/ZxKoPEs0AAAAASUVORK5CYII='
-
led8 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEi0lEQVRoge2ZT4gcRRTGf+9Vd/VkIRjxIqggJF5E8JSDF/EQjKAICflDQjAnUdGD6EEQDHrRmwdBD6IIweQkiiIevOTgKSAiiiAiBDxpomhiJLvTVf08TPVkZqdmMz272Z2FfPCoobrr1dev/r1vSgBjwaBbTSCHW6RmxUKSKnKVkuxmw8ivMlld35LZjCU5ra8JUgD3qyJNQ5+bM74R2AGsqPJL02RJGGCSyru9t5NVZf+JmIEFsGYDrQYzsL9U7diOHXavc2P9AzYMhEvlURH2x8iBsuSaKi69KRtgxmAS/y3CwbLkZAg8FuNY/5AZncvAsRA41DQ86T1XRFAgAM06LKTOflflibLkuRjZX9f8I/klZYAVqXy2qsxSiM+UpT3ivV1KQ9mk+q7WtvvNOXuoquwL54bPToiM9Z9+57EMHK9ryqLgcFnyoRlLZkS6bRcGlMCfwDPO8XoIPBojy0BvSpsJUjLyoAYOh0C/LNltxs5EqisUuAp8mQjVieg0TI0UXJ9896lCCFyNsfP+1U5wBfaM+G26kBodmpZAYzbxrCuxBmaO8pqRGnUK04+FWTHrR01sCZtx5t0IC5klbI9ILSSpjYZM+b0WNi1Syvihe6N3Fw6bOqcWbvi6YKYdvT2nlO47envEqMjAj417yPmbIJWbZC4lYs2cpNrjKec7NzITpK6YgQiNGcJgxZwz43Yz7mSQQXZFAfwhwjlV9oRw/WAW4V8RMMsLh6GJ2HtFYZayzTe9tyPeDwVEAIsdLKQM85qqPV5V9s5I1vmW94bImGhgICImdZ+J8EFR0Ae+UeVMv4+kr5lnIbTtVkQ4WhQcEuFi0/ByjEO/uTajLM2DoWoPem8rqhY2QGq17a+o2p6qMkTMr5JWUyOlQCPCKeeIqlw04/0QhonePGgnuQAnvOcBMy4Bb4eAmmWz0LEoIWKnynIw7iL2Qlna096bqVqjatbRRtsc895e837gW9WeL8tWfOYj5Rikq8erijN1TZ3kdAG8UpZ8JcI9ZvTprmYq4IIZJ0R4NQTqtLILVfaL8HWMw/7HItXqrpeS7qtHVs557w3nsuN/I5OR8ufkL3Jdvj+VZPuaui83d9ovaCX8PDDye1yuv0k1k5HR7U4c10Fq1M8YMqS2R+qy2YiLFKl2kuQE6pZHKre9zERqs68ktjxSOdwiNSu2L6mNmuhr/VE2ipnUjKajZ56sc6hmpnRmKUcf628th+2XnY8RzBARrKM1IgN5pcq3xYDW6hRlNaZGKjD49/Z0UXBOle+ahmWRucWqA153jiXgQAgspzoykZpKqgd8UpacLQo+XVlhqWnWLRxO9/scLAp2Ose+GEFktki1Oc/nzvGuCJ8tL7NkRpiTUIsGuMOMszFy1Hs88HCMBJ2cQRM1twHfO8cbzvFxXbPLjHqdhNqO+sBdTcNHdc2LZcmvRcHOKe+PJe/7ej070uvZhSQa5736mGYxlT+q2sFez/YWxYR4GJNYrRDdC+w24zIz7hkdIAxuMnYBP6nyQ+a+b/vcjLbC8WbDyO/yWVJbjYU8kP8HKX/sOnQ6GT4AAAAASUVORK5CYII='
-
led9 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAAEYklEQVRoge2ZT2gkRRTGf+91VXeQJcp6kAVBEFn/ggh7ET0IggjBGBNW1+Bpc1E8uDfPIgqC4OLNu6IL4oJ48+RR0LMXvQgii4d1E4PZdFc9D1M9yWRqJjPpbGbAfFBM0l3d7+tXr+q9r0oAY86gsyaQwympSTGXpFzuoqR2p2HkZ5kcvN6SOYkpOcrWECmAh1XxMbIDFHeATAQqIKjyS4zZPgaYpN9zInapquymqhlYDRaPsTVgBrYlYm+UpT1YlgP2AesHeuuRJTPWQ2CtLLklgks95RiaJTvbqqyWJa/EyEXVAfuQmX03gZeaho0QWPaev0RQoEluP2prkrFbIiyXJZdCYLVp+Nvy0WuAufT7WnKvgV0vCnumLO2PojBL7rcjtPa5G6r2bFnaV973771dVQP209957AArIbAAvO49n4lwFqiZbrkweuvOlggbIlwJgbUQ2AEWRjwzRKp1pk8EXgyBWoRHzThDbyimRQFsAdeAtRCoc4bHkWqDrA1KgIdiRMz4xyy/hkwCVc471yc57uOGSDmR3mjvQ2BvBsL0q70kEmFEUB9KKmew6yrfPjdpoh3uJyeR9cZjiNTsKZ16anLMZZE37Kl5HL55wGlMTYrhvJiJqTZPdfmCtq46iFyWGJmQc50iI4r6CZGrDDTjhH6/1tAmgAhmRkgkf1DlbIzcZ0ZzRDI3gO9FeJJegifZ2RwnHGiLdxH7uCzNRMzArjpny2VpO0lINGBhitakyrMWsVXv7aOy7FWdIvap94bIgGhIPIZ1n6nySVGwKMJ3wLW6xpsNlC/ToH0uAutVxfNmRDPeDAGJMRsOB1n26+XHqsq2i6IvjbpKqwD2r6o9UVWGqpUHpNVIT7XecqrUIrxTFFyta+IxeEpF2PCe+4EmRj5sGpTRs3KgFUVhImIKhnP2lvdmqhZVzTq0y1VlV1olI2LvOme0dsZ5SpM4jGlWFCIEVS6IcK8IzZTeMqAEfhfhZeCD3V3qdM+LcNF7vt7dpWBvVmZFRdw3TYMZzoyfY8QmrLGzUOXzJBw0kcWMc8kJ+z90nNLZI0a31VzoKeQmfdT+4cmJiYlItdO56/bQ0YXDHGAiUp1i6bB3Z67N3FNzSSqHmZOaS0/NjNTYDJCZRP9fT/UxoaY8UVIxM1QTCYccNNXsXVFkPHWk4VMgivRyn8iRWrsV8GP6sLDfwKQJuc3iDmhUWSoK3gd20p7ntDB6xx7vqbLoPet1zU56/yj/D1R9qrq3r61qz3lv20nJdN1H31K1FxYW7Bvn+vcuO3f4PrrR24xtRLjgHNebhrtinLrizL33TIx8efs2K95TqrIUY78K3Y8hUgXQiPCIc3wbAvfEyC7dT7ME2AXOmvFF07DiPQ80DYvjjkFo5Y6qPe6c/dlxyA4byt+Kwl5dWLCnve+FzSjh0P7zlCrnY2ST3snDcVZTQu8k427gVxF+yhwYdNmv6IxR+/PZJUE5mc0zIy9EZ+qpUZh5lZDDfw9J5SDpQQEIAAAAAElFTkSuQmCC'
-
ledcolon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACUAAAA8CAYAAADhR3NQAAAACXBIWXMAADBKAAAwSgHCONjjAAABdklEQVRoge2YQU7DMBBFn9MEVVBYcADEmiVH4BScD3EL7gIcAYEEFSQdFrZRCJGYeBa1qnlSZSuVJz8/k/EkARAqo9m3gDlclBYXpcVFaXFRWlyUFhelpUpRrWVxAFaTYwIMlqApbnX9VJFT+UpOgRugA3ZE195C4EHE7JYs/TVpvAJ5BxGQPo1PTSObEASQUBA7xS/nE/hI8+yMEJ20YBK1G81zwnfWoKXr85MxAF9p3o/+24tT45N2k/EYOLIoovDpy069AvfAOdGxDngR+cmzUg6nTv0XoJ85toQqnapyQ3ZRWlyUFhelpUpR5oo+1xFYq3GVFd3Uo18Cd8CGuN+tgcem4VaErUjxFZtErYFr4ITY8K2AM2ILsy0JnDD36Pnk49a4mh49B2qtQUvX5zzZ8TdnVqVBR5jWD/x2C6Ko6av8UkwloQUuiImd32J64Bnb94TDqVNj5u7/9JYupUqnqtyQXZQWF6XFRWlxUVpclBYXpeUbS+1qOYf8HRgAAAAASUVORK5CYII='
-
ledblank = 'data:image/png;base64,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/GMzLdsAADpEaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzE0NSA3OS4xNjIzMTksIDIwMTgvMDIvMTUtMjA6Mjk6NDMgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgICAgICAgICB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgICAgICAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgICAgICAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iPgogICAgICAgICA8eG1wOkNyZWF0b3JUb29sPkFkb2JlIFBob3Rvc2hvcCBFbGVtZW50cyAxNy4wIChXaW5kb3dzKTwveG1wOkNyZWF0b3JUb29sPgogICAgICAgICA8eG1wOkNyZWF0ZURhdGU+MjAxOC0xMC0yMFQxNjowOToyNS0wNDowMDwveG1wOkNyZWF0ZURhdGU+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE4LTEwLTIwVDE2OjM2OjMyLTA0OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOC0xMC0yMFQxNjozNjozMi0wNDowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8L2RjOmZvcm1hdD4KICAgICAgICAgPHBob3Rvc2hvcDpDb2xvck1vZGU+MzwvcGhvdG9zaG9wOkNvbG9yTW9kZT4KICAgICAgICAgPHBob3Rvc2hvcDpJQ0NQcm9maWxlPnNSR0IgSUVDNjE5NjYtMi4xPC9waG90b3Nob3A6SUNDUHJvZmlsZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDoxYTc5NDQ5ZC05ZmM0LWZiNGItOTNjYy0xNTcyMjU1MGU5OTk8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkMGIzZGFmNS1kNGE3LTExZTgtOGI3ZC1jYmQxMWFlODBkMGE8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDowZDlkYjE1OS1lYThmLWQxNDEtYWE4MS04YTY4NzFkNTI4YTc8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6MGQ5ZGIxNTktZWE4Zi1kMTQxLWFhODEtOGE2ODcxZDUyOGE3PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE4LTEwLTIwVDE2OjA5OjI1LTA0OjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgRWxlbWVudHMgMTcuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjFhNzk0NDlkLTlmYzQtZmI0Yi05M2NjLTE1NzIyNTUwZTk5OTwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAxOC0xMC0yMFQxNjozNjozMi0wNDowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIEVsZW1lbnRzIDE3LjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjMxNDAwMDAvMTAwMDA8L3RpZmY6WFJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOllSZXNvbHV0aW9uPjMxNDAwMDAvMTAwMDA8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOlJlc29sdXRpb25Vbml0PjI8L3RpZmY6UmVzb2x1dGlvblVuaXQ+CiAgICAgICAgIDxleGlmOkNvbG9yU3BhY2U+MTwvZXhpZjpDb2xvclNwYWNlPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+Mzc8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFlEaW1lbnNpb24+NjA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PvnJsBAAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAEZJREFUeNrszkEBACAMAKHT/p1nDPeABJxqWua2kJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSU1G8PAAD//wMAGdkBdyV2S1QAAAAASUVORK5CYII='
-
w1 = 'data:image/png;base64,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'
-
w2 = 'data:image/png;base64,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'
-
w3 = 'data:image/png;base64,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'
-
w4 = 'data:image/png;base64,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'
-
w5 = 'data:image/png;base64,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'
-
orangeround = 'data:image/png;base64,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'
-
weather_icon_dict = {'clear-day': w1, 'clear-night': w1, 'rain': w3, 'snow': w3, 'sleet': w3, 'wind': w3, 'fog': w3,
'cloudy': w4, 'partly-cloudy-day': w5, 'partly-cloudy-night': w5}
-
led_digits = [led0, led1, led2, led3, led4, led5, led6, led7, led8, led9]
-
if __name__ == '__main__':
led_clock()
diff --git a/DemoPrograms/Demo_LED_Indicators.py b/DemoPrograms/Demo_LED_Indicators.py
index 790b3cb8..4f44b907 100644
--- a/DemoPrograms/Demo_LED_Indicators.py
+++ b/DemoPrograms/Demo_LED_Indicators.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import time
import random
@@ -23,9 +18,9 @@ def LEDIndicator(key=None, radius=30):
pad=(0, 0), key=key)
def SetLED(window, key, color):
- graph = window.FindElement(key)
- graph.Erase()
- graph.DrawCircle((0, 0), 12, fill_color=color, line_color=color)
+ graph = window[key]
+ graph.erase()
+ graph.draw_circle((0, 0), 12, fill_color=color, line_color=color)
layout = [[sg.Text('My LED Status Indicators', size=(20,1))],
@@ -35,11 +30,11 @@ layout = [[sg.Text('My LED Status Indicators', size=(20,1))],
[sg.Text('Server 1'), LEDIndicator('_server1_')],
[sg.Button('Exit')]]
-window = sg.Window('My new window', default_element_size=(12, 1), auto_size_text=False).Layout(layout).Finalize()
+window = sg.Window('My new window', layout, default_element_size=(12, 1), auto_size_text=False, finalize=True)
i = 0
while True: # Event Loop
- event, value = window.Read(timeout=400)
+ event, value = window.read(timeout=400)
if event == 'Exit' or event is None:
break
if value is None:
@@ -49,3 +44,4 @@ while True: # Event Loop
SetLED(window, '_ram_', 'green' if random.randint(1, 10) > 5 else 'red')
SetLED(window, '_temp_', 'green' if random.randint(1, 10) > 5 else 'red')
SetLED(window, '_server1_', 'green' if random.randint(1, 10) > 5 else 'red')
+window.close()
diff --git a/DemoPrograms/Demo_Layout_Generation.py b/DemoPrograms/Demo_Layout_Generation.py
index 201a0ce0..7ac1702f 100644
--- a/DemoPrograms/Demo_Layout_Generation.py
+++ b/DemoPrograms/Demo_Layout_Generation.py
@@ -34,10 +34,11 @@ def layout0():
window = sg.Window('Generated Layouts', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
+
"""
Construct #1 - List comprehension to generate a Column of Buttons
@@ -46,15 +47,17 @@ def layout0():
"""
+
def layout1():
- layout = [[sg.Button(i)] for i in range(4)] # a List of lists of buttons. Notice the ] after Button
+ # a List of lists of buttons. Notice the ] after Button
+ layout = [[sg.Button(i)] for i in range(4)]
window = sg.Window('Generated Layouts', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -70,15 +73,17 @@ def layout1():
See next Construct on how to not use a \ that also results in a VISUALLY similar to a norma layout
"""
+
def layout2():
- layout = [[sg.Button(i) for i in range(4)]] + [[sg.OK()]] # if want to split, can't add newline after + to do it
+ # if want to split, can't add newline after + to do it
+ layout = [[sg.Button(i) for i in range(4)]] + [[sg.OK()]]
window = sg.Window('Generated Layouts', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -94,18 +99,21 @@ def layout2():
use this way because it modifies the layout list directly.
"""
+
def layout3():
# in terms of formatting, the layout to the RIGHT of the = sign looks like a 2-line GUI (ignore the layout +=
- layout = [[sg.Button(i) for i in range(4)]]
- layout += [[sg.OK()]] # this row is better than, but is the same as
- layout.append([sg.Cancel()]) # .. this row in that they both add a new ROW with a button on it
+ layout = [[sg.Button(i) for i in range(4)]]
+ # this row is better than, but is the same as
+ layout += [[sg.OK()]]
+ # .. this row in that they both add a new ROW with a button on it
+ layout.append([sg.Cancel()])
window = sg.Window('Generated Layouts', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -115,15 +123,16 @@ def layout3():
items in one list are added to the items in another. That's true for all these contructs using +
"""
+
def layout4():
- layout = [[sg.Text('Enter some info')] + [sg.Input()] + [sg.Exit()]]
+ layout = [[sg.Text('Enter some info')] + [sg.Input()] + [sg.Exit()]]
window = sg.Window('Generated Layouts', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -147,20 +156,24 @@ def layout4():
Works as long as the things you are adding together look like this [[ ]] (the famous double bracket layouts of PSG)
"""
+
def layout5():
questions = ('Managing your day-to-day life', 'Coping with problems in your life?', 'Concentrating?',
'Get along with people in your family?', 'Get along with people outside your family?',
'Get along well in social situations?', 'Feel close to another person',
'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
- layout = [[sg.T(qnum + 1, size=(2, 2)), sg.T(q, size=(30, 2))] + [sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, col)) for col in range(5)] for qnum, q in enumerate(questions)]
+ layout = [[sg.Text(qnum + 1, size=(2, 2)), sg.Text(q, size=(30, 2))] +
+ [sg.Radio('', group_id=qnum, size=(7, 2),
+ key=(qnum, col)) for col in range(5)]
+ for qnum, q in enumerate(questions)]
layout += [[sg.OK()]]
window = sg.Window('Computed Layout Questionnaire', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -181,20 +194,26 @@ def layout6():
'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
layout = [[]]
- for qnum, question in enumerate(questions): # loop through questions
- row_layout = [sg.T(qnum + 1, size=(2, 2)), sg.T(question, size=(30, 2))] # rows start with # and question
- for radio_num in range(5): # loop through 5 radio buttons and add to row
- row_layout += [sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, radio_num))]
- layout += [row_layout] # after row is completed layout, tack it onto the end of final layout
+ for qnum, question in enumerate(questions):
+ # rows start with # and question
+ row_layout = [sg.Text(qnum + 1, size=(2, 2)),
+ sg.Text(question, size=(30, 2))]
- layout += [[sg.OK()]] # and finally, add a row to the bottom that has an OK button
+ # loop through 5 radio buttons and add to row
+ for radio_num in range(5):
+ row_layout += [sg.Radio('', group_id=qnum,
+ size=(7, 2), key=(qnum, radio_num))]
+ # after row is completed layout, tack it onto the end of final layout
+ layout += [row_layout]
+
+ # and finally, add a row to the bottom that has an OK button
+ layout += [[sg.OK()]]
window = sg.Window('Computed Layout Questionnaire', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
-
+ window.close()
"""
@@ -223,21 +242,24 @@ def layout6():
variable number of rows and a variable number of columns in each row.
"""
+
def layout7():
questions = ('Managing your day-to-day life', 'Coping with problems in your life?', 'Concentrating?',
'Get along with people in your family?', 'Get along with people outside your family?',
'Get along well in social situations?', 'Feel close to another person',
'Feel like you had someone to turn to if you needed help?', 'Felt confident in yourself?')
- layout = [[*[sg.T(qnum + 1, size=(2, 2)), sg.T(q, size=(30, 2))], # These are the question # and the question text
- *[sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, col)) for col in range(5)]] for qnum, q in enumerate(questions)] + [[sg.OK()]] # finally add an OK button at the very bottom by using the '+' operator
+ # These are the question # and the question text
+ layout = [[*[sg.Text(qnum + 1, size=(2, 2)), sg.Text(q, size=(30, 2))],
+ # finally add an OK button at the very bottom by using the '+' operator
+ *[sg.Radio('', group_id=qnum, size=(7, 2), key=(qnum, col)) for col in range(5)]] for qnum, q in enumerate(questions)] + [[sg.OK()]]
window = sg.Window('Questionnaire', layout)
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- window.Close()
+ window.close()
"""
@@ -249,20 +271,25 @@ def layout7():
In this example we start with a "Header" Text element and build from there.
"""
+
def layout8():
# The questions and answers
q_and_a = [
- ['1. What is the thing that makes light in our solar system', ['A. The Moon', 'B. Jupiter', 'C. I dunno']],
- ['2. What is Pluto', ['A. The 9th planet', 'B. A dwarf-planet', 'C. The 8th planet', 'D. Goofies pet dog']],
+ ['1. What is the thing that makes light in our solar system',
+ ['A. The Moon', 'B. Jupiter', 'C. I dunno']],
+ ['2. What is Pluto', ['A. The 9th planet', 'B. A dwarf-planet',
+ 'C. The 8th planet', 'D. Goofies pet dog']],
['3. When did man step foot on the moon', ['A. 1969', 'B. 1960', 'C. 1970', 'D. 1869']], ]
- layout = [[sg.Text('Astronomy Quiz #1', font='ANY 15', size=(30, 2))]] # make Header larger
+ # make Header larger
+ layout = [[sg.Text('Astronomy Quiz #1', font='ANY 15', size=(30, 2))]]
# "generate" the layout for the window based on the Question and Answer information
for qa in q_and_a:
q = qa[0]
a_list = qa[1]
- layout += [[sg.Text(q)]] + [[sg.Radio(a, group_id=q)] for a in a_list] + [[sg.Text('_' * 50)]]
+ layout += [[sg.Text(q)]] + [[sg.Radio(a, group_id=q)]
+ for a in a_list] + [[sg.Text('_' * 50)]]
layout += [[sg.Button('Submit Answers', key='SUBMIT')]]
@@ -279,11 +306,11 @@ def layout8():
# ------------------------- Call each of the Constructs -------------------------
layout0()
-layout1()
-layout2()
-layout3()
-layout4()
-layout5()
-layout6()
-layout7()
-layout8()
+# layout1()
+# layout2()
+# layout3()
+# layout4()
+# layout5()
+# layout6()
+# layout7()
+# layout8()
diff --git a/DemoPrograms/Demo_Listbox_Search_Filter.py b/DemoPrograms/Demo_Listbox_Search_Filter.py
index 88f4ce45..3db24890 100644
--- a/DemoPrograms/Demo_Listbox_Search_Filter.py
+++ b/DemoPrograms/Demo_Listbox_Search_Filter.py
@@ -1,27 +1,29 @@
import PySimpleGUI as sg
names = ['Roberta', 'Kylie', 'Jenny', 'Helen',
- 'Andrea', 'Meredith','Deborah','Pauline',
- 'Belinda', 'Wendy']
+ 'Andrea', 'Meredith', 'Deborah', 'Pauline',
+ 'Belinda', 'Wendy']
-layout = [ [sg.Text('Listbox with search')],
- [sg.Input(do_not_clear=True, size=(20,1),enable_events=True, key='_INPUT_')],
- [sg.Listbox(names, size=(20,4), enable_events=True, key='_LIST_')],
- [sg.Button('Chrome'), sg.Button('Exit')]]
+layout = [[sg.Text('Listbox with search')],
+ [sg.Input(size=(20, 1), enable_events=True, key='-INPUT-')],
+ [sg.Listbox(names, size=(20, 4), enable_events=True, key='-LIST-')],
+ [sg.Button('Chrome'), sg.Button('Exit')]]
-window = sg.Window('Listbox with Search').Layout(layout)
+window = sg.Window('Listbox with Search', layout)
# Event Loop
while True:
- event, values = window.Read()
- if event is None or event == 'Exit': # always check for closed window
+ event, values = window.read()
+ if event in (None, 'Exit'): # always check for closed window
break
- if values['_INPUT_'] != '': # if a keystroke entered in search field
- search = values['_INPUT_']
+ if values['-INPUT-'] != '': # if a keystroke entered in search field
+ search = values['-INPUT-']
new_values = [x for x in names if search in x] # do the filtering
- window.Element('_LIST_').Update(new_values) # display in the listbox
+ window['-LIST-'].update(new_values) # display in the listbox
else:
- window.Element('_LIST_').Update(names) # display original unfiltered list
- if event == '_LIST_' and len(values['_LIST_']): # if a list item is chosen
- sg.Popup('Selected ', values['_LIST_'])
+ # display original unfiltered list
+ window['-LIST-'].update(names)
+ # if a list item is chosen
+ if event == '-LIST-' and len(values['-LIST-']):
+ sg.popup('Selected ', values['-LIST-'])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_MIDI_Player.py b/DemoPrograms/Demo_MIDI_Player.py
index 11b4445c..0be9a348 100644
--- a/DemoPrograms/Demo_MIDI_Player.py
+++ b/DemoPrograms/Demo_MIDI_Player.py
@@ -1,13 +1,8 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import os
import mido
import time
-import sys
PLAYER_COMMAND_NONE = 0
PLAYER_COMMAND_EXIT = 1
@@ -18,6 +13,8 @@ PLAYER_COMMAND_RESTART_SONG = 4
# ---------------------------------------------------------------------- #
# PlayerGUI CLASS #
# ---------------------------------------------------------------------- #
+
+
class PlayerGUI():
'''
Class implementing GUI for both initial screen but the player itself
@@ -26,8 +23,10 @@ class PlayerGUI():
def __init__(self):
self.Window = None
self.TextElem = None
- self.PortList = mido.get_output_names() # use to get the list of midi ports
- self.PortList = self.PortList[::-1] # reverse the list so the last one is first
+ # use to get the list of midi ports
+ self.PortList = mido.get_output_names()
+ # reverse the list so the last one is first
+ self.PortList = self.PortList[::-1]
# ---------------------------------------------------------------------- #
# PlayerChooseSongGUI #
@@ -37,20 +36,27 @@ class PlayerGUI():
# ---------------------- DEFINION OF CHOOSE WHAT TO PLAY GUI ----------------------------
- layout = [[sg.Text('MIDI File Player', font=("Helvetica", 15), size=(20, 1), text_color='green')],
- [sg.Text('File Selection', font=("Helvetica", 15), size=(20, 1))],
- [sg.Text('Single File Playback', justification='right'), sg.InputText(size=(65, 1), key='midifile'), sg.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
- [sg.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'), sg.InputText(size=(65, 1), key='folder'), sg.FolderBrowse(size=(10, 1))],
+ helv = ("Helvetica", 15)
+ layout = [[sg.Text('MIDI File Player', font=helv15, size=(20, 1), text_color='green')],
+ [sg.Text('File Selection', font=helv15, size=(20, 1))],
+ [sg.Text('Single File Playback', justification='right'),
+ sg.InputText(size=(65, 1), key='midifile'),
+ sg.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
+ [sg.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'),
+ sg.InputText(size=(65, 1), key='folder'),
+ sg.FolderBrowse(size=(10, 1))],
[sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
[sg.Text('Choose MIDI Output Device', size=(22, 1)),
sg.Listbox(values=self.PortList, size=(30, len(self.PortList) + 1), key='device')],
[sg.Text('_' * 250, auto_size_text=False, size=(100, 1))],
- [sg.SimpleButton('PLAY', size=(12, 2), button_color=('red', 'white'), font=("Helvetica", 15), bind_return_key=True), sg.Text(' ' * 2, size=(4, 1)), sg.Cancel(size=(8, 2), font=("Helvetica", 15))]]
+ [sg.SimpleButton('PLAY', size=(12, 2), button_color=('red', 'white'), font=helv15, bind_return_key=True),
+ sg.Text(' ' * 2, size=(4, 1)),
+ sg.Cancel(size=(8, 2), font=helv15)]]
- window = sg.Window('MIDI File Player', auto_size_text=False, default_element_size=(30, 1), font=("Helvetica", 12)).Layout(layout)
+ window = sg.Window('MIDI File Player', layout, auto_size_text=False,
+ default_element_size=(30, 1), font=helv)
self.Window = window
- return window.Read()
-
+ return window.read()
def PlayerPlaybackGUIStart(self, NumFiles=1):
# ------- Make a new FlexForm ------- #
@@ -60,37 +66,44 @@ class PlayerGUI():
image_next = './ButtonGraphics/Next.png'
image_exit = './ButtonGraphics/Exit.png'
- self.TextElem = sg.T('Song loading....', size=(70, 5 + NumFiles), font=("Helvetica", 14), auto_size_text=False)
- self.SliderElem = sg.Slider(range=(1, 100), size=(50, 8), orientation='h', text_color='#f0f0f0')
+ self.TextElem = sg.Text('Song loading....',
+ size=(70, 5 + NumFiles),
+ font=("Helvetica", 14), auto_size_text=False)
+ self.SliderElem = sg.Slider(range=(1, 100),
+ size=(50, 8),
+ orientation='h', text_color='#f0f0f0')
+ css = {
+ 'image_size': (50, 50),
+ 'image_subsample': 2,
+ 'border_width': 0,
+ 'button_color': sg.TRANSPARENT_BUTTON
+ }
layout = [
- [sg.T('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
- [self.TextElem],
- [self.SliderElem],
- [sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
- image_filename=image_pause, image_size=(50,50), image_subsample=2, border_width=0, key='PAUSE'), sg.T(' '),
- sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
- image_filename=image_next, image_size=(50,50), image_subsample=2, border_width=0, key='NEXT'), sg.T(' '),
- sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
- image_filename=image_restart, image_size=(50,50), image_subsample=2, border_width=0, key='Restart Song'), sg.T(' '),
- sg.Button('', button_color=sg.TRANSPARENT_BUTTON,
- image_filename=image_exit, image_size=(50,50), image_subsample=2, border_width=0,key='EXIT')]
- ]
+ [sg.Text('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
+ [self.TextElem],
+ [self.SliderElem],
+ [sg.Button('', image_filename=image_pause, **css, key='PAUSE'), sg.Text(' '),
+ sg.Button('', image_filename=image_next, **css, key='NEXT'), sg.Text(' '),
+ sg.Button('', image_filename=image_restart, **css, key='Restart Song'), sg.Text(' '),
+ sg.Button('', image_filename=image_exit, **css, key='EXIT')]
+ ]
- window = sg.Window('MIDI File Player', default_element_size=(30, 1), font=("Helvetica", 25)).Layout(layout).Finalize()
+ window = sg.Window('MIDI File Player', layout, default_element_size=(
+ 30, 1), font=("Helvetica", 25), finalize=True)
self.Window = window
-
-
# ------------------------------------------------------------------------- #
# PlayerPlaybackGUIUpdate #
# Refresh the GUI for the main playback interface (must call periodically #
# ------------------------------------------------------------------------- #
+
def PlayerPlaybackGUIUpdate(self, DisplayString):
window = self.Window
- if 'window' not in locals() or window is None: # if the widnow has been destoyed don't mess with it
+ # if the widnow has been destoyed don't mess with it
+ if 'window' not in locals() or window is None:
return PLAYER_COMMAND_EXIT
- self.TextElem.Update(DisplayString)
- event, (values) = window.Read(timeout=0)
+ self.TextElem.update(DisplayString)
+ event, (values) = window.read(timeout=0)
if event is None:
return PLAYER_COMMAND_EXIT
if event == 'PAUSE':
@@ -118,38 +131,42 @@ def main():
'''
return int(round(time.time() * 1000))
-
pback = PlayerGUI()
button, values = pback.PlayerChooseSongGUI()
if button != 'PLAY':
- sg.PopupCancel('Cancelled...\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
- sys.exit(69)
+ sg.popup_cancel('Cancelled...\nAutoclose in 2 sec...',
+ auto_close=True, auto_close_duration=2)
+ return
if values['device']:
midi_port = values['device'][0]
else:
- sg.PopupCancel('No devices found\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
+ sg.popup_cancel('No devices found\nAutoclose in 2 sec...',
+ auto_close=True, auto_close_duration=2)
batch_folder = values['folder']
midi_filename = values['midifile']
# ------ Build list of files to play --------------------------------------------------------- #
if batch_folder:
filelist = os.listdir(batch_folder)
- filelist = [batch_folder+'/'+f for f in filelist if f.endswith(('.mid', '.MID'))]
+ filelist = [batch_folder+'/' +
+ f for f in filelist if f.endswith(('.mid', '.MID'))]
filetitles = [os.path.basename(f) for f in filelist]
elif midi_filename: # an individual filename
- filelist = [midi_filename,]
- filetitles = [os.path.basename(midi_filename),]
+ filelist = [midi_filename, ]
+ filetitles = [os.path.basename(midi_filename), ]
else:
- sg.PopupError('*** Error - No MIDI files specified ***')
- sys.exit(666)
+ sg.popup_error('*** Error - No MIDI files specified ***')
+ return
# ------ LOOP THROUGH MULTIPLE FILES --------------------------------------------------------- #
- pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <=10 else 10)
+ pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <= 10 else 10)
port = None
+
# Loop through the files in the filelist
for now_playing_number, current_midi_filename in enumerate(filelist):
- display_string = 'Playing Local File...\n{} of {}\n{}'.format(now_playing_number+1, len(filelist), current_midi_filename)
+ display_string = 'Playing Local File...\n{} of {}\n{}'.format(
+ now_playing_number+1, len(filelist), current_midi_filename)
midi_title = filetitles[now_playing_number]
# --------------------------------- REFRESH THE GUI ----------------------------------------- #
pback.PlayerPlaybackGUIUpdate(display_string)
@@ -164,16 +181,19 @@ def main():
try:
mid = mido.MidiFile(filename=midi_filename)
except:
- print('****** Exception trying to play MidiFile filename = {}***************'.format(midi_filename))
- sg.PopupError('Exception trying to play MIDI file:', midi_filename, 'Skipping file')
+ print(' Fail at playing Midi file = {}****'.format(midi_filename))
+ sg.popup_error('Exception trying to play MIDI file:',
+ midi_filename, 'Skipping file')
continue
# Build list of data contained in MIDI File using only track 0
midi_length_in_seconds = mid.length
- display_file_list = '>> ' + '\n'.join([f for i, f in enumerate(filetitles[now_playing_number:]) if i < 10])
+ display_file_list = '>> ' + \
+ '\n'.join([f for i, f in enumerate(
+ filetitles[now_playing_number:]) if i < 10])
paused = cancelled = next_file = False
######################### Loop through MIDI Messages ###########################
- while(True):
+ while True :
start_playback_time = GetCurrentTime()
port.reset()
@@ -181,12 +201,14 @@ def main():
#################### GUI - read values ##################
if not midi_msg_number % 4: # update the GUI every 4 MIDI messages
t = (GetCurrentTime() - start_playback_time)//1000
- display_midi_len = '{:02d}:{:02d}'.format(*divmod(int(midi_length_in_seconds),60))
+ display_midi_len = '{:02d}:{:02d}'.format(
+ *divmod(int(midi_length_in_seconds), 60))
display_string = 'Now Playing {} of {}\n{}\n {:02d}:{:02d} of {}\nPlaylist:'.\
format(now_playing_number+1, len(filelist), midi_title, *divmod(t, 60), display_midi_len)
- # display list of next 10 files to be played.
- pback.SliderElem.Update(t, range=(1,midi_length_in_seconds))
- rc = pback.PlayerPlaybackGUIUpdate(display_string + '\n' + display_file_list)
+ # display list of next 10 files to be played.
+ pback.SliderElem.update(t, range=(1, midi_length_in_seconds))
+ rc = pback.PlayerPlaybackGUIUpdate(
+ display_string + '\n' + display_file_list)
else: # fake rest of code as if GUI did nothing
rc = PLAYER_COMMAND_NONE
if paused:
@@ -219,9 +241,9 @@ def main():
if cancelled:
break
+
# ---------------------------------------------------------------------- #
# LAUNCH POINT -- program starts and ends here #
# ---------------------------------------------------------------------- #
if __name__ == '__main__':
main()
-
diff --git a/DemoPrograms/Demo_Machine_Learning.py b/DemoPrograms/Demo_Machine_Learning.py
index 0b5881f1..a21c2f39 100644
--- a/DemoPrograms/Demo_Machine_Learning.py
+++ b/DemoPrograms/Demo_Machine_Learning.py
@@ -1,65 +1,75 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
def MachineLearningGUI():
- sg.SetOptions(text_justification='right')
+ sg.set_options(text_justification='right')
- flags = [[sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))],
- [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)],
- [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))],
- [sg.Checkbox('Normalize', size=(12, 1), default=True), sg.Checkbox('Verbose', size=(20, 1))],
- [sg.Checkbox('Cluster', size=(12, 1)), sg.Checkbox('Flush Output', size=(20, 1), default=True)],
- [sg.Checkbox('Write Results', size=(12, 1)), sg.Checkbox('Keep Intermediate Data', size=(20, 1))],]
+ flags = [[sg.CB('Normalize', size=(12, 1), default=True), sg.CB('Verbose', size=(20, 1))],
+ [sg.CB('Cluster', size=(12, 1)), sg.CB(
+ 'Flush Output', size=(20, 1), default=True)],
+ [sg.CB('Write Results', size=(12, 1)), sg.CB(
+ 'Keep Intermediate Data', size=(20, 1))],
+ [sg.CB('Normalize', size=(12, 1), default=True),
+ sg.CB('Verbose', size=(20, 1))],
+ [sg.CB('Cluster', size=(12, 1)), sg.CB(
+ 'Flush Output', size=(20, 1), default=True)],
+ [sg.CB('Write Results', size=(12, 1)), sg.CB('Keep Intermediate Data', size=(20, 1))], ]
- loss_functions = [[sg.Radio('Cross-Entropy', 'loss', size=(12, 1)), sg.Radio('Logistic', 'loss', default=True, size=(12, 1))],
- [sg.Radio('Hinge', 'loss', size=(12, 1)), sg.Radio('Huber', 'loss', size=(12, 1))],
- [sg.Radio('Kullerback', 'loss', size=(12, 1)), sg.Radio('MAE(L1)', 'loss', size=(12, 1))],
- [sg.Radio('MSE(L2)', 'loss', size=(12, 1)), sg.Radio('MB(L0)', 'loss', size=(12, 1))],]
+ loss_functions = [[sg.Rad('Cross-Entropy', 'loss', size=(12, 1)), sg.Rad('Logistic', 'loss', default=True, size=(12, 1))],
+ [sg.Rad('Hinge', 'loss', size=(12, 1)),
+ sg.Rad('Huber', 'loss', size=(12, 1))],
+ [sg.Rad('Kullerback', 'loss', size=(12, 1)),
+ sg.Rad('MAE(L1)', 'loss', size=(12, 1))],
+ [sg.Rad('MSE(L2)', 'loss', size=(12, 1)), sg.Rad('MB(L0)', 'loss', size=(12, 1))], ]
command_line_parms = [[sg.Text('Passes', size=(8, 1)), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1)),
- sg.Text('Steps', size=(8, 1), pad=((7,3))), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1))],
- [sg.Text('ooa', size=(8, 1)), sg.In(default_text='6', size=(8, 1)), sg.Text('nn', size=(8, 1)),
- sg.In(default_text='10', size=(10, 1))],
- [sg.Text('q', size=(8, 1)), sg.In(default_text='ff', size=(8, 1)), sg.Text('ngram', size=(8, 1)),
- sg.In(default_text='5', size=(10, 1))],
- [sg.Text('l', size=(8, 1)), sg.In(default_text='0.4', size=(8, 1)), sg.Text('Layers', size=(8, 1)),
- sg.Drop(values=('BatchNorm', 'other'), auto_size_text=True)],]
+ sg.Text('Steps', size=(8, 1), pad=((7, 3))), sg.Spin(values=[i for i in range(1, 1000)], initial_value=20, size=(6, 1))],
+ [sg.Text('ooa', size=(8, 1)), sg.Input(default_text='6', size=(8, 1)), sg.Text('nn', size=(8, 1)),
+ sg.Input(default_text='10', size=(10, 1))],
+ [sg.Text('q', size=(8, 1)), sg.Input(default_text='ff', size=(8, 1)), sg.Text('ngram', size=(8, 1)),
+ sg.Input(default_text='5', size=(10, 1))],
+ [sg.Text('l', size=(8, 1)), sg.Input(default_text='0.4', size=(8, 1)), sg.Text('Layers', size=(8, 1)),
+ sg.Drop(values=('BatchNorm', 'other'))], ]
layout = [[sg.Frame('Command Line Parameteres', command_line_parms, title_color='green', font='Any 12')],
[sg.Frame('Flags', flags, font='Any 12', title_color='blue')],
- [sg.Frame('Loss Functions', loss_functions, font='Any 12', title_color='red')],
+ [sg.Frame('Loss Functions', loss_functions,
+ font='Any 12', title_color='red')],
[sg.Submit(), sg.Cancel()]]
- window = sg.Window('Machine Learning Front End', font=("Helvetica", 12)).Layout(layout)
- button, values = window.Read()
- sg.SetOptions(text_justification='left')
+ sg.set_options(text_justification='left')
+ window = sg.Window('Machine Learning Front End',
+ layout, font=("Helvetica", 12))
+ button, values = window.read()
+ window.close()
print(button, values)
+
def CustomMeter():
# layout the form
layout = [[sg.Text('A custom progress meter')],
- [sg.ProgressBar(1000, orientation='h', size=(20,20), key='progress')],
+ [sg.ProgressBar(1000, orientation='h',
+ size=(20, 20), key='progress')],
[sg.Cancel()]]
# create the form`
- window = sg.Window('Custom Progress Meter').Layout(layout)
- progress_bar = window.FindElement('progress')
+ window = sg.Window('Custom Progress Meter', layout)
+ progress_bar = window['progress']
# loop that would normally do something useful
for i in range(1000):
# check to see if the cancel button was clicked and exit loop if clicked
- event, values = window.Read(timeout=0, timeout_key='timeout')
+ event, values = window.read(timeout=0, timeout_key='timeout')
if event == 'Cancel' or event == None:
break
# update bar with loop value +1 so that bar eventually reaches the maximum
- progress_bar.UpdateBar(i+1)
+ progress_bar.update_bar(i+1)
# done with loop... need to destroy the window as it's still open
window.CloseNonBlocking()
+
if __name__ == '__main__':
CustomMeter()
MachineLearningGUI()
diff --git a/DemoPrograms/Demo_Matplotlib.py b/DemoPrograms/Demo_Matplotlib.py
index 63164465..0f76faa3 100644
--- a/DemoPrograms/Demo_Matplotlib.py
+++ b/DemoPrograms/Demo_Matplotlib.py
@@ -1,11 +1,12 @@
#!/usr/bin/env python
+from matplotlib.ticker import NullFormatter # useful for `logit` scale
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import PySimpleGUI as sg
-
import matplotlib
matplotlib.use('TkAgg')
-from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
-
"""
Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
@@ -21,12 +22,7 @@ Basic steps are:
"""
-#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
-
-import numpy as np
-import matplotlib.pyplot as plt
-
-from matplotlib.ticker import NullFormatter # useful for `logit` scale
+# ------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
# Fixing random state for reproducibility
np.random.seed(19680801)
@@ -75,9 +71,9 @@ plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25,
fig = plt.gcf() # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
-#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
+# ------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
-#------------------------------- Beginning of Matplotlib helper code -----------------------
+# ------------------------------- Beginning of Matplotlib helper code -----------------------
def draw_figure(canvas, figure, loc=(0, 0)):
@@ -85,7 +81,8 @@ def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
-#------------------------------- Beginning of GUI CODE -------------------------------
+# ------------------------------- Beginning of GUI CODE -------------------------------
+
# define the window layout
layout = [[sg.Text('Plot test', font='Any 18')],
@@ -93,9 +90,12 @@ layout = [[sg.Text('Plot test', font='Any 18')],
[sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
# create the form and show it without the plot
-window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
+ layout, finalize=True)
# add the plot to the window
fig_canvas_agg = draw_figure(window['canvas'].TKCanvas, fig)
event, values = window.read()
+
+window.close()
diff --git a/DemoPrograms/Demo_Matplotlib_Animated.py b/DemoPrograms/Demo_Matplotlib_Animated.py
index da28c03c..9311613b 100644
--- a/DemoPrograms/Demo_Matplotlib_Animated.py
+++ b/DemoPrograms/Demo_Matplotlib_Animated.py
@@ -1,10 +1,11 @@
#!/usr/bin/env python
import PySimpleGUI as sg
-
from random import randint
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg
from matplotlib.figure import Figure
+# Yet another usage of MatPlotLib with animations.
+
def draw_figure(canvas, figure, loc=(0, 0)):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
@@ -15,19 +16,23 @@ def main():
NUM_DATAPOINTS = 10000
# define the form layout
- layout = [[sg.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')],
+ layout = [[sg.Text('Animated Matplotlib', size=(40, 1),
+ justification='center', font='Helvetica 20')],
[sg.Canvas(size=(640, 480), key='-CANVAS-')],
[sg.Text('Progress through the data')],
- [sg.Slider(range=(0, NUM_DATAPOINTS), size=(60, 10), orientation='h', key='-SLIDER-')],
+ [sg.Slider(range=(0, NUM_DATAPOINTS), size=(60, 10),
+ orientation='h', key='-SLIDER-')],
[sg.Text('Number of data points to display on screen')],
- [sg.Slider(range=(10, 500), default_value=40, size=(40, 10), orientation='h', key='-SLIDER-DATAPOINTS-')],
+ [sg.Slider(range=(10, 500), default_value=40, size=(40, 10),
+ orientation='h', key='-SLIDER-DATAPOINTS-')],
[sg.Button('Exit', size=(10, 1), pad=((280, 0), 3), font='Helvetica 14')]]
# create the form and show it without the plot
- window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
+ window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
+ layout, finalize=True)
- canvas_elem = window.FindElement('-CANVAS-')
- slider_elem = window.FindElement('-SLIDER-')
+ canvas_elem = window['-CANVAS-']
+ slider_elem = window['-SLIDER-']
canvas = canvas_elem.TKCanvas
# draw the initial plot in the window
@@ -41,16 +46,18 @@ def main():
dpts = [randint(0, 10) for x in range(NUM_DATAPOINTS)]
for i in range(len(dpts)):
- event, values = window.Read(timeout=10)
+
+ event, values = window.read(timeout=10)
if event in ('Exit', None):
exit(69)
- slider_elem.Update(i) # slider shows "progress" through the data points
+ slider_elem.update(i) # slider shows "progress" through the data points
ax.cla() # clear the subplot
ax.grid() # draw the grid
data_points = int(values['-SLIDER-DATAPOINTS-']) # draw this many data points (on next line)
ax.plot(range(data_points), dpts[i:i+data_points], color='purple')
fig_agg.draw()
+ window.close()
if __name__ == '__main__':
main()
diff --git a/DemoPrograms/Demo_Matplotlib_Animated_Scatter.py b/DemoPrograms/Demo_Matplotlib_Animated_Scatter.py
index 21df8cdf..e1dcd081 100644
--- a/DemoPrograms/Demo_Matplotlib_Animated_Scatter.py
+++ b/DemoPrograms/Demo_Matplotlib_Animated_Scatter.py
@@ -1,6 +1,4 @@
#!/usr/bin/env python
-import PySimpleGUI as sg
-
import PySimpleGUI as sg
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
@@ -12,9 +10,6 @@ def draw_figure(canvas, figure):
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
-
-
-
def main():
# define the form layout
layout = [[sg.Text('Animated Matplotlib', size=(40, 1), justification='center', font='Helvetica 20')],
@@ -24,7 +19,7 @@ def main():
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
- canvas_elem = window.FindElement('-CANVAS-')
+ canvas_elem = window['-CANVAS-']
canvas = canvas_elem.TKCanvas
# draw the intitial scatter plot
fig, ax = plt.subplots()
@@ -32,7 +27,7 @@ def main():
fig_agg = draw_figure(canvas, fig)
while True:
- event, values = window.Read(timeout=10)
+ event, values = window.read(timeout=10)
if event in ('Exit', None):
exit(69)
@@ -45,6 +40,7 @@ def main():
ax.scatter(x, y, c=color, s=scale, label=color, alpha=0.3, edgecolors='none')
ax.legend()
fig_agg.draw()
+ window.close()
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Matplotlib_Browser.py b/DemoPrograms/Demo_Matplotlib_Browser.py
index a05242b0..193e1f7b 100644
--- a/DemoPrograms/Demo_Matplotlib_Browser.py
+++ b/DemoPrograms/Demo_Matplotlib_Browser.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python
import PySimpleGUI as sg
import matplotlib
-matplotlib.use('TkAgg')
import inspect
+matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
@@ -846,17 +846,17 @@ fig_dict = {'Pyplot Simple':PyplotSimple, 'Pyplot Formatstr':PyplotFormatstr,'Py
'Pyplot Scatter With Legend' :PyplotScatterWithLegend, 'Artist Customized Box Plots' : PyplotArtistBoxPlots,
'Artist Customized Box Plots 2' : ArtistBoxplot2, 'Pyplot Histogram' : PyplotHistogram}
-sg.ChangeLookAndFeel('LightGreen')
+sg.change_look_and_feel('LightGreen')
figure_w, figure_h = 650, 650
# define the form layout
listbox_values = list(fig_dict)
col_listbox = [[sg.Listbox(values=listbox_values, enable_events=True, size=(28, len(listbox_values)), key='-LISTBOX-')],
- [sg.T(' ' * 12), sg.Exit(size=(5, 2))]]
+ [sg.Text(' ' * 12), sg.Exit(size=(5, 2))]]
layout = [[sg.Text('Matplotlib Plot Test', font=('current 18'))],
- [sg.Column(col_listbox, pad=(5, (3, 330))), sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-') ,
- sg.Multiline(size=(70, 35), pad=(5, (3, 90)), key='-MULTILINE-')],]
+ [sg.Col(col_listbox, pad=(5, (3, 330))), sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-') ,
+ sg.MLine(size=(70, 35), pad=(5, (3, 90)), key='-MULTILINE-')],]
# create the form and show it without the plot
window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', layout, grab_anywhere=False, finalize=True)
@@ -872,7 +872,7 @@ while True:
delete_figure_agg(figure_agg)
choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
func = fig_dict[choice] # get function to call from the dictionary
- window['-MULTILINE-'].Update(inspect.getsource(func)) # show source code to function in multiline
+ window['-MULTILINE-'].update(inspect.getsource(func)) # show source code to function in multiline
fig = func() # call function to get the figure
figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Matplotlib_Browser_Paned.py b/DemoPrograms/Demo_Matplotlib_Browser_Paned.py
index 3a61374d..704046de 100644
--- a/DemoPrograms/Demo_Matplotlib_Browser_Paned.py
+++ b/DemoPrograms/Demo_Matplotlib_Browser_Paned.py
@@ -1,12 +1,12 @@
#!/usr/bin/env python
#!/usr/bin/env python
+from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
+import matplotlib.pyplot as plt
+import numpy as np
+import inspect
import PySimpleGUI as sg
import matplotlib
matplotlib.use('TkAgg')
-import inspect
-import numpy as np
-import matplotlib.pyplot as plt
-from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
"""
Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
@@ -20,11 +20,6 @@ Basic steps are:
"""
-
-import numpy as np
-import matplotlib.pyplot as plt
-
-
def PyplotSimple():
import numpy as np
import matplotlib.pyplot as plt
@@ -38,6 +33,7 @@ def PyplotSimple():
fig = plt.gcf() # get the figure to show
return fig
+
def PyplotHistogram():
"""
=============================================================
@@ -87,6 +83,7 @@ def PyplotHistogram():
fig.tight_layout()
return fig
+
def PyplotArtistBoxPlots():
"""
=========================================
@@ -143,6 +140,7 @@ def PyplotArtistBoxPlots():
fig.subplots_adjust(hspace=0.4)
return fig
+
def ArtistBoxplot2():
# fake data
@@ -189,6 +187,7 @@ def ArtistBoxplot2():
fig.subplots_adjust(hspace=0.4)
return fig
+
def PyplotScatterWithLegend():
import matplotlib.pyplot as plt
from numpy.random import rand
@@ -205,6 +204,7 @@ def PyplotScatterWithLegend():
ax.grid(True)
return fig
+
def PyplotLineStyles():
"""
==========
@@ -258,6 +258,7 @@ def PyplotLineStyles():
plt.tight_layout()
return plt.gcf()
+
def PyplotLinePolyCollection():
import matplotlib.pyplot as plt
from matplotlib import collections, colors, transforms
@@ -361,6 +362,7 @@ def PyplotLinePolyCollection():
ax4.set_ylim(ax4.get_ylim()[::-1])
return fig
+
def PyplotGGPlotSytleSheet():
import numpy as np
import matplotlib.pyplot as plt
@@ -405,6 +407,7 @@ def PyplotGGPlotSytleSheet():
fig = plt.gcf() # get the figure to show
return fig
+
def PyplotBoxPlot():
import numpy as np
import matplotlib.pyplot as plt
@@ -423,6 +426,7 @@ def PyplotBoxPlot():
ax1.boxplot(data)
return fig1
+
def PyplotRadarChart():
import numpy as np
@@ -609,6 +613,7 @@ def PyplotRadarChart():
size='large')
return fig
+
def DifferentScales():
import numpy as np
import matplotlib.pyplot as plt
@@ -629,13 +634,15 @@ def DifferentScales():
ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis
color = 'tab:blue'
- ax2.set_ylabel('sin', color=color) # we already handled the x-label with ax1
+ # we already handled the x-label with ax1
+ ax2.set_ylabel('sin', color=color)
ax2.plot(t, data2, color=color)
ax2.tick_params(axis='y', labelcolor=color)
fig.tight_layout() # otherwise the right y-label is slightly clipped
return fig
+
def ExploringNormalizations():
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
@@ -662,6 +669,7 @@ def ExploringNormalizations():
fig.tight_layout()
return fig
+
def PyplotFormatstr():
def f(t):
@@ -679,6 +687,7 @@ def PyplotFormatstr():
fig = plt.gcf() # get the figure to show
return fig
+
def UnicodeMinus():
import numpy as np
import matplotlib
@@ -693,6 +702,7 @@ def UnicodeMinus():
ax.set_title('Using hyphen instead of Unicode minus')
return fig
+
def Subplot3d():
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm
@@ -723,6 +733,7 @@ def Subplot3d():
ax.plot_wireframe(X, Y, Z, rstride=10, cstride=10)
return fig
+
def PyplotScales():
import numpy as np
import matplotlib.pyplot as plt
@@ -823,12 +834,15 @@ def AxesGrid():
return plt.gcf()
# The magic function that makes it possible.... glues together tkinter and pyplot using Canvas Widget
+
+
def draw_figure(canvas, figure):
figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
figure_canvas_agg.draw()
figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
return figure_canvas_agg
+
def delete_figure_agg(figure_agg):
figure_agg.get_tk_widget().forget()
plt.close('all')
@@ -842,48 +856,52 @@ def delete_figure_agg(figure_agg):
# print(inspect.getsource(PyplotSimple))
-fig_dict = {'Pyplot Simple':PyplotSimple, 'Pyplot Formatstr':PyplotFormatstr,'PyPlot Three':Subplot3d,
- 'Unicode Minus': UnicodeMinus, 'Pyplot Scales' : PyplotScales, 'Axes Grid' : AxesGrid,
- 'Exploring Normalizations' : ExploringNormalizations, 'Different Scales' : DifferentScales,
- 'Pyplot Box Plot' : PyplotBoxPlot, 'Pyplot ggplot Style Sheet' : PyplotGGPlotSytleSheet,
- 'Pyplot Line Poly Collection' : PyplotLinePolyCollection, 'Pyplot Line Styles' : PyplotLineStyles,
- 'Pyplot Scatter With Legend' :PyplotScatterWithLegend, 'Artist Customized Box Plots' : PyplotArtistBoxPlots,
- 'Artist Customized Box Plots 2' : ArtistBoxplot2, 'Pyplot Histogram' : PyplotHistogram}
+fig_dict = {'Pyplot Simple': PyplotSimple, 'Pyplot Formatstr': PyplotFormatstr, 'PyPlot Three': Subplot3d,
+ 'Unicode Minus': UnicodeMinus, 'Pyplot Scales': PyplotScales, 'Axes Grid': AxesGrid,
+ 'Exploring Normalizations': ExploringNormalizations, 'Different Scales': DifferentScales,
+ 'Pyplot Box Plot': PyplotBoxPlot, 'Pyplot ggplot Style Sheet': PyplotGGPlotSytleSheet,
+ 'Pyplot Line Poly Collection': PyplotLinePolyCollection, 'Pyplot Line Styles': PyplotLineStyles,
+ 'Pyplot Scatter With Legend': PyplotScatterWithLegend, 'Artist Customized Box Plots': PyplotArtistBoxPlots,
+ 'Artist Customized Box Plots 2': ArtistBoxplot2, 'Pyplot Histogram': PyplotHistogram}
-sg.ChangeLookAndFeel('LightGreen')
+sg.change_look_and_feel('LightGreen')
figure_w, figure_h = 650, 650
# define the form layout
listbox_values = list(fig_dict)
col_listbox = [[sg.Listbox(values=listbox_values, change_submits=True, size=(28, len(listbox_values)), key='-LISTBOX-')],
- [sg.T(' ' * 12), sg.Exit(size=(5, 2))]]
+ [sg.Text(' ' * 12), sg.Exit(size=(5, 2))]]
-col_multiline = sg.Column([[sg.Multiline(size=(70, 35), key='-MULTILINE-')]])
-col_canvas = sg.Column([[ sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-')]])
-col_instructions = sg.Column([[sg.Pane([col_canvas, col_multiline], size=(800,600))],
- [sg.Text('Grab square above and slide upwards to view source code for graph')]])
+col_multiline = sg.Col([[sg.MLine(size=(70, 35), key='-MULTILINE-')]])
+col_canvas = sg.Col([[sg.Canvas(size=(figure_w, figure_h), key='-CANVAS-')]])
+col_instructions = sg.Col([[sg.Pane([col_canvas, col_multiline], size=(800, 600))],
+ [sg.Text('Grab square above and slide upwards to view source code for graph')]])
layout = [[sg.Text('Matplotlib Plot Test', font=('ANY 18'))],
- [sg.Column(col_listbox), col_instructions],]
+ [sg.Col(col_listbox), col_instructions], ]
# create the form and show it without the plot
-window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',layout, resizable=True, finalize=True)
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
+ layout, resizable=True, finalize=True)
-canvas_elem = window.FindElement('-CANVAS-')
-multiline_elem= window.FindElement('-MULTILINE-')
+canvas_elem = window['-CANVAS-']
+multiline_elem = window['-MULTILINE-']
figure_agg = None
while True:
- event, values = window.Read()
+ event, values = window.read()
if event in (None, 'Exit'):
break
if figure_agg:
# ** IMPORTANT ** Clean up previous drawing before drawing again
delete_figure_agg(figure_agg)
- choice = values['-LISTBOX-'][0] # get first listbox item chosen (returned as a list)
- func = fig_dict[choice] # get function to call from the dictionary
- window['-MULTILINE-'].Update(inspect.getsource(func)) # show source code to function in multiline
+ # get first listbox item chosen (returned as a list)
+ choice = values['-LISTBOX-'][0]
+ # get function to call from the dictionary
+ func = fig_dict[choice]
+ # show source code to function in multiline
+ window['-MULTILINE-'].update(inspect.getsource(func))
fig = func() # call function to get the figure
- figure_agg = draw_figure(window['-CANVAS-'].TKCanvas, fig) # draw the figure
-
+ figure_agg = draw_figure(
+ window['-CANVAS-'].TKCanvas, fig) # draw the figure
diff --git a/DemoPrograms/Demo_Matplotlib_Ping_Graph.py b/DemoPrograms/Demo_Matplotlib_Ping_Graph.py
index b5687502..457464c9 100644
--- a/DemoPrograms/Demo_Matplotlib_Ping_Graph.py
+++ b/DemoPrograms/Demo_Matplotlib_Ping_Graph.py
@@ -1,15 +1,10 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasAgg
import matplotlib.backends.tkagg as tkagg
+import matplotlib.pyplot as plt
+import PySimpleGUI as sg
import tkinter as tk
-
"""
A graph of time to ping Google.com
Demonstrates Matploylib used in an animated way.
@@ -646,12 +641,15 @@ def main():
global g_my_globals
# define the form layout
- layout = [[ sg.Canvas(size=SIZE, background_color='white',key='canvas') , sg.Button('Exit', pad=(0, (210, 0)))]]
+ layout = [
+ [ sg.Canvas(size=SIZE, background_color='white',key='canvas'),
+ sg.Button('Exit', pad=(0, (210, 0)))]
+ ]
# create the form and show it without the plot
- window = sg.Window('Ping Graph', background_color='white', grab_anywhere=True).Layout(layout).Finalize()
+ window = sg.Window('Ping Graph', layout, background_color='white', grab_anywhere=True, finalize=True)
- canvas_elem = window.FindElement('canvas')
+ canvas_elem = window['canvas']
canvas = canvas_elem.TKCanvas
fig = plt.figure(figsize=(3.1, 2.25), tight_layout={'pad':0})
@@ -662,13 +660,14 @@ def main():
plt.tight_layout()
while True:
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event in ('Exit', None):
- exit(0)
+ break
run_a_ping_and_graph()
photo = draw(fig, canvas)
-
+
+ window.close()
if __name__ == '__main__':
main()
diff --git a/DemoPrograms/Demo_Matplotlib_Ping_Graph_Large.py b/DemoPrograms/Demo_Matplotlib_Ping_Graph_Large.py
index 38f7cf60..c1f85fa7 100644
--- a/DemoPrograms/Demo_Matplotlib_Ping_Graph_Large.py
+++ b/DemoPrograms/Demo_Matplotlib_Ping_Graph_Large.py
@@ -1,35 +1,37 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-import matplotlib.pyplot as plt
-import ping
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, FigureCanvasAgg
import matplotlib.backends.tkagg as tkagg
+import matplotlib.pyplot as plt
+import PySimpleGUI as sg
import tkinter as tk
+import ping
-#================================================================================
+# ================================================================================
# Globals
# These are needed because callback functions are used.
# Need to retain state across calls
-#================================================================================
+# ================================================================================
+
+
class MyGlobals:
axis_pings = None
ping_x_array = []
ping_y_array = []
+
g_my_globals = MyGlobals()
-#================================================================================
+# ================================================================================
# Performs *** PING! ***
-#================================================================================
+# ================================================================================
+
+
def run_a_ping_and_graph():
- global g_my_globals # graphs are global so that can be retained across multiple calls to this callback
+ # graphs are global so that can be retained across multiple calls to this callback
+ global g_my_globals
#===================== Do the ping =====================#
- response = ping.quiet_ping('google.com',timeout=1000)
+ response = ping.quiet_ping('google.com', timeout=1000)
if response[0] == 0:
ping_time = 1000
else:
@@ -46,21 +48,26 @@ def run_a_ping_and_graph():
y_array = g_my_globals.ping_y_array
# ===================== Call graphinc functions =====================#
- g_my_globals.axis_ping.clear() # clear before graphing
- g_my_globals.axis_ping.plot(x_array,y_array) # graph the ping values
+ # clear before graphing
+ g_my_globals.axis_ping.clear()
+ # graph the ping values
+ g_my_globals.axis_ping.plot(x_array, y_array)
-#================================================================================
+# ================================================================================
# Function: Set graph titles and Axis labels
# Sets the text for the subplots
# Have to do this in 2 places... initially when creating and when updating
# So, putting into a function so don't have to duplicate code
-#================================================================================
+# ================================================================================
+
+
def set_chart_labels():
global g_my_globals
g_my_globals.axis_ping.set_xlabel('Time')
g_my_globals.axis_ping.set_ylabel('Ping (ms)')
- g_my_globals.axis_ping.set_title('Current Ping Duration', fontsize = 12)
+ g_my_globals.axis_ping.set_title('Current Ping Duration', fontsize=12)
+
def draw(fig, canvas):
# Magic code that draws the figure onto the Canvas Element's canvas
@@ -73,30 +80,34 @@ def draw(fig, canvas):
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
return photo
-#================================================================================
+# ================================================================================
# Function: MAIN
-#================================================================================
+# ================================================================================
+
+
def main():
global g_my_globals
# define the form layout
- layout = [[sg.Text('Animated Ping', size=(40, 1), justification='center', font='Helvetica 20')],
+ layout = [[sg.Text('Animated Ping', size=(40, 1),
+ justification='center', font='Helvetica 20')],
[sg.Canvas(size=(640, 480), key='canvas')],
[sg.Button('Exit', size=(10, 2), pad=((280, 0), 3), font='Helvetica 14')]]
# create the form and show it without the plot
- window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI').Layout(layout).Finalize()
+ window = sg.Window(
+ 'Demo Application - Embedding Matplotlib In PySimpleGUI', layout, finalize=True)
- canvas_elem = window.FindElement('canvas')
+ canvas_elem = window['canvas']
canvas = canvas_elem.TKCanvas
fig = plt.figure()
- g_my_globals.axis_ping = fig.add_subplot(1,1,1)
+ g_my_globals.axis_ping = fig.add_subplot(1, 1, 1)
set_chart_labels()
plt.tight_layout()
while True:
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event in ('Exit', None):
break
diff --git a/DemoPrograms/Demo_Media_Player.py b/DemoPrograms/Demo_Media_Player.py
index 74101bb0..89814502 100644
--- a/DemoPrograms/Demo_Media_Player.py
+++ b/DemoPrograms/Demo_Media_Player.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
- # import PySimpleGUIQt as sg # portable to QT
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
#
# An Async Demonstration of a media player
@@ -12,42 +7,39 @@ else:
# See how it looks here:
# https://user-images.githubusercontent.com/13696193/43159403-45c9726e-8f50-11e8-9da0-0d272e20c579.jpg
#
+
+
def MediaPlayerGUI():
background = '#F0F0F0'
- # Set the backgrounds the same as the background on the buttons
- sg.SetOptions(background_color=background, element_background_color=background)
- # Images are located in a subfolder in the Demo Media Player.py folder
- image_pause = './ButtonGraphics/Pause.png'
- image_restart = './ButtonGraphics/Restart.png'
- image_next = './ButtonGraphics/Next.png'
- image_exit = './ButtonGraphics/Exit.png'
+ sg.set_options(background_color=background,
+ element_background_color=background)
- # A text element that will be changed to display messages in the GUI
-
- ImageButton = lambda image_filename, key:sg.Button('', button_color=(background,background), image_filename=image_filename, image_size=(50, 50), image_subsample=2, border_width=0, key=key)
+ def ImageButton(title, key):
+ return sg.Button(title, button_color=(background, background),
+ border_width=0, key=key)
# define layout of the rows
- layout= [[sg.Text('Media File Player', font=("Helvetica", 25))],
- [sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')],
- [ImageButton(image_restart, key='Restart Song'), sg.Text(' ' * 2),
- ImageButton(image_pause, key='Pause'),
- sg.Text(' ' * 2),
- ImageButton(image_next, key='Next'),
- sg.Text(' ' * 2),
- sg.Text(' ' * 2),ImageButton(image_exit, key='Exit')],
- ]
+ layout = [[sg.Text('Media File Player', font=("Helvetica", 25))],
+ [sg.Text('', size=(15, 2), font=("Helvetica", 14), key='output')],
+ [ImageButton('restart', key='Restart Song'), sg.Text(' ' * 2),
+ ImageButton('pause', key='Pause'), sg.Text(' ' * 2),
+ ImageButton('next', key='Next'), sg.Text(' ' * 2),
+ sg.Text(' ' * 2), ImageButton('exit', key='Exit')],
+ ]
- # Open a form, note that context manager can't be used generally speaking for async forms
- window = sg.Window('Media File Player', auto_size_text=True, default_element_size=(20, 1),
- font=("Helvetica", 25)).Layout(layout)
- # Our event loop
- while(True):
- event, values = window.Read(timeout=100) # Poll every 100 ms
+ window = sg.Window('Media File Player', layout,
+ default_element_size=(20, 1),
+ font=("Helvetica", 25))
+
+ while True:
+ event, values = window.read(timeout=100)
if event == 'Exit' or event is None:
break
# If a button was pressed, display it on the GUI by updating the text element
if event != sg.TIMEOUT_KEY:
- window.FindElement('output').Update(event)
+ window['output'].update(event)
+
+ window.close()
+
MediaPlayerGUI()
-
diff --git a/DemoPrograms/Demo_Menu_With_Toolbar.py b/DemoPrograms/Demo_Menu_With_Toolbar.py
index 9a9afa94..3ab552aa 100644
--- a/DemoPrograms/Demo_Menu_With_Toolbar.py
+++ b/DemoPrograms/Demo_Menu_With_Toolbar.py
@@ -1,82 +1,73 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
+# Usage of icons as base64 string and toolbar
+
+house64 = 'data:image/png;base64,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'
+timer64 = 'data:image/png;base64,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'
+close64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAE30lEQVRIiZ2VXYgdRRqGn6+quvucM/85iRoTNevMBJFEWY0GFQTBC1HBlaz/jMpoFFfXBdmFvdiLvRIEFRHFGBXMjUQhF/6Bol6sSNaIruCNir/R/Dlx5iRzck736e6qby/6JDlx9CIWFN10Ue/7vW+9X7XcDn8bryWPL2vERkNQQPj9Q72K7F3s7Hxb9bZ98L0bj91jt1y23kxNTxIEGUQ/aTYR6WW9cud/Prx01zf7/7FP5EHXHG7Y6bVTpBPLMSegCWKEEMKvkihgjEWDP+FbEjxTa1bjv9l/CsIKF3ypHhUDSFGACCKC956iKKjV6/hfkCjgUNK0TW1oCA3h+EJk8UUBYFCsQaSyRajArUWLnEONcTrT68nTLtZaEKmmMTiUlsREGy9HO0dgcL1y6lgtZrAsEYFexhwxq2buYfru+1mcOo+828UYg4rgUH7OSkY3zbDq1lkaV1yFP9TqEyy18jiBCMF7DjYmOOu+hxifnCSKItZuvp/F6fPJ05TEwE+dHhN33MfpGy4iFAVjf7qF8etvBV9y1IilBApGIMt6TExOM372JKqKqhLFMdOz93Jk6jx+bHVoztzLyj9eiHqP2Gq7O3UlGAuq1RwYDlUwhoChMdSAz3ZxaEeD8T/fBggaAnGtxpqZWdKFBSbOPLMCCQGJItJPdrHw4lOYRgNsBM6dSCDGErIuodtGkhoyPEr68U5svcbI1ZsQY0CV2vAw9ZGRKjEiSBTR/fQjDm9/AddcjqoSul182kYHVDhJauRffUH7wD7ilatxzVOwI6PM7XiJLO2x4rob0CgGVTSEKigidD94j/ltW9Dg0b0/4BfmyQ8ewKUdWLZ6wCIB9SXFXJvQ+hLkc6QeEznHf199jY1rpjh1w0ZUFTGm7z18/tSj2Hffor5shKLdhhJCADMcw7IlKRIkAqkJRIa4LPl6d5c/PPJkBd5vpArcArD+ue101l1Md08bFxuIBUlOyOUggUIAVIl94Kv5wKqtz7L+7r/0bRHEmApcFbwnHhljw6tv0b3kEtK5gDWmj/GbfQAWZbdaztjyPOfP3oN6D8GDCO133uDAvx9CyxKsRX1JMjbBBa+8Rnbl5RSpR35RfXUGfVLnYGFBcTfdwLo77yLkPYy14CLa773JngfuoNy7QOh2WPnw09WVkufUm8s598G/s+eT9wmBJZ1m+sVTFNBc4Wi8vJ3v//kAJk7AOhbf3MGezTfjWwuYCcv8s1s58K+/okWOxDGdjz5g7+YZtKRSoL+igCp5FKVntGk48sTTzDWb1C+4mB833wgETD2CELBjEfNbtyAjo4xdcz27N11L6B5GGoZQhN+26KiSoII9LebnJx9BkggzNIQkyfEdItiRQGvbM7S2bQHJMGN1NO8ds2dQhBORYBCjAFEE1kFSw0QxuAiTJCAGce64vz4gviTkOTJcErIMMRbyDIxg7bHTFnc47clcmpdj43VkeBRJEkytgdTqSL2OiRMkSRDroH9t4EtCUaBZhmYpIUurZ9pFfVnuX+w62xfjeq3D3/6vbifXrT1XkzgWdREmipA4RlwMUYRY21cg/X+lJ5gSbIHGOVovCHmOCSX7DrbMx599icIhVI2cA5c5mC1gbGnITm4oqAOr0PoOXs9g51HAGiITyCDByXDp4KuiaoESmP8/YC0Y5GajmEsAAAAASUVORK5CYII='
+psg64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAABlbkiVhkyZikyFjmShjkyhlly1mli5nlylnmS5olyppnC5qmi5rmzBpmDBpmTFqmDFqmTFqmjNrmzFrnDNsmzJsnDJtnTFunjNvnzRsmTRtmzVunTVunjVvnzZwnjlxny5toDFvozJwoTNzpjVwoDRzpDRzqDd4rThyojp0ozl1pTt2pjt3pz11ozt4qDl4qTp6rTl6rzx4qD55qTx5qj97qz17rT58rD98rT9+rz5+sEB3pEF9rkB+r0F+sEF/sT2AtD6AtT6Btk+Aqk2CrEOBs0GBtEKCtEGCtUSBskWBs0SCtEWDtUaEtkWFt0aGt0KFuUCEukOGu0eFuEWHu0eJvEiGuUiHukiIuUiIukmJu0uKvEyKvFCAp1CCqlODrVKCrl2Kr1OEs1KGsFWGsFaIsVaPvFqKsl6NsmKNsWeTuG6YvHadvlySwV6UxF+YxW+bxW6dxnKewHGex3SdwHSfx3egwXGizHmgwHqkxnyjxHio0f/RMf7QMv/TMf/SMv3SNf7UOv7UO//UPP/UPf/UPv/VP//WPP/XPv/VQ//WQv7XQ//WRP/XRf/WR//YRv/YSP/YSf/YSv/ZS//aS//ZTf/aTP7aTf7aTv7bT//cT//bUP/cUP7cUv/cU/7cVf/eVf/fVv/eV/7dWP/eWP/eWf/fWv/fW//fYvzcaf/hW//gXP/gXv/gX//hYP/hYf/hY//iYP/jY//gZf/iZP7iZf/jZv/iZ//lZv/jaf/kav7ka//maf/ma//kbf/lbv7mbP/mbv/mb//id//mcP/ncv/nc//ld//ndv/meP/ocf/ocv/oc//odP/odf/odv/peP/pff/qfY+11ZSzz5G41qC81aW/1P/jgf/qiv/qjv7qoMnZ5szb587d6eDm2+fo1+7v3e/x3vXw1fHx3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJblQd8AAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAABcQAAAXEAEYYRHbAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAABzUlEQVQoU2P4jwNAJZIEuLJA9M2u/iNgAaiEELOgIFPc//+r6puaalvAQmAJdg4pPj4h5oT/2+raWtqaGmAS/PxC/IJAGdbEB7saW5pb20AyQAkFNiFhUSEgkGZNfjizua29u70XJJHr8j+eV0RGVkRMTJb56u2mvt7eSR0gCT2gPsbMGzbi8hJyPDl3OidPnDRlwhagRHbG/zTXe5WSqqqqmpzXb/VMmz5jztSVIDtSWFLvl3Jrampq8ZY8WThj1tx586ZCXFV9t1xRR1tbR6Lw0f6ZC+YvWDAb6tz/xUom+rrGymWPD8xaunjZ0oUgMZBEsYqZqampWsnTY/PWLF+xZhFIHCRRpW5raWFhUPT/3IJ1a9euW/H//5oTYAlDezs7Kwvv//+XbN6wcev6//+3/z8FltDwcrC3N8/7v3rHtu07Nv3/vxVo0CWQhJGPm5ubdf7/TXt279699//JnTA70j38fH19wv//33b00OGj+w6fPXz5KMRVTiH+/gHuFf//7zl+5szZs2fO7YPo+H/FOSIyPMqz5v//g+dAMocvQCX+XwsMjYmNdgSy9p0/d/bgRZAYWOL//4LgoDAwY+++02AaJoEJcEj8/w8A4UqG4COjF7gAAAAASUVORK5CYII='
+cpu64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAFi0lEQVRIiZ1WS2wbRRj+/5l92O6u7WycTW03xLEd6uBUjVO1hfbSByLQEwhxoIhbCwcuvApUiEOFhAQVEiAOvK4gqoojSEhtgUNApaJKG5qkdWpa1JfcZL3teu3sZmeGQ9ZVGhIJ8Z/+2Vn93//45psBWMMqlcoXxWLxEACAaZpju3btOkkIoZRSsnv37hOmaY4BABSLxUOVSuXzteJIq32klMqyLBuqqhoAAKqqGpTSpKIoSUQESZK6OnuKohiKohiUUpkxtvivWCvWBABEOp1+sr+//5V169ZtnJub+6FUKh3Rdf3hVqv1l6Zp5Ww2ezASifQ0Go3fhoaGjsZisYdardaM4zjTiEgAQHQC4j0HkQghAAC4oiiJRCKxBQBIs9m8oOt6iRASa7VaVwEAYrFYP+e85TjOpXg8PiyE4LZtn/F93wYAgogghOD3AYS+UFW1q1AovGoYxp4wGxIEgS2EEIQQCQCAcx7gkslCCB8AwLbt07Va7SPP8xqdWPdmIElSxDTNfZZlncrn828MDg6+VavVPvF9fy4Wi/X19fUdWJHMfSaEYJlMZgwRpVqtdtQwjD31ev2HIAgWpJGRkS8VRTEMw9g9OTm5v7u7+9GpqamXq9XqxwAAmzZt+oBzjpzzYC0QIQRDRJpIJLanUqmdw8PDX1mW9ZPv+5bkOM5FVVVTiURia1i24rruDQCAUqn09sDAwCHGGEdEadnwlgOJZT5BRMIYc5rN5iXP8+ax0y9N04qc84Vt27aduHjx4uuEED46Ovo95xxEOH1ExKWEhQh9DPe4JEl0fn7+14mJiecQUWo2m7MAgNQ0zb3d3d3bhoaGjrTb7Wld1x/p6uoa2bBhw4uyLGsAEFBKKSIi51xQSjFcIiICIQRDAhDXdWue502Vy+X3hRALqqr2SoODg2/KsmzE4/GNlNJ1nPOF9evXPxYEAbiue7lWq72rKIphmub+GzdufBeNRg1d14cZYx4hhBJClFQqNRbOQlBKo8lkcms+n48vLi5a0vj4+OOKoiTT6fQzjuNcJYRIQRCALMswOzv7LSEk0tPT85TjOBeCIKi12+1rtm3/ruv6FgDgAMB7e3vHgiAAQgh1HOfquXPnXr958+Zx3/dtshopltp7nyEiUtd1rxuG8URfX99B13Un2+32rKIo3ZzztRgMdOfOnT/mcrkX+vv79zcajVOapm3XNC3HGINoNNpnWdZJz/P+TiQSOzRNK6bT6WcjkUh/q9WaQUTIZrMHEFEjhECz2fzL9/2ZkZGRz0zT3JfNZp+WqtXq+5FIJJXL5V5kjLVDdgDnnMVisYFyufxVSFHgnO9gjDFElIvF4jth34ExxgCAIiIyxtq2bZ+5cuXK5wsLC3NSvV4/BQDCsqw/hBBBLpeTO+WF/KdhC0TIHAoAIggCjogYMnjpEBAi27Z96ezZsy90aCoVCoXXVFVNZbPZ/TMzMy9xzr1ljSdhYLHicN0DCkFYWKFnGMamUqn06fXr17/xPG9e0nV9Y6jnWqiAPCydrTm5laxY+pcCABdCcEqprmnag4qiWNLExMTBZWI3Ho/Hd2Qymb1CCBpm+V8AQJZluHPnzum5ubnx8+fPH+iI3apync/nX04mk9vDXihCiMX/K9drXTjJZDK5FRHJ3bt3/9R1/cH/e+Esb0FnkKK3t3ff5s2bv+7p6Rm7devWsXK5/GGhUDjsOM5kNBp9oFKpfKNp2kC9Xv9xdHT0eCaTed513fPhlYmd4CsBOiDQarVmu7q6KpZl/XLt2rVjQggvHo8PTE9PH242m1PpdPrRy5cvf3L79u2fo9GoyRi7U61W3wsDL5fv1V8VjLFF3/ct3/ctAADP86wgCBq+7zcAABljtud5FgCA7/uWLMvWai8KAIB/ACsf4Gh+DNwbAAAAAElFTkSuQmCC'
+camera64 = 'data:image/png;base64,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'
+checkmark64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAD2ElEQVRIibWVe4hUVRzHP+feuTOz82gfrqwu5gM0zTQpS1yfoGQPyc3IjOwlYRElPcDICEP6Q6i/IvojCCIpTA0tSlSqJSvbTLZE0dbWxz4nXWdnxrk7M/feOY/+sHyEa7pt57/z45zP53x/B84R/B/jzeNLgeXADDHE4IeBV2Ihpo6LK1rzNqEhAW9oGw18UhelYeWUGHFL0tx5lqPZyBAI3mi9o8YRm16cWTlsVFLQVwjY2+4SSI2W+j8KXj+ybmwytO69xjo7Wyqzr8sldbaE60ksIdBlhTVo+KuHXppY5azftKzeNsbQkurntOuTKQQobYhFbCgPNsGaA5NDWm94ZlYV7fmAX3pcenIlTucDAqlJRENUxcJQLgwiwfMtYcpq4503JMJjq8M0d+XpyBRJnfUpBpJwyKYqFqbCcSCQg0gQyCeq4qHp90yr5Pd0kY6+ImnXJ1CaeDhEdSJCTSJKzLEHLXhu4oQEuWKZ79uzZAoX2hKPhOn+I6DtuEdfLriC4NE9L4CYhzEP8dH84Hz9kT0NBHLqvMlJmo5nyBQDylITj4RwM5rmw70orcEA0AL8Q/DgN8OBr/DltL8q64G1F52+obomwr6US7boE0hNhRPiVIdHx7H+EvA2sJ0tC3/+e8uFS27c/SS+7ElGrGkbnp5EfV0UArmGxt0Lzq/x5YzKWocz/T4FXyGEINvj0XE410QgJ7Fl4dqL4ecS3PVlJYgdllKzx04ZxqolY8h4mkm315JPl+z+nP8Bd++4hZ2LM/hyuokLCr7Eti28TJnOA5ndGLOUnYtLl+u2YMHnJ4BxY2bWsWj2SA72eoBBG4PnBvy2qwvpq81gVjhJp1Q7q9axLIFVMqSaz3ytfLWEpsbLwgFs6pc1o/R9+e7+eK9joSMWvjR4gSLA4FSGKLS7UyirUmRkbJFTG0VI6N17+oR0/bl8d/+A8HMJAG7bPB7BTmGL8TVz64mMiKGNQSuN0hqvq59CS59Kzq2zo8MrcH/s1V6qMIf9y5uvBL8gALj54xpgG5aYH589klB9BdoYjDY0XJ9k9HURPj2aRZ/ycL/tfouDK17+N/ilAoAbP6wAsRGLB8INI7BGJUAYLGEhLAtLCApfnDymc95NtD4eDMC8ZNiXzNKfSdLbt5K8N6o68nNMwoHqKCAwlkVwKI06ln2MtpWtVwMHBnjspHyNQO1Xe7pRbTmUEchCGbk/laKsdl0tfGBB51OKQM0hUD/ppk7kkTTy11NQku/TuUpdi+DKn/7wdyuAHzDcii0Uykwg/ezJoRMAVL9TCWwFjpJdvfpa4AB/Akx4zQw8GDagAAAAAElFTkSuQmCC'
+cookbook64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsSAAALEgHS3X78AAADMUlEQVQ4jY1UQUgCWxS9782M5hQzle3NTRGEQdBipCKKECoEg4JaJAqCmyBsYauEaNOqjQtXLSKINuFiKMiwIMtF1iLK2kWRZLUoDRxo5s37ixfSt4+/u7xzz51z7r3nwc7Ojt1uBwCEEPwtWKXL5eIvLy+HhoYIIYIgCIJAKa0Do5RyHPfy8nJycnJ1dcUjhJxOZygUOj09zefzFoulDp5SihCKRqPLy8vJZBI4jgOAo6Mjj8cDABjj/6WdTqdDoRAAfJeyFn8MQohhGADAY4xFUSyVSpIkAYBpmgih+soRQmxm2GazbW5u7u7ujoyMKIrCmP+ePMdxv9nhSqXi8/lmZmb29vay2Syrs1gs8EM/QogQQgipBWOMOzs7397eWlpabDYbAMiyHAwGu7u7mQTWzu/3R6PRxsZG+HERvNVqjcVix8fHfX19Nzc3T09PHo+HUjo1NVUulx8fHwFgbW0tEolQSguFwtbWVpU/rlQqs7Ozc3NzqqrmcjmXy9Xe3m61WgcGBubn5wGgo6NjYWEBAEql0t3dHQBUx8ljjNva2orFYnNzM8/zBwcHFoslGo329/cXCgUA6OnpwRh/fHwsLS3lcjm2qm9wQ0NDPB7f398fHBx8eHjIZrOqqhaLRUmSwuFwPB53OBw+ny+dTn9+ftYujed5AEilUhMTE9U9saTX66WUJhKJmv0dHh4Gg0FgF4YxJoQwANNjGIaiKLFYbHp62ul0Li4umqb5H5crSVIymQwEAolEwu12s6SiKNfX15OTkwDgcDguLi4ikUgVUv0zCIJgs9lUVWWlrP3q6qrf72dfAaCrq2tjY0OW5RowTynVNM1qteq6XqW9srJiGAZCSNd1hNDt7W04HGZm+NeFiaKYTCa3t7fHx8fdbjez+9fXV7UR87Cu66Zp1oI1TQsEAl6vN51Os9smhCCEfpbWmMw0TZbBpmm+v7+3traWy2VKKdP825I/M7Isi6IIAFxTU9P6+nomk+nt7X19fX1+fsYY1/ez0+k8Pz+/v7/nMMblcnl4eDifz5+dnWmaVgfGolQq2e32sbGx7wcok8mMjo7C396wVCpFKSWE/ANWXYLwO0+V8wAAAABJRU5ErkJggg=='
+download64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAEl0lEQVRIia2WS2hdZRDHf/Odc+7NvbZpYky1Gk2rEbRa26pt8UWLitYHIqigCD426kIQxCfWhYu68U0RBBERXCq+WrXoorYIFWpbEY0lpk0NTWKtuUlzn+d834yLm5smqQspzurMcM785j+c+eYTTtUcESAzvhIAm/+azHacA4FodszAVNFTrUPm+Q6iecmUyJkEna5OiDCCXCtvJ2cnV+Ep+9R7/VIfQhmeryKeySywok+SSMMKMwqAihDXPIcPDDMURUQCgiPWjJCck6x87ZXXV3cXu3XTO5tkYOvAHbnIfZipTpghLdAMIEngi1cXvtlzwfrHpG0x5ismzsvo0E9D9z7z++M799s2EcSm67OUkAs5cpbzkkoMtPtAAzdXQ9zqjHkt1Ol5SHofx0KWYRUxrdiS3FlLtzz51wd7+v2OQl7qHnPtorUXS3ZxPRUKUT5x4mTDWu559LbCNS+9X9v025Duc4KoYdMAA7A4Mk92EMp/JFIZwR/rx9dL1teVdC2/Qe8yzQg+pS0JvLUzx3hjioPVQamGGlcu47KNq6qrPj+fsd+GeAEYA2SmRQiCNSJKP1Ad3IVaG0nnlWRxKqkkVlYxJxGZwhmFIo34U/fh0Hv4v6YYrY+ihYtkorDUNj+298GPvzv6ZRrkMzA/oyCXh9rEMOOHfiLfcx+5zhXkOnppswxEpJHVxdTjs0CycDHy9XcMlwc5a0E3EoTconOls/dyBsb6lYRLY4m/9T6blDgi8oHw3rPx83fesubl4oVPWFvXBUKoQzqB92Xitpite77n/k/epaN7AZO1CTIROtZ14fJC6ccS9ndGUhRLK0Eum1h2YGpH5eFfD47sjluzcFo+f+vp655F03alNhZhASMjloA1qtzedzab125kiw2QLhHaQ0zIFM2MztUdkBcqx1Lp+0o59NGRP49OVQs0Z3d6nEyMUMP8OGgVtAJaA19CagP4xn4e6DPuPhox1V9HTRFr/h9mRmWkwbJtGSsHK4xXq4cQGQDCDABM0ClEy6DlJiA9DLV90BgktirFzhrPXX0mT6Y9lAaqkAhRItRKGT3bjetTYd2aYM7JYcwm5wwaAP44hDyQYukokg5jliICZoFIoNjZ4Ol1HdhueOPgCLlFjt7twvo63HwztGuipml20lEBBlrGfBXzR5BsDGjOPBrAAkJKRKBwuuepNUXyP5/HN7tKXFGvcuMGY/3qhAO/NLCTJ7kFmIT0OPgjmAhiYKYIASFgGoCUyAILu+o8ckng0jSwsF1YuzxP0hYwm3tizwIIpKPQOIY4BXUYCiiYYWSIKYYHMoRAV1fKTddFxJKQOA/mmW9zFWRjoCmYw6R1lrcg2kxgAfCIeRxKMa+YBSw0Vc7fOScAZuAnMXWYE8yaIUFBDFSbS8sCgscsayZWD3jMAmhT7b8CnDPIeZw6RGTOLmwWFRALMA3BZvkamoBcwM3Zh7MA9Yb5I3v/YKoKTlr9sROKZVrlTGDWsylmkMTGxCQ4h0ObGaT1aRJzHsbtwJJmWSet0/9kIpB69gPbgersJA4oMm/pn6JlQI1/uWX87/YP06p9rkZQnAYAAAAASUVORK5CYII='
+github64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAADAwMDQ0NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhyjGAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABzSURBVChTbYxRFoAgDMPQ+98Z1zbIeJqPbU3RMRfDECqyGpjMg6ivT6NBbKTw5WySq0jKt/sHrXiJ8PwpAAVIgQGkwABSYAApMIAUGEAalFmK9UJ24dC1i7qdj6IO5F+xnxfLu0jS0c7kqxd3Dk+JY8/5AKFrLuM7mfCAAAAAAElFTkSuQmCC'
+run64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAszD0iAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABqSURBVChTpY5JDsAwCMTy/09TMGvFpVF9aAZPRHpkcXC7OIodPg0uCjPq+MwCrWRGKkiIvLyTqzw3aqoI73eqUNAoXBXlg4zudxF+NONfPIVvbSZPgww5oW0Vz8T4Lgbt/xbjia+rahR5AEYEg4vdzh2JAAAAAElFTkSuQmCC'
+storage64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAABwcHDQ0NDg4ODw8PFxcXGRkZGhoaGxsbHh4eIyMjJSUlJiYmJycnKCgoMTExMjIyNTU1NjY2Nzc3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAouNksgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdQAAA3UAe+RuhUAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAC5SURBVChTfZLbDsMgDEPpbb3TDv7/W7PYuAztYUeqhO2QAGowkXIMIeYkaSU4QsNBi4GcyhNINpTglmq4GWSphvy/ldkuLXZ4HmAxy3NmFJaA4guKGCwsjClfV05+fWdhYBtFw+amB292aygW3M7fsPTwjmadZkCvHEtWaAYTViBqVwgTA3tJVnB6D/xhaimItDhjMBvlhtFsaIafnEtOaAY/twAw/eslK70CbX8obUvgJNw9Jv0+Zh8D4s5+VAm/LwAAAABJRU5ErkJggg=='
def ShowMeTheButtons():
# ------ Menu Definition ------ #
- menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit' ]],
- ['&Edit', ['&Paste', ['Special', 'Normal',], 'Undo'],],
- ['&Toolbar', ['---', 'Command &1', 'Command &2', '---', 'Command &3', 'Command &4']],
- ['&Help', '&About...'],]
+ menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit']],
+ ['&Edit', ['&Paste', ['Special', 'Normal', ], 'Undo'], ],
+ ['&Toolbar', ['---', 'Command &1', 'Command &2',
+ '---', 'Command &3', 'Command &4']],
+ ['&Help', '&About...'], ]
- sg.SetOptions(auto_size_buttons=True, margins=(0,0), button_color=sg.COLOR_SYSTEM_DEFAULT)
+ sg.set_options(auto_size_buttons=True,
+ margins=(0, 0),
+ button_color=sg.COLOR_SYSTEM_DEFAULT)
+
+ toolbar_buttons = [[sg.Button('', image_data=close64[22:],button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0,0), key='-close-'),
+ sg.Button('', image_data=timer64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-timer-'),
+ sg.Button('', image_data=house64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-house-'),
+ sg.Button('', image_data=cpu64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-cpu-'),
+ sg.Button('', image_data=camera64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-camera-'),
+ sg.Button('', image_data=checkmark64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-checkmark-'),
+ sg.Button('', image_data=cookbook64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-cookbook-'),
+ sg.Button('', image_data=download64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-download-'),
+ sg.Button('', image_data=github64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-github-'),
+ sg.Button('', image_data=psg64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-psg-'),
+ sg.Button('', image_data=run64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-run-'),
+ sg.Button('', image_data=storage64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='-storage-'),
+ ]]
- toolbar_buttons = [[sg.Button('', image_data=close64[22:],button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0,0), key='_close_'),
- sg.Button('', image_data=timer64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_timer_'),
- sg.Button('', image_data=house64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_house_'),
- sg.Button('', image_data=cpu64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_cpu_'),
- sg.Button('', image_data=camera64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_camera_'),
- sg.Button('', image_data=checkmark64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_checkmark_'),
- sg.Button('', image_data=cookbook64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_cookbook_'),
- sg.Button('', image_data=download64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_download_'),
- sg.Button('', image_data=github64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_github_'),
- sg.Button('', image_data=psg64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_psg_'),
- sg.Button('', image_data=run64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_run_'),
- sg.Button('', image_data=storage64[22:], button_color=('white', sg.COLOR_SYSTEM_DEFAULT), pad=(0, 0), key='_storage_'),
- ]]
# layout = toolbar_buttons
# ------ GUI Defintion ------ #
- layout = [ [sg.Menu(menu_def, )],
- [sg.Frame('', toolbar_buttons,title_color='white', background_color=sg.COLOR_SYSTEM_DEFAULT, pad=(0,0))],
- [sg.Text('', size=(20,8))],
- [sg.Text('Status Bar', relief=sg.RELIEF_SUNKEN, size=(55, 1), pad=(0, 3), key='_status_')]
- ]
+ layout = [[sg.Menu(menu_def, )],
+ [sg.Frame('', toolbar_buttons, title_color='white',
+ background_color=sg.COLOR_SYSTEM_DEFAULT, pad=(0, 0))],
+ [sg.Text('', size=(20, 8))],
+ [sg.Text('Status Bar', relief=sg.RELIEF_SUNKEN,
+ size=(55, 1), pad=(0, 3), key='-status-')]
+ ]
- window = sg.Window('Toolbar').Layout(layout)
+ window = sg.Window('Toolbar', layout)
# ---===--- Loop taking in user input --- #
while True:
- button, value = window.Read()
+ button, value = window.read()
print(button)
- if button in ('_close_', 'Exit') or button is None:
+ if button in ('-close-', 'Exit') or button is None:
break # exit button clicked
- elif button == '_timer_':
+ elif button == '-timer-':
pass # add your call to launch a timer program
- elif button == '_cpu_':
+ elif button == '-cpu-':
pass # add your call to launch a CPU measuring utility
-
if __name__ == '__main__':
- house64 = 'data:image/png;base64,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'
-
- timer64 = 'data:image/png;base64,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'
-
- close64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAE30lEQVRIiZ2VXYgdRRqGn6+quvucM/85iRoTNevMBJFEWY0GFQTBC1HBlaz/jMpoFFfXBdmFvdiLvRIEFRHFGBXMjUQhF/6Bol6sSNaIruCNir/R/Dlx5iRzck736e6qby/6JDlx9CIWFN10Ue/7vW+9X7XcDn8bryWPL2vERkNQQPj9Q72K7F3s7Hxb9bZ98L0bj91jt1y23kxNTxIEGUQ/aTYR6WW9cud/Prx01zf7/7FP5EHXHG7Y6bVTpBPLMSegCWKEEMKvkihgjEWDP+FbEjxTa1bjv9l/CsIKF3ypHhUDSFGACCKC956iKKjV6/hfkCjgUNK0TW1oCA3h+EJk8UUBYFCsQaSyRajArUWLnEONcTrT68nTLtZaEKmmMTiUlsREGy9HO0dgcL1y6lgtZrAsEYFexhwxq2buYfru+1mcOo+828UYg4rgUH7OSkY3zbDq1lkaV1yFP9TqEyy18jiBCMF7DjYmOOu+hxifnCSKItZuvp/F6fPJ05TEwE+dHhN33MfpGy4iFAVjf7qF8etvBV9y1IilBApGIMt6TExOM372JKqKqhLFMdOz93Jk6jx+bHVoztzLyj9eiHqP2Gq7O3UlGAuq1RwYDlUwhoChMdSAz3ZxaEeD8T/fBggaAnGtxpqZWdKFBSbOPLMCCQGJItJPdrHw4lOYRgNsBM6dSCDGErIuodtGkhoyPEr68U5svcbI1ZsQY0CV2vAw9ZGRKjEiSBTR/fQjDm9/AddcjqoSul182kYHVDhJauRffUH7wD7ilatxzVOwI6PM7XiJLO2x4rob0CgGVTSEKigidD94j/ltW9Dg0b0/4BfmyQ8ewKUdWLZ6wCIB9SXFXJvQ+hLkc6QeEznHf199jY1rpjh1w0ZUFTGm7z18/tSj2Hffor5shKLdhhJCADMcw7IlKRIkAqkJRIa4LPl6d5c/PPJkBd5vpArcArD+ue101l1Md08bFxuIBUlOyOUggUIAVIl94Kv5wKqtz7L+7r/0bRHEmApcFbwnHhljw6tv0b3kEtK5gDWmj/GbfQAWZbdaztjyPOfP3oN6D8GDCO133uDAvx9CyxKsRX1JMjbBBa+8Rnbl5RSpR35RfXUGfVLnYGFBcTfdwLo77yLkPYy14CLa773JngfuoNy7QOh2WPnw09WVkufUm8s598G/s+eT9wmBJZ1m+sVTFNBc4Wi8vJ3v//kAJk7AOhbf3MGezTfjWwuYCcv8s1s58K+/okWOxDGdjz5g7+YZtKRSoL+igCp5FKVntGk48sTTzDWb1C+4mB833wgETD2CELBjEfNbtyAjo4xdcz27N11L6B5GGoZQhN+26KiSoII9LebnJx9BkggzNIQkyfEdItiRQGvbM7S2bQHJMGN1NO8ds2dQhBORYBCjAFEE1kFSw0QxuAiTJCAGce64vz4gviTkOTJcErIMMRbyDIxg7bHTFnc47clcmpdj43VkeBRJEkytgdTqSL2OiRMkSRDroH9t4EtCUaBZhmYpIUurZ9pFfVnuX+w62xfjeq3D3/6vbifXrT1XkzgWdREmipA4RlwMUYRY21cg/X+lJ5gSbIHGOVovCHmOCSX7DrbMx599icIhVI2cA5c5mC1gbGnITm4oqAOr0PoOXs9g51HAGiITyCDByXDp4KuiaoESmP8/YC0Y5GajmEsAAAAASUVORK5CYII='
-
- psg64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAABlbkiVhkyZikyFjmShjkyhlly1mli5nlylnmS5olyppnC5qmi5rmzBpmDBpmTFqmDFqmTFqmjNrmzFrnDNsmzJsnDJtnTFunjNvnzRsmTRtmzVunTVunjVvnzZwnjlxny5toDFvozJwoTNzpjVwoDRzpDRzqDd4rThyojp0ozl1pTt2pjt3pz11ozt4qDl4qTp6rTl6rzx4qD55qTx5qj97qz17rT58rD98rT9+rz5+sEB3pEF9rkB+r0F+sEF/sT2AtD6AtT6Btk+Aqk2CrEOBs0GBtEKCtEGCtUSBskWBs0SCtEWDtUaEtkWFt0aGt0KFuUCEukOGu0eFuEWHu0eJvEiGuUiHukiIuUiIukmJu0uKvEyKvFCAp1CCqlODrVKCrl2Kr1OEs1KGsFWGsFaIsVaPvFqKsl6NsmKNsWeTuG6YvHadvlySwV6UxF+YxW+bxW6dxnKewHGex3SdwHSfx3egwXGizHmgwHqkxnyjxHio0f/RMf7QMv/TMf/SMv3SNf7UOv7UO//UPP/UPf/UPv/VP//WPP/XPv/VQ//WQv7XQ//WRP/XRf/WR//YRv/YSP/YSf/YSv/ZS//aS//ZTf/aTP7aTf7aTv7bT//cT//bUP/cUP7cUv/cU/7cVf/eVf/fVv/eV/7dWP/eWP/eWf/fWv/fW//fYvzcaf/hW//gXP/gXv/gX//hYP/hYf/hY//iYP/jY//gZf/iZP7iZf/jZv/iZ//lZv/jaf/kav7ka//maf/ma//kbf/lbv7mbP/mbv/mb//id//mcP/ncv/nc//ld//ndv/meP/ocf/ocv/oc//odP/odf/odv/peP/pff/qfY+11ZSzz5G41qC81aW/1P/jgf/qiv/qjv7qoMnZ5szb587d6eDm2+fo1+7v3e/x3vXw1fHx3gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJblQd8AAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAABcQAAAXEAEYYRHbAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAABzUlEQVQoU2P4jwNAJZIEuLJA9M2u/iNgAaiEELOgIFPc//+r6puaalvAQmAJdg4pPj4h5oT/2+raWtqaGmAS/PxC/IJAGdbEB7saW5pb20AyQAkFNiFhUSEgkGZNfjizua29u70XJJHr8j+eV0RGVkRMTJb56u2mvt7eSR0gCT2gPsbMGzbi8hJyPDl3OidPnDRlwhagRHbG/zTXe5WSqqqqmpzXb/VMmz5jztSVIDtSWFLvl3Jrampq8ZY8WThj1tx586ZCXFV9t1xRR1tbR6Lw0f6ZC+YvWDAb6tz/xUom+rrGymWPD8xaunjZ0oUgMZBEsYqZqampWsnTY/PWLF+xZhFIHCRRpW5raWFhUPT/3IJ1a9euW/H//5oTYAlDezs7Kwvv//+XbN6wcev6//+3/z8FltDwcrC3N8/7v3rHtu07Nv3/vxVo0CWQhJGPm5ubdf7/TXt279699//JnTA70j38fH19wv//33b00OGj+w6fPXz5KMRVTiH+/gHuFf//7zl+5szZs2fO7YPo+H/FOSIyPMqz5v//g+dAMocvQCX+XwsMjYmNdgSy9p0/d/bgRZAYWOL//4LgoDAwY+++02AaJoEJcEj8/w8A4UqG4COjF7gAAAAASUVORK5CYII='
-
- cpu64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAFi0lEQVRIiZ1WS2wbRRj+/5l92O6u7WycTW03xLEd6uBUjVO1hfbSByLQEwhxoIhbCwcuvApUiEOFhAQVEiAOvK4gqoojSEhtgUNApaJKG5qkdWpa1JfcZL3teu3sZmeGQ9ZVGhIJ8Z/+2Vn93//45psBWMMqlcoXxWLxEACAaZpju3btOkkIoZRSsnv37hOmaY4BABSLxUOVSuXzteJIq32klMqyLBuqqhoAAKqqGpTSpKIoSUQESZK6OnuKohiKohiUUpkxtvivWCvWBABEOp1+sr+//5V169ZtnJub+6FUKh3Rdf3hVqv1l6Zp5Ww2ezASifQ0Go3fhoaGjsZisYdardaM4zjTiEgAQHQC4j0HkQghAAC4oiiJRCKxBQBIs9m8oOt6iRASa7VaVwEAYrFYP+e85TjOpXg8PiyE4LZtn/F93wYAgogghOD3AYS+UFW1q1AovGoYxp4wGxIEgS2EEIQQCQCAcx7gkslCCB8AwLbt07Va7SPP8xqdWPdmIElSxDTNfZZlncrn828MDg6+VavVPvF9fy4Wi/X19fUdWJHMfSaEYJlMZgwRpVqtdtQwjD31ev2HIAgWpJGRkS8VRTEMw9g9OTm5v7u7+9GpqamXq9XqxwAAmzZt+oBzjpzzYC0QIQRDRJpIJLanUqmdw8PDX1mW9ZPv+5bkOM5FVVVTiURia1i24rruDQCAUqn09sDAwCHGGEdEadnwlgOJZT5BRMIYc5rN5iXP8+ax0y9N04qc84Vt27aduHjx4uuEED46Ovo95xxEOH1ExKWEhQh9DPe4JEl0fn7+14mJiecQUWo2m7MAgNQ0zb3d3d3bhoaGjrTb7Wld1x/p6uoa2bBhw4uyLGsAEFBKKSIi51xQSjFcIiICIQRDAhDXdWue502Vy+X3hRALqqr2SoODg2/KsmzE4/GNlNJ1nPOF9evXPxYEAbiue7lWq72rKIphmub+GzdufBeNRg1d14cZYx4hhBJClFQqNRbOQlBKo8lkcms+n48vLi5a0vj4+OOKoiTT6fQzjuNcJYRIQRCALMswOzv7LSEk0tPT85TjOBeCIKi12+1rtm3/ruv6FgDgAMB7e3vHgiAAQgh1HOfquXPnXr958+Zx3/dtshopltp7nyEiUtd1rxuG8URfX99B13Un2+32rKIo3ZzztRgMdOfOnT/mcrkX+vv79zcajVOapm3XNC3HGINoNNpnWdZJz/P+TiQSOzRNK6bT6WcjkUh/q9WaQUTIZrMHEFEjhECz2fzL9/2ZkZGRz0zT3JfNZp+WqtXq+5FIJJXL5V5kjLVDdgDnnMVisYFyufxVSFHgnO9gjDFElIvF4jth34ExxgCAIiIyxtq2bZ+5cuXK5wsLC3NSvV4/BQDCsqw/hBBBLpeTO+WF/KdhC0TIHAoAIggCjogYMnjpEBAi27Z96ezZsy90aCoVCoXXVFVNZbPZ/TMzMy9xzr1ljSdhYLHicN0DCkFYWKFnGMamUqn06fXr17/xPG9e0nV9Y6jnWqiAPCydrTm5laxY+pcCABdCcEqprmnag4qiWNLExMTBZWI3Ho/Hd2Qymb1CCBpm+V8AQJZluHPnzum5ubnx8+fPH+iI3apync/nX04mk9vDXihCiMX/K9drXTjJZDK5FRHJ3bt3/9R1/cH/e+Esb0FnkKK3t3ff5s2bv+7p6Rm7devWsXK5/GGhUDjsOM5kNBp9oFKpfKNp2kC9Xv9xdHT0eCaTed513fPhlYmd4CsBOiDQarVmu7q6KpZl/XLt2rVjQggvHo8PTE9PH242m1PpdPrRy5cvf3L79u2fo9GoyRi7U61W3wsDL5fv1V8VjLFF3/ct3/ctAADP86wgCBq+7zcAABljtud5FgCA7/uWLMvWai8KAIB/ACsf4Gh+DNwbAAAAAElFTkSuQmCC'
-
- camera64 = 'data:image/png;base64,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'
-
- checkmark64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAD2ElEQVRIibWVe4hUVRzHP+feuTOz82gfrqwu5gM0zTQpS1yfoGQPyc3IjOwlYRElPcDICEP6Q6i/IvojCCIpTA0tSlSqJSvbTLZE0dbWxz4nXWdnxrk7M/feOY/+sHyEa7pt57/z45zP53x/B84R/B/jzeNLgeXADDHE4IeBV2Ihpo6LK1rzNqEhAW9oGw18UhelYeWUGHFL0tx5lqPZyBAI3mi9o8YRm16cWTlsVFLQVwjY2+4SSI2W+j8KXj+ybmwytO69xjo7Wyqzr8sldbaE60ksIdBlhTVo+KuHXppY5azftKzeNsbQkurntOuTKQQobYhFbCgPNsGaA5NDWm94ZlYV7fmAX3pcenIlTucDAqlJRENUxcJQLgwiwfMtYcpq4503JMJjq8M0d+XpyBRJnfUpBpJwyKYqFqbCcSCQg0gQyCeq4qHp90yr5Pd0kY6+ImnXJ1CaeDhEdSJCTSJKzLEHLXhu4oQEuWKZ79uzZAoX2hKPhOn+I6DtuEdfLriC4NE9L4CYhzEP8dH84Hz9kT0NBHLqvMlJmo5nyBQDylITj4RwM5rmw70orcEA0AL8Q/DgN8OBr/DltL8q64G1F52+obomwr6US7boE0hNhRPiVIdHx7H+EvA2sJ0tC3/+e8uFS27c/SS+7ElGrGkbnp5EfV0UArmGxt0Lzq/x5YzKWocz/T4FXyGEINvj0XE410QgJ7Fl4dqL4ecS3PVlJYgdllKzx04ZxqolY8h4mkm315JPl+z+nP8Bd++4hZ2LM/hyuokLCr7Eti28TJnOA5ndGLOUnYtLl+u2YMHnJ4BxY2bWsWj2SA72eoBBG4PnBvy2qwvpq81gVjhJp1Q7q9axLIFVMqSaz3ytfLWEpsbLwgFs6pc1o/R9+e7+eK9joSMWvjR4gSLA4FSGKLS7UyirUmRkbJFTG0VI6N17+oR0/bl8d/+A8HMJAG7bPB7BTmGL8TVz64mMiKGNQSuN0hqvq59CS59Kzq2zo8MrcH/s1V6qMIf9y5uvBL8gALj54xpgG5aYH589klB9BdoYjDY0XJ9k9HURPj2aRZ/ycL/tfouDK17+N/ilAoAbP6wAsRGLB8INI7BGJUAYLGEhLAtLCApfnDymc95NtD4eDMC8ZNiXzNKfSdLbt5K8N6o68nNMwoHqKCAwlkVwKI06ln2MtpWtVwMHBnjspHyNQO1Xe7pRbTmUEchCGbk/laKsdl0tfGBB51OKQM0hUD/ppk7kkTTy11NQku/TuUpdi+DKn/7wdyuAHzDcii0Uykwg/ezJoRMAVL9TCWwFjpJdvfpa4AB/Akx4zQw8GDagAAAAAElFTkSuQmCC'
-
- cookbook64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAYCAIAAAB1KUohAAAACXBIWXMAAAsSAAALEgHS3X78AAADMUlEQVQ4jY1UQUgCWxS9782M5hQzle3NTRGEQdBipCKKECoEg4JaJAqCmyBsYauEaNOqjQtXLSKINuFiKMiwIMtF1iLK2kWRZLUoDRxo5s37ixfSt4+/u7xzz51z7r3nwc7Ojt1uBwCEEPwtWKXL5eIvLy+HhoYIIYIgCIJAKa0Do5RyHPfy8nJycnJ1dcUjhJxOZygUOj09zefzFoulDp5SihCKRqPLy8vJZBI4jgOAo6Mjj8cDABjj/6WdTqdDoRAAfJeyFn8MQohhGADAY4xFUSyVSpIkAYBpmgih+soRQmxm2GazbW5u7u7ujoyMKIrCmP+ePMdxv9nhSqXi8/lmZmb29vay2Syrs1gs8EM/QogQQgipBWOMOzs7397eWlpabDYbAMiyHAwGu7u7mQTWzu/3R6PRxsZG+HERvNVqjcVix8fHfX19Nzc3T09PHo+HUjo1NVUulx8fHwFgbW0tEolQSguFwtbWVpU/rlQqs7Ozc3NzqqrmcjmXy9Xe3m61WgcGBubn5wGgo6NjYWEBAEql0t3dHQBUx8ljjNva2orFYnNzM8/zBwcHFoslGo329/cXCgUA6OnpwRh/fHwsLS3lcjm2qm9wQ0NDPB7f398fHBx8eHjIZrOqqhaLRUmSwuFwPB53OBw+ny+dTn9+ftYujed5AEilUhMTE9U9saTX66WUJhKJmv0dHh4Gg0FgF4YxJoQwANNjGIaiKLFYbHp62ul0Li4umqb5H5crSVIymQwEAolEwu12s6SiKNfX15OTkwDgcDguLi4ikUgVUv0zCIJgs9lUVWWlrP3q6qrf72dfAaCrq2tjY0OW5RowTynVNM1qteq6XqW9srJiGAZCSNd1hNDt7W04HGZm+NeFiaKYTCa3t7fHx8fdbjez+9fXV7UR87Cu66Zp1oI1TQsEAl6vN51Os9smhCCEfpbWmMw0TZbBpmm+v7+3traWy2VKKdP825I/M7Isi6IIAFxTU9P6+nomk+nt7X19fX1+fsYY1/ez0+k8Pz+/v7/nMMblcnl4eDifz5+dnWmaVgfGolQq2e32sbGx7wcok8mMjo7C396wVCpFKSWE/ANWXYLwO0+V8wAAAABJRU5ErkJggg=='
-
- download64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsSAAALEgHS3X78AAAEl0lEQVRIia2WS2hdZRDHf/Odc+7NvbZpYky1Gk2rEbRa26pt8UWLitYHIqigCD426kIQxCfWhYu68U0RBBERXCq+WrXoorYIFWpbEY0lpk0NTWKtuUlzn+d834yLm5smqQspzurMcM785j+c+eYTTtUcESAzvhIAm/+azHacA4FodszAVNFTrUPm+Q6iecmUyJkEna5OiDCCXCtvJ2cnV+Ep+9R7/VIfQhmeryKeySywok+SSMMKMwqAihDXPIcPDDMURUQCgiPWjJCck6x87ZXXV3cXu3XTO5tkYOvAHbnIfZipTpghLdAMIEngi1cXvtlzwfrHpG0x5ismzsvo0E9D9z7z++M799s2EcSm67OUkAs5cpbzkkoMtPtAAzdXQ9zqjHkt1Ol5SHofx0KWYRUxrdiS3FlLtzz51wd7+v2OQl7qHnPtorUXS3ZxPRUKUT5x4mTDWu559LbCNS+9X9v025Duc4KoYdMAA7A4Mk92EMp/JFIZwR/rx9dL1teVdC2/Qe8yzQg+pS0JvLUzx3hjioPVQamGGlcu47KNq6qrPj+fsd+GeAEYA2SmRQiCNSJKP1Ad3IVaG0nnlWRxKqkkVlYxJxGZwhmFIo34U/fh0Hv4v6YYrY+ihYtkorDUNj+298GPvzv6ZRrkMzA/oyCXh9rEMOOHfiLfcx+5zhXkOnppswxEpJHVxdTjs0CycDHy9XcMlwc5a0E3EoTconOls/dyBsb6lYRLY4m/9T6blDgi8oHw3rPx83fesubl4oVPWFvXBUKoQzqB92Xitpite77n/k/epaN7AZO1CTIROtZ14fJC6ccS9ndGUhRLK0Eum1h2YGpH5eFfD47sjluzcFo+f+vp655F03alNhZhASMjloA1qtzedzab125kiw2QLhHaQ0zIFM2MztUdkBcqx1Lp+0o59NGRP49OVQs0Z3d6nEyMUMP8OGgVtAJaA19CagP4xn4e6DPuPhox1V9HTRFr/h9mRmWkwbJtGSsHK4xXq4cQGQDCDABM0ClEy6DlJiA9DLV90BgktirFzhrPXX0mT6Y9lAaqkAhRItRKGT3bjetTYd2aYM7JYcwm5wwaAP44hDyQYukokg5jliICZoFIoNjZ4Ol1HdhueOPgCLlFjt7twvo63HwztGuipml20lEBBlrGfBXzR5BsDGjOPBrAAkJKRKBwuuepNUXyP5/HN7tKXFGvcuMGY/3qhAO/NLCTJ7kFmIT0OPgjmAhiYKYIASFgGoCUyAILu+o8ckng0jSwsF1YuzxP0hYwm3tizwIIpKPQOIY4BXUYCiiYYWSIKYYHMoRAV1fKTddFxJKQOA/mmW9zFWRjoCmYw6R1lrcg2kxgAfCIeRxKMa+YBSw0Vc7fOScAZuAnMXWYE8yaIUFBDFSbS8sCgscsayZWD3jMAmhT7b8CnDPIeZw6RGTOLmwWFRALMA3BZvkamoBcwM3Zh7MA9Yb5I3v/YKoKTlr9sROKZVrlTGDWsylmkMTGxCQ4h0ObGaT1aRJzHsbtwJJmWSet0/9kIpB69gPbgersJA4oMm/pn6JlQI1/uWX87/YP06p9rkZQnAYAAAAASUVORK5CYII='
-
- github64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAADAwMDQ0NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhyjGAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABzSURBVChTbYxRFoAgDMPQ+98Z1zbIeJqPbU3RMRfDECqyGpjMg6ivT6NBbKTw5WySq0jKt/sHrXiJ8PwpAAVIgQGkwABSYAApMIAUGEAalFmK9UJ24dC1i7qdj6IO5F+xnxfLu0jS0c7kqxd3Dk+JY8/5AKFrLuM7mfCAAAAAAElFTkSuQmCC'
-
-
- run64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAszD0iAAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdUAAA3VAT3WWPEAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAABqSURBVChTpY5JDsAwCMTy/09TMGvFpVF9aAZPRHpkcXC7OIodPg0uCjPq+MwCrWRGKkiIvLyTqzw3aqoI73eqUNAoXBXlg4zudxF+NONfPIVvbSZPgww5oW0Vz8T4Lgbt/xbjia+rahR5AEYEg4vdzh2JAAAAAElFTkSuQmCC'
-
- storage64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAABwcHDQ0NDg4ODw8PFxcXGRkZGhoaGxsbHh4eIyMjJSUlJiYmJycnKCgoMTExMjIyNTU1NjY2Nzc3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAouNksgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADdQAAA3UAe+RuhUAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuMWMqnEsAAAC5SURBVChTfZLbDsMgDEPpbb3TDv7/W7PYuAztYUeqhO2QAGowkXIMIeYkaSU4QsNBi4GcyhNINpTglmq4GWSphvy/ldkuLXZ4HmAxy3NmFJaA4guKGCwsjClfV05+fWdhYBtFw+amB292aygW3M7fsPTwjmadZkCvHEtWaAYTViBqVwgTA3tJVnB6D/xhaimItDhjMBvlhtFsaIafnEtOaAY/twAw/eslK70CbX8obUvgJNw9Jv0+Zh8D4s5+VAm/LwAAAABJRU5ErkJggg=='
-
ShowMeTheButtons()
-
diff --git a/DemoPrograms/Demo_Menus.py b/DemoPrograms/Demo_Menus.py
index 92235224..5d4b6ba8 100644
--- a/DemoPrograms/Demo_Menus.py
+++ b/DemoPrograms/Demo_Menus.py
@@ -6,6 +6,8 @@ import PySimpleGUI as sg
Check out the variable menu_def for a hint on how to
define menus
"""
+
+
def second_window():
layout = [[sg.Text('The second form is small \nHere to show that opening a window using a window works')],
@@ -15,23 +17,24 @@ def second_window():
event, values = window.read()
window.close()
-def test_menus():
+def test_menus():
sg.change_look_and_feel('LightGreen')
sg.set_options(element_padding=(0, 0))
# ------ Menu Definition ------ #
- menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit' ]],
- ['&Edit', ['&Paste', ['Special', 'Normal',], 'Undo'],],
- ['&Toolbar', ['---', 'Command &1', 'Command &2', '---', 'Command &3', 'Command &4']],
- ['&Help', '&About...'],]
+ menu_def = [['&File', ['&Open', '&Save', '&Properties', 'E&xit']],
+ ['&Edit', ['&Paste', ['Special', 'Normal', ], 'Undo'], ],
+ ['&Toolbar', ['---', 'Command &1', 'Command &2',
+ '---', 'Command &3', 'Command &4']],
+ ['&Help', '&About...'], ]
# ------ GUI Defintion ------ #
layout = [
- [sg.Menu(menu_def, tearoff=False, pad=(20,1))],
- [sg.Output(size=(60,20))],
- ]
+ [sg.Menu(menu_def, tearoff=False, pad=(20, 1))],
+ [sg.Output(size=(60, 20))],
+ ]
window = sg.Window("Windows-like program",
layout,
@@ -43,13 +46,14 @@ def test_menus():
# ------ Loop & Process button menu choices ------ #
while True:
event, values = window.read()
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
return
print('Event = ', event)
# ------ Process menu choices ------ #
if event == 'About...':
window.disappear()
- sg.popup('About this program','Version 1.0', 'PySimpleGUI rocks...', grab_anywhere=True)
+ sg.popup('About this program', 'Version 1.0',
+ 'PySimpleGUI rocks...', grab_anywhere=True)
window.reappear()
elif event == 'Open':
filename = sg.popup_get_file('file to open', no_window=True)
@@ -57,4 +61,7 @@ def test_menus():
elif event == 'Properties':
second_window()
-test_menus()
\ No newline at end of file
+ window.close()
+
+
+test_menus()
diff --git a/DemoPrograms/Demo_Multiple_Windows_Experimental.py b/DemoPrograms/Demo_Multiple_Windows_Experimental.py
index a5902d60..e8c20ca1 100644
--- a/DemoPrograms/Demo_Multiple_Windows_Experimental.py
+++ b/DemoPrograms/Demo_Multiple_Windows_Experimental.py
@@ -1,46 +1,50 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
+'''
+ Parallel windows executing.
+'''
layout1 = [[ sg.Text('Window 1') ],
- [sg.Input(do_not_clear=True)],
+ [sg.Input('')],
[ sg.Button('Read')]]
-window1 = sg.Window('My new window', location=(800,500)).Layout(layout1)
+window1 = sg.Window('My new window', layout1, location=(800,500))
layout2 = [[ sg.Text('Window 2') ],
- [sg.Input(do_not_clear=True)],
+ [sg.Input('')],
[ sg.Button('Read')]]
-window2 = sg.Window('My new window', location=(800, 625), return_keyboard_events=True).Layout(layout2)
+window2 = sg.Window('My new window', layout2, location=(800, 625), return_keyboard_events=True)
layout3 = [[ sg.Text('Window 3') ],
[sg.Input(do_not_clear=False)],
[ sg.Button('Read')]]
-window3 = sg.Window('My new window', location=(800,750), return_keyboard_events=True).Layout(layout3)
+window3 = sg.Window('My new window', layout3, location=(800,750), return_keyboard_events=True)
while True: # Event Loop
- event, values = window1.Read(timeout=50)
+ event, values = window1.read(timeout=0)
if event is None:
break
elif event != '__timeout__':
print(event, values)
- event, values = window2.Read(timeout=0)
+ event, values = window2.read(timeout=0)
if event is None:
break
elif event != '__timeout__':
print(event, values)
- event, values = window3.Read(timeout=0)
+ event, values = window3.read(timeout=0)
if event is None:
break
elif event != '__timeout__':
print(event, values)
+
+window1.close()
+window2.close()
+window3.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Multithreaded_Logging.py b/DemoPrograms/Demo_Multithreaded_Logging.py
index b5f0deb6..9c71f4e9 100644
--- a/DemoPrograms/Demo_Multithreaded_Logging.py
+++ b/DemoPrograms/Demo_Multithreaded_Logging.py
@@ -1,9 +1,4 @@
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUIQt as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import queue
import logging
import threading
@@ -49,15 +44,18 @@ class QueueHandler(logging.Handler):
def main():
- window = sg.FlexForm('Log window', default_element_size=(30, 2), font=('Helvetica', ' 10'), default_button_element_size=(8, 2), return_keyboard_events=True)
- layout = \
- [
- [sg.Multiline(size=(50, 15), key='Log')],
- [sg.Button('Start', bind_return_key=True, key='_START_'), sg.Button('Exit')]
+ layout = [
+ [sg.MLine(size=(50, 15), key='Log')],
+ [sg.Button('Start', bind_return_key=True, key='-START-'), sg.Button('Exit')]
]
- window.Layout(layout).Read(timeout=0)
+ window = sg.FlexForm('Log window', layout,
+ default_element_size=(30, 2),
+ font=('Helvetica', ' 10'),
+ default_button_element_size=(8, 2),
+ return_keyboard_events=True)
+ window.read(timeout=0)
appStarted = False
# Setup logging and start app
@@ -70,13 +68,13 @@ def main():
# Loop taking in user input and querying queue
while True:
# Wake every 100ms and look for work
- event, values = window.Read(timeout=100)
+ event, values = window.read(timeout=100)
- if event == '_START_':
+ if event == '-START-':
if appStarted is False:
threadedApp.start()
logger.debug('App started')
- window.FindElement('_START_').Update(disabled=True)
+ window['-START-'].update(disabled=True)
appStarted = True
elif event in (None, 'Exit'):
break
@@ -88,10 +86,9 @@ def main():
pass
else:
msg = queue_handler.format(record)
- window.FindElement('Log').Update(msg+'\n', append=True)
+ window['Log'].update(msg+'\n', append=True)
- window.Close()
- exit()
+ window.close()
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Multithreaded_Long_Tasks.py b/DemoPrograms/Demo_Multithreaded_Long_Tasks.py
index f74bee00..ca5bf0a4 100644
--- a/DemoPrograms/Demo_Multithreaded_Long_Tasks.py
+++ b/DemoPrograms/Demo_Multithreaded_Long_Tasks.py
@@ -1,15 +1,11 @@
#!/usr/bin/python3
-
import queue
import threading
import time
+import PySimpleGUI as sg
# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
# To try something other than tkinter version, just comment out the first import and uncomment the one you want
-import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
-# import PySimpleGUIWx as sg
-# import PySimpleGUIWeb as sg
"""
DESIGN PATTERN - Multithreaded Long Tasks GUI
@@ -41,14 +37,6 @@ def long_operation_thread(seconds, gui_queue):
gui_queue.put('** Done **') # put a message into queue for GUI
-###### ## ## ####
-## ## ## ## ##
-## ## ## ##
-## #### ## ## ##
-## ## ## ## ##
-## ## ## ## ##
-###### ####### ####
-
def the_gui():
"""
Starts and executes the GUI
@@ -60,23 +48,27 @@ def the_gui():
layout = [[sg.Text('Long task to perform example')],
[sg.Output(size=(70, 12))],
- [sg.Text('Number of seconds your task will take'),sg.Input(key='_SECONDS_', size=(5,1)), sg.Button('Do Long Task', bind_return_key=True)],
+ [sg.Text('Number of seconds your task will take'),
+ sg.Input(key='-SECONDS-', size=(5, 1)),
+ sg.Button('Do Long Task', bind_return_key=True)],
[sg.Button('Click Me'), sg.Button('Exit')], ]
- window = sg.Window('Multithreaded Window').Layout(layout)
+ window = sg.Window('Multithreaded Window', layout)
# --------------------- EVENT LOOP ---------------------
while True:
- event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
- if event is None or event == 'Exit':
+ event, values = window.read(timeout=100)
+ if event in (None, 'Exit'):
break
elif event.startswith('Do'):
try:
- seconds = int(values['_SECONDS_'])
- print('Starting thread to do long work....sending value of {} seconds'.format(seconds))
- threading.Thread(target=long_operation_thread, args=(seconds , gui_queue,), daemon=True).start()
+ seconds = int(values['-SECONDS-'])
+ print('Thread ALIVE! Long work....sending value of {} seconds'.format(seconds))
+ threading.Thread(target=long_operation_thread,
+ args=(seconds, gui_queue,), daemon=True).start()
except Exception as e:
- print('Error starting work thread. Did you input a valid # of seconds? You entered: %s' % values['_SECONDS_'])
+ print('Error starting work thread. Bad seconds input: "%s"' %
+ values['-SECONDS-'])
elif event == 'Click Me':
print('Your GUI is alive and well')
# --------------- Check for incoming messages from threads ---------------
@@ -90,17 +82,8 @@ def the_gui():
print('Got a message back from the thread: ', message)
# if user exits the window, then close the window and exit the GUI func
- window.Close()
-
-
-## ## ### #### ## ##
-### ### ## ## ## ### ##
-#### #### ## ## ## #### ##
-## ### ## ## ## ## ## ## ##
-## ## ######### ## ## ####
-## ## ## ## ## ## ###
-## ## ## ## #### ## ##
+ window.close()
if __name__ == '__main__':
the_gui()
- print('Exiting Program')
\ No newline at end of file
+ print('Exiting Program')
diff --git a/DemoPrograms/Demo_Multithreaded_Queued.py b/DemoPrograms/Demo_Multithreaded_Queued.py
index 5e397925..b4ec9bb8 100644
--- a/DemoPrograms/Demo_Multithreaded_Queued.py
+++ b/DemoPrograms/Demo_Multithreaded_Queued.py
@@ -8,13 +8,7 @@ import queue
import threading
import time
import itertools
-
-# This program has been tested on all flavors of PySimpleGUI and it works with no problems at all
-# To try something other than tkinter version, just comment out the first import and uncomment the one you want
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
-# import PySimpleGUIWx as sg
-# import PySimpleGUIWeb as sg
"""
DESIGN PATTERN - Multithreaded GUI
@@ -33,13 +27,13 @@ import PySimpleGUI as sg
"""
-######## ## ## ######## ######## ### ########
- ## ## ## ## ## ## ## ## ## ##
- ## ## ## ## ## ## ## ## ## ##
- ## ######### ######## ###### ## ## ## ##
- ## ## ## ## ## ## ######### ## ##
- ## ## ## ## ## ## ## ## ## ##
- ## ## ## ## ## ######## ## ## ########
+# ######## ## ## ######## ######## ### ########
+# ## ## ## ## ## ## ## ## ## ##
+# ## ## ## ## ## ## ## ## ## ##
+# ## ######### ######## ###### ## ## ## ##
+# ## ## ## ## ## ## ######### ## ##
+# ## ## ## ## ## ## ## ## ## ##
+# ## ## ## ## ## ######## ## ## ########
def worker_thread(thread_name, run_freq, gui_queue):
"""
@@ -54,15 +48,17 @@ def worker_thread(thread_name, run_freq, gui_queue):
print('Starting thread - {} that runs every {} ms'.format(thread_name, run_freq))
for i in itertools.count(): # loop forever, keeping count in i as it loops
time.sleep(run_freq/1000) # sleep for a while
- gui_queue.put('{} - {}'.format(thread_name, i)) # put a message into queue for GUI
+ # put a message into queue for GUI
+ gui_queue.put('{} - {}'.format(thread_name, i))
+
+# ###### ## ## ####
+# ## ## ## ## ##
+# ## ## ## ##
+# ## #### ## ## ##
+# ## ## ## ## ##
+# ## ## ## ## ##
+# ###### ####### ####
- ###### ## ## ####
-## ## ## ## ##
-## ## ## ##
-## #### ## ## ##
-## ## ## ## ##
-## ## ## ## ##
- ###### ####### ####
def the_gui(gui_queue):
"""
@@ -73,18 +69,19 @@ def the_gui(gui_queue):
:param gui_queue: Queue the GUI should read from
:return:
"""
- layout = [ [sg.Text('Multithreaded Window Example')],
- [sg.Text('', size=(15,1), key='_OUTPUT_')],
- [sg.Output(size=(40,6))],
- [sg.Button('Exit')],]
+ layout = [[sg.Text('Multithreaded Window Example')],
+ [sg.Text('', size=(15, 1), key='-OUTPUT-')],
+ [sg.Output(size=(40, 6))],
+ [sg.Button('Exit')], ]
- window = sg.Window('Multithreaded Window').Layout(layout)
+ window = sg.Window('Multithreaded Window', layout)
# --------------------- EVENT LOOP ---------------------
while True:
- event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
- if event is None or event == 'Exit':
+ # wait for up to 100 ms for a GUI event
+ event, values = window.read(timeout=100)
+ if event in (None, 'Exit'):
break
- #--------------- Loop through all messages coming in from threads ---------------
+ # --------------- Loop through all messages coming in from threads ---------------
while True: # loop executes until runs out of messages in Queue
try: # see if something has been posted to Queue
message = gui_queue.get_nowait()
@@ -92,11 +89,12 @@ def the_gui(gui_queue):
break # break from the loop if no more messages are queued up
# if message received from queue, display the message in the Window
if message:
- window.Element('_OUTPUT_').Update(message)
- window.Refresh() # do a refresh because could be showing multiple messages before next Read
+ window['-OUTPUT-'].update(message)
+ # do a refresh because could be showing multiple messages before next Read
+ window.refresh()
print(message)
# if user exits the window, then close the window and exit the GUI func
- window.Close()
+ window.close()
## ## ### #### ## ##
@@ -108,12 +106,16 @@ def the_gui(gui_queue):
## ## ## ## #### ## ##
if __name__ == '__main__':
- #-- Create a Queue to communicate with GUI --
- gui_queue = queue.Queue() # queue used to communicate between the gui and the threads
- #-- Start worker threads, one runs twice as often as the other
- threading.Thread(target=worker_thread, args=('Thread 1', 500, gui_queue,), daemon=True).start()
- threading.Thread(target=worker_thread, args=('Thread 2', 200, gui_queue,), daemon=True).start()
- threading.Thread(target=worker_thread, args=('Thread 3', 1000, gui_queue,), daemon=True).start()
- #-- Start the GUI passing in the Queue --
+ # -- Create a Queue to communicate with GUI --
+ # queue used to communicate between the gui and the threads
+ gui_queue = queue.Queue()
+ # -- Start worker threads, one runs twice as often as the other
+ threading.Thread(target=worker_thread, args=(
+ 'Thread 1', 500, gui_queue,), daemon=True).start()
+ threading.Thread(target=worker_thread, args=(
+ 'Thread 2', 200, gui_queue,), daemon=True).start()
+ threading.Thread(target=worker_thread, args=(
+ 'Thread 3', 1000, gui_queue,), daemon=True).start()
+ # -- Start the GUI passing in the Queue --
the_gui(gui_queue)
- print('Exiting Program')
\ No newline at end of file
+ print('Exiting Program')
diff --git a/DemoPrograms/Demo_Nice_Buttons.py b/DemoPrograms/Demo_Nice_Buttons.py
index 2640cd48..db911072 100644
--- a/DemoPrograms/Demo_Nice_Buttons.py
+++ b/DemoPrograms/Demo_Nice_Buttons.py
@@ -6,6 +6,11 @@ import base64
import subprocess
import sys
+orange64 = 'iVBORw0KGgoAAAANSUhEUgAAAiIAAADLCAMAAABkvgh7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANtvJ99sId5tIt5uI91tJNxuJNxuJttvKN90NN91Ntt3PN52ONx3Otx3PNt4Pdp4Ptx4PepfD+pfEOtgD+piD+tkD+ttD+dnFOdoE+doFOVoFuNqG+JrHOFrHuFsHuRpGORpGuRqGORqGupgEOpiEOpkEOlmEOhnEupoEOpoEupqFettEetsF+tuFOxqFetrGOtsGetvHOxrGOxsGOxtGuxuG+xuHexxFutwH+xxGOxyGOxyGuxxHuBsIOJvJ+NvKORvKONwKeNwKuJxLONyLeJyLuRwKeRwKuRxLOtxIexyIexzJOx0JOx1Ju11KOx2KOx2Kux5Iux4Kux5Le1/LeFyMOFzMuB0NOB1Nux7MOx8MOx8Mu19NO1+NO1+Ntd7RdV8RtN+S9J/TdF/TtV9SNR9Stl5QNl6Qdh6Qth7RK+Tfa+Tfq6Ufr+JZb+JZr+KZ7uMbr6KaL6LaryMbLyNb7yOb7WPdLuNcLqOcLmOcrmPdLOQd7SQdrKReLKRerGSerCTfLCTfrmQde2AN+2AOO2BOu2COu6DPO6EPe6EPsWDV8OFXcOFXsOGXcOGXsaDWMSEWsqCVs+AUM2AUs6CU82CVMqDWMmEWtCAT9CAUMKHYMCIYsCIZMCKZu6FQO6GQe6GQu6HRO6IRe6JRu6KR+6KSO+LSu+MS++MTO+OTe+OTu+OUO+RUu+TVfCNTvCOTvCPUPCQUfCQUvCSU/CRVPCSVPCSVvCUV/CUWPCVWvGWWvGXXPGYXvCWYPGaYfGcYvGcZPKdZvKeZ/GdavKeaPKebfKebvKgavKhbPKibfGibvKjcPGkcfKmdPKiePOod/OpePSpePSqevSrfPSsfPSsfvSqg/SugfWwgvWwhPWyhfWyhvWxi/Wyj/W0ifW2jfa4j/a1kPW2kva2lfW2lvW4kPa6kfa7kva5lvW7lPa8lPW8lve+l/a4mva5nPa6nPa7nva+mPa+mva/nPa8oPa9ovbAm/bAnfXAnvbCofbEo/XGp/bGqPbIqQAAAC/NnaUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAAJXElEQVR4Xu3de3zVdR3H8Xb9/sJh60LlOTtubdIZEyckSdedMzkdZyRGIePmZdqki8s0KllJmtGCTAXRlUAyrireUeZga2JRkooYRWR0JbXMLsIqZ5fT7/L5nRvj9/md0/fhw/Px/fyHP+D33fb4vPj+LvsxXpMA8IREgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgJF7Ih1Xf3bVsuu/AYXu+mUrL+u8iqbqIcdErvhme0V8SjQCIpwWe+0nli+k2R5NTolc0jYzqpQBcigV+UDltZ57SQ6JfLqyRRXRyiCIis7u7qAhj8B3Ip1tLdg/pFLR0Z+hOR/JbyIrWxGIZCp289E2Ep+J3HzEFqLKy0qhUJUfeUkZnf8lGnYWX4l0tDfTOg5VEqoJNNTXh6EgmaMLBKuaymmcJFLRSfPO5CuRtgitYisPBehDQUFrqC3O2EtUxRdo4Bn8JHJhlNawlNZh85Aj2JQeiRp9JY08nY9Euk+nFUwq1ECLgww1ZTRaS+R8mnk6PpEF8dTDkNIgLQxiHFdNw7U0d9PU07CJdLw+tRUVpy5Cxo19+bz9lYI+n5cNfdhcjWGZf2jsCTTJcDiUmnBR/HKaewqbSHfqQqSJrkLGvuuctZu3jOgBD1u99B7Fg6a+Vwrrk8kNfRX+0XE2+qg527adsc38M723d01yK3lbqhH1SZp7CpfIolnJw4udQk44ddPOn/3q6b+M6K8+/c2vF+jXVw29X/ALI7N+Z3jome+f/QZ7pOG6VCMtl9Dkk7hElifvd0uPs1cb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'
+green_pill64 = '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'
+red_pill64 = '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'
+button64 = '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'
+
def image_file_to_bytes(image64, size):
image_file = io.BytesIO(base64.b64decode(image64))
@@ -17,42 +22,45 @@ def image_file_to_bytes(image64, size):
return imgbytes
-
def ShowMeTheButtons():
bcolor = ('black', 'black')
wcolor = ('white', 'black')
- sg.ChangeLookAndFeel('Black')
- sg.SetOptions(auto_size_buttons=True, border_width=0, button_color=sg.COLOR_SYSTEM_DEFAULT)
+ sg.change_look_and_feel('Black')
+ sg.set_options(auto_size_buttons=True, border_width=0,
+ button_color=sg.COLOR_SYSTEM_DEFAULT)
- toolbar_buttons = [ [sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45,3))],
- [sg.Text('All of these buttons are part of the code itself', size=(45,2))],
-
- [sg.RButton('Next', image_data=image_file_to_bytes(button64, (100,50)),button_color=wcolor, font='Any 15', pad=(0,0), key='_close_'),
- # [sg.RButton('Exit', image_data=image_file_to_bytes(black64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),],
- sg.RButton('Submit', image_data=image_file_to_bytes(red_pill64, (100,50)),button_color=wcolor, font='Any 15', pad=(0,0), key='_close_'),
- sg.RButton('OK', image_data=image_file_to_bytes(green_pill64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),
- sg.RButton('Exit', image_data=image_file_to_bytes(orange64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='_close_'),],
- ]
+ toolbar_buttons = [[sg.Text('Who says Windows have to be ugly when using tkinter?', size=(45, 3))],
+ [sg.Text(
+ 'All of these buttons are part of the code itself', size=(45, 2))],
+ [sg.RButton('Next', image_data=image_file_to_bytes(button64, (100, 50)), button_color=wcolor, font='Any 15', pad=(0, 0), key='-close-'),
+ # [sg.RButton('Exit', image_data=image_file_to_bytes(black64, (100,50)),button_color=bcolor, font='Any 15', pad=(0,0), key='-close-'),],
+ sg.RButton('Submit',
+ image_data=image_file_to_bytes(
+ red_pill64, (100, 50)),
+ button_color=wcolor, font='Any 15', pad=(0, 0), key='-close-'),
+ sg.RButton('OK', image_data=image_file_to_bytes(green_pill64, (100, 50)),
+ button_color=bcolor, font='Any 15', pad=(0, 0), key='-close-'),
+ sg.RButton('Exit', image_data=image_file_to_bytes(orange64, (100, 50)), button_color=bcolor, font='Any 15', pad=(0, 0), key='-close-'), ],
+ ]
# layout = toolbar_buttons
- layout = [[sg.Frame('Nice Buttons', toolbar_buttons, font=('any 18'), background_color='black')]]
+ layout = [[sg.Frame('Nice Buttons', toolbar_buttons,
+ font=('any 18'), background_color='black')]]
- window = sg.Window('Demo of Nice Looking Buttons',
- no_titlebar=False,
- grab_anywhere=True,
- keep_on_top=True,
- use_default_focus=False,
- font='any 15',
- background_color='black').Layout(layout).Finalize()
+ window = sg.Window('Demo of Nice Looking Buttons', layout,
+ no_titlebar=False, grab_anywhere=True,
+ keep_on_top=True, use_default_focus=False,
+ font='any 15', background_color='black', finalize=True)
# ---===--- Loop taking in user input --- #
while True:
- button, value = window.Read()
+ button, value = window.read()
print(button)
- if button == '_close_' or button is None:
+ if button == '-close-' or button is None:
break # exit button clicked
+
if __name__ == '__main__':
# To convert your PNG into Base 64:
@@ -62,13 +70,4 @@ if __name__ == '__main__':
# Create a string variable name to hold your image
# Paste data from webpage as a string
# Delete the "header" stuff - up to the data portion (data:image/png;base64,)
- orange64 = 'iVBORw0KGgoAAAANSUhEUgAAAiIAAADLCAMAAABkvgh7AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAANtvJ99sId5tIt5uI91tJNxuJNxuJttvKN90NN91Ntt3PN52ONx3Otx3PNt4Pdp4Ptx4PepfD+pfEOtgD+piD+tkD+ttD+dnFOdoE+doFOVoFuNqG+JrHOFrHuFsHuRpGORpGuRqGORqGupgEOpiEOpkEOlmEOhnEupoEOpoEupqFettEetsF+tuFOxqFetrGOtsGetvHOxrGOxsGOxtGuxuG+xuHexxFutwH+xxGOxyGOxyGuxxHuBsIOJvJ+NvKORvKONwKeNwKuJxLONyLeJyLuRwKeRwKuRxLOtxIexyIexzJOx0JOx1Ju11KOx2KOx2Kux5Iux4Kux5Le1/LeFyMOFzMuB0NOB1Nux7MOx8MOx8Mu19NO1+NO1+Ntd7RdV8RtN+S9J/TdF/TtV9SNR9Stl5QNl6Qdh6Qth7RK+Tfa+Tfq6Ufr+JZb+JZr+KZ7uMbr6KaL6LaryMbLyNb7yOb7WPdLuNcLqOcLmOcrmPdLOQd7SQdrKReLKRerGSerCTfLCTfrmQde2AN+2AOO2BOu2COu6DPO6EPe6EPsWDV8OFXcOFXsOGXcOGXsaDWMSEWsqCVs+AUM2AUs6CU82CVMqDWMmEWtCAT9CAUMKHYMCIYsCIZMCKZu6FQO6GQe6GQu6HRO6IRe6JRu6KR+6KSO+LSu+MS++MTO+OTe+OTu+OUO+RUu+TVfCNTvCOTvCPUPCQUfCQUvCSU/CRVPCSVPCSVvCUV/CUWPCVWvGWWvGXXPGYXvCWYPGaYfGcYvGcZPKdZvKeZ/GdavKeaPKebfKebvKgavKhbPKibfGibvKjcPGkcfKmdPKiePOod/OpePSpePSqevSrfPSsfPSsfvSqg/SugfWwgvWwhPWyhfWyhvWxi/Wyj/W0ifW2jfa4j/a1kPW2kva2lfW2lvW4kPa6kfa7kva5lvW7lPa8lPW8lve+l/a4mva5nPa6nPa7nva+mPa+mva/nPa8oPa9ovbAm/bAnfXAnvbCofbEo/XGp/bGqPbIqQAAAC/NnaUAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjFjKpxLAAAJXElEQVR4Xu3de3zVdR3H8Xb9/sJh60LlOTtubdIZEyckSdedMzkdZyRGIePmZdqki8s0KllJmtGCTAXRlUAyrireUeZga2JRkooYRWR0JbXMLsIqZ5fT7/L5nRvj9/md0/fhw/Px/fyHP+D33fb4vPj+LvsxXpMA8IREgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgIFEgJF7Ih1Xf3bVsuu/AYXu+mUrL+u8iqbqIcdErvhme0V8SjQCIpwWe+0nli+k2R5NTolc0jYzqpQBcigV+UDltZ57SQ6JfLqyRRXRyiCIis7u7qAhj8B3Ip1tLdg/pFLR0Z+hOR/JbyIrWxGIZCp289E2Ep+J3HzEFqLKy0qhUJUfeUkZnf8lGnYWX4l0tDfTOg5VEqoJNNTXh6EgmaMLBKuaymmcJFLRSfPO5CuRtgitYisPBehDQUFrqC3O2EtUxRdo4Bn8JHJhlNawlNZh85Aj2JQeiRp9JY08nY9Euk+nFUwq1ECLgww1ZTRaS+R8mnk6PpEF8dTDkNIgLQxiHFdNw7U0d9PU07CJdLw+tRUVpy5Cxo19+bz9lYI+n5cNfdhcjWGZf2jsCTTJcDiUmnBR/HKaewqbSHfqQqSJrkLGvuuctZu3jOgBD1u99B7Fg6a+Vwrrk8kNfRX+0XE2+qg527adsc38M723d01yK3lbqhH1SZp7CpfIolnJw4udQk44ddPOn/3q6b+M6K8+/c2vF+jXVw29X/ALI7N+Z3jome+f/QZ7pOG6VCMtl9Dkk7hElifvd0uPs1cb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'
-
- green_pill64 = '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'
-
- red_pill64 = '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'
-
- button64 = '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'
-
ShowMeTheButtons()
-
diff --git a/DemoPrograms/Demo_NonBlocking_Form.py b/DemoPrograms/Demo_NonBlocking_Form.py
index f3561f7c..22efe0ed 100644
--- a/DemoPrograms/Demo_NonBlocking_Form.py
+++ b/DemoPrograms/Demo_NonBlocking_Form.py
@@ -1,21 +1,21 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import time
-# Window that doen't block
-# good for applications with an loop that polls hardware
+'''
+ Window that doesn't block
+ good for applications with an loop that polls hardware
+'''
+
def StatusOutputExample():
# Create a text element that will be updated with status information on the GUI itself
# Create the rows
layout = [[sg.Text('Non-blocking GUI with updates')],
- [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='output')],
- [sg.Button('LED On'), sg.Button('LED Off'), sg.Button('Quit')]]
+ [sg.Text('', size=(8, 2), font=('Helvetica', 20),
+ justification='center', key='output')],
+ [sg.Button('LED On'), sg.Button('LED Off'), sg.Button('Quit')]]
# Layout the rows of the Window and perform a read. Indicate the Window is non-blocking!
- window = sg.Window('Running Timer', auto_size_text=True).Layout(layout)
+ window = sg.Window('Running Timer', layout, auto_size_text=True)
#
# Some place later in your code...
@@ -23,11 +23,12 @@ def StatusOutputExample():
# else it won't refresh.
#
# your program's main loop
- i=0
- while (True):
+ i = 0
+ while True:
# This is the code that reads and updates your window
- event, values = window.Read(timeout=10)
- window.FindElement('output').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100))
+ event, values = window.read(timeout=10)
+ window['output'].update('{:02d}:{:02d}.{:02d}'.format(
+ (i // 100) // 60, (i // 100) % 60, i % 100))
if event in ('Quit', None):
break
if event == 'LED On':
@@ -39,20 +40,22 @@ def StatusOutputExample():
# Your code begins here
# Broke out of main loop. Close the window.
- window.Close()
+ window.close()
def RemoteControlExample():
layout = [[sg.Text('Robotics Remote Control')],
- [sg.T(' '*10), sg.RealtimeButton('Forward')],
- [ sg.RealtimeButton('Left'), sg.T(' '*15), sg.RealtimeButton('Right')],
- [sg.T(' '*10), sg.RealtimeButton('Reverse')],
- [sg.T('')],
- [sg.Quit(button_color=('black', 'orange'))]
- ]
+ [sg.Text(' '*10), sg.RealtimeButton('Forward')],
+ [sg.RealtimeButton('Left'), sg.Text(' '*15),
+ sg.RealtimeButton('Right')],
+ [sg.Text(' '*10), sg.RealtimeButton('Reverse')],
+ [sg.Text('')],
+ [sg.Quit(button_color=('black', 'orange'))]
+ ]
- window = sg.Window('Robotics Remote Control', auto_size_text=True).Layout(layout).Finalize()
+ window = sg.Window('Robotics Remote Control', layout,
+ auto_size_text=True, finalize=True)
#
# Some place later in your code...
@@ -60,21 +63,21 @@ def RemoteControlExample():
# else it won't refresh.
#
# your program's main loop
- while (True):
+ while True:
# This is the code that reads and updates your window
- event, values = window.Read(timeout=0, timeout_key='timeout')
+ event, values = window.read(timeout=0, timeout_key='timeout')
if event != 'timeout':
print(event)
if event in ('Quit', None):
break
- window.Close()
+ window.close()
def main():
RemoteControlExample()
StatusOutputExample()
- sg.Popup('End of non-blocking demonstration')
+ sg.popup('End of non-blocking demonstration')
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Notification_Window_Alpha_Channel.py b/DemoPrograms/Demo_Notification_Window_Alpha_Channel.py
index ea645eae..1ac4e538 100644
--- a/DemoPrograms/Demo_Notification_Window_Alpha_Channel.py
+++ b/DemoPrograms/Demo_Notification_Window_Alpha_Channel.py
@@ -1,40 +1,35 @@
#!/usr/bin/env python
-import sys
+import PySimpleGUI as sg
import time
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
# Demonstrates a notification window that's partially transparent
# The window slowly fades-in
# Includes a small red-X button to close the window
# Base 64 encoded button is in-lined to avoid reading a file
# Free online encoder - https://www.base64-image.de/
-red_x ="R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+red_x = "R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw=="
+sg.change_look_and_feel('Topanga')
+sg.set_options(border_width=0, margins=(0, 0))
-sg.ChangeLookAndFeel('Topanga')
-sg.SetOptions(border_width=0, margins=(0,0))
-bcolor=('black', '#282923')
+layout = [[sg.Text('Notification'+' '*14),
+ sg.CButton('',
+ image_data=red_x,
+ button_color=('#282923', '#282923'))],
+ [sg.Text('You have 6 new emails')], ]
-sg.SetOptions(border_width=0, margins=(0,0))
-
-layout = [[sg.T('Notification'+' '*14),
- sg.CloseButton('', image_data=red_x, button_color=('#282923', '#282923'))],
- [sg.T('')],
- [sg.T('You have 6 new emails')],]
-
-window = sg.Window('',
+window = sg.Window('', layout,
no_titlebar=True,
grab_anywhere=True,
keep_on_top=True,
- alpha_channel=0
- ).Layout(layout).Finalize()
+ alpha_channel=0,
+ finalize=True)
# Classy fade-in
for i in range(1, 75, 2):
window.AlphaChannel = float(i)/100
time.sleep(.01)
-event, values = window.Read()
\ No newline at end of file
+event, values = window.read()
+
+window.close()
diff --git a/DemoPrograms/Demo_OpenCV.py b/DemoPrograms/Demo_OpenCV.py
index 194980af..1ade99cd 100644
--- a/DemoPrograms/Demo_OpenCV.py
+++ b/DemoPrograms/Demo_OpenCV.py
@@ -1,13 +1,8 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-import cv2 as cv
+import PySimpleGUI as sg
from PIL import Image
+import cv2 as cv
import io
-from sys import exit as exit
"""
Demo program to open and play a file using OpenCV
@@ -18,55 +13,70 @@ It's main purpose is to show you:
For added fun, you can reposition the video using the slider.
"""
+
def main():
# ---===--- Get the filename --- #
- filename = sg.PopupGetFile('Filename to play')
+ filename = sg.popup_get_file('Filename to play')
if filename is None:
- exit(69)
+ return
vidFile = cv.VideoCapture(filename)
# ---===--- Get some Stats --- #
num_frames = vidFile.get(cv.CAP_PROP_FRAME_COUNT)
fps = vidFile.get(cv.CAP_PROP_FPS)
- sg.ChangeLookAndFeel('Black')
+ sg.change_look_and_feel('Black')
# ---===--- define the window layout --- #
layout = [[sg.Text('OpenCV Demo', size=(15, 1), font='Helvetica 20')],
- [sg.Image(filename='', key='_image_')],
- [sg.Slider(range=(0, num_frames), size=(60, 10), orientation='h', key='_slider_')],
+ [sg.Image(filename='', key='-image-')],
+ [sg.Slider(range=(0, num_frames),
+ size=(60, 10), orientation='h', key='-slider-')],
[sg.Button('Exit', size=(7, 1), pad=((600, 0), 3), font='Helvetica 14')]]
# create the window and show it without the plot
- window = sg.Window('Demo Application - OpenCV Integration', no_titlebar=False, location=(0,0)).Layout(layout)
+ window = sg.Window('Demo Application - OpenCV Integration',
+ layout,
+ no_titlebar=False,
+ location=(0, 0))
- image_elem = window.Element('_image_') # locate the elements we'll be updating. Does the search only 1 time
- slider_elem = window.Element('_slider_')
+ # locate the elements we'll be updating. Does the search only 1 time
+ image_elem = window['-image-']
+ slider_elem = window['-slider-']
# ---===--- LOOP through video file by frame --- #
cur_frame = 0
while vidFile.isOpened():
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event in ('Exit', None):
- exit(69)
+ break
ret, frame = vidFile.read()
if not ret: # if out of data stop looping
break
- if int(values['_slider_']) != cur_frame-1: # if someone moved the slider manually, the jump to that frame
- cur_frame = int(values['_slider_'])
+ # if someone moved the slider manually, the jump to that frame
+ if int(values['-slider-']) != cur_frame-1:
+ cur_frame = int(values['-slider-'])
vidFile.set(cv.CAP_PROP_POS_FRAMES, cur_frame)
- slider_elem.Update(cur_frame)
+ slider_elem.update(cur_frame)
cur_frame += 1
imgbytes = cv.imencode('.png', frame)[1].tobytes() # ditto
- image_elem.Update(data=imgbytes)
+ image_elem.update(data=imgbytes)
-"""
+ #############
+ # | | #
+ # | | #
+ # |_| #
+ # __ __ #
+ # \ \ / / #
+ # \ V / #
+ # \_/ #
+""" #############
# This was another way updates were being done, but seems slower than the above
img = Image.fromarray(frame) # create PIL image from frame
bio = io.BytesIO() # a binary memory resident stream
img.save(bio, format= 'PNG') # save image as png to it
imgbytes = bio.getvalue() # this can be used by OpenCV hopefully
- image_elem.Update(data=imgbytes)
+ image_elem.update(data=imgbytes)
"""
-main()
\ No newline at end of file
+main()
diff --git a/DemoPrograms/Demo_OpenCV_4_Line_Program.py b/DemoPrograms/Demo_OpenCV_4_Line_Program.py
index 6e032cf3..6430fb06 100644
--- a/DemoPrograms/Demo_OpenCV_4_Line_Program.py
+++ b/DemoPrograms/Demo_OpenCV_4_Line_Program.py
@@ -1,4 +1,4 @@
import cv2, PySimpleGUI as sg
-window, cap = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')],], location=(800,400)), cv2.VideoCapture(0)
+window, cap = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')], ], location=(800, 400)), cv2.VideoCapture(0)
while window(timeout=20)[0] is not None:
window['image'](data=cv2.imencode('.png', cap.read()[1])[1].tobytes())
diff --git a/DemoPrograms/Demo_OpenCV_7_Line_Program.py b/DemoPrograms/Demo_OpenCV_7_Line_Program.py
index b1cc0ff7..82807f79 100644
--- a/DemoPrograms/Demo_OpenCV_7_Line_Program.py
+++ b/DemoPrograms/Demo_OpenCV_7_Line_Program.py
@@ -2,9 +2,9 @@ import cv2, PySimpleGUI as sg
window = sg.Window('Demo Application - OpenCV Integration', [[sg.Image(filename='', key='image')],], location=(800,400))
cap = cv2.VideoCapture(0) # Setup the camera as a capture device
while True: # The PSG "Event Loop"
- event, values = window.Read(timeout=20, timeout_key='timeout') # get events for the window with 20ms max wait
+ event, values = window.read(timeout=20, timeout_key='timeout') # get events for the window with 20ms max wait
if event is None: break # if user closed window, quit
- window.FindElement('image').Update(data=cv2.imencode('.png', cap.read()[1])[1].tobytes()) # Update image in window
+ window['image'].update(data=cv2.imencode('.png', cap.read()[1])[1].tobytes()) # Update image in window
"""
Putting the comment at the bottom so that you can see that the code is indeed 7 lines long. And, there is nothing
diff --git a/DemoPrograms/Demo_OpenCV_Draw_On_Webcam_Image.py b/DemoPrograms/Demo_OpenCV_Draw_On_Webcam_Image.py
index 46a1cdb4..07a1b964 100644
--- a/DemoPrograms/Demo_OpenCV_Draw_On_Webcam_Image.py
+++ b/DemoPrograms/Demo_OpenCV_Draw_On_Webcam_Image.py
@@ -10,29 +10,28 @@ import cv2
"""
def main():
- layout = [[sg.Graph((600,450),(0,450), (600,0), key='_GRAPH_', enable_events=True, drag_submits=True)],]
+ layout = [[sg.Graph((600,450),(0,450), (600,0), key='-GRAPH-', enable_events=True, drag_submits=True)],]
window = sg.Window('Demo Application - OpenCV Integration', layout)
-
- graph_elem = window.Element('_GRAPH_') # type: sg.Graph
-
- id = None
+ graph_elem = window['-GRAPH-'] # type: sg.Graph
+ a_id = None
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
cap = cv2.VideoCapture(0)
while True:
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event in ('Exit', None):
break
ret, frame = cap.read()
imgbytes=cv2.imencode('.png', frame)[1].tobytes()
- if id:
- graph_elem.DeleteFigure(id) # delete previous image
- id = graph_elem.DrawImage(data=imgbytes, location=(0,0)) # draw new image
- graph_elem.TKCanvas.tag_lower(id) # move image to the "bottom" of all other drawings
+ if a_id:
+ graph_elem.delete_figure(a_id) # delete previous image
+ a_id = graph_elem.draw_image(data=imgbytes, location=(0,0)) # draw new image
+ graph_elem.TKCanvas.tag_lower(a_id) # move image to the "bottom" of all other drawings
- if event == '_GRAPH_':
- graph_elem.DrawCircle(values['_GRAPH_'], 5, fill_color='red', line_color='red')
- window.Close()
+ if event == '-GRAPH-':
+ graph_elem.draw_circle(values['-GRAPH-'], 5, fill_color='red', line_color='red')
+
+ window.close()
main()
diff --git a/DemoPrograms/Demo_OpenCV_Simple_GUI.py b/DemoPrograms/Demo_OpenCV_Simple_GUI.py
index 7399dc96..f0a6cd40 100644
--- a/DemoPrograms/Demo_OpenCV_Simple_GUI.py
+++ b/DemoPrograms/Demo_OpenCV_Simple_GUI.py
@@ -1,12 +1,6 @@
-import sys
-
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import cv2
import numpy as np
-from sys import exit as exit
"""
Demo program that displays a webcam using OpenCV and applies some very basic image functions
@@ -24,65 +18,78 @@ enhance: applies local contrast enhancement on the luma channel to make the i
def main():
- sg.ChangeLookAndFeel('LightGreen')
+ sg.change_look_and_feel('LightGreen')
# define the window layout
- layout = [[sg.Text('OpenCV Demo', size=(40, 1), justification='center')],
- [sg.Image(filename='', key='image')],
- [sg.Radio('None', 'Radio', True, size=(10, 1))],
- [sg.Radio('threshold', 'Radio', size=(10, 1), key='thresh'),
- sg.Slider((0, 255), 128, 1, orientation='h', size=(40, 15), key='thresh_slider')],
- [sg.Radio('canny', 'Radio', size=(10, 1), key='canny'),
- sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_a'),
- sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_b')],
- [sg.Radio('contour', 'Radio', size=(10, 1), key='contour'),
- sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='contour_slider'),
- sg.Slider((0, 255), 80, 1, orientation='h', size=(20, 15), key='base_slider')],
- [sg.Radio('blur', 'Radio', size=(10, 1), key='blur'),
- sg.Slider((1, 11), 1, 1, orientation='h', size=(40, 15), key='blur_slider')],
- [sg.Radio('hue', 'Radio', size=(10, 1), key='hue'),
- sg.Slider((0, 225), 0, 1, orientation='h', size=(40, 15), key='hue_slider')],
- [sg.Radio('enhance', 'Radio', size=(10, 1), key='enhance'),
- sg.Slider((1, 255), 128, 1, orientation='h', size=(40, 15), key='enhance_slider')],
- [sg.Button('Exit', size=(10, 1))]]
+ layout = [
+ [sg.Text('OpenCV Demo', size=(40, 1), justification='center')],
+ [sg.Image(filename='', key='image')],
+ [sg.Radio('None', 'Radio', True, size=(10, 1))],
+ [sg.Radio('threshold', 'Radio', size=(10, 1), key='thresh'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(40, 15), key='thresh_slider')],
+ [sg.Radio('canny', 'Radio', size=(10, 1), key='canny'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_a'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='canny_slider_b')],
+ [sg.Radio('contour', 'Radio', size=(10, 1), key='contour'),
+ sg.Slider((0, 255), 128, 1, orientation='h', size=(20, 15), key='contour_slider'),
+ sg.Slider((0, 255), 80, 1, orientation='h', size=(20, 15), key='base_slider')],
+ [sg.Radio('blur', 'Radio', size=(10, 1), key='blur'),
+ sg.Slider((1, 11), 1, 1, orientation='h', size=(40, 15), key='blur_slider')],
+ [sg.Radio('hue', 'Radio', size=(10, 1), key='hue'),
+ sg.Slider((0, 225), 0, 1, orientation='h', size=(40, 15), key='hue_slider')],
+ [sg.Radio('enhance', 'Radio', size=(10, 1), key='enhance'),
+ sg.Slider((1, 255), 128, 1, orientation='h', size=(40, 15), key='enhance_slider')],
+ [sg.Button('Exit', size=(10, 1))]
+ ]
# create the window and show it without the plot
window = sg.Window('Demo Application - OpenCV Integration',
- location=(800, 400))
- window.Layout(layout).Finalize()
+ layout,
+ location=(800, 400),
+ finalize=True)
cap = cv2.VideoCapture(0)
while True:
- event, values = window.Read(timeout=0, timeout_key='timeout')
+ event, values = window.read(timeout=0, timeout_key='timeout')
if event == 'Exit' or event is None:
- sys.exit(0)
+ break
+
ret, frame = cap.read()
+
if values['thresh']:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)[:, :, 0]
- _, frame = cv2.threshold(frame, values['thresh_slider'], 255, cv2.THRESH_BINARY)
+ frame = cv2.threshold(frame, values['thresh_slider'], 255, cv2.THRESH_BINARY)[1]
+
if values['canny']:
frame = cv2.Canny(frame, values['canny_slider_a'], values['canny_slider_b'])
+
if values['blur']:
frame = cv2.GaussianBlur(frame, (21, 21), values['blur_slider'])
+
if values['hue']:
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
frame[:, :, 0] += values['hue_slider']
frame = cv2.cvtColor(frame, cv2.COLOR_HSV2BGR)
+
if values['enhance']:
enh_val = values['enhance_slider'] / 40
clahe = cv2.createCLAHE(clipLimit=enh_val, tileGridSize=(8, 8))
lab = cv2.cvtColor(frame, cv2.COLOR_BGR2LAB)
lab[:, :, 0] = clahe.apply(lab[:, :, 0])
frame = cv2.cvtColor(lab, cv2.COLOR_LAB2BGR)
+
if values['contour']:
hue = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
hue = cv2.GaussianBlur(hue, (21, 21), 1)
hue = cv2.inRange(hue, np.array([values['contour_slider'], values['base_slider'], 40]),
np.array([values['contour_slider'] + 30, 255, 220]))
- _, cnts, _ = cv2.findContours(hue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
+ cnts = cv2.findContours(hue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(frame, cnts, -1, (0, 0, 255), 2)
- imgbytes = cv2.imencode('.png', frame)[1].tobytes() # ditto
- window.FindElement('image').Update(data=imgbytes)
+
+ imgbytes = cv2.imencode('.png', frame)[1].tobytes()
+ window['image'].update(data=imgbytes)
+
+ window.close()
main()
diff --git a/DemoPrograms/Demo_OpenCV_Webcam.py b/DemoPrograms/Demo_OpenCV_Webcam.py
index 287f485a..8a112d7a 100644
--- a/DemoPrograms/Demo_OpenCV_Webcam.py
+++ b/DemoPrograms/Demo_OpenCV_Webcam.py
@@ -1,48 +1,51 @@
#!/usr/bin/env python
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
import cv2
import numpy as np
-import sys
-from sys import exit as exit
"""
Demo program that displays a webcam using OpenCV
"""
+
+
def main():
- sg.ChangeLookAndFeel('Black')
+ sg.change_look_and_feel('Black')
# define the window layout
layout = [[sg.Text('OpenCV Demo', size=(40, 1), justification='center', font='Helvetica 20')],
[sg.Image(filename='', key='image')],
[sg.Button('Record', size=(10, 1), font='Helvetica 14'),
sg.Button('Stop', size=(10, 1), font='Any 14'),
- sg.Button('Exit', size=(10, 1), font='Helvetica 14'),]]
+ sg.Button('Exit', size=(10, 1), font='Helvetica 14'), ]]
# create the window and show it without the plot
- window = sg.Window('Demo Application - OpenCV Integration', layout,
- location=(800,400))
+ window = sg.Window('Demo Application - OpenCV Integration',
+ layout, location=(800, 400))
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
cap = cv2.VideoCapture(0)
recording = False
+
while True:
- event, values = window.Read(timeout=20)
+ event, values = window.read(timeout=20)
if event == 'Exit' or event is None:
- sys.exit(0)
+ return
+
elif event == 'Record':
recording = True
+
elif event == 'Stop':
recording = False
- img = np.full((480, 640),255)
- imgbytes=cv2.imencode('.png', img)[1].tobytes() #this is faster, shorter and needs less includes
- window.FindElement('image').Update(data=imgbytes)
+ img = np.full((480, 640), 255)
+ # this is faster, shorter and needs less includes
+ imgbytes = cv2.imencode('.png', img)[1].tobytes()
+ window['image'].update(data=imgbytes)
if recording:
ret, frame = cap.read()
- imgbytes=cv2.imencode('.png', frame)[1].tobytes() #ditto
- window.FindElement('image').Update(data=imgbytes)
+ imgbytes = cv2.imencode('.png', frame)[1].tobytes() # ditto
+ window['image'].update(data=imgbytes)
+
main()
-exit()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_OpenCV_Webcam_ASCII.py b/DemoPrograms/Demo_OpenCV_Webcam_ASCII.py
index 61232d00..2d674370 100644
--- a/DemoPrograms/Demo_OpenCV_Webcam_ASCII.py
+++ b/DemoPrograms/Demo_OpenCV_Webcam_ASCII.py
@@ -1,9 +1,9 @@
+import cv2
from PIL import Image
import numpy as np
-import PySimpleGUI as sg; font_size=6; USING_QT=False
-# import PySimpleGUIQt as sg; font_size=8; USING_QT=True # if using, be sure and use the second layout that is commented out
-# import PySimpleGUIWeb as sg; font_size=12; USING_QT=False # yes, it runs in a webpage too! Not as good as tkinter but works
-import cv2
+import PySimpleGUI as sg
+font_size = 6
+USING_QT = False
"""
Interesting program that shows your webcam's image as ASCII text. Runs in realtime, producing a stream of
@@ -28,33 +28,45 @@ import cv2
chars = np.asarray(list(' .,:;irsXA253hMHGS#9B&@'))
SC, GCF, WCF = .1, 1, 7/4
-sg.ChangeLookAndFeel('Black') # make it look cool
+sg.change_look_and_feel('Black') # make it look cool
# define the window layout
-NUM_LINES = 48 # number of lines of text elements. Depends on cameras image size and the variable SC (scaller)
+# number of lines of text elements. Depends on cameras image size and the variable SC (scaller)
+NUM_LINES = 48
if USING_QT:
- layout = [[sg.T(i, size_px=(800, 12), font=('Courier', font_size), key='_OUT_' + str(i))] for i in range(NUM_LINES)]
+ layout = [[sg.Text(i, size_px=(800, 12),
+ font=('Courier', font_size),
+ key='-OUT-' + str(i))] for i in range(NUM_LINES)]
else:
- layout = [[sg.T(i,size=(120,1), font=('Courier', font_size), pad=(0,0), key='_OUT_'+str(i))] for i in range(NUM_LINES)]
+ layout = [[sg.Text(i, size=(120, 1), font=('Courier', font_size),
+ pad=(0, 0), key='-OUT-'+str(i))] for i in range(NUM_LINES)]
-layout += [[ sg.Button('Exit', size=(5,1)),
- sg.T('GCF', size=(4,1)), sg.Spin([round(i,2) for i in np.arange(0.1,20.0,0.1)], initial_value=1, key='_SPIN_GCF_', size=(5,1)),
- sg.T('WCF', size=(4,1)), sg.Slider((1,4), resolution=.05, default_value=1.75, orientation='h', key='_SLIDER_WCF_', size=(15,15))]]
+layout += [[sg.Button('Exit', size=(5, 1)),
+ sg.Text('GCF', size=(4, 1)),
+ sg.Spin([round(i, 2) for i in np.arange(0.1, 20.0, 0.1)],
+ initial_value=1, key='-SPIN-GCF-', size=(5, 1)),
+ sg.Text('WCF', size=(4, 1)),
+ sg.Slider((1, 4), resolution=.05, default_value=1.75,
+ orientation='h', key='-SLIDER-WCF-', size=(15, 15))]]
# create the window and show it without the plot
-window = sg.Window('Demo Application - OpenCV Integration', layout, location=(800,400), font='Any 18')
+window = sg.Window('Demo Application - OpenCV Integration', layout,
+ location=(800, 400), font='Any 18')
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
-cap = cv2.VideoCapture(0) # Setup the OpenCV capture device (webcam)
+# Setup the OpenCV capture device (webcam)
+cap = cv2.VideoCapture(0)
while True:
- event, values = window.Read(timeout=0)
+
+ event, values = window.read(timeout=0)
if event in ('Exit', None):
break
- ret, frame = cap.read() # Read image from capture device (camera)
+ # Read image from capture device (camera)
+ ret, frame = cap.read()
img = Image.fromarray(frame) # create PIL image from frame
- GCF = float(values['_SPIN_GCF_'])
- WCF = values['_SLIDER_WCF_']
+ GCF = float(values['-SPIN-GCF-'])
+ WCF = values['-SLIDER-WCF-']
# More magic that coverts the image to ascii
S = (round(img.size[0] * SC * WCF), round(img.size[1] * SC))
img = np.sum(np.asarray(img.resize(S)), axis=2)
@@ -63,5 +75,6 @@ while True:
# "Draw" the image in the window, one line of text at a time!
for i, r in enumerate(chars[img.astype(int)]):
- window.Element('_OUT_'+str(i)).Update("".join(r))
-window.Close()
+ window['-OUT-'+str(i)].update("".join(r))
+
+window.close()
diff --git a/DemoPrograms/Demo_OpenCV_Webcam_Minimal.py b/DemoPrograms/Demo_OpenCV_Webcam_Minimal.py
index 3d08fa03..f43211d1 100644
--- a/DemoPrograms/Demo_OpenCV_Webcam_Minimal.py
+++ b/DemoPrograms/Demo_OpenCV_Webcam_Minimal.py
@@ -1,6 +1,4 @@
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg
-# import PySimpleGUIWeb as sg # has a known flicker problem that's being worked
import cv2
"""
@@ -10,10 +8,10 @@ import cv2
in PySimpleGUIQt (yet).
"""
-sg.ChangeLookAndFeel('Black')
+sg.change_look_and_feel('Black')
# define the window layout
-layout = [[sg.Image(filename='', key='_IMAGE_', tooltip='Right click for exit menu')],]
+layout = [[sg.Image(filename='', key='-IMAGE-', tooltip='Right click for exit menu')],]
# create the window and show it without the plot
window = sg.Window('Demo Application - OpenCV Integration', layout, location=(800,400),
@@ -23,9 +21,11 @@ window = sg.Window('Demo Application - OpenCV Integration', layout, location=(80
# ---===--- Event LOOP Read and display frames, operate the GUI --- #
cap = cv2.VideoCapture(0) # Setup the OpenCV capture device (webcam)
while True:
- event, values = window.Read(timeout=20, timeout_key='timeout')
+ event, values = window.read(timeout=20)
if event in ('Exit', None):
break
ret, frame = cap.read() # Read image from capture device (camera)
imgbytes=cv2.imencode('.png', frame)[1].tobytes() # Convert the image to PNG Bytes
- window.FindElement('_IMAGE_').Update(data=imgbytes) # Change the Image Element to show the new image
+ window['-IMAGE-'].update(data=imgbytes) # Change the Image Element to show the new image
+
+window.close()
diff --git a/DemoPrograms/Demo_PDF_Viewer.py b/DemoPrograms/Demo_PDF_Viewer.py
index 48671c6d..7ad4d690 100644
--- a/DemoPrograms/Demo_PDF_Viewer.py
+++ b/DemoPrograms/Demo_PDF_Viewer.py
@@ -34,19 +34,16 @@ pixmaps and page re-visits will re-use a once-created display list.
"""
import sys
import fitz
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-from sys import exit as exit
-from binascii import hexlify
+import PySimpleGUI as sg
+from sys import exit
-sg.ChangeLookAndFeel('GreenTan')
+sg.change_look_and_feel('GreenTan')
if len(sys.argv) == 1:
- fname = sg.PopupGetFile('PDF Browser', 'PDF file to open', file_types=(("PDF Files", "*.pdf"),))
+ fname = sg.popup_get_file(
+ 'PDF Browser', 'PDF file to open', file_types=(("PDF Files", "*.pdf"),))
if fname is None:
- sg.PopupCancel('Cancelling')
+ sg.popup_cancel('Cancelling')
exit(0)
else:
fname = sys.argv[1]
@@ -90,12 +87,13 @@ def get_page(pno, zoom=0):
return pix.getPNGData() # return the PNG image
-window = sg.Window(title, return_keyboard_events=True, use_default_focus=False)
+window = sg.Window(title, layout,
+ 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)
-goto = sg.InputText(str(cur_page + 1), size=(5, 1), do_not_clear=True)
+goto = sg.InputText(str(cur_page + 1), size=(5, 1))
layout = [
[
@@ -113,8 +111,6 @@ layout = [
],
[image_elem],
]
-
-window.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")
@@ -124,7 +120,7 @@ old_zoom = 0 # used for zoom on/off
# the zoom buttons work in on/off mode.
while True:
- event, values = window.Read(timeout=100)
+ event, values = window.read(timeout=100)
zoom = 0
force_page = False
if event is None:
@@ -132,7 +128,6 @@ while True:
if event in ("Escape:27",): # this spares me a 'Quit' button!
break
- # print("hex(button)", hexlify(button.encode()))
if event[0] == chr(13): # surprise: this is 'Enter'!
try:
cur_page = int(values[0]) - 1 # check if valid
@@ -140,7 +135,7 @@ while True:
cur_page += page_count
except:
cur_page = 0 # this guy's trying to fool me
- goto.Update(str(cur_page + 1))
+ goto.update(str(cur_page + 1))
# goto.TKStringVar.set(str(cur_page + 1))
elif event in ("Next", "Next:34", "MouseWheel:Down"):
@@ -177,11 +172,11 @@ while True:
if force_page:
data = get_page(cur_page, zoom)
- image_elem.Update(data=data)
+ image_elem.update(data=data)
old_page = cur_page
old_zoom = zoom
# update page number field
if event in my_keys or not values[0]:
- goto.Update(str(cur_page + 1))
+ goto.update(str(cur_page + 1))
# goto.TKStringVar.set(str(cur_page + 1))
diff --git a/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py b/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py
index b11a71bd..61a24ad7 100644
--- a/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py
+++ b/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py
@@ -1,26 +1,25 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+from PIL import Image
+from sys import exit
+import PySimpleGUI as sg
import os
-from sys import exit as exit
-from PIL import Image
import io
import numpy as np
-thumbnails = {}
+
+thumbnails = {}
ROWS = 8
COLUMNS = 8
-sg.SetOptions(border_width=0)
+sg.set_options(border_width=0)
# Get the folder containing the images from the user
# folder = 'A:/TEMP/pdfs'
-folder = sg.PopupGetFolder('Image folder to open')
+folder = sg.popup_get_folder('Image folder to open')
if folder is None:
- sg.PopupCancel('Cancelling')
+ sg.popup_cancel('Cancelling')
exit(0)
+
+
def image_file_to_bytes(filename, size):
try:
image = Image.open(filename)
@@ -32,65 +31,72 @@ def image_file_to_bytes(filename, size):
imgbytes = None
return imgbytes
+
def set_image_to_blank(key):
img = Image.new('RGB', (100, 100), (255, 255, 255))
img.thumbnail((1, 1), Image.ANTIALIAS)
bio = io.BytesIO()
img.save(bio, format='PNG')
imgbytes = bio.getvalue()
- window.FindElement(key).Update(image_data=imgbytes)
-
+ window[key].update(image_data=imgbytes)
# get list of PNG files in folder
-png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f]
+png_files = [os.path.join(folder, f)
+ for f in os.listdir(folder) if '.png' in f]
filenames_only = [f for f in os.listdir(folder) if '.png' in f]
if len(png_files) == 0:
- sg.Popup('No PNG images in folder')
+ sg.popup('No PNG images in folder')
exit(0)
# define menu layout
-menu = [['&File', ['&Open Folder', 'E&xit']], ['&Help', ['&About',]]]
+menu = [['&File', ['&Open Folder', 'E&xit']], ['&Help', ['&About', ]]]
buttons = []
for display_index in range(ROWS):
row = []
for j in range(COLUMNS):
- row.append(sg.Button('',border_width=0,button_color=sg.COLOR_SYSTEM_DEFAULT, key=(display_index, j)))
+ row.append(sg.Button('', border_width=0,
+ button_color=sg.COLOR_SYSTEM_DEFAULT, key=(display_index, j)))
buttons.append(row)
col_buttons = [[]]
# define layout, show and read the window
col = [[sg.Text(png_files[0], size=(80, 3), key='filename')],
- [sg.Image(data=image_file_to_bytes(png_files[0], (500,500)), key='image')],]
+ [sg.Image(data=image_file_to_bytes(png_files[0], (500, 500)), key='image')], ]
-layout = [[sg.Menu(menu)], [sg.Column(buttons), sg.Column([[sg.Slider((len(png_files),0),default_value=0,size=(38,20),orientation='v', key='_slider_', change_submits=True)]]), sg.Column(col)]]
-window = sg.Window('Image Browser',
+layout = [
+ [sg.Menu(menu)],
+ [sg.Col(buttons), sg.Col([[sg.Slider((len(png_files), 0), default_value=0, size=(38, 20), orientation='v', key='-slider-', change_submits=True)]]), sg.Col(col)]
+]
+
+window = sg.Window('Image Browser', layout,
return_keyboard_events=True,
- use_default_focus=False ).Layout(layout).Finalize()
+ use_default_focus=False, finalize=True)
# -------========= Event Loop =========--------
-display_index=0
+display_index = 0
while True:
+
for x in range(ROWS): # update thumbnails
for y in range(COLUMNS):
cur_index = display_index + (x * 4) + y
if cur_index < len(png_files):
filename = png_files[cur_index]
if filename not in thumbnails:
- imgbytes = image_file_to_bytes(filename, (100,100))
+ imgbytes = image_file_to_bytes(filename, (100, 100))
thumbnails[filename] = imgbytes
else:
imgbytes = thumbnails[filename]
- button_elem = window.FindElement(key=(x,y))
- button_elem.Update(image_data=imgbytes)
+ button_elem = window[(x, y)]
+ button_elem.update(image_data=imgbytes)
else:
- set_image_to_blank((x,y))
+ set_image_to_blank((x, y))
- event, values = window.Read()
- display_index = values['_slider_']
+ event, values = window.read()
+ display_index = values['-slider-']
# --------------------- Button & Keyboard ---------------------
if event in (None, 'Exit'):
break
@@ -103,28 +109,30 @@ while True:
elif event in ('Next:34', 'Next'):
display_index += 16
- window.FindElement('_slider_').Update(display_index)
+ window['-slider-'].update(display_index)
# ----------------- Menu choices -----------------
if event == 'Open Folder':
- newfolder = sg.PopupGetFolder('New folder', no_window=True)
+ newfolder = sg.popup_get_folder('New folder', no_window=True)
if newfolder is None:
continue
folder = newfolder
- png_files = [os.path.join(folder, f) for f in os.listdir(folder) if '.png' in f]
+ png_files = [os.path.join(folder, f)
+ for f in os.listdir(folder) if '.png' in f]
filenames_only = [f for f in os.listdir(folder) if '.png' in f]
display_index = 0
thumbnail = {}
for j in range(ROWS):
for i in range(COLUMNS):
- set_image_to_blank((i,j))
+ set_image_to_blank((i, j))
elif event == 'About':
- sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!')
+ sg.popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!')
elif type(event) is tuple:
x, y = event
image_index = display_index + (x * 4) + y
if image_index < len(png_files):
filename = png_files[image_index]
imgbytes = image_file_to_bytes(filename, (500, 500))
- window.FindElement('image').Update(data=imgbytes)
- window.FindElement('filename').Update(filename)
+ window['image'].update(data=imgbytes)
+ window['filename'].update(filename)
+window.close()
diff --git a/DemoPrograms/Demo_PNG_Viewer.py b/DemoPrograms/Demo_PNG_Viewer.py
index 4fba5b1a..f23c14a1 100644
--- a/DemoPrograms/Demo_PNG_Viewer.py
+++ b/DemoPrograms/Demo_PNG_Viewer.py
@@ -1,78 +1,92 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import os
-from sys import exit as exit
-# Simple Image Browser based on PySimpleGUI
-
-# Get the folder containing the images from the user
-folder = sg.PopupGetFolder('Image folder to open')
-if folder is None:
- sg.PopupCancel('Cancelling')
- exit(0)
-
-# get list of PNG files in folder
-png_files = [folder + '\\' + f for f in os.listdir(folder) if '.png' in f]
-filenames_only = [f for f in os.listdir(folder) if '.png' in f]
-
-if len(png_files) == 0:
- sg.Popup('No PNG images in folder')
- exit(0)
+'''
+ Simple Image Browser based on PySimpleGUI
+'''
-# define menu layout
-menu = [['File', ['Open Folder', 'Exit']], ['Help', ['About',]]]
+def main():
-# define layout, show and read the window
-col = [[sg.Text(png_files[0], size=(80, 3), key='filename')],
- [sg.Image(filename=png_files[0], key='image')],
- [sg.Button('Next', size=(8,2)), sg.Button('Prev', size=(8,2)),
- sg.Text('File 1 of {}'.format(len(png_files)), size=(15,1), key='filenum')]]
+ # Get the folder containing the images from the user
+ folder = sg.popup_get_folder('Image folder to open')
+ if folder is None:
+ sg.popup_cancel('Cancelling')
+ return
-col_files = [[sg.Listbox(values=filenames_only, size=(60,30), key='listbox')],
- [sg.Button('Read')]]
-layout = [[sg.Menu(menu)], [sg.Column(col_files), sg.Column(col)]]
-window = sg.Window('Image Browser', return_keyboard_events=True, location=(0,0), use_default_focus=False ).Layout(layout)
+ # get list of PNG files in folder
+ png_files = [folder + '\\' + f for f in os.listdir(folder) if '.png' in f]
+ filenames_only = [f for f in os.listdir(folder) if '.png' in f]
-# loop reading the user input and displaying image, filename
-i=0
-while True:
+ if len(png_files) == 0:
+ sg.popup('No PNG images in folder')
+ return
- event, values = window.Read()
- # --------------------- Button & Keyboard ---------------------
- if event is None:
- break
- elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34') and i < len(png_files)-1:
- i += 1
- elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33') and i > 0:
- i -= 1
- elif event == 'Exit':
- exit(69)
+ # define menu layout
+ menu = [['File', ['Open Folder', 'Exit']], ['Help', ['About', ]]]
- filename = folder + '/' + values['listbox'][0] if event == 'Read' else png_files[i]
+ # define layout, show and read the window
+ col = [[sg.Text(png_files[0], size=(80, 3), key='filename')],
+ [sg.Image(filename=png_files[0], key='image')],
+ [sg.Button('Next', size=(8, 2)), sg.Button('Prev', size=(8, 2)),
+ sg.Text('File 1 of {}'.format(len(png_files)), size=(15, 1), key='filenum')]]
- # ----------------- Menu choices -----------------
- if event == 'Open Folder':
- newfolder = sg.PopupGetFolder('New folder', no_window=True)
- if newfolder is None:
- continue
- folder = newfolder
- png_files = [folder + '/' + f for f in os.listdir(folder) if '.png' in f]
- filenames_only = [f for f in os.listdir(folder) if '.png' in f]
- window.FindElement('listbox').Update(values=filenames_only)
- window.Refresh()
- i = 0
- elif event == 'About':
- sg.Popup('Demo PNG Viewer Program', 'Please give PySimpleGUI a try!')
+ col_files = [[sg.Listbox(values=filenames_only, size=(60, 30), key='listbox')],
+ [sg.Button('Read')]]
+ layout = [[sg.Menu(menu)], [sg.Col(col_files), sg.Col(col)]]
+ window = sg.Window('Image Browser', layout,
+ return_keyboard_events=True,
+ location=(0, 0),
+ use_default_focus=False)
- # update window with new image
- window.FindElement('image').Update(filename=filename)
- # update window with filename
- window.FindElement('filename').Update(filename)
- # update page display
- window.FindElement('filenum').Update('File {} of {}'.format(i+1, len(png_files)))
+ # loop reading the user input and displaying image, filename
+ i = 0
+ while True:
+ event, values = window.read()
+ # --------------------- Button & Keyboard ---------------------
+ if event is None:
+ break
+ elif event in ('Next', 'MouseWheel:Down', 'Down:40', 'Next:34') and i < len(png_files)-1:
+ i += 1
+ elif event in ('Prev', 'MouseWheel:Up', 'Up:38', 'Prior:33') and i > 0:
+ i -= 1
+ elif event == 'Exit':
+ break
+
+ if event == 'Read':
+ filename = folder + '/' + values['listbox'][0]
+ else:
+ filename = png_files[i]
+
+ # ----------------- Menu choices -----------------
+ if event == 'Open Folder':
+ newfolder = sg.popup_get_folder('New folder', no_window=True)
+ if newfolder is None:
+ continue
+
+ folder = newfolder
+ png_files = [folder + '/' +
+ f for f in os.listdir(folder) if '.png' in f]
+ filenames_only = [f for f in os.listdir(folder) if '.png' in f]
+
+ window['listbox'].update(values=filenames_only)
+ window.refresh()
+
+ i = 0
+ elif event == 'About':
+ sg.popup('Demo PNG Viewer Program',
+ 'Please give PySimpleGUI a try!')
+
+ # update window with new image
+ window['image'].update(filename=filename)
+ # update window with filename
+ window['filename'].update(filename)
+ # update page display
+ window['filenum'].update('File {} of {}'.format(i+1, len(png_files)))
+
+ window.close()
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Paned_Window.py b/DemoPrograms/Demo_Paned_Window.py
index 0bc884ac..0c9aca4c 100644
--- a/DemoPrograms/Demo_Paned_Window.py
+++ b/DemoPrograms/Demo_Paned_Window.py
@@ -1,45 +1,52 @@
import PySimpleGUI as sg
-sg.ChangeLookAndFeel('GreenTan')
+sg.change_look_and_feel('GreenTan')
-col1 = sg.Column([[sg.Text('in pane1', text_color='blue')],
- [sg.T('Pane1')],
- [sg.T('Pane1')],
- ])
-col2 = sg.Column([[sg.Text('in pane2', text_color='red')],
- [sg.T('Pane2')],
- [sg.Input(key='_IN2_', do_not_clear=True)],
- [sg.T('Pane2')],
- [sg.T('Pane2')],
- ], key='_COL2_', visible=False)
-col3 = sg.Column([[sg.Text('in pane 4', text_color='green')],
- [sg.In(key='_IN3_', enable_events=True, do_not_clear=True)],
- ], key='_COL3_', visible=False)
-col4 = sg.Column([[sg.Text('Column 4', text_color='firebrick')],
- [sg.In()],
- ], key='_COL4_')
-col5 = sg.Column([[sg.Frame('Frame', [[sg.Text('Column 5', text_color='purple')],
- [sg.In()],
- ])]])
+col1 = sg.Col([[sg.Text('in pane1', text_color='blue')],
+ [sg.Text('Pane1')],
+ [sg.Text('Pane1')],
+ ])
+col2 = sg.Col([[sg.Text('in pane2', text_color='red')],
+ [sg.Text('Pane2')],
+ [sg.Input('', key='-IN2-')],
+ [sg.Text('Pane2')],
+ [sg.Text('Pane2')],
+ ], key='-COL2-', visible=False)
+col3 = sg.Col([[sg.Text('in pane 4', text_color='green')],
+ [sg.Input(key='-IN3-', enable_events=True)],
+ ], key='-COL3-', visible=False)
+col4 = sg.Col([[sg.Text('Column 4', text_color='firebrick')],
+ [sg.Input()],
+ ], key='-COL4-')
+col5 = sg.Col([[sg.Frame('Frame', [[sg.Text('Column 5', text_color='purple')],
+ [sg.Input()],
+ ])]])
-layout = [ [sg.Text('Click'), sg.Text('', key='_OUTPUT_')],
- [sg.Button('Remove'), sg.Button('Add')],
- [sg.Pane([col5, sg.Column([[sg.Pane([col1, col2, col4], handle_size=15, orientation='v', background_color='red', show_handle=True, visible=True, key='_PANE_', border_width=0, relief=sg.RELIEF_GROOVE),]]),col3 ], orientation='h', background_color=None, size=(160,160), relief=sg.RELIEF_RAISED, border_width=0)]
- ]
+layout = [[sg.Text('Click'), sg.Text('', key='-OUTPUT-')],
+ [sg.Button('Remove'), sg.Button('Add')],
+ [sg.Pane([col5,
+ sg.Col([[sg.Pane([col1, col2, col4], handle_size=15,
+ orientation='v', background_color='red', show_handle=True,
+ visible=True, key='-PANE-', border_width=0,
+ relief=sg.RELIEF_GROOVE), ]]), col3],
+ orientation='h', background_color=None, size=(160, 160),
+ relief=sg.RELIEF_RAISED, border_width=0)]
+ ]
-window = sg.Window('Window Title', default_element_size=(15,1), resizable=True, border_depth=5).Layout(layout)
+window = sg.Window('Window Title', layout, border_depth=5,
+ default_element_size=(15, 1), resizable=True)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
if event == 'Remove':
- window.Element('_COL2_').Update(visible=False)
- window.Element('_COL3_').Update(visible=False)
+ window['-COL2-'].update(visible=False)
+ window['-COL3-'].update(visible=False)
elif event == 'Add':
- window.Element('_COL2_').Update(visible=True)
- window.Element('_COL3_').Update(visible=True)
- window.Element('_IN2_').Update(values['_IN3_'])
+ window['-COL2-'].update(visible=True)
+ window['-COL3-'].update(visible=True)
+ window['-IN2-'].update(values['-IN3-'])
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Password_Login.py b/DemoPrograms/Demo_Password_Login.py
index 1f7abfbd..f33be448 100644
--- a/DemoPrograms/Demo_Password_Login.py
+++ b/DemoPrograms/Demo_Password_Login.py
@@ -1,11 +1,6 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import hashlib
-from sys import exit as exit
"""
Create a secure login for your scripts without having to include your password
@@ -19,47 +14,59 @@ from sys import exit as exit
matching password to the hash code in this example gets a $5 PayPal payment
"""
-# Use this GUI to get your password's hash code
-def HashGeneratorGUI():
- layout = [[sg.T('Password Hash Generator', size=(30,1), font='Any 15')],
- [sg.T('Password'), sg.In(key='password')],
- [sg.T('SHA Hash'), sg.In('', size=(40,1), key='hash')],
- ]
- window = sg.Window('SHA Generator', auto_size_text=False, default_element_size=(10,1),
- text_justification='r', return_keyboard_events=True, grab_anywhere=False).Layout(layout)
+def main():
+ # Use this GUI to get your password's hash code
+ def HashGeneratorGUI():
+ layout = [
+ [sg.Text('Password Hash Generator', size=(30, 1), font='Any 15')],
+ [sg.Text('Password'), sg.Input(key='-password-')],
+ [sg.Text('SHA Hash'), sg.Input('', size=(40, 1), key='hash')],
+ ]
- while True:
- event, values = window.Read()
- if event is None:
- exit(69)
+ window = sg.Window('SHA Generator', layout,
+ auto_size_text=False,
+ default_element_size=(10, 1),
+ text_justification='r',
+ return_keyboard_events=True,
+ grab_anywhere=False)
- password = values['password']
- try:
- password_utf = password.encode('utf-8')
- sha1hash = hashlib.sha1()
- sha1hash.update(password_utf)
- password_hash = sha1hash.hexdigest()
- window.FindElement('hash').Update(password_hash)
- except:
- pass
+ while True:
+ event, values = window.read()
+ if event is None:
+ break
-# ----------------------------- Paste this code into your program / script -----------------------------
-# determine if a password matches the secret password by comparing SHA1 hash codes
-def PasswordMatches(password, hash):
- password_utf = password.encode('utf-8')
- sha1hash = hashlib.sha1()
- sha1hash.update(password_utf)
- password_hash = sha1hash.hexdigest()
- return password_hash == hash
+ password = values['-password-']
+ try:
+ password_utf = password.encode('utf-8')
+ sha1hash = hashlib.sha1()
+ sha1hash.update(password_utf)
+ password_hash = sha1hash.hexdigest()
+ window['hash'].update(password_hash)
+ except:
+ pass
+ window.close()
+
+ # ----------------------------- Paste this code into your program / script -----------------------------
+ # determine if a password matches the secret password by comparing SHA1 hash codes
+ def PasswordMatches(password, a_hash):
+ password_utf = password.encode('utf-8')
+ sha1hash = hashlib.sha1()
+ sha1hash.update(password_utf)
+ password_hash = sha1hash.hexdigest()
+ return password_hash == a_hash
+
+ login_password_hash = '6adfb183a4a2c94a2f92dab5ade762a47889a5a1' # helloworld
+ password = sg.popup_get_text(
+ 'Password: (type gui for other window)', password_char='*')
+ if password and password == 'gui': # Remove when pasting into your program
+ HashGeneratorGUI() # Remove when pasting into your program
+ return # Remove when pasting into your program
+ if PasswordMatches(password, login_password_hash):
+ print('Login SUCCESSFUL')
+ else:
+ print('Login FAILED!!')
-login_password_hash = 'e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4'
-password = sg.PopupGetText('Password', password_char='*')
-if password == 'gui': # Remove when pasting into your program
- HashGeneratorGUI() # Remove when pasting into your program
- exit(69) # Remove when pasting into your program
-if PasswordMatches(password, login_password_hash):
- print('Login SUCCESSFUL')
-else:
- print('Login FAILED!!')
+if __name__ == '__main__':
+ main()
diff --git a/DemoPrograms/Demo_Pi_LEDs.py b/DemoPrograms/Demo_Pi_LEDs.py
index c82b9583..23e28109 100644
--- a/DemoPrograms/Demo_Pi_LEDs.py
+++ b/DemoPrograms/Demo_Pi_LEDs.py
@@ -1,31 +1,30 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-# GUI for switching an LED on and off to GPIO14
-
-# GPIO and time library:
+import PySimpleGUI as sg
import time
+# App for Raspberry Pi.
+
if sys.platform == 'win32':
- from random import randint
class GPIO():
LOW = 0
HIGH = 1
BCM = OUT = 0
current_value = 0
+
@classmethod
def setmode(self, mode):
return
+
@classmethod
def setup(self, arg1, arg2):
return
+
@classmethod
def output(self, port, value):
self.current_value = value
+
@classmethod
def input(self, port):
return self.current_value
@@ -36,6 +35,7 @@ else:
GPIO.setmode(GPIO.BCM)
GPIO.setup(14, GPIO.OUT)
+
def SwitchLED():
varLedStatus = GPIO.input(14)
if varLedStatus == 0:
@@ -53,26 +53,26 @@ def FlashLED():
GPIO.output(14, GPIO.LOW)
time.sleep(0.5)
-layout = [[sg.T('Raspberry Pi LEDs')],
- [sg.T('', size=(20, 1), key='output')],
- [sg.Button('Switch LED')],
- [sg.Button('Flash LED')],
- [sg.Exit()]]
+
+layout = [[sg.Text('Raspberry Pi LEDs')],
+ [sg.Text('', size=(20, 1), key='output')],
+ [sg.Button('Switch LED')],
+ [sg.Button('Flash LED')],
+ [sg.Exit()]]
window = sg.Window('Raspberry Pi GUI', layout, grab_anywhere=False)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event in (None, 'Exit'):
break
if event == 'Switch LED':
- window.FindElement('output').Update(SwitchLED())
+ window['output'].update(SwitchLED())
elif event == 'Flash LED':
- window.FindElement('output').Update('LED is Flashing')
- window.Refresh()
+ window['output'].update('LED is Flashing')
+ window.refresh()
FlashLED()
- window.FindElement('output').Update('')
+ window['output'].update('')
-window.Close()
-sg.Popup('Done... exiting')
+window.close()
diff --git a/DemoPrograms/Demo_Pi_Robotics.py b/DemoPrograms/Demo_Pi_Robotics.py
index 0c2ea577..a3124872 100644
--- a/DemoPrograms/Demo_Pi_Robotics.py
+++ b/DemoPrograms/Demo_Pi_Robotics.py
@@ -1,9 +1,7 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
# Robotics design pattern
# Uses Realtime Buttons to simulate the controls for a robot
@@ -13,25 +11,26 @@ else:
def RemoteControlExample():
# Make a form, but don't use context manager
- sg.SetOptions(element_padding=(0,0))
- back ='#eeeeee'
- image_forward = 'ButtonGraphics/RobotForward.png'
- image_backward = 'ButtonGraphics/RobotBack.png'
- image_left = 'ButtonGraphics/RobotLeft.png'
- image_right = 'ButtonGraphics/RobotRight.png'
+ sg.set_options(element_padding=(0,0))
+ back = '#eeeeee'
- sg.SetOptions(border_width=0, button_color=('black', back), background_color=back, element_background_color=back, text_element_background_color=back)
+ sg.set_options(border_width=0,
+ button_color=('black', back),
+ background_color=back,
+ element_background_color=back,
+ text_element_background_color=back)
+ mypad = ((50,0),0)
layout = [[sg.Text('Robotics Remote Control')],
- [sg.T('', justification='center', size=(19,1), key='status')],
- [ sg.RealtimeButton('', key='Forward', image_filename=image_forward, pad=((50,0),0))],
- [ sg.RealtimeButton('', key='Left', image_filename=image_left),
- sg.RealtimeButton('', key='Right', image_filename=image_right, pad=((50,0), 0))],
- [ sg.RealtimeButton('', key='Reverse', image_filename=image_backward, pad=((50,0),0))],
- [sg.T('')],
+ [sg.Text('', justification='center', size=(19,1), key='status')],
+ [ sg.RealtimeButton('', key='Forward', pad=mypad)],
+ [ sg.RealtimeButton('', key='Left', ),
+ sg.RealtimeButton('', key='Right', pad=mypad)],
+ [ sg.RealtimeButton('', key='Reverse', pad=mypad)],
+ [sg.Text('')],
[sg.Quit(button_color=('black', 'orange'))]]
- window = sg.Window('Robotics Remote Control', auto_size_text=True, grab_anywhere=False).Layout(layout)
+ window = sg.Window('Robotics Remote Control', layout, grab_anywhere=False)
#
# Some place later in your code...
@@ -39,32 +38,32 @@ def RemoteControlExample():
# else it won't refresh.
#
# your program's main loop
- while (True):
+ while True:
# This is the code that reads and updates your window
- event, values = window.Read(timeout=0, timeout_key='timeout')
+ event, values = window.read(timeout=0, timeout_key='timeout')
if event is not None:
- window.FindElement('status').Update(event)
+ window['status'].update(event)
elif event != 'timeout':
- window.FindElement('status').Update('')
+ window['status'].update('')
# if user clicked quit button OR closed the form using the X, then break out of loop
if event == 'Quit' or values is None:
break
- window.Close()
+ window.close()
def RemoteControlExample_NoGraphics():
# Make a form, but don't use context manager
layout = [[sg.Text('Robotics Remote Control', justification='center')],
- [sg.T('', justification='center', size=(19,1), key='status')],
- [sg.T(' '*8), sg.RealtimeButton('Forward')],
- [ sg.RealtimeButton('Left'), sg.T(' '), sg.RealtimeButton('Right')],
- [sg.T(' '*8), sg.RealtimeButton('Reverse')],
- [sg.T('')],
+ [sg.Text('', justification='center', size=(19,1), key='status')],
+ [sg.Text(' '*8), sg.RealtimeButton('Forward')],
+ [ sg.RealtimeButton('Left'), sg.Text(' '), sg.RealtimeButton('Right')],
+ [sg.Text(' '*8), sg.RealtimeButton('Reverse')],
+ [sg.Text('')],
[sg.Quit(button_color=('black', 'orange'))]]
# Display form to user
- window = sg.Window('Robotics Remote Control', auto_size_text=True, grab_anywhere=False).Layout(layout)
+ window = sg.Window('Robotics Remote Control', layout, grab_anywhere=False)
#
# Some place later in your code...
@@ -75,26 +74,26 @@ def RemoteControlExample_NoGraphics():
# responsive the GUI itself will be Match your timeout with your hardware's capabilities
#
# your program's main loop
- while (True):
+ while True :
# This is the code that reads and updates your window
- event, values = window.Read(timeout=100, timeout_key='timeout')
+ event, values = window.read(timeout=100, timeout_key='timeout')
# print(event, values)
if event != 'timeout':
- window.FindElement('status').Update(event)
+ window['status'].update(event)
else:
- window.FindElement('status').Update('')
+ window['status'].update('')
# if user clicked quit button OR closed the form using the X, then break out of loop
if event in (None, 'Quit'):
break
- window.Close()
+ window.close()
# ------------------------------------- main -------------------------------------
def main():
RemoteControlExample_NoGraphics()
# Uncomment to get the fancy graphics version. Be sure and download the button images!
RemoteControlExample()
- # sg.Popup('End of non-blocking demonstration')
+ # sg.popup('End of non-blocking demonstration')
if __name__ == '__main__':
diff --git a/DemoPrograms/Demo_Ping_Line_Graph.py b/DemoPrograms/Demo_Ping_Line_Graph.py
index d7e8f182..f3b0b829 100644
--- a/DemoPrograms/Demo_Ping_Line_Graph.py
+++ b/DemoPrograms/Demo_Ping_Line_Graph.py
@@ -1,17 +1,11 @@
-#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
-from threading import Thread
-import time
-from sys import exit as exit
-
# !/usr/bin/env python3
# -*- coding: utf-8 -*-
+from sys import exit as exit
+from threading import Thread
+import PySimpleGUI as sg
+import time
+
"""
A pure python ping implementation using raw sockets.
@@ -217,7 +211,13 @@ from sys import exit as exit
# =============================================================================#
import argparse
-import os, sys, socket, struct, select, time, signal
+import os
+import sys
+import socket
+import struct
+import select
+import time
+import signal
__description__ = 'A pure python ICMP ping implementation using raw sockets.'
@@ -265,7 +265,7 @@ def checksum(source_string):
Network data is big-endian, hosts are typically little-endian
"""
countTo = (int(len(source_string) / 2)) * 2
- sum = 0
+ sum_val = 0
count = 0
# Handle bytes in pairs (decoding as short ints)
@@ -279,9 +279,9 @@ def checksum(source_string):
loByte = source_string[count + 1]
hiByte = source_string[count]
try: # For Python3
- sum = sum + (hiByte * 256 + loByte)
+ sum_val = sum_val + (hiByte * 256 + loByte)
except: # For Python2
- sum = sum + (ord(hiByte) * 256 + ord(loByte))
+ sum_val = sum_val + (ord(hiByte) * 256 + ord(loByte))
count += 2
# Handle last byte if applicable (odd-number of bytes)
@@ -289,16 +289,16 @@ def checksum(source_string):
if countTo < len(source_string): # Check for odd length
loByte = source_string[len(source_string) - 1]
try: # For Python3
- sum += loByte
+ sum_val += loByte
except: # For Python2
- sum += ord(loByte)
+ sum_val += ord(loByte)
- sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
+ sum_val &= 0xffffffff # Truncate sum_val to 32 bits (a variance from ping.c, which
# uses signed ints, but overflow is unlikely in ping)
- sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
- sum += (sum >> 16) # Add carry from above (if any)
- answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ sum_val = (sum_val >> 16) + (sum_val & 0xffff) # Add high 16 bits to low 16 bits
+ sum_val += (sum_val >> 16) # Add carry from above (if any)
+ answer = ~sum_val & 0xffff # Invert and truncate to 16 bits
answer = socket.htons(answer)
return answer
@@ -312,7 +312,8 @@ def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=F
delay = None
try: # One could use UDP here, but it's obscure
- mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ mySocket = socket.socket(
+ socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
except socket.error as e:
print("failed. (socket error: '%s')" % e.args[1])
raise # raise the original error
@@ -326,7 +327,8 @@ def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=F
myStats.pktsSent += 1
- recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout)
+ recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(
+ mySocket, my_ID, timeout)
mySocket.close()
@@ -335,7 +337,7 @@ def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=F
if not quiet:
print("%d bytes from %s: icmp_seq=%d ttl=%d time=%d ms" % (
dataSize, socket.inet_ntoa(struct.pack("!I", iphSrcIP)), icmpSeqNumber, iphTTL, delay)
- )
+ )
myStats.pktsRcvd += 1
myStats.totTime += delay
if myStats.minTime > delay:
@@ -361,9 +363,7 @@ def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
myChecksum = 0
# Make a dummy heder with a 0 checksum.
- header = struct.pack(
- "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
- )
+ header = struct.pack("!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber )
padBytes = []
startVal = 0x42
@@ -385,16 +385,15 @@ def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
# Now that we have the right checksum, we put that in. It's just easier
# to make up a new header than to stuff it into the dummy.
- header = struct.pack(
- "!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber
- )
+ header = struct.pack("!BBHHH", ICMP_ECHO, 0, myChecksum, myID, mySeqNumber )
packet = header + data
sendTime = default_timer()
try:
- mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP
+ # Port number is irrelevant for ICMP
+ mySocket.sendto(packet, (destIP, 1))
except socket.error as e:
print("General failure (%s)" % (e.args[1]))
return
@@ -422,16 +421,14 @@ def receive_one_ping(mySocket, myID, timeout):
ipHeader = recPacket[:20]
iphVersion, iphTypeOfSvc, iphLength, \
- iphID, iphFlags, iphTTL, iphProtocol, \
- iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
- "!BBHHHBBHII", ipHeader
- )
+ iphID, iphFlags, iphTTL, iphProtocol, \
+ iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
+ "!BBHHHBBHII", ipHeader
+ )
icmpHeader = recPacket[20:28]
icmpType, icmpCode, icmpChecksum, \
- icmpPacketID, icmpSeqNumber = struct.unpack(
- "!BBHHH", icmpHeader
- )
+ icmpPacketID, icmpSeqNumber = struct.unpack("!BBHHH", icmpHeader)
if icmpPacketID == myID: # Our packet
dataSize = len(recPacket) - 28
@@ -451,7 +448,8 @@ def dump_stats(myStats):
print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP))
if myStats.pktsSent > 0:
- myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent
+ myStats.fracLoss = (myStats.pktsSent -
+ myStats.pktsRcvd) / myStats.pktsSent
print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss
@@ -494,7 +492,8 @@ def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
try:
destIP = socket.gethostbyname(hostname)
- print("\nPYTHON PING %s (%s): %d data bytes" % (hostname, destIP, packet_size))
+ print("\nPYTHON PING %s (%s): %d data bytes" %
+ (hostname, destIP, packet_size))
except socket.gaierror as e:
print("\nPYTHON PING: Unknown host: %s (%s)" % (hostname, e.args[1]))
print()
@@ -503,7 +502,8 @@ def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
myStats.thisIP = destIP
for i in range(count):
- delay = do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size)
+ delay = do_one(myStats, destIP, hostname,
+ timeout, mySeqNumber, packet_size)
if delay == None:
delay = 0
@@ -556,7 +556,8 @@ def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
time.sleep((MAX_SLEEP - delay) / 1000)
if myStats.pktsSent > 0:
- myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd) / myStats.pktsSent
+ myStats.fracLoss = (myStats.pktsSent -
+ myStats.pktsRcvd) / myStats.pktsSent
if myStats.pktsRcvd > 0:
myStats.avrgTime = myStats.totTime / myStats.pktsRcvd
@@ -604,6 +605,7 @@ y_top = 500
g_exit = False
g_response_time = None
+
def ping_thread(args):
global g_exit, g_response_time
@@ -611,7 +613,7 @@ def ping_thread(args):
g_response_time = quiet_ping('google.com', timeout=1000)
-def convert_xy_to_canvas_xy(x_in,y_in):
+def convert_xy_to_canvas_xy(x_in, y_in):
scale_x = (canvas_right - canvas_left) / (x_right - x_left)
scale_y = (canvas_top - canvas_bottom) / (y_top - y_bottom)
new_x = canvas_left + scale_x * (x_in - x_left)
@@ -619,26 +621,27 @@ def convert_xy_to_canvas_xy(x_in,y_in):
return new_x, new_y
-
# start ping measurement thread
thread = Thread(target=ping_thread, args=(None,))
thread.start()
-layout = [ [sg.T('Ping times to Google.com', font='Any 18')],
- [sg.Canvas(size=(canvas_right, canvas_bottom), background_color='white', key='canvas')],
- [sg.Quit()] ]
+layout = [[sg.Text('Ping times to Google.com', font='Any 18')],
+ [sg.Canvas(size=(canvas_right, canvas_bottom),
+ background_color='white', key='canvas')],
+ [sg.Quit()]]
-window = sg.Window('Ping Times To Google.com', grab_anywhere=True).Layout(layout).Finalize()
+window = sg.Window('Ping Times To Google.com',
+ layout, grab_anywhere=True, finalize=True)
-canvas = window.FindElement('canvas').TKCanvas
+canvas = window['canvas'].TKCanvas
prev_response_time = None
-i=0
-prev_x, prev_y = canvas_left, canvas_bottom
+i = 0
+prev_x, prev_y = canvas_left, canvas_bottom
while True:
time.sleep(.2)
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event == 'Quit' or event is None:
break
@@ -646,7 +649,8 @@ while True:
continue
try:
new_x, new_y = convert_xy_to_canvas_xy(i, g_response_time[0])
- except: continue
+ except:
+ continue
prev_response_time = g_response_time
canvas.create_line(prev_x, prev_y, new_x, new_y, width=1, fill='black')
@@ -655,11 +659,12 @@ while True:
i = 0
prev_x = prev_y = last_x = last_y = 0
canvas.delete('all')
- else: i += 1
+ else:
+ i += 1
# tell thread we're done. wait for thread to exit
g_exit = True
thread.join()
-
-exit(69)
\ No newline at end of file
+window.close()
+exit(0)
diff --git a/DemoPrograms/Demo_Pong.py b/DemoPrograms/Demo_Pong.py
index df4111e4..a0f55e7b 100644
--- a/DemoPrograms/Demo_Pong.py
+++ b/DemoPrograms/Demo_Pong.py
@@ -1,12 +1,8 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
import random
import time
-from sys import exit as exit
"""
@@ -14,6 +10,7 @@ from sys import exit as exit
Modified. Original code: https://www.pygame.org/project/3649/5739
"""
+
class Ball:
def __init__(self, canvas, bat, bat2, color):
self.canvas = canvas
@@ -38,14 +35,15 @@ class Ball:
winner = 'Player Right'
return winner
-
def updatep(self, val):
self.canvas.delete(self.drawP)
- self.drawP = self.canvas.create_text(170, 50, font=('freesansbold.ttf', 40), text=str(val), fill='white')
+ self.drawP = self.canvas.create_text(170, 50, font=(
+ 'freesansbold.ttf', 40), text=str(val), fill='white')
def updatep1(self, val):
self.canvas.delete(self.drawP1)
- self.drawP1 = self.canvas.create_text(550, 50, font=('freesansbold.ttf', 40), text=str(val), fill='white')
+ self.drawP1 = self.canvas.create_text(550, 50, font=(
+ 'freesansbold.ttf', 40), text=str(val), fill='white')
def hit_bat(self, pos):
bat_pos = self.canvas.coords(self.bat.id)
@@ -132,14 +130,15 @@ class pongbat2():
def pong():
# ------------- Define GUI layout -------------
- layout = [[sg.Canvas(size=(700, 400), background_color='black', key='canvas')],
- [sg.T(''), sg.Button('Quit')]]
+ layout = [[sg.Canvas(size=(700, 400),
+ background_color='black',
+ key='canvas')],
+ [sg.Text(''), sg.Button('Quit')]]
# ------------- Create window -------------
- window = sg.Window('The Classic Game of Pong', return_keyboard_events=True).Layout(layout).Finalize()
- # window.Finalize() # TODO Replace with call to window.Finalize once code released
+ window = sg.Window('The Classic Game of Pong', layout,
+ return_keyboard_events=True, finalize=True)
- # ------------- Get the tkinter Canvas we're drawing on -------------
- canvas = window.FindElement('canvas').TKCanvas
+ canvas = window['canvas'].TKCanvas
# ------------- Create line down center, the bats and ball -------------
canvas.create_line(350, 0, 350, 400, fill='white')
@@ -155,10 +154,10 @@ def pong():
bat2.draw()
# ------------- Read the form, get keypresses -------------
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
# ------------- If quit -------------
if event is None or event == 'Quit':
- exit(69)
+ break
# ------------- Keypresses -------------
if event is not None:
if event.startswith('Up'):
@@ -171,16 +170,14 @@ def pong():
bat1.down(1)
if ball1.checkwin():
- sg.Popup('Game Over', ball1.checkwin() + ' won!!')
+ sg.popup('Game Over', ball1.checkwin() + ' won!!')
break
-
# ------------- Bottom of loop, delay between animations -------------
# time.sleep(.01)
canvas.after(10)
+ window.close()
+
if __name__ == '__main__':
pong()
-
-
-
diff --git a/DemoPrograms/Demo_Pong_Multiple_Platforms.py b/DemoPrograms/Demo_Pong_Multiple_Platforms.py
index 2e88127c..de9a6332 100644
--- a/DemoPrograms/Demo_Pong_Multiple_Platforms.py
+++ b/DemoPrograms/Demo_Pong_Multiple_Platforms.py
@@ -1,8 +1,6 @@
# !/usr/bin/env python
# Based on work by - Siddharth Natamai
# At the moment, this source file runs on TWO of the 4 PySimpleGUI ports with a third one coming soon (Qt).
-# import PySimpleGUIQt as sg # not quite working on Qt yet... needs Graph.Relocate fixed first
-# import PySimpleGUIWeb as sg
import PySimpleGUI as sg
import random
@@ -19,7 +17,8 @@ BALL_COLOR = 'green1'
num_rounds = 0
while num_rounds == 0:
try:
- num_rounds = int(sg.PopupGetText('How many rounds would you like to play?'))
+ num_rounds = int(sg.popup_get_text(
+ 'How many rounds would you like to play?'))
except Exception as e:
num_rounds = 0
@@ -33,9 +32,10 @@ class Ball:
self.player_2_Score = player_2_Starting_Score
self.draw_P1 = None
self.draw_P2 = None
- self.id = self.graph.DrawCircle(STARTING_BALL_POSITION, BALL_RADIUS, line_color=colour, fill_color=colour)
+ self.id = self.graph.draw_circle(
+ STARTING_BALL_POSITION, BALL_RADIUS, line_color=colour, fill_color=colour)
self.curx, self.cury = STARTING_BALL_POSITION
- # self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ # self.graph.relocate_figure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
self.x = random.choice([-2.5, 2.5])
self.y = -2.5
@@ -48,12 +48,14 @@ class Ball:
return winner
def update_player1_score(self, val):
- self.graph.DeleteFigure(self.draw_P1)
- self.draw_P1 = self.graph.DrawText(str(val), (170, 50), font=('Courier 60'), color='white')
+ self.graph.delete_figure(self.draw_P1)
+ self.draw_P1 = self.graph.draw_text(
+ str(val), (170, 50), font=('Courier 60'), color='white')
def update_player2_score(self, val):
- self.graph.DeleteFigure(self.draw_P2)
- self.draw_P2 = self.graph.DrawText(str(val), (550, 50), font=('courier 40'), color='white')
+ self.graph.delete_figure(self.draw_P2)
+ self.draw_P2 = self.graph.draw_text(
+ str(val), (550, 50), font=('courier 40'), color='white')
def hit_bat(self, pos):
bat_pos = (self.bat_1.curx, self.bat_1.cury)
@@ -69,11 +71,10 @@ class Ball:
return True
return False
-
def draw(self):
self.curx += self.x
self.cury += self.y
- self.graph.RelocateFigure(self.id, self.curx, self.cury)
+ self.graph.relocate_figure(self.id, self.curx, self.cury)
if self.cury <= 0: # see if hit top or bottom of play area. If so, reverse y direction
self.y = 4
self.cury = 0
@@ -82,13 +83,15 @@ class Ball:
self.cury = GAMEPLAY_SIZE[1]-BALL_RADIUS/2
if self.curx <= 0: # see if beyond player
self.player_1_Score += 1
- self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ self.graph.relocate_figure(
+ self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
self.x = 4
self.update_player2_score(self.player_1_Score)
self.curx, self.cury = STARTING_BALL_POSITION
if self.curx >= GAMEPLAY_SIZE[0]:
self.player_2_Score += 1
- self.graph.RelocateFigure(self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
+ self.graph.relocate_figure(
+ self.id, STARTING_BALL_POSITION[0], STARTING_BALL_POSITION[1])
self.x = -4
self.update_player1_score(self.player_2_Score)
self.curx, self.cury = STARTING_BALL_POSITION
@@ -98,10 +101,11 @@ class Ball:
self.x = -4
-class PongBat():
- def __init__(self, graph:sg.Graph, colour, x, width=BAT_SIZE[0], height=BAT_SIZE[1]):
+class PongBall():
+ def __init__(self, graph: sg.Graph, colour, x, width=BAT_SIZE[0], height=BAT_SIZE[1]):
self.graph = graph
- self.id = graph.DrawRectangle((x - width / 2, 200), (x + width / 2, 200 + height), fill_color=colour)
+ self.id = graph.draw_rectangle(
+ (x - width / 2, 200), (x + width / 2, 200 + height), fill_color=colour)
self.y = 0
self.x = x
self.curx = x
@@ -119,7 +123,7 @@ class PongBat():
return pos
def draw(self):
- self.graph.RelocateFigure(self.id, self.curx, self.cury)
+ self.graph.relocate_figure(self.id, self.curx, self.cury)
if self.cury + self.y + BAT_SIZE[1] <= GAMEPLAY_SIZE[1] and self.cury + self.y + BAT_SIZE[1] >= 0:
self.cury += self.y
if self.cury <= 0:
@@ -131,16 +135,28 @@ class PongBat():
def pong():
- layout = [[sg.Graph(GAMEPLAY_SIZE, (0,GAMEPLAY_SIZE[1]), (GAMEPLAY_SIZE[0],0), background_color=BACKGROUND_COLOR, key='_GRAPH_')],
- [sg.T(''), sg.Button('Exit'), sg.T('Speed'), sg.Slider((0,20),default_value=10, orientation='h', enable_events=True, key='_SPEED_')]]
+ layout = [[sg.Graph(GAMEPLAY_SIZE,
+ (0, GAMEPLAY_SIZE[1]),
+ (GAMEPLAY_SIZE[0], 0),
+ background_color=BACKGROUND_COLOR,
+ key='-GRAPH-')],
+ [sg.Text(''),
+ sg.Button('Exit'),
+ sg.Text('Speed'),
+ sg.Slider((0, 20),
+ default_value=10,
+ orientation='h',
+ enable_events=True,
+ key='-SPEED-')]
+ ]
- window = sg.Window('Pong', layout, return_keyboard_events=True).Finalize()
+ window = sg.Window(
+ 'Pong', layout, return_keyboard_events=True, finalize=True)
- graph_elem = window.FindElement('_GRAPH_') # type: sg.Graph
-
- bat_1 = PongBat(graph_elem, 'red', 30)
- bat_2 = PongBat(graph_elem, 'blue', 670)
+ graph_elem = window['-GRAPH-'] # type: sg.Graph
+ bat_1 = PongBall(graph_elem, 'red', 30)
+ bat_2 = PongBall(graph_elem, 'blue', 670)
ball_1 = Ball(graph_elem, bat_1, bat_2, 'green1')
sleep_time = 10
@@ -149,8 +165,9 @@ def pong():
bat_1.draw()
bat_2.draw()
- event, values = window.Read(timeout=sleep_time) # type: str, str
- if event is None or event == 'Exit':
+ event, values = window.read(
+ timeout=sleep_time) # type: str, str
+ if event in (None, 'Exit'):
break
elif event.startswith('Up') or event.endswith('Up'):
bat_2.up(5)
@@ -160,13 +177,14 @@ def pong():
bat_1.up(5)
elif event == 's':
bat_1.down(5)
- elif event == '_SPEED_':
- sleep_time = int(values['_SPEED_'])
+ elif event == '-SPEED-':
+ sleep_time = int(values['-SPEED-'])
if ball_1.win_loss_check():
- sg.Popup('Game Over', ball_1.win_loss_check() + ' won!!')
+ sg.popup('Game Over', ball_1.win_loss_check() + ' won!!')
break
- window.Close()
+ window.close()
+
if __name__ == '__main__':
pong()
diff --git a/DemoPrograms/Demo_Popup_Custom.py b/DemoPrograms/Demo_Popup_Custom.py
index 718adb8f..32bc6a35 100644
--- a/DemoPrograms/Demo_Popup_Custom.py
+++ b/DemoPrograms/Demo_Popup_Custom.py
@@ -1,14 +1,10 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
'''
Use this code as a starting point for creating your own Popup functions.
Rather than creating a long list of Popup high-level API calls, PySimpleGUI provides
- you with the tools to easily create your own. If you need more than what the standard PopupGetText and
+ you with the tools to easily create your own. If you need more than what the standard popup_get_text and
other calls provide, then it's time for you to graduate into making your own windows. Or, maybe you need
another window that pops-up over your primary window. Whatever the need, don't hesitate to dive in
and create your own Popup call.
@@ -19,15 +15,15 @@ else:
def PopupDropDown(title, text, values):
- window = sg.Window(title).Layout([[sg.Text(text)],
- [sg.DropDown(values, key='_DROP_')],
- [sg.OK(), sg.Cancel()]])
- event, values = window.Read()
- return None if event != 'OK' else values['_DROP_']
-
+ window = sg.Window(title,
+ [[sg.Text(text)],
+ [sg.DropDown(values, key='-DROP-')],
+ [sg.OK(), sg.Cancel()]
+ ])
+ event, values = window.read()
+ return None if event != 'OK' else values['-DROP-']
# ----------------------- Calling your PopupDropDown function -----------------------
values = ['choice {}'.format(x) for x in range(30)]
-
print(PopupDropDown('My Title', 'Please make a selection', values))
diff --git a/DemoPrograms/Demo_Popups.py b/DemoPrograms/Demo_Popups.py
index a2435a82..6ba21181 100644
--- a/DemoPrograms/Demo_Popups.py
+++ b/DemoPrograms/Demo_Popups.py
@@ -1,37 +1,34 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-from PySimpleGUI import Print as print
+'''
+ Usage of all Popups in PSG
+'''
-
-print('test')
-sg.PopupGetFile('Get file', save_as=True,file_types=(("ALL Files", "*.jpg"),))
+sg.Print('test')
+sg.popup_get_file('Get file', save_as=True,
+ file_types=(("ALL Files", "*.jpg"),))
# Here, have some windows on me....
-[sg.PopupNoWait('No-wait Popup', location=(500+100*x,500)) for x in range(10)]
-
-answer = sg.PopupYesNo('Do not worry about all those open windows... they will disappear at the end', 'Are you OK with that?')
+[sg.popup_no_wait('No-wait Popup', location=(500+100*x, 500))
+ for x in range(10)]
+answer = sg.popup_yes_no(
+ 'Do not worry about all those open windows... they will disappear at the end', 'Are you OK with that?')
if answer == 'No':
- sg.PopupCancel('OK, we will destroy those windows as soon as you close this window')
+ sg.popup_cancel(
+ 'OK, we will destroy those windows as soon as you close this window')
sys.exit()
-sg.PopupNonBlocking('Your answer was',answer, location=(1000,600))
-
-text = sg.PopupGetText('This is a call to PopopGetText', location=(1000,200))
-sg.PopupGetFile('Get file')
-sg.PopupGetFolder('Get folder')
-
-
-sg.Popup('Simple popup')
-
-sg.PopupNoTitlebar('No titlebar')
-sg.PopupNoBorder('No border')
-sg.PopupNoFrame('No frame')
-sg.PopupCancel('Cancel')
-sg.PopupOKCancel('OK Cancel')
-sg.PopupAutoClose('Autoclose')
+sg.popup_non_blocking('Your answer was', answer, location=(1000, 600))
+text = sg.popup_get_text(
+ 'This is a call to PopopGetText', location=(1000, 200))
+sg.popup_get_file('Get file')
+sg.popup_get_folder('Get folder')
+sg.popup('Simple popup')
+sg.popup_no_titlebar('No titlebar')
+sg.popup_no_border('No border')
+sg.popup_no_frame('No frame')
+sg.popup_cancel('Cancel')
+sg.popup_okCancel('OK Cancel')
+sg.popup_auto_close('Autoclose')
diff --git a/DemoPrograms/Demo_Progress_Meters.py b/DemoPrograms/Demo_Progress_Meters.py
index 0ffc4c8e..c5d6c232 100644
--- a/DemoPrograms/Demo_Progress_Meters.py
+++ b/DemoPrograms/Demo_Progress_Meters.py
@@ -1,59 +1,72 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
from time import sleep
"""
- Demonstration of simple and multiple OneLineProgressMeter's as well as the Progress Meter Element
+ Demonstration of simple and multiple one_line_progress_meter's as well as the Progress Meter Element
There are 4 demos
1. Manually updated progress bar
2. Custom progress bar built into your window, updated in a loop
- 3. OneLineProgressMeters, nested meters showing how 2 can be run at the same time.
- 4. An "iterable" style progress meter - a wrapper for OneLineProgressMeters
+ 3. one_line_progress_meters, nested meters showing how 2 can be run at the same time.
+ 4. An "iterable" style progress meter - a wrapper for one_line_progress_meters
If the software determined that a meter should be cancelled early,
calling OneLineProgresMeterCancel(key) will cancel the meter with the matching key
"""
-
"""
The simple case is that you want to add a single meter to your code. The one-line solution.
- This demo function shows 3 different OneLineProgressMeter tests
+ This demo function shows 3 different one_line_progress_meter tests
1. A horizontal with red and white bar colors
2. A vertical bar with default colors
3. A test showing 2 running at the same time
"""
+
def demo_one_line_progress_meter():
# Display a progress meter. Allow user to break out of loop using cancel button
for i in range(10000):
- if not sg.OneLineProgressMeter('My 1-line progress meter', i+1, 10000, 'meter key','MY MESSAGE1', 'MY MESSAGE 2', orientation='h', bar_color=('white', 'red')):
+ if not sg.one_line_progress_meter('My 1-line progress meter',
+ i+1, 10000,
+ 'meter key',
+ 'MY MESSAGE1',
+ 'MY MESSAGE 2',
+ orientation='h',
+ bar_color=('white', 'red')):
print('Hit the break')
break
for i in range(10000):
- if not sg.OneLineProgressMeter('My 1-line progress meter', i+1, 10000, 'meter key', 'MY MESSAGE1', 'MY MESSAGE 2',orientation='v' ):
+ if not sg.one_line_progress_meter('My 1-line progress meter',
+ i+1, 10000,
+ 'meter key',
+ 'MY MESSAGE1',
+ 'MY MESSAGE 2',
+ orientation='v'):
print('Hit the break')
break
layout = [
- [sg.T('One-Line Progress Meter Demo', font=('Any 18'))],
- [sg.T('Outer Loop Count', size=(15,1), justification='r'), sg.In(default_text='100', size=(5,1), key='CountOuter', do_not_clear=True),
- sg.T('Delay'), sg.In(default_text='10', key='TimeOuter', size=(5,1), do_not_clear=True), sg.T('ms')],
- [sg.T('Inner Loop Count', size=(15,1), justification='r'), sg.In(default_text='100', size=(5,1), key='CountInner', do_not_clear=True) ,
- sg.T('Delay'), sg.In(default_text='10', key='TimeInner', size=(5,1), do_not_clear=True), sg.T('ms')],
- [sg.Button('Show', pad=((0,0), 3), bind_return_key=True), sg.T('me the meters!')]
- ]
+ [sg.Text('One-Line Progress Meter Demo', font=('Any 18'))],
- window = sg.Window('One-Line Progress Meter Demo').Layout(layout)
+ [sg.Text('Outer Loop Count', size=(15, 1), justification='r'),
+ sg.Input(default_text='100', size=(5, 1), key='CountOuter'),
+ sg.Text('Delay'), sg.Input(default_text='10', key='TimeOuter', size=(5, 1)), sg.Text('ms')],
+
+ [sg.Text('Inner Loop Count', size=(15, 1), justification='r'),
+ sg.Input(default_text='100', size=(5, 1), key='CountInner'),
+ sg.Text('Delay'), sg.Input(default_text='10', key='TimeInner', size=(5, 1)), sg.Text('ms')],
+
+ [sg.Button('Show', pad=((0, 0), 3), bind_return_key=True),
+ sg.Text('me the meters!')]
+ ]
+
+ window = sg.Window('One-Line Progress Meter Demo', layout)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event == 'Show':
@@ -62,13 +75,14 @@ def demo_one_line_progress_meter():
delay_inner = int(values['TimeInner'])
delay_outer = int(values['TimeOuter'])
for i in range(max_outer):
- if not sg.OneLineProgressMeter('Outer Loop', i+1, max_outer, 'outer'):
+ if not sg.one_line_progress_meter('Outer Loop', i+1, max_outer, 'outer'):
break
sleep(delay_outer/1000)
for j in range(max_inner):
- if not sg.OneLineProgressMeter('Inner Loop', j+1, max_inner, 'inner'):
+ if not sg.one_line_progress_meter('Inner Loop', j+1, max_inner, 'inner'):
break
sleep(delay_inner/1000)
+ window.close()
'''
@@ -81,36 +95,39 @@ def demo_one_line_progress_meter():
these kinds of sleeps to a GUI based program normally.
'''
+
def manually_updated_meter_test():
# layout the form
layout = [[sg.Text('This meter is manually updated 4 times')],
- [sg.ProgressBar(max_value=10, orientation='h', size=(20,20), key='progress')]]
+ [sg.ProgressBar(max_value=10, orientation='h', size=(20, 20), key='progress')]]
# create the form`
- window = sg.Window('Custom Progress Meter', layout).Finalize() # must finalize since not running an event loop
+ # must finalize since not running an event loop
+ window = sg.Window('Custom Progress Meter', layout, finalize=True)
- progress_bar = window.FindElement('progress') # Get the element to make updating easier
+ # Get the element to make updating easier
+ progress_bar = window['progress']
# -------------------- Your Program Code --------------------
# Spot #1 to indicate progress
- progress_bar.UpdateBar(1) # show 10% complete
+ progress_bar.update_bar(1) # show 10% complete
sleep(2)
# more of your code.... perhaps pages and pages of code.
# Spot #2 to indicate progress
- progress_bar.UpdateBar(2) # show 20% complete
+ progress_bar.update_bar(2) # show 20% complete
sleep(2)
# more of your code.... perhaps pages and pages of code.
# Spot #3 to indicate progress
- progress_bar.UpdateBar(6) # show 60% complete
+ progress_bar.update_bar(6) # show 60% complete
sleep(2)
# more of your code.... perhaps pages and pages of code.
# Spot #4 to indicate progress
- progress_bar.UpdateBar(9) # show 90% complete
+ progress_bar.update_bar(9) # show 90% complete
sleep(2)
- window.Close()
+ window.close()
'''
@@ -118,29 +135,30 @@ def manually_updated_meter_test():
how to update the bar to indicate progress is being made
'''
+
def custom_meter_example():
# layout the form
layout = [[sg.Text('A typical custom progress meter')],
- [sg.ProgressBar(1, orientation='h', size=(20,20), key='progress')],
+ [sg.ProgressBar(1, orientation='h', size=(20, 20), key='progress')],
[sg.Cancel()]]
# create the form`
- window = sg.Window('Custom Progress Meter').Layout(layout)
- progress_bar = window.FindElement('progress')
+ window = sg.Window('Custom Progress Meter', layout)
+ progress_bar = window['progress']
# loop that would normally do something useful
for i in range(10000):
# check to see if the cancel button was clicked and exit loop if clicked
- event, values = window.Read(timeout=0)
+ event, values = window.read(timeout=0)
if event == 'Cancel' or event == None:
break
# update bar with loop value +1 so that bar eventually reaches the maximum
- progress_bar.UpdateBar(i+1, 10000)
+ progress_bar.update_bar(i+1, 10000)
# done with loop... need to destroy the window as it's still open
- window.Close()
+ window.close()
'''
- A Wrapper for OneLineProgressMeter that combines your iterable with a progress meter into a single interface. Two functions are provided
+ A Wrapper for one_line_progress_meter that combines your iterable with a progress meter into a single interface. Two functions are provided
progess_bar - The basic wrapper
progress_bar_range - A "convienence function" if you wanted to specify like a range
'''
@@ -153,13 +171,13 @@ def progress_bar(key, iterable, *args, title='', **kwargs):
:param iterable: your iterable
:param args: To be shown in one line progress meter
:param title: Title shown in meter window
- :param kwargs: Other arguments to pass to OneLineProgressMeter
+ :param kwargs: Other arguments to pass to one_line_progress_meter
:return:
"""
- sg.OneLineProgressMeter(title, 0, len(iterable), key, *args, **kwargs)
+ sg.one_line_progress_meter(title, 0, len(iterable), key, *args, **kwargs)
for i, val in enumerate(iterable):
yield val
- if not sg.OneLineProgressMeter(title, i+1, len(iterable), key, *args, **kwargs):
+ if not sg.one_line_progress_meter(title, i+1, len(iterable), key, *args, **kwargs):
break
@@ -179,7 +197,8 @@ def progress_bar_range(key, start, stop=None, step=1, *args, **kwargs):
# -------------------- Demo Usage --------------------
def demo_iterable_progress_bar():
- my_list = list(range(1000)) # start with a list of 100 integers as the user's list
+ # start with a list of 100 integers as the user's list
+ my_list = list(range(1000))
# first form takes an iterable and a key and will return a value from your iterable
# and bump the progress meter at the same time
@@ -191,8 +210,7 @@ def demo_iterable_progress_bar():
my_list = [x for x in progress_bar('bar1', my_list)]
-
demo_iterable_progress_bar()
manually_updated_meter_test()
custom_meter_example()
-demo_one_line_progress_meter()
\ No newline at end of file
+demo_one_line_progress_meter()
diff --git a/DemoPrograms/Demo_PyGame_Integration.py b/DemoPrograms/Demo_PyGame_Integration.py
index dae260ff..e9f6c5ba 100644
--- a/DemoPrograms/Demo_PyGame_Integration.py
+++ b/DemoPrograms/Demo_PyGame_Integration.py
@@ -5,35 +5,39 @@ import os
"""
Demo of integrating PyGame with PySimpleGUI, the tkinter version
A similar technique may be possible with WxPython
- Only works on windows from what I've read
+ To make it work on Linux, set SDL_VIDEODRIVER like
+ specified in http://www.pygame.org/docs/ref/display.html, in the
+ pygame.display.init() section.
"""
# --------------------- PySimpleGUI window layout and creation --------------------
-layout = [[sg.T('Test of PySimpleGUI with PyGame')],
- [sg.Graph((500,500), (0,0), (500,500), background_color='lightblue', key='_GRAPH_' )],
- [sg.B('Draw'), sg.Exit()]]
+layout = [[sg.Text('Test of PySimpleGUI with PyGame')],
+ [sg.Graph((500, 500), (0, 0), (500, 500),
+ background_color='lightblue', key='-GRAPH-')],
+ [sg.Button('Draw'), sg.Exit()]]
-window = sg.Window('PySimpleGUI + PyGame', layout).Finalize()
-graph = window.Element('_GRAPH_')
+window = sg.Window('PySimpleGUI + PyGame', layout, finalize=True)
+graph = window['-GRAPH-']
# -------------- Magic code to integrate PyGame with tkinter -------
embed = graph.TKCanvas
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
+# change this to 'x11' to make it work on Linux
os.environ['SDL_VIDEODRIVER'] = 'windib'
# ----------------------------- PyGame Code -----------------------------
-screen = pygame.display.set_mode((500,500))
-screen.fill(pygame.Color(255,255,255))
+screen = pygame.display.set_mode((500, 500))
+screen.fill(pygame.Color(255, 255, 255))
pygame.display.init()
pygame.display.update()
while True:
- event, values = window.Read(timeout=10)
+ event, values = window.read(timeout=10)
if event in (None, 'Exit'):
break
elif event == 'Draw':
pygame.draw.circle(screen, (0, 0, 0), (250, 250), 125)
pygame.display.update()
-window.Close()
\ No newline at end of file
+window.close()
diff --git a/DemoPrograms/Demo_PyGame_Snake_Game.py b/DemoPrograms/Demo_PyGame_Snake_Game.py
index 6d1485e7..227518f7 100644
--- a/DemoPrograms/Demo_PyGame_Snake_Game.py
+++ b/DemoPrograms/Demo_PyGame_Snake_Game.py
@@ -31,6 +31,7 @@ class Segment(pygame.sprite.Sprite):
""" Class to represent one segment of the snake. """
# -- Methods
# Constructor function
+
def __init__(self, x, y):
# Call the parent's constructor
super().__init__()
@@ -46,14 +47,17 @@ class Segment(pygame.sprite.Sprite):
# --------------------------- GUI Setup & Create Window -------------------------------
-layout = [[sg.T('Snake Game - PySimpleGUI + PyGame')],
- [sg.Graph((800,600), (0,0), (800,600), background_color='lightblue', key='_GRAPH_')],
+
+layout = [[sg.Text('Snake Game - PySimpleGUI + PyGame')],
+ [sg.Graph((800, 600), (0, 0), (800, 600),
+ background_color='lightblue', key='-GRAPH-')],
[sg.Exit()]]
-window = sg.Window('Snake Game using PySimpleGUI and PyGame', layout).Finalize()
+window = sg.Window('Snake Game using PySimpleGUI and PyGame',
+ layout, finalize=True)
# ------------------------ Do the magic that integrates PyGame and Graph Element ------------------
-graph = window.Element('_GRAPH_') # type: sg.Graph
+graph = window['-GRAPH-'] # type: sg.Graph
embed = graph.TKCanvas
os.environ['SDL_WINDOWID'] = str(embed.winfo_id())
os.environ['SDL_VIDEODRIVER'] = 'windib'
@@ -61,8 +65,8 @@ os.environ['SDL_VIDEODRIVER'] = 'windib'
# ----------------------------- PyGame Code -----------------------------
# Call this function so the Pygame library can initialize itself
# pygame.init()
-screen = pygame.display.set_mode((800,600))
-screen.fill(pygame.Color(255,255,255))
+screen = pygame.display.set_mode((800, 600))
+screen.fill(pygame.Color(255, 255, 255))
pygame.display.init()
pygame.display.update()
@@ -84,7 +88,7 @@ for i in range(15):
clock = pygame.time.Clock()
while True:
- event, values = window.Read(timeout=10)
+ event, values = window.read(timeout=10)
if event in (None, 'Exit'):
break
pygame.display.update()
@@ -135,4 +139,4 @@ while True:
# Pause
clock.tick(5)
-window.Close()
+window.close()
diff --git a/DemoPrograms/Demo_Pyplot_Bar_Chart.py b/DemoPrograms/Demo_Pyplot_Bar_Chart.py
index 34265b3d..efade3dd 100644
--- a/DemoPrograms/Demo_Pyplot_Bar_Chart.py
+++ b/DemoPrograms/Demo_Pyplot_Bar_Chart.py
@@ -1,13 +1,6 @@
#!/usr/bin/env python
-import sys
-
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import matplotlib
-
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasAgg
import matplotlib.backends.tkagg as tkagg
@@ -77,11 +70,13 @@ layout = [[sg.Text('Plot test', font='Any 18')],
[sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
# create the form and show it without the plot
-window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=True).Layout(
- layout).Finalize()
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
+ layout, force_toplevel=True, finalize=True)
# add the plot to the window
-fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
+fig_photo = draw_figure(window['canvas'].TKCanvas, fig)
# show it all again and get buttons
-event, values = window.Read()
+event, values = window.read()
+
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Pyploy_Bar_Chart2.py b/DemoPrograms/Demo_Pyploy_Bar_Chart2.py
index fa02b35f..2791538f 100644
--- a/DemoPrograms/Demo_Pyploy_Bar_Chart2.py
+++ b/DemoPrograms/Demo_Pyploy_Bar_Chart2.py
@@ -1,15 +1,12 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import numpy as np
+import matplotlib.pyplot as plt
+import tkinter as Tk
+import matplotlib.backends.tkagg as tkagg
+from matplotlib.backends.backend_tkagg import FigureCanvasAgg
+import PySimpleGUI as sg
import matplotlib
matplotlib.use('TkAgg')
-from matplotlib.backends.backend_tkagg import FigureCanvasAgg
-import matplotlib.backends.tkagg as tkagg
-import tkinter as Tk
"""
Demonstrates one way of embedding Matplotlib figures into a PySimpleGUI window.
@@ -24,12 +21,11 @@ If you want to change the GUI, make changes to the GUI portion marked below.
"""
-#------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
-import matplotlib.pyplot as plt
-import numpy as np
+# ------------------------------- PASTE YOUR MATPLOTLIB CODE HERE -------------------------------
label = ['Adventure', 'Action', 'Drama', 'Comedy', 'Thriller/Suspense', 'Horror', 'Romantic Comedy', 'Musical',
'Documentary', 'Black Comedy', 'Western', 'Concert/Performance', 'Multiple Genres', 'Reality']
-no_movies = [941, 854, 4595, 2125, 942, 509, 548, 149, 1952, 161, 64, 61, 35, 5]
+no_movies = [941, 854, 4595, 2125, 942,
+ 509, 548, 149, 1952, 161, 64, 61, 35, 5]
index = np.arange(len(label))
plt.bar(index, no_movies)
@@ -38,9 +34,9 @@ plt.ylabel('No of Movies', fontsize=5)
plt.xticks(index, label, fontsize=5, rotation=30)
plt.title('Market Share for Each Genre 1995-2017')
-#------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
+# ------------------------------- END OF YOUR MATPLOTLIB CODE -------------------------------
-#------------------------------- Beginning of Matplotlib helper code -----------------------
+# ------------------------------- Beginning of Matplotlib helper code -----------------------
def draw_figure(canvas, figure, loc=(0, 0)):
@@ -59,7 +55,8 @@ def draw_figure(canvas, figure, loc=(0, 0)):
tkagg.blit(photo, figure_canvas_agg.get_renderer()._renderer, colormode=2)
return photo
-#------------------------------- Beginning of GUI CODE -------------------------------
+# ------------------------------- Beginning of GUI CODE -------------------------------
+
fig = plt.gcf() # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds
@@ -70,10 +67,13 @@ layout = [[sg.Text('Plot test', font='Any 18')],
[sg.OK(pad=((figure_w / 2, 0), 3), size=(4, 2))]]
# create the form and show it without the plot
-window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI', force_toplevel=True).Layout(layout).Finalize()
+window = sg.Window('Demo Application - Embedding Matplotlib In PySimpleGUI',
+ layout, force_toplevel=True, finalize=True)
# add the plot to the window
-fig_photo = draw_figure(window.FindElement('canvas').TKCanvas, fig)
+fig_photo = draw_figure(window['canvas'].TKCanvas, fig)
# show it all again and get buttons
-event, values = window.Read()
+event, values = window.read()
+
+window.close()
diff --git a/DemoPrograms/Demo_Script_Launcher.py b/DemoPrograms/Demo_Script_Launcher.py
index 7dff6a4f..8137d363 100644
--- a/DemoPrograms/Demo_Script_Launcher.py
+++ b/DemoPrograms/Demo_Script_Launcher.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import glob
import ntpath
import subprocess
@@ -11,13 +7,16 @@ import subprocess
LOCATION_OF_YOUR_SCRIPTS = ''
# Execute the command. Will not see the output from the command until it completes.
+
+
def execute_command_blocking(command, *args):
expanded_args = []
for a in args:
expanded_args.append(a)
# expanded_args += a
try:
- sp = subprocess.Popen([command,expanded_args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ sp = subprocess.Popen([command, expanded_args], shell=True,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = sp.communicate()
if out:
print(out.decode("utf-8"))
@@ -28,50 +27,60 @@ def execute_command_blocking(command, *args):
return out
# Executes command and immediately returns. Will not see anything the script outputs
+
+
def execute_command_nonblocking(command, *args):
expanded_args = []
for a in args:
expanded_args += a
try:
- sp = subprocess.Popen([command,expanded_args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- except: pass
+ sp = subprocess.Popen([command, expanded_args], shell=True,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ except:
+ pass
+
def Launcher2():
- sg.ChangeLookAndFeel('GreenTan')
- window = sg.Window('Script launcher')
+ sg.change_look_and_feel('GreenTan')
filelist = glob.glob(LOCATION_OF_YOUR_SCRIPTS+'*.py')
namesonly = []
for file in filelist:
namesonly.append(ntpath.basename(file))
- layout = [
- [sg.Listbox(values=namesonly, size=(30, 19), select_mode=sg.SELECT_MODE_EXTENDED, key='demolist'), sg.Output(size=(88, 20), font='Courier 10')],
- [sg.Checkbox('Wait for program to complete', default=False, key='wait')],
- [sg.Button('Run'), sg.Button('Shortcut 1'), sg.Button('Fav Program'), sg.Button('EXIT')],
- ]
+ layout = [
+ [sg.Listbox(values=namesonly, size=(30, 19),
+ select_mode=sg.SELECT_MODE_EXTENDED, key='demolist'),
+ sg.Output(size=(88, 20), font='Courier 10')],
+ [sg.CBox('Wait for program to complete', default=False, key='wait')],
+ [sg.Button('Run'), sg.Button('Shortcut 1'), sg.Button('Fav Program'), sg.Button('EXIT')],
+ ]
- window.Layout(layout)
+ window = sg.Window('Script launcher', layout)
# ---===--- Loop taking in user input --- #
while True:
- event, values = window.Read()
+ event, values = window.read()
if event in ('EXIT', None):
break # exit button clicked
if event in ('Shortcut 1', 'Fav Program'):
print('Quickly launch your favorite programs using these shortcuts')
- print('Or copy files to your github folder. Or anything else you type on the command line')
+ print('''
+ Or copy files to your github folder.
+ Or anything else you type on the command line''')
# copyfile(source, dest)
elif event == 'Run':
for index, file in enumerate(values['demolist']):
- print('Launching %s'%file)
- window.Refresh() # make the print appear immediately
+ print('Launching %s' % file)
+ window.refresh() # make the print appear immediately
if values['wait']:
execute_command_blocking(LOCATION_OF_YOUR_SCRIPTS + file)
else:
- execute_command_nonblocking(LOCATION_OF_YOUR_SCRIPTS + file)
+ execute_command_nonblocking(
+ LOCATION_OF_YOUR_SCRIPTS + file)
+
+ window.close()
if __name__ == '__main__':
Launcher2()
-
diff --git a/DemoPrograms/Demo_Script_Launcher_Realtime_Output.py b/DemoPrograms/Demo_Script_Launcher_Realtime_Output.py
index 12b54c6b..0c8a3c10 100644
--- a/DemoPrograms/Demo_Script_Launcher_Realtime_Output.py
+++ b/DemoPrograms/Demo_Script_Launcher_Realtime_Output.py
@@ -3,46 +3,46 @@ import sys
import PySimpleGUI as sg
"""
- Demo Program - Realtime output of a shell command in the window
- Shows how you can run a long-running subprocess and have the output
- be displayed in realtime in the window.
+ Demo Program - Realtime output of a shell command in the window
+ Shows how you can run a long-running subprocess and have the output
+ be displayed in realtime in the window.
"""
def main():
- layout = [ [sg.Text('Enter the command you wish to run')],
- [sg.Input(key='_IN_')],
- [sg.Output(size=(60,15))],
- [sg.Button('Run'), sg.Button('Exit')] ]
+ layout = [ [sg.Text('Enter the command you wish to run')],
+ [sg.Input(key='-IN-')],
+ [sg.Output(size=(60,15))],
+ [sg.Button('Run'), sg.Button('Exit')] ]
- window = sg.Window('Realtime Shell Command Output', layout)
+ window = sg.Window('Realtime Shell Command Output', layout)
- while True: # Event Loop
- event, values = window.Read()
- # print(event, values)
- if event in (None, 'Exit'):
- break
- elif event == 'Run':
- runCommand(cmd=values['_IN_'], window=window)
- window.Close()
+ while True: # Event Loop
+ event, values = window.read()
+ # print(event, values)
+ if event in (None, 'Exit'):
+ break
+ elif event == 'Run':
+ runCommand(cmd=values['-IN-'], window=window)
+ window.close()
def runCommand(cmd, timeout=None, window=None):
- """ run shell command
+ """ run shell command
@param cmd: command to execute
@param timeout: timeout for command execution
@param window: the PySimpleGUI window that the output is going to (needed to do refresh on)
@return: (return code from command, command output)
"""
- p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- output = ''
- for line in p.stdout:
- line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip()
- output += line
- print(line)
- window.Refresh() if window else None # yes, a 1-line if, so shoot me
+ p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ output = ''
+ for line in p.stdout:
+ line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip()
+ output += line
+ print(line)
+ window.refresh() if window else None # yes, a 1-line if, so shoot me
- retval = p.wait(timeout)
- return (retval, output)
+ retval = p.wait(timeout)
+ return (retval, output)
main()
diff --git a/DemoPrograms/Demo_Script_Parameters.py b/DemoPrograms/Demo_Script_Parameters.py
index 82fdda64..6f3da38c 100644
--- a/DemoPrograms/Demo_Script_Parameters.py
+++ b/DemoPrograms/Demo_Script_Parameters.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
'''
Quickly add a GUI to your script!
@@ -19,12 +14,16 @@ stores the result in the variable fname, just like the command line parsing did.
'''
if len(sys.argv) == 1:
- event, (fname,) = sg.Window('My Script').Layout([[sg.T('Document to open')],
- [sg.In(), sg.FileBrowse()],
- [sg.CloseButton('Open'), sg.CloseButton('Cancel')]]).Read()
+ layout = [
+ [sg.Text('Document to open')],
+ [sg.Input(), sg.FileBrowse()],
+ [sg.CloseButton('Open'), sg.CloseButton('Cancel')]
+ ]
+ window = sg.Window('My Script', layout)
+ event, values = window.read()
+ window.close()
else:
fname = sys.argv[1]
-
if not fname:
- sg.Popup("Cancel", "No filename supplied")
+ sg.popup("Cancel", "No filename supplied")
raise SystemExit("Cancelling: no filename supplied")
diff --git a/DemoPrograms/Demo_Sort_Visualizer.py b/DemoPrograms/Demo_Sort_Visualizer.py
index 1f07a1b9..f208dda1 100644
--- a/DemoPrograms/Demo_Sort_Visualizer.py
+++ b/DemoPrograms/Demo_Sort_Visualizer.py
@@ -2,7 +2,8 @@ import PySimpleGUI as sg
import random
# ------- Sort visualizer. Displays bar chart representing list items -------
BAR_SPACING, BAR_WIDTH, EDGE_OFFSET = 11, 10, 3
-DATA_SIZE = GRAPH_SIZE = (700,500) # width, height of the graph portion
+DATA_SIZE = GRAPH_SIZE = (700, 500) # width, height of the graph portion
+
def bubble_sort(arr):
def swap(i, j):
@@ -19,30 +20,34 @@ def bubble_sort(arr):
swapped = True
yield arr
-def draw_bars(graph, items): # draws all the bars for all values across screen
+
+def draw_bars(graph, items):
# type: (sg.Graph, List)->None
for i, item in enumerate(items):
graph.draw_rectangle(top_left=(i * BAR_SPACING + EDGE_OFFSET, item),
- bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0), fill_color='#76506d')
+ bottom_right=(i * BAR_SPACING + EDGE_OFFSET + BAR_WIDTH, 0),
+ fill_color='#76506d')
+
def main():
sg.change_look_and_feel('LightGreen')
# Make list to sort
num_bars = DATA_SIZE[0]//(BAR_WIDTH+1)
- list_to_sort = [DATA_SIZE[1]//num_bars*i for i in range(1,num_bars)]
+ list_to_sort = [DATA_SIZE[1]//num_bars*i for i in range(1, num_bars)]
random.shuffle(list_to_sort)
# define window layout
- graph = sg.Graph(GRAPH_SIZE, (0,0), DATA_SIZE)
+ graph = sg.Graph(GRAPH_SIZE, (0, 0), DATA_SIZE)
layout = [[graph],
- [sg.T('Speed Faster'), sg.Slider((0,20), orientation='h', default_value=10, key='-SPEED-'), sg.T('Slower')]]
+ [sg.Text('Speed Faster'), sg.Slider((0, 20), orientation='h', default_value=10, key='-SPEED-'), sg.Text('Slower')]]
window = sg.Window('Sort Demonstration', layout, finalize=True)
- draw_bars(graph, list_to_sort) # draw the initial window's bars
+ # draw the initial window's bars
+ draw_bars(graph, list_to_sort)
sg.popup('Click OK to begin Bubblesort') # Wait for user to start it up
bsort = bubble_sort(list_to_sort) # get an iterator for the sort
- timeout=10 # start with 10ms delays between draws
+ timeout = 10 # start with 10ms delays between draws
while True: # ----- The event loop -----
event, values = window.read(timeout=timeout)
if event is None:
@@ -52,8 +57,11 @@ def main():
except:
sg.popup('Sorting done!')
break
- graph.Erase()
+ graph.erase()
draw_bars(graph, partially_sorted_list)
timeout = int(values['-SPEED-'])
+
window.close()
-main()
\ No newline at end of file
+
+
+main()
diff --git a/DemoPrograms/Demo_Spinner_Compound_Element.py b/DemoPrograms/Demo_Spinner_Compound_Element.py
index dc9bb246..d9c0171e 100644
--- a/DemoPrograms/Demo_Spinner_Compound_Element.py
+++ b/DemoPrograms/Demo_Spinner_Compound_Element.py
@@ -1,37 +1,38 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
"""
Demo of how to combine elements into your own custom element
"""
-sg.SetOptions(element_padding=(0,0))
-# sg.ChangeLookAndFeel('Dark')
+sg.set_options(element_padding=(0, 0))
+# sg.change_look_and_feel('Dark')
# --- Define our "Big-Button-Spinner" compound element. Has 2 buttons and an input field --- #
-NewSpinner = [sg.Button('-', size=(2,1), font='Any 12'),
- sg.In('0', size=(2,1), font='Any 14', justification='r', key='spin'),
- sg.Button('+', size=(2,1), font='Any 12')]
+NewSpinner = [sg.Button('-', size=(2, 1), font='Any 12'),
+ sg.Input('0', size=(2, 1), font='Any 14',
+ justification='r', key='spin'),
+ sg.Button('+', size=(2, 1), font='Any 12')]
# --- Define Window --- #
layout = [
- [sg.Text('Spinner simulation')],
- NewSpinner,
- [sg.T('')],
- [sg.Ok()]
- ]
+ [sg.Text('Spinner simulation')],
+ NewSpinner,
+ [sg.Text('')],
+ [sg.Ok()]
+]
+
+window = sg.Window('Spinner simulation', layout)
-window = sg.Window('Spinner simulation').Layout(layout)
# --- Event Loop --- #
counter = 0
while True:
- event, values = window.Read()
+ event, values = window.read()
if event == 'Ok' or event is None: # be nice to your user, always have an exit from your form
break
# --- do spinner stuff --- #
counter += 1 if event == '+' else -1 if event == '-' else 0
- window.FindElement('spin').Update(counter)
+ window['spin'].update(counter)
+
+window.close()
diff --git a/DemoPrograms/Demo_Stdout.py b/DemoPrograms/Demo_Stdout.py
index 1ad90090..3f5847bb 100644
--- a/DemoPrograms/Demo_Stdout.py
+++ b/DemoPrograms/Demo_Stdout.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
"""
Demo program that reroutes stdout and stderr.
@@ -16,16 +12,17 @@ else:
layout = [
- [sg.Text('Type something in input field and click print')],
- [sg.In()],
- [sg.Output()],
- [sg.Button('Print')]
- ]
+ [sg.Text('Type something in input field and click print')],
+ [sg.Input()],
+ [sg.Output()],
+ [sg.Button('Print')]
+]
-window = sg.Window('Reroute stdout').Layout(layout)
+window = sg.Window('Reroute stdout', layout)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
print('You typed: ', values[0])
+window.close()
diff --git a/DemoPrograms/Demo_Super_Simple_Form.py b/DemoPrograms/Demo_Super_Simple_Form.py
index 48bf949a..34c493ea 100644
--- a/DemoPrograms/Demo_Super_Simple_Form.py
+++ b/DemoPrograms/Demo_Super_Simple_Form.py
@@ -1,11 +1,15 @@
import PySimpleGUI as sg
-layout = [[sg.Text('Please enter your Name, Address, Phone')],
- [sg.Text('Name', size=(10,1)), sg.InputText(key='-NAME-')],
- [sg.Text('Address', size=(10,1)), sg.InputText(key='-ADDRESS-')],
- [sg.Text('Phone', size=(10,1)), sg.InputText(key='-PHONE-')],
- [sg.Button('Submit'), sg.Button('Cancel')]]
+# Basic Example
+
+layout = [[sg.Text('Please enter your Name, Address, Phone')],
+ [sg.Text('Name', size=(10, 1)), sg.InputText(key='-NAME-')],
+ [sg.Text('Address', size=(10, 1)), sg.InputText(key='-ADDRESS-')],
+ [sg.Text('Phone', size=(10, 1)), sg.InputText(key='-PHONE-')],
+ [sg.Button('Submit'), sg.Button('Cancel')]]
window = sg.Window('Simple Data Entry Window', layout)
event, values = window.read()
print(event, values['-NAME-'], values['-ADDRESS-'], values['-PHONE-'])
+
+window.close()
diff --git a/DemoPrograms/Demo_Table_CSV.py b/DemoPrograms/Demo_Table_CSV.py
index 0662e83d..336dda3f 100644
--- a/DemoPrograms/Demo_Table_CSV.py
+++ b/DemoPrograms/Demo_Table_CSV.py
@@ -1,19 +1,17 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import csv
+# Show CSV data in Table
+
def table_example():
- filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),))
+ filename = sg.popup_get_file('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),))
# --- populate table with file contents --- #
if filename == '':
- sys.exit(69)
+ return
data = []
header_list = []
- button = sg.PopupYesNo('Does this file have column names already?')
+ button = sg.popup_yes_no('Does this file have column names already?')
if filename is not None:
with open(filename, "r") as infile:
reader = csv.reader(infile)
@@ -24,9 +22,9 @@ def table_example():
if button == 'No':
header_list = ['column' + str(x) for x in range(len(data[0]))]
except:
- sg.PopupError('Error reading file')
- sys.exit(69)
- sg.SetOptions(element_padding=(0, 0))
+ sg.popup_error('Error reading file')
+ return
+ sg.set_options(element_padding=(0, 0))
layout = [[sg.Table(values=data,
headings=header_list,
@@ -37,9 +35,9 @@ def table_example():
num_rows=min(len(data), 20))]]
- window = sg.Window('Table', grab_anywhere=False).Layout(layout)
- event, values = window.Read()
+ window = sg.Window('Table', layout, grab_anywhere=False)
+ event, values = window.read()
- sys.exit(69)
+ window.close()
table_example()
diff --git a/DemoPrograms/Demo_Table_Element.py b/DemoPrograms/Demo_Table_Element.py
index 4a2eac26..82400507 100644
--- a/DemoPrograms/Demo_Table_Element.py
+++ b/DemoPrograms/Demo_Table_Element.py
@@ -1,56 +1,53 @@
#!/usr/bin/env python
-import sys
-
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import random
import string
+# Yet another example of showing CSV data in Table
-# ------------------ Create a fake table ------------------
-class Fake():
- @classmethod
- def word(self):
- return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
- @classmethod
- def number(self, max=1000):
- return random.randint(0,max)
+def word():
+ return ''.join(random.choice(string.ascii_lowercase) for i in range(10))
+def number(max_val=1000):
+ return random.randint(0, max_val)
def make_table(num_rows, num_cols):
data = [[j for j in range(num_cols)] for i in range(num_rows)]
- data[0] = [Fake.word() for _ in range(num_cols)]
+ data[0] = [word() for _ in range(num_cols)]
for i in range(1, num_rows):
- data[i] = [Fake.word(), *[Fake.number() for i in range(num_cols - 1)]]
+ data[i] = [word(), *[number() for i in range(num_cols - 1)]]
return data
+
data = make_table(num_rows=15, num_cols=6)
-# sg.SetOptions(element_padding=(0,0))
+# sg.set_options(element_padding=(0,0))
headings = [data[0][x] for x in range(len(data[0]))]
layout = [[sg.Table(values=data[1:][:], headings=headings, max_col_width=25, background_color='lightblue',
- auto_size_columns=True, display_row_numbers=True, justification='right', num_rows=20, alternating_row_color='blue', key='_table_', tooltip='This is a table')],
+ auto_size_columns=True,
+ display_row_numbers=True,
+ justification='right',
+ num_rows=20,
+ alternating_row_color='blue',
+ key='-table-',
+ tooltip='This is a table')],
[sg.Button('Read'), sg.Button('Double'), sg.Button('Update')],
- [sg.T('Read = read which rows are selected')],[sg.T('Double = double the amount of data in the table')]]
+ [sg.Text('Read = read which rows are selected')], [sg.Text('Double = double the amount of data in the table')]]
-window = sg.Window('Table', grab_anywhere=False, resizable=True).Layout(layout)
+window = sg.Window('Table', layout, grab_anywhere=False, resizable=True)
while True:
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
if event is None:
break
if event == 'Double':
for i in range(len(data)):
data.append(data[i])
- window.FindElement('_table_').Update(values = data)
+ window['-table-'].update(values=data)
elif event == 'Update':
- window.FindElement('_table_').Update( row_colors=((8,'white', 'red'), (9,'black')))
+ window['-table-'].update(row_colors=((8, 'white',
+ 'red'), (9, 'black')))
- # sg.Popup(event, values)
- # print(event, values)
-window.Close()
-sys.exit(69)
+window.close()
diff --git a/DemoPrograms/Demo_Table_Pandas.py b/DemoPrograms/Demo_Table_Pandas.py
index bc192046..32f9f544 100644
--- a/DemoPrograms/Demo_Table_Pandas.py
+++ b/DemoPrograms/Demo_Table_Pandas.py
@@ -1,40 +1,50 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import pandas as pd
+# Yet another example of showing CSV data in Table
def table_example():
- sg.SetOptions(auto_size_buttons=True)
- filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files", "*.csv"),))
+
+ sg.set_options(auto_size_buttons=True)
+ filename = sg.popup_get_file(
+ 'filename to open', no_window=True, file_types=(("CSV Files", "*.csv"),))
# --- populate table with file contents --- #
if filename == '':
- sys.exit(69)
+ return
+
data = []
header_list = []
- button = sg.PopupYesNo('Does this file have column names already?')
+ button = sg.popup_yes_no('Does this file have column names already?')
+
if filename is not None:
try:
- df = pd.read_csv(filename, sep=',', engine='python', header=None) # Header=None means you directly pass the columns names to the dataframe
+ # Header=None means you directly pass the columns names to the dataframe
+ df = pd.read_csv(filename, sep=',', engine='python', header=None)
data = df.values.tolist() # read everything else into a list of rows
if button == 'Yes': # Press if you named your columns in the csv
- header_list = df.iloc[0].tolist() # Uses the first row (which should be column names) as columns names
- data = df[1:].values.tolist() # Drops the first row in the table (otherwise the header names and the first row will be the same)
+ # Uses the first row (which should be column names) as columns names
+ header_list = df.iloc[0].tolist()
+ # Drops the first row in the table (otherwise the header names and the first row will be the same)
+ data = df[1:].values.tolist()
elif button == 'No': # Press if you didn't name the columns in the csv
- header_list = ['column' + str(x) for x in range(len(data[0]))] # Creates columns names for each column ('column0', 'column1', etc)
+ # Creates columns names for each column ('column0', 'column1', etc)
+ header_list = ['column' + str(x) for x in range(len(data[0]))]
except:
- sg.PopupError('Error reading file')
- sys.exit(69)
+ sg.popup_error('Error reading file')
+ return
- layout = [[sg.Table(values=data, headings=header_list, display_row_numbers=True,
- auto_size_columns=False, num_rows=min(25,len(data)))]]
+ layout = [
+ [sg.Table(values=data,
+ headings=header_list,
+ display_row_numbers=True,
+ auto_size_columns=False,
+ num_rows=min(25, len(data)))]
+ ]
- window = sg.Window('Table', grab_anywhere=False)
- event, values = window.Layout(layout).Read()
+ window = sg.Window('Table', layout, grab_anywhere=False)
+ event, values = window.read()
+ window.close()
- sys.exit(69)
table_example()
diff --git a/DemoPrograms/Demo_Table_Simulation.py b/DemoPrograms/Demo_Table_Simulation.py
index 32018c5a..b41111ae 100644
--- a/DemoPrograms/Demo_Table_Simulation.py
+++ b/DemoPrograms/Demo_Table_Simulation.py
@@ -1,9 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import csv
@@ -11,70 +7,77 @@ def TableSimulation():
"""
Display data in a table format
"""
- sg.SetOptions(element_padding=(0,0))
+ sg.set_options(element_padding=(0, 0))
menu_def = [['File', ['Open', 'Save', 'Exit']],
- ['Edit', ['Paste', ['Special', 'Normal',], 'Undo'],],
- ['Help', 'About...'],]
+ ['Edit', ['Paste', ['Special', 'Normal', ], 'Undo'], ],
+ ['Help', 'About...'], ]
columm_layout = [[]]
MAX_ROWS = 20
MAX_COL = 10
for i in range(MAX_ROWS):
- inputs = [sg.T('{}'.format(i), size=(4,1), justification='right')] + [sg.In(size=(10, 1), pad=(1, 1), justification='right', key=(i,j), do_not_clear=True) for j in range(MAX_COL)]
+ inputs = [sg.Text(str(i), size=(4, 1), justification='right')] + [sg.Input(size=(10, 1), pad=(
+ 1, 1), justification='right', key=(i, j)) for j in range(MAX_COL)]
columm_layout.append(inputs)
- layout = [ [sg.Menu(menu_def)],
- [sg.T('Table Using Combos and Input Elements', font='Any 18')],
- [sg.T('Type in a row, column and value. The form will update the values in realtime as you type'),
- sg.In(key='inputrow', justification='right', size=(8,1), pad=(1,1), do_not_clear=True),
- sg.In(key='inputcol', size=(8,1), pad=(1,1), justification='right', do_not_clear=True),
- sg.In(key='value', size=(8,1), pad=(1,1), justification='right', do_not_clear=True)],
- [sg.Column(columm_layout, size=(800,600), scrollable=True)] ]
+ layout = [[sg.Menu(menu_def)],
+ [sg.Text('Table Using Combos and Input Elements', font='Any 18')],
+ [sg.Text('Type in a row, column and value. The form will update the values in realtime as you type'),
+ sg.Input(key='inputrow', justification='right', size=(8, 1), pad=(1, 1)),
+ sg.Input(key='inputcol', size=(8, 1), pad=(1, 1), justification='right'),
+ sg.Input(key='value', size=(8, 1), pad=(1, 1), justification='right')],
+ [sg.Col(columm_layout, size=(800, 600), scrollable=True)]]
- window = sg.Window('Table', return_keyboard_events=True).Layout(layout)
+ window = sg.Window('Table', layout, return_keyboard_events=True)
while True:
- event, values = window.Read()
+ event, values = window.read()
# --- Process buttons --- #
- if event is None or event == 'Exit':
+ if event in (None, 'Exit'):
break
elif event == 'About...':
- sg.Popup('Demo of table capabilities')
+ sg.popup('Demo of table capabilities')
elif event == 'Open':
- filename = sg.PopupGetFile('filename to open', no_window=True, file_types=(("CSV Files","*.csv"),))
+ filename = sg.popup_get_file(
+ 'filename to open', no_window=True, file_types=(("CSV Files", "*.csv"),))
# --- populate table with file contents --- #
if filename is not None:
with open(filename, "r") as infile:
reader = csv.reader(infile)
try:
- data = list(reader) # read everything else into a list of rows
+ # read everything else into a list of rows
+ data = list(reader)
except:
- sg.PopupError('Error reading file')
+ sg.popup_error('Error reading file')
continue
# clear the table
- [window.FindElement((i,j)).Update('') for j in range(MAX_COL) for i in range(MAX_ROWS)]
+ [window[(i, j)].update('') for j in range(MAX_COL)
+ for i in range(MAX_ROWS)]
for i, row in enumerate(data):
for j, item in enumerate(row):
- location = (i,j)
+ location = (i, j)
try: # try the best we can at reading and filling the table
- target_element = window.FindElement(location)
+ target_element = window[location]
new_value = item
if target_element is not None and new_value != '':
- target_element.Update(new_value)
+ target_element.update(new_value)
except:
pass
# if a valid table location entered, change that location's value
try:
location = (int(values['inputrow']), int(values['inputcol']))
- target_element = window.FindElement(location)
+ target_element = window[location]
new_value = values['value']
if target_element is not None and new_value != '':
- target_element.Update(new_value)
+ target_element.update(new_value)
except:
pass
-TableSimulation()
\ No newline at end of file
+ window.close()
+
+
+TableSimulation()
diff --git a/DemoPrograms/Demo_Tabs.py b/DemoPrograms/Demo_Tabs.py
index 733cb334..7519a3cf 100644
--- a/DemoPrograms/Demo_Tabs.py
+++ b/DemoPrograms/Demo_Tabs.py
@@ -1,51 +1,54 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-sg.SetOptions(background_color='cornsilk4', element_background_color='cornsilk2', input_elements_background_color='cornsilk2')
+# Usage of Tabs in PSG
-tab1_layout = [[sg.T('This is inside tab 1', background_color='darkslateblue', text_color='white')],
- [sg.In(key='_in0_')]]
+sg.set_options(background_color='cornsilk4',
+ element_background_color='cornsilk2',
+ input_elements_background_color='cornsilk2')
-tab2_layout = [[sg.T('This is inside tab 2', background_color='tan1')],
- [sg.In(key='_in2_')]]
+tab1_layout = [[sg.Text('This is inside tab 1', background_color='darkslateblue', text_color='white')],
+ [sg.Input(key='-in0-')]]
+
+tab2_layout = [[sg.Text('This is inside tab 2', background_color='tan1')],
+ [sg.Input(key='-in2-')]]
-tab3_layout = [[sg.T('This is inside tab 3')],
- [sg.In(key='_in2_')]]
+tab3_layout = [[sg.Text('This is inside tab 3')],
+ [sg.Input(key='-in2-')]]
-tab4_layout = [[sg.T('This is inside tab 4', background_color='darkseagreen')],
- [sg.In(key='_in3_')]]
+tab4_layout = [[sg.Text('This is inside tab 4', background_color='darkseagreen')],
+ [sg.Input(key='-in3-')]]
-tab5_layout = [[sg.T('This is inside tab 5')],
- [sg.In(key='_in4_')]]
+tab5_layout = [[sg.Text('This is inside tab 5')],
+ [sg.Input(key='-in4-')]]
-layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='_mykey_'),
+layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='-mykey-'),
sg.Tab('Tab 2', tab2_layout, background_color='tan1'),
sg.Tab('Tab 3', tab3_layout)]],
- key='_group2_', title_color='red',
- selected_title_color='green', tab_location='right'),
- sg.TabGroup([[sg.Tab('Tab 4', tab4_layout,background_color='darkseagreen', key='_mykey_'),
- sg.Tab('Tab 5', tab5_layout)]], key='_group1_', tab_location='top', selected_title_color='purple')],
- [sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='_mykey_'),
+ key='-group2-', title_color='red',
+ selected_title_color='green', tab_location='right'),
+ sg.TabGroup([[sg.Tab('Tab 4', tab4_layout, background_color='darkseagreen', key='-mykey-'),
+ sg.Tab('Tab 5', tab5_layout)]], key='-group1-', tab_location='top', selected_title_color='purple')],
+ [sg.TabGroup([[sg.Tab('Tab 1', tab1_layout, background_color='darkslateblue', key='-mykey-'),
sg.Tab('Tab 2', tab2_layout, background_color='tan1'),
sg.Tab('Tab 3', tab3_layout)]],
- key='_group3_', title_color='red',
- selected_title_color='green', tab_location='left'),
- sg.TabGroup([[sg.Tab('Tab 4', tab4_layout,background_color='darkseagreen', key='_mykey_'),
- sg.Tab('Tab 5', tab5_layout)]], key='_group4_', tab_location='bottom', selected_title_color='purple')],
- [sg.Button('Read')]]
+ key='-group3-', title_color='red',
+ selected_title_color='green', tab_location='left'),
+ sg.TabGroup([[sg.Tab('Tab 4', tab4_layout, background_color='darkseagreen', key='-mykey-'),
+ sg.Tab('Tab 5', tab5_layout)]], key='-group4-', tab_location='bottom', selected_title_color='purple')],
+ [sg.Button('Read')]]
-window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout)
+window = sg.Window('My window with tabs', layout,
+ default_element_size=(12, 1))
while True:
- event, values = window.Read()
- sg.PopupNonBlocking(event, values)
+ event, values = window.read()
+ sg.popup_non_blocking(event, values)
print(event, values)
if event is None: # always, always give a way out!
break
+window.close()
diff --git a/DemoPrograms/Demo_Tabs_Nested.py b/DemoPrograms/Demo_Tabs_Nested.py
index 8868cb53..46a16e62 100644
--- a/DemoPrograms/Demo_Tabs_Nested.py
+++ b/DemoPrograms/Demo_Tabs_Nested.py
@@ -1,39 +1,39 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-sg.ChangeLookAndFeel('GreenTan')
-tab2_layout = [[sg.T('This is inside tab 2')],
- [sg.T('Tabs can be anywhere now!')]]
+# Yet another example of TabGroup element
-tab1_layout = [[sg.T('Type something here and click button'), sg.In(key='in')]]
+sg.change_look_and_feel('GreenTan')
+tab2_layout = [[sg.Text('This is inside tab 2')],
+ [sg.Text('Tabs can be anywhere now!')]]
-tab3_layout = [[sg.T('This is inside tab 3')]]
-tab4_layout = [[sg.T('This is inside tab 4')]]
+tab1_layout = [[sg.Text('Type something here and click button'), sg.Input(key='in')]]
-tab_layout = [[sg.T('This is inside of a tab')]]
+tab3_layout = [[sg.Text('This is inside tab 3')]]
+tab4_layout = [[sg.Text('This is inside tab 4')]]
+
+tab_layout = [[sg.Text('This is inside of a tab')]]
tab_group = sg.TabGroup([[sg.Tab('Tab 7', tab_layout), sg.Tab('Tab 8', tab_layout)]])
-tab5_layout = [[sg.T('Watch this window')],
+tab5_layout = [[sg.Text('Watch this window')],
[sg.Output(size=(40,5))]]
-tab6_layout = [[sg.T('This is inside tab 6')],
- [sg.T('How about a second row of stuff in tab 6?'), tab_group]]
+tab6_layout = [[sg.Text('This is inside tab 6')],
+ [sg.Text('How about a second row of stuff in tab 6?'), tab_group]]
-layout = [[sg.T('My Window!')], [sg.Frame('A Frame', layout=
+layout = [[sg.Text('My Window!')], [sg.Frame('A Frame', layout=
[[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]]), sg.TabGroup([[sg.Tab('Tab3', tab3_layout), sg.Tab('Tab 4', tab4_layout)]])]])],
- [sg.T('This text is on a row with a column'),sg.Column(layout=[[sg.T('In a column')],
+ [sg.Text('This text is on a row with a column'),sg.Col(layout=[[sg.Text('In a column')],
[sg.TabGroup([[sg.Tab('Tab 5', tab5_layout), sg.Tab('Tab 6', tab6_layout)]])],
[sg.Button('Click me')]])],]
-window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout).Finalize()
+window = sg.Window('My window with tabs', layout, default_element_size=(12,1), finalize=True)
print('Are there enough tabs for you?')
while True:
- event, values = window.Read()
+ event, values = window.read()
print(event, values)
if event is None: # always, always give a way out!
- break
\ No newline at end of file
+ break
+
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Tabs_Simple.py b/DemoPrograms/Demo_Tabs_Simple.py
index d666c326..68665ffa 100644
--- a/DemoPrograms/Demo_Tabs_Simple.py
+++ b/DemoPrograms/Demo_Tabs_Simple.py
@@ -1,25 +1,28 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
-tab1_layout = [[sg.T('Tab 1')],
- [sg.T('Put your layout in here')],
- [sg.T('Input something'),sg.In(key='_IN0_')]]
+# Simple example of TabGroup element
-tab2_layout = [[sg.T('Tab2')]]
+tab1_layout = [[sg.Text('Tab 1')],
+ [sg.Text('Put your layout in here')],
+ [sg.Text('Input something'), sg.Input(key='-IN0-')]]
+
+tab2_layout = [[sg.Text('Tab2')]]
-layout = [[sg.TabGroup([[sg.Tab('Tab 1', tab1_layout), sg.Tab('Tab 2', tab2_layout)]], key='_TABGROUP_')],
- [sg.Button('Read')]]
+layout = [[sg.TabGroup([[
+ sg.Tab('Tab 1', tab1_layout),
+ sg.Tab('Tab 2', tab2_layout)]], key='-TABGROUP-')],
+ [sg.Button('Read')]]
-window = sg.Window('My window with tabs', default_element_size=(12,1)).Layout(layout)
+window = sg.Window('My window with tabs', layout
+ default_element_size=(12, 1))
while True:
- event, values = window.Read()
- sg.PopupNonBlocking('button = %s' % event, 'Values dictionary', values)
+ event, values = window.read()
+ sg.popup_non_blocking('button = %s' % event, 'Values dictionary', values)
if event is None: # always, always give a way out!
break
+
+window.close()
diff --git a/DemoPrograms/Demo_Template.py b/DemoPrograms/Demo_Template.py
index 581bf934..6af2b7b3 100644
--- a/DemoPrograms/Demo_Template.py
+++ b/DemoPrograms/Demo_Template.py
@@ -1,39 +1,34 @@
-#choose one of these are your starting point. Copy, paste, have fun
+#!/usr/bin/env python
+import PySimpleGUI as sg
+
+#
+# Choose one of these are your starting point. Copy, paste, have fun
+#
+
# ---------------------------------#
# DESIGN PATTERN 1 - Simple Window #
# ---------------------------------#
-#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
layout = [[ sg.Text('My layout') ],
[ sg.CloseButton('Next Window')]]
-window = sg.Window('My window').Layout(layout)
-event, values = window.Read()
+window = sg.Window('My window', layout)
+event, values = window.read()
# --------------------------------------------------#
# DESIGN PATTERN 2 - Persistent Window (stays open) #
# --------------------------------------------------#
-#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
layout = [[ sg.Text('My Window') ],
[ sg.Button('Read The Window')]]
-window = sg.Window('My Window Title').Layout(layout)
+window = sg.Window('My Window Title', layout)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event is None: # if window closed with X
break
- print(event, values)
\ No newline at end of file
+ print(event, values)
+
+window.close()
diff --git a/DemoPrograms/Demo_Threaded_Work.py b/DemoPrograms/Demo_Threaded_Work.py
index e4dd3b9b..b8b5f77e 100644
--- a/DemoPrograms/Demo_Threaded_Work.py
+++ b/DemoPrograms/Demo_Threaded_Work.py
@@ -39,10 +39,13 @@ import PySimpleGUI as sg
# Put your long running code inside this "wrapper"
# NEVER make calls to PySimpleGUI from this thread (or any thread)!
# Create one of these functions for EVERY long-running call you want to make
+
+
def long_function_wrapper(work_id, gui_queue):
# LOCATION 1
# this is our "long running function call"
- time.sleep(5) # sleep for a while as a simulation of a long-running computation
+ # sleep for a while as a simulation of a long-running computation
+ time.sleep(5)
# at the end of the work, before exiting, send a message back to the GUI indicating end
gui_queue.put('{} ::: done'.format(work_id))
# at this point, the thread exits
@@ -51,28 +54,34 @@ def long_function_wrapper(work_id, gui_queue):
############################# Begin GUI code #############################
def the_gui():
- gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code
+ # queue used to communicate between the gui and long-running code
+ gui_queue = queue.Queue()
layout = [[sg.Text('Multithreaded Work Example')],
[sg.Text('Click Go to start a long-running function call')],
- [sg.Text('', size=(25, 1), key='_OUTPUT_')],
- [sg.Text('', size=(25, 1), key='_OUTPUT2_')],
- [sg.Graph((10,10),(0,0),(10,10),background_color='black',key=i) for i in range(20)],
+ [sg.Text('', size=(25, 1), key='-OUTPUT-')],
+ [sg.Text('', size=(25, 1), key='-OUTPUT2-')],
+ [sg.Graph((10, 10), (0, 0), (10, 10),
+ background_color='black', key=i) for i in range(20)],
[sg.Button('Go'), sg.Button('Popup'), sg.Button('Exit')], ]
- window = sg.Window('Multithreaded Window').Layout(layout)
+ window = sg.Window('Multithreaded Window', layout)
# --------------------- EVENT LOOP ---------------------
work_id = 0
while True:
- event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
- if event is None or event == 'Exit':
+ # wait for up to 100 ms for a GUI event
+ event, values = window.read(timeout=100)
+ if event in (None, 'Exit'):
break
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
- window.Element('_OUTPUT_').Update('Starting long work %s'%work_id)
- window.Element(work_id).Update(background_color='red')
+ window['-OUTPUT-'].update('Starting long work %s' % work_id)
+ window[work_id].update(background_color='red')
# LOCATION 2
# STARTING long run by starting a thread
- thread_id = threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True)
+ thread_id = threading.Thread(
+ target=long_function_wrapper,
+ args=(work_id, gui_queue,),
+ daemon=True)
thread_id.start()
work_id = work_id+1 if work_id < 19 else 0
# --------------- Read next message coming in from threads ---------------
@@ -85,18 +94,21 @@ def the_gui():
if message is not None:
# LOCATION 3
# this is the place you would execute code at ENDING of long running task
- # You can check the completed_work_id variable to see exactly which long-running function completed
+ # You can check the completed_work_id variable
+ # to see exactly which long-running function completed
completed_work_id = int(message[:message.index(' :::')])
- window.Element('_OUTPUT2_').Update('Complete Work ID "{}"'.format(completed_work_id))
- window.Element(completed_work_id).Update(background_color='green')
+ window['-OUTPUT2-'].update(
+ 'Complete Work ID "{}"'.format(completed_work_id))
+ window[completed_work_id].update(background_color='green')
if event == 'Popup':
- sg.Popup('This is a popup showing that the GUI is running')
+ sg.popup('This is a popup showing that the GUI is running')
# if user exits the window, then close the window and exit the GUI func
- window.Close()
+ window.close()
############################# Main #############################
+
if __name__ == '__main__':
the_gui()
- print('Exiting Program')
\ No newline at end of file
+ print('Exiting Program')
diff --git a/DemoPrograms/Demo_Timer.py b/DemoPrograms/Demo_Timer.py
index 4bf3d386..85c2ff33 100644
--- a/DemoPrograms/Demo_Timer.py
+++ b/DemoPrograms/Demo_Timer.py
@@ -1,44 +1,41 @@
import PySimpleGUI as sg
import time
-# form that doen't block
-# good for applications with an loop that polls hardware
+# Basic timer in PSG
+
def Timer():
- sg.ChangeLookAndFeel('Dark')
- sg.SetOptions(element_padding=(0,0))
- # Make a form, but don't use context manager
- window = sg.Window('Running Timer', no_titlebar=True, auto_size_buttons=False)
- # Create a text element that will be updated with status information on the GUI itself
- # Create the rows
- form_rows = [[sg.Text('')],
- [sg.Text('', size=(8, 2), font=('Helvetica', 20), justification='center', key='text')],
- [sg.Button('Pause', key='-RUN-PAUSE-'), sg.Button('Reset'), sg.Exit(button_color=('white','firebrick4'))]]
- # Layout the rows of the form and perform a read. Indicate the form is non-blocking!
- window.Layout(form_rows)
- #
- # your program's main loop
+ sg.change_look_and_feel('Dark')
+ sg.set_options(element_padding=(0, 0))
+ form_rows = [[sg.Text(size=(8, 2), font=('Helvetica', 20),
+ justification='center', key='text')],
+ [sg.Button('Pause', key='-RUN-PAUSE-'),
+ sg.Button('Reset'),
+ sg.Exit(button_color=('white', 'firebrick4'))]]
+ window = sg.Window('Running Timer', form_rows,
+ no_titlebar=True, auto_size_buttons=False)
i = 0
paused = False
start_time = int(round(time.time() * 100))
- while (True):
+
+ while True:
# This is the code that reads and updates your window
button, values = window.read(timeout=0)
- window.FindElement('text').Update('{:02d}:{:02d}.{:02d}'.format((i // 100) // 60, (i // 100) % 60, i % 100))
+ window['text'].update('{:02d}:{:02d}.{:02d}'.format(
+ (i // 100) // 60, (i // 100) % 60, i % 100))
if values is None or button == 'Exit':
break
if button == 'Reset':
- i=0
+ i = 0
+
elif button == '-RUN-PAUSE-':
paused = not paused
- window['-RUN-PAUSE-'].Update('Run' if paused else 'Pause')
+ window['-RUN-PAUSE-'].update('Run' if paused else 'Pause')
if not paused:
i += 1
-
- # Broke out of main loop. Close the window.
window.close()
-Timer()
\ No newline at end of file
+Timer()
diff --git a/DemoPrograms/Demo_Touch_Keyboard.py b/DemoPrograms/Demo_Touch_Keyboard.py
index 350eab31..48abb711 100644
--- a/DemoPrograms/Demo_Touch_Keyboard.py
+++ b/DemoPrograms/Demo_Touch_Keyboard.py
@@ -1,6 +1,8 @@
import PySimpleGUI as sg
-# import PySimpleGUIQt as sg # 100% portable to Qt by changing to this import
+'''
+ Example of on-screen keyboard.
+'''
class keyboard():
def __init__(self, location=(None, None), font=('Arial', 16)):
@@ -12,21 +14,16 @@ class keyboard():
keyboard_layout = [[sg.Button(c, key=c, size=(4, 2), font=self.font) for c in numberRow] + [
sg.Button('⌫', key='back', size=(4, 2), font=self.font),
sg.Button('Esc', key='close', size=(4, 2), font=self.font)],
- [sg.T(' ' * 4)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
- topRow] + [sg.Stretch()],
- [sg.T(' ' * 11)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
- midRow] + [sg.Stretch()],
- [sg.T(' ' * 18)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
- bottomRow] + [sg.Stretch()]]
+ [sg.Text(' ' * 4)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ topRow] + [sg.Stretch()],
+ [sg.Text(' ' * 11)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ midRow] + [sg.Stretch()],
+ [sg.Text(' ' * 18)] + [sg.Button(c, key=c, size=(4, 2), font=self.font) for c in
+ bottomRow] + [sg.Stretch()]]
- self.window = sg.Window('keyboard',
- grab_anywhere=True,
- keep_on_top=True,
- alpha_channel=0,
- no_titlebar=True,
- element_padding=(0,0),
- location=location
- ).Layout(keyboard_layout).Finalize()
+ self.window = sg.Window('keyboard', keyboard_layout,
+ grab_anywhere=True, keep_on_top=True, alpha_channel=0,
+ no_titlebar=True, element_padding=(0, 0), location=location, finalize=True)
self.hide()
def _keyboardhandler(self):
@@ -34,12 +31,12 @@ class keyboard():
if self.event == 'close':
self.hide()
elif len(self.event) == 1:
- self.focus.Update(self.focus.Get() + self.event)
+ self.focus.update(self.focus.Get() + self.event)
elif self.event == 'back':
Text = self.focus.Get()
if len(Text) > 0:
Text = Text[:-1]
- self.focus.Update(Text)
+ self.focus.update(Text)
def hide(self):
self.visible = False
@@ -56,38 +53,37 @@ class keyboard():
self.show()
def update(self, focus):
- self.event, _ = self.window.Read(timeout=0)
+ self.event, _ = self.window.read(timeout=0)
if focus is not None:
self.focus = focus
self._keyboardhandler()
def close(self):
- self.window.Close()
+ self.window.close()
class GUI():
def __init__(self):
layout = [[sg.Text('Enter Text')],
- [sg.Input(size=(17, 1), key='input1', do_not_clear=True)],
- [sg.InputText(size=(17, 1), key='input2', do_not_clear=True)],
+ [sg.Input('', size=(17, 1), key='input1')],
+ [sg.InputText('', size=(17, 1), key='input2')],
[sg.Button('on-screen keyboard', key='keyboard')],
[sg.Button('close', key='close')]]
- self.mainWindow = sg.Window('On-screen test',
- grab_anywhere=True,
- no_titlebar=False,
- ).Layout(layout).Finalize()
- location = self.mainWindow.CurrentLocation()
+ self.mainWindow = sg.Window('On-screen test', layout,
+ grab_anywhere=True, no_titlebar=False, finalize=True)
+ location = self.mainWindow.current_location()
location = location[0]-200, location[1]+200
self.keyboard = keyboard(location)
self.focus = None
def run(self):
while True:
- cur_focus = self.mainWindow.FindElementWithFocus()
+ cur_focus = self.mainWindow.find_element_with_focus()
if cur_focus is not None:
self.focus = cur_focus
- event, values = self.mainWindow.Read(timeout=200, timeout_key='timeout')
+ event, values = self.mainWindow.read(
+ timeout=200, timeout_key='timeout')
if self.focus is not None:
self.keyboard.update(self.focus)
if event == 'keyboard':
diff --git a/DemoPrograms/Demo_Tree_Element.py b/DemoPrograms/Demo_Tree_Element.py
index e83ec3e8..554477ec 100644
--- a/DemoPrograms/Demo_Tree_Element.py
+++ b/DemoPrograms/Demo_Tree_Element.py
@@ -1,11 +1,7 @@
#!/usr/bin/env python
import sys
import os
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
"""
A PySimpleGUI or PySimpleGUIQt demo program that will display a folder heirarchy with icons for the folders and files.
@@ -17,48 +13,50 @@ else:
# Base64 versions of images of a folder and a file. PNG files (may not work with PySimpleGUI27, swap with GIFs)
folder_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABnUlEQVQ4y8WSv2rUQRSFv7vZgJFFsQg2EkWb4AvEJ8hqKVilSmFn3iNvIAp21oIW9haihBRKiqwElMVsIJjNrprsOr/5dyzml3UhEQIWHhjmcpn7zblw4B9lJ8Xag9mlmQb3AJzX3tOX8Tngzg349q7t5xcfzpKGhOFHnjx+9qLTzW8wsmFTL2Gzk7Y2O/k9kCbtwUZbV+Zvo8Md3PALrjoiqsKSR9ljpAJpwOsNtlfXfRvoNU8Arr/NsVo0ry5z4dZN5hoGqEzYDChBOoKwS/vSq0XW3y5NAI/uN1cvLqzQur4MCpBGEEd1PQDfQ74HYR+LfeQOAOYAmgAmbly+dgfid5CHPIKqC74L8RDyGPIYy7+QQjFWa7ICsQ8SpB/IfcJSDVMAJUwJkYDMNOEPIBxA/gnuMyYPijXAI3lMse7FGnIKsIuqrxgRSeXOoYZUCI8pIKW/OHA7kD2YYcpAKgM5ABXk4qSsdJaDOMCsgTIYAlL5TQFTyUIZDmev0N/bnwqnylEBQS45UKnHx/lUlFvA3fo+jwR8ALb47/oNma38cuqiJ9AAAAAASUVORK5CYII='
-
file_icon = b'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsSAAALEgHS3X78AAABU0lEQVQ4y52TzStEURiHn/ecc6XG54JSdlMkNhYWsiILS0lsJaUsLW2Mv8CfIDtr2VtbY4GUEvmIZnKbZsY977Uwt2HcyW1+dTZvt6fn9557BGB+aaNQKBR2ifkbgWR+cX13ubO1svz++niVTA1ArDHDg91UahHFsMxbKWycYsjze4muTsP64vT43v7hSf/A0FgdjQPQWAmco68nB+T+SFSqNUQgcIbN1bn8Z3RwvL22MAvcu8TACFgrpMVZ4aUYcn77BMDkxGgemAGOHIBXxRjBWZMKoCPA2h6qEUSRR2MF6GxUUMUaIUgBCNTnAcm3H2G5YQfgvccYIXAtDH7FoKq/AaqKlbrBj2trFVXfBPAea4SOIIsBeN9kkCwxsNkAqRWy7+B7Z00G3xVc2wZeMSI4S7sVYkSk5Z/4PyBWROqvox3A28PN2cjUwinQC9QyckKALxj4kv2auK0xAAAAAElFTkSuQmCC'
-starting_path = sg.PopupGetFolder('Folder to display')
+starting_path = sg.popup_get_folder('Folder to display')
if not starting_path:
- sys.exit()
+ sys.exit(0)
treedata = sg.TreeData()
+
def add_files_in_folder(parent, dirname):
files = os.listdir(dirname)
for f in files:
- fullname = os.path.join(dirname,f)
+ fullname = os.path.join(dirname, f)
if os.path.isdir(fullname): # if it's a folder, add folder and recurse
treedata.Insert(parent, fullname, f, values=[], icon=folder_icon)
add_files_in_folder(fullname, fullname)
else:
- treedata.Insert(parent, fullname, f, values=[os.stat(fullname).st_size], icon=file_icon)
+ treedata.Insert(parent, fullname, f, values=[
+ os.stat(fullname).st_size], icon=file_icon)
+
add_files_in_folder('', starting_path)
-layout = [[ sg.Text('File and folder browser Test') ],
- [ sg.Tree(data=treedata,
- headings=['Size', ],
- auto_size_columns=True,
- num_rows=20,
- col0_width=30,
- key='_TREE_',
- show_expanded=False,
- enable_events=True),
- ],
- [ sg.Button('Ok'), sg.Button('Cancel')]]
+layout = [[sg.Text('File and folder browser Test')],
+ [sg.Tree(data=treedata,
+ headings=['Size', ],
+ auto_size_columns=True,
+ num_rows=20,
+ col0_width=30,
+ key='-TREE-',
+ show_expanded=False,
+ enable_events=True),
+ ],
+ [sg.Button('Ok'), sg.Button('Cancel')]]
-window = sg.Window('Tree Element Test').Layout(layout)
+window = sg.Window('Tree Element Test', layout)
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event in (None, 'Cancel'):
break
print(event, values)
-
+window.close()
diff --git a/DemoPrograms/Demo_Turtle.py b/DemoPrograms/Demo_Turtle.py
index dcc2b0b2..3c778061 100644
--- a/DemoPrograms/Demo_Turtle.py
+++ b/DemoPrograms/Demo_Turtle.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
-
+import PySimpleGUI as sg
import turtle
"""
@@ -17,41 +12,41 @@ import turtle
"""
-layout = [[ sg.Text('My layout') ],
- [sg.Canvas(size=(800,800), key='_canvas_')],
- [ sg.Button('F'), sg.Button('B'), sg.Button('L'), sg.Button('R')],
+layout = [[sg.Text('My layout')],
+ [sg.Canvas(size=(800, 800), key='-canvas-')],
+ [sg.Button('F'), sg.Button('B'), sg.Button('L'), sg.Button('R')],
[sg.Button('Spiral'), sg.Button('Inside Out'), sg.Button('Circles')]]
-window = sg.Window('My new window').Layout(layout).Finalize()
+window = sg.Window('My new window', layout, finalize=True)
-canvas = window.FindElement('_canvas_').TKCanvas
+canvas = window['-canvas-'].TKCanvas
-t = turtle.RawTurtle(canvas)
-t.pencolor("#ff0000") # Red
-
-t.penup()
-t.pendown()
+a_turtle = turtle.RawTurtle(canvas)
+a_turtle.pencolor("#ff0000") # Red
+a_turtle.penup()
+a_turtle.pendown()
while True: # Event Loop
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event == 'F':
- t.forward(100)
+ a_turtle.forward(100)
elif event == 'B':
- t.back(100)
+ a_turtle.back(100)
elif event == 'L':
- t.left(90)
+ a_turtle.left(90)
elif event == 'R':
- t.right(90)
+ a_turtle.right(90)
elif event == 'Spiral':
canvas.config(bg='light green')
- t.color("blue")
+ a_turtle.color("blue")
+
def sqrfunc(size):
for i in range(4):
- t.fd(size)
- t.left(90)
+ a_turtle.fd(size)
+ a_turtle.left(90)
size = size - 5
sqrfunc(146)
sqrfunc(126)
@@ -61,12 +56,13 @@ while True: # Event Loop
sqrfunc(46)
sqrfunc(26)
elif event == 'Inside Out':
- canvas.config(bg = "light green")
- t.color("blue")
+ canvas.config(bg="light green")
+ a_turtle.color("blue")
+
def sqrfunc(size):
for i in range(4):
- t.fd(size)
- t.left(90)
+ a_turtle.fd(size)
+ a_turtle.left(90)
size = size + 5
sqrfunc(6)
sqrfunc(26)
@@ -77,8 +73,9 @@ while True: # Event Loop
sqrfunc(126)
sqrfunc(146)
elif event == 'Circles':
- t.speed(0)
+ a_turtle.speed(0)
for i in range(400):
- t.circle(2 * i*.25)
- t.circle(-2 * i*.25)
- t.left(i)
+ a_turtle.circle(2 * i*.25)
+ a_turtle.circle(-2 * i*.25)
+ a_turtle.left(i)
+window.close()
diff --git a/DemoPrograms/Demo_Window_Disappear.py b/DemoPrograms/Demo_Window_Disappear.py
index 603fdb0d..32ac9b82 100644
--- a/DemoPrograms/Demo_Window_Disappear.py
+++ b/DemoPrograms/Demo_Window_Disappear.py
@@ -1,21 +1,21 @@
#!/usr/bin/env python
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
+
+# Example .disappear() .reappear() methods in window
+
layout = [[ sg.Text('My Window') ],
[ sg.Button('Disappear')]]
-window = sg.Window('My window').Layout(layout)
+window = sg.Window('My window', layout)
while True:
- event, values = window.Read()
+ event, values = window.read()
if event is None:
break
if event == 'Disappear':
- window.Disappear()
- sg.Popup('Click OK to make window reappear')
- window.Reappear()
+ window.disappear()
+ sg.popup('Click OK to make window reappear')
+ window.reappear()
+window.close()
\ No newline at end of file
diff --git a/DemoPrograms/Demo_YouTube_Intro.py b/DemoPrograms/Demo_YouTube_Intro.py
index 88c2690f..2961dee4 100644
--- a/DemoPrograms/Demo_YouTube_Intro.py
+++ b/DemoPrograms/Demo_YouTube_Intro.py
@@ -4,8 +4,8 @@ layout = [[sg.Text('What is your name?')],
[sg.InputText()],
[sg.Button('Ok')]]
-window = sg.Window('Title of Window').Layout(layout)
+window = sg.Window('Title of Window', layout)
+event, values = window.read()
+window.close()
-event, values = window.Read()
-
-sg.Popup('Hello {}'.format(values[0]))
\ No newline at end of file
+sg.popup('Hello {}'.format(values[0]))
\ No newline at end of file
diff --git a/DemoPrograms/Demo_Youtube-dl_Frontend.py b/DemoPrograms/Demo_Youtube-dl_Frontend.py
index c2503b85..1c2a37e5 100644
--- a/DemoPrograms/Demo_Youtube-dl_Frontend.py
+++ b/DemoPrograms/Demo_Youtube-dl_Frontend.py
@@ -1,9 +1,6 @@
#!/usr/bin/env python
import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import subprocess
"""
@@ -12,50 +9,60 @@ Paste the youtube link into the GUI. The GUI link is queried when you click Get
Get List will populate the pulldown list with the language options available for the video.
Choose the language to download and click Download
"""
+youtube_executable = 'path/to/youtube-dl'
+
def DownloadSubtitlesGUI():
- sg.ChangeLookAndFeel('Dark')
+ sg.change_look_and_feel('Dark')
- combobox = sg.InputCombo(values=['',], size=(10,1), key='lang')
- layout = [
- [sg.Text('Subtitle Grabber', size=(40, 1), font=('Any 15'))],
- [sg.T('YouTube Link'),sg.In(default_text='',size=(60,1), key='link', do_not_clear=True) ],
- [sg.Output(size=(90,20), font='Courier 12')],
- [sg.Button('Get List')],
- [sg.T('Language Code'), combobox, sg.Button('Download')],
- [sg.Button('Exit', button_color=('white', 'firebrick3'))]
- ]
+ combobox = sg.Combo(values=['', ], size=(10, 1), key='lang')
+ layout = [
+ [sg.Text('Subtitle Grabber', size=(40, 1), font=('Any 15'))],
+ [sg.Text('YouTube Link'), sg.Input(default_text='', size=(60, 1), key='link')],
+ [sg.Output(size=(90, 20), font='Courier 12')],
+ [sg.Button('Get List')],
+ [sg.Text('Language Code'), combobox, sg.Button('Download')],
+ [sg.Button('Exit', button_color=('white', 'firebrick3'))]
+ ]
- window = sg.Window('Subtitle Grabber launcher', text_justification='r', default_element_size=(15,1), font=('Any 14')).Layout(layout)
+ window = sg.Window('Subtitle Grabber launcher', layout,
+ text_justification='r',
+ default_element_size=(15, 1),
+ font=('Any 14'))
# ---===--- Loop taking in user input and using it to query HowDoI --- #
while True:
- event, values = window.Read()
+ event, values = window.read()
if event in ('Exit', None):
break # exit button clicked
link = values['link']
if event == 'Get List':
print('Getting list of subtitles....')
- window.Refresh()
- command = [f'C:\\Python\\Anaconda3\\Scripts\\youtube-dl.exe --list-subs {link}',]
+ window.refresh()
+ command = [youtube_executable + f' --list-subs {link}']
output = ExecuteCommandSubprocess(command, wait=True, quiet=True)
- lang_list = [o[:5].rstrip() for o in output.split('\n') if 'vtt' in o]
+ lang_list = [o[:5].rstrip()
+ for o in output.split('\n') if 'vtt' in o]
lang_list = sorted(lang_list)
- combobox.Update(values=lang_list)
+ combobox.update(values=lang_list)
print('Done')
elif event == 'Download':
lang = values['lang'] or 'en'
print(f'Downloading subtitle for {lang}...')
- window.Refresh()
- command = [f'C:\\Python\\Anaconda3\\Scripts\\youtube-dl.exe --sub-lang {lang} --write-sub {link}',]
+ window.refresh()
+ command = [youtube_executable + f' --sub-lang {lang} --write-sub {link}', ]
print(ExecuteCommandSubprocess(command, wait=True, quiet=False))
print('Done')
+ window.close()
def ExecuteCommandSubprocess(command, wait=False, quiet=True, *args):
try:
- sp = subprocess.Popen([command,*args], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ sp = subprocess.Popen([command, *args],
+ shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
if wait:
out, err = sp.communicate()
if not quiet:
@@ -72,4 +79,3 @@ def ExecuteCommandSubprocess(command, wait=False, quiet=True, *args):
if __name__ == '__main__':
DownloadSubtitlesGUI()
-
diff --git a/DemoPrograms/Demo_psutil_Kill_Processes.py b/DemoPrograms/Demo_psutil_Kill_Processes.py
index 95be7052..6bc16708 100644
--- a/DemoPrograms/Demo_psutil_Kill_Processes.py
+++ b/DemoPrograms/Demo_psutil_Kill_Processes.py
@@ -1,10 +1,5 @@
#!/usr/bin/env python
-import sys
-import sys
-if sys.version_info[0] >= 3:
- import PySimpleGUI as sg
-else:
- import PySimpleGUI27 as sg
+import PySimpleGUI as sg
import os
import signal
import psutil
@@ -45,38 +40,38 @@ def show_list_by_name(window):
display_list = []
for process in sorted_by_cpu_procs:
display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0] / 10, process[1]))
- window.FindElement('_processes_').Update(display_list)
+ window['-processes-'].update(display_list)
return display_list
def main():
# ---------------- Create Form ----------------
- # sg.ChangeLookAndFeel('Topanga')
+ # sg.change_look_and_feel('Topanga')
layout = [[sg.Text('Process Killer - Choose one or more processes',
size=(45,1), font=('Helvetica', 15), text_color='red')],
- [sg.Listbox(values=[' '], size=(50, 30), select_mode=sg.SELECT_MODE_EXTENDED, font=('Courier', 12), key='_processes_')],
+ [sg.Listbox(values=[' '], size=(50, 30), select_mode=sg.SELECT_MODE_EXTENDED, font=('Courier', 12), key='-processes-')],
[sg.Text('Click refresh once or twice.. once for list, second to get CPU usage')],
- [sg.T('Filter by typing name', font='ANY 14'), sg.In(size=(15,1), font='any 14', key='_filter_')],
+ [sg.Text('Filter by typing name', font='ANY 14'), sg.Input(size=(15,1), font='any 14', key='-filter-')],
[sg.Button('Sort by Name', ),
sg.Button('Sort by % CPU', button_color=('white', 'DarkOrange2')),
sg.Button('Kill', button_color=('white','red'), bind_return_key=True),
sg.Exit(button_color=('white', 'sea green'))]]
- window = sg.Window('Process Killer',
+ window = sg.Window('Process Killer', layout,
keep_on_top=True,
auto_size_buttons=False,
default_button_element_size=(12,1),
return_keyboard_events=True,
- ).Layout(layout).Finalize()
+ finalize=True)
display_list = show_list_by_name(window)
# ---------------- main loop ----------------
- while (True):
+ while True:
# --------- Read and update window --------
- event, values = window.Read()
- if event is None or event == 'Exit':
+ event, values = window.read()
+ if event in (None, 'Exit'):
break
# skip mouse, control key and shift key events entirely
@@ -93,16 +88,16 @@ def main():
# display_list = []
# for process in sorted_by_cpu_procs:
# display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0]/10, process[1]))
- # window.FindElement('_processes_').Update(display_list)
+ # window['-processes-'].update(display_list)
elif event == 'Kill':
- processes_to_kill = values['_processes_']
+ processes_to_kill = values['-processes-']
for proc in processes_to_kill:
pid = int(proc[0:5])
- # if sg.PopupYesNo('About to kill {} {}'.format(pid, proc[12:]), keep_on_top=True) == 'Yes':
+ # if sg.popup_yes_no('About to kill {} {}'.format(pid, proc[12:]), keep_on_top=True) == 'Yes':
try:
kill_proc_tree(pid=pid)
except:
- sg.PopupNoWait('Error killing process', auto_close_duration=1, auto_close=True)
+ sg.popup_no_wait('Error killing process', auto_close_duration=1, auto_close=True)
elif event == 'Sort by % CPU':
psutil.cpu_percent(interval=.1)
procs = psutil.process_iter()
@@ -111,16 +106,17 @@ def main():
display_list = []
for process in sorted_by_cpu_procs:
display_list.append('{:5d} {:5.2f} {}\n'.format(process[2], process[0]/10, process[1]))
- window.FindElement('_processes_').Update(display_list)
+ window['-processes-'].update(display_list)
else: # was a typed character
if display_list is not None:
new_output = []
for line in display_list:
- if values['_filter_'] in line.lower():
+ if values['-filter-'] in line.lower():
new_output.append(line)
- window.FindElement('_processes_').Update(new_output)
+ window['-processes-'].update(new_output)
+ window.close()
if __name__ == "__main__":
main()
- sys.exit(0)
\ No newline at end of file
+
\ No newline at end of file
diff --git a/DemoPrograms/TutorialCPUUtilization.py b/DemoPrograms/TutorialCPUUtilization.py
index 003115d7..f868fd9e 100644
--- a/DemoPrograms/TutorialCPUUtilization.py
+++ b/DemoPrograms/TutorialCPUUtilization.py
@@ -1,18 +1,23 @@
import PySimpleGUI as sg
import psutil
-layout = [ [sg.Text('CPU Utilization')] ,
- [sg.Text('', size=(8,2), font='Helvetica 20', justification='center', key='_text_')],
- [sg.Exit()]]
+# Usage of PSG and cpu data
-window = sg.Window('CPU Meter').Layout(layout)
+layout = [[sg.Text('CPU Utilization')],
+ [sg.Text('', size=(8, 2), font='Helvetica 20',
+ justification='center', key='-text-')],
+ [sg.Exit()]]
+
+window = sg.Window('CPU Meter', layout)
while True:
- button, values = window.ReadNonBlocking()
+ event, values = window.ReadNonBlocking()
- if button == 'Exit' or values is None:
+ if event == 'Exit' or values is None:
break
cpu_percent = psutil.cpu_percent(interval=1)
- window.FindElement('_text_').Update(f'CPU {cpu_percent:02.0f}%')
+ window['-text-'].update(f'CPU {cpu_percent:02.0f}%')
+
+window.close()
diff --git a/DemoPrograms/ping.py b/DemoPrograms/ping.py
index 3df8635d..7422b9ce 100644
--- a/DemoPrograms/ping.py
+++ b/DemoPrograms/ping.py
@@ -206,7 +206,13 @@
#=============================================================================#
import argparse
-import os, sys, socket, struct, select, time, signal
+import os
+import sys
+import socket
+import struct
+import select
+import time
+import signal
__description__ = 'A pure python ICMP ping implementation using raw sockets.'
@@ -224,25 +230,29 @@ WAIT_TIMEOUT = 3.0
#=============================================================================#
# ICMP parameters
-ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
-ICMP_ECHO = 8 # Echo request (per RFC792)
-ICMP_MAX_RECV = 2048 # Max size of incoming buffer
+ICMP_ECHOREPLY = 0 # Echo reply (per RFC792)
+ICMP_ECHO = 8 # Echo request (per RFC792)
+ICMP_MAX_RECV = 2048 # Max size of incoming buffer
MAX_SLEEP = 1000
+
class MyStats:
- thisIP = "0.0.0.0"
+ thisIP = "0.0.0.0"
pktsSent = 0
pktsRcvd = 0
- minTime = 999999999
- maxTime = 0
- totTime = 0
+ minTime = 999999999
+ maxTime = 0
+ totTime = 0
avrgTime = 0
fracLoss = 1.0
-myStats = MyStats # NOT Used globally anymore.
+
+myStats = MyStats # NOT Used globally anymore.
#=============================================================================#
+
+
def checksum(source_string):
"""
A port of the functionality of in_cksum() from ping.c
@@ -251,7 +261,7 @@ def checksum(source_string):
Network data is big-endian, hosts are typically little-endian
"""
countTo = (int(len(source_string)/2))*2
- sum = 0
+ suma = 0
count = 0
# Handle bytes in pairs (decoding as short ints)
@@ -265,42 +275,46 @@ def checksum(source_string):
loByte = source_string[count + 1]
hiByte = source_string[count]
try: # For Python3
- sum = sum + (hiByte * 256 + loByte)
+ suma = suma + (hiByte * 256 + loByte)
except: # For Python2
- sum = sum + (ord(hiByte) * 256 + ord(loByte))
+ suma = suma + (ord(hiByte) * 256 + ord(loByte))
count += 2
# Handle last byte if applicable (odd-number of bytes)
# Endianness should be irrelevant in this case
- if countTo < len(source_string): # Check for odd length
+ if countTo < len(source_string): # Check for odd length
loByte = source_string[len(source_string)-1]
try: # For Python3
- sum += loByte
+ suma += loByte
except: # For Python2
- sum += ord(loByte)
+ suma += ord(loByte)
- sum &= 0xffffffff # Truncate sum to 32 bits (a variance from ping.c, which
- # uses signed ints, but overflow is unlikely in ping)
+ # Truncate suma to 32 bits (a variance from ping.c, which
+ suma &= 0xffffffff
+ # uses signed ints, but overflow is unlikely in ping)
- sum = (sum >> 16) + (sum & 0xffff) # Add high 16 bits to low 16 bits
- sum += (sum >> 16) # Add carry from above (if any)
- answer = ~sum & 0xffff # Invert and truncate to 16 bits
+ suma = (suma >> 16) + (suma & 0xffff) # Add high 16 bits to low 16 bits
+ suma += (suma >> 16) # Add carry from above (if any)
+ answer = ~suma & 0xffff # Invert and truncate to 16 bits
answer = socket.htons(answer)
return answer
#=============================================================================#
-def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet = False):
+
+
+def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet=False):
"""
Returns either the delay (in ms) or None on timeout.
"""
delay = None
- try: # One could use UDP here, but it's obscure
- mySocket = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
+ try: # One could use UDP here, but it's obscure
+ mySocket = socket.socket(
+ socket.AF_INET, socket.SOCK_RAW, socket.getprotobyname("icmp"))
except socket.error as e:
print("failed. (socket error: '%s')" % e.args[1])
- raise # raise the original error
+ raise # raise the original error
my_ID = os.getpid() & 0xFFFF
@@ -311,7 +325,8 @@ def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet =
myStats.pktsSent += 1
- recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(mySocket, my_ID, timeout)
+ recvTime, dataSize, iphSrcIP, icmpSeqNumber, iphTTL = receive_one_ping(
+ mySocket, my_ID, timeout)
mySocket.close()
@@ -334,6 +349,8 @@ def do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size, quiet =
return delay
#=============================================================================#
+
+
def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
"""
Send one ping to the given >destIP<.
@@ -364,9 +381,8 @@ def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
#data = bytes(padBytes)
data = bytearray(padBytes)
-
# Calculate the checksum on the data and the dummy header.
- myChecksum = checksum(header + data) # Checksum is in network order
+ myChecksum = checksum(header + data) # Checksum is in network order
# Now that we have the right checksum, we put that in. It's just easier
# to make up a new header than to stuff it into the dummy.
@@ -379,7 +395,8 @@ def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
sendTime = default_timer()
try:
- mySocket.sendto(packet, (destIP, 1)) # Port number is irrelevant for ICMP
+ # Port number is irrelevant for ICMP
+ mySocket.sendto(packet, (destIP, 1))
except socket.error as e:
print("General failure (%s)" % (e.args[1]))
return
@@ -387,17 +404,19 @@ def send_one_ping(mySocket, destIP, myID, mySeqNumber, packet_size):
return sendTime
#=============================================================================#
+
+
def receive_one_ping(mySocket, myID, timeout):
"""
Receive the ping from the socket. Timeout = in ms
"""
timeLeft = timeout/1000
- while True: # Loop while waiting for packet or timeout
+ while True: # Loop while waiting for packet or timeout
startedSelect = default_timer()
whatReady = select.select([mySocket], [], [], timeLeft)
howLongInSelect = (default_timer() - startedSelect)
- if whatReady[0] == []: # Timeout
+ if whatReady[0] == []: # Timeout
return None, 0, 0, 0, 0
timeReceived = default_timer()
@@ -406,18 +425,18 @@ def receive_one_ping(mySocket, myID, timeout):
ipHeader = recPacket[:20]
iphVersion, iphTypeOfSvc, iphLength, \
- iphID, iphFlags, iphTTL, iphProtocol, \
- iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
- "!BBHHHBBHII", ipHeader
- )
+ iphID, iphFlags, iphTTL, iphProtocol, \
+ iphChecksum, iphSrcIP, iphDestIP = struct.unpack(
+ "!BBHHHBBHII", ipHeader
+ )
icmpHeader = recPacket[20:28]
icmpType, icmpCode, icmpChecksum, \
- icmpPacketID, icmpSeqNumber = struct.unpack(
- "!BBHHH", icmpHeader
- )
+ icmpPacketID, icmpSeqNumber = struct.unpack(
+ "!BBHHH", icmpHeader
+ )
- if icmpPacketID == myID: # Our packet
+ if icmpPacketID == myID: # Our packet
dataSize = len(recPacket) - 28
#print (len(recPacket.encode()))
return timeReceived, (dataSize+8), iphSrcIP, icmpSeqNumber, iphTTL
@@ -427,6 +446,8 @@ def receive_one_ping(mySocket, myID, timeout):
return None, 0, 0, 0, 0
#=============================================================================#
+
+
def dump_stats(myStats):
"""
Show stats when pings are done
@@ -434,7 +455,8 @@ def dump_stats(myStats):
print("\n----%s PYTHON PING Statistics----" % (myStats.thisIP))
if myStats.pktsSent > 0:
- myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd)/myStats.pktsSent
+ myStats.fracLoss = (myStats.pktsSent -
+ myStats.pktsRcvd)/myStats.pktsSent
print("%d packets transmitted, %d packets received, %0.1f%% packet loss" % (
myStats.pktsSent, myStats.pktsRcvd, 100.0 * myStats.fracLoss
@@ -449,6 +471,8 @@ def dump_stats(myStats):
return
#=============================================================================#
+
+
def signal_handler(signum, frame):
"""
Handle exit via signals
@@ -458,6 +482,8 @@ def signal_handler(signum, frame):
sys.exit(0)
#=============================================================================#
+
+
def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
packet_size=PACKET_SIZE, path_finder=False):
"""
@@ -469,13 +495,14 @@ def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
# Handle Ctrl-Break e.g. under Windows
signal.signal(signal.SIGBREAK, signal_handler)
- myStats = MyStats() # Reset the stats
+ myStats = MyStats() # Reset the stats
- mySeqNumber = 0 # Starting value
+ mySeqNumber = 0 # Starting value
try:
destIP = socket.gethostbyname(hostname)
- print("\nPYTHON PING %s (%s): %d data bytes" % (hostname, destIP, packet_size))
+ print("\nPYTHON PING %s (%s): %d data bytes" %
+ (hostname, destIP, packet_size))
except socket.gaierror as e:
print("\nPYTHON PING: Unknown host: %s (%s)" % (hostname, e.args[1]))
print()
@@ -484,7 +511,8 @@ def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
myStats.thisIP = destIP
for i in range(count):
- delay = do_one(myStats, destIP, hostname, timeout, mySeqNumber, packet_size)
+ delay = do_one(myStats, destIP, hostname,
+ timeout, mySeqNumber, packet_size)
if delay == None:
delay = 0
@@ -498,13 +526,15 @@ def verbose_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
dump_stats(myStats)
#=============================================================================#
+
+
def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
packet_size=PACKET_SIZE, path_finder=False):
"""
Same as verbose_ping, but the results are returned as tuple
"""
- myStats = MyStats() # Reset the stats
- mySeqNumber = 0 # Starting value
+ myStats = MyStats() # Reset the stats
+ mySeqNumber = 0 # Starting value
try:
destIP = socket.gethostbyname(hostname)
@@ -519,12 +549,12 @@ def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
if path_finder:
fakeStats = MyStats()
do_one(fakeStats, destIP, hostname, timeout,
- mySeqNumber, packet_size, quiet=True)
+ mySeqNumber, packet_size, quiet=True)
time.sleep(0.5)
for i in range(count):
delay = do_one(myStats, destIP, hostname, timeout,
- mySeqNumber, packet_size, quiet=True)
+ mySeqNumber, packet_size, quiet=True)
if delay == None:
delay = 0
@@ -536,7 +566,8 @@ def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
time.sleep((MAX_SLEEP - delay)/1000)
if myStats.pktsSent > 0:
- myStats.fracLoss = (myStats.pktsSent - myStats.pktsRcvd)/myStats.pktsSent
+ myStats.fracLoss = (myStats.pktsSent -
+ myStats.pktsRcvd)/myStats.pktsSent
if myStats.pktsRcvd > 0:
myStats.avrgTime = myStats.totTime / myStats.pktsRcvd
@@ -544,8 +575,10 @@ def quiet_ping(hostname, timeout=WAIT_TIMEOUT, count=NUM_PACKETS,
return myStats.maxTime, myStats.minTime, myStats.avrgTime, myStats.fracLoss
#=============================================================================#
+
+
def main():
-
+
parser = argparse.ArgumentParser(description=__description__)
parser.add_argument('-q', '--quiet', action='store_true',
help='quiet output')
@@ -568,5 +601,6 @@ def main():
# ping(args.destination, timeout=args.timeout*1000, count=args.count,
# packet_size=args.packet_size)
+
if __name__ == '__main__':
main()
diff --git a/PySimpleGUI.py b/PySimpleGUI.py
index 511cf1b0..abecabd1 100644
--- a/PySimpleGUI.py
+++ b/PySimpleGUI.py
@@ -628,7 +628,7 @@ class Element():
:param event:
"""
- print(f'In return handler. event = {event}')
+ # print(f'In return handler. event = {event}')
MyForm = self.ParentForm
button_element = self._FindReturnKeyBoundButton(MyForm)
if button_element is not None: