start-gui.sh et améliorations diverses

This commit is contained in:
2026-09-08 12:04:18 +02:00
parent 06d7bad04e
commit db4ed2ef31
14 changed files with 697 additions and 50 deletions
+120 -2
View File
@@ -279,6 +279,8 @@ class CopienatorApp(tk.Tk):
ttk.Label(command_box, textvariable=self.command_var, wraplength=690, justify="left").grid(
row=0, column=0, sticky="ew"
)
self.copy_command_button = ttk.Button(command_box, text="Copier la commande", command=self._copy_command)
self.copy_command_button.grid(row=1, column=0, sticky="w", pady=(5, 0))
buttons = ttk.Frame(self.detail)
buttons.grid(row=4, column=0, sticky="ew", pady=(5, 0))
@@ -304,7 +306,22 @@ class CopienatorApp(tk.Tk):
foreground="#efefef",
insertbackground="white",
state="disabled",
takefocus=True,
exportselection=False,
)
self.console.bind("<Control-c>", self._copy_console_selection)
self.console.bind("<Control-Shift-C>", self._copy_console_selection)
self.console.bind("<Control-a>", self._select_console_all)
self.console.bind("<Button-1>", lambda _event: self.console.focus_set())
self.console_menu = tk.Menu(self.console, tearoff=False)
self.console_menu.add_command(label="Copier la sélection", command=self._copy_console_selection)
self.console_menu.add_command(label="Tout sélectionner", command=self._select_console_all)
self.console_menu.add_command(label="Copier toute la sortie", command=self._copy_console_all)
self.console.bind("<Button-3>", self._show_console_menu)
if self.tk.call("tk", "windowingsystem") == "aqua":
self.console.bind("<Command-c>", self._copy_console_selection)
self.console.bind("<Command-a>", self._select_console_all)
self.console.bind("<Button-2>", self._show_console_menu)
console_scroll = ttk.Scrollbar(console_frame, orient="vertical", command=self.console.yview)
self.console.configure(yscrollcommand=console_scroll.set)
self.console.grid(row=0, column=0, sticky="nsew")
@@ -313,6 +330,10 @@ class CopienatorApp(tk.Tk):
console_actions = ttk.Frame(console_frame)
console_actions.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(5, 0))
console_actions.columnconfigure(1, weight=1)
clipboard_actions = ttk.Frame(console_actions)
clipboard_actions.grid(row=1, column=0, columnspan=6, sticky="w", pady=(5, 0))
ttk.Button(clipboard_actions, text="Copier la sélection", command=self._copy_console_selection).pack(side="left")
ttk.Button(clipboard_actions, text="Copier toute la sortie", command=self._copy_console_all).pack(side="left", padx=6)
ttk.Label(console_actions, text="Réponse au script").grid(row=0, column=0, padx=(0, 6))
self.stdin_var = tk.StringVar()
self.stdin_entry = ttk.Entry(console_actions, textvariable=self.stdin_var)
@@ -467,7 +488,13 @@ class CopienatorApp(tk.Tk):
if not evaluation:
return list(step.requires)
missing = []
for pattern in step.requires:
requirements = step.requires
if step.id == "statement":
variant = (self.variant_var.get() if self.current_step and self.current_step.id == step.id
else self.state_store.step(step.id).get("variant", "gemini"))
if variant == "personal":
requirements = ("enonce.tex",)
for pattern in requirements:
if any(char in pattern for char in "*?["):
exists = next(evaluation.glob(pattern), None) is not None
else:
@@ -475,8 +502,19 @@ class CopienatorApp(tk.Tk):
exists = path.exists()
if not exists:
missing.append(pattern)
if step.id == "inputs" and not ((evaluation / "names").is_file() or (self.repository / "names").is_file()):
missing.append("names")
return missing
def _reload_inputs(self) -> None:
if not self.state_store.evaluation or self.active_step_id or self.runner.running:
return
missing = self._missing_requirements(self.step_by_id["inputs"])
self.state_store.update_step("inputs", status="ready" if missing else "success")
self._populate_tree()
self._render_step()
self.info_var.set("Fichiers revérifiés — " + ("manquants : " + ", ".join(missing) if missing else "tous les fichiers dentrée sont présents."))
def _on_tree_select(self, _event: tk.Event[Any] | None = None) -> None:
selection = self.tree.selection()
if not selection or selection[0].startswith("section:"):
@@ -561,6 +599,10 @@ class CopienatorApp(tk.Tk):
self.title_label.configure(text=step.title)
missing = self._missing_requirements(step)
description = step.description
if step.id == "statement" and saved_variant == "personal":
description = ("Lit les blocs SHEETINFO de enonce.tex et récupère les énoncés, solutions "
"et barèmes du service personnel (localhost:8080). Crée un groupe par exercice. "
"Les actions Gemini ci-dessous restent facultatives.")
if missing:
description += "\nPrérequis non détectés : " + ", ".join(missing)
self.description_label.configure(text=description)
@@ -633,6 +675,19 @@ class CopienatorApp(tk.Tk):
self._update_controls()
def _render_context_controls(self, step: StepDefinition, row: int) -> int:
if self.show_personal_steps and step.id in {"statement", "statement_groups", "statement_persp", "review_persp"}:
actions = ttk.LabelFrame(self.form, text="Après génération — facultatif", padding=7)
actions.grid(row=row, column=0, columnspan=2, sticky="ew", pady=(0, 8))
for ident, title in (("statement_groups", "Regrouper avec Gemini…"),
("statement_persp", "Remplacer Persp avec Gemini…")):
ttk.Button(actions, text=title, command=lambda target=ident: self._select_statement_action(target)).pack(
side="left", padx=(0, 6))
row += 1
if step.id == "inputs":
ttk.Button(self.form, text="Recharger — vérifier à nouveau", command=self._reload_inputs).grid(
row=row, column=0, columnspan=2, sticky="w", pady=(0, 8)
)
return row + 1
if step.section == REFAIRE_SECTION:
evaluation = self.evaluation
if step.id in {"refaire_selection", "refaire_correct"}:
@@ -675,7 +730,8 @@ class CopienatorApp(tk.Tk):
ttk.Label(copies, text=summary, wraplength=570, justify="left").grid(
row=0, column=0, columnspan=2, sticky="ew"
)
self.copy_var.set(names[0] if names else "")
if self.copy_var.get() not in names:
self.copy_var.set(names[0] if names else "")
selector = ttk.Combobox(
copies,
textvariable=self.copy_var,
@@ -689,6 +745,14 @@ class CopienatorApp(tk.Tk):
command=self._open_selected_copy,
state="normal" if names else "disabled",
).grid(row=1, column=1, padx=(6, 0), pady=(7, 0))
if step.id == "page_splitter":
actions = ttk.Frame(copies)
actions.grid(row=2, column=0, columnspan=2, sticky="w", pady=(7, 0))
ttk.Button(actions, text="Refaire la copie sélectionnée", command=self._redo_selected_pages,
state="normal" if names else "disabled").pack(side="left")
ttk.Button(actions, text="Cibler tout le dossier", command=self._target_all_pages).pack(side="left", padx=6)
ttk.Label(copies, text="La reprise utilise loriginal conservé. Vérifiez la commande puis cliquez sur Exécuter.",
wraplength=570).grid(row=3, column=0, columnspan=2, sticky="w", pady=(5, 0))
row += 1
if step.id == "plotting":
@@ -702,6 +766,14 @@ class CopienatorApp(tk.Tk):
row += 1
return row
def _select_statement_action(self, step_id: str) -> None:
self._save_current_form()
target = self.arg_vars.get("target")
if target:
self.state_store.update_step(step_id, values={"target": target.get()})
self.tree.selection_set(step_id)
self.tree.see(step_id)
def _refaire_restart_controls(self, row: int) -> int:
controls = ttk.Frame(self.form)
controls.grid(row=row, column=0, columnspan=2, sticky="w", pady=(0, 5))
@@ -780,6 +852,16 @@ class CopienatorApp(tk.Tk):
def _open_selected_copy(self) -> None:
self._open_desktop_path(self.copy_paths.get(self.copy_var.get()), "copie")
def _redo_selected_pages(self) -> None:
path = self.copy_paths.get(self.copy_var.get())
if path and "target" in self.arg_vars:
self.arg_vars["target"].set(str(path))
self.info_var.set(f"Reprise de {path.name} prête — cliquez sur Exécuter.")
def _target_all_pages(self) -> None:
if "target" in self.arg_vars:
self.arg_vars["target"].set(self._evaluation_arg())
def _open_desktop_path(self, path: Path | None, label: str) -> None:
if path is None or not path.exists():
messagebox.showerror("Élément introuvable", f"Le {label} nexiste pas encore.")
@@ -907,6 +989,7 @@ class CopienatorApp(tk.Tk):
def _update_command_preview(self) -> None:
if self._rendering or not self.current_step:
return
self.copy_command_button.configure(state="disabled")
if self.current_step.is_manual:
self.command_var.set("La sélection sera utilisée pour toutes les étapes de ce parcours." if self.current_step.id == "refaire_selection" else "Étape manuelle — aucune commande ne sera exécutée.")
return
@@ -916,9 +999,44 @@ class CopienatorApp(tk.Tk):
if len(commands) > 2:
preview += f"\nPuis {len(commands) - 2} autres copies, successivement."
self.command_var.set(preview)
self.copy_command_button.configure(state="normal")
except (OSError, ValueError, TypeError) as exc:
self.command_var.set(f"Arguments invalides : {exc}")
def _copy_text(self, text: str) -> None:
self.clipboard_clear()
self.clipboard_append(text)
def _copy_command(self) -> None:
if not self.current_step or self.current_step.is_manual:
return
try:
commands = self._refaire_commands() if self.current_step.section == REFAIRE_SECTION else [self._make_command()]
self._copy_text("\n".join(command_display(command) for command in commands))
self.info_var.set("Commande(s) copiée(s) — à exécuter depuis " + str(self.repository))
except (OSError, ValueError, TypeError) as exc:
self.info_var.set(f"Copie impossible : {exc}")
def _copy_console_selection(self, _event=None) -> str:
if self.console.tag_ranges("sel"):
self._copy_text(self.console.get("sel.first", "sel.last"))
return "break"
def _select_console_all(self, _event=None) -> str:
self.console.tag_add("sel", "1.0", "end-1c")
self.console.focus_set()
return "break"
def _copy_console_all(self) -> None:
self._copy_text(self.console.get("1.0", "end-1c"))
def _show_console_menu(self, event) -> str:
try:
self.console_menu.tk_popup(event.x_root, event.y_root)
finally:
self.console_menu.grab_release()
return "break"
def _browse_target_file(self, variable: tk.Variable) -> None:
selected = filedialog.askopenfilename(initialdir=self.evaluation or self.repository)
if selected: