QGIS API Documentation 3.41.0-Master (3440c17df1d)
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 {
87 //until first progress report, we show a progress bar of interderminant length
88 if ( progress > 0 )
89 {
90 progressBar->setMaximum( 100 );
91 progressBar->setValue( static_cast< int >( std::round( progress ) ) );
92 }
93 else
94 progressBar->setMaximum( 0 );
95 }
96 );
97 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
98
99 QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
100 statusWidget->setAutoFillBackground( true );
101 connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
102 connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
103 mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
104 }
105}
106
107void QgsTaskManagerWidget::clicked( const QModelIndex &index )
108{
109 QgsTask *task = mModel->indexToTask( index );
110 if ( !task )
111 return;
112
113 mManager->triggerTask( task );
114}
115
117//
118// QgsTaskManagerModel
119//
120
121QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
122 : QAbstractItemModel( parent )
123 , mManager( manager )
124{
125 Q_ASSERT( mManager );
126
127 //populate row to id map
128 const auto constTasks = mManager->tasks();
129 for ( QgsTask *task : constTasks )
130 {
131 mRowToTaskIdList << mManager->taskId( task );
132 }
133
134 connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
135 connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
136 connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
137}
138
139QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
140{
141 if ( column < 0 || column >= columnCount() )
142 {
143 //column out of bounds
144 return QModelIndex();
145 }
146
147 if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
148 {
149 //return an index for the task at this position
150 return createIndex( row, column );
151 }
152
153 //only top level supported
154 return QModelIndex();
155
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(),
281 mRowToTaskIdList.count() );
282 mRowToTaskIdList << id;
283 endInsertRows();
284}
285
286void QgsTaskManagerModel::taskDeleted( long id )
287{
288 for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
289 {
290 if ( mRowToTaskIdList.at( row ) == id )
291 {
292 beginRemoveRows( QModelIndex(), row, row );
293 mRowToTaskIdList.removeAt( row );
294 endRemoveRows();
295 return;
296 }
297 }
298}
299
300void QgsTaskManagerModel::progressChanged( long id, double progress )
301{
302 Q_UNUSED( progress )
303
304 const QModelIndex index = idToIndex( id, Progress );
305 if ( !index.isValid() )
306 {
307 return;
308 }
309
310 emit dataChanged( index, index );
311}
312
313void QgsTaskManagerModel::statusChanged( long id, int status )
314{
315 if ( status == QgsTask::Complete || status == QgsTask::Terminated )
316 {
317 taskDeleted( id );
318 }
319 else
320 {
321 const QModelIndex index = idToIndex( id, Status );
322 if ( !index.isValid() )
323 {
324 return;
325 }
326
327 emit dataChanged( index, index );
328 }
329}
330
331QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
332{
333 if ( !index.isValid() || index.parent().isValid() )
334 return nullptr;
335
336 const long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
337 if ( id >= 0 )
338 return mManager->task( id );
339 else
340 return nullptr;
341}
342
343int QgsTaskManagerModel::idToRow( long id ) const
344{
345 for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
346 {
347 if ( mRowToTaskIdList.at( row ) == id )
348 {
349 return row;
350 }
351 }
352 return -1;
353}
354
355QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
356{
357 const int row = idToRow( id );
358 if ( row < 0 )
359 return QModelIndex();
360
361 return index( row, column );
362}
363
364QString QgsTaskManagerModel::createTooltip( QgsTask *task, ToolTipType type )
365{
366 if ( task->status() != QgsTask::Running )
367 {
368 switch ( type )
369 {
370 case ToolTipDescription:
371 return task->description();
372
373 case ToolTipStatus:
374 case ToolTipProgress:
375 {
376 switch ( task->status() )
377 {
378 case QgsTask::Queued:
379 return tr( "Queued" );
380 case QgsTask::OnHold:
381 return tr( "On hold" );
382 case QgsTask::Running:
383 {
384 if ( type == ToolTipStatus && !task->canCancel() )
385 return tr( "Running (cannot cancel)" );
386 else
387 return tr( "Running" );
388 }
390 return tr( "Complete" );
392 return tr( "Terminated" );
393 }
394 }
395 }
396 }
397
398 QString formattedTime;
399
400 const qint64 elapsed = task->elapsedTime();
401
402 if ( task->progress() > 0 )
403 {
404 // estimate time remaining
405 const qint64 msRemain = static_cast< qint64 >( elapsed * 100.0 / task->progress() - elapsed );
406 if ( msRemain > 120 * 1000 )
407 {
408 const long long minutes = msRemain / 1000 / 60;
409 const int seconds = ( msRemain / 1000 ) % 60;
410 formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
411 }
412 else
413 formattedTime = tr( "%1 seconds" ).arg( msRemain / 1000 );
414
415 formattedTime = tr( "Estimated time remaining: %1" ).arg( formattedTime );
416
417 const QTime estimatedEnd = QTime::currentTime().addMSecs( msRemain );
418 formattedTime += tr( " (%1)" ).arg( QLocale::system().toString( estimatedEnd, QLocale::ShortFormat ) );
419 }
420 else
421 {
422 if ( elapsed > 120 * 1000 )
423 {
424 const long long minutes = elapsed / 1000 / 60;
425 const int seconds = ( elapsed / 1000 ) % 60;
426 formattedTime = tr( "%1:%2 minutes" ).arg( minutes ).arg( seconds, 2, 10, QChar( '0' ) );
427 }
428 else
429 formattedTime = tr( "%1 seconds" ).arg( elapsed / 1000 );
430
431 formattedTime = tr( "Time elapsed: %1" ).arg( formattedTime );
432 }
433
434 switch ( type )
435 {
436 case ToolTipDescription:
437 return tr( "%1<br>%2" ).arg( task->description(), formattedTime );
438
439 case ToolTipStatus:
440 case ToolTipProgress:
441 {
442 switch ( task->status() )
443 {
444 case QgsTask::Queued:
445 return tr( "Queued" );
446 case QgsTask::OnHold:
447 return tr( "On hold" );
448 case QgsTask::Running:
449 {
450 QString statusDesc;
451 if ( type == ToolTipStatus && !task->canCancel() )
452 statusDesc = tr( "Running (cannot cancel)" );
453 else
454 statusDesc = tr( "Running" );
455 return tr( "%1<br>%2" ).arg( statusDesc, formattedTime );
456 }
458 return tr( "Complete" );
460 return tr( "Terminated" );
461 }
462 }
463 }
464 // no warnings
465 return QString();
466}
467
468
469//
470// QgsTaskStatusDelegate
471//
472
473QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
474 : QWidget( parent )
475 , mCanCancel( canCancel )
476 , mStatus( status )
477{
478 setMouseTracking( true );
479}
480
481QSize QgsTaskStatusWidget::sizeHint() const
482{
483 return QSize( 32, 32 );
484}
485
486void QgsTaskStatusWidget::setStatus( int status )
487{
488 mStatus = static_cast< QgsTask::TaskStatus >( status );
489 update();
490}
491
492void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
493{
494 QWidget::paintEvent( e );
495
496 QIcon icon;
497 if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
498 {
499 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
500 }
501 else
502 {
503 switch ( mStatus )
504 {
505 case QgsTask::Queued:
506 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
507 break;
508 case QgsTask::OnHold:
509 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
510 break;
511 case QgsTask::Running:
512 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
513 break;
515 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
516 break;
518 icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
519 break;
520 }
521 }
522
523 QPainter p( this );
524 icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
525 p.end();
526}
527
528void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
529{
530 if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
531 emit cancelClicked();
532}
533
534void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
535{
536 if ( !mInside )
537 {
538 mInside = true;
539 update();
540 }
541}
542
543void QgsTaskStatusWidget::leaveEvent( QEvent * )
544{
545 mInside = false;
546 update();
547}
548
549
550/*
551bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
552{
553 Q_UNUSED( option )
554 if ( event->type() == QEvent::MouseButtonPress )
555 {
556 QMouseEvent *e = static_cast<QMouseEvent*>( event );
557 if ( e->button() == Qt::LeftButton )
558 {
559 if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
560 {
561 //item not editable
562 return false;
563 }
564
565 return model->setData( index, true, Qt::EditRole );
566 }
567 }
568 return false;
569}
570*/
571
572QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
573 : QgsFloatingWidget( parent )
574{
575 setLayout( new QVBoxLayout() );
576 QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
577
578 const int minWidth = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR );
579 const int minHeight = static_cast< int >( fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR );
580 setMinimumSize( minWidth, minHeight );
581 layout()->addWidget( w );
582 setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
583 "border-top-right-radius: 8px; background-color: rgba(0, 0, 0, 70%); }" );
584}
585
586
587QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
588 : QToolButton( parent )
589 , mManager( manager )
590{
591 setAutoRaise( true );
592 setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
593 setLayout( new QVBoxLayout() );
594
595 mProgressBar = new QProgressBar();
596 mProgressBar->setMinimum( 0 );
597 mProgressBar->setMaximum( 0 );
598 layout()->setContentsMargins( 5, 5, 5, 5 );
599 layout()->addWidget( mProgressBar );
600
601 mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
602 mFloatingWidget->setAnchorWidget( this );
603 mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
604 mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
605 mFloatingWidget->hide();
606 connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
607 hide();
608
609 connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
610 connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
611 connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
612 connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
613
614 if ( manager->countActiveTasks() )
615 showButton();
616}
617
618QSize QgsTaskManagerStatusBarWidget::sizeHint() const
619{
620 const int width = static_cast< int >( fontMetrics().horizontalAdvance( 'X' ) * 20 * Qgis::UI_SCALE_FACTOR );
621 const int height = QToolButton::sizeHint().height();
622 return QSize( width, height );
623}
624
625void QgsTaskManagerStatusBarWidget::changeEvent( QEvent *event )
626{
627 QToolButton::changeEvent( event );
628
629 if ( event->type() == QEvent::FontChange )
630 {
631 mProgressBar->setFont( font() );
632 }
633}
634
635void QgsTaskManagerStatusBarWidget::toggleDisplay()
636{
637 if ( mFloatingWidget->isVisible() )
638 mFloatingWidget->hide();
639 else
640 {
641 mFloatingWidget->show();
642 mFloatingWidget->raise();
643 }
644}
645
646void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
647{
648 mProgressBar->setValue( static_cast< int >( std::round( progress ) ) );
649 if ( qgsDoubleNear( progress, 0.0 ) )
650 mProgressBar->setMaximum( 0 );
651 else if ( mProgressBar->maximum() == 0 )
652 mProgressBar->setMaximum( 100 );
653 setToolTip( QgsTaskManagerModel::createTooltip( mManager->activeTasks().at( 0 ), QgsTaskManagerModel::ToolTipDescription ) );
654}
655
656void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
657{
658 if ( count > 1 )
659 {
660 mProgressBar->setMaximum( 0 );
661 setToolTip( tr( "%n active task(s) running", nullptr, count ) );
662 }
663}
664
665void QgsTaskManagerStatusBarWidget::allFinished()
666{
667 mFloatingWidget->hide();
668 hide();
669
670 mProgressBar->setMaximum( 0 );
671 mProgressBar->setValue( 0 );
672}
673
674void QgsTaskManagerStatusBarWidget::showButton()
675{
676 if ( !isVisible() )
677 {
678 mProgressBar->setMaximum( 0 );
679 mProgressBar->setValue( 0 );
680 show();
681 }
682}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5667
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:5958