mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Merge pull request #2600 from m-kuhn/orderby-renderer
Allow definition of feature rendering order
This commit is contained in:
commit
e6a265c103
@ -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.
|
||||
|
@ -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 );
|
||||
|
||||
|
34
python/gui/qgsorderbydialog.sip
Normal file
34
python/gui/qgsorderbydialog.sip
Normal 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();
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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( ", " );
|
||||
}
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ QgsInvertedPolygonRenderer* QgsInvertedPolygonRenderer::clone() const
|
||||
newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data() );
|
||||
}
|
||||
newRenderer->setPreprocessingEnabled( preprocessingEnabled() );
|
||||
copyPaintEffect( newRenderer );
|
||||
copyRendererData( newRenderer );
|
||||
return newRenderer;
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ QgsPointDisplacementRenderer* QgsPointDisplacementRenderer::clone() const
|
||||
{
|
||||
r->setCenterSymbol( mCenterSymbol->clone() );
|
||||
}
|
||||
copyPaintEffect( r );
|
||||
copyRendererData( r );
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
};
|
||||
|
@ -911,7 +911,7 @@ QgsRuleBasedRendererV2* QgsRuleBasedRendererV2::clone() const
|
||||
QgsRuleBasedRendererV2* r = new QgsRuleBasedRendererV2( clonedRoot );
|
||||
|
||||
r->setUsingSymbolLevels( usingSymbolLevels() );
|
||||
copyPaintEffect( r );
|
||||
copyRendererData( r );
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
147
src/gui/qgsorderbydialog.cpp
Normal file
147
src/gui/qgsorderbydialog.cpp
Normal 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;
|
||||
}
|
||||
|
69
src/gui/qgsorderbydialog.h
Normal file
69
src/gui/qgsorderbydialog.h
Normal 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
|
@ -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 )
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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() ) )
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
83
src/ui/qgsorderbydialogbase.ui
Normal file
83
src/ui/qgsorderbydialogbase.ui
Normal 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>
|
@ -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>
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
90
tests/src/python/test_qgssinglesymbolrenderer.py
Normal file
90
tests/src/python/test_qgssinglesymbolrenderer.py
Normal 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()
|
BIN
tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png
vendored
Normal file
BIN
tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 460 KiB |
Loading…
x
Reference in New Issue
Block a user