Merge pull request #2600 from m-kuhn/orderby-renderer

Allow definition of feature rendering order
This commit is contained in:
Matthias Kuhn 2015-12-22 17:45:01 +01:00
commit e6a265c103
35 changed files with 870 additions and 60 deletions

View File

@ -78,6 +78,42 @@ class QgsFeatureRequest
};
class OrderBy
{
public:
OrderBy();
OrderBy( const QList<QgsFeatureRequest::OrderByClause>& other );
/**
* Get a copy as a list of OrderByClauses
*
* This is only required in python where the inheritance
* is not properly propagated and this makes it usable.
*/
QList<QgsFeatureRequest::OrderByClause> list() const;
/**
* Serialize to XML
*/
void save( QDomElement& elem ) const;
/**
* Deserialize from XML
*/
void load( const QDomElement& elem );
/**
* Returns a set of used attributes
*/
QSet<QString> usedAttributes() const;
/**
* Dumps the content to an SQL equivalent syntax
*/
QString dump() const;
};
/**
* A special attribute that if set matches all attributes
*/
@ -175,12 +211,12 @@ class QgsFeatureRequest
/**
* Return a list of order by clauses specified for this feature request.
*/
QList<QgsFeatureRequest::OrderByClause> orderBys() const;
QgsFeatureRequest::OrderBy orderBy() const;
/**
* Set a list of order by clauses.
*/
void setOrderBys(const QList<QgsFeatureRequest::OrderByClause>& orderBys );
QgsFeatureRequest& setOrderBy(const QgsFeatureRequest::OrderBy& orderBy );
/** Set the maximum number of features to request.
* @param limit maximum number of features, or -1 to request all features.

View File

@ -312,6 +312,18 @@ class QgsFeatureRendererV2
*/
void setForceRasterRender( bool forceRaster );
/**
* Get the order in which features shall be processed by this renderer.
* @note added in QGIS 2.14
*/
QgsFeatureRequest::OrderBy orderBy() const;
/**
* Define the order in which features shall be processed by this renderer.
* @note added in QGIS 2.14
*/
void setOrderBy( const QgsFeatureRequest::OrderBy& orderBy );
protected:
QgsFeatureRendererV2( const QString& type );

View File

@ -0,0 +1,34 @@
/***************************************************************************
qgsorderbydialog.h
---------------------
begin : 20.12.2015
copyright : (C) 2015 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* 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. *
* *
***************************************************************************/
class QgsOrderByDialog : QDialog
{
%TypeHeaderCode
#include "qgsorderbydialog.h"
%End
public:
QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent = nullptr );
/**
* Set the order by to manage
*/
void setOrderBys( const QList<QgsFeatureRequest::OrderByClause>& orderBys );
/**
* Get the order by defined in the dialog
*/
QgsFeatureRequest::OrderBy orderBys();
};

View File

@ -120,6 +120,7 @@ class QgsExpressionSorter
QgsAbstractFeatureIterator::QgsAbstractFeatureIterator( const QgsFeatureRequest& request )
: mRequest( request )
, mClosed( false )
, mZombie( false )
, refs( 0 )
, mFetchedCount( 0 )
, mGeometrySimplifier( nullptr )
@ -149,6 +150,12 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f )
++mFeatureIterator;
dataOk = true;
}
else
{
dataOk = false;
// even the zombie dies at this point...
mZombie = false;
}
}
else
{
@ -213,7 +220,7 @@ void QgsAbstractFeatureIterator::ref()
prepareSimplification( mRequest.simplifyMethod() );
// Should be called as last preparation step since it possibly will already fetch all features
setupOrderBy( mRequest.orderBys() );
setupOrderBy( mRequest.orderBy() );
}
refs++;
}
@ -279,10 +286,12 @@ void QgsAbstractFeatureIterator::setupOrderBy( const QList<QgsFeatureRequest::Or
mCachedFeatures.append( indexedFeature );
}
std::sort( mCachedFeatures.begin(), mCachedFeatures.end(), QgsExpressionSorter( preparedOrderBys ) );
qSort( mCachedFeatures.begin(), mCachedFeatures.end(), QgsExpressionSorter( preparedOrderBys ) );
mFeatureIterator = mCachedFeatures.constBegin();
mUseCachedFeatures = true;
// The real iterator is closed, we are only serving cached features
mZombie = true;
}
}

View File

@ -89,7 +89,17 @@ class CORE_EXPORT QgsAbstractFeatureIterator
/** Set to true, as soon as the iterator is closed. */
bool mClosed;
/**
* A feature iterator may be closed already but still be serving features from the cache.
* This is done when we serve features which have been pre-fetched and the order by has
* been locally sorted.
* In such a scenario, all resources have been released (mClosed is true) but the deads
* are still alive.
*/
bool mZombie;
//! reference counting (to allow seamless copying of QgsFeatureIterator instances)
//! TODO QGIS3: make this private
int refs;
void ref(); //!< add reference
void deref(); //!< remove reference, delete if refs == 0
@ -247,7 +257,7 @@ inline bool QgsFeatureIterator::close()
inline bool QgsFeatureIterator::isClosed() const
{
return mIter ? mIter->mClosed : true;
return mIter ? mIter->mClosed && !mIter->mZombie : true;
}
inline bool operator== ( const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2 )

View File

@ -84,7 +84,7 @@ QgsFeatureRequest& QgsFeatureRequest::operator=( const QgsFeatureRequest & rh )
mAttrs = rh.mAttrs;
mSimplifyMethod = rh.mSimplifyMethod;
mLimit = rh.mLimit;
mOrderBys = rh.mOrderBys;
mOrderBy = rh.mOrderBy;
return *this;
}
@ -144,24 +144,25 @@ QgsFeatureRequest &QgsFeatureRequest::setExpressionContext( const QgsExpressionC
QgsFeatureRequest& QgsFeatureRequest::addOrderBy( const QString& expression, bool ascending )
{
mOrderBys.append( OrderByClause( expression, ascending ) );
mOrderBy.append( OrderByClause( expression, ascending ) );
return *this;
}
QgsFeatureRequest& QgsFeatureRequest::addOrderBy( const QString& expression, bool ascending, bool nullsfirst )
{
mOrderBys.append( OrderByClause( expression, ascending, nullsfirst ) );
mOrderBy.append( OrderByClause( expression, ascending, nullsfirst ) );
return *this;
}
QList<QgsFeatureRequest::OrderByClause> QgsFeatureRequest::orderBys() const
QgsFeatureRequest::OrderBy QgsFeatureRequest::orderBy() const
{
return mOrderBys;
return mOrderBy;
}
void QgsFeatureRequest::setOrderBys( const QList<QgsFeatureRequest::OrderByClause>& orderBys )
QgsFeatureRequest& QgsFeatureRequest::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy )
{
mOrderBys = orderBys;
mOrderBy = orderBy;
return *this;
}
QgsFeatureRequest& QgsFeatureRequest::setLimit( long limit )
@ -307,7 +308,90 @@ void QgsFeatureRequest::OrderByClause::setNullsFirst( bool nullsFirst )
mNullsFirst = nullsFirst;
}
QString QgsFeatureRequest::OrderByClause::dump() const
{
return QString( "%1 %2 %3" )
.arg( mExpression.expression() )
.arg( mAscending ? "ASC" : "DESC" )
.arg( mNullsFirst ? "NULLS FIRST" : "NULLS LAST" );
}
QgsExpression QgsFeatureRequest::OrderByClause::expression() const
{
return mExpression;
}
QgsFeatureRequest::OrderBy::OrderBy( const QList<QgsFeatureRequest::OrderByClause>& other )
{
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, other )
{
append( clause );
}
}
QList<QgsFeatureRequest::OrderByClause> QgsFeatureRequest::OrderBy::list() const
{
return *this;
}
void QgsFeatureRequest::OrderBy::save( QDomElement& elem ) const
{
QDomDocument doc = elem.ownerDocument();
QList<OrderByClause>::ConstIterator it;
for ( it = constBegin(); it != constEnd(); ++it )
{
const OrderByClause& clause = *it;
QDomElement clauseElem = doc.createElement( "orderByClause" );
clauseElem.setAttribute( "asc", clause.ascending() );
clauseElem.setAttribute( "nullsFirst", clause.nullsFirst() );
clauseElem.appendChild( doc.createTextNode( clause.expression().expression() ) );
elem.appendChild( clauseElem );
}
}
void QgsFeatureRequest::OrderBy::load( const QDomElement& elem )
{
clear();
QDomNodeList clauses = elem.childNodes();
for ( int i = 0; i < clauses.size(); ++i )
{
QDomElement clauseElem = clauses.at( i ).toElement();
QString expression = clauseElem.text();
bool asc = clauseElem.attribute( "asc" ).toInt() != 0;
bool nullsFirst = clauseElem.attribute( "nullsFirst" ).toInt() != 0;
append( OrderByClause( expression, asc, nullsFirst ) );
}
}
QSet<QString> QgsFeatureRequest::OrderBy::usedAttributes() const
{
QSet<QString> usedAttributes;
QList<OrderByClause>::ConstIterator it;
for ( it = constBegin(); it != constEnd(); ++it )
{
const OrderByClause& clause = *it;
usedAttributes.unite( clause.expression().referencedColumns().toSet() );
}
return usedAttributes;
}
QString QgsFeatureRequest::OrderBy::dump() const
{
QStringList results;
QList<OrderByClause>::ConstIterator it;
for ( it = constBegin(); it != constEnd(); ++it )
{
const OrderByClause& clause = *it;
results << clause.dump();
}
return results.join( ", " );
}

View File

