Index: installers/installer_generator.py
===================================================================
--- installers/installer_generator.py	(revision 0595bb7954497ab0a25be7ffb88395a048df52ae)
+++ installers/installer_generator.py	(revision 988deabd03498c048287f24fba03e1e340595a61)
@@ -55,5 +55,5 @@
         for item in wild_cards:
             #['All (*.*)|*.*']
-            file_type, ext = string.split(item, "|*", 1)
+            file_type, ext = item.split("|*", 1)
             if ext.strip() not in ['.*', ''] and ext.strip() not in list_data:
                 list_data.append((ext, 'string', file_type))
@@ -61,5 +61,5 @@
         pass
     try:
-        file_type, ext = string.split(local_config.APPLICATION_WLIST, "|*", 1)
+        file_type, ext = local_config.APPLICATION_WLIST.split("|*", 1)
         if ext.strip() not in ['.', ''] and ext.strip() not in list_app:
             list_app.append((ext, 'string', file_type))
@@ -68,5 +68,5 @@
     try:
         for item in local_config.PLUGINS_WLIST:
-            file_type, ext = string.split(item, "|*", 1)
+            file_type, ext = item.split("|*", 1)
             if ext.strip() not in ['.', ''] and ext.strip() not in list_app:
                 list_app.append((ext, 'string', file_type))
@@ -83,6 +83,6 @@
     msg = ""
     if data_extension is not None and data_extension:
+        msg = "\n\n[Registry]\n"
         openwithlist = "OpenWithList\%s" % str(APPLICATION)
-        msg = "\n\n[Registry]\n"
         for (ext, type, _) in data_extension:
             list = os.path.join(ext, openwithlist)
@@ -192,5 +192,5 @@
     return msg
 
-dist_path = "dist"
+dist_path = "dist\sasview"
 def write_file():
     """
@@ -200,11 +200,9 @@
     msg += """Source: "%s\%s";\t""" % (dist_path, str(APPLICATION))
     msg += """DestDir: "{app}";\tFlags: ignoreversion\n"""
-    msg += """Source: "dist\*";\tDestDir: "{app}";\t"""
+    msg += """Source: "dist\sasview\*";\tDestDir: "{app}";\t"""
     msg += """Flags: ignoreversion recursesubdirs createallsubdirs\n"""
-    msg += """Source: "dist\plugin_models\*";\tDestDir: "{userdesktop}\..\.sasview\plugin_models";\t"""
+    msg += """Source: "dist\sasview\plugin_models\*";\tDestDir: "{userdesktop}\..\.sasview\plugin_models";\t"""
     msg += """Flags: recursesubdirs createallsubdirs\n"""
-    msg += """Source: "dist\compiled_models\*";\tDestDir: "{userdesktop}\..\.sasmodels\compiled_models";\t"""
-    msg += """Flags: recursesubdirs createallsubdirs\n"""
-    msg += """Source: "dist\config\custom_config.py";\tDestDir: "{userdesktop}\..\.sasview\config";\t"""
+    msg += """Source: "dist\sasview\custom_config.py";\tDestDir: "{userdesktop}\..\.sasview\config";\t"""
     msg += """Flags: recursesubdirs createallsubdirs\n"""
     #msg += """Source: "dist\default_categories.json";    DestDir: "{userdesktop}\..\.sasview";\t"""
Index: installers/sasview_qt5.spec
===================================================================
--- installers/sasview_qt5.spec	(revision aefde773650ce9e4c4b6cba7a714220c1eff3a44)
+++ installers/sasview_qt5.spec	(revision aefde773650ce9e4c4b6cba7a714220c1eff3a44)
@@ -0,0 +1,172 @@
+# -*- mode: python -*-
+# Modifiable location
+import sys
+import os
+import platform
+
+import warnings
+from distutils.filelist import findall
+
+PYTHON_LOC = sys.exec_prefix
+# Portability settings
+if os.name == 'posix':
+    LIBPREFIX='lib'
+    LIBSUFFIX='so'
+    LIBLOC = os.path.join(PYTHON_LOC,'lib')
+    # Darwin needs UPX=False, Linux actually builds with both...
+    if platform.system() == 'Darwin':
+        UPX=False
+    else:
+        UPX=True
+else:
+    LIBPREFIX=''
+    LIBSUFFIX='dll'
+    LIBLOC = os.path.join(PYTHON_LOC,'Library','bin')
+    UPX=True
+
+SCRIPT_TO_SOURCE = 'sasview.py'
+
+# Warning! pyinstaller/py2exe etc. don't have the __file__ attribute
+WORK_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
+
+#### LOCAL METHODS
+def add_data(data):
+    for component in data:
+        target = component[0]
+        for filename in component[1]:
+           datas.append((filename, target))
+
+def add_binary(binary):
+    return (os.path.join(LIBLOC,binary),'.')
+
+# ADDITIONAL DATA ############################################################
+datas = [('../src/sas/sasview/images', 'images')]
+
+datas.append(('../src/sas/sasview/media','media'))
+datas.append(('../src/sas/sasview/test','test'))
+datas.append(('../src/sas/sasview/custom_config.py','.'))
+datas.append(('../src/sas/sasview/local_config.py','.'))
+datas.append(('../src/sas/sasview/wxcruft.py','.'))
+datas.append(('../src/sas/logger_config.py','.'))
+
+# pyinstaller gets mightily confused by upper/lower case,
+# so some modules need to be copied explicitly to avoid
+# messages like
+# WARNING: Attempted to add Python module twice with different upper/lowercases
+datas.append((os.path.join(PYTHON_LOC,'Lib','SocketServer.py'),'.'))
+datas.append((os.path.join(PYTHON_LOC,'Lib','Queue.py'),'.'))
+
+# TODO
+# NEED BETTER WAY TO DEAL WITH THESE RELATIVE PATHS
+datas.append((os.path.join('..', '..','sasmodels','sasmodels'),'sasmodels'))
+datas.append((os.path.join('..', 'src','sas','sasgui','perspectives','fitting','plugin_models'),'plugin_models'))
+datas.append((os.path.join(PYTHON_LOC,'Library','plugins','platforms'),'platforms'))
+datas.append((os.path.join(PYTHON_LOC,'Lib','site-packages','zmq','libzmq.cp36-win_amd64.pyd'),'.'))
+
+# These depend on whether we have MKL or Atlas numpy
+if os.path.exists(os.path.join(LIBLOC, LIBPREFIX + 'mkl_core.' + LIBSUFFIX)):
+    datas.append(add_binary(LIBPREFIX + 'mkl_avx2.' + LIBSUFFIX))
+    datas.append(add_binary(LIBPREFIX + 'mkl_def.' + LIBSUFFIX))
+elif os.path.exists(os.path.join(LIBLOC, LIBPREFIX + 'numpy-atlas.' + LIBSUFFIX)):
+    datas.append(add_binary(LIBPREFIX + 'numpy-atlas.' + LIBSUFFIX))
+else:
+    raise Exception("No numerical library for numpy found.")
+
+#import sas.sascalc.dataloader.readers
+#f = os.path.join(sas.sascalc.dataloader.readers.get_data_path(), 'defaults.json')
+#datas.append((f, '.'))
+
+# Add a custom pyopencl hook, as described in
+# https://github.com/pyinstaller/pyinstaller/issues/2130
+from PyInstaller.utils.hooks import copy_metadata
+datas.append(copy_metadata('pyopencl')[0])
+
+import sasmodels
+add_data(sasmodels.data_files())
+
+try:
+    import tinycc
+    add_data(tinycc.data_files())
+except ImportError:
+    warnings.warn("TinyCC package is not available and will not be included")
+
+import periodictable
+add_data(periodictable.data_files())
+
+import matplotlib
+matplotlibdatadir = matplotlib.get_data_path()
+matplotlibdata = findall(matplotlibdatadir)
+for f in matplotlibdata:
+    dirname = os.path.join('mpl-data', f[len(matplotlibdatadir)+1:])
+    datas.append((f, os.path.split(dirname)[0]))
+
+binaries = []
+
+# EXCLUDED FILES ############################################################
+# Spelled out to enable easier editing
+excludes = []
+
+# Need to explicitly exclude sasmodels here!!
+excludes.append('sasmodels')
+
+# HIDDEN MODULES ############################################################
+hiddenimports = [
+ 'PyQt5',
+ 'periodictable.core',
+ 'sasmodels.core',
+ 'pyopencl',
+ 'tinycc',
+ 'SocketServer',
+ 'logging',
+ 'logging.config',
+ 'ipykernel', 'ipykernel.datapub',
+ 'pygments', 'pygments.lexers','pygments.lexers.python',
+ 'pygments.styles','pygments.styles.default',
+ 'atexit', 'cython', 'sip', 'xhtml2pdf',
+ 'zmq', 'zmq.utils', 'zmq.utils.strtypes',
+ 'zmq.utils.jsonapi','zmq.utils.garbage',
+ 'zmq.backend.cython','zmq.backend.cffi',
+ 'site','lxml._elementpath','lxml.etree',
+ 'scipy._lib.messagestream',
+]
+
+a = Analysis([SCRIPT_TO_SOURCE],
+             pathex=[WORK_DIR],
+             binaries=binaries,
+             datas=datas,
+             hiddenimports=hiddenimports,
+             hookspath=[],
+             runtime_hooks=[],
+             excludes=excludes,
+             win_no_prefer_redirects=False,
+             win_private_assemblies=False,
+             cipher=None)
+
+pyz = PYZ(a.pure, a.zipped_data,
+             cipher=None)
+
+exe = EXE(pyz,
+          a.scripts,
+          exclude_binaries=True,
+          name='sasview',
+          debug=False,
+          upx=UPX,
+          icon=os.path.join("../src/sas/sasview/images","ball.ico"),
+          version="version.txt",
+          console=False )
+
+# COLLECT creates a directory instead of a single file.
+coll = COLLECT(exe,
+               a.binaries,
+               a.zipfiles,
+               a.datas,
+               strip=False,
+               upx=UPX,
+               name='sasview')
+
+if platform.system() == 'Darwin':
+    app = BUNDLE(exe,
+        name='SasView.app',
+        icon='../src/sas/sasview/images/ball.ico',
+        bundle_identifier=None)
+
Index: run.py
===================================================================
--- run.py	(revision 4992ff2626be19df7de9e5dfc0078b96bf47a75e)
+++ run.py	(revision a3221b61cb987c01cf74c03ef08544847fe3aa6a)
@@ -151,5 +151,5 @@
     logger.debug("Starting SASVIEW in debug mode.")
     prepare()
-    from sas.qtgui.MainWindow.MainWindow import run
-    run()
+    from sas.qtgui.MainWindow.MainWindow import run_sasview
+    run_sasview()
     logger.debug("Ending SASVIEW in debug mode.")
Index: src/sas/qtgui/MainWindow/MainWindow.py
===================================================================
--- src/sas/qtgui/MainWindow/MainWindow.py	(revision 8ac35512dd80b475772933cefe78a3437a8ec204)
+++ src/sas/qtgui/MainWindow/MainWindow.py	(revision a3221b61cb987c01cf74c03ef08544847fe3aa6a)
@@ -50,5 +50,5 @@
     return splashScreen
 
-def run():
+def run_sasview():
     app = QApplication([])
 
@@ -85,3 +85,3 @@
 
 if __name__ == "__main__":
-    run()
+    run_sasview()
Index: src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
===================================================================
--- src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision d6e3866160051b03e5bdb581acf0fd2227b090b4)
+++ src/sas/qtgui/Perspectives/Fitting/FittingWidget.py	(revision ded5e77dee7514710580502d488f33dd8b6acc19)
@@ -191,4 +191,8 @@
         self.is_batch_fitting = False
         self.is_chain_fitting = False
+        # Is the fit job running?
+        self.fit_started=False
+        # The current fit thread
+        self.calc_fit = None
         # Current SasModel in view
         self.kernel_module = None
@@ -1200,4 +1204,8 @@
         Perform fitting on the current data
         """
+        if self.fit_started:
+            self.stopFit()
+            return
+
         # initialize fitter constants
         fit_id = 0
@@ -1226,5 +1234,5 @@
         completefn = self.batchFittingCompleted if self.is_batch_fitting else self.fittingCompleted
 
-        calc_fit = FitThread(handler=handler,
+        self.calc_fit = FitThread(handler=handler,
                             fn=fitters,
                             batch_inputs=batch_inputs,
@@ -1237,16 +1245,31 @@
         if LocalConfig.USING_TWISTED:
             # start the trhrhread with twisted
-            calc_thread = threads.deferToThread(calc_fit.compute)
+            calc_thread = threads.deferToThread(self.calc_fit.compute)
             calc_thread.addCallback(completefn)
             calc_thread.addErrback(self.fitFailed)
         else:
             # Use the old python threads + Queue
-            calc_fit.queue()
-            calc_fit.ready(2.5)
+            self.calc_fit.queue()
+            self.calc_fit.ready(2.5)
 
         self.communicate.statusBarUpdateSignal.emit('Fitting started...')
+        self.fit_started = True
         # Disable some elements
         self.setFittingStarted()
 
+    def stopFit(self):
+        """
+        Attempt to stop the fitting thread
+        """
+        if self.calc_fit is None or not self.calc_fit.isrunning():
+            return
+        self.calc_fit.stop()
+        #self.fit_started=False
+        #re-enable the Fit button
+        self.setFittingStopped()
+
+        msg = "Fitting cancelled."
+        self.communicate.statusBarUpdateSignal.emit(msg)
+
     def updateFit(self):
         """
@@ -1258,6 +1281,7 @@
         """
         """
-        print("FIT FAILED: ", reason)
-        pass
+        self.setFittingStopped()
+        msg = "Fitting failed with: "+ str(reason)
+        self.communicate.statusBarUpdateSignal.emit(msg)
 
     def batchFittingCompleted(self, result):
@@ -1309,5 +1333,9 @@
 
         elapsed = result[1]
-        msg = "Fitting completed successfully in: %s s.\n" % GuiUtils.formatNumber(elapsed)
+        if self.calc_fit._interrupting:
+            msg = "Fitting cancelled by user after: %s s." % GuiUtils.formatNumber(elapsed)
+            logging.warning("\n"+msg+"\n")
+        else:
+            msg = "Fitting completed successfully in: %s s." % GuiUtils.formatNumber(elapsed)
         self.communicate.statusBarUpdateSignal.emit(msg)
 
@@ -2386,17 +2414,19 @@
     def setFittingStarted(self):
         """
-        Set item enablement on fitting start
-        """
-        #disable the Fit button
-        self.cmdFit.setText('Running...')
-        self.cmdFit.setEnabled(False)
+        Set buttion caption on fitting start
+        """
+        # Notify the user that fitting is being run
+        # Allow for stopping the job
+        self.cmdFit.setStyleSheet('QPushButton {color: red;}')
+        self.cmdFit.setText('Stop fit')
 
     def setFittingStopped(self):
         """
-        Set item enablement on fitting stop
-        """
-        #enable the Fit button
+        Set button caption on fitting stop
+        """
+        # Notify the user that fitting is available
+        self.cmdFit.setStyleSheet('QPushButton {color: black;}')
         self.cmdFit.setText("Fit")
-        self.cmdFit.setEnabled(True)
+        self.fit_started = False
 
     def readFitPage(self, fp):
Index: src/sas/qtgui/Utilities/TabbedModelEditor.py
===================================================================
--- src/sas/qtgui/Utilities/TabbedModelEditor.py	(revision 3b8cc00520d905f403798a463444af479d8bdd87)
+++ src/sas/qtgui/Utilities/TabbedModelEditor.py	(revision 3b8cc00520d905f403798a463444af479d8bdd87)
@@ -126,7 +126,8 @@
         self.editor_widget.setEnabled(True)
         self.editor_widget.blockSignals(False)
-        self.filename, _ = os.path.splitext(os.path.basename(filename))
-
-        self.setWindowTitle(self.window_title + " - " + self.filename)
+        self.filename = filename
+        display_name, _ = os.path.splitext(os.path.basename(filename))
+
+        self.setWindowTitle(self.window_title + " - " + display_name)
 
     def onModifiedExit(self):
