Переглянути джерело

Merge branch 'cli-module-support'

* cli-module-support:
  Fixed highlighting of progress widgets in case of deleted widgets.
  Add settings for maximum parallel running modules.
  Change frontend tab when clicking on a progress widget.
  Further improved module registration error handling.
  Do not complain about paths which have already been added to the widget.
  Added a warning about the usage of ctkCmdLineModuleFutureWatcher.
  Improved handling of invalide modules. Added filter box and load menu.
  Added getter for the standard item for further widget customization.
  Added a getter for the ModuleManager validation mode.
  Do not create invalid module references when querying the manager.
  Removed debug output.
  Improved invalid module handling. Added module filter capabilities.
  Fixed URL to schema file.
  Optimized exception throwing for invalid XML descriptions.
  Always cache the XML output of a module.
  Use weak validation mode and display error messages in tooltip.
  Improved progress widget handling.
Sascha Zelzer 12 роки тому
батько
коміт
7636c10d3c
38 змінених файлів з 1323 додано та 130 видалено
  1. 9 0
      Applications/ctkCommandLineModuleExplorer/CMakeLists.txt
  2. 2 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.cpp
  3. 2 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.h
  4. 2 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerDirectorySettings.h
  5. 40 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.cpp
  6. 42 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.h
  7. 97 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.ui
  8. 62 27
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp
  9. 1 2
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.h
  10. 116 20
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.ui
  11. 68 2
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.cpp
  12. 11 1
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.h
  13. 134 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressListWidget.cpp
  14. 67 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressListWidget.h
  15. 30 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.cpp
  16. 15 1
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.h
  17. 40 1
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.ui
  18. 89 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerShowXmlAction.cpp
  19. 49 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerShowXmlAction.h
  20. 5 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.cpp
  21. 2 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.h
  22. 182 59
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTreeWidget.cpp
  23. 36 6
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTreeWidget.h
  24. 68 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.cpp
  25. 40 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.h
  26. BIN
      Applications/ctkCommandLineModuleExplorer/resources/clear.png
  27. 1 0
      Applications/ctkCommandLineModuleExplorer/resources/ctkCmdLineModuleExplorer.qrc
  28. 1 1
      Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.cpp
  29. 10 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.h
  30. 10 3
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp
  31. 6 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.h
  32. 23 2
      Libs/CommandLineModules/Core/ctkCmdLineModuleReference.cpp
  33. 1 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleReference.h
  34. 3 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleReference_p.h
  35. 1 1
      Libs/CommandLineModules/README.md
  36. 7 4
      Libs/Widgets/ctkPathListButtonsWidget.cpp
  37. 36 0
      Libs/Widgets/ctkPathListWidget.cpp
  38. 15 0
      Libs/Widgets/ctkPathListWidget.h

+ 9 - 0
Applications/ctkCommandLineModuleExplorer/CMakeLists.txt

@@ -8,21 +8,29 @@ set(KIT_SRCS
   ctkCommandLineModuleExplorerMain.cpp
   ctkCmdLineModuleExplorerConstants.cpp
   ctkCmdLineModuleExplorerDirectorySettings.cpp
+  ctkCmdLineModuleExplorerGeneralModuleSettings.cpp
   ctkCmdLineModuleExplorerMainWindow.h
   ctkCmdLineModuleExplorerMainWindow.cpp
   ctkCmdLineModuleExplorerModulesSettings.cpp
   ctkCmdLineModuleExplorerOutputText.cpp
+  ctkCmdLineModuleExplorerProgressListWidget.cpp
   ctkCmdLineModuleExplorerProgressWidget.cpp
+  ctkCmdLineModuleExplorerShowXmlAction.cpp
   ctkCmdLineModuleExplorerTabList.cpp
   ctkCmdLineModuleExplorerTreeWidget.cpp
+  ctkCmdLineModuleExplorerUtils.cpp
 )
 
 # Headers that should run through moc
 set(KIT_MOC_SRCS
+  ctkCmdLineModuleExplorerDirectorySettings.h
+  ctkCmdLineModuleExplorerGeneralModuleSettings.h
   ctkCmdLineModuleExplorerMainWindow.h
   ctkCmdLineModuleExplorerModulesSettings.h
   ctkCmdLineModuleExplorerOutputText.h
+  ctkCmdLineModuleExplorerProgressListWidget.h
   ctkCmdLineModuleExplorerProgressWidget.h
+  ctkCmdLineModuleExplorerShowXmlAction.h
   ctkCmdLineModuleExplorerTabList.h
   ctkCmdLineModuleExplorerTreeWidget.h
 )
