Skip to content

Commit 0d5515b

Browse files
committed
Merge branch 'master' of https://github.com/scqubits/qfit into ztp_debug
2 parents c9094be + d984c55 commit 0d5515b

File tree

8 files changed

+120
-49
lines changed

8 files changed

+120
-49
lines changed

qfit/controllers/fit_ctrl.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,20 +114,22 @@ def dynamicalInit(self):
114114
reinitialize the all relevant models and views. In particular, the
115115
fitting parameters are initialized with the prefit parameters.
116116
"""
117-
# build paramset
117+
# model: build paramset
118+
self.fitHSParams.clear()
118119
self.fitHSParams.setAttrByParamSet(
119120
self.prefitHSParams.toFitParams(),
120121
insertMissing=True,
121122
)
122123
# change this later to make it more safe
123124
self.fitHSParams.parentNameByObj = self.prefitHSParams.parentNameByObj
124125
self.fitHSParams.parentObjByName = self.prefitHSParams.parentObjByName
125-
126+
self.fitCaliParams.clear()
126127
self.fitCaliParams.setAttrByParamSet(
127128
self.caliParamModel.toFitParams(),
128129
insertMissing=True,
129130
)
130-
# insert parameters
131+
132+
# view: insert parameters
131133
self.fitParamView.fitTableInserts(
132134
self.fitHSParams.paramNamesDict(),
133135
self.fitCaliParams.paramNamesDict(),

qfit/controllers/io_ctrl.py

Lines changed: 69 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
List,
2828
Callable,
2929
Tuple,
30+
Literal,
3031
)
3132

3233
if TYPE_CHECKING:
@@ -272,19 +273,11 @@ def _saveAndCloseApp(self, save_as: bool = False):
272273
if not success:
273274
return
274275
self._closeApp()
275-
276-
def closeAppAfterSaving(self) -> bool:
276+
277+
def _unsavedChangesExist(self) -> bool:
277278
"""
278-
Close the app after asking the user whether to save the changes.
279-
280-
Returns
281-
-------
282-
bool
283-
whether the app is closed
279+
Check if there are any unsaved changes.
284280
"""
285-
# first, if the project is open from a file, check the registry dict of the old file
286-
# with that obtained from the current session, if something changed, ask the user
287-
# whether to save the changes
288281
if self.mainWindow.projectFile is not None:
289282
registryDict = copy.deepcopy(self.registry.exportDict())
290283
registryDictFromFile = copy.deepcopy(
@@ -312,8 +305,36 @@ def closeAppAfterSaving(self) -> bool:
312305

313306
else:
314307
self.mainWindow.unsavedChanges = True
308+
309+
return self.mainWindow.unsavedChanges
310+
311+
def _saveCheckWithDialog(
312+
self,
313+
) -> Literal[
314+
"SAVE_AND_CLOSE",
315+
"CLOSE",
316+
"CANCEL",
317+
]:
318+
"""
319+
Before closing the app or moving to a new opened file, check if there
320+
are any unsaved changes.
315321
316-
if self.mainWindow.unsavedChanges and self.measDataSet.importFinished:
322+
Returns
323+
-------
324+
Literal[
325+
"SAVE_AND_CLOSE",
326+
"CLOSE",
327+
"CANCEL",
328+
]
329+
The action to take before closing the app.
330+
"""
331+
# first, if the project is open from a file, check the registry dict of the old file
332+
# with that obtained from the current session, if something changed, ask the user
333+
# whether to save the changes
334+
unsavedChangesExist = self._unsavedChangesExist()
335+
336+
# if there are unsaved changes, ask the user whether to save the changes
337+
if unsavedChangesExist and self.measDataSet.importFinished:
317338
msgBox = QMessageBox()
318339
msgBox.setWindowTitle("qfit")
319340
msgBox.setIcon(QMessageBox.Question)
@@ -327,17 +348,34 @@ def closeAppAfterSaving(self) -> bool:
327348
reply = msgBox.exec_()
328349

329350
if reply == QMessageBox.Save:
330-
self._saveAndCloseApp(save_as=self.mainWindow.projectFile is None)
331-
return True
351+
return "SAVE_AND_CLOSE"
332352
elif reply == QMessageBox.Discard:
333-
self._closeApp()
334-
return True
353+
return "CLOSE"
335354
else: # reply == QMessageBox.Cancel
336-
return False
355+
return "CANCEL"
337356

338357
else:
358+
return "CLOSE"
359+
360+
def closeAppAfterSaving(self) -> bool:
361+
"""
362+
Close the app after asking the user whether to save the changes.
363+
364+
Returns
365+
-------
366+
bool
367+
whether the app is closed
368+
"""
369+
status = self._saveCheckWithDialog()
370+
if status == "SAVE_AND_CLOSE":
371+
self._saveAndCloseApp(save_as=self.mainWindow.projectFile is None)
372+
return True
373+
elif status == "CLOSE":
339374
self._closeApp()
340375
return True
376+
else: # status == "CANCEL"
377+
return False
378+
341379

342380
# slots ###################################################################
343381
@Slot()
@@ -418,9 +456,20 @@ def openFile(
418456
cause correlated updates to the original HilbertSpace object / other
419457
HilbertSpace objects.
420458
"""
421-
if fromMenu and self.menu.isVisible():
422-
self.menu.toggle()
423-
459+
# check if there are unsaved changes in the current project
460+
if fromMenu:
461+
if self.menu.isVisible():
462+
self.menu.toggle()
463+
464+
savingStatus = self._saveCheckWithDialog()
465+
print(savingStatus)
466+
if savingStatus == "SAVE_AND_CLOSE":
467+
self._saveProject()
468+
elif savingStatus == "CLOSE":
469+
pass
470+
else: # savingStatus == "CANCEL"
471+
return
472+
424473
# check if file exists
425474
if fileName is not None:
426475
if not os.path.isfile(fileName):

