source: sasview/src/sas/qtgui/Utilities/AddMultEditor.py @ 017b285

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 017b285 was 017b285, checked in by Piotr Rozyczko <rozyczko@…>, 4 years ago

Re-enable the Apply button on checkbox change.

  • Property mode set to 100644
File size: 10.4 KB
Line 
1"""
2Widget for simple add / multiply editor.
3"""
4# numpy methods required for the validator! Don't remove.
5# pylint: disable=unused-import,unused-wildcard-import,redefined-builtin
6from numpy import *
7
8from PyQt5 import QtCore
9from PyQt5 import QtGui
10from PyQt5 import QtWidgets
11import webbrowser
12
13import os
14import numpy as np
15import re
16import logging
17import sas.qtgui.Utilities.GuiUtils as GuiUtils
18from sasmodels.sasview_model import load_standard_models
19from sas.qtgui.Perspectives.Fitting import ModelUtilities
20
21# Local UI
22from sas.qtgui.Utilities.UI.AddMultEditorUI import Ui_AddMultEditorUI
23
24# Template for the output plugin file
25SUM_TEMPLATE = """
26from sasmodels.core import load_model_info
27from sasmodels.sasview_model import make_model_from_info
28
29model_info = load_model_info('{model1}{operator}{model2}')
30model_info.name = '{name}'
31model_info.description = '{desc_line}'
32Model = make_model_from_info(model_info)
33"""
34
35# Color of backgrounds to underline valid or invalid input
36BG_WHITE = "background-color: rgb(255, 255, 255);"
37BG_RED = "background-color: rgb(244, 170, 164);"
38
39
40class AddMultEditor(QtWidgets.QDialog, Ui_AddMultEditorUI):
41    """
42       Dialog for easy custom composite models.  Provides a Dialog panel
43       to choose two existing models (including pre-existing Plugin Models which
44       may themselves be composite models) as well as an operation on those models
45       (add or multiply) the resulting model will add a scale parameter and a
46       background parameter.
47       The user can also give a brief help for the model in the description box and
48       must provide a unique name which is verified before the new model is saved.
49    """
50    def __init__(self, parent=None):
51        super(AddMultEditor, self).__init__(parent._parent)
52
53        self.parent = parent
54
55        self.setupUi(self)
56
57        #  uncheck self.chkOverwrite
58        self.chkOverwrite.setChecked(False)
59        self.canOverwriteName = False
60
61        # Disabled Apply button until input of valid output plugin name
62        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
63
64        # Flag for correctness of resulting name
65        self.good_name = False
66
67        self.setupSignals()
68
69        self.list_models = self.readModels()
70
71        # Fill models' comboboxes
72        self.setupModels()
73
74        self.setFixedSize(self.minimumSizeHint())
75
76        # Name and directory for saving new plugin model
77        self.plugin_filename = None
78        self.plugin_dir = ModelUtilities.find_plugins_dir()
79
80        # Validators
81        rx = QtCore.QRegExp("^[A-Za-z0-9_]*$")
82        txt_validator = QtGui.QRegExpValidator(rx)
83        self.txtName.setValidator(txt_validator)
84
85    def setupModels(self):
86        """ Add list of models to 'Model1' and 'Model2' comboboxes """
87        # Load the model dict
88        self.cbModel1.addItems(self.list_models)
89        self.cbModel2.addItems(self.list_models)
90
91        # set the default initial value of Model1 and Model2
92        index_ini_model1 = self.cbModel1.findText('sphere', QtCore.Qt.MatchFixedString)
93
94        if index_ini_model1 >= 0:
95            self.cbModel1.setCurrentIndex(index_ini_model1)
96        else:
97            self.cbModel1.setCurrentIndex(0)
98
99        index_ini_model2 = self.cbModel2.findText('cylinder',
100                                                  QtCore.Qt.MatchFixedString)
101        if index_ini_model2 >= 0:
102            self.cbModel2.setCurrentIndex(index_ini_model2)
103        else:
104            self.cbModel2.setCurrentIndex(0)
105
106    def readModels(self):
107        """ Generate list of models """
108        models = load_standard_models()
109        models_dict = {}
110        for model in models:
111            models_dict[model.name] = model
112
113        return sorted([model_name for model_name in models_dict])
114
115    def setupSignals(self):
116        """ Signals from various elements """
117        # check existence of output filename when entering name
118        # or when overwriting not allowed
119        self.txtName.editingFinished.connect(self.onNameCheck)
120        self.chkOverwrite.stateChanged.connect(self.onOverwrite)
121
122        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).clicked.connect(self.onApply)
123        self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self.onHelp)
124        self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.close)
125
126        # change displayed equation when changing operator
127        self.cbOperator.currentIndexChanged.connect(self.onOperatorChange)
128
129    def onOverwrite(self):
130        """
131        Modify state on checkbox change
132        """
133        self.canOverwriteName = self.chkOverwrite.isChecked()
134        # State changed -> allow Apply
135        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(True)
136
137    def onNameCheck(self):
138        """
139        Check if proposed new model name does not already exists
140        (if the overwriting is not allowed).
141        If not an error message not show error message is displayed
142        """
143
144        # Get the function/file name
145        title = self.txtName.text().lstrip().rstrip()
146
147        filename = title + '.py'
148
149        if self.canOverwriteName:
150            # allow overwriting -> only valid name needs to be checked
151            # (done with validator in __init__ above)
152            self.good_name = True
153            self.txtName.setStyleSheet(BG_WHITE)
154            self.plugin_filename = os.path.join(self.plugin_dir, filename)
155        else:
156            # No overwriting -> check existence of filename
157            # Create list of existing model names for comparison
158            # fake existing regular model name list
159            models_list = [item + '.py' for item in self.list_models]
160            if filename in models_list:
161                self.good_name = False
162                self.txtName.setStyleSheet(BG_RED)
163                msg = "Plugin with specified name already exists.\n"
164                msg += "Please specify different filename or allow file overwrite."
165                logging.warning(msg)
166                QtWidgets.QMessageBox.critical(self, 'Plugin Error', msg)
167            else:
168                s_title = title
169                if len(title) > 20:
170                    s_title = title[0:19] + '...'
171                logging.info("Model function ({}) has been set!\n".
172                                format(str(s_title)))
173                self.good_name = True
174                self.txtName.setStyleSheet(BG_WHITE)
175                self.plugin_filename = os.path.join(self.plugin_dir, filename)
176
177        # Enable Apply push button only if valid name
178        self.buttonBox.button(
179            QtWidgets.QDialogButtonBox.Apply).setEnabled(self.good_name)
180
181    def onOperatorChange(self, index):
182        """ Respond to operator combo box changes """
183
184        self.lblEquation.setText("Plugin_model = scale_factor * "
185                                 "(model_1 {} model_2) + background".
186                                 format(self.cbOperator.currentText()))
187
188    def onApply(self):
189        """ Validity check, save model to file """
190
191        # Set the button enablement, so no double clicks can be made
192        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
193
194        # Check the name/overwrite combination again, in case we managed to confuse the UI
195        self.onNameCheck()
196        if not self.good_name:
197            return
198
199        self.write_new_model_to_file(self.plugin_filename,
200                                     self.cbModel1.currentText(),
201                                     self.cbModel2.currentText(),
202                                     self.cbOperator.currentText())
203
204        success = GuiUtils.checkModel(self.plugin_filename)
205
206        if not success:
207            return
208
209        # Update list of models in FittingWidget and AddMultEditor
210        self.parent.communicate.customModelDirectoryChanged.emit()
211        # Re-read the model list so the new model is included
212        self.list_models = self.readModels()
213        self.updateModels()
214
215        # Notify the user
216        title = self.txtName.text().lstrip().rstrip()
217        msg = "Custom model "+title + " successfully created."
218        self.parent.communicate.statusBarUpdateSignal.emit(msg)
219        logging.info(msg)
220
221
222    def write_new_model_to_file(self, fname, model1_name, model2_name, operator):
223        """ Write and Save file """
224
225        description = self.txtDescription.text().lstrip().rstrip()
226        if description.strip() != '':
227            # Sasmodels generates a description for us. If the user provides
228            # their own description, add a line to overwrite the sasmodels one
229            desc_line = description
230        else:
231            desc_line = "{} {} {}".format(model1_name,
232                                          operator,
233                                          model2_name)
234
235        name = os.path.splitext(os.path.basename(fname))[0]
236        output = SUM_TEMPLATE.format(name=name,
237                                     model1=model1_name,
238                                     model2=model2_name,
239                                     operator=operator,
240                                     desc_line=desc_line)
241
242        with open(fname, 'w') as out_f:
243            out_f.write(output)
244
245    def updateModels(self):
246        """ Update contents of comboboxes with new plugin models """
247
248        # Keep pointers to the current indices so we can show the comboboxes with
249        # original selection
250        model_1 = self.cbModel1.currentText()
251        model_2 = self.cbModel2.currentText()
252
253        self.cbModel1.blockSignals(True)
254        self.cbModel1.clear()
255        self.cbModel1.blockSignals(False)
256
257        self.cbModel2.blockSignals(True)
258        self.cbModel2.clear()
259        self.cbModel2.blockSignals(False)
260        # Retrieve the list of models
261        model_list = self.readModels()
262        # Populate the models comboboxes
263        self.cbModel1.addItems(model_list)
264        self.cbModel2.addItems(model_list)
265
266        # Scroll back to the user chosen models
267        self.cbModel1.setCurrentIndex(self.cbModel1.findText(model_1))
268        self.cbModel2.setCurrentIndex(self.cbModel2.findText(model_2))
269
270    def onHelp(self):
271        """ Display related help section """
272
273        try:
274            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
275                            "/user/sasgui/perspectives/fitting/fitting_help.html#sum-multi-p1-p2"
276            webbrowser.open('file://' + os.path.realpath(help_location))
277        except AttributeError:
278            # No manager defined - testing and standalone runs
279            pass
Note: See TracBrowser for help on using the repository browser.