V2.0.0 — Refonte interface complete
- Nouveau layout : menus en haut, vignettes a gauche, viewer au centre - Panneau vignettes redimensionnable (splitter) - Miniatures de chaque page - Drag & drop pour reordonner - Clic droit : supprimer page, inserer page blanche - Clic = naviguer vers la page - Viewer PDF ameliore : - Ajustement auto a l'ouverture - Roulette = defiler les pages - Ctrl+roulette = zoom - Drag & drop de fichier PDF sur la fenetre - Barre de progression pendant les traitements - Fusion de PDF (Fichier > Fusionner) - Enlever les fonds perdus (Annotations > Enlever) - Menus par categorie : Imposition, Pages, Annotations - Toolbar : Ouvrir, Enregistrer, Zoom, info page - Style sombre coherent CopyDev - Suppression du numero de telephone dans A propos Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
"""Widget de visualisation PDF avec pypdfium2."""
|
||||
"""Widget de visualisation PDF avec pypdfium2 — V2."""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QPushButton, QSpinBox, QScrollArea, QSizePolicy)
|
||||
from PyQt6.QtGui import QImage, QPixmap
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
QScrollArea, QSizePolicy, QApplication)
|
||||
from PyQt6.QtGui import QImage, QPixmap, QWheelEvent
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
|
||||
class PdfViewer(QWidget):
|
||||
"""Widget d'apercu PDF avec navigation et zoom."""
|
||||
"""Widget d'apercu PDF avec zoom molette et navigation fluide."""
|
||||
|
||||
page_changed = pyqtSignal(int, int) # page_num, total_pages
|
||||
page_changed = pyqtSignal(int, int) # page_num (0-based), total_pages
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._pdf = None
|
||||
self._pdf_path = None
|
||||
self._current_page = 0
|
||||
self._total_pages = 0
|
||||
self._zoom = 1.0
|
||||
@@ -23,73 +24,48 @@ class PdfViewer(QWidget):
|
||||
def _setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Zone d'affichage avec scroll
|
||||
self._scroll = QScrollArea()
|
||||
self._scroll.setWidgetResizable(True)
|
||||
self._scroll.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._scroll.setStyleSheet("QScrollArea { background: #1e1e1e; border: none; }")
|
||||
|
||||
self._image_label = QLabel()
|
||||
self._image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
|
||||
self._image_label.setStyleSheet("background: #1e1e1e;")
|
||||
self._scroll.setWidget(self._image_label)
|
||||
|
||||
# Intercepter la molette sur le scroll area
|
||||
self._scroll.wheelEvent = self._on_wheel
|
||||
|
||||
layout.addWidget(self._scroll, 1)
|
||||
|
||||
# Barre de navigation
|
||||
nav = QHBoxLayout()
|
||||
|
||||
self._btn_prev = QPushButton("<")
|
||||
self._btn_prev.setFixedWidth(40)
|
||||
self._btn_prev.clicked.connect(self._prev_page)
|
||||
nav.addWidget(self._btn_prev)
|
||||
|
||||
self._page_spin = QSpinBox()
|
||||
self._page_spin.setMinimum(1)
|
||||
self._page_spin.setMaximum(1)
|
||||
self._page_spin.valueChanged.connect(self._go_to_page)
|
||||
nav.addWidget(self._page_spin)
|
||||
|
||||
self._label_total = QLabel("/ 0")
|
||||
nav.addWidget(self._label_total)
|
||||
|
||||
self._btn_next = QPushButton(">")
|
||||
self._btn_next.setFixedWidth(40)
|
||||
self._btn_next.clicked.connect(self._next_page)
|
||||
nav.addWidget(self._btn_next)
|
||||
|
||||
nav.addSpacing(20)
|
||||
|
||||
self._btn_zoom_out = QPushButton("-")
|
||||
self._btn_zoom_out.setFixedWidth(30)
|
||||
self._btn_zoom_out.clicked.connect(self._zoom_out)
|
||||
nav.addWidget(self._btn_zoom_out)
|
||||
|
||||
self._label_zoom = QLabel("100%")
|
||||
self._label_zoom.setFixedWidth(45)
|
||||
self._label_zoom.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
nav.addWidget(self._label_zoom)
|
||||
|
||||
self._btn_zoom_in = QPushButton("+")
|
||||
self._btn_zoom_in.setFixedWidth(30)
|
||||
self._btn_zoom_in.clicked.connect(self._zoom_in)
|
||||
nav.addWidget(self._btn_zoom_in)
|
||||
|
||||
self._btn_fit = QPushButton("Ajuster")
|
||||
self._btn_fit.clicked.connect(self._zoom_fit)
|
||||
nav.addWidget(self._btn_fit)
|
||||
|
||||
nav.addStretch()
|
||||
layout.addLayout(nav)
|
||||
def _on_wheel(self, event: QWheelEvent):
|
||||
"""Molette = pages, Ctrl+Molette = zoom."""
|
||||
if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
|
||||
delta = event.angleDelta().y()
|
||||
if delta > 0:
|
||||
self._zoom = min(self._zoom * 1.15, 5.0)
|
||||
else:
|
||||
self._zoom = max(self._zoom / 1.15, 0.2)
|
||||
self._render_page()
|
||||
else:
|
||||
delta = event.angleDelta().y()
|
||||
if delta < 0:
|
||||
self.next_page()
|
||||
elif delta > 0:
|
||||
self.prev_page()
|
||||
|
||||
def load_pdf(self, path):
|
||||
"""Charger un fichier PDF."""
|
||||
try:
|
||||
self._pdf_path = path
|
||||
self._pdf = pdfium.PdfDocument(path)
|
||||
self._total_pages = len(self._pdf)
|
||||
self._current_page = 0
|
||||
self._page_spin.setMaximum(self._total_pages)
|
||||
self._page_spin.setValue(1)
|
||||
self._label_total.setText(f"/ {self._total_pages}")
|
||||
self._zoom_fit()
|
||||
self._render_page()
|
||||
except Exception as e:
|
||||
self._image_label.setText(f"Erreur: {e}")
|
||||
@@ -100,9 +76,7 @@ class PdfViewer(QWidget):
|
||||
self._pdf = pdfium.PdfDocument(data)
|
||||
self._total_pages = len(self._pdf)
|
||||
self._current_page = 0
|
||||
self._page_spin.setMaximum(self._total_pages)
|
||||
self._page_spin.setValue(1)
|
||||
self._label_total.setText(f"/ {self._total_pages}")
|
||||
self._zoom_fit()
|
||||
self._render_page()
|
||||
except Exception as e:
|
||||
self._image_label.setText(f"Erreur: {e}")
|
||||
@@ -113,44 +87,17 @@ class PdfViewer(QWidget):
|
||||
return
|
||||
|
||||
page = self._pdf[self._current_page]
|
||||
# Rendu a 150 DPI * zoom
|
||||
scale = 150 / 72 * self._zoom
|
||||
bitmap = page.render(scale=scale)
|
||||
pil_image = bitmap.to_pil()
|
||||
|
||||
# Convertir PIL -> QImage -> QPixmap
|
||||
data = pil_image.tobytes("raw", "RGBA")
|
||||
qimage = QImage(data, pil_image.width, pil_image.height, QImage.Format.Format_RGBA8888)
|
||||
pixmap = QPixmap.fromImage(qimage)
|
||||
|
||||
self._image_label.setPixmap(pixmap)
|
||||
self._image_label.adjustSize()
|
||||
self._label_zoom.setText(f"{int(self._zoom * 100)}%")
|
||||
self.page_changed.emit(self._current_page + 1, self._total_pages)
|
||||
|
||||
def _prev_page(self):
|
||||
if self._current_page > 0:
|
||||
self._current_page -= 1
|
||||
self._page_spin.setValue(self._current_page + 1)
|
||||
|
||||
def _next_page(self):
|
||||
if self._current_page < self._total_pages - 1:
|
||||
self._current_page += 1
|
||||
self._page_spin.setValue(self._current_page + 1)
|
||||
|
||||
def _go_to_page(self, value):
|
||||
page = value - 1
|
||||
if 0 <= page < self._total_pages and page != self._current_page:
|
||||
self._current_page = page
|
||||
self._render_page()
|
||||
|
||||
def _zoom_in(self):
|
||||
self._zoom = min(self._zoom * 1.25, 5.0)
|
||||
self._render_page()
|
||||
|
||||
def _zoom_out(self):
|
||||
self._zoom = max(self._zoom / 1.25, 0.25)
|
||||
self._render_page()
|
||||
self.page_changed.emit(self._current_page, self._total_pages)
|
||||
|
||||
def _zoom_fit(self):
|
||||
"""Ajuster le zoom pour que la page tienne dans la zone visible."""
|
||||
@@ -160,15 +107,56 @@ class PdfViewer(QWidget):
|
||||
pw, ph = page.get_size()
|
||||
vw = self._scroll.viewport().width() - 20
|
||||
vh = self._scroll.viewport().height() - 20
|
||||
if vw <= 0 or vh <= 0:
|
||||
self._zoom = 1.0
|
||||
return
|
||||
scale_x = vw / pw
|
||||
scale_y = vh / ph
|
||||
self._zoom = min(scale_x, scale_y) * (72 / 150)
|
||||
|
||||
def prev_page(self):
|
||||
if self._current_page > 0:
|
||||
self._current_page -= 1
|
||||
self._render_page()
|
||||
|
||||
def next_page(self):
|
||||
if self._current_page < self._total_pages - 1:
|
||||
self._current_page += 1
|
||||
self._render_page()
|
||||
|
||||
def go_to_page(self, page_index):
|
||||
if 0 <= page_index < self._total_pages and page_index != self._current_page:
|
||||
self._current_page = page_index
|
||||
self._render_page()
|
||||
|
||||
def zoom_in(self):
|
||||
self._zoom = min(self._zoom * 1.25, 5.0)
|
||||
self._render_page()
|
||||
|
||||
def zoom_out(self):
|
||||
self._zoom = max(self._zoom / 1.25, 0.2)
|
||||
self._render_page()
|
||||
|
||||
def zoom_fit(self):
|
||||
self._zoom_fit()
|
||||
self._render_page()
|
||||
|
||||
def refresh(self):
|
||||
"""Rafraichir l'affichage."""
|
||||
self._render_page()
|
||||
|
||||
def get_page_thumbnail(self, page_index, width=120):
|
||||
"""Generer une miniature pour le panneau de vignettes."""
|
||||
if not self._pdf or page_index >= self._total_pages:
|
||||
return None
|
||||
page = self._pdf[page_index]
|
||||
pw, ph = page.get_size()
|
||||
scale = width / pw
|
||||
bitmap = page.render(scale=scale)
|
||||
pil_image = bitmap.to_pil()
|
||||
data = pil_image.tobytes("raw", "RGBA")
|
||||
qimage = QImage(data, pil_image.width, pil_image.height, QImage.Format.Format_RGBA8888)
|
||||
return QPixmap.fromImage(qimage)
|
||||
|
||||
@property
|
||||
def current_page(self):
|
||||
return self._current_page
|
||||
@@ -176,3 +164,11 @@ class PdfViewer(QWidget):
|
||||
@property
|
||||
def total_pages(self):
|
||||
return self._total_pages
|
||||
|
||||
@property
|
||||
def zoom_level(self):
|
||||
return self._zoom
|
||||
|
||||
@property
|
||||
def pdf_path(self):
|
||||
return self._pdf_path
|
||||
|
||||
Reference in New Issue
Block a user