QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsfeaturefilterwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfeaturefilterwidget.cpp
3  --------------------------------------
4  Date : 20.9.2019
5  Copyright : (C) 2019 Julien Cabieces
6  Email : julien dot cabieces at oslandia dot com
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 
17 
18 //
19 // W A R N I N G
20 // -------------
21 //
22 // This file is not part of the QGIS API. It exists purely as an
23 // implementation detail. This header file may change from version to
24 // version without notice, or even be removed.
25 //
26 
28 
29 #include "qgsapplication.h"
30 #include "qgssearchwidgetwrapper.h"
31 #include "qgsdualview.h"
36 #include "qgsgui.h"
37 #include "qgsdialog.h"
38 #include "qgsexpressionlineedit.h"
39 #include "qgsmessagebar.h"
40 
41 #include <QMenu>
42 
43 QgsFeatureFilterWidget::QgsFeatureFilterWidget( QWidget *parent )
44  : QWidget( parent )
45 {
46  setupUi( this );
47 
48  // Initialize filter gui elements
49  mFilterColumnsMenu = new QMenu( this );
50  mActionFilterColumnsMenu->setMenu( mFilterColumnsMenu );
51  mStoredFilterExpressionMenu = new QMenu( this );
52  mActionStoredFilterExpressions->setMenu( mStoredFilterExpressionMenu );
53 
54  // Set filter icon in a couple of places
55  QIcon filterIcon = QgsApplication::getThemeIcon( "/mActionFilter2.svg" );
56  mActionShowAllFilter->setIcon( filterIcon );
57  mActionAdvancedFilter->setIcon( filterIcon );
58  mActionSelectedFilter->setIcon( filterIcon );
59  mActionVisibleFilter->setIcon( filterIcon );
60  mActionEditedFilter->setIcon( filterIcon );
61 
62 
63  // Set button to store or delete stored filter expressions
64  mStoreFilterExpressionButton->setDefaultAction( mActionHandleStoreFilterExpression );
65  connect( mActionSaveAsStoredFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::saveAsStoredFilterExpression );
66  connect( mActionEditStoredFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::editStoredFilterExpression );
67  connect( mActionHandleStoreFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::handleStoreFilterExpression );
68  mApplyFilterButton->setDefaultAction( mActionApplyFilter );
69 
70  // Connect filter signals
71  connect( mActionAdvancedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterExpressionBuilder );
72  connect( mActionShowAllFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterShowAll );
73  connect( mActionSelectedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterSelected );
74  connect( mActionVisibleFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterVisible );
75  connect( mActionEditedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterEdited );
76  connect( mFilterQuery, &QLineEdit::returnPressed, this, &QgsFeatureFilterWidget::filterQueryAccepted );
77  connect( mActionApplyFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterQueryAccepted );
78  connect( mFilterQuery, &QLineEdit::textChanged, this, &QgsFeatureFilterWidget::onFilterQueryTextChanged );
79 }
80 
81 void QgsFeatureFilterWidget::init( QgsVectorLayer *layer, const QgsAttributeEditorContext &context, QgsDualView *mainView,
82  QgsMessageBar *messageBar, int )
83 {
84  mMainView = mainView;
85  mLayer = layer;
86  mEditorContext = context;
87  mMessageBar = messageBar;
88 
89  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsFeatureFilterWidget::columnBoxInit );
90  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsFeatureFilterWidget::columnBoxInit );
91 
92  //set delay on entering text
93  mFilterQueryTimer.setSingleShot( true );
94  connect( &mFilterQueryTimer, &QTimer::timeout, this, &QgsFeatureFilterWidget::updateCurrentStoredFilterExpression );
95 
96  columnBoxInit();
97  storedFilterExpressionBoxInit();
98  storeExpressionButtonInit();
99 }
100 
101 void QgsFeatureFilterWidget::filterShowAll()
102 {
103  mFilterButton->setDefaultAction( mActionShowAllFilter );
104  mFilterButton->setPopupMode( QToolButton::InstantPopup );
105  mFilterQuery->setVisible( false );
106  mFilterQuery->setText( QString() );
107  if ( mCurrentSearchWidgetWrapper )
108  {
109  mCurrentSearchWidgetWrapper->widget()->setVisible( false );
110  }
111  mApplyFilterButton->setVisible( false );
112  mStoreFilterExpressionButton->setVisible( false );
113  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowAll );
114 }
115 
116 void QgsFeatureFilterWidget::filterSelected()
117 {
118  mFilterButton->setDefaultAction( mActionSelectedFilter );
119  mFilterButton->setPopupMode( QToolButton::InstantPopup );
120  mFilterQuery->setVisible( false );
121  mApplyFilterButton->setVisible( false );
122  mStoreFilterExpressionButton->setVisible( false );
123  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowSelected );
124 }
125 
126 void QgsFeatureFilterWidget::filterVisible()
127 {
128  if ( !mLayer->isSpatial() )
129  {
130  filterShowAll();
131  return;
132  }
133 
134  mFilterButton->setDefaultAction( mActionVisibleFilter );
135  mFilterButton->setPopupMode( QToolButton::InstantPopup );
136  mFilterQuery->setVisible( false );
137  mApplyFilterButton->setVisible( false );
138  mStoreFilterExpressionButton->setVisible( false );
139  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
140 }
141 
142 void QgsFeatureFilterWidget::filterEdited()
143 {
144  mFilterButton->setDefaultAction( mActionEditedFilter );
145  mFilterButton->setPopupMode( QToolButton::InstantPopup );
146  mFilterQuery->setVisible( false );
147  mApplyFilterButton->setVisible( false );
148  mStoreFilterExpressionButton->setVisible( false );
149  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowEdited );
150 }
151 
152 
153 void QgsFeatureFilterWidget::filterQueryAccepted()
154 {
155  if ( ( mFilterQuery->isVisible() && mFilterQuery->text().isEmpty() ) ||
156  ( mCurrentSearchWidgetWrapper && mCurrentSearchWidgetWrapper->widget()->isVisible()
157  && mCurrentSearchWidgetWrapper->expression().isEmpty() ) )
158  {
159  filterShowAll();
160  return;
161  }
162  filterQueryChanged( mFilterQuery->text() );
163 }
164 
165 void QgsFeatureFilterWidget::filterQueryChanged( const QString &query )
166 {
167  QString str;
168  if ( mFilterButton->defaultAction() == mActionAdvancedFilter )
169  {
170  str = query;
171  }
172  else if ( mCurrentSearchWidgetWrapper )
173  {
174  str = mCurrentSearchWidgetWrapper->expression();
175  }
176 
177  setFilterExpression( str );
178 }
179 
180 void QgsFeatureFilterWidget::columnBoxInit()
181 {
182  const auto constActions = mFilterColumnsMenu->actions();
183  for ( QAction *a : constActions )
184  {
185  mFilterColumnsMenu->removeAction( a );
186  mFilterButton->removeAction( a );
187  delete a;
188  }
189 
190  mFilterButton->addAction( mActionShowAllFilter );
191  mFilterButton->addAction( mActionSelectedFilter );
192  if ( mLayer->isSpatial() )
193  {
194  mFilterButton->addAction( mActionVisibleFilter );
195  }
196  mFilterButton->addAction( mActionEditedFilter );
197  mFilterButton->addAction( mActionFilterColumnsMenu );
198  mFilterButton->addAction( mActionAdvancedFilter );
199  mFilterButton->addAction( mActionStoredFilterExpressions );
200 
201  const QList<QgsField> fields = mLayer->fields().toList();
202 
203  const auto constFields = fields;
204  for ( const QgsField &field : constFields )
205  {
206  int idx = mLayer->fields().lookupField( field.name() );
207  if ( idx < 0 )
208  continue;
209 
210  if ( QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() ).type() != QLatin1String( "Hidden" ) )
211  {
212  QIcon icon = mLayer->fields().iconForField( idx );
213  QString alias = mLayer->attributeDisplayName( idx );
214 
215  // Generate action for the filter popup button
216  QAction *filterAction = new QAction( icon, alias, mFilterButton );
217  filterAction->setData( field.name() );
218 
219  connect( filterAction, &QAction::triggered, this, [ = ] { filterColumnChanged( filterAction ); } );
220  mFilterColumnsMenu->addAction( filterAction );
221  }
222  }
223 }
224 
225 void QgsFeatureFilterWidget::handleStoreFilterExpression()
226 {
227  if ( !mActionHandleStoreFilterExpression->isChecked() )
228  {
229  mLayer->storedExpressionManager()->removeStoredExpression( mActionHandleStoreFilterExpression->data().toString() );
230  }
231  else
232  {
233  mLayer->storedExpressionManager()->addStoredExpression( mFilterQuery->text(), mFilterQuery->text() );
234  }
235 
236  updateCurrentStoredFilterExpression();
237  storedFilterExpressionBoxInit();
238 }
239 
240 void QgsFeatureFilterWidget::storedFilterExpressionBoxInit()
241 {
242  const auto constActions = mStoredFilterExpressionMenu->actions();
243  for ( QAction *a : constActions )
244  {
245  mStoredFilterExpressionMenu->removeAction( a );
246  delete a;
247  }
248 
249  const QList< QgsStoredExpression > storedExpressions = mLayer->storedExpressionManager()->storedExpressions();
250  for ( const QgsStoredExpression &storedExpression : storedExpressions )
251  {
252  QAction *storedExpressionAction = new QAction( storedExpression.name, mFilterButton );
253  connect( storedExpressionAction, &QAction::triggered, this, [ = ]()
254  {
255  setFilterExpression( storedExpression.expression, QgsAttributeForm::ReplaceFilter, true );
256  } );
257  mStoredFilterExpressionMenu->addAction( storedExpressionAction );
258  }
259 }
260 
261 void QgsFeatureFilterWidget::storeExpressionButtonInit()
262 {
263  if ( mActionHandleStoreFilterExpression->isChecked() )
264  {
265  mActionHandleStoreFilterExpression->setToolTip( tr( "Delete stored expression" ) );
266  mActionHandleStoreFilterExpression->setText( tr( "Delete Stored Expression" ) );
267  mActionHandleStoreFilterExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionHandleStoreFilterExpressionChecked.svg" ) ) );
268  mStoreFilterExpressionButton->removeAction( mActionSaveAsStoredFilterExpression );
269  mStoreFilterExpressionButton->addAction( mActionEditStoredFilterExpression );
270  }
271  else
272  {
273  mActionHandleStoreFilterExpression->setToolTip( tr( "Save expression with the text as name" ) );
274  mActionHandleStoreFilterExpression->setText( tr( "Save Expression" ) );
275  mActionHandleStoreFilterExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionHandleStoreFilterExpressionUnchecked.svg" ) ) );
276  mStoreFilterExpressionButton->addAction( mActionSaveAsStoredFilterExpression );
277  mStoreFilterExpressionButton->removeAction( mActionEditStoredFilterExpression );
278  }
279 }
280 
281 
282 void QgsFeatureFilterWidget::filterColumnChanged( QAction *filterAction )
283 {
284  mFilterButton->setDefaultAction( filterAction );
285  mFilterButton->setPopupMode( QToolButton::InstantPopup );
286  // replace the search line edit with a search widget that is suited to the selected field
287  // delete previous widget
288  if ( mCurrentSearchWidgetWrapper )
289  {
290  mCurrentSearchWidgetWrapper->widget()->setVisible( false );
291  delete mCurrentSearchWidgetWrapper;
292  }
293  QString fieldName = mFilterButton->defaultAction()->data().toString();
294  // get the search widget
295  int fldIdx = mLayer->fields().lookupField( fieldName );
296  if ( fldIdx < 0 )
297  return;
298  const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldName );
299  mCurrentSearchWidgetWrapper = QgsGui::editorWidgetRegistry()->
300  createSearchWidget( setup.type(), mLayer, fldIdx, setup.config(), mFilterContainer, mEditorContext );
301  if ( mCurrentSearchWidgetWrapper->applyDirectly() )
302  {
303  connect( mCurrentSearchWidgetWrapper, &QgsSearchWidgetWrapper::expressionChanged, this, &QgsFeatureFilterWidget::filterQueryChanged );
304  mApplyFilterButton->setVisible( false );
305  mStoreFilterExpressionButton->setVisible( false );
306  }
307  else
308  {
309  connect( mCurrentSearchWidgetWrapper, &QgsSearchWidgetWrapper::expressionChanged, this, &QgsFeatureFilterWidget::filterQueryAccepted );
310  mApplyFilterButton->setVisible( true );
311  mStoreFilterExpressionButton->setVisible( true );
312  }
313 
314  replaceSearchWidget( mFilterQuery, mCurrentSearchWidgetWrapper->widget() );
315 }
316 
317 void QgsFeatureFilterWidget::filterExpressionBuilder()
318 {
319  // Show expression builder
321 
322  QgsExpressionBuilderDialog dlg( mLayer, mFilterQuery->text(), this, QStringLiteral( "generic" ), context );
323  dlg.setWindowTitle( tr( "Expression Based Filter" ) );
324 
325  QgsDistanceArea myDa;
326  myDa.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
327  myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
328  dlg.setGeomCalculator( myDa );
329 
330  if ( dlg.exec() == QDialog::Accepted )
331  {
332  setFilterExpression( dlg.expressionText(), QgsAttributeForm::ReplaceFilter, true );
333  }
334 }
335 
336 void QgsFeatureFilterWidget::saveAsStoredFilterExpression()
337 {
338  QgsDialog *dlg = new QgsDialog( this, Qt::WindowFlags(), QDialogButtonBox::Save | QDialogButtonBox::Cancel );
339  dlg->setWindowTitle( tr( "Save Expression As" ) );
340  QVBoxLayout *layout = dlg->layout();
341  dlg->resize( std::max( 400, this->width() / 2 ), dlg->height() );
342 
343  QLabel *nameLabel = new QLabel( tr( "Name" ), dlg );
344  QLineEdit *nameEdit = new QLineEdit( dlg );
345  layout->addWidget( nameLabel );
346  layout->addWidget( nameEdit );
347  nameEdit->setFocus();
348 
349  if ( dlg->exec() == QDialog::Accepted )
350  {
351  mLayer->storedExpressionManager()->addStoredExpression( nameEdit->text(), mFilterQuery->text() );
352 
353  updateCurrentStoredFilterExpression();
354  storedFilterExpressionBoxInit();
355  }
356 }
357 
358 void QgsFeatureFilterWidget::editStoredFilterExpression()
359 {
360  QgsDialog *dlg = new QgsDialog( this, Qt::WindowFlags(), QDialogButtonBox::Save | QDialogButtonBox::Cancel );
361  dlg->setWindowTitle( tr( "Edit expression" ) );
362  QVBoxLayout *layout = dlg->layout();
363  dlg->resize( std::max( 400, this->width() / 2 ), dlg->height() );
364 
365  QLabel *nameLabel = new QLabel( tr( "Name" ), dlg );
366  QLineEdit *nameEdit = new QLineEdit( mLayer->storedExpressionManager()->storedExpression( mActionHandleStoreFilterExpression->data().toString() ).name, dlg );
367  QLabel *expressionLabel = new QLabel( tr( "Expression" ), dlg );
368  QgsExpressionLineEdit *expressionEdit = new QgsExpressionLineEdit( dlg );
369  expressionEdit->setExpression( mLayer->storedExpressionManager()->storedExpression( mActionHandleStoreFilterExpression->data().toString() ).expression );
370 
371  layout->addWidget( nameLabel );
372  layout->addWidget( nameEdit );
373  layout->addWidget( expressionLabel );
374  layout->addWidget( expressionEdit );
375  nameEdit->setFocus();
376 
377  if ( dlg->exec() == QDialog::Accepted )
378  {
379  //update stored expression
380  mLayer->storedExpressionManager()->updateStoredExpression( mActionHandleStoreFilterExpression->data().toString(), nameEdit->text(), expressionEdit->expression(), QgsStoredExpression::Category::FilterExpression );
381 
382  //update text
383  mFilterQuery->setValue( expressionEdit->expression() );
384 
385  storedFilterExpressionBoxInit();
386  }
387 }
388 
389 void QgsFeatureFilterWidget::updateCurrentStoredFilterExpression()
390 {
391  QgsStoredExpression currentStoredExpression = mLayer->storedExpressionManager()->findStoredExpressionByExpression( mFilterQuery->value() );
392 
393  //set checked when it's an existing stored expression
394  mActionHandleStoreFilterExpression->setChecked( !currentStoredExpression.id.isNull() );
395 
396  mActionHandleStoreFilterExpression->setData( currentStoredExpression.id );
397  mActionEditStoredFilterExpression->setData( currentStoredExpression.id );
398 
399  //update bookmark button
400  storeExpressionButtonInit();
401 }
402 
403 void QgsFeatureFilterWidget::setFilterExpression( const QString &filterString, QgsAttributeForm::FilterType type, bool alwaysShowFilter )
404 {
405  QString filter;
406  if ( !mFilterQuery->text().isEmpty() && !filterString.isEmpty() )
407  {
408  switch ( type )
409  {
411  filter = filterString;
412  break;
413 
415  filter = QStringLiteral( "(%1) AND (%2)" ).arg( mFilterQuery->text(), filterString );
416  break;
417 
419  filter = QStringLiteral( "(%1) OR (%2)" ).arg( mFilterQuery->text(), filterString );
420  break;
421  }
422  }
423  else if ( !filterString.isEmpty() )
424  {
425  filter = filterString;
426  }
427  else
428  {
429  filterShowAll();
430  return;
431  }
432 
433  mFilterQuery->setText( filter );
434 
435  if ( alwaysShowFilter || !mCurrentSearchWidgetWrapper || !mCurrentSearchWidgetWrapper->applyDirectly() )
436  {
437 
438  mFilterButton->setDefaultAction( mActionAdvancedFilter );
439  mFilterButton->setPopupMode( QToolButton::MenuButtonPopup );
440  mFilterQuery->setVisible( true );
441  mApplyFilterButton->setVisible( true );
442  mStoreFilterExpressionButton->setVisible( true );
443  if ( mCurrentSearchWidgetWrapper )
444  {
445  // replace search widget widget with the normal filter query line edit
446  replaceSearchWidget( mCurrentSearchWidgetWrapper->widget(), mFilterQuery );
447  }
448  }
449 
450  // parse search string and build parsed tree
451  QgsExpression filterExpression( filter );
452  if ( filterExpression.hasParserError() )
453  {
454  mMessageBar->pushMessage( tr( "Parsing error" ), filterExpression.parserErrorString(), Qgis::Warning );
455  return;
456  }
457 
459 
460  if ( !filterExpression.prepare( &context ) )
461  {
462  mMessageBar->pushMessage( tr( "Evaluation error" ), filterExpression.evalErrorString(), Qgis::Warning );
463  }
464 
465  mMainView->filterFeatures( filterExpression, context );
466 
467  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowFilteredList );
468 }
469 
470 void QgsFeatureFilterWidget::replaceSearchWidget( QWidget *oldw, QWidget *neww )
471 {
472  mFilterLayout->removeWidget( oldw );
473  oldw->setVisible( false );
474  mFilterLayout->addWidget( neww, 0, 0 );
475  neww->setVisible( true );
476  neww->setFocus();
477 }
478 
479 void QgsFeatureFilterWidget::onFilterQueryTextChanged( const QString &value )
480 {
481  Q_UNUSED( value );
482  mFilterQueryTimer.start( 300 );
483 }
484 
@ Warning
Definition: qgis.h:91
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
This class contains context information for attribute editor widgets.
FilterType
Filter types.
@ ReplaceFilter
Filter should replace any existing filter.
@ FilterOr
Filter should be combined using "OR".
@ FilterAnd
Filter should be combined using "AND".
@ ShowFilteredList
Show only features whose ids are on the filter list. {.
@ ShowVisible
Show only visible features (depends on the map canvas)
@ ShowSelected
Show only selected features.
@ ShowEdited
Show only features which have unsaved changes.
A generic dialog with layout and button box.
Definition: qgsdialog.h:34
QVBoxLayout * layout()
Returns the central layout. Widgets added to it must have this dialog as parent.
Definition: qgsdialog.h:46
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
This widget is used to show the attributes of a set of features of a QgsVectorLayer.
Definition: qgsdualview.h:45
QgsEditorWidgetSetup findBest(const QgsVectorLayer *vl, const QString &fieldName) const
Find the best editor widget and its configuration for a given field.
Holder for the widget type and its configuration for a field.
QVariantMap config() const
A generic dialog for building expression strings.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
The QgsExpressionLineEdit widget includes a line edit for entering expressions together with a button...
QString expression() const
Returns the current expression shown in the widget.
void setExpression(const QString &expression)
Sets the current expression to show in the widget.
Class for parsing and evaluation of expressions (formerly called "search strings").
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
QString name
Definition: qgsfield.h:60
static QgsEditorWidgetRegistry * editorWidgetRegistry()
Returns the global editor widget registry, used for managing all known edit widget factories.
Definition: qgsgui.cpp:76
A bar for displaying non-blocking messages to the user.
Definition: qgsmessagebar.h:61
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:501
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:105
void expressionChanged(const QString &exp)
Emitted whenever the expression changes.
Represents a vector layer which manages a vector based data sets.
void attributeAdded(int idx)
Will be emitted, when a new attribute has been added to this vector layer.
void attributeDeleted(int idx)
Will be emitted, when an attribute has been deleted from this vector layer.
const QgsField & field
Definition: qgsfield.h:472
Stored expression containing name, content (expression text) and a category tag.
QString id
generated uuid used for identification