QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsmapthemecollection.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmapthemecollection.cpp
3  --------------------------------------
4  Date : September 2014
5  Copyright : (C) 2014 by Martin Dobias
6  Email : wonder dot sk at gmail dot 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 "qgsmapthemecollection.h"
17 
18 #include "qgslayertree.h"
19 #include "qgslayertreemodel.h"
21 #include "qgsmaplayerlistutils.h"
23 #include "qgsproject.h"
24 #include "qgsrenderer.h"
25 #include "qgsvectorlayer.h"
26 #include "qgssymbol.h"
27 
29  : mProject( project )
30 {
31  connect( project, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
32 }
33 
34 QgsMapThemeCollection::MapThemeLayerRecord QgsMapThemeCollection::createThemeLayerRecord( QgsLayerTreeLayer *nodeLayer, QgsLayerTreeModel *model )
35 {
36  MapThemeLayerRecord layerRec( nodeLayer->layer() );
37  layerRec.isVisible = nodeLayer->isVisible();
38  layerRec.usingCurrentStyle = true;
39  layerRec.currentStyle = nodeLayer->layer()->styleManager()->currentStyle();
40  layerRec.expandedLayerNode = nodeLayer->isExpanded();
41  layerRec.expandedLegendItems = qgis::listToSet( nodeLayer->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList() );
42 
43  // get checked legend items
44  bool hasCheckableItems = false;
45  bool someItemsUnchecked = false;
46  QSet<QString> checkedItems;
47  const QList<QgsLayerTreeModelLegendNode *> layerLegendNodes = model->layerLegendNodes( nodeLayer, true );
48  for ( QgsLayerTreeModelLegendNode *legendNode : layerLegendNodes )
49  {
50  if ( legendNode->flags() & Qt::ItemIsUserCheckable )
51  {
52  hasCheckableItems = true;
53 
54  if ( legendNode->data( Qt::CheckStateRole ).toInt() == Qt::Checked )
55  checkedItems << legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
56  else
57  someItemsUnchecked = true;
58  }
59  }
60 
61  if ( hasCheckableItems && someItemsUnchecked )
62  {
63  layerRec.usingLegendItems = true;
64  layerRec.checkedLegendItems = checkedItems;
65  }
66  return layerRec;
67 }
68 
69 static QString _groupId( QgsLayerTreeNode *node )
70 {
71  QStringList lst;
72  while ( node->parent() )
73  {
74  lst.prepend( node->name() );
75  node = node->parent();
76  }
77  return lst.join( '/' );
78 }
79 
81 {
82  const QList<QgsLayerTreeNode *> constChildren = parent->children();
83  for ( QgsLayerTreeNode *node : constChildren )
84  {
85  if ( QgsLayerTree::isGroup( node ) )
86  {
88  if ( node->isExpanded() )
89  rec.mExpandedGroupNodes.insert( _groupId( node ) );
90  if ( node->itemVisibilityChecked() != Qt::Unchecked )
91  rec.mCheckedGroupNodes.insert( _groupId( node ) );
92  }
93  else if ( QgsLayerTree::isLayer( node ) )
94  {
95  QgsLayerTreeLayer *nodeLayer = QgsLayerTree::toLayer( node );
96  if ( node->itemVisibilityChecked() != Qt::Unchecked && nodeLayer->layer() )
97  rec.mLayerRecords << createThemeLayerRecord( nodeLayer, model );
98  }
99  }
100 }
101 
103 {
105  rec.setHasExpandedStateInfo( true ); // all newly created theme records have expanded state info
106  rec.setHasCheckedStateInfo( true ); // all newly created theme records have checked state info
107  createThemeFromCurrentState( root, model, rec );
108  return rec;
109 }
110 
111 bool QgsMapThemeCollection::findRecordForLayer( QgsMapLayer *layer, const QgsMapThemeCollection::MapThemeRecord &rec, QgsMapThemeCollection::MapThemeLayerRecord &layerRec )
112 {
113  for ( const QgsMapThemeCollection::MapThemeLayerRecord &lr : std::as_const( rec.mLayerRecords ) )
114  {
115  if ( lr.layer() == layer )
116  {
117  layerRec = lr;
118  return true;
119  }
120  }
121  return false;
122 }
123 
124 void QgsMapThemeCollection::applyThemeToLayer( QgsLayerTreeLayer *nodeLayer, QgsLayerTreeModel *model, const QgsMapThemeCollection::MapThemeRecord &rec )
125 {
126  MapThemeLayerRecord layerRec;
127  const bool recordExists = findRecordForLayer( nodeLayer->layer(), rec, layerRec );
128 
129  // Make sure the whole tree is visible
130  if ( recordExists )
131  {
132  if ( rec.hasCheckedStateInfo() )
133  nodeLayer->setItemVisibilityChecked( true );
134  else
135  nodeLayer->setItemVisibilityCheckedParentRecursive( true );
136  }
137  else
138  nodeLayer->setItemVisibilityChecked( false );
139 
140  if ( !recordExists )
141  return;
142 
143  if ( layerRec.usingCurrentStyle )
144  {
145  // apply desired style first
146  nodeLayer->layer()->styleManager()->setCurrentStyle( layerRec.currentStyle );
147  }
148 
149  if ( layerRec.usingLegendItems )
150  {
151  // some nodes are not checked
152  const QList<QgsLayerTreeModelLegendNode *> constLayerLegendNodes = model->layerLegendNodes( nodeLayer, true );
153  for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
154  {
155  QString ruleKey = legendNode->data( QgsLayerTreeModelLegendNode::RuleKeyRole ).toString();
156  Qt::CheckState shouldHaveState = layerRec.checkedLegendItems.contains( ruleKey ) ? Qt::Checked : Qt::Unchecked;
157  if ( ( legendNode->flags() & Qt::ItemIsUserCheckable ) &&
158  legendNode->data( Qt::CheckStateRole ).toInt() != shouldHaveState )
159  legendNode->setData( shouldHaveState, Qt::CheckStateRole );
160  }
161  }
162  else
163  {
164  // all nodes should be checked
165  const QList<QgsLayerTreeModelLegendNode *> constLayerLegendNodes = model->layerLegendNodes( nodeLayer, true );
166  for ( QgsLayerTreeModelLegendNode *legendNode : constLayerLegendNodes )
167  {
168  if ( ( legendNode->flags() & Qt::ItemIsUserCheckable ) &&
169  legendNode->data( Qt::CheckStateRole ).toInt() != Qt::Checked )
170  legendNode->setData( Qt::Checked, Qt::CheckStateRole );
171  }
172  }
173 
174  // apply expanded/collapsed state to the layer and its legend nodes
175  if ( rec.hasExpandedStateInfo() )
176  {
177  nodeLayer->setExpanded( layerRec.expandedLayerNode );
178  nodeLayer->setCustomProperty( QStringLiteral( "expandedLegendNodes" ), QStringList( qgis::setToList( layerRec.expandedLegendItems ) ) );
179  }
180 }
181 
182 
183 void QgsMapThemeCollection::applyThemeToGroup( QgsLayerTreeGroup *parent, QgsLayerTreeModel *model, const QgsMapThemeCollection::MapThemeRecord &rec )
184 {
185  const QList<QgsLayerTreeNode *> constChildren = parent->children();
186  for ( QgsLayerTreeNode *node : constChildren )
187  {
188  if ( QgsLayerTree::isGroup( node ) )
189  {
190  applyThemeToGroup( QgsLayerTree::toGroup( node ), model, rec );
191  if ( rec.hasExpandedStateInfo() )
192  node->setExpanded( rec.expandedGroupNodes().contains( _groupId( node ) ) );
193  if ( rec.hasCheckedStateInfo() )
194  node->setItemVisibilityChecked( rec.checkedGroupNodes().contains( _groupId( node ) ) );
195  }
196  else if ( QgsLayerTree::isLayer( node ) )
197  applyThemeToLayer( QgsLayerTree::toLayer( node ), model, rec );
198  }
199 }
200 
201 
203 {
204  applyThemeToGroup( root, model, mapThemeState( name ) );
205 
206  // also make sure that the preset is up-to-date (not containing any non-existent legend items)
207  update( name, createThemeFromCurrentState( root, model ) );
208 }
209 
211 {
212  return mProject;
213 }
214 
216 {
217  if ( project == mProject )
218  return;
219 
220  disconnect( mProject, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
221  mProject = project;
222  connect( mProject, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
223  emit projectChanged();
224 }
225 
226 QList<QgsMapLayer *> QgsMapThemeCollection::masterLayerOrder() const
227 {
228  if ( !mProject )
229  return QList< QgsMapLayer * >();
230 
231  return mProject->layerTreeRoot()->layerOrder();
232 }
233 
234 QList<QgsMapLayer *> QgsMapThemeCollection::masterVisibleLayers() const
235 {
236  const QList< QgsMapLayer *> allLayers = masterLayerOrder();
237  const QList< QgsMapLayer * > visibleLayers = mProject->layerTreeRoot()->checkedLayers();
238 
239  if ( allLayers.isEmpty() )
240  {
241  // no project layer order set
242  return visibleLayers;
243  }
244  else
245  {
246  QList< QgsMapLayer * > orderedVisibleLayers;
247  for ( QgsMapLayer *layer : allLayers )
248  {
249  if ( visibleLayers.contains( layer ) )
250  orderedVisibleLayers << layer;
251  }
252  return orderedVisibleLayers;
253  }
254 }
255 
256 
257 bool QgsMapThemeCollection::hasMapTheme( const QString &name ) const
258 {
259  return mMapThemes.contains( name );
260 }
261 
263 {
264  mMapThemes.insert( name, state );
265 
266  reconnectToLayersStyleManager();
267  emit mapThemeChanged( name );
268  emit mapThemesChanged();
269 }
270 
271 void QgsMapThemeCollection::update( const QString &name, const MapThemeRecord &state )
272 {
273  if ( !mMapThemes.contains( name ) )
274  return;
275 
276  mMapThemes[name] = state;
277 
278  reconnectToLayersStyleManager();
279  emit mapThemeChanged( name );
280  emit mapThemesChanged();
281 }
282 
283 bool QgsMapThemeCollection::renameMapTheme( const QString &name, const QString &newName )
284 {
285  if ( !mMapThemes.contains( name ) || mMapThemes.contains( newName ) )
286  return false;
287 
288  const MapThemeRecord state = mMapThemes[name];
289  const MapThemeRecord newState = state;
290  insert( newName, newState );
291  emit mapThemeRenamed( name, newName );
292  removeMapTheme( name );
293  return true;
294 }
295 
296 void QgsMapThemeCollection::removeMapTheme( const QString &name )
297 {
298  if ( !mMapThemes.contains( name ) )
299  return;
300 
301  mMapThemes.remove( name );
302 
303  reconnectToLayersStyleManager();
304  emit mapThemesChanged();
305 }
306 
308 {
309  mMapThemes.clear();
310 
311  reconnectToLayersStyleManager();
312  emit mapThemesChanged();
313 }
314 
316 {
317  return mMapThemes.keys();
318 }
319 
320 QStringList QgsMapThemeCollection::mapThemeVisibleLayerIds( const QString &name ) const
321 {
322  QStringList layerIds;
323  const QList<QgsMapLayer *> constMapThemeVisibleLayers = mapThemeVisibleLayers( name );
324  for ( QgsMapLayer *layer : constMapThemeVisibleLayers )
325  {
326  layerIds << layer->id();
327  }
328  return layerIds;
329 }
330 
331 QList<QgsMapLayer *> QgsMapThemeCollection::mapThemeVisibleLayers( const QString &name ) const
332 {
333  QList<QgsMapLayer *> layers;
334  const QList<MapThemeLayerRecord> recs = mMapThemes.value( name ).mLayerRecords;
335  const QList<QgsMapLayer *> layerOrder = masterLayerOrder();
336  if ( layerOrder.isEmpty() )
337  {
338  // no master layer order - so we have to just use the stored theme layer order as a fallback
339  const QList<MapThemeLayerRecord> records { mMapThemes.value( name ).mLayerRecords };
340  for ( const MapThemeLayerRecord &layerRec : records )
341  {
342  if ( layerRec.isVisible && layerRec.layer() )
343  layers << layerRec.layer();
344  }
345  }
346  else
347  {
348  for ( QgsMapLayer *layer : layerOrder )
349  {
350  for ( const MapThemeLayerRecord &layerRec : recs )
351  {
352  if ( layerRec.isVisible && layerRec.layer() == layer )
353  layers << layerRec.layer();
354  }
355  }
356  }
357 
358  return layers;
359 }
360 
361 
362 void QgsMapThemeCollection::applyMapThemeCheckedLegendNodesToLayer( const MapThemeLayerRecord &layerRec, QgsMapLayer *layer )
363 {
364  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
365  if ( !vlayer || !vlayer->renderer() )
366  return;
367 
368  QgsFeatureRenderer *renderer = vlayer->renderer();
369  if ( !renderer->legendSymbolItemsCheckable() )
370  return; // no need to do anything
371 
372  bool someNodesUnchecked = layerRec.usingLegendItems;
373 
374  const auto constLegendSymbolItems = vlayer->renderer()->legendSymbolItems();
375  for ( const QgsLegendSymbolItem &item : constLegendSymbolItems )
376  {
377  bool checked = renderer->legendSymbolItemChecked( item.ruleKey() );
378  bool shouldBeChecked = someNodesUnchecked ? layerRec.checkedLegendItems.contains( item.ruleKey() ) : true;
379  if ( checked != shouldBeChecked )
380  renderer->checkLegendSymbolItem( item.ruleKey(), shouldBeChecked );
381  }
382 }
383 
384 
385 QMap<QString, QString> QgsMapThemeCollection::mapThemeStyleOverrides( const QString &presetName )
386 {
387  QMap<QString, QString> styleOverrides;
388  if ( !mMapThemes.contains( presetName ) )
389  return styleOverrides;
390 
391  const QList<MapThemeLayerRecord> records {mMapThemes.value( presetName ).mLayerRecords};
392  for ( const MapThemeLayerRecord &layerRec : records )
393  {
394  if ( !layerRec.layer() )
395  continue;
396 
397  if ( layerRec.usingCurrentStyle )
398  {
399  QgsMapLayer *layer = layerRec.layer();
400  QgsMapLayerStyleOverride styleOverride( layer );
401  styleOverride.setOverrideStyle( layerRec.currentStyle );
402 
403  // set the checked legend nodes
404  applyMapThemeCheckedLegendNodesToLayer( layerRec, layer );
405 
406  // save to overrides
407  QgsMapLayerStyle layerStyle;
408  layerStyle.readFromLayer( layer );
409  styleOverrides[layer->id()] = layerStyle.xmlData();
410  }
411  }
412  return styleOverrides;
413 }
414 
415 void QgsMapThemeCollection::reconnectToLayersStyleManager()
416 {
417  // disconnect( 0, 0, this, SLOT( layerStyleRenamed( QString, QString ) ) );
418 
419  QSet<QgsMapLayer *> layers;
420  for ( const MapThemeRecord &rec : std::as_const( mMapThemes ) )
421  {
422  for ( const MapThemeLayerRecord &layerRec : std::as_const( rec.mLayerRecords ) )
423  {
424  if ( auto *lLayer = layerRec.layer() )
425  layers << lLayer;
426  }
427  }
428 
429  const QSet<QgsMapLayer *> constLayers = layers;
430  for ( QgsMapLayer *ml : constLayers )
431  {
432  connect( ml->styleManager(), &QgsMapLayerStyleManager::styleRenamed, this, &QgsMapThemeCollection::layerStyleRenamed );
433  }
434 }
435 
436 void QgsMapThemeCollection::readXml( const QDomDocument &doc )
437 {
438  clear();
439 
440  QDomElement visPresetsElem = doc.firstChildElement( QStringLiteral( "qgis" ) ).firstChildElement( QStringLiteral( "visibility-presets" ) );
441  if ( visPresetsElem.isNull() )
442  return;
443 
444  QDomElement visPresetElem = visPresetsElem.firstChildElement( QStringLiteral( "visibility-preset" ) );
445  while ( !visPresetElem.isNull() )
446  {
447  QHash<QString, MapThemeLayerRecord> layerRecords; // key = layer ID
448 
449  bool expandedStateInfo = false;
450  if ( visPresetElem.hasAttribute( QStringLiteral( "has-expanded-info" ) ) )
451  expandedStateInfo = visPresetElem.attribute( QStringLiteral( "has-expanded-info" ) ).toInt();
452 
453  bool checkedStateInfo = false;
454  if ( visPresetElem.hasAttribute( QStringLiteral( "has-checked-group-info" ) ) )
455  checkedStateInfo = visPresetElem.attribute( QStringLiteral( "has-checked-group-info" ) ).toInt();
456 
457  QString presetName = visPresetElem.attribute( QStringLiteral( "name" ) );
458  QDomElement visPresetLayerElem = visPresetElem.firstChildElement( QStringLiteral( "layer" ) );
459  while ( !visPresetLayerElem.isNull() )
460  {
461  QString layerID = visPresetLayerElem.attribute( QStringLiteral( "id" ) );
462  if ( QgsMapLayer *layer = mProject->mapLayer( layerID ) )
463  {
464  layerRecords[layerID] = MapThemeLayerRecord( layer );
465  layerRecords[layerID].isVisible = visPresetLayerElem.attribute( QStringLiteral( "visible" ), QStringLiteral( "1" ) ).toInt();
466 
467  if ( visPresetLayerElem.hasAttribute( QStringLiteral( "style" ) ) )
468  {
469  layerRecords[layerID].usingCurrentStyle = true;
470  layerRecords[layerID].currentStyle = visPresetLayerElem.attribute( QStringLiteral( "style" ) );
471  }
472 
473  if ( visPresetLayerElem.hasAttribute( QStringLiteral( "expanded" ) ) )
474  layerRecords[layerID].expandedLayerNode = visPresetLayerElem.attribute( QStringLiteral( "expanded" ) ).toInt();
475  }
476  visPresetLayerElem = visPresetLayerElem.nextSiblingElement( QStringLiteral( "layer" ) );
477  }
478 
479  QDomElement checkedLegendNodesElem = visPresetElem.firstChildElement( QStringLiteral( "checked-legend-nodes" ) );
480  while ( !checkedLegendNodesElem.isNull() )
481  {
482  QSet<QString> checkedLegendNodes;
483 
484  QDomElement checkedLegendNodeElem = checkedLegendNodesElem.firstChildElement( QStringLiteral( "checked-legend-node" ) );
485  while ( !checkedLegendNodeElem.isNull() )
486  {
487  checkedLegendNodes << checkedLegendNodeElem.attribute( QStringLiteral( "id" ) );
488  checkedLegendNodeElem = checkedLegendNodeElem.nextSiblingElement( QStringLiteral( "checked-legend-node" ) );
489  }
490 
491  QString layerID = checkedLegendNodesElem.attribute( QStringLiteral( "id" ) );
492  if ( mProject->mapLayer( layerID ) ) // only use valid IDs
493  {
494  layerRecords[layerID].usingLegendItems = true;
495  layerRecords[layerID].checkedLegendItems = checkedLegendNodes;
496  }
497  checkedLegendNodesElem = checkedLegendNodesElem.nextSiblingElement( QStringLiteral( "checked-legend-nodes" ) );
498  }
499 
500  QSet<QString> expandedGroupNodes;
501  if ( expandedStateInfo )
502  {
503  // expanded state of legend nodes
504  QDomElement expandedLegendNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-legend-nodes" ) );
505  while ( !expandedLegendNodesElem.isNull() )
506  {
507  QSet<QString> expandedLegendNodes;
508 
509  QDomElement expandedLegendNodeElem = expandedLegendNodesElem.firstChildElement( QStringLiteral( "expanded-legend-node" ) );
510  while ( !expandedLegendNodeElem.isNull() )
511  {
512  expandedLegendNodes << expandedLegendNodeElem.attribute( QStringLiteral( "id" ) );
513  expandedLegendNodeElem = expandedLegendNodeElem.nextSiblingElement( QStringLiteral( "expanded-legend-node" ) );
514  }
515 
516  QString layerID = expandedLegendNodesElem.attribute( QStringLiteral( "id" ) );
517  if ( mProject->mapLayer( layerID ) ) // only use valid IDs
518  {
519  layerRecords[layerID].expandedLegendItems = expandedLegendNodes;
520  }
521  expandedLegendNodesElem = expandedLegendNodesElem.nextSiblingElement( QStringLiteral( "expanded-legend-nodes" ) );
522  }
523 
524  // expanded state of group nodes
525  QDomElement expandedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-group-nodes" ) );
526  if ( !expandedGroupNodesElem.isNull() )
527  {
528  QDomElement expandedGroupNodeElem = expandedGroupNodesElem.firstChildElement( QStringLiteral( "expanded-group-node" ) );
529  while ( !expandedGroupNodeElem.isNull() )
530  {
531  expandedGroupNodes << expandedGroupNodeElem.attribute( QStringLiteral( "id" ) );
532  expandedGroupNodeElem = expandedGroupNodeElem.nextSiblingElement( QStringLiteral( "expanded-group-node" ) );
533  }
534  }
535  }
536 
537  QSet<QString> checkedGroupNodes;
538  if ( checkedStateInfo )
539  {
540  // expanded state of legend nodes
541  QDomElement checkedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "checked-group-nodes" ) );
542  if ( !checkedGroupNodesElem.isNull() )
543  {
544  QDomElement checkedGroupNodeElem = checkedGroupNodesElem.firstChildElement( QStringLiteral( "checked-group-node" ) );
545  while ( !checkedGroupNodeElem.isNull() )
546  {
547  checkedGroupNodes << checkedGroupNodeElem.attribute( QStringLiteral( "id" ) );
548  checkedGroupNodeElem = checkedGroupNodeElem.nextSiblingElement( QStringLiteral( "checked-group-node" ) );
549  }
550  }
551  }
552 
553  MapThemeRecord rec;
554  rec.setLayerRecords( layerRecords.values() );
555  rec.setHasExpandedStateInfo( expandedStateInfo );
556  rec.setExpandedGroupNodes( expandedGroupNodes );
557  rec.setHasCheckedStateInfo( checkedStateInfo );
558  rec.setCheckedGroupNodes( checkedGroupNodes );
559  mMapThemes.insert( presetName, rec );
560  emit mapThemeChanged( presetName );
561 
562  visPresetElem = visPresetElem.nextSiblingElement( QStringLiteral( "visibility-preset" ) );
563  }
564 
565  reconnectToLayersStyleManager();
566  emit mapThemesChanged();
567 }
568 
569 void QgsMapThemeCollection::writeXml( QDomDocument &doc )
570 {
571  QDomElement visPresetsElem = doc.createElement( QStringLiteral( "visibility-presets" ) );
572 
573  QList< QString > keys = mMapThemes.keys();
574 
575  std::sort( keys.begin(), keys.end() );
576 
577  for ( const QString &grpName : std::as_const( keys ) )
578  {
579  const MapThemeRecord &rec = mMapThemes.value( grpName );
580  QDomElement visPresetElem = doc.createElement( QStringLiteral( "visibility-preset" ) );
581  visPresetElem.setAttribute( QStringLiteral( "name" ), grpName );
582  if ( rec.hasExpandedStateInfo() )
583  visPresetElem.setAttribute( QStringLiteral( "has-expanded-info" ), QStringLiteral( "1" ) );
584  if ( rec.hasCheckedStateInfo() )
585  visPresetElem.setAttribute( QStringLiteral( "has-checked-group-info" ), QStringLiteral( "1" ) );
586  for ( const MapThemeLayerRecord &layerRec : std::as_const( rec.mLayerRecords ) )
587  {
588  if ( !layerRec.layer() )
589  continue;
590  QString layerID = layerRec.layer()->id();
591  QDomElement layerElem = doc.createElement( QStringLiteral( "layer" ) );
592  layerElem.setAttribute( QStringLiteral( "id" ), layerID );
593  layerElem.setAttribute( QStringLiteral( "visible" ), layerRec.isVisible ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
594  if ( layerRec.usingCurrentStyle )
595  layerElem.setAttribute( QStringLiteral( "style" ), layerRec.currentStyle );
596  visPresetElem.appendChild( layerElem );
597 
598  if ( layerRec.usingLegendItems )
599  {
600  QDomElement checkedLegendNodesElem = doc.createElement( QStringLiteral( "checked-legend-nodes" ) );
601  checkedLegendNodesElem.setAttribute( QStringLiteral( "id" ), layerID );
602  for ( const QString &checkedLegendNode : std::as_const( layerRec.checkedLegendItems ) )
603  {
604  QDomElement checkedLegendNodeElem = doc.createElement( QStringLiteral( "checked-legend-node" ) );
605  checkedLegendNodeElem.setAttribute( QStringLiteral( "id" ), checkedLegendNode );
606  checkedLegendNodesElem.appendChild( checkedLegendNodeElem );
607  }
608  visPresetElem.appendChild( checkedLegendNodesElem );
609  }
610 
611  if ( rec.hasExpandedStateInfo() )
612  {
613  layerElem.setAttribute( QStringLiteral( "expanded" ), layerRec.expandedLayerNode ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
614 
615  QDomElement expandedLegendNodesElem = doc.createElement( QStringLiteral( "expanded-legend-nodes" ) );
616  expandedLegendNodesElem.setAttribute( QStringLiteral( "id" ), layerID );
617  for ( const QString &expandedLegendNode : std::as_const( layerRec.expandedLegendItems ) )
618  {
619  QDomElement expandedLegendNodeElem = doc.createElement( QStringLiteral( "expanded-legend-node" ) );
620  expandedLegendNodeElem.setAttribute( QStringLiteral( "id" ), expandedLegendNode );
621  expandedLegendNodesElem.appendChild( expandedLegendNodeElem );
622  }
623  visPresetElem.appendChild( expandedLegendNodesElem );
624  }
625  }
626 
627  if ( rec.hasCheckedStateInfo() )
628  {
629  QDomElement checkedGroupElems = doc.createElement( QStringLiteral( "checked-group-nodes" ) );
630  const QSet<QString> checkedGroupNodes = rec.checkedGroupNodes();
631  for ( const QString &groupId : checkedGroupNodes )
632  {
633  QDomElement checkedGroupElem = doc.createElement( QStringLiteral( "checked-group-node" ) );
634  checkedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
635  checkedGroupElems.appendChild( checkedGroupElem );
636  }
637  visPresetElem.appendChild( checkedGroupElems );
638  }
639 
640  if ( rec.hasExpandedStateInfo() )
641  {
642  QDomElement expandedGroupElems = doc.createElement( QStringLiteral( "expanded-group-nodes" ) );
643  const QSet<QString> expandedGroupNodes = rec.expandedGroupNodes();
644  for ( const QString &groupId : expandedGroupNodes )
645  {
646  QDomElement expandedGroupElem = doc.createElement( QStringLiteral( "expanded-group-node" ) );
647  expandedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
648  expandedGroupElems.appendChild( expandedGroupElem );
649  }
650  visPresetElem.appendChild( expandedGroupElems );
651  }
652 
653  visPresetsElem.appendChild( visPresetElem );
654  }
655 
656  doc.firstChildElement( QStringLiteral( "qgis" ) ).appendChild( visPresetsElem );
657 }
658 
659 void QgsMapThemeCollection::registryLayersRemoved( const QStringList &layerIDs )
660 {
661  // while layers are stored as weak pointers, this triggers the mapThemeChanged signal for
662  // affected themes
663  QSet< QString > changedThemes;
664  MapThemeRecordMap::iterator it = mMapThemes.begin();
665  for ( ; it != mMapThemes.end(); ++it )
666  {
667  MapThemeRecord &rec = it.value();
668  for ( int i = 0; i < rec.mLayerRecords.count(); ++i )
669  {
670  MapThemeLayerRecord &layerRec = rec.mLayerRecords[i];
671  if ( layerRec.layer() && layerIDs.contains( layerRec.layer()->id() ) )
672  {
673  rec.mLayerRecords.removeAt( i-- );
674  changedThemes << it.key();
675  }
676  }
677  }
678 
679  for ( const QString &theme : std::as_const( changedThemes ) )
680  {
681  emit mapThemeChanged( theme );
682  }
683  emit mapThemesChanged();
684 }
685 
686 void QgsMapThemeCollection::layerStyleRenamed( const QString &oldName, const QString &newName )
687 {
688  QgsMapLayerStyleManager *styleMgr = qobject_cast<QgsMapLayerStyleManager *>( sender() );
689  if ( !styleMgr )
690  return;
691 
692  QSet< QString > changedThemes;
693 
694  MapThemeRecordMap::iterator it = mMapThemes.begin();
695  for ( ; it != mMapThemes.end(); ++it )
696  {
697  MapThemeRecord &rec = it.value();
698  for ( int i = 0; i < rec.mLayerRecords.count(); ++i )
699  {
700  MapThemeLayerRecord &layerRec = rec.mLayerRecords[i];
701  if ( layerRec.layer() == styleMgr->layer() )
702  {
703  if ( layerRec.currentStyle == oldName )
704  {
705  layerRec.currentStyle = newName;
706  changedThemes << it.key();
707  }
708  }
709  }
710  }
711 
712  for ( const QString &theme : std::as_const( changedThemes ) )
713  {
714  emit mapThemeChanged( theme );
715  }
716  emit mapThemesChanged();
717 }
718 
720 {
721  for ( int i = 0; i < mLayerRecords.length(); ++i )
722  {
723  if ( mLayerRecords.at( i ).layer() == layer )
724  mLayerRecords.removeAt( i );
725  }
726 }
727 
729 {
730  mLayerRecords.append( record );
731 }
732 
733 QHash<QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord> QgsMapThemeCollection::MapThemeRecord::validLayerRecords() const
734 {
735  QHash<QgsMapLayer *, MapThemeLayerRecord> validSet;
736  for ( const MapThemeLayerRecord &layerRec : mLayerRecords )
737  {
738  if ( auto *lLayer = layerRec.layer() )
739  validSet.insert( lLayer, layerRec );
740  }
741  return validSet;
742 }
743 
745 {
746  mLayer = layer;
747 }
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
virtual bool legendSymbolItemsCheckable() const
items of symbology items in legend should be checkable
virtual bool legendSymbolItemChecked(const QString &key)
items of symbology items in legend is checked
virtual void checkLegendSymbolItem(const QString &key, bool state=true)
item in symbology was checked
Layer tree group node serves as a container for layers and further groups.
Layer tree node points to a map layer.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
@ RuleKeyRole
Rule key of the node (QString)
virtual Qt::ItemFlags flags() const
Returns item flags associated with the item. Default implementation returns Qt::ItemIsEnabled.
virtual bool setData(const QVariant &value, int role)
Sets some data associated with the item. Default implementation does nothing and returns false.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
QList< QgsLayerTreeModelLegendNode * > layerLegendNodes(QgsLayerTreeLayer *nodeLayer, bool skipNodeEmbeddedInParent=false)
Returns filtered list of active legend nodes attached to a particular layer node (by default it retur...
This class is a base class for nodes in a layer tree.
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well)
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
void setExpanded(bool expanded)
Sets whether the node should be shown as expanded or collapsed in GUI.
virtual QString name() const =0
Returns name of the node.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children)
QList< QgsMapLayer * > checkedLayers() const
Returns a list of any checked layers which belong to this node or its children.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children)
void setItemVisibilityCheckedParentRecursive(bool checked)
Check or uncheck a node and all its parents.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
Management of styles for use with one map layer.
QString currentStyle() const
Returns name of the current style.
bool setCurrentStyle(const QString &name)
Set a different style as the current style - will apply it to the layer.
QgsMapLayer * layer() const
Gets pointer to the associated map layer.
void styleRenamed(const QString &oldName, const QString &newName)
Emitted when a style has been renamed.
Restore overridden layer style on destruction.
void setOverrideStyle(const QString &style)
Temporarily apply a different style to the layer.
Stores style information (renderer, opacity, labeling, diagrams etc.) applicable to a map layer.
void readFromLayer(QgsMapLayer *layer)
Store layer's active style information in the instance.
QString xmlData() const
Returns XML content of the style.
Base class for all map layer types.
Definition: qgsmaplayer.h:70
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
Individual record of a visible layer in a map theme record.
void setLayer(QgsMapLayer *layer)
Sets the map layer for this record.
Individual map theme record of visible layers and styles.
void setExpandedGroupNodes(const QSet< QString > &expandedGroupNodes)
Sets a set of group identifiers for group nodes that should have expanded state.
void removeLayerRecord(QgsMapLayer *layer)
Removes a record for layer if present.
void setCheckedGroupNodes(const QSet< QString > &checkedGroupNodes)
Sets a set of group identifiers for group nodes that should have checked state.
void setHasExpandedStateInfo(bool hasInfo)
Sets whether the map theme contains valid expanded/collapsed state of nodes.
QSet< QString > checkedGroupNodes() const
Returns a set of group identifiers for group nodes that should have checked state (other group nodes ...
bool hasExpandedStateInfo() const
Returns whether information about expanded/collapsed state of nodes has been recorded and thus whethe...
void setLayerRecords(const QList< QgsMapThemeCollection::MapThemeLayerRecord > &records)
Sets layer records for the theme.
void setHasCheckedStateInfo(bool hasInfo)
Sets whether the map theme contains valid checked/unchecked state of group nodes.
QHash< QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord > validLayerRecords() const
Returns set with only records for valid layers.
void addLayerRecord(const QgsMapThemeCollection::MapThemeLayerRecord &record)
Add a new record for a layer.
bool hasCheckedStateInfo() const
Returns whether information about checked/unchecked state of groups has been recorded and thus whethe...
QSet< QString > expandedGroupNodes() const
Returns a set of group identifiers for group nodes that should have expanded state (other group nodes...
void mapThemesChanged()
Emitted when map themes within the collection are changed.
static QgsMapThemeCollection::MapThemeRecord createThemeFromCurrentState(QgsLayerTreeGroup *root, QgsLayerTreeModel *model)
Static method to create theme from the current state of layer visibilities in layer tree,...
void insert(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Inserts a new map theme to the collection.
bool renameMapTheme(const QString &name, const QString &newName)
Renames the existing map theme called name to newName.
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
void clear()
Removes all map themes from the collection.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
QList< QgsMapLayer * > mapThemeVisibleLayers(const QString &name) const
Returns the list of layers that are visible for the specified map theme.
void removeMapTheme(const QString &name)
Removes an existing map theme from collection.
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
void setProject(QgsProject *project)
QList< QgsMapLayer * > masterLayerOrder() const
Returns the master layer order (this will always match the project's QgsProject::layerOrder() ).
void applyTheme(const QString &name, QgsLayerTreeGroup *root, QgsLayerTreeModel *model)
Apply theme given by its name and modify layer tree, current style of layers and checked legend items...
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme.
QList< QgsMapLayer * > masterVisibleLayers() const
Returns the master list of visible layers.
QStringList mapThemeVisibleLayerIds(const QString &name) const
Returns the list of layer IDs that are visible for the specified map theme.
void update(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Updates a map theme within the collection.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
QgsMapThemeCollection(QgsProject *project=nullptr)
Create map theme collection that handles themes of the given project.
void writeXml(QDomDocument &doc)
Writes the map theme collection state to XML.
void projectChanged()
Emitted when the project changes.
void readXml(const QDomDocument &doc)
Reads the map theme collection state from XML.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
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.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
Represents a vector layer which manages a vector based data sets.
QgsFeatureRenderer * renderer()
Returns the feature renderer used for rendering the features in the layer in 2D map views.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)