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