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

Last change on this file since 6c7ebb88 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
RevLine 
[7fd20fc]1"""
2Widget for parameter constraints.
3"""
[e147ce2]4import os
[5e66738]5import webbrowser
6
7# numpy methods required for the validator! Don't remove.
8# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
[0595bb7]9from numpy import *
10
[7fd20fc]11from PyQt5 import QtWidgets
[33c0561]12from PyQt5 import QtCore
[7fd20fc]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):
[5e66738]20    """
21    Logic class for interacting with MultiConstrainedUI view
22    """
[baeac95]23    def __init__(self, parent=None, params=None, constraint=None):
[5e66738]24        """
25        parent: ConstraintWidget object
26        params: tuple of strings describing model parameters
27        """
[cf9f39e]28        super(MultiConstraint, self).__init__(parent)
[7fd20fc]29
30        self.setupUi(self)
31        self.setModal(True)
[33c0561]32
33        # disable the context help icon
34        windowFlags = self.windowFlags()
35        self.setWindowFlags(windowFlags & ~QtCore.Qt.WindowContextHelpButtonHint)
36
[7fd20fc]37        self.params = params
[5e66738]38        self.parent = parent
[09e0c32]39        # Text of the constraint
[baeac95]40        self.function = None
[09e0c32]41        # Should this constraint be validated?
42        self.validate = True
[baeac95]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
[09e0c32]50            # Passed constraint may be too complex for simple validation
51            self.validate = constraint.validate
[baeac95]52        else:
53            self.model_name = self.params[1]
[7fd20fc]54
55        self.setupLabels()
56        self.setupTooltip()
57
[0595bb7]58        # Set param text control to the second parameter passed
[baeac95]59        if self.input_constraint is None:
60            self.txtConstraint.setText(self.params[1])
61        else:
62            self.txtConstraint.setText(self.function)
[7fd20fc]63        self.cmdOK.clicked.connect(self.accept)
[d3c0b95]64        self.cmdHelp.clicked.connect(self.onHelp)
[7fd20fc]65        self.cmdRevert.clicked.connect(self.revert)
[0595bb7]66        self.txtConstraint.editingFinished.connect(self.validateFormula)
[7fd20fc]67
[14ec91c5]68        # Default focus is on OK
69        self.cmdOK.setFocus()
70
[7fd20fc]71    def revert(self):
72        """
[5e66738]73        Swap parameters in the view
[7fd20fc]74        """
[eae226b]75        # Switch parameters
[7fd20fc]76        self.params[1], self.params[0] = self.params[0], self.params[1]
[baeac95]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])
[eae226b]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
[7fd20fc]84        self.setupLabels()
85        self.setupTooltip()
86
87    def setupLabels(self):
88        """
89        Setup labels based on current parameters
90        """
[baeac95]91        l1 = str(self.params[0])
92        l2 = str(self.params[1])
[7fd20fc]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
[0595bb7]105    def validateFormula(self):
106        """
107        Add visual cues when formula is incorrect
108        """
[09e0c32]109        # Don't validate if requested
110        if not self.validate: return
111
[0595bb7]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
[baeac95]129        param_str = self.model_name
[0595bb7]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)
[eae226b]137        if parameter_string_start < 0:
[0595bb7]138            return False
139        parameter_string_end = parameter_string_start + len(param_str)
140
[eae226b]141        # 3. parameter name should be a separate word, but can have "()[]*+-/ " around
[3b3b40b]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
[0595bb7]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
[d3c0b95]164    def onHelp(self):
165        """
166        Display related help section
167        """
168        try:
[e147ce2]169            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
[aed0532]170            "/user/qtgui/Perspectives/Fitting/fitting_help.html#simultaneous-fits-with-constraints"
[e147ce2]171            webbrowser.open('file://' + os.path.realpath(help_location))
[d3c0b95]172        except AttributeError:
173            # No manager defined - testing and standalone runs
174            pass
[0595bb7]175
[7fd20fc]176
Note: See TracBrowser for help on using the repository browser.