Skip to content

Commit

Permalink
[FEATURE] Indicators for layer tree view + filter indicator implement…
Browse files Browse the repository at this point in the history
…ation

This adds a mini-framework for display of extra icons in layer tree views
next to layer and group names. Tool tip text can be associated with indicators
to give extra context for indicators. In addition, a signal gets emitted
when user clicks indicators and custom actions can be defined.

The main window's layer tree view (ToC) gets support for indicators
that are shown when a vector layer has a filter applied. This makes it easier
for users to understand that they are looking at a subset of all data.
Clicking the indicator's icon brings up query builder.
  • Loading branch information
wonder-sk committed Feb 26, 2018
1 parent 68ee969 commit f56d70f
Show file tree
Hide file tree
Showing 14 changed files with 708 additions and 3 deletions.
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Expand Up @@ -287,6 +287,7 @@
%Include layertree/qgslayertreemapcanvasbridge.sip
%Include layertree/qgslayertreeview.sip
%Include layertree/qgslayertreeviewdefaultactions.sip
%Include layertree/qgslayertreeviewindicator.sip
%Include layout/qgslayoutcustomdrophandler.sip
%Include layout/qgslayoutdesignerinterface.sip
%Include layout/qgslayoutitemcombobox.sip
Expand Down
33 changes: 32 additions & 1 deletion python/gui/layertree/qgslayertreeview.sip.in
Expand Up @@ -109,6 +109,37 @@ Return list of selected nodes filtered to just layer nodes
QList<QgsMapLayer *> selectedLayers() const;
%Docstring
Get list of selected layers
%End

void addIndicator( QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator );
%Docstring
Adds an indicator to the given layer tree node. Indicators are icons shown next to layer/group names
in the layer tree view. They can be used to show extra information with tree nodes and they allow
user interaction.

Does not take ownership of the indicator. One indicator object may be used for multiple layer tree nodes.
\sa removeIndicator
\sa indicators

.. versionadded:: 3.2
%End

void removeIndicator( QgsLayerTreeNode *node, QgsLayerTreeViewIndicator *indicator );
%Docstring
Removes a previously added indicator to a layer tree node. Does not delete the indicator.
\sa addIndicator
\sa indicators

.. versionadded:: 3.2
%End

QList<QgsLayerTreeViewIndicator *> indicators( QgsLayerTreeNode *node ) const;
%Docstring
Returns list of indicators associated with a particular layer tree node.
\sa addIndicator
\sa removeIndicator

.. versionadded:: 3.2
%End

public slots:
Expand Down Expand Up @@ -153,7 +184,6 @@ Emitted when a current layer is changed
virtual void dropEvent( QDropEvent *event );



protected slots:

void modelRowsInserted( const QModelIndex &index, int start, int end );
Expand All @@ -166,6 +196,7 @@ Emitted when a current layer is changed
void onModelReset();

protected:

};


Expand Down
62 changes: 62 additions & 0 deletions python/gui/layertree/qgslayertreeviewindicator.sip
@@ -0,0 +1,62 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layertree/qgslayertreeviewindicator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/




