QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 = nodeLayer->customProperty( QStringLiteral( "expandedLegendNodes" ) ).toStringList().toSet();
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( layerRec.expandedLegendItems.toList() ) );
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 void QgsMapThemeCollection::removeMapTheme( const QString &name )
285 {
286  if ( !mMapThemes.contains( name ) )
287  return;
288 
289  mMapThemes.remove( name );
290 
291  reconnectToLayersStyleManager();
292  emit mapThemesChanged();
293 }
294 
296 {
297  mMapThemes.clear();
298 
299  reconnectToLayersStyleManager();
300  emit mapThemesChanged();
301 }
302 
303 QStringList QgsMapThemeCollection::mapThemes() const
304 {
305  return mMapThemes.keys();
306 }
307 
308 QStringList QgsMapThemeCollection::mapThemeVisibleLayerIds( const QString &name ) const
309 {
310  QStringList layerIds;
311  const QList<QgsMapLayer *> constMapThemeVisibleLayers = mapThemeVisibleLayers( name );
312  for ( QgsMapLayer *layer : constMapThemeVisibleLayers )
313  {
314  layerIds << layer->id();
315  }
316  return layerIds;
317 }
318 
319 QList<QgsMapLayer *> QgsMapThemeCollection::mapThemeVisibleLayers( const QString &name ) const
320 {
321  QList<QgsMapLayer *> layers;
322  const QList<MapThemeLayerRecord> recs = mMapThemes.value( name ).mLayerRecords;
323  const QList<QgsMapLayer *> layerOrder = masterLayerOrder();
324  if ( layerOrder.isEmpty() )
325  {
326  // no master layer order - so we have to just use the stored theme layer order as a fallback
327  const QList<MapThemeLayerRecord> records { mMapThemes.value( name ).mLayerRecords };
328  for ( const MapThemeLayerRecord &layerRec : records )
329  {
330  if ( layerRec.isVisible && layerRec.layer() )
331  layers << layerRec.layer();
332  }
333  }
334  else
335  {
336  for ( QgsMapLayer *layer : layerOrder )
337  {
338  for ( const MapThemeLayerRecord &layerRec : recs )
339  {
340  if ( layerRec.isVisible && layerRec.layer() == layer )
341  layers << layerRec.layer();
342  }
343  }
344  }
345 
346  return layers;
347 }
348 
349 
350 void QgsMapThemeCollection::applyMapThemeCheckedLegendNodesToLayer( const MapThemeLayerRecord &layerRec, QgsMapLayer *layer )
351 {
352  QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( layer );
353  if ( !vlayer || !vlayer->renderer() )
354  return;
355 
356  QgsFeatureRenderer *renderer = vlayer->renderer();
357  if ( !renderer->legendSymbolItemsCheckable() )
358  return; // no need to do anything
359 
360  bool someNodesUnchecked = layerRec.usingLegendItems;
361 
362  const auto constLegendSymbolItems = vlayer->renderer()->legendSymbolItems();
363  for ( const QgsLegendSymbolItem &item : constLegendSymbolItems )
364  {
365  bool checked = renderer->legendSymbolItemChecked( item.ruleKey() );
366  bool shouldBeChecked = someNodesUnchecked ? layerRec.checkedLegendItems.contains( item.ruleKey() ) : true;
367  if ( checked != shouldBeChecked )
368  renderer->checkLegendSymbolItem( item.ruleKey(), shouldBeChecked );
369  }
370 }
371 
372 
373 QMap<QString, QString> QgsMapThemeCollection::mapThemeStyleOverrides( const QString &presetName )
374 {
375  QMap<QString, QString> styleOverrides;
376  if ( !mMapThemes.contains( presetName ) )
377  return styleOverrides;
378 
379  const QList<MapThemeLayerRecord> records {mMapThemes.value( presetName ).mLayerRecords};
380  for ( const MapThemeLayerRecord &layerRec : records )
381  {
382  if ( !layerRec.layer() )
383  continue;
384 
385  if ( layerRec.usingCurrentStyle )
386  {
387  QgsMapLayer *layer = layerRec.layer();
388  QgsMapLayerStyleOverride styleOverride( layer );
389  styleOverride.setOverrideStyle( layerRec.currentStyle );
390 
391  // set the checked legend nodes
392  applyMapThemeCheckedLegendNodesToLayer( layerRec, layer );
393 
394  // save to overrides
395  QgsMapLayerStyle layerStyle;
396  layerStyle.readFromLayer( layer );
397  styleOverrides[layer->id()] = layerStyle.xmlData();
398  }
399  }
400  return styleOverrides;
401 }
402 
403 void QgsMapThemeCollection::reconnectToLayersStyleManager()
404 {
405  // disconnect( 0, 0, this, SLOT( layerStyleRenamed( QString, QString ) ) );
406 
407  QSet<QgsMapLayer *> layers;
408  for ( const MapThemeRecord &rec : qgis::as_const( mMapThemes ) )
409  {
410  for ( const MapThemeLayerRecord &layerRec : qgis::as_const( rec.mLayerRecords ) )
411  {
412  if ( layerRec.layer() )
413  layers << layerRec.layer();
414  }
415  }
416 
417  const QSet<QgsMapLayer *> constLayers = layers;
418  for ( QgsMapLayer *ml : constLayers )
419  {
420  connect( ml->styleManager(), &QgsMapLayerStyleManager::styleRenamed, this, &QgsMapThemeCollection::layerStyleRenamed );
421  }
422 }
423 
424 void QgsMapThemeCollection::readXml( const QDomDocument &doc )
425 {
426  clear();
427 
428  QDomElement visPresetsElem = doc.firstChildElement( QStringLiteral( "qgis" ) ).firstChildElement( QStringLiteral( "visibility-presets" ) );
429  if ( visPresetsElem.isNull() )
430  return;
431 
432  QDomElement visPresetElem = visPresetsElem.firstChildElement( QStringLiteral( "visibility-preset" ) );
433  while ( !visPresetElem.isNull() )
434  {
435  QHash<QString, MapThemeLayerRecord> layerRecords; // key = layer ID
436 
437  bool expandedStateInfo = false;
438  if ( visPresetElem.hasAttribute( QStringLiteral( "has-expanded-info" ) ) )
439  expandedStateInfo = visPresetElem.attribute( QStringLiteral( "has-expanded-info" ) ).toInt();
440 
441  bool checkedStateInfo = false;
442  if ( visPresetElem.hasAttribute( QStringLiteral( "has-checked-group-info" ) ) )
443  checkedStateInfo = visPresetElem.attribute( QStringLiteral( "has-checked-group-info" ) ).toInt();
444 
445  QString presetName = visPresetElem.attribute( QStringLiteral( "name" ) );
446  QDomElement visPresetLayerElem = visPresetElem.firstChildElement( QStringLiteral( "layer" ) );
447  while ( !visPresetLayerElem.isNull() )
448  {
449  QString layerID = visPresetLayerElem.attribute( QStringLiteral( "id" ) );
450  if ( QgsMapLayer *layer = mProject->mapLayer( layerID ) )
451  {
452  layerRecords[layerID] = MapThemeLayerRecord( layer );
453  layerRecords[layerID].isVisible = visPresetLayerElem.attribute( QStringLiteral( "visible" ), QStringLiteral( "1" ) ).toInt();
454 
455  if ( visPresetLayerElem.hasAttribute( QStringLiteral( "style" ) ) )
456  {
457  layerRecords[layerID].usingCurrentStyle = true;
458  layerRecords[layerID].currentStyle = visPresetLayerElem.attribute( QStringLiteral( "style" ) );
459  }
460 
461  if ( visPresetLayerElem.hasAttribute( QStringLiteral( "expanded" ) ) )
462  layerRecords[layerID].expandedLayerNode = visPresetLayerElem.attribute( QStringLiteral( "expanded" ) ).toInt();
463  }
464  visPresetLayerElem = visPresetLayerElem.nextSiblingElement( QStringLiteral( "layer" ) );
465  }
466 
467  QDomElement checkedLegendNodesElem = visPresetElem.firstChildElement( QStringLiteral( "checked-legend-nodes" ) );
468  while ( !checkedLegendNodesElem.isNull() )
469  {
470  QSet<QString> checkedLegendNodes;
471 
472  QDomElement checkedLegendNodeElem = checkedLegendNodesElem.firstChildElement( QStringLiteral( "checked-legend-node" ) );
473  while ( !checkedLegendNodeElem.isNull() )
474  {
475  checkedLegendNodes << checkedLegendNodeElem.attribute( QStringLiteral( "id" ) );
476  checkedLegendNodeElem = checkedLegendNodeElem.nextSiblingElement( QStringLiteral( "checked-legend-node" ) );
477  }
478 
479  QString layerID = checkedLegendNodesElem.attribute( QStringLiteral( "id" ) );
480  if ( mProject->mapLayer( layerID ) ) // only use valid IDs
481  {
482  layerRecords[layerID].usingLegendItems = true;
483  layerRecords[layerID].checkedLegendItems = checkedLegendNodes;
484  }
485  checkedLegendNodesElem = checkedLegendNodesElem.nextSiblingElement( QStringLiteral( "checked-legend-nodes" ) );
486  }
487 
488  QSet<QString> expandedGroupNodes;
489  if ( expandedStateInfo )
490  {
491  // expanded state of legend nodes
492  QDomElement expandedLegendNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-legend-nodes" ) );
493  while ( !expandedLegendNodesElem.isNull() )
494  {
495  QSet<QString> expandedLegendNodes;
496 
497  QDomElement expandedLegendNodeElem = expandedLegendNodesElem.firstChildElement( QStringLiteral( "expanded-legend-node" ) );
498  while ( !expandedLegendNodeElem.isNull() )
499  {
500  expandedLegendNodes << expandedLegendNodeElem.attribute( QStringLiteral( "id" ) );
501  expandedLegendNodeElem = expandedLegendNodeElem.nextSiblingElement( QStringLiteral( "expanded-legend-node" ) );
502  }
503 
504  QString layerID = expandedLegendNodesElem.attribute( QStringLiteral( "id" ) );
505  if ( mProject->mapLayer( layerID ) ) // only use valid IDs
506  {
507  layerRecords[layerID].expandedLegendItems = expandedLegendNodes;
508  }
509  expandedLegendNodesElem = expandedLegendNodesElem.nextSiblingElement( QStringLiteral( "expanded-legend-nodes" ) );
510  }
511 
512  // expanded state of group nodes
513  QDomElement expandedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "expanded-group-nodes" ) );
514  if ( !expandedGroupNodesElem.isNull() )
515  {
516  QDomElement expandedGroupNodeElem = expandedGroupNodesElem.firstChildElement( QStringLiteral( "expanded-group-node" ) );
517  while ( !expandedGroupNodeElem.isNull() )
518  {
519  expandedGroupNodes << expandedGroupNodeElem.attribute( QStringLiteral( "id" ) );
520  expandedGroupNodeElem = expandedGroupNodeElem.nextSiblingElement( QStringLiteral( "expanded-group-node" ) );
521  }
522  }
523  }
524 
525  QSet<QString> checkedGroupNodes;
526  if ( checkedStateInfo )
527  {
528  // expanded state of legend nodes
529  QDomElement checkedGroupNodesElem = visPresetElem.firstChildElement( QStringLiteral( "checked-group-nodes" ) );
530  if ( !checkedGroupNodesElem.isNull() )
531  {
532  QDomElement checkedGroupNodeElem = checkedGroupNodesElem.firstChildElement( QStringLiteral( "checked-group-node" ) );
533  while ( !checkedGroupNodeElem.isNull() )
534  {
535  checkedGroupNodes << checkedGroupNodeElem.attribute( QStringLiteral( "id" ) );
536  checkedGroupNodeElem = checkedGroupNodeElem.nextSiblingElement( QStringLiteral( "checked-group-node" ) );
537  }
538  }
539  }
540 
541  MapThemeRecord rec;
542  rec.setLayerRecords( layerRecords.values() );
543  rec.setHasExpandedStateInfo( expandedStateInfo );
544  rec.setExpandedGroupNodes( expandedGroupNodes );
545  rec.setHasCheckedStateInfo( checkedStateInfo );
546  rec.setCheckedGroupNodes( checkedGroupNodes );
547  mMapThemes.insert( presetName, rec );
548  emit mapThemeChanged( presetName );
549 
550  visPresetElem = visPresetElem.nextSiblingElement( QStringLiteral( "visibility-preset" ) );
551  }
552 
553  reconnectToLayersStyleManager();
554  emit mapThemesChanged();
555 }
556 
557 void QgsMapThemeCollection::writeXml( QDomDocument &doc )
558 {
559  QDomElement visPresetsElem = doc.createElement( QStringLiteral( "visibility-presets" ) );
560 
561  QList< QString > keys = mMapThemes.keys();
562 
563  std::sort( keys.begin(), keys.end() );
564 
565  for ( const QString &grpName : qgis::as_const( keys ) )
566  {
567  const MapThemeRecord &rec = mMapThemes.value( grpName );
568  QDomElement visPresetElem = doc.createElement( QStringLiteral( "visibility-preset" ) );
569  visPresetElem.setAttribute( QStringLiteral( "name" ), grpName );
570  if ( rec.hasExpandedStateInfo() )
571  visPresetElem.setAttribute( QStringLiteral( "has-expanded-info" ), QStringLiteral( "1" ) );
572  if ( rec.hasCheckedStateInfo() )
573  visPresetElem.setAttribute( QStringLiteral( "has-checked-group-info" ), QStringLiteral( "1" ) );
574  for ( const MapThemeLayerRecord &layerRec : qgis::as_const( rec.mLayerRecords ) )
575  {
576  if ( !layerRec.layer() )
577  continue;
578  QString layerID = layerRec.layer()->id();
579  QDomElement layerElem = doc.createElement( QStringLiteral( "layer" ) );
580  layerElem.setAttribute( QStringLiteral( "id" ), layerID );
581  layerElem.setAttribute( QStringLiteral( "visible" ), layerRec.isVisible ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
582  if ( layerRec.usingCurrentStyle )
583  layerElem.setAttribute( QStringLiteral( "style" ), layerRec.currentStyle );
584  visPresetElem.appendChild( layerElem );
585 
586  if ( layerRec.usingLegendItems )
587  {
588  QDomElement checkedLegendNodesElem = doc.createElement( QStringLiteral( "checked-legend-nodes" ) );
589  checkedLegendNodesElem.setAttribute( QStringLiteral( "id" ), layerID );
590  for ( const QString &checkedLegendNode : qgis::as_const( layerRec.checkedLegendItems ) )
591  {
592  QDomElement checkedLegendNodeElem = doc.createElement( QStringLiteral( "checked-legend-node" ) );
593  checkedLegendNodeElem.setAttribute( QStringLiteral( "id" ), checkedLegendNode );
594  checkedLegendNodesElem.appendChild( checkedLegendNodeElem );
595  }
596  visPresetElem.appendChild( checkedLegendNodesElem );
597  }
598 
599  if ( rec.hasExpandedStateInfo() )
600  {
601  layerElem.setAttribute( QStringLiteral( "expanded" ), layerRec.expandedLayerNode ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
602 
603  QDomElement expandedLegendNodesElem = doc.createElement( QStringLiteral( "expanded-legend-nodes" ) );
604  expandedLegendNodesElem.setAttribute( QStringLiteral( "id" ), layerID );
605  for ( const QString &expandedLegendNode : qgis::as_const( layerRec.expandedLegendItems ) )
606  {
607  QDomElement expandedLegendNodeElem = doc.createElement( QStringLiteral( "expanded-legend-node" ) );
608  expandedLegendNodeElem.setAttribute( QStringLiteral( "id" ), expandedLegendNode );
609  expandedLegendNodesElem.appendChild( expandedLegendNodeElem );
610  }
611  visPresetElem.appendChild( expandedLegendNodesElem );
612  }
613  }
614 
615  if ( rec.hasCheckedStateInfo() )
616  {
617  QDomElement checkedGroupElems = doc.createElement( QStringLiteral( "checked-group-nodes" ) );
618  const QSet<QString> checkedGroupNodes = rec.checkedGroupNodes();
619  for ( const QString &groupId : checkedGroupNodes )
620  {
621  QDomElement checkedGroupElem = doc.createElement( QStringLiteral( "checked-group-node" ) );
622  checkedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
623  checkedGroupElems.appendChild( checkedGroupElem );
624  }
625  visPresetElem.appendChild( checkedGroupElems );
626  }
627 
628  if ( rec.hasExpandedStateInfo() )
629  {
630  QDomElement expandedGroupElems = doc.createElement( QStringLiteral( "expanded-group-nodes" ) );
631  const QSet<QString> expandedGroupNodes = rec.expandedGroupNodes();
632  for ( const QString &groupId : expandedGroupNodes )
633  {
634  QDomElement expandedGroupElem = doc.createElement( QStringLiteral( "expanded-group-node" ) );
635  expandedGroupElem.setAttribute( QStringLiteral( "id" ), groupId );
636  expandedGroupElems.appendChild( expandedGroupElem );
637  }
638  visPresetElem.appendChild( expandedGroupElems );
639  }
640 
641  visPresetsElem.appendChild( visPresetElem );
642  }
643 
644  doc.firstChildElement( QStringLiteral( "qgis" ) ).appendChild( visPresetsElem );
645 }
646 
647 void QgsMapThemeCollection::registryLayersRemoved( const QStringList &layerIDs )
648 {
649  // while layers are stored as weak pointers, this triggers the mapThemeChanged signal for
650  // affected themes
651  QSet< QString > changedThemes;
652  MapThemeRecordMap::iterator it = mMapThemes.begin();
653  for ( ; it != mMapThemes.end(); ++it )
654  {
655  MapThemeRecord &rec = it.value();
656  for ( int i = 0; i < rec.mLayerRecords.count(); ++i )
657  {
658  MapThemeLayerRecord &layerRec = rec.mLayerRecords[i];
659  if ( layerRec.layer() && layerIDs.contains( layerRec.layer()->id() ) )
660  {
661  rec.mLayerRecords.removeAt( i-- );
662  changedThemes << it.key();
663  }
664  }
665  }
666 
667  for ( const QString &theme : qgis::as_const( changedThemes ) )
668  {
669  emit mapThemeChanged( theme );
670  }
671  emit mapThemesChanged();
672 }
673 
674 void QgsMapThemeCollection::layerStyleRenamed( const QString &oldName, const QString &newName )
675 {
676  QgsMapLayerStyleManager *styleMgr = qobject_cast<QgsMapLayerStyleManager *>( sender() );
677  if ( !styleMgr )
678  return;
679 
680  QSet< QString > changedThemes;
681 
682  MapThemeRecordMap::iterator it = mMapThemes.begin();
683  for ( ; it != mMapThemes.end(); ++it )
684  {
685  MapThemeRecord &rec = it.value();
686  for ( int i = 0; i < rec.mLayerRecords.count(); ++i )
687  {
688  MapThemeLayerRecord &layerRec = rec.mLayerRecords[i];
689  if ( layerRec.layer() == styleMgr->layer() )
690  {
691  if ( layerRec.currentStyle == oldName )
692  {
693  layerRec.currentStyle = newName;
694  changedThemes << it.key();
695  }
696  }
697  }
698  }
699 
700  for ( const QString &theme : qgis::as_const( changedThemes ) )
701  {
702  emit mapThemeChanged( theme );
703  }
704  emit mapThemesChanged();
705 }
706 
708 {
709  for ( int i = 0; i < mLayerRecords.length(); ++i )
710  {
711  if ( mLayerRecords.at( i ).layer() == layer )
712  mLayerRecords.removeAt( i );
713  }
714 }
715 
717 {
718  mLayerRecords.append( record );
719 }
720 
721 QHash<QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord> QgsMapThemeCollection::MapThemeRecord::validLayerRecords() const
722 {
723  QHash<QgsMapLayer *, MapThemeLayerRecord> validSet;
724  for ( const MapThemeLayerRecord &layerRec : mLayerRecords )
725  {
726  if ( layerRec.layer() )
727  validSet.insert( layerRec.layer(), layerRec );
728  }
729  return validSet;
730 }
731 
733 {
734  mLayer = layer;
735 }
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:79
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.
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()
Remove 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)
Remove 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:91
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...
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.