V2.3.1 — Recadrage avec selection visuelle
- Bouton "Selectionner sur l'apercu" dans le dialogue Recadrer - Tracer un rectangle a la souris directement sur le PDF - Zone sombre en dehors de la selection, cadre bleu en pointilles - Les marges se mettent a jour automatiquement depuis la selection - Les marges saisies manuellement affichent le cadre sur l'apercu - Synchronisation bidirectionnelle selection <-> valeurs - Nettoyage auto du mode selection a la fermeture du dialogue Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,15 @@
|
||||
"""Dialogue Recadrer les pages."""
|
||||
"""Dialogue Recadrer les pages — avec selection visuelle."""
|
||||
|
||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QDoubleSpinBox, QComboBox, QRadioButton,
|
||||
QDialogButtonBox, QLineEdit)
|
||||
QDialogButtonBox, QLineEdit, QPushButton)
|
||||
from PyQt6.QtCore import Qt
|
||||
from app.engine.pdf_utils import mm_to_pt, pt_to_mm
|
||||
|
||||
|
||||
class CropDialog(QDialog):
|
||||
def __init__(self, page_width_pt=595, page_height_pt=842, total_pages=1,
|
||||
current_page=0, parent=None):
|
||||
current_page=0, viewer=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Recadrer")
|
||||
self.setMinimumWidth(450)
|
||||
@@ -16,19 +17,40 @@ class CropDialog(QDialog):
|
||||
self._page_h = page_height_pt
|
||||
self._total_pages = total_pages
|
||||
self._current_page = current_page
|
||||
self._viewer = viewer
|
||||
self._setup_ui()
|
||||
self._update_preview()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Info page actuelle
|
||||
# Info page
|
||||
pw_mm = pt_to_mm(self._page_w)
|
||||
ph_mm = pt_to_mm(self._page_h)
|
||||
info = QLabel(f"Page actuelle : {pw_mm:.1f} x {ph_mm:.1f} mm")
|
||||
info.setStyleSheet("color: #888; font-size: 11px;")
|
||||
layout.addWidget(info)
|
||||
|
||||
# Bouton selection visuelle
|
||||
if self._viewer:
|
||||
sel_layout = QHBoxLayout()
|
||||
self.btn_select = QPushButton("Selectionner sur l'apercu")
|
||||
self.btn_select.setCheckable(True)
|
||||
self.btn_select.setStyleSheet("""
|
||||
QPushButton { background: #0066cc; color: white; padding: 8px 16px;
|
||||
border-radius: 4px; font-weight: bold; }
|
||||
QPushButton:checked { background: #cc3300; }
|
||||
QPushButton:hover { background: #0055aa; }
|
||||
QPushButton:checked:hover { background: #aa2200; }
|
||||
""")
|
||||
self.btn_select.toggled.connect(self._toggle_select_mode)
|
||||
sel_layout.addWidget(self.btn_select)
|
||||
sel_layout.addStretch()
|
||||
layout.addLayout(sel_layout)
|
||||
|
||||
# Connecter les signaux du viewer
|
||||
self._viewer.selection_changed.connect(self._on_selection_changed)
|
||||
|
||||
# Mode
|
||||
grp_mode = QGroupBox("Mode de recadrage")
|
||||
ml = QVBoxLayout(grp_mode)
|
||||
@@ -38,14 +60,14 @@ class CropDialog(QDialog):
|
||||
ml.addWidget(self.rb_margins)
|
||||
ml.addWidget(self.rb_size)
|
||||
layout.addWidget(grp_mode)
|
||||
|
||||
self.rb_margins.toggled.connect(self._on_mode_changed)
|
||||
self.rb_size.toggled.connect(self._on_mode_changed)
|
||||
|
||||
# Marges de recadrage
|
||||
# Marges
|
||||
self.grp_margins_input = QGroupBox("Marges a retirer (mm)")
|
||||
mgl = QVBoxLayout(self.grp_margins_input)
|
||||
self.margin_spins = {}
|
||||
|
||||
row1 = QHBoxLayout()
|
||||
row1.addStretch()
|
||||
row1.addWidget(QLabel("Haut :"))
|
||||
@@ -53,6 +75,7 @@ class CropDialog(QDialog):
|
||||
self.margin_spins["haut"].setRange(0, 500)
|
||||
self.margin_spins["haut"].setSuffix(" mm")
|
||||
self.margin_spins["haut"].valueChanged.connect(self._update_preview)
|
||||
self.margin_spins["haut"].valueChanged.connect(self._show_selection_from_margins)
|
||||
row1.addWidget(self.margin_spins["haut"])
|
||||
row1.addStretch()
|
||||
mgl.addLayout(row1)
|
||||
@@ -63,6 +86,7 @@ class CropDialog(QDialog):
|
||||
self.margin_spins["gauche"].setRange(0, 500)
|
||||
self.margin_spins["gauche"].setSuffix(" mm")
|
||||
self.margin_spins["gauche"].valueChanged.connect(self._update_preview)
|
||||
self.margin_spins["gauche"].valueChanged.connect(self._show_selection_from_margins)
|
||||
row2.addWidget(self.margin_spins["gauche"])
|
||||
row2.addStretch()
|
||||
row2.addWidget(QLabel("Droite :"))
|
||||
@@ -70,6 +94,7 @@ class CropDialog(QDialog):
|
||||
self.margin_spins["droite"].setRange(0, 500)
|
||||
self.margin_spins["droite"].setSuffix(" mm")
|
||||
self.margin_spins["droite"].valueChanged.connect(self._update_preview)
|
||||
self.margin_spins["droite"].valueChanged.connect(self._show_selection_from_margins)
|
||||
row2.addWidget(self.margin_spins["droite"])
|
||||
mgl.addLayout(row2)
|
||||
|
||||
@@ -80,6 +105,7 @@ class CropDialog(QDialog):
|
||||
self.margin_spins["bas"].setRange(0, 500)
|
||||
self.margin_spins["bas"].setSuffix(" mm")
|
||||
self.margin_spins["bas"].valueChanged.connect(self._update_preview)
|
||||
self.margin_spins["bas"].valueChanged.connect(self._show_selection_from_margins)
|
||||
row3.addWidget(self.margin_spins["bas"])
|
||||
row3.addStretch()
|
||||
mgl.addLayout(row3)
|
||||
@@ -105,7 +131,7 @@ class CropDialog(QDialog):
|
||||
layout.addWidget(self.grp_size_input)
|
||||
self.grp_size_input.setVisible(False)
|
||||
|
||||
# Apercu dimension finale
|
||||
# Apercu
|
||||
self.lbl_preview = QLabel()
|
||||
self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;")
|
||||
layout.addWidget(self.lbl_preview)
|
||||
@@ -123,7 +149,6 @@ class CropDialog(QDialog):
|
||||
al.addWidget(self.rb_all)
|
||||
al.addWidget(self.rb_even)
|
||||
al.addWidget(self.rb_odd)
|
||||
|
||||
range_layout = QHBoxLayout()
|
||||
range_layout.addWidget(self.rb_range)
|
||||
self.edit_range = QLineEdit()
|
||||
@@ -132,7 +157,6 @@ class CropDialog(QDialog):
|
||||
range_layout.addWidget(self.edit_range)
|
||||
al.addLayout(range_layout)
|
||||
layout.addWidget(grp_apply)
|
||||
|
||||
self.rb_range.toggled.connect(self.edit_range.setEnabled)
|
||||
|
||||
# Boutons
|
||||
@@ -143,6 +167,54 @@ class CropDialog(QDialog):
|
||||
buttons.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
def _toggle_select_mode(self, checked):
|
||||
if self._viewer:
|
||||
self._viewer.set_select_mode(checked)
|
||||
if checked:
|
||||
self.btn_select.setText("Cliquez et tracez sur l'apercu...")
|
||||
else:
|
||||
self.btn_select.setText("Selectionner sur l'apercu")
|
||||
|
||||
def _on_selection_changed(self, left, top, right, bottom):
|
||||
"""Recoit les ratios de selection du viewer et met a jour les marges."""
|
||||
if not self.rb_margins.isChecked():
|
||||
return
|
||||
pw_mm = pt_to_mm(self._page_w)
|
||||
ph_mm = pt_to_mm(self._page_h)
|
||||
|
||||
# Les ratios representent la zone SELECTIONNEE (a garder)
|
||||
# Les marges sont ce qu'on ENLEVE
|
||||
margin_left = left * pw_mm
|
||||
margin_right = (1 - right) * pw_mm
|
||||
margin_top = top * ph_mm
|
||||
margin_bottom = (1 - bottom) * ph_mm
|
||||
|
||||
# Mettre a jour sans declencher les signaux en boucle
|
||||
for spin in self.margin_spins.values():
|
||||
spin.blockSignals(True)
|
||||
self.margin_spins["gauche"].setValue(margin_left)
|
||||
self.margin_spins["droite"].setValue(margin_right)
|
||||
self.margin_spins["haut"].setValue(margin_top)
|
||||
self.margin_spins["bas"].setValue(margin_bottom)
|
||||
for spin in self.margin_spins.values():
|
||||
spin.blockSignals(False)
|
||||
|
||||
self._update_preview()
|
||||
|
||||
def _show_selection_from_margins(self):
|
||||
"""Afficher le rectangle de selection sur le viewer a partir des marges."""
|
||||
if not self._viewer or not self.rb_margins.isChecked():
|
||||
return
|
||||
pw_mm = pt_to_mm(self._page_w)
|
||||
ph_mm = pt_to_mm(self._page_h)
|
||||
|
||||
left = self.margin_spins["gauche"].value() / pw_mm if pw_mm > 0 else 0
|
||||
top = self.margin_spins["haut"].value() / ph_mm if ph_mm > 0 else 0
|
||||
right = 1 - (self.margin_spins["droite"].value() / pw_mm) if pw_mm > 0 else 1
|
||||
bottom = 1 - (self.margin_spins["bas"].value() / ph_mm) if ph_mm > 0 else 1
|
||||
|
||||
self._viewer.show_selection(left, top, right, bottom)
|
||||
|
||||
def _on_mode_changed(self):
|
||||
self.grp_margins_input.setVisible(self.rb_margins.isChecked())
|
||||
self.grp_size_input.setVisible(self.rb_size.isChecked())
|
||||
@@ -168,7 +240,6 @@ class CropDialog(QDialog):
|
||||
self.lbl_preview.setStyleSheet("font-weight: bold; font-size: 13px; color: #0066cc; padding: 8px;")
|
||||
|
||||
def _parse_range(self):
|
||||
"""Parser la plage de pages. Retourne une liste d'indices (0-based)."""
|
||||
text = self.edit_range.text().strip()
|
||||
if not text:
|
||||
return []
|
||||
@@ -192,16 +263,30 @@ class CropDialog(QDialog):
|
||||
pass
|
||||
return sorted(set(indices))
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Nettoyer le mode selection a la fermeture."""
|
||||
if self._viewer:
|
||||
self._viewer.set_select_mode(False)
|
||||
self._viewer.clear_selection()
|
||||
super().closeEvent(event)
|
||||
|
||||
def reject(self):
|
||||
if self._viewer:
|
||||
self._viewer.set_select_mode(False)
|
||||
self._viewer.clear_selection()
|
||||
super().reject()
|
||||
|
||||
def get_settings(self):
|
||||
# Mode
|
||||
if self._viewer:
|
||||
self._viewer.set_select_mode(False)
|
||||
self._viewer.clear_selection()
|
||||
|
||||
if self.rb_margins.isChecked():
|
||||
mode = "margins"
|
||||
crop_top = mm_to_pt(self.margin_spins["haut"].value())
|
||||
crop_bottom = mm_to_pt(self.margin_spins["bas"].value())
|
||||
crop_left = mm_to_pt(self.margin_spins["gauche"].value())
|
||||
crop_right = mm_to_pt(self.margin_spins["droite"].value())
|
||||
else:
|
||||
mode = "size"
|
||||
final_w = mm_to_pt(self.spin_final_w.value())
|
||||
final_h = mm_to_pt(self.spin_final_h.value())
|
||||
crop_left = (self._page_w - final_w) / 2
|
||||
@@ -209,7 +294,6 @@ class CropDialog(QDialog):
|
||||
crop_top = (self._page_h - final_h) / 2
|
||||
crop_bottom = crop_top
|
||||
|
||||
# Pages cibles
|
||||
if self.rb_current.isChecked():
|
||||
pages = [self._current_page]
|
||||
elif self.rb_all.isChecked():
|
||||
|
||||
@@ -610,6 +610,7 @@ class MainWindow(QMainWindow):
|
||||
page_width_pt=w, page_height_pt=h,
|
||||
total_pages=len(reader.pages),
|
||||
current_page=self._viewer.current_page,
|
||||
viewer=self._viewer,
|
||||
parent=self
|
||||
)
|
||||
if dlg.exec():
|
||||
|
||||
@@ -1,16 +1,148 @@
|
||||
"""Widget de visualisation PDF avec pypdfium2 — V2."""
|
||||
"""Widget de visualisation PDF avec pypdfium2 — V2.3.1 avec selection."""
|
||||
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QLabel,
|
||||
QScrollArea, QSizePolicy, QApplication)
|
||||
from PyQt6.QtGui import QImage, QPixmap, QWheelEvent
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QTimer
|
||||
from PyQt6.QtGui import (QImage, QPixmap, QWheelEvent, QPainter, QPen, QColor,
|
||||
QMouseEvent, QCursor)
|
||||
from PyQt6.QtCore import Qt, pyqtSignal, QRect, QPoint
|
||||
import pypdfium2 as pdfium
|
||||
|
||||
|
||||
class PdfViewer(QWidget):
|
||||
"""Widget d'apercu PDF avec zoom molette et navigation fluide."""
|
||||
class SelectableImageLabel(QLabel):
|
||||
"""QLabel avec possibilite de dessiner un rectangle de selection."""
|
||||
|
||||
page_changed = pyqtSignal(int, int) # page_num (0-based), total_pages
|
||||
selection_changed = pyqtSignal(float, float, float, float) # left, top, right, bottom en ratio (0-1)
|
||||
selection_done = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._selecting = False
|
||||
self._select_mode = False
|
||||
self._start = QPoint()
|
||||
self._end = QPoint()
|
||||
self._rect = QRect()
|
||||
self._has_selection = False
|
||||
self._img_rect = QRect() # zone de l'image dans le label
|
||||
|
||||
def set_select_mode(self, enabled):
|
||||
self._select_mode = enabled
|
||||
self.setCursor(QCursor(Qt.CursorShape.CrossCursor if enabled else Qt.CursorShape.ArrowCursor))
|
||||
if not enabled:
|
||||
self._has_selection = False
|
||||
self.update()
|
||||
|
||||
def clear_selection(self):
|
||||
self._has_selection = False
|
||||
self.update()
|
||||
|
||||
def set_selection_from_margins(self, left_ratio, top_ratio, right_ratio, bottom_ratio):
|
||||
"""Afficher un rectangle de selection a partir de ratios (0-1)."""
|
||||
if not self.pixmap():
|
||||
return
|
||||
pm = self.pixmap()
|
||||
img_w, img_h = pm.width(), pm.height()
|
||||
# Calculer la position de l'image dans le label (centree)
|
||||
lbl_w, lbl_h = self.width(), self.height()
|
||||
off_x = max(0, (lbl_w - img_w) // 2)
|
||||
off_y = max(0, (lbl_h - img_h) // 2)
|
||||
|
||||
x1 = off_x + int(left_ratio * img_w)
|
||||
y1 = off_y + int(top_ratio * img_h)
|
||||
x2 = off_x + int(right_ratio * img_w)
|
||||
y2 = off_y + int(bottom_ratio * img_h)
|
||||
self._rect = QRect(QPoint(x1, y1), QPoint(x2, y2))
|
||||
self._has_selection = True
|
||||
self.update()
|
||||
|
||||
def mousePressEvent(self, event: QMouseEvent):
|
||||
if self._select_mode and event.button() == Qt.MouseButton.LeftButton:
|
||||
self._selecting = True
|
||||
self._start = event.pos()
|
||||
self._end = event.pos()
|
||||
self._rect = QRect(self._start, self._end).normalized()
|
||||
self._has_selection = True
|
||||
self.update()
|
||||
else:
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def mouseMoveEvent(self, event: QMouseEvent):
|
||||
if self._selecting:
|
||||
self._end = event.pos()
|
||||
self._rect = QRect(self._start, self._end).normalized()
|
||||
self.update()
|
||||
self._emit_ratios()
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent):
|
||||
if self._selecting:
|
||||
self._selecting = False
|
||||
self._end = event.pos()
|
||||
self._rect = QRect(self._start, self._end).normalized()
|
||||
self.update()
|
||||
self._emit_ratios()
|
||||
self.selection_done.emit()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def _emit_ratios(self):
|
||||
if not self.pixmap():
|
||||
return
|
||||
pm = self.pixmap()
|
||||
img_w, img_h = pm.width(), pm.height()
|
||||
lbl_w, lbl_h = self.width(), self.height()
|
||||
off_x = max(0, (lbl_w - img_w) // 2)
|
||||
off_y = max(0, (lbl_h - img_h) // 2)
|
||||
|
||||
left = max(0, (self._rect.left() - off_x)) / img_w
|
||||
top = max(0, (self._rect.top() - off_y)) / img_h
|
||||
right = min(1, (self._rect.right() - off_x)) / img_w
|
||||
bottom = min(1, (self._rect.bottom() - off_y)) / img_h
|
||||
|
||||
left = max(0, min(1, left))
|
||||
top = max(0, min(1, top))
|
||||
right = max(0, min(1, right))
|
||||
bottom = max(0, min(1, bottom))
|
||||
|
||||
self.selection_changed.emit(left, top, right, bottom)
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().paintEvent(event)
|
||||
if self._has_selection and not self._rect.isNull():
|
||||
painter = QPainter(self)
|
||||
# Zone sombre en dehors de la selection
|
||||
painter.setBrush(QColor(0, 0, 0, 100))
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
full = self.rect()
|
||||
r = self._rect.normalized()
|
||||
# Haut
|
||||
painter.drawRect(full.left(), full.top(), full.width(), r.top() - full.top())
|
||||
# Bas
|
||||
painter.drawRect(full.left(), r.bottom(), full.width(), full.bottom() - r.bottom())
|
||||
# Gauche
|
||||
painter.drawRect(full.left(), r.top(), r.left() - full.left(), r.height())
|
||||
# Droite
|
||||
painter.drawRect(r.right(), r.top(), full.right() - r.right(), r.height())
|
||||
|
||||
# Cadre de selection
|
||||
pen = QPen(QColor(0, 120, 255), 2, Qt.PenStyle.DashLine)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(Qt.BrushStyle.NoBrush)
|
||||
painter.drawRect(r)
|
||||
|
||||
# Dimensions en mm (affichees dans le coin)
|
||||
painter.setPen(QColor(0, 120, 255))
|
||||
painter.drawText(r.left() + 4, r.top() - 4,
|
||||
f"{r.width()}x{r.height()} px")
|
||||
painter.end()
|
||||
|
||||
|
||||
class PdfViewer(QWidget):
|
||||
"""Widget d'apercu PDF avec zoom molette, navigation et selection."""
|
||||
|
||||
page_changed = pyqtSignal(int, int)
|
||||
selection_changed = pyqtSignal(float, float, float, float) # ratios
|
||||
selection_done = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
@@ -19,6 +151,8 @@ class PdfViewer(QWidget):
|
||||
self._current_page = 0
|
||||
self._total_pages = 0
|
||||
self._zoom = 1.0
|
||||
self._page_width_pt = 0
|
||||
self._page_height_pt = 0
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
@@ -31,19 +165,27 @@ class PdfViewer(QWidget):
|
||||
self._scroll.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._scroll.setStyleSheet("QScrollArea { background: #1e1e1e; border: none; }")
|
||||
|
||||
self._image_label = QLabel()
|
||||
self._image_label = SelectableImageLabel()
|
||||
self._image_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._image_label.setSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored)
|
||||
self._image_label.setStyleSheet("background: #1e1e1e;")
|
||||
self._image_label.selection_changed.connect(self.selection_changed.emit)
|
||||
self._image_label.selection_done.connect(self.selection_done.emit)
|
||||
self._scroll.setWidget(self._image_label)
|
||||
|
||||
# Intercepter la molette sur le scroll area
|
||||
self._scroll.wheelEvent = self._on_wheel
|
||||
|
||||
layout.addWidget(self._scroll, 1)
|
||||
|
||||
def set_select_mode(self, enabled):
|
||||
self._image_label.set_select_mode(enabled)
|
||||
|
||||
def clear_selection(self):
|
||||
self._image_label.clear_selection()
|
||||
|
||||
def show_selection(self, left_ratio, top_ratio, right_ratio, bottom_ratio):
|
||||
self._image_label.set_selection_from_margins(left_ratio, top_ratio, right_ratio, bottom_ratio)
|
||||
|
||||
def _on_wheel(self, event: QWheelEvent):
|
||||
"""Molette = pages, Ctrl+Molette = zoom."""
|
||||
if event.modifiers() & Qt.KeyboardModifier.ControlModifier:
|
||||
delta = event.angleDelta().y()
|
||||
if delta > 0:
|
||||
@@ -59,7 +201,6 @@ class PdfViewer(QWidget):
|
||||
self.prev_page()
|
||||
|
||||
def load_pdf(self, path):
|
||||
"""Charger un fichier PDF."""
|
||||
try:
|
||||
self._pdf_path = path
|
||||
self._pdf = pdfium.PdfDocument(path)
|
||||
@@ -71,7 +212,6 @@ class PdfViewer(QWidget):
|
||||
self._image_label.setText(f"Erreur: {e}")
|
||||
|
||||
def load_pdf_bytes(self, data):
|
||||
"""Charger un PDF depuis des bytes."""
|
||||
try:
|
||||
self._pdf = pdfium.PdfDocument(data)
|
||||
self._total_pages = len(self._pdf)
|
||||
@@ -82,25 +222,21 @@ class PdfViewer(QWidget):
|
||||
self._image_label.setText(f"Erreur: {e}")
|
||||
|
||||
def _render_page(self):
|
||||
"""Afficher la page courante."""
|
||||
if not self._pdf or self._current_page >= self._total_pages:
|
||||
return
|
||||
|
||||
page = self._pdf[self._current_page]
|
||||
self._page_width_pt, self._page_height_pt = page.get_size()
|
||||
scale = 150 / 72 * self._zoom
|
||||
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)
|
||||
pixmap = QPixmap.fromImage(qimage)
|
||||
|
||||
self._image_label.setPixmap(pixmap)
|
||||
self._image_label.adjustSize()
|
||||
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."""
|
||||
if not self._pdf or self._current_page >= self._total_pages:
|
||||
return
|
||||
page = self._pdf[self._current_page]
|
||||
@@ -145,7 +281,6 @@ class PdfViewer(QWidget):
|
||||
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]
|
||||
@@ -172,3 +307,11 @@ class PdfViewer(QWidget):
|
||||
@property
|
||||
def pdf_path(self):
|
||||
return self._pdf_path
|
||||
|
||||
@property
|
||||
def page_width_pt(self):
|
||||
return self._page_width_pt
|
||||
|
||||
@property
|
||||
def page_height_pt(self):
|
||||
return self._page_height_pt
|
||||
|
||||
@@ -14,7 +14,7 @@ REPO = "jules/copydev-imposing-tool"
|
||||
BRANCH = "master"
|
||||
|
||||
# Version actuelle (incrementee a chaque release)
|
||||
VERSION = "2.3.0"
|
||||
VERSION = "2.3.1"
|
||||
|
||||
|
||||
def get_remote_version():
|
||||
|
||||
Reference in New Issue
Block a user