V2.6.0 — Recadrage non-destructif + impression + licence dialog

- Recadrage non-destructif :
  - Garde le fichier original en memoire
  - Reouvrir Recadrer reaffiche la page entiere avec le cadre existant
  - Les marges sont pre-remplies avec le crop precedent
  - Annuler revient au fichier actuel
- Impression : os.startfile(path, "print") ouvre le dialogue d'impression
  Windows directement (au lieu d'ouvrir le navigateur)
- Dialogue de licence custom (logo, champ monospace, bouton cyan)
- Messages erreur/succes en dialogues custom colores

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jules
2026-03-21 15:35:57 +01:00
parent c6d3ddcf9d
commit cee86f5c03
4 changed files with 79 additions and 14 deletions

View File

@@ -9,7 +9,7 @@ from app.engine.pdf_utils import mm_to_pt, pt_to_mm
class CropDialog(QDialog):
def __init__(self, page_width_pt=595, page_height_pt=842, total_pages=1,
current_page=0, viewer=None, parent=None):
current_page=0, viewer=None, existing_crop=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Recadrer")
self.setFixedWidth(360)
@@ -20,7 +20,9 @@ class CropDialog(QDialog):
self._total_pages = total_pages
self._current_page = current_page
self._viewer = viewer
self._existing_crop = existing_crop
self._setup_ui()
self._apply_existing_crop()
self._update_preview()
def _setup_ui(self):
@@ -196,6 +198,22 @@ class CropDialog(QDialog):
spin.blockSignals(False)
self._update_preview()
def _apply_existing_crop(self):
"""Pre-remplir les marges si on a un crop existant."""
if not self._existing_crop:
return
from app.engine.pdf_utils import pt_to_mm
for spin in self.margin_spins.values():
spin.blockSignals(True)
self.margin_spins["haut"].setValue(pt_to_mm(self._existing_crop.get("crop_top", 0)))
self.margin_spins["bas"].setValue(pt_to_mm(self._existing_crop.get("crop_bottom", 0)))
self.margin_spins["gauche"].setValue(pt_to_mm(self._existing_crop.get("crop_left", 0)))
self.margin_spins["droite"].setValue(pt_to_mm(self._existing_crop.get("crop_right", 0)))
for spin in self.margin_spins.values():
spin.blockSignals(False)
# Afficher le cadre sur le viewer
self._show_selection_from_margins()
def _on_selection_changed(self, left, top, right, bottom):
"""Recoit les ratios de selection du viewer et met a jour les marges."""
if not self.rb_margins.isChecked():

View File

@@ -56,6 +56,8 @@ class MainWindow(QMainWindow):
self._current_file = None
self._result_file = None
self._original_file = None # fichier avant crop (pour recadrage non-destructif)
self._crop_settings = {} # {page_index: {top, bottom, left, right} en pt}
# Drag & drop
self.setAcceptDrops(True)
@@ -444,11 +446,12 @@ class MainWindow(QMainWindow):
def _open_pdf(self, path):
self._current_file = path
self._result_file = None
self._original_file = path # reset l'original
self._crop_settings = {} # reset les crops
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):
@@ -701,15 +704,30 @@ class MainWindow(QMainWindow):
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]
from app.engine.pdf_utils import get_page_size, pt_to_mm
# Toujours travailler depuis l'original pour permettre la re-edition
source = self._original_file or self._current_file
reader = PdfReader(source)
cur_page = self._viewer.current_page
page = reader.pages[cur_page]
w, h = get_page_size(page)
# Recharger l'original dans le viewer pour voir la page entiere
self._viewer.load_pdf(source)
self._viewer.go_to_page(cur_page)
self._page_panel.load_thumbnails(self._viewer)
QApplication.processEvents()
# Si on a des settings de crop existants pour cette page, pre-remplir
existing = self._crop_settings.get(cur_page, None)
self._crop_dlg = CropDialog(
page_width_pt=w, page_height_pt=h,
total_pages=len(reader.pages),
current_page=self._viewer.current_page,
current_page=cur_page,
viewer=self._viewer,
existing_crop=existing,
parent=self
)
@@ -724,30 +742,59 @@ class MainWindow(QMainWindow):
return
if not settings.get("pages"):
return
# Sauvegarder les settings de crop par page
for pg in settings["pages"]:
self._crop_settings[pg] = {
"crop_top": settings["crop_top"],
"crop_bottom": settings["crop_bottom"],
"crop_left": settings["crop_left"],
"crop_right": settings["crop_right"],
}
# Appliquer le crop depuis l'original
self._show_progress()
source_file = self._original_file or self._current_file
output = self._make_temp()
try:
crop_pages(self._current_file, output, **settings)
self._show_result(output)
crop_pages(source_file, output, **settings)
self._result_file = output
self._current_file = output
self._viewer.load_pdf(output)
self._viewer.go_to_page(cur_page)
self._page_panel.load_thumbnails(self._viewer)
self._hide_progress()
self._status_label.setText(
f"Recadrage applique — {len(settings['pages'])} page(s)"
)
QTimer.singleShot(100, self._viewer.zoom_fit)
except Exception as e:
self._hide_progress()
QMessageBox.critical(self, "Erreur", str(e))
def on_crop_rejected():
# Revenir au fichier actuel (croppe ou non)
current = self._current_file
self._viewer.load_pdf(current)
self._viewer.go_to_page(cur_page)
self._page_panel.load_thumbnails(self._viewer)
self._crop_dlg = None
self._crop_dlg.accepted.connect(on_crop_accepted)
self._crop_dlg.rejected.connect(lambda: setattr(self, '_crop_dlg', None))
self._crop_dlg.rejected.connect(on_crop_rejected)
self._crop_dlg.show()
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...")
if sys.platform == "win32":
os.startfile(source, "print")
else:
import subprocess
subprocess.Popen(["lp", source])
self._status_label.setText("Impression lancee...")
except Exception as e:
QMessageBox.critical(self, "Erreur", f"Impossible d'imprimer :\n{e}")

View File

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