source: sasview/src/sas/qtgui/Perspectives/Fitting/ComplexConstraint.py @ 2d466e4

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

Started work on the complex constraint widget SASVIEW-853

  • Property mode set to 100755
File size: 6.7 KB
Line 
1"""
2Widget for multi-model constraints.
3"""
4import os
5from numpy import *
6
7from PyQt5 import QtCore
8from PyQt5 import QtGui
9from PyQt5 import QtWidgets
10import webbrowser
11
12import sas.qtgui.Utilities.GuiUtils as GuiUtils
13ALLOWED_OPERATORS = ['=','<','>','>=','<=']
14
15# Local UI
16from sas.qtgui.Perspectives.Fitting.UI.ComplexConstraintUI import Ui_ComplexConstraintUI
17
18class ComplexConstraint(QtWidgets.QDialog, Ui_ComplexConstraintUI):
19    def __init__(self, parent=None, tabs=None):
20        super(ComplexConstraint, self).__init__()
21
22        self.setupUi(self)
23        #self.setFixedSize(self.minimumSizeHint())
24        self.setModal(True)
25
26        # Useful globals
27        self.tabs = tabs
28        self.params = None
29        self.tab_names = None
30        self.operator = '='
31
32        self.setupData()
33        self.setupWidgets()
34        self.setupSignals()
35        #self.setupTooltip()
36
37        self.setFixedSize(self.minimumSizeHint())
38
39    def setupData(self):
40        """
41        Digs into self.tabs and pulls out relevant info
42        """
43        self.tab_names = [tab.kernel_module.name for tab in self.tabs]
44        self.params = [tab.getParamNames() for tab in self.tabs]
45        pass
46
47    def setupSignals(self):
48        """
49        Signals from various elements
50        """
51        self.cmdOK.clicked.connect(self.accept)
52        self.cmdHelp.clicked.connect(self.onHelp)
53        self.cmdRevert.clicked.connect(self.onRevert)
54        #self.txtConstraint.editingFinished.connect(self.validateFormula)
55
56        self.cbParam1.currentIndexChanged.connect(self.onParamIndexChange)
57        self.cbParam2.currentIndexChanged.connect(self.onParamIndexChange)
58        self.cbOperator.currentIndexChanged.connect(self.onOperatorChange)
59
60    def setupWidgets(self):
61        """
62        Setup widgets based on current parameters
63        """
64        self.txtName1.setText(self.tab_names[0])
65        self.txtName2.setText(self.tab_names[1])
66
67        self.cbParam1.clear()
68        self.cbParam1.addItems(self.params[0])
69        self.cbParam2.clear()
70        self.cbParam2.addItems(self.params[1])
71
72        self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText())
73
74        self.cbOperator.clear()
75        self.cbOperator.addItems(ALLOWED_OPERATORS)
76        self.txtOperator.setText(self.cbOperator.currentText())
77
78        self.txtConstraint.setText(self.tab_names[1]+"."+self.cbParam2.currentText())
79
80    def setupTooltip(self):
81        """
82        Tooltip for txtConstraint
83        """
84        tooltip = "E.g.\n%s = 2.0 * (%s)\n" %(self.params[0], self.params[1])
85        tooltip += "%s = sqrt(%s) + 5"%(self.params[0], self.params[1])
86        self.txtConstraint.setToolTip(tooltip)
87
88    def onParamIndexChange(self, index):
89        """
90        Respond to parameter combo box changes
91        """
92        # Find out the signal source
93        source = self.sender().objectName()
94        if source == "cbParam1":
95            self.txtParam.setText(self.tab_names[0] + ":" + self.cbParam1.currentText())
96        else:
97            self.txtConstraint.setText(self.cbParam2.currentText())
98        pass
99
100    def onOperatorChange(self, index):
101        """
102        Respond to operator combo box changes
103        """
104        self.txtOperator.setText(self.cbOperator.currentText())
105
106    def onRevert(self):
107        """
108        switch M1 <-> M2
109        """
110        # Switch parameters
111        self.params[1], self.params[0] = self.params[0], self.params[1]
112        self.tab_names[1], self.tab_names[0] = self.tab_names[0], self.tab_names[1]
113        # Try to swap parameter names in the line edit
114        current_text = self.txtConstraint.text()
115        new_text = current_text.replace(self.cbParam1.currentText(), self.cbParam2.currentText())
116        self.txtConstraint.setText(new_text)
117        # Update labels and tooltips
118        index1 = self.cbParam1.currentIndex()
119        index2 = self.cbParam2.currentIndex()
120        indexOp = self.cbOperator.currentIndex()
121        self.setupWidgets()
122
123        # Original indices
124        self.cbParam1.setCurrentIndex(index2)
125        self.cbParam2.setCurrentIndex(index1)
126        self.cbOperator.setCurrentIndex(indexOp)
127        #self.setupTooltip()
128
129    def validateFormula(self):
130        """
131        Add visual cues when formula is incorrect
132        """
133        formula_is_valid = False
134        formula_is_valid = self.validateConstraint(self.txtConstraint.text())
135        if not formula_is_valid:
136            self.cmdOK.setEnabled(False)
137            self.txtConstraint.setStyleSheet("QLineEdit {background-color: red;}")
138        else:
139            self.cmdOK.setEnabled(True)
140            self.txtConstraint.setStyleSheet("QLineEdit {background-color: white;}")
141
142    def validateConstraint(self, constraint_text):
143        """
144        Ensure the constraint has proper form
145        """
146        # 0. none or empty
147        if not constraint_text or not isinstance(constraint_text, str):
148            return False
149
150        param_str = str(self.params[1])
151        constraint_text = constraint_text.strip()
152
153        # 1. just the parameter
154        if param_str == constraint_text:
155            return True
156
157        # 2. ensure the text contains parameter name
158        parameter_string_start = constraint_text.find(param_str)
159        if parameter_string_start < 0:
160            return False
161        parameter_string_end = parameter_string_start + len(param_str)
162
163        # 3. parameter name should be a separate word, but can have "()[]*+-/ " around
164        valid_neighbours = "()[]*+-/ "
165        has_only_parameter = False
166        start_loc = parameter_string_start -1
167        end_loc = parameter_string_end
168        if not any([constraint_text[start_loc] == char for char in valid_neighbours]):
169            return False
170        if end_loc < len(constraint_text):
171            if not any([constraint_text[end_loc] == char for char in valid_neighbours]):
172                return False
173
174        # 4. replace parameter name with "1" and try to evaluate the expression
175        try:
176            expression_to_evaluate = constraint_text.replace(param_str, "1.0")
177            eval(expression_to_evaluate)
178        except Exception:
179            # Too many cases to cover individually, just a blanket
180            # Exception should be sufficient
181            # Note that in current numpy things like sqrt(-1) don't
182            # raise but just return warnings
183            return False
184
185        return True
186
187    def onHelp(self):
188        """
189        Display related help section
190        """
191        try:
192            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
193            "/user/sasgui/perspectives/fitting/fitting_help.html#simultaneous-fits-with-constraints"
194            webbrowser.open('file://' + os.path.realpath(help_location))
195        except AttributeError:
196            # No manager defined - testing and standalone runs
197            pass
198
199
200
Note: See TracBrowser for help on using the repository browser.