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