"""Gestionnaire de snippets : creer, modifier, supprimer, rechercher, organiser par categorie.""" from __future__ import annotations from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QPushButton, QMessageBox, QLineEdit, QLabel, QFormLayout, QTextEdit, QDialogButtonBox, QComboBox, ) from app.snippets import Snippet, SnippetStore SNIPPET_ROLE = Qt.UserRole class SnippetEditDialog(QDialog): def __init__(self, snippet: Snippet | None = None, categories: list[str] | None = None, parent=None): super().__init__(parent) self.snippet = snippet or Snippet() self.setWindowTitle("Nouveau snippet" if snippet is None else "Modifier le snippet") self.setMinimumWidth(420) layout = QVBoxLayout(self) form = QFormLayout() self.name_edit = QLineEdit(self.snippet.name) self.category_combo = QComboBox() self.category_combo.setEditable(True) for cat in categories or []: self.category_combo.addItem(cat) self.category_combo.setCurrentText(self.snippet.category) self.description_edit = QLineEdit(self.snippet.description) self.command_edit = QTextEdit(self.snippet.command) self.command_edit.setFixedHeight(80) self.command_edit.setPlaceholderText("ex : docker ps -a") form.addRow("Nom :", self.name_edit) form.addRow("Categorie :", self.category_combo) form.addRow("Description :", self.description_edit) form.addRow("Commande :", self.command_edit) layout.addLayout(form) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self._on_accept) buttons.rejected.connect(self.reject) layout.addWidget(buttons) def _on_accept(self) -> None: if not self.name_edit.text().strip(): QMessageBox.warning(self, "Nom manquant", "Indiquez un nom pour ce snippet.") self.name_edit.setFocus() return if not self.command_edit.toPlainText().strip(): QMessageBox.warning(self, "Commande manquante", "Indiquez la commande du snippet.") self.command_edit.setFocus() return self.snippet.name = self.name_edit.text().strip() self.snippet.category = self.category_combo.currentText().strip() self.snippet.description = self.description_edit.text().strip() self.snippet.command = self.command_edit.toPlainText().rstrip("\n") self.accept() def result_snippet(self) -> Snippet: return self.snippet class SnippetManagerDialog(QDialog): def __init__(self, store: SnippetStore, parent=None): super().__init__(parent) self.store = store self.setWindowTitle("Gestionnaire de snippets") self.setMinimumSize(560, 420) layout = QVBoxLayout(self) self.search_edit = QLineEdit() self.search_edit.setPlaceholderText("Rechercher (nom, categorie, description, commande)...") self.search_edit.textChanged.connect(self.refresh) layout.addWidget(self.search_edit) self.tree = QTreeWidget() self.tree.setHeaderLabels(["Nom", "Description"]) self.tree.setColumnWidth(0, 220) self.tree.itemDoubleClicked.connect(lambda _i, _c: self.edit_selected()) self.tree.itemSelectionChanged.connect(self._update_preview) layout.addWidget(self.tree) self.command_preview = QLabel("") self.command_preview.setWordWrap(True) self.command_preview.setObjectName("statusLabel") layout.addWidget(self.command_preview) btn_row = QHBoxLayout() self.new_btn = QPushButton("Nouveau...") self.edit_btn = QPushButton("Modifier...") self.delete_btn = QPushButton("Supprimer") for b in (self.new_btn, self.edit_btn, self.delete_btn): btn_row.addWidget(b) layout.addLayout(btn_row) close_row = QHBoxLayout() close_row.addStretch() close_btn = QPushButton("Fermer") close_btn.clicked.connect(self.accept) close_row.addWidget(close_btn) layout.addLayout(close_row) self.new_btn.clicked.connect(self.create_new) self.edit_btn.clicked.connect(self.edit_selected) self.delete_btn.clicked.connect(self.delete_selected) self.refresh() def refresh(self) -> None: self.tree.clear() query = self.search_edit.text() matches = {s.id for s in self.store.search(query)} if query.strip() else None for category, snippets in self.store.by_category().items(): visible = [s for s in snippets if matches is None or s.id in matches] if not visible: continue cat_item = QTreeWidgetItem([category]) cat_item.setFlags(cat_item.flags() & ~Qt.ItemIsSelectable) font = cat_item.font(0) font.setBold(True) cat_item.setFont(0, font) self.tree.addTopLevelItem(cat_item) for snippet in visible: item = QTreeWidgetItem([snippet.name, snippet.description]) item.setData(0, SNIPPET_ROLE, snippet.id) cat_item.addChild(item) self.tree.expandAll() self._update_preview() def _current_snippet(self) -> Snippet | None: item = self.tree.currentItem() if not item: return None snippet_id = item.data(0, SNIPPET_ROLE) return self.store.get(snippet_id) if snippet_id else None def _update_preview(self) -> None: snippet = self._current_snippet() self.command_preview.setText(snippet.command if snippet else "") has_selection = snippet is not None self.edit_btn.setEnabled(has_selection) self.delete_btn.setEnabled(has_selection) def create_new(self) -> None: dialog = SnippetEditDialog(categories=self.store.categories(), parent=self) if dialog.exec(): self.store.add(dialog.result_snippet()) self.refresh() def edit_selected(self) -> None: snippet = self._current_snippet() if not snippet: return dialog = SnippetEditDialog(snippet=snippet, categories=self.store.categories(), parent=self) if dialog.exec(): self.store.update(dialog.result_snippet()) self.refresh() def delete_selected(self) -> None: snippet = self._current_snippet() if not snippet: return if QMessageBox.question(self, "Supprimer", f"Supprimer le snippet « {snippet.name} » ?") == QMessageBox.Yes: self.store.delete(snippet.id) self.refresh()