V3.1.0 — Selection multiple, impression, MAJ dans Aide, menu contextuel
- 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) <noreply@anthropic.com>
This commit is contained in:
@@ -249,6 +249,8 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# Aide
|
# Aide
|
||||||
help_menu = menubar.addMenu("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)
|
self._add_action(help_menu, "A propos de CopyDev Imposing Tool", None, self._show_about)
|
||||||
|
|
||||||
def _add_action(self, menu, text, shortcut, callback):
|
def _add_action(self, menu, text, shortcut, callback):
|
||||||
@@ -908,20 +910,25 @@ class MainWindow(QMainWindow):
|
|||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
source = self._result_file or self._current_file
|
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")
|
tmp_print = os.path.join(tempfile.gettempdir(), "copydev_print.pdf")
|
||||||
shutil.copy2(source, tmp_print)
|
shutil.copy2(source, tmp_print)
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
import subprocess
|
# "print" envoie directement au dialogue d'impression
|
||||||
# Ouvrir avec le lecteur PDF par defaut (qui a un menu imprimer)
|
os.startfile(tmp_print, "print")
|
||||||
subprocess.Popen(["cmd", "/c", "start", "", tmp_print])
|
|
||||||
else:
|
else:
|
||||||
import subprocess
|
import subprocess
|
||||||
subprocess.Popen(["xdg-open", tmp_print])
|
subprocess.Popen(["lp", tmp_print])
|
||||||
self._status_label.setText("Fichier ouvert pour impression")
|
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:
|
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):
|
def _do_crop_marks(self):
|
||||||
if not self._require_file():
|
if not self._require_file():
|
||||||
@@ -974,6 +981,29 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# ── A propos ──
|
# ── 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):
|
def _show_about(self):
|
||||||
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox
|
from PyQt6.QtWidgets import QDialog, QVBoxLayout, QLabel, QDialogButtonBox
|
||||||
from PyQt6.QtGui import QPixmap, QFont
|
from PyQt6.QtGui import QPixmap, QFont
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class PagePanel(QWidget):
|
|||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
self._list.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
self._list.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
self._list.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
|
||||||
self._list.setDefaultDropAction(Qt.DropAction.MoveAction)
|
self._list.setDefaultDropAction(Qt.DropAction.MoveAction)
|
||||||
self._list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
self._list.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
@@ -94,11 +95,21 @@ class PagePanel(QWidget):
|
|||||||
indices.append(original_index)
|
indices.append(original_index)
|
||||||
self.pages_reordered.emit(indices)
|
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):
|
def _context_menu(self, pos):
|
||||||
item = self._list.itemAt(pos)
|
item = self._list.itemAt(pos)
|
||||||
if not item:
|
if not item:
|
||||||
return
|
return
|
||||||
row = self._list.row(item)
|
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 = QMenu(self)
|
||||||
menu.setStyleSheet("""
|
menu.setStyleSheet("""
|
||||||
@@ -107,21 +118,25 @@ class PagePanel(QWidget):
|
|||||||
QMenu::item:selected { background: #e0f4ff; color: #006fa6; }
|
QMenu::item:selected { background: #e0f4ff; color: #006fa6; }
|
||||||
""")
|
""")
|
||||||
|
|
||||||
act_rot_right = menu.addAction("Pivoter +90°")
|
act_rot_right = menu.addAction(f"Pivoter +90° ({label_pages})")
|
||||||
act_rot_left = menu.addAction("Pivoter -90°")
|
act_rot_left = menu.addAction(f"Pivoter -90° ({label_pages})")
|
||||||
act_rot_180 = menu.addAction("Pivoter 180°")
|
act_rot_180 = menu.addAction(f"Pivoter 180° ({label_pages})")
|
||||||
menu.addSeparator()
|
menu.addSeparator()
|
||||||
act_insert = menu.addAction("Inserer une page blanche apres")
|
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))
|
action = menu.exec(self._list.mapToGlobal(pos))
|
||||||
if action == act_delete:
|
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:
|
elif action == act_insert:
|
||||||
self.page_insert_requested.emit(row)
|
self.page_insert_requested.emit(row)
|
||||||
elif action == act_rot_right:
|
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:
|
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:
|
elif action == act_rot_180:
|
||||||
self.page_rotate_requested.emit(row, 180)
|
for idx in selected:
|
||||||
|
self.page_rotate_requested.emit(idx, 180)
|
||||||
|
|||||||
45
app/presets/preset_manager.py
Normal file
45
app/presets/preset_manager.py
Normal file
@@ -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()
|
||||||
@@ -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 = "3.0.0"
|
VERSION = "3.1.0"
|
||||||
|
|
||||||
|
|
||||||
def get_remote_version():
|
def get_remote_version():
|
||||||
|
|||||||
40
main.py
40
main.py
@@ -201,30 +201,30 @@ def main():
|
|||||||
splash.show()
|
splash.show()
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
|
|
||||||
splash.set_status("Recherche de mises a jour...")
|
# Plus de verification de MAJ au demarrage — c'est dans Aide > Rechercher une MAJ
|
||||||
# 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}")
|
|
||||||
|
|
||||||
splash.set_status("Chargement de l'interface...")
|
splash.set_status("Chargement de l'interface...")
|
||||||
from app.main_window import MainWindow
|
from app.main_window import MainWindow
|
||||||
window = 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()
|
window.show()
|
||||||
splash.close()
|
splash.close()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|||||||
95
register_windows.bat
Normal file
95
register_windows.bat
Normal file
@@ -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
|
||||||
@@ -1 +1 @@
|
|||||||
3.0.0
|
3.1.0
|
||||||
|
|||||||
Reference in New Issue
Block a user