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