@@ -30,6 +38,7 @@ set(KIT_MOC_SRCS
 # UI files
 set(KIT_UI_FORMS
   ctkCmdLineModuleExplorerDirectorySettings.ui
+  ctkCmdLineModuleExplorerGeneralModuleSettings.ui
   ctkCmdLineModuleExplorerMainWindow.ui
   ctkCmdLineModuleExplorerModulesSettings.ui
   ctkCmdLineModuleExplorerProgressWidget.ui

+ 2 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.cpp

@@ -23,3 +23,5 @@
 
 const QString ctkCmdLineModuleExplorerConstants::KEY_SEARCH_PATHS = "ModuleSearchPaths";
 const QString ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES = "RegisteredModules";
+
+const QString ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES = "MaxParallelModules";

+ 2 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.h

@@ -28,6 +28,8 @@ struct ctkCmdLineModuleExplorerConstants
 {
   static const QString KEY_SEARCH_PATHS;
   static const QString KEY_REGISTERED_MODULES;
+
+  static const QString KEY_MAX_PARALLEL_MODULES;
 };
 
 #endif // CTKCMDLINEMODULEEXPLORERCONSTANTS_H

+ 2 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerDirectorySettings.h

@@ -34,6 +34,8 @@ class ctkCmdLineModuleDirectoryWatcher;
  */
 class ctkCmdLineModuleExplorerDirectorySettings : public ctkSettingsPanel, public Ui::ctkCmdLineModuleExplorerDirectorySettings
 {
+  Q_OBJECT
+
 public:
   ctkCmdLineModuleExplorerDirectorySettings(ctkCmdLineModuleDirectoryWatcher* directoryWatcher);
 

+ 40 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.cpp

@@ -0,0 +1,40 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include "ctkCmdLineModuleExplorerGeneralModuleSettings.h"
+#include "ctkCmdLineModuleExplorerConstants.h"
+
+#include <QThreadPool>
+#include <QSettings>
+
+ctkCmdLineModuleExplorerGeneralModuleSettings::ctkCmdLineModuleExplorerGeneralModuleSettings()
+{
+  this->setupUi(this);
+
+  this->registerProperty(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES,
+                         this->MaxParallelModules, "value", SIGNAL(valueChanged(int)));
+}
+
+void ctkCmdLineModuleExplorerGeneralModuleSettings::applySettings()
+{
+  int maxParallelModules = this->propertyValue(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES).toInt();
+  QThreadPool::globalInstance()->setMaxThreadCount(maxParallelModules);
+}

+ 42 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.h

@@ -0,0 +1,42 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef CTKCMDLINEMODULEEXPLORERGENERALMODULESETTINGS_H
+#define CTKCMDLINEMODULEEXPLORERGENERALMODULESETTINGS_H
+
+#include <ctkSettingsPanel.h>
+
+#include "ui_ctkCmdLineModuleExplorerGeneralModuleSettings.h"
+
+class ctkCmdLineModuleExplorerGeneralModuleSettings : public ctkSettingsPanel,
+    public Ui::ctkCmdLineModuleExplorerGeneralModuleSettings
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleExplorerGeneralModuleSettings();
+
+  void applySettings();
+
+};
+
+#endif // CTKCMDLINEMODULEEXPLORERGENERALMODULESETTINGS_H

+ 97 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.ui

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ctkCmdLineModuleExplorerGeneralModuleSettings</class>
+ <widget class="QWidget" name="ctkCmdLineModuleExplorerGeneralModuleSettings">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>400</width>
+    <height>300</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Module Settings            </string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="leftMargin">
+    <number>6</number>
+   </property>
+   <property name="topMargin">
+    <number>0</number>
+   </property>
+   <property name="rightMargin">
+    <number>0</number>
+   </property>
+   <property name="bottomMargin">
+    <number>0</number>
+   </property>
+   <item>
+    <widget class="QGroupBox" name="groupBox">
+     <property name="title">
+      <string>Run Settings</string>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <item row="1" column="0">
+       <spacer name="verticalSpacer">
+        <property name="orientation">
+         <enum>Qt::Vertical</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>40</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="0" column="1">
+       <widget class="QSpinBox" name="MaxParallelModules">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="maximumSize">
+         <size>
+          <width>60</width>
+          <height>16777215</height>
+         </size>
+        </property>
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>999</number>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="0">
+       <widget class="QLabel" name="label">
+        <property name="text">
+         <string>Maximum parallel running modules:</string>
+        </property>
+       </widget>
+      </item>
+      <item row="0" column="2">
+       <spacer name="horizontalSpacer">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 62 - 27
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp

@@ -22,11 +22,13 @@
 #include "ctkCmdLineModuleExplorerMainWindow.h"
 #include "ui_ctkCmdLineModuleExplorerMainWindow.h"
 
+#include "ctkCmdLineModuleExplorerGeneralModuleSettings.h"
 #include "ctkCmdLineModuleExplorerDirectorySettings.h"
 #include "ctkCmdLineModuleExplorerModulesSettings.h"
 #include "ctkCmdLineModuleExplorerTabList.h"
 #include "ctkCmdLineModuleExplorerProgressWidget.h"
 #include "ctkCmdLineModuleExplorerConstants.h"
+#include "ctkCmdLineModuleExplorerUtils.h"
 
 #include <ctkCmdLineModuleManager.h>
 #include <ctkCmdLineModuleConcurrentHelpers.h>
@@ -36,6 +38,7 @@
 #include <ctkCmdLineModuleBackendLocalProcess.h>
 #include <ctkCmdLineModuleBackendFunctionPointer.h>
 #include <ctkException.h>
+#include <ctkCmdLineModuleXmlException.h>
 
 #include <ctkSettingsDialog.h>
 
@@ -44,26 +47,36 @@
 #include <QMessageBox>
 #include <QFutureSynchronizer>
 #include <QCloseEvent>
-#include <QDebug>
+#include <QFileDialog>
 
 
 ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   QMainWindow(parent),
   ui(new Ui::ctkCmdLineModuleExplorerMainWindow),
   defaultModuleFrontendFactory(NULL),
-  moduleManager(ctkCmdLineModuleManager::STRICT_VALIDATION, QDesktopServices::storageLocation(QDesktopServices::CacheLocation)),
-  directoryWatcher(&moduleManager)
+  moduleManager(ctkCmdLineModuleManager::WEAK_VALIDATION, QDesktopServices::storageLocation(QDesktopServices::CacheLocation)),
+  directoryWatcher(&moduleManager),
+  settingsDialog(NULL)
 {
   ui->setupUi(this);
 
   settings.restoreState(this->objectName(), *this);
 
+  if (!settings.contains(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES))
+  {
+    settings.setValue(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES, QThread::idealThreadCount());
+  }
+  QThreadPool::globalInstance()->setMaxThreadCount(settings.value(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES,
+                                                                  QThread::idealThreadCount()).toInt());
+
   // Frontends
   moduleFrontendFactories << new ctkCmdLineModuleFrontendFactoryQtGui;
   moduleFrontendFactories << new ctkCmdLineModuleFrontendFactoryQtWebKit;
   defaultModuleFrontendFactory = moduleFrontendFactories.front();
 
-  ui->modulesTreeWidget->setModuleFrontendFactories(moduleFrontendFactories);
+  ui->modulesTreeWidget->setModuleFrontendFactories(moduleFrontendFactories, moduleFrontendFactories.front());
+  ui->modulesSearchBox->setClearIcon(QIcon(":/icons/clear.png"));
+  connect(ui->modulesSearchBox, SIGNAL(textChanged(QString)), ui->modulesTreeWidget, SLOT(setFilter(QString)));
 
   // Backends
   ctkCmdLineModuleBackendFunctionPointer* backendFunctionPointer = new ctkCmdLineModuleBackendFunctionPointer;
@@ -75,24 +88,22 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
     moduleManager.registerBackend(moduleBackends[i]);
   }
 
-  settingsDialog = new ctkSettingsDialog(this);
-  settings.restoreState(settingsDialog->objectName(), *settingsDialog);
-  settingsDialog->setSettings(&settings);
-  settingsDialog->addPanel(new ctkCmdLineModuleExplorerDirectorySettings(&directoryWatcher));
-  settingsDialog->addPanel(new ctkCmdLineModuleExplorerModulesSettings(&moduleManager));
-
   tabList.reset(new ctkCmdLineModuleExplorerTabList(ui->mainTabWidget));
 
-  // If a module is registered via the ModuleManager, add it the tree
+  // If a module is registered via the ModuleManager, add it to the tree
   connect(&moduleManager, SIGNAL(moduleRegistered(ctkCmdLineModuleReference)), ui->modulesTreeWidget, SLOT(addModuleItem(ctkCmdLineModuleReference)));
   connect(&moduleManager, SIGNAL(moduleUnregistered(ctkCmdLineModuleReference)), ui->modulesTreeWidget, SLOT(removeModuleItem(ctkCmdLineModuleReference)));
-  // Double-clicking on an item in the tree creates a new tab with the default frontend
-  connect(ui->modulesTreeWidget, SIGNAL(moduleDoubleClicked(ctkCmdLineModuleReference)), this, SLOT(addModuleTab(ctkCmdLineModuleReference)));
   // React to specific frontend creations
   connect(ui->modulesTreeWidget, SIGNAL(moduleFrontendCreated(ctkCmdLineModuleFrontend*)), tabList.data(), SLOT(addTab(ctkCmdLineModuleFrontend*)));
   // React to tab-changes
   connect(tabList.data(), SIGNAL(tabActivated(ctkCmdLineModuleFrontend*)), SLOT(moduleTabActivated(ctkCmdLineModuleFrontend*)));
+  connect(tabList.data(), SIGNAL(tabActivated(ctkCmdLineModuleFrontend*)), ui->progressListWidget, SLOT(setCurrentProgressWidget(ctkCmdLineModuleFrontend*)));
   connect(tabList.data(), SIGNAL(tabClosed(ctkCmdLineModuleFrontend*)), ui->outputText, SLOT(frontendRemoved(ctkCmdLineModuleFrontend*)));
+  connect(tabList.data(), SIGNAL(tabClosed(ctkCmdLineModuleFrontend*)), ui->progressListWidget, SLOT(removeProgressWidget(ctkCmdLineModuleFrontend*)));
+
+  connect(ui->progressListWidget, SIGNAL(progressWidgetClicked(ctkCmdLineModuleFrontend*)), tabList.data(), SLOT(setActiveTab(ctkCmdLineModuleFrontend*)));
+
+  connect(ui->ClearButton, SIGNAL(clicked()), ui->progressListWidget, SLOT(clearList()));
 
   // Listen to future events for the currently active tab
 
@@ -112,8 +123,8 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   }
 
   // Register persistent modules
-  QtConcurrent::mapped(settings.value(ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES).toStringList(),
-                       ctkCmdLineModuleConcurrentRegister(&moduleManager, true));
+  QFuture<void> future = QtConcurrent::mapped(settings.value(ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES).toStringList(),
+                                              ctkCmdLineModuleConcurrentRegister(&moduleManager, true));
 
   // Start watching directories
   directoryWatcher.setDebug(true);
@@ -122,6 +133,8 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   moduleTabActivated(NULL);
 
   pollPauseTimer.start();
+
+  future.waitForFinished();
 }
 
 ctkCLModuleExplorerMainWindow::~ctkCLModuleExplorerMainWindow()
@@ -130,7 +143,10 @@ ctkCLModuleExplorerMainWindow::~ctkCLModuleExplorerMainWindow()
   qDeleteAll(moduleFrontendFactories);
 
   settings.saveState(*this, this->objectName());
-  settings.saveState(*settingsDialog, settingsDialog->objectName());
+  if (settingsDialog)
+  {
+    settings.saveState(*settingsDialog, settingsDialog->objectName());
+  }
 }
 
 void ctkCLModuleExplorerMainWindow::addModule(const QUrl &location)
