Skip to content

Commit

Permalink
[api] Optionally allow items in QgsMapLayerModel to be reordered via …
Browse files Browse the repository at this point in the history
…drag and drop
  • Loading branch information
nyalldawson committed Jun 9, 2020
1 parent 9e23fb9 commit 154a499
Show file tree
Hide file tree
Showing 4 changed files with 454 additions and 26 deletions.
47 changes: 42 additions & 5 deletions python/core/auto_generated/qgsmaplayermodel.sip.in
Expand Up @@ -10,7 +10,6 @@




class QgsMapLayerModel : QAbstractItemModel
{
%Docstring
Expand All @@ -36,19 +35,43 @@ The QgsMapLayerModel class is a model to display layers in widgets.
AdditionalRole,
};

explicit QgsMapLayerModel( QObject *parent /TransferThis/ = 0 );
explicit QgsMapLayerModel( QObject *parent /TransferThis/ = 0, QgsProject *project = 0 );
%Docstring
QgsMapLayerModel creates a model to display layers in widgets.

If a specific ``project`` is not specified then the QgsProject.instance() project will be used to
populate the model.
%End

explicit QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent = 0 );
explicit QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent = 0, QgsProject *project = 0 );
%Docstring
QgsMapLayerModel creates a model to display a specific list of layers in a widget.

If a specific ``project`` is not specified then the QgsProject.instance() project will be used to
populate the model.
%End

void setItemsCheckable( bool checkable );
%Docstring
setItemsCheckable defines if layers should be selectable in the widget
%End

void setItemsCanBeReordered( bool allow );
%Docstring
Sets whether items in the model can be reordered via drag and drop.

.. seealso:: :py:func:`itemsCanBeReordered`

.. versionadded:: 3.14
%End

bool itemsCanBeReordered() const;
%Docstring
Returns ``True`` if items in the model can be reordered via drag and drop.

.. seealso:: :py:func:`setItemsCanBeReordered`

.. versionadded:: 3.14
%End

void checkAll( Qt::CheckState checkState );
Expand Down Expand Up @@ -147,12 +170,25 @@ Returns the list of additional (non map layer) items included at the end of the

virtual QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const;



virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole );

virtual Qt::ItemFlags flags( const QModelIndex &index ) const;

virtual bool insertRows( int row, int count, const QModelIndex &parent = QModelIndex() );

virtual bool removeRows( int row, int count, const QModelIndex &parent = QModelIndex() );

virtual QStringList mimeTypes() const;

virtual bool canDropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent ) const;

virtual QMimeData *mimeData( const QModelIndexList &indexes ) const;

virtual bool dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent );

virtual Qt::DropActions supportedDropActions() const;



static QIcon iconForLayer( QgsMapLayer *layer );
%Docstring
Expand All @@ -167,6 +203,7 @@ Returns the icon corresponding to a specified map ``layer``.

protected:


};

