Browse Source

Added enhanced ctkCmdLineModuleFutureWatcher class for watching output.

Sascha Zelzer 12 years ago
parent
commit
4141050532
20 changed files with 1000 additions and 131 deletions
  1. 2 6
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessTask.cpp
  2. 16 0
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher.cpp
  3. 3 0
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessWatcher_p.h
  4. 5 2
      Libs/CommandLineModules/Core/CMakeLists.txt
  5. 16 1
      Libs/CommandLineModules/Core/ctkCmdLineModuleFrontend.h
  6. 57 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.cpp
  7. 10 10
      Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.h
  8. 201 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.cpp
  9. 31 36
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.h
  10. 90 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface_p.h
  11. 216 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.cpp
  12. 86 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleFutureWatcher.h
  13. 1 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp
  14. 38 2
      Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.cpp
  15. 6 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.h
  16. 123 43
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp
  17. 66 21
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.cpp
  18. 20 5
      Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleSignalTester.h
  19. 12 4
      Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp
  20. 1 1
      Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.xml

+ 2 - 6
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleProcessTask.cpp

@@ -90,16 +90,12 @@ void ctkCmdLineModuleProcessTask::run()
 
   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();

+ 16 - 0
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()));
@@ -122,6 +125,19 @@ void ctkCmdLineModuleProcessWatcher::cancelProcess()
   process.terminate();
 }
 
+//----------------------------------------------------------------------------
+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
 )
 

+ 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 class 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 class 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().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;
 };

+ 123 - 43
Libs/CommandLineModules/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp

@@ -27,6 +27,7 @@
 #include <ctkCmdLineModuleParameter.h>
 #include <ctkCmdLineModuleRunException.h>
 #include <ctkCmdLineModuleFuture.h>
+#include <ctkCmdLineModuleFutureWatcher.h>
 
 #include "ctkCmdLineModuleSignalTester.h"
 
@@ -85,6 +86,11 @@ class ctkCmdLineModuleFutureTester : public QObject
 {
   Q_OBJECT
 
+public Q_SLOTS:
+
+  void ouputDataReady();
+  void errorDataReady();
+
 private Q_SLOTS:
 
   void initTestCase();
@@ -95,10 +101,16 @@ private Q_SLOTS:
   void testStartFinish();
   void testProgress();
   void testPauseAndCancel();
+  void testOutput();
   void testError();
 
 private:
 
+  QByteArray outputData;
+  QByteArray errorData;
+
+  ctkCmdLineModuleFutureWatcher* currentWatcher;
+
   ctkCmdLineModuleFrontendMockupFactory factory;
   ctkCmdLineModuleBackendLocalProcess backend;
 
@@ -109,6 +121,24 @@ private:
 };
 
 //-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::ouputDataReady()
+{
+  if (this->currentWatcher)
+  {
+    outputData.append(currentWatcher->readPendingOutputData());
+  }
+}
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::errorDataReady()
+{
+  if (this->currentWatcher)
+  {
+    errorData.append(currentWatcher->readPendingErrorData());
+  }
+}
+
+//-----------------------------------------------------------------------------
 void ctkCmdLineModuleFutureTester::initTestCase()
 {
   manager.registerBackend(&backend);
@@ -120,6 +150,7 @@ void ctkCmdLineModuleFutureTester::initTestCase()
 //-----------------------------------------------------------------------------
 void ctkCmdLineModuleFutureTester::init()
 {
+  currentWatcher = 0;
   frontend = factory.create(moduleRef);
 }
 
@@ -127,23 +158,26 @@ void ctkCmdLineModuleFutureTester::init()
 void ctkCmdLineModuleFutureTester::cleanup()
 {
   delete frontend;
+  outputData.clear();
+  errorData.clear();
 }
 
 //-----------------------------------------------------------------------------
 void ctkCmdLineModuleFutureTester::testStartFinish()
 {
   QList<QString> expectedSignals;
-  expectedSignals.push_back("module.started");
-  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(finished()), &signalTester, SLOT(moduleFinished()));
-
   ctkCmdLineModuleFuture future = manager.run(frontend);
-  watcher.setFuture(future);
+  signalTester.setFuture(future);
   future.waitForFinished();
 
   QCoreApplication::processEvents();
@@ -154,35 +188,35 @@ void ctkCmdLineModuleFutureTester::testStartFinish()
 void ctkCmdLineModuleFutureTester::testProgress()
 {
   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"
+                     // 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)"
 
-  ctkCmdLineModuleSignalTester signalTester;
+                     // 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)"
 
-  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()));
+                     // 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);
+  signalTester.setFuture(future);
 
   future.waitForFinished();
 
@@ -197,19 +231,17 @@ void ctkCmdLineModuleFutureTester::testPauseAndCancel()
 {
   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);
+  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
@@ -254,15 +286,63 @@ void ctkCmdLineModuleFutureTester::testPauseAndCancel()
 }
 
 //-----------------------------------------------------------------------------
+void ctkCmdLineModuleFutureTester::testOutput()
+{
+  ctkCmdLineModuleSignalTester signalTester;
+
+  connect(signalTester.watcher(), SIGNAL(outputDataReady()), SLOT(ouputDataReady()));
+  connect(signalTester.watcher(), SIGNAL(errorDataReady()), SLOT(errorDataReady()));
+
+  this->currentWatcher = signalTester.watcher();
+
+  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();
+
+  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);
+}
+
+//-----------------------------------------------------------------------------
 void ctkCmdLineModuleFutureTester::testError()
 {
   frontend->setValue("fileVar", "output1");
   frontend->setValue("exitCodeVar", 24);
   frontend->setValue("errorTextVar", "Some error occured\n");
 
-  QFutureWatcher<ctkCmdLineModuleResult> watcher;
   ctkCmdLineModuleFuture future = manager.run(frontend);
-  watcher.setFuture(future);
 
   try
   {
@@ -272,7 +352,7 @@ void ctkCmdLineModuleFutureTester::testError()
   catch (const ctkCmdLineModuleRunException& e)
   {
     QVERIFY2(e.errorCode() == 24, "Test matching error code");
-    QVERIFY2(e.errorString() == "Some error occured\n", "Error text mismatch");
+    QCOMPARE(future.readAllErrorData().data(), "A superficial error message.\nSome error occured\n");
   }
 }
 

+ 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

+ 12 - 4
Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

@@ -66,7 +66,7 @@ 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)");
+  parser.addArgument("errorText", "", QVariant::String, "Error text printed at the end");
   QTextStream out(stdout, QIODevice::WriteOnly);
   QTextStream err(stderr, QIODevice::WriteOnly);
 
@@ -104,6 +104,12 @@ int main(int argc, char* argv[])
   bool exitCrash = parsedArgs["exitCrash"].toBool();
   QString errorText = parsedArgs["errorText"].toString();
 
+  err << "A superficial error message.\n";
+  err.flush();
+
+  // sleep 500ms to give the "errorReady" signal a chance
+  sleep_ms(500);
+
   QStringList outputs;
   for (int i = 0; i < numOutputs; ++i)
   {
@@ -158,15 +164,17 @@ int main(int argc, char* argv[])
     }
   }
 
-  // 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>