Skip to content

Commit 3cd237e

Browse files
committedApr 29, 2020
more work
1 parent 1065039 commit 3cd237e

20 files changed

+1664
-85
lines changed
 
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/core/qgsfeaturechoosermodel.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
class QgsFeatureChooserModel : QAbstractItemModel
12+
{
13+
%Docstring
14+
Provides a list of features based on filter conditions.
15+
Features are fetched asynchronously.
16+
17+
.. versionadded:: 3.0
18+
%End
19+
20+
%TypeHeaderCode
21+
#include "qgsfeaturechoosermodel.h"
22+
%End
23+
public:
24+
25+
enum Role
26+
{
27+
ValueRole,
28+
FeatureRole,
29+
FeatureIdRole
30+
};
31+
32+
explicit QgsFeatureChooserModel( QObject *parent = 0 );
33+
%Docstring
34+
Create a new QgsFeatureChooserModel, optionally specifying a ``parent``.
35+
%End
36+
~QgsFeatureChooserModel();
37+
38+
QgsVectorLayer *sourceLayer() const;
39+
%Docstring
40+
The source layer from which features will be fetched.
41+
%End
42+
43+
void setSourceLayer( QgsVectorLayer *sourceLayer );
44+
%Docstring
45+
The source layer from which features will be fetched.
46+
%End
47+
48+
QString displayExpression() const;
49+
%Docstring
50+
The display expression will be used for
51+
52+
- displaying values in the combobox
53+
- filtering based on filterValue
54+
%End
55+
56+
void setDisplayExpression( const QString &displayExpression );
57+
%Docstring
58+
The display expression will be used for
59+
60+
- displaying values in the combobox
61+
- filtering based on filterValue
62+
%End
63+
64+
QString filterValue() const;
65+
%Docstring
66+
This value will be used to filter the features available from
67+
this model. Whenever a substring of the displayExpression of a feature
68+
matches the filter value, it will be accessible by this model.
69+
%End
70+
71+
void setFilterValue( const QString &filterValue );
72+
%Docstring
73+
This value will be used to filter the features available from
74+
this model. Whenever a substring of the displayExpression of a feature
75+
matches the filter value, it will be accessible by this model.
76+
%End
77+
78+
virtual QModelIndex index( int row, int column, const QModelIndex &parent ) const;
79+
80+
virtual QModelIndex parent( const QModelIndex &child ) const;
81+
82+
virtual int rowCount( const QModelIndex &parent ) const;
83+
84+
virtual int columnCount( const QModelIndex &parent ) const;
85+
86+
virtual QVariant data( const QModelIndex &index, int role ) const;
87+
88+
89+
QString filterExpression() const;
90+
%Docstring
91+
An additional filter expression to apply, next to the filterValue.
92+
Can be used for spatial filtering etc.
93+
%End
94+
95+
void setFilterExpression( const QString &filterExpression );
96+
%Docstring
97+
An additional filter expression to apply, next to the filterValue.
98+
Can be used for spatial filtering etc.
99+
%End
100+
101+
bool isLoading() const;
102+
%Docstring
103+
Indicator if the model is currently performing any feature iteration in the background.
104+
%End
105+
106+
bool allowNull() const;
107+
%Docstring
108+
Add a NULL entry to the list.
109+
%End
110+
111+
void setAllowNull( bool allowNull );
112+
%Docstring
113+
Add a NULL entry to the list.
114+
%End
115+
116+
QgsFeature currentFeature() const;
117+
118+
void setCurrentFeature( const QgsFeatureId &featureId );
119+
%Docstring
120+
Sets the current feature id
121+
%End
122+
123+
int currentIndex() const;
124+
%Docstring
125+
Returns the current index
126+
%End
127+
128+
signals:
129+
130+
void currentIndexChanged( int index );
131+
%Docstring
132+
Emitted when the current index changed
133+
%End
134+
135+
void currentFeatureChanged( const QgsFeature &feature );
136+
%Docstring
137+
Emitted when the current feature changed
138+
%End
139+
140+
void sourceLayerChanged();
141+
%Docstring
142+
The source layer from which features will be fetched.
143+
%End
144+
145+
void displayExpressionChanged();
146+
%Docstring
147+
The display expression will be used for
148+
149+
- displaying values in the combobox
150+
- filtering based on filterValue
151+
%End
152+
153+
void filterValueChanged();
154+
%Docstring
155+
This value will be used to filter the features available from
156+
this model. Whenever a substring of the displayExpression of a feature
157+
matches the filter value, it will be accessible by this model.
158+
%End
159+
160+
void filterExpressionChanged();
161+
%Docstring
162+
An additional filter expression to apply, next to the filterValue.
163+
Can be used for spatial filtering etc.
164+
%End
165+
166+
void isLoadingChanged();
167+
%Docstring
168+
Indicator if the model is currently performing any feature iteration in the background.
169+
%End
170+
171+
void filterJobCompleted();
172+
%Docstring
173+
Indicates that a filter job has been completed and new data may be available.
174+
%End
175+
176+
void beginUpdate();
177+
%Docstring
178+
Notification that the model is about to be changed because a job was completed.
179+
%End
180+
181+
void endUpdate();
182+
%Docstring
183+
Notification that the model change is finished. Will always be emitted in sync with beginUpdate.
184+
%End
185+
186+
void allowNullChanged();
187+
%Docstring
188+
Add a NULL entry to the list.
189+
%End
190+
191+
};
192+
193+
/************************************************************************
194+
* This file has been generated automatically from *
195+
* *
196+
* src/core/qgsfeaturechoosermodel.h *
197+
* *
198+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
199+
************************************************************************/

‎python/core/auto_generated/qgsfeaturefiltermodel.sip.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99

1010

