Skip to content

Commit f34a727

Browse files
committed
UniqueValuesModel is now used in dropdown, cleared outdated methods in FilterController
1 parent ffec2e9 commit f34a727

5 files changed

Lines changed: 207 additions & 420 deletions

File tree

app/filtercontroller.cpp

Lines changed: 25 additions & 233 deletions
Original file line numberDiff line numberDiff line change
@@ -318,30 +318,6 @@ void FilterController::processFilters( const QVariantMap &newFilters )
318318
applyFiltersToAllLayers();
319319
}
320320

321-
QStringList FilterController::getFieldUniqueValues( QgsVectorLayer *layer, const QString &fieldName ) const
322-
{
323-
QStringList result;
324-
325-
if ( !layer )
326-
return result;
327-
328-
int fieldIndex = layer->fields().lookupField( fieldName );
329-
if ( fieldIndex < 0 )
330-
return result;
331-
332-
QSet<QVariant> uniqueValues = layer->uniqueValues( fieldIndex, 100 );
333-
for ( const QVariant &v : uniqueValues )
334-
{
335-
if ( !v.isNull() && !v.toString().isEmpty() )
336-
{
337-
result << v.toString();
338-
}
339-
}
340-
341-
result.sort();
342-
return result;
343-
}
344-
345321
bool FilterController::hasActiveFilterOnLayer( const QString &layerId )
346322
{
347323
const QgsProject *project = QgsProject::instance();
@@ -352,237 +328,53 @@ bool FilterController::hasActiveFilterOnLayer( const QString &layerId )
352328
return !layer->subsetString().isEmpty();
353329
}
354330

