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()