QGIS API Documentation  3.17.0-Master (a035f434f4)
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 
316 QStringList QgsMapThemeCollection::mapThemes() const
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 }
Layer tree group node serves as a container for layers and further groups.
virtual QgsLegendSymbolList legendSymbolItems() const
Returns a list of symbology items for the legend.
static QgsLayerTreeLayer * toLayer(QgsLayerTreeNode *node)
Cast node to a layer.
Definition: qgslayertree.h:75
QgsMapLayer * layer() const
Returns map layer or nullptr if the layer does not exist anymore.
Base class for all map layer types.
Definition: qgsmaplayer.h:83
void setLayer(QgsMapLayer *layer)
Sets the map layer for this record.
QSet< QString > checkedGroupNodes() const
Returns a set of group identifiers for group nodes that should have checked state (other group nodes ...
void setProject(QgsProject *project)
The QgsProject on which this map theme collection works.
virtual QString name() const =0
Returns name of the node.
virtual Qt::ItemFlags flags() const
Returns item flags associated with the item. Default implementation returns Qt::ItemIsEnabled.
bool hasMapTheme(const QString &name) const
Returns whether a map theme with a matching name exists.
static bool isGroup(QgsLayerTreeNode *node)
Check whether the node is a valid group node.
Definition: qgslayertree.h:43
void styleRenamed(const QString &oldName, const QString &newName)
Emitted when a style has been renamed.
bool renameMapTheme(const QString &name, const QString &newName)
Renames the existing map theme called name to newName.
QList< QgsMapLayer * > mapThemeVisibleLayers(const QString &name) const
Returns the list of layers that are visible for the specified map theme.
bool itemVisibilityChecked() const
Returns whether a node is checked (independently of its ancestors or children)
QMap< QString, QString > mapThemeStyleOverrides(const QString &name)
Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme...
QString xmlData() const
Returns XML content of the style.
Restore overridden layer style on destruction.
QHash< QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord > validLayerRecords() const
Returns set with only records for valid layers.
virtual bool setData(const QVariant &value, int role)
Sets some data associated with the item. Default implementation does nothing and returns false...
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
static QgsLayerTreeGroup * toGroup(QgsLayerTreeNode *node)
Cast node to a group.
Definition: qgslayertree.h:64
Individual map theme record of visible layers and styles.
void update(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Updates a map theme within the collection.
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well) ...
void clear()
Removes all map themes from the collection.
void setExpandedGroupNodes(const QSet< QString > &expandedGroupNodes)
Sets a set of group identifiers for group nodes that should have expanded state.
Individual record of a visible layer in a map theme record.
void setHasCheckedStateInfo(bool hasInfo)
Sets whether the map theme contains valid checked/unchecked state of group nodes. ...
bool isExpanded() const
Returns whether the node should be shown as expanded or collapsed in GUI.
void mapThemesChanged()
Emitted when map themes within the collection are changed.
void setHasExpandedStateInfo(bool hasInfo)
Sets whether the map theme contains valid expanded/collapsed state of nodes.
QList< QgsMapLayer *> checkedLayers() const
Returns a list of any checked layers which belong to this node or its children.
void removeLayerRecord(QgsMapLayer *layer)
Removes a record for layer if present.
QStringList mapThemeVisibleLayerIds(const QString &name) const
Returns the list of layer IDs that are visible for the specified map theme.
void readXml(const QDomDocument &doc)
Reads the map theme collection state from XML.
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer&#39;s style manager.
void projectChanged()
Emitted when the project changes.
void readFromLayer(QgsMapLayer *layer)
Store layer&#39;s active style information in the instance.
The QgsLayerTreeModel class is model implementation for Qt item views framework.
Stores style information (renderer, opacity, labeling, diagrams etc.) applicable to a map layer...
QList< QgsMapLayer *> masterVisibleLayers() const
Returns the master list of visible layers.
void mapThemeChanged(const QString &theme)
Emitted when a map theme changes definition.
bool hasCheckedStateInfo() const
Returns whether information about checked/unchecked state of groups has been recorded and thus whethe...
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
QgsMapThemeCollection(QgsProject *project=nullptr)
Create map theme collection that handles themes of the given project.
QList< QgsMapLayer *> masterLayerOrder() const
Returns the master layer order (this will always match the project&#39;s QgsProject::layerOrder() )...
void removeMapTheme(const QString &name)
Removes an existing map theme from collection.
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
void setLayerRecords(const QList< QgsMapThemeCollection::MapThemeLayerRecord > &records)
Sets layer records for the theme.
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...
QSet< QString > checkedLegendItems
Rule keys of check legend items in layer tree model.
QStringList mapThemes() const
Returns a list of existing map theme names.
static bool isLayer(const QgsLayerTreeNode *node)
Check whether the node is a valid layer node.
Definition: qgslayertree.h:53
This class is a base class for nodes in a layer tree.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts, annotations, canvases, etc.
Definition: qgsproject.h:94
virtual bool legendSymbolItemChecked(const QString &key)
items of symbology items in legend is checked
QgsFeatureRenderer * renderer()
Returns renderer.
bool isVisible
true if the layer is visible in the associated theme.
QgsLayerTreeModelLegendNode * legendNode(const QString &rule, QgsLayerTreeModel &model)
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...
virtual void checkLegendSymbolItem(const QString &key, bool state=true)
item in symbology was checked
bool hasExpandedStateInfo() const
Returns whether information about expanded/collapsed state of nodes has been recorded and thus whethe...
void setExpanded(bool expanded)
Sets whether the node should be shown as expanded or collapsed in GUI.
void insert(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Inserts a new map theme to the collection.
void setItemVisibilityCheckedParentRecursive(bool checked)
Check or uncheck a node and all its parents.
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project&#39;s layer tree.
QgsMapLayer * layer() const
Gets pointer to the associated map layer.
QgsMapLayer * layer() const
Returns the map layer associated with this node.
QString currentStyle() const
Returns name of the current style.
void setCheckedGroupNodes(const QSet< QString > &checkedGroupNodes)
Sets a set of group identifiers for group nodes that should have checked state.
QList< QgsMapLayer * > layerOrder() const
The order in which layers will be rendered on the canvas.
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
QgsProject * project()
The QgsProject on which this map theme collection works.
QSet< QString > expandedLegendItems
Rule keys of expanded legend items in layer tree view.
QSet< QString > expandedGroupNodes() const
Returns a set of group identifiers for group nodes that should have expanded state (other group nodes...
virtual QVariant data(int role) const =0
Returns data associated with the item. Must be implemented in derived class.
The QgsLegendRendererItem class is abstract interface for legend items returned from QgsMapLayerLegen...
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...
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children)
bool expandedLayerNode
Whether the layer&#39;s tree node is expanded (only to be applied if the parent MapThemeRecord has the in...
void mapThemeRenamed(const QString &name, const QString &newName)
Emitted when a map theme within the collection is renamed.
virtual bool legendSymbolItemsCheckable() const
items of symbology items in legend should be checkable
void setOverrideStyle(const QString &style)
Temporarily apply a different style to the layer.
static QgsMapThemeCollection::MapThemeRecord createThemeFromCurrentState(QgsLayerTreeGroup *root, QgsLayerTreeModel *model)
Static method to create theme from the current state of layer visibilities in layer tree...
Management of styles for use with one map layer.
Represents a vector layer which manages a vector based data sets.
QString currentStyle
Name of the current style of the layer.
bool usingCurrentStyle
Whether current style is valid and should be applied.
void writeXml(QDomDocument &doc)
Writes the map theme collection state to XML.
void addLayerRecord(const QgsMapThemeCollection::MapThemeLayerRecord &record)
Add a new record for a layer.
bool setCurrentStyle(const QString &name)
Set a different style as the current style - will apply it to the layer.
bool usingLegendItems
Whether checkedLegendItems should be applied.
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...
Layer tree node points to a map layer.