• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MerginMaps / input / 6746094652

03 Nov 2023 01:55PM UTC coverage: 62.294% (+0.02%) from 62.277%
6746094652

push

github

web-flow
Merge pull request #2902 from MerginMaps/translations_app-i18n-input-en-ts--master_fi

Updates for file app/i18n/input_en.ts in fi

7489 of 12022 relevant lines covered (62.29%)

124.03 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.25
/app/featuresmodel.cpp
1
/***************************************************************************
2
 *                                                                         *
3
 *   This program is free software; you can redistribute it and/or modify  *
4
 *   it under the terms of the GNU General Public License as published by  *
5
 *   the Free Software Foundation; either version 2 of the License, or     *
6
 *   (at your option) any later version.                                   *
7
 *                                                                         *
8
 ***************************************************************************/
9

10
#include "featuresmodel.h"
11
#include "coreutils.h"
12

13
#include "qgsproject.h"
14
#include "qgsexpressioncontextutils.h"
15
#include "qgsvectorlayerfeatureiterator.h"
16

17
#include <QLocale>
18
#include <QTimer>
19
#include <QtConcurrent>
20

21

22
FeaturesModel::FeaturesModel( QObject *parent )
8✔
23
  : QAbstractListModel( parent ),
24
    mLayer( nullptr )
8✔
25
{
26
  connect( &mSearchResultWatcher, &QFutureWatcher<QgsFeatureList>::finished, this, &FeaturesModel::onFutureFinished );
8✔
27
}
8✔
28

29
FeaturesModel::~FeaturesModel() = default;
8✔
30

31
void FeaturesModel::populate()
18✔
32
{
33
  if ( mLayer )
18✔
34
  {
35
    mFetchingResults = true;
18✔
36
    emit fetchingResultsChanged( mFetchingResults );
18✔
37
    beginResetModel();
18✔
38
    mFeatures.clear();
18✔
39
    endResetModel();
18✔
40

41
    QgsFeatureRequest req;
18✔
42
    setupFeatureRequest( req );
18✔
43

44
    int searchId = mNextSearchId.fetchAndAddOrdered( 1 );
18✔
45
    QgsVectorLayerFeatureSource *source = new QgsVectorLayerFeatureSource( mLayer );
18✔
46
    mSearchResultWatcher.setFuture( QtConcurrent::run( &FeaturesModel::fetchFeatures, this, source, req, searchId ) );
18✔
47
  }
18✔
48
}
18✔
49

50
QgsFeatureList FeaturesModel::fetchFeatures( QgsVectorLayerFeatureSource *source, QgsFeatureRequest req, int searchId )
18✔
51
{
52
  std::unique_ptr<QgsVectorLayerFeatureSource> fs( source );
18✔
53
  QgsFeatureList fl;
18✔
54

55
  // a search might have been queued if no threads were available in the pool, so we also
56
  // check if canceled before we start as the first iteration can potentially be slow
57
  bool canceled = searchId + 1 != mNextSearchId.loadAcquire();
18✔
58
  if ( canceled )
18✔
59
  {
60
    qDebug() << QString( "Search (%1) was cancelled before it started!" ).arg( searchId );
2✔
61
    return fl;
2✔
62
  }
63

64
  QElapsedTimer t;
16✔
65
  t.start();
16✔
66
  QgsFeatureIterator it = fs->getFeatures( req );
16✔
67
  QgsFeature f;
16✔
68

69
  while ( it.nextFeature( f ) )
41✔
70
  {
71
    if ( searchId + 1 != mNextSearchId.loadAcquire() )
27✔
72
    {
73
      canceled = true;
2✔
74
      break;
2✔
75
    }
76

77
    if ( FID_IS_NEW( f.id() ) || FID_IS_NULL( f.id() ) )
25✔
78
    {
79
      continue; // ignore uncommited features
×
80
    }
81

82
    fl.append( f );
25✔
83
  }
84

85
  qDebug() << QString( "Search (%1) %2 after %3ms, results: %4" ).arg( searchId ).arg( canceled ? "was canceled" : "completed" ).arg( t.elapsed() ).arg( fl.count() );
16✔
86
  return fl;
16✔
87
}
18✔
88

89
void FeaturesModel::onFutureFinished()
12✔
90
{
91
  QFutureWatcher<QgsFeatureList> *watcher = static_cast< QFutureWatcher<QgsFeatureList> *>( sender() );
12✔
92
  const QgsFeatureList features = watcher->future().result();
12✔
93
  beginResetModel();
12✔
94
  mFeatures.clear();
12✔
95
  for ( const auto &f : features )
35✔
96
  {
97
    mFeatures << FeatureLayerPair( f, mLayer );
23✔
98
  }
99
  emit layerFeaturesCountChanged( layerFeaturesCount() );
12✔
100
  endResetModel();
12✔
101
  mFetchingResults = false;
12✔
102
  emit fetchingResultsChanged( mFetchingResults );
12✔
103
}
12✔
104

