Files
pyPhotoSorter/photo_sorter/ui/main_window.py
Hirnwunde 87ab7ee73c first
2026-02-26 08:42:12 +01:00

249 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from pathlib import Path
from typing import Dict, Optional
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction, QKeySequence
from PyQt6.QtWidgets import (
QFileDialog,
QLabel,
QMainWindow,
QStatusBar,
QVBoxLayout,
QWidget,
QMessageBox,
)
from photo_sorter.core.config_manager import AppConfig, ConfigManager, TargetConfig
from photo_sorter.core.file_operations import FileOperations, FileOperationError
from photo_sorter.core.image_loader import ImageLoader
from photo_sorter.ui.image_viewer import ImageViewer
from photo_sorter.ui.settings_dialog import SettingsDialog, KEYS
from photo_sorter.ui.target_bar import TargetBar, TargetDisplay
class MainWindow(QMainWindow):
def __init__(self, config_manager: ConfigManager, parent: Optional[QWidget] = None) -> None: # type: ignore[name-defined]
super().__init__(parent)
self.setWindowTitle("Photo Sorter")
self.resize(1000, 700)
self._config_manager = config_manager
self._config: AppConfig = self._config_manager.load()
self._image_loader = ImageLoader()
self._file_ops = FileOperations()
self._targets_by_key: Dict[str, TargetConfig] = {t.key: t for t in self._config.targets}
self._setup_ui()
self._apply_config_to_ui()
# Beim Start direkt Quellverzeichnis wählen
self._select_source_directory(initial=True)
# --- UI Setup ---
def _setup_ui(self) -> None:
central = QWidget(self)
self.setCentralWidget(central)
layout = QVBoxLayout(central)
layout.setContentsMargins(8, 8, 8, 4)
layout.setSpacing(8)
self._info_label = QLabel("Kein Bild geladen", self)
self._info_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(self._info_label)
self._viewer = ImageViewer(self)
layout.addWidget(self._viewer, stretch=1)
self._target_bar = TargetBar(self)
layout.addWidget(self._target_bar)
status = QStatusBar(self)
self.setStatusBar(status)
self._create_menus()
def _create_menus(self) -> None:
menubar = self.menuBar()
file_menu = menubar.addMenu("Datei")
open_src_action = QAction("Quellverzeichnis öffnen…", self)
open_src_action.setShortcut(QKeySequence.StandardKey.Open)
open_src_action.triggered.connect(self._select_source_directory) # type: ignore[arg-type]
file_menu.addAction(open_src_action)
file_menu.addSeparator()
exit_action = QAction("Beenden", self)
exit_action.setShortcut(QKeySequence.StandardKey.Quit)
exit_action.triggered.connect(self.close) # type: ignore[arg-type]
file_menu.addAction(exit_action)
settings_menu = menubar.addMenu("Einstellungen")
targets_action = QAction("Ziele konfigurieren…", self)
targets_action.triggered.connect(self._open_settings) # type: ignore[arg-type]
settings_menu.addAction(targets_action)
# --- Konfiguration & Zielleiste ---
def _apply_config_to_ui(self) -> None:
self._targets_by_key = {t.key: t for t in self._config.targets}
displays = [TargetDisplay(key=t.key, label=t.label) for t in self._config.targets]
self._target_bar.set_targets(displays)
def _open_settings(self) -> None:
dialog = SettingsDialog(self._config, self)
if dialog.exec() == dialog.DialogCode.Accepted: # type: ignore[comparison-overlap]
new_config = dialog.get_config()
self._config = new_config
self._config_manager.save(new_config)
self._apply_config_to_ui()
# --- Quellverzeichnis & Bildanzeige ---
def _select_source_directory(self, initial: bool = False) -> None:
directory = QFileDialog.getExistingDirectory(self, "Quellverzeichnis wählen")
if not directory:
if initial:
# Wenn beim ersten Start nichts gewählt wird, bleibt die App einfach leer geöffnet.
return
return
self._image_loader.load_from_directory(Path(directory))
if self._image_loader.count == 0:
QMessageBox.information(
self,
"Keine Bilder",
"Im gewählten Verzeichnis wurden keine unterstützten Bilddateien gefunden.",
)
self._viewer.clear()
self._info_label.setText("Keine Bilder gefunden")
return
self._show_current_image()
def _show_current_image(self) -> None:
path = self._image_loader.get_current()
count = self._image_loader.count
index = self._image_loader.current_index
if path is None:
self._viewer.clear()
self._info_label.setText("Kein Bild geladen")
return
self._viewer.set_image(path)
self._info_label.setText(f"Bild {index + 1} / {count} {path.name}")
# --- Tastatursteuerung ---
def keyPressEvent(self, event) -> None: # type: ignore[override]
key = event.key()
if key in (Qt.Key.Key_Right, Qt.Key.Key_Space):
self._skip_forward()
return
if key == Qt.Key.Key_Left:
self._go_back()
return
if key in (Qt.Key.Key_Z,) and (event.modifiers() & Qt.KeyboardModifier.ControlModifier):
self._undo_action()
return
if key == Qt.Key.Key_Z:
self._undo_action()
return
# Zieltasten 10
key_char = None
if key == Qt.Key.Key_1:
key_char = "1"
elif key == Qt.Key.Key_2:
key_char = "2"
elif key == Qt.Key.Key_3:
key_char = "3"
elif key == Qt.Key.Key_4:
key_char = "4"
elif key == Qt.Key.Key_5:
key_char = "5"
elif key == Qt.Key.Key_6:
key_char = "6"
elif key == Qt.Key.Key_7:
key_char = "7"
elif key == Qt.Key.Key_8:
key_char = "8"
elif key == Qt.Key.Key_9:
key_char = "9"
elif key == Qt.Key.Key_0:
key_char = "0"
if key_char is not None:
self._apply_target_action(key_char)
return
super().keyPressEvent(event)
def _skip_forward(self) -> None:
if self._image_loader.count == 0:
return
self._image_loader.next()
self._show_current_image()
def _go_back(self) -> None:
if self._image_loader.count == 0:
return
self._image_loader.previous()
self._show_current_image()
def _apply_target_action(self, key: str) -> None:
if self._image_loader.count == 0:
return
target = self._targets_by_key.get(key)
if not target:
return
current = self._image_loader.get_current()
if current is None:
return
try:
new_path = self._file_ops.move_or_copy(
current,
Path(target.path),
self._config.default_action,
self._image_loader.current_index,
)
except FileOperationError as e:
QMessageBox.warning(self, "Fehler beim Verschieben/Kopieren", str(e))
return
# Pfad in der Bildliste aktualisieren (für Zurückblättern etc.)
self._image_loader.update_current_path(new_path)
# Visuelles Highlight des Ziels
self._target_bar.highlight_target(key)
# Nächstes Bild laden
self._image_loader.next()
self._show_current_image()
def _undo_action(self) -> None:
from photo_sorter.core.file_operations import FileAction
action = self._file_ops.undo_last()
if action is None:
return
# Bildliste wieder auf den Index der Aktion setzen
self._image_loader.set_index(action.index)
# Pfad in der Liste anpassen (Move: wieder Originalpfad, Copy: Original bleibt, Liste ändert sich nicht)
if action.action == "move":
self._image_loader.update_current_path(action.original_path)
self._show_current_image()