"""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, }