; Manager.ahk — Le Gestionnaire : bibliothèque en trois volets (architecture §9.1). class PromptManager { GuiObj := "" TV := "" LV := "" Preview := "" Status := "" NodeFilter := Map() ; id de nœud TreeView -> filtre (catégorie ou __all__/__fav__/__recent__/__errors__) Items := [] Filter := "__all__" OnUse := "" ; Func(prompt) OnEdit := "" ; Func(prompt) Build(onUse, onEdit) { this.OnUse := onUse this.OnEdit := onEdit g := Gui("-MaximizeBox +Resize", "PromptKey — Gestionnaire") g.SetFont("s10", "Segoe UI") this.GuiObj := g this.TV := g.Add("TreeView", "xm w180 r18") this.TV.OnEvent("ItemSelect", (tv, id) => this.NodeFilter.Has(id) ? this.SetFilter(this.NodeFilter[id]) : "") this.LV := g.Add("ListView", "x+10 w430 r18", ["Titre", "Modifié", "Utilisations"]) this.LV.ModifyCol(1, 240) this.LV.ModifyCol(2, 90) this.LV.ModifyCol(3, 95) this.LV.OnEvent("ItemSelect", (lv, row, selected) => selected ? this.UpdatePreview(row) : "") this.LV.OnEvent("DoubleClick", (lv, row) => row > 0 ? this.EditSelected() : "") this.Preview := g.Add("Edit", "x+10 w330 r18 ReadOnly -Wrap", "") btnUse := g.Add("Button", "xm w130", "Utiliser") btnUse.OnEvent("Click", (*) => this.UseSelected()) btnEdit := g.Add("Button", "x+8 w130", "Modifier") btnEdit.OnEvent("Click", (*) => this.EditSelected()) btnNew := g.Add("Button", "x+8 w130", "Nouveau") btnNew.OnEvent("Click", (*) => App.NewPromptInteractive()) btnClip := g.Add("Button", "x+8 w180", "Nouveau depuis le presse-papiers") btnClip.OnEvent("Click", (*) => this.NewFromClipboard()) btnDup := g.Add("Button", "xm w130", "Dupliquer") btnDup.OnEvent("Click", (*) => this.DuplicateSelected()) btnDel := g.Add("Button", "x+8 w130", "Supprimer") btnDel.OnEvent("Click", (*) => this.DeleteSelected()) btnFolder := g.Add("Button", "x+8 w160", "Ouvrir le dossier") btnFolder.OnEvent("Click", (*) => Run(App.Settings.LibraryPath)) btnReload := g.Add("Button", "x+8 w130", "Recharger") btnReload.OnEvent("Click", (*) => this.Refresh()) this.Status := g.Add("Text", "xm w950", "") g.OnEvent("Close", (*) => g.Hide()) g.OnEvent("Escape", (*) => g.Hide()) } Show() { this.Refresh() this.GuiObj.Show("w990") } Hide() { this.GuiObj.Hide() } SetFilter(filter) { this.Filter := filter this.RefreshList() } Refresh() { App.Store.Load() this.NodeFilter := Map() this.TV.Delete() root := this.TV.Add("Bibliothèque") this.NodeFilter[root] := "__all__" for cat in App.Store.Categories() { node := this.TV.Add(cat, root) this.NodeFilter[node] := cat } fav := this.TV.Add("★ Favoris", root) this.NodeFilter[fav] := "__fav__" rec := this.TV.Add("◷ Récents", root) this.NodeFilter[rec] := "__recent__" if (App.Store.ErrorFiles.Length > 0) { err := this.TV.Add("⚠ En erreur (" App.Store.ErrorFiles.Length ")", root) this.NodeFilter[err] := "__errors__" } this.TV.Modify(root, "Expand") this.RefreshList() } FilteredPrompts() { out := [] switch this.Filter { case "__all__": return App.Store.Prompts case "__fav__": for p in App.Store.Prompts { if p.Favorite out.Push(p) } case "__recent__": for p in App.Store.Prompts { if (App.Usage && App.Usage.Data.Has(p.Id)) out.Push(p) } case "__errors__": return App.Store.ErrorFiles default: for p in App.Store.Prompts { if (p.Category = this.Filter) out.Push(p) } } return out } RefreshList() { this.Items := this.FilteredPrompts() this.LV.Delete() for p in this.Items { count := App.Usage ? App.Usage.Count(p.Id) : 0 marker := (p.Favorite ? "★ " : "") (p.Pinned ? "📌 " : "") this.LV.Add("", marker p.Title, p.Updated, count) } if (this.Items.Length > 0) { this.LV.Modify(1, "Select") this.UpdatePreview(1) } else { this.Preview.Value := "" } this.Status.Text := App.Store.Prompts.Length " prompt(s) dans la bibliothèque · filtre : " this.Filter } UpdatePreview(row) { if (row < 1 || row > this.Items.Length) return p := this.Items[row] txt := p.Body if (p.ParseError != "") txt := "⚠ Erreur d'en-tête : " p.ParseError "`r`nFichier : " p.Path "`r`n`r`n" txt else txt := "Catégorie : " p.Category " · Étiquettes : " Join(p.Tags, ", ") "`r`n" (p.Hotkey != "" ? "Raccourci : " p.Hotkey "`r`n" : "") (p.Hotstring != "" ? "Hotstring : ::" p.Hotstring "`r`n" : "") "`r`n" txt this.Preview.Value := txt } SelectedPrompt() { row := this.LV.GetNext() if (row >= 1 && row <= this.Items.Length) return this.Items[row] return "" } UseSelected() { p := this.SelectedPrompt() if (p = "") return this.Hide() this.OnUse(p) } EditSelected() { p := this.SelectedPrompt() if (p = "") return this.OnEdit(p) } DuplicateSelected() { p := this.SelectedPrompt() if (p = "") return App.Store.Duplicate(p) this.Refresh() } DeleteSelected() { p := this.SelectedPrompt() if (p = "") return r := MsgBox("Envoyer « " p.Title " » à la corbeille ?", "PromptKey", "YesNo Icon?") if (r != "Yes") return App.Store.Delete(p) App.RefreshAfterLibraryChange() this.Refresh() } NewFromClipboard() { txt := ClipboardGuard.ClipboardText() if (Trim(txt) = "") { TrayTip("Le presse-papiers ne contient pas de texte.", "PromptKey") return } ib := InputBox("Titre du nouveau prompt :", "PromptKey — Depuis le presse-papiers") if (ib.Result != "OK" || Trim(ib.Value) = "") return item := App.Store.CreateNew(Trim(ib.Value), "Sans catégorie", txt) App.RefreshAfterLibraryChange() this.Refresh() TrayTip("Prompt « " item.Title " » créé depuis le presse-papiers.", "PromptKey") } }