Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 24 additions & 17 deletions app/filtercontroller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,10 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons
}
case FieldFilter::NumberFilter:
{
const QString valueFrom = filter.value.toList().at( 0 ).toString();
const QString valueTo = filter.value.toList().at( 1 ).toString();

if ( valueFrom.isEmpty() || valueTo.isEmpty() )
{
expressionCopy = {};
break;
}
const QVariant &variantFrom = filter.value.toList().at( 0 );
const QString valueFrom = variantFrom.isValid() ? variantFrom.toString() : QString::number( std::numeric_limits<int>::min() );
const QVariant &variantTo = filter.value.toList().at( 1 );
const QString valueTo = variantTo.isValid() ? variantTo.toString() : QString::number( std::numeric_limits<int>::max() );

expressionCopy.replace( QStringLiteral( "%%value_from%%" ), valueFrom );
expressionCopy.replace( QStringLiteral( "%%value_to%%" ), valueTo );
Expand All @@ -173,14 +169,13 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons
// so we must convert local datetimes to UTC before comparing.
// Use a custom format to avoid the 'Z' suffix that Qt::ISODate adds for UTC.
const QString isoFormat = QStringLiteral( "yyyy-MM-ddTHH:mm:ss" );
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind the upper bound for seconds (min: 0 seconds, max: 59 seconds, 999 miliseconds)

const QString dateFrom = filter.value.toList().at( 0 ).toDateTime().toUTC().toString( isoFormat );
const QString dateTo = filter.value.toList().at( 1 ).toDateTime().toUTC().toString( isoFormat );
const QString minimumDateTime = QStringLiteral( "0001-01-01T00:00:00" );
const QString maximumDateTime = QStringLiteral( "9999-12-31T23:59:59" );

if ( dateFrom.isEmpty() || dateTo.isEmpty() )
{
expressionCopy = {};
break;
}
const QVariant &variantFrom = filter.value.toList().at( 0 );
const QString dateFrom = variantFrom.isValid() ? variantFrom.toDateTime().toString( isoFormat ) : minimumDateTime;
const QVariant &variantTo = filter.value.toList().at( 1 );
const QString dateTo = variantTo.isValid() ? variantTo.toDateTime().toString( isoFormat ) : maximumDateTime;

expressionCopy.replace( QStringLiteral( "%%value_from%%" ), QgsExpression::quotedString( dateFrom ) );
expressionCopy.replace( QStringLiteral( "%%value_to%%" ), QgsExpression::quotedString( dateTo ) );
Expand Down Expand Up @@ -315,8 +310,6 @@ void FilterController::processFilters( const QVariantMap &newFilters )
{
if ( newFilters.contains( filter.filterId ) )
{
//TODO: we need to have both upper and lower bounds for numbers and dates,
//if user didn't supply use numeric_limits for numbers and year 1 to 9999 for dates
filter.value = newFilters.value( filter.filterId );
}
}
Expand All @@ -334,6 +327,20 @@ bool FilterController::hasActiveFilterOnLayer( const QString &layerId )
return !layer->subsetString().isEmpty();
}

bool FilterController::isDateFilterDateTime( const QString &filterId )
{
for ( FieldFilter &filter : mFieldFilters )
{
if ( filter.filterId == filterId )
{
const QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( filter.layerId ) );
const QMetaType::Type fieldType = layer->fields().field( filter.fieldName ).type();
return fieldType == QMetaType::QDateTime;
}
}
return false;
}

