source: sasview/src/sas/qtgui/Perspectives/Fitting/MultiConstraint.py @ d32a594

Last change on this file since d32a594 was cf9f39e, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 6 years ago

Set the parent correctly for constraint widgets so window icons show up.
Return empty list, so reporting is possible.

  • Property mode set to 100644
File size: 6.0 KB
Line 
1"""
2Widget for parameter constraints.
3"""
4import os
5import webbrowser
6
7# numpy methods required for the validator! Don't remove.
8# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
9from numpy import *
10
11from PyQt5 import QtWidgets
12from PyQt5 import QtCore
13
14import sas.qtgui.Utilities.GuiUtils as GuiUtils
15
16# Local UI
17from sas.qtgui.Perspectives.Fitting.UI.MultiConstraintUI import Ui_MultiConstraintUI
18
19class MultiConstraint(QtWidgets.QDialog, Ui_MultiConstraintUI):
20    """
21    Logic class for interacting with MultiConstrainedUI view
22    """
23    def __init__(self, parent=None, params=None, constraint=None):
24        """
25        parent: ConstraintWidget object
26        params: tuple of strings describing model parameters
27        """
28        super(MultiConstraint, self).__init__(parent)
29
30        self.setupUi(self)
31        self.setModal(True)
32
33        # disable the context help icon
34        windowFlags = self.windowFlags()
35        self.setWindowFlags(windowFlags & ~QtCore.Qt.WindowContextHelpButtonHint)
36
37        self.params = params
38        self.parent = parent
39        # Text of the constraint
40        self.function = None
41        # Should this constraint be validated?
42        self.validate = True
43
44        self.input_constraint = constraint
45        if self.input_constraint is not None:
46            variable = constraint.value
47            self.function = constraint.func
48            self.params.append(variable)
49            self.model_name = constraint.value_ex
50            # Passed constraint may be too complex for simple validation
51            self.validate = constraint.validate
52        else:
53            self.model_name = self.params[1]
54
55        self.setupLabels()
56        self.setupTooltip()
57
58        # Set param text control to the second parameter passed
59        if self.input_constraint is None:
60            self.txtConstraint.setText(self.params[1])
61        else:
62            self.txtConstraint.setText(self.function)
63        self.cmdOK.clicked.connect(self.accept)
64        self.cmdHelp.clicked.connect(self.onHelp)
65        self.cmdRevert.clicked.connect(self.revert)
66        self.txtConstraint.editingFinished.connect(self.validateFormula)
67
68        # Default focus is on OK
69        self.cmdOK.setFocus()
70
71    def revert(self):
72        """
73        Swap parameters in the view
74        """
75        # Switch parameters
76        self.params[1], self.params[0] = self.params[0], self.params[1]
77        # change fully qualified param name (e.g. M1.sld -> M1.sld_solvent)
78        self.model_name = self.model_name.replace(self.params[0], self.params[1])
79        # Try to swap parameter names in the line edit
80        current_text = self.txtConstraint.text()
81        new_text = current_text.replace(self.params[0], self.params[1])
82        self.txtConstraint.setText(new_text)
83        # Update labels and tooltips
84        self.setupLabels()
85        self.setupTooltip()
86
87    def setupLabels(self):
88        """
89        Setup labels based on current parameters
90        """
91        l1 = str(self.params[0])
92        l2 = str(self.params[1])
93        self.txtParam1.setText(l1)
94        self.txtParam1_2.setText(l1)
95        self.txtParam2.setText(l2)
96
97    def setupTooltip(self):
98        """
99        Tooltip for txtConstraint
100        """
101        tooltip = "E.g.\n%s = 2.0 * (%s)\n" %(self.params[0], self.params[1])
102        tooltip += "%s = sqrt(%s) + 5"%(self.params[0], self.params[1])
103        self.txtConstraint.setToolTip(tooltip)
104
105    def validateFormula(self):
106        """
107        Add visual cues when formula is incorrect
108        """
109        # Don't validate if requested
110        if not self.validate: return
111
112        formula_is_valid = False
113        formula_is_valid = self.validateConstraint(self.txtConstraint.text())
114        if not formula_is_valid:
115            self.cmdOK.setEnabled(False)
116            self.txtConstraint.setStyleSheet("QLineEdit {background-color: red;}")
117        else:
118            self.cmdOK.setEnabled(True)
119            self.txtConstraint.setStyleSheet("QLineEdit {background-color: white;}")
120
121    def validateConstraint(self, constraint_text):
122        """
123        Ensure the constraint has proper form
124        """
125        # 0. none or empty
126        if not constraint_text or not isinstance(constraint_text, str):
127            return False
128
129        param_str = self.model_name
130
131        # 1. just the parameter
132        if param_str == constraint_text:
133            return True
134
135        # 2. ensure the text contains parameter name
136        parameter_string_start = constraint_text.find(param_str)
137        if parameter_string_start < 0:
138            return False
139        parameter_string_end = parameter_string_start + len(param_str)
140
141        # 3. parameter name should be a separate word, but can have "()[]*+-/ " around
142        #valid_neighbours = "()[]*+-/ "
143        #start_loc = parameter_string_start -1
144        #end_loc = parameter_string_end
145        #if not any([constraint_text[start_loc] == ch for ch in valid_neighbours]):
146        #    return False
147        #if end_loc < len(constraint_text):
148        #    if not any([constraint_text[end_loc] == ch for ch in valid_neighbours]):
149        #        return False
150
151        # 4. replace parameter name with "1" and try to evaluate the expression
152        try:
153            expression_to_evaluate = constraint_text.replace(param_str, "1.0")
154            eval(expression_to_evaluate)
155        except Exception:
156            # Too many cases to cover individually, just a blanket
157            # Exception should be sufficient
158            # Note that in current numpy things like sqrt(-1) don't
159            # raise but just return warnings
160            return False
161
162        return True
163
164    def onHelp(self):
165        """
166        Display related help section
167        """
168        try:
169            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
170            "/user/qtgui/Perspectives/Fitting/fitting_help.html#simultaneous-fits-with-constraints"
171            webbrowser.open('file://' + os.path.realpath(help_location))
172        except AttributeError:
173            # No manager defined - testing and standalone runs
174            pass
175
176
Note: See TracBrowser for help on using the repository browser.