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:
Jules
2026-03-21 16:15:25 +01:00
parent 673d76caa2
commit 1d48e103e3
7 changed files with 223 additions and 38 deletions

View File

@@ -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

View File

@@ -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)

View 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()

View File

@@ -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():