Skip to content

Commit

Permalink
Add more options for filtering legend elements
Browse files Browse the repository at this point in the history
This introduces two new options to filter legend elements:
- filter by expression: a boolean expression can be set. Only symbols of
features that make the expression evaluated to true will be kept in the legend
- filter by polygon: only symbols of features that are inside the given
polygon will be part of the legend.

The polygon filtering is used in particular for a new option in the
composer legend that allows to filter out anything that is not included
in the current atlas polygon.
  • Loading branch information
Hugo Mercier committed Nov 3, 2015
1 parent 16def06 commit d16cdcf
Show file tree
Hide file tree
Showing 34 changed files with 991 additions and 116 deletions.
3 changes: 3 additions & 0 deletions python/core/composer/qgsatlascomposition.sip
Expand Up @@ -260,6 +260,9 @@ public:
/** Returns the current atlas feature. Must be called after prepareForFeature(). */
QgsFeature* currentFeature() /Deprecated/;

/** Returns the current atlas geometry in the given projection system (default to the coverage layer's CRS) */
QgsGeometry currentGeometry( const QgsCoordinateReferenceSystem& projectedTo = QgsCoordinateReferenceSystem() ) const;

public slots:

/** Refreshes the current atlas feature, by refetching its attributes from the vector layer provider
Expand Down
10 changes: 10 additions & 0 deletions python/core/composer/qgscomposerlegend.sip
Expand Up @@ -65,6 +65,16 @@ class QgsComposerLegend : QgsComposerItem
//! @note added in 2.6
bool legendFilterByMapEnabled() const;

//! When set to true, during an atlas rendering, it will filter out legend elements
//! where features are outside the current atlas feature.
//! @note added in 2.14
void setLegendFilterOutAtlas( bool doFilter );

//! Whether to filter out legend elements outside of the current atlas feature
//! @see setLegendFilterOutAtlas()
//! @note added in 2.14
bool legendFilterOutAtlas() const;

//setters and getters
void setTitle( const QString& t );
QString title() const;
Expand Down
15 changes: 15 additions & 0 deletions python/core/layertree/qgslayertreemodel.sip
Expand Up @@ -133,8 +133,23 @@ class QgsLayerTreeModel : QAbstractItemModel
//! Ownership of map settings pointer does not change.
//! @note added in 2.6
void setLegendFilterByMap( const QgsMapSettings* settings );

//! Filter display of legend nodes for given map settings
//! @param settings Map settings. Setting a null pointer or invalid settings will disable any filter. Ownership is not changed, a copy is made
//! @param useExtent Whether to use the extent of the map settings as a first spatial filter on legend nodes
//! @param polygon If not empty, this polygon will be used instead of the map extent to filter legend nodes
//! @param useExpressions Whether to use legend node filter expressions
//! @note added in 2.14
void setLegendFilter( const QgsMapSettings* settings, bool useExtent = true, const QgsGeometry& polygon = QgsGeometry(), bool useExpressions = true );

//! Returns the current map settings used for legend filtering
//! @deprecated It has been renamed to legendFilterMapSettings()
const QgsMapSettings* legendFilterByMap() const;

//! Returns the current map settings used for the current legend filter (or null if none is enabled)
//! @note added in 2.14
const QgsMapSettings* legendFilterMapSettings() const;

//! Give the layer tree model hints about the currently associated map view
//! so that legend nodes that use map units can be scaled currectly
//! @note added in 2.6
Expand Down
7 changes: 7 additions & 0 deletions python/core/layertree/qgslayertreeutils.sip
Expand Up @@ -37,4 +37,11 @@ class QgsLayerTreeUtils

//! get invisible layers
static QStringList invisibleLayerList( QgsLayerTreeNode *node );

//! Set the expression filter of a legend layer
static void setLegendFilterByExpression( QgsLayerTreeLayer& layer, const QString& expr, bool enabled = true );
//! Return the expression filter of a legend layer
static QString legendFilterByExpression( const QgsLayerTreeLayer& layer, bool* enabled = 0 );
//! Test if one of the layers in a group has an expression filter
static bool hasLegendFilterExpression( const QgsLayerTreeGroup& group );
};
1 change: 1 addition & 0 deletions python/gui/gui.sip
Expand Up @@ -70,6 +70,7 @@
%Include qgshtmlannotationitem.sip
%Include qgsidentifymenu.sip
%Include qgslegendinterface.sip
%Include qgslegendfilterbutton.sip
%Include qgslonglongvalidator.sip
%Include qgsludialog.sip
%Include qgsmanageconnectionsdialog.sip
Expand Down
50 changes: 50 additions & 0 deletions python/gui/qgslegendfilterbutton.sip
@@ -0,0 +1,50 @@
/** \ingroup gui
* \class QgsFilterLegendButton
* A tool button that allows to enable or disable legend filter by contents of the map.
* An additional pop down menu allows to define a boolean expression to refine the filtering.
* @note added in 2.14
*/

