Initial commit — Copydev Imposing Tool

Outil d'imposition PDF desktop (PyQt6) remplacant Quite Imposing Plus.
9 fonctions : livret, n-up, step & repeat, imposition manuelle,
jointure pages, pages blanches, numerotation, masquage, fonds perdus.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-20 10:54:02 +01:00
commit e3dfd90933
30 changed files with 2364 additions and 0 deletions

0
app/dialogs/__init__.py Normal file
View File

View File

@@ -0,0 +1,53 @@
"""Dialogue Definition des fonds perdus."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QDoubleSpinBox, QDialogButtonBox)
class BleedsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Fonds perdus (Bleeds)")
self.setMinimumWidth(350)
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel(
"Definir la zone de fond perdu autour de chaque page.\n"
"Cette zone sera prise en compte par les fonctions N-Up et Repetition."
))
grp = QGroupBox("Fonds perdus (mm)")
gl = QVBoxLayout(grp)
self.spins = {}
for label in ["Haut", "Bas", "Gauche", "Droite"]:
hl = QHBoxLayout()
hl.addWidget(QLabel(f"{label} :"))
spin = QDoubleSpinBox()
spin.setRange(0, 50)
spin.setValue(3)
spin.setSuffix(" mm")
hl.addWidget(spin)
hl.addStretch()
gl.addLayout(hl)
self.spins[label.lower()] = spin
layout.addWidget(grp)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
return {
"bleed_top": self.spins["haut"].value(),
"bleed_bottom": self.spins["bas"].value(),
"bleed_left": self.spins["gauche"].value(),
"bleed_right": self.spins["droite"].value(),
}

View File

@@ -0,0 +1,104 @@
"""Dialogue Creer un livret."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QComboBox, QGroupBox, QRadioButton, QSpinBox,
QDialogButtonBox, QDoubleSpinBox)
from app.engine.pdf_utils import PAGE_SIZES
class BookletDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Creer un livret")
self.setMinimumWidth(400)
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Type de reliure
grp_binding = QGroupBox("Type de reliure")
bl = QVBoxLayout(grp_binding)
self.rb_saddle = QRadioButton("Piqure a cheval (livret agrafe)")
self.rb_saddle.setChecked(True)
self.rb_perfect = QRadioButton("Dos carre colle (livre)")
self.rb_continuous = QRadioButton("Continu (pages sequentielles)")
self.rb_cut_stacks = QRadioButton("Cut stacks (couper et empiler)")
bl.addWidget(self.rb_saddle)
bl.addWidget(self.rb_perfect)
bl.addWidget(self.rb_continuous)
bl.addWidget(self.rb_cut_stacks)
layout.addWidget(grp_binding)
# Taille signature (perfect bound)
sig_layout = QHBoxLayout()
sig_layout.addWidget(QLabel("Pages par cahier :"))
self.spin_signature = QSpinBox()
self.spin_signature.setRange(4, 128)
self.spin_signature.setValue(32)
self.spin_signature.setSingleStep(4)
self.spin_signature.setEnabled(False)
sig_layout.addWidget(self.spin_signature)
sig_layout.addStretch()
layout.addLayout(sig_layout)
self.rb_perfect.toggled.connect(self.spin_signature.setEnabled)
# Taille de feuille
grp_sheet = QGroupBox("Taille de la feuille")
sl = QHBoxLayout(grp_sheet)
sl.addWidget(QLabel("Format :"))
self.combo_sheet = QComboBox()
self.combo_sheet.addItem("Automatique (2x page)")
for name in PAGE_SIZES:
self.combo_sheet.addItem(name)
sl.addWidget(self.combo_sheet)
layout.addWidget(grp_sheet)
# Alignement
grp_align = QGroupBox("Alignement")
al = QVBoxLayout(grp_align)
self.rb_center = QRadioButton("Centrer")
self.rb_center.setChecked(True)
self.rb_pull = QRadioButton("Vers le dos (centre)")
self.rb_bl = QRadioButton("Bas-gauche")
al.addWidget(self.rb_center)
al.addWidget(self.rb_pull)
al.addWidget(self.rb_bl)
layout.addWidget(grp_align)
# Boutons OK/Annuler
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
"""Retourner les parametres selectionnes."""
if self.rb_saddle.isChecked():
binding = "saddle_stitch"
elif self.rb_perfect.isChecked():
binding = "perfect_bound"
elif self.rb_continuous.isChecked():
binding = "continuous"
else:
binding = "cut_stacks"
sheet_text = self.combo_sheet.currentText()
sheet_size = PAGE_SIZES.get(sheet_text, None)
if self.rb_center.isChecked():
alignment = "center"
elif self.rb_pull.isChecked():
alignment = "pull_center"
else:
alignment = "bottom_left"
return {
"binding": binding,
"sheet_size": sheet_size,
"signature_size": self.spin_signature.value(),
"alignment": alignment,
}

