From acf1ed3bcb466ddc9bb7bb0404fb23ccab163673 Mon Sep 17 00:00:00 2001
From: Martyn Gigg <martyn.gigg@gmail.com>
Date: Thu, 2 May 2019 08:14:37 +0100
Subject: [PATCH] Ask InterfaceManager for known C++ interfaces in workbench

Populates the Interfaces menu if any exist.
---
 MantidPlot/src/ApplicationWindow.cpp          |  3 +-
 .../workbench/workbench/app/mainwindow.py     | 78 +++++++++++++------
 qt/python/mantidqt/_common.sip                | 26 +++++--
 qt/python/mantidqt/usersubwindowfactory.py    | 15 ++++
 .../Common/UserSubWindowFactory.h             | 47 ++++++++++-
 qt/widgets/common/src/InterfaceManager.cpp    |  2 +-
 .../common/src/UserSubWindowFactory.cpp       | 47 +----------
 7 files changed, 139 insertions(+), 79 deletions(-)
 create mode 100644 qt/python/mantidqt/usersubwindowfactory.py

diff --git a/MantidPlot/src/ApplicationWindow.cpp b/MantidPlot/src/ApplicationWindow.cpp
index 28e05f98eb2..706d97bae07 100644
--- a/MantidPlot/src/ApplicationWindow.cpp
+++ b/MantidPlot/src/ApplicationWindow.cpp
@@ -508,8 +508,7 @@ void ApplicationWindow::init(bool factorySettings, const QStringList &args) {
         qMakePair(userSubWindowName, userSubWindowName));
 
     const QSet<QString> categories =
-        UserSubWindowFactory::Instance().getInterfaceCategories(
-            userSubWindowName);
+        UserSubWindowFactory::Instance().categories(userSubWindowName);
 
     m_interfaceCategories[userSubWindowName] = categories;
     m_allCategories += categories;
diff --git a/qt/applications/workbench/workbench/app/mainwindow.py b/qt/applications/workbench/workbench/app/mainwindow.py
index 7c7aedc2e35..abf4d98d0b6 100644
--- a/qt/applications/workbench/workbench/app/mainwindow.py
+++ b/qt/applications/workbench/workbench/app/mainwindow.py
@@ -52,6 +52,8 @@ from mantidqt.widgets.codeeditor.execution import PythonCodeExecution  # noqa
 from mantidqt.utils.qt import (add_actions, create_action, plugins,
                                widget_updates_disabled)  # noqa
 from mantidqt.project.project import Project  # noqa
+from mantidqt.interfacemanager import InterfaceManager  # noqa
+from mantidqt.usersubwindowfactory import UserSubWindowFactory  # noqa
 
 # Pre-application setup
 plugins.setup_library_paths()
@@ -175,8 +177,9 @@ class MainWindow(QMainWindow):
         self.project = None
         self.project_recovery = None
 
-        # Interface Runner
-        self.executioner = None
+        # Interfaces
+        self.interface_manager = None
+        self.interface_executor = None
 
     def setup(self):
         # menus must be done first so they can be filled by the
@@ -226,6 +229,9 @@ class MainWindow(QMainWindow):
         self.project_recovery = ProjectRecovery(globalfiguremanager=GlobalFigureManager,
                                                 multifileinterpreter=self.editor.editors,
                                                 main_window=self)
+
+        self.interface_executor = PythonCodeExecution()
+        self.interface_executor.sig_exec_error.connect(lambda errobj: logger.warning(str(errobj)))
         self.interface_manager = InterfaceManager()
 
         # uses default configuration as necessary
@@ -234,13 +240,12 @@ class MainWindow(QMainWindow):
 
         self.setup_layout()
         self.create_actions()
-        self.populate_menus()
 
     def post_mantid_init(self):
         """Run any setup that requires mantid
         to have been initialized
         """
-        self.populate_interfaces_menu()
+        self.populate_menus()
         self.algorithm_selector.refresh()
 
         # turn on algorithm factory notifications
@@ -336,18 +341,41 @@ class MainWindow(QMainWindow):
         add_actions(self.file_menu, self.file_menu_actions)
         add_actions(self.view_menu, self.view_menu_actions)
         add_actions(self.help_menu, self.help_menu_actions)
+        self.populate_interfaces_menu()
 
-    def launch_custom_gui(self, filename):
-        if self.executioner is None:
-            self.executioner = PythonCodeExecution()
-            self.executioner.sig_exec_error.connect(lambda errobj: logger.warning(str(errobj)))
-
+    def launch_custom_python_gui(self, filename):
         self.executioner.execute(open(filename).read(), filename)
 
+    def launch_custom_cpp_gui(self, interface_name):
+        interface = self.interface_manager.createSubWindow(interface_name)
+        interface.setAttribute(Qt.WA_DeleteOnClose, True)
+        interface.show()
+
     def populate_interfaces_menu(self):
+        """Populate then Interfaces menu with all Python and C++ interfaces"""
         interface_dir = ConfigService['mantidqt.python_interfaces_directory']
-        items = ConfigService['mantidqt.python_interfaces'].split()
+        interfaces = self._discover_python_interfaces(interface_dir)
+        interfaces.update(self._discover_cpp_interfaces())
 
+        keys = list(interfaces.keys())
+        keys.sort()
+        for key in keys:
+            submenu = self.interfaces_menu.addMenu(key)
+            names = interfaces[key]
+            names.sort()
+            for name in names:
+                if '.py' in name:
+                    action = submenu.addAction(name.replace('.py', '').replace('_', ' '))
+                    script = os.path.join(interface_dir, name)
+                    action.triggered.connect(lambda checked_py, script=script: self.launch_custom_python_gui(script))
+                else:
+                    action = submenu.addAction(name)
+                    action.triggered.connect(lambda checked_cpp, name=name:
+                                             self.launch_custom_cpp_gui(name))
+
+    def _discover_python_interfaces(self, interface_dir):
+        """Return a dictionary mapping a category to a set of named Python interfaces"""
+        items = ConfigService['mantidqt.python_interfaces'].split()
         # list of custom interfaces that are not qt4/qt5 compatible
         GUI_BLACKLIST = ['ISIS_Reflectometry_Old.py',
                          'Frequency_Domain_Analysis_Old.py',
@@ -364,21 +392,23 @@ class MainWindow(QMainWindow):
             if scriptname in GUI_BLACKLIST:
                 logger.information('Not adding gui "{}"'.format(scriptname))
                 continue
-            temp = interfaces.get(key, [])
-            temp.append(scriptname)
-            interfaces[key] = temp
+            interfaces.setdefault(key, []).append(scriptname)
 
-        # add the interfaces to the menu
-        keys = list(interfaces.keys())
-        keys.sort()
-        for key in keys:
-            submenu = self.interfaces_menu.addMenu(key)
-            names = interfaces[key]
-            names.sort()
-            for name in names:
-                action = submenu.addAction(name.replace('.py', '').replace('_', ' '))
-                script = os.path.join(interface_dir, name)
-                action.triggered.connect(lambda checked, script=script: self.launch_custom_gui(script))
+        return interfaces
+
+    def _discover_cpp_interfaces(self):
+        """Return a dictionary mapping a category to a set of named C++ interfaces"""
+        interfaces = {}
+        cpp_interface_factory = UserSubWindowFactory.Instance()
+        interface_names = cpp_interface_factory.keys()
+        for name in interface_names:
+            categories = cpp_interface_factory.categories(name)
+            if len(categories) == 0:
+                categories = ["General"]
+            for category in categories:
+                interfaces.setdefault(category, []).append(name)
+
+        return interfaces
 
     def add_dockwidget(self, plugin):
         """Create a dockwidget around a plugin and add the dock to window"""
diff --git a/qt/python/mantidqt/_common.sip b/qt/python/mantidqt/_common.sip
index dfafd05aa8a..049b1c0327c 100644
--- a/qt/python/mantidqt/_common.sip
+++ b/qt/python/mantidqt/_common.sip
@@ -231,12 +231,28 @@ class UserSubWindow : QMainWindow {
 #include "MantidQtWidgets/Common/UserSubWindow.h"
 using namespace MantidQt::API;
 %End
-public:
+private:
   UserSubWindow(QWidget *parent = nullptr);
-  void setInterfaceName(const QString &iface_name);
-  void initializeLayout();
-protected:
-  virtual void initLayout() = 0;
+};
+
+class UserSubWindowFactoryImpl /PyName=UserSubWindowFactory/ {
+%TypeHeaderCode
+#include "MantidQtWidgets/Common/UserSubWindowFactory.h"
+%End
+public:
+    static SIP_PYOBJECT Instance() const;
+    %MethodCode
+        auto &cppInstance = MantidQt::API::UserSubWindowFactory::Instance();
+        return sipConvertFromType(&cppInstance, sipType_UserSubWindowFactoryImpl, nullptr);
+    %End
+    UserSubWindow *createUnwrapped(const std::string &name) const;
+    QSet<QString> categories(const QString &interfaceName) const;
+    QStringList keys() const;
+
+private:
+  UserSubWindowFactoryImpl();
+  UserSubWindowFactoryImpl(const UserSubWindowFactoryImpl &);
+  ~UserSubWindowFactoryImpl();
 };
 
 
diff --git a/qt/python/mantidqt/usersubwindowfactory.py b/qt/python/mantidqt/usersubwindowfactory.py
new file mode 100644
index 00000000000..ae6ac54b8fa
--- /dev/null
+++ b/qt/python/mantidqt/usersubwindowfactory.py
@@ -0,0 +1,15 @@
+# Mantid Repository : https://github.com/mantidproject/mantid
+#
+# Copyright &copy; 2019 ISIS Rutherford Appleton Laboratory UKRI,
+#     NScD Oak Ridge National Laboratory, European Spallation Source
+#     & Institut Laue - Langevin
+# SPDX - License - Identifier: GPL - 3.0 +
+#  This file is part of the mantidqt package
+#
+#
+from __future__ import (absolute_import)
+
+from mantidqt.utils.qt import import_qt
+
+
+UserSubWindowFactory = import_qt('._common', 'mantidqt', 'UserSubWindowFactory')
diff --git a/qt/widgets/common/inc/MantidQtWidgets/Common/UserSubWindowFactory.h b/qt/widgets/common/inc/MantidQtWidgets/Common/UserSubWindowFactory.h
index 0806051e2be..ef56205dfa1 100644
--- a/qt/widgets/common/inc/MantidQtWidgets/Common/UserSubWindowFactory.h
+++ b/qt/widgets/common/inc/MantidQtWidgets/Common/UserSubWindowFactory.h
@@ -45,9 +45,8 @@ public:
   // Override createUnwrapped to search through the alias list
   UserSubWindow *createUnwrapped(const std::string &name) const override;
 
-  QSet<QString> getInterfaceCategories(const QString &interfaceName) const;
-
-  QStringList getUserSubWindowKeys() const;
+  QSet<QString> categories(const QString &interfaceName) const;
+  QStringList keys() const;
 
   template <typename TYPE> void subscribe();
 
@@ -77,9 +76,51 @@ private:
   QHash<QString, QSet<QString>> m_categoryLookup;
 };
 
+template <typename TYPE> void UserSubWindowFactoryImpl::subscribe() {
+  std::string realName = TYPE::name();
+  Mantid::Kernel::DynamicFactory<UserSubWindow>::subscribe<TYPE>(realName);
+  saveAliasNames<TYPE>(realName);
+
+  // Make a record of each interface's categories.
+  const QStringList categories =
+      TYPE::categoryInfo().split(";", QString::SkipEmptyParts);
+  QSet<QString> result;
+  foreach (const QString category, categories) {
+    result.insert(category.trimmed());
+  }
+  m_categoryLookup[QString::fromStdString(realName)] = result;
+}
+
+/**
+ * Save the alias names of an interface
+ * @param realName :: The real name of the interface
+ */
+template <typename TYPE>
+void UserSubWindowFactoryImpl::saveAliasNames(const std::string &realName) {
+  std::set<std::string> aliases = TYPE::aliases();
+  for (const auto &alias_std_str : aliases) {
+    QString alias = QString::fromStdString(alias_std_str);
+    if (m_aliasLookup.contains(alias)) {
+      if (m_badAliases.contains(alias)) {
+        QList<std::string> names = m_badAliases.value(alias);
+        names.append(realName);
+        m_badAliases[alias] = names;
+      } else {
+        QList<std::string> names;
+        names.append(m_aliasLookup.value(alias));
+        names.append(realName);
+        m_badAliases.insert(alias, names);
+      }
+      continue;
+    }
+    m_aliasLookup.insert(alias, realName);
+  }
+}
+
 /// The specific instantiation of the templated type
 using UserSubWindowFactory =
     Mantid::Kernel::SingletonHolder<UserSubWindowFactoryImpl>;
