Index: src/sas/qtgui/MainWindow/DataExplorer.py
===================================================================
--- src/sas/qtgui/MainWindow/DataExplorer.py	(revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
+++ src/sas/qtgui/MainWindow/DataExplorer.py	(revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
@@ -560,17 +560,10 @@
         # Now query the model item for available plots
         plots = GuiUtils.plotsFromFilename(filename, model)
-        ids_keys = list(self.active_plots.keys())
-        ids_vals = [val.data.id for val in self.active_plots.values()]
 
         new_plots = []
         for item, plot in plots.items():
-            plot_id = plot.id
-            if plot_id in ids_keys:
-                self.active_plots[plot_id].replacePlot(plot_id, plot)
-            elif plot_id in ids_vals:
-                list(self.active_plots.values())[ids_vals.index(plot_id)].replacePlot(plot_id, plot)
-            else:
+            if not self.updatePlot(plot):
                 # Don't plot intermediate results, e.g. P(Q), S(Q)
-                match = GuiUtils.theory_plot_ID_pattern.match(plot_id)
+                match = GuiUtils.theory_plot_ID_pattern.match(plot.id)
                 # 2nd match group contains the identifier for the intermediate result, if present (e.g. "[P(Q)]")
                 if match and match.groups()[1] != None:
@@ -706,9 +699,13 @@
                 self.active_plots[plot_set.id] = old_plot
 
-    def updatePlot(self, new_data):
-        """
-        Modify existing plot for immediate response
-        """
-        data = new_data[0]
+    def updatePlot(self, data):
+        """
+        Modify existing plot for immediate response and returns True.
+        Returns false, if the plot does not exist already.
+        """
+        try: # there might be a list or a single value being passed
+            data = data[0]
+        except TypeError:
+            pass
         assert type(data).__name__ in ['Data1D', 'Data2D']
 
@@ -719,6 +716,9 @@
         if data_id in ids_keys:
             self.active_plots[data_id].replacePlot(data_id, data)
+            return True
         elif data_id in ids_vals:
             list(self.active_plots.values())[ids_vals.index(data_id)].replacePlot(data_id, data)
+            return True
+        return False
 
     def chooseFiles(self):
Index: src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py	(revision 04f775db13543cbc91b64834af6068a163b77f8b)
+++ src/sas/qtgui/Perspectives/Fitting/AssociatedComboBox.py	(revision 04f775db13543cbc91b64834af6068a163b77f8b)
@@ -0,0 +1,38 @@
+from PyQt5 import QtGui, QtWidgets
+
+
+class AssociatedComboBox(QtWidgets.QComboBox):
+    """Just a regular combo box, but associated with a particular QStandardItem so that it can be used to display and
+    select an item's current text and a restricted list of other possible text.
+
+    When the combo box's current text is changed, the change is immediately reflected in the associated item; either
+    the text itself is set as the item's data, or the current index is used; see constructor."""
+    item = None  # type: QtGui.QStandardItem
+
+    def __init__(self, item, idx_as_value=False, parent=None):
+        # type: (QtGui.QStandardItem, bool, QtWidgets.QWidget) -> None
+        """
+        Initialize widget. idx_as_value indicates whether to use the combo box's current index (otherwise, current text)
+        as the associated item's new data.
+        """
+        super(AssociatedComboBox, self).__init__(parent)
+        self.item = item
+
+        if idx_as_value:
+            self.currentIndexChanged[int].connect(self._onIndexChanged)
+        else:
+            self.currentTextChanged.connect(self._onTextChanged)
+
+    def _onTextChanged(self, text):
+        # type: (str) -> None
+        """
+        Callback for combo box's currentTextChanged. Set associated item's data to the new text.
+        """
+        self.item.setText(text)
+
+    def _onIndexChanged(self, idx):
+        # type: (int) -> None
+        """
+        Callback for combo box's currentIndexChanged. Set associated item's data to the new index.
+        """
+        self.item.setText(str(idx))
Index: src/sas/qtgui/Perspectives/Fitting/FittingLogic.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingLogic.py	(revision b4d05bdc03c6c40fee47932a9a2d20921b1bbe4e)
+++ src/sas/qtgui/Perspectives/Fitting/FittingLogic.py	(revision dcabba7be660ff2e140942eb331ca846d1f2a399)
@@ -161,13 +161,7 @@
         Create a new 1D data instance based on fitting results
         """
-        # Unpack return data from Calc1D
-        x, y, page_id, state, weight,\
-        fid, toggle_mode_on, \
-        elapsed, index, model, \
-        data, update_chisqr, source, \
-        unsmeared_output, unsmeared_data, unsmeared_error, \
-        pq_values, sq_values = return_data
-
-        return self._create1DPlot(tab_id, x, y, model, data)
+
+        return self._create1DPlot(tab_id, return_data['x'], return_data['y'],
+                                  return_data['model'], return_data['data'])
 
     def new2DPlot(self, return_data):
@@ -175,7 +169,7 @@
         Create a new 2D data instance based on fitting results
         """
-        image, data, page_id, model, state, toggle_mode_on,\
-        elapsed, index, fid, qmin, qmax, weight, \
-        update_chisqr, source = return_data
+        image = return_data['image']
+        data = return_data['data']
+        model = return_data['model']
 
         np.nan_to_num(image)
@@ -183,6 +177,6 @@
         new_plot.name = model.name + '2d'
         new_plot.title = "Analytical model 2D "
-        new_plot.id = str(page_id) + " " + data.name
-        new_plot.group_id = str(page_id) + " Model2D"
+        new_plot.id = str(return_data['page_id']) + " " + data.name
+        new_plot.group_id = str(return_data['page_id']) + " Model2D"
         new_plot.detector = data.detector
         new_plot.source = data.source
@@ -218,19 +212,16 @@
         (pq_plot, sq_plot). If either are unavailable, the corresponding plot is None.
         """
-        # Unpack return data from Calc1D
-        x, y, page_id, state, weight, \
-        fid, toggle_mode_on, \
-        elapsed, index, model, \
-        data, update_chisqr, source, \
-        unsmeared_output, unsmeared_data, unsmeared_error, \
-        pq_values, sq_values = return_data
 
         pq_plot = None
         sq_plot = None
 
-        if pq_values is not None:
-            pq_plot = self._create1DPlot(tab_id, x, pq_values, model, data, component="P(Q)")
-        if sq_values is not None:
-            sq_plot = self._create1DPlot(tab_id, x, sq_values, model, data, component="S(Q)")
+        if return_data.get('pq_values', None) is not None:
+            pq_plot = self._create1DPlot(tab_id, return_data['x'],
+                    return_data['pq_values'], return_data['model'],
+                    return_data['data'], component="P(Q)")
+        if return_data.get('sq_values', None) is not None:
+            sq_plot = self._create1DPlot(tab_id, return_data['x'],
+                    return_data['sq_values'], return_data['model'],
+                    return_data['data'], component="S(Q)")
 
         return pq_plot, sq_plot
Index: src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py	(revision b764ae5882f084a8b74c7cf664276c13fb07c29e)
+++ src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py	(revision 70f445859abd127c3fa7cd9f6b8e2700e7bcb1a2)
@@ -8,4 +8,6 @@
 from sas.qtgui.Plotting.PlotterData import Data1D
 from sas.qtgui.Plotting.PlotterData import Data2D
+
+from sas.qtgui.Perspectives.Fitting.AssociatedComboBox import AssociatedComboBox
 
 model_header_captions = ['Parameter', 'Value', 'Min', 'Max', 'Units']
@@ -61,7 +63,37 @@
     return (param_name, param_length)
 
-def addParametersToModel(parameters, kernel_module, is2D):
-    """
-    Update local ModelModel with sasmodel parameters
+def createFixedChoiceComboBox(param, item_row):
+    """
+    Determines whether param is a fixed-choice parameter, modifies items in item_row appropriately and returns a combo
+    box containing the fixed choices. Returns None if param is not fixed-choice.
+    
+    item_row is a list of QStandardItem objects for insertion into the parameter table. 
+    """
+
+    # Determine whether this is a fixed-choice parameter. There are lots of conditionals, simply because the
+    # implementation is not yet concrete; there are several possible indicators that the parameter is fixed-choice.
+    # TODO: (when the sasmodels implementation is concrete, clean this up)
+    choices = None
+    if isinstance(param.choices, (list, tuple)) and len(param.choices) > 0:
+        # The choices property is concrete in sasmodels, probably will use this
+        choices = param.choices
+    elif isinstance(param.units, (list, tuple)):
+        choices = [str(x) for x in param.units]
+
+    cbox = None
+    if choices is not None:
+        # Use combo box for input, if it is fixed-choice
+        cbox = AssociatedComboBox(item_row[1], idx_as_value=True)
+        cbox.addItems(choices)
+        item_row[2].setEditable(False)
+        item_row[3].setEditable(False)
+
+    return cbox
+
+def addParametersToModel(parameters, kernel_module, is2D, model=None, view=None):
+    """
+    Update local ModelModel with sasmodel parameters.
+    Actually appends to model, if model and view params are not None.
+    Always returns list of lists of QStandardItems.
     """
     multishell_parameters = getIterParams(parameters)
@@ -72,19 +104,20 @@
     else:
         params = parameters.iq_parameters
-    item = []
+
+    rows = []
     for param in params:
         # don't include shell parameters
         if param.name == multishell_param_name:
             continue
+
         # Modify parameter name from <param>[n] to <param>1
         item_name = param.name
         if param in multishell_parameters:
             continue
-        #    item_name = replaceShellName(param.name, 1)
 
         item1 = QtGui.QStandardItem(item_name)
         item1.setCheckable(True)
         item1.setEditable(False)
-        # item_err = QtGui.QStandardItem()
+
         # check for polydisp params
         if param.polydisperse:
@@ -93,4 +126,5 @@
             item1_1 = QtGui.QStandardItem("Distribution")
             item1_1.setEditable(False)
+
             # Find param in volume_params
             for p in parameters.form_volume_parameters:
@@ -99,5 +133,4 @@
                 width = kernel_module.getParam(p.name+'.width')
                 ptype = kernel_module.getParam(p.name+'.type')
-
                 item1_2 = QtGui.QStandardItem(str(width))
                 item1_2.setEditable(False)
@@ -110,21 +143,36 @@
                 poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
                 break
+
             # Add the polydisp item as a child
             item1.appendRow([poly_item])
+
         # Param values
         item2 = QtGui.QStandardItem(str(param.default))
-        # TODO: the error column.
-        # Either add a proxy model or a custom view delegate
-        #item_err = QtGui.QStandardItem()
         item3 = QtGui.QStandardItem(str(param.limits[0]))
         item4 = QtGui.QStandardItem(str(param.limits[1]))
-        item5 = QtGui.QStandardItem(param.units)
+        item5 = QtGui.QStandardItem(str(param.units))
         item5.setEditable(False)
-        item.append([item1, item2, item3, item4, item5])
-    return item
-
-def addSimpleParametersToModel(parameters, is2D):
-    """
-    Update local ModelModel with sasmodel parameters
+
+        # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+        row = [item1, item2, item3, item4, item5]
+        cbox = createFixedChoiceComboBox(param, row)
+
+        # Append to the model and use the combobox, if required
+        if None not in (model, view):
+            model.appendRow(row)
+            if cbox:
+                view.setIndexWidget(item2.index(), cbox)
+        rows.append(row)
+
+    return rows
+
+def addSimpleParametersToModel(parameters, is2D, parameters_original=None, model=None, view=None):
+    """
+    Update local ModelModel with sasmodel parameters (non-dispersed, non-magnetic)
+    Actually appends to model, if model and view params are not None.
+    Always returns list of lists of QStandardItems.
+
+    parameters_original: list of parameters before any tagging on their IDs, e.g. for product model (so that those are
+    the display names; see below)
     """
     if is2D:
@@ -132,20 +180,46 @@
     else:
         params = parameters.iq_parameters
-    item = []
-    for param in params:
+
+    if parameters_original:
+        # 'parameters_original' contains the parameters as they are to be DISPLAYED, while 'parameters'
+        # contains the parameters as they were renamed; this is for handling name collisions in product model.
+        # The 'real name' of the parameter will be stored in the item's user data.
+        if is2D:
+            params_orig = [p for p in parameters_original.kernel_parameters if p.type != 'magnetic']
+        else:
+            params_orig = parameters_original.iq_parameters
+    else:
+        # no difference in names anyway
+        params_orig = params
+
+    rows = []
+    for param, param_orig in zip(params, params_orig):
         # Create the top level, checkable item
-        item_name = param.name
+        item_name = param_orig.name
         item1 = QtGui.QStandardItem(item_name)
+        item1.setData(param.name, QtCore.Qt.UserRole)
         item1.setCheckable(True)
         item1.setEditable(False)
+
         # Param values
         # TODO: add delegate for validation of cells
         item2 = QtGui.QStandardItem(str(param.default))
-        item4 = QtGui.QStandardItem(str(param.limits[0]))
-        item5 = QtGui.QStandardItem(str(param.limits[1]))
-        item6 = QtGui.QStandardItem(param.units)
-        item6.setEditable(False)
-        item.append([item1, item2, item4, item5, item6])
-    return item
+        item3 = QtGui.QStandardItem(str(param.limits[0]))
+        item4 = QtGui.QStandardItem(str(param.limits[1]))
+        item5 = QtGui.QStandardItem(str(param.units))
+        item5.setEditable(False)
+
+        # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+        row = [item1, item2, item3, item4, item5]
+        cbox = createFixedChoiceComboBox(param, row)
+
+        # Append to the model and use the combobox, if required
+        if None not in (model, view):
+            model.appendRow(row)
+            if cbox:
+                view.setIndexWidget(item2.index(), cbox)
+        rows.append(row)
+
+    return rows
 
 def markParameterDisabled(model, row):
@@ -182,4 +256,20 @@
     model.appendRow(item_list)
 
+def addHeadingRowToModel(model, name):
+    """adds a non-interactive top-level row to the model"""
+    header_row = [QtGui.QStandardItem() for i in range(5)]
+    header_row[0].setText(name)
+
+    font = header_row[0].font()
+    font.setBold(True)
+    header_row[0].setFont(font)
+
+    for item in header_row:
+        item.setEditable(False)
+        item.setCheckable(False)
+        item.setSelectable(False)
+
+    model.appendRow(header_row)
+
 def addHeadersToModel(model):
     """
@@ -227,10 +317,14 @@
     model.header_tooltips = copy.copy(poly_header_error_tooltips)
 
-def addShellsToModel(parameters, model, index):
-    """
-    Find out multishell parameters and update the model with the requested number of them
+def addShellsToModel(parameters, model, index, row_num=None, view=None):
+    """
+    Find out multishell parameters and update the model with the requested number of them.
+    Inserts them after the row at row_num, if not None; otherwise, appends to end.
+    If view param is not None, supports fixed-choice params.
+    Returns a list of lists of QStandardItem objects.
     """
     multishell_parameters = getIterParams(parameters)
 
+    rows = []
     for i in range(index):
         for par in multishell_parameters:
@@ -250,5 +344,5 @@
                     item1_3 = QtGui.QStandardItem(str(p.limits[0]))
                     item1_4 = QtGui.QStandardItem(str(p.limits[1]))
-                    item1_5 = QtGui.QStandardItem(p.units)
+                    item1_5 = QtGui.QStandardItem(str(p.units))
                     poly_item.appendRow([item1_1, item1_2, item1_3, item1_4, item1_5])
                     break
@@ -258,6 +352,25 @@
             item3 = QtGui.QStandardItem(str(par.limits[0]))
             item4 = QtGui.QStandardItem(str(par.limits[1]))
-            item5 = QtGui.QStandardItem(par.units)
-            model.appendRow([item1, item2, item3, item4, item5])
+            item5 = QtGui.QStandardItem(str(par.units))
+            item5.setEditable(False)
+
+            # Check if fixed-choice (returns combobox, if so, also makes some items uneditable)
+            row = [item1, item2, item3, item4, item5]
+            cbox = createFixedChoiceComboBox(par, row)
+
+            # Always add to the model
+            if row_num is None:
+                model.appendRow(row)
+            else:
+                model.insertRow(row_num, row)
+                row_num += 1
+
+            # Apply combobox if required
+            if None not in (view, cbox):
+                view.setIndexWidget(item2.index(), cbox)
+
+            rows.append(row)
+
+    return rows
 
 def calculateChi2(reference_data, current_data):
Index: src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
+++ src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision 339e22b9f5c08262b1d5b0b551ce467bcf83178c)
@@ -90,6 +90,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):
@@ -219,6 +219,7 @@
         # Utility variable to enable unselectable option in category combobox
         self._previous_category_index = 0
-        # Utility variable for multishell display
-        self._last_model_row = 0
+        # Utility variables for multishell display
+        self._n_shells_row = 0
+        self._num_shell_params = 0
         # Dictionary of {model name: model class} for the current category
         self.models = {}
@@ -247,4 +248,8 @@
         # copy of current kernel model
         self.kernel_module_copy = None
+
+        # dictionaries of current params
+        self.poly_params = {}
+        self.magnet_params = {}
 
         # Page id for fitting
@@ -672,5 +677,7 @@
         Return list of all parameters for the current model
         """
-        return [self._model_model.item(row).text() for row in range(self._model_model.rowCount())]
+        return [self._model_model.item(row).text()
+                for row in range(self._model_model.rowCount())
+                if self.isCheckable(row)]
 
     def modifyViewOnRow(self, row, font=None, brush=None):
@@ -700,4 +707,5 @@
         assert isinstance(constraint, Constraint)
         assert 0 <= row <= self._model_model.rowCount()
+        assert self.isCheckable(row)
 
         item = QtGui.QStandardItem()
@@ -720,4 +728,5 @@
         max_col = self.lstParams.itemDelegate().param_max
         for row in self.selectedParameters():
+            assert(self.isCheckable(row))
             param = self._model_model.item(row, 0).text()
             value = self._model_model.item(row, 1).text()
@@ -762,4 +771,6 @@
         max_col = self.lstParams.itemDelegate().param_max
         for row in range(self._model_model.rowCount()):
+            if not self.isCheckable(row):
+                continue
             if not self.rowHasConstraint(row):
                 continue
@@ -790,10 +801,12 @@
         For the given row, return its constraint, if any
         """
-        try:
+        if self.isCheckable(row):
             item = self._model_model.item(row, 1)
-            return item.child(0).data()
-        except AttributeError:
-            # return none when no constraints
-            return None
+            try:
+                return item.child(0).data()
+            except AttributeError:
+                # return none when no constraints
+                pass
+        return None
 
     def rowHasConstraint(self, row):
@@ -801,9 +814,10 @@
         Finds out if row of the main model has a constraint child
         """
-        item = self._model_model.item(row, 1)
-        if item.hasChildren():
-            c = item.child(0).data()
-            if isinstance(c, Constraint):
-                return True
+        if self.isCheckable(row):
+            item = self._model_model.item(row, 1)
+            if item.hasChildren():
+                c = item.child(0).data()
+                if isinstance(c, Constraint):
+                    return True
         return False
 
@@ -812,9 +826,10 @@
         Finds out if row of the main model has an active constraint child
         """
-        item = self._model_model.item(row, 1)
-        if item.hasChildren():
-            c = item.child(0).data()
-            if isinstance(c, Constraint) and c.active:
-                return True
+        if self.isCheckable(row):
+            item = self._model_model.item(row, 1)
+            if item.hasChildren():
+                c = item.child(0).data()
+                if isinstance(c, Constraint) and c.active:
+                    return True
         return False
 
@@ -823,9 +838,10 @@
         Finds out if row of the main model has an active, nontrivial constraint child
         """
-        item = self._model_model.item(row, 1)
-        if item.hasChildren():
-            c = item.child(0).data()
-            if isinstance(c, Constraint) and c.func and c.active:
-                return True
+        if self.isCheckable(row):
+            item = self._model_model.item(row, 1)
+            if item.hasChildren():
+                c = item.child(0).data()
+                if isinstance(c, Constraint) and c.func and c.active:
+                    return True
         return False
 
@@ -1186,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
@@ -1195,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):
@@ -1224,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()
 
@@ -1481,4 +1507,6 @@
         # update charts
         self.onPlot()
+        #self.recalculatePlotData()
+
 
         # Read only value - we can get away by just printing it here
@@ -1495,7 +1523,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
@@ -1561,9 +1591,10 @@
             # internal so can use closure for param_dict
             param_name = str(self._model_model.item(row, 0).text())
-            if param_name not in list(param_dict.keys()):
+            if not self.isCheckable(row) or param_name not in list(param_dict.keys()):
                 return
             # modify the param value
             param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
             self._model_model.item(row, 1).setText(param_repr)
+            self.kernel_module.setParam(param_name, param_dict[param_name][0])
             if self.has_error_column:
                 error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
@@ -1573,5 +1604,5 @@
             # Utility function for updateof polydispersity part of the main model
             param_name = str(self._model_model.item(row, 0).text())+'.width'
-            if param_name not in list(param_dict.keys()):
+            if not self.isCheckable(row) or param_name not in list(param_dict.keys()):
                 return
             # modify the param value
@@ -1607,8 +1638,4 @@
             poly_item.insertColumn(2, [QtGui.QStandardItem("")])
 
-        # block signals temporarily, so we don't end up
-        # updating charts with every single model change on the end of fitting
-        self._model_model.blockSignals(True)
-
         if not self.has_error_column:
             # create top-level error column
@@ -1617,9 +1644,5 @@
             self.iterateOverModel(createErrorColumn)
 
-            # we need to enable signals for this, otherwise the final column mysteriously disappears (don't ask, I don't
-            # know)
-            self._model_model.blockSignals(False)
             self._model_model.insertColumn(2, error_column)
-            self._model_model.blockSignals(True)
 
             FittingUtilities.addErrorHeadersToModel(self._model_model)
@@ -1630,8 +1653,10 @@
             self.has_error_column = True
 
+        # block signals temporarily, so we don't end up
+        # updating charts with every single model change on the end of fitting
+        self._model_model.itemChanged.disconnect()
         self.iterateOverModel(updateFittedValues)
         self.iterateOverModel(updatePolyValues)
-
-        self._model_model.blockSignals(False)
+        self._model_model.itemChanged.connect(self.onMainParamsChange)
 
         # Adjust the table cells width.
@@ -1668,8 +1693,8 @@
             param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
             self._poly_model.item(row_i, 1).setText(param_repr)
+            self.kernel_module.setParam(param_name, param_dict[param_name][0])
             if self.has_poly_error_column:
                 error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
                 self._poly_model.item(row_i, 2).setText(error_repr)
-
 
         def createErrorColumn(row_i):
@@ -1692,7 +1717,7 @@
         # block signals temporarily, so we don't end up
         # updating charts with every single model change on the end of fitting
-        self._poly_model.blockSignals(True)
+        self._poly_model.itemChanged.disconnect()
         self.iterateOverPolyModel(updateFittedValues)
-        self._poly_model.blockSignals(False)
+        self._poly_model.itemChanged.connect(self.onPolyModelChange)
 
         if self.has_poly_error_column:
@@ -1704,7 +1729,5 @@
 
         # switch off reponse to model change
-        self._poly_model.blockSignals(True)
         self._poly_model.insertColumn(2, error_column)
-        self._poly_model.blockSignals(False)
         FittingUtilities.addErrorPolyHeadersToModel(self._poly_model)
 
@@ -1739,4 +1762,5 @@
             param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True)
             self._magnet_model.item(row, 1).setText(param_repr)
