ctkWorkflow.cpp 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081
  1. /*=========================================================================
  2. Library: CTK
  3. Copyright (c) Kitware Inc.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.commontk.org/LICENSE
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =========================================================================*/
  14. // Qt includes
  15. #include <QStateMachine>
  16. #include <QState>
  17. // CTK includes
  18. #include "ctkWorkflow.h"
  19. #include "ctkWorkflowStep.h"
  20. #include "ctkWorkflowStep_p.h"
  21. #include "ctkWorkflowTransitions.h"
  22. #include "ctkLogger.h"
  23. // STD includes
  24. #include <iostream>
  25. //--------------------------------------------------------------------------
  26. static ctkLogger logger("org.commontk.libs.core.ctkWorkflow");
  27. //--------------------------------------------------------------------------
  28. // --------------------------------------------------------------------------
  29. // ctkWorkflowPrivate methods
  30. // --------------------------------------------------------------------------
  31. ctkWorkflowPrivate::ctkWorkflowPrivate(ctkWorkflow& object)
  32. :q_ptr(&object)
  33. {
  34. this->InitialStep = 0;
  35. this->CurrentStep = 0;
  36. this->OriginStep = 0;
  37. this->DestinationStep = 0;
  38. this->GoToStep = 0;
  39. this->StartingStep = 0;
  40. this->TransitionToPreviousStartingStep = 0;
  41. // By default, go back to the origin step upon success of the goToStep(targetId) attempt.
  42. this->GoBackToOriginStepUponSuccess = true;
  43. this->ARTIFICIAL_BRANCH_ID_PREFIX = "ctkWorkflowArtificialBranchId_";
  44. }
  45. // --------------------------------------------------------------------------
  46. ctkWorkflowPrivate::~ctkWorkflowPrivate()
  47. {
  48. }
  49. // --------------------------------------------------------------------------
  50. void ctkWorkflowPrivate::addStep(ctkWorkflowStep* step)
  51. {
  52. Q_Q(ctkWorkflow);
  53. Q_ASSERT(step);
  54. Q_ASSERT(!q->hasStep(step->id()));
  55. Q_ASSERT(!this->StateMachine->isRunning());
  56. // Add the states, creating them if necessary
  57. this->StateMachine->addState(step->processingState());
  58. this->StateMachine->addState(step->validationState());
  59. // Update the map of steps to transitions and the <state,step> map
  60. this->StepToForwardAndBackwardStepMap.insert(step, new forwardAndBackwardSteps);
  61. this->StateToStepMap[step->processingState()] = step;
  62. this->StateToStepMap[step->validationState()] = step;
  63. // Setup the signal/slot that triggers the attempt to go to the next step
  64. QObject::connect(step->validationState(), SIGNAL(entered()),
  65. q, SLOT(attemptToGoToNextStep()));
  66. // Setup the signal/slot that triggers the evaluation of the validation results
  67. // after validate(const QString&) is called
  68. this->connect(
  69. step->ctkWorkflowStepQObject(), SIGNAL(validationComplete(bool, const QString&)),
  70. q, SLOT(evaluateValidationResults(bool, const QString&)));
  71. this->connect(
  72. step->ctkWorkflowStepQObject(), SIGNAL(onEntryComplete()),
  73. SLOT(processingAfterOnEntry()));
  74. this->connect(
  75. step->ctkWorkflowStepQObject(), SIGNAL(onExitComplete()),
  76. SLOT(processingAfterOnExit()));
  77. }
  78. // --------------------------------------------------------------------------
  79. bool ctkWorkflowPrivate::hasDuplicateTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
  80. const ctkWorkflow::TransitionDirectionality directionality)
  81. {
  82. Q_Q(ctkWorkflow);
  83. Q_ASSERT(origin);
  84. Q_ASSERT(destination);
  85. Q_ASSERT(directionality == ctkWorkflow::Forward || ctkWorkflow::Backward);
  86. ctkWorkflowPrivate::StepListType stepList;
  87. ctkWorkflowStep* targetStep = 0;
  88. if (directionality == ctkWorkflow::Forward)
  89. {
  90. stepList = q->forwardSteps(origin);
  91. targetStep = destination;
  92. }
  93. else if (directionality == ctkWorkflow::Backward)
  94. {
  95. stepList = q->backwardSteps(destination);
  96. targetStep = origin;
  97. }
  98. foreach(ctkWorkflowStep * step, stepList)
  99. {
  100. if (step == targetStep)
  101. {
  102. return true;
  103. }
  104. }
  105. return false;
  106. }
  107. // --------------------------------------------------------------------------
  108. bool ctkWorkflowPrivate::hasTransitionWithSameBranchId(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
  109. const QString& branchId,
  110. const ctkWorkflow::TransitionDirectionality directionality)
  111. {
  112. Q_ASSERT(origin);
  113. Q_ASSERT(destination);
  114. Q_ASSERT(directionality == ctkWorkflow::Forward || ctkWorkflow::Backward);
  115. Q_ASSERT(!branchId.isEmpty());
  116. QList<QString> branchIdList;
  117. if (directionality == ctkWorkflow::Forward)
  118. {
  119. branchIdList = this->forwardBranchIds(origin);
  120. }
  121. else if (directionality == ctkWorkflow::Backward)
  122. {
  123. branchIdList = this->backwardBranchIds(destination);
  124. }
  125. foreach(QString id, branchIdList)
  126. {
  127. if (QString::compare(id, branchId, Qt::CaseInsensitive) == 0)
  128. {
  129. return true;
  130. }
  131. }
  132. return false;
  133. }
  134. // --------------------------------------------------------------------------
  135. void ctkWorkflowPrivate::createTransitionToNextStep(ctkWorkflowStep* origin,
  136. ctkWorkflowStep* destination,
  137. const QString& branchId)
  138. {
  139. Q_Q(ctkWorkflow);
  140. Q_ASSERT(origin);
  141. Q_ASSERT(destination);
  142. Q_ASSERT(!q->hasTransition(origin, destination, branchId, ctkWorkflow::Forward));
  143. QString id;
  144. // create an artificial branchId if one is not given
  145. if (branchId.isEmpty())
  146. {
  147. id.setNum(this->numberOfForwardSteps(origin));
  148. id.prepend(this->ARTIFICIAL_BRANCH_ID_PREFIX);
  149. }
  150. else
  151. {
  152. id = branchId;
  153. }
  154. // Create the transition
  155. ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToNextStep, id);
  156. transition->setTargetState(destination->processingState());
  157. origin->validationState()->addTransition(transition);
  158. // Update the step to transitions map
  159. this->StepToForwardAndBackwardStepMap.value(origin)->appendForwardStep(destination, id);
  160. // Setup the signal/slot that shows and hides the steps' user interfaces
  161. // on transition to the next step
  162. QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
  163. }
  164. // --------------------------------------------------------------------------
  165. void ctkWorkflowPrivate::createTransitionToPreviousStep(ctkWorkflowStep* origin,
  166. ctkWorkflowStep* destination,
  167. const QString& branchId)
  168. {
  169. Q_Q(ctkWorkflow);
  170. Q_ASSERT(origin);
  171. Q_ASSERT(destination);
  172. Q_ASSERT(!q->hasTransition(origin, destination, branchId, ctkWorkflow::Backward));
  173. QString id;
  174. // create an artificial branchId if one is not given
  175. if (branchId.isEmpty())
  176. {
  177. id.setNum(this->numberOfBackwardSteps(destination));
  178. id.prepend(this->ARTIFICIAL_BRANCH_ID_PREFIX);
  179. }
  180. else
  181. {
  182. id = branchId;
  183. }
  184. ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(ctkWorkflowInterstepTransition::TransitionToPreviousStep, id);
  185. transition->setTargetState(origin->processingState());
  186. destination->processingState()->addTransition(transition);
  187. // Update the step to transitions map
  188. this->StepToForwardAndBackwardStepMap.value(destination)->appendBackwardStep(origin, id);
  189. // Setup the signal/slot that shows and hides the steps' user
  190. // interfaces on transition to the previous step
  191. QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
  192. }
  193. // --------------------------------------------------------------------------
  194. void ctkWorkflowPrivate::createTransitionToPreviousStartingStep(ctkWorkflowStep* startingStep,
  195. ctkWorkflowStep* currentStep)
  196. {
  197. Q_Q(ctkWorkflow);
  198. Q_ASSERT(startingStep);
  199. Q_ASSERT(currentStep);
  200. if (!this->TransitionToPreviousStartingStep)
  201. {
  202. ctkWorkflowInterstepTransition* transition = new ctkWorkflowInterstepTransition(
  203. ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
  204. // Setup the signal/slot that shows and hides the steps' user interfaces
  205. // on transition to the previous step
  206. QObject::connect(transition, SIGNAL(triggered()), q, SLOT(performTransitionBetweenSteps()));
  207. this->TransitionToPreviousStartingStep = transition;
  208. }
  209. QState* currentState;
  210. // looping on the finish step
  211. if (startingStep == currentStep)
  212. {
  213. currentState = currentStep->validationState();
  214. }
  215. else
  216. {
  217. currentState = currentStep->processingState();
  218. }
  219. this->TransitionToPreviousStartingStep->setTargetState(startingStep->processingState());
  220. currentState->addTransition(this->TransitionToPreviousStartingStep);
  221. }
  222. // --------------------------------------------------------------------------
  223. ctkWorkflowStep* ctkWorkflowPrivate::stepFromId(const QString& id)const
  224. {
  225. foreach(ctkWorkflowStep* step, this->StepToForwardAndBackwardStepMap.keys())
  226. {
  227. Q_ASSERT(step);
  228. if (QString::compare(step->id(), id, Qt::CaseInsensitive) == 0)
  229. {
  230. return step;
  231. }
  232. }
  233. return 0;
  234. }
  235. // --------------------------------------------------------------------------
  236. QList<QString> ctkWorkflowPrivate::forwardBranchIds(ctkWorkflowStep* step)const
  237. {
  238. Q_ASSERT(step);
  239. return this->StepToForwardAndBackwardStepMap.value(step)->forwardBranchIds();
  240. }
  241. // --------------------------------------------------------------------------
  242. QList<QString> ctkWorkflowPrivate::backwardBranchIds(ctkWorkflowStep* step)const
  243. {
  244. Q_ASSERT(step);
  245. return this->StepToForwardAndBackwardStepMap.value(step)->backwardBranchIds();
  246. }
  247. //---------------------------------------------------------------------------
  248. void ctkWorkflowPrivate::validateInternal(ctkWorkflowStep* step)
  249. {
  250. Q_ASSERT(step);
  251. logger.debug(QString("validateInternal - validating input from %1").arg(step->name()));
  252. if (step->hasValidateCommand())
  253. {
  254. step->invokeValidateCommand(this->DesiredBranchId);
  255. }
  256. else
  257. {
  258. step->validate(this->DesiredBranchId);
  259. }
  260. }
  261. // --------------------------------------------------------------------------
  262. void ctkWorkflowPrivate::onEntryInternal(
  263. ctkWorkflowStep* step,
  264. ctkWorkflowStep* comingFrom,
  265. const ctkWorkflowInterstepTransition::InterstepTransitionType& transitionType)
  266. {
  267. Q_ASSERT(step);
  268. logger.debug(QString("onEntryInternal - entering input from %1").arg(step->name()));
  269. //Ensure we are transitioning between steps or starting the workflow
  270. Q_ASSERT(transitionType == ctkWorkflowInterstepTransition::TransitionToNextStep
  271. || transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep
  272. || transitionType == ctkWorkflowInterstepTransition::StartingWorkflow
  273. || transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
  274. if (step->hasOnEntryCommand())
  275. {
  276. step->invokeOnEntryCommand(comingFrom, transitionType);
  277. }
  278. else
  279. {
  280. step->onEntry(comingFrom, transitionType);
  281. }
  282. }
  283. // --------------------------------------------------------------------------
  284. void ctkWorkflowPrivate::processingAfterOnEntry()
  285. {
  286. Q_Q(ctkWorkflow);
  287. logger.debug("processingAfterOnEntry");
  288. if (!this->DestinationStep)
  289. {
  290. logger.error("processingAfterOnEntry - Called processingAfterOnEntry without "
  291. "having set a destination step");
  292. return;
  293. }
  294. // Update the currentStep and previous step
  295. this->CurrentStep = this->DestinationStep;
  296. // Reset the pointers used internally for performing a transition
  297. this->OriginStep = 0;
  298. this->DestinationStep = 0;
  299. // // Reset the pointers used internally for performing a transition
  300. // // back to the starting step
  301. // if (d->TransitionToPreviousStartingStep)
  302. // {
  303. // std::cout << "TRANSITION TO PREVIOUS STARTING STEP EXISTS" << std::endl;
  304. // //d->TransitionToPreviousStartingStep->sourceState()->removeTransition(d->TransitionToPreviousStartingStep);
  305. // //std::cout << "removed" << std::endl;
  306. // // d->TransitionToPreviousStartingStep = 0;
  307. // //destination->processingState()->removeTransition(d->TransitionToPreviousStartingStep);
  308. // //delete d->TransitionToPreviousStartingStep;
  309. // // d->TransitionToPreviousStartingStep = 0;
  310. // std::cout << "here" << std::endl;
  311. // }
  312. // If we are trying to get to the finish step, then check if we are
  313. // finished.
  314. if (this->GoToStep)
  315. {
  316. if (this->CurrentStep == this->GoToStep)
  317. {
  318. q->goToStepSucceeded();
  319. }
  320. // if we're not finished, continue transitioning to the next step
  321. else
  322. {
  323. q->goForward();
  324. }
  325. }
  326. else
  327. {
  328. emit q->currentStepChanged(this->CurrentStep);
  329. }
  330. }
  331. // --------------------------------------------------------------------------
  332. void ctkWorkflowPrivate::onExitInternal(
  333. ctkWorkflowStep* step,
  334. ctkWorkflowStep* goingTo,
  335. const ctkWorkflowInterstepTransition::InterstepTransitionType& transitionType)
  336. {
  337. Q_ASSERT(step);
  338. logger.debug(QString("onExitInternal - exiting %1").arg(step->name()));
  339. // Ensure we are transitioning between steps or starting the workflow
  340. Q_ASSERT (transitionType == ctkWorkflowInterstepTransition::TransitionToNextStep ||
  341. transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep ||
  342. transitionType == ctkWorkflowInterstepTransition::StoppingWorkflow ||
  343. transitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
  344. if (step->hasOnExitCommand())
  345. {
  346. step->invokeOnExitCommand(goingTo, transitionType);
  347. }
  348. else
  349. {
  350. step->onExit(goingTo, transitionType);
  351. }
  352. }
  353. // --------------------------------------------------------------------------
  354. void ctkWorkflowPrivate::processingAfterOnExit()
  355. {
  356. // enter the destination step if we have one
  357. if (this->DestinationStep)
  358. {
  359. this->onEntryInternal(this->DestinationStep, this->OriginStep, this->TransitionType);
  360. }
  361. // reset the pointers used internally for performing a transition if we're done
  362. else
  363. {
  364. this->OriginStep = 0;
  365. this->DestinationStep = 0;
  366. // we've exited the CurrentStep and haven't gone into another step, so we no longer have a
  367. // currentStep.
  368. this->CurrentStep = 0;
  369. }
  370. }
  371. // --------------------------------------------------------------------------
  372. ctkWorkflowStep* ctkWorkflowPrivate::stepFromState(const QAbstractState* state)
  373. {
  374. if (state)
  375. {
  376. return this->StateToStepMap.value(state);
  377. }
  378. return 0;
  379. }
  380. // --------------------------------------------------------------------------
  381. int ctkWorkflowPrivate::numberOfForwardSteps(ctkWorkflowStep* step)
  382. {
  383. Q_Q(ctkWorkflow);
  384. return q->forwardSteps(step).length();
  385. }
  386. // --------------------------------------------------------------------------
  387. int ctkWorkflowPrivate::numberOfBackwardSteps(ctkWorkflowStep* step)
  388. {
  389. Q_Q(ctkWorkflow);
  390. return q->backwardSteps(step).length();
  391. }
  392. // --------------------------------------------------------------------------
  393. bool ctkWorkflowPrivate::pathExists(const QString& goalId, ctkWorkflowStep* origin)const
  394. {
  395. Q_Q(const ctkWorkflow);
  396. Q_ASSERT(!goalId.isEmpty());
  397. Q_ASSERT(this->CurrentStep);
  398. QString originId;
  399. if (origin)
  400. {
  401. originId = origin->id();
  402. }
  403. else
  404. {
  405. originId = this->CurrentStep->id();
  406. }
  407. // there exists a path from the origin to the goal if:
  408. // - there is a goal AND
  409. // - either:
  410. // - the origin is already the goal
  411. // - there is a path from at least one of the origin's successors to the goal
  412. return (q->hasStep(goalId)
  413. && ((QString::compare(goalId, originId, Qt::CaseInsensitive) == 0)
  414. || (q->canGoForward(origin)))); // <-- TODO insert logic here for looking at graph
  415. }
  416. // --------------------------------------------------------------------------
  417. bool ctkWorkflowPrivate::pathExistsFromNextStep(const QString& goalId, const QString& branchId)const
  418. {
  419. Q_ASSERT(!goalId.isEmpty());
  420. Q_ASSERT(!branchId.isEmpty());
  421. Q_ASSERT(this->CurrentStep);
  422. // return whether there exists a path from the the step that will be followed (given the branchId) to the goal
  423. ctkWorkflowStep* nextStep = this->StepToForwardAndBackwardStepMap.value(this->CurrentStep)->forwardStep(branchId);
  424. if (!nextStep)
  425. {
  426. return false;
  427. }
  428. else
  429. {
  430. return this->pathExists(goalId, nextStep);
  431. }
  432. }
  433. // --------------------------------------------------------------------------
  434. // ctkWorkflow methods
  435. // --------------------------------------------------------------------------
  436. ctkWorkflow::ctkWorkflow(QObject* _parent) : Superclass(_parent)
  437. , d_ptr(new ctkWorkflowPrivate(*this))
  438. {
  439. Q_D(ctkWorkflow);
  440. d->StateMachine = new QStateMachine(this);
  441. }
  442. // --------------------------------------------------------------------------
  443. ctkWorkflow::~ctkWorkflow()
  444. {
  445. Q_D(ctkWorkflow);
  446. if (d->StateMachine->isRunning())
  447. {
  448. d->StateMachine->stop();
  449. }
  450. }
  451. // --------------------------------------------------------------------------
  452. bool ctkWorkflow::addTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
  453. const QString& branchId,
  454. const ctkWorkflow::TransitionDirectionality directionality)
  455. {
  456. Q_D(ctkWorkflow);
  457. if (d->StateMachine->isRunning())
  458. {
  459. logger.warn("addTransition - Cannot add a transition while the workflow is started !");
  460. return false;
  461. }
  462. // Set origin id if empty
  463. if (origin && origin->id().isEmpty())
  464. {
  465. origin->setId(QString("step%1").arg(d->StepToForwardAndBackwardStepMap.count()));
  466. }
  467. // cannot currently create a transition between two steps of the same id, which is equivalent to
  468. // adding a transition from a step to itself
  469. if (origin && destination && (QString::compare(origin->id(), destination->id(), Qt::CaseInsensitive) == 0))
  470. {
  471. logger.error("addTransition - Workflow does not currently support a transition"
  472. " from a step to itself. Use GoToStep instead !");
  473. return false;
  474. }
  475. // add the origin step if it doesn't exist in the workflow yet
  476. if (origin && !this->hasStep(origin->id()))
  477. {
  478. d->addStep(origin);
  479. }
  480. // Set destination id if empty
  481. if (destination && destination->id().isEmpty())
  482. {
  483. destination->setId(QString("step%1").arg(d->StepToForwardAndBackwardStepMap.count()));
  484. }
  485. // add the destination step if it doesn't exist in the workflow yet
  486. if (destination && !this->hasStep(destination->id()))
  487. {
  488. d->addStep(destination);
  489. }
  490. if (origin && destination)
  491. {
  492. // ensure we haven't already added a transition with the same origin, destination and directionality
  493. if (this->hasTransition(origin, destination, branchId, directionality))
  494. {
  495. logger.warn("addTransition - Cannot create a transition that matches a "
  496. "previously created transtiion");
  497. return false;
  498. }
  499. // create the forward transition
  500. if (directionality == ctkWorkflow::Forward
  501. || directionality == ctkWorkflow::Bidirectional)
  502. {
  503. d->createTransitionToNextStep(origin, destination, branchId);
  504. }
  505. // create the backward transition
  506. if (directionality == ctkWorkflow::Backward
  507. || directionality == ctkWorkflow::Bidirectional)
  508. {
  509. d->createTransitionToPreviousStep(origin, destination, branchId);
  510. }
  511. }
  512. // Set initialStep if needed
  513. if (origin && d->StepToForwardAndBackwardStepMap.count() == 2 && !this->initialStep())
  514. {
  515. this->setInitialStep(origin);
  516. }
  517. return true;
  518. }
  519. // --------------------------------------------------------------------------
  520. bool ctkWorkflow::hasTransition(ctkWorkflowStep* origin, ctkWorkflowStep* destination,
  521. const QString& branchId,
  522. const ctkWorkflow::TransitionDirectionality directionality)
  523. {
  524. Q_D(ctkWorkflow);
  525. // we have a bidirectional transition if we have both a forward and a backward transition
  526. if (directionality == ctkWorkflow::Bidirectional)
  527. {
  528. return this->hasTransition(origin, destination, branchId, ctkWorkflow::Forward)
  529. && this->hasTransition(origin, destination, branchId, ctkWorkflow::Backward);
  530. }
  531. else
  532. {
  533. if (branchId.isEmpty())
  534. {
  535. return d->hasDuplicateTransition(origin, destination, directionality);
  536. }
  537. else
  538. {
  539. return d->hasDuplicateTransition(origin, destination, directionality)
  540. || d->hasTransitionWithSameBranchId(origin, destination, branchId, directionality);
  541. }
  542. }
  543. }
  544. // --------------------------------------------------------------------------
  545. QList<ctkWorkflowStep*> ctkWorkflow::forwardSteps(ctkWorkflowStep* step)const
  546. {
  547. Q_D(const ctkWorkflow);
  548. // use the given step if provided, otherwise use the workflow's current step
  549. if (step && d->StepToForwardAndBackwardStepMap.contains(step))
  550. {
  551. return d->StepToForwardAndBackwardStepMap.value(step)->forwardSteps();
  552. }
  553. else if (d->CurrentStep && d->StepToForwardAndBackwardStepMap.contains(d->CurrentStep))
  554. {
  555. return d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->forwardSteps();
  556. }
  557. else
  558. {
  559. return QList<ctkWorkflowStep*>();
  560. }
  561. }
  562. // --------------------------------------------------------------------------
  563. QList<ctkWorkflowStep*> ctkWorkflow::backwardSteps(ctkWorkflowStep* step)const
  564. {
  565. Q_D(const ctkWorkflow);
  566. // use the current step if provided, otherwise use the workflow's current step
  567. if (step && d->StepToForwardAndBackwardStepMap.contains(step))
  568. {
  569. return d->StepToForwardAndBackwardStepMap.value(step)->backwardSteps();
  570. }
  571. else if (d->CurrentStep && d->StepToForwardAndBackwardStepMap.contains(d->CurrentStep))
  572. {
  573. return d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->backwardSteps();
  574. }
  575. else
  576. {
  577. return QList<ctkWorkflowStep*>();
  578. }
  579. }
  580. // --------------------------------------------------------------------------
  581. QList<ctkWorkflowStep*> ctkWorkflow::finishSteps()const
  582. {
  583. Q_D(const ctkWorkflow);
  584. // iterate through our list of steps, and keep the steps that don't have anything following them
  585. QList<ctkWorkflowStep*> finishSteps;
  586. foreach (ctkWorkflowStep* step, d->StepToForwardAndBackwardStepMap.keys())
  587. {
  588. if (!this->canGoForward(step))
  589. {
  590. finishSteps.append(step);
  591. }
  592. }
  593. return finishSteps;
  594. }
  595. // --------------------------------------------------------------------------
  596. bool ctkWorkflow::canGoForward(ctkWorkflowStep* step)const
  597. {
  598. return (!this->forwardSteps(step).isEmpty());
  599. }
  600. // --------------------------------------------------------------------------
  601. bool ctkWorkflow::canGoBackward(ctkWorkflowStep* step)const
  602. {
  603. return (!this->backwardSteps(step).isEmpty());
  604. }
  605. // --------------------------------------------------------------------------
  606. bool ctkWorkflow::canGoToStep(const QString& targetId, ctkWorkflowStep* step)const
  607. {
  608. Q_D(const ctkWorkflow);
  609. return d->pathExists(targetId, step);
  610. }
  611. // --------------------------------------------------------------------------
  612. bool ctkWorkflow::hasStep(const QString& id)const
  613. {
  614. Q_D(const ctkWorkflow);
  615. return d->stepFromId(id);
  616. }
  617. // --------------------------------------------------------------------------
  618. // Convenience method to set the QStateMachine's initialState to a
  619. // specific step's processing state.
  620. CTK_GET_CPP(ctkWorkflow, ctkWorkflowStep*, initialStep, InitialStep);
  621. CTK_SET_CPP(ctkWorkflow, ctkWorkflowStep*, setInitialStep, InitialStep);
  622. // --------------------------------------------------------------------------
  623. CTK_GET_CPP(ctkWorkflow, ctkWorkflowStep*, currentStep, CurrentStep);
  624. // --------------------------------------------------------------------------
  625. void ctkWorkflow::start()
  626. {
  627. Q_D(ctkWorkflow);
  628. if (!d->InitialStep)
  629. {
  630. logger.warn("start - Cannot start workflow without an initial step");
  631. return;
  632. }
  633. // Setup to do the entry processing for the initial setp
  634. d->StateMachine->setInitialState(d->InitialStep->processingState());
  635. d->OriginStep = 0;
  636. d->DestinationStep = d->InitialStep;
  637. d->TransitionType = ctkWorkflowInterstepTransition::StartingWorkflow;
  638. d->onEntryInternal(d->DestinationStep, d->OriginStep, d->TransitionType);
  639. d->StateMachine->start();
  640. }
  641. // --------------------------------------------------------------------------
  642. bool ctkWorkflow::isRunning()const
  643. {
  644. Q_D(const ctkWorkflow);
  645. return d->StateMachine->isRunning();
  646. }
  647. // --------------------------------------------------------------------------
  648. void ctkWorkflow::stop()
  649. {
  650. Q_D(ctkWorkflow);
  651. if (!d->StateMachine->isRunning())
  652. {
  653. return;
  654. }
  655. // Setup to do the exit processing for the current step
  656. if (d->CurrentStep)
  657. {
  658. d->OriginStep = d->CurrentStep;
  659. d->DestinationStep = 0;
  660. d->TransitionType = ctkWorkflowInterstepTransition::StoppingWorkflow;
  661. d->onExitInternal(d->OriginStep, d->DestinationStep, d->TransitionType);
  662. }
  663. d->StateMachine->stop();
  664. }
  665. // --------------------------------------------------------------------------
  666. void ctkWorkflow::goForward(const QString& desiredBranchId)
  667. {
  668. Q_D(ctkWorkflow);
  669. if (!this->isRunning())
  670. {
  671. logger.warn("goForward - The workflow is not running !");
  672. return;
  673. }
  674. // if we're just going to the next step and not to a 'goTo' step, then check to make sure that
  675. // there exists a step following the current step
  676. if (!d->GoToStep)
  677. {
  678. if (!this->canGoForward())
  679. {
  680. logger.warn("goForward - Attempt to goForward from a finish step !");
  681. return;
  682. }
  683. }
  684. d->DesiredBranchId = desiredBranchId;
  685. logger.info("goForward - posting ValidationTransition");
  686. d->StateMachine->postEvent(
  687. new ctkWorkflowIntrastepTransitionEvent(ctkWorkflowIntrastepTransition::ValidationTransition));
  688. }
  689. // --------------------------------------------------------------------------
  690. void ctkWorkflow::goBackward(const QString& desiredBranchId)
  691. {
  692. Q_D(ctkWorkflow);
  693. if (!this->isRunning())
  694. {
  695. logger.warn("goBackward - The workflow is not running !");
  696. return;
  697. }
  698. if (!this->canGoBackward())
  699. {
  700. logger.warn("goBackward - Attempt to goBackward from first step !");
  701. return;
  702. }
  703. ctkWorkflowStep* previousStep = d->StepToPreviousStepMap[d->CurrentStep];
  704. Q_ASSERT(previousStep);
  705. QString branchId = d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->backwardBranchId(previousStep);
  706. Q_ASSERT(!branchId.isEmpty());
  707. d->DesiredBranchId = desiredBranchId;
  708. logger.info("goBackward - posting TransitionToPreviousStep");
  709. d->StateMachine->postEvent(
  710. new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToPreviousStep, branchId));
  711. }
  712. // --------------------------------------------------------------------------
  713. CTK_GET_CPP(ctkWorkflow, bool, goBackToOriginStepUponSuccess, GoBackToOriginStepUponSuccess);
  714. CTK_SET_CPP(ctkWorkflow, bool, setGoBackToOriginStepUponSuccess, GoBackToOriginStepUponSuccess);
  715. // --------------------------------------------------------------------------
  716. void ctkWorkflow::goToStep(const QString& targetId)
  717. {
  718. Q_D(ctkWorkflow);
  719. if (!this->isRunning())
  720. {
  721. logger.warn("goToStep - The workflow is not running !");
  722. return;
  723. }
  724. // TODO currently returns true only if the workflow is running - need logic here
  725. if (!this->canGoToStep(targetId))
  726. {
  727. logger.warn(QString("goToStep - Cannot goToStep %1 ").arg(targetId));
  728. return;
  729. }
  730. #ifndef QT_NO_DEBUG
  731. ctkWorkflowStep* step = d->stepFromId(targetId);
  732. Q_ASSERT(step);
  733. #endif
  734. logger.info(QString("goToStep - Attempting to go to finish step %1").arg(targetId));
  735. // if (step == d->CurrentStep)
  736. // {
  737. // qDebug() << "we are already in the desired finish step";
  738. // return;
  739. // }
  740. d->GoToStep = d->stepFromId(targetId);
  741. d->StartingStep = d->CurrentStep;
  742. this->goForward();
  743. }
  744. // --------------------------------------------------------------------------
  745. void ctkWorkflow::attemptToGoToNextStep()
  746. {
  747. logger.info("attemptToGoToNextStep - Attempting to go to the next step ");
  748. Q_D(ctkWorkflow);
  749. Q_ASSERT(d->CurrentStep);
  750. //Q_ASSERT(this->canGoForward(d->CurrentStep));
  751. d->validateInternal(d->CurrentStep);
  752. }
  753. // --------------------------------------------------------------------------
  754. void ctkWorkflow::evaluateValidationResults(bool validationSucceeded, const QString& branchId)
  755. {
  756. if (validationSucceeded)
  757. {
  758. this->goToNextStepAfterSuccessfulValidation(branchId);
  759. }
  760. else
  761. {
  762. this->goToProcessingStateAfterValidationFailed();
  763. }
  764. }
  765. // --------------------------------------------------------------------------
  766. // if ctkWorkflowStep::validationComplete() did not provide a branchId, then:
  767. // - if there is one step following the current step, we will follow that transition
  768. // - if there are multiple steps following the current step, then we will follow the first
  769. // transition that was added
  770. // (either way this corresponds to following the first forwardBranchId we've recorded)
  771. // if ctkWorkflowStep::validationComplete() provided a branchId, then:
  772. // - if there is one transition following the current step that was not created using a branchId,
  773. // then we will follow it
  774. // - otherwise do a conditional branching based on the branchId provided by validationComplete()
  775. void ctkWorkflow::goToNextStepAfterSuccessfulValidation(const QString& branchId)
  776. {
  777. Q_D(ctkWorkflow);
  778. logger.debug("goToNextStepAfterSuccessfulValidation - Calidation succeeded");
  779. logger.info("goToNextStepAfterSuccessfulValidation - Posting TransitionToNextStep");
  780. // we may already be in the 'goTo' step - i.e. looping on a finish step
  781. if (d->GoToStep && d->CurrentStep == d->GoToStep)
  782. {
  783. this->goToStepSucceeded();
  784. return;
  785. }
  786. QString transitionBranchId;
  787. // these values are helpful for the logic below
  788. QString firstForwardBranchId = d->StepToForwardAndBackwardStepMap.value(d->CurrentStep)->firstForwardBranchId();
  789. int numberOfForwardSteps = d->numberOfForwardSteps(d->CurrentStep);
  790. Q_ASSERT(!firstForwardBranchId.isEmpty());
  791. Q_ASSERT(numberOfForwardSteps);
  792. // validationComplete() does not give us a branchId
  793. if (branchId.isEmpty())
  794. {
  795. transitionBranchId = firstForwardBranchId;
  796. if (numberOfForwardSteps > 1)
  797. {
  798. logger.warn("goToNextStepAfterSuccessfulValidation - ctkWorkflowStep::ValidatComplete() "
  799. "did not provide branchId at a branch in the workflow - will follow first "
  800. "transition that was created");
  801. }
  802. }
  803. // validationComplete() gives us a branchId
  804. else
  805. {
  806. if (numberOfForwardSteps == 1 && firstForwardBranchId.contains(d->ARTIFICIAL_BRANCH_ID_PREFIX))
  807. {
  808. transitionBranchId = firstForwardBranchId;
  809. logger.warn("goToNextStepAfterSuccessfulValidation - ctkWorkflowStep::ValidationComplete()"
  810. " returns a branchId, but was overridden by the workflow");
  811. }
  812. else
  813. {
  814. transitionBranchId = branchId;
  815. }
  816. }
  817. // 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
  818. if (d->GoToStep && !d->pathExistsFromNextStep(d->GoToStep->id(), transitionBranchId))
  819. {
  820. this->goToProcessingStateAfterValidationFailed();
  821. return;
  822. }
  823. d->StateMachine->postEvent(new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToNextStep, transitionBranchId));
  824. }
  825. // --------------------------------------------------------------------------
  826. void ctkWorkflow::goToProcessingStateAfterValidationFailed()
  827. {
  828. logger.debug("goToNextStepAfterSuccessfulValidation - Validation failed");
  829. Q_D(ctkWorkflow);
  830. // Validation failed in the process of attempting to go to the finish step
  831. if (d->GoToStep)
  832. {
  833. this->goToStepFailed();
  834. }
  835. logger.info("goToNextStepAfterSuccessfulValidation - Posting ValidationFailedTransition");
  836. d->StateMachine->postEvent(new ctkWorkflowIntrastepTransitionEvent(ctkWorkflowIntrastepTransition::ValidationFailedTransition));
  837. }
  838. // --------------------------------------------------------------------------
  839. void ctkWorkflow::performTransitionBetweenSteps()
  840. {
  841. Q_D(ctkWorkflow);
  842. logger.debug("performTransitionBetweenSteps - Performing transition between steps");
  843. // Alternative: could find the origin and destination step based on
  844. // d->CurrentStep rather than QObject::sender(), but would require
  845. // keeping track of an origin step's destination step (and would be
  846. // tricky in an extension to branching workflows, unless we change
  847. // this method signature)
  848. ctkWorkflowInterstepTransition* transition = qobject_cast<ctkWorkflowInterstepTransition*>(QObject::sender());
  849. Q_ASSERT(transition);
  850. d->OriginStep = d->stepFromState(transition->sourceState());
  851. d->DestinationStep = d->stepFromState(transition->targetState());
  852. d->TransitionType = transition->transitionType();
  853. Q_ASSERT(d->TransitionType == ctkWorkflowInterstepTransition::TransitionToNextStep
  854. || d->TransitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStep
  855. || d->TransitionType == ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep);
  856. // update the map from the step to the previous step if we are going forward
  857. if (d->TransitionType == ctkWorkflowInterstepTransition::TransitionToNextStep)
  858. {
  859. d->StepToPreviousStepMap.insert(d->DestinationStep, d->OriginStep);
  860. }
  861. // exit the destination step
  862. d->onExitInternal(d->OriginStep, d->DestinationStep, d->TransitionType);
  863. }
  864. // --------------------------------------------------------------------------
  865. void ctkWorkflow::goToStepSucceeded()
  866. {
  867. Q_D(ctkWorkflow);
  868. logger.debug("goToStepSucceeded");
  869. // after success, go back to the step at which we begin looking for
  870. // the finish step (will exit the current step and enter the starting step)
  871. // only if the property goBackToOriginStepUponSuccess is true.
  872. if (this->goBackToOriginStepUponSuccess())
  873. {
  874. d->createTransitionToPreviousStartingStep(d->StartingStep, d->CurrentStep);
  875. }
  876. d->GoToStep = 0;
  877. d->StartingStep->setStatusText("Attempt to go to the finish step succeeded");
  878. d->StartingStep = 0;
  879. if (this->goBackToOriginStepUponSuccess())
  880. {
  881. this->goFromGoToStepToStartingStep();
  882. }
  883. }
  884. // --------------------------------------------------------------------------
  885. void ctkWorkflow::goFromGoToStepToStartingStep()
  886. {
  887. Q_D(ctkWorkflow);
  888. logger.info("goFromGoToStepToStartingStep - Posting TransitionToPreviousStartingStep");
  889. d->StateMachine->postEvent(new ctkWorkflowInterstepTransitionEvent(ctkWorkflowInterstepTransition::TransitionToPreviousStartingStepAfterSuccessfulGoToFinishStep));
  890. }
  891. // --------------------------------------------------------------------------
  892. void ctkWorkflow::goToStepFailed()
  893. {
  894. // Abort attempt to get to the finish step
  895. Q_D(ctkWorkflow);
  896. d->GoToStep = 0;
  897. d->StartingStep = 0;
  898. // We don't need to transition between steps - leave the user at the
  899. // point of failure, so that they can try to continue manually
  900. // Emit the signal that we have changed the current step, since it wasn't emitted in the process
  901. // of going to the 'goTo' step.
  902. emit this->currentStepChanged(d->CurrentStep);
  903. // if (failedOnBranch)
  904. // {
  905. // this->goToProcessingStateAfterValidationFailed();
  906. // }
  907. }