V2.5.1 — Dialogue licence custom + fix recadrage + style

- Dialogue de licence custom (remplace QInputDialog moche)
  - Logo CopyDev, champ stylise, bouton Activer cyan
  - Messages erreur/succes en dialogues custom colores
- Fix recadrage : reference au dialogue preservee avant destruction
- Splash screen sans bordure carree
- Style coherent gris/cyan sur tous les dialogues

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 14:49:29 +01:00
parent d7a538aa8c
commit e94158aae8
6 changed files with 228 additions and 34 deletions

187
app/license_dialog.py Normal file
View File

@@ -0,0 +1,187 @@
"""Dialogue de saisie de licence — style moderne."""
import os
import sys
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QApplication)
from PyQt6.QtGui import QPixmap, QFont
from PyQt6.QtCore import Qt
class LicenseDialog(QDialog):
"""Dialogue de saisie de cle de licence."""
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("CopyDev Imposing Tool — Activation")
self.setFixedSize(420, 340)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
self._key = ""
self._setup_ui()
self._apply_style()
def _setup_ui(self):
layout = QVBoxLayout(self)
layout.setContentsMargins(32, 24, 32, 24)
layout.setSpacing(0)
# 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(80, 80,
Qt.AspectRatioMode.KeepAspectRatio,
Qt.TransformationMode.SmoothTransformation)
logo_label.setPixmap(pixmap)
logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(logo_label)
layout.addSpacing(12)
title = QLabel("Activation")
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
title.setFont(QFont("Segoe UI", 16, QFont.Weight.Bold))
layout.addWidget(title)
layout.addSpacing(4)
subtitle = QLabel("Entrez votre cle de licence pour activer le logiciel")
subtitle.setAlignment(Qt.AlignmentFlag.AlignCenter)
subtitle.setFont(QFont("Segoe UI", 10))
subtitle.setStyleSheet("color: #888;")
subtitle.setWordWrap(True)
layout.addWidget(subtitle)
layout.addSpacing(20)
# Champ de saisie
self._input = QLineEdit()
self._input.setPlaceholderText("COPYDEV-XXXX-XXXX-XXXX")
self._input.setFont(QFont("Consolas", 13))
self._input.setAlignment(Qt.AlignmentFlag.AlignCenter)
self._input.setMinimumHeight(40)
self._input.returnPressed.connect(self._on_activate)
layout.addWidget(self._input)
layout.addSpacing(16)
# Boutons
btn_layout = QHBoxLayout()
btn_layout.setSpacing(10)
self._btn_cancel = QPushButton("Annuler")
self._btn_cancel.setMinimumHeight(36)
self._btn_cancel.clicked.connect(self.reject)
btn_layout.addWidget(self._btn_cancel)
self._btn_activate = QPushButton("Activer")
self._btn_activate.setMinimumHeight(36)
self._btn_activate.setDefault(True)
self._btn_activate.clicked.connect(self._on_activate)
btn_layout.addWidget(self._btn_activate)
layout.addLayout(btn_layout)
layout.addStretch()
# Contact
contact = QLabel("contact@copydev.fr — www.copydev.fr")
contact.setAlignment(Qt.AlignmentFlag.AlignCenter)
contact.setFont(QFont("Segoe UI", 8))
contact.setStyleSheet("color: #aaa;")
layout.addWidget(contact)
def _apply_style(self):
self.setStyleSheet("""
QDialog { background: #f5f5f5; }
QLabel { color: #2d2d2d; }
QLineEdit {
background: #fff; color: #2d2d2d;
border: 2px solid #d0d0d0; border-radius: 8px;
padding: 8px; font-size: 13px;
}
QLineEdit:focus { border-color: #00b4d8; }
QPushButton#cancel {
background: #e8e8e8; color: #555; border: 1px solid #d0d0d0;
border-radius: 8px; font-size: 13px;
}
QPushButton#cancel:hover { background: #ddd; }
""")
self._btn_activate.setStyleSheet("""
QPushButton {
background: #00b4d8; color: white; border: none;
border-radius: 8px; font-size: 13px; font-weight: bold;
}
QPushButton:hover { background: #0096b7; }
QPushButton:pressed { background: #007a96; }
""")
self._btn_cancel.setStyleSheet("""
QPushButton {
background: #e8e8e8; color: #555; border: 1px solid #d0d0d0;
border-radius: 8px; font-size: 13px;
}
QPushButton:hover { background: #ddd; }
""")
def _on_activate(self):
self._key = self._input.text().strip()
if self._key:
self.accept()
def get_key(self):
return self._key
class LicenseMessageDialog(QDialog):
"""Dialogue de message stylise (remplace QMessageBox)."""
def __init__(self, title, message, msg_type="info", parent=None):
super().__init__(parent)
self.setWindowTitle(title)
self.setFixedWidth(400)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
layout = QVBoxLayout(self)
layout.setContentsMargins(28, 24, 28, 20)
layout.setSpacing(12)
# Icone couleur selon le type
colors = {"info": "#00b4d8", "success": "#2ecc71", "error": "#e74c3c", "warning": "#f39c12"}
color = colors.get(msg_type, "#00b4d8")
bar = QLabel()
bar.setFixedHeight(4)
bar.setStyleSheet(f"background: {color}; border-radius: 2px;")
layout.addWidget(bar)
title_lbl = QLabel(title)
title_lbl.setFont(QFont("Segoe UI", 14, QFont.Weight.Bold))
title_lbl.setStyleSheet(f"color: {color};")
layout.addWidget(title_lbl)
msg_lbl = QLabel(message)
msg_lbl.setFont(QFont("Segoe UI", 11))
msg_lbl.setWordWrap(True)
msg_lbl.setStyleSheet("color: #444; line-height: 1.4;")
layout.addWidget(msg_lbl)
layout.addSpacing(8)
btn = QPushButton("OK")
btn.setMinimumHeight(36)
btn.setStyleSheet(f"""
QPushButton {{
background: {color}; color: white; border: none;
border-radius: 8px; font-size: 13px; font-weight: bold;
min-width: 100px;
}}
QPushButton:hover {{ background: {color}cc; }}
""")
btn.clicked.connect(self.accept)
btn_layout = QHBoxLayout()
btn_layout.addStretch()
btn_layout.addWidget(btn)
layout.addLayout(btn_layout)
self.setStyleSheet("QDialog { background: #f5f5f5; }")

