QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgsfeaturefiltermodel.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfeaturefiltermodel.cpp - QgsFeatureFilterModel
3 
4  ---------------------
5  begin : 10.3.2017
6  copyright : (C) 2017 by Matthias Kuhn
7  email : [email protected]
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 #include "qgsfeaturefiltermodel.h"
18 
19 #include "qgsvectorlayer.h"
20 #include "qgsconditionalstyle.h"
21 #include "qgssettings.h"
22 
24  : QAbstractItemModel( parent )
25 {
26  mReloadTimer.setInterval( 100 );
27  mReloadTimer.setSingleShot( true );
28  connect( &mReloadTimer, &QTimer::timeout, this, &QgsFeatureFilterModel::scheduledReload );
29  setExtraIdentifierValueUnguarded( QVariant() );
30 }
31 
33 {
34  if ( mGatherer )
35  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
36 }
37 
39 {
40  return mSourceLayer;
41 }
42 
44 {
45  if ( mSourceLayer == sourceLayer )
46  return;
47 
48  mSourceLayer = sourceLayer;
49  mExpressionContext = sourceLayer->createExpressionContext();
50  reload();
51  emit sourceLayerChanged();
52 }
53 
55 {
56  return mDisplayExpression.expression();
57 }
58 
60 {
61  if ( mDisplayExpression.expression() == displayExpression )
62  return;
63 
64  mDisplayExpression = QgsExpression( displayExpression );
65  reload();
67 }
68 
70 {
71  return mFilterValue;
72 }
73 
75 {
76  if ( mFilterValue == filterValue )
77  return;
78 
79  mFilterValue = filterValue;
80  reload();
81  emit filterValueChanged();
82 }
83 
85 {
86  return mFilterExpression;
87 }
88 
90 {
91  if ( mFilterExpression == filterExpression )
92  return;
93 
94  mFilterExpression = filterExpression;
95  reload();
97 }
98 
100 {
101  return mGatherer;
102 }
103 
104 QModelIndex QgsFeatureFilterModel::index( int row, int column, const QModelIndex &parent ) const
105 {
106  Q_UNUSED( parent )
107  return createIndex( row, column, nullptr );
108 }
109 
110 QModelIndex QgsFeatureFilterModel::parent( const QModelIndex &child ) const
111 {
112  Q_UNUSED( child )
113  return QModelIndex();
114 }
115 
116 int QgsFeatureFilterModel::rowCount( const QModelIndex &parent ) const
117 {
118  Q_UNUSED( parent );
119 
120  return mEntries.size();
121 }
122 
123 int QgsFeatureFilterModel::columnCount( const QModelIndex &parent ) const
124 {
125  Q_UNUSED( parent )
126  return 1;
127 }
128 
129 QVariant QgsFeatureFilterModel::data( const QModelIndex &index, int role ) const
130 {
131  if ( !index.isValid() )
132  return QVariant();
133 
134  switch ( role )
135  {
136  case Qt::DisplayRole:
137  case Qt::EditRole:
138  case ValueRole:
139  return mEntries.value( index.row() ).value;
140 
141  case IdentifierValueRole:
142  return mEntries.value( index.row() ).identifierValue;
143 
144  case Qt::BackgroundColorRole:
145  case Qt::TextColorRole:
146  case Qt::DecorationRole:
147  case Qt::FontRole:
148  {
149  if ( mEntries.value( index.row() ).identifierValue.isNull() )
150  {
151  // Representation for NULL value
152  if ( role == Qt::TextColorRole )
153  {
154  return QBrush( QColor( Qt::gray ) );
155  }
156  else if ( role == Qt::FontRole )
157  {
158  QFont font = QFont();
159  if ( index.row() == mExtraIdentifierValueIndex )
160  font.setBold( true );
161 
162  if ( mEntries.value( index.row() ).identifierValue.isNull() )
163  {
164  font.setItalic( true );
165  }
166  return font;
167  }
168  }
169  else
170  {
171  // Respect conditional style
172  const QgsConditionalStyle style = featureStyle( mEntries.value( index.row() ).feature );
173 
174  if ( style.isValid() )
175  {
176  if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
177  return style.backgroundColor();
178  if ( role == Qt::TextColorRole && style.validTextColor() )
179  return style.textColor();
180  if ( role == Qt::DecorationRole )
181  return style.icon();
182  if ( role == Qt::FontRole )
183  return style.font();
184  }
185  }
186  break;
187  }
188  }
189 
190  return QVariant();
191 }
192 
193 void QgsFeatureFilterModel::updateCompleter()
194 {
195  emit beginUpdate();
196  QVector<Entry> entries = mGatherer->entries();
197 
198  if ( mExtraIdentifierValueIndex == -1 )
199  setExtraIdentifierValueUnguarded( QVariant() );
200 
201  // Only reloading the current entry?
202  if ( mGatherer->data().toBool() )
203  {
204  if ( !entries.isEmpty() )
205  {
206  mEntries.replace( mExtraIdentifierValueIndex, entries.at( 0 ) );
207  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
208  mShouldReloadCurrentFeature = false;
209  setExtraValueDoesNotExist( false );
210  }
211  else
212  {
213  setExtraValueDoesNotExist( true );
214  }
215 
216  mShouldReloadCurrentFeature = false;
217 
218  if ( mFilterValue.isEmpty() )
219  reload();
220  }
221  else
222  {
223  // We got strings for a filter selection
224  std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
225 
226  if ( mAllowNull )
227  entries.prepend( Entry( QVariant( QVariant::Int ), QgsApplication::nullRepresentation(), QgsFeature() ) );
228 
229  const int newEntriesSize = entries.size();
230 
231  // Find the index of the extra entry in the new list
232  int currentEntryInNewList = -1;
233  if ( mExtraIdentifierValueIndex != -1 )
234  {
235  for ( int i = 0; i < newEntriesSize; ++i )
236  {
237  if ( entries.at( i ).identifierValue == mExtraIdentifierValue )
238  {
239  currentEntryInNewList = i;
240  mEntries.replace( mExtraIdentifierValueIndex, entries.at( i ) );
241  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
242  setExtraValueDoesNotExist( false );
243  break;
244  }
245  }
246  }
247  else
248  {
249  Q_ASSERT_X( false, "QgsFeatureFilterModel::updateCompleter", "No extra identifier value generated. Should not get here." );
250  }
251 
252  int firstRow = 0;
253 
254  // Move the extra entry to the first position
255  if ( mExtraIdentifierValueIndex != -1 )
256  {
257  if ( mExtraIdentifierValueIndex != 0 )
258  {
259  beginMoveRows( QModelIndex(), mExtraIdentifierValueIndex, mExtraIdentifierValueIndex, QModelIndex(), 0 );
260 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
261  Entry extraEntry = mEntries.takeAt( mExtraIdentifierValueIndex );
262  mEntries.prepend( extraEntry );
263 #else
264  mEntries.move( mExtraIdentifierValueIndex, 0 );
265 #endif
266  endMoveRows();
267  }
268  firstRow = 1;
269  }
270 
271  // Remove all entries (except for extra entry if existent)
272  beginRemoveRows( QModelIndex(), firstRow, mEntries.size() - firstRow );
273  mEntries.remove( firstRow, mEntries.size() - firstRow );
274  endRemoveRows();
275 
276  if ( currentEntryInNewList == -1 )
277  {
278  beginInsertRows( QModelIndex(), 1, entries.size() + 1 );
279  mEntries += entries;
280  endInsertRows();
281  setExtraIdentifierValueIndex( 0 );
282  }
283  else
284  {
285  if ( currentEntryInNewList != 0 )
286  {
287  beginInsertRows( QModelIndex(), 0, currentEntryInNewList - 1 );
288  mEntries = entries.mid( 0, currentEntryInNewList ) + mEntries;
289  endInsertRows();
290  }
291  else
292  {
293  mEntries.replace( 0, entries.at( 0 ) );
294  }
295 
296  emit dataChanged( index( currentEntryInNewList, 0, QModelIndex() ), index( currentEntryInNewList, 0, QModelIndex() ) );
297 
298  beginInsertRows( QModelIndex(), currentEntryInNewList + 1, newEntriesSize - currentEntryInNewList - 1 );
299  mEntries += entries.mid( currentEntryInNewList + 1 );
300  endInsertRows();
301  setExtraIdentifierValueIndex( currentEntryInNewList );
302  }
303 
304  emit filterJobCompleted();
305  }
306  emit endUpdate();
307 }
308 
309 void QgsFeatureFilterModel::gathererThreadFinished()
310 {
311  delete mGatherer;
312  mGatherer = nullptr;
313  emit isLoadingChanged();
314 }
315 
316 void QgsFeatureFilterModel::scheduledReload()
317 {
318  if ( !mSourceLayer )
319  return;
320 
321  bool wasLoading = false;
322 
323  if ( mGatherer )
324  {
325  // Send the gatherer thread to the graveyard:
326  // forget about it, tell it to stop and delete when finished
327  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
328  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
329  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
330  mGatherer->stop();
331  wasLoading = true;
332  }
333 
334  QgsFeatureRequest request;
335 
336  if ( mShouldReloadCurrentFeature )
337  {
338  request.setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mIdentifierField ), QgsExpression::quotedValue( mExtraIdentifierValue ) ) );
339  }
340  else
341  {
342  QString filterClause;
343 
344  if ( mFilterValue.isEmpty() && !mFilterExpression.isEmpty() )
345  filterClause = mFilterExpression;
346  else if ( mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
347  filterClause = QStringLiteral( "(%1) ILIKE '%%2%'" ).arg( mDisplayExpression, mFilterValue );
348  else if ( !mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
349  filterClause = QStringLiteral( "(%1) AND ((%2) ILIKE '%%3%')" ).arg( mFilterExpression, mDisplayExpression, mFilterValue );
350 
351  if ( !filterClause.isEmpty() )
352  request.setFilterExpression( filterClause );
353  }
354  QSet<QString> attributes;
355  if ( request.filterExpression() )
356  attributes = request.filterExpression()->referencedColumns();
357  attributes << mIdentifierField;
358  request.setSubsetOfAttributes( attributes, mSourceLayer->fields() );
360 
361  request.setLimit( QgsSettings().value( QStringLiteral( "maxEntriesRelationWidget" ), 100, QgsSettings::Gui ).toInt() );
362 
363  mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierField, request );
364  mGatherer->setData( mShouldReloadCurrentFeature );
365 
366  connect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
367  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
368 
369  mGatherer->start();
370  if ( !wasLoading )
371  emit isLoadingChanged();
372 }
373 
374 QSet<QString> QgsFeatureFilterModel::requestedAttributes() const
375 {
376  QSet<QString> requestedAttrs;
377 
378  const auto rowStyles = mSourceLayer->conditionalStyles()->rowStyles();
379 
380  for ( const QgsConditionalStyle &style : rowStyles )
381  {
382  QgsExpression exp( style.rule() );
383  requestedAttrs += exp.referencedColumns();
384  }
385 
386  if ( mDisplayExpression.isField() )
387  {
388  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
389  Q_FOREACH ( const QgsConditionalStyle &style, mSourceLayer->conditionalStyles()->fieldStyles( fieldName ) )
390  {
391  QgsExpression exp( style.rule() );
392  requestedAttrs += exp.referencedColumns();
393  }
394  }
395 
396  return requestedAttrs;
397 }
398 
399 void QgsFeatureFilterModel::setExtraIdentifierValueIndex( int index, bool force )
400 {
401  if ( mExtraIdentifierValueIndex == index && !force )
402  return;
403 
404  mExtraIdentifierValueIndex = index;
406 }
407 
408 void QgsFeatureFilterModel::reloadCurrentFeature()
409 {
410  mShouldReloadCurrentFeature = true;
411  mReloadTimer.start();
412 }
413 
414 void QgsFeatureFilterModel::setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue )
415 {
416  const QVector<Entry> entries = mEntries;
417 
418  int index = 0;
419  for ( const Entry &entry : entries )
420  {
421  if ( entry.identifierValue == extraIdentifierValue
422  && entry.identifierValue.isNull() == extraIdentifierValue.isNull()
423  && entry.identifierValue.isValid() == extraIdentifierValue.isValid() )
424  {
425  setExtraIdentifierValueIndex( index );
426  break;
427  }
428 
429  index++;
430  }
431 
432  // Value not found in current entries
433  if ( mExtraIdentifierValueIndex != index )
434  {
435  beginInsertRows( QModelIndex(), 0, 0 );
436  if ( extraIdentifierValue.isNull() )
437  mEntries.prepend( Entry( QVariant( QVariant::Int ), QgsApplication::nullRepresentation( ), QgsFeature() ) );
438  else
439  mEntries.prepend( Entry( extraIdentifierValue, QStringLiteral( "(%1)" ).arg( extraIdentifierValue.toString() ), QgsFeature() ) );
440  endInsertRows();
441 
442  setExtraIdentifierValueIndex( 0, true );
443 
444  reloadCurrentFeature();
445  }
446 }
447 
448 QgsConditionalStyle QgsFeatureFilterModel::featureStyle( const QgsFeature &feature ) const
449 {
450  if ( !mSourceLayer )
451  return QgsConditionalStyle();
452 
453  QgsVectorLayer *layer = mSourceLayer;
454  QgsFeatureId fid = feature.id();
455  mExpressionContext.setFeature( feature );
456 
457  auto styles = QgsConditionalStyle::matchingConditionalStyles( layer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
458 
459  if ( mDisplayExpression.referencedColumns().count() == 1 )
460  {
461  // Style specific for this field
462  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
463  const auto allStyles = layer->conditionalStyles()->fieldStyles( fieldName );
464  const auto matchingFieldStyles = QgsConditionalStyle::matchingConditionalStyles( allStyles, feature.attribute( fieldName ), mExpressionContext );
465 
466  styles += matchingFieldStyles;
467  }
468 
469  QgsConditionalStyle style;
470  style = QgsConditionalStyle::compressStyles( styles );
471  mEntryStylesMap.insert( fid, style );
472 
473  return style;
474 }
475 
477 {
478  return mAllowNull;
479 }
480 
482 {
483  if ( mAllowNull == allowNull )
484  return;
485 
486  mAllowNull = allowNull;
487  emit allowNullChanged();
488 
489  reload();
490 }
491 
493 {
494  return mExtraValueDoesNotExist;
495 }
496 
497 void QgsFeatureFilterModel::setExtraValueDoesNotExist( bool extraValueDoesNotExist )
498 {
499  if ( mExtraValueDoesNotExist == extraValueDoesNotExist )
500  return;
501 
502  mExtraValueDoesNotExist = extraValueDoesNotExist;
504 }
505 
507 {
508  return mExtraIdentifierValueIndex;
509 }
510 
512 {
513  return mIdentifierField;
514 }
515 
517 {
518  if ( mIdentifierField == identifierField )
519  return;
520 
521  mIdentifierField = identifierField;
522  emit identifierFieldChanged();
523 }
524 
525 void QgsFeatureFilterModel::reload()
526 {
527  mReloadTimer.start();
528 }
529 
531 {
532  return mExtraIdentifierValue;
533 }
534 
536 {
537  if ( qgsVariantEqual( extraIdentifierValue, mExtraIdentifierValue ) && mExtraIdentifierValue.isValid() )
538  return;
539 
540  if ( mIsSettingExtraIdentifierValue )
541  return;
542 
543  mIsSettingExtraIdentifierValue = true;
544 
545  mExtraIdentifierValue = extraIdentifierValue;
546 
547  setExtraIdentifierValueUnguarded( extraIdentifierValue );
548 
549  mIsSettingExtraIdentifierValue = false;
550 
552 }
Class for parsing and evaluation of expressions (formerly called "search strings").
void filterValueChanged()
This value will be used to filter the features available from this model.
QgsFeatureId id
Definition: qgsfeature.h:64
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
bool validTextColor() const
Check if the text color is valid for render.
void beginUpdate()
Notification that the model is about to be changed because a job was completed.
int extraIdentifierValueIndex() const
The index at which the extra identifier value is available within the model.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QColor textColor() const
The text color set for style.
bool validBackgroundColor() const
Check if the background color is valid for render.
int rowCount(const QModelIndex &parent) const override
Used to retrieve the displayExpression of a feature.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QgsVectorLayer * sourceLayer() const
The source layer from which features will be fetched.
QFont font() const
The font for the style.
void setFilterExpression(const QString &filterExpression)
An additional filter expression to apply, next to the filterValue.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
QList< QgsConditionalStyle > fieldStyles(const QString &fieldName)
Returns the conditional styles set for the field UI properties.
QString filterExpression() const
An additional filter expression to apply, next to the filterValue.
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsConditionalLayerStyles * conditionalStyles() const
Returns the conditional styles that are set for this layer.
void filterJobCompleted()
Indicates that a filter job has been completed and new data may be available.
void extraIdentifierValueIndexChanged(int index)
The index at which the extra identifier value is available within the model.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool allowNull() const
Add a NULL entry to the list.
friend class QgsFieldExpressionValuesGatherer
QList< QgsConditionalStyle > rowStyles()
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
void identifierFieldChanged()
The identifier field should be a unique field that can be used to identify individual features...
QPixmap icon() const
The icon set for style generated from the set symbol.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Conditional styling for a rule.
QString displayExpression() const
The display expression will be used for.
static QList< QgsConditionalStyle > matchingConditionalStyles(const QList< QgsConditionalStyle > &styles, const QVariant &value, QgsExpressionContext &context)
Find and return the matching styles for the value and feature.
void setFilterValue(const QString &filterValue)
This value will be used to filter the features available from this model.
QModelIndex index(int row, int column, const QModelIndex &parent) const override
This class wraps a request for features to a vector layer (or directly its vector data provider)...
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
QColor backgroundColor() const
The background color for style.
bool isField() const
Checks whether an expression consists only of a single field reference.
static QgsConditionalStyle compressStyles(const QList< QgsConditionalStyle > &styles)
Compress a list of styles into a single style.
void isLoadingChanged()
Indicator if the model is currently performing any feature iteration in the background.
QModelIndex parent(const QModelIndex &child) const override
void allowNullChanged()
Add a NULL entry to the list.
QgsFeatureFilterModel(QObject *parent=nullptr)
Create a new QgsFeatureFilterModel, optionally specifying a parent.
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QVariant extraIdentifierValue() const
Allows specifying one value that does not need to match the filter criteria but will still be availab...
void filterExpressionChanged()
An additional filter expression to apply, next to the filterValue.
void endUpdate()
Notification that the model change is finished.
int columnCount(const QModelIndex &parent) const override
void extraIdentifierValueChanged()
Allows specifying one value that does not need to match the filter criteria but will still be availab...
void setIdentifierField(const QString &identifierField)
The identifier field should be a unique field that can be used to identify individual features...
void setSourceLayer(QgsVectorLayer *sourceLayer)
The source layer from which features will be fetched.
void sourceLayerChanged()
The source layer from which features will be fetched.
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
QString filterValue() const
This value will be used to filter the features available from this model.
bool isLoading() const
Indicator if the model is currently performing any feature iteration in the background.
void setExtraIdentifierValue(const QVariant &extraIdentifierValue)
Allows specifying one value that does not need to match the filter criteria but will still be availab...
QString identifierField() const
The identifier field should be a unique field that can be used to identify individual features...
QgsFeatureRequest & setLimit(long limit)
Set the maximum number of features to request.
QVariant data(const QModelIndex &index, int role) const override
QString expression() const
Returns the original, unmodified expression string.
void setAllowNull(bool allowNull)
Add a NULL entry to the list.
QString rule() const
The condition rule set for the style.
bool isValid() const
isValid Check if this rule is valid.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required...
bool qgsVariantEqual(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether they are equal, NULL values are treated as equal...
Definition: qgis.cpp:310
Geometry is not required. It may still be returned if e.g. required for a filter condition.
bool extraValueDoesNotExist() const
Flag indicating that the extraIdentifierValue does not exist in the data.
Used to retrieve the identifierValue (primary key) of a feature.
void setDisplayExpression(const QString &displayExpression)
The display expression will be used for.
Represents a vector layer which manages a vector based data sets.
void extraValueDoesNotExistChanged()
Flag indicating that the extraIdentifierValue does not exist in the data.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
void displayExpressionChanged()
The display expression will be used for.
QgsExpression * filterExpression() const
Returns the filter expression if set.