/*========================================================================= Library: CTK Copyright (c) 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.apache.org/licenses/LICENSE-2.0.txt 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 // CTK includes #include "ctkPimpl.h" #include "ctkCoreExport.h" class ctkWorkflowStep; class ctkWorkflowPrivate; class QAbstractState; /// \ingroup Core /// \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 Q_ENUMS(TransitionDirectionality) Q_PROPERTY(bool isRunning READ isRunning DESIGNABLE false) Q_PROPERTY(bool goBackToOriginStepUponSuccess READ goBackToOriginStepUponSuccess WRITE setGoBackToOriginStepUponSuccess) 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. Q_INVOKABLE 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. Q_INVOKABLE virtual void stop(); /// \brief Transition directionalities. /// /// The direction of transitions between an origin step and a destination step can be either: /// 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. /// /// To add a single step, \a destination can be set to 0. /// /// Returns true/false indicating whether the method was successful. Q_INVOKABLE virtual bool addTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination, const QString& branchId = QString(), const ctkWorkflow::TransitionDirectionality directionality = ctkWorkflow::Bidirectional); /// \brief Determine whether a transition has already been added /// Q_INVOKABLE bool hasTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination, const QString& branchId = QString(), const ctkWorkflow::TransitionDirectionality directionality = ctkWorkflow::Bidirectional); /// \brief Set/get the initial step. /// \note In not specified, the first step added will be considered as the initialStep Q_INVOKABLE ctkWorkflowStep* initialStep()const; Q_INVOKABLE virtual void setInitialStep(ctkWorkflowStep* step); /// \brief Register ctkWorkflowStep so that ctkWorkflow keeps track of the associated steps /// and clean the memory when appropriate. /// \note This function is declared public for convenience and shouldn't be directly used. /// The step will register itself when instantiated. /// \note Since ctkWorkflowStep are neither QObject nor QWidget, they will be registered. On /// on the othen hand, ctkWorkflowWidgetStep will be managed by their parent QWidget and /// won't be registered. void registerWorkflowStep(ctkWorkflowStep* step); /// Get the current step of the state machine Q_INVOKABLE ctkWorkflowStep* currentStep()const; /// Check to see if there is a step with a given id in the workflow. Q_INVOKABLE bool hasStep(const QString& id)const; /// Returns whether or not we can go forward: i.e. there exists a step that directly follows the /// given step. /// /// If no step is given, then the workflow's current step will be used. Q_INVOKABLE bool canGoForward(ctkWorkflowStep* step=0)const; /// Returns whether or not we can go backward: i.e. there exists a step that directly preceeds the /// given step. /// /// If no step is given, then the workflow's current step will be used. Q_INVOKABLE 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 /// Note: does not currently work in branching workflows if the origin and target steps are not on /// the same branch Q_INVOKABLE 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. Q_INVOKABLE QList 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. Q_INVOKABLE QList backwardSteps(ctkWorkflowStep* step=0)const; /// Get the steps that are 'finish' steps (i.e. have no step following them) Q_INVOKABLE QList finishSteps()const; /// Configures the behavior of goToStep(targetId). /// /// If set to true, goToStep(targetId) goes back to the origin step after /// the attempt of going to the target step succeeded. /// If set to false, goToStep(targetId) stays at the target step when the attempt /// succeeded. bool goBackToOriginStepUponSuccess()const; void setGoBackToOriginStepUponSuccess(bool flag); 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. virtual void evaluateValidationResults(bool validationSucceeded, const QString& branchId); protected: void goToNextStepAfterSuccessfulValidation(const QString& branchId); 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(); 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. void attemptToGoToNextStep(); /// \brief 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); protected: QScopedPointer d_ptr; private: Q_DECLARE_PRIVATE(ctkWorkflow); Q_DISABLE_COPY(ctkWorkflow); }; #endif