View File

@@ -0,0 +1,82 @@
"""Dialogue Inserer des pages blanches."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QRadioButton, QSpinBox,
QDialogButtonBox)
class InsertBlankDialog(QDialog):
def __init__(self, total_pages=1, parent=None):
super().__init__(parent)
self.setWindowTitle("Inserer des pages blanches")
self.setMinimumWidth(400)
self._total_pages = total_pages
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
grp = QGroupBox("Mode d'insertion")
gl = QVBoxLayout(grp)
self.rb_after = QRadioButton("Apres la page :")
self.rb_after.setChecked(True)
after_layout = QHBoxLayout()
after_layout.addWidget(self.rb_after)
self.spin_pos = QSpinBox()
self.spin_pos.setRange(1, self._total_pages)
self.spin_pos.setValue(1)
after_layout.addWidget(self.spin_pos)
after_layout.addStretch()
gl.addLayout(after_layout)
self.rb_before = QRadioButton("Avant la page selectionnee")
gl.addWidget(self.rb_before)
every_layout = QHBoxLayout()
self.rb_every = QRadioButton("Toutes les")
every_layout.addWidget(self.rb_every)
self.spin_every = QSpinBox()
self.spin_every.setRange(1, 100)
self.spin_every.setValue(2)
self.spin_every.setEnabled(False)
every_layout.addWidget(self.spin_every)
every_layout.addWidget(QLabel("pages"))
every_layout.addStretch()
gl.addLayout(every_layout)
pad_layout = QHBoxLayout()
self.rb_pad = QRadioButton("Completer au multiple de")
pad_layout.addWidget(self.rb_pad)
self.spin_multiple = QSpinBox()
self.spin_multiple.setRange(2, 32)
self.spin_multiple.setValue(4)
self.spin_multiple.setEnabled(False)
pad_layout.addWidget(self.spin_multiple)
pad_layout.addWidget(QLabel("pages"))
pad_layout.addStretch()
gl.addLayout(pad_layout)
layout.addWidget(grp)
# Activer/desactiver les spinbox
self.rb_every.toggled.connect(self.spin_every.setEnabled)
self.rb_pad.toggled.connect(self.spin_multiple.setEnabled)
# Boutons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
if self.rb_after.isChecked():
return {"mode": "after", "position": self.spin_pos.value()}
elif self.rb_before.isChecked():
return {"mode": "before", "position": self.spin_pos.value()}
elif self.rb_every.isChecked():
return {"mode": "every_n", "every_n": self.spin_every.value()}
else:
return {"mode": "pad_to_multiple", "target_multiple": self.spin_multiple.value()}

View File

@@ -0,0 +1,38 @@
"""Dialogue Joindre deux pages."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QLabel, QGroupBox,
QRadioButton, QDialogButtonBox)
class JoinPagesDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Joindre deux pages")
self.setMinimumWidth(300)
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.addWidget(QLabel("Les pages seront jointes par paires (1+2, 3+4, ...)."))
grp = QGroupBox("Direction")
gl = QVBoxLayout(grp)
self.rb_horizontal = QRadioButton("Cote a cote (horizontal)")
self.rb_horizontal.setChecked(True)
self.rb_vertical = QRadioButton("Dessus/dessous (vertical)")
gl.addWidget(self.rb_horizontal)
gl.addWidget(self.rb_vertical)
layout.addWidget(grp)
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
return {
"direction": "horizontal" if self.rb_horizontal.isChecked() else "vertical"
}

View File