QVariantMap FilterController::getDropdownConfiguration( const QString &filterId )
{
if ( filterId.isEmpty() ) return {};
Expand Down
5 changes: 5 additions & 0 deletions app/filtercontroller.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ class FilterController : public QObject
*/
Q_INVOKABLE bool hasActiveFilterOnLayer( const QString &layerId );

/**
* Returns whether the date filter is datetime or just date field. Used to show date or date & time UI for users.
*/
Q_INVOKABLE bool isDateFilterDateTime( const QString &filterId );

bool hasFiltersAvailable() const;

bool hasFiltersEnabled() const;
Expand Down
151 changes: 97 additions & 54 deletions app/qml/filters/components/MMFilterDateRange.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* (at your option) any later version. *
* *
***************************************************************************/
pragma ComponentBehavior: Bound

import QtQuick

Expand All @@ -18,48 +19,23 @@ Column {
width: parent.width
spacing: __style.margin8

required property string fieldDisplayName
required property bool hasTime
required property string filterName
required property string filterId
required property var currentValue
required property var currentValueTo
required property string fieldLayerId
required property string fieldName

property var initialFromDate: {
let v = root.currentValue
if ( v === null || v === undefined ) return null
let d = new Date( v )
return isNaN( d.getTime() ) ? null : d
}
property var initialToDate: {
let v = root.currentValueTo
if ( v === null || v === undefined ) return null
let d = new Date( v )
return isNaN( d.getTime() ) ? null : d
}

property var fromDate: initialFromDate
property var toDate: initialToDate

property bool rangeInvalid: fromDate !== null && toDate !== null && fromDate.getTime() > toDate.getTime()
readonly property bool hasTime: __activeProject.filterController.isDateFilterDateTime(filterId)

property bool _initialized: false
Component.onCompleted: _initialized = true

function applyDateFilter() {
if ( !_initialized || !fieldLayerId || !fieldName ) return
__activeProject.filterController.setDateFilter( fieldLayerId, fieldName, fromDate, toDate, hasTime )
property bool rangeInvalid: {
if ( !currentValue || !currentValue[0] || !currentValue[1] ){
return false
}
return currentValue[0] > currentValue[1]
}

onFromDateChanged: applyDateFilter()
onToDateChanged: applyDateFilter()

MMText {
width: parent.width
text: root.fieldDisplayName
text: root.filterName
font: __style.p6
color: __style.nightColor
visible: root.fieldDisplayName !== ""
}

Row {
Expand All @@ -75,21 +51,43 @@ Column {

width: parent.width
type: MMFilterTextInput.InputType.Date
checked: root.fromDate !== null && !root.rangeInvalid
placeholderText: qsTr( "From" )
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : ""
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be sooner than \"To\"" ) : ""
text: {
if ( !root.fromDate ) return ""
if ( root.hasTime ) return Qt.formatDateTime( root.fromDate, Qt.DefaultLocaleShortDate )
return Qt.formatDate( root.fromDate, Qt.DefaultLocaleShortDate )
if ( !root.currentValue || !root.currentValue[0] ) return ""
if ( root.hasTime ) return Qt.formatDateTime( root.currentValue[0] )
return Qt.formatDate( root.currentValue[0] )
}

onTextClicked: fromCalendarLoader.active = true
onRightContentClicked: {
if ( root.fromDate ) {
root.fromDate = null
if (checked) {
textField.clear()
checked = false
if ( root.currentValue[1] ){
root.currentValue = [undefined, root.currentValue[1]]
} else {
root.currentValue = undefined
}
root.currentValueChanged()
} else {
fromCalendarLoader.active = true
let currentTimestamp = new Date()

if (root.hasTime) {
text = Qt.formatDateTime(currentTimestamp)
} else {
text = Qt.formatDate(currentTimestamp)
}

if (!root.hasTime) {
currentTimestamp.setHours(0, 0, 0, 0)
}
if (!root.currentValue) {
root.currentValue = [currentTimestamp, undefined]
} else {
root.currentValue[0] = currentTimestamp
}
root.currentValueChanged()
}
}
}
Expand All @@ -106,10 +104,21 @@ Column {
MMFormComponents.MMCalendarDrawer {
hasDatePicker: true
hasTimePicker: root.hasTime
dateTime: root.fromDate ? root.fromDate : new Date()
dateTime: root.currentValue && root.currentValue[0] ? root.currentValue[0] : new Date()

onPrimaryButtonClicked: {
root.fromDate = dateTime
let currentTimestamp = dateTime
if (!root.hasTime) {
currentTimestamp.setHours(0, 0, 0, 0)
}
if (!root.currentValue){
root.currentValue = [currentTimestamp, undefined]
} else {
root.currentValue[0] = currentTimestamp
root.currentValueChanged()
}

fromDateInput.text = root.hasTime ? Qt.formatDateTime(dateTime) : Qt.formatDate(dateTime)
}
onClosed: fromCalendarLoader.active = false
Component.onCompleted: open()
Expand All @@ -126,21 +135,44 @@ Column {

width: parent.width
type: MMFilterTextInput.InputType.Date
checked: root.toDate !== null && !root.rangeInvalid
placeholderText: qsTr( "To" )
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : ""
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be sooner than \"To\"" ) : ""
text: {
if ( !root.toDate ) return ""
if ( root.hasTime ) return Qt.formatDateTime( root.toDate, Qt.DefaultLocaleShortDate )
return Qt.formatDate( root.toDate, Qt.DefaultLocaleShortDate )
if ( !root.currentValue || !root.currentValue[1] ) return ""
if ( root.hasTime ) return Qt.formatDateTime( root.currentValue[1] )
return Qt.formatDate( root.currentValue[1] )
}

onTextClicked: toCalendarLoader.active = true
onRightContentClicked: {
if ( root.toDate ) {
root.toDate = null
if (checked) {
textField.clear()
checked = false
if ( root.currentValue[0] ){
root.currentValue = [root.currentValue[0], undefined]
} else {
root.currentValue = undefined
}
root.currentValueChanged()

} else {
toCalendarLoader.active = true
let currentTimestamp = new Date()

if (root.hasTime) {
text = Qt.formatDateTime(currentTimestamp)
} else {
text = Qt.formatDate(currentTimestamp)
}

if (!root.hasTime) {
currentTimestamp.setHours(0, 0, 0, 0)
}
if (!root.currentValue) {
root.currentValue = [undefined, currentTimestamp]
} else {
root.currentValue[1] = currentTimestamp
root.currentValueChanged()
}
}
}
}
Expand All @@ -157,10 +189,21 @@ Column {
MMFormComponents.MMCalendarDrawer {
hasDatePicker: true
hasTimePicker: root.hasTime
dateTime: root.toDate ? root.toDate : new Date()
dateTime: root.currentValue && root.currentValue[1] ? root.currentValue[1] : new Date()

onPrimaryButtonClicked: {
root.toDate = dateTime
let currentTimestamp = dateTime
if (!root.hasTime) {
currentTimestamp.setHours(0, 0, 0, 0)
}
if (!root.currentValue){
root.currentValue = [undefined, currentTimestamp]
} else {
root.currentValue[1] = currentTimestamp
}
root.currentValueChanged()

toDateInput.text = root.hasTime ? Qt.formatDateTime(dateTime) : Qt.formatDate(dateTime)
}
onClosed: toCalendarLoader.active = false
Component.onCompleted: open()
Expand Down
49 changes: 25 additions & 24 deletions app/qml/filters/components/MMFilterRangeInput.qml
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,15 @@ Column {
width: parent.width
spacing: __style.margin8

required property string fieldDisplayName
required property string filterName
required property string filterId
required property var currentValue
required property var currentValueTo
required property string fieldLayerId
required property string fieldName

property string initialFrom: {
let v = root.currentValue
return ( v !== null && v !== undefined ) ? String( v ) : ""
}
property string initialTo: {
let v = root.currentValueTo
return ( v !== null && v !== undefined ) ? String( v ) : ""
}

property bool _initialized: false
Component.onCompleted: _initialized = true

MMText {
width: parent.width
text: root.fieldDisplayName
text: root.filterName
font: __style.p6
color: __style.nightColor
visible: root.fieldDisplayName !== ""
}

Row {
Expand All @@ -61,11 +46,10 @@ Column {
width: ( parent.width - __style.margin12 ) / 2
type: MMFilterTextInput.InputType.Number
placeholderText: qsTr( "Min" )
text: root.initialFrom
text: root.currentValue && root.currentValue[0] ? root.currentValue[0] : ""
errorMsg: rangeRow.rangeInvalid ? qsTr( "\"Min\" must be less than \"Max\"" ) : ""

onTextChanged: {
if ( !root._initialized ) return
debounceTimer.restart()
}
}
Expand All @@ -76,11 +60,10 @@ Column {
width: ( parent.width - __style.margin12 ) / 2
type: MMFilterTextInput.InputType.Number
placeholderText: qsTr( "Max" )
text: root.initialTo
text: root.currentValue && root.currentValue[1] ? root.currentValue[1] : ""
errorMsg: rangeRow.rangeInvalid ? qsTr( "\"Min\" must be less than \"Max\"" ) : ""

onTextChanged: {
if ( !root._initialized ) return
debounceTimer.restart()
}
}
Expand All @@ -91,8 +74,26 @@ Column {
interval: 300
repeat: false
onTriggered: {
if ( root.fieldLayerId && root.fieldName )
__activeProject.filterController.setNumberFilter( root.fieldLayerId, root.fieldName, fromInput.text, toInput.text )
let newValues = []
const valueFrom = parseFloat(fromInput.text)
if ( !isNaN(valueFrom) ) {
newValues[0] = valueFrom
} else {
newValues[0] = undefined
}

const valueTo = parseFloat(toInput.text)
if ( !isNaN(valueTo) ) {
newValues[1] = valueTo
} else {
newValues[1] = undefined
}

if ( newValues[0] === undefined && newValues[1] === undefined ) {
root.currentValue = undefined
} else {
root.currentValue = newValues
}
}
}
}
Loading