From bad8cd09a5cc0baa01c06ec49dd6bc9518ac387e Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Thu, 26 Mar 2026 19:37:24 +0200 Subject: [PATCH 1/4] Updated the filter icon Deleted filled filter icon Updated visuals for the chip Changed the chip position Formatted code --- app/icons/Filter.svg | 11 +++++++-- app/icons/FilterFilled.svg | 3 --- app/icons/icons.qrc | 1 - app/mmstyle.h | 2 -- app/qml/map/MMMapController.qml | 31 ++++++++++---------------- app/qml/map/components/MMMapButton.qml | 2 ++ 6 files changed, 23 insertions(+), 27 deletions(-) delete mode 100644 app/icons/FilterFilled.svg diff --git a/app/icons/Filter.svg b/app/icons/Filter.svg index d62e7cf66..f9a65ccca 100644 --- a/app/icons/Filter.svg +++ b/app/icons/Filter.svg @@ -1,3 +1,10 @@ - - + + + + + + + + + diff --git a/app/icons/FilterFilled.svg b/app/icons/FilterFilled.svg deleted file mode 100644 index 03a60d659..000000000 --- a/app/icons/FilterFilled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/icons/icons.qrc b/app/icons/icons.qrc index ce46904c9..656d4b479 100644 --- a/app/icons/icons.qrc +++ b/app/icons/icons.qrc @@ -28,7 +28,6 @@ Features.svg FeaturesFilled.svg Filter.svg - FilterFilled.svg GPSAntennaHeight.svg GPSIcon.svg GPSSatellite.svg diff --git a/app/mmstyle.h b/app/mmstyle.h index f7d896d36..aa2207657 100644 --- a/app/mmstyle.h +++ b/app/mmstyle.h @@ -128,7 +128,6 @@ class MMStyle: public QObject Q_PROPERTY( QUrl facebookIcon READ facebookIcon CONSTANT ) Q_PROPERTY( QUrl featuresIcon READ featuresIcon CONSTANT ) Q_PROPERTY( QUrl filterIcon READ filterIcon CONSTANT ) - Q_PROPERTY( QUrl filterFilledIcon READ filterFilledIcon CONSTANT ) Q_PROPERTY( QUrl globeIcon READ globeIcon CONSTANT ) Q_PROPERTY( QUrl globalIcon READ globalIcon CONSTANT ) Q_PROPERTY( QUrl gpsIcon READ gpsIcon CONSTANT ) @@ -433,7 +432,6 @@ class MMStyle: public QObject QUrl deleteIcon() const {return QUrl( "qrc:/Delete.svg" );} QUrl featuresIcon() const {return QUrl( "qrc:/Features.svg" );} QUrl filterIcon() const {return QUrl( "qrc:/Filter.svg" );} - QUrl filterFilledIcon() const {return QUrl( "qrc:/FilterFilled.svg" );} QUrl downloadIcon() const {return QUrl( "qrc:/Download.svg" );} QUrl uploadIcon() const {return QUrl( "qrc:/Upload.svg" );} QUrl editIcon() const {return QUrl( "qrc:/Edit.svg" );} diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index e512bb7d2..61505c9c4 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -588,25 +588,6 @@ Item { } } - // Filter indicator button - left side, 20% from top - MMMapButton { - id: filterIndicatorButton - - visible: root.state === "view" && __activeProject.filterController && (__activeProject.filterController.hasActiveFilters || AppSettings.alwaysShowFilterButton) - iconSource: __activeProject.filterController && __activeProject.filterController.hasActiveFilters ? __style.filterFilledIcon : __style.filterIcon - bgndColor: __activeProject.filterController && __activeProject.filterController.hasActiveFilters ? __style.sandColor : __style.polarColor - - anchors { - left: parent.left - top: parent.top - topMargin: parent.height * 0.2 - } - - onClicked: { - root.openFiltersPanel() - } - } - Item { // bottom buttons group width: parent.width @@ -876,6 +857,18 @@ Item { } } + MMMapButton { + id: filterIndicatorButton + + visible: root.state === "view" && root.filterController + iconSource: __style.filterIcon + bgndColor: root.filterController && root.filterController.hasActiveFilters ? __style.positiveColor : __style.polarColor + + onClicked: { + root.openFiltersPanel() + } + } + MMMapButton { id: gpsButton diff --git a/app/qml/map/components/MMMapButton.qml b/app/qml/map/components/MMMapButton.qml index 4f0074b2e..3f51db7a0 100644 --- a/app/qml/map/components/MMMapButton.qml +++ b/app/qml/map/components/MMMapButton.qml @@ -19,6 +19,7 @@ Item { property alias iconSource: icon.source property color bgndColor: __style.polarColor + property real iconSize: __style.icon32 signal clicked signal clickAndHold @@ -37,6 +38,7 @@ Item { anchors.centerIn: parent color: __style.forestColor + size: control.iconSize } MouseArea { From eda0cd8e199d3ed86a7d987f7242c5b4139987d8 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 27 Mar 2026 16:13:25 +0200 Subject: [PATCH 2/4] Formatted qml code Updated the chip visuals --- app/qml/filters/MMFilterLayerSection.qml | 96 ++++++++++++------------ app/qml/filters/MMFiltersPanel.qml | 4 +- app/qml/map/MMMapController.qml | 1 + app/qml/map/components/MMMapButton.qml | 2 +- 4 files changed, 52 insertions(+), 51 deletions(-) diff --git a/app/qml/filters/MMFilterLayerSection.qml b/app/qml/filters/MMFilterLayerSection.qml index 9c5f5d0f4..93a87480c 100644 --- a/app/qml/filters/MMFilterLayerSection.qml +++ b/app/qml/filters/MMFilterLayerSection.qml @@ -54,7 +54,7 @@ Column { property var fieldInfo: modelData property string fieldName: fieldInfo ? fieldInfo.name : "" - property string fieldDisplayName: fieldInfo ? (fieldInfo.displayName || fieldInfo.name) : "" + property string fieldDisplayName: fieldInfo ? ( fieldInfo.displayName || fieldInfo.name ) : "" property string filterType: fieldInfo ? fieldInfo.filterType : "text" property var currentValue: fieldInfo ? fieldInfo.currentValue : null property var currentValueTo: fieldInfo ? fieldInfo.currentValueTo : null @@ -82,17 +82,17 @@ Column { spacing: __style.margin12 property bool rangeInvalid: { - let fromVal = parseFloat(fromNumberInput.text) - let toVal = parseFloat(toNumberInput.text) - return !isNaN(fromVal) && !isNaN(toVal) && fromVal > toVal + let fromVal = parseFloat( fromNumberInput.text ) + let toVal = parseFloat( toNumberInput.text ) + return !isNaN( fromVal ) && !isNaN( toVal ) && fromVal > toVal } MMTextInput { id: fromNumberInput - width: (parent.width - __style.margin12) / 2 - placeholderText: qsTr("From") - text: fieldDelegate.currentValue !== null && fieldDelegate.currentValue !== undefined ? String(fieldDelegate.currentValue) : "" - errorMsg: parent.rangeInvalid ? qsTr("\"From\" must be less than \"To\"") : "" + width: ( parent.width - __style.margin12 ) / 2 + placeholderText: qsTr( "From" ) + text: fieldDelegate.currentValue !== null && fieldDelegate.currentValue !== undefined ? String( fieldDelegate.currentValue ) : "" + errorMsg: parent.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : "" property bool initialized: false Component.onCompleted: initialized = true @@ -105,9 +105,9 @@ Column { MMTextInput { id: toNumberInput - width: (parent.width - __style.margin12) / 2 - placeholderText: qsTr("To") - text: fieldDelegate.currentValueTo !== null && fieldDelegate.currentValueTo !== undefined ? String(fieldDelegate.currentValueTo) : "" + width: ( parent.width - __style.margin12 ) / 2 + placeholderText: qsTr( "To" ) + text: fieldDelegate.currentValueTo !== null && fieldDelegate.currentValueTo !== undefined ? String( fieldDelegate.currentValueTo ) : "" property bool initialized: false Component.onCompleted: initialized = true @@ -138,12 +138,12 @@ Column { spacing: __style.margin12 property bool rangeInvalid: { - if (!fromDateInput.selectedDate || !toDateInput.selectedDate) return false + if ( !fromDateInput.selectedDate || !toDateInput.selectedDate ) return false return fromDateInput.selectedDate > toDateInput.selectedDate } Item { - width: (parent.width - __style.margin12) / 2 + width: ( parent.width - __style.margin12 ) / 2 height: fromDateInput.height MMPrivateComponents.MMBaseSingleLineInput { @@ -154,20 +154,20 @@ Column { Component.onCompleted: { let val = fieldDelegate.currentValue - if (val !== null && val !== undefined) { - let d = new Date(val) - if (!isNaN(d.getTime())) selectedDate = d + if ( val !== null && val !== undefined ) { + let d = new Date( val ) + if ( !isNaN( d.getTime() ) ) selectedDate = d } } - placeholderText: qsTr("From") + placeholderText: qsTr( "From" ) text: { - if (!selectedDate) return "" - if (fieldDelegate.hasTime) return Qt.formatDateTime(selectedDate, Qt.DefaultLocaleShortDate) - return Qt.formatDate(selectedDate, Qt.DefaultLocaleShortDate) + if ( !selectedDate ) return "" + if ( fieldDelegate.hasTime ) return Qt.formatDateTime( selectedDate, Qt.DefaultLocaleShortDate ) + return Qt.formatDate( selectedDate, Qt.DefaultLocaleShortDate ) } textField.readOnly: true - errorMsg: dateRangeRow.rangeInvalid ? qsTr("\"From\" must be less than \"To\"") : "" + errorMsg: dateRangeRow.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : "" rightContent: MMIcon { size: __style.icon24 @@ -177,7 +177,7 @@ Column { onTextClicked: fromCalendarLoader.active = true onRightContentClicked: { - if (fromDateInput.selectedDate) { + if ( fromDateInput.selectedDate ) { fromDateInput.selectedDate = null let toDate = toDateInput.selectedDate ? toDateInput.selectedDate : null __activeProject.filterController.setDateFilter(root.layerId, fieldDelegate.fieldName, null, toDate, fieldDelegate.hasTime) @@ -216,7 +216,7 @@ Column { } Item { - width: (parent.width - __style.margin12) / 2 + width: ( parent.width - __style.margin12 ) / 2 height: toDateInput.height MMPrivateComponents.MMBaseSingleLineInput { @@ -227,17 +227,17 @@ Column { Component.onCompleted: { let val = fieldDelegate.currentValueTo - if (val !== null && val !== undefined) { - let d = new Date(val) - if (!isNaN(d.getTime())) selectedDate = d + if ( val !== null && val !== undefined ) { + let d = new Date( val ) + if ( !isNaN( d.getTime() ) ) selectedDate = d } } - placeholderText: qsTr("To") + placeholderText: qsTr( "To" ) text: { - if (!selectedDate) return "" - if (fieldDelegate.hasTime) return Qt.formatDateTime(selectedDate, Qt.DefaultLocaleShortDate) - return Qt.formatDate(selectedDate, Qt.DefaultLocaleShortDate) + if ( !selectedDate ) return "" + if ( fieldDelegate.hasTime ) return Qt.formatDateTime( selectedDate, Qt.DefaultLocaleShortDate ) + return Qt.formatDate( selectedDate, Qt.DefaultLocaleShortDate ) } textField.readOnly: true @@ -249,7 +249,7 @@ Column { onTextClicked: toCalendarLoader.active = true onRightContentClicked: { - if (toDateInput.selectedDate) { + if ( toDateInput.selectedDate ) { toDateInput.selectedDate = null let fromDate = fromDateInput.selectedDate ? fromDateInput.selectedDate : null __activeProject.filterController.setDateFilter(root.layerId, fieldDelegate.fieldName, fromDate, null, fieldDelegate.hasTime) @@ -295,13 +295,13 @@ Column { width: parent.width visible: fieldDelegate.filterType === "text" title: fieldDelegate.fieldDisplayName - placeholderText: qsTr("Type to filter...") + placeholderText: qsTr( "Type to filter..." ) // Explicitly handle undefined/null values text: { let val = fieldDelegate.currentValue - if (val !== null && val !== undefined && val !== "") { - return String(val) + if ( val !== null && val !== undefined && val !== "" ) { + return String( val ) } return "" } @@ -311,7 +311,7 @@ Column { Component.onCompleted: initialized = true onTextChanged: { - if (!initialized) return + if ( !initialized ) return // Pass raw text to C++ - validation happens there __activeProject.filterController.setTextFilter(root.layerId, fieldDelegate.fieldName, text) } @@ -338,14 +338,14 @@ Column { width: parent.width textField.readOnly: true - placeholderText: qsTr("Select...") + placeholderText: qsTr( "Select..." ) text: { let texts = fieldDelegate.currentValueTexts - if (!texts || texts.length === 0) return "" - if (fieldDelegate.multiSelect && texts.length > 1) { - return qsTr("%1 selected").arg(texts.length) + if ( !texts || texts.length === 0 ) return "" + if ( fieldDelegate.multiSelect && texts.length > 1 ) { + return qsTr( "%1 selected" ).arg( texts.length ) } - return texts.join(", ") + return texts.join( ", " ) } rightContent: MMIcon { @@ -386,7 +386,7 @@ Column { list.model: ListModel { id: dropdownListModel } - onSearchTextChanged: function(searchText) { + onSearchTextChanged: function( searchText ) { internal.pendingSearchText = searchText searchDebounceTimer.restart() } @@ -410,15 +410,15 @@ Column { interval: 300 repeat: false onTriggered: { - populateOptions(internal.pendingSearchText) + populateOptions( internal.pendingSearchText ) } } function populateOptions(searchText) { let options = __activeProject.filterController.getDropdownOptions(root.vectorLayer, fieldDelegate.fieldName, searchText, 100) dropdownListModel.clear() - for (let i = 0; i < options.length; i++) { - dropdownListModel.append(options[i]) + for ( let i = 0; i < options.length; i++ ) { + dropdownListModel.append( options[i] ) } } @@ -426,14 +426,14 @@ Column { // Set selected imperatively — QStringList from C++ needs // conversion to a plain JS array for includes() to work let val = fieldDelegate.currentValue - if (val && val.length > 0) { + if ( val && val.length > 0 ) { let arr = [] - for (let i = 0; i < val.length; i++) { - arr.push(String(val[i])) + for ( let i = 0; i < val.length; i++ ) { + arr.push( String( val[i] ) ) } selected = arr } - populateOptions("") + populateOptions( "" ) open() } } diff --git a/app/qml/filters/MMFiltersPanel.qml b/app/qml/filters/MMFiltersPanel.qml index bcb81327d..e4037d2c2 100644 --- a/app/qml/filters/MMFiltersPanel.qml +++ b/app/qml/filters/MMFiltersPanel.qml @@ -89,7 +89,7 @@ MMComponents.MMDrawer { drawerContent: Item { width: parent.width - height: root.maxHeightHit ? root.drawerContentAvailableHeight : (contentColumn.implicitHeight + __style.margin12 + showResultsButton.height) + height: root.maxHeightHit ? root.drawerContentAvailableHeight : ( contentColumn.implicitHeight + __style.margin12 + showResultsButton.height ) MMComponents.MMScrollView { id: scrollView @@ -169,7 +169,7 @@ MMComponents.MMDrawer { Connections { target: __activeProject - function onProjectReloaded(qgsProject) { + function onProjectReloaded( qgsProject ) { internal.refreshLayers() } } diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index 61505c9c4..03a0c833e 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -862,6 +862,7 @@ Item { visible: root.state === "view" && root.filterController iconSource: __style.filterIcon + iconSize: __style.icon24 bgndColor: root.filterController && root.filterController.hasActiveFilters ? __style.positiveColor : __style.polarColor onClicked: { diff --git a/app/qml/map/components/MMMapButton.qml b/app/qml/map/components/MMMapButton.qml index 3f51db7a0..cc356820a 100644 --- a/app/qml/map/components/MMMapButton.qml +++ b/app/qml/map/components/MMMapButton.qml @@ -19,7 +19,7 @@ Item { property alias iconSource: icon.source property color bgndColor: __style.polarColor - property real iconSize: __style.icon32 + property real iconSize: __style.icon24 signal clicked signal clickAndHold From 26f3e9d14a3d9285a190deca60e1e23e8fbc6f20 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Fri, 27 Mar 2026 16:41:28 +0200 Subject: [PATCH 3/4] Reverted logic in MMMapButton Updated the filter svg to be 24 x 24 --- app/icons/Filter.svg | 11 ++--------- app/qml/map/MMMapController.qml | 5 ++--- app/qml/map/components/MMMapButton.qml | 2 -- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/icons/Filter.svg b/app/icons/Filter.svg index f9a65ccca..f9b351a07 100644 --- a/app/icons/Filter.svg +++ b/app/icons/Filter.svg @@ -1,10 +1,3 @@ - - - - - - - - - + + diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index 03a0c833e..e6b594d59 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -860,10 +860,9 @@ Item { MMMapButton { id: filterIndicatorButton - visible: root.state === "view" && root.filterController + visible: root.state === "view" && root.filterController && root.filterController.hasActiveFilters iconSource: __style.filterIcon - iconSize: __style.icon24 - bgndColor: root.filterController && root.filterController.hasActiveFilters ? __style.positiveColor : __style.polarColor + bgndColor: __style.positiveColor onClicked: { root.openFiltersPanel() diff --git a/app/qml/map/components/MMMapButton.qml b/app/qml/map/components/MMMapButton.qml index cc356820a..4f0074b2e 100644 --- a/app/qml/map/components/MMMapButton.qml +++ b/app/qml/map/components/MMMapButton.qml @@ -19,7 +19,6 @@ Item { property alias iconSource: icon.source property color bgndColor: __style.polarColor - property real iconSize: __style.icon24 signal clicked signal clickAndHold @@ -38,7 +37,6 @@ Item { anchors.centerIn: parent color: __style.forestColor - size: control.iconSize } MouseArea { From f20b660f65f889bdc8f0f1e4cd59ca6dbab10603 Mon Sep 17 00:00:00 2001 From: Gabriel Bolbotina Date: Wed, 1 Apr 2026 11:52:44 +0300 Subject: [PATCH 4/4] Implemented code findings Added click and hold Added draft filtersEnabled variable in filter controller --- app/filtercontroller.cpp | 19 +++++++++++++++++++ app/filtercontroller.h | 12 ++++++++++++ app/qml/map/MMMapController.qml | 16 ++++++++++++++-- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/filtercontroller.cpp b/app/filtercontroller.cpp index 81fe79fd7..7367bf63d 100644 --- a/app/filtercontroller.cpp +++ b/app/filtercontroller.cpp @@ -26,6 +26,19 @@ FilterController::FilterController( QObject *parent ) { } +bool FilterController::filtersEnabled() const +{ + return mFiltersEnabled; +} + +void FilterController::setFiltersEnabled( bool enabled ) +{ + if ( mFiltersEnabled == enabled ) + return; + mFiltersEnabled = enabled; + emit filtersEnabledChanged(); +} + bool FilterController::hasActiveFilters() const { for ( auto it = mAppliedFilters.constBegin(); it != mAppliedFilters.constEnd(); ++it ) @@ -463,6 +476,12 @@ void FilterController::applyFiltersToAllLayers() { emit hasActiveFiltersChanged(); } + + if ( !mFiltersEnabled ) + { + mFiltersEnabled = true; + emit filtersEnabledChanged(); + } } void FilterController::discardPendingChanges() diff --git a/app/filtercontroller.h b/app/filtercontroller.h index 3e7342ea1..46b50a547 100644 --- a/app/filtercontroller.h +++ b/app/filtercontroller.h @@ -63,6 +63,12 @@ class FilterController : public QObject */ Q_PROPERTY( QStringList filteredLayerIds READ filteredLayerIds NOTIFY filtersChanged ) + /** + * Whether filters are currently enabled (applied to the map). + * When false, filters are defined but not applied. + */ + Q_PROPERTY( bool filtersEnabled READ filtersEnabled WRITE setFiltersEnabled NOTIFY filtersEnabledChanged ) + public: explicit FilterController( QObject *parent = nullptr ); ~FilterController() override = default; @@ -70,6 +76,9 @@ class FilterController : public QObject bool hasActiveFilters() const; QStringList filteredLayerIds() const; + bool filtersEnabled() const; + void setFiltersEnabled( bool enabled ); + /** * @brief Sets a filter for a specific field on a layer * @param layerId The layer ID @@ -211,6 +220,7 @@ class FilterController : public QObject void filtersChanged(); void hasActiveFiltersChanged(); void layerFilterChanged( const QString &layerId ); + void filtersEnabledChanged(); private: QString buildFieldExpression( const FieldFilter &filter ) const; @@ -224,6 +234,8 @@ class FilterController : public QObject // Applied state, updated only when the user confirms via "Show results" QMap> mAppliedFilters; + + bool mFiltersEnabled = true; }; #endif // FILTERCONTROLLER_H diff --git a/app/qml/map/MMMapController.qml b/app/qml/map/MMMapController.qml index e6b594d59..d5c3d3531 100644 --- a/app/qml/map/MMMapController.qml +++ b/app/qml/map/MMMapController.qml @@ -860,13 +860,25 @@ Item { MMMapButton { id: filterIndicatorButton - visible: root.state === "view" && root.filterController && root.filterController.hasActiveFilters + visible: root.state === "view" && __activeProject.filterController && __activeProject.filterController.hasActiveFilters iconSource: __style.filterIcon - bgndColor: __style.positiveColor + bgndColor: __activeProject.filterController && __activeProject.filterController.filtersEnabled ? __style.positiveColor : __style.polarColor onClicked: { root.openFiltersPanel() } + + onClickAndHold: { + if ( __activeProject.filterController ) { + const enabling = !__activeProject.filterController.filtersEnabled + __activeProject.filterController.filtersEnabled = enabling + if ( enabling ) { + __notificationModel.addSuccess( qsTr( "All filters have been re-enabled" ) ) + } else { + __notificationModel.addWarning( qsTr( "All filters have been temporarily disabled. Press and hold to re-enable them" ) ) + } + } + } } MMMapButton {