""" PdfViewerMixin - Mixin für PDF-Viewer-Operationen. Dieses Mixin enthält alle Methoden für die PDF-Anzeige und -Vergleich im MainWindow: - PDF-Rendering und -Anzeige - Alpha-Blending und Zoom - Thumbnail-Navigation - Drag-to-Scroll - PDF-Dokument-Management """ import gc import logging import time from pathlib import Path from PySide6.QtCore import Qt, QSize, QTimer, QUrl from PySide6.QtGui import QCursor, QPixmap, QPainter, QDesktopServices from PySide6.QtWidgets import QLabel, QMessageBox, QSpacerItem, QSizePolicy from PySide6.QtPdf import QPdfDocument logger = logging.getLogger(__name__) class PdfViewerMixin: """ Mixin-Klasse für PDF-Viewer-Operationen. Dieses Mixin erwartet, dass die verwendende Klasse folgende Attribute hat: - self.ui: UI-Objekt mit Layouts, Slidern, etc. - self.project: Aktuelles Projekt - self.pdf_documents: Dict für PDF-Dokumente - self.current_rendered_pixmaps: Cache für gerenderte Pixmaps - self.fullsize_label: QLabel für Vollbild-Anzeige - self.thumbnail_to_page: Dict für Thumbnail-zu-Seite-Mapping - self.current_zoom: Aktueller Zoom-Faktor - self.current_page: Aktuelle Seitennummer - self.current_pdf: Aktueller PDF-Dateiname - self.is_dragging: Drag-Status - self.last_drag_position: Letzte Drag-Position - self.drag_threshold: Mindestbewegung für Drag - self.scroll_sensitivity: Scroll-Empfindlichkeit """ def render_and_display_page(self, pdf_filename, page_num): """ Rendert und zeigt eine spezifische Seite in der Vollansicht an. Cached die gerenderten Pixmaps für bessere Performance. Args: pdf_filename: Name der PDF-Datei page_num: Seitennummer (0-basiert) """ logger.debug(f"Rendere Seite {page_num + 1} von {pdf_filename}") if pdf_filename not in self.pdf_documents: logger.warning(f"PDF-Dokument {pdf_filename} nicht gefunden") return start_time = time.time() try: docs = self.pdf_documents[pdf_filename] # Diff-Seite laden (bestimmt die Abmessungen) diff_doc = docs["diff"] page_size = diff_doc.pagePointSize(page_num) # Hohe Auflösung für Vollbild (entspricht ca. Matrix(2.0, 2.0) in PyMuPDF) scale_factor = 2.0 render_size = QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor)) # Diff-Seite rendern (immer vorhanden) diff_image = diff_doc.render(page_num, render_size) diff_pixmap = QPixmap.fromImage(diff_image) # Ermittle die Abmessungen für weiße Seiten diff_width = diff_pixmap.width() diff_height = diff_pixmap.height() # Ref-Seite prüfen und rendern oder weiße Seite erstellen ref_doc = docs["ref"] if page_num < ref_doc.pageCount(): ref_image = ref_doc.render(page_num, render_size) ref_pixmap = QPixmap.fromImage(ref_image) logger.debug(f"Ref-Seite {page_num + 1} gerendert") else: # Erstelle weiße Seite mit gleichen Abmessungen wie Diff-Seite ref_pixmap = QPixmap(diff_width, diff_height) ref_pixmap.fill(Qt.GlobalColor.white) logger.debug(f"Weiße Ref-Seite {page_num + 1} erstellt") # New-Seite prüfen und rendern oder weiße Seite erstellen new_doc = docs["new"] if page_num < new_doc.pageCount(): new_image = new_doc.render(page_num, render_size) new_pixmap = QPixmap.fromImage(new_image) logger.debug(f"New-Seite {page_num + 1} gerendert") else: # Erstelle weiße Seite mit gleichen Abmessungen wie Diff-Seite new_pixmap = QPixmap(diff_width, diff_height) new_pixmap.fill(Qt.GlobalColor.white) logger.debug(f"Weiße New-Seite {page_num + 1} erstellt") # Cache die gerenderten Pixmaps für schnelle Alpha/Zoom-Operationen self.current_rendered_pixmaps = {"ref": ref_pixmap, "diff": diff_pixmap, "new": new_pixmap} # Aktualisiere aktuelle Seite self.current_page = page_num self.current_pdf = pdf_filename # Zeige das Bild mit aktuellem Alpha- und Zoom-Wert an self.update_current_display() render_time = time.time() - start_time logger.debug(f"Performance: Seite {page_num + 1} gerendert in {render_time:.3f}s") except Exception as e: logger.error(f"Fehler beim Rendern der Seite {page_num + 1}: {e}", exc_info=True) def update_current_display(self): """ Aktualisiert die Anzeige der aktuellen Seite basierend auf gecachten Pixmaps. Verwendet für Alpha- und Zoom-Änderungen ohne erneutes PDF-Rendering. """ if not self.current_rendered_pixmaps: logger.warning("Keine gerenderten Pixmaps verfügbar") return if self.fullsize_label is None: logger.warning("Fullsize-Label ist nicht verfügbar") return try: # Hole die gecachten Pixmaps ref_pixmap = self.current_rendered_pixmaps["ref"] diff_pixmap = self.current_rendered_pixmaps["diff"] new_pixmap = self.current_rendered_pixmaps["new"] # Erstelle das überlagerte Bild mit aktuellem Alpha-Wert 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) except RuntimeError as e: # C++-Objekt wurde bereits gelöscht logger.warning(f"Fullsize-Label wurde bereits gelöscht: {e}") self.fullsize_label = None 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 = abs(alpha_value) / 100 diff_opacity = 1.0 - abs(alpha_value) / 100.0 new_opacity = 0.0 else: 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 _schedule_thumbnail_rendering(self, diff_doc, pdf_basename, thumbnail_labels, page_num): """Rendert Thumbnails progressiv — ein Thumbnail pro Event-Loop-Iteration.""" if page_num >= len(thumbnail_labels): return thumbnail = thumbnail_labels[page_num] # Prüfe ob das Label noch existiert (Benutzer könnte inzwischen anderes PDF geöffnet haben) if thumbnail_labels[page_num] not in self.thumbnail_to_page: return page_size = diff_doc.pagePointSize(page_num) scale_factor = 200.0 / page_size.width() page_image = diff_doc.render( page_num, QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor)), ) thumbnail.setPixmap(QPixmap.fromImage(page_image).scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)) thumbnail.setMinimumHeight(0) QTimer.singleShot(0, lambda: self._schedule_thumbnail_rendering(diff_doc, pdf_basename, thumbnail_labels, page_num + 1)) 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 on_alpha_changed(self, alpha_value): """ Wird ausgeführt, wenn der Alpha-Slider geändert wird. Optimiert: Verwendet gecachte Pixmaps statt erneutes PDF-Rendering. Args: alpha_value: Der neue Alpha-Wert (-100 bis 100) """ logger.debug(f"Alpha geändert auf {alpha_value}") start_time = time.time() # Verwende gecachte Pixmaps für schnelle Alpha-Änderungen self.update_current_display() alpha_time = time.time() - start_time logger.debug(f"Alpha-Update in {alpha_time:.6f}s") 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"] logger.debug(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. Optimiert: Verwendet gecachte Pixmaps statt erneutes PDF-Rendering. Args: zoom_value: Der neue Zoom-Wert (in Prozent) """ self.current_zoom = zoom_value logger.debug(f"Zoom geändert auf {zoom_value}%") # Verwende gecachte Pixmaps für schnelle Zoom-Änderungen self.update_current_display() def on_fullsize_mouse_press(self, event, fullsize_label): """Wird ausgeführt, wenn die Maustaste auf einem großen Bild gedrückt wird.""" 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.""" 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.""" if event.button() == Qt.MouseButton.LeftButton: self.is_dragging = False self.last_drag_position = None fullsize_label.setCursor(QCursor(Qt.CursorShape.OpenHandCursor)) def _load_pdf_for_comparison(self, xml_file_path: Path, xsl_id_str: str): """ Lädt die PDFs (diff, ref, new) einer Transformation in den Vergleichs-Viewer. Args: xml_file_path: Pfad zur XML-Datei (relativ) xsl_id_str: XSL-ID als String (z.B. "2002_1_128") """ try: if not self.project: QMessageBox.warning(self, "Fehler", "Kein Projekt geöffnet") return # Ermittle PDF-Dateinamen basierend auf XML und XSL-ID xml_stem = xml_file_path.stem pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf" # Pfade zu den drei PDFs diff_dir = self.project.project_dir / "diff" ref_dir = self.project.project_dir / "ref" new_dir = self.project.project_dir / "new" diff_pdf_path = diff_dir / pdf_basename ref_pdf_path = ref_dir / pdf_basename new_pdf_path = new_dir / pdf_basename # Prüfe ob PDFs existieren if not diff_pdf_path.exists(): QMessageBox.information(self, "Keine Diff-PDF", f"Diff-PDF nicht gefunden:\n{pdf_basename}") return if not ref_pdf_path.exists() or not new_pdf_path.exists(): QMessageBox.warning( self, "Fehlende PDFs", f"Ref-PDF oder New-PDF nicht gefunden:\n{pdf_basename}\n\nNur Diff-PDF vorhanden.", ) return logger.info(f"Lade PDFs für Vergleich: {pdf_basename}") # Entferne bestehende Widgets aus den Layouts self._clear_layout(self.ui.verticalLayout_2) self._clear_layout(self.ui.verticalLayout_3) # Setze kompaktes Spacing für Thumbnail-Layout self.ui.verticalLayout_2.setSpacing(5) # Minimaler Abstand zwischen Widgets self.ui.verticalLayout_2.setContentsMargins(0, 0, 0, 0) # Keine Ränder # Dicts zurücksetzen self.thumbnail_to_page = {} self.pdf_documents = {} self.current_rendered_pixmaps = None self.fullsize_label = None # Label wurde durch _clear_layout gelöscht # Alle drei PDF-Dateien öffnen mit QtPdf diff_doc = QPdfDocument() ref_doc = QPdfDocument() new_doc = QPdfDocument() # PDF-Dateien laden diff_doc.load(str(diff_pdf_path)) ref_doc.load(str(ref_pdf_path)) new_doc.load(str(new_pdf_path)) # Warten bis PDFs geladen sind if ( diff_doc.status() != QPdfDocument.Status.Ready or ref_doc.status() != QPdfDocument.Status.Ready or new_doc.status() != QPdfDocument.Status.Ready ): QMessageBox.critical(self, "Fehler", f"Fehler beim Laden der PDFs:\n{pdf_basename}") return # PDF-Dokumente speichern self.pdf_documents[pdf_basename] = {"diff": diff_doc, "ref": ref_doc, "new": new_doc} # PDF-Pfade für System-Viewer speichern self.current_ref_pdf_path = ref_pdf_path self.current_new_pdf_path = new_pdf_path # Buttons zum Öffnen der PDFs im System-Viewer aktivieren self.ui.view_ref_pdf.setEnabled(True) self.ui.view_new_pdf.setEnabled(True) # Slider aktivieren self.ui.alpha.setEnabled(True) self.ui.zoom.setEnabled(True) logger.info(f"PDFs geladen: {pdf_basename}") logger.info(f" diff: {diff_doc.pageCount()} Seiten") logger.info(f" ref: {ref_doc.pageCount()} Seiten") logger.info(f" new: {new_doc.pageCount()} Seiten") # Nehme die Seitenzahl der diff-PDF als Basis max_pages = diff_doc.pageCount() # Erstelle Placeholder-Labels für alle Seiten (sofort, ohne Rendern) thumbnail_labels = [] for page_num in range(max_pages): thumbnail = QLabel() thumbnail.setObjectName(f"thumbnail_{pdf_basename}_page_{page_num + 1}") thumbnail.setText(f"Seite {page_num + 1}\n…") thumbnail.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) thumbnail.setMouseTracking(True) thumbnail.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail.setMinimumHeight(150) self.ui.verticalLayout_2.addWidget(thumbnail) thumbnail_info = QLabel(f"Seite {page_num + 1}") thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail_info.setMaximumHeight(18) thumbnail_info.setContentsMargins(0, 0, 0, 0) self.ui.verticalLayout_2.addWidget(thumbnail_info) self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_basename, "page_num": page_num} thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t) thumbnail_labels.append(thumbnail) # Thumbnails progressiv rendern (nicht blockierend) self._schedule_thumbnail_rendering(diff_doc, pdf_basename, thumbnail_labels, 0) # Füge expandierenden Spacer am Ende hinzu, damit Thumbnails oben bleiben spacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.ui.verticalLayout_2.addItem(spacer) # Erstelle das Vollbild-Label für die rechte Spalte (falls noch nicht vorhanden) if self.fullsize_label is None: self.fullsize_label = QLabel() self.fullsize_label.setObjectName("fullsize_current_page") self.fullsize_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.fullsize_label.setCursor(QCursor(Qt.CursorShape.OpenHandCursor)) 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 ) # Setze die aktuelle PDF self.current_pdf = pdf_basename # Speichere Diff-PDF-Informationen für Accept Changes self.current_diff_xml_path = xml_file_path self.current_diff_xsl_id = xsl_id_str # Aktiviere Accept-Changes-Button self.ui.accept_changes.setEnabled(True) # Zeige die erste Seite initial an self.render_and_display_page(pdf_basename, 0) logger.info(f"PDF-Vergleich geladen: {pdf_basename}") except Exception as e: logger.error(f"Fehler beim Laden der PDFs für Vergleich: {e}") QMessageBox.critical(self, "Fehler", f"Konnte PDFs nicht laden:\n{str(e)}") def _close_all_pdf_documents(self): """Schließt alle geöffneten PDF-Dokumente explizit (wichtig für Windows).""" if self.pdf_documents: for pdf_basename, docs in self.pdf_documents.items(): for doc_type, doc in docs.items(): if doc: doc.close() logger.debug(f"PDF-Dokument geschlossen: {pdf_basename} ({doc_type})") # Lösche alle Referenzen self.pdf_documents.clear() # Lösche gerenderte Pixmaps self.current_rendered_pixmaps = None # Erzwinge Garbage Collection um Dateihandles freizugeben (wichtig für Windows) gc.collect() logger.info("Alle PDF-Dokumente geschlossen und Referenzen freigegeben") def _clear_pdf_viewer(self): """Leert den PDF-Viewer und alle Thumbnails.""" # Schließe alle PDF-Dokumente explizit (wichtig für Windows) self._close_all_pdf_documents() # Entferne Widgets aus Layouts self._clear_layout(self.ui.verticalLayout_2) self._clear_layout(self.ui.verticalLayout_3) # Zurücksetzen der Datenstrukturen self.thumbnail_to_page = {} self.pdf_documents = {} self.current_rendered_pixmaps = None self.fullsize_label = None self.current_pdf = None self.current_diff_xml_path = None self.current_diff_xsl_id = None # PDF-Pfade zurücksetzen und Buttons deaktivieren self.current_ref_pdf_path = None self.current_new_pdf_path = None self.ui.view_ref_pdf.setEnabled(False) self.ui.view_new_pdf.setEnabled(False) self.ui.accept_changes.setEnabled(False) # Slider deaktivieren self.ui.alpha.setEnabled(False) self.ui.zoom.setEnabled(False) logger.info("PDF-Viewer geleert") def _on_view_ref_pdf_clicked(self): """ Handler für view_ref_pdf Button. Öffnet die Referenz-PDF im systemseitig installierten PDF-Viewer. """ if not self.current_ref_pdf_path or not self.current_ref_pdf_path.exists(): QMessageBox.warning(self, "Fehler", "Referenz-PDF nicht gefunden") logger.warning("Referenz-PDF nicht verfügbar") return logger.info(f"Öffne Referenz-PDF im System-Viewer: {self.current_ref_pdf_path}") url = QUrl.fromLocalFile(str(self.current_ref_pdf_path)) if not QDesktopServices.openUrl(url): QMessageBox.critical(self, "Fehler", f"Konnte Referenz-PDF nicht öffnen:\n{self.current_ref_pdf_path}") logger.error(f"Fehler beim Öffnen der Referenz-PDF: {self.current_ref_pdf_path}") def _load_ref_pdf_for_display(self, xml_file_path: Path, xsl_id_str: str): """ Lädt nur die Ref-PDF in den Viewer (wenn keine Diff-PDF vorhanden ist). Args: xml_file_path: Pfad zur XML-Datei (relativ) xsl_id_str: XSL-ID als String """ try: if not self.project: return xml_stem = xml_file_path.stem pdf_basename = f"{xml_stem}_xsl_{xsl_id_str}.pdf" ref_pdf_path = self.project.project_dir / "ref" / pdf_basename if not ref_pdf_path.exists(): if self.pdf_documents: self._clear_pdf_viewer() return logger.info(f"Lade Ref-PDF für Anzeige: {pdf_basename}") self._clear_layout(self.ui.verticalLayout_2) self._clear_layout(self.ui.verticalLayout_3) self.ui.verticalLayout_2.setSpacing(5) self.ui.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.thumbnail_to_page = {} self.pdf_documents = {} self.current_rendered_pixmaps = None self.fullsize_label = None ref_doc = QPdfDocument() ref_doc.load(str(ref_pdf_path)) if ref_doc.status() != QPdfDocument.Status.Ready: QMessageBox.critical(self, "Fehler", f"Fehler beim Laden der Ref-PDF:\n{pdf_basename}") return # Ref-Doc in allen drei Slots speichern, damit render_and_display_page funktioniert self.pdf_documents[pdf_basename] = {"diff": ref_doc, "ref": ref_doc, "new": ref_doc} self.current_ref_pdf_path = ref_pdf_path self.current_new_pdf_path = None self.ui.view_ref_pdf.setEnabled(True) self.ui.view_new_pdf.setEnabled(False) # Alpha deaktivieren (Blending nicht sinnvoll ohne Diff), Zoom aktivieren self.ui.alpha.setValue(0) self.ui.alpha.setEnabled(False) self.ui.zoom.setEnabled(True) self.ui.accept_changes.setEnabled(False) max_pages = ref_doc.pageCount() logger.info(f"Ref-PDF geladen: {pdf_basename}, {max_pages} Seiten") thumbnail_labels = [] for page_num in range(max_pages): thumbnail = QLabel() thumbnail.setObjectName(f"thumbnail_{pdf_basename}_page_{page_num + 1}") thumbnail.setText(f"Seite {page_num + 1}\n…") thumbnail.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) thumbnail.setMouseTracking(True) thumbnail.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail.setMinimumHeight(150) self.ui.verticalLayout_2.addWidget(thumbnail) thumbnail_info = QLabel(f"Seite {page_num + 1}") thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter) thumbnail_info.setMaximumHeight(18) thumbnail_info.setContentsMargins(0, 0, 0, 0) self.ui.verticalLayout_2.addWidget(thumbnail_info) self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_basename, "page_num": page_num} thumbnail.mousePressEvent = lambda event, t=thumbnail: self.on_thumbnail_clicked(event, t) thumbnail_labels.append(thumbnail) self._schedule_thumbnail_rendering(ref_doc, pdf_basename, thumbnail_labels, 0) spacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) self.ui.verticalLayout_2.addItem(spacer) if self.fullsize_label is None: self.fullsize_label = QLabel() self.fullsize_label.setObjectName("fullsize_current_page") self.fullsize_label.setAlignment(Qt.AlignmentFlag.AlignHCenter) self.fullsize_label.setCursor(QCursor(Qt.CursorShape.OpenHandCursor)) self.ui.verticalLayout_3.addWidget(self.fullsize_label) 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 ) self.current_pdf = pdf_basename self.current_diff_xml_path = None self.current_diff_xsl_id = None self.render_and_display_page(pdf_basename, 0) logger.info(f"Ref-PDF-Anzeige geladen: {pdf_basename}") except Exception as e: logger.error(f"Fehler beim Laden der Ref-PDF: {e}") QMessageBox.critical(self, "Fehler", f"Konnte Ref-PDF nicht laden:\n{str(e)}") def _on_view_new_pdf_clicked(self): """ Handler für view_new_pdf Button. Öffnet die neue PDF im systemseitig installierten PDF-Viewer. """ if not self.current_new_pdf_path or not self.current_new_pdf_path.exists(): QMessageBox.warning(self, "Fehler", "Neue PDF nicht gefunden") logger.warning("Neue PDF nicht verfügbar") return logger.info(f"Öffne neue PDF im System-Viewer: {self.current_new_pdf_path}") url = QUrl.fromLocalFile(str(self.current_new_pdf_path)) if not QDesktopServices.openUrl(url): QMessageBox.critical(self, "Fehler", f"Konnte neue PDF nicht öffnen:\n{self.current_new_pdf_path}") logger.error(f"Fehler beim Öffnen der neuen PDF: {self.current_new_pdf_path}")