- Recadrage de pages (Pages > Recadrer) : - Mode marges : retirer X mm de chaque cote - Mode taille finale : definir la taille souhaitee (centrage auto) - Apercu en temps reel de la dimension finale - Appliquer a : page courante, toutes, paires, impaires, plage - Plage de pages personnalisee (ex: 1-3, 5, 8-10) - Impression (Fichier > Imprimer / Ctrl+P) : ouvre le PDF dans le viewer par defaut Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
676 lines
25 KiB
Python
676 lines
25 KiB
Python
"""Fenetre principale de Copydev Imposing Tool — V2."""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import shutil
|
|
from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
QLabel, QFileDialog, QMessageBox, QStatusBar,
|
|
QSplitter, QProgressBar, QApplication, QToolBar)
|
|
from PyQt6.QtCore import Qt, QTimer
|
|
from PyQt6.QtGui import QAction, QIcon, QDragEnterEvent, QDropEvent
|
|
|
|
from app.pdf_viewer import PdfViewer
|
|
from app.page_panel import PagePanel
|
|
|
|
from app.dialogs.booklet_dialog import BookletDialog
|
|
from app.dialogs.nup_dialog import NupDialog
|
|
from app.dialogs.step_repeat_dialog import StepRepeatDialog
|
|
from app.dialogs.manual_impose_dialog import ManualImposeDialog
|
|
from app.dialogs.join_pages_dialog import JoinPagesDialog
|
|
from app.dialogs.insert_blank_dialog import InsertBlankDialog
|
|
from app.dialogs.page_numbers_dialog import PageNumbersDialog
|
|
from app.dialogs.masking_dialog import MaskingDialog
|
|
from app.dialogs.bleeds_dialog import BleedsDialog
|
|
from app.dialogs.crop_marks_dialog import CropMarksDialog
|
|
from app.dialogs.crop_dialog import CropDialog
|
|
|
|
from app.engine.booklet import create_booklet
|
|
from app.engine.nup import nup_pages
|
|
from app.engine.step_repeat import step_and_repeat
|
|
from app.engine.manual_impose import manual_impose
|
|
from app.engine.join_pages import join_pages
|
|
from app.engine.insert_blank import insert_blank_pages
|
|
from app.engine.page_numbers import add_page_numbers
|
|
from app.engine.masking import apply_masking
|
|
from app.engine.bleeds import define_bleeds
|
|
from app.engine.crop_marks import add_crop_marks, remove_crop_marks
|
|
from app.engine.crop import crop_pages
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
from app.updater import VERSION
|
|
self.setWindowTitle(f"CopyDev Imposing Tool v{VERSION} — www.copydev.fr")
|
|
self.setMinimumSize(1100, 750)
|
|
|
|
# Icone
|
|
_base = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
icon_path = os.path.join(_base, "resources", "logo.png")
|
|
if os.path.exists(icon_path):
|
|
self.setWindowIcon(QIcon(icon_path))
|
|
|
|
self._current_file = None
|
|
self._result_file = None
|
|
|
|
# Drag & drop
|
|
self.setAcceptDrops(True)
|
|
|
|
self._setup_menu()
|
|
self._setup_toolbar()
|
|
self._setup_ui()
|
|
self._setup_statusbar()
|
|
|
|
# Style global
|
|
self.setStyleSheet("""
|
|
QMainWindow { background: #1a1a2e; }
|
|
QMenuBar { background: #16213e; color: #e0e0e0; font-size: 13px; padding: 2px; }
|
|
QMenuBar::item:selected { background: #0f3460; }
|
|
QMenu { background: #16213e; color: #e0e0e0; border: 1px solid #333; }
|
|
QMenu::item:selected { background: #0f3460; }
|
|
QMenu::separator { background: #333; height: 1px; margin: 4px 10px; }
|
|
QToolBar { background: #16213e; border: none; spacing: 4px; padding: 2px; }
|
|
QToolBar QToolButton { color: #e0e0e0; padding: 4px 8px; border-radius: 3px; }
|
|
QToolBar QToolButton:hover { background: #0f3460; }
|
|
QStatusBar { background: #0f0f23; color: #888; font-size: 11px; }
|
|
QSplitter::handle { background: #333; width: 3px; }
|
|
""")
|
|
|
|
# ── Drag & Drop ──
|
|
|
|
def dragEnterEvent(self, event: QDragEnterEvent):
|
|
if event.mimeData().hasUrls():
|
|
for url in event.mimeData().urls():
|
|
if url.toLocalFile().lower().endswith('.pdf'):
|
|
event.acceptProposedAction()
|
|
return
|
|
|
|
def dropEvent(self, event: QDropEvent):
|
|
for url in event.mimeData().urls():
|
|
path = url.toLocalFile()
|
|
if path.lower().endswith('.pdf'):
|
|
self._open_pdf(path)
|
|
return
|
|
|
|
# ── Menu ──
|
|
|
|
def _setup_menu(self):
|
|
menubar = self.menuBar()
|
|
|
|
# Fichier
|
|
file_menu = menubar.addMenu("Fichier")
|
|
self._add_action(file_menu, "Ouvrir PDF...", "Ctrl+O", self._open_file)
|
|
self._add_action(file_menu, "Enregistrer le resultat...", "Ctrl+S", self._save_result)
|
|
self._add_action(file_menu, "Imprimer...", "Ctrl+P", self._do_print)
|
|
file_menu.addSeparator()
|
|
self._add_action(file_menu, "Fusionner des PDF...", "Ctrl+M", self._do_merge)
|
|
file_menu.addSeparator()
|
|
self._add_action(file_menu, "Quitter", "Ctrl+Q", self.close)
|
|
|
|
# Imposition
|
|
impose_menu = menubar.addMenu("Imposition")
|
|
self._add_action(impose_menu, "Creer un livret", None, self._do_booklet)
|
|
self._add_action(impose_menu, "N pages par feuille", None, self._do_nup)
|
|
self._add_action(impose_menu, "Repetition (Step && Repeat)", None, self._do_step_repeat)
|
|
self._add_action(impose_menu, "Imposition manuelle", None, self._do_manual_impose)
|
|
self._add_action(impose_menu, "Joindre deux pages", None, self._do_join_pages)
|
|
|
|
# Pages
|
|
pages_menu = menubar.addMenu("Pages")
|
|
self._add_action(pages_menu, "Recadrer...", None, self._do_crop)
|
|
pages_menu.addSeparator()
|
|
self._add_action(pages_menu, "Inserer des pages blanches", None, self._do_insert_blank)
|
|
|
|
# Annotations
|
|
annot_menu = menubar.addMenu("Annotations")
|
|
self._add_action(annot_menu, "Numerotation de pages", None, self._do_page_numbers)
|
|
self._add_action(annot_menu, "Masquage (ruban blanc)", None, self._do_masking)
|
|
annot_menu.addSeparator()
|
|
annot_menu.addSeparator()
|
|
self._add_action(annot_menu, "Reperes de coupe...", None, self._do_crop_marks)
|
|
self._add_action(annot_menu, "Enlever les reperes de coupe", None, self._do_remove_crop_marks)
|
|
annot_menu.addSeparator()
|
|
self._add_action(annot_menu, "Definir les fonds perdus", None, self._do_bleeds)
|
|
self._add_action(annot_menu, "Enlever les fonds perdus", None, self._do_remove_bleeds)
|
|
|
|
# Aide
|
|
help_menu = menubar.addMenu("Aide")
|
|
self._add_action(help_menu, "A propos de CopyDev Imposing Tool", None, self._show_about)
|
|
|
|
def _add_action(self, menu, text, shortcut, callback):
|
|
act = QAction(text, self)
|
|
if shortcut:
|
|
act.setShortcut(shortcut)
|
|
act.triggered.connect(callback)
|
|
menu.addAction(act)
|
|
|
|
# ── Toolbar ──
|
|
|
|
def _setup_toolbar(self):
|
|
tb = QToolBar()
|
|
tb.setMovable(False)
|
|
self.addToolBar(tb)
|
|
|
|
tb.addAction("Ouvrir", self._open_file)
|
|
tb.addAction("Enregistrer", self._save_result)
|
|
tb.addSeparator()
|
|
tb.addAction("Zoom +", lambda: self._viewer.zoom_in())
|
|
tb.addAction("Zoom -", lambda: self._viewer.zoom_out())
|
|
tb.addAction("Ajuster", lambda: self._viewer.zoom_fit())
|
|
tb.addSeparator()
|
|
self._page_label = QLabel(" Aucun fichier ")
|
|
self._page_label.setStyleSheet("color: #aaa; padding: 0 10px;")
|
|
tb.addWidget(self._page_label)
|
|
|
|
# ── UI principale ──
|
|
|
|
def _setup_ui(self):
|
|
central = QWidget()
|
|
self.setCentralWidget(central)
|
|
layout = QVBoxLayout(central)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
# Splitter : vignettes | viewer
|
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
|
|
# Panneau vignettes
|
|
self._page_panel = PagePanel()
|
|
self._page_panel.page_selected.connect(self._on_thumbnail_click)
|
|
self._page_panel.page_delete_requested.connect(self._on_page_delete)
|
|
self._page_panel.page_insert_requested.connect(self._on_page_insert)
|
|
self._page_panel.pages_reordered.connect(self._on_pages_reordered)
|
|
splitter.addWidget(self._page_panel)
|
|
|
|
# Viewer PDF
|
|
self._viewer = PdfViewer()
|
|
self._viewer.page_changed.connect(self._on_page_changed)
|
|
splitter.addWidget(self._viewer)
|
|
|
|
splitter.setSizes([160, 900])
|
|
splitter.setStretchFactor(0, 0)
|
|
splitter.setStretchFactor(1, 1)
|
|
|
|
layout.addWidget(splitter)
|
|
|
|
# Barre de progression
|
|
self._progress = QProgressBar()
|
|
self._progress.setFixedHeight(3)
|
|
self._progress.setTextVisible(False)
|
|
self._progress.setStyleSheet("""
|
|
QProgressBar { background: #0f0f23; border: none; }
|
|
QProgressBar::chunk { background: #0066cc; }
|
|
""")
|
|
self._progress.hide()
|
|
layout.addWidget(self._progress)
|
|
|
|
# ── Statusbar ──
|
|
|
|
def _setup_statusbar(self):
|
|
self._statusbar = QStatusBar()
|
|
self.setStatusBar(self._statusbar)
|
|
self._status_label = QLabel("Pret — Glissez un PDF ou utilisez Fichier > Ouvrir")
|
|
self._statusbar.addWidget(self._status_label, 1)
|
|
self._file_label = QLabel("")
|
|
self._statusbar.addPermanentWidget(self._file_label)
|
|
brand = QLabel(" CopyDev — contact@copydev.fr ")
|
|
brand.setStyleSheet("color: #555; font-size: 10px;")
|
|
self._statusbar.addPermanentWidget(brand)
|
|
|
|
# ── Callbacks ──
|
|
|
|
def _on_page_changed(self, page, total):
|
|
self._page_label.setText(f" Page {page + 1} / {total} | Zoom {int(self._viewer.zoom_level * 100)}% ")
|
|
self._page_panel.select_page(page)
|
|
|
|
def _on_thumbnail_click(self, index):
|
|
self._viewer.go_to_page(index)
|
|
|
|
def _on_page_delete(self, index):
|
|
if not self._current_file:
|
|
return
|
|
from pypdf import PdfReader, PdfWriter
|
|
reader = PdfReader(self._current_file)
|
|
if len(reader.pages) <= 1:
|
|
QMessageBox.warning(self, "Erreur", "Impossible de supprimer la derniere page.")
|
|
return
|
|
writer = PdfWriter()
|
|
for i, page in enumerate(reader.pages):
|
|
if i != index:
|
|
writer.add_page(page)
|
|
output = self._make_temp()
|
|
with open(output, "wb") as f:
|
|
writer.write(f)
|
|
self._show_result(output)
|
|
|
|
def _on_page_insert(self, after_index):
|
|
if not self._current_file:
|
|
return
|
|
from pypdf import PdfReader, PdfWriter
|
|
from app.engine.pdf_utils import get_page_size
|
|
reader = PdfReader(self._current_file)
|
|
writer = PdfWriter()
|
|
ref_page = reader.pages[min(after_index, len(reader.pages) - 1)]
|
|
w, h = get_page_size(ref_page)
|
|
for i, page in enumerate(reader.pages):
|
|
writer.add_page(page)
|
|
if i == after_index:
|
|
writer.add_blank_page(w, h)
|
|
output = self._make_temp()
|
|
with open(output, "wb") as f:
|
|
writer.write(f)
|
|
self._show_result(output)
|
|
|
|
def _on_pages_reordered(self, indices):
|
|
if not self._current_file:
|
|
return
|
|
from pypdf import PdfReader, PdfWriter
|
|
reader = PdfReader(self._current_file)
|
|
writer = PdfWriter()
|
|
for idx in indices:
|
|
if 0 <= idx < len(reader.pages):
|
|
writer.add_page(reader.pages[idx])
|
|
output = self._make_temp()
|
|
with open(output, "wb") as f:
|
|
writer.write(f)
|
|
self._current_file = output
|
|
self._result_file = output
|
|
|
|
# ── Fichiers ──
|
|
|
|
def _open_file(self):
|
|
path, _ = QFileDialog.getOpenFileName(self, "Ouvrir un fichier PDF", "", "PDF (*.pdf)")
|
|
if path:
|
|
self._open_pdf(path)
|
|
|
|
def _open_pdf(self, path):
|
|
self._current_file = path
|
|
self._result_file = None
|
|
self._viewer.load_pdf(path)
|
|
self._page_panel.load_thumbnails(self._viewer)
|
|
self._file_label.setText(os.path.basename(path))
|
|
self._status_label.setText(f"Fichier charge : {os.path.basename(path)}")
|
|
# Ajuster apres affichage
|
|
QTimer.singleShot(100, self._viewer.zoom_fit)
|
|
|
|
def _save_result(self):
|
|
source = self._result_file or self._current_file
|
|
if not source:
|
|
QMessageBox.warning(self, "Erreur", "Aucun fichier a enregistrer.")
|
|
return
|
|
path, _ = QFileDialog.getSaveFileName(self, "Enregistrer le resultat", "", "PDF (*.pdf)")
|
|
if path:
|
|
if not path.lower().endswith(".pdf"):
|
|
path += ".pdf"
|
|
shutil.copy2(source, path)
|
|
self._status_label.setText(f"Enregistre : {os.path.basename(path)}")
|
|
|
|
def _require_file(self):
|
|
if not self._current_file:
|
|
QMessageBox.warning(self, "Erreur", "Veuillez d'abord ouvrir un fichier PDF.")
|
|
return False
|
|
return True
|
|
|
|
def _make_temp(self):
|
|
fd, path = tempfile.mkstemp(suffix=".pdf")
|
|
os.close(fd)
|
|
return path
|
|
|
|
def _show_progress(self):
|
|
self._progress.setRange(0, 0)
|
|
self._progress.show()
|
|
self._status_label.setText("Traitement en cours...")
|
|
QApplication.processEvents()
|
|
|
|
def _hide_progress(self):
|
|
self._progress.hide()
|
|
self._progress.setRange(0, 1)
|
|
|
|
def _show_result(self, output_path):
|
|
self._result_file = output_path
|
|
self._current_file = output_path
|
|
self._viewer.load_pdf(output_path)
|
|
self._page_panel.load_thumbnails(self._viewer)
|
|
self._hide_progress()
|
|
self._status_label.setText("Traitement termine")
|
|
QTimer.singleShot(100, self._viewer.zoom_fit)
|
|
|
|
# ── Actions imposition ──
|
|
|
|
def _do_booklet(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = BookletDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
create_booklet(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_nup(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = NupDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
nup_pages(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_step_repeat(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = StepRepeatDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
step_and_repeat(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_manual_impose(self):
|
|
if not self._require_file():
|
|
return
|
|
from pypdf import PdfReader
|
|
reader = PdfReader(self._current_file)
|
|
dlg = ManualImposeDialog(total_pages=len(reader.pages), parent=self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
manual_impose(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_join_pages(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = JoinPagesDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
join_pages(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_insert_blank(self):
|
|
if not self._require_file():
|
|
return
|
|
from pypdf import PdfReader
|
|
reader = PdfReader(self._current_file)
|
|
dlg = InsertBlankDialog(total_pages=len(reader.pages), parent=self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
insert_blank_pages(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_page_numbers(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = PageNumbersDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
add_page_numbers(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_masking(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = MaskingDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
apply_masking(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_bleeds(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = BleedsDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
define_bleeds(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_remove_bleeds(self):
|
|
if not self._require_file():
|
|
return
|
|
from pypdf import PdfReader, PdfWriter
|
|
self._show_progress()
|
|
try:
|
|
reader = PdfReader(self._current_file)
|
|
writer = PdfWriter()
|
|
for page in reader.pages:
|
|
if hasattr(page, 'trimbox'):
|
|
page.trimbox = page.mediabox
|
|
if hasattr(page, 'bleedbox'):
|
|
page.bleedbox = page.mediabox
|
|
writer.add_page(page)
|
|
output = self._make_temp()
|
|
with open(output, "wb") as f:
|
|
writer.write(f)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_crop(self):
|
|
if not self._require_file():
|
|
return
|
|
from pypdf import PdfReader
|
|
from app.engine.pdf_utils import get_page_size
|
|
reader = PdfReader(self._current_file)
|
|
page = reader.pages[self._viewer.current_page]
|
|
w, h = get_page_size(page)
|
|
dlg = CropDialog(
|
|
page_width_pt=w, page_height_pt=h,
|
|
total_pages=len(reader.pages),
|
|
current_page=self._viewer.current_page,
|
|
parent=self
|
|
)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
output = self._make_temp()
|
|
try:
|
|
crop_pages(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_print(self):
|
|
if not self._require_file():
|
|
return
|
|
import subprocess
|
|
try:
|
|
source = self._result_file or self._current_file
|
|
subprocess.Popen(["cmd", "/c", "start", "", source], shell=True)
|
|
self._status_label.setText("Ouverture pour impression...")
|
|
except Exception as e:
|
|
QMessageBox.critical(self, "Erreur", f"Impossible d'imprimer :\n{e}")
|
|
|
|
def _do_crop_marks(self):
|
|
if not self._require_file():
|
|
return
|
|
dlg = CropMarksDialog(self)
|
|
if dlg.exec():
|
|
self._show_progress()
|
|
settings = dlg.get_settings()
|
|
settings["current_page"] = self._viewer.current_page
|
|
output = self._make_temp()
|
|
try:
|
|
add_crop_marks(self._current_file, output, **settings)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_remove_crop_marks(self):
|
|
if not self._require_file():
|
|
return
|
|
self._show_progress()
|
|
try:
|
|
output = self._make_temp()
|
|
remove_crop_marks(self._current_file, output)
|
|
self._show_result(output)
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
def _do_merge(self):
|
|
paths, _ = QFileDialog.getOpenFileNames(self, "Selectionner les PDF a fusionner", "", "PDF (*.pdf)")
|
|
if not paths or len(paths) < 2:
|
|
if paths and len(paths) == 1:
|
|
QMessageBox.information(self, "Fusion", "Selectionnez au moins 2 fichiers PDF.")
|
|
return
|
|
from pypdf import PdfWriter
|
|
self._show_progress()
|
|
try:
|
|
writer = PdfWriter()
|
|
for path in paths:
|
|
writer.append(path)
|
|
output = self._make_temp()
|
|
with open(output, "wb") as f:
|
|
writer.write(f)
|
|
self._show_result(output)
|
|
self._status_label.setText(f"Fusion de {len(paths)} fichiers terminee")
|
|
except Exception as e:
|
|
self._hide_progress()
|
|
QMessageBox.critical(self, "Erreur", str(e))
|
|
|
|
# ── A propos ──
|
|
|
|
def _show_about(self):
|
|
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox
|
|
from PyQt6.QtGui import QPixmap, QFont
|
|
|
|
dlg = QDialog(self)
|
|
dlg.setWindowTitle("A propos — CopyDev Imposing Tool")
|
|
dlg.setFixedSize(520, 480)
|
|
dlg.setStyleSheet("QDialog { background-color: #0f172a; }")
|
|
layout = QVBoxLayout(dlg)
|
|
layout.setContentsMargins(40, 30, 40, 25)
|
|
|
|
# Logo
|
|
logo_label = QLabel()
|
|
_base = sys._MEIPASS if getattr(sys, 'frozen', False) else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
logo_path = os.path.join(_base, "resources", "logo.png")
|
|
if os.path.exists(logo_path):
|
|
pixmap = QPixmap(logo_path).scaled(150, 150, Qt.AspectRatioMode.KeepAspectRatio,
|
|
Qt.TransformationMode.SmoothTransformation)
|
|
logo_label.setPixmap(pixmap)
|
|
logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
layout.addWidget(logo_label)
|
|
|
|
layout.addSpacing(10)
|
|
|
|
title = QLabel("COPYDEV IMPOSING TOOL")
|
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
title.setFont(QFont("Segoe UI", 20, QFont.Weight.Bold))
|
|
title.setStyleSheet("color: white;")
|
|
layout.addWidget(title)
|
|
|
|
from app.updater import VERSION
|
|
version = QLabel(f"Version {VERSION}")
|
|
version.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
version.setFont(QFont("Segoe UI", 10))
|
|
version.setStyleSheet("color: #64748b;")
|
|
layout.addWidget(version)
|
|
|
|
layout.addSpacing(15)
|
|
|
|
desc = QLabel(
|
|
"Outil professionnel d'imposition PDF\n"
|
|
"Livrets, n-up, repetition, imposition manuelle,\n"
|
|
"numerotation, masquage et fonds perdus."
|
|
)
|
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
desc.setFont(QFont("Segoe UI", 10))
|
|
desc.setStyleSheet("color: #94a3b8;")
|
|
desc.setWordWrap(True)
|
|
layout.addWidget(desc)
|
|
|
|
layout.addSpacing(20)
|
|
|
|
sep = QLabel()
|
|
sep.setFixedHeight(1)
|
|
sep.setStyleSheet("background-color: #1e293b;")
|
|
layout.addWidget(sep)
|
|
|
|
layout.addSpacing(15)
|
|
|
|
contact = QLabel(
|
|
'<p style="text-align:center; line-height: 1.6;">'
|
|
'<span style="font-size:14px; font-weight:bold; color:white;">CopyDev</span><br>'
|
|
'<span style="color:#94a3b8;">Jules Dubois — Developpeur logiciel</span><br><br>'
|
|
'<span style="color:#94a3b8;">Email : </span>'
|
|
'<a href="mailto:contact@copydev.fr" style="color:#38bdf8;">contact@copydev.fr</a><br>'
|
|
'<span style="color:#94a3b8;">Web : </span>'
|
|
'<a href="https://copydev.fr" style="color:#38bdf8;">www.copydev.fr</a>'
|
|
'</p>'
|
|
)
|
|
contact.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
contact.setOpenExternalLinks(True)
|
|
layout.addWidget(contact)
|
|
|
|
layout.addStretch()
|
|
|
|
copy_label = QLabel("2026 CopyDev — Tous droits reserves\nLogiciel sous licence — Reproduction interdite")
|
|
copy_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
copy_label.setFont(QFont("Segoe UI", 8))
|
|
copy_label.setStyleSheet("color: #334155;")
|
|
layout.addWidget(copy_label)
|
|
|
|
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
|
|
buttons.setStyleSheet("QPushButton { background: #1e293b; color: white; border: 1px solid #334155; padding: 6px 20px; border-radius: 4px; } QPushButton:hover { background: #334155; }")
|
|
buttons.rejected.connect(dlg.close)
|
|
layout.addWidget(buttons)
|
|
|
|
dlg.exec()
|