From a5f88181f69515f9636ff8db58d43509c533c5fb Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sun, 20 Dec 2015 20:07:53 +0100 Subject: [PATCH 01/10] Fix fallback orderby and attribute subset Also properly closes the iterator when a fallback orderby is used --- python/core/qgsfeaturerequest.sip | 28 +++++++- src/core/qgsfeatureiterator.cpp | 7 ++ src/core/qgsfeaturerequest.cpp | 75 +++++++++++++++++++++- src/core/qgsfeaturerequest.h | 45 ++++++++++++- src/core/qgsvectorlayerfeatureiterator.cpp | 21 ++++-- 5 files changed, 165 insertions(+), 11 deletions(-) diff --git a/python/core/qgsfeaturerequest.sip b/python/core/qgsfeaturerequest.sip index c44fe1c5894..49720b0828f 100644 --- a/python/core/qgsfeaturerequest.sip +++ b/python/core/qgsfeaturerequest.sip @@ -78,6 +78,30 @@ class QgsFeatureRequest }; + class OrderBy + { + public: + /** + * 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 +199,12 @@ class QgsFeatureRequest /** * Return a list of order by clauses specified for this feature request. */ - QList orderBys() const; + QgsFeatureRequest::OrderBy orderBys() const; /** * Set a list of order by clauses. */ - void setOrderBys(const QList& orderBys ); + QgsFeatureRequest& setOrderBys(const QgsFeatureRequest::OrderBy& orderBys ); /** Set the maximum number of features to request. * @param limit maximum number of features, or -1 to request all features. diff --git a/src/core/qgsfeatureiterator.cpp b/src/core/qgsfeatureiterator.cpp index 5d16f822b87..94b06fc2b3b 100644 --- a/src/core/qgsfeatureiterator.cpp +++ b/src/core/qgsfeatureiterator.cpp @@ -149,6 +149,12 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f ) ++mFeatureIterator; dataOk = true; } + else + { + dataOk = false; + // don't call close, the provider connection has already been closed + mClosed = true; + } } else { @@ -283,6 +289,7 @@ void QgsAbstractFeatureIterator::setupOrderBy( const QList QgsFeatureRequest::orderBys() const +QgsFeatureRequest::OrderBy QgsFeatureRequest::orderBys() const { return mOrderBys; } -void QgsFeatureRequest::setOrderBys( const QList& orderBys ) +QgsFeatureRequest& QgsFeatureRequest::setOrderBys( const QgsFeatureRequest::OrderBy& orderBys ) { mOrderBys = orderBys; + return *this; } QgsFeatureRequest& QgsFeatureRequest::setLimit( long limit ) @@ -307,7 +308,77 @@ 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; } + +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.toText().data(); + 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..1a0b4842459 100644 --- a/src/core/qgsfeaturerequest.h +++ b/src/core/qgsfeaturerequest.h @@ -155,12 +155,47 @@ 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: + /** + * 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 +312,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 orderBys() const; /** * Set a list of order by clauses. + * + * @note added in 2.14 */ - void setOrderBys( const QList& orderBys ); + QgsFeatureRequest& setOrderBys( const OrderBy& orderBys ); /** Set the maximum number of features to request. * @param limit maximum number of features, or -1 to request all features. @@ -346,7 +385,7 @@ class CORE_EXPORT QgsFeatureRequest QgsAttributeList mAttrs; QgsSimplifyMethod mSimplifyMethod; long mLimit; - QList mOrderBys; + OrderBy mOrderBys; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags ) diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp index 618794fd6e9..1dc2341c31c 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.orderBys().isEmpty() ) + { + Q_FOREACH ( const QString& attr, mProviderRequest.orderBys().usedAttributes() ) + { + providerSubset << mSource->mFields.fieldNameIndex( attr ); + } + } + + mProviderRequest.setSubsetOfAttributes( providerSubset.toList() ); } if ( mProviderRequest.filterType() == QgsFeatureRequest::FilterExpression ) From 73ba0e805ac720e245a5e4308a52e8bd19d11f03 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 10:41:35 +0100 Subject: [PATCH 02/10] [FEATURE] Allow definition of rendering order for renderers This allows defining the order in which features are processed by renderers. --- python/gui/qgsorderbydialog.sip | 34 ++++ src/core/qgsfeaturerequest.cpp | 2 +- src/core/qgsvectorlayerrenderer.cpp | 5 +- .../qgscategorizedsymbolrendererv2.cpp | 9 +- .../qgsgraduatedsymbolrendererv2.cpp | 9 +- src/core/symbology-ng/qgsheatmaprenderer.cpp | 9 +- src/core/symbology-ng/qgsrendererv2.cpp | 30 ++- src/core/symbology-ng/qgsrendererv2.h | 30 ++- .../qgssinglesymbolrendererv2.cpp | 8 +- src/gui/CMakeLists.txt | 2 + src/gui/qgsorderbydialog.cpp | 178 ++++++++++++++++++ src/gui/qgsorderbydialog.h | 65 +++++++ .../qgsrendererv2propertiesdialog.cpp | 58 ++++-- .../qgsrendererv2propertiesdialog.h | 7 + src/ui/qgsorderbydialogbase.ui | 89 +++++++++ src/ui/qgsrendererv2propsdialogbase.ui | 43 ++++- 16 files changed, 546 insertions(+), 32 deletions(-) create mode 100644 python/gui/qgsorderbydialog.sip create mode 100644 src/gui/qgsorderbydialog.cpp create mode 100644 src/gui/qgsorderbydialog.h create mode 100644 src/ui/qgsorderbydialogbase.ui 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/qgsfeaturerequest.cpp b/src/core/qgsfeaturerequest.cpp index ee0f2f2092d..c9afc31b79b 100644 --- a/src/core/qgsfeaturerequest.cpp +++ b/src/core/qgsfeaturerequest.cpp @@ -346,7 +346,7 @@ void QgsFeatureRequest::OrderBy::load( const QDomElement& elem ) for ( int i = 0; i < clauses.size(); ++i ) { QDomElement clauseElem = clauses.at( i ).toElement(); - QString expression = clauseElem.toText().data(); + QString expression = clauseElem.text(); bool asc = clauseElem.attribute( "asc" ).toInt() != 0; bool nullsFirst = clauseElem.attribute( "nullsFirst" ).toInt() != 0; diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp index 9c6fcd71f98..80468a4c3ec 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() ) + .setOrderBys( 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/qgsrendererv2.cpp b/src/core/symbology-ng/qgsrendererv2.cpp index c56016c0a1d..2eb455a14b4 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() +{ + return mOrderBy; +} + +void QgsFeatureRendererV2::setOrderBy( const QgsFeatureRequest::OrderBy& orderBys ) +{ + mOrderBy = orderBys; +} + 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..e0314b6b5c3 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(); + + /** + * Define the order in which features shall be processed by this renderer. + * @note added in QGIS 2.14 + */ + void setOrderBy( const QgsFeatureRequest::OrderBy& orderBys ); + 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/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/qgsorderbydialog.cpp b/src/gui/qgsorderbydialog.cpp new file mode 100644 index 00000000000..05878070143 --- /dev/null +++ b/src/gui/qgsorderbydialog.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + 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 +#include +#include + +QgsOrderByDialog::QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent ) + : QDialog( parent ) + , mLayer( layer ) +{ + setupUi( this ); + connect( mOrderByTableWidget, SIGNAL( cellDoubleClicked( int, int ) ), this, SLOT( onCellDoubleClicked( int, int ) ) ); + connect( mOrderByTableWidget, SIGNAL( cellChanged( int, int ) ), this, SLOT( onCellChanged( int, int ) ) ); + + mOrderByTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch ); + mOrderByTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::Interactive ); + mOrderByTableWidget->horizontalHeader()->setResizeMode( 2, QHeaderView::Interactive ); + + mOrderByTableWidget->installEventFilter( this ); +} + +void QgsOrderByDialog::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) +{ + mOrderByTableWidget->clear(); + mOrderByTableWidget->setRowCount( orderBy.length() + 1 ); + + int i = 0; + Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderByClause, orderBy ) + { + QTableWidgetItem* expressionItem = new QTableWidgetItem( orderByClause.expression().expression() ); + QCheckBox* ascCheckBox = new QCheckBox(); + ascCheckBox->setChecked( orderByClause.ascending() ); + QCheckBox* nullsFirstCheckBox = new QCheckBox(); + nullsFirstCheckBox->setChecked( orderByClause.nullsFirst() ); + + mOrderByTableWidget->setItem( i, 0, expressionItem ); + mOrderByTableWidget->setCellWidget( i, 1, ascCheckBox ); + mOrderByTableWidget->setCellWidget( i, 2, nullsFirstCheckBox ); + + ++i; + } + + // Add an empty widget at the end + QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); + QCheckBox* ascCheckBox = new QCheckBox(); + ascCheckBox->setChecked( true ); + QCheckBox* nullsFirstCheckBox = new QCheckBox(); + + mOrderByTableWidget->setItem( i, 0, expressionItem ); + mOrderByTableWidget->setCellWidget( i, 1, ascCheckBox ); + mOrderByTableWidget->setCellWidget( i, 2, nullsFirstCheckBox ); +} + +QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() +{ + QgsFeatureRequest::OrderBy orderBys; + + for ( int i = 0; i < mOrderByTableWidget->rowCount(); ++i ) + { + QString expressionText = mOrderByTableWidget->item( i, 0 )->text(); + + if ( ! expressionText.isEmpty() ) + { + bool asc = static_cast( mOrderByTableWidget->cellWidget( i, 1 ) )->checkState(); + bool nullsFirst = static_cast( mOrderByTableWidget->cellWidget( i, 2 ) )->checkState(); + QgsFeatureRequest::OrderByClause orderBy( expressionText, asc, nullsFirst ); + + orderBys << orderBy; + } + } + + return orderBys; +} + +void QgsOrderByDialog::onCellDoubleClicked( int row, int column ) +{ + // Only act on first cell where the expression text is + if ( 0 == column ) + { + QgsExpressionBuilderDialog dlg( mLayer ); + + dlg.setExpressionText( mOrderByTableWidget->item( row, column )->text() ); + + if ( dlg.exec() ) + { + QString expressionText = dlg.expressionText(); + + mOrderByTableWidget->item( row, column )->setText( expressionText ); + + if ( row == mOrderByTableWidget->rowCount() - 1 ) + { + // Add an empty widget at the end if the last row was edited + mOrderByTableWidget->insertRow( mOrderByTableWidget->rowCount() ); + + QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); + QCheckBox* ascCheckBox = new QCheckBox(); + ascCheckBox->setChecked( true ); + QCheckBox* nullsFirstCheckBox = new QCheckBox(); + + mOrderByTableWidget->setItem( row + 1, 0, expressionItem ); + mOrderByTableWidget->setCellWidget( row + 1, 1, ascCheckBox ); + mOrderByTableWidget->setCellWidget( row + 1, 2, nullsFirstCheckBox ); + } + } + } +} + +void QgsOrderByDialog::onCellChanged( int row, int column ) +{ + // If the text was cleared + if ( mOrderByTableWidget->item( row, column )->text().isEmpty() ) + { + // If the first column (expression text) and not the last row was edited + if ( 0 == column && row != mOrderByTableWidget->rowCount() - 1 ) + { + { + mOrderByTableWidget->removeRow( row ); + } + } + } + else + { + // If it's the last row and an expression was added: add a new empty one + if ( row == mOrderByTableWidget->rowCount() - 1 && !mOrderByTableWidget->item( row, column )->text().isEmpty() ) + { + // Add an empty widget at the end if the last row was edited + mOrderByTableWidget->insertRow( mOrderByTableWidget->rowCount() ); + + QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); + QCheckBox* ascCheckBox = new QCheckBox(); + ascCheckBox->setChecked( true ); + QCheckBox* nullsFirstCheckBox = new QCheckBox(); + + mOrderByTableWidget->setItem( row + 1, 0, expressionItem ); + mOrderByTableWidget->setCellWidget( row + 1, 1, ascCheckBox ); + mOrderByTableWidget->setCellWidget( row + 1, 2, nullsFirstCheckBox ); + } + } +} + +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..f0a1cd1f217 --- /dev/null +++ b/src/gui/qgsorderbydialog.h @@ -0,0 +1,65 @@ +/*************************************************************************** + 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 onCellDoubleClicked( int row, int column ); + void onCellChanged( int row, int column ); + + private: + 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/ui/qgsorderbydialogbase.ui b/src/ui/qgsorderbydialogbase.ui new file mode 100644 index 00000000000..f4a00782124 --- /dev/null +++ b/src/ui/qgsorderbydialogbase.ui @@ -0,0 +1,89 @@ + + + OrderByDialogBase + + + + 0 + 0 + 747 + 296 + + + + Define order + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + Expression + + + + + Ascending + + + + + Nulls First + + + + + + + + + + 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 + + + + + + + ... + + + + From 3dc832a32b05cb5da674c6e938916e31f450d0ea Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 11:21:32 +0100 Subject: [PATCH 03/10] Don't crash providers when destrucing with fake open connection --- src/core/qgsfeatureiterator.cpp | 8 +++++--- src/core/qgsfeatureiterator.h | 12 +++++++++++- src/providers/ogr/qgsogrfeatureiterator.cpp | 6 ++++-- .../postgres/qgspostgresfeatureiterator.cpp | 2 +- .../spatialite/qgsspatialitefeatureiterator.cpp | 2 +- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/core/qgsfeatureiterator.cpp b/src/core/qgsfeatureiterator.cpp index 94b06fc2b3b..f6348a249d2 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 ) @@ -152,8 +153,8 @@ bool QgsAbstractFeatureIterator::nextFeature( QgsFeature& f ) else { dataOk = false; - // don't call close, the provider connection has already been closed - mClosed = true; + // even the zombie dies at this point... + mZombie = false; } } else @@ -289,7 +290,8 @@ 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/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..592b5cc04ed 100644 --- a/src/providers/postgres/qgspostgresfeatureiterator.cpp +++ b/src/providers/postgres/qgspostgresfeatureiterator.cpp @@ -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..cf8246efeb0 100644 --- a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp +++ b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp @@ -235,7 +235,7 @@ bool QgsSpatiaLiteFeatureIterator::rewind() bool QgsSpatiaLiteFeatureIterator::close() { - if ( mClosed ) + if ( !mHandle ) return false; iteratorClosed(); From 3f1db6d55002b89c57402a4f88c2b482017abe9f Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 11:29:47 +0100 Subject: [PATCH 04/10] Add test for combination orderby / subset of attributes --- tests/src/python/providertestbase.py | 5 +++++ 1 file changed, 5 insertions(+) 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)) From 168c6f70bc58d7c9170307d3130bedc1787afd88 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 11:50:29 +0100 Subject: [PATCH 05/10] Rename OrderBys -> OrderBy and OrderBy -> OrderByClause And some sip fixes --- python/core/qgsfeaturerequest.sip | 16 +++++++++-- python/core/symbology-ng/qgsrendererv2.sip | 12 +++++++++ src/core/qgsfeatureiterator.cpp | 2 +- src/core/qgsfeaturerequest.cpp | 27 ++++++++++++++----- src/core/qgsfeaturerequest.h | 26 +++++++++++++++--- src/core/qgsvectorlayerfeatureiterator.cpp | 4 +-- src/core/qgsvectorlayerrenderer.cpp | 2 +- .../qgsinvertedpolygonrenderer.cpp | 2 +- .../qgspointdisplacementrenderer.cpp | 2 +- src/core/symbology-ng/qgsrendererv2.cpp | 6 ++--- src/core/symbology-ng/qgsrendererv2.h | 4 +-- .../symbology-ng/qgsrulebasedrendererv2.cpp | 2 +- src/gui/qgsorderbydialog.cpp | 8 +++--- .../mssql/qgsmssqlfeatureiterator.cpp | 2 +- .../postgres/qgspostgresfeatureiterator.cpp | 2 +- .../qgsspatialitefeatureiterator.cpp | 2 +- 16 files changed, 88 insertions(+), 31 deletions(-) diff --git a/python/core/qgsfeaturerequest.sip b/python/core/qgsfeaturerequest.sip index 49720b0828f..398ddae52da 100644 --- a/python/core/qgsfeaturerequest.sip +++ b/python/core/qgsfeaturerequest.sip @@ -81,6 +81,18 @@ 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 */ @@ -199,12 +211,12 @@ class QgsFeatureRequest /** * Return a list of order by clauses specified for this feature request. */ - QgsFeatureRequest::OrderBy orderBys() const; + QgsFeatureRequest::OrderBy orderBy() const; /** * Set a list of order by clauses. */ - QgsFeatureRequest& setOrderBys(const QgsFeatureRequest::OrderBy& 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/src/core/qgsfeatureiterator.cpp b/src/core/qgsfeatureiterator.cpp index f6348a249d2..2059a7b7131 100644 --- a/src/core/qgsfeatureiterator.cpp +++ b/src/core/qgsfeatureiterator.cpp @@ -220,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++; } diff --git a/src/core/qgsfeaturerequest.cpp b/src/core/qgsfeaturerequest.cpp index c9afc31b79b..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,24 @@ 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; } -QgsFeatureRequest::OrderBy QgsFeatureRequest::orderBys() const +QgsFeatureRequest::OrderBy QgsFeatureRequest::orderBy() const { - return mOrderBys; + return mOrderBy; } -QgsFeatureRequest& QgsFeatureRequest::setOrderBys( const QgsFeatureRequest::OrderBy& orderBys ) +QgsFeatureRequest& QgsFeatureRequest::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) { - mOrderBys = orderBys; + mOrderBy = orderBy; return *this; } @@ -321,6 +321,19 @@ 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(); diff --git a/src/core/qgsfeaturerequest.h b/src/core/qgsfeaturerequest.h index 1a0b4842459..ed15ee7297b 100644 --- a/src/core/qgsfeaturerequest.h +++ b/src/core/qgsfeaturerequest.h @@ -175,6 +175,26 @@ class CORE_EXPORT QgsFeatureRequest 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 */ @@ -315,14 +335,14 @@ class CORE_EXPORT QgsFeatureRequest * * @note added in 2.14 */ - OrderBy orderBys() const; + OrderBy orderBy() const; /** * Set a list of order by clauses. * * @note added in 2.14 */ - QgsFeatureRequest& setOrderBys( const OrderBy& 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. @@ -385,7 +405,7 @@ class CORE_EXPORT QgsFeatureRequest QgsAttributeList mAttrs; QgsSimplifyMethod mSimplifyMethod; long mLimit; - OrderBy mOrderBys; + OrderBy mOrderBy; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QgsFeatureRequest::Flags ) diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp index 1dc2341c31c..e3fbeec5c89 100644 --- a/src/core/qgsvectorlayerfeatureiterator.cpp +++ b/src/core/qgsvectorlayerfeatureiterator.cpp @@ -120,9 +120,9 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat // 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.orderBys().isEmpty() ) + if ( !mProviderRequest.orderBy().isEmpty() ) { - Q_FOREACH ( const QString& attr, mProviderRequest.orderBys().usedAttributes() ) + Q_FOREACH ( const QString& attr, mProviderRequest.orderBy().usedAttributes() ) { providerSubset << mSource->mFields.fieldNameIndex( attr ); } diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp index 80468a4c3ec..4145d2b9423 100644 --- a/src/core/qgsvectorlayerrenderer.cpp +++ b/src/core/qgsvectorlayerrenderer.cpp @@ -156,7 +156,7 @@ bool QgsVectorLayerRenderer::render() .setFilterRect( requestExtent ) .setSubsetOfAttributes( mAttrNames, mFields ) .setExpressionContext( mContext.expressionContext() ) - .setOrderBys( orderBy ); + .setOrderBy( orderBy ); const QgsFeatureFilterProvider* featureFilterProvider = mContext.featureFilterProvider(); if ( featureFilterProvider ) 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 2eb455a14b4..a9051834382 100644 --- a/src/core/symbology-ng/qgsrendererv2.cpp +++ b/src/core/symbology-ng/qgsrendererv2.cpp @@ -616,14 +616,14 @@ void QgsFeatureRendererV2::setPaintEffect( QgsPaintEffect *effect ) mPaintEffect = effect; } -QgsFeatureRequest::OrderBy QgsFeatureRendererV2::orderBy() +QgsFeatureRequest::OrderBy QgsFeatureRendererV2::orderBy() const { return mOrderBy; } -void QgsFeatureRendererV2::setOrderBy( const QgsFeatureRequest::OrderBy& orderBys ) +void QgsFeatureRendererV2::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) { - mOrderBy = orderBys; + mOrderBy = orderBy; } void QgsFeatureRendererV2::convertSymbolSizeScale( QgsSymbolV2 * symbol, QgsSymbolV2::ScaleMethod method, const QString & field ) diff --git a/src/core/symbology-ng/qgsrendererv2.h b/src/core/symbology-ng/qgsrendererv2.h index e0314b6b5c3..b14ba7ef5b2 100644 --- a/src/core/symbology-ng/qgsrendererv2.h +++ b/src/core/symbology-ng/qgsrendererv2.h @@ -348,13 +348,13 @@ class CORE_EXPORT QgsFeatureRendererV2 * Get the order in which features shall be processed by this renderer. * @note added in QGIS 2.14 */ - QgsFeatureRequest::OrderBy orderBy(); + 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& orderBys ); + void setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ); protected: QgsFeatureRendererV2( const QString& type ); 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/gui/qgsorderbydialog.cpp b/src/gui/qgsorderbydialog.cpp index 05878070143..2b702c415b9 100644 --- a/src/gui/qgsorderbydialog.cpp +++ b/src/gui/qgsorderbydialog.cpp @@ -71,7 +71,7 @@ void QgsOrderByDialog::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() { - QgsFeatureRequest::OrderBy orderBys; + QgsFeatureRequest::OrderBy orderBy; for ( int i = 0; i < mOrderByTableWidget->rowCount(); ++i ) { @@ -81,13 +81,13 @@ QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() { bool asc = static_cast( mOrderByTableWidget->cellWidget( i, 1 ) )->checkState(); bool nullsFirst = static_cast( mOrderByTableWidget->cellWidget( i, 2 ) )->checkState(); - QgsFeatureRequest::OrderByClause orderBy( expressionText, asc, nullsFirst ); + QgsFeatureRequest::OrderByClause orderByClause( expressionText, asc, nullsFirst ); - orderBys << orderBy; + orderBy << orderByClause; } } - return orderBys; + return orderBy; } void QgsOrderByDialog::onCellDoubleClicked( int row, int column ) 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/postgres/qgspostgresfeatureiterator.cpp b/src/providers/postgres/qgspostgresfeatureiterator.cpp index 592b5cc04ed..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(); diff --git a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp index cf8246efeb0..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(); From e00815db33589a84bb9ba6f69dd1fe94fcb2f412 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 12:28:59 +0100 Subject: [PATCH 06/10] Add test for rendering order --- tests/src/python/CMakeLists.txt | 1 + .../python/test_qgssinglesymbolrenderer.py | 90 ++++++++++++++++++ .../expected_singlesymbol_orderby.png | Bin 0 -> 471539 bytes 3 files changed, 91 insertions(+) create mode 100644 tests/src/python/test_qgssinglesymbolrenderer.py create mode 100644 tests/testdata/control_images/expected_singlesymbol_orderby/expected_singlesymbol_orderby.png 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/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 0000000000000000000000000000000000000000..3e8ac50a32411f0c7bc3a45f4e94eb8d65521076 GIT binary patch literal 471539 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 GbN>$lPQF?I literal 0 HcmV?d00001 From 9c9ff79b48465279e41dab9706d49b9049ce23c8 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 15:43:58 +0100 Subject: [PATCH 07/10] Add LongLong support to virtual provider --- src/providers/virtual/qgsvirtuallayersqlitemodule.cpp | 4 ++++ 1 file changed, 4 insertions(+) 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; From 09cc9e4e138649697e06861f3077dafcdb3306a7 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Mon, 21 Dec 2015 18:16:07 +0100 Subject: [PATCH 08/10] Use field expression widget in order by dialog --- src/gui/qgsfieldexpressionwidget.h | 4 ++ src/gui/qgsorderbydialog.cpp | 107 +++++++++-------------------- src/gui/qgsorderbydialog.h | 8 ++- 3 files changed, 42 insertions(+), 77 deletions(-) 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 index 2b702c415b9..16f2b32cb06 100644 --- a/src/gui/qgsorderbydialog.cpp +++ b/src/gui/qgsorderbydialog.cpp @@ -17,6 +17,7 @@ #include "qgsorderbydialog.h" #include "qgsexpressionbuilderdialog.h" +#include "qgsfieldexpressionwidget.h" #include #include @@ -27,8 +28,6 @@ QgsOrderByDialog::QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent ) , mLayer( layer ) { setupUi( this ); - connect( mOrderByTableWidget, SIGNAL( cellDoubleClicked( int, int ) ), this, SLOT( onCellDoubleClicked( int, int ) ) ); - connect( mOrderByTableWidget, SIGNAL( cellChanged( int, int ) ), this, SLOT( onCellChanged( int, int ) ) ); mOrderByTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch ); mOrderByTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::Interactive ); @@ -45,28 +44,13 @@ void QgsOrderByDialog::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) int i = 0; Q_FOREACH ( const QgsFeatureRequest::OrderByClause& orderByClause, orderBy ) { - QTableWidgetItem* expressionItem = new QTableWidgetItem( orderByClause.expression().expression() ); - QCheckBox* ascCheckBox = new QCheckBox(); - ascCheckBox->setChecked( orderByClause.ascending() ); - QCheckBox* nullsFirstCheckBox = new QCheckBox(); - nullsFirstCheckBox->setChecked( orderByClause.nullsFirst() ); - - mOrderByTableWidget->setItem( i, 0, expressionItem ); - mOrderByTableWidget->setCellWidget( i, 1, ascCheckBox ); - mOrderByTableWidget->setCellWidget( i, 2, nullsFirstCheckBox ); + setRow( i, orderByClause ); ++i; } // Add an empty widget at the end - QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); - QCheckBox* ascCheckBox = new QCheckBox(); - ascCheckBox->setChecked( true ); - QCheckBox* nullsFirstCheckBox = new QCheckBox(); - - mOrderByTableWidget->setItem( i, 0, expressionItem ); - mOrderByTableWidget->setCellWidget( i, 1, ascCheckBox ); - mOrderByTableWidget->setCellWidget( i, 2, nullsFirstCheckBox ); + setRow( i, QgsFeatureRequest::OrderByClause( "" ) ); } QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() @@ -75,7 +59,7 @@ QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() for ( int i = 0; i < mOrderByTableWidget->rowCount(); ++i ) { - QString expressionText = mOrderByTableWidget->item( i, 0 )->text(); + QString expressionText = static_cast( mOrderByTableWidget->cellWidget( i, 0 ) )->currentText(); if ( ! expressionText.isEmpty() ) { @@ -90,70 +74,43 @@ QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() return orderBy; } -void QgsOrderByDialog::onCellDoubleClicked( int row, int column ) +void QgsOrderByDialog::onExpressionChanged( const QString& expression ) { - // Only act on first cell where the expression text is - if ( 0 == column ) + // The sender() is the field widget which is the cell widget of the first column + int row; + for ( row = 0; row < mOrderByTableWidget->rowCount(); ++row ) { - QgsExpressionBuilderDialog dlg( mLayer ); - - dlg.setExpressionText( mOrderByTableWidget->item( row, column )->text() ); - - if ( dlg.exec() ) + if ( mOrderByTableWidget->cellWidget( row, 0 ) == sender() ) { - QString expressionText = dlg.expressionText(); - - mOrderByTableWidget->item( row, column )->setText( expressionText ); - - if ( row == mOrderByTableWidget->rowCount() - 1 ) - { - // Add an empty widget at the end if the last row was edited - mOrderByTableWidget->insertRow( mOrderByTableWidget->rowCount() ); - - QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); - QCheckBox* ascCheckBox = new QCheckBox(); - ascCheckBox->setChecked( true ); - QCheckBox* nullsFirstCheckBox = new QCheckBox(); - - mOrderByTableWidget->setItem( row + 1, 0, expressionItem ); - mOrderByTableWidget->setCellWidget( row + 1, 1, ascCheckBox ); - mOrderByTableWidget->setCellWidget( row + 1, 2, nullsFirstCheckBox ); - } + 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::onCellChanged( int row, int column ) +void QgsOrderByDialog::setRow( int row, const QgsFeatureRequest::OrderByClause& orderByClause ) { - // If the text was cleared - if ( mOrderByTableWidget->item( row, column )->text().isEmpty() ) - { - // If the first column (expression text) and not the last row was edited - if ( 0 == column && row != mOrderByTableWidget->rowCount() - 1 ) - { - { - mOrderByTableWidget->removeRow( row ); - } - } - } - else - { - // If it's the last row and an expression was added: add a new empty one - if ( row == mOrderByTableWidget->rowCount() - 1 && !mOrderByTableWidget->item( row, column )->text().isEmpty() ) - { - // Add an empty widget at the end if the last row was edited - mOrderByTableWidget->insertRow( mOrderByTableWidget->rowCount() ); + QgsFieldExpressionWidget* fieldExpression = new QgsFieldExpressionWidget(); + fieldExpression->setLayer( mLayer ); + fieldExpression->setField( orderByClause.expression().expression() ); + connect( fieldExpression, SIGNAL( fieldChanged( QString ) ), this, SLOT( onExpressionChanged( QString ) ) ); + QCheckBox* ascCheckBox = new QCheckBox(); + ascCheckBox->setChecked( orderByClause.ascending() ); + QCheckBox* nullsFirstCheckBox = new QCheckBox(); + nullsFirstCheckBox->setChecked( orderByClause.nullsFirst() ); - QTableWidgetItem* expressionItem = new QTableWidgetItem( "" ); - QCheckBox* ascCheckBox = new QCheckBox(); - ascCheckBox->setChecked( true ); - QCheckBox* nullsFirstCheckBox = new QCheckBox(); - - mOrderByTableWidget->setItem( row + 1, 0, expressionItem ); - mOrderByTableWidget->setCellWidget( row + 1, 1, ascCheckBox ); - mOrderByTableWidget->setCellWidget( row + 1, 2, nullsFirstCheckBox ); - } - } + mOrderByTableWidget->setCellWidget( row, 0, fieldExpression ); + mOrderByTableWidget->setCellWidget( row, 1, ascCheckBox ); + mOrderByTableWidget->setCellWidget( row, 2, nullsFirstCheckBox ); } bool QgsOrderByDialog::eventFilter( QObject* obj, QEvent* e ) diff --git a/src/gui/qgsorderbydialog.h b/src/gui/qgsorderbydialog.h index f0a1cd1f217..34d69969bb8 100644 --- a/src/gui/qgsorderbydialog.h +++ b/src/gui/qgsorderbydialog.h @@ -53,10 +53,14 @@ class GUI_EXPORT QgsOrderByDialog : public QDialog, private Ui::OrderByDialogBas QgsFeatureRequest::OrderBy orderBy(); private slots: - void onCellDoubleClicked( int row, int column ); - void onCellChanged( int row, int column ); + 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 ); From 9a64569fc8b3501b2d0ddee09f2853d5a0ef6020 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Tue, 22 Dec 2015 09:40:16 +0100 Subject: [PATCH 09/10] Fix orderby table headers and use combobox --- src/gui/qgsorderbydialog.cpp | 36 ++++++++++++++++++++++------------ src/ui/qgsorderbydialogbase.ui | 10 ++-------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/gui/qgsorderbydialog.cpp b/src/gui/qgsorderbydialog.cpp index 16f2b32cb06..9bc594ad954 100644 --- a/src/gui/qgsorderbydialog.cpp +++ b/src/gui/qgsorderbydialog.cpp @@ -20,7 +20,6 @@ #include "qgsfieldexpressionwidget.h" #include -#include #include QgsOrderByDialog::QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent ) @@ -30,15 +29,14 @@ QgsOrderByDialog::QgsOrderByDialog( QgsVectorLayer* layer, QWidget* parent ) setupUi( this ); mOrderByTableWidget->horizontalHeader()->setResizeMode( QHeaderView::Stretch ); - mOrderByTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::Interactive ); - mOrderByTableWidget->horizontalHeader()->setResizeMode( 2, QHeaderView::Interactive ); + mOrderByTableWidget->horizontalHeader()->setResizeMode( 1, QHeaderView::ResizeToContents ); + mOrderByTableWidget->horizontalHeader()->setResizeMode( 2, QHeaderView::ResizeToContents ); mOrderByTableWidget->installEventFilter( this ); } void QgsOrderByDialog::setOrderBy( const QgsFeatureRequest::OrderBy& orderBy ) { - mOrderByTableWidget->clear(); mOrderByTableWidget->setRowCount( orderBy.length() + 1 ); int i = 0; @@ -63,8 +61,16 @@ QgsFeatureRequest::OrderBy QgsOrderByDialog::orderBy() if ( ! expressionText.isEmpty() ) { - bool asc = static_cast( mOrderByTableWidget->cellWidget( i, 1 ) )->checkState(); - bool nullsFirst = static_cast( mOrderByTableWidget->cellWidget( i, 2 ) )->checkState(); + 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; @@ -103,14 +109,20 @@ void QgsOrderByDialog::setRow( int row, const QgsFeatureRequest::OrderByClause& fieldExpression->setLayer( mLayer ); fieldExpression->setField( orderByClause.expression().expression() ); connect( fieldExpression, SIGNAL( fieldChanged( QString ) ), this, SLOT( onExpressionChanged( QString ) ) ); - QCheckBox* ascCheckBox = new QCheckBox(); - ascCheckBox->setChecked( orderByClause.ascending() ); - QCheckBox* nullsFirstCheckBox = new QCheckBox(); - nullsFirstCheckBox->setChecked( orderByClause.nullsFirst() ); + + 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, ascCheckBox ); - mOrderByTableWidget->setCellWidget( row, 2, nullsFirstCheckBox ); + mOrderByTableWidget->setCellWidget( row, 1, ascComboBox ); + mOrderByTableWidget->setCellWidget( row, 2, nullsFirstComboBox ); } bool QgsOrderByDialog::eventFilter( QObject* obj, QEvent* e ) diff --git a/src/ui/qgsorderbydialogbase.ui b/src/ui/qgsorderbydialogbase.ui index f4a00782124..44ba05cf6f8 100644 --- a/src/ui/qgsorderbydialogbase.ui +++ b/src/ui/qgsorderbydialogbase.ui @@ -26,12 +26,6 @@ - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - Expression @@ -39,12 +33,12 @@ - Ascending + Asc / Desc - Nulls First + NULLs handling From d5585ffdcd3b21e518a10d85c374635352573e88 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Tue, 22 Dec 2015 09:40:22 +0100 Subject: [PATCH 10/10] Use qSort instead of std::sort, former does not crash --- src/core/qgsfeatureiterator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/qgsfeatureiterator.cpp b/src/core/qgsfeatureiterator.cpp index 2059a7b7131..43c730f333c 100644 --- a/src/core/qgsfeatureiterator.cpp +++ b/src/core/qgsfeatureiterator.cpp @@ -286,7 +286,7 @@ void QgsAbstractFeatureIterator::setupOrderBy( const QList