Skip to content

Commit

Permalink
Merge pull request #1755 from wonder-sk/multiple-styles
Browse files Browse the repository at this point in the history
[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.
  • Loading branch information
wonder-sk committed Jan 7, 2015
2 parents 0b954c1 + 2144be0 commit 3ce9c70
Show file tree
Hide file tree
Showing 18 changed files with 846 additions and 11 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -54,6 +54,7 @@
%Include qgsmaplayerlegend.sip
%Include qgsmaplayerregistry.sip
%Include qgsmaplayerrenderer.sip
%Include qgsmaplayerstylemanager.sip
%Include qgsmaprenderer.sip
%Include qgsmaprenderercache.sip
%Include qgsmaprenderercustompainterjob.sip
Expand Down
20 changes: 20 additions & 0 deletions python/core/qgsmaplayer.sip
Expand Up @@ -370,6 +370,21 @@ class QgsMapLayer : QObject
*/
QgsMapLayerLegend* legend() const;

/**
* Enable or disable layer's style manager. When disabled (default), the styleManager() will return null pointer.
* By enabling the style manager will be created with one default style (same as the layer's active style).
* By disabling the style manager all associated styles will be lost (only the layer's active style will stay).
* @note added in 2.8
*/
void enableStyleManager( bool enable = true );

/**
* Get access to the layer's style manager. Style manager allows switching between multiple styles.
* If the style manager is not enabled, null pointer will be returned.
* @note added in 2.8
*/
QgsMapLayerStyleManager* styleManager() const;

/**Returns the minimum scale denominator at which the layer is visible.
* Scale based visibility is only used if hasScaleBasedVisibility is true.
* @returns minimum scale denominator at which the layer will render
Expand Down Expand Up @@ -517,6 +532,11 @@ class QgsMapLayer : QObject
/** Write custom properties to project file. */
void writeCustomProperties( QDomNode & layerNode, QDomDocument & doc ) const;

/** Read style manager's configuration (if any). To be called by subclasses. */
void readStyleManager( const QDomNode& layerNode );
/** Write style manager's configuration (if exists). To be called by subclasses. */
void writeStyleManager( QDomNode& layerNode, QDomDocument& doc ) const;

/** debugging member - invoked when a connect() is made to this object */
void connectNotify( const char * signal );

Expand Down
63 changes: 63 additions & 0 deletions python/core/qgsmaplayerstylemanager.sip
@@ -0,0 +1,63 @@

class QgsMapLayerStyle
{
%TypeHeaderCode
#include <qgsmaplayerstylemanager.h>
%End
public:
//! construct invalid style
QgsMapLayerStyle();

//! Tell whether the style is valid (i.e. there is something stored in it)
bool isValid() const;

//! Return information about the style - for debugging purposes only
QString dump() const;

//! Store layer's active style information in the instance
void readFromLayer( QgsMapLayer* layer );
//! Apply stored layer's style information to the layer
void writeToLayer( QgsMapLayer* layer ) const;

//! Read style configuration (for project file reading)
void readXml( const QDomElement& styleElement );
//! Write style configuration (for project file writing)
void writeXml( QDomElement& styleElement ) const;
};


class QgsMapLayerStyleManager
{
%TypeHeaderCode
#include <qgsmaplayerstylemanager.h>
%End
public:
//! Construct a style manager associated with a map layer (must not be null)
QgsMapLayerStyleManager( QgsMapLayer* layer );

//! Read configuration (for project loading)
void readXml( const QDomElement& mgrElement );
//! Write configuration (for project saving)
void writeXml( QDomElement& mgrElement ) const;

//! Return list of all defined style names
QStringList styles() const;
//! Return data of a stored style - accessed by its unique name
QgsMapLayerStyle style( const QString& name ) const;

//! Add a style with given name and data
//! @return true on success (name is unique and style is valid)
bool addStyle( const QString& name, const QgsMapLayerStyle& style );
//! Add style by cloning the current one
//! @return true on success
bool addStyleFromLayer( const QString& name );
//! Remove a stored style
//! @return true on success (style exists and it is not the last one)
bool removeStyle( const QString& name );

//! Return name of the current style
QString currentStyle() const;
//! Set a different style as the current style - will apply it to the layer
//! @return true on success
bool setCurrentStyle( const QString& name );
};
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -45,6 +45,7 @@ SET(QGIS_APP_SRCS
qgslabelinggui.cpp
qgslabelpreview.cpp
qgsloadstylefromdbdialog.cpp
qgsmaplayerstyleguiutils.cpp
qgsmapmouseevent.cpp
qgssavestyletodbdialog.cpp
qgsguivectorlayertools.cpp
Expand Down Expand Up @@ -200,6 +201,7 @@ SET (QGIS_APP_MOC_HDRS
qgslabelinggui.h
qgslabelpropertydialog.h
qgsloadstylefromdbdialog.h
qgsmaplayerstyleguiutils.h
qgssavestyletodbdialog.h
qgsshortcutsmanager.h
qgsapplayertreeviewmenuprovider.h
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.cpp
Expand Up @@ -149,6 +149,7 @@
#include "qgsmapcanvas.h"
#include "qgsmaplayer.h"
#include "qgsmaplayerregistry.h"
#include "qgsmaplayerstyleguiutils.h"
#include "qgsmapoverviewcanvas.h"
#include "qgsmaprenderer.h"
#include "qgsmapsettings.h"
Expand Down Expand Up @@ -897,6 +898,8 @@ QgisApp::~QgisApp()
delete QgsProject::instance();

delete mPythonUtils;

QgsMapLayerStyleGuiUtils::cleanup();
}

void QgisApp::dragEnterEvent( QDragEnterEvent *event )
Expand Down
26 changes: 16 additions & 10 deletions src/app/qgsapplayertreeviewmenuprovider.cpp
Expand Up @@ -7,6 +7,7 @@
#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgsmaplayerstyleguiutils.h"
#include "qgsproject.h"
#include "qgsrasterlayer.h"
#include "qgsvectordataprovider.h"
Expand Down Expand Up @@ -87,6 +88,21 @@ QMenu* QgsAppLayerTreeViewMenuProvider::createContextMenu()
// assign layer crs to project
menu->addAction( QgsApplication::getThemeIcon( "/mActionSetProjectCRS.png" ), tr( "Set &Project CRS from Layer" ), QgisApp::instance(), SLOT( setProjectCRSFromLayer() ) );

// style-related actions
if ( mView->selectedLayerNodes().count() == 1 )
{
QMenu* menuStyleManager = QgsMapLayerStyleGuiUtils::instance()->createStyleManagerMenu( layer );

QgisApp* app = QgisApp::instance();
menuStyleManager->addSeparator();
menuStyleManager->addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) );
if ( app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
{
menuStyleManager->addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) );
}
menu->addMenu( menuStyleManager );
}

