1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081 |
- /*=========================================================================
- 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.
- =========================================================================*/
- // Qt includes
- #include <QStateMachine>
- #include <QState>
- // CTK includes
- #include "ctkWorkflow.h"
- #include "ctkWorkflowStep.h"
- #include "ctkWorkflowStep_p.h"
- #include "ctkWorkflowTransitions.h"
- #include "ctkLogger.h"
- // STD includes
- #include <iostream>
- //--------------------------------------------------------------------------
- static ctkLogger logger("org.commontk.libs.core.ctkWorkflow");
- //--------------------------------------------------------------------------
- // --------------------------------------------------------------------------
- // ctkWorkflowPrivate methods
- // --------------------------------------------------------------------------
- ctkWorkflowPrivate::ctkWorkflowPrivate(ctkWorkflow& object)
- :q_ptr(&object)
- {
- this->InitialStep = 0;
- this->CurrentStep = 0;
- this->OriginStep = 0;
- this->DestinationStep = 0;
- this->GoToStep = 0;
- this->StartingStep = 0;
- this->TransitionToPreviousStartingStep = 0;
- // By default, go back to the origin step upon success of the goToStep(targetId) attempt.
- this->GoBackToOriginStepUponSuccess = true;
- this->ARTIFICIAL_BRANCH_ID_PREFIX = "ctkWorkflowArtificialBranchId_";
- }
- // --------------------------------------------------------------------------
- ctkWorkflowPrivate::~ctkWorkflowPrivate()
- {
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::addStep(ctkWorkflowStep* step)
- {
- Q_Q(ctkWorkflow);
- Q_ASSERT(step);
- Q_ASSERT(!q->hasStep(step->id()));
- Q_ASSERT(!this->StateMachine->isRunning());
- // Add the states, creating them if necessary
- this->StateMachine->addState(step->processingState());
- this->StateMachine->addState(step->validationState());
- // Update the map of steps to transitions and the <state,step> map
- this->StepToForwardAndBackwardStepMap.insert(step, new forwardAndBackwardSteps);
- this->StateToStepMap[step->processingState()] = step;
- this->StateToStepMap[step->validationState()] = step;
- // Setup the signal/slot that triggers the attempt to go to the next step
- QObject::connect(step->validationState(), SIGNAL(entered()),
- q, SLOT(attemptToGoToNextStep()));
- // Setup the signal/slot that triggers the evaluation of the validation results
- // after validate(const QString&) is called
- this->connect(
- step->ctkWorkflowStepQObject(), SIGNAL(validationComplete(bool,QString)),
- q, SLOT(evaluateValidationResults(bool,QString)));
- this->connect(
- step->ctkWorkflowStepQObject(), SIGNAL(onEntryComplete()),
- SLOT(processingAfterOnEntry()));
- this->connect(
- step->ctkWorkflowStepQObject(), SIGNAL(onExitComplete()),
- SLOT(processingAfterOnExit()));
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflowPrivate::hasDuplicateTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
- const ctkWorkflow::TransitionDirectionality directionality)
- {
- Q_Q(ctkWorkflow);
- Q_ASSERT(origin);
- Q_ASSERT(destination);
- Q_ASSERT(directionality == ctkWorkflow::Forward || ctkWorkflow::Backward);
- ctkWorkflowPrivate::StepListType stepList;
- ctkWorkflowStep* targetStep = 0;
- if (directionality == ctkWorkflow::Forward)
- {
- stepList = q->forwardSteps(origin);
- targetStep = destination;
- }
- else if (directionality == ctkWorkflow::Backward)
- {
- stepList = q->backwardSteps(destination);
- targetStep = origin;
- }
- foreach(ctkWorkflowStep * step, stepList)
- {
- if (step == targetStep)
- {
- return true;
- }
- }
- return false;
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflowPrivate::hasTransitionWithSameBranchId(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
- const QString& branchId,
- const ctkWorkflow::TransitionDirectionality directionality)
- {
- Q_ASSERT(origin);
- Q_ASSERT(destination);
- Q_ASSERT(directionality == ctkWorkflow::Forward || ctkWorkflow::Backward);
- Q_ASSERT(!branchId.isEmpty());
- QList<QString> branchIdList;
- if (directionality == ctkWorkflow::Forward)
- {
- branchIdList = this->forwardBranchIds(origin);
- }
- else if (directionality == ctkWorkflow::Backward)
- {
- branchIdList = this->backwardBranchIds(destination);
- }
- foreach(QString id, branchIdList)
- {
- if (QString::compare(id, branchId, Qt::CaseInsensitive) == 0)
- {
- return true;
- }
- }
- return false;
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::createTransitionToNextStep(ctkWorkflowStep* origin,
- ctkWorkflowStep* destination,
- const QString& branchId)
- {
- Q_Q(ctkWorkflow);
- Q_ASSERT(origin);
- Q_ASSERT(destination);
- Q_ASSERT(!q->hasTransition(origin, destination, branchId, ctkWorkflow::Forward));
- QString id;
- // create an artificial branchId if one is not given
- if (branchId.isEmpty())
- {
- id.setNum(this->numberOfForwardSteps(origin));
- id.prepend(this->ARTIFICIAL_BRANCH_ID_PREFIX);
- }
- else
- {
- id = branchId;
- }
- // Create the transition
- ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToNextStep, id);
- transition->setTargetState(destination->processingState());
- origin->validationState()->addTransition(transition);
- // Update the step to transitions map
- this->StepToForwardAndBackwardStepMap.value(origin)->appendForwardStep(destination, id);
- // Setup the signal/slot that shows and hides the steps' user interfaces
- // on transition to the next step
- QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::createTransitionToPreviousStep(ctkWorkflowStep* origin,
- ctkWorkflowStep* destination,
- const QString& branchId)
- {
- Q_Q(ctkWorkflow);
- Q_ASSERT(origin);
- Q_ASSERT(destination);
- Q_ASSERT(!q->hasTransition(origin, destination, branchId, ctkWorkflow::Backward));
- QString id;
- // create an artificial branchId if one is not given
- if (branchId.isEmpty())
- {
- id.setNum(this->numberOfBackwardSteps(destination));
- id.prepend(this->ARTIFICIAL_BRANCH_ID_PREFIX);
- }
- else
- {
- id = branchId;
- }
- ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToPreviousStep, id);
- transition->setTargetState(origin->processingState());
- destination->processingState()->addTransition(transition);
- // Update the step to transitions map
- this->StepToForwardAndBackwardStepMap.value(destination)->appendBackwardStep(origin, id);
- // Setup the signal/slot that shows and hides the steps' user
- // interfaces on transition to the previous step
- QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::createTransitionToPreviousStartingStep(ctkWorkflowStep* startingStep,
- ctkWorkflowStep* currentStep)
- {
- Q_Q(ctkWorkflow);
- Q_ASSERT(startingStep);
- Q_ASSERT(currentStep);
- if (!this->TransitionToPreviousStartingStep)
- {
- ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(
- ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
- // Setup the signal/slot that shows and hides the steps' user interfaces
- // on transition to the previous step
- QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
- this->TransitionToPreviousStartingStep = transition;
- }
-
- QState* currentState;
- // looping on the finish step
- if (startingStep == currentStep)
- {
- currentState = currentStep->validationState();
- }
- else
- {
- currentState = currentStep->processingState();
- }
- this->TransitionToPreviousStartingStep->setTargetState(startingStep->processingState());
- currentState->addTransition(this->TransitionToPreviousStartingStep);
- }
- // --------------------------------------------------------------------------
- ctkWorkflowStep* ctkWorkflowPrivate::stepFromId(const QString& id)const
- {
- foreach(ctkWorkflowStep* step, this->StepToForwardAndBackwardStepMap.keys())
- {
- Q_ASSERT(step);
- if (QString::compare(step->id(), id, Qt::CaseInsensitive) == 0)
- {
- return step;
- }
- }
- return 0;
- }
- // --------------------------------------------------------------------------
- QList<QString> ctkWorkflowPrivate::forwardBranchIds(ctkWorkflowStep* step)const
- {
- Q_ASSERT(step);
- return this->StepToForwardAndBackwardStepMap.value(step)->forwardBranchIds();
- }
- // --------------------------------------------------------------------------
- QList<QString> ctkWorkflowPrivate::backwardBranchIds(ctkWorkflowStep* step)const
- {
- Q_ASSERT(step);
- return this->StepToForwardAndBackwardStepMap.value(step)->backwardBranchIds();
- }
- //---------------------------------------------------------------------------
- void ctkWorkflowPrivate::validateInternal(ctkWorkflowStep* step)
- {
- Q_ASSERT(step);
- logger.debug(QString("validateInternal - validating input from %1").arg(step->name()));
- if (step->hasValidateCommand())
- {
- step->invokeValidateCommand(this->DesiredBranchId);
- }
- else
- {
- step->validate(this->DesiredBranchId);
- }
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::onEntryInternal(
- ctkWorkflowStep* step,
- ctkWorkflowStep* comingFrom,
- const ctkWorkflowInterstepTransition::InterstepTransitionType& transitionType)
- {
- Q_ASSERT(step);
- logger.debug(QString("onEntryInternal - entering input from %1").arg(step->name()));
- //Ensure we are transitioning between steps or starting the workflow
- Q_ASSERT(transitionType == ctkWorkflowInterstepTransition::TransitionToNextStep
- || transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep
- || transitionType == ctkWorkflowInterstepTransition::StartingWorkflow
- || transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
- if (step->hasOnEntryCommand())
- {
- step->invokeOnEntryCommand(comingFrom, transitionType);
- }
- else
- {
- step->onEntry(comingFrom, transitionType);
- }
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::processingAfterOnEntry()
- {
- Q_Q(ctkWorkflow);
- logger.debug("processingAfterOnEntry");
- if (!this->DestinationStep)
- {
- logger.error("processingAfterOnEntry - Called processingAfterOnEntry without "
- "having set a destination step");
- return;
- }
- // Update the currentStep and previous step
- this->CurrentStep = this->DestinationStep;
- // Reset the pointers used internally for performing a transition
- this->OriginStep = 0;
- this->DestinationStep = 0;
- // // Reset the pointers used internally for performing a transition
- // // back to the starting step
- // if (d->TransitionToPreviousStartingStep)
- // {
- // std::cout << "TRANSITION TO PREVIOUS STARTING STEP EXISTS" << std::endl;
- // //d->TransitionToPreviousStartingStep->sourceState()->removeTransition(d->TransitionToPreviousStartingStep);
- // //std::cout << "removed" << std::endl;
- // // d->TransitionToPreviousStartingStep = 0;
- // //destination->processingState()->removeTransition(d->TransitionToPreviousStartingStep);
- // //delete d->TransitionToPreviousStartingStep;
- // // d->TransitionToPreviousStartingStep = 0;
- // std::cout << "here" << std::endl;
- // }
- // If we are trying to get to the finish step, then check if we are
- // finished.
- if (this->GoToStep)
- {
- if (this->CurrentStep == this->GoToStep)
- {
- q->goToStepSucceeded();
- }
- // if we're not finished, continue transitioning to the next step
- else
- {
- q->goForward();
- }
- }
- else
- {
- emit q->currentStepChanged(this->CurrentStep);
- }
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::onExitInternal(
- ctkWorkflowStep* step,
- ctkWorkflowStep* goingTo,
- const ctkWorkflowInterstepTransition::InterstepTransitionType& transitionType)
- {
- Q_ASSERT(step);
- logger.debug(QString("onExitInternal - exiting %1").arg(step->name()));
- // Ensure we are transitioning between steps or starting the workflow
- Q_ASSERT (transitionType == ctkWorkflowInterstepTransition::TransitionToNextStep ||
- transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep ||
- transitionType == ctkWorkflowInterstepTransition::StoppingWorkflow ||
- transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
- if (step->hasOnExitCommand())
- {
- step->invokeOnExitCommand(goingTo, transitionType);
- }
- else
- {
- step->onExit(goingTo, transitionType);
- }
- }
- // --------------------------------------------------------------------------
- void ctkWorkflowPrivate::processingAfterOnExit()
- {
- // enter the destination step if we have one
- if (this->DestinationStep)
- {
- this->onEntryInternal(this->DestinationStep, this->OriginStep, this->TransitionType);
- }
- // reset the pointers used internally for performing a transition if we're done
- else
- {
- this->OriginStep = 0;
- this->DestinationStep = 0;
- // we've exited the CurrentStep and haven't gone into another step, so we no longer have a
- // currentStep.
- this->CurrentStep = 0;
- }
- }
- // --------------------------------------------------------------------------
- ctkWorkflowStep* ctkWorkflowPrivate::stepFromState(const QAbstractState* state)
- {
- if (state)
- {
- return this->StateToStepMap.value(state);
- }
- return 0;
- }
- // --------------------------------------------------------------------------
- int ctkWorkflowPrivate::numberOfForwardSteps(ctkWorkflowStep* step)
- {
- Q_Q(ctkWorkflow);
- return q->forwardSteps(step).length();
- }
- // --------------------------------------------------------------------------
- int ctkWorkflowPrivate::numberOfBackwardSteps(ctkWorkflowStep* step)
- {
- Q_Q(ctkWorkflow);
- return q->backwardSteps(step).length();
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflowPrivate::pathExists(const QString& goalId, ctkWorkflowStep* origin)const
- {
- Q_Q(const ctkWorkflow);
- Q_ASSERT(!goalId.isEmpty());
- Q_ASSERT(this->CurrentStep);
- QString originId;
- if (origin)
- {
- originId = origin->id();
- }
- else
- {
- originId = this->CurrentStep->id();
- }
- // there exists a path from the origin to the goal if:
- // - there is a goal AND
- // - either:
- // - the origin is already the goal
- // - there is a path from at least one of the origin's successors to the goal
- return (q->hasStep(goalId)
- && ((QString::compare(goalId, originId, Qt::CaseInsensitive) == 0)
- || (q->canGoForward(origin)))); // <-- TODO insert logic here for looking at graph
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflowPrivate::pathExistsFromNextStep(const QString& goalId, const QString& branchId)const
- {
- Q_ASSERT(!goalId.isEmpty());
- Q_ASSERT(!branchId.isEmpty());
- Q_ASSERT(this->CurrentStep);
- // return whether there exists a path from the the step that will be followed (given the branchId) to the goal
- ctkWorkflowStep* nextStep = this->StepToForwardAndBackwardStepMap.value(this->CurrentStep)->forwardStep(branchId);
- if (!nextStep)
- {
- return false;
- }
- else
- {
- return this->pathExists(goalId, nextStep);
- }
- }
- // --------------------------------------------------------------------------
- // ctkWorkflow methods
- // --------------------------------------------------------------------------
- ctkWorkflow::ctkWorkflow(QObject* _parent) : Superclass(_parent)
- , d_ptr(new ctkWorkflowPrivate(*this))
- {
- Q_D(ctkWorkflow);
- d->StateMachine = new QStateMachine(this);
- }
- // --------------------------------------------------------------------------
- ctkWorkflow::~ctkWorkflow()
- {
- Q_D(ctkWorkflow);
- if (d->StateMachine->isRunning())
- {
- d->StateMachine->stop();
- }
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::addTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
- const QString& branchId,
- const ctkWorkflow::TransitionDirectionality directionality)
- {
- Q_D(ctkWorkflow);
- if (d->StateMachine->isRunning())
- {
- logger.warn("addTransition - Cannot add a transition while the workflow is started !");
- return false;
- }
- // Set origin id if empty
- if (origin && origin->id().isEmpty())
- {
- origin->setId(QString("step%1").arg(d->StepToForwardAndBackwardStepMap.count()));
- }
- // cannot currently create a transition between two steps of the same id, which is equivalent to
- // adding a transition from a step to itself
- if (origin && destination && (QString::compare(origin->id(), destination->id(), Qt::CaseInsensitive) == 0))
- {
- logger.error("addTransition - Workflow does not currently support a transition"
- " from a step to itself. Use GoToStep instead !");
- return false;
- }
- // add the origin step if it doesn't exist in the workflow yet
- if (origin && !this->hasStep(origin->id()))
- {
- d->addStep(origin);
- }
- // Set destination id if empty
- if (destination && destination->id().isEmpty())
- {
- destination->setId(QString("step%1").arg(d->StepToForwardAndBackwardStepMap.count()));
- }
- // add the destination step if it doesn't exist in the workflow yet
- if (destination && !this->hasStep(destination->id()))
- {
- d->addStep(destination);
- }
- if (origin && destination)
- {
- // ensure we haven't already added a transition with the same origin, destination and directionality
- if (this->hasTransition(origin, destination, branchId, directionality))
- {
- logger.warn("addTransition - Cannot create a transition that matches a "
- "previously created transtiion");
- return false;
- }
- // create the forward transition
- if (directionality == ctkWorkflow::Forward
- || directionality == ctkWorkflow::Bidirectional)
- {
- d->createTransitionToNextStep(origin, destination, branchId);
- }
- // create the backward transition
- if (directionality == ctkWorkflow::Backward
- || directionality == ctkWorkflow::Bidirectional)
- {
- d->createTransitionToPreviousStep(origin, destination, branchId);
- }
- }
- // Set initialStep if needed
- if (origin && d->StepToForwardAndBackwardStepMap.count() == 2 && !this->initialStep())
- {
- this->setInitialStep(origin);
- }
- return true;
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::hasTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
- const QString& branchId,
- const ctkWorkflow::TransitionDirectionality directionality)
- {
- Q_D(ctkWorkflow);
- // we have a bidirectional transition if we have both a forward and a backward transition
- if (directionality == ctkWorkflow::Bidirectional)
- {
- return this->hasTransition(origin, destination, branchId, ctkWorkflow::Forward)
- && this->hasTransition(origin, destination, branchId, ctkWorkflow::Backward);
- }
- else
- {
- if (branchId.isEmpty())
- {
- return d->hasDuplicateTransition(origin, destination, directionality);
- }
- else
- {
- return d->hasDuplicateTransition(origin, destination, directionality)
- || d->hasTransitionWithSameBranchId(origin, destination, branchId, directionality);
- }
- }
- }
- // --------------------------------------------------------------------------
- QList<ctkWorkflowStep*> ctkWorkflow::forwardSteps(ctkWorkflowStep* step)const
- {
- Q_D(const ctkWorkflow);
- // use the given step if provided, otherwise use the workflow's current step
- if (step && d->StepToForwardAndBackwardStepMap.contains(step))
- {
- return d->StepToForwardAndBackwardStepMap.value(step)->forwardSteps();
- }
- else if (d->CurrentStep && d->StepToForwardAndBackwardStepMap.contains(d->CurrentStep))
- {
- return d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->forwardSteps();
- }
- else
- {
- return QList<ctkWorkflowStep*>();
- }
- }
- // --------------------------------------------------------------------------
- QList<ctkWorkflowStep*> ctkWorkflow::backwardSteps(ctkWorkflowStep* step)const
- {
- Q_D(const ctkWorkflow);
- // use the current step if provided, otherwise use the workflow's current step
- if (step && d->StepToForwardAndBackwardStepMap.contains(step))
- {
- return d->StepToForwardAndBackwardStepMap.value(step)->backwardSteps();
- }
- else if (d->CurrentStep && d->StepToForwardAndBackwardStepMap.contains(d->CurrentStep))
- {
- return d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->backwardSteps();
- }
- else
- {
- return QList<ctkWorkflowStep*>();
- }
- }
- // --------------------------------------------------------------------------
- QList<ctkWorkflowStep*> ctkWorkflow::finishSteps()const
- {
- Q_D(const ctkWorkflow);
- // iterate through our list of steps, and keep the steps that don't have anything following them
- QList<ctkWorkflowStep*> finishSteps;
- foreach (ctkWorkflowStep* step, d->StepToForwardAndBackwardStepMap.keys())
- {
- if (!this->canGoForward(step))
- {
- finishSteps.append(step);
- }
- }
- return finishSteps;
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::canGoForward(ctkWorkflowStep* step)const
- {
- return (!this->forwardSteps(step).isEmpty());
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::canGoBackward(ctkWorkflowStep* step)const
- {
- return (!this->backwardSteps(step).isEmpty());
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::canGoToStep(const QString& targetId, ctkWorkflowStep* step)const
- {
- Q_D(const ctkWorkflow);
- return d->pathExists(targetId, step);
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::hasStep(const QString& id)const
- {
- Q_D(const ctkWorkflow);
- return d->stepFromId(id);
- }
- // --------------------------------------------------------------------------
- // Convenience method to set the QStateMachine's initialState to a
- // specific step's processing state.
- CTK_GET_CPP(ctkWorkflow, ctkWorkflowStep*, initialStep, InitialStep);
- CTK_SET_CPP(ctkWorkflow, ctkWorkflowStep*, setInitialStep, InitialStep);
- // --------------------------------------------------------------------------
- CTK_GET_CPP(ctkWorkflow, ctkWorkflowStep*, currentStep, CurrentStep);
- // --------------------------------------------------------------------------
- void ctkWorkflow::start()
- {
- Q_D(ctkWorkflow);
- if (!d->InitialStep)
- {
- logger.warn("start - Cannot start workflow without an initial step");
- return;
- }
-
- // Setup to do the entry processing for the initial setp
- d->StateMachine->setInitialState(d->InitialStep->processingState());
- d->OriginStep = 0;
- d->DestinationStep = d->InitialStep;
- d->TransitionType = ctkWorkflowInterstepTransition::StartingWorkflow;
- d->onEntryInternal(d->DestinationStep, d->OriginStep, d->TransitionType);
- d->StateMachine->start();
- }
- // --------------------------------------------------------------------------
- bool ctkWorkflow::isRunning()const
- {
- Q_D(const ctkWorkflow);
- return d->StateMachine->isRunning();
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::stop()
- {
- Q_D(ctkWorkflow);
- if (!d->StateMachine->isRunning())
- {
- return;
- }
- // Setup to do the exit processing for the current step
- if (d->CurrentStep)
- {
- d->OriginStep = d->CurrentStep;
- d->DestinationStep = 0;
- d->TransitionType = ctkWorkflowInterstepTransition::StoppingWorkflow;
- d->onExitInternal(d->OriginStep, d->DestinationStep, d->TransitionType);
- }
- d->StateMachine->stop();
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goForward(const QString& desiredBranchId)
- {
- Q_D(ctkWorkflow);
- if (!this->isRunning())
- {
- logger.warn("goForward - The workflow is not running !");
- return;
- }
- // if we're just going to the next step and not to a 'goTo' step, then check to make sure that
- // there exists a step following the current step
- if (!d->GoToStep)
- {
- if (!this->canGoForward())
- {
- logger.warn("goForward - Attempt to goForward from a finish step !");
- return;
- }
- }
- d->DesiredBranchId = desiredBranchId;
- logger.info("goForward - posting ValidationTransition");
- d->StateMachine->postEvent(
- new ctkWorkflowIntrastepTransitionEvent(ctkWorkflowIntrastepTransition::ValidationTransition));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goBackward(const QString& desiredBranchId)
- {
- Q_D(ctkWorkflow);
- if (!this->isRunning())
- {
- logger.warn("goBackward - The workflow is not running !");
- return;
- }
- if (!this->canGoBackward())
- {
- logger.warn("goBackward - Attempt to goBackward from first step !");
- return;
- }
- ctkWorkflowStep* previousStep = d->StepToPreviousStepMap[d->CurrentStep];
- Q_ASSERT(previousStep);
- QString branchId = d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->backwardBranchId(previousStep);
- Q_ASSERT(!branchId.isEmpty());
- d->DesiredBranchId = desiredBranchId;
-
- logger.info("goBackward - posting TransitionToPreviousStep");
- d->StateMachine->postEvent(
- new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToPreviousStep, branchId));
- }
- // --------------------------------------------------------------------------
- CTK_GET_CPP(ctkWorkflow, bool, goBackToOriginStepUponSuccess, GoBackToOriginStepUponSuccess);
- CTK_SET_CPP(ctkWorkflow, bool, setGoBackToOriginStepUponSuccess, GoBackToOriginStepUponSuccess);
- // --------------------------------------------------------------------------
- void ctkWorkflow::goToStep(const QString& targetId)
- {
- Q_D(ctkWorkflow);
- if (!this->isRunning())
- {
- logger.warn("goToStep - The workflow is not running !");
- return;
- }
- // TODO currently returns true only if the workflow is running - need logic here
- if (!this->canGoToStep(targetId))
- {
- logger.warn(QString("goToStep - Cannot goToStep %1 ").arg(targetId));
- return;
- }
- #ifndef QT_NO_DEBUG
- ctkWorkflowStep* step = d->stepFromId(targetId);
- Q_ASSERT(step);
- #endif
- logger.info(QString("goToStep - Attempting to go to finish step %1").arg(targetId));
- // if (step == d->CurrentStep)
- // {
- // qDebug() << "we are already in the desired finish step";
- // return;
- // }
- d->GoToStep = d->stepFromId(targetId);
- d->StartingStep = d->CurrentStep;
- this->goForward();
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::attemptToGoToNextStep()
- {
- logger.info("attemptToGoToNextStep - Attempting to go to the next step ");
-
- Q_D(ctkWorkflow);
- Q_ASSERT(d->CurrentStep);
- //Q_ASSERT(this->canGoForward(d->CurrentStep));
- d->validateInternal(d->CurrentStep);
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::evaluateValidationResults(bool validationSucceeded, const QString& branchId)
- {
- if (validationSucceeded)
- {
- this->goToNextStepAfterSuccessfulValidation(branchId);
- }
- else
- {
- this->goToProcessingStateAfterValidationFailed();
- }
- }
- // --------------------------------------------------------------------------
- // if ctkWorkflowStep::validationComplete() did not provide a branchId, then:
- // - if there is one step following the current step, we will follow that transition
- // - if there are multiple steps following the current step, then we will follow the first
- // transition that was added
- // (either way this corresponds to following the first forwardBranchId we've recorded)
- // if ctkWorkflowStep::validationComplete() provided a branchId, then:
- // - if there is one transition following the current step that was not created using a branchId,
- // then we will follow it
- // - otherwise do a conditional branching based on the branchId provided by validationComplete()
- void ctkWorkflow::goToNextStepAfterSuccessfulValidation(const QString& branchId)
- {
- Q_D(ctkWorkflow);
- logger.debug("goToNextStepAfterSuccessfulValidation - Calidation succeeded");
- logger.info("goToNextStepAfterSuccessfulValidation - Posting TransitionToNextStep");
- // we may already be in the 'goTo' step - i.e. looping on a finish step
- if (d->GoToStep && d->CurrentStep == d->GoToStep)
- {
- this->goToStepSucceeded();
- return;
- }
- QString transitionBranchId;
- // these values are helpful for the logic below
- QString firstForwardBranchId = d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->firstForwardBranchId();
- int numberOfForwardSteps = d->numberOfForwardSteps(d->CurrentStep);
- Q_ASSERT(!firstForwardBranchId.isEmpty());
- Q_ASSERT(numberOfForwardSteps);
- // validationComplete() does not give us a branchId
- if (branchId.isEmpty())
- {
- transitionBranchId = firstForwardBranchId;
- if (numberOfForwardSteps > 1)
- {
- logger.warn("goToNextStepAfterSuccessfulValidation - ctkWorkflowStep::ValidatComplete() "
- "did not provide branchId at a branch in the workflow - will follow first "
- "transition that was created");
- }
- }
- // validationComplete() gives us a branchId
- else
- {
- if (numberOfForwardSteps == 1 && firstForwardBranchId.contains(d->ARTIFICIAL_BRANCH_ID_PREFIX))
- {
- transitionBranchId = firstForwardBranchId;
- logger.warn("goToNextStepAfterSuccessfulValidation - ctkWorkflowStep::ValidationComplete()"
- " returns a branchId, but was overridden by the workflow");
- }
- else
- {
- transitionBranchId = branchId;
- }
- }
- // if we are trying to go to a 'goTo' step, check that the selected branch will still take us along a path that leads to the 'goTo' step, and fail if not
- if (d->GoToStep && !d->pathExistsFromNextStep(d->GoToStep->id(), transitionBranchId))
- {
- this->goToProcessingStateAfterValidationFailed();
- return;
- }
- d->StateMachine->postEvent(new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToNextStep, transitionBranchId));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goToProcessingStateAfterValidationFailed()
- {
- logger.debug("goToNextStepAfterSuccessfulValidation - Validation failed");
- Q_D(ctkWorkflow);
- // Validation failed in the process of attempting to go to the finish step
- if (d->GoToStep)
- {
- this->goToStepFailed();
- }
- logger.info("goToNextStepAfterSuccessfulValidation - Posting ValidationFailedTransition");
- d->StateMachine->postEvent(new ctkWorkflowIntrastepTransitionEvent(ctkWorkflowIntrastepTransition::ValidationFailedTransition));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::performTransitionBetweenSteps()
- {
- Q_D(ctkWorkflow);
- logger.debug("performTransitionBetweenSteps - Performing transition between steps");
- // Alternative: could find the origin and destination step based on
- // d->CurrentStep rather than QObject::sender(), but would require
- // keeping track of an origin step's destination step (and would be
- // tricky in an extension to branching workflows, unless we change
- // this method signature)
- ctkWorkflowInterstepTransition* transition = qobject_cast<ctkWorkflowInterstepTransition*>(QObject::sender());
- Q_ASSERT(transition);
-
- d->OriginStep = d->stepFromState(transition->sourceState());
- d->DestinationStep = d->stepFromState(transition->targetState());
- d->TransitionType = transition->transitionType();
- Q_ASSERT(d->TransitionType == ctkWorkflowInterstepTransition::TransitionToNextStep
- || d->TransitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep
- || d->TransitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
- // update the map from the step to the previous step if we are going forward
- if (d->TransitionType == ctkWorkflowInterstepTransition::TransitionToNextStep)
- {
- d->StepToPreviousStepMap.insert(d->DestinationStep, d->OriginStep);
- }
-
- // exit the destination step
- d->onExitInternal(d->OriginStep, d->DestinationStep, d->TransitionType);
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goToStepSucceeded()
- {
- Q_D(ctkWorkflow);
- logger.debug("goToStepSucceeded");
- // after success, go back to the step at which we begin looking for
- // the finish step (will exit the current step and enter the starting step)
- // only if the property goBackToOriginStepUponSuccess is true.
- if (this->goBackToOriginStepUponSuccess())
- {
- d->createTransitionToPreviousStartingStep(d->StartingStep, d->CurrentStep);
- }
- d->GoToStep = 0;
- d->StartingStep->setStatusText("Attempt to go to the finish step succeeded");
- d->StartingStep = 0;
- if (this->goBackToOriginStepUponSuccess())
- {
- this->goFromGoToStepToStartingStep();
- }
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goFromGoToStepToStartingStep()
- {
- Q_D(ctkWorkflow);
- logger.info("goFromGoToStepToStartingStep - Posting TransitionToPreviousStartingStep");
- d->StateMachine->postEvent(new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep));
- }
- // --------------------------------------------------------------------------
- void ctkWorkflow::goToStepFailed()
- {
- // Abort attempt to get to the finish step
- Q_D(ctkWorkflow);
-
- d->GoToStep = 0;
- d->StartingStep = 0;
- // We don't need to transition between steps - leave the user at the
- // point of failure, so that they can try to continue manually
- // Emit the signal that we have changed the current step, since it wasn't emitted in the process
- // of going to the 'goTo' step.
- emit this->currentStepChanged(d->CurrentStep);
- // if (failedOnBranch)
- // {
- // this->goToProcessingStateAfterValidationFailed();
- // }
- }
|