From 29574ce0dcf11b8f67581be78474703779bff498 Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Mon, 9 Mar 2026 20:19:42 +0100 Subject: [PATCH] Perf: PDF-Thumbnails progressiv rendern statt alle auf einmal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Placeholder-Labels werden sofort erstellt, das eigentliche Rendern erfolgt asynchron über QTimer.singleShot(0) — ein Thumbnail pro Event-Loop-Iteration. UI friert nicht mehr ein; RAM-Spitze wird verteilt. Co-Authored-By: Claude Sonnet 4.6 --- src/ui/mixins/pdf_viewer.py | 56 +++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/src/ui/mixins/pdf_viewer.py b/src/ui/mixins/pdf_viewer.py index 7c61fda..0c7ba65 100644 --- a/src/ui/mixins/pdf_viewer.py +++ b/src/ui/mixins/pdf_viewer.py @@ -14,7 +14,7 @@ import logging import time from pathlib import Path -from PySide6.QtCore import Qt, QSize, QUrl +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 @@ -206,6 +206,27 @@ class PdfViewerMixin: 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: @@ -395,43 +416,30 @@ class PdfViewerMixin: # Nehme die Seitenzahl der diff-PDF als Basis max_pages = diff_doc.pageCount() - # Erstelle Thumbnails für alle Seiten + # Erstelle Placeholder-Labels für alle Seiten (sofort, ohne Rendern) + thumbnail_labels = [] for page_num in range(max_pages): - # Nur diff-Seite für Thumbnail rendern - page_size = diff_doc.pagePointSize(page_num) - - # Skalierung für Thumbnail - scale_factor = 200.0 / page_size.width() # 200 Pixel Breite - - # Seite rendern - page_image = diff_doc.render( - page_num, - QSize(int(page_size.width() * scale_factor), int(page_size.height() * scale_factor)), - ) - - diff_pixmap = QPixmap.fromImage(page_image) - - # Thumbnail erstellen und zur linken Spalte hinzufügen thumbnail = QLabel() thumbnail.setObjectName(f"thumbnail_{pdf_basename}_page_{page_num + 1}") - thumbnail.setPixmap(diff_pixmap.scaledToWidth(200, Qt.TransformationMode.SmoothTransformation)) + 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) - # Seitennummer für Thumbnail anzeigen thumbnail_info = QLabel(f"Seite {page_num + 1}") thumbnail_info.setAlignment(Qt.AlignmentFlag.AlignCenter) - thumbnail_info.setMaximumHeight(18) # Kompakte Höhe - thumbnail_info.setContentsMargins(0, 0, 0, 0) # Keine Ränder + thumbnail_info.setMaximumHeight(18) + thumbnail_info.setContentsMargins(0, 0, 0, 0) self.ui.verticalLayout_2.addWidget(thumbnail_info) - # Beziehung zwischen Thumbnail und Seitennummer speichern self.thumbnail_to_page[thumbnail] = {"pdf_filename": pdf_basename, "page_num": page_num} - - # Click-Event für das Thumbnail einrichten 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)