@@ -0,0 +1,122 @@
"""Dialogue Imposition manuelle."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QComboBox, QSpinBox, QDoubleSpinBox,
QDialogButtonBox, QPushButton, QListWidget,
QCheckBox)
from app.engine.pdf_utils import PAGE_SIZES, mm_to_pt
class ManualImposeDialog(QDialog):
def __init__(self, total_pages=1, parent=None):
super().__init__(parent)
self.setWindowTitle("Imposition manuelle")
self.setMinimumWidth(500)
self._total_pages = total_pages
self._placements = []
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Taille feuille
grp_sheet = QGroupBox("Taille de la feuille cible")
sl = QHBoxLayout(grp_sheet)
sl.addWidget(QLabel("Format :"))
self.combo_sheet = QComboBox()
for name in PAGE_SIZES:
self.combo_sheet.addItem(name)
self.combo_sheet.setCurrentText("A3 Paysage")
sl.addWidget(self.combo_sheet)
layout.addWidget(grp_sheet)
# Placement
grp_place = QGroupBox("Placer une page")
pl = QHBoxLayout(grp_place)
pl.addWidget(QLabel("Page:"))
self.spin_page = QSpinBox()
self.spin_page.setRange(1, self._total_pages)
pl.addWidget(self.spin_page)
pl.addWidget(QLabel("X:"))
self.spin_x = QDoubleSpinBox()
self.spin_x.setRange(0, 2000)
self.spin_x.setSuffix(" mm")
pl.addWidget(self.spin_x)
pl.addWidget(QLabel("Y:"))
self.spin_y = QDoubleSpinBox()
self.spin_y.setRange(0, 2000)
self.spin_y.setSuffix(" mm")
pl.addWidget(self.spin_y)
pl.addWidget(QLabel("Rot:"))
self.combo_rot = QComboBox()
self.combo_rot.addItems(["0", "90", "180", "270"])
pl.addWidget(self.combo_rot)
pl.addWidget(QLabel("Ech:"))
self.spin_scale = QDoubleSpinBox()
self.spin_scale.setRange(0.1, 10.0)
self.spin_scale.setValue(1.0)
self.spin_scale.setSingleStep(0.1)
pl.addWidget(self.spin_scale)
layout.addWidget(grp_place)
# Boutons
btn_layout = QHBoxLayout()
btn_add = QPushButton("Ajouter")
btn_add.clicked.connect(self._add_placement)
btn_layout.addWidget(btn_add)
btn_del = QPushButton("Supprimer")
btn_del.clicked.connect(self._del_placement)
btn_layout.addWidget(btn_del)
btn_layout.addStretch()
layout.addLayout(btn_layout)
# Liste
self.list_placements = QListWidget()
layout.addWidget(self.list_placements)
# Crop marks
self.chk_crop = QCheckBox("Reperes de coupe")
layout.addWidget(self.chk_crop)
# Boutons OK/Annuler
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def _add_placement(self):
p = {
"page": self.spin_page.value(),
"x": mm_to_pt(self.spin_x.value()),
"y": mm_to_pt(self.spin_y.value()),
"rotation": int(self.combo_rot.currentText()),
"scale": self.spin_scale.value(),
}
self._placements.append(p)
self.list_placements.addItem(
f"Page {p['page']} - X={self.spin_x.value():.1f} Y={self.spin_y.value():.1f} mm "
f"Rot={self.combo_rot.currentText()} Ech={self.spin_scale.value():.1f}"
)
def _del_placement(self):
row = self.list_placements.currentRow()
if row >= 0:
self.list_placements.takeItem(row)
self._placements.pop(row)
def get_settings(self):
sheet_text = self.combo_sheet.currentText()
sheet_size = PAGE_SIZES[sheet_text]
return {
"sheet_size": sheet_size,
"placements": self._placements,
"crop_marks": self.chk_crop.isChecked(),
}

View File

@@ -0,0 +1,112 @@
"""Dialogue Masquage (rectangles blancs)."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QComboBox, QDoubleSpinBox,
QDialogButtonBox, QPushButton, QListWidget,
QListWidgetItem)
from app.engine.pdf_utils import mm_to_pt
class MaskingDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Masquage (ruban blanc)")
self.setMinimumWidth(450)
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)
rl.addWidget(QLabel("X:"))
self.spin_x = QDoubleSpinBox()
self.spin_x.setRange(0, 1000)
self.spin_x.setSuffix(" mm")
rl.addWidget(self.spin_x)
rl.addWidget(QLabel("Y:"))
self.spin_y = QDoubleSpinBox()
self.spin_y.setRange(0, 1000)
self.spin_y.setSuffix(" mm")
rl.addWidget(self.spin_y)
rl.addWidget(QLabel("L:"))
self.spin_w = QDoubleSpinBox()
self.spin_w.setRange(1, 1000)
self.spin_w.setValue(50)
self.spin_w.setSuffix(" mm")
rl.addWidget(self.spin_w)
rl.addWidget(QLabel("H:"))
self.spin_h = QDoubleSpinBox()
self.spin_h.setRange(1, 1000)
self.spin_h.setValue(10)
self.spin_h.setSuffix(" mm")
rl.addWidget(self.spin_h)
layout.addWidget(grp_rect)
# Boutons ajouter/supprimer
btn_layout = QHBoxLayout()
btn_add = QPushButton("Ajouter")
btn_add.clicked.connect(self._add_rect)
btn_layout.addWidget(btn_add)
btn_del = QPushButton("Supprimer")
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()
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)
layout.addWidget(grp_apply)
# Boutons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def _add_rect(self):
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()),
}
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"
)
def _del_rect(self):
row = self.list_rects.currentRow()
if row >= 0:
self.list_rects.takeItem(row)
self._rectangles.pop(row)
def get_settings(self):
apply_map = {
"Toutes les pages": "all",
"Pages paires": "even",
"Pages impaires": "odd"
}
return {
"rectangles": self._rectangles,
"apply_to": apply_map[self.combo_apply.currentText()],
}

