Pārlūkot izejas kodu

Merge branch 'cli-module-support' of git://github.com/commontk/CTK into cli-module-support

MattClarkson 12 gadi atpakaļ
vecāks
revīzija
701bdb3cec
32 mainītis faili ar 1371 papildinājumiem un 365 dzēšanām
  1. 2 0
      Applications/ctkCommandLineModuleExplorer/CMakeLists.txt
  2. 3 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp
  3. 1 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.h
  4. 33 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.ui
  5. 129 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerOutputText.cpp
  6. 61 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerOutputText.h
  7. 1 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.cpp
  8. 1 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.h
  9. 3 7
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessTask.cpp
  10. 17 1
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher.cpp
  11. 3 0
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher_p.h
  12. 5 2
      Libs/CommandLineModules/Core/CMakeLists.txt
  13. 4 1
      Libs/CommandLineModules/Core/Testing/Cpp/CMakeLists.txt
  14. 32 53
      Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleXmlProgressWatcherTest.cpp
  15. 16 1
      Libs/CommandLineModules/Core/ctkCmdLineModuleFrontend.h
  16. 57 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.cpp
  17. 10 10
      Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.h
  18. 201 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.cpp
  19. 31 36
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.h
  20. 90 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface_p.h
  21. 216 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.cpp
  22. 86 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.h
  23. 1 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp
  24. 38 2
      Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.cpp
  25. 6 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.h
  26. 12 13
      Libs/CommandLineModules/Frontend/QtGui/Testing/Cpp/ctkCmdLineModuleQtXslTransformTest.cpp
  27. 8 3
      Libs/CommandLineModules/Testing/Cpp/CMakeLists.txt
  28. 203 202
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp
  29. 66 21
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.cpp
  30. 20 5
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.h
  31. 14 7
      Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp
  32. 1 1
      Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.xml

+ 2 - 0
Applications/ctkCommandLineModuleExplorer/CMakeLists.txt

@@ -11,6 +11,7 @@ set(KIT_SRCS
   ctkCmdLineModuleExplorerMainWindow.h
   ctkCmdLineModuleExplorerMainWindow.cpp
   ctkCmdLineModuleExplorerModulesSettings.cpp
+  ctkCmdLineModuleExplorerOutputText.cpp
   ctkCmdLineModuleExplorerProgressWidget.cpp
   ctkCmdLineModuleExplorerTabList.cpp
   ctkCmdLineModuleExplorerTreeWidget.cpp
@@ -20,6 +21,7 @@ set(KIT_SRCS
 set(KIT_MOC_SRCS
   ctkCmdLineModuleExplorerMainWindow.h
   ctkCmdLineModuleExplorerModulesSettings.h
+  ctkCmdLineModuleExplorerOutputText.h
   ctkCmdLineModuleExplorerProgressWidget.h
   ctkCmdLineModuleExplorerTabList.h
   ctkCmdLineModuleExplorerTreeWidget.h

+ 3 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp

@@ -92,6 +92,7 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   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(tabClosed(ctkCmdLineModuleFrontend*)), ui->outputText, SLOT(frontendRemoved(ctkCmdLineModuleFrontend*)));
 
   // Listen to future events for the currently active tab
 
@@ -290,6 +291,7 @@ void ctkCLModuleExplorerMainWindow::moduleTabActivated(ctkCmdLineModuleFrontend
     ui->actionPause->setEnabled(false);
     ui->actionCancel->setEnabled(false);
     ui->actionReset->setEnabled(false);
+    ui->outputText->setActiveFrontend(NULL);
     currentFutureWatcher.setFuture(ctkCmdLineModuleFuture());
   }
   else
@@ -299,6 +301,7 @@ void ctkCLModuleExplorerMainWindow::moduleTabActivated(ctkCmdLineModuleFrontend
     ui->actionPause->setChecked(module->isPaused());
     ui->actionCancel->setEnabled(module->future().canCancel() && module->isRunning());
     ui->actionReset->setEnabled(true);
+    ui->outputText->setActiveFrontend(module);
     currentFutureWatcher.setFuture(module->future());
   }
 }

+ 1 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.h

@@ -90,6 +90,7 @@ private:
 
   QTimer pollPauseTimer;
   QFutureWatcher<ctkCmdLineModuleResult> currentFutureWatcher;
+  QHash<ctkCmdLineModuleFrontend*, QByteArray> frontendToOutputMap;
 
   ctkCmdLineModuleDirectoryWatcher directoryWatcher;
 

+ 33 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.ui

@@ -159,6 +159,34 @@
     </layout>
    </widget>
   </widget>
+  <widget class="QDockWidget" name="dockWidget_3">
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="allowedAreas">
+    <set>Qt::BottomDockWidgetArea|Qt::RightDockWidgetArea|Qt::TopDockWidgetArea</set>
+   </property>
+   <property name="windowTitle">
+    <string>Output</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>8</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents_3">
+    <layout class="QVBoxLayout" name="verticalLayout_3">
+     <property name="margin">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="ctkCmdLineModuleExplorerOutputText" name="outputText">
+       <property name="readOnly">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
   <action name="actionRun">
    <property name="icon">
     <iconset resource="resources/ctkCmdLineModuleExplorer.qrc">
@@ -243,6 +271,11 @@
    <extends>QTreeWidget</extends>
    <header>ctkCmdLineModuleExplorerTreeWidget.h</header>
   </customwidget>
+  <customwidget>
+   <class>ctkCmdLineModuleExplorerOutputText</class>
+   <extends>QTextEdit</extends>
+   <header>ctkCmdLineModuleExplorerOutputText.h</header>
+  </customwidget>
  </customwidgets>
  <resources>
   <include location="resources/ctkCmdLineModuleExplorer.qrc"/>

+ 129 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerOutputText.cpp

@@ -0,0 +1,129 @@
+/*=============================================================================
+
+  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 "ctkCmdLineModuleExplorerOutputText.h"
+
+#include "ctkCmdLineModuleFrontend.h"
+#include "ctkCmdLineModuleFuture.h"
+#include "ctkCmdLineModuleFutureWatcher.h"
+
+ctkCmdLineModuleExplorerOutputText::ctkCmdLineModuleExplorerOutputText(QWidget* parent)
+  : QTextEdit(parent)
+  , CurrentWatcher(NULL)
+  , CurrentFrontend(NULL)
+{
+}
+
+ctkCmdLineModuleExplorerOutputText::~ctkCmdLineModuleExplorerOutputText()
+{
+  qDeleteAll(this->FrontendToWatcherMap);
+}
+
+void ctkCmdLineModuleExplorerOutputText::setActiveFrontend(ctkCmdLineModuleFrontend* moduleFrontend)
+{
+  if (this->CurrentFrontend == moduleFrontend) return;
+
+  if (this->CurrentFrontend)
+  {
+    if (this->CurrentWatcher)
+    {
+      this->CurrentWatcher->disconnect();
+    }
+    this->CurrentFrontend->disconnect();
+
+    // save the current output text
+    this->FrontendToOutputMap[this->CurrentFrontend] = this->toHtml();
+    this->clear();
+  }
+
+  this->CurrentFrontend = moduleFrontend;
+  if (moduleFrontend)
+  {
+    // restore previous content
+    this->setHtml(this->FrontendToOutputMap[moduleFrontend]);
+    QTextCursor endCursor = this->textCursor();
+    endCursor.movePosition(QTextCursor::End);
+    this->setTextCursor(endCursor);
+
+    this->CurrentWatcher = FrontendToWatcherMap[moduleFrontend];
+    if (this->CurrentWatcher == NULL)
+    {
+      this->CurrentWatcher = new ctkCmdLineModuleFutureWatcher;
+      this->FrontendToWatcherMap[moduleFrontend] = this->CurrentWatcher;
+    }
+
+    connect(this->CurrentFrontend, SIGNAL(started()), SLOT(frontendStarted()));
+
+    connect(this->CurrentWatcher, SIGNAL(outputDataReady()), SLOT(outputDataReady()));
+    connect(this->CurrentWatcher, SIGNAL(errorDataReady()), SLOT(errorDataReady()));
+
+    this->CurrentWatcher->setFuture(moduleFrontend->future());
+
+    // if the frontend is already finished get any output we have not yet fetched
+    if (moduleFrontend->future().isFinished())
+    {
+      this->outputDataReady();
+      this->errorDataReady();
+    }
+  }
+  else
+  {
+    if (this->CurrentWatcher)
+    {
+      this->CurrentWatcher->disconnect();
+      this->CurrentWatcher = NULL;
+    }
+    if (this->CurrentFrontend)
+    {
+      this->CurrentFrontend->disconnect();
+      this->CurrentFrontend = NULL;
+    }
+    this->clear();
+  }
+}
+
+void ctkCmdLineModuleExplorerOutputText::frontendRemoved(ctkCmdLineModuleFrontend *frontend)
+{
+  delete this->FrontendToWatcherMap[frontend];
+  this->FrontendToWatcherMap.remove(frontend);
+  this->FrontendToOutputMap.remove(frontend);
+}
+
+void ctkCmdLineModuleExplorerOutputText::frontendStarted()
+{
+  this->clear();
+  this->FrontendToOutputMap[this->CurrentFrontend].clear();
+  this->CurrentWatcher->setFuture(this->CurrentFrontend->future());
+}
+
+void ctkCmdLineModuleExplorerOutputText::outputDataReady()
+{
+  QByteArray newOutput = this->CurrentWatcher->readPendingOutputData();
+  this->setTextColor(QColor(Qt::black));
+  this->insertPlainText(newOutput.data());
+}
+
+void ctkCmdLineModuleExplorerOutputText::errorDataReady()
+{
+  QByteArray newOutput = this->CurrentWatcher->readPendingErrorData();
+  this->setTextColor(QColor(Qt::darkRed));
+  this->insertPlainText(newOutput.data());
+}

+ 61 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerOutputText.h

@@ -0,0 +1,61 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULEEXPLOREROUTPUTTEXT_H
+#define CTKCMDLINEMODULEEXPLOREROUTPUTTEXT_H
+
+
+#include <QTextEdit>
+
+class ctkCmdLineModuleFrontend;
+class ctkCmdLineModuleFutureWatcher;
+
+class ctkCmdLineModuleExplorerOutputText : public QTextEdit
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleExplorerOutputText(QWidget* parent = 0);
+  ~ctkCmdLineModuleExplorerOutputText();
+
+public Q_SLOTS:
+
+  void setActiveFrontend(ctkCmdLineModuleFrontend* frontend);
+
+  void frontendRemoved(ctkCmdLineModuleFrontend* frontend);
+
+private Q_SLOTS:
+
+  void frontendStarted();
+
+  void outputDataReady();
+  void errorDataReady();
+
+private:
+
+  ctkCmdLineModuleFutureWatcher* CurrentWatcher;
+  ctkCmdLineModuleFrontend* CurrentFrontend;
+  QHash<ctkCmdLineModuleFrontend*,ctkCmdLineModuleFutureWatcher*> FrontendToWatcherMap;
+  QHash<ctkCmdLineModuleFrontend*,QString> FrontendToOutputMap;
+};
+
+#endif // CTKCMDLINEMODULEEXPLOREROUTPUTTEXT_H

+ 1 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.cpp

@@ -116,6 +116,7 @@ void ctkCmdLineModuleExplorerTabList::tabCloseRequested(int index)
   {
     this->TabIndexToFrontend.removeAt(index);
     this->TabWidget->removeTab(index);
+    emit this->tabClosed(frontend);
     delete frontend;
   }
 }

+ 1 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerTabList.h

@@ -51,6 +51,7 @@ public:
   Q_SLOT void addTab(ctkCmdLineModuleFrontend* frontend);
 
   Q_SIGNAL void tabActivated(ctkCmdLineModuleFrontend* module);
+  Q_SIGNAL void tabClosed(ctkCmdLineModuleFrontend* module);
 
 private:
 

+ 3 - 7
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessTask.cpp

@@ -83,23 +83,19 @@ void ctkCmdLineModuleProcessTask::run()
   QObject::connect(&process, SIGNAL(finished(int)), &localLoop, SLOT(quit()));
   QObject::connect(&process, SIGNAL(error(QProcess::ProcessError)), &localLoop, SLOT(quit()));
 
-  process.start(d->Location, d->Args);
+  process.start(d->Location, d->Args, QIODevice::ReadOnly | QIODevice::Text);
 
   ctkCmdLineModuleProcessWatcher progressWatcher(process, d->Location, *this);
   Q_UNUSED(progressWatcher)
 
   localLoop.exec();
 
-  if (process.error() != QProcess::UnknownError)
+  if (process.error() != QProcess::UnknownError || process.exitCode() != 0)
   {
     this->reportException(ctkCmdLineModuleRunException(d->Location, process.exitCode(), process.errorString()));
   }
-  else if (process.exitCode() != 0)
-  {
-    this->reportException(ctkCmdLineModuleRunException(d->Location, process.exitCode(), process.readAllStandardError()));
-  }
 
-  this->setProgressValueAndText(1000, process.readAllStandardError());
+  this->setProgressValue(1000);
 
   //this->reportResult(result);
   this->reportFinished();

+ 17 - 1
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher.cpp

@@ -42,6 +42,9 @@ ctkCmdLineModuleProcessWatcher::ctkCmdLineModuleProcessWatcher(QProcess& process
   connect(&processXmlWatcher, SIGNAL(filterFinished(QString)), SLOT(filterFinished(QString)));
   connect(&processXmlWatcher, SIGNAL(filterXmlError(QString)), SLOT(filterXmlError(QString)));
 
+  connect(&processXmlWatcher, SIGNAL(outputDataAvailable(QByteArray)), SLOT(outputDataAvailable(QByteArray)));
+  connect(&processXmlWatcher, SIGNAL(errorDataAvailable(QByteArray)), SLOT(errorDataAvailable(QByteArray)));
+
   connect(&futureWatcher, SIGNAL(canceled()), SLOT(cancelProcess()));
 #ifdef Q_OS_UNIX
   connect(&futureWatcher, SIGNAL(resumed()), SLOT(resumeProcess()));
@@ -119,9 +122,22 @@ void ctkCmdLineModuleProcessWatcher::resumeProcess()
 //----------------------------------------------------------------------------
 void ctkCmdLineModuleProcessWatcher::cancelProcess()
 {
-  process.terminate();
+  process.kill();
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleProcessWatcher::outputDataAvailable(const QByteArray &outputData)
+{
+  futureInterface.reportOutputData(outputData);
 }
 
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleProcessWatcher::errorDataAvailable(const QByteArray &errorData)
+{
+  futureInterface.reportErrorData(errorData);
+}
+
+//----------------------------------------------------------------------------
 int ctkCmdLineModuleProcessWatcher::updateProgress(float progress)
 {
   progressValue = static_cast<int>(progress * 1000.0f);

+ 3 - 0
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher_p.h

@@ -59,6 +59,9 @@ protected Q_SLOTS:
   void resumeProcess();
   void cancelProcess();
 
+  void outputDataAvailable(const QByteArray& outputData);
+  void errorDataAvailable(const QByteArray& errorData);
+
 private:
 
   int updateProgress(float progress);

+ 5 - 2
Libs/CommandLineModules/Core/CMakeLists.txt

@@ -25,8 +25,10 @@ set(KIT_SRCS
   ctkCmdLineModuleDirectoryWatcher_p.h
   ctkCmdLineModuleFrontend.cpp
   ctkCmdLineModuleFrontendFactory.cpp
-  ctkCmdLineModuleFuture.h
-  ctkCmdLineModuleFutureInterface.h
+  ctkCmdLineModuleFuture.cpp
+  ctkCmdLineModuleFutureInterface_p.h
+  ctkCmdLineModuleFutureInterface.cpp
+  ctkCmdLineModuleFutureWatcher.cpp
   ctkCmdLineModuleManager.cpp
   ctkCmdLineModuleParameter.cpp
   ctkCmdLineModuleParameterGroup.cpp
@@ -51,6 +53,7 @@ set(KIT_MOC_SRCS
   ctkCmdLineModuleDirectoryWatcher.h
   ctkCmdLineModuleDirectoryWatcher_p.h
   ctkCmdLineModuleFrontend.h
+  ctkCmdLineModuleFutureWatcher.h
   ctkCmdLineModuleManager.h
 )
 

+ 4 - 1
Libs/CommandLineModules/Core/Testing/Cpp/CMakeLists.txt

@@ -23,6 +23,9 @@ include_directories(
 
 set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
+QT4_GENERATE_MOCS(
+  ctkCmdLineModuleXmlProgressWatcherTest.cpp
+)
 set(Tests_UI_CPP)
 if(TEST_UI_FORMS)
   QT4_WRAP_UI(Tests_UI_CPP ${Tests_UI_FORMS})
@@ -37,4 +40,4 @@ target_link_libraries(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES})
 # Add Tests
 #
 SIMPLE_TEST(ctkCmdLineModuleXmlProgressWatcherTest)
-SIMPLE_TEST(ctkCmdLineModuleDefaultPathBuilderTest ${CTK_CMAKE_RUNTIME_OUTPUT_DIRECTORY})
+SIMPLE_TEST(ctkCmdLineModuleDefaultPathBuilderTest ${CTK_CMAKE_RUNTIME_OUTPUT_DIRECTORY})

+ 32 - 53
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleXmlProgressWatcherTest.cpp

@@ -24,15 +24,17 @@
 
 #include "ctkCmdLineModuleSignalTester.h"
 
+#include "ctkTest.h"
+
 #include <QCoreApplication>
 #include <QBuffer>
 #include <QDataStream>
 #include <QDebug>
 
-#include <cstdlib>
 
 namespace {
 
+//-----------------------------------------------------------------------------
 // Custom signal tester
 class SignalTester : public ctkCmdLineModuleSignalTester
 {
@@ -82,7 +84,21 @@ public:
   float accumulatedProgress;
 };
 
-bool xmlProgressWatcherTestSignalsAndValues()
+}
+
+//-----------------------------------------------------------------------------
+class ctkCmdLineModuleXmlProgressWatcherTester : public QObject
+{
+  Q_OBJECT
+
+private Q_SLOTS:
+
+  void testSignalsAndValues();
+  void testMalformedXml();
+};
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleXmlProgressWatcherTester::testSignalsAndValues()
 {
   // Test data
   QByteArray filterStart = "<filter-start>\n"
@@ -106,14 +122,11 @@ bool xmlProgressWatcherTestSignalsAndValues()
   signalTester.connect(&progressWatcher, SIGNAL(filterXmlError(QString)), &signalTester, SLOT(filterXmlError(QString)));
 
   buffer.write(filterStart);
-  QCoreApplication::processEvents();
   buffer.write(filterProgress.arg(0.3).toLatin1());
-  QCoreApplication::processEvents();
   buffer.write(filterProgress.arg(0.6).toLatin1());
-  QCoreApplication::processEvents();
   buffer.write(filterProgress.arg(0.9).toLatin1());
-  QCoreApplication::processEvents();
   buffer.write(filterEnd);
+
   QCoreApplication::processEvents();
 
   QList<QString> expectedSignals;
@@ -126,24 +139,16 @@ bool xmlProgressWatcherTestSignalsAndValues()
   if (!signalTester.error.isEmpty())
   {
     qDebug() << signalTester.error;
-    return false;
-  }
-
-  if (!signalTester.checkSignals(expectedSignals))
-  {
-    return false;
+    QFAIL("XML parsing error");
   }
 
-  if (signalTester.accumulatedProgress != 1.8f)
-  {
-    qDebug() << "Progress information wrong. Expected 1.8, got" << signalTester.accumulatedProgress;
-    return false;
-  }
+  QVERIFY(signalTester.checkSignals(expectedSignals));
 
-  return true;
+  QCOMPARE(signalTester.accumulatedProgress, 1.8f);
 }
 
-bool xmlProgressWatcherTestMalformedXml()
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleXmlProgressWatcherTester::testMalformedXml()
 {
   // Test data
   QByteArray filterOutput = "<filter-start>\n"
@@ -170,8 +175,8 @@ bool xmlProgressWatcherTestMalformedXml()
   signalTester.connect(&progressWatcher, SIGNAL(filterXmlError(QString)), &signalTester, SLOT(filterXmlError(QString)));
 
   buffer.write(filterOutput);
-  QCoreApplication::processEvents();
 
+  QCoreApplication::processEvents();
 
   QList<QString> expectedSignals;
   expectedSignals << "filter.xmlError";
@@ -179,41 +184,15 @@ bool xmlProgressWatcherTestMalformedXml()
   expectedSignals << "filter.progress";
   expectedSignals << "filter.finished";
 
-  if (!signalTester.error.isEmpty())
-  {
-    qDebug() << signalTester.error;
-    //return false;
-  }
-
-  if (!signalTester.checkSignals(expectedSignals))
-  {
-    return false;
-  }
+  QVERIFY(!signalTester.error.isEmpty());
+  qDebug() << signalTester.error;
 
-  if (signalTester.accumulatedProgress != 0.5f)
-  {
-    qDebug() << "Progress information wrong. Expected 1.8, got" << signalTester.accumulatedProgress;
-    return false;
-  }
+  QVERIFY(signalTester.checkSignals(expectedSignals));
 
-  return true;
+  QCOMPARE(signalTester.accumulatedProgress, 0.5f);
 }
 
-}
 
-int ctkCmdLineModuleXmlProgressWatcherTest(int argc, char* argv[])
-{
-  QCoreApplication app(argc, argv);
-
-  if (!xmlProgressWatcherTestSignalsAndValues())
-  {
-    return EXIT_FAILURE;
-  }
-
-  if (!xmlProgressWatcherTestMalformedXml())
-  {
-    return EXIT_FAILURE;
-  }
-
-  return EXIT_SUCCESS;
-}
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkCmdLineModuleXmlProgressWatcherTest)
+#include "moc_ctkCmdLineModuleXmlProgressWatcherTest.cpp"

+ 16 - 1
Libs/CommandLineModules/Core/ctkCmdLineModuleFrontend.h

@@ -104,7 +104,12 @@ public:
 
   /**
    * @brief Return the ctkCmdLineModuleFuture, derived from QFuture to
-   * provide asynchronous processing.
+   * provide asynchronous processing and interaction with the running frontend.
+   *
+   * Note that the future returned by this method will be different after the
+   * frontend was started. Either use isRunning() to check wether this frontend
+   * is currently running or connect to the started() signal.
+   *
    * @see ctkCmdLineModuleFuture
    */
   virtual ctkCmdLineModuleFuture future() const;
@@ -172,6 +177,16 @@ public:
 
   void resetValues();
 
+Q_SIGNALS:
+
+  /**
+   * @brief This signal is emitted when the frontend is run.
+   *
+   * You can use this signal to get the ctkCmdLineModuleFuture instance
+   * from future() to interact with the running frontend.
+   */
+  void started();
+
 protected:
 
   /**

+ 57 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.cpp

@@ -0,0 +1,57 @@
+/*=============================================================================
+
+  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 "ctkCmdLineModuleFuture.h"
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFuture::ctkCmdLineModuleFuture()
+{
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFuture::ctkCmdLineModuleFuture(ctkCmdLineModuleFutureInterface* p)
+  : QFuture<ctkCmdLineModuleResult>(p)
+{
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFuture::readAllOutputData() const
+{
+  return d.outputData();
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFuture::readAllErrorData() const
+{
+  return d.errorData();
+}
+
+//----------------------------------------------------------------------------
+bool ctkCmdLineModuleFuture::canCancel() const
+{
+  return d.canCancel();
+}
+
+//----------------------------------------------------------------------------
+bool ctkCmdLineModuleFuture::canPause() const
+{
+  return d.canPause();
+}

+ 10 - 10
Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.h

@@ -22,6 +22,8 @@
 #ifndef CTKCMDLINEMODULEFUTURE_H
 #define CTKCMDLINEMODULEFUTURE_H
 
+#include "ctkCommandLineModulesCoreExport.h"
+
 #include "ctkCmdLineModuleFutureInterface.h"
 
 #include <QFuture>
@@ -37,21 +39,19 @@
  *   - bool canCancel()
  *   - bool canPause()
  */
-class ctkCmdLineModuleFuture : public QFuture<ctkCmdLineModuleResult>
+class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleFuture : public QFuture<ctkCmdLineModuleResult>
 {
 public:
 
-  ctkCmdLineModuleFuture()
-  {
-  }
+  ctkCmdLineModuleFuture();
+
+  explicit ctkCmdLineModuleFuture(ctkCmdLineModuleFutureInterface* p); // internal
 
-  explicit ctkCmdLineModuleFuture(ctkCmdLineModuleFutureInterface* p) // internal
-    : QFuture<ctkCmdLineModuleResult>(p)
-  {
-  }
+  QByteArray readAllOutputData() const;
+  QByteArray readAllErrorData() const;
 
-  bool canCancel() const { return  d.canCancel(); }
-  bool canPause() const { return d.canPause(); }
+  bool canCancel() const;
+  bool canPause() const;
 
 };
 

+ 201 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.cpp

@@ -0,0 +1,201 @@
+/*=============================================================================
+
+  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 "ctkCmdLineModuleFutureInterface.h"
+#include "ctkCmdLineModuleFutureInterface_p.h"
+
+const int ctkCmdLineModuleFutureCallOutEvent::TypeId = QEvent::registerEventType();
+
+//----------------------------------------------------------------------------
+// ctkCmdLineModuleFutureInterfacePrivate
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFutureInterfacePrivate::ctkCmdLineModuleFutureInterfacePrivate(ctkCmdLineModuleFutureInterface* q)
+  : RefCount(1)
+  , CanCancel(false)
+  , CanPause(false)
+  , q(q)
+{
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureInterfacePrivate::sendCallOut(const ctkCmdLineModuleFutureCallOutEvent &callOutEvent)
+{
+  if (OutputConnections.isEmpty())
+  {
+    return;
+  }
+
+  for (int i = 0; i < OutputConnections.count(); ++i)
+  {
+    OutputConnections.at(i)->postCmdLineModuleCallOutEvent(callOutEvent);
+  }
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureInterfacePrivate::connectOutputInterface(ctkCmdLineModuleFutureCallOutInterface *iface)
+{
+  QMutexLocker locker(&Mutex);
+
+  if (q->isStarted())
+  {
+    if (!this->OutputData.isEmpty())
+    {
+      iface->postCmdLineModuleCallOutEvent(ctkCmdLineModuleFutureCallOutEvent(ctkCmdLineModuleFutureCallOutEvent::OutputReady));
+    }
+    if (!this->ErrorData.isEmpty())
+    {
+      iface->postCmdLineModuleCallOutEvent(ctkCmdLineModuleFutureCallOutEvent(ctkCmdLineModuleFutureCallOutEvent::ErrorReady));
+    }
+  }
+
+  OutputConnections.append(iface);
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureInterfacePrivate::disconnectOutputInterface(ctkCmdLineModuleFutureCallOutInterface *iface)
+{
+  QMutexLocker lock(&Mutex);
+  const int index = OutputConnections.indexOf(iface);
+  if (index == -1)
+    return;
+  OutputConnections.removeAt(index);
+
+  iface->cmdLineModuleCallOutInterfaceDisconnected();
+}
+
+//----------------------------------------------------------------------------
+// QFutureInterface<ctkCmdLineModuleResult>
+
+//----------------------------------------------------------------------------
+QFutureInterface<ctkCmdLineModuleResult>::QFutureInterface(State initialState)
+  : QFutureInterfaceBase(initialState)
+  , d(new ctkCmdLineModuleFutureInterfacePrivate(this))
+{
+}
+
+//----------------------------------------------------------------------------
+QFutureInterface<ctkCmdLineModuleResult>::QFutureInterface(const QFutureInterface& other)
+  : QFutureInterfaceBase(other)
+  , d(other.d)
+{
+  d->RefCount.ref();
+}
+
+//----------------------------------------------------------------------------
+QFutureInterface<ctkCmdLineModuleResult>::~QFutureInterface()
+{
+  if (referenceCountIsOne())
+    resultStore().clear();
+
+  if (!d->RefCount.deref())
+  {
+    delete d;
+  }
+}
+
+//----------------------------------------------------------------------------
+QFutureInterface<ctkCmdLineModuleResult> QFutureInterface<ctkCmdLineModuleResult>::canceledResult()
+{
+  return QFutureInterface(State(Started | Finished | Canceled));
+}
+
+//----------------------------------------------------------------------------
+QFutureInterface<ctkCmdLineModuleResult>&
+QFutureInterface<ctkCmdLineModuleResult>::operator=(const QFutureInterface& other)
+{
+  if (referenceCountIsOne())
+    resultStore().clear();
+
+  QFutureInterfaceBase::operator=(other);
+
+  other.d->RefCount.ref();
+  if (!d->RefCount.deref())
+    delete d;
+  d = other.d;
+
+  // update the q pointer in the private implementation
+  d->q = this;
+
+  return *this;
+}
+
+//----------------------------------------------------------------------------
+bool QFutureInterface<ctkCmdLineModuleResult>::canCancel() const
+{
+  return d->CanCancel;
+}
+
+//----------------------------------------------------------------------------
+void QFutureInterface<ctkCmdLineModuleResult>::setCanCancel(bool canCancel)
+{
+  d->CanCancel = canCancel;
+}
+
+//----------------------------------------------------------------------------
+bool QFutureInterface<ctkCmdLineModuleResult>::canPause() const
+{
+  return d->CanPause;
+}
+
+//----------------------------------------------------------------------------
+void QFutureInterface<ctkCmdLineModuleResult>::setCanPause(bool canPause)
+{
+  d->CanPause = canPause;
+}
+
+//----------------------------------------------------------------------------
+void QFutureInterface<ctkCmdLineModuleResult>::reportOutputData(const QByteArray& outputData)
+{
+  QMutexLocker l(&d->Mutex);
+
+  if (isCanceled() || isFinished()) return;
+  d->OutputData.append(outputData);
+  d->sendCallOut(ctkCmdLineModuleFutureCallOutEvent(ctkCmdLineModuleFutureCallOutEvent::OutputReady));
+}
+
+//----------------------------------------------------------------------------
+void QFutureInterface<ctkCmdLineModuleResult>::reportErrorData(const QByteArray& errorData)
+{
+  QMutexLocker l(&d->Mutex);
+
+  if (isCanceled() || isFinished()) return;
+  d->ErrorData.append(errorData);
+  d->sendCallOut(ctkCmdLineModuleFutureCallOutEvent(ctkCmdLineModuleFutureCallOutEvent::ErrorReady));
+}
+
+//----------------------------------------------------------------------------
+QByteArray QFutureInterface<ctkCmdLineModuleResult>::outputData(int position, int size) const
+{
+  QMutexLocker l(&d->Mutex);
+  if (size < 0) size = d->OutputData.size();
+  if (size > d->OutputData.size() - position) size = d->OutputData.size() - position;
+  return QByteArray(d->OutputData.data() + position, size);
+}
+
+//----------------------------------------------------------------------------
+QByteArray QFutureInterface<ctkCmdLineModuleResult>::errorData(int position, int size) const
+{
+  QMutexLocker l(&d->Mutex);
+  if (size < 0) size = d->ErrorData.size();
+  if (size > d->ErrorData.size() - position) size = d->ErrorData.size() - position;
+  return QByteArray(d->ErrorData.data() + position, size);
+}

+ 31 - 36
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.h

@@ -22,76 +22,71 @@
 #ifndef CTKCMDLINEMODULEFUTUREINTERFACE_H
 #define CTKCMDLINEMODULEFUTUREINTERFACE_H
 
+#include <ctkCommandLineModulesCoreExport.h>
+
 #include "ctkCmdLineModuleResult.h"
 
 #include <QFutureInterface>
 
 class ctkCmdLineModuleFuture;
+class ctkCmdLineModuleFutureInterfacePrivate;
 
 /**
- * \class ctkCmdLineModuleFutureInterface
  * \ingroup CommandLineModulesCore
+ *
+ * \brief A QFutureInterface specialization.
+ *
+ * This QFutureInterface must be used by custom backend implementations to retrieve
+ * a suitable QFuture object and to report state changes to it via this interface.
  */
 template <>
-class QFutureInterface<ctkCmdLineModuleResult> : public QFutureInterfaceBase
+class CTK_CMDLINEMODULECORE_EXPORT QFutureInterface<ctkCmdLineModuleResult> : public QFutureInterfaceBase
 {
 
 public:
 
-  QFutureInterface(State initialState = NoState)
-    : QFutureInterfaceBase(initialState),
-      CanCancel(false), CanPause(false)
-  { }
-
-  QFutureInterface(const QFutureInterface &other)
-    : QFutureInterfaceBase(other),
-      CanCancel(other.CanCancel), CanPause(other.CanPause)
-  { }
-
-  ~QFutureInterface()
-  {
-    if (referenceCountIsOne())
-      resultStore().clear();
-  }
-
-  static QFutureInterface canceledResult()
-  { return QFutureInterface(State(Started | Finished | Canceled)); }
-
-  QFutureInterface& operator=(const QFutureInterface& other)
-  {
-    if (referenceCountIsOne())
-      resultStore().clear();
-    QFutureInterfaceBase::operator=(other);
-    CanCancel = other.CanCancel;
-    CanPause = other.CanPause;
-    return *this;
-  }
+  QFutureInterface(State initialState = NoState);
+
+  QFutureInterface(const QFutureInterface &other);
+
+  ~QFutureInterface();
+
+  static QFutureInterface canceledResult();
+
+  QFutureInterface& operator=(const QFutureInterface& other);
 
   inline ctkCmdLineModuleFuture future(); // implemented in ctkCmdLineModuleFuture.h
 
-  inline bool canCancel() const { return CanCancel; }
-  inline void setCanCancel(bool canCancel) { CanCancel = canCancel; }
-  inline bool canPause() const { return CanPause; }
-  inline void setCanPause(bool canPause) { CanPause = canPause; }
+  bool canCancel() const;
+  void setCanCancel(bool canCancel);
+  bool canPause() const;
+  void setCanPause(bool canPause);
 
   inline void reportResult(const ctkCmdLineModuleResult *result, int index = -1);
   inline void reportResult(const ctkCmdLineModuleResult &result, int index = -1);
   inline void reportResults(const QVector<ctkCmdLineModuleResult> &results, int beginIndex = -1, int count = -1);
   inline void reportFinished(const ctkCmdLineModuleResult *result = 0);
 
+  void reportOutputData(const QByteArray& outputData);
+  void reportErrorData(const QByteArray& errorData);
+
   inline const ctkCmdLineModuleResult &resultReference(int index) const;
   inline const ctkCmdLineModuleResult *resultPointer(int index) const;
   inline QList<ctkCmdLineModuleResult> results();
 
+  QByteArray outputData(int position = 0, int size = -1) const;
+  QByteArray errorData(int position = 0, int size = -1) const;
+
 private:
 
+  friend struct ctkCmdLineModuleFutureWatcherPrivate;
+
   QtConcurrent::ResultStore<ctkCmdLineModuleResult> &resultStore()
   { return static_cast<QtConcurrent::ResultStore<ctkCmdLineModuleResult> &>(resultStoreBase()); }
   const QtConcurrent::ResultStore<ctkCmdLineModuleResult> &resultStore() const
   { return static_cast<const QtConcurrent::ResultStore<ctkCmdLineModuleResult> &>(resultStoreBase()); }
 
-  bool CanCancel;
-  bool CanPause;
+  ctkCmdLineModuleFutureInterfacePrivate* d;
 };
 
 inline void QFutureInterface<ctkCmdLineModuleResult>::reportResult(const ctkCmdLineModuleResult *result, int index)

+ 90 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface_p.h

@@ -0,0 +1,90 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULEFUTUREINTERFACE_P_H
+#define CTKCMDLINEMODULEFUTUREINTERFACE_P_H
+
+#include <QEvent>
+#include <QAtomicInt>
+#include <QMutex>
+
+class ctkCmdLineModuleFutureCallOutEvent : public QEvent
+{
+public:
+
+  static const int TypeId;
+
+  enum CallOutType {
+    OutputReady,
+    ErrorReady
+  };
+
+  ctkCmdLineModuleFutureCallOutEvent()
+    : QEvent(static_cast<QEvent::Type>(TypeId))
+    , callOutType(CallOutType(0))
+  {}
+
+  ctkCmdLineModuleFutureCallOutEvent(CallOutType callOutType)
+    : QEvent(static_cast<QEvent::Type>(TypeId))
+    , callOutType(callOutType)
+  {}
+
+  ctkCmdLineModuleFutureCallOutEvent* clone() const
+  {
+    return new ctkCmdLineModuleFutureCallOutEvent(callOutType);
+  }
+
+  CallOutType callOutType;
+};
+
+class ctkCmdLineModuleFutureCallOutInterface
+{
+public:
+  virtual ~ctkCmdLineModuleFutureCallOutInterface() {}
+  virtual void postCmdLineModuleCallOutEvent(const ctkCmdLineModuleFutureCallOutEvent &) = 0;
+  virtual void cmdLineModuleCallOutInterfaceDisconnected() = 0;
+};
+
+class ctkCmdLineModuleFutureInterfacePrivate
+{
+public:
+
+  ctkCmdLineModuleFutureInterfacePrivate(ctkCmdLineModuleFutureInterface* q);
+
+  QAtomicInt RefCount;
+  mutable QMutex Mutex;
+
+  QList<ctkCmdLineModuleFutureCallOutInterface *> OutputConnections;
+
+  bool CanCancel;
+  bool CanPause;
+
+  QByteArray OutputData;
+  QByteArray ErrorData;
+
+  ctkCmdLineModuleFutureInterface* q;
+
+  void sendCallOut(const ctkCmdLineModuleFutureCallOutEvent &callOut);
+  void connectOutputInterface(ctkCmdLineModuleFutureCallOutInterface *iface);
+  void disconnectOutputInterface(ctkCmdLineModuleFutureCallOutInterface *iface);
+};
+
+#endif // CTKCMDLINEMODULEFUTUREINTERFACE_P_H

+ 216 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.cpp

@@ -0,0 +1,216 @@
+/*=============================================================================
+
+  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 "ctkCmdLineModuleFutureWatcher.h"
+
+#include "ctkCmdLineModuleFuture.h"
+#include "ctkCmdLineModuleFutureInterface_p.h"
+
+#include <QThread>
+#include <QCoreApplication>
+
+//----------------------------------------------------------------------------
+struct ctkCmdLineModuleFutureWatcherPrivate : public ctkCmdLineModuleFutureCallOutInterface
+{
+  ctkCmdLineModuleFutureWatcherPrivate(ctkCmdLineModuleFutureWatcher* q)
+    : q(q)
+    , pendingOutputReadyEvent(NULL)
+    , pendingErrorReadyEvent(NULL)
+    , outputPos(0)
+    , errorPos(0)
+  {}
+
+  void connectOutputInterface()
+  {
+    q->futureInterface().d->connectOutputInterface(this);
+  }
+
+  void disconnectOutputInterface(bool pendingAssignment = false)
+  {
+    if (pendingAssignment)
+    {
+      delete this->pendingOutputReadyEvent;
+      this->pendingOutputReadyEvent = NULL;
+      delete this->pendingErrorReadyEvent;
+      this->pendingErrorReadyEvent = NULL;
+    }
+
+    q->futureInterface().d->disconnectOutputInterface(this);
+  }
+
+  void postCmdLineModuleCallOutEvent(const ctkCmdLineModuleFutureCallOutEvent& callOutEvent)
+  {
+    QCoreApplication::postEvent(q, callOutEvent.clone());
+  }
+
+  void cmdLineModuleCallOutInterfaceDisconnected()
+  {
+    QCoreApplication::removePostedEvents(q, ctkCmdLineModuleFutureCallOutEvent::TypeId);
+  }
+
+  void sendCmdLineModuleCallOutEvent(ctkCmdLineModuleFutureCallOutEvent* event)
+  {
+    if (q->futureInterface().isCanceled()) return;
+
+    switch (event->callOutType)
+    {
+    case ctkCmdLineModuleFutureCallOutEvent::OutputReady:
+      emit q->outputDataReady();
+      break;
+    case ctkCmdLineModuleFutureCallOutEvent::ErrorReady:
+      emit q->errorDataReady();
+      break;
+    default: break;
+    }
+  }
+
+  ctkCmdLineModuleFutureWatcher* q;
+
+  ctkCmdLineModuleFuture Future;
+
+  ctkCmdLineModuleFutureCallOutEvent* pendingOutputReadyEvent;
+  ctkCmdLineModuleFutureCallOutEvent* pendingErrorReadyEvent;
+  int outputPos;
+  int errorPos;
+};
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFutureWatcher::ctkCmdLineModuleFutureWatcher(QObject* parent)
+  : QFutureWatcher(parent)
+  , d(new ctkCmdLineModuleFutureWatcherPrivate(this))
+{
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFutureWatcher::~ctkCmdLineModuleFutureWatcher()
+{
+  disconnectOutputInterface();
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureWatcher::setFuture(const ctkCmdLineModuleFuture& future)
+{
+  if (d->Future == future) return;
+
+  d->outputPos = 0;
+  d->errorPos = 0;
+
+  d->disconnectOutputInterface(true);
+  d->Future = future;
+  QFutureWatcher::setFuture(future);
+  d->connectOutputInterface();
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFuture ctkCmdLineModuleFutureWatcher::future() const
+{
+  return d->Future;
+}
+
+//----------------------------------------------------------------------------
+bool ctkCmdLineModuleFutureWatcher::event(QEvent *event)
+{
+  if (event->type() == ctkCmdLineModuleFutureCallOutEvent::TypeId)
+  {
+    ctkCmdLineModuleFutureCallOutEvent* callOutEvent = static_cast<ctkCmdLineModuleFutureCallOutEvent*>(event);
+
+    if (futureInterface().isPaused())
+    {
+      if (callOutEvent->callOutType == ctkCmdLineModuleFutureCallOutEvent::OutputReady &&
+          d->pendingOutputReadyEvent == NULL)
+      {
+        d->pendingOutputReadyEvent = callOutEvent->clone();
+      }
+      if (callOutEvent->callOutType == ctkCmdLineModuleFutureCallOutEvent::ErrorReady &&
+          d->pendingErrorReadyEvent == NULL)
+      {
+        d->pendingErrorReadyEvent = callOutEvent->clone();
+      }
+      return true;
+    }
+
+    d->sendCmdLineModuleCallOutEvent(callOutEvent);
+    return true;
+  }
+  else if (event->type() == QEvent::FutureCallOut)
+  {
+    bool result = QFutureWatcher::event(event);
+
+    if (futureInterface().isRunning())
+    {
+      // send all pending call outs
+      if (d->pendingOutputReadyEvent)
+      {
+        d->sendCmdLineModuleCallOutEvent(d->pendingOutputReadyEvent);
+        delete d->pendingOutputReadyEvent;
+        d->pendingOutputReadyEvent = NULL;
+      }
+      if (d->pendingErrorReadyEvent)
+      {
+        d->sendCmdLineModuleCallOutEvent(d->pendingErrorReadyEvent);
+        delete d->pendingErrorReadyEvent;
+        d->pendingErrorReadyEvent = NULL;
+      }
+    }
+    return result;
+  }
+  return QFutureWatcher::event(event);
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFutureWatcher::readPendingOutputData() const
+{
+  QByteArray output = futureInterface().outputData(d->outputPos);
+  d->outputPos += output.size();
+  return output;
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFutureWatcher::readPendingErrorData() const
+{
+  QByteArray errorOutput = futureInterface().errorData(d->errorPos);
+  d->errorPos += errorOutput.size();
+  return errorOutput;
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFutureWatcher::readAllOutputData() const
+{
+  return d->Future.readAllOutputData();
+}
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleFutureWatcher::readAllErrorData() const
+{
+  return d->Future.readAllErrorData();
+}
+
+//----------------------------------------------------------------------------
+const ctkCmdLineModuleFutureInterface& ctkCmdLineModuleFutureWatcher::futureInterface() const
+{
+  return d->Future.d;
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleFutureInterface& ctkCmdLineModuleFutureWatcher::futureInterface()
+{
+  return d->Future.d;
+}

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

@@ -0,0 +1,86 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULEFUTUREWATCHER_H
+#define CTKCMDLINEMODULEFUTUREWATCHER_H
+
+#include "ctkCommandLineModulesCoreExport.h"
+
+#include "ctkCmdLineModuleResult.h"
+#include "ctkCmdLineModuleFutureInterface.h"
+
+#include <QFutureWatcher>
+
+class ctkCmdLineModuleFuture;
+struct ctkCmdLineModuleFutureWatcherPrivate;
+
+/**
+ * @brief The ctkCmdLineModuleFutureWatcher class provides enhanced monitoring of a
+ *        ctkCmdLineModuleFuture using signals and slots.
+ *
+ * This class enhances the standard QFutureWatcher class by adding the two signals
+ * outputDataReady() and errorDataReady(). These signals are fired whenver the watched
+ * future reports new output data (usually text written to the standard output channel) or
+ * new error data (usually text written to the standard error channel).
+ *
+ * Use readPendingOutputData() or readPendingErrorData() to get the newly added data.
+ */
+class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleFutureWatcher : public QFutureWatcher<ctkCmdLineModuleResult>
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleFutureWatcher(QObject* parent = 0);
+
+  ~ctkCmdLineModuleFutureWatcher();
+
+  void setFuture(const ctkCmdLineModuleFuture& future);
+
+  ctkCmdLineModuleFuture future() const;
+
+  bool event(QEvent* event);
+
+  QByteArray readPendingOutputData() const;
+  QByteArray readPendingErrorData() const;
+
+  QByteArray readAllOutputData() const;
+  QByteArray readAllErrorData() const;
+
+Q_SIGNALS:
+
+  void outputDataReady();
+  void errorDataReady();
+
+private:
+
+  friend struct ctkCmdLineModuleFutureWatcherPrivate;
+
+  QScopedPointer<ctkCmdLineModuleFutureWatcherPrivate> d;
+
+  const ctkCmdLineModuleFutureInterface& futureInterface() const;
+  ctkCmdLineModuleFutureInterface& futureInterface();
+
+  // not imlemented
+  void setFuture(const QFuture<ctkCmdLineModuleResult>&);
+};
+
+#endif // CTKCMDLINEMODULEFUTUREWATCHER_H

+ 1 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp

@@ -282,5 +282,6 @@ ctkCmdLineModuleFuture ctkCmdLineModuleManager::run(ctkCmdLineModuleFrontend *fr
 
   ctkCmdLineModuleFuture future = d->SchemeToBackend[frontend->location().scheme()]->run(frontend);
   frontend->setFuture(future);
+  emit frontend->started();
   return future;
 }

+ 38 - 2
Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.cpp

@@ -22,6 +22,7 @@
 #include "ctkCmdLineModuleXmlProgressWatcher.h"
 
 #include <QIODevice>
+#include <QProcess>
 #include <QXmlStreamReader>
 
 #include <QDebug>
@@ -42,7 +43,14 @@ class ctkCmdLineModuleXmlProgressWatcherPrivate
 public:
 
   ctkCmdLineModuleXmlProgressWatcherPrivate(QIODevice* input, ctkCmdLineModuleXmlProgressWatcher* qq)
-    : input(input), readPos(0), q(qq), error(false), currentProgress(0)
+    : input(input), process(NULL), readPos(0), q(qq), error(false), currentProgress(0)
+  {
+    // wrap the content in an artifical root element
+    reader.addData("<module-root>");
+  }
+
+  ctkCmdLineModuleXmlProgressWatcherPrivate(QProcess* input, ctkCmdLineModuleXmlProgressWatcher* qq)
+    : input(input), process(input), readPos(0), q(qq), error(false), currentProgress(0)
   {
     // wrap the content in an artifical root element
     reader.addData("<module-root>");
@@ -56,9 +64,15 @@ public:
     parseProgressXml();
   }
 
+  void _q_readyReadError()
+  {
+    emit q->errorDataAvailable(process->readAllStandardError());
+  }
+
   void parseProgressXml()
   {
     QXmlStreamReader::TokenType type = reader.readNext();
+    QByteArray outputData;
     while(type != QXmlStreamReader::Invalid)
     {
       switch(type)
@@ -66,7 +80,14 @@ public:
       case QXmlStreamReader::NoToken: break;
       case QXmlStreamReader::Characters:
       {
-        if (stack.empty()) break;
+        if (stack.empty())
+        {
+          QByteArray output(reader.text().toString().toAscii());
+          // get rid of a possible newline after the last xml end tag
+          if (output.startsWith('\n')) output = output.remove(0,1);
+          outputData.append(output);
+          break;
+        }
 
         if (stack.size() == 2 && stack.front() == FILTER_START)
         {
@@ -160,6 +181,10 @@ public:
                                .arg(reader.lineNumber()).arg(reader.columnNumber()) + reader.errorString());
       }
     }
+    if (!outputData.isEmpty())
+    {
+      emit q->outputDataAvailable(outputData);
+    }
   }
 
   void unexpectedNestedElement(const QString& element)
@@ -173,6 +198,7 @@ public:
   }
 
   QIODevice* input;
+  QProcess* process;
   qint64 readPos;
   ctkCmdLineModuleXmlProgressWatcher* q;
   bool error;
