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