Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions xija/gui_fit/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from xija.get_model_spec import get_xija_model_spec

from .fitter import FitWorker, fit_logger
from .plots import FitStatWindow, HistogramWindow, PlotsBox
from .plots import FitStatWindow, HistogramWindow, PlotsPanel

gui_config = {}

Expand Down Expand Up @@ -956,10 +956,10 @@ class MainLeftPanel(Panel):
def __init__(self, model, main_window):
Panel.__init__(self, orient="v")
self.control_buttons_panel = ControlButtonsPanel(model)
self.plots_box = PlotsBox(model, main_window)
self.plots_panel = PlotsPanel(model, main_window)
self.plots_box = self.plots_panel.plots_box
self.pack_start(self.control_buttons_panel)
# This specialized code is for the PlotsBox because we
# want it to be scrollable
# Make PlotsPanel scrollable
self.scroll = QtWidgets.QScrollArea()
self.scroll.setWidgetResizable(True)
self.scroll.setSizePolicy(
Expand All @@ -968,12 +968,7 @@ def __init__(self, model, main_window):
self.scroll.setFrameShape(
QtWidgets.QFrame.NoFrame
) # optional, just looks nicer
container = QtWidgets.QWidget()
container.setLayout(self.plots_box)
container.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred
)
self.scroll.setWidget(container)
self.scroll.setWidget(self.plots_panel)
self.box.addWidget(self.scroll, 1)


Expand Down Expand Up @@ -1026,9 +1021,10 @@ def __init__(self, model, fit_worker): # noqa: PLR0915

self.main_left_panel = MainLeftPanel(model, self)
mlp = self.main_left_panel
self.plots_panel = self.main_left_panel.plots_panel
self.plots_box = self.main_left_panel.plots_box

self.main_right_panel = MainRightPanel(model, mlp.plots_box)
self.main_right_panel = MainRightPanel(model, self.plots_panel)
mrp = self.main_right_panel

self.show_radzones = False
Expand Down
64 changes: 51 additions & 13 deletions xija/gui_fit/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ def __init__(self, main_window):
self.fig = Figure()

canvas = FigureCanvas(self.fig)
self.canvas = canvas
toolbar = NavigationToolbar(canvas, parent=None)

toolbar_box = QtWidgets.QHBoxLayout()
Expand Down Expand Up @@ -269,6 +270,8 @@ def update_plot(self):
self.fig.canvas.flush_events()

def close_window(self, *args):
if hasattr(self, "canvas"):
self.canvas = None
self.close()


Expand All @@ -291,6 +294,7 @@ def __init__(self, model, hist_msids): # noqa: PLR0915
self.fig = Figure()

canvas = FigureCanvas(self.fig)
self.canvas = canvas
toolbar = NavigationToolbar(canvas, parent=None)

msid_select = QtWidgets.QComboBox()
Expand Down Expand Up @@ -387,6 +391,8 @@ def emax_edited(self):
self.update_plots()

def close_window(self, *args):
if hasattr(self, "canvas"):
self.canvas = None
self.close()

_rz_mask = None
Expand Down Expand Up @@ -649,15 +655,38 @@ def update_plots(self): # noqa: PLR0915
self.fig.canvas.flush_events()


class PlotBox(QtWidgets.QVBoxLayout):
def __init__(self, plot_name, plots_box):
super().__init__()
class PlotsPanel(QtWidgets.QWidget):
"""
QWidget wrapper for PlotsBox (QVBoxLayout), ensures persistent ownership and correct
parent/child relationship. Also exposes main_window for compatibility with code
expecting PlotsBox.main_window.
"""

def __init__(self, model, main_window):
super().__init__()
self.main_window = main_window
self.model = model
self.plots_box = PlotsBox(model, main_window, parent_widget=self)
# Keep explicit reference to plot_boxes to prevent GC
self._plot_boxes_ref = self.plots_box.plot_boxes
layout = QtWidgets.QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
layout.addLayout(self.plots_box)
self.setLayout(layout)


class PlotBox(QtWidgets.QWidget):
def __init__(self, plot_name, plots_box, parent=None):
super().__init__(parent)
self.plot_name = plot_name
comp_name, plot_method = plot_name.split() # E.g. "tephin fit_resid"
self.comp = plots_box.model.comp[comp_name]
self.plot_method = plot_method
self.comp_name = comp_name
self.plot_name = plot_name

# Layout for this widget
vbox = QtWidgets.QVBoxLayout()
self.setLayout(vbox)

self.fig = Figure(constrained_layout=True)
canvas = FigureCanvas(self.fig)
Expand All @@ -681,8 +710,8 @@ def __init__(self, plot_name, plots_box):
toolbar_box.addStretch(1)
toolbar_box.addWidget(delete_plot_button)

self.addWidget(canvas)
self.addLayout(toolbar_box)
vbox.addWidget(canvas)
vbox.addLayout(toolbar_box)

# Add shared x-axes for plots with time on the x-axis
xaxis = plot_method.split("__")
Expand Down Expand Up @@ -824,11 +853,14 @@ def update(self, first=False):


class PlotsBox(QtWidgets.QVBoxLayout):
def __init__(self, model, main_window):
def __init__(self, model, main_window, parent_widget=None):
super().__init__()
self.main_window = main_window
self.model = model
self.plot_boxes = []
self.parent_widget = (
parent_widget # Store the parent widget for proper Qt ownership
)
self.plot_boxes = [] # Keep strong references to PlotBox objects
self.plot_names = []

self.set_times()
Expand Down Expand Up @@ -868,19 +900,25 @@ def add_plot_box(self, plot_name):
if plot_name == "Add plot..." or plot_name in self.plot_names:
return
print("Adding plot ", plot_name)
Comment thread
jeanconn marked this conversation as resolved.
plot_box = PlotBox(plot_name, self)
self.addLayout(plot_box)
plot_box.update(first=True)
# Pass parent_widget during PlotBox construction for proper Qt ownership
plot_box = PlotBox(plot_name, self, parent=self.parent_widget)
# Keep strong references BEFORE any other operations
self.plot_boxes.append(plot_box)
self.plot_names.append(plot_name)
# Now add to layout - layout takes ownership but we keep Python ref
self.addWidget(plot_box)
# Defer update to allow Qt to fully establish ownership
# This prevents premature garbage collection of the PlotBox
QtCore.QTimer.singleShot(0, lambda: plot_box.update(first=True))

def delete_plot_box(self, plot_name):
for i, pb in enumerate(list(self.plot_boxes)):
if pb.plot_name == plot_name:
self.plot_boxes.pop(i)
self.plot_names.pop(i)
self.removeItem(pb)
clear_layout(pb)
self.removeWidget(pb)
pb.setParent(None)
pb.deleteLater()
break
self.update()

Expand Down