/************************************************************************
Expand Down
184 changes: 169 additions & 15 deletions src/core/qgsmaplayermodel.cpp
Expand Up @@ -21,27 +21,38 @@
#include "qgsapplication.h"
#include "qgsvectorlayer.h"


QgsMapLayerModel::QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent )
QgsMapLayerModel::QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent, QgsProject *project )
: QAbstractItemModel( parent )
, mProject( project ? project : QgsProject::instance() )
{
connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
addLayers( layers );
}

QgsMapLayerModel::QgsMapLayerModel( QObject *parent )
QgsMapLayerModel::QgsMapLayerModel( QObject *parent, QgsProject *project )
: QAbstractItemModel( parent )
, mProject( project ? project : QgsProject::instance() )
{
connect( QgsProject::instance(), &QgsProject::layersAdded, this, &QgsMapLayerModel::addLayers );
connect( QgsProject::instance(), static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
addLayers( QgsProject::instance()->mapLayers().values() );
connect( mProject, &QgsProject::layersAdded, this, &QgsMapLayerModel::addLayers );
connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
addLayers( mProject->mapLayers().values() );
}

void QgsMapLayerModel::setItemsCheckable( bool checkable )
{
mItemCheckable = checkable;
}

void QgsMapLayerModel::setItemsCanBeReordered( bool allow )
{
mCanReorder = allow;
}

bool QgsMapLayerModel::itemsCanBeReordered() const
{
return mCanReorder;
}

void QgsMapLayerModel::checkAll( Qt::CheckState checkState )
{
QMap<QString, Qt::CheckState>::iterator i = mLayersChecked.begin();
Expand Down Expand Up @@ -138,8 +149,7 @@ void QgsMapLayerModel::removeLayers( const QStringList &layerIds )
if ( mAllowEmpty )
offset++;

const auto constLayerIds = layerIds;
for ( const QString &layerId : constLayerIds )
for ( const QString &layerId : layerIds )
{
QModelIndex startIndex = index( 0, 0 );
QModelIndexList list = match( startIndex, LayerIdRole, layerId, 1 );
Expand Down Expand Up @@ -340,20 +350,145 @@ Qt::ItemFlags QgsMapLayerModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
{
return nullptr;
if ( mCanReorder )
return Qt::ItemIsDropEnabled;
else
return nullptr;
}

bool isEmpty = index.row() == 0 && mAllowEmpty;
int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();

Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;

if ( mCanReorder && !isEmpty && additionalIndex < 0 )
{
flags |= Qt::ItemIsDragEnabled;
}

if ( mItemCheckable && !isEmpty && additionalIndex < 0 )
{
flags |= Qt::ItemIsUserCheckable;
}
return flags;
}

bool QgsMapLayerModel::insertRows( int row, int count, const QModelIndex &parent )
{
if ( parent.isValid() )
return false;

int offset = 0;
if ( mAllowEmpty )
offset++;

beginInsertRows( parent, row, row + count - 1 );
for ( int i = row; i < row + count; ++i )
mLayers.insert( i - offset, nullptr );
endInsertRows();

return true;
}

bool QgsMapLayerModel::removeRows( int row, int count, const QModelIndex &parent )
{
if ( parent.isValid() || row < 0 )
return false;

int offset = 0;
if ( mAllowEmpty )
{
if ( row == 0 )
return false;

offset++;
}

if ( row - offset > mLayers.count() - 1 )
{
return false;
}

beginRemoveRows( parent, row, row + count - 1 );
for ( int i = 0; i != count; ++i )
mLayers.removeAt( row - offset );
endRemoveRows();

return true;
}

QStringList QgsMapLayerModel::mimeTypes() const
{
QStringList types;
types << QStringLiteral( "application/qgis.layermodeldata" );
return types;
}

bool QgsMapLayerModel::canDropMimeData( const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex & ) const
{
if ( !mCanReorder || action != Qt::MoveAction || !data->hasFormat( QStringLiteral( "application/qgis.layermodeldata" ) ) )
return false;
return true;
}

QMimeData *QgsMapLayerModel::mimeData( const QModelIndexList &indexes ) const
{
std::unique_ptr< QMimeData > mimeData = qgis::make_unique< QMimeData >();

QByteArray encodedData;
QDataStream stream( &encodedData, QIODevice::WriteOnly );

for ( const QModelIndex &index : indexes )
{
if ( index.isValid() )
{
QString text = data( index, LayerIdRole ).toString();
stream << text;
}
}
mimeData->setData( QStringLiteral( "application/qgis.layermodeldata" ), encodedData );
return mimeData.release();
}

bool QgsMapLayerModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
if ( !canDropMimeData( data, action, row, column, parent ) || row < 0 )
return false;

if ( action == Qt::IgnoreAction )
return true;
else if ( action != Qt::MoveAction )
return false;

QByteArray encodedData = data->data( QStringLiteral( "application/qgis.layermodeldata" ) );
QDataStream stream( &encodedData, QIODevice::ReadOnly );
QStringList newItems;
int rows = 0;

while ( !stream.atEnd() )
{
QString text;
stream >> text;
newItems << text;
++rows;
}

insertRows( row, rows, QModelIndex() );
for ( const QString &text : qgis::as_const( newItems ) )
{
QModelIndex idx = index( row, 0, QModelIndex() );
setData( idx, text, LayerIdRole );
row++;
}

return true;
}

Qt::DropActions QgsMapLayerModel::supportedDropActions() const
{
return Qt::MoveAction;
}

QIcon QgsMapLayerModel::iconForLayer( QgsMapLayer *layer )
{
switch ( layer->type() )
Expand Down Expand Up @@ -415,15 +550,34 @@ QIcon QgsMapLayerModel::iconForLayer( QgsMapLayer *layer )

bool QgsMapLayerModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
if ( !index.isValid() )
return false;

bool isEmpty = index.row() == 0 && mAllowEmpty;
int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();

if ( role == Qt::CheckStateRole && !isEmpty && additionalIndex < 0 )
switch ( role )
{
QgsMapLayer *layer = static_cast<QgsMapLayer *>( index.internalPointer() );
mLayersChecked[layer->id()] = ( Qt::CheckState )value.toInt();
emit dataChanged( index, index );
return true;
case Qt::CheckStateRole:
{
if ( !isEmpty && additionalIndex < 0 )
{
QgsMapLayer *layer = static_cast<QgsMapLayer *>( index.internalPointer() );
mLayersChecked[layer->id()] = ( Qt::CheckState )value.toInt();
emit dataChanged( index, index );
return true;
}
break;
}

case LayerIdRole:
if ( !isEmpty && additionalIndex < 0 )
{
mLayers[index.row() - ( mAllowEmpty ? 1 : 0 )] = mProject->mapLayer( value.toString() );
emit dataChanged( index, index );
return true;
}
break;
}

return false;
Expand Down

0 comments on commit 154a499

Please sign in to comment.