Index: src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision 0109f2aa27af3031fd84d645607f81a645da0ff4)
+++ src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision 0109f2aa27af3031fd84d645607f81a645da0ff4)
@@ -49,5 +49,4 @@
 
 
-
 TAB_MAGNETISM = 4
 TAB_POLY = 3
@@ -61,5 +60,4 @@
 
 logger = logging.getLogger(__name__)
-
 
 class ToolTippedItemModel(QtGui.QStandardItemModel):
@@ -93,6 +91,6 @@
     fittingFinishedSignal = QtCore.pyqtSignal(tuple)
     batchFittingFinishedSignal = QtCore.pyqtSignal(tuple)
-    Calc1DFinishedSignal = QtCore.pyqtSignal(tuple)
-    Calc2DFinishedSignal = QtCore.pyqtSignal(tuple)
+    Calc1DFinishedSignal = QtCore.pyqtSignal(dict)
+    Calc2DFinishedSignal = QtCore.pyqtSignal(dict)
 
     def __init__(self, parent=None, data=None, tab_id=1):
@@ -251,4 +249,8 @@
         self.kernel_module_copy = None
 
+        # dictionaries of current params
+        self.poly_params = {}
+        self.magnet_params = {}
+
         # Page id for fitting
         # To keep with previous SasView values, use 200 as the start offset
@@ -267,4 +269,7 @@
         self.has_poly_error_column = False
         self.has_magnet_error_column = False
+
+        # If the widget generated theory item, save it
+        self.theory_item = None
 
         # signal communicator
@@ -390,5 +395,8 @@
         # Tag along functionality
         self.label.setText("Data loaded from: ")
-        self.lblFilename.setText(self.logic.data.filename)
+        if self.logic.data.filename:
+            self.lblFilename.setText(self.logic.data.filename)
+        else:
+            self.lblFilename.setText(self.logic.data.name)
         self.updateQRange()
         # Switch off Data2D control
@@ -522,5 +530,4 @@
 
         # Signals from separate tabs asking for replot
-        self.options_widget.plot_signal.connect(self.onOptionsUpdate)
         self.options_widget.plot_signal.connect(self.onOptionsUpdate)
 
@@ -964,9 +971,10 @@
         model = self.cbModel.currentText()
 
-        # empty combobox forced to be read
+        # Assure the control is active
+        if not self.cbModel.isEnabled():
+            return
+        # Empty combobox forced to be read
         if not model:
             return
-        # Reset structure factor
-        self.cbStructureFactor.setCurrentIndex(0)
 
         # Reset parameters to fit
@@ -975,5 +983,8 @@
         self.has_poly_error_column = False
 
-        self.respondToModelStructure(model=model, structure_factor=None)
+        structure = None
+        if self.cbStructureFactor.isEnabled():
+            structure = str(self.cbStructureFactor.currentText())
+        self.respondToModelStructure(model=model, structure_factor=structure)
 
     def onSelectBatchFilename(self, data_index):
@@ -1118,4 +1129,7 @@
             self.disableModelCombo()
             self.enableStructureCombo()
+            # set the index to 0
+            self.cbStructureFactor.setCurrentIndex(0)
+            self.model_parameters = None
             self._model_model.clear()
             return
@@ -1142,6 +1156,6 @@
         model_row = item.row()
         name_index = self._poly_model.index(model_row, 0)
-        parameter_name = str(name_index.data()).lower() # "distribution of sld" etc.
-        if "distribution of" in parameter_name:
+        parameter_name = str(name_index.data()) # "distribution of sld" etc.
+        if "istribution of" in parameter_name:
             # just the last word
             parameter_name = parameter_name.rsplit()[-1]
@@ -1188,5 +1202,7 @@
             # Update the sasmodel
             # PD[ratio] -> width, npts -> npts, nsigs -> nsigmas
-            self.kernel_module.setParam(parameter_name + '.' + delegate.columnDict()[model_column], value)
+            #self.kernel_module.setParam(parameter_name + '.' + delegate.columnDict()[model_column], value)
+            key = parameter_name + '.' + delegate.columnDict()[model_column]
+            self.poly_params[key] = value
 
             # Update plot
@@ -1197,5 +1213,7 @@
             row = self.getRowFromName(parameter_name)
             param_item = self._model_model.item(row)
+            self._model_model.blockSignals(True)
             param_item.child(0).child(0, model_column).setText(item.text())
+            self._model_model.blockSignals(False)
 
     def onMagnetModelChange(self, item):
@@ -1226,15 +1244,21 @@
             # Unparsable field
             return
-
-        property_index = self._magnet_model.headerData(1, model_column)-1 # Value, min, max, etc.
-
-        # Update the parameter value - note: this supports +/-inf as well
-        self.kernel_module.params[parameter_name] = value
-
-        # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
-        self.kernel_module.details[parameter_name][property_index] = value
-
-        # Force the chart update when actual parameters changed
-        if model_column == 1:
+        delegate = self.lstMagnetic.itemDelegate()
+
+        if model_column > 1:
+            if model_column == delegate.mag_min:
+                pos = 1
+            elif model_column == delegate.mag_max:
+                pos = 2
+            elif model_column == delegate.mag_unit:
+                pos = 0
+            else:
+                raise AttributeError("Wrong column in magnetism table.")
+            # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf]
+            self.kernel_module.details[parameter_name][pos] = value
+        else:
+            self.magnet_params[parameter_name] = value
+            #self.kernel_module.setParam(parameter_name) = value
+            # Force the chart update when actual parameters changed
             self.recalculatePlotData()
 
@@ -1497,7 +1521,9 @@
         # Data going in
         data = self.logic.data
-        model = self.kernel_module
+        model = copy.deepcopy(self.kernel_module)
         qmin = self.q_range_min
         qmax = self.q_range_max
+        # add polydisperse/magnet parameters if asked
+        self.updateKernelModelWithExtraParams(model)
 
         params_to_fit = self.main_params_to_fit
@@ -1509,9 +1535,4 @@
             raise ValueError('Fitting requires at least one parameter to optimize.')
 
-        # Potential smearing added
-        # Remember that smearing_min/max can be None ->
-        # deal with it until Python gets discriminated unions
-        self.addWeightingToData(data)
-
         # Get the constraints.
         constraints = self.getComplexConstraintsForModel()
@@ -1530,13 +1551,13 @@
             data = GuiUtils.dataFromItem(fit_index)
             # Potential weights added directly to data
-            self.addWeightingToData(data)
+            weighted_data = self.addWeightingToData(data)
             try:
-                fitter_single.set_model(model, fit_id, params_to_fit, data=data,
+                fitter_single.set_model(model, fit_id, params_to_fit, data=weighted_data,
                              constraints=constraints)
             except ValueError as ex:
                 raise ValueError("Setting model parameters failed with: %s" % ex)
 
-            qmin, qmax, _ = self.logic.computeRangeFromData(data)
-            fitter_single.set_data(data=data, id=fit_id, smearer=smearer, qmin=qmin,
+            qmin, qmax, _ = self.logic.computeRangeFromData(weighted_data)
+            fitter_single.set_data(data=weighted_data, id=fit_id, smearer=smearer, qmin=qmin,
                             qmax=qmax)
             fitter_single.select_problem_for_fit(id=fit_id, value=1)
@@ -1929,11 +1950,13 @@
         Adds weighting contribution to fitting data
         """
+        new_data = copy.deepcopy(data)
         # Send original data for weighting
         weight = FittingUtilities.getWeight(data=data, is2d=self.is2D, flag=self.weighting)
         if self.is2D:
-            data.err_data = weight
+            new_data.err_data = weight
         else:
-            data.dy = weight
-        pass
+            new_data.dy = weight
+
+        return new_data
 
     def updateQRange(self):
@@ -1970,14 +1993,17 @@
             self.addExtraShells()
 
-            if structure_factor not in (None, "None"):
-                # add S(Q)
+            # Allow the SF combobox visibility for the given sasmodel
+            self.enableStructureFactorControl(structure_factor)
+        
+            # Add S(Q)
+            if self.cbStructureFactor.isEnabled():
+                structure_factor = self.cbStructureFactor.currentText()
                 self.fromStructureFactorToQModel(structure_factor)
-            else:
-                # enable selection of S(Q)
-                self.enableStructureFactorControl(structure_factor)
 
             # Add polydispersity to the model
+            self.poly_params = {}
             self.setPolyModel()
             # Add magnetic parameters to the model
+            self.magnet_params = {}
             self.setMagneticModel()
 
@@ -2005,4 +2031,5 @@
         """
         name = model_name
+        kernel_module = None
         if self.cbCategory.currentText() == CATEGORY_CUSTOM:
             # custom kernel load requires full path
@@ -2010,9 +2037,16 @@
         try:
             kernel_module = generate.load_kernel_module(name)
-        except ModuleNotFoundError:
-            # maybe it's a recategorised custom model?
-            name = os.path.join(ModelUtilities.find_plugins_dir(), model_name+".py")
-            # If this rises, it's a valid problem.
-            kernel_module = generate.load_kernel_module(name)
+        except ModuleNotFoundError as ex:
+            pass
+
+        if kernel_module is None:
+            # mismatch between "name" attribute and actual filename.
+            curr_model = self.models[model_name]
+            name, _ = os.path.splitext(os.path.basename(curr_model.filename))
+            try:
+                kernel_module = generate.load_kernel_module(name)
+            except ModuleNotFoundError as ex:
+                logging.error("Can't find the model "+ str(ex))
+                return
 
         if hasattr(kernel_module, 'parameters'):
@@ -2059,4 +2093,7 @@
         Setting model parameters into QStandardItemModel based on selected _structure factor_
         """
+        if structure_factor is None or structure_factor=="None":
+            return
+
         s_kernel = self.models[structure_factor]()
         p_kernel = self.kernel_module
@@ -2257,6 +2294,6 @@
         name = self.nameFromData(fitted_data)
         # Notify the GUI manager so it can create the theory model in DataExplorer
-        new_item = GuiUtils.createModelItemWithPlot(fitted_data, name=name)
-        self.communicate.updateTheoryFromPerspectiveSignal.emit(new_item)
+        self.theory_item = GuiUtils.createModelItemWithPlot(fitted_data, name=name)
+        self.communicate.updateTheoryFromPerspectiveSignal.emit(self.theory_item)
 
     def nameFromData(self, fitted_data):
@@ -2281,4 +2318,21 @@
         return self.completed1D if isinstance(self.data, Data1D) else self.completed2D
 
+    def updateKernelModelWithExtraParams(self, model=None):
+        """
+        Updates kernel model 'model' with extra parameters from
+        the polydisp and magnetism tab, if the tabs are enabled
+        """
+        if model is None: return
+        if not hasattr(model, 'setParam'): return
+
+        # add polydisperse parameters if asked
+        if self.chkPolydispersity.isChecked():
+            for key, value in self.poly_params.items():
+                model.setParam(key, value)
+        # add magnetic params if asked
+        if self.chkMagnetism.isChecked():
+            for key, value in self.magnet_params.items():
+                model.setParam(key, value)
+
     def calculateQGridForModelExt(self, data=None, model=None, completefn=None, use_threads=True):
         """
@@ -2288,8 +2342,12 @@
             data = self.data
         if model is None:
-            model = self.kernel_module
+            model = copy.deepcopy(self.kernel_module)
+            self.updateKernelModelWithExtraParams(model)
+
         if completefn is None:
             completefn = self.methodCompleteForData()
         smearer = self.smearing_widget.smearer()
+        weight = FittingUtilities.getWeight(data=data, is2d=self.is2D, flag=self.weighting)
+
         # Awful API to a backend method.
         calc_thread = self.methodCalculateForData()(data=data,
@@ -2300,5 +2358,5 @@
                                                smearer=smearer,
                                                state=None,
-                                               weight=None,
+                                               weight=weight,
                                                fid=None,
                                                toggle_mode_on=False,
@@ -2348,6 +2406,13 @@
         residuals = self.calculateResiduals(fitted_data)
         self.model_data = fitted_data
-
-        new_plots = [fitted_data, residuals]
+        new_plots = [fitted_data]
+        if residuals is not None:
+            new_plots.append(residuals)
+
+        if self.data_is_loaded:
+            GuiUtils.deleteRedundantPlots(self.all_data[self.data_index], new_plots)
+        else:
+            # delete theory items for the model, in order to get rid of any redundant items, e.g. beta(Q), S_eff(Q)
+            self.communicate.deleteIntermediateTheoryPlotsSignal.emit(self.kernel_module.id)
 
         # Create plots for intermediate product data
@@ -2364,27 +2429,6 @@
             new_plots.append(sq_data)
 
-        if self.data_is_loaded:
-            GuiUtils.deleteRedundantPlots(self.all_data[self.data_index], new_plots)
-
-        # TODO: merge rest of beta approx implementation in
-        # TODO: refactor
-        # deal with constrained radius_effective
-        # for row in range(self._model_model.rowCount()):
-        #     if self._model_model.item(row, 0).text() == "radius_effective_mode":
-        #         if GuiUtils.toDouble(self._model_model.item(row, 1).text()) == 0:
-        #             return
-        # radius_effective = intermediate_ER()
-        # if radius_effective:
-        #     for row in range(self._model_model.rowCount()):
-        #         if self._model_model.item(row, 0).text() == "radius_effective":
-        #             self._model_model.item(row, 1).setText(str(radius_effective))
-        #             break
-
         for plot in new_plots:
-            if hasattr(plot, "id") and "esidual" in plot.id:
-                # TODO: fix updates to residuals plot
-                pass
-            elif plot is not None:
-                self.communicate.plotUpdateSignal.emit([plot])
+            self.communicate.plotUpdateSignal.emit([plot])
 
     def complete2D(self, return_data):
@@ -2393,6 +2437,13 @@
         """
         fitted_data = self.logic.new2DPlot(return_data)
-        self.calculateResiduals(fitted_data)
+        residuals = self.calculateResiduals(fitted_data)
         self.model_data = fitted_data
+        new_plots = [fitted_data]
+        if residuals is not None:
+            new_plots.append(residuals)
+
+        # Update/generate plots
+        for plot in new_plots:
+            self.communicate.plotUpdateSignal.emit([plot])
 
     def calculateResiduals(self, fitted_data):
@@ -2404,20 +2455,18 @@
 
         # Modify fitted_data with weighting
-        self.addWeightingToData(fitted_data)
-
-        self.createNewIndex(fitted_data)
+        weighted_data = self.addWeightingToData(fitted_data)
+
+        self.createNewIndex(weighted_data)
         # Calculate difference between return_data and logic.data
-        self.chi2 = FittingUtilities.calculateChi2(fitted_data, self.logic.data)
+        self.chi2 = FittingUtilities.calculateChi2(weighted_data, self.logic.data)
         # Update the control
         chi2_repr = "---" if self.chi2 is None else GuiUtils.formatNumber(self.chi2, high=True)
         self.lblChi2Value.setText(chi2_repr)
 
-        # self.communicate.plotUpdateSignal.emit([fitted_data])
-
         # Plot residuals if actual data
         if not self.data_is_loaded:
             return
 
-        residuals_plot = FittingUtilities.plotResiduals(self.data, fitted_data)
+        residuals_plot = FittingUtilities.plotResiduals(self.data, weighted_data)
         residuals_plot.id = "Residual " + residuals_plot.id
         self.createNewIndex(residuals_plot)
@@ -2488,6 +2537,11 @@
         self._poly_model.clear()
 
+        parameters = self.model_parameters.form_volume_parameters
+        if self.is2D:
+            parameters += self.model_parameters.orientation_parameters
+
         [self.setPolyModelParameters(i, param) for i, param in \
-            enumerate(self.model_parameters.form_volume_parameters) if param.polydisperse]
+            enumerate(parameters) if param.polydisperse]
+
         FittingUtilities.addPolyHeadersToModel(self._poly_model)
 
@@ -2522,4 +2576,9 @@
         _, min, max = self.kernel_module.details[param_name]
 
+        # Update local param dict
+        self.poly_params[param_name + '.width'] = width
+        self.poly_params[param_name + '.npts'] = npts
+        self.poly_params[param_name + '.nsigmas'] = nsigs
+
         # Construct a row with polydisp. related variable.
         # This will get added to the polydisp. model
@@ -2571,8 +2630,11 @@
             if not self.isCheckable(row):
                 return
+            self._model_model.blockSignals(True)
             param_name = str(self._model_model.item(row, 0).text())
+            self._model_model.blockSignals(False)
             if param_name !=  param.name:
                 return
             # Modify the param value
+            self._model_model.blockSignals(True)
             if self.has_error_column:
                 # err column changes the indexing
@@ -2580,4 +2642,5 @@
             else:
                 self._model_model.item(row, 0).child(0).child(0,4).setText(combo_string)
+            self._model_model.blockSignals(False)
 
         if combo_string == 'array':
@@ -2698,4 +2761,6 @@
                         param.units]
 
+        self.magnet_params[param.name] = param.default
+
         FittingUtilities.addCheckedListToModel(model, checked_list)
 
@@ -2876,5 +2941,4 @@
         # TODO: add polidyspersity and magnetism
 
-
     def updateUndo(self):
         """
@@ -2913,4 +2977,6 @@
         if self.all_data:
             index = self.all_data[self.data_index]
+        else:
+            index = self.theory_item
         report_logic = ReportPageLogic(self,
                                        kernel_module=self.kernel_module,
@@ -3036,4 +3102,6 @@
         # first - regular params
         param_list = []
+
+        param_list.append(['model_name', str(self.cbModel.currentText())])
         def gatherParams(row):
             """
@@ -3122,5 +3190,12 @@
         if lines[0] != 'sasview_parameter_values':
             return False
-        for line in lines[1:-1]:
+
+        model = lines[1].split(',')
+
+        if model[0] != 'model_name':
+            return False
+
+        context['model_name'] = [model[1]]
+        for line in lines[2:-1]:
             if len(line) != 0:
                 item = line.split(',')
@@ -3148,4 +3223,16 @@
                     except IndexError:
                         pass
+
+        if str(self.cbModel.currentText()) != str(context['model_name'][0]):
+            msg = QtWidgets.QMessageBox()
+            msg.setIcon(QtWidgets.QMessageBox.Information)
+            msg.setText("The model in the clipboard is not the same as the currently loaded model. \
+                         Not all parameters saved may paste correctly.")
+            msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
+            result = msg.exec_()
+            if result == QtWidgets.QMessageBox.Ok:
+                pass
+            else:
+                return
 
         self.updateFullModel(context)
@@ -3186,4 +3273,6 @@
             param_repr = GuiUtils.formatNumber(param_dict[param_name][3+ioffset], high=True)
             self._model_model.item(row, 3+ioffset).setText(param_repr)
+            self.setFocus()
+
 
         # block signals temporarily, so we don't end up
@@ -3192,4 +3281,5 @@
         self.iterateOverModel(updateFittedValues)
         self._model_model.blockSignals(False)
+
 
     def updateFullPolyModel(self, param_dict):
@@ -3236,4 +3326,5 @@
             param_repr = GuiUtils.formatNumber(param_dict[param_name][5+ioffset], high=True)
             self._poly_model.item(row, 5+ioffset).setText(param_repr)
+            self.setFocus()
 
         # block signals temporarily, so we don't end up
@@ -3243,3 +3334,2 @@
         self._poly_model.blockSignals(False)
 
-