qfit/controllers/meas_data_ctrl.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,5 @@ def dataLoadConnects(self) -> None:
105105
"""
106106
Establish connections for reading .qfit file.
107107
"""
108-
self.measDataSet.dataLoaded.connect(lambda _: self.continueToPostImportStages())
109-
self.measDataSet.dataLoaded.connect(self.measDataView.reloadFig)
108+
self.measDataSet.dataReloadCompleted.connect(lambda _: self.continueToPostImportStages())
109+
self.measDataSet.dataReloadCompleted.connect(self.measDataView.reloadFig)

qfit/controllers/prefit_ctrl.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ def _inheritCaliParams(
263263
self,
264264
):
265265
# initialize calibration parameters
266+
self.prefitCaliParams.clear()
266267
self.prefitCaliParams.setAttrByParamSet(
267268
self.caliParamModel.toPrefitParams(),
268269
insertMissing=True,

qfit/models/measurement_data.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,7 +1102,7 @@ class MeasDataSet(QAbstractListModel, Registrable, metaclass=ListModelMeta):
11021102
rawXYConfigChanged = Signal(MeasRawXYConfig)
11031103
updateStatus = Signal(Status)
11041104
newFigAdded = Signal(list)
1105-
dataLoaded = Signal(list)
1105+
dataReloadCompleted = Signal(list)
11061106

11071107
# single data processing
11081108
readyToPlot = Signal(PlotElement)
@@ -1128,8 +1128,10 @@ def __init__(self, parent: QObject | None = None):
11281128
# init & load data list ============================================
11291129
def loadDataSet(self, measDataList: List[MeasDataType]):
11301130
"""
1131-
Replace all the measurement data with the new data. It will emit the
1132-
signals to update the view and proceed to the next stage.
1131+
Replace all the measurement data with the new data. It will
1132+
1. emit the signals to update the view
1133+
2. emit the dataReloadCompleted signal to proceed to the next stage,
1134+
including a full dynamical initialization.
11331135
"""
11341136
self.fullData = measDataList
11351137

@@ -1147,7 +1149,7 @@ def loadDataSet(self, measDataList: List[MeasDataType]):
11471149
dataNames = [measData.name for measData in self.fullData]
11481150

11491151
# emit to proceed to the next stage
1150-
self.dataLoaded.emit(dataNames)
1152+
self.dataReloadCompleted.emit(dataNames)
11511153

11521154
@staticmethod
11531155
def _rawDataFromFile(fileName) -> MeasDataType | None:

qfit/utils/helpers.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from labellines import labelLines
2525

2626
from PySide6 import QtCore as QtCore
27-
from PySide6.QtWidgets import QWidget, QPushButton
27+
from PySide6.QtWidgets import QWidget, QPushButton, QLayout, QLayoutItem, QSpacerItem
2828
from PySide6.QtCore import QEventLoop, QTimer
2929

3030
from typing import Dict, List, Literal, Optional, Tuple, Union, Callable
@@ -262,22 +262,45 @@ def makeUnique(names: List[str]):
262262

263263

264264
# widgets ######################################################################
265+
def clearLayout(layout: QLayout):
266+
"""Recursively clear all items from a layout"""
267+
if layout is None:
268+
return
269+
270+
while layout.count():
271+
item = layout.takeAt(0)
272+
if item is None:
273+
continue
274+
275+
# If it's a widget, delete it
276+
if item.widget():
277+
item.widget().setParent(None)
278+
item.widget().deleteLater()
279+
# If it's a layout, recursively clear it
280+
elif item.layout():
281+
clearLayout(item.layout())
282+
# If it's a spacer item, just delete it
283+
elif item.spacerItem():
284+
del item
285+
265286
def clearChildren(widget: QWidget):
266287
"""
267-
Clear all children of the given widget.
268-
288+
Clear all visible content from a widget, including child widgets and layout items like spacers.
289+
269290
Parameters
270291
----------
271292
widget: QWidget
272-
"""
273-
layout = widget.layout()
274-
if layout is None:
275-
return
276-
for i in reversed(range(layout.count())):
277-
widget = layout.itemAt(i).widget()
278-
if widget: # Check if the item is a widget
279-
widget.setParent(None)
280-
widget.deleteLater()
293+
The widget to clear all content from
294+
"""
295+
# Clear the widget's layout if it has one
296+
if widget.layout():
297+
clearLayout(widget.layout())
298+
299+
# Clear any remaining child widgets that might not be in layouts
300+
for child in widget.findChildren(QWidget):
301+
child.setParent(None)
302+
child.deleteLater()
303+
281304

282305

283306
def modifyStyleSheet(widget: QWidget, property_name: str, new_value: str):

qfit/views/calibration_view.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from qfit.models.data_structures import QMSweepParam, ParamAttr
3232

3333
from qfit.widgets.custom_table import FoldableTable, CollectionType, WidgetCollection
34-
from qfit.utils.helpers import modifyStyleSheet
34+
from qfit.utils.helpers import modifyStyleSheet, clearChildren
3535

3636

3737
class CalibrationView(QObject):
@@ -167,10 +167,7 @@ def dynamicalInit(
167167
the model will reinitialized by this method.
168168
"""
169169
# delete the calibration table entirely
170-
try:
171-
self.caliXTable.deleteLater()
172-
except AttributeError:
173-
pass
170+
clearChildren(self.caliXScrollAreaWidget)
174171

175172
# generate the X calibration table
176173
self.XParamItems = self._generateXParamItems()
@@ -369,8 +366,6 @@ def _generateLineEditSet(self):
369366

370367
def _generateXDataSourceSet(self):
371368
if self.XDataSourceSet != {}:
372-
for XRowIdx in range(self.caliTableXRowNr):
373-
self.XDataSourceSet[f"X{XRowIdx+1}"]["DATA<br>SOURCE"].setText("")
374369
self.XDataSourceSet.clear()
375370
for XRowIdx in range(self.caliTableXRowNr):
376371
self.XDataSourceSet[f"X{XRowIdx+1}"] = {}

qfit/views/fit_view.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ def fitTableInserts(
7979
removeExisting : bool, optional
8080
Whether to remove the existing widgets, by default True.
8181
"""
82-
8382
self.HSNames = list(HSParamNames.keys())
8483
paramNameDict = HSParamNames | caliParamNames
8584

0 commit comments

Comments
 (0)