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