diff --git a/bagheeraview.py b/bagheeraview.py index 8a5c201..69837bf 100755 --- a/bagheeraview.py +++ b/bagheeraview.py @@ -1847,8 +1847,8 @@ class MainWindow(QMainWindow): self, UITexts.DUPLICATE_DETECTION_TITLE, UITexts.DUPLICATE_NONE_FOUND) return - # Por defecto usamos el modo optimizado (incremental) para no repetir - # comparaciones + # By default, we use optimized (incremental) mode to avoid repeating + # comparisons. self.start_duplicate_detection(force_full=False, custom_paths=paths) def _gather_files_for_duplicates(self): @@ -3205,8 +3205,8 @@ class MainWindow(QMainWindow): current_item = self.thumbnail_model.item(model_idx) if self._match_item(target, current_item): - # Si es una cabecera, actualizamos el texto por si cambió el - # contador + # If it is a header, update the text in case the counter + # changed. if isinstance(target, tuple) and target[0] == 'HEADER': _, (_, header_text, _) = target if current_item.data(DIR_ROLE) != header_text: diff --git a/changelog.txt b/changelog.txt index 09f153b..ee96e3b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -67,6 +67,9 @@ Ahora que la carga es rápida, ¿cómo puedo implementar una precarga inteligent ¿Cómo puedo hacer que la selección de archivos sea persistente incluso después de recargar o filtrar la vista? +v0.9.18 - +· Better messages + v0.9.17 - · Fixes diff --git a/constants.py b/constants.py index 95194cf..c2d259e 100644 --- a/constants.py +++ b/constants.py @@ -29,7 +29,7 @@ if FORCE_X11: # --- CONFIGURATION --- PROG_NAME = "Bagheera Image Viewer" PROG_ID = "bagheeraview" -PROG_VERSION = "0.9.18-dev" +PROG_VERSION = "0.9.18" PROG_AUTHOR = "Ignacio Serantes" # --- CACHE SETTINGS --- diff --git a/duplicatecache.py b/duplicatecache.py index 4e56dd8..feb4446 100644 --- a/duplicatecache.py +++ b/duplicatecache.py @@ -712,7 +712,7 @@ class DuplicateDetector(QThread): progress = int((processed_initial / total_files) * total_files) self.progress_update.emit( progress, total_files * 2, - UITexts.DUPLICATE_MSG_HASHING.format(filename=os.path.basename(path))) + UITexts.DUPLICATE_MSG_HASHING.format(filename="...")) last_update_time = time.perf_counter() except OSError: @@ -771,9 +771,11 @@ class DuplicateDetector(QThread): break # Sub-phase: Indexing hashes into the BK-Tree for comparison - if time.perf_counter() - last_update_time > 0.05 or i == 0 or i == total_items - 1: + if time.perf_counter() - last_update_time > 0.05 \ + or i == 0 or i == total_items - 1: # Scale Indexing to 50% - 75% range of the total bar - indexing_progress = int((i / total_items) * (total_files / 2)) if total_items > 0 else 0 + indexing_progress = int((i / total_items) * (total_files / 2)) \ + if total_items > 0 else 0 self.progress_update.emit( total_files + indexing_progress, total_files * 2, UITexts.DUPLICATE_MSG_ANALYZING.format(filename="...")) @@ -814,7 +816,8 @@ class DuplicateDetector(QThread): items1 = hash_map[h1] # Update progress more frequently during analysis phase - if time.perf_counter() - last_update_time > 0.05 or i == 0 or i == total_queries - 1: + if time.perf_counter() - last_update_time > 0.05 \ + or i == 0 or i == total_queries - 1: # Scale Comparison to 75% - 100% range comparison_progress = int(((i + 1) / total_queries) * (total_files / 2)) \ if total_queries > 0 else (total_files / 2) @@ -853,10 +856,10 @@ class DuplicateDetector(QThread): # Frequent UI heartbeat for large duplicate groups if time.perf_counter() - last_update_time > 0.05: - phase2_progress = int(((i + 1) / total_queries) * total_files) + comparison_progress = int(((i + 1) / total_queries) * (total_files / 2)) self.progress_update.emit( - total_files + phase2_progress, total_files * 2, - UITexts.DUPLICATE_MSG_ANALYZING.format(filename=os.path.basename(p1))) + int(total_files * 1.5 + comparison_progress), total_files * 2, + UITexts.DUPLICATE_MSG_ANALYZING.format(filename="...")) last_update_time = time.perf_counter() # Collect for batch update to improve performance diff --git a/duplicatedialog.py b/duplicatedialog.py index fddefc7..a9398bd 100644 --- a/duplicatedialog.py +++ b/duplicatedialog.py @@ -23,10 +23,10 @@ class DuplicateManagerDialog(QDialog): self.main_win = main_win self.review_mode = review_mode - self.active_pane = None + self.active_pane = None # Track the focused pane self.current_dup_pair = None # Stores the current DuplicateResult object self.panes_linked = True # Default to linked - self._user_link_preference = True # Persiste la intención del usuario + self._user_link_preference = True # Persists user intent self._is_syncing = False # Guard to prevent recursion during synchronization self.setWindowTitle(UITexts.DUPLICATE_MANAGER_TITLE) @@ -194,7 +194,7 @@ class DuplicateManagerDialog(QDialog): def wheelEvent(self, event): """Handles mouse wheel events for zooming (with Ctrl).""" if event.modifiers() & Qt.ControlModifier and self.active_pane: - # Calcular el punto de enfoque relativo al pane activo + # Calculate the focus point relative to the active pane. focus_pos = self.active_pane.mapFromGlobal(event.globalPosition().toPoint()) if event.angleDelta().y() > 0: self.active_pane.zoom_manager.zoom(1.1, focus_point=focus_pos) @@ -364,8 +364,7 @@ class DuplicateManagerDialog(QDialog): else: col_offset = 0 - # Columna similarity (usamos DisplayRole con int para que ordene - # numéricamente) + # Similarity column (using DisplayRole with int for numerical sorting). sim_item = QTableWidgetItem() sim_item.setData(Qt.DisplayRole, dup.similarity if dup.similarity is not None else 0) @@ -373,7 +372,7 @@ class DuplicateManagerDialog(QDialog): if not self.review_mode: sim_item.setData(Qt.UserRole, i) - # Columna 1: Nombres de ficheros + # Column 1: File names names_item = QTableWidgetItem(f"{name1} ↔ {name2}") self.table_widget.setItem(row, col_offset, sim_item) @@ -392,8 +391,8 @@ class DuplicateManagerDialog(QDialog): if row < 0 or row >= self.table_widget.rowCount(): return - # Obtenemos el índice real de la lista duplicates guardado en el UserRole del - # item + # Get the real index of the duplicates list stored in the UserRole of + # the item. item = self.table_widget.item(row, 0) if not item: return @@ -407,7 +406,7 @@ class DuplicateManagerDialog(QDialog): if dup.similarity == 100: similarity_color = "#2ecc71" # Green elif dup.similarity < 80: - similarity_color = "#e74c3c" # Red + similarity_color = "#e74c3c" # Red self.similarity_lbl.setText(f"{dup.similarity}% Similarity") self.similarity_lbl.setStyleSheet( @@ -524,11 +523,11 @@ class DuplicateManagerDialog(QDialog): dir_lbl.setText("N/A") return True - # Metadatos + # Metadata size_bytes = os.path.getsize(path) size_str = self._format_size(size_bytes) - # Detección de imágenes animadas o resoluciones inválidas + # Detection of animated images or invalid resolutions reader = QImageReader(path) is_animated = reader.supportsAnimation() and reader.imageCount() > 1 is_invalid = (pane.controller.pixmap_original.isNull() or @@ -755,7 +754,7 @@ class DuplicateManagerDialog(QDialog): if d.path1 == old_path or d.path2 == old_path: p1 = new_path if d.path1 == old_path else d.path1 p2 = new_path if d.path2 == old_path else d.path2 - # Actualizamos la tupla con nombre usando _replace + # Update the named tuple using _replace self.duplicates[i] = d._replace(path1=p1, path2=p2) updated = True @@ -781,10 +780,10 @@ class DuplicateManagerDialog(QDialog): if self.review_mode and self.current_dup_pair: self.cache.mark_as_exception( self.current_dup_pair.path1, self.current_dup_pair.path2, False) - # Borramos los hashes para que el detector las trate como imágenes nuevas - # y fuerce una nueva comparación en el siguiente escaneo. - # Usamos clear_relationships=False para no perder otras posibles - # coincidencias ya marcadas. + # Clear hashes so the detector treats them as new images and + # forces a new comparison in the next scan. We use + # clear_relationships=False to preserve other possible matches + # already identified. self.cache.remove_hash_for_path( self.current_dup_pair.path1, clear_relationships=False) self.cache.remove_hash_for_path( diff --git a/settings.py b/settings.py index cc693ee..e097687 100644 --- a/settings.py +++ b/settings.py @@ -1195,7 +1195,7 @@ class SettingsDialog(QDialog): elif self.download_model_btn: self.download_model_btn.hide() - # --- Mascotas (Pets) --- + # --- Pets --- if not AVAILABLE_PET_ENGINES: self.pet_engine_combo.setEnabled(False) self.pet_tags_edit.setEnabled(False) @@ -1341,14 +1341,14 @@ class SettingsDialog(QDialog): self.downloader_thread = None def done(self, r): - self._stop_downloader_thread() # Asegura que el hilo de descarga se detenga y espere + self._stop_downloader_thread() # Ensure downloader thread stops and waits. if self.counter_thread and self.counter_thread.isRunning(): self.counter_thread.stop() self.counter_thread.wait() super().done(r) def closeEvent(self, event): - self._stop_downloader_thread() # Asegura que el hilo de descarga se detenga y espere + self._stop_downloader_thread() # Ensure downloader thread stops and waits. if self.counter_thread and self.counter_thread.isRunning(): self.counter_thread.stop() self.counter_thread.wait() @@ -1366,15 +1366,15 @@ class SettingsDialog(QDialog): if existing_p == path: return - # Si una carpeta padre ya existe, no añadimos esta subcarpeta + # If a parent folder already exists, do not add this subfolder. if path.startswith(existing_p + os.sep): return - # Si la nueva ruta es padre de una existente, marcamos la existente para borrar + # If the new path is a parent of an existing one, mark it for removal. if existing_p.startswith(path + os.sep): to_remove.append(i) - # Borramos las subcarpetas innecesarias (en orden inverso para no alterar los índices) + # Remove unnecessary subfolders (reverse order to not alter indices). for i in sorted(to_remove, reverse=True): list_widget.takeItem(i) diff --git a/widgets.py b/widgets.py index 05d5a78..f3f18e3 100644 --- a/widgets.py +++ b/widgets.py @@ -1341,8 +1341,8 @@ class FaceNameInputWidget(QWidget): super().__init__(parent) self.main_win = main_win self.region_type = region_type - # Usamos deque para gestionar el historial de forma eficiente con un máximo - # configurable de elementos. + # Use deque to manage history efficiently with a configurable maximum + # number of items. max_items = APP_CONFIG.get("faces_menu_max_items", FACES_MENU_MAX_ITEMS_DEFAULT) if self.region_type == "Pet": @@ -1372,7 +1372,7 @@ class FaceNameInputWidget(QWidget): self.name_combo.setToolTip(UITexts.FACE_NAME_TOOLTIP) self.name_combo.lineEdit().setClearButtonEnabled(True) - # 2. Completer para la funcionalidad de autocompletado. + # 2. Completer for autocomplete functionality. self.completer = QCompleter(self) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setFilterMode(Qt.MatchContains)