+            self.kernel_module.setParam(param_name, param_dict[param_name][0])
             if self.has_magnet_error_column:
                 error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True)
@@ -1758,7 +1782,7 @@
         # block signals temporarily, so we don't end up
         # updating charts with every single model change on the end of fitting
-        self._magnet_model.blockSignals(True)
+        self._magnet_model.itemChanged.disconnect()
         self.iterateOverMagnetModel(updateFittedValues)
-        self._magnet_model.blockSignals(False)
+        self._magnet_model.itemChanged.connect(self.onMagnetModelChange)
 
         if self.has_magnet_error_column:
@@ -1770,7 +1794,5 @@
 
         # switch off reponse to model change
-        self._magnet_model.blockSignals(True)
         self._magnet_model.insertColumn(2, error_column)
-        self._magnet_model.blockSignals(False)
         FittingUtilities.addErrorHeadersToModel(self._magnet_model)
 
@@ -1784,6 +1806,6 @@
         self.cmdPlot.setText("Show Plot")
         # Force data recalculation so existing charts are updated
+        self.showPlot()
         self.recalculatePlotData()
-        self.showPlot()
 
     def onSmearingOptionsUpdate(self):
@@ -1950,31 +1972,33 @@
         # Crete/overwrite model items
         self._model_model.clear()
