diff --git a/src/core/featurelistmodel.cpp b/src/core/featurelistmodel.cpp index b7e31e7efe..963cb6cbd5 100644 --- a/src/core/featurelistmodel.cpp +++ b/src/core/featurelistmodel.cpp @@ -80,13 +80,20 @@ int FeatureListModel::columnCount( const QModelIndex &parent ) const QVariant FeatureListModel::data( const QModelIndex &index, int role ) const { - if ( role == Qt::DisplayRole || role == DisplayStringRole ) - { - return mEntries.value( index.row() ).displayString; - } - else if ( role == KeyFieldRole ) + if ( index.row() < 0 || index.row() >= mEntries.size() ) + return QVariant(); + + switch ( role ) { - return mEntries.value( index.row() ).key; + case Qt::DisplayRole: + case DisplayStringRole: + return mEntries.value( index.row() ).displayString; + + case KeyFieldRole: + return mEntries.value( index.row() ).key; + + case GroupFieldRole: + return mEntries.value( index.row() ).group; } return QVariant(); @@ -98,6 +105,7 @@ QHash FeatureListModel::roleNames() const roles[KeyFieldRole] = "keyFieldValue"; roles[DisplayStringRole] = "displayString"; + roles[GroupFieldRole] = "groupFieldValue"; return roles; } @@ -167,6 +175,38 @@ void FeatureListModel::setDisplayValueField( const QString &displayValueField ) emit displayValueFieldChanged(); } +QString FeatureListModel::groupField() const +{ + return mGroupField; +} + +void FeatureListModel::setGroupField( const QString &groupField ) +{ + if ( mGroupField == groupField ) + return; + + mGroupField = groupField; + + reloadLayer(); + + emit groupFieldChanged(); +} + +bool FeatureListModel::displayGroupName() const +{ + return mDisplayGroupName; +} + +void FeatureListModel::setDisplayGroupName( bool displayGroupName ) +{ + if ( mDisplayGroupName == displayGroupName ) + return; + + mDisplayGroupName = displayGroupName; + + emit displayGroupNameChanged(); +} + int FeatureListModel::findKey( const QVariant &key ) const { int idx = 0; @@ -255,6 +295,9 @@ void FeatureListModel::gatherFeatureList() if ( !keyField().isNull() ) referencedColumns << mKeyField; + if ( !groupField().isNull() ) + referencedColumns << mGroupField; + referencedColumns << mDisplayValueField; QgsFields fields = mCurrentLayer->fields(); @@ -303,7 +346,7 @@ void FeatureListModel::gatherFeatureList() cleanupGatherer(); - mGatherer = new FeatureExpressionValuesGatherer( mCurrentLayer, fieldDisplayString, request, QStringList() << keyField() ); + mGatherer = new FeatureExpressionValuesGatherer( mCurrentLayer, fieldDisplayString, request, QStringList() << keyField() << groupField() ); connect( mGatherer, &QThread::finished, this, &FeatureListModel::processFeatureList ); mGatherer->start(); } @@ -318,7 +361,7 @@ void FeatureListModel::processFeatureList() QList entries; if ( mAddNull ) - entries.append( Entry( QStringLiteral( "NULL" ), QVariant(), QgsFeatureId() ) ); + entries.append( Entry( QStringLiteral( "NULL" ), QVariant(), QVariant(), QgsFeatureId() ) ); const QVector gatheredEntries = mGatherer->entries(); mGatherer->deleteLater(); @@ -328,7 +371,7 @@ void FeatureListModel::processFeatureList() { Entry entry; - entry = Entry( gatheredEntry.value, gatheredEntry.identifierFields.at( 0 ), gatheredEntry.featureId ); + entry = Entry( gatheredEntry.value, gatheredEntry.identifierFields.at( 0 ), gatheredEntry.identifierFields.at( 1 ), gatheredEntry.featureId ); if ( !mSearchTerm.isEmpty() ) { @@ -340,7 +383,7 @@ void FeatureListModel::processFeatureList() entries.append( entry ); } - if ( mOrderByValue || !mSearchTerm.isEmpty() ) + if ( mOrderByValue || !mGroupField.isEmpty() || !mSearchTerm.isEmpty() ) { std::sort( entries.begin(), entries.end(), [=]( const Entry &entry1, const Entry &entry2 ) { if ( entry1.key.isNull() ) @@ -349,6 +392,11 @@ void FeatureListModel::processFeatureList() if ( entry2.key.isNull() ) return false; + if ( !mGroupField.isEmpty() && entry1.group != entry2.group ) + { + return entry1.group < entry2.group; + } + if ( !mSearchTerm.isEmpty() ) { const bool entry1StartsWithSearchTerm = entry1.displayString.toLower().startsWith( mSearchTerm.toLower() ); diff --git a/src/core/featurelistmodel.h b/src/core/featurelistmodel.h index de15a4814a..58b8bfcda6 100644 --- a/src/core/featurelistmodel.h +++ b/src/core/featurelistmodel.h @@ -42,22 +42,34 @@ class FeatureListModel : public QAbstractItemModel * The vector layer to list */ Q_PROPERTY( QgsVectorLayer *currentLayer READ currentLayer WRITE setCurrentLayer NOTIFY currentLayerChanged ) + /** * The primary key field */ Q_PROPERTY( QString keyField READ keyField WRITE setKeyField NOTIFY keyFieldChanged ) + /** * The display value field */ Q_PROPERTY( QString displayValueField READ displayValueField WRITE setDisplayValueField NOTIFY displayValueFieldChanged ) /** - * Whether the features should be ordered by value + * The grouping key field + */ + Q_PROPERTY( QString groupField READ groupField WRITE setGroupField NOTIFY groupFieldChanged ) + + /** + * Set to TRUE if the group name will be displayed in the list + */ + Q_PROPERTY( bool displayGroupName READ displayGroupName WRITE setDisplayGroupName NOTIFY displayGroupNameChanged ) + + /** + * Set to TRUE if features should be ordered by value */ Q_PROPERTY( bool orderByValue READ orderByValue WRITE setOrderByValue NOTIFY orderByValueChanged ) /** - * Whether a null values is allowed in the list + * Set to TRUE if null values are allowed in the list */ Q_PROPERTY( bool addNull READ addNull WRITE setAddNull NOTIFY addNullChanged ) @@ -81,6 +93,7 @@ class FeatureListModel : public QAbstractItemModel { KeyFieldRole = Qt::UserRole + 1, DisplayStringRole, + GroupFieldRole, }; Q_ENUM( FeatureListRoles ) @@ -112,6 +125,12 @@ class FeatureListModel : public QAbstractItemModel QString displayValueField() const; void setDisplayValueField( const QString &displayValueField ); + QString groupField() const; + void setGroupField( const QString &groupField ); + + bool displayGroupName() const; + void setDisplayGroupName( bool displayGroupName ); + /** * Get the row for a given key value. */ @@ -176,6 +195,8 @@ class FeatureListModel : public QAbstractItemModel void currentLayerChanged(); void keyFieldChanged(); void displayValueFieldChanged(); + void groupFieldChanged(); + void displayGroupNameChanged(); void orderByValueChanged(); void addNullChanged(); void filterExpressionChanged(); @@ -198,9 +219,10 @@ class FeatureListModel : public QAbstractItemModel private: struct Entry { - Entry( const QString &displayString, const QVariant &key, const QgsFeatureId &fid ) + Entry( const QString &displayString, const QVariant &key, const QVariant &group, const QgsFeatureId &fid ) : displayString( displayString ) , key( key ) + , group( group ) , fid( fid ) , fuzzyScore( 0 ) {} @@ -215,6 +237,7 @@ class FeatureListModel : public QAbstractItemModel QString displayString; QVariant key; + QVariant group; QgsFeatureId fid; double fuzzyScore; }; @@ -237,6 +260,8 @@ class FeatureListModel : public QAbstractItemModel QList mEntries; QString mKeyField; QString mDisplayValueField; + QString mGroupField; + bool mDisplayGroupName = false; bool mOrderByValue = false; bool mAddNull = false; QString mFilterExpression; diff --git a/src/qml/RelationCombobox.qml b/src/qml/RelationCombobox.qml index 747d6b381d..85597d6e23 100644 --- a/src/qml/RelationCombobox.qml +++ b/src/qml/RelationCombobox.qml @@ -98,7 +98,7 @@ Item { anchors.left: parent.left anchors.right: parent.right - placeholderText: displayText == '' ? qsTr("Search…") : '' + placeholderText: !focus && displayText == '' ? qsTr("Search…") : '' placeholderTextColor: Theme.mainColor height: fontMetrics.height * 2.5 @@ -163,6 +163,25 @@ Item { } } + section.property: featureListModel.groupField != "" ? "groupFieldValue" : "" + section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels + section.delegate: Component { + Rectangle { + width:parent.width + height: featureListModel.displayGroupName ? 30 : 5 + color: Theme.controlBackgroundAlternateColor + + Text { + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + font.bold: true + font.pointSize: Theme.resultFont.pointSize + color: Theme.mainTextColor + text: section + visible: featureListModel.displayGroupName + } + } + } + delegate: Rectangle { id: delegateRect @@ -175,7 +194,7 @@ Item { anchors.margins: 10 height: radioButton.visible ? radioButton.height : checkBoxButton.height width: parent ? parent.width : undefined - color: model.checked ? Theme.mainColor : Theme.controlBackgroundAlternateColor + color: model.checked ? Theme.mainColor : searchFeaturePopup.Material ? searchFeaturePopup.Material.dialogColor : Theme.mainBackgroundColor Row { RadioButton { @@ -302,15 +321,15 @@ Item { anchors.fill: parent propagateComposedEvents: true - onClicked: { mouse.accepted = false; } - onPressed: { - forceActiveFocus(); - mouse.accepted = false; + onClicked: (mouse) => { mouse.accepted = false } + onPressed: (mouse) => { + forceActiveFocus() + mouse.accepted = false } - onReleased: mouse.accepted = false; - onDoubleClicked: mouse.accepted = false; - onPositionChanged: mouse.accepted = false; - onPressAndHold: mouse.accepted = false; + onReleased: (mouse) => { mouse.accepted = false } + onDoubleClicked: (mouse) => { mouse.accepted = false } + onPositionChanged: (mouse) => { mouse.accepted = false } + onPressAndHold: (mouse) => { mouse.accepted = false } } Component.onCompleted: { @@ -318,9 +337,6 @@ Item { } font: Theme.defaultFont - popup.font: Theme.defaultFont - popup.topMargin: mainWindow.sceneTopMargin - popup.bottomMargin: mainWindow.sceneTopMargin contentItem: Text { leftPadding: enabled ? 5 : 0 @@ -335,6 +351,45 @@ Item { elide: Text.ElideRight } + popup: Popup { + y: comboBox.height - 1 + width: comboBox.width + implicitHeight: contentItem.implicitHeight + padding: 1 + font: Theme.defaultFont + topMargin: mainWindow.sceneTopMargin + bottomMargin: mainWindow.sceneTopMargin + + contentItem: ListView { + clip: true + implicitHeight: Math.min(mainWindow.height - mainWindow.sceneTopMargin - mainWindow.sceneTopMargin, contentHeight) + model: comboBox.popup.visible ? comboBox.delegateModel : null + currentIndex: comboBox.highlightedIndex + + section.property: featureListModel.groupField != "" ? "groupFieldValue" : "" + section.labelPositioning: ViewSection.CurrentLabelAtStart | ViewSection.InlineLabels + section.delegate: Component { + Rectangle { + width:parent.width + height: featureListModel.displayGroupName ? 30 : 5 + color: Theme.mainBackgroundColor + + Text { + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + font.bold: true + font.pointSize: Theme.resultFont.pointSize + color: Theme.mainTextColor + text: section + visible: featureListModel.displayGroupName + } + } + } + + ScrollIndicator.vertical: ScrollIndicator { } + } + } + + background: Item { implicitWidth: 120 implicitHeight: 36 diff --git a/src/qml/editorwidgets/ValueRelation.qml b/src/qml/editorwidgets/ValueRelation.qml index e8173215ab..bc3b64ec85 100644 --- a/src/qml/editorwidgets/ValueRelation.qml +++ b/src/qml/editorwidgets/ValueRelation.qml @@ -34,6 +34,8 @@ EditorWidgetBase { currentFormFeature: currentFeature keyField: config['Key'] displayValueField: config['Value'] + groupField: config['Group'] + displayGroupName: config['DisplayGroupName'] addNull: config['AllowNull'] orderByValue: config['OrderByValue'] filterExpression: config['FilterExpression']