@@ -2676,6 +2676,7 @@ def __init__(self, pipeline=None, parent=None, show_sub_pipelines=False,
26762676 self ._restricted_edition = False
26772677 self .disable_overwrite = False
26782678 self ._userlevel = userlevel
2679+ self ._pipeline_released = False
26792680 self .doc_browser = None
26802681
26812682 self .set_pipeline (pipeline )
@@ -2688,10 +2689,10 @@ def __init__(self, pipeline=None, parent=None, show_sub_pipelines=False,
26882689 self .link_keydelete_clicked .connect (self ._link_delete_clicked )
26892690 self .node_keydelete_clicked .connect (self ._node_delete_clicked )
26902691
2691- def __del__ (self ):
2692- # print('PipelineDeveloperView.__del__')
2693- self .release_pipeline (delete = True )
2694- # super(PipelineDeveloperView, self).__del__()
2692+ # def __del__(self):
2693+ # # print('PipelineDeveloperView.__del__')
2694+ # self.release_pipeline(delete=True)
2695+ # # super(PipelineDeveloperView, self).__del__()
26952696
26962697 @property
26972698 def userlevel (self ):
@@ -2791,6 +2792,11 @@ def set_pipeline(self, pipeline):
27912792 if hasattr (pipeline , 'pipeline_steps' ):
27922793 pipeline .pipeline_steps .on_trait_change (
27932794 self ._reset_pipeline , dispatch = 'ui' )
2795+
2796+ def closeEvent (self , event ):
2797+ """Ensure pipeline is released before the widget is closed."""
2798+ self .release_pipeline ()
2799+ super ().closeEvent (event )
27942800
27952801 def release_pipeline (self , delete = False ):
27962802 '''
@@ -2801,59 +2807,104 @@ def release_pipeline(self, delete=False):
28012807 '''
28022808 # Setup callback to update view when pipeline state is modified
28032809 from soma .qt_gui .qt_backend import sip
2804- pipeline = None
2805- if self .scene is not None and hasattr (self .scene , 'pipeline' ):
2806- pipeline = self .scene .pipeline
2807- if pipeline is not None :
2808- if hasattr (pipeline , 'pipeline_steps' ):
2809- pipeline .pipeline_steps .on_trait_change (
2810- self ._reset_pipeline , remove = True )
2811- pipeline .on_trait_change (self ._reset_pipeline , 'selection_changed' ,
2812- remove = True )
2813- pipeline .on_trait_change (self ._reset_pipeline ,
2814- 'user_traits_changed' , remove = True )
2815- if sip .isdeleted (self ):
2816- # prevent 'C++ object has been deleted' error
2817- return
2818- self .setScene (None )
2819- if self .scene :
2820- # force destruction of scene internals now that the Qt object
2821- # still exists
2822- self .scene ._release ()
2823- # the scene is not deleted after all refs are released, even
2824- # after self.setScene(None). This is probably a bug in PyQt:
2825- # the C++ layer keeps ownership of the scene, whereas it should
2826- # not: the Qt doc specifies for QGraphicsView.setScene():
2827- # "The view does not take ownership of scene.", however in PyQt it
2828- # does, and only releases it when the QGraphicsView is deleted.
2829- # Thus we have to force it by hand:
2830- from soma .qt_gui .qt_backend import sip
2831- sip .transferback (self .scene )
2832- self .scene = None
2833- import gc
2834- gc .collect ()
2835- if not delete and (pipeline is not None or self .scene is None ):
2836- self .scene = PipelineScene (self , userlevel = self .userlevel )
2837- self .scene .set_enable_edition (self ._enable_edition )
2838- self .scene .logical_view = self ._logical_view
2839- self .scene .colored_parameters = self .colored_parameters
2840- self .scene .subpipeline_clicked .connect (self .subpipeline_clicked )
2841- self .scene .subpipeline_clicked .connect (self .onLoadSubPipelineClicked )
2842- self .scene .process_clicked .connect (self ._node_clicked )
2843- self .scene .node_clicked .connect (self ._node_clicked )
2844- self .scene .node_clicked_ctrl .connect (self ._node_clicked_ctrl )
2845- self .scene .switch_clicked .connect (self .switch_clicked )
2846- self .scene .node_right_clicked .connect (self .node_right_clicked )
2847- self .scene .node_right_clicked .connect (self .onOpenProcessController )
2848- self .scene .plug_clicked .connect (self .plug_clicked )
2849- self .scene .plug_right_clicked .connect (self .plug_right_clicked )
2850- self .scene .link_right_clicked .connect (self .link_right_clicked )
2851- self .scene .link_keydelete_clicked .connect (self .link_keydelete_clicked )
2852- self .scene .node_keydelete_clicked .connect (self .node_keydelete_clicked )
2853- self .scene .pos = {}
2854- self .scene .dim = {}
2855- self .setWindowTitle ('<no pipeline>' )
2856- self .setScene (self .scene )
2810+
2811+ if getattr (self , '_pipeline_released' , False ):
2812+ return # already released
2813+
2814+ self ._pipeline_released = True
2815+
2816+ try :
2817+ pipeline = None
2818+
2819+
2820+ if (
2821+ hasattr (self , 'scene' )
2822+ and self .scene is not None
2823+ and hasattr (self .scene , 'pipeline' )
2824+ ):
2825+ pipeline = self .scene .pipeline
2826+
2827+ if pipeline is not None :
2828+
2829+ if hasattr (pipeline , 'pipeline_steps' ):
2830+ pipeline .pipeline_steps .on_trait_change (
2831+ self ._reset_pipeline , remove = True
2832+ )
2833+
2834+ pipeline .on_trait_change (
2835+ self ._reset_pipeline , 'selection_changed' , remove = True
2836+ )
2837+ pipeline .on_trait_change (
2838+ self ._reset_pipeline , 'user_traits_changed' , remove = True
2839+ )
2840+
2841+ if sip .isdeleted (self ):
2842+ # prevent 'C++ object has been deleted' error
2843+ return
2844+
2845+ self .setScene (None )
2846+
2847+ if hasattr (self , 'scene' ) and self .scene :
2848+
2849+ # force destruction of scene internals now that the Qt object
2850+ # still exists
2851+ try :
2852+ self .scene ._release ()
2853+
2854+ except Exception :
2855+ pass
2856+
2857+ # the scene is not deleted after all refs are released, even
2858+ # after self.setScene(None). This is probably a bug in PyQt:
2859+ # the C++ layer keeps ownership of the scene, whereas it should
2860+ # not: the Qt doc specifies for QGraphicsView.setScene():
2861+ # "The view does not take ownership of scene.", however in PyQt it
2862+ # does, and only releases it when the QGraphicsView is deleted.
2863+ # Thus we have to force it by hand:
2864+ try :
2865+ sip .transferback (self .scene )
2866+
2867+ except Exception :
2868+ pass
2869+
2870+
2871+ self .scene = None
2872+ import gc
2873+ gc .collect ()
2874+
2875+ if not delete and (pipeline is not None or self .scene is None ):
2876+
2877+ try :
2878+ self .scene = PipelineScene (self , userlevel = self .userlevel )
2879+ self .scene .set_enable_edition (self ._enable_edition )
2880+ self .scene .logical_view = self ._logical_view
2881+ self .scene .colored_parameters = self .colored_parameters
2882+ self .scene .subpipeline_clicked .connect (self .subpipeline_clicked )
2883+ self .scene .subpipeline_clicked .connect (self .onLoadSubPipelineClicked )
2884+ self .scene .process_clicked .connect (self ._node_clicked )
2885+ self .scene .node_clicked .connect (self ._node_clicked )
2886+ self .scene .node_clicked_ctrl .connect (self ._node_clicked_ctrl )
2887+ self .scene .switch_clicked .connect (self .switch_clicked )
2888+ self .scene .node_right_clicked .connect (self .node_right_clicked )
2889+ self .scene .node_right_clicked .connect (self .onOpenProcessController )
2890+ self .scene .plug_clicked .connect (self .plug_clicked )
2891+ self .scene .plug_right_clicked .connect (self .plug_right_clicked )
2892+ self .scene .link_right_clicked .connect (self .link_right_clicked )
2893+ self .scene .link_keydelete_clicked .connect (self .link_keydelete_clicked )
2894+ self .scene .node_keydelete_clicked .connect (self .node_keydelete_clicked )
2895+ self .scene .pos = {}
2896+ self .scene .dim = {}
2897+ self .setWindowTitle ('<no pipeline>' )
2898+ self .setScene (self .scene )
2899+
2900+ except Exception as e :
2901+ print (
2902+ f"[release_pipeline] Exception "
2903+ f"during scene rebuild: { e } "
2904+ )
2905+
2906+ except Exception as e :
2907+ print (f"[release_pipeline] Exception: { e } " )
28572908
28582909 def is_logical_view (self ):
28592910 '''
0 commit comments