miscs (Interro02) : horizontal cutting resolution
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
from tkinter import messagebox, ttk
|
||||
|
||||
from copienator import CliError
|
||||
from copienator.commands.resolve_manual import get_actual_pdf, parse_instruction_text
|
||||
from copienator.platform import open_path
|
||||
from .cut_helper import CutHelper
|
||||
|
||||
|
||||
HELP = """La correction propose x> pour un mauvais label et -> pour une réponse supplémentaire lorsque le PDF cible existe déjà. Vérifiez les deux PDF avant de choisir.
|
||||
|
||||
Format : Copie01 Label source OP Label cible|
|
||||
Conservez Copie suivi du numéro et les labels exacts (sans .pdf). Les espaces dans les labels sont acceptés ; entourez l’opérateur d’espaces.
|
||||
|
||||
-> : fusionner dans la cible, conserver la source.
|
||||
x> : fusionner dans la cible, archiver la source.
|
||||
-x : remplacer la cible par une copie de la source, conserver la source.
|
||||
xx : remplacer la cible par une copie de la source, archiver la source.
|
||||
ss : conserver les deux PDF sans fusion.
|
||||
sx : conserver la source, archiver la cible, sans fusion.
|
||||
xs : archiver la source, conserver la cible, sans fusion.
|
||||
|
||||
c{43}1> : couper la source à 43 %, conserver le début et ajouter la fin à la cible.
|
||||
c{43}2x : couper la source à 43 %, conserver la fin et remplacer la cible par le début.
|
||||
1 conserve le début, 2 conserve la fin ; > fusionne l’autre partie avec la cible, x la remplace. Le pourcentage porte sur la hauteur cumulée des pages visibles. Les décimales sont acceptées. Une seule coupe par label source est autorisée.
|
||||
Cut permet de placer la coupe, avec accrochage entre les pages. Entrée ferme l’aperçu et affiche la commande à recopier ci-dessus : aucun fichier n’est modifié. Une séparation entre pages conserve les pages entières. La source complète est archivée en _old ; les deux labels modifiés sont générés en _new et ajoutés à refaire.json.
|
||||
|
||||
Pour les fusions : « Source x> Cible| » place la cible avant la source ; « Source x> |Cible » place la source avant la cible. Même règle avec -> et c{…}1>/c{…}2> (pour la partie transférée) ; sans |, la cible vient en premier.
|
||||
Vous pouvez changer l’opérateur, déplacer |, corriger les labels ou retirer une instruction. Les lignes vides et celles commençant par ### sont ignorées. Retirer/commenter une ligne ne résout pas son conflit.
|
||||
|
||||
Enregistrez dans l’éditeur, puis cliquez sur Recharger et enfin sur Exécuter. Seul le fichier enregistré est appliqué. L’archivage utilise le suffixe _old ; les PDF créés utilisent _new. Après succès, manual_resolutions.txt est supprimé et correction.json est mis à jour. Si des PDF sont créés, refaire.json est généré : relancez la correction avec --refaire."""
|
||||
|
||||
|
||||
class ManualResolutionPanel(ttk.Frame):
|
||||
def __init__(self, parent, get_evaluation):
|
||||
super().__init__(parent)
|
||||
self.get_evaluation = get_evaluation
|
||||
self.pdf_buttons = []
|
||||
self.cut_buttons = []
|
||||
actions = ttk.Frame(self)
|
||||
actions.pack(fill="x")
|
||||
self.editor_button = ttk.Button(
|
||||
actions, text="Ouvrir dans un éditeur de texte", command=self.open_editor
|
||||
)
|
||||
self.editor_button.pack(side="left")
|
||||
ttk.Button(actions, text="Recharger", command=self.reload).pack(side="left", padx=6)
|
||||
self.cut_result = tk.StringVar()
|
||||
result = ttk.Frame(self)
|
||||
result.pack(fill="x", pady=4)
|
||||
ttk.Entry(result, textvariable=self.cut_result, state="readonly").pack(side="left", fill="x", expand=True)
|
||||
ttk.Button(result, text="Copier la commande Cut", command=self.copy_cut_command).pack(side="left", padx=6)
|
||||
self.status = ttk.Label(self, wraplength=650)
|
||||
self.status.pack(fill="x", pady=4)
|
||||
preview = ttk.Frame(self)
|
||||
preview.pack(fill="both", expand=True)
|
||||
self.text = tk.Text(preview, height=10, width=50, wrap="word", state="disabled")
|
||||
scroll = ttk.Scrollbar(preview, command=self.text.yview)
|
||||
self.text.configure(yscrollcommand=scroll.set)
|
||||
scroll.pack(side="right", fill="y")
|
||||
self.text.pack(fill="both", expand=True)
|
||||
ttk.Label(self, text=HELP, wraplength=650, justify="left").pack(fill="x", pady=8)
|
||||
self.reload()
|
||||
|
||||
def manual_path(self) -> Path | None:
|
||||
evaluation = self.get_evaluation()
|
||||
return evaluation / "manual_resolutions.txt" if evaluation else None
|
||||
|
||||
def open_editor(self):
|
||||
path = self.manual_path()
|
||||
if path is None or not path.is_file():
|
||||
self.reload()
|
||||
return
|
||||
try:
|
||||
# .txt is opened in the desktop's associated text editor.
|
||||
open_path(path)
|
||||
except (OSError, RuntimeError) as exc:
|
||||
messagebox.showerror("Ouverture impossible", str(exc))
|
||||
|
||||
def open_pdf(self, evaluation, copy_id, label):
|
||||
path = get_actual_pdf(evaluation / "Copies", copy_id, label)
|
||||
try:
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"PDF introuvable : {path}")
|
||||
open_path(path)
|
||||
except (OSError, RuntimeError) as exc:
|
||||
messagebox.showerror("Ouverture du PDF", str(exc))
|
||||
|
||||
def reload(self):
|
||||
for button in self.pdf_buttons + self.cut_buttons:
|
||||
button.destroy()
|
||||
self.pdf_buttons.clear()
|
||||
self.cut_buttons.clear()
|
||||
self.cut_result.set("")
|
||||
self.text.configure(state="normal")
|
||||
self.text.delete("1.0", "end")
|
||||
path = self.manual_path()
|
||||
self.editor_button.configure(state="disabled")
|
||||
try:
|
||||
if path is None:
|
||||
raise FileNotFoundError("Chargez une évaluation.")
|
||||
content = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeError) as exc:
|
||||
self.status.configure(text=f"manual_resolutions.txt indisponible : {exc}")
|
||||
else:
|
||||
self.editor_button.configure(state="normal")
|
||||
malformed = []
|
||||
for number, raw in enumerate(content.splitlines(keepends=True), 1):
|
||||
self.text.insert("end", raw.rstrip("\r\n"))
|
||||
try:
|
||||
instructions = parse_instruction_text(raw)
|
||||
except CliError:
|
||||
malformed.append(str(number))
|
||||
else:
|
||||
if instructions:
|
||||
instruction = instructions[0]
|
||||
for title, label in (("PDF source", instruction.old_label),
|
||||
("PDF cible", instruction.new_label)):
|
||||
button = ttk.Button(
|
||||
self.text, text=title,
|
||||
command=lambda label=label, copy_id=instruction.copy_id, root=path.parent: self.open_pdf(root, copy_id, label),
|
||||
)
|
||||
self.pdf_buttons.append(button)
|
||||
self.text.window_create("end", window=button, padx=8)
|
||||
button = ttk.Button(self.text, text="Cut",
|
||||
command=lambda item=instruction, root=path.parent: self.open_cut(root, item))
|
||||
self.cut_buttons.append(button)
|
||||
self.text.window_create("end", window=button, padx=8)
|
||||
if raw.endswith("\n"):
|
||||
self.text.insert("end", "\n")
|
||||
detail = (" — lignes invalides : " + ", ".join(malformed)) if malformed else (
|
||||
f" — {len(self.pdf_buttons) // 2} instruction(s)"
|
||||
)
|
||||
self.status.configure(text=str(path) + detail)
|
||||
finally:
|
||||
self.text.configure(state="disabled")
|
||||
|
||||
def copy_cut_command(self):
|
||||
if self.cut_result.get():
|
||||
self.clipboard_clear()
|
||||
self.clipboard_append(self.cut_result.get())
|
||||
|
||||
def open_cut(self, evaluation, instruction):
|
||||
source = get_actual_pdf(evaluation / "Copies", instruction.copy_id, instruction.old_label)
|
||||
def accepted(operator):
|
||||
target = ("|" + instruction.new_label) if instruction.pipe_first else (instruction.new_label + "|")
|
||||
self.cut_result.set(f"Copie{instruction.copy_id} {instruction.old_label} {operator} {target}")
|
||||
try:
|
||||
initial = (*instruction.cut, instruction.operator[-1]) if instruction.cut else None
|
||||
CutHelper(self, source, accepted, initial)
|
||||
except (OSError, RuntimeError, ValueError) as exc:
|
||||
messagebox.showerror("Ouverture du PDF", str(exc))
|
||||
Reference in New Issue
Block a user