QGIS API Documentation 4.3.0-Master (767b36bf018)
Loading...
Searching...
No Matches
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
6 Email : denis.rouzaud@gmail.com
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 "qgsmaplayermodel.h"
17
18#include "qgsiconutils.h"
20#include "qgsmaplayerutils.h"
21#include "qgsproject.h"
22#include "qgsvectorlayer.h"
23
24#include <QIcon>
25#include <QMimeData>
26#include <QString>
27
28#include "moc_qgsmaplayermodel.cpp"
29
30using namespace Qt::StringLiterals;
31
32// TODO QGIS 5.0 Remove deprecated constructor
33QgsMapLayerModel::QgsMapLayerModel( const QList<QgsMapLayer *> &layers, QObject *parent, QgsProject *project )
34 : QgsMapLayerModel( project ? project : QgsProject::instance(), layers, parent ) // skip-keyword-check
35{}
36
37// TODO QGIS 5.0 Remove deprecated constructor
39 : QgsMapLayerModel( project ? project : QgsProject::instance(), parent ) // skip-keyword-check
40{}
41
42QgsMapLayerModel::QgsMapLayerModel( QgsProject *project, const QList<QgsMapLayer *> &layers, QObject *parent )
43 : QAbstractItemModel( parent )
44 , mProject( project )
45{
46 if ( mProject )
47 {
48 connect( mProject, static_cast< void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
49 }
50 addLayers( layers );
51}
52
54 : QAbstractItemModel( parent )
55 , mProject( project )
56{
57 if ( mProject )
58 {
60 connect( mProject, static_cast< void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
61 addLayers( mProject->mapLayers().values() );
62 }
63}
64
66{
67 if ( !project )
68 return;
69
70 if ( mProject == project )
71 return;
72
73 // remove layers from previous project
74 if ( mProject )
75 {
76 removeLayers( mProject->mapLayers().keys() );
78 disconnect( mProject, static_cast< void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
79 }
80
81 mProject = project;
82
84 connect( mProject, static_cast< void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsMapLayerModel::removeLayers );
85 addLayers( mProject->mapLayers().values() );
86}
87
88
90{
91 mItemCheckable = checkable;
92}
93
95{
96 mCanReorder = allow;
97}
98
100{
101 return mCanReorder;
102}
103
104void QgsMapLayerModel::checkAll( Qt::CheckState checkState )
105{
106 QMap<QString, Qt::CheckState>::iterator i = mLayersChecked.begin();
107 for ( ; i != mLayersChecked.end(); ++i )
108 {
109 *i = checkState;
110 }
111 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ) );
112}
113
114void QgsMapLayerModel::setAllowEmptyLayer( bool allowEmpty, const QString &text, const QIcon &icon )
115{
116 mEmptyText = text;
117 mEmptyIcon = icon;
118 if ( allowEmpty == mAllowEmpty )
119 return;
120
121 if ( allowEmpty )
122 {
123 beginInsertRows( QModelIndex(), 0, 0 );
124 mAllowEmpty = true;
125 endInsertRows();
126 }
127 else
128 {
129 beginRemoveRows( QModelIndex(), 0, 0 );
130 mAllowEmpty = false;
131 endRemoveRows();
132 }
133}
134
136{
137 if ( mShowCrs == showCrs )
138 return;
139
140 mShowCrs = showCrs;
141 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::DisplayRole );
142}
143
144QList<QgsMapLayer *> QgsMapLayerModel::layersChecked( Qt::CheckState checkState )
145{
146 QList<QgsMapLayer *> layers;
147 const auto constMLayers = mLayers;
148 for ( QgsMapLayer *layer : constMLayers )
149 {
150 if ( mLayersChecked[layer->id()] == checkState )
151 {
152 layers.append( layer );
153 }
154 }
155 return layers;
156}
157
158void QgsMapLayerModel::setLayersChecked( const QList<QgsMapLayer *> &layers )
159{
160 QMap<QString, Qt::CheckState>::iterator i = mLayersChecked.begin();
161 for ( ; i != mLayersChecked.end(); ++i )
162 {
163 *i = Qt::Unchecked;
164 }
165 for ( const QgsMapLayer *layer : layers )
166 {
167 mLayersChecked[layer->id()] = Qt::Checked;
168 }
169 emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ), QVector<int>() << Qt::CheckStateRole );
170}
171
173{
174 int r = mLayers.indexOf( layer );
175 if ( r >= 0 && mAllowEmpty )
176 r++;
177 return index( r, 0 );
178}
179
181{
182 return mProject->mapLayer( index.data( static_cast< int >( CustomRole::LayerId ) ).toString() );
183}
184
185void QgsMapLayerModel::setAdditionalItems( const QStringList &items )
186{
187 if ( items == mAdditionalItems )
188 return;
189
190 int offset = 0;
191 if ( mAllowEmpty )
192 offset++;
193
194 offset += mLayers.count();
195
196 //remove existing
197 if ( !mAdditionalItems.isEmpty() )
198 {
199 beginRemoveRows( QModelIndex(), offset, offset + mAdditionalItems.count() - 1 );
200 mAdditionalItems.clear();
201 endRemoveRows();
202 }
203
204 //add new
205 beginInsertRows( QModelIndex(), offset, offset + items.count() - 1 );
206 mAdditionalItems = items;
207 endInsertRows();
208}
209
210void QgsMapLayerModel::setAdditionalLayers( const QList<QgsMapLayer *> &layers )
211{
212 if ( layers == _qgis_listQPointerToRaw( mAdditionalLayers ) )
213 return;
214
215 QStringList layerIdsToRemove;
216 for ( QgsMapLayer *layer : std::as_const( mAdditionalLayers ) )
217 {
218 if ( layer )
219 layerIdsToRemove << layer->id();
220 }
221 removeLayers( layerIdsToRemove );
222
223 for ( QgsMapLayer *layer : layers )
224 {
225 if ( layer )
226 {
227 addLayers( { layer } );
228 const QString layerId = layer->id();
229 connect( layer, &QgsMapLayer::willBeDeleted, this, [this, layerId] { removeLayers( { layerId } ); } );
230 }
231 }
232
233 mAdditionalLayers = _qgis_listRawToQPointer( layers );
234}
235
236QList<QgsMapLayer *> QgsMapLayerModel::additionalLayers() const
237{
238 return _qgis_listQPointerToRaw( mAdditionalLayers );
239}
240
241void QgsMapLayerModel::removeLayers( const QStringList &layerIds )
242{
243 int offset = 0;
244 if ( mAllowEmpty )
245 offset++;
246
247 for ( const QString &layerId : layerIds )
248 {
249 QModelIndex startIndex = index( 0, 0 );
250 QModelIndexList list = match( startIndex, static_cast< int >( CustomRole::LayerId ), layerId, 1 );
251 if ( !list.isEmpty() )
252 {
253 QModelIndex index = list[0];
254 beginRemoveRows( QModelIndex(), index.row(), index.row() );
255 mLayersChecked.remove( layerId );
256 mLayers.removeAt( index.row() - offset );
257 endRemoveRows();
258 }
259 }
260}
261
262void QgsMapLayerModel::addLayers( const QList<QgsMapLayer *> &layers )
263{
264 if ( !layers.empty() )
265 {
266 int offset = 0;
267 if ( mAllowEmpty )
268 offset++;
269
270 beginInsertRows( QModelIndex(), mLayers.count() + offset, mLayers.count() + layers.count() - 1 + offset );
271 const auto constLayers = layers;
272 for ( QgsMapLayer *layer : constLayers )
273 {
274 mLayers.append( layer );
275 mLayersChecked.insert( layer->id(), Qt::Unchecked );
276 }
277 endInsertRows();
278 }
279}
280
281QModelIndex QgsMapLayerModel::index( int row, int column, const QModelIndex &parent ) const
282{
283 int offset = 0;
284 if ( mAllowEmpty )
285 offset++;
286
287 if ( hasIndex( row, column, parent ) )
288 {
289 QgsMapLayer *layer = nullptr;
290 if ( row - offset >= 0 && row - offset < mLayers.count() )
291 layer = mLayers.at( row - offset );
292
293 return createIndex( row, column, layer );
294 }
295
296 return QModelIndex();
297}
298
299QModelIndex QgsMapLayerModel::parent( const QModelIndex &child ) const
300{
301 Q_UNUSED( child )
302 return QModelIndex();
303}
304
305
306int QgsMapLayerModel::rowCount( const QModelIndex &parent ) const
307{
308 if ( parent.isValid() )
309 return 0;
310
311 return ( mAllowEmpty ? 1 : 0 ) + mLayers.length() + mAdditionalItems.count();
312}
313
314int QgsMapLayerModel::columnCount( const QModelIndex &parent ) const
315{
316 Q_UNUSED( parent )
317 return 1;
318}
319
320
321QVariant QgsMapLayerModel::data( const QModelIndex &index, int role ) const
322{
323 if ( !index.isValid() )
324 return QVariant();
325
326 bool isEmpty = index.row() == 0 && mAllowEmpty;
327 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
328
329 switch ( role )
330 {
331 case Qt::DisplayRole:
332 case Qt::EditRole:
333 {
334 if ( index.row() == 0 && mAllowEmpty )
335 return mEmptyText;
336
337 if ( additionalIndex >= 0 )
338 return mAdditionalItems.at( additionalIndex );
339
340 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
341 if ( !layer )
342 return QVariant();
343
344 if ( !mShowCrs || !layer->isSpatial() || role == Qt::EditRole )
345 {
346 return layer->name();
347 }
348 else
349 {
350 return tr( "%1 [%2]" ).arg( layer->name(), layer->crs().authid() );
351 }
352 }
353
354 case static_cast< int >( CustomRole::LayerId ):
355 {
356 if ( isEmpty || additionalIndex >= 0 )
357 return QVariant();
358
359 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
360 return layer ? layer->id() : QVariant();
361 }
362
363 case static_cast< int >( CustomRole::Layer ):
364 {
365 if ( isEmpty || additionalIndex >= 0 )
366 return QVariant();
367
368 return QVariant::fromValue<QgsMapLayer *>( mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) ) );
369 }
370
371 case static_cast< int >( CustomRole::Empty ):
372 return isEmpty;
373
374 case static_cast< int >( CustomRole::Additional ):
375 return additionalIndex >= 0;
376
377 case Qt::CheckStateRole:
378 {
379 if ( mItemCheckable )
380 {
381 if ( isEmpty || additionalIndex >= 0 )
382 return QVariant();
383
384 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
385 return layer ? mLayersChecked[layer->id()] : QVariant();
386 }
387
388 return QVariant();
389 }
390
391 case Qt::ToolTipRole:
392 {
393 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
394 if ( layer )
395 {
396 return QgsMapLayerUtils::layerToolTip( layer );
397 }
398 return QVariant();
399 }
400
401 case Qt::DecorationRole:
402 {
403 if ( isEmpty )
404 return mEmptyIcon.isNull() ? QVariant() : mEmptyIcon;
405
406 if ( additionalIndex >= 0 )
407 return QVariant();
408
409 QgsMapLayer *layer = mLayers.value( index.row() - ( mAllowEmpty ? 1 : 0 ) );
410 if ( !layer )
411 return QVariant();
412
413 return iconForLayer( layer );
414 }
415
416 default:
417 break;
418 }
419
420 return QVariant();
421}
422
423QHash<int, QByteArray> QgsMapLayerModel::roleNames() const
424{
425 QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
426 roles[static_cast< int >( CustomRole::LayerId )] = "layerId";
427 roles[static_cast< int >( CustomRole::Layer )] = "layer";
428
429 return roles;
430}
431
432Qt::ItemFlags QgsMapLayerModel::flags( const QModelIndex &index ) const
433{
434 if ( !index.isValid() )
435 {
436 if ( mCanReorder )
437 return Qt::ItemIsDropEnabled;
438 else
439 return Qt::ItemFlags();
440 }
441
442 bool isEmpty = index.row() == 0 && mAllowEmpty;
443 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
444
445 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
446
447 if ( mCanReorder && !isEmpty && additionalIndex < 0 )
448 {
449 flags |= Qt::ItemIsDragEnabled;
450 }
451
452 if ( mItemCheckable && !isEmpty && additionalIndex < 0 )
453 {
454 flags |= Qt::ItemIsUserCheckable;
455 }
456 return flags;
457}
458
459bool QgsMapLayerModel::insertRows( int row, int count, const QModelIndex &parent )
460{
461 if ( parent.isValid() )
462 return false;
463
464 int offset = 0;
465 if ( mAllowEmpty )
466 offset++;
467
468 beginInsertRows( parent, row, row + count - 1 );
469 for ( int i = row; i < row + count; ++i )
470 mLayers.insert( i - offset, nullptr );
471 endInsertRows();
472
473 return true;
474}
475
476bool QgsMapLayerModel::removeRows( int row, int count, const QModelIndex &parent )
477{
478 if ( parent.isValid() || row < 0 )
479 return false;
480
481 int offset = 0;
482 if ( mAllowEmpty )
483 {
484 if ( row == 0 )
485 return false;
486
487 offset++;
488 }
489
490 if ( row - offset > mLayers.count() - 1 )
491 {
492 return false;
493 }
494
495 beginRemoveRows( parent, row, row + count - 1 );
496 for ( int i = 0; i != count; ++i )
497 mLayers.removeAt( row - offset );
498 endRemoveRows();
499
500 return true;
501}
502
504{
505 QStringList types;
506 types << u"application/qgis.layermodeldata"_s;
507 return types;
508}
509
510bool QgsMapLayerModel::canDropMimeData( const QMimeData *data, Qt::DropAction action, int, int, const QModelIndex & ) const
511{
512 if ( !mCanReorder || action != Qt::MoveAction || !data->hasFormat( u"application/qgis.layermodeldata"_s ) )
513 return false;
514 return true;
515}
516
517QMimeData *QgsMapLayerModel::mimeData( const QModelIndexList &indexes ) const
518{
519 auto mimeData = std::make_unique< QMimeData >();
520
521 QByteArray encodedData;
522 QDataStream stream( &encodedData, QIODevice::WriteOnly );
523 QSet< QString > addedLayers;
524
525 for ( const QModelIndex &i : indexes )
526 {
527 if ( i.isValid() )
528 {
529 const QString id = data( index( i.row(), 0, i.parent() ), static_cast< int >( CustomRole::LayerId ) ).toString();
530 if ( !addedLayers.contains( id ) )
531 {
532 addedLayers.insert( id );
533 stream << id;
534 }
535 }
536 }
537 mimeData->setData( u"application/qgis.layermodeldata"_s, encodedData );
538 return mimeData.release();
539}
540
541bool QgsMapLayerModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
542{
543 if ( !canDropMimeData( data, action, row, column, parent ) || row < 0 )
544 return false;
545
546 if ( action == Qt::IgnoreAction )
547 return true;
548 else if ( action != Qt::MoveAction )
549 return false;
550
551 QByteArray encodedData = data->data( u"application/qgis.layermodeldata"_s );
552 QDataStream stream( &encodedData, QIODevice::ReadOnly );
553 QStringList newItems;
554 int rows = 0;
555
556 while ( !stream.atEnd() )
557 {
558 QString text;
559 stream >> text;
560 newItems << text;
561 ++rows;
562 }
563
564 insertRows( row, rows, QModelIndex() );
565 for ( const QString &text : std::as_const( newItems ) )
566 {
567 QModelIndex idx = index( row, 0, QModelIndex() );
568 setData( idx, text, static_cast< int >( CustomRole::LayerId ) );
569 row++;
570 }
571
572 return true;
573}
574
576{
577 return Qt::MoveAction;
578}
579
581{
582 return QgsIconUtils::iconForLayer( layer );
583}
584
585bool QgsMapLayerModel::setData( const QModelIndex &index, const QVariant &value, int role )
586{
587 if ( !index.isValid() )
588 return false;
589
590 bool isEmpty = index.row() == 0 && mAllowEmpty;
591 int additionalIndex = index.row() - ( mAllowEmpty ? 1 : 0 ) - mLayers.count();
592
593 switch ( role )
594 {
595 case Qt::CheckStateRole:
596 {
597 if ( !isEmpty && additionalIndex < 0 )
598 {
599 QgsMapLayer *layer = static_cast<QgsMapLayer *>( index.internalPointer() );
600 mLayersChecked[layer->id()] = ( Qt::CheckState ) value.toInt();
601 emit dataChanged( index, index, QVector< int >() << Qt::CheckStateRole );
602 return true;
603 }
604 break;
605 }
606
607 case static_cast< int >( CustomRole::LayerId ):
608 if ( !isEmpty && additionalIndex < 0 )
609 {
610 mLayers[index.row() - ( mAllowEmpty ? 1 : 0 )] = mProject->mapLayer( value.toString() );
611 emit dataChanged( index, index );
612 return true;
613 }
614 break;
615
616 default:
617 break;
618 }
619
620 return false;
621}
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)
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)
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
static QString layerToolTip(const QgsMapLayer *layer)
Returns the consistent tooltip for the given layer.
Base class for all map layer types.
Definition qgsmaplayer.h:83
QString name
Definition qgsmaplayer.h:87
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:90
QString id
Definition qgsmaplayer.h:86
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:114
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.