131
app/dialogs/nup_dialog.py Normal file
View File

@@ -0,0 +1,131 @@
"""Dialogue N pages par feuille."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QComboBox, QGroupBox, QSpinBox, QCheckBox,
QDialogButtonBox, QDoubleSpinBox, QFileDialog)
from app.engine.pdf_utils import PAGE_SIZES, mm_to_pt
class NupDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("N pages par feuille")
self.setMinimumWidth(450)
self._bg_path = None
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Grille
grp_grid = QGroupBox("Disposition")
gl = QHBoxLayout(grp_grid)
gl.addWidget(QLabel("Colonnes :"))
self.spin_cols = QSpinBox()
self.spin_cols.setRange(1, 10)
self.spin_cols.setValue(2)
gl.addWidget(self.spin_cols)
gl.addWidget(QLabel("Lignes :"))
self.spin_rows = QSpinBox()
self.spin_rows.setRange(1, 10)
self.spin_rows.setValue(2)
gl.addWidget(self.spin_rows)
layout.addWidget(grp_grid)
# Taille feuille
grp_sheet = QGroupBox("Taille de la feuille")
sl = QHBoxLayout(grp_sheet)
sl.addWidget(QLabel("Format :"))
self.combo_sheet = QComboBox()
for name in PAGE_SIZES:
self.combo_sheet.addItem(name)
self.combo_sheet.setCurrentText("A4 Paysage")
sl.addWidget(self.combo_sheet)
layout.addWidget(grp_sheet)
# Marges
grp_margins = QGroupBox("Marges (mm)")
ml = QHBoxLayout(grp_margins)
self.margins = {}
for label in ["Haut", "Bas", "Gauche", "Droite"]:
ml.addWidget(QLabel(f"{label}:"))
spin = QDoubleSpinBox()
spin.setRange(0, 100)
spin.setValue(5)
spin.setSuffix(" mm")
ml.addWidget(spin)
self.margins[label.lower()] = spin
layout.addWidget(grp_margins)
# Gouttiere
grp_gutter = QGroupBox("Gouttiere (mm)")
gutl = QHBoxLayout(grp_gutter)
gutl.addWidget(QLabel("Horizontale :"))
self.spin_gutter_h = QDoubleSpinBox()
self.spin_gutter_h.setRange(0, 50)
self.spin_gutter_h.setValue(0)
self.spin_gutter_h.setSuffix(" mm")
gutl.addWidget(self.spin_gutter_h)
gutl.addWidget(QLabel("Verticale :"))
self.spin_gutter_v = QDoubleSpinBox()
self.spin_gutter_v.setRange(0, 50)
self.spin_gutter_v.setValue(0)
self.spin_gutter_v.setSuffix(" mm")
gutl.addWidget(self.spin_gutter_v)
layout.addWidget(grp_gutter)
# Options
grp_opts = QGroupBox("Options")
ol = QVBoxLayout(grp_opts)
self.chk_fit = QCheckBox("Ajuster les pages a la cellule")
self.chk_fit.setChecked(True)
ol.addWidget(self.chk_fit)
self.chk_crop = QCheckBox("Reperes de coupe")
ol.addWidget(self.chk_crop)
bg_layout = QHBoxLayout()
self.chk_bg = QCheckBox("Fond PDF :")
bg_layout.addWidget(self.chk_bg)
self.lbl_bg = QLabel("Aucun")
bg_layout.addWidget(self.lbl_bg)
from PyQt6.QtWidgets import QPushButton
btn_bg = QPushButton("Choisir...")
btn_bg.clicked.connect(self._choose_bg)
bg_layout.addWidget(btn_bg)
bg_layout.addStretch()
ol.addLayout(bg_layout)
layout.addWidget(grp_opts)
# Boutons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def _choose_bg(self):
path, _ = QFileDialog.getOpenFileName(self, "Choisir un fond PDF", "", "PDF (*.pdf)")
if path:
self._bg_path = path
self.lbl_bg.setText(path.split("/")[-1].split("\\")[-1])
self.chk_bg.setChecked(True)
def get_settings(self):
sheet_text = self.combo_sheet.currentText()
sheet_size = PAGE_SIZES[sheet_text]
return {
"cols": self.spin_cols.value(),
"rows": self.spin_rows.value(),
"sheet_size": sheet_size,
"margin_top": mm_to_pt(self.margins["haut"].value()),
"margin_bottom": mm_to_pt(self.margins["bas"].value()),
"margin_left": mm_to_pt(self.margins["gauche"].value()),
"margin_right": mm_to_pt(self.margins["droite"].value()),
"gutter_h": mm_to_pt(self.spin_gutter_h.value()),
"gutter_v": mm_to_pt(self.spin_gutter_v.value()),
"scale_mode": "fit" if self.chk_fit.isChecked() else "none",
"crop_marks": self.chk_crop.isChecked(),
"background_path": self._bg_path if self.chk_bg.isChecked() else None,
}

View File

@@ -0,0 +1,120 @@
"""Dialogue Numerotation de pages."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QGroupBox, QComboBox, QSpinBox, QLineEdit,
QDialogButtonBox, QDoubleSpinBox)
from reportlab.lib.units import mm
class PageNumbersDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Numerotation de pages")
self.setMinimumWidth(400)
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Position
grp_pos = QGroupBox("Position")
pl = QHBoxLayout(grp_pos)
pl.addWidget(QLabel("Emplacement :"))
self.combo_pos = QComboBox()
positions = [
"haut-gauche", "haut-centre", "haut-droite",
"milieu-gauche", "milieu-centre", "milieu-droite",
"bas-gauche", "bas-centre", "bas-droite"
]
self.combo_pos.addItems(positions)
self.combo_pos.setCurrentText("bas-centre")
pl.addWidget(self.combo_pos)
layout.addWidget(grp_pos)
# Police
grp_font = QGroupBox("Police")
fl = QHBoxLayout(grp_font)
fl.addWidget(QLabel("Police :"))
self.combo_font = QComboBox()
self.combo_font.addItems(["Helvetica", "Times-Roman", "Courier"])
fl.addWidget(self.combo_font)
fl.addWidget(QLabel("Taille :"))
self.spin_size = QSpinBox()
self.spin_size.setRange(4, 72)
self.spin_size.setValue(10)
fl.addWidget(self.spin_size)
layout.addWidget(grp_font)
# Texte
grp_text = QGroupBox("Texte")
tl = QHBoxLayout(grp_text)
tl.addWidget(QLabel("Prefixe :"))
self.edit_prefix = QLineEdit()
self.edit_prefix.setPlaceholderText("ex: Page ")
tl.addWidget(self.edit_prefix)
tl.addWidget(QLabel("Suffixe :"))
self.edit_suffix = QLineEdit()
self.edit_suffix.setPlaceholderText("ex: / 24")
tl.addWidget(self.edit_suffix)
layout.addWidget(grp_text)
# Numero de depart
start_layout = QHBoxLayout()
start_layout.addWidget(QLabel("Commencer a :"))
self.spin_start = QSpinBox()
self.spin_start.setRange(0, 9999)
self.spin_start.setValue(1)
start_layout.addWidget(self.spin_start)
start_layout.addStretch()
layout.addLayout(start_layout)
# Marges
margin_layout = QHBoxLayout()
margin_layout.addWidget(QLabel("Marge X :"))
self.spin_mx = QDoubleSpinBox()
self.spin_mx.setRange(0, 100)
self.spin_mx.setValue(10)
self.spin_mx.setSuffix(" mm")
margin_layout.addWidget(self.spin_mx)
margin_layout.addWidget(QLabel("Marge Y :"))
self.spin_my = QDoubleSpinBox()
self.spin_my.setRange(0, 100)
self.spin_my.setValue(10)
self.spin_my.setSuffix(" mm")
margin_layout.addWidget(self.spin_my)
layout.addLayout(margin_layout)
# 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)
layout.addWidget(grp_apply)
# Boutons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
apply_map = {
"Toutes les pages": "all",
"Pages paires": "even",
"Pages impaires": "odd"
}
return {
"position": self.combo_pos.currentText(),
"font_name": self.combo_font.currentText(),
"font_size": self.spin_size.value(),
"color": (0, 0, 0),
"prefix": self.edit_prefix.text(),
"suffix": self.edit_suffix.text(),
"start_number": self.spin_start.value(),
"margin_x": self.spin_mx.value() * mm,
"margin_y": self.spin_my.value() * mm,
"apply_to": apply_map[self.combo_apply.currentText()],
}

