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:
+14
-1
@@ -155,6 +155,15 @@ class FaderWidget(QWidget):
|
||||
self.pickup_checkbox.stateChanged.connect(self.on_pickup_changed)
|
||||
inner.addWidget(self.pickup_checkbox, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
# Remote checkbox
|
||||
self.dest_spin = QSpinBox()
|
||||
self.dest_spin.setRange(0, 9)
|
||||
self.dest_spin.setSpecialValueText("Off")
|
||||
self.dest_spin.setPrefix("T:")
|
||||
self.dest_spin.setValue(1)
|
||||
self.dest_spin.setFixedWidth(52)
|
||||
inner.addWidget(self.dest_spin, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
# Scribble strip
|
||||
self.title_edit = QLineEdit(label)
|
||||
self.title_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
@@ -437,7 +446,8 @@ class FaderWidget(QWidget):
|
||||
"hw_channel": self.hw_channel,
|
||||
"zone": self.zone,
|
||||
"center_index": self.center_index,
|
||||
"zone_index": self.zone_index
|
||||
"zone_index": self.zone_index,
|
||||
"dest_id": self.dest_spin.value(),
|
||||
}
|
||||
|
||||
def set_state(self, state):
|
||||
@@ -464,6 +474,9 @@ class FaderWidget(QWidget):
|
||||
self.zone_btn.set_state(self.zone == "left", self.zone == "right")
|
||||
self.hw_cc = state.get("hw_cc", None)
|
||||
self.hw_channel = state.get("hw_channel", None)
|
||||
self.dest_spin.blockSignals(True)
|
||||
self.dest_spin.setValue(state.get("dest_id", 1))
|
||||
self.dest_spin.blockSignals(False)
|
||||
if self.hw_cc is not None:
|
||||
self.learn_btn.setText(f"HW: CC{self.hw_cc}")
|
||||
self.cc_input_lbl.setText(self._cc_input_label())
|
||||
|
||||
+55
-5
@@ -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)
|
||||
|
||||
+14
-1
@@ -2,7 +2,7 @@ import uuid
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy
|
||||
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy, QCheckBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QEvent, QTimer
|
||||
|
||||
@@ -180,6 +180,15 @@ class ToggleWidget(QWidget):
|
||||
toggle_btn_row.addWidget(self.plus_btn)
|
||||
inner.addLayout(toggle_btn_row)
|
||||
|
||||
# Remote checkbox
|
||||
self.dest_spin = QSpinBox()
|
||||
self.dest_spin.setRange(0, 9)
|
||||
self.dest_spin.setSpecialValueText("Off")
|
||||
self.dest_spin.setPrefix("T:")
|
||||
self.dest_spin.setValue(1)
|
||||
self.dest_spin.setFixedWidth(52)
|
||||
inner.addWidget(self.dest_spin, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
# Zone button
|
||||
self.zone_btn = ZoneButton()
|
||||
self.zone_btn.left_callback = lambda active: self.on_zone_checked("left", active)
|
||||
@@ -370,6 +379,7 @@ class ToggleWidget(QWidget):
|
||||
"hw_cc": self.hw_cc,
|
||||
"hw_channel": self.hw_channel,
|
||||
"trigger_mode": self.trigger_mode,
|
||||
"dest_id": self.dest_spin.value(),
|
||||
}
|
||||
|
||||
def set_state(self, state):
|
||||
@@ -394,6 +404,9 @@ class ToggleWidget(QWidget):
|
||||
self.hw_cc = state.get("hw_cc", None)
|
||||
self.hw_channel = state.get("hw_channel", None)
|
||||
self.set_trigger_mode(state.get("trigger_mode", "toggle"))
|
||||
self.dest_spin.blockSignals(True)
|
||||
self.dest_spin.setValue(state.get("dest_id", 1))
|
||||
self.dest_spin.blockSignals(False)
|
||||
if self.hw_cc is not None:
|
||||
self.learn_btn.setText(f"HW: CC{self.hw_cc}")
|
||||
self.cc_input_lbl.setText(self._cc_input_label())
|
||||
|
||||
+15
-2
@@ -2,7 +2,7 @@ import uuid
|
||||
|
||||
from PyQt6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy
|
||||
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy, QCheckBox
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QEvent, QTimer
|
||||
|
||||
@@ -195,6 +195,15 @@ class TransportWidget(QWidget):
|
||||
btn_row.addWidget(self.plus_btn)
|
||||
inner.addLayout(btn_row)
|
||||
|
||||
# Remote checkbox
|
||||
self.dest_spin = QSpinBox()
|
||||
self.dest_spin.setRange(0, 9)
|
||||
self.dest_spin.setSpecialValueText("Off")
|
||||
self.dest_spin.setPrefix("T:")
|
||||
self.dest_spin.setValue(1)
|
||||
self.dest_spin.setFixedWidth(52)
|
||||
inner.addWidget(self.dest_spin, alignment=Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
# Zone button
|
||||
self.zone_btn = ZoneButton()
|
||||
self.zone_btn.left_callback = lambda active: self.on_zone_checked("left", active)
|
||||
@@ -378,6 +387,7 @@ class TransportWidget(QWidget):
|
||||
"output_mode": self.output_mode,
|
||||
"osc_addr": self.osc_addr_edit.text(),
|
||||
"trigger_mode": self.trigger_mode,
|
||||
"dest_id": self.dest_spin.value(),
|
||||
}
|
||||
|
||||
def set_state(self, state):
|
||||
@@ -403,4 +413,7 @@ class TransportWidget(QWidget):
|
||||
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px;")
|
||||
self.osc_addr_edit.setText(state.get("osc_addr", "/play"))
|
||||
self.set_output_mode(state.get("output_mode", "midi"))
|
||||
self.set_trigger_mode(state.get("trigger_mode", "momentary"))
|
||||
self.set_trigger_mode(state.get("trigger_mode", "momentary"))
|
||||
self.dest_spin.blockSignals(True)
|
||||
self.dest_spin.setValue(state.get("dest_id", 1))
|
||||
self.dest_spin.blockSignals(False)
|
||||
Reference in New Issue
Block a user