11-
1211
class QgsFeatureFilterModel : QAbstractItemModel
1312
{
1413
%Docstring

‎python/core/core_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
%Include auto_generated/qgsexpressioncontextscopegenerator.sip
6262
%Include auto_generated/qgsexpressionfieldbuffer.sip
6363
%Include auto_generated/qgsfeature.sip
64+
%Include auto_generated/qgsfeaturechoosermodel.sip
6465
%Include auto_generated/qgsfeaturefiltermodel.sip
6566
%Include auto_generated/qgsfeaturefilterprovider.sip
6667
%Include auto_generated/qgsfeatureid.sip

‎python/gui/auto_generated/qgsexpressionpreviewwidget.sip.in

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ Sets the expression context for the widget. The context is used for the expressi
5454
preview result and to populate the list of available functions and variables.
5555

5656
:param context: expression context
57-
58-
.. versionadded:: 2.12
5957
%End
6058

6159
void setGeomCalculator( const QgsDistanceArea &da );
@@ -114,6 +112,11 @@ Emitted whenever the tool tip changed
114112
%End
115113

116114
public slots:
115+
void setCurrentFeature( const QgsFeature &feature );
116+
%Docstring
117+
sets the current feature used
118+
%End
119+
117120

118121
};
119122

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/qgsfeaturechooserwidget.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
13+
class QgsFeatureChooserWidget : QWidget
14+
{
15+
%Docstring
16+
This offers a combobox with autocompleter that allows selecting features from a layer.
17+
18+
It will show up to 100 entries at a time. The entries can be chosen based on the displayExpression
19+
and whenever text is typed into the combobox, the completer and popup will adjust to features matching the typed text.
20+
21+
.. versionadded:: 3.0
22+
%End
23+
24+
%TypeHeaderCode
25+
#include "qgsfeaturechooserwidget.h"
26+
%End
27+
public:
28+
29+
QgsFeatureChooserWidget( QWidget *parent = 0 );
30+
%Docstring
31+
Create a new QgsFeatureChooserWidget, optionally specifying a ``parent``.
32+
%End
33+
34+
QgsVectorLayer *layer() const;
35+
%Docstring
36+
The layer from which features should be listed.
37+
%End
38+
39+
void setLayer( QgsVectorLayer *layer );
40+
%Docstring
41+
The layer from which features should be listed.
42+
%End
43+
44+
void setCurrentFeature( const QgsFeature &feature );
45+
%Docstring
46+
Sets the current index by using the given feature
47+
%End
48+
49+
QString displayExpression() const;
50+
%Docstring
51+
The display expression will be used to display features as well as
52+
the value to match the typed text against.
53+
%End
54+
55+
void setDisplayExpression( const QString &displayExpression );
56+
%Docstring
57+
The display expression will be used to display features as well as
58+
the value to match the typed text against.
59+
%End
60+
61+
QString filterExpression() const;
62+
%Docstring
63+
An additional expression to further restrict the available features.
64+
This can be used to integrate additional spatial or other constraints.
65+
%End
66+
67+
int nullIndex() const;
68+
%Docstring
69+
Returns the current index of the NULL value, or -1 if NULL values are
70+
not allowed.
71+
72+
.. versionadded:: 3.2
73+
%End
74+
75+
void setFilterExpression( const QString &filterExpression );
76+
%Docstring
77+
An additional expression to further restrict the available features.
78+
This can be used to integrate additional spatial or other constraints.
79+
80+
TODO!
81+
%End
82+
83+
bool allowNull() const;
84+
%Docstring
85+
Determines if a NULL value should be available in the list.
86+
%End
87+
88+
void setAllowNull( bool allowNull );
89+
%Docstring
90+
Determines if a NULL value should be available in the list.
91+
%End
92+
93+
QModelIndex currentModelIndex() const;
94+
%Docstring
95+
The index of the currently selected item.
96+
%End
97+
98+
virtual void focusOutEvent( QFocusEvent *event );
99+
100+
101+
virtual void keyPressEvent( QKeyEvent *event );
102+
103+
104+
signals:
105+
106+
void modelUpdated();
107+
%Docstring
108+
The underlying model has been updated.
109+
110+
.. versionadded:: 3.2
111+
%End
112+
113+
void layerChanged();
114+
%Docstring
115+
The layer from which features should be listed.
116+
%End
117+
118+
void displayExpressionChanged();
119+
%Docstring
120+
The display expression will be used to display features as well as
121+
the the value to match the typed text against.
122+
%End
123+
124+
void filterExpressionChanged();
125+
%Docstring
126+
An additional expression to further restrict the available features.
127+
This can be used to integrate additional spatial or other constraints.
128+
%End
129+
130+
void allowNullChanged();
131+
%Docstring
132+
Determines if a NULL value should be available in the list.
133+
%End
134+
135+
void currentFeatureChanged( const QgsFeature &feature );
136+
%Docstring
137+
Sends the feature as soon as it is chosen
138+
%End
139+
140+
};
141+
142+
143+
144+
/************************************************************************
145+
* This file has been generated automatically from *
146+
* *
147+
* src/gui/qgsfeaturechooserwidget.h *
148+
* *
149+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
150+
************************************************************************/

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
%Include auto_generated/qgsextentgroupbox.sip
8181
%Include auto_generated/qgsextentwidget.sip
8282
%Include auto_generated/qgsexternalresourcewidget.sip
83+
%Include auto_generated/qgsfeaturechooserwidget.sip
8384
%Include auto_generated/qgsfeaturelistcombobox.sip
8485
%Include auto_generated/qgsfeatureselectiondlg.sip
8586
%Include auto_generated/qgsfieldcombobox.sip

‎scripts/sip_include.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fi
3434
if [[ -n $1 ]]; then
3535
modules=("$1")
3636
else
37-
modules=(core gui analysis server)
37+
modules=(core gui analysis server 3d)
3838
fi
3939
sources=(HDRS MOC_HDRS SRCS)
4040