355-
QVariantList FilterController::getDropdownOptions( const QString &filterId, const QString &searchText, const int limit )
331+
QVariantMap FilterController::getDropdownConfiguration( const QString &filterId )
356332
{
357-
if ( filterId.isEmpty() )
358-
return {};
333+
if ( filterId.isEmpty() ) return {};
359334

360335
FieldFilter fieldFilter;
361-
for ( const FieldFilter &filter : mFieldFilters )
336+
for ( const FieldFilter &filter : std::as_const( mFieldFilters ) )
362337
{
363338
if ( filterId == filter.filterId )
364-
{
339+
{
365340
fieldFilter = filter;
341+
break;
366342
}
367343
}
368344

369-
const QgsProject *project = QgsProject::instance();
370-
if ( !project )
371-
return {};
372-
373-
const QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( project->mapLayers().value( fieldFilter.layerId ) );
374-
const int fieldIndex = layer->fields().lookupField( fieldFilter.fieldName );
375-
if ( fieldIndex < 0 )
376-
return {};
377-
378-
const QgsEditorWidgetSetup widgetSetup = layer->editorWidgetSetup( fieldIndex );
379-
const QString widgetType = widgetSetup.type();
380-
const QVariantMap config = widgetSetup.config();
381-
382-
if ( widgetType == QLatin1String( "ValueMap" ) )
383-
{
384-
return extractValueMapOptions( config, searchText );
385-
}
386-
else if ( widgetType == QLatin1String( "ValueRelation" ) )
387-
{
388-
return extractValueRelationOptions( config, searchText, limit, fieldFilter.value.toStringList() );
389-
}
390-
391-
return {};
392-
}
393-
394-
QVariantList FilterController::extractValueMapOptions( const QVariantMap &config, const QString &searchText ) const
395-
{
396-
QVariantList result;
397-
398-
QVariantList mapList = config.value( QStringLiteral( "map" ) ).toList();
399-
for ( const QVariant &entry : mapList )
400-
{
401-
QVariantMap entryMap = entry.toMap();
402-
if ( entryMap.isEmpty() )
403-
continue;
404-
405-
// Each entry is a single-key map: {"Display Text": "stored_value"}
406-
QString displayText = entryMap.constBegin().key();
407-
QString storedValue = entryMap.constBegin().value().toString();
408-
409-
// Filter by search text
410-
if ( !searchText.isEmpty() && !displayText.contains( searchText, Qt::CaseInsensitive ) )
411-
continue;
412-
413-
QVariantMap option;
414-
option[QStringLiteral( "text" )] = displayText;
415-
option[QStringLiteral( "value" )] = storedValue;
416-
result << option;
417-
}
418-
419-
return result;
420-
}
345+
if ( !fieldFilter.hasFilterInfo() ) return {};
421346

422-
QVariantList FilterController::extractValueRelationOptions( const QVariantMap &config, const QString &searchText, int limit, const QStringList &alwaysIncludeKeys ) const
423-
{
424-
QVariantList result;
347+
if ( fieldFilter.filterType != FieldFilter::SingleSelectFilter && fieldFilter.filterType != FieldFilter::MultiSelectFilter ) return {};
425348

426-
QgsVectorLayer *referencedLayer = QgsValueRelationFieldFormatter::resolveLayer( config, QgsProject::instance() );
427-
if ( !referencedLayer )
428-
return result;
349+
QVariantMap map;
429350

430-
QString keyFieldName = config.value( QStringLiteral( "Key" ) ).toString();
431-
QString valueFieldName = config.value( QStringLiteral( "Value" ) ).toString();
432-
433-
if ( referencedLayer->fields().indexOf( keyFieldName ) < 0 || referencedLayer->fields().indexOf( valueFieldName ) < 0 )
434-
return result;
435-
436-
// Build feature request
437-
QgsFeatureRequest request;
438-
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
439-
request.setSubsetOfAttributes( QStringList( { keyFieldName, valueFieldName } ), referencedLayer->fields() );
440-
441-
// Apply search filter
442-
if ( !searchText.isEmpty() )
443-
{
444-
QString escapedSearch = searchText;
445-
escapedSearch.replace( "'", "''" );
446-
QString filterExpr = QStringLiteral( "LOWER(%1) LIKE '%%2%'" )
447-
.arg( QgsExpression::quotedColumnRef( valueFieldName ), escapedSearch.toLower() );
448-
request.setFilterExpression( filterExpr );
449-
}
450-
451-
// Apply configured filter expression (only if it doesn't require form scope)
452-
QString configFilterExpr = config.value( QStringLiteral( "FilterExpression" ) ).toString();
453-
if ( !configFilterExpr.isEmpty() && !QgsValueRelationFieldFormatter::expressionRequiresFormScope( configFilterExpr ) )
454-
{
455-
request.combineFilterExpression( configFilterExpr );
456-
}
457-
458-
// Apply ordering
459-
if ( config.value( QStringLiteral( "OrderByValue" ) ).toBool() )
460-
{
461-
request.setOrderBy( QgsFeatureRequest::OrderBy( { QgsFeatureRequest::OrderByClause( valueFieldName ) } ) );
462-
}
463-
464-
request.setLimit( limit );
465-
466-
// Fetch features
467-
QSet<QString> seenKeys;
468-
QgsFeatureIterator it = referencedLayer->getFeatures( request );
469-
QgsFeature feature;
470-
while ( it.nextFeature( feature ) )
471-
{
472-
QString key = feature.attribute( keyFieldName ).toString();
473-
QString value = feature.attribute( valueFieldName ).toString();
474-
seenKeys.insert( key );
475-
476-
QVariantMap option;
477-
option[QStringLiteral( "text" )] = value;
478-
option[QStringLiteral( "value" )] = key;
479-
result << option;
480-
}
481-
482-
// Ensure currently selected keys are always visible in the list
483-
if ( !alwaysIncludeKeys.isEmpty() )
484-
{
485-
QStringList missingKeys;
486-
for ( const QString &key : alwaysIncludeKeys )
487-
{
488-
if ( !seenKeys.contains( key ) )
489-
{
490-
missingKeys << key;
491-
}
492-
}
351+
const QgsProject *project = QgsProject::instance();
493352

494-
if ( !missingKeys.isEmpty() )
495-
{
496-
QStringList quotedKeys;
497-
for ( const QString &k : missingKeys )
498-
{
499-
quotedKeys << QgsExpression::quotedValue( k );
500-
}
353+
if ( !project ) return {};
501354

502-
QgsFeatureRequest selectedRequest;
503-
selectedRequest.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
504-
selectedRequest.setSubsetOfAttributes( QStringList( { keyFieldName, valueFieldName } ), referencedLayer->fields() );
505-
selectedRequest.setFilterExpression(
506-
QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) )
507-
);
508-
509-
QVariantList selectedItems;
510-
QgsFeatureIterator selIt = referencedLayer->getFeatures( selectedRequest );
511-
QgsFeature selFeature;
512-
while ( selIt.nextFeature( selFeature ) )
513-
{
514-
QVariantMap option;
515-
option[QStringLiteral( "text" )] = selFeature.attribute( valueFieldName ).toString();
516-
option[QStringLiteral( "value" )] = selFeature.attribute( keyFieldName ).toString();
517-
selectedItems << option;
518-
}
519-
520-
// Prepend selected items so they appear first
521-
selectedItems.append( result );
522-
result = selectedItems;
523-
}
524-
}
355+
const QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( project->mapLayers().value( fieldFilter.layerId ) );
525356

526-
return result;
527-
}
357+
if ( !layer ) return {};
528358

