QGIS API Documentation 3.41.0-Master (57ec4277f5e)
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#include "moc_qgstaskmanagerwidget.cpp"
20#include "qgstaskmanager.h"
21#include "qgsapplication.h"
22#include <QPainter>
23#include <QMouseEvent>
24#include <QTreeView>
25#include <QLayout>
26#include <QToolBar>
27#include <QProgressBar>
28#include <QAction>
29#include <QHeaderView>
30
31//
32// QgsTaskManagerWidget
33//
34
36 : QWidget( parent )
37 , mManager( manager )
38{
39 Q_ASSERT( manager );
40
41 QVBoxLayout *vLayout = new QVBoxLayout();
42 vLayout->setContentsMargins( 0, 0, 0, 0 );
43 mTreeView = new QTreeView();
44 mModel = new QgsTaskManagerModel( manager, this );
45 mTreeView->setModel( mModel );
46 connect( mModel, &QgsTaskManagerModel::rowsInserted, this, &QgsTaskManagerWidget::modelRowsInserted );
47 mTreeView->setHeaderHidden( true );
48 mTreeView->setRootIsDecorated( false );
49 mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
50
51 const int progressColWidth = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR );
52 mTreeView->setColumnWidth( QgsTaskManagerModel::Progress, progressColWidth );
53
54 const int statusColWidth = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 2 * Qgis::UI_SCALE_FACTOR );
55 mTreeView->setColumnWidth( QgsTaskManagerModel::Status, statusColWidth );
56 mTreeView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
57 mTreeView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
58 mTreeView->header()->setStretchLastSection( false );
59 mTreeView->header()->setSectionResizeMode( QgsTaskManagerModel::Description, QHeaderView::Stretch );
60
61 connect( mTreeView, &QTreeView::clicked, this, &QgsTaskManagerWidget::clicked );
62
63 vLayout->addWidget( mTreeView );
64
65 setLayout( vLayout );
66}
67
72
73
74void QgsTaskManagerWidget::modelRowsInserted( const QModelIndex &, int start, int end )
75{
76 for ( int row = start; row <= end; ++row )
77 {
78 QgsTask *task = mModel->indexToTask( mModel->index( row, 1 ) );
79 if ( !task )
80 continue;
81
82 QProgressBar *progressBar = new QProgressBar();
83 progressBar->setAutoFillBackground( true );
84 progressBar->setRange( 0, 0 );
85 connect( task, &QgsTask::progressChanged, progressBar, [progressBar]( double progress ) {
86 //until first progress report, we show a progress bar of interderminant length
87 if ( progress > 0 )
88 {
89 progressBar->setMaximum( 100 );
90 progressBar->setValue( static_cast<int>( std::round( progress ) ) );
91 }
92 else
93 progressBar->setMaximum( 0 );
94 } );
95 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
96
97 QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
98 statusWidget->setAutoFillBackground( true );
99 connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
100 connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
101 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
102 }
103}
104
105void QgsTaskManagerWidget::clicked( const QModelIndex &index )
106{
107 QgsTask *task = mModel->indexToTask( index );
108 if ( !task )
109 return;
110
111 mManager->triggerTask( task );
112}
113
115//
116// QgsTaskManagerModel
117//
118
119QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
120 : QAbstractItemModel( parent )
121 , mManager( manager )
122{
123 Q_ASSERT( mManager );
124
125 //populate row to id map
126 const auto constTasks = mManager->tasks();
127 for ( QgsTask *task : constTasks )
128 {
129 mRowToTaskIdList << mManager->taskId( task );
130 }
131
132 connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
133 connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
134 connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
135}
136
137QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
138{
139 if ( column < 0 || column >= columnCount() )
140 {
141 //column out of bounds
142 return QModelIndex();
143 }
144
145 if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
146 {
147 //return an index for the task at this position
148 return createIndex( row, column );
149 }
150
151 //only top level supported
152 return QModelIndex();
153}
154
155QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
156{
157 Q_UNUSED( index )
158
159 //all items are top level
160 return QModelIndex();
161}
162
163int 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
176int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
177{
178 Q_UNUSED( parent )
179 return 3;
180}
181
182QVariant 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 static_cast<int>( CustomRole::Status ):
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
232Qt::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
250bool 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
275void QgsTaskManagerModel::taskAdded( long id )
276{
277 beginInsertRows( QModelIndex(), mRowToTaskIdList.count(), mRowToTaskIdList.count() );
278 mRowToTaskIdList << id;
279 endInsertRows();
280}
281
282void 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
296void QgsTaskManagerModel::progressChanged( long id, double progress )
297{
298 Q_UNUSED( progress )
299
300 const QModelIndex index = idToIndex( id, Progress );
301 if ( !index.isValid() )
302 {
303 return;
304 }
305
306 emit dataChanged( index, index );
307}
308
309void QgsTaskManagerModel::statusChanged( long id, int status )
310{
311 if ( status == QgsTask::Complete || status == QgsTask::Terminated )
312 {
313 taskDeleted( id );
314 }
315 else
316 {
317 const QModelIndex index = idToIndex( id, Status );
318 if ( !index.isValid() )
319 {
320 return;
321 }
322
323 emit dataChanged( index, index );
324 }
325}
326
327QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
328{
329 if ( !index.isValid() || index.parent().isValid() )
330 return nullptr;
331
332 const 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
339int 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
351QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
352{
353 const int row = idToRow( id );
354 if ( row < 0 )
355 return QModelIndex();
356
357 return index( row, column );
358}
359
360QString 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 }
386 return tr( "Complete" );
388 return tr( "Terminated" );
389 }
390 }
391 }
392 }
393
394 QString formattedTime;
395
396 const qint64 elapsed = task->elapsedTime();
397
398 if ( task->progress() > 0 )
399 {
400 // estimate time remaining
401 const qint64 msRemain = static_cast<qint64>( elapsed * 100.0 / task->progress() - elapsed );
402 if ( msRemain > 120 * 1000 )
403 {
404 const long long minutes = msRemain / 1000 / 60;
405 const 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 const 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 const long long minutes = elapsed / 1000 / 60;
421 const 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 }
454 return tr( "Complete" );
456 return tr( "Terminated" );
457 }
458 }
459 }
460 // no warnings
461 return QString();
462}
463
464
465//
466// QgsTaskStatusDelegate
467//
468
469QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
470 : QWidget( parent )
471 , mCanCancel( canCancel )
472 , mStatus( status )
473{
474 setMouseTracking( true );
475}
476
477QSize QgsTaskStatusWidget::sizeHint() const
478{
479 return QSize( 32, 32 );
480}
481
482void QgsTaskStatusWidget::setStatus( int status )
483{
484 mStatus = static_cast<QgsTask::TaskStatus>( status );
485 update();
486}
487
488void 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;
511 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
512 break;
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
524void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
525{
526 if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
527 emit cancelClicked();
528}
529
530void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
531{
532 if ( !mInside )
533 {
534 mInside = true;
535 update();
536 }
537}
538
539void QgsTaskStatusWidget::leaveEvent( QEvent * )
540{
541 mInside = false;
542 update();
543}
544
545
546/*
547bool 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
568QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
569 : QgsFloatingWidget( parent )
570{
571 setLayout( new QVBoxLayout() );
572 QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
573
574 const int minWidth = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR );
575 const 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: rgba(0, 0, 0, 70%); }" );
580}
581
582
583QgsTaskManagerStatusBarWidget::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 if ( manager->countActiveTasks() )
611 showButton();
612}
613
614QSize QgsTaskManagerStatusBarWidget::sizeHint() const
615{
616 const int width = static_cast<int>( fontMetrics().horizontalAdvance( 'X' ) * 20 * Qgis::UI_SCALE_FACTOR );
617 const int height = QToolButton::sizeHint().height();
618 return QSize( width, height );
619}
620
621void QgsTaskManagerStatusBarWidget::changeEvent( QEvent *event )
622{
623 QToolButton::changeEvent( event );
624
625 if ( event->type() == QEvent::FontChange )
626 {
627 mProgressBar->setFont( font() );
628 }
629}
630
631void QgsTaskManagerStatusBarWidget::toggleDisplay()
632{
633 if ( mFloatingWidget->isVisible() )
634 mFloatingWidget->hide();
635 else
636 {
637 mFloatingWidget->show();
638 mFloatingWidget->raise();
639 }
640}
641
642void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
643{
644 mProgressBar->setValue( static_cast<int>( std::round( progress ) ) );
645 if ( qgsDoubleNear( progress, 0.0 ) )
646 mProgressBar->setMaximum( 0 );
647 else if ( mProgressBar->maximum() == 0 )
648 mProgressBar->setMaximum( 100 );
649 setToolTip( QgsTaskManagerModel::createTooltip( mManager->activeTasks().at( 0 ), QgsTaskManagerModel::ToolTipDescription ) );
650}
651
652void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
653{
654 if ( count > 1 )
655 {
656 mProgressBar->setMaximum( 0 );
657 setToolTip( tr( "%n active task(s) running", nullptr, count ) );
658 }
659}
660
661void QgsTaskManagerStatusBarWidget::allFinished()
662{
663 mFloatingWidget->hide();
664 hide();
665
666 mProgressBar->setMaximum( 0 );
667 mProgressBar->setValue( 0 );
668}
669
670void QgsTaskManagerStatusBarWidget::showButton()
671{
672 if ( !isVisible() )
673 {
674 mProgressBar->setMaximum( 0 );
675 mProgressBar->setValue( 0 );
676 show();
677 }
678}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5775
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.
A widget which displays tasks from a QgsTaskManager and allows for interaction with the manager.
QgsTaskManagerWidget(QgsTaskManager *manager, QWidget *parent=nullptr)
Constructor for QgsTaskManagerWidget.
Task manager for managing a set of long-running QgsTask tasks.
QList< QgsTask * > tasks() const
Returns all tasks tracked by the manager.
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.
long taskId(QgsTask *task) const
Returns the unique task ID corresponding to a task managed by the class.
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 triggerTask(QgsTask *task)
Triggers a task, e.g.
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:6066