@@ -524,6 +524,28 @@ def _center_window(self):
524524 except Exception :
525525 pass
526526
527+ def _is_valid (self , widget ) -> bool :
528+ """Vérifie si un widget Qt est toujours valide.
529+
530+ Contrairement à hasattr(), cette méthode vérifie si l'objet C++
531+ sous-jacent n'a pas été détruit.
532+
533+ Args:
534+ widget: Le widget Qt à vérifier
535+
536+ Returns:
537+ True si le widget est valide, False sinon
538+ """
539+ if widget is None :
540+ return False
541+ try :
542+ # Tentative d'accès à une propriété du widget
543+ # Si l'objet C++ a été détruit, une RuntimeError sera levée
544+ widget .objectName ()
545+ return True
546+ except RuntimeError :
547+ return False
548+
527549 def _apply_theme (self , theme_name : str ):
528550 """Applique le thème visuel."""
529551 if theme_name == "dark" :
@@ -633,11 +655,24 @@ def _apply_language(self, lang_code: str):
633655 _CURRENT_LANGUAGE = lang_code
634656
635657 # Les traductions sont gérées via tr() dans setup_ui
636- self ._log (tr ("Language set to English" , "Langue définie sur Français" ))
658+ # Note: On utilise une vérification pour éviter l'erreur si log_text n'est pas encore initialisé
659+ if self ._is_valid (self .log_text ):
660+ try :
661+ self ._log (tr ("Language set to English" , "Langue définie sur Français" ))
662+ except (RuntimeError , AttributeError ):
663+ pass # Ignorer si le widget a été supprimé
637664
638665 def _discover_plugins (self ):
639666 """Découvre et affiche les plugins BCASL disponibles."""
640- self .plugins_list .clear ()
667+ # Vérifier que les widgets sont initialisés et valides
668+ if not self ._is_valid (self .plugins_list ):
669+ return
670+
671+ try :
672+ self .plugins_list .clear ()
673+ except (RuntimeError , AttributeError ):
674+ return # Widget supprimé
675+
641676 self .plugins_meta = {}
642677
643678 if not self .Plugins_dir or not self .Plugins_dir .exists ():
@@ -657,11 +692,14 @@ def _discover_plugins(self):
657692 )
658693 )
659694 # Créer un message dans la liste
660- item = QListWidgetItem (
661- tr ("No plugins available" , "Aucun plugin disponible" )
662- )
663- item .setFlags (item .flags () & ~ Qt .ItemFlag .ItemIsEnabled )
664- self .plugins_list .addItem (item )
695+ try :
696+ item = QListWidgetItem (
697+ tr ("No plugins available" , "Aucun plugin disponible" )
698+ )
699+ item .setFlags (item .flags () & ~ Qt .ItemFlag .ItemIsEnabled )
700+ self .plugins_list .addItem (item )
701+ except (RuntimeError , AttributeError ):
702+ pass # Widget supprimé
665703 return
666704
667705 # Trier les plugins par ordre de priorité (tags)
@@ -744,17 +782,25 @@ def _add_plugin_item(self, plugin_id: str, meta: Dict[str, Any]):
744782
745783 def _on_global_toggle (self , checked : bool ):
746784 """Gère l'activation/désactivation globale de BCASL."""
785+ # Vérifier que la liste des plugins est valide
786+ if not self ._is_valid (self .plugins_list ):
787+ return
788+
747789 # Activer/désactiver tous les items
748- for i in range (self .plugins_list .count ()):
749- item = self .plugins_list .item (i )
750- plugin_id = item .data (Qt .ItemDataRole .UserRole )
751- if plugin_id :
752- item .setCheckState (
753- Qt .CheckState .Checked if checked else Qt .CheckState .Unchecked
754- )
790+ try :
791+ for i in range (self .plugins_list .count ()):
792+ item = self .plugins_list .item (i )
793+ plugin_id = item .data (Qt .ItemDataRole .UserRole )
794+ if plugin_id :
795+ item .setCheckState (
796+ Qt .CheckState .Checked if checked else Qt .CheckState .Unchecked
797+ )
798+ except (RuntimeError , AttributeError ):
799+ return # Widget supprimé
755800
756801 # Activer/désactiver les contrôles
757- self .plugins_list .setEnabled (checked )
802+ if self ._is_valid (self .plugins_list ):
803+ self .plugins_list .setEnabled (checked )
758804 self .btn_move_up .setEnabled (checked )
759805 self .btn_move_down .setEnabled (checked )
760806 self .btn_run .setEnabled (checked )
@@ -766,42 +812,70 @@ def _on_global_toggle(self, checked: bool):
766812
767813 def _move_plugin_up (self ):
768814 """Déplace le plugin sélectionné vers le haut."""
815+ # Vérifier que la liste des plugins est valide
816+ if not self ._is_valid (self .plugins_list ):
817+ return
818+
769819 row = self .plugins_list .currentRow ()
770820 if row <= 0 :
771821 return
772822
773- item = self .plugins_list .takeItem (row )
774- self .plugins_list .insertItem (row - 1 , item )
775- self .plugins_list .setCurrentRow (row - 1 )
823+ try :
824+ item = self .plugins_list .takeItem (row )
825+ self .plugins_list .insertItem (row - 1 , item )
826+ self .plugins_list .setCurrentRow (row - 1 )
827+ except (RuntimeError , AttributeError ):
828+ pass # Widget supprimé
776829
777830 def _move_plugin_down (self ):
778831 """Déplace le plugin sélectionné vers le bas."""
832+ # Vérifier que la liste des plugins est valide
833+ if not self ._is_valid (self .plugins_list ):
834+ return
835+
779836 row = self .plugins_list .currentRow ()
780837 if row < 0 or row >= self .plugins_list .count () - 1 :
781838 return
782839
783- item = self .plugins_list .takeItem (row )
784- self .plugins_list .insertItem (row + 1 , item )
785- self .plugins_list .setCurrentRow (row + 1 )
840+ try :
841+ item = self .plugins_list .takeItem (row )
842+ self .plugins_list .insertItem (row + 1 , item )
843+ self .plugins_list .setCurrentRow (row + 1 )
844+ except (RuntimeError , AttributeError ):
845+ pass # Widget supprimé
786846
787847 def _get_plugin_order (self ) -> List [str ]:
788848 """Récupère l'ordre actuel des plugins."""
849+ # Vérifier que la liste des plugins est valide
850+ if not self ._is_valid (self .plugins_list ):
851+ return []
852+
789853 order = []
790- for i in range (self .plugins_list .count ()):
791- item = self .plugins_list .item (i )
792- plugin_id = item .data (Qt .ItemDataRole .UserRole )
793- if plugin_id :
794- order .append (plugin_id )
854+ try :
855+ for i in range (self .plugins_list .count ()):
856+ item = self .plugins_list .item (i )
857+ plugin_id = item .data (Qt .ItemDataRole .UserRole )
858+ if plugin_id :
859+ order .append (plugin_id )
860+ except (RuntimeError , AttributeError ):
861+ return [] # Widget supprimé
795862 return order
796863
797864 def _get_enabled_plugins (self ) -> Dict [str , bool ]:
798865 """Récupère l'état d'activation des plugins."""
866+ # Vérifier que la liste des plugins est valide
867+ if not self ._is_valid (self .plugins_list ):
868+ return {}
869+
799870 enabled = {}
800- for i in range (self .plugins_list .count ()):
801- item = self .plugins_list .item (i )
802- plugin_id = item .data (Qt .ItemDataRole .UserRole )
803- if plugin_id :
804- enabled [plugin_id ] = item .checkState () == Qt .CheckState .Checked
871+ try :
872+ for i in range (self .plugins_list .count ()):
873+ item = self .plugins_list .item (i )
874+ plugin_id = item .data (Qt .ItemDataRole .UserRole )
875+ if plugin_id :
876+ enabled [plugin_id ] = item .checkState () == Qt .CheckState .Checked
877+ except (RuntimeError , AttributeError ):
878+ return {} # Widget supprimé
805879 return enabled
806880
807881 def _run_plugins (self ):
@@ -868,7 +942,8 @@ def _run_plugins(self):
868942 # Désactiver les contrôles pendant l'exécution
869943 self .btn_run .setEnabled (False )
870944 self .btn_cancel .setEnabled (True )
871- self .plugins_list .setEnabled (False )
945+ if self ._is_valid (self .plugins_list ):
946+ self .plugins_list .setEnabled (False )
872947 self .btn_move_up .setEnabled (False )
873948 self .btn_move_down .setEnabled (False )
874949 self .global_enable_check .setEnabled (False )
@@ -926,7 +1001,8 @@ def _on_execution_finished(
9261001 # Réactiver les contrôles
9271002 self .btn_run .setEnabled (True )
9281003 self .btn_cancel .setEnabled (False )
929- self .plugins_list .setEnabled (self .global_enable_check .isChecked ())
1004+ if self ._is_valid (self .plugins_list ):
1005+ self .plugins_list .setEnabled (self .global_enable_check .isChecked ())
9301006 self .btn_move_up .setEnabled (self .global_enable_check .isChecked ())
9311007 self .btn_move_down .setEnabled (self .global_enable_check .isChecked ())
9321008 self .global_enable_check .setEnabled (True )
@@ -980,23 +1056,34 @@ def _on_execution_error(self, error: str):
9801056 # Réactiver les contrôles
9811057 self .btn_run .setEnabled (True )
9821058 self .btn_cancel .setEnabled (False )
983- self .plugins_list .setEnabled (self .global_enable_check .isChecked ())
1059+ if self ._is_valid (self .plugins_list ):
1060+ self .plugins_list .setEnabled (self .global_enable_check .isChecked ())
9841061 self .btn_move_up .setEnabled (self .global_enable_check .isChecked ())
9851062 self .btn_move_down .setEnabled (self .global_enable_check .isChecked ())
9861063 self .global_enable_check .setEnabled (True )
9871064 self .progress_bar .setVisible (False )
9881065
9891066 def _clear_log (self ):
9901067 """Efface le log."""
991- self .log_text .clear ()
1068+ if self ._is_valid (self .log_text ):
1069+ try :
1070+ self .log_text .clear ()
1071+ except (RuntimeError , AttributeError ):
1072+ pass # Ignorer si le widget a été supprimé
9921073
9931074 def _log (self , message : str ):
9941075 """Ajoute un message au log."""
995- timestamp = datetime .now ().strftime ("%H:%M:%S" )
996- self .log_text .append (f"[{ timestamp } ] { message } " )
997- # Défiler automatiquement vers le bas
998- scrollbar = self .log_text .verticalScrollBar ()
999- scrollbar .setValue (scrollbar .maximum ())
1076+ # Vérifier que le widget existe et n'a pas été supprimé
1077+ if not self ._is_valid (self .log_text ):
1078+ return
1079+ try :
1080+ timestamp = datetime .now ().strftime ("%H:%M:%S" )
1081+ self .log_text .append (f"[{ timestamp } ] { message } " )
1082+ # Défiler automatiquement vers le bas
1083+ scrollbar = self .log_text .verticalScrollBar ()
1084+ scrollbar .setValue (scrollbar .maximum ())
1085+ except (RuntimeError , AttributeError ):
1086+ pass # Ignorer si le widget a été supprimé
10001087
10011088
10021089def launch_bcasl_gui (
0 commit comments