@@ -152,7 +168,7 @@ void ctkCLModuleExplorerMainWindow::closeEvent(QCloseEvent *event)
   if (!runningFrontends.empty())
   {
     QMessageBox::StandardButton button =
-        QMessageBox::warning(QApplication::topLevelWidgets().front(),
+        QMessageBox::warning(QApplication::activeWindow(),
                              QString("Closing %1 running modules").arg(runningFrontends.size()),
                              "Some modules are still running.\n"
                              "Closing the application will cancel all current computations.",
@@ -187,19 +203,18 @@ void ctkCLModuleExplorerMainWindow::on_actionRun_triggered()
   ctkCmdLineModuleFrontend* moduleFrontend = this->tabList->activeTab();
   Q_ASSERT(moduleFrontend);
 
-  ctkCmdLineModuleExplorerProgressWidget* progressWidget = new ctkCmdLineModuleExplorerProgressWidget();
-  this->ui->progressInfoWidget->layout()->addWidget(progressWidget);
-
   ui->actionRun->setEnabled(false);
   qobject_cast<QWidget*>(moduleFrontend->guiHandle())->setEnabled(false);
 
   ctkCmdLineModuleFuture future = moduleManager.run(moduleFrontend);
 
+  ui->progressListWidget->addProgressWidget(moduleFrontend, future);
+  ui->progressListWidget->setCurrentProgressWidget(moduleFrontend);
+
   ui->actionPause->setEnabled(future.canPause() && future.isRunning());
   ui->actionPause->setChecked(future.isPaused());
   ui->actionCancel->setEnabled(future.canCancel() && future.isRunning());
 
-  progressWidget->setFuture(future);
   this->currentFutureWatcher.setFuture(future);
 }
 
@@ -215,9 +230,33 @@ void ctkCLModuleExplorerMainWindow::on_actionCancel_triggered()
 
 void ctkCLModuleExplorerMainWindow::on_actionOptions_triggered()
 {
+  if (settingsDialog == NULL)
+  {
+    settingsDialog = new ctkSettingsDialog(this);
+    settings.restoreState(settingsDialog->objectName(), *settingsDialog);
+    settingsDialog->setSettings(&settings);
+    ctkSettingsPanel* generalModulePanel = new ctkCmdLineModuleExplorerGeneralModuleSettings();
+    settingsDialog->addPanel(generalModulePanel);
+    settingsDialog->addPanel(new ctkCmdLineModuleExplorerDirectorySettings(&directoryWatcher), generalModulePanel);
+    settingsDialog->addPanel(new ctkCmdLineModuleExplorerModulesSettings(&moduleManager), generalModulePanel);
+  }
+
   settingsDialog->exec();
 }
 
+void ctkCLModuleExplorerMainWindow::on_actionLoad_triggered()
+{
+  QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Load modules..."));
+
+  this->setCursor(Qt::BusyCursor);
+  QFuture<ctkCmdLineModuleReference> future = QtConcurrent::mapped(fileNames, ctkCmdLineModuleConcurrentRegister(&this->moduleManager));
+  future.waitForFinished();
+  this->unsetCursor();
+
+  ctkCmdLineModuleExplorerUtils::messageBoxModuleRegistration(fileNames, future.results(),
+                                                              this->moduleManager.validationMode());
+}
+
 void ctkCLModuleExplorerMainWindow::on_actionQuit_triggered()
 {
   this->close();
@@ -298,16 +337,12 @@ void ctkCLModuleExplorerMainWindow::moduleTabActivated(ctkCmdLineModuleFrontend
   {
     ui->actionRun->setEnabled(!module->isRunning());
     ui->actionPause->setEnabled(module->future().canPause() && module->isRunning());
+    ui->actionPause->blockSignals(true);
     ui->actionPause->setChecked(module->isPaused());
+    ui->actionPause->blockSignals(false);
     ui->actionCancel->setEnabled(module->future().canCancel() && module->isRunning());
     ui->actionReset->setEnabled(true);
     ui->outputText->setActiveFrontend(module);
     currentFutureWatcher.setFuture(module->future());
   }
 }
-
-void ctkCLModuleExplorerMainWindow::addModuleTab(const ctkCmdLineModuleReference &moduleRef)
-{
-  ctkCmdLineModuleFrontend* moduleFrontend = this->defaultModuleFrontendFactory->create(moduleRef);
-  this->tabList->addTab(moduleFrontend);
-}

+ 1 - 2
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.h

@@ -64,6 +64,7 @@ protected Q_SLOTS:
   void on_actionPause_toggled(bool toggled);
   void on_actionCancel_triggered();
   void on_actionOptions_triggered();
+  void on_actionLoad_triggered();
   void on_actionQuit_triggered();
 
   void on_actionReset_triggered();
@@ -74,8 +75,6 @@ protected Q_SLOTS:
   void currentModuleFinished();
 
   void moduleTabActivated(ctkCmdLineModuleFrontend* module);
-
-  void addModuleTab(const ctkCmdLineModuleReference& moduleRef);
   
 private:
 

+ 116 - 20
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.ui

@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>800</width>
-    <height>600</height>
+    <width>1126</width>
+    <height>933</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -44,7 +44,7 @@
     <rect>
      <x>0</x>
      <y>0</y>
-     <width>800</width>
+     <width>1126</width>
      <height>25</height>
     </rect>
    </property>
@@ -99,19 +99,29 @@
    </attribute>
    <widget class="QWidget" name="dockWidgetContents">
     <layout class="QVBoxLayout" name="verticalLayout">
+     <property name="spacing">
+      <number>2</number>
+     </property>
      <property name="margin">
       <number>0</number>
      </property>
      <item>
+      <widget class="ctkSearchBox" name="modulesSearchBox"/>
+     </item>
+     <item>
       <widget class="ctkCmdLineModuleExplorerTreeWidget" name="modulesTreeWidget">
-       <attribute name="headerVisible">
-        <bool>false</bool>
-       </attribute>
-       <column>
-        <property name="text">
-         <string notr="true">1</string>
-        </property>
-       </column>
+       <property name="editTriggers">
+        <set>QAbstractItemView::NoEditTriggers</set>
+       </property>
+       <property name="textElideMode">
+        <enum>Qt::ElideMiddle</enum>
+       </property>
+       <property name="sortingEnabled">
+        <bool>true</bool>
+       </property>
+       <property name="headerHidden">
+        <bool>true</bool>
+       </property>
       </widget>
      </item>
     </layout>
@@ -136,23 +146,98 @@
       <number>0</number>
      </property>
      <item>
-      <widget class="QWidget" name="progressInfoWidget" native="true">
+      <widget class="QWidget" name="progressContainer" native="true">
        <layout class="QVBoxLayout" name="verticalLayout_4">
         <property name="spacing">
-         <number>4</number>
+         <number>2</number>
         </property>
         <property name="leftMargin">
-         <number>0</number>
+         <number>6</number>
         </property>
         <property name="topMargin">
-         <number>4</number>
+         <number>0</number>
         </property>
         <property name="rightMargin">
          <number>0</number>
         </property>
         <property name="bottomMargin">
-         <number>4</number>
+         <number>0</number>
         </property>
+        <item>
+         <layout class="QHBoxLayout" name="horizontalLayout_2">
+          <item>
+           <spacer name="horizontalSpacer">
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>40</width>
+              <height>20</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+          <item>
+           <widget class="QPushButton" name="ClearButton">
+            <property name="text">
+             <string>Clear</string>
+            </property>
+           </widget>
+          </item>
+         </layout>
+        </item>
+        <item>
+         <widget class="QScrollArea" name="scrollArea">
+          <property name="frameShape">
+           <enum>QFrame::NoFrame</enum>
+          </property>
+          <property name="widgetResizable">
+           <bool>true</bool>
+          </property>
+          <widget class="QWidget" name="scrollAreaContent">
+           <property name="geometry">
+            <rect>
+             <x>0</x>
+             <y>0</y>
+             <width>250</width>
+             <height>194</height>
+            </rect>
+           </property>
+           <layout class="QVBoxLayout" name="verticalLayout_5">
+            <property name="leftMargin">
+             <number>0</number>
+            </property>
+            <property name="rightMargin">
+             <number>0</number>
+            </property>
+            <item>
+             <widget class="Line" name="line">
+              <property name="orientation">
+               <enum>Qt::Horizontal</enum>
+              </property>
+             </widget>
+            </item>
+            <item>
+             <widget class="ctkCmdLineModuleExplorerProgressListWidget" name="progressListWidget" native="true"/>
+            </item>
+            <item>
+             <spacer name="verticalSpacer">
+              <property name="orientation">
+               <enum>Qt::Vertical</enum>
+              </property>
+              <property name="sizeHint" stdset="0">
+               <size>
+                <width>20</width>
+                <height>64</height>
+               </size>
+              </property>
+             </spacer>
+            </item>
+           </layout>
+          </widget>
+         </widget>
+        </item>
        </layout>
       </widget>
      </item>
@@ -219,7 +304,7 @@
   </action>
   <action name="actionLoad">
    <property name="text">
-    <string>&amp;Load Module...</string>
+    <string>&amp;Load Modules...</string>
    </property>
    <property name="toolTip">
     <string>Load module</string>
@@ -267,15 +352,26 @@
  </widget>
  <customwidgets>
   <customwidget>
-   <class>ctkCmdLineModuleExplorerTreeWidget</class>
-   <extends>QTreeWidget</extends>
-   <header>ctkCmdLineModuleExplorerTreeWidget.h</header>
+   <class>ctkSearchBox</class>
+   <extends>QLineEdit</extends>
+   <header>ctkSearchBox.h</header>
   </customwidget>
   <customwidget>
    <class>ctkCmdLineModuleExplorerOutputText</class>
    <extends>QTextEdit</extends>
    <header>ctkCmdLineModuleExplorerOutputText.h</header>
   </customwidget>
+  <customwidget>
+   <class>ctkCmdLineModuleExplorerProgressListWidget</class>
+   <extends>QWidget</extends>
+   <header>ctkCmdLineModuleExplorerProgressListWidget.h</header>
+   <container>1</container>
+  </customwidget>
+  <customwidget>
+   <class>ctkCmdLineModuleExplorerTreeWidget</class>
+   <extends>QTreeView</extends>
+   <header>ctkCmdLineModuleExplorerTreeWidget.h</header>
+  </customwidget>
  </customwidgets>
  <resources>
   <include location="resources/ctkCmdLineModuleExplorer.qrc"/>

+ 68 - 2
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.cpp

@@ -21,6 +21,9 @@
 
 #include "ctkCmdLineModuleExplorerModulesSettings.h"
 #include "ctkCmdLineModuleExplorerConstants.h"
+#include "ctkCmdLineModuleExplorerUtils.h"
+#include "ctkCmdLineModuleExplorerShowXmlAction.h"
+#include "ctkCmdLineModuleExplorerUtils.h"
 
 #include "ui_ctkCmdLineModuleExplorerModulesSettings.h"
 
@@ -28,15 +31,25 @@
 #include <ctkCmdLineModuleConcurrentHelpers.h>
 
 #include <QUrl>
+#include <QStandardItem>
 #include <QtConcurrentMap>
+#include <QFutureSynchronizer>
 
 ctkCmdLineModuleExplorerModulesSettings::ctkCmdLineModuleExplorerModulesSettings(ctkCmdLineModuleManager *moduleManager)
   : ui(new Ui::ctkCmdLineModuleExplorerModulesSettings)
   , ModuleManager(moduleManager)
+  , ShowXmlAction(new ctkCmdLineModuleExplorerShowXmlAction(this))
 {
   ui->setupUi(this);
 
   ui->PathListButtonsWidget->init(ui->PathListWidget);
+  ui->PathListWidget->addAction(this->ShowXmlAction);
+  ui->PathListWidget->setContextMenuPolicy(Qt::ActionsContextMenu);
+
+  this->ShowXmlAction->setEnabled(false);
+
+  connect(ui->PathListWidget, SIGNAL(currentPathChanged(QString,QString)), SLOT(pathSelected(QString)));
+  connect(ui->PathListWidget, SIGNAL(pathsChanged(QStringList,QStringList)), SLOT(pathsAdded(QStringList)));
 
   this->registerProperty(ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES,
                          ui->PathListWidget, "paths", SIGNAL(pathsChanged(QStringList,QStringList)));
@@ -66,8 +79,61 @@ void ctkCmdLineModuleExplorerModulesSettings::applySettings()
     }
   }
 
-  QtConcurrent::mapped(removedModules, ctkCmdLineModuleConcurrentUnRegister(this->ModuleManager));
-  QtConcurrent::mapped(addedModules, ctkCmdLineModuleConcurrentRegister(this->ModuleManager));
+  this->setCursor(Qt::BusyCursor);
+
+  QFuture<void> future1 = QtConcurrent::mapped(removedModules, ctkCmdLineModuleConcurrentUnRegister(this->ModuleManager));
+  QFuture<ctkCmdLineModuleReference> future2 = QtConcurrent::mapped(addedModules, ctkCmdLineModuleConcurrentRegister(this->ModuleManager));
 
   ctkSettingsPanel::applySettings();
+
+  QFutureSynchronizer<void> sync;
+  sync.addFuture(future1);
+  sync.addFuture(future2);
+  sync.waitForFinished();
+
+  this->pathsAdded(addedModules);
+
+  this->unsetCursor();
+
+  ctkCmdLineModuleExplorerUtils::messageBoxModuleRegistration(addedModules, future2.results(),
+                                                              this->ModuleManager->validationMode());
+
+}
+
+void ctkCmdLineModuleExplorerModulesSettings::pathSelected(const QString &path)
+{
+  this->ShowXmlAction->setEnabled(!path.isEmpty());
+  ctkCmdLineModuleReference moduleRef = this->ModuleManager->moduleReference(QUrl::fromLocalFile(path));
+  this->ShowXmlAction->setModuleReference(moduleRef);
+}
+
+void ctkCmdLineModuleExplorerModulesSettings::pathsAdded(const QStringList &paths)
+{
+  // Check the validity of the entries
+  foreach(const QString& path, paths)
+  {
+    ctkCmdLineModuleReference moduleRef = this->ModuleManager->moduleReference(QUrl::fromLocalFile(path));
+    if (!moduleRef || !moduleRef.xmlValidationErrorString().isEmpty())
+    {
+      QStandardItem* item = ui->PathListWidget->item(path);
+      if (this->WarningIcon.isNull())
+      {
+        this->WarningIcon = ctkCmdLineModuleExplorerUtils::createIconOverlay(
+              item->icon().pixmap(item->icon().availableSizes().front()),
+              QApplication::style()->standardPixmap(QStyle::SP_MessageBoxWarning));
+      }
+      item->setIcon(this->WarningIcon);
+
+      QString toolTip = path + "\n\n" + tr("Warning") + ":\n\n";
+      if (moduleRef)
+      {
+        toolTip += moduleRef.xmlValidationErrorString();
+      }
+      else
+      {
+        toolTip += tr("No XML output available.");
+      }
+      item->setToolTip(toolTip);
+    }
+  }
 }

+ 11 - 1
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.h

@@ -24,7 +24,10 @@
 
 #include <ctkSettingsPanel.h>
 
+#include <QIcon>
+
 class ctkCmdLineModuleManager;
+class ctkCmdLineModuleExplorerShowXmlAction;
 
 namespace Ui {
 class ctkCmdLineModuleExplorerModulesSettings;
@@ -39,11 +42,18 @@ public:
   ~ctkCmdLineModuleExplorerModulesSettings();
 
   void applySettings();
-  
+
 private:
+
+  Q_SLOT void pathSelected(const QString& path);
+
+  Q_SLOT void pathsAdded(const QStringList& paths);
+
   Ui::ctkCmdLineModuleExplorerModulesSettings *ui;
 
   ctkCmdLineModuleManager* ModuleManager;
+  ctkCmdLineModuleExplorerShowXmlAction* ShowXmlAction;
+  QIcon WarningIcon;
 };
 
 #endif // CTKCMDLINEMODULEEXPLORERMODULESSETTINGS_H

+ 134 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressListWidget.cpp

@@ -0,0 +1,134 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include "ctkCmdLineModuleExplorerProgressListWidget.h"
+#include "ctkCmdLineModuleExplorerProgressWidget.h"
+
+#include "ctkCmdLineModuleFrontend.h"
+#include "ctkCmdLineModuleReference.h"
+#include "ctkCmdLineModuleDescription.h"
+
+#include <QVBoxLayout>
+
+ctkCmdLineModuleExplorerProgressListWidget::ctkCmdLineModuleExplorerProgressListWidget(QWidget *parent)
+  : QWidget(parent)
+  , CurrentWidget(NULL)
+{
+  QVBoxLayout* progressLayout = new QVBoxLayout();
+  progressLayout->setContentsMargins(0, 0, 0, 0);
+  this->setLayout(progressLayout);
+}
+
+void ctkCmdLineModuleExplorerProgressListWidget::addProgressWidget(ctkCmdLineModuleFrontend *frontend,
+                                                                   const ctkCmdLineModuleFuture &future)
+{
+  ctkCmdLineModuleExplorerProgressWidget* progressWidget = FrontendToProgressWidgetMap[frontend];
+  if (progressWidget == NULL)
+  {
+    progressWidget = new ctkCmdLineModuleExplorerProgressWidget();
+    FrontendToProgressWidgetMap[frontend] = progressWidget;
+    ProgressWidgetToFrontendMap[progressWidget] = frontend;
+
+    connect(progressWidget, SIGNAL(clicked()), SLOT(progressWidgetClicked()));
+    connect(progressWidget, SIGNAL(destroyed(QObject*)), SLOT(progressWidgetDestroyed(QObject*)));
+
+    this->layout()->addWidget(progressWidget);
+  }
+
+  progressWidget->setHighlightStyle(false);
+  progressWidget->setTitle(frontend->moduleReference().description().title());
+  progressWidget->setFuture(future);
+}
+
+
+void ctkCmdLineModuleExplorerProgressListWidget::progressWidgetDestroyed(QObject* widget)
+{
+  ctkCmdLineModuleExplorerProgressWidget* progressWidget = static_cast<ctkCmdLineModuleExplorerProgressWidget*>(widget);
+  if (CurrentWidget == progressWidget)
+  {
+    CurrentWidget = NULL;
+  }
+  ctkCmdLineModuleFrontend* frontend = ProgressWidgetToFrontendMap.take(progressWidget);
+  FrontendToProgressWidgetMap.remove(frontend);
+}
+
+
+void ctkCmdLineModuleExplorerProgressListWidget::removeProgressWidget(ctkCmdLineModuleFrontend *frontend)
+{
+  if (FrontendToProgressWidgetMap.contains(frontend))
+  {
+    FrontendToProgressWidgetMap[frontend]->deleteLater();
+  }
+}
+
+void ctkCmdLineModuleExplorerProgressListWidget::setCurrentProgressWidget(ctkCmdLineModuleFrontend *frontend)
+{
+  if (frontend == NULL && CurrentWidget != NULL)
+  {
+    CurrentWidget->setHighlightStyle(false);
+    CurrentWidget = NULL;
+    return;
+  }
+
+  if (CurrentWidget != NULL)
+  {
+    CurrentWidget->setHighlightStyle(false);
+    CurrentWidget = NULL;
+  }
+
+  ctkCmdLineModuleExplorerProgressWidget* progressWidget = FrontendToProgressWidgetMap[frontend];
+  if (progressWidget)
+  {
+    progressWidget->setHighlightStyle(true);
+    CurrentWidget = progressWidget;
+  }
+}
+
+void ctkCmdLineModuleExplorerProgressListWidget::progressWidgetClicked()
+{
+  ctkCmdLineModuleExplorerProgressWidget* progressWidget =
+      static_cast<ctkCmdLineModuleExplorerProgressWidget*>(this->sender());
+
+  ctkCmdLineModuleFrontend* frontend = ProgressWidgetToFrontendMap[progressWidget];
+  Q_ASSERT(frontend);
+
+  this->setCurrentProgressWidget(frontend);
+  emit progressWidgetClicked(frontend);
+}
+
+void ctkCmdLineModuleExplorerProgressListWidget::clearList()
+{
+  QList<ctkCmdLineModuleExplorerProgressWidget*> widgetsToRemove;
+  QHashIterator<ctkCmdLineModuleFrontend*, ctkCmdLineModuleExplorerProgressWidget*> iter(FrontendToProgressWidgetMap);
+  while (iter.hasNext())
+  {
+    iter.next();
+    if (!iter.key()->isRunning())
+    {
+      widgetsToRemove.push_back(iter.value());
+    }
+  }
+
+  foreach(ctkCmdLineModuleExplorerProgressWidget* widget, widgetsToRemove)
+  {
+    widget->deleteLater();
+  }
+}

+ 67 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressListWidget.h

@@ -0,0 +1,67 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef CTKCMDLINEMODULEEXPLORERPROGRESSLISTWIDGET_H
+#define CTKCMDLINEMODULEEXPLORERPROGRESSLISTWIDGET_H
+
+#include <QWidget>
+#include <QHash>
+
+class ctkCmdLineModuleFrontend;
+class ctkCmdLineModuleFuture;
+class ctkCmdLineModuleExplorerProgressWidget;
+
+class ctkCmdLineModuleExplorerProgressListWidget : public QWidget
+{
+  Q_OBJECT
+
+public:
+  explicit ctkCmdLineModuleExplorerProgressListWidget(QWidget *parent = 0);
+  
+  void addProgressWidget(ctkCmdLineModuleFrontend* frontend, const ctkCmdLineModuleFuture& future);
+
+signals:
+
+  void progressWidgetClicked(ctkCmdLineModuleFrontend* frontend);
+
+public slots:
+
+  void removeProgressWidget(ctkCmdLineModuleFrontend* frontend);
+
+  void setCurrentProgressWidget(ctkCmdLineModuleFrontend* frontend);
+
+  void progressWidgetClicked();
+
+  void clearList();
+  
+private slots:
+
+  void progressWidgetDestroyed(QObject* progressWidget);
+
+private:
+
+  QHash<ctkCmdLineModuleFrontend*, ctkCmdLineModuleExplorerProgressWidget*> FrontendToProgressWidgetMap;
+  QHash<ctkCmdLineModuleExplorerProgressWidget*, ctkCmdLineModuleFrontend*> ProgressWidgetToFrontendMap;
+
+  ctkCmdLineModuleExplorerProgressWidget* CurrentWidget;
+};
+
+#endif // CTKCMDLINEMODULEEXPLORERPROGRESSLISTWIDGET_H

+ 30 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.cpp

@@ -31,6 +31,8 @@ ctkCmdLineModuleExplorerProgressWidget::ctkCmdLineModuleExplorerProgressWidget(Q
 {
   ui->setupUi(this);
 
+  ui->RemoveButton->setIcon(QApplication::style()->standardIcon(QStyle::SP_TitleBarCloseButton));
+
   // Due to Qt bug 12152, we cannot listen to the "paused" signal because it is
   // not emitted directly when the QFuture is paused. Instead, it is emitted after
   // resuming the future, after the "resume" signal has been emitted... we use
@@ -60,14 +62,40 @@ void ctkCmdLineModuleExplorerProgressWidget::setFuture(const ctkCmdLineModuleFut
 {
   ui->PauseButton->setEnabled(future.canPause());
   ui->CancelButton->setEnabled(future.canCancel());
+  ui->RemoveButton->setEnabled(!future.isRunning());
+
   FutureWatcher.setFuture(future);
 }
 
+void ctkCmdLineModuleExplorerProgressWidget::setTitle(const QString &title)
+{
+  ui->ProgressTitle->setText(title);
+}
+
+void ctkCmdLineModuleExplorerProgressWidget::setHighlightStyle(bool highlight)
+{
+  QPalette::ColorRole labelRole = highlight ? QPalette::NoRole : QPalette::Midlight;
+
+  ui->ProgressTitle->setForegroundRole(labelRole);
+  ui->ProgressText->setForegroundRole(labelRole);
+  ui->ProgressBar->setEnabled(highlight);
+}
+
+void ctkCmdLineModuleExplorerProgressWidget::mouseReleaseEvent(QMouseEvent*)
+{
+  emit clicked();
+}
+
 void ctkCmdLineModuleExplorerProgressWidget::on_PauseButton_toggled(bool toggled)
 {
   this->FutureWatcher.setPaused(toggled);
 }
 
+void ctkCmdLineModuleExplorerProgressWidget::on_RemoveButton_clicked()
+{
+  this->deleteLater();
+}
+
 void ctkCmdLineModuleExplorerProgressWidget::moduleStarted()
 {
   this->ui->ProgressBar->setMaximum(0);
@@ -78,6 +106,7 @@ void ctkCmdLineModuleExplorerProgressWidget::moduleCanceled()
   this->ui->PauseButton->setEnabled(false);
   this->ui->PauseButton->setChecked(false);
   this->ui->CancelButton->setEnabled(false);
+  this->ui->RemoveButton->setEnabled(true);
 }
 
 void ctkCmdLineModuleExplorerProgressWidget::moduleFinished()
@@ -85,6 +114,7 @@ void ctkCmdLineModuleExplorerProgressWidget::moduleFinished()
   this->ui->PauseButton->setEnabled(false);
   this->ui->PauseButton->setChecked(false);
   this->ui->CancelButton->setEnabled(false);
+  this->ui->RemoveButton->setEnabled(true);
 }
 
 void ctkCmdLineModuleExplorerProgressWidget::checkModulePaused()

+ 15 - 1
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.h

@@ -23,6 +23,7 @@
 #define CTKCMDLINEMODULEEXPLORERPROGRESSWIDGET_H
 
 #include "ctkCmdLineModuleResult.h"
+#include "ctkCmdLineModuleFutureWatcher.h"
 
 #include <QWidget>
 #include <QFutureWatcher>
@@ -49,9 +50,22 @@ public:
 
   void setFuture(const ctkCmdLineModuleFuture& future);
 
+  void setTitle(const QString& title);
+
+  void setHighlightStyle(bool highlight);
+
+Q_SIGNALS:
+
+  void clicked();
+
+protected:
+
+  void mouseReleaseEvent(QMouseEvent*);
+
 private Q_SLOTS:
 
   void on_PauseButton_toggled(bool toggled);
+  void on_RemoveButton_clicked();
 
   void checkModulePaused();
 
@@ -66,7 +80,7 @@ private Q_SLOTS:
 private:
   Ui::ctkCmdLineModuleExplorerProgressWidget *ui;
 
-  QFutureWatcher<ctkCmdLineModuleResult> FutureWatcher;
+  ctkCmdLineModuleFutureWatcher FutureWatcher;
   QTimer PollPauseTimer;
 
 };

+ 40 - 1
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerProgressWidget.ui

@@ -7,13 +7,19 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>98</height>
+    <height>83</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Form</string>
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
+   <property name="spacing">
+    <number>2</number>
+   </property>
+   <property name="margin">
+    <number>0</number>
+   </property>
    <item>
     <widget class="QLabel" name="ProgressTitle">
      <property name="text">
@@ -61,6 +67,16 @@
        </property>
       </widget>
      </item>
+     <item>
+      <widget class="QToolButton" name="RemoveButton">
+       <property name="text">
+        <string>Remove</string>
+       </property>
+       <property name="autoRaise">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
@@ -70,6 +86,29 @@
      </property>
     </widget>
    </item>
+   <item>
+    <spacer name="verticalSpacer">
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeType">
+      <enum>QSizePolicy::Fixed</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>20</width>
+       <height>4</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources>

+ 89 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerShowXmlAction.cpp

@@ -0,0 +1,89 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+
+#include "ctkCmdLineModuleExplorerShowXmlAction.h"
+#include "ctkCmdLineModuleDescription.h"
+#include "ctkCmdLineModuleXmlException.h"
+
+#include <QDialog>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QSpacerItem>
+#include <QTextEdit>
+#include <QUrl>
+#include <QLabel>
+
+ctkCmdLineModuleExplorerShowXmlAction::ctkCmdLineModuleExplorerShowXmlAction(QObject *parent)
+  : QAction(parent)
+{
+  this->setText("Show XML Description");
+
+  connect(this, SIGNAL(triggered()), SLOT(run()));
+}
+
+void ctkCmdLineModuleExplorerShowXmlAction::setModuleReference(const ctkCmdLineModuleReference& ref)
+{
+  this->ModuleRef = ref;
+}
+
+void ctkCmdLineModuleExplorerShowXmlAction::run()
+{
+  QDialog* dialog = new QDialog();
+  try
+  {
+    dialog->setWindowTitle(this->ModuleRef.description().title());
+  }
+  catch (const ctkCmdLineModuleXmlException&)
+  {
+    dialog->setWindowTitle(this->ModuleRef.location().toString());
+  }
+
+  dialog->setLayout(new QVBoxLayout());
+
+  QHBoxLayout* buttonLayout = new QHBoxLayout();
+  buttonLayout->addStretch(1);
+  QPushButton* closeButton = new QPushButton(tr("Close"), dialog);
+  buttonLayout->addWidget(closeButton);
+
+  QTextEdit* textEdit = new QTextEdit(dialog);
+  textEdit->setPlainText(this->ModuleRef.rawXmlDescription().data());
+
+  QLabel* statusLabel = new QLabel(dialog);
+  statusLabel->setWordWrap(true);
+  if (this->ModuleRef.xmlValidationErrorString().isEmpty())
+  {
+    statusLabel->setText(tr("No validation errors."));
+  }
+  else
+  {
+    statusLabel->setText(this->ModuleRef.xmlValidationErrorString());
+  }
+  dialog->layout()->addWidget(statusLabel);
+  dialog->layout()->addWidget(textEdit);
+  dialog->layout()->addItem(buttonLayout);
+
+  connect(closeButton, SIGNAL(clicked()), dialog, SLOT(close()));
+
+  dialog->resize(800, 600);
+  dialog->show();
+}

+ 49 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerShowXmlAction.h

@@ -0,0 +1,49 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+
+#ifndef CTKCMDLINEMODULEEXPLORERSHOWXMLACTION_H
+#define CTKCMDLINEMODULEEXPLORERSHOWXMLACTION_H
+
+#include "ctkCmdLineModuleReference.h"
+
+#include <QAction>
+
+class ctkCmdLineModuleExplorerShowXmlAction : public QAction
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleExplorerShowXmlAction(QObject* parent);
+
+  void setModuleReference(const ctkCmdLineModuleReference& ref);
+
+protected:
+
+  Q_SLOT virtual void run();
+
+private:
+
+  ctkCmdLineModuleReference ModuleRef;
+};
+
+#endif // CTKCMDLINEMODULEEXPLORERSHOWXMLACTION_H

+ 5 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.cpp

@@ -60,6 +60,11 @@ QList<ctkCmdLineModuleFrontend *> ctkCmdLineModuleExplorerTabList::tabs() const
   return this->TabIndexToFrontend;
 }
 
+void ctkCmdLineModuleExplorerTabList::setActiveTab(ctkCmdLineModuleFrontend *frontend)
+{
+  this->TabWidget->setCurrentIndex(this->TabIndexToFrontend.indexOf(frontend));
+}
+
 void ctkCmdLineModuleExplorerTabList::addTab(ctkCmdLineModuleFrontend* moduleFrontend)
 {
   QWidget* widget = qobject_cast<QWidget*>(moduleFrontend->guiHandle());

+ 2 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.h

@@ -48,6 +48,8 @@ public:
 
   QList<ctkCmdLineModuleFrontend*> tabs() const;
 
+  Q_SLOT void setActiveTab(ctkCmdLineModuleFrontend* frontend);
+
   Q_SLOT void addTab(ctkCmdLineModuleFrontend* frontend);
 
   Q_SIGNAL void tabActivated(ctkCmdLineModuleFrontend* module);

+ 182 - 59
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTreeWidget.cpp

@@ -20,40 +20,52 @@
 =============================================================================*/
 
 #include "ctkCmdLineModuleExplorerTreeWidget.h"
+#include "ctkCmdLineModuleExplorerShowXmlAction.h"
 
 #include <ctkCmdLineModuleFrontend.h>
 #include <ctkCmdLineModuleBackend.h>
 #include <ctkCmdLineModuleFrontendFactory.h>
 #include <ctkCmdLineModuleDescription.h>
+#include <ctkCmdLineModuleXmlException.h>
 
+#include <QStandardItemModel>
+#include <QSortFilterProxyModel>
 #include <QContextMenuEvent>
 #include <QMenu>
 #include <QDebug>
+#include <QUrl>
+#include <QApplication>
+#include <QMessageBox>
 
 QString ctkCmdLineModuleExplorerTreeWidget::CATEGORY_UNKNOWN = "Uncategorized";
 
-class ctkCmdLineModuleTreeWidgetItem : public QTreeWidgetItem
+class ctkCmdLineModuleTreeWidgetItem : public QStandardItem
 {
 public:
 
-  static const int CmdLineModuleType = 1001;
-
   ctkCmdLineModuleTreeWidgetItem(const ctkCmdLineModuleReference& moduleRef)
-    : QTreeWidgetItem(CmdLineModuleType), ModuleRef(moduleRef)
-  {
-    init();
-  }
-
-  ctkCmdLineModuleTreeWidgetItem(QTreeWidget* parent, const ctkCmdLineModuleReference& moduleRef)
-    : QTreeWidgetItem(parent, CmdLineModuleType), ModuleRef(moduleRef)
-  {
-    init();
-  }
-
-  ctkCmdLineModuleTreeWidgetItem(QTreeWidgetItem* parent, const ctkCmdLineModuleReference& moduleRef)
-    : QTreeWidgetItem(parent, CmdLineModuleType), ModuleRef(moduleRef)
+    : QStandardItem()
+    , ModuleRef(moduleRef)
   {
-    init();
+    QString title;
+    try
+    {
+      title = ModuleRef.description().title();
+    }
+    catch (const ctkCmdLineModuleXmlException&)
+    {
+      title = ModuleRef.location().toString();
+    }
+
+    this->setText(title + " [" + ModuleRef.backend()->name() + "]");
+    this->setData(QVariant::fromValue(ModuleRef));
+    QString toolTip = ModuleRef.location().toString();
+    if (!ModuleRef.xmlValidationErrorString().isEmpty())
+    {
+      this->setIcon(QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning));
+      toolTip += "\n\nWarning:\n\n" + ModuleRef.xmlValidationErrorString();
+    }
+    this->setToolTip(toolTip);
   }
 
   ctkCmdLineModuleReference moduleReference() const
@@ -63,30 +75,36 @@ public:
 
 private:
 
-  void init()
-  {
-    this->setText(0, ModuleRef.description().title() + " [" + ModuleRef.backend()->name() + "]");
-    this->setData(0, Qt::UserRole, QVariant::fromValue(ModuleRef));
-  }
-
   ctkCmdLineModuleReference ModuleRef;
 
 };
 
 ctkCmdLineModuleExplorerTreeWidget::ctkCmdLineModuleExplorerTreeWidget(QWidget *parent)
