Refactor: Module-level UI construction (MIDI IN column)
This commit is contained in:
@@ -36,6 +36,12 @@ from styles import (
|
|||||||
from presets import (
|
from presets import (
|
||||||
load_presets_file,
|
load_presets_file,
|
||||||
save_presets_file,
|
save_presets_file,
|
||||||
|
ensure_preset_structure,
|
||||||
|
on_preset_selected,
|
||||||
|
save_preset,
|
||||||
|
save_preset_as,
|
||||||
|
delete_preset,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
from zonebutton import ZoneButton
|
from zonebutton import ZoneButton
|
||||||
@@ -129,12 +135,94 @@ def stop_osc_server():
|
|||||||
_osc_server = None
|
_osc_server = None
|
||||||
_osc_thread = None
|
_osc_thread = None
|
||||||
|
|
||||||
|
def set_app_window(window: QMainWindow):
|
||||||
|
global app_window
|
||||||
|
app_window = window
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: GLOBALS
|
||||||
|
ready = False
|
||||||
|
selected_strip = None
|
||||||
|
learning_strip = None
|
||||||
|
faders = []
|
||||||
|
strip_width = 100
|
||||||
|
spacing = 10
|
||||||
|
padding = 40
|
||||||
|
loaded_presets = load_presets_file()
|
||||||
|
app_window = None
|
||||||
|
|
||||||
|
#MARK: CREATE APP
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
app.setStyle("Fusion")
|
||||||
|
|
||||||
|
#MARK: MAIN WINDOW
|
||||||
|
containerMain = QWidget()
|
||||||
|
|
||||||
|
#MARK: PRESET ROW
|
||||||
|
hstack_preset_row = QHBoxLayout()
|
||||||
|
|
||||||
|
#MARK: PRESENT
|
||||||
|
menuPresets = QComboBox()
|
||||||
|
menuPresets.setMinimumWidth(160)
|
||||||
|
menuPresets.currentTextChanged.connect(lambda name: on_preset_selected(menuPresets, app_window, ready, loaded_presets))
|
||||||
|
|
||||||
|
#button preset save
|
||||||
|
buttonSave = QPushButton("Save")
|
||||||
|
buttonSave.setFixedWidth(50)
|
||||||
|
buttonSave.clicked.connect(lambda: save_preset(app_window, menuPresets, loaded_presets))
|
||||||
|
|
||||||
|
#button preset save as -> save_as_btn
|
||||||
|
buttonSaveAs = QPushButton("Save As")
|
||||||
|
buttonSaveAs.setFixedWidth(60)
|
||||||
|
buttonSaveAs.clicked.connect(lambda: save_preset_as(app_window, menuPresets, loaded_presets))
|
||||||
|
|
||||||
|
#button preset delete -> del_btn
|
||||||
|
buttonRemove = QPushButton("Remove")
|
||||||
|
buttonRemove.setFixedWidth(65)
|
||||||
|
buttonRemove.clicked.connect(lambda: delete_preset(app_window, menuPresets, loaded_presets))
|
||||||
|
|
||||||
|
#save status label (should be used for other stuff as well) -> save_status_label
|
||||||
|
labelStatus = QLabel("")
|
||||||
|
labelStatus.setStyleSheet("color: #00e676; font-style: italic;")
|
||||||
|
|
||||||
|
#container io row
|
||||||
|
hstack_io_row = QHBoxLayout()
|
||||||
|
hstack_io_row.setSpacing(16)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ===== MODULE LEVEL =====
|
||||||
|
# REFACTOR MAP: Old names → New names
|
||||||
|
# self.in_blink → activity_indicator_midi_in
|
||||||
|
# self.in_status → label_connection_midi_in
|
||||||
|
# self.in_combo → picker_midi_in
|
||||||
|
# self.available_in_ports → available_ports_midi_in
|
||||||
|
|
||||||
|
|
||||||
|
# vstack_midi_in
|
||||||
|
vstack_midi_in = QVBoxLayout()
|
||||||
|
vstack_midi_in.setSpacing(2)
|
||||||
|
|
||||||
|
# hstack_midi_in_activity_and_label
|
||||||
|
hstack_midi_in_activity_and_label = QHBoxLayout()
|
||||||
|
|
||||||
|
# activity_indicator_midi_in
|
||||||
|
activity_indicator_midi_in = QLabel("●")
|
||||||
|
activity_indicator_midi_in.setStyleSheet("color: #444; font-size: 14px; background-color: rgba(255, 0, 0, 0.15); border: 1px solid red; padding: 2px;")
|
||||||
|
|
||||||
|
# label_connection_midi_in
|
||||||
|
label_connection_midi_in = QLabel("Not Connected")
|
||||||
|
label_connection_midi_in.setStyleSheet("color: #888; background-color: rgba(0, 0, 255, 0.15); border: 1px solid blue; padding: 2px;")
|
||||||
|
|
||||||
|
# label_midi_in
|
||||||
|
label_midi_in = QLabel("MIDI Input")
|
||||||
|
label_midi_in.setStyleSheet("background-color: rgba(255, 255, 0, 0.15); border: 1px solid yellow; padding: 2px;")
|
||||||
|
|
||||||
|
# picker_midi_in
|
||||||
|
picker_midi_in = QComboBox()
|
||||||
|
picker_midi_in.setFixedWidth(200)
|
||||||
|
picker_midi_in.setStyleSheet("background-color: rgba(0, 255, 0, 0.15); border: 1px solid green;")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -142,84 +230,85 @@ def stop_osc_server():
|
|||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setWindowTitle("MIDI Controller")
|
|
||||||
self.ready = False
|
|
||||||
self.selected_strip = None
|
|
||||||
self.learning_strip = None
|
|
||||||
self.faders = []
|
|
||||||
self.strip_width = 100
|
|
||||||
self.spacing = 10
|
|
||||||
self.padding = 40
|
|
||||||
|
|
||||||
self.data = load_presets_file()
|
global loaded_presets
|
||||||
if "presets" not in self.data:
|
global containerPresetRow
|
||||||
self.data = {"__last_used__": None, "presets": self.data}
|
global menuPresets
|
||||||
|
global buttonSave
|
||||||
|
global buttonSaveAs
|
||||||
|
global buttonRemove
|
||||||
|
global labelStatus
|
||||||
|
global hstack_io_row
|
||||||
|
global available_ports_midi_in
|
||||||
|
|
||||||
central = QWidget()
|
self.setWindowTitle("Controller")
|
||||||
self.setCentralWidget(central)
|
#set app window reference
|
||||||
main_layout = QVBoxLayout(central)
|
set_app_window(self)
|
||||||
|
|
||||||
# --- Preset manager ---
|
# ui main window
|
||||||
preset_row = QHBoxLayout()
|
self.setCentralWidget(containerMain)
|
||||||
|
main_layout = QVBoxLayout(containerMain)
|
||||||
|
|
||||||
self.preset_combo = QComboBox()
|
#MARK: PRESETS ROW
|
||||||
self.preset_combo.setMinimumWidth(160)
|
# ui preset row + drop down + buttont
|
||||||
self.preset_combo.currentTextChanged.connect(self.on_preset_selected)
|
main_layout.addLayout(hstack_preset_row)
|
||||||
preset_row.addWidget(self.preset_combo)
|
|
||||||
|
|
||||||
save_btn = QPushButton("Save")
|
hstack_preset_row.addWidget(menuPresets) #dropdown
|
||||||
save_btn.setFixedWidth(50)
|
hstack_preset_row.addWidget(buttonSave) #save button
|
||||||
save_btn.clicked.connect(self.save_preset)
|
hstack_preset_row.addWidget(buttonSaveAs) #save as button
|
||||||
preset_row.addWidget(save_btn)
|
hstack_preset_row.addWidget(buttonRemove) #remove button
|
||||||
|
hstack_preset_row.addStretch()
|
||||||
|
|
||||||
save_as_btn = QPushButton("Save As")
|
#section seperator
|
||||||
save_as_btn.setFixedWidth(60)
|
|
||||||
save_as_btn.clicked.connect(self.save_preset_as)
|
|
||||||
preset_row.addWidget(save_as_btn)
|
|
||||||
|
|
||||||
del_btn = QPushButton("Remove")
|
|
||||||
del_btn.setFixedWidth(65)
|
|
||||||
del_btn.clicked.connect(self.delete_preset)
|
|
||||||
preset_row.addWidget(del_btn)
|
|
||||||
|
|
||||||
preset_row.addStretch()
|
|
||||||
|
|
||||||
self.save_status_label = QLabel("")
|
|
||||||
self.save_status_label.setStyleSheet("color: #00e676; font-style: italic;")
|
|
||||||
main_layout.addLayout(preset_row)
|
|
||||||
main_layout.addWidget(self._separator())
|
main_layout.addWidget(self._separator())
|
||||||
|
|
||||||
# --- MIDI I/O row ---
|
#MARK: MIDI IN COLUMN
|
||||||
io_row = QHBoxLayout()
|
#build the hstack with activity indicator and connection state label
|
||||||
io_row.setSpacing(16)
|
hstack_midi_in_activity_and_label.addWidget(activity_indicator_midi_in)
|
||||||
|
hstack_midi_in_activity_and_label.addWidget(label_connection_midi_in)
|
||||||
|
hstack_midi_in_activity_and_label.addStretch()
|
||||||
|
|
||||||
# Input side
|
#add all 3 to the vstack
|
||||||
in_col = QVBoxLayout()
|
vstack_midi_in.addLayout(hstack_midi_in_activity_and_label) #activity indicator and connection label
|
||||||
in_col.setSpacing(2)
|
vstack_midi_in.addWidget(label_midi_in) # midi input label
|
||||||
in_top = QHBoxLayout()
|
vstack_midi_in.addWidget(picker_midi_in) # dropdown menu
|
||||||
self.in_blink = QLabel("●")
|
|
||||||
self.in_blink.setStyleSheet("color: #444; font-size: 14px;")
|
# add the combined stack to the row
|
||||||
self.in_status = QLabel("Not Connected")
|
hstack_io_row.addLayout(vstack_midi_in)
|
||||||
self.in_status.setStyleSheet("color: #888;")
|
|
||||||
in_top.addWidget(self.in_blink)
|
|
||||||
in_top.addWidget(self.in_status)
|
|
||||||
in_top.addStretch()
|
|
||||||
in_col.addLayout(in_top)
|
|
||||||
in_col.addWidget(QLabel("MIDI Input"))
|
|
||||||
self.in_combo = QComboBox()
|
# picker_midi_in population & signal
|
||||||
self.in_combo.setFixedWidth(200)
|
available_ports_midi_in = get_available_input_ports()
|
||||||
self.available_in_ports = get_available_input_ports() # list of (display_name, index, available)
|
for display_name, idx, available in available_ports_midi_in:
|
||||||
for display_name, idx, available in self.available_in_ports:
|
picker_midi_in.addItem(display_name)
|
||||||
self.in_combo.addItem(display_name)
|
|
||||||
if not available:
|
if not available:
|
||||||
# Gray out the item
|
item_index = picker_midi_in.count() - 1
|
||||||
item_index = self.in_combo.count() - 1
|
picker_midi_in.model().item(item_index).setEnabled(False)
|
||||||
self.in_combo.model().item(item_index).setEnabled(False)
|
picker_midi_in.model().item(item_index).setForeground(QColor('#888888'))
|
||||||
self.in_combo.model().item(item_index).setForeground(QColor('#888888'))
|
|
||||||
self.in_combo.currentTextChanged.connect(self.on_input_port_select)
|
|
||||||
in_col.addWidget(self.in_combo)
|
|
||||||
io_row.addLayout(in_col)
|
|
||||||
|
|
||||||
|
picker_midi_in.currentTextChanged.connect(self.on_input_port_select)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: COL 2 MIDI OUT
|
||||||
# Output side
|
# Output side
|
||||||
out_col = QVBoxLayout()
|
out_col = QVBoxLayout()
|
||||||
out_col.setSpacing(2)
|
out_col.setSpacing(2)
|
||||||
@@ -238,8 +327,9 @@ class MainWindow(QMainWindow):
|
|||||||
self.port_combo.setFixedWidth(200)
|
self.port_combo.setFixedWidth(200)
|
||||||
self.port_combo.currentTextChanged.connect(self.on_port_select)
|
self.port_combo.currentTextChanged.connect(self.on_port_select)
|
||||||
out_col.addWidget(self.port_combo)
|
out_col.addWidget(self.port_combo)
|
||||||
io_row.addLayout(out_col)
|
hstack_io_row.addLayout(out_col)
|
||||||
|
|
||||||
|
#MARK: COL 3 TRANSPORT
|
||||||
# Transport Output column
|
# Transport Output column
|
||||||
t_col = QVBoxLayout()
|
t_col = QVBoxLayout()
|
||||||
t_col.setSpacing(2)
|
t_col.setSpacing(2)
|
||||||
@@ -258,8 +348,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.t_out_combo.setFixedWidth(200)
|
self.t_out_combo.setFixedWidth(200)
|
||||||
self.t_out_combo.currentTextChanged.connect(self.on_transport_output_select)
|
self.t_out_combo.currentTextChanged.connect(self.on_transport_output_select)
|
||||||
t_col.addWidget(self.t_out_combo)
|
t_col.addWidget(self.t_out_combo)
|
||||||
io_row.addLayout(t_col)
|
hstack_io_row.addLayout(t_col)
|
||||||
|
|
||||||
|
|
||||||
|
# #MARK: COL 4 OSC
|
||||||
# OSC column
|
# OSC column
|
||||||
osc_col = QVBoxLayout()
|
osc_col = QVBoxLayout()
|
||||||
osc_col.setSpacing(2)
|
osc_col.setSpacing(2)
|
||||||
@@ -315,12 +407,14 @@ class MainWindow(QMainWindow):
|
|||||||
osc_btn_row.addWidget(self.osc_log_btn)
|
osc_btn_row.addWidget(self.osc_log_btn)
|
||||||
osc_btn_row.addStretch()
|
osc_btn_row.addStretch()
|
||||||
osc_col.addLayout(osc_btn_row)
|
osc_col.addLayout(osc_btn_row)
|
||||||
io_row.addLayout(osc_col)
|
hstack_io_row.addLayout(osc_col)
|
||||||
|
|
||||||
io_row.addStretch()
|
hstack_io_row.addStretch()
|
||||||
main_layout.addLayout(io_row)
|
main_layout.addLayout(hstack_io_row)
|
||||||
main_layout.addWidget(self._separator())
|
main_layout.addWidget(self._separator())
|
||||||
|
|
||||||
|
|
||||||
|
# #MARK: OSC STATUS ROW
|
||||||
# --- OSC Feedback panel ---
|
# --- OSC Feedback panel ---
|
||||||
self.osc_panel = QWidget()
|
self.osc_panel = QWidget()
|
||||||
self.osc_panel.setStyleSheet("background-color: #252526; border-radius: 4px;")
|
self.osc_panel.setStyleSheet("background-color: #252526; border-radius: 4px;")
|
||||||
@@ -358,6 +452,7 @@ class MainWindow(QMainWindow):
|
|||||||
osc_div.setStyleSheet("background-color: #3a3a3a;")
|
osc_div.setStyleSheet("background-color: #3a3a3a;")
|
||||||
osc_panel_layout.addWidget(osc_div)
|
osc_panel_layout.addWidget(osc_div)
|
||||||
|
|
||||||
|
|
||||||
# Bottom row — track and preset info
|
# Bottom row — track and preset info
|
||||||
osc_info_row = QHBoxLayout()
|
osc_info_row = QHBoxLayout()
|
||||||
osc_info_row.setSpacing(8)
|
osc_info_row.setSpacing(8)
|
||||||
@@ -455,6 +550,8 @@ class MainWindow(QMainWindow):
|
|||||||
out_log_layout.addWidget(self.osc_out_log)
|
out_log_layout.addWidget(self.osc_out_log)
|
||||||
self.osc_out_log_panel.setVisible(False)
|
self.osc_out_log_panel.setVisible(False)
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: PRESET ROW
|
||||||
# --- Preset browser ---
|
# --- Preset browser ---
|
||||||
main_layout.addWidget(QLabel("Preset Browser"))
|
main_layout.addWidget(QLabel("Preset Browser"))
|
||||||
browser_row = QHBoxLayout()
|
browser_row = QHBoxLayout()
|
||||||
@@ -492,13 +589,15 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addWidget(self._separator())
|
main_layout.addWidget(self._separator())
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: TRANSPORT ROW
|
||||||
# --- Transport ---
|
# --- Transport ---
|
||||||
self.transports = []
|
self.transports = []
|
||||||
self.transport_strip_width = 100
|
self.transport_strip_width = 100
|
||||||
self._capturing_key_for = None
|
self._capturing_key_for = None
|
||||||
|
|
||||||
self.full_transport_row = QHBoxLayout()
|
self.full_transport_row = QHBoxLayout()
|
||||||
self.full_transport_row.setSpacing(self.spacing)
|
self.full_transport_row.setSpacing(spacing)
|
||||||
main_layout.addLayout(self.full_transport_row)
|
main_layout.addLayout(self.full_transport_row)
|
||||||
|
|
||||||
self.transport_zone_left_slot = QHBoxLayout()
|
self.transport_zone_left_slot = QHBoxLayout()
|
||||||
@@ -513,7 +612,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.full_transport_row.addWidget(self.transport_vsep_left)
|
self.full_transport_row.addWidget(self.transport_vsep_left)
|
||||||
|
|
||||||
self.transport_row = QHBoxLayout()
|
self.transport_row = QHBoxLayout()
|
||||||
self.transport_row.setSpacing(self.spacing)
|
self.transport_row.setSpacing(spacing)
|
||||||
self.full_transport_row.addLayout(self.transport_row)
|
self.full_transport_row.addLayout(self.transport_row)
|
||||||
|
|
||||||
self.transport_vsep_right = QFrame()
|
self.transport_vsep_right = QFrame()
|
||||||
@@ -529,6 +628,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addWidget(self._separator())
|
main_layout.addWidget(self._separator())
|
||||||
|
|
||||||
|
#MARK: SELECTED CHANNEL ROW
|
||||||
# --- Selected channel label row ---
|
# --- Selected channel label row ---
|
||||||
info_row = QHBoxLayout()
|
info_row = QHBoxLayout()
|
||||||
info_row.addStretch()
|
info_row.addStretch()
|
||||||
@@ -537,13 +637,15 @@ class MainWindow(QMainWindow):
|
|||||||
self.selected_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.selected_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
info_row.addWidget(self.selected_label)
|
info_row.addWidget(self.selected_label)
|
||||||
info_row.addStretch()
|
info_row.addStretch()
|
||||||
self.save_status_label.setParent(None)
|
labelStatus.setParent(None)
|
||||||
info_row.addWidget(self.save_status_label)
|
info_row.addWidget(labelStatus)
|
||||||
main_layout.addLayout(info_row)
|
main_layout.addLayout(info_row)
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: FADERS ROW
|
||||||
# --- Faders row ---
|
# --- Faders row ---
|
||||||
self.full_fader_row = QHBoxLayout()
|
self.full_fader_row = QHBoxLayout()
|
||||||
self.full_fader_row.setSpacing(self.spacing)
|
self.full_fader_row.setSpacing(spacing)
|
||||||
main_layout.addLayout(self.full_fader_row)
|
main_layout.addLayout(self.full_fader_row)
|
||||||
|
|
||||||
# Zone Left slot
|
# Zone Left slot
|
||||||
@@ -561,7 +663,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Custom fader row
|
# Custom fader row
|
||||||
self.fader_row = QHBoxLayout()
|
self.fader_row = QHBoxLayout()
|
||||||
self.fader_row.setSpacing(self.spacing)
|
self.fader_row.setSpacing(spacing)
|
||||||
self.full_fader_row.addLayout(self.fader_row)
|
self.full_fader_row.addLayout(self.fader_row)
|
||||||
|
|
||||||
# Right vertical separator — always visible
|
# Right vertical separator — always visible
|
||||||
@@ -578,12 +680,14 @@ class MainWindow(QMainWindow):
|
|||||||
self.full_fader_row.addLayout(self.zone_right_slot)
|
self.full_fader_row.addLayout(self.zone_right_slot)
|
||||||
main_layout.addWidget(self._separator())
|
main_layout.addWidget(self._separator())
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: TOGGLE ROW
|
||||||
# --- Toggle ---
|
# --- Toggle ---
|
||||||
self.toggles = []
|
self.toggles = []
|
||||||
self.toggle_strip_width = 100
|
self.toggle_strip_width = 100
|
||||||
|
|
||||||
self.full_toggle_row = QHBoxLayout()
|
self.full_toggle_row = QHBoxLayout()
|
||||||
self.full_toggle_row.setSpacing(self.spacing)
|
self.full_toggle_row.setSpacing(spacing)
|
||||||
main_layout.addLayout(self.full_toggle_row)
|
main_layout.addLayout(self.full_toggle_row)
|
||||||
|
|
||||||
self.toggle_zone_left_slot = QHBoxLayout()
|
self.toggle_zone_left_slot = QHBoxLayout()
|
||||||
@@ -614,20 +718,29 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
main_layout.addStretch()
|
main_layout.addStretch()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#MARK: SET DEFAULTS
|
||||||
# Add initial 2 faders, 1 toggle, 4 transport strips
|
# Add initial 2 faders, 1 toggle, 4 transport strips
|
||||||
|
|
||||||
|
#load presets
|
||||||
|
loaded_presets = ensure_preset_structure(loaded_presets)
|
||||||
|
|
||||||
self.add_fader()
|
self.add_fader()
|
||||||
self.add_fader()
|
self.add_fader()
|
||||||
self.add_toggle()
|
self.add_toggle()
|
||||||
for name, cc in [("Play", 117), ("Stop", 116), ("Record", 118), ("Arm", 119)]:
|
for name, cc in [("Play", 117), ("Stop", 116), ("Record", 118), ("Arm", 119)]:
|
||||||
self.add_transport(name, cc)
|
self.add_transport(name, cc)
|
||||||
|
|
||||||
self.ready = True
|
global ready
|
||||||
|
ready = True
|
||||||
|
|
||||||
self.refresh_preset_combo()
|
self.refresh_preset_combo()
|
||||||
self.restore_last_used()
|
self.restore_last_used()
|
||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
self.refresh_port_availability()
|
self.refresh_port_availability()
|
||||||
|
|
||||||
midi_receiver.midi_in_signal.connect(lambda msg: self.blink(self.in_blink))
|
midi_receiver.midi_in_signal.connect(lambda msg: self.blink(activity_indicator_midi_in))
|
||||||
midi_receiver.midi_out_signal.connect(lambda: self.blink(self.out_blink))
|
midi_receiver.midi_out_signal.connect(lambda: self.blink(self.out_blink))
|
||||||
midi_receiver.transport_out_signal.connect(lambda: self.blink(self.t_out_blink))
|
midi_receiver.transport_out_signal.connect(lambda: self.blink(self.t_out_blink))
|
||||||
midi_receiver.hw_cc_signal.connect(self.on_hw_cc)
|
midi_receiver.hw_cc_signal.connect(self.on_hw_cc)
|
||||||
@@ -642,17 +755,22 @@ class MainWindow(QMainWindow):
|
|||||||
osc_receiver.raw_message_signal.connect(self.on_osc_raw)
|
osc_receiver.raw_message_signal.connect(self.on_osc_raw)
|
||||||
osc_receiver.osc_out_signal.connect(self.on_osc_out_raw)
|
osc_receiver.osc_out_signal.connect(self.on_osc_out_raw)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def refresh_all_id_labels(self):
|
def refresh_all_id_labels(self):
|
||||||
# Center faders
|
# Center faders
|
||||||
center = [f for f in self.faders if not f.zone]
|
|
||||||
|
global faders
|
||||||
|
|
||||||
|
center = [f for f in faders if not f.zone]
|
||||||
for i, f in enumerate(center):
|
for i, f in enumerate(center):
|
||||||
f.update_id_label(None, i)
|
f.update_id_label(None, i)
|
||||||
# Left zone faders
|
# Left zone faders
|
||||||
left = sorted([f for f in self.faders if f.zone == "left"], key=lambda f: f.zone_index or 0)
|
left = sorted([f for f in faders if f.zone == "left"], key=lambda f: f.zone_index or 0)
|
||||||
for i, f in enumerate(left):
|
for i, f in enumerate(left):
|
||||||
f.update_id_label("left", i)
|
f.update_id_label("left", i)
|
||||||
# Right zone faders
|
# Right zone faders
|
||||||
right = sorted([f for f in self.faders if f.zone == "right"], key=lambda f: f.zone_index or 0)
|
right = sorted([f for f in faders if f.zone == "right"], key=lambda f: f.zone_index or 0)
|
||||||
for i, f in enumerate(right):
|
for i, f in enumerate(right):
|
||||||
f.update_id_label("right", i)
|
f.update_id_label("right", i)
|
||||||
# Center toggles
|
# Center toggles
|
||||||
@@ -717,10 +835,13 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def on_fader_zone_change(self, fader):
|
def on_fader_zone_change(self, fader):
|
||||||
# Save center index before removing from center row
|
# Save center index before removing from center row
|
||||||
|
|
||||||
|
global faders
|
||||||
|
|
||||||
if fader.zone is not None and fader.center_index is None:
|
if fader.zone is not None and fader.center_index is None:
|
||||||
# Being zoned for first time — save current center position
|
# Being zoned for first time — save current center position
|
||||||
if fader in self.faders:
|
if fader in faders:
|
||||||
fader.center_index = self.faders.index(fader)
|
fader.center_index = faders.index(fader)
|
||||||
|
|
||||||
# Remove by UUID from whichever slot it's in
|
# Remove by UUID from whichever slot it's in
|
||||||
for layout in [self.fader_row, self.zone_left_slot, self.zone_right_slot]:
|
for layout in [self.fader_row, self.zone_left_slot, self.zone_right_slot]:
|
||||||
@@ -747,18 +868,20 @@ class MainWindow(QMainWindow):
|
|||||||
self.fader_row.insertWidget(idx, fader)
|
self.fader_row.insertWidget(idx, fader)
|
||||||
fader.center_index = None
|
fader.center_index = None
|
||||||
fader.zone_index = None
|
fader.zone_index = None
|
||||||
self.vsep_left.setVisible(any(f.zone == "left" for f in self.faders))
|
self.vsep_left.setVisible(any(f.zone == "left" for f in faders))
|
||||||
self.vsep_right.setVisible(any(f.zone == "right" for f in self.faders))
|
self.vsep_right.setVisible(any(f.zone == "right" for f in faders))
|
||||||
has_left = any(f.zone == "left" for f in self.faders)
|
has_left = any(f.zone == "left" for f in faders)
|
||||||
has_right = any(f.zone == "right" for f in self.faders)
|
has_right = any(f.zone == "right" for f in faders)
|
||||||
|
|
||||||
self.update_window_width()
|
self.update_window_width()
|
||||||
|
|
||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
|
|
||||||
def select_strip(self, strip):
|
def select_strip(self, strip):
|
||||||
if self.selected_strip and self.selected_strip != strip:
|
global selected_strip
|
||||||
self.selected_strip.set_selected(False)
|
if selected_strip and selected_strip != strip:
|
||||||
self.selected_strip = strip
|
selected_strip.set_selected(False)
|
||||||
|
selected_strip = strip
|
||||||
strip.set_selected(True)
|
strip.set_selected(True)
|
||||||
self.selected_label.setText(f"Selected: {strip.title_edit.text()}")
|
self.selected_label.setText(f"Selected: {strip.title_edit.text()}")
|
||||||
|
|
||||||
@@ -781,10 +904,11 @@ class MainWindow(QMainWindow):
|
|||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
|
|
||||||
def remove_toggle(self, toggle):
|
def remove_toggle(self, toggle):
|
||||||
|
global selected_strip
|
||||||
if len(self.toggles) <= 1:
|
if len(self.toggles) <= 1:
|
||||||
return
|
return
|
||||||
if self.selected_strip == toggle:
|
if selected_strip == toggle:
|
||||||
self.selected_strip = None
|
selected_strip = None
|
||||||
self.selected_label.setText("Selected: None")
|
self.selected_label.setText("Selected: None")
|
||||||
self.toggle_row.removeWidget(toggle)
|
self.toggle_row.removeWidget(toggle)
|
||||||
toggle.deleteLater()
|
toggle.deleteLater()
|
||||||
@@ -793,14 +917,17 @@ class MainWindow(QMainWindow):
|
|||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
|
|
||||||
def add_fader(self, insert_after=None):
|
def add_fader(self, insert_after=None):
|
||||||
n = len(self.faders) + 1
|
|
||||||
|
global faders
|
||||||
|
|
||||||
|
n = len(faders) + 1
|
||||||
fader = FaderWidget(f"Fader {n}", default_cc=1)
|
fader = FaderWidget(f"Fader {n}", default_cc=1)
|
||||||
if insert_after is not None and insert_after in self.faders:
|
if insert_after is not None and insert_after in faders:
|
||||||
idx = self.faders.index(insert_after)
|
idx = faders.index(insert_after)
|
||||||
self.faders.insert(idx + 1, fader)
|
faders.insert(idx + 1, fader)
|
||||||
self.fader_row.insertWidget(idx + 1, fader)
|
self.fader_row.insertWidget(idx + 1, fader)
|
||||||
else:
|
else:
|
||||||
self.faders.append(fader)
|
faders.append(fader)
|
||||||
self.fader_row.addWidget(fader)
|
self.fader_row.addWidget(fader)
|
||||||
fader.plus_btn.clicked.connect(lambda: self.add_fader(insert_after=fader))
|
fader.plus_btn.clicked.connect(lambda: self.add_fader(insert_after=fader))
|
||||||
fader.minus_btn.clicked.connect(lambda: self.remove_fader(fader))
|
fader.minus_btn.clicked.connect(lambda: self.remove_fader(fader))
|
||||||
@@ -811,22 +938,31 @@ class MainWindow(QMainWindow):
|
|||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
|
|
||||||
def remove_fader(self, fader):
|
def remove_fader(self, fader):
|
||||||
if len(self.faders) <= 1:
|
|
||||||
|
global selected_strip
|
||||||
|
global faders
|
||||||
|
global spacing
|
||||||
|
|
||||||
|
if len(faders) <= 1:
|
||||||
return
|
return
|
||||||
if self.selected_strip == fader:
|
if selected_strip == fader:
|
||||||
self.selected_strip = None
|
selected_strip = None
|
||||||
self.selected_label.setText("Selected: None")
|
self.selected_label.setText("Selected: None")
|
||||||
self.fader_row.removeWidget(fader)
|
self.fader_row.removeWidget(fader)
|
||||||
fader.deleteLater()
|
fader.deleteLater()
|
||||||
self.faders.remove(fader)
|
faders.remove(fader)
|
||||||
self.update_window_width()
|
self.update_window_width()
|
||||||
self.refresh_all_id_labels()
|
self.refresh_all_id_labels()
|
||||||
|
|
||||||
def update_window_width(self):
|
def update_window_width(self):
|
||||||
nf = len(self.faders)
|
|
||||||
|
global faders
|
||||||
|
global strip_width
|
||||||
|
|
||||||
|
nf = len(faders)
|
||||||
nt = len(self.toggles)
|
nt = len(self.toggles)
|
||||||
fader_w = (nf * self.strip_width) + ((nf - 1) * self.spacing) + self.padding
|
fader_w = (nf * strip_width) + ((nf - 1) * spacing) + padding
|
||||||
toggle_w = (nt * self.toggle_strip_width) + ((nt - 1) * self.spacing) + self.padding
|
toggle_w = (nt * self.toggle_strip_width) + ((nt - 1) * spacing) + padding
|
||||||
w = max(fader_w, toggle_w, 300)
|
w = max(fader_w, toggle_w, 300)
|
||||||
self.resize(w, self.height())
|
self.resize(w, self.height())
|
||||||
|
|
||||||
@@ -838,27 +974,34 @@ class MainWindow(QMainWindow):
|
|||||||
return sep
|
return sep
|
||||||
|
|
||||||
def refresh_preset_combo(self):
|
def refresh_preset_combo(self):
|
||||||
self.preset_combo.blockSignals(True)
|
|
||||||
self.preset_combo.clear()
|
global loaded_presets
|
||||||
self.preset_combo.addItems(sorted(self.data["presets"].keys()))
|
global menuPresets
|
||||||
self.preset_combo.blockSignals(False)
|
|
||||||
|
menuPresets.blockSignals(True)
|
||||||
|
menuPresets.clear()
|
||||||
|
menuPresets.addItems(sorted(loaded_presets["presets"].keys()))
|
||||||
|
menuPresets.blockSignals(False)
|
||||||
|
|
||||||
def restore_last_used(self):
|
def restore_last_used(self):
|
||||||
last = self.data.get("__last_used__")
|
|
||||||
if last and last in self.data["presets"]:
|
global loaded_presets
|
||||||
self.preset_combo.blockSignals(True)
|
global menuPresets
|
||||||
self.preset_combo.setCurrentText(last)
|
|
||||||
self.preset_combo.blockSignals(False)
|
last = loaded_presets.get("__last_used__")
|
||||||
self.apply_preset(self.data["presets"][last])
|
if last and last in loaded_presets["presets"]:
|
||||||
|
menuPresets.blockSignals(True)
|
||||||
|
menuPresets.setCurrentText(last)
|
||||||
|
menuPresets.blockSignals(False)
|
||||||
|
self.apply_preset(loaded_presets["presets"][last])
|
||||||
print(f"Restored last used preset: {last}")
|
print(f"Restored last used preset: {last}")
|
||||||
|
|
||||||
def on_preset_selected(self, name):
|
|
||||||
if self.ready and name and name in self.data["presets"]:
|
|
||||||
self.data["__last_used__"] = name
|
|
||||||
save_presets_file(self.data)
|
|
||||||
self.apply_preset(self.data["presets"][name])
|
|
||||||
|
|
||||||
def _show_save_status(self):
|
def _show_save_status(self):
|
||||||
|
|
||||||
|
global labelStatus
|
||||||
|
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
today = date.today()
|
today = date.today()
|
||||||
@@ -869,15 +1012,18 @@ class MainWindow(QMainWindow):
|
|||||||
time_str = f"Yesterday {time_part}"
|
time_str = f"Yesterday {time_part}"
|
||||||
else:
|
else:
|
||||||
time_str = f"{now.strftime('%b')} {now.day} {time_part}"
|
time_str = f"{now.strftime('%b')} {now.day} {time_part}"
|
||||||
self.save_status_label.setText(f"[ Saved {time_str} ]")
|
labelStatus.setText(f"[ Saved {time_str} ]")
|
||||||
QTimer.singleShot(4000, lambda: self.save_status_label.setText(""))
|
QTimer.singleShot(4000, lambda: labelStatus.setText(""))
|
||||||
|
|
||||||
def _build_preset_data(self, name):
|
def _build_preset_data(self, name):
|
||||||
|
|
||||||
|
global faders
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"midi_port": self.port_combo.currentText(),
|
"midi_port": self.port_combo.currentText(),
|
||||||
"midi_input": self.in_combo.currentText(),
|
"midi_input": picker_midi_in.currentText(),
|
||||||
"transport_port": self.t_out_combo.currentText(),
|
"transport_port": self.t_out_combo.currentText(),
|
||||||
"faders": [f.get_state() for f in self.faders],
|
"faders": [f.get_state() for f in faders],
|
||||||
"toggles": [t.get_state() for t in self.toggles],
|
"toggles": [t.get_state() for t in self.toggles],
|
||||||
"transports": [t.get_state() for t in self.transports],
|
"transports": [t.get_state() for t in self.transports],
|
||||||
"browser": {
|
"browser": {
|
||||||
@@ -891,52 +1037,12 @@ class MainWindow(QMainWindow):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_preset(self):
|
|
||||||
name = self.preset_combo.currentText()
|
|
||||||
if not name or name not in self.data["presets"]:
|
|
||||||
# No current preset — fall through to Save As
|
|
||||||
self.save_preset_as()
|
|
||||||
return
|
|
||||||
self.data["presets"][name] = self._build_preset_data(name)
|
|
||||||
self.data["__last_used__"] = name
|
|
||||||
save_presets_file(self.data)
|
|
||||||
self._show_save_status()
|
|
||||||
print(f"Saved preset: {name}")
|
|
||||||
|
|
||||||
def save_preset_as(self):
|
|
||||||
name, ok = QInputDialog.getText(self, "Save As", "Preset name:")
|
|
||||||
if not ok or not name.strip():
|
|
||||||
return
|
|
||||||
name = name.strip()
|
|
||||||
self.data["presets"][name] = self._build_preset_data(name)
|
|
||||||
self.data["__last_used__"] = name
|
|
||||||
save_presets_file(self.data)
|
|
||||||
self.refresh_preset_combo()
|
|
||||||
self.preset_combo.setCurrentText(name)
|
|
||||||
self._show_save_status()
|
|
||||||
print(f"Saved preset as: {name}")
|
|
||||||
|
|
||||||
def delete_preset(self):
|
|
||||||
from PyQt6.QtWidgets import QMessageBox
|
|
||||||
name = self.preset_combo.currentText()
|
|
||||||
if not name or name not in self.data["presets"]:
|
|
||||||
return
|
|
||||||
msg = QMessageBox(self)
|
|
||||||
msg.setWindowTitle("Remove Preset")
|
|
||||||
msg.setText(f'Are you sure you want to remove "{name}"?')
|
|
||||||
msg.setIcon(QMessageBox.Icon.NoIcon)
|
|
||||||
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
|
||||||
reply = msg.exec()
|
|
||||||
if reply != QMessageBox.StandardButton.Yes:
|
|
||||||
return
|
|
||||||
del self.data["presets"][name]
|
|
||||||
if self.data["__last_used__"] == name:
|
|
||||||
self.data["__last_used__"] = None
|
|
||||||
save_presets_file(self.data)
|
|
||||||
self.refresh_preset_combo()
|
|
||||||
print(f"Deleted preset: {name}")
|
|
||||||
|
|
||||||
def apply_preset(self, preset):
|
def apply_preset(self, preset):
|
||||||
|
|
||||||
|
global faders
|
||||||
|
global available_ports_midi_in
|
||||||
|
|
||||||
port_name = preset.get("midi_port", "")
|
port_name = preset.get("midi_port", "")
|
||||||
if port_name in available_ports:
|
if port_name in available_ports:
|
||||||
self.port_combo.setCurrentText(port_name)
|
self.port_combo.setCurrentText(port_name)
|
||||||
@@ -946,23 +1052,23 @@ class MainWindow(QMainWindow):
|
|||||||
self.t_out_combo.setCurrentText(transport_port)
|
self.t_out_combo.setCurrentText(transport_port)
|
||||||
|
|
||||||
midi_input = preset.get("midi_input", "")
|
midi_input = preset.get("midi_input", "")
|
||||||
input_names = [entry[0] for entry in self.available_in_ports]
|
input_names = [entry[0] for entry in available_ports_midi_in]
|
||||||
if midi_input in input_names:
|
if midi_input in input_names:
|
||||||
self.in_combo.setCurrentText(midi_input)
|
picker_midi_in.setCurrentText(midi_input)
|
||||||
fader_states = preset.get("faders", [])
|
fader_states = preset.get("faders", [])
|
||||||
while len(self.faders) < len(fader_states):
|
while len(faders) < len(fader_states):
|
||||||
self.add_fader()
|
self.add_fader()
|
||||||
while len(self.faders) > len(fader_states):
|
while len(faders) > len(fader_states):
|
||||||
widget = self.faders.pop()
|
widget = faders.pop()
|
||||||
self.fader_row.removeWidget(widget)
|
self.fader_row.removeWidget(widget)
|
||||||
widget.deleteLater()
|
widget.deleteLater()
|
||||||
self.update_window_width()
|
self.update_window_width()
|
||||||
for i, widget in enumerate(self.faders):
|
for i, widget in enumerate(faders):
|
||||||
widget.set_state(fader_states[i])
|
widget.set_state(fader_states[i])
|
||||||
|
|
||||||
# First place all center faders in their saved positions
|
# First place all center faders in their saved positions
|
||||||
center_faders = sorted(
|
center_faders = sorted(
|
||||||
[f for f in self.faders if not f.zone],
|
[f for f in faders if not f.zone],
|
||||||
key=lambda f: f.center_index if f.center_index is not None else 999
|
key=lambda f: f.center_index if f.center_index is not None else 999
|
||||||
)
|
)
|
||||||
for f in center_faders:
|
for f in center_faders:
|
||||||
@@ -973,11 +1079,11 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Then place zoned faders in zone order
|
# Then place zoned faders in zone order
|
||||||
left_faders = sorted(
|
left_faders = sorted(
|
||||||
[f for f in self.faders if f.zone == "left"],
|
[f for f in faders if f.zone == "left"],
|
||||||
key=lambda f: f.zone_index if f.zone_index is not None else 999
|
key=lambda f: f.zone_index if f.zone_index is not None else 999
|
||||||
)
|
)
|
||||||
right_faders = sorted(
|
right_faders = sorted(
|
||||||
[f for f in self.faders if f.zone == "right"],
|
[f for f in faders if f.zone == "right"],
|
||||||
key=lambda f: f.zone_index if f.zone_index is not None else 999
|
key=lambda f: f.zone_index if f.zone_index is not None else 999
|
||||||
)
|
)
|
||||||
for f in left_faders:
|
for f in left_faders:
|
||||||
@@ -1057,16 +1163,24 @@ class MainWindow(QMainWindow):
|
|||||||
self.osc_port_spin.setValue(osc.get("listen_port", 9000))
|
self.osc_port_spin.setValue(osc.get("listen_port", 9000))
|
||||||
self.osc_send_port_spin.setValue(osc.get("send_port", 8000))
|
self.osc_send_port_spin.setValue(osc.get("send_port", 8000))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_input_port_select(self, display_name):
|
def on_input_port_select(self, display_name):
|
||||||
|
|
||||||
|
global available_ports_midi_in
|
||||||
|
|
||||||
if not display_name:
|
if not display_name:
|
||||||
return
|
return
|
||||||
port_entry = next((entry for entry in self.available_in_ports if entry[0] == display_name), None)
|
port_entry = next((entry for entry in available_ports_midi_in if entry[0] == display_name), None)
|
||||||
if port_entry is None:
|
if port_entry is None:
|
||||||
return
|
return
|
||||||
display_name, original_index, available = port_entry
|
display_name, original_index, available = port_entry
|
||||||
if not available:
|
if not available:
|
||||||
self.in_status.setText("In use by another program")
|
label_connection_midi_in.setText("In use by another program")
|
||||||
self.in_status.setStyleSheet("color: #e67e22;")
|
label_connection_midi_in.setStyleSheet("color: #e67e22;")
|
||||||
return
|
return
|
||||||
if hasattr(self, "midi_in"):
|
if hasattr(self, "midi_in"):
|
||||||
close_midi_input_port(self.midi_in)
|
close_midi_input_port(self.midi_in)
|
||||||
@@ -1074,16 +1188,19 @@ class MainWindow(QMainWindow):
|
|||||||
try:
|
try:
|
||||||
|
|
||||||
self.midi_in = open_midi_input_port(original_index, self.on_midi_in)
|
self.midi_in = open_midi_input_port(original_index, self.on_midi_in)
|
||||||
self.in_status.setText("Connected")
|
label_connection_midi_in.setText("Connected")
|
||||||
self.in_status.setStyleSheet("color: #27ae60;")
|
label_connection_midi_in.setStyleSheet("color: #27ae60;")
|
||||||
print(f"Opened input port: {display_name}")
|
print(f"Opened input port: {display_name}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to open port {display_name}: {e}")
|
print(f"Failed to open port {display_name}: {e}")
|
||||||
self.in_status.setText("Failed to open")
|
label_connection_midi_in.setText("Failed to open")
|
||||||
self.in_status.setStyleSheet("color: #c0392b;")
|
label_connection_midi_in.setStyleSheet("color: #c0392b;")
|
||||||
|
|
||||||
def on_hw_cc(self, fader_uid, value):
|
def on_hw_cc(self, fader_uid, value):
|
||||||
for fader in self.faders:
|
|
||||||
|
global faders
|
||||||
|
|
||||||
|
for fader in faders:
|
||||||
if fader.uid == fader_uid:
|
if fader.uid == fader_uid:
|
||||||
out_cc = fader.cc_spin.value()
|
out_cc = fader.cc_spin.value()
|
||||||
print(f"[convert] HW CC{fader.hw_cc} ({value}) → CC{out_cc} ({value})")
|
print(f"[convert] HW CC{fader.hw_cc} ({value}) → CC{out_cc} ({value})")
|
||||||
@@ -1121,12 +1238,19 @@ class MainWindow(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def start_learn(self, fader):
|
def start_learn(self, fader):
|
||||||
if self.learning_strip and self.learning_strip != fader:
|
|
||||||
self.learning_strip.stop_learn()
|
global learning_strip
|
||||||
self.learning_strip = fader
|
|
||||||
|
if learning_strip and learning_strip != fader:
|
||||||
|
learning_strip.stop_learn()
|
||||||
|
learning_strip = fader
|
||||||
fader.start_learn()
|
fader.start_learn()
|
||||||
|
|
||||||
def on_midi_in(self, event, data=None):
|
def on_midi_in(self, event, data=None):
|
||||||
|
|
||||||
|
global learning_strip
|
||||||
|
global faders
|
||||||
|
|
||||||
message, _ = event
|
message, _ = event
|
||||||
status = message[0] & 0xF0
|
status = message[0] & 0xF0
|
||||||
cc_num = message[1] if len(message) >= 2 else None
|
cc_num = message[1] if len(message) >= 2 else None
|
||||||
@@ -1136,7 +1260,7 @@ class MainWindow(QMainWindow):
|
|||||||
bound_fader = None
|
bound_fader = None
|
||||||
bound_transport = None
|
bound_transport = None
|
||||||
if status == 0xB0:
|
if status == 0xB0:
|
||||||
for fader in self.faders:
|
for fader in faders:
|
||||||
if fader.hw_cc == cc_num:
|
if fader.hw_cc == cc_num:
|
||||||
bound_fader = fader
|
bound_fader = fader
|
||||||
break
|
break
|
||||||
@@ -1152,9 +1276,9 @@ class MainWindow(QMainWindow):
|
|||||||
break
|
break
|
||||||
|
|
||||||
# Learning mode — bind CC to strip, don't forward
|
# Learning mode — bind CC to strip, don't forward
|
||||||
if status == 0xB0 and self.learning_strip:
|
if status == 0xB0 and learning_strip:
|
||||||
self.learning_strip.bind_hw_cc(cc_num)
|
learning_strip.bind_hw_cc(cc_num)
|
||||||
self.learning_strip = None
|
learning_strip = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if bound_transport:
|
if bound_transport:
|
||||||
@@ -1416,8 +1540,9 @@ class MainWindow(QMainWindow):
|
|||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
app.setStyle("Fusion")
|
|
||||||
|
|
||||||
|
|
||||||
# VS Code dark theme palette
|
# VS Code dark theme palette
|
||||||
dark_palette = app.palette()
|
dark_palette = app.palette()
|
||||||
|
|||||||
+83
@@ -1,11 +1,20 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from PyQt6.QtWidgets import (
|
||||||
|
QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||||
|
QHBoxLayout, QSlider, QComboBox, QLabel, QPushButton,
|
||||||
|
QSizePolicy, QSpinBox, QLineEdit, QInputDialog, QFrame,
|
||||||
|
QCheckBox, QMessageBox
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
PRESETS_DIR = os.path.join(BASE_DIR, "presets")
|
PRESETS_DIR = os.path.join(BASE_DIR, "presets")
|
||||||
PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json")
|
PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json")
|
||||||
os.makedirs(PRESETS_DIR, exist_ok=True)
|
os.makedirs(PRESETS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def load_presets_file():
|
def load_presets_file():
|
||||||
if os.path.exists(PRESETS_FILE):
|
if os.path.exists(PRESETS_FILE):
|
||||||
with open(PRESETS_FILE, "r") as f:
|
with open(PRESETS_FILE, "r") as f:
|
||||||
@@ -15,3 +24,77 @@ def load_presets_file():
|
|||||||
def save_presets_file(data):
|
def save_presets_file(data):
|
||||||
with open(PRESETS_FILE, "w") as f:
|
with open(PRESETS_FILE, "w") as f:
|
||||||
json.dump(data, f, indent=2)
|
json.dump(data, f, indent=2)
|
||||||
|
|
||||||
|
def ensure_preset_structure(presets):
|
||||||
|
if "presets" not in presets:
|
||||||
|
return {"__last_used__": None, "presets": presets}
|
||||||
|
return presets
|
||||||
|
|
||||||
|
def on_preset_selected(menu_presets: QComboBox, app_window: QMainWindow, ready: bool, loaded_presets: dict):
|
||||||
|
|
||||||
|
selectedOption = menu_presets.currentText()
|
||||||
|
|
||||||
|
if ready and selectedOption and selectedOption in loaded_presets["presets"]:
|
||||||
|
loaded_presets["__last_used__"] = selectedOption
|
||||||
|
save_presets_file(loaded_presets)
|
||||||
|
app_window.apply_preset(loaded_presets["presets"][selectedOption])
|
||||||
|
|
||||||
|
def save_preset(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict):
|
||||||
|
|
||||||
|
selectedOption = menu_presets.currentText()
|
||||||
|
|
||||||
|
if not selectedOption or selectedOption not in loaded_presets["presets"]:
|
||||||
|
app_window.save_preset_as() # not self
|
||||||
|
return
|
||||||
|
loaded_presets["presets"][selectedOption] = app_window._build_preset_data(selectedOption) # not self
|
||||||
|
loaded_presets["__last_used__"] = selectedOption
|
||||||
|
save_presets_file(loaded_presets)
|
||||||
|
app_window._show_save_status() # not self
|
||||||
|
print(f"Saved preset: {selectedOption}")
|
||||||
|
|
||||||
|
def save_preset_as(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict):
|
||||||
|
|
||||||
|
print(f"save as button tapped")
|
||||||
|
|
||||||
|
selectedOption = menu_presets.currentText()
|
||||||
|
|
||||||
|
selectedOption, ok = QInputDialog.getText(app_window, "Save As", "Preset name:")
|
||||||
|
if not ok or not selectedOption.strip():
|
||||||
|
return
|
||||||
|
selectedOption = selectedOption.strip()
|
||||||
|
loaded_presets["presets"][selectedOption] = app_window._build_preset_data(selectedOption)
|
||||||
|
loaded_presets["__last_used__"] = selectedOption
|
||||||
|
save_presets_file(loaded_presets)
|
||||||
|
app_window.refresh_preset_combo()
|
||||||
|
menu_presets.setCurrentText(selectedOption)
|
||||||
|
app_window._show_save_status()
|
||||||
|
print(f"Saved preset as: {selectedOption}")
|
||||||
|
|
||||||
|
|
||||||
|
def delete_preset(app_window: QMainWindow, menu_presets: QComboBox, loaded_presets: dict):
|
||||||
|
|
||||||
|
selectedOption = menu_presets.currentText()
|
||||||
|
|
||||||
|
selectedOption = menu_presets.currentText()
|
||||||
|
|
||||||
|
if not selectedOption or selectedOption not in loaded_presets["presets"]:
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = QMessageBox(app_window)
|
||||||
|
msg.setWindowTitle("Remove Preset")
|
||||||
|
msg.setText(f'Are you sure you want to remove "{selectedOption}"?')
|
||||||
|
msg.setIcon(QMessageBox.Icon.NoIcon)
|
||||||
|
msg.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
||||||
|
reply = msg.exec()
|
||||||
|
|
||||||
|
if reply != QMessageBox.StandardButton.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
del loaded_presets["presets"][selectedOption]
|
||||||
|
|
||||||
|
if loaded_presets["__last_used__"] == selectedOption:
|
||||||
|
loaded_presets["__last_used__"] = None
|
||||||
|
|
||||||
|
save_presets_file(loaded_presets)
|
||||||
|
app_window.refresh_preset_combo()
|
||||||
|
print(f"Deleted preset: {selectedOption}")
|
||||||
+982
-27
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user