v0.43.0: visualizer presets
Each visualizer style's settings page has a Preset drop-down and a "Save as Preset…" button. The drop-down lists the built-in Default, your presets, and "Edit Presets…" (rename in place, delete); it reads "Custom" whenever the settings match no preset. Presets sync with the preferences; the per-machine sync delay is never part of one.
This commit is contained in:
@@ -866,3 +866,176 @@ class TestNewControls:
|
||||
return sum(QColor(img.pixel(x, y)) != QColor("white")
|
||||
for x in range(400) for y in range(0, 400, 2))
|
||||
assert ink(0.5) < ink(1.0) < ink(2.0)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# 8. Presets
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
from lintunes.gui import visualizer_presets as presets
|
||||
|
||||
|
||||
def _combo_texts(combo):
|
||||
return [combo.itemText(i) for i in range(combo.count())]
|
||||
|
||||
|
||||
class TestPresets:
|
||||
def _dialog(self, prefs):
|
||||
return TestPreferencesPage._dialog(None, prefs)
|
||||
|
||||
def test_every_style_has_a_preset_menu(self, qapp, tmp_path):
|
||||
dialog = self._dialog(Preferences(tmp_path))
|
||||
for style in ("bars", "radial", "laser"):
|
||||
combo = dialog._preset_combos[style]
|
||||
assert combo.currentText() == "Default" # untouched = Default
|
||||
assert _combo_texts(combo)[-1] == "Edit Presets…"
|
||||
|
||||
def test_changing_a_setting_reads_custom(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
dialog = self._dialog(prefs)
|
||||
dialog._spokes_slider.setValue(60)
|
||||
assert dialog._preset_combos["radial"].currentText() == "Custom"
|
||||
assert dialog._preset_combos["laser"].currentText() == "Default"
|
||||
|
||||
def test_save_and_apply(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
dialog = self._dialog(prefs)
|
||||
dialog._angle_sliders["visualizer_laser_pitch"].setValue(40)
|
||||
dialog._grid.setChecked(True)
|
||||
dialog._speaker.setValues(150, 300)
|
||||
dialog._width_box.setValue(3.5)
|
||||
dialog._ask_preset_name = lambda suggestion: "Big Tilt"
|
||||
dialog._save_preset("laser")
|
||||
combo = dialog._preset_combos["laser"]
|
||||
assert combo.currentText() == "Big Tilt"
|
||||
|
||||
# Back to Default: every control (and setting) follows.
|
||||
dialog._on_preset_chosen("laser", combo.findData("Default"))
|
||||
assert prefs.get("visualizer_laser_pitch") == 0
|
||||
assert prefs.get("visualizer_laser_grid") is False
|
||||
assert prefs.get("visualizer_laser_width") == 1.5
|
||||
assert dialog._speaker.values() == (0, 300)
|
||||
assert combo.currentText() == "Default"
|
||||
|
||||
dialog._on_preset_chosen("laser", combo.findData("Big Tilt"))
|
||||
assert prefs.get("visualizer_laser_pitch") == 40
|
||||
assert prefs.get("visualizer_laser_grid") is True
|
||||
assert prefs.get("visualizer_laser_width") == 3.5
|
||||
assert dialog._angle_sliders["visualizer_laser_pitch"].value() == 40
|
||||
assert dialog._speaker.values() == (150, 300)
|
||||
assert combo.currentText() == "Big Tilt"
|
||||
|
||||
def test_presets_survive_a_restart(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
dialog = self._dialog(prefs)
|
||||
dialog._bars_fade_slider.setValue(20)
|
||||
dialog._ask_preset_name = lambda suggestion: "Ghosty"
|
||||
dialog._save_preset("bars")
|
||||
again = self._dialog(Preferences(tmp_path))
|
||||
assert "Ghosty" in _combo_texts(again._preset_combos["bars"])
|
||||
|
||||
def test_default_is_reserved(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
dialog = self._dialog(prefs)
|
||||
warned = []
|
||||
dialog._preset_warning = warned.append
|
||||
dialog._ask_preset_name = lambda suggestion: "Default"
|
||||
dialog._save_preset("radial")
|
||||
assert warned and presets.presets(prefs, "radial") == {}
|
||||
|
||||
def test_overwrite_asks_first(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
dialog = self._dialog(prefs)
|
||||
dialog._ask_preset_name = lambda suggestion: "Mine"
|
||||
dialog._save_preset("radial")
|
||||
dialog._spokes_slider.setValue(200)
|
||||
dialog._confirm_overwrite = lambda name: False
|
||||
dialog._save_preset("radial")
|
||||
assert presets.presets(prefs, "radial")["Mine"][
|
||||
"visualizer_radial_spokes"] == 120 # kept
|
||||
dialog._confirm_overwrite = lambda name: True
|
||||
dialog._save_preset("radial")
|
||||
assert presets.presets(prefs, "radial")["Mine"][
|
||||
"visualizer_radial_spokes"] == 200
|
||||
|
||||
def test_a_preset_only_holds_its_own_style(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "bars", "x")
|
||||
assert set(presets.presets(prefs, "bars")["x"]) == {"visualizer_bars_fade"}
|
||||
assert "visualizer_sync_delay_ms" not in presets.STYLE_KEYS["laser"]
|
||||
|
||||
def test_an_old_preset_takes_new_settings_defaults(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
prefs.set(presets.PRESETS_KEY,
|
||||
{"laser": {"Old": {"visualizer_laser_pitch": 20}}})
|
||||
values = presets.values_of(prefs, "laser", "Old")
|
||||
assert values["visualizer_laser_pitch"] == 20
|
||||
assert values["visualizer_laser_glow"] is True # default
|
||||
|
||||
def test_every_style_key_has_a_control(self, qapp, tmp_path):
|
||||
dialog = self._dialog(Preferences(tmp_path))
|
||||
for style, keys in presets.STYLE_KEYS.items():
|
||||
for key in keys:
|
||||
assert key in dialog._viz_setters or key in (
|
||||
"visualizer_laser_low_hz", "visualizer_laser_high_hz")
|
||||
|
||||
|
||||
class TestPresetEditor:
|
||||
def _editor(self, prefs, style="laser"):
|
||||
return presets.PresetEditor(prefs, style, "Laser")
|
||||
|
||||
def test_lists_only_user_presets(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
editor = self._editor(prefs)
|
||||
assert editor.list.count() == 0 and not editor.empty.isHidden()
|
||||
presets.save(prefs, "laser", "b")
|
||||
presets.save(prefs, "laser", "A")
|
||||
editor = self._editor(prefs)
|
||||
assert [editor.list.item(i).text() for i in range(2)] == ["A", "b"]
|
||||
|
||||
def test_rename_in_place(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "laser", "Old")
|
||||
editor = self._editor(prefs)
|
||||
editor.list.item(0).setText("New")
|
||||
assert list(presets.presets(prefs, "laser")) == ["New"]
|
||||
assert editor.list.item(0).text() == "New"
|
||||
|
||||
def test_rename_to_a_taken_name_is_refused(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "laser", "One")
|
||||
presets.save(prefs, "laser", "Two")
|
||||
editor = self._editor(prefs)
|
||||
warned = []
|
||||
editor._warn = warned.append
|
||||
editor.list.item(0).setText("Two")
|
||||
assert warned
|
||||
assert sorted(presets.presets(prefs, "laser")) == ["One", "Two"]
|
||||
assert editor.list.item(0).text() == "One"
|
||||
|
||||
def test_rename_to_default_is_refused(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "laser", "One")
|
||||
editor = self._editor(prefs)
|
||||
editor._warn = lambda text: None
|
||||
editor.list.item(0).setText("Default")
|
||||
assert list(presets.presets(prefs, "laser")) == ["One"]
|
||||
|
||||
def test_delete_asks_then_deletes(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "laser", "Gone")
|
||||
editor = self._editor(prefs)
|
||||
editor._confirm = lambda text: False
|
||||
editor._delete_current()
|
||||
assert "Gone" in presets.presets(prefs, "laser")
|
||||
editor._confirm = lambda text: True
|
||||
editor._delete_current()
|
||||
assert presets.presets(prefs, "laser") == {}
|
||||
assert not editor.delete_button.isEnabled()
|
||||
|
||||
def test_editing_one_style_leaves_the_others(self, qapp, tmp_path):
|
||||
prefs = Preferences(tmp_path)
|
||||
presets.save(prefs, "bars", "Keep")
|
||||
presets.save(prefs, "laser", "Drop")
|
||||
presets.delete(prefs, "laser", "Drop")
|
||||
assert "Keep" in presets.presets(prefs, "bars")
|
||||
|
||||
Reference in New Issue
Block a user