V2.0.0 — Refonte interface complete

- Nouveau layout : menus en haut, vignettes a gauche, viewer au centre
- Panneau vignettes redimensionnable (splitter)
  - Miniatures de chaque page
  - Drag & drop pour reordonner
  - Clic droit : supprimer page, inserer page blanche
  - Clic = naviguer vers la page
- Viewer PDF ameliore :
  - Ajustement auto a l'ouverture
  - Roulette = defiler les pages
  - Ctrl+roulette = zoom
- Drag & drop de fichier PDF sur la fenetre
- Barre de progression pendant les traitements
- Fusion de PDF (Fichier > Fusionner)
- Enlever les fonds perdus (Annotations > Enlever)
- Menus par categorie : Imposition, Pages, Annotations
- Toolbar : Ouvrir, Enregistrer, Zoom, info page
- Style sombre coherent CopyDev
- Suppression du numero de telephone dans A propos

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 07:49:44 +01:00
parent de3e83b57e
commit 9cc256ff51
5 changed files with 488 additions and 225 deletions

View File

@@ -1,15 +1,18 @@
"""Fenetre principale de Copydev Imposing Tool."""
"""Fenetre principale de Copydev Imposing Tool — V2."""
import os
import sys
import tempfile
import shutil
from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QFileDialog, QMessageBox,
QGroupBox, QStatusBar, QMenuBar, QSplitter,
QProgressBar)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
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
@@ -36,172 +39,259 @@ class MainWindow(QMainWindow):
super().__init__()
from app.updater import VERSION
self.setWindowTitle(f"CopyDev Imposing Tool v{VERSION} — www.copydev.fr")
self.setMinimumSize(1000, 700)
self.setMinimumSize(1100, 750)
# Icone de la fenetre
import sys as _sys
from PyQt6.QtGui import QIcon
_base = _sys._MEIPASS if getattr(_sys, 'frozen', False) else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 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()
# Menu Fichier
# Fichier
file_menu = menubar.addMenu("Fichier")
open_act = QAction("Ouvrir PDF...", self)
open_act.setShortcut("Ctrl+O")
open_act.triggered.connect(self._open_file)
file_menu.addAction(open_act)
save_act = QAction("Enregistrer le resultat...", self)
save_act.setShortcut("Ctrl+S")
save_act.triggered.connect(self._save_result)
file_menu.addAction(save_act)
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)
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)
quit_act = QAction("Quitter", self)
quit_act.setShortcut("Ctrl+Q")
quit_act.triggered.connect(self.close)
file_menu.addAction(quit_act)
# 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)
# Menu Aide
# Pages
pages_menu = menubar.addMenu("Pages")
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()
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")
about_act = QAction("A propos...", self)
about_act.triggered.connect(self._show_about)
help_menu.addAction(about_act)
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)
main_layout = QHBoxLayout(central)
layout = QVBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# === Panneau gauche : boutons ===
left_panel = QWidget()
left_panel.setFixedWidth(200)
left_layout = QVBoxLayout(left_panel)
left_layout.setContentsMargins(5, 5, 5, 5)
# Splitter : vignettes | viewer
splitter = QSplitter(Qt.Orientation.Horizontal)
# Ouvrir
btn_open = QPushButton("Ouvrir PDF...")
btn_open.clicked.connect(self._open_file)
left_layout.addWidget(btn_open)
# 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)
btn_save = QPushButton("Enregistrer resultat...")
btn_save.clicked.connect(self._save_result)
left_layout.addWidget(btn_save)
left_layout.addSpacing(10)
# Imposition
grp_impose = QGroupBox("Imposition")
il = QVBoxLayout(grp_impose)
btn_booklet = QPushButton("Creer un livret")
btn_booklet.clicked.connect(self._do_booklet)
il.addWidget(btn_booklet)
btn_nup = QPushButton("N pages par feuille")
btn_nup.clicked.connect(self._do_nup)
il.addWidget(btn_nup)
btn_repeat = QPushButton("Repetition")
btn_repeat.clicked.connect(self._do_step_repeat)
il.addWidget(btn_repeat)
btn_manual = QPushButton("Imposition manuelle")
btn_manual.clicked.connect(self._do_manual_impose)
il.addWidget(btn_manual)
btn_join = QPushButton("Joindre deux pages")
btn_join.clicked.connect(self._do_join_pages)
il.addWidget(btn_join)
left_layout.addWidget(grp_impose)
# Pages
grp_pages = QGroupBox("Pages")
pl = QVBoxLayout(grp_pages)
btn_blank = QPushButton("Pages blanches")
btn_blank.clicked.connect(self._do_insert_blank)
pl.addWidget(btn_blank)
left_layout.addWidget(grp_pages)
# Annotations
grp_annot = QGroupBox("Annotations")
al = QVBoxLayout(grp_annot)
btn_numbers = QPushButton("Numerotation")
btn_numbers.clicked.connect(self._do_page_numbers)
al.addWidget(btn_numbers)
btn_mask = QPushButton("Masquage")
btn_mask.clicked.connect(self._do_masking)
al.addWidget(btn_mask)
btn_bleeds = QPushButton("Fonds perdus")
btn_bleeds.clicked.connect(self._do_bleeds)
al.addWidget(btn_bleeds)
left_layout.addWidget(grp_annot)
left_layout.addStretch()
main_layout.addWidget(left_panel)
# === Zone centrale : viewer PDF ===
# Viewer PDF
self._viewer = PdfViewer()
self._viewer.page_changed.connect(self._on_page_changed)
main_layout.addWidget(self._viewer, 1)
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")
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: #64748b; font-size: 10px;")
brand.setStyleSheet("color: #555; font-size: 10px;")
self._statusbar.addPermanentWidget(brand)
# ── Callbacks ──
def _on_page_changed(self, page, total):
pass
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)"
)
path, _ = QFileDialog.getOpenFileName(self, "Ouvrir un fichier PDF", "", "PDF (*.pdf)")
if path:
self._current_file = path
self._result_file = None
self._viewer.load_pdf(path)
self._file_label.setText(os.path.basename(path))
self._status_label.setText(f"Fichier charge : {os.path.basename(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)"
)
path, _ = QFileDialog.getSaveFileName(self, "Enregistrer le resultat", "", "PDF (*.pdf)")
if path:
if not path.lower().endswith(".pdf"):
path += ".pdf"
import shutil
shutil.copy2(source, path)
self._status_label.setText(f"Enregistre : {os.path.basename(path)}")
@@ -216,22 +306,40 @@ class MainWindow(QMainWindow):
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):
@@ -239,12 +347,14 @@ class MainWindow(QMainWindow):
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):
@@ -252,12 +362,14 @@ class MainWindow(QMainWindow):
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):
@@ -267,12 +379,14 @@ class MainWindow(QMainWindow):
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):
@@ -280,12 +394,14 @@ class MainWindow(QMainWindow):
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):
@@ -295,12 +411,14 @@ class MainWindow(QMainWindow):
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):
@@ -308,12 +426,14 @@ class MainWindow(QMainWindow):
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):
@@ -321,12 +441,14 @@ class MainWindow(QMainWindow):
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):
@@ -334,30 +456,75 @@ class MainWindow(QMainWindow):
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_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
from PyQt6.QtCore import Qt
dlg = QDialog(self)
dlg.setWindowTitle("A propos — CopyDev Imposing Tool")
dlg.setFixedSize(520, 520)
dlg.setFixedSize(520, 480)
dlg.setStyleSheet("QDialog { background-color: #0f172a; }")
layout = QVBoxLayout(dlg)
layout.setContentsMargins(40, 30, 40, 25)
# Logo
logo_label = QLabel()
import sys as _sys
_base = _sys._MEIPASS if getattr(_sys, 'frozen', False) else os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_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,
@@ -368,14 +535,12 @@ class MainWindow(QMainWindow):
layout.addSpacing(10)
# Titre
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)
# Version
from app.updater import VERSION
version = QLabel(f"Version {VERSION}")
version.setAlignment(Qt.AlignmentFlag.AlignCenter)
@@ -385,7 +550,6 @@ class MainWindow(QMainWindow):
layout.addSpacing(15)
# Description
desc = QLabel(
"Outil professionnel d'imposition PDF\n"
"Livrets, n-up, repetition, imposition manuelle,\n"
@@ -399,7 +563,6 @@ class MainWindow(QMainWindow):
layout.addSpacing(20)
# Separateur
sep = QLabel()
sep.setFixedHeight(1)
sep.setStyleSheet("background-color: #1e293b;")
@@ -407,7 +570,6 @@ class MainWindow(QMainWindow):
layout.addSpacing(15)
# Coordonnees
contact = QLabel(
'<p style="text-align:center; line-height: 1.6;">'
'<span style="font-size:14px; font-weight:bold; color:white;">CopyDev</span><br>'
@@ -415,9 +577,7 @@ class MainWindow(QMainWindow):
'<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><br>'
'<span style="color:#94a3b8;">Tel : </span>'
'<span style="color:#e2e8f0;">06 52 07 92 35</span>'
'<a href="https://copydev.fr" style="color:#38bdf8;">www.copydev.fr</a>'
'</p>'
)
contact.setAlignment(Qt.AlignmentFlag.AlignCenter)
@@ -426,14 +586,12 @@ class MainWindow(QMainWindow):
layout.addStretch()
# Copyright
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)
# Bouton Fermer
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)