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

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

Batch results tabs now have meaningful names SASVIEW-1204

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