QGIS API Documentation  3.0.2-Girona (307d082)
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  connect( task, &QgsTask::progressChanged, progressBar, [progressBar]( double progress )
82  {
83  //until first progress report, we show a progress bar of interderminant length
84  if ( progress > 0 )
85  {
86  progressBar->setMaximum( 100 );
87  progressBar->setValue( progress );
88  }
89  else
90  progressBar->setMaximum( 0 );
91  }
92  );
93  mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
94 
95  QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
96  statusWidget->setAutoFillBackground( true );
97  connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
98  connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
99  mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
100  }
101 }
102 
103 void QgsTaskManagerWidget::clicked( const QModelIndex &index )
104 {
105  QgsTask *task = mModel->indexToTask( index );
106  if ( !task )
107  return;
108 
109  mManager->triggerTask( task );
110 }
111 
113 //
114 // QgsTaskManagerModel
115 //
116 
117 QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
118  : QAbstractItemModel( parent )
119  , mManager( manager )
120 {
121  Q_ASSERT( mManager );
122 
123  //populate row to id map
124  Q_FOREACH ( QgsTask *task, mManager->tasks() )
125  {
126  mRowToTaskIdList << mManager->taskId( task );
127  }
128 
129  connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
130  connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
131  connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
132 }
133 
134 QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
135 {
136  if ( column < 0 || column >= columnCount() )
137  {
138  //column out of bounds
139  return QModelIndex();
140  }
141 
142  if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
143  {
144  //return an index for the task at this position
145  return createIndex( row, column );
146  }
147 
148  //only top level supported
149  return QModelIndex();
150 
151 }
152 
153 QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
154 {
155  Q_UNUSED( index );
156 
157  //all items are top level
158  return QModelIndex();
159 }
160 
161 int QgsTaskManagerModel::rowCount( const QModelIndex &parent ) const
162 {
163  if ( !parent.isValid() )
164  {
165  return mRowToTaskIdList.count();
166  }
167  else
168  {
169  //no children
170  return 0;
171  }
172 }
173 
174 int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
175 {
176  Q_UNUSED( parent );
177  return 3;
178 }
179 
180 QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
181 {
182  if ( !index.isValid() )
183  return QVariant();
184 
185  QgsTask *task = indexToTask( index );
186  if ( task )
187  {
188  switch ( role )
189  {
190  case Qt::DisplayRole:
191  case Qt::EditRole:
192  switch ( index.column() )
193  {
194  case Description:
195  return task->description();
196  case Progress:
197  return task->progress();
198  case Status:
199  // delegate shows status
200  return QVariant();
201  default:
202  return QVariant();
203  }
204 
205  case StatusRole:
206  return static_cast<int>( task->status() );
207 
208  case Qt::ToolTipRole:
209  switch ( index.column() )
210  {
211  case Description:
212  return task->description();
213  case Progress:
214  case Status:
215  {
216  switch ( task->status() )
217  {
218  case QgsTask::Queued:
219  return tr( "Queued" );
220  case QgsTask::OnHold:
221  return tr( "On hold" );
222  case QgsTask::Running:
223  {
224  if ( index.column() == Status && !task->canCancel() )
225  return tr( "Running (cannot cancel)" );
226  else
227  return tr( "Running" );
228  }
229  case QgsTask::Complete:
230  return tr( "Complete" );
231  case QgsTask::Terminated:
232  return tr( "Terminated" );
233  }
234  return QVariant();
235  }
236  default:
237  return QVariant();
238  }
239 
240 
241  default:
242  return QVariant();
243  }
244  }
245 
246  return QVariant();
247 }
248 
249 Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
250 {
251  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
252 
253  if ( ! index.isValid() )
254  {
255  return flags;
256  }
257 
258  QgsTask *task = indexToTask( index );
259  if ( index.column() == Status )
260  {
261  if ( task && task->canCancel() )
262  flags = flags | Qt::ItemIsEditable;
263  }
264  return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
265 }
266 
267 bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
268 {
269  Q_UNUSED( role );
270 
271  if ( !index.isValid() )
272  return false;
273 
274  QgsTask *task = indexToTask( index );
275  if ( !task )
276  return false;
277 
278  switch ( index.column() )
279  {
280  case Status:
281  {
282  if ( value.toBool() && task->canCancel() )
283  task->cancel();
284  return true;
285  }
286 
287  default:
288  return false;
289  }
290 }
291 
292 void QgsTaskManagerModel::taskAdded( long id )
293 {
294  beginInsertRows( QModelIndex(), mRowToTaskIdList.count(),
295  mRowToTaskIdList.count() );
296  mRowToTaskIdList << id;
297  endInsertRows();
298 }
299 
300 void QgsTaskManagerModel::taskDeleted( long id )
301 {
302  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
303  {
304  if ( mRowToTaskIdList.at( row ) == id )
305  {
306  beginRemoveRows( QModelIndex(), row, row );
307  mRowToTaskIdList.removeAt( row );
308  endRemoveRows();
309  return;
310  }
311  }
312 }
313 
314 void QgsTaskManagerModel::progressChanged( long id, double progress )
315 {
316  Q_UNUSED( progress );
317 
318  QModelIndex index = idToIndex( id, Progress );
319  if ( !index.isValid() )
320  {
321  return;
322  }
323 
324  emit dataChanged( index, index );
325 }
326 
327 void QgsTaskManagerModel::statusChanged( long id, int status )
328 {
329  if ( status == QgsTask::Complete || status == QgsTask::Terminated )
330  {
331  taskDeleted( id );
332  }
333  else
334  {
335  QModelIndex index = idToIndex( id, Status );
336  if ( !index.isValid() )
337  {
338  return;
339  }
340 
341  emit dataChanged( index, index );
342  }
343 }
344 
345 QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
346 {
347  if ( !index.isValid() || index.parent().isValid() )
348  return nullptr;
349 
350  long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
351  if ( id >= 0 )
352  return mManager->task( id );
353  else
354  return nullptr;
355 }
356 
357 int QgsTaskManagerModel::idToRow( long id ) const
358 {
359  for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
360  {
361  if ( mRowToTaskIdList.at( row ) == id )
362  {
363  return row;
364  }
365  }
366  return -1;
367 }
368 
369 QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
370 {
371  int row = idToRow( id );
372  if ( row < 0 )
373  return QModelIndex();
374 
375  return index( row, column );
376 }
377 
378 
379 //
380 // QgsTaskStatusDelegate
381 //
382 
383 QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
384  : QWidget( parent )
385  , mCanCancel( canCancel )
386  , mStatus( status )
387 {
388  setMouseTracking( true );
389 }
390 
391 QSize QgsTaskStatusWidget::sizeHint() const
392 {
393  return QSize( 32, 32 );
394 }
395 
396 void QgsTaskStatusWidget::setStatus( int status )
397 {
398  mStatus = static_cast< QgsTask::TaskStatus >( status );
399  update();
400 }
401 
402 void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
403 {
404  QWidget::paintEvent( e );
405 
406  QIcon icon;
407  if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
408  {
409  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
410  }
411  else
412  {
413  switch ( mStatus )
414  {
415  case QgsTask::Queued:
416  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
417  break;
418  case QgsTask::OnHold:
419  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
420  break;
421  case QgsTask::Running:
422  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
423  break;
424  case QgsTask::Complete:
425  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
426  break;
427  case QgsTask::Terminated:
428  icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
429  break;
430  }
431  }
432 
433  QPainter p( this );
434  icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
435  p.end();
436 }
437 
438 void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
439 {
440  if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
441  emit cancelClicked();
442 }
443 
444 void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
445 {
446  if ( !mInside )
447  {
448  mInside = true;
449  update();
450  }
451 }
452 
453 void QgsTaskStatusWidget::leaveEvent( QEvent * )
454 {
455  mInside = false;
456  update();
457 }
458 
459 
460 /*
461 bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
462 {
463  Q_UNUSED( option );
464  if ( event->type() == QEvent::MouseButtonPress )
465  {
466  QMouseEvent *e = static_cast<QMouseEvent*>( event );
467  if ( e->button() == Qt::LeftButton )
468  {
469  if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
470  {
471  //item not editable
472  return false;
473  }
474 
475  return model->setData( index, true, Qt::EditRole );
476  }
477  }
478  return false;
479 }
480 */
481 
482 QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
483  : QgsFloatingWidget( parent )
484 {
485  setLayout( new QVBoxLayout() );
486  QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
487  int minWidth = fontMetrics().width( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR;
488  int minHeight = fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR;
489  setMinimumSize( minWidth, minHeight );
490  layout()->addWidget( w );
491  setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
492  "border-top-right-radius: 8px; background-color: rgb(0, 0, 0, 70%); }" );
493 }
494 
495 
496 QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
497  : QToolButton( parent )
498  , mManager( manager )
499 {
500  setAutoRaise( true );
501  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
502  setLayout( new QVBoxLayout() );
503 
504  mProgressBar = new QProgressBar();
505  mProgressBar->setMinimum( 0 );
506  mProgressBar->setMaximum( 0 );
507  layout()->setContentsMargins( 5, 5, 5, 5 );
508  layout()->addWidget( mProgressBar );
509 
510  mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
511  mFloatingWidget->setAnchorWidget( this );
512  mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
513  mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
514  mFloatingWidget->hide();
515  connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
516  hide();
517 
518  connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
519  connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
520  connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
521  connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
522 }
523 
524 QSize QgsTaskManagerStatusBarWidget::sizeHint() const
525 {
526  int width = fontMetrics().width( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR;
527  int height = QToolButton::sizeHint().height();
528  return QSize( width, height );
529 }
530 
531 void QgsTaskManagerStatusBarWidget::toggleDisplay()
532 {
533  if ( mFloatingWidget->isVisible() )
534  mFloatingWidget->hide();
535  else
536  {
537  mFloatingWidget->show();
538  mFloatingWidget->raise();
539  }
540 }
541 
542 void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
543 {
544  mProgressBar->setValue( progress );
545  if ( qgsDoubleNear( progress, 0.0 ) )
546  mProgressBar->setMaximum( 0 );
547  else if ( mProgressBar->maximum() == 0 )
548  mProgressBar->setMaximum( 100 );
549  setToolTip( mManager->activeTasks().at( 0 )->description() );
550 }
551 
552 void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
553 {
554  if ( count > 1 )
555  {
556  mProgressBar->setMaximum( 0 );
557  setToolTip( tr( "%1 active tasks running" ).arg( count ) );
558  }
559 }
560 
561 void QgsTaskManagerStatusBarWidget::allFinished()
562 {
563  mFloatingWidget->hide();
564  hide();
565 
566  mProgressBar->setMaximum( 0 );
567  mProgressBar->setValue( 0 );
568 }
569 
570 void QgsTaskManagerStatusBarWidget::showButton()
571 {
572  if ( !isVisible() )
573  {
574  mProgressBar->setMaximum( 100 );
575  mProgressBar->setValue( 0 );
576  show();
577  }
578 }
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.
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.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
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...