Skip to content

Commit 3ce9c70

Browse files
committedJan 7, 2015
Merge pull request #1755 from wonder-sk/multiple-styles
[FEATURE] Support for multiple styles per map layer Available in legend context menu in Styles sub-menu where it is possible to add/remove styles and quickly switch between them.
2 parents 0b954c1 + 2144be0 commit 3ce9c70

18 files changed

+846
-11
lines changed
 

‎python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
%Include qgsmaplayerlegend.sip
5555
%Include qgsmaplayerregistry.sip
5656
%Include qgsmaplayerrenderer.sip
57+
%Include qgsmaplayerstylemanager.sip
5758
%Include qgsmaprenderer.sip
5859
%Include qgsmaprenderercache.sip
5960
%Include qgsmaprenderercustompainterjob.sip

‎python/core/qgsmaplayer.sip

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,21 @@ class QgsMapLayer : QObject
370370
*/
371371
QgsMapLayerLegend* legend() const;
372372

373+
/**
374+
* Enable or disable layer's style manager. When disabled (default), the styleManager() will return null pointer.
375+
* By enabling the style manager will be created with one default style (same as the layer's active style).
376+
* By disabling the style manager all associated styles will be lost (only the layer's active style will stay).
377+
* @note added in 2.8
378+
*/
379+
void enableStyleManager( bool enable = true );
380+
381+
/**
382+
* Get access to the layer's style manager. Style manager allows switching between multiple styles.
383+
* If the style manager is not enabled, null pointer will be returned.
384+
* @note added in 2.8
385+
*/
386+
QgsMapLayerStyleManager* styleManager() const;
387+
373388
/**Returns the minimum scale denominator at which the layer is visible.
374389
* Scale based visibility is only used if hasScaleBasedVisibility is true.
375390
* @returns minimum scale denominator at which the layer will render
@@ -517,6 +532,11 @@ class QgsMapLayer : QObject
517532
/** Write custom properties to project file. */
518533
void writeCustomProperties( QDomNode & layerNode, QDomDocument & doc ) const;
519534

535+
/** Read style manager's configuration (if any). To be called by subclasses. */
536+
void readStyleManager( const QDomNode& layerNode );
537+
/** Write style manager's configuration (if exists). To be called by subclasses. */
538+
void writeStyleManager( QDomNode& layerNode, QDomDocument& doc ) const;
539+
520540
/** debugging member - invoked when a connect() is made to this object */
521541
void connectNotify( const char * signal );
522542

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
2+
class QgsMapLayerStyle
3+
{
4+
%TypeHeaderCode
5+
#include <qgsmaplayerstylemanager.h>
6+
%End
7+
public:
8+
//! construct invalid style
9+
QgsMapLayerStyle();
10+
11+
//! Tell whether the style is valid (i.e. there is something stored in it)
12+
bool isValid() const;
13+
14+
//! Return information about the style - for debugging purposes only
15+
QString dump() const;
16+
17+
//! Store layer's active style information in the instance
18+
void readFromLayer( QgsMapLayer* layer );
19+
//! Apply stored layer's style information to the layer
20+
void writeToLayer( QgsMapLayer* layer ) const;
21+
22+
//! Read style configuration (for project file reading)
23+
void readXml( const QDomElement& styleElement );
24+
//! Write style configuration (for project file writing)
25+
void writeXml( QDomElement& styleElement ) const;
26+
};
27+
28+
29+
class QgsMapLayerStyleManager
30+
{
31+
%TypeHeaderCode
32+
#include <qgsmaplayerstylemanager.h>
33+
%End
34+
public:
35+
//! Construct a style manager associated with a map layer (must not be null)
36+
QgsMapLayerStyleManager( QgsMapLayer* layer );
37+
38+
//! Read configuration (for project loading)
39+
void readXml( const QDomElement& mgrElement );
40+
//! Write configuration (for project saving)
41+
void writeXml( QDomElement& mgrElement ) const;
42+
43+
//! Return list of all defined style names
44+
QStringList styles() const;
45+
//! Return data of a stored style - accessed by its unique name
46+
QgsMapLayerStyle style( const QString& name ) const;
47+
48+
//! Add a style with given name and data
49+
//! @return true on success (name is unique and style is valid)
50+
bool addStyle( const QString& name, const QgsMapLayerStyle& style );
51+
//! Add style by cloning the current one
52+
//! @return true on success
53+
bool addStyleFromLayer( const QString& name );
54+
//! Remove a stored style
55+
//! @return true on success (style exists and it is not the last one)
56+
bool removeStyle( const QString& name );
57+
58+
//! Return name of the current style
59+
QString currentStyle() const;
60+
//! Set a different style as the current style - will apply it to the layer
61+
//! @return true on success
62+
bool setCurrentStyle( const QString& name );
63+
};

‎src/app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ SET(QGIS_APP_SRCS
4545
qgslabelinggui.cpp
4646
qgslabelpreview.cpp
4747
qgsloadstylefromdbdialog.cpp
48+
qgsmaplayerstyleguiutils.cpp
4849
qgsmapmouseevent.cpp
4950
qgssavestyletodbdialog.cpp
5051
qgsguivectorlayertools.cpp
@@ -200,6 +201,7 @@ SET (QGIS_APP_MOC_HDRS
200201
qgslabelinggui.h
201202
qgslabelpropertydialog.h
202203
qgsloadstylefromdbdialog.h
204+
qgsmaplayerstyleguiutils.h
203205
qgssavestyletodbdialog.h
204206
qgsshortcutsmanager.h
205207
qgsapplayertreeviewmenuprovider.h

‎src/app/qgisapp.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
#include "qgsmapcanvas.h"
150150
#include "qgsmaplayer.h"
151151
#include "qgsmaplayerregistry.h"
152+
#include "qgsmaplayerstyleguiutils.h"
152153
#include "qgsmapoverviewcanvas.h"
153154
#include "qgsmaprenderer.h"
154155
#include "qgsmapsettings.h"
@@ -897,6 +898,8 @@ QgisApp::~QgisApp()
897898
delete QgsProject::instance();
898899

899900
delete mPythonUtils;
901+
902+
QgsMapLayerStyleGuiUtils::cleanup();
900903
}
901904

902905
void QgisApp::dragEnterEvent( QDragEnterEvent *event )

‎src/app/qgsapplayertreeviewmenuprovider.cpp

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "qgslayertree.h"
88
#include "qgslayertreemodel.h"
99
#include "qgslayertreeviewdefaultactions.h"
10+
#include "qgsmaplayerstyleguiutils.h"
1011
#include "qgsproject.h"
1112
#include "qgsrasterlayer.h"
1213
#include "qgsvectordataprovider.h"
@@ -87,6 +88,21 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
8788
// assign layer crs to project
8889
menu->addAction( QgsApplication::getThemeIcon( "/mActionSetProjectCRS.png" ), tr( "Set &Project CRS from Layer" ), QgisApp::instance(), SLOT( setProjectCRSFromLayer() ) );
8990

91+
// style-related actions
92+
if ( mView->selectedLayerNodes().count() == 1 )
93+
{
94+
QMenu* menuStyleManager = QgsMapLayerStyleGuiUtils::instance()->createStyleManagerMenu( layer );
95+
96+
QgisApp* app = QgisApp::instance();
97+
menuStyleManager->addSeparator();
98+
menuStyleManager->addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) );
99+
if ( app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
100+
{
101+
menuStyleManager->addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) );
102+
}
103+
menu->addMenu( menuStyleManager );
104+
}
105+
90106
menu->addSeparator();
91107

92108
if ( layer && layer->type() == QgsMapLayer::VectorLayer )
@@ -157,16 +173,6 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
157173

158174
if ( mView->selectedNodes( true ).count() >= 2 )
159175
menu->addAction( actions->actionGroupSelected( menu ) );
160-
161-
if ( mView->selectedLayerNodes().count() == 1 )
162-
{
163-
QgisApp* app = QgisApp::instance();
164-
menu->addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) );
165-
if ( app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
166-
{
167-
menu->addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) );
168-
}
169-
}
170176
}
171177

172178
}

‎src/app/qgsapplayertreeviewmenuprovider.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ class QgsAppLayerTreeViewMenuProvider : public QObject, public QgsLayerTreeViewM
3636
void removeLegendLayerActionsForLayer( QgsMapLayer* layer );
3737
QList< LegendLayerAction > legendLayerActions( QgsMapLayer::LayerType type ) const;
3838

39-
4039
protected:
4140

4241
void addCustomLayerActions( QMenu* menu, QgsMapLayer* layer );