@@ -198,6 +224,16 @@ ctkCmdLineModuleXmlProgressWatcher::ctkCmdLineModuleXmlProgressWatcher(QIODevice
 }
 
 //----------------------------------------------------------------------------
+ctkCmdLineModuleXmlProgressWatcher::ctkCmdLineModuleXmlProgressWatcher(QProcess* input)
+  : d(new ctkCmdLineModuleXmlProgressWatcherPrivate(input, this))
+{
+  if (d->input == NULL) return;
+
+  connect(input, SIGNAL(readyReadStandardOutput()), SLOT(_q_readyRead()));
+  connect(input, SIGNAL(readyReadStandardError()), SLOT(_q_readyReadError()));
+}
+
+//----------------------------------------------------------------------------
 ctkCmdLineModuleXmlProgressWatcher::~ctkCmdLineModuleXmlProgressWatcher()
 {
 }

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

@@ -29,6 +29,7 @@
 class ctkCmdLineModuleXmlProgressWatcherPrivate;
 
 class QIODevice;
+class QProcess;
 
 /**
  * \class ctkCmdLineModuleXmlProgressWatcher
@@ -42,6 +43,7 @@ class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleXmlProgressWatcher : public Q
 public:
 
   ctkCmdLineModuleXmlProgressWatcher(QIODevice* input);
+  ctkCmdLineModuleXmlProgressWatcher(QProcess* input);
   ~ctkCmdLineModuleXmlProgressWatcher();
 
 Q_SIGNALS:
@@ -51,11 +53,15 @@ Q_SIGNALS:
   void filterFinished(const QString& name);
   void filterXmlError(const QString& error);
 
+  void outputDataAvailable(const QByteArray& outputData);
+  void errorDataAvailable(const QByteArray& errorData);
+
 private:
 
   friend class ctkCmdLineModuleXmlProgressWatcherPrivate;
 
   Q_PRIVATE_SLOT(d, void _q_readyRead())
+  Q_PRIVATE_SLOT(d, void _q_readyReadError())
 
   QScopedPointer<ctkCmdLineModuleXmlProgressWatcherPrivate> d;
 };

+ 12 - 13
Libs/CommandLineModules/Frontend/QtGui/Testing/Cpp/ctkCmdLineModuleQtXslTransformTest.cpp

@@ -34,6 +34,9 @@ class ctkCmdLineModuleQtXslTransformTester: public QObject
 {
   Q_OBJECT
 private slots:
+
+  void initTestCase();
+
   void testTransform();
   void testTransform_data();
 
@@ -171,6 +174,14 @@ QString integerWidgetSpinBoxFooter =
 
 
 // ----------------------------------------------------------------------------
+void ctkCmdLineModuleQtXslTransformTester::initTestCase()
+{
+  // Introduce a dummy linker dependency to CTKCommandLineModulesFrontendQtGui to
+  // get access to the ctkCmdLineModuleXmlToQtUi.xsl resource.
+  ctkCmdLineModuleFrontendFactoryQtGui guiFactory;
+}
+
+// ----------------------------------------------------------------------------
 void ctkCmdLineModuleQtXslTransformTester::testTransform()
 {
   ctkCmdLineModuleXslTransform transformer;
@@ -452,17 +463,5 @@ void ctkCmdLineModuleQtXslTransformTester::testXslExtraTransformation_data()
 }
 
 // ----------------------------------------------------------------------------
-//CTK_TEST_MAIN(ctkCmdLineModuleQtXslTransformTest)
-int ctkCmdLineModuleQtXslTransformTest(int argc, char *argv[])
-{
-  QCoreApplication app(argc, argv);
-  QTEST_DISABLE_KEYPAD_NAVIGATION
-
-  // Introduce a dummy linker dependency to CTKCommandLineModulesFrontendQtGui to
-  // get access to the ctkCmdLineModuleXmlToQtUi.xsl resource.
-  ctkCmdLineModuleFrontendFactoryQtGui guiFactory;
-
-  ctkCmdLineModuleQtXslTransformTester tc;
-  return QTest::qExec(&tc, argc, argv);
-}
+CTK_TEST_MAIN(ctkCmdLineModuleQtXslTransformTest)
 #include "moc_ctkCmdLineModuleQtXslTransformTest.cpp"

+ 8 - 3
Libs/CommandLineModules/Testing/Cpp/CMakeLists.txt

@@ -2,6 +2,7 @@ set(KIT CTKCommandLineModules)
 set(LIBRARY_NAME ${KIT})
 
 set(_test_srcs)
+set(_test_mocs)
 
 if(CTK_LIB_CommandLineModules/Frontend/QtGui)
   set(QT_USE_QTUITOOLS 1)
@@ -9,12 +10,11 @@ if(CTK_LIB_CommandLineModules/Frontend/QtGui)
 
   if(CTK_LIB_CommandLineModules/Backend/LocalProcess)
     list(APPEND _test_srcs ctkCmdLineModuleFutureTest.cpp)
+    list(APPEND _test_mocs ctkCmdLineModuleFutureTest.cpp)
   endif()
   if(CTK_LIB_CommandLineModules/Backend/FunctionPointer)
     list(APPEND _test_srcs ctkCmdLineModuleQtCustomizationTest.cpp)
-    QT4_GENERATE_MOCS(
-      ctkCmdLineModuleQtCustomizationTest.cpp
-    )
+    list(APPEND _test_mocs ctkCmdLineModuleQtCustomizationTest.cpp)
   endif()
 endif()
 
@@ -65,6 +65,11 @@ endif()
 
 set(Tests_MOC_CPP)
 QT4_WRAP_CPP(Tests_MOC_CPP ${Tests_MOC_SRCS})
+
+if(_test_mocs)
+  QT4_GENERATE_MOCS(${_test_mocs})
+endif()
+
 set(Tests_UI_CPP)
 if(TEST_UI_FORMS)
   QT4_WRAP_UI(Tests_UI_CPP ${Tests_UI_FORMS})

+ 203 - 202
Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp

@@ -27,37 +27,21 @@
 #include <ctkCmdLineModuleParameter.h>
 #include <ctkCmdLineModuleRunException.h>
 #include <ctkCmdLineModuleFuture.h>
+#include <ctkCmdLineModuleFutureWatcher.h>
 
 #include "ctkCmdLineModuleSignalTester.h"
 
 #include "ctkCmdLineModuleBackendLocalProcess.h"
 
+#include "ctkTest.h"
+
 #include <QVariant>
 #include <QCoreApplication>
 #include <QDebug>
 #include <QFutureWatcher>
 
-#include <cstdlib>
-
-
-#ifdef Q_OS_WIN
-#include <windows.h>
-#else
-#include <time.h>
-#endif
-
-void sleep_ms(int ms)
-{
-#ifdef Q_OS_WIN
-  Sleep(ms);
-#else
-  struct timespec nanostep;
-  nanostep.tv_sec = ms / 1000;
-  nanostep.tv_nsec = ((ms % 1000) * 1000.0 * 1000.0);
-  nanosleep(&nanostep, NULL);
-#endif
-}
 
+//-----------------------------------------------------------------------------
 class ctkCmdLineModuleFrontendMockupFactory : public ctkCmdLineModuleFrontendFactory
 {
 public:
@@ -97,110 +81,167 @@ public:
   virtual QString description() const { return "A mock-up factory for testing."; }
 };
 
-bool futureTestStartFinish(ctkCmdLineModuleManager* manager, ctkCmdLineModuleFrontend* frontend)
+//-----------------------------------------------------------------------------
+class ctkCmdLineModuleFutureTester : public QObject
 {
-  qDebug() << "Testing ctkCmdLineModuleFuture start/finish signals.";
+  Q_OBJECT
 
-  QList<QString> expectedSignals;
-  expectedSignals.push_back("module.started");
-  expectedSignals.push_back("module.finished");
+public Q_SLOTS:
 
-  ctkCmdLineModuleSignalTester signalTester;
+  void ouputDataReady();
+  void errorDataReady();
 
-  QFutureWatcher<ctkCmdLineModuleResult> watcher;
-  QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
-  QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
+private Q_SLOTS:
 
-  ctkCmdLineModuleFuture future = manager->run(frontend);
-  watcher.setFuture(future);
+  void initTestCase();
 
-  try
+  void init();
+  void cleanup();
+
+  void testStartFinish();
+  void testProgress();
+  void testPauseAndCancel();
+  void testOutput();
+  void testError();
+
+private:
+
+  QByteArray outputData;
+  QByteArray errorData;
+
+  ctkCmdLineModuleFutureWatcher* currentWatcher;
+
+  ctkCmdLineModuleFrontendMockupFactory factory;
+  ctkCmdLineModuleBackendLocalProcess backend;
+
+  ctkCmdLineModuleManager manager;
+
+  ctkCmdLineModuleReference moduleRef;
+  ctkCmdLineModuleFrontend* frontend;
+};
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::ouputDataReady()
+{
+  if (this->currentWatcher)
   {
-    future.waitForFinished();
+    outputData.append(currentWatcher->readPendingOutputData());
   }
-  catch (const ctkCmdLineModuleRunException& e)
+}
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::errorDataReady()
+{
+  if (this->currentWatcher)
   {
-    qDebug() << e;
-    return false;
+    errorData.append(currentWatcher->readPendingErrorData());
   }
+}
 
-  // process pending events
-  QCoreApplication::processEvents();
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::initTestCase()
+{
+  manager.registerBackend(&backend);
+
+  QUrl moduleUrl = QUrl::fromLocalFile(QCoreApplication::applicationDirPath() + "/ctkCmdLineModuleTestBed");
+  moduleRef = manager.registerModule(moduleUrl);
+}
 
-  return signalTester.checkSignals(expectedSignals);
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::init()
+{
+  currentWatcher = 0;
+  frontend = factory.create(moduleRef);
 }
 
-bool futureTestProgress(ctkCmdLineModuleManager* manager, ctkCmdLineModuleFrontend* frontend)
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::cleanup()
 {
-  qDebug() << "Testing ctkCmdLineModuleFuture progress signals.";
+  delete frontend;
+  outputData.clear();
+  errorData.clear();
+}
 
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testStartFinish()
+{
   QList<QString> expectedSignals;
-  expectedSignals.push_back("module.started");
-  // this signal is send when connecting a QFutureWatcher to
-  // an already started QFuture
-  expectedSignals.push_back("module.progressValueChanged");
-
-  // the following two signals are send when the module reports "filter start"
-  expectedSignals.push_back("module.progressValueChanged");
-  expectedSignals.push_back("module.progressTextChanged");
-
-  // this signal is send when the module reports progress for "output1"
-  expectedSignals.push_back("module.progressValueChanged");
-
-  // the following two signal are sent at the end to report
-  // completion and the full standard output text.
-  expectedSignals.push_back("module.progressValueChanged");
-  expectedSignals.push_back("module.progressTextChanged");
-  expectedSignals.push_back("module.finished");
+  expectedSignals << "module.started"
+                  << "module.progressRangeChanged(0,0)"
+                  << "module.progressValueChanged(0)"
+                  << "module.progressRangeChanged(0,1000)"
+                  << "module.errorReady"
+                  << "module.progressValueChanged(1000)"
+                  << "module.finished";
 
   ctkCmdLineModuleSignalTester signalTester;
 
-  QFutureWatcher<ctkCmdLineModuleResult> watcher;
-  QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
-  QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &signalTester, SLOT(moduleProgressValueChanged(int)));
-  QObject::connect(&watcher, SIGNAL(progressTextChanged(QString)), &signalTester, SLOT(moduleProgressTextChanged(QString)));
-  QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
+  ctkCmdLineModuleFuture future = manager.run(frontend);
+  signalTester.setFuture(future);
+  future.waitForFinished();
+
+  QCoreApplication::processEvents();
+  QVERIFY(signalTester.checkSignals(expectedSignals));
+}
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testProgress()
+{
+  QList<QString> expectedSignals;
+  expectedSignals << "module.started"
+                     // the following signals are send when connecting a QFutureWatcher to
+                     // an already started QFuture
+                  << "module.progressRangeChanged(0,0)"
+                  << "module.progressValueChanged(0)"
+                  << "module.progressRangeChanged(0,1000)"
+
+                     // the test module always reports error data when starting
+                  << "module.errorReady"
+
+                     // the following two signals are send when the module reports "filter start"
+                  << "module.progressValueChanged(1)"
+                  << "module.progressTextChanged(Test Filter)"
+
+                     // this signal is send when the module reports progress for "output1"
+                  << "module.progressValueChanged(999)"
+
+                     // the output data (the order is not really deterministic here...)
+                  << "module.outputReady"
+
+                     // the following signal is sent at the end to report completion
+                  << "module.progressValueChanged(1000)"
+                  << "module.finished";
+
+  ctkCmdLineModuleSignalTester signalTester;
 
   frontend->setValue("numOutputsVar", 1);
-  ctkCmdLineModuleFuture future = manager->run(frontend);
-  watcher.setFuture(future);
+  ctkCmdLineModuleFuture future = manager.run(frontend);
+  signalTester.setFuture(future);
 
-  try
-  {
-    future.waitForFinished();
-  }
-  catch (const ctkCmdLineModuleRunException& e)
-  {
-    qDebug() << e;
-    return false;
-  }
+  future.waitForFinished();
 
   // process pending events
   QCoreApplication::processEvents();
 
-  return signalTester.checkSignals(expectedSignals);
+  QVERIFY(signalTester.checkSignals(expectedSignals));
 }
 
-bool futureTestPauseAndCancel(ctkCmdLineModuleManager* manager, ctkCmdLineModuleFrontend* frontend)
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testPauseAndCancel()
 {
-  qDebug() << "Testing ctkCmdLineModuleFuture pause and cancel capabilities";
-
-
   ctkCmdLineModuleSignalTester signalTester;
 
-  QFutureWatcher<ctkCmdLineModuleResult> watcher;
-  QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
-  QObject::connect(&watcher, SIGNAL(paused()), &signalTester, SLOT(modulePaused()));
-  QObject::connect(&watcher, SIGNAL(resumed()), &signalTester, SLOT(moduleResumed()));
-  QObject::connect(&watcher, SIGNAL(canceled()), &signalTester, SLOT(moduleCanceled()));
-  QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
-
   frontend->setValue("runtimeVar", 60);
-  ctkCmdLineModuleFuture future = manager->run(frontend);
-  watcher.setFuture(future);
+  ctkCmdLineModuleFuture future = manager.run(frontend);
+  signalTester.setFuture(future);
 
   QList<QString> expectedSignals;
-  expectedSignals.push_back("module.started");
+  expectedSignals << "module.started"
+                  << "module.progressRangeChanged(0,0)"
+                  << "module.progressValueChanged(0)"
+                  << "module.progressRangeChanged(0,1000)"
+                  << "module.errorReady";
+
   if (future.canPause())
   {
     // Due to Qt bug 12152, these two signals are reversed
@@ -214,154 +255,114 @@ bool futureTestPauseAndCancel(ctkCmdLineModuleManager* manager, ctkCmdLineModule
   }
   expectedSignals.push_back("module.finished");
 
-  sleep_ms(500);
-
-  QCoreApplication::processEvents();
-  future.pause();
-  sleep_ms(500);
-  QCoreApplication::processEvents();
+  QTest::qWait(100);
 
   if (future.canPause())
   {
-    if (!(future.isPaused() && future.isRunning()))
-    {
-      qDebug() << "Pause state wrong";
-      future.setPaused(false);
-      future.cancel();
-      QCoreApplication::processEvents();
-      future.waitForFinished();
-      return false;
-    }
+    future.pause();
+    QTest::qWait(100);
+    QVERIFY(future.isPaused());
   }
 
-  future.togglePaused();
-  QCoreApplication::processEvents();
-
-  sleep_ms(500);
+  QVERIFY(future.isRunning());
 
-  if (future.isPaused() && future.isRunning())
+  if (future.canPause())
   {
-    qDebug() << "Pause state wrong (module is paused, but it shouldn't be)";
-    future.cancel();
-    QCoreApplication::processEvents();
-    future.waitForFinished();
-    return false;
+    future.togglePaused();
+    QTest::qWait(100);
   }
 
-  try
+  QVERIFY(!future.isPaused());
+  QVERIFY(future.isRunning());
+
+  if (future.canCancel())
   {
+    // give event processing a chance before killing the process
+    QTest::qWait(200);
     future.cancel();
-    QCoreApplication::processEvents();
-    future.waitForFinished();
-  }
-  catch (const ctkCmdLineModuleRunException& e)
-  {
-    qDebug() << e;
-    return false;
   }
+  future.waitForFinished();
 
   // process pending events
   QCoreApplication::processEvents();
 
-  if (!signalTester.checkSignals(expectedSignals))
-  {
-    return false;
-  }
+  QVERIFY(future.isCanceled());
+  QVERIFY(future.isFinished());
 
-  if (!(future.isCanceled() && future.isFinished()))
-  {
-    qDebug() << "Cancel state wrong";
-    return false;
-  }
-  return true;
+  QVERIFY(signalTester.checkSignals(expectedSignals));
 }
 
-bool futureTestError(ctkCmdLineModuleManager* manager, ctkCmdLineModuleFrontend* frontend)
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testOutput()
 {
-  qDebug() << "Testing ctkCmdLineModuleFuture error reporting.";
+  ctkCmdLineModuleSignalTester signalTester;
 
-  frontend->setValue("fileVar", "output1");
-  frontend->setValue("exitCodeVar", 24);
-  frontend->setValue("errorTextVar", "Some error occured\n");
+  connect(signalTester.watcher(), SIGNAL(outputDataReady()), SLOT(ouputDataReady()));
+  connect(signalTester.watcher(), SIGNAL(errorDataReady()), SLOT(errorDataReady()));
 
-  QFutureWatcher<ctkCmdLineModuleResult> watcher;
-  ctkCmdLineModuleFuture future = manager->run(frontend);
-  watcher.setFuture(future);
+  this->currentWatcher = signalTester.watcher();
 
-  try
-  {
-    future.waitForFinished();
-    return EXIT_FAILURE;
-  }
-  catch (const ctkCmdLineModuleRunException& e)
-  {
-    Q_ASSERT_X(e.errorCode() == 24, __FUNCTION__, "Error code mismatch");
-    Q_ASSERT_X(e.errorString() == "Some error occured\n", __FUNCTION__, "Error text mismatch");
-  }
+  frontend->setValue("numOutputsVar", 2);
+  frontend->setValue("errorTextVar", "Final error msg.");
+  ctkCmdLineModuleFuture future = manager.run(frontend);
+  signalTester.setFuture(future);
+
+  future.waitForFinished();
 
   // process pending events
   QCoreApplication::processEvents();
 
-  return true;
+  QVERIFY(future.isFinished());
+
+  QList<QString> expectedSignals;
+  expectedSignals << "module.started"
+                  << "module.progressRangeChanged(0,0)"
+                  << "module.progressValueChanged(0)"
+                  << "module.progressRangeChanged(0,1000)"
+                  << "module.errorReady"
+                  << "module.progressValueChanged(1)"
+                  << "module.progressTextChanged(Test Filter)"
+                  << "module.progressValueChanged(500)"
+                  << "module.outputReady"
+                  << "module.progressValueChanged(999)"
+                  << "module.outputReady"
+                  << "module.errorReady"
+                  << "module.progressValueChanged(1000)"
+                  << "module.finished";
+
+  QVERIFY(signalTester.checkSignals(expectedSignals));
+
+  const char* expectedOutput = "Output 1\nOutput 2\n";
+  const char* expectedError = "A superficial error message.\nFinal error msg.";
+
+  QCOMPARE(this->outputData.data(), expectedOutput);
+  QCOMPARE(this->errorData.data(), expectedError);
+
+  QCOMPARE(future.readAllOutputData().data(), expectedOutput);
+  QCOMPARE(future.readAllErrorData().data(), expectedError);
 }
 
-int ctkCmdLineModuleFutureTest(int argc, char* argv[])
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testError()
 {
-  QCoreApplication app(argc, argv);
-
-  ctkCmdLineModuleFrontendMockupFactory factory;
-  ctkCmdLineModuleBackendLocalProcess backend;
+  frontend->setValue("fileVar", "output1");
+  frontend->setValue("exitCodeVar", 24);
+  frontend->setValue("errorTextVar", "Some error occured\n");
 
-  ctkCmdLineModuleManager manager;
-  manager.registerBackend(&backend);
+  ctkCmdLineModuleFuture future = manager.run(frontend);
 
-  QUrl moduleUrl = QUrl::fromLocalFile(app.applicationDirPath() + "/ctkCmdLineModuleTestBed");
-  ctkCmdLineModuleReference moduleRef;
   try
   {
-    moduleRef = manager.registerModule(moduleUrl);
-  }
-  catch (const ctkException& e)
-  {
-    qCritical() << "Module at" << moduleUrl << "could not be registered: " << e;
-  }
-
-  {
-    QScopedPointer<ctkCmdLineModuleFrontend> frontend(factory.create(moduleRef));
-    if (!futureTestStartFinish(&manager, frontend.data()))
-    {
-      return EXIT_FAILURE;
-    }
-  }
-
-  {
-    QScopedPointer<ctkCmdLineModuleFrontend> frontend(factory.create(moduleRef));
-    if (!futureTestProgress(&manager, frontend.data()))
-    {
-      return EXIT_FAILURE;
-    }
-  }
-
-  {
-    QScopedPointer<ctkCmdLineModuleFrontend> frontend(factory.create(moduleRef));
-    if (!futureTestError(&manager, frontend.data()))
-    {
-      return EXIT_FAILURE;
-    }
+    future.waitForFinished();
+    QFAIL("Expected exception not thrown.");
   }
-
+  catch (const ctkCmdLineModuleRunException& e)
   {
-    QScopedPointer<ctkCmdLineModuleFrontend> frontend(factory.create(moduleRef));
-    if (!futureTestPauseAndCancel(&manager, frontend.data()))
-    {
-      return EXIT_FAILURE;
-    }
+    QVERIFY2(e.errorCode() == 24, "Test matching error code");
+    QCOMPARE(future.readAllErrorData().data(), "A superficial error message.\nSome error occured\n");
   }
-
-  //  if (!futureTestResultReady(frontend.data()))
-  //  {
-  //    return EXIT_FAILURE;
-  //  }
-
-  return EXIT_SUCCESS;
 }
+
+// ----------------------------------------------------------------------------
+CTK_TEST_MAIN(ctkCmdLineModuleFutureTest)
+#include "moc_ctkCmdLineModuleFutureTest.cpp"

+ 66 - 21
Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.cpp

@@ -23,64 +23,109 @@
 
 #include <QDebug>
 
+ctkCmdLineModuleSignalTester::ctkCmdLineModuleSignalTester()
+{
+  connect(&Watcher, SIGNAL(started()), SLOT(moduleStarted()));
+  connect(&Watcher, SIGNAL(canceled()), SLOT(moduleCanceled()));
+  connect(&Watcher, SIGNAL(finished()), SLOT(moduleFinished()));
+
+  connect(&Watcher, SIGNAL(paused()), SLOT(modulePaused()));
+  connect(&Watcher, SIGNAL(resumed()), SLOT(moduleResumed()));
+
+  connect(&Watcher, SIGNAL(resultReadyAt(int)), SLOT(resultReadyAt(int)));
+  connect(&Watcher, SIGNAL(resultsReadyAt(int,int)), SLOT(resultReadyAt(int,int)));
+
+  connect(&Watcher, SIGNAL(progressRangeChanged(int,int)), SLOT(progressRangeChanged(int,int)));
+  connect(&Watcher, SIGNAL(progressTextChanged(QString)), SLOT(progressTextChanged(QString)));
+  connect(&Watcher, SIGNAL(progressValueChanged(int)), SLOT(progressValueChanged(int)));
+
+  connect(&Watcher, SIGNAL(outputDataReady()), SLOT(outputDataReady()));
+  connect(&Watcher, SIGNAL(errorDataReady()), SLOT(errorDataReady()));
+}
+
+void ctkCmdLineModuleSignalTester::setFuture(const ctkCmdLineModuleFuture &future)
+{
+  this->Watcher.setFuture(future);
+}
+
+ctkCmdLineModuleFutureWatcher *ctkCmdLineModuleSignalTester::watcher()
+{
+  return &this->Watcher;
+}
+
 void ctkCmdLineModuleSignalTester::moduleStarted()
 {
-  events.push_back("module.started");
+  Events.push_back("module.started");
 }
 
 void ctkCmdLineModuleSignalTester::moduleFinished()
 {
-  events.push_back("module.finished");
+  Events.push_back("module.finished");
 }
 
-void ctkCmdLineModuleSignalTester::moduleProgressValueChanged(int /*progress*/)
+void ctkCmdLineModuleSignalTester::moduleProgressValueChanged(int progress)
 {
-  events.push_back("module.progressValueChanged");
+  Events.push_back(QString("module.progressValueChanged(%1)").arg(progress));
 }
 