View File

@@ -714,16 +714,27 @@ class MainWindow(QMainWindow):
) )
def on_crop_accepted(): def on_crop_accepted():
if not hasattr(self, '_crop_dlg') or not self._crop_dlg:
return
dlg_ref = self._crop_dlg
self._crop_dlg = None
try:
settings = dlg_ref.get_settings()
except Exception:
return
if not settings.get("pages"):
return
self._show_progress() self._show_progress()
settings = self._crop_dlg.get_settings()
output = self._make_temp() output = self._make_temp()
try: try:
crop_pages(self._current_file, output, **settings) crop_pages(self._current_file, output, **settings)
self._show_result(output) self._show_result(output)
self._status_label.setText(
f"Recadrage applique — {len(settings['pages'])} page(s)"
)
except Exception as e: except Exception as e:
self._hide_progress() self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e)) QMessageBox.critical(self, "Erreur", str(e))
self._crop_dlg = None
self._crop_dlg.accepted.connect(on_crop_accepted) self._crop_dlg.accepted.connect(on_crop_accepted)
self._crop_dlg.rejected.connect(lambda: setattr(self, '_crop_dlg', None)) self._crop_dlg.rejected.connect(lambda: setattr(self, '_crop_dlg', None))

View File

@@ -13,7 +13,12 @@ class SplashScreen(QWidget):
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint) self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.WindowStaysOnTopHint)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, False)
self.setFixedSize(500, 400) self.setFixedSize(500, 400)
self.setStyleSheet("background-color: #f5f5f5; border: 1px solid #d0d0d0;") self.setStyleSheet("""
QWidget {
background-color: #f5f5f5;
border-radius: 16px;
}
""")
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
layout.setContentsMargins(40, 30, 40, 30) layout.setContentsMargins(40, 30, 40, 30)