+
 } // namespace API
 } // namespace MantidQt
 
diff --git a/qt/widgets/common/src/InterfaceManager.cpp b/qt/widgets/common/src/InterfaceManager.cpp
index 1ded3ba974d..494e3cc53bf 100644
--- a/qt/widgets/common/src/InterfaceManager.cpp
+++ b/qt/widgets/common/src/InterfaceManager.cpp
@@ -176,7 +176,7 @@ UserSubWindow *InterfaceManager::createSubWindow(const QString &interface_name,
  * refer to UserSubWindow classes
  */
 QStringList InterfaceManager::getUserSubWindowKeys() const {
-  return UserSubWindowFactory::Instance().getUserSubWindowKeys();
+  return UserSubWindowFactory::Instance().keys();
 }
 
 //----------------------------------
diff --git a/qt/widgets/common/src/UserSubWindowFactory.cpp b/qt/widgets/common/src/UserSubWindowFactory.cpp
index ee29cd9db8a..da47021dcda 100644
--- a/qt/widgets/common/src/UserSubWindowFactory.cpp
+++ b/qt/widgets/common/src/UserSubWindowFactory.cpp
@@ -68,8 +68,8 @@ UserSubWindowFactoryImpl::createUnwrapped(const std::string &name) const {
  *been registered,
  *          else an empty set.
  */
-QSet<QString> UserSubWindowFactoryImpl::getInterfaceCategories(
-    const QString &interfaceName) const {
+QSet<QString>
+UserSubWindowFactoryImpl::categories(const QString &interfaceName) const {
   if (!m_categoryLookup.contains(interfaceName))
     return QSet<QString>();
 
@@ -114,53 +114,12 @@ UserSubWindowFactoryImpl::createFromAlias(const std::string &name) const {
   }
 }
 
-template <typename TYPE> void UserSubWindowFactoryImpl::subscribe() {
-  std::string realName = TYPE::name();
-  Mantid::Kernel::DynamicFactory<UserSubWindow>::subscribe<TYPE>(realName);
-  saveAliasNames<TYPE>(realName);
-
-  // Make a record of each interface's categories.
-  const QStringList categories =
-      TYPE::categoryInfo().split(";", QString::SkipEmptyParts);
-  QSet<QString> result;
-  foreach (const QString category, categories) {
-    result.insert(category.trimmed());
-  }
-  m_categoryLookup[QString::fromStdString(realName)] = result;
-}
-
-/**
- * Save the alias names of an interface
- * @param realName :: The real name of the interface
- */
-template <typename TYPE>
-void UserSubWindowFactoryImpl::saveAliasNames(const std::string &realName) {
-  std::set<std::string> aliases = TYPE::aliases();
-  for (const auto &alias_std_str : aliases) {
-    QString alias = QString::fromStdString(alias_std_str);
-    if (m_aliasLookup.contains(alias)) {
-      if (m_badAliases.contains(alias)) {
-        QList<std::string> names = m_badAliases.value(alias);
-        names.append(realName);
-        m_badAliases[alias] = names;
-      } else {
-        QList<std::string> names;
-        names.append(m_aliasLookup.value(alias));
-        names.append(realName);
-        m_badAliases.insert(alias, names);
-      }
-      continue;
-    }
-    m_aliasLookup.insert(alias, realName);
-  }
-}
-
 /**
  * The keys associated with UserSubWindow classes
  * @returns A QStringList containing the keys from the UserSubWindowFactory that
  * refer to UserSubWindow classes
  */
-QStringList UserSubWindowFactoryImpl::getUserSubWindowKeys() const {
+QStringList UserSubWindowFactoryImpl::keys() const {
   QStringList key_list;
   const auto keys = getKeys();
   for (const auto &key : keys) {
-- 
GitLab