import glob import os import time import pymupdf # PyMuPDF from PySide6.QtCore import Qt from PySide6.QtGui import QCursor, QPixmap, QImage, QPainter, QAction from PySide6.QtWidgets import QLabel, QMainWindow, QApplication, QStyleFactory from ui.MainWinddow_ui import Ui_MainWindow class MainWindow(QMainWindow): def __init__(self, parent=None): """ Konstruktor für die MainWindow-Klasse. Args: parent: Übergeordnetes Widget, falls vorhanden """ super().__init__(parent) # UI einrichten self.ui = Ui_MainWindow() self.ui.setupUi(self) # Dict zum Speichern der Beziehung zwischen Thumbnails und Seitennummern self.thumbnail_to_page = {} # PDF-Dokumente für späteres On-Demand-Rendering speichern self.pdf_documents = {} # {pdf_filename: {'diff': doc, 'ref': doc, 'new': doc}} # Aktueller Zoom-Faktor self.current_zoom = 100 # 100% # Aktuell angezeigte Seite self.current_page = 0 self.current_pdf = None # Label für die Vollansicht (nur ein einziges Label) self.fullsize_label = None # Variablen für Drag-to-Scroll (Anti-Jitter für 4K/DPI-Skalierung) self.is_dragging = False self.last_drag_position = None self.drag_threshold = 3 # Mindestbewegung in Pixeln vor dem Scrollen self.scroll_sensitivity = 0.7 # Reduzierte Empfindlichkeit für sanfteres Scrollen # Theme-Menü initialisieren self._setup_theme_menu() # Bilder laden self._load_images() # Signale und Slots verbinden self._connect_signals() def _setup_theme_menu(self): """Initialisiert das Theme-Menü mit verfügbaren Themes.""" # Hole alle verfügbaren Themes available_themes = QStyleFactory.keys() current_theme = QApplication.style().objectName() print(f"Verfügbare Themes: {available_themes}") print(f"Aktuelles Theme: {current_theme}") # Füge Theme-Aktionen zum Menü hinzu for theme_name in available_themes: action = QAction(theme_name, self) action.setCheckable(True) # Markiere das aktuelle Theme if theme_name.lower() == current_theme.lower(): action.setChecked(True) # Verbinde die Aktion mit der Theme-Wechsel-Funktion action.triggered.connect(lambda checked, theme=theme_name: self.change_theme(theme)) # Füge die Aktion zum Theme-Menü hinzu self.ui.menuThema.addAction(action) def change_theme(self, theme_name): """ Wechselt das Theme der Anwendung. Args: theme_name: Name des zu verwendenden Themes """ print(f"Wechsle zu Theme: {theme_name}") try: # Erstelle den neuen Style style = QStyleFactory.create(theme_name) if style: # Wende den neuen Style auf die Anwendung an QApplication.setStyle(style) # Aktualisiere die Checkmarks im Menü for action in self.ui.menuThema.actions(): action.setChecked(action.text() == theme_name) print(f"Theme erfolgreich gewechselt zu: {theme_name}") else: print(f"Fehler: Theme '{theme_name}' konnte nicht erstellt werden") except Exception as e: print(f"Fehler beim Wechseln des Themes: {e}") def _load_images(self): """Lädt PDF-Thumbnails und bereitet On-Demand-Rendering vor.""" # Entferne bestehende Widgets aus den Layouts self._clear_layout(self.ui.verticalLayout_2) self._clear_layout(self.ui.verticalLayout_3) # Dicts zurücksetzen self.thumbnail_to_page = {} self.pdf_documents = {} # Basis-Pfad zu den PDF-Ordnern base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) pdf_base_dir = os.path.join(base_dir, "src", "ui", "res", "pdf") diff_dir = os.path.join(pdf_base_dir, "diff") ref_dir = os.path.join(pdf_base_dir, "ref") new_dir = os.path.join(pdf_base_dir, "new") # Prüfe ob diff-Ordner existiert und PDF-Dateien enthält if not os.path.exists(diff_dir): print(f"Diff-Ordner nicht gefunden: {diff_dir}") return # Finde alle PDF-Dateien im diff-Ordner diff_pdfs = glob.glob(os.path.join(diff_dir, "*.pdf")) if not diff_pdfs: print("Keine PDF-Dateien im diff-Ordner gefunden") return print(f"Gefundene PDF-Dateien im diff-Ordner: {diff_pdfs}") # Für jede PDF-Datei im diff-Ordner for diff_pdf_path in diff_pdfs: pdf_filename = os.path.basename(diff_pdf_path) ref_pdf_path = os.path.join(ref_dir, pdf_filename) new_pdf_path = os.path.join(new_dir, pdf_filename) # Prüfe ob gleichnamige PDFs in ref und new existieren if not os.path.exists(ref_pdf_path): print(f"Referenz-PDF nicht gefunden: {ref_pdf_path}") continue if not os.path.exists(new_pdf_path): print(f"New-PDF nicht gefunden: {new_pdf_path}") continue try: # Alle drei PDF-Dateien öffnen und speichern diff_doc = pymupdf.open(diff_pdf_path) ref_doc = pymupdf.open(ref_pdf_path) new_doc = pymupdf.open(new_pdf_path) # PDF-Dokumente für später speichern self.pdf_documents[pdf_filename] = { 'diff': diff_doc, 'ref': ref_doc, 'new': new_doc } print(f"PDFs geladen: {pdf_filename}") print(f" diff: {len(diff_doc)} Seiten") print(f" ref: {len(ref_doc)} Seiten") print(f" new: {len(new_doc)} Seiten") # Nehme die minimale Seitenzahl aller drei PDFs max_pages = min(len(diff_doc), len(ref_doc), len(new_doc)) # Erstelle nur Thumbnails (keine Vollbilder) for page_num in range(max_pages): # Nur diff-Seite für Thumbnail rendern diff_page = diff_doc[page_num] matrix = pymupdf.Matrix(1.0, 1.0) # Normale Auflösung für Thumbnails diff_pix = diff_page.get_pixmap(matrix=matrix) diff_img_data = diff_pix.tobytes("png") diff_qimg = QImage.fromData(diff_img_data) diff_pixmap = QPixmap.fromImage(diff_qimg) # Thumbnail erstellen und zur linken Spalte hinzufügen thumbnail = QLabel() thumbnail.setObjectName(f"thumbnail_{pdf_filename}_page_{page_num + 1}") thumbnail.setPixmap(diff_pixmap.scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)) thumbnail.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) thumbnail.setMouseTracking(True) self.ui.verticalLayout_2.addWidget(thumbnail) # Seitennummer für Thumbnail anzeigen thumbnail_info = QLabel(f"Seite {page_num + 1}") thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter) self.ui.verticalLayout_2.addWidget(thumbnail_info) # Beziehung zwischen Thumbnail und Seitennummer speichern self.thumbnail_to_page[thumbnail] = { 'pdf_filename': pdf_filename, 'page_num': page_num } # Click-Event für das Thumbnail einrichten thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t) print(f"Thumbnail für Seite {page_num + 1} erstellt") # Setze die erste PDF als aktuelle PDF if self.current_pdf is None: self.current_pdf = pdf_filename except Exception as e: print(f"Fehler beim Laden der PDFs: {e}") # Erstelle das eine Vollbild-Label für die rechte Spalte self.fullsize_label = QLabel() self.fullsize_label.setObjectName("fullsize_current_page") self.fullsize_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.ui.verticalLayout_3.addWidget(self.fullsize_label) # Drag-to-Scroll Events für das große Bild einrichten self.fullsize_label.mousePressEvent = lambda event: self.on_fullsize_mouse_press(event, self.fullsize_label) self.fullsize_label.mouseMoveEvent = lambda event: self.on_fullsize_mouse_move(event, self.fullsize_label) self.fullsize_label.mouseReleaseEvent = lambda event: self.on_fullsize_mouse_release(event, self.fullsize_label) # Zeige die erste Seite initial an if self.current_pdf: self.render_and_display_page(self.current_pdf, 0) def render_and_display_page(self, pdf_filename, page_num): """ Rendert und zeigt eine spezifische Seite in der Vollansicht an. Args: pdf_filename: Name der PDF-Datei page_num: Seitennummer (0-basiert) """ print(f"Rendere Seite {page_num + 1} von {pdf_filename}") if pdf_filename not in self.pdf_documents: print(f"PDF-Dokument {pdf_filename} nicht gefunden") return try: docs = self.pdf_documents[pdf_filename] # Seiten aus allen drei PDFs laden diff_page = docs['diff'][page_num] ref_page = docs['ref'][page_num] new_page = docs['new'][page_num] # Seiten in hoher Auflösung rendern matrix = pymupdf.Matrix(2.0, 2.0) # 2x Vergrößerung für bessere Qualität # Alle drei Ebenen rendern diff_pix = diff_page.get_pixmap(matrix=matrix) diff_img_data = diff_pix.tobytes("png") diff_qimg = QImage.fromData(diff_img_data) diff_pixmap = QPixmap.fromImage(diff_qimg) ref_pix = ref_page.get_pixmap(matrix=matrix) ref_img_data = ref_pix.tobytes("png") ref_qimg = QImage.fromData(ref_img_data) ref_pixmap = QPixmap.fromImage(ref_qimg) new_pix = new_page.get_pixmap(matrix=matrix) new_img_data = new_pix.tobytes("png") new_qimg = QImage.fromData(new_img_data) new_pixmap = QPixmap.fromImage(new_qimg) # Erstelle das überlagerte Bild alpha_value = self.ui.alpha.value() layered_pixmap = self.create_layered_pixmap(ref_pixmap, diff_pixmap, new_pixmap, alpha_value) # Wende aktuellen Zoom an zoom_factor = self.current_zoom / 100.0 if zoom_factor != 1.0: new_width = int(layered_pixmap.width() * zoom_factor) layered_pixmap = layered_pixmap.scaledToWidth(new_width, Qt.TransformationMode.SmoothTransformation) # Setze das überlagerte Bild self.fullsize_label.setPixmap(layered_pixmap) self.fullsize_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) # Aktualisiere aktuelle Seite self.current_page = page_num self.current_pdf = pdf_filename print(f"Seite {page_num + 1} erfolgreich angezeigt") except Exception as e: print(f"Fehler beim Rendern der Seite {page_num + 1}: {e}") def _clear_layout(self, layout): """Entfernt alle Widgets aus einem Layout.""" if layout is not None: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.deleteLater() def _connect_signals(self): """Verbindet Signale mit den entsprechenden Slots.""" # Button-Klicks verbinden self.ui.pushButton.clicked.connect(self.on_button_clicked) # Zoom-Slider verbinden self.ui.zoom.valueChanged.connect(self.apply_zoom) # Alpha-Slider verbinden self.ui.alpha.valueChanged.connect(self.on_alpha_changed) def on_alpha_changed(self, alpha_value): """ Wird ausgeführt, wenn der Alpha-Slider geändert wird. Args: alpha_value: Der neue Alpha-Wert (-100 bis 100) """ print(f"Alpha geändert auf {alpha_value}") # Nur die aktuell angezeigte Seite neu rendern start = time.time() if self.current_pdf: self.render_and_display_page(self.current_pdf, self.current_page) dauer = time.time() - start print(f"Dauer: {dauer}") def create_layered_pixmap(self, ref_pixmap, diff_pixmap, new_pixmap, alpha_value): """ Erstellt ein übergelagertes Pixmap basierend auf dem Alpha-Wert. Args: ref_pixmap: Unterste Ebene (ref) diff_pixmap: Mittlere Ebene (diff) new_pixmap: Oberste Ebene (new) alpha_value: Alpha-Wert (-100 bis 100) Returns: QPixmap: Das überlagerte Bild """ # Verwende die Größe des größten Bildes max_width = max(ref_pixmap.width(), diff_pixmap.width(), new_pixmap.width()) max_height = max(ref_pixmap.height(), diff_pixmap.height(), new_pixmap.height()) # Erstelle ein leeres Pixmap für das Ergebnis result = QPixmap(max_width, max_height) result.fill(Qt.GlobalColor.white) painter = QPainter(result) painter.setRenderHint(QPainter.RenderHint.Antialiasing) if alpha_value <= 0: # Alpha von -100 bis 0: Übergang von ref zu diff ref_opacity = 1.0 - (alpha_value + 100) / 100.0 diff_opacity = (alpha_value + 100) / 100.0 new_opacity = 0.0 else: # Alpha von 0 bis 100: Übergang von diff zu new ref_opacity = 0.0 diff_opacity = 1.0 - alpha_value / 100.0 new_opacity = alpha_value / 100.0 # Zeichne die Ebenen mit entsprechender Transparenz if ref_opacity > 0: painter.setOpacity(ref_opacity) painter.drawPixmap(0, 0, ref_pixmap) if diff_opacity > 0: painter.setOpacity(diff_opacity) painter.drawPixmap(0, 0, diff_pixmap) if new_opacity > 0: painter.setOpacity(new_opacity) painter.drawPixmap(0, 0, new_pixmap) painter.end() return result def on_button_clicked(self): """Wird ausgeführt, wenn der Button geklickt wird.""" print("Button wurde geklickt!") def on_thumbnail_clicked(self, event, thumbnail): """ Wird ausgeführt, wenn ein Thumbnail angeklickt wird. Args: event: Das Maus-Event thumbnail: Das geklickte Thumbnail-Label """ page_info = self.thumbnail_to_page.get(thumbnail) if page_info: pdf_filename = page_info['pdf_filename'] page_num = page_info['page_num'] print(f"Thumbnail für Seite {page_num + 1} von {pdf_filename} wurde angeklickt") # Rendere und zeige die gewählte Seite an self.render_and_display_page(pdf_filename, page_num) def apply_zoom(self, zoom_value): """ Wendet den Zoom-Faktor auf das aktuelle Bild an. Args: zoom_value: Der neue Zoom-Wert (in Prozent) """ self.current_zoom = zoom_value print(f"Zoom geändert auf {zoom_value}%") # Nur die aktuell angezeigte Seite neu rendern if self.current_pdf: self.render_and_display_page(self.current_pdf, self.current_page) def on_fullsize_mouse_press(self, event, fullsize_label): """ Wird ausgeführt, wenn die Maustaste auf einem großen Bild gedrückt wird. Args: event: Das Maus-Event fullsize_label: Das große Bild-Label """ if event.button() == Qt.MouseButton.LeftButton: self.is_dragging = True self.last_drag_position = event.globalPosition().toPoint() fullsize_label.setCursor(QCursor(Qt.CursorShape.ClosedHandCursor)) def on_fullsize_mouse_move(self, event, fullsize_label): """ Wird ausgeführt, wenn die Maus über einem großen Bild bewegt wird. Args: event: Das Maus-Event fullsize_label: Das große Bild-Label """ if self.is_dragging and self.last_drag_position is not None: current_pos = event.globalPosition().toPoint() delta = current_pos - self.last_drag_position if abs(delta.x()) >= self.drag_threshold or abs(delta.y()) >= self.drag_threshold: v_scrollbar = self.ui.scrollArea_2.verticalScrollBar() h_scrollbar = self.ui.scrollArea_2.horizontalScrollBar() scroll_delta_y = int(-delta.y() * self.scroll_sensitivity) scroll_delta_x = int(-delta.x() * self.scroll_sensitivity) new_v_value = v_scrollbar.value() + scroll_delta_y new_h_value = h_scrollbar.value() + scroll_delta_x v_scrollbar.setValue(new_v_value) h_scrollbar.setValue(new_h_value) self.last_drag_position = current_pos def on_fullsize_mouse_release(self, event, fullsize_label): """ Wird ausgeführt, wenn die Maustaste auf einem großen Bild losgelassen wird. Args: event: Das Maus-Event fullsize_label: Das große Bild-Label """ if event.button() == Qt.MouseButton.LeftButton: self.is_dragging = False self.last_drag_position = None fullsize_label.setCursor(QCursor(Qt.CursorShape.ArrowCursor)) def closeEvent(self, event): """Wird beim Schließen der Anwendung aufgerufen.""" # PDF-Dokumente schließen for pdf_filename, docs in self.pdf_documents.items(): docs['diff'].close() docs['ref'].close() docs['new'].close() super().closeEvent(event)