QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  const auto constTasks = mManager->tasks();
126  for ( QgsTask *task : constTasks )
127  {
128  mRowToTaskIdList << mManager->taskId( task );
129  }
130 
131  connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
132  connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
133  connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
134 }
135 
136 QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
137 {
138  if ( column < 0 || column >= columnCount() )
139  {
140  //column out of bounds
141  return QModelIndex();
142  }
143 
144  if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
145  {
146  //return an index for the task at this position
147  return createIndex( row, column );
148  }
149 
150  //only top level supported
151  return QModelIndex();
152 
153 }
154 
155 QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
156 {
157  Q_UNUSED( index )
158 
159  //all items are top level
160  return QModelIndex();
161 }
162 
163 int QgsTaskManagerModel::rowCount( const QModelIndex &parent ) const
164 {
165  if ( !parent.isValid() )
166  {
167  return mRowToTaskIdList.count();
168  }
169  else
170  {
171  //no children
172  return 0;
173  }
174 }
175 
176 int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
177 {
178  Q_UNUSED( parent )
179  return 3;
180 }
181 
182 QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
183 {
184  if ( !index.isValid() )
185  return QVariant();
186 
187  QgsTask *task = indexToTask( index );
188  if ( task )
189  {
190  switch ( role )
191  {
192  case Qt::DisplayRole:
193  case Qt::EditRole:
194  switch ( index.column() )
195  {
196  case Description:
197  return task->description();
198  case Progress:
199  return task->progress();
200  case Status:
201  // delegate shows status
202  return QVariant();
203  default:
204  return QVariant();
205  }
206 
207  case StatusRole:
208  return static_cast<int>( task->status() );
209 
210  case Qt::ToolTipRole:
211  switch ( index.column() )
212  {
213  case Description:
214  return createTooltip( task, ToolTipDescription );
215  case Progress:
216  return createTooltip( task, ToolTipProgress );
217  case Status:
218  return createTooltip( task, ToolTipStatus );
219  default:
220  return QVariant();
221  }
222 
223 
224  default:
225  return QVariant();
226  }
227  }
228 
229  return QVariant();
230 }
231 
232 Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
233 {
234  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
235 
236  if ( ! index.isValid() )
237  {
238  return flags;
239  }
240 
241  QgsTask *task = indexToTask( index );
242  if ( index.column() == Status )
243  {
244  if ( task && task->canCancel() )
245  flags = flags | Qt::ItemIsEditable;
246  }
247  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
248 }
249 
250 bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
251 {
252  Q_UNUSED( role )
253 
254  if ( !index.isValid() )
255  return false;
256 
257  QgsTask *task = indexToTask( index );
258  if ( !task )
259  return false;
260 
261  switch ( index.column() )
262  {
263  case Status:
264  {
265  if ( value.toBool() && task->canCancel() )
266  task->cancel();
267  return true;
268  }
269 
270  default:
271  return false;
272  }
273 }
274 
275 void QgsTaskManagerModel::taskAdded( long id )
276 {
277  beginInsertRows( QModelIndex(), mRowToTaskIdList.count(),
278  mRowToTaskIdList.count() );
279  mRowToTaskIdList << id;
280  endInsertRows();
281 }
282 
283 void QgsTaskManagerModel::taskDeleted( long id )
284 {
285  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
286  {
287  if ( mRowToTaskIdList.at( row ) == id )
288  {
289  beginRemoveRows( QModelIndex(), row, row );
290  mRowToTaskIdList.removeAt( row );
291  endRemoveRows();
292  return;
293  }
294  }
295 }
296 
297 void QgsTaskManagerModel::progressChanged( long id, double progress )
298 {
299  Q_UNUSED( progress )
300 
301  QModelIndex index = idToIndex( id, Progress );
302  if ( !index.isValid() )
303  {
304  return;
305  }
306 
307  emit dataChanged( index, index );
308 }
309 
310 void QgsTaskManagerModel::statusChanged( long id, int status )
311 {
312  if ( status == QgsTask::Complete || status == QgsTask::Terminated )
313  {
314  taskDeleted( id );
315  }
316  else
317  {
318  QModelIndex index = idToIndex( id, Status );
319  if ( !index.isValid() )
320  {
321  return;
322  }
323 
324  emit dataChanged( index, index );
325  }
326 }
327 
328 QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
329 {
330  if ( !index.isValid() || index.parent().isValid() )
331  return nullptr;
332 
333  long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
334  if ( id >= 0 )
335  return mManager->task( id );
336  else
337  return nullptr;
338 }
339 
340 int QgsTaskManagerModel::idToRow( long id ) const
341 {
342  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
343  {
344  if ( mRowToTaskIdList.at( row ) == id )
345  {
346  return row;
347  }
348  }
349  return -1;
350 }
351 
352 QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
353 {
354  int row = idToRow( id );
355  if ( row < 0 )
356  return QModelIndex();
357 
358  return index( row, column );
359 }
360 
361 QString QgsTaskManagerModel::createTooltip( QgsTask *task, ToolTipType type )
362 {
363  if ( task->status() != QgsTask::Running )
364  {
365  switch ( type )
366  {
367  case ToolTipDescription:
368  return task->description();
369 
370  case ToolTipStatus:
371  case ToolTipProgress:
372  {
373  switch ( task->status() )
374  {
375  case QgsTask::Queued:
376  return tr( "Queued" );
377  case QgsTask::OnHold:
378  return tr( "On hold" );
379  case QgsTask::Running:
380  {
381  if ( type == ToolTipStatus && !task->canCancel() )
382  return tr( "Running (cannot cancel)" );
383  else
384  return tr( "Running" );
385  }
386  case QgsTask::Complete:
387  return tr( "Complete" );
388  case QgsTask::Terminated:
389  return tr( "Terminated" );
390  }
391  }
392  }
393  }
394 
395  QString formattedTime;
396 
397  qint64 elapsed = task->elapsedTime();
398 
399  if ( task->progress() > 0 )
400  {
401  // estimate time remaining
402  qint64 msRemain = static_cast< qint64 >( elapsed * 100.0 / task->progress() - elapsed );
403  if ( msRemain > 120 * 1000 )
404  {
405  long long minutes = msRemain / 1000 / 60;
406  int seconds = ( msRemain / 1000 ) % 60;
407  formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
408  }
409  else
410  formattedTime = tr( "%1 seconds" ).arg( msRemain / 1000 );
411 
412  formattedTime = tr( "Estimated time remaining: %1" ).arg( formattedTime );
413 
414  QTime estimatedEnd = QTime::currentTime().addMSecs( msRemain );
415  formattedTime += tr( " (%1)" ).arg( QLocale::system().toString( estimatedEnd, QLocale::ShortFormat ) );
416  }
417  else
418  {
419  if ( elapsed > 120 * 1000 )
420  {
421  long long minutes = elapsed / 1000 / 60;
422  int seconds = ( elapsed / 1000 ) % 60;
423  formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
424  }
425  else
426  formattedTime = tr( "%1 seconds" ).arg( elapsed / 1000 );
427 
428  formattedTime = tr( "Time elapsed: %1" ).arg( formattedTime );
429  }
430 
431  switch ( type )
432  {
433  case ToolTipDescription:
434  return tr( "%1<br>%2" ).arg( task->description(), formattedTime );
435 
436  case ToolTipStatus:
437  case ToolTipProgress:
438  {
439  switch ( task->status() )
440  {
441  case QgsTask::Queued:
442  return tr( "Queued" );
443  case QgsTask::OnHold:
444  return tr( "On hold" );
445  case QgsTask::Running:
446  {
447  QString statusDesc;
448  if ( type == ToolTipStatus && !task->canCancel() )
449  statusDesc = tr( "Running (cannot cancel)" );
450  else
451  statusDesc = tr( "Running" );
452  return tr( "%1<br>%2" ).arg( statusDesc, formattedTime );
453  }
454  case QgsTask::Complete:
455  return tr( "Complete" );
456  case QgsTask::Terminated:
457  return tr( "Terminated" );
458  }
459  }
460  }
461  // no warnings
462  return QString();
463 }
464 
465 
466 //
467 // QgsTaskStatusDelegate
468 //
469 
470 QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
471  : QWidget( parent )
472  , mCanCancel( canCancel )
473  , mStatus( status )
474 {
475  setMouseTracking( true );
476 }
477 
478 QSize QgsTaskStatusWidget::sizeHint() const
479 {
480  return QSize( 32, 32 );
481 }
482 
483 void QgsTaskStatusWidget::setStatus( int status )
484 {
485  mStatus = static_cast< QgsTask::TaskStatus >( status );
486  update();
487 }
488 
489 void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
490 {
491  QWidget::paintEvent( e );
492 
493  QIcon icon;
494  if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
495  {
496  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
497  }
498  else
499  {
500  switch ( mStatus )
501  {
502  case QgsTask::Queued:
503  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
504  break;
505  case QgsTask::OnHold:
506  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
507  break;
508  case QgsTask::Running:
509  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
510  break;
511  case QgsTask::Complete:
512  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
513  break;
514  case QgsTask::Terminated:
515  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
516  break;
517  }
518  }
519 
520  QPainter p( this );
521  icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
522  p.end();
523 }
524 
525 void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
526 {
527  if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
528  emit cancelClicked();
529 }
530 
531 void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
532 {
533  if ( !mInside )
534  {
535  mInside = true;
536  update();
537  }
538 }
539 
540 void QgsTaskStatusWidget::leaveEvent( QEvent * )
541 {
542  mInside = false;
543  update();
544 }
545 
546 
547 /*
548 bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
549 {
550  Q_UNUSED( option )
551  if ( event->type() == QEvent::MouseButtonPress )
552  {
553  QMouseEvent *e = static_cast<QMouseEvent*>( event );
554  if ( e->button() == Qt::LeftButton )
555  {
556  if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
557  {
558  //item not editable
559  return false;
560  }
561 
562  return model->setData( index, true, Qt::EditRole );
563  }
564  }
565  return false;
566 }
567 */
568 
569 QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
570  : QgsFloatingWidget( parent )
571 {
572  setLayout( new QVBoxLayout() );
573  QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
574  int minWidth = static_cast< int >( fontMetrics().width( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR );
575  int minHeight = static_cast< int >( fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR );
576  setMinimumSize( minWidth, minHeight );
577  layout()->addWidget( w );
578  setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
579  "border-top-right-radius: 8px; background-color: rgb(0, 0, 0, 70%); }" );
580 }
581 
582 
583 QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
584  : QToolButton( parent )
585  , mManager( manager )
586 {
587  setAutoRaise( true );
588  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
589  setLayout( new QVBoxLayout() );
590 
591  mProgressBar = new QProgressBar();
592  mProgressBar->setMinimum( 0 );
593  mProgressBar->setMaximum( 0 );
594  layout()->setContentsMargins( 5, 5, 5, 5 );
595  layout()->addWidget( mProgressBar );
596 
597  mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
598  mFloatingWidget->setAnchorWidget( this );
599  mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
600  mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
601  mFloatingWidget->hide();
602  connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
603  hide();
604 
605  connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
606  connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
607  connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
608  connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
609 }
610 
611 QSize QgsTaskManagerStatusBarWidget::sizeHint() const
612 {
613  int width = static_cast< int >( fontMetrics().width( 'X' ) * 20 * Qgis::UI_SCALE_FACTOR );
614  int height = QToolButton::sizeHint().height();
615  return QSize( width, height );
616 }
617 
618 void QgsTaskManagerStatusBarWidget::changeEvent( QEvent *event )
619 {
620  QToolButton::changeEvent( event );
621 
622  if ( event->type() == QEvent::FontChange )
623  {
624  mProgressBar->setFont( font() );
625  }
626 }
627 
628 void QgsTaskManagerStatusBarWidget::toggleDisplay()
629 {
630  if ( mFloatingWidget->isVisible() )
631  mFloatingWidget->hide();
632  else
633  {
634  mFloatingWidget->show();
635  mFloatingWidget->raise();
636  }
637 }
638 
639 void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
640 {
641  mProgressBar->setValue( static_cast< int >( std::round( progress ) ) );
642  if ( qgsDoubleNear( progress, 0.0 ) )
643  mProgressBar->setMaximum( 0 );
644  else if ( mProgressBar->maximum() == 0 )
645  mProgressBar->setMaximum( 100 );
646  setToolTip( QgsTaskManagerModel::createTooltip( mManager->activeTasks().at( 0 ), QgsTaskManagerModel::ToolTipDescription ) );
647 }
648 
649 void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
650 {
651  if ( count > 1 )
652  {
653  mProgressBar->setMaximum( 0 );
654  setToolTip( tr( "%1 active tasks running" ).arg( count ) );
655  }
656 }
657 
658 void QgsTaskManagerStatusBarWidget::allFinished()
659 {
660  mFloatingWidget->hide();
661  hide();
662 
663  mProgressBar->setMaximum( 0 );
664  mProgressBar->setValue( 0 );
665 }
666 
667 void QgsTaskManagerStatusBarWidget::showButton()
668 {
669  if ( !isVisible() )
670  {
671  mProgressBar->setMaximum( 0 );
672  mProgressBar->setValue( 0 );
673  show();
674  }
675 }
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:154
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:280
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...