@ -155,12 +155,67 @@ class CORE_EXPORT QgsFeatureRequest
*/
void setNullsFirst( bool nullsFirst );
/**
* Dumps the content to an SQL equivalent
*/
QString dump() const;
private:
QgsExpression mExpression;
bool mAscending;
bool mNullsFirst;
};
/**
* Represents a list of OrderByClauses, with the most important first and the least
* important last.
*
* @note added in QGIS 2.14
*/
class OrderBy : public QList<OrderByClause>
{
public:
/**
* Create a new empty order by
*/
OrderBy()
: QList<OrderByClause>()
{}
/**
* Create a new order by from a list of clauses
*/
OrderBy( const QList<OrderByClause>& other );
/**
* Get a copy as a list of OrderByClauses
*
* This is only required in python where the inheritance
* is not properly propagated and this makes it usable.
*/
QList<OrderByClause> list() const;
/**
* Serialize to XML
*/
void save( QDomElement& elem ) const;
/**
* Deserialize from XML
*/
void load( const QDomElement& elem );
/**
* Returns a set of used attributes
*/
QSet<QString> usedAttributes() const;
/**
* Dumps the content to an SQL equivalent syntax
*/
QString dump() const;
};
/**
* A special attribute that if set matches all attributes
*/
@ -277,13 +332,17 @@ class CORE_EXPORT QgsFeatureRequest
/**
* Return a list of order by clauses specified for this feature request.
*
* @note added in 2.14
*/
QList<OrderByClause> orderBys() const;
OrderBy orderBy() const;
/**
* Set a list of order by clauses.
*
* @note added in 2.14
*/
void setOrderBys( const QList<OrderByClause>& orderBys );
QgsFeatureRequest& setOrderBy( const OrderBy& orderBy );
/** Set the maximum number of features to request.
* @param limit maximum number of features, or -1 to request all features.
@ -346,7 +405,7 @@ class CORE_EXPORT QgsFeatureRequest
QgsAttributeList mAttrs;
QgsSimplifyMethod mSimplifyMethod;
long mLimit;
QList<OrderByClause> mOrderBys;
OrderBy mOrderBy;
};
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags )

View File

@ -105,17 +105,30 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
if ( mProviderRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
{
// prepare list of attributes to match provider fields
QgsAttributeList providerSubset;
QSet<int> providerSubset;
QgsAttributeList subset = mProviderRequest.subsetOfAttributes();
int nPendingFields = mSource->mFields.count();
for ( int i = 0; i < subset.count(); ++i )
Q_FOREACH ( int attrIndex, subset )
{
int attrIndex = subset[i];
if ( attrIndex < 0 || attrIndex >= nPendingFields ) continue;
if ( mSource->mFields.fieldOrigin( attrIndex ) == QgsFields::OriginProvider )
providerSubset << mSource->mFields.fieldOriginIndex( attrIndex );
}
mProviderRequest.setSubsetOfAttributes( providerSubset );
// This is done in order to be prepared to do fallback order bys
// and be sure we have the required columns.
// TODO:
// It would be nicer to first check if we can compile the order by
// and only modify the subset if we cannot.
if ( !mProviderRequest.orderBy().isEmpty() )
{
Q_FOREACH ( const QString& attr, mProviderRequest.orderBy().usedAttributes() )
{
providerSubset << mSource->mFields.fieldNameIndex( attr );
}
}
mProviderRequest.setSubsetOfAttributes( providerSubset.toList() );
}
if ( mProviderRequest.filterType() == QgsFeatureRequest::FilterExpression )

View File

@ -150,10 +150,13 @@ bool QgsVectorLayerRenderer::render()
QgsRectangle requestExtent = mContext.extent();
mRendererV2->modifyRequestExtent( requestExtent, mContext );
QgsFeatureRequest::OrderBy orderBy = mRendererV2->orderBy();
QgsFeatureRequest featureRequest = QgsFeatureRequest()
.setFilterRect( requestExtent )
.setSubsetOfAttributes( mAttrNames, mFields )
.setExpressionContext( mContext.expressionContext() );
.setExpressionContext( mContext.expressionContext() )
.setOrderBy( orderBy );
const QgsFeatureFilterProvider* featureFilterProvider = mContext.featureFilterProvider();
if ( featureFilterProvider )

View File

@ -498,7 +498,7 @@ QgsCategorizedSymbolRendererV2* QgsCategorizedSymbolRendererV2::clone() const
r->setUsingSymbolLevels( usingSymbolLevels() );
r->setSizeScaleField( sizeScaleField() );
copyPaintEffect( r );
copyRendererData( r );
return r;
}
@ -761,6 +761,13 @@ QDomElement QgsCategorizedSymbolRendererV2::save( QDomDocument& doc )
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( "orderby" );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
return rendererElem;
}

View File

@ -529,7 +529,7 @@ QgsGraduatedSymbolRendererV2* QgsGraduatedSymbolRendererV2::clone() const
r->setSizeScaleField( sizeScaleField() );
r->setLabelFormat( labelFormat() );
r->setGraduatedMethod( graduatedMethod() );
copyPaintEffect( r );
copyRendererData( r );
return r;
}
@ -1171,6 +1171,13 @@ QDomElement QgsGraduatedSymbolRendererV2::save( QDomDocument& doc )
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( "orderby" );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
return rendererElem;
}

View File

@ -297,7 +297,7 @@ QgsHeatmapRenderer* QgsHeatmapRenderer::clone() const
newRenderer->setMaximumValue( mExplicitMax );
newRenderer->setRenderQuality( mRenderQuality );
newRenderer->setWeightExpression( mWeightExpressionString );
copyPaintEffect( newRenderer );
copyRendererData( newRenderer );
return newRenderer;
}
@ -365,6 +365,13 @@ QDomElement QgsHeatmapRenderer::save( QDomDocument& doc )
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( "orderby" );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
return rendererElem;
}

View File

@ -344,7 +344,7 @@ QgsInvertedPolygonRenderer* QgsInvertedPolygonRenderer::clone() const
newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data() );
}
newRenderer->setPreprocessingEnabled( preprocessingEnabled() );
copyPaintEffect( newRenderer );
copyRendererData( newRenderer );
return newRenderer;
}

View File

@ -80,7 +80,7 @@ QgsPointDisplacementRenderer* QgsPointDisplacementRenderer::clone() const
{
r->setCenterSymbol( mCenterSymbol->clone() );
}
copyPaintEffect( r );
copyRendererData( r );
return r;
}

View File

@ -204,6 +204,15 @@ void QgsFeatureRendererV2::setScaleMethodToSymbol( QgsSymbolV2* symbol, int scal
}
}
void QgsFeatureRendererV2::copyRendererData( QgsFeatureRendererV2* destRenderer ) const
{
if ( !destRenderer || !mPaintEffect )
return;
destRenderer->setPaintEffect( mPaintEffect->clone() );
destRenderer->mOrderBy = mOrderBy;
}
void QgsFeatureRendererV2::copyPaintEffect( QgsFeatureRendererV2 *destRenderer ) const
{
if ( !destRenderer || !mPaintEffect )
@ -212,7 +221,6 @@ void QgsFeatureRendererV2::copyPaintEffect( QgsFeatureRendererV2 *destRenderer )
destRenderer->setPaintEffect( mPaintEffect->clone() );
}
QgsFeatureRendererV2::QgsFeatureRendererV2( const QString& type )
: mType( type )
, mUsingSymbolLevels( false )
@ -331,6 +339,10 @@ QgsFeatureRendererV2* QgsFeatureRendererV2::load( QDomElement& element )
{
r->setPaintEffect( QgsPaintEffectRegistry::instance()->createEffect( effectElem ) );
}
// restore order by
QDomElement orderByElem = element.firstChildElement( "orderby" );
r->mOrderBy.load( orderByElem );
}
return r;
}
@ -344,6 +356,12 @@ QDomElement QgsFeatureRendererV2::save( QDomDocument& doc )
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( "orderby" );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
return rendererElem;
}
@ -598,6 +616,16 @@ void QgsFeatureRendererV2::setPaintEffect( QgsPaintEffect *effect )
mPaintEffect = effect;
}
QgsFeatureRequest::OrderBy QgsFeatureRendererV2::orderBy() const
{
return mOrderBy;
}
void QgsFeatureRendererV2::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy )
{
mOrderBy = orderBy;
}
void QgsFeatureRendererV2::convertSymbolSizeScale( QgsSymbolV2 * symbol, QgsSymbolV2::ScaleMethod method, const QString & field )
{
if ( symbol->type() == QgsSymbolV2::Marker )

View File

@ -21,6 +21,7 @@
#include "qgsrendercontext.h"
#include "qgssymbolv2.h"
#include "qgsfield.h"
#include "qgsfeaturerequest.h"
#include <QList>
#include <QString>
@ -343,6 +344,18 @@ class CORE_EXPORT QgsFeatureRendererV2
*/
void setForceRasterRender( bool forceRaster ) { mForceRaster = forceRaster; }
/**
* Get the order in which features shall be processed by this renderer.
* @note added in QGIS 2.14
*/
QgsFeatureRequest::OrderBy orderBy() const;
/**
* Define the order in which features shall be processed by this renderer.
* @note added in QGIS 2.14
*/
void setOrderBy( const QgsFeatureRequest::OrderBy& orderBy );
protected:
QgsFeatureRendererV2( const QString& type );
@ -366,10 +379,21 @@ class CORE_EXPORT QgsFeatureRendererV2
void setScaleMethodToSymbol( QgsSymbolV2* symbol, int scaleMethod );
/** Copies paint effect of this renderer to another renderer
/**
* Clones generic renderer data to another renderer.
* Currently clones
* * Order By
* * Paint Effect
*
* @param destRenderer destination renderer for copied effect
*/
void copyPaintEffect( QgsFeatureRendererV2 *destRenderer ) const;
void copyRendererData( QgsFeatureRendererV2 *destRenderer ) const;
/** Copies paint effect of this renderer to another renderer
* @param destRenderer destination renderer for copied effect
* @deprecated use copyRendererData instead
*/
Q_DECL_DEPRECATED void copyPaintEffect( QgsFeatureRendererV2 *destRenderer ) const;
QString mType;
@ -393,6 +417,8 @@ class CORE_EXPORT QgsFeatureRendererV2
*/
static void convertSymbolRotation( QgsSymbolV2 * symbol, const QString & field );
QgsFeatureRequest::OrderBy mOrderBy;
private:
Q_DISABLE_COPY( QgsFeatureRendererV2 )
};

