Initial support for user general remarks
This commit is contained in:
1 parent
a38c1ff4b3
commit
2c9dc65640
16 files changed
+606
-20
No files matched your search
+32
-3
@@ -29,6 +29,7 @@ from .diagnostics import collect_diagnostics
|
||||
from .batch_monitor import BatchMonitor
|
||||
from .giving_names import GivingNamesPanel, find_name_issues
|
||||
from .scores import ScoreEditorPanel
|
||||
from .general_remarks import GeneralRemarksPanel
|
||||
from .notifications import notify_desktop
|
||||
from .syncthing import SyncthingPanel
|
||||
from .manual_resolution import ManualResolutionPanel
|
||||
@@ -285,6 +286,7 @@ class CopienatorApp(tk.Tk):
|
||||
self._rendering = False
|
||||
self.refaire_panel: RefaireSelection | None = None
|
||||
self.giving_names_panel: GivingNamesPanel | None = None
|
||||
self.general_remarks_panel = None
|
||||
self.score_editor_panel: ScoreEditorPanel | None = None
|
||||
self.complete_giving_names_button: ttk.Button | None = None
|
||||
self.label_groups_button: ttk.Button | None = None
|
||||
@@ -816,7 +818,7 @@ class CopienatorApp(tk.Tk):
|
||||
if not step:
|
||||
return
|
||||
self._rendering = True
|
||||
if step.id in {"personal_read", "edit_scores"}:
|
||||
if step.id in {"personal_read", "edit_scores", "general_remarks"}:
|
||||
self.command_box.grid_remove()
|
||||
else:
|
||||
self.command_box.grid()
|
||||
@@ -831,6 +833,7 @@ class CopienatorApp(tk.Tk):
|
||||
"modify_annotations",
|
||||
"giving_names",
|
||||
"edit_scores",
|
||||
"general_remarks",
|
||||
"statement_persp",
|
||||
}
|
||||
self.console.configure(height=8 if expanded_form else 14)
|
||||
@@ -844,6 +847,7 @@ class CopienatorApp(tk.Tk):
|
||||
self.arg_vars.clear()
|
||||
self.refaire_panel = None
|
||||
self.giving_names_panel = None
|
||||
self.general_remarks_panel = None
|
||||
self.score_editor_panel = None
|
||||
self.complete_giving_names_button = None
|
||||
self.label_groups_button = None
|
||||
@@ -1084,6 +1088,12 @@ class CopienatorApp(tk.Tk):
|
||||
)
|
||||
row += 1
|
||||
return row
|
||||
if step.id == "general_remarks" and self.evaluation:
|
||||
self.general_remarks_panel = GeneralRemarksPanel(
|
||||
self.form, self.evaluation, self._general_remarks_changed
|
||||
)
|
||||
self.general_remarks_panel.grid(row=row, column=0, columnspan=2, sticky="ew", pady=8)
|
||||
row += 1
|
||||
if step.id == "edit_scores" and self.evaluation:
|
||||
self.score_editor_panel = ScoreEditorPanel(
|
||||
self.form, self.evaluation, self._score_changed, self._remark_changed
|
||||
@@ -1644,7 +1654,7 @@ class CopienatorApp(tk.Tk):
|
||||
if (step.is_manual or step.id == "personal_create"
|
||||
or (step.id == "personal_read" and review_command is None)):
|
||||
self._mark_step("success")
|
||||
if step.id == "edit_scores":
|
||||
if step.id == "general_remarks":
|
||||
entry = self.state_store.step("read_annotations")
|
||||
values = dict(entry.get("values", {}))
|
||||
values["update_score"] = True
|
||||
@@ -1778,7 +1788,7 @@ class CopienatorApp(tk.Tk):
|
||||
return
|
||||
self._save_current_form()
|
||||
# Score edits already invalidate downstream steps when Set saves a change.
|
||||
if self.current_step.id != "edit_scores":
|
||||
if self.current_step.id not in {"edit_scores", "general_remarks"}:
|
||||
self._invalidate_after_step(self.current_step, self._current_variant().id)
|
||||
self.state_store.update_step(self.current_step.id, status=status)
|
||||
history: dict[str, object] = {
|
||||
@@ -1808,6 +1818,23 @@ class CopienatorApp(tk.Tk):
|
||||
self._populate_tree()
|
||||
self.info_var.set("Renommage validé — aucun fichier modifié.")
|
||||
|
||||
def _general_remarks_changed(self, copy_id, previous, current):
|
||||
step = self.step_by_id["general_remarks"]
|
||||
self._invalidate_after_step(step, "default")
|
||||
self.state_store.update_step(step.id, status="detected")
|
||||
entry = self.state_store.step("read_annotations")
|
||||
values = dict(entry.get("values", {}))
|
||||
values["update_score"] = True
|
||||
self.state_store.update_step("read_annotations", status="stale", values=values)
|
||||
self.state_store.add_history({
|
||||
"step": step.id, "copy_id": copy_id, "previous_remarks": previous,
|
||||
"remarks": current, "manual": True,
|
||||
})
|
||||
for item in self.steps:
|
||||
if self.tree.exists(item.id):
|
||||
status = self._step_status(item)
|
||||
self.tree.item(item.id, values=(STATUS_LABELS.get(status, status),))
|
||||
|
||||
def _remark_changed(self, copy_id: str, label: str, previous: str, current: str) -> None:
|
||||
self._score_changed(copy_id, label, previous, current, remark=True)
|
||||
|
||||
@@ -2243,6 +2270,8 @@ class CopienatorApp(tk.Tk):
|
||||
self.complete_giving_names_button.configure(
|
||||
state="disabled" if running else "normal"
|
||||
)
|
||||
if self.general_remarks_panel is not None:
|
||||
self.general_remarks_panel.set_busy(running)
|
||||
if self.score_editor_panel is not None:
|
||||
self.score_editor_panel.set_busy(running)
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
"""Editor for ordered, copy-level LaTeX remarks."""
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, ttk
|
||||
|
||||
from copienator.general_remarks import remarks_for_copy, set_remarks
|
||||
from .scores import score_path_for_id
|
||||
|
||||
|
||||
class GeneralRemarksPanel(ttk.Frame):
|
||||
def __init__(self, parent, evaluation, on_change):
|
||||
super().__init__(parent)
|
||||
self.evaluation, self.on_change = evaluation, on_change
|
||||
self.busy = False
|
||||
self.valid = False
|
||||
self.remarks = []
|
||||
self.id_var = tk.StringVar()
|
||||
self.status_var = tk.StringVar()
|
||||
self.columnconfigure(1, weight=1)
|
||||
ttk.Label(self, text="Id de copie").grid(row=0, column=0, sticky="w")
|
||||
self.id_entry = ttk.Entry(self, textvariable=self.id_var)
|
||||
self.id_entry.grid(row=0, column=1, sticky="ew")
|
||||
self.listbox = tk.Listbox(self, height=4, exportselection=False)
|
||||
self.listbox.grid(row=1, column=0, columnspan=2, sticky="ew", pady=6)
|
||||
self.listbox.bind("<<ListboxSelect>>", self.select)
|
||||
self.editor = tk.Text(self, height=5, width=50, wrap="word", undo=True)
|
||||
self.editor.grid(row=2, column=0, columnspan=2, sticky="ew")
|
||||
actions = ttk.Frame(self)
|
||||
actions.grid(row=3, column=0, columnspan=2, sticky="w", pady=6)
|
||||
self.buttons = []
|
||||
for text, command in (
|
||||
("Nouvelle", self.new), ("Enregistrer", self.save),
|
||||
("Supprimer", self.delete), ("Monter", lambda: self.move(-1)),
|
||||
("Descendre", lambda: self.move(1)),
|
||||
):
|
||||
button = ttk.Button(actions, text=text, command=command)
|
||||
button.pack(side="left", padx=(0, 5))
|
||||
self.buttons.append(button)
|
||||
ttk.Label(self, text="Texte LaTeX ; chaque remarque forme un paragraphe.").grid(
|
||||
row=4, column=0, columnspan=2, sticky="w")
|
||||
ttk.Label(self, textvariable=self.status_var, wraplength=650).grid(
|
||||
row=5, column=0, columnspan=2, sticky="w", pady=6)
|
||||
self.id_var.trace_add("write", self.load)
|
||||
self.load()
|
||||
|
||||
def load(self, *_):
|
||||
self.valid = False
|
||||
self.remarks = []
|
||||
try:
|
||||
path = score_path_for_id(self.evaluation, self.id_var.get())
|
||||
self.remarks = remarks_for_copy(self.evaluation, self.id_var.get())
|
||||
self.valid = True
|
||||
self.status_var.set(path.parent.name)
|
||||
except (OSError, ValueError) as exc:
|
||||
self.status_var.set(str(exc))
|
||||
self.refresh()
|
||||
self.set_busy(self.busy)
|
||||
|
||||
def refresh(self, index=None):
|
||||
self.listbox.configure(state="normal")
|
||||
self.listbox.delete(0, "end")
|
||||
for i, text in enumerate(self.remarks, 1):
|
||||
self.listbox.insert("end", f"{i}. {' '.join(text.split())}")
|
||||
self.editor.configure(state="normal")
|
||||
self.editor.delete("1.0", "end")
|
||||
if index is not None:
|
||||
self.listbox.selection_set(index)
|
||||
self.listbox.see(index)
|
||||
self.editor.insert("1.0", self.remarks[index])
|
||||
self.editor.edit_reset()
|
||||
|
||||
def select(self, *_):
|
||||
if self.busy:
|
||||
return
|
||||
selected = self.listbox.curselection()
|
||||
if selected:
|
||||
self.editor.delete("1.0", "end")
|
||||
self.editor.insert("1.0", self.remarks[selected[0]])
|
||||
self.editor.edit_reset()
|
||||
|
||||
def new(self):
|
||||
if self.valid and not self.busy:
|
||||
self.refresh()
|
||||
self.editor.focus_set()
|
||||
|
||||
def commit(self, remarks, index=None):
|
||||
try:
|
||||
previous = set_remarks(self.evaluation, self.id_var.get(), remarks)
|
||||
self.remarks = remarks
|
||||
self.refresh(index)
|
||||
if previous != remarks:
|
||||
self.on_change(self.id_var.get(), previous, remarks)
|
||||
self.status_var.set("Remarques enregistrées — lecture des annotations à revalider, « Régénérer » coché.")
|
||||
except (OSError, ValueError) as exc:
|
||||
messagebox.showerror("Remarques générales", str(exc))
|
||||
|
||||
def save(self):
|
||||
if not self.valid or self.busy:
|
||||
return
|
||||
text = self.editor.get("1.0", "end-1c").strip()
|
||||
if not text:
|
||||
self.status_var.set("Saisissez une remarque ou utilisez « Supprimer ».")
|
||||
return
|
||||
remarks = self.remarks.copy()
|
||||
selected = self.listbox.curselection()
|
||||
index = selected[0] if selected else len(remarks)
|
||||
if selected:
|
||||
remarks[index] = text
|
||||
else:
|
||||
remarks.append(text)
|
||||
self.commit(remarks, index)
|
||||
|
||||
def delete(self):
|
||||
selected = self.listbox.curselection()
|
||||
if self.valid and not self.busy and selected:
|
||||
remarks = self.remarks.copy()
|
||||
remarks.pop(selected[0])
|
||||
self.commit(remarks)
|
||||
|
||||
def move(self, offset):
|
||||
selected = self.listbox.curselection()
|
||||
if not self.valid or self.busy or not selected:
|
||||
return
|
||||
index = selected[0]
|
||||
target = index + offset
|
||||
if 0 <= target < len(self.remarks):
|
||||
remarks = self.remarks.copy()
|
||||
remarks[index], remarks[target] = remarks[target], remarks[index]
|
||||
self.commit(remarks, target)
|
||||
|
||||
def set_busy(self, busy):
|
||||
self.busy = busy
|
||||
self.id_entry.configure(state="disabled" if busy else "normal")
|
||||
state = "normal" if self.valid and not busy else "disabled"
|
||||
self.listbox.configure(state=state)
|
||||
self.editor.configure(state=state)
|
||||
for button in self.buttons:
|
||||
button.configure(state=state)
|
||||
@@ -765,6 +765,17 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
optional=True,
|
||||
requires=("A Rendre",),
|
||||
),
|
||||
StepDefinition(
|
||||
"general_remarks",
|
||||
"Finalisation",
|
||||
"Remarques générales",
|
||||
"Ajoutez, modifiez, supprimez ou réordonnez les remarques générales de chaque copie. "
|
||||
"Enregistrez chaque remarque avant de changer de sélection. "
|
||||
"Elles seront composées en LaTeX en tête des sorties lors de la régénération.",
|
||||
(manual("default"),),
|
||||
optional=True,
|
||||
requires=("A Rendre",),
|
||||
),
|
||||
StepDefinition(
|
||||
"personal_create",
|
||||
"Étapes personnelles",
|
||||
|
||||
Reference in new issue
Block a user