-  : QTreeWidget(parent)
+  : QTreeView(parent)
+  , DefaultFrontendFactory(NULL)
 {
   this->ContextMenu = new QMenu(this);
   this->ShowFrontendMenu = this->ContextMenu->addMenu("Create Frontend");
 
-  this->ContextMenu->addAction("Properties");
+  this->ShowXmlAction = new ctkCmdLineModuleExplorerShowXmlAction(this);
+  this->ContextMenu->addAction(ShowXmlAction);
 
   connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(moduleDoubleClicked(QModelIndex)));
+
+  TreeModel = new QStandardItemModel(this);
+
+  FilterProxyModel = new ModuleSortFilterProxyModel(this);
+  FilterProxyModel->setSourceModel(TreeModel);
+  FilterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
+  FilterProxyModel->setDynamicSortFilter(true);
+  this->setModel(FilterProxyModel);
 }
 
-void ctkCmdLineModuleExplorerTreeWidget::setModuleFrontendFactories(const QList<ctkCmdLineModuleFrontendFactory *> &frontendFactories)
+void ctkCmdLineModuleExplorerTreeWidget::setModuleFrontendFactories(const QList<ctkCmdLineModuleFrontendFactory *> &frontendFactories,
+                                                                    ctkCmdLineModuleFrontendFactory* defaultFactory)
 {
   this->FrontendFactories = frontendFactories;
+  this->DefaultFrontendFactory = defaultFactory;
 
   this->ShowFrontendMenu->clear();
   foreach(ctkCmdLineModuleFrontendFactory* factory, this->FrontendFactories)
@@ -99,56 +117,100 @@ void ctkCmdLineModuleExplorerTreeWidget::setModuleFrontendFactories(const QList<
 
 void ctkCmdLineModuleExplorerTreeWidget::addModuleItem(const ctkCmdLineModuleReference &moduleRef)
 {
-  QString category = moduleRef.description().category();
-
-  QTreeWidgetItem* rootItem = NULL;
+  QString categories;
+  try
+  {
+    categories = moduleRef.description().category();
+  }
+  catch (const ctkCmdLineModuleXmlException&)
+  {
+    categories = CATEGORY_UNKNOWN;
+  }
 
-  if (category.isEmpty())
+  if (categories.isEmpty())
   {
-    category = CATEGORY_UNKNOWN;
+    categories = CATEGORY_UNKNOWN;
   }
 
-  rootItem = TreeWidgetCategories[category];
-  if (rootItem == NULL)
+  QString currentCategories;
+  QStandardItem* oldRootItem = NULL;
+  foreach (const QString& category, categories.split('.', QString::SkipEmptyParts))
   {
-    // lazily create the root item for the category
-    rootItem = new QTreeWidgetItem(this);
-    rootItem->setText(0, category);
-    TreeWidgetCategories[category] = rootItem;
+    currentCategories += (currentCategories.isEmpty() ? QString() : QString(".")) + category;
+    QStandardItem* rootItem = TreeWidgetCategories[currentCategories];
+    if (rootItem == NULL)
+    {
+      rootItem = new QStandardItem(category);
+      TreeWidgetCategories[currentCategories] = rootItem;
+      if (oldRootItem != NULL)
+      {
+        oldRootItem->appendRow(rootItem);
+      }
+      else
+      {
+        TreeModel->appendRow(rootItem);
+      }
+    }
+    oldRootItem = rootItem;
   }
-  TreeWidgetItems[moduleRef] = new ctkCmdLineModuleTreeWidgetItem(rootItem, moduleRef);
+
+  QStandardItem* moduleItem =  new ctkCmdLineModuleTreeWidgetItem(moduleRef);
+  TreeWidgetItems[moduleRef] = moduleItem;
+  oldRootItem->appendRow(moduleItem);
 }
 
 void ctkCmdLineModuleExplorerTreeWidget::removeModuleItem(const ctkCmdLineModuleReference &moduleRef)
 {
-  QString category = moduleRef.description().category();
-  if (category.isEmpty())
+  QStandardItem* treeWidgetItem = TreeWidgetItems.take(moduleRef);
+  if (treeWidgetItem == NULL) return;
+
+  QString categories;
+  try
   {
-    category = CATEGORY_UNKNOWN;
+    categories = moduleRef.description().category();
+  }
+  catch (const ctkCmdLineModuleXmlException&)
+  {
+    categories = CATEGORY_UNKNOWN;
   }
 
+  if (categories.isEmpty())
+  {
+    categories = CATEGORY_UNKNOWN;
+  }
 
-  QTreeWidgetItem* treeWidgetItem = TreeWidgetItems.take(moduleRef);
-  if (treeWidgetItem == NULL) return;
-
-  this->removeItemWidget(treeWidgetItem, 0);
-  delete treeWidgetItem;
-
-  QTreeWidgetItem* rootItem = TreeWidgetCategories[category];
-  if (rootItem && rootItem->childCount() == 0)
+  QStringList categoryList = categories.split('.', QString::SkipEmptyParts);
+  while (!categoryList.isEmpty())
+  {
+    QStandardItem* rootItem = TreeWidgetCategories[categoryList.join(".")];
+    Q_ASSERT(rootItem);
+
+    rootItem->removeRow(treeWidgetItem->row());
+
+    if (rootItem->rowCount() == 0)
+    {
+      treeWidgetItem = rootItem;
+      TreeWidgetCategories.remove(categoryList.join("."));
+      categoryList.pop_back();
+    }
+    else
+    {
+      break;
+    }
+  }
+  if (categoryList.isEmpty())
   {
-    this->removeItemWidget(rootItem, 0);
-    TreeWidgetCategories.remove(category);
-    delete rootItem;
+    TreeModel->removeRow(treeWidgetItem->row());
   }
 }
 
 void ctkCmdLineModuleExplorerTreeWidget::contextMenuEvent(QContextMenuEvent *event)
 {
-  QTreeWidgetItem* item = this->itemAt(this->viewport()->mapFromGlobal(event->globalPos()));
-  if (item != NULL && item->type() == ctkCmdLineModuleTreeWidgetItem::CmdLineModuleType)
+  QModelIndex index = this->indexAt(this->viewport()->mapFromGlobal(event->globalPos()));
+  if (index.isValid() && index.data(Qt::UserRole+1).isValid())
   {
-    this->ContextReference = item->data(0, Qt::UserRole).value<ctkCmdLineModuleReference>();
+    this->ContextReference = index.data(Qt::UserRole+1).value<ctkCmdLineModuleReference>();
+    this->ShowXmlAction->setModuleReference(this->ContextReference);
     this->ContextMenu->exec(event->globalPos());
     event->accept();
   }
@@ -161,18 +223,79 @@ void ctkCmdLineModuleExplorerTreeWidget::contextMenuEvent(QContextMenuEvent *eve
 
 void ctkCmdLineModuleExplorerTreeWidget::moduleDoubleClicked(const QModelIndex &index)
 {
-  QVariant data = index.data(Qt::UserRole);
+  if (this->DefaultFrontendFactory == NULL) return;
+
+  QVariant data = index.data(Qt::UserRole+1);
   if (!data.isValid()) return;
 
   ctkCmdLineModuleReference moduleRef = data.value<ctkCmdLineModuleReference>();
   if (!moduleRef) return;
 
-  emit moduleDoubleClicked(moduleRef);
+  this->createFrontend(moduleRef, this->DefaultFrontendFactory);
 }
 
 void ctkCmdLineModuleExplorerTreeWidget::frontendFactoryActionTriggered()
 {
   ctkCmdLineModuleFrontendFactory* frontendFactory = this->ActionsToFrontendFactoryMap[static_cast<QAction*>(this->sender())];
-  ctkCmdLineModuleFrontend* frontend = frontendFactory->create(this->ContextReference);
-  emit moduleFrontendCreated(frontend);
+  this->createFrontend(this->ContextReference, frontendFactory);
+}
+
+
+void ctkCmdLineModuleExplorerTreeWidget::setFilter(const QString &filter)
+{
+  this->FilterProxyModel->setFilterWildcard(filter);
+}
+
+ctkCmdLineModuleFrontend* ctkCmdLineModuleExplorerTreeWidget::createFrontend(const ctkCmdLineModuleReference &moduleRef,
+                                                                             ctkCmdLineModuleFrontendFactory* frontendFactory)
+{
+  try
+  {
+    moduleRef.description();
+    ctkCmdLineModuleFrontend* moduleFrontend = frontendFactory->create(moduleRef);
+    emit moduleFrontendCreated(moduleFrontend);
+    return moduleFrontend;
+  }
+  catch (const ctkException& e)
+  {
+    QMessageBox::information(this, "Frontend creation failed", "Creating a " + frontendFactory->name()
+                             + " frontend failed:\n\n" + e.what());
+    return NULL;
+  }
+}
+
+
+bool ModuleSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+  QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+
+  QModelIndex childIndex = index.child(0, 0);
+  if (childIndex.isValid())
+  {
+    int i = 0;
+    bool accept = false;
+    while(childIndex.isValid())
+    {
+      accept = this->filterAcceptsRow(childIndex.row(), index);
+      if (accept) return true;
+
+      childIndex = index.child(++i, 0);
+    }
+    return false;
+  }
+
+  return (sourceModel()->data(index).toString().contains(filterRegExp()));
+}
+
+bool ModuleSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+    QVariant l = (left.model() ? left.model()->data(left, this->sortRole()) : QVariant());
+    QVariant r = (right.model() ? right.model()->data(right, this->sortRole()) : QVariant());
+    return l.toString().compare(r.toString(), this->sortCaseSensitivity()) > 0;
+}
+
+
+ModuleSortFilterProxyModel::ModuleSortFilterProxyModel(QObject *parent)
+  : QSortFilterProxyModel(parent)
+{
 }

+ 36 - 6
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTreeWidget.h

@@ -24,16 +24,36 @@
 
 #include <ctkCmdLineModuleReference.h>
 
-#include <QTreeWidget>
+#include <QSortFilterProxyModel>
+#include <QTreeView>
 
 class ctkCmdLineModuleFrontend;
 struct ctkCmdLineModuleFrontendFactory;
+class ctkCmdLineModuleExplorerShowXmlAction;
+
+class QStandardItem;
+class QStandardItemModel;
+
+class ModuleSortFilterProxyModel : public QSortFilterProxyModel
+{
+  Q_OBJECT
+
+public:
+
+  ModuleSortFilterProxyModel(QObject *parent = 0);
+
+protected:
+
+  bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+  bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+};
 
 /**
  * \class ctkCmdLineModuleExplorerTreeWidget
  * \brief Example application tree widget.
  */
-class ctkCmdLineModuleExplorerTreeWidget : public QTreeWidget
+class ctkCmdLineModuleExplorerTreeWidget : public QTreeView
 {
   Q_OBJECT
 
@@ -41,20 +61,24 @@ public:
 
   explicit ctkCmdLineModuleExplorerTreeWidget(QWidget* parent = 0);
 
-  void setModuleFrontendFactories(const QList<ctkCmdLineModuleFrontendFactory*>& frontendFactories);
+  void setModuleFrontendFactories(const QList<ctkCmdLineModuleFrontendFactory*>& frontendFactories, ctkCmdLineModuleFrontendFactory *defaultFactory);
 
   Q_SLOT void addModuleItem(const ctkCmdLineModuleReference& moduleRef);
   Q_SLOT void removeModuleItem(const ctkCmdLineModuleReference& moduleRef);
 
-  Q_SIGNAL void moduleDoubleClicked(ctkCmdLineModuleReference moduleRef);
   Q_SIGNAL void moduleFrontendCreated(ctkCmdLineModuleFrontend* moduleFrontend);
 
+  Q_SLOT void setFilter(const QString& filter);
+
 protected:
 
   void contextMenuEvent(QContextMenuEvent* event);
 
 private:
 
+  ctkCmdLineModuleFrontend* createFrontend(const ctkCmdLineModuleReference &moduleRef,
+                                           ctkCmdLineModuleFrontendFactory* frontendFactory);
+
   Q_SLOT void moduleDoubleClicked(const QModelIndex& index);
   Q_SLOT void frontendFactoryActionTriggered();
 
@@ -63,11 +87,17 @@ private:
   QMenu* ContextMenu;
   QMenu* ShowFrontendMenu;
 
+  ctkCmdLineModuleExplorerShowXmlAction* ShowXmlAction;
+
   ctkCmdLineModuleReference ContextReference;
   QList<ctkCmdLineModuleFrontendFactory*> FrontendFactories;
-  QHash<QString, QTreeWidgetItem*> TreeWidgetCategories;
-  QHash<ctkCmdLineModuleReference, QTreeWidgetItem*> TreeWidgetItems;
+  ctkCmdLineModuleFrontendFactory* DefaultFrontendFactory;
+  QHash<QString, QStandardItem*> TreeWidgetCategories;
+  QHash<ctkCmdLineModuleReference, QStandardItem*> TreeWidgetItems;
   QHash<QAction*, ctkCmdLineModuleFrontendFactory*> ActionsToFrontendFactoryMap;
+
+  QStandardItemModel* TreeModel;
+  QSortFilterProxyModel* FilterProxyModel;
 };
 
 #endif // CTKCMDLINEMODULEEXPLORERTREEWIDGET_H

+ 68 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.cpp

@@ -0,0 +1,68 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include "ctkCmdLineModuleExplorerUtils.h"
+
+#include <QPainter>
+#include <QObject>
+#include <QWidget>
+#include <QApplication>
+#include <QMessageBox>
+
+QPixmap ctkCmdLineModuleExplorerUtils::createIconOverlay(const QPixmap &base, const QPixmap &overlay)
+{
+  QPixmap result(base.width(), base.height());
+  result.fill(Qt::transparent);
+  QPainter painter(&result);
+  painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+  painter.drawPixmap(0, 0, base);
+  painter.drawPixmap(base.width()/2, base.height()/2,
+                     overlay.scaled(base.width()/2, base.height()/2, Qt::KeepAspectRatio));
+  return result;
+}
+
+void ctkCmdLineModuleExplorerUtils:: messageBoxModuleRegistration(const QStringList& modulePaths,
+                                                                 const QList<ctkCmdLineModuleReference>& moduleRefs,
+                                                                 ctkCmdLineModuleManager::ValidationMode validationMode)
+{
+  Q_ASSERT(modulePaths.size() == moduleRefs.size());
+
+  QString errorMsg;
+  for(int i = 0; i < modulePaths.size(); ++i)
+  {
+    if (!moduleRefs.at(i))
+    {
+      errorMsg += QObject::tr("Failed to register ") + modulePaths.at(i) + "\n\n";
+    }
+    else if (!moduleRefs.at(i).xmlValidationErrorString().isEmpty() &&
+             validationMode == ctkCmdLineModuleManager::STRICT_VALIDATION)
+    {
+      errorMsg += QObject::tr("Failed to register ") + modulePaths.at(i) + ":\n" + moduleRefs.at(i).xmlValidationErrorString() + "\n\n";
+    }
+  }
+
+  if (!errorMsg.isEmpty())
+  {
+    QWidget* widget = QApplication::activeModalWidget();
+    if (widget == NULL) widget = QApplication::activeWindow();
+    QMessageBox::critical(widget, QObject::tr("Failed to register modules"), errorMsg);
+  }
+}

+ 40 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.h

@@ -0,0 +1,40 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef CTKCOMMANDLINEMODULEEXPLORERUTILS_H
+#define CTKCOMMANDLINEMODULEEXPLORERUTILS_H
+
+#include "ctkCmdLineModuleManager.h"
+
+#include <QPixmap>
+
+struct ctkCmdLineModuleExplorerUtils
+{
+
+  static QPixmap createIconOverlay(const QPixmap& base, const QPixmap& overlay);
+
+  static void messageBoxModuleRegistration(const QStringList& modulePaths,
+                                           const QList<ctkCmdLineModuleReference>& moduleRefs,
+                                           ctkCmdLineModuleManager::ValidationMode validationMode);
+
+};
+
+#endif // CTKCOMMANDLINEMODULEEXPLORERUTILS_H

BIN
Applications/ctkCommandLineModuleExplorer/resources/clear.png


+ 1 - 0
Applications/ctkCommandLineModuleExplorer/resources/ctkCmdLineModuleExplorer.qrc

@@ -3,5 +3,6 @@
         <file>run.png</file>
         <file>stop.png</file>
         <file>pause.png</file>
+        <file>clear.png</file>
     </qresource>
 </RCC>

+ 1 - 1
Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.cpp

@@ -229,7 +229,7 @@ qint64 ctkCmdLineModuleBackendFunctionPointer::timeStamp(const QUrl &location) c
 QByteArray ctkCmdLineModuleBackendFunctionPointer::rawXmlDescription(const QUrl& location)
 {
   if (!d->UrlToFpDescription.contains(location)) return QByteArray();
-  qDebug() << d->UrlToFpDescription[location].d->xmlDescription();
+  //qDebug() << d->UrlToFpDescription[location].d->xmlDescription();
   return QByteArray(qPrintable(d->UrlToFpDescription[location].d->xmlDescription()));
 }
 

+ 10 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.h

@@ -44,6 +44,16 @@ struct ctkCmdLineModuleFutureWatcherPrivate;
  * new error data (usually text written to the standard error channel).
  *
  * Use readPendingOutputData() or readPendingErrorData() to get the newly added data.
+ *
+ * \warning While you could use a QFutureWatcher<ctkCmdLineModuleResult> instance directly (and
+ *          provide a ctkCmdLineModuleFuture via QFutureWatcher<ctkCmdLineModuleResult>::setFuture(future)
+ *          by virtue of "slicing") this is discouraged. The reason is that a member variable of type
+ *          QFutureWatcher<ctkCmdLineModuleResult> will have a different size, depending on the inclusion
+ *          of the ctkCmdLineModuleFutureInterface.h header (this header provides a specialization of the
+ *          QFutureInterface template which adds a data member). This can lead to subtle heap corruptions.
+ *          Since the code compiles with or without the ctkCmdLindeModuleFutureInterface.h inclusion, you
+ *          should always use ctkCmdLineModuleFutureWatcher when working with ctkCmdLineModuleFuture objects
+ *          to avoid runtime heap corruptions.
  */
 class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleFutureWatcher : public QFutureWatcher<ctkCmdLineModuleResult>
 {

+ 10 - 3
Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp

@@ -96,6 +96,12 @@ ctkCmdLineModuleManager::~ctkCmdLineModuleManager()
 }
 
 //----------------------------------------------------------------------------
+ctkCmdLineModuleManager::ValidationMode ctkCmdLineModuleManager::validationMode() const
+{
+  return d->ValidationMode;
+}
+
+//----------------------------------------------------------------------------
 void ctkCmdLineModuleManager::registerBackend(ctkCmdLineModuleBackend *backend)
 {
   QMutexLocker lock(&d->Mutex);
@@ -195,8 +201,8 @@ ctkCmdLineModuleManager::registerModule(const QUrl &location)
     {
       if (d->ModuleCache)
       {
-        // validation failed, cache an empty description
-        d->ModuleCache->cacheXmlDescription(location, newTimeStamp, QByteArray());
+        // validation failed, cache the description anyway
+        d->ModuleCache->cacheXmlDescription(location, newTimeStamp, xml);
       }
 
       if (d->ValidationMode == STRICT_VALIDATION)
@@ -266,7 +272,8 @@ void ctkCmdLineModuleManager::unregisterModule(const ctkCmdLineModuleReference&
 ctkCmdLineModuleReference ctkCmdLineModuleManager::moduleReference(const QUrl &location) const
 {
   QMutexLocker lock(&d->Mutex);
-  return d->LocationToRef[location];
+  QHash<QUrl, ctkCmdLineModuleReference>::const_iterator iter = d->LocationToRef.find(location);
+  return (iter == d->LocationToRef.end()) ? ctkCmdLineModuleReference() : iter.value();
 }
 
 //----------------------------------------------------------------------------

+ 6 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleManager.h

@@ -100,6 +100,12 @@ public:
   ~ctkCmdLineModuleManager();
 
   /**
+   * @brief Get the validation mode.
+   * @return The validation mode.
+   */
+  ValidationMode validationMode() const;
+
+  /**
    * @brief Registers a new back-end.
    * @param backend The new back-end.
    * @throws ctkInvalidArgumentException if another back-end was already registered handling

+ 23 - 2
Libs/CommandLineModules/Core/ctkCmdLineModuleReference.cpp

@@ -22,26 +22,47 @@
 #include "ctkCmdLineModuleReference.h"
 #include "ctkCmdLineModuleReference_p.h"
 #include "ctkCmdLineModuleXmlParser_p.h"
+#include "ctkCmdLineModuleXmlException.h"
 
 #include <QBuffer>
 
 
 //----------------------------------------------------------------------------
 ctkCmdLineModuleReferencePrivate::ctkCmdLineModuleReferencePrivate()
-  : Backend(NULL)
+  : Backend(NULL), XmlException(NULL)
 {
 }
 
 //----------------------------------------------------------------------------
+ctkCmdLineModuleReferencePrivate::~ctkCmdLineModuleReferencePrivate()
+{
+  delete XmlException;
+}
+
+//----------------------------------------------------------------------------
 ctkCmdLineModuleDescription ctkCmdLineModuleReferencePrivate::description() const
 {
+  // If we already got an XML exception just throw it immediately, since
+  // the XML description cannot change for this module reference.
+  if (XmlException)
+  {
+    throw *XmlException;
+  }
+
   // Lazy creation. The title is a required XML element.
   if (Description.title().isNull())
   {
     QByteArray xml(RawXmlDescription);
     QBuffer xmlInput(&xml);
     ctkCmdLineModuleXmlParser parser(&xmlInput, &Description);
-    parser.doParse();
+    try
+    {
+      parser.doParse();
+    }
+    catch (const ctkCmdLineModuleXmlException& e)
+    {
+      XmlException = e.clone();
+    }
   }
   return Description;
 }

+ 1 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleReference.h

@@ -62,6 +62,7 @@ public:
   /**
    * @brief Get the module description for the parameters.
    * @return The XML description as a class representation.
+   * @throws ctkCmdLineModuleXmlException if the raw XML description cannot be parsed.
    */
   ctkCmdLineModuleDescription description() const;
 

+ 3 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleReference_p.h

@@ -28,10 +28,12 @@
 #include <QUrl>
 
 struct ctkCmdLineModuleBackend;
+class ctkCmdLineModuleXmlException;
 
 struct ctkCmdLineModuleReferencePrivate : public QSharedData
 {
   ctkCmdLineModuleReferencePrivate();
+  ~ctkCmdLineModuleReferencePrivate();
 
   ctkCmdLineModuleDescription description() const;
 
@@ -43,6 +45,7 @@ struct ctkCmdLineModuleReferencePrivate : public QSharedData
 private:
 
   mutable ctkCmdLineModuleDescription Description;
+  mutable ctkCmdLineModuleXmlException* XmlException;
 };
 
 #endif // CTKCMDLINEMODULEREFERENCEPRIVATE_H

+ 1 - 1
Libs/CommandLineModules/README.md

@@ -83,7 +83,7 @@ For example a progress report containing a progress value and text would look li
 
 Here is the XML [progress and result schema documentation](ctkCmdLineModuleProcess.xsd)
 ([absolute link](http://www.commontk.org/docs/html/ctkCmdLineModuleProcess.xsd)) describing the valid XML fragments. The raw
-schema file is available [here](https://raw.github.com/commontk/CTK/master/Libs/CommandLineModules/Core/Resources/ctkCmdLineModuleProcess.xsd).
+schema file is available [here](https://raw.github.com/commontk/CTK/master/Libs/CommandLineModules/Backend/LocalProcess/Resources/ctkCmdLineModuleProcess.xsd).
 
 
 Library Design

+ 7 - 4
Libs/Widgets/ctkPathListButtonsWidget.cpp

@@ -306,14 +306,17 @@ void ctkPathListButtonsWidgetPrivate::addPathsWithWarningMessage(const QStringLi
     QString problematicPaths;
     foreach(const QString& path, paths)
     {
-      if (!addedPaths.contains(path))
+      if (!addedPaths.contains(path) && !this->PathListWidget->contains(path))
       {
         problematicPaths += path + '\n';
       }
     }
-    QMessageBox::information(q, tr("Adding paths failed"),
-                             QString(tr("Failed to add the following paths:\n\n%1\nPlease check your permissions."))
-                             .arg(problematicPaths));
+    if (!problematicPaths.isEmpty())
+    {
+      QMessageBox::information(q, tr("Adding paths failed"),
+                               QString(tr("Failed to add the following paths:\n\n%1\nPlease check your permissions."))
+                               .arg(problematicPaths));
+    }
   }
 }
 

+ 36 - 0
Libs/Widgets/ctkPathListWidget.cpp

@@ -584,6 +584,30 @@ QString ctkPathListWidget::path(int row) const
 }
 
 // --------------------------------------------------------------------------
+QStandardItem* ctkPathListWidget::item(int row) const
+{
+  Q_D(const ctkPathListWidget);
+  return d->PathListModel.item(row);
+}
+
+// --------------------------------------------------------------------------
+QStandardItem *ctkPathListWidget::item(const QString &absolutePath) const
+{
+  Q_D(const ctkPathListWidget);
+  QModelIndexList result = d->PathListModel.match(d->PathListModel.index(0,0), AbsolutePathRole,
+                                                  absolutePath, 1, Qt::MatchExactly);
+  Q_ASSERT(result.count() < 2);
+  if (result.isEmpty())
+  {
+    return NULL;
+  }
+  else
+  {
+    return d->PathListModel.item(result.front().row());
+  }
+}
+
+// --------------------------------------------------------------------------
 QString ctkPathListWidget::pathAt(const QPoint& point) const
 {
   Q_D(const ctkPathListWidget);
@@ -591,6 +615,18 @@ QString ctkPathListWidget::pathAt(const QPoint& point) const
 }
 
 // --------------------------------------------------------------------------
+QStandardItem* ctkPathListWidget::itemAt(const QPoint &point) const
+{
+  Q_D(const ctkPathListWidget);
+  QModelIndex index = this->indexAt(point);
+  if (index.isValid())
+  {
+    return d->PathListModel.item(index.row());
+  }
+  return NULL;
+}
+
+// --------------------------------------------------------------------------
 int ctkPathListWidget::row(const QString& path) const
 {
   Q_D(const ctkPathListWidget);

+ 15 - 0
Libs/Widgets/ctkPathListWidget.h

@@ -32,6 +32,8 @@
 
 class ctkPathListWidgetPrivate;
 
+class QStandardItem;
+
 /// \ingroup Widgets
 ///
 /// \brief The ctkPathListWidget lists files and/or directories.
@@ -181,13 +183,26 @@ public:
   /// \return The absolute path for \a row or a null QString if \a row is out of range.
   QString path(int row) const;
 
+  /// \return The item for \a row or NULL if \a row is out of range.
+  QStandardItem* item(int row) const;
+
+  /// \return The item for the given absolute path or NULL if the the path is not known.
+  QStandardItem* item(const QString& absolutePath) const;
+
   /// \return The absolute path for the entry located at the point \a point (in the
   ///         widget coordinate system) or a null QString if no entry could be found for \a point.
   QString pathAt(const QPoint& point) const;
 
+  /// \return The item for the entry located at the point \a point (in the widget
+  ///         coordinate system) or NULL if no ite could be found for \a point.
+  QStandardItem* itemAt(const QPoint& point) const;
+
   /// \see pathAt(const QPoint&)
   QString pathAt(int x, int y) const { return pathAt(QPoint(x, y)); }
 
+  /// \see itemAt(const QPoint&)
+  QStandardItem* itemAt(int x, int y) const { return itemAt(QPoint(x, y)); }
+
   /// \return The row number for the given \a path or -1 if \a path is not in the list of current
   ///         entries.
   int row(const QString& path) const;