10 Commits

Author SHA1 Message Date
6ae1a42d90 Reorder top GUI sections 2026-04-19 00:11:42 +03:00
63aee123e7 Fix top panel layout overlap 2026-04-19 00:09:03 +03:00
1999ce3657 Make GUI layout more horizontal 2026-04-18 23:58:01 +03:00
f78a130926 Add drive analysis and offset tools to GUI 2026-04-18 23:53:27 +03:00
5baadd9dbb Fix window icon and remove duplicate log 2026-04-18 23:39:20 +03:00
997bfef893 Reduce GUI vertical footprint 2026-04-18 23:33:42 +03:00
290d650f80 Respect system GTK theme 2026-04-18 23:27:22 +03:00
05838116d0 Make GUI more EAC-like 2026-04-18 23:22:04 +03:00
7aef00e66a Improve GUI action logging 2026-04-18 23:16:35 +03:00
9564c7cf20 Fix GUI 2026-04-18 18:13:05 +03:00
3 changed files with 732 additions and 132 deletions

View File

@@ -1,5 +1,5 @@
pkgname=whipper-git pkgname=whipper-git
pkgver=0.10.0.r66.g992923b pkgver=0.10.2.r0.g9564c7c
pkgrel=1 pkgrel=1
pkgdesc='CD-DA ripper prioritising accuracy over speed' pkgdesc='CD-DA ripper prioritising accuracy over speed'
arch=('x86_64') arch=('x86_64')

File diff suppressed because it is too large Load Diff

View File

