QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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
19
20#include "qgsapplication.h"
21#include "qgstaskmanager.h"
22
23#include <QAction>
24#include <QHeaderView>
25#include <QLayout>
26#include <QMouseEvent>
27#include <QPainter>
28#include <QProgressBar>
29#include <QToolBar>
30#include <QTreeView>
31
32#include "moc_qgstaskmanagerwidget.cpp"
33
34//
35// QgsTaskManagerWidget
36//
37
39 : QWidget( parent )
40 , mManager( manager )
41{
42 Q_ASSERT( manager );
43
44 QVBoxLayout *vLayout = new QVBoxLayout();
45 vLayout->setContentsMargins( 0, 0, 0, 0 );
46 mTreeView = new QTreeView();
47 mModel = new QgsTaskManagerModel( manager, this );
48 mTreeView->setModel( mModel );
49 connect( mModel, &QgsTaskManagerModel::rowsInserted, this, &QgsTaskManagerWidget::modelRowsInserted );
50 mTreeView->setHeaderHidden( true );
51 mTreeView->setRootIsDecorated( false );
52 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
53
54 const int progressColWidth = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR );
55 mTreeView->setColumnWidth( QgsTaskManagerModel::Progress, progressColWidth );
56
57 const int statusColWidth = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 2 * Qgis::UI_SCALE_FACTOR );
58 mTreeView->setColumnWidth( QgsTaskManagerModel::Status, statusColWidth );
59 mTreeView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
60 mTreeView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
61 mTreeView->header()->setStretchLastSection( false );
62 mTreeView->header()->setSectionResizeMode( QgsTaskManagerModel::Description, QHeaderView::Stretch );
63
64 connect( mTreeView, &QTreeView::clicked, this, &QgsTaskManagerWidget::clicked );
65
66 vLayout->addWidget( mTreeView );
67
68 setLayout( vLayout );
69}
70
75
76
77void QgsTaskManagerWidget::modelRowsInserted( const QModelIndex &, int start, int end )
78{
79 for ( int row = start; row <= end; ++row )
80 {
81 QgsTask *task = mModel->indexToTask( mModel->index( row, 1 ) );
82 if ( !task )
83 continue;
84
85 QProgressBar *progressBar = new QProgressBar();
86 progressBar->setAutoFillBackground( true );
87 progressBar->setRange( 0, 0 );
88 connect( task, &QgsTask::progressChanged, progressBar, [progressBar]( double progress ) {
89 //until first progress report, we show a progress bar of interderminant length
90 if ( progress > 0 )
91 {
92 progressBar->setMaximum( 100 );
93 progressBar->setValue( static_cast<int>( progress ) );
94 }
95 else
96 progressBar->setMaximum( 0 );
97 } );
98 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
99
100 QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
101 statusWidget->setAutoFillBackground( true );
102 connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
103 connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
104 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
105 }
106}
107
108void QgsTaskManagerWidget::clicked( const QModelIndex &index )
109{
110 QgsTask *task = mModel->indexToTask( index );
111 if ( !task )
112 return;
113
114 mManager->triggerTask( task );
115}
116
118//
119// QgsTaskManagerModel
120//
121
122QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
123 : QAbstractItemModel( parent )
124 , mManager( manager )
125{
126 Q_ASSERT( mManager );
127
128 //populate row to id map
129 const auto constTasks = mManager->tasks();
130 for ( QgsTask *task : constTasks )
131 {
132 mRowToTaskIdList << mManager->taskId( task );
133 }
134
135 connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
136 connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
137 connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
138}
139
140QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
141{
142 if ( column < 0 || column >= columnCount() )
143 {
144 //column out of bounds
145 return QModelIndex();
146 }
147
148 if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
149 {
150 //return an index for the task at this position
151 return createIndex( row, column );
152 }
153
154 //only top level supported
155 return QModelIndex();
156}
157
158QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
159{
160 Q_UNUSED( index )
161
162 //all items are top level
163 return QModelIndex();
164}
165
166int QgsTaskManagerModel::rowCount( const QModelIndex &parent ) const
167{
168 if ( !parent.isValid() )
169 {
170 return mRowToTaskIdList.count();
171 }
172 else
173 {
174 //no children
175 return 0;
176 }
177}
178
179int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
180{
181 Q_UNUSED( parent )
182 return 3;
183}
184
185QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
186{
187 if ( !index.isValid() )
188 return QVariant();
189
190 QgsTask *task = indexToTask( index );
191 if ( task )
192 {
193 switch ( role )
194 {
195 case Qt::DisplayRole:
196 case Qt::EditRole:
197 switch ( index.column() )
198 {
199 case Description:
200 return task->description();
201 case Progress:
202 return task->progress();
203 case Status:
204 // delegate shows status
205 return QVariant();
206 default:
207 return QVariant();
208 }
209
210 case static_cast<int>( CustomRole::Status ):
211 return static_cast<int>( task->status() );
212
213 case Qt::ToolTipRole:
214 switch ( index.column() )
215 {
216 case Description:
217 return createTooltip( task, ToolTipDescription );
218 case Progress:
219 return createTooltip( task, ToolTipProgress );
220 case Status:
221 return createTooltip( task, ToolTipStatus );
222 default:
223 return QVariant();
224 }
225
226
227 default:
228 return QVariant();
229 }
230 }
231
232 return QVariant();
233}
234
235Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
236{
237 Qt::ItemFlags flags = QAbstractItemModel::flags( index );
238
239 if ( !index.isValid() )
240 {
241 return flags;
242 }
243
244 QgsTask *task = indexToTask( index );
245 if ( index.column() == Status )
246 {
247 if ( task && task->canCancel() )
248 flags = flags | Qt::ItemIsEditable;
249 }
250 return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
251}
252
253bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
254{
255 Q_UNUSED( role )
256
257 if ( !index.isValid() )
258 return false;
259
260 QgsTask *task = indexToTask( index );
261 if ( !task )
262 return false;
263
264 switch ( index.column() )
265 {
266 case Status:
267 {
268 if ( value.toBool() && task->canCancel() )
269 task->cancel();
270 return true;
271 }
272
273 default:
274 return false;
275 }
276}
277
278void QgsTaskManagerModel::taskAdded( long id )
279{
280 beginInsertRows( QModelIndex(), mRowToTaskIdList.count(), mRowToTaskIdList.count() );
281 mRowToTaskIdList << id;
282 endInsertRows();
283}
284
285void 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
299void 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
312void 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
330QgsTask *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
342int 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
354QModelIndex 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
363QString 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 }
389 return tr( "Complete" );
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 }
457 return tr( "Complete" );
459 return tr( "Terminated" );
460 }
461 }
462 }
463 // no warnings
464 return QString();
465}
466
467
468//
469// QgsTaskStatusDelegate
470//
471
472QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
473 : QWidget( parent )
474 , mCanCancel( canCancel )
475 , mStatus( status )
476{
477 setMouseTracking( true );
478}
479
480QSize QgsTaskStatusWidget::sizeHint() const
481{
482 return QSize( 32, 32 );
483}
484
485void QgsTaskStatusWidget::setStatus( int status )
486{
487 mStatus = static_cast<QgsTask::TaskStatus>( status );
488 update();
489}
490
491void 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;
514 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
515 break;
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
527void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
528{
529 if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
530 emit cancelClicked();
531}
532
533void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
534{
535 if ( !mInside )
536 {
537 mInside = true;
538 update();
539 }
540}
541
542void QgsTaskStatusWidget::leaveEvent( QEvent * )
543{
544 mInside = false;
545 update();
546}
547
548
549/*
550bool 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
571QgsTaskManagerFloatingWidget::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
586QgsTaskManagerStatusBarWidget::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
617QSize 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
624void QgsTaskManagerStatusBarWidget::changeEvent( QEvent *event )
625{
626 QToolButton::changeEvent( event );
627
628 if ( event->type() == QEvent::FontChange )
629 {
630 mProgressBar->setFont( font() );
631 }
632}
633
634void QgsTaskManagerStatusBarWidget::toggleDisplay()
635{
636 if ( mFloatingWidget->isVisible() )
637 mFloatingWidget->hide();
638 else
639 {
640 mFloatingWidget->show();
641 mFloatingWidget->raise();
642 }
643}
644
645void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
646{
647 mProgressBar->setValue( static_cast<int>( 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
655void 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
664void QgsTaskManagerStatusBarWidget::allFinished()
665{
666 mFloatingWidget->hide();
667 hide();
668
669 mProgressBar->setMaximum( 0 );
670 mProgressBar->setValue( 0 );
671}
672
673void 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:6222
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.
QgsTaskManagerWidget(QgsTaskManager *manager, QWidget *parent=nullptr)
Constructor for QgsTaskManagerWidget.
Task manager for managing a set of long-running QgsTask tasks.
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.
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 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:6607