menu->addSeparator();

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

if ( mView->selectedNodes( true ).count() >= 2 )
menu->addAction( actions->actionGroupSelected( menu ) );

if ( mView->selectedLayerNodes().count() == 1 )
{
QgisApp* app = QgisApp::instance();
menu->addAction( tr( "Copy Style" ), app, SLOT( copyStyle() ) );
if ( app->clipboard()->hasFormat( QGSCLIPBOARD_STYLE_MIME ) )
{
menu->addAction( tr( "Paste Style" ), app, SLOT( pasteStyle() ) );
}
}
}

}
Expand Down
1 change: 0 additions & 1 deletion src/app/qgsapplayertreeviewmenuprovider.h
Expand Up @@ -36,7 +36,6 @@ class QgsAppLayerTreeViewMenuProvider : public QObject, public QgsLayerTreeViewM
void removeLegendLayerActionsForLayer( QgsMapLayer* layer );
QList< LegendLayerAction > legendLayerActions( QgsMapLayer::LayerType type ) const;


protected:

void addCustomLayerActions( QMenu* menu, QgsMapLayer* layer );
Expand Down
139 changes: 139 additions & 0 deletions src/app/qgsmaplayerstyleguiutils.cpp
@@ -0,0 +1,139 @@
/***************************************************************************
qgsmaplayerstyleguiutils.cpp
--------------------------------------
Date : January 2015
Copyright : (C) 2015 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsmaplayerstyleguiutils.h"

#include <QAction>
#include <QInputDialog>
#include <QMenu>

#include "qgslogger.h"
#include "qgsmapcanvas.h"
#include "qgsmaplayer.h"
#include "qgsmaplayerstylemanager.h"


QMenu* QgsMapLayerStyleGuiUtils::createStyleManagerMenu( QgsMapLayer* layer )
{
QMenu* m = new QMenu( tr( "Styles" ) );
QAction* actionAdd = m->addAction( tr( "Add..." ), this, SLOT( addStyle() ) );
actionAdd->setData( QVariant::fromValue<QObject*>( layer ) );

QgsMapLayerStyleManager* mgr = layer->styleManager();

if ( !mgr )
return m;

QMenu* mRemove = m->addMenu( tr( "Remove" ) );
m->addSeparator();

foreach ( QString name, mgr->styles() )
{
bool active = name == mgr->currentStyle();
if ( name.isEmpty() )
name = defaultStyleName();
QAction* actionUse = m->addAction( name, this, SLOT( useStyle() ) );
actionUse->setCheckable( true );
actionUse->setChecked( active );
actionUse->setData( QVariant::fromValue<QObject*>( layer ) );

QAction* actionRemove = mRemove->addAction( name, this, SLOT( removeStyle() ) );
actionRemove->setData( QVariant::fromValue<QObject*>( layer ) );
}

return m;
}

QString QgsMapLayerStyleGuiUtils::defaultStyleName()
{
return tr( "(default)" );
}


void QgsMapLayerStyleGuiUtils::addStyle()
{
QAction* a = qobject_cast<QAction*>( sender() );
if ( !a )
return;
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
if ( !layer )
return;

bool ok;
QString text = QInputDialog::getText( 0, tr( "New style" ),
tr( "Style name:" ), QLineEdit::Normal,
"new style", &ok );
if ( !ok || text.isEmpty() )
return;

layer->enableStyleManager(); // make sure it exists

bool res = layer->styleManager()->addStyleFromLayer( text );

if ( res ) // make it active!
layer->styleManager()->setCurrentStyle( text );
else
QgsDebugMsg( "Failed to add style: " + text );
}

void QgsMapLayerStyleGuiUtils::useStyle()
{
QAction* a = qobject_cast<QAction*>( sender() );
if ( !a )
return;
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
if ( !layer )
return;
QString name = a->text();
if ( name == defaultStyleName() )
name.clear();

bool res = layer->styleManager()->setCurrentStyle( name );
if ( !res )
QgsDebugMsg( "Failed to set current style: " + name );

layer->triggerRepaint();
}


void QgsMapLayerStyleGuiUtils::removeStyle()
{
QAction* a = qobject_cast<QAction*>( sender() );
if ( !a )
return;
QgsMapLayer* layer = qobject_cast<QgsMapLayer*>( a->data().value<QObject*>() );
if ( !layer )
return;

if ( layer->styleManager()->styles().count() == 1 )
{
// let's get rid of the style manager altogether
layer->enableStyleManager( false );
return;
}

QString name = a->text();
if ( name == defaultStyleName() )
name.clear();

bool needsRefresh = ( layer->styleManager()->currentStyle() == name );

bool res = layer->styleManager()->removeStyle( name );
if ( !res )
QgsDebugMsg( "Failed to remove style: " + name );

if ( needsRefresh )
layer->triggerRepaint();
}
45 changes: 45 additions & 0 deletions src/app/qgsmaplayerstyleguiutils.h
@@ -0,0 +1,45 @@
/***************************************************************************
qgsmaplayerstyleguiutils.h
--------------------------------------
Date : January 2015
Copyright : (C) 2015 by Martin Dobias
Email : wonder dot sk at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSMAPLAYERSTYLEGUIUTILS_H
#define QGSMAPLAYERSTYLEGUIUTILS_H

#include <QObject>

#include "qgssingleton.h"

class QgsMapLayer;
class QMenu;

/** Various GUI utility functions for dealing with map layer's style manager */
class QgsMapLayerStyleGuiUtils : public QObject, public QgsSingleton<QgsMapLayerStyleGuiUtils>
{
Q_OBJECT
public:

//! Return menu instance with actions for the give map layer
QMenu* createStyleManagerMenu( QgsMapLayer* layer );

private:
QString defaultStyleName();

private slots:
void addStyle();
void useStyle();
void removeStyle();

};

#endif // QGSMAPLAYERSTYLEGUIUTILS_H
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -106,6 +106,7 @@ SET(QGIS_CORE_SRCS
qgsmaplayer.cpp
qgsmaplayerlegend.cpp
qgsmaplayerregistry.cpp
qgsmaplayerstylemanager.cpp
qgsmaprenderer.cpp
qgsmaprenderercache.cpp
qgsmaprenderercustompainterjob.cpp
Expand Down Expand Up @@ -495,6 +496,7 @@ SET(QGIS_CORE_HDRS
qgsmaplayer.h
qgsmaplayerlegend.h
qgsmaplayerregistry.h
qgsmaplayerstylemanager.h
qgsmaprenderer.h
qgsmaprenderercache.h
qgsmaprenderercustompainterjob.h
Expand Down

0 comments on commit 3ce9c70

Please sign in to comment.