diff --git a/.github/ISSUE_TEMPLATE/issue-form---must-fill-in-this-form-with-every-new-issue-submitted.md b/.github/ISSUE_TEMPLATE/issue-form---must-fill-in-this-form-with-every-new-issue-submitted.md index eabdac93..62688945 100644 --- a/.github/ISSUE_TEMPLATE/issue-form---must-fill-in-this-form-with-every-new-issue-submitted.md +++ b/.github/ISSUE_TEMPLATE/issue-form---must-fill-in-this-form-with-every-new-issue-submitted.md @@ -61,6 +61,7 @@ These items may solve your problem. Please check those you've done by changing - - [ ] Searched main docs for your problem www.PySimpleGUI.org - [ ] Looked for Demo Programs that are similar to your goal. It is recommend you use the Demo Browser! Demos.PySimpleGUI.org +- [ ] None of your GUI code was generated by an AI algorithm like GPT - [ ] If not tkinter - looked for Demo Programs for specific port - [ ] For non tkinter - Looked at readme for your specific port if not PySimpleGUI (Qt, WX, Remi) - [ ] Run your program outside of your debugger (from a command line) diff --git a/DemoPrograms/Browser_START_HERE_Demo_Programs_Browser.py b/DemoPrograms/Browser_START_HERE_Demo_Programs_Browser.py index 60150ca8..f7c4a84d 100644 --- a/DemoPrograms/Browser_START_HERE_Demo_Programs_Browser.py +++ b/DemoPrograms/Browser_START_HERE_Demo_Programs_Browser.py @@ -5,7 +5,7 @@ import warnings import PySimpleGUI as sg -__version__ = '1.12.0' +__version__ = '1.12.2' """ PySimpleGUI Demo Program Browser @@ -36,7 +36,7 @@ __version__ = '1.12.0' Versions: 1.8.0 - Addition of option to show ALL file types, not just Python files 1.12.0 - Fix for problem with spaces in filename and using an editor specified in the demo program settings - + 1.12.2 - Better error handling for no editor configured Copyright 2021, 2022 PySimpleGUI.org """ @@ -577,16 +577,16 @@ def main(): sg.cprint(f'Editing using {editor_program}', c='white on red', end='') sg.cprint('') sg.cprint(f'{full_filename}', c='white on purple') - # if line != 1: - if using_local_editor(): - sg.execute_command_subprocess(editor_program, f'"{full_filename}"') + if not get_editor(): + sg.popup_error_with_traceback('No editor has been configured', 'You need to configure an editor in order to use this feature', 'You can configure the editor in the Demo Brower Settings or the PySimpleGUI Global Settings') else: - try: - sg.execute_editor(full_filename, line_number=int(line)) - except: + if using_local_editor(): sg.execute_command_subprocess(editor_program, f'"{full_filename}"') - # else: - # sg.execute_editor(full_filename) + else: + try: + sg.execute_editor(full_filename, line_number=int(line)) + except: + sg.execute_command_subprocess(editor_program, f'"{full_filename}"') else: sg.cprint('Editing canceled') elif event == 'Run': diff --git a/DemoPrograms/Demo_All_Elements.py b/DemoPrograms/Demo_All_Elements.py index a39c6b98..4566c226 100644 --- a/DemoPrograms/Demo_All_Elements.py +++ b/DemoPrograms/Demo_All_Elements.py @@ -11,7 +11,7 @@ Displays the values dictionary entry for each element And more! - Copyright 2021, 2022 PySimpleGUI + Copyright 2021, 2022, 2023 PySimpleGUI """ import PySimpleGUI as sg @@ -94,12 +94,11 @@ def make_window(theme): def main(): window = make_window(sg.theme()) - + # This is an Event Loop while True: event, values = window.read(timeout=100) # keep an animation running so show things are happening - window['-GIF-IMAGE-'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100) if event not in (sg.TIMEOUT_EVENT, sg.WIN_CLOSED): print('============ Event = ', event, ' ==============') print('-------- Values Dictionary (key=value) --------') @@ -108,7 +107,9 @@ def main(): if event in (None, 'Exit'): print("[LOG] Clicked Exit!") break - elif event == 'About': + + window['-GIF-IMAGE-'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100) + if event == 'About': print("[LOG] Clicked About!") sg.popup('PySimpleGUI Demo All Elements', 'Right click anywhere to see right click menu', diff --git a/DemoPrograms/Demo_Buttons_Nice_Graphics.py b/DemoPrograms/Demo_Buttons_Nice_Graphics.py index 3d372416..45b3c915 100644 --- a/DemoPrograms/Demo_Buttons_Nice_Graphics.py +++ b/DemoPrograms/Demo_Buttons_Nice_Graphics.py @@ -26,7 +26,7 @@ def resize_base64_image(image64, size): ''' image_file = io.BytesIO(base64.b64decode(image64)) img = Image.open(image_file) - img.thumbnail(size, Image.ANTIALIAS) + img.thumbnail(size, Image.LANCZOS) bio = io.BytesIO() img.save(bio, format='PNG') imgbytes = bio.getvalue() diff --git a/DemoPrograms/Demo_Chat.py b/DemoPrograms/Demo_Chat.py index 76b9a635..d3b81381 100644 --- a/DemoPrograms/Demo_Chat.py +++ b/DemoPrograms/Demo_Chat.py @@ -4,25 +4,28 @@ import PySimpleGUI as sg ''' A simple send/response chat window. Add call to your send-routine and print the response If async responses can come in, then will need to use a different design that uses PySimpleGUI async design pattern + +Copyright 2023 PySimpleGUI + ''' sg.theme('GreenTan') # give our window a spiffy set of colors layout = [[sg.Text('Your output will go here', size=(40, 1))], [sg.Output(size=(110, 20), font=('Helvetica 10'))], - [sg.Multiline(size=(70, 5), enter_submits=False, key='-QUERY-', do_not_clear=False), + [sg.Multiline(size=(70, 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', layout, font=('Helvetica', ' 13'), default_button_element_size=(8,2), use_default_focus=False) while True: # The Event Loop - event, value = window.read() + event, values = window.read() if event in (sg.WIN_CLOSED, 'EXIT'): # quit if exit button or X break if event == 'SEND': - query = value['-QUERY-'].rstrip() + query = values['-QUERY-'].rstrip() # EXECUTE YOUR COMMAND HERE print('The command you entered was {}'.format(query), flush=True) -window.close() \ No newline at end of file +window.close() diff --git a/DemoPrograms/Demo_Class_Wrapper.py b/DemoPrograms/Demo_Class_Wrapper.py index 99995ee8..4cf359c3 100644 --- a/DemoPrograms/Demo_Class_Wrapper.py +++ b/DemoPrograms/Demo_Class_Wrapper.py @@ -4,10 +4,38 @@ import PySimpleGUI as sg Demo - Class wrapper Using a class to encapsulate PySimpleGUI Window creation & event loop + + This is NOT a recommended design pattern. It mimics the object oriented design that many OO-based + GUI frameworks use, but there is no advantage to structuring you code in his manner. It adds + confusion, not clarity. + + The class version is 18 lines of code. The plain version is 13 lines of code. + + Two things about the class wrapper jump out as adding confusion: + 1. Unneccessary fragmentation of the event loop - the button click code is pulled out of the loop entirely + 2. "self" clutters the code without adding value + - Copyright 2022 PySimpleGUI + Copyright 2022, 2023 PySimpleGUI """ +''' + MM'""""'YMM dP + M' .mmm. `M 88 + M MMMMMooM 88 .d8888b. .d8888b. .d8888b. + M MMMMMMMM 88 88' `88 Y8ooooo. Y8ooooo. + M. `MMM' .M 88 88. .88 88 88 + MM. .dM dP `88888P8 `88888P' `88888P' + MMMMMMMMMMM + + M""MMMMM""M oo + M MMMMM M + M MMMMP M .d8888b. 88d888b. .d8888b. dP .d8888b. 88d888b. + M MMMM' .M 88ooood8 88' `88 Y8ooooo. 88 88' `88 88' `88 + M MMP' .MM 88. ... 88 88 88 88. .88 88 88 + M .dMMM `88888P' dP `88888P' dP `88888P' dP dP + MMMMMMMMMMM +''' class SampleGUI(): def __init__(self): @@ -35,3 +63,41 @@ class SampleGUI(): my_gui = SampleGUI() # run the event loop my_gui.run() + + +''' + M"""""""`YM dP + M mmmm. M 88 + M MMMMM M .d8888b. 88d888b. 88d8b.d8b. .d8888b. 88 + M MMMMM M 88' `88 88' `88 88'`88'`88 88' `88 88 + M MMMMM M 88. .88 88 88 88 88 88. .88 88 + M MMMMM M `88888P' dP dP dP dP `88888P8 dP + MMMMMMMMMMM + + M""MMMMM""M oo + M MMMMM M + M MMMMP M .d8888b. 88d888b. .d8888b. dP .d8888b. 88d888b. + M MMMM' .M 88ooood8 88' `88 Y8ooooo. 88 88' `88 88' `88 + M MMP' .MM 88. ... 88 88 88 88. .88 88 88 + M .dMMM `88888P' dP `88888P' dP `88888P' dP dP + MMMMMMMMMMM +''' + +def gui_function(): + layout = [ [sg.Text('My layout')], + [sg.Input(key='-IN-')], + [sg.Button('Go'), sg.Button('Exit')] ] + + window = sg.Window('My new window', layout) + + while True: # Event Loop + event, values = window.read() + if event in (sg.WIN_CLOSED, 'Exit'): + break + + if event == 'Go': + sg.popup('Go button clicked', 'Input value:', values['-IN-']) + + window.close() + +gui_function() diff --git a/DemoPrograms/Demo_Desktop_Widget_Digital_Picture_Frame.py b/DemoPrograms/Demo_Desktop_Widget_Digital_Picture_Frame.py index 8ee2c959..2e5f6bb0 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Digital_Picture_Frame.py +++ b/DemoPrograms/Demo_Desktop_Widget_Digital_Picture_Frame.py @@ -1,8 +1,12 @@ import PySimpleGUI as sg import datetime -import PIL.Image, PIL.ImageTk +import PIL +from PIL import Image import random import os +import io +import base64 + """ Another simple Desktop Widget using PySimpleGUI @@ -16,17 +20,76 @@ import os * How long to show the image and if you wnt this time to vary semi-randomly * Folder containing your images - Copyright 2021 PySimpleGUI + Copyright 2021, 2023 PySimpleGUI """ ALPHA = 0.9 # Initial alpha until user changes refresh_font = sg.user_settings_get_entry('-refresh font-', 'Courier 8') -def convert_to_bytes(file_or_bytes, resize=None): - image = PIL.Image.open(file_or_bytes) - image.thumbnail(resize) - photo_img = PIL.ImageTk.PhotoImage(image) - return photo_img + +def make_square(im, fill_color=(0, 0, 0, 0)): + x, y = im.size + size = max(x, y) + new_im = Image.new('RGBA', (size, size), fill_color) + new_im.paste(im, (int((size - x) / 2), int((size - y) / 2))) + return new_im + +def get_image_size(source): + if isinstance(source, str): + image = PIL.Image.open(source) + elif isinstance(source, bytes): + image = PIL.Image.open(io.BytesIO(base64.b64decode(source))) + else: + image = PIL.Image.open(io.BytesIO(source)) + + width, height = image.size + return (width, height) + +def convert_to_bytes(source, size=(None, None), subsample=None, zoom=None, fill=False): + """ + Will convert into bytes and optionally resize an image that is a file or a base64 bytes object. + Turns into PNG format in the process so that can be displayed by tkinter + :param source: either a string filename or a bytes base64 image object + :type source: (Union[str, bytes]) + :param size: optional new size (width, height) + :type size: (Tuple[int, int] or None) + :param subsample: change the size by multiplying width and height by 1/subsample + :type subsample: (int) + :param zoom: change the size by multiplying width and height by zoom + :type zoom: (int) + :param fill: If True then the image is filled/padded so that the image is square + :type fill: (bool) + :return: (bytes) a byte-string object + :rtype: (bytes) + """ + # print(f'converting {source} {size}') + if isinstance(source, str): + image = PIL.Image.open(source) + elif isinstance(source, bytes): + image = PIL.Image.open(io.BytesIO(base64.b64decode(source))) + else: + image = PIL.Image.open(io.BytesIO(source)) + + width, height = image.size + + scale = None + if size != (None, None): + new_width, new_height = size + scale = min(new_height/height, new_width/width) + elif subsample is not None: + scale = 1/subsample + elif zoom is not None: + scale = zoom + + resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS) if scale is not None else image + if fill and scale is not None: + resized_image = make_square(resized_image) + # encode a PNG formatted version of image into BASE64 + with io.BytesIO() as bio: + resized_image.save(bio, format="PNG") + contents = bio.getvalue() + encoded = base64.b64encode(contents) + return encoded def choose_theme(location): @@ -43,6 +106,15 @@ def choose_theme(location): else: return None +def reset_settings(): + sg.user_settings_set_entry('-time per image-', 60) + sg.user_settings_set_entry('-random time-', False) + sg.user_settings_set_entry('-image size-', (None, None)) + sg.user_settings_set_entry('-image_folder-', None) + sg.user_settings_set_entry('-location-', (None, None)) + sg.user_settings_set_entry('-single image-', None) + sg.user_settings_set_entry('-alpha-', ALPHA) + def make_window(location): alpha = sg.user_settings_get_entry('-alpha-', ALPHA) @@ -61,7 +133,7 @@ def make_window(location): layout = [[sg.Image(k='-IMAGE-', enable_events=True)], [sg.pin(sg.Column(refresh_info, key='-REFRESH INFO-', element_justification='c', visible=sg.user_settings_get_entry('-show refresh-', True)))]] - window = sg.Window('Photo Frame', 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, keep_on_top=True, enable_close_attempted_event=True) + window = sg.Window('Photo Frame', 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, keep_on_top=True, enable_close_attempted_event=True, enable_window_config_events=True) return window @@ -69,11 +141,10 @@ def make_window(location): def main(): loc = sg.user_settings_get_entry('-location-', (None, None)) sg.theme(sg.user_settings_get_entry('-theme-', None)) - window = make_window(loc) time_per_image = sg.user_settings_get_entry('-time per image-', 60) vary_randomly = sg.user_settings_get_entry('-random time-', False) - width, height = sg.user_settings_get_entry('-image size-', (400,300)) + width, height = sg.user_settings_get_entry('-image size-', (None, None)) image_folder = sg.user_settings_get_entry('-image_folder-', None) try: @@ -82,36 +153,26 @@ def main(): image_folder = None sg.user_settings_set_entry('-image_folder-', None) - single_image = sg.user_settings_get_entry('-single image-', None) + image_name = single_image = sg.user_settings_get_entry('-single image-', None) if image_folder is None and single_image is None: - while True: - images = None - image_folder = sg.popup_get_folder('Choose location of your images', location=window.current_location(), keep_on_top=True) - if image_folder is not None: - sg.user_settings_set_entry('-image_folder-', image_folder) - break - else: - if sg.popup_yes_no('No folder entered','Go you want to exit the program entirely?', keep_on_top=True) == 'Yes': - exit() - elif single_image is None: + image_name = single_image = sg.popup_get_file('Choose a starting image', keep_on_top=True) + if not single_image: + if sg.popup_yes_no('No folder entered','Go you want to exit the program entirely?', keep_on_top=True) == 'Yes': + exit() + if image_folder is not None and single_image is None: images = os.listdir(image_folder) images = [i for i in images if i.lower().endswith(('.png', '.jpg', '.gif'))] + image_name = os.path.join(image_folder, random.choice(images)) else: # means single image is not none images = None + image_name = single_image + window = make_window(loc) + + window_size = window.size + image_data = convert_to_bytes(image_name, (width, height)) + while True: # Event Loop - # First update the status information - # for debugging show the last update date time - if single_image is None: - image_name =random.choice(images) - image_data = convert_to_bytes(os.path.join(image_folder, image_name), (width, height)) - window['-FOLDER-'].update(image_folder) - else: - image_name = single_image - image_data = convert_to_bytes(single_image, (width, height)) - window['-FILENAME-'].update(image_name) - window['-IMAGE-'].update(data=image_data) - window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y %I:%M:%S %p")) # -------------- Start of normal event loop -------------- timeout = time_per_image * 1000 + (random.randint(int(-time_per_image * 500), int(time_per_image * 500)) if vary_randomly else 0) if single_image is None else None event, values = window.read(timeout=timeout) @@ -120,6 +181,28 @@ def main(): elif 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 + # First update the status information + # for debugging show the last update date time + if event == sg.TIMEOUT_EVENT: + if single_image is None: + image_name =random.choice(images) + image_data = convert_to_bytes(os.path.join(image_folder, image_name)) + window['-FOLDER-'].update(image_folder) + else: + image_name = single_image + image_data = convert_to_bytes(single_image, (width, height)) + window['-FILENAME-'].update(image_name) + window['-IMAGE-'].update(data=image_data) + window['-REFRESHED-'].update(datetime.datetime.now().strftime("%m/%d/%Y %I:%M:%S %p")) + if event == sg.WINDOW_CONFIG_EVENT: + new_size = window.size + if new_size != window_size: + print(f'resizing {new_size}') + (width, height) = new_size + image_data = convert_to_bytes(image_data, (width, height)) + window['-IMAGE-'].update(data=image_data) + window.size = get_image_size(image_data) + window_size = window.size if event == 'Edit Me': sg.execute_editor(__file__) elif event == 'Choose Image Folder': @@ -175,12 +258,15 @@ def main(): window.close() window = make_window(loc) elif event == 'Choose Single Image': - single_image = sg.popup_get_file('Choose single image to show', history=True) + image_name = single_image = sg.popup_get_file('Choose single image to show', history=True) sg.user_settings_set_entry('-single image-', single_image) - - - + (width, height) = get_image_size(single_image) + sg.user_settings_set_entry('-image size-', (width, height)) + image_data = convert_to_bytes(image_name, (width, height)) + window['-IMAGE-'].update(data=image_data) + window.size = window_size = (width, height) window.close() if __name__ == '__main__': + # reset_settings() # if get corrupted problems, uncomment this main() \ No newline at end of file diff --git a/DemoPrograms/Demo_Dispatchers.py b/DemoPrograms/Demo_Dispatchers.py index 7b9e3987..2084e9b3 100644 --- a/DemoPrograms/Demo_Dispatchers.py +++ b/DemoPrograms/Demo_Dispatchers.py @@ -12,6 +12,7 @@ * If-Else * Dictionaries * Functions as keys + * Lambda as key (callable like functions are) The handlers in this demo are all functions that are called once the event is detected @@ -22,7 +23,7 @@ event loop rather than functions, then do it in the event loop. http://www.PySimpleGUI.org - Copyright 2021 PySimpleGUI + Copyright 2021, 2022, 2023 PySimpleGUI """ import PySimpleGUI as sg @@ -76,7 +77,7 @@ def main(): [sg.Text('Status:'), sg.Text(size=(3, 1), key='-STATUS-')], [sg.Text(size=(50, 1), key='-OUT-')], [sg.Button('Simple'), sg.Button('Go'), sg.Button('Stop'), sg.Button('Other', key=do_other), - sg.Button('Tuple', key=(1,2)), sg.Button('Bad')]] + sg.Button('Tuple', key=(1,2)), sg.Button('Lambda', key= lambda window: do_other(window)), sg.Button('Bad')]] window = sg.Window('Dispatchers', layout, font='Default 16', keep_on_top=True) diff --git a/DemoPrograms/Demo_Edit_Me_Option.py b/DemoPrograms/Demo_Edit_Me_Option.py index c05130f1..b5e4be90 100644 --- a/DemoPrograms/Demo_Edit_Me_Option.py +++ b/DemoPrograms/Demo_Edit_Me_Option.py @@ -1,7 +1,7 @@ import PySimpleGUI as sg """ - Demo "Edit Me" + Demo "Edit Me" (and Version) More and more of these Demos are getting an "Edit me" option added. @@ -12,26 +12,26 @@ import PySimpleGUI as sg You can add this capability to your program by adding a right click menu to your window and calling the editor that you set up in the global PySimpleGUI options. - You need to do 2 things to make this work: - 1. Add a right click menu - requires you to add 1 parameter to your Window creation - 2. Add 1 if statement to your event loop. - + A constant MENU_RIGHT_CLICK_EDITME_VER_EXIT, when set at the right click menu shows a "Version" and "Edit Me" meny item. + You will need to have first set up your editor by using the menu in sg.main() - Copyright 2021 PySimpleGUI.org + Copyright 2021, 2022, 2023 PySimpleGUI.org """ layout = [[sg.Text('Edit this program by right clicking and choosing "Edit me"')], [sg.Button('Exit')]] -window = sg.Window('Edit Me Right Click Menu Demo', layout, right_click_menu=[[''], ['Edit Me', 'Exit',]]) +window = sg.Window('Edit Me Right Click Menu Demo', layout, right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT) while True: # Event Loop event, values = window.read() if event == sg.WIN_CLOSED or event == 'Exit': break if event == 'Edit Me': - sg.execute_editor(__file__) + sg.execute_editor(__file__) + elif event == 'Version': + sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True) window.close() diff --git a/DemoPrograms/Demo_Emoji_Toolbar_PIL.py b/DemoPrograms/Demo_Emoji_Toolbar_PIL.py index 0ff22ce9..dd0cecee 100644 --- a/DemoPrograms/Demo_Emoji_Toolbar_PIL.py +++ b/DemoPrograms/Demo_Emoji_Toolbar_PIL.py @@ -92,7 +92,7 @@ def convert_to_bytes(file_or_bytes, resize=None, fill=False): if resize: new_width, new_height = resize scale = min(new_height / cur_height, new_width / cur_width) - img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.LANCZOS) if fill: if resize is not None: img = make_square(img, resize[0]) diff --git a/DemoPrograms/Demo_Graph_Drawing_And_Dragging_Figures.py b/DemoPrograms/Demo_Graph_Drawing_And_Dragging_Figures.py index b3c1affe..3afcff8e 100644 --- a/DemoPrograms/Demo_Graph_Drawing_And_Dragging_Figures.py +++ b/DemoPrograms/Demo_Graph_Drawing_And_Dragging_Figures.py @@ -1,5 +1,4 @@ import PySimpleGUI as sg -from PIL import ImageGrab """ Demo - Drawing and moving demo @@ -7,22 +6,9 @@ from PIL import ImageGrab This demo shows how to use a Graph Element to (optionally) display an image and then use the mouse to "drag" and draw rectangles and circles. - Copyright 2020 PySimpleGUI.org + Copyright 2020, 2021, 2022, 2023 PySimpleGUI.org """ -def save_element_as_file(element, filename): - """ - Saves any element as an image file. Element needs to have an underlyiong Widget available (almost if not all of them do) - :param element: The element to save - :param filename: The filename to save to. The extension of the filename determines the format (jpg, png, gif, ?) - """ - widget = element.Widget - box = (widget.winfo_rootx(), widget.winfo_rooty(), widget.winfo_rootx() + widget.winfo_width(), widget.winfo_rooty() + widget.winfo_height()) - grab = ImageGrab.grab(bbox=box) - grab.save(filename) - - - def main(): sg.theme('Dark Blue 3') @@ -38,7 +24,6 @@ def main(): [sg.R('Bring to front', 1, key='-FRONT-', enable_events=True)], [sg.R('Move Everything', 1, key='-MOVEALL-', enable_events=True)], [sg.R('Move Stuff', 1, key='-MOVE-', enable_events=True)], - [sg.B('Save Image', key='-SAVE-')], ] layout = [[sg.Graph( @@ -49,20 +34,19 @@ def main(): enable_events=True, background_color='lightblue', drag_submits=True, - right_click_menu=[[],['Erase item',]] + motion_events=True, + right_click_menu=[[''],['Erase item','Send to back']] ), sg.Col(col, key='-COL-') ], [sg.Text(key='-INFO-', size=(60, 1))]] window = sg.Window("Drawing and Moving Stuff Around", layout, finalize=True) # get the graph element for ease of use later - graph = window["-GRAPH-"] # type: sg.Graph + graph = window["-GRAPH-"] # type: sg.Graph graph.draw_image(data=logo200, location=(0,400)) - dragging = False start_point = end_point = prior_rect = None - # graph.bind('', '+RIGHT+') - + crosshair_lines = [] while True: event, values = window.read() print(event, values) @@ -73,7 +57,14 @@ def main(): graph.set_cursor(cursor='fleur') # not yet released method... coming soon! elif not event.startswith('-GRAPH-'): graph.set_cursor(cursor='left_ptr') # not yet released method... coming soon! - + if event.endswith('+MOVE'): + window["-INFO-"].update(value=f"mouse {values['-GRAPH-']}") + # Delete crosshairs if any exists + if len(crosshair_lines): + for fig in crosshair_lines: + graph.delete_figure(fig) + crosshair_lines = [] + window.refresh() if event == "-GRAPH-": # if there's a "Graph" event, then it's a mouse x, y = values["-GRAPH-"] if not dragging: @@ -114,25 +105,28 @@ def main(): for fig in drag_figures: graph.send_figure_to_back(fig) window["-INFO-"].update(value=f"mouse {values['-GRAPH-']}") - elif event.endswith('+UP'): # The drawing has ended because mouse up + elif event.endswith('+UP'): # The drawing has ended because mouse up window["-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 prior_rect = None - elif event.endswith('+RIGHT+'): # Righ click - window["-INFO-"].update(value=f"Right clicked location {values['-GRAPH-']}") - elif event.endswith('+MOTION+'): # Righ click - window["-INFO-"].update(value=f"mouse freely moving {values['-GRAPH-']}") - elif event == '-SAVE-': - # filename = sg.popup_get_file('Choose file (PNG, JPG, GIF) to save to', save_as=True) - filename=r'test.jpg' - save_element_as_file(window['-GRAPH-'], filename) + # elif event.endswith('+RIGHT+'): # Right click + # window["-INFO-"].update(value=f"Right clicked location {values['-GRAPH-']}") + # elif event.endswith('+MOTION+'): # Right click + # window["-INFO-"].update(value=f"mouse freely moving {values['-GRAPH-']}") + elif event == 'Send to back': # Right clicked menu item + figures = graph.get_figures_at_location(values["-GRAPH-"]) # get items in front-to-back order + if figures: # make sure at least 1 item found + graph.send_figure_to_back(figures[-1]) # get the last item which will be the top-most elif event == 'Erase item': window["-INFO-"].update(value=f"Right click erase at {values['-GRAPH-']}") if values['-GRAPH-'] != (None, None): - drag_figures = graph.get_figures_at_location(values['-GRAPH-']) - for figure in drag_figures: - graph.delete_figure(figure) + figures = graph.get_figures_at_location(values['-GRAPH-']) + if figures: + graph.delete_figure(figures[-1]) # delete the one on top + location = values['-GRAPH-'] + crosshair_lines = [graph.draw_line((location[0], 0), (location[0], 800), color='red'), + graph.draw_line((0, location[1]), (800, location[1]), color='red')] window.close() diff --git a/DemoPrograms/Demo_Graph_Elem_CPU_Meter.py b/DemoPrograms/Demo_Graph_Elem_CPU_Meter.py new file mode 100644 index 00000000..f12c6213 --- /dev/null +++ b/DemoPrograms/Demo_Graph_Elem_CPU_Meter.py @@ -0,0 +1,156 @@ +import PySimpleGUI as sg +import math +import psutil + +""" + Demo Program - Display CPI Usage as a VU Meter + + Artwork and algorithm for handling of needle positioning generously provided by GitHub user neovich. + + A long-time PySimpleGUI user and brilliant programmer posted a screenshot of an incredibly + complex audio recording mixing application with features like custom sliders and VU meters made + entirely of Graph elements. I asked him to draw us some special artwork for this demo. An ENORMOUS + thank you to him for the encouragement, support, and hard work! + + This demo uses the psutil library to get the CPI utilization. It is then shown on a nicely rendered + VU meter. + + Copyright 2023 PySimpleGUI +""" + +# --- VU Meter Parameters ---------------------------------------------- + +x_needle_base = 169 +y_needle_base = 10 +needle_length = 280 +needle_multiply = 2 +needle_width = 2 +needle_color = '#434443' +needle_cutoff = 100 +angle_min = 60 +angle_max = 122 +CANVAS_KEY = 'CANVAS_vu_meter' + +# --- Colours ---------------------------------------------------------- +tick1_color = '#222222' +tick2_color = tick1_color +background = '#626059' +module_background = '#F2E2CA' +win0_background_color = background +tab_inner_colour = 'black' +background_main = background + + +sg.set_options(background_color=background, element_background_color=background) + +# ---------------------- Definitions ----------------------------------- + + +def VU_METER_update(CONT_CANVAS_vu_meter, a): + if a < angle_min : + a = angle_min + if a > angle_max: + a = angle_max + CONT_CANVAS_vu_meter.erase() + OBJ_VU_meter = CONT_CANVAS_vu_meter.draw_image(data=vu_meter_2,location= (0,234)) + x_angle = math.cos(math.radians(180-a)) + y_angle = math.sin(math.radians(180-a)) + x_cur = x_needle_base+(x_angle * needle_length) + y_cur = y_needle_base+int((y_angle * needle_length)*0.7) + x_cur_low = int(x_needle_base+(x_angle * (needle_length/needle_multiply))) + y_cur_low = int(y_needle_base+int((y_angle * (needle_length/needle_multiply))*0.7)) + OBJ_VU_meter_needle = CONT_CANVAS_vu_meter.draw_line( (x_cur_low,y_cur_low),(int(x_cur),int(y_cur)) ,color=needle_color,width=needle_width) + + +def main(): + # ------------------------- Init the VU_Meter -------------------------- + + + VU_METER_cont = [[sg.Graph ( canvas_size = ( 339,234 ), + graph_bottom_left=(0,0), + graph_top_right=(339,234), + background_color=module_background, + drag_submits=True, + enable_events=True, + float_values=True, + key=CANVAS_KEY )]] + + + # ------------------------- Tab Set Ups -------------------------------- + layout = [[sg.Column(VU_METER_cont ,background_color=module_background )]] + + + + location = sg.user_settings_get_entry('-location-', (None, None)) + + # ------------------------ Finalize Windows ---------------------------- + + window = sg.Window('CPU Usage as a VU Meter', layout, + no_titlebar=True, + auto_size_buttons=False, + keep_on_top=True, + grab_anywhere=True, + force_toplevel=False, + finalize=True, + location=location, + right_click_menu=sg.MENU_RIGHT_CLICK_EDITME_VER_EXIT, + enable_close_attempted_event=True) + + # ------------------------ Init the VU_Meter --------------------------- + + CONT_CANVAS_vu_meter = window[CANVAS_KEY] + angle = angle_min + x_angle = math.cos(math.radians(180-angle)) + y_angle = math.sin(math.radians(180-angle)) + x_cur = x_needle_base+(x_angle * needle_length) + y_cur = y_needle_base+int((y_angle * needle_length)*0.7) + x_cur_low = int(x_needle_base+(x_angle * (needle_length/needle_multiply))) + y_cur_low = int(y_needle_base+int((y_angle * (needle_length/needle_multiply))*0.7)) + OBJ_VU_meter_needle = CONT_CANVAS_vu_meter.draw_line( (x_cur_low,y_cur_low),(int(x_cur),int(y_cur)) ,color=needle_color,width=needle_width) + window.refresh() + + ######################################################################## + ## MAIN LOOP ## + ######################################################################## + temp_angle = 0 + angle_impulse = 2 + angle_range = angle_max-angle_min + while True: + event, values = window.read(timeout=30) + 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__) + elif event == 'Version': + sg.popup_scrolled(__file__, sg.get_versions(), location=window.current_location(), keep_on_top=True, non_blocking=True) + + + cpu_percent = psutil.cpu_percent(interval=1) + target_angle = angle_range * cpu_percent/100 + angle_min + if temp_angle == 0: + temp_angle = target_angle + + delta = abs(temp_angle - target_angle) + if temp_angle > target_angle: + temp_angle -= min(angle_impulse, delta) + else: + temp_angle += min(angle_impulse, delta) + VU_METER_update(CONT_CANVAS_vu_meter, temp_angle) + CONT_CANVAS_vu_meter.draw_text(f'{int(cpu_percent)}% CPU USED', (170, 40), color=module_background, font='_ 18') + + + + +if __name__ == '__main__': + + # --------- Images ----------------------------------------------------- + + needle = b'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' + + + vu_meter_2 = b'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' + + dot3 = b'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' + + main() diff --git a/DemoPrograms/Demo_Graph_Elem_Image_Album.py b/DemoPrograms/Demo_Graph_Elem_Image_Album.py index 115f72c5..4a38454f 100644 --- a/DemoPrograms/Demo_Graph_Elem_Image_Album.py +++ b/DemoPrograms/Demo_Graph_Elem_Image_Album.py @@ -44,7 +44,7 @@ def convert_to_bytes(file_or_bytes, resize=None): if resize: new_width, new_height = resize scale = min(new_height/cur_height, new_width/cur_width) - img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS) bio = io.BytesIO() img.save(bio, format="PNG") del img diff --git a/DemoPrograms/Demo_Image_Elem_Image_Viewer_PIL_Based.py b/DemoPrograms/Demo_Image_Elem_Image_Viewer_PIL_Based.py index 715bb351..9585b96c 100644 --- a/DemoPrograms/Demo_Image_Elem_Image_Viewer_PIL_Based.py +++ b/DemoPrograms/Demo_Image_Elem_Image_Viewer_PIL_Based.py @@ -45,7 +45,7 @@ def convert_to_bytes(file_or_bytes, resize=None): if resize: new_width, new_height = resize scale = min(new_height/cur_height, new_width/cur_width) - img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS) with io.BytesIO() as bio: img.save(bio, format="PNG") del img diff --git a/DemoPrograms/Demo_Image_Resize_and_Base64_Encode.pyw b/DemoPrograms/Demo_Image_Resize_and_Base64_Encode.pyw index 12e880be..f1a00789 100644 --- a/DemoPrograms/Demo_Image_Resize_and_Base64_Encode.pyw +++ b/DemoPrograms/Demo_Image_Resize_and_Base64_Encode.pyw @@ -55,7 +55,7 @@ def resize(input_file, size, output_file=None, encode_format='PNG'): new_width, new_height = size if new_width != width or new_height != height: # if the requested size is different than original size scale = min(new_height / height, new_width / width) - resized_image = image.resize((int(width * scale), int(height * scale)), Image.ANTIALIAS) + resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS) else: resized_image = image diff --git a/DemoPrograms/Demo_Image_Viewer_Thumbnails.py b/DemoPrograms/Demo_Image_Viewer_Thumbnails.py index c766fed2..d7f77498 100644 --- a/DemoPrograms/Demo_Image_Viewer_Thumbnails.py +++ b/DemoPrograms/Demo_Image_Viewer_Thumbnails.py @@ -56,7 +56,7 @@ def convert_to_bytes(file_or_bytes, resize=None, fill=False): if resize: new_width, new_height = resize scale = min(new_height / cur_height, new_width / cur_width) - img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.LANCZOS) if fill: img = make_square(img, THUMBNAIL_SIZE[0]) with io.BytesIO() as bio: diff --git a/DemoPrograms/Demo_Invisible_Elements_Pinning.py b/DemoPrograms/Demo_Invisible_Elements_Pinning.py index fe9b9ce9..6216f975 100644 --- a/DemoPrograms/Demo_Invisible_Elements_Pinning.py +++ b/DemoPrograms/Demo_Invisible_Elements_Pinning.py @@ -22,9 +22,9 @@ import PySimpleGUI as sg Copyright 2020, 2022 PySimpleGUI.org """ -layout = [ [sg.Text('Hide Button or Input. Button3 hides Input. Buttons 1 & 2 hide Button 2')], +layout = [ [sg.Text('Hide Button or Multiline. Buttons 1 & 2 hide Button 2')], [sg.pin(sg.Multiline(size=(60, 10), key='-MLINE-'))], - [sg.pin(sg.Button('Button1')), sg.pin(sg.Button('Button2'), shrink=False), sg.B('Button3'), sg.B('Toggle Multiline')], + [sg.pin(sg.Button('Button1')), sg.pin(sg.Button('Button2'), shrink=False), sg.B('Toggle Multiline')], ] window = sg.Window('Visible / Invisible Element Demo', layout) @@ -39,9 +39,6 @@ while True: # Event Loop if event in ('Button1', 'Button2'): window['Button2'].update(visible=toggle) toggle = not toggle - if event == 'Button3': - window['-IN-'].update(visible=toggle_in) - toggle_in = not toggle_in elif event == 'Toggle Multiline': window['-MLINE-'].update(visible=not window['-MLINE-'].visible) window.close() diff --git a/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py b/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py index f42f26ee..09097450 100644 --- a/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py +++ b/DemoPrograms/Demo_Matplotlib_Grid_of_Graphs_Using_PIL.py @@ -890,7 +890,7 @@ def convert_to_bytes(file_or_bytes, resize=None): if resize: new_width, new_height = resize scale = min(new_height/cur_height, new_width/cur_width) - img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width*scale), int(cur_height*scale)), PIL.Image.LANCZOS) with io.BytesIO() as bio: img.save(bio, format="PNG") del img diff --git a/DemoPrograms/Demo_Nice_Buttons.py b/DemoPrograms/Demo_Nice_Buttons.py index a1cefe13..9e782d11 100644 --- a/DemoPrograms/Demo_Nice_Buttons.py +++ b/DemoPrograms/Demo_Nice_Buttons.py @@ -15,7 +15,7 @@ button64 = 'iVBORw0KGgoAAAANSUhEUgAAAoAAAAFACAMAAAAbEz04AAAABGdBTUEAALGPC/xhBQAA def image_file_to_bytes(image64, size): image_file = io.BytesIO(base64.b64decode(image64)) img = Image.open(image_file) - img.thumbnail(size, Image.ANTIALIAS) + img.thumbnail(size, Image.LANCZOS) bio = io.BytesIO() img.save(bio, format='PNG') imgbytes = bio.getvalue() diff --git a/DemoPrograms/Demo_PIL_Use.py b/DemoPrograms/Demo_PIL_Use.py index ca7a37a4..0854e242 100644 --- a/DemoPrograms/Demo_PIL_Use.py +++ b/DemoPrograms/Demo_PIL_Use.py @@ -60,7 +60,7 @@ def convert_to_bytes(source, size=(None, None), subsample=None, zoom=None, fill= elif zoom is not None: scale = zoom - resized_image = image.resize((int(width * scale), int(height * scale)), Image.ANTIALIAS) if scale is not None else image + resized_image = image.resize((int(width * scale), int(height * scale)), Image.LANCZOS) if scale is not None else image if fill and scale is not None: resized_image = make_square(resized_image) # encode a PNG formatted version of image into BASE64 diff --git a/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py b/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py index 36e1d8c6..9b400da0 100644 --- a/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py +++ b/DemoPrograms/Demo_PNG_Thumbnail_Viewer.py @@ -59,7 +59,7 @@ def convert_to_bytes(file_or_bytes, resize=None): if resize: new_width, new_height = resize scale = min(new_height / cur_height, new_width / cur_width) - img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.ANTIALIAS) + img = img.resize((int(cur_width * scale), int(cur_height * scale)), PIL.Image.LANCZOS) with io.BytesIO() as bio: img.save(bio, format="PNG") del img @@ -69,7 +69,7 @@ def convert_to_bytes(file_or_bytes, resize=None): # def image_file_to_bytes(filename, size): # try: # image = Image.open(filename) -# image.thumbnail(size, Image.ANTIALIAS) +# image.thumbnail(size, Image.LANCZOS) # bio = io.BytesIO() # a binary memory resident stream # image.save(bio, format='PNG') # save image as png to it # imgbytes = bio.getvalue() @@ -80,7 +80,7 @@ def convert_to_bytes(file_or_bytes, resize=None): def set_image_to_blank(key): img = PIL.Image.new('RGB', (100, 100), (255, 255, 255)) - img.thumbnail((1, 1), PIL.Image.ANTIALIAS) + img.thumbnail((1, 1), PIL.Image.LANCZOS) bio = io.BytesIO() img.save(bio, format='PNG') imgbytes = bio.getvalue() diff --git a/DemoPrograms/Demo_Sudoku.py b/DemoPrograms/Demo_Sudoku.py index 7e6ba67b..041faf96 100644 --- a/DemoPrograms/Demo_Sudoku.py +++ b/DemoPrograms/Demo_Sudoku.py @@ -2,18 +2,17 @@ import PySimpleGUI as sg, random import numpy as np from typing import List, Any, Union, Tuple, Dict - """ Sudoku Puzzle Demo - + How to easily generate a GUI for a Sudoku puzzle. The Window definition and creation is a single line of code. - + Code to generate a playable puzzle was supplied from: https://github.com/MorvanZhou/sudoku - + Copyright 2020 PySimpleGUI.com - + """ @@ -27,7 +26,7 @@ def generate_sudoku(mask_rate): """ while True: n = 9 - solution = np.zeros((n, n), np.int) + solution = np.zeros((n, n), np.int_) rg = np.arange(1, n + 1) solution[0, :] = np.random.choice(rg, n, replace=False) try: @@ -36,10 +35,11 @@ def generate_sudoku(mask_rate): col_rest = np.setdiff1d(rg, solution[:r, c]) row_rest = np.setdiff1d(rg, solution[r, :c]) avb1 = np.intersect1d(col_rest, row_rest) - sub_r, sub_c = r//3, c//3 - avb2 = np.setdiff1d(np.arange(0, n+1), solution[sub_r*3:(sub_r+1)*3, sub_c*3:(sub_c+1)*3].ravel()) + sub_r, sub_c = r // 3, c // 3 + avb2 = np.setdiff1d(np.arange(0, n + 1), solution[sub_r * 3:(sub_r + 1) * 3, sub_c * 3:(sub_c + 1) * 3].ravel()) avb = np.intersect1d(avb1, avb2) - solution[r, c] = np.random.choice(avb, size=1) + # solution[r, c] = np.random.choice(avb, size=1) + solution[r, c] = np.random.choice(avb, size=1)[0] break except ValueError: pass @@ -48,7 +48,6 @@ def generate_sudoku(mask_rate): return puzzle, solution - def check_progress(window, solution): """ Gives you a visual hint on your progress. @@ -65,38 +64,23 @@ def check_progress(window, solution): solved = True for r, row in enumerate(solution): for c, col in enumerate(row): - value = window[r,c].get() + value = window[r, c].get() if value: try: value = int(value) except: value = 0 if value != solution[r][c]: - window[r,c].update(background_color='red') + window[r, c].update(background_color='red') solved = False else: - window[r,c].update(background_color=sg.theme_input_background_color()) + window[r, c].update(background_color=sg.theme_input_background_color()) else: solved = False window[r, c].update(background_color='yellow') return solved -def create_and_show_puzzle(window): - # create and display a puzzle by updating the Input elements - rate = DEFAULT_MASK_RATE - if window['-RATE-'].get(): - try: - rate = float(window['-RATE-'].get()) - except: - pass - puzzle, solution = generate_sudoku(mask_rate=rate) - for r, row in enumerate(puzzle): - for c, col in enumerate(row): - window[r, c].update(puzzle[r][c] if puzzle[r][c] else '', background_color=sg.theme_input_background_color()) - return puzzle, solution - - def main(mask_rate=0.7): """" The Main GUI - It does it all. @@ -105,8 +89,19 @@ def main(mask_rate=0.7): addressing of the individual squares is via a key that's a tuple (0,0) to (8,8) """ - - + def create_and_show_puzzle(): + # create and display a puzzle by updating the Input elements + rate = mask_rate + if window['-RATE-'].get(): + try: + rate = float(window['-RATE-'].get()) + except: + pass + puzzle, solution = generate_sudoku(mask_rate=rate) + for r, row in enumerate(puzzle): + for c, col in enumerate(row): + window[r, c].update(puzzle[r][c] if puzzle[r][c] else '', background_color=sg.theme_input_background_color()) + return puzzle, solution # It's 1 line of code to make a Sudoku board. If you don't like it, then replace it. # Dude (Dudette), it's 1-line of code. If you don't like the board, write a line of code. @@ -114,16 +109,18 @@ def main(mask_rate=0.7): # Get an input element for a position using: window[row, col] # To get a better understanding, take it apart. Spread it out. You'll learn in the process. window = sg.Window('Sudoku', - [[sg.Frame('', [[sg.I(random.randint(1,9), justification='r', size=(3,1),enable_events=True, key=(fr*3+r,fc*3+c)) for c in range(3)] for r in range(3)]) for fc in range(3)] for fr in range(3)] + - [[sg.B('Solve'), sg.B('Check'), sg.B('Hint'), sg.B('New Game'), sg.T('Mask rate (0-1)'), sg.In(str(mask_rate), size=(3,1),key='-RATE-')],], finalize=True) + [[sg.Frame('', [[sg.I(random.randint(1, 9), justification='r', size=(3, 1), key=(fr * 3 + r, fc * 3 + c)) for c in range(3)] for r in range(3)]) for fc in + range(3)] for fr in range(3)] + + [[sg.B('Solve'), sg.B('Check'), sg.B('Hint'), sg.B('New Game')], [sg.T('Mask rate (0-1)'), sg.In(str(mask_rate), size=(3, 1), key='-RATE-')], ], + finalize=True) # create and display a puzzle by updating the Input elements - puzzle, solution = create_and_show_puzzle(window) - check_showing = False - while True: # The Event Loop + puzzle, solution = create_and_show_puzzle() + + while True: # The Event Loop event, values = window.read() - if event == sg.WIN_CLOSED: + if event is None: break if event == 'Solve': @@ -131,7 +128,6 @@ def main(mask_rate=0.7): for c, col in enumerate(row): window[r, c].update(solution[r][c], background_color=sg.theme_input_background_color()) elif event == 'Check': - check_showing = True solved = check_progress(window, solution) if solved: sg.popup('Solved! You have solved the puzzle correctly.') @@ -140,17 +136,14 @@ def main(mask_rate=0.7): try: elem.update(solution[elem.Key[0]][elem.Key[1]], background_color=sg.theme_input_background_color()) except: - pass # Likely because an input element didn't have focus + pass # Likely because an input element didn't have focus elif event == 'New Game': - puzzle, solution = create_and_show_puzzle(window) - elif check_showing: # an input was changed, so clear any background colors from prior hints - check_showing = False - for r, row in enumerate(solution): - for c, col in enumerate(row): - window[r, c].update(background_color=sg.theme_input_background_color()) + puzzle, solution = create_and_show_puzzle() + window.close() -if __name__ == "__main__": - DEFAULT_MASK_RATE = 0.7 # % Of cells to hide - main(DEFAULT_MASK_RATE) + +if __name__ == "__main__": + mask_rate = 0.7 # % Of cells to hide + main(mask_rate) diff --git a/DemoPrograms/Demo_System_Tray_Reminder.py b/DemoPrograms/Demo_System_Tray_Reminder.py index b93d883e..94bba7cb 100644 --- a/DemoPrograms/Demo_System_Tray_Reminder.py +++ b/DemoPrograms/Demo_System_Tray_Reminder.py @@ -29,7 +29,7 @@ def resize_base64_image(image64, size): """ image_file = io.BytesIO(base64.b64decode(image64)) img = Image.open(image_file) - img.thumbnail(size, Image.ANTIALIAS) + img.thumbnail(size, Image.LANCZOS) bio = io.BytesIO() img.save(bio, format='PNG') imgbytes = bio.getvalue() diff --git a/DemoPrograms/Demo_Table_Checkmark.py b/DemoPrograms/Demo_Table_Checkmark.py new file mode 100644 index 00000000..83e7fd77 --- /dev/null +++ b/DemoPrograms/Demo_Table_Checkmark.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +import PySimpleGUI as sg +import random +import string + +""" + Demo Program - Table with checkboxes + + This clever solution was sugged by GitHub user robochopbg. + The beauty of the simplicity is that the checkbox is simply another column in the table. When the checkbox changes + state, then the data in the table is changed and the table is updated in the Table element. + A big thank you again to user robochopbg! + +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⠆⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⡿⠁⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⠟⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⡿⠃⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀ +⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠺⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠈⠻⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠈⠻⣿⣿⣷⣤⡀⠀⠀⣰⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣦⣼⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + + Copyright 2023 PySimpleGUI +""" + +# Characters used for the checked and unchecked checkboxes. Feel free to change +BLANK_BOX = '☐' +CHECKED_BOX = '☑' + +# ------ Some functions to help generate data for the table ------ +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] = [word() for __ in range(num_cols)] + for i in range(1, num_rows): + data[i] = [BLANK_BOX if random.randint(0,2) % 2 else CHECKED_BOX] + [word(), *[number() for i in range(num_cols - 1)]] + return data + +# ------ Make the Table Data ------ +data = make_table(num_rows=15, num_cols=6) +headings = [str(data[0][x])+' ..' for x in range(len(data[0]))] +headings[0] = 'Checkbox' +# The selected rows is stored in a set +selected = {i for i, row in enumerate(data[1:][:]) if row[0] == CHECKED_BOX} + +# ------ Window Layout ------ +layout = [[sg.Table(values=data[1:][:], headings=headings, max_col_width=25, auto_size_columns=False, col_widths=[10, 10, 20, 20 ,30, 5], + display_row_numbers=True, justification='center', num_rows=20, key='-TABLE-', selected_row_colors='red on yellow', + expand_x=False, expand_y=True, vertical_scroll_only=False, enable_click_events=True, font='_ 14'), + sg.Sizegrip()]] + +# ------ Create Window ------ +window = sg.Window('Table with Checkbox', layout, resizable=True, finalize=True) + +# Highlight the rows (select) that have checkboxes checked +window['-TABLE-'].update(values=data[1:][:], select_rows=list(selected)) + +# ------ Event Loop ------ +while True: + event, values = window.read() + if event == sg.WIN_CLOSED: + break + elif event[0] == '-TABLE-' and event[2][0] not in (None, -1): # if clicked a data row rather than header or outside table + row = event[2][0]+1 + if data[row][0] == CHECKED_BOX: # Going from Checked to Unchecked + selected.remove(row-1) + data[row][0] = BLANK_BOX + else: # Going from Unchecked to Checked + selected.add(row-1) + data[row ][0] = CHECKED_BOX + window['-TABLE-'].update(values=data[1:][:], select_rows=list(selected)) # Update the table and the selected rows + +window.close() + diff --git a/DemoPrograms/Demo_Theme_Dark_Custom_Elements_Check_Toggle_Buttons.py b/DemoPrograms/Demo_Theme_Dark_Custom_Elements_Check_Toggle_Buttons.py new file mode 100644 index 00000000..9292e17e --- /dev/null +++ b/DemoPrograms/Demo_Theme_Dark_Custom_Elements_Check_Toggle_Buttons.py @@ -0,0 +1,96 @@ + +import PySimpleGUI as sg + + +DarkGreyFig = {'BACKGROUND': '#232429', + 'TEXT': '#828692', + 'INPUT': '#333742', + 'TEXT_INPUT': '#f7fbff', + 'SCROLL': '#505F69', + 'BUTTON': ('#fafdff', '#1d5ffe'), + 'PROGRESS': ('#505F69', '#32414B'), + 'BORDER': 1, 'SLIDER_DEPTH': 0, 'PROGRESS_DEPTH': 0, + } + +# Add your dictionary to the PySimpleGUI themes +sg.theme_add_new('DarkGreyFig', DarkGreyFig) + +# Switch your theme to use the newly added one +sg.theme('Dark GreyFig') + + +def Check(state=None, key=None): + return sg.Image(state, key=key, metadata=state, enable_events=True) + +def Toggle(state=None, key=None): + return sg.Image(state, key=key, metadata=state, enable_events=True) + +def DarkButton(state=None, key=None): + return sg.Image(state, key=key, metadata=state, enable_events=True) + +def main(): + gray_bg= '#333742' + + col_cb_layout = [ [Check(cb_blank, ('-CB-', 0)), sg.Text('Label')], + [Check(cb_check, ('-CB-', 1)), sg.Text('Label')], + [Check(cb_minus, ('-CB-', 2)), sg.Text('Label')], + [sg.Text(s=(1,2))], + [Toggle(toggle_light, ('-TOGGLE-', 0)), sg.Text('Light')], + [Toggle(toggle_dark, ('-TOGGLE-', 1)), sg.Text('Dark')], + ] + + col_left_layout = [ [sg.Text('Label')], + [sg.Input(key='-IN-', border_width=0, s=30)], + [sg.Frame('Tags', [[sg.Image(button_green_keyword, background_color=gray_bg), sg.Image(button_orange_keyword,background_color=gray_bg)]], background_color=gray_bg, border_width=0)], + [sg.Frame('', [[DarkButton(button_dark, key=('-DARK BUTTON-', 0)), DarkButton(button_darker, key=('-DARK BUTTON-', 1)), DarkButton(button_darker, key=('-DARK BUTTON-', 2))]])], + [sg.Image(submit_button) ] ] + + + layout = [[sg.Column(col_left_layout), sg.Col(col_cb_layout)]] + window = sg.Window('Dark Custom Mockup', layout, font='_ 16', border_depth=0, element_padding=(10,10),use_custom_titlebar=True, titlebar_background_color=sg.theme_input_background_color()) + + while True: # Event Loop + event, values = window.read() + print(event, values) + if event == sg.WIN_CLOSED or event == 'Exit': + break + + if event[0].startswith('-CB-'): + if window[event].metadata == cb_blank: + window[event].update(cb_check) + window[event].metadata = cb_check + elif window[event].metadata == cb_check: + window[event].update(cb_minus) + window[event].metadata = cb_minus + elif window[event].metadata == cb_minus: + window[event].update(cb_blank) + window[event].metadata = cb_blank + elif event[0].startswith('-TOGGLE-'): + if window[event].metadata == toggle_dark: + window[event].update(toggle_light) + window[event].metadata = toggle_light + elif window[event].metadata == toggle_light: + window[event].update(toggle_dark) + window[event].metadata = toggle_dark + elif event[0].startswith('-DARK BUTTON-'): + [window[('-DARK BUTTON-', i)].update(button_darker) for i in range(3)] + window[event].update(button_dark) + + window.close() + +if __name__ == '__main__': + button_green_keyword = b'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' + button_orange_keyword = b'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' + + cb_blank = b'iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAD+UlEQVR4nM2Y328bRRDHP7O354tTXYpLfsG/UMkIUNWWIiHEG38w4oEn0qoIgdqo/RPSR5vgNBfbt7vDw96eL46bGBqsfKWVvXt3u9+ZnZ2ZHeEDePToe83zDGt7FEWOtT1UPdb2gACYK78BMMYAoKqEEGJTRwgBVaWqKqbTKa//fC6r1r00+HD4WPf3DimKgiyzOFfjfQCUEBTnaozJAG0+Xf4F7z0qICKICAbBGFn0jaHX6+Gc4/TvMb+9+EVWEhp++UT39w+xWY/pdEZVVYQQEImvJGlvgmQGVV30NZGL/SzLEBF6vR5lWeK95927d7w5fiEANn24v39InueMxn/hfVSviKBoS8z28htJJQG6pACUON/c1YgI0/mManrB7u4uh59/xpvj+J4BePrtD7q9vc1oNKKu65ZMUnGSynt/o4ZU9QqZNO69J89zVJWiKHDOMR6PyfOcZ9/9qAD24fCxPniwy+npKSGE1ihBm8m70sfx/4IkoHOOLMuo6xprLc7Nef9+wmBwP2rIWouq4pxbKdn/BRFptelcPIXDr5+qLYoiDtYBDYJkNxvux6GxTV2sU9c13nu2t7cxRVEQQsB73xik+fBct0mr2Q0Radcven1snucEDyEo1mYE9CNJLWv48lwioCqoCiIGY6RxKWCtxWRmcZRFNqed5B66YyaeatMSuh2jNkttmQyANMJLVGgAUUHIMMnab4/QeuhGgO66JvkdEZCV4e62iUD0cTFGGmvAgNfoNDdjNGtCJdwtQrApp7MGkh3dGUIJd4ZQOnV3hlDC3SOU0g9jzEYcY3LEXceYciVVxayTJ98mluNYN1KISCTUHdwksUSiC5PyoE0RWhXpu8+M9x5jTLuPm8Klq1Kztvce45xrCW0aXQM3xlDXdSS0Se0sn7L0P8synHObP2XL6Bq39x5zcX7OvX4fo3IlhU3XlH9j8KoeCIho2yCg6lH1bXaa7oCqSp7nAFRVhXn16kicc5RlyWw2g7DY09TSzXUdYinhaysfDYEsy7DWkuc51tr2thFCaJ+9/uNILMBkcsrOzicU57Eq4ZtLY7KtbrtZQ0LMmbtaBu+VmCn6lnhSRFmWjMfjOA4wGo24uLjg08GgZZ8MLfW70l9PaGETaY5V2oZ47dnZ2QHg5dHPi+rH27e/i7Xf6N7eHgcHB1RVRVVVsfDgA9JsQV27NUg1FRMNl0OCWWg4z+LWDQYD5vM5Jycnna87GA6f6L3yPmVZsrW1hXOO2Wy2KM00VbHrEMJlDS1afG6Mod/v45xjMpnw/NefVheslvHFV8+0KAr6/T5FUQCxOra1tXUtIaMLDSYBnHN47/He49ycs7Mzjo9frlz7HzLiWEBTYnmBAAAAAElFTkSuQmCC' + + cb_check = b'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' + cb_minus = b'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' + toggle_light = b'iVBORw0KGgoAAAANSUhEUgAAADwAAAAmCAYAAACYsfiPAAAN0ElEQVR4nK2Ze6xnV1XHP2vtfc75/X73OfdeOp1Hp+0wZYZabAtSlaItVviDoKKGiBo1KVAxMcSYGE0MRE3kPx9ofPAIiSGYiEFDIhoeRRraCkUR2yqlMwPTzvt1Z+7r9zhn7738Y5/f797p0NLp7bo5uff+zvntfb57rf1da3238DLY/C3vNVVFxRFiQ5KESSIJbDz1MQGYOviAqYGYooBYAsAEkmyOZUlwIkBAJN8QNQCW27G2Y9c8wNKdv2Fl7xX0Zq6j11ugKGdQqVArMAHxrgWbSGabE02AgpIQA7ArABsgomAJHS+ISfsswIh6uMJweJn+ynmGa+c48/hfXxOGF/3wrrs+aAu7bqU7t5cgXYIICY+JghVIKkAckYhdNapmoAZCwqvQNCO89wQLoEIdAq7wxNjgvUcFJBkkRVUxM5I1eA1gNSXAaI24cYbV5cN85+HffVFYvu9De+76fVvYfSfl9AEat0AdpwhSEDWHrIjDyKDHlmhdYjqZIHspQUyoA+ccIQREDFwGFC2iLiFiSGwoBArXwcwwi6gXRvUGAIVUSD3ExT69ap26f5QLp77J8Uf++AUxPe/NxdveYwu7Xsfcztdh5R76TZc6VSR1JNGMR3IYbu7BHIbagoU2HMUm9wCcK0jRCCFRlp56OKKsCswaxALONaTRKmG0jjOITY2RcGWBFAWu6OL9PIInNgFLfcpijUovs3bqfzn8ufufF9f3vDF/+3ttz033MLv4GlbrRfpxBiumaEL2TgbVggZMjSQRk/HeVLSN6zHgRAQSTitGoxqhwIlSeYhhgHcNygBrLlDqgH27Fjhw8y4WZ3t0ShCF/sC4sLLOMyfOc/TEJQZDT6e3E3yXEGoq39AJywwvPcWZY1/h4pN/cRU+/9wPdtz+e7b3VfdQTe3nwqCHuXms6tDESNFxpBjyfkyCtKDMhKiS9/N4JU3b3ymTmRhJhCTgyoKuc6Qm4eIGs1VDf/UE8zPr3PuG3dx9120c3A/dEqarHDGxAXFCZIZzKzMcOw1feuQYDz/2NGv9ObzbTRhNM3IF1ULJ3rIAxC4++aErQF/xz+LBB2z3bT9H6rwS3Dw1PRp6mEIwiKmmUEWN7ME2bAGSGCYJyy5FkptMYGKYRJIkRAs8wqjfZ7YyfHMOCWe449YF3vXLB/nBA1AArr1IgCVKVUQgREgORsCGwbe+C//4mdM89PBJ/NReRlJhccB8sUKz+m3++59+9vkBH3rTh6y7+ycY6PUkKQnmaShJCCaCc0qymEO6Dd3sYpnsYyVMAI/Tick43xopJUoHHRoYnWa+Os873nYb7/jpHlMeKgNJIALOtaAxNBliGbQlGMREKpRa4UIfvvDgiI9/6lHW/U2YziPDNebKVdZOfY2nPv/uCc7JHze+/gN2w6G3sKL76ds8CSGhIB5UiCaklCbFANKSkCkmm7lSCTnUzYG1HgZiGwxOwKUNOnGZueIi97/zDn7qzVApdICyXcZoYAbarqmaIbQsCaA5F6xHwCn9CJ/9cuTP/u4xmmIvlkoqhhTNCU4//QVOf/0PZYuLYOnmN3LJdjFI0zRSECkwXAaeQMxwImj7JTXNF/meGiiGioAYUSG0nsWNOdqwNKBgjXJ0gvt//g7e+uMwpzANeALjTeIEvDKZT0TyjfGFIig9p3SAOYG33+N499tfTzU8iXeBkZYM/RyL+26fRLEC3PjGPzKZ2k2t8zRSYThM9AoSeiFTmKQea6sryyVVDmcbv2ik0IbCLnH3XXv4yTfCjgq8Zfb0Jpt5+znXxJ7zgSPv+ZJER+Dtb/bceWiOwcZZmhSprUtv7kZueP37Nzfh3OI+kpXE8KLwfV9LZpnJJacTI6AScIxwtk7pVnnnL+xjfh6ckquoBMncS55TRFCFHTPwq7/0anZ0Q7s9phjGKeZ2HgBAd975PuvM7mbYKM532BLl25p87GlSRDCcRLzUNMOz3Hv3QW7ZNyYkUJXJXt3OnE4gGRzcD296w6tp1s9RaEUTOswu7WfHa95n2pndTSoXCVJh4rFr7yeeO3MGYdoyec7FTsExZKpY574fm8ciVAQ8bD63zakVqAQ6Aj/62hlKu4TTgPgp+vUM3ZkbUd+9nibNYDJF8zKEtJkhkmtjadtBEcGbQLPBzTdMs2sJZhwoDUrCSMQU2dIWvWTABTmBvHIf3HrwOjbWL9BEw4o5ujN70KK3RC0z4LuI82wvpJVIbvEm6QtBLbd8qelzy74lXjELhUFBRYoxpyo1hLgtwGPzwM5FuH6pxLuA954merq9JbTTWyRRUdcgTr9Ha3dtZsmRyG3g5u7IezqGIUs7epQCmoCgOPGkNElabG0yrtXaDIimzPx79yzSH6zl7s11UFehaAnmyNSyTTNB2rytzkAa2vaCQoUUAp2yolCIdfuGE4LTLddLt3EOF4NOWeXFFEfE8GWJVxIky8X9thFnU4MkMcs4rYJhRIyIODYDN+Xc5bR4oeGuzSwDVsl84goPklAF9Q6lHuJSjdf2BbZjYrgsXOWSE4+IkCQRiSDCoB4RBaTIXRaWa+MQEumlR/MELBazzmDQHw4IFrLGZpHhsI82g8sU1DhrEN3ujGlyJRzRNjnBkqBlxTOnzjIKZNBtcyoK3um2c3HmDEcyGAW4cGkddQ4IOBrCaB0N6xcppY9Is+WFtzNnBHKoGoqZYOYwPEU5w5Fj51jpQ9A2tLcIeNv2cDteEFhehae/e5pOp0dqhjhGWLOK1v1lnPXxNAhhu6mQMelMspIJyZSEQ/0sx55d49tHMthADufJN7fpYQPqBObhO8fh6aPLOD8NKVLRp796Gh2tXyQMVlANpG0nf4XkMVPMEklbtQNHTA6zHibX85VHTzCoWz6T0LaaLxNjOliv4aFHzzIKiyjTdIsSjSuMVk6g9fA8sT5PwRBHDeRN/lJMWhVAUnaVWmZHk5yfg5U4fx1ffOhJnj2XFYumVZkE2cQ9vlqzthr7fksSgQb4zkn43L//D72pXVgwfBgwvPwM5574E9Fzhz8iFy48iYYLlDpENJBImFpmVnJhnlJodam0hZpkImdsqpM5nDUqisdie3LgjIRHq1k24jR/+uFHuDCEtQS16SZ9hBZs2ARbp5rU/hjtNmhrlHGT0oTEKMGZVfj4J5/g8qBD0Z1C04CeW6N/6Uh2AsDxR/9AwuAUXe2jVqMSiXVN6TyqEELA+wKztkwka1rjoh+xiYzTFtDt4AYSmbT/okTrUPX28Njj5/nUZ86Bz/rUFd4zwEPdRBqLOPWkLDRlBSW1oBWSCMMIjSoj4NP/uswXHznC4s6bGAz69IqG0foJDj/8HMXj3LNPoM15itCni9ArlDAaYDFRek9qgChIEnTr8Uf7R4LJmVL2fVt4XBGbjiY4nF+iLPbzib9/hH/+tyHDBBsOgqet/vMCusKh4gGhwCNRILXklupMUkDj4FIDn/nSkI/9w0O46T0M64aZnqDxImeOf3PyCldUzjff9xFbuuk+LvdnwHeyiGeKLypC3GTerUWoIa3uTK5oxg5uF2LSbloW9QoPLgZscAEXTlHEY9z/K/fwM29dZKGXdS2L4F2ugwzDi7THNHn9mibgK08/wlBhdQSf/pezfPQTD1L7fbhqiU7h6bgNLj/7VY4++N4Jzit06eWT36A3u5eZ2UMMmkiyHp1qllHKYru5BNa0oPwEhCFtqOim59t8I63OMw75YR2onODKBZoQiSnx53/zeZ54aj+/9os/zKF90Gk1sFweCnVoxT9pw7jyrAG1wuNPw8c++RgPffUwvdmb8Z1X4NXh7TJx9RjLJ76+FeLV3f7cD/yO3XTwPrqz+1lvpojFIkMKhimgXjCafBKYPOAmzHxVl7VVo93ymUjCqZJGgUoTjC6TmjOkcJyOv8jb7ruDt9x7O3t2wVQXZrtZhFeFGLMYurwCR44nPvvFb/CV/zjK2nCKorsTV80CMN2JhMtPc/zbX+YFhfix7bj1N23v/rvpLrya9bBArXOMtMCczzxprgW7SQK5fLbM3LSx99wpJJJshCqUrkOqAy4ESAOGwzOUboN67QzO+hw6cBMH9t/A/EzF3PQ0IkK/rnnm5BmOnTzL0e+eJ7kpinKOmDydaoqqMqaKdUYrRzn5rUc5/39/eRW+5+1+F171Ltu570eYuf4OhrrIkC6NdIlSYPh8WrglHUl7TJqlXSbFRD5jUiCRxChcpAk1KgVOPFZn+Sc26zTNGjBEbUgardMMNtDUgEVCjJjzFN1pTEuKchrVEo/R65Z0ioCLF7l46j85+uBvXdth2lbb+0MfsKUbXks1fzPDNMsodYjaIYrH0qZYl/VoJZrL1ZWlCYllvVZJktAttbqItGdQCVLEGNHYENIA0gBnCWftoTgeccoo1HhXAIpLgdkSOn7IxuUjXDz7OCe+9sGXdly61ZYO/bpNLRxgamE/3bkbCK6HaZFBm7T81DbvMtYiM+BNEtvkRyOiBqab00vK/XISwxghcQTWPtdGiamhBLx3VA4kDOkvn2D5zGFO/df7XxSWaxZ0dt7x21b0dtCdvY7e9CJlZwanJeCzHk24grD0qoJQJgKfjc+NJbUHc+CkgGS5lLQGI2AWQQKiiWF/lbq/Sn/9IqF/mRPf+KtrwrBNBSvb1IEHDHOIGuuH/1YAZm55j2E6OYNaO/xRAZi/5QEzM1aOfPTqDHHgPabtuUxOY8bKkQ+/LO84tv8HptzX6iYZ7DwAAAAASUVORK5CYII=' + toggle_dark = b'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' + submit_button = b'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' + + button_dark = b'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' + + button_darker = b'iVBORw0KGgoAAAANSUhEUgAAAGsAAAA2CAMAAAD3cZcXAAACuFBMVEUfIiscHSEkJy4gIyohJCsjKC4kJjIeISghJC0jJi0gIywjJi9FSVRKTllMUFt8f4tydIBzdoJrbnl6fIh4e4Z9gIuDhpB8f4pucXwiJjF7fop/go2ChY97fodsb3p4eoV6fYh/gYyChY58fYdRVWBWWWROUlxsb3mFiJFVVmBcXWdUVWBSUl1aW2ROT1gdISp8foloa3R7fYhpbHVucXt1d4NWWWNdYGp7fYlgYWokKDF8f4l3eoR3eoVfYm1sbnlhY207PURSVF1tbngiJi9ZXGZhY24+P0YmJy0nKC4lJyxAQUdHSlVucXpiY246O0IoKS8pKzEyMzlMTVYvMDZJS1R7foh1eYI/QUc9PkVdYGtvcXs8PkMkJSpoaXN6fIZBQkl3eIJ9f4oeHyMfICQpKi9maHJtcHl2eINLS1MrLDI2Nz2Fh5EkJzBPUVc9PkNGR05BQ0pmaXNucHo7PUNNTVVoaXRISVBnaHOHiJOChI55e4VAQUhnanQ+QEc6PEJRUlqGh5J+f4lzdX8xMjgsLjQhIic8PURGR09MTlVnaHJTVmBYW2VYWmZXWWRZWmNfX2lHSlIiJS6Ago19foiFh5I6O0FkZ3IjJCl1d4JIS1NdYGmDho96e4UnJy81Nz0sLTJvcnwnKS55e4Z7fol9f4liZ21+gYyBhI5sbHZvcX1tcXttcXpwdH5pbHd4eYMwMjdlaHRtcHssLjNJTVdWWGNNUFhqb3iHiZNeYGx6fIdvcn1zdYFhZG50eIIcIyuBhI9maXGDhY6EhY+DhI+EhpJKTFh5e4d5fIZ2eoGAg4xLT1pTV2B/goxzd4B0doFOUV1VWGOHiZSGiZNdYmpfZm5/g41kZ3EhIy8gJSseIykfIywjJzBMT1ofJCpKTVdOUVtISlQdJCwlKC8fIikkJCwdICkAAACsCdXOAAAA6HRSTlP///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8Asi2gugAAAwJJREFUeJzdmAdTE1EQx7OJSonlrxRRo2KvWFAsIAqWE1tERQRRQcWKWLGhYhcVFAWxgdjF3rBi7xV7x240X8P37iCEGaIzN8kbx525293bffebN2/fldWYi0VDjhGtzkJQVDkHgYpEZ8VyLIlJeQvL4SgmRSwRKA5jrApiWE6cJQbFJqYxOwtkiUKRk0CWi0AWaVz/U5awMmQsvUCWONQ/w6poK1DJ9pjK6lhVULXsQDW42RrjDg9VLE9ULzvgBdgYUqMmaqliGVC77EAd1LU1xhv1VLHqo0Epv2GjxorRBE2JmjVvURJq6dNK1q3b2IXV1heAdztutmcsP+b5dVBCHTsxx7czs/wD7MHqAkW8ZFZgoOJ15aFuCAruHhyEHkQ97TGvXpB6h/Tp209Cf6IBgHFg6KDBRmAIkQ+CwlhGWBCGknO4PeY1LEJWkRhOFAWM4M5II0YRRSNSDsVgNNEYlSxDqfUay47YcRLGE02AMVa+FoGJNAmYLDtxmELxvnZhTZ02fYbElmgm0ayimVAC22izgTlzuczDfHJVu16J1qwFvBSkhRIWcVaScnExYy2BRZaSXm0dJmKZxV7OamPFylWUzFlRxfOKAFavAdampK5bn5a2YSOlByBDFcuATRY7E5tlnYwtvOaNW2UvCdtoO5BleQf6q31uZGNHTglrJ1e7JM7aDcRwb48RWUR7sU9O2Z95gCgcuapYB5Fx6HDUkaPHjhOdgHQyNCSBFccpotNsfyXl5cWx/XWGKAVwO0vn2GY/T6S2Ni5YFv0iUb5suAMMfAm4rARSed6VoqyrRNc8cF0V68bNW7fvGLKzPXPvMi86H/fu04OHj4geFzyhp/zuz5TE576ZL14WvOLm6zdvVbFIp9O/S3//ofAjd7T0iavP/FTIji9fv5U15vsf7vevfG/YnfVDIEsrkBUvkCXyG1vkv4PAQhT4X6kVyNJpzDl/z7KPCO6lmAXtZpO43pde6bOZRLDE9SrJqgf707Ekk3W/12z+5eqgp7CLqRjxG7LsF9Z68qMOAAAAAElFTkSuQmCC' + main() diff --git a/DemoPrograms/Demo_Time_Chooser.py b/DemoPrograms/Demo_Time_Chooser.py new file mode 100644 index 00000000..89e20a97 --- /dev/null +++ b/DemoPrograms/Demo_Time_Chooser.py @@ -0,0 +1,79 @@ +import PySimpleGUI as sg + +""" + Demo Time Chooser + + A sample window for choosing a time. + + This particular implementation uses a Spin element. Numerous possibilities exist for entering a time of day. Instead + of Spin elements, Input or Combo Elements could be used. + + If you do not want your user to be able to manually enter values using the keyboard, then set readonly=True in + the Spin elements. + + Copyright 2023 PySimpleGUI +""" + + + +def popup_get_time(title='Time Entry', starting_hour=1, starting_minute=0, allow_manual_input=True, font=None): + """ + Shows a window that will gather a time of day. + + :param title: The title that is shown on the window + :type title: str + :param starting_hour: Value to initially show in the hour field + :type starting_hour: int + :param starting_minute: Value to initially show in the minute field + :type starting_minute: int + :param allow_manual_input: If True, then the Spin elements can be manually edited + :type allow_manual_input: bool + :param font: Font to use for the window + :type font: str | tuple + :return: Tuple with format: (hour, minute, am-pm string) + :type: (int, int, str) + """ + + max_value_dict = {'-HOUR-':(1, 12), '-MIN-':(0, 59)} + hour_list = [i for i in range(0, 15)] + minute_list = [i for i in range(-1, 62)] + + layout = [[sg.Spin(hour_list, initial_value=starting_hour, key='-HOUR-', s=3, enable_events=True, readonly=not allow_manual_input), + sg.Text(':'), + sg.Spin(minute_list, initial_value=starting_minute, key='-MIN-', s=3, enable_events=True, readonly=not allow_manual_input), + sg.Combo(['AM', 'PM'], 'AM', readonly=True, key='-AMPM-')], + [sg.Button('Ok'), sg.Button('Cancel')]] + + window = sg.Window(title, layout, font=font) + + while True: + event, values = window.read() + # print(event, values) + if event == sg.WIN_CLOSED or event == 'Cancel': + hours = minutes = ampm = None + break + + if event == '-HOUR-' or event == '-MIN-': + spin_value = values[event] + if spin_value > max_value_dict[event][1]: + values[event] = max_value_dict[event][0] + window[event].update(values[event]) + elif spin_value < max_value_dict[event][0]: + values[event] = max_value_dict[event][1] + window[event].update(values[event]) + if event == 'Ok': + # Do validation on the input values to ensure they're valid + try: + hours = int(values["-HOUR-"]) + minutes = int(values["-MIN-"]) + ampm = values["-AMPM-"] + except: + continue # if not valid, then don't allow exiting the window using OK. + if 1 <= hours <= 12 and 0 <= minutes < 60: # make sure the hour and minute values are in a valid range + break + + window.close() + + return hours, minutes, ampm + +print(popup_get_time(font='_ 15')) \ No newline at end of file diff --git a/DemoPrograms/Demo_User_Settings_Auto_Load_and_Save.py b/DemoPrograms/Demo_User_Settings_Auto_Load_and_Save.py new file mode 100644 index 00000000..4a16fbd6 --- /dev/null +++ b/DemoPrograms/Demo_User_Settings_Auto_Load_and_Save.py @@ -0,0 +1,55 @@ +import PySimpleGUI as sg + +""" + Demo - User Setting API to automatically save and load Input Elements + + This Demo Program shows an easy way to add saving and loading of Input elements. + + The variable keys_to_save is used to determine which elements will be saved to the user settings file. + + The function make_key returns a dictionary that's used as keyword parameters that are passed to the Input elements. Using this technique allows the Input elements in the layout to benefit from the docstrings provided by PySimpleGUI. Another approach could be to use a function that returns an Input element, but that limits the flexibility for configuring Input elements. + + Copyright 2023 PySimpleGUI +""" + +keys_to_save = ('-IN1-', '-IN2-', '-IN3-', '-IN4-') + +def make_key(key): + """ + Returns a dictionary that is used to pass parameters to an Input element. + Another approach could be to return an Input element. The downside to that approach is + the lack of parameters and associated docstrings when creating the layout. + + :param key: + :return: Dict + """ + return {'default_text':sg.user_settings_get_entry(key, ''), 'key':key} + + +def main(): + layout = [ [sg.Text('Automatically Load and Save Of Inputs', font='_ 15')], + [sg.Text('Input 1'), sg.Input(**make_key('-IN1-'))], + [sg.Text('Input 2'), sg.Input(**make_key('-IN2-'), background_color='green')], + [sg.Text('Input 3'), sg.Input(**make_key('-IN3-'), text_color='blue')], + [sg.Text('Input 4'), sg.Input(**make_key('-IN4-'), size=5)], + [sg.Button('Exit (and save)', key='-EXIT SAVE-'), sg.Button('Exit without save')] ] + + window = sg.Window('Save / Load Inputs Using User Settings API', layout) + + while True: # Event Loop + event, values = window.read() + print(event, values) + if event == sg.WIN_CLOSED or event == 'Exit without save': + sg.popup_quick_message('Exiting without save', text_color='white', background_color='red', font='_ 20') + + break + elif event == '-EXIT SAVE-': + sg.popup_quick_message('Saving settings & Exiting', text_color='white', background_color='red', font='_ 20') + for key in keys_to_save: + sg.user_settings_set_entry(key, values[key]) + break + + window.close() + +if __name__ == '__main__': + main() diff --git a/DemoPrograms/Demo_Watermark_Window.py b/DemoPrograms/Demo_Watermark_Window.py new file mode 100644 index 00000000..49b61713 --- /dev/null +++ b/DemoPrograms/Demo_Watermark_Window.py @@ -0,0 +1,109 @@ +import PySimpleGUI as sg + +""" + Demo - Watermarking all windows + + Watermarking windows can be done in 4.60.0.160 and greater. It's a very simple mechanism for now. + + The option is normally set in the Global Settings control panel. However, you can "Force" the watermark + on all windows by setting the Window paramter watermark=True on any window you create and from then on + all windows will have the watermark. + + Copyright 2023 PySimpleGUI +""" + +""" +M"""""""`YM +M mmmm. M +M MMMMM M .d8888b. +M MMMMM M 88' `88 +M MMMMM M 88. .88 +M MMMMM M `88888P' +MMMMMMMMMMM + +M""MMM""MMM""M dP dP +M MMM MMM M 88 88 +M MMP MMP M .d8888b. d8888P .d8888b. 88d888b. 88d8b.d8b. .d8888b. 88d888b. 88 .dP +M MM' MM' .M 88' `88 88 88ooood8 88' `88 88'`88'`88 88' `88 88' `88 88888" +M `' . '' .MM 88. .88 88 88. ... 88 88 88 88 88. .88 88 88 `8b. +M .d .dMMM `88888P8 dP `88888P' dP dP dP dP `88888P8 dP dP `YP +MMMMMMMMMMMMMM +""" + +layout = [ [sg.Text('No Watermark')], + [sg.Button('Exit')] ] + +window = sg.Window('No Watermark', layout) + +while True: + event, values = window.read() + if event == sg.WIN_CLOSED or event == 'Exit': + break + +window.close() + + +""" +MP""""""`MM dP +M mmmmm..M 88 +M. `YM dP dP .d8888b. d8888P .d8888b. 88d8b.d8b. +MMMMMMM. M 88 88 Y8ooooo. 88 88ooood8 88'`88'`88 +M. .MMM' M 88. .88 88 88 88. ... 88 88 88 +Mb. .dM `8888P88 `88888P' dP `88888P' dP dP dP +MMMMMMMMMMM .88 + d8888P +M""MMM""MMM""M dP dP +M MMM MMM M 88 88 +M MMP MMP M .d8888b. d8888P .d8888b. 88d888b. 88d8b.d8b. .d8888b. 88d888b. 88 .dP +M MM' MM' .M 88' `88 88 88ooood8 88' `88 88'`88'`88 88' `88 88' `88 88888" +M `' . '' .MM 88. .88 88 88. ... 88 88 88 88 88. .88 88 88 `8b. +M .d .dMMM `88888P8 dP `88888P' dP dP dP dP `88888P8 dP dP `YP +MMMMMMMMMMMMMM +""" + +sg.set_options(watermark_text='') # noramlly not requird unless previously set by user + +layout = [ [sg.Text('System Provided Watermark')], + [sg.Button('Exit')] ] + +window = sg.Window('System Watermark', layout, watermark=True) + +while True: + event, values = window.read() + if event == sg.WIN_CLOSED or event == 'Exit': + break + +window.close() + + +""" +M""MMMMM""M +M MMMMM M +M MMMMM M .d8888b. .d8888b. 88d888b. +M MMMMM M Y8ooooo. 88ooood8 88' `88 +M `MMM' M 88 88. ... 88 +Mb dM `88888P' `88888P' dP +MMMMMMMMMMM + +M""MMM""MMM""M dP dP +M MMM MMM M 88 88 +M MMP MMP M .d8888b. d8888P .d8888b. 88d888b. 88d8b.d8b. .d8888b. 88d888b. 88 .dP +M MM' MM' .M 88' `88 88 88ooood8 88' `88 88'`88'`88 88' `88 88' `88 88888" +M `' . '' .MM 88. .88 88 88. ... 88 88 88 88 88. .88 88 88 `8b. +M .d .dMMM `88888P8 dP `88888P' dP dP dP dP `88888P8 dP dP `YP +MMMMMMMMMMMMMM +""" + +sg.set_options(watermark_text='User Supplied Version 1.0') + +layout = [ [sg.Text('User Supplied Watermark')], + [sg.Button('Exit')] ] + +window = sg.Window('User Supplied Watermark', layout, watermark=True) + +while True: + event, values = window.read() + if event == sg.WIN_CLOSED or event == 'Exit': + break + +window.close() diff --git a/DemoPrograms/Demo_Window_Open_Multiple_Times.py b/DemoPrograms/Demo_Window_Open_Multiple_Times.py index ffcc6a49..ac4bbca7 100644 --- a/DemoPrograms/Demo_Window_Open_Multiple_Times.py +++ b/DemoPrograms/Demo_Window_Open_Multiple_Times.py @@ -10,10 +10,10 @@ import PySimpleGUI as sg The purpose of this demo is to show you the simple "make window" design pattern. It simply makes a window using a layout that's defined in that function and returns the Window object. It's not a bad - way to encapsulate windows if your applcation is gettinga little larger than the typical small data + way to encapsulate windows if your application is getting a little larger than the typical small data entry window. - Copyright 2020 PySimpleGUI.org + Copyright 2020, 2023 PySimpleGUI.org """ @@ -25,11 +25,12 @@ def make_window(): :return: Window that is created using the layout defined in the function :rtype: Window """ - layout = [[sg.Text('My Window')], - [sg.Input(key='-IN-'), sg.Text(size=(12, 1), key='-OUT-')], - [sg.Button('Go'), sg.Button('Exit')]] + layout = [[sg.Text('The program will only exit using the "Quit Program" button.')], + [sg.Text('Closing the window or using Exit button will cause a new window to be created.')], + [sg.Input(key='-IN-')], + [sg.Button('Does Nothing'), sg.Button('Exit'), sg.Button('Quit Program')]] - return sg.Window('Window Title', layout) + return sg.Window('Window that restarts on exit', layout) def main(): @@ -41,8 +42,10 @@ def main(): if event == sg.WIN_CLOSED or event == 'Exit': window.close() window = make_window() - elif event == 'Go': - window['-OUT-'].update(values['-IN-']) + elif event == 'Quit Program': # The Quit Program button break out of event loop and exits program + break + + window.close() if __name__ == '__main__': diff --git a/PySimpleGUI.py b/PySimpleGUI.py index fa0fbc42..fbbfd08d 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1,343 +1,491 @@ #!/usr/bin/python3 -version = __version__ = "4.60.4.135 Unreleased" +version = __version__ = "4.61.0.206 Unreleased" _change_log = """ Changelog since 4.60.0 released to PyPI on 8-May-2022 - 4.60.0.1 + 4.61.0.1 main_open_github_issue - prefill the "Details" using the platform module (thank you macdeport!) Fills Mac, Windows and Linux with details - 4.60.0.2 + 4.61.0.2 Fix for the "jumping window problem on Linux". Major credit to Chr0nic for his amazing "stick with it" work on this problem! - 4.60.0.3 + 4.61.0.3 Removed the previous fix attempt for jumping window on linux Added ability for Mac users to specify file_type in Browse and popup_get_file This feature must be ENABLED by the user in the Mac control panel that can be found in the PySimpleGUI Global Settings The default is this feature is OFF - 4.60.0.4 + 4.61.0.4 New location parameter option for Windows. Setting location=None tells PySimpleGUI to not set any location when window is created. It's up to the OS to decide. The docstring for Window has been changed, but not all the other places (like popup). Want to make sure this works before making all those changes. - 4.60.0.5 + 4.61.0.5 Added check for None invalid values parm when creating a Listbox element - 4.60.0.6 + 4.61.0.6 Column docstring changed to add reminder to call contents_changed if changing the contents of a scrollable column - 4.60.0.7 + 4.61.0.7 Fixed crash when horizontal_scroll=True for Listbox element - 4.60.0.8 + 4.61.0.8 Added readonly to Input.update - 4.60.0.9 + 4.61.0.9 Added Window.set_resizable - can change the X and Y axis resizing after window is created - 4.60.0.10 + 4.61.0.10 Added wrap parameter to Spin element - if True, wraps back to the first value when at the end Temp test code added for a new verification feature - 4.60.0.11 + 4.61.0.11 Fixed Spin Element docstring - readonly was not correct - 4.60.0.12 + 4.61.0.12 Output element - addition of wrap_lines and horizontal_scroll parameters Multiline element - addition of wrap_lines parameter - 4.60.0.13 + 4.61.0.13 Added Window.unbind - 4.60.0.14 + 4.61.0.14 Added (None, None) to the Window docstring - 4.60.0.15 + 4.61.0.15 Fix for continuous Graph element mouse up events when reading with a timeout=0. Big thank you to @davesmivers (THANKS DAVE!!) for finding and fixing - 4.60.0.16 + 4.61.0.16 Added platform (Windows, Mac, Linux) and platform version information to the get_versions function - 4.60.0.17 + 4.61.0.17 Added a fix for the file_types Mac problem that doesn't require the system settings to be used... let's give it a go! - 4.60.0.18 + 4.61.0.18 Added ubiquitious Edit Me to the right click menu - 4.60.0.19 + 4.61.0.19 PySimpleGUI Anniversary sale on Udemy course coupon - 4.60.0.20 + 4.61.0.20 Fix for bind_return_key - if a button has been disabled, then the event shouldn't be generated for the return key being pressed - 4.60.0.21 + 4.61.0.21 Added cols_justification for Table element - list or tuple of strings that indicates how each column should be justified - 4.60.0.22 + 4.61.0.22 Better error handling for table element's new justification list. If a bad value is found, will use the default value - 4.60.0.23 + 4.61.0.23 Additional mac filetype testing.... added more combinations that specify - 4.60.0.24 + 4.61.0.24 Added * *.* to the Mac filetypes to check for - 4.60.0.25 + 4.61.0.25 New logic for checking for the * case for Mac filetypes - 4.60.0.26 + 4.61.0.26 Docstring update - TabGroup visible parameter marked as deprecated . Use a Column element to make a TabGroup invisible - 4.60.0.27 + 4.61.0.27 Docstring update for the pin helper function that describes the shrinking of the container that it helps provide. Also added explanation that it's the elements you want to make visible/invisible that are what you want to pin - 4.60.0.28 + 4.61.0.28 Applied same Mac file_types fix to popup_get_file Removed filetypes setting from Mac Feature Control Panel - 4.60.0.29 + 4.61.0.29 Addition of enable_window_config_events to the Window object. This will cause a EVENT_WIMDOW_CONFIG event to be returned if the window is moved or resized. - 4.60.0.30 + 4.61.0.30 Made upgrade from GitHub window resizable so can screencapture the entire session - 4.60.0.31 + 4.61.0.31 Added new constant TKINTER_CURSORS which contains a list of the standard tkinter cursor names - 4.60.0.32 + 4.61.0.32 Added erase_all parameter to cprint (like the Debug Print already has) - 4.60.0.33 + 4.61.0.33 Fix popup_scrolled - was only adding the Sizegrip when there was no titlebar. It should be added to all windows unless the no_sizegrip parameter is set. popup_scrolled - added no_buttons option. If True then there will not be a row at the bottom where the buttons normally are. User will have to close the window with the "X" - 4.60.0.34 + 4.61.0.34 popup_scrolled - added button_justification parameter. Wanted to make scrolled popups consistent with other popups which have left justified buttons. But since they've been right justified in the past, want to give users the ability to retain that look. Since the Sizegrip works correctly now, it increases the changes of accidently clicking a button if it's right justified. - 4.60.0.35 + 4.61.0.35 Added default_color to ColorChooser button - 4.60.0.36 + 4.61.0.36 Added to Button element error message that images must be in PNG or GIF format - 4.60.0.37 + 4.61.0.37 Added exapnd_x and expand_y to all of the "lazy buttons" and Chooser buttons - 4.60.0.38 + 4.61.0.38 Column element - added horizontal_scroll_only parameter (fingers crossed on this one....) - 4.60.0.39 + 4.61.0.39 New signature testing - 4.60.0.40 + 4.61.0.40 Exposed the Table Element's ttk style using member variable TABLE.table_ttk_style_name - 4.60.0.41 + 4.61.0.41 New signature format - 4.60.0.42 - Backed out the changes from 4.60.0.38 (horizontal_scroll_only parameter). Those changes broke code in the scrollable columns. Need to make time to work on this feature more. - 4.60.0.43 + 4.61.0.42 + Backed out the changes from 4.61.0.38 (horizontal_scroll_only parameter). Those changes broke code in the scrollable columns. Need to make time to work on this feature more. + 4.61.0.43 Added a print if get an exception trying to set the alpha channel after a window is created (troubleshooting a Mac problem) - 4.60.0.44 + 4.61.0.44 Updated Menubar docstring to clarify the Menubar iself cannot have colors changed, only the submenus. Use MenubarCustom if you want full control Format of file-signature changed - 4.60.0.45 + 4.61.0.45 Further refinement of Menubar docstring - 4.60.0.46 + 4.61.0.46 Added suggestion of using the Demo Browser to the checklist item of "Look for Demo Programs similar to your problem" - 4.60.0.47 + 4.61.0.47 Testing some importing methods Delay rerouting stdout and stderr in Output and Multiline elements until window is being built instead of when element is initialized - 4.60.0.48 + 4.61.0.48 Additional window movement capability. If Control+Mouse movement feature is enabled, then Control+Arrow key will move the window 1 pixel in the indicated direction - 4.60.0.49 + 4.61.0.49 Added Window.set_size to match the other settings that are performed through method calls. There is also the Window.size property, but since PySimpleGUI rarely uses properties, it makes sense to include a method as well as a property - 4.60.0.50 + 4.61.0.50 Fix for ColorChooser button filling in a None value when cancel from color choise dialog box. Nothing will be filled in target if dialog cancelled - 4.60.0.51 + 4.61.0.51 vtop, vcenter, vbottom helper functions gets background_color parameter vcenter and vbottom - added USING the expand_x and expand_y parms that were already defined. (HOPE NOTHING BREAKS!) - 4.60.0.52 + 4.61.0.52 justification parameter added to Listbox (default is left.. can be right and center now too) - 4.60.0.53 + 4.61.0.53 Made settings dictionary multiline in test harness write-only. New coupon code - 4.60.0.54 + 4.61.0.54 alpha_channel added to set_options. This sets the default value for the alpha_channel for all windows both user generated and PySimpleGUI generated (such as popups). - 4.60.0.55 + 4.61.0.55 Allow Browse/Chooser buttons (that have a target) to indicate a target key that is a tuple. - 4.60.1.55 + 4.61.0.55 While not actually correct.... 4.60.1 was released in the middle of the development above... I'm changing the version to look as if this release is based on 4.60.1. This code DOES have the same code that's in 4.60.1 so it's more a matter of symantics. Hoping this clears up confusion. Sorry for the dot-release causing so much confusion. - 4.60.1.56 + 4.61.0.56 Fix for Window.extend_layout. Was not picking up the background color of the container that the rows were being added to. - 4.60.1.57 + 4.61.0.57 Fixed Text element's update method docstring to indicate that value can be "Any" type not just strings - 4.60.1.58 + 4.61.0.58 Addition of without_titlebar paramter to Window.current_location. Defaults to False. If True, then the location of the main portion of the window will be returned (i.e. will not have the titlebar) - 4.60.1.59 + 4.61.0.59 Fix for crash if COLOR_SYSTEM_DEFAULT specified in parameter disabled_readonly_background_color or disabled_readonly_text_color for Input Element. Also applied similar fix for Tab element's focus color - 4.60.1.60 + 4.61.0.60 Addition of set_option parameter hide_window_when_creating. If set to False then window will not be hidden while creating and moving - 4.60.1.61 + 4.61.0.61 Changed the documentation location to PySimpleGUI.org (updated some comments as well as the SDK Reference Window's links) New coupon code. Make the Udemy button in the test harness now include the coupon code automatically - 4.60.1.62 + 4.61.0.62 Removed the "NOT avoilable on the MAC" from file_types parameter in the docstrings Use Withdraw to hide window during creation - 4.60.1.63 + 4.61.0.63 Addition of checklist item when logging new issue to GitHub - upgraded to latest version of PySimpleGUI on PyPI - Listbox justification parameter found to not be implemented on some early verions of tkinter so had to protect this situation. This new feature crached on the Pi for example - 4.60.1.64 + Listbox justification parameter found to not be implemented on some early verions of tkinter so had to protect this situation. This new feature crashed on the Pi for example + 4.61.0.64 Allow set_options(window_location=None) to indicate the OS should provide the window location. This will stop the Alpha channel being set to 0 when the window is created - 4.60.1.65 + 4.61.0.65 Addition of new Mac Control Panel option and emergency patch for MacOS version 12.3+ If MacOS version 12.3 or greater than option is ON by default When ON, the default Alpha channel for all windows is set to 0.99. This can either be turned off, or can be overridden by calling set_options in your application - 4.60.2.65 + 4.61.0.65 Bumping version number to avoid confusion. An emergency 4.60.2 release was posted to PyPI. This change was added to this current GitHub version of PySimpleGUI. - 4.60.3.66 + 4.61.0.66 Fixed bug in checking Mac OS version number that is being released as 4.60.3 - 4.60.3.67 + 4.61.0.67 Correctly check for Mac 12.3+ AND 13+ this time. - 4.60.3.68 + 4.61.0.68 Roll in the changes being released to PyPI as 4.60.3 - 4.60.3.69 + 4.61.0.69 Test to see if the additional pack of Notebook in Tab code was causing expansion problems - 4.60.3.70 + 4.61.0.70 Debug Print - fix for bug caused by no_button being set with non_blocking... a lesson in thorough testing... assumption was either blocking OR no_button (or else app would close without seeing the output... unless something else blocked. (DOH) - 4.60.3.71 + 4.61.0.71 "Window closed" check added to update methods for elements. This will prevent a crash and instead show an error popup Will be helpful for users that forget to check for closed window event in their event loop and try to call update after window closed. - 4.60.3.72 + 4.61.0.72 Output element now automatically sets auto_refresh to True. Should this not be desired, switch to using the Multiline element. There will likely be no impact to this change as it seems like the windows are alredy refreshing OK, but adding it just to be sure. - 4.60.3.73 + 4.61.0.73 Addition of Window.key_is_good(key) method. Returns True if key is used in the window. Saves from having to understand the window's key dictionary. Makes for easier code completion versus writing "if key in window.key_dict" - 4.60.3.74 + 4.61.0.74 Combo - if readonly, then set the select colors to be "transparent" (text=text color, background=background color) - 4.60.3.75 + 4.61.0.75 Better description of bar_color parm for the ProgressMeter element and the one_line_progress_meter function Combo element - addition of select parameter to enable easier selection of the contents of clearing of the selection of the contents. - 4.60.3.76 + 4.61.0.76 Changed the _this_elements_window_closed to use a flag "quick_check" for cheking is the window is closed. Found that calling tkinter.update takes over 500ms sometimes! For appllications that call update frequently, this caused a catestrophic slowdown for complex windows. - 4.60.3.77 + 4.61.0.77 New Window method - get_scaling - gets the scaling value from tkinter. Returns DEFAULT_SCALING if error. - 4.60.3.78 + 4.61.0.78 Custom Titlebar - Support added to Window.minimize, Window.maximize, and Window.normal - 4.60.3.79 + 4.61.0.79 Fix for Mulitline showing constant error messages after a Window is closed. Fix for correctly restoring stdout, stderr after they've been rerouted. THIS CODE IS NOT YET COMPLETE! Shooting for this weekend to get it done! Image element - more speicific with tkinter when chaning to a new image so that pypy would stop crashing due to garbage collect not running. This change didn't fix the pypy problem but it also didn't hurt the code to have it - 4.60.3.80 + 4.61.0.80 Quick and dirty addition of Alt-shortcuts for Buttons (like exists for Menus) For backward compatablity, must be enabled using set_options with use_button_shortcuts=True Fixed docstring errors in set_options docstring - 4.60.3.81 + 4.61.0.81 Completed restoration of stdout & stderr If an Output Element is used or a Multline element to reroute stdout and/or stderr, then this hasn't worked quite right in the past Hopefuly now, it does. A LIFO list (stack) is used to keep track of the current output device and is scrubbed for closed windows and restored if one is closed - 4.60.3.82 + 4.61.0.82 Addition of Style Names for horizontaland vertical ttk scrollbars - hsb_style_name and vsb_style_name so that scrollbar colors can be changed in user code - 4.60.3.83 + 4.61.0.83 Output element - now automatically reroutes cprint to here as well. Assumption is that you want stuff to go here without needing to specify each thing. If want more control, then use the Multiline directly - 4.60.3.84 + 4.61.0.84 Output element - updated docstring - 4.60.3.85 + 4.61.0.85 Combo Element - new parameter enable_per_char_events. When True will get an event when individual characters are entered. - 4.60.3.86 + 4.61.0.86 Added path to the interpreter to the get_versions information for better debugging - 4.60.3.87 + 4.61.0.87 Dark Gray 16 theme added - 4.60.3.88 + 4.61.0.88 New batch of Emojis! - 4.60.3.89 + 4.61.0.89 Addition of TITLEBAR_TEXT_KEY to provide access to custom titlebar titles - 4.60.3.90 + 4.61.0.90 Implemented the visible parameter for TabGroup. Was not being honored when creating element. Added TabGroup.update so it can be made visible. - 4.60.3.91 + 4.61.0.91 Added support for Custom Titlebar to the Window.set_title method - 4.60.3.92 + 4.61.0.92 Addition of starting_row_number parameter to the Table element. Sets the value for the first row in the table. - 4.60.3.93 + 4.61.0.93 Added 2 parameters to popup - drop_whitespace is passed to the wraptext.fill method. right_justify_buttons will "push" buttons to the right side if set to True - 4.60.3.94 + 4.61.0.94 Added Element.save_element_screenshot_to_disk - uses the same PIL integration that the save window screenshot to disk uses but applied to a single element - 4.60.3.95 + 4.61.0.95 Changed popup again - replaced right_justify_buttons with button_justification. Also removed the extra padding that was being added to the buttons. This matches a changed made to popup_scrolled earlier - 4.60.3.96 + 4.61.0.96 More emojis? Yes... more emojis... - 4.60.3.97 + 4.61.0.97 The main test harness now shows the python interpreter used to launch the test harness to make clearer what's running - 4.60.3.98 + 4.61.0.98 Better alignment of text in test harness Fixed mispelling in SystemTray.show_message - crashed if an int was passed in as the time value - 4.60.3.99 + 4.61.0.99 popup_get_text - Addition of history feature to bring up to same level as other popup_get_ functions. - 4.60.3.100 + 4.61.0.100 Set the "Active" foreground and background colors for Menu and ButtonMenu items. Automatically uses the swapped foreground and background colors. This impacts both inside the menus themseleves as well as the ButtonMenus so that when they are used in a MenubarCustom they mouseover nicely now. - 4.60.3.101 + 4.61.0.101 Added Window.is_hidden method. Returns True if the window is currently hidden - 4.60.3.102 + 4.61.0.102 Fixed error in the main test harness "not modal" popup test. Was setting the "Force Modal" setting to true after the popup test. - 4.60.3.103 + 4.61.0.103 Trinket is detected using a new mechansim now. The previous one was waayyy too simnple and as a result has broken in the past week. - 4.60.4.104 + 4.61.0.104 Version bump to keep up with the PyPI emergency 4.60.4 release - 4.60.4.105 + 4.61.0.105 Added SYMBOL_BULLET character - 4.60.4.106 + 4.61.0.106 Neon Green, Blue, Yellow themes... was writing some tests using them and thought why not start a new theme color category... "neon" - 4.60.4.107 + 4.61.0.107 Fixed an unreported problem perhaps... Added saving new menu into the Menu.Widget memeber variable in the Menu.update method. - 4.60.4.108 + 4.61.0.108 Added drop_whitespace to the docstring for popup. Parm has been in the code for quite some time but forgot the docstring so it's not in the SDK reference. - 4.60.4.109 + 4.61.0.109 Changed error message in error window when an element reuse has been detected in a layout. Previous message wasn't clear why there was an error. - 4.60.4.110 + 4.61.0.110 Added very detailed information to popup_error_with_traceback if the Exception information is passed in as one of the arguments - 4.60.4.111 + 4.61.0.111 Menu Element - delete all items in existing Menu widget rather than making a new one when the Menu definition changes - 4.60.4.112 + 4.61.0.112 Input.update - added font parameter - 4.60.4.113 + 4.61.0.113 Dark Blue 18 theme, a materially kinda theme, added - tip - experiment like PySimpleGUI does when "computing" colors. Grab a color of a part of a theme and use it as a background or a secondary button color. In other words, mix and match since the colors should all work together by design. - 4.60.4.114 + 4.61.0.114 Added execute_py_get_running_interpreter to differentiate between the one in the settings file versus currently running interpreter - 4.60.4.115 + 4.61.0.115 Image Element... added Zooooooooommmmm parameter - 4.60.4.116 + 4.61.0.116 Proliferiation/infection of the zoom parameter to more elements with images - Button, ButtonMenu Note that zoom and subsample can BOTH be used. This enables fractional scaling. Want 2/3 the size of the image? subsample=3, zoom=2 Tab is the remaining element this is being added to The Buttons implemented as functions need this addition as well Addition of the image_source parameter to Button and Button.update. This way of specifying images is commonly used in other elements Fixed ButtonMenu bug - subsample was not being applied to initial image in the layout - 4.60.4.117 + 4.61.0.117 Fix for set_vscroll_position not working correctly for a scrollable Column - 4.60.4.118 + 4.61.0.118 Completed addition of zoom options for images by adding image_zoom parameter to Tab element - 4.60.4.119 + 4.61.0.119 Fixed Neon Yellow theme. Had an extra "#" in a color. - 4.60.4.120 + 4.61.0.120 New coupon code - 4.60.4.121 + 4.61.0.121 New Jedi emoji - 4.60.4.122 + 4.61.0.122 Swapped Push and Stretch, VPush and VStretch. Made Push and VPush the function and Stratch and VStresth the aliases. Did this because Push is used almost universally, not Stretch. - 4.60.4.123 + 4.61.0.123 Fix for incorrect values for Element.ttk_style and Element.ttk_style_name. Some elements had values overwritten if a scrollbar, etc, was used Changed a number of the ttk elements (Button for example) to use the base name as the parm to creating the custom style to achieve a more predictable naming style (relies on the formula used in the create style function rather than ad hoc adding "custom" onto name) - 4.60.4.124 + 4.61.0.124 Multiline Element docstring fixes - 4.60.4.125 + 4.61.0.125 Addition of 2 overrides to Window.find_element so that more control is available to applications wishing to perform special key lookups - 4.60.4.126 + 4.61.0.126 Made button_color parameter's docstring value consistent across all calls. Now set to - (str, str) | str - 4.60.4.127 + 4.61.0.127 User settings delete calls - aded silent_on_error option so deletes of non-existant entries can uappen silently if desired. popup_quick_message - now defaults to keep-on-top to True - 4.60.4.128 + 4.61.0.128 Cleaned up User Settings API code for porting - 4.60.4.129 + 4.61.0.129 button_color parm added to ButtonMenu.update - 4.60.4.130 + 4.61.0.130 New coupon - 4.60.4.131 + 4.61.0.131 Window timers feature added. Get a single or repeating timer events for your Window by calling window.timer_start - 4.60.4.132 + 4.61.0.132 Added the Window.stop_all method to stop all timers for a window - 4.60.4.133 + 4.61.0.133 Added Window.timer_get_active_timers to get a list of the active timers for the window - 4.60.4.134 + 4.61.0.134 popup_get_date - exposed the fonts as parameters so that the user code and modify them (specifically to get around a Mac font bug) - 4.60.4.135 + 4.61.0.135 Renamed QuickMeter to _QuickMeter so that it's clear that it's not an object meant to be used by users - + 4.61.0.136 + Tree element - if headings is set to None, no headings area is shown + 4.61.0.137 + "Take me to error" button is disabled in error traceback popup if not editor is configured. Also adds instructions if no editor. + 4.61.0.138 + Added begin_at_sunday_plus to the CalendarButton docstring + 4.61.0.139 + Moved debugger constants to inside of the debugger class. Simplified the locals and globals popups. + 4.61.0.140 + Experimental change.... Table.get now returns the values from the widget's selection method + 4.61.0.141 + Made the Debugger's use of popups change the theme to the same dark gray theme used in the rest of the debugger windows. + 4.61.0.142 + Added selected_text_color & selected_background_color to Input element. Will override the default values + 4.61.0.143 + Fixed bug in Combo.update - the width of the element wasn't getting updated to match new values + 4.61.0.144 + Added selected_text_color & selected_background_color to Multiline element. Will override the default values + 4.61.0.145 + Fixed bind_return_key docstrings in the pre-defined buttons. Made the Button bind_return_key docstring more descriptive + 4.61.0.146 + Changed version numbers to 4.61.0 to try and fix the confusion about what's been released to PyPI. + 4.61.0.147 + New Udemy coupon code + 4.61.0.148 + Removed the print when the Mac Alpha Channel 0.99 patch is applied + 4.61.0.149 + Removed second print when Mac patch applied + 4.61.0.150 + Tree Element new parameter - click_toggles_select - if True then clicking a selected item will unselect it + 4.61.0.151 + Fixed problem with TabGroups when the text was blank for a Tab. Was not correctly identifying the active tab in the Values dictionary + 4.61.0.152 + Updated TabGroup.get to use the same method to find the currently active tab that was just added above. + 4.61.0.153 + Updated layout error messages to include "sometimes" in the description of what may be causing error + 4.61.0.154 + Updated Window.start_timer docstring to include the constants EVENT_TIMER and TIMER_KEY since the call reference doesn't show the variable names but rather the string value. + 4.61.0.155 + Multiline new parameter autoscroll_only_at_bottom. When True, the element will autoscroll (keep scrollbar at the bottom) only if the scrollbar is already at the bottom. + 4.61.0.156 + Added the new Multiline parameter autoscroll_only_at_bottom so that the Output element can also use this option + 4.61.0.157 + Added the _optional_window_data function that is used to help with local PySimpleGUI testing of release candidates. Not meant to be used by end-users. + 4.61.0.158 + Changed Checkbox activeforeground to be the same as the text so mouseover doesn't change color + 4.61.0.159 + New Global Settings feature - Window watermarking. Can be forced on temporarily by settings watermark=True in your Window creation + 4.61.0.160 + Fix "Bold" crash from watermarking feature + 4.61.0.161 + New set_options to control user-defined watermarks + 4.61.0.162 + Addition of new parms to Combo.update - text color, background color. Also font now applied correctly to dropdown list + 4.61.0.163 + Checkbox - added highlight thickness parm to control how thick the focus ring is. Defaults to 1 still but now changable + 4.61.0.164 + Input element - fixed problem where the input 'cursor' (the I-beam) was being set to the THEME'S color, not the color indicated by the individual element + 4.61.0.165 + Multiline & Spin - Applied same fix for input "cursor" (I-Beam) color that was added to the Input element. + Added new method - set_ibeam_color to Input, Multiline and Spin elements. Combo is a ttk element so it's not available using this call yet + 4.61.0.166 + New Udemy coupon + 4.61.0.167 + New Udemy coupon + Fix for bad user settings key for user watermark. Added Python version to watermark + 4.61.0.168 + Changed Radio activeforeground to be the same as the text so mouseover doesn't change color + 4.61.0.169 + Allow no end-key to be specified for perform_long_operation/start_thread. Careful with backward compatibility! If you skip adding parm on old versions of PySimpleGUI then it'll not work. + 4.61.0.170 + Possible fix for Mac Input Element issue that's been happening with no-titlebar windows on MacOS 13.2.1 Ventura + 4.61.0.171 + Added formatted_datetime_now function for easy timestamps for logging + 4.61.0.172 + Added upgrade service - No notification popups should be shown yet. Don't want to SPAM users while testing + 4.61.0.173 + Made changing the "Show only critical" setting in global settings take effect immediately rather than waiting until closed settings window + Added timer_stop_usec to return timer value in microseconds + 4.61.0.174 + Overwrite upgrade data if any portion has changed + 4.61.0.175 + Notification window - added countdown counter. Added hand cursor if message is a link and enable clicking of link to open the browser to that link + 4.61.0.176 + Improved linux distro detection + 4.61.0.177 + Custom Titlebar - Support for disabling resizing (maximizing too), support for disable minimize and disable close + 4.61.0.178 + Input element - fix for bug with text color & logic wasn't quite right with the "read for disabled" stuff in the update as well as when making window + 4.61.0.179 + New Udemy coupon + 4.61.0.180 + Removed Security tab from system settings + 4.61.0.181 + Added check for None and COLOR_SYSTEM_DEFAULT before any colors being set in Input.update + 4.61.0.182 + Only enable the Mac alpha channel 0.99 patch when tkinter version is 8.6.12. Have learned this is not needed for any other tkinter version + 4.61.0.183 + Show Critical upgrade service messages. Removed the extra upgrade from github button from tab. + 4.61.0.184 + Fix for Combo.update background color changing incorrect widget setting. + 4.61.0.185 + Fix for crash when no headings specified for a table by casting values into strings + 4.61.0.186 + Fix for popup_get_file when using no_window=True. Now returns None if cancelled or window closed + 4.61.0.187 + Corrected the Table.get docstring to reflect that it returns a list of ints + 4.61.0.188 + Finished correcting the Table.get docstring.... think I got it right this time.... + 4.61.0.189 + Changed Table click events to be generated on Button Release instead of Button (down). Was not getting the + correct selected rows in the values dictionary when the click event was generated using down. Now the selected rows is correct + 4.61.0.190 + Addition of black2 theme + Fix typo of text in _widget_was_created + 4.61.0.191 + Fixed bug in Button.update. Was setting the activeforeground and activebackground which broke the mouseover or mouse press colors + 4.61.0.192 + Fixed bug in Button.update. Corrected when activeforeground and activebackground are set. Removing them in version above was a mistake + 4.61.0.193 + Fixed spelling errors... resuse should have been reuse + 4.61.0.194 + Added Listbox.select_index and Listbox.set_index_color + 4.61.0.195 + New Udemy Coupon + 4.61.0.196 + Added highlight colors to the set_index_color method. Parms highlight_text_color & highlight_background_color control changing the highlight colors + 4.61.0.197 + Made Table Element Header mouse-over and clicked be the inverse of the normal header colors. Makes for a much nicer experience + 4.61.0.198 + Added no_buffering option to popup_animated + 4.61.0.199 + Updated Udemy coupon code + 4.61.0.200 + Fix for grab anywhere window movement and control+left_mouse_drag. Window move smoother, including the Move-All-Windows feature. Thank you JASON for the help! + 4.61.0.201 + Added init for _mouse_offset_x and y in case tkinter doesn't call the mouse down callback + 4.61.0.202 + Added doctring and destroy previous right click menu to set_right_click_menu + 4.61.0.203 + Changed Sizer element to use Canvas instead of Column element + 4.61.0.204 + One more change to sizer so that it uses pad instead of size. + 4.61.0.205 + Fixed docstring for execute_command_subprocess. The command description was incorrect + 4.61.0.206 + New Udemy Coupon code + + """ __version__ = version.split()[0] # For PEP 396 and PEP 345 @@ -361,7 +509,7 @@ port = 'PySimpleGUI' """ - Copyright 2018, 2019, 2020, 2021, 2022 PySimpleGUI(tm) + Copyright 2018, 2019, 2020, 2021, 2022, 2023 PySimpleGUI(tm) Before getting into the details, let's talk about the high level goals of the PySimpleGUI project. @@ -561,9 +709,9 @@ def timer_start(): def timer_stop(): """ - Time your code easily.... stop the timer and print the number of milliseconds since the timer start + Time your code easily.... stop the timer and print the number of MILLISECONDS since the timer start - :return: delta in milliseconds from timer_start was called + :return: delta in MILLISECONDS from timer_start was called :rtype: int """ global g_time_delta, g_time_end @@ -572,6 +720,19 @@ def timer_stop(): g_time_delta = g_time_end - g_time_start return int(g_time_delta * 1000) +def timer_stop_usec(): + """ + Time your code easily.... stop the timer and print the number of MICROSECONDS since the timer start + + :return: delta in MICROSECONDS from timer_start was called + :rtype: int + """ + global g_time_delta, g_time_end + + g_time_end = time.time() + g_time_delta = g_time_end - g_time_start + return int(g_time_delta * 1000000) + def _timeit(func): """ @@ -628,6 +789,19 @@ def _timeit_summary(func): return wrapper +def formatted_datetime_now(): + """ + Returns a string with current date and time formatted YYYY-MM-DD HH:MM:SS for easy logging + + :return: String with date and time formatted YYYY-MM-DD HH:MM:SS + :rtype: (str) + """ + now = datetime.datetime.now() + current_time = now.strftime("%Y-%m-%d %H:%M:%S") + return current_time + + + def running_linux(): """ Determines the OS is Linux by using sys.platform @@ -2048,7 +2222,7 @@ class Element(): if SUPPRESS_WIDGET_NOT_FINALIZED_WARNINGS: return False - warnings.warn('You cannot Update element with key = {} until the window.read() is called or finalized=True when creating window'.format(self.Key), UserWarning) + warnings.warn('You cannot Update element with key = {} until the window.read() is called or set finalize=True when creating window'.format(self.Key), UserWarning) if not SUPPRESS_ERROR_POPUPS: _error_popup_with_traceback('Unable to complete operation on element with key {}'.format(self.Key), 'You cannot perform operations (such as calling update) on an Element until:', @@ -2104,6 +2278,12 @@ class Element(): def set_right_click_menu(self, menu=None): + """ + Sets a right click menu for an element. + If a menu is already set for the element, it will call the tkinter destroy method to remove it + :param menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. + :type menu: List[List[ List[str] | str ]] + """ if menu == MENU_RIGHT_CLICK_DISABLED: return if menu is None: @@ -2111,6 +2291,12 @@ class Element(): if menu is None: return if menu: + # If previously had a menu destroy it + if self.TKRightClickMenu: + try: + self.TKRightClickMenu.destroy() + except: + pass top_menu = tk.Menu(self.ParentForm.TKroot, tearoff=self.ParentForm.right_click_menu_tearoff, tearoffcommand=self._tearoff_menu_callback) if self.ParentForm.right_click_menu_background_color not in (COLOR_SYSTEM_DEFAULT, None): @@ -2275,7 +2461,7 @@ class Input(Element): def __init__(self, default_text='', size=(None, None), s=(None, None), disabled=False, password_char='', justification=None, background_color=None, text_color=None, font=None, tooltip=None, border_width=None, change_submits=False, enable_events=False, do_not_clear=True, key=None, k=None, focus=False, pad=None, p=None, - use_readonly_for_disable=True, readonly=False, disabled_readonly_background_color=None, disabled_readonly_text_color=None, expand_x=False, expand_y=False, + use_readonly_for_disable=True, readonly=False, disabled_readonly_background_color=None, disabled_readonly_text_color=None, selected_text_color=None, selected_background_color=None, expand_x=False, expand_y=False, right_click_menu=None, visible=True, metadata=None): """ :param default_text: Text initially shown in the input box as a default value(Default value = ''). Will automatically be converted to string @@ -2324,6 +2510,10 @@ class Input(Element): :type disabled_readonly_background_color: (str) :param disabled_readonly_text_color: If state is set to readonly or disabled, the color to use for the text :type disabled_readonly_text_color: (str) + :param selected_text_color: Color of text when it is selected (using mouse or control+A, etc) + :type selected_text_color: (str) + :param selected_background_color: Color of background when it is selected (using mouse or control+A, etc) + :type selected_background_color: (str) :param expand_x: If True the element will automatically expand in the X direction to fill available space :type expand_x: (bool) :param expand_y: If True the element will automatically expand in the Y direction to fill available space @@ -2341,6 +2531,8 @@ class Input(Element): self.PasswordCharacter = password_char bg = background_color if background_color is not None else DEFAULT_INPUT_ELEMENTS_COLOR fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.selected_text_color = selected_text_color + self.selected_background_color = selected_background_color self.Focus = focus self.do_not_clear = do_not_clear self.Justification = justification @@ -2401,11 +2593,28 @@ class Input(Element): _error_popup_with_traceback('Error in Input.update - The window was closed') return + if background_color not in (None, COLOR_SYSTEM_DEFAULT): + self.TKEntry.configure(background=background_color) + self.BackgroundColor = background_color + if text_color not in (None, COLOR_SYSTEM_DEFAULT): + self.TKEntry.configure(fg=text_color) + self.TextColor = text_color + if disabled is True: - self.TKEntry['state'] = 'readonly' if self.UseReadonlyForDisable else 'disabled' + if self.UseReadonlyForDisable: + if self.disabled_readonly_text_color not in (None, COLOR_SYSTEM_DEFAULT): + self.TKEntry.configure(fg=self.disabled_readonly_text_color) + self.TKEntry['state'] = 'readonly' + else: + if self.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + self.TKEntry.configure(fg=self.TextColor) + self.TKEntry['state'] = 'disabled' + self.Disabled = True elif disabled is False: - self.TKEntry['state'] = 'readonly' if self.ReadOnly else 'normal' - self.Disabled = disabled if disabled is not None else self.Disabled + self.TKEntry['state'] = 'normal' + if self.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + self.TKEntry.configure(fg=self.TextColor) + self.Disabled = False if readonly is True: self.TKEntry['state'] = 'readonly' @@ -2414,10 +2623,7 @@ class Input(Element): - if background_color not in (None, COLOR_SYSTEM_DEFAULT): - self.TKEntry.configure(background=background_color) - if text_color not in (None, COLOR_SYSTEM_DEFAULT): - self.TKEntry.configure(fg=text_color) + if value is not None: if paste is not True: try: @@ -2454,6 +2660,28 @@ class Input(Element): + def set_ibeam_color(self, ibeam_color=None): + """ + Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by + many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term + ibeam is used in this case. + :param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted + :type ibeam_color: (str) + """ + + if not self._widget_was_created(): + return + if ibeam_color is not None: + try: + self.Widget.config(insertbackground=ibeam_color) + except Exception as e: + _error_popup_with_traceback('Error setting I-Beam color in set_ibeam_color', + 'The element has a key:', self.Key, + 'The color passed in was:', ibeam_color) + + + + def get(self): """ Read and return the current value of the input element. Must call `Window.Read` or `Window.Finalize` prior @@ -2505,7 +2733,7 @@ class Combo(Element): :type button_background_color: (str) :param button_arrow_color: The color of the arrow on the button on the combo box :type button_arrow_color: (str) - :param bind_return_key: If True, then the return key will cause a the Combo to generate an event + :param bind_return_key: If True, then the return key will cause a the Combo to generate an event when return key is pressed :type bind_return_key: (bool) :param change_submits: DEPRICATED DO NOT USE. Use `enable_events` instead :type change_submits: (bool) @@ -2567,7 +2795,7 @@ class Combo(Element): super().__init__(ELEM_TYPE_INPUT_COMBO, size=sz, auto_size_text=auto_size_text, background_color=bg, text_color=fg, key=key, pad=pad, tooltip=tooltip, font=font or DEFAULT_FONT, visible=visible, metadata=metadata) - def update(self, value=None, values=None, set_to_index=None, disabled=None, readonly=None, font=None, visible=None, size=(None, None), select=None): + def update(self, value=None, values=None, set_to_index=None, disabled=None, readonly=None, font=None, visible=None, size=(None, None), select=None, text_color=None, background_color=None): """ Changes some of the settings for the Combo Element. Must call `Window.Read` or `Window.Finalize` prior. Note that the state can be in 3 states only.... enabled, disabled, readonly even @@ -2580,25 +2808,30 @@ class Combo(Element): function "pin" to ensure your element is "pinned" to that location in your layout so that it returns there when made visible. - :param value: change which value is current selected based on new list of previous list of choices - :type value: (Any) - :param values: change list of choices - :type values: List[Any] - :param set_to_index: change selection to a particular choice starting with index = 0 - :type set_to_index: (int) - :param disabled: disable or enable state of the element - :type disabled: (bool) - :param readonly: if True make element readonly (user cannot change any choices). Enables the element if either choice are made. - :type readonly: (bool) - :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike - :type font: (str or (str, int[, str]) or None) - :param visible: control visibility of element - :type visible: (bool) - :param size: width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list - :type size: (int, int) - :param select: if True, then the text will be selected, if False then selection will be cleared - :type select: (bool) + :param value: change which value is current selected based on new list of previous list of choices + :type value: (Any) + :param values: change list of choices + :type values: List[Any] + :param set_to_index: change selection to a particular choice starting with index = 0 + :type set_to_index: (int) + :param disabled: disable or enable state of the element + :type disabled: (bool) + :param readonly: if True make element readonly (user cannot change any choices). Enables the element if either choice are made. + :type readonly: (bool) + :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike + :type font: (str or (str, int[, str]) or None) + :param visible: control visibility of element + :type visible: (bool) + :param size: width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list + :type size: (int, int) + :param select: if True, then the text will be selected, if False then selection will be cleared + :type select: (bool) + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) """ + if size != (None, None): if isinstance(size, int): size = (size, 1) @@ -2628,7 +2861,7 @@ class Combo(Element): width = self.Size[0] else: width = max_line_len + 1 - # self.TKCombo.configure(width=width) + self.TKCombo.configure(width=width) else: self.TKCombo.configure(height=size[1]) self.TKCombo.configure(width=size[0]) @@ -2663,8 +2896,44 @@ class Combo(Element): elif disabled is False and self.Readonly is False: self.TKCombo['state'] = 'enable' self.Disabled = disabled if disabled is not None else self.Disabled + + combostyle = self.ttk_style + style_name = self.ttk_style_name + if text_color is not None: + combostyle.configure(style_name, foreground=text_color) + combostyle.configure(style_name, selectforeground=text_color) + combostyle.configure(style_name, insertcolor=text_color) + combostyle.map(style_name, fieldforeground=[('readonly', text_color)]) + self.TextColor = text_color + if background_color is not None: + combostyle.configure(style_name, selectbackground=background_color) + combostyle.map(style_name, fieldbackground=[('readonly', background_color)]) + combostyle.configure(style_name, fieldbackground=background_color) + self.BackgroundColor = background_color + + if self.Readonly is True: + if text_color not in (None, COLOR_SYSTEM_DEFAULT): + combostyle.configure(style_name, selectforeground=text_color) + if background_color not in (None, COLOR_SYSTEM_DEFAULT): + combostyle.configure(style_name, selectbackground=background_color) + + if font is not None: + self.Font = font self.TKCombo.configure(font=font) + self._dropdown_newfont = tkinter.font.Font(font=font) + self.ParentRowFrame.option_add("*TCombobox*Listbox*Font", self._dropdown_newfont) + + + # make tcl call to deal with colors for the drop-down formatting + try: + if self.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT) and \ + self.TextColor not in (None, COLOR_SYSTEM_DEFAULT): + self.Widget.tk.eval( + '[ttk::combobox::PopdownWindow {}].f.l configure -foreground {} -background {} -selectforeground {} -selectbackground {} -font {}'.format(self.Widget, self.TextColor, self.BackgroundColor, self.BackgroundColor, self.TextColor, self._dropdown_newfont)) + except Exception as e: + pass # going to let this one slide + if visible is False: self._pack_forget_save_settings() # self.TKCombo.pack_forget() @@ -2678,6 +2947,7 @@ class Combo(Element): elif select is False: self.TKCombo.select_clear() + def get(self): """ Returns the current (right now) value of the Combo. DO NOT USE THIS AS THE NORMAL WAY OF READING A COMBO! @@ -2872,7 +3142,7 @@ class Listbox(Element): :type change_submits: (bool) :param enable_events: Turns on the element specific events. Listbox generates events when an item is clicked :type enable_events: (bool) - :param bind_return_key: If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: If True, then the return key will cause a the Listbox to generate an event when return key is pressed :type bind_return_key: (bool) :param size: w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 :type size: (int, int) | (int, None) | int @@ -3096,6 +3366,77 @@ class Listbox(Element): value = [] return value + + def select_index(self, index, highlight_text_color=None, highlight_background_color=None): + """ + Selects an index while providing capability to setting the selected color for the index to specific text/background color + + :param index: specifies which item to change. index starts at 0 and goes to length of values list minus one + :type index: (int) + :param highlight_text_color: color of the text when this item is selected. + :type highlight_text_color: (str) + :param highlight_background_color: color of the background when this item is selected + :type highlight_background_color: (str) + """ + + if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow + return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Listbox.select_item - The window was closed') + return + + if index >= len(self.Values): + _error_popup_with_traceback('Index {} is out of range for Listbox.select_index. Max allowed index is {}.'.format(index, len(self.Values)-1)) + return + + self.TKListbox.selection_set(index, index) + + if highlight_text_color is not None: + self.widget.itemconfig(index, selectforeground=highlight_text_color) + if highlight_background_color is not None: + self.widget.itemconfig(index, selectbackground=highlight_background_color) + + + def set_index_color(self, index, text_color=None, background_color=None, highlight_text_color=None, highlight_background_color=None): + """ + Sets the color of a specific item without selecting it + + :param index: specifies which item to change. index starts at 0 and goes to length of values list minus one + :type index: (int) + :param text_color: color of the text for this item + :type text_color: (str) + :param background_color: color of the background for this item + :type background_color: (str) + :param highlight_text_color: color of the text when this item is selected. + :type highlight_text_color: (str) + :param highlight_background_color: color of the background when this item is selected + :type highlight_background_color: (str) + """ + + if not self._widget_was_created(): # if widget hasn't been created yet, then don't allow + return + + if self._this_elements_window_closed(): + _error_popup_with_traceback('Error in Listbox.set_item_color - The window was closed') + return + + if index >= len(self.Values): + _error_popup_with_traceback('Index {} is out of range for Listbox.set_index_color. Max allowed index is {}.'.format(index, len(self.Values)-1)) + return + + if text_color is not None: + self.widget.itemconfig(index, fg=text_color) + if background_color is not None: + self.widget.itemconfig(index, bg=background_color) + if highlight_text_color is not None: + self.widget.itemconfig(index, selectforeground=highlight_text_color) + if highlight_background_color is not None: + self.widget.itemconfig(index, selectbackground=highlight_background_color) + + + + GetIndexes = get_indexes GetListValues = get_list_values SetValue = set_value @@ -3322,56 +3663,59 @@ class Checkbox(Element): """ def __init__(self, text, default=False, size=(None, None), s=(None, None), auto_size_text=None, font=None, background_color=None, - text_color=None, checkbox_color=None, change_submits=False, enable_events=False, disabled=False, key=None, k=None, pad=None, p=None, tooltip=None, + text_color=None, checkbox_color=None, highlight_thickness=1, change_submits=False, enable_events=False, disabled=False, key=None, k=None, pad=None, p=None, tooltip=None, right_click_menu=None, expand_x=False, expand_y=False, visible=True, metadata=None): """ - :param text: Text to display next to checkbox - :type text: (str) - :param default: Set to True if you want this checkbox initially checked - :type default: (bool) - :param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 - :type size: (int, int) | (None, None) | int - :param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used - :type s: (int, int) | (None, None) | int - :param auto_size_text: if True will size the element to match the length of the text - :type auto_size_text: (bool) - :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike - :type font: (str or (str, int[, str]) or None) - :param background_color: color of background - :type background_color: (str) - :param text_color: color of the text - :type text_color: (str) - :param checkbox_color: color of background of the box that has the check mark in it. The checkmark is the same color as the text - :type checkbox_color: (str) - :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead - :type change_submits: (bool) - :param enable_events: Turns on the element specific events. Checkbox events happen when an item changes - :type enable_events: (bool) - :param disabled: set disable state - :type disabled: (bool) - :param key: Used with window.find_element and with return values to uniquely identify this element - :type key: str | int | tuple | object - :param k: Same as the Key. You can use either k or key. Which ever is set will be used. - :type k: str | int | tuple | object - :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) - :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int - :param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used - :type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int - :param tooltip: text, that will appear when mouse hovers over the element - :type tooltip: (str) - :param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. - :type right_click_menu: List[List[ List[str] | str ]] - :param expand_x: If True the element will automatically expand in the X direction to fill available space - :type expand_x: (bool) - :param expand_y: If True the element will automatically expand in the Y direction to fill available space - :type expand_y: (bool) - :param visible: set visibility state of the element - :type visible: (bool) - :param metadata: User metadata that can be set to ANYTHING - :type metadata: (Any) + :param text: Text to display next to checkbox + :type text: (str) + :param default: Set to True if you want this checkbox initially checked + :type default: (bool) + :param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 + :type size: (int, int) | (None, None) | int + :param s: Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used + :type s: (int, int) | (None, None) | int + :param auto_size_text: if True will size the element to match the length of the text + :type auto_size_text: (bool) + :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike + :type font: (str or (str, int[, str]) or None) + :param background_color: color of background + :type background_color: (str) + :param text_color: color of the text + :type text_color: (str) + :param checkbox_color: color of background of the box that has the check mark in it. The checkmark is the same color as the text + :type checkbox_color: (str) + :param highlight_thickness: thickness of border around checkbox when gets focus + :type highlight_thickness: (int) + :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead + :type change_submits: (bool) + :param enable_events: Turns on the element specific events. Checkbox events happen when an item changes + :type enable_events: (bool) + :param disabled: set disable state + :type disabled: (bool) + :param key: Used with window.find_element and with return values to uniquely identify this element + :type key: str | int | tuple | object + :param k: Same as the Key. You can use either k or key. Which ever is set will be used. + :type k: str | int | tuple | object + :param pad: Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) + :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int + :param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used + :type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int + :param tooltip: text, that will appear when mouse hovers over the element + :type tooltip: (str) + :param right_click_menu: A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. + :type right_click_menu: List[List[ List[str] | str ]] + :param expand_x: If True the element will automatically expand in the X direction to fill available space + :type expand_x: (bool) + :param expand_y: If True the element will automatically expand in the Y direction to fill available space + :type expand_y: (bool) + :param visible: set visibility state of the element + :type visible: (bool) + :param metadata: User metadata that can be set to ANYTHING + :type metadata: (Any) """ + self.Text = text self.InitialState = bool(default) self.Value = None @@ -3379,6 +3723,7 @@ class Checkbox(Element): self.Disabled = disabled self.TextColor = text_color if text_color else theme_text_color() self.RightClickMenu = right_click_menu + self.highlight_thickness = highlight_thickness # ---- compute color of circle background --- if checkbox_color is None: @@ -3538,7 +3883,7 @@ class Spin(Element): :type s: (int, int) | (None, None) | int :param auto_size_text: if True will size the element to match the length of the text :type auto_size_text: (bool) - :param bind_return_key: If True, then the return key will cause a the element to generate an event + :param bind_return_key: If True, then the return key will cause a the element to generate an event when return key is pressed :type bind_return_key: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) @@ -3679,6 +4024,30 @@ class Spin(Element): # Window._window_that_exited = self.ParentForm # self.ParentForm.TKroot.quit() # kick the users out of the mainloop + + + + def set_ibeam_color(self, ibeam_color=None): + """ + Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by + many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term + ibeam is used in this case. + :param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted + :type ibeam_color: (str) + """ + + if not self._widget_was_created(): + return + if ibeam_color is not None: + try: + self.Widget.config(insertbackground=ibeam_color) + except Exception as e: + _error_popup_with_traceback('Error setting I-Beam color in set_ibeam_color', + 'The element has a key:', self.Key, + 'The color passed in was:', ibeam_color) + + + def get(self): """ Return the current chosen value showing in spinbox. @@ -3712,8 +4081,8 @@ class Multiline(Element): one up in the future too. """ - def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, border_width=None, - size=(None, None), s=(None, None), auto_size_text=None, background_color=None, text_color=None, horizontal_scroll=False, change_submits=False, + def __init__(self, default_text='', enter_submits=False, disabled=False, autoscroll=False, autoscroll_only_at_bottom=False, border_width=None, + size=(None, None), s=(None, None), auto_size_text=None, background_color=None, text_color=None, selected_text_color=None, selected_background_color=None, horizontal_scroll=False, change_submits=False, enable_events=False, do_not_clear=True, key=None, k=None, write_only=False, auto_refresh=False, reroute_stdout=False, reroute_stderr=False, reroute_cprint=False, echo_stdout_stderr=False, focus=False, font=None, pad=None, p=None, tooltip=None, justification=None, no_scrollbar=False, wrap_lines=None, sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None, expand_x=False, expand_y=False, rstrip=True, right_click_menu=None, visible=True, metadata=None): @@ -3726,6 +4095,8 @@ class Multiline(Element): :type disabled: (bool) :param autoscroll: If True the contents of the element will automatically scroll as more data added to the end :type autoscroll: (bool) + :param autoscroll_only_at_bottom: If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline + :type autoscroll_only_at_bottom: (bool) :param border_width: width of border around element in pixels :type border_width: (int) :param size: (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 @@ -3738,6 +4109,10 @@ class Multiline(Element): :type background_color: (str) :param text_color: color of the text :type text_color: (str) + :param selected_text_color: Color of text when it is selected (using mouse or control+A, etc) + :type selected_text_color: (str) + :param selected_background_color: Color of background when it is selected (using mouse or control+A, etc) + :type selected_background_color: (str) :param horizontal_scroll: Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical :type horizontal_scroll: (bool) :param change_submits: DO NOT USE. Only listed for backwards compat - Use enable_events instead @@ -3813,6 +4188,9 @@ class Multiline(Element): self.Focus = focus self.do_not_clear = do_not_clear fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR + self.selected_text_color = selected_text_color + self.selected_background_color = selected_background_color self.Autoscroll = autoscroll self.Disabled = disabled self.ChangeSubmits = change_submits or enable_events @@ -3839,6 +4217,7 @@ class Multiline(Element): self.reroute_stderr = reroute_stderr self.no_scrollbar = no_scrollbar self.hscrollbar = None # The horizontal scrollbar + self.auto_scroll_only_at_bottom = autoscroll_only_at_bottom sz = size if size != (None, None) else s super().__init__(ELEM_TYPE_INPUT_MULTILINE, size=sz, auto_size_text=auto_size_text, background_color=bg, @@ -3893,6 +4272,7 @@ class Multiline(Element): if autoscroll is not None: self.Autoscroll = autoscroll + current_scroll_position = self.TKText.yview()[1] if justification is not None: if justification.startswith('l'): @@ -3939,8 +4319,11 @@ class Multiline(Element): self.TKText.configure(state='disabled') self.DefaultText = value + # if self.Autoscroll: + # self.TKText.see(tk.END) if self.Autoscroll: - self.TKText.see(tk.END) + if not self.auto_scroll_only_at_bottom or (self.auto_scroll_only_at_bottom and current_scroll_position == 1.0): + self.TKText.see(tk.END) if disabled is True: self.TKText.configure(state='disabled') elif disabled is False: @@ -4107,6 +4490,31 @@ class Multiline(Element): # pass return + + + + + def set_ibeam_color(self, ibeam_color=None): + """ + Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by + many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term + ibeam is used in this case. + :param ibeam_color: color to set the "I-Beam" used to indicate where characters will be inserted + :type ibeam_color: (str) + """ + + if not self._widget_was_created(): + return + if ibeam_color is not None: + try: + self.Widget.config(insertbackground=ibeam_color) + except Exception as e: + _error_popup_with_traceback('Error setting I-Beam color in set_ibeam_color', + 'The element has a key:', self.Key, + 'The color passed in was:', ibeam_color) + + + def __del__(self): """ AT ONE TIME --- If this Widget is deleted, be sure and restore the old stdout, stderr @@ -4721,7 +5129,7 @@ class Output(Multiline): so that an item is not included in the values dictionary on every window.read call """ - def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, echo_stdout_stderr=False, font=None, tooltip=None, + def __init__(self, size=(None, None), s=(None, None), background_color=None, text_color=None, pad=None, p=None, autoscroll_only_at_bottom=False, echo_stdout_stderr=False, font=None, tooltip=None, key=None, k=None, right_click_menu=None, expand_x=False, expand_y=False, visible=True, metadata=None, wrap_lines=None, horizontal_scroll=None, sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None): """ @@ -4737,6 +5145,8 @@ class Output(Multiline): :type pad: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int :param p: Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used :type p: (int, int) or ((int, int),(int,int)) or (int,(int,int)) or ((int, int),int) | int + :param autoscroll_only_at_bottom: If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline + :type autoscroll_only_at_bottom: (bool) :param echo_stdout_stderr: If True then output to stdout will be output to this element AND also to the normal console location :type echo_stdout_stderr: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike @@ -4778,7 +5188,7 @@ class Output(Multiline): """ - super().__init__(size=size, s=s, background_color=background_color, text_color=text_color, pad=pad, p=p, echo_stdout_stderr=echo_stdout_stderr, font=font, tooltip=tooltip, wrap_lines=wrap_lines, horizontal_scroll=horizontal_scroll, key=key, k=k, right_click_menu=right_click_menu, write_only=True, reroute_stdout=True, reroute_stderr=True, reroute_cprint=True, autoscroll=True, auto_refresh=True, expand_x=expand_x, expand_y=expand_y, visible=visible, metadata=metadata, sbar_trough_color=sbar_trough_color, sbar_background_color=sbar_background_color, sbar_arrow_color=sbar_arrow_color, sbar_width=sbar_width, sbar_arrow_width=sbar_arrow_width, sbar_frame_color=sbar_frame_color, sbar_relief=sbar_relief) + super().__init__(size=size, s=s, background_color=background_color, autoscroll_only_at_bottom=autoscroll_only_at_bottom, text_color=text_color, pad=pad, p=p, echo_stdout_stderr=echo_stdout_stderr, font=font, tooltip=tooltip, wrap_lines=wrap_lines, horizontal_scroll=horizontal_scroll, key=key, k=k, right_click_menu=right_click_menu, write_only=True, reroute_stdout=True, reroute_stderr=True, reroute_cprint=True, autoscroll=True, auto_refresh=True, expand_x=expand_x, expand_y=expand_y, visible=visible, metadata=metadata, sbar_trough_color=sbar_trough_color, sbar_background_color=sbar_background_color, sbar_arrow_color=sbar_arrow_color, sbar_width=sbar_width, sbar_arrow_width=sbar_arrow_width, sbar_frame_color=sbar_frame_color, sbar_relief=sbar_relief) @@ -4850,7 +5260,7 @@ class Button(Element): :type use_ttk_buttons: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: If True the return key will cause this button to be pressed + :param bind_return_key: If True then pressing the return key in an Input or Multiline Element will cause this button to appear to be clicked (generates event with this button's key :type bind_return_key: (bool) :param focus: if True, initial focus will be put on this button :type focus: (bool) @@ -5280,9 +5690,9 @@ class Button(Element): button_style.configure(style_name, background=bc[1]) else: if bc[0] not in (None, COLOR_SYSTEM_DEFAULT): - self.TKButton.config(foreground=bc[0], activeforeground=bc[0]) + self.TKButton.config(foreground=bc[0], activebackground=bc[0]) if bc[1] not in (None, COLOR_SYSTEM_DEFAULT): - self.TKButton.config(background=bc[1], activebackground=bc[1]) + self.TKButton.config(background=bc[1], activeforeground=bc[1]) self.ButtonColor = bc if disabled is True: self.TKButton['state'] = 'disabled' @@ -7042,7 +7452,7 @@ class Frame(Element): if type(element) == list: PopupError('Error creating Frame layout', 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', + 'This sometimes means you have a badly placed ]', 'The offensive list is:', element, 'This list will be stripped from your layout', @@ -7060,7 +7470,7 @@ class Frame(Element): continue if element.ParentContainer is not None: warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', + '*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', UserWarning) _error_popup_with_traceback('Error creating Frame layout', 'The layout specified has already been used', @@ -7381,7 +7791,7 @@ class Tab(Element): if type(element) == list: popup_error_with_traceback('Error creating Tab layout', 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', + 'This sometimes means you have a badly placed ]', 'The offensive list is:', element, 'This list will be stripped from your layout') @@ -7396,7 +7806,7 @@ class Tab(Element): continue if element.ParentContainer is not None: warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', + '*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', UserWarning) popup_error_with_traceback('Error creating Tab layout', 'The layout specified has already been used', @@ -7600,6 +8010,7 @@ class TabGroup(Element): self.Rows = [] self.TKNotebook = None # type: ttk.Notebook self.Widget = None # type: ttk.Notebook + self.tab_index_to_key = {} # has a list of the tabs in the notebook and their associated key self.TabCount = 0 self.BorderWidth = border_width self.BackgroundColor = background_color if background_color is not None else DEFAULT_BACKGROUND_COLOR @@ -7638,7 +8049,7 @@ class TabGroup(Element): if type(element) == list: PopupError('Error creating Tab layout', 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', + 'This sometimes means you have a badly placed ]', 'The offensive list is:', element, 'This list will be stripped from your layout', keep_on_top=True, image=_random_error_emoji() @@ -7654,7 +8065,7 @@ class TabGroup(Element): continue if element.ParentContainer is not None: warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', + '*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', UserWarning) PopupError('Error creating Tab layout', 'The layout specified has already been used', @@ -7726,25 +8137,39 @@ class TabGroup(Element): return element.Key return None + + def find_currently_active_tab_key(self): + """ + Returns the key for the currently active tab in this TabGroup + :return: Returns the key or None of no key found + :rtype: key | None + """ + try: + current_index = self.TKNotebook.index('current') + key = self.tab_index_to_key.get(current_index, None) + except: + key = None + + return key + def get(self): """ Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on the tab if no key is defined. Returns None if an error occurs. - Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you + Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you are using this method correctly? - :return: The key of the currently selected tab or the tab's text if it has no key + :return: The key of the currently selected tab or None if there is an error :rtype: Any | None """ try: - value = self.TKNotebook.tab(self.TKNotebook.index('current'))['text'] - tab_key = self.FindKeyFromTabName(value) - if tab_key is not None: - value = tab_key + current_index = self.TKNotebook.index('current') + key = self.tab_index_to_key.get(current_index, None) except: - value = None - return value + key = None + + return key def add_tab(self, tab_element): """ @@ -8285,7 +8710,7 @@ class Column(Element): if type(element) == list: PopupError('Error creating Column layout', 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', + 'This sometimes means you have a badly placed ]', 'The offensive list is:', element, 'This list will be stripped from your layout', keep_on_top=True, image=_random_error_emoji() @@ -8301,7 +8726,7 @@ class Column(Element): continue if element.ParentContainer is not None: warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', + '*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', UserWarning) PopupError('Error creating Column layout', 'The layout specified has already been used', @@ -9263,7 +9688,7 @@ class Table(Element): self.TKTreeview.selection_set(selections) # print(selections) self.SelectedRows = [int(x) - 1 for x in selections] - # print('The new selected rows = ', self.SelectedRows) + # print('The new selected rows = ', self.SelectedRows, 'selections =', selections) if self.enable_click_events is True: if self.Key is not None: self.ParentForm.LastButtonClicked = (self.Key, TABLE_CLICKED_INDICATOR, (row, column)) @@ -9276,15 +9701,15 @@ class Table(Element): def get(self): """ - Dummy function for tkinter port. In the Qt port you can read back the values in the table in case they were - edited. Don't know yet how to enable editing of a Tree in tkinter so just returning the values provided by - user when Table was created or Updated. + Get the selected rows using tktiner's selection method. Returns a list of the selected rows. - :return: the current table values (for now what was originally provided up updated) - :rtype: List[List[Any]] + :return: a list of the index of the selected rows (a list of ints) + :rtype: List[int] """ - return self.Values + selections = self.TKTreeview.selection() + selected_rows = [int(x) - 1 for x in selections] + return selected_rows def get_last_clicked_position(self): """ @@ -9313,7 +9738,7 @@ class Tree(Element): def __init__(self, data=None, headings=None, visible_column_map=None, col_widths=None, col0_width=10, col0_heading='', def_col_width=10, auto_size_columns=True, max_col_width=20, select_mode=None, show_expanded=False, - change_submits=False, enable_events=False, font=None, justification='right', text_color=None, border_width=None, + change_submits=False, enable_events=False, click_toggles_select=None, font=None, justification='right', text_color=None, border_width=None, background_color=None, selected_row_colors=(None, None), header_text_color=None, header_background_color=None, header_font=None, header_border_width=None, header_relief=None, num_rows=None, sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None, row_height=None, vertical_scroll_only=True, hide_vertical_scroll=False, pad=None, p=None, key=None, k=None, tooltip=None, @@ -9345,6 +9770,8 @@ class Tree(Element): :type change_submits: (bool) :param enable_events: Turns on the element specific events. Tree events happen when row is clicked :type enable_events: (bool) + :param click_toggles_select: If True then clicking a row will cause the selection for that row to toggle between selected and deselected + :type click_toggles_select: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) :param justification: 'left', 'right', 'center' are valid choices @@ -9427,7 +9854,7 @@ class Tree(Element): self.HeaderBorderWidth = header_border_width self.BorderWidth = border_width self.HeaderRelief = header_relief - + self.click_toggles_select = click_toggles_select if selected_row_colors == (None, None): # selected_row_colors = DEFAULT_TABLE_AND_TREE_SELECTED_ROW_COLORS selected_row_colors = theme_button_color() @@ -9478,8 +9905,14 @@ class Tree(Element): """ selections = self.TKTreeview.selection() - # self.SelectedRows = [x for x in selections] + selected_rows = [self.IdToKey[x] for x in selections] + if self.click_toggles_select: + if set(self.SelectedRows) == set(selected_rows): + for item in selections: + self.TKTreeview.selection_remove(item) + selections = [] self.SelectedRows = [self.IdToKey[x] for x in selections] + if self.ChangeSubmits: MyForm = self.ParentForm if self.Key is not None: @@ -9491,6 +9924,7 @@ class Tree(Element): # self.ParentForm.TKroot.quit() _exit_mainloop(self.ParentForm) + def add_treeview_data(self, node): """ Not a user function. Recursive method that inserts tree data into the tkinter treeview widget. @@ -9929,6 +10363,10 @@ class Window: _rerouted_stderr_stack = [] # type: List[Tuple[Window, Element]] _original_stdout = None _original_stderr = None + _watermark = None + _watermark_temp_forced = False + _watermark_user_text = '' + def __init__(self, title, layout=None, default_element_size=None, default_button_element_size=(None, None), auto_size_text=None, auto_size_buttons=None, location=(None, None), relative_location=(None, None), size=(None, None), @@ -9943,7 +10381,7 @@ class Window: finalize=False, element_justification='left', ttk_theme=None, use_ttk_buttons=None, modal=False, enable_close_attempted_event=False, enable_window_config_events=False, titlebar_background_color=None, titlebar_text_color=None, titlebar_font=None, titlebar_icon=None, use_custom_titlebar=None, scaling=None, - sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None, + sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None, watermark=None, metadata=None): """ :param title: The title that will be displayed in the Titlebar and on the Taskbar @@ -10066,6 +10504,8 @@ class Window: :type sbar_frame_color: (str) :param sbar_relief: Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID :type sbar_relief: (str) + :param watermark: If True, then turns on watermarking temporarily for ALL windows created from this point forward. See global settings doc for more info + :type watermark: bool :param metadata: User metadata that can be set to ANYTHING :type metadata: (Any) """ @@ -10203,6 +10643,15 @@ class Window: if self.use_custom_titlebar: self.Margins = (0, 0) self.NoTitleBar = True + self._mouse_offset_x = self._mouse_offset_y = 0 + + if watermark is True: + Window._watermark_temp_forced = True + _global_settings_get_watermark_info() + elif watermark is False: + Window._watermark = None + Window._watermark_temp_forced = False + self.ttk_part_overrides = TTKPartOverrides(sbar_trough_color=sbar_trough_color, sbar_background_color=sbar_background_color, sbar_arrow_color=sbar_arrow_color, sbar_width=sbar_width, sbar_arrow_width=sbar_arrow_width, sbar_frame_color=sbar_frame_color, sbar_relief=sbar_relief) @@ -10303,7 +10752,7 @@ class Window: continue _error_popup_with_traceback('Error creating Window layout', 'Layout has a LIST instead of an ELEMENT', - 'This means you have a badly placed ]', + 'This sometimes means you have a badly placed ]', 'The offensive list is:', element, 'This list will be stripped from your layout' @@ -10319,10 +10768,10 @@ class Window: continue if element.ParentContainer is not None: warnings.warn( - '*** YOU ARE ATTEMPTING TO RESUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', + '*** YOU ARE ATTEMPTING TO REUSE AN ELEMENT IN YOUR LAYOUT! Once placed in a layout, an element cannot be used in another layout. ***', UserWarning) _error_popup_with_traceback('Error detected in layout - Contains an element that has already been used.', - 'You have attempted to resuse an element in your layout.', + 'You have attempted to reuse an element in your layout.', "The layout specified has an element that's already been used.", 'You MUST start with a "clean", unused layout every time you create a window', 'The offensive Element = ', @@ -10363,6 +10812,12 @@ class Window: 'This item will be stripped from your layout') continue self.add_row(*row) + # if _optional_window_data(self) is not None: + # self.add_row(_optional_window_data(self)) + if Window._watermark is not None: + self.add_row(Window._watermark(self)) + + def layout(self, rows): """ @@ -10804,8 +11259,11 @@ class Window: # self.TKroot.protocol("WM_DESTROY_WINDOW", self._OnClosingCallback) # self.TKroot.protocol("WM_DELETE_WINDOW", self._OnClosingCallback) Window._window_running_mainloop = self - Window._root_running_mainloop.mainloop() - + try: + Window._root_running_mainloop.mainloop() + except: + print('**** EXITING ****') + exit(-1) # print('Out main') self.CurrentlyRunningMainloop = False # if self.LastButtonClicked != TIMEOUT_KEY: @@ -11315,8 +11773,8 @@ class Window: :param event: event information passed in by tkinter. Contains x,y position of mouse :type event: (event) """ - - self._StartMove(event) + self._start_move_save_offset(event) + return def _StartMoveGrabAnywhere(self, event): @@ -11330,31 +11788,11 @@ class Window: if (isinstance(event.widget, GRAB_ANYWHERE_IGNORE_THESE_WIDGETS) or event.widget in self._grab_anywhere_ignore_these_list) and event.widget not in self._grab_anywhere_include_these_list: # print('Found widget to ignore in grab anywhere...') return - - self._StartMove(event) - + self._start_move_save_offset(event) def _StartMove(self, event): - try: - geometry = self.TKroot.geometry() - location = geometry[geometry.find('+')+1:].split('+') - self._startx = int(location[0]) - self._starty = int(location[1]) - self._mousex = event.x + event.widget.winfo_rootx() - self._mousey = event.y + event.widget.winfo_rooty() - # print(self._startx, self._starty) - # print(self._mousex, self._mousey) - # print(self.TKroot.geometry()) - except: - pass - # print('Start move {},{} widget {}'.format(event.x,event.y, event.widget)) - - if Window._move_all_windows: - for window in Window._active_windows: - window._offsetx = event.x + event.widget.winfo_rootx() - window.TKroot.winfo_rootx() - window._offsety = event.y + event.widget.winfo_rooty() - window.TKroot.winfo_rooty() - - + self._start_move_save_offset(event) + return def _StopMove(self, event): """ @@ -11365,6 +11803,28 @@ class Window: """ return + def _start_move_save_offset(self, event): + self._mousex = event.x + event.widget.winfo_rootx() + self._mousey = event.y + event.widget.winfo_rooty() + geometry = self.TKroot.geometry() + location = geometry[geometry.find('+') + 1:].split('+') + self._startx = int(location[0]) + self._starty = int(location[1]) + self._mouse_offset_x = self._mousex - self._startx + self._mouse_offset_y = self._mousey - self._starty + # ------ Move All Windows code ------ + if Window._move_all_windows: + # print('Moving all') + for win in Window._active_windows: + if win == self: + continue + geometry = win.TKroot.geometry() + location = geometry[geometry.find('+') + 1:].split('+') + _startx = int(location[0]) + _starty = int(location[1]) + win._mouse_offset_x = event.x_root - _startx + win._mouse_offset_y = event.y_root - _starty + def _OnMotionUsingControlKey(self, event): self._OnMotion(event) @@ -11385,22 +11845,16 @@ class Window: def _OnMotion(self, event): - try: - _mousex = event.x + event.widget.winfo_rootx() - _mousey = event.y + event.widget.winfo_rooty() - deltax = _mousex - self._mousex - deltay = _mousey - self._mousey - x = self._startx + deltax - y = self._starty + deltay - self.TKroot.geometry("+%s+%s" % (x, y)) # this is what really moves the window + self.TKroot.geometry(f"+{event.x_root-self._mouse_offset_x}+{event.y_root-self._mouse_offset_y}") + # print(f"+{event.x_root}+{event.y_root}") + # ------ Move All Windows code ------ + try: if Window._move_all_windows: - for window in Window._active_windows: - deltax = window._offsetx - deltay = window._offsety - x = window.TKroot.winfo_pointerx() - deltax - y = window.TKroot.winfo_pointery() - deltay - window.TKroot.geometry("+%s+%s" % (x, y)) # this is what really moves the window + for win in Window._active_windows: + if win == self: + continue + win.TKroot.geometry(f"+{event.x_root-win._mouse_offset_x}+{event.y_root-win._mouse_offset_y}") except Exception as e: print('on motion error', e) @@ -12368,7 +12822,7 @@ class Window: return grab - def perform_long_operation(self, func, end_key): + def perform_long_operation(self, func, end_key=None): """ Call your function that will take a long time to execute. When it's complete, send an event specified by the end_key. @@ -12385,8 +12839,8 @@ class Window: :param func: A lambda or a function name with no parms :type func: Any - :param end_key: The key that will be generated when the function returns - :type end_key: (Any) + :param end_key: Optional key that will be generated when the function returns + :type end_key: (Any | None) :return: The id of the thread :rtype: threading.Thread """ @@ -12492,20 +12946,24 @@ class Window: :return: """ if key == TITLEBAR_MINIMIZE_KEY: - self._custom_titlebar_minimize() + if not self.DisableMinimize: + self._custom_titlebar_minimize() elif key == TITLEBAR_MAXIMIZE_KEY: - if self.maximized: - self.normal() - else: - self.maximize() + if self.Resizable: + if self.maximized: + self.normal() + else: + self.maximize() elif key == TITLEBAR_CLOSE_KEY: - self._OnClosingCallback() + if not self.DisableClose: + self._OnClosingCallback() def timer_start(self, frequency_ms, key=EVENT_TIMER, repeating=True): """ Starts a timer that gnerates Timer Events. The default is to repeat the timer events until timer is stopped. - You can provide your own key or a default key will be used. + You can provide your own key or a default key will be used. The default key is defined + with the constants EVENT_TIMER or TIMER_KEY. They both equal the same value. The values dictionary will contain the timer ID that is returned from this function. :param frequency_ms: How often to generate timer events in milliseconds @@ -12702,14 +13160,15 @@ def _long_func_thread(window, end_key, original_func): :param window: The window that will get the event :type window: (Window) - :param end_key: The event that will be sent when function returns - :type end_key: (Any) + :param end_key: The event that will be sent when function returns. If None then no event will be sent when exiting thread + :type end_key: (Any|None) :param original_func: The user's function that is called. Can be a function with no arguments or a lambda experession :type original_func: (Any) """ return_value = original_func() - window.write_event_value(end_key, return_value) + if end_key is not None: + window.write_event_value(end_key, return_value) def _exit_mainloop(exiting_window): @@ -13155,12 +13614,11 @@ def Sizer(h_pixels=0, v_pixels=0): :type h_pixels: (int) :param v_pixels: number of vertical pixels :type v_pixels: (int) - :return: (Column) A column element that has a pad setting set according to parameters - :rtype: (Column) + :return: (Canvas) A canvas element that has a pad setting set according to parameters + :rtype: (Canvas) """ - return Column([[]], pad=((h_pixels, 0), (v_pixels, 0))) - + return Canvas(size=(0, 0), pad=((h_pixels, 0), (v_pixels, 0))) def pin(elem, vertical_alignment=None, shrink=True, expand_x=None, expand_y=None): """ @@ -13696,7 +14154,7 @@ def Save(button_text='Save', size=(None, None), s=(None, None), auto_size_button :type auto_size_button: (bool) :param button_color: button color (foreground, background) :type button_color: (str, str) | str - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param disabled: set disable state for element (Default = False) :type disabled: (bool) @@ -13747,7 +14205,7 @@ def Submit(button_text='Submit', size=(None, None), s=(None, None), auto_size_bu :type button_color: (str, str) | str :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) @@ -13797,7 +14255,7 @@ def Open(button_text='Open', size=(None, None), s=(None, None), auto_size_button :type button_color: (str, str) | str :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) @@ -13846,7 +14304,7 @@ def OK(button_text='OK', size=(None, None), s=(None, None), auto_size_button=Non :type button_color: (str, str) | str :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) @@ -13895,7 +14353,7 @@ def Ok(button_text='Ok', size=(None, None), s=(None, None), auto_size_button=Non :type button_color: (str, str) | str :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) @@ -13948,7 +14406,7 @@ def Cancel(button_text='Cancel', size=(None, None), s=(None, None), auto_size_bu :type tooltip: (str) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: @@ -13997,7 +14455,7 @@ def Quit(button_text='Quit', size=(None, None), s=(None, None), auto_size_button :type tooltip: (str) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: (bool) @@ -14046,7 +14504,7 @@ def Exit(button_text='Exit', size=(None, None), s=(None, None), auto_size_button :type tooltip: (str) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: @@ -14095,7 +14553,7 @@ def Yes(button_text='Yes', size=(None, None), s=(None, None), auto_size_button=N :type tooltip: (str) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = True) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: @@ -14193,7 +14651,7 @@ def Help(button_text='Help', size=(None, None), s=(None, None), auto_size_button :type font: (str or (str, int[, str]) or None) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: @@ -14245,7 +14703,7 @@ def Debug(button_text='', size=(None, None), s=(None, None), auto_size_button=No :type font: (str or (str, int[, str]) or None) :param tooltip: text, that will appear when mouse hovers over the element :type tooltip: (str) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: @@ -14309,7 +14767,7 @@ def SimpleButton(button_text, image_filename=None, image_data=None, image_size=( :type button_color: (str, str) | str :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param disabled: set disable state for element (Default = False) :type disabled: (bool) @@ -14371,7 +14829,7 @@ def CloseButton(button_text, image_filename=None, image_data=None, image_size=(N :type button_color: (str, str) | str :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param disabled: set disable state for element (Default = False) :type disabled: (bool) @@ -14431,7 +14889,7 @@ def ReadButton(button_text, image_filename=None, image_data=None, image_size=(No :type button_color: (str, str) | str :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param disabled: set disable state for element (Default = False) :type disabled: (bool) @@ -14500,7 +14958,7 @@ def RealtimeButton(button_text, image_filename=None, image_data=None, image_size :type font: (str or (str, int[, str]) or None) :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: (bool) @@ -14568,7 +15026,7 @@ def DummyButton(button_text, image_filename=None, image_data=None, image_size=(N :type font: (str or (str, int[, str]) or None) :param disabled: set disable state for element (Default = False) :type disabled: (bool) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: if focus should be set to this :type focus: (bool) @@ -14641,7 +15099,7 @@ def CalendarButton(button_text, target=(ThisRow, -1), close_when_date_chosen=Tru :type disabled: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: (Default = False) If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: bool :param focus: if focus should be set to this :type focus: bool @@ -14657,6 +15115,8 @@ def CalendarButton(button_text, target=(ThisRow, -1), close_when_date_chosen=Tru :type locale: str :param format: formats result using this strftime format :type format: str + :param begin_at_sunday_plus: Determines the left-most day in the display. 0=sunday, 1=monday, etc + :type begin_at_sunday_plus: (int) :param month_names: optional list of month names to use (should be 12 items) :type month_names: List[str] :param day_abbreviations: optional list of abbreviations to display as the day of week @@ -14733,7 +15193,7 @@ def ColorChooserButton(button_text, target=(ThisRow, -1), image_filename=None, i :type disabled: (bool) :param font: specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike :type font: (str or (str, int[, str]) or None) - :param bind_return_key: If True, then the return key will cause a the Listbox to generate an event + :param bind_return_key: (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options :type bind_return_key: (bool) :param focus: Determines if initial focus should go to this element. :type focus: (bool) @@ -15053,7 +15513,8 @@ def _BuildResultsForSubform(form, initialize_only, top_level_form): elif element.Type == ELEM_TYPE_TAB_GROUP: try: value = element.TKNotebook.tab(element.TKNotebook.index('current'))['text'] - tab_key = element.FindKeyFromTabName(value) + tab_key = element.find_currently_active_tab_key() + # tab_key = element.FindKeyFromTabName(value) if tab_key is not None: value = tab_key except: @@ -16412,14 +16873,20 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKEntry.bind('', element._KeyboardHandler) element.TKEntry.bind('', element._ReturnKeyHandler) + if element.BackgroundColor not in (None, COLOR_SYSTEM_DEFAULT): element.TKEntry.configure(background=element.BackgroundColor, selectforeground=element.BackgroundColor) + if text_color not in (None, COLOR_SYSTEM_DEFAULT): element.TKEntry.configure(fg=text_color, selectbackground=text_color) - + element.TKEntry.config(insertbackground=text_color) + if element.selected_background_color not in (None, COLOR_SYSTEM_DEFAULT): + element.TKEntry.configure(selectbackground=element.selected_background_color) + if element.selected_text_color not in (None, COLOR_SYSTEM_DEFAULT): + element.TKEntry.configure(selectforeground=element.selected_text_color) if element.disabled_readonly_background_color not in (None, COLOR_SYSTEM_DEFAULT): element.TKEntry.config(readonlybackground=element.disabled_readonly_background_color) - if element.disabled_readonly_text_color not in (None, COLOR_SYSTEM_DEFAULT): + if element.disabled_readonly_text_color not in (None, COLOR_SYSTEM_DEFAULT) and element.Disabled: element.TKEntry.config(fg=element.disabled_readonly_text_color) element.Widget.config(highlightthickness=0) @@ -16441,8 +16908,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.Tooltip is not None: element.TooltipObject = ToolTip(element.TKEntry, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) _add_right_click_menu_and_grab(element) - if theme_input_text_color() not in (COLOR_SYSTEM_DEFAULT, None): - element.Widget.config(insertbackground=theme_input_text_color()) # row_should_expand = True @@ -16494,8 +16959,8 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): "Parent Window's Title: {}".format(toplevel_form.Title)) # Strange code that is needed to set the font for the drop-down list - element._newfont = tkinter.font.Font(font=font) - tk_row_frame.option_add("*TCombobox*Listbox*Font", element._newfont) + element._dropdown_newfont = tkinter.font.Font(font=font) + tk_row_frame.option_add("*TCombobox*Listbox*Font", element._dropdown_newfont) element.TKCombo = element.Widget = ttk.Combobox(tk_row_frame, width=width, textvariable=element.TKStringVar, font=font, style=style_name) @@ -16717,9 +17182,15 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.DefaultText: element.TKText.insert(1.0, element.DefaultText) # set the default text element.TKText.config(highlightthickness=0) + if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: + element.TKText.configure(fg=text_color, selectbackground=text_color) + element.TKText.config(insertbackground=text_color) if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: element.TKText.configure(background=element.BackgroundColor, selectforeground=element.BackgroundColor) - + if element.selected_background_color not in (None, COLOR_SYSTEM_DEFAULT): + element.TKText.configure(selectbackground=element.selected_background_color) + if element.selected_text_color not in (None, COLOR_SYSTEM_DEFAULT): + element.TKText.configure(selectforeground=element.selected_text_color) element.TKText.tag_configure("center", justify='center') element.TKText.tag_configure("left", justify='left') element.TKText.tag_configure("right", justify='right') @@ -16754,8 +17225,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.Focus is True or (toplevel_form.UseDefaultFocus and not toplevel_form.FocusSet): toplevel_form.FocusSet = True element.TKText.focus_set() - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKText.configure(fg=text_color, selectbackground=text_color) if element.Disabled is True: @@ -16767,8 +17236,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): cprint_set_output_destination(toplevel_form, element.Key) _add_right_click_menu_and_grab(element) - if theme_input_text_color() not in (COLOR_SYSTEM_DEFAULT, None): - element.Widget.config(insertbackground=theme_input_text_color()) if element.reroute_stdout: element.reroute_stdout_to_here() @@ -16798,8 +17265,9 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKCheckbutton.configure(activebackground=element.BackgroundColor) if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: element.TKCheckbutton.configure(fg=text_color) + element.TKCheckbutton.configure(activeforeground=element.TextColor) - element.Widget.configure(highlightthickness=1) + element.Widget.configure(highlightthickness=element.highlight_thickness) if element.BackgroundColor != COLOR_SYSTEM_DEFAULT: element.TKCheckbutton.config(highlightbackground=element.BackgroundColor) if element.TextColor != COLOR_SYSTEM_DEFAULT: @@ -16876,6 +17344,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TKRadio.configure(activebackground=element.BackgroundColor) if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: element.TKRadio.configure(fg=text_color) + element.TKRadio.configure(activeforeground=text_color) element.Widget.configure(highlightthickness=1) if element.BackgroundColor != COLOR_SYSTEM_DEFAULT: @@ -16907,6 +17376,9 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.BackgroundColor is not None and element.BackgroundColor != COLOR_SYSTEM_DEFAULT: element.TKSpinBox.configure(background=element.BackgroundColor) element.TKSpinBox.configure(buttonbackground=element.BackgroundColor) + if text_color not in (None, COLOR_SYSTEM_DEFAULT): + element.TKSpinBox.configure(fg=text_color) + element.TKSpinBox.config(insertbackground=text_color) element.Widget.config(highlightthickness=0) if element.wrap is True: element.Widget.configure(wrap=True) @@ -16915,8 +17387,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if element.visible is False: element._pack_forget_save_settings() # element.TKSpinBox.pack_forget() - if text_color is not None and text_color != COLOR_SYSTEM_DEFAULT: - element.TKSpinBox.configure(fg=text_color) if element.ChangeSubmits: element.TKSpinBox.configure(command=element._SpinboxSelectHandler) # element.TKSpinBox.bind('', element._SpinChangedHandler) @@ -16930,8 +17400,6 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.TooltipObject = ToolTip(element.TKSpinBox, text=element.Tooltip, timeout=DEFAULT_TOOLTIP_TIME) if element.BindReturnKey: element.TKSpinBox.bind('', element._SpinboxSelectHandler) - if theme_input_text_color() not in (COLOR_SYSTEM_DEFAULT, None): - element.Widget.config(insertbackground=theme_input_text_color()) _add_right_click_menu_and_grab(element) # ------------------------- IMAGE placement element ------------------------- # elif element_type == ELEM_TYPE_IMAGE: @@ -17118,6 +17586,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): # ------------------------- Tab placement element ------------------------- # elif element_type == ELEM_TYPE_TAB: element = element # type: Tab + form = form # type: TabGroup element.TKFrame = element.Widget = tk.Frame(form.TKNotebook) PackFormIntoFrame(element, element.TKFrame, toplevel_form) state = 'normal' @@ -17169,6 +17638,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): element.ParentNotebook = form.TKNotebook element.TabID = form.TabCount + form.tab_index_to_key[element.TabID] = element.key # has a list of the tabs in the notebook and their associated key form.TabCount += 1 if element.BackgroundColor not in (COLOR_SYSTEM_DEFAULT, None): element.TKFrame.configure(background=element.BackgroundColor, highlightbackground=element.BackgroundColor, highlightcolor=element.BackgroundColor) @@ -17335,6 +17805,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): headings = element.ColumnHeadings if element.ColumnHeadings is not None else element.Values[0] for i, heading in enumerate(headings): + # heading = str(heading) treeview.heading(heading, text=heading) if element.AutoSizeColumns: col_width = column_widths.get(i, len(heading)) # in case more headings than there are columns of data @@ -17411,10 +17882,17 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): table_style.configure(style_name, font=font) if element.BorderWidth is not None: table_style.configure(style_name, borderwidth=element.BorderWidth) + + if element.HeaderBackgroundColor not in (None, COLOR_SYSTEM_DEFAULT) and element.HeaderTextColor not in (None, COLOR_SYSTEM_DEFAULT): + table_style.map(style_name + ".Heading", background=[('pressed', '!focus', element.HeaderBackgroundColor), + ('active', element.HeaderTextColor),]) + table_style.map(style_name + ".Heading", foreground=[('pressed', '!focus', element.HeaderTextColor), + ('active', element.HeaderBackgroundColor),]) + treeview.configure(style=style_name) # scrollable_frame.pack(side=tk.LEFT, padx=elementpad[0], pady=elementpad[1], expand=True, fill='both') if element.enable_click_events is True: - treeview.bind('', element._table_clicked) + treeview.bind('', element._table_clicked) if element.right_click_selects: if running_mac(): treeview.bind('', element._table_clicked) @@ -17504,7 +17982,8 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): column_headings = element.ColumnHeadings # ------------- GET THE TREEVIEW WIDGET ------------- element.TKTreeview = element.Widget = ttk.Treeview(element_frame, columns=column_headings, - displaycolumns=displaycolumns, show='tree headings', + displaycolumns=displaycolumns, + show='tree headings' if column_headings is not None else 'tree', height=height, selectmode=element.SelectMode) treeview = element.TKTreeview @@ -17515,19 +17994,19 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form): if len(str(value)) > max_width: max_widths[i] = len(str(value)) - - for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings - treeview.heading(heading, text=heading) - if element.AutoSizeColumns: - max_width = max_widths.get(i, 0) - max_width = max(max_width, len(heading)) - width = min(element.MaxColumnWidth, max_width+1) - else: - try: - width = element.ColumnWidths[i] - except: - width = element.DefaultColumnWidth - treeview.column(heading, width=width * _char_width_in_pixels(font) + 10, anchor=anchor) + if element.ColumnHeadings is not None: + for i, heading in enumerate(element.ColumnHeadings): # Configure cols + headings + treeview.heading(heading, text=heading) + if element.AutoSizeColumns: + max_width = max_widths.get(i, 0) + max_width = max(max_width, len(heading)) + width = min(element.MaxColumnWidth, max_width+1) + else: + try: + width = element.ColumnWidths[i] + except: + width = element.DefaultColumnWidth + treeview.column(heading, width=width * _char_width_in_pixels(font) + 10, anchor=anchor) def add_treeview_data(node): """ @@ -17964,7 +18443,10 @@ def StartupTK(window): # for the Raspberry Pi. Need to set the attributes here, prior to the building of the window # so going ahead and doing it for all platforms, in addition to doing it after the window is packed - _no_titlebar_setup(window) + # 2023-April - this call seems to be causing problems on MacOS 13.2.1 Ventura. Input elements become non-responsive + # if this call is made here and at the end of building the window + if not running_mac(): + _no_titlebar_setup(window) if not window.Resizable: root.resizable(False, False) @@ -18882,7 +19364,7 @@ def set_options(icon=None, button_color=None, element_size=(None, None), button_ enable_mac_notitlebar_patch=None, use_custom_titlebar=None, titlebar_background_color=None, titlebar_text_color=None, titlebar_font=None, titlebar_icon=None, user_settings_path=None, pysimplegui_settings_path=None, pysimplegui_settings_filename=None, keep_on_top=None, dpi_awareness=None, scaling=None, disable_modal_windows=None, force_modal_windows=None, tooltip_offset=(None, None), sbar_trough_color=None, sbar_background_color=None, sbar_arrow_color=None, sbar_width=None, sbar_arrow_width=None, sbar_frame_color=None, sbar_relief=None, alpha_channel=None, - hide_window_when_creating=None, use_button_shortcuts=None): + hide_window_when_creating=None, use_button_shortcuts=None, watermark_text=None): """ :param icon: Can be either a filename or Base64 value. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO. Most portable is to use a Base64 of a PNG file. This works universally across all OS's :type icon: bytes | str @@ -19016,6 +19498,8 @@ def set_options(icon=None, button_color=None, element_size=(None, None), button_ :type hide_window_when_creating: (bool) :param use_button_shortcuts: If True then Shortcut Char will be used with Buttons :type use_button_shortcuts: (bool) + :param watermark_text: Set the text that will be used if a window is watermarked + :type watermark_text: (str) :return: None :rtype: None """ @@ -19293,6 +19777,10 @@ def set_options(icon=None, button_color=None, element_size=(None, None), button_ if use_button_shortcuts is not None: DEFAULT_USE_BUTTON_SHORTCUTS = use_button_shortcuts + + if watermark_text is not None: + Window._watermark_user_text = watermark_text + return True @@ -19347,6 +19835,8 @@ LOOK_AND_FEEL_TABLE = { "PROGRESS": DEFAULT_PROGRESS_BAR_COMPUTE, "BORDER": 1, "SLIDER_DEPTH": 0, "PROGRESS_DEPTH": 0, }, "Black": {"BACKGROUND": "#000000", "TEXT": "#FFFFFF", "INPUT": "#4D4D4D", "TEXT_INPUT": "#FFFFFF", "SCROLL": "#707070", "BUTTON": ("#000000", "#FFFFFF"), "PROGRESS": DEFAULT_PROGRESS_BAR_COMPUTE, "BORDER": 1, "SLIDER_DEPTH": 0, "PROGRESS_DEPTH": 0, }, + "Black2": {"BACKGROUND": "#000000", "TEXT": "#FFFFFF", "INPUT": "#000000", "TEXT_INPUT": "#FFFFFF", "SCROLL": "#FFFFFF", "BUTTON": ("#000000", "#FFFFFF"), + "PROGRESS": DEFAULT_PROGRESS_BAR_COMPUTE, "BORDER": 1, "SLIDER_DEPTH": 0, "PROGRESS_DEPTH": 0, }, "Tan": {"BACKGROUND": "#fdf6e3", "TEXT": "#268bd1", "INPUT": "#eee8d5", "TEXT_INPUT": "#6c71c3", "SCROLL": "#eee8d5", "BUTTON": ("#FFFFFF", "#063542"), "PROGRESS": DEFAULT_PROGRESS_BAR_COMPUTE, "BORDER": 1, "SLIDER_DEPTH": 0, "PROGRESS_DEPTH": 0, }, "TanBlue": {"BACKGROUND": "#e5dece", "TEXT": "#063289", "INPUT": "#f9f8f4", "TEXT_INPUT": "#242834", "SCROLL": "#eee8d5", "BUTTON": ("#FFFFFF", "#063289"), @@ -21618,6 +22108,8 @@ def popup_get_file(message, title=None, default_path='', default_extension='', s if not multiple_files and type(filename) in (tuple, list): if len(filename): # only if not 0 length, otherwise will get an error filename = filename[0] + if not filename: + return None return filename if save_as: @@ -21969,7 +22461,7 @@ def popup_get_date(start_mon=None, start_day=None, start_year=None, begin_at_sun # --------------------------- PopupAnimated --------------------------- def popup_animated(image_source, message=None, background_color=None, text_color=None, font=None, no_titlebar=True, grab_anywhere=True, keep_on_top=True, - location=(None, None), relative_location=(None, None), alpha_channel=None, time_between_frames=0, transparent_color=None, title='', icon=None): + location=(None, None), relative_location=(None, None), alpha_channel=None, time_between_frames=0, transparent_color=None, title='', icon=None, no_buffering=False): """ Show animation one frame at a time. This function has its own internal clocking meaning you can call it at any frequency and the rate the frames of video is shown remains constant. Maybe your frames update every 30 ms but your @@ -22006,6 +22498,8 @@ def popup_animated(image_source, message=None, background_color=None, text_color :type title: (str) :param icon: Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO :type icon: str | bytes + :param no_buffering: If True then no buffering will be used for the GIF. May work better if you have a large animation + :type no_buffering: (bool) :return: True if the window updated OK. False if the window was closed :rtype: bool """ @@ -22031,7 +22525,10 @@ def popup_animated(image_source, message=None, background_color=None, text_color Window._animated_popup_dict[image_source] = window else: window = Window._animated_popup_dict[image_source] - window['-IMAGE-'].update_animation(image_source, time_between_frames=time_between_frames) + if no_buffering: + window['-IMAGE-'].update_animation_no_buffering(image_source, time_between_frames=time_between_frames) + else: + window['-IMAGE-'].update_animation(image_source, time_between_frames=time_between_frames) event, values = window.read(1) if event == WIN_CLOSED: return False @@ -22200,6 +22697,7 @@ def _error_popup_with_code(title, filename, line_num, *args, emoji=None): :param emoji: An optional BASE64 Encoded image to shows in the error window :type emoji: bytes """ + editor_filename = execute_get_editor() emoji_data = emoji if emoji is not None else _random_error_emoji() layout = [[Text('ERROR'), Text(title)], [Image(data=emoji_data)]] @@ -22216,9 +22714,9 @@ def _error_popup_with_code(title, filename, line_num, *args, emoji=None): max_line_len = max(max_line_len, max([len(s) for s in line])) layout += [[Text(''.join(line), size=(min(max_line_len, 90), None))] for line in lines] - - layout += [[Button('Close'), Button('Take me to error'), Button('Kill Application', button_color='white on red')]] - + layout += [[Button('Close'), Button('Take me to error', disabled=True if not editor_filename else False), Button('Kill Application', button_color='white on red')]] + if not editor_filename: + layout += [[Text('Configure editor in the Global settings to enable "Take me to error" feature')]] window = Window(title, layout, keep_on_top=True) while True: @@ -23200,7 +23698,7 @@ def execute_command_subprocess(command, *args, wait=False, cwd=None, pipe_output The function will immediately return without waiting for the process to complete running. You can use the returned Popen object to communicate with the subprocess and get the results. Returns a subprocess Popen object. - :param command: Filename to load settings from (and save to in the future) + :param command: The command/file to execute. What you would type at a console to run a program or shell command. :type command: (str) :param *args: Variable number of arguments that are passed to the program being started as command line parms :type *args: (Any) @@ -23481,7 +23979,7 @@ def _create_full_editor_command(file_to_edit, line_number, edit_format_string): return command -def _get_editor(): +def execute_get_editor(): """ Get the path to the editor based on user settings or on PySimpleGUI's global settings @@ -23570,13 +24068,17 @@ def _mac_should_set_alpha_to_99(): if not ENABLE_MAC_ALPHA_99_PATCH: return False + # ONLY enable this patch for tkinter version 8.6.12 + if framework_version != '8.6.12': + return False + # At this point, we're running a Mac and the alpha patch is enabled # Final check is to see if Mac OS version is 12.3 or later try: platform_mac_ver = platform.mac_ver()[0] mac_ver = platform_mac_ver.split('.') if '.' in platform_mac_ver else (platform_mac_ver, 0) if (int(mac_ver[0]) >= 12 and int(mac_ver[1]) >= 3) or int(mac_ver[0]) >= 13 : - print("Mac OS Version is {} and patch enabled so applying the patch".format(platform_mac_ver)) + # print("Mac OS Version is {} and patch enabled so applying the patch".format(platform_mac_ver)) return True except Exception as e: warnings.warn('_mac_should_seet_alpha_to_99 Exception while trying check mac_ver. Error = {}'.format(e), UserWarning) @@ -23643,26 +24145,28 @@ def main_mac_feature_control(): red_x = b"R0lGODlhEAAQAPeQAIsAAI0AAI4AAI8AAJIAAJUAAJQCApkAAJoAAJ4AAJkJCaAAAKYAAKcAAKcCAKcDA6cGAKgAAKsAAKsCAKwAAK0AAK8AAK4CAK8DAqUJAKULAKwLALAAALEAALIAALMAALMDALQAALUAALYAALcEALoAALsAALsCALwAAL8AALkJAL4NAL8NAKoTAKwbAbEQALMVAL0QAL0RAKsREaodHbkQELMsALg2ALk3ALs+ALE2FbgpKbA1Nbc1Nb44N8AAAMIWAMsvAMUgDMcxAKVABb9NBbVJErFYEq1iMrtoMr5kP8BKAMFLAMxKANBBANFCANJFANFEB9JKAMFcANFZANZcANpfAMJUEMZVEc5hAM5pAMluBdRsANR8AM9YOrdERMpIQs1UVMR5WNt8X8VgYMdlZcxtYtx4YNF/btp9eraNf9qXXNCCZsyLeNSLd8SSecySf82kd9qqc9uBgdyBgd+EhN6JgtSIiNuJieGHhOGLg+GKhOKamty1ste4sNO+ueenp+inp+HHrebGrefKuOPTzejWzera1O7b1vLb2/bl4vTu7fbw7ffx7vnz8f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAJAALAAAAAAQABAAAAjUACEJHEiwYEEABniQKfNFgQCDkATQwAMokEU+PQgUFDAjjR09e/LUmUNnh8aBCcCgUeRmzBkzie6EeQBAoAAMXuA8ciRGCaJHfXzUMCAQgYooWN48anTokR8dQk4sELggBhQrU9Q8evSHiJQgLCIIfMDCSZUjhbYuQkLFCRAMAiOQGGLE0CNBcZYmaRIDLqQFGF60eTRoSxc5jwjhACFWIAgMLtgUocJFy5orL0IQRHAiQgsbRZYswbEhBIiCCH6EiJAhAwQMKU5DjHCi9gnZEHMTDAgAOw==" -COLOR_SCHEME = 'dark grey 13' -DEBUGGER_POPOUT_THEME = 'dark grey 13' -WIDTH_VARIABLES = 23 -WIDTH_RESULTS = 46 -WIDTH_WATCHER_VARIABLES = 20 -WIDTH_WATCHER_RESULTS = 60 - -WIDTH_LOCALS = 80 -NUM_AUTO_WATCH = 9 - -MAX_LINES_PER_RESULT_FLOATING = 4 -MAX_LINES_PER_RESULT_MAIN = 3 - -POPOUT_WINDOW_FONT = 'Sans 8' -DEBUGGER_VARIABLE_DETAILS_FONT = 'Courier 10' class _Debugger: debugger = None + DEBUGGER_MAIN_WINDOW_THEME = 'dark grey 13' + DEBUGGER_POPOUT_THEME = 'dark grey 13' + WIDTH_VARIABLES = 23 + WIDTH_RESULTS = 46 + + WIDTH_WATCHER_VARIABLES = 20 + WIDTH_WATCHER_RESULTS = 60 + + WIDTH_LOCALS = 80 + NUM_AUTO_WATCH = 9 + + MAX_LINES_PER_RESULT_FLOATING = 4 + MAX_LINES_PER_RESULT_MAIN = 3 + + DEBUGGER_POPOUT_WINDOW_FONT = 'Sans 8' + DEBUGGER_VARIABLE_DETAILS_FONT = 'Courier 10' + ''' # # ###### ## ## ## # # # # # ###### ##### # # #### #### ###### ##### @@ -23685,12 +24189,12 @@ class _Debugger: # Includes the DUAL PANE (now 2 tabs)! Don't forget REPL is there too! def _build_main_debugger_window(self, location=(None, None)): old_theme = theme() - theme(COLOR_SCHEME) + theme(_Debugger.DEBUGGER_MAIN_WINDOW_THEME) def InVar(key1): row1 = [T(' '), - I(key=key1, size=(WIDTH_VARIABLES, 1)), - T('', key=key1 + 'CHANGED_', size=(WIDTH_RESULTS, 1)), B('Detail', key=key1 + 'DETAIL_'), + I(key=key1, size=(_Debugger.WIDTH_VARIABLES, 1)), + T('', key=key1 + 'CHANGED_', size=(_Debugger.WIDTH_RESULTS, 1)), B('Detail', key=key1 + 'DETAIL_'), B('Obj', key=key1 + 'OBJ_'), ] return row1 @@ -23711,10 +24215,9 @@ class _Debugger: Button('Popout', key='-POPOUT-')]] var_layout = [] - for i in range(NUM_AUTO_WATCH): - var_layout.append([T('', size=(WIDTH_WATCHER_VARIABLES, 1), key='_WATCH%s_' % i), - T('', size=(WIDTH_WATCHER_RESULTS, MAX_LINES_PER_RESULT_MAIN), key='_WATCH%s_RESULT_' % i, - )]) + for i in range(_Debugger.NUM_AUTO_WATCH): + var_layout.append([T('', size=(_Debugger.WIDTH_WATCHER_VARIABLES, 1), key='_WATCH%s_' % i), + T('', size=(_Debugger.WIDTH_WATCHER_RESULTS, _Debugger.MAX_LINES_PER_RESULT_MAIN), key='_WATCH%s_RESULT_' % i,)]) col1 = [ # [Frame('Auto Watches', autowatch_frame+variable_values, title_color='blue')] @@ -23731,8 +24234,7 @@ class _Debugger: [TabGroup([[Tab('Variables', col1), Tab('REPL & Watches', col2)]])]] # ------------------------------- Create main window ------------------------------- - window = Window("PySimpleGUI Debugger", layout, icon=PSG_DEBUGGER_LOGO, margins=(0, 0), location=location, keep_on_top=True, - right_click_menu=[[''], ['Exit', ]]) + window = Window("PySimpleGUI Debugger", layout, icon=PSG_DEBUGGER_LOGO, margins=(0, 0), location=location, keep_on_top=True, right_click_menu=[[''], ['Exit', ]]) Window._read_call_from_debugger = True window.finalize() @@ -23790,7 +24292,10 @@ class _Debugger: result = str(eval(str(var), myglobals, mylocals)) except: result = '' - popup_scrolled(str(values['_VAR{}_'.format(event[4])]) + '\n' + result, title=var, non_blocking=True, font=DEBUGGER_VARIABLE_DETAILS_FONT) + old_theme = theme() + theme(_Debugger.DEBUGGER_MAIN_WINDOW_THEME) + popup_scrolled(str(values['_VAR{}_'.format(event[4])]) + '\n' + result, title=var, non_blocking=True, font=_Debugger.DEBUGGER_VARIABLE_DETAILS_FONT) + theme(old_theme) # BUTTON - OBJ elif event.endswith('_OBJ_'): # OBJECT BUTTON var = values['_VAR{}_'.format(event[4])] @@ -23802,17 +24307,20 @@ class _Debugger: result = ObjToStringSingleObj(result) except Exception as e: result = '{}\nError showing object {}'.format(e, var) - popup_scrolled(str(var) + '\n' + str(result), title=var, non_blocking=True, font=DEBUGGER_VARIABLE_DETAILS_FONT) + old_theme = theme() + theme(_Debugger.DEBUGGER_MAIN_WINDOW_THEME) + popup_scrolled(str(var) + '\n' + str(result), title=var, non_blocking=True, font=_Debugger.DEBUGGER_VARIABLE_DETAILS_FONT) + theme(old_theme) # ------------------------------- Process Watch Tab ------------------------------- # BUTTON - Choose Locals to see elif event == '-LOCALS-': # Show all locals BUTTON self._choose_auto_watches(mylocals) # BUTTON - Locals (quick popup) elif event == '-ALL_LOCALS-': - self._display_all_vars(mylocals) + self._display_all_vars('All Locals', mylocals) # BUTTON - Globals (quick popup) elif event == '-GLOBALS-': - self._display_all_vars(myglobals) + self._display_all_vars('All Globals', myglobals) # BUTTON - clear all elif event == 'Clear All Auto Watches': if popup_yes_no('Do you really want to clear all Auto-Watches?', 'Really Clear??') == 'Yes': @@ -23855,7 +24363,7 @@ class _Debugger: self.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update('') slot += 1 - if slot + int(not self.custom_watch in (None, '')) >= NUM_AUTO_WATCH: + if slot + int(not self.custom_watch in (None, '')) >= _Debugger.NUM_AUTO_WATCH: break # If a custom watch was set, display that value in the window if self.custom_watch: @@ -23867,7 +24375,7 @@ class _Debugger: self.watcher_window.Element('_WATCH{}_RESULT_'.format(slot)).Update(self.myrc) slot += 1 # blank out all of the slots not used (blank) - for i in range(slot, NUM_AUTO_WATCH): + for i in range(slot, _Debugger.NUM_AUTO_WATCH): self.watcher_window.Element('_WATCH{}_'.format(i)).Update('') self.watcher_window.Element('_WATCH{}_RESULT_'.format(i)).Update('') @@ -23916,12 +24424,12 @@ class _Debugger: ''' # displays them into a single text box - def _display_all_vars(self, dict): + def _display_all_vars(self, title, dict): num_cols = 3 output_text = '' num_lines = 2 cur_col = 0 - out_text = 'All of your Vars' + out_text = title + '\n' longest_line = max([len(key) for key in dict]) line = [] sorted_dict = {} @@ -23929,15 +24437,19 @@ class _Debugger: sorted_dict[key] = dict[key] for key in sorted_dict: value = dict[key] - wrapped_list = textwrap.wrap(str(value), 60) - wrapped_text = '\n'.join(wrapped_list) + # wrapped_list = textwrap.wrap(str(value), 60) + # wrapped_text = '\n'.join(wrapped_list) + wrapped_text = str(value) out_text += '{} - {}\n'.format(key, wrapped_text) - if cur_col + 1 == num_cols: - cur_col = 0 - num_lines += len(wrapped_list) - else: - cur_col += 1 - popup_scrolled(out_text, non_blocking=True) + # if cur_col + 1 == num_cols: + # cur_col = 0 + # num_lines += len(wrapped_list) + # else: + # cur_col += 1 + old_theme = theme() + theme(_Debugger.DEBUGGER_MAIN_WINDOW_THEME) + popup_scrolled(out_text, title=title, non_blocking=True, font=_Debugger.DEBUGGER_VARIABLE_DETAILS_FONT, keep_on_top=True, icon=PSG_DEBUGGER_LOGO) + theme(old_theme) ''' ##### # # @@ -23959,7 +24471,7 @@ class _Debugger: def _choose_auto_watches(self, my_locals): old_theme = theme() - theme(COLOR_SCHEME) + theme(_Debugger.DEBUGGER_MAIN_WINDOW_THEME) num_cols = 3 output_text = '' num_lines = 2 @@ -23987,7 +24499,7 @@ class _Debugger: layout += [ [Ok(), Cancel(), Button('Clear All'), Button('Select [almost] All', key='-AUTO_SELECT-')]] - window = Window('All Locals', layout, icon=PSG_DEBUGGER_LOGO, finalize=True) + window = Window('Choose Watches', layout, icon=PSG_DEBUGGER_LOGO, finalize=True, keep_on_top=True) while True: # event loop event, values = window.read() @@ -24042,7 +24554,7 @@ class _Debugger: if self.popout_window: # if floating window already exists, close it first self.popout_window.Close() old_theme = theme() - theme(DEBUGGER_POPOUT_THEME) + theme(_Debugger.DEBUGGER_POPOUT_THEME) num_cols = 2 width_var = 15 width_value = 30 @@ -24059,10 +24571,10 @@ class _Debugger: for key in self.popout_choices: if self.popout_choices[key] is True: value = str(self.locals.get(key)) - h = min(len(value) // width_value + 1, MAX_LINES_PER_RESULT_FLOATING) - line += [Text('{}'.format(key), size=(width_var, 1), font=POPOUT_WINDOW_FONT), - Text(' = ', font=POPOUT_WINDOW_FONT), - Text(value, key=key, size=(width_value, h), font=POPOUT_WINDOW_FONT)] + h = min(len(value) // width_value + 1, _Debugger.MAX_LINES_PER_RESULT_FLOATING) + line += [Text('{}'.format(key), size=(width_var, 1), font=_Debugger.DEBUGGER_POPOUT_WINDOW_FONT), + Text(' = ', font=_Debugger.DEBUGGER_POPOUT_WINDOW_FONT), + Text(value, key=key, size=(width_value, h), font=_Debugger.DEBUGGER_POPOUT_WINDOW_FONT)] if col + 1 < num_cols: line += [VerticalSeparator(), T(' ')] col += 1 @@ -24519,6 +25031,203 @@ RED_X_BASE64 = b'iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAAA4qEECAAAQ5ElEQVR4nO1ca3S GREEN_CHECK_BASE64 = b'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' + +''' +M""MMMMM""M dP +M MMMMM M 88 +M MMMMM M 88d888b. .d8888b. 88d888b. .d8888b. .d888b88 .d8888b. +M MMMMM M 88' `88 88' `88 88' `88 88' `88 88' `88 88ooood8 +M `MMM' M 88. .88 88. .88 88 88. .88 88. .88 88. ... +Mb dM 88Y888P' `8888P88 dP `88888P8 `88888P8 `88888P' +MMMMMMMMMMM 88 .88 + dP d8888P +MP""""""`MM oo +M mmmmm..M +M. `YM .d8888b. 88d888b. dP .dP dP .d8888b. .d8888b. +MMMMMMM. M 88ooood8 88' `88 88 d8' 88 88' `"" 88ooood8 +M. .MMM' M 88. ... 88 88 .88' 88 88. ... 88. ... +Mb. .dM `88888P' dP 8888P' dP `88888P' `88888P' +MMMMMMMMMMM +''' + +__upgrade_server_ip = 'upgradeapi.PySimpleGUI.com' +__upgrade_server_port = '5353' + + +def __send_dict(ip, port, dict_to_send): + """ + Send a dictionary to the upgrade server and get back a dictionary in response + :param ip: ip address of the upgrade server + :type ip: str + :param port: port number + :type port: int | str + :param dict_to_send: dictionary of items to send + :type dict_to_send: dict + :return: dictionary that is the reply + :rtype: dict + """ + + # print(f'sending dictionary to ip {ip} port {port}') + try: + # Create a socket object + s = socket.socket() + + s.settimeout(5.0) # set a 5 second timeout + + # connect to the server on local computer + s.connect((ip , int(port))) + # send a python dictionary + s.send(json.dumps(dict_to_send).encode()) + + # receive data from the server + reply_data = s.recv(1024).decode() + # close the connection + s.close() + except Exception as e: + # print(f'Error sending to server:', e) + # print(f'payload:\n', dict_to_send) + reply_data = e + try: + data_dict = json.loads(reply_data) + except Exception as e: + # print(f'UPGRADE THREAD - Error decoding reply {reply_data} as a dictionary. Error = {e}') + data_dict = {} + return data_dict + +def __show_previous_upgrade_information(): + """ + Shows information about upgrades if upgrade information is waiting to be shown + + :return: + """ + + # if nothing to show, then just return + if pysimplegui_user_settings.get('-upgrade info seen-', True) and not pysimplegui_user_settings.get('-upgrade info available-', False): + return + if pysimplegui_user_settings.get('-upgrade show only critical-', False) and pysimplegui_user_settings.get('-severity level-', '') != 'Critical': + return + + message1 = pysimplegui_user_settings.get('-upgrade message 1-', '') + message2 = pysimplegui_user_settings.get('-upgrade message 2-', '') + recommended_version = pysimplegui_user_settings.get('-upgrade recommendation-', '') + severity_level = pysimplegui_user_settings.get('-severity level-', '') + + if severity_level != 'Critical': + return + + layout = [[Image(EMOJI_BASE64_HAPPY_THUMBS_UP), T('An upgrade is available & recommended', font='_ 14')], + [T('It is recommended you upgrade to version {}'.format(recommended_version))], + [T(message1, enable_events=True, k='-MESSAGE 1-')], + [T(message2, enable_events=True, k='-MESSAGE 2-')], + [CB('Do not show this message again in the future', default=True, k='-SKIP IN FUTURE-')], + [B('Close'), T('This window auto-closes in'), T('30', k='-CLOSE TXT-', text_color='white', background_color='red'), T('seconds')]] + + window = Window('PySimpleGUI Intelligent Upgrade', layout, finalize=True) + if 'http' in message1: + window['-MESSAGE 1-'].set_cursor('hand1') + if 'http' in message2: + window['-MESSAGE 2-'].set_cursor('hand1') + + seconds_left=30 + while True: + event, values = window.read(timeout=1000) + if event in ('Close', WIN_CLOSED) or seconds_left < 1: + break + if values['-SKIP IN FUTURE-']: + if not running_trinket(): + pysimplegui_user_settings['-upgrade info available-'] = False + pysimplegui_user_settings['-upgrade info seen-'] = True + if event == '-MESSAGE 1-' and 'http' in message1 and webbrowser_available: + webbrowser.open_new_tab(message1) + elif event == '-MESSAGE 2-' and 'http' in message2 and webbrowser_available: + webbrowser.open_new_tab(message2) + window['-CLOSE TXT-'].update(seconds_left) + seconds_left -= 1 + + window.close() + + +def __get_linux_distribution(): + line_tuple = ('Linux Distro', 'Unknown', 'No lines Found in //etc//os-release') + try: + with open('/etc/os-release') as f: + data = f.read() + lines = data.split('\n') + for line in lines: + if line.startswith('PRETTY_NAME'): + line_split = line.split('=')[1].strip('"') + line_tuple = tuple(line_split.split(' ')) + return line_tuple + except: + line_tuple = ('Linux Distro', 'Exception','Error reading//processing //etc//os-release') + + return line_tuple + + +def __perform_upgrade_check_thread(): + # print(f'Upgrade thread...seen = {pysimplegui_user_settings.get("-upgrade info seen-", False)}') + try: + if running_trinket(): + os_name = 'Trinket' + os_ver = __get_linux_distribution() + elif running_replit(): + os_name = 'REPL.IT' + os_ver = __get_linux_distribution() + elif running_windows(): + os_name = 'Windows' + os_ver = platform.win32_ver() + elif running_linux(): + os_name = 'Linux' + os_ver = __get_linux_distribution() + elif running_mac(): + os_name = 'Mac' + os_ver = platform.mac_ver() + else: + os_name = 'Other' + os_ver = '' + + psg_ver = version + framework_ver = framework_version + python_ver = sys.version + + upgrade_dict = { + 'OSName' : str(os_name), + 'OSVersion' : str(os_ver), + 'PythonVersion' : str(python_ver), + 'PSGVersion' : str(psg_ver), + 'FrameworkName' : 'tkinter', + 'FrameworkVersion' : str(framework_ver), + } + reply_data = __send_dict(__upgrade_server_ip, __upgrade_server_port, upgrade_dict) + + recommended_version = reply_data.get('SuggestedVersion', '') + message1 = reply_data.get('Message1', '') + message2 = reply_data.get('Message2', '') + severity_level = reply_data.get('SeverityLevel', '') + # If any part of the reply has changed from the last reply, overwrite the data and set flags so user will be informed + if (message1 or message2) and not running_trinket(): + if pysimplegui_user_settings.get('-upgrade message 1-', '') != message1 or \ + pysimplegui_user_settings.get('-upgrade message 2-', '') != message2 or \ + pysimplegui_user_settings.get('-upgrade recommendation-', '') != recommended_version or \ + pysimplegui_user_settings.get('-severity level-', '') != severity_level: + # Save the data to the settings file + pysimplegui_user_settings['-upgrade info seen-'] = False + pysimplegui_user_settings['-upgrade info available-'] = True + pysimplegui_user_settings['-upgrade message 1-'] = message1 + pysimplegui_user_settings['-upgrade message 2-'] = message2 + pysimplegui_user_settings['-upgrade recommendation-'] = recommended_version + pysimplegui_user_settings['-severity level-'] = severity_level + except Exception as e: + reply_data = {} + # print('Upgrade server error', e) + # print(f'Upgrade Reply = {reply_data}') + +def __perform_upgrade_check(): + # For now, do not show data returned. Still testing and do not want to "SPAM" users with any popups + __show_previous_upgrade_information() + threading.Thread(target=lambda: __perform_upgrade_check_thread(), daemon=True).start() + + # =========================================================================# # MP""""""`MM dP dP # M mmmmm..M 88 88 @@ -25348,6 +26057,27 @@ def _global_settings_get_ttk_scrollbar_info(): DEFAULT_TTK_THEME = pysimplegui_user_settings.get('-ttk theme-', DEFAULT_TTK_THEME) +def _global_settings_get_watermark_info(): + if not pysimplegui_user_settings.get('-watermark-', False) and not Window._watermark_temp_forced: + Window._watermark = None + return + forced = Window._watermark_temp_forced + prefix_text = pysimplegui_user_settings.get('-watermark text-', '') + + ver_text = ' ' + version.split(" ", 1)[0] if pysimplegui_user_settings.get('-watermark ver-', False if not forced else True) or forced else '' + framework_ver_text = ' Tk ' + framework_version if pysimplegui_user_settings.get('-watermark framework ver-', False if not forced else True) or forced else '' + watermark_font = pysimplegui_user_settings.get('-watermark font-', '_ 9 bold') + # background_color = pysimplegui_user_settings.get('-watermark bg color-', 'window.BackgroundColor') + user_text = pysimplegui_user_settings.get('-watermark text-', '') + python_text = ' Py {}.{}.{}'.format(sys.version_info.major, sys.version_info.minor, sys.version_info.micro) + if user_text: + text = str(user_text) + else: + text = prefix_text + ver_text + python_text + framework_ver_text + Window._watermark = lambda window: Text(text, font=watermark_font, background_color= window.BackgroundColor) + + + def main_global_get_screen_snapshot_symcode(): pysimplegui_user_settings = UserSettings(filename=DEFAULT_USER_SETTINGS_PYSIMPLEGUI_FILENAME, path=DEFAULT_USER_SETTINGS_PYSIMPLEGUI_PATH) @@ -25484,15 +26214,20 @@ def main_global_pysimplegui_settings(): theme_tab = Tab('Theme', [[T('Leave blank for "official" PySimpleGUI default theme: {}'.format(OFFICIAL_PYSIMPLEGUI_THEME))], [T('Default Theme For All Programs:'), - Combo([''] + theme_list(), settings.get('-theme-', None), readonly=True, k='-THEME-', tooltip=tooltip_theme), Checkbox('Always use custom Titlebar', default=pysimplegui_user_settings.get('-custom titlebar-',False), k='-CUSTOM TITLEBAR-')]], - font='_ 16', expand_x=True) + Combo([''] + theme_list(), settings.get('-theme-', None), readonly=True, k='-THEME-', tooltip=tooltip_theme), Checkbox('Always use custom Titlebar', default=pysimplegui_user_settings.get('-custom titlebar-',False), k='-CUSTOM TITLEBAR-')], + [Frame('Window Watermarking', + [[Checkbox('Enable Window Watermarking', pysimplegui_user_settings.get('-watermark-', False), k='-WATERMARK-')], + [T('Prefix Text String:'), Input(pysimplegui_user_settings.get('-watermark text-', ''), k='-WATERMARK TEXT-')], + [Checkbox('PySimpleGUI Version', pysimplegui_user_settings.get('-watermark ver-', False), k='-WATERMARK VER-')], + [Checkbox('Framework Version',pysimplegui_user_settings.get('-watermark framework ver-', False), k='-WATERMARK FRAMEWORK VER-')], + [T('Font:'), Input(pysimplegui_user_settings.get('-watermark font-', '_ 9 bold'), k='-WATERMARK FONT-')], + # [T('Background Color:'), Input(pysimplegui_user_settings.get('-watermark bg color-', 'window.BackgroundColor'), k='-WATERMARK BG COLOR-')], + ], + font='_ 16', expand_x=True)]]) - # ------------------------- Security Tab ------------------------- - security_tab = Tab('Security', - [[T('PySimpleGUI hashcode')], [T(scheck_hh())]], - expand_x=True) - settings_tab_group = TabGroup([[theme_tab, ttk_tab, interpreter_tab, explorer_tab, editor_tab, snapshots_tab, security_tab ]]) + + settings_tab_group = TabGroup([[theme_tab, ttk_tab, interpreter_tab, explorer_tab, editor_tab, snapshots_tab, ]]) layout += [[settings_tab_group]] # [T('Buttons (Leave Unchecked To Use Default) NOT YET IMPLEMENTED!', font='_ 16')], # [Checkbox('Always use TTK buttons'), CBox('Always use TK Buttons')], @@ -25517,6 +26252,12 @@ def main_global_pysimplegui_settings(): pysimplegui_user_settings.set('-python command-', values['-PYTHON COMMAND-']) pysimplegui_user_settings.set('-custom titlebar-', values['-CUSTOM TITLEBAR-']) pysimplegui_user_settings.set('-theme-', new_theme) + pysimplegui_user_settings.set('-watermark-', values['-WATERMARK-']) + pysimplegui_user_settings.set('-watermark text-', values['-WATERMARK TEXT-']) + pysimplegui_user_settings.set('-watermark ver-', values['-WATERMARK VER-']) + pysimplegui_user_settings.set('-watermark framework ver-', values['-WATERMARK FRAMEWORK VER-']) + pysimplegui_user_settings.set('-watermark font-', values['-WATERMARK FONT-']) + # pysimplegui_user_settings.set('-watermark bg color-', values['-WATERMARK BG COLOR-']) # TTK SETTINGS pysimplegui_user_settings.set('-ttk theme-', values['-TTK THEME-']) @@ -25544,9 +26285,15 @@ def main_global_pysimplegui_settings(): if key[0] == '-TTK SCROLL-': pysimplegui_user_settings.set(json.dumps(('-ttk scroll-', key[1])), value) + # Upgrade Service Settings + pysimplegui_user_settings.set('-upgrade show only critical-', values['-UPGRADE SHOW ONLY CRITICAL-']) + + + theme(new_theme) _global_settings_get_ttk_scrollbar_info() + _global_settings_get_watermark_info() window.close() return True @@ -25570,6 +26317,7 @@ def main_global_pysimplegui_settings(): for i in range(100): Print(i, keep_on_top=True) Print('Close this window to continue...', keep_on_top=True) + window.close() # In case some of the settings were modified and tried out, reset the ttk info to be what's in the config file style = ttk.Style(Window.hidden_master_root) @@ -25911,14 +26659,23 @@ def _create_main_window(): frame6 = [[VPush()],[graph_elem]] - global_settings_tab_layout = [[T('Settings Filename:'), T(pysimplegui_user_settings.full_filename, s=(50,2))], - [T('Settings Dictionary:'), MLine(pysimplegui_user_settings, size=(50,8), write_only=True)], - ] - themes_tab_layout = [[T('You can see a preview of the themes, the color swatches, or switch themes for this window')], [T('If you want to change the default theme for PySimpleGUI, use the Global Settings')], [B('Themes'), B('Theme Swatches'), B('Switch Themes')]] + + upgrade_recommendation_tab_layout = [[T('Latest Recommendation and Announcements For You', font='_ 14')], + [T('Severity Level of Update:'), T(pysimplegui_user_settings.get('-severity level-',''))], + [T('Recommended Version To Upgrade To:'), T(pysimplegui_user_settings.get('-upgrade recommendation-',''))], + [T(pysimplegui_user_settings.get('-upgrade message 1-',''))], + [T(pysimplegui_user_settings.get('-upgrade message 2-',''))], + [Checkbox('Show Only Critical Messages', default=pysimplegui_user_settings.get('-upgrade show only critical-', False), key='-UPGRADE SHOW ONLY CRITICAL-', enable_events=True)], + [Button('Show Notification Again'), +], + ] + tab_upgrade = Tab('Upgrade\n',upgrade_recommendation_tab_layout, expand_x=True) + + tab1 = Tab('Graph\n', frame6, tooltip='Graph is in here', title_color='red') tab2 = Tab('CB, Radio\nList, Combo', [[Frame('Multiple Choice Group', frame2, title_color='#FFFFFF', tooltip='Checkboxes, radio buttons, etc', vertical_alignment='t',), @@ -25930,7 +26687,6 @@ def _create_main_window(): tab6 = Tab('Course or\nSponsor', frame7, k='-TAB SPONSOR-') tab7 = Tab('Popups\n', pop_test_tab_layout, k='-TAB POPUP-') tab8 = Tab('Themes\n', themes_tab_layout, k='-TAB THEMES-') - tab9 = Tab('Global\nSettings', global_settings_tab_layout, k='-TAB GlOBAL SETTINGS-') def VerLine(version, description, justification='r', size=(40, 1)): return [T(version, justification=justification, font='Any 12', text_color='yellow', size=size, pad=(0,0)), T(description, font='Any 12', pad=(0,0))] @@ -25949,7 +26705,7 @@ def _create_main_window(): layout_bottom = [ [B(SYMBOL_DOWN, pad=(0, 0), k='-HIDE TABS-'), - pin(Col([[TabGroup([[tab1, tab2, tab3, tab6, tab4, tab5, tab7, tab8, tab9]], key='-TAB_GROUP-')]], k='-TAB GROUP COL-'))], + pin(Col([[TabGroup([[tab1, tab2, tab3, tab6, tab4, tab5, tab7, tab8, tab_upgrade]], key='-TAB_GROUP-')]], k='-TAB GROUP COL-'))], [B('Button', highlight_colors=('yellow', 'red'),pad=(1, 0)), B('ttk Button', use_ttk_buttons=True, tooltip='This is a TTK Button',pad=(1, 0)), B('See-through Mode', tooltip='Make the background transparent',pad=(1, 0)), @@ -26052,7 +26808,7 @@ def main(): elif event.startswith('See'): window._see_through = not window._see_through window.set_transparent_color(theme_background_color() if window._see_through else '') - elif event == '-INSTALL-': + elif event in ('-INSTALL-', '-UPGRADE FROM GITHUB-'): _upgrade_gui() elif event == 'Popup': popup('This is your basic popup', keep_on_top=True) @@ -26065,7 +26821,7 @@ def main(): elif event == 'Get Text': popup_scrolled('Returned:', popup_get_text('Enter some text', keep_on_top=True)) elif event.startswith('-UDEMY-'): - webbrowser.open_new_tab(r'https://www.udemy.com/course/pysimplegui/?couponCode=266B9C51C90B3728782E') + webbrowser.open_new_tab(r'https://www.udemy.com/course/pysimplegui/?couponCode=522B20BF5EF123C4AB30') elif event.startswith('-SPONSOR-'): if webbrowser_available: webbrowser.open_new_tab(r'https://www.paypal.me/pythongui') @@ -26073,9 +26829,8 @@ def main(): if webbrowser_available: webbrowser.open_new_tab(r'https://www.buymeacoffee.com/PySimpleGUI') elif event in ('-EMOJI-HEARTS-', '-HEART-', '-PYTHON HEARTS-'): - popup_scrolled("Oh look! It's a Udemy discount coupon!", '266B9C51C90B3728782E', + popup_scrolled("Oh look! It's a Udemy discount coupon!", '522B20BF5EF123C4AB30', 'A personal message from Mike -- thank you so very much for supporting PySimpleGUI!', title='Udemy Coupon', image=EMOJI_BASE64_MIKE, keep_on_top=True) - elif event == 'Themes': search_string = popup_get_text('Enter a search term or leave blank for all themes', 'Show Available Themes', keep_on_top=True) if search_string is not None: @@ -26123,6 +26878,15 @@ def main(): window.minimize() main_open_github_issue() window.normal() + elif event == 'Show Notification Again': + if not running_trinket(): + pysimplegui_user_settings.set('-upgrade info seen-', False) + __show_previous_upgrade_information() + elif event == '-UPGRADE SHOW ONLY CRITICAL-': + if not running_trinket(): + pysimplegui_user_settings.set('-upgrade show only critical-', values['-UPGRADE SHOW ONLY CRITICAL-']) + + i += 1 # _refresh_debugger() print('event = ', event) @@ -26185,6 +26949,18 @@ TimerStop = timer_stop test = main sdk_help = main_sdk_help +def _optional_window_data(window): + """ + A function to help with testing PySimpleGUI releases. Makes it easier to add a watermarked line to the bottom + of a window while testing release candidates. + + :param window: + :type window: Window + :return: An element that will be added to the bottom of the layout + :rtype: None | Element + """ + return None + pysimplegui_user_settings = UserSettings(filename=DEFAULT_USER_SETTINGS_PYSIMPLEGUI_FILENAME, path=DEFAULT_USER_SETTINGS_PYSIMPLEGUI_PATH) # ------------------------ Set the "Official PySimpleGUI Theme Colors" ------------------------ @@ -26193,6 +26969,9 @@ theme(theme_global()) # ------------------------ Read the ttk scrollbar info ------------------------ _global_settings_get_ttk_scrollbar_info() +# ------------------------ Read the window watermark info ------------------------ +_global_settings_get_watermark_info() + # See if running on Trinket. If Trinket, then use custom titlebars since Trinket doesn't supply any if running_trinket(): USE_CUSTOM_TITLEBAR = True @@ -26212,15 +26991,13 @@ if running_windows(): _read_mac_global_settings() if _mac_should_set_alpha_to_99(): - print('Applyting Mac OS 12.3+ Alpha Channel fix. Your default Alpha Channel is now 0.99') + # Applyting Mac OS 12.3+ Alpha Channel fix. Sets the default Alpha Channel to 0.99 set_options(alpha_channel=0.99) -# if running_mac(): -# print('Your Mac patches are:') -# print('Modal windows disabled:', ENABLE_MAC_MODAL_DISABLE_PATCH) -# print('No titlebar patch:', ENABLE_MAC_NOTITLEBAR_PATCH) -# print ('No grab anywhere allowed with titlebar:', ENABLE_MAC_DISABLE_GRAB_ANYWHERE_WITH_TITLEBAR) -# print('Currently the no titlebar patch ' + ('WILL' if _mac_should_apply_notitlebar_patch() else 'WILL NOT') + ' be applied') - + + +__perform_upgrade_check() + + # -------------------------------- ENTRY POINT IF RUN STANDALONE -------------------------------- # if __name__ == '__main__': # To execute the upgrade from command line, type: @@ -26233,4 +27010,4 @@ if __name__ == '__main__': exit(0) main() exit(0) -#2d2045fc87ee17cd939d4e616b04275ddcce76fd8db3487270058eb7ccb30c1dbab79d6c7956da4eefa99fedf57f849d7106d75dbf09aaa1e372ccc051d85418f14c6b9252fa3870cb5d88023398f9d503145049ec9f9f148650f10040ae8ff41a79915066663d75df7bc63827711503d6e0a2f6b5f1f15ad8d8517f7a5cfa04bb7614ca65fa6ef8a3a0f4d5f8a0b432bb60f20dc1b3cd68dd047e5c00a93930732be95921a4ae79f4dbb1ccb9c34de276e7e704a7f179a72be7e14a4d537463807e0b73ab8a040e33918e596374af841c5e9a6b9cd350280e3d3ab577f247bdafdccf659b26b231322973c78fdb0464d5b93ffac5e0a177dce4291d73ea535c01b579812d7929f0e8b3a3ace84524d2e55ee617171782de6866bb7e219d5f46ee3ecd4821207a224880c0826e70fd2e00292bcb034a8b61cc8782ea035c4e566337b4ba7e5ebf018450042966f99c68a1fd4cfa1649042ef3ccaac8a0c472d0d81da274d4028246660ad6b16334ff3b8d77096dbb7640a92a7127713443e7a1c184542f8069c04f4e6528b19766f26f3eac9743f3d16558789d1579ecfc955e4d7ff7ed78aa1f2e33b25e73c7751bd267947bc4643992e030523cab2d8c025f38bec65c270a036d4fe76f6a19d729f9b544f155d3a775c944615ed0bf87cf04515bfe51fe5d1ec4ffe39c0c04ea0861fe91d385aa42fa97bb7f2d898e186ae0 \ No newline at end of file +#25424909a31c4fa789f5aa4e210e7e07d412560195dc21abe678b68a3b4bdb2a8a78651d8613daaded730bc2a31adc02ba8b99717fff701cda8ae13c31f1dcee9da8837908626f1c5cc81e7a34d3b9cd032dba190647564bba72d248ad6b83e30c8abc057f3f1b1fb3a2ca853069de936f3f53522fd4732b743268e0fcde54577a05880f2057efe6bbd6349f77d6c002544f38e24db40ab84f3dde4a4b8b31e84480db31656fb74ae0c01a7af0b35ac66cf8a0fbb8ca85685fea075608c7862da6635511d0e5403c4a637138324ce1fb1308b765cba53863ddf7b01ca4fc988932b03c4a8403a72b8105f821913f02925218dbecf1e089bd32e78667939503f2abfd89b37fa293927e30550d441f21dc68273d2d07ed910f6a69bc8c792015eb623ada7e65347cf0389cf2a1696a7ccf88098a4fb4bfa44e88fac2a94a44e25b010355e48d483d896c58eb771ef47e01066156f9344750b487e176ca0642601951f096d4c03045aa8f912d475dbe04b82c6ddf1ac3adbf815aef4ca2c6add058c2789b66a9abd875f334752ec1bde11b9b56e334823304b6cc3fadf7daae277c982ebc7eadb726a33e2740d075ad082b9c20304c4a53228d6f05357c40903a78113aea4e6169e1a5351866f7a9ffc6666eb08a31bfb84d90cb3002f7ebf87871988b88a7b8a52d36a1a7dd826360b5c6ad922829d9f73d204f09d1b9ad9ffd8d \ No newline at end of file diff --git a/docs/Screens.md b/docs/Screens.md index 04c74418..23a4da7f 100644 --- a/docs/Screens.md +++ b/docs/Screens.md @@ -1,3 +1,156 @@ +[splatert](https://github.com/splatert) 2023-11-19T04:48:55Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/749bd2e5-d6fa-4a83-8508-a04aabc63202) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/3beb849e-63b0-4d55-a541-116c8789f364) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/16c8b7a0-3b55-40c0-9f82-13d73797374f) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/758d4b52-9cb1-4aa7-982f-953168ba5fed) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/970cddfc-3aa9-4932-8f38-8c12f44a0fae) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/cf85ee38-17c4-4f19-b473-4c0427c3d203) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-11-07T14:22:15Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/1cedfd51-46ca-4fef-bfa3-2b05dfba09bc) + +----------- + +[SaSp73](https://github.com/SaSp73) 2023-11-07T09:35:57Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/145710363/eedb2ea9-7df5-47ab-b0cb-0894f10c0753) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-10-26T09:38:54Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/c68c3fdd-3bc3-4654-9cef-7b8cf1565443) + +----------- + +[ikeman32](https://github.com/ikeman32) 2023-10-25T21:58:09Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/fdddd0e4-6654-41c0-a6c7-a9262c903e8d) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/f61d5e2b-7178-4008-8fad-6ae482aa5cfc) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/90dac6a7-92b0-4e34-9813-aa557d4a47e7) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-10-21T16:11:14Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/c2881407-2cd6-43ee-86e5-10005caed189) + +----------- + +[onyx-and-iris](https://github.com/onyx-and-iris) 2023-09-06T00:00:13Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/55352169/f99212f5-1870-4387-ba4c-159dd40d4c8e) + +----------- + +[definite-d](https://github.com/definite-d) 2023-08-26T16:34:23Z + +![launcher_dark](https://github.com/PySimpleGUI/PySimpleGUI/assets/38317208/579bc2be-abaa-4fd6-a1ea-609ada3be1a9) +![editor_dark](https://github.com/PySimpleGUI/PySimpleGUI/assets/38317208/725fd3bd-4147-4b45-a034-c85b5bd3d74d) + +----------- + +[luisegarduno](https://github.com/luisegarduno) 2023-06-25T05:18:40Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/30121656/80b10f6e-30f7-4159-8574-5f79bff705b1) + +----------- + +[mrtnbm](https://github.com/mrtnbm) 2023-05-25T16:24:22Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/1a234b86-9457-4d10-8080-ea064e915397) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/faf9adc7-6de7-42c7-b405-05e5bf081095) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/55a72eec-56d7-4442-b660-b42e122be488) + +----------- + +[eagleEggs](https://github.com/eagleEggs) 2023-05-24T04:03:28Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/29800532/5751e594-4748-431b-a1b2-76b1b7f346db) + +----------- + +[gnuchanos](https://github.com/gnuchanos) 2023-05-24T02:35:07Z + +![2023-05-24-053345_1280x1024_scrot](https://github.com/PySimpleGUI/PySimpleGUI/assets/117280480/acb7b360-af3a-4ec1-a226-a35d76676167) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-05-12T09:55:03Z + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/98b78824-5286-4183-bb92-0d6fc5405270) + +----------- + +[zaricj](https://github.com/zaricj) 2023-04-19T09:55:26Z + +![Chocolatey-GUI](https://user-images.githubusercontent.com/93329694/233038905-5a3b1f42-4794-4c6e-8411-f70f8fa79723.png) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-03-16T18:56:49Z + +![image](https://user-images.githubusercontent.com/46163555/225724235-81b435c8-84f2-48e8-9ba0-9353f6179517.png) + +----------- + +[lucasmartins19](https://github.com/lucasmartins19) 2023-03-14T21:50:57Z + +![image](https://user-images.githubusercontent.com/85654292/225143962-28c12d61-2475-47ca-ab54-85ebd5b2824b.png) +![image](https://user-images.githubusercontent.com/85654292/225144198-85d79b35-23d5-440b-91b3-bb1838fda88a.png) +![image](https://user-images.githubusercontent.com/85654292/225144622-d87c5e80-ba7c-469a-9664-4e94d8550e75.png) +![image](https://user-images.githubusercontent.com/85654292/225145054-9ea8b5c2-b8b0-481c-b065-da8af4a2ea0f.png) +![image](https://user-images.githubusercontent.com/85654292/225147555-7417e84f-248b-40e0-b55e-4096ec82534a.png) +![image](https://user-images.githubusercontent.com/85654292/225147854-4570747c-1d3c-4878-ba40-04ec70af1c0f.png) +![image](https://user-images.githubusercontent.com/85654292/225145887-782775e3-e44f-45ba-aaa2-b318ea241002.png) + +----------- + +[J-Josu](https://github.com/J-Josu) 2023-02-14T12:29:42Z + +![image](https://user-images.githubusercontent.com/46163555/217663277-1e69dfcf-c816-4930-aada-610b157eaf0a.png) + +----------- + +[chanon-kr](https://github.com/chanon-kr) 2023-02-14T06:27:10Z + +![image](https://user-images.githubusercontent.com/64777509/218656441-a53a0b28-76ac-4be0-a90b-3f4e57a71a56.png) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-02-08T22:16:35Z + +![image](https://user-images.githubusercontent.com/46163555/217663277-1e69dfcf-c816-4930-aada-610b157eaf0a.png) + +----------- + +[J-Josu](https://github.com/J-Josu) 2023-01-30T13:20:08Z + +![image](https://user-images.githubusercontent.com/92873227/215487552-cd9a9185-5ca1-4c63-8c4e-b084a1fce2e5.png) +![image](https://user-images.githubusercontent.com/92873227/215487639-4facc1ba-2c5f-430f-ac41-2cceb4f52708.png) +![image](https://user-images.githubusercontent.com/92873227/215487687-61a880c8-13f2-4641-80c9-455a8378a9a3.png) +![image](https://user-images.githubusercontent.com/92873227/215487759-278903f6-de06-4d47-b016-d0f5324ad6e2.png) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-01-29T15:31:26Z + +![image](https://user-images.githubusercontent.com/46163555/215336720-2cc04845-ecf1-4c80-8769-8d8d92026c93.png) +![image](https://user-images.githubusercontent.com/46163555/215336743-7519cbb7-d700-47b3-8c6f-b2abb29fdea2.png) + +----------- + +[eagleEggs](https://github.com/eagleEggs) 2023-01-28T02:30:37Z + +![image](https://user-images.githubusercontent.com/29800532/215237562-60eb47d3-b5da-4880-8b38-53f33c185f74.png) +![image](https://user-images.githubusercontent.com/29800532/215237481-117ba32e-a94e-480b-9c89-8d66922b7bff.png) + +----------- + [hseera](https://github.com/hseera) 2023-01-04T23:30:35Z ![image](https://user-images.githubusercontent.com/59352356/210669262-7f74f961-5294-43b7-885e-d931d995569a.png) @@ -5,13 +158,13 @@ ----------- -[kubilayyalcinyt](https://github.com/kubilayyalcinyt) 2022-12-18T21:41:57Z +[gnuchanos](https://github.com/gnuchanos) 2022-12-18T21:41:57Z ![2022-12-19-003757_1280x1012_scrot](https://user-images.githubusercontent.com/117280480/208321005-c731df95-a680-4fb7-8682-5c9b86bd6afb.png) ----------- -[kubilayyalcinyt](https://github.com/kubilayyalcinyt) 2022-12-18T19:36:12Z +[gnuchanos](https://github.com/gnuchanos) 2022-12-18T19:36:12Z ![displayManager](https://user-images.githubusercontent.com/117280480/208315826-fc2172ae-349d-42ba-9cdc-3c828b7e4c26.png) ![gnuchanOS_Window_Manager](https://user-images.githubusercontent.com/117280480/208315827-1e8eb182-ea3d-4cb7-8c3f-a093ca58d8ec.png) @@ -585,7 +738,7 @@ ----------- -[vohe](https://github.com/vohe) 2022-01-23T13:53:09Z +[ghost](https://github.com/ghost) 2022-01-23T13:53:09Z ![AudioRecScreenshot](https://user-images.githubusercontent.com/7021635/150681690-1874c8c0-4464-46a4-ae0f-748a618fb845.png) @@ -735,7 +888,7 @@ ----------- -[Scania-Creations-16](https://github.com/Scania-Creations-16) 2021-11-04T02:55:59Z +[Pranav-P-16](https://github.com/Pranav-P-16) 2021-11-04T02:55:59Z ![yt1](https://user-images.githubusercontent.com/91425738/140249931-76fdda21-fbdf-42a0-81a7-add95ab122b6.png) ![yt2](https://user-images.githubusercontent.com/91425738/140249937-26fba679-b813-471e-a09c-f81ff1a7a53d.png) @@ -744,7 +897,7 @@ ----------- -[Scania-Creations-16](https://github.com/Scania-Creations-16) 2021-10-31T04:14:25Z +[Pranav-P-16](https://github.com/Pranav-P-16) 2021-10-31T04:14:25Z ![mp1](https://user-images.githubusercontent.com/91425738/139567280-a9b18a68-e052-414a-898c-7537c90f2aac.png) ![mp2](https://user-images.githubusercontent.com/91425738/139567295-35c8d463-2a27-4724-94e6-c21c85aa55e4.png) @@ -1081,13 +1234,13 @@ ----------- -[vohe](https://github.com/vohe) 2020-08-08T11:11:29Z +[ghost](https://github.com/ghost) 2020-08-08T11:11:29Z ![Picture](https://user-images.githubusercontent.com/7021635/89708548-82ae8600-d978-11ea-8a8d-59b24e092830.png) ----------- -[vohe](https://github.com/vohe) 2020-08-05T13:31:05Z +[ghost](https://github.com/ghost) 2020-08-05T13:31:05Z ![Popup Directory](https://user-images.githubusercontent.com/7021635/89418628-9c5d8c80-d730-11ea-9f6f-42d190cfdbf5.png) ![Popup Directory2](https://user-images.githubusercontent.com/7021635/89418636-9e275000-d730-11ea-8de6-f02c4a304701.png) @@ -1100,13 +1253,13 @@ ----------- -[vohe](https://github.com/vohe) 2020-08-02T17:28:23Z +[ghost](https://github.com/ghost) 2020-08-02T17:28:23Z ![mockupall](https://user-images.githubusercontent.com/7021635/89128448-ec9cda80-d4f5-11ea-965d-05cef5844f8f.png) ----------- -[vohe](https://github.com/vohe) 2020-07-05T16:45:42Z +[ghost](https://github.com/ghost) 2020-07-05T16:45:42Z ![english](https://user-images.githubusercontent.com/7021635/86537489-b61c7180-beef-11ea-9b65-4d11b0a4d515.png) ![german](https://user-images.githubusercontent.com/7021635/86537492-b7e63500-beef-11ea-8b41-e79d1cbbc904.png) @@ -1114,7 +1267,7 @@ ----------- -[vohe](https://github.com/vohe) 2020-07-04T15:43:31Z +[ghost](https://github.com/ghost) 2020-07-04T15:43:31Z ![Audiorecorder (spotify)](https://user-images.githubusercontent.com/7021635/86515989-6a00fc80-be1d-11ea-89c9-08411c829eb7.png) diff --git a/docs/Screens2.md b/docs/Screens2.md index cdef5da7..43e8598c 100644 --- a/docs/Screens2.md +++ b/docs/Screens2.md @@ -1,3 +1,380 @@ +[splatert](https://github.com/splatert) 2023-11-19T04:48:55Z +Hello. I would like to thank you for the awesome UI framework you built as it really does help create interfaces in a really simple manner. I've given credit to you on my project's readme file and provided a link that leads to your github page. + +With the power of your library, I've created an alternative UI frontend for SpotDL. A tool for downloading Spotify tracks using URLs that you provide. Now SpotDL does have their own interface which is loaded onto the web browser but It felt like it was slow as it took a couple of seconds to start up, which is why I wanted to create my own. + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/749bd2e5-d6fa-4a83-8508-a04aabc63202) + +The way this frontend is used is that you provide each link into the interface's textbox then hit the plus button to pass them to a list of URLs (which then the listbox shown above displays the urls list). + +Upon providing URLs, you then press the download button to initiate the download process (given that you provide the SpotDL executable). + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/3beb849e-63b0-4d55-a541-116c8789f364) +If the control shown above is checked, the program would ask you to provide a name for the folder you want to create and send downloaded songs to. Prompt dialog is shown below. + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/16c8b7a0-3b55-40c0-9f82-13d73797374f) +Pressing **OK** will assign the folder name to a string variable then tell the program that you want to create a folder. +Pressing **Cancel** or having the checkbox mentioned above unmarked would not tell the program to create any folders. + + +After information is provided, the program executes SpotDL with the links you entered passed as arguments and will wait for it to complete the download job. +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/758d4b52-9cb1-4aa7-982f-953168ba5fed) + + +After the download job is finished, songs get transferred over to the frontend's music directory and you will get a message saying that the download process has been completed. +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/970cddfc-3aa9-4932-8f38-8c12f44a0fae) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/82643571/cf85ee38-17c4-4f19-b473-4c0427c3d203) + + +Here's a link to the project repository. +https://github.com/splatert/spotdl-ui + +----------- + +[SaSp73](https://github.com/SaSp73) 2023-11-07T15:52:58Z +Thanks for your kind words. I will send you the code in a couple of days when I return back home. +It need some extra hardware (PICAN-M board, Adafruit ADC and BME280 barometer), but you will manage to get around this with a little bit of tinkering. +Basically the whole control is a graph with elements redrawing according to the calculated data... + +BTW, a Rotate_Figure(figure_id, center_point, degrees_of_rotation) would be very helpful (I will open a suggestion later) + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-11-07T14:22:15Z +WOW @SaSp73 !! + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/1cedfd51-46ca-4fef-bfa3-2b05dfba09bc) + +Truly mind-blowing and very inspiring to see. I've never seen anything like this done with PySimpleGUI! One of the things I love about this project are the surprises by what people create. + +I would love to see how you did some of the things you did if you ever care to share the code (even privately would be great.... I'll be confidential. I'm curious what features were helpful). + +Thank you so so much for sharing. + + + +----------- + +[kcl1s](https://github.com/kcl1s) 2023-11-07T11:01:09Z +Wow +Those graphics are fantastic. Thanks for sharing. + + +----------- + +[SaSp73](https://github.com/SaSp73) 2023-11-07T09:35:57Z +This is my use of PySimpleGUI, a program reading NMEA200 networks, running on raspberry pi 4, and presenting various data used for sail racing. +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/145710363/eedb2ea9-7df5-47ab-b0cb-0894f10c0753) + + + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-10-26T09:38:54Z +We're on very similar wavelengths @ikeman32. It's great to see some add-on tools to help users. + +As part of the PySimpleGUI 5 release of the PySimpleGUI applications and add-on modules, I've written a GUI tool that creates all of the necessary files to upload a PySimpleGUI application to PyPI, opening up distribution of Python applications to PySimpleGUI users. We've been releasing tools via PyPI for some time. "psgresizer" is a good example. Users can pip install it and then once installed it can be run by typing "psgresizer" from the command line. There's already a tool, psgshortcut, that takes it all a start further by making shortcuts (icons) that can be pinned to the taskbar or double-clicked. + +The idea is to make distribution easy as well as the result be familiar feeling. The command line isn't a normal part of a normal Windows user's world, so getting it out of the way entirely makes a lot of sense. I've just about got the final tool finished that will make it all work end to end. + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/c68c3fdd-3bc3-4654-9cef-7b8cf1565443) + + +----------- + +[ikeman32](https://github.com/ikeman32) 2023-10-25T21:58:09Z +I have created a simple bash script to initialize a PySimpleGUI project. It can be found here: https://github.com/ikeman32/ScriptHelpers/tree/main/devscripts + +It's very basic at the moment, but functional. For Windows users, I do have an AI-generated port, but it remains untested as I gave up my Windows addiction in 2017. I may eventually create a platform-independent version of the script using Python. + +I do accept code contributions, so if there are Windows users that want to develop a Windows batch equivalent or a Python version of this script, see the README. Or you can code your own. + +PySimpleGUI is an absolute God-sent for me. I like simplicity, and I am also lazy, so if there is an easier way to do something, I'm all for it. I'm also in the planning stages for a visual editor/IDE for PySimpleGUI applications. + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/fdddd0e4-6654-41c0-a6c7-a9262c903e8d) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/f61d5e2b-7178-4008-8fad-6ae482aa5cfc) +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/7841462/90dac6a7-92b0-4e34-9813-aa557d4a47e7) + + + +----------- + +[maria-korosteleva](https://github.com/maria-korosteleva) 2023-10-23T09:26:11Z +@PySimpleGUI Thank you very much for your kind words! 🥰 + + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-10-21T16:11:14Z +Wow @maria-korosteleva what an incredible application! I love what you've created! ![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/c2881407-2cd6-43ee-86e5-10005caed189) + +I've never seen anything like what you've made. There's clearly a **lot** going on with your work than just a GUI. I had never thought about the intersection of CAD and making garments. It's a fascinating use of technology. I really appreciate you taking the time to post a screenshot and letting us know about your project. + +----------- + +[maria-korosteleva](https://github.com/maria-korosteleva) 2023-10-20T11:52:13Z +I've used the framework to build a garment design configurator in my latest research paper on programmable garments =) +https://github.com/maria-korosteleva/GarmentCode +https://igl.ethz.ch/projects/garmentcode/ + +Screenshot GUI_2_mosaic + + +----------- + +[onyx-and-iris](https://github.com/onyx-and-iris) 2023-09-06T00:00:13Z +A small remote utility for Voicemeeter, designed to work with the NVDA screen reader. + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/55352169/f99212f5-1870-4387-ba4c-159dd40d4c8e) +https://github.com/onyx-and-iris/nvda-voicemeeter + +Thanks for creating PYSimpleGUI, first time using but very impressed! + + + +----------- + +[definite-d](https://github.com/definite-d) 2023-08-26T16:34:23Z +[Themera](https://github.com/definite-d/Themera) v2.1.0 has been released (available [here](https://github.com/definite-d/Themera/releases/tag/v2.1.0)). I'd like to share screenshots and point out that with Windows 11, the color of the "built-in" titlebar can be customized. I accomplished it within Themera with a bit of a non-standard solution, but it's proof of concept. + +![launcher_dark](https://github.com/PySimpleGUI/PySimpleGUI/assets/38317208/579bc2be-abaa-4fd6-a1ea-609ada3be1a9) +![editor_dark](https://github.com/PySimpleGUI/PySimpleGUI/assets/38317208/725fd3bd-4147-4b45-a034-c85b5bd3d74d) + + +----------- + +[luisegarduno](https://github.com/luisegarduno) 2023-06-25T05:18:40Z +**Chess (updated!)** + +Originally added/created by @MikeTheWatchGuy, the chess demos (player vs. player & player vs. Ai) were included in PySimpleGUI ([see here](https://github.com/PySimpleGUI/PySimpleGUI/tree/8b23740fca08b7f5bad3f0d32760f42a5202d3e1/Chess)), but were removed from the main branch towards the end of 2022. + +In terms of the changes that I made to the original code (see #5052): + +I updated **requirements.txt** to include the latest versions: +``` +PySimpleGUI==4.60.5 +python-chess==1.999 +``` +I then made changes within both of the demo files to remove any outdated or deprecated code causing errors. I also added compatibility for Linux, allowing users to import files that do not end in ".exe". + +Both chess demos have only been tested on Ubuntu 20.04 but should also work on Windows since not much was changed from the original code. Lastly, for the engine, I resulted in having to use Stockfish 13, since there was several issues when trying to use Stockfish 14 or Stockfish 15. + +https://github.com/luisegarduno/ChessGUI + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/30121656/80b10f6e-30f7-4159-8574-5f79bff705b1) + +----------- + +[mrtnbm](https://github.com/mrtnbm) 2023-05-25T16:24:22Z +Thanks to PSG, I've created the nicest GUI I've ever made that I'm now using for my own little naive prompt lib. + +- Main window: +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/1a234b86-9457-4d10-8080-ea064e915397) + +- Edit Window: +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/faf9adc7-6de7-42c7-b405-05e5bf081095) + +The heart can be clicked and will change to outline or fill mode. + + - For reference, here is a screenshot without rescaling: + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/49289399/55a72eec-56d7-4442-b660-b42e122be488) + +----------- + +[eagleEggs](https://github.com/eagleEggs) 2023-05-24T04:03:28Z +Started this a few days ago. Thanks to PSG it has already proven the hypothesis as valid - Streamlining novel development in a curated parameter driven manner with op3nAI API. Much more functionality to add in, but what an amazing GUI framework <3 <3 <3 And thanks to @jason990420 for all of the tkinter hacks he's posted which has always covered everything I've needed :) + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/29800532/5751e594-4748-431b-a1b2-76b1b7f346db) + + +----------- + +[gnuchanos](https://github.com/gnuchanos) 2023-05-24T02:35:07Z +![2023-05-24-053345_1280x1024_scrot](https://github.com/PySimpleGUI/PySimpleGUI/assets/117280480/acb7b360-af3a-4ec1-a226-a35d76676167) +not finish yet + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-05-12T09:55:03Z +I saw this awesome screenshot: + +![image](https://github.com/PySimpleGUI/PySimpleGUI/assets/46163555/98b78824-5286-4183-bb92-0d6fc5405270) + +posted in the readme for this project: + +https://github.com/bsanders2/FrosthavenApp + +It's got a nice design and the custom buttons look great. + +----------- + +[zaricj](https://github.com/zaricj) 2023-04-19T09:55:26Z +I've been using PySimpleGUI to make a GUI for the Chocolatey Package Manager, thought of sending a screenshot of it here. + +![Chocolatey-GUI](https://user-images.githubusercontent.com/93329694/233038905-5a3b1f42-4794-4c6e-8411-f70f8fa79723.png) + +Thanks for this amazing module, I'm really having fun learning it! + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-03-16T18:56:49Z +@ssweber the color scheme looks like the `GrayGrayGray` theme to me which means no colors will be added (all system defaults will be used). The important feature that's adding the images is either `Image` elements or `Button` elements. Using a Base64 version of images will enable you to drop them directly into your source code. The PySimpleGUI application `psgresizer` is very helpful in enabling you to quickly add button graphics to your application. + +Here are some steps I followed: + +- I made a video of a session where I took images that I clipped from @lucasmartins19 's application and saved them as PNG files +- Used `psgresizer` to convert each PNG file into a Base64 string +- Pasted the Base64 string into my test program +- Created a layout that used these Base64 images using the `Image` element. + +My layout looked liket his: +```python + layout = [ [sg.Text('Base64 Images Example')], + [sg.Image(b1, key='-I1-')], + [sg.Image(b2, key='-I2-')], + [sg.Image(b3, key='-I3-')], + [sg.Button('Go'), sg.Button('Exit')] ] +``` + +When I ran the code, this is the window I saw: + +![image](https://user-images.githubusercontent.com/46163555/225724235-81b435c8-84f2-48e8-9ba0-9353f6179517.png) + +If you enable events on the `Image` elements, then you'll get an event when the image is clicked. + +```python + layout = [ [sg.Text('Base64 Images Example')], + [sg.Image(b1, key='-I1-', enable_events=True)], + [sg.Image(b2, key='-I2-', enable_events=True)], + [sg.Image(b3, key='-I3-', enable_events=True)], + [sg.Button('Go'), sg.Button('Exit')] ] +``` + +Here's how the process to do all this looked: + + +https://user-images.githubusercontent.com/46163555/225724860-33b5b5ba-9e63-4159-9ccc-825b72aef11a.mp4 + + + +----------- + +[lucasmartins19](https://github.com/lucasmartins19) 2023-03-16T14:04:53Z +> @lucasmartins19 That looks amazing. Would you mind uploading that to a repository? I’m working on a general purpose pysimplegui toolkit and I really like your color-scheme /buttons. Would like to see how you put that all together. + +Thank you! + +Currently, I have a private one to manage everything and provide updates through GitHub. However, I will create a new private repository and invite you as a collaborator. Please feel free to ask me if you need any help understanding anything. + +----------- + +[ssweber](https://github.com/ssweber) 2023-03-16T13:22:48Z +@lucasmartins19 That looks amazing. Would you mind uploading that to a repository? I’m working on a general purpose pysimplegui toolkit and I really like your color-scheme /buttons. Would like to see how you put that all together. + +----------- + +[lucasmartins19](https://github.com/lucasmartins19) 2023-03-14T21:50:57Z +Hi! +I developed this simple application using PySimpleGUI to help my cousin manage his optics lab. +Don't mind the data, it was randomly generated for testing purposes. + +![image](https://user-images.githubusercontent.com/85654292/225143962-28c12d61-2475-47ca-ab54-85ebd5b2824b.png) +![image](https://user-images.githubusercontent.com/85654292/225144198-85d79b35-23d5-440b-91b3-bb1838fda88a.png) +![image](https://user-images.githubusercontent.com/85654292/225144622-d87c5e80-ba7c-469a-9664-4e94d8550e75.png) +![image](https://user-images.githubusercontent.com/85654292/225145054-9ea8b5c2-b8b0-481c-b065-da8af4a2ea0f.png) +![image](https://user-images.githubusercontent.com/85654292/225147555-7417e84f-248b-40e0-b55e-4096ec82534a.png) +![image](https://user-images.githubusercontent.com/85654292/225147854-4570747c-1d3c-4878-ba40-04ec70af1c0f.png) +![image](https://user-images.githubusercontent.com/85654292/225145887-782775e3-e44f-45ba-aaa2-b318ea241002.png) + + + + + +----------- + +[J-Josu](https://github.com/J-Josu) 2023-02-14T12:29:42Z +> Thank you @J-Josu ![image](https://user-images.githubusercontent.com/46163555/217663277-1e69dfcf-c816-4930-aada-610b157eaf0a.png) for sharing your program and your story. I love hearing when students use PySimpleGUI! I'm glad it went well. The screenshots look great! +> +> Code annotation too? Do you have a GitHub Repo with your code? +> +> I'm impressed by bi-lingual programmers. It's hard enough coding, reading, writing, and thinking just in English. Throwing another language on top of all that is next-level stuff. Really happy you posted what you've made! + +Hi @PySimpleGUI, yes i have uploaded the project to the following [repository](https://github.com/J-Josu/FiguRace) + +Thanks you for all! + +----------- + +[chanon-kr](https://github.com/chanon-kr) 2023-02-14T06:27:10Z +Hi, I use PySimpleGUI to make a Simple Object Detection GUI with YOLOv8. + +Check my GitHub repo from this link. + +[Simple Object Detection GUI](https://github.com/chanon-kr/simple_imagui_app) + +And here is a screenshot of the GUI. + +![image](https://user-images.githubusercontent.com/64777509/218656441-a53a0b28-76ac-4be0-a90b-3f4e57a71a56.png) + +Thanks to @PySimpleGUI for suggestions and improvement!! + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-02-08T22:16:35Z +Thank you @J-Josu ![image](https://user-images.githubusercontent.com/46163555/217663277-1e69dfcf-c816-4930-aada-610b157eaf0a.png) for sharing your program and your story. I love hearing when students use PySimpleGUI! I'm glad it went well. The screenshots look great! + +Code annotation too? Do you have a GitHub Repo with your code? + +I'm impressed by bi-lingual programmers. It's hard enough coding, reading, writing, and thinking just in English. Throwing another language on top of all that is next-level stuff. Really happy you posted what you've made! + +----------- + +[J-Josu](https://github.com/J-Josu) 2023-01-30T13:20:08Z +Hi! I'm a stundent of Computer Science in Argentina and i`ve had to do a Final project for my Python Course. The project its about a card game where you can choose between availible themes to try to guess the correct option. The data is automtically loaded from cvs. + +Here are some screenshoots of the final result: + +![image](https://user-images.githubusercontent.com/92873227/215487552-cd9a9185-5ca1-4c63-8c4e-b084a1fce2e5.png) + +![image](https://user-images.githubusercontent.com/92873227/215487639-4facc1ba-2c5f-430f-ac41-2cceb4f52708.png) + +![image](https://user-images.githubusercontent.com/92873227/215487687-61a880c8-13f2-4641-80c9-455a8378a9a3.png) + +![image](https://user-images.githubusercontent.com/92873227/215487759-278903f6-de06-4d47-b016-d0f5324ad6e2.png) + +And other pages... + +All the ui its in spanish, but the code all written in english. +The project includes a mini-framework to build pages easly and some cli utilities. +Also the code have all the corresponding typing annotations and more interesting stuff. + +----------- + +[PySimpleGUI](https://github.com/PySimpleGUI) 2023-01-29T15:31:26Z +@eagleEggs I briefly saw a version of this GUI and then it disappeared. I'm THRILLED you've posted it. ![image](https://user-images.githubusercontent.com/46163555/215336720-2cc04845-ecf1-4c80-8769-8d8d92026c93.png) + +Thank you so very much for sharing your talent in using PySimpleGUI. You're such an expert now and I SO appreciate you being here since the early days. + +That's a beautiful window.... just plain beautiful! + +Your support and encouragement have been really appreciated. + +![image](https://user-images.githubusercontent.com/46163555/215336743-7519cbb7-d700-47b3-8c6f-b2abb29fdea2.png) + + +----------- + +[eagleEggs](https://github.com/eagleEggs) 2023-01-28T02:30:37Z +PSG WOOOHOOOO. Super fast implementation of this thanks to PSG. We were able to jump straight into core code, and add new things on the fly in minutes. This is a vulnerability management / parser, scanner... and moreeeee. + +![image](https://user-images.githubusercontent.com/29800532/215237562-60eb47d3-b5da-4880-8b38-53f33c185f74.png) +![image](https://user-images.githubusercontent.com/29800532/215237481-117ba32e-a94e-480b-9c89-8d66922b7bff.png) + +Thanks again PSG <3 + +----------- + [hseera](https://github.com/hseera) 2023-01-04T23:30:35Z Using PySimpleGUI framework, I built an opernsource tool called "CloudWatch Dashboard Builder" for SRE, Performance Engineers and Operations teams, who work with AWS services. It gives them capability to build CloudWatch Dashboard from different Namespace templates. The tool lets you modify the template too. @@ -15,14 +392,14 @@ https://www.youtube.com/watch?v=tPCHCc-GiHM ----------- -[kubilayyalcinyt](https://github.com/kubilayyalcinyt) 2022-12-18T21:41:57Z +[gnuchanos](https://github.com/gnuchanos) 2022-12-18T21:41:57Z ![2022-12-19-003757_1280x1012_scrot](https://user-images.githubusercontent.com/117280480/208321005-c731df95-a680-4fb7-8682-5c9b86bd6afb.png) this is what ı say ı can't do like my tiling manager ----------- -[kubilayyalcinyt](https://github.com/kubilayyalcinyt) 2022-12-18T21:05:02Z +[gnuchanos](https://github.com/gnuchanos) 2022-12-18T21:05:02Z okay ı try example finish ı ask again ----------- @@ -32,7 +409,7 @@ okay ı try example finish ı ask again ----------- -[kubilayyalcinyt](https://github.com/kubilayyalcinyt) 2022-12-18T19:36:12Z +[gnuchanos](https://github.com/gnuchanos) 2022-12-18T19:36:12Z ![displayManager](https://user-images.githubusercontent.com/117280480/208315826-fc2172ae-349d-42ba-9cdc-3c828b7e4c26.png) ![gnuchanOS_Window_Manager](https://user-images.githubusercontent.com/117280480/208315827-1e8eb182-ea3d-4cb7-8c3f-a093ca58d8ec.png) how ı create like this window first login screen second normal window ı try but ı can't do @@ -2131,7 +2508,7 @@ Obviously the GUI was the easy part! ----------- -[vohe](https://github.com/vohe) 2022-01-23T13:53:09Z +[ghost](https://github.com/ghost) 2022-01-23T13:53:09Z The Januar Version is ready to go. I manged it , finally, to work without lastfm. For everyone who can use this: Audiorecorder,, records (e.g.) spotify web player - with free account. @@ -3028,7 +3405,7 @@ Love all the graphics you're including your GUIs. It adds a level up from the b ----------- -[Scania-Creations-16](https://github.com/Scania-Creations-16) 2021-11-04T02:55:59Z +[Pranav-P-16](https://github.com/Pranav-P-16) 2021-11-04T02:55:59Z Created a Simple Youtube Downloader using PySimpleGUI :blush: ![yt1](https://user-images.githubusercontent.com/91425738/140249931-76fdda21-fbdf-42a0-81a7-add95ab122b6.png) @@ -3043,7 +3420,7 @@ Created a Simple Youtube Downloader using PySimpleGUI :blush: ----------- -[Scania-Creations-16](https://github.com/Scania-Creations-16) 2021-11-01T04:11:56Z +[Pranav-P-16](https://github.com/Pranav-P-16) 2021-11-01T04:11:56Z > You're quite welcome. > > **Thank you** @Scania-Creations-16 for posting your screenshot! Awesome awesome creation! I'm impressed! @@ -3079,7 +3456,7 @@ I'm sure there are things that others can learn. If you're a beginner, then I'm ----------- -[Scania-Creations-16](https://github.com/Scania-Creations-16) 2021-10-31T04:14:25Z +[Pranav-P-16](https://github.com/Pranav-P-16) 2021-10-31T04:14:25Z I made a Music Player using PySimpleGUI @@ -4254,7 +4631,7 @@ GitHub.com/MOABdali/megaCheckers if anyone wants to try it. Please keep in mind ----------- -[vohe](https://github.com/vohe) 2020-08-08T11:11:29Z +[ghost](https://github.com/ghost) 2020-08-08T11:11:29Z Ready to go, I found that putting move than 3000 elements in a sg.treedata() structure works, but as i close that there'll be a huge garbage collection of tkinter under the hood. So my directory popup window isn't usable for a 'normal' user directory, especially if the show hidden files option is active. @@ -4268,7 +4645,7 @@ Have Fun, stay healthy. ----------- -[vohe](https://github.com/vohe) 2020-08-05T13:31:05Z +[ghost](https://github.com/ghost) 2020-08-05T13:31:05Z In addition to get a Folder Browser running , which does not - show hidden files, - throws errors on Permissions @@ -4291,7 +4668,7 @@ The bars for each color have the color of the original image and the other-color ----------- -[vohe](https://github.com/vohe) 2020-08-02T17:28:23Z +[ghost](https://github.com/ghost) 2020-08-02T17:28:23Z Hi folks, here is another one i playing with.... The TreeElement is not as flexible as i want it. But many things are possible. @@ -4301,7 +4678,7 @@ I got the functionallity i want, but the interface isn't that what i like to get ----------- -[vohe](https://github.com/vohe) 2020-07-05T16:45:42Z +[ghost](https://github.com/ghost) 2020-07-05T16:45:42Z Thank you, first of all i got it as i like (but not so elegant) First of all it works. ![english](https://user-images.githubusercontent.com/7021635/86537489-b61c7180-beef-11ea-9b65-4d11b0a4d515.png) @@ -4326,7 +4703,7 @@ https://github.com/PySimpleGUI/PySimpleGUI/issues/new?assignees=&labels=&templat ----------- -[vohe](https://github.com/vohe) 2020-07-04T21:28:44Z +[ghost](https://github.com/ghost) 2020-07-04T21:28:44Z > Very nice @vohe ! Thanks for posting it! > I like they use of tabs. Great , simple layout. I might have to use that idea for settings sometime. It's never dawned on me before to put them into a tab like that. @@ -4343,7 +4720,7 @@ I like they use of tabs. Great , simple layout. I might have to use that idea ----------- -[vohe](https://github.com/vohe) 2020-07-04T15:43:31Z +[ghost](https://github.com/ghost) 2020-07-04T15:43:31Z ![Audiorecorder (spotify)](https://user-images.githubusercontent.com/7021635/86515989-6a00fc80-be1d-11ea-89c9-08411c829eb7.png) A little Programm, that records files form a webside (actually open.spotify) store them as mp3, tag them, sort them into directorys , notify the user when a new song is beginning. diff --git a/docs/call reference.md b/docs/call reference.md index b2037bda..e909d3e7 100644 --- a/docs/call reference.md +++ b/docs/call reference.md @@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30. - @@ -126,7 +126,7 @@ Parameter Descriptions: | (str, str) or str | mouseover_colors | Important difference between Linux & Windows! Linux - Colors when mouse moved over button. Windows - colors when button is pressed. The default is to switch the text and background colors (an inverse effect) | | bool | use_ttk_buttons | True = use ttk buttons. False = do not use ttk buttons. None (Default) = use ttk buttons only if on a Mac and not with button images | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | If True the return key will cause this button to be pressed | +| bool | bind_return_key | If True then pressing the return key in an Input or Multiline Element will cause this button to appear to be clicked (generates event with this button's key | | bool | focus | if True, initial focus will be put on this button | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -1305,6 +1305,7 @@ Checkbox(text, background_color = None, text_color = None, checkbox_color = None, + highlight_thickness = 1, change_submits = False, enable_events = False, disabled = False, @@ -1324,28 +1325,29 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | text | Text to display next to checkbox | -| bool | default | Set to True if you want this checkbox initially checked | -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| bool | auto_size_text | if True will size the element to match the length of the text | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| str | background_color | color of background | -| str | text_color | color of the text | -| str | checkbox_color | color of background of the box that has the check mark in it. The checkmark is the same color as the text | -| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | -| bool | enable_events | Turns on the element specific events. Checkbox events happen when an item changes | -| bool | disabled | set disable state | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| str | tooltip | text, that will appear when mouse hovers over the element | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | +| str | text | Text to display next to checkbox | +| bool | default | Set to True if you want this checkbox initially checked | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| bool | auto_size_text | if True will size the element to match the length of the text | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| str | background_color | color of background | +| str | text_color | color of the text | +| str | checkbox_color | color of background of the box that has the check mark in it. The checkmark is the same color as the text | +| int | highlight_thickness | thickness of border around checkbox when gets focus | +| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | +| bool | enable_events | Turns on the element specific events. Checkbox events happen when an item changes | +| bool | disabled | set disable state | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| str | tooltip | text, that will appear when mouse hovers over the element | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | ### bind @@ -2214,7 +2216,7 @@ Parameter Descriptions: | str | text_color | color of the text | | str | button_background_color | The color of the background of the button on the combo box | | str | button_arrow_color | The color of the arrow on the button on the combo box | -| bool | bind_return_key | If True, then the return key will cause a the Combo to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the Combo to generate an event when return key is pressed | | bool | change_submits | DEPRICATED DO NOT USE. Use `enable_events` instead | | bool | enable_events | Turns on the element specific events. Combo event is when a choice is made | | bool | enable_per_char_events | Enables generation of events for every character that's input. This is like the Input element's events | @@ -2481,22 +2483,26 @@ update(value = None, font = None, visible = None, size = (None, None), - select = None) + select = None, + text_color = None, + background_color = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | value | change which value is current selected based on new list of previous list of choices | -| List[Any] | values | change list of choices | -| int | set_to_index | change selection to a particular choice starting with index = 0 | -| bool | disabled | disable or enable state of the element | -| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | visible | control visibility of element | -| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | -| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| Any | value | change which value is current selected based on new list of previous list of choices | +| List[Any] | values | change list of choices | +| int | set_to_index | change selection to a particular choice starting with index = 0 | +| bool | disabled | disable or enable state of the element | +| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| bool | visible | control visibility of element | +| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | +| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| str | background_color | color of background | +| str | text_color | color of the text | ### visible @@ -2586,22 +2592,26 @@ Update(value = None, font = None, visible = None, size = (None, None), - select = None) + select = None, + text_color = None, + background_color = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | value | change which value is current selected based on new list of previous list of choices | -| List[Any] | values | change list of choices | -| int | set_to_index | change selection to a particular choice starting with index = 0 | -| bool | disabled | disable or enable state of the element | -| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | visible | control visibility of element | -| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | -| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| Any | value | change which value is current selected based on new list of previous list of choices | +| List[Any] | values | change list of choices | +| int | set_to_index | change selection to a particular choice starting with index = 0 | +| bool | disabled | disable or enable state of the element | +| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| bool | visible | control visibility of element | +| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | +| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| str | background_color | color of background | +| str | text_color | color of the text | --------- @@ -4881,6 +4891,8 @@ Input(default_text = "", readonly = False, disabled_readonly_background_color = None, disabled_readonly_text_color = None, + selected_text_color = None, + selected_background_color = None, expand_x = False, expand_y = False, right_click_menu = None, @@ -4915,6 +4927,8 @@ Parameter Descriptions: | bool | readonly | If True tkinter state set to 'readonly'. Use this in place of use_readonly_for_disable as another way of achieving readonly. Note cannot set BOTH readonly and disabled as tkinter only supplies a single flag | | str | disabled_readonly_background_color | If state is set to readonly or disabled, the color to use for the background | | str | disabled_readonly_text_color | If state is set to readonly or disabled, the color to use for the text | +| str | selected_text_color | Color of text when it is selected (using mouse or control+A, etc) | +| str | selected_background_color | Color of background when it is selected (using mouse or control+A, etc) | | bool | expand_x | If True the element will automatically expand in the X direction to fill available space | | bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | | List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | @@ -5096,6 +5110,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -5359,7 +5389,7 @@ Parameter Descriptions: | [enum] | select_mode | Select modes are used to determine if only 1 item can be selected or multiple and how they can be selected. Valid choices begin with "LISTBOX_SELECT_MODE_" and include: LISTBOX_SELECT_MODE_SINGLE LISTBOX_SELECT_MODE_MULTIPLE LISTBOX_SELECT_MODE_BROWSE LISTBOX_SELECT_MODE_EXTENDED | | bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | | bool | enable_events | Turns on the element specific events. Listbox generates events when an item is clicked | -| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event when return key is pressed | | (int, int) or (int, None) or int | size | w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | disabled | set disable state for element | @@ -5553,6 +5583,24 @@ Metadata is an Element property that you can use at any time to hold any value |---|---|---| |(Any)| **return** | the current metadata value | +### select_index + +Selects an index while providing capability to setting the selected color for the index to specific text/background color + +``` +select_index(index, + highlight_text_color = None, + highlight_background_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | index | specifies which item to change. index starts at 0 and goes to length of values list minus one | +| str | highlight_text_color | color of the text when this item is selected. | +| str | highlight_background_color | color of the background when this item is selected | + ### set_cursor Sets the cursor for the current Element. @@ -5586,6 +5634,28 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_index_color + +Sets the color of a specific item without selecting it + +``` +set_index_color(index, + text_color = None, + background_color = None, + highlight_text_color = None, + highlight_background_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | index | specifies which item to change. index starts at 0 and goes to length of values list minus one | +| str | text_color | color of the text for this item | +| str | background_color | color of the background for this item | +| str | highlight_text_color | color of the text when this item is selected. | +| str | highlight_background_color | color of the background when this item is selected | + ### set_size Changes the size of an element to a specific size. @@ -6244,12 +6314,15 @@ Multiline(default_text = "", enter_submits = False, disabled = False, autoscroll = False, + autoscroll_only_at_bottom = False, border_width = None, size = (None, None), s = (None, None), auto_size_text = None, background_color = None, text_color = None, + selected_text_color = None, + selected_background_color = None, horizontal_scroll = False, change_submits = False, enable_events = False, @@ -6289,49 +6362,52 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | default_text | Initial text to show | -| bool | enter_submits | if True, the Window.read call will return is enter key is pressed in this element | -| bool | disabled | set disable state | -| bool | autoscroll | If True the contents of the element will automatically scroll as more data added to the end | -| int | border_width | width of border around element in pixels | -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| bool | auto_size_text | if True will size the element to match the length of the text | -| str | background_color | color of background | -| str | text_color | color of the text | -| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical | -| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | -| bool | enable_events | If True then any key press that happens when the element has focus will generate an event. | -| bool | do_not_clear | if False the element will be cleared any time the Window.read call returns | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| bool | write_only | If True then no entry will be added to the values dictionary when the window is read | -| bool | auto_refresh | If True then anytime the element is updated, the window will be refreshed so that the change is immediately displayed | -| bool | reroute_stdout | If True then all output to stdout will be output to this element | -| bool | reroute_stderr | If True then all output to stderr will be output to this element | -| bool | reroute_cprint | If True your cprint calls will output to this element. It's the same as you calling cprint_set_output_destination | -| bool | echo_stdout_stderr | If True then output to stdout and stderr will be output to this element AND also to the normal console location | -| bool | focus | if True initial focus will go to this element | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| str | tooltip | text, that will appear when mouse hovers over the element | -| str | justification | text justification. left, right, center. Can use single characters l, r, c. | -| bool | no_scrollbar | If False then a vertical scrollbar will be shown (the default) | -| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | -| str | sbar_trough_color | Scrollbar color of the trough | -| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | -| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | -| int | sbar_width | Scrollbar width in pixels | -| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | -| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | -| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | rstrip | If True the value returned in will have whitespace stripped from the right side | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | +| Any | default_text | Initial text to show | +| bool | enter_submits | if True, the Window.read call will return is enter key is pressed in this element | +| bool | disabled | set disable state | +| bool | autoscroll | If True the contents of the element will automatically scroll as more data added to the end | +| bool | autoscroll_only_at_bottom | If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline | +| int | border_width | width of border around element in pixels | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| bool | auto_size_text | if True will size the element to match the length of the text | +| str | background_color | color of background | +| str | text_color | color of the text | +| str | selected_text_color | Color of text when it is selected (using mouse or control+A, etc) | +| str | selected_background_color | Color of background when it is selected (using mouse or control+A, etc) | +| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical | +| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | +| bool | enable_events | If True then any key press that happens when the element has focus will generate an event. | +| bool | do_not_clear | if False the element will be cleared any time the Window.read call returns | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| bool | write_only | If True then no entry will be added to the values dictionary when the window is read | +| bool | auto_refresh | If True then anytime the element is updated, the window will be refreshed so that the change is immediately displayed | +| bool | reroute_stdout | If True then all output to stdout will be output to this element | +| bool | reroute_stderr | If True then all output to stderr will be output to this element | +| bool | reroute_cprint | If True your cprint calls will output to this element. It's the same as you calling cprint_set_output_destination | +| bool | echo_stdout_stderr | If True then output to stdout and stderr will be output to this element AND also to the normal console location | +| bool | focus | if True initial focus will go to this element | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| str | tooltip | text, that will appear when mouse hovers over the element | +| str | justification | text justification. left, right, center. Can use single characters l, r, c. | +| bool | no_scrollbar | If False then a vertical scrollbar will be shown (the default) | +| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | +| str | sbar_trough_color | Scrollbar color of the trough | +| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | +| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | +| int | sbar_width | Scrollbar width in pixels | +| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | +| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | +| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | rstrip | If True the value returned in will have whitespace stripped from the right side | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | ### bind @@ -6590,6 +6666,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -7200,11 +7292,11 @@ Parameter Descriptions: --------- -## Output Element (No longer recommended - USE `Multiline` instead) +## Output Element -Rather than use the `Output` element, it's recommended that you use the `Multiline` element instead. The reason for this is that more controls have been added to the Multiline and in the future you can expect more features will be added to the Multiline while the `Output` element has stopped being enhanced. +Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted. -You can control which Multiline Element receives your stdout output as well as use the color-print (`cprint`) with a Multiline. +Now based on the `Multiline` element Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted. @@ -7227,6 +7319,7 @@ Output(size = (None, None), text_color = None, pad = None, p = None, + autoscroll_only_at_bottom = False, echo_stdout_stderr = False, font = None, tooltip = None, @@ -7252,31 +7345,32 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| str | background_color | color of background | -| str | text_color | color of the text | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| bool | echo_stdout_stderr | If True then output to stdout will be output to this element AND also to the normal console location | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| str | tooltip | text, that will appear when mouse hovers over the element | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | -| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | -| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True, then line wrapping will be off by default | -| str | sbar_trough_color | Scrollbar color of the trough | -| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | -| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | -| int | sbar_width | Scrollbar width in pixels | -| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | -| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | -| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| str | background_color | color of background | +| str | text_color | color of the text | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| bool | autoscroll_only_at_bottom | If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline | +| bool | echo_stdout_stderr | If True then output to stdout will be output to this element AND also to the normal console location | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| str | tooltip | text, that will appear when mouse hovers over the element | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | +| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | +| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True, then line wrapping will be off by default | +| str | sbar_trough_color | Scrollbar color of the trough | +| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | +| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | +| int | sbar_width | Scrollbar width in pixels | +| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | +| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | +| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | ### bind @@ -8902,7 +8996,7 @@ Parameter Descriptions: |--|--|--| | int | h_pixels | number of horizontal pixels | | int | v_pixels | number of vertical pixels | -| (Column) | **RETURN** | (Column) A column element that has a pad setting set according to parameters +| (Canvas) | **RETURN** | (Canvas) A canvas element that has a pad setting set according to parameters ------- @@ -9616,7 +9710,7 @@ Parameter Descriptions: | (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | auto_size_text | if True will size the element to match the length of the text | -| bool | bind_return_key | If True, then the return key will cause a the element to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the element to generate an event when return key is pressed | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | background_color | color of background | | str | text_color | color of the text | @@ -9809,6 +9903,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -10980,14 +11090,14 @@ Parameter Descriptions: Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on the tab if no key is defined. Returns None if an error occurs. -Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you +Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you are using this method correctly? `get()` |Type|Name|Meaning| |---|---|---| -|Any | None| **return** | The key of the currently selected tab or the tab's text if it has no key | +|Any | None| **return** | The key of the currently selected tab or None if there is an error | ### get_next_focus @@ -11212,14 +11322,14 @@ Parameter Descriptions: Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on the tab if no key is defined. Returns None if an error occurs. -Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you +Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you are using this method correctly? `Get()` |Type|Name|Meaning| |---|---|---| -|Any | None| **return** | The key of the currently selected tab or the tab's text if it has no key | +|Any | None| **return** | The key of the currently selected tab or None if there is an error | ### SetFocus @@ -11420,15 +11530,13 @@ Parameter Descriptions: ### get -Dummy function for tkinter port. In the Qt port you can read back the values in the table in case they were -edited. Don't know yet how to enable editing of a Tree in tkinter so just returning the values provided by -user when Table was created or Updated. +Get the selected rows using tktiner's selection method. Returns a list of the selected rows. `get()` |Type|Name|Meaning| |---|---|---| -|List[List[Any]]| **return** | the current table values (for now what was originally provided up updated) | +|List[int]| **return** | a list of the index of the selected rows (a list of ints) | ### get_next_focus @@ -11652,15 +11760,13 @@ The following methods are here for backwards compatibility reference. You will ### Get -Dummy function for tkinter port. In the Qt port you can read back the values in the table in case they were -edited. Don't know yet how to enable editing of a Tree in tkinter so just returning the values provided by -user when Table was created or Updated. +Get the selected rows using tktiner's selection method. Returns a list of the selected rows. `Get()` |Type|Name|Meaning| |---|---|---| -|List[List[Any]]| **return** | the current table values (for now what was originally provided up updated) | +|List[int]| **return** | a list of the index of the selected rows (a list of ints) | ### SetFocus @@ -12194,6 +12300,7 @@ Tree(data = None, show_expanded = False, change_submits = False, enable_events = False, + click_toggles_select = None, font = None, justification = "right", text_color = None, @@ -12245,6 +12352,7 @@ Parameter Descriptions: | bool | show_expanded | if True then the tree will be initially shown with all nodes completely expanded | | bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | | bool | enable_events | Turns on the element specific events. Tree events happen when row is clicked | +| bool | click_toggles_select | If True then clicking a row will cause the selection for that row to toggle between selected and deselected | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | justification | 'left', 'right', 'center' are valid choices | | str | text_color | color of the text | @@ -13044,6 +13152,7 @@ Window(title, sbar_arrow_width = None, sbar_frame_color = None, sbar_relief = None, + watermark = None, metadata = None) ``` @@ -13111,6 +13220,7 @@ Parameter Descriptions: | int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | | str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | | str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| bool | watermark | If True, then turns on watermarking temporarily for ALL windows created from this point forward. See global settings doc for more info | | Any | metadata | User metadata that can be set to ANYTHING | ### add_row @@ -13615,15 +13725,15 @@ IMPORTANT - This method uses THREADS... this means you CANNOT make any PySimpleG the function you provide with the exception of one function, Window.write_event_value. ``` -perform_long_operation(func, end_key) +perform_long_operation(func, end_key = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | func | A lambda or a function name with no parms | -| Any | end_key | The key that will be generated when the function returns | +| Any | func | A lambda or a function name with no parms | +| (Any or None) | end_key | Optional key that will be generated when the function returns | | threading.Thread | **RETURN** | The id of the thread ### read @@ -13837,17 +13947,71 @@ IMPORTANT - This method uses THREADS... this means you CANNOT make any PySimpleG the function you provide with the exception of one function, Window.write_event_value. ``` -start_thread(func, end_key) +start_thread(func, end_key = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | func | A lambda or a function name with no parms | -| Any | end_key | The key that will be generated when the function returns | +| Any | func | A lambda or a function name with no parms | +| (Any or None) | end_key | Optional key that will be generated when the function returns | | threading.Thread | **RETURN** | The id of the thread +### timer_get_active_timers + +Returns a list of currently active timers for a window + +`timer_get_active_timers()` + +|Type|Name|Meaning| +|---|---|---| +|List[int]| **return** | List of timers for the window | + +### timer_start + +Starts a timer that gnerates Timer Events. The default is to repeat the timer events until timer is stopped. +You can provide your own key or a default key will be used. The default key is defined +with the constants EVENT_TIMER or TIMER_KEY. They both equal the same value. +The values dictionary will contain the timer ID that is returned from this function. + +``` +timer_start(frequency_ms, + key = "__TIMER EVENT__", + repeating = True) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | frequency_ms | How often to generate timer events in milliseconds | +| str or int or tuple or object | key | Key to be returned as the timer event | +| bool | repeating | If True then repeat timer events until timer is explicitly stopped | +| int | **RETURN** | Timer ID for the timer + +### timer_stop + +Stops a timer with a given ID + +``` +timer_stop(timer_id) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | timer_id | Timer ID of timer to stop | + +### timer_stop_all + +Stops all timers for THIS window + +```python +timer_stop_all() +``` + ### un_hide Used to bring back a window that was previously hidden using the Hide method @@ -14911,7 +15075,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -14919,6 +15083,7 @@ Parameter Descriptions: | str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | | str | locale | defines the locale used to get day names | | str | format | formats result using this strftime format | +| int | begin_at_sunday_plus | Determines the left-most day in the display. 0=sunday, 1=monday, etc | | List[str] | month_names | optional list of month names to use (should be 12 items) | | List[str] | day_abbreviations | optional list of abbreviations to display as the day of week | | str | title | Title shown on the date chooser window | @@ -14963,7 +15128,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15021,7 +15186,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | Determines if initial focus should go to this element. | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15072,7 +15237,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | tooltip | text, that will appear when mouse hovers over the element | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15134,7 +15299,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15179,7 +15344,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15432,7 +15597,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | tooltip | text, that will appear when mouse hovers over the element | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15520,7 +15685,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15565,7 +15730,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15610,7 +15775,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15657,7 +15822,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15712,7 +15877,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15754,7 +15919,7 @@ Parameter Descriptions: | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | @@ -15852,7 +16017,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15899,7 +16064,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15956,7 +16121,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16009,7 +16174,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16067,7 +16232,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16124,7 +16289,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16571,11 +16736,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -16625,11 +16790,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -16738,7 +16903,8 @@ popup_animated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -16760,6 +16926,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed Popup that closes itself after some time period @@ -16942,6 +17109,9 @@ popup_get_date(start_mon = None, locale = None, month_names = None, day_abbreviations = None, + day_font = "TkFixedFont 9", + mon_year_font = "TkFixedFont 10", + arrow_font = "TkFixedFont 7", modal = True) ``` @@ -16963,6 +17133,9 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | List[str] | month_names | optional list of month names to use (should be 12 items) | | List[str] | day_abbreviations | optional list of abbreviations to display as the day of week | +| str or tuple | day_font | Font and size to use for the calendar | +| str or tuple | mon_year_font | Font and size to use for the month and year at the top | +| str or tuple | arrow_font | Font and size to use for the arrow buttons | | bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | None or (int, int, int) | **RETURN** | Tuple containing (month, day, year) of chosen date or None if was cancelled @@ -17816,7 +17989,8 @@ PopupAnimated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -17838,6 +18012,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed Display a Popup without a titlebar. Enables grab anywhere so you can move it @@ -19704,7 +19879,7 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | command | Filename to load settings from (and save to in the future) | +| str | command | The command/file to execute. What you would type at a console to run a program or shell command. | | Any | *args | Variable number of arguments that are passed to the program being started as command line parms | | bool | wait | If True then wait for the subprocess to finish | | str | cwd | Working directory to use when executing the subprocess | @@ -19960,7 +20135,8 @@ set_options(icon = None, sbar_relief = None, alpha_channel = None, hide_window_when_creating = None, - use_button_shortcuts = None) + use_button_shortcuts = None, + watermark_text = None) ``` Parameter Descriptions: @@ -20033,6 +20209,7 @@ Parameter Descriptions: | float | alpha_channel | Default alpha channel to be used on all windows | | bool | hide_window_when_creating | If True then alpha will be set to 0 while a window is made and moved to location indicated | | bool | use_button_shortcuts | If True then Shortcut Char will be used with Buttons | +| str | watermark_text | Set the text that will be used if a window is watermarked | | None | **RETURN** | None ### Non PEP8 versions @@ -20116,7 +20293,8 @@ SetOptions(icon = None, sbar_relief = None, alpha_channel = None, hide_window_when_creating = None, - use_button_shortcuts = None) + use_button_shortcuts = None, + watermark_text = None) ``` Parameter Descriptions: @@ -20189,6 +20367,7 @@ Parameter Descriptions: | float | alpha_channel | Default alpha channel to be used on all windows | | bool | hide_window_when_creating | If True then alpha will be set to 0 while a window is made and moved to location indicated | | bool | use_button_shortcuts | If True then Shortcut Char will be used with Buttons | +| str | watermark_text | Set the text that will be used if a window is watermarked | | None | **RETURN** | None ## Old Themes (Look and Feel) - Replaced by theme() diff --git a/docs/cookbook.md b/docs/cookbook.md index 2205af6e..0b977854 100644 --- a/docs/cookbook.md +++ b/docs/cookbook.md @@ -1,7 +1,7 @@
- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30
-
diff --git a/docs/index.md b/docs/index.md index 52331638..fd28ee9a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@
- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30
-
@@ -57,7 +57,7 @@ # PySimpleGUI User's Manual -## Python GUI For Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces +## User Interfaces for Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces ## The Call Reference Section Moved to here @@ -2028,7 +2028,8 @@ popup_animated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -2050,6 +2051,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed ***To close animated popups***, call PopupAnimated with `image_source=None`. This will close all of the currently open PopupAnimated windows. @@ -2078,11 +2080,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -7112,12 +7114,54 @@ There are a number of demo programs that show how to use UserSettings to create If you're using the default path, remember that previous runs of your file may have old settings that are still in your settings file. It can get confusing when you've forgotten that you previously wrote a setting. Not seeing the filename can have drawbacks like this. -Also, because the settings automatically save after every update, it can be easy to accidently overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. +Also, because the settings automatically save after every update, it can be easy to accidentally overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. To save your Python dictionary to a settings file, simply call `user_settings_write_new_dictionary(dict)`, passing in your dictionary as the parameter. ------------------------- +# Timer API + +The Timer API calls are in version 4.61.0 that is currently only available on the PySimpleGUI GitHub. It has not been released to PyPI yet. + +## Do Not Use Sleeps In Your Event Loop... + +Instead of sleeping, you can request that an event be generated after some period of time. If you need to "Sleep for 3 seconds" as part of some operation, instead schedule a timer for 3 seconds. Your `window.read` call will return a value of `sg.TIMER_KEY` or `sg.EVENT_TIMER` (they are aliases and thus have the same value). + +## Timer API Calls + +These are the API calls that you'll use to manage timers: + +`window.timer_start` starts a timer + +`window.timer_stop` stops a single timer + +`window.timer_stop_all` stops all timers + +`window.timer_get_active_timers` returns a list of active timer IDs + +Example - start a 3 second timer that does not repeat: + +```python +window.timer_start(3000, repeating=False) # start a 3-second timer +``` + +When this timer expires, you'll get an event `sg.EVENT_TIMER`. If you want to specify your own key to be returned, then use the `key` parameter: + +```python +window.timer_start(3000, key='-MY TIMER KEY-', repeating=False) +``` + +See the call reference for the details of each call. + +## Timer Demo Programs + +Using the PySimpleGUI Demo Browser, search for `window.timer_` to find Demo Programs that use the Timer API calls. + +The program `Demo_WindowTimer.py` demonstrates both repeating and non-repeating timers as well as using custom keys. It's a simple set of API calls to use and the docstrings in combination with the Demo Programs should give you all you need to start using this capability. + +--------------------------- + # Extending PySimpleGUI PySimpleGUI doesn't and can't provide every single setting available in the underlying GUI framework. Not all tkinter options are available for a `Text` Element. Same with PySimpleGUIQt and the other ports. @@ -10092,6 +10136,22 @@ Test Harness and Settings Windows fit on small screens better * Emergency Patch Release for Mac OS 12.3 and greater * Fixed bug in Mac OS version check in yesterday's 4.60.2 release +## 4.60.4 PySimpleGUI 10-Oct-2022 + +* Dot release to quickly fix the Trinket detection which stopped working recently + +## 4.60.5 PySimpleGUI 21-May-2023 + +* Mac fixes + * Fix for Input Element not working in no-titlebar windows on MacOs 13.2.1 + * Change to the 0.99 Alpha fix made in 4.60.2. Now only applies patch when running on 8.6.12, regardless of Mac Control Panel setting in PySimpleGUI Global Settings. Removes the need for users to disable when upgrading tkinter. +* Added Intelligent Upgrade Service - inform users when there are releases of PySimpleGUI that fix a problem that may be unique to their combination of components +* Change to GitHub Issue GUI + * Added checkbox for checking if running latest PyPI version + * Recommended using Demo Browser to search Demo Programs + * Use platform module to fill in the OS information field +* SDK Help Window - changed all readthedocs links to use the PySimpleGUI.org hostname for better portability + ## Code Condition Make it run diff --git a/docs/readme.md b/docs/readme.md index bb87045c..1504d49e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -1,6 +1,6 @@

- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -9,8 +9,8 @@ PySimpleGUI Udemy Course
-
apply coupon for discount:
266B9C51C90B3728782E
- click here to visit course page +
apply coupon for discount:
522B20BF5EF123C4AB30
+ click here to visit course page
diff --git a/readme.md b/readme.md index bb87045c..1504d49e 100644 --- a/readme.md +++ b/readme.md @@ -1,6 +1,6 @@

- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -9,8 +9,8 @@ PySimpleGUI Udemy Course
-
apply coupon for discount:
266B9C51C90B3728782E
- click here to visit course page +
apply coupon for discount:
522B20BF5EF123C4AB30
+ click here to visit course page
diff --git a/readme_creator/markdown input files/1_HEADER_top_part.md b/readme_creator/markdown input files/1_HEADER_top_part.md index 502b40f0..f1232855 100644 --- a/readme_creator/markdown input files/1_HEADER_top_part.md +++ b/readme_creator/markdown input files/1_HEADER_top_part.md @@ -27,8 +27,8 @@ HOW DO I INSERT IMAGES ???
- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -52,9 +52,9 @@ HOW DO I INSERT IMAGES ??? apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30
-
@@ -96,7 +96,7 @@ HOW DO I INSERT IMAGES ??? # PySimpleGUI User's Manual -## Python GUI For Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces +## User Interfaces for Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces ## The Call Reference Section Moved to here diff --git a/readme_creator/markdown input files/2_readme.md b/readme_creator/markdown input files/2_readme.md index 979af1f3..bdd7cebe 100644 --- a/readme_creator/markdown input files/2_readme.md +++ b/readme_creator/markdown input files/2_readme.md @@ -5574,13 +5574,60 @@ There are a number of demo programs that show how to use UserSettings to create If you're using the default path, remember that previous runs of your file may have old settings that are still in your settings file. It can get confusing when you've forgotten that you previously wrote a setting. Not seeing the filename can have drawbacks like this. -Also, because the settings automatically save after every update, it can be easy to accidently overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. +Also, because the settings automatically save after every update, it can be easy to accidentally overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. To save your Python dictionary to a settings file, simply call `user_settings_write_new_dictionary(dict)`, passing in your dictionary as the parameter. ------------------------- +# Timer API + +The Timer API calls are in version 4.61.0 that is currently only available on the PySimpleGUI GitHub. It has not been released to PyPI yet. + +## Do Not Use Sleeps In Your Event Loop... + +Instead of sleeping, you can request that an event be generated after some period of time. If you need to "Sleep for 3 seconds" as part of some operation, instead schedule a timer for 3 seconds. Your `window.read` call will return a value of `sg.TIMER_KEY` or `sg.EVENT_TIMER` (they are aliases and thus have the same value). + + +## Timer API Calls + +These are the API calls that you'll use to manage timers: + + +`window.timer_start` starts a timer + +`window.timer_stop` stops a single timer + +`window.timer_stop_all` stops all timers + +`window.timer_get_active_timers` returns a list of active timer IDs + +Example - start a 3 second timer that does not repeat: + +```python +window.timer_start(3000, repeating=False) # start a 3-second timer +``` + +When this timer expires, you'll get an event `sg.EVENT_TIMER`. If you want to specify your own key to be returned, then use the `key` parameter: + +```python +window.timer_start(3000, key='-MY TIMER KEY-', repeating=False) +``` + + +See the call reference for the details of each call. + +## Timer Demo Programs + +Using the PySimpleGUI Demo Browser, search for `window.timer_` to find Demo Programs that use the Timer API calls. + +The program `Demo_WindowTimer.py` demonstrates both repeating and non-repeating timers as well as using custom keys. It's a simple set of API calls to use and the docstrings in combination with the Demo Programs should give you all you need to start using this capability. + + + +--------------------------- + # Extending PySimpleGUI diff --git a/readme_creator/markdown input files/4_Release_notes.md b/readme_creator/markdown input files/4_Release_notes.md index 82200a1d..7aa73856 100644 --- a/readme_creator/markdown input files/4_Release_notes.md +++ b/readme_creator/markdown input files/4_Release_notes.md @@ -2590,6 +2590,25 @@ Test Harness and Settings Windows fit on small screens better * Emergency Patch Release for Mac OS 12.3 and greater * Fixed bug in Mac OS version check in yesterday's 4.60.2 release +## 4.60.4 PySimpleGUI 10-Oct-2022 + +* Dot release to quickly fix the Trinket detection which stopped working recently + + +## 4.60.5 PySimpleGUI 21-May-2023 + +* Mac fixes + * Fix for Input Element not working in no-titlebar windows on MacOs 13.2.1 + * Change to the 0.99 Alpha fix made in 4.60.2. Now only applies patch when running on 8.6.12, regardless of Mac Control Panel setting in PySimpleGUI Global Settings. Removes the need for users to disable when upgrading tkinter. +* Added Intelligent Upgrade Service - inform users when there are releases of PySimpleGUI that fix a problem that may be unique to their combination of components +* Change to GitHub Issue GUI + * Added checkbox for checking if running latest PyPI version + * Recommended using Demo Browser to search Demo Programs + * Use platform module to fill in the OS information field +* SDK Help Window - changed all readthedocs links to use the PySimpleGUI.org hostname for better portability + + + ## Code Condition diff --git a/readme_creator/markdown input files/5_call_reference.md b/readme_creator/markdown input files/5_call_reference.md index b32f61e2..24c9e48c 100644 --- a/readme_creator/markdown input files/5_call_reference.md +++ b/readme_creator/markdown input files/5_call_reference.md @@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30.
-
@@ -1111,6 +1111,9 @@ The following methods are here for backwards compatibility reference. You will ### set_focus +### set_ibeam_color + + ### set_size @@ -1204,12 +1207,18 @@ The following methods are here for backwards compatibility reference. You will ### metadata +### select_index + + ### set_cursor ### set_focus +### set_index_color + + ### set_size @@ -1421,6 +1430,9 @@ See the Column element to get a list of method calls available. The function re ### set_focus +### set_ibeam_color + + ### set_size @@ -1557,11 +1569,11 @@ The following methods are here for backwards compatibility reference. You will --------- -## Output Element (No longer recommended - USE `Multiline` instead) +## Output Element -Rather than use the `Output` element, it's recommended that you use the `Multiline` element instead. The reason for this is that more controls have been added to the Multiline and in the future you can expect more features will be added to the Multiline while the `Output` element has stopped being enhanced. +Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted. -You can control which Multiline Element receives your stdout output as well as use the color-print (`cprint`) with a Multiline. +Now based on the `Multiline` element @@ -2162,6 +2174,9 @@ The following methods are here for backwards compatibility reference. You will ### set_focus +### set_ibeam_color + + ### set_size @@ -3040,6 +3055,18 @@ Example: If first row has a `VPush`, then your layout will be At the bottom of ### start_thread +### timer_get_active_timers + + +### timer_start + + +### timer_stop + + +### timer_stop_all + + ### un_hide diff --git a/readme_creator/output/call reference.md b/readme_creator/output/call reference.md index b2037bda..e909d3e7 100644 --- a/readme_creator/output/call reference.md +++ b/readme_creator/output/call reference.md @@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30.
- @@ -126,7 +126,7 @@ Parameter Descriptions: | (str, str) or str | mouseover_colors | Important difference between Linux & Windows! Linux - Colors when mouse moved over button. Windows - colors when button is pressed. The default is to switch the text and background colors (an inverse effect) | | bool | use_ttk_buttons | True = use ttk buttons. False = do not use ttk buttons. None (Default) = use ttk buttons only if on a Mac and not with button images | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | If True the return key will cause this button to be pressed | +| bool | bind_return_key | If True then pressing the return key in an Input or Multiline Element will cause this button to appear to be clicked (generates event with this button's key | | bool | focus | if True, initial focus will be put on this button | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -1305,6 +1305,7 @@ Checkbox(text, background_color = None, text_color = None, checkbox_color = None, + highlight_thickness = 1, change_submits = False, enable_events = False, disabled = False, @@ -1324,28 +1325,29 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | text | Text to display next to checkbox | -| bool | default | Set to True if you want this checkbox initially checked | -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| bool | auto_size_text | if True will size the element to match the length of the text | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| str | background_color | color of background | -| str | text_color | color of the text | -| str | checkbox_color | color of background of the box that has the check mark in it. The checkmark is the same color as the text | -| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | -| bool | enable_events | Turns on the element specific events. Checkbox events happen when an item changes | -| bool | disabled | set disable state | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| str | tooltip | text, that will appear when mouse hovers over the element | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | +| str | text | Text to display next to checkbox | +| bool | default | Set to True if you want this checkbox initially checked | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| bool | auto_size_text | if True will size the element to match the length of the text | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| str | background_color | color of background | +| str | text_color | color of the text | +| str | checkbox_color | color of background of the box that has the check mark in it. The checkmark is the same color as the text | +| int | highlight_thickness | thickness of border around checkbox when gets focus | +| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | +| bool | enable_events | Turns on the element specific events. Checkbox events happen when an item changes | +| bool | disabled | set disable state | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| str | tooltip | text, that will appear when mouse hovers over the element | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | ### bind @@ -2214,7 +2216,7 @@ Parameter Descriptions: | str | text_color | color of the text | | str | button_background_color | The color of the background of the button on the combo box | | str | button_arrow_color | The color of the arrow on the button on the combo box | -| bool | bind_return_key | If True, then the return key will cause a the Combo to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the Combo to generate an event when return key is pressed | | bool | change_submits | DEPRICATED DO NOT USE. Use `enable_events` instead | | bool | enable_events | Turns on the element specific events. Combo event is when a choice is made | | bool | enable_per_char_events | Enables generation of events for every character that's input. This is like the Input element's events | @@ -2481,22 +2483,26 @@ update(value = None, font = None, visible = None, size = (None, None), - select = None) + select = None, + text_color = None, + background_color = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | value | change which value is current selected based on new list of previous list of choices | -| List[Any] | values | change list of choices | -| int | set_to_index | change selection to a particular choice starting with index = 0 | -| bool | disabled | disable or enable state of the element | -| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | visible | control visibility of element | -| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | -| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| Any | value | change which value is current selected based on new list of previous list of choices | +| List[Any] | values | change list of choices | +| int | set_to_index | change selection to a particular choice starting with index = 0 | +| bool | disabled | disable or enable state of the element | +| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| bool | visible | control visibility of element | +| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | +| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| str | background_color | color of background | +| str | text_color | color of the text | ### visible @@ -2586,22 +2592,26 @@ Update(value = None, font = None, visible = None, size = (None, None), - select = None) + select = None, + text_color = None, + background_color = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | value | change which value is current selected based on new list of previous list of choices | -| List[Any] | values | change list of choices | -| int | set_to_index | change selection to a particular choice starting with index = 0 | -| bool | disabled | disable or enable state of the element | -| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | visible | control visibility of element | -| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | -| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| Any | value | change which value is current selected based on new list of previous list of choices | +| List[Any] | values | change list of choices | +| int | set_to_index | change selection to a particular choice starting with index = 0 | +| bool | disabled | disable or enable state of the element | +| bool | readonly | if True make element readonly (user cannot change any choices). Enables the element if either choice are made. | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| bool | visible | control visibility of element | +| (int, int) | size | width, height. Width = characters-wide, height = NOTE it's the number of entries to show in the list | +| bool | select | if True, then the text will be selected, if False then selection will be cleared | +| str | background_color | color of background | +| str | text_color | color of the text | --------- @@ -4881,6 +4891,8 @@ Input(default_text = "", readonly = False, disabled_readonly_background_color = None, disabled_readonly_text_color = None, + selected_text_color = None, + selected_background_color = None, expand_x = False, expand_y = False, right_click_menu = None, @@ -4915,6 +4927,8 @@ Parameter Descriptions: | bool | readonly | If True tkinter state set to 'readonly'. Use this in place of use_readonly_for_disable as another way of achieving readonly. Note cannot set BOTH readonly and disabled as tkinter only supplies a single flag | | str | disabled_readonly_background_color | If state is set to readonly or disabled, the color to use for the background | | str | disabled_readonly_text_color | If state is set to readonly or disabled, the color to use for the text | +| str | selected_text_color | Color of text when it is selected (using mouse or control+A, etc) | +| str | selected_background_color | Color of background when it is selected (using mouse or control+A, etc) | | bool | expand_x | If True the element will automatically expand in the X direction to fill available space | | bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | | List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | @@ -5096,6 +5110,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -5359,7 +5389,7 @@ Parameter Descriptions: | [enum] | select_mode | Select modes are used to determine if only 1 item can be selected or multiple and how they can be selected. Valid choices begin with "LISTBOX_SELECT_MODE_" and include: LISTBOX_SELECT_MODE_SINGLE LISTBOX_SELECT_MODE_MULTIPLE LISTBOX_SELECT_MODE_BROWSE LISTBOX_SELECT_MODE_EXTENDED | | bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | | bool | enable_events | Turns on the element specific events. Listbox generates events when an item is clicked | -| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event when return key is pressed | | (int, int) or (int, None) or int | size | w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | disabled | set disable state for element | @@ -5553,6 +5583,24 @@ Metadata is an Element property that you can use at any time to hold any value |---|---|---| |(Any)| **return** | the current metadata value | +### select_index + +Selects an index while providing capability to setting the selected color for the index to specific text/background color + +``` +select_index(index, + highlight_text_color = None, + highlight_background_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | index | specifies which item to change. index starts at 0 and goes to length of values list minus one | +| str | highlight_text_color | color of the text when this item is selected. | +| str | highlight_background_color | color of the background when this item is selected | + ### set_cursor Sets the cursor for the current Element. @@ -5586,6 +5634,28 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_index_color + +Sets the color of a specific item without selecting it + +``` +set_index_color(index, + text_color = None, + background_color = None, + highlight_text_color = None, + highlight_background_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | index | specifies which item to change. index starts at 0 and goes to length of values list minus one | +| str | text_color | color of the text for this item | +| str | background_color | color of the background for this item | +| str | highlight_text_color | color of the text when this item is selected. | +| str | highlight_background_color | color of the background when this item is selected | + ### set_size Changes the size of an element to a specific size. @@ -6244,12 +6314,15 @@ Multiline(default_text = "", enter_submits = False, disabled = False, autoscroll = False, + autoscroll_only_at_bottom = False, border_width = None, size = (None, None), s = (None, None), auto_size_text = None, background_color = None, text_color = None, + selected_text_color = None, + selected_background_color = None, horizontal_scroll = False, change_submits = False, enable_events = False, @@ -6289,49 +6362,52 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | default_text | Initial text to show | -| bool | enter_submits | if True, the Window.read call will return is enter key is pressed in this element | -| bool | disabled | set disable state | -| bool | autoscroll | If True the contents of the element will automatically scroll as more data added to the end | -| int | border_width | width of border around element in pixels | -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| bool | auto_size_text | if True will size the element to match the length of the text | -| str | background_color | color of background | -| str | text_color | color of the text | -| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical | -| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | -| bool | enable_events | If True then any key press that happens when the element has focus will generate an event. | -| bool | do_not_clear | if False the element will be cleared any time the Window.read call returns | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| bool | write_only | If True then no entry will be added to the values dictionary when the window is read | -| bool | auto_refresh | If True then anytime the element is updated, the window will be refreshed so that the change is immediately displayed | -| bool | reroute_stdout | If True then all output to stdout will be output to this element | -| bool | reroute_stderr | If True then all output to stderr will be output to this element | -| bool | reroute_cprint | If True your cprint calls will output to this element. It's the same as you calling cprint_set_output_destination | -| bool | echo_stdout_stderr | If True then output to stdout and stderr will be output to this element AND also to the normal console location | -| bool | focus | if True initial focus will go to this element | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| str | tooltip | text, that will appear when mouse hovers over the element | -| str | justification | text justification. left, right, center. Can use single characters l, r, c. | -| bool | no_scrollbar | If False then a vertical scrollbar will be shown (the default) | -| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | -| str | sbar_trough_color | Scrollbar color of the trough | -| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | -| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | -| int | sbar_width | Scrollbar width in pixels | -| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | -| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | -| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | rstrip | If True the value returned in will have whitespace stripped from the right side | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | +| Any | default_text | Initial text to show | +| bool | enter_submits | if True, the Window.read call will return is enter key is pressed in this element | +| bool | disabled | set disable state | +| bool | autoscroll | If True the contents of the element will automatically scroll as more data added to the end | +| bool | autoscroll_only_at_bottom | If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline | +| int | border_width | width of border around element in pixels | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| bool | auto_size_text | if True will size the element to match the length of the text | +| str | background_color | color of background | +| str | text_color | color of the text | +| str | selected_text_color | Color of text when it is selected (using mouse or control+A, etc) | +| str | selected_background_color | Color of background when it is selected (using mouse or control+A, etc) | +| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True a horizontal scrollbar will be shown in addition to vertical | +| bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | +| bool | enable_events | If True then any key press that happens when the element has focus will generate an event. | +| bool | do_not_clear | if False the element will be cleared any time the Window.read call returns | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| bool | write_only | If True then no entry will be added to the values dictionary when the window is read | +| bool | auto_refresh | If True then anytime the element is updated, the window will be refreshed so that the change is immediately displayed | +| bool | reroute_stdout | If True then all output to stdout will be output to this element | +| bool | reroute_stderr | If True then all output to stderr will be output to this element | +| bool | reroute_cprint | If True your cprint calls will output to this element. It's the same as you calling cprint_set_output_destination | +| bool | echo_stdout_stderr | If True then output to stdout and stderr will be output to this element AND also to the normal console location | +| bool | focus | if True initial focus will go to this element | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| str | tooltip | text, that will appear when mouse hovers over the element | +| str | justification | text justification. left, right, center. Can use single characters l, r, c. | +| bool | no_scrollbar | If False then a vertical scrollbar will be shown (the default) | +| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | +| str | sbar_trough_color | Scrollbar color of the trough | +| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | +| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | +| int | sbar_width | Scrollbar width in pixels | +| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | +| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | +| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | rstrip | If True the value returned in will have whitespace stripped from the right side | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | ### bind @@ -6590,6 +6666,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -7200,11 +7292,11 @@ Parameter Descriptions: --------- -## Output Element (No longer recommended - USE `Multiline` instead) +## Output Element -Rather than use the `Output` element, it's recommended that you use the `Multiline` element instead. The reason for this is that more controls have been added to the Multiline and in the future you can expect more features will be added to the Multiline while the `Output` element has stopped being enhanced. +Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted. -You can control which Multiline Element receives your stdout output as well as use the color-print (`cprint`) with a Multiline. +Now based on the `Multiline` element Output Element - a multi-lined text area to where stdout, stderr, cprint are rerouted. @@ -7227,6 +7319,7 @@ Output(size = (None, None), text_color = None, pad = None, p = None, + autoscroll_only_at_bottom = False, echo_stdout_stderr = False, font = None, tooltip = None, @@ -7252,31 +7345,32 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | -| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | -| str | background_color | color of background | -| str | text_color | color of the text | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | -| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | -| bool | echo_stdout_stderr | If True then output to stdout will be output to this element AND also to the normal console location | -| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| str | tooltip | text, that will appear when mouse hovers over the element | -| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | -| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | -| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | -| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | -| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | -| bool | visible | set visibility state of the element | -| Any | metadata | User metadata that can be set to ANYTHING | -| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | -| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True, then line wrapping will be off by default | -| str | sbar_trough_color | Scrollbar color of the trough | -| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | -| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | -| int | sbar_width | Scrollbar width in pixels | -| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | -| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | -| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | +| (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | +| str | background_color | color of background | +| str | text_color | color of the text | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | +| (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | +| bool | autoscroll_only_at_bottom | If True the contents of the element will automatically scroll only if the scrollbar is at the bottom of the multiline | +| bool | echo_stdout_stderr | If True then output to stdout will be output to this element AND also to the normal console location | +| (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | +| str | tooltip | text, that will appear when mouse hovers over the element | +| str or int or tuple or object | key | Used with window.find_element and with return values to uniquely identify this element to uniquely identify this element | +| str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | +| List[List[ List[str] or str ]] | right_click_menu | A list of lists of Menu items to show when this element is right clicked. See user docs for exact format. | +| bool | expand_x | If True the element will automatically expand in the X direction to fill available space | +| bool | expand_y | If True the element will automatically expand in the Y direction to fill available space | +| bool | visible | set visibility state of the element | +| Any | metadata | User metadata that can be set to ANYTHING | +| bool | wrap_lines | If True, the lines will be wrapped automatically. Other parms affect this setting, but this one will override them all. Default is it does nothing and uses previous settings for wrapping. | +| bool | horizontal_scroll | Controls if a horizontal scrollbar should be shown. If True, then line wrapping will be off by default | +| str | sbar_trough_color | Scrollbar color of the trough | +| str | sbar_background_color | Scrollbar color of the background of the arrow buttons at the ends AND the color of the "thumb" (the thing you grab and slide). Switches to arrow color when mouse is over | +| str | sbar_arrow_color | Scrollbar color of the arrow at the ends of the scrollbar (it looks like a button). Switches to background color when mouse is over | +| int | sbar_width | Scrollbar width in pixels | +| int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | +| str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | +| str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | ### bind @@ -8902,7 +8996,7 @@ Parameter Descriptions: |--|--|--| | int | h_pixels | number of horizontal pixels | | int | v_pixels | number of vertical pixels | -| (Column) | **RETURN** | (Column) A column element that has a pad setting set according to parameters +| (Canvas) | **RETURN** | (Canvas) A canvas element that has a pad setting set according to parameters ------- @@ -9616,7 +9710,7 @@ Parameter Descriptions: | (int, int) or (None, None) or int | size | (w, h) w=characters-wide, h=rows-high. If an int instead of a tuple is supplied, then height is auto-set to 1 | | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | auto_size_text | if True will size the element to match the length of the text | -| bool | bind_return_key | If True, then the return key will cause a the element to generate an event | +| bool | bind_return_key | If True, then the return key will cause a the element to generate an event when return key is pressed | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | background_color | color of background | | str | text_color | color of the text | @@ -9809,6 +9903,22 @@ Parameter Descriptions: |--|--|--| | bool | force | if True will call focus_force otherwise calls focus_set | +### set_ibeam_color + +Sets the color of the I-Beam that is used to "insert" characters. This is oftens called a "Cursor" by +many users. To keep from being confused with tkinter's definition of cursor (the mouse pointer), the term +ibeam is used in this case. + +``` +set_ibeam_color(ibeam_color = None) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| str | ibeam_color | color to set the "I-Beam" used to indicate where characters will be inserted | + ### set_size Changes the size of an element to a specific size. @@ -10980,14 +11090,14 @@ Parameter Descriptions: Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on the tab if no key is defined. Returns None if an error occurs. -Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you +Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you are using this method correctly? `get()` |Type|Name|Meaning| |---|---|---| -|Any | None| **return** | The key of the currently selected tab or the tab's text if it has no key | +|Any | None| **return** | The key of the currently selected tab or None if there is an error | ### get_next_focus @@ -11212,14 +11322,14 @@ Parameter Descriptions: Returns the current value for the Tab Group, which will be the currently selected tab's KEY or the text on the tab if no key is defined. Returns None if an error occurs. -Note that this is exactly the same data that would be returned from a call to Window.Read. Are you sure you +Note that this is exactly the same data that would be returned from a call to Window.read. Are you sure you are using this method correctly? `Get()` |Type|Name|Meaning| |---|---|---| -|Any | None| **return** | The key of the currently selected tab or the tab's text if it has no key | +|Any | None| **return** | The key of the currently selected tab or None if there is an error | ### SetFocus @@ -11420,15 +11530,13 @@ Parameter Descriptions: ### get -Dummy function for tkinter port. In the Qt port you can read back the values in the table in case they were -edited. Don't know yet how to enable editing of a Tree in tkinter so just returning the values provided by -user when Table was created or Updated. +Get the selected rows using tktiner's selection method. Returns a list of the selected rows. `get()` |Type|Name|Meaning| |---|---|---| -|List[List[Any]]| **return** | the current table values (for now what was originally provided up updated) | +|List[int]| **return** | a list of the index of the selected rows (a list of ints) | ### get_next_focus @@ -11652,15 +11760,13 @@ The following methods are here for backwards compatibility reference. You will ### Get -Dummy function for tkinter port. In the Qt port you can read back the values in the table in case they were -edited. Don't know yet how to enable editing of a Tree in tkinter so just returning the values provided by -user when Table was created or Updated. +Get the selected rows using tktiner's selection method. Returns a list of the selected rows. `Get()` |Type|Name|Meaning| |---|---|---| -|List[List[Any]]| **return** | the current table values (for now what was originally provided up updated) | +|List[int]| **return** | a list of the index of the selected rows (a list of ints) | ### SetFocus @@ -12194,6 +12300,7 @@ Tree(data = None, show_expanded = False, change_submits = False, enable_events = False, + click_toggles_select = None, font = None, justification = "right", text_color = None, @@ -12245,6 +12352,7 @@ Parameter Descriptions: | bool | show_expanded | if True then the tree will be initially shown with all nodes completely expanded | | bool | change_submits | DO NOT USE. Only listed for backwards compat - Use enable_events instead | | bool | enable_events | Turns on the element specific events. Tree events happen when row is clicked | +| bool | click_toggles_select | If True then clicking a row will cause the selection for that row to toggle between selected and deselected | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | justification | 'left', 'right', 'center' are valid choices | | str | text_color | color of the text | @@ -13044,6 +13152,7 @@ Window(title, sbar_arrow_width = None, sbar_frame_color = None, sbar_relief = None, + watermark = None, metadata = None) ``` @@ -13111,6 +13220,7 @@ Parameter Descriptions: | int | sbar_arrow_width | Scrollbar width of the arrow on the scrollbar. It will potentially impact the overall width of the scrollbar | | str | sbar_frame_color | Scrollbar Color of frame around scrollbar (available only on some ttk themes) | | str | sbar_relief | Scrollbar relief that will be used for the "thumb" of the scrollbar (the thing you grab that slides). Should be a constant that is defined at starting with "RELIEF_" - RELIEF_RAISED, RELIEF_SUNKEN, RELIEF_FLAT, RELIEF_RIDGE, RELIEF_GROOVE, RELIEF_SOLID | +| bool | watermark | If True, then turns on watermarking temporarily for ALL windows created from this point forward. See global settings doc for more info | | Any | metadata | User metadata that can be set to ANYTHING | ### add_row @@ -13615,15 +13725,15 @@ IMPORTANT - This method uses THREADS... this means you CANNOT make any PySimpleG the function you provide with the exception of one function, Window.write_event_value. ``` -perform_long_operation(func, end_key) +perform_long_operation(func, end_key = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | func | A lambda or a function name with no parms | -| Any | end_key | The key that will be generated when the function returns | +| Any | func | A lambda or a function name with no parms | +| (Any or None) | end_key | Optional key that will be generated when the function returns | | threading.Thread | **RETURN** | The id of the thread ### read @@ -13837,17 +13947,71 @@ IMPORTANT - This method uses THREADS... this means you CANNOT make any PySimpleG the function you provide with the exception of one function, Window.write_event_value. ``` -start_thread(func, end_key) +start_thread(func, end_key = None) ``` Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| Any | func | A lambda or a function name with no parms | -| Any | end_key | The key that will be generated when the function returns | +| Any | func | A lambda or a function name with no parms | +| (Any or None) | end_key | Optional key that will be generated when the function returns | | threading.Thread | **RETURN** | The id of the thread +### timer_get_active_timers + +Returns a list of currently active timers for a window + +`timer_get_active_timers()` + +|Type|Name|Meaning| +|---|---|---| +|List[int]| **return** | List of timers for the window | + +### timer_start + +Starts a timer that gnerates Timer Events. The default is to repeat the timer events until timer is stopped. +You can provide your own key or a default key will be used. The default key is defined +with the constants EVENT_TIMER or TIMER_KEY. They both equal the same value. +The values dictionary will contain the timer ID that is returned from this function. + +``` +timer_start(frequency_ms, + key = "__TIMER EVENT__", + repeating = True) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | frequency_ms | How often to generate timer events in milliseconds | +| str or int or tuple or object | key | Key to be returned as the timer event | +| bool | repeating | If True then repeat timer events until timer is explicitly stopped | +| int | **RETURN** | Timer ID for the timer + +### timer_stop + +Stops a timer with a given ID + +``` +timer_stop(timer_id) +``` + +Parameter Descriptions: + +|Type|Name|Meaning| +|--|--|--| +| int | timer_id | Timer ID of timer to stop | + +### timer_stop_all + +Stops all timers for THIS window + +```python +timer_stop_all() +``` + ### un_hide Used to bring back a window that was previously hidden using the Hide method @@ -14911,7 +15075,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -14919,6 +15083,7 @@ Parameter Descriptions: | str or int or tuple or object | k | Same as the Key. You can use either k or key. Which ever is set will be used. | | str | locale | defines the locale used to get day names | | str | format | formats result using this strftime format | +| int | begin_at_sunday_plus | Determines the left-most day in the display. 0=sunday, 1=monday, etc | | List[str] | month_names | optional list of month names to use (should be 12 items) | | List[str] | day_abbreviations | optional list of abbreviations to display as the day of week | | str | title | Title shown on the date chooser window | @@ -14963,7 +15128,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15021,7 +15186,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | Determines if initial focus should go to this element. | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15072,7 +15237,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | tooltip | text, that will appear when mouse hovers over the element | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15134,7 +15299,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15179,7 +15344,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15432,7 +15597,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | str | tooltip | text, that will appear when mouse hovers over the element | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15520,7 +15685,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15565,7 +15730,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15610,7 +15775,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15657,7 +15822,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15712,7 +15877,7 @@ Parameter Descriptions: | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15754,7 +15919,7 @@ Parameter Descriptions: | (int, int) or (None, None) or int | s | Same as size parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, size will be used | | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | @@ -15852,7 +16017,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | bool | disabled | set disable state for element (Default = False) | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | | idk_yetReally | focus | if focus should be set to this | @@ -15899,7 +16064,7 @@ Parameter Descriptions: | bool | disabled | set disable state for element (Default = False) | | str | tooltip | text, that will appear when mouse hovers over the element | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = True) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = True) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | p | Same as pad parameter. It's an alias. If EITHER of them are set, then the one that's set will be used. If BOTH are set, pad will be used | @@ -15956,7 +16121,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16009,7 +16174,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16067,7 +16232,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16124,7 +16289,7 @@ Parameter Descriptions: | bool | auto_size_button | True if button size is determined by button text | | (str, str) or str | button_color | button color (foreground, background) | | (str or (str, int[, str]) or None) | font | specifies the font family, size, etc. Tuple or Single string format 'name size styles'. Styles: italic * roman bold normal underline overstrike | -| bool | bind_return_key | (Default = False) If True, then the return key will cause a the Listbox to generate an event | +| bool | bind_return_key | (Default = False) If True, this button will appear to be clicked when return key is pressed in other elements such as Input and elements with return key options | | bool | disabled | set disable state for element (Default = False) | | idk_yetReally | focus | if focus should be set to this | | (int, int or (int, int),(int,int) or int,(int,int)) or ((int, int),int) or int | pad | Amount of padding to put around element in pixels (left/right, top/bottom) or ((left, right), (top, bottom)) or an int. If an int, then it's converted into a tuple (int, int) | @@ -16571,11 +16736,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -16625,11 +16790,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -16738,7 +16903,8 @@ popup_animated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -16760,6 +16926,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed Popup that closes itself after some time period @@ -16942,6 +17109,9 @@ popup_get_date(start_mon = None, locale = None, month_names = None, day_abbreviations = None, + day_font = "TkFixedFont 9", + mon_year_font = "TkFixedFont 10", + arrow_font = "TkFixedFont 7", modal = True) ``` @@ -16963,6 +17133,9 @@ Parameter Descriptions: | bool | keep_on_top | If True the window will remain above all current windows | | List[str] | month_names | optional list of month names to use (should be 12 items) | | List[str] | day_abbreviations | optional list of abbreviations to display as the day of week | +| str or tuple | day_font | Font and size to use for the calendar | +| str or tuple | mon_year_font | Font and size to use for the month and year at the top | +| str or tuple | arrow_font | Font and size to use for the arrow buttons | | bool | modal | If True then makes the popup will behave like a Modal window... all other windows are non-operational until this one is closed. Default = True | | None or (int, int, int) | **RETURN** | Tuple containing (month, day, year) of chosen date or None if was cancelled @@ -17816,7 +17989,8 @@ PopupAnimated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -17838,6 +18012,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed Display a Popup without a titlebar. Enables grab anywhere so you can move it @@ -19704,7 +19879,7 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | command | Filename to load settings from (and save to in the future) | +| str | command | The command/file to execute. What you would type at a console to run a program or shell command. | | Any | *args | Variable number of arguments that are passed to the program being started as command line parms | | bool | wait | If True then wait for the subprocess to finish | | str | cwd | Working directory to use when executing the subprocess | @@ -19960,7 +20135,8 @@ set_options(icon = None, sbar_relief = None, alpha_channel = None, hide_window_when_creating = None, - use_button_shortcuts = None) + use_button_shortcuts = None, + watermark_text = None) ``` Parameter Descriptions: @@ -20033,6 +20209,7 @@ Parameter Descriptions: | float | alpha_channel | Default alpha channel to be used on all windows | | bool | hide_window_when_creating | If True then alpha will be set to 0 while a window is made and moved to location indicated | | bool | use_button_shortcuts | If True then Shortcut Char will be used with Buttons | +| str | watermark_text | Set the text that will be used if a window is watermarked | | None | **RETURN** | None ### Non PEP8 versions @@ -20116,7 +20293,8 @@ SetOptions(icon = None, sbar_relief = None, alpha_channel = None, hide_window_when_creating = None, - use_button_shortcuts = None) + use_button_shortcuts = None, + watermark_text = None) ``` Parameter Descriptions: @@ -20189,6 +20367,7 @@ Parameter Descriptions: | float | alpha_channel | Default alpha channel to be used on all windows | | bool | hide_window_when_creating | If True then alpha will be set to 0 while a window is made and moved to location indicated | | bool | use_button_shortcuts | If True then Shortcut Char will be used with Buttons | +| str | watermark_text | Set the text that will be used if a window is watermarked | | None | **RETURN** | None ## Old Themes (Look and Feel) - Replaced by theme() diff --git a/readme_creator/output/index.md b/readme_creator/output/index.md index 52331638..fd28ee9a 100644 --- a/readme_creator/output/index.md +++ b/readme_creator/output/index.md @@ -1,7 +1,7 @@
- Python GUIs for Humans -

Python GUIs for Humans

+ User Interfaces for Humans +

User Interfaces for HumansTM

@@ -25,9 +25,9 @@ apply coupon for discount: - 266B9C51C90B3728782E + 522B20BF5EF123C4AB30
-
@@ -57,7 +57,7 @@ # PySimpleGUI User's Manual -## Python GUI For Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces +## User Interfaces for Humans - Transforms tkinter, Qt, Remi, WxPython into portable people-friendly Pythonic interfaces ## The Call Reference Section Moved to here @@ -2028,7 +2028,8 @@ popup_animated(image_source, time_between_frames = 0, transparent_color = None, title = "", - icon = None) + icon = None, + no_buffering = False) ``` Parameter Descriptions: @@ -2050,6 +2051,7 @@ Parameter Descriptions: | str | transparent_color | This color will be completely see-through in your window. Can even click through | | str | title | Title that will be shown on the window | | str or bytes | icon | Same as Window icon parameter. Can be either a filename or Base64 byte string. For Windows if filename, it MUST be ICO format. For Linux, must NOT be ICO | +| bool | no_buffering | If True then no buffering will be used for the GIF. May work better if you have a large animation | | bool | **RETURN** | True if the window updated OK. False if the window was closed ***To close animated popups***, call PopupAnimated with `image_source=None`. This will close all of the currently open PopupAnimated windows. @@ -2078,11 +2080,11 @@ Parameter Descriptions: |Type|Name|Meaning| |--|--|--| -| str | title | text to display in eleemnt | +| str | title | text to display in titlebar of window | | int | current_value | current value | -| int | max_value | max value of QuickMeter | -| Any | *args | stuff to output | -| str or int or tuple or object | key | Used to differentiate between mutliple meters. Used to cancel meter early. Now optional as there is a default value for single meters | +| int | max_value | max value of progress meter | +| Any | *args | stuff to output as text in the window along with the meter | +| str or int or tuple or object | key | Used to differentiate between multiple meters. Used to cancel meter early. Now optional as there is a default value for single meters | | str | orientation | 'horizontal' or 'vertical' ('h' or 'v' work) (Default value = 'vertical' / 'v') | | (str, str) or str | bar_color | The 2 colors that make up a progress bar. Either a tuple of 2 strings or a string. Tuple - (bar, background). A string with 1 color changes the background of the bar only. A string with 2 colors separated by "on" like "red on blue" specifies a red bar on a blue background. | | (str, str) or str | button_color | button color (foreground, background) | @@ -7112,12 +7114,54 @@ There are a number of demo programs that show how to use UserSettings to create If you're using the default path, remember that previous runs of your file may have old settings that are still in your settings file. It can get confusing when you've forgotten that you previously wrote a setting. Not seeing the filename can have drawbacks like this. -Also, because the settings automatically save after every update, it can be easy to accidently overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. +Also, because the settings automatically save after every update, it can be easy to accidentally overwrite a previously saved setting. If you want to avoid this, then perhaps it's best that you work with a dictionary within your code and then explicitly save your dictionary when you're ready to commit it to disk. To save your Python dictionary to a settings file, simply call `user_settings_write_new_dictionary(dict)`, passing in your dictionary as the parameter. ------------------------- +# Timer API + +The Timer API calls are in version 4.61.0 that is currently only available on the PySimpleGUI GitHub. It has not been released to PyPI yet. + +## Do Not Use Sleeps In Your Event Loop... + +Instead of sleeping, you can request that an event be generated after some period of time. If you need to "Sleep for 3 seconds" as part of some operation, instead schedule a timer for 3 seconds. Your `window.read` call will return a value of `sg.TIMER_KEY` or `sg.EVENT_TIMER` (they are aliases and thus have the same value). + +## Timer API Calls + +These are the API calls that you'll use to manage timers: + +`window.timer_start` starts a timer + +`window.timer_stop` stops a single timer + +`window.timer_stop_all` stops all timers + +`window.timer_get_active_timers` returns a list of active timer IDs + +Example - start a 3 second timer that does not repeat: + +```python +window.timer_start(3000, repeating=False) # start a 3-second timer +``` + +When this timer expires, you'll get an event `sg.EVENT_TIMER`. If you want to specify your own key to be returned, then use the `key` parameter: + +```python +window.timer_start(3000, key='-MY TIMER KEY-', repeating=False) +``` + +See the call reference for the details of each call. + +## Timer Demo Programs + +Using the PySimpleGUI Demo Browser, search for `window.timer_` to find Demo Programs that use the Timer API calls. + +The program `Demo_WindowTimer.py` demonstrates both repeating and non-repeating timers as well as using custom keys. It's a simple set of API calls to use and the docstrings in combination with the Demo Programs should give you all you need to start using this capability. + +--------------------------- + # Extending PySimpleGUI PySimpleGUI doesn't and can't provide every single setting available in the underlying GUI framework. Not all tkinter options are available for a `Text` Element. Same with PySimpleGUIQt and the other ports. @@ -10092,6 +10136,22 @@ Test Harness and Settings Windows fit on small screens better * Emergency Patch Release for Mac OS 12.3 and greater * Fixed bug in Mac OS version check in yesterday's 4.60.2 release +## 4.60.4 PySimpleGUI 10-Oct-2022 + +* Dot release to quickly fix the Trinket detection which stopped working recently + +## 4.60.5 PySimpleGUI 21-May-2023 + +* Mac fixes + * Fix for Input Element not working in no-titlebar windows on MacOs 13.2.1 + * Change to the 0.99 Alpha fix made in 4.60.2. Now only applies patch when running on 8.6.12, regardless of Mac Control Panel setting in PySimpleGUI Global Settings. Removes the need for users to disable when upgrading tkinter. +* Added Intelligent Upgrade Service - inform users when there are releases of PySimpleGUI that fix a problem that may be unique to their combination of components +* Change to GitHub Issue GUI + * Added checkbox for checking if running latest PyPI version + * Recommended using Demo Browser to search Demo Programs + * Use platform module to fill in the OS information field +* SDK Help Window - changed all readthedocs links to use the PySimpleGUI.org hostname for better portability + ## Code Condition Make it run diff --git a/readthedocs.yml b/readthedocs.yml index 47eaa0a4..d05298fb 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,7 +1,12 @@ version: 2 +version: 2 +build: + os: "ubuntu-22.04" + tools: + python: "3.11" + python: - version: 3.6 install: - requirements: docs/requirements.txt