QGIS API Documentation 3.29.0-Master (ade4f0cf0f)
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( LayerIdRole ).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, LayerIdRole, 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 LayerIdRole:
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 LayerRole:
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 EmptyRole:
346 return isEmpty;
347
348 case AdditionalRole:
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
408 return QVariant();
409}
410
411QHash<int, QByteArray> QgsMapLayerModel::roleNames() const
412{
413 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
414 roles[LayerIdRole] = "layerId";
415 roles[LayerRole] = "layer";
416
417 return roles;
418}
419
420Qt::ItemFlags QgsMapLayerModel::flags( const QModelIndex &index ) const
421{
422 if ( !index.isValid() )
423 {
424 if ( mCanReorder )
425 return Qt::ItemIsDropEnabled;
426 else
427 return Qt::ItemFlags();
428 }
429
430 bool isEmpty = index.row() == 0 && mAllowEmpty;
431 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
432
433 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
434
435 if ( mCanReorder && !isEmpty && additionalIndex < 0 )
436 {
437 flags |= Qt::ItemIsDragEnabled;
438 }
439
440 if ( mItemCheckable && !isEmpty && additionalIndex < 0 )
441 {
442 flags |= Qt::ItemIsUserCheckable;
443 }
444 return flags;
445}
446
447bool QgsMapLayerModel::insertRows( int row, int count, const QModelIndex &parent )
448{
449 if ( parent.isValid() )
450 return false;
451
452 int offset = 0;
453 if ( mAllowEmpty )
454 offset++;
455
456 beginInsertRows( parent, row, row + count - 1 );
457 for ( int i = row; i < row + count; ++i )
458 mLayers.insert( i - offset, nullptr );
459 endInsertRows();
460
461 return true;
462}
463
464bool QgsMapLayerModel::removeRows( int row, int count, const QModelIndex &parent )
465{
466 if ( parent.isValid() || row < 0 )
467 return false;
468
469 int offset = 0;
470 if ( mAllowEmpty )
471 {
472 if ( row == 0 )
473 return false;
474
475 offset++;
476 }
477
478 if ( row - offset > mLayers.count() - 1 )
479 {
480 return false;
481 }
482
483 beginRemoveRows( parent, row, row + count - 1 );
484 for ( int i = 0; i != count; ++i )
485 mLayers.removeAt( row - offset );
486 endRemoveRows();
487
488 return true;
489}
490
492{
493 QStringList types;
494 types << QStringLiteral( "application/qgis.layermodeldata" );
495 return types;
496}
497
498bool QgsMapLayerModel::canDropMimeData( const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex & ) const
499{
500 if ( !mCanReorder || action != Qt::MoveAction || !data->hasFormat( QStringLiteral( "application/qgis.layermodeldata" ) ) )
501 return false;
502 return true;
503}
504
505QMimeData *QgsMapLayerModel::mimeData( const QModelIndexList &indexes ) const
506{
507 std::unique_ptr< QMimeData > mimeData = std::make_unique< QMimeData >();
508
509 QByteArray encodedData;
510 QDataStream stream( &encodedData, QIODevice::WriteOnly );
511 QSet< QString > addedLayers;
512
513 for ( const QModelIndex &i : indexes )
514 {
515 if ( i.isValid() )
516 {
517 const QString id = data( index( i.row(), 0, i.parent() ), LayerIdRole ).toString();
518 if ( !addedLayers.contains( id ) )
519 {
520 addedLayers.insert( id );
521 stream << id;
522 }
523 }
524 }
525 mimeData->setData( QStringLiteral( "application/qgis.layermodeldata" ), encodedData );
526 return mimeData.release();
527}
528
529bool QgsMapLayerModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
530{
531 if ( !canDropMimeData( data, action, row, column, parent ) || row < 0 )
532 return false;
533
534 if ( action == Qt::IgnoreAction )
535 return true;
536 else if ( action != Qt::MoveAction )
537 return false;
538
539 QByteArray encodedData = data->data( QStringLiteral( "application/qgis.layermodeldata" ) );
540 QDataStream stream( &encodedData, QIODevice::ReadOnly );
541 QStringList newItems;
542 int rows = 0;
543
544 while ( !stream.atEnd() )
545 {
546 QString text;
547 stream >> text;
548 newItems << text;
549 ++rows;
550 }
551
552 insertRows( row, rows, QModelIndex() );
553 for ( const QString &text : std::as_const( newItems ) )
554 {
555 QModelIndex idx = index( row, 0, QModelIndex() );
556 setData( idx, text, LayerIdRole );
557 row++;
558 }
559
560 return true;
561}
562
564{
565 return Qt::MoveAction;
566}
567
569{
570 return QgsIconUtils::iconForLayer( layer );
571}
572
573bool QgsMapLayerModel::setData( const QModelIndex &index, const QVariant &value, int role )
574{
575 if ( !index.isValid() )
576 return false;
577
578 bool isEmpty = index.row() == 0 && mAllowEmpty;
579 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
580
581 switch ( role )
582 {
583 case Qt::CheckStateRole:
584 {
585 if ( !isEmpty && additionalIndex < 0 )
586 {
587 QgsMapLayer *layer = static_cast<QgsMapLayer *>( index.internalPointer() );
588 mLayersChecked[layer->id()] = ( Qt::CheckState )value.toInt();
589 emit dataChanged( index, index, QVector< int >() << Qt::CheckStateRole );
590 return true;
591 }
592 break;
593 }
594
595 case LayerIdRole:
596 if ( !isEmpty && additionalIndex < 0 )
597 {
598 mLayers[index.row() - ( mAllowEmpty ? 1 : 0 )] = mProject->mapLayer( value.toString() );
599 emit dataChanged( index, index );
600 return true;
601 }
602 break;
603 }
604
605 return false;
606}
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
@ EmptyRole
True if index corresponds to the empty (not set) value.
@ LayerIdRole
Stores the map layer ID.
@ LayerRole
Stores pointer to the map layer itself.
@ AdditionalRole
True if index corresponds to an additional (non map layer) item.
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
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:73
QString name
Definition: qgsmaplayer.h:76
virtual bool isSpatial() const
Returns true if the layer is considered a spatial layer, ie it has some form of geometry associated w...
QString publicSource() const
Gets a version of the internal layer definition that has sensitive bits removed (for example,...
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
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 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:311
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:105
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:476
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(Type type) SIP_HOLDGIL
Returns a non-translated display string type for a WKB type, e.g., the geometry name used in WKT geom...