Implement live device add/delete component and app actions
Fix add component from palette in live mode: resolve <app> element as comp_id 0, fix type_id lookup to use type_name_map, set type_name on newly added SoxComponent, sync device values into DOM after add, write meta position back to device, and subscribe new component to COV. Fix delete component in live mode: clean up subscriptions for deleted component and all its descendants. Add tree root context menu with device app actions (save, hibernate, quit, restart, reboot) in live mode. Device save clears dirty state. Clean up connection lost handling to do a full disconnect. Fix slot registry to copy base slots and renumber IDs correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -445,6 +445,39 @@ def do_download_to_sax():
|
||||
messagebox.showerror("Download Failed", str(e))
|
||||
|
||||
|
||||
def _invoke_app_action(slot_name):
|
||||
"""Invokes an action slot on the app component (comp_id 0) of the connected device."""
|
||||
ds = app_state.device_session
|
||||
if ds is None or not ds.is_connected:
|
||||
app_state.set_status("Not connected to a device", warn=True)
|
||||
return
|
||||
comp = ds.sox_client.cache_get(0)
|
||||
if comp is None:
|
||||
app_state.set_status("App component not loaded", warn=True)
|
||||
return
|
||||
slots = ds.sox_client.slot_registry.get((comp.kit_id, comp.type_id))
|
||||
if slots is None:
|
||||
app_state.set_status("No slot registry for the app component", warn=True)
|
||||
return
|
||||
slot = next((s for s in slots if s["name"] == slot_name and s.get("is_action")), None)
|
||||
if slot is None:
|
||||
app_state.set_status(f"Action slot '{slot_name}' not found on app", warn=True)
|
||||
return
|
||||
try:
|
||||
ds.sox_client.invoke(0, slot["id"])
|
||||
app_state.set_status(f"Invoked '{slot_name}' on device")
|
||||
except Exception as e:
|
||||
app_state.set_status(f"Invoke '{slot_name}' failed: {e}", warn=True)
|
||||
return
|
||||
if slot_name == "save":
|
||||
app_state.mark_clean()
|
||||
elif slot_name in ("hibernate", "quit", "restart", "reboot"):
|
||||
do_device_disconnect()
|
||||
|
||||
|
||||
_APP_ACTIONS = ["save", "hibernate", "quit", "restart", "reboot"]
|
||||
|
||||
|
||||
NL = chr(10) # newline, for the multi-line message boxes
|
||||
|
||||
|
||||
@@ -876,6 +909,15 @@ def on_tree_right_click(event):
|
||||
parent_id = tree.parent(item_id)
|
||||
|
||||
if parent_id == "":
|
||||
tree_root_context_menu.delete(0, tk.END)
|
||||
if app_state.live_mode and app_state.device_session and app_state.device_session.is_connected:
|
||||
for action_name in _APP_ACTIONS:
|
||||
tree_root_context_menu.add_command(
|
||||
label=action_name.capitalize(),
|
||||
command=lambda a=action_name: _invoke_app_action(a))
|
||||
else:
|
||||
tree_root_context_menu.add_command(
|
||||
label="Save", command=sax_file.execute_direct_overwrite_save)
|
||||
tree_root_context_menu.post(event.x_root, event.y_root)
|
||||
return
|
||||
|
||||
@@ -2086,7 +2128,7 @@ def _start_cov_polling():
|
||||
if ds is None or not ds.is_connected:
|
||||
_cov_poll_id = None
|
||||
if app_state.live_mode:
|
||||
app_state.connection_status = "error"
|
||||
do_device_disconnect()
|
||||
app_state.set_status("Connection lost", warn=True)
|
||||
return
|
||||
|
||||
@@ -2192,7 +2234,6 @@ def _rebuild_link_context_menu():
|
||||
tree_comp_context_menu = tk.Menu(window, tearoff=0)
|
||||
|
||||
tree_root_context_menu = tk.Menu(window, tearoff=0)
|
||||
tree_root_context_menu.add_command(label="Save", command=sax_file.execute_direct_overwrite_save)
|
||||
|
||||
# --- Keyboard shortcuts ---
|
||||
# Command and Control are both bound on every platform, so muscle memory from either
|
||||
|
||||
Reference in New Issue
Block a user