From 1d48e103e3c76399f46587753af558af57b6d625 Mon Sep 17 00:00:00 2001 From: Jules Date: Sat, 21 Mar 2026 16:15:25 +0100 Subject: [PATCH] =?UTF-8?q?V3.1.0=20=E2=80=94=20Selection=20multiple,=20im?= =?UTF-8?q?pression,=20MAJ=20dans=20Aide,=20menu=20contextuel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Selection multiple dans les vignettes (Ctrl+clic, Maj+clic) - Rotation/suppression sur selection multiple - Impression : os.startfile(print) avec fallback - MAJ deplacee dans Aide > Rechercher une mise a jour (plus au demarrage) - Support arguments CLI : ouvrir un PDF en argument, fusionner si plusieurs - register_windows.bat : association fichier PDF + menu contextuel Windows - Clic droit > CopyDev Impose > Ouvrir / Livret A4 / Livret A3 - Presets manager (base pour macros) Co-Authored-By: Claude Opus 4.6 (1M context) --- app/main_window.py | 46 ++++++++++++++--- app/page_panel.py | 31 +++++++++--- app/presets/preset_manager.py | 45 +++++++++++++++++ app/updater.py | 2 +- main.py | 40 +++++++-------- register_windows.bat | 95 +++++++++++++++++++++++++++++++++++ version.txt | 2 +- 7 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 app/presets/preset_manager.py create mode 100644 register_windows.bat diff --git a/app/main_window.py b/app/main_window.py index 984cce8..dddf576 100644 --- a/app/main_window.py +++ b/app/main_window.py @@ -249,6 +249,8 @@ class MainWindow(QMainWindow): # Aide help_menu = menubar.addMenu("Aide") + self._add_action(help_menu, "Rechercher une mise a jour...", None, self._do_check_update) + help_menu.addSeparator() self._add_action(help_menu, "A propos de CopyDev Imposing Tool", None, self._show_about) def _add_action(self, menu, text, shortcut, callback): @@ -908,20 +910,25 @@ class MainWindow(QMainWindow): return try: source = self._result_file or self._current_file - # Copier dans un fichier temp avec un nom lisible - import shutil tmp_print = os.path.join(tempfile.gettempdir(), "copydev_print.pdf") shutil.copy2(source, tmp_print) if sys.platform == "win32": - import subprocess - # Ouvrir avec le lecteur PDF par defaut (qui a un menu imprimer) - subprocess.Popen(["cmd", "/c", "start", "", tmp_print]) + # "print" envoie directement au dialogue d'impression + os.startfile(tmp_print, "print") else: import subprocess - subprocess.Popen(["xdg-open", tmp_print]) - self._status_label.setText("Fichier ouvert pour impression") + subprocess.Popen(["lp", tmp_print]) + self._status_label.setText("Impression lancee...") + except OSError: + # Fallback : ouvrir le fichier normalement + try: + if sys.platform == "win32": + os.startfile(tmp_print) + self._status_label.setText("Fichier ouvert — utilisez Ctrl+P pour imprimer") + except Exception as e2: + QMessageBox.critical(self, "Erreur", f"Impossible d'imprimer :\n{e2}") except Exception as e: - QMessageBox.critical(self, "Erreur", f"Impossible d'ouvrir pour impression :\n{e}") + QMessageBox.critical(self, "Erreur", f"Impossible d'imprimer :\n{e}") def _do_crop_marks(self): if not self._require_file(): @@ -974,6 +981,29 @@ class MainWindow(QMainWindow): # ── A propos ── + def _do_check_update(self): + from app.updater import check_for_update, do_update, VERSION + self._status_label.setText("Recherche de mises a jour...") + QApplication.processEvents() + update_available, remote_version = check_for_update() + if update_available: + reply = QMessageBox.question( + self, "Mise a jour disponible", + f"Nouvelle version : {remote_version}\n" + f"Version actuelle : {VERSION}\n\n" + "Mettre a jour maintenant ?", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No + ) + if reply == QMessageBox.StandardButton.Yes: + success, msg = do_update() + if success: + QMessageBox.information(self, "Mise a jour", f"{msg}\n\nRedemarrez l'application.") + else: + QMessageBox.warning(self, "Mise a jour", f"Echec :\n{msg}") + else: + QMessageBox.information(self, "Mise a jour", "Vous etes a jour !") + self._status_label.setText("Pret") + def _show_about(self): from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox from PyQt6.QtGui import QPixmap, QFont diff --git a/app/page_panel.py b/app/page_panel.py index f048323..1f20c9d 100644 --- a/app/page_panel.py +++ b/app/page_panel.py @@ -55,6 +55,7 @@ class PagePanel(QWidget): } """) + self._list.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) self._list.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) self._list.setDefaultDropAction(Qt.DropAction.MoveAction) self._list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) @@ -94,11 +95,21 @@ class PagePanel(QWidget): indices.append(original_index) self.pages_reordered.emit(indices) + def get_selected_indices(self): + """Retourne les indices des pages selectionnees.""" + return sorted(set(self._list.row(item) for item in self._list.selectedItems())) + def _context_menu(self, pos): item = self._list.itemAt(pos) if not item: return row = self._list.row(item) + selected = self.get_selected_indices() + if row not in selected: + selected = [row] + + count = len(selected) + label_pages = f"{count} page(s)" if count > 1 else "cette page" menu = QMenu(self) menu.setStyleSheet(""" @@ -107,21 +118,25 @@ class PagePanel(QWidget): QMenu::item:selected { background: #e0f4ff; color: #006fa6; } """) - act_rot_right = menu.addAction("Pivoter +90°") - act_rot_left = menu.addAction("Pivoter -90°") - act_rot_180 = menu.addAction("Pivoter 180°") + act_rot_right = menu.addAction(f"Pivoter +90° ({label_pages})") + act_rot_left = menu.addAction(f"Pivoter -90° ({label_pages})") + act_rot_180 = menu.addAction(f"Pivoter 180° ({label_pages})") menu.addSeparator() act_insert = menu.addAction("Inserer une page blanche apres") - act_delete = menu.addAction("Supprimer cette page") + act_delete = menu.addAction(f"Supprimer {label_pages}") action = menu.exec(self._list.mapToGlobal(pos)) if action == act_delete: - self.page_delete_requested.emit(row) + for idx in reversed(selected): + self.page_delete_requested.emit(idx) elif action == act_insert: self.page_insert_requested.emit(row) elif action == act_rot_right: - self.page_rotate_requested.emit(row, 90) + for idx in selected: + self.page_rotate_requested.emit(idx, 90) elif action == act_rot_left: - self.page_rotate_requested.emit(row, -90) + for idx in selected: + self.page_rotate_requested.emit(idx, -90) elif action == act_rot_180: - self.page_rotate_requested.emit(row, 180) + for idx in selected: + self.page_rotate_requested.emit(idx, 180) diff --git a/app/presets/preset_manager.py b/app/presets/preset_manager.py new file mode 100644 index 0000000..a02410c --- /dev/null +++ b/app/presets/preset_manager.py @@ -0,0 +1,45 @@ +"""Gestionnaire de presets / macros.""" + +import os +import sys +import json + +PRESETS_FILE = os.path.join( + os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) + else os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), + ".presets.json" +) + + +def load_presets(): + if not os.path.exists(PRESETS_FILE): + return {} + try: + with open(PRESETS_FILE, "r") as f: + return json.load(f) + except Exception: + return {} + + +def save_presets(presets): + try: + with open(PRESETS_FILE, "w") as f: + json.dump(presets, f, indent=2) + except Exception: + pass + + +def save_preset(name, action_type, settings): + presets = load_presets() + presets[name] = {"action": action_type, "settings": settings} + save_presets(presets) + + +def delete_preset(name): + presets = load_presets() + presets.pop(name, None) + save_presets(presets) + + +def list_presets(): + return load_presets() diff --git a/app/updater.py b/app/updater.py index fa29604..be7301a 100644 --- a/app/updater.py +++ b/app/updater.py @@ -14,7 +14,7 @@ REPO = "jules/copydev-imposing-tool" BRANCH = "master" # Version actuelle (incrementee a chaque release) -VERSION = "3.0.0" +VERSION = "3.1.0" def get_remote_version(): diff --git a/main.py b/main.py index 989c485..01ca64a 100644 --- a/main.py +++ b/main.py @@ -201,30 +201,30 @@ def main(): splash.show() QApplication.processEvents() - splash.set_status("Recherche de mises a jour...") - # Verification de mise a jour silencieuse - from app.updater import check_for_update, VERSION - update_available, remote_version = check_for_update() - if update_available: - reply = QMessageBox.question( - None, "Mise a jour disponible", - f"Une nouvelle version est disponible : {remote_version}\n" - f"(version actuelle : {VERSION})\n\n" - "Voulez-vous mettre a jour maintenant ?", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - if reply == QMessageBox.StandardButton.Yes: - from app.updater import do_update - success, msg = do_update() - if success: - QMessageBox.information(None, "Mise a jour", f"{msg}\n\nL'application va redemarrer.") - os.execv(sys.executable, [sys.executable] + sys.argv) - else: - QMessageBox.warning(None, "Mise a jour", f"Echec de la mise a jour :\n{msg}") + # Plus de verification de MAJ au demarrage — c'est dans Aide > Rechercher une MAJ splash.set_status("Chargement de l'interface...") from app.main_window import MainWindow window = MainWindow() + + # Ouvrir les fichiers passes en argument + files_to_open = [a for a in sys.argv[1:] if a.lower().endswith('.pdf') and os.path.exists(a)] + if len(files_to_open) == 1: + window._open_pdf(files_to_open[0]) + elif len(files_to_open) > 1: + # Plusieurs fichiers = fusionner + from pypdf import PdfWriter + import tempfile + writer = PdfWriter() + for f in files_to_open: + writer.append(f) + fd, merged = tempfile.mkstemp(suffix=".pdf") + os.close(fd) + with open(merged, "wb") as out: + writer.write(out) + window._open_pdf(merged) + window._status_label.setText(f"{len(files_to_open)} fichiers fusionnes") + window.show() splash.close() sys.exit(app.exec()) diff --git a/register_windows.bat b/register_windows.bat new file mode 100644 index 0000000..1931b10 --- /dev/null +++ b/register_windows.bat @@ -0,0 +1,95 @@ +@echo off +chcp 65001 >nul 2>&1 +echo ============================================================ +echo CopyDev Imposing Tool — Enregistrement Windows +echo ============================================================ +echo. + +:: Trouver l'exe +set "EXE_PATH=%~dp0CopydevImposingTool.exe" +if not exist "%EXE_PATH%" ( + set "EXE_PATH=%~dp0dist\CopydevImposingTool.exe" +) +if not exist "%EXE_PATH%" ( + echo [ERREUR] CopydevImposingTool.exe introuvable. + pause + exit /b 1 +) +echo Executable : %EXE_PATH% +echo. + +echo 1. Associer les fichiers PDF +echo 2. Ajouter au menu contextuel +echo 3. Tout enregistrer (1+2) +echo 4. Tout desenregistrer +echo 5. Quitter +echo. +set /p "CHOIX=Votre choix [1-5] : " + +if "%CHOIX%"=="1" goto ASSOC +if "%CHOIX%"=="2" goto CONTEXT +if "%CHOIX%"=="3" goto ALL +if "%CHOIX%"=="4" goto UNREGISTER +if "%CHOIX%"=="5" exit /b 0 + +:ALL +call :DO_ASSOC +call :DO_CONTEXT +goto DONE + +:ASSOC +call :DO_ASSOC +goto DONE + +:CONTEXT +call :DO_CONTEXT +goto DONE + +:DO_ASSOC +echo. +echo [1] Association des fichiers PDF... +reg add "HKCU\Software\Classes\.pdf\OpenWithProgids" /v "CopyDevImpose" /t REG_SZ /d "" /f >nul 2>&1 +reg add "HKCU\Software\Classes\CopyDevImpose" /ve /d "CopyDev Imposing Tool" /f >nul 2>&1 +reg add "HKCU\Software\Classes\CopyDevImpose\shell\open\command" /ve /d "\"%EXE_PATH%\" \"%%1\"" /f >nul 2>&1 +reg add "HKCU\Software\Classes\CopyDevImpose\DefaultIcon" /ve /d "%EXE_PATH%,0" /f >nul 2>&1 +echo [OK] PDF associe a CopyDev Imposing Tool +echo (clic droit > Ouvrir avec > CopyDev Imposing Tool) +goto :EOF + +:DO_CONTEXT +echo. +echo [2] Menu contextuel Windows... +:: Entree principale +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose" /ve /d "CopyDev Impose" /f >nul 2>&1 +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose" /v "SubCommands" /t REG_SZ /d "" /f >nul 2>&1 +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose" /v "Icon" /t REG_SZ /d "%EXE_PATH%,0" /f >nul 2>&1 + +:: Sous-commandes +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\open" /ve /d "Ouvrir" /f >nul 2>&1 +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\open\command" /ve /d "\"%EXE_PATH%\" \"%%1\"" /f >nul 2>&1 + +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\bookletA4" /ve /d "Livret A4" /f >nul 2>&1 +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\bookletA4\command" /ve /d "\"%EXE_PATH%\" --action booklet_a4 \"%%1\"" /f >nul 2>&1 + +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\bookletA3" /ve /d "Livret A3" /f >nul 2>&1 +reg add "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose\shell\bookletA3\command" /ve /d "\"%EXE_PATH%\" --action booklet_a3 \"%%1\"" /f >nul 2>&1 + +echo [OK] Menu contextuel ajoute +echo (clic droit sur PDF > CopyDev Impose > ...) +goto :EOF + +:UNREGISTER +echo. +echo Suppression des enregistrements... +reg delete "HKCU\Software\Classes\CopyDevImpose" /f >nul 2>&1 +reg delete "HKCU\Software\Classes\.pdf\OpenWithProgids" /v "CopyDevImpose" /f >nul 2>&1 +reg delete "HKCU\Software\Classes\SystemFileAssociations\.pdf\shell\CopyDevImpose" /f >nul 2>&1 +echo [OK] Tout desenregistre +goto DONE + +:DONE +echo. +echo ============================================================ +echo Termine +echo ============================================================ +pause diff --git a/version.txt b/version.txt index 4a36342..fd2a018 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0 +3.1.0