class QgsLegendFilterButton: public QToolButton
{
%TypeHeaderCode
#include <qgslegendfilterbutton.h>
%End

public:
/**
* Construct a new filter legend button
*
* @param parent The parent QWidget
*/
QgsLegendFilterButton( QWidget* parent = 0 );
~QgsLegendFilterButton();

/**
* Returns the current text used as filter expression
*/
QString expressionText() const;

/**
* Sets the current text used as filter expression.
* This will update the menu
*/
void setExpressionText( const QString& expression );

/**
* Returns the current associated vectorLayer
* May be null
*/
QgsVectorLayer* vectorLayer() const;
/**
* Sets the associated vectorLayer
* May be null
*/
void setVectorLayer( QgsVectorLayer* layer );

signals:
/**
* Emitted when the expression text changes
*/
void expressionTextChanged();
};
101 changes: 86 additions & 15 deletions src/app/composer/qgscomposerlegendwidget.cpp
Expand Up @@ -28,6 +28,7 @@
#include "qgisapp.h"
#include "qgsapplication.h"
#include "qgslayertree.h"
#include "qgslayertreeutils.h"
#include "qgslayertreemodel.h"
#include "qgslayertreemodellegendnode.h"
#include "qgslegendrenderer.h"
Expand Down Expand Up @@ -119,6 +120,10 @@ QgsComposerLegendWidget::QgsComposerLegendWidget( QgsComposerLegend* legend )
mItemTreeView->setMenuProvider( new QgsComposerLegendMenuProvider( mItemTreeView, this ) );
connect( legend, SIGNAL( itemChanged() ), this, SLOT( setGuiElements() ) );
mWrapCharLineEdit->setText( legend->wrapChar() );

// connect atlas state to the filter legend by atlas checkbox
connect( &legend->composition()->atlasComposition(), SIGNAL( toggled( bool ) ), this, SLOT( updateFilterLegendByAtlasButton() ) );
connect( &legend->composition()->atlasComposition(), SIGNAL( coverageLayerChanged( QgsVectorLayer* ) ), this, SLOT( updateFilterLegendByAtlasButton() ) );
}

setGuiElements();
Expand Down Expand Up @@ -574,15 +579,22 @@ void QgsComposerLegendWidget::on_mCheckBoxAutoUpdate_stateChanged( int state )

mLegend->setAutoUpdateModel( state == Qt::Checked );

mLegend->update();
mLegend->updateItem();
mLegend->endCommand();

// do not allow editing of model if auto update is on - we would modify project's layer tree
QList<QWidget*> widgets;
widgets << mMoveDownToolButton << mMoveUpToolButton << mRemoveToolButton << mAddToolButton
<< mEditPushButton << mCountToolButton << mUpdateAllPushButton << mAddGroupToolButton;
<< mEditPushButton << mCountToolButton << mUpdateAllPushButton << mAddGroupToolButton
<< mExpressionFilterButton;
Q_FOREACH ( QWidget* w, widgets )
w->setEnabled( state != Qt::Checked );

if ( state == Qt::Unchecked )
{
// update widgets state based on current selection
selectedChanged( QModelIndex(), QModelIndex() );
}
}

void QgsComposerLegendWidget::on_mMapComboBox_currentIndexChanged( int index )
Expand Down Expand Up @@ -616,7 +628,7 @@ void QgsComposerLegendWidget::on_mMapComboBox_currentIndexChanged( int index )
{
mLegend->beginCommand( tr( "Legend map changed" ) );
mLegend->setComposerMap( map );
mLegend->update();
mLegend->updateItem();
mLegend->endCommand();
}
}
Expand Down Expand Up @@ -748,7 +760,7 @@ void QgsComposerLegendWidget::on_mRemoveToolButton_clicked()
}

mLegend->adjustBoxSize();
mLegend->update();
mLegend->updateItem();
mLegend->endCommand();
}

Expand Down Expand Up @@ -811,7 +823,7 @@ void QgsComposerLegendWidget::on_mEditPushButton_clicked()
}

mLegend->adjustBoxSize();
mLegend->update();
mLegend->updateItem();
mLegend->endCommand();
}

Expand Down Expand Up @@ -853,7 +865,7 @@ void QgsComposerLegendWidget::resetLayerNodeToDefaults()

mItemTreeView->layerTreeModel()->refreshLayerLegend( nodeLayer );

mLegend->update();
mLegend->updateItem();
mLegend->adjustBoxSize();
mLegend->endCommand();
}
Expand All @@ -879,16 +891,43 @@ void QgsComposerLegendWidget::on_mCountToolButton_clicked( bool checked )

mLegend->beginCommand( tr( "Legend updated" ) );
currentNode->setCustomProperty( "showFeatureCount", checked ? 1 : 0 );
mLegend->update();
mLegend->updateItem();
mLegend->adjustBoxSize();
mLegend->endCommand();
}

void QgsComposerLegendWidget::on_mFilterByMapToolButton_clicked( bool checked )
void QgsComposerLegendWidget::on_mFilterByMapToolButton_toggled( bool checked )
{
mLegend->beginCommand( tr( "Legend updated" ) );
mLegend->setLegendFilterByMapEnabled( checked );
mLegend->update();
mLegend->adjustBoxSize();
mLegend->endCommand();
}

void QgsComposerLegendWidget::on_mExpressionFilterButton_toggled( bool checked )
{
if ( !mLegend )
{
return;
}

//get current item
QModelIndex currentIndex = mItemTreeView->currentIndex();
if ( !currentIndex.isValid() )
{
return;
}

QgsLayerTreeNode* currentNode = mItemTreeView->currentNode();
if ( !QgsLayerTree::isLayer( currentNode ) )
return;

QgsLayerTreeUtils::setLegendFilterByExpression( *qobject_cast<QgsLayerTreeLayer*>( currentNode ),
mExpressionFilterButton->expressionText(),
checked );

mLegend->beginCommand( tr( "Legend updated" ) );
mLegend->updateItem();
mLegend->adjustBoxSize();
mLegend->endCommand();
}
Expand All @@ -904,11 +943,24 @@ void QgsComposerLegendWidget::on_mAddGroupToolButton_clicked()
{
mLegend->beginCommand( tr( "Legend group added" ) );
mLegend->modelV2()->rootGroup()->addGroup( tr( "Group" ) );
mLegend->update();
mLegend->updateItem();
mLegend->endCommand();
}
}

void QgsComposerLegendWidget::on_mFilterLegendByAtlasCheckBox_toggled( bool toggled )
{
if ( mLegend )
{
mLegend->setLegendFilterOutAtlas( toggled );
// force update of legend when in preview mode
if ( mLegend->composition()->atlasMode() == QgsComposition::PreviewAtlas )
{
mLegend->composition()->atlasComposition().refreshFeature();
}
}
}

