V3.6.0 — Demonter pages, masquage visuel, regles, bleeds, corrections
- Demonter les pages : couper en 2 sans reordonner (sous Delivretiser) - Masquage visuel : selection au curseur sur l'apercu + plage de pages - Regles horizontale/verticale en mm (menu Affichage) - Fonds perdus : filigrane rouge pour visualiser les bleeds - Reperes de coupe : marques a l'exterieur (page agrandie), 2 traits/coin - Numerotation : couleur configurable via selecteur - Decalage : Trim & Shift renomme en francais, creep -> chasse - Recadrages multiples cumulatifs (plages differentes) - Fix spinbox format personnalise (fleches aleatoires) - Fix combo livret : option Automatique preservee Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,12 +64,18 @@ class CustomSizeDialog(QDialog):
|
||||
self.spin_w.setRange(10, 5000)
|
||||
self.spin_w.setValue(210)
|
||||
self.spin_w.setSuffix(" mm")
|
||||
self.spin_w.setDecimals(1)
|
||||
self.spin_w.setSingleStep(1.0)
|
||||
self.spin_w.setMinimumWidth(100)
|
||||
gl.addWidget(self.spin_w)
|
||||
gl.addWidget(QLabel("Hauteur :"))
|
||||
self.spin_h = QDoubleSpinBox()
|
||||
self.spin_h.setRange(10, 5000)
|
||||
self.spin_h.setValue(297)
|
||||
self.spin_h.setSuffix(" mm")
|
||||
self.spin_h.setDecimals(1)
|
||||
self.spin_h.setSingleStep(1.0)
|
||||
self.spin_h.setMinimumWidth(100)
|
||||
gl.addWidget(self.spin_h)
|
||||
layout.addWidget(grp)
|
||||
|
||||
|
||||
@@ -12,10 +12,27 @@ ADD_LABEL = "Ajouter un format..."
|
||||
|
||||
|
||||
def populate_format_combo(combo, default="A4 Paysage"):
|
||||
"""Remplir un combo avec les formats standard + custom + options."""
|
||||
"""Remplir un combo avec les formats standard + custom + options.
|
||||
|
||||
Si default n'est pas un format standard, il est conserve en tete du combo.
|
||||
"""
|
||||
combo.blockSignals(True)
|
||||
|
||||
# Sauvegarder l'item par defaut s'il n'est pas un format standard
|
||||
keep_first = None
|
||||
if default and default not in PAGE_SIZES:
|
||||
# Verifier s'il est deja dans le combo avec des data
|
||||
idx = combo.findText(default)
|
||||
if idx >= 0:
|
||||
keep_first = (default, combo.itemData(idx))
|
||||
|
||||
combo.clear()
|
||||
|
||||
# Re-ajouter l'item special en tete si necessaire
|
||||
if keep_first:
|
||||
combo.addItem(keep_first[0], keep_first[1])
|
||||
combo.insertSeparator(combo.count())
|
||||
|
||||
# Formats standard
|
||||
for name in PAGE_SIZES:
|
||||
combo.addItem(name, PAGE_SIZES[name])
|
||||
|
||||
@@ -1,61 +1,109 @@
|
||||
"""Dialogue Masquage (rectangles blancs)."""
|
||||
"""Dialogue Masquage (rectangles blancs) — avec selection visuelle."""
|
||||
|
||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QComboBox, QDoubleSpinBox,
|
||||
QDialogButtonBox, QPushButton, QListWidget,
|
||||
QListWidgetItem)
|
||||
from app.engine.pdf_utils import mm_to_pt
|
||||
QLineEdit, QRadioButton)
|
||||
from PyQt6.QtCore import Qt
|
||||
from app.engine.pdf_utils import mm_to_pt, pt_to_mm
|
||||
|
||||
|
||||
class MaskingDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, page_width_pt=595, page_height_pt=842, total_pages=1,
|
||||
current_page=0, viewer=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Masquage (ruban blanc)")
|
||||
self.setMinimumWidth(450)
|
||||
self.setFixedWidth(380)
|
||||
self.setModal(False)
|
||||
self.setWindowFlags(self.windowFlags() | Qt.WindowType.Tool)
|
||||
self._page_w = page_width_pt
|
||||
self._page_h = page_height_pt
|
||||
self._total_pages = total_pages
|
||||
self._current_page = current_page
|
||||
self._viewer = viewer
|
||||
self._rectangles = []
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Definition rectangle
|
||||
grp_rect = QGroupBox("Definir un rectangle de masquage (mm)")
|
||||
rl = QHBoxLayout(grp_rect)
|
||||
# Info
|
||||
pw_mm = pt_to_mm(self._page_w)
|
||||
ph_mm = pt_to_mm(self._page_h)
|
||||
info = QLabel(f"Page : {pw_mm:.1f} x {ph_mm:.1f} mm")
|
||||
info.setStyleSheet("color: #888; font-size: 11px;")
|
||||
layout.addWidget(info)
|
||||
|
||||
rl.addWidget(QLabel("X:"))
|
||||
# Bouton selection visuelle
|
||||
if self._viewer:
|
||||
sel_layout = QHBoxLayout()
|
||||
self.btn_select = QPushButton("Selectionner la zone sur l'apercu")
|
||||
self.btn_select.setCheckable(True)
|
||||
self.btn_select.setStyleSheet("""
|
||||
QPushButton { background: #00b4d8; color: white; padding: 6px 14px;
|
||||
border-radius: 6px; font-weight: bold; font-size: 11px; }
|
||||
QPushButton:checked { background: #e74c3c; }
|
||||
QPushButton:hover { background: #0096b7; }
|
||||
QPushButton:checked:hover { background: #c0392b; }
|
||||
""")
|
||||
self.btn_select.toggled.connect(self._toggle_select_mode)
|
||||
sel_layout.addWidget(self.btn_select)
|
||||
sel_layout.addStretch()
|
||||
layout.addLayout(sel_layout)
|
||||
|
||||
self._viewer.selection_changed.connect(self._on_selection_changed)
|
||||
|
||||
# Coordonnees manuelles
|
||||
grp_rect = QGroupBox("Zone de masquage (mm)")
|
||||
rl = QVBoxLayout(grp_rect)
|
||||
|
||||
row1 = QHBoxLayout()
|
||||
row1.addWidget(QLabel("X :"))
|
||||
self.spin_x = QDoubleSpinBox()
|
||||
self.spin_x.setRange(0, 1000)
|
||||
self.spin_x.setRange(0, 2000)
|
||||
self.spin_x.setSuffix(" mm")
|
||||
rl.addWidget(self.spin_x)
|
||||
|
||||
rl.addWidget(QLabel("Y:"))
|
||||
row1.addWidget(self.spin_x)
|
||||
row1.addWidget(QLabel("Y :"))
|
||||
self.spin_y = QDoubleSpinBox()
|
||||
self.spin_y.setRange(0, 1000)
|
||||
self.spin_y.setRange(0, 2000)
|
||||
self.spin_y.setSuffix(" mm")
|
||||
rl.addWidget(self.spin_y)
|
||||
row1.addWidget(self.spin_y)
|
||||
rl.addLayout(row1)
|
||||
|
||||
rl.addWidget(QLabel("L:"))
|
||||
row2 = QHBoxLayout()
|
||||
row2.addWidget(QLabel("Largeur :"))
|
||||
self.spin_w = QDoubleSpinBox()
|
||||
self.spin_w.setRange(1, 1000)
|
||||
self.spin_w.setRange(1, 2000)
|
||||
self.spin_w.setValue(50)
|
||||
self.spin_w.setSuffix(" mm")
|
||||
rl.addWidget(self.spin_w)
|
||||
|
||||
rl.addWidget(QLabel("H:"))
|
||||
row2.addWidget(self.spin_w)
|
||||
row2.addWidget(QLabel("Hauteur :"))
|
||||
self.spin_h = QDoubleSpinBox()
|
||||
self.spin_h.setRange(1, 1000)
|
||||
self.spin_h.setRange(1, 2000)
|
||||
self.spin_h.setValue(10)
|
||||
self.spin_h.setSuffix(" mm")
|
||||
rl.addWidget(self.spin_h)
|
||||
row2.addWidget(self.spin_h)
|
||||
rl.addLayout(row2)
|
||||
|
||||
layout.addWidget(grp_rect)
|
||||
|
||||
# Boutons ajouter/supprimer
|
||||
# Bouton ajouter
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_add = QPushButton("Ajouter")
|
||||
btn_add.clicked.connect(self._add_rect)
|
||||
btn_layout.addWidget(btn_add)
|
||||
self.btn_add = QPushButton("Ajouter ce masque")
|
||||
self.btn_add.setStyleSheet("""
|
||||
QPushButton { background: #2ecc71; color: white; padding: 6px 14px;
|
||||
border-radius: 6px; font-weight: bold; }
|
||||
QPushButton:hover { background: #27ae60; }
|
||||
""")
|
||||
self.btn_add.clicked.connect(self._add_rect)
|
||||
btn_layout.addWidget(self.btn_add)
|
||||
|
||||
btn_del = QPushButton("Supprimer")
|
||||
btn_del.setStyleSheet("""
|
||||
QPushButton { background: #e8e8e8; color: #555; padding: 6px 14px;
|
||||
border-radius: 6px; border: 1px solid #ccc; }
|
||||
QPushButton:hover { background: #ddd; }
|
||||
""")
|
||||
btn_del.clicked.connect(self._del_rect)
|
||||
btn_layout.addWidget(btn_del)
|
||||
btn_layout.addStretch()
|
||||
@@ -63,15 +111,31 @@ class MaskingDialog(QDialog):
|
||||
|
||||
# Liste des rectangles
|
||||
self.list_rects = QListWidget()
|
||||
self.list_rects.setMaximumHeight(100)
|
||||
layout.addWidget(self.list_rects)
|
||||
|
||||
# Appliquer a
|
||||
grp_apply = QGroupBox("Appliquer a")
|
||||
al = QHBoxLayout(grp_apply)
|
||||
self.combo_apply = QComboBox()
|
||||
self.combo_apply.addItems(["Toutes les pages", "Pages paires", "Pages impaires"])
|
||||
al.addWidget(self.combo_apply)
|
||||
al = QVBoxLayout(grp_apply)
|
||||
self.rb_current = QRadioButton(f"Page courante ({self._current_page + 1})")
|
||||
self.rb_current.setChecked(True)
|
||||
self.rb_all = QRadioButton("Toutes les pages")
|
||||
self.rb_even = QRadioButton("Pages paires")
|
||||
self.rb_odd = QRadioButton("Pages impaires")
|
||||
self.rb_range = QRadioButton("Plage de pages :")
|
||||
al.addWidget(self.rb_current)
|
||||
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()
|
||||
self.edit_range.setPlaceholderText("ex: 1-3, 5, 8-10")
|
||||
self.edit_range.setEnabled(False)
|
||||
range_layout.addWidget(self.edit_range)
|
||||
al.addLayout(range_layout)
|
||||
layout.addWidget(grp_apply)
|
||||
self.rb_range.toggled.connect(self.edit_range.setEnabled)
|
||||
|
||||
# Boutons
|
||||
buttons = QDialogButtonBox(
|
||||
@@ -81,32 +145,128 @@ class MaskingDialog(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("Mode selection actif — dessiner la zone")
|
||||
else:
|
||||
self.btn_select.setText("Selectionner la zone sur l'apercu")
|
||||
|
||||
def _on_selection_changed(self, left, top, right, bottom):
|
||||
"""Recoit les ratios de selection du viewer."""
|
||||
pw_mm = pt_to_mm(self._page_w)
|
||||
ph_mm = pt_to_mm(self._page_h)
|
||||
|
||||
x_mm = left * pw_mm
|
||||
y_mm = top * ph_mm
|
||||
w_mm = (right - left) * pw_mm
|
||||
h_mm = (bottom - top) * ph_mm
|
||||
|
||||
if w_mm > 0 and h_mm > 0:
|
||||
self.spin_x.blockSignals(True)
|
||||
self.spin_y.blockSignals(True)
|
||||
self.spin_w.blockSignals(True)
|
||||
self.spin_h.blockSignals(True)
|
||||
self.spin_x.setValue(x_mm)
|
||||
self.spin_y.setValue(y_mm)
|
||||
self.spin_w.setValue(w_mm)
|
||||
self.spin_h.setValue(h_mm)
|
||||
self.spin_x.blockSignals(False)
|
||||
self.spin_y.blockSignals(False)
|
||||
self.spin_w.blockSignals(False)
|
||||
self.spin_h.blockSignals(False)
|
||||
|
||||
def _add_rect(self):
|
||||
x_mm = self.spin_x.value()
|
||||
y_mm = self.spin_y.value()
|
||||
w_mm = self.spin_w.value()
|
||||
h_mm = self.spin_h.value()
|
||||
|
||||
# Convertir Y : dans le viewer le Y part du haut, dans le PDF du bas
|
||||
page_h_mm = pt_to_mm(self._page_h)
|
||||
pdf_y_mm = page_h_mm - y_mm - h_mm
|
||||
|
||||
rect = {
|
||||
"x": mm_to_pt(self.spin_x.value()),
|
||||
"y": mm_to_pt(self.spin_y.value()),
|
||||
"width": mm_to_pt(self.spin_w.value()),
|
||||
"height": mm_to_pt(self.spin_h.value()),
|
||||
"x": mm_to_pt(x_mm),
|
||||
"y": mm_to_pt(pdf_y_mm),
|
||||
"width": mm_to_pt(w_mm),
|
||||
"height": mm_to_pt(h_mm),
|
||||
}
|
||||
self._rectangles.append(rect)
|
||||
self.list_rects.addItem(
|
||||
f"X={self.spin_x.value():.1f} Y={self.spin_y.value():.1f} "
|
||||
f"L={self.spin_w.value():.1f} H={self.spin_h.value():.1f} mm"
|
||||
f"X={x_mm:.1f} Y={y_mm:.1f} L={w_mm:.1f} H={h_mm:.1f} mm"
|
||||
)
|
||||
|
||||
# Nettoyer la selection pour en dessiner une autre
|
||||
if self._viewer:
|
||||
self._viewer.clear_selection()
|
||||
|
||||
def _del_rect(self):
|
||||
row = self.list_rects.currentRow()
|
||||
if row >= 0:
|
||||
self.list_rects.takeItem(row)
|
||||
self._rectangles.pop(row)
|
||||
|
||||
def _parse_range(self):
|
||||
text = self.edit_range.text().strip()
|
||||
if not text:
|
||||
return []
|
||||
indices = []
|
||||
for part in text.split(","):
|
||||
part = part.strip()
|
||||
if "-" in part:
|
||||
try:
|
||||
start, end = part.split("-", 1)
|
||||
for p in range(int(start), int(end) + 1):
|
||||
if 1 <= p <= self._total_pages:
|
||||
indices.append(p - 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
p = int(part)
|
||||
if 1 <= p <= self._total_pages:
|
||||
indices.append(p - 1)
|
||||
except ValueError:
|
||||
pass
|
||||
return sorted(set(indices))
|
||||
|
||||
def closeEvent(self, event):
|
||||
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):
|
||||
apply_map = {
|
||||
"Toutes les pages": "all",
|
||||
"Pages paires": "even",
|
||||
"Pages impaires": "odd"
|
||||
}
|
||||
if self._viewer:
|
||||
self._viewer.set_select_mode(False)
|
||||
self._viewer.clear_selection()
|
||||
|
||||
# Determiner apply_to
|
||||
if self.rb_current.isChecked():
|
||||
apply_to = self._current_page + 1 # 1-based pour le moteur
|
||||
elif self.rb_all.isChecked():
|
||||
apply_to = "all"
|
||||
elif self.rb_even.isChecked():
|
||||
apply_to = "even"
|
||||
elif self.rb_odd.isChecked():
|
||||
apply_to = "odd"
|
||||
else:
|
||||
# Plage personnalisee
|
||||
pages = self._parse_range()
|
||||
if pages:
|
||||
apply_to = pages # liste d'indices 0-based
|
||||
else:
|
||||
apply_to = "all"
|
||||
|
||||
return {
|
||||
"rectangles": self._rectangles,
|
||||
"apply_to": apply_map[self.combo_apply.currentText()],
|
||||
"apply_to": apply_to,
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QComboBox, QSpinBox, QLineEdit,
|
||||
QDialogButtonBox, QDoubleSpinBox)
|
||||
QDialogButtonBox, QDoubleSpinBox, QPushButton,
|
||||
QColorDialog)
|
||||
from PyQt6.QtGui import QColor
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
|
||||
@@ -11,6 +13,7 @@ class PageNumbersDialog(QDialog):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Numerotation de pages")
|
||||
self.setMinimumWidth(400)
|
||||
self._color = QColor(0, 0, 0)
|
||||
self._setup_ui()
|
||||
|
||||
def _setup_ui(self):
|
||||
@@ -45,6 +48,17 @@ class PageNumbersDialog(QDialog):
|
||||
fl.addWidget(self.spin_size)
|
||||
layout.addWidget(grp_font)
|
||||
|
||||
# Couleur
|
||||
color_layout = QHBoxLayout()
|
||||
color_layout.addWidget(QLabel("Couleur :"))
|
||||
self.btn_color = QPushButton()
|
||||
self.btn_color.setFixedSize(60, 28)
|
||||
self._update_color_button()
|
||||
self.btn_color.clicked.connect(self._pick_color)
|
||||
color_layout.addWidget(self.btn_color)
|
||||
color_layout.addStretch()
|
||||
layout.addLayout(color_layout)
|
||||
|
||||
# Texte
|
||||
grp_text = QGroupBox("Texte")
|
||||
tl = QHBoxLayout(grp_text)
|
||||
@@ -100,6 +114,17 @@ class PageNumbersDialog(QDialog):
|
||||
buttons.rejected.connect(self.reject)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
def _pick_color(self):
|
||||
color = QColorDialog.getColor(self._color, self, "Couleur de la numerotation")
|
||||
if color.isValid():
|
||||
self._color = color
|
||||
self._update_color_button()
|
||||
|
||||
def _update_color_button(self):
|
||||
self.btn_color.setStyleSheet(
|
||||
f"background-color: {self._color.name()}; border: 1px solid #999; border-radius: 4px;"
|
||||
)
|
||||
|
||||
def get_settings(self):
|
||||
apply_map = {
|
||||
"Toutes les pages": "all",
|
||||
@@ -110,7 +135,7 @@ class PageNumbersDialog(QDialog):
|
||||
"position": self.combo_pos.currentText(),
|
||||
"font_name": self.combo_font.currentText(),
|
||||
"font_size": self.spin_size.value(),
|
||||
"color": (0, 0, 0),
|
||||
"color": (self._color.redF(), self._color.greenF(), self._color.blueF()),
|
||||
"prefix": self.edit_prefix.text(),
|
||||
"suffix": self.edit_suffix.text(),
|
||||
"start_number": self.spin_start.value(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Dialogue Trim & Shift."""
|
||||
"""Dialogue Decalage du contenu."""
|
||||
|
||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
||||
QGroupBox, QDoubleSpinBox, QCheckBox,
|
||||
@@ -9,7 +9,7 @@ from app.engine.pdf_utils import mm_to_pt
|
||||
class TrimShiftDialog(QDialog):
|
||||
def __init__(self, total_pages=1, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Trim && Shift — Decaler le contenu")
|
||||
self.setWindowTitle("Decalage du contenu")
|
||||
self.setMinimumWidth(420)
|
||||
self._total_pages = total_pages
|
||||
self._setup_ui()
|
||||
@@ -55,10 +55,10 @@ class TrimShiftDialog(QDialog):
|
||||
self.chk_different.toggled.connect(self.spin_x_even.setEnabled)
|
||||
self.chk_different.toggled.connect(self.spin_y_even.setEnabled)
|
||||
|
||||
# Creep
|
||||
grp_creep = QGroupBox("Compensation de creep (livrets)")
|
||||
# Chasse (creep)
|
||||
grp_creep = QGroupBox("Compensation de chasse (livrets)")
|
||||
cl = QHBoxLayout(grp_creep)
|
||||
cl.addWidget(QLabel("Creep max :"))
|
||||
cl.addWidget(QLabel("Chasse max :"))
|
||||
self.spin_creep = QDoubleSpinBox()
|
||||
self.spin_creep.setRange(0, 20)
|
||||
self.spin_creep.setSuffix(" mm")
|
||||
|
||||
Reference in New Issue
Block a user