-void ctkCmdLineModuleSignalTester::moduleProgressTextChanged(const QString &/*text*/)
+void ctkCmdLineModuleSignalTester::moduleProgressTextChanged(const QString& text)
 {
-  events.push_back("module.progressTextChanged");
+  Events.push_back(QString("module.progressTextChanged(\"%1\")").arg(text));
 }
 
 void ctkCmdLineModuleSignalTester::modulePaused()
 {
-  events.push_back("module.paused");
+  Events.push_back("module.paused");
 }
 
 void ctkCmdLineModuleSignalTester::moduleResumed()
 {
-  events.push_back("module.resumed");
+  Events.push_back("module.resumed");
 }
 
 void ctkCmdLineModuleSignalTester::moduleCanceled()
 {
-  events.push_back("module.canceled");
+  Events.push_back("module.canceled");
+}
+
+void ctkCmdLineModuleSignalTester::resultReadyAt(int resultIndex)
+{
+  Events.push_back(QString("module.resultReadyAt(%1)").arg(resultIndex));
+}
+
+void ctkCmdLineModuleSignalTester::resultReadyAt(int beginIndex, int endIndex)
+{
+  Events.push_back(QString("module.resultReadyAt(%1,%2)").arg(beginIndex).arg(endIndex));
+}
+
+void ctkCmdLineModuleSignalTester::progressRangeChanged(int minimum, int maximum)
+{
+  Events.push_back(QString("module.progressRangeChanged(%1,%2)").arg(minimum).arg(maximum));
 }
 
