Browse Source

ENH: Workflow - Add ctkWorkflow, ctkWorkflowStep and ctkWorkflowTransitions

 The workflow manager doesn't rely on QWidget

 ctkWorkflowStep:
  - step in the workflow, consisting of a processing state and a validation state

 ctkWorkflow:
  - workflow conisting of multiple steps. Transitions between steps occur only after validation.

 ctkWorkflowTransition:
  - transition between states, either between the two states of the same step, or between states of different steps.

Designed and implemented by Danielle Pace and Jean-Christophe Fillion-Robin working at Kitware Inc.

Work supported by NA-MIC and funded by ARRASuplements 'Usability of the EM segmenter'.
See http://wiki.na-mic.org/Wiki/index.php/Projects:ARRASuplements
Jean-Christophe Fillion-Robin 14 years ago
parent
commit
d08bce61a9

+ 9 - 1
Libs/Core/CMakeLists.txt

@@ -20,7 +20,7 @@ SET(KIT_export_directive "CTK_CORE_EXPORT")
 SET(KIT_include_directories
   ${Log4Qt_INCLUDE_DIR}
   )
-  
+
 # Source files
 SET(KIT_SRCS
   ctkAbstractFactory.h
@@ -51,6 +51,11 @@ SET(KIT_SRCS
   ctkTransferFunctionRepresentation.h
   ctkUtils.cpp
   ctkUtils.h
+  ctkWorkflow.h
+  ctkWorkflow.cpp
+  ctkWorkflowStep.h
+  ctkWorkflowStep.cpp
+  ctkWorkflowTransitions.h
   )
 
 IF(CTK_HAVE_BFD)
@@ -67,6 +72,9 @@ SET(KIT_MOC_SRCS
   ctkModelTester.h
   ctkTransferFunction.h
   ctkTransferFunctionRepresentation.h
+  ctkWorkflow.h
+  ctkWorkflowStep.h
+ ctkWorkflowTransitions.h
   )
 
 # UI files

+ 24 - 1
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -12,6 +12,9 @@ CREATE_TEST_SOURCELIST(Tests ${KIT}CppTests.cpp
   ctkDependencyGraphTest1.cpp
   ctkPimplTest1.cpp
   ctkSingletonTest1.cpp
+  ctkWorkflowTest1.cpp
+  ctkWorkflowTest2.cpp
+  ctkWorkflowTest3.cpp
   #EXTRA_INCLUDE TestingMacros.h
   )
 
@@ -20,7 +23,24 @@ REMOVE (TestsToRun ${KIT}CppTests.cpp)
 
 SET(LIBRARY_NAME ${PROJECT_NAME})
 
-ADD_EXECUTABLE(${KIT}CppTests ${Tests})
+SET(Tests_SRCS
+  ctkBranchingWorkflowStep.h
+  ctkExampleDerivedWorkflowStep.cpp
+  ctkExampleDerivedWorkflowStep.h
+  ctkExampleWorkflowStepUsingSignalsAndSlots.cpp
+  ctkExampleWorkflowStepUsingSignalsAndSlots.h
+)
+
+SET(Tests_MOC_SRCS
+  ctkBranchingWorkflowStep.h
+  ctkExampleWorkflowStepUsingSignalsAndSlots.h
+  ctkExampleDerivedWorkflowStep.h
+)
+
+SET(MY_MOC_CXX)
+QT4_WRAP_CPP(MY_MOC_CXX ${Tests_MOC_SRCS})
+
+ADD_EXECUTABLE(${KIT}CppTests ${Tests} ${Tests_SRCS} ${MY_MOC_CXX})
 TARGET_LINK_LIBRARIES(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES})
 
 SET( KIT_TESTS ${CPP_TEST_PATH}/${KIT}CppTests)
@@ -43,3 +63,6 @@ SIMPLE_TEST( ctkModelTesterTest1 )
 SIMPLE_TEST( ctkPimplTest1 )
 SIMPLE_TEST( ctkSingletonTest1 )
 SIMPLE_TEST( ctkUtilsTest1 )
+SIMPLE_TEST( ctkWorkflowTest1 )
+SIMPLE_TEST( ctkWorkflowTest2 )
+SIMPLE_TEST( ctkWorkflowTest3 )

+ 55 - 0
Libs/Core/Testing/Cpp/ctkBranchingWorkflowStep.h

@@ -0,0 +1,55 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkBranchingWorkflowStep_h
+#define __ctkBranchingWorkflowStep_h
+
+// CTK includes
+#include "ctkWorkflowStep.h"
+#include "ctkWorkflow.h"
+
+//-----------------------------------------------------------------------------
+class ctkBranchingWorkflowStep : public ctkWorkflowStep
+{
+  Q_OBJECT
+
+public:
+
+  typedef ctkWorkflowStep Superclass;
+  explicit ctkBranchingWorkflowStep(ctkWorkflow* newWorkflow, const QString& newId) : Superclass(newWorkflow, newId){};
+  virtual ~ctkBranchingWorkflowStep(){}
+
+  void setBranchId(const QString& newId)
+  {
+    this->branchId = newId;
+  }
+
+  virtual void validate(const QString& desiredBranchId = QString())
+  {
+    Q_UNUSED(desiredBranchId);
+    emit validationComplete(true, this->branchId);
+  }
+
+private:
+  QString branchId;
+
+};
+
+#endif

+ 99 - 0
Libs/Core/Testing/Cpp/ctkExampleDerivedWorkflowStep.cpp

