diff --git a/qt/python/mantidqt/widgets/codeeditor/editor.py b/qt/python/mantidqt/widgets/codeeditor/editor.py
index 0aed423995f7bcb0ac2c2940c8e4b8c1adaf8ef6..badf4f3f7f3aae0db8eeb5899280b9af4318cbff 100644
--- a/qt/python/mantidqt/widgets/codeeditor/editor.py
+++ b/qt/python/mantidqt/widgets/codeeditor/editor.py
@@ -17,6 +17,7 @@
 from __future__ import (absolute_import, unicode_literals)
 
 # 3rd party imports
+from qtpy.QtCore import QObject, Signal
 from qtpy.QtWidgets import QStatusBar, QTabWidget, QVBoxLayout, QWidget
 
 # local imports
@@ -48,11 +49,11 @@ class ExecutableCodeEditor(QWidget):
         super(ExecutableCodeEditor, self).__init__(parent)
 
         # layout
-        self.editor = CodeEditor(language, self)
-        self.status = QStatusBar(self)
+        self._editor = CodeEditor(language, self)
+        self._status = QStatusBar(self)
         layout = QVBoxLayout()
-        layout.addWidget(self.editor)
-        layout.addWidget(self.status)
+        layout.addWidget(self._editor)
+        layout.addWidget(self._status)
         self.setLayout(layout)
         layout.setContentsMargins(0, 0, 0, 0)
 
@@ -61,22 +62,34 @@ class ExecutableCodeEditor(QWidget):
     def execute_all_async(self):
         self.presenter.req_execute_all_async()
 
+    def set_editor_readonly(self, ro):
+        self._editor.setReadOnly(ro)
 
-class ExecutableCodeEditorPresenter(object):
+    def set_status_message(self, msg):
+        self._status.showMessage(msg)
+
+
+class ExecutableCodeEditorPresenter(QObject):
     """Presenter part of MVP to control actions on the editor"""
 
-    def __init__(self, view, model=None):
+    def __init__(self, view):
+        super(ExecutableCodeEditorPresenter, self).__init__()
         self.view = view
-        self.model = model if model is not None else PythonCodeExecution()
-        self.view.status.showMessage(IDLE_STATUS_MSG)
+        self.model = PythonCodeExecution(success_cb=self._on_exec_success)
+
+        self.view.set_status_message(IDLE_STATUS_MSG)
 
     def req_execute_all_async(self):
         text = self.view.text()
         if not text:
             return
-        self.view.status.showMessage(RUNNING_STATUS_MSG)
+        self.view.set_editor_read_only()
+        self.view.set_status_message(RUNNING_STATUS_MSG)
         self.model.execute_async(text)
 
+    def _on_exec_success(self):
+        self.view.set_status_message(IDLE_STATUS_MSG)
+
 
 class MultiFileCodeEditor(QWidget):
     """Provides a tabbed widget for editing multiple files"""
diff --git a/qt/python/mantidqt/widgets/codeeditor/execution.py b/qt/python/mantidqt/widgets/codeeditor/execution.py
index f5ac9f9d8e73c264472390488939aa28e8e16574..d7002b2ef50f9fc4547a1faa61960a23f8d1a068 100644
--- a/qt/python/mantidqt/widgets/codeeditor/execution.py
+++ b/qt/python/mantidqt/widgets/codeeditor/execution.py
@@ -42,9 +42,9 @@ class PythonCodeExecution(object):
             def success_cb_wrap(_): success_cb()
         else:
             def success_cb_wrap(_): pass
-        self.on_success = success_cb_wrap
-        self.on_error = error_cb
-        self.on_progress_update = progress_cb
+        self._on_success = success_cb_wrap
+        self._on_error = error_cb
+        self._on_progress_update = progress_cb
 
         self._globals_ns, self._locals_ns = None, None
         self.reset_context()
@@ -75,7 +75,7 @@ class PythonCodeExecution(object):
         # as these are not useful in this context
         t = AsyncTask(self.execute, args=(code_str,),
                       stack_chop=3,
-                      success_cb=self.on_success, error_cb=self.on_error)
+                      success_cb=self._on_success, error_cb=self._on_error)
         t.start()
         return t
 
@@ -89,7 +89,7 @@ class PythonCodeExecution(object):
         :raises: Any error that the code generates
         """
         # execute whole string if no reporting is required
-        if self.on_progress_update is None:
+        if self._on_progress_update is None:
             self._do_exec(code_str)
         else:
             self._execute_as_blocks(code_str)
@@ -100,7 +100,7 @@ class PythonCodeExecution(object):
         # will raise a SyntaxError if any of the code is invalid
         compile(code_str, "<string>", mode='exec')
 
-        progress_cb = self.on_progress_update
+        progress_cb = self._on_progress_update
         for block in code_blocks(code_str):
             progress_cb(block.lineno)
             self._do_exec(block.code_obj)
diff --git a/qt/python/mantidqt/widgets/codeeditor/test/test_executablecodeeditor.py b/qt/python/mantidqt/widgets/codeeditor/test/test_executablecodeeditor.py
index ec30f4610ab4e400e06a7e124f06509c1be8a44a..464b8565c010305f4bb4196da12e5a7097553c57 100644
--- a/qt/python/mantidqt/widgets/codeeditor/test/test_executablecodeeditor.py
+++ b/qt/python/mantidqt/widgets/codeeditor/test/test_executablecodeeditor.py
@@ -37,35 +37,35 @@ class ExecutableCodeEditorPresenterTest(unittest.TestCase):
 
     def setUp(self):
         self.view = mock.create_autospec(ExecutableCodeEditor)
-        self.view.status = mock.create_autospec(QStatusBar)
-        self.view.status.showMessage = mock.MagicMock()
-        self.model = mock.create_autospec(PythonCodeExecution)
+        self.view.set_status_message = mock.MagicMock()
+        self.view.set_editor_readonly = mock.MagicMock()
+
+        self.presenter = ExecutableCodeEditorPresenter(self.view)
+        self.view.set_status_message.reset_mock()
 
     def test_construction_sets_idle_status(self):
-        ExecutableCodeEditorPresenter(self.view, self.model)
-        self.view.status.showMessage.assert_called_once_with("Status: Idle")
+        ExecutableCodeEditorPresenter(self.view)
+        self.view.set_status_message.assert_called_once_with("Status: Idle")
 
     def test_execute_all_async_with_empty_code_does_nothing(self):
         self.view.text = mock.MagicMock(return_value="")
-        self.model.execute_async = mock.MagicMock()
+        self.presenter.model.execute_async = mock.MagicMock()
 
-        presenter = ExecutableCodeEditorPresenter(self.view, self.model)
-        self.view.status.showMessage.reset_mock()
-        presenter.req_execute_all_async()
+        self.presenter.req_execute_all_async()
         self.view.text.assert_called_once_with()
-        self.view.status.showMessage.assert_not_called()
-        self.model.execute_async.assert_not_called()
+        self.view.set_editor_readonly.assert_not_called()
+        self.view.set_status_message.assert_not_called()
+        self.presenter.model.execute_async.assert_not_called()
 
     def test_execute_all_async_with_successful_code(self):
         code_str = "x = 1 + 2"
         self.view.text = mock.MagicMock(return_value=code_str)
         self.model.execute_async = mock.MagicMock()
 
-        presenter = ExecutableCodeEditorPresenter(self.view, self.model)
-        self.view.status.showMessage.reset_mock()
-        presenter.req_execute_all_async()
+        self.presenter.req_execute_all_async()
         self.view.text.assert_called_once_with()
-        self.view.status.showMessage.assert_called_once_with("Status: Running")
+        self.view.set_editor_readonly.assert_called_once_with(True)
+        self.view.set_status_message.assert_called_once_with("Status: Running")
         self.model.execute_async.assert_called_once_with(code_str)