‎src/core/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ SET(QGIS_CORE_SRCS
257257
qgsexpressioncontext.cpp
258258
qgsexpressionfieldbuffer.cpp
259259
qgsfeature.cpp
260+
qgsfeaturechoosermodel.cpp
260261
qgsfeatureiterator.cpp
261262
qgsfeaturerequest.cpp
262263
qgsfeaturesink.cpp
@@ -796,6 +797,8 @@ SET(QGIS_CORE_HDRS
796797
qgsexpressioncontextscopegenerator.h
797798
qgsexpressionfieldbuffer.h
798799
qgsfeature.h
800+
qgsfeaturechoosermodel.h
801+
qgsfeatureexpressionvaluesgatherer.h
799802
qgsfeaturefiltermodel.h
800803
qgsfeaturefilterprovider.h
801804
qgsfeatureid.h
@@ -1391,7 +1394,6 @@ SET(QGIS_CORE_PRIVATE_HDRS
13911394
qgscoordinatetransformcontext_p.h
13921395
qgscoordinatetransform_p.h
13931396
qgseditformconfig_p.h
1394-
qgsfeaturefiltermodel_p.h
13951397
qgsfeature_p.h
13961398
qgsfield_p.h
13971399
qgsfields_p.h

‎src/core/qgsfeaturechoosermodel.cpp

Lines changed: 457 additions & 0 deletions
Large diffs are not rendered by default.

‎src/core/qgsfeaturechoosermodel.h

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/***************************************************************************
2+
qgsfeaturechoosermodel.h - QgsFeatureChooserModel
3+
---------------------
4+
begin : 03.04.2020
5+
copyright : (C) 2020 by Denis Rouzaud
6+
email : denis@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSFEATURECHOOSERMODEL_H
16+
#define QGSFEATURECHOOSERMODEL_H
17+
18+
#include <QAbstractItemModel>
19+
20+
#include "qgsconditionalstyle.h"
21+
#include "qgsfeatureexpressionvaluesgatherer.h"
22+
23+
/**
24+
* \ingroup core
25+
* Provides a list of features based on filter conditions.
26+
* Features are fetched asynchronously.
27+
*
28+
* \since QGIS 3.0
29+
*/
30+
class CORE_EXPORT QgsFeatureChooserModel : public QAbstractItemModel
31+
{
32+
Q_OBJECT
33+
34+
Q_PROPERTY( QgsVectorLayer *sourceLayer READ sourceLayer WRITE setSourceLayer NOTIFY sourceLayerChanged )
35+
Q_PROPERTY( QString displayExpression READ displayExpression WRITE setDisplayExpression NOTIFY displayExpressionChanged )
36+
Q_PROPERTY( QString filterValue READ filterValue WRITE setFilterValue NOTIFY filterValueChanged )
37+
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged )
38+
Q_PROPERTY( bool allowNull READ allowNull WRITE setAllowNull NOTIFY allowNullChanged )
39+
Q_PROPERTY( bool isLoading READ isLoading NOTIFY isLoadingChanged )
40+
41+
public:
42+
43+
/**
44+
* Extra roles that can be used to fetch data from this model.
45+
*/
46+
enum Role
47+
{
48+
ValueRole = Qt::UserRole + 1, //!< Used to retrieve the displayExpression of a feature.
49+
FeatureRole, //!< Used to retrieve the feature.
50+
FeatureIdRole //!< Used to retrieve the id of a feature.
51+
};
52+
53+
/**
54+
* Create a new QgsFeatureChooserModel, optionally specifying a \a parent.
55+
*/
56+
explicit QgsFeatureChooserModel( QObject *parent = nullptr );
57+
~QgsFeatureChooserModel() override;
58+
59+
/**
60+
* The source layer from which features will be fetched.
61+
*/
62+
QgsVectorLayer *sourceLayer() const;
63+
64+
/**
65+
* The source layer from which features will be fetched.
66+
*/
67+
void setSourceLayer( QgsVectorLayer *sourceLayer );
68+
69+
/**
70+
* The display expression will be used for
71+
*
72+
* - displaying values in the combobox
73+
* - filtering based on filterValue
74+
*/
75+
QString displayExpression() const;
76+
77+
/**
78+
* The display expression will be used for
79+
*
80+
* - displaying values in the combobox
81+
* - filtering based on filterValue
82+
*/
83+
void setDisplayExpression( const QString &displayExpression );
84+
85+
/**
86+
* This value will be used to filter the features available from
87+
* this model. Whenever a substring of the displayExpression of a feature
88+
* matches the filter value, it will be accessible by this model.
89+
*/
90+
QString filterValue() const;
91+
92+
/**
93+
* This value will be used to filter the features available from
94+
* this model. Whenever a substring of the displayExpression of a feature
95+
* matches the filter value, it will be accessible by this model.
96+
*/
97+
void setFilterValue( const QString &filterValue );
98+
99+
QModelIndex index( int row, int column, const QModelIndex &parent ) const override;
100+
QModelIndex parent( const QModelIndex &child ) const override;
101+
int rowCount( const QModelIndex &parent ) const override;
102+
int columnCount( const QModelIndex &parent ) const override;
103+
QVariant data( const QModelIndex &index, int role ) const override;
104+
105+
/**
106+
* An additional filter expression to apply, next to the filterValue.
107+
* Can be used for spatial filtering etc.
108+
*/
109+
QString filterExpression() const;
110+
111+
/**
112+
* An additional filter expression to apply, next to the filterValue.
113+
* Can be used for spatial filtering etc.
114+
*/
115+
void setFilterExpression( const QString &filterExpression );
116+
117+
/**
118+
* Indicator if the model is currently performing any feature iteration in the background.
119+
*/
120+
bool isLoading() const;
121+
122+
/**
123+
* Add a NULL entry to the list.
124+
*/
125+
bool allowNull() const;
126+
127+
/**
128+
* Add a NULL entry to the list.
129+
*/
130+
void setAllowNull( bool allowNull );
131+
132+
QgsFeature currentFeature() const;
133+
134+
/**
135+
* Sets the current feature id
136+
*/
137+
void setCurrentFeature( const QgsFeatureId &featureId );
138+
139+
/**
140+
* Returns the current index
141+
*/
142+
int currentIndex() const {return mCurrentIndex;}
143+
144+
signals:
145+
146+
/**
147+
* Emitted when the current index changed
148+
*/
149+
void currentIndexChanged( int index );
150+
151+
/**
152+
* Emitted when the current feature changed
153+
*/
154+
void currentFeatureChanged( const QgsFeature &feature );
155+
156+
/**
157+
* The source layer from which features will be fetched.
158+
*/
159+
void sourceLayerChanged();
160+
161+
/**
162+
* The display expression will be used for
163+
*
164+
* - displaying values in the combobox
165+
* - filtering based on filterValue
166+
*/
167+
void displayExpressionChanged();
168+
169+
/**
170+
* This value will be used to filter the features available from
171+
* this model. Whenever a substring of the displayExpression of a feature
172+
* matches the filter value, it will be accessible by this model.
173+
*/
174+
void filterValueChanged();
175+
176+
/**
177+
* An additional filter expression to apply, next to the filterValue.
178+
* Can be used for spatial filtering etc.
179+
*/
180+
void filterExpressionChanged();
181+
182+
/**
183+
* Indicator if the model is currently performing any feature iteration in the background.
184+
*/
185+
void isLoadingChanged();
186+
187+
/**
188+
* Indicates that a filter job has been completed and new data may be available.
189+
*/
190+
void filterJobCompleted();
191+
192+
/**
193+
* Notification that the model is about to be changed because a job was completed.
194+
*/
195+
void beginUpdate();
196+
197+
/**
198+
* Notification that the model change is finished. Will always be emitted in sync with beginUpdate.
199+
*/
200+
void endUpdate();
201+
202+
/**
203+
* Add a NULL entry to the list.
204+
*/
205+
void allowNullChanged();
206+
207+
private slots:
208+
void updateCompleter();
209+
void scheduledReload();
210+
211+
private:
212+
void setCurrentIndex( int index, bool force = false );
213+
void reload();
214+
void setCurrentFeatureUnguarded( const QgsFeatureId &featureId );
215+
QgsConditionalStyle featureStyle( const QgsFeature &feature ) const;
216+
217+
QgsVectorLayer *mSourceLayer = nullptr;
218+
QgsExpression mDisplayExpression;
219+
QString mFilterValue;
220+
QString mFilterExpression;
221+
222+
mutable QgsExpressionContext mExpressionContext;
223+
mutable QMap< QgsFeatureId, QgsConditionalStyle > mEntryStylesMap;
224+
QVector<QgsFeatureExpressionValuesGatherer::Entry> mEntries;
225+
QgsFeatureExpressionValuesGatherer *mGatherer = nullptr;
226+
QTimer mReloadTimer;
227+
bool mAllowNull = false;
228+
229+
int mCurrentIndex = -1;
230+
bool mIsSettingCurrentFeature = false;
231+
232+
};
233+
234+
#endif // QGSFEATURECHOOSERMODEL_H

‎src/core/qgsfeaturefiltermodel_p.h renamed to ‎src/core/qgsfeatureexpressionvaluesgatherer.h

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/***************************************************************************
2-
qgsfeaturefiltermodel_p - QgsFieldExpressionValuesGatherer
2+
qgsfeatureexpressionvaluesgatherer - QgsFeatureExpressionValuesGatherer
33
---------------------
44
begin : 10.3.2017
55
copyright : (C) 2017 by Matthias Kuhn
@@ -12,13 +12,14 @@
1212
* (at your option) any later version. *
1313
* *
1414
***************************************************************************/
15-
#ifndef QGSFEATUREFILTERMODEL_P_H
16-
#define QGSFEATUREFILTERMODEL_P_H
15+
#ifndef QGSFEATUREEXPRESSIONVALUESGATHERER_H
16+
#define QGSFEATUREEXPRESSIONVALUESGATHERER_H
1717

1818
#include <QThread>
1919
#include <QMutex>
20-
#include "qgsfeaturefiltermodel.h"
20+
#include "qgsapplication.h"
2121
#include "qgslogger.h"
22+
#include "qgsvectorlayer.h"
2223
#include "qgsvectorlayerfeatureiterator.h"
2324

2425
#define SIP_NO_FILE
@@ -32,22 +33,52 @@
3233
*
3334
* \since QGIS 3.0
3435
*/
35-
class QgsFieldExpressionValuesGatherer: public QThread
36+
class QgsFeatureExpressionValuesGatherer: public QThread
3637
{
3738
Q_OBJECT
3839

3940
public:
40-
QgsFieldExpressionValuesGatherer( QgsVectorLayer *layer,
41-
const QString &displayExpression,
42-
const QStringList &identifierFields,
43-
const QgsFeatureRequest &request = QgsFeatureRequest() )
41+
42+
/**
43+
* Constructor
44+
* \param layer the vector layer
45+
* \param displayExpression if empty, the display expression is taken from the layer definition
46+
* \param request the reqeust to perform
47+
* \param identifierFields an optional list of fields name to be save in a variant list for an easier reuse
48+
*/
49+
QgsFeatureExpressionValuesGatherer( QgsVectorLayer *layer,
50+
const QString &displayExpression = QString(),
51+
const QgsFeatureRequest &request = QgsFeatureRequest(),
52+
const QStringList &identifierFields = QStringList() )
4453
: mSource( new QgsVectorLayerFeatureSource( layer ) )
45-
, mDisplayExpression( displayExpression )
54+
, mDisplayExpression( displayExpression.isEmpty() ? layer->displayExpression() : displayExpression )
4655
, mRequest( request )
4756
, mIdentifierFields( identifierFields )
4857
{
4958
}
5059

60+
struct Entry
61+
{
62+
Entry() = default;
63+
64+
Entry( const QVariantList &_identifierValues, const QString &_value, const QgsFeature &_feature )
65+
: identifierValues( _identifierValues )
66+
, value( _value )
67+
, feature( _feature )
68+
{}
69+
70+
QVariantList identifierValues;
71+
QString value;
72+
QgsFeature feature;
73+
74+
bool operator()( const Entry &lhs, const Entry &rhs ) const;
75+
};
76+
77+
static Entry nullEntry()
78+
{
79+
return Entry( QVariantList(), QgsApplication::nullRepresentation(), QgsFeature() );
80+
}
81+
5182
void run() override
5283
{
5384
mWasCanceled = false;
@@ -67,7 +98,7 @@ class QgsFieldExpressionValuesGatherer: public QThread
6798
QVariantList attributes;
6899
for ( const int idx : attributeIndexes )
69100
attributes << feat.attribute( idx );
70-
mEntries.append( QgsFeatureFilterModel::Entry( attributes, mDisplayExpression.evaluate( &mExpressionContext ).toString(), feat ) );
101+
mEntries.append( Entry( attributes, mDisplayExpression.evaluate( &mExpressionContext ).toString(), feat ) );
71102

72103
QMutexLocker locker( &mCancelMutex );
73104
if ( mWasCanceled )
@@ -89,7 +120,7 @@ class QgsFieldExpressionValuesGatherer: public QThread
89120
return mWasCanceled;
90121
}
91122

92-
QVector<QgsFeatureFilterModel::Entry> entries() const
123+
QVector<Entry> entries() const
93124
{
94125
return mEntries;
95126
}
@@ -124,12 +155,12 @@ class QgsFieldExpressionValuesGatherer: public QThread
124155
QgsFeatureIterator mIterator;
125156
bool mWasCanceled = false;
126157
mutable QMutex mCancelMutex;
127-
QVector<QgsFeatureFilterModel::Entry> mEntries;
158+
QVector<Entry> mEntries;
128159
QStringList mIdentifierFields;
129160
QVariant mData;
130161
};
131162

132163
///@endcond
133164

134165

135-
#endif // QGSFEATUREFILTERMODEL_P_H
166+
#endif // QGSFEATUREEXPRESSIONVALUESGATHERER_H

‎src/core/qgsfeaturefiltermodel.cpp

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* *
1414
***************************************************************************/
1515
#include "qgsfeaturefiltermodel.h"
16-
#include "qgsfeaturefiltermodel_p.h"
16+
#include "qgsfeatureexpressionvaluesgatherer.h"
1717

1818
#include "qgsvectorlayer.h"
1919
#include "qgsconditionalstyle.h"
@@ -46,7 +46,7 @@ QgsFeatureFilterModel::QgsFeatureFilterModel( QObject *parent )
4646
QgsFeatureFilterModel::~QgsFeatureFilterModel()
4747
{
4848
if ( mGatherer )
49-
connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
49+
connect( mGatherer, &QgsFeatureExpressionValuesGatherer::finished, mGatherer, &QgsFeatureExpressionValuesGatherer::deleteLater );
5050
}
5151

5252
QgsVectorLayer *QgsFeatureFilterModel::sourceLayer() const
@@ -228,14 +228,14 @@ void QgsFeatureFilterModel::updateCompleter()
228228
{
229229
emit beginUpdate();
230230

231-
QgsFieldExpressionValuesGatherer *gatherer = qobject_cast<QgsFieldExpressionValuesGatherer *>( sender() );
231+
QgsFeatureExpressionValuesGatherer *gatherer = qobject_cast<QgsFeatureExpressionValuesGatherer *>( sender() );
232232
if ( gatherer->wasCanceled() )
233233
{
234234
delete gatherer;
235235
return;
236236
}
237237

238-
QVector<Entry> entries = mGatherer->entries();
238+
QVector<QgsFeatureExpressionValuesGatherer::Entry> entries = mGatherer->entries();
239239

240240
if ( mExtraIdentifierValueIndex == -1 )
241241
{
@@ -266,11 +266,11 @@ void QgsFeatureFilterModel::updateCompleter()
266266
else
267267
{
268268
// We got strings for a filter selection
269-
std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
269+
std::sort( entries.begin(), entries.end(), []( const QgsFeatureExpressionValuesGatherer::Entry & a, const QgsFeatureExpressionValuesGatherer::Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
270270

271271
if ( mAllowNull )
272272
{
273-
entries.prepend( nullEntry() );
273+
entries.prepend( QgsFeatureExpressionValuesGatherer::nullEntry() );
274274
}
275275

276276
const int newEntriesSize = entries.size();
@@ -425,9 +425,9 @@ void QgsFeatureFilterModel::scheduledReload()
425425

426426
request.setLimit( QgsSettings().value( QStringLiteral( "maxEntriesRelationWidget" ), 100, QgsSettings::Gui ).toInt() );
427427

428-
mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierFields, request );
428+
mGatherer = new QgsFeatureExpressionValuesGatherer( mSourceLayer, mDisplayExpression, request, mIdentifierFields );
429429
mGatherer->setData( mShouldReloadCurrentFeature );
430-
connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::updateCompleter );
430+
connect( mGatherer, &QgsFeatureExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::updateCompleter );
431431

432432

433433
mGatherer->start();
@@ -478,10 +478,10 @@ void QgsFeatureFilterModel::reloadCurrentFeature()
478478

479479
void QgsFeatureFilterModel::setExtraIdentifierValuesUnguarded( const QVariantList &extraIdentifierValues )
480480
{
481-
const QVector<Entry> entries = mEntries;
481+
const QVector<QgsFeatureExpressionValuesGatherer::Entry> entries = mEntries;
482482

483483
int index = 0;
484-
for ( const Entry &entry : entries )
484+
for ( const QgsFeatureExpressionValuesGatherer::Entry &entry : entries )
485485
{
486486
if ( entry.identifierValues == extraIdentifierValues )
487487
{
@@ -504,12 +504,12 @@ void QgsFeatureFilterModel::setExtraIdentifierValuesUnguarded( const QVariantLis
504504
for ( const QVariant &v : qgis::as_const( extraIdentifierValues ) )
505505
values << QStringLiteral( "(%1)" ).arg( v.toString() );
506506

507-
mEntries.prepend( Entry( extraIdentifierValues, values.join( QStringLiteral( " " ) ), QgsFeature() ) );
507+
mEntries.prepend( QgsFeatureExpressionValuesGatherer::Entry( extraIdentifierValues, values.join( QStringLiteral( " " ) ), QgsFeature() ) );
508508
reloadCurrentFeature();
509509
}
510510
else
511511
{
512-
mEntries.prepend( nullEntry() );
512+
mEntries.prepend( QgsFeatureExpressionValuesGatherer::nullEntry() );
513513
}
514514
endInsertRows();
515515

@@ -518,11 +518,6 @@ void QgsFeatureFilterModel::setExtraIdentifierValuesUnguarded( const QVariantLis
518518
}
519519
}
520520

521-
QgsFeatureFilterModel::Entry QgsFeatureFilterModel::nullEntry()
522-
{
523-
return Entry( QVariantList(), QgsApplication::nullRepresentation(), QgsFeature() );
524-
}
525-
526521
QgsConditionalStyle QgsFeatureFilterModel::featureStyle( const QgsFeature &feature ) const
527522
{
528523
if ( !mSourceLayer )

‎src/core/qgsfeaturefiltermodel.h

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@
1818
#include <QAbstractItemModel>
1919

2020
#include "qgsconditionalstyle.h"
21-
22-
class QgsFieldExpressionValuesGatherer;
21+
#include "qgsfeatureexpressionvaluesgatherer.h"
2322

2423
/**
2524
* \ingroup core
@@ -305,23 +304,7 @@ class CORE_EXPORT QgsFeatureFilterModel : public QAbstractItemModel
305304
void reload();
306305
void reloadCurrentFeature();
307306
void setExtraIdentifierValuesUnguarded( const QVariantList &extraIdentifierValues );
308-
struct Entry
309-
{
310-
Entry() = default;
311-
312-
Entry( const QVariantList &_identifierValues, const QString &_value, const QgsFeature &_feature )
313-
: identifierValues( _identifierValues )
314-
, value( _value )
315-
, feature( _feature )
316-
{}
317307

318-
QVariantList identifierValues;
319-
QString value;
320-
QgsFeature feature;
321-
322-
bool operator()( const Entry &lhs, const Entry &rhs ) const;
323-
};
324-
Entry nullEntry();
325308

326309
QgsConditionalStyle featureStyle( const QgsFeature &feature ) const;
327310

@@ -332,8 +315,8 @@ class CORE_EXPORT QgsFeatureFilterModel : public QAbstractItemModel
332315

333316
mutable QgsExpressionContext mExpressionContext;
334317
mutable QMap< QgsFeatureId, QgsConditionalStyle > mEntryStylesMap;
335-
QVector<Entry> mEntries;
336-
QgsFieldExpressionValuesGatherer *mGatherer = nullptr;
318+
QVector<QgsFeatureExpressionValuesGatherer::Entry> mEntries;
319+
QgsFeatureExpressionValuesGatherer *mGatherer = nullptr;
337320
QTimer mReloadTimer;
338321
bool mShouldReloadCurrentFeature = false;
339322
bool mExtraValueDoesNotExist = false;
@@ -345,7 +328,6 @@ class CORE_EXPORT QgsFeatureFilterModel : public QAbstractItemModel
345328

346329
int mExtraIdentifierValueIndex = -1;
347330

348-
friend class QgsFieldExpressionValuesGatherer;
349331
};
350332

351333
#endif // QGSFEATUREFILTERMODEL_H

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ SET(QGIS_GUI_SRCS
403403
qgsfeatureselectiondlg.cpp
404404
qgsfieldcombobox.cpp
405405
qgsfieldexpressionwidget.cpp
406+
qgsfeaturechooserwidget.cpp
406407
qgsfieldmappingwidget.cpp
407408
qgsfieldmappingmodel.cpp
408409
qgsfeaturelistcombobox.cpp
@@ -627,6 +628,7 @@ SET(QGIS_GUI_HDRS
627628
qgsextentgroupbox.h
628629
qgsextentwidget.h
629630
qgsexternalresourcewidget.h
631+
qgsfeaturechooserwidget.h
630632
qgsfeaturelistcombobox.h
631633
qgsfeatureselectiondlg.h
632634
qgsfieldcombobox.h

‎src/gui/qgsexpressionpreviewwidget.cpp

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
#include "qgsexpressionpreviewwidget.h"
1717
#include "qgsmessageviewer.h"
1818
#include "qgsvectorlayer.h"
19+
#include "qgsfeaturechooserwidget.h"
20+
21+
1922

2023

2124

@@ -25,20 +28,44 @@ QgsExpressionPreviewWidget::QgsExpressionPreviewWidget( QWidget *parent )
2528
setupUi( this );
2629
mPreviewLabel->clear();
2730

31+
connect( mFeatureChooserWidget, &QgsFeatureChooserWidget::currentFeatureChanged, this, &QgsExpressionPreviewWidget::setCurrentFeature );
2832
connect( mPreviewLabel, &QLabel::linkActivated, this, &QgsExpressionPreviewWidget::linkActivated );
2933
}
3034

3135
void QgsExpressionPreviewWidget::setLayer( QgsVectorLayer *layer )
3236
{
3337
mLayer = layer;
34-
mFeatureListComboBox->setSourceLayer( layer );
38+
mFeatureChooserWidget->setLayer( layer );
3539
}
3640

3741
void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
42+
{
43+
mExpressionText = expression;
44+
refreshPreview();
45+
}
46+
47+
void QgsExpressionPreviewWidget::setCurrentFeature( const QgsFeature &feature )
48+
{
49+
// todo: update the combo box if it has been set externaly?
50+
mExpressionContext.setFeature( feature );
51+
refreshPreview();
52+
}
53+
54+
void QgsExpressionPreviewWidget::setGeomCalculator( const QgsDistanceArea &da )
55+
{
56+
mDa = da;
57+
}
58+
59+
void QgsExpressionPreviewWidget::setExpressionContext( const QgsExpressionContext &context )
60+
{
61+
mExpressionContext = context;
62+
}
63+
64+
void QgsExpressionPreviewWidget::refreshPreview()
3865
{
3966
// If the string is empty the expression will still "fail" although
4067
// we don't show the user an error as it will be confusing.
41-
if ( expression.isEmpty() )
68+
if ( mExpressionText.isEmpty() )
4269
{
4370
mPreviewLabel->clear();
4471
mPreviewLabel->setStyleSheet( QString() );
@@ -48,20 +75,13 @@ void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
4875
}
4976
else
5077
{
51-
mExpression = QgsExpression( expression );
78+
mExpression = QgsExpression( mExpressionText );
5279

5380
if ( mLayer )
5481
{
82+
// TODO: is this OK?
5583
// Only set calculator if we have layer, else use default.
5684
mExpression.setGeomCalculator( &mDa );
57-
58-
if ( !mExpressionContext.feature().isValid() )
59-
{
60-
// no feature passed yet, try to get from layer
61-
QgsFeature f;
62-
mLayer->getFeatures( QgsFeatureRequest().setLimit( 1 ) ).nextFeature( f );
63-
mExpressionContext.setFeature( f );
64-
}
6585
}
6686

6787
QVariant value = mExpression.evaluate( &mExpressionContext );
@@ -99,16 +119,6 @@ void QgsExpressionPreviewWidget::setExpressionText( const QString &expression )
99119
}
100120
}
101121

102-
void QgsExpressionPreviewWidget::setGeomCalculator( const QgsDistanceArea &da )
103-
{
104-
mDa = da;
105-
}
106-
107-
void QgsExpressionPreviewWidget::setExpressionContext( const QgsExpressionContext &context )
108-
{
109-
mExpressionContext = context;
110-
}
111-
112122
void QgsExpressionPreviewWidget::linkActivated( const QString & )
113123
{
114124
QgsMessageViewer *mv = new QgsMessageViewer( this );

‎src/gui/qgsexpressionpreviewwidget.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ class GUI_EXPORT QgsExpressionPreviewWidget : public QWidget, private Ui::QgsExp
5656
* Sets the expression context for the widget. The context is used for the expression
5757
* preview result and to populate the list of available functions and variables.
5858
* \param context expression context
59-
* \since QGIS 2.12
6059
*/
6160
void setExpressionContext( const QgsExpressionContext &context );
6261

@@ -107,6 +106,9 @@ class GUI_EXPORT QgsExpressionPreviewWidget : public QWidget, private Ui::QgsExp
107106
void toolTipChanged( const QString &toolTip );
108107

109108
public slots:
109+
//! sets the current feature used
110+
void setCurrentFeature( const QgsFeature &feature );
111+
110112

111113
private slots:
112114
void linkActivated( const QString & );
@@ -116,13 +118,15 @@ class GUI_EXPORT QgsExpressionPreviewWidget : public QWidget, private Ui::QgsExp
116118

117119
private:
118120
void setExpressionToolTip( const QString &toolTip );
121+
void refreshPreview();
119122

120123
QgsVectorLayer *mLayer = nullptr;
121124
QgsExpressionContext mExpressionContext;
122125
QgsDistanceArea mDa;
123126
QString mToolTip;
124127
bool mEvalError = true;
125128
bool mParserError = true;
129+
QString mExpressionText;
126130
QgsExpression mExpression;
127131
};
128132

‎src/gui/qgsfeaturechooserwidget.cpp

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/***************************************************************************
2+
qgsfeaturechooserwidget.cpp - QgsFeatureChooserWidget
3+
---------------------
4+
begin : 03.04.2020
5+
copyright : (C) 2020 by Denis Rouzaud
6+
email : denis@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include <QHBoxLayout>
17+
#include <QKeyEvent>
18+
19+
#include "qgsfeaturechooserwidget.h"
20+
#include "qgsfilterlineedit.h"
21+
#include "qgsfeaturechoosermodel.h"
22+
23+
QgsFeatureChooserWidget::QgsFeatureChooserWidget( QWidget *parent )
24+
: QWidget( parent )
25+
, mModel( new QgsFeatureChooserModel( this ) )
26+
, mCompleter( new QCompleter( mModel ) )
27+
{
28+
QHBoxLayout *layout = new QHBoxLayout();
29+
mComboBox = new QComboBox( this );
30+
mComboBox->setEditable( true );
31+
layout->addWidget( mComboBox );
32+
setLayout( layout );
33+
34+
mCompleter->setCaseSensitivity( Qt::CaseInsensitive );
35+
mCompleter->setFilterMode( Qt::MatchContains );
36+
mComboBox->setCompleter( mCompleter );
37+
mCompleter->setWidget( mComboBox );
38+
connect( mModel, &QgsFeatureChooserModel::sourceLayerChanged, this, &QgsFeatureChooserWidget::layerChanged );
39+
connect( mModel, &QgsFeatureChooserModel::displayExpressionChanged, this, &QgsFeatureChooserWidget::displayExpressionChanged );
40+
connect( mModel, &QgsFeatureChooserModel::filterExpressionChanged, this, &QgsFeatureChooserWidget::filterExpressionChanged );
41+
connect( mModel, &QgsFeatureChooserModel::isLoadingChanged, this, &QgsFeatureChooserWidget::onLoadingChanged );
42+
connect( mModel, &QgsFeatureChooserModel::filterJobCompleted, this, &QgsFeatureChooserWidget::onFilterUpdateCompleted );
43+
connect( mModel, &QgsFeatureChooserModel::allowNullChanged, this, &QgsFeatureChooserWidget::allowNullChanged );
44+
connect( mModel, &QgsFeatureChooserModel::currentIndexChanged, mComboBox, &QComboBox::setCurrentIndex );
45+
connect( mModel, &QgsFeatureChooserModel::currentFeatureChanged, this, &QgsFeatureChooserWidget::currentFeatureChanged );
46+
connect( mCompleter, static_cast<void( QCompleter::* )( const QModelIndex & )>( &QCompleter::highlighted ), this, &QgsFeatureChooserWidget::onItemSelected );
47+
connect( mCompleter, static_cast<void( QCompleter::* )( const QModelIndex & )>( &QCompleter::activated ), this, &QgsFeatureChooserWidget::onActivated );
48+
connect( mModel, &QgsFeatureChooserModel::beginUpdate, this, &QgsFeatureChooserWidget::storeLineEditState );
49+
connect( mModel, &QgsFeatureChooserModel::endUpdate, this, &QgsFeatureChooserWidget::restoreLineEditState );
50+
connect( mModel, &QgsFeatureChooserModel::endUpdate, this, &QgsFeatureChooserWidget::modelUpdated );
51+
connect( mModel, &QgsFeatureChooserModel::dataChanged, this, &QgsFeatureChooserWidget::onDataChanged );
52+
53+
connect( mComboBox, static_cast<void( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsFeatureChooserWidget::onCurrentIndexChanged );
54+
55+
mLineEdit = new QgsFilterLineEdit( nullptr, QgsApplication::nullRepresentation() );
56+
mLineEdit->setSelectOnFocus( true );
57+
mLineEdit->setShowClearButton( allowNull() );
58+
59+
mComboBox->setEditable( true );
60+
mComboBox->setLineEdit( mLineEdit );
61+
mComboBox->setModel( mModel );
62+
63+
connect( mLineEdit, &QgsFilterLineEdit::textEdited, this, &QgsFeatureChooserWidget::onCurrentTextChanged );
64+
65+
setToolTip( tr( "Just start typing what you are looking for." ) );
66+
}
67+
68+
QgsVectorLayer *QgsFeatureChooserWidget::layer() const
69+
{
70+
return mModel->sourceLayer();
71+
}
72+
73+
void QgsFeatureChooserWidget::setLayer( QgsVectorLayer *sourceLayer )
74+
{
75+
mModel->setSourceLayer( sourceLayer );
76+
}
77+
78+
void QgsFeatureChooserWidget::setCurrentFeature(QgsFeatureId featureId )
79+
{
80+
mModel->setCurrentFeature( featureId );
81+
}
82+
83+
QString QgsFeatureChooserWidget::displayExpression() const
84+
{
85+
return mModel->displayExpression();
86+
}
87+
88+
void QgsFeatureChooserWidget::setDisplayExpression( const QString &expression )
89+
{
90+
mModel->setDisplayExpression( expression );
91+
}
92+
93+
void QgsFeatureChooserWidget::onCurrentTextChanged( const QString &text )
94+
{
95+
mIsCurrentlyEdited = true;
96+
mPopupRequested = true;
97+
mModel->setFilterValue( text );
98+
}
99+
100+
void QgsFeatureChooserWidget::onFilterUpdateCompleted()
101+
{
102+
if ( mPopupRequested )
103+
mCompleter->complete();
104+
105+
mPopupRequested = false;
106+
}
107+
108+
void QgsFeatureChooserWidget::onLoadingChanged()
109+
{
110+
mLineEdit->setShowSpinner( mModel->isLoading() );
111+
}
112+
113+
void QgsFeatureChooserWidget::onItemSelected( const QModelIndex &index )
114+
{
115+
mComboBox->setCurrentIndex( index.row() );
116+
}
117+
118+
void QgsFeatureChooserWidget::onCurrentIndexChanged( int i )
119+
{
120+
if ( !mHasStoredEditState )
121+
mIsCurrentlyEdited = false;
122+
QModelIndex modelIndex = mModel->index( i, 0, QModelIndex() );
123+
mModel->setCurrentFeature( mModel->data( modelIndex, QgsFeatureChooserModel::FeatureIdRole ).value<QgsFeatureId>() );
124+
mLineEdit->setText( mModel->data( modelIndex, QgsFeatureChooserModel::ValueRole ).toString() );
125+
mLineEdit->setFont( mModel->data( modelIndex, Qt::FontRole ).value<QFont>() );
126+
QPalette palette = mLineEdit->palette();
127+
palette.setBrush( mLineEdit->foregroundRole(), mModel->data( modelIndex, Qt::ForegroundRole ).value<QBrush>() );
128+
mLineEdit->setPalette( palette );
129+
}
130+
131+
void QgsFeatureChooserWidget::onActivated( QModelIndex modelIndex )
132+
{
133+
setCurrentFeature( mModel->data( modelIndex, QgsFeatureChooserModel::FeatureIdRole ).value<QgsFeatureId>() );
134+
mLineEdit->setText( mModel->data( modelIndex, QgsFeatureChooserModel::ValueRole ).toString() );
135+
}
136+
137+
void QgsFeatureChooserWidget::storeLineEditState()
138+
{
139+
if ( mIsCurrentlyEdited )
140+
{
141+
mHasStoredEditState = true;
142+
mLineEditState.store( mLineEdit );
143+
}
144+
}
145+
146+
void QgsFeatureChooserWidget::restoreLineEditState()
147+
{
148+
if ( mIsCurrentlyEdited )
149+
{
150+
mHasStoredEditState = false;
151+
mLineEditState.restore( mLineEdit );
152+
}
153+
}
154+
155+
int QgsFeatureChooserWidget::nullIndex() const
156+
{
157+
int index = -1;
158+
159+
if ( allowNull() )
160+
{
161+
index = mComboBox->findText( QgsApplication::nullRepresentation( ) );
162+
}
163+
164+
return index;
165+
}
166+
167+
void QgsFeatureChooserWidget::onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles )
168+
{
169+
Q_UNUSED( roles )
170+
if ( !mIsCurrentlyEdited )
171+
{
172+
const int currentIndex = mModel->currentIndex();
173+
if ( currentIndex >= topLeft.row() && currentIndex <= bottomRight.row() )
174+
{
175+
QModelIndex modelIndex = mModel->index( currentIndex, 0, QModelIndex() );
176+
mLineEdit->setText( mModel->data( modelIndex, QgsFeatureChooserModel::ValueRole ).toString() );
177+
}
178+
}
179+
}
180+
181+
QModelIndex QgsFeatureChooserWidget::currentModelIndex() const
182+
{
183+
return mModel->index( mModel->currentIndex(), 0, QModelIndex() );
184+
}
185+
186+
void QgsFeatureChooserWidget::focusOutEvent( QFocusEvent *event )
187+
{
188+
Q_UNUSED( event )
189+
QWidget::focusOutEvent( event );
190+
mLineEdit->setText( mModel->data( currentModelIndex(), QgsFeatureChooserModel::ValueRole ).toString() );
191+
}
192+
193+
void QgsFeatureChooserWidget::keyPressEvent( QKeyEvent *event )
194+
{
195+
if ( event->key() == Qt::Key_Escape )
196+
{
197+
mLineEdit->setText( mModel->data( currentModelIndex(), QgsFeatureChooserModel::ValueRole ).toString() );
198+
}
199+
QWidget::keyReleaseEvent( event );
200+
}
201+
202+
bool QgsFeatureChooserWidget::allowNull() const
203+
{
204+
return mModel->allowNull();
205+
}
206+
207+
void QgsFeatureChooserWidget::setAllowNull( bool allowNull )
208+
{
209+
mModel->setAllowNull( allowNull );
210+
mLineEdit->setClearMode( allowNull ? QgsFilterLineEdit::ClearToNull : QgsFilterLineEdit::ClearToDefault );
211+
}
212+
213+
QString QgsFeatureChooserWidget::filterExpression() const
214+
{
215+
return mModel->filterExpression();
216+
}
217+
218+
void QgsFeatureChooserWidget::setFilterExpression( const QString &filterExpression )
219+
{
220+
mModel->setFilterExpression( filterExpression );
221+
}
222+
223+
void QgsFeatureChooserWidget::LineEditState::store( QLineEdit *lineEdit )
224+
{
225+
text = lineEdit->text();
226+
selectionStart = lineEdit->selectionStart();
227+
selectionLength = lineEdit->selectedText().length();
228+
cursorPosition = lineEdit->cursorPosition();
229+
230+
}
231+
232+
void QgsFeatureChooserWidget::LineEditState::restore( QLineEdit *lineEdit ) const
233+
{
234+
lineEdit->setText( text );
235+
lineEdit->setCursorPosition( cursorPosition );
236+
if ( selectionStart > -1 )
237+
lineEdit->setSelection( selectionStart, selectionLength );
238+
}

‎src/gui/qgsfeaturechooserwidget.h

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/***************************************************************************
2+
qgsfeaturechooserwidget.h - QgsFeatureChooserWidget
3+
---------------------
4+
begin : 03.04.2020
5+
copyright : (C) 2020 by Denis Rouzaud
6+
email : denis@opengis.ch
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSFEATURECHOOSER_H
16+
#define QGSFEATURECHOOSER_H
17+
18+
#include <QWidget>
19+
#include <QComboBox>
20+
#include <QLineEdit>
21+
#include <QCompleter>
22+
23+
#include "qgsfeature.h"
24+
#include "qgsfeaturerequest.h"
25+
#include "qgis_gui.h"
26+
27+
class QgsVectorLayer;
28+
class QgsFeatureChooserModel;
29+
class QgsAnimatedIcon;
30+
class QgsFilterLineEdit;
31+
32+
33+
/**
34+
* \ingroup gui
35+
* This offers a combobox with autocompleter that allows selecting features from a layer.
36+
*
37+
* It will show up to 100 entries at a time. The entries can be chosen based on the displayExpression
38+
* and whenever text is typed into the combobox, the completer and popup will adjust to features matching the typed text.
39+
*
40+
* \since QGIS 3.0
41+
*/
42+
class GUI_EXPORT QgsFeatureChooserWidget : public QWidget
43+
{
44+
Q_OBJECT
45+
46+
Q_PROPERTY( QgsVectorLayer *layer READ layer WRITE setLayer NOTIFY layerChanged )
47+
Q_PROPERTY( QString displayExpression READ displayExpression WRITE setDisplayExpression NOTIFY displayExpressionChanged )
48+
Q_PROPERTY( QString filterExpression READ filterExpression WRITE setFilterExpression NOTIFY filterExpressionChanged )
49+
Q_PROPERTY( bool allowNull READ allowNull WRITE setAllowNull NOTIFY allowNullChanged )
50+
51+
public:
52+
53+
/**
54+
* Create a new QgsFeatureChooserWidget, optionally specifying a \a parent.
55+
*/
56+
QgsFeatureChooserWidget( QWidget *parent = nullptr );
57+
58+
/**
59+
* The layer from which features should be listed.
60+
*/
61+
QgsVectorLayer *layer() const;
62+
63+
/**
64+
* The layer from which features should be listed.
65+
*/
66+
void setLayer( QgsVectorLayer *layer );
67+
68+
/**
69+
* Sets the current index by using the given feature
70+
*/
71+
void setCurrentFeature( QgsFeatureId featureId );
72+
73+
/**
74+
* The display expression will be used to display features as well as
75+
* the value to match the typed text against.
76+
*/
77+
QString displayExpression() const;
78+
79+
/**
80+
* The display expression will be used to display features as well as
81+
* the value to match the typed text against.
82+
*/
83+
void setDisplayExpression( const QString &displayExpression );
84+
85+
/**
86+
* An additional expression to further restrict the available features.
87+
* This can be used to integrate additional spatial or other constraints.
88+
*/
89+
QString filterExpression() const;
90+
91+
/**
92+
* Returns the current index of the NULL value, or -1 if NULL values are
93+
* not allowed.
94+
*
95+
* \since QGIS 3.2
96+
*/
97+
int nullIndex() const;
98+
99+
/**
100+
* An additional expression to further restrict the available features.
101+
* This can be used to integrate additional spatial or other constraints.
102+
*
103+
* TODO!
104+
*/
105+
void setFilterExpression( const QString &filterExpression );
106+
107+
/**
108+
* Determines if a NULL value should be available in the list.
109+
*/
110+
bool allowNull() const;
111+
112+
/**
113+
* Determines if a NULL value should be available in the list.
114+
*/
115+
void setAllowNull( bool allowNull );
116+
117+
/**
118+
* The index of the currently selected item.
119+
*/
120+
QModelIndex currentModelIndex() const;
121+
122+
void focusOutEvent( QFocusEvent *event ) override;
123+
124+
void keyPressEvent( QKeyEvent *event ) override;
125+
126+
signals:
127+
128+
/**
129+
* The underlying model has been updated.
130+
*
131+
* \since QGIS 3.2
132+
*/
133+
void modelUpdated();
134+
135+
/**
136+
* The layer from which features should be listed.
137+
*/
138+
void layerChanged();
139+
140+
/**
141+
* The display expression will be used to display features as well as
142+
* the the value to match the typed text against.
143+
*/
144+
void displayExpressionChanged();
145+
146+
/**
147+
* An additional expression to further restrict the available features.
148+
* This can be used to integrate additional spatial or other constraints.
149+
*/
150+
void filterExpressionChanged();
151+
152+
/**
153+
* Determines if a NULL value should be available in the list.
154+
*/
155+
void allowNullChanged();
156+
157+
//! Sends the feature as soon as it is chosen
158+
void currentFeatureChanged( const QgsFeature &feature );
159+
160+
private slots:
161+
void onCurrentTextChanged( const QString &text );
162+
void onFilterUpdateCompleted();
163+
void onLoadingChanged();
164+
void onItemSelected( const QModelIndex &index );
165+
void onCurrentIndexChanged( int i );
166+
void onActivated( QModelIndex index );
167+
void storeLineEditState();
168+
void restoreLineEditState();
169+
void onDataChanged( const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>() );
170+
171+
private:
172+
struct LineEditState
173+
{
174+
void store( QLineEdit *lineEdit );
175+
void restore( QLineEdit *lineEdit ) const;
176+
177+
QString text;
178+
int selectionStart;
179+
int selectionLength;
180+
int cursorPosition;
181+
};
182+
183+
QComboBox *mComboBox;
184+
QgsFeatureChooserModel *mModel = nullptr;
185+
QCompleter *mCompleter = nullptr;
186+
QgsFilterLineEdit *mLineEdit;
187+
bool mPopupRequested = false;
188+
bool mIsCurrentlyEdited = false;
189+
bool mHasStoredEditState = false;
190+
LineEditState mLineEditState;
191+
192+
friend class TestQgsFeatureChooserWidget;
193+
};
194+
195+
196+
197+
#endif // QGSFEATURECHOOSER_H

‎src/ui/qgsexpressionpreviewbase.ui

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,21 +68,19 @@
6868
</widget>
6969
</item>
7070
<item>
71-
<widget class="QgsFeatureListComboBox" name="mFeatureListComboBox"/>
71+
<widget class="QgsFeatureChooserWidget" name="mFeatureChooserWidget"/>
7272
</item>
7373
</layout>
7474
</item>
7575
</layout>
7676
</widget>
7777
<customwidgets>
7878
<customwidget>
79-
<class>QgsFeatureListComboBox</class>
79+
<class>QgsFeatureChooserWidget</class>
8080
<extends>QComboBox</extends>
81-
<header>qgsfeaturelistcombobox.h</header>
81+
<header>qgsfeaturechooserwidget.h</header>
8282
</customwidget>
8383
</customwidgets>
84-
<resources>
85-
<include location="../../images/images.qrc"/>
86-
</resources>
84+
<resources/>
8785
<connections/>
8886
</ui>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for edit widgets.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Denis Rouzaud'
10+
__date__ = '24/04/2020'
11+
__copyright__ = 'Copyright 2015, The QGIS Project'
12+
13+
import qgis # NOQA
14+
15+
import os
16+
17+
from qgis.core import (
18+
QgsFeature,
19+
QgsVectorLayer,
20+
QgsProject,
21+
QgsRelation,
22+
QgsTransaction,
23+
QgsFeatureRequest,
24+
QgsVectorLayerTools,
25+
QgsGeometry
26+
)
27+
28+
from qgis.gui import (
29+
QgsGui,
30+
QgsRelationWidgetWrapper,
31+
QgsAttributeEditorContext,
32+
QgsMapCanvas,
33+
QgsAdvancedDigitizingDockWidget
34+
)
35+
36+
from qgis.PyQt.QtCore import QTimer
37+
from qgis.PyQt.QtWidgets import (
38+
QToolButton,
39+
QMessageBox,
40+
QDialogButtonBox,
41+
QTableView,
42+
QDialog
43+
)
44+
from qgis.testing import start_app, unittest
45+
46+
start_app()
47+
48+
49+
def createLayer():
50+
layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer",
51+
"test layer", "memory")
52+
pr = layer.dataProvider()
53+
f = QgsFeature()
54+
f.setAttributes(["test", 123])
55+
f.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(100, 200)))
56+
f2 = QgsFeature()
57+
f2.setAttributes(["test2", 457])
58+
f2.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(200, 200)))
59+
f3 = QgsFeature()
60+
f3.setAttributes(["test2", 888])
61+
f3.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(300, 200)))
62+
assert pr.addFeatures([f, f2, f3])
63+
assert layer.featureCount() == 3
64+
return layer
65+
66+
67+
class TestQgsRelationEditWidget(unittest.TestCase):
68+
69+
def testSetFeature(self):
70+
createLayer()
71+
72+
73+
if __name__ == '__main__':
74+
unittest.main()
75+
76+

0 commit comments

Comments
 (0)
Please sign in to comment.