docs(crepe-carottage): essai d'ui avec une variable et sa description
This commit is contained in:
parent
628f3fb7ca
commit
1fe0875a5a
3 changed files with 1317 additions and 0 deletions
468
doc/crepe.py
Normal file
468
doc/crepe.py
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
|
||||
from textual import events
|
||||
from typing import List
|
||||
|
||||
class CustomListItem(ListItem):
|
||||
"""ListItem personnalisé pour stocker le terme et sa définition"""
|
||||
def __init__(self, term: str, definition: str) -> None:
|
||||
super().__init__(Label(term))
|
||||
self.term = term
|
||||
self.definition = definition
|
||||
|
||||
class DictionaryApp(App):
|
||||
"""Application de dictionnaire avec autocomplétion et aperçu en temps réel"""
|
||||
|
||||
CSS = """
|
||||
Screen {
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#search-container {
|
||||
height: auto;
|
||||
min-height: 5;
|
||||
max-height: 8;
|
||||
margin-bottom: 1;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#results-container {
|
||||
height: 100%;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#search-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Input {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
border: solid #585b70;
|
||||
padding: 1;
|
||||
width: 100%;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
Input:focus {
|
||||
border: solid #89b4fa;
|
||||
}
|
||||
|
||||
#results-list {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
height: 100%;
|
||||
min-height: 10;
|
||||
border: solid #585b70;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ListItem {
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ListItem:hover {
|
||||
background: #45476a;
|
||||
}
|
||||
|
||||
ListItem:focus {
|
||||
background: #89b4fa;
|
||||
color: #1e1e2e;
|
||||
}
|
||||
|
||||
ListItem > Label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#results-label {
|
||||
color: #a6e3a1;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preview-container {
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
margin-top: 1;
|
||||
min-height: 3;
|
||||
max-height: 5;
|
||||
border: solid #585b70;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
#preview-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preview-content {
|
||||
color: #f9e2af;
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
min-height: 2;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#definition-box {
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
margin-top: 1;
|
||||
min-height: 8;
|
||||
max-height: 30;
|
||||
border: solid #585b70;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
#definition-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#definition-content {
|
||||
color: #cdd6f4;
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
height: 100%;
|
||||
min-height: 5;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highlight-term {
|
||||
color: #f9e2af;
|
||||
text-style: bold;
|
||||
background: #45476a;
|
||||
padding: 1;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Dictionnaire de termes avec leurs définitions
|
||||
self.dictionary = {
|
||||
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
|
||||
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
|
||||
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
|
||||
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
|
||||
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
|
||||
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
|
||||
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
|
||||
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
|
||||
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
|
||||
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
|
||||
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
|
||||
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
|
||||
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
|
||||
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
|
||||
"Docker": "Plateforme de conteneurisation pour applications distribuées",
|
||||
"Git": "Système de contrôle de version distribué pour le suivi de code",
|
||||
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
|
||||
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
|
||||
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
|
||||
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
|
||||
}
|
||||
|
||||
# Liste triée des termes pour la recherche
|
||||
self.terms = sorted(self.dictionary.keys())
|
||||
self.filtered_terms = self.terms.copy()
|
||||
self.current_input = ""
|
||||
self.last_selected_term = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose l'interface utilisateur"""
|
||||
yield Header()
|
||||
yield Container(
|
||||
Container(
|
||||
# Zone de recherche
|
||||
Container(
|
||||
Label("🔍 Rechercher un terme :", id="search-label"),
|
||||
Input(placeholder="Tapez un terme...", id="search-input"),
|
||||
id="search-container"
|
||||
),
|
||||
# Zone d'aperçu du texte en cours
|
||||
Container(
|
||||
Label("✏️ Texte saisi :", id="preview-label"),
|
||||
Static("En attente de saisie...", id="preview-content"),
|
||||
id="preview-container"
|
||||
),
|
||||
# Résultats et définition
|
||||
Container(
|
||||
Label("📋 0 terme trouvé", id="results-label"),
|
||||
ListView(id="results-list"),
|
||||
Container(
|
||||
Label("📖 Définition", id="definition-label"),
|
||||
Static("Sélectionnez un terme pour voir sa définition", id="definition-content"),
|
||||
id="definition-box"
|
||||
),
|
||||
id="results-container"
|
||||
),
|
||||
id="main-container"
|
||||
),
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Initialisation après le montage"""
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.focus()
|
||||
self.update_results()
|
||||
self.apply_responsive_styles()
|
||||
|
||||
def on_resize(self, event: events.Resize) -> None:
|
||||
"""Gère le redimensionnement du terminal"""
|
||||
self.apply_responsive_styles()
|
||||
self.update_list_height()
|
||||
|
||||
def apply_responsive_styles(self) -> None:
|
||||
"""Applique les styles responsifs en fonction de la taille du terminal"""
|
||||
width = self.size.width
|
||||
height = self.size.height
|
||||
|
||||
# Ajuster les paddings selon la largeur
|
||||
main_container = self.query_one("#main-container")
|
||||
search_container = self.query_one("#search-container")
|
||||
results_container = self.query_one("#results-container")
|
||||
preview_container = self.query_one("#preview-container")
|
||||
definition_box = self.query_one("#definition-box")
|
||||
definition_content = self.query_one("#definition-content")
|
||||
|
||||
if width < 80:
|
||||
# Petit terminal
|
||||
main_container.styles.padding = 1
|
||||
search_container.styles.padding = 1
|
||||
results_container.styles.padding = 1
|
||||
preview_container.styles.padding = 1
|
||||
definition_box.styles.padding = 1
|
||||
definition_content.styles.padding = 1
|
||||
definition_box.styles.min_height = 6
|
||||
definition_box.styles.max_height = 12
|
||||
preview_container.styles.min_height = 2
|
||||
preview_container.styles.max_height = 3
|
||||
|
||||
search_container.styles.min_height = 4
|
||||
search_container.styles.max_height = 6
|
||||
|
||||
elif width > 120 and height > 40:
|
||||
# Grand terminal
|
||||
main_container.styles.padding = 2
|
||||
search_container.styles.padding = 2
|
||||
results_container.styles.padding = 2
|
||||
preview_container.styles.padding = 2
|
||||
definition_box.styles.padding = 2
|
||||
definition_content.styles.padding = 2
|
||||
definition_box.styles.min_height = 12
|
||||
definition_box.styles.max_height = 30
|
||||
preview_container.styles.min_height = 4
|
||||
preview_container.styles.max_height = 6
|
||||
|
||||
search_container.styles.min_height = 6
|
||||
search_container.styles.max_height = 10
|
||||
|
||||
else:
|
||||
# Terminal moyen (par défaut)
|
||||
main_container.styles.padding = 1
|
||||
search_container.styles.padding = 1
|
||||
results_container.styles.padding = 1
|
||||
preview_container.styles.padding = 1
|
||||
definition_box.styles.padding = 1
|
||||
definition_content.styles.padding = 1
|
||||
definition_box.styles.min_height = 8
|
||||
definition_box.styles.max_height = 20
|
||||
preview_container.styles.min_height = 3
|
||||
preview_container.styles.max_height = 5
|
||||
|
||||
search_container.styles.min_height = 5
|
||||
search_container.styles.max_height = 8
|
||||
|
||||
# Ajuster la hauteur de la définition selon la hauteur du terminal
|
||||
if height < 30:
|
||||
definition_box.styles.min_height = 4
|
||||
definition_box.styles.max_height = 8
|
||||
preview_container.styles.min_height = 2
|
||||
preview_container.styles.max_height = 3
|
||||
elif height > 50:
|
||||
definition_box.styles.min_height = 10
|
||||
definition_box.styles.max_height = 25
|
||||
preview_container.styles.min_height = 4
|
||||
preview_container.styles.max_height = 6
|
||||
|
||||
def update_list_height(self) -> None:
|
||||
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
|
||||
try:
|
||||
results_list = self.query_one("#results-list")
|
||||
available_height = self.size.height - self.calculate_fixed_height()
|
||||
|
||||
# Ajuster la hauteur de la liste
|
||||
if available_height > 15:
|
||||
results_list.styles.height = available_height - 2
|
||||
elif available_height > 10:
|
||||
results_list.styles.height = available_height
|
||||
else:
|
||||
results_list.styles.height = 8
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def calculate_fixed_height(self) -> int:
|
||||
"""Calcule la hauteur fixe des éléments (header, footer, search, preview, definition)"""
|
||||
try:
|
||||
search_container = self.query_one("#search-container")
|
||||
preview_container = self.query_one("#preview-container")
|
||||
definition_box = self.query_one("#definition-box")
|
||||
|
||||
# Récupérer les hauteurs actuelles
|
||||
search_height = search_container.styles.min_height or 5
|
||||
preview_height = preview_container.styles.min_height or 3
|
||||
definition_height = definition_box.styles.min_height or 8
|
||||
|
||||
# Estimer la hauteur fixe totale
|
||||
if isinstance(search_height, (int, float)):
|
||||
search_h = search_height
|
||||
else:
|
||||
search_h = 5
|
||||
|
||||
if isinstance(preview_height, (int, float)):
|
||||
preview_h = preview_height
|
||||
else:
|
||||
preview_h = 3
|
||||
|
||||
if isinstance(definition_height, (int, float)):
|
||||
definition_h = definition_height
|
||||
else:
|
||||
definition_h = 8
|
||||
|
||||
# Header + Footer + margins + padding
|
||||
fixed_height = 2 + search_h + preview_h + definition_h + 6
|
||||
|
||||
return int(fixed_height)
|
||||
except Exception:
|
||||
return 25 # Valeur par défaut en cas d'erreur
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Gère les changements dans le champ de recherche"""
|
||||
if event.input.id == "search-input":
|
||||
self.current_input = event.value
|
||||
self.update_preview()
|
||||
self.update_results()
|
||||
|
||||
def update_preview(self) -> None:
|
||||
"""Met à jour l'aperçu du texte saisi"""
|
||||
preview_content = self.query_one("#preview-content")
|
||||
|
||||
if self.current_input:
|
||||
# Afficher le texte avec mise en forme
|
||||
preview_text = f"📝 {self.current_input}"
|
||||
|
||||
# Si le texte correspond à un terme connu, le mettre en évidence
|
||||
matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()]
|
||||
if matching_terms:
|
||||
preview_text = f"✅ {self.current_input} (terme trouvé !)"
|
||||
|
||||
preview_content.update(preview_text)
|
||||
else:
|
||||
preview_content.update("En attente de saisie...")
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
"""Gère la soumission du champ de recherche (touche Entrée)"""
|
||||
if event.input.id == "search-input":
|
||||
results_list = self.query_one("#results-list")
|
||||
if results_list.children:
|
||||
first_item = results_list.children[0]
|
||||
if hasattr(first_item, 'term') and hasattr(first_item, 'definition'):
|
||||
self.show_definition(first_item.term, first_item.definition)
|
||||
|
||||
def update_results(self) -> None:
|
||||
"""Met à jour la liste des résultats"""
|
||||
search_text = self.current_input.lower().strip()
|
||||
|
||||
# Filtrer les termes
|
||||
if search_text:
|
||||
self.filtered_terms = [
|
||||
term for term in self.terms
|
||||
if search_text in term.lower()
|
||||
]
|
||||
else:
|
||||
self.filtered_terms = self.terms.copy()
|
||||
|
||||
# Mettre à jour la ListView
|
||||
results_list = self.query_one("#results-list")
|
||||
results_list.clear()
|
||||
|
||||
for term in self.filtered_terms:
|
||||
definition = self.dictionary[term]
|
||||
item = CustomListItem(term, definition)
|
||||
results_list.append(item)
|
||||
|
||||
# Mettre à jour le compteur
|
||||
results_label = self.query_one("#results-label")
|
||||
count = len(self.filtered_terms)
|
||||
if count == 0:
|
||||
results_label.update("❌ Aucun résultat trouvé")
|
||||
else:
|
||||
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
"""Gère la sélection d'un terme dans la liste"""
|
||||
if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'):
|
||||
self.show_definition(event.item.term, event.item.definition)
|
||||
|
||||
def show_definition(self, term: str, definition: str) -> None:
|
||||
"""Affiche la définition du terme sélectionné"""
|
||||
definition_content = self.query_one("#definition-content")
|
||||
|
||||
# Mettre en valeur le terme dans la définition
|
||||
definition_text = f"[b]{term}[/b]\n\n{definition}"
|
||||
definition_content.update(definition_text)
|
||||
|
||||
# Mettre à jour le champ de recherche avec le terme sélectionné
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = term
|
||||
self.current_input = term
|
||||
self.update_preview()
|
||||
self.update_results()
|
||||
|
||||
self.last_selected_term = term
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
"""Gère les touches spéciales"""
|
||||
if event.key == "escape":
|
||||
# Effacer la recherche
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = ""
|
||||
self.current_input = ""
|
||||
self.update_preview()
|
||||
self.update_results()
|
||||
search_input.focus()
|
||||
elif event.key == "ctrl+f":
|
||||
# Focus sur la recherche
|
||||
self.query_one("#search-input").focus()
|
||||
elif event.key == "ctrl+d":
|
||||
# Effacer la définition
|
||||
definition_content = self.query_one("#definition-content")
|
||||
definition_content.update("Sélectionnez un terme pour voir sa définition")
|
||||
|
||||
# Point d'entrée de l'application
|
||||
if __name__ == "__main__":
|
||||
app = DictionaryApp()
|
||||
app.run()
|
||||
389
doc/crepe.py.ori
Normal file
389
doc/crepe.py.ori
Normal file
|
|
@ -0,0 +1,389 @@
|
|||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container
|
||||
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
|
||||
from textual import events
|
||||
from textual.message import Message
|
||||
from typing import List, Optional
|
||||
|
||||
class CustomListItem(ListItem):
|
||||
"""ListItem personnalisé pour stocker le terme et sa définition"""
|
||||
def __init__(self, term: str, definition: str) -> None:
|
||||
super().__init__(Label(term))
|
||||
self.term = term
|
||||
self.definition = definition
|
||||
|
||||
class DictionaryApp(App):
|
||||
"""Application de dictionnaire avec autocomplétion et responsive design"""
|
||||
|
||||
CSS = """
|
||||
Screen {
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#search-container {
|
||||
height: auto;
|
||||
min-height: 5;
|
||||
max-height: 8;
|
||||
margin-bottom: 1;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#results-container {
|
||||
height: 100%;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#search-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Input {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
border: solid #585b70;
|
||||
padding: 1;
|
||||
width: 100%;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
Input:focus {
|
||||
border: solid #89b4fa;
|
||||
}
|
||||
|
||||
#results-list {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
height: 100%;
|
||||
min-height: 10;
|
||||
border: solid #585b70;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ListItem {
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
ListItem:hover {
|
||||
background: #45476a;
|
||||
}
|
||||
|
||||
ListItem:focus {
|
||||
background: #89b4fa;
|
||||
color: #1e1e2e;
|
||||
}
|
||||
|
||||
ListItem > Label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#results-label {
|
||||
color: #a6e3a1;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#definition-box {
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
margin-top: 1;
|
||||
min-height: 8;
|
||||
max-height: 30;
|
||||
border: solid #585b70;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
#definition-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#definition-content {
|
||||
color: #cdd6f4;
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
height: 100%;
|
||||
min-height: 5;
|
||||
overflow-y: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Dictionnaire de termes avec leurs définitions
|
||||
self.dictionary = {
|
||||
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
|
||||
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
|
||||
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
|
||||
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
|
||||
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
|
||||
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
|
||||
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
|
||||
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
|
||||
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
|
||||
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
|
||||
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
|
||||
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
|
||||
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
|
||||
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
|
||||
"Docker": "Plateforme de conteneurisation pour applications distribuées",
|
||||
"Git": "Système de contrôle de version distribué pour le suivi de code",
|
||||
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
|
||||
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
|
||||
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
|
||||
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
|
||||
}
|
||||
|
||||
# Liste triée des termes pour la recherche
|
||||
self.terms = sorted(self.dictionary.keys())
|
||||
self.filtered_terms = self.terms.copy()
|
||||
self.current_input = ""
|
||||
self.last_selected_term = None
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose l'interface utilisateur"""
|
||||
yield Header()
|
||||
yield Container(
|
||||
Container(
|
||||
Container(
|
||||
Label("🔍 Rechercher un terme :", id="search-label"),
|
||||
Input(placeholder="Tapez un terme...", id="search-input"),
|
||||
id="search-container"
|
||||
),
|
||||
Container(
|
||||
Label("📋 0 terme trouvé", id="results-label"),
|
||||
ListView(id="results-list"),
|
||||
Container(
|
||||
Label("📖 Définition", id="definition-label"),
|
||||
Static("Sélectionnez un terme pour voir sa définition", id="definition-content"),
|
||||
id="definition-box"
|
||||
),
|
||||
id="results-container"
|
||||
),
|
||||
id="main-container"
|
||||
),
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Initialisation après le montage"""
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.focus()
|
||||
self.update_results()
|
||||
self.apply_responsive_styles()
|
||||
|
||||
def on_resize(self, event: events.Resize) -> None:
|
||||
"""Gère le redimensionnement du terminal"""
|
||||
self.apply_responsive_styles()
|
||||
self.update_list_height()
|
||||
|
||||
def apply_responsive_styles(self) -> None:
|
||||
"""Applique les styles responsifs en fonction de la taille du terminal"""
|
||||
width = self.size.width
|
||||
height = self.size.height
|
||||
|
||||
# Ajuster les paddings selon la largeur
|
||||
main_container = self.query_one("#main-container")
|
||||
search_container = self.query_one("#search-container")
|
||||
results_container = self.query_one("#results-container")
|
||||
definition_box = self.query_one("#definition-box")
|
||||
definition_content = self.query_one("#definition-content")
|
||||
|
||||
if width < 80:
|
||||
# Petit terminal
|
||||
main_container.styles.padding = 0.5
|
||||
search_container.styles.padding = 0.5
|
||||
results_container.styles.padding = 0.5
|
||||
definition_box.styles.padding = 0.5
|
||||
definition_content.styles.padding = 0.5
|
||||
definition_box.styles.min_height = 6
|
||||
definition_box.styles.max_height = 12
|
||||
|
||||
# Réduire la police ou l'espacement
|
||||
search_container.styles.min_height = 4
|
||||
search_container.styles.max_height = 6
|
||||
|
||||
elif width > 120 and height > 40:
|
||||
# Grand terminal
|
||||
main_container.styles.padding = 2
|
||||
search_container.styles.padding = 2
|
||||
results_container.styles.padding = 2
|
||||
definition_box.styles.padding = 2
|
||||
definition_content.styles.padding = 2
|
||||
definition_box.styles.min_height = 12
|
||||
definition_box.styles.max_height = 30
|
||||
|
||||
search_container.styles.min_height = 6
|
||||
search_container.styles.max_height = 10
|
||||
|
||||
else:
|
||||
# Terminal moyen (par défaut)
|
||||
main_container.styles.padding = 1
|
||||
search_container.styles.padding = 1
|
||||
results_container.styles.padding = 1
|
||||
definition_box.styles.padding = 1
|
||||
definition_content.styles.padding = 1
|
||||
definition_box.styles.min_height = 8
|
||||
definition_box.styles.max_height = 20
|
||||
|
||||
search_container.styles.min_height = 5
|
||||
search_container.styles.max_height = 8
|
||||
|
||||
# Ajuster la hauteur de la définition selon la hauteur du terminal
|
||||
if height < 30:
|
||||
definition_box.styles.min_height = 4
|
||||
definition_box.styles.max_height = 8
|
||||
elif height > 50:
|
||||
definition_box.styles.min_height = 10
|
||||
definition_box.styles.max_height = 25
|
||||
|
||||
def update_list_height(self) -> None:
|
||||
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
|
||||
try:
|
||||
results_list = self.query_one("#results-list")
|
||||
available_height = self.size.height - self.calculate_fixed_height()
|
||||
|
||||
# Ajuster la hauteur de la liste
|
||||
if available_height > 15:
|
||||
results_list.styles.height = available_height - 2
|
||||
elif available_height > 10:
|
||||
results_list.styles.height = available_height
|
||||
else:
|
||||
results_list.styles.height = 8
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def calculate_fixed_height(self) -> int:
|
||||
"""Calcule la hauteur fixe des éléments (header, footer, search, definition)"""
|
||||
try:
|
||||
search_container = self.query_one("#search-container")
|
||||
definition_box = self.query_one("#definition-box")
|
||||
|
||||
# Récupérer les hauteurs actuelles
|
||||
search_height = search_container.styles.min_height or 5
|
||||
definition_height = definition_box.styles.min_height or 8
|
||||
|
||||
# Estimer la hauteur fixe totale
|
||||
if isinstance(search_height, (int, float)):
|
||||
search_h = search_height
|
||||
else:
|
||||
search_h = 5
|
||||
|
||||
if isinstance(definition_height, (int, float)):
|
||||
definition_h = definition_height
|
||||
else:
|
||||
definition_h = 8
|
||||
|
||||
# Header + Footer + margins + padding
|
||||
fixed_height = 2 + search_h + definition_h + 4
|
||||
|
||||
return int(fixed_height)
|
||||
except Exception:
|
||||
return 20 # Valeur par défaut en cas d'erreur
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Gère les changements dans le champ de recherche"""
|
||||
if event.input.id == "search-input":
|
||||
self.current_input = event.value
|
||||
self.update_results()
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
"""Gère la soumission du champ de recherche (touche Entrée)"""
|
||||
if event.input.id == "search-input":
|
||||
results_list = self.query_one("#results-list")
|
||||
if results_list.children:
|
||||
first_item = results_list.children[0]
|
||||
if hasattr(first_item, 'term') and hasattr(first_item, 'definition'):
|
||||
self.show_definition(first_item.term, first_item.definition)
|
||||
|
||||
def update_results(self) -> None:
|
||||
"""Met à jour la liste des résultats"""
|
||||
search_text = self.current_input.lower().strip()
|
||||
|
||||
# Filtrer les termes
|
||||
if search_text:
|
||||
self.filtered_terms = [
|
||||
term for term in self.terms
|
||||
if search_text in term.lower()
|
||||
]
|
||||
else:
|
||||
self.filtered_terms = self.terms.copy()
|
||||
|
||||
# Mettre à jour la ListView
|
||||
results_list = self.query_one("#results-list")
|
||||
results_list.clear()
|
||||
|
||||
for term in self.filtered_terms:
|
||||
definition = self.dictionary[term]
|
||||
item = CustomListItem(term, definition)
|
||||
results_list.append(item)
|
||||
|
||||
# Mettre à jour le compteur
|
||||
results_label = self.query_one("#results-label")
|
||||
count = len(self.filtered_terms)
|
||||
if count == 0:
|
||||
results_label.update("❌ Aucun résultat trouvé")
|
||||
else:
|
||||
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
"""Gère la sélection d'un terme dans la liste"""
|
||||
if event.item and hasattr(event.item, 'term') and hasattr(event.item, 'definition'):
|
||||
self.show_definition(event.item.term, event.item.definition)
|
||||
|
||||
def show_definition(self, term: str, definition: str) -> None:
|
||||
"""Affiche la définition du terme sélectionné"""
|
||||
definition_content = self.query_one("#definition-content")
|
||||
|
||||
# Mettre en valeur le terme dans la définition
|
||||
definition_text = f"[b]{term}[/b]\n\n{definition}"
|
||||
definition_content.update(definition_text)
|
||||
|
||||
# Mettre à jour le champ de recherche avec le terme sélectionné
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = term
|
||||
self.current_input = term
|
||||
self.update_results()
|
||||
|
||||
self.last_selected_term = term
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
"""Gère les touches spéciales"""
|
||||
if event.key == "escape":
|
||||
# Effacer la recherche
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = ""
|
||||
self.current_input = ""
|
||||
self.update_results()
|
||||
search_input.focus()
|
||||
elif event.key == "ctrl+f":
|
||||
# Focus sur la recherche
|
||||
self.query_one("#search-input").focus()
|
||||
elif event.key == "ctrl+d":
|
||||
# Effacer la définition
|
||||
definition_content = self.query_one("#definition-content")
|
||||
definition_content.update("Sélectionnez un terme pour voir sa définition")
|
||||
|
||||
# Point d'entrée de l'application
|
||||
if __name__ == "__main__":
|
||||
app = DictionaryApp()
|
||||
app.run()
|
||||
460
doc/crepe3.py
Normal file
460
doc/crepe3.py
Normal file
|
|
@ -0,0 +1,460 @@
|
|||
from textual.app import App, ComposeResult
|
||||
from textual.containers import Container, Vertical
|
||||
from textual.widgets import Header, Footer, Input, ListView, ListItem, Label, Static
|
||||
from textual import events
|
||||
from typing import List, Optional
|
||||
|
||||
class TermListItem(ListItem):
|
||||
"""ListItem personnalisé pour afficher un terme et sa définition"""
|
||||
def __init__(self, term: str, definition: str, expanded: bool = False) -> None:
|
||||
self.term = term
|
||||
self.definition = definition
|
||||
self.expanded = expanded
|
||||
|
||||
# Créer le contenu avec le terme et éventuellement la définition
|
||||
content = self._create_content()
|
||||
super().__init__(content)
|
||||
|
||||
def _create_content(self):
|
||||
"""Crée le contenu du ListItem avec ou sans définition"""
|
||||
if self.expanded:
|
||||
return Vertical(
|
||||
Label(f"📌 {self.term}", classes="term-title"),
|
||||
Label(f" {self.definition}", classes="term-definition"),
|
||||
classes="term-expanded"
|
||||
)
|
||||
else:
|
||||
return Label(f"📌 {self.term}", classes="term-title")
|
||||
|
||||
def toggle_expand(self):
|
||||
"""Bascule l'état d'expansion du terme"""
|
||||
self.expanded = not self.expanded
|
||||
# Mettre à jour le contenu
|
||||
self._update_content()
|
||||
|
||||
def _update_content(self):
|
||||
"""Met à jour le contenu du ListItem"""
|
||||
new_content = self._create_content()
|
||||
# Remplacer l'ancien contenu
|
||||
self.remove_children()
|
||||
self.mount(new_content)
|
||||
|
||||
class DictionaryApp(App):
|
||||
"""Application de dictionnaire avec affichage de la définition sous le terme"""
|
||||
|
||||
CSS = """
|
||||
Screen {
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 1;
|
||||
background: #1e1e2e;
|
||||
}
|
||||
|
||||
#search-container {
|
||||
height: auto;
|
||||
min-height: 5;
|
||||
max-height: 8;
|
||||
margin-bottom: 1;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
#results-container {
|
||||
height: 100%;
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#search-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Input {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
border: solid #585b70;
|
||||
padding: 1;
|
||||
width: 100%;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
Input:focus {
|
||||
border: solid #89b4fa;
|
||||
}
|
||||
|
||||
#results-list {
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
height: 100%;
|
||||
min-height: 10;
|
||||
border: solid #585b70;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ListItem {
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
color: #cdd6f4;
|
||||
width: 100%;
|
||||
border-bottom: solid #45476a;
|
||||
}
|
||||
|
||||
ListItem:hover {
|
||||
background: #45476a;
|
||||
}
|
||||
|
||||
ListItem:focus {
|
||||
background: #45476a;
|
||||
}
|
||||
|
||||
.term-title {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
width: 100%;
|
||||
padding: 1;
|
||||
}
|
||||
|
||||
.term-definition {
|
||||
color: #cdd6f4;
|
||||
padding: 1;
|
||||
padding-left: 2;
|
||||
background: #3d3d5c;
|
||||
width: 100%;
|
||||
border-left: solid #89b4fa;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
.term-expanded {
|
||||
background: #2d2d44;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#results-label {
|
||||
color: #a6e3a1;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preview-container {
|
||||
background: #2d2d44;
|
||||
padding: 1;
|
||||
margin-top: 1;
|
||||
min-height: 3;
|
||||
max-height: 5;
|
||||
border: solid #585b70;
|
||||
color: #cdd6f4;
|
||||
}
|
||||
|
||||
#preview-label {
|
||||
color: #89b4fa;
|
||||
text-style: bold;
|
||||
padding-bottom: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#preview-content {
|
||||
color: #f9e2af;
|
||||
padding: 1;
|
||||
background: #3d3d5c;
|
||||
min-height: 2;
|
||||
width: 100%;
|
||||
}
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Dictionnaire de termes avec leurs définitions
|
||||
self.dictionary = {
|
||||
"Python": "Langage de programmation interprété, orienté objet, avec une syntaxe claire et une grande lisibilité",
|
||||
"Textual": "Framework Python pour créer des interfaces utilisateur en mode texte avancées",
|
||||
"Algorithm": "Suite d'instructions pour résoudre un problème ou effectuer une tâche spécifique",
|
||||
"API": "Interface de programmation d'application, ensemble de règles pour interagir avec un logiciel",
|
||||
"Framework": "Ensemble d'outils et de bibliothèques pour développer des applications structurées",
|
||||
"Machine Learning": "Domaine de l'IA permettant aux machines d'apprendre à partir de données",
|
||||
"Deep Learning": "Sous-ensemble du machine learning utilisant des réseaux de neurones profonds",
|
||||
"Neural Network": "Système informatique inspiré du cerveau biologique et de ses connexions",
|
||||
"Data Science": "Domaine interdisciplinaire pour extraire des connaissances des données",
|
||||
"Cloud Computing": "Fourniture de services informatiques via internet à la demande",
|
||||
"DevOps": "Pratique combinant développement et opérations informatiques en continu",
|
||||
"Agile": "Méthodologie de gestion de projet itérative et flexible centrée sur l'humain",
|
||||
"Scrum": "Framework Agile pour la gestion de projets complexes et adaptatifs",
|
||||
"Kubernetes": "Plateforme d'orchestration de conteneurs open-source pour la production",
|
||||
"Docker": "Plateforme de conteneurisation pour applications distribuées",
|
||||
"Git": "Système de contrôle de version distribué pour le suivi de code",
|
||||
"GitHub": "Plateforme d'hébergement de code basée sur Git et de collaboration",
|
||||
"VS Code": "Éditeur de code source développé par Microsoft avec extensions",
|
||||
"PyCharm": "IDE Python développé par JetBrains avec intégration complète",
|
||||
"Jupyter": "Application web pour créer des notebooks interactifs en direct",
|
||||
}
|
||||
|
||||
# Liste triée des termes pour la recherche
|
||||
self.terms = sorted(self.dictionary.keys())
|
||||
self.filtered_terms = self.terms.copy()
|
||||
self.current_input = ""
|
||||
self.last_selected_term = None
|
||||
self.expanded_terms = set() # Ensemble des termes actuellement expansés
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose l'interface utilisateur"""
|
||||
yield Header()
|
||||
yield Container(
|
||||
Container(
|
||||
# Zone de recherche
|
||||
Container(
|
||||
Label("🔍 Rechercher un terme :", id="search-label"),
|
||||
Input(placeholder="Tapez un terme...", id="search-input"),
|
||||
id="search-container"
|
||||
),
|
||||
# Zone d'aperçu du texte en cours
|
||||
Container(
|
||||
Label("✏️ Texte saisi :", id="preview-label"),
|
||||
Static("En attente de saisie...", id="preview-content"),
|
||||
id="preview-container"
|
||||
),
|
||||
# Résultats
|
||||
Container(
|
||||
Label("📋 0 terme trouvé", id="results-label"),
|
||||
ListView(id="results-list"),
|
||||
id="results-container"
|
||||
),
|
||||
id="main-container"
|
||||
),
|
||||
)
|
||||
yield Footer()
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Initialisation après le montage"""
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.focus()
|
||||
self.update_results()
|
||||
self.apply_responsive_styles()
|
||||
|
||||
def on_resize(self, event: events.Resize) -> None:
|
||||
"""Gère le redimensionnement du terminal"""
|
||||
self.apply_responsive_styles()
|
||||
self.update_list_height()
|
||||
|
||||
def apply_responsive_styles(self) -> None:
|
||||
"""Applique les styles responsifs en fonction de la taille du terminal"""
|
||||
width = self.size.width
|
||||
height = self.size.height
|
||||
|
||||
# Ajuster les paddings selon la largeur
|
||||
main_container = self.query_one("#main-container")
|
||||
search_container = self.query_one("#search-container")
|
||||
results_container = self.query_one("#results-container")
|
||||
preview_container = self.query_one("#preview-container")
|
||||
|
||||
if width < 80:
|
||||
# Petit terminal
|
||||
main_container.styles.padding = 1
|
||||
search_container.styles.padding = 1
|
||||
results_container.styles.padding = 1
|
||||
preview_container.styles.padding = 1
|
||||
preview_container.styles.min_height = 2
|
||||
preview_container.styles.max_height = 3
|
||||
search_container.styles.min_height = 4
|
||||
search_container.styles.max_height = 6
|
||||
|
||||
elif width > 120 and height > 40:
|
||||
# Grand terminal
|
||||
main_container.styles.padding = 2
|
||||
search_container.styles.padding = 2
|
||||
results_container.styles.padding = 2
|
||||
preview_container.styles.padding = 2
|
||||
preview_container.styles.min_height = 4
|
||||
preview_container.styles.max_height = 6
|
||||
search_container.styles.min_height = 6
|
||||
search_container.styles.max_height = 10
|
||||
|
||||
else:
|
||||
# Terminal moyen (par défaut)
|
||||
main_container.styles.padding = 1
|
||||
search_container.styles.padding = 1
|
||||
results_container.styles.padding = 1
|
||||
preview_container.styles.padding = 1
|
||||
preview_container.styles.min_height = 3
|
||||
preview_container.styles.max_height = 5
|
||||
search_container.styles.min_height = 5
|
||||
search_container.styles.max_height = 8
|
||||
|
||||
# Ajuster la hauteur de la preview selon la hauteur du terminal
|
||||
if height < 30:
|
||||
preview_container.styles.min_height = 2
|
||||
preview_container.styles.max_height = 3
|
||||
elif height > 50:
|
||||
preview_container.styles.min_height = 4
|
||||
preview_container.styles.max_height = 6
|
||||
|
||||
def update_list_height(self) -> None:
|
||||
"""Met à jour la hauteur de la liste en fonction de l'espace disponible"""
|
||||
try:
|
||||
results_list = self.query_one("#results-list")
|
||||
available_height = self.size.height - self.calculate_fixed_height()
|
||||
|
||||
# Ajuster la hauteur de la liste
|
||||
if available_height > 15:
|
||||
results_list.styles.height = available_height - 2
|
||||
elif available_height > 10:
|
||||
results_list.styles.height = available_height
|
||||
else:
|
||||
results_list.styles.height = 8
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def calculate_fixed_height(self) -> int:
|
||||
"""Calcule la hauteur fixe des éléments (header, footer, search, preview)"""
|
||||
try:
|
||||
search_container = self.query_one("#search-container")
|
||||
preview_container = self.query_one("#preview-container")
|
||||
|
||||
# Récupérer les hauteurs actuelles
|
||||
search_height = search_container.styles.min_height or 5
|
||||
preview_height = preview_container.styles.min_height or 3
|
||||
|
||||
# Estimer la hauteur fixe totale
|
||||
if isinstance(search_height, (int, float)):
|
||||
search_h = search_height
|
||||
else:
|
||||
search_h = 5
|
||||
|
||||
if isinstance(preview_height, (int, float)):
|
||||
preview_h = preview_height
|
||||
else:
|
||||
preview_h = 3
|
||||
|
||||
# Header + Footer + margins + padding
|
||||
fixed_height = 2 + search_h + preview_h + 4
|
||||
|
||||
return int(fixed_height)
|
||||
except Exception:
|
||||
return 18 # Valeur par défaut en cas d'erreur
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Gère les changements dans le champ de recherche"""
|
||||
if event.input.id == "search-input":
|
||||
self.current_input = event.value
|
||||
self.update_preview()
|
||||
self.update_results()
|
||||
|
||||
def update_preview(self) -> None:
|
||||
"""Met à jour l'aperçu du texte saisi"""
|
||||
preview_content = self.query_one("#preview-content")
|
||||
|
||||
if self.current_input:
|
||||
preview_text = f"📝 {self.current_input}"
|
||||
|
||||
# Si le texte correspond à un terme connu
|
||||
matching_terms = [term for term in self.terms if term.lower() == self.current_input.lower()]
|
||||
if matching_terms:
|
||||
preview_text = f"✅ {self.current_input} (terme trouvé !)"
|
||||
|
||||
preview_content.update(preview_text)
|
||||
else:
|
||||
preview_content.update("En attente de saisie...")
|
||||
|
||||
def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||
"""Gère la soumission du champ de recherche (touche Entrée)"""
|
||||
if event.input.id == "search-input":
|
||||
# Sélectionner le premier résultat
|
||||
results_list = self.query_one("#results-list")
|
||||
if results_list.children:
|
||||
first_item = results_list.children[0]
|
||||
if isinstance(first_item, TermListItem):
|
||||
self.toggle_term_expansion(first_item.term)
|
||||
|
||||
def update_results(self) -> None:
|
||||
"""Met à jour la liste des résultats"""
|
||||
search_text = self.current_input.lower().strip()
|
||||
|
||||
# Filtrer les termes
|
||||
if search_text:
|
||||
self.filtered_terms = [
|
||||
term for term in self.terms
|
||||
if search_text in term.lower()
|
||||
]
|
||||
else:
|
||||
self.filtered_terms = self.terms.copy()
|
||||
|
||||
# Mettre à jour la ListView
|
||||
results_list = self.query_one("#results-list")
|
||||
results_list.clear()
|
||||
|
||||
for term in self.filtered_terms:
|
||||
definition = self.dictionary[term]
|
||||
# Vérifier si le terme est expansé
|
||||
expanded = term in self.expanded_terms
|
||||
item = TermListItem(term, definition, expanded)
|
||||
results_list.append(item)
|
||||
|
||||
# Mettre à jour le compteur
|
||||
results_label = self.query_one("#results-label")
|
||||
count = len(self.filtered_terms)
|
||||
if count == 0:
|
||||
results_label.update("❌ Aucun résultat trouvé")
|
||||
else:
|
||||
results_label.update(f"📋 {count} terme{'s' if count > 1 else ''} trouvé{'s' if count > 1 else ''}")
|
||||
|
||||
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
||||
"""Gère la sélection d'un terme dans la liste"""
|
||||
if event.item and isinstance(event.item, TermListItem):
|
||||
# Basculer l'expansion du terme sélectionné
|
||||
self.toggle_term_expansion(event.item.term)
|
||||
|
||||
def toggle_term_expansion(self, term: str) -> None:
|
||||
"""Bascule l'état d'expansion d'un terme"""
|
||||
if term in self.expanded_terms:
|
||||
self.expanded_terms.remove(term)
|
||||
else:
|
||||
# Si un autre terme est expansé, le réduire
|
||||
if len(self.expanded_terms) > 0:
|
||||
# Réduire tous les autres termes
|
||||
for other_term in list(self.expanded_terms):
|
||||
if other_term != term:
|
||||
self.expanded_terms.remove(other_term)
|
||||
self.expanded_terms.add(term)
|
||||
|
||||
# Mettre à jour l'affichage
|
||||
self.update_results()
|
||||
|
||||
# Mettre à jour le champ de recherche
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = term
|
||||
self.current_input = term
|
||||
self.update_preview()
|
||||
|
||||
def on_key(self, event: events.Key) -> None:
|
||||
"""Gère les touches spéciales"""
|
||||
if event.key == "escape":
|
||||
# Effacer la recherche
|
||||
search_input = self.query_one("#search-input")
|
||||
search_input.value = ""
|
||||
self.current_input = ""
|
||||
self.expanded_terms.clear()
|
||||
self.update_preview()
|
||||
self.update_results()
|
||||
search_input.focus()
|
||||
elif event.key == "ctrl+f":
|
||||
# Focus sur la recherche
|
||||
self.query_one("#search-input").focus()
|
||||
elif event.key == "enter":
|
||||
# Si un item est sélectionné, basculer son expansion
|
||||
results_list = self.query_one("#results-list")
|
||||
if results_list.children and hasattr(results_list, 'highlighted_child'):
|
||||
highlighted = results_list.highlighted_child
|
||||
if highlighted and isinstance(highlighted, TermListItem):
|
||||
self.toggle_term_expansion(highlighted.term)
|
||||
|
||||
# Point d'entrée de l'application
|
||||
if __name__ == "__main__":
|
||||
app = DictionaryApp()
|
||||
app.run()
|
||||
Loading…
Reference in a new issue