diff --git a/app/gui.py b/app/gui.py index 8b8e095..9f6bc64 100644 --- a/app/gui.py +++ b/app/gui.py @@ -4308,10 +4308,6 @@ class MainWindow(QMainWindow): self.search_missing_btn.setEnabled(False) self.search_missing_btn.setToolTip("Scrape metadata and covers for games that are missing one") bar.addWidget(self.search_missing_btn) - self.scan_shortcut_btn = QPushButton("Scan…") - self.scan_shortcut_btn.setToolTip("Scan your folders for new discs and apply renames") - self.scan_shortcut_btn.clicked.connect(self._open_scan) - bar.addWidget(self.scan_shortcut_btn) layout.addLayout(bar) splitter = QSplitter(Qt.Horizontal) @@ -4347,10 +4343,6 @@ class MainWindow(QMainWindow): self.details_panel.play_requested.connect(self._play_current_on_ppsspp) return page - def _open_scan(self): - self.library_subtabs.setCurrentWidget(self.scan_view) - self._start_scan() - def _on_library_subtab_changed(self, index): self.details_panel.setVisible(self.library_subtabs.widget(index) is not self.scan_view) @@ -4411,6 +4403,8 @@ class MainWindow(QMainWindow): header.setContextMenuPolicy(Qt.CustomContextMenu) header.customContextMenuRequested.connect(self._show_column_menu) self.table.itemChanged.connect(self._on_item_changed) + self.table.setContextMenuPolicy(Qt.CustomContextMenu) + self.table.customContextMenuRequested.connect(self._on_scan_table_menu) layout.addWidget(self.table) return self.scan_view @@ -5283,6 +5277,79 @@ class MainWindow(QMainWindow): break self.apply_btn.setEnabled(enabled) + def _on_scan_table_menu(self, pos): + row = self.table.rowAt(pos.y()) + if row < 0 or row >= len(self._rows): + return + rec = self._rows[row] + method = rec.get("method") or "" + if not rec.get("serial") or method not in ("choice", "unknown"): + return + menu = QMenu(self) + menu.addAction("Redo match choice…", lambda: self._redo_choice(row)) + menu.exec(self.table.viewport().mapToGlobal(pos)) + + def _redo_choice(self, row): + rec = self._rows[row] + serial = (rec.get("serial") or "").strip().upper() + if not serial: + return + conn = get_connection(config.DB_PATH) + try: + # Drop the remembered choice so the disc can be re-identified. + conn.execute("DELETE FROM choices WHERE serial = ? AND size = ?", + (serial, rec.get("size"))) + candidates = matcher.serial_candidates(conn, rec) + chosen = None + if len(candidates) == 1: + chosen = candidates[0] + elif len(candidates) > 1: + chosen = self._ask_user(rec, candidates) + if chosen is not None: + conn.execute( + "INSERT OR REPLACE INTO choices (serial, size, release_id) VALUES (?,?,?)", + (serial, rec.get("size"), chosen["id"]), + ) + ident = pipeline.identify_with_release(rec, chosen, "choice") + else: + ident = {"method": "unknown", "release": None, + "serial": rec.get("serial"), "canonical": None} + rel = ident.get("release") or {} + conn.execute("UPDATE files SET release_id = ? WHERE path = ?", + (rel.get("id"), rec["path"])) + conn.commit() + finally: + conn.close() + self._apply_row_ident(row, rec, ident) + self.load_library() + + def _apply_row_ident(self, row, rec, ident): + rec["method"] = ident["method"] + rec["release"] = ident.get("release") + rec["serial"] = ident.get("serial") + rec["canonical"] = ident.get("canonical") + rec["_type"] = self._record_type(rec) + item = self.table.item(row, 0) + if item is not None: + item.setText(self._type_label(rec["_type"])) + item = self.table.item(row, 3) + if item is not None: + item.setText(ident["method"]) + item = self.table.item(row, 4) + if item is not None: + item.setText(ident.get("serial") or "") + canonical = ident.get("canonical") or "" + item = self.table.item(row, 5) + if item is not None: + item.setText(canonical) + cb = self.table.cellWidget(row, 6) + if isinstance(cb, QCheckBox): + do_rename = ident["method"] != "unknown" and canonical and Path(rec["path"]).name != canonical + cb.blockSignals(True) + cb.setChecked(do_rename) + cb.blockSignals(False) + self._update_apply_enabled() + def _record_type(self, rec): release = rec.get("release") if release and release.get("system"):