@@ -209,6 +209,7 @@ def __init__(self, main_window):
209209 self .fig = Figure ()
210210
211211 canvas = FigureCanvas (self .fig )
212+ self .canvas = canvas
212213 toolbar = NavigationToolbar (canvas , parent = None )
213214
214215 toolbar_box = QtWidgets .QHBoxLayout ()
@@ -269,6 +270,8 @@ def update_plot(self):
269270 self .fig .canvas .flush_events ()
270271
271272 def close_window (self , * args ):
273+ if hasattr (self , "canvas" ):
274+ self .canvas = None
272275 self .close ()
273276
274277
@@ -291,6 +294,7 @@ def __init__(self, model, hist_msids): # noqa: PLR0915
291294 self .fig = Figure ()
292295
293296 canvas = FigureCanvas (self .fig )
297+ self .canvas = canvas
294298 toolbar = NavigationToolbar (canvas , parent = None )
295299
296300 msid_select = QtWidgets .QComboBox ()
@@ -387,6 +391,8 @@ def emax_edited(self):
387391 self .update_plots ()
388392
389393 def close_window (self , * args ):
394+ if hasattr (self , "canvas" ):
395+ self .canvas = None
390396 self .close ()
391397
392398 _rz_mask = None
@@ -649,15 +655,38 @@ def update_plots(self): # noqa: PLR0915
649655 self .fig .canvas .flush_events ()
650656
651657
652- class PlotBox (QtWidgets .QVBoxLayout ):
653- def __init__ (self , plot_name , plots_box ):
654- super ().__init__ ()
658+ class PlotsPanel (QtWidgets .QWidget ):
659+ """
660+ QWidget wrapper for PlotsBox (QVBoxLayout), ensures persistent ownership and correct
661+ parent/child relationship. Also exposes main_window for compatibility with code
662+ expecting PlotsBox.main_window.
663+ """
655664
665+ def __init__ (self , model , main_window ):
666+ super ().__init__ ()
667+ self .main_window = main_window
668+ self .model = model
669+ self .plots_box = PlotsBox (model , main_window , parent_widget = self )
670+ # Keep explicit reference to plot_boxes to prevent GC
671+ self ._plot_boxes_ref = self .plots_box .plot_boxes
672+ layout = QtWidgets .QVBoxLayout ()
673+ layout .setContentsMargins (0 , 0 , 0 , 0 )
674+ layout .addLayout (self .plots_box )
675+ self .setLayout (layout )
676+
677+
678+ class PlotBox (QtWidgets .QWidget ):
679+ def __init__ (self , plot_name , plots_box , parent = None ):
680+ super ().__init__ (parent )
681+ self .plot_name = plot_name
656682 comp_name , plot_method = plot_name .split () # E.g. "tephin fit_resid"
657683 self .comp = plots_box .model .comp [comp_name ]
658684 self .plot_method = plot_method
659685 self .comp_name = comp_name
660- self .plot_name = plot_name
686+
687+ # Layout for this widget
688+ vbox = QtWidgets .QVBoxLayout ()
689+ self .setLayout (vbox )
661690
662691 self .fig = Figure (constrained_layout = True )
663692 canvas = FigureCanvas (self .fig )
@@ -681,8 +710,8 @@ def __init__(self, plot_name, plots_box):
681710 toolbar_box .addStretch (1 )
682711 toolbar_box .addWidget (delete_plot_button )
683712
684- self .addWidget (canvas )
685- self .addLayout (toolbar_box )
713+ vbox .addWidget (canvas )
714+ vbox .addLayout (toolbar_box )
686715
687716 # Add shared x-axes for plots with time on the x-axis
688717 xaxis = plot_method .split ("__" )
@@ -824,11 +853,14 @@ def update(self, first=False):
824853
825854
826855class PlotsBox (QtWidgets .QVBoxLayout ):
827- def __init__ (self , model , main_window ):
856+ def __init__ (self , model , main_window , parent_widget = None ):
828857 super ().__init__ ()
829858 self .main_window = main_window
830859 self .model = model
831- self .plot_boxes = []
860+ self .parent_widget = (
861+ parent_widget # Store the parent widget for proper Qt ownership
862+ )
863+ self .plot_boxes = [] # Keep strong references to PlotBox objects
832864 self .plot_names = []
833865
834866 self .set_times ()
@@ -868,19 +900,25 @@ def add_plot_box(self, plot_name):
868900 if plot_name == "Add plot..." or plot_name in self .plot_names :
869901 return
870902 print ("Adding plot " , plot_name )
871- plot_box = PlotBox ( plot_name , self )
872- self . addLayout ( plot_box )
873- plot_box . update ( first = True )
903+ # Pass parent_widget during PlotBox construction for proper Qt ownership
904+ plot_box = PlotBox ( plot_name , self , parent = self . parent_widget )
905+ # Keep strong references BEFORE any other operations
874906 self .plot_boxes .append (plot_box )
875907 self .plot_names .append (plot_name )
908+ # Now add to layout - layout takes ownership but we keep Python ref
909+ self .addWidget (plot_box )
910+ # Defer update to allow Qt to fully establish ownership
911+ # This prevents premature garbage collection of the PlotBox
912+ QtCore .QTimer .singleShot (0 , lambda : plot_box .update (first = True ))
876913
877914 def delete_plot_box (self , plot_name ):
878915 for i , pb in enumerate (list (self .plot_boxes )):
879916 if pb .plot_name == plot_name :
880917 self .plot_boxes .pop (i )
881918 self .plot_names .pop (i )
882- self .removeItem (pb )
883- clear_layout (pb )
919+ self .removeWidget (pb )
920+ pb .setParent (None )
921+ pb .deleteLater ()
884922 break
885923 self .update ()
886924
0 commit comments