[FEATURE] Visibility groups of map layers in layer tree

New toolbar button allows quick changes between the groups of layers that should be visible.
Also features a simple way for management of the groups (add/remove)
This commit is contained in:
Martin Dobias 2014-09-04 15:00:40 +07:00
parent dd78876636
commit e3c2711309
4 changed files with 406 additions and 0 deletions

View File

@ -108,6 +108,7 @@ SET(QGIS_APP_SRCS
qgstipgui.cpp
qgstipfactory.cpp
qgsvectorlayerproperties.cpp
qgsvisibilitygroups.cpp
qgshandlebadlayers.cpp
composer/qgsattributeselectiondialog.cpp
@ -246,6 +247,7 @@ SET (QGIS_APP_MOC_HDRS
qgstipfactory.h
qgsundowidget.h
qgsvectorlayerproperties.h
qgsvisibilitygroups.h
qgshandlebadlayers.h
composer/qgsattributeselectiondialog.h

View File

@ -195,6 +195,7 @@
#include "qgsvectorfilewriter.h"
#include "qgsvectorlayer.h"
#include "qgsvectorlayerproperties.h"
#include "qgsvisibilitygroups.h"
#include "qgsmessagelogviewer.h"
#include "qgsdataitem.h"
#include "qgsmaplayeractionregistry.h"
@ -1654,6 +1655,14 @@ void QgisApp::createToolBars()
newLayerAction->setObjectName( "ActionNewLayer" );
connect( bt, SIGNAL( triggered( QAction * ) ), this, SLOT( toolButtonActionTriggered( QAction * ) ) );
// visibility groups tool button
bt = new QToolButton();
bt->setIcon( QgsApplication::getThemeIcon( "/mActionShowAllLayers.png" ) );
bt->setPopupMode( QToolButton::InstantPopup );
bt->setMenu( QgsVisibilityGroups::instance()->menu() );
mMapNavToolBar->addWidget( bt );
// Help Toolbar
QAction* actionWhatsThis = QWhatsThis::createAction( this );
@ -3447,6 +3456,8 @@ void QgisApp::fileNew( bool thePromptToSaveFlag, bool forceBlank )
fileNewFromDefaultTemplate();
}
QgsVisibilityGroups::instance()->clear();
// set the initial map tool
#ifndef HAVE_TOUCH
mMapCanvas->setMapTool( mMapTools.mPan );

View File

@ -0,0 +1,295 @@
/***************************************************************************
qgsvisibilitygroups.cpp
--------------------------------------
Date : September 2014
Copyright : (C) 2014 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 "qgsvisibilitygroups.h"
#include "qgslayertree.h"
#include "qgsproject.h"
#include "qgisapp.h"
#include <QInputDialog>
QgsVisibilityGroups* QgsVisibilityGroups::sInstance;
QgsVisibilityGroups::QgsVisibilityGroups()
: mMenu( new QMenu )
, mMenuDirty( false )
{
mMenu->addAction( QgisApp::instance()->actionShowAllLayers() );
mMenu->addAction( QgisApp::instance()->actionHideAllLayers() );
mMenu->addSeparator();
mMenu->addAction( tr( "Add group..." ), this, SLOT( addGroup() ) );
mMenuSeparator = mMenu->addSeparator();
mActionRemoveCurrentGroup = mMenu->addAction( tr( "Remove current group" ), this, SLOT( removeCurrentGroup() ) );
connect( mMenu, SIGNAL( aboutToShow() ), this, SLOT( menuAboutToShow() ) );
QgsLayerTreeGroup* root = QgsProject::instance()->layerTreeRoot();
connect( root, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ),
this, SLOT( layerTreeVisibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ) );
connect( root, SIGNAL( addedChildren( QgsLayerTreeNode*, int, int ) ),
this, SLOT( layerTreeAddedChildren( QgsLayerTreeNode*, int, int ) ) );
connect( root, SIGNAL( willRemoveChildren( QgsLayerTreeNode*, int, int ) ),
this, SLOT( layerTreeWillRemoveChildren( QgsLayerTreeNode*, int, int ) ) );
connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ),
this, SLOT( readProject( const QDomDocument & ) ) );
connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ),
this, SLOT( writeProject( QDomDocument & ) ) );
}
void QgsVisibilityGroups::addVisibleLayersToGroup( QgsLayerTreeGroup* parent, QgsVisibilityGroups::GroupRecord& rec )
{
foreach ( QgsLayerTreeNode* node, parent->children() )
{
if ( QgsLayerTree::isGroup( node ) )
addVisibleLayersToGroup( QgsLayerTree::toGroup( node ), rec );
else if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
if ( nodeLayer->isVisible() )
rec.mVisibleLayerIDs << nodeLayer->layerId();
}
}
}
QgsVisibilityGroups::GroupRecord QgsVisibilityGroups::currentState()
{
GroupRecord rec;
QgsLayerTreeGroup* root = QgsProject::instance()->layerTreeRoot();
addVisibleLayersToGroup( root, rec );
return rec;
}
QgsVisibilityGroups* QgsVisibilityGroups::instance()
{
if ( !sInstance )
sInstance = new QgsVisibilityGroups();
return sInstance;
}
void QgsVisibilityGroups::addGroup( const QString& name )
{
mGroups.insert( name, currentState() );
mMenuDirty = true;
}
void QgsVisibilityGroups::updateGroup( const QString& name )
{
if ( !mGroups.contains( name ) )
return;
mGroups[name] = currentState();
mMenuDirty = true;
}
void QgsVisibilityGroups::removeGroup( const QString& name )
{
mGroups.remove( name );
mMenuDirty = true;
}
void QgsVisibilityGroups::clear()
{
mGroups.clear();
mMenuDirty = true;
}
QStringList QgsVisibilityGroups::groups() const
{
return mGroups.keys();
}
QMenu* QgsVisibilityGroups::menu()
{
return mMenu;
}
void QgsVisibilityGroups::addGroup()
{
bool ok;
QString name = QInputDialog::getText( 0, tr( "Visibility groups" ), tr( "Name of the new group" ), QLineEdit::Normal, QString(), &ok );
if ( !ok && name.isEmpty() )
return;
addGroup( name );
}
void QgsVisibilityGroups::groupTriggerred()
{
QAction* actionGroup = qobject_cast<QAction*>( sender() );
if ( !actionGroup )
return;
applyState( actionGroup->text() );
}
void QgsVisibilityGroups::applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const QSet<QString>& visibleLayerIDs )
{
foreach ( QgsLayerTreeNode* node, parent->children() )
{
if ( QgsLayerTree::isGroup( node ) )
applyStateToLayerTreeGroup( QgsLayerTree::toGroup( node ), visibleLayerIDs );
else if ( QgsLayerTree::isLayer( node ) )
{
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
nodeLayer->setVisible( visibleLayerIDs.contains( nodeLayer->layerId() ) ? Qt::Checked : Qt::Unchecked );
}
}
}
void QgsVisibilityGroups::applyState( const QString& groupName )
{
if ( !mGroups.contains( groupName ) )
return;
const GroupRecord& rec = mGroups[groupName];
applyStateToLayerTreeGroup( QgsProject::instance()->layerTreeRoot(), QSet<QString>::fromList( rec.mVisibleLayerIDs ) );
mMenuDirty = true;
}
void QgsVisibilityGroups::removeCurrentGroup()
{
foreach ( QAction* a, mMenuGroupActions )
{
if ( a->isChecked() )
{
removeGroup( a->text() );
break;
}
}
}
void QgsVisibilityGroups::menuAboutToShow()
{
if ( !mMenuDirty )
return;
// lazy update of the menu only when necessary - so that we do not do too much work when it is not necessary
qDeleteAll( mMenuGroupActions );
mMenuGroupActions.clear();
GroupRecord rec = currentState();
bool hasCurrent = false;
foreach ( const QString& grpName, mGroups.keys() )
{
QAction* a = new QAction( grpName, mMenu );
a->setCheckable( true );
if ( rec == mGroups[grpName] )
{
a->setChecked( true );
hasCurrent = true;
}
connect( a, SIGNAL( triggered() ), this, SLOT( groupTriggerred() ) );
mMenuGroupActions.append( a );
}
mMenu->insertActions( mMenuSeparator, mMenuGroupActions );
mActionRemoveCurrentGroup->setEnabled( hasCurrent );
mMenuDirty = false;
}
void QgsVisibilityGroups::layerTreeVisibilityChanged( QgsLayerTreeNode* node, Qt::CheckState state )
{
Q_UNUSED( node );
Q_UNUSED( state );
mMenuDirty = true;
}
void QgsVisibilityGroups::layerTreeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{
Q_UNUSED( node );
Q_UNUSED( indexFrom );
Q_UNUSED( indexTo );
mMenuDirty = true;
}
void QgsVisibilityGroups::layerTreeWillRemoveChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
{
Q_UNUSED( node );
Q_UNUSED( indexFrom );
Q_UNUSED( indexTo );
mMenuDirty = true;
}
void QgsVisibilityGroups::readProject( const QDomDocument& doc )
{
clear();
QDomElement visGroupsElem = doc.firstChildElement( "qgis" ).firstChildElement( "visibility-groups" );
if ( visGroupsElem.isNull() )
return;
QDomElement visGroupElem = visGroupsElem.firstChildElement( "visibility-group" );
while ( !visGroupElem.isNull() )
{
QString groupName = visGroupElem.attribute( "name" );
GroupRecord rec;
QDomElement visGroupLayerElem = visGroupElem.firstChildElement( "layer" );
while ( !visGroupLayerElem.isNull() )
{
rec.mVisibleLayerIDs << visGroupLayerElem.attribute( "id" );
visGroupLayerElem = visGroupLayerElem.nextSiblingElement( "layer" );
}
mGroups.insert( groupName, rec );
visGroupElem = visGroupElem.nextSiblingElement( "visibility-group" );
}
}
void QgsVisibilityGroups::writeProject( QDomDocument& doc )
{
QDomElement visGroupsElem = doc.createElement( "visibility-groups" );
foreach ( const QString& grpName, mGroups.keys() )
{
QDomElement visGroupElem = doc.createElement( "visibility-group" );
visGroupElem.setAttribute( "name", grpName );
foreach ( QString layerID, mGroups[grpName].mVisibleLayerIDs )
{
QDomElement layerElem = doc.createElement( "layer" );
layerElem.setAttribute( "id", layerID );
visGroupElem.appendChild( layerElem );
}
visGroupsElem.appendChild( visGroupElem );
}
doc.firstChildElement( "qgis" ).appendChild( visGroupsElem );
}

View File

@ -0,0 +1,98 @@
/***************************************************************************
qgsvisibilitygroups.h
--------------------------------------
Date : September 2014
Copyright : (C) 2014 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 QGSVISIBILITYGROUPS_H
#define QGSVISIBILITYGROUPS_H
#include <QMap>
#include <QObject>
#include <QStringList>
class QAction;
class QDomDocument;
class QMenu;
class QgsLayerTreeNode;
class QgsLayerTreeGroup;
class QgsVisibilityGroups : public QObject
{
Q_OBJECT
public:
static QgsVisibilityGroups* instance();
void addGroup( const QString& name );
void updateGroup( const QString& name );
void removeGroup( const QString& name );
void clear();
QStringList groups() const;
QMenu* menu();
signals:
void groupsChanged();
protected slots:
void addGroup();
void groupTriggerred();
void removeCurrentGroup();
void menuAboutToShow();
void layerTreeVisibilityChanged( QgsLayerTreeNode* node, Qt::CheckState state );
void layerTreeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo );
void layerTreeWillRemoveChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo );
void readProject( const QDomDocument& doc );
void writeProject( QDomDocument& doc );
protected:
QgsVisibilityGroups(); // singleton
typedef struct GroupRecord
{
bool operator==( const GroupRecord& other ) const
{
return mVisibleLayerIDs == other.mVisibleLayerIDs;
}
//! List of layers that are visible
QStringList mVisibleLayerIDs;
//! For layers that have checkable legend symbols and not all symbols are checked - list which ones are
//QMap<QString, QStringList> mPerLayerCheckedLegendSymbols;
} GroupRecord;
typedef QMap<QString, GroupRecord> GroupRecordMap;
void addVisibleLayersToGroup( QgsLayerTreeGroup* parent, GroupRecord& rec );
void applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const QSet<QString>& visibleLayerIDs );
GroupRecord currentState();
void applyState( const QString& groupName );
static QgsVisibilityGroups* sInstance;
GroupRecordMap mGroups;
QMenu* mMenu;
bool mMenuDirty;
QAction* mMenuSeparator;
QAction* mActionRemoveCurrentGroup;
QList<QAction*> mMenuGroupActions;
};
#endif // QGSVISIBILITYGROUPS_H