class QgsLayerTreeViewIndicator : QObject
{
%Docstring
Indicator that can be used in a layer tree view to display icons next to items of the layer tree.
They add extra context to the item and interactivity (using clicked() signal).

Indicators can be added/removed to individual layer tree items using :py:func:`QgsLayerTreeView.addIndicator()`
and QgsLayerTreeView.removeIndicator() calls.

.. versionadded:: 3.2
%End

%TypeHeaderCode
#include "qgslayertreeviewindicator.h"
%End
public:
explicit QgsLayerTreeViewIndicator( QObject *parent /TransferThis/ = 0 );

QIcon icon() const;
%Docstring
Indicator icon that will be displayed in the layer tree view
%End
void setIcon( const QIcon &icon );
%Docstring
Sets indicator icon that will be displayed in the layer tree view
%End

QString toolTip() const;
%Docstring
Returns tool tip text that will be shown when user hovers mouse over the indicator
%End
void setToolTip( const QString &tip );
%Docstring
Sets tool tip text
%End

signals:
void clicked( const QModelIndex &index );
%Docstring
Signal that is emitted when user clicks on the indicator
%End

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/gui/layertree/qgslayertreeviewindicator.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
2 changes: 2 additions & 0 deletions src/app/CMakeLists.txt
Expand Up @@ -52,6 +52,7 @@ SET(QGIS_APP_SRCS
qgslabelengineconfigdialog.cpp
qgslabelinggui.cpp
qgslabelingwidget.cpp
qgslayertreeviewfilterindicator.cpp
qgsloadstylefromdbdialog.cpp
qgsmapcanvasdockwidget.cpp
qgsmaplayerstyleguiutils.cpp
Expand Down Expand Up @@ -263,6 +264,7 @@ SET (QGIS_APP_MOC_HDRS
qgslabelinggui.h
qgslabelingwidget.h
qgslabelpropertydialog.h
qgslayertreeviewfilterindicator.h
qgsloadstylefromdbdialog.h
qgsmapcanvasdockwidget.h
qgsmaplayerstyleguiutils.h
Expand Down
3 changes: 2 additions & 1 deletion src/app/qgisapp.cpp
Expand Up @@ -201,6 +201,7 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
#include "qgslayertreeutils.h"
#include "qgslayertreeview.h"
#include "qgslayertreeviewdefaultactions.h"
#include "qgslayertreeviewfilterindicator.h"
#include "qgslayout.h"
#include "qgslayoutatlas.h"
#include "qgslayoutcustomdrophandler.h"
Expand Down Expand Up @@ -3623,7 +3624,6 @@ void QgisApp::addUserInputWidget( QWidget *widget )
mUserInputDockWidget->addUserInputWidget( widget );
}


void QgisApp::initLayerTreeView()
{
mLayerTreeView->setWhatsThis( tr( "Map legend that displays all the layers currently on the map canvas. Click on the checkbox to turn a layer on or off. Double-click on a layer in the legend to customize its appearance and set other properties." ) );
Expand All @@ -3646,6 +3646,7 @@ void QgisApp::initLayerTreeView()

mLayerTreeView->setModel( model );
mLayerTreeView->setMenuProvider( new QgsAppLayerTreeViewMenuProvider( mLayerTreeView, mMapCanvas ) );
new QgsLayerTreeViewFilterIndicatorManager( mLayerTreeView ); // gets parented to the layer view

setupLayerTreeViewFromSettings();

Expand Down
165 changes: 165 additions & 0 deletions src/app/qgslayertreeviewfilterindicator.cpp
@@ -0,0 +1,165 @@
/***************************************************************************
qgslayertreeviewfilterindicator.cpp
--------------------------------------
Date : Januray 2018
Copyright : (C) 2018 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 "qgslayertreeviewfilterindicator.h"

#include "qgslayertree.h"
#include "qgslayertreemodel.h"
#include "qgslayertreeview.h"
#include "qgsquerybuilder.h"
#include "qgsvectorlayer.h"


QgsLayerTreeViewFilterIndicatorManager::QgsLayerTreeViewFilterIndicatorManager( QgsLayerTreeView *view )
: QObject( view )
, mLayerTreeView( view )
{
mIndicator = new QgsLayerTreeViewIndicator( this );
mIndicator->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionFilter2.svg" ) ) );
mIndicator->setToolTip( "Filtered" );
connect( mIndicator, &QgsLayerTreeViewIndicator::clicked, this, &QgsLayerTreeViewFilterIndicatorManager::onIndicatorClicked );

QgsLayerTree *tree = mLayerTreeView->layerTreeModel()->rootGroup();
onAddedChildren( tree, 0, tree->children().count() - 1 );

connect( tree, &QgsLayerTree::addedChildren, this, &QgsLayerTreeViewFilterIndicatorManager::onAddedChildren );
connect( tree, &QgsLayerTree::willRemoveChildren, this, &QgsLayerTreeViewFilterIndicatorManager::onWillRemoveChildren );
}


void QgsLayerTreeViewFilterIndicatorManager::onAddedChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
// recursively connect to providers' dataChanged() signal

QList<QgsLayerTreeNode *> children = node->children();
for ( int i = indexFrom; i <= indexTo; ++i )
{
QgsLayerTreeNode *childNode = children[i];

if ( QgsLayerTree::isGroup( childNode ) )
{
onAddedChildren( childNode, 0, childNode->children().count() - 1 );
}
else if ( QgsLayerTree::isLayer( childNode ) )
{
QgsLayerTreeLayer *childLayerNode = QgsLayerTree::toLayer( childNode );
if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( childLayerNode->layer() ) )
{
if ( vlayer->dataProvider() )
{
connect( vlayer->dataProvider(), &QgsDataProvider::dataChanged, this, &QgsLayerTreeViewFilterIndicatorManager::onProviderDataChanged );

addOrRemoveIndicator( childLayerNode, vlayer->dataProvider() );
}
}
else if ( !childLayerNode->layer() )
{
// wait for layer to be loaded (e.g. when loading project, first the tree is loaded, afterwards the references to layers are resolved)
connect( childLayerNode, &QgsLayerTreeLayer::layerLoaded, this, &QgsLayerTreeViewFilterIndicatorManager::onLayerLoaded );
}
}
}
}


void QgsLayerTreeViewFilterIndicatorManager::onWillRemoveChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo )
{
// recursively disconnect from providers' dataChanged() signal

QList<QgsLayerTreeNode *> children = node->children();
for ( int i = indexFrom; i <= indexTo; ++i )
{
QgsLayerTreeNode *childNode = children[i];

if ( QgsLayerTree::isGroup( childNode ) )
{
onWillRemoveChildren( childNode, 0, childNode->children().count() - 1 );
}
else if ( QgsLayerTree::isLayer( childNode ) )
{
QgsLayerTreeLayer *childLayerNode = QgsLayerTree::toLayer( childNode );
if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( childLayerNode->layer() ) )
{
if ( vlayer->dataProvider() )
disconnect( vlayer->dataProvider(), &QgsDataProvider::dataChanged, this, &QgsLayerTreeViewFilterIndicatorManager::onProviderDataChanged );
}
}
}
}


void QgsLayerTreeViewFilterIndicatorManager::onLayerLoaded()
{
QgsLayerTreeLayer *nodeLayer = qobject_cast<QgsLayerTreeLayer *>( sender() );
if ( !nodeLayer )
return;

if ( QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ) )
{
if ( vlayer->dataProvider() )
{
connect( vlayer->dataProvider(), &QgsDataProvider::dataChanged, this, &QgsLayerTreeViewFilterIndicatorManager::onProviderDataChanged );

addOrRemoveIndicator( nodeLayer, vlayer->dataProvider() );
}
}
}


void QgsLayerTreeViewFilterIndicatorManager::onProviderDataChanged()
{
QgsVectorDataProvider *provider = qobject_cast<QgsVectorDataProvider *>( sender() );
if ( !provider )
return;

// walk the tree and find layer node that needs to be updated
const QList<QgsLayerTreeLayer *> layerNodes = mLayerTreeView->layerTreeModel()->rootGroup()->findLayers();
for ( QgsLayerTreeLayer *node : layerNodes )
{
if ( node->layer() && node->layer()->dataProvider() == provider )
{
addOrRemoveIndicator( node, provider );
break;
}
}
}


void QgsLayerTreeViewFilterIndicatorManager::onIndicatorClicked( const QModelIndex &index )
{
QgsLayerTreeNode *node = mLayerTreeView->layerTreeModel()->index2node( index );
if ( !QgsLayerTree::isLayer( node ) )
return;

QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( QgsLayerTree::toLayer( node )->layer() );
if ( !vlayer || !vlayer->dataProvider() )
return;

// launch the query builder
QgsQueryBuilder qb( vlayer );
qb.setSql( vlayer->dataProvider()->subsetString() );
if ( qb.exec() )
vlayer->dataProvider()->setSubsetString( qb.sql() );
}


void QgsLayerTreeViewFilterIndicatorManager::addOrRemoveIndicator( QgsLayerTreeNode *node, QgsVectorDataProvider *provider )
{
QString filter = provider->subsetString();
if ( !filter.isEmpty() )
mLayerTreeView->addIndicator( node, mIndicator );
else
mLayerTreeView->removeIndicator( node, mIndicator );
}
53 changes: 53 additions & 0 deletions src/app/qgslayertreeviewfilterindicator.h
@@ -0,0 +1,53 @@
/***************************************************************************
qgslayertreeviewfilterindicator.h
--------------------------------------
Date : Januray 2018
Copyright : (C) 2018 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 QGSLAYERTREEVIEWFILTERINDICATOR_H
#define QGSLAYERTREEVIEWFILTERINDICATOR_H

#include "qgslayertreeviewindicator.h"

class QgsLayerTreeNode;
class QgsLayerTreeView;
class QgsVectorDataProvider;


//! Adds indicators showing whether vector layers have a filter applied.
class QgsLayerTreeViewFilterIndicatorManager : public QObject
{
Q_OBJECT
public:
explicit QgsLayerTreeViewFilterIndicatorManager( QgsLayerTreeView *view );

private slots:
//! Connects to signals of layers newly added to the tree
void onAddedChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo );
//! Disconnects from layers about to be removed from the tree
void onWillRemoveChildren( QgsLayerTreeNode *node, int indexFrom, int indexTo );
//! Starts listening to layer provider's dataChanged signal
void onLayerLoaded();
//! Adds/removes indicator of a layer
void onProviderDataChanged();

void onIndicatorClicked( const QModelIndex &index );

private:
void addOrRemoveIndicator( QgsLayerTreeNode *node, QgsVectorDataProvider *provider );

private:
QgsLayerTreeView *mLayerTreeView;
QgsLayerTreeViewIndicator *mIndicator = nullptr;
};

#endif // QGSLAYERTREEVIEWFILTERINDICATOR_H

0 comments on commit f56d70f

Please sign in to comment.