source: sasview/src/sas/qtgui/Utilities/AddMultEditor.py @ 27689dc

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 27689dc was 3b8cc00, checked in by Piotr Rozyczko <rozyczko@…>, 6 years ago

Code review changes

  • Property mode set to 100644
File size: 10.3 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
135    def onNameCheck(self):
136        """
137        Check if proposed new model name does not already exists
138        (if the overwriting is not allowed).
139        If not an error message not show error message is displayed
140        """
141
142        # Get the function/file name
143        title = self.txtName.text().lstrip().rstrip()
144
145        filename = title + '.py'
146
147        if self.canOverwriteName:
148            # allow overwriting -> only valid name needs to be checked
149            # (done with validator in __init__ above)
150            self.good_name = True
151            self.txtName.setStyleSheet(BG_WHITE)
152            self.plugin_filename = os.path.join(self.plugin_dir, filename)
153        else:
154            # No overwriting -> check existence of filename
155            # Create list of existing model names for comparison
156            # fake existing regular model name list
157            models_list = [item + '.py' for item in self.list_models]
158            if filename in models_list:
159                self.good_name = False
160                self.txtName.setStyleSheet(BG_RED)
161                msg = "Plugin with specified name already exists.\n"
162                msg += "Please specify different filename or allow file overwrite."
163                logging.warning(msg)
164                QtWidgets.QMessageBox.critical(self, 'Plugin Error', msg)
165            else:
166                s_title = title
167                if len(title) > 20:
168                    s_title = title[0:19] + '...'
169                logging.info("Model function ({}) has been set!\n".
170                                format(str(s_title)))
171                self.good_name = True
172                self.txtName.setStyleSheet(BG_WHITE)
173                self.plugin_filename = os.path.join(self.plugin_dir, filename)
174
175        # Enable Apply push button only if valid name
176        self.buttonBox.button(
177            QtWidgets.QDialogButtonBox.Apply).setEnabled(self.good_name)
178
179    def onOperatorChange(self, index):
180        """ Respond to operator combo box changes """
181
182        self.lblEquation.setText("Plugin_model = scale_factor * "
183                                 "(model_1 {} model_2) + background".
184                                 format(self.cbOperator.currentText()))
185
186    def onApply(self):
187        """ Validity check, save model to file """
188
189        # Set the button enablement, so no double clicks can be made
190        self.buttonBox.button(QtWidgets.QDialogButtonBox.Apply).setEnabled(False)
191
192        # Check the name/overwrite combination again, in case we managed to confuse the UI
193        self.onNameCheck()
194        if not self.good_name:
195            return
196
197        self.write_new_model_to_file(self.plugin_filename,
198                                     self.cbModel1.currentText(),
199                                     self.cbModel2.currentText(),
200                                     self.cbOperator.currentText())
201
202        success = GuiUtils.checkModel(self.plugin_filename)
203
204        if not success:
205            return
206
207        # Update list of models in FittingWidget and AddMultEditor
208        self.parent.communicate.customModelDirectoryChanged.emit()
209        # Re-read the model list so the new model is included
210        self.list_models = self.readModels()
211        self.updateModels()
212
213        # Notify the user
214        title = self.txtName.text().lstrip().rstrip()
215        msg = "Custom model "+title + " successfully created."
216        self.parent.communicate.statusBarUpdateSignal.emit(msg)
217        logging.info(msg)
218
219
220    def write_new_model_to_file(self, fname, model1_name, model2_name, operator):
221        """ Write and Save file """
222
223        description = self.txtDescription.text().lstrip().rstrip()
224        if description.strip() != '':
225            # Sasmodels generates a description for us. If the user provides
226            # their own description, add a line to overwrite the sasmodels one
227            desc_line = description
228        else:
229            desc_line = "{} {} {}".format(model1_name,
230                                          operator,
231                                          model2_name)
232
233        name = os.path.splitext(os.path.basename(fname))[0]
234        output = SUM_TEMPLATE.format(name=name,
235                                     model1=model1_name,
236                                     model2=model2_name,
237                                     operator=operator,
238                                     desc_line=desc_line)
239
240        with open(fname, 'w') as out_f:
241            out_f.write(output)
242
243    def updateModels(self):
244        """ Update contents of comboboxes with new plugin models """
245
246        # Keep pointers to the current indices so we can show the comboboxes with
247        # original selection
248        model_1 = self.cbModel1.currentText()
249        model_2 = self.cbModel2.currentText()
250
251        self.cbModel1.blockSignals(True)
252        self.cbModel1.clear()
253        self.cbModel1.blockSignals(False)
254
255        self.cbModel2.blockSignals(True)
256        self.cbModel2.clear()
257        self.cbModel2.blockSignals(False)
258        # Retrieve the list of models
259        model_list = self.readModels()
260        # Populate the models comboboxes
261        self.cbModel1.addItems(model_list)
262        self.cbModel2.addItems(model_list)
263
264        # Scroll back to the user chosen models
265        self.cbModel1.setCurrentIndex(self.cbModel1.findText(model_1))
266        self.cbModel2.setCurrentIndex(self.cbModel2.findText(model_2))
267
268    def onHelp(self):
269        """ Display related help section """
270
271        try:
272            help_location = GuiUtils.HELP_DIRECTORY_LOCATION + \
273                            "/user/sasgui/perspectives/fitting/fitting_help.html#sum-multi-p1-p2"
274            webbrowser.open('file://' + os.path.realpath(help_location))
275        except AttributeError:
276            # No manager defined - testing and standalone runs
277            pass
Note: See TracBrowser for help on using the repository browser.