void QgsComposerLegendWidget::updateLegend()
{
if ( mLegend )
Expand All @@ -918,8 +970,7 @@ void QgsComposerLegendWidget::updateLegend()
// this will reset the model completely, loosing any changes
mLegend->setAutoUpdateModel( true );
mLegend->setAutoUpdateModel( false );

mLegend->update();
mLegend->updateItem();
mLegend->endCommand();
}
}
Expand Down Expand Up @@ -997,11 +1048,16 @@ void QgsComposerLegendWidget::selectedChanged( const QModelIndex & current, cons
Q_UNUSED( previous );
QgsDebugMsg( "Entered" );

if ( mLegend && mLegend->autoUpdateModel() )
return;

mCountToolButton->setChecked( false );
mCountToolButton->setEnabled( false );

if ( mLegend && mLegend->autoUpdateModel() )
return;
mExpressionFilterButton->blockSignals( true );
mExpressionFilterButton->setChecked( false );
mExpressionFilterButton->setEnabled( false );
mExpressionFilterButton->blockSignals( false );

QgsLayerTreeNode* currentNode = mItemTreeView->currentNode();
if ( !QgsLayerTree::isLayer( currentNode ) )
Expand All @@ -1014,6 +1070,15 @@ void QgsComposerLegendWidget::selectedChanged( const QModelIndex & current, cons

mCountToolButton->setChecked( currentNode->customProperty( "showFeatureCount", 0 ).toInt() );
mCountToolButton->setEnabled( true );

bool exprEnabled;
QString expr = QgsLayerTreeUtils::legendFilterByExpression( *qobject_cast<QgsLayerTreeLayer*>( currentNode ), &exprEnabled );
mExpressionFilterButton->blockSignals( true );
mExpressionFilterButton->setExpressionText( expr );
mExpressionFilterButton->setVectorLayer( vl );
mExpressionFilterButton->setEnabled( true );
mExpressionFilterButton->setChecked( exprEnabled );
mExpressionFilterButton->blockSignals( false );
}

void QgsComposerLegendWidget::setCurrentNodeStyleFromAction()
Expand All @@ -1023,5 +1088,11 @@ void QgsComposerLegendWidget::setCurrentNodeStyleFromAction()
return;

QgsLegendRenderer::setNodeLegendStyle( mItemTreeView->currentNode(), ( QgsComposerLegendStyle::Style ) a->data().toInt() );
mLegend->update();
mLegend->updateItem();
}

void QgsComposerLegendWidget::updateFilterLegendByAtlasButton()
{
const QgsAtlasComposition& atlas = mLegend->composition()->atlasComposition();
mFilterLegendByAtlasCheckBox->setEnabled( atlas.enabled() && atlas.coverageLayer() && atlas.coverageLayer()->geometryType() == QGis::Polygon );
}
8 changes: 7 additions & 1 deletion src/app/composer/qgscomposerlegendwidget.h
Expand Up @@ -80,11 +80,14 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
void on_mAddToolButton_clicked();
void on_mEditPushButton_clicked();
void on_mCountToolButton_clicked( bool checked );
void on_mFilterByMapToolButton_clicked( bool checked );
void on_mExpressionFilterButton_toggled( bool checked );
void on_mFilterByMapToolButton_toggled( bool checked );
void resetLayerNodeToDefaults();
void on_mUpdateAllPushButton_clicked();
void on_mAddGroupToolButton_clicked();

void on_mFilterLegendByAtlasCheckBox_toggled( bool checked );

void selectedChanged( const QModelIndex & current, const QModelIndex & previous );

void setCurrentNodeStyleFromAction();
Expand All @@ -96,6 +99,9 @@ class QgsComposerLegendWidget: public QgsComposerItemBaseWidget, private Ui::Qgs
/** Sets GUI according to state of mLegend*/
void setGuiElements();

/** update the enabling state of the filter by atlas button */
void updateFilterLegendByAtlasButton();

private:
QgsComposerLegendWidget();
void blockAllSignals( bool b );
Expand Down

0 comments on commit d16cdcf

Please sign in to comment.