diff --git a/app_state.py b/app_state.py index 24e7b5a..4dc1c5e 100644 --- a/app_state.py +++ b/app_state.py @@ -8,7 +8,7 @@ so later rebinding would only ever be seen by the module that did it. import configparser import os -VERSION = "0.0.0.139" +VERSION = "0.0.0.140" PROPERTIES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "editor.properties") DEFAULT_GRID_SIZE = 10 DEFAULT_ADD_TIMESTAMP = True diff --git a/main.py b/main.py index bb553a7..91ac383 100644 --- a/main.py +++ b/main.py @@ -2435,6 +2435,30 @@ def do_device_disconnect(): wiresheet.render_components([]) +def quit_app(): + """File > Exit and the window's close box: let go of the device before going. + + Leaving without a proper close keeps the device's Sox session and subscriptions + alive until it times them out, so the next connect may be turned away. + """ + global _cov_poll_id + + if _cov_poll_id is not None: + app_state.window.after_cancel(_cov_poll_id) + _cov_poll_id = None + + ds = app_state.device_session + app_state.device_session = None + app_state.live_mode = False + if ds is not None: + try: + ds.disconnect() + except Exception: + pass + + app_state.window.destroy() + + def _start_cov_polling(): """Starts the 100ms COV event polling loop on the main thread.""" global _cov_poll_id @@ -2616,7 +2640,7 @@ menu_file.add_command(label="Save As...", command=sax_file.save_file_as) menu_file.add_separator() menu_file.add_command(label="Print...", command=wiresheet.print_sheet, accelerator=f"{accel_mod}+P") menu_file.add_separator() -menu_file.add_command(label="Exit", command=window.quit) +menu_file.add_command(label="Exit", command=quit_app) menu_bar.add_cascade(label="File", menu=menu_file) menu_edit = tk.Menu(menu_bar, tearoff=0) @@ -3000,6 +3024,8 @@ paned_window.add(frame_editor, weight=3) app_state.dirty_display_callback = refresh_dirty_display undo.set_refresh_callback(refresh_all_views) sax_file.set_tree_filled_callback(on_tree_filled) +sax_file.set_disconnect_device_callback(do_device_disconnect) +window.protocol("WM_DELETE_WINDOW", quit_app) refresh_palette_kits() apply_left_pane_width() diff --git a/sax_file.py b/sax_file.py index 86b22e8..92baff6 100644 --- a/sax_file.py +++ b/sax_file.py @@ -31,6 +31,15 @@ def set_tree_filled_callback(callback): _tree_filled = callback +_disconnect_device = None + + +def set_disconnect_device_callback(callback): + """Registers how to drop the live device session before a file takes its place.""" + global _disconnect_device + _disconnect_device = callback + + def parse_xml_to_tree(treeview, parent_node, xml_element): """Recursively traverses XML elements and adds tags to the Treeview.""" for child in xml_element: @@ -132,19 +141,6 @@ def open_file(): messagebox.showwarning("Action Blocked", "Cannot load SAX profiles because 'Sedona home folder' configuration is missing or empty.") return - if app_state.live_mode: - answer = messagebox.askyesnocancel( - "Device Connected", - "A live device connection is active.\n\n" - "Disconnect from the device and open a file?\n\n" - "Choose Yes to disconnect and open, No to cancel.", - ) - if not answer: - return - # Import here to avoid circular import at module level - from main import do_device_disconnect - do_device_disconnect() - file_path = filedialog.askopenfilename( title="Select SAX File", filetypes=[("SAX Files", "*.sax"), ("XML Files", "*.xml"), ("All Files", "*.*")], @@ -152,6 +148,11 @@ def open_file(): if not file_path: return + # A session is a file or a device, never both. The device goes only once a file has + # actually been chosen, so cancelling the dialog leaves the connection as it was. + if app_state.device_session is not None and _disconnect_device is not None: + _disconnect_device() + canvas = app_state.canvas tree = app_state.tree