diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py index 21f27eea..925abbbc 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Square.py @@ -27,7 +27,7 @@ sg.theme(THEME) graph = sg.Graph(GSIZE, (0, 0), GSIZE, key='-GRAPH-', enable_events=True) layout = [[graph]] -window = sg.Window('CPU Usage', layout, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True) +window = sg.Window('CPU Usage Widget Square', layout, no_titlebar=True, grab_anywhere=True, margins=(0, 0), element_padding=(0, 0), alpha_channel=ALPHA, finalize=True) text_id2 = graph.draw_text(f'CPU', (GSIZE[0] // 2, GSIZE[1] // 4), font='Any 20', text_location=sg.TEXT_LOCATION_CENTER, color=sg.theme_button_color()[0]) diff --git a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py b/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py similarity index 98% rename from DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py rename to DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py index e55999e6..6ef0fc8e 100644 --- a/DemoPrograms/Demo_Desktop_Widget_CPU_Utilization.py +++ b/DemoPrograms/Demo_Desktop_Widget_CPU_Top_Processes.py @@ -52,7 +52,7 @@ def main(): ] ] - window = sg.Window('CPU Utilization', layout, + window = sg.Window('Top CPU Processes', layout, no_titlebar=True, keep_on_top=True, use_default_focus=False, alpha_channel=.8, grab_anywhere=True) # start cpu measurement thread diff --git a/DemoPrograms/Demo_Desktop_Widget_Drive_Usage.py b/DemoPrograms/Demo_Desktop_Widget_Drive_Usage.py index 3319cac7..b7c94bf0 100644 --- a/DemoPrograms/Demo_Desktop_Widget_Drive_Usage.py +++ b/DemoPrograms/Demo_Desktop_Widget_Drive_Usage.py @@ -58,7 +58,7 @@ def main(): layout += [[sg.Text('Refresh', font='Any 8', key='-REFRESH-', enable_events=True), sg.Text('❎', enable_events=True, key='Exit Text')]] # ---------------- Create Window ---------------- - window = sg.Window('Drive status', layout, keep_on_top=True, grab_anywhere=True, no_titlebar=True, alpha_channel=ALPHA, use_default_focus=False, + window = sg.Window('Drive Status Widget', layout, keep_on_top=True, grab_anywhere=True, no_titlebar=True, alpha_channel=ALPHA, use_default_focus=False, finalize=True) update_window(window) # sets the progress bars diff --git a/DemoPrograms/Demo_Save_Windows_As_Images.py b/DemoPrograms/Demo_Save_Windows_As_Images.py new file mode 100644 index 00000000..170a42d7 --- /dev/null +++ b/DemoPrograms/Demo_Save_Windows_As_Images.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python +import PySimpleGUI as sg +import os +import psutil +import win32api +import win32con +import win32gui +import win32process +import cv2 +from PIL import ImageGrab +import numpy as np + + +def convert_string_to_tuple(string): + """ + Converts a string that represents a tuple. These strings have the format: + "('item 1', 'item 2')" + The desired return value is ('item 1', 'item 2') + :param string: + :return: + """ + parts = string[1:-1].split(',') + part1 = parts[0][1:-1] + part2 = parts[1][2:-1] + return part1, part2 + + +def show_list_by_name(window, output_key, python_only): + process_list = get_window_list() + + title_list = [] + for proc in process_list: + names = convert_string_to_tuple(proc) + if python_only and names[0] == 'python.exe': + title_list.append(names[1]) + elif not python_only: + title_list.append(names[1]) + title_list.sort() + window[output_key].update(title_list) + return title_list + + +def get_window_list(): + titles = [] + t = [] + pidList = [(p.pid, p.name()) for p in psutil.process_iter()] + + def enumWindowsProc(hwnd, lParam): + """ append window titles which match a pid """ + if (lParam is None) or ((lParam is not None) and (win32process.GetWindowThreadProcessId(hwnd)[1] == lParam)): + text = win32gui.GetWindowText(hwnd) + if text: + wStyle = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE) + if wStyle & win32con.WS_VISIBLE: + t.append("%s" % (text)) + return + + def enumProcWnds(pid=None): + win32gui.EnumWindows(enumWindowsProc, pid) + + for pid, pName in pidList: + enumProcWnds(pid) + if t: + for title in t: + titles.append("('{0}', '{1}')".format(pName, title)) + t = [] + titles = sorted(titles, key=lambda x: x[0].lower()) + return titles + + +def save_win(filename=None, title=None, crop=True): + """ + Saves a window with the title provided as a file using the provided filename. + If one of them is missing, then a window is created and the information collected + + :param filename: + :param title: + :return: + """ + C = 7 if crop else 0 # pixels to crop + if filename is None or title is None: + layout = [[sg.T('Choose window to save', font='Any 18')], + [sg.T('The extension you choose for filename will determine the image format')], + [sg.T('Window Title:', size=(12, 1)), sg.I(title if title is not None else '', key='-T-')], + [sg.T('Filename:', size=(12, 1)), sg.I(filename if filename is not None else '', key='-F-')], + [sg.Button('Ok', bind_return_key=True), sg.Button('Cancel')]] + event, values = sg.Window('Choose Win Title and Filename', layout).read(close=True) + if event != 'Ok': # if cancelled or closed the window + print('Cancelling the save') + return + filename, title = values['-F-'], values['-T-'] + try: + fceuxHWND = win32gui.FindWindow(None, title) + rect = win32gui.GetWindowRect(fceuxHWND) + rect_cropped = (rect[0] + C, rect[1], rect[2] - C, rect[3] - C) + frame = np.array(ImageGrab.grab(bbox=rect_cropped), dtype=np.uint8) + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + cv2.imwrite(filename, frame) + sg.cprint('Wrote image to file:', filename) + except Exception as e: + sg.popup('Error trying to save screenshot file', e, keep_on_top=True) + + +def main(): + layout = [[sg.Text('Window Snapshot', key='-T-', font='Any 20', justification='c')], + [sg.Listbox(values=[' '], size=(50, 20), select_mode=sg.SELECT_MODE_EXTENDED, font=('Courier', 12), key='-PROCESSES-')], + [sg.Checkbox('Show only Python programs', default=True, key='-PYTHON ONLY-')], + [sg.Checkbox('Crop image', default=True, key='-CROP-')], + [sg.Multiline(size=(63, 10), font=('Courier', 10), key='-ML-')], + [sg.Text('Output folder:'), sg.In(os.path.dirname(__file__), key='-FOLDER-'), sg.FolderBrowse()], + [sg.Button('Refresh'), + sg.Button('Snapshot', button_color=('white', 'DarkOrange2')), + sg.Exit(button_color=('white', 'sea green'))]] + + window = sg.Window('Window Snapshot', layout, keep_on_top=True, auto_size_buttons=False, default_button_element_size=(12, 1), finalize=True) + + window['-T-'].expand(True, False, False) # causes text to center by expanding the element + + sg.cprint_set_output_destination(window, '-ML-') + show_list_by_name(window, '-PROCESSES-', True) + + # ---------------- main loop ---------------- + while True: + # --------- Read and update window -------- + event, values = window.read() + if event in (sg.WIN_CLOSED, 'Exit'): + break + + # --------- Do Button Operations -------- + if event == 'Refresh': + show_list_by_name(window, '-PROCESSES-', values['-PYTHON ONLY-']) + elif event == 'Snapshot': + for title in values['-PROCESSES-']: + sg.cprint('Saving: ', end='', c='white on red') + sg.cprint(title) + output_filename = os.path.join(values['-FOLDER-'], f'{title}.png') + save_win(output_filename, title, values['-CROP-']) + window.close() + + +if __name__ == "__main__": + main() diff --git a/PySimpleGUI.py b/PySimpleGUI.py index 56fd6f8e..d7800726 100644 --- a/PySimpleGUI.py +++ b/PySimpleGUI.py @@ -1,6 +1,6 @@ #!/usr/bin/python3 -version = __version__ = "4.24.0.1 Unreleased\nAdded k parameter to buttons, new text wrapping behavior for popups, new docstring for keys" +version = __version__ = "4.24.0.2 Unreleased\nAdded k parameter to buttons, new text wrapping behavior for popups, new docstring for keys, new single-string button_color format ('white on red')" port = 'PySimpleGUI' @@ -2885,7 +2885,7 @@ class Button(Element): :param auto_size_button: if True the button size is sized to fit the text :type auto_size_button: (bool) :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green". - :type button_color: Tuple[str, str] == (text color, background color) + :type button_color: Tuple[str, str] or (str) == (text color, background color) :param disabled_button_color: colors to use when button is disabled (text, background). Use None for a color if don't want to change. Only ttk buttons support both text and background colors. tk buttons only support changing text color :type disabled_button_color: Tuple[str, str] :param 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 @@ -2914,7 +2914,16 @@ class Button(Element): self.Widget = self.TKButton = None # type: tk.Button self.Target = target self.ButtonText = str(button_text) - self.ButtonColor = button_color if button_color != None else DEFAULT_BUTTON_COLOR + # Button colors can be a tuple (text, background) or a string with format "text on background" + if button_color is None: + button_color = DEFAULT_BUTTON_COLOR + else: + try: + if isinstance(button_color,str): + button_color = button_color.split(' on ') + except Exception as e: + print('* cprint warning * you messed up with color formatting', e) + self.ButtonColor = button_color self.DisabledButtonColor = disabled_button_color if disabled_button_color is not None else (None, None) self.ImageFilename = image_filename self.ImageData = image_data @@ -3155,7 +3164,7 @@ class Button(Element): :param text: sets button text :type text: (str) :param button_color: of button. Easy to remember which is which if you say "ON" between colors. "red" on "green" - :type button_color: Tuple[str, str] == (text color, background color) + :type button_color: Tuple[str, str] or (str) :param disabled: disable or enable state of the element :type disabled: (bool) :param image_data: Raw or Base64 representation of the image to put on button. Choose either filename or data @@ -3182,6 +3191,11 @@ class Button(Element): self.TKButton.configure(text=text) self.ButtonText = text if button_color != (None, None): + if isinstance(button_color, str): + try: + button_color = button_color.split(' on ') + except Exception as e: + print('** Error in formatting your button color **', button_color, e) if self.UseTtkButtons: if button_color[0] is not None: button_style.configure(style_name, foreground=button_color[0]) @@ -16534,7 +16548,7 @@ def main(): [Button('Button'), B('Hide Stuff', metadata='my metadata'), Button('ttk Button', use_ttk_buttons=True, tooltip='This is a TTK Button'), Button('See-through Mode', tooltip='Make the background transparent'), - Button('Install PySimpleGUI from GitHub', button_color=('white', 'red') ,key='-INSTALL-'), + Button('Install PySimpleGUI from GitHub', button_color='white on red' ,key='-INSTALL-'), Button('Exit', tooltip='Exit button')], ]