Skip to content

Commit e3c2711

Browse files
committed
[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)
1 parent dd78876 commit e3c2711

File tree

4 files changed

+406
-0
lines changed

4 files changed

+406
-0
lines changed

src/app/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ SET(QGIS_APP_SRCS
108108
qgstipgui.cpp
109109
qgstipfactory.cpp
110110
qgsvectorlayerproperties.cpp
111+
qgsvisibilitygroups.cpp
111112
qgshandlebadlayers.cpp
112113

113114
composer/qgsattributeselectiondialog.cpp
@@ -246,6 +247,7 @@ SET (QGIS_APP_MOC_HDRS
246247
qgstipfactory.h
247248
qgsundowidget.h
248249
qgsvectorlayerproperties.h
250+
qgsvisibilitygroups.h
249251
qgshandlebadlayers.h
250252

251253
composer/qgsattributeselectiondialog.h

src/app/qgisapp.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@
195195
#include "qgsvectorfilewriter.h"
196196
#include "qgsvectorlayer.h"
197197
#include "qgsvectorlayerproperties.h"
198+
#include "qgsvisibilitygroups.h"
198199
#include "qgsmessagelogviewer.h"
199200
#include "qgsdataitem.h"
200201
#include "qgsmaplayeractionregistry.h"
@@ -1654,6 +1655,14 @@ void QgisApp::createToolBars()
16541655
newLayerAction->setObjectName( "ActionNewLayer" );
16551656
connect( bt, SIGNAL( triggered( QAction * ) ), this, SLOT( toolButtonActionTriggered( QAction * ) ) );
16561657

1658+
// visibility groups tool button
1659+
1660+
bt = new QToolButton();
1661+
bt->setIcon( QgsApplication::getThemeIcon( "/mActionShowAllLayers.png" ) );
1662+
bt->setPopupMode( QToolButton::InstantPopup );
1663+
bt->setMenu( QgsVisibilityGroups::instance()->menu() );
1664+
mMapNavToolBar->addWidget( bt );
1665+
16571666
// Help Toolbar
16581667

16591668
QAction* actionWhatsThis = QWhatsThis::createAction( this );
@@ -3447,6 +3456,8 @@ void QgisApp::fileNew( bool thePromptToSaveFlag, bool forceBlank )
34473456
fileNewFromDefaultTemplate();
34483457
}
34493458

3459+
QgsVisibilityGroups::instance()->clear();
3460+
34503461
// set the initial map tool
34513462
#ifndef HAVE_TOUCH
34523463
mMapCanvas->setMapTool( mMapTools.mPan );

src/app/qgsvisibilitygroups.cpp

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
/***************************************************************************
2+
qgsvisibilitygroups.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 "qgsvisibilitygroups.h"
17+
18+
#include "qgslayertree.h"
19+
#include "qgsproject.h"
20+
#include "qgisapp.h"
21+
22+
#include <QInputDialog>
23+
24+
25+
QgsVisibilityGroups* QgsVisibilityGroups::sInstance;
26+
27+
28+
QgsVisibilityGroups::QgsVisibilityGroups()
29+
: mMenu( new QMenu )
30+
, mMenuDirty( false )
31+
{
32+
33+
mMenu->addAction( QgisApp::instance()->actionShowAllLayers() );
34+
mMenu->addAction( QgisApp::instance()->actionHideAllLayers() );
35+
mMenu->addSeparator();
36+
37+
mMenu->addAction( tr( "Add group..." ), this, SLOT( addGroup() ) );
38+
mMenuSeparator = mMenu->addSeparator();
39+
40+
mActionRemoveCurrentGroup = mMenu->addAction( tr( "Remove current group" ), this, SLOT( removeCurrentGroup() ) );
41+
42+
connect( mMenu, SIGNAL( aboutToShow() ), this, SLOT( menuAboutToShow() ) );
43+
44+
QgsLayerTreeGroup* root = QgsProject::instance()->layerTreeRoot();
45+
connect( root, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ),
46+
this, SLOT( layerTreeVisibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ) );
47+
connect( root, SIGNAL( addedChildren( QgsLayerTreeNode*, int, int ) ),
48+
this, SLOT( layerTreeAddedChildren( QgsLayerTreeNode*, int, int ) ) );
49+
connect( root, SIGNAL( willRemoveChildren( QgsLayerTreeNode*, int, int ) ),
50+
this, SLOT( layerTreeWillRemoveChildren( QgsLayerTreeNode*, int, int ) ) );
51+
52+
connect( QgsProject::instance(), SIGNAL( readProject( const QDomDocument & ) ),
53+
this, SLOT( readProject( const QDomDocument & ) ) );
54+
connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ),
55+
this, SLOT( writeProject( QDomDocument & ) ) );
56+
}
57+
58+
void QgsVisibilityGroups::addVisibleLayersToGroup( QgsLayerTreeGroup* parent, QgsVisibilityGroups::GroupRecord& rec )
59+
{
60+
foreach ( QgsLayerTreeNode* node, parent->children() )
61+
{
62+
if ( QgsLayerTree::isGroup( node ) )
63+
addVisibleLayersToGroup( QgsLayerTree::toGroup( node ), rec );
64+
else if ( QgsLayerTree::isLayer( node ) )
65+
{
66+
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
67+
if ( nodeLayer->isVisible() )
68+
rec.mVisibleLayerIDs << nodeLayer->layerId();
69+
}
70+
}
71+
}
72+
73+
QgsVisibilityGroups::GroupRecord QgsVisibilityGroups::currentState()
74+
{
75+
GroupRecord rec;
76+
QgsLayerTreeGroup* root = QgsProject::instance()->layerTreeRoot();
77+
addVisibleLayersToGroup( root, rec );
78+
return rec;
79+
}
80+
81+
82+
QgsVisibilityGroups* QgsVisibilityGroups::instance()
83+
{
84+
if ( !sInstance )
85+
sInstance = new QgsVisibilityGroups();
86+
87+
return sInstance;
88+
}
89+
90+
void QgsVisibilityGroups::addGroup( const QString& name )
91+
{
92+
mGroups.insert( name, currentState() );
93+
94+
mMenuDirty = true;
95+
}
96+
97+
void QgsVisibilityGroups::updateGroup( const QString& name )
98+
{
99+
if ( !mGroups.contains( name ) )
100+
return;
101+
102+
mGroups[name] = currentState();
103+
104+
mMenuDirty = true;
105+
}
106+
107+
void QgsVisibilityGroups::removeGroup( const QString& name )
108+
{
109+
mGroups.remove( name );
110+
111+
mMenuDirty = true;
112+
}
113+
114+
void QgsVisibilityGroups::clear()
115+
{
116+
mGroups.clear();
117+
118+
mMenuDirty = true;
119+
}
120+
121+
QStringList QgsVisibilityGroups::groups() const
122+
{
123+
return mGroups.keys();
124+
}
125+
126+
QMenu* QgsVisibilityGroups::menu()
127+
{
128+
return mMenu;
129+
}
130+
131+
132+
void QgsVisibilityGroups::addGroup()
133+
{
134+
bool ok;
135+
QString name = QInputDialog::getText( 0, tr( "Visibility groups" ), tr( "Name of the new group" ), QLineEdit::Normal, QString(), &ok );
136+
if ( !ok && name.isEmpty() )
137+
return;
138+
139+
addGroup( name );
140+
}
141+
142+
143+
void QgsVisibilityGroups::groupTriggerred()
144+
{
145+
QAction* actionGroup = qobject_cast<QAction*>( sender() );
146+
if ( !actionGroup )
147+
return;
148+
149+
applyState( actionGroup->text() );
150+
}
151+
152+
153+
void QgsVisibilityGroups::applyStateToLayerTreeGroup( QgsLayerTreeGroup* parent, const QSet<QString>& visibleLayerIDs )
154+
{
155+
foreach ( QgsLayerTreeNode* node, parent->children() )
156+
{
157+
if ( QgsLayerTree::isGroup( node ) )
158+
applyStateToLayerTreeGroup( QgsLayerTree::toGroup( node ), visibleLayerIDs );
159+
else if ( QgsLayerTree::isLayer( node ) )
160+
{
161+
QgsLayerTreeLayer* nodeLayer = QgsLayerTree::toLayer( node );
162+
nodeLayer->setVisible( visibleLayerIDs.contains( nodeLayer->layerId() ) ? Qt::Checked : Qt::Unchecked );
163+
}
164+
}
165+
}
166+
167+
168+
void QgsVisibilityGroups::applyState( const QString& groupName )
169+
{
170+
if ( !mGroups.contains( groupName ) )
171+
return;
172+
173+
const GroupRecord& rec = mGroups[groupName];
174+
applyStateToLayerTreeGroup( QgsProject::instance()->layerTreeRoot(), QSet<QString>::fromList( rec.mVisibleLayerIDs ) );
175+
176+
mMenuDirty = true;
177+
}
178+
179+
180+
void QgsVisibilityGroups::removeCurrentGroup()
181+
{
182+
foreach ( QAction* a, mMenuGroupActions )
183+
{
184+
if ( a->isChecked() )
185+
{
186+
removeGroup( a->text() );
187+
break;
188+
}
189+
}
190+
}
191+
192+
193+
void QgsVisibilityGroups::menuAboutToShow()
194+
{
195+
if ( !mMenuDirty )
196+
return;
197+
198+
// lazy update of the menu only when necessary - so that we do not do too much work when it is not necessary
199+
200+
qDeleteAll( mMenuGroupActions );
201+
mMenuGroupActions.clear();
202+
203+
GroupRecord rec = currentState();
204+
bool hasCurrent = false;
205+
206+
foreach ( const QString& grpName, mGroups.keys() )
207+
{
208+
QAction* a = new QAction( grpName, mMenu );
209+
a->setCheckable( true );
210+
if ( rec == mGroups[grpName] )
211+
{
212+
a->setChecked( true );
213+
hasCurrent = true;
214+
}
215+
connect( a, SIGNAL( triggered() ), this, SLOT( groupTriggerred() ) );
216+
mMenuGroupActions.append( a );
217+
}
218+
mMenu->insertActions( mMenuSeparator, mMenuGroupActions );
219+
220+
mActionRemoveCurrentGroup->setEnabled( hasCurrent );
221+
222+
mMenuDirty = false;
223+
}
224+
225+
226+
void QgsVisibilityGroups::layerTreeVisibilityChanged( QgsLayerTreeNode* node, Qt::CheckState state )
227+
{
228+
Q_UNUSED( node );
229+
Q_UNUSED( state );
230+
231+
mMenuDirty = true;
232+
}
233+
234+
void QgsVisibilityGroups::layerTreeAddedChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
235+
{
236+
Q_UNUSED( node );
237+
Q_UNUSED( indexFrom );
238+
Q_UNUSED( indexTo );
239+
240+
mMenuDirty = true;
241+
}
242+
243+
void QgsVisibilityGroups::layerTreeWillRemoveChildren( QgsLayerTreeNode* node, int indexFrom, int indexTo )
244+
{
245+
Q_UNUSED( node );
246+
Q_UNUSED( indexFrom );
247+
Q_UNUSED( indexTo );
248+
249+
mMenuDirty = true;
250+
}
251+
252+
void QgsVisibilityGroups::readProject( const QDomDocument& doc )
253+
{
254+
clear();
255+
256+
QDomElement visGroupsElem = doc.firstChildElement( "qgis" ).firstChildElement( "visibility-groups" );
257+
if ( visGroupsElem.isNull() )
258+
return;
259+
260+
QDomElement visGroupElem = visGroupsElem.firstChildElement( "visibility-group" );
261+
while ( !visGroupElem.isNull() )
262+
{
263+
QString groupName = visGroupElem.attribute( "name" );
264+
GroupRecord rec;
265+
QDomElement visGroupLayerElem = visGroupElem.firstChildElement( "layer" );
266+
while ( !visGroupLayerElem.isNull() )
267+
{
268+
rec.mVisibleLayerIDs << visGroupLayerElem.attribute( "id" );
269+
visGroupLayerElem = visGroupLayerElem.nextSiblingElement( "layer" );
270+
}
271+
mGroups.insert( groupName, rec );
272+
273+
visGroupElem = visGroupElem.nextSiblingElement( "visibility-group" );
274+
}
275+
}
276+
277+
void QgsVisibilityGroups::writeProject( QDomDocument& doc )
278+
{
279+
QDomElement visGroupsElem = doc.createElement( "visibility-groups" );
280+
foreach ( const QString& grpName, mGroups.keys() )
281+
{
282+
QDomElement visGroupElem = doc.createElement( "visibility-group" );
283+
visGroupElem.setAttribute( "name", grpName );
284+
foreach ( QString layerID, mGroups[grpName].mVisibleLayerIDs )
285+
{
286+
QDomElement layerElem = doc.createElement( "layer" );
287+
layerElem.setAttribute( "id", layerID );
288+
visGroupElem.appendChild( layerElem );
289+
}
290+
291+
visGroupsElem.appendChild( visGroupElem );
292+
}
293+
294+
doc.firstChildElement( "qgis" ).appendChild( visGroupsElem );
295+
}

0 commit comments

Comments
 (0)