diff --git a/python/core/qgsfeaturerequest.sip b/python/core/qgsfeaturerequest.sip index c44fe1c5894..398ddae52da 100644 --- a/python/core/qgsfeaturerequest.sip +++ b/python/core/qgsfeaturerequest.sip @@ -78,6 +78,42 @@ class QgsFeatureRequest }; + class OrderBy + { + public: + OrderBy(); + + OrderBy( const QList& 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 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 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 orderBys() const; + QgsFeatureRequest::OrderBy orderBy() const; /** * Set a list of order by clauses. */ - void setOrderBys(const QList& 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. diff --git a/python/core/symbology-ng/qgsrendererv2.sip b/python/core/symbology-ng/qgsrendererv2.sip index efa8a563bfc..92e1beb693e 100644 --- a/python/core/symbology-ng/qgsrendererv2.sip +++ b/python/core/symbology-ng/qgsrendererv2.sip @@ -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 ); diff --git a/python/gui/qgsorderbydialog.sip b/python/gui/qgsorderbydialog.sip new file mode 100644 index 00000000000..9b90d5f157b --- /dev/null +++ b/python/gui/qgsorderbydialog.sip @@ -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& orderBys ); + + /** + * Get the order by defined in the dialog + */ + QgsFeatureRequest::OrderBy orderBys(); +}; diff --git a/src/core/qgsfeatureiterator.cpp b/src/core/qgsfeatureiterator.cpp index 5d16f822b87..43c730f333c 100644 --- a/src/core/qgsfeatureiterator.cpp +++ b/src/core/qgsfeatureiterator.cpp @@ -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 QListmClosed : true; + return mIter ? mIter->mClosed && !mIter->mZombie : true; } inline bool operator== ( const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2 ) diff --git a/src/core/qgsfeaturerequest.cpp b/src/core/qgsfeaturerequest.cpp index 5eff645315f..8cc6cded92d 100644 --- a/src/core/qgsfeaturerequest.cpp +++ b/src/core/qgsfeaturerequest.cpp @@ -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::orderBys() const +QgsFeatureRequest::OrderBy QgsFeatureRequest::orderBy() const { - return mOrderBys; + return mOrderBy; } -void QgsFeatureRequest::setOrderBys( const QList& 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& other ) +{ + Q_FOREACH ( const QgsFeatureRequest::OrderByClause& clause, other ) + { + append( clause ); + } +} + +QList QgsFeatureRequest::OrderBy::list() const +{ + return *this; +} + +void QgsFeatureRequest::OrderBy::save( QDomElement& elem ) const +{ + QDomDocument doc = elem.ownerDocument(); + QList::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 QgsFeatureRequest::OrderBy::usedAttributes() const +{ + QSet usedAttributes; + + QList::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::ConstIterator it; + for ( it = constBegin(); it != constEnd(); ++it ) + { + const OrderByClause& clause = *it; + + results << clause.dump(); + } + + return results.join( ", " ); +} diff --git a/src/core/qgsfeaturerequest.h b/src/core/qgsfeaturerequest.h index bd9db1117cf..ed15ee7297b 100644 --- a/src/core/qgsfeaturerequest.h +++ b/src/core/qgsfeaturerequest.h @@ -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 + { + public: + /** + * Create a new empty order by + */ + OrderBy() + : QList() + {} + + /** + * Create a new order by from a list of clauses + */ + OrderBy( const QList& 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 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 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 orderBys() const; + OrderBy orderBy() const; /** * Set a list of order by clauses. + * + * @note added in 2.14 */ - void setOrderBys( const QList& 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 mOrderBys; + OrderBy mOrderBy; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags ) diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp index 618794fd6e9..e3fbeec5c89 100644 --- a/src/core/qgsvectorlayerfeatureiterator.cpp +++ b/src/core/qgsvectorlayerfeatureiterator.cpp @@ -105,17 +105,30 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat if ( mProviderRequest.flags() & QgsFeatureRequest::SubsetOfAttributes ) { // prepare list of attributes to match provider fields - QgsAttributeList providerSubset; + QSet 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 ) diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp index 9c6fcd71f98..4145d2b9423 100644 --- a/src/core/qgsvectorlayerrenderer.cpp +++ b/src/core/qgsvectorlayerrenderer.cpp @@ -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 ) diff --git a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp index fc9af983ea1..c70c5e6458f 100644 --- a/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgscategorizedsymbolrendererv2.cpp @@ -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; } diff --git a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp index 0fa171f0131..2afc51bf795 100644 --- a/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgsgraduatedsymbolrendererv2.cpp @@ -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; } diff --git a/src/core/symbology-ng/qgsheatmaprenderer.cpp b/src/core/symbology-ng/qgsheatmaprenderer.cpp index 17ba1a26de1..84697008f6a 100644 --- a/src/core/symbology-ng/qgsheatmaprenderer.cpp +++ b/src/core/symbology-ng/qgsheatmaprenderer.cpp @@ -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; } diff --git a/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp b/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp index 2323185de20..9615605ab24 100644 --- a/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp +++ b/src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp @@ -344,7 +344,7 @@ QgsInvertedPolygonRenderer* QgsInvertedPolygonRenderer::clone() const newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data() ); } newRenderer->setPreprocessingEnabled( preprocessingEnabled() ); - copyPaintEffect( newRenderer ); + copyRendererData( newRenderer ); return newRenderer; } diff --git a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp index ac9f5923210..56a7694825b 100644 --- a/src/core/symbology-ng/qgspointdisplacementrenderer.cpp +++ b/src/core/symbology-ng/qgspointdisplacementrenderer.cpp @@ -80,7 +80,7 @@ QgsPointDisplacementRenderer* QgsPointDisplacementRenderer::clone() const { r->setCenterSymbol( mCenterSymbol->clone() ); } - copyPaintEffect( r ); + copyRendererData( r ); return r; } diff --git a/src/core/symbology-ng/qgsrendererv2.cpp b/src/core/symbology-ng/qgsrendererv2.cpp index c56016c0a1d..a9051834382 100644 --- a/src/core/symbology-ng/qgsrendererv2.cpp +++ b/src/core/symbology-ng/qgsrendererv2.cpp @@ -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 ) diff --git a/src/core/symbology-ng/qgsrendererv2.h b/src/core/symbology-ng/qgsrendererv2.h index cb0d33235fd..b14ba7ef5b2 100644 --- a/src/core/symbology-ng/qgsrendererv2.h +++ b/src/core/symbology-ng/qgsrendererv2.h @@ -21,6 +21,7 @@ #include "qgsrendercontext.h" #include "qgssymbolv2.h" #include "qgsfield.h" +#include "qgsfeaturerequest.h" #include #include @@ -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 ) }; diff --git a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp index a3c1525d790..ec0562b1860 100644 --- a/src/core/symbology-ng/qgsrulebasedrendererv2.cpp +++ b/src/core/symbology-ng/qgsrulebasedrendererv2.cpp @@ -911,7 +911,7 @@ QgsRuleBasedRendererV2* QgsRuleBasedRendererV2::clone() const QgsRuleBasedRendererV2* r = new QgsRuleBasedRendererV2( clonedRoot ); r->setUsingSymbolLevels( usingSymbolLevels() ); - copyPaintEffect( r ); + copyRendererData( r ); return r; } diff --git a/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp b/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp index 0915c4e0219..783a473964b 100644 --- a/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp +++ b/src/core/symbology-ng/qgssinglesymbolrendererv2.cpp @@ -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; } diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 3b5f3593aba..abf535ff96b 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -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 diff --git a/src/gui/qgsfieldexpressionwidget.h b/src/gui/qgsfieldexpressionwidget.h index 29da3693c11..a712406baec 100644 --- a/src/gui/qgsfieldexpressionwidget.h +++ b/src/gui/qgsfieldexpressionwidget.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 */ diff --git a/src/gui/qgsorderbydialog.cpp b/src/gui/qgsorderbydialog.cpp new file mode 100644 index 00000000000..9bc594ad954 --- /dev/null +++ b/src/gui/qgsorderbydialog.cpp @@ -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 +#include + +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( mOrderByTableWidget->cellWidget( i, 0 ) )->currentText(); + + if ( ! expressionText.isEmpty() ) + { + bool asc = true; + int ascIndex = static_cast( mOrderByTableWidget->cellWidget( i, 1 ) )->currentIndex(); + if ( ascIndex == 1 ) + asc = false; + + bool nullsFirst = false; + int nullsFirstIndex = static_cast( 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( e ); + + if ( keyEvent->key() == Qt::Key_Delete ) + { + if ( mOrderByTableWidget->currentRow() != mOrderByTableWidget->rowCount() - 1 ) + mOrderByTableWidget->removeRow( mOrderByTableWidget->currentRow() ); + return true; + } + } + + return false; +} + diff --git a/src/gui/qgsorderbydialog.h b/src/gui/qgsorderbydialog.h new file mode 100644 index 00000000000..34d69969bb8 --- /dev/null +++ b/src/gui/qgsorderbydialog.h @@ -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 + +#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 diff --git a/src/gui/symbology-ng/qgsrendererv2propertiesdialog.cpp b/src/gui/symbology-ng/qgsrendererv2propertiesdialog.cpp index be2c2a4959c..e76a6315046 100644 --- a/src/gui/symbology-ng/qgsrendererv2propertiesdialog.cpp +++ b/src/gui/symbology-ng/qgsrendererv2propertiesdialog.cpp @@ -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 ) { diff --git a/src/gui/symbology-ng/qgsrendererv2propertiesdialog.h b/src/gui/symbology-ng/qgsrendererv2propertiesdialog.h index d7622a15324..db22bcde245 100644 --- a/src/gui/symbology-ng/qgsrendererv2propertiesdialog.h +++ b/src/gui/symbology-ng/qgsrendererv2propertiesdialog.h @@ -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; }; diff --git a/src/providers/mssql/qgsmssqlfeatureiterator.cpp b/src/providers/mssql/qgsmssqlfeatureiterator.cpp index 791f01173ff..ac6ed8c7cbd 100644 --- a/src/providers/mssql/qgsmssqlfeatureiterator.cpp +++ b/src/providers/mssql/qgsmssqlfeatureiterator.cpp @@ -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() ) ) { diff --git a/src/providers/ogr/qgsogrfeatureiterator.cpp b/src/providers/ogr/qgsogrfeatureiterator.cpp index a56df327b35..3def9ffc1fd 100644 --- a/src/providers/ogr/qgsogrfeatureiterator.cpp +++ b/src/providers/ogr/qgsogrfeatureiterator.cpp @@ -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; diff --git a/src/providers/postgres/qgspostgresfeatureiterator.cpp b/src/providers/postgres/qgspostgresfeatureiterator.cpp index 5160085099c..33dad7140ef 100644 --- a/src/providers/postgres/qgspostgresfeatureiterator.cpp +++ b/src/providers/postgres/qgspostgresfeatureiterator.cpp @@ -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 ); diff --git a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp index b43a84878d9..a429eb34ddf 100644 --- a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp +++ b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp @@ -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(); diff --git a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp index 9121b285e92..8bbf1b15516 100644 --- a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp +++ b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp @@ -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; diff --git a/src/ui/qgsorderbydialogbase.ui b/src/ui/qgsorderbydialogbase.ui new file mode 100644 index 00000000000..44ba05cf6f8 --- /dev/null +++ b/src/ui/qgsorderbydialogbase.ui @@ -0,0 +1,83 @@ + + + OrderByDialogBase + + + + 0 + 0 + 747 + 296 + + + + Define order + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + Expression + + + + + Asc / Desc + + + + + NULLs handling + + + + + + + + + + buttonBox + accepted() + OrderByDialogBase + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + OrderByDialogBase + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui/qgsrendererv2propsdialogbase.ui b/src/ui/qgsrendererv2propsdialogbase.ui index 070c203e567..199be372770 100644 --- a/src/ui/qgsrendererv2propsdialogbase.ui +++ b/src/ui/qgsrendererv2propsdialogbase.ui @@ -85,6 +85,16 @@ true + + + + + 16 + 16 + + + + @@ -133,15 +143,30 @@ - - - - - 16 - 16 - - - + + + + + + Control feature rendering order + + + + + + + true + + + + + + + ... + + + + diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index cd25b16202a..ec29ed3bbd3 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -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) diff --git a/tests/src/python/providertestbase.py b/tests/src/python/providertestbase.py index 8931ed3ac79..04c39efb878 100644 --- a/tests/src/python/providertestbase.py +++ b/tests/src/python/providertestbase.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)) diff --git a/tests/src/python/test_qgssinglesymbolrenderer.py b/tests/src/python/test_qgssinglesymbolrenderer.py new file mode 100644 index 00000000000..d9303402af6 --- /dev/null +++ b/tests/src/python/test_qgssinglesymbolrenderer.py @@ -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() diff --git a/tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png b/tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png new file mode 100644 index 00000000000..3e8ac50a324 Binary files /dev/null and b/tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png differ