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

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 47d7d2d was 47d7d2d, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Editable moniker field, updating the model below.
Improved table behaviour

  • Property mode set to 100755
File size: 16.1 KB
Line 
1import os
2import sys
3
4import sas.qtgui.Utilities.GuiUtils as GuiUtils
5from PyQt5 import QtGui, QtCore, QtWidgets
6
7import sas.qtgui.Utilities.ObjectLibrary as ObjectLibrary
8
9from sas.qtgui.Perspectives.Fitting.UI.ConstraintWidgetUI import Ui_ConstraintWidgetUI
10from sas.qtgui.Perspectives.Fitting.FittingWidget import FittingWidget
11
12class ConstraintWidget(QtWidgets.QWidget, Ui_ConstraintWidgetUI):
13    """
14    Constraints Dialog to select the desired parameter/model constraints.
15    """
16
17    def __init__(self, parent=None):
18        super(ConstraintWidget, self).__init__()
19        self.parent = parent
20        self.setupUi(self)
21        self.currentType = "FitPage"
22
23        # Remember previous content of modified cell
24        self.current_cell = ""
25
26        # Set up the widgets
27        self.initializeWidgets()
28
29        # Set up signals/slots
30        self.initializeSignals()
31
32        # Create the list of tabs
33        self.initializeFitList()
34
35    def acceptsData(self):
36        """ Tells the caller this widget doesn't accept data """
37        return False
38
39    def initializeWidgets(self):
40        """
41        Set up various widget states
42        """
43        labels = ['FitPage', 'Model', 'Data', 'Mnemonic']
44        # tab widget - headers
45        self.editable_tab_columns = [labels.index('Mnemonic')]
46        self.tblTabList.setColumnCount(len(labels))
47        self.tblTabList.setHorizontalHeaderLabels(labels)
48        self.tblTabList.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
49
50        self.tblTabList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
51        self.tblTabList.customContextMenuRequested.connect(self.showModelContextMenu)
52
53        # disabled constraint
54        labels = ['Constraint']
55        self.tblConstraints.setColumnCount(len(labels))
56        self.tblConstraints.setHorizontalHeaderLabels(labels)
57        self.tblConstraints.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)
58        self.tblConstraints.setEnabled(False)
59
60        self.tblConstraints.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
61        self.tblConstraints.customContextMenuRequested.connect(self.showConstrContextMenu)
62
63    def initializeSignals(self):
64        """
65        Set up signals/slots for this widget
66        """
67        # simple widgets
68        self.btnSingle.toggled.connect(self.onFitTypeChange)
69        self.btnBatch.toggled.connect(self.onFitTypeChange)
70        self.cbCases.currentIndexChanged.connect(self.onSpecialCaseChange)
71        self.cmdFit.clicked.connect(self.onFit)
72        self.cmdHelp.clicked.connect(self.onHelp)
73
74        # QTableWidgets
75        self.tblTabList.cellChanged.connect(self.onMonikerEdit)
76        self.tblTabList.cellDoubleClicked.connect(self.onTabCellEntered)
77        self.tblConstraints.cellChanged.connect(self.onConstraintChange)
78
79        # External signals
80        self.parent.tabsModifiedSignal.connect(self.initializeFitList)
81
82    def updateSignalsFromTab(self, tab=None):
83        """
84        Intercept update signals from fitting tabs
85        """
86        if tab is not None:
87            ObjectLibrary.getObject(tab).constraintAddedSignal.connect(self.initializeFitList)
88            ObjectLibrary.getObject(tab).newModelSignal.connect(self.initializeFitList)
89
90    def onFitTypeChange(self, checked):
91        """
92        Respond to the fit type change
93        single fit/batch fit
94        """
95        pass
96
97    def onSpecialCaseChange(self, index):
98        """
99        Respond to the combobox change for special case constraint sets
100        """
101        pass
102
103    def onFit(self):
104        """
105        Perform the constrained/simultaneous fit
106        """
107        pass
108
109    def onHelp(self):
110        """
111        Display the help page
112        """
113        pass
114
115    def onMonikerEdit(self, row, column):
116        """
117        Modify the model moniker
118        """
119        if column not in self.editable_tab_columns:
120            return
121        item = self.tblTabList.item(row, column)
122        new_moniker = item.data(0)
123
124        # The new name should be validated on the fly, with QValidator
125        # but let's just assure it post-factum
126        is_good_moniker = self.validateMoniker(new_moniker)
127        is_good_moniker = True
128        if not is_good_moniker:
129            item.setBackground(QtCore.Qt.red)
130            self.cmdFit.setEnabled(False)
131        else:
132            self.tblTabList.blockSignals(True)
133            item.setBackground(QtCore.Qt.white)
134            self.tblTabList.blockSignals(False)
135            self.cmdFit.setEnabled(True)
136            if not self.current_cell:
137                return
138            # Remember the value
139            if self.current_cell not in self.available_tabs:
140                return
141            temp_tab = self.available_tabs[self.current_cell]
142            # Remove the key from the dictionaries
143            self.available_tabs.pop(self.current_cell, None)
144            # Change the model name
145            model = temp_tab.kernel_module
146            model.name = new_moniker
147            # Replace constraint name
148            temp_tab.replaceConstraintName(self.current_cell, new_moniker)
149            # Reinitialize the display
150            self.initializeFitList()
151        pass
152
153    def onConstraintChange(self, row, column):
154        """
155        Modify the constraint in-place.
156        Tricky.
157        """
158        pass
159
160    def onTabCellEntered(self, row, column):
161        """
162        Remember the original tab list cell data.
163        Needed for reverting back on bad validation
164        """
165        if column != 3:
166            return
167        self.current_cell = self.tblTabList.item(row, column).data(0)
168
169    def isTabImportable(self, tab):
170        """
171        Determines if the tab can be imported and included in the widget
172        """
173        if not self.currentType in tab: return False
174        object = ObjectLibrary.getObject(tab)
175        if not isinstance(object, FittingWidget): return False
176        if object.data is None: return False
177        return True
178
179    def showModelContextMenu(self, position):
180        """
181        Show context specific menu in the tab table widget.
182        """
183        menu = QtWidgets.QMenu()
184        rows = [s.row() for s in self.tblTabList.selectionModel().selectedRows()]
185        num_rows = len(rows)
186        if num_rows <= 0:
187            return
188        # Select for fitting
189        param_string = "Fit Page " if num_rows==1 else "Fit Pages "
190        to_string = "to its current value" if num_rows==1 else "to their current values"
191
192        self.actionSelect = QtWidgets.QAction(self)
193        self.actionSelect.setObjectName("actionSelect")
194        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
195        # Unselect from fitting
196        self.actionDeselect = QtWidgets.QAction(self)
197        self.actionDeselect.setObjectName("actionDeselect")
198        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
199
200        self.actionRemoveConstraint = QtWidgets.QAction(self)
201        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
202        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove all constraints on selected models"))
203
204        self.actionMutualMultiConstrain = QtWidgets.QAction(self)
205        self.actionMutualMultiConstrain.setObjectName("actionMutualMultiConstrain")
206        self.actionMutualMultiConstrain.setText(QtCore.QCoreApplication.translate("self", "Mutual constrain of parameters in selected models..."))
207
208        menu.addAction(self.actionSelect)
209        menu.addAction(self.actionDeselect)
210        menu.addSeparator()
211
212        #menu.addAction(self.actionRemoveConstraint)
213        if num_rows >= 2:
214            menu.addAction(self.actionMutualMultiConstrain)
215
216        # Define the callbacks
217        #self.actionConstrain.triggered.connect(self.addSimpleConstraint)
218        #self.actionRemoveConstraint.triggered.connect(self.deleteConstraint)
219        self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint)
220        self.actionSelect.triggered.connect(self.selectModels)
221        self.actionDeselect.triggered.connect(self.deselectModels)
222        try:
223            menu.exec_(self.tblTabList.viewport().mapToGlobal(position))
224        except AttributeError as ex:
225            logging.error("Error generating context menu: %s" % ex)
226        return
227
228    def showConstrContextMenu(self, position):
229        """
230        Show context specific menu in the tab table widget.
231        """
232        menu = QtWidgets.QMenu()
233        rows = [s.row() for s in self.tblConstraints.selectionModel().selectedRows()]
234        num_rows = len(rows)
235        if num_rows <= 0:
236            return
237        # Select for fitting
238        param_string = "constraint " if num_rows==1 else "constraints "
239        to_string = "to its current value" if num_rows==1 else "to their current values"
240
241        self.actionSelect = QtWidgets.QAction(self)
242        self.actionSelect.setObjectName("actionSelect")
243        self.actionSelect.setText(QtCore.QCoreApplication.translate("self", "Select "+param_string+" for fitting"))
244        # Unselect from fitting
245        self.actionDeselect = QtWidgets.QAction(self)
246        self.actionDeselect.setObjectName("actionDeselect")
247        self.actionDeselect.setText(QtCore.QCoreApplication.translate("self", "De-select "+param_string+" from fitting"))
248
249        self.actionRemoveConstraint = QtWidgets.QAction(self)
250        self.actionRemoveConstraint.setObjectName("actionRemoveConstrain")
251        self.actionRemoveConstraint.setText(QtCore.QCoreApplication.translate("self", "Remove "+param_string))
252
253        menu.addAction(self.actionSelect)
254        menu.addAction(self.actionDeselect)
255        menu.addSeparator()
256        menu.addAction(self.actionRemoveConstraint)
257
258        # Define the callbacks
259        #self.actionConstrain.triggered.connect(self.addSimpleConstraint)
260        self.actionRemoveConstraint.triggered.connect(self.deleteConstraint)
261        #self.actionMutualMultiConstrain.triggered.connect(self.showMultiConstraint)
262        self.actionSelect.triggered.connect(self.selectConstraints)
263        self.actionDeselect.triggered.connect(self.deselectConstraints)
264        try:
265            menu.exec_(self.tblConstraints.viewport().mapToGlobal(position))
266        except AttributeError as ex:
267            logging.error("Error generating context menu: %s" % ex)
268        return
269
270    def selectConstraints(self):
271        """
272        Selected constraints are chosen for fitting
273        """
274        status = QtCore.Qt.Checked
275        self.setRowSelection(self.tblConstraints, status)
276
277    def deselectConstraints(self):
278        """
279        Selected constraints are removed for fitting
280        """
281        status = QtCore.Qt.Unchecked
282        self.setRowSelection(self.tblConstraints, status)
283
284    def selectModels(self):
285        """
286        Selected models are chosen for fitting
287        """
288        status = QtCore.Qt.Checked
289        self.setRowSelection(self.tblTabList, status)
290
291    def deselectModels(self):
292        """
293        Selected models are removed for fitting
294        """
295        status = QtCore.Qt.Unchecked
296        self.setRowSelection(self.tblTabList, status)
297
298    def selectedParameters(self, widget):
299        """ Returns list of selected (highlighted) parameters """
300        return [s.row() for s in widget.selectionModel().selectedRows()]
301
302    def setRowSelection(self, widget, status=QtCore.Qt.Unchecked):
303        """
304        Selected models are chosen for fitting
305        """
306        # Convert to proper indices and set requested enablement
307        for row in self.selectedParameters(widget):
308            widget.item(row, 0).setCheckState(status)
309
310    def deleteConstraint(self):#, row):
311        """
312        Delete all selected constraints.
313        """
314        # Removing rows from the table we're iterating over,
315        # so prepare a list of data first
316        constraints_to_delete = []
317        for row in self.selectedParameters(self.tblConstraints):
318            constraints_to_delete.append(self.tblConstraints.item(row, 0).data(0))
319        for constraint in constraints_to_delete:
320            moniker = constraint[:constraint.index(':')]
321            param = constraint[constraint.index(':')+1:constraint.index('=')].strip()
322            tab = self.available_tabs[moniker]
323            tab.deleteConstraintOnParameter(param)
324        # Constraints removed - refresh the table widget
325        self.initializeFitList()
326
327    def uneditableItem(self, data=""):
328        """
329        Returns an uneditable Table Widget Item
330        """
331        item = QtWidgets.QTableWidgetItem(data)
332        item.setFlags( QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled )
333        return item
334
335    def updateFitLine(self, tab):
336        """
337        Update a single line of the table widget with tab info
338        """
339        model = ObjectLibrary.getObject(tab).kernel_module
340        if model is None:
341            return
342        tab_name = tab
343        model_name = model.id
344        moniker = model.name
345        model_data = ObjectLibrary.getObject(tab).data
346        model_filename = model_data.filename
347        self.available_tabs[moniker] = ObjectLibrary.getObject(tab)
348
349        # Update the model table widget
350        #item = QtWidgets.QTableWidgetItem(tab_name)
351        #item.setCheckState(QtCore.Qt.Checked)
352        #item.setFlags( QtCore.Qt.ItemIsSelectable |  QtCore.Qt.ItemIsEnabled )
353        pos = self.tblTabList.rowCount()
354        self.tblTabList.insertRow(pos)
355        item = self.uneditableItem(tab_name)
356        item.setFlags(item.flags() ^ QtCore.Qt.ItemIsUserCheckable)
357        item.setCheckState(QtCore.Qt.Checked)
358        self.tblTabList.setItem(pos, 0, item)
359        self.tblTabList.setItem(pos, 1, self.uneditableItem(model_name))
360        self.tblTabList.setItem(pos, 2, self.uneditableItem(model_filename))
361        # Moniker is editable, so no option change
362        item = QtWidgets.QTableWidgetItem(moniker)
363        # Disable signals so we don't get infinite call recursion
364        self.tblTabList.blockSignals(True)
365        self.tblTabList.setItem(pos, 3, item)
366        self.tblTabList.blockSignals(False)
367
368        # Check if any constraints present in tab
369        constraints = ObjectLibrary.getObject(tab).getConstraintsForModel()
370        if not constraints: 
371            return
372        self.tblConstraints.setEnabled(True)
373        for constraint in constraints:
374            # Create the text for widget item
375            label = moniker + ":"+ constraint[0] + " = " + constraint[1]
376
377            # Show the text in the constraint table
378            item = QtWidgets.QTableWidgetItem(label)
379            item.setCheckState(QtCore.Qt.Checked)
380            pos = self.tblConstraints.rowCount()
381            self.tblConstraints.insertRow(pos)
382            self.tblConstraints.setItem(pos, 0, item)
383            self.available_constraints[pos] = constraints
384
385    def initializeFitList(self):
386        """
387        Fill the list of model/data sets for fitting/constraining
388        """
389        # look at the object library to find all fit tabs
390        # Show the content of the current "model"
391        objects = ObjectLibrary.listObjects()
392
393        # Tab dict
394        # moniker -> (kernel_module, data)
395        self.available_tabs = {}
396        # Constraint dict
397        # moniker -> [constraints]
398        self.available_constraints = {}
399
400        # Reset the table widgets
401        self.tblTabList.setRowCount(0)
402        self.tblConstraints.setRowCount(0)
403
404        # Fit disabled
405        self.cmdFit.setEnabled(False)
406
407        if not objects:
408            return
409
410        tabs = [tab for tab in ObjectLibrary.listObjects() if self.isTabImportable(tab)]
411        for tab in tabs:
412            self.updateFitLine(tab)
413            self.updateSignalsFromTab(tab)
414            # We have at least 1 fit page, allow fitting
415            self.cmdFit.setEnabled(True)
416
417    def validateMoniker(self, new_moniker=None):
418        """
419        Check new_moniker for correctness.
420        It must be non-empty.
421        It must not be the same as other monikers.
422        """
423        if not new_moniker:
424            return False
425
426        for existing_moniker in self.available_tabs:
427            if existing_moniker == new_moniker and existing_moniker != self.current_cell:
428                return False
429
430        return True
431
432    def showMultiConstraint(self):
433        """
434        Invoke the complex constraint editor
435        """
436        pass
Note: See TracBrowser for help on using the repository browser.