QGIS API Documentation  3.0.2-Girona (307d082)
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 "qgsconditionalstyle.h"
20 
22  : QAbstractItemModel( parent )
23 {
24  mReloadTimer.setInterval( 100 );
25  mReloadTimer.setSingleShot( true );
26  connect( &mReloadTimer, &QTimer::timeout, this, &QgsFeatureFilterModel::scheduledReload );
27  setExtraIdentifierValueUnguarded( QVariant() );
28 }
29 
31 {
32  if ( mGatherer )
33  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
34 }
35 
37 {
38  return mSourceLayer;
39 }
40 
42 {
43  if ( mSourceLayer == sourceLayer )
44  return;
45 
46  mSourceLayer = sourceLayer;
47  mExpressionContext = sourceLayer->createExpressionContext();
48  reload();
49  emit sourceLayerChanged();
50 }
51 
53 {
54  return mDisplayExpression.expression();
55 }
56 
58 {
59  if ( mDisplayExpression.expression() == displayExpression )
60  return;
61 
62  mDisplayExpression = QgsExpression( displayExpression );
63  reload();
65 }
66 
68 {
69  return mFilterValue;
70 }
71 
73 {
74  if ( mFilterValue == filterValue )
75  return;
76 
77  mFilterValue = filterValue;
78  reload();
79  emit filterValueChanged();
80 }
81 
83 {
84  return mFilterExpression;
85 }
86 
88 {
89  if ( mFilterExpression == filterExpression )
90  return;
91 
92  mFilterExpression = filterExpression;
93  reload();
95 }
96 
98 {
99  return mGatherer;
100 }
101 
102 QModelIndex QgsFeatureFilterModel::index( int row, int column, const QModelIndex &parent ) const
103 {
104  Q_UNUSED( parent )
105  return createIndex( row, column, nullptr );
106 }
107 
108 QModelIndex QgsFeatureFilterModel::parent( const QModelIndex &child ) const
109 {
110  Q_UNUSED( child )
111  return QModelIndex();
112 }
113 
114 int QgsFeatureFilterModel::rowCount( const QModelIndex &parent ) const
115 {
116  Q_UNUSED( parent );
117 
118  return mEntries.size();
119 }
120 
121 int QgsFeatureFilterModel::columnCount( const QModelIndex &parent ) const
122 {
123  Q_UNUSED( parent )
124  return 1;
125 }
126 
127 QVariant QgsFeatureFilterModel::data( const QModelIndex &index, int role ) const
128 {
129  if ( !index.isValid() )
130  return QVariant();
131 
132  switch ( role )
133  {
134  case Qt::DisplayRole:
135  case Qt::EditRole:
136  case ValueRole:
137  return mEntries.value( index.row() ).value;
138 
139  case IdentifierValueRole:
140  return mEntries.value( index.row() ).identifierValue;
141 
142  case Qt::BackgroundColorRole:
143  case Qt::TextColorRole:
144  case Qt::DecorationRole:
145  case Qt::FontRole:
146  {
147  if ( mEntries.value( index.row() ).identifierValue.isNull() )
148  {
149  // Representation for NULL value
150  if ( role == Qt::TextColorRole )
151  {
152  return QBrush( QColor( Qt::gray ) );
153  }
154  else if ( role == Qt::FontRole )
155  {
156  QFont font = QFont();
157  if ( index.row() == mExtraIdentifierValueIndex )
158  font.setBold( true );
159 
160  if ( mEntries.value( index.row() ).identifierValue.isNull() )
161  {
162  font.setItalic( true );
163  }
164  return font;
165  }
166  }
167  else
168  {
169  // Respect conditional style
170  const QgsConditionalStyle style = featureStyle( mEntries.value( index.row() ).feature );
171 
172  if ( style.isValid() )
173  {
174  if ( role == Qt::BackgroundColorRole && style.validBackgroundColor() )
175  return style.backgroundColor();
176  if ( role == Qt::TextColorRole && style.validTextColor() )
177  return style.textColor();
178  if ( role == Qt::DecorationRole )
179  return style.icon();
180  if ( role == Qt::FontRole )
181  return style.font();
182  }
183  }
184  break;
185  }
186  }
187 
188  return QVariant();
189 }
190 
191 void QgsFeatureFilterModel::updateCompleter()
192 {
193  emit beginUpdate();
194  QVector<Entry> entries = mGatherer->entries();
195 
196  if ( mExtraIdentifierValueIndex == -1 )
197  setExtraIdentifierValueUnguarded( QVariant() );
198 
199  // Only reloading the current entry?
200  if ( mGatherer->data().toBool() )
201  {
202  if ( !entries.isEmpty() )
203  {
204  mEntries.replace( mExtraIdentifierValueIndex, entries.at( 0 ) );
205  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
206  mShouldReloadCurrentFeature = false;
207  setExtraValueDoesNotExist( false );
208  }
209  else
210  {
211  setExtraValueDoesNotExist( true );
212  }
213 
214  mShouldReloadCurrentFeature = false;
215 
216  if ( mFilterValue.isEmpty() )
217  reload();
218  }
219  else
220  {
221  // We got strings for a filter selection
222  std::sort( entries.begin(), entries.end(), []( const Entry & a, const Entry & b ) { return a.value.localeAwareCompare( b.value ) < 0; } );
223 
224  if ( mAllowNull )
225  entries.prepend( Entry( QVariant( QVariant::Int ), tr( "NULL" ), QgsFeature() ) );
226 
227  const int newEntriesSize = entries.size();
228 
229  // Find the index of the extra entry in the new list
230  int currentEntryInNewList = -1;
231  if ( mExtraIdentifierValueIndex != -1 )
232  {
233  for ( int i = 0; i < newEntriesSize; ++i )
234  {
235  if ( entries.at( i ).identifierValue == mExtraIdentifierValue )
236  {
237  currentEntryInNewList = i;
238  mEntries.replace( mExtraIdentifierValueIndex, entries.at( i ) );
239  emit dataChanged( index( mExtraIdentifierValueIndex, 0, QModelIndex() ), index( mExtraIdentifierValueIndex, 0, QModelIndex() ) );
240  setExtraValueDoesNotExist( false );
241  break;
242  }
243  }
244  }
245  else
246  {
247  Q_ASSERT_X( false, "QgsFeatureFilterModel::updateCompleter", "No extra identifier value generated. Should not get here." );
248  }
249 
250  int firstRow = 0;
251 
252  // Move the extra entry to the first position
253  if ( mExtraIdentifierValueIndex != -1 )
254  {
255  if ( mExtraIdentifierValueIndex != 0 )
256  {
257  beginMoveRows( QModelIndex(), mExtraIdentifierValueIndex, mExtraIdentifierValueIndex, QModelIndex(), 0 );
258 #if QT_VERSION < QT_VERSION_CHECK(5, 6, 0)
259  Entry extraEntry = mEntries.takeAt( mExtraIdentifierValueIndex );
260  mEntries.prepend( extraEntry );
261 #else
262  mEntries.move( mExtraIdentifierValueIndex, 0 );
263 #endif
264  endMoveRows();
265  }
266  firstRow = 1;
267  }
268 
269  // Remove all entries (except for extra entry if existent)
270  beginRemoveRows( QModelIndex(), firstRow, mEntries.size() - firstRow );
271  mEntries.remove( firstRow, mEntries.size() - firstRow );
272  endRemoveRows();
273 
274  if ( currentEntryInNewList == -1 )
275  {
276  beginInsertRows( QModelIndex(), 1, entries.size() + 1 );
277  mEntries += entries;
278  endInsertRows();
279  setExtraIdentifierValueIndex( 0 );
280  }
281  else
282  {
283  if ( currentEntryInNewList != 0 )
284  {
285  beginInsertRows( QModelIndex(), 0, currentEntryInNewList - 1 );
286  mEntries = entries.mid( 0, currentEntryInNewList ) + mEntries;
287  endInsertRows();
288  }
289  else
290  {
291  mEntries.replace( 0, entries.at( 0 ) );
292  }
293 
294  emit dataChanged( index( currentEntryInNewList, 0, QModelIndex() ), index( currentEntryInNewList, 0, QModelIndex() ) );
295 
296  beginInsertRows( QModelIndex(), currentEntryInNewList + 1, newEntriesSize - currentEntryInNewList - 1 );
297  mEntries += entries.mid( currentEntryInNewList + 1 );
298  endInsertRows();
299  setExtraIdentifierValueIndex( currentEntryInNewList );
300  }
301 
302  emit filterJobCompleted();
303  }
304  emit endUpdate();
305 }
306 
307 void QgsFeatureFilterModel::gathererThreadFinished()
308 {
309  delete mGatherer;
310  mGatherer = nullptr;
311  emit isLoadingChanged();
312 }
313 
314 void QgsFeatureFilterModel::scheduledReload()
315 {
316  if ( !mSourceLayer )
317  return;
318 
319  bool wasLoading = false;
320 
321  if ( mGatherer )
322  {
323  // Send the gatherer thread to the graveyard:
324  // forget about it, tell it to stop and delete when finished
325  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
326  disconnect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
327  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, mGatherer, &QgsFieldExpressionValuesGatherer::deleteLater );
328  mGatherer->stop();
329  wasLoading = true;
330  }
331 
332  QgsFeatureRequest request;
333 
334  if ( mShouldReloadCurrentFeature )
335  {
336  request.setFilterExpression( QStringLiteral( "%1 = %2" ).arg( QgsExpression::quotedColumnRef( mIdentifierField ), QgsExpression::quotedValue( mExtraIdentifierValue ) ) );
337  }
338  else
339  {
340  QString filterClause;
341 
342  if ( mFilterValue.isEmpty() && !mFilterExpression.isEmpty() )
343  filterClause = mFilterExpression;
344  else if ( mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
345  filterClause = QStringLiteral( "(%1) ILIKE '%%2%'" ).arg( mDisplayExpression, mFilterValue );
346  else if ( !mFilterExpression.isEmpty() && !mFilterValue.isEmpty() )
347  filterClause = QStringLiteral( "(%1) AND ((%2) ILIKE '%%3%')" ).arg( mFilterExpression, mDisplayExpression, mFilterValue );
348 
349  if ( !filterClause.isEmpty() )
350  request.setFilterExpression( filterClause );
351  }
352  QSet<QString> attributes;
353  if ( request.filterExpression() )
354  attributes = request.filterExpression()->referencedColumns();
355  attributes << mIdentifierField;
356  request.setSubsetOfAttributes( attributes, mSourceLayer->fields() );
358 
359  request.setLimit( 100 );
360 
361  mGatherer = new QgsFieldExpressionValuesGatherer( mSourceLayer, mDisplayExpression, mIdentifierField, request );
362  mGatherer->setData( mShouldReloadCurrentFeature );
363 
364  connect( mGatherer, &QgsFieldExpressionValuesGatherer::collectedValues, this, &QgsFeatureFilterModel::updateCompleter );
365  connect( mGatherer, &QgsFieldExpressionValuesGatherer::finished, this, &QgsFeatureFilterModel::gathererThreadFinished );
366 
367  mGatherer->start();
368  if ( !wasLoading )
369  emit isLoadingChanged();
370 }
371 
372 QSet<QString> QgsFeatureFilterModel::requestedAttributes() const
373 {
374  QSet<QString> requestedAttrs;
375 
376  const auto rowStyles = mSourceLayer->conditionalStyles()->rowStyles();
377 
378  for ( const QgsConditionalStyle &style : rowStyles )
379  {
380  QgsExpression exp( style.rule() );
381  requestedAttrs += exp.referencedColumns();
382  }
383 
384  if ( mDisplayExpression.isField() )
385  {
386  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
387  Q_FOREACH ( const QgsConditionalStyle &style, mSourceLayer->conditionalStyles()->fieldStyles( fieldName ) )
388  {
389  QgsExpression exp( style.rule() );
390  requestedAttrs += exp.referencedColumns();
391  }
392  }
393 
394  return requestedAttrs;
395 }
396 
397 void QgsFeatureFilterModel::setExtraIdentifierValueIndex( int index )
398 {
399  if ( mExtraIdentifierValueIndex == index )
400  return;
401 
402  mExtraIdentifierValueIndex = index;
403  emit extraIdentifierValueIndexChanged( index );
404 }
405 
406 void QgsFeatureFilterModel::reloadCurrentFeature()
407 {
408  mShouldReloadCurrentFeature = true;
409  mReloadTimer.start();
410 }
411 
412 void QgsFeatureFilterModel::setExtraIdentifierValueUnguarded( const QVariant &extraIdentifierValue )
413 {
414  const QVector<Entry> entries = mEntries;
415 
416  int index = 0;
417  for ( const Entry &entry : entries )
418  {
419  if ( entry.identifierValue == extraIdentifierValue && entry.identifierValue.isNull() == extraIdentifierValue.isNull() && 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 ), QStringLiteral( "%1" ).arg( tr( "NULL" ) ), QgsFeature() ) );
434  else
435  mEntries.prepend( Entry( extraIdentifierValue, QStringLiteral( "(%1)" ).arg( extraIdentifierValue.toString() ), QgsFeature() ) );
436  endInsertRows();
437  setExtraIdentifierValueIndex( 0 );
438 
439  reloadCurrentFeature();
440  }
441 }
442 
443 QgsConditionalStyle QgsFeatureFilterModel::featureStyle( const QgsFeature &feature ) const
444 {
445  if ( !mSourceLayer )
446  return QgsConditionalStyle();
447 
448  QgsVectorLayer *layer = mSourceLayer;
449  QgsFeatureId fid = feature.id();
450  mExpressionContext.setFeature( feature );
451 
452  auto styles = QgsConditionalStyle::matchingConditionalStyles( layer->conditionalStyles()->rowStyles(), QVariant(), mExpressionContext );
453 
454  if ( mDisplayExpression.referencedColumns().count() == 1 )
455  {
456  // Style specific for this field
457  QString fieldName = *mDisplayExpression.referencedColumns().constBegin();
458  const auto allStyles = layer->conditionalStyles()->fieldStyles( fieldName );
459  const auto matchingFieldStyles = QgsConditionalStyle::matchingConditionalStyles( allStyles, feature.attribute( fieldName ), mExpressionContext );
460 
461  styles += matchingFieldStyles;
462  }
463 
464  QgsConditionalStyle style;
465  style = QgsConditionalStyle::compressStyles( styles );
466  mEntryStylesMap.insert( fid, style );
467 
468  return style;
469 }
470 
472 {
473  return mAllowNull;
474 }
475 
477 {
478  if ( mAllowNull == allowNull )
479  return;
480 
481  mAllowNull = allowNull;
482  emit allowNullChanged();
483 
484  reload();
485 }
486 
488 {
489  return mExtraValueDoesNotExist;
490 }
491 
492 void QgsFeatureFilterModel::setExtraValueDoesNotExist( bool extraValueDoesNotExist )
493 {
494  if ( mExtraValueDoesNotExist == extraValueDoesNotExist )
495  return;
496 
497  mExtraValueDoesNotExist = extraValueDoesNotExist;
499 }
500 
502 {
503  return mExtraIdentifierValueIndex;
504 }
505 
507 {
508  return mIdentifierField;
509 }
510 
512 {
513  if ( mIdentifierField == identifierField )
514  return;
515 
516  mIdentifierField = identifierField;
517  emit identifierFieldChanged();
518 }
519 
520 void QgsFeatureFilterModel::reload()
521 {
522  mReloadTimer.start();
523 }
524 
526 {
527  return mExtraIdentifierValue;
528 }
529 
531 {
532  if ( extraIdentifierValue == mExtraIdentifierValue && extraIdentifierValue.isNull() == mExtraIdentifierValue.isNull() && mExtraIdentifierValue.isValid() )
533  return;
534 
535  setExtraIdentifierValueUnguarded( extraIdentifierValue );
536 
537  mExtraIdentifierValue = extraIdentifierValue;
539 }
void filterValueChanged()
This value will be used to filter the features available from this model.
QgsFeatureId id
Definition: qgsfeature.h:71
bool allowNull() const
Add a NULL entry to the list.
void beginUpdate()
Notification that the model is about to be changed because a job was completed.
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.
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.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
friend class QgsFieldExpressionValuesGatherer
QgsConditionalLayerStyles * conditionalStyles() const
Return 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...
Conditional styling for a rule.
QString displayExpression() const
The display expression will be used for.
QgsFields fields() const override
Returns the list of fields of this layer.
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 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.
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...
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
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.
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.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
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.
QgsVectorLayer * sourceLayer() const
The source layer from which features will be fetched.
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:255
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)
Set flags that affect how features will be fetched.
void displayExpressionChanged()
The display expression will be used for.