View File

@ -911,7 +911,7 @@ QgsRuleBasedRendererV2* QgsRuleBasedRendererV2::clone() const
QgsRuleBasedRendererV2* r = new QgsRuleBasedRendererV2( clonedRoot );
r->setUsingSymbolLevels( usingSymbolLevels() );
copyPaintEffect( r );
copyRendererData( r );
return r;
}

View File

@ -201,7 +201,7 @@ QgsSingleSymbolRendererV2* QgsSingleSymbolRendererV2::clone() const
QgsSingleSymbolRendererV2* r = new QgsSingleSymbolRendererV2( mSymbol->clone() );
r->setUsingSymbolLevels( usingSymbolLevels() );
r->setSizeScaleField( sizeScaleField() );
copyPaintEffect( r );
copyRendererData( r );
return r;
}
@ -381,6 +381,12 @@ QDomElement QgsSingleSymbolRendererV2::save( QDomDocument& doc )
if ( mPaintEffect && !QgsPaintEffectRegistry::isDefaultStack( mPaintEffect ) )
mPaintEffect->saveProperties( doc, rendererElem );
if ( !mOrderBy.isEmpty() )
{
QDomElement orderBy = doc.createElement( "orderby" );
mOrderBy.save( orderBy );
rendererElem.appendChild( orderBy );
}
return rendererElem;
}

View File

@ -238,6 +238,7 @@ SET(QGIS_GUI_SRCS
qgsnewvectorlayerdialog.cpp
qgsnumericsortlistviewitem.cpp
qgsoptionsdialogbase.cpp
qgsorderbydialog.cpp
qgsowssourceselect.cpp
qgspixmaplabel.cpp
qgspluginmanagerinterface.cpp
@ -367,6 +368,7 @@ SET(QGIS_GUI_MOC_HDRS
qgsnewnamedialog.h
qgsnewvectorlayerdialog.h
qgsoptionsdialogbase.h
qgsorderbydialog.h
qgsowssourceselect.h
qgspixmaplabel.h
qgspluginmanagerinterface.h

View File

@ -79,7 +79,11 @@ class GUI_EXPORT QgsFieldExpressionWidget : public QWidget
*/
bool isValidExpression( QString *expressionError = nullptr ) const;
/**
* If the content is not just a simple field this method will return true.
*/
bool isExpression() const;
/**
* Return the current text that is set in the expression area
*/

View File

@ -0,0 +1,147 @@
/***************************************************************************
qgosorderbydialog.cpp
---------------------
begin : 20.12.2015
copyright : (C) 2015 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* 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 "qgsorderbydialog.h"
#include "qgsexpressionbuilderdialog.h"
#include "qgsfieldexpressionwidget.h"
#include <QTableWidget>
#include <QKeyEvent>
QgsOrderByDialog::QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent )
: QDialog( parent )
, mLayer( layer )
{
setupUi( this );
mOrderByTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch );
mOrderByTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::ResizeToContents );
mOrderByTableWidget->horizontalHeader()->setResizeMode( 2, QHeaderView::ResizeToContents );
mOrderByTableWidget->installEventFilter( this );
}
void QgsOrderByDialog::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy )
{
mOrderByTableWidget->setRowCount( orderBy.length() + 1 );
int i = 0;
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderByClause, orderBy )
{
setRow( i, orderByClause );
++i;
}
// Add an empty widget at the end
setRow( i, QgsFeatureRequest::OrderByClause( "" ) );
}
QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy()
{
QgsFeatureRequest::OrderBy orderBy;
for ( int i = 0; i < mOrderByTableWidget->rowCount(); ++i )
{
QString expressionText = static_cast<QgsFieldExpressionWidget*>( mOrderByTableWidget->cellWidget( i, 0 ) )->currentText();
if ( ! expressionText.isEmpty() )
{
bool asc = true;
int ascIndex = static_cast<QComboBox*>( mOrderByTableWidget->cellWidget( i, 1 ) )->currentIndex();
if ( ascIndex == 1 )
asc = false;
bool nullsFirst = false;
int nullsFirstIndex = static_cast<QComboBox*>( mOrderByTableWidget->cellWidget( i, 2 ) )->currentIndex();
if ( nullsFirstIndex == 1 )
nullsFirst = true;
QgsFeatureRequest::OrderByClause orderByClause( expressionText, asc, nullsFirst );
orderBy << orderByClause;
}
}
return orderBy;
}
void QgsOrderByDialog::onExpressionChanged( const QString& expression )
{
// The sender() is the field widget which is the cell widget of the first column
int row;
for ( row = 0; row < mOrderByTableWidget->rowCount(); ++row )
{
if ( mOrderByTableWidget->cellWidget( row, 0 ) == sender() )
{
break;
}
}
if ( expression.isEmpty() && row != mOrderByTableWidget->rowCount() - 1 )
{
mOrderByTableWidget->removeRow( row );
}
else if ( !expression.isEmpty() && row == mOrderByTableWidget->rowCount() - 1 )
{
mOrderByTableWidget->insertRow( mOrderByTableWidget->rowCount() );
setRow( row + 1, QgsFeatureRequest::OrderByClause( "" ) );
}
}
void QgsOrderByDialog::setRow( int row, const QgsFeatureRequest::OrderByClause& orderByClause )
{
QgsFieldExpressionWidget* fieldExpression = new QgsFieldExpressionWidget();
fieldExpression->setLayer( mLayer );
fieldExpression->setField( orderByClause.expression().expression() );
connect( fieldExpression, SIGNAL( fieldChanged( QString ) ), this, SLOT( onExpressionChanged( QString ) ) );
QComboBox* ascComboBox = new QComboBox();
ascComboBox->addItem( tr( "Ascending" ) );
ascComboBox->addItem( tr( "Descencing" ) );
ascComboBox->setCurrentIndex( orderByClause.ascending() ? 0 : 1 );
QComboBox* nullsFirstComboBox = new QComboBox();
nullsFirstComboBox->addItem( tr( "NULLs last" ) );
nullsFirstComboBox->addItem( tr( "NULLs first" ) );
nullsFirstComboBox->setCurrentIndex( orderByClause.nullsFirst() ? 1 : 0 );
mOrderByTableWidget->setCellWidget( row, 0, fieldExpression );
mOrderByTableWidget->setCellWidget( row, 1, ascComboBox );
mOrderByTableWidget->setCellWidget( row, 2, nullsFirstComboBox );
}
bool QgsOrderByDialog::eventFilter( QObject* obj, QEvent* e )
{
Q_UNUSED( obj )
Q_ASSERT( obj == mOrderByTableWidget );
if ( e->type() == QEvent::KeyPress )
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>( e );
if ( keyEvent->key() == Qt::Key_Delete )
{
if ( mOrderByTableWidget->currentRow() != mOrderByTableWidget->rowCount() - 1 )
mOrderByTableWidget->removeRow( mOrderByTableWidget->currentRow() );
return true;
}
}
return false;
}

View File

@ -0,0 +1,69 @@
/***************************************************************************
qgsorderbydialog.h
---------------------
begin : 20.12.2015
copyright : (C) 2015 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* 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 QGSORDERBYDIALOG_H
#define QGSORDERBYDIALOG_H
#include <QDialog>
#include "qgsfeaturerequest.h"
#include "ui_qgsorderbydialogbase.h"
/**
* This is a dialog to build and manage a list of order by clauses.
*
* @note added in 2.14
*/
class GUI_EXPORT QgsOrderByDialog : public QDialog, private Ui::OrderByDialogBase
{
Q_OBJECT
public:
/**
* Create a new order by dialog. This helps building order by structures.
*
* @param layer The vector layer for which the order by should be produced
* @param parent The parent widget, optional
*/
QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent = nullptr );
/**
* Set the order by to manage
*/
void setOrderBy( const QgsFeatureRequest::OrderBy& orderBy );
/**
* Get the order by defined in the dialog
*/
QgsFeatureRequest::OrderBy orderBy();
private slots:
void onExpressionChanged( const QString& expression );
private:
/**
* Initialize a row with the given information
*/
void setRow( int row, const QgsFeatureRequest::OrderByClause& orderByClause );
QgsVectorLayer* mLayer;
bool eventFilter( QObject *obj, QEvent *e );
};
#endif // QGSORDERBYDIALOG_H

View File

@ -25,6 +25,7 @@
#include "qgspointdisplacementrendererwidget.h"
#include "qgsinvertedpolygonrendererwidget.h"
#include "qgsheatmaprendererwidget.h"
#include "qgsorderbydialog.h"
#include "qgsapplication.h"
#include "qgslogger.h"
@ -109,13 +110,17 @@ QgsRendererV2PropertiesDialog::QgsRendererV2PropertiesDialog( QgsVectorLayer* la
connect( mLayerTransparencySpnBx, SIGNAL( valueChanged( int ) ), mLayerTransparencySlider, SLOT( setValue( int ) ) );
//paint effect widget
if ( mLayer->rendererV2() && mLayer->rendererV2()->paintEffect() )
if ( mLayer->rendererV2() )
{
mPaintEffect = mLayer->rendererV2()->paintEffect()->clone();
mEffectWidget->setPaintEffect( mPaintEffect );
if ( mLayer->rendererV2()->paintEffect() )
{
mPaintEffect = mLayer->rendererV2()->paintEffect()->clone();
mEffectWidget->setPaintEffect( mPaintEffect );
}
mOrderBy = mLayer->rendererV2()->orderBy();
}
QPixmap pix;
QgsRendererV2Registry* reg = QgsRendererV2Registry::instance();
QStringList renderers = reg->renderersList();
Q_FOREACH ( const QString& name, renderers )
@ -128,21 +133,31 @@ QgsRendererV2PropertiesDialog::QgsRendererV2PropertiesDialog( QgsVectorLayer* la
// setup slot rendererChanged()
connect( cboRenderers, SIGNAL( currentIndexChanged( int ) ), this, SLOT( rendererChanged() ) );
//setup order by
if ( mOrderBy.isEmpty() )
{
btnOrderBy->setEnabled( false );
checkboxEnableOrderBy->setChecked( false );
lineEditOrderBy->setEnabled( false );
}
else
{
checkboxEnableOrderBy->setChecked( true );
}
lineEditOrderBy->setReadOnly( true );
connect( checkboxEnableOrderBy, SIGNAL( toggled( bool ) ), btnOrderBy, SLOT( setEnabled( bool ) ) );
connect( checkboxEnableOrderBy, SIGNAL( toggled( bool ) ), lineEditOrderBy, SLOT( setEnabled( bool ) ) );
connect( btnOrderBy, SIGNAL( clicked( bool ) ), this, SLOT( showOrderByDialog() ) );
lineEditOrderBy->setText( mOrderBy.dump() );
// set current renderer from layer
QString rendererName = mLayer->rendererV2()->type();
for ( int i = 0; i < cboRenderers->count(); i++ )
{
if ( cboRenderers->itemData( i ).toString() == rendererName )
{
cboRenderers->setCurrentIndex( i );
return;
}
}
int rendererIdx = cboRenderers->findData( rendererName );
cboRenderers->setCurrentIndex( rendererIdx );
// no renderer found... this mustn't happen
Q_ASSERT( false && "there must be a renderer!" );
Q_ASSERT( rendererIdx != -1 && "there must be a renderer!" );
}
QgsRendererV2PropertiesDialog::~QgsRendererV2PropertiesDialog()
@ -223,6 +238,9 @@ void QgsRendererV2PropertiesDialog::apply()
if ( renderer )
{
renderer->setPaintEffect( mPaintEffect->clone() );
// set the order by
renderer->setOrderBy( mOrderBy );
mLayer->setRendererV2( renderer->clone() );
}
@ -240,6 +258,18 @@ void QgsRendererV2PropertiesDialog::onOK()
accept();
}
void QgsRendererV2PropertiesDialog::showOrderByDialog()
{
QgsOrderByDialog dlg( mLayer );
dlg.setOrderBy( mOrderBy );
if ( dlg.exec() )
{
mOrderBy = dlg.orderBy();
lineEditOrderBy->setText( mOrderBy.dump() );
}
}
void QgsRendererV2PropertiesDialog::keyPressEvent( QKeyEvent * e )
{

View File

@ -21,6 +21,8 @@
#include "ui_qgsrendererv2propsdialogbase.h"
#include "qgsfeaturerequest.h"
class QKeyEvent;
class QgsVectorLayer;
@ -52,6 +54,9 @@ class GUI_EXPORT QgsRendererV2PropertiesDialog : public QDialog, private Ui::Qgs
void apply();
void onOK();
private slots:
void showOrderByDialog();
protected:
//! Reimplements dialog keyPress event so we can ignore it
@ -66,6 +71,8 @@ class GUI_EXPORT QgsRendererV2PropertiesDialog : public QDialog, private Ui::Qgs
QgsPaintEffect* mPaintEffect;
QgsMapCanvas* mMapCanvas;
QgsFeatureRequest::OrderBy mOrderBy;
};

View File

@ -197,7 +197,7 @@ void QgsMssqlFeatureIterator::BuildStatement( const QgsFeatureRequest& request )
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
{
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBys() )
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBy() )
{
if (( clause.ascending() && !clause.nullsFirst() ) || ( !clause.ascending() && clause.nullsFirst() ) )
{

View File

@ -243,7 +243,7 @@ bool QgsOgrFeatureIterator::rewind()
bool QgsOgrFeatureIterator::close()
{
if ( mClosed )
if ( !mConn )
return false;
iteratorClosed();
@ -253,7 +253,9 @@ bool QgsOgrFeatureIterator::close()
OGR_DS_ReleaseResultSet( mConn->ds, ogrLayer );
}
QgsOgrConnPool::instance()->releaseConnection( mConn );
if ( mConn )
QgsOgrConnPool::instance()->releaseConnection( mConn );
mConn = nullptr;
mClosed = true;

View File

@ -118,7 +118,7 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
{
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBys() )
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBy() )
{
QgsPostgresExpressionCompiler compiler = QgsPostgresExpressionCompiler( source );
QgsExpression expression = clause.expression();
@ -313,7 +313,7 @@ bool QgsPostgresFeatureIterator::rewind()
bool QgsPostgresFeatureIterator::close()
{
if ( mClosed )
if ( !mConn )
return false;
mConn->closeCursor( mCursorName );

View File

@ -123,7 +123,7 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
if ( QSettings().value( "/qgis/compileExpressions", true ).toBool() )
{
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBys() )
Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, request.orderBy() )
{
QgsSpatiaLiteExpressionCompiler compiler = QgsSpatiaLiteExpressionCompiler( source );
QgsExpression expression = clause.expression();
@ -235,7 +235,7 @@ bool QgsSpatiaLiteFeatureIterator::rewind()
bool QgsSpatiaLiteFeatureIterator::close()
{
if ( mClosed )
if ( !mHandle )
return false;
iteratorClosed();

View File

@ -188,6 +188,7 @@ private:
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
case QVariant::LongLong:
typeName = "int";
break;
case QVariant::Double:
@ -588,6 +589,9 @@ int vtable_column( sqlite3_vtab_cursor *cursor, sqlite3_context* ctxt, int idx )
case QVariant::UInt:
sqlite3_result_int( ctxt, v.toInt() );
break;
case QVariant::LongLong:
sqlite3_result_int64( ctxt, v.toLongLong() );
break;
case QVariant::Double:
sqlite3_result_double( ctxt, v.toDouble() );
break;

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OrderByDialogBase</class>
<widget class="QDialog" name="OrderByDialogBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>747</width>
<height>296</height>
</rect>
</property>
<property name="windowTitle">
<string>Define order</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QTableWidget" name="mOrderByTableWidget">
<column>
<property name="text">
<string>Expression</string>
</property>
</column>
<column>
<property name="text">
<string>Asc / Desc</string>
</property>
</column>
<column>
<property name="text">
<string>NULLs handling</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OrderByDialogBase</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OrderByDialogBase</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -85,6 +85,16 @@
<bool>true</bool>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0" colspan="4">
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
@ -133,15 +143,30 @@
<item row="1" column="1">
<widget class="QgsBlendModeComboBox" name="mBlendModeComboBox"/>
</item>
<item row="2" column="0" colspan="2">
<widget class="QgsEffectStackCompactWidget" name="mEffectWidget" native="true">
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
<item row="3" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="checkboxEnableOrderBy">
<property name="text">
<string>Control feature rendering order</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditOrderBy">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btnOrderBy">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -54,6 +54,7 @@ ADD_PYTHON_TEST(PyQgsRasterLayer test_qgsrasterlayer.py)
ADD_PYTHON_TEST(PyQgsRectangle test_qgsrectangle.py)
ADD_PYTHON_TEST(PyQgsRelation test_qgsrelation.py)
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)

View File

@ -185,6 +185,11 @@ class ProviderTestCase(object):
values = [f['pk'] for f in self.provider.getFeatures(request)]
self.assertEquals(values, [5, 4])
# Combination with subset of attributes
request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes(['pk'], self.vl.fields())
values = [f['pk'] for f in self.vl.getFeatures(request)]
self.assertEquals(values, [5, 4, 3, 2, 1])
def testGetFeaturesFidTests(self):
fids = [f.id() for f in self.provider.getFeatures()]
assert len(fids) == 5, 'Expected 5 features, got {} instead'.format(len(fids))

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
test_qgssinglesymbolrenderer.py
---------------------
Date : December 2015
Copyright : (C) 2015 by Matthias Kuhn
Email : matthias at opengis dot ch
***************************************************************************
* *
* 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. *
* *
***************************************************************************
"""
__author__ = 'Matthias Kuhn'
__date__ = 'December 2015'
__copyright__ = '(C) 2015, Matthiasd Kuhn'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis
import os
from PyQt4.QtCore import QSize
from qgis.core import (QgsVectorLayer,
QgsMapLayerRegistry,
QgsRectangle,
QgsMultiRenderChecker,
QgsSingleSymbolRendererV2,
QgsFillSymbolV2,
QgsMarkerSymbolV2,
QgsRendererCategoryV2,
QgsCategorizedSymbolRendererV2,
QgsGraduatedSymbolRendererV2,
QgsRendererRangeV2,
QgsFeatureRequest
)
from utilities import (unitTestDataPath,
getQgisTestApp,
TestCase,
unittest
)
# Convenience instances in case you may need them
# not used in this test
QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsSingleSymbolRenderer(TestCase):
def setUp(self):
myShpFile = os.path.join(TEST_DATA_DIR, 'polys_overlapping.shp')
layer = QgsVectorLayer(myShpFile, 'Polys', 'ogr')
QgsMapLayerRegistry.instance().addMapLayer(layer)
# Create rulebased style
sym1 = QgsFillSymbolV2.createSimple({'color': '#fdbf6f'})
self.renderer = QgsSingleSymbolRendererV2(sym1)
layer.setRendererV2(self.renderer)
rendered_layers = [layer.id()]
self.mapsettings = CANVAS.mapSettings()
self.mapsettings.setOutputSize(QSize(400, 400))
self.mapsettings.setOutputDpi(96)
self.mapsettings.setExtent(QgsRectangle(-163, 22, -70, 52))
self.mapsettings.setLayers(rendered_layers)
def tearDown(self):
QgsMapLayerRegistry.instance().removeAllMapLayers()
def testOrderBy(self):
self.renderer.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('Value', False)]))
# Setup rendering check
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(self.mapsettings)
renderchecker.setControlName('expected_singlesymbol_orderby')
result = renderchecker.runTest('singlesymbol_orderby')
assert result
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB