Skip to content

Commit c235213

Browse files
committed
Clarify imported APO preset state
1 parent 2ee5139 commit c235213

5 files changed

Lines changed: 97 additions & 22 deletions

File tree

src/mini_eq/app.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .instance import MiniEqAlreadyRunningError, MiniEqInstanceGuard
2929
from .routing import SystemWideEqController
3030
from .window import MiniEqWindow
31+
from .window_presets import imported_apo_curve_label
3132

3233

3334
class MiniEqApplication(Adw.Application):
@@ -91,20 +92,22 @@ def ensure_window(self, *, present: bool) -> None:
9192
return
9293

9394
controller: SystemWideEqController | None = None
95+
initial_curve_label: str | None = None
9496

9597
try:
9698
controller = SystemWideEqController(self.args.output_sink)
9799
controller.start()
98100

99101
if self.args.import_apo:
100102
controller.import_apo_preset(self.args.import_apo)
103+
initial_curve_label = imported_apo_curve_label(self.args.import_apo)
101104
except Exception as exc:
102105
if controller is not None:
103106
controller.shutdown()
104107
raise SystemExit(str(exc)) from exc
105108

106109
self.controller = controller
107-
self.window = MiniEqWindow(self, self.controller, self.args.auto_route)
110+
self.window = MiniEqWindow(self, self.controller, self.args.auto_route, initial_curve_label=initial_curve_label)
108111
self.window.set_icon_name(APP_ICON_NAME)
109112
self.window.present_after_setup = present
110113
self.window.set_visible(present)

src/mini_eq/window.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
from .window_headroom import MiniEqWindowHeadroomMixin, format_headroom_peak_db
4545
from .window_layout import MiniEqWindowLayoutMixin
4646
from .window_preferences import MiniEqWindowPreferencesMixin
47-
from .window_presets import MiniEqWindowPresetMixin
47+
from .window_presets import MiniEqWindowPresetMixin, imported_apo_curve_label
4848
from .window_utility import MiniEqWindowUtilityPaneMixin
4949
from .window_utils import requested_switch_state, set_switch_confirmed_state
5050