-void ctkCmdLineModuleSignalTester::filterStarted(const QString &/*name*/, const QString &/*comment*/)
+void ctkCmdLineModuleSignalTester::progressValueChanged(int progressValue)
 {
-  events.push_back("filter.started");
+  Events.push_back(QString("module.progressValueChanged(%1)").arg(progressValue));
 }
 
-void ctkCmdLineModuleSignalTester::filterProgress(float /*progress*/)
+void ctkCmdLineModuleSignalTester::progressTextChanged(const QString &progressText)
 {
-  events.push_back("filter.progress");
+  Events.push_back(QString("module.progressTextChanged(%1)").arg(progressText));
 }
 
-void ctkCmdLineModuleSignalTester::filterFinished(const QString &/*name*/)
+void ctkCmdLineModuleSignalTester::outputDataReady()
 {
-  events.push_back("filter.finished");
+  Events.push_back("module.outputReady");
 }
 
-void ctkCmdLineModuleSignalTester::filterXmlError(const QString &/*error*/)
+void ctkCmdLineModuleSignalTester::errorDataReady()
 {
-  events.push_back("filter.xmlError");
+  Events.push_back("module.errorReady");
 }
 
 bool ctkCmdLineModuleSignalTester::checkSignals(const QList<QString>& expectedSignals)
 {
-  if (events.size() != expectedSignals.size())
+  if (Events.size() != expectedSignals.size())
   {
     dumpSignals(expectedSignals);
     return false;
@@ -88,7 +133,7 @@ bool ctkCmdLineModuleSignalTester::checkSignals(const QList<QString>& expectedSi
 
   for (int i=0; i < expectedSignals.size(); ++i)
   {
-    if (expectedSignals[i] != events[i])
+    if (expectedSignals[i] != Events[i])
     {
       dumpSignals(expectedSignals);
       return false;
@@ -99,11 +144,11 @@ bool ctkCmdLineModuleSignalTester::checkSignals(const QList<QString>& expectedSi
 
 void ctkCmdLineModuleSignalTester::dumpSignals(const QList<QString>& expectedSignals)
 {
-  int max = events.size() > expectedSignals.size() ? events.size() : expectedSignals.size();
+  int max = Events.size() > expectedSignals.size() ? Events.size() : expectedSignals.size();
   qDebug() << "Expected signal --  Actual signal";
   for (int i = 0; i < max; ++i)
   {
-    QString sig = i < events.size() ? events[i] : QString();
+    QString sig = i < Events.size() ? Events[i] : QString();
     if (i < expectedSignals.size())
     {
       qDebug() << " " << expectedSignals[i] << "--" << sig;

+ 20 - 5
Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.h

@@ -22,15 +22,24 @@
 #ifndef CTKCMDLINEMODULESIGNALTESTER_H
 #define CTKCMDLINEMODULESIGNALTESTER_H
 
+#include "ctkCmdLineModuleFutureWatcher.h"
+
 #include <QObject>
 #include <QList>
 
+class ctkCmdLineModuleFuture;
+
 class ctkCmdLineModuleSignalTester : public QObject
 {
   Q_OBJECT
 
 public:
 
+  ctkCmdLineModuleSignalTester();
+
+  void setFuture(const ctkCmdLineModuleFuture& future);
+  ctkCmdLineModuleFutureWatcher* watcher();
+
   bool checkSignals(const QList<QString>& expectedSignals);
   void dumpSignals(const QList<QString>& expectedSignals);
 
@@ -46,14 +55,20 @@ public Q_SLOTS:
   virtual void moduleResumed();
   virtual void moduleCanceled();
 
-  virtual void filterStarted(const QString& name, const QString& comment);
-  virtual void filterProgress(float progress);
-  virtual void filterFinished(const QString& name);
-  virtual void filterXmlError(const QString& error);
+  virtual void resultReadyAt(int resultIndex);
+  virtual void resultReadyAt(int beginIndex, int endIndex);
+
+  virtual void progressRangeChanged(int minimum, int maximum);
+  virtual void progressValueChanged(int progressValue);
+  virtual void progressTextChanged(const QString &progressText);
+
+  virtual void outputDataReady();
+  virtual void errorDataReady();
 
 private:
 
-  QList<QString> events;
+  ctkCmdLineModuleFutureWatcher Watcher;
+  QList<QString> Events;
 };
 
 #endif // CTKCMDLINEMODULESIGNALTESTER_H

+ 14 - 7
Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

@@ -66,9 +66,9 @@ int main(int argc, char* argv[])
   parser.addArgument("exitCode", "", QVariant::Int, "Exit code", 0);
   parser.addArgument("exitCrash", "", QVariant::Bool, "Force crash", false);
   parser.addArgument("exitTime", "", QVariant::Int, "Exit time", 0);
-  parser.addArgument("errorText", "", QVariant::String, "Error text (will not be printed on exit code 0)");
-  QTextStream out(stdout, QIODevice::WriteOnly);
-  QTextStream err(stderr, QIODevice::WriteOnly);
+  parser.addArgument("errorText", "", QVariant::String, "Error text printed at the end");
+  QTextStream out(stdout, QIODevice::WriteOnly | QIODevice::Text);
+  QTextStream err(stderr, QIODevice::WriteOnly | QIODevice::Text);
 
   // Parse the command line arguments
   bool ok = false;
@@ -104,6 +104,11 @@ int main(int argc, char* argv[])
   bool exitCrash = parsedArgs["exitCrash"].toBool();
   QString errorText = parsedArgs["errorText"].toString();
 
+  err << "A superficial error message." << endl;
+
+  // sleep 500ms to give the "errorReady" signal a chance
+  sleep_ms(500);
+
   QStringList outputs;
   for (int i = 0; i < numOutputs; ++i)
   {
@@ -152,21 +157,23 @@ int main(int argc, char* argv[])
     // print the first output
     if (output != "dummy")
     {
-      out << output; endl(out);
+      out << output << endl;
       // report progress
       out << "<filter-progress>" << (i+1)*progressStep << "</filter-progress>\n";
     }
   }
 
-  // sleep 1 second to avoid squashing the last progress event with the finished event
-  sleep_ms(1000);
+  // sleep 500ms to avoid squashing the last progress event with the finished event
+  sleep_ms(500);
+
+  out.flush();
 
   if (exitCrash)
   {
     int* crash = 0;
     *crash = 5;
   }
-  if (exitCode != 0 && !errorText.isEmpty())
+  if (!errorText.isEmpty())
   {
     err << errorText;
   }

+ 1 - 1
Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.xml

@@ -56,7 +56,7 @@ Configurable behaviour for testing purposes.
     <string>
       <name>errorTextVar</name>
       <longflag>errorText</longflag>
-      <description>Final error message (not displayed if the exit code is 0).</description>
+      <description>Final error message at the end.</description>
       <label>Error text</label>
     </string>
   </parameters>