QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
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  mActionShowAllFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTable.svg" ) );
56  mActionAdvancedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionFilter2.svg" ) );
57  mActionSelectedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableSelected.svg" ) );
58  mActionVisibleFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableVisible.svg" ) );
59  mActionEditedFilter->setIcon( QgsApplication::getThemeIcon( "/mActionOpenTableEdited.svg" ) );
60 
61 
62  // Set button to store or delete stored filter expressions
63  mStoreFilterExpressionButton->setDefaultAction( mActionHandleStoreFilterExpression );
64  connect( mActionSaveAsStoredFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::saveAsStoredFilterExpression );
65  connect( mActionEditStoredFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::editStoredFilterExpression );
66  connect( mActionHandleStoreFilterExpression, &QAction::triggered, this, &QgsFeatureFilterWidget::handleStoreFilterExpression );
67  mApplyFilterButton->setDefaultAction( mActionApplyFilter );
68 
69  // Connect filter signals
70  connect( mActionAdvancedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterExpressionBuilder );
71  connect( mActionShowAllFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterShowAll );
72  connect( mActionSelectedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterSelected );
73  connect( mActionVisibleFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterVisible );
74  connect( mActionEditedFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterEdited );
75  connect( mFilterQuery, &QLineEdit::returnPressed, this, &QgsFeatureFilterWidget::filterQueryAccepted );
76  connect( mActionApplyFilter, &QAction::triggered, this, &QgsFeatureFilterWidget::filterQueryAccepted );
77  connect( mFilterQuery, &QLineEdit::textChanged, this, &QgsFeatureFilterWidget::onFilterQueryTextChanged );
78 }
79 
80 void QgsFeatureFilterWidget::init( QgsVectorLayer *layer, const QgsAttributeEditorContext &context, QgsDualView *mainView,
81  QgsMessageBar *messageBar, int )
82 {
83  mMainView = mainView;
84  mLayer = layer;
85  mEditorContext = context;
86  mMessageBar = messageBar;
87 
88  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsFeatureFilterWidget::columnBoxInit );
89  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsFeatureFilterWidget::columnBoxInit );
90 
91  //set delay on entering text
92  mFilterQueryTimer.setSingleShot( true );
93  connect( &mFilterQueryTimer, &QTimer::timeout, this, &QgsFeatureFilterWidget::updateCurrentStoredFilterExpression );
94 
95  columnBoxInit();
96  storedFilterExpressionBoxInit();
97  storeExpressionButtonInit();
98 }
99 
100 void QgsFeatureFilterWidget::filterShowAll()
101 {
102  mFilterButton->setDefaultAction( mActionShowAllFilter );
103  mFilterButton->setPopupMode( QToolButton::InstantPopup );
104  mFilterQuery->setVisible( false );
105  mFilterQuery->setText( QString() );
106  if ( mCurrentSearchWidgetWrapper )
107  {
108  mCurrentSearchWidgetWrapper->widget()->setVisible( false );
109  }
110  mApplyFilterButton->setVisible( false );
111  mStoreFilterExpressionButton->setVisible( false );
112  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowAll );
113 }
114 
115 void QgsFeatureFilterWidget::filterSelected()
116 {
117  mFilterButton->setDefaultAction( mActionSelectedFilter );
118  mFilterButton->setPopupMode( QToolButton::InstantPopup );
119  mFilterQuery->setVisible( false );
120  mApplyFilterButton->setVisible( false );
121  mStoreFilterExpressionButton->setVisible( false );
122  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowSelected );
123 }
124 
125 void QgsFeatureFilterWidget::filterVisible()
126 {
127  if ( !mLayer->isSpatial() )
128  {
129  filterShowAll();
130  return;
131  }
132 
133  mFilterButton->setDefaultAction( mActionVisibleFilter );
134  mFilterButton->setPopupMode( QToolButton::InstantPopup );
135  mFilterQuery->setVisible( false );
136  mApplyFilterButton->setVisible( false );
137  mStoreFilterExpressionButton->setVisible( false );
138  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowVisible );
139 }
140 
141 void QgsFeatureFilterWidget::filterEdited()
142 {
143  mFilterButton->setDefaultAction( mActionEditedFilter );
144  mFilterButton->setPopupMode( QToolButton::InstantPopup );
145  mFilterQuery->setVisible( false );
146  mApplyFilterButton->setVisible( false );
147  mStoreFilterExpressionButton->setVisible( false );
148  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowEdited );
149 }
150 
151 
152 void QgsFeatureFilterWidget::filterQueryAccepted()
153 {
154  if ( ( mFilterQuery->isVisible() && mFilterQuery->text().isEmpty() ) ||
155  ( mCurrentSearchWidgetWrapper && mCurrentSearchWidgetWrapper->widget()->isVisible()
156  && mCurrentSearchWidgetWrapper->expression().isEmpty() ) )
157  {
158  filterShowAll();
159  return;
160  }
161  filterQueryChanged( mFilterQuery->text() );
162 }
163 
164 void QgsFeatureFilterWidget::filterQueryChanged( const QString &query )
165 {
166  QString str;
167  if ( mFilterButton->defaultAction() == mActionAdvancedFilter )
168  {
169  str = query;
170  }
171  else if ( mCurrentSearchWidgetWrapper )
172  {
173  str = mCurrentSearchWidgetWrapper->expression();
174  }
175 
176  setFilterExpression( str );
177 }
178 
179 void QgsFeatureFilterWidget::columnBoxInit()
180 {
181  const auto constActions = mFilterColumnsMenu->actions();
182  for ( QAction *a : constActions )
183  {
184  mFilterColumnsMenu->removeAction( a );
185  mFilterButton->removeAction( a );
186  delete a;
187  }
188 
189  mFilterButton->addAction( mActionShowAllFilter );
190  mFilterButton->addAction( mActionSelectedFilter );
191  if ( mLayer->isSpatial() )
192  {
193  mFilterButton->addAction( mActionVisibleFilter );
194  }
195  mFilterButton->addAction( mActionEditedFilter );
196  mFilterButton->addAction( mActionFilterColumnsMenu );
197  mFilterButton->addAction( mActionAdvancedFilter );
198  mFilterButton->addAction( mActionStoredFilterExpressions );
199 
200  const QList<QgsField> fields = mLayer->fields().toList();
201 
202  const auto constFields = fields;
203  for ( const QgsField &field : constFields )
204  {
205  const int idx = mLayer->fields().lookupField( field.name() );
206  if ( idx < 0 )
207  continue;
208 
209  if ( QgsGui::editorWidgetRegistry()->findBest( mLayer, field.name() ).type() != QLatin1String( "Hidden" ) )
210  {
211  const QIcon icon = mLayer->fields().iconForField( idx );
212  const QString alias = mLayer->attributeDisplayName( idx );
213 
214  // Generate action for the filter popup button
215  QAction *filterAction = new QAction( icon, alias, mFilterButton );
216  filterAction->setData( field.name() );
217 
218  connect( filterAction, &QAction::triggered, this, [ = ] { filterColumnChanged( filterAction ); } );
219  mFilterColumnsMenu->addAction( filterAction );
220  }
221  }
222 }
223 
224 void QgsFeatureFilterWidget::handleStoreFilterExpression()
225 {
226  if ( !mActionHandleStoreFilterExpression->isChecked() )
227  {
228  mLayer->storedExpressionManager()->removeStoredExpression( mActionHandleStoreFilterExpression->data().toString() );
229  }
230  else
231  {
232  mLayer->storedExpressionManager()->addStoredExpression( mFilterQuery->text(), mFilterQuery->text() );
233  }
234 
235  updateCurrentStoredFilterExpression();
236  storedFilterExpressionBoxInit();
237 }
238 
239 void QgsFeatureFilterWidget::storedFilterExpressionBoxInit()
240 {
241  const auto constActions = mStoredFilterExpressionMenu->actions();
242  for ( QAction *a : constActions )
243  {
244  mStoredFilterExpressionMenu->removeAction( a );
245  delete a;
246  }
247 
248  const QList< QgsStoredExpression > storedExpressions = mLayer->storedExpressionManager()->storedExpressions();
249  for ( const QgsStoredExpression &storedExpression : storedExpressions )
250  {
251  QAction *storedExpressionAction = new QAction( storedExpression.name, mFilterButton );
252  connect( storedExpressionAction, &QAction::triggered, this, [ = ]()
253  {
254  setFilterExpression( storedExpression.expression, QgsAttributeForm::ReplaceFilter, true );
255  } );
256  mStoredFilterExpressionMenu->addAction( storedExpressionAction );
257  }
258 }
259 
260 void QgsFeatureFilterWidget::storeExpressionButtonInit()
261 {
262  if ( mActionHandleStoreFilterExpression->isChecked() )
263  {
264  mActionHandleStoreFilterExpression->setToolTip( tr( "Delete stored expression" ) );
265  mActionHandleStoreFilterExpression->setText( tr( "Delete Stored Expression" ) );
266  mActionHandleStoreFilterExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionHandleStoreFilterExpressionChecked.svg" ) ) );
267  mStoreFilterExpressionButton->removeAction( mActionSaveAsStoredFilterExpression );
268  mStoreFilterExpressionButton->addAction( mActionEditStoredFilterExpression );
269  }
270  else
271  {
272  mActionHandleStoreFilterExpression->setToolTip( tr( "Save expression with the text as name" ) );
273  mActionHandleStoreFilterExpression->setText( tr( "Save Expression" ) );
274  mActionHandleStoreFilterExpression->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mActionHandleStoreFilterExpressionUnchecked.svg" ) ) );
275  mStoreFilterExpressionButton->addAction( mActionSaveAsStoredFilterExpression );
276  mStoreFilterExpressionButton->removeAction( mActionEditStoredFilterExpression );
277  }
278 }
279 
280 
281 void QgsFeatureFilterWidget::filterColumnChanged( QAction *filterAction )
282 {
283  mFilterButton->setDefaultAction( filterAction );
284  mFilterButton->setPopupMode( QToolButton::InstantPopup );
285  // replace the search line edit with a search widget that is suited to the selected field
286  // delete previous widget
287  if ( mCurrentSearchWidgetWrapper )
288  {
289  mCurrentSearchWidgetWrapper->widget()->setVisible( false );
290  delete mCurrentSearchWidgetWrapper;
291  }
292  const QString fieldName = mFilterButton->defaultAction()->data().toString();
293  // get the search widget
294  const int fldIdx = mLayer->fields().lookupField( fieldName );
295  if ( fldIdx < 0 )
296  return;
297  const QgsEditorWidgetSetup setup = QgsGui::editorWidgetRegistry()->findBest( mLayer, fieldName );
298  mCurrentSearchWidgetWrapper = QgsGui::editorWidgetRegistry()->
299  createSearchWidget( setup.type(), mLayer, fldIdx, setup.config(), mFilterContainer, mEditorContext );
300  if ( mCurrentSearchWidgetWrapper->applyDirectly() )
301  {
302  connect( mCurrentSearchWidgetWrapper, &QgsSearchWidgetWrapper::expressionChanged, this, &QgsFeatureFilterWidget::filterQueryChanged );
303  mApplyFilterButton->setVisible( false );
304  mStoreFilterExpressionButton->setVisible( false );
305  }
306  else
307  {
308  connect( mCurrentSearchWidgetWrapper, &QgsSearchWidgetWrapper::expressionChanged, this, &QgsFeatureFilterWidget::filterQueryAccepted );
309  mApplyFilterButton->setVisible( true );
310  mStoreFilterExpressionButton->setVisible( true );
311  }
312 
313  replaceSearchWidget( mFilterQuery, mCurrentSearchWidgetWrapper->widget() );
314 }
315 
316 void QgsFeatureFilterWidget::filterExpressionBuilder()
317 {
318  // Show expression builder
320 
321  QgsExpressionBuilderDialog dlg( mLayer, mFilterQuery->text(), this, QStringLiteral( "generic" ), context );
322  dlg.setWindowTitle( tr( "Expression Based Filter" ) );
323 
324  QgsDistanceArea myDa;
325  myDa.setSourceCrs( mLayer->crs(), QgsProject::instance()->transformContext() );
326  myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
327  dlg.setGeomCalculator( myDa );
328 
329  if ( dlg.exec() == QDialog::Accepted )
330  {
331  setFilterExpression( dlg.expressionText(), QgsAttributeForm::ReplaceFilter, true );
332  }
333 }
334 
335 void QgsFeatureFilterWidget::saveAsStoredFilterExpression()
336 {
337  QgsDialog *dlg = new QgsDialog( this, Qt::WindowFlags(), QDialogButtonBox::Save | QDialogButtonBox::Cancel );
338  dlg->setWindowTitle( tr( "Save Expression As" ) );
339  QVBoxLayout *layout = dlg->layout();
340  dlg->resize( std::max( 400, this->width() / 2 ), dlg->height() );
341 
342  QLabel *nameLabel = new QLabel( tr( "Name" ), dlg );
343  QLineEdit *nameEdit = new QLineEdit( dlg );
344  layout->addWidget( nameLabel );
345  layout->addWidget( nameEdit );
346  nameEdit->setFocus();
347 
348  if ( dlg->exec() == QDialog::Accepted )
349  {
350  mLayer->storedExpressionManager()->addStoredExpression( nameEdit->text(), mFilterQuery->text() );
351 
352  updateCurrentStoredFilterExpression();
353  storedFilterExpressionBoxInit();
354  }
355 }
356 
357 void QgsFeatureFilterWidget::editStoredFilterExpression()
358 {
359  QgsDialog *dlg = new QgsDialog( this, Qt::WindowFlags(), QDialogButtonBox::Save | QDialogButtonBox::Cancel );
360  dlg->setWindowTitle( tr( "Edit expression" ) );
361  QVBoxLayout *layout = dlg->layout();
362  dlg->resize( std::max( 400, this->width() / 2 ), dlg->height() );
363 
364  QLabel *nameLabel = new QLabel( tr( "Name" ), dlg );
365  QLineEdit *nameEdit = new QLineEdit( mLayer->storedExpressionManager()->storedExpression( mActionHandleStoreFilterExpression->data().toString() ).name, dlg );
366  QLabel *expressionLabel = new QLabel( tr( "Expression" ), dlg );
367  QgsExpressionLineEdit *expressionEdit = new QgsExpressionLineEdit( dlg );
368  expressionEdit->setExpression( mLayer->storedExpressionManager()->storedExpression( mActionHandleStoreFilterExpression->data().toString() ).expression );
369 
370  layout->addWidget( nameLabel );
371  layout->addWidget( nameEdit );
372  layout->addWidget( expressionLabel );
373  layout->addWidget( expressionEdit );
374  nameEdit->setFocus();
375 
376  if ( dlg->exec() == QDialog::Accepted )
377  {
378  //update stored expression
379  mLayer->storedExpressionManager()->updateStoredExpression( mActionHandleStoreFilterExpression->data().toString(), nameEdit->text(), expressionEdit->expression(), QgsStoredExpression::Category::FilterExpression );
380 
381  //update text
382  mFilterQuery->setValue( expressionEdit->expression() );
383 
384  storedFilterExpressionBoxInit();
385  }
386 }
387 
388 void QgsFeatureFilterWidget::updateCurrentStoredFilterExpression()
389 {
390  const QgsStoredExpression currentStoredExpression = mLayer->storedExpressionManager()->findStoredExpressionByExpression( mFilterQuery->value() );
391 
392  //set checked when it's an existing stored expression
393  mActionHandleStoreFilterExpression->setChecked( !currentStoredExpression.id.isNull() );
394 
395  mActionHandleStoreFilterExpression->setData( currentStoredExpression.id );
396  mActionEditStoredFilterExpression->setData( currentStoredExpression.id );
397 
398  //update bookmark button
399  storeExpressionButtonInit();
400 }
401 
402 void QgsFeatureFilterWidget::setFilterExpression( const QString &filterString, QgsAttributeForm::FilterType type, bool alwaysShowFilter )
403 {
404  QString filter;
405  if ( !mFilterQuery->text().isEmpty() && !filterString.isEmpty() )
406  {
407  switch ( type )
408  {
410  filter = filterString;
411  break;
412 
414  filter = QStringLiteral( "(%1) AND (%2)" ).arg( mFilterQuery->text(), filterString );
415  break;
416 
418  filter = QStringLiteral( "(%1) OR (%2)" ).arg( mFilterQuery->text(), filterString );
419  break;
420  }
421  }
422  else if ( !filterString.isEmpty() )
423  {
424  filter = filterString;
425  }
426  else
427  {
428  filterShowAll();
429  return;
430  }
431 
432  mFilterQuery->setText( filter );
433 
434  if ( alwaysShowFilter || !mCurrentSearchWidgetWrapper || !mCurrentSearchWidgetWrapper->applyDirectly() )
435  {
436 
437  mFilterButton->setDefaultAction( mActionAdvancedFilter );
438  mFilterButton->setPopupMode( QToolButton::MenuButtonPopup );
439  mFilterQuery->setVisible( true );
440  mApplyFilterButton->setVisible( true );
441  mStoreFilterExpressionButton->setVisible( true );
442  if ( mCurrentSearchWidgetWrapper )
443  {
444  // replace search widget widget with the normal filter query line edit
445  replaceSearchWidget( mCurrentSearchWidgetWrapper->widget(), mFilterQuery );
446  }
447  }
448 
449  // parse search string and build parsed tree
450  QgsExpression filterExpression( filter );
451  if ( filterExpression.hasParserError() )
452  {
453  mMessageBar->pushMessage( tr( "Parsing error" ), filterExpression.parserErrorString(), Qgis::MessageLevel::Warning );
454  return;
455  }
456 
458 
459  if ( !filterExpression.prepare( &context ) )
460  {
461  mMessageBar->pushMessage( tr( "Evaluation error" ), filterExpression.evalErrorString(), Qgis::MessageLevel::Warning );
462  }
463 
464  mMainView->filterFeatures( filterExpression, context );
465 
466  mMainView->setFilterMode( QgsAttributeTableFilterModel::ShowFilteredList );
467 }
468 
469 void QgsFeatureFilterWidget::replaceSearchWidget( QWidget *oldw, QWidget *neww )
470 {
471  mFilterLayout->removeWidget( oldw );
472  oldw->setVisible( false );
473  mFilterLayout->addWidget( neww, 0, 0 );
474  neww->setVisible( true );
475  neww->setFocus();
476 }
477 
478 void QgsFeatureFilterWidget::onFilterQueryTextChanged( const QString &value )
479 {
480  Q_UNUSED( value );
481  mFilterQueryTimer.start( 300 );
482 }
483 
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
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:83
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:467
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:107
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.
#define str(x)
Definition: qgis.cpp:37
const QgsField & field
Definition: qgsfield.h:463
Stored expression containing name, content (expression text) and a category tag.
QString id
generated uuid used for identification