View File

@@ -14,7 +14,7 @@ REPO = "jules/copydev-imposing-tool"
BRANCH = "master" BRANCH = "master"
# Version actuelle (incrementee a chaque release) # Version actuelle (incrementee a chaque release)
VERSION = "2.5.0" VERSION = "2.5.1"
def get_remote_version(): def get_remote_version():

49
main.py
View File

@@ -19,7 +19,7 @@ else:
sys.path.insert(0, BUNDLE_DIR) sys.path.insert(0, BUNDLE_DIR)
from PyQt6.QtWidgets import QApplication, QInputDialog, QMessageBox, QLineEdit from PyQt6.QtWidgets import QApplication, QMessageBox
import hashlib import hashlib
import platform import platform
@@ -131,57 +131,48 @@ def _check_license():
return True return True
if result is False: if result is False:
QMessageBox.critical( from app.license_dialog import LicenseMessageDialog
None, "Licence invalide", LicenseMessageDialog("Licence invalide",
f"Votre licence a ete refusee :\n{msg}\n\n" f"Votre licence a ete refusee :\n{msg}\n\n"
"Contactez CopyDev : contact@copydev.fr" "Contactez CopyDev : contact@copydev.fr", "error").exec()
)
return False return False
# Hors-ligne et cache expire # Hors-ligne et cache expire
QMessageBox.critical( from app.license_dialog import LicenseMessageDialog
None, "Licence expiree hors-ligne", LicenseMessageDialog("Licence expiree hors-ligne",
f"Impossible de verifier votre licence depuis {int(elapsed_hours)}h.\n" f"Impossible de verifier votre licence depuis {int(elapsed_hours)}h.\n"
f"Le delai hors-ligne autorise est de {timeout_hours}h.\n\n" f"Le delai hors-ligne autorise est de {timeout_hours}h.\n\n"
"Connectez-vous a internet ou contactez CopyDev :\n" "Connectez-vous a internet ou contactez CopyDev :\n"
"contact@copydev.fr" "contact@copydev.fr", "error").exec()
)
return False return False
# Pas de cache — demander la cle de licence # Pas de cache — demander la cle de licence
key, ok = QInputDialog.getText( from app.license_dialog import LicenseDialog, LicenseMessageDialog
None, dlg = LicenseDialog()
"Copydev Imposing Tool — Activation", if dlg.exec() != LicenseDialog.DialogCode.Accepted:
"Entrez votre cle de licence :", return False
QLineEdit.EchoMode.Normal,
) key = dlg.get_key()
if not ok or not key.strip(): if not key:
return False return False
key = key.strip()
result, msg = _check_license_online(key) result, msg = _check_license_online(key)
if result is True: if result is True:
QMessageBox.information( LicenseMessageDialog("Licence activee",
None, "Licence activee", f"Bienvenue, {msg} !\nVotre licence est activee.", "success").exec()
f"Bienvenue, {msg} !\nVotre licence est activee."
)
return True return True
if result is False: if result is False:
QMessageBox.critical( LicenseMessageDialog("Licence invalide",
None, "Licence invalide", f"{msg}\n\nContactez CopyDev : contact@copydev.fr", "error").exec()
f"{msg}\n\nContactez CopyDev : contact@copydev.fr"
)
return False return False
# Hors-ligne a la premiere activation # Hors-ligne a la premiere activation
QMessageBox.critical( LicenseMessageDialog("Connexion requise",
None, "Connexion requise",
"Impossible de contacter le serveur de licences.\n" "Impossible de contacter le serveur de licences.\n"
"Une connexion internet est necessaire pour la premiere activation.\n\n" "Une connexion internet est necessaire pour la premiere activation.\n\n"
"Contactez CopyDev : contact@copydev.fr" "Contactez CopyDev : contact@copydev.fr", "error").exec()
)
return False return False

View File

@@ -1 +1 @@
2.5.0 2.5.1