preset browser widget / lock preset

This commit is contained in:
Paul Lipscomb
2026-06-29 01:49:39 -04:00
parent 0a5cfa88f1
commit 0aad488db5
3 changed files with 392 additions and 63 deletions
+90 -58
View File
@@ -19,6 +19,7 @@ import threading
from togglewidget import ToggleWidget
from faderwidget import FaderWidget
from transportwidget import TransportWidget
from presetwidget import PresetWidget
from palette import (
PALETTE,
@@ -100,6 +101,7 @@ midi_in_port = None
fader_show_hide_config = False
transport_show_hide_config = False
toggle_show_hide_config = False
preset_switch_locked = False
uuid_presets_matched = []
uuid_presets_received_values = []
@@ -163,6 +165,14 @@ buttonImport = QPushButton("Import")
buttonImport.setFixedWidth(55)
buttonImport.clicked.connect(lambda: import_preset())
checkbox_lock_preset = QCheckBox("Lock Preset")
checkbox_lock_preset.setStyleSheet("color: #aaa; font-size: 11px;")
checkbox_lock_preset.setChecked(False)
def on_lock_preset_toggled(checked):
global preset_switch_locked
preset_switch_locked = checked
checkbox_lock_preset.toggled.connect(on_lock_preset_toggled)
#save status label (should be used for other stuff as well) -> save_status_label
labelStatus = QLabel("")
labelStatus.setStyleSheet("color: #00e676; font-style: italic;")
@@ -488,42 +498,16 @@ textedit_osc_out_log.setStyleSheet("""
""")
# MARK: PRESET BROWSER ROW MODULE
# REFACTOR MAP: Old names → New names
# self.browser_back_spin → spinner_preset_browser_back
# self.browser_fwd_spin → spinner_preset_browser_fwd
# Widget: label_preset_browser_title
label_preset_browser_title = QLabel("Preset Browser")
# Layout: hstack_preset_browser
hstack_preset_browser = QHBoxLayout()
hstack_preset_browser.setSpacing(8)
# Widget: button_preset_browser_back
button_preset_browser_back = QPushButton("")
button_preset_browser_back.setFixedSize(36, 28)
preset_browser_back = PresetWidget(label="Back", default_cc=22)
preset_browser_fwd = PresetWidget(label="Fwd", default_cc=23)
preset_browsers = [preset_browser_back, preset_browser_fwd]
# Widget: spinner_preset_browser_back
spinner_preset_browser_back = QSpinBox()
spinner_preset_browser_back.setMinimum(1)
spinner_preset_browser_back.setMaximum(127)
spinner_preset_browser_back.setValue(22)
spinner_preset_browser_back.setFixedWidth(55)
# Widget: button_preset_browser_fwd
button_preset_browser_fwd = QPushButton("")
button_preset_browser_fwd.setFixedSize(36, 28)
# Widget: spinner_preset_browser_fwd
spinner_preset_browser_fwd = QSpinBox()
spinner_preset_browser_fwd.setMinimum(1)
spinner_preset_browser_fwd.setMaximum(127)
spinner_preset_browser_fwd.setValue(23)
spinner_preset_browser_fwd.setFixedWidth(55)
# Widget: spacer_preset_browser
spacer_preset_browser = QWidget()
spacer_preset_browser.setFixedWidth(12)
# show/hide state (mirrors fader/toggle pattern)
preset_browser_show_hide_config = False
# MARK: TRANSPORT MODULE
# REFACTOR MAP: Old names → New names
@@ -701,6 +685,13 @@ button_transport_show_hide_config.setStyleSheet(_show_hide_btn_style)
hstack_row_toggle_control_buttons = QHBoxLayout()
hstack_row_toggle_control_buttons.setSpacing(8)
# Widget: button_preset_browser_show_hide_config
button_preset_browser_show_hide_config = QPushButton("Hide Parameters")
button_preset_browser_show_hide_config.setFixedWidth(120)
button_preset_browser_show_hide_config.setCheckable(True)
button_preset_browser_show_hide_config.setFlat(True)
button_preset_browser_show_hide_config.setStyleSheet(_show_hide_btn_style)
# Widget: button_toggle_show_hide_config
button_toggle_show_hide_config = QPushButton("Hide Parameters")
button_toggle_show_hide_config.setFixedWidth(120)
@@ -756,6 +747,7 @@ class MainWindow(QMainWindow):
hstack_preset_row.addWidget(buttonRemove) #remove button
hstack_preset_row.addWidget(buttonExport)
hstack_preset_row.addWidget(buttonImport)
hstack_preset_row.addWidget(checkbox_lock_preset)
hstack_preset_row.addWidget(label_preset_uuid)
hstack_preset_row.addWidget(button_preset_uuid_gen)
hstack_preset_row.addWidget(button_preset_uuid_copy)
@@ -1031,32 +1023,26 @@ class MainWindow(QMainWindow):
global container_preset_browser
global label_preset_browser_title
global hstack_preset_browser
global button_preset_browser_back
global spinner_preset_browser_back
global button_preset_browser_fwd
global spinner_preset_browser_fwd
global spacer_preset_browser
container_preset_browser = QWidget()
vstack_preset_browser_container = QVBoxLayout(container_preset_browser)
vstack_preset_browser_container.setContentsMargins(0, 0, 0, 0)
# assemble browser controls
hstack_preset_browser.addWidget(button_preset_browser_back)
hstack_preset_browser.addWidget(spinner_preset_browser_back)
hstack_preset_browser.addWidget(spacer_preset_browser)
hstack_preset_browser.addWidget(button_preset_browser_fwd)
hstack_preset_browser.addWidget(spinner_preset_browser_fwd)
hstack_preset_browser.addWidget(preset_browser_back)
hstack_preset_browser.addWidget(preset_browser_fwd)
hstack_preset_browser.addStretch(1)
hstack_preset_browser_controls = QHBoxLayout()
hstack_preset_browser_controls.addWidget(button_preset_browser_show_hide_config)
hstack_preset_browser_controls.addStretch()
button_preset_browser_show_hide_config.clicked.connect(preset_browser_show_hide_config_toggle)
vstack_preset_browser_container.addWidget(make_separator())
vstack_preset_browser_container.addWidget(label_preset_browser_title)
vstack_preset_browser_container.addLayout(hstack_preset_browser_controls)
vstack_preset_browser_container.addLayout(hstack_preset_browser)
main_layout.addWidget(container_preset_browser)
# connect signals
button_preset_browser_back.clicked.connect(lambda: send_cc(spinner_preset_browser_back.value(), 127))
button_preset_browser_fwd.clicked.connect(lambda: send_cc(spinner_preset_browser_fwd.value(), 127))
# MARK: CONTAINER ROWS AND WIDGETS (fader + toggle only)
global container_rows_and_widgets
@@ -1285,7 +1271,7 @@ def new_blank_preset():
if not ok or not name.strip():
return
name = name.strip()
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {}, "show_hide": {}}
loaded_presets["presets"][name] = {"faders": [], "toggles": [], "preset_uuid": "", "browser": {"back": preset_browser_back.get_state(), "fwd": preset_browser_fwd.get_state()}, "show_hide": {}}
loaded_presets["__last_used__"] = name
save_presets_file(loaded_presets)
populate_preset_dropdown_menu()
@@ -1449,7 +1435,8 @@ def _check_track_uuid_arrived():
_track_uuid_check_pending = False
label_osc_status.setText("No preset linked to this track")
label_osc_status.setStyleSheet("color: #f0c040; font-size: 11px; font-style: italic;")
ui_show_uuid_error_overlay("No preset linked to this track", "#f0c040")
if not preset_switch_locked:
ui_show_uuid_error_overlay("No preset linked to this track", "#f0c040")
def update_ui_on_osc_track_received(name):
global _track_uuid_check_pending
@@ -1816,14 +1803,15 @@ def preset_build_snapshot(name):
"faders": [f.get_state() for f in faders],
"toggles": [t.get_state() for t in toggles],
"browser": {
"back_cc": spinner_preset_browser_back.value(),
"fwd_cc": spinner_preset_browser_fwd.value()
"back": preset_browser_back.get_state(),
"fwd": preset_browser_fwd.get_state(),
},
"preset_uuid": label_preset_uuid.text().strip(),
"show_hide": {
"fader": fader_show_hide_config,
"toggle": toggle_show_hide_config,
"transport": transport_show_hide_config,
"browser": preset_browser_show_hide_config,
}
}
@@ -1916,8 +1904,14 @@ def preset_load(preset):
hstack_toggle_zone_right.addWidget(t)
browser = preset.get("browser", {})
spinner_preset_browser_back.setValue(browser.get("back_cc", 22))
spinner_preset_browser_fwd.setValue(browser.get("fwd_cc", 23))
# migrate old format that only stored cc numbers
if "back" not in browser and "back_cc" in browser:
browser = {
"back": {"label": "Back", "cc": browser.get("back_cc", 22), "ch": 1},
"fwd": {"label": "Fwd", "cc": browser.get("fwd_cc", 23), "ch": 1},
}
preset_browser_back.set_state(browser.get("back", {"label": "Back", "cc": 22, "ch": 1}))
preset_browser_fwd.set_state(browser.get("fwd", {"label": "Fwd", "cc": 23, "ch": 1}))
show_hide = preset.get("show_hide", {})
fader_show_hide_config_set(show_hide.get("fader", False))
@@ -1928,6 +1922,10 @@ def preset_load(preset):
button_toggle_show_hide_config.setText("Show Parameters" if show_hide.get("toggle", False) else "Hide Parameters")
button_toggle_show_hide_config.setChecked(show_hide.get("toggle", False))
preset_browser_show_hide_config_set(show_hide.get("browser", False))
button_preset_browser_show_hide_config.setText("Show Parameters" if show_hide.get("browser", False) else "Hide Parameters")
button_preset_browser_show_hide_config.setChecked(show_hide.get("browser", False))
label_preset_uuid.setText(preset.get("preset_uuid", ""))
check_and_index_strip_labels()
@@ -2012,22 +2010,25 @@ def resolve_uuid_batch():
global uuid_presets_matched, uuid_presets_received_values
uuid_presets_matched = list(dict.fromkeys(uuid_presets_matched))
if len(uuid_presets_matched) == 1:
menuPresets.setCurrentText(uuid_presets_matched[0])
track_name = label_osc_track.text()
label_osc_preset.setText(uuid_presets_matched[0])
label_osc_status.setText(f"DAW Track Selected: {track_name}")
label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")
ui_show_rows_and_widgets()
if not preset_switch_locked:
menuPresets.setCurrentText(uuid_presets_matched[0])
ui_show_rows_and_widgets()
elif len(uuid_presets_matched) > 1:
label_osc_status.setText("MULTIPLE PRESET IDs DETECTED")
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
ui_show_uuid_error_overlay("MULTIPLE PRESET IDs DETECTED", "#ff4444")
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
if not preset_switch_locked:
ui_show_uuid_error_overlay("MULTIPLE PRESET IDs DETECTED", "#ff4444")
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
else:
label_osc_status.setText("NO MATCHING ID IN PRESET LIST")
label_osc_status.setStyleSheet("color: #ff4444; font-size: 11px; font-style: italic;")
ui_show_uuid_error_overlay("NO MATCHING ID IN PRESET LIST", "#ff4444")
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
if not preset_switch_locked:
ui_show_uuid_error_overlay("NO MATCHING ID IN PRESET LIST", "#ff4444")
QTimer.singleShot(4000, lambda: (label_osc_status.setText(""), label_osc_status.setStyleSheet("color: #00e676; font-size: 11px; font-style: italic;")))
uuid_presets_matched.clear()
uuid_presets_received_values.clear()
@@ -2086,6 +2087,7 @@ def route_midi_message_input(event, data=None):
bound_fader = None
bound_transport = None
bound_browser = None
if status == 0xB0:
for fader in faders:
if fader.hw_cc == cc_num:
@@ -2101,13 +2103,20 @@ def route_midi_message_input(event, data=None):
if hasattr(t, 'hw_cc') and t.hw_cc == cc_num:
bound_transport = t
break
if not bound_fader and not bound_transport:
for w in preset_browsers:
if w.hw_cc == cc_num:
bound_browser = w
break
if status == 0xB0 and learning_strip:
learning_strip.bind_hw_cc(cc_num, channel=channel)
learning_strip = None
return
if bound_transport:
if bound_browser:
bound_browser.on_hw_trigger(cc_val)
elif bound_transport:
print(f"[transport hw] CC{cc_num} val={cc_val} → uid={bound_transport.uid}")
midi_receiver.hw_cc_signal.emit(bound_transport.uid, cc_val)
elif bound_fader:
@@ -2333,7 +2342,30 @@ def toggle_show_hide_config_toggle():
button_toggle_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters")
button_toggle_show_hide_config.setChecked(enabled)
# MARK: PRESET BROWSER SHOW HIDE CONFIG
def preset_browser_show_hide_config_set(enabled):
global preset_browser_show_hide_config
preset_browser_show_hide_config = enabled
for w in preset_browsers:
w.grp_learn.setVisible(not enabled)
w.arrow_lbl.setVisible(not enabled)
w.grp_cc.setVisible(not enabled)
w.title_edit.setVisible(not enabled)
w.momentary_btn.setVisible(not enabled)
w.toggle_mode_btn.setVisible(not enabled)
w.btn.setFixedHeight(24 if enabled else 40)
w.container.setStyleSheet(
"QFrame#presetContainer { background: transparent; border: none; }"
if enabled else
"QFrame#presetContainer { background-color: #4a5568; border-radius: 6px; border: 2px solid rgba(0,0,0,0.3); }"
)
def preset_browser_show_hide_config_toggle():
global preset_browser_show_hide_config
enabled = not preset_browser_show_hide_config
preset_browser_show_hide_config_set(enabled)
button_preset_browser_show_hide_config.setText("Show Parameters" if enabled else "Hide Parameters")
button_preset_browser_show_hide_config.setChecked(enabled)
# VS Code dark theme palette
+44 -5
View File
@@ -1,5 +1,5 @@
{
"__last_used__": "preset1",
"__last_used__": "preset5",
"presets": {
"preset1": {
"faders": [
@@ -231,14 +231,29 @@
],
"toggles": [],
"browser": {
"back_cc": 22,
"fwd_cc": 23
"back": {
"label": "Back",
"cc": 22,
"ch": 1,
"hw_cc": null,
"hw_channel": null,
"trigger_mode": "momentary"
},
"fwd": {
"label": "Fwd",
"cc": 23,
"ch": 1,
"hw_cc": null,
"hw_channel": null,
"trigger_mode": "momentary"
}
},
"preset_uuid": "e658b6bf-4bb7-4d68-bd8b-07775ee80c53",
"show_hide": {
"fader": false,
"toggle": false,
"transport": false
"transport": true,
"browser": true
}
},
"whatever": {
@@ -367,6 +382,30 @@
"toggle": false,
"transport": false
}
},
"new preset browser shit": {
"faders": [],
"toggles": [],
"preset_uuid": "",
"browser": {
"back": {
"label": "Back",
"cc": 22,
"ch": 1,
"hw_cc": null,
"hw_channel": null,
"trigger_mode": "momentary"
},
"fwd": {
"label": "Fwd",
"cc": 23,
"ch": 1,
"hw_cc": null,
"hw_channel": null,
"trigger_mode": "momentary"
}
},
"show_hide": {}
}
},
"transport_preset": [
@@ -427,7 +466,7 @@
"trigger_mode": "momentary"
}
],
"transport_show_hide": false,
"transport_show_hide": true,
"io_config": {
"midi_out": "Not Connected",
"midi_in": "VMPK Output",
+258
View File
@@ -0,0 +1,258 @@
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QSpinBox, QLineEdit, QFrame, QSizePolicy
)
from PyQt6.QtCore import Qt
from styles import TITLE_STYLE
from midi_sender import send_cc
class PresetWidget(QWidget):
def __init__(self, label="Preset", default_cc=1):
super().__init__()
self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Preferred)
self.setFixedWidth(115)
self.hw_cc = None
self.hw_channel = None
self.is_learning = False
outer = QVBoxLayout(self)
outer.setContentsMargins(0, 0, 0, 0)
outer.setSpacing(2)
self.container = QFrame()
self.container.setFrameShape(QFrame.Shape.StyledPanel)
self.container.setObjectName("presetContainer")
self.container.setStyleSheet("""
QFrame#presetContainer {
background-color: #4a5568;
border-radius: 6px;
border: 2px solid rgba(0,0,0,0.3);
}
""")
inner = QVBoxLayout(self.container)
inner.setContentsMargins(4, 6, 4, 6)
inner.setSpacing(4)
inner.setAlignment(Qt.AlignmentFlag.AlignHCenter)
_grp_style = "QFrame { border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; }"
# Group 1: Learn + input readout
self.grp_learn = QFrame()
self.grp_learn.setStyleSheet(_grp_style)
_grp1_layout = QVBoxLayout(self.grp_learn)
_grp1_layout.setContentsMargins(4, 4, 4, 4)
_grp1_layout.setSpacing(3)
self.learn_btn = QPushButton("Learn")
self.learn_btn.setFixedHeight(20)
self.learn_btn.setStyleSheet("border: none;")
self.learn_btn.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
self.learn_btn.customContextMenuRequested.connect(self.on_learn_context_menu)
self.learn_btn.clicked.connect(self.start_learn)
_grp1_layout.addWidget(self.learn_btn)
self.trigger_mode = "momentary"
self.toggle_state = False
self.cc_input_lbl = QLabel("")
self.cc_input_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter)
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
self.cc_input_lbl.setFixedHeight(16)
_grp1_layout.addWidget(self.cc_input_lbl)
inner.addWidget(self.grp_learn)
self.arrow_lbl = QLabel("")
self.arrow_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter)
self.arrow_lbl.setStyleSheet("color: rgba(255,255,255,0.3); font-size: 9px; background: transparent;")
self.arrow_lbl.setFixedHeight(12)
inner.addWidget(self.arrow_lbl)
# Group 2: Mom./Tog. + CC spin + CH spin + output readout
self.grp_cc = QFrame()
self.grp_cc.setStyleSheet(_grp_style)
_grp2_layout = QVBoxLayout(self.grp_cc)
_grp2_layout.setContentsMargins(4, 4, 4, 4)
_grp2_layout.setSpacing(3)
# Momentary / Toggle selector
_trigger_row = QHBoxLayout()
_trigger_row.setContentsMargins(0, 0, 0, 0)
_trigger_row.setSpacing(2)
self.momentary_btn = QPushButton("Mom.")
self.momentary_btn.setFixedHeight(18)
self.momentary_btn.setCheckable(True)
self.momentary_btn.setChecked(True)
self.toggle_mode_btn = QPushButton("Tog.")
self.toggle_mode_btn.setFixedHeight(18)
self.toggle_mode_btn.setCheckable(True)
self.toggle_mode_btn.setChecked(False)
self.momentary_btn.clicked.connect(lambda: self.set_trigger_mode("momentary"))
self.toggle_mode_btn.clicked.connect(lambda: self.set_trigger_mode("toggle"))
_trigger_row.addWidget(self.momentary_btn)
_trigger_row.addWidget(self.toggle_mode_btn)
_grp2_layout.addLayout(_trigger_row)
self.cc_spin = QSpinBox()
self.cc_spin.setMinimum(0)
self.cc_spin.setMaximum(127)
self.cc_spin.setValue(default_cc)
self.cc_spin.setFixedWidth(55)
_cc_lbl = QLabel("CC")
_cc_lbl.setStyleSheet("color: #fff; font-size: 10px; border: none;")
self.cc_spin_row = QWidget()
_hbox_cc = QHBoxLayout(self.cc_spin_row)
_hbox_cc.setContentsMargins(0, 0, 0, 0)
_hbox_cc.setSpacing(4)
_hbox_cc.addWidget(_cc_lbl)
_hbox_cc.addWidget(self.cc_spin)
_grp2_layout.addWidget(self.cc_spin_row)
self.ch_spin = QSpinBox()
self.ch_spin.setMinimum(1)
self.ch_spin.setMaximum(16)
self.ch_spin.setValue(1)
self.ch_spin.setFixedWidth(55)
_ch_lbl = QLabel("CH")
_ch_lbl.setStyleSheet("color: #fff; font-size: 10px; border: none;")
self.ch_spin_row = QWidget()
_hbox_ch = QHBoxLayout(self.ch_spin_row)
_hbox_ch.setContentsMargins(0, 0, 0, 0)
_hbox_ch.setSpacing(4)
_hbox_ch.addWidget(_ch_lbl)
_hbox_ch.addWidget(self.ch_spin)
_grp2_layout.addWidget(self.ch_spin_row)
self.cc_output_lbl = QLabel(f"CC{default_cc} [--]")
self.cc_output_lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter)
self.cc_output_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;")
self.cc_output_lbl.setFixedHeight(16)
_grp2_layout.addWidget(self.cc_output_lbl)
inner.addWidget(self.grp_cc)
# Trigger button
self.btn = QPushButton(label)
self.btn.setFixedHeight(40)
self.btn.clicked.connect(self.on_click)
inner.addWidget(self.btn)
# Scribble strip
self.title_edit = QLineEdit(label)
self.title_edit.setAlignment(Qt.AlignmentFlag.AlignHCenter)
self.title_edit.setStyleSheet(TITLE_STYLE)
self.title_edit.textChanged.connect(self.btn.setText)
inner.addWidget(self.title_edit)
outer.addWidget(self.container)
def _cc_input_label(self, vel="--"):
if self.hw_cc is None:
return ""
ch = f" CH{self.hw_channel}" if self.hw_channel is not None else ""
return f"CC{self.hw_cc}{ch} [{vel}]"
def _cc_output_label(self, vel="--"):
return f"CC{self.cc_spin.value()} CH{self.ch_spin.value()} [{vel}]"
def start_learn(self):
self.is_learning = True
self.learn_btn.setText("Listening...")
self.learn_btn.setStyleSheet("color: orange; font-weight: bold; border: none;")
def stop_learn(self):
self.is_learning = False
self.learn_btn.setStyleSheet("border: none;")
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())
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;")
else:
self.learn_btn.setText("Learn")
self.cc_input_lbl.setText("")
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
def bind_hw_cc(self, cc_number, channel=None):
self.hw_cc = cc_number
self.hw_channel = channel
self.stop_learn()
def on_learn_context_menu(self, pos):
from PyQt6.QtWidgets import QMenu
if self.hw_cc is None:
return
menu = QMenu(self)
clear_action = menu.addAction("Clear Input Assignment")
action = menu.exec(self.learn_btn.mapToGlobal(pos))
if action == clear_action:
self.hw_cc = None
self.hw_channel = None
self.is_learning = False
self.learn_btn.setText("Learn")
self.learn_btn.setStyleSheet("border: none;")
self.cc_input_lbl.setText("")
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")
def set_trigger_mode(self, mode):
self.trigger_mode = mode
if mode == "momentary":
self.momentary_btn.setChecked(True)
self.toggle_mode_btn.setChecked(False)
self.momentary_btn.setStyleSheet("font-weight: bold; color: #00e676;")
self.toggle_mode_btn.setStyleSheet("")
else:
self.toggle_mode_btn.setChecked(True)
self.momentary_btn.setChecked(False)
self.toggle_mode_btn.setStyleSheet("font-weight: bold; color: #00e676;")
self.momentary_btn.setStyleSheet("")
def _fire(self):
out_cc = self.cc_spin.value()
out_ch = self.ch_spin.value()
if self.trigger_mode == "momentary":
out_val = 127
else:
self.toggle_state = not self.toggle_state
out_val = 127 if self.toggle_state else 0
print(f"[preset] CC{out_cc} CH{out_ch} ({out_val})")
send_cc(out_cc, out_val, channel=out_ch)
self.cc_output_lbl.setText(self._cc_output_label(out_val))
def on_click(self):
self._fire()
def on_hw_trigger(self, value):
if value == 0:
return # ignore releases — normalized by mom/tog logic
self.cc_input_lbl.setText(self._cc_input_label(value))
self._fire()
def get_state(self):
return {
"label": self.title_edit.text(),
"cc": self.cc_spin.value(),
"ch": self.ch_spin.value(),
"hw_cc": self.hw_cc,
"hw_channel": self.hw_channel,
"trigger_mode": self.trigger_mode,
}
def set_state(self, state):
self.title_edit.setText(state.get("label", self.title_edit.text()))
self.cc_spin.setValue(state.get("cc", 1))
self.ch_spin.setValue(state.get("ch", 1))
self.hw_cc = state.get("hw_cc", None)
self.hw_channel = state.get("hw_channel", None)
self.set_trigger_mode(state.get("trigger_mode", "momentary"))
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())
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #00e676; font-size: 10px; padding: 1px; border: none;")
else:
self.learn_btn.setText("Learn")
self.cc_input_lbl.setText("")
self.cc_input_lbl.setStyleSheet("background-color: #000000; color: #888888; font-size: 10px; padding: 1px; border: none;")