105

106
void FeaturesModel::setup()
×
107
{
108
  // define in submodels
109
}
×
110

111
QVariant FeaturesModel::data( const QModelIndex &index, int role ) const
26✔
112
{
113
  int row = index.row();
26✔
114
  if ( row < 0 || row >= mFeatures.count() )
26✔
115
    return QVariant();
×
116

117
  if ( !index.isValid() )
26✔
118
    return QVariant();
×
119

120
  const FeatureLayerPair pair = mFeatures.at( index.row() );
26✔
121

122
  switch ( role )
26✔
123
  {
124
    case FeatureTitle: return featureTitle( pair );
1✔
125
    case FeatureId: return QVariant( pair.feature().id() );
13✔
126
    case Feature: return QVariant::fromValue<QgsFeature>( pair.feature() );
20✔
127
    case FeaturePair: return QVariant::fromValue<FeatureLayerPair>( pair );
2✔
128
    case Description: return QVariant( QString( "Feature ID %1" ).arg( pair.feature().id() ) );
×
129
    case SearchResult: return searchResultPair( pair );
×
130
    case Qt::DisplayRole: return featureTitle( pair );
×
131
  }
132

133
  return QVariant();
×
134
}
26✔
135

136
int FeaturesModel::rowCount( const QModelIndex &parent ) const
49✔
137
{
138
  // For list models only the root node (an invalid parent) should return the list's size. For all
139
  // other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
140
  if ( parent.isValid() )
49✔
141
    return 0;
×
142

143
  return mFeatures.count();
49✔
144
}
145

146
QVariant FeaturesModel::featureTitle( const FeatureLayerPair &featurePair ) const
1✔
147
{
148
  QString title;
1✔
149

150
  QgsExpressionContext context( QgsExpressionContextUtils::globalProjectLayerScopes( featurePair.layer() ) );
1✔
151
  context.setFeature( featurePair.feature() );
1✔
152
  QgsExpression expr( featurePair.layer()->displayExpression() );
1✔
153
  title = expr.evaluate( &context ).toString();
1✔
154

155
  if ( title.isEmpty() )
1✔
156
    return featurePair.feature().id();
×
157

158
  return title;
1✔
159
}
1✔
160

161
QString FeaturesModel::searchResultPair( const FeatureLayerPair &pair ) const
×
162
{
163
  if ( mSearchExpression.isEmpty() )
×
164
    return QString();
×
165

166
  QgsFields fields = pair.feature().fields();
×
167
  const QStringList words = mSearchExpression.split( ' ', Qt::SkipEmptyParts );
×
168
  QStringList foundPairs;
×
169

170
  for ( const QString &word : words )
×
171
  {
172
    for ( const QgsField &field : fields )
×
173
    {
174
      if ( field.configurationFlags().testFlag( QgsField::ConfigurationFlag::NotSearchable ) )
×
175
        continue;
×
176

177
      QString attrValue = pair.feature().attribute( field.name() ).toString();
×
178

179
      if ( attrValue.toLower().indexOf( word.toLower() ) != -1 )
×
180
      {
181
        foundPairs << field.name() + ": " + attrValue;
×
182

183
        // remove found field from list of fields to not select it more than once
184
        fields.remove( fields.lookupField( field.name() ) );
×
185
      }
186
    }
×
187
  }
188

189
  return foundPairs.join( ", " );
×
190
}
×
191

192
QString FeaturesModel::buildSearchExpression()
3✔
193
{
194
  if ( mSearchExpression.isEmpty() || !mLayer )
3✔
195
    return QString();
×
196

197
  const QgsFields fields = mLayer->fields();
3✔
198
  QStringList expressionParts;
3✔
199
  QStringList wordExpressions;
3✔
200

201
  const QLocale locale;
3✔
202
  const QStringList words = mSearchExpression.split( ' ', Qt::SkipEmptyParts );
3✔
203

204
  for ( const QString &word : words )
6✔
205
  {
206
    bool searchExpressionIsNumeric;
207
    // we only need to know if expression is numeric, return value is not used
208
    locale.toFloat( word, &searchExpressionIsNumeric );
3✔
209

210

211
    for ( const QgsField &field : fields )
14✔
212
    {
213
      if ( field.configurationFlags().testFlag( QgsField::ConfigurationFlag::NotSearchable ) ||
22✔
214
           ( field.isNumeric() && !searchExpressionIsNumeric ) )
11✔
215
        continue;
3✔
216
      else if ( field.type() == QVariant::String || field.isNumeric() )
8✔
217
        expressionParts << QStringLiteral( "%1 ILIKE '%%2%'" ).arg( QgsExpression::quotedColumnRef( field.name() ), word );
8✔
218
    }
219
    wordExpressions << QStringLiteral( "(%1)" ).arg( expressionParts.join( QLatin1String( " ) OR ( " ) ) );
3✔
220
    expressionParts.clear();
3✔
221
  }
222

223
  const QString expression = QStringLiteral( "(%1)" ).arg( wordExpressions.join( QLatin1String( " ) AND ( " ) ) );
6✔
224

225
  return expression;
3✔
226
}
3✔
227