@@ -0,0 +1,99 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+  =========================================================================*/
+
+// CTK includes
+#include "ctkExampleDerivedWorkflowStep.h"
+
+//-----------------------------------------------------------------------------
+class ctkExampleDerivedWorkflowStepPrivate : public ctkPrivate<ctkExampleDerivedWorkflowStep>
+{
+public:
+  ctkExampleDerivedWorkflowStepPrivate();
+
+  // counters of the number of times we have run the onEntry()
+  // and onExit() functions
+  int numberOfTimesRanOnEntry;
+  int numberOfTimesRanOnExit;
+};
+
+//-----------------------------------------------------------------------------
+// ctkExampleDerivedWorkflowStepPrivate methods
+
+//-----------------------------------------------------------------------------
+ctkExampleDerivedWorkflowStepPrivate::ctkExampleDerivedWorkflowStepPrivate()
+{
+  this->numberOfTimesRanOnEntry = 0;
+  this->numberOfTimesRanOnExit = 0;
+}
+
+//-----------------------------------------------------------------------------
+// ctkExampleDerivedWorkflowStep methods
+
+//-----------------------------------------------------------------------------
+ctkExampleDerivedWorkflowStep::ctkExampleDerivedWorkflowStep(ctkWorkflow* newWorkflow, const QString& newId) :
+  Superclass(newWorkflow, newId)
+{
+  CTK_INIT_PRIVATE(ctkExampleDerivedWorkflowStep);
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleDerivedWorkflowStep::validate(const QString& desiredBranchId)
+{
+  // Always returns true in this simple example
+  emit validationComplete(true,desiredBranchId);
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleDerivedWorkflowStep::onEntry(
+    const ctkWorkflowStep* comingFrom,
+    const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(comingFrom);
+  Q_UNUSED(transitionType);
+
+  // Simply implements our counter of the number of times we have run this function
+  CTK_D(ctkExampleDerivedWorkflowStep);
+  d->numberOfTimesRanOnEntry++;
+
+  // signals that we are finished
+  emit onEntryComplete();
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleDerivedWorkflowStep::onExit(
+    const ctkWorkflowStep* goingTo,
+    const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(goingTo);
+  Q_UNUSED(transitionType);
+
+  // simply implements our counter of the number of times we have run
+  // this function
+  CTK_D(ctkExampleDerivedWorkflowStep);
+  d->numberOfTimesRanOnExit++;
+
+  // signals that we are finished
+  emit onExitComplete();
+}
+
+//-----------------------------------------------------------------------------
+CTK_GET_CXX(ctkExampleDerivedWorkflowStep, int, numberOfTimesRanOnEntry, numberOfTimesRanOnEntry);
+CTK_GET_CXX(ctkExampleDerivedWorkflowStep, int, numberOfTimesRanOnExit, numberOfTimesRanOnExit);
+

+ 67 - 0
Libs/Core/Testing/Cpp/ctkExampleDerivedWorkflowStep.h

@@ -0,0 +1,67 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkExampleDerivedWorkflowStep_h
+#define __ctkExampleDerivedWorkflowStep_h
+
+// CTK includes
+#include "ctkPimpl.h"
+#include "ctkWorkflowStep.h"
+#include "ctkWorkflowTransitions.h"
+
+class ctkExampleDerivedWorkflowStepPrivate;
+
+///
+/// ctkExampleDerivedWorkflowStep is an example custom step created by
+/// deriving ctkWorkflowStep and re-implementing validate(const QString&),
+/// onEntry() and onExit().
+
+class ctkExampleDerivedWorkflowStep : public ctkWorkflowStep
+{
+
+  Q_OBJECT
+
+public:
+
+  typedef ctkWorkflowStep Superclass;
+  explicit ctkExampleDerivedWorkflowStep(ctkWorkflow* newWorkflow, const QString& newId);
+  virtual ~ctkExampleDerivedWorkflowStep(){}
+
+  /// Get the values for the counters of the number of times we have
+  /// run the onEntry() and onExit() functions
+  virtual int numberOfTimesRanOnEntry()const;
+  virtual int numberOfTimesRanOnExit()const;
+
+  virtual void validate(const QString& desiredBranchId = QString());
+
+  /// Increments the counter numberOfTimesRanOnEntry
+  virtual void onEntry(const ctkWorkflowStep* comingFrom,
+                       const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+  /// Increments the counter numberOfTimesRanOnExit
+  virtual void onExit(const ctkWorkflowStep* goingTo,
+                      const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+private:
+  CTK_DECLARE_PRIVATE(ctkExampleDerivedWorkflowStep);
+
+};
+
+#endif

+ 108 - 0
Libs/Core/Testing/Cpp/ctkExampleWorkflowStepUsingSignalsAndSlots.cpp

@@ -0,0 +1,108 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+  =========================================================================*/
+
+// Qt includes
+
+// CTK includes
+#include "ctkExampleWorkflowStepUsingSignalsAndSlots.h"
+#include "ctkWorkflowStep.h"
+
+// STD includes
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+class ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate : public ctkPrivate<ctkExampleWorkflowStepUsingSignalsAndSlots>
+{
+public:
+  ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate();
+
+  // counters of the number of times we have run the onEntry()
+  // and onExit() functions
+  int numberOfTimesRanOnEntry;
+  int numberOfTimesRanOnExit;
+};
+
+//-----------------------------------------------------------------------------
+// ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate methods
+
+//-----------------------------------------------------------------------------
+ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate::ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate()
+{
+  this->numberOfTimesRanOnEntry = 0;
+  this->numberOfTimesRanOnExit = 0;
+}
+
+//-----------------------------------------------------------------------------
+// ctkExampleWorkflowStepUsingSignalsAndSlots methods
+
+//-----------------------------------------------------------------------------
+ctkExampleWorkflowStepUsingSignalsAndSlots::ctkExampleWorkflowStepUsingSignalsAndSlots(QObject* _parent) : Superclass(_parent)
+{
+  CTK_INIT_PRIVATE(ctkExampleWorkflowStepUsingSignalsAndSlots);
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleWorkflowStepUsingSignalsAndSlots::validate(const QString& desiredBranchId)const
+{
+  // Always returns true in this simple example
+  emit validationComplete(true, desiredBranchId);
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleWorkflowStepUsingSignalsAndSlots::validateFails()const
+{
+  // Always returns false in this simple example
+  emit validationComplete(false);
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleWorkflowStepUsingSignalsAndSlots::onEntry(const ctkWorkflowStep* comingFrom, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(comingFrom);
+  Q_UNUSED(transitionType);
+
+  // simply implements our counter of the number of times we have run
+  // this function
+  CTK_D(ctkExampleWorkflowStepUsingSignalsAndSlots);
+  d->numberOfTimesRanOnEntry++;
+
+  // signals that we are finished
+  emit onEntryComplete();
+}
+
+//-----------------------------------------------------------------------------
+void ctkExampleWorkflowStepUsingSignalsAndSlots::onExit(const ctkWorkflowStep* goingTo, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(goingTo);
+  Q_UNUSED(transitionType);
+
+  // simply implements our counter of the number of times we have run
+  // this function
+  CTK_D(ctkExampleWorkflowStepUsingSignalsAndSlots);
+  d->numberOfTimesRanOnExit++;
+
+  // signals that we are finished
+  emit onExitComplete();
+}
+
+//-----------------------------------------------------------------------------
+CTK_GET_CXX(ctkExampleWorkflowStepUsingSignalsAndSlots, int, numberOfTimesRanOnEntry, numberOfTimesRanOnEntry);
+CTK_GET_CXX(ctkExampleWorkflowStepUsingSignalsAndSlots, int, numberOfTimesRanOnExit, numberOfTimesRanOnExit);
+

+ 113 - 0
Libs/Core/Testing/Cpp/ctkExampleWorkflowStepUsingSignalsAndSlots.h

@@ -0,0 +1,113 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkExampleWorkflowStepUsingSignalsAndSlots_h
+#define __ctkExampleWorkflowStepUsingSignalsAndSlots_h
+
+// CTK includes
+#include "ctkPimpl.h"
+class ctkWorkflowStep;
+#include "ctkWorkflowTransitions.h"
+
+class ctkExampleWorkflowStepUsingSignalsAndSlotsPrivate;
+
+///
+/// ctkExampleWorkflowStepUsingSignalsAndSlots represents an example
+/// custom step created by deriving QObject (not ctkWorkflowStep) and
+/// implementing functions for validate(const QString&), onEntry() and
+/// onExit() that work using signals and slots.
+
+///
+/// Need two connections to use this class's validate(const QString&) function, and
+/// must also set the step's hasValidateCommand flag:
+/// QObject::connect(step, SIGNAL(invokeValidateCommand(const QString&)), qObject,
+/// SLOT(validate(const QString&)))
+/// QObject::connect(qObject, SIGNAL(validationComplete(int)),
+/// workflow, SLOT(evaluateValidationResults(int)));
+/// step->setHasValidateCommand(1);
+
+///
+/// Need two connections to use this class's onEntry()
+/// function, and must also set the step's hasOnEntryCommand
+/// flag:
+/// QObject::connect(step, SIGNAL(invokeOnEntryCommand(const
+/// ctkWorkflowStep*, const
+/// ctkWorkflowTransition::WorkflowTransitionType)), qObject,
+/// SLOT(onEntry(const ctkWorkflowStep*, const
+/// ctkWorkflowTransition::WorkflowTransitionType)));
+/// QObject::connect(qObject, SIGNAL(onEntryComplete()), step,
+/// SLOT(evaluateOnEntryResults()));
+/// step->setHasOnEntryCommand(1);
+
+///
+/// Need two connectins to use this class's onExit() function,
+/// and must also set the step's hasOnExitCommand() flag:
+/// QObject::connect(step, SIGNAL(invokeOnExitCommand(const
+/// ctkWorkflowStep*, const
+/// ctkWorkflowTransition::WorkflowTransitionType)), qObject,
+/// SLOT(onExit(const ctkWorkflowStep*, const
+/// ctkWorkflowTransition::WorkflowTransitionType)));
+/// QObject::connect(qObject, SIGNAL(onExitComplete()), step,
+/// SLOT(evaluateOnExitResults()));
+/// step->setHasOnExitCommand(1);
+
+class ctkExampleWorkflowStepUsingSignalsAndSlots : public QObject
+{
+  Q_OBJECT
+
+public:
+  typedef QObject Superclass;
+  explicit ctkExampleWorkflowStepUsingSignalsAndSlots(QObject* parent = 0);
+  virtual ~ctkExampleWorkflowStepUsingSignalsAndSlots(){}
+
+  /// Get the values for the counters of the number of times we have
+  /// run the onEntry() and onExit() functions
+  virtual int numberOfTimesRanOnEntry()const;
+  virtual int numberOfTimesRanOnExit()const;
+
+protected slots:
+
+  /// Always returns 1 (validation successful)
+  virtual void validate(const QString& desiredBranchId = QString())const;
+
+  /// Always returns 0 (validation failed)
+  virtual void validateFails()const;
+
+  /// Increments the counter numberOfTimesRanOnEntry
+  virtual void onEntry(const ctkWorkflowStep* comingFrom, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+  ///
+  /// Increments the counter numberOfTimesRanOnExit
+  virtual void onExit(const ctkWorkflowStep* goingTo, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+signals:
+  ///
+  /// Signals indicating to the workflow that these processes have
+  /// completed
+  void validationComplete(bool validationSucceeded, const QString& branchId = "")const;
+  void onEntryComplete()const;
+  void onExitComplete()const;
+
+private:
+  CTK_DECLARE_PRIVATE(ctkExampleWorkflowStepUsingSignalsAndSlots);
+
+};
+
+#endif

+ 483 - 0
Libs/Core/Testing/Cpp/ctkWorkflowTest1.cpp

@@ -0,0 +1,483 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+  =========================================================================*/
+
+// QT includes
+#include <QApplication>
+#include <QTimer>
+
+// CTK includes
+#include "ctkExampleDerivedWorkflowStep.h"
+#include "ctkWorkflow.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int numberOfTimesEntryExitTest(ctkExampleDerivedWorkflowStep* step1=0, int step1Entry=0, int step1Exit=0, ctkExampleDerivedWorkflowStep* step2=0, int step2Entry=0, int step2Exit=0, ctkExampleDerivedWorkflowStep* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleDerivedWorkflowStep* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  if (step1)
+    {
+    if (step1->numberOfTimesRanOnEntry() != step1Entry || step1->numberOfTimesRanOnExit() != step1Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step2)
+    {
+    if (step2->numberOfTimesRanOnEntry() != step2Entry || step2->numberOfTimesRanOnExit() != step2Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step3)
+    {
+    if (step3->numberOfTimesRanOnEntry() != step3Entry || step3->numberOfTimesRanOnExit() != step3Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step4)
+    {
+    if (step4->numberOfTimesRanOnEntry() != step4Entry || step4->numberOfTimesRanOnExit() != step4Exit)
+      {
+      return 0;
+      }    
+    }
+
+  return 1;
+}
+
+//-----------------------------------------------------------------------------
+int currentStepAndNumberOfTimesEntryExitTest(ctkWorkflow* workflow, ctkExampleDerivedWorkflowStep* expectedStep, ctkExampleDerivedWorkflowStep* step1, int step1Entry, int step1Exit, ctkExampleDerivedWorkflowStep* step2, int step2Entry, int step2Exit, ctkExampleDerivedWorkflowStep* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleDerivedWorkflowStep* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  if (expectedStep)
+    {
+    if (workflow->currentStep() != expectedStep)
+      {
+      return 0;
+      }
+    }
+  return numberOfTimesEntryExitTest(step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int transitionTest(ctkWorkflow* workflow, int defaultTime, QApplication& app, ctkExampleDerivedWorkflowStep* expectedStep, ctkExampleDerivedWorkflowStep* step1, int step1Entry, int step1Exit, ctkExampleDerivedWorkflowStep* step2, int step2Entry, int step2Exit, ctkExampleDerivedWorkflowStep* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleDerivedWorkflowStep* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  return currentStepAndNumberOfTimesEntryExitTest(workflow, expectedStep, step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int testStartWorkflow(ctkWorkflow* workflow, int defaultTime, QApplication& app, int shouldRun, ctkExampleDerivedWorkflowStep* expectedStep=0, ctkExampleDerivedWorkflowStep* step1=0, int step1Entry=0, int step1Exit=0, ctkExampleDerivedWorkflowStep* step2=0, int step2Entry=0, int step2Exit=0, ctkExampleDerivedWorkflowStep* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleDerivedWorkflowStep* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  workflow->start();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->isRunning() != shouldRun)
+    {
+    return 0;
+    }
+  return currentStepAndNumberOfTimesEntryExitTest(workflow, expectedStep, step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int testStopWorkflow(ctkWorkflow* workflow, int defaultTime, QApplication& app, ctkExampleDerivedWorkflowStep* step1, int step1Entry, int step1Exit, ctkExampleDerivedWorkflowStep* step2, int step2Entry, int step2Exit, ctkExampleDerivedWorkflowStep* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleDerivedWorkflowStep* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  workflow->stop();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->isRunning())
+    {
+    return 0;
+    }
+  return numberOfTimesEntryExitTest(step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int ctkWorkflowTest1(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+  int defaultTime = 100;
+
+  // create two steps and the workflow
+  ctkWorkflow *workflow = new ctkWorkflow();
+  ctkExampleDerivedWorkflowStep *step1 = new ctkExampleDerivedWorkflowStep(workflow, "Step 1");
+  step1->setName("Step 1");
+  step1->setDescription("Description for step 1");
+  ctkExampleDerivedWorkflowStep *step2 = new ctkExampleDerivedWorkflowStep(workflow, "Step 2");
+  step2->setName("Step 2");
+  step2->setDescription("Description for step 2");
+
+  // --------------------------------------------------------------------------
+  // try to add a transition for a step with the same id
+  ctkExampleDerivedWorkflowStep *step1Duplicated = new ctkExampleDerivedWorkflowStep(workflow, "Step 1");
+  if (workflow->addTransition(step1, step1Duplicated))
+    {
+    std::cerr << "workflow connected two steps with the same id";
+    return EXIT_FAILURE;
+    }
+
+  // try to add a transition from a step to itself
+  if (workflow->addTransition(step1, step1))
+    {
+    std::cerr << "workflow connected two steps with the same id";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with no steps
+  // try erroneously starting with no steps
+
+  if (!testStartWorkflow(workflow, defaultTime, app, 0))
+    {
+    std::cerr << "empty workflow is running after start()";
+    return EXIT_FAILURE;
+    }
+
+  // add the first step
+  if (!workflow->addTransition(step1, 0))
+    {
+    std::cerr << "could not add first step";
+    return EXIT_FAILURE;
+    }
+
+  // try erroneously starting with no initial step
+  if (!testStartWorkflow(workflow, defaultTime, app, 0))
+    {
+    std::cerr << "workflow is running after start() with no initial step";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with one step
+  
+  // set the initial step (which sets the initial state)
+  workflow->setInitialStep(step1);
+
+  // try starting with one step
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, step1, 1, 0, step2, 0, 0))
+    {
+    std::cerr << "workflow is not running after start() with a single step";
+    return EXIT_FAILURE;
+    }
+
+  // triggering ValidationTransition and TransitionToPreviousStep
+  // should keep us in the same step, when there is only one step
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 1, 0, step2, 0, 0))
+    {
+    std::cerr << "error in validation transition in a workflow with a single step";
+    return EXIT_FAILURE;
+    }
+
+  // transition to the previous step
+  workflow->goBackward();
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 1, 0, step2, 0, 0))
+    {
+    std::cerr << "error after transition to previous step in a workflow with a single step";
+    return EXIT_FAILURE;
+    }
+
+  // stop the workflow
+  if (!testStopWorkflow(workflow, defaultTime, app, step1, 1, 1, step2, 0, 0))
+    {
+    std::cerr << "workflow with one step still running after stop";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with two steps
+
+  // add the second step
+  if (!workflow->addTransition(step1, step2))
+    {
+    std::cerr << "could not add second step";
+    return EXIT_FAILURE;
+    }
+
+  // start the workflow
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, step1, 2, 1, step2, 0, 0))
+    {
+    std::cerr << "workflow is not running after start() with two steps";
+    return EXIT_FAILURE;
+    }
+
+  // make sure the workflow has the steps
+  if (!workflow->hasStep(step1->id()))
+    {
+    std::cerr << "Step1 not added to workflow";
+    return EXIT_FAILURE;
+    }
+
+  if (!workflow->hasStep(step2->id()))
+    {
+    std::cerr << "Step2 not added to workflow";
+    return EXIT_FAILURE;
+    }
+  
+  // if (workflow->numberOfSteps() != 2)
+  //   {
+  //   std::cerr << "workflow has " << workflow->numberOfSteps() << " steps, not 2";
+  //   return EXIT_FAILURE;
+  //   }
+
+  // Test that the workflow transitions from processing to validation state
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step2, step1, 2, 2, step2, 1, 0))
+    {
+    std::cerr << "error transitioning to next step in workflow with two steps";
+    return EXIT_FAILURE;
+    }
+
+  // Test that the workflow transitions back to the previous step
+  workflow->goBackward();
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 3, 2, step2, 1, 1))
+    {
+    std::cerr << "error transitioning to previous step in workflow with step steps";
+    return EXIT_FAILURE;
+    }
+
+  // make sure the workflow stops properly
+  if (!testStopWorkflow(workflow, defaultTime, app, step1, 3, 3, step2, 1, 1))
+    {
+    std::cerr << "workflow with two steps is running after stop()";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with three steps
+
+  // add a third step manually
+  ctkExampleDerivedWorkflowStep *step3 = new ctkExampleDerivedWorkflowStep(workflow, "Step 3");
+  step3->setName("Step 3");
+  step3->setDescription("Description for step 3");
+
+  if (!workflow->addTransition(step2, step3, "", ctkWorkflow::Forward))
+    {
+    std::cerr << "could not add step 3 with forward transition";
+    return EXIT_FAILURE;
+    }
+
+  if (!workflow->addTransition(step2, step3, "",  ctkWorkflow::Backward))
+    {
+    std::cerr << "could not add next transition between step2 and step 3";
+    return EXIT_FAILURE;
+    }
+
+  if (workflow->forwardSteps(step1).length() != 1
+      || workflow->forwardSteps(step1).first() != step2
+      || workflow->forwardSteps(step2).length() != 1
+      || workflow->forwardSteps(step2).first() != step3
+      || workflow->forwardSteps(step3).length() != 0)
+    {
+    std::cerr << "error in list of forward steps" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (workflow->backwardSteps(step1).length() != 0
+      || workflow->backwardSteps(step2).length() != 1
+      || workflow->backwardSteps(step2).first() != step1
+      || workflow->backwardSteps(step3).length() != 1
+      || workflow->backwardSteps(step3).first() != step2)
+    {
+    std::cerr << "error in list of backward steps" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (!workflow->hasStep(step3->id()))
+    {
+    std::cerr << "Step3 not added to workflow";
+    return EXIT_FAILURE;
+    }
+  // if (workflow->numberOfSteps() != 3)
+  //   {
+  //   std::cerr << "workflow has " << workflow->numberOfSteps() << " steps, not 2";
+  //   return EXIT_FAILURE;
+  //   }
+
+  // now that we've stopped and restarted the state machine, we should
+  // be back in the initial step (step 1)
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, step1, 4, 3, step2, 1, 1, step3, 0, 0))
+    {
+    std::cerr << "workflow is not running after start() with three steps";
+    return EXIT_FAILURE;
+    }
+
+  // test to make sure our lists of forward and backwards steps is correct
+  if (!workflow->canGoForward(step1)
+      || workflow->canGoBackward(step1)
+      || !workflow->canGoForward(step2)
+      || !workflow->canGoBackward(step2)
+      || workflow->canGoForward(step3)
+      || !workflow->canGoBackward(step3))
+    {
+    std::cerr << "error in can go forward/backward" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // Test that the workflow transitions from step1 to step 2 to step 3
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step3, step1, 4, 4, step2, 2, 2, step3, 1, 0))
+    {
+    std::cerr << "error transitioning to step3 in workflow with three steps";
+    return EXIT_FAILURE;
+    }
+
+  // Test that the workflow transitions back to the previous step
+  workflow->goBackward();
+  if (!transitionTest(workflow, defaultTime, app, step2, step1, 4, 4, step2, 3, 2, step3, 1, 1))
+    {
+    std::cerr << "error transitioning to previous step in workflow with three steps";
+    return EXIT_FAILURE;
+    }
+
+  // make sure the workflow stops properly
+  if (!testStopWorkflow(workflow, defaultTime, app, step1, 4, 4, step2, 3, 3, step3, 1, 1))
+    {
+    std::cerr << "error stopping workflow with three steps";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with a finish step (step 3)
+
+  // restart the workflow
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, step1, 5, 4, step2, 3, 3, step3, 1, 1))
+    {
+    std::cerr << "workflow with finish step is not running after start()";
+    return EXIT_FAILURE;
+    }
+
+  // try to go automatically to step 3
+  workflow->goToStep("Step 3");
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 6, 5, step2, 4, 4, step3, 2, 2))
+    {
+    std::cerr << "error after going to finish step";
+    return EXIT_FAILURE;
+    }
+
+  // try to go automatically to step 3 again
+  workflow->goToStep("Step 3");
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 7, 6, step2, 5, 5, step3, 3, 3))
+    {
+    std::cerr << "error after going to finish step the second time";
+    return EXIT_FAILURE;
+    }
+
+  // stop workflow
+  if (!testStopWorkflow(workflow, defaultTime, app, step1, 7, 7, step2, 5, 5, step3, 3, 3))
+    {
+    std::cerr << "error stopping workflow with finish step";
+    return EXIT_FAILURE;
+    }
+
+  // --------------------------------------------------------------------------
+  // workflow with two finishing steps (step3 and step4)
+  ctkExampleDerivedWorkflowStep *step4 = new ctkExampleDerivedWorkflowStep(workflow, "Step 4");
+  step4->setName("Step 4");
+  step4->setDescription("Description for step 4");
+  workflow->addTransition(step3, step4);
+
+  // restart the workflow
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, step1, 8, 7, step2, 5, 5, step3, 3, 3, step4, 0, 0))
+    {
+    std::cerr << "workflow with two finish steps is not running after start()";
+    return EXIT_FAILURE;
+    }
+
+  // try to go automatically to step 3
+  workflow->goToStep("Step 3");
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 9, 8, step2, 6, 6, step3, 4, 4, step4, 0, 0))
+    {
+    std::cerr << "error going to the first finish step of two";
+    return EXIT_FAILURE;
+    }
+
+  // try to go automatically to step 4
+  workflow->goToStep("Step 4");
+  if (!transitionTest(workflow, defaultTime, app, step1, step1, 10, 9, step2, 7, 7, step3, 5, 5, step4, 1, 1))
+    {
+    std::cerr << "error going to the second finish step of two";
+    return EXIT_FAILURE;
+    }
+
+  // go to step 3 (a finish step)
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step3, step1, 10, 10, step2, 8, 8, step3, 6, 5, step4, 1, 1))
+    {
+    std::cerr << "error going from step1 to step3";
+    return EXIT_FAILURE;
+    }
+
+  // try to go automatically to step 4 (another goTo step)
+  workflow->goToStep("Step 4");
+  if (!transitionTest(workflow, defaultTime, app, step3, step1, 10, 10, step2, 8, 8, step3, 7, 6, step4, 2, 2))
+    {
+    std::cerr << "error going from the first finish step to the second finish step";
+    return EXIT_FAILURE;
+    }
+
+  // go to step 4, and then go forward (should not let you go past last step)
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step4, step1, 10, 10, step2, 8, 8, step3, 7, 7, step4, 3, 2))
+    {
+    std::cerr << "error going forward past last step - shouldn't let you";
+    return EXIT_FAILURE;
+    }
+
+  // now try to go from step 4 to step 4 (should loop)
+  workflow->goToStep("Step 4");
+  if (!transitionTest(workflow, defaultTime, app, step4, step1, 10, 10, step2, 8, 8, step3, 7, 7, step4, 4, 3))
+    {
+    std::cerr << "error looping from step 4 to step 4";
+    return EXIT_FAILURE;
+    }  
+
+  // go back to step 3, and then go from step 3 to step 3 (should loop without hitting step4)
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  workflow->goToStep("Step 3");
+  if (!transitionTest(workflow, defaultTime, app, step3, step1, 10, 10, step2, 8, 8, step3, 9, 8, step4, 4, 4))
+    {
+    std::cerr << "error looping from step 3 to step 3";
+    return EXIT_FAILURE;
+    }  
+
+
+  // handles deletions of the workflow, steps, states and transitions
+  delete workflow;
+
+  return EXIT_SUCCESS;
+}

+ 260 - 0
Libs/Core/Testing/Cpp/ctkWorkflowTest2.cpp

@@ -0,0 +1,260 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+=========================================================================*/
+
+// QT includes
+#include <QApplication>
+#include <QTimer>
+
+// CTK includes
+#include "ctkExampleWorkflowStepUsingSignalsAndSlots.h"
+#include "ctkWorkflowStep.h"
+#include "ctkWorkflow.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+int numberOfTimesEntryExitTest(ctkExampleWorkflowStepUsingSignalsAndSlots* step1=0, int step1Entry=0, int step1Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step2=0, int step2Entry=0, int step2Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  if (step1)
+    {
+    if (step1->numberOfTimesRanOnEntry() != step1Entry || step1->numberOfTimesRanOnExit() != step1Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step2)
+    {
+    if (step2->numberOfTimesRanOnEntry() != step2Entry || step2->numberOfTimesRanOnExit() != step2Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step3)
+    {
+    if (step3->numberOfTimesRanOnEntry() != step3Entry || step3->numberOfTimesRanOnExit() != step3Exit)
+      {
+      return 0;
+      }
+    }
+
+  if (step4)
+    {
+    if (step4->numberOfTimesRanOnEntry() != step4Entry || step4->numberOfTimesRanOnExit() != step4Exit)
+      {
+      return 0;
+      }    
+    }
+
+  return 1;
+}
+
+//-----------------------------------------------------------------------------
+int currentStepAndNumberOfTimesEntryExitTest(ctkWorkflow* workflow, ctkWorkflowStep* expectedStep, ctkExampleWorkflowStepUsingSignalsAndSlots* step1, int step1Entry, int step1Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step2, int step2Entry, int step2Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  if (expectedStep)
+    {
+    if (workflow->currentStep() != expectedStep)
+      {
+      return 0;
+      }
+    }
+  return numberOfTimesEntryExitTest(step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int transitionTest(ctkWorkflow* workflow, int defaultTime, QApplication& app, ctkWorkflowStep* expectedStep, ctkExampleWorkflowStepUsingSignalsAndSlots* step1, int step1Entry, int step1Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step2, int step2Entry, int step2Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  return currentStepAndNumberOfTimesEntryExitTest(workflow, expectedStep, step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int testStartWorkflow(ctkWorkflow* workflow, int defaultTime, QApplication& app, int shouldRun, ctkWorkflowStep* expectedStep=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step1=0, int step1Entry=0, int step1Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step2=0, int step2Entry=0, int step2Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  workflow->start();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->isRunning() != shouldRun)
+    {
+    return 0;
+    }
+  return currentStepAndNumberOfTimesEntryExitTest(workflow, expectedStep, step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int testStopWorkflow(ctkWorkflow* workflow, int defaultTime, QApplication& app, ctkExampleWorkflowStepUsingSignalsAndSlots* step1, int step1Entry, int step1Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step2, int step2Entry, int step2Exit, ctkExampleWorkflowStepUsingSignalsAndSlots* step3=0, int step3Entry=0, int step3Exit=0, ctkExampleWorkflowStepUsingSignalsAndSlots* step4=0, int step4Entry=0, int step4Exit=0)
+{
+  workflow->stop();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->isRunning())
+    {
+    return 0;
+    }
+  return numberOfTimesEntryExitTest(step1, step1Entry, step1Exit, step2, step2Entry, step2Exit, step3, step3Entry, step3Exit, step4, step4Entry, step4Exit);
+}
+
+//-----------------------------------------------------------------------------
+int ctkWorkflowTest2(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+  int defaultTime = 100;
+
+  // create the steps and the workflow
+  ctkWorkflow *workflow = new ctkWorkflow();
+  ctkWorkflowStep *step1 = new ctkWorkflowStep(workflow, "Step 1");
+  step1->setName("Step 1");
+  step1->setDescription("Description for step 1");
+  ctkWorkflowStep *step2 = new ctkWorkflowStep(workflow, "Step 2");
+  step2->setName("Step 2");
+  step2->setDescription("Description for step 2");
+  ctkWorkflowStep *step3 = new ctkWorkflowStep(workflow, "Step 3");
+  step3->setName("Step 3");
+  step3->setDescription("Description for step 3");
+  ctkWorkflowStep *step4 = new ctkWorkflowStep(workflow, "Step 4");
+  step4->setName("Step 4");
+  step4->setDescription("Description for step 4");
+
+  // create the qObjects that implement the required functions, and
+  // communicate with the workflow using signals and slots
+  ctkExampleWorkflowStepUsingSignalsAndSlots* qObject1 = new ctkExampleWorkflowStepUsingSignalsAndSlots;
+  ctkExampleWorkflowStepUsingSignalsAndSlots* qObject2 = new ctkExampleWorkflowStepUsingSignalsAndSlots;
+  ctkExampleWorkflowStepUsingSignalsAndSlots* qObject3 = new ctkExampleWorkflowStepUsingSignalsAndSlots;
+  ctkExampleWorkflowStepUsingSignalsAndSlots* qObject4 = new ctkExampleWorkflowStepUsingSignalsAndSlots;
+
+  // use the qObjects for validation
+  QObject::connect(step1, SIGNAL(invokeValidateCommand(const QString&)), qObject1, SLOT(validate(const QString&)));
+  QObject::connect(qObject1, SIGNAL(validationComplete(bool, const QString&)), workflow, SLOT(evaluateValidationResults(bool, const QString&)));
+  QObject::connect(step2, SIGNAL(invokeValidateCommand(const QString&)), qObject2, SLOT(validate(const QString&)));
+  QObject::connect(qObject2, SIGNAL(validationComplete(bool, const QString&)), workflow, SLOT(evaluateValidationResults(bool, const QString&)));
+  // step 3's validation will always fail
+  QObject::connect(step3, SIGNAL(invokeValidateCommand(const QString&)), qObject3, SLOT(validateFails()));
+  QObject::connect(qObject3, SIGNAL(validationComplete(bool, const QString&)), workflow, SLOT(evaluateValidationResults(bool, const QString&)));
+
+  QObject::connect(step4, SIGNAL(invokeValidateCommand(const QString&)), qObject4, SLOT(validate(const QString&)));
+  QObject::connect(qObject4, SIGNAL(validationComplete(bool, const QString&)), workflow, SLOT(evaluateValidationResults(bool, const QString&)));
+
+  // use the qObjects for entry processing
+  QObject::connect(step1, SIGNAL(invokeOnEntryCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject1, SLOT(onEntry(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject1, SIGNAL(onEntryComplete()), workflow, SLOT(processingAfterOnEntry()));
+  QObject::connect(step2, SIGNAL(invokeOnEntryCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject2, SLOT(onEntry(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject2, SIGNAL(onEntryComplete()), workflow, SLOT(processingAfterOnEntry()));
+  QObject::connect(step3, SIGNAL(invokeOnEntryCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject3, SLOT(onEntry(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject3, SIGNAL(onEntryComplete()), workflow, SLOT(processingAfterOnEntry()));
+  QObject::connect(step4, SIGNAL(invokeOnEntryCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject4, SLOT(onEntry(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject4, SIGNAL(onEntryComplete()), workflow, SLOT(processingAfterOnEntry()));
+
+  // use the qObjects for exit processing
+  QObject::connect(step1, SIGNAL(invokeOnExitCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject1, SLOT(onExit(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject1, SIGNAL(onExitComplete()), workflow, SLOT(processingAfterOnExit()));
+  QObject::connect(step2, SIGNAL(invokeOnExitCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject2, SLOT(onExit(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject2, SIGNAL(onExitComplete()), workflow, SLOT(processingAfterOnExit()));
+  QObject::connect(step3, SIGNAL(invokeOnExitCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject3, SLOT(onExit(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject3, SIGNAL(onExitComplete()), workflow, SLOT(processingAfterOnExit()));
+  QObject::connect(step4, SIGNAL(invokeOnExitCommand(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)), qObject4, SLOT(onExit(const ctkWorkflowStep*, const ctkWorkflowInterstepTransition::InterstepTransitionType)));
+  QObject::connect(qObject4, SIGNAL(onExitComplete()), workflow, SLOT(processingAfterOnExit()));
+
+  step1->setHasValidateCommand(1);
+  step2->setHasValidateCommand(1);
+  step3->setHasValidateCommand(1);
+  step4->setHasValidateCommand(1);
+
+  step1->setHasOnEntryCommand(1);
+  step2->setHasOnEntryCommand(1);
+  step3->setHasOnEntryCommand(1);
+  step4->setHasOnEntryCommand(1);
+
+  step1->setHasOnExitCommand(1);
+  step2->setHasOnExitCommand(1);
+  step3->setHasOnExitCommand(1);
+  step4->setHasOnExitCommand(1);
+  
+  // set the initial step (which sets the initial state)
+  workflow->setInitialStep(step1);
+  
+  // add the first and second steps
+  if (!workflow->addTransition(step1, step2))
+    {
+    std::cerr << "could not add 1st and 2nd step" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // add the third step
+  if (!workflow->addTransition(step2, step3))
+    {
+    std::cerr << "could not add 3rd step" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // add the fourth step
+  if (!workflow->addTransition(step3, step4))
+    {
+    std::cerr << "could not add 4rd step" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // start the workflow
+  if (!testStartWorkflow(workflow, defaultTime, app, 1, step1, qObject1, 1, 0, qObject2, 0, 0, qObject3, 0, 0, qObject4, 0, 0))
+    {
+    std::cerr << "error starting workflow";
+    return EXIT_FAILURE;
+    }
+
+  // trigger transition to the next step
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step2, qObject1, 1, 1, qObject2, 1, 0, qObject3, 0, 0, qObject4, 0, 0))
+    {
+    std::cerr << "error transitioning to step 2";
+    return EXIT_FAILURE;
+    }
+
+  // trigger transition to the next step
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step3, qObject1, 1, 1, qObject2, 1, 1, qObject3, 1, 0, qObject4, 0, 0))
+    {
+    std::cerr << "error transitioning to step 3";
+    return EXIT_FAILURE;
+    }
+
+  // trigger transition to the next state (this should fail!)
+  workflow->goForward();
+  if (!transitionTest(workflow, defaultTime, app, step3, qObject1, 1, 1, qObject2, 1, 1, qObject3, 1, 0, qObject4, 0, 0))
+    {
+    std::cerr << "error after transition failure at step 3";
+    return EXIT_FAILURE;
+    }
+
+  // make sure the workflow stops properly
+  if (!testStopWorkflow(workflow, defaultTime, app, qObject1, 1, 1, qObject2, 1, 1, qObject3, 1, 1, qObject4, 0, 0))
+    {
+    std::cerr << "error after stopping workflow";
+    return EXIT_FAILURE;
+    }
+
+  // handles deletions of the workflow, steps, states and transitions
+  delete workflow;
+
+  return EXIT_SUCCESS;
+}

+ 296 - 0
Libs/Core/Testing/Cpp/ctkWorkflowTest3.cpp

@@ -0,0 +1,296 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+  =========================================================================*/
+
+// QT includes
+#include <QApplication>
+#include <QTimer>
+
+// CTK includes
+#include "ctkBranchingWorkflowStep.h"
+#include "ctkExampleDerivedWorkflowStep.h"
+#include "ctkWorkflow.h"
+
+// STD includes
+#include <cstdlib>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+/*       "simple" s3----s4
+//               /        \
+//  s0----s1----s2        s7----s8
+//               \        /
+//     "advanced" s5----s6
+*/
+
+//-----------------------------------------------------------------------------
+int ctkWorkflowTest3(int argc, char * argv [] )
+{
+  QApplication app(argc, argv);
+  int defaultTime = 100;
+
+  // create two steps and the workflow
+  ctkWorkflow *workflow = new ctkWorkflow();
+  ctkExampleDerivedWorkflowStep* s0 = new ctkExampleDerivedWorkflowStep(workflow, "Step 0");
+  ctkExampleDerivedWorkflowStep* s1 = new ctkExampleDerivedWorkflowStep(workflow, "Step 1");
+  ctkBranchingWorkflowStep* s2 = new ctkBranchingWorkflowStep(workflow, "Step 2");
+  ctkExampleDerivedWorkflowStep* s3 = new ctkExampleDerivedWorkflowStep(workflow, "Step 3");
+  ctkExampleDerivedWorkflowStep* s4 = new ctkExampleDerivedWorkflowStep(workflow, "Step 4");
+  ctkExampleDerivedWorkflowStep* s5 = new ctkExampleDerivedWorkflowStep(workflow, "Step 5");
+  ctkExampleDerivedWorkflowStep* s6 = new ctkExampleDerivedWorkflowStep(workflow, "Step 6");
+  ctkExampleDerivedWorkflowStep* s7 = new ctkExampleDerivedWorkflowStep(workflow, "Step 7");
+  ctkExampleDerivedWorkflowStep* s8 = new ctkExampleDerivedWorkflowStep(workflow, "Step 8");
+
+  workflow->addTransition(s0, s1);
+  workflow->addTransition(s1, s2);
+  workflow->addTransition(s2, s3, "simple");
+  workflow->addTransition(s3, s4);
+  workflow->addTransition(s4, s7);
+  workflow->addTransition(s7, s8);
+  workflow->addTransition(s2, s5, "advanced");
+  workflow->addTransition(s5, s6);
+  workflow->addTransition(s6, s7);
+  workflow->setInitialStep(s0);
+
+  // test error handling for branching workflows:
+  if (workflow->addTransition(s6, s7))
+    {
+    std::cout << "should not be able to add duplicates of the same transition" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (workflow->addTransition(s6, s6))
+    {
+    std::cout << "currently do not support transitions from a step to itself" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  if (workflow->addTransition(s2, s5, "simple"))
+    {
+    std::cout << "should not be able to add multiple transitions with same id" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // test that the transitions are occuring properly
+  workflow->start();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+
+  // transition to s1
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s1)
+    {
+    std::cerr << "error transitioning s0->s1" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s2
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s2)
+    {
+    std::cerr << "error transitioning s1->s2" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s3
+  s2->setBranchId("simple");
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s3)
+    {
+    std::cerr << "*** branch *** error transitioning s2->s3" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s4
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s4)
+    {
+    std::cerr << "error transitioning s3->s4" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s7
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s7)
+    {
+    std::cerr << "error transitioning s4->s7" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s8
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s8)
+    {
+    std::cerr << "error transitioning s7->s8" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s7
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s7)
+    {
+    std::cerr << "error transitioning s8->s7" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s4
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s4)
+    {
+    std::cerr << "*** reverse branch *** error transitioning s7->s4" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s3
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s3)
+    {
+    std::cerr << "error transitioning s4->s3" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s2
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s2)
+    {
+    std::cerr << "error transitioning s3->s2" << std::endl;
+    return EXIT_FAILURE;
+    }
+ 
+  // transition to s5
+  s2->setBranchId("advanced");
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s5)
+    {
+    std::cerr << "*** branch *** error transitioning s2->s5" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s6
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s6)
+    {
+    std::cerr << "error transitioning s5->s6" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s7
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s7)
+    {
+    std::cerr << "error transitioning s6->s7" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition to s8
+  workflow->goForward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s8)
+    {
+    std::cerr << "error transitioning s7->s8" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s7
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s7)
+    {
+    std::cerr << "error transitioning s8->s7" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s6
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s6)
+    {
+    std::cerr << "error transitioning s7->s6" << std::endl;
+    std::cerr << qPrintable(workflow->currentStep()->id());
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s5
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s5)
+    {
+    std::cerr << "error transitioning s6->s5" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s2
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s2)
+    {
+    std::cerr << "error transitioning s5->s2" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  // transition back to s1
+  workflow->goBackward();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+  if (workflow->currentStep() != s1)
+    {
+    std::cerr << "error transitioning s2->s1" << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  workflow->stop();
+  QTimer::singleShot(defaultTime, &app, SLOT(quit()));
+  app.exec();
+
+  // handles deletions of the workflow, steps, states and transitions
+  delete workflow;
+
+  return EXIT_SUCCESS;
+}

File diff suppressed because it is too large
+ 1295 - 0
Libs/Core/ctkWorkflow.cpp


+ 255 - 0
Libs/Core/ctkWorkflow.h

@@ -0,0 +1,255 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkWorkflow_h
+#define __ctkWorkflow_h
+
+// Qt includes
+#include <QObject>
+
+// CTK includes
+#include "ctkPimpl.h"
+#include "CTKCoreExport.h"
+
+class ctkWorkflowStep;
+class ctkWorkflowPrivate;
+class QAbstractState;
+
+/// \brief ctkWorkflow is the basis for a workflow engine, i.e. a state
+/// machine with enhancements to support ctkWorkflowStep.
+
+class CTK_CORE_EXPORT ctkWorkflow : public QObject
+{
+  Q_OBJECT
+
+public:
+
+  typedef QObject Superclass;
+  explicit ctkWorkflow(QObject* parent = 0);
+  virtual ~ctkWorkflow();
+
+  /// \brief Start the workflow.
+  /// The workflow will always start in the initial step, even if it is stopped and restarted).
+  /// \note Calls onEntry() for the initial step.
+  virtual void start();
+
+  /// \brief Returns whether the workflow is currently running
+  bool isRunning()const;
+
+  /// \brief Stops the workflow.
+  /// \note Calls onExit() for the current step.
+  virtual void stop();
+
+  /// \brief Transition directionalities.
+  ///
+  /// The direction of transitions between an origin step and a destination step can be either:
+  /// <ul>
+  ///  <li>\a Bidirectional: A transition from the origin to the destination, and a transition from
+  /// the destination to the origin</li>
+  ///  <li>\a Forward: A transition from the origin to the destination only</li>
+  ///  <li>\a Backward: A transition from the destination to the origin only</li>
+  enum TransitionDirectionality
+  {
+    Bidirectional = 0,
+    Forward,
+    Backward
+  };
+
+  /// \brief Creates a transition between two steps, and adds the two steps to the workflow if they
+  /// have not been previously added. (Cannot add two steps with the same id).
+  ///
+  /// The destination step should semantically be a next step, i.e. from a workflow perspective, the
+  /// \a destination step is meant to appear after the \a origin step.
+  /// Tthis method will:
+  /// <ul>
+  ///  <li>Call addStep()</li> to add the origin and destination steps, if they have not been
+  /// previously added to the workflow</li>
+  ///  <li>If \a directionality is ctkWorkflow::Bidirectional or ctkWorkflow::Forward, creates a
+  /// transition from the origin to the destination (more specifically, the transition is from the
+  /// \a origin's validation state to the \a destination's processing state, and is of type ctkWorkflowTransition::TransitionToNextStep</li>
+  ///  <li>If \a directionality is ctkWorkflow::Bidirectional or ctkWorkflow::Backward, creates a
+  /// transition from the destination to the origin (more specifically, the transition is from the
+  /// \a destination's processing state to the \a origin's processing state, and is of type
+  /// ctkWorkflowTransition::TransitionToPreviousStep</li>
+  ///
+  /// The default value of directionality is ctkWorkflow::Bidirectional.
+  ///
+  /// To add a single step, \a destination can be set to 0.
+  ///
+  /// Returns true/false indicating whether the method was successful.
+  virtual bool addTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
+                             const QString& branchId = QString(),
+                             const ctkWorkflow::TransitionDirectionality directionality
+                               = ctkWorkflow::Bidirectional);
+
+  // /// \Determine whether a transition has already been added
+  // bool hasTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
+  //                    const ctkWorkflow::TransitionDirectionality directionality/*,
+  //                    const QString& branchId = QString()*/);
+
+  /// \Determine whether a transition has already been added
+  /// If a branch id is not given or is empty: a transition exists if the transition has been
+  /// previously added with the same origin, destination and directionality
+  /// if a non-empty branch id is given: a transition exists if the transition has been previously
+  /// added with the same origin, destination and directionality, OR if a transition has been
+  /// previously added wtih the same origin and branch id (for forward transitions) or
+  /// with the same destination and branch id (for backward transitions)
+  bool hasTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
+                     const QString& branchId = QString(),
+                     const ctkWorkflow::TransitionDirectionality directionality = ctkWorkflow::Bidirectional);
+
+  /// \brief Set/get the initial step.
+  ///
+  /// Convenience method to set the QStateMachine's initialState to a specific step's
+  /// processing state.
+  ///
+  /// \note The initialStep() function *must* be called to set the state machine's initial state
+  /// correctly
+  ctkWorkflowStep* initialStep()const;
+  virtual void setInitialStep(ctkWorkflowStep* step);
+
+  /// Get the current step of the state machine
+  ctkWorkflowStep* currentStep()const;
+
+  /// Check to see if there is a step with a given id in the workflow.
+  bool hasStep(const QString& id)const;
+
+  /// Returns whether or not we can go forward: i.e. the workflow is running and there exists a step
+  /// that directly follows the given step.
+  ///
+  /// If no step is given, then the workflow's current step will be used.
+  bool canGoForward(ctkWorkflowStep* step=0)const;
+
+  /// Returns whether or not we can go backward: i.e. the workflow is running and there exists a
+  /// step that directly preceeds the given step.
+  ///
+  /// If no step is given, then the workflow's current step will be used.
+  bool canGoBackward(ctkWorkflowStep* step=0)const;
+
+  /// Returns whether or not we can go to the goal step from the origin step: i.e. there is a path
+  /// in the workflow from the current step to the given step.
+  ///
+  /// If no step is designated as the 'origin', then the workflow's current step will be used
+  bool canGoToStep(const QString& targetId, ctkWorkflowStep* step=0)const;
+
+  /// Get the steps that directly follow the given step.
+  ///
+  /// More specifically, the returned list of steps will be the destination steps for which
+  /// addTransition() has been called with the given step as the origin step and directionality set
+  /// to ctkWorkflow::Bidirectional or ctkWorkflow::Forward.
+  ///
+  /// If no step is given, then the workflow's current step will be used.
+  QList<ctkWorkflowStep*> forwardSteps(ctkWorkflowStep* step=0)const;
+
+  /// Get the steps that directly preceed the given step.
+  ///
+  /// More specifically, the returned list of steps will be the origin steps for which
+  /// addTransition() has been called with the given step as the destination step and directionality
+  /// set to ctkWorkflow::Bidirectional or ctkWorkflow::Backward.
+  ///
+  /// If no step is given, then the workflow's current step will be used.
+  QList<ctkWorkflowStep*> backwardSteps(ctkWorkflowStep* step=0)const;
+
+  /// Get the steps that are 'finish' steps (i.e. have no step following them)
+  QList<ctkWorkflowStep*> finishSteps()const;
+
+public slots:
+
+  /// Use this to trigger evaluation of the processing state of the current step, and subsequent
+  /// conditional transition to the next step.
+  virtual void goForward(const QString& desiredBranchId = QString());
+
+  /// Use this to trigger transition to the previous step (does not require validation)
+  virtual void goBackward(const QString& desiredBranchId = QString());
+
+  /// Go to the given step by iteratively calling goForward() until we reach it.
+  virtual void goToStep(const QString& targetId);
+
+  /// \brief Receives the result of a step's validate(const QString&) function.
+  ///
+  /// If the validation is successful, then this slot begins the transition to the next step.
+  ///
+  /// This slot should be connected to each ctkWorkflowStep's validationComplete() signal.
+  virtual void evaluateValidationResults(bool validationSucceeded, const QString& branchId);
+
+  /// \brief Workflow processing executed after a step's onEntry function is run.
+  ///
+  /// This slot should be connected to each ctkWorkflowStep's onEntryComplete() signal.
+  virtual void processingAfterOnEntry();
+
+  /// \brief Workflow processing executed after a step's onExit function is run.
+  ///
+  /// This slot should be connected to each ctkWorkflowStep's onExitComplete() signal.
+  virtual void processingAfterOnExit();
+
+protected:
+
+  /// \brief Triggers the start of a ctkWorkflowTransition of type
+  /// ctkWorkflowTransitionType::TransitionToNextStep()
+  void goToNextStepAfterSuccessfulValidation(const QString& branchId);
+
+  /// \brief Triggers the start of a ctkWorkflowTransition of type
+  /// ctkWorkflowTransitionType::ValidationFailedTransition
+  void goToProcessingStateAfterValidationFailed();
+
+  /// \brief Processing that occurs after the attempt to go to a 'goTo' step succeeds
+  virtual void goToStepSucceeded();
+
+  /// \brief Processing that occurs after the attempt to go to a 'goTo' step fails
+  virtual void goToStepFailed();
+ 
+  /// \brief Goes to the step from which the attempt to go to the 'goTo' step was initiated
+  void goFromGoToStepToStartingStep();
+
+  /// \brief Performs required connections between the step and this
+  /// workflow, if the user is deriving a custom step as a subclasses of ctkWorkflowStep
+  virtual void connectStep(ctkWorkflowStep* step);
+ 
+protected slots:
+
+  /// On an attempt to go to the next step, calls the current step's
+  /// validate(const QString&) function to validate the processing step.  The
+  /// validate(const QString&) function emits a signal that is connected to the
+  /// workflow's evaluateValidationResults slot.  If the validation is
+  /// successful, then the slot triggers the start of a
+  /// ctkWorkflowTransition of type
+  /// ctkWorkflowTransitionType::TransitionToNextStep, otherwise it
+  /// triggers the start of a ctkWorkflowTransition of type
+  /// ctkWorkflowTransitionType::ValidationFailedTransition.
+  void attemptToGoToNextStep();
+
+  /// \brief May be called when transitioning to the next step upon successful validation, or
+  /// when transitioning to the previous step.
+  /// Calls onExit() of the transition's origin step and then onEntry() of
+  /// the transition's destination step.
+  /// \note Must be sent by a ctkWorkflowTransition.
+  void performTransitionBetweenSteps();
+
+signals:
+  /// Emitted when the current step has changed, after the step's onEntry() has completed.
+  /// \note This signal is not emitted in the process of going to a goToStep
+  void currentStepChanged(ctkWorkflowStep* currentStep);
+
+private:
+  CTK_DECLARE_PRIVATE(ctkWorkflow);
+};
+
+#endif
+

+ 192 - 0
Libs/Core/ctkWorkflowStep.cpp

@@ -0,0 +1,192 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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.
+ 
+  =========================================================================*/
+
+// Qt includes
+#include <QState>
+
+// CTK includes
+#include "ctkLogger.h"
+#include "ctkWorkflowStep.h"
+#include "ctkWorkflow.h"
+
+// STD includes
+#include <iostream>
+
+//--------------------------------------------------------------------------
+static ctkLogger logger("org.commontk.core.ctkWorkflowStep");
+//--------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+class ctkWorkflowStepPrivate: public ctkPrivate<ctkWorkflowStep>
+{
+public:
+  CTK_DECLARE_PUBLIC(ctkWorkflowStep);
+  ctkWorkflowStepPrivate();
+  ~ctkWorkflowStepPrivate();
+
+  ctkWorkflow* Workflow;
+
+  QString      Id;
+  QString      Name;
+  QString      Description;
+  QString      StatusText;
+
+  QState* ProcessingState;
+  QState* ValidationState;
+
+  ctkWorkflowIntrastepTransition* ValidationTransition;
+  ctkWorkflowIntrastepTransition* ValidationFailedTransition;
+
+  bool HasValidateCommand;
+  bool HasOnEntryCommand;
+  bool HasOnExitCommand;
+};
+
+// --------------------------------------------------------------------------
+// ctkWorkflowStepPrivate methods
+
+// --------------------------------------------------------------------------
+ctkWorkflowStepPrivate::ctkWorkflowStepPrivate()
+{
+  this->Workflow = 0;
+
+  this->HasValidateCommand = false;
+  this->HasOnEntryCommand = false;
+  this->HasOnExitCommand = false;
+
+  // Create state
+  this->ProcessingState = new QState();
+  this->ValidationState = new QState();
+
+  // Create 'validation' transition
+  this->ValidationTransition =
+      new ctkWorkflowIntrastepTransition(ctkWorkflowIntrastepTransition::ValidationTransition);
+  this->ValidationTransition->setTargetState(this->ValidationState);
+  this->ProcessingState->addTransition(this->ValidationTransition);
+
+  // Create 'failed validation' transation
+  this->ValidationFailedTransition = 0;
+  this->ValidationFailedTransition =
+      new ctkWorkflowIntrastepTransition(ctkWorkflowIntrastepTransition::ValidationFailedTransition);
+  this->ValidationFailedTransition->setTargetState(this->ProcessingState);
+  this->ValidationState->addTransition(this->ValidationFailedTransition);
+}
+
+// --------------------------------------------------------------------------
+ctkWorkflowStepPrivate::~ctkWorkflowStepPrivate()
+{
+  delete this->ValidationState;
+  delete this->ProcessingState;
+
+  // If we delete the states, then Qt will handle deleting the transitions
+}
+
+// --------------------------------------------------------------------------
+// ctkWorkflowStep methods
+
+// --------------------------------------------------------------------------
+ctkWorkflowStep::ctkWorkflowStep(ctkWorkflow* newWorkflow, const QString& newId) : Superclass()
+{
+  CTK_INIT_PRIVATE(ctkWorkflowStep);
+  CTK_D(ctkWorkflowStep);
+
+  if (newId.isEmpty())
+    {
+    d->Id = this->metaObject()->className();
+    }
+  else
+    {
+    d->Id = newId;
+    }
+
+  d->Workflow = newWorkflow;
+}
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, ctkWorkflow*, workflow, Workflow);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, QString, id, Id);
+CTK_SET_CXX(ctkWorkflowStep, const QString&, setId, Id);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, QString, name, Name);
+CTK_SET_CXX(ctkWorkflowStep, const QString&, setName, Name);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, QString, description, Description);
+CTK_SET_CXX(ctkWorkflowStep, const QString&, setDescription, Description);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, QString, statusText, StatusText);
+CTK_SET_CXX(ctkWorkflowStep, const QString&, setStatusText, StatusText);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, bool, hasValidateCommand, HasValidateCommand);
+CTK_SET_CXX(ctkWorkflowStep, bool, setHasValidateCommand, HasValidateCommand);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, bool, hasOnEntryCommand, HasOnEntryCommand);
+CTK_SET_CXX(ctkWorkflowStep, bool, setHasOnEntryCommand, HasOnEntryCommand);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, bool, hasOnExitCommand, HasOnExitCommand);
+CTK_SET_CXX(ctkWorkflowStep, bool, setHasOnExitCommand, HasOnExitCommand);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, QState*, processingState, ProcessingState);
+CTK_GET_CXX(ctkWorkflowStep, QState*, validationState, ValidationState);
+
+// --------------------------------------------------------------------------
+CTK_GET_CXX(ctkWorkflowStep, ctkWorkflowIntrastepTransition*, validationTransition, ValidationTransition);
+CTK_GET_CXX(ctkWorkflowStep, ctkWorkflowIntrastepTransition*,
+            validationFailedTransition, ValidationFailedTransition);
+
+// --------------------------------------------------------------------------
+void ctkWorkflowStep::validate(const QString& desiredBranchId)
+{
+  CTK_D(ctkWorkflowStep);
+  logger.info(QString("validate - validating the input from %1").arg(d->Name));
+
+  emit this->validationComplete(true, desiredBranchId);
+}
+
+
+// --------------------------------------------------------------------------
+void ctkWorkflowStep::onEntry(const ctkWorkflowStep* comingFrom,
+                              const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(comingFrom);
+  Q_UNUSED(transitionType);
+
+  // Signals that we are finished
+  emit this->onEntryComplete();
+}
+
+// --------------------------------------------------------------------------
+void ctkWorkflowStep::onExit(const ctkWorkflowStep* goingTo,
+                             const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)
+{
+  Q_UNUSED(goingTo);
+  Q_UNUSED(transitionType);
+
+  // Signals that we are finished
+  emit this->onExitComplete();
+}

+ 252 - 0
Libs/Core/ctkWorkflowStep.h

@@ -0,0 +1,252 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkWorkflowStep_h
+#define __ctkWorkflowStep_h
+
+// Qt includes
+#include <QObject>
+
+// CTK includes
+#include "ctkPimpl.h"
+#include "CTKCoreExport.h"
+#include "ctkWorkflowTransitions.h"
+class ctkWorkflow;
+
+class ctkWorkflowStepPrivate;
+class QState;
+
+/// \brief ctkWorkflowStep is the basis for a workflow step.
+///
+/// A workflow step is a placeholder for various states and transitions that are used in a
+/// typical workflow. Such steps can be added to instances of the ctkWorkflow class.
+
+class CTK_CORE_EXPORT ctkWorkflowStep : public QObject
+{
+  Q_OBJECT
+
+public:
+  typedef QObject Superclass;
+  explicit ctkWorkflowStep(ctkWorkflow* newWorkflow, const QString& newId = QString());
+  virtual ~ctkWorkflowStep(){}
+
+  /// Get the \a workflow associated with this step
+  ctkWorkflow* workflow()const;
+
+  /// Get id
+  QString id()const;
+
+  /// Set/get \a name
+  QString name()const;
+  void setName(const QString& newName);
+
+  /// Set/get \a description
+  QString description()const;
+  void setDescription(const QString& newDescription);
+
+  ///
+  /// Set/get \a statusText
+  QString statusText()const;
+  void setStatusText(const QString& newText);
+
+  ///
+  /// Set/get whether a validationCommand has been provided in a separate QObject
+  /// \note see method 2 described for validation()
+  /// \sa validation()
+  bool hasValidateCommand()const;
+  void setHasValidateCommand(bool newHasValidateCommand);
+
+  /// Set/get whether an onEntryCommand has been provided in a separate QObject
+  /// \note See method2 in onEntry()
+  /// \sa onEntry()
+  bool hasOnEntryCommand()const;
+  void setHasOnEntryCommand(bool newHasOnEntryCommand);
+
+  ///
+  /// Set/get whether an onExitCommand has been provided in a separate QObject
+  /// \note  See method2 in onExit()
+  /// \sa onExit()
+  bool hasOnExitCommand()const;
+  void setHasOnExitCommand(bool newHasOnExitCommand);
+
+protected:
+
+  /// Set step Id
+  void setId(const QString& newStepId);
+
+  /// \brief Get the step's processing state.
+  ///
+  /// This state is used to perform the processing associated with this step.
+  QState* processingState()const;
+
+  /// \brief Get the step's validation state.
+  ///
+  /// The validate(const QString&) method is the key component to define for this state to work as expected.
+  /// This state is used to validate the processing pertaining to this
+  /// step, then branch to the next step's processingState state on
+  /// success, or back to the current step's processingState state on
+  /// error.
+  ///
+  /// When the validation state emits its entered() signal, the corresponding workflow's
+  /// attemptToGoToNextStep() slot is called.
+  ///
+  /// This function calls the step's validate(const QString&) method to evaluate
+  /// whether one can transition to the next step.
+  QState* validationState()const;
+
+  /// \brief Get the step's validation transition.
+  ///
+  /// The validation transition is used to bring the state machine
+  /// from the step's processingState to its validationState.
+  /// More specifically:
+  /// <ul>
+  ///  <li>its origin state is the processingState state</li>
+  ///  <li>its destination state is the validationState state</li>
+  /// </ul>
+  ///
+  /// The transition is of type ctkWorkflowTransition with the value
+  /// ctkWorkflowTransitionType::ValidationTransition.
+  ctkWorkflowIntrastepTransition* validationTransition()const;
+
+  /// \brief Get the step's validation failed transition.
+  ///
+  /// The validationFailed transition is used to bring the state
+  /// machine from the step's validationState state back to its
+  /// processingState, when validation of the processing step fails
+  /// (i.e. validate(const QString&) returns false).
+  ///
+  /// More specifically:
+  /// <ul>
+  ///  <li>its origin state is the validatationState state</li>
+  ///  <li>its destination state is the processingState state</li>
+  /// </ul>
+  ///
+  /// The transition is of type ctkWorkflowTransition with the value
+  /// ctkWorkflowTransitionType::ValidationFailedTransition.
+  ctkWorkflowIntrastepTransition* validationFailedTransition()const;
+
+  ///\brief  Reimplement this function for step-specific processing when entering a step.
+  ///
+  /// To define a custom step, developers can either reimplement the onEntry() method in a subclass
+  /// of ctkWorkflowStep, or create a ctkWorkflowStep instance and set the onEntryCommand to point
+  /// to a callback of their choice.
+  ///
+  /// Each step should be self-contained, \a comingFrom and \a transitionType may
+  /// be used only to decide on how processing should be done for the current step.
+  ///
+  /// \param comingFrom gives the step that the state machine was in before
+  /// transitioning to this step.
+  ///
+  /// \param transitionType gives the type of the transition used to get to this step.
+  virtual void onEntry(const ctkWorkflowStep* comingFrom, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+  /// \brief Reimplement this function for step-specific processing when exiting a step.
+  ///
+  /// To define a custom step, developers can either reimplement the onExit() method
+  /// in a subclass of ctkWorkflowStep, or create a ctkWorkflowStep instance and set the
+  /// onExitCommand to point to a callback of their choice.
+  ///
+  /// Each step should be self-contained, \a goingTo and \a transitionType may be used only to
+  /// decide on how processing should be done for the current step.
+  ///
+  /// \param goingTo gives the step that the state machine will go to after
+  /// transitioning from this step.
+  /// \param transitionType gives the type of the transition used to get to this step.
+  virtual void onExit(const ctkWorkflowStep* goingTo,
+                      const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType);
+
+  /// \brief Validates the computation performed in this step's processing state.
+  ///
+  /// The validate(const QString&) function is called from the workflow's attemptToGoToNextStep() slot,
+  /// which is invoked by the validatationState state's entered() signal.  It must emit a
+  /// signal with a single integer parameter.  This signal is connected to the workflow's
+  /// evaluateValidationResults(int) slot, which then performs conditional transition to
+  /// the next state.
+  ///
+  /// When creating a custom step, developers can create a validate(const QString&) method is one of two ways:
+  /// 1) Reimplement the validate(const QString&) method in a subclass of
+  /// ctkWorkflowStep, following these instructions:
+  /// <ul>
+  ///   <li>*do* call the Superclass's validate(const QString&) function, and fail if it returns 0</li>
+  ///   <li>emit the signal ctkWorkflowStep::validateComplete(int) (1 on successful validation,
+  /// 0 on failure)</li>
+  ///   <li>return an int (1 on successful validation, 0 otherwise)</li>
+  /// </ul>
+  //
+  /// OR:
+  ///
+  /// 2) Create an instance of a ctkWorkflowStep then:
+  /// <ul>
+  ///  <li>Call setHasValidateCommand(1) on the step *before* adding the step to the workflow</li>
+  ///  <li>Create a slot foo() associated with any QObject*, following these instructions:</li>
+  ///  <ul>
+  ///    <li>There is *NO* need to call the Superclass's validate(const QString&) function
+  /// (error checking is performed before the invokeValidateCommand(const QString&)
+  /// signal is emitted)</li>
+  ///     <li>Emit a signal bar(int) (1 on successful validation, 0 on
+  /// failure)</li>
+  ///     <li>Return: 1 on successful validation, 0 otherwise</li>
+  ///     <li>Set the following two connections:</li>
+  ///     <ul>
+  ///       <li>QObject::connect(step, SIGNAL(invokeValidateCommand(const QString&)), object,
+  /// SLOT(foo()))</li>
+  ///       <li>QObject::connect(object, SIGNAl(bar(int)), workflow,
+  /// SLOT(evaluateValidationResults(int)))</li>
+  ///      </ul>
+  ///    </ul>
+  ///  </ul>
+  ///
+  /// \note ctkWorkflowStep does not function within a workflow as implemented here,
+  /// one of the above two methods must be followed
+  virtual void validate(const QString& desiredBranchId = QString());
+
+signals:
+  /// \brief Signal indicating that validation of this step's processing should be performed.
+  ///
+  /// \note Should be used if a validationCommand has been provided
+  /// See method 2 described in validation()
+  ///
+  /// \sa validation()
+  void invokeValidateCommand(const QString& desiredBranchId = QString())const;
+
+  /// \brief Signal indicating that validation of this step's processing has completed.
+  ///
+  /// \note Should be used if a validationCommand has not been provided
+  /// See method 1 described in validation()
+  ///
+  /// \param validationSuceeded 1 on successful validation, 0 on failure
+  void validationComplete(bool validationSuceeded, const QString& branchId = QString())const;
+
+  void invokeOnEntryCommand(const ctkWorkflowStep* comingFrom, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)const;
+
+  void onEntryComplete()const;
+
+  void invokeOnExitCommand(const ctkWorkflowStep* goingTo, const ctkWorkflowInterstepTransition::InterstepTransitionType transitionType)const;
+
+  void onExitComplete()const;
+
+private:
+
+  CTK_DECLARE_PRIVATE(ctkWorkflowStep);
+  friend class ctkWorkflow;        // For access to processingState, validationState, setWorkflow, validate
+  friend class ctkWorkflowPrivate; // For access to invokeOn{Entry,Exit}Command, on{Entry,Exit}
+};
+
+#endif

+ 186 - 0
Libs/Core/ctkWorkflowTransitions.h

@@ -0,0 +1,186 @@
+/*=========================================================================
+
+  Library:   CTK
+ 
+  Copyright (c) 2010  Kitware Inc.
+
+  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.commontk.org/LICENSE
+
+  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 __ctkWorkflowTransition_h
+#define __ctkWorkflowTransition_h
+
+// Qt includes
+#include <QEvent>
+#include <QAbstractTransition>
+#include <QString>
+
+// CTK includes
+#include "CTKCoreExport.h"
+
+/// \brief Custom transitions for use with ctkWorkflow.
+///
+/// ctkWorkflowIntrastepTransition: for transition between states of the same step.  The transition
+/// occurs only when the transition event's EventTransitionType matches the transition's
+/// TransitionType).
+///
+/// ctkWorkflowInterstepTransition: for transition between states of different steps (i.e. when
+/// transitioning between steps).  The transition occurs only when the transition event's
+/// EventTransitionType matches the transition's TransitionType, and when the transition event's
+/// EventId matches the transition's Id.
+///
+/// Example:
+/// \code
+/// ctkWorkflowInterstepTransition *transition = new
+/// ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToNextStep, "branchId");
+///
+/// workflow.postEvent(new
+/// ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToNextStep, "branchId")
+/// \endcode
+
+//-----------------------------------------------------------------------------
+struct CTK_CORE_EXPORT ctkWorkflowIntrastepTransitionEvent : public QEvent
+{
+
+  /// EventTransitionType is the value of a transition event, used to conditionally trigger transitions
+  ctkWorkflowIntrastepTransitionEvent(int newTransitionType)
+    : QEvent(QEvent::Type(getWorkflowIntrastepTransitionEventType())),
+      EventTransitionType(newTransitionType){}
+
+  /// Reserve a custom event type, ensuring that we are not re-using an
+  /// event type that was previously used
+  static inline int getWorkflowIntrastepTransitionEventType()
+  {
+    static int workflowIntrastepTransitionEventType = QEvent::registerEventType(QEvent::User+1);
+    return workflowIntrastepTransitionEventType;
+  }
+
+  int EventTransitionType;
+};
+
+//-----------------------------------------------------------------------------
+class CTK_CORE_EXPORT ctkWorkflowIntrastepTransition : public QAbstractTransition
+{
+  Q_OBJECT
+
+public:
+
+  enum IntrastepTransitionType
+  {
+    ValidationTransition = 0,
+    ValidationFailedTransition
+  };
+
+  ctkWorkflowIntrastepTransition(IntrastepTransitionType newTransitionType)
+    : TransitionType(newTransitionType){}
+
+  IntrastepTransitionType transitionType() {return this->TransitionType;}
+
+protected:
+  virtual bool eventTest(QEvent* e)
+  {
+    // check the event type
+    if (e->type() != ctkWorkflowIntrastepTransitionEvent::getWorkflowIntrastepTransitionEventType())
+      {
+      return false;
+      }
+
+    // check the event value (i.e. the TransitionType)
+    ctkWorkflowIntrastepTransitionEvent* workflowEvent = static_cast<ctkWorkflowIntrastepTransitionEvent*>(e);
+
+    return (this->TransitionType == workflowEvent->EventTransitionType);
+  }
+
+  void onTransition(QEvent*){}
+
+private:
+  IntrastepTransitionType TransitionType;
+
+};
+
+//-----------------------------------------------------------------------------
+struct CTK_CORE_EXPORT ctkWorkflowInterstepTransitionEvent : public QEvent
+{
+
+  /// EventTransitionType is the value of a transition event, used to conditionally trigger transitions
+  ctkWorkflowInterstepTransitionEvent(int newTransitionType)
+    : QEvent(QEvent::Type(getWorkflowInterstepTransitionEventType())),
+      EventTransitionType(newTransitionType){}
+  ctkWorkflowInterstepTransitionEvent(int newTransitionType, const QString& newId)
+    : QEvent(QEvent::Type(getWorkflowInterstepTransitionEventType())),
+    EventTransitionType(newTransitionType),
+    EventId(newId){}
+
+  /// Reserve a custom event type, ensuring that we are not re-using an
+  /// event type that was previously used
+  static inline int getWorkflowInterstepTransitionEventType()
+  {
+    static int workflowInterstepTransitionEventType = QEvent::registerEventType(QEvent::User+1);
+    return workflowInterstepTransitionEventType;
+  }
+
+  int     EventTransitionType;
+  QString EventId;
+};
+
+//-----------------------------------------------------------------------------
+class CTK_CORE_EXPORT ctkWorkflowInterstepTransition : public QAbstractTransition
+{
+  Q_OBJECT
+
+public:
+
+  enum InterstepTransitionType
+  {
+    TransitionToNextStep = 0,
+    TransitionToPreviousStep,
+    StartingWorkflow,
+    StoppingWorkflow,
+    TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep
+  };
+
+  ctkWorkflowInterstepTransition(InterstepTransitionType newTransitionType)
+    : TransitionType(newTransitionType){}
+  ctkWorkflowInterstepTransition(InterstepTransitionType newTransitionType, const QString& newId)
+    : TransitionType(newTransitionType),
+    Id(newId) {}
+
+  InterstepTransitionType transitionType() {return this->TransitionType;}
+  QString& id() {return this->Id;}
+
+protected:
+  virtual bool eventTest(QEvent* e)
+  {
+    // check the event type
+    if (e->type() != ctkWorkflowInterstepTransitionEvent::getWorkflowInterstepTransitionEventType())
+      {
+      return false;
+      }
+
+    // check the event value (i.e. the TransitionType)
+    ctkWorkflowInterstepTransitionEvent* workflowEvent = static_cast<ctkWorkflowInterstepTransitionEvent*>(e);
+
+    return (this->TransitionType == workflowEvent->EventTransitionType
+            && this->Id == workflowEvent->EventId); 
+  }
+
+  void onTransition(QEvent*){}
+
+private:
+  InterstepTransitionType TransitionType;
+  QString                 Id;
+
+};
+
+#endif