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