From f9c0604787989a1a53930732825f4ea926c4ed28 Mon Sep 17 00:00:00 2001 From: Peter Story Date: Mon, 8 Jul 2024 15:02:55 -0400 Subject: [PATCH] Close "Received Files" modal if an error occurs For #13 --- src/pydiode/gui/common.py | 99 +++++++++++++++++++++++++++++++++++ src/pydiode/gui/receive.py | 102 +++---------------------------------- 2 files changed, 105 insertions(+), 96 deletions(-) diff --git a/src/pydiode/gui/common.py b/src/pydiode/gui/common.py index 9d9fd22..e1995c5 100755 --- a/src/pydiode/gui/common.py +++ b/src/pydiode/gui/common.py @@ -1,11 +1,107 @@ +import os +from pathlib import Path import signal +import subprocess import sys +from tkinter import Toplevel, ttk from tkinter.messagebox import showerror # Check subprocesses every SLEEP milliseconds SLEEP = 250 +class SavedWindow: + # A modal window shown after files were received + top = None + + # Whether to show the window. Configured as a BooleanVar in gui_main(). + should_show = None + + @classmethod + def on_ok(cls, event=None): + if cls.top: + cls.top.destroy() + + @classmethod + def on_show_files(cls, target_dir): + # Based on: https://stackoverflow.com/a/17317468/3043071 + if sys.platform == "win32": + os.startfile(target_dir) + else: + opener = "open" if sys.platform == "darwin" else "xdg-open" + subprocess.run([opener, target_dir]) + cls.top.destroy() + + @classmethod + def show_window(cls, root, target_dir): + # Only create a window if: + # - The user didn't permanently dismiss the window and + # - The window hasn't yet been created, or it was destroyed + if cls.should_show.get() and ( + not cls.top or not cls.top.winfo_exists() + ): + cls.top = Toplevel(root) + cls.top.grid_rowconfigure(0, weight=1) + cls.top.grid_columnconfigure(0, weight=1) + cls.top.title("Received Files") + + ttk.Label( + cls.top, text=f"Saved files to: {Path(target_dir).name}" + ).grid(column=0, row=0, columnspan=3, pady=(15, 0)) + + ttk.Checkbutton( + cls.top, + text="Do not show again", + variable=cls.should_show, + onvalue=False, + offvalue=True, + ).grid(column=0, row=1, padx=10, pady=10) + + show_files_button = ttk.Button( + cls.top, + text="Show Files", + command=lambda: cls.on_show_files(target_dir), + ) + show_files_button.grid(column=1, row=1, pady=10) + + ok_button = ttk.Button( + cls.top, text="OK", default="active", command=cls.on_ok + ) + ok_button.grid(column=2, row=1, padx=10, pady=10) + + # Dismiss if escape or return are pressed + cls.top.bind("", cls.on_ok) + cls.top.bind("", cls.on_ok) + + # Use modal style on macOS + if sys.platform == "darwin": + cls.top.tk.call( + "::tk::unsupported::MacWindowStyle", + "style", + cls.top._w, + "modal", + ) + + # Set the modal's minimum size, and center it over the main window. + # If the size exceeds these dimensions, the modal won't be + # perfectly centered. + width = 400 + height = 100 + cls.top.minsize(width=width, height=height) + x = root.winfo_x() + (root.winfo_width() // 2) - (width // 2) + y = root.winfo_y() + (root.winfo_height() // 2) - (height // 4) + cls.top.geometry(f"+{x}+{y}") + + # Prevent resizing + cls.top.resizable(False, False) + + # Stay on top of the main window + cls.top.transient(root) + + # Take focus + cls.top.grab_set() + + class ProcessPipeline: def __init__(self): @@ -190,6 +286,9 @@ def check_subprocesses( # If any subprocesses exited irregularly, describe the issue error_msgs = pipeline.get_process_errors() if error_msgs: + # Dismiss the "Received Files" window, if it's open + SavedWindow.on_ok() + # Show the error message showerror(title="Error", message=error_msgs) # Clear the pipeline, so it doesn't grow as more subprocesses are # started diff --git a/src/pydiode/gui/receive.py b/src/pydiode/gui/receive.py index 91a9374..b0a6b9f 100644 --- a/src/pydiode/gui/receive.py +++ b/src/pydiode/gui/receive.py @@ -1,12 +1,13 @@ -import os -from pathlib import Path import subprocess import sys -from tkinter import Toplevel, ttk from tkinter.filedialog import askdirectory -from tkinter.messagebox import showinfo -from pydiode.gui.common import check_subprocesses, ProcessPipeline, SLEEP +from pydiode.gui.common import ( + check_subprocesses, + ProcessPipeline, + SavedWindow, + SLEEP, +) # Information about our subprocesses RECEIVE_PIPELINE = ProcessPipeline() @@ -68,97 +69,6 @@ def receive_or_cancel( ) -class SavedWindow: - # A modal window shown after files were received - top = None - - # Whether to show the window. Configured as a BooleanVar in gui_main(). - should_show = None - - @classmethod - def on_ok(cls, event=None): - cls.top.destroy() - - @classmethod - def on_show_files(cls, target_dir): - # Based on: https://stackoverflow.com/a/17317468/3043071 - if sys.platform == "win32": - os.startfile(target_dir) - else: - opener = "open" if sys.platform == "darwin" else "xdg-open" - subprocess.run([opener, target_dir]) - cls.top.destroy() - - @classmethod - def show_window(cls, root, target_dir): - # Only create a window if: - # - The user didn't permanently dismiss the window and - # - The window hasn't yet been created, or it was destroyed - if cls.should_show.get() and ( - not cls.top or not cls.top.winfo_exists() - ): - cls.top = Toplevel(root) - cls.top.grid_rowconfigure(0, weight=1) - cls.top.grid_columnconfigure(0, weight=1) - cls.top.title("Received Files") - - ttk.Label( - cls.top, text=f"Saved files to: {Path(target_dir).name}" - ).grid(column=0, row=0, columnspan=3, pady=(15, 0)) - - ttk.Checkbutton( - cls.top, - text="Do not show again", - variable=cls.should_show, - onvalue=False, - offvalue=True, - ).grid(column=0, row=1, padx=10, pady=10) - - show_files_button = ttk.Button( - cls.top, - text="Show Files", - command=lambda: cls.on_show_files(target_dir), - ) - show_files_button.grid(column=1, row=1, pady=10) - - ok_button = ttk.Button( - cls.top, text="OK", default="active", command=cls.on_ok - ) - ok_button.grid(column=2, row=1, padx=10, pady=10) - - # Dismiss if escape or return are pressed - cls.top.bind("", cls.on_ok) - cls.top.bind("", cls.on_ok) - - # Use modal style on macOS - if sys.platform == "darwin": - cls.top.tk.call( - "::tk::unsupported::MacWindowStyle", - "style", - cls.top._w, - "modal", - ) - - # Set the modal's minimum size, and center it over the main window. - # If the size exceeds these dimensions, the modal won't be - # perfectly centered. - width = 400 - height = 100 - cls.top.minsize(width=width, height=height) - x = root.winfo_x() + (root.winfo_width() // 2) - (width // 2) - y = root.winfo_y() + (root.winfo_height() // 2) - (height // 4) - cls.top.geometry(f"+{x}+{y}") - - # Prevent resizing - cls.top.resizable(False, False) - - # Stay on top of the main window - cls.top.transient(root) - - # Take focus - cls.top.grab_set() - - def receive_files( root, target_dir,