USGS

Isis 3.0 Object Programmers' Reference

Home

WorkOrder.cpp
Go to the documentation of this file.
1 
23 #include "IsisDebug.h"
24 
25 #include "WorkOrder.h"
26 
27 #include <QDebug>
28 #include <QFutureWatcher>
29 #include <QMutex>
30 #include <QtConcurrentRun>
31 #include <QTimer>
32 #include <QXmlStreamWriter>
33 
34 #include "IException.h"
35 #include "IString.h"
36 #include "ProgressBar.h"
37 #include "Project.h"
38 #include "XmlStackedHandlerReader.h"
39 
40 namespace Isis {
47  WorkOrder::WorkOrder(Project *project) : QAction(project) {
48  m_project = project;
49 
50  m_createsCleanState = false;
51  m_modifiesDiskState = false;
52  m_status = WorkOrderNotStarted;
53  m_queuedAction = NoQueuedAction;
54  m_transparentConstMutex = NULL;
55  m_elapsedTimer = NULL;
56 
57  m_context = NoContext;
58  m_images = new ImageList;
59  m_futureWatcher = new QFutureWatcher<void>;
60  m_transparentConstMutex = new QMutex;
61 
62  m_progressRangeMinValue = 0;
63  m_progressRangeMaxValue = 100;
64  m_progressValue = 0;
65 
66  m_secondsElapsed = 0.0;
67 
68  if (!m_project) {
70  tr("Work orders cannot be created without a project."), _FILEINFO_);
71  }
72 
73  connect(this, SIGNAL(triggered()),
74  this, SLOT(addCloneToProject()));
75  connect(m_futureWatcher, SIGNAL(finished()),
76  this, SLOT(asyncFinished()));
77  }
78 
79 
80  WorkOrder::~WorkOrder() {
81  delete m_images;
82  delete m_futureWatcher;
83  delete m_progressBar;
84  delete m_progressBarDeletionTimer;
85  delete m_progressBarUpdateTimer;
86  delete m_transparentConstMutex;
87 
88  m_nextWorkOrder = NULL;
89  m_previousWorkOrder = NULL;
90  m_project = NULL;
91  m_transparentConstMutex = NULL;
92  }
93 
94 
101  QAction(other.icon(), ((QAction &)other).text(), other.parentWidget()),
102  QUndoCommand(((QUndoCommand &)other).text()) {
103  m_transparentConstMutex = NULL;
104  m_elapsedTimer = NULL;
105 
106  m_project = other.project();
107 
108  m_context = other.m_context;
109  m_imageIds = other.m_imageIds;
110  m_images = new ImageList(*other.m_images);
111  m_controls = other.m_controls;
112  m_internalData = other.m_internalData;
113 
116 
118 
119  m_status = other.m_status;
120  m_queuedAction = other.m_queuedAction;
121 
122  m_secondsElapsed = other.m_secondsElapsed;
123 
124  m_progressRangeMinValue = other.m_progressRangeMinValue;
125  m_progressRangeMaxValue = other.m_progressRangeMaxValue;
126  m_progressValue = other.m_progressValue;
127 
128  m_futureWatcher = new QFutureWatcher<void>;
129  m_transparentConstMutex = new QMutex;
130 
131  if (!other.isInStableState()) {
133  tr("Can not copy work order [%1] because it is currently running")
134  .arg(((QUndoCommand &)other).text()),
135  _FILEINFO_);
136  }
137 
138  connect(this, SIGNAL(triggered()),
139  this, SLOT(addCloneToProject()));
140  connect(m_futureWatcher, SIGNAL(finished()),
141  this, SLOT(asyncFinished()));
142 
143  listenForImageDestruction();
144  }
145 
146 
153  bool WorkOrder::isExecutable(Context context) {
154  return false;
155  }
156 
157 
166  return false;
167  }
168 
169 
179  return false;
180  }
181 
182 
187  xmlReader->pushContentHandler(new XmlHandler(this));
188  }
189 
190 
206  void WorkOrder::save(QXmlStreamWriter &stream) const {
207  if (!isInStableState()) {
209  tr("Can not store an unstable work order. The work order [%1] is currently "
210  "working").arg(bestText()),
211  _FILEINFO_);
212  }
213 
214  stream.writeStartElement("workOrder");
215 
216  stream.writeAttribute("actionText", QAction::text());
217  stream.writeAttribute("undoText", QUndoCommand::text());
218  stream.writeAttribute("executionTime", m_executionTime.toString());
219  stream.writeAttribute("type", metaObject()->className());
220  stream.writeAttribute("status", toString(m_status));
221 
222  if (m_imageIds.count()) {
223  stream.writeStartElement("images");
224 
225  foreach (QString imageId, m_imageIds) {
226  stream.writeStartElement("image");
227  stream.writeAttribute("id", imageId);
228  stream.writeEndElement();
229  }
230 
231  stream.writeEndElement();
232  }
233 
234  if (m_internalData.count()) {
235  stream.writeStartElement("internalDataValues");
236 
237  foreach (QString str, m_internalData) {
238  stream.writeStartElement("dataValue");
239  stream.writeAttribute("value", str);
240  stream.writeEndElement();
241  }
242 
243  stream.writeEndElement();
244  }
245 
246  if (m_context != NoContext) {
247  stream.writeStartElement("context");
248 
249  QString contextStr = "ProjectContext";
250  stream.writeAttribute("value", contextStr);
251 
252  stream.writeEndElement();
253  }
254 
255  stream.writeEndElement();
256  }
257 
258 
259  void WorkOrder::setData(Context context) {
260  m_context = context;
261  }
262 
263 
264  void WorkOrder::setData(ImageList *images) {
265  m_imageIds.clear();
266  delete m_images;
267 
268  m_images = new ImageList(*images);
269  listenForImageDestruction();
270  }
271 
272 
273  void WorkOrder::setData(QList<Control *> controls) {
274  m_controls = controls;
275  }
276 
277 
278  void WorkOrder::setNext(WorkOrder *nextWorkOrder) {
279  m_nextWorkOrder = nextWorkOrder;
280  }
281 
282 
283  void WorkOrder::setPrevious(WorkOrder *previousWorkOrder) {
284  m_previousWorkOrder = previousWorkOrder;
285  }
286 
287 
288  ImageList *WorkOrder::imageList() {
289  if (!m_images) {
290  bool anyImagesAreNull = false;
291 
292  m_images = new ImageList;
293 
294  foreach (QString id, m_imageIds) {
295  Image *image = project()->image(id);
296  m_images->append(image);
297 
298  if (!image) {
299  anyImagesAreNull = true;
300  }
301  }
302 
303  if (anyImagesAreNull) {
304  delete m_images;
305  }
306  else {
307  listenForImageDestruction();
308  }
309  }
310 
311  return m_images;
312  }
313 
314 
315  QList<Control *> WorkOrder::controlList() {
316  return m_controls;
317  }
318 
319 
320  const ImageList *WorkOrder::imageList() const {
321  QMutexLocker lock(m_transparentConstMutex);
322  return const_cast<WorkOrder *>(this)->imageList();
323  }
324 
325 
326  bool WorkOrder::dependsOn(WorkOrder *) const {
327  return true;
328  }
329 
330 
331  QString WorkOrder::bestText() const {
332  QString result = QUndoCommand::text().remove("&").remove("...");
333 
334  // We don't use action text anymore because Directory likes to rename our actions... it
335  // converts a set of actions that have the same text, like Zoom Fit, to be in a menu named
336  // Zoom Fit with items that name their widgets. Widget names are unhelpful as a description
337  // of the action.
338  //
339  // See Directory::restructureActions
340  //
341 
342  // if the QUndoCommand has no text, create a warning
343  if (result.isEmpty()) {
344  // get the name of the work order
345  result = QString(metaObject()->className()).remove("Isis::").remove("WorkOrder")
346  .replace(QRegExp("([a-z0-9])([A-Z])"), "\\1 \\2");
347  qWarning() << QString("WorkOrder::bestText(): Work order [%1] has no QUndoCommand text").arg(result);
348  }
349 
350  return result;
351  }
352 
353 
354  bool WorkOrder::createsCleanState() const {
355  return m_createsCleanState;
356  }
357 
358 
359  QDateTime WorkOrder::executionTime() const {
360  return m_executionTime;
361  }
362 
363 
364  bool WorkOrder::isFinished() const {
365  return m_status == WorkOrderFinished;
366  }
367 
368 
369  bool WorkOrder::isRedoing() const {
370  return m_status == WorkOrderRedoing;
371  }
372 
373 
374  bool WorkOrder::isRedone() const {
375  return m_status == WorkOrderRedone;
376  }
377 
378 
379  bool WorkOrder::isUndoing() const {
380  return m_status == WorkOrderUndoing;
381  }
382 
383 
384  bool WorkOrder::isUndone() const {
385  return m_status == WorkOrderUndone;
386  }
387 
388 
389  bool WorkOrder::modifiesDiskState() const {
390  return m_modifiesDiskState;
391  }
392 
393 
394  WorkOrder *WorkOrder::next() const {
395  return m_nextWorkOrder;
396  }
397 
398 
399  WorkOrder *WorkOrder::previous() const {
400  return m_previousWorkOrder;
401  }
402 
403 
404  QString WorkOrder::statusText() const {
405  QString result = toString(m_status);
406 
407  if (m_secondsElapsed) {
408  // QTime can't format in the way that I want (0-n minutes, 00-59 seconds, no hours
409  // displayed)... so do it manually.
410  // Expected output format examples: 0:01, 0:55, 1:30, 55:55, 90:00, 100:12
411  int seconds = qRound(m_secondsElapsed) % 60;
412  int minutes = qRound(m_secondsElapsed) / 60;
413  result += tr(" (elapsed: %1:%2)").arg(minutes).arg(seconds, 2, 10, QChar('0'));
414  }
415 
416  return result;
417  }
418 
419 
420  ProgressBar *WorkOrder::progressBar() {
421  return m_progressBar;
422  }
423 
424 
425  WorkOrder::WorkOrderStatus WorkOrder::fromStatusString(QString statusString) {
426  statusString = statusString.toUpper();
427  WorkOrderStatus result = WorkOrderUnknownStatus;
428 
429  for (WorkOrderStatus possibleResult = WorkOrderUnknownStatus;
430  possibleResult <= WorkOrderLastStatus;
431  possibleResult = (WorkOrderStatus)(possibleResult + 1)) {
432  if (statusString == toString(possibleResult).toUpper()) {
433  result = possibleResult;
434  }
435  }
436 
437  return result;
438  }
439 
440 
441  QString WorkOrder::toString(WorkOrderStatus status) {
442  QString result;
443 
444  switch (status) {
445  case WorkOrderUnknownStatus:
446  result = tr("Unknown");
447  break;
448  case WorkOrderNotStarted:
449  result = tr("Not Started");
450  break;
451  case WorkOrderRedoing:
452  result = tr("In Progress");
453  break;
454  case WorkOrderRedone:
455  result = tr("Completed");
456  break;
457  case WorkOrderUndoing:
458  result = tr("Undoing");
459  break;
460  case WorkOrderUndone:
461  result = tr("Undone");
462  break;
463  case WorkOrderFinished:
464  result = tr("Finished");
465  break;
466  }
467 
468  return result;
469  }
470 
471 
476  if (!isInStableState()) {
477  m_queuedAction = RedoQueuedAction;
478  }
479 
480  if (!isRedone()) {
481  bool mustQueueThisRedo = false;
482 
483  WorkOrder *dependency = NULL;
484  WorkOrder *current = this;
485  while (current->previous() && !dependency) {
486  if (!current->previous()->isRedone() && !current->previous()->isFinished()) {
487  WorkOrder *possibleDependency = current->previous();
488 
489  if (dependsOn(possibleDependency)) {
490  connect(possibleDependency, SIGNAL(finished(WorkOrder *)),
491  this, SLOT(attemptQueuedAction()));
492  dependency = possibleDependency;
493  mustQueueThisRedo = true;
494  }
495  }
496 
497  current = current->previous();
498  }
499 
500  if (!imageList()) {
501  connect(project(), SIGNAL(imagesAdded(ImageList *)),
502  this, SLOT(attemptQueuedAction()));
503  mustQueueThisRedo = true;
504  }
505 
506  if (mustQueueThisRedo && !isUndoing() && !isRedoing()) {
507  m_queuedAction = RedoQueuedAction;
508 
509  QString queueStatusText;
510 
511  if (dependency) {
512  QString dependencyText = dependency->bestText();
513 
514  if (dependencyText.count() > 5) {
515  dependencyText = dependencyText.mid(0, 5) + "...";
516  }
517 
518  queueStatusText = tr("Wait for [%1]").arg(dependencyText);
519  }
520  else if (!imageList()) {
521  queueStatusText = tr("Wait for images");
522  }
523 
524  resetProgressBar();
525  m_progressBar->setValue(m_progressBar->minimum());
526  m_progressBar->setText(queueStatusText);
527  m_progressBar->update();
528  }
529 
530  if (m_queuedAction == NoQueuedAction) {
531  m_status = WorkOrderRedoing;
532  emit statusChanged(this);
533 
534  resetProgressBar();
535  m_progressBar->setText("Starting...");
536  m_progressBar->update();
537 
538  delete m_elapsedTimer;
539  m_elapsedTimer = new QTime;
540  m_elapsedTimer->start();
541 
542  syncRedo();
543 
544  m_progressBar->setText("Running...");
545  m_progressBar->update();
546  QFuture<void> future = QtConcurrent::run(this, &WorkOrder::asyncRedo);
547  m_futureWatcher->setFuture(future);
548  }
549  }
550  else {
551  setProgressToFinalText();
552  }
553  }
554 
555 
560  if (!isInStableState()) {
561  m_queuedAction = UndoQueuedAction;
562  }
563 
564  if (!isUndone() && m_status != WorkOrderNotStarted) {
565  WorkOrder *dependency = NULL;
566  WorkOrder *current = this;
567  while (current->next() && !dependency) {
568  if (!current->next()->isUndone() && !current->next()->isFinished() &&
569  current->next()->m_status != WorkOrderNotStarted) {
570  connect(current->next(), SIGNAL(finished(WorkOrder *)),
571  this, SLOT(attemptQueuedAction()));
572  m_queuedAction = UndoQueuedAction;
573  dependency = current->next();
574  }
575 
576  current = current->next();
577  }
578 
579  if (dependency && !isUndoing() && !isRedoing()) {
580  QString prevText = dependency->bestText();
581 
582  if (prevText.count() > 5) {
583  prevText = prevText.mid(0, 5) + "...";
584  }
585 
586  resetProgressBar();
587  m_progressBar->setValue(m_progressBar->minimum());
588  m_progressBar->setText(tr("Undo after [%1]").arg(prevText));
589  m_progressBar->update();
590  }
591 
592  if (m_queuedAction == NoQueuedAction) {
593  m_status = WorkOrderUndoing;
594  emit statusChanged(this);
595 
596  resetProgressBar();
597  m_progressBar->setText("Starting Undo...");
598  m_progressBar->update();
599 
600  delete m_elapsedTimer;
601  m_elapsedTimer = new QTime;
602  m_elapsedTimer->start();
603 
604  syncUndo();
605 
606  m_progressBar->setText("Undoing...");
607  m_progressBar->update();
608  QFuture<void> future = QtConcurrent::run(this, &WorkOrder::asyncUndo);
609  m_futureWatcher->setFuture(future);
610  }
611  }
612  else {
613  setProgressToFinalText();
614  }
615  }
616 
617 
642  // We're finished at this point if we save/open a project, we're not finished if we need to do
643  // redo()
644  if (createsCleanState()) {
645  m_status = WorkOrderFinished;
646 
647  emit statusChanged(this);
648  }
649 
650  m_executionTime = QDateTime::currentDateTime();
651 
652  resetProgressBar();
653 
654  if (createsCleanState()) {
655  setProgressToFinalText();
656  }
657  else {
658  m_progressBar->setText("Initializing...");
659  }
660 
661  return true;
662  }
663 
664 
665  Directory *WorkOrder::directory() const {
666  return project()->directory();
667  }
668 
669 
670  Project *WorkOrder::project() const {
671  if (!m_project) {
672  throw IException(IException::Programmer,
673  "This work order no longer has a project.", _FILEINFO_);
674  }
675 
676  return m_project;
677  }
678 
679 
680  void WorkOrder::setInternalData(QStringList data) {
681  m_internalData = data;
682  }
683 
684 
685  int WorkOrder::progressMin() const {
686  return m_progressRangeMinValue;
687  }
688 
689 
690  int WorkOrder::progressMax() const {
691  return m_progressRangeMaxValue;
692  }
693 
694 
695  int WorkOrder::progressValue() const {
696  return m_progressValue;
697  }
698 
699 
700  void WorkOrder::setProgressRange(int minValue, int maxValue) {
701  m_progressRangeMinValue = minValue;
702  m_progressRangeMaxValue = maxValue;
703  }
704 
705 
706  void WorkOrder::setProgressValue(int value) {
707  m_progressValue = value;
708  }
709 
710 
711  QStringList WorkOrder::internalData() const {
712  return m_internalData;
713  }
714 
715 
728  }
729 
730 
746  }
747 
748 
761  }
762 
763 
776  }
777 
778 
793  }
794 
795 
808  }
809 
810 
811  void WorkOrder::addCloneToProject() {
812  if (project()) {
813  project()->addToProject(clone());
814  }
815  }
816 
817 
818  bool WorkOrder::isInStableState() const {
819  bool result = true;
820 
821  if (isRedoing() || isUndoing() || m_queuedAction != NoQueuedAction) {
822  result = false;
823  }
824 
825  return result;
826  }
827 
828 
829  void WorkOrder::listenForImageDestruction() {
830  m_imageIds.clear();
831  foreach (Image *image, *m_images) {
832  if (image) {
833  m_imageIds.append(image->id());
834 
835  // If we lose any images, destroy the entire list. This will let us know that we need to
836  // rebuild it, if needed, when requested.
837  connect(image, SIGNAL(destroyed(QObject *)),
838  this, SLOT(clearImageList()));
839  }
840  }
841  }
842 
843 
844  void WorkOrder::resetProgressBar() {
845  delete m_progressBarDeletionTimer;
846 
847  if (!m_progressBar) {
848  m_progressBar = new ProgressBar;
849  emit creatingProgress(this);
850  }
851 
852  if (!m_progressBarUpdateTimer) {
853  m_progressBarUpdateTimer = new QTimer;
854  connect(m_progressBarUpdateTimer, SIGNAL(timeout()),
855  this, SLOT(updateProgress()));
856  m_progressBarUpdateTimer->start(100);
857  }
858 
859  m_progressRangeMinValue = 0;
860  m_progressRangeMaxValue = 100;
861  m_progressValue = 0;
862  }
863 
864 
865  void WorkOrder::setProgressToFinalText() {
866  if (m_progressBar) {
867  if (isRedone()) {
868  m_progressBar->setText(tr("Finished"));
869  }
870  else if (isUndone() || m_status == WorkOrderNotStarted) {
871  m_progressBar->setText(tr("Undone"));
872  }
873 
874  if (m_progressBar->minimum() != 0 || m_progressBar->maximum() != 0) {
875  m_progressBar->setValue(m_progressBar->maximum());
876  }
877  else {
878  m_progressBar->setRange(0, 100);
879  m_progressBar->setValue(100);
880  }
881 
882  delete m_progressBarDeletionTimer;
883  m_progressBarDeletionTimer = new QTimer;
884  m_progressBarDeletionTimer->setSingleShot(true);
885  connect(m_progressBarDeletionTimer, SIGNAL(timeout()),
886  this, SLOT(deleteProgress()));
887 
888  m_progressBarDeletionTimer->start(5 * 1000); // 5 seconds
889 
890  m_progressBar->update();
891  }
892  }
893 
894 
895  void WorkOrder::attemptQueuedAction() {
896  QueuedWorkOrderAction queued = m_queuedAction;
897  m_queuedAction = NoQueuedAction;
898 
899  if (queued == RedoQueuedAction && m_status != WorkOrderRedone) {
900  redo();
901  }
902  else if (queued == UndoQueuedAction && m_status != WorkOrderUndone) {
903  undo();
904  }
905  }
906 
907 
908  void WorkOrder::asyncFinished() {
909  delete m_progressBarUpdateTimer;
910 
911  WorkOrderStatus finishedStatus = WorkOrderRedone;
912  void (WorkOrder::*postSyncMethod)() = &WorkOrder::postSyncRedo;
913 
914  if (isUndoing()) {
915  finishedStatus = WorkOrderUndone;
916  postSyncMethod = &WorkOrder::postSyncUndo;
917  }
918 
919  (this->*postSyncMethod)();
920 
921  m_status = finishedStatus;
922 
923  m_secondsElapsed = m_elapsedTimer->elapsed() / 1000.0;
924 
925  delete m_elapsedTimer;
926  m_elapsedTimer = NULL;
927 
928  emit statusChanged(this);
929  setProgressToFinalText();
930  emit finished(this);
931 
932  attemptQueuedAction();
933  }
934 
935 
936  void WorkOrder::clearImageList() {
937  delete m_images;
938  }
939 
940 
941  void WorkOrder::deleteProgress() {
942  ProgressBar *progress = m_progressBar;
943 
944  if (m_progressBar) {
945  m_progressBar = NULL;
946  emit deletingProgress(this);
947  delete progress;
948  }
949  }
950 
951 
952  void WorkOrder::updateProgress() {
953  if (m_progressBar && (isRedoing() || isUndoing())) {
954  m_progressBar->setRange(m_progressRangeMinValue, m_progressRangeMaxValue);
955  m_progressBar->setValue(m_progressValue);
956  }
957  }
958 
959 
960  void WorkOrder::startRedo() {
961  }
962 
963 
971  void WorkOrder::setCreatesCleanState(bool createsCleanState) {
972  m_createsCleanState = createsCleanState;
973  }
974 
975 
976  void WorkOrder::setModifiesDiskState(bool changesProjectOnDisk) {
977  m_modifiesDiskState = changesProjectOnDisk;
978  }
979 
980 
981  WorkOrder::XmlHandler::XmlHandler(WorkOrder *workOrder) {
982  m_workOrder = workOrder;
983  }
984 
985 
991  bool WorkOrder::XmlHandler::startElement(const QString &namespaceURI, const QString &localName,
992  const QString &qName, const QXmlAttributes &atts) {
993  if (XmlStackedHandler::startElement(namespaceURI, localName, qName, atts)) {
994  if (localName == "workOrder") {
995  QString actionText = atts.value("actionText");
996  QString undoText = atts.value("undoText");
997  QString executionTime = atts.value("executionTime");
998  QString statusStr = atts.value("status");
999 
1000  if (!actionText.isEmpty()) {
1001  ((QAction *)m_workOrder)->setText(actionText);
1002  }
1003 
1004  if (!undoText.isEmpty()) {
1005  ((QUndoCommand *)m_workOrder)->setText(undoText);
1006  }
1007 
1008  if (!executionTime.isEmpty()) {
1009  m_workOrder->m_executionTime = QDateTime::fromString(executionTime);
1010  }
1011 
1012  if (!statusStr.isEmpty()) {
1013  m_workOrder->m_status = fromStatusString(statusStr);
1014  }
1015  else {
1016  if (m_workOrder->createsCleanState()) {
1017  m_workOrder->m_status = WorkOrderFinished;
1018  }
1019  else {
1020  m_workOrder->m_status = WorkOrderRedone;
1021  }
1022  }
1023  }
1024  else if (localName == "dataValue") {
1025  m_workOrder->m_internalData.append(atts.value("value"));
1026  }
1027  else if (localName == "context") {
1028  if (atts.value("value") == "ProjectContext") {
1029  m_workOrder->m_context = ProjectContext;
1030  }
1031  }
1032  }
1033 
1034  return true;
1035  }
1036 }