@@ -81,7 +81,13 @@ class MiniEqWindow(
8181
MiniEqWindowLayoutMixin,
8282
Adw.ApplicationWindow,
8383
):
84-
def __init__(self, app: Adw.Application, controller: SystemWideEqController, auto_route: bool) -> None:
84+
def __init__(
85+
self,
86+
app: Adw.Application,
87+
controller: SystemWideEqController,
88+
auto_route: bool,
89+
initial_curve_label: str | None = None,
90+
) -> None:
8591
super().__init__(application=app, title=APP_NAME)
8692
self.add_css_class("mini-eq-window")
8793
self.controller = controller
@@ -117,7 +123,7 @@ def __init__(self, app: Adw.Application, controller: SystemWideEqController, aut
117123
self.curve_revert_baseline_label: str | None = None
118124
self.curve_revert_baseline_signature: str | None = None
119125
self.curve_revert_baseline_payload: dict[str, object] | None = None
120-
self.set_curve_revert_baseline("Neutral")
126+
self.set_curve_revert_baseline(initial_curve_label or "Neutral")
121127
self.output_preset_auto_applied = False
122128
self.output_preset_curve_auto_loaded = False
123129
self.updating_output_preset_switch = False
@@ -822,23 +828,13 @@ def on_import_apo_done(self, dialog: Gtk.FileDialog, result: Gio.AsyncResult) ->
822828
self.set_visible_band_count(imported_count)
823829
self.current_preset_name = None
824830
self.saved_preset_signature = self.controller.state_signature()
825-
self.set_curve_revert_baseline("Imported APO Preset")
831+
self.set_curve_revert_baseline(imported_apo_curve_label(path))
826832
self.output_preset_curve_auto_loaded = False
833+
self.refresh_preset_list()
827834
self.sync_ui_from_state()
828835
except Exception as exc:
829836
self.set_status(str(exc))
830837

831-
def on_clear_clicked(self, button: Gtk.Button) -> None:
832-
self.controller.reset_state()
833-
self.selected_band_index = None
834-
self.set_visible_band_count(DEFAULT_ACTIVE_BANDS)
835-
self.current_preset_name = None
836-
self.saved_preset_signature = self.controller.state_signature()
837-
self.set_curve_revert_baseline("Neutral")
838-
self.output_preset_curve_auto_loaded = False
839-
self.sync_ui_from_state()
840-
self.set_status("Equalizer Reset")
841-
842838
def on_output_changed(self, combo: Gtk.DropDown, _param: object) -> None:
843839
if self.updating_output_combo:
844840
return

src/mini_eq/window_layout.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ def add_window_action(action_name: str, callback) -> None:
126126
self.add_action(action)
127127

128128
add_window_action("import-apo", lambda: self.on_import_apo_clicked(tools_button))
129-
add_window_action("reset-eq", lambda: self.on_clear_clicked(tools_button))
130129
add_window_action("preferences", self.show_preferences_dialog)
131130
add_window_action("about", self.show_about_dialog)
132131
self.appearance_action = Gio.SimpleAction.new_stateful(
@@ -139,7 +138,6 @@ def add_window_action(action_name: str, callback) -> None:
139138

140139
tools_menu = Gio.Menu()
141140
tools_menu.append("Import Equalizer APO…", "win.import-apo")
142-
tools_menu.append("Reset EQ", "win.reset-eq")
143141

144142
appearance_menu = Gio.Menu()
145143
appearance_menu.append("Follow System", "win.appearance::system")

src/mini_eq/window_presets.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@
3232
)
3333
from .window_utils import requested_switch_state, set_switch_confirmed_state
3434

35+
APO_IMPORT_LABEL_PREFIX = "Imported APO: "
36+
37+
38+
def imported_apo_curve_label(path: str) -> str:
39+
preset_name = sanitize_preset_name(Path(path).stem)
40+
if preset_name:
41+
return f"{APO_IMPORT_LABEL_PREFIX}{preset_name}"
42+
return "Imported APO"
43+
3544

3645
@dataclass(frozen=True)
3746
class PresetPanelUiState:
@@ -175,6 +184,25 @@ def has_curve_revert_changes(self) -> bool:
175184
def curve_revert_target_is_neutral(self) -> bool:
176185
return self.current_preset_name is None and self.curve_revert_signature() == self.default_preset_signature
177186

187+
def current_curve_selector_label(self) -> str | None:
188+
if self.current_preset_name is not None:
189+
return None
190+
191+
label = self.curve_revert_label()
192+
signature = self.curve_revert_signature()
193+
if not label or signature is None or signature == self.default_preset_signature:
194+
return None
195+
return label
196+
197+
def suggested_save_as_name(self) -> str:
198+
if self.current_preset_name is not None:
199+
return self.current_preset_name
200+
201+
label = self.current_curve_selector_label()
202+
if label and label.startswith(APO_IMPORT_LABEL_PREFIX):
203+
return sanitize_preset_name(label[len(APO_IMPORT_LABEL_PREFIX) :])
204+
return ""
205+
178206
def output_preset_is_active(self) -> bool:
179207
linked_preset = self.output_preset_link_name()
180208
return bool(
@@ -443,11 +471,19 @@ def update_default_preset_state(self) -> None:
443471

444472
def refresh_preset_list(self) -> None:
445473
self.preset_names = list_preset_names()
446-
self.preset_model.splice(0, self.preset_model.get_n_items(), self.preset_names)
474+
display_names = list(self.preset_names)
475+
combo_preset_names: list[str | None] = list(self.preset_names)
447476

448477
selected_index = Gtk.INVALID_LIST_POSITION
449478
if self.current_preset_name in self.preset_names:
450479
selected_index = self.preset_names.index(self.current_preset_name)
480+
elif current_curve_label := self.current_curve_selector_label():
481+
display_names.insert(0, current_curve_label)
482+
combo_preset_names.insert(0, None)
483+
selected_index = 0
484+
485+
self.preset_combo_preset_names = combo_preset_names
486+
self.preset_model.splice(0, self.preset_model.get_n_items(), display_names)
451487

452488
self.updating_preset_combo = True
453489
try:
@@ -685,11 +721,16 @@ def on_preset_selected(self, combo: Gtk.DropDown, _param: object) -> None:
685721
return
686722

687723
selected = combo.get_selected()
688-
if selected == Gtk.INVALID_LIST_POSITION or selected >= len(self.preset_names):
724+
combo_preset_names = getattr(self, "preset_combo_preset_names", self.preset_names)
725+
if selected == Gtk.INVALID_LIST_POSITION or selected >= len(combo_preset_names):
726+
return
727+
728+
preset_name = combo_preset_names[selected]
729+
if preset_name is None:
689730
return
690731

691732
try:
692-
self.load_library_preset(self.preset_names[selected])
733+
self.load_library_preset(preset_name)
693734
except Exception as exc:
694735
self.set_status(str(exc))
695736

@@ -704,7 +745,7 @@ def on_preset_save_clicked(self, button: Gtk.Button) -> None:
704745
self.on_preset_save_as_clicked(button)
705746

706747
def on_preset_save_as_clicked(self, button: Gtk.Button) -> None:
707-
initial_name = self.current_preset_name or ""
748+
initial_name = self.suggested_save_as_name()
708749
self.prompt_for_preset_name("Save Preset As", "Save", initial_name, self.save_current_state_to_preset)
709750

710751
def on_preset_revert_clicked(self, button: Gtk.Button) -> None:

tests/test_mini_eq_output_presets.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,43 @@ def test_revert_action_tracks_unsaved_import_baseline() -> None:
385385
assert test_window.statuses[-1] == "Reverted to Imported APO Preset"
386386

387387

388+
def test_unsaved_apo_import_is_shown_in_preset_selector(monkeypatch, tmp_path) -> None:
389+
monkeypatch.setattr(core, "PRESET_STORAGE_DIR", tmp_path / "presets")
390+
controller = make_controller()
391+
controller.bands[0].gain_db = 2.0
392+
test_window = OutputPresetWindow(controller)
393+
test_window.set_curve_revert_baseline("Imported APO: HD 650")
394+
395+
test_window.refresh_preset_list()
396+
397+
assert test_window.preset_model.items == ["Imported APO: HD 650"]
398+
assert test_window.preset_combo.selected == 0
399+
assert test_window.suggested_save_as_name() == "HD 650"
400+
401+
test_window.on_preset_selected(test_window.preset_combo, None)
402+
403+
assert test_window.current_preset_name is None
404+
405+
406+
def test_saved_preset_selection_maps_past_unsaved_apo_selector_item(monkeypatch, tmp_path) -> None:
407+
monkeypatch.setattr(core, "PRESET_STORAGE_DIR", tmp_path / "presets")
408+
write_test_preset("Headphones", 4.0)
409+
controller = make_controller()
410+
controller.bands[0].gain_db = 2.0
411+
test_window = OutputPresetWindow(controller)
412+
test_window.set_curve_revert_baseline("Imported APO: HD 650")
413+
414+
test_window.refresh_preset_list()
415+
416+
assert test_window.preset_model.items == ["Imported APO: HD 650", "Headphones"]
417+
418+
test_window.preset_combo.selected = 1
419+
test_window.on_preset_selected(test_window.preset_combo, None)
420+
421+
assert test_window.current_preset_name == "Headphones"
422+
assert controller.bands[0].gain_db == 4.0
423+
424+
388425
def test_initial_output_preset_auto_loads_linked_preset(monkeypatch, tmp_path) -> None:
389426
monkeypatch.setattr(core, "PRESET_STORAGE_DIR", tmp_path / "presets")
390427
monkeypatch.setattr(core, "OUTPUT_PRESET_LINKS_PATH", tmp_path / "output-presets.json")

0 commit comments

Comments
 (0)