‎src/app/qgsmaplayerstyleguiutils.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/***************************************************************************
2+
qgsmaplayerstyleguiutils.cpp
3+
--------------------------------------
4+
Date : January 2015
5+
Copyright : (C) 2015 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 "qgsmaplayerstyleguiutils.h"
17+
18+
#include <QAction>
19+
#include <QInputDialog>
20+
#include <QMenu>
21+
22+
#include "qgslogger.h"
23+
#include "qgsmapcanvas.h"
24+
#include "qgsmaplayer.h"
25+
#include "qgsmaplayerstylemanager.h"
26+
27+
28+
QMenu* QgsMapLayerStyleGuiUtils::createStyleManagerMenu( QgsMapLayer* layer )
29+
{
30+
QMenu* m = new QMenu( tr( "Styles" ) );
31+
QAction* actionAdd = m->addAction( tr( "Add..." ), this, SLOT( addStyle() ) );
32+
actionAdd->setData( QVariant::fromValue<QObject*>( layer ) );
33+
34+
QgsMapLayerStyleManager* mgr = layer->styleManager();
35+
36+
if ( !mgr )
37+
return m;
38+
39+
QMenu* mRemove = m->addMenu( tr( "Remove" ) );
40+
m->addSeparator();
41+
42+
foreach ( QString name, mgr->styles() )
43+
{
44+
bool active = name == mgr->currentStyle();
45+
if ( name.isEmpty() )
46+
name = defaultStyleName();
47+
QAction* actionUse = m->addAction( name, this, SLOT( useStyle() ) );
48+
actionUse->setCheckable( true );
49+
actionUse->setChecked( active );
50+
actionUse->setData( QVariant::fromValue<QObject*>( layer ) );
51+
52+
QAction* actionRemove = mRemove->addAction( name, this, SLOT( removeStyle() ) );
53+
actionRemove->setData( QVariant::fromValue<QObject*>( layer ) );
54+
}
55+
56+
return m;
57+
}
58+
59+
QString QgsMapLayerStyleGuiUtils::defaultStyleName()
60+
{
61+
return tr( "(default)" );
62+
}
63+
64+
65+
void QgsMapLayerStyleGuiUtils::addStyle()
66+
{
67+
QAction* a = qobject_cast<QAction*>( sender() );
68+
if ( !a )
69+
return;
70+
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
71+
if ( !layer )
72+
return;
73+
74+
bool ok;
75+
QString text = QInputDialog::getText( 0, tr( "New style" ),
76+
tr( "Style name:" ), QLineEdit::Normal,
77+
"new style", &ok );
78+
if ( !ok || text.isEmpty() )
79+
return;
80+
81+
layer->enableStyleManager(); // make sure it exists
82+
83+
bool res = layer->styleManager()->addStyleFromLayer( text );
84+
85+
if ( res ) // make it active!
86+
layer->styleManager()->setCurrentStyle( text );
87+
else
88+
QgsDebugMsg( "Failed to add style: " + text );
89+
}
90+
91+
void QgsMapLayerStyleGuiUtils::useStyle()
92+
{
93+
QAction* a = qobject_cast<QAction*>( sender() );
94+
if ( !a )
95+
return;
96+
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
97+
if ( !layer )
98+
return;
99+
QString name = a->text();
100+
if ( name == defaultStyleName() )
101+
name.clear();
102+
103+
bool res = layer->styleManager()->setCurrentStyle( name );
104+
if ( !res )
105+
QgsDebugMsg( "Failed to set current style: " + name );
106+
107+
layer->triggerRepaint();
108+
}
109+
110+
111+
void QgsMapLayerStyleGuiUtils::removeStyle()
112+
{
113+
QAction* a = qobject_cast<QAction*>( sender() );
114+
if ( !a )
115+
return;
116+
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
117+
if ( !layer )
118+
return;
119+
120+
if ( layer->styleManager()->styles().count() == 1 )
121+
{
122+
// let's get rid of the style manager altogether
123+
layer->enableStyleManager( false );
124+
return;
125+
}
126+
127+
QString name = a->text();
128+
if ( name == defaultStyleName() )
129+
name.clear();
130+
131+
bool needsRefresh = ( layer->styleManager()->currentStyle() == name );
132+
133+
bool res = layer->styleManager()->removeStyle( name );
134+
if ( !res )
135+
QgsDebugMsg( "Failed to remove style: " + name );
136+
137+
if ( needsRefresh )
138+
layer->triggerRepaint();
139+
}

‎src/app/qgsmaplayerstyleguiutils.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/***************************************************************************
2+
qgsmaplayerstyleguiutils.h
3+
--------------------------------------
4+
Date : January 2015
5+
Copyright : (C) 2015 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 QGSMAPLAYERSTYLEGUIUTILS_H
17+
#define QGSMAPLAYERSTYLEGUIUTILS_H
18+
19+
#include <QObject>
20+
21+
#include "qgssingleton.h"
22+
23+
class QgsMapLayer;
24+
class QMenu;
25+
26+
/** Various GUI utility functions for dealing with map layer's style manager */
27+
class QgsMapLayerStyleGuiUtils : public QObject, public QgsSingleton<QgsMapLayerStyleGuiUtils>
28+
{
29+
Q_OBJECT
30+
public:
31+
32+
//! Return menu instance with actions for the give map layer
33+
QMenu* createStyleManagerMenu( QgsMapLayer* layer );
34+
35+
private:
36+
QString defaultStyleName();
37+
38+
private slots:
39+
void addStyle();
40+
void useStyle();
41+
void removeStyle();
42+
43+
};
44+
45+
#endif // QGSMAPLAYERSTYLEGUIUTILS_H

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ SET(QGIS_CORE_SRCS
106106
qgsmaplayer.cpp
107107
qgsmaplayerlegend.cpp
108108
qgsmaplayerregistry.cpp
109+
qgsmaplayerstylemanager.cpp
109110
qgsmaprenderer.cpp
110111
qgsmaprenderercache.cpp
111112
qgsmaprenderercustompainterjob.cpp
@@ -495,6 +496,7 @@ SET(QGIS_CORE_HDRS
495496
qgsmaplayer.h
496497
qgsmaplayerlegend.h
497498
qgsmaplayerregistry.h
499+
qgsmaplayerstylemanager.h
498500
qgsmaprenderer.h
499501
qgsmaprenderercache.h
500502
qgsmaprenderercustompainterjob.h

‎src/core/qgsmaplayer.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "qgscoordinatereferencesystem.h"
3737
#include "qgsapplication.h"
3838
#include "qgsmaplayerlegend.h"
39+
#include "qgsmaplayerstylemanager.h"
3940
#include "qgsproject.h"
4041
#include "qgspluginlayerregistry.h"
4142
#include "qgsprojectfiletransform.h"
@@ -55,6 +56,7 @@ QgsMapLayer::QgsMapLayer( QgsMapLayer::LayerType type,
5556
mLayerType( type ),
5657
mBlendMode( QPainter::CompositionMode_SourceOver ) // Default to normal blending
5758
, mLegend( 0 )
59+
, mStyleManager( 0 )
5860
{
5961
mCRS = new QgsCoordinateReferenceSystem();
6062

@@ -702,6 +704,28 @@ void QgsMapLayer::writeCustomProperties( QDomNode &layerNode, QDomDocument &doc
702704
mCustomProperties.writeXml( layerNode, doc );
703705
}
704706

707+
void QgsMapLayer::readStyleManager( const QDomNode& layerNode )
708+
{
709+
QDomElement styleMgrElem = layerNode.firstChildElement( "map-layer-style-manager" );
710+
if ( !styleMgrElem.isNull() )
711+
{
712+
enableStyleManager();
713+
styleManager()->readXml( styleMgrElem );
714+
}
715+
else
716+
enableStyleManager( false );
717+
}
718+
719+
void QgsMapLayer::writeStyleManager( QDomNode& layerNode, QDomDocument& doc ) const
720+
{
721+
if ( mStyleManager )
722+
{
723+
QDomElement styleMgrElem = doc.createElement( "map-layer-style-manager" );
724+
mStyleManager->writeXml( styleMgrElem );
725+
layerNode.appendChild( styleMgrElem );
726+
}
727+
}
728+
705729

706730

707731

@@ -1424,6 +1448,27 @@ QgsMapLayerLegend*QgsMapLayer::legend() const
14241448
return mLegend;
14251449
}
14261450

1451+
void QgsMapLayer::enableStyleManager( bool enable )
1452+
{
1453+
if (( enable && mStyleManager ) || ( !enable && !mStyleManager ) )
1454+
return;
1455+
1456+
if ( enable )
1457+
{
1458+
mStyleManager = new QgsMapLayerStyleManager( this );
1459+
}
1460+
else
1461+
{
1462+
delete mStyleManager;
1463+
mStyleManager = 0;
1464+
}
1465+
}
1466+
1467+
QgsMapLayerStyleManager* QgsMapLayer::styleManager() const
1468+
{
1469+
return mStyleManager;
1470+
}
1471+
14271472
void QgsMapLayer::clearCacheImage()
14281473
{
14291474
emit repaintRequested();

‎src/core/qgsmaplayer.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class QgsRenderContext;
3636
class QgsCoordinateReferenceSystem;
3737
class QgsMapLayerLegend;
3838
class QgsMapLayerRenderer;
39+
class QgsMapLayerStyleManager;
3940

4041
class QDomDocument;
4142
class QKeyEvent;
@@ -387,6 +388,21 @@ class CORE_EXPORT QgsMapLayer : public QObject
387388
*/
388389
QgsMapLayerLegend* legend() const;
389390

391+
/**
392+
* Enable or disable layer's style manager. When disabled (default), the styleManager() will return null pointer.
393+
* By enabling the style manager will be created with one default style (same as the layer's active style).
394+
* By disabling the style manager all associated styles will be lost (only the layer's active style will stay).
395+
* @note added in 2.8
396+
*/
397+
void enableStyleManager( bool enable = true );
398+
399+
/**
400+
* Get access to the layer's style manager. Style manager allows switching between multiple styles.
401+
* If the style manager is not enabled, null pointer will be returned.
402+
* @note added in 2.8
403+
*/
404+
QgsMapLayerStyleManager* styleManager() const;
405+
390406
/**Returns the minimum scale denominator at which the layer is visible.
391407
* Scale based visibility is only used if hasScaleBasedVisibility is true.
392408
* @returns minimum scale denominator at which the layer will render
@@ -533,6 +549,11 @@ class CORE_EXPORT QgsMapLayer : public QObject
533549
/** Write custom properties to project file. */
534550
void writeCustomProperties( QDomNode & layerNode, QDomDocument & doc ) const;
535551

552+
/** Read style manager's configuration (if any). To be called by subclasses. */
553+
void readStyleManager( const QDomNode& layerNode );
554+
/** Write style manager's configuration (if exists). To be called by subclasses. */
555+
void writeStyleManager( QDomNode& layerNode, QDomDocument& doc ) const;
556+
536557
/** debugging member - invoked when a connect() is made to this object */
537558
void connectNotify( const char * signal );
538559

@@ -621,6 +642,9 @@ class CORE_EXPORT QgsMapLayer : public QObject
621642

622643
//! Controller of legend items of this layer
623644
QgsMapLayerLegend* mLegend;
645+
646+
//! Manager of multiple styles available for a layer (may be null)
647+
QgsMapLayerStyleManager* mStyleManager;
624648
};
625649

626650
#endif

‎src/core/qgsmaplayerstylemanager.cpp

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/***************************************************************************
2+
qgsmaplayerstylemanager.cpp
3+
--------------------------------------
4+
Date : January 2015
5+
Copyright : (C) 2015 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 "qgsmaplayerstylemanager.h"
17+
18+
#include "qgslogger.h"
19+
#include "qgsmaplayer.h"
20+
21+
#include <QDomElement>
22+
#include <QTemporaryFile>
23+
#include <QTextStream>
24+
25+
QgsMapLayerStyleManager::QgsMapLayerStyleManager( QgsMapLayer* layer )
26+
: mLayer( layer )
27+
{
28+
QgsMapLayerStyle defaultStyle;
29+
defaultStyle.readFromLayer( mLayer );
30+
mStyles.insert( QString(), defaultStyle );
31+
}
32+
33+
void QgsMapLayerStyleManager::readXml( const QDomElement& mgrElement )
34+
{
35+
mCurrentStyle = mgrElement.attribute( "current" );
36+
37+
mStyles.clear();
38+
QDomElement ch = mgrElement.firstChildElement( "map-layer-style" );
39+
while ( !ch.isNull() )
40+
{
41+
QString name = ch.attribute( "name" );
42+
QgsMapLayerStyle style;
43+
style.readXml( ch );
44+
mStyles.insert( name, style );
45+
46+
ch = ch.nextSiblingElement( "map-layer-style" );
47+
}
48+
}
49+
50+
void QgsMapLayerStyleManager::writeXml( QDomElement& mgrElement ) const
51+
{
52+
const_cast<QgsMapLayerStyleManager*>( this )->syncCurrentStyle();
53+
54+
QDomDocument doc = mgrElement.ownerDocument();
55+
mgrElement.setAttribute( "current", mCurrentStyle );
56+
57+
foreach ( const QString& name, styles() )
58+
{
59+
QDomElement ch = doc.createElement( "map-layer-style" );
60+
ch.setAttribute( "name", name );
61+
mStyles[name].writeXml( ch );
62+
mgrElement.appendChild( ch );
63+
}
64+
}
65+
66+
QStringList QgsMapLayerStyleManager::styles() const
67+
{
68+
return mStyles.keys();
69+
}
70+
71+
QgsMapLayerStyle QgsMapLayerStyleManager::style( const QString& name ) const
72+
{
73+
if ( name == mCurrentStyle ) // make sure it is sync'ed
74+
const_cast<QgsMapLayerStyleManager*>( this )->syncCurrentStyle();
75+
76+
return mStyles.value( name );
77+
}
78+
79+
bool QgsMapLayerStyleManager::addStyle( const QString& name, const QgsMapLayerStyle& style )
80+
{
81+
if ( mStyles.contains( name ) )
82+
return false;
83+
if ( !style.isValid() )
84+
return false;
85+
86+
mStyles.insert( name, style );
87+
return true;
88+
}
89+
90+
bool QgsMapLayerStyleManager::addStyleFromLayer( const QString& name )
91+
{
92+
QgsMapLayerStyle style;
93+
style.readFromLayer( mLayer );
94+
return addStyle( name, style );
95+
}
96+
97+
bool QgsMapLayerStyleManager::removeStyle( const QString& name )
98+
{
99+
if ( !mStyles.contains( name ) )
100+
return false;
101+
if ( mStyles.count() == 1 )
102+
return false; // cannot remove the last one
103+
104+
// change to a different style if this one is the current
105+
if ( mCurrentStyle == name )
106+
{
107+
QStringList keys = mStyles.keys();
108+
QString newCurrent = keys[0];
109+
if ( newCurrent == name )
110+
newCurrent = keys[1]; // there must be at least one more
111+
setCurrentStyle( newCurrent );
112+
}
113+
114+
mStyles.remove( name );
115+
return true;
116+
}
117+
118+
QString QgsMapLayerStyleManager::currentStyle() const
119+
{
120+
return mCurrentStyle;
121+
}
122+
123+
bool QgsMapLayerStyleManager::setCurrentStyle( const QString& name )
124+
{
125+
if ( !mStyles.contains( name ) )
126+
return false;
127+
128+
if ( mCurrentStyle == name )
129+
return true; // nothing to do
130+
131+
syncCurrentStyle(); // sync before unloading it
132+
mCurrentStyle = name;
133+
mStyles[mCurrentStyle].writeToLayer( mLayer );
134+
return true;
135+
}
136+
137+
void QgsMapLayerStyleManager::syncCurrentStyle()
138+
{
139+
mStyles[mCurrentStyle].readFromLayer( mLayer );
140+
}
141+
142+
// -----
143+
144+
QgsMapLayerStyle::QgsMapLayerStyle()
145+
{
146+
}
147+
148+
bool QgsMapLayerStyle::isValid() const
149+
{
150+
return !mXmlData.isEmpty();
151+
}
152+
153+
QString QgsMapLayerStyle::dump() const
154+
{
155+
return mXmlData;
156+
}
157+
158+
void QgsMapLayerStyle::readFromLayer( QgsMapLayer* layer )
159+
{
160+
QString errorMsg;
161+
QDomDocument doc;
162+
layer->exportNamedStyle( doc, errorMsg );
163+
if ( !errorMsg.isEmpty() )
164+
{
165+
QgsDebugMsg( "Failed to export style from layer: " + errorMsg );
166+
return;
167+
}
168+
169+
QTextStream stream( &mXmlData );
170+
doc.save( stream, 0 );
171+
}
172+
173+
void QgsMapLayerStyle::writeToLayer( QgsMapLayer* layer ) const
174+
{
175+
// QgsMapLayer does not have a importNamedStyle() method - working it around like this
176+
QTemporaryFile f;
177+
f.open();
178+
f.write( mXmlData );
179+
f.flush();
180+
181+
bool res;
182+
QString status = layer->loadNamedStyle( f.fileName(), res );
183+
if ( !res )
184+
QgsDebugMsg( "Failed to import style to layer: " + status );
185+
}
186+
187+
void QgsMapLayerStyle::readXml( const QDomElement& styleElement )
188+
{
189+
QTextStream stream( &mXmlData );
190+
styleElement.firstChildElement().save( stream, 0 );
191+
}
192+
193+
void QgsMapLayerStyle::writeXml( QDomElement& styleElement ) const
194+
{
195+
QDomDocument docX;
196+
docX.setContent( mXmlData );
197+
styleElement.appendChild( docX.documentElement() );
198+
}

‎src/core/qgsmaplayerstylemanager.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/***************************************************************************
2+
qgsmaplayerstylemanager.h
3+
--------------------------------------
4+
Date : January 2015
5+
Copyright : (C) 2015 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 QGSMAPLAYERSTYLEMANAGER_H
17+
#define QGSMAPLAYERSTYLEMANAGER_H
18+
19+
20+
class QgsMapLayer;
21+
22+
#include <QByteArray>
23+
#include <QMap>
24+
#include <QStringList>
25+
26+
class QDomElement;
27+
28+
/**
29+
* Stores style information (renderer, transparency, labeling, diagrams etc.) applicable to a map layer.
30+
*
31+
* Stored data are considered as opaque - it is not possible to access them directly or modify them - it is
32+
* only possible to read or write layer's current style.
33+
*
34+
* @note added in 2.8
35+
*/
36+
class CORE_EXPORT QgsMapLayerStyle
37+
{
38+
public:
39+
//! construct invalid style
40+
QgsMapLayerStyle();
41+
42+
//! Tell whether the style is valid (i.e. there is something stored in it)
43+
bool isValid() const;
44+
45+
//! Return information about the style - for debugging purposes only
46+
QString dump() const;
47+
48+
//! Store layer's active style information in the instance
49+
void readFromLayer( QgsMapLayer* layer );
50+
//! Apply stored layer's style information to the layer
51+
void writeToLayer( QgsMapLayer* layer ) const;
52+
53+
//! Read style configuration (for project file reading)
54+
void readXml( const QDomElement& styleElement );
55+
//! Write style configuration (for project file writing)
56+
void writeXml( QDomElement& styleElement ) const;
57+
58+
private:
59+
QByteArray mXmlData;
60+
};
61+
62+
63+
/**
64+
* Management of styles for use with one map layer. Stored styles are identified by their names. The manager
65+
* always keep track of which style of the stored ones is currently active. When the current style is changed,
66+
* the new style is applied to the associated layer.
67+
*
68+
* The class takes care of updating itself when the layer's current style configuration changes.
69+
* When some of layer style's properties change (e.g. transparency / colors), the style manager will
70+
* record them in the currently active style without any extra effort required.
71+
*
72+
* When an instance is created, it creates "default" style (with empty name) recorded from the associated map layer
73+
* and it is set as the current style.
74+
*
75+
* The instance must always contain at least one style. If no extra styles are wanted, the style manager should get
76+
* disabled in QgsMapLayer instance.
77+
*
78+
* @note added in 2.8
79+
*/
80+
class CORE_EXPORT QgsMapLayerStyleManager
81+
{
82+
public:
83+
//! Construct a style manager associated with a map layer (must not be null)
84+
QgsMapLayerStyleManager( QgsMapLayer* layer );
85+
86+
//! Read configuration (for project loading)
87+
void readXml( const QDomElement& mgrElement );
88+
//! Write configuration (for project saving)
89+
void writeXml( QDomElement& mgrElement ) const;
90+
91+
//! Return list of all defined style names
92+
QStringList styles() const;
93+
//! Return data of a stored style - accessed by its unique name
94+
QgsMapLayerStyle style( const QString& name ) const;
95+
96+
//! Add a style with given name and data
97+
//! @return true on success (name is unique and style is valid)
98+
bool addStyle( const QString& name, const QgsMapLayerStyle& style );
99+
//! Add style by cloning the current one
100+
//! @return true on success
101+
bool addStyleFromLayer( const QString& name );
102+
//! Remove a stored style
103+
//! @return true on success (style exists and it is not the last one)
104+
bool removeStyle( const QString& name );
105+
106+
//! Return name of the current style
107+
QString currentStyle() const;
108+
//! Set a different style as the current style - will apply it to the layer
109+
//! @return true on success
110+
bool setCurrentStyle( const QString& name );
111+
112+
private:
113+
void syncCurrentStyle();
114+
void ensureCurrentInSync() const;
115+
116+
private:
117+
QgsMapLayer* mLayer;
118+
QMap<QString, QgsMapLayerStyle> mStyles;
119+
QString mCurrentStyle;
120+
};
121+
122+
#endif // QGSMAPLAYERSTYLEMANAGER_H

‎src/core/qgsvectorlayer.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,8 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
13281328
return false;
13291329
}
13301330

1331+
readStyleManager( layer_node );
1332+
13311333
setLegend( QgsMapLayerLegend::defaultVectorLegend( this ) );
13321334

13331335
return mValid; // should be true if read successfully
@@ -1478,6 +1480,8 @@ bool QgsVectorLayer::writeXml( QDomNode & layer_node,
14781480
// save expression fields
14791481
mExpressionFieldBuffer->writeXml( layer_node, document );
14801482

1483+
writeStyleManager( layer_node, document );
1484+
14811485
// renderer specific settings
14821486
QString errorMsg;
14831487
return writeSymbology( layer_node, document, errorMsg );

‎src/core/raster/qgsrasterlayer.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,6 +1435,8 @@ bool QgsRasterLayer::readXml( const QDomNode& layer_node )
14351435
}
14361436
}
14371437

1438+
readStyleManager( layer_node );
1439+
14381440
return res;
14391441
} // QgsRasterLayer::readXml( QDomNode & layer_node )
14401442

