Framework for task manager

Adds new classes:
- QgsTask. An interface for long-running background tasks
- QgsTaskManager. Handles groups of tasks - also available as a global
instance for tracking application wide tasks
- QgsTaskManagerWidget. A list view for showing active tasks and their
progress, and for cancelling them

A new dock widget has been added with a task manager widget showing
global tasks
This commit is contained in:
Nyall Dawson 2016-04-15 07:35:22 +10:00
parent dc697d0590
commit ebae15f23a
14 changed files with 1488 additions and 0 deletions

View File

@ -138,6 +138,7 @@
%Include qgsstatisticalsummary.sip
%Include qgsstringstatisticalsummary.sip
%Include qgsstringutils.sip
%Include qgstaskmanager.sip
%Include qgstextrenderer.sip
%Include qgstolerance.sip
%Include qgstracer.sip

View File

@ -0,0 +1,174 @@
/** \ingroup core
* \class QgsTask
* \brief Interface class for long running tasks which will be handled by a QgsTaskManager
* \note Added in version 2.16
*/
class QgsTask : QObject
{
%TypeHeaderCode
#include <qgstaskmanager.h>
%End
public:
//! Status of tasks
enum TaskStatus
{
Running, /*!< Task is currently running */
Complete, /*!< Task successfully completed */
Terminated, /*!< Task was terminated or errored */
// Paused,
// Queued,
};
/** Constructor for QgsTask.
* @param description text description of task
*/
QgsTask( const QString& description = QString() );
//! Will be called when task has been terminated, either through
//! user interaction or other reason (eg application exit)
//! @note derived classes must ensure they call the base method
virtual void terminate();
//! Returns true if the task is active, ie it is not complete and has
//! not been terminated.
bool isActive() const;
//! Returns the current task status.
TaskStatus status() const;
//! Returns the task's description.
QString description() const;
//! Returns the task's progress (between 0.0 and 100.0)
double progress() const;
signals:
//! Will be emitted by task when its progress changes
//! @param progress percent of progress, from 0.0 - 100.0
//! @note derived classes should not emit this signal directly, instead they should call
//! setProgress()
void progressChanged( double progress );
//! Will be emitted by task when its status changes
//! @param status new task status
//! @note derived classes should not emit this signal directly, instead they should call
//! completed() or stopped()
void statusChanged( int status );
//! Will be emitted by task to indicate its completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! completed()
void taskCompleted();
//! Will be emitted by task if it has terminated for any reason
//! other then completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! stopped()//!
void taskStopped();
protected:
//! Sets the task's current progress. Should be called whenever the
//! task wants to update it's progress. Calling will automatically emit the progressChanged
//! signal.
//! @param progress percent of progress, from 0.0 - 100.0
void setProgress( double progress );
//! Sets the task as completed. Should be called when the task is complete.
//! Calling will automatically emit the statusChanged and taskCompleted signals.
void completed();
//! Sets the task as stopped. Should be called whenever the task ends for any
//! reason other than successful completion.
//! Calling will automatically emit the statusChanged and taskStopped signals.
void stopped();
};
/** \ingroup core
* \class QgsTaskManager
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
* or accessed via a global instance.
* \note Added in version 2.16
*/
class QgsTaskManager : QObject
{
%TypeHeaderCode
#include <qgstaskmanager.h>
%End
public:
/** Returns the global task manager instance pointer, creating the object on the first call.
*/
static QgsTaskManager * instance();
/** Constructor for QgsTaskManager.
* @param parent parent QObject
*/
QgsTaskManager( QObject* parent /TransferThis/ = nullptr );
virtual ~QgsTaskManager();
/** Adds a task to the manager. Ownership of the task is transferred
* to the manager.
* @param task task to add
* @returns unique task ID
*/
long addTask( QgsTask* task /Transfer/ );
/** Deletes the specified task, first terminating it if it is currently
* running.
* @param id task ID
* @returns true if task was found and deleted
*/
bool deleteTask( long id );
/** Deletes the specified task, first terminating it if it is currently
* running.
* @param task task to delete
* @returns true if task was contained in manager and deleted
*/
bool deleteTask( QgsTask* task );
/** Returns the task with matching ID.
* @param id task ID
* @returns task if found, or nullptr
*/
QgsTask* task( long id ) const;
/** Returns all tasks tracked by the manager.
*/
QList<QgsTask*> tasks() const;
//! Returns the number of tasks tracked by the manager.
int count() const;
/** Returns the unique task ID corresponding to a task managed by the class.
* @param task task to find
* @returns task ID, or -1 if task not found
*/
long taskId( QgsTask* task ) const;
signals:
//! Will be emitted when a task reports a progress change
//! @param taskId ID of task
//! @param progress percent of progress, from 0.0 - 100.0
void progressChanged( long taskId, double progress );
//! Will be emitted when a task reports a status change
//! @param taskId ID of task
//! @param status new task status
void statusChanged( long taskId, int status );
//! Emitted when a new task has been added to the manager
//! @param taskId ID of task
void taskAdded( long taskId );
//! Emitted when a task is about to be deleted
//! @param taskId ID of task
void taskAboutToBeDeleted( long taskId );
};

