From d24ec659d4eafeea1d4d99f044cf76dd3ed920b4 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Wed, 25 Mar 2026 17:28:13 +0200 Subject: [PATCH 1/2] Added filter banner Added extra small MMButton size Added function to check if the referencing layer is filtered Added info message in gallery and relation editor pages --- app/filtercontroller.cpp | 20 ++-- app/filtercontroller.h | 7 ++ app/qml/CMakeLists.txt | 1 + app/qml/components/MMButton.qml | 22 ++++- app/qml/components/private/MMBaseInput.qml | 33 +++++++ app/qml/filters/components/MMFilterBanner.qml | 93 +++++++++++++++++++ app/qml/form/editors/MMFormGalleryEditor.qml | 4 + app/qml/form/editors/MMFormRelationEditor.qml | 4 + app/qml/layers/MMFeaturesListPage.qml | 51 ++-------- 9 files changed, 182 insertions(+), 53 deletions(-) create mode 100644 app/qml/filters/components/MMFilterBanner.qml 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..ca09ad654 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 @@ -163,7 +163,10 @@ 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 ) + implicitWidth: { + if ( root.size === MMButton.Sizes.ExtraSmall ) return row.paintedChildrenWidth + 2 * __style.margin12 + return row.paintedChildrenWidth + 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + } topPadding: { if ( root.type === MMButton.Types.Tertiary ) { @@ -172,6 +175,9 @@ Button { else if ( root.size === MMButton.Sizes.Small ) { return 7 * __dp; } + else if ( root.size === MMButton.Sizes.ExtraSmall ) { + return 2 * __dp; + } else { return 11 * __dp; } @@ -184,6 +190,9 @@ Button { else if ( root.size === MMButton.Sizes.Small ) { return 7 * __dp; } + else if ( root.size === MMButton.Sizes.ExtraSmall ) { + return 2 * __dp; + } else { return 11 * __dp; } @@ -200,7 +209,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.margin12 + return parent.width - 2 * ( root.size === MMButton.Sizes.Small ? __style.margin16 : __style.margin20 ) + } x: ( parent.width - width ) / 2 @@ -232,7 +244,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 +278,7 @@ Button { background: Rectangle { id: buttonBackground - radius: __style.radius30 + radius: root.size === MMButton.Sizes.ExtraSmall ? __style.radius12 : __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..d69809e77 --- /dev/null +++ b/app/qml/filters/components/MMFilterBanner.qml @@ -0,0 +1,93 @@ +/*************************************************************************** + * * + * 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 bool showIcon: false + property string actionText: "" + property bool showClose: false + + signal actionClicked() + signal closeClicked() + + color: __style.informativeColor + radius: __style.radius8 + implicitHeight: bannerText.implicitHeight + 2 * __style.margin12 + + MMComponents.MMIcon { + id: infoIcon + + visible: root.showIcon + + anchors.left: parent.left + anchors.leftMargin: __style.margin12 + anchors.verticalCenter: parent.verticalCenter + + source: __style.infoIcon + color: __style.deepOceanColor + size: __style.icon24 + } + + MMComponents.MMText { + id: bannerText + + anchors.left: infoIcon.visible ? infoIcon.right : parent.left + anchors.leftMargin: infoIcon.visible ? __style.spacing8 : __style.margin12 + anchors.right: actionButton.visible ? actionButton.left : ( closeButton.visible ? closeButton.left : parent.right ) + anchors.rightMargin: ( actionButton.visible || closeButton.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.margin12 + anchors.verticalCenter: parent.verticalCenter + + onClicked: root.actionClicked() + } + + MMComponents.MMRoundButton { + id: closeButton + + visible: root.showClose + + iconSource: __style.closeIcon + iconColor: __style.deepOceanColor + bgndColor: __style.transparentColor + bgndHoverColor: __style.transparentColor + + anchors.right: parent.right + anchors.rightMargin: __style.margin4 + anchors.verticalCenter: parent.verticalCenter + + onClicked: root.closeClicked() + } +} 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..0ca8330ec 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,57 +34,23 @@ MMComponents.MMPage { width: parent.width height: parent.height - Rectangle { + MMFilterComponents.MMFilterBanner { id: filterNotification 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() - } - } + text: qsTr("Active filters applied") + actionText: qsTr("Reset") + + onActionClicked: { + globalFilterController.clearAllFilters() + globalFilterController.applyFiltersToAllLayers() + featuresModel.reloadFeatures() } } From 8f4d1be7ac60227f488124a2f01e1abc23253045 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 27 Mar 2026 15:23:35 +0200 Subject: [PATCH 2/2] Modified MMButton extraSmall size to match the design Simplified Filter banner component Added banner in the linked features corrected position according to figma design --- app/qml/components/MMButton.qml | 29 +++++++----- app/qml/filters/components/MMFilterBanner.qml | 46 +++---------------- .../components/MMFeaturesListPageDrawer.qml | 16 +++++++ app/qml/layers/MMFeaturesListPage.qml | 32 ++++++------- 4 files changed, 55 insertions(+), 68 deletions(-) diff --git a/app/qml/components/MMButton.qml b/app/qml/components/MMButton.qml index ca09ad654..e5788503e 100644 --- a/app/qml/components/MMButton.qml +++ b/app/qml/components/MMButton.qml @@ -162,37 +162,42 @@ Button { state: "default" - implicitHeight: root.type === MMButton.Types.Tertiary ? buttonContent.height : buttonContent.height + topPadding + bottomPadding + 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.margin12 + 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 ) { return 7 * __dp; } - else if ( root.size === MMButton.Sizes.ExtraSmall ) { - return 2 * __dp; - } else { return 11 * __dp; } } 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 ) { return 7 * __dp; } - else if ( root.size === MMButton.Sizes.ExtraSmall ) { - return 2 * __dp; - } else { return 11 * __dp; } @@ -210,7 +215,7 @@ Button { property real paintedChildrenWidth: buttonIconLeft.paintedWidth + buttonContent.implicitWidth + buttonIconRight.paintedWidth + spacing property real maxWidth: { - if ( root.size === MMButton.Sizes.ExtraSmall ) return parent.width - 2 * __style.margin12 + 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 ) } @@ -278,7 +283,7 @@ Button { background: Rectangle { id: buttonBackground - radius: root.size === MMButton.Sizes.ExtraSmall ? __style.radius12 : __style.radius30 + radius: root.size === MMButton.Sizes.ExtraSmall ? __style.radius40 : __style.radius30 border.width: 2 * __dp } diff --git a/app/qml/filters/components/MMFilterBanner.qml b/app/qml/filters/components/MMFilterBanner.qml index d69809e77..36bc4e10a 100644 --- a/app/qml/filters/components/MMFilterBanner.qml +++ b/app/qml/filters/components/MMFilterBanner.qml @@ -15,38 +15,21 @@ Rectangle { id: root property string text - property bool showIcon: false property string actionText: "" - property bool showClose: false signal actionClicked() - signal closeClicked() color: __style.informativeColor radius: __style.radius8 - implicitHeight: bannerText.implicitHeight + 2 * __style.margin12 - - MMComponents.MMIcon { - id: infoIcon - - visible: root.showIcon - - anchors.left: parent.left - anchors.leftMargin: __style.margin12 - anchors.verticalCenter: parent.verticalCenter - - source: __style.infoIcon - color: __style.deepOceanColor - size: __style.icon24 - } + implicitHeight: bannerText.implicitHeight + 2 * __style.margin8 MMComponents.MMText { id: bannerText - anchors.left: infoIcon.visible ? infoIcon.right : parent.left - anchors.leftMargin: infoIcon.visible ? __style.spacing8 : __style.margin12 - anchors.right: actionButton.visible ? actionButton.left : ( closeButton.visible ? closeButton.left : parent.right ) - anchors.rightMargin: ( actionButton.visible || closeButton.visible ) ? __style.spacing8 : __style.margin12 + 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 @@ -68,26 +51,9 @@ Rectangle { bgndColor: __style.deepOceanColor anchors.right: parent.right - anchors.rightMargin: __style.margin12 + anchors.rightMargin: __style.margin8 anchors.verticalCenter: parent.verticalCenter onClicked: root.actionClicked() } - - MMComponents.MMRoundButton { - id: closeButton - - visible: root.showClose - - iconSource: __style.closeIcon - iconColor: __style.deepOceanColor - bgndColor: __style.transparentColor - bgndHoverColor: __style.transparentColor - - anchors.right: parent.right - anchors.rightMargin: __style.margin4 - anchors.verticalCenter: parent.verticalCenter - - onClicked: root.closeClicked() - } } 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/layers/MMFeaturesListPage.qml b/app/qml/layers/MMFeaturesListPage.qml index 0ca8330ec..b1a7a56a9 100644 --- a/app/qml/layers/MMFeaturesListPage.qml +++ b/app/qml/layers/MMFeaturesListPage.qml @@ -34,14 +34,26 @@ MMComponents.MMPage { width: parent.width height: parent.height - MMFilterComponents.MMFilterBanner { - id: filterNotification + MMSearchInput { + id: searchBar anchors.top: parent.top anchors.topMargin: __style.spacing20 width: parent.width + delayedSearch: true + onSearchTextChanged: featuresModel.searchExpression = searchBar.text + } + + MMFilterComponents.MMFilterBanner { + id: filterBanner + + anchors.top: searchBar.bottom + anchors.topMargin: __style.spacing20 + + width: parent.width + visible: root.selectedLayer && globalFilterController.filteredLayerIds.indexOf(root.selectedLayer.id) >= 0 text: qsTr("Active filters applied") @@ -54,27 +66,15 @@ MMComponents.MMPage { } } - MMSearchInput { - id: searchBar - - anchors.top: filterNotification.visible ? filterNotification.bottom : parent.top - anchors.topMargin: __style.spacing20 - - width: parent.width - - delayedSearch: true - onSearchTextChanged: featuresModel.searchExpression = searchBar.text - } - MMComponents.MMListView { id: listView 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 {