@@ -1523,6 +1525,8 @@ bool QgsRasterLayer::writeXml( QDomNode & layer_node,
15231525
layer_node.appendChild( noData );
15241526
}
15251527

1528+
writeStyleManager( layer_node, document );
1529+
15261530
//write out the symbology
15271531
QString errorMsg;
15281532
return writeSymbology( layer_node, document, errorMsg );

‎tests/src/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,4 @@ ADD_QGIS_TEST(maptopixeltest testqgsmaptopixel.cpp)
142142
ADD_QGIS_TEST(networkcontentfetcher testqgsnetworkcontentfetcher.cpp )
143143
ADD_QGIS_TEST(legendrenderertest testqgslegendrenderer.cpp )
144144
ADD_QGIS_TEST(vectorlayerjoinbuffer testqgsvectorlayerjoinbuffer.cpp )
145+
ADD_QGIS_TEST(maplayerstylemanager testqgsmaplayerstylemanager.cpp )
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
2+
#include <QtTest/QtTest>
3+
#include <QObject>
4+
5+
#include "qgsapplication.h"
6+
#include "qgsmaplayerregistry.h"
7+
#include "qgsmaplayerstylemanager.h"
8+
#include "qgssinglesymbolrendererv2.h"
9+
#include "qgsvectorlayer.h"
10+
11+
12+
class TestQgsMapLayerStyleManager : public QObject
13+
{
14+
Q_OBJECT
15+
private slots:
16+
void initTestCase();// will be called before the first testfunction is executed.
17+
void cleanupTestCase();// will be called after the last testfunction was executed.
18+
void init();// will be called before each testfunction is executed.
19+
void cleanup();// will be called after every testfunction.
20+
21+
void testEnabled();
22+
void testDefault();
23+
void testStyle();
24+
void testReadWrite();
25+
26+
private:
27+
QgsVectorLayer* mVL;
28+
};
29+
30+
void TestQgsMapLayerStyleManager::initTestCase()
31+
{
32+
QgsApplication::init();
33+
QgsApplication::initQgis();
34+
}
35+
36+
void TestQgsMapLayerStyleManager::cleanupTestCase()
37+
{
38+
QgsApplication::exitQgis();
39+
}
40+
41+
void TestQgsMapLayerStyleManager::init()
42+
{
43+
mVL = new QgsVectorLayer( "LineString", "Line Layer", "memory" );
44+
QgsMapLayerRegistry::instance()->addMapLayer( mVL );
45+
}
46+
47+
void TestQgsMapLayerStyleManager::cleanup()
48+
{
49+
QgsMapLayerRegistry::instance()->removeAllMapLayers();
50+
}
51+
52+
void TestQgsMapLayerStyleManager::testEnabled()
53+
{
54+
QVERIFY( !mVL->styleManager() );
55+
56+
mVL->enableStyleManager( false );
57+
QVERIFY( !mVL->styleManager() );
58+
59+
mVL->enableStyleManager();
60+
QVERIFY( mVL->styleManager() );
61+
62+
mVL->enableStyleManager( false );
63+
QVERIFY( !mVL->styleManager() );
64+
}
65+
66+
void TestQgsMapLayerStyleManager::testDefault()
67+
{
68+
mVL->enableStyleManager();
69+
QgsMapLayerStyleManager* mgr = mVL->styleManager();
70+
QVERIFY( mgr );
71+
72+
QCOMPARE( mgr->styles().count(), 1 );
73+
QCOMPARE( mgr->style( QString() ).isValid(), true );
74+
}
75+
76+
void TestQgsMapLayerStyleManager::testStyle()
77+
{
78+
QgsLineSymbolV2* sym1 = new QgsLineSymbolV2();
79+
sym1->setColor( Qt::magenta );
80+
QgsLineSymbolV2* sym2 = new QgsLineSymbolV2();
81+
sym2->setColor( Qt::red );
82+
83+
QgsMapLayerStyle st;
84+
QCOMPARE( st.isValid(), false );
85+
86+
mVL->setRendererV2( new QgsSingleSymbolRendererV2( sym1 ) );
87+
88+
QgsMapLayerStyle st1;
89+
st1.readFromLayer( mVL );
90+
QCOMPARE( st1.isValid(), true );
91+
92+
qDebug( "CNT-1: %s", st1.dump().toAscii().data() );
93+
94+
mVL->setRendererV2( new QgsSingleSymbolRendererV2( sym2 ) );
95+
96+
QgsMapLayerStyle st2;
97+
st2.readFromLayer( mVL );
98+
99+
qDebug( "CNT-2: %s", st2.dump().toAscii().data() );
100+
101+
st1.writeToLayer( mVL );
102+
103+
QgsSingleSymbolRendererV2* r1 = dynamic_cast<QgsSingleSymbolRendererV2*>( mVL->rendererV2() );
104+
QVERIFY( r1 );
105+
QCOMPARE( r1->symbol()->color(), QColor( Qt::magenta ) );
106+
107+
st2.writeToLayer( mVL );
108+
109+
QgsSingleSymbolRendererV2* r2 = dynamic_cast<QgsSingleSymbolRendererV2*>( mVL->rendererV2() );
110+
QVERIFY( r2 );
111+
QCOMPARE( r2->symbol()->color(), QColor( Qt::red ) );
112+
}
113+
114+
115+
void TestQgsMapLayerStyleManager::testReadWrite()
116+
{
117+
QgsSingleSymbolRendererV2* r0 = dynamic_cast<QgsSingleSymbolRendererV2*>( mVL->rendererV2() );
118+
r0->symbol()->setColor( Qt::red );
119+
120+
// create and populate the manager with one more style
121+
122+
QgsMapLayerStyleManager sm0( mVL );
123+
124+
sm0.addStyleFromLayer( "blue" );
125+
sm0.setCurrentStyle( "blue" );
126+
QgsSingleSymbolRendererV2* r1 = dynamic_cast<QgsSingleSymbolRendererV2*>( mVL->rendererV2() );
127+
r1->symbol()->setColor( Qt::blue );
128+
129+
// read and write
130+
131+
QDomDocument doc;
132+
QDomElement mgrElem = doc.createElement( "map-layer-style-manager" );
133+
doc.appendChild( mgrElem );
134+
sm0.writeXml( mgrElem );
135+
136+
QString xml;
137+
QTextStream ts(&xml);
138+
doc.save(ts, 2);
139+
qDebug("%s", xml.toAscii().data());
140+
141+
QgsMapLayerStyleManager sm1( mVL );
142+
sm1.readXml( mgrElem );
143+
144+
QCOMPARE( sm1.styles().count(), 2 );
145+
QCOMPARE( sm1.style(QString()).isValid(), true );
146+
QCOMPARE( sm1.style("blue").isValid(), true );
147+
QCOMPARE( sm1.currentStyle(), QString("blue") );
148+
149+
// now use the default style - the symbol should get red color
150+
sm1.setCurrentStyle( QString() );
151+
152+
QgsSingleSymbolRendererV2* r2 = dynamic_cast<QgsSingleSymbolRendererV2*>( mVL->rendererV2() );
153+
QCOMPARE( r2->symbol()->color(), QColor( Qt::red ) );
154+
}
155+
156+
QTEST_MAIN( TestQgsMapLayerStyleManager )
157+
#include "testqgsmaplayerstylemanager.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.