QGIS API Documentation  3.27.0-Master (e113457133)
qgstaskmanagerwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgstaskmanagerwidget.cpp
3  ------------------------
4  begin : April 2016
5  copyright : (C) 2016 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgstaskmanagerwidget.h"
19 #include "qgstaskmanager.h"
20 #include "qgsapplication.h"
21 #include <QPainter>
22 #include <QMouseEvent>
23 #include <QTreeView>
24 #include <QLayout>
25 #include <QToolBar>
26 #include <QProgressBar>
27 #include <QAction>
28 #include <QHeaderView>
29 
30 //
31 // QgsTaskManagerWidget
32 //
33 
35  : QWidget( parent )
36  , mManager( manager )
37 {
38  Q_ASSERT( manager );
39 
40  QVBoxLayout *vLayout = new QVBoxLayout();
41  vLayout->setContentsMargins( 0, 0, 0, 0 );
42  mTreeView = new QTreeView();
43  mModel = new QgsTaskManagerModel( manager, this );
44  mTreeView->setModel( mModel );
45  connect( mModel, &QgsTaskManagerModel::rowsInserted, this, &QgsTaskManagerWidget::modelRowsInserted );
46  mTreeView->setHeaderHidden( true );
47  mTreeView->setRootIsDecorated( false );
48  mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
49 
50  const int progressColWidth = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR );
51  mTreeView->setColumnWidth( QgsTaskManagerModel::Progress, progressColWidth );
52 
53  const int statusColWidth = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 2 * Qgis::UI_SCALE_FACTOR );
54  mTreeView->setColumnWidth( QgsTaskManagerModel::Status, statusColWidth );
55  mTreeView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
56  mTreeView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
57  mTreeView->header()->setStretchLastSection( false );
58  mTreeView->header()->setSectionResizeMode( QgsTaskManagerModel::Description, QHeaderView::Stretch );
59 
60  connect( mTreeView, &QTreeView::clicked, this, &QgsTaskManagerWidget::clicked );
61 
62  vLayout->addWidget( mTreeView );
63 
64  setLayout( vLayout );
65 }
66 
68 {
69  delete mModel;
70 }
71 
72 
73 void QgsTaskManagerWidget::modelRowsInserted( const QModelIndex &, int start, int end )
74 {
75  for ( int row = start; row <= end; ++row )
76  {
77  QgsTask *task = mModel->indexToTask( mModel->index( row, 1 ) );
78  if ( !task )
79  continue;
80 
81  QProgressBar *progressBar = new QProgressBar();
82  progressBar->setAutoFillBackground( true );
83  progressBar->setRange( 0, 0 );
84  connect( task, &QgsTask::progressChanged, progressBar, [progressBar]( double progress )
85  {
86  //until first progress report, we show a progress bar of interderminant length
87  if ( progress > 0 )
88  {
89  progressBar->setMaximum( 100 );
90  progressBar->setValue( static_cast< int >( std::round( progress ) ) );
91  }
92  else
93  progressBar->setMaximum( 0 );
94  }
95  );
96  mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
97 
98  QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
99  statusWidget->setAutoFillBackground( true );
100  connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
101  connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
102  mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
103  }
104 }
105 
106 void QgsTaskManagerWidget::clicked( const QModelIndex &index )
107 {
108  QgsTask *task = mModel->indexToTask( index );
109  if ( !task )
110  return;
111 
112  mManager->triggerTask( task );
113 }
114 
116 //
117 // QgsTaskManagerModel
118 //
119 
120 QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
121  : QAbstractItemModel( parent )
122  , mManager( manager )
123 {
124  Q_ASSERT( mManager );
125 
126  //populate row to id map
127  const auto constTasks = mManager->tasks();
128  for ( QgsTask *task : constTasks )
129  {
130  mRowToTaskIdList << mManager->taskId( task );
131  }
132 
133  connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
134  connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
135  connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
136 }
137 
138 QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
139 {
140  if ( column < 0 || column >= columnCount() )
141  {
142  //column out of bounds
143  return QModelIndex();
144  }
145 
146  if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
147  {
148  //return an index for the task at this position
149  return createIndex( row, column );
150  }
151 
152  //only top level supported
153  return QModelIndex();
154 
155 }
156 
157 QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
158 {
159  Q_UNUSED( index )
160 
161  //all items are top level
162  return QModelIndex();
163 }
164 
165 int QgsTaskManagerModel::rowCount( const QModelIndex &parent ) const
166 {
167  if ( !parent.isValid() )
168  {
169  return mRowToTaskIdList.count();
170  }
171  else
172  {
173  //no children
174  return 0;
175  }
176 }
177 
178 int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
179 {
180  Q_UNUSED( parent )
181  return 3;
182 }
183 
184 QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
185 {
186  if ( !index.isValid() )
187  return QVariant();
188 
189  QgsTask *task = indexToTask( index );
190  if ( task )
191  {
192  switch ( role )
193  {
194  case Qt::DisplayRole:
195  case Qt::EditRole:
196  switch ( index.column() )
197  {
198  case Description:
199  return task->description();
200  case Progress:
201  return task->progress();
202  case Status:
203  // delegate shows status
204  return QVariant();
205  default:
206  return QVariant();
207  }
208 
209  case StatusRole:
210  return static_cast<int>( task->status() );
211 
212  case Qt::ToolTipRole:
213  switch ( index.column() )
214  {
215  case Description:
216  return createTooltip( task, ToolTipDescription );
217  case Progress:
218  return createTooltip( task, ToolTipProgress );
219  case Status:
220  return createTooltip( task, ToolTipStatus );
221  default:
222  return QVariant();
223  }
224 
225 
226  default:
227  return QVariant();
228  }
229  }
230 
231  return QVariant();
232 }
233 
234 Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
235 {
236  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
237 
238  if ( ! index.isValid() )
239  {
240  return flags;
241  }
242 
243  QgsTask *task = indexToTask( index );
244  if ( index.column() == Status )
245  {
246  if ( task && task->canCancel() )
247  flags = flags | Qt::ItemIsEditable;
248  }
249  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
250 }
251 
252 bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
253 {
254  Q_UNUSED( role )
255 
256  if ( !index.isValid() )
257  return false;
258 
259  QgsTask *task = indexToTask( index );
260  if ( !task )
261  return false;
262 
263  switch ( index.column() )
264  {
265  case Status:
266  {
267  if ( value.toBool() && task->canCancel() )
268  task->cancel();
269  return true;
270  }
271 
272  default:
273  return false;
274  }
275 }
276 
277 void QgsTaskManagerModel::taskAdded( long id )
278 {
279  beginInsertRows( QModelIndex(), mRowToTaskIdList.count(),
280  mRowToTaskIdList.count() );
281  mRowToTaskIdList << id;
282  endInsertRows();
283 }
284 
285 void QgsTaskManagerModel::taskDeleted( long id )
286 {
287  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
288  {
289  if ( mRowToTaskIdList.at( row ) == id )
290  {
291  beginRemoveRows( QModelIndex(), row, row );
292  mRowToTaskIdList.removeAt( row );
293  endRemoveRows();
294  return;
295  }
296  }
297 }
298 
299 void QgsTaskManagerModel::progressChanged( long id, double progress )
300 {
301  Q_UNUSED( progress )
302 
303  const QModelIndex index = idToIndex( id, Progress );
304  if ( !index.isValid() )
305  {
306  return;
307  }
308 
309  emit dataChanged( index, index );
310 }
311 
312 void QgsTaskManagerModel::statusChanged( long id, int status )
313 {
314  if ( status == QgsTask::Complete || status == QgsTask::Terminated )
315  {
316  taskDeleted( id );
317  }
318  else
319  {
320  const QModelIndex index = idToIndex( id, Status );
321  if ( !index.isValid() )
322  {
323  return;
324  }
325 
326  emit dataChanged( index, index );
327  }
328 }
329 
330 QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
331 {
332  if ( !index.isValid() || index.parent().isValid() )
333  return nullptr;
334 
335  const long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
336  if ( id >= 0 )
337  return mManager->task( id );
338  else
339  return nullptr;
340 }
341 
342 int QgsTaskManagerModel::idToRow( long id ) const
343 {
344  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
345  {
346  if ( mRowToTaskIdList.at( row ) == id )
347  {
348  return row;
349  }
350  }
351  return -1;
352 }
353 
354 QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
355 {
356  const int row = idToRow( id );
357  if ( row < 0 )
358  return QModelIndex();
359 
360  return index( row, column );
361 }
362 
363 QString QgsTaskManagerModel::createTooltip( QgsTask *task, ToolTipType type )
364 {
365  if ( task->status() != QgsTask::Running )
366  {
367  switch ( type )
368  {
369  case ToolTipDescription:
370  return task->description();
371 
372  case ToolTipStatus:
373  case ToolTipProgress:
374  {
375  switch ( task->status() )
376  {
377  case QgsTask::Queued:
378  return tr( "Queued" );
379  case QgsTask::OnHold:
380  return tr( "On hold" );
381  case QgsTask::Running:
382  {
383  if ( type == ToolTipStatus && !task->canCancel() )
384  return tr( "Running (cannot cancel)" );
385  else
386  return tr( "Running" );
387  }
388  case QgsTask::Complete:
389  return tr( "Complete" );
390  case QgsTask::Terminated:
391  return tr( "Terminated" );
392  }
393  }
394  }
395  }
396 
397  QString formattedTime;
398 
399  const qint64 elapsed = task->elapsedTime();
400 
401  if ( task->progress() > 0 )
402  {
403  // estimate time remaining
404  const qint64 msRemain = static_cast< qint64 >( elapsed * 100.0 / task->progress() - elapsed );
405  if ( msRemain > 120 * 1000 )
406  {
407  const long long minutes = msRemain / 1000 / 60;
408  const int seconds = ( msRemain / 1000 ) % 60;
409  formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
410  }
411  else
412  formattedTime = tr( "%1 seconds" ).arg( msRemain / 1000 );
413 
414  formattedTime = tr( "Estimated time remaining: %1" ).arg( formattedTime );
415 
416  const QTime estimatedEnd = QTime::currentTime().addMSecs( msRemain );
417  formattedTime += tr( " (%1)" ).arg( QLocale::system().toString( estimatedEnd, QLocale::ShortFormat ) );
418  }
419  else
420  {
421  if ( elapsed > 120 * 1000 )
422  {
423  const long long minutes = elapsed / 1000 / 60;
424  const int seconds = ( elapsed / 1000 ) % 60;
425  formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
426  }
427  else
428  formattedTime = tr( "%1 seconds" ).arg( elapsed / 1000 );
429 
430  formattedTime = tr( "Time elapsed: %1" ).arg( formattedTime );
431  }
432 
433  switch ( type )
434  {
435  case ToolTipDescription:
436  return tr( "%1<br>%2" ).arg( task->description(), formattedTime );
437 
438  case ToolTipStatus:
439  case ToolTipProgress:
440  {
441  switch ( task->status() )
442  {
443  case QgsTask::Queued:
444  return tr( "Queued" );
445  case QgsTask::OnHold:
446  return tr( "On hold" );
447  case QgsTask::Running:
448  {
449  QString statusDesc;
450  if ( type == ToolTipStatus && !task->canCancel() )
451  statusDesc = tr( "Running (cannot cancel)" );
452  else
453  statusDesc = tr( "Running" );
454  return tr( "%1<br>%2" ).arg( statusDesc, formattedTime );
455  }
456  case QgsTask::Complete:
457  return tr( "Complete" );
458  case QgsTask::Terminated:
459  return tr( "Terminated" );
460  }
461  }
462  }
463  // no warnings
464  return QString();
465 }
466 
467 
468 //
469 // QgsTaskStatusDelegate
470 //
471 
472 QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
473  : QWidget( parent )
474  , mCanCancel( canCancel )
475  , mStatus( status )
476 {
477  setMouseTracking( true );
478 }
479 
480 QSize QgsTaskStatusWidget::sizeHint() const
481 {
482  return QSize( 32, 32 );
483 }
484 
485 void QgsTaskStatusWidget::setStatus( int status )
486 {
487  mStatus = static_cast< QgsTask::TaskStatus >( status );
488  update();
489 }
490 
491 void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
492 {
493  QWidget::paintEvent( e );
494 
495  QIcon icon;
496  if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
497  {
498  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
499  }
500  else
501  {
502  switch ( mStatus )
503  {
504  case QgsTask::Queued:
505  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
506  break;
507  case QgsTask::OnHold:
508  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
509  break;
510  case QgsTask::Running:
511  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
512  break;
513  case QgsTask::Complete:
514  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
515  break;
516  case QgsTask::Terminated:
517  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
518  break;
519  }
520  }
521 
522  QPainter p( this );
523  icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
524  p.end();
525 }
526 
527 void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
528 {
529  if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
530  emit cancelClicked();
531 }
532 
533 void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
534 {
535  if ( !mInside )
536  {
537  mInside = true;
538  update();
539  }
540 }
541 
542 void QgsTaskStatusWidget::leaveEvent( QEvent * )
543 {
544  mInside = false;
545  update();
546 }
547 
548 
549 /*
550 bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
551 {
552  Q_UNUSED( option )
553  if ( event->type() == QEvent::MouseButtonPress )
554  {
555  QMouseEvent *e = static_cast<QMouseEvent*>( event );
556  if ( e->button() == Qt::LeftButton )
557  {
558  if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
559  {
560  //item not editable
561  return false;
562  }
563 
564  return model->setData( index, true, Qt::EditRole );
565  }
566  }
567  return false;
568 }
569 */
570 
571 QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
572  : QgsFloatingWidget( parent )
573 {
574  setLayout( new QVBoxLayout() );
575  QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
576 
577  const int minWidth = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR );
578  const int minHeight = static_cast< int >( fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR );
579  setMinimumSize( minWidth, minHeight );
580  layout()->addWidget( w );
581  setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
582  "border-top-right-radius: 8px; background-color: rgba(0, 0, 0, 70%); }" );
583 }
584 
585 
586 QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
587  : QToolButton( parent )
588  , mManager( manager )
589 {
590  setAutoRaise( true );
591  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
592  setLayout( new QVBoxLayout() );
593 
594  mProgressBar = new QProgressBar();
595  mProgressBar->setMinimum( 0 );
596  mProgressBar->setMaximum( 0 );
597  layout()->setContentsMargins( 5, 5, 5, 5 );
598  layout()->addWidget( mProgressBar );
599 
600  mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
601  mFloatingWidget->setAnchorWidget( this );
602  mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
603  mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
604  mFloatingWidget->hide();
605  connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
606  hide();
607 
608  connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
609  connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
610  connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
611  connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
612 
613  if ( manager->countActiveTasks() )
614  showButton();
615 }
616 
617 QSize QgsTaskManagerStatusBarWidget::sizeHint() const
618 {
619  const int width = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 20 * Qgis::UI_SCALE_FACTOR );
620  const int height = QToolButton::sizeHint().height();
621  return QSize( width, height );
622 }
623 
624 void QgsTaskManagerStatusBarWidget::changeEvent( QEvent *event )
625 {
626  QToolButton::changeEvent( event );
627 
628  if ( event->type() == QEvent::FontChange )
629  {
630  mProgressBar->setFont( font() );
631  }
632 }
633 
634 void QgsTaskManagerStatusBarWidget::toggleDisplay()
635 {
636  if ( mFloatingWidget->isVisible() )
637  mFloatingWidget->hide();
638  else
639  {
640  mFloatingWidget->show();
641  mFloatingWidget->raise();
642  }
643 }
644 
645 void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
646 {
647  mProgressBar->setValue( static_cast< int >( std::round( progress ) ) );
648  if ( qgsDoubleNear( progress, 0.0 ) )
649  mProgressBar->setMaximum( 0 );
650  else if ( mProgressBar->maximum() == 0 )
651  mProgressBar->setMaximum( 100 );
652  setToolTip( QgsTaskManagerModel::createTooltip( mManager->activeTasks().at( 0 ), QgsTaskManagerModel::ToolTipDescription ) );
653 }
654 
655 void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
656 {
657  if ( count > 1 )
658  {
659  mProgressBar->setMaximum( 0 );
660  setToolTip( tr( "%n active task(s) running", nullptr, count ) );
661  }
662 }
663 
664 void QgsTaskManagerStatusBarWidget::allFinished()
665 {
666  mFloatingWidget->hide();
667  hide();
668 
669  mProgressBar->setMaximum( 0 );
670  mProgressBar->setValue( 0 );
671 }
672 
673 void QgsTaskManagerStatusBarWidget::showButton()
674 {
675  if ( !isVisible() )
676  {
677  mProgressBar->setMaximum( 0 );
678  mProgressBar->setValue( 0 );
679  show();
680  }
681 }
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2039
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
A QWidget subclass for creating widgets which float outside of the normal Qt layout system.
@ BottomMiddle
Bottom center of widget.
@ TopMiddle
Top center of widget.
A widget which displays tasks from a QgsTaskManager and allows for interaction with the manager.
QgsTaskManagerWidget(QgsTaskManager *manager, QWidget *parent=nullptr)
Constructor for QgsTaskManagerWidget.
Task manager for managing a set of long-running QgsTask tasks.
QList< QgsTask * > tasks() const
Returns all tasks tracked by the manager.
void finalTaskProgressChanged(double progress)
Will be emitted when only a single task remains to complete and that task has reported a progress cha...
void statusChanged(long taskId, int status)
Will be emitted when a task reports a status change.
void taskAdded(long taskId)
Emitted when a new task has been added to the manager.
long taskId(QgsTask *task) const
Returns the unique task ID corresponding to a task managed by the class.
void allTasksFinished()
Emitted when all tasks are complete.
void progressChanged(long taskId, double progress)
Will be emitted when a task reports a progress change.
int countActiveTasks(bool includeHidden=true) const
Returns the number of active (queued or running) tasks.
void triggerTask(QgsTask *task)
Triggers a task, e.g.
void countActiveTasksChanged(int count)
Emitted when the number of active tasks changes.
Abstract base class for long running background tasks.
TaskStatus status() const
Returns the current task status.
double progress() const
Returns the task's progress (between 0.0 and 100.0)
void progressChanged(double progress)
Will be emitted by task when its progress changes.
virtual void cancel()
Notifies the task that it should terminate.
void statusChanged(int status)
Will be emitted by task when its status changes.
qint64 elapsedTime() const
Returns the elapsed time since the task commenced, in milliseconds.
TaskStatus
Status of tasks.
@ Terminated
Task was terminated or errored.
@ Queued
Task is queued and has not begun.
@ OnHold
Task is queued but on hold and will not be started.
@ Running
Task is currently running.
@ Complete
Task successfully completed.
QString description() const
Returns the task's description.
bool canCancel() const
Returns true if the task can be canceled.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2260