source: sasview/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py @ 09e0c32

ESS_GUIESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 09e0c32 was 09e0c32, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 5 years ago

Added tooltip on COnstraints table.
Added "validate" parameter to Constraint, allowing for looser validation
of complex, multi-fitpage setups.

  • Property mode set to 100644
File size: 29.7 KB
Line 
1import logging
2
3from twisted.internet import threads
4
5import sas.qtgui.Utilities.GuiUtils as GuiUtils
6import sas.qtgui.Utilities.LocalConfig as LocalConfig
7
8from PyQt5 import QtGui, QtCore, QtWidgets
9
10from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
11
12import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
13from sas.qtgui.Perspectives.Fitting.UI.ConstraintWidgetUI import Ui_ConstraintWidgetUI
14from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget
15from sas.qtgui.Perspectives.Fitting.FitThread import FitThread
16from sas.qtgui.Perspectives.Fitting.ConsoleUpdate import ConsoleUpdate
17from sas.qtgui.Perspectives.Fitting.ComplexConstraint import ComplexConstraint
18from sas.qtgui.Perspectives.Fitting.Constraint import Constraint
19
20class ConstraintWidget(QtWidgets.QWidget, Ui_ConstraintWidgetUI):
21    """
22    Constraints Dialog to select the desired parameter/model constraints.
23    """
24    fitCompleteSignal = QtCore.pyqtSignal(tuple)
25    def __init__(self, parent=None):
26        super(ConstraintWidget, self).__init__()
27        self.parent = parent
28        self.setupUi(self)
29        self.currentType = "FitPage"
30        # Page id for fitting
31        # To keep with previous SasView values, use 300 as the start offset
32        self.page_id = 301
33        self.tab_id = self.page_id
34
35        # Are we chain fitting?
36        self.is_chain_fitting = False
37
38        # Remember previous content of modified cell
39        self.current_cell = ""
40
41        # Tabs used in simultaneous fitting
42        # tab_name : True/False
43        self.tabs_for_fitting = {}
44
45        # Set up the widgets
46        self.initializeWidgets()
47
48        # Set up signals/slots
49        self.initializeSignals()
50
51        # Create the list of tabs
52        self.initializeFitList()
53
54    def acceptsData(self):
55        """ Tells the caller this widget doesn't accept data """
56        return False
57
58    def initializeWidgets(self):
59        """
60        Set up various widget states
61        """
62        labels = ['FitPage', 'Model', 'Data', 'Mnemonic']
63        # tab widget - headers
64        self.editable_tab_columns = [labels.index('Mnemonic')]
65        self.tblTabList.setColumnCount(len(labels))
66        self.tblTabList.setHorizontalHeaderLabels(labels)
67        self.tblTabList.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
68
69        self.tblTabList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
70        self.tblTabList.customContextMenuRequested.connect(self.showModelContextMenu)
71
72        # Single Fit is the default, so disable chainfit
73        self.chkChain.setVisible(False)
74
75        # disabled constraint
76        labels = ['Constraint']
77        self.tblConstraints.setColumnCount(len(labels))
78        self.tblConstraints.setHorizontalHeaderLabels(labels)
79        self.tblConstraints.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
80        self.tblConstraints.setEnabled(False)
81        header = self.tblConstraints.horizontalHeaderItem(0)
82        header.setToolTip("Double click to edit.")
83
84        self.tblConstraints.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
85        self.tblConstraints.customContextMenuRequested.connect(self.showConstrContextMenu)
86
87    def initializeSignals(self):
88        """
89        Set up signals/slots for this widget
90        """
91        # simple widgets
92        self.btnSingle.toggled.connect(self.onFitTypeChange)
93        self.btnBatch.toggled.connect(self.onFitTypeChange)
94        self.cbCases.currentIndexChanged.connect(self.onSpecialCaseChange)
95        self.cmdFit.clicked.connect(self.onFit)
96        self.cmdHelp.clicked.connect(self.onHelp)
97        self.cmdAdd.clicked.connect(self.showMultiConstraint)
98        self.chkChain.toggled.connect(self.onChainFit)
99
100        # QTableWidgets
101        self.tblTabList.cellChanged.connect(self.onTabCellEdit)
102        self.tblTabList.cellDoubleClicked.connect(self.onTabCellEntered)
103        self.tblConstraints.cellChanged.connect(self.onConstraintChange)
104
105        # Internal signals
106        self.fitCompleteSignal.connect(self.fitComplete)
107
108        # External signals
109        self.parent.tabsModifiedSignal.connect(self.initializeFitList)
110
111    def updateSignalsFromTab(self, tab=None):
112        """
113        Intercept update signals from fitting tabs
114        """
115        if tab is None:
116            return
117        tab_object = ObjectLibrary.getObject(tab)
118
119        # Disconnect all local slots
120        #tab_object.disconnect()
121
122        # Reconnect tab signals to local slots
123        tab_object.constraintAddedSignal.connect(self.initializeFitList)
124        tab_object.newModelSignal.connect(self.initializeFitList)
125
126    def onFitTypeChange(self, checked):
127        """
128        Respond to the fit type change
129        single fit/batch fit
130        """
131        source = self.sender().objectName()
132        self.currentType = "BatchPage" if source == "btnBatch" else "FitPage"
133        self.chkChain.setVisible(source=="btnBatch")
134        self.initializeFitList()
135
136    def onSpecialCaseChange(self, index):
137        """
138        Respond to the combobox change for special case constraint sets
139        """
140        pass
141
142    def getTabsForFit(self):
143        """
144        Returns list of tab names selected for fitting
145        """
146        return [tab for tab in self.tabs_for_fitting if self.tabs_for_fitting[tab]]
147
148    def onChainFit(self, is_checked):
149        """
150        Respond to selecting the Chain Fit checkbox
151        """
152        self.is_chain_fitting = is_checked
153
154    def onFit(self):
155        """
156        Perform the constrained/simultaneous fit
157        """
158        # Find out all tabs to fit
159        tabs_to_fit = self.getTabsForFit()
160
161        # Single fitter for the simultaneous run
162        fitter = Fit()
163        fitter.fitter_id = self.page_id
164
165        # prepare fitting problems for each tab
166        #
167        page_ids = []
168        fitter_id = 0
169        sim_fitter_list=[fitter]
170        # Prepare the fitter object
171        try:
172            for tab in tabs_to_fit:
173                tab_object = ObjectLibrary.getObject(tab)
174                if tab_object is None:
175                    # No such tab!
176                    return
177                sim_fitter_list, fitter_id = \
178                    tab_object.prepareFitters(fitter=sim_fitter_list[0], fit_id=fitter_id)
179                page_ids.append([tab_object.page_id])
180        except ValueError:
181            # No parameters selected in one of the tabs
182            no_params_msg = "Fitting cannot be performed.\n" +\
183                            "Not all tabs chosen for fitting have parameters selected for fitting."
184            QtWidgets.QMessageBox.warning(self,
185                                          'Warning',
186                                           no_params_msg,
187                                           QtWidgets.QMessageBox.Ok)
188
189            return
190
191        # Create the fitting thread, based on the fitter
192        completefn = self.onBatchFitComplete if self.currentType=='BatchPage' else self.onFitComplete
193
194        if LocalConfig.USING_TWISTED:
195            handler = None
196            updater = None
197        else:
198            handler = ConsoleUpdate(parent=self.parent,
199                                    manager=self,
200                                    improvement_delta=0.1)
201            updater = handler.update_fit
202
203        batch_inputs = {}
204        batch_outputs = {}
205
206        # Notify the parent about fitting started
207        self.parent.fittingStartedSignal.emit(tabs_to_fit)
208
209        # new fit thread object
210        calc_fit = FitThread(handler=handler,
211                             fn=sim_fitter_list,
212                             batch_inputs=batch_inputs,
213                             batch_outputs=batch_outputs,
214                             page_id=page_ids,
215                             updatefn=updater,
216                             completefn=completefn,
217                             reset_flag=self.is_chain_fitting)
218
219        if LocalConfig.USING_TWISTED:
220            # start the trhrhread with twisted
221            calc_thread = threads.deferToThread(calc_fit.compute)
222            calc_thread.addCallback(completefn)
223            calc_thread.addErrback(self.onFitFailed)
224        else:
225            # Use the old python threads + Queue
226            calc_fit.queue()
227            calc_fit.ready(2.5)
228
229
230        #disable the Fit button
231        self.cmdFit.setText('Running...')
232        self.parent.communicate.statusBarUpdateSignal.emit('Fitting started...')
233        self.cmdFit.setEnabled(False)
234
235    def onHelp(self):
236        """
237        Show the "Fitting" section of help
238        """
239        tree_location = "/user/qtgui/Perspectives/Fitting/"
240
241        helpfile = "fitting_help.html#simultaneous-fit-mode"
242        help_location = tree_location + helpfile
243
244        # OMG, really? Crawling up the object hierarchy...
245        self.parent.parent.showHelp(help_location)
246
247    def onTabCellEdit(self, row, column):
248        """
249        Respond to check/uncheck and to modify the model moniker actions
250        """
251        item = self.tblTabList.item(row, column)
252        if column == 0:
253            # Update the tabs for fitting list
254            tab_name = item.text()
255            self.tabs_for_fitting[tab_name] = (item.checkState() == QtCore.Qt.Checked)
256            # Enable fitting only when there are models to fit
257            self.cmdFit.setEnabled(any(self.tabs_for_fitting.values()))
258
259        if column not in self.editable_tab_columns:
260            return
261        new_moniker = item.data(0)
262
263        # The new name should be validated on the fly, with QValidator
264        # but let's just assure it post-factum
265        is_good_moniker = self.validateMoniker(new_moniker)
266        if not is_good_moniker:
267            self.tblTabList.blockSignals(True)
268            item.setBackground(QtCore.Qt.red)
269            self.tblTabList.blockSignals(False)
270            self.cmdFit.setEnabled(False)
271            return
272        self.tblTabList.blockSignals(True)
273        item.setBackground(QtCore.Qt.white)
274        self.tblTabList.blockSignals(False)
275        self.cmdFit.setEnabled(True)
276        if not self.current_cell:
277            return
278        # Remember the value
279        if self.current_cell not in self.available_tabs:
280            return
281        temp_tab = self.available_tabs[self.current_cell]
282        # Remove the key from the dictionaries
283        self.available_tabs.pop(self.current_cell, None)
284        # Change the model name
285        model = temp_tab.kernel_module
286        model.name = new_moniker
287        # Replace constraint name
288        temp_tab.replaceConstraintName(self.current_cell, new_moniker)
289        # Replace constraint name in the remaining tabs
290        for tab in self.available_tabs.values():
291            tab.replaceConstraintName(self.current_cell, new_moniker)
292        # Reinitialize the display
293        self.initializeFitList()
294
295    def onConstraintChange(self, row, column):
296        """
297        Modify the constraint's "active" instance variable.
298        """
299        item = self.tblConstraints.item(row, column)
300        if column != 0: return
301        # Update the tabs for fitting list
302        constraint = self.available_constraints[row]
303        constraint.active = (item.checkState() == QtCore.Qt.Checked)
304        # Update the constraint formula
305        constraint = self.available_constraints[row]
306        function = item.text()
307        # remove anything left of '=' to get the constraint
308        function = function[function.index('=')+1:]
309        # No check on function here - trust the user (R)
310        if function != constraint.func:
311            # This becomes rather difficult to validate now.
312            # Turn off validation for Edit Constraint
313            constraint.func = function
314            constraint.validate = False
315
316    def onTabCellEntered(self, row, column):
317        """
318        Remember the original tab list cell data.
319        Needed for reverting back on bad validation
320        """
321        if column != 3:
322            return
323        self.current_cell = self.tblTabList.item(row, column).data(0)
324
325    def onFitComplete(self, result):
326        """
327        Send the fit complete signal to main thread
328        """
329        self.fitCompleteSignal.emit(result)
330
331    def fitComplete(self, result):
332        """
333        Respond to the successful fit complete signal
334        """
335        #re-enable the Fit button
336        self.cmdFit.setText("Fit")
337        self.cmdFit.setEnabled(True)
338
339        # Notify the parent about completed fitting
340        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
341
342        # Assure the fitting succeeded
343        if result is None or not result:
344            msg = "Fitting failed. Please ensure correctness of chosen constraints."
345            self.parent.communicate.statusBarUpdateSignal.emit(msg)
346            return
347
348        # get the elapsed time
349        elapsed = result[1]
350
351        # result list
352        results = result[0][0]
353
354        # Find out all tabs to fit
355        tabs_to_fit = [tab for tab in self.tabs_for_fitting if self.tabs_for_fitting[tab]]
356
357        # update all involved tabs
358        for i, tab in enumerate(tabs_to_fit):
359            tab_object = ObjectLibrary.getObject(tab)
360            if tab_object is None:
361                # No such tab. removed while job was running
362                return
363            # Make sure result and target objects are the same (same model moniker)
364            if tab_object.kernel_module.name == results[i].model.name:
365                tab_object.fitComplete(([[results[i]]], elapsed))
366
367        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
368        self.parent.communicate.statusBarUpdateSignal.emit(msg)
369
370    def onBatchFitComplete(self, result):
371        """
372        Respond to the successful batch fit complete signal
373        """
374        #re-enable the Fit button
375        self.cmdFit.setText("Fit")
376        self.cmdFit.setEnabled(True)
377
378        # Notify the parent about completed fitting
379        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
380
381        # get the elapsed time
382        elapsed = result[1]
383
384        if result is None:
385            msg = "Fitting failed."
386            self.parent.communicate.statusBarUpdateSignal.emit(msg)
387            return
388
389        # Show the grid panel
390        self.parent.communicate.sendDataToGridSignal.emit(result[0])
391
392        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
393        self.parent.communicate.statusBarUpdateSignal.emit(msg)
394
395    def onFitFailed(self, reason):
396        """
397        Respond to fitting failure.
398        """
399        #re-enable the Fit button
400        self.cmdFit.setText("Fit")
401        self.cmdFit.setEnabled(True)
402
403        # Notify the parent about completed fitting
404        self.parent.fittingStoppedSignal.emit(self.getTabsForFit())
405
406        msg = "Fitting failed: %s s.\n" % reason
407        self.parent.communicate.statusBarUpdateSignal.emit(msg)
408
409    def isTabImportable(self, tab):
410        """
411        Determines if the tab can be imported and included in the widget
412        """
413        if not isinstance(tab, str): return False
414        if not self.currentType in tab: return False
415        object = ObjectLibrary.getObject(tab)
416        if not isinstance(object, FittingWidget): return False
417        if not object.data_is_loaded : return False
418        return True
419
420    def showModelContextMenu(self, position):
421        """
422        Show context specific menu in the tab table widget.
423        """
424        menu = QtWidgets.QMenu()
425        rows = [s.row() for s in self.tblTabList.selectionModel().selectedRows()]
426        num_rows = len(rows)
427        if num_rows <= 0:
428            return
429        # Select for fitting
430        param_string = "Fit Page " if num_rows==1 else "Fit Pages "
431
432        self.actionSelect = QtWidgets.QAction(self)
433        self.actionSelect.setObjectName("actionSelect")
434        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
435        # Unselect from fitting
436        self.actionDeselect = QtWidgets.QAction(self)
437        self.actionDeselect.setObjectName("actionDeselect")
438        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
439
440        self.actionRemoveConstraint = QtWidgets.QAction(self)
441        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
442        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove all constraints on selected models"))
443
444        self.actionMutualMultiConstrain = QtWidgets.QAction(self)
445        self.actionMutualMultiConstrain.setObjectName("actionMutualMultiConstrain")
446        self.actionMutualMultiConstrain.setText(QtCore.QCoreApplication.translate("self", "Mutual constrain of parameters in selected models..."))
447
448        menu.addAction(self.actionSelect)
449        menu.addAction(self.actionDeselect)
450        menu.addSeparator()
451
452        if num_rows >= 2:
453            menu.addAction(self.actionMutualMultiConstrain)
454
455        # Define the callbacks
456        self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint)
457        self.actionSelect.triggered.connect(self.selectModels)
458        self.actionDeselect.triggered.connect(self.deselectModels)
459        try:
460            menu.exec_(self.tblTabList.viewport().mapToGlobal(position))
461        except AttributeError as ex:
462            logging.error("Error generating context menu: %s" % ex)
463        return
464
465    def showConstrContextMenu(self, position):
466        """
467        Show context specific menu in the tab table widget.
468        """
469        menu = QtWidgets.QMenu()
470        rows = [s.row() for s in self.tblConstraints.selectionModel().selectedRows()]
471        num_rows = len(rows)
472        if num_rows <= 0:
473            return
474        # Select for fitting
475        param_string = "constraint " if num_rows==1 else "constraints "
476
477        self.actionSelect = QtWidgets.QAction(self)
478        self.actionSelect.setObjectName("actionSelect")
479        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
480        # Unselect from fitting
481        self.actionDeselect = QtWidgets.QAction(self)
482        self.actionDeselect.setObjectName("actionDeselect")
483        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
484
485        self.actionRemoveConstraint = QtWidgets.QAction(self)
486        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
487        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove "+param_string))
488
489        menu.addAction(self.actionSelect)
490        menu.addAction(self.actionDeselect)
491        menu.addSeparator()
492        menu.addAction(self.actionRemoveConstraint)
493
494        # Define the callbacks
495        self.actionRemoveConstraint.triggered.connect(self.deleteConstraint)
496        self.actionSelect.triggered.connect(self.selectConstraints)
497        self.actionDeselect.triggered.connect(self.deselectConstraints)
498        try:
499            menu.exec_(self.tblConstraints.viewport().mapToGlobal(position))
500        except AttributeError as ex:
501            logging.error("Error generating context menu: %s" % ex)
502        return
503
504    def selectConstraints(self):
505        """
506        Selected constraints are chosen for fitting
507        """
508        status = QtCore.Qt.Checked
509        self.setRowSelection(self.tblConstraints, status)
510
511    def deselectConstraints(self):
512        """
513        Selected constraints are removed for fitting
514        """
515        status = QtCore.Qt.Unchecked
516        self.setRowSelection(self.tblConstraints, status)
517
518    def selectModels(self):
519        """
520        Selected models are chosen for fitting
521        """
522        status = QtCore.Qt.Checked
523        self.setRowSelection(self.tblTabList, status)
524
525    def deselectModels(self):
526        """
527        Selected models are removed for fitting
528        """
529        status = QtCore.Qt.Unchecked
530        self.setRowSelection(self.tblTabList, status)
531
532    def selectedParameters(self, widget):
533        """ Returns list of selected (highlighted) parameters """
534        return [s.row() for s in widget.selectionModel().selectedRows()]
535
536    def setRowSelection(self, widget, status=QtCore.Qt.Unchecked):
537        """
538        Selected models are chosen for fitting
539        """
540        # Convert to proper indices and set requested enablement
541        for row in self.selectedParameters(widget):
542            widget.item(row, 0).setCheckState(status)
543
544    def deleteConstraint(self):#, row):
545        """
546        Delete all selected constraints.
547        """
548        # Removing rows from the table we're iterating over,
549        # so prepare a list of data first
550        constraints_to_delete = []
551        for row in self.selectedParameters(self.tblConstraints):
552            constraints_to_delete.append(self.tblConstraints.item(row, 0).data(0))
553        for constraint in constraints_to_delete:
554            moniker = constraint[:constraint.index(':')]
555            param = constraint[constraint.index(':')+1:constraint.index('=')].strip()
556            tab = self.available_tabs[moniker]
557            tab.deleteConstraintOnParameter(param)
558        # Constraints removed - refresh the table widget
559        self.initializeFitList()
560
561    def uneditableItem(self, data=""):
562        """
563        Returns an uneditable Table Widget Item
564        """
565        item = QtWidgets.QTableWidgetItem(data)
566        item.setFlags( QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled )
567        return item
568
569    def updateFitLine(self, tab):
570        """
571        Update a single line of the table widget with tab info
572        """
573        fit_page = ObjectLibrary.getObject(tab)
574        model = fit_page.kernel_module
575        if model is None:
576            return
577        tab_name = tab
578        model_name = model.id
579        moniker = model.name
580        model_data = fit_page.data
581        model_filename = model_data.filename
582        self.available_tabs[moniker] = fit_page
583
584        # Update the model table widget
585        pos = self.tblTabList.rowCount()
586        self.tblTabList.insertRow(pos)
587        item = self.uneditableItem(tab_name)
588        item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)
589        if tab_name in self.tabs_for_fitting:
590            state = QtCore.Qt.Checked if self.tabs_for_fitting[tab_name] else QtCore.Qt.Unchecked
591            item.setCheckState(state)
592        else:
593            item.setCheckState(QtCore.Qt.Checked)
594            self.tabs_for_fitting[tab_name] = True
595
596        # Disable signals so we don't get infinite call recursion
597        self.tblTabList.blockSignals(True)
598        self.tblTabList.setItem(pos, 0, item)
599        self.tblTabList.setItem(pos, 1, self.uneditableItem(model_name))
600        self.tblTabList.setItem(pos, 2, self.uneditableItem(model_filename))
601        # Moniker is editable, so no option change
602        item = QtWidgets.QTableWidgetItem(moniker)
603        self.tblTabList.setItem(pos, 3, item)
604        self.tblTabList.blockSignals(False)
605
606        # Check if any constraints present in tab
607        constraint_names = fit_page.getComplexConstraintsForModel()
608        constraints = fit_page.getConstraintObjectsForModel()
609        if not constraints: 
610            return
611        self.tblConstraints.setEnabled(True)
612        self.tblConstraints.blockSignals(True)
613        for constraint, constraint_name in zip(constraints, constraint_names):
614            # Create the text for widget item
615            label = moniker + ":"+ constraint_name[0] + " = " + constraint_name[1]
616            pos = self.tblConstraints.rowCount()
617            self.available_constraints[pos] = constraint
618
619            # Show the text in the constraint table
620            item = self.uneditableItem(label)
621            item = QtWidgets.QTableWidgetItem(label)
622            item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)
623            item.setCheckState(QtCore.Qt.Checked)
624            self.tblConstraints.insertRow(pos)
625            self.tblConstraints.setItem(pos, 0, item)
626        self.tblConstraints.blockSignals(False)
627
628    def initializeFitList(self):
629        """
630        Fill the list of model/data sets for fitting/constraining
631        """
632        # look at the object library to find all fit tabs
633        # Show the content of the current "model"
634        objects = ObjectLibrary.listObjects()
635
636        # Tab dict
637        # moniker -> (kernel_module, data)
638        self.available_tabs = {}
639        # Constraint dict
640        # moniker -> [constraints]
641        self.available_constraints = {}
642
643        # Reset the table widgets
644        self.tblTabList.setRowCount(0)
645        self.tblConstraints.setRowCount(0)
646
647        # Fit disabled
648        self.cmdFit.setEnabled(False)
649
650        if not objects:
651            return
652
653        tabs = [tab for tab in ObjectLibrary.listObjects() if self.isTabImportable(tab)]
654        for tab in tabs:
655            self.updateFitLine(tab)
656            self.updateSignalsFromTab(tab)
657            # We have at least 1 fit page, allow fitting
658            self.cmdFit.setEnabled(True)
659
660    def validateMoniker(self, new_moniker=None):
661        """
662        Check new_moniker for correctness.
663        It must be non-empty.
664        It must not be the same as other monikers.
665        """
666        if not new_moniker:
667            return False
668
669        for existing_moniker in self.available_tabs:
670            if existing_moniker == new_moniker and existing_moniker != self.current_cell:
671                return False
672
673        return True
674
675    def getObjectByName(self, name):
676        """
677        Given name of the fit, returns associated fit object
678        """
679        for object_name in ObjectLibrary.listObjects():
680            object = ObjectLibrary.getObject(object_name)
681            if isinstance(object, FittingWidget):
682                try:
683                    if object.kernel_module.name == name:
684                        return object
685                except AttributeError:
686                    # Disregard atribute errors - empty fit widgets
687                    continue
688        return None
689
690    def onAcceptConstraint(self, con_tuple):
691        """
692        Receive constraint tuple from the ComplexConstraint dialog and adds contraint
693        """
694        #"M1, M2, M3" etc
695        model_name, constraint = con_tuple
696        constrained_tab = self.getObjectByName(model_name)
697        if constrained_tab is None:
698            return
699
700        # Find the constrained parameter row
701        constrained_row = constrained_tab.getRowFromName(constraint.param)
702
703        # Update the tab
704        constrained_tab.addConstraintToRow(constraint, constrained_row)
705
706    def showMultiConstraint(self):
707        """
708        Invoke the complex constraint editor
709        """
710        selected_rows = self.selectedParameters(self.tblTabList)
711        if len(selected_rows)!=2:
712            msg = "Please select two fit pages from the Source Choice table."
713            msgbox = QtWidgets.QMessageBox(self.parent)
714            msgbox.setIcon(QtWidgets.QMessageBox.Warning)
715            msgbox.setText(msg)
716            msgbox.setWindowTitle("2 fit page constraints")
717            retval = msgbox.exec_()
718            return
719
720        tab_list = [ObjectLibrary.getObject(self.tblTabList.item(s, 0).data(0)) for s in selected_rows]
721        # Create and display the widget for param1 and param2
722        cc_widget = ComplexConstraint(self, tabs=tab_list)
723        cc_widget.constraintReadySignal.connect(self.onAcceptConstraint)
724
725        if cc_widget.exec_() != QtWidgets.QDialog.Accepted:
726            return
727
728    def getFitPage(self):
729        """
730        Retrieves the state of this page
731        """
732        param_list = []
733
734        param_list.append(['is_constraint', 'True'])
735        param_list.append(['data_id', "cs_tab"+str(self.page_id)])
736        param_list.append(['current_type', self.currentType])
737        param_list.append(['is_chain_fitting', str(self.is_chain_fitting)])
738        param_list.append(['special_case', self.cbCases.currentText()])
739
740        return param_list
741
742    def getFitModel(self):
743        """
744        Retrieves current model
745        """
746        model_list = []
747
748        checked_models = {}
749        for row in range(self.tblTabList.rowCount()):
750            model_name = self.tblTabList.item(row,1).data(0)
751            active = self.tblTabList.item(row,0).checkState()# == QtCore.Qt.Checked
752            checked_models[model_name] = str(active)
753
754        checked_constraints = {}
755        for row in range(self.tblConstraints.rowCount()):
756            model_name = self.tblConstraints.item(row,0).data(0)
757            active = self.tblConstraints.item(row,0).checkState()# == QtCore.Qt.Checked
758            checked_constraints[model_name] = str(active)
759
760        model_list.append(['checked_models', checked_models])
761        model_list.append(['checked_constraints', checked_constraints])
762        return model_list
763
764    def createPageForParameters(self, parameters=None):
765        """
766        Update the page with passed parameter values
767        """
768        # checked models
769        if not 'checked_models' in parameters:
770            return
771        models = parameters['checked_models'][0]
772        for model, check_state in models.items():
773            for row in range(self.tblTabList.rowCount()):
774                model_name = self.tblTabList.item(row,1).data(0)
775                if model_name != model:
776                    continue
777                # check/uncheck item
778                self.tblTabList.item(row,0).setCheckState(int(check_state))
779
780        if not 'checked_constraints' in parameters:
781            return
782        # checked constraints
783        models = parameters['checked_constraints'][0]
784        for model, check_state in models.items():
785            for row in range(self.tblConstraints.rowCount()):
786                model_name = self.tblConstraints.item(row,0).data(0)
787                if model_name != model:
788                    continue
789                # check/uncheck item
790                self.tblConstraints.item(row,0).setCheckState(int(check_state))
791
792        # fit/batch radio
793        isBatch = parameters['current_type'][0] == 'BatchPage'
794        if isBatch:
795            self.btnBatch.toggle()
796
797        # chain
798        is_chain = parameters['is_chain_fitting'][0] == 'True'
799        if isBatch:
800            self.chkChain.setChecked(is_chain)
801
802
Note: See TracBrowser for help on using the repository browser.