source: sasview/src/sas/qtgui/Perspectives/Fitting/ConstraintWidget.py @ 93c79b5

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalc
Last change on this file since 93c79b5 was 8b480d27, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Code cleanup and minor fixes

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