1 /*************************************************************************** 2 qgsmapthemecollection.h 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 #ifndef QGSMAPTHEMECOLLECTION_H 17 #define QGSMAPTHEMECOLLECTION_H 18 19 #include "qgis_core.h" 20 #include "qgis_sip.h" 21 #include <QMap> 22 #include <QObject> 23 #include <QPointer> 24 #include <QSet> 25 #include <QStringList> 26 27 #include "qgsmaplayer.h" 28 29 class QDomDocument; 30 class QgsLayerTreeModel; 31 class QgsLayerTreeNode; 32 class QgsLayerTreeGroup; 33 class QgsLayerTreeLayer; 34 class QgsProject; 35 36 /** 37 * \class QgsMapThemeCollection 38 * \ingroup core 39 * \brief Container class that allows storage of map themes consisting of visible 40 * map layers and layer styles. 41 * \since QGIS 2.12 42 */ 43 44 class CORE_EXPORT QgsMapThemeCollection : public QObject 45 { 46 Q_OBJECT 47 Q_PROPERTY(QStringList mapThemes READ mapThemes NOTIFY mapThemesChanged)48 Q_PROPERTY( QStringList mapThemes READ mapThemes NOTIFY mapThemesChanged ) 49 Q_PROPERTY( QgsProject *project READ project WRITE setProject NOTIFY projectChanged ) 50 51 public: 52 53 /** 54 * \ingroup core 55 * \brief Individual record of a visible layer in a map theme record. 56 * \since QGIS 3.0 57 */ 58 class CORE_EXPORT MapThemeLayerRecord 59 { 60 public: 61 //! Initialize layer record with a map layer - it will be stored as a weak pointer 62 MapThemeLayerRecord( QgsMapLayer *l = nullptr ): mLayer( l ) {} 63 64 // TODO c++20 - replace with = default 65 bool operator==( const QgsMapThemeCollection::MapThemeLayerRecord &other ) const 66 { 67 return mLayer == other.mLayer && isVisible == other.isVisible && 68 usingCurrentStyle == other.usingCurrentStyle && currentStyle == other.currentStyle && 69 usingLegendItems == other.usingLegendItems && checkedLegendItems == other.checkedLegendItems && 70 expandedLegendItems == other.expandedLegendItems && expandedLayerNode == other.expandedLayerNode; 71 } 72 bool operator!=( const QgsMapThemeCollection::MapThemeLayerRecord &other ) const 73 { 74 return !( *this == other ); 75 } 76 77 //! Returns map layer or NULLPTR if the layer does not exist anymore 78 QgsMapLayer *layer() const { return mLayer; } 79 80 //! Sets the map layer for this record 81 void setLayer( QgsMapLayer *layer ); 82 83 /** 84 * TRUE if the layer is visible in the associated theme. 85 * \since QGIS 3.14 86 */ 87 bool isVisible = true; 88 89 //! Whether current style is valid and should be applied 90 bool usingCurrentStyle = false; 91 //! Name of the current style of the layer 92 QString currentStyle; 93 //! Whether checkedLegendItems should be applied 94 bool usingLegendItems = false; 95 //! Rule keys of check legend items in layer tree model 96 QSet<QString> checkedLegendItems; 97 98 /** 99 * Rule keys of expanded legend items in layer tree view. 100 * \since QGIS 3.2 101 */ 102 QSet<QString> expandedLegendItems; 103 104 /** 105 * Whether the layer's tree node is expanded 106 * (only to be applied if the parent MapThemeRecord has the information about expanded nodes stored) 107 * \since QGIS 3.2 108 */ 109 bool expandedLayerNode = false; 110 private: 111 //! Weak pointer to the layer 112 QgsWeakMapLayerPointer mLayer; 113 }; 114 115 /** 116 * \ingroup core 117 * \brief Individual map theme record of visible layers and styles. 118 * 119 * \since QGIS 3.0, Previously called PresetRecord 120 */ 121 class CORE_EXPORT MapThemeRecord 122 { 123 public: 124 125 bool operator==( const QgsMapThemeCollection::MapThemeRecord &other ) const 126 { 127 return validLayerRecords() == other.validLayerRecords() && 128 mHasExpandedStateInfo == other.mHasExpandedStateInfo && 129 mExpandedGroupNodes == other.mExpandedGroupNodes && mCheckedGroupNodes == other.mCheckedGroupNodes; 130 } 131 bool operator!=( const QgsMapThemeCollection::MapThemeRecord &other ) const 132 { 133 return !( *this == other ); 134 } 135 136 //! Returns a list of records for all visible layer belonging to the theme. layerRecords()137 QList<QgsMapThemeCollection::MapThemeLayerRecord> layerRecords() const { return mLayerRecords; } 138 139 //! Sets layer records for the theme. setLayerRecords(const QList<QgsMapThemeCollection::MapThemeLayerRecord> & records)140 void setLayerRecords( const QList<QgsMapThemeCollection::MapThemeLayerRecord> &records ) { mLayerRecords = records; } 141 142 //! Removes a record for \a layer if present. 143 void removeLayerRecord( QgsMapLayer *layer ); 144 145 //! Add a new record for a layer. 146 void addLayerRecord( const QgsMapThemeCollection::MapThemeLayerRecord &record ); 147 148 /** 149 * Returns set with only records for valid layers 150 */ 151 QHash<QgsMapLayer *, QgsMapThemeCollection::MapThemeLayerRecord> validLayerRecords() const SIP_SKIP; 152 153 /** 154 * Returns whether information about expanded/collapsed state of nodes has been recorded 155 * and thus whether expandedGroupNodes() and expandedLegendItems + expandedLayerNode from layer records are valid. 156 * \since QGIS 3.2 157 */ hasExpandedStateInfo()158 bool hasExpandedStateInfo() const { return mHasExpandedStateInfo; } 159 160 /** 161 * Returns whether information about checked/unchecked state of groups has been recorded 162 * and thus whether checkedGroupNodes() is valid. 163 * \note Not available in Python bindings 164 * \since QGIS 3.10.1 165 */ hasCheckedStateInfo()166 bool hasCheckedStateInfo() const { return mHasCheckedStateInfo; } SIP_SKIP; 167 168 /** 169 * Sets whether the map theme contains valid expanded/collapsed state of nodes 170 * \since QGIS 3.2 171 */ setHasExpandedStateInfo(bool hasInfo)172 void setHasExpandedStateInfo( bool hasInfo ) { mHasExpandedStateInfo = hasInfo; } 173 174 /** 175 * Sets whether the map theme contains valid checked/unchecked state of group nodes 176 * \note Not available in Python bindings 177 * \since QGIS 3.10.1 178 */ setHasCheckedStateInfo(bool hasInfo)179 void setHasCheckedStateInfo( bool hasInfo ) { mHasCheckedStateInfo = hasInfo; } SIP_SKIP; 180 181 /** 182 * Returns a set of group identifiers for group nodes that should have expanded state (other group nodes should be collapsed). 183 * The returned value is valid only when hasExpandedStateInfo() returns TRUE. 184 * Group identifiers are built using group names, a sub-group name is prepended by parent group's identifier 185 * and a forward slash, e.g. "level1/level2" 186 * \since QGIS 3.2 187 */ expandedGroupNodes()188 QSet<QString> expandedGroupNodes() const { return mExpandedGroupNodes; } 189 190 /** 191 * Returns a set of group identifiers for group nodes that should have checked state (other group nodes should be unchecked). 192 * The returned value is valid only when hasCheckedStateInfo() returns TRUE. 193 * Group identifiers are built using group names, a sub-group name is prepended by parent group's identifier 194 * and a forward slash, e.g. "level1/level2" 195 * \since QGIS 3.10.1 196 */ checkedGroupNodes()197 QSet<QString> checkedGroupNodes() const { return mCheckedGroupNodes; } 198 199 /** 200 * Sets a set of group identifiers for group nodes that should have expanded state. See expandedGroupNodes(). 201 * \since QGIS 3.2 202 */ setExpandedGroupNodes(const QSet<QString> & expandedGroupNodes)203 void setExpandedGroupNodes( const QSet<QString> &expandedGroupNodes ) { mExpandedGroupNodes = expandedGroupNodes; } 204 205 /** 206 * Sets a set of group identifiers for group nodes that should have checked state. See checkedGroupNodes(). 207 * \since QGIS 3.10.1 208 */ setCheckedGroupNodes(const QSet<QString> & checkedGroupNodes)209 void setCheckedGroupNodes( const QSet<QString> &checkedGroupNodes ) { mCheckedGroupNodes = checkedGroupNodes; } 210 211 private: 212 //! Layer-specific records for the theme. Only visible layers are listed. 213 QList<MapThemeLayerRecord> mLayerRecords; 214 215 //! Whether the information about expanded/collapsed state of groups, layers and legend items has been stored 216 bool mHasExpandedStateInfo = false; 217 //! Whether the information about checked/unchecked state of groups, layers and legend items has been stored 218 bool mHasCheckedStateInfo = false; 219 220 /** 221 * Which groups should be expanded. Each group is identified by its name (sub-groups IDs are prepended with parent 222 * group and forward slash - e.g. "level1/level2/level3"). 223 */ 224 QSet<QString> mExpandedGroupNodes; 225 226 /** 227 * Which groups should be checked. Each group is identified by its name (sub-groups IDs are prepended with parent 228 * group and forward slash - e.g. "level1/level2/level3"). 229 */ 230 QSet<QString> mCheckedGroupNodes; 231 232 friend class QgsMapThemeCollection; 233 }; 234 235 /** 236 * Create map theme collection that handles themes of the given project. 237 */ 238 QgsMapThemeCollection( QgsProject *project = nullptr ); 239 240 /** 241 * Returns whether a map theme with a matching name exists. 242 * \since QGIS 3.0 243 */ 244 bool hasMapTheme( const QString &name ) const; 245 246 /** 247 * Inserts a new map theme to the collection. 248 * \see update() 249 */ 250 void insert( const QString &name, const QgsMapThemeCollection::MapThemeRecord &state ); 251 252 /** 253 * Updates a map theme within the collection. 254 * \param name name of map theme to update 255 * \param state map theme record to replace existing map theme 256 * \see insert() 257 */ 258 void update( const QString &name, const QgsMapThemeCollection::MapThemeRecord &state ); 259 260 /** 261 * Removes an existing map theme from collection. 262 * \since QGIS 3.0 263 */ 264 void removeMapTheme( const QString &name ); 265 266 /** 267 * Renames the existing map theme called \a name to \a newName. 268 * Returns TRUE if the rename was successful, or FALSE if it failed (e.g. due to a duplicate name for \a newName). 269 * \since QGIS 3.14 270 */ 271 bool renameMapTheme( const QString &name, const QString &newName ); 272 273 //! Removes all map themes from the collection. 274 void clear(); 275 276 /** 277 * Returns a list of existing map theme names. 278 * \since QGIS 3.0 279 */ 280 QStringList mapThemes() const; 281 282 /** 283 * Returns the recorded state of a map theme. 284 * \since QGIS 3.0 285 */ mapThemeState(const QString & name)286 QgsMapThemeCollection::MapThemeRecord mapThemeState( const QString &name ) const { return mMapThemes[name]; } 287 288 /** 289 * Returns the list of layer IDs that are visible for the specified map theme. 290 * 291 * \note The order of the returned list is not guaranteed to reflect the order of layers 292 * in the canvas. 293 * \since QGIS 3.0 294 */ 295 QStringList mapThemeVisibleLayerIds( const QString &name ) const; 296 297 /** 298 * Returns the list of layers that are visible for the specified map theme. 299 * 300 * \note The order of the returned list is not guaranteed to reflect the order of layers 301 * in the canvas. 302 * \since QGIS 3.0 303 */ 304 QList<QgsMapLayer *> mapThemeVisibleLayers( const QString &name ) const; 305 306 /** 307 * Gets layer style overrides (for QgsMapSettings) of the visible layers for given map theme. 308 * \since QGIS 3.0 309 */ 310 QMap<QString, QString> mapThemeStyleOverrides( const QString &name ); 311 312 /** 313 * Reads the map theme collection state from XML 314 * \param doc DOM document 315 * \see writeXml 316 */ 317 void readXml( const QDomDocument &doc ); 318 319 /** 320 * Writes the map theme collection state to XML. 321 * \param doc DOM document 322 * \see readXml 323 */ 324 void writeXml( QDomDocument &doc ); 325 326 /** 327 * Static method to create theme from the current state of layer visibilities in layer tree, 328 * current style of layers and check state of legend items (from a layer tree model). 329 * \since QGIS 3.0 330 */ 331 static QgsMapThemeCollection::MapThemeRecord createThemeFromCurrentState( QgsLayerTreeGroup *root, QgsLayerTreeModel *model ); 332 333 /** 334 * Apply theme given by its name and modify layer tree, current style of layers and checked 335 * legend items of passed layer tree model. 336 * \since QGIS 3.0 337 */ 338 void applyTheme( const QString &name, QgsLayerTreeGroup *root, QgsLayerTreeModel *model ); 339 340 /** 341 * The QgsProject on which this map theme collection works. 342 * 343 * \since QGIS 3.0 344 */ 345 QgsProject *project(); 346 347 /** 348 * \copydoc project() 349 * \since QGIS 3.0 350 */ 351 void setProject( QgsProject *project ); 352 353 /** 354 * Returns the master layer order (this will always match the project's QgsProject::layerOrder() ). 355 * All map themes will maintain the same layer order as the master layer order. 356 * \see masterVisibleLayers() 357 * \since QGIS 3.0 358 */ 359 QList< QgsMapLayer * > masterLayerOrder() const; 360 361 /** 362 * Returns the master list of visible layers. The order of returned layers will always match those 363 * of masterLayerOrder(), but the returned layers are filtered to only include those visible 364 * in the project's layer tree. 365 * \see masterLayerOrder() 366 * \since QGIS 3.0 367 */ 368 QList< QgsMapLayer * > masterVisibleLayers() const; 369 370 signals: 371 372 /** 373 * Emitted when map themes within the collection are changed. 374 * \since QGIS 3.0 375 */ 376 void mapThemesChanged(); 377 378 /** 379 * Emitted when a map theme changes definition. 380 * \since QGIS 3.0 381 */ 382 void mapThemeChanged( const QString &theme ); 383 384 /** 385 * Emitted when a map theme within the collection is renamed. 386 * \since QGIS 3.14 387 */ 388 void mapThemeRenamed( const QString &name, const QString &newName ); 389 390 /** 391 * Emitted when the project changes 392 * 393 * \copydoc project() 394 * \since QGIS 3.0 395 */ 396 void projectChanged(); 397 398 private slots: 399 400 /** 401 * Handles updates of the map theme collection when layers are removed from the registry 402 */ 403 void registryLayersRemoved( const QStringList &layerIDs ); 404 405 //! Update style name if a stored style gets renamed 406 void layerStyleRenamed( const QString &oldName, const QString &newName ); 407 408 private: 409 410 /** 411 * Apply check states of legend nodes of a given layer as defined in the map theme. 412 */ 413 void applyMapThemeCheckedLegendNodesToLayer( const MapThemeLayerRecord &layerRec, QgsMapLayer *layer ); 414 415 /** 416 * Reconnects all map theme layers to handle style renames 417 */ 418 void reconnectToLayersStyleManager(); 419 420 static bool findRecordForLayer( QgsMapLayer *layer, const MapThemeRecord &rec, MapThemeLayerRecord &layerRec ); 421 static MapThemeLayerRecord createThemeLayerRecord( QgsLayerTreeLayer *nodeLayer, QgsLayerTreeModel *model ); 422 static void createThemeFromCurrentState( QgsLayerTreeGroup *parent, QgsLayerTreeModel *model, MapThemeRecord &rec ); 423 static void applyThemeToLayer( QgsLayerTreeLayer *nodeLayer, QgsLayerTreeModel *model, const MapThemeRecord &rec ); 424 static void applyThemeToGroup( QgsLayerTreeGroup *parent, QgsLayerTreeModel *model, const MapThemeRecord &rec ); 425 426 typedef QMap<QString, MapThemeRecord> MapThemeRecordMap; 427 MapThemeRecordMap mMapThemes; 428 //! project used to retrieve layers from layer IDs 429 QgsProject *mProject = nullptr; 430 }; 431 432 433 #endif // QGSMAPTHEMECOLLECTION_H 434