QGIS API Documentation  3.2.0-Bonn (bc43194)
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 = fontMetrics().width( "X" ) * 10 * Qgis::UI_SCALE_FACTOR;
50  mTreeView->setColumnWidth( QgsTaskManagerModel::Progress, progressColWidth );
51  int statusColWidth = 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( 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 task->description();
214  case Progress:
215  case Status:
216  {
217  switch ( task->status() )
218  {
219  case QgsTask::Queued:
220  return tr( "Queued" );
221  case QgsTask::OnHold:
222  return tr( "On hold" );
223  case QgsTask::Running:
224  {
225  if ( index.column() == Status && !task->canCancel() )
226  return tr( "Running (cannot cancel)" );
227  else
228  return tr( "Running" );
229  }
230  case QgsTask::Complete:
231  return tr( "Complete" );
232  case QgsTask::Terminated:
233  return tr( "Terminated" );
234  }
235  return QVariant();
236  }
237  default:
238  return QVariant();
239  }
240 
241 
242  default:
243  return QVariant();
244  }
245  }
246 
247  return QVariant();
248 }
249 
250 Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
251 {
252  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
253 
254  if ( ! index.isValid() )
255  {
256  return flags;
257  }
258 
259  QgsTask *task = indexToTask( index );
260  if ( index.column() == Status )
261  {
262  if ( task && task->canCancel() )
263  flags = flags | Qt::ItemIsEditable;
264  }
265  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
266 }
267 
268 bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
269 {
270  Q_UNUSED( role );
271 
272  if ( !index.isValid() )
273  return false;
274 
275  QgsTask *task = indexToTask( index );
276  if ( !task )
277  return false;
278 
279  switch ( index.column() )
280  {
281  case Status:
282  {
283  if ( value.toBool() && task->canCancel() )
284  task->cancel();
285  return true;
286  }
287 
288  default:
289  return false;
290  }
291 }
292 
293 void QgsTaskManagerModel::taskAdded( long id )
294 {
295  beginInsertRows( QModelIndex(), mRowToTaskIdList.count(),
296  mRowToTaskIdList.count() );
297  mRowToTaskIdList << id;
298  endInsertRows();
299 }
300 
301 void QgsTaskManagerModel::taskDeleted( long id )
302 {
303  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
304  {
305  if ( mRowToTaskIdList.at( row ) == id )
306  {
307  beginRemoveRows( QModelIndex(), row, row );
308  mRowToTaskIdList.removeAt( row );
309  endRemoveRows();
310  return;
311  }
312  }
313 }
314 
315 void QgsTaskManagerModel::progressChanged( long id, double progress )
316 {
317  Q_UNUSED( progress );
318 
319  QModelIndex index = idToIndex( id, Progress );
320  if ( !index.isValid() )
321  {
322  return;
323  }
324 
325  emit dataChanged( index, index );
326 }
327 
328 void QgsTaskManagerModel::statusChanged( long id, int status )
329 {
330  if ( status == QgsTask::Complete || status == QgsTask::Terminated )
331  {
332  taskDeleted( id );
333  }
334  else
335  {
336  QModelIndex index = idToIndex( id, Status );
337  if ( !index.isValid() )
338  {
339  return;
340  }
341 
342  emit dataChanged( index, index );
343  }
344 }
345 
346 QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
347 {
348  if ( !index.isValid() || index.parent().isValid() )
349  return nullptr;
350 
351  long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
352  if ( id >= 0 )
353  return mManager->task( id );
354  else
355  return nullptr;
356 }
357 
358 int QgsTaskManagerModel::idToRow( long id ) const
359 {
360  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
361  {
362  if ( mRowToTaskIdList.at( row ) == id )
363  {
364  return row;
365  }
366  }
367  return -1;
368 }
369 
370 QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
371 {
372  int row = idToRow( id );
373  if ( row < 0 )
374  return QModelIndex();
375 
376  return index( row, column );
377 }
378 
379 
380 //
381 // QgsTaskStatusDelegate
382 //
383 
384 QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
385  : QWidget( parent )
386  , mCanCancel( canCancel )
387  , mStatus( status )
388 {
389  setMouseTracking( true );
390 }
391 
392 QSize QgsTaskStatusWidget::sizeHint() const
393 {
394  return QSize( 32, 32 );
395 }
396 
397 void QgsTaskStatusWidget::setStatus( int status )
398 {
399  mStatus = static_cast< QgsTask::TaskStatus >( status );
400  update();
401 }
402 
403 void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
404 {
405  QWidget::paintEvent( e );
406 
407  QIcon icon;
408  if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
409  {
410  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
411  }
412  else
413  {
414  switch ( mStatus )
415  {
416  case QgsTask::Queued:
417  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
418  break;
419  case QgsTask::OnHold:
420  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
421  break;
422  case QgsTask::Running:
423  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
424  break;
425  case QgsTask::Complete:
426  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
427  break;
428  case QgsTask::Terminated:
429  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
430  break;
431  }
432  }
433 
434  QPainter p( this );
435  icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
436  p.end();
437 }
438 
439 void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
440 {
441  if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
442  emit cancelClicked();
443 }
444 
445 void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
446 {
447  if ( !mInside )
448  {
449  mInside = true;
450  update();
451  }
452 }
453 
454 void QgsTaskStatusWidget::leaveEvent( QEvent * )
455 {
456  mInside = false;
457  update();
458 }
459 
460 
461 /*
462 bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
463 {
464  Q_UNUSED( option );
465  if ( event->type() == QEvent::MouseButtonPress )
466  {
467  QMouseEvent *e = static_cast<QMouseEvent*>( event );
468  if ( e->button() == Qt::LeftButton )
469  {
470  if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
471  {
472  //item not editable
473  return false;
474  }
475 
476  return model->setData( index, true, Qt::EditRole );
477  }
478  }
479  return false;
480 }
481 */
482 
483 QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
484  : QgsFloatingWidget( parent )
485 {
486  setLayout( new QVBoxLayout() );
487  QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
488  int minWidth = fontMetrics().width( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR;
489  int minHeight = fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR;
490  setMinimumSize( minWidth, minHeight );
491  layout()->addWidget( w );
492  setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
493  "border-top-right-radius: 8px; background-color: rgb(0, 0, 0, 70%); }" );
494 }
495 
496 
497 QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
498  : QToolButton( parent )
499  , mManager( manager )
500 {
501  setAutoRaise( true );
502  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
503  setLayout( new QVBoxLayout() );
504 
505  mProgressBar = new QProgressBar();
506  mProgressBar->setMinimum( 0 );
507  mProgressBar->setMaximum( 0 );
508  layout()->setContentsMargins( 5, 5, 5, 5 );
509  layout()->addWidget( mProgressBar );
510 
511  mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
512  mFloatingWidget->setAnchorWidget( this );
513  mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
514  mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
515  mFloatingWidget->hide();
516  connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
517  hide();
518 
519  connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
520  connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
521  connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
522  connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
523 }
524 
525 QSize QgsTaskManagerStatusBarWidget::sizeHint() const
526 {
527  int width = fontMetrics().width( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR;
528  int height = QToolButton::sizeHint().height();
529  return QSize( width, height );
530 }
531 
532 void QgsTaskManagerStatusBarWidget::toggleDisplay()
533 {
534  if ( mFloatingWidget->isVisible() )
535  mFloatingWidget->hide();
536  else
537  {
538  mFloatingWidget->show();
539  mFloatingWidget->raise();
540  }
541 }
542 
543 void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
544 {
545  mProgressBar->setValue( progress );
546  if ( qgsDoubleNear( progress, 0.0 ) )
547  mProgressBar->setMaximum( 0 );
548  else if ( mProgressBar->maximum() == 0 )
549  mProgressBar->setMaximum( 100 );
550  setToolTip( mManager->activeTasks().at( 0 )->description() );
551 }
552 
553 void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
554 {
555  if ( count > 1 )
556  {
557  mProgressBar->setMaximum( 0 );
558  setToolTip( tr( "%1 active tasks running" ).arg( count ) );
559  }
560 }
561 
562 void QgsTaskManagerStatusBarWidget::allFinished()
563 {
564  mFloatingWidget->hide();
565  hide();
566 
567  mProgressBar->setMaximum( 0 );
568  mProgressBar->setValue( 0 );
569 }
570 
571 void QgsTaskManagerStatusBarWidget::showButton()
572 {
573  if ( !isVisible() )
574  {
575  mProgressBar->setMaximum( 100 );
576  mProgressBar->setValue( 0 );
577  show();
578  }
579 }
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:151
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:251
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...