diff --git a/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp b/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp index d02787c7fe2..ae1b29c3f03 100644 --- a/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp +++ b/src/core/fieldformatter/qgsvaluerelationfieldformatter.cpp @@ -31,16 +31,6 @@ using namespace nlohmann; #include -bool orderByKeyLessThan( const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2 ) -{ - return p1.group == p2.group ? qgsVariantLessThan( p1.key, p2.key ) : qgsVariantLessThan( p1.group, p2.group ); -} - -bool orderByValueLessThan( const QgsValueRelationFieldFormatter::ValueRelationItem &p1, const QgsValueRelationFieldFormatter::ValueRelationItem &p2 ) -{ - return p1.group == p2.group ? qgsVariantLessThan( p1.value, p2.value ) : qgsVariantLessThan( p1.group, p2.group ); -} - QgsValueRelationFieldFormatter::QgsValueRelationFieldFormatter() { setFlags( flags() | QgsFieldFormatter::CanProvideAvailableValues ); @@ -176,6 +166,11 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte } QgsFeatureIterator fit = layer->getFeatures( request ); + const bool orderByField { config.value( QStringLiteral( "OrderByField" ) ).toBool() }; + const int fieldIdx { orderByField ? layer->fields().lookupField( config.value( QStringLiteral( "OrderByFieldName" ) ).toString() ) : -1 }; + const bool reverseSort { config.value( QStringLiteral( "OrderByDescending" ) ).toBool() }; + + QMap orderByFieldValues; QgsFeature f; while ( fit.nextFeature( f ) ) @@ -187,16 +182,47 @@ QgsValueRelationFieldFormatter::ValueRelationCache QgsValueRelationFieldFormatte description = descriptionExpression.evaluate( &context ).toString(); } const QVariant group = groupIdx > -1 ? f.attribute( groupIdx ) : QVariant(); - cache.append( ValueRelationItem( f.attribute( keyIdx ), f.attribute( valueIdx ).toString(), description, group ) ); + const QVariant keyValue = f.attribute( keyIdx ); + if ( fieldIdx != -1 ) + { + orderByFieldValues.insert( keyValue, f.attribute( fieldIdx ) ); + } + cache.append( ValueRelationItem( keyValue, f.attribute( valueIdx ).toString(), description, group ) ); } + if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() ) { - std::sort( cache.begin(), cache.end(), orderByValueLessThan ); + std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool + { + if ( reverseSort ) + return p1.group == p2.group ? qgsVariantGreaterThan( p1.value, p2.value ) : qgsVariantGreaterThan( p1.group, p2.group ); + else + return p1.group == p2.group ? qgsVariantLessThan( p1.value, p2.value ) : qgsVariantLessThan( p1.group, p2.group ); + } ); } + // Order by field + else if ( fieldIdx != -1 ) + { + std::sort( cache.begin(), cache.end(), [&reverseSort, &orderByFieldValues]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool + { + if ( reverseSort ) + return p1.group == p2.group ? qgsVariantGreaterThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantGreaterThan( p1.group, p2.group ); + else + return p1.group == p2.group ? qgsVariantLessThan( orderByFieldValues.value( p1.key ), orderByFieldValues.value( p2.key ) ) : qgsVariantLessThan( p1.group, p2.group ); + + } ); + } + // OrderByKey is the default else { - std::sort( cache.begin(), cache.end(), orderByKeyLessThan ); + std::sort( cache.begin(), cache.end(), [&reverseSort]( const QgsValueRelationFieldFormatter::ValueRelationItem & p1, const QgsValueRelationFieldFormatter::ValueRelationItem & p2 ) -> bool + { + if ( reverseSort ) + return p1.group == p2.group ? qgsVariantGreaterThan( p1.key, p2.key ) : qgsVariantGreaterThan( p1.group, p2.group ); + else + return p1.group == p2.group ? qgsVariantLessThan( p1.key, p2.key ) : qgsVariantLessThan( p1.group, p2.group ); + } ); } return cache; diff --git a/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp b/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp index acb860001be..731c301d390 100644 --- a/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp +++ b/src/gui/editorwidgets/qgsvaluerelationconfigdlg.cpp @@ -31,12 +31,20 @@ QgsValueRelationConfigDlg::QgsValueRelationConfigDlg( QgsVectorLayer *vl, int fi mGroupColumn->setLayer( mLayerName->currentLayer() ); mGroupColumn->setAllowEmptyFieldName( true ); mDescriptionExpression->setLayer( mLayerName->currentLayer() ); + mOrderByFieldName->setLayer( mLayerName->currentLayer() ); + mOrderByFieldName->setAllowEmptyFieldName( false ); connect( mLayerName, &QgsMapLayerComboBox::layerChanged, mKeyColumn, &QgsFieldComboBox::setLayer ); connect( mLayerName, &QgsMapLayerComboBox::layerChanged, mValueColumn, &QgsFieldComboBox::setLayer ); connect( mLayerName, &QgsMapLayerComboBox::layerChanged, mGroupColumn, &QgsFieldComboBox::setLayer ); connect( mLayerName, &QgsMapLayerComboBox::layerChanged, mDescriptionExpression, &QgsFieldExpressionWidget::setLayer ); connect( mLayerName, &QgsMapLayerComboBox::layerChanged, this, &QgsValueRelationConfigDlg::layerChanged ); connect( mEditExpression, &QAbstractButton::clicked, this, &QgsValueRelationConfigDlg::editExpression ); + connect( mOrderByField, &QAbstractButton::toggled, mOrderByFieldName, [ = ]( bool enabled ) + { + mOrderByFieldName->setEnabled( enabled ); + } ); + + mOrderByGroupBox->setCollapsed( true ); mNofColumns->setMinimum( 1 ); mNofColumns->setMaximum( 10 ); @@ -93,6 +101,10 @@ QVariantMap QgsValueRelationConfigDlg::config() cfg.insert( QStringLiteral( "NofColumns" ), mNofColumns->value() ); cfg.insert( QStringLiteral( "AllowNull" ), mAllowNull->isChecked() ); cfg.insert( QStringLiteral( "OrderByValue" ), mOrderByValue->isChecked() ); + cfg.insert( QStringLiteral( "OrderByKey" ), mOrderByKey->isChecked() ); + cfg.insert( QStringLiteral( "OrderByField" ), mOrderByField->isChecked() ); + cfg.insert( QStringLiteral( "OrderByFieldName" ), mOrderByFieldName->currentField() ); + cfg.insert( QStringLiteral( "OrderByDescending" ), mOrderByDescending->isChecked() ); cfg.insert( QStringLiteral( "FilterExpression" ), mFilterExpression->toPlainText() ); cfg.insert( QStringLiteral( "UseCompleter" ), mUseCompleter->isChecked() ); const Qt::MatchFlags completerMatchFlags { mCompleterMatchFromStart->isChecked() ? Qt::MatchFlag::MatchStartsWith : Qt::MatchFlag::MatchContains }; @@ -105,6 +117,7 @@ void QgsValueRelationConfigDlg::setConfig( const QVariantMap &config ) { QgsVectorLayer *lyr = QgsValueRelationFieldFormatter::resolveLayer( config, QgsProject::instance() ); mLayerName->setLayer( lyr ); + mOrderByFieldName->setLayer( lyr ); mKeyColumn->setField( config.value( QStringLiteral( "Key" ) ).toString() ); mValueColumn->setField( config.value( QStringLiteral( "Value" ) ).toString() ); mGroupColumn->setField( config.value( QStringLiteral( "Group" ) ).toString() ); @@ -119,6 +132,22 @@ void QgsValueRelationConfigDlg::setConfig( const QVariantMap &config ) } mAllowNull->setChecked( config.value( QStringLiteral( "AllowNull" ) ).toBool() ); mOrderByValue->setChecked( config.value( QStringLiteral( "OrderByValue" ) ).toBool() ); + mOrderByField->setChecked( config.value( QStringLiteral( "OrderByField" ) ).toBool() ); + mOrderByKey->setChecked( config.value( QStringLiteral( "OrderByKey" ) ).toBool() ); + mOrderByFieldName->setField( config.value( QStringLiteral( "OrderByFieldName" ) ).toString() ); + mOrderByDescending->setChecked( config.value( QStringLiteral( "OrderByDescending" ) ).toBool() ); + + if ( !mOrderByField->isChecked() && !mOrderByValue->isChecked() && !mOrderByKey->isChecked() ) + { + mOrderByKey->setChecked( true ); + } + + // order by key is the default, if it is not checked, expand the config + if ( !mOrderByKey->isChecked() ) + { + mOrderByGroupBox->setCollapsed( false ); + } + mFilterExpression->setPlainText( config.value( QStringLiteral( "FilterExpression" ) ).toString() ); mUseCompleter->setChecked( config.value( QStringLiteral( "UseCompleter" ) ).toBool() ); // Default is MatchStartsWith for backwards compatibility @@ -130,6 +159,7 @@ void QgsValueRelationConfigDlg::layerChanged() { mFilterExpression->setEnabled( qobject_cast( mLayerName->currentLayer() ) ); mEditExpression->setEnabled( qobject_cast( mLayerName->currentLayer() ) ); + mOrderByFieldName->setLayer( mLayerName->currentLayer() ); } void QgsValueRelationConfigDlg::editExpression() diff --git a/src/ui/editorwidgets/qgsvaluerelationconfigdlgbase.ui b/src/ui/editorwidgets/qgsvaluerelationconfigdlgbase.ui index c5ef96c14c7..292b2659536 100644 --- a/src/ui/editorwidgets/qgsvaluerelationconfigdlgbase.ui +++ b/src/ui/editorwidgets/qgsvaluerelationconfigdlgbase.ui @@ -6,15 +6,116 @@ 0 0 - 318 - 490 + 342 + 620 Form - + + + + Key column + + + mKeyColumn + + + + + + + Use completer + + + + + + + Description + + + + + + + + 1 + 0 + + + + + + + + Group column + + + mGroupColumn + + + + + + + + + + + + + + + + Select layer, key column and value column + + + + + + + Allow multiple selections + + + + + + + Layer + + + mLayerName + + + + + + + Allow NULL value + + + + + + + Value column + + + mValueColumn + + + + + + + Only match from the beginning of the string + + + + @@ -32,7 +133,7 @@ Qt::RightToLeft - + :/images/themes/default/mIconExpression.svg:/images/themes/default/mIconExpression.svg @@ -52,137 +153,85 @@ - - - - Number of columns - - - - - - - - - - Select layer, key column and value column - - - - - - - Layer - - - mLayerName - - - - - - - - - - Order by value - - - - - - - - - - Allow multiple selections - - - - - - - Allow NULL value - - - - - - - Use completer - - - - - - - Only match from the beginning of the string - - - - - - - Key column - - - mKeyColumn - - - - - - - - 1 - 0 - - - - - - - - - - - Value column - - - mValueColumn - - - - - - - Group column - - - mGroupColumn - - - - + - + Display group name - - + + + + + - Description + Number of columns - + + + + + Order By + + + + + + Use the values in the key column for sorting + + + Key + + + + + + + Use the values in the value column for sorting + + + Value + + + + + + + + + Use the values from a specific field for sorting + + + Field + + + + + + + + + + + + Reverse the sorting + + + Descending order + + + + + + @@ -207,6 +256,12 @@ QComboBox
qgsmaplayercombobox.h
+ + QgsCollapsibleGroupBox + QGroupBox +
qgscollapsiblegroupbox.h
+ 1 +
mLayerName @@ -214,7 +269,6 @@ mValueColumn mGroupColumn mDescriptionExpression - mOrderByValue mAllowNull mAllowMulti mUseCompleter @@ -223,6 +277,8 @@ mEditExpression mFilterExpression - + + + diff --git a/tests/src/core/testqgsvaluerelationfieldformatter.cpp b/tests/src/core/testqgsvaluerelationfieldformatter.cpp index deb2e1d6985..3d43a2daf56 100644 --- a/tests/src/core/testqgsvaluerelationfieldformatter.cpp +++ b/tests/src/core/testqgsvaluerelationfieldformatter.cpp @@ -39,6 +39,8 @@ class TestQgsValueRelationFieldFormatter: public QObject void testDependencies(); void testSortValueNull(); void testGroup(); + void testOrderBy_data(); + void testOrderBy(); private: std::unique_ptr mLayer1; @@ -112,7 +114,7 @@ void TestQgsValueRelationFieldFormatter::init() QgsFeature ft3( mLayer2->fields() ); ft3.setAttribute( QStringLiteral( "pk" ), 11 ); ft3.setAttribute( QStringLiteral( "material" ), "iron" ); - ft3.setAttribute( QStringLiteral( "diameter" ), 120 ); + ft3.setAttribute( QStringLiteral( "diameter" ), 110 ); ft3.setAttribute( QStringLiteral( "raccord" ), "sleeve" ); mLayer2->startEditing(); mLayer2->addFeature( ft3 ); @@ -121,7 +123,7 @@ void TestQgsValueRelationFieldFormatter::init() QgsFeature ft4( mLayer2->fields() ); ft4.setAttribute( QStringLiteral( "pk" ), 12 ); ft4.setAttribute( QStringLiteral( "material" ), "steel" ); - ft4.setAttribute( QStringLiteral( "diameter" ), 120 ); + ft4.setAttribute( QStringLiteral( "diameter" ), 100 ); ft4.setAttribute( QStringLiteral( "raccord" ), "collar" ); mLayer2->startEditing(); mLayer2->addFeature( ft4 ); @@ -180,5 +182,62 @@ void TestQgsValueRelationFieldFormatter::testGroup() QCOMPARE( cache.at( cache.size() - 1 ).group, QVariant( QStringLiteral( "steel" ) ) ); } +void TestQgsValueRelationFieldFormatter::testOrderBy_data() +{ + QTest::addColumn( "orderBy" ); + QTest::addColumn( "fieldName" ); + QTest::addColumn( "expectedFirst" ); + QTest::addColumn( "expectedLast" ); + + QTest::newRow( "orderByDefault(pk)" ) << QString() << QString() << "brides" << "collar"; + QTest::newRow( "orderByKey(pk)" ) << QString() << QStringLiteral( "Key" ) << "brides" << "collar"; + QTest::newRow( "orderByValue(raccord)" ) << QString() << QStringLiteral( "Value" ) << "brides" << "collar"; + QTest::newRow( "orderByField(raccord)" ) << QStringLiteral( "Field" ) << QStringLiteral( "raccord" ) << "brides" << "sleeve"; + QTest::newRow( "orderByField(diameter)" ) << QStringLiteral( "Field" ) << QStringLiteral( "diameter" ) << "collar" << "brides"; + QTest::newRow( "orderByField(material)" ) << QStringLiteral( "Field" ) << QStringLiteral( "material" ) << "brides" << "collar"; +} + +void TestQgsValueRelationFieldFormatter::testOrderBy() +{ + + QFETCH( QString, orderBy ); + QFETCH( QString, fieldName ); + QFETCH( QString, expectedFirst ); + QFETCH( QString, expectedLast ); + + QVariantMap config; + config.insert( QStringLiteral( "Layer" ), mLayer2->id() ); + config.insert( QStringLiteral( "Key" ), QStringLiteral( "pk" ) ); + config.insert( QStringLiteral( "Value" ), QStringLiteral( "raccord" ) ); + + if ( !orderBy.isEmpty() ) + { + config.insert( QStringLiteral( "OrderBy%1" ).arg( orderBy ), true ); + } + if ( !fieldName.isEmpty() ) + { + config.insert( QStringLiteral( "OrderByFieldName" ), fieldName ); + } + + // Ascending + { + const QgsValueRelationFieldFormatter formatter; + QgsValueRelationFieldFormatter::ValueRelationCache cache = formatter.createCache( config ); + QVERIFY( !cache.isEmpty() ); + QCOMPARE( cache.at( 0 ).value, expectedFirst ); + QCOMPARE( cache.at( mLayer2->featureCount() - 1 ).value, expectedLast ); + } + + // Descending + { + config.insert( QStringLiteral( "OrderByDescending" ), true ); + const QgsValueRelationFieldFormatter formatter; + QgsValueRelationFieldFormatter::ValueRelationCache cache = formatter.createCache( config ); + QVERIFY( !cache.isEmpty() ); + QCOMPARE( cache.at( 0 ).value, expectedLast ); + QCOMPARE( cache.at( mLayer2->featureCount() - 1 ).value, expectedFirst ); + } +} + QGSTEST_MAIN( TestQgsValueRelationFieldFormatter ) #include "testqgsvaluerelationfieldformatter.moc"