Files
copydev-imposing-tool/app/dialogs/masking_dialog.py
Jules 202834a794 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>
2026-03-29 22:12:54 +02:00

273 lines
9.6 KiB
Python

"""Dialogue Masquage (rectangles blancs) — avec selection visuelle."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QComboBox, QDoubleSpinBox,
QDialogButtonBox, QPushButton, QListWidget,
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, 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.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)
# 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)
# 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, 2000)
self.spin_x.setSuffix(" mm")
row1.addWidget(self.spin_x)
row1.addWidget(QLabel("Y :"))
self.spin_y = QDoubleSpinBox()
self.spin_y.setRange(0, 2000)
self.spin_y.setSuffix(" mm")
row1.addWidget(self.spin_y)
rl.addLayout(row1)
row2 = QHBoxLayout()
row2.addWidget(QLabel("Largeur :"))
self.spin_w = QDoubleSpinBox()
self.spin_w.setRange(1, 2000)
self.spin_w.setValue(50)
self.spin_w.setSuffix(" mm")
row2.addWidget(self.spin_w)
row2.addWidget(QLabel("Hauteur :"))
self.spin_h = QDoubleSpinBox()
self.spin_h.setRange(1, 2000)
self.spin_h.setValue(10)
self.spin_h.setSuffix(" mm")
row2.addWidget(self.spin_h)
rl.addLayout(row2)
layout.addWidget(grp_rect)
# Bouton ajouter
btn_layout = QHBoxLayout()
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()
layout.addLayout(btn_layout)
# 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 = 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(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
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(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={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):
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_to,
}