@@ -169,10 +169,18 @@ class FakeSelection:
class FakeReleaseView: class FakeReleaseView:
def __init__(self, store): def __init__(self, store):
self.selection = FakeSelection(store) self.selection = FakeSelection(store)
self.cursor = None
self.scrolled_path = None
def get_selection(self): def get_selection(self):
return self.selection return self.selection
def set_cursor(self, path):
self.cursor = path
def scroll_to_cell(self, path, *_args):
self.scrolled_path = path
class FakeRunner: class FakeRunner:
def __init__(self): def __init__(self):
@@ -269,6 +277,8 @@ def _make_ui_app(tmp_path):
app.disc_template_entry = FakeEntry("%d") app.disc_template_entry = FakeEntry("%d")
app.read_button = FakeButton() app.read_button = FakeButton()
app.refresh_button = FakeButton() app.refresh_button = FakeButton()
app.analyze_button = FakeButton()
app.find_offset_button = FakeButton()
app.rip_button = FakeButton() app.rip_button = FakeButton()
app.stop_button = FakeButton() app.stop_button = FakeButton()
app.status_label = FakeLabel() app.status_label = FakeLabel()
@@ -277,10 +287,21 @@ def _make_ui_app(tmp_path):
app.track_bar = FakeProgressBar() app.track_bar = FakeProgressBar()
app.release_store = FakeListStore() app.release_store = FakeListStore()
app.track_store = FakeListStore() app.track_store = FakeListStore()
app.track_columns = {
"number": 0,
"status": 1,
"artist": 2,
"title": 3,
"length": 4,
"test_crc": 5,
"copy_crc": 6,
"accuraterip": 7,
}
app.release_view = FakeReleaseView(app.release_store) app.release_view = FakeReleaseView(app.release_store)
app.release_details = FakeLabel() app.release_details = FakeLabel()
app.info_labels = {key: FakeLabel() for key in [ app.info_labels = {key: FakeLabel() for key in [
"device", "disc_status", "cddb", "mbid", "duration", "tracks" "device", "vendor", "model", "release", "read_offset", "cache_defeat",
"disc_status", "cddb", "mbid", "duration", "tracks"
]} ]}
app.scan_runner = None app.scan_runner = None
app.rip_runner = None app.rip_runner = None
@@ -296,14 +317,17 @@ def _make_ui_app(tmp_path):
app._config_path = lambda: tmp_path / "gui.json" app._config_path = lambda: tmp_path / "gui.json"
_bind_methods( _bind_methods(
app, app,
"_log_action",
"_collect_gui_settings", "_collect_gui_settings",
"_save_gui_settings", "_save_gui_settings",
"_load_gui_settings", "_load_gui_settings",
"_can_rip", "_can_rip",
"_set_label", "_set_label",
"_set_running_state", "_set_running_state",
"_update_drive_info",
"_update_release_store", "_update_release_store",
"_update_track_store", "_update_track_store",
"_set_track_field",
"_update_release_details", "_update_release_details",
"_update_rip_task_progress", "_update_rip_task_progress",
"_finish_rip", "_finish_rip",
@@ -311,6 +335,8 @@ def _make_ui_app(tmp_path):
"_pulse_progress", "_pulse_progress",
"_resolve_release_metadata", "_resolve_release_metadata",
"_on_release_selected", "_on_release_selected",
"_on_analyze_drive_clicked",
"_on_find_offset_clicked",
"_on_stop_clicked", "_on_stop_clicked",
) )
app._remove_gui_log_handler = gui.WhipperGui._remove_gui_log_handler app._remove_gui_log_handler = gui.WhipperGui._remove_gui_log_handler
@@ -389,13 +415,72 @@ def test_release_selection_and_progress_updates(monkeypatch, tmp_path):
gui.WhipperGui._finish_rip(app, 0, "Done") gui.WhipperGui._finish_rip(app, 0, "Done")
assert app.current_release is metadata assert app.current_release is metadata
assert app.track_store[0][1] == "Track Artist" assert app.track_store[0][2] == "Track Artist"
assert app.track_store[0][1].startswith("Encoding")
assert app.release_details.get_text().startswith("Artist: Artist") assert app.release_details.get_text().startswith("Artist: Artist")
assert app.overall_bar.fraction == 1.0 assert app.overall_bar.fraction == 1.0
assert app.track_bar.fraction == 1.0 assert app.track_bar.fraction == 1.0
assert app.progress_label.get_text() == "Rip complete" assert app.progress_label.get_text() == "Rip complete"
def test_update_release_store_uses_visible_fallbacks(tmp_path):
app = _make_ui_app(tmp_path)
metadata = SimpleNamespace(
artist=None,
releaseTitle=None,
title=None,
release=None,
releaseType=None,
countries=[],
duration=1000,
tracks=[],
discNumber=1,
discTotal=1,
mbid="mbid-empty",
url="https://example.invalid/mbid-empty",
barcode=None,
catalogNumbers=[],
)
gui.WhipperGui._update_release_store(app, [metadata])
assert app.release_store[0][0] == "Unknown Artist"
assert app.release_store[0][1] == "Unknown Release"
assert app.release_store[0][3] == "Unknown"
assert app.release_store[0][4] == ""
assert app.release_view.cursor is not None
assert app.release_view.scrolled_path is not None
def test_update_drive_info_reads_config(monkeypatch, tmp_path):
app = _make_ui_app(tmp_path)
class FakeConfig:
def getReadOffset(self, vendor, model, release):
assert (vendor, model, release) == ("Vendor", "Model", "1.0")
return 667
def getDefeatsCache(self, vendor, model, release):
assert (vendor, model, release) == ("Vendor", "Model", "1.0")
return True
monkeypatch.setattr(gui.drive, "getDeviceInfo", lambda device: ("Vendor", "Model", "1.0"))
monkeypatch.setattr(gui.config, "Config", lambda: FakeConfig())
gui.WhipperGui._update_drive_info(app, "/dev/cdrom")
assert app.info_labels["device"].get_text() == "/dev/cdrom"
assert app.info_labels["vendor"].get_text() == "Vendor"
assert app.info_labels["model"].get_text() == "Model"
assert app.info_labels["release"].get_text() == "1.0"
assert app.info_labels["read_offset"].get_text() == "667"
assert app.info_labels["cache_defeat"].get_text() == "Yes"
def test_parse_offset_candidates_supports_ranges(tmp_path):
assert gui.WhipperGui._parse_offset_candidates("-1, 0, 2:4") == [-1, 0, 2, 3, 4]
def test_main_reports_missing_runtime(monkeypatch, capsys): def test_main_reports_missing_runtime(monkeypatch, capsys):
monkeypatch.setattr(gui, "_GUI_IMPORT_ERROR", ImportError("missing")) monkeypatch.setattr(gui, "_GUI_IMPORT_ERROR", ImportError("missing"))
monkeypatch.setattr(gui, "gi", None) monkeypatch.setattr(gui, "gi", None)