View File

@@ -0,0 +1,95 @@
"""Dialogue Repetition (Step & Repeat)."""
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QComboBox, QGroupBox, QSpinBox, QCheckBox,
QDialogButtonBox, QDoubleSpinBox)
from app.engine.pdf_utils import PAGE_SIZES, mm_to_pt
class StepRepeatDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Repetition (Step & Repeat)")
self.setMinimumWidth(400)
self._setup_ui()
def _setup_ui(self):
layout = QVBoxLayout(self)
# Taille feuille
grp_sheet = QGroupBox("Taille de la feuille")
sl = QHBoxLayout(grp_sheet)
sl.addWidget(QLabel("Format :"))
self.combo_sheet = QComboBox()
for name in PAGE_SIZES:
self.combo_sheet.addItem(name)
self.combo_sheet.setCurrentText("A3 Paysage")
sl.addWidget(self.combo_sheet)
layout.addWidget(grp_sheet)
# Nombre de copies
grp_copies = QGroupBox("Nombre de copies")
cl = QVBoxLayout(grp_copies)
self.chk_auto = QCheckBox("Automatique (remplir la feuille)")
self.chk_auto.setChecked(True)
cl.addWidget(self.chk_auto)
manual = QHBoxLayout()
manual.addWidget(QLabel("Horizontal :"))
self.spin_h = QSpinBox()
self.spin_h.setRange(1, 20)
self.spin_h.setValue(2)
self.spin_h.setEnabled(False)
manual.addWidget(self.spin_h)
manual.addWidget(QLabel("Vertical :"))
self.spin_v = QSpinBox()
self.spin_v.setRange(1, 20)
self.spin_v.setValue(2)
self.spin_v.setEnabled(False)
manual.addWidget(self.spin_v)
cl.addLayout(manual)
layout.addWidget(grp_copies)
self.chk_auto.toggled.connect(lambda checked: self.spin_h.setEnabled(not checked))
self.chk_auto.toggled.connect(lambda checked: self.spin_v.setEnabled(not checked))
# Espacement
grp_spacing = QGroupBox("Espacement (mm)")
spl = QHBoxLayout(grp_spacing)
spl.addWidget(QLabel("Horizontal :"))
self.spin_sp_h = QDoubleSpinBox()
self.spin_sp_h.setRange(0, 50)
self.spin_sp_h.setValue(0)
self.spin_sp_h.setSuffix(" mm")
spl.addWidget(self.spin_sp_h)
spl.addWidget(QLabel("Vertical :"))
self.spin_sp_v = QDoubleSpinBox()
self.spin_sp_v.setRange(0, 50)
self.spin_sp_v.setValue(0)
self.spin_sp_v.setSuffix(" mm")
spl.addWidget(self.spin_sp_v)
layout.addWidget(grp_spacing)
# Options
self.chk_crop = QCheckBox("Reperes de coupe")
layout.addWidget(self.chk_crop)
# Boutons
buttons = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
def get_settings(self):
sheet_text = self.combo_sheet.currentText()
sheet_size = PAGE_SIZES[sheet_text]
return {
"sheet_size": sheet_size,
"copies_h": None if self.chk_auto.isChecked() else self.spin_h.value(),
"copies_v": None if self.chk_auto.isChecked() else self.spin_v.value(),
"spacing_h": mm_to_pt(self.spin_sp_h.value()),
"spacing_v": mm_to_pt(self.spin_sp_v.value()),
"crop_marks": self.chk_crop.isChecked(),
}