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/icons/Filter.svg b/app/icons/Filter.svg index d62e7cf66..f9b351a07 100644 --- a/app/icons/Filter.svg +++ b/app/icons/Filter.svg @@ -1,3 +1,3 @@ - + 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/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 e512bb7d2..d5c3d3531 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,30 @@ Item { } } + MMMapButton { + id: filterIndicatorButton + + visible: root.state === "view" && __activeProject.filterController && __activeProject.filterController.hasActiveFilters + iconSource: __style.filterIcon + 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 { id: gpsButton