diff --git a/app/filtercontroller.cpp b/app/filtercontroller.cpp index 237991341..dd692a0b2 100644 --- a/app/filtercontroller.cpp +++ b/app/filtercontroller.cpp @@ -359,7 +359,7 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons // Match all positions: only value {k}, first {k,...}, last ...,k}, middle ...,k,... keyConditions << QStringLiteral( "(%1 LIKE '{%2}' OR %1 LIKE '{%2,%%' OR %1 LIKE '%%,%2}' OR %1 LIKE '%%,%2,%%')" ) - .arg( quotedField, escapedKey ); + .arg( quotedField, escapedKey ); } return keyConditions.join( QStringLiteral( " OR " ) ); } @@ -726,7 +726,7 @@ QVariantList FilterController::extractValueRelationOptions( const QVariantMap &c QString escapedSearch = searchText; escapedSearch.replace( "'", "''" ); QString filterExpr = QStringLiteral( "LOWER(%1) LIKE '%%2%'" ) - .arg( QgsExpression::quotedColumnRef( valueFieldName ), escapedSearch.toLower() ); + .arg( QgsExpression::quotedColumnRef( valueFieldName ), escapedSearch.toLower() ); request.setFilterExpression( filterExpr ); } @@ -785,8 +785,8 @@ QVariantList FilterController::extractValueRelationOptions( const QVariantMap &c selectedRequest.setFlags( Qgis::FeatureRequestFlag::NoGeometry ); selectedRequest.setSubsetOfAttributes( QStringList( { keyFieldName, valueFieldName } ), referencedLayer->fields() ); selectedRequest.setFilterExpression( - QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) ) - ); + QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) ) + ); QVariantList selectedItems; QgsFeatureIterator selIt = referencedLayer->getFeatures( selectedRequest ); @@ -856,8 +856,8 @@ QStringList FilterController::lookupValueRelationTexts( const QVariantMap &confi request.setFlags( Qgis::FeatureRequestFlag::NoGeometry ); request.setSubsetOfAttributes( QStringList( { keyFieldName, valueFieldName } ), referencedLayer->fields() ); request.setFilterExpression( - QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) ) - ); + QStringLiteral( "%1 IN (%2)" ).arg( QgsExpression::quotedColumnRef( keyFieldName ), quotedKeys.join( QStringLiteral( ", " ) ) ) + ); QgsFeatureIterator it = referencedLayer->getFeatures( request ); QgsFeature feature; @@ -868,3 +868,11 @@ QStringList FilterController::lookupValueRelationTexts( const QVariantMap &confi return texts; } + +bool FilterController::isReferencingLayerFiltered( const QgsRelation &relation ) const +{ + QgsVectorLayer *layer = qobject_cast( relation.referencingLayer() ); + if ( !layer ) + return false; + return !layer->subsetString().isEmpty(); +} diff --git a/app/filtercontroller.h b/app/filtercontroller.h index 8dd21be06..b172a0670 100644 --- a/app/filtercontroller.h +++ b/app/filtercontroller.h @@ -14,6 +14,8 @@ #include #include +#include "qgsrelation.h" + class QgsVectorLayer; class QgsMapLayer; @@ -203,6 +205,11 @@ class FilterController : public QObject */ Q_INVOKABLE QVariantList getVectorLayers() const; + /** + * @brief Returns true if the referencing layer of the given relation has active filters applied + */ + Q_INVOKABLE bool isReferencingLayerFiltered( const QgsRelation &relation ) const; + /** * @brief Discards pending filter changes, reverting to the last applied state. * Call this when the user closes the filter drawer without pressing "Show results". diff --git a/app/qml/CMakeLists.txt b/app/qml/CMakeLists.txt index ad6db84a0..ec241473e 100644 --- a/app/qml/CMakeLists.txt +++ b/app/qml/CMakeLists.txt @@ -62,6 +62,7 @@ set(MM_QML filters/MMFilterChip.qml filters/MMFilterLayerSection.qml filters/MMFiltersPanel.qml + filters/components/MMFilterBanner.qml dialogs/MMCloseAccountDialog.qml dialogs/MMDownloadProjectDialog.qml dialogs/MMMigrateToMerginDialog.qml diff --git a/app/qml/components/MMButton.qml b/app/qml/components/MMButton.qml index 0c91ef0d9..e5788503e 100644 --- a/app/qml/components/MMButton.qml +++ b/app/qml/components/MMButton.qml @@ -15,7 +15,7 @@ Button { id: root enum Types { Primary, Secondary, Tertiary } - enum Sizes { Small, Regular } + enum Sizes { Small, Regular, ExtraSmall } property int type: MMButton.Types.Primary property int size: MMButton.Sizes.Regular @@ -162,11 +162,22 @@ Button { state: "default" - implicitHeight: root.type === MMButton.Types.Tertiary ? buttonContent.height : buttonContent.height + topPadding + bottomPadding - implicitWidth: row.paintedChildrenWidth + 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + implicitHeight: { + if ( root.type === MMButton.Types.Tertiary && root.size !== MMButton.Sizes.ExtraSmall ) + return buttonContent.height + else + return buttonContent.height + topPadding + bottomPadding + } + implicitWidth: { + if ( root.size === MMButton.Sizes.ExtraSmall ) return row.paintedChildrenWidth + 2 * __style.margin8 + return row.paintedChildrenWidth + 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + } topPadding: { - if ( root.type === MMButton.Types.Tertiary ) { + if ( root.size === MMButton.Sizes.ExtraSmall ) { + return __style.margin2; + } + else if ( root.type === MMButton.Types.Tertiary ) { return 0; } else if ( root.size === MMButton.Sizes.Small ) { @@ -178,7 +189,10 @@ Button { } bottomPadding: { - if ( root.type === MMButton.Types.Tertiary ) { + if ( root.size === MMButton.Sizes.ExtraSmall ) { + return __style.margin2; + } + else if ( root.type === MMButton.Types.Tertiary ) { return 0; } else if ( root.size === MMButton.Sizes.Small ) { @@ -200,7 +214,10 @@ Button { id: row property real paintedChildrenWidth: buttonIconLeft.paintedWidth + buttonContent.implicitWidth + buttonIconRight.paintedWidth + spacing - property real maxWidth: parent.width - 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + property real maxWidth: { + if ( root.size === MMButton.Sizes.ExtraSmall ) return parent.width - 2 * __style.margin8 + return parent.width - 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + } x: ( parent.width - width ) / 2 @@ -232,7 +249,7 @@ Button { width: parent.width - buttonIconLeft.paintedWidth - buttonIconRight.paintedWidth - font: __style.t3 + font: root.size === MMButton.Sizes.ExtraSmall ? __style.t5 : __style.t3 text: root.text } @@ -266,7 +283,7 @@ Button { background: Rectangle { id: buttonBackground - radius: __style.radius30 + radius: root.size === MMButton.Sizes.ExtraSmall ? __style.radius40 : __style.radius30 border.width: 2 * __dp } diff --git a/app/qml/components/private/MMBaseInput.qml b/app/qml/components/private/MMBaseInput.qml index 0044acf53..793105f10 100644 --- a/app/qml/components/private/MMBaseInput.qml +++ b/app/qml/components/private/MMBaseInput.qml @@ -32,6 +32,7 @@ Item { property string errorMsg: "" property string warningMsg: "" + property string hintMsg: "" property alias inputContent: contentGroup.children @@ -165,6 +166,38 @@ Item { height: childrenRect.height } + Item { + // hint message + + width: parent.width + height: hintMessageGroup.implicitHeight + + visible: root.hintMsg !== "" + + RowLayout { + id: hintMessageGroup + + width: parent.width + + MMComponents.MMIcon { + source: __style.infoFilledIcon + size: __style.icon16 + color: __style.informativeColor + } + + MMComponents.MMText { + Layout.fillWidth: true + + text: root.hintMsg + color: __style.deepOceanColor + font: __style.t4 + + wrapMode: Text.Wrap + maximumLineCount: 10 + } + } + } + Item { // validation messages diff --git a/app/qml/filters/components/MMFilterBanner.qml b/app/qml/filters/components/MMFilterBanner.qml new file mode 100644 index 000000000..36bc4e10a --- /dev/null +++ b/app/qml/filters/components/MMFilterBanner.qml @@ -0,0 +1,59 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +import QtQuick + +import "../../components" as MMComponents + +Rectangle { + id: root + + property string text + property string actionText: "" + + signal actionClicked() + + color: __style.informativeColor + radius: __style.radius8 + implicitHeight: bannerText.implicitHeight + 2 * __style.margin8 + + MMComponents.MMText { + id: bannerText + + anchors.left: parent.left + anchors.leftMargin: __style.margin12 + anchors.right: actionButton.visible ? actionButton.left : parent.right + anchors.rightMargin: actionButton.visible ? __style.spacing8 : __style.margin12 + anchors.verticalCenter: parent.verticalCenter + + text: root.text + font: __style.t4 + color: __style.deepOceanColor + wrapMode: Text.Wrap + elide: Text.ElideNone + } + + MMComponents.MMButton { + id: actionButton + + visible: root.actionText !== "" + + type: MMComponents.MMButton.Types.Tertiary + size: MMComponents.MMButton.Sizes.ExtraSmall + text: root.actionText + fontColor: __style.skyColor + bgndColor: __style.deepOceanColor + + anchors.right: parent.right + anchors.rightMargin: __style.margin8 + anchors.verticalCenter: parent.verticalCenter + + onClicked: root.actionClicked() + } +} diff --git a/app/qml/form/components/MMFeaturesListPageDrawer.qml b/app/qml/form/components/MMFeaturesListPageDrawer.qml index da8797332..3c1985a07 100644 --- a/app/qml/form/components/MMFeaturesListPageDrawer.qml +++ b/app/qml/form/components/MMFeaturesListPageDrawer.qml @@ -12,6 +12,7 @@ import QtQuick.Controls import "../../inputs" as MMInputs import "../../components" as MMComponents +import "../../filters/components" as MMFilters // // Special type of drawer/page -> looks like page, but it is actually Drawer @@ -75,11 +76,26 @@ Drawer { MMComponents.MMListSpacer { height: __style.spacing20 } + MMFilters.MMFilterBanner { + id: filterBanner + + visible: globalFilterController.hasActiveFilters + + width: parent.width + text: qsTr( "Some features may be hidden by active filters" ) + } + + MMComponents.MMListSpacer { + visible: filterBanner.visible + height: __style.spacing10 + } + MMComponents.MMListView { id: listView width: parent.width height: parent.height - 2 * __style.spacing20 - searchBar.height + - ( filterBanner.visible ? filterBanner.height + __style.spacing10 : 0 ) clip: true diff --git a/app/qml/form/editors/MMFormGalleryEditor.qml b/app/qml/form/editors/MMFormGalleryEditor.qml index bfa98db84..86d7c71c5 100644 --- a/app/qml/form/editors/MMFormGalleryEditor.qml +++ b/app/qml/form/editors/MMFormGalleryEditor.qml @@ -30,6 +30,10 @@ MMPrivateComponents.MMBaseInput { title: _fieldShouldShowTitle ? _fieldTitle : "" + hintMsg: root._fieldAssociatedRelation && globalFilterController.hasActiveFilters + ? qsTr( "Some features may be hidden by active filters" ) + : "" + inputContent: MMComponents.MMListView { id: rowView diff --git a/app/qml/form/editors/MMFormRelationEditor.qml b/app/qml/form/editors/MMFormRelationEditor.qml index c2206f970..67386714e 100644 --- a/app/qml/form/editors/MMFormRelationEditor.qml +++ b/app/qml/form/editors/MMFormRelationEditor.qml @@ -42,6 +42,10 @@ MMPrivateComponents.MMBaseInput { title: _fieldShouldShowTitle ? _fieldTitle : "" + hintMsg: root._fieldAssociatedRelation && globalFilterController.hasActiveFilters + ? qsTr( "Some features may be hidden by active filters" ) + : "" + inputContent: Rectangle { width: parent.width height: privates.itemHeight * privates.rows + 2 * flow.spacing + 2 * __style.margin12 diff --git a/app/qml/layers/MMFeaturesListPage.qml b/app/qml/layers/MMFeaturesListPage.qml index e5232c7d7..b1a7a56a9 100644 --- a/app/qml/layers/MMFeaturesListPage.qml +++ b/app/qml/layers/MMFeaturesListPage.qml @@ -15,6 +15,7 @@ import mm 1.0 as MM import "../inputs" import "../components" as MMComponents +import "../filters/components" as MMFilterComponents MMComponents.MMPage { id: root @@ -33,70 +34,36 @@ MMComponents.MMPage { width: parent.width height: parent.height - Rectangle { - id: filterNotification + MMSearchInput { + id: searchBar anchors.top: parent.top anchors.topMargin: __style.spacing20 width: parent.width - height: filterRow.implicitHeight + 2 * __style.margin8 - radius: __style.radius12 - - visible: root.selectedLayer && globalFilterController.filteredLayerIds.indexOf(root.selectedLayer.id) >= 0 - color: __style.sandColor - border.width: 1 * __dp - border.color: __style.sunsetColor - - Row { - id: filterRow - - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: __style.margin12 - anchors.rightMargin: __style.margin12 - - spacing: __style.margin4 - - MMComponents.MMText { - width: parent.width - resetButton.width - parent.spacing - text: qsTr("Some features are hidden by a filter.") - font: __style.p6 - color: __style.nightColor - anchors.verticalCenter: parent.verticalCenter - wrapMode: Text.Wrap - } - - MMComponents.MMButton { - id: resetButton - - type: MMButton.Types.Tertiary - text: qsTr("Reset") - fontColor: __style.earthColor - size: MMButton.Sizes.Small - anchors.verticalCenter: parent.verticalCenter - - onClicked: { - globalFilterController.clearAllFilters() - globalFilterController.applyFiltersToAllLayers() - featuresModel.reloadFeatures() - } - } - } + delayedSearch: true + onSearchTextChanged: featuresModel.searchExpression = searchBar.text } - MMSearchInput { - id: searchBar + MMFilterComponents.MMFilterBanner { + id: filterBanner - anchors.top: filterNotification.visible ? filterNotification.bottom : parent.top + anchors.top: searchBar.bottom anchors.topMargin: __style.spacing20 width: parent.width - delayedSearch: true - onSearchTextChanged: featuresModel.searchExpression = searchBar.text + visible: root.selectedLayer && globalFilterController.filteredLayerIds.indexOf(root.selectedLayer.id) >= 0 + + text: qsTr("Active filters applied") + actionText: qsTr("Reset") + + onActionClicked: { + globalFilterController.clearAllFilters() + globalFilterController.applyFiltersToAllLayers() + featuresModel.reloadFeatures() + } } MMComponents.MMListView { @@ -105,9 +72,9 @@ MMComponents.MMPage { width: parent.width anchors { - top: searchBar.bottom + top: filterBanner.visible ? filterBanner.bottom : searchBar.bottom bottom: parent.bottom - topMargin: __style.spacing20 + topMargin: filterBanner.visible ? __style.spacing10 : __style.spacing20 } model: MM.LayerFeaturesModel {