529-
QStringList FilterController::lookupValueMapTexts( const QVariantMap &config, const QStringList &keys ) const
530-
{
531-
QStringList texts;
532-
QSet<QString> keySet( keys.begin(), keys.end() );
359+
const int fieldIndex = layer->fields().lookupField( fieldFilter.fieldName );
360+
const QgsEditorWidgetSetup fieldConfig = layer->editorWidgetSetup( fieldIndex );
533361

534-
QVariantList mapList = config.value( QStringLiteral( "map" ) ).toList();
535-
for ( const QVariant &entry : mapList )
362+
if ( QString::compare( fieldConfig.type(), QStringLiteral( "ValueMap" ), Qt::CaseInsensitive ) == 0 )
536363
{
537-
QVariantMap entryMap = entry.toMap();
538-
if ( entryMap.isEmpty() )
539-
continue;
540-
541-
QString displayText = entryMap.constBegin().key();
542-
QString storedValue = entryMap.constBegin().value().toString();
543-
544-
if ( keySet.contains( storedValue ) )
545-
{
546-
texts << displayText;
547-
}
364+
map["type"] = QStringLiteral("value_map");
365+
map["config"] = fieldConfig.config();
548366
}
549-
550-
return texts;
551-
}
552-
553-
QStringList FilterController::lookupValueRelationTexts( const QVariantMap &config, const QStringList &keys ) const
554-
{
555-
QStringList texts;
556-
557-
QgsVectorLayer *referencedLayer = QgsValueRelationFieldFormatter::resolveLayer( config, QgsProject::instance() );
558-
if ( !referencedLayer )
559-
return texts;
560-
561-
QString keyFieldName = config.value( QStringLiteral( "Key" ) ).toString();
562-
QString valueFieldName = config.value( QStringLiteral( "Value" ) ).toString();
563-
564-
if ( referencedLayer->fields().indexOf( keyFieldName ) < 0 || referencedLayer->fields().indexOf( valueFieldName ) < 0 )
565-
return texts;
566-
567-
QStringList quotedKeys;
568-
for ( const QString &k : keys )
367+
else if ( QString::compare( fieldConfig.type(), QStringLiteral( "ValueRelation" ), Qt::CaseInsensitive ) == 0 )
569368
{
570-
quotedKeys << QgsExpression::quotedValue( k );
369+
map["type"] = QStringLiteral("value_relation");
370+
map["config"] = fieldConfig.config();
571371
}
572-
573-
QgsFeatureRequest request;
574-
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
575-
request.setSubsetOfAttributes( QStringList( { keyFieldName, valueFieldName } ), referencedLayer->fields() );
576-
request.setFilterExpression(
577-
QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) )
578-
);
579-
580-
QgsFeatureIterator it = referencedLayer->getFeatures( request );
581-
QgsFeature feature;
582-
while ( it.nextFeature( feature ) )
372+
else
583373
{
584-
texts << feature.attribute( valueFieldName ).toString();
374+
map["type"] = QStringLiteral("unique_values");
375+
map["layer_id"] = fieldFilter.layerId;
376+
map["field_name"] = fieldFilter.fieldName;
585377
}
586378

587-
return texts;
379+
return map;
588380
}

app/filtercontroller.h

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ struct FieldFilter
5151
{
5252
const bool hasValue = value.isValid() && !value.isNull();
5353
const bool isNamed = !filterName.isEmpty();
54-
const bool hasFilterInfo = !layerId.isEmpty() && !fieldName.isEmpty() && !sqlExpression.isEmpty();
55-
return isNamed && hasValue && hasFilterInfo;
54+
return isNamed && hasValue && hasFilterInfo();
55+
}
56+
57+
bool hasFilterInfo() const
58+
{
59+
return !layerId.isEmpty() && !fieldName.isEmpty() && !sqlExpression.isEmpty();
5660
}
5761
};
5862

@@ -102,20 +106,8 @@ class FilterController : public QObject
102106
*/
103107
Q_INVOKABLE void clearAllFilters();
104108

105-
/**
106-
* @brief Gets dropdown options for a ValueMap or ValueRelation field (lazy-loaded)
107-
* @param filterId ID of filter which is querying this info
108-
* @param searchText Filter options by display text (case-insensitive)
109-
* @param limit Maximum number of options to return (for ValueRelation)
110-
* @return List of maps with "text" (display) and "value" (key) entries
111-
*/
112-
Q_INVOKABLE QVariantList getDropdownOptions( const QString &filterId, const QString &searchText = QString(), int limit = 100 );
113-
114-
/**
115-
* @brief Gets unique values for a field (for multichoice filters)
116-
* TODO: rework to use filter UUID instead
117-
*/
118-
Q_INVOKABLE QStringList getFieldUniqueValues( QgsVectorLayer *layer, const QString &fieldName ) const;
109+
// Indicates if the filter with filterId should use unique values, value relation or value map + returns configs for each
110+
Q_INVOKABLE QVariantMap getDropdownConfiguration( const QString &filterId );
119111

120112
/**
121113
* Queries whether there is any filtering active on the layer specified by ID. Essentially just checks the
@@ -168,12 +160,6 @@ class FilterController : public QObject
168160
* \return String of usable SQL expression
169161
*/
170162
QString buildFieldExpression( const FieldFilter &filter ) const;
171-
//TODO: needs rework to support newer lookup
172-
// utility functions to support value lookup
173-
QVariantList extractValueMapOptions( const QVariantMap &config, const QString &searchText ) const;
174-
QVariantList extractValueRelationOptions( const QVariantMap &config, const QString &searchText, int limit, const QStringList &alwaysIncludeKeys ) const;
175-
QStringList lookupValueMapTexts( const QVariantMap &config, const QStringList &keys ) const;
176-
QStringList lookupValueRelationTexts( const QVariantMap &config, const QStringList &keys ) const;
177163

178164
QList<FieldFilter> mFieldFilters;
179165

0 commit comments

Comments
 (0)