Multi-iPad dest_id routing, activity ring, context menu hide, live label/visibility sync

Desktop:
- Replace Remote checkbox with T: dest_spin (0=Off, 1-9=tablet) on all widgets
- dest_id persisted in get_state/set_state, broadcast in tablet snapshot
- dest_spin.valueChanged triggers save + broadcast to iPad
- _on_widget_visibility_from_tablet updates dest_spin from iPad hide event
- _broadcast_preset_update shared helper; ws_server.has_clients guard
- label editingFinished triggers live label sync to iPad

iOS:
- VC-Arrange/VC-Logging/Widgets-Reusable-UI file reorganisation
- ArrangeObjects/ArrangeView rename, 7-bucket Arrange-Functions split
- myTabletId (UserDefaults) + tabletIdField next to port in toolbar
- Preset filter: destId == myTabletId instead of visible bool
- BaseWidget activity ring: long press gesture (touch) + final update() flash
- Context menu long press (arrange mode): hide widget, save layout, send dest_id:0
- Same-UUID preset check skips Preset Switched alert on visibility/label updates
- autoSwitchEnabled, toolbarStatusLabel, presetLabel, statusToken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Paul Lipscomb
2026-07-02 21:56:35 -04:00
parent 7a74cfa75e
commit d6201c59b4
32 changed files with 1042 additions and 501 deletions
+55 -5
View File
@@ -135,11 +135,21 @@ containerMain = QWidget()
#MARK: PRESET ROW
hstack_preset_row = QHBoxLayout()
#MARK: PRESENT
#MARK: PRESENT
menuPresets = QComboBox()
menuPresets.setMinimumWidth(160)
menuPresets.currentTextChanged.connect(lambda name: on_preset_selected(menuPresets, app_window, ready, loaded_presets, on_load=preset_load))
buttonPresetPrev = QPushButton("")
buttonPresetPrev.setFixedWidth(28)
buttonPresetPrev.setToolTip("Previous preset")
buttonPresetPrev.clicked.connect(lambda: menuPresets.setCurrentIndex(max(0, menuPresets.currentIndex() - 1)))
buttonPresetNext = QPushButton("")
buttonPresetNext.setFixedWidth(28)
buttonPresetNext.setToolTip("Next preset")
buttonPresetNext.clicked.connect(lambda: menuPresets.setCurrentIndex(min(menuPresets.count() - 1, menuPresets.currentIndex() + 1)))
#button new blank preset
buttonNew = QPushButton("New")
@@ -781,6 +791,8 @@ for _label, _cc in [("Play", 117), ("Stop", 116), ("Record", 118), ("Arm", 119)]
_t.on_select_callback = lambda strip: check_and_update_selected_strip(strip)
_t.on_context_menu_callback = lambda strip, pos: strip_context_menu(strip, pos)
_t.on_zone_change_callback = lambda strip: handle_transport_zone_change(strip)
_t.dest_spin.valueChanged.connect(lambda val: _on_remote_checkbox_changed())
_t.title_edit.editingFinished.connect(lambda: _broadcast_preset_update())
del _t, _label, _cc
# MARK: SELECTED CHANNEL ROW MODULE
@@ -964,6 +976,8 @@ class MainWindow(QMainWindow):
main_layout.addLayout(hstack_preset_row)
hstack_preset_row.addWidget(menuPresets) #dropdown
hstack_preset_row.addWidget(buttonPresetPrev)
hstack_preset_row.addWidget(buttonPresetNext)
hstack_preset_row.addWidget(buttonNew)
hstack_preset_row.addWidget(buttonSave) #save button
hstack_preset_row.addWidget(buttonSaveAs) #save as button
@@ -2181,6 +2195,8 @@ def toggle_add(insert_after=None):
toggle.on_context_menu_callback = strip_context_menu
toggle.on_zone_change_callback = handle_toggle_zone_change
toggle.learn_btn.clicked.connect(lambda: check_and_start_midi_learn(toggle))
toggle.dest_spin.valueChanged.connect(lambda val: _on_remote_checkbox_changed())
toggle.title_edit.editingFinished.connect(_broadcast_preset_update)
update_window_width()
check_and_index_strip_labels()
_update_toggle_placeholder()
@@ -2214,6 +2230,8 @@ def fader_add(insert_after=None):
fader.on_select_callback = check_and_update_selected_strip
fader.on_context_menu_callback = strip_context_menu
fader.on_zone_change_callback = handle_fader_zone_change
fader.dest_spin.valueChanged.connect(lambda val: _on_remote_checkbox_changed())
fader.title_edit.editingFinished.connect(_broadcast_preset_update)
update_window_width()
check_and_index_strip_labels()
_update_fader_placeholder()
@@ -2238,6 +2256,8 @@ def transport_toggle_add(label, default_cc, insert_after=None):
t.on_zone_change_callback = lambda strip: handle_transport_zone_change(strip)
t.plus_btn.clicked.connect(lambda: transport_toggle_add(f"T{len(transports)+1}", 20, insert_after=t))
t.minus_btn.clicked.connect(lambda: transport_remove(t))
t.dest_spin.valueChanged.connect(lambda val: _on_remote_checkbox_changed())
t.title_edit.editingFinished.connect(_broadcast_preset_update)
params_visible = not transport_show_hide_config
for attr in ['grp_learn', 'arrow_lbl', 'grp_cc', 'zone_btn', 'minus_btn',
'plus_btn', 'title_edit', 'id_sep', 'id_label', 'color_swatch_btn']:
@@ -2351,9 +2371,9 @@ def _build_tablet_snapshot():
"preset_uuid": label_preset_uuid.text().strip(),
"preset_name": name,
"big_title": big_title_edit.text() if big_title_edit else "",
"faders": [{"uid": f.uid, "label": f.title_edit.text(), "value": f.fader.value(), "visible": True, "color": PALETTE_HEX.get(f.color_name, "#00e676")} for f in faders],
"toggles": [{"uid": t.uid, "label": t.title_edit.text(), "state": t.toggle_state, "visible": True, "color": PALETTE_HEX.get(t.color_name, "#00e676")} for t in toggles],
"transports": [{"uid": t.uid, "label": t.title_edit.text(), "visible": True} for t in transports],
"faders": [{"uid": f.uid, "label": f.title_edit.text(), "value": f.fader.value(), "dest_id": f.dest_spin.value(), "color": PALETTE_HEX.get(f.color_name, "#00e676")} for f in faders],
"toggles": [{"uid": t.uid, "label": t.title_edit.text(), "state": t.toggle_state, "dest_id": t.dest_spin.value(), "color": PALETTE_HEX.get(t.color_name, "#00e676")} for t in toggles],
"transports": [{"uid": t.uid, "label": t.title_edit.text(), "dest_id": t.dest_spin.value()} for t in transports],
}
return snapshot, saved_layout
@@ -2365,6 +2385,35 @@ def _on_tablet_connected():
log_ws_outgoing(snapshot)
def _on_widget_visibility_from_tablet(uid, dest_id):
for w in faders + toggles + transports:
if w.uid == uid:
w.dest_spin.blockSignals(True)
w.dest_spin.setValue(dest_id)
w.dest_spin.blockSignals(False)
name = menuPresets.currentText()
if name and name in loaded_presets.get("presets", {}):
loaded_presets["presets"][name].update(preset_build_snapshot(name))
save_presets_file(loaded_presets)
show_save_status()
break
def _broadcast_preset_update():
name = menuPresets.currentText()
if name and name in loaded_presets.get("presets", {}):
loaded_presets["presets"][name].update(preset_build_snapshot(name))
save_presets_file(loaded_presets)
if ws_server and ws_server.has_clients:
snapshot, layout = _build_tablet_snapshot()
ws_server.broadcast_preset(snapshot, layout)
log_ws_outgoing(snapshot)
def _on_remote_checkbox_changed():
_broadcast_preset_update()
def _on_tablet_layout_saved(preset_uuid, layout):
name = menuPresets.currentText()
if name and name in loaded_presets.get("presets", {}):
@@ -2498,7 +2547,7 @@ def preset_load(preset):
check_and_index_strip_labels()
if ws_server:
if ws_server and ws_server.has_clients:
snapshot, layout = _build_tablet_snapshot()
ws_server.broadcast_preset(snapshot, layout)
log_ws_outgoing(snapshot)
@@ -2865,6 +2914,7 @@ def _start_tablet_server():
ws_server.raw_in_signal.connect(log_ws_incoming)
ws_server.control_received.connect(lambda uid, val: midi_receiver.hw_cc_signal.emit(uid, val))
ws_server.layout_saved.connect(_on_tablet_layout_saved)
ws_server.widget_visibility_received.connect(_on_widget_visibility_from_tablet)
ws_server.client_connected.connect(_on_tablet_connected)
try:
s = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM)