diff --git a/.flake8 b/.flake8
index 03532a6d20ef60a78e1923ba931b7ac66aee7029..75306b2f44c1f07c3df7b2eb17e846bf6d4f8772 100644
--- a/.flake8
+++ b/.flake8
@@ -14,7 +14,9 @@ exclude =
     installers,
     instrument,
     MantidPlot,
-    qt,
+    qt/paraview_ext,
+    qt/scientific_interfaces,
+    qt/widgets,
     scripts/test,
     Testing/PerformanceTests,
     Testing/SystemTests/lib,
diff --git a/.gitignore b/.gitignore
index 21d37fac751f97074d2ecaac926b5610ab2d2c7c..3871a5934c967678ff5421fcc32cc6e190a37dfc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -97,7 +97,8 @@ build/
 *.tmp
 *.vspscc
 .builds
-
+qt/python/mantidqt/utils/qt/plugins.py
+*.egg-info/
 
 # Visual C++ cache files
 ipch/
@@ -181,4 +182,3 @@ Third_Party/
 win64/
 dbg/
 rel/
-
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5f6ee8b137822b08888eb7b615d4b123c11cf328..b010c53ffa42a9c0634dba0916607d3b954bd1d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -119,8 +119,11 @@ if (ENABLE_MANTIDPLOT)
   separate_arguments ( PYQT4_SIP_FLAGS )
 endif()
 
-if (ENABLE_WORKBENCH)
+if ( ENABLE_WORKBENCH )
   find_package ( Qt5 COMPONENTS Core Gui Widgets REQUIRED )
+  if ( Qt5_FOUND )
+    message ( STATUS "Found Qt ${Qt5_VERSION}: ${Qt5_DIR}" )
+  endif()
   find_package ( PyQt5 REQUIRED )
   find_package ( SIP REQUIRED )
   separate_arguments ( PYQT5_SIP_FLAGS )
diff --git a/buildconfig/CMake/FindPyUnitTest.cmake b/buildconfig/CMake/FindPyUnitTest.cmake
index 9bfc279dca311adcce015b4edb2496a8d1a7179a..93d79e29df9430b7579e4aab2ef0294674aa4f3f 100644
--- a/buildconfig/CMake/FindPyUnitTest.cmake
+++ b/buildconfig/CMake/FindPyUnitTest.cmake
@@ -1,6 +1,7 @@
 #
 # PYUNITTEST_ADD_TEST (public macro to add unit tests)
 #   Adds a set of python tests based upon the unittest module
+#
 #   Parameters:
 #       _test_src_dir_base :: A base directory when added to the relative test paths gives
 #                             an absolute path to that test. This directory is added to the
diff --git a/buildconfig/CMake/QtTargetFunctions.cmake b/buildconfig/CMake/QtTargetFunctions.cmake
index ac724e8de03e8ad7cb3c893770fac4a158a23fb0..504a0fd8e81d007b2dfb99a64ebb7db3f99b0766 100644
--- a/buildconfig/CMake/QtTargetFunctions.cmake
+++ b/buildconfig/CMake/QtTargetFunctions.cmake
@@ -56,7 +56,7 @@ endfunction()
 # keyword: UI Qt designer ui files that are to be parsed by the UI compiler
 # keyword: NOMOC Additional headers that are not to be passed to moc
 # keyword: RES Any resource .qrc files
-# keyword: DEFS Compiler definitions to set to the target
+# keyword: DEFS Compiler definitions to set for all targets. Also QTX_DEFS can be used to set per-version targets
 # keyword: OUTPUT_DIR_BASE Base directory the build output. The final product goes into a subdirectory based on the Qt version
 # keyword: OUTPUT_SUBDIR Additional directory to added to the final output path
 # keyword: INCLUDE_DIRS A list of include directories to add to the target
@@ -79,8 +79,8 @@ function (mtd_add_qt_target)
   set (oneValueArgs
     TARGET_NAME QT_VERSION OUTPUT_DIR_BASE OUTPUT_SUBDIR
     INSTALL_DIR INSTALL_DIR_BASE PRECOMPILED)
-  set (multiValueArgs SRC QT4_SRC QT5_SRC UI MOC
-    NOMOC RES DEFS INCLUDE_DIRS SYSTEM_INCLUDE_DIRS LINK_LIBS
+  set (multiValueArgs SRC UI MOC
+    NOMOC RES DEFS QT4_DEFS QT5_DEFS INCLUDE_DIRS SYSTEM_INCLUDE_DIRS LINK_LIBS
     QT4_LINK_LIBS QT5_LINK_LIBS MTD_QT_LINK_LIBS OSX_INSTALL_RPATH)
   cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
                          "${multiValueArgs}" ${ARGN})
@@ -98,15 +98,16 @@ function (mtd_add_qt_target)
   _append_qt_suffix (AS_DIR VERSION ${PARSED_QT_VERSION} OUTPUT_VARIABLE _ui_dir
                      ${CMAKE_CURRENT_BINARY_DIR})
   set (CMAKE_CURRENT_BINARY_DIR ${_ui_dir})
+  set ( _all_defines ${PARSED_DEFS};${PARSED_QT${PARSED_QT_VERSION}_DEFS} )
   if (PARSED_QT_VERSION EQUAL 4)
     qt4_wrap_ui (UI_HEADERS ${PARSED_UI})
-    _internal_qt_wrap_cpp ( 4 MOC_GENERATED ${PARSED_MOC})
+    _internal_qt_wrap_cpp ( 4 MOC_GENERATED DEFS ${_all_defines} INFILES ${PARSED_MOC})
     set (ALL_SRC ${PARSED_SRC} ${PARSED_QT4_SRC} ${MOC_GENERATED})
     qt4_add_resources (RES_FILES ${PARSED_RES})
     set (_qt_link_libraries Qt4::QtGui ${PARSED_QT4_LINK_LIBS})
   elseif (PARSED_QT_VERSION EQUAL 5)
     qt5_wrap_ui (UI_HEADERS ${PARSED_UI})
-    _internal_qt_wrap_cpp ( 5 MOC_GENERATED ${PARSED_MOC} )
+    _internal_qt_wrap_cpp ( 5 MOC_GENERATED DEFS ${_all_defines} INFILES ${PARSED_MOC} )
     set (ALL_SRC ${PARSED_SRC} ${PARSED_QT5_SRC} ${MOC_GENERATED})
     qt5_add_resources (RES_FILES ${PARSED_RES})
     set (_qt_link_libraries Qt5::Widgets ${PARSED_QT5_LINK_LIBS})
@@ -162,8 +163,8 @@ function (mtd_add_qt_target)
   endif()
   target_link_libraries (${_target} PRIVATE ${_qt_link_libraries}
                          ${PARSED_LINK_LIBS} ${_mtd_qt_libs})
-  if(PARSED_DEFS)
-    set_target_properties ( ${_target} PROPERTIES COMPILE_DEFINITIONS "${PARSED_DEFS}" )
+  if(_all_defines)
+    set_target_properties ( ${_target} PROPERTIES COMPILE_DEFINITIONS "${_all_defines}" )
   endif()
 
   if (OSX_VERSION VERSION_GREATER 10.8)
@@ -336,19 +337,29 @@ endfunction ()
 # allowed limit on Windows (260 chars). It is assumed that the input paths
 # can be made absolute by prefixing them with ${CMAKE_CURRENT_LIST_DIR}
 # It assumes that the unnamed arguments are the input files to run through moc
-function(_internal_qt_wrap_cpp qtversion moc_generated)
-  foreach (_infile ${ARGN})
+function (_internal_qt_wrap_cpp qtversion moc_generated )
+  set (options)
+  set (oneValueArgs)
+  set (multiValueArgs DEFS INFILES)
+  cmake_parse_arguments (PARSED "${options}" "${oneValueArgs}"
+                         "${multiValueArgs}" ${ARGN})
+  foreach (_def ${PARSED_DEFS})
+    list ( APPEND _moc_defs "-D${_def}")
+  endforeach ()
+  foreach (_infile ${PARSED_INFILES})
     if(qtversion EQUAL 4)
-      qt4_wrap_cpp (moc_generated ${_infile} OPTIONS -i -f${CMAKE_CURRENT_LIST_DIR}/${_infile} )
+      qt4_wrap_cpp (moc_generated ${_infile}
+        OPTIONS -i -f${CMAKE_CURRENT_LIST_DIR}/${_infile} ${_moc_defs} )
     elseif (qtversion EQUAL 5)
-      qt5_wrap_cpp (moc_generated ${_infile} OPTIONS -i -f${CMAKE_CURRENT_LIST_DIR}/${_infile} )
+      qt5_wrap_cpp (moc_generated ${_infile}
+        OPTIONS -i -f${CMAKE_CURRENT_LIST_DIR}/${_infile} ${_moc_defs} )
     else()
       message (FATAL_ERROR "Unknown Qt version='${qtversion}'.")
     endif()
   endforeach()
   # Pass the output variable out
   set (${moc_generated} ${${moc_generated}} PARENT_SCOPE)
-endfunction()
+endfunction ()
 
 # Disables suggest override for versions of Qt < 5.6.2 as
 # Q_OBJECT produces them and the cannot be avoided.
diff --git a/buildconfig/windows/buildenv.bat.in b/buildconfig/windows/buildenv.bat.in
index a32094c47e03935399c3fadb7ef6f09a64da9301..1792ac68a50f37abeefff0df5f9e11903ebc6920 100644
--- a/buildconfig/windows/buildenv.bat.in
+++ b/buildconfig/windows/buildenv.bat.in
@@ -13,6 +13,9 @@ set THIRD_PARTY_DIR=%CM_THIRD_PARTY_DIR:/=\%
 :: Qt4 - exes are in bin & dlls are in lib
 set QT4_BIN=%THIRD_PARTY_DIR%\lib\qt4\bin;%THIRD_PARTY_DIR%\lib\qt4\lib
 
+:: Qt5 - exes are in bin & dlls are in lib
+set QT5_BIN=%THIRD_PARTY_DIR%\lib\qt5\bin;%THIRD_PARTY_DIR%\lib\qt5\lib
+
 :: Python -
 set PYTHONHOME=%THIRD_PARTY_DIR%\lib\python@PYTHON_MAJOR_VERSION@.@PYTHON_MINOR_VERSION@
 
@@ -20,4 +23,4 @@ set PYTHONHOME=%THIRD_PARTY_DIR%\lib\python@PYTHON_MAJOR_VERSION@.@PYTHON_MINOR_
 set MISC_BIN=%THIRD_PARTY_DIR%\bin;%THIRD_PARTY_DIR%\bin\mingw
 
 :: Update PATH
-set PATH=%MISC_BIN%;%PYTHONHOME%;%QT4_BIN%;%PATH%
+set PATH=%MISC_BIN%;%PYTHONHOME%;%QT5_BIN%;%QT4_BIN%;%PATH%
diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt
index f32ea2d43b601759de7fee94d1e408599e404bc2..82b42ecd9bc51ed36905e6e998ee0f6a6d408e27 100644
--- a/qt/CMakeLists.txt
+++ b/qt/CMakeLists.txt
@@ -1,14 +1,47 @@
-###########################################################################
-# Now add the packages one-by-one, building up the dependencies as we go
-###########################################################################
 find_package ( QScintillaQt4 REQUIRED )
 
 # Utilities for defining targets
 include ( QtTargetFunctions )
 
+# Function to create links to python packages in the source tree
+# If the EXECUTABLE option is provided then it additional build rules are
+# defined to ensure startup scripts are regenerated appropriately
+function ( add_python_package pkg_name )
+  # Create a setup.py file
+  set ( _setup_py ${CMAKE_CURRENT_SOURCE_DIR}/setup.py )
+  set ( _egg_link_dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR} )
+  set ( _egg_link ${_egg_link_dir}/${pkg_name}.egg-link )
+  if ( ARGV0 EQUAL "EXECUTABLE" )
+      if ( WIN32 )
+        set ( _startup_script ${_egg_link_dir}/${pkg_name}-script.pyw )
+        set ( _startup_exe ${_egg_link_dir}/${pkg_name}.exe )
+      else ()
+        set ( _startup_script )
+        set ( _startup_exe ${_egg_link_dir}/${pkg_name} )
+      endif ()
+  endif ()
+  set ( _outputs "${_egg_link} ${_startup_script} ${_startup_exe}" )
+  add_custom_command ( OUTPUT ${_outputs}
+    COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${_egg_link_dir}
+      ${PYTHON_EXECUTABLE} ${_setup_py} develop
+      --install-dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
+      --script-dir ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${CMAKE_CFG_INTDIR}
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    DEPENDS ${_setup_py}
+  )
+  add_custom_target ( ${pkg_name} ALL
+    DEPENDS ${_outputs}
+  )
+endfunction ()
+
+###########################################################################
+# Qt-based targets
+###########################################################################
 add_subdirectory ( widgets )
 add_subdirectory ( python )
 add_subdirectory ( scientific_interfaces )
 if ( MAKE_VATES )
   add_subdirectory ( paraview_ext )
 endif ( MAKE_VATES )
+
+add_subdirectory ( applications )
diff --git a/qt/applications/CMakeLists.txt b/qt/applications/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..cb53ce0a4a3fecbf5c5bfa2bed416d0b4822cb93
--- /dev/null
+++ b/qt/applications/CMakeLists.txt
@@ -0,0 +1,3 @@
+if ( ENABLE_WORKBENCH )
+  add_subdirectory ( workbench )
+endif ()
diff --git a/qt/applications/workbench/CMakeLists.txt b/qt/applications/workbench/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c6414f123903ba2e20521dac1ed7af6887e9ddc0
--- /dev/null
+++ b/qt/applications/workbench/CMakeLists.txt
@@ -0,0 +1,15 @@
+# Creates egg link to binary output directory
+add_python_package ( workbench EXECUTABLE )
+add_dependencies ( workbench mantidqt )
+
+# ctest targets
+set ( TEST_FILES
+  workbench/config/test/user_test.py
+  workbench/test/import_test.py
+)
+
+pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR}
+  workbench ${TEST_FILES}
+)
+
+# Not installed yet...
diff --git a/qt/applications/workbench/scripts/workbench.py b/qt/applications/workbench/scripts/workbench.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/qt/applications/workbench/setup.py b/qt/applications/workbench/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e3db4ad665309e688d04d56de958f5b79b2dfc1
--- /dev/null
+++ b/qt/applications/workbench/setup.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from setuptools import setup
+
+# The most basic setup possible to be able to use setup.py develop
+setup(
+    name="workbench",
+    install_requires=['mantidqt'],
+    entry_points={
+        'gui_scripts': [
+            'workbench = workbench.app.mainwindow:main'
+        ]
+    },
+)
diff --git a/qt/applications/workbench/workbench/__init__.py b/qt/applications/workbench/workbench/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..27614544cb414ae77fa4a9d3637b3d201e6197ac
--- /dev/null
+++ b/qt/applications/workbench/workbench/__init__.py
@@ -0,0 +1,26 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+workbench
+---------
+
+This package contains the mantidproject's Qt-based workbench. It is the primary
+GUI for interacting with the mantid reduction and analysis package.
+"""
+
+# This file should be free from Qt imports to keep it lightweight to import
+# and check metadata.
diff --git a/qt/applications/workbench/workbench/app/__init__.py b/qt/applications/workbench/workbench/app/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fde0b4389afda4c454662daf6773bc9e22dfae86
--- /dev/null
+++ b/qt/applications/workbench/workbench/app/__init__.py
@@ -0,0 +1,22 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+workbench.app
+-------------
+
+Holds code related to application startup.
+"""
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fd5915e1a5858025e595c9074960329529a436d
--- /dev/null
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -0,0 +1,140 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Defines the QMainWindow of the application and the main() entry point.
+"""
+from __future__ import (absolute_import, division,
+                        print_function, unicode_literals)
+
+import sys
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+ORIGINAL_SYS_EXIT = sys.exit
+STDERR = sys.stderr
+
+# -----------------------------------------------------------------------------
+# Requirements
+# -----------------------------------------------------------------------------
+from workbench import requirements  # noqa
+requirements.check_qt()
+
+# -----------------------------------------------------------------------------
+# Qt
+# -----------------------------------------------------------------------------
+from qtpy.QtCore import QCoreApplication, Qt  # noqa
+from qtpy.QtWidgets import QApplication, QMainWindow  # noqa
+from mantidqt.utils.qt import load_ui, plugins  # noqa
+
+# Pre-application startup
+plugins.setup_library_paths()
+if hasattr(Qt, 'AA_EnableHighDpiScaling'):
+    QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
+
+
+# -----------------------------------------------------------------------------
+# Create the application instance early, set the application name for window
+# titles and hold on to a reference to it. Required to be performed early so
+# that the splash screen can be displayed
+# -----------------------------------------------------------------------------
+
+def qapplication():
+    """Either return a reference to an existing application instance
+    or create a new one
+    :return: A reference to the QApplication object
+    """
+    app = QApplication.instance()
+    if app is None:
+        app = QApplication(['Mantid Workbench'])
+    return app
+
+# Create the application object early
+MAIN_APP = qapplication()
+
+
+# -----------------------------------------------------------------------------
+# MainWindow
+# -----------------------------------------------------------------------------
+class MainWindow(QMainWindow):
+
+    def __init__(self):
+        QMainWindow.__init__(self)
+        self._ui = load_ui(__file__, 'mainwindow.ui', baseinstance=self)
+        MAIN_APP.setAttribute(Qt.AA_UseHighDpiPixmaps)
+
+
+def initialize():
+    """Perform an initialization of the application instance. Most notably
+    this patches sys.exit so that it does nothing.
+
+    :return: A reference to the existing application instance
+    """
+    app = qapplication()
+
+    # Monkey patching sys.exit so users can't kill
+    # the application this way
+    def fake_sys_exit(arg=[]):
+        pass
+    sys.exit = fake_sys_exit
+
+    return app
+
+
+def start_workbench(app):
+    """Given an application instance create the MainWindow,
+    show it and start the main event loop
+    """
+    main_window = MainWindow()
+    # do some pre-show setup...
+    main_window.show()
+    # do some pre-show setup...
+
+    # lift-off!
+    app.exec_()
+
+    return main_window
+
+
+def main():
+    """Main entry point for the application"""
+
+    # parse command arguments early
+
+    # general initialization
+    app = initialize()
+
+    main_window = None
+    try:
+        main_window = start_workbench(app)
+    except BaseException:
+        # We count this as a crash
+        import traceback
+        # This is type of thing we want to capture and have reports
+        # about. Prints to stderr as we can't really count on anything
+        # else
+        traceback.print_exc(file=STDERR)
+
+    if main_window is None:
+        # An exception occurred don't exit here
+        return
+
+    ORIGINAL_SYS_EXIT()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/qt/applications/workbench/workbench/app/mainwindow.ui b/qt/applications/workbench/workbench/app/mainwindow.ui
new file mode 100644
index 0000000000000000000000000000000000000000..a6f65a22a74fc103e55779b6ab61ba47b2f41970
--- /dev/null
+++ b/qt/applications/workbench/workbench/app/mainwindow.ui
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Mantid Workbench</string>
+  </property>
+  <widget class="QWidget" name="centralwidget"/>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>26</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="actionQuit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <action name="actionQuit">
+   <property name="text">
+    <string>Quit</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+Q</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>actionQuit</sender>
+   <signal>triggered()</signal>
+   <receiver>MainWindow</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>399</x>
+     <y>299</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/qt/applications/workbench/workbench/config/__init__.py b/qt/applications/workbench/workbench/config/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..38b6452e68f2d819b646bccff5c213e92581ea5a
--- /dev/null
+++ b/qt/applications/workbench/workbench/config/__init__.py
@@ -0,0 +1,16 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
diff --git a/qt/applications/workbench/workbench/config/main.py b/qt/applications/workbench/workbench/config/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..dbaa95639b656c75d4c1ce99b65726cdf1a419a7
--- /dev/null
+++ b/qt/applications/workbench/workbench/config/main.py
@@ -0,0 +1,47 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+""" Main configuration module.
+
+A singleton instance called CONF is defined. Modules wishing to access the settings
+should import the CONF object as
+
+    from workbench.config.main import CONF
+
+and use it to access the settings
+"""
+from __future__ import (absolute_import, unicode_literals)
+
+from workbench.config.user import UserConfig
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+ORGANIZATION = 'mantidproject'
+APPNAME = 'workbench'
+
+# Iterable containing defaults for each configurable section of the code
+# General application settings are in the main section
+DEFAULTS = {
+    'main': {
+      'high_dpi_scaling': True,
+    }
+}
+
+# -----------------------------------------------------------------------------
+# 'Singleton' instance
+# -----------------------------------------------------------------------------
+CONF = UserConfig(ORGANIZATION, APPNAME, defaults=DEFAULTS)
diff --git a/qt/applications/workbench/workbench/config/test/user_test.py b/qt/applications/workbench/workbench/config/test/user_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..ed66573c01c7063c802e3e07425b98b75e1bd404
--- /dev/null
+++ b/qt/applications/workbench/workbench/config/test/user_test.py
@@ -0,0 +1,71 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import absolute_import
+
+import os
+from unittest import TestCase
+
+from workbench.config.user import UserConfig
+
+
+class ConfigUserTest(TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        if not hasattr(cls, 'cfg'):
+            defaults = {
+                'main': {'a-default-key': 100}
+            }
+            cls.cfg = UserConfig(cls.__name__, cls.__name__, defaults)
+
+    @classmethod
+    def tearDownClass(cls):
+        try:
+            os.remove(cls.cfg.filename)
+            del cls.cfg
+        except OSError:
+            pass
+
+    # ----------------------------------------------
+    # Success tests
+    # ----------------------------------------------
+
+    def test_stored_value_not_in_defaults_is_retrieved(self):
+        self.cfg.set('main', 'key1', 1)
+        self.assertEqual(1, self.cfg.get('main', 'key1'))
+
+    def test_value_not_in_settings_retrieves_default_if_it_exists(self):
+        self.assertEqual(100, self.cfg.get('main', 'a-default-key'))
+
+    # ----------------------------------------------
+    # Failure tests
+    # ----------------------------------------------
+
+    def test_get_raises_error_with_invalid_section_type(self):
+        self.assertRaises(RuntimeError, self.cfg.get, 1, 'key1')
+
+    def test_get_raises_error_with_invalid_option_type(self):
+        self.assertRaises(RuntimeError, self.cfg.get, 'section', 1)
+
+    def test_get_raises_keyerror_with_no_saved_setting_or_default(self):
+        self.assertRaises(KeyError, self.cfg.get, 'main', 'missing-key')
+
+    def test_set_raises_error_with_invalid_section_type(self):
+        self.assertRaises(RuntimeError, self.cfg.set, 1, 'key1', 1)
+
+    def test_set_raises_error_with_invalid_option_type(self):
+        self.assertRaises(RuntimeError, self.cfg.set, 'section', 1, 1)
diff --git a/qt/applications/workbench/workbench/config/user.py b/qt/applications/workbench/workbench/config/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc0a914dc21dd63930e8cbba409e955a17b05ba5
--- /dev/null
+++ b/qt/applications/workbench/workbench/config/user.py
@@ -0,0 +1,119 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+from mantidqt.py3compat import is_text_string
+
+from qtpy.QtCore import QSettings
+
+
+class UserConfig(object):
+    """Holds user configuration option. Options are assigned a section
+    and a key must only be unique within a section.
+
+    Uses QSettings for the heavy lifting. All platforms use the Ini format
+    at UserScope
+    """
+
+    # The raw QSettings instance
+    settings = None
+    defaults = None
+
+    def __init__(self, organization, application, defaults=None):
+        """
+
+        :param organization: A string name for the organization
+        :param application: A string name for the application name
+        :param defaults: Default configuration values for this instance in the
+        form of nested dict instances
+        """
+        # Loads the saved settings if found
+        self.settings = QSettings(QSettings.IniFormat, QSettings.UserScope,
+                                  organization, application)
+        self.defaults = defaults
+
+    def all_keys(self):
+        return self.settings.allKeys()
+
+    @property
+    def filename(self):
+        return self.settings.fileName()
+
+    def get(self, section, option):
+        """
+        Return a value for an option in a given section. If not
+        specified in the saved settings then the defaults are
+        consulted. If no option is found then a KeyError is raised
+        :param section: A string section name
+        :param option: A string option name
+        :return: The value of the option
+        """
+        value = self.settings.value(self._settings_path(section, option))
+        if not value:
+            value = self._get_default_or_raise(section, option)
+
+        return value
+
+    def set(self, section, option, value):
+        """
+        Set a value for an option in a given section.
+        :param section: A string section name
+        :param option: A string option name
+        :param value: The value of the setting
+        """
+        self.settings.setValue(self._settings_path(section, option), value)
+
+    # -------------------------------------------------------------------------
+    # "Private" methods
+    # -------------------------------------------------------------------------
+
+    def _check_section_option_is_valid(self, section, option):
+        """
+        Sanity check the section and option are strings
+        """
+        if not is_text_string(section):
+            raise RuntimeError("section is not a text string")
+        if not is_text_string(option):
+            raise RuntimeError("option is not a text string")
+
+    def _get_default_or_raise(self, section, option):
+        """
+        Returns the value listed in the defaults if it exists
+        :param section: A string denoting the section (not checked)
+        :param option: A string denoting the option name
+        :return: The value of the default
+        :raises KeyError: if the item does not exist
+        """
+        value = None
+        if self.defaults and section in self.defaults:
+            value = self.defaults[section].get(option, None)
+        if not value:
+            raise KeyError("Unknown config item requested: " +
+                           self._settings_path(section, option))
+        return value
+
+    def _settings_path(self, section, option):
+        """
+        Private method to construct a path to the given option with the
+        section
+        :param section: The name of the section
+        :param option: The name of the option
+        :return: A path to the location within the QSettings instance
+        """
+        self._check_section_option_is_valid(section, option)
+        return section + "/" + option
diff --git a/qt/applications/workbench/workbench/requirements.py b/qt/applications/workbench/workbench/requirements.py
new file mode 100644
index 0000000000000000000000000000000000000000..f591b866497f93c1010fed6e75ab0d27a4022fb5
--- /dev/null
+++ b/qt/applications/workbench/workbench/requirements.py
@@ -0,0 +1,39 @@
+"""Defines methods to check requirements for the application
+"""
+from __future__ import absolute_import
+
+from distutils.version import LooseVersion
+
+
+def show_warning(message):
+    """Show warning using Tkinter if available"""
+    try:
+        # If Tkinter is installed (highly probable), showing an error pop-up
+        import Tkinter
+        import tkMessageBox
+        root = Tkinter.Tk()
+        root.withdraw()
+        tkMessageBox.showerror("Mantid Workbench", message)
+    except ImportError:
+        pass
+    raise RuntimeError(message)
+
+
+def check_qt():
+    """Check the qt requirements are as expected
+    """
+    package_name, required_ver = ("PyQt5", "5.2")
+    try:
+        import qtpy
+        actual_ver = qtpy.PYQT_VERSION
+        if LooseVersion(actual_ver) < LooseVersion(required_ver):
+            show_warning(
+              "Please check installation requirements:\n"
+              "{} {}+ is required (found v{}).".format(
+                                                  package_name,
+                                                  required_ver,
+                                                  actual_ver)
+            )
+    except ImportError as exc:
+        show_warning("Failed to import qtpy: '{}'\n".format(str(exc)) +
+                     "Please check the installation.")
diff --git a/qt/applications/workbench/workbench/test/import_test.py b/qt/applications/workbench/workbench/test/import_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..03fa79e0b7f4ce9ea843ed5e91877af912e0bf69
--- /dev/null
+++ b/qt/applications/workbench/workbench/test/import_test.py
@@ -0,0 +1,29 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from __future__ import (absolute_import, division, print_function,
+                        unicode_literals)
+
+import unittest
+
+
+class ImportTest(unittest.TestCase):
+
+    def test_import_workbench(self):
+        import workbench  # noqa
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/qt/python/CMakeLists.txt b/qt/python/CMakeLists.txt
index d8ae39214d00fa852f081fc9e32e00a3e8bd99cf..3a2c792eed2e23e24307cd583f374a92ec457620 100644
--- a/qt/python/CMakeLists.txt
+++ b/qt/python/CMakeLists.txt
@@ -5,14 +5,34 @@
 # Legacy wrappers for MantidPlot
 add_subdirectory ( mantidqtpython )
 
-# mantidqt is run from the source directory so just add tests
-set ( PYTHON_TEST_FILES
-  mantidqt/test/import_test.py
-)
+###########################################################################
+# mantidqt
+###########################################################################
 
-# Tests
-pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR}
-  mantidqt ${PYTHON_TEST_FILES}
-)
+if ( ENABLE_WORKBENCH )
 
-# No package installation yet...
+  # Configure utils.qt.plugins file for build. It is placed in the source
+  # directory and added to the .gitignore for simplicity.
+  if ( WIN32 )
+  set ( QT_PLUGINS_PATH "${THIRD_PARTY_DIR}/lib/qt%V/plugins" )
+  endif ()
+  configure_file ( mantidqt/utils/qt/plugins.py.in
+    ${CMAKE_CURRENT_SOURCE_DIR}/mantidqt/utils/qt/plugins.py
+  )
+
+  # Create egg link to binary output directory for mantidqt
+  add_python_package ( mantidqt )
+
+  # ctest targets
+  set ( PYTHON_TEST_FILES
+    mantidqt/test/import_test.py
+  )
+
+  # Tests
+  pyunittest_add_test ( ${CMAKE_CURRENT_SOURCE_DIR}
+    mantidqt ${PYTHON_TEST_FILES}
+  )
+
+  # No package installation yet...
+  # Configure utils.qt.plugins file for install
+endif ()
diff --git a/qt/python/mantidqt/py3compat.py b/qt/python/mantidqt/py3compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..43d094229f8d18689d0aa33abab74da94147c26a
--- /dev/null
+++ b/qt/python/mantidqt/py3compat.py
@@ -0,0 +1,67 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+mantidqt.py3compat
+------------------
+
+Transitional module providing compatibility functions intended to help
+migrating from Python 2 to Python 3. Mostly just wraps six but allowing
+for additional functionality of our own.
+
+This module should be fully compatible with:
+    * Python >=v2.7
+    * Python 3
+"""
+
+from __future__ import absolute_import, print_function
+
+import six
+from six import * # noqa
+
+# -----------------------------------------------------------------------------
+# Globals and constants
+# -----------------------------------------------------------------------------
+__all__ = dir(six)
+
+
+# -----------------------------------------------------------------------------
+# Strings
+# -----------------------------------------------------------------------------
+def is_text_string(obj):
+    """Return True if `obj` is a text string, False if it is anything else,
+    like binary data (Python 3) or QString (Python 2, PyQt API #1)"""
+    return isinstance(obj, string_types)
+
+
+def to_text_string(obj, encoding=None):
+    """Convert `obj` to (unicode) text string"""
+
+    if PY2:
+        # Python 2
+        if encoding is None:
+            return unicode(obj)
+        else:
+            return unicode(obj, encoding)
+    else:
+        # Python 3
+        if encoding is None:
+            return str(obj)
+        elif isinstance(obj, str):
+            # In case this function is not used properly, this could happen
+            return obj
+        else:
+            return str(obj, encoding)
diff --git a/qt/python/mantidqt/test/import_test.py b/qt/python/mantidqt/test/import_test.py
index 672afdaaf1ff83ca644c112f0badebeb1557854e..2ac0a192e5a0f7d57839f3c02e607d937f373542 100644
--- a/qt/python/mantidqt/test/import_test.py
+++ b/qt/python/mantidqt/test/import_test.py
@@ -3,6 +3,7 @@ from __future__ import (absolute_import, division, print_function,
 
 import unittest
 
+
 class ImportTest(unittest.TestCase):
 
     def test_import(self):
diff --git a/qt/python/mantidqt/utils/__init__.py b/qt/python/mantidqt/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/qt/python/mantidqt/utils/qt/__init__.py b/qt/python/mantidqt/utils/qt/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..821a7937edbd768ed79c34f3af240da8b64857db
--- /dev/null
+++ b/qt/python/mantidqt/utils/qt/__init__.py
@@ -0,0 +1,41 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""A selection of utility functions related to Qt functionality
+"""
+from __future__ import absolute_import
+
+# stdlib modules
+import os
+
+# 3rd-party modules
+from qtpy.uic import loadUi
+
+
+def load_ui(caller_filename, ui_relfilename, baseinstance=None):
+    """Load a ui file assuming it is relative to a given callers filepath
+
+    :param caller_filename: An absolute path to a file whose basename when
+    joined with ui_relfilename gives the full path to the .ui file. Generally
+    this called with __file__
+    :param ui_relfilename: A relative filepath that when joined with the
+    basename of caller_filename gives the absolute path to the .ui file.
+    :param baseinstance: An instance of a widget to pass to uic.loadUi
+    that becomes the base class rather than a new widget being created.
+    :return: A new instance of the form class
+    """
+    filepath = os.path.join(os.path.dirname(caller_filename), ui_relfilename)
+    return loadUi(filepath, baseinstance=baseinstance)
diff --git a/qt/python/mantidqt/utils/qt/plugins.py.in b/qt/python/mantidqt/utils/qt/plugins.py.in
new file mode 100644
index 0000000000000000000000000000000000000000..666ca568da5ee293fc168f04e0d1e5e24d41c447
--- /dev/null
+++ b/qt/python/mantidqt/utils/qt/plugins.py.in
@@ -0,0 +1,57 @@
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""Defines functions to setup paths to Qt plugins at runtime. This is
+a generated file. Any changes made here will be lost. See
+qt/python/mantidqt/utils/qt/plugins.py.in
+"""
+from __future__ import absolute_import
+
+# stdlib imports
+import os
+
+# 3rd-party imports
+from qtpy import QT_VERSION
+from qtpy.QtCore import QCoreApplication
+
+
+# Qt needs to be able to find its plugins and by default it looks in the
+# directories where they were built. On Linux the libraries are in fixed
+# locations.
+#
+#  On Windows the package can be installed anywhere so the locations must
+#  be determined at runtime. The two options are:
+#    1. using a qt.conf file: requires it to be next to the application
+#       executable but this would be python.exe and then would not allow
+#       switching between Qt4/Qt5
+#    2. add extra paths to the LibraryPath list here
+# As we can dynamically determine the Qt version here we choose option 2.
+
+def setup_library_paths_win():
+    """Adds the build-time configured directory to the Qt library path.
+    The buildsystem generates the path at build time.
+    A %V marker can be used that will be replaced by the major version of Qt
+    at runtime.
+    """
+    QCoreApplication.addLibraryPath(
+        '@QT_PLUGINS_PATH@'.replace('%V', QT_VERSION[0])
+    )
+
+# Set the implementation appropriate for the platform
+if os.name == 'nt':
+    setup_library_paths = setup_library_paths_win
+else:
+    setup_library_paths = lambda: None
diff --git a/qt/python/setup.py b/qt/python/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdfe7f9bd16f6f1e16d2ca037f09879741805526
--- /dev/null
+++ b/qt/python/setup.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+#  This file is part of the mantid workbench.
+#
+#  Copyright (C) 2017 mantidproject
+#
+#  This program is free software: you can redistribute it and/or modify
+#  it under the terms of the GNU General Public License as published by
+#  the Free Software Foundation, either version 3 of the License, or
+#  (at your option) any later version.
+#
+#  This program is distributed in the hope that it will be useful,
+#  but WITHOUT ANY WARRANTY; without even the implied warranty of
+#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#  GNU General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from setuptools import setup
+
+# The most basic setup possible to be able to use setup.py develop
+setup(
+    name="mantidqt",
+)
diff --git a/qt/widgets/common/CMakeLists.txt b/qt/widgets/common/CMakeLists.txt
index 0f6696bbb21bb383a59e7ccd050b2a8083a427d4..32ec0b86f5128e87c6652f7feef8fc1ac53691d8 100644
--- a/qt/widgets/common/CMakeLists.txt
+++ b/qt/widgets/common/CMakeLists.txt
@@ -421,9 +421,25 @@ endif()
 ###########################################################################
 # Target
 ###########################################################################
-if (ENABLE_WORKBENCH)
+if ( ENABLE_WORKBENCH )
+  # Additional components for this module
   find_package ( Qt5 COMPONENTS Concurrent Help Network PrintSupport
-    WebKitWidgets REQUIRED )
+    REQUIRED )
+  # Prefer WebEngineWidgets over WebkitWidgets
+  unset ( Qt5_FOUND )
+  find_package ( Qt5 COMPONENTS WebEngineWidgets QUIET )
+  if ( Qt5_FOUND )
+    set ( _webwidgets_tgt Qt5::WebEngineWidgets )
+  else ()
+    find_package ( Qt5 COMPONENTS WebKitWidgets QUIET )
+    if ( Qt5_FOUND )
+      set ( _webwidgets_tgt Qt5::WebKitWidgets )
+      set ( _webengine_def USE_QTWEBKIT )
+    else ()
+      message ( FATAL_ERROR "Unable to find suitable module for web widgets. Tried: WebEnginewidgets, WebKitWidgets" )
+    endif ()
+  endif ()
+
   find_package ( QScintillaQt5 REQUIRED )
 endif()
 
@@ -433,7 +449,12 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsCommon
   NOMOC ${INC_FILES}
   UI ${UI_FILES}
   DEFS
-    IN_MANTIDQT_COMMON QSCINTILLA_DLL
+    IN_MANTIDQT_COMMON
+    QSCINTILLA_DLL
+  QT4_DEFS
+    USE_QTWEBKIT
+  QT5_DEFS
+    ${_webengine_def}
   INCLUDE_DIRS
     inc
     ${PYTHON_INCLUDE_PATH}
@@ -450,7 +471,7 @@ mtd_add_qt_library (TARGET_NAME MantidQtWidgetsCommon
     Qt5::Help
     Qt5::Network
     Qt5::PrintSupport
-    Qt5::WebKitWidgets
+    ${_webwidgets_tgt}
     Qt5::Qscintilla
   OSX_INSTALL_RPATH
     @loader_path/../MacOS
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/pqHelpWindow.h b/qt/widgets/common/inc/MantidQtWidgets/Common/pqHelpWindow.h
index 94236a6470a372124a90ebdaccf361ca94e1b712..43914261b212705e2591fe7b70867d737f02edec 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/pqHelpWindow.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/pqHelpWindow.h
@@ -35,11 +35,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <QMainWindow>
 #include "DllOption.h"
 
-class HelpBrowser;
 class QHelpEngine;
 class QToolButton;
 class QUrl;
+#if defined(USE_QTWEBKIT)
 class QWebView;
+using QWebEngineView = QWebView;
+#else
+#include <QWebEnginePage>
+class QWebEngineView;
+
+/// Mimic the WebKit class to emit linkClicked signal from the page
+class DelegatingWebPage : public QWebEnginePage {
+  Q_OBJECT
+
+public:
+  DelegatingWebPage(QObject *parent = nullptr) : QWebEnginePage(parent) {}
+
+  bool acceptNavigationRequest(const QUrl &url,
+                               QWebEnginePage::NavigationType type,
+                               bool) override {
+    if (type == QWebEnginePage::NavigationTypeLinkClicked) {
+
+      emit linkClicked(url);
+    }
+    return true;
+  }
+
+signals:
+  void linkClicked(const QUrl &);
+};
+
+#endif
 
 /// pqHelpWindow provides a assistant-like window  for showing help provided by
 /// a QHelpEngine.
@@ -50,7 +77,6 @@ class EXPORT_OPT_MANTIDQT_COMMON pqHelpWindow : public QMainWindow {
 public:
   pqHelpWindow(QHelpEngine *engine, QWidget *parent = 0,
                Qt::WindowFlags flags = 0);
-  ~pqHelpWindow() override;
 
 public slots:
   /// Requests showing of a particular page. The url must begin with "qthelp:"
@@ -72,13 +98,13 @@ signals:
 
 protected slots:
   void search();
-  void linkHovered(const QString &link, const QString &title,
-                   const QString &textContent);
+  void linkHovered(const QString &link, const QString &title = "",
+                   const QString &textContent = "");
   void updateNavButtons();
 
 protected:
   QHelpEngine *m_helpEngine;
-  QWebView *m_browser;
+  QWebEngineView *m_browser;
   QToolButton *m_forward;
   QToolButton *m_backward;
 
diff --git a/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp b/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp
index 77691026f4c28b9810aebffd3b7dc49b84450cec..80229ea10fb1b1f3574b2017140535db88980235 100644
--- a/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp
+++ b/qt/widgets/common/src/QtPropertyBrowser/qttreepropertybrowser.cpp
@@ -131,7 +131,12 @@ QtPropertyEditorView::QtPropertyEditorView(QWidget *parent, bool darkTopLevel)
 void QtPropertyEditorView::drawRow(QPainter *painter,
                                    const QStyleOptionViewItem &option,
                                    const QModelIndex &index) const {
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
   QStyleOptionViewItemV3 opt = option;
+#else
+  QStyleOptionViewItem opt = option;
+#endif
+
   bool hasValue = true;
   if (m_editorPrivate) {
     QtProperty *property = m_editorPrivate->indexToProperty(index);
@@ -308,7 +313,12 @@ void QtPropertyEditorDelegate::paint(QPainter *painter,
     if (property)
       hasValue = property->hasValue();
   }
+
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
   QStyleOptionViewItemV3 opt = option;
+#else
+  QStyleOptionViewItem opt = option;
+#endif
   if ((m_editorPrivate && index.column() == 0) || !hasValue) {
     QtProperty *property = m_editorPrivate->indexToProperty(index);
     if (property && property->isModified()) {
diff --git a/qt/widgets/common/src/ScriptRepositoryView.cpp b/qt/widgets/common/src/ScriptRepositoryView.cpp
index 248dc374fd81498b38c612f036e2bd7ecda27a72..1fdd3e636266c931dd9c704d3744bff625affcb2 100644
--- a/qt/widgets/common/src/ScriptRepositoryView.cpp
+++ b/qt/widgets/common/src/ScriptRepositoryView.cpp
@@ -452,7 +452,11 @@ void ScriptRepositoryView::CheckBoxDelegate::paint(
   if (painter->device() == nullptr)
     return;
 
+#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
   QStyleOptionViewItemV4 modifiedOption(option);
+#else
+  QStyleOptionViewItem modifiedOption(option);
+#endif
 
   QPoint p = modifiedOption.rect.center();
   QSize curr = modifiedOption.rect.size();
diff --git a/qt/widgets/common/src/pqHelpWindow.cxx b/qt/widgets/common/src/pqHelpWindow.cxx
index a19abcca8aa9610a01d5bdb0cd048d3c26eb7f2d..09f29aeda238b2778b1a5de781fc7244dccfc3ee 100644
--- a/qt/widgets/common/src/pqHelpWindow.cxx
+++ b/qt/widgets/common/src/pqHelpWindow.cxx
@@ -51,27 +51,24 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <QToolBar>
 #include <QToolButton>
 #include <QUrl>
-#include <QWebHistory>
-#include <QWebView>
-
-using MantidQt::API::MantidDesktopServices;
 
 // ****************************************************************************
 //            CLASS pqHelpWindowNetworkReply
 // ****************************************************************************
-/// Internal class used to add support to QWebView to load files from
+/// Internal class used to add support to QWeb(Engine)View to load files from
 /// QHelpEngine.
-class pqHelpWindowNetworkReply : public QNetworkReply
-{
+class pqHelpWindowNetworkReply : public QNetworkReply {
   typedef QNetworkReply Superclass;
+
 public:
-  pqHelpWindowNetworkReply(const QUrl& url, QHelpEngineCore* helpEngine);
+  pqHelpWindowNetworkReply(const QUrl &url, QHelpEngineCore *helpEngine,
+                           QObject *parent = nullptr);
 
   void abort() override {}
 
   qint64 bytesAvailable() const override {
     return (this->RawData.size() - this->Offset) +
-        this->Superclass::bytesAvailable();
+           this->Superclass::bytesAvailable();
   }
   bool isSequential() const override { return true; }
 
@@ -80,14 +77,16 @@ protected:
 
   QByteArray RawData;
   qint64 Offset;
+
 private:
   Q_DISABLE_COPY(pqHelpWindowNetworkReply)
 };
 
 //-----------------------------------------------------------------------------
-pqHelpWindowNetworkReply::pqHelpWindowNetworkReply(
-    const QUrl& my_url, QHelpEngineCore* engine) : Superclass(engine), Offset(0)
-{
+pqHelpWindowNetworkReply::pqHelpWindowNetworkReply(const QUrl &my_url,
+                                                   QHelpEngineCore *engine,
+                                                   QObject *parent)
+    : Superclass(parent), Offset(0) {
   Q_ASSERT(engine);
 
   this->RawData = engine->fileData(my_url);
@@ -95,37 +94,34 @@ pqHelpWindowNetworkReply::pqHelpWindowNetworkReply(
   QString content_type = "text/plain";
   QString extension = QFileInfo(my_url.path()).suffix().toLower();
   QMap<QString, QString> extension_type_map;
-  extension_type_map["jpg"]   = "image/jpeg";
-  extension_type_map["jpeg"]  = "image/jpeg";
-  extension_type_map["png"]   = "image/png";
-  extension_type_map["gif"]   = "image/gif";
-  extension_type_map["tiff"]  = "image/tiff";
-  extension_type_map["htm"]   = "text/html";
-  extension_type_map["html"]  = "text/html";
-  extension_type_map["css"]   = "text/css";
-  extension_type_map["xml"]   = "text/xml";
-
-  if (extension_type_map.contains(extension))
-  {
+  extension_type_map["jpg"] = "image/jpeg";
+  extension_type_map["jpeg"] = "image/jpeg";
+  extension_type_map["png"] = "image/png";
+  extension_type_map["gif"] = "image/gif";
+  extension_type_map["tiff"] = "image/tiff";
+  extension_type_map["htm"] = "text/html";
+  extension_type_map["html"] = "text/html";
+  extension_type_map["css"] = "text/css";
+  extension_type_map["xml"] = "text/xml";
+
+  if (extension_type_map.contains(extension)) {
     content_type = extension_type_map[extension];
   }
 
   this->setHeader(QNetworkRequest::ContentLengthHeader,
                   QVariant(this->RawData.size()));
   this->setHeader(QNetworkRequest::ContentTypeHeader, content_type);
-  this->open(QIODevice::ReadOnly|QIODevice::Unbuffered);
+  this->open(QIODevice::ReadOnly | QIODevice::Unbuffered);
   this->setUrl(my_url);
   QTimer::singleShot(0, this, SIGNAL(readyRead()));
   QTimer::singleShot(0, this, SLOT(finished()));
 }
 
 //-----------------------------------------------------------------------------
-qint64 pqHelpWindowNetworkReply::readData(char *data, qint64 maxSize)
-{
-  if (this->Offset <= this->RawData.size())
-  {
-    qint64 end = qMin(this->Offset + maxSize,
-                      static_cast<qint64>(this->RawData.size()));
+qint64 pqHelpWindowNetworkReply::readData(char *data, qint64 maxSize) {
+  if (this->Offset <= this->RawData.size()) {
+    qint64 end =
+        qMin(this->Offset + maxSize, static_cast<qint64>(this->RawData.size()));
     qint64 delta = end - this->Offset;
     memcpy(data, this->RawData.constData() + this->Offset, delta);
     this->Offset += delta;
@@ -134,22 +130,22 @@ qint64 pqHelpWindowNetworkReply::readData(char *data, qint64 maxSize)
   return -1;
 }
 
+#if defined(USE_QTWEBKIT)
+#include <QWebHistory>
+#include <QWebView>
 
 // ****************************************************************************
 //    CLASS pqHelpWindow::pqNetworkAccessManager
 // ****************************************************************************
 //-----------------------------------------------------------------------------
-class pqHelpWindow::pqNetworkAccessManager : public QNetworkAccessManager
-{
+class pqHelpWindow::pqNetworkAccessManager : public QNetworkAccessManager {
   typedef QNetworkAccessManager Superclass;
   QPointer<QHelpEngineCore> Engine;
+
 public:
-  pqNetworkAccessManager(
-    QHelpEngineCore* helpEngine, QNetworkAccessManager *manager,
-    QObject *parentObject) :
-    Superclass(parentObject),
-    Engine(helpEngine)
-  {
+  pqNetworkAccessManager(QHelpEngineCore *helpEngine,
+                         QNetworkAccessManager *manager, QObject *parentObject)
+      : Superclass(parentObject), Engine(helpEngine) {
     Q_ASSERT(manager != NULL && helpEngine != NULL);
 
     this->setCache(manager->cache());
@@ -162,12 +158,9 @@ protected:
   QNetworkReply *createRequest(Operation operation,
                                const QNetworkRequest &request,
                                QIODevice *device) override {
-    if (request.url().scheme() == "qthelp" && operation == GetOperation)
-    {
-      return new pqHelpWindowNetworkReply(request.url(), this->Engine);
-    }
-    else
-    {
+    if (request.url().scheme() == "qthelp" && operation == GetOperation) {
+      return new pqHelpWindowNetworkReply(request.url(), this->Engine, this);
+    } else {
       return this->Superclass::createRequest(operation, request, device);
     }
   }
@@ -176,23 +169,50 @@ private:
   Q_DISABLE_COPY(pqNetworkAccessManager)
 };
 
+#else
+
+#include <QWebEngineHistory>
+#include <QWebEnginePage>
+#include <QWebEngineProfile>
+#include <QWebEngineUrlRequestJob>
+#include <QWebEngineUrlSchemeHandler>
+#include <QWebEngineView>
+
+/// Adds support for qthelp scheme links that load content from them QHelpEngine
+class QtHelpUrlHandler : public QWebEngineUrlSchemeHandler {
+public:
+  QtHelpUrlHandler(QHelpEngineCore *helpEngine, QObject *parent = nullptr)
+      : QWebEngineUrlSchemeHandler(parent), Engine(helpEngine) {}
+
+protected:
+  void requestStarted(QWebEngineUrlRequestJob *request) override {
+    request->reply("text/html",
+                   new pqHelpWindowNetworkReply(request->requestUrl(),
+                                                this->Engine, request));
+  }
+
+private:
+  QHelpEngineCore *Engine;
+};
+
+#endif
+
 // ****************************************************************************
 //            CLASS pqHelpWindow
 // ****************************************************************************
 
 //-----------------------------------------------------------------------------
-pqHelpWindow::pqHelpWindow(
-  QHelpEngine* engine, QWidget* parentObject, Qt::WindowFlags parentFlags)
-  : Superclass(parentObject, parentFlags), m_helpEngine(engine)
-{
+pqHelpWindow::pqHelpWindow(QHelpEngine *engine, QWidget *parentObject,
+                           Qt::WindowFlags parentFlags)
+    : Superclass(parentObject, parentFlags), m_helpEngine(engine) {
   Q_ASSERT(engine != NULL);
 
   Ui::pqHelpWindow ui;
   ui.setupUi(this);
 
   // all warnings from the help engine get logged
-  QObject::connect(this->m_helpEngine, SIGNAL(warning(const QString&)),
-                   this, SIGNAL(helpWarnings(const QString&)));
+  QObject::connect(this->m_helpEngine, SIGNAL(warning(const QString &)), this,
+                   SIGNAL(helpWarnings(const QString &)));
 
   // add a navigation toolbar
   QToolBar *navigation = new QToolBar("Navigation");
@@ -229,13 +249,13 @@ pqHelpWindow::pqHelpWindow(
   ui.contentsDock->raise();
 
   // setup the search tab
-  QWidget* searchPane = new QWidget(this);
-  QVBoxLayout* vbox = new QVBoxLayout();
+  QWidget *searchPane = new QWidget(this);
+  QVBoxLayout *vbox = new QVBoxLayout();
   searchPane->setLayout(vbox);
   vbox->addWidget(this->m_helpEngine->searchEngine()->queryWidget());
   vbox->addWidget(this->m_helpEngine->searchEngine()->resultWidget());
-  connect(this->m_helpEngine->searchEngine()->resultWidget(), SIGNAL(requestShowLink(QUrl)),
-          this, SLOT(showPage(QUrl)));
+  connect(this->m_helpEngine->searchEngine()->resultWidget(),
+          SIGNAL(requestShowLink(QUrl)), this, SLOT(showPage(QUrl)));
 
   // set the search connection
   ui.searchDock->setWidget(searchPane);
@@ -243,50 +263,56 @@ pqHelpWindow::pqHelpWindow(
           this, SLOT(search()));
 
   // connect the index page to the content pane
-  connect(m_helpEngine->contentWidget(), SIGNAL(linkActivated(QUrl)),
-          this, SLOT(showPage(QUrl)));
-  connect(this->m_helpEngine->indexWidget(), SIGNAL(linkActivated(QUrl,QString)),
-          this, SLOT(showPage(QUrl)));
-
-  // setup the content pane
-  this->m_browser = new QWebView(this);
-  m_browser->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
-  this->setCentralWidget(this->m_browser);
+  connect(m_helpEngine->contentWidget(), SIGNAL(linkActivated(QUrl)), this,
+          SLOT(showPage(QUrl)));
+  connect(this->m_helpEngine->indexWidget(),
+          SIGNAL(linkActivated(QUrl, QString)), this, SLOT(showPage(QUrl)));
 
+// setup the content pane
+#if defined(USE_QTWEBKIT)
   QNetworkAccessManager *oldManager = m_browser->page()->networkAccessManager();
-  pqNetworkAccessManager* newManager = new pqNetworkAccessManager(
-        m_helpEngine, oldManager, this);
+  pqNetworkAccessManager *newManager =
+      new pqNetworkAccessManager(m_helpEngine, oldManager, this);
+  m_browser = new QWebView(this);
+  m_browser->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
   m_browser->page()->setNetworkAccessManager(newManager);
   m_browser->page()->setForwardUnsupportedContent(false);
-  connect(this->m_browser, SIGNAL(linkClicked(QUrl)), this, SLOT(showPage(QUrl)));
+  connect(m_browser, SIGNAL(linkClicked(QUrl)), this, SLOT(showPage(QUrl)));
+  // set up the status bar
+  connect(m_browser->page(), SIGNAL(linkHovered(QString, QString, QString)),
+          this, SLOT(linkHovered(QString, QString, QString)));
+#else
+  QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(
+      "qthelp", new QtHelpUrlHandler(engine));
+  m_browser = new QWebEngineView(this);
+  m_browser->setPage(new DelegatingWebPage(m_browser));
+  connect(m_browser->page(), SIGNAL(linkClicked(QUrl)), this,
+          SLOT(showPage(QUrl)));
+  // set up the status bar
+  connect(m_browser->page(), SIGNAL(linkHovered(QString)), this,
+          SLOT(linkHovered(QString)));
+#endif
+  this->setCentralWidget(this->m_browser);
 
   // connect the navigation buttons
   connect(home, SIGNAL(clicked()), this, SLOT(showHomePage()));
   connect(print, SIGNAL(clicked()), this, SLOT(printPage()));
-  connect(m_forward,  SIGNAL(clicked()), m_browser, SLOT(forward()));
+  connect(m_forward, SIGNAL(clicked()), m_browser, SLOT(forward()));
   connect(m_backward, SIGNAL(clicked()), m_browser, SLOT(back()));
-  connect(m_forward,  SIGNAL(clicked()), this, SLOT(updateNavButtons()));
+  connect(m_forward, SIGNAL(clicked()), this, SLOT(updateNavButtons()));
   connect(m_backward, SIGNAL(clicked()), this, SLOT(updateNavButtons()));
 
-  // set up the status bar
-  connect(m_browser->page(), SIGNAL(linkHovered(QString, QString, QString)), this,
-          SLOT(linkHovered(QString, QString, QString)));
-
   // setup the search engine to do its job
   m_helpEngine->searchEngine()->reindexDocumentation();
 }
 
 //-----------------------------------------------------------------------------
-pqHelpWindow::~pqHelpWindow()
-{
-}
 
 /**
  * Set the contents of the browser to show an error message.
  * @param url The url that could not be found.
  */
-void pqHelpWindow::errorMissingPage(const QUrl& url)
-{
+void pqHelpWindow::errorMissingPage(const QUrl &url) {
   QString htmlDoc =
       QString(QLatin1String(
                   "<html><head><title>Invalid Url - %1</title></head><body>"))
@@ -302,24 +328,20 @@ void pqHelpWindow::errorMissingPage(const QUrl& url)
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::showPage(const QString& url)
-{
+void pqHelpWindow::showPage(const QString &url) {
   this->showPage(QUrl::fromUserInput(url));
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::showPage(const QUrl& url)
-{
-  if(url.scheme() == "qthelp")
-  {
+void pqHelpWindow::showPage(const QUrl &url) {
+  if (url.scheme() == "qthelp") {
     if (this->m_helpEngine->findFile(url).isValid())
       this->m_browser->setUrl(url);
     else
       errorMissingPage(url);
     this->updateNavButtons();
-  }
-  else
-  {
+  } else {
+    using MantidQt::API::MantidDesktopServices;
     MantidDesktopServices::openUrl(url);
   }
 }
@@ -331,48 +353,45 @@ void pqHelpWindow::printPage() {
   dialog.setWindowTitle(tr("Print Document"));
   if (dialog.exec() != QDialog::Accepted)
     return;
+#if defined(USE_QTWEBKIT)
   m_browser->print(&printer);
+#else
+  m_browser->page()->print(&printer, [](bool) {});
+#endif
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::updateNavButtons()
-{
+void pqHelpWindow::updateNavButtons() {
   m_forward->setEnabled(m_browser->history()->canGoForward());
   m_backward->setEnabled(m_browser->history()->canGoBack());
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::search()
-{
+void pqHelpWindow::search() {
   QList<QHelpSearchQuery> query =
       this->m_helpEngine->searchEngine()->queryWidget()->query();
   this->m_helpEngine->searchEngine()->search(query);
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::linkHovered(const QString & link, const QString & title,
-                               const QString & textContent)
-{
+void pqHelpWindow::linkHovered(const QString &link, const QString &title,
+                               const QString &textContent) {
   (void)title;
   (void)textContent;
   this->statusBar()->showMessage(link);
 }
 
-void pqHelpWindow::showHomePage()
-{
+void pqHelpWindow::showHomePage() {
   showPage(QString("qthelp://org.mantidproject/doc/index.html"));
 }
 
 //-----------------------------------------------------------------------------
-void pqHelpWindow::showHomePage(const QString& namespace_name)
-{
-  QList<QUrl> html_pages = this->m_helpEngine->files(namespace_name,
-                                                     QStringList(), "html");
+void pqHelpWindow::showHomePage(const QString &namespace_name) {
+  QList<QUrl> html_pages =
+      this->m_helpEngine->files(namespace_name, QStringList(), "html");
   // now try to locate a file named index.html in this collection.
-  foreach (QUrl url, html_pages)
-  {
-    if (url.path().endsWith("index.html"))
-    {
+  foreach (QUrl url, html_pages) {
+    if (url.path().endsWith("index.html")) {
       this->showPage(url.toString());
       return;
     }