import PySimpleGUI as sg import sys import datetime """ Another simple Desktop Widget using PySimpleGUI The "Days UNTIL _______" Desktop Widget (Like Rainmeter) This widget counts the number of days UNTIL some date of your choosing. Maybe you want to track the number of days until an anniversary? Unlike other demos, this one is being released as a .pyw file. If launched on Windows, open with Pythonw.exe so that you don't get a console Window. That's how these desktop widgets are supposed to be launched, with no console. I usually launch them from another Python program that starts them all at once using the Exec APIs built into PySimpleGUI. Copyright 2021 PySimpleGUI Project """ 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') main_info_size = (None, 1) # May add ability to change theme from the user interface. For now forcing to constant UPDATE_FREQUENCY_MILLISECONDS = 1000 * 60 * 60 # update every hour def choose_theme(location): layout = [[sg.Text(f'Current theme {sg.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, keep_on_top=True) 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]) test_window = make_window(location=(location[0] - 200, location[1]), test_window=True) test_window.read(close=True) window.close() 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): title_font = sg.user_settings_get_entry('-title font-', 'Courier 8') title = sg.user_settings_get_entry('-title-', '') main_number_font = sg.user_settings_get_entry('-main number font-', 'Courier 70') if not test_window: theme = sg.user_settings_get_entry('-theme-', THEME) sg.theme(theme) alpha = sg.user_settings_get_entry('-alpha-', ALPHA) # ------------------- Window Layout ------------------- # If this is a test window (for choosing theme), then uses some extra Text Elements to display theme info # and also enables events for the elements to make the window easy to close if test_window: top_elements = [[sg.Text(title, font=title_font, justification='c', k='-TITLE-', enable_events=True)], [sg.Text('Click to close', font=title_font, enable_events=True)], [sg.Text('This is theme', font=title_font, enable_events=True)], [sg.Text(sg.theme(), font=title_font, enable_events=True)]] right_click_menu = [[''], ['Exit', ]] else: top_elements = [[sg.Text(title, size=(20, 1), font=title_font, justification='c', k='-TITLE-')]] right_click_menu = [[''], ['Choose Date', 'Choose Title', '---', 'Set Title Font', 'Set Main Font', 'Edit Me', 'Change Theme', '---', 'Refresh', 'Show Refresh Info', 'Hide Refresh Info', '---', 'Alpha', [str(x) for x in range(1, 11)], '---', 'Show Debug Info', 'Route Print To Screen', 'Stop print to screen', 'Exit', ]] layout = top_elements + \ [[sg.pin(sg.Text('0', size=main_info_size, font=main_number_font, k='-MAIN INFO NUM-', justification='c', enable_events=test_window, pad=(0, 0)))], [sg.pin(sg.Text(size=(15, None), font=refresh_font, k='-REFRESHED-', justification='c', visible=sg.user_settings_get_entry('-show refresh-', False)))]] try: window = sg.Window('Day Number', layout, location=location, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_justification='c', element_padding=(0, 0), alpha_channel=alpha, finalize=True, right_click_menu=right_click_menu, right_click_menu_tearoff=False, keep_on_top=True, enable_close_attempted_event=True) except Exception as e: if sg.popup_yes_no('Error creating your window', e, 'These are your current settings:', sg.user_settings(), 'Do you want to delete your settings file?') == 'Yes': sg.user_settings_delete_filename() sg.popup('Settings deleted.', 'Please restart your program') exit() window = None return window def main(location): if sg.user_settings_get_entry('-end date-', None) is None: sg.popup('You have no yet set up an end date - set an end date to begin.', 'When you close this Window you will be shown a calendar to choose an ending date.') new_end = sg.popup_get_date( keep_on_top=True) if new_end is not None: sg.user_settings_set_entry('-end date-', new_end) else: sg.popup_error('You have to set an end date to use this program. Try again later when you are ready to begin for real....') exit() loc = sg.user_settings_get_entry('-location-', location) if sg.user_settings_get_entry('-print to debug-', False) is True: mprint = sg.Print else: mprint = print window = make_window(loc) saved_date = sg.user_settings_get_entry('-end date-', (1, 1, 2021)) end_date = datetime.datetime(saved_date[2], saved_date[0], saved_date[1]) while True: # Event Loop # First update the status information delta = end_date - datetime.datetime.now() title_data = delta.days + 1 msg_num = f'{title_data}' window['-MAIN INFO NUM-'].update(msg_num) # for debugging show the last update date time window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y\n%I:%M:%S %p") + \ f'\nEnd date '+ end_date.strftime("%m/%d/%Y")) # -------------- Start of normal event loop -------------- event, values = window.read(timeout=UPDATE_FREQUENCY_MILLISECONDS) mprint(event, values) if event == sg.WIN_CLOSE_ATTEMPTED_EVENT or 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__) elif event == 'Choose Date': new_end = sg.popup_get_date(location=window.current_location(), keep_on_top=True) if new_end is not None: end_date = datetime.datetime(new_end[2], new_end[0], new_end[1]) sg.user_settings_set_entry('-end date-', new_end) elif event == 'Choose Title': new_title = sg.popup_get_text('Choose a title for your date', default_text=sg.user_settings_get_entry('-title-', ''), location=window.current_location(), keep_on_top=True) if new_title is not None: window['-TITLE-'].update(new_title) 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 == '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)]: window.set_alpha(int(event) / 10) sg.user_settings_set_entry('-alpha-', int(event) / 10) elif event == 'Change Theme': loc = window.current_location() if choose_theme(loc) is not None: # 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()) if font: sg.user_settings_set_entry('-main number font-', font) _, window = window.close(), make_window(loc) elif event == 'Set Title Font': font = sg.popup_get_text('Title Font and Size (e.g. courier 8)', default_text=sg.user_settings_get_entry('-title font-'), keep_on_top=True, location=window.current_location()) if font: sg.user_settings_set_entry('-title font-', font) _, window = window.close(), make_window(loc) elif event == 'Show Debug Info': mprint(sg.get_versions()) elif event == 'Route Print To Screen': mprint = sg.Print elif event == 'Stop print to screen': mprint = print 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 if len(sys.argv) > 1: location = sys.argv[1].split(',') location = (int(location[0]), int(location[1])) else: location = (None, None) main(location)