-
-        # First, add parameters from the main model
-        if model_name is not None:
+        self._poly_model.clear()
+        self._magnet_model.clear()
+
+        if model_name is None:
+            if structure_factor not in (None, "None"):
+                # S(Q) on its own, treat the same as a form factor
+                self.kernel_module = None
+                self.fromStructureFactorToQModel(structure_factor)
+            else:
+                # No models selected
+                return
+        else:
             self.fromModelToQModel(model_name)
-
-        # Then, add structure factor derived parameters
-        if structure_factor is not None and structure_factor != "None":
-            if model_name is None:
-                # Instantiate the current sasmodel for SF-only models
-                self.kernel_module = self.models[structure_factor]()
-            self.fromStructureFactorToQModel(structure_factor)
-        else:
+            self.addExtraShells()
+
             # 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)
 
-        # Then, add multishells
-        if model_name is not None:
-            # Multishell models need additional treatment
-            self.addExtraShells()
-
-        # Add polydispersity to the model
-        self.setPolyModel()
-        # Add magnetic parameters to the model
-        self.setMagneticModel()
+            # Add polydispersity to the model
+            self.poly_params = {}
+            self.setPolyModel()
+            # Add magnetic parameters to the model
+            self.magnet_params = {}
+            self.setMagneticModel()
 
         # Adjust the table cells width
@@ -2049,11 +2073,14 @@
         self.shell_names = self.shellNamesList()
 
+        # Add heading row
+        FittingUtilities.addHeadingRowToModel(self._model_model, model_name)
+
         # Update the QModel
-        new_rows = FittingUtilities.addParametersToModel(self.model_parameters, self.kernel_module, self.is2D)
-
-        for row in new_rows:
-            self._model_model.appendRow(row)
-        # Update the counter used for multishell display
-        self._last_model_row = self._model_model.rowCount()
+        FittingUtilities.addParametersToModel(
+                self.model_parameters,
+                self.kernel_module,
+                self.is2D,
+                self._model_model,
+                self.lstParams)
 
     def fromStructureFactorToQModel(self, structure_factor):
@@ -2063,22 +2090,48 @@
         if structure_factor is None or structure_factor=="None":
             return
-        structure_module = generate.load_kernel_module(structure_factor)
-        structure_parameters = modelinfo.make_parameter_table(getattr(structure_module, 'parameters', []))
-
-        structure_kernel = self.models[structure_factor]()
-        form_kernel = self.kernel_module
-
-        self.kernel_module = MultiplicationModel(form_kernel, structure_kernel)
-
-        new_rows = FittingUtilities.addSimpleParametersToModel(structure_parameters, self.is2D)
-        for row in new_rows:
-            self._model_model.appendRow(row)
-            # disable fitting of parameters not listed in self.kernel_module (probably radius_effective)
-            if row[0].text() not in self.kernel_module.params.keys():
-                row_num = self._model_model.rowCount() - 1
-                FittingUtilities.markParameterDisabled(self._model_model, row_num)
-
-        # Update the counter used for multishell display
-        self._last_model_row = self._model_model.rowCount()
+
+        if self.kernel_module is None:
+            # Structure factor is the only selected model; build it and show all its params
+            self.kernel_module = self.models[structure_factor]()
+            s_params = self.kernel_module._model_info.parameters
+            s_params_orig = s_params
+
+        else:
+            s_kernel = self.models[structure_factor]()
+            p_kernel = self.kernel_module
+
+            p_pars_len = len(p_kernel._model_info.parameters.kernel_parameters)
+            s_pars_len = len(s_kernel._model_info.parameters.kernel_parameters)
+
+            self.kernel_module = MultiplicationModel(p_kernel, s_kernel)
+            all_params = self.kernel_module._model_info.parameters.kernel_parameters
+            all_param_names = [param.name for param in all_params]
+
+            # S(Q) params from the product model are not necessarily the same as those from the S(Q) model; any
+            # conflicting names with P(Q) params will cause a rename
+
+            if "radius_effective_mode" in all_param_names:
+                # Show all parameters
+                s_params = modelinfo.ParameterTable(all_params[p_pars_len:p_pars_len+s_pars_len])
+                s_params_orig = modelinfo.ParameterTable(s_kernel._model_info.parameters.kernel_parameters)
+            else:
+                # Ensure radius_effective is not displayed
+                s_params_orig = modelinfo.ParameterTable(s_kernel._model_info.parameters.kernel_parameters[1:])
+                if "radius_effective" in all_param_names:
+                    s_params = modelinfo.ParameterTable(all_params[p_pars_len+1:p_pars_len+s_pars_len])
+                else:
+                    s_params = modelinfo.ParameterTable(all_params[p_pars_len:p_pars_len+s_pars_len-1])
+
+        # Add heading row
+        FittingUtilities.addHeadingRowToModel(self._model_model, structure_factor)
+
+        # Get new rows for QModel
+        # Any renamed parameters are stored as data in the relevant item, for later handling
+        FittingUtilities.addSimpleParametersToModel(
+                s_params,
+                self.is2D,
+                s_params_orig,
+                self._model_model,
+                self.lstParams)
 
     def haveParamsToFit(self):
@@ -2106,4 +2159,5 @@
         model_row = item.row()
         name_index = self._model_model.index(model_row, 0)
+        name_item = self._model_model.itemFromIndex(name_index)
 
         # Extract changed value.
@@ -2114,5 +2168,9 @@
             return
 
-        parameter_name = str(self._model_model.data(name_index)) # sld, background etc.
+        # if the item has user data, this is the actual parameter name (e.g. to handle duplicate names)
+        if name_item.data(QtCore.Qt.UserRole):
+            parameter_name = str(name_item.data(QtCore.Qt.UserRole))
+        else:
+            parameter_name = str(self._model_model.data(name_index))
 
         # Update the parameter value - note: this supports +/-inf as well
@@ -2237,4 +2295,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):
         """
@@ -2244,5 +2319,7 @@
             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()
@@ -2329,15 +2406,21 @@
             new_plots.append(sq_data)
 
+        for plot in new_plots:
+            self.communicate.plotUpdateSignal.emit([plot])
+
+    def complete2D(self, return_data):
+        """
+        Plot the current 2D data
+        """
+        fitted_data = self.logic.new2DPlot(return_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 complete2D(self, return_data):
-        """
-        Plot the current 2D data
-        """
-        fitted_data = self.logic.new2DPlot(return_data)
-        self.calculateResiduals(fitted_data)
-        self.model_data = fitted_data
 
     def calculateResiduals(self, fitted_data):
@@ -2470,4 +2553,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
@@ -2517,8 +2605,13 @@
         def updateFunctionCaption(row):
             # Utility function for update of polydispersity function name in the main model
+            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
@@ -2526,4 +2619,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':
@@ -2644,4 +2738,6 @@
                         param.units]
 
+        self.magnet_params[param.name] = param.default
+
         FittingUtilities.addCheckedListToModel(model, checked_list)
 
@@ -2683,5 +2779,5 @@
 
         self.lstParams.setIndexWidget(shell_index, func)
-        self._last_model_row = self._model_model.rowCount()
+        self._n_shells_row = shell_row - 1
 
         # Set the index to the state-kept value
@@ -2694,11 +2790,18 @@
         """
         # Find row location of the combobox
-        last_row = self._last_model_row
-        remove_rows = self._model_model.rowCount() - last_row
+        first_row = self._n_shells_row + 1
+        remove_rows = self._num_shell_params
 
         if remove_rows > 1:
-            self._model_model.removeRows(last_row, remove_rows)
-
-        FittingUtilities.addShellsToModel(self.model_parameters, self._model_model, index)
+            self._model_model.removeRows(first_row, remove_rows)
+
+        new_rows = FittingUtilities.addShellsToModel(
+                self.model_parameters,
+                self._model_model,
+                index,
+                first_row,
+                self.lstParams)
+
+        self._num_shell_params = len(new_rows)
         self.current_shell_displayed = index
 
Index: src/sas/qtgui/Perspectives/Fitting/ModelThread.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/ModelThread.py	(revision 2df558ebcc19b99ebc1900efaa872a981b31a67d)
+++ src/sas/qtgui/Perspectives/Fitting/ModelThread.py	(revision dcabba7be660ff2e140942eb331ca846d1f2a399)
@@ -101,36 +101,16 @@
         elapsed = time.time() - self.starttime
 
+        res = dict(image = output, data = self.data, page_id = self.page_id,
+            model = self.model, state = self.state,
+            toggle_mode_on = self.toggle_mode_on, elapsed = elapsed,
+            index = index_model, fid = self.fid,
+            qmin = self.qmin, qmax = self.qmax,
+            weight = self.weight, update_chisqr = self.update_chisqr,
+            source = self.source)
+
         if LocalConfig.USING_TWISTED:
-            return (output,
-                    self.data,
-                    self.page_id,
-                    self.model,
-                    self.state,
-                    self.toggle_mode_on,
-                    elapsed,
-                    index_model,
-                    self.fid,
-                    self.qmin,
-                    self.qmax,
-                    self.weight,
-                    self.update_chisqr,
-                    self.source)
-        else:
-            self.completefn((output,
-                           self.data,
-                           self.page_id,
-                           self.model,
-                           self.state,
-                           self.toggle_mode_on,
-                           elapsed,
-                           index_model,
-                           self.fid,
-                           self.qmin,
-                           self.qmax,
-                           self.weight,
-                           #qstep=self.qstep,
-                           self.update_chisqr,
-                           self.source))
-
+            return res
+        else:
+            self.completefn(res)
 
 class Calc1D(CalcThread):
@@ -236,30 +216,17 @@
         elapsed = time.time() - self.starttime
 
+        res = dict(x = self.data.x[index], y = output[index],
+            page_id = self.page_id, state = self.state, weight = self.weight,
+            fid = self.fid, toggle_mode_on = self.toggle_mode_on,
+            elapsed = elapsed, index = index, model = self.model,
+            data = self.data, update_chisqr = self.update_chisqr,
+            source = self.source, unsmeared_output = unsmeared_output,
+            unsmeared_data = unsmeared_data, unsmeared_error = unsmeared_error,
+            pq_values = pq_values, sq_values = sq_values)
+
         if LocalConfig.USING_TWISTED:
-            return (self.data.x[index], output[index],
-                    self.page_id,
-                    self.state,
-                    self.weight,
-                    self.fid,
-                    self.toggle_mode_on,
-                    elapsed, index, self.model,
-                    self.data,
-                    self.update_chisqr,
-                    self.source,
-                    unsmeared_output, unsmeared_data, unsmeared_error,
-                    pq_values, sq_values)
-        else:
-            self.completefn((self.data.x[index], output[index],
-                        self.page_id,
-                        self.state,
-                        self.weight,
-                        self.fid,
-                        self.toggle_mode_on,
-                        elapsed, index, self.model,
-                        self.data,
-                        self.update_chisqr,
-                        self.source,
-                        unsmeared_output, unsmeared_data, unsmeared_error,
-                        pq_values, sq_values))
+            return res
+        else:
+            self.completefn(res)
 
     def results(self):
Index: src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py	(revision 605d944a69488f22ea74b9d65f595b664f5aae54)
+++ src/sas/qtgui/Perspectives/Fitting/UnitTesting/FittingWidgetTest.py	(revision 3fbd77b038f1503431a47e6e7b3a7ad275c0655c)
@@ -256,5 +256,5 @@
         self.widget.cbStructureFactor.setCurrentIndex(structure_index)
 
-        # We have 4 more rows now
+        # We have 3 more param rows now (radius_effective is removed), and a new heading
         self.assertEqual(self.widget._model_model.rowCount(), rowcount+4)
 
@@ -276,6 +276,6 @@
         last_index = self.widget.cbStructureFactor.count()
         self.widget.cbStructureFactor.setCurrentIndex(last_index-1)
-        # Do we have all the rows?
-        self.assertEqual(self.widget._model_model.rowCount(), 4)
+        # Do we have all the rows (incl. radius_effective & heading row)?
+        self.assertEqual(self.widget._model_model.rowCount(), 5)
 
         # Are the command buttons properly enabled?
@@ -445,21 +445,24 @@
         self.assertEqual(self.widget.kernel_module.details['radius_bell'][1], 1.0)
 
+        #self.widget.show()
+        #QtWidgets.QApplication.exec_()
+
         # Change the number of points
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
         self.widget._poly_model.item(0,4).setText("22")
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
         # try something stupid
         self.widget._poly_model.item(0,4).setText("butt")
         # see that this didn't annoy the control at all
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 22)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 22)
 
         # Change the number of sigmas
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 3)
+        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 3)
         self.widget._poly_model.item(0,5).setText("222")
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
+        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
         # try something stupid again
         self.widget._poly_model.item(0,4).setText("beer")
         # no efect
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 222)
+        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 222)
 
     def testOnPolyComboIndexChange(self):
@@ -482,16 +485,16 @@
         self.widget.onPolyComboIndexChange('rectangle', 0)
         # check values
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 35)
-        self.assertAlmostEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 1.73205, 5)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 35)
+        self.assertAlmostEqual(self.widget.poly_params['radius_bell.nsigmas'], 1.73205, 5)
         # Change the index
         self.widget.onPolyComboIndexChange('lognormal', 0)
         # check values
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
+        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
         # Change the index
         self.widget.onPolyComboIndexChange('schulz', 0)
         # check values
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.npts'), 80)
-        self.assertEqual(self.widget.kernel_module.getParam('radius_bell.nsigmas'), 8)
+        self.assertEqual(self.widget.poly_params['radius_bell.npts'], 80)
+        self.assertEqual(self.widget.poly_params['radius_bell.nsigmas'], 8)
 
         # mock up file load
@@ -506,5 +509,9 @@
         Test opening of the load file dialog for 'array' polydisp. function
         """
+
+        # open a non-existent file
         filename = os.path.join("UnitTesting", "testdata_noexist.txt")
+        with self.assertRaises(OSError, msg="testdata_noexist.txt should be a non-existent file"):
+            os.stat(filename)
         QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
         self.widget.show()
@@ -522,5 +529,12 @@
 
         # good file
+        # TODO: this depends on the working directory being src/sas/qtgui,
+        # TODO: which isn't convenient if you want to run this test suite
+        # TODO: individually
         filename = os.path.join("UnitTesting", "testdata.txt")
+        try:
+            os.stat(filename)
+        except OSError:
+            self.assertTrue(False, "testdata.txt does not exist")
         QtWidgets.QFileDialog.getOpenFileName = MagicMock(return_value=(filename,''))
 
@@ -588,7 +602,10 @@
 
         # Assure we have the combobox available
-        last_row = self.widget._last_model_row
-        func_index = self.widget._model_model.index(last_row-1, 1)
+        cbox_row = self.widget._n_shells_row
+        func_index = self.widget._model_model.index(cbox_row, 1)
         self.assertIsInstance(self.widget.lstParams.indexWidget(func_index), QtWidgets.QComboBox)
+
+        # get number of rows before changing shell count
+        last_row = self.widget._model_model.rowCount()
 
         # Change the combo box index
@@ -1024,5 +1041,5 @@
 
          # Check the model
-        self.assertEqual(self.widget._model_model.rowCount(), 6)
+        self.assertEqual(self.widget._model_model.rowCount(), 7)
         self.assertEqual(self.widget._model_model.columnCount(), 5)
 
@@ -1140,5 +1157,5 @@
         # two rows selected
         index1 = self.widget.lstParams.model().index(1, 0, QtCore.QModelIndex())
-        index2 = self.widget.lstParams.model().index(2, 0, QtCore.QModelIndex())
+        index2 = self.widget.lstParams.model().index(3, 0, QtCore.QModelIndex())
         selection_model = self.widget.lstParams.selectionModel()
         selection_model.select(index1, selection_model.Select | selection_model.Rows)
@@ -1176,5 +1193,5 @@
         # several random parameters
         self.assertEqual(self.widget.getRowFromName('scale'), 0)
-        self.assertEqual(self.widget.getRowFromName('length'), 5)
+        self.assertEqual(self.widget.getRowFromName('length'), 6)
 
     def testGetParamNames(self):
@@ -1213,5 +1230,5 @@
         # Create a constraint object
         const = Constraint(parent=None, value=7.0)
-        row = 2
+        row = 3
 
         spy = QtSignalSpy(self.widget, self.widget.constraintAddedSignal)
@@ -1232,5 +1249,5 @@
         # assign complex constraint now
         const = Constraint(parent=None, param='radius', func='5*sld')
-        row = 4
+        row = 5
         # call the method tested
         self.widget.addConstraintToRow(constraint=const, row=row)
@@ -1291,7 +1308,14 @@
         self.widget.cbModel.setCurrentIndex(model_index)
 
+        row1 = 1
+        row2 = 5
+
+        param1 = "background"
+        param2 = "radius"
+
+        #default_value1 = "0.001"
+        default_value2 = "20"
+
         # select two rows
-        row1 = 1
-        row2 = 4
         index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
         index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1310,10 +1334,10 @@
 
         # delete one of the constraints
-        self.widget.deleteConstraintOnParameter(param='background')
+        self.widget.deleteConstraintOnParameter(param=param1)
 
         # see that the other constraint is still present
-        cons = self.widget.getConstraintForRow(4) # 4 = radius
-        self.assertEqual(cons.param, "radius")
-        self.assertEqual(cons.value, "20")
+        cons = self.widget.getConstraintForRow(row2)
+        self.assertEqual(cons.param, param2)
+        self.assertEqual(cons.value, default_value2)
 
         # kill the other constraint
@@ -1321,5 +1345,5 @@
 
         # see that the other constraint is still present
-        self.assertEqual(self.widget.getConstraintsForModel(), [('radius', None)])
+        self.assertEqual(self.widget.getConstraintsForModel(), [(param2, None)])
 
     def testGetConstraintForRow(self):
@@ -1341,7 +1365,8 @@
         self.widget.cbModel.setCurrentIndex(model_index)
 
+        row1 = 1
+        row2 = 5
+
         # select two rows
-        row1 = 1
-        row2 = 4
         index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
         index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1353,5 +1378,5 @@
         self.widget.addSimpleConstraint()
 
-        con_list = [False, True, False, False, True, False]
+        con_list = [False, True, False, False, False, True, False]
         new_list = []
         for row in range(self.widget._model_model.rowCount()):
@@ -1371,7 +1396,8 @@
         self.widget.cbModel.setCurrentIndex(model_index)
 
+        row1 = 1
+        row2 = 5
+
         # select two rows
-        row1 = 1
-        row2 = 4
         index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
         index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1387,5 +1413,5 @@
         constraint_objects[0].active = False
 
-        con_list = [False, False, False, False, True, False]
+        con_list = [False, False, False, False, False, True, False]
         new_list = []
         for row in range(self.widget._model_model.rowCount()):
@@ -1408,7 +1434,14 @@
         self.assertEqual(self.widget.getConstraintsForModel(),[])
 
+        row1 = 1
+        row2 = 5
+
+        param1 = "background"
+        param2 = "radius"
+
+        default_value1 = "0.001"
+        default_value2 = "20"
+
         # select two rows
-        row1 = 1
-        row2 = 4
         index1 = self.widget.lstParams.model().index(row1, 0, QtCore.QModelIndex())
         index2 = self.widget.lstParams.model().index(row2, 0, QtCore.QModelIndex())
@@ -1422,28 +1455,31 @@
         # simple constraints
         # self.assertEqual(self.widget.getConstraintsForModel(), [('background', '0.001'), ('radius', '20')])
-        cons = self.widget.getConstraintForRow(1) # 1 - background
-        self.assertEqual(cons.param, "background")
-        self.assertEqual(cons.value, "0.001")
-        cons = self.widget.getConstraintForRow(4) # 4 = radius
-        self.assertEqual(cons.param, "radius")
-        self.assertEqual(cons.value, "20")
+        cons = self.widget.getConstraintForRow(row1)
+        self.assertEqual(cons.param, param1)
+        self.assertEqual(cons.value, default_value1)
+        cons = self.widget.getConstraintForRow(row2)
+        self.assertEqual(cons.param, param2)
+        self.assertEqual(cons.value, default_value2)
 
         objects = self.widget.getConstraintObjectsForModel()
         self.assertEqual(len(objects), 2)
-        self.assertEqual(objects[1].value, '20')
-        self.assertEqual(objects[0].param, 'background')
+        self.assertEqual(objects[1].value, default_value2)
+        self.assertEqual(objects[0].param, param1)
+
+        row = 0
+        param = "scale"
+        func = "5*sld"
 
         # add complex constraint
-        const = Constraint(parent=None, param='scale', func='5*sld')
-        row = 0
+        const = Constraint(parent=None, param=param, func=func)
         self.widget.addConstraintToRow(constraint=const, row=row)
         #self.assertEqual(self.widget.getConstraintsForModel(),[('scale', '5*sld'), ('background', '0.001'), ('radius', None)])
-        cons = self.widget.getConstraintForRow(4) # 4 = radius
-        self.assertEqual(cons.param, "radius")
-        self.assertEqual(cons.value, "20")
+        cons = self.widget.getConstraintForRow(row2)
+        self.assertEqual(cons.param, param2)
+        self.assertEqual(cons.value, default_value2)
 
         objects = self.widget.getConstraintObjectsForModel()
         self.assertEqual(len(objects), 3)
-        self.assertEqual(objects[0].func, '5*sld')
+        self.assertEqual(objects[0].func, func)
 
     def testReplaceConstraintName(self):
Index: src/sas/qtgui/Plotting/Plotter.py
===================================================================
--- src/sas/qtgui/Plotting/Plotter.py	(revision b764ae5882f084a8b74c7cf664276c13fb07c29e)
+++ src/sas/qtgui/Plotting/Plotter.py	(revision c2f3ca2c72aeba33d19dcf49f5793abf90fa7a3b)
@@ -30,5 +30,7 @@
         # Dictionary of {plot_id:Data1d}
         self.plot_dict = {}
-
+        # Dictionaty of {plot_id:line}
+
+        self.plot_lines = {}
         # Window for text add
         self.addText = AddText(self)
@@ -182,4 +184,6 @@
         self.plot_dict[self._data.id] = self.data
 
+        self.plot_lines[self._data.id] = line
+
         # Now add the legend with some customizations.
 
@@ -196,5 +200,5 @@
 
         # refresh canvas
-        self.canvas.draw_idle()
+        self.canvas.draw()
         # This is an important processEvent.
         # This allows charts to be properly updated in order
@@ -416,4 +420,5 @@
         This effectlvely refreshes the chart with changes to one of its plots
         """
+        import logging
         self.removePlot(id)
         self.plot(data=new_plot)
@@ -470,5 +475,5 @@
         """
         selected_plot = self.plot_dict[id]
-
+        selected_line = self.plot_lines[id]
         # Old style color - single integer for enum color
         # New style color - #hhhhhh
