diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py index 333f5a87..230ca479 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Dashboard.py @@ -85,7 +85,8 @@ def main(location): margins=(1,1), element_padding=(0,0), border_depth=0, - location=location) + location=location, + right_click_menu=[[''], ['Edit Me', 'Exit',]]) graphs = [] @@ -95,12 +96,15 @@ def main(location): window[('-TXT-', (rows, cols))], 0, colors[(rows*NUM_COLS+cols)%len(colors)])] + # ---------------- main loop ---------------- while True : # --------- Read and update window once every Polling Frequency -------- event, values = window.read(timeout=POLL_FREQUENCY) if event in (sg.WIN_CLOSED, 'Exit'): # Be nice and give an exit break + elif event == 'Edit Me': + sg.execute_editor(__file__) # read CPU for each core stats = psutil.cpu_percent(percpu=True) diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Gauge.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Gauge.py index a387b1ac..97b164ce 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Gauge.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Gauge.py @@ -42,7 +42,7 @@ class Gauge(): : Return Addition result for number1 and number2. """ - return number1 + number1 + return number1 + number2 def limit(number): """ @@ -274,7 +274,7 @@ def main(location): [sg.T(size=(5, 1), font='Any 20', justification='c', background_color='black', k='-gauge VALUE-')]] - window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT) + window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, background_color='black', element_justification='c', finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True) gauge = Gauge(pointer_color=sg.theme_text_color(), clock_color=sg.theme_text_color(), major_tick_color=sg.theme_text_color(), minor_tick_color=sg.theme_input_background_color(), pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45, @@ -293,7 +293,8 @@ def main(location): # ----------- update the graphics and text in the window ------------ # update the window, wait for a while, then check for exit event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS) - if event == sg.WIN_CLOSED or event == 'Exit': + if event in (sg.WIN_CLOSE_ATTEMPTED_EVENT, 'Exit'): + sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting break if event == 'Edit Me': sg.execute_editor(__file__) @@ -306,5 +307,5 @@ if __name__ == '__main__': location = sys.argv[1].split(',') location = (int(location[0]), int(location[1])) else: - location = (None, None) + location = sg.user_settings_get_entry('-location-', (None, None)) main(location) diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Grid_Of_Gauges.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Grid_Of_Gauges.py index 96ecd738..932faa28 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Grid_Of_Gauges.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Grid_Of_Gauges.py @@ -2,6 +2,7 @@ import PySimpleGUI as sg import sys import psutil +import math """ Desktop floating widget - CPU Cores as Gauges @@ -17,20 +18,17 @@ import psutil Copyright 2020 PySimpleGUI """ -GRAPH_WIDTH = 120 # each individual graph size in pixels -GRAPH_HEIGHT = 40 +# gsize = (120, 75) +# gsize = (85, 40) +gsize = (55, 30) + TRANSPARENCY = .8 # how transparent the window looks. 0 = invisible, 1 = normal window NUM_COLS = 4 -POLL_FREQUENCY = 2000 # how often to update graphs in milliseconds +POLL_FREQUENCY = 1500 # how often to update graphs in milliseconds colors = ('#23a0a0', '#56d856', '#be45be', '#5681d8', '#d34545', '#BE7C29') - -import math -import random - - class Gauge(): def mapping(func, sequence, *argc): """ @@ -56,7 +54,7 @@ class Gauge(): : Return Addition result for number1 and number2. """ - return number1 + number1 + return number1 + number2 def limit(number): """ @@ -67,7 +65,6 @@ class Gauge(): angel degree in 0 ~ 360, return 0 if number < 0, 360 if number > 360. """ return max(min(360, number), 0) - class Clock(): """ Draw background circle or arc @@ -134,7 +131,7 @@ class Gauge(): self.figure = [] self.stop_angle = angle self.graph_elem = graph_elem - self.new(degree=angle) + self.new(degree=angle, color=pointer_color) def new(self, degree=0, color=None): """ @@ -224,10 +221,10 @@ class Gauge(): All angles defined as count clockwise from negative x-axis. Should create instance of clock, pointer, minor tick and major tick first. """ - def __init__(self, center=(0, 0), start_angle=0, stop_angle=180, major_tick_width=5, minor_tick_width=2,major_tick_start_radius=90, major_tick_stop_radius=100, major_tick_step=30, clock_radius=100, pointer_line_width=5, pointer_inner_radius=10, pointer_outer_radius=75, pointer_color='white', pointer_origin_color='black', pointer_outer_color='white', pointer_angle=0, degree=0, clock_color='white', major_tick_color='black', minor_tick_color='black', minor_tick_start_radius=90, minor_tick_stop_radius=100, graph_elem=None): + def __init__(self, center=(0, 0), start_angle=0, stop_angle=180, major_tick_width=5, minor_tick_width=2,major_tick_start_radius=90, major_tick_stop_radius=100, minor_tick_step=5, major_tick_step=30, clock_radius=100, pointer_line_width=5, pointer_inner_radius=10, pointer_outer_radius=75, pointer_color='white', pointer_origin_color='black', pointer_outer_color='white', pointer_angle=0, degree=0, clock_color='white', major_tick_color='black', minor_tick_color='black', minor_tick_start_radius=90, minor_tick_stop_radius=100, graph_elem=None): self.clock = Gauge.Clock(start_angle=start_angle, stop_angle=stop_angle, fill_color=clock_color, radius=clock_radius, graph_elem=graph_elem) - self.minor_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=minor_tick_width, line_color=minor_tick_color, start_radius=minor_tick_start_radius, stop_radius=minor_tick_stop_radius, graph_elem=graph_elem) + self.minor_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=minor_tick_width, line_color=minor_tick_color, start_radius=minor_tick_start_radius, stop_radius=minor_tick_stop_radius, graph_elem=graph_elem, step=minor_tick_step) self.major_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=major_tick_width, start_radius=major_tick_start_radius, stop_radius=major_tick_stop_radius, step=major_tick_step, line_color=major_tick_color, graph_elem=graph_elem) self.pointer = Gauge.Pointer(angle=pointer_angle, inner_radius=pointer_inner_radius, outer_radius=pointer_outer_radius, pointer_color=pointer_color, outer_color=pointer_outer_color, origin_color=pointer_origin_color, line_width=pointer_line_width, graph_elem=graph_elem) @@ -250,7 +247,7 @@ class Gauge(): if self.pointer: self.pointer.move(delta_x, delta_y) - def change(self, degree=None, step=1): + def change(self, degree=None, step=1, pointer_color=None): """ Rotation of pointer call it with degree and step to set initial options for rotation. @@ -266,17 +263,17 @@ class Gauge(): new_degree = now + step if ((step > 0 and new_degree < self.pointer.stop_degree) or (step < 0 and new_degree > self.pointer.stop_degree)): - self.pointer.new(degree=new_degree, color='red' if new_degree > 90 else None) + self.pointer.new(degree=new_degree, color=pointer_color) return False else: - self.pointer.new(degree=self.pointer.stop_degree, color='red' if self.pointer.stop_degree > 90 else None) + self.pointer.new(degree=self.pointer.stop_degree, color=pointer_color) return True - +# ------------------------------ BEGINNING OF CPU WIDGET GUI CODE ------------------------------ # DashGraph does the drawing of each graph class DashGraph(object): - def __init__(self, graph_elem, text_elem, starting_count, color): + def __init__(self, gsize, graph_elem, text_elem, starting_count, color): self.graph_current_item = 0 self.graph_elem = graph_elem # type: sg.Graph self.text_elem = text_elem @@ -285,26 +282,36 @@ class DashGraph(object): self.color = color self.line_list = [] # list of currently visible lines. Used to delete oild figures - self.gauge = Gauge(pointer_color=color, clock_color=color, major_tick_color=color, - minor_tick_color=color, pointer_outer_color=sg.theme_text_color(), major_tick_start_radius=45, - minor_tick_start_radius=45, minor_tick_stop_radius=50, major_tick_stop_radius=50, major_tick_step=30, clock_radius=50, pointer_line_width=3, - pointer_inner_radius=10, pointer_outer_radius=50, graph_elem=graph_elem) + self.gauge = Gauge(pointer_color=color, + clock_color=color, + major_tick_color=color, + minor_tick_color=color, + pointer_outer_color=color, + major_tick_start_radius=gsize[1] - 10, + minor_tick_start_radius=gsize[1] - 10, + minor_tick_stop_radius=gsize[1] - 5, + major_tick_stop_radius=gsize[1] - 5, + clock_radius=gsize[1] - 5, + pointer_outer_radius=gsize[1] - 5, + major_tick_step=30, + minor_tick_step=15, + pointer_line_width=3, + pointer_inner_radius=10, + graph_elem=graph_elem) + self.gauge.change(degree=0) def graph_percentage_abs(self, value): - if self.gauge.change(): + if self.gauge.change(pointer_color='red' if value > 50 else None): new_angle = value*180/100 - self.gauge.change(degree=new_angle, step=new_angle) - self.gauge.change() + self.gauge.change(degree=new_angle, step=100, pointer_color='red' if value > 50 else None) + self.gauge.change(pointer_color='red' if value > 50 else None) def text_display(self, text): self.text_elem.update(text) def main(location): - gsize = (100, 55) - - # A couple of "User defined elements" that combine several elements and enable bulk edits def Txt(text, **kwargs): return(sg.Text(text, font=('Helvetica 8'), **kwargs)) @@ -312,7 +319,7 @@ def main(location): def GraphColumn(name, key): layout = [ [sg.Graph(gsize, (-gsize[0] // 2, 0), (gsize[0] // 2, gsize[1]), key=key+'-GRAPH-')], - [sg.T(size=(5, 1), justification='c', font='Courier 14', k=key+'-GAUGE VALUE-')]] + [sg.T(size=(5, 1), justification='c', font='Courier 10', k=key+'-GAUGE VALUE-')]] return sg.Column(layout, pad=(2, 2), element_justification='c') num_cores = len(psutil.cpu_percent(percpu=True)) # get the number of cores in the CPU @@ -320,9 +327,7 @@ def main(location): sg.theme('black') sg.set_options(element_padding=(0,0), margins=(1,1), border_width=0) - # 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'), + layout = [[ sg.Button(image_data=sg.red_x, button_color=('black', 'black'), key='Exit', tooltip='Closes window'), sg.Text(' CPU Core Usage')] ] # add on the graphs @@ -342,10 +347,12 @@ def main(location): use_default_focus=False, finalize=True, location=location, - right_click_menu=[[''], 'Exit']) + right_click_menu=[[''], 'Exit'], + # transparent_color='black', + ) # setup graphs & initial values - graphs = [DashGraph(window['-CPU-'+str(i)+'-GRAPH-'], + graphs = [DashGraph(gsize, window['-CPU-'+str(i)+'-GRAPH-'], window['-CPU-'+str(i) + '-GAUGE VALUE-'], 0, colors[i%6]) for i in range(num_cores) ] diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py index 7d2a6619..c4636477 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py @@ -4,7 +4,7 @@ import sys """ Another simple Desktop Widget using PySimpleGUI - This time a RAM indicator. The Widget is square. + This time a CPU Usage indicator. The Widget is square. The bottom section will be shaded to represent the total amount CPU currently in use. Uses the theme's button color for colors. @@ -13,23 +13,19 @@ import sys """ ALPHA = 0.5 -THEME = 'Dark purple 6 ' +THEME = 'Dark purple 6' GSIZE = (160, 160) UPDATE_FREQUENCY_MILLISECONDS = 2 * 1000 -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(location): - graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-', enable_events=True) + graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-') + layout = [[graph]] - window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT) + window = sg.Window('CPU Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True) - text_id2 = graph.draw_text(f'CPU', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0]) + text_id2 = graph.draw_text(f'CPU', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0]) while True: # Event Loop @@ -45,7 +41,8 @@ def main(location): # update the window, wait for a while, then check for exit event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS) - if event == sg.WIN_CLOSED or event == 'Exit': + if event in (sg.WIN_CLOSE_ATTEMPTED_EVENT, 'Exit'): + sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting break if event == 'Edit Me': sg.execute_editor(__file__) @@ -62,5 +59,5 @@ if __name__ == '__main__': location = sys.argv[1].split(',') location = (int(location[0]), int(location[1])) else: - location = (None, None) + location = sg.user_settings_get_entry('-location-', (None, None)) main(location) diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py index e1853239..fa83b089 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py @@ -24,7 +24,6 @@ g_cpu_percent = 0 g_procs = None g_exit = False - def CPU_thread(args): global g_interval, g_cpu_percent, g_procs, g_exit @@ -53,7 +52,7 @@ def main(location): ] window = sg.Window('Top CPU Processes', layout, - no_titlebar=True, keep_on_top=True,location=location, use_default_focus=False, alpha_channel=.8, grab_anywhere=True) + no_titlebar=True, keep_on_top=True,location=location, use_default_focus=False, alpha_channel=.8, grab_anywhere=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT) # start cpu measurement thread thread = Thread(target=CPU_thread, args=(None,)) @@ -67,9 +66,10 @@ def main(location): # --------- Do Button Operations -------- if event in (sg.WIN_CLOSED, 'Exit'): break - + elif event == 'Edit Me': + sg.execute_editor(__file__) timeout_value = int(values['spin']) * 1000 # for now on, use spinner for timeout - + g_interval = int(values['spin']) cpu_percent = g_cpu_percent display_string = '' if g_procs: diff --git a/DemoPrograms/Demo_Desktop_Widget_Count_To_A_Goal.py b/DemoPrograms/Demo_Desktop_Widget_Count_To_A_Goal.py index f53b2d94..ca95912b 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Count_To_A_Goal.py +++ b/DemoPrograms/Demo_Desktop_Widget_Count_To_A_Goal.py @@ -50,7 +50,7 @@ class Gauge(): : Return Addition result for number1 and number2. """ - return number1 + number1 + return number1 + number2 def limit(number): """ diff --git a/DemoPrograms/Demo_Desktop_Widget_Date.pyw b/DemoPrograms/Demo_Desktop_Widget_Date.pyw new file mode 100644 index 00000000..b4a3d887 --- /dev/null +++ b/DemoPrograms/Demo_Desktop_Widget_Date.pyw @@ -0,0 +1,198 @@ +import PySimpleGUI as sg +import sys +from datetime import datetime +from datetime import timedelta +""" + Desktop Widget - Display the date + Simple display of the date in the format of: + Day of week Day Month Year + + You can change the format by modifying the function get_date_string + + Copyright 2021 PySimpleGUI +""" + +ALPHA = 0.9 # Initial alpha until user changes +THEME = 'Dark green 3' # Initial theme until user changes +refresh_font = title_font = 'Courier 8' +main_info_font = sg.user_settings_get_entry('-main info font-', 'Courier 60') + +main_info_size = (12, 1) +UPDATE_FREQUENCY_MILLISECONDS = 1000 * 60 * 60 # update every hour by default until set by user + + +def choose_theme(location, size): + """ + A window to allow new themes to be tried out. + Changes the theme to the newly chosen one and returns theme's name + Automaticallyi switches to new theme and saves the setting in user settings file + + :param location: (x,y) location of the Widget's window + :type location: Tuple[int, int] + :param size: Size in pixels of the Widget's window + :type size: Tuple[int, int] + :return: The name of the newly selected theme + :rtype: None | str + """ + layout = [[sg.Text('Try a theme')], + [sg.Listbox(values=sg.theme_list(), size=(20, 20), key='-LIST-', enable_events=True)], + [sg.OK(), sg.Cancel()]] + + window = sg.Window('Look and Feel Browser', layout, location=location) + old_theme = sg.theme() + while True: # Event Loop + event, values = window.read() + if event in (sg.WIN_CLOSED, 'Exit', 'OK', 'Cancel'): + break + sg.theme(values['-LIST-'][0]) + window.hide() + # make at test window to the left of the current one + test_window = make_window(location=((location[0] - size[0] * 1.2, location[1])), test_window=True) + test_window.read(close=True) + window.un_hide() + window.close() + + # after choice made, save theme or restore the old one + if event == 'OK' and values['-LIST-']: + sg.theme(values['-LIST-'][0]) + sg.user_settings_set_entry('-theme-', values['-LIST-'][0]) + return values['-LIST-'][0] + else: + sg.theme(old_theme) + return None + + +def make_window(location, test_window=False): + """ + Defines the layout and creates the window for the main window + If the parm test_window is True, then a simplified, and EASY to close version is shown + + :param location: (x,y) location to create the window + :type location: Tuple[int, int] + :param test_window: If True, then this is a test window & will close by clicking on it + :type test_window: bool + :return: newly created window + :rtype: sg.Window + """ + title = sg.user_settings_get_entry('-title-', '') + if not test_window: + theme = sg.user_settings_get_entry('-theme-', THEME) + sg.theme(theme) + main_info_font = sg.user_settings_get_entry('-main info font-', 'Courier 60') + + # ------------------- Window Layout ------------------- + initial_text = get_date_string() + if test_window: + title_element = sg.Text('Click to close', font=title_font, enable_events=True) + right_click_menu = [[''], ['Exit', ]] + else: + title_element = sg.pin(sg.Text(title, size=(20, 1), font=title_font, justification='c', k='-TITLE-')) + right_click_menu = [[''], + ['Choose Title', 'Edit Me', 'New Theme', 'Save Location', 'Font', 'Refresh', 'Set Refresh Rate', 'Show Refresh Info', 'Hide Refresh Info', + 'Alpha', [str(x) for x in range(1, 11)], 'Exit', ]] + + + layout = [[title_element], + [sg.Text(initial_text, size=(len(initial_text)+2, 1), font=main_info_font, k='-MAIN INFO-', justification='c', enable_events=test_window)], + [sg.pin( + sg.Text(size=(15, 2), font=refresh_font, k='-REFRESHED-', justification='c', visible=sg.user_settings_get_entry('-show refresh-', True)))]] + + # ------------------- Window Creation ------------------- + try: + window = sg.Window('Desktop Widget Template', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_justification='c', element_padding=(0, 0), alpha_channel=sg.user_settings_get_entry('-alpha-', ALPHA), finalize=True, right_click_menu=right_click_menu, right_click_menu_tearoff=False) + except Exception as e: + if sg.popup_yes_no('Error creating the window', e, 'Do you want to delete your settings file to fix?') == 'Yes': + sg.user_settings_delete_filename() + sg.popup('Settings file deleted. Please restart your program.') + exit() + return window + +def get_date_string(): + dtime_here = datetime.utcnow() + timedelta(hours=-5) + return dtime_here.strftime('%a %d %b %Y') + + +def main(location): + """ + Where execution begins + The Event Loop lives here, but the window creation is done in another function + This is an important design pattern + + :param location: Location to create the main window if one is not found in the user settings + :type location: Tuple[int, int] + """ + + window = make_window(sg.user_settings_get_entry('-location-', location)) + + refresh_frequency = sg.user_settings_get_entry('-fresh frequency-', UPDATE_FREQUENCY_MILLISECONDS) + + while True: # Event Loop + # Normally a window.read goes here, but first we're updating the values in the window, then reading it + # First update the status information + window['-MAIN INFO-'].update(get_date_string()) + # for debugging show the last update date time + if sg.user_settings_get_entry('-title-', 'None') in ('None', 'Hide'): + window['-TITLE-'].update(visible=False) + else: + window['-TITLE-'].update(sg.user_settings_get_entry('-title-', 'None'),visible=True) + window['-REFRESHED-'].update(datetime.now().strftime("%m/%d/%Y\n%I:%M:%S %p")) + + # -------------- Start of normal event loop -------------- + event, values = window.read(timeout=refresh_frequency) + print(event, values) + if event in (sg.WIN_CLOSED, 'Exit'): # standard exit test... ALWAYS do this + break + if event == 'Edit Me': + sg.execute_editor(__file__) + elif event == 'Choose Title': + new_title = sg.popup_get_text('Choose a title for your Widget\nEnter None if you do not want anything displayed', location=window.current_location()) + if new_title is not None: + if new_title in ('None', 'Hide'): + window['-TITLE-'].update(visible=False) + else: + window['-TITLE-'].update(new_title, visible=True) + sg.user_settings_set_entry('-title-', new_title) + elif event == 'Show Refresh Info': + window['-REFRESHED-'].update(visible=True) + sg.user_settings_set_entry('-show refresh-', True) + elif event == 'Save Location': + sg.user_settings_set_entry('-location-', window.current_location()) + sg.popup_notify(f'Saved your current window location:', window.current_location(), title='Saved Location') + elif event == 'Hide Refresh Info': + window['-REFRESHED-'].update(visible=False) + sg.user_settings_set_entry('-show refresh-', False) + elif event in [str(x) for x in range(1, 11)]: # if Alpha Channel was chosen + window.set_alpha(int(event) / 10) + sg.user_settings_set_entry('-alpha-', int(event) / 10) + elif event == 'Set Refresh Rate': + choice = sg.popup_get_text('How frequently to update window in seconds? (can be a float)', + default_text=sg.user_settings_get_entry('-fresh frequency-', UPDATE_FREQUENCY_MILLISECONDS) / 1000, + location=window.current_location()) + if choice is not None: + try: + refresh_frequency = float(choice) * 1000 # convert to milliseconds + sg.user_settings_set_entry('-fresh frequency-', float(refresh_frequency)) + except Exception as e: + sg.popup_error(f'You entered an incorrect number of seconds: {choice}', f'Error: {e}', location=window.current_location()) + elif event == 'New Theme': + loc = window.current_location() + if choose_theme(window.current_location(), window.size) is not None: + window.close() # out with the old... + window = make_window(loc) # in with the new + elif event == 'Font': + font = sg.popup_get_text('Enter font string using PySimpleGUI font format (e.g. courier 70 or courier 70 bold)', default_text=sg.user_settings_get_entry('-main info font-'), keep_on_top=True) + if font: + sg.user_settings_set_entry('-main info font-', font) + loc = window.current_location() + _, window = window.close(), make_window(loc) + window.close() + + +if __name__ == '__main__': + # To start the window at a specific location, get this location on the command line + # The location should be in form x,y with no spaces + location = (None, None) # assume no location provided + if len(sys.argv) > 1: + location = sys.argv[1].split(',') + location = (int(location[0]), int(location[1])) + main(location) \ No newline at end of file diff --git a/DemoPrograms/Demo_Desktop_Widget_Days_Until_Date.pyw b/DemoPrograms/Demo_Desktop_Widget_Days_Until_Date.pyw index 45fd8e11..67e5f474 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Days_Until_Date.pyw +++ b/DemoPrograms/Demo_Desktop_Widget_Days_Until_Date.pyw @@ -19,6 +19,7 @@ import datetime ALPHA = 0.9 # Initial alpha until user changes THEME = 'Dark green 3' # Initial theme until user changes +sg.user_settings_filename(filename='DaysUntil.json') refresh_font = sg.user_settings_get_entry('-refresh font-', 'Courier 8') title_font = sg.user_settings_get_entry('-title font-', 'Courier 8') main_number_font = sg.user_settings_get_entry('-main number font-', 'Courier 70') diff --git a/DemoPrograms/Demo_Desktop_Widget_Drive_Usage_Gauges.py b/DemoPrograms/Demo_Desktop_Widget_Drive_Usage_Gauges.py new file mode 100644 index 00000000..76b3a534 --- /dev/null +++ b/DemoPrograms/Demo_Desktop_Widget_Drive_Usage_Gauges.py @@ -0,0 +1,359 @@ +#!/usr/bin/env python +import PySimpleGUI as sg +import psutil +import sys +import math + +""" + Desktop "Rainmeter" style widget - Drive usage + Requires: psutil + Uses a "Gauge" to display drive usage +""" + +ALPHA = 0.7 +THEME = 'black' +UPDATE_FREQUENCY_MILLISECONDS = 20 * 1000 + +BAR_COLORS = ('#23a0a0', '#56d856', '#be45be', '#5681d8', '#d34545', '#BE7C29') +gsize = (50, 30) + +class Gauge(): + def mapping(func, sequence, *argc): + """ + Map function with extra argument, not for tuple. + : Parameters + func - function to call. + sequence - list for iteration. + argc - more arguments for func. + : Return + list of func(element of sequence, *argc) + """ + if isinstance(sequence, list): + return list(map(lambda i: func(i, *argc), sequence)) + else: + return func(sequence, *argc) + + def add(number1, number2): + """ + Add two number + : Parameter + number1 - number to add. + numeer2 - number to add. + : Return + Addition result for number1 and number2. + """ + return number1 + number1 + + def limit(number): + """ + Limit angle in range 0 ~ 360 + : Parameter + number: angle degree. + : Return + angel degree in 0 ~ 360, return 0 if number < 0, 360 if number > 360. + """ + return max(min(360, number), 0) + class Clock(): + """ + Draw background circle or arc + All angles defined as clockwise from negative x-axis. + """ + + def __init__(self, center_x=0, center_y=0, radius=100, start_angle=0, + stop_angle=360, fill_color='white', line_color='black', line_width=2, graph_elem=None): + + instance = Gauge.mapping(isinstance, [center_x, center_y, radius, start_angle, + stop_angle, line_width], (int, float)) + Gauge.mapping(isinstance, + [fill_color, line_color], str) + if False in instance: + raise ValueError + start_angle, stop_angle = Gauge.limit(start_angle), Gauge.limit(stop_angle) + self.all = [center_x, center_y, radius, start_angle, stop_angle, + fill_color, line_color, line_width] + self.figure = [] + self.graph_elem = graph_elem + self.new() + + def new(self): + """ + Draw Arc or circle + """ + x, y, r, start, stop, fill, line, width = self.all + start, stop = (180 - start, 180 - stop) if stop < start else (180 - stop, 180 - start) + if start == stop % 360: + self.figure.append(self.graph_elem.DrawCircle((x, y), r, fill_color=fill, + line_color=line, line_width=width)) + else: + self.figure.append(self.graph_elem.DrawArc((x - r, y + r), (x + r, y - r), stop - start, + start, style='arc', arc_color=fill)) + + def move(self, delta_x, delta_y): + """ + Move circle or arc in clock by delta x, delta y + """ + if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)): + raise ValueError + self.all[0] += delta_x + self.all[1] += delta_y + for figure in self.figure: + self.graph_elem.MoveFigure(figure, delta_x, delta_y) + + class Pointer(): + """ + Draw pointer of clock + All angles defined as clockwise from negative x-axis. + """ + + def __init__(self, center_x=0, center_y=0, angle=0, inner_radius=20, + outer_radius=80, outer_color='white', pointer_color='blue', + origin_color='black', line_width=2, graph_elem=None): + + instance = Gauge.mapping(isinstance, [center_x, center_y, angle, inner_radius, + outer_radius, line_width], (int, float)) + Gauge.mapping(isinstance, + [outer_color, pointer_color, origin_color], str) + if False in instance: + raise ValueError + + self.all = [center_x, center_y, angle, inner_radius, outer_radius, + outer_color, pointer_color, origin_color, line_width] + self.figure = [] + self.stop_angle = angle + self.graph_elem = graph_elem + self.new(degree=angle, color=pointer_color) + + def new(self, degree=0, color=None): + """ + Draw new pointer by angle, erase old pointer if exist + degree defined as clockwise from negative x-axis. + """ + (center_x, center_y, angle, inner_radius, outer_radius, + outer_color, pointer_color, origin_color, line_width) = self.all + pointer_color = color or pointer_color + if self.figure != []: + for figure in self.figure: + self.graph_elem.DeleteFigure(figure) + self.figure = [] + d = degree - 90 + self.all[2] = degree + dx1 = int(2 * inner_radius * math.sin(d / 180 * math.pi)) + dy1 = int(2 * inner_radius * math.cos(d / 180 * math.pi)) + dx2 = int(outer_radius * math.sin(d / 180 * math.pi)) + dy2 = int(outer_radius * math.cos(d / 180 * math.pi)) + self.figure.append(self.graph_elem.DrawLine((center_x - dx1, center_y - dy1), + (center_x + dx2, center_y + dy2), + color=pointer_color, width=line_width)) + self.figure.append(self.graph_elem.DrawCircle((center_x, center_y), inner_radius, + fill_color=origin_color, line_color=outer_color, line_width=line_width)) + + def move(self, delta_x, delta_y): + """ + Move pointer with delta x and delta y + """ + if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)): + raise ValueError + self.all[:2] = [self.all[0] + delta_x, self.all[1] + delta_y] + for figure in self.figure: + self.graph_elem.MoveFigure(figure, delta_x, delta_y) + + class Tick(): + """ + Create tick on click for minor tick, also for major tick + All angles defined as clockwise from negative x-axis. + """ + + def __init__(self, center_x=0, center_y=0, start_radius=90, stop_radius=100, + start_angle=0, stop_angle=360, step=6, line_color='black', line_width=2, graph_elem=None): + + instance = Gauge.mapping(isinstance, [center_x, center_y, start_radius, + stop_radius, start_angle, stop_angle, step, line_width], + (int, float)) + [Gauge.mapping(isinstance, line_color, (list, str))] + if False in instance: + raise ValueError + start_angle, stop_angle = Gauge.limit(start_angle), Gauge.limit(stop_angle) + self.all = [center_x, center_y, start_radius, stop_radius, + start_angle, stop_angle, step, line_color, line_width] + self.figure = [] + self.graph_elem = graph_elem + + self.new() + + def new(self): + """ + Draw ticks on clock + """ + (x, y, start_radius, stop_radius, start_angle, stop_angle, step, + line_color, line_width) = self.all + start_angle, stop_angle = (180 - start_angle, 180 - stop_angle + ) if stop_angle < start_angle else (180 - stop_angle, 180 - start_angle) + for i in range(start_angle, stop_angle + 1, step): + start_x = x + start_radius * math.cos(i / 180 * math.pi) + start_y = y + start_radius * math.sin(i / 180 * math.pi) + stop_x = x + stop_radius * math.cos(i / 180 * math.pi) + stop_y = y + stop_radius * math.sin(i / 180 * math.pi) + self.figure.append(self.graph_elem.DrawLine((start_x, start_y), + (stop_x, stop_y), color=line_color, width=line_width)) + + def move(self, delta_x, delta_y): + """ + Move ticks by delta x and delta y + """ + if False in Gauge.mapping(isinstance, [delta_x, delta_y], (int, float)): + raise ValueError + self.all[0] += delta_x + self.all[1] += delta_y + for figure in self.figure: + self.graph_elem.MoveFigure(figure, delta_x, delta_y) + + """ + Create Gauge + All angles defined as count clockwise from negative x-axis. + Should create instance of clock, pointer, minor tick and major tick first. + """ + def __init__(self, center=(0, 0), start_angle=0, stop_angle=180, major_tick_width=5, minor_tick_width=2,major_tick_start_radius=90, major_tick_stop_radius=100, minor_tick_step=5, major_tick_step=30, clock_radius=100, pointer_line_width=5, pointer_inner_radius=10, pointer_outer_radius=75, pointer_color='white', pointer_origin_color='black', pointer_outer_color='white', pointer_angle=0, degree=0, clock_color='white', major_tick_color='black', minor_tick_color='black', minor_tick_start_radius=90, minor_tick_stop_radius=100, graph_elem=None): + + self.clock = Gauge.Clock(start_angle=start_angle, stop_angle=stop_angle, fill_color=clock_color, radius=clock_radius, graph_elem=graph_elem) + self.minor_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=minor_tick_width, line_color=minor_tick_color, start_radius=minor_tick_start_radius, stop_radius=minor_tick_stop_radius, graph_elem=graph_elem, step=minor_tick_step) + self.major_tick = Gauge.Tick(start_angle=start_angle, stop_angle=stop_angle, line_width=major_tick_width, start_radius=major_tick_start_radius, stop_radius=major_tick_stop_radius, step=major_tick_step, line_color=major_tick_color, graph_elem=graph_elem) + self.pointer = Gauge.Pointer(angle=pointer_angle, inner_radius=pointer_inner_radius, outer_radius=pointer_outer_radius, pointer_color=pointer_color, outer_color=pointer_outer_color, origin_color=pointer_origin_color, line_width=pointer_line_width, graph_elem=graph_elem) + + self.center_x, self.center_y = self.center = center + self.degree = degree + self.dx = self.dy = 1 + + def move(self, delta_x, delta_y): + """ + Move gauge to move all componenets in gauge. + """ + self.center_x, self.center_y =self.center = ( + self.center_x+delta_x, self.center_y+delta_y) + if self.clock: + self.clock.move(delta_x, delta_y) + if self.minor_tick: + self.minor_tick.move(delta_x, delta_y) + if self.major_tick: + self.major_tick.move(delta_x, delta_y) + if self.pointer: + self.pointer.move(delta_x, delta_y) + + def change(self, degree=None, step=1, pointer_color=None): + """ + Rotation of pointer + call it with degree and step to set initial options for rotation. + Without any option to start rotation. + """ + if self.pointer: + if degree != None: + self.pointer.stop_degree = degree + self.pointer.step = step if self.pointer.all[2] < degree else -step + return True + now = self.pointer.all[2] + step = self.pointer.step + new_degree = now + step + if ((step > 0 and new_degree < self.pointer.stop_degree) or + (step < 0 and new_degree > self.pointer.stop_degree)): + self.pointer.new(degree=new_degree, color=pointer_color) + return False + else: + self.pointer.new(degree=self.pointer.stop_degree, color=pointer_color) + return True + + + +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 update_window(window): + particians = psutil.disk_partitions() + for count, part in enumerate(particians): + mount = part[0] + try: + usage = psutil.disk_usage(mount) + window[('-NAME-', mount)].update(mount) + # window[('-PROG-', mount)].update_bar(int(usage.percent)) + window[('-%-', mount)].update(f'{usage.percent}%') + window[('-STATS-', mount)].update(f'{human_size(usage.used)} / {human_size(usage.total)} = {human_size(usage.free)} free') + gauge = Gauge(pointer_color=window[('-GRAPH-', mount)].metadata, + clock_color=window[('-GRAPH-', mount)].metadata, + major_tick_color=sg.theme_input_background_color(), + minor_tick_color=sg.theme_input_text_color(), + pointer_outer_color=sg.theme_input_background_color(), + major_tick_start_radius=gsize[1] - 10, + minor_tick_start_radius=gsize[1] - 10, + minor_tick_stop_radius=gsize[1] - 5, + major_tick_stop_radius=gsize[1] - 5, + clock_radius=gsize[1] - 5, + pointer_outer_radius=gsize[1] - 5, + major_tick_step=30, + minor_tick_step=15, + pointer_line_width=3, + pointer_inner_radius=10, + graph_elem=window[('-GRAPH-', mount)]) + gauge.change(degree=0) + gauge.change(degree=180 * usage.percent / 100, step=180) + gauge.change() + except KeyError as e: # A key error means a new drive was added + print('Got a key error, so a new drive was added. Window will restart') + return False + except BaseException as e: + print(e) + + + + return True + +def create_window(location): + layout = [[sg.Text('Drive Status', font='Any 16')]] + + # Add a row for every partician that has a bar graph and text stats + particians = psutil.disk_partitions() + for count, part in enumerate(particians): + mount = part[0] + try: + bar_color = sg.theme_progress_bar_color() + this_color = BAR_COLORS[count % len(BAR_COLORS)] + usage = psutil.disk_usage(mount) + stats_info = f'{human_size(usage.used)} / {human_size(usage.total)} = {human_size(usage.free)} free' + layout += [[sg.Text(mount, size=(3, 1), key=('-NAME-', mount)), + sg.Graph(gsize, (-gsize[0] // 2, 0), (gsize[0] // 2, gsize[1]), key=('-GRAPH-', mount), metadata=this_color), + # sg.ProgressBar(100, 'h', size=(10, 15), key=('-PROG-', mount), bar_color=(this_color, bar_color[1])), + sg.Text(f'{usage.percent}%', size=(6, 1), key=('-%-', mount)), sg.T(stats_info, size=(30, 1), key=('-STATS-', mount)), + ]] + except: + pass + layout += [[sg.Text('Refresh', font='Any 8', key='-REFRESH-', enable_events=True), sg.Text('❎', enable_events=True, key='Exit Text')]] + + # ---------------- Create Window ---------------- + window = sg.Window('Drive Status Widget', layout, location=location, keep_on_top=True, grab_anywhere=True, no_titlebar=True, alpha_channel=ALPHA, use_default_focus=False, + finalize=True) + return window + +def main(location): + # Turn off the popups because key errors are normal in this program. + # Will get a key error is a new drive is added. Want to get the key error as an exception. + sg.set_options(suppress_error_popups=True, suppress_raise_key_errors=False, suppress_key_guessing=True) + sg.theme(THEME) + + window = create_window(location) + + update_window(window) # sets the progress bars + + # ---------------- Event Loop ---------------- + while True: + event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS) + if event == sg.WIN_CLOSED or event.startswith('Exit'): + break + if not update_window(window): + window.close() + window = create_window(location) + update_window(window) + +if __name__ == '__main__': + if len(sys.argv) > 1: + location = sys.argv[1].split(',') + location = (int(location[0]), int(location[1])) + else: + location = (None, None) + main(location) + diff --git a/DemoPrograms/Demo_Desktop_Widget_FedEx_Package_Tracking.py b/DemoPrograms/Demo_Desktop_Widget_FedEx_Package_Tracking.py index 0b5222a8..bf178221 100644 --- a/DemoPrograms/Demo_Desktop_Widget_FedEx_Package_Tracking.py +++ b/DemoPrograms/Demo_Desktop_Widget_FedEx_Package_Tracking.py @@ -150,6 +150,8 @@ def main(): while True: event, values = window.read(timeout=1000*60*60) # wake every hour if event == sg.WIN_CLOSED or event == 'Exit': + if event == 'Exit': + sg.user_settings_set_entry('-location-', window.current_location()) break if event == 'Add Package': window.metadata += 1 diff --git a/DemoPrograms/Demo_Desktop_Widget_Manual_Counter.py b/DemoPrograms/Demo_Desktop_Widget_Manual_Counter.py index 6a417186..6c19801a 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Manual_Counter.py +++ b/DemoPrograms/Demo_Desktop_Widget_Manual_Counter.py @@ -121,7 +121,7 @@ def main(): if new_count is not None: counter = int(new_count) elif event == 'Choose Title': - new_title = sg.popup_get_text('Choose a title for your counter', default_text=sg.user_settings_get_entry('-title-', ''), location=window.current_location(), keep_on_top=True) + new_title = sg.popup_get_text('Choose a title for your counter', default_text=sg.user_settings_get_entry('-title-', ''), location=window.current_location(), ) if new_title is not None: window['-TITLE-'].update(new_title) sg.user_settings_set_entry('-title-', new_title) @@ -134,7 +134,7 @@ def main(): # this is result of hacking code down to 99 lines in total. Not tried it before. Interesting test. _, window = window.close(), make_window(loc) elif event == 'Set Main Font': - font = sg.popup_get_text('Main Information Font and Size (e.g. courier 70)', default_text=sg.user_settings_get_entry('-main number font-'), keep_on_top=True, location=window.current_location()) + font = sg.popup_get_text('Main Information Font and Size (e.g. courier 70)', default_text=sg.user_settings_get_entry('-main number font-'), keep_on_top=False, location=window.current_location()) if font: sg.user_settings_set_entry('-main number font-', font) _, window = window.close(), make_window(loc) @@ -164,7 +164,7 @@ def main(): if sound_file is not None: sg.user_settings_set_entry('-sound file-', sound_file) elif event =='Show Settings': - sg.Print(sg.UserSettings._default_for_function_interface) + sg.popup_scrolled(sg.UserSettings._default_for_function_interface, location=window.current_location()) sg.user_settings_set_entry('-counter-', counter) @@ -172,4 +172,5 @@ def main(): if __name__ == '__main__': + sg.set_options(keep_on_top=True) main() \ No newline at end of file diff --git a/DemoPrograms/Demo_Desktop_Widget_RAM_Gauge.py b/DemoPrograms/Demo_Desktop_Widget_RAM_Gauge.py index c3ec10b5..747c550f 100644 --- a/DemoPrograms/Demo_Desktop_Widget_RAM_Gauge.py +++ b/DemoPrograms/Demo_Desktop_Widget_RAM_Gauge.py @@ -51,7 +51,7 @@ class Gauge(): : Return Addition result for number1 and number2. """ - return number1 + number1 + return number1 + number2 def limit(number): """ diff --git a/DemoPrograms/Demo_Desktop_Widget_RAM_Square.py b/DemoPrograms/Demo_Desktop_Widget_RAM_Square.py index 7232f0e8..3d1836b5 100644 --- a/DemoPrograms/Demo_Desktop_Widget_RAM_Square.py +++ b/DemoPrograms/Demo_Desktop_Widget_RAM_Square.py @@ -30,7 +30,7 @@ def main(location): graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-', enable_events=True) layout = [[graph]] - window = sg.Window('RAM Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT) + window = sg.Window('RAM Usage Widget Square', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_EXIT, enable_close_attempted_event=True) while True: # Event Loop @@ -43,7 +43,9 @@ def main(location): text_id2 = graph.draw_text(f'{human_size(ram.used)} used', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0]) event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS) - if event == sg.WIN_CLOSED or event == 'Exit': + if event in (sg.WIN_CLOSED, 'Exit', sg.WIN_CLOSE_ATTEMPTED_EVENT): + if event != sg.WIN_CLOSED: + sg.user_settings_set_entry('-location-', window.current_location()) # The line of code to save the position before exiting break elif event == 'Edit Me': sg.execute_editor(__file__) @@ -59,5 +61,5 @@ if __name__ == '__main__': location = sys.argv[1].split(',') location = (int(location[0]), int(location[1])) else: - location = (None, None) + location = sg.user_settings_get_entry('-location-', (None, None)) main(location) diff --git a/DemoPrograms/Demo_Desktop_Widget_Weather.py b/DemoPrograms/Demo_Desktop_Widget_Weather.py index add15f26..1c3e9116 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Weather.py +++ b/DemoPrograms/Demo_Desktop_Widget_Weather.py @@ -208,11 +208,12 @@ def create_window(win_location): layout = [[top_col], [lf_col, rt_col], - [bot_col]] + [bot_col], + [sg.Text(f'{sg.ver} {sg.framework_version} {sys.version}', font=('Arial', 8), background_color=BG_COLOR, text_color=TXT_COLOR, pad=(0,0))]] window = sg.Window(layout=layout, title='Weather Widget', margins=(0, 0), finalize=True, location=win_location, element_justification='center', keep_on_top=True, no_titlebar=True, grab_anywhere=True, alpha_channel=ALPHA, - right_click_menu=[[''], 'Exit']) + right_click_menu=[[''], ['Edit Me', 'Versions', 'Exit',]], enable_close_attempted_event=True) for col in ['COL1', 'COL2', 'TopCOL', 'BotCOL', '-QUIT-']: window[col].expand(expand_y=True, expand_x=True) @@ -261,14 +262,19 @@ def main(refresh_rate, win_location): while True: # Event Loop event, values = window.read(timeout=refresh_in_milliseconds) - if event in (None, '-QUIT-', 'Exit'): + if event in (None, '-QUIT-', 'Exit', sg.WIN_CLOSE_ATTEMPTED_EVENT): + sg.user_settings_set_entry('-win location-', window.current_location()) # The line of code to save the position before exiting break if event == '-CHANGE-': x, y = window.current_location() settings = change_settings(settings, (x + 200, y+50)) elif event == '-REFRESH-': sg.popup_quick_message('Refreshing...', keep_on_top=True, background_color='red', text_color='white', - auto_close_duration=3, non_blocking=False, location=(win_location[0]+170, win_location[1]+150)) + auto_close_duration=3, non_blocking=False, location=(window.current_location()[0]+window.size[0]//2-30, window.current_location()[1]+window.size[1]//2-10)) + elif event == 'Edit Me': + sg.execute_editor(__file__) + elif event == 'Versions': + sg.main_get_debug_data() elif event != sg.TIMEOUT_KEY: sg.Print('Unknown event received\nEvent & values:\n', event, values, location=win_location) @@ -282,5 +288,6 @@ if __name__ == '__main__': win_location = sys.argv[1].split(',') win_location = (int(win_location[0]), int(win_location[1])) else: - win_location = (None, None) - main(refresh_rate=1, win_location=win_location) + win_location = sg.user_settings_get_entry('-win location-', (None, None)) + + main(refresh_rate=1, win_location=win_location) \ No newline at end of file diff --git a/DemoPrograms/Demo_User_Settings_Browse_File_Folder.py b/DemoPrograms/Demo_User_Settings_Browse_File_Folder.py index 9b6bc10e..1121eb19 100644 --- a/DemoPrograms/Demo_User_Settings_Browse_File_Folder.py +++ b/DemoPrograms/Demo_User_Settings_Browse_File_Folder.py @@ -25,34 +25,38 @@ import PySimpleGUI as sg """ # ------------------- The Old Way ------------------- +import PySimpleGUI as sg layout = [[sg.Text('My Window')], [sg.InputText(size=(50, 1), key='-FILENAME-'), sg.FileBrowse()], [sg.Button('Go'), sg.Button('Exit')]] -event1, values1 = sg.Window('Window Title', layout).read(close=True) +event1, values1 = sg.Window('Normal Filename', layout).read(close=True) # ------------------- The New Way with history ------------------- +import PySimpleGUI as sg layout = [[sg.Text('My Window')], [sg.Combo(sg.user_settings_get_entry('-filenames-', []), default_value=sg.user_settings_get_entry('-last filename-', ''), size=(50, 1), key='-FILENAME-'), sg.FileBrowse()], [sg.Button('Go'), sg.Button('Exit')]] -event, values = sg.Window('Window Title', layout).read(close=True) +event, values = sg.Window('Filename with History', layout).read(close=True) if event == 'Go': sg.user_settings_set_entry('-filenames-', list(set(sg.user_settings_get_entry('-filenames-', []) + [values['-FILENAME-'], ]))) sg.user_settings_set_entry('-last filename-', values['-FILENAME-']) + # ------------------- The New Way with history and clear ------------------- +import PySimpleGUI as sg layout = [[sg.Text('My Window')], [sg.Combo(sg.user_settings_get_entry('-filenames-', []), default_value=sg.user_settings_get_entry('-last filename-', ''), size=(50, 1), key='-FILENAME-'), sg.FileBrowse()], [sg.Button('Go'), sg.B('Clear'), sg.Button('Exit')]] -window = sg.Window('Window Title', layout) +window = sg.Window('Filename History Clearable', layout) while True: event, values = window.read()