Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ mini_eq = [
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
filterwarnings = [
"ignore:GLib\\.unix_signal_add_full is deprecated; use GLibUnix\\.signal_add_full instead:gi.PyGIDeprecationWarning",
]

[tool.ruff]
line-length = 120
Expand Down
6 changes: 3 additions & 3 deletions src/mini_eq/band_fader.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ def update_accessible_state(self) -> None:
[
f"Band {self.index + 1} Gain",
description,
GAIN_MIN_DB,
GAIN_MAX_DB,
self.gain_db,
float(GAIN_MIN_DB),
float(GAIN_MAX_DB),
float(self.gain_db),
f"{self.gain_db:+.1f} dB",
],
)
Expand Down
13 changes: 0 additions & 13 deletions src/mini_eq/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -167,19 +167,6 @@ overlay-split-view.mini-eq-workspace > widget.sidebar-pane {
min-width: 190px;
}

.preset-library-popover {
min-width: 260px;
}

.preset-library-list {
min-width: 248px;
}

.preset-library-action {
padding-top: 5px;
padding-bottom: 5px;
}

.preset-menu-separator {
margin-top: 4px;
margin-bottom: 4px;
Expand Down
87 changes: 30 additions & 57 deletions src/mini_eq/window_presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
gi.require_version("Adw", "1")
gi.require_version("Gtk", "4.0")

from gi.repository import Adw, Gio, GLib, Gtk, Pango
from gi.repository import Adw, Gio, GLib, Gtk

from .core import (
DEFAULT_ACTIVE_BANDS,
Expand Down Expand Up @@ -37,6 +37,8 @@

APO_IMPORT_LABEL_PREFIX = "Imported APO: "
DELETED_PRESET_LABEL_PREFIX = "Unsaved copy: "
PRESET_PICKER_PLACEHOLDER = "Choose…"
PRESET_PICKER_EMPTY = "No saved presets"


def imported_apo_curve_label_for_name(name: str) -> str:
Expand Down Expand Up @@ -664,66 +666,28 @@ def refresh_preset_actions(self, state: PresetPanelUiState | None = None) -> Non
self.update_output_preset_state()
self.update_fallback_preset_state()

def refresh_preset_library_popover(self) -> None:
load_button = getattr(self, "preset_load_button", None)
if load_button is not None:
load_button.set_label("Choose…")
load_button.set_sensitive(bool(self.preset_names))
load_button.set_tooltip_text("Load a saved preset" if self.preset_names else "No saved presets")

box = getattr(self, "preset_library_box", None)
if box is None:
return

while child := box.get_first_child():
box.remove(child)

if not self.preset_names:
empty_label = Gtk.Label(label="No saved presets", xalign=0.0)
empty_label.add_css_class("dim-label")
empty_label.set_margin_top(8)
empty_label.set_margin_bottom(8)
empty_label.set_margin_start(10)
empty_label.set_margin_end(10)
box.append(empty_label)
def refresh_preset_picker(self) -> None:
combo = getattr(self, "preset_combo", None)
if combo is None:
return

for preset_name in self.preset_names:
button = Gtk.Button()
button.set_can_shrink(True)
button.set_hexpand(True)
button.add_css_class("popover-action")
button.add_css_class("preset-library-action")
button.add_css_class("flat")
button.set_tooltip_text(preset_name)

label = Gtk.Label(label=preset_name, xalign=0.0)
label.set_hexpand(True)
label.set_wrap(True)
label.set_wrap_mode(Pango.WrapMode.WORD_CHAR)
label.set_max_width_chars(42)
button.set_child(label)
button.connect("clicked", self.on_preset_library_button_clicked, preset_name)
box.append(button)

def on_preset_library_button_clicked(self, _button: Gtk.Button, preset_name: str) -> None:
popover = getattr(self, "preset_library_popover", None)
if popover is not None:
popover.popdown()

try:
self.load_library_preset(preset_name)
except Exception as exc:
self.set_status(str(exc))
has_presets = bool(self.preset_names)
combo.set_sensitive(has_presets)
combo.set_tooltip_text("Load a saved preset" if has_presets else "No saved presets")

def selected_preset_combo_index(self) -> int:
if (
self.current_preset_name is not None
and self.current_preset_name in self.preset_names
and self.controller.state_signature() == self.saved_preset_signature
):
return self.preset_names.index(self.current_preset_name)
return Gtk.INVALID_LIST_POSITION
return self.preset_names.index(self.current_preset_name) + 1
return 0

def preset_picker_labels(self) -> list[str]:
if not self.preset_names:
return [PRESET_PICKER_EMPTY]
return [PRESET_PICKER_PLACEHOLDER, *self.preset_names]

def sync_preset_combo_selection(self) -> None:
combo = getattr(self, "preset_combo", None)
Expand Down Expand Up @@ -818,9 +782,13 @@ def refresh_preset_list(self) -> None:
else:
self.sync_current_preset_signature_from_library()

self.preset_model.splice(0, self.preset_model.get_n_items(), self.preset_names)
self.sync_preset_combo_selection()
self.refresh_preset_library_popover()
self.updating_preset_combo = True
try:
self.preset_model.splice(0, self.preset_model.get_n_items(), self.preset_picker_labels())
self.preset_combo.set_selected(self.selected_preset_combo_index())
finally:
self.updating_preset_combo = False
self.refresh_preset_picker()

self.update_preset_state()

Expand Down Expand Up @@ -1192,11 +1160,16 @@ def on_preset_selected(self, combo: Gtk.DropDown, _param: object) -> None:
return

selected = combo.get_selected()
if selected == Gtk.INVALID_LIST_POSITION or selected >= len(self.preset_names):
if selected == Gtk.INVALID_LIST_POSITION or selected == 0:
self.sync_preset_combo_selection()
return

preset_index = selected - 1
if preset_index >= len(self.preset_names):
return

try:
self.load_library_preset(self.preset_names[selected])
self.load_library_preset(self.preset_names[preset_index])
except Exception as exc:
self.set_status(str(exc))

Expand Down
28 changes: 10 additions & 18 deletions src/mini_eq/window_utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .window_utils import (
bind_label_to_control,
make_ellipsizing_string_list_factory,
set_accessible_description,
set_accessible_label,
)
Expand Down Expand Up @@ -49,29 +50,20 @@ def make_preset_section(self) -> Gtk.Box:
self.current_curve_row.append(self.current_curve_state_label)
preset_section.append(self.current_curve_row)

self.preset_library_popover = Gtk.Popover()
self.preset_library_popover.add_css_class("preset-library-popover")
self.preset_library_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
self.preset_library_box.add_css_class("preset-library-list")
self.preset_library_box.set_margin_top(6)
self.preset_library_box.set_margin_bottom(6)
self.preset_library_box.set_margin_start(6)
self.preset_library_box.set_margin_end(6)
self.preset_library_popover.set_child(self.preset_library_box)

self.preset_load_button = Gtk.MenuButton(label="Choose…")
self.preset_load_button.set_can_shrink(True)
self.preset_load_button.set_hexpand(True)
self.preset_load_button.add_css_class("toolbar-button")
self.preset_load_button.set_popover(self.preset_library_popover)
set_accessible_label(self.preset_load_button, "Load Preset")
self.preset_combo.set_hexpand(True)
self.preset_combo.set_enable_search(True)
self.preset_combo.add_css_class("toolbar-select")
self.preset_combo.set_factory(make_ellipsizing_string_list_factory(28))
self.preset_combo.set_list_factory(make_ellipsizing_string_list_factory(42))
set_accessible_label(self.preset_combo, "Load Preset")
set_accessible_description(self.preset_combo, "Load a saved preset")

preset_row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
preset_row.add_css_class("utility-row")
preset_label = Gtk.Label(label="Load Preset", xalign=0.0)
bind_label_to_control(preset_label, self.preset_load_button)
bind_label_to_control(preset_label, self.preset_combo)
preset_row.append(preset_label)
preset_row.append(self.preset_load_button)
preset_row.append(self.preset_combo)
preset_section.append(preset_row)

self.output_scope_state_label.set_hexpand(True)
Expand Down
12 changes: 2 additions & 10 deletions tests/test_mini_eq_atspi_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,16 +439,8 @@ def verify_dropdown_exposes_options(frame, *, combo_name, required_options):
raise AssertionError("Monitor Off status is missing")
if find_accessible(frame, name="EQ output", role="combo box", showing=True) is None:
raise AssertionError("EQ output combo box is missing")
if (
find_accessible_with_roles(
frame,
name="Load Preset",
roles={"push button", "toggle button"},
showing=True,
)
is None
):
raise AssertionError("Load Preset menu button is missing")
if find_accessible(frame, name="Load Preset", role="combo box", showing=True) is None:
raise AssertionError("Load Preset combo box is missing")

verify_dropdown_exposes_options(frame, combo_name="Type", required_options=("Notch", "Bell"))

Expand Down
28 changes: 28 additions & 0 deletions tests/test_mini_eq_band_fader.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,34 @@ def test_band_fader_compact_labels_fit_small_tiles() -> None:
assert fader.show_q_in_tile(170.0) is True


def test_band_fader_accessible_range_values_are_doubles() -> None:
fader, _selected, _gains, _activated = make_fader(gain_db=-4)
fader.frequency_label = "32 Hz"
fader.filter_type_label = "Bell"
fader.q_label = "0.80"
fader.selected = False
fader.active = True
fader.muted = False
fader.soloed = False
fader.solo_active = False
captured = {}

def update_property(properties, values) -> None:
captured["properties"] = properties
captured["values"] = values

fader.update_property = update_property
fader.update_state = lambda _states, _values: None

fader.update_accessible_state()

values = captured["values"]
assert isinstance(values[2], float)
assert isinstance(values[3], float)
assert isinstance(values[4], float)
assert values[4] == -4.0


def test_band_fader_keyboard_steps_select_and_clamp_gain() -> None:
fader, selected, gains, activated = make_fader(gain_db=19.8)

Expand Down
Loading