228
void FeaturesModel::setupFeatureRequest( QgsFeatureRequest &request )
19✔
229
{
230
  if ( !mSearchExpression.isEmpty() )
19✔
231
  {
232
    request.setFilterExpression( buildSearchExpression() );
3✔
233
  }
234

235
  request.setLimit( FEATURES_LIMIT );
19✔
236
}
19✔
237

238
void FeaturesModel::reloadFeatures()
1✔
239
{
240
  populate();
1✔
241
}
1✔
242

243
int FeaturesModel::layerFeaturesCount() const
19✔
244
{
245
  if ( mLayer && mLayer->isValid() )
19✔
246
  {
247
    return mLayer->dataProvider()->featureCount();
19✔
248
  }
249

250
  return 0;
×
251
}
252

253
QHash<int, QByteArray> FeaturesModel::roleNames() const
×
254
{
255
  QHash<int, QByteArray> roleNames = QAbstractListModel::roleNames();
×
256
  roleNames[FeatureTitle] = QStringLiteral( "FeatureTitle" ).toLatin1();
×
257
  roleNames[FeatureId] = QStringLiteral( "FeatureId" ).toLatin1();
×
258
  roleNames[Feature] = QStringLiteral( "Feature" ).toLatin1();
×
259
  roleNames[FeaturePair] = QStringLiteral( "FeaturePair" ).toLatin1();
×
260
  roleNames[Description] = QStringLiteral( "Description" ).toLatin1();
×
261
  roleNames[SearchResult] = QStringLiteral( "SearchResult" ).toLatin1();
×
262
  return roleNames;
×
263
}
×
264

265
int FeaturesModel::rowFromRoleValue( const int role, const QVariant &value ) const
×
266
{
267
  for ( int i = 0; i < mFeatures.count(); ++i )
×
268
  {
269
    QVariant d = data( index( i, 0 ), role );
×
270
    if ( d == value )
×
271
    {
272
      return i;
×
273
    }
274
  }
×
275
  return -1;
×
276
}
277

278
QVariant FeaturesModel::convertRoleValue( const int role, const QVariant &value, const int requestedRole ) const
3✔
279
{
280
  for ( int i = 0; i < mFeatures.count(); ++i )
11✔
281
  {
282
    QVariant d = data( index( i, 0 ), role );
11✔
283
    if ( d.toString().trimmed() == value.toString().trimmed() )
11✔
284
    {
285
      QVariant key = data( index( i, 0 ), requestedRole );
3✔
286
      return key;
3✔
287
    }
3✔
288
  }
11✔
289
  return QVariant();
×
290
}
291

292
void FeaturesModel::reset()
1✔
293
{
294
  mFeatures.clear();
1✔
295
  mLayer = nullptr;
1✔
296
  mSearchExpression.clear();
1✔
297
}
1✔
298

299
QString FeaturesModel::searchExpression() const
×
300
{
301
  return mSearchExpression;
×
302
}
303

304
void FeaturesModel::setSearchExpression( const QString &searchExpression )
4✔
305
{
306
  if ( mSearchExpression != searchExpression )
4✔
307
  {
308
    mSearchExpression = searchExpression;
4✔
309
    emit searchExpressionChanged( mSearchExpression );
4✔
310
    populate();
4✔
311
  }
312
}
4✔
313

314
int FeaturesModel::featuresLimit() const
×
315
{
316
  return FEATURES_LIMIT;
×
317
}
318

319
void FeaturesModel::setLayer( QgsVectorLayer *newLayer )
7✔
320
{
321
  if ( mLayer != newLayer )
7✔
322
  {
323
    if ( mLayer )
7✔
324
    {
325
      disconnect( mLayer );
×
326
    }
327

328
    mLayer = newLayer;
7✔
329
    emit layerChanged( mLayer );
7✔
330

331
    if ( mLayer )
7✔
332
    {
333
      // avoid dangling pointers to mLayer when switching projects
334
      connect( mLayer, &QgsMapLayer::willBeDeleted, this, &FeaturesModel::reset );
7✔
335

336
      connect( mLayer, &QgsVectorLayer::featureAdded, this, &FeaturesModel::populate );
7✔
337
      connect( mLayer, &QgsVectorLayer::featuresDeleted, this, &FeaturesModel::populate );
7✔
338
      connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &FeaturesModel::populate );
7✔
339
    }
340

341
    emit layerFeaturesCountChanged( layerFeaturesCount() );
7✔
342
  }
343
}
7✔
344

345
QgsVectorLayer *FeaturesModel::layer() const
8✔
346
{
347
  return mLayer;
8✔
348
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc