QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsmaplayermodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsmaplayermodel.cpp
3 --------------------------------------
4 Date : 01.04.2014
5 Copyright : (C) 2014 Denis Rouzaud
7***************************************************************************
8* *
9* This program is free software; you can redistribute it and/or modify *
10* it under the terms of the GNU General Public License as published by *
11* the Free Software Foundation; either version 2 of the License, or *
12* (at your option) any later version. *
13* *
14***************************************************************************/
15
16#include <QIcon>
17
18#include "qgsmaplayermodel.h"
19#include "qgsproject.h"
20#include "qgsapplication.h"
21#include "qgsvectorlayer.h"
22#include "qgsiconutils.h"
24#include <QMimeData>
25
26QgsMapLayerModel::QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent, QgsProject *project )
27 : QAbstractItemModel( parent )
28 , mProject( project ? project : QgsProject::instance() )
29{
30 connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
31 addLayers( layers );
32}
33
35 : QAbstractItemModel( parent )
36 , mProject( project ? project : QgsProject::instance() )
37{
39 connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
40 addLayers( mProject->mapLayers().values() );
41}
42
44{
45
46 // remove layers from previous project
47 if ( mProject )
48 {
49 removeLayers( mProject->mapLayers().keys() );
51 disconnect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
52 }
53
54 mProject = project ? project : QgsProject::instance();
55
57 connect( mProject, static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
58 addLayers( mProject->mapLayers().values() );
59}
60
61
63{
64 mItemCheckable = checkable;
65}
66
68{
69 mCanReorder = allow;
70}
71
73{
74 return mCanReorder;
75}
76
77void QgsMapLayerModel::checkAll( Qt::CheckState checkState )
78{
79 QMap<QString, Qt::CheckState>::iterator i = mLayersChecked.begin();
80 for ( ; i != mLayersChecked.end(); ++i )
81 {
82 *i = checkState;
83 }
84 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ) );
85}
86
87void QgsMapLayerModel::setAllowEmptyLayer( bool allowEmpty, const QString &text, const QIcon &icon )
88{
89 mEmptyText = text;
90 mEmptyIcon = icon;
91 if ( allowEmpty == mAllowEmpty )
92 return;
93
94 if ( allowEmpty )
95 {
96 beginInsertRows( QModelIndex(), 0, 0 );
97 mAllowEmpty = true;
98 endInsertRows();
99 }
100 else
101 {
102 beginRemoveRows( QModelIndex(), 0, 0 );
103 mAllowEmpty = false;
104 endRemoveRows();
105 }
106}
107
109{
110 if ( mShowCrs == showCrs )
111 return;
112
113 mShowCrs = showCrs;
114 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::DisplayRole );
115}
116
117QList<QgsMapLayer *> QgsMapLayerModel::layersChecked( Qt::CheckState checkState )
118{
119 QList<QgsMapLayer *> layers;
120 const auto constMLayers = mLayers;
121 for ( QgsMapLayer *layer : constMLayers )
122 {
123 if ( mLayersChecked[layer->id()] == checkState )
124 {
125 layers.append( layer );
126 }
127 }
128 return layers;
129}
130
131void QgsMapLayerModel::setLayersChecked( const QList<QgsMapLayer *> &layers )
132{
133 QMap<QString, Qt::CheckState>::iterator i = mLayersChecked.begin();
134 for ( ; i != mLayersChecked.end(); ++i )
135 {
136 *i = Qt::Unchecked;
137 }
138 for ( const QgsMapLayer *layer : layers )
139 {
140 mLayersChecked[ layer->id() ] = Qt::Checked;
141 }
142 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::CheckStateRole );
143}
144
146{
147 int r = mLayers.indexOf( layer );
148 if ( r >= 0 && mAllowEmpty )
149 r++;
150 return index( r, 0 );
151}
152
153QgsMapLayer *QgsMapLayerModel::layerFromIndex( const QModelIndex &index ) const
154{
155 return mProject->mapLayer( index.data( static_cast< int >( CustomRole::LayerId ) ).toString() );
156}
157
158void QgsMapLayerModel::setAdditionalItems( const QStringList &items )
159{
160 if ( items == mAdditionalItems )
161 return;
162
163 int offset = 0;
164 if ( mAllowEmpty )
165 offset++;
166
167 offset += mLayers.count();
168
169 //remove existing
170 if ( !mAdditionalItems.isEmpty() )
171 {
172 beginRemoveRows( QModelIndex(), offset, offset + mAdditionalItems.count() - 1 );
173 mAdditionalItems.clear();
174 endRemoveRows();
175 }
176
177 //add new
178 beginInsertRows( QModelIndex(), offset, offset + items.count() - 1 );
179 mAdditionalItems = items;
180 endInsertRows();
181}
182
183void QgsMapLayerModel::setAdditionalLayers( const QList<QgsMapLayer *> &layers )
184{
185 if ( layers == _qgis_listQPointerToRaw( mAdditionalLayers ) )
186 return;
187
188 QStringList layerIdsToRemove;
189 for ( QgsMapLayer *layer : std::as_const( mAdditionalLayers ) )
190 {
191 if ( layer )
192 layerIdsToRemove << layer->id();
193 }
194 removeLayers( layerIdsToRemove );
195
196 for ( QgsMapLayer *layer : layers )
197 {
198 if ( layer )
199 {
200 addLayers( { layer } );
201 const QString layerId = layer->id();
202 connect( layer, &QgsMapLayer::willBeDeleted, this, [this, layerId] { removeLayers( {layerId} ); } );
203 }
204 }
205
206 mAdditionalLayers = _qgis_listRawToQPointer( layers );
207}
208
209QList<QgsMapLayer *> QgsMapLayerModel::additionalLayers() const
210{
211 return _qgis_listQPointerToRaw( mAdditionalLayers );
212}
213
214void QgsMapLayerModel::removeLayers( const QStringList &layerIds )
215{
216 int offset = 0;
217 if ( mAllowEmpty )
218 offset++;
219
220 for ( const QString &layerId : layerIds )
221 {
222 QModelIndex startIndex = index( 0, 0 );
223 QModelIndexList list = match( startIndex, static_cast< int >( CustomRole::LayerId ), layerId, 1 );
224 if ( !list.isEmpty() )
225 {
226 QModelIndex index = list[0];
227 beginRemoveRows( QModelIndex(), index.row(), index.row() );
228 mLayersChecked.remove( layerId );
229 mLayers.removeAt( index.row() - offset );
230 endRemoveRows();
231 }
232 }
233}
234
235void QgsMapLayerModel::addLayers( const QList<QgsMapLayer *> &layers )
236{
237 if ( !layers.empty( ) )
238 {
239 int offset = 0;
240 if ( mAllowEmpty )
241 offset++;
242
243 beginInsertRows( QModelIndex(), mLayers.count() + offset, mLayers.count() + layers.count() - 1 + offset );
244 const auto constLayers = layers;
245 for ( QgsMapLayer *layer : constLayers )
246 {
247 mLayers.append( layer );
248 mLayersChecked.insert( layer->id(), Qt::Unchecked );
249 }
250 endInsertRows();
251 }
252}
253
254QModelIndex QgsMapLayerModel::index( int row, int column, const QModelIndex &parent ) const
255{
256 int offset = 0;
257 if ( mAllowEmpty )
258 offset++;
259
260 if ( hasIndex( row, column, parent ) )
261 {
262 QgsMapLayer *layer = nullptr;
263 if ( row - offset >= 0 && row - offset < mLayers.count() )
264 layer = mLayers.at( row - offset );
265
266 return createIndex( row, column, layer );
267 }
268
269 return QModelIndex();
270
271}
272
273QModelIndex QgsMapLayerModel::parent( const QModelIndex &child ) const
274{
275 Q_UNUSED( child )
276 return QModelIndex();
277}
278
279
280int QgsMapLayerModel::rowCount( const QModelIndex &parent ) const
281{
282 if ( parent.isValid() )
283 return 0;
284
285 return ( mAllowEmpty ? 1 : 0 ) + mLayers.length() + mAdditionalItems.count();
286}
287
288int QgsMapLayerModel::columnCount( const QModelIndex &parent ) const
289{
290 Q_UNUSED( parent )
291 return 1;
292}
293
294
295QVariant QgsMapLayerModel::data( const QModelIndex &index, int role ) const
296{
297 if ( !index.isValid() )
298 return QVariant();
299
300 bool isEmpty = index.row() == 0 && mAllowEmpty;
301 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
302
303 switch ( role )
304 {
305 case Qt::DisplayRole:
306 case Qt::EditRole:
307 {
308 if ( index.row() == 0 && mAllowEmpty )
309 return mEmptyText;
310
311 if ( additionalIndex >= 0 )
312 return mAdditionalItems.at( additionalIndex );
313
314 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
315 if ( !layer )
316 return QVariant();
317
318 if ( !mShowCrs || !layer->isSpatial() || role == Qt::EditRole )
319 {
320 return layer->name();
321 }
322 else
323 {
324 return tr( "%1 [%2]" ).arg( layer->name(), layer->crs().authid() );
325 }
326 }
327
328 case static_cast< int >( CustomRole::LayerId ):
329 {
330 if ( isEmpty || additionalIndex >= 0 )
331 return QVariant();
332
333 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
334 return layer ? layer->id() : QVariant();
335 }
336
337 case static_cast< int >( CustomRole::Layer ):
338 {
339 if ( isEmpty || additionalIndex >= 0 )
340 return QVariant();
341
342 return QVariant::fromValue<QgsMapLayer *>( mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) ) );
343 }
344
345 case static_cast< int >( CustomRole::Empty ):
346 return isEmpty;
347
348 case static_cast< int >( CustomRole::Additional ):
349 return additionalIndex >= 0;
350
351 case Qt::CheckStateRole:
352 {
353 if ( mItemCheckable )
354 {
355 if ( isEmpty || additionalIndex >= 0 )
356 return QVariant();
357
358 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
359 return layer ? mLayersChecked[layer->id()] : QVariant();
360 }
361
362 return QVariant();
363 }
364
365 case Qt::ToolTipRole:
366 {
367 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
368 if ( layer )
369 {
370 QStringList parts;
371 QString title = layer->title().isEmpty() ? layer->shortName() : layer->title();
372 if ( title.isEmpty() )
373 title = layer->name();
374 title = "<b>" + title + "</b>";
375 if ( layer->isSpatial() && layer->crs().isValid() )
376 {
377 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
378 title = tr( "%1 (%2 - %3)" ).arg( title, QgsWkbTypes::displayString( vl->wkbType() ), layer->crs().authid() );
379 else
380 title = tr( "%1 (%2) " ).arg( title, layer->crs().authid() );
381 }
382 parts << title;
383
384 if ( !layer->abstract().isEmpty() )
385 parts << "<br/>" + layer->abstract().replace( QLatin1String( "\n" ), QLatin1String( "<br/>" ) );
386 parts << "<i>" + layer->publicSource() + "</i>";
387 return parts.join( QLatin1String( "<br/>" ) );
388 }
389 return QVariant();
390 }
391
392 case Qt::DecorationRole:
393 {
394 if ( isEmpty )
395 return mEmptyIcon.isNull() ? QVariant() : mEmptyIcon;
396
397 if ( additionalIndex >= 0 )
398 return QVariant();
399
400 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
401 if ( !layer )
402 return QVariant();
403
404 return iconForLayer( layer );
405 }
406
407 default:
408 break;
409 }
410
411 return QVariant();
412}
413
414QHash<int, QByteArray> QgsMapLayerModel::roleNames() const
415{
416 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
417 roles[static_cast< int >( CustomRole::LayerId ) ] = "layerId";
418 roles[static_cast< int >( CustomRole::Layer )] = "layer";
419
420 return roles;
421}
422
423Qt::ItemFlags QgsMapLayerModel::flags( const QModelIndex &index ) const
424{
425 if ( !index.isValid() )
426 {
427 if ( mCanReorder )
428 return Qt::ItemIsDropEnabled;
429 else
430 return Qt::ItemFlags();
431 }
432
433 bool isEmpty = index.row() == 0 && mAllowEmpty;
434 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
435
436 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
437
438 if ( mCanReorder && !isEmpty && additionalIndex < 0 )
439 {
440 flags |= Qt::ItemIsDragEnabled;
441 }
442
443 if ( mItemCheckable && !isEmpty && additionalIndex < 0 )
444 {
445 flags |= Qt::ItemIsUserCheckable;
446 }
447 return flags;
448}
449
450bool QgsMapLayerModel::insertRows( int row, int count, const QModelIndex &parent )
451{
452 if ( parent.isValid() )
453 return false;
454
455 int offset = 0;
456 if ( mAllowEmpty )
457 offset++;
458
459 beginInsertRows( parent, row, row + count - 1 );
460 for ( int i = row; i < row + count; ++i )
461 mLayers.insert( i - offset, nullptr );
462 endInsertRows();
463
464 return true;
465}
466
467bool QgsMapLayerModel::removeRows( int row, int count, const QModelIndex &parent )
468{
469 if ( parent.isValid() || row < 0 )
470 return false;
471
472 int offset = 0;
473 if ( mAllowEmpty )
474 {
475 if ( row == 0 )
476 return false;
477
478 offset++;
479 }
480
481 if ( row - offset > mLayers.count() - 1 )
482 {
483 return false;
484 }
485
486 beginRemoveRows( parent, row, row + count - 1 );
487 for ( int i = 0; i != count; ++i )
488 mLayers.removeAt( row - offset );
489 endRemoveRows();
490
491 return true;
492}
493
495{
496 QStringList types;
497 types << QStringLiteral( "application/qgis.layermodeldata" );
498 return types;
499}
500
501bool QgsMapLayerModel::canDropMimeData( const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex & ) const
502{
503 if ( !mCanReorder || action != Qt::MoveAction || !data->hasFormat( QStringLiteral( "application/qgis.layermodeldata" ) ) )
504 return false;
505 return true;
506}
507
508QMimeData *QgsMapLayerModel::mimeData( const QModelIndexList &indexes ) const
509{
510 std::unique_ptr< QMimeData > mimeData = std::make_unique< QMimeData >();
511
512 QByteArray encodedData;
513 QDataStream stream( &encodedData, QIODevice::WriteOnly );
514 QSet< QString > addedLayers;
515
516 for ( const QModelIndex &i : indexes )
517 {
518 if ( i.isValid() )
519 {
520 const QString id = data( index( i.row(), 0, i.parent() ), static_cast< int >( CustomRole::LayerId ) ).toString();
521 if ( !addedLayers.contains( id ) )
522 {
523 addedLayers.insert( id );
524 stream << id;
525 }
526 }
527 }
528 mimeData->setData( QStringLiteral( "application/qgis.layermodeldata" ), encodedData );
529 return mimeData.release();
530}
531
532bool QgsMapLayerModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
533{
534 if ( !canDropMimeData( data, action, row, column, parent ) || row < 0 )
535 return false;
536
537 if ( action == Qt::IgnoreAction )
538 return true;
539 else if ( action != Qt::MoveAction )
540 return false;
541
542 QByteArray encodedData = data->data( QStringLiteral( "application/qgis.layermodeldata" ) );
543 QDataStream stream( &encodedData, QIODevice::ReadOnly );
544 QStringList newItems;
545 int rows = 0;
546
547 while ( !stream.atEnd() )
548 {
549 QString text;
550 stream >> text;
551 newItems << text;
552 ++rows;
553 }
554
555 insertRows( row, rows, QModelIndex() );
556 for ( const QString &text : std::as_const( newItems ) )
557 {
558 QModelIndex idx = index( row, 0, QModelIndex() );
559 setData( idx, text, static_cast< int >( CustomRole::LayerId ) );
560 row++;
561 }
562
563 return true;
564}
565
567{
568 return Qt::MoveAction;
569}
570
572{
573 return QgsIconUtils::iconForLayer( layer );
574}
575
576bool QgsMapLayerModel::setData( const QModelIndex &index, const QVariant &value, int role )
577{
578 if ( !index.isValid() )
579 return false;
580
581 bool isEmpty = index.row() == 0 && mAllowEmpty;
582 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
583
584 switch ( role )
585 {
586 case Qt::CheckStateRole:
587 {
588 if ( !isEmpty && additionalIndex < 0 )
589 {
590 QgsMapLayer *layer = static_cast<QgsMapLayer *>( index.internalPointer() );
591 mLayersChecked[layer->id()] = ( Qt::CheckState )value.toInt();
592 emit dataChanged( index, index, QVector< int >() << Qt::CheckStateRole );
593 return true;
594 }
595 break;
596 }
597
598 case static_cast< int >( CustomRole::LayerId ):
599 if ( !isEmpty && additionalIndex < 0 )
600 {
601 mLayers[index.row() - ( mAllowEmpty ? 1 : 0 )] = mProject->mapLayer( value.toString() );
602 emit dataChanged( index, index );
603 return true;
604 }
605 break;
606
607 default:
608 break;
609 }
610
611 return false;
612}
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
static QIcon iconForLayer(const QgsMapLayer *layer)
Returns the icon corresponding to a specified map layer.
QList< QPointer< QgsMapLayer > > mAdditionalLayers
void setShowCrs(bool showCrs)
Sets whether the CRS of layers is also included in the model's display role.
void setItemsCanBeReordered(bool allow)
Sets whether items in the model can be reordered via drag and drop.
QHash< int, QByteArray > roleNames() const override
Returns strings for all roles supported by this model.
Qt::ItemFlags flags(const QModelIndex &index) const override
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QList< QgsMapLayer * > layersChecked(Qt::CheckState checkState=Qt::Checked)
layersChecked returns the list of layers which are checked (or unchecked)
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
bool insertRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QModelIndex parent(const QModelIndex &child) const override
QgsProject * mProject
void setProject(QgsProject *project)
Sets the QgsProject from which map layers are shown.
bool removeRows(int row, int count, const QModelIndex &parent=QModelIndex()) override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QModelIndex indexFromLayer(QgsMapLayer *layer) const
indexFromLayer returns the model index for a given layer
void setAllowEmptyLayer(bool allowEmpty, const QString &text=QString(), const QIcon &icon=QIcon())
Sets whether an optional empty layer ("not set") option is present in the model.
void setAdditionalItems(const QStringList &items)
Sets a list of additional (non map layer) items to include at the end of the model.
QList< QgsMapLayer * > additionalLayers() const
Returns the list of additional layers added to the model.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void setItemsCheckable(bool checkable)
setItemsCheckable defines if layers should be selectable in the widget
static QIcon iconForLayer(QgsMapLayer *layer)
Returns the icon corresponding to a specified map layer.
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QgsMapLayer * layerFromIndex(const QModelIndex &index) const
Returns the map layer corresponding to the specified index.
Qt::DropActions supportedDropActions() const override
void setLayersChecked(const QList< QgsMapLayer * > &layers)
Sets which layers are checked in the model.
void checkAll(Qt::CheckState checkState)
checkAll changes the checkstate for all the layers
void removeLayers(const QStringList &layerIds)
QStringList mimeTypes() const override
@ Additional
True if index corresponds to an additional (non map layer) item.
@ Layer
Stores pointer to the map layer itself.
@ LayerId
Stores the map layer ID.
@ Empty
True if index corresponds to the empty (not set) value.
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override
bool itemsCanBeReordered() const
Returns true if items in the model can be reordered via drag and drop.
QMap< QString, Qt::CheckState > mLayersChecked
QgsMapLayerModel(QObject *parent=nullptr, QgsProject *project=nullptr)
QgsMapLayerModel creates a model to display layers in widgets.
QList< QgsMapLayer * > mLayers
void setAdditionalLayers(const QList< QgsMapLayer * > &layers)
Sets a list of additional layers to include in the model.
void addLayers(const QList< QgsMapLayer * > &layers)
QMimeData * mimeData(const QModelIndexList &indexes) const override
Base class for all map layer types.
Definition: qgsmaplayer.h:75
QString name
Definition: qgsmaplayer.h:78
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QString abstract() const
Returns the abstract of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:327
QString publicSource(bool hidePassword=false) const
Gets a version of the internal layer definition that has sensitive bits removed (for example,...
QString shortName() const
Returns the short name of the layer used by QGIS Server to identify the layer.
QString title() const
Returns the title of the layer used by QGIS Server in GetCapabilities request.
Definition: qgsmaplayer.h:312
void willBeDeleted()
Emitted in the destructor when the layer is about to be deleted, but it is still in a perfectly valid...
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
void layersAdded(const QList< QgsMapLayer * > &layers)
Emitted when one or more layers were added to the registry.
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Represents a vector layer which manages a vector based data sets.
static QString displayString(Qgis::WkbType type)
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...