QGIS/src/gui/qgstaskmanagerwidget.cpp
2018-01-16 19:45:40 +10:00

580 lines
16 KiB
C++

/***************************************************************************
qgstaskmanagerwidget.cpp
------------------------
begin : April 2016
copyright : (C) 2016 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgstaskmanagerwidget.h"
#include "qgstaskmanager.h"
#include "qgsapplication.h"
#include <QPainter>
#include <QMouseEvent>
#include <QTreeView>
#include <QLayout>
#include <QToolBar>
#include <QProgressBar>
#include <QAction>
#include <QHeaderView>
//
// QgsTaskManagerWidget
//
QgsTaskManagerWidget::QgsTaskManagerWidget( QgsTaskManager *manager, QWidget *parent )
: QWidget( parent )
, mManager( manager )
{
Q_ASSERT( manager );
QVBoxLayout *vLayout = new QVBoxLayout();
vLayout->setMargin( 0 );
mTreeView = new QTreeView();
mModel = new QgsTaskManagerModel( manager, this );
mTreeView->setModel( mModel );
connect( mModel, &QgsTaskManagerModel::rowsInserted, this, &QgsTaskManagerWidget::modelRowsInserted );
mTreeView->setHeaderHidden( true );
mTreeView->setRootIsDecorated( false );
mTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
int progressColWidth = fontMetrics().width( "X" ) * 10 * Qgis::UI_SCALE_FACTOR;
mTreeView->setColumnWidth( QgsTaskManagerModel::Progress, progressColWidth );
int statusColWidth = fontMetrics().width( "X" ) * 2 * Qgis::UI_SCALE_FACTOR;
mTreeView->setColumnWidth( QgsTaskManagerModel::Status, statusColWidth );
mTreeView->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
mTreeView->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
mTreeView->header()->setStretchLastSection( false );
mTreeView->header()->setSectionResizeMode( QgsTaskManagerModel::Description, QHeaderView::Stretch );
connect( mTreeView, &QTreeView::clicked, this, &QgsTaskManagerWidget::clicked );
vLayout->addWidget( mTreeView );
setLayout( vLayout );
}
QgsTaskManagerWidget::~QgsTaskManagerWidget()
{
delete mModel;
}
void QgsTaskManagerWidget::modelRowsInserted( const QModelIndex &, int start, int end )
{
for ( int row = start; row <= end; ++row )
{
QgsTask *task = mModel->indexToTask( mModel->index( row, 1 ) );
if ( !task )
continue;
QProgressBar *progressBar = new QProgressBar();
progressBar->setAutoFillBackground( true );
connect( task, &QgsTask::progressChanged, progressBar, [progressBar]( double progress )
{
//until first progress report, we show a progress bar of interderminant length
if ( progress > 0 )
{
progressBar->setMaximum( 100 );
progressBar->setValue( progress );
}
else
progressBar->setMaximum( 0 );
}
);
mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Progress ), progressBar );
QgsTaskStatusWidget *statusWidget = new QgsTaskStatusWidget( nullptr, task->status(), task->canCancel() );
statusWidget->setAutoFillBackground( true );
connect( task, &QgsTask::statusChanged, statusWidget, &QgsTaskStatusWidget::setStatus );
connect( statusWidget, &QgsTaskStatusWidget::cancelClicked, task, &QgsTask::cancel );
mTreeView->setIndexWidget( mModel->index( row, QgsTaskManagerModel::Status ), statusWidget );
}
}
void QgsTaskManagerWidget::clicked( const QModelIndex &index )
{
QgsTask *task = mModel->indexToTask( index );
if ( !task )
return;
mManager->triggerTask( task );
}
///@cond PRIVATE
//
// QgsTaskManagerModel
//
QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
: QAbstractItemModel( parent )
, mManager( manager )
{
Q_ASSERT( mManager );
//populate row to id map
Q_FOREACH ( QgsTask *task, mManager->tasks() )
{
mRowToTaskIdList << mManager->taskId( task );
}
connect( mManager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerModel::taskAdded );
connect( mManager, &QgsTaskManager::progressChanged, this, &QgsTaskManagerModel::progressChanged );
connect( mManager, &QgsTaskManager::statusChanged, this, &QgsTaskManagerModel::statusChanged );
}
QModelIndex QgsTaskManagerModel::index( int row, int column, const QModelIndex &parent ) const
{
if ( column < 0 || column >= columnCount() )
{
//column out of bounds
return QModelIndex();
}
if ( !parent.isValid() && row >= 0 && row < mRowToTaskIdList.count() )
{
//return an index for the task at this position
return createIndex( row, column );
}
//only top level supported
return QModelIndex();
}
QModelIndex QgsTaskManagerModel::parent( const QModelIndex &index ) const
{
Q_UNUSED( index );
//all items are top level
return QModelIndex();
}
int QgsTaskManagerModel::rowCount( const QModelIndex &parent ) const
{
if ( !parent.isValid() )
{
return mRowToTaskIdList.count();
}
else
{
//no children
return 0;
}
}
int QgsTaskManagerModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent );
return 3;
}
QVariant QgsTaskManagerModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();
QgsTask *task = indexToTask( index );
if ( task )
{
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
switch ( index.column() )
{
case Description:
return task->description();
case Progress:
return task->progress();
case Status:
// delegate shows status
return QVariant();
default:
return QVariant();
}
case StatusRole:
return static_cast<int>( task->status() );
case Qt::ToolTipRole:
switch ( index.column() )
{
case Description:
return task->description();
case Progress:
case Status:
{
switch ( task->status() )
{
case QgsTask::Queued:
return tr( "Queued" );
case QgsTask::OnHold:
return tr( "On hold" );
case QgsTask::Running:
{
if ( index.column() == Status && !task->canCancel() )
return tr( "Running (cannot cancel)" );
else
return tr( "Running" );
}
case QgsTask::Complete:
return tr( "Complete" );
case QgsTask::Terminated:
return tr( "Terminated" );
}
return QVariant();
}
default:
return QVariant();
}
default:
return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( ! index.isValid() )
{
return flags;
}
QgsTask *task = indexToTask( index );
if ( index.column() == Status )
{
if ( task && task->canCancel() )
flags = flags | Qt::ItemIsEditable;
}
return flags | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
bool QgsTaskManagerModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
Q_UNUSED( role );
if ( !index.isValid() )
return false;
QgsTask *task = indexToTask( index );
if ( !task )
return false;
switch ( index.column() )
{
case Status:
{
if ( value.toBool() && task->canCancel() )
task->cancel();
return true;
}
default:
return false;
}
}
void QgsTaskManagerModel::taskAdded( long id )
{
beginInsertRows( QModelIndex(), mRowToTaskIdList.count(),
mRowToTaskIdList.count() );
mRowToTaskIdList << id;
endInsertRows();
}
void QgsTaskManagerModel::taskDeleted( long id )
{
for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
{
if ( mRowToTaskIdList.at( row ) == id )
{
beginRemoveRows( QModelIndex(), row, row );
mRowToTaskIdList.removeAt( row );
endRemoveRows();
return;
}
}
}
void QgsTaskManagerModel::progressChanged( long id, double progress )
{
Q_UNUSED( progress );
QModelIndex index = idToIndex( id, Progress );
if ( !index.isValid() )
{
return;
}
emit dataChanged( index, index );
}
void QgsTaskManagerModel::statusChanged( long id, int status )
{
if ( status == QgsTask::Complete || status == QgsTask::Terminated )
{
taskDeleted( id );
}
else
{
QModelIndex index = idToIndex( id, Status );
if ( !index.isValid() )
{
return;
}
emit dataChanged( index, index );
}
}
QgsTask *QgsTaskManagerModel::indexToTask( const QModelIndex &index ) const
{
if ( !index.isValid() || index.parent().isValid() )
return nullptr;
long id = index.row() >= 0 && index.row() < mRowToTaskIdList.count() ? mRowToTaskIdList.at( index.row() ) : -1;
if ( id >= 0 )
return mManager->task( id );
else
return nullptr;
}
int QgsTaskManagerModel::idToRow( long id ) const
{
for ( int row = 0; row < mRowToTaskIdList.count(); ++row )
{
if ( mRowToTaskIdList.at( row ) == id )
{
return row;
}
}
return -1;
}
QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
{
int row = idToRow( id );
if ( row < 0 )
return QModelIndex();
return index( row, column );
}
//
// QgsTaskStatusDelegate
//
QgsTaskStatusWidget::QgsTaskStatusWidget( QWidget *parent, QgsTask::TaskStatus status, bool canCancel )
: QWidget( parent )
, mCanCancel( canCancel )
, mStatus( status )
{
setMouseTracking( true );
}
QSize QgsTaskStatusWidget::sizeHint() const
{
return QSize( 32, 32 );
}
void QgsTaskStatusWidget::setStatus( int status )
{
mStatus = static_cast< QgsTask::TaskStatus >( status );
update();
}
void QgsTaskStatusWidget::paintEvent( QPaintEvent *e )
{
QWidget::paintEvent( e );
QIcon icon;
if ( mInside && ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) ) )
{
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskCancel.svg" ) );
}
else
{
switch ( mStatus )
{
case QgsTask::Queued:
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskQueued.svg" ) );
break;
case QgsTask::OnHold:
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskOnHold.svg" ) );
break;
case QgsTask::Running:
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskRunning.svg" ) );
break;
case QgsTask::Complete:
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskComplete.svg" ) );
break;
case QgsTask::Terminated:
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mTaskTerminated.svg" ) );
break;
}
}
QPainter p( this );
icon.paint( &p, 1, height() / 2 - 12, 24, 24 );
p.end();
}
void QgsTaskStatusWidget::mousePressEvent( QMouseEvent * )
{
if ( mCanCancel || ( mStatus == QgsTask::Queued || mStatus == QgsTask::OnHold ) )
emit cancelClicked();
}
void QgsTaskStatusWidget::mouseMoveEvent( QMouseEvent * )
{
if ( !mInside )
{
mInside = true;
update();
}
}
void QgsTaskStatusWidget::leaveEvent( QEvent * )
{
mInside = false;
update();
}
/*
bool QgsTaskStatusWidget::editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index )
{
Q_UNUSED( option );
if ( event->type() == QEvent::MouseButtonPress )
{
QMouseEvent *e = static_cast<QMouseEvent*>( event );
if ( e->button() == Qt::LeftButton )
{
if ( !index.model()->flags( index ).testFlag( Qt::ItemIsEditable ) )
{
//item not editable
return false;
}
return model->setData( index, true, Qt::EditRole );
}
}
return false;
}
*/
QgsTaskManagerFloatingWidget::QgsTaskManagerFloatingWidget( QgsTaskManager *manager, QWidget *parent )
: QgsFloatingWidget( parent )
{
setLayout( new QVBoxLayout() );
QgsTaskManagerWidget *w = new QgsTaskManagerWidget( manager );
int minWidth = fontMetrics().width( 'X' ) * 60 * Qgis::UI_SCALE_FACTOR;
int minHeight = fontMetrics().height() * 15 * Qgis::UI_SCALE_FACTOR;
setMinimumSize( minWidth, minHeight );
layout()->addWidget( w );
setStyleSheet( ".QgsTaskManagerFloatingWidget { border-top-left-radius: 8px;"
"border-top-right-radius: 8px; background-color: rgb(0, 0, 0, 70%); }" );
}
QgsTaskManagerStatusBarWidget::QgsTaskManagerStatusBarWidget( QgsTaskManager *manager, QWidget *parent )
: QToolButton( parent )
, mManager( manager )
{
setAutoRaise( true );
setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
setLayout( new QVBoxLayout() );
mProgressBar = new QProgressBar();
mProgressBar->setMinimum( 0 );
mProgressBar->setMaximum( 0 );
layout()->setContentsMargins( 5, 5, 5, 5 );
layout()->addWidget( mProgressBar );
mFloatingWidget = new QgsTaskManagerFloatingWidget( manager, parent ? parent->window() : nullptr );
mFloatingWidget->setAnchorWidget( this );
mFloatingWidget->setAnchorPoint( QgsFloatingWidget::BottomMiddle );
mFloatingWidget->setAnchorWidgetPoint( QgsFloatingWidget::TopMiddle );
mFloatingWidget->hide();
connect( this, &QgsTaskManagerStatusBarWidget::clicked, this, &QgsTaskManagerStatusBarWidget::toggleDisplay );
hide();
connect( manager, &QgsTaskManager::taskAdded, this, &QgsTaskManagerStatusBarWidget::showButton );
connect( manager, &QgsTaskManager::allTasksFinished, this, &QgsTaskManagerStatusBarWidget::allFinished );
connect( manager, &QgsTaskManager::finalTaskProgressChanged, this, &QgsTaskManagerStatusBarWidget::overallProgressChanged );
connect( manager, &QgsTaskManager::countActiveTasksChanged, this, &QgsTaskManagerStatusBarWidget::countActiveTasksChanged );
}
QSize QgsTaskManagerStatusBarWidget::sizeHint() const
{
int width = fontMetrics().width( 'X' ) * 10 * Qgis::UI_SCALE_FACTOR;
int height = QToolButton::sizeHint().height();
return QSize( width, height );
}
void QgsTaskManagerStatusBarWidget::toggleDisplay()
{
if ( mFloatingWidget->isVisible() )
mFloatingWidget->hide();
else
{
mFloatingWidget->show();
mFloatingWidget->raise();
}
}
void QgsTaskManagerStatusBarWidget::overallProgressChanged( double progress )
{
mProgressBar->setValue( progress );
if ( qgsDoubleNear( progress, 0.0 ) )
mProgressBar->setMaximum( 0 );
else if ( mProgressBar->maximum() == 0 )
mProgressBar->setMaximum( 100 );
setToolTip( mManager->activeTasks().at( 0 )->description() );
}
void QgsTaskManagerStatusBarWidget::countActiveTasksChanged( int count )
{
if ( count > 1 )
{
mProgressBar->setMaximum( 0 );
setToolTip( tr( "%1 active tasks running" ).arg( count ) );
}
}
void QgsTaskManagerStatusBarWidget::allFinished()
{
mFloatingWidget->hide();
hide();
mProgressBar->setMaximum( 0 );
mProgressBar->setValue( 0 );
}
void QgsTaskManagerStatusBarWidget::showButton()
{
if ( !isVisible() )
{
mProgressBar->setMaximum( 100 );
mProgressBar->setValue( 0 );
show();
}
}
///@endcond