View File

@ -161,6 +161,7 @@
%Include qgstablewidgetbase.sip
%Include qgstabwidget.sip
%Include qgstablewidgetitem.sip
%Include qgstaskmanagerwidget.sip
%Include qgstextannotationitem.sip
%Include qgstextformatwidget.sip
%Include qgstextpreview.sip

View File

@ -0,0 +1,95 @@
/** \ingroup gui
* \class QgsTaskManagerWidget
* A widget which displays tasks from a QgsTaskManager and allows for interaction with the manager
* @see QgsTaskManager
* @note introduced in QGIS 2.16
*/
class QgsTaskManagerWidget : QTreeView
{
%TypeHeaderCode
#include <qgstaskmanagerwidget.h>
%End
public:
/** Constructor for QgsTaskManagerWidget
* @param manager task manager associated with widget
* @param parent parent widget
*/
QgsTaskManagerWidget( QgsTaskManager* manager, QWidget* parent /TransferThis/ = nullptr );
};
/** \ingroup gui
* \class QgsTaskManagerModel
* A model representing a QgsTaskManager
* @see QgsTaskManager
* @note introduced in QGIS 2.16
*/
class QgsTaskManagerModel: QAbstractItemModel
{
%TypeHeaderCode
#include <qgstaskmanagerwidget.h>
%End
public:
/** Constructor for QgsTaskManagerModel
* @param manager task manager for model
* @param parent parent object
*/
explicit QgsTaskManagerModel( QgsTaskManager* manager, QObject* parent /TransferThis/ = nullptr );
//reimplemented QAbstractItemModel methods
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const;
QModelIndex parent( const QModelIndex &index ) const;
int rowCount( const QModelIndex &parent = QModelIndex() ) const;
int columnCount( const QModelIndex &parent = QModelIndex() ) const;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;
Qt::ItemFlags flags( const QModelIndex & index ) const;
bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole );
};
/** \ingroup gui
* \class QgsProgressBarDelegate
* A delegate for showing a progress bar within a view
* @note introduced in QGIS 2.16
*/
class QgsProgressBarDelegate : QStyledItemDelegate
{
%TypeHeaderCode
#include <qgstaskmanagerwidget.h>
%End
public:
/** Constructor for QgsProgressBarDelegate
* @param parent parent object
*/
QgsProgressBarDelegate( QObject* parent /TransferThis/ = nullptr );
void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const;
};
/** \ingroup gui
* \class QgsProgressBarDelegate
* A delegate for showing task status within a view. Clicks on the delegate will cause the task to be cancelled (via the model).
* @note introduced in QGIS 2.16
*/
class QgsTaskStatusDelegate : QStyledItemDelegate
{
%TypeHeaderCode
#include <qgstaskmanagerwidget.h>
%End
public:
/** Constructor for QgsTaskStatusDelegate
* @param parent parent object
*/
QgsTaskStatusDelegate( QObject* parent /TransferThis/ = nullptr );
void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const;
QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const;
bool editorEvent( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index );
};

View File

@ -221,6 +221,8 @@
#include "qgsstatusbarscalewidget.h"
#include "qgsstyle.h"
#include "qgssvgannotationitem.h"
#include "qgstaskmanager.h"
#include "qgstaskmanagerwidget.h"
#include "qgssymbolselectordialog.h"
#include "qgstextannotationitem.h"
#include "qgstipgui.h"
@ -851,6 +853,13 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
QMainWindow::addDockWidget( Qt::BottomDockWidgetArea, mUserInputDockWidget );
mUserInputDockWidget->setFloating( true );
// create the task manager dock on starting QGIS - this is like the browser
mTaskManagerDock = new QDockWidget( tr( "Task Manager" ), this );
mTaskManagerDock->setObjectName( "TaskManager" );
addDockWidget( Qt::RightDockWidgetArea, mTaskManagerDock );
mTaskManagerDock->setWidget( new QgsTaskManagerWidget( QgsTaskManager::instance(), mTaskManagerDock ) );
mTaskManagerDock->hide();
// create the GPS tool on starting QGIS - this is like the browser
mpGpsWidget = new QgsGPSInformationWidget( mMapCanvas );
//create the dock widget
@ -1139,6 +1148,7 @@ QgisApp::QgisApp()
, mOverviewDock( nullptr )
, mpGpsDock( nullptr )
, mLogDock( nullptr )
, mTaskManagerDock( nullptr )
, mNonEditMapTool( nullptr )
, mScaleWidget( nullptr )
, mMagnifierWidget( nullptr )

View File

@ -1560,6 +1560,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
QgsDockWidget *mOverviewDock;
QgsDockWidget *mpGpsDock;
QgsDockWidget *mLogDock;
QDockWidget *mTaskManagerDock;
#ifdef Q_OS_MAC
//! Window menu action to select this window

View File

@ -211,6 +211,7 @@ SET(QGIS_CORE_SRCS
qgsstatisticalsummary.cpp
qgsstringstatisticalsummary.cpp
qgsstringutils.cpp
qgstaskmanager.cpp
qgstextlabelfeature.cpp
qgstextrenderer.cpp
qgstolerance.cpp
@ -495,6 +496,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsrelationmanager.h
qgsrunprocess.h
qgssnappingutils.h
qgstaskmanager.h
qgstracer.h
qgstrackedvectorlayertools.h
qgstransaction.h

181
src/core/qgstaskmanager.cpp Normal file
View File

@ -0,0 +1,181 @@
/***************************************************************************
qgstaskmanager.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 "qgstaskmanager.h"
#include <QMutex>
//
// QgsTask
//
QgsTask::QgsTask( const QString &name )
: QObject()
, mDescription( name )
, mStatus( Running )
, mProgress( 0.0 )
{}
void QgsTask::setProgress( double progress )
{
mProgress = progress;
emit progressChanged( progress );
}
void QgsTask::completed()
{
mStatus = Complete;
emit statusChanged( Complete );
emit taskCompleted();
}
void QgsTask::stopped()
{
mStatus = Terminated;
emit statusChanged( Terminated );
emit taskStopped();
}
//
// QgsTaskManager
//
// Static calls to enforce singleton behaviour
QgsTaskManager *QgsTaskManager::sInstance = nullptr;
QgsTaskManager *QgsTaskManager::instance()
{
if ( !sInstance )
{
sInstance = new QgsTaskManager();
}
return sInstance;
}
QgsTaskManager::QgsTaskManager( QObject* parent )
: QObject( parent )
, mNextTaskId( 0 )
{}
QgsTaskManager::~QgsTaskManager()
{
QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
{
cleanupAndDeleteTask( it.value() );
}
}
long QgsTaskManager::addTask( QgsTask* task )
{
static QMutex sAddMutex( QMutex::Recursive );
QMutexLocker locker( &sAddMutex );
mTasks.insert( mNextTaskId, task );
connect( task, SIGNAL( progressChanged( double ) ), this, SLOT( taskProgressChanged( double ) ) );
connect( task, SIGNAL( statusChanged( int ) ), this, SLOT( taskStatusChanged( int ) ) );
emit taskAdded( mNextTaskId );
return mNextTaskId++;
}
bool QgsTaskManager::deleteTask( long id )
{
QgsTask* task = mTasks.value( id );
return deleteTask( task );
}
bool QgsTaskManager::deleteTask( QgsTask *task )
{
if ( !task )
return false;
bool result = cleanupAndDeleteTask( task );
// remove from internal task list
for ( QMap< long, QgsTask* >::iterator it = mTasks.begin(); it != mTasks.end(); )
{
if ( it.value() == task )
it = mTasks.erase( it );
else
++it;
}
return result;
}
QgsTask*QgsTaskManager::task( long id ) const
{
return mTasks.value( id );
}
QList<QgsTask*> QgsTaskManager::tasks() const
{
return mTasks.values();
}
long QgsTaskManager::taskId( QgsTask *task ) const
{
if ( !task )
return -1;
QMap< long, QgsTask* >::const_iterator it = mTasks.constBegin();
for ( ; it != mTasks.constEnd(); ++it )
{
if ( it.value() == task )
return it.key();
}
return -1;
}
void QgsTaskManager::taskProgressChanged( double progress )
{
QgsTask* task = qobject_cast< QgsTask* >( sender() );
//find ID of task
long id = taskId( task );
if ( id < 0 )
return;
emit progressChanged( id, progress );
}
void QgsTaskManager::taskStatusChanged( int status )
{
QgsTask* task = qobject_cast< QgsTask* >( sender() );
//find ID of task
long id = taskId( task );
if ( id < 0 )
return;
emit statusChanged( id, status );
}
bool QgsTaskManager::cleanupAndDeleteTask( QgsTask *task )
{
if ( !task )
return false;
if ( task->isActive() )
task->terminate();
emit taskAboutToBeDeleted( taskId( task ) );
task->deleteLater();
return true;
}

222
src/core/qgstaskmanager.h Normal file
View File

@ -0,0 +1,222 @@
/***************************************************************************
qgstaskmanager.h
----------------
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. *
* *
***************************************************************************/
#ifndef QGSTASKMANAGER_H
#define QGSTASKMANAGER_H
#include <QObject>
#include <QMap>
#include <QAbstractItemModel>
/** \ingroup core
* \class QgsTask
* \brief Interface class for long running background tasks which will be handled by a QgsTaskManager
* \note Added in version 2.16
*/
class CORE_EXPORT QgsTask : public QObject
{
Q_OBJECT
public:
//! Status of tasks
enum TaskStatus
{
Running, /*!< Task is currently running */
Complete, /*!< Task successfully completed */
Terminated, /*!< Task was terminated or errored */
// Paused,
// Queued,
};
/** Constructor for QgsTask.
* @param description text description of task
*/
QgsTask( const QString& description = QString() );
//! Will be called when task has been terminated, either through
//! user interaction or other reason (eg application exit)
//! @note derived classes must ensure they call the base method
virtual void terminate()
{
stopped();
}
//! Returns true if the task is active, ie it is not complete and has
//! not been terminated.
bool isActive() const { return mStatus == Running; }
//! Returns the current task status.
TaskStatus status() const { return mStatus; }
//! Returns the task's description.
QString description() const { return mDescription; }
//! Returns the task's progress (between 0.0 and 100.0)
double progress() const { return mProgress; }
signals:
//! Will be emitted by task when its progress changes
//! @param progress percent of progress, from 0.0 - 100.0
//! @note derived classes should not emit this signal directly, instead they should call
//! setProgress()
void progressChanged( double progress );
//! Will be emitted by task when its status changes
//! @param status new task status
//! @note derived classes should not emit this signal directly, instead they should call
//! completed() or stopped()
void statusChanged( int status );
//! Will be emitted by task to indicate its completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! completed()
void taskCompleted();
//! Will be emitted by task if it has terminated for any reason
//! other then completion.
//! @note derived classes should not emit this signal directly, instead they should call
//! stopped()//!
void taskStopped();
protected:
//! Sets the task's current progress. Should be called whenever the
//! task wants to update it's progress. Calling will automatically emit the progressChanged
//! signal.
//! @param progress percent of progress, from 0.0 - 100.0
void setProgress( double progress );
//! Sets the task as completed. Should be called when the task is complete.
//! Calling will automatically emit the statusChanged and taskCompleted signals.
void completed();
//! Sets the task as stopped. Should be called whenever the task ends for any
//! reason other than successful completion.
//! Calling will automatically emit the statusChanged and taskStopped signals.
void stopped();
private:
QString mDescription;
TaskStatus mStatus;
double mProgress;
};
/** \ingroup core
* \class QgsTaskManager
* \brief Task manager for managing a set of long-running QgsTask tasks. This class can be created directly,
* or accessed via a global instance.
* \note Added in version 2.16
*/
class CORE_EXPORT QgsTaskManager : public QObject
{
Q_OBJECT
public:
/** Returns the global task manager instance pointer, creating the object on the first call.
*/
static QgsTaskManager * instance();
/** Constructor for QgsTaskManager.
* @param parent parent QObject
*/
QgsTaskManager( QObject* parent = nullptr );
virtual ~QgsTaskManager();
/** Adds a task to the manager. Ownership of the task is transferred
* to the manager.
* @param task task to add
* @returns unique task ID
*/
long addTask( QgsTask* task );
/** Deletes the specified task, first terminating it if it is currently
* running.
* @param id task ID
* @returns true if task was found and deleted
*/
bool deleteTask( long id );
/** Deletes the specified task, first terminating it if it is currently
* running.
* @param task task to delete
* @returns true if task was contained in manager and deleted
*/
bool deleteTask( QgsTask* task );
/** Returns the task with matching ID.
* @param id task ID
* @returns task if found, or nullptr
*/
QgsTask* task( long id ) const;
/** Returns all tasks tracked by the manager.
*/
QList<QgsTask*> tasks() const;
//! Returns the number of tasks tracked by the manager.
int count() const { return mTasks.count(); }
/** Returns the unique task ID corresponding to a task managed by the class.
* @param task task to find
* @returns task ID, or -1 if task not found
*/
long taskId( QgsTask* task ) const;
signals:
//! Will be emitted when a task reports a progress change
//! @param taskId ID of task
//! @param progress percent of progress, from 0.0 - 100.0
void progressChanged( long taskId, double progress );
//! Will be emitted when a task reports a status change
//! @param taskId ID of task
//! @param status new task status
void statusChanged( long taskId, int status );
//! Emitted when a new task has been added to the manager
//! @param taskId ID of task
void taskAdded( long taskId );
//! Emitted when a task is about to be deleted
//! @param taskId ID of task
void taskAboutToBeDeleted( long taskId );
private slots:
void taskProgressChanged( double progress );
void taskStatusChanged( int status );
private:
static QgsTaskManager *sInstance;
QMap< long, QgsTask* > mTasks;
//! Tracks the next unique task ID
long mNextTaskId;
bool cleanupAndDeleteTask( QgsTask* task );
};
#endif //QGSTASKMANAGER_H

View File

@ -308,6 +308,7 @@ SET(QGIS_GUI_SRCS
qgstablewidgetbase.cpp
qgstabwidget.cpp
qgstablewidgetitem.cpp
qgstaskmanagerwidget.cpp
qgstextannotationitem.cpp
qgstextformatwidget.cpp
qgstextpreview.cpp
@ -465,6 +466,7 @@ SET(QGIS_GUI_MOC_HDRS
qgssubstitutionlistwidget.h
qgstablewidgetbase.h
qgstabwidget.h
qgstaskmanagerwidget.h
qgstextformatwidget.h
qgstextpreview.h
qgstreewidgetitem.h

View File

@ -0,0 +1,363 @@
/***************************************************************************
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>
//
// QgsTaskManagerWidget
//
QgsTaskManagerWidget::QgsTaskManagerWidget( QgsTaskManager *manager, QWidget *parent )
: QTreeView( parent )
{
Q_ASSERT( manager );
setModel( new QgsTaskManagerModel( manager, this ) );
setItemDelegateForColumn( 1, new QgsProgressBarDelegate( this ) );
setItemDelegateForColumn( 2, new QgsTaskStatusDelegate( this ) );
setHeaderHidden( true );
setRootIsDecorated( false );
setSelectionBehavior( QAbstractItemView::SelectRows );
}
//
// QgsTaskManagerModel
//
QgsTaskManagerModel::QgsTaskManagerModel( QgsTaskManager *manager, QObject *parent )
: QAbstractItemModel( parent )
, mManager( manager )
{
Q_ASSERT( mManager );
//populate row to id map
int i = 0;
Q_FOREACH ( QgsTask* task, mManager->tasks() )
{
mRowToTaskIdMap.insert( i, mManager->taskId( task ) );
}
connect( mManager, SIGNAL( taskAdded( long ) ), this, SLOT( taskAdded( long ) ) );
connect( mManager, SIGNAL( taskAboutToBeDeleted( long ) ), this, SLOT( taskDeleted( long ) ) );
connect( mManager, SIGNAL( progressChanged( long, double ) ), this, SLOT( progressChanged( long, double ) ) );
connect( mManager, SIGNAL( statusChanged( long, int ) ), this, SLOT( statusChanged( long, int ) ) );
}
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 < mManager->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 mManager->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 )
return QVariant();
switch ( role )
{
case Qt::DisplayRole:
case Qt::EditRole:
switch ( index.column() )
{
case Description:
return task->description();
case Progress:
return task->progress();
case Status:
return static_cast<int>( task->status() );
default:
return QVariant();
}
case StatusRole:
return static_cast<int>( task->status() );
default:
return QVariant();
}
}
Qt::ItemFlags QgsTaskManagerModel::flags( const QModelIndex &index ) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags( index );
if ( ! index.isValid() )
{
return flags;
}
if ( index.column() == Status )
{
if ( static_cast< QgsTask::TaskStatus >( data( index, StatusRole ).toInt() ) == QgsTask::Running )
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->terminate();
return true;
}
default:
return false;
}
}
void QgsTaskManagerModel::taskAdded( long id )
{
beginInsertRows( QModelIndex(), mRowToTaskIdMap.count(), mRowToTaskIdMap.count() );
mRowToTaskIdMap.insert( mRowToTaskIdMap.count(), id );
endInsertRows();
}
void QgsTaskManagerModel::taskDeleted( long id )
{
for ( QMap< int, long >::iterator it = mRowToTaskIdMap.begin(); it != mRowToTaskIdMap.end(); )
{
if ( it.value() == id )
{
beginRemoveRows( QModelIndex(), it.key(), it.key() );
it = mRowToTaskIdMap.erase( it );
endRemoveRows();
return;
}
else
++it;
}
}
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 )
{
Q_UNUSED( status );
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 = mRowToTaskIdMap.value( index.row() );
return mManager->task( id );
}
int QgsTaskManagerModel::idToRow( long id ) const
{
for ( QMap< int, long >::const_iterator it = mRowToTaskIdMap.constBegin(); it != mRowToTaskIdMap.constEnd(); ++it )
{
if ( it.value() == id )
{
return it.key();
}
}
return -1;
}
QModelIndex QgsTaskManagerModel::idToIndex( long id, int column ) const
{
int row = idToRow( id );
if ( row < 0 )
return QModelIndex();
return index( row, column );
}
//
// QgsProgressBarDelegate
//
QgsProgressBarDelegate::QgsProgressBarDelegate( QObject *parent )
: QStyledItemDelegate( parent )
{}
void QgsProgressBarDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QStyledItemDelegate::paint( painter, option, index );
int progress = index.data().toInt();
QStyleOptionProgressBarV2 progressBarOption;
progressBarOption.state = option.state;
progressBarOption.rect = option.rect;
progressBarOption.rect.setTop( option.rect.top() + 1 );
progressBarOption.rect.setHeight( option.rect.height() - 2 );
progressBarOption.minimum = 0;
progressBarOption.maximum = 100;
progressBarOption.progress = progress;
progressBarOption.text = QString::number( progress ) + "%";
progressBarOption.textVisible = true;
progressBarOption.textAlignment = Qt::AlignCenter;
QgsApplication::style()->drawControl( QStyle::CE_ProgressBar, &progressBarOption, painter );
}
QSize QgsProgressBarDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
Q_UNUSED( index );
return QSize( option.rect.width(), option.fontMetrics.height() + 10 );
}
//
// QgsTaskStatusDelegate
//
QgsTaskStatusDelegate::QgsTaskStatusDelegate( QObject *parent )
: QStyledItemDelegate( parent )
{
}
void QgsTaskStatusDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
QStyledItemDelegate::paint( painter, option, index );
QIcon icon;
switch ( static_cast< QgsTask::TaskStatus >( index.data().toInt() ) )
{
case QgsTask::Running:
icon = QgsApplication::getThemeIcon( "/mActionRefresh.png" );
break;
case QgsTask::Complete:
icon = QgsApplication::getThemeIcon( "/mActionCheckQgisVersion.png" );
break;
case QgsTask::Terminated:
icon = QgsApplication::getThemeIcon( "/mActionRemove.svg" );
break;
}
icon.paint( painter, option.rect.left() + 1, ( option.rect.top() + option.rect.bottom() ) / 2 - 12, 24, 24 );
}
QSize QgsTaskStatusDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
Q_UNUSED( option );
Q_UNUSED( index );
return QSize( 32, 32 );
}
bool QgsTaskStatusDelegate::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;
}

View File

@ -0,0 +1,146 @@
/***************************************************************************
qgstaskmanagerwidget.h
----------------------
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. *
* *
***************************************************************************/
#ifndef QGSTASKMANAGERWIDGET_H
#define QGSTASKMANAGERWIDGET_H
#include <QTreeView>
#include <QStyledItemDelegate>
class QgsTaskManager;
class QgsTask;
/** \ingroup gui
* \class QgsTaskManagerWidget
* A widget which displays tasks from a QgsTaskManager and allows for interaction with the manager
* @see QgsTaskManager
* @note introduced in QGIS 2.16
*/
class GUI_EXPORT QgsTaskManagerWidget : public QTreeView
{
Q_OBJECT
public:
/** Constructor for QgsTaskManagerWidget
* @param manager task manager associated with widget
* @param parent parent widget
*/
QgsTaskManagerWidget( QgsTaskManager* manager, QWidget* parent = nullptr );
};
/** \ingroup gui
* \class QgsTaskManagerModel
* A model representing a QgsTaskManager
* @see QgsTaskManager
* @note introduced in QGIS 2.16
*/
class GUI_EXPORT QgsTaskManagerModel: public QAbstractItemModel
{
Q_OBJECT
public:
/** Constructor for QgsTaskManagerModel
* @param manager task manager for model
* @param parent parent object
*/
explicit QgsTaskManagerModel( QgsTaskManager* manager, QObject* parent = nullptr );
//reimplemented QAbstractItemModel methods
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override;
QModelIndex parent( const QModelIndex &index ) const override;
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
Qt::ItemFlags flags( const QModelIndex & index ) const override;
bool setData( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole ) override;
private slots:
void taskAdded( long id );
void taskDeleted( long id );
void progressChanged( long id, double progress );
void statusChanged( long id, int status );
private:
enum Columns
{
Description = 0,
Progress = 1,
Status = 2,
};
enum Roles
{
StatusRole = Qt::UserRole,
};
QgsTaskManager* mManager;
QMap< int, long > mRowToTaskIdMap;
QgsTask* indexToTask( const QModelIndex& index ) const;
int idToRow( long id ) const;
QModelIndex idToIndex( long id, int column ) const;
};
/** \ingroup gui
* \class QgsProgressBarDelegate
* A delegate for showing a progress bar within a view
* @note introduced in QGIS 2.16
*/
class GUI_EXPORT QgsProgressBarDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
/** Constructor for QgsProgressBarDelegate
* @param parent parent object
*/
QgsProgressBarDelegate( QObject* parent = nullptr );
void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
};
/** \ingroup gui
* \class QgsProgressBarDelegate
* A delegate for showing task status within a view. Clicks on the delegate will cause the task to be cancelled (via the model).
* @note introduced in QGIS 2.16
*/
class GUI_EXPORT QgsTaskStatusDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
/** Constructor for QgsTaskStatusDelegate
* @param parent parent object
*/
QgsTaskStatusDelegate( QObject* parent = nullptr );
void paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const override;
QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
bool editorEvent( QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index ) override;
};
#endif //QGSTASKMANAGERWIDGET_H

View File

@ -184,6 +184,7 @@ ADD_QGIS_TEST(stringutilstest testqgsstringutils.cpp)
ADD_QGIS_TEST(styletest testqgsstyle.cpp)
ADD_QGIS_TEST(svgmarkertest testqgssvgmarker.cpp)
ADD_QGIS_TEST(symboltest testqgssymbol.cpp)
ADD_QGIS_TEST(taskmanagertest testqgstaskmanager.cpp)
ADD_QGIS_TEST(tracertest testqgstracer.cpp)
#for some obscure reason calling this test "fontutils" kills the build on Ubuntu 15.10
ADD_QGIS_TEST(typographicstylingutils testqgsfontutils.cpp)

View File

@ -0,0 +1,289 @@
/***************************************************************************
testqgstaskmanager.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 "qgstaskmanager.h"
#include <QObject>
#include <QSharedPointer>
#include <QtTest/QtTest>
class TestTask : public QgsTask
{
Q_OBJECT
public:
TestTask( const QString& desc = QString() ) : QgsTask( desc ) {}
void emitProgressChanged( double progress ) { setProgress( progress ); }
void emitTaskStopped() { stopped(); }
void emitTaskCompleted() { completed(); }
};
class TestTerminationTask : public TestTask
{
Q_OBJECT
public:
~TestTerminationTask()
{
//make sure task has been terminated by manager prior to deletion
Q_ASSERT( status() == QgsTask::Terminated );
}
};
class TestQgsTaskManager : public QObject
{
Q_OBJECT
private slots:
void initTestCase();// will be called before the first testfunction is executed.
void cleanupTestCase();// will be called after the last testfunction was executed.
void init();// will be called before each testfunction is executed.
void cleanup();// will be called after every testfunction.
void task();
void createInstance();
void addTask();
void deleteTask();
void taskTerminationBeforeDelete();
void taskId();
void progressChanged();
void statusChanged();
private:
};
void TestQgsTaskManager::initTestCase()
{
}
void TestQgsTaskManager::cleanupTestCase()
{
}
void TestQgsTaskManager::init()
{
}
void TestQgsTaskManager::cleanup()
{
}
void TestQgsTaskManager::task()
{
QScopedPointer< TestTask > task( new TestTask( "desc" ) );
QCOMPARE( task->status(), QgsTask::Running );
QCOMPARE( task->description(), QString( "desc" ) );
QVERIFY( task->isActive() );
//test that calling stopped sets correct state
QSignalSpy stoppedSpy( task.data(), SIGNAL( taskStopped() ) );
QSignalSpy statusSpy( task.data(), SIGNAL( statusChanged( int ) ) );
task->emitTaskStopped();
QCOMPARE( task->status(), QgsTask::Terminated );
QVERIFY( !task->isActive() );
QCOMPARE( stoppedSpy.count(), 1 );
QCOMPARE( statusSpy.count(), 1 );
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy.last().at( 0 ).toInt() ), QgsTask::Terminated );
//test that calling completed sets correct state
task.reset( new TestTask() );
QSignalSpy completeSpy( task.data(), SIGNAL( taskCompleted() ) );
QSignalSpy statusSpy2( task.data(), SIGNAL( statusChanged( int ) ) );
task->emitTaskCompleted();
QCOMPARE( task->status(), QgsTask::Complete );
QVERIFY( !task->isActive() );
QCOMPARE( completeSpy.count(), 1 );
QCOMPARE( statusSpy2.count(), 1 );
QCOMPARE( static_cast< QgsTask::TaskStatus >( statusSpy2.last().at( 0 ).toInt() ), QgsTask::Complete );
}
void TestQgsTaskManager::createInstance()
{
QgsTaskManager* manager = QgsTaskManager::instance();
QVERIFY( manager );
}
void TestQgsTaskManager::addTask()
{
//create an empty manager
QgsTaskManager manager;
//should be empty
QVERIFY( manager.tasks().isEmpty() );
QCOMPARE( manager.count(), 0 );
QVERIFY( !manager.task( 0L ) );
QSignalSpy spy( &manager, SIGNAL( taskAdded( long ) ) );
//add a task
TestTask* task = new TestTask();
long id = manager.addTask( task );
QCOMPARE( id, 0L );
QCOMPARE( manager.tasks().count(), 1 );
QCOMPARE( manager.count(), 1 );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
//retrieve task
QCOMPARE( manager.task( 0L ), task );
QCOMPARE( manager.tasks().at( 0 ), task );
//add a second task
TestTask* task2 = new TestTask();
id = manager.addTask( task2 );
QCOMPARE( id, 1L );
QCOMPARE( manager.tasks().count(), 2 );
QCOMPARE( manager.count(), 2 );
QCOMPARE( manager.task( 0L ), task );
QCOMPARE( manager.tasks().at( 0 ), task );
QCOMPARE( manager.task( 1L ), task2 );
QCOMPARE( manager.tasks().at( 1 ), task2 );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
}
void TestQgsTaskManager::deleteTask()
{
//create manager with some tasks
QgsTaskManager manager;
TestTask* task = new TestTask();
TestTask* task2 = new TestTask();
TestTask* task3 = new TestTask();
manager.addTask( task );
manager.addTask( task2 );
manager.addTask( task3 );
QSignalSpy spy( &manager, SIGNAL( taskAboutToBeDeleted( long ) ) );
//try deleting a non-existant task
QVERIFY( !manager.deleteTask( 56 ) );
QCOMPARE( spy.count(), 0 );
//try deleting a task by ID
QVERIFY( manager.deleteTask( 1 ) );
QCOMPARE( manager.tasks().count(), 2 );
QVERIFY( !manager.task( 1 ) );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
//can't delete twice
QVERIFY( !manager.deleteTask( 1 ) );
QCOMPARE( spy.count(), 1 );
//delete task by reference
QVERIFY( manager.deleteTask( task ) );
QCOMPARE( manager.tasks().count(), 1 );
QVERIFY( !manager.task( 0 ) );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
}
void TestQgsTaskManager::taskTerminationBeforeDelete()
{
//test that task is terminated by manager prior to delete
QgsTaskManager* manager = new QgsTaskManager();
//TestTerminationTask will assert that it's been terminated prior to deletion
TestTask* task = new TestTerminationTask();
manager->addTask( task );
// if task is not terminated assert will trip
delete manager;
QApplication::sendPostedEvents( nullptr, QEvent::DeferredDelete );
}
void TestQgsTaskManager::taskId()
{
//test finding task IDs
//create manager with some tasks
QgsTaskManager manager;
TestTask* task = new TestTask();
TestTask* task2 = new TestTask();
manager.addTask( task );
manager.addTask( task2 );
//also a task not in the manager
TestTask* task3 = new TestTask();
QCOMPARE( manager.taskId( nullptr ), -1L );
QCOMPARE( manager.taskId( task ), 0L );
QCOMPARE( manager.taskId( task2 ), 1L );
QCOMPARE( manager.taskId( task3 ), -1L );
delete task3;
}
void TestQgsTaskManager::progressChanged()
{
// check that progressChanged signals emitted by tasks result in progressChanged signal from manager
QgsTaskManager manager;
TestTask* task = new TestTask();
TestTask* task2 = new TestTask();
manager.addTask( task );
manager.addTask( task2 );
QSignalSpy spy( &manager, SIGNAL( progressChanged( long, double ) ) );
task->emitProgressChanged( 50.0 );
QCOMPARE( task->progress(), 50.0 );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( spy.last().at( 1 ).toDouble(), 50.0 );
task2->emitProgressChanged( 75.0 );
QCOMPARE( task2->progress(), 75.0 );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
QCOMPARE( spy.last().at( 1 ).toDouble(), 75.0 );
}
void TestQgsTaskManager::statusChanged()
{
// check that statusChanged signals emitted by tasks result in statusChanged signal from manager
QgsTaskManager manager;
TestTask* task = new TestTask();
TestTask* task2 = new TestTask();
manager.addTask( task );
manager.addTask( task2 );
QSignalSpy spy( &manager, SIGNAL( statusChanged( long, int ) ) );
task->emitTaskStopped();
QCOMPARE( spy.count(), 1 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 0LL );
QCOMPARE( static_cast< QgsTask::TaskStatus >( spy.last().at( 1 ).toInt() ), QgsTask::Terminated );
task2->emitTaskCompleted();
QCOMPARE( spy.count(), 2 );
QCOMPARE( spy.last().at( 0 ).toLongLong(), 1LL );
QCOMPARE( static_cast< QgsTask::TaskStatus >( spy.last().at( 1 ).toInt() ), QgsTask::Complete );
}
QTEST_MAIN( TestQgsTaskManager )
#include "testqgstaskmanager.moc"