Skip to content

Commit 3c47b41

Browse files
authored
Feature filtering integration 3 (#4449)
1 parent 7846669 commit 3c47b41

File tree

4 files changed

+151
-95
lines changed

4 files changed

+151
-95
lines changed

app/filtercontroller.cpp

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -154,14 +154,10 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons
154154
}
155155
case FieldFilter::NumberFilter:
156156
{
157-
const QString valueFrom = filter.value.toList().at( 0 ).toString();
158-
const QString valueTo = filter.value.toList().at( 1 ).toString();
159-
160-
if ( valueFrom.isEmpty() || valueTo.isEmpty() )
161-
{
162-
expressionCopy = {};
163-
break;
164-
}
157+
const QVariant &variantFrom = filter.value.toList().at( 0 );
158+
const QString valueFrom = variantFrom.isValid() ? variantFrom.toString() : QString::number( std::numeric_limits<int>::min() );
159+
const QVariant &variantTo = filter.value.toList().at( 1 );
160+
const QString valueTo = variantTo.isValid() ? variantTo.toString() : QString::number( std::numeric_limits<int>::max() );
165161

166162
expressionCopy.replace( QStringLiteral( "%%value_from%%" ), valueFrom );
167163
expressionCopy.replace( QStringLiteral( "%%value_to%%" ), valueTo );
@@ -173,14 +169,13 @@ QString FilterController::buildFieldExpression( const FieldFilter &filter ) cons
173169
// so we must convert local datetimes to UTC before comparing.
174170
// Use a custom format to avoid the 'Z' suffix that Qt::ISODate adds for UTC.
175171
const QString isoFormat = QStringLiteral( "yyyy-MM-ddTHH:mm:ss" );
176-
const QString dateFrom = filter.value.toList().at( 0 ).toDateTime().toUTC().toString( isoFormat );
177-
const QString dateTo = filter.value.toList().at( 1 ).toDateTime().toUTC().toString( isoFormat );
172+
const QString minimumDateTime = QStringLiteral( "0001-01-01T00:00:00" );
173+
const QString maximumDateTime = QStringLiteral( "9999-12-31T23:59:59" );
178174

179-
if ( dateFrom.isEmpty() || dateTo.isEmpty() )
180-
{
181-
expressionCopy = {};
182-
break;
183-
}
175+
const QVariant &variantFrom = filter.value.toList().at( 0 );
176+
const QString dateFrom = variantFrom.isValid() ? variantFrom.toDateTime().toString( isoFormat ) : minimumDateTime;
177+
const QVariant &variantTo = filter.value.toList().at( 1 );
178+
const QString dateTo = variantTo.isValid() ? variantTo.toDateTime().toString( isoFormat ) : maximumDateTime;
184179

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

330+
bool FilterController::isDateFilterDateTime( const QString &filterId )
331+
{
332+
for ( FieldFilter &filter : mFieldFilters )
333+
{
334+
if ( filter.filterId == filterId )
335+
{
336+
const QgsVectorLayer *layer = qobject_cast<QgsVectorLayer *>( QgsProject::instance()->mapLayer( filter.layerId ) );
337+
const QMetaType::Type fieldType = layer->fields().field( filter.fieldName ).type();
338+
return fieldType == QMetaType::QDateTime;
339+
}
340+
}
341+
return false;
342+
}
343+
337344
QVariantMap FilterController::getDropdownConfiguration( const QString &filterId )
338345
{
339346
if ( filterId.isEmpty() ) return {};

app/filtercontroller.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ class FilterController : public QObject
115115
*/
116116
Q_INVOKABLE bool hasActiveFilterOnLayer( const QString &layerId );
117117

118+
/**
119+
* Returns whether the date filter is datetime or just date field. Used to show date or date & time UI for users.
120+
*/
121+
Q_INVOKABLE bool isDateFilterDateTime( const QString &filterId );
122+
118123
bool hasFiltersAvailable() const;
119124

120125
bool hasFiltersEnabled() const;

app/qml/filters/components/MMFilterDateRange.qml

Lines changed: 97 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* (at your option) any later version. *
77
* *
88
***************************************************************************/
9+
pragma ComponentBehavior: Bound
910

1011
import QtQuick
1112

@@ -18,48 +19,23 @@ Column {
1819
width: parent.width
1920
spacing: __style.margin8
2021

21-
required property string fieldDisplayName
22-
required property bool hasTime
22+
required property string filterName
23+
required property string filterId
2324
required property var currentValue
24-
required property var currentValueTo
25-
required property string fieldLayerId
26-
required property string fieldName
27-
28-
property var initialFromDate: {
29-
let v = root.currentValue
30-
if ( v === null || v === undefined ) return null
31-
let d = new Date( v )
32-
return isNaN( d.getTime() ) ? null : d
33-
}
34-
property var initialToDate: {
35-
let v = root.currentValueTo
36-
if ( v === null || v === undefined ) return null
37-
let d = new Date( v )
38-
return isNaN( d.getTime() ) ? null : d
39-
}
40-
41-
property var fromDate: initialFromDate
42-
property var toDate: initialToDate
43-
44-
property bool rangeInvalid: fromDate !== null && toDate !== null && fromDate.getTime() > toDate.getTime()
25+
readonly property bool hasTime: __activeProject.filterController.isDateFilterDateTime(filterId)
4526

46-
property bool _initialized: false
47-
Component.onCompleted: _initialized = true
48-
49-
function applyDateFilter() {
50-
if ( !_initialized || !fieldLayerId || !fieldName ) return
51-
__activeProject.filterController.setDateFilter( fieldLayerId, fieldName, fromDate, toDate, hasTime )
27+
property bool rangeInvalid: {
28+
if ( !currentValue || !currentValue[0] || !currentValue[1] ){
29+
return false
30+
}
31+
return currentValue[0] > currentValue[1]
5232
}
5333

54-
onFromDateChanged: applyDateFilter()
55-
onToDateChanged: applyDateFilter()
56-
5734
MMText {
5835
width: parent.width
59-
text: root.fieldDisplayName
36+
text: root.filterName
6037
font: __style.p6
6138
color: __style.nightColor
62-
visible: root.fieldDisplayName !== ""
6339
}
6440

6541
Row {
@@ -75,21 +51,43 @@ Column {
7551

7652
width: parent.width
7753
type: MMFilterTextInput.InputType.Date
78-
checked: root.fromDate !== null && !root.rangeInvalid
7954
placeholderText: qsTr( "From" )
80-
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : ""
55+
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be sooner than \"To\"" ) : ""
8156
text: {
82-
if ( !root.fromDate ) return ""
83-
if ( root.hasTime ) return Qt.formatDateTime( root.fromDate, Qt.DefaultLocaleShortDate )
84-
return Qt.formatDate( root.fromDate, Qt.DefaultLocaleShortDate )
57+
if ( !root.currentValue || !root.currentValue[0] ) return ""
58+
if ( root.hasTime ) return Qt.formatDateTime( root.currentValue[0] )
59+
return Qt.formatDate( root.currentValue[0] )
8560
}
8661

8762
onTextClicked: fromCalendarLoader.active = true
8863
onRightContentClicked: {
89-
if ( root.fromDate ) {
90-
root.fromDate = null
64+
if (checked) {
65+
textField.clear()
66+
checked = false
67+
if ( root.currentValue[1] ){
68+
root.currentValue = [undefined, root.currentValue[1]]
69+
} else {
70+
root.currentValue = undefined
71+
}
72+
root.currentValueChanged()
9173
} else {
92-
fromCalendarLoader.active = true
74+
let currentTimestamp = new Date()
75+
76+
if (root.hasTime) {
77+
text = Qt.formatDateTime(currentTimestamp)
78+
} else {
79+
text = Qt.formatDate(currentTimestamp)
80+
}
81+
82+
if (!root.hasTime) {
83+
currentTimestamp.setHours(0, 0, 0, 0)
84+
}
85+
if (!root.currentValue) {
86+
root.currentValue = [currentTimestamp, undefined]
87+
} else {
88+
root.currentValue[0] = currentTimestamp
89+
}
90+
root.currentValueChanged()
9391
}
9492
}
9593
}
@@ -106,10 +104,21 @@ Column {
106104
MMFormComponents.MMCalendarDrawer {
107105
hasDatePicker: true
108106
hasTimePicker: root.hasTime
109-
dateTime: root.fromDate ? root.fromDate : new Date()
107+
dateTime: root.currentValue && root.currentValue[0] ? root.currentValue[0] : new Date()
110108

111109
onPrimaryButtonClicked: {
112-
root.fromDate = dateTime
110+
let currentTimestamp = dateTime
111+
if (!root.hasTime) {
112+
currentTimestamp.setHours(0, 0, 0, 0)
113+
}
114+
if (!root.currentValue){
115+
root.currentValue = [currentTimestamp, undefined]
116+
} else {
117+
root.currentValue[0] = currentTimestamp
118+
root.currentValueChanged()
119+
}
120+
121+
fromDateInput.text = root.hasTime ? Qt.formatDateTime(dateTime) : Qt.formatDate(dateTime)
113122
}
114123
onClosed: fromCalendarLoader.active = false
115124
Component.onCompleted: open()
@@ -126,21 +135,44 @@ Column {
126135

127136
width: parent.width
128137
type: MMFilterTextInput.InputType.Date
129-
checked: root.toDate !== null && !root.rangeInvalid
130138
placeholderText: qsTr( "To" )
131-
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be less than \"To\"" ) : ""
139+
errorMsg: root.rangeInvalid ? qsTr( "\"From\" must be sooner than \"To\"" ) : ""
132140
text: {
133-
if ( !root.toDate ) return ""
134-
if ( root.hasTime ) return Qt.formatDateTime( root.toDate, Qt.DefaultLocaleShortDate )
135-
return Qt.formatDate( root.toDate, Qt.DefaultLocaleShortDate )
141+
if ( !root.currentValue || !root.currentValue[1] ) return ""
142+
if ( root.hasTime ) return Qt.formatDateTime( root.currentValue[1] )
143+
return Qt.formatDate( root.currentValue[1] )
136144
}
137145

138146
onTextClicked: toCalendarLoader.active = true
139147
onRightContentClicked: {
140-
if ( root.toDate ) {
141-
root.toDate = null
148+
if (checked) {
149+
textField.clear()
150+
checked = false
151+
if ( root.currentValue[0] ){
152+
root.currentValue = [root.currentValue[0], undefined]
153+
} else {
154+
root.currentValue = undefined
155+
}
156+
root.currentValueChanged()
157+
142158
} else {
143-
toCalendarLoader.active = true
159+
let currentTimestamp = new Date()
160+
161+
if (root.hasTime) {
162+
text = Qt.formatDateTime(currentTimestamp)
163+
} else {
164+
text = Qt.formatDate(currentTimestamp)
165+
}
166+
167+
if (!root.hasTime) {
168+
currentTimestamp.setHours(0, 0, 0, 0)
169+
}
170+
if (!root.currentValue) {
171+
root.currentValue = [undefined, currentTimestamp]
172+
} else {
173+
root.currentValue[1] = currentTimestamp
174+
root.currentValueChanged()
175+
}
144176
}
145177
}
146178
}
@@ -157,10 +189,21 @@ Column {
157189
MMFormComponents.MMCalendarDrawer {
158190
hasDatePicker: true
159191
hasTimePicker: root.hasTime
160-
dateTime: root.toDate ? root.toDate : new Date()
192+
dateTime: root.currentValue && root.currentValue[1] ? root.currentValue[1] : new Date()
161193

162194
onPrimaryButtonClicked: {
163-
root.toDate = dateTime
195+
let currentTimestamp = dateTime
196+
if (!root.hasTime) {
197+
currentTimestamp.setHours(0, 0, 0, 0)
198+
}
199+
if (!root.currentValue){
200+
root.currentValue = [undefined, currentTimestamp]
201+
} else {
202+
root.currentValue[1] = currentTimestamp
203+
}
204+
root.currentValueChanged()
205+
206+
toDateInput.text = root.hasTime ? Qt.formatDateTime(dateTime) : Qt.formatDate(dateTime)
164207
}
165208
onClosed: toCalendarLoader.active = false
166209
Component.onCompleted: open()

app/qml/filters/components/MMFilterRangeInput.qml

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,15 @@ Column {
1717
width: parent.width
1818
spacing: __style.margin8
1919

20-
required property string fieldDisplayName
20+
required property string filterName
21+
required property string filterId
2122
required property var currentValue
22-
required property var currentValueTo
23-
required property string fieldLayerId
24-
required property string fieldName
25-
26-
property string initialFrom: {
27-
let v = root.currentValue
28-
return ( v !== null && v !== undefined ) ? String( v ) : ""
29-
}
30-
property string initialTo: {
31-
let v = root.currentValueTo
32-
return ( v !== null && v !== undefined ) ? String( v ) : ""
33-
}
34-
35-
property bool _initialized: false
36-
Component.onCompleted: _initialized = true
3723

3824
MMText {
3925
width: parent.width
40-
text: root.fieldDisplayName
26+
text: root.filterName
4127
font: __style.p6
4228
color: __style.nightColor
43-
visible: root.fieldDisplayName !== ""
4429
}
4530

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

6752
onTextChanged: {
68-
if ( !root._initialized ) return
6953
debounceTimer.restart()
7054
}
7155
}
@@ -76,11 +60,10 @@ Column {
7660
width: ( parent.width - __style.margin12 ) / 2
7761
type: MMFilterTextInput.InputType.Number
7862
placeholderText: qsTr( "Max" )
79-
text: root.initialTo
63+
text: root.currentValue && root.currentValue[1] ? root.currentValue[1] : ""
8064
errorMsg: rangeRow.rangeInvalid ? qsTr( "\"Min\" must be less than \"Max\"" ) : ""
8165

8266
onTextChanged: {
83-
if ( !root._initialized ) return
8467
debounceTimer.restart()
8568
}
8669
}
@@ -91,8 +74,26 @@ Column {
9174
interval: 300
9275
repeat: false
9376
onTriggered: {
94-
if ( root.fieldLayerId && root.fieldName )
95-
__activeProject.filterController.setNumberFilter( root.fieldLayerId, root.fieldName, fromInput.text, toInput.text )
77+
let newValues = []
78+
const valueFrom = parseFloat(fromInput.text)
79+
if ( !isNaN(valueFrom) ) {
80+
newValues[0] = valueFrom
81+
} else {
82+
newValues[0] = undefined
83+
}
84+
85+
const valueTo = parseFloat(toInput.text)
86+
if ( !isNaN(valueTo) ) {
87+
newValues[1] = valueTo
88+
} else {
89+
newValues[1] = undefined
90+
}
91+
92+
if ( newValues[0] === undefined && newValues[1] === undefined ) {
93+
root.currentValue = undefined
94+
} else {
95+
root.currentValue = newValues
96+
}
9697
}
9798
}
9899
}

0 commit comments

Comments
 (0)