11"""User interface functions and aliases."""
2+ import contextlib
23import threading
34from functools import partial
45from typing import List , Optional
6+ from warnings import warn
57
68import napari
79
@@ -804,9 +806,12 @@ def __init__(
804806 self .layer_description .setVisible (False )
805807 # self.layer_list.setSizeAdjustPolicy(QComboBox.AdjustToContents) # use tooltip instead ?
806808
809+ # connect to LayerList events
807810 self ._viewer .layers .events .inserted .connect (partial (self ._add_layer ))
808811 self ._viewer .layers .events .removed .connect (partial (self ._remove_layer ))
812+ self ._viewer .layers .events .changed .connect (self ._check_for_layers )
809813
814+ # update self.layer_list when layers are added or removed
810815 self .layer_list .currentIndexChanged .connect (self ._update_tooltip )
811816 self .layer_list .currentTextChanged .connect (self ._update_description )
812817
@@ -816,20 +821,81 @@ def __init__(
816821 )
817822 self ._check_for_layers ()
818823
824+ def _get_all_layers (self ):
825+ return [
826+ self .layer_list .itemText (i ) for i in range (self .layer_list .count ())
827+ ]
828+
819829 def _check_for_layers (self ):
830+ """Check for layers of the correct type and update the dropdown menu.
831+
832+ Also removes layers that have been removed from the viewer.
833+ """
820834 for layer in self ._viewer .layers :
821- if isinstance (layer , self .layer_type ):
835+ layer .events .name .connect (self ._rename_layer )
836+
837+ if (
838+ isinstance (layer , self .layer_type )
839+ and layer .name not in self ._get_all_layers ()
840+ ):
841+ logger .debug (
842+ f"Layer { layer .name } - List : { self ._get_all_layers ()} "
843+ )
844+ # add new layers of correct type
822845 self .layer_list .addItem (layer .name )
846+ logger .debug (f"Layer { layer .name } has been added to the menu" )
847+ # break
848+ # once added, check again for previously renamed layers
849+ self ._check_for_removed_layer (layer )
850+
851+ if layer .name in self ._get_all_layers () and not isinstance (
852+ layer , self .layer_type
853+ ):
854+ # remove layers of incorrect type
855+ index = self .layer_list .findText (layer .name )
856+ self .layer_list .removeItem (index )
857+ logger .debug (
858+ f"Layer { layer .name } has been removed from the menu"
859+ )
860+
861+ self ._check_for_removed_layers ()
862+ self ._update_tooltip ()
863+ self ._update_description ()
864+
865+ def _check_for_removed_layer (self , layer ):
866+ """Check if a specific layer has been removed from the viewer and must be removed from the menu."""
867+ if isinstance (layer , str ):
868+ name = layer
869+ elif isinstance (layer , self .layer_type ):
870+ name = layer .name
871+ else :
872+ logger .warning ("Layer is not a string or a valid napari layer" )
873+ return
874+
875+ if name in self ._get_all_layers () and name not in [
876+ l .name for l in self ._viewer .layers
877+ ]:
878+ index = self .layer_list .findText (name )
879+ self .layer_list .removeItem (index )
880+ logger .debug (f"Layer { name } has been removed from the menu" )
881+
882+ def _check_for_removed_layers (self ):
883+ """Check for layers that have been removed from the viewer and must be removed from the menu."""
884+ for layer in self ._get_all_layers ():
885+ self ._check_for_removed_layer (layer )
823886
824887 def _update_tooltip (self ):
825888 self .layer_list .setToolTip (self .layer_list .currentText ())
826889
827890 def _update_description (self ):
828891 try :
829892 if self .layer_list .currentText () != "" :
830- self .layer_description .setVisible (True )
831- shape_desc = f"Shape : { self .layer_data ().shape } "
832- self .layer_description .setText (shape_desc )
893+ try :
894+ shape_desc = f"Shape : { self .layer_data ().shape } "
895+ self .layer_description .setText (shape_desc )
896+ self .layer_description .setVisible (True )
897+ except AttributeError :
898+ self .layer_description .setVisible (False )
833899 else :
834900 self .layer_description .setVisible (False )
835901 except KeyError :
@@ -841,6 +907,13 @@ def _add_layer(self, event):
841907 if isinstance (inserted_layer , self .layer_type ):
842908 self .layer_list .addItem (inserted_layer .name )
843909
910+ # check for renaming
911+ inserted_layer .events .name .connect (self ._rename_layer )
912+
913+ def _rename_layer (self , _ ):
914+ # on layer rename, check for removed/new layers
915+ self ._check_for_layers ()
916+
844917 def _remove_layer (self , event ):
845918 removed_layer = event .value
846919
@@ -867,15 +940,24 @@ def layer(self):
867940
868941 def layer_name (self ):
869942 """Returns the name of the layer selected in the dropdown menu."""
870- return self .layer_list .currentText ()
943+ try :
944+ return self .layer_list .currentText ()
945+ except (KeyError , ValueError ):
946+ logger .warning ("Layer list is empty" )
947+ return None
871948
872949 def layer_data (self ):
873950 """Returns the data of the layer selected in the dropdown menu."""
874951 if self .layer_list .count () < 1 :
875952 logger .debug ("Layer list is empty" )
876953 return None
877-
878- return self .layer ().data
954+ try :
955+ return self .layer ().data
956+ except (KeyError , ValueError ):
957+ msg = f"Layer { self .layer_name ()} has no data. Layer might have been renamed or removed."
958+ logger .warning (msg )
959+ warn (msg , stacklevel = 1 )
960+ return None
879961
880962
881963class FilePathWidget (QWidget ): # TODO include load as folder
0 commit comments