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

MerginMaps / input / 6416155585

05 Oct 2023 07:48AM UTC coverage: 61.883% (-0.02%) from 61.9%
6416155585

Pull #2829

github

PeterPetrik
fix #1671 wrong calendar day with TZ=America/Mexico_City
Pull Request #2829: WIP: calendar widget fixes

7577 of 12244 relevant lines covered (61.88%)

101.83 hits per line

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

74.33
/app/attributes/attributecontroller.cpp
1
/***************************************************************************
2
 attributecontroller.cpp
3
  --------------------------------------
4
  Date                 : 20.4.2021
5
  Copyright            : (C) 2021 by Peter Petrik
6
  Email                : zilolv@gmail.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

16
#include "attributecontroller.h"
17
#include "attributeformmodel.h"
18
#include "attributetabmodel.h"
19
#include "fieldvalidator.h"
20

21
#include <QDebug>
22
#include <QSet>
23

24
#include "qgsproject.h"
25
#include "qgsvectorlayer.h"
26
#include "qgsattributeeditorfield.h"
27
#include "qgsattributeeditorrelation.h"
28
#include "qgsattributeeditorcontainer.h"
29
#include "qgsvectorlayerutils.h"
30
#include "qgsvectorlayereditbuffer.h"
31
#include "qgsexpressioncontextutils.h"
32
#include "qgsrelation.h"
33
#include "qgsmessagelog.h"
34
#include "inpututils.h"
35
#include "coreutils.h"
36

37
AttributeController::AttributeController( QObject *parent )
19✔
38
  : QObject( parent )
39
  , mAttributeTabProxyModel( new AttributeTabProxyModel() )
19✔
40
{
41
}
19✔
42

43
void AttributeController::reset()
×
44
{
45
  setFeatureLayerPair( FeatureLayerPair() );
×
46
}
×
47

48
AttributeController::~AttributeController() = default;
19✔
49

50

51
FeatureLayerPair AttributeController::featureLayerPair() const
980✔
52
{
53
  return mFeatureLayerPair;
980✔
54
}
55

56
void AttributeController::setFeatureLayerPair( const FeatureLayerPair &pair )
24✔
57
{
58
  if ( mFeatureLayerPair != pair )
24✔
59
  {
60
    // block signals:
61
    // everything should be invalidated by
62
    // featureLayerPairChanged & attributeTabProxyModelChanged signals
63
    blockSignals( true );
24✔
64

65
    bool hasLayerChanged = mFeatureLayerPair.layer() != pair.layer();
24✔
66
    // Set new active pair
67
    mFeatureLayerPair = pair;
24✔
68
    if ( hasLayerChanged )
24✔
69
    {
70
      // layer changed!
71
      updateOnLayerChange();
18✔
72
    }
73

74
    // feature changed!
75
    updateOnFeatureChange();
24✔
76

77
    // Done, emit signals
78
    blockSignals( false );
24✔
79
    if ( hasLayerChanged )
24✔
80
    {
81
      emit attributeTabProxyModelChanged();
18✔
82
      emit hasTabsChanged();
18✔
83
    }
84
    emit featureLayerPairChanged();
24✔
85
    emit hasAnyChangesChanged();
24✔
86
    emit hasValidationErrorsChanged();
24✔
87
  }
88
}
24✔
89

90
QgsAttributeEditorContainer *AttributeController::autoLayoutTabContainer() const  //#spellok
11✔
91
{
92
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
11✔
93
  Q_ASSERT( layer );
11✔
94

95
  std::unique_ptr<QgsAttributeEditorContainer> root( new QgsAttributeEditorContainer( QLatin1String( "AutoLayoutRoot" ), nullptr ) );
11✔
96
  root->setIsGroupBox( false ); //tab!
11✔
97

98
  const QgsFields fields = layer->fields();
11✔
99
  for ( int i = 0; i < fields.size(); ++i )
51✔
100
  {
101
    QgsAttributeEditorField *field = new QgsAttributeEditorField( fields.at( i ).name(), i, root.get() );
40✔
102
    root->addChildElement( field );
40✔
103
  }
104
  return root.release();
22✔
105
}
11✔
106

107
QgsEditorWidgetSetup AttributeController::getEditorWidgetSetup( QgsVectorLayer *layer, int fieldIndex ) const
85✔
108
{
109
  QgsEditorWidgetSetup setup = layer->editorWidgetSetup( fieldIndex );
85✔
110
  if ( setup.type().isEmpty() )
85✔
111
  {
112
    QgsField field = layer->fields().at( fieldIndex );
8✔
113
    return InputUtils::getEditorWidgetSetup( field );
8✔
114
  }
8✔
115
  return setup;
77✔
116
}
85✔
117

118
void AttributeController::prefillRelationReferenceField()
2✔
119
{
120
  if ( !mParentController || !mLinkedRelation.isValid() )
2✔
121
    return;
1✔
122

123
  const QList<QgsRelation::FieldPair> fieldPairs = mLinkedRelation.fieldPairs();
1✔
124
  for ( const QgsRelation::FieldPair &fieldPair : fieldPairs )
2✔
125
  {
126
    QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
1✔
127
    while ( formItemsIterator != mFormItems.end() )
1✔
128
    {
129
      std::shared_ptr<FormItem> itemData = formItemsIterator.value();
1✔
130
      if ( itemData->field().name() == fieldPair.referencingField() )
1✔
131
      {
132
        QVariant fk = mParentController->featureLayerPair().feature().attribute( fieldPair.referencedField() );
2✔
133
        setFormValue( itemData->id(), fk );
1✔
134
        break;
1✔
135
      }
1✔
136
      ++formItemsIterator;
×
137
    }
1✔
138
  }
139
}
1✔
140

141
bool AttributeController::allowTabs( QgsAttributeEditorContainer *container )
7✔
142
{
143
  for ( QgsAttributeEditorElement *element : container->children() )
9✔
144
  {
145
    if ( element->type() == Qgis::AttributeEditorType::Container )
8✔
146
    {
147
      QgsAttributeEditorContainer *elemContainer = static_cast<QgsAttributeEditorContainer *>( element );
3✔
148
      if ( elemContainer->isGroupBox() )
3✔
149
        return false;
1✔
150
    }
151
    else
152
      return false;
5✔
153
  }
7✔
154

155
  return !container->children().isEmpty();
1✔
156
}
157

158
VariablesManager *AttributeController::variablesManager() const
×
159
{
160
  return mVariablesManager;
×
161
}
162

163
void AttributeController::setVariablesManager( VariablesManager *variablesManager )
×
164
{
165
  if ( mVariablesManager != variablesManager )
×
166
  {
167
    mVariablesManager = variablesManager;
×
168
    emit variablesManagerChanged();
×
169
  }
170
}
×
171

172
RememberAttributesController *AttributeController::rememberAttributesController() const
×
173
{
174
  return mRememberAttributesController;
×
175
}
176

177
void AttributeController::setRememberAttributesController( RememberAttributesController *rememberAttributes )
×
178
{
179
  if ( mRememberAttributesController != rememberAttributes )
×
180
  {
181
    mRememberAttributesController = rememberAttributes;
×
182
    if ( mRememberAttributesController && mFeatureLayerPair.layer() )
×
183
      mRememberAttributesController->storeLayerFields( mFeatureLayerPair.layer() );
×
184
    emit rememberAttributesChanged();
×
185
  }
186
}
×
187

188
void AttributeController::flatten(
25✔
189
  QgsAttributeEditorContainer *container,
190
  int parentTabRow,
191
  const QString &parentVisibilityExpressions,
192
  QVector<QUuid> &items
193
)
194
{
195
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
25✔
196
  Q_ASSERT( layer );
25✔
197

198
  for ( QgsAttributeEditorElement *element : container->children() )
120✔
199
  {
200
    switch ( element->type() )
95✔
201
    {
202
      case Qgis::AttributeEditorType::Container:
6✔
203
      {
204
        QString visibilityExpression = parentVisibilityExpressions;
6✔
205
        QgsAttributeEditorContainer *container = static_cast<QgsAttributeEditorContainer *>( element );
6✔
206
        if ( container->visibilityExpression().enabled() )
6✔
207
        {
208
          if ( visibilityExpression.isNull() )
×
209
            visibilityExpression = container->visibilityExpression().data().expression();
×
210
          else
211
            visibilityExpression += " AND " + container->visibilityExpression().data().expression();
×
212
        }
213

214
        flatten( container, parentTabRow, visibilityExpression, items );
6✔
215
        break;
6✔
216
      }
6✔
217

218
      case Qgis::AttributeEditorType::Field:
85✔
219
      {
220
        QUuid fieldUuid = QUuid::createUuid();
85✔
221

222
        QgsAttributeEditorField *editorField = static_cast<QgsAttributeEditorField *>( element );
85✔
223
        int fieldIndex = editorField->idx();
85✔
224
        if ( fieldIndex < 0 || fieldIndex >= layer->fields().size() )
85✔
225
        {
226
          CoreUtils::log( "Forms", QStringLiteral( "Invalid fieldIndex for editorField in layer %1" ).arg( layer->name() ) );
×
227
          continue;
×
228
        }
229
        QgsField field = layer->fields().at( fieldIndex );
85✔
230
        QStringList expressions;
85✔
231
        QString expression = field.constraints().constraintExpression();
85✔
232

233
        if ( !expression.isEmpty() )
85✔
234
        {
235
          expressions << field.constraints().constraintExpression();
1✔
236
        }
237

238
        bool isReadOnly = ( layer->editFormConfig().readOnly( fieldIndex ) ) ||
255✔
239
                          ( !field.defaultValueDefinition().expression().isEmpty() && field.defaultValueDefinition().applyOnUpdate() );
170✔
240

241
        const QString groupName = container->isGroupBox() ? container->name() : QString();
85✔
242
        std::shared_ptr<FormItem> formItemData =
243
          std::shared_ptr<FormItem>(
244
            FormItem::createFieldItem(
245
              fieldUuid,
246
              field,
247
              groupName,
248
              parentTabRow,
249
              FormItem::Field,
250
              layer->attributeDisplayName( fieldIndex ),
85✔
251
              !isReadOnly,
85✔
252
              getEditorWidgetSetup( layer, fieldIndex ),
170✔
253
              fieldIndex,
254
              parentVisibilityExpressions // field doesn't have visibility expression itself
255
            )
256
          );
170✔
257

258
        mFormItems[formItemData->id()] = formItemData;
85✔
259

260

261
        items.append( fieldUuid );
85✔
262
        break;
85✔
263
      }
85✔
264

265
      case Qgis::AttributeEditorType::Relation:
4✔
266
      {
267
        QUuid widgetUuid = QUuid::createUuid();
4✔
268

269
        QgsAttributeEditorRelation *relationField = static_cast<QgsAttributeEditorRelation *>( element );
4✔
270
        QgsRelation associatedRelation = relationField->relation();
4✔
271

272
        bool isValid = associatedRelation.isValid();
4✔
273
        if ( !isValid )
4✔
274
        {
275
          CoreUtils::log( "Relations", QStringLiteral( "Ignoring invalid relation in layer %1" ).arg( layer->name() ) );
×
276
          continue;
×
277
        }
278

279
        bool isNmRelation = layer->editFormConfig().widgetConfig( associatedRelation.id() )[QStringLiteral( "nm-rel" )].toBool();
8✔
280
        if ( isNmRelation )
4✔
281
        {
282
          CoreUtils::log( "Relations", QStringLiteral( "Nm relations are not supported in layer %1" ).arg( layer->name() ) );
×
283
          continue;
×
284
        }
285

286
        const QString groupName = container->isGroupBox() ? container->name() : QString();
4✔
287
        QString label = relationField->label();
4✔
288
        if ( label.isEmpty() )
4✔
289
        {
290
          label = associatedRelation.name();
2✔
291
          if ( label.isEmpty() ) // relation name can be empty
2✔
292
          {
293
            label = associatedRelation.referencingLayer()->name();
×
294
          }
295
        }
296

297
        std::shared_ptr<FormItem> formItemData =
298
          std::shared_ptr<FormItem>(
299
            FormItem::createRelationItem(
300
              widgetUuid,
301
              groupName,
302
              parentTabRow,
303
              FormItem::Relation,
304
              label,
305
              associatedRelation
306
            )
307
          );
4✔
308

309
        mFormItems[formItemData->id()] = formItemData;
4✔
310
        items.append( widgetUuid );
4✔
311

312
        break;
4✔
313
      }
8✔
314

315
      default:
×
316
        // Invalid, Action, QmlElement, HtmlElement, TextElement and SpacerElement
317
        // are not supported at the moment
318
        break;
×
319
    }
320
  }
25✔
321
}
25✔
322

323
void AttributeController::createTab( QgsAttributeEditorContainer *container )
19✔
324
{
325
  Q_ASSERT( container );
19✔
326

327
  int tabRow = mTabItems.size();
19✔
328

329
  QgsExpression expr;
19✔
330
  if ( container->visibilityExpression().enabled() )
19✔
331
  {
332
    expr = container->visibilityExpression().data();
×
333
  }
334

335
  QVector<QUuid> formItemsUuids;
19✔
336
  flatten( container, tabRow, QString(), formItemsUuids );
19✔
337

338
  std::shared_ptr<TabItem> tabItem( new TabItem( tabRow,
339
                                    container->name(),
19✔
340
                                    formItemsUuids,
341
                                    expr )
19✔
342
                                  );
19✔
343

344
  mTabItems.push_back( tabItem );
19✔
345
}
19✔
346

347
void AttributeController::clearAll()
18✔
348
{
349
  mAttributeFormProxyModelForTabItem.clear();
18✔
350
  mAttributeTabProxyModel.reset( new AttributeTabProxyModel() );
18✔
351
  setHasValidationErrors( false );
18✔
352
  mFormItems.clear();
18✔
353
  mTabItems.clear();
18✔
354
  mExpressionFieldsOutsideForm.clear();
18✔
355
  mHasTabs = false;
18✔
356
}
18✔
357

358
void AttributeController::updateOnLayerChange()
18✔
359
{
360
  clearAll();
18✔
361

362
  // 1) DATA
363
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
18✔
364
  if ( layer )
18✔
365
  {
366
    if ( layer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
18✔
367
    {
368
      QgsAttributeEditorContainer *root = layer->editFormConfig().invisibleRootContainer();
7✔
369
      if ( root->columnCount() > 1 )
7✔
370
      {
371
        qDebug() << "root tab in manual config has multiple columns. not supported on mobile devices!";
×
372
        root->setColumnCount( 1 );
×
373
      }
374

375
      mHasTabs = allowTabs( root );
7✔
376
      if ( mHasTabs )
7✔
377
      {
378
        for ( QgsAttributeEditorElement *element : root->children() )
3✔
379
        {
380
          if ( element->type() == Qgis::AttributeEditorType::Container )
2✔
381
          {
382
            QgsAttributeEditorContainer *container = static_cast<QgsAttributeEditorContainer *>( element );
2✔
383
            if ( container->columnCount() > 1 )
2✔
384
            {
385
              qDebug() << "tab " << container->name() << " in manual config has multiple columns. not supported on mobile devices!";
×
386
              container->setColumnCount( 1 );
×
387
            }
388
            createTab( container );
2✔
389
          }
390
        }
1✔
391
      }
392
      else
393
      {
394
        createTab( root );
6✔
395
      }
396
    }
397
    else
398
    {
399
      // Auto-Generated Layout
400
      // We create fake root tab
401
      QgsAttributeEditorContainer *tab = autoLayoutTabContainer();
11✔
402

403
      // We need to look for relations and include them into form,
404
      // in auto-generated layout they are not included in form config
405
      discoverRelations( tab );
11✔
406

407
      createTab( tab );
11✔
408
    }
409

410
    if ( mRememberAttributesController )
18✔
411
      mRememberAttributesController->storeLayerFields( layer );
×
412
  }
413

414
  // 2) MODELS
415
  // for all other models, ownership is managed by Qt parent system
416
  AttributeTabModel *tabModel = new AttributeTabModel( mAttributeTabProxyModel.get(), this, mTabItems.size() );
18✔
417
  mAttributeTabProxyModel->setSourceModel( tabModel );
18✔
418
  mAttributeFormProxyModelForTabItem.resize( mTabItems.size() );
18✔
419
  QVector<std::shared_ptr<TabItem>>::iterator tabItemsIterator = mTabItems.begin();
18✔
420
  while ( tabItemsIterator != mTabItems.end() )
37✔
421
  {
422
    std::shared_ptr<TabItem> item = *tabItemsIterator;
19✔
423
    const QVector<QUuid> &formItems = item->formItems();
19✔
424

425
    AttributeFormProxyModel *proxyFormModel = new AttributeFormProxyModel( mAttributeTabProxyModel.get() );
19✔
426
    AttributeFormModel *formModel = new AttributeFormModel( mAttributeTabProxyModel.get(), this, formItems );
19✔
427
    proxyFormModel->setSourceModel( formModel );
19✔
428
    mAttributeFormProxyModelForTabItem[item->tabIndex()] = proxyFormModel;
19✔
429
    ++tabItemsIterator;
19✔
430
  }
19✔
431

432
  // collect fields which have default value expression and are not in the form
433
  QSet<int> fieldIndexes;
18✔
434
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
18✔
435
  while ( formItemsIterator != mFormItems.end() )
107✔
436
  {
437
    std::shared_ptr<FormItem> item = formItemsIterator.value();
89✔
438
    if ( item->type() == FormItem::Field )
89✔
439
    {
440
      fieldIndexes << item->fieldIndex();
85✔
441
    }
442

443
    ++formItemsIterator;
89✔
444
  }
89✔
445

446
  for ( int i = 0; i < mFeatureLayerPair.layer()->fields().count(); i++ )
107✔
447
  {
448
    if ( !fieldIndexes.contains( i ) )
89✔
449
    {
450
      QgsField f = mFeatureLayerPair.layer()->fields().at( i );
4✔
451
      const QgsDefaultValue defaultDefinition = f.defaultValueDefinition();
4✔
452

453
      if ( !defaultDefinition.expression().isEmpty() )
4✔
454
      {
455
        mExpressionFieldsOutsideForm << i;
3✔
456
      }
457
    }
4✔
458
  }
459

460
}
18✔
461

462
void AttributeController::updateOnFeatureChange()
24✔
463
{
464
  const QgsFeature feature = mFeatureLayerPair.feature();
24✔
465

466
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
24✔
467
  while ( formItemsIterator != mFormItems.end() )
155✔
468
  {
469
    std::shared_ptr<FormItem> itemData = formItemsIterator.value();
131✔
470
    if ( itemData->type() == FormItem::Field )
131✔
471
    {
472
      int fieldIndex = itemData->fieldIndex();
126✔
473
      const QVariant newVal = feature.attribute( fieldIndex );
126✔
474
      mFormItems[itemData->id()]->setOriginalValue( newVal );
126✔
475
      mFormItems[itemData->id()]->setRawValue( newVal ); // we need to set raw value as well, as we use it in form now
126✔
476
      if ( mRememberAttributesController && isNewFeature() ) // this is a new feature
126✔
477
      {
478
        QVariant rememberedValue;
×
479
        bool shouldUseRememberedValue = mRememberAttributesController->rememberedValue(
×
480
                                          mFeatureLayerPair.layer(),
×
481
                                          fieldIndex,
482
                                          rememberedValue
483
                                        );
484
        if ( shouldUseRememberedValue )
×
485
        {
486
          mFeatureLayerPair.featureRef().setAttribute( fieldIndex, rememberedValue );
×
487
          itemData->setRawValue( rememberedValue );
×
488
        }
489
      }
×
490
    }
126✔
491
    ++formItemsIterator;
131✔
492
  }
131✔
493

494
  bool formValueChange = false;
24✔
495
  // if feature geometry was changed we also need recalculate defaults
496
  // as some attributes may contain expressions which use feature geometry
497
  if ( mFeatureLayerPair.layer()->isEditable() )
24✔
498
  {
499
    formValueChange = mFeatureLayerPair.layer()->editBuffer()->changedGeometries().contains( feature.id() );
1✔
500
  }
501
  recalculateDerivedItems( formValueChange, isNewFeature() );
24✔
502
}
24✔
503

504
bool AttributeController::isNewFeature() const
139✔
505
{
506
  QgsFeatureId id = mFeatureLayerPair.feature().id();
139✔
507
  return FID_IS_NEW( id ) || FID_IS_NULL( id );
139✔
508
}
509

510
void AttributeController::acquireId()
×
511
{
512
  if ( !mFeatureLayerPair.layer() )
×
513
    return;
×
514

515
  QgsFeature feat = mFeatureLayerPair.feature();
×
516
  bool featureIsNotYetAdded = FID_IS_NULL( feat.id() );
×
517

518
  startEditing();
×
519

520
  if ( featureIsNotYetAdded )
×
521
  {
522
    if ( !mFeatureLayerPair.layer()->addFeature( feat ) )
×
523
    {
524
      QgsMessageLog::logMessage( tr( "Feature could not be added" ),
×
525
                                 QStringLiteral( "Input" ),
×
526
                                 Qgis::Critical );
527

528
    }
529
  }
530
  else
531
  {
532
    if ( !mFeatureLayerPair.layer()->updateFeature( feat ) )
×
533
      QgsMessageLog::logMessage( tr( "Cannot update feature" ),
×
534
                                 QStringLiteral( "Input" ),
×
535
                                 Qgis::Warning );
536
  }
537

538
  connect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
×
539

540
  if ( !commit() )
×
541
  {
542
    emit commitFailed();
×
543
  }
544

545
  disconnect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
×
546
}
×
547

548
bool AttributeController::recalculateDefaultValues(
122✔
549
  QSet<QUuid> &changedFormItems,
550
  QgsExpressionContext &expressionContext,
551
  bool isFormValueChange,
552
  bool isFirstUpdateOfNewFeature
553
)
554
{
555
  // update default values for fields which are not in the form
556
  for ( const int idx : mExpressionFieldsOutsideForm )
147✔
557
  {
558
    QgsField f = mFeatureLayerPair.layer()->fields().at( idx );
25✔
559
    const QgsDefaultValue defaultDefinition = f.defaultValueDefinition();
25✔
560

561
    bool shouldApplyDefaultValue =
562
      !defaultDefinition.expression().isEmpty() &&
67✔
563
      ( isFirstUpdateOfNewFeature || ( isFormValueChange && defaultDefinition.applyOnUpdate() ) );
42✔
564

565
    if ( shouldApplyDefaultValue )
25✔
566
    {
567
      QgsExpression exp( defaultDefinition.expression() );
11✔
568
      exp.prepare( &expressionContext );
11✔
569
      if ( exp.hasParserError() )
11✔
570
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg(
×
571
                                     mFeatureLayerPair.layer()->name(),
×
572
                                     f.name(),
×
573
                                     exp.parserErrorString() ),
×
574
                                   QStringLiteral( "Input" ),
×
575
                                   Qgis::Warning );
576

577
      QVariant value = exp.evaluate( &expressionContext );
11✔
578

579
      if ( exp.hasEvalError() )
11✔
580
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg(
×
581
                                     mFeatureLayerPair.layer()->name(),
×
582
                                     f.name(),
×
583
                                     exp.evalErrorString() ),
×
584
                                   QStringLiteral( "Input" ),
×
585
                                   Qgis::Warning );
586
      else
587
      {
588
        QVariant val( value );
11✔
589
        if ( !f.convertCompatible( val ) )
11✔
590
        {
591
          QString msg( tr( "Value \"%1\" %4 could not be converted to a compatible value for field %2(%3)." ).arg( value.toString(), f.name(), f.typeName(), value.isNull() ? "NULL" : "NOT NULL" ) );
×
592
          QgsMessageLog::logMessage( msg );
×
593
        }
×
594
        else
595
        {
596
          mFeatureLayerPair.featureRef().setAttribute( idx, val );
11✔
597
          expressionContext.setFeature( featureLayerPair().featureRef() );
11✔
598
        }
599
      }
11✔
600
    }
11✔
601
  }
25✔
602

603
  // evaluate default values for fields in the form
604
  bool hasChanges = false;
122✔
605
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
122✔
606
  while ( formItemsIterator != mFormItems.end() )
988✔
607
  {
608
    std::shared_ptr<FormItem> item = formItemsIterator.value();
866✔
609
    const QgsField field = item->field();
866✔
610
    const QgsDefaultValue defaultDefinition = field.defaultValueDefinition();
866✔
611

612
    bool shouldApplyDefaultValue =
613
      !defaultDefinition.expression().isEmpty() &&
1,779✔
614
      ( isFirstUpdateOfNewFeature || ( isFormValueChange && defaultDefinition.applyOnUpdate() ) );
913✔
615

616
    if ( shouldApplyDefaultValue )
866✔
617
    {
618
      QgsExpression exp( field.defaultValueDefinition().expression() );
124✔
619
      exp.prepare( &expressionContext );
62✔
620
      if ( exp.hasParserError() )
62✔
621
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg(
×
622
                                     mFeatureLayerPair.layer()->name(),
×
623
                                     field.name(),
×
624
                                     exp.parserErrorString() ),
×
625
                                   QStringLiteral( "Input" ),
×
626
                                   Qgis::Warning );
627

628
      QVariant value = exp.evaluate( &expressionContext );
62✔
629

630
      if ( exp.hasEvalError() )
62✔
631
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg(
×
632
                                     mFeatureLayerPair.layer()->name(),
×
633
                                     field.name(),
×
634
                                     exp.evalErrorString() ),
×
635
                                   QStringLiteral( "Input" ),
×
636
                                   Qgis::Warning );
637
      else
638
      {
639
        QVariant val( value );
62✔
640
        if ( !field.convertCompatible( val ) )
62✔
641
        {
642
          QString msg( tr( "Value \"%1\" %4 could not be converted to a compatible value for field %2(%3)." ).arg( value.toString(), field.name(), field.typeName(), value.isNull() ? "NULL" : "NOT NULL" ) );
×
643
          QgsMessageLog::logMessage( msg );
×
644
        }
×
645
        else
646
        {
647
          QVariant oldVal = mFeatureLayerPair.feature().attribute( item->fieldIndex() );
62✔
648

649
          if ( val != oldVal )
62✔
650
          {
651
            mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), val );
14✔
652
            item->setRawValue( val );
14✔
653
            emit formDataChanged( item->id(), { AttributeFormModel::RawValue, AttributeFormModel::RawValueIsNull } );
14✔
654
            // Update also expression context after an attribute change
655
            expressionContext.setFeature( featureLayerPair().featureRef() );
14✔
656
            changedFormItems.insert( item->id() );
14✔
657
            hasChanges = true;
14✔
658
          }
659
        }
62✔
660
      }
62✔
661
    }
62✔
662

663
    if ( isFirstUpdateOfNewFeature )
866✔
664
    {
665
      // check if item is a slider, if so and it does not have default value,
666
      // set it's initial value from NULL to zero or minimum
667
      bool isSlider = item->editorWidgetType() == QStringLiteral( "Range" ) &&
273✔
668
                      item->editorWidgetConfig().value( QStringLiteral( "Style" ) ) == QStringLiteral( "Slider" );
106✔
669
      bool hasDefaultValueDefinition = !defaultDefinition.expression().isEmpty();
88✔
670

671
      if ( isSlider && !hasDefaultValueDefinition )
88✔
672
      {
673
        double min = item->editorWidgetConfig().value( "Min" ).toDouble();
1✔
674
        double max = item->editorWidgetConfig().value( "Max" ).toDouble();
1✔
675
        double valueToSet = 0;
1✔
676

677
        if ( !( min <= valueToSet && valueToSet <= max ) )
1✔
678
        {
679
          valueToSet = qMin( min, max );
×
680
        }
681

682
        mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), valueToSet );
1✔
683
        item->setRawValue( valueToSet );
1✔
684
        emit formDataChanged( item->id(), { AttributeFormModel::RawValue } );
1✔
685
        changedFormItems.insert( item->id() );
1✔
686
      }
687
    }
688

689
    ++formItemsIterator;
866✔
690
  }
866✔
691
  return hasChanges;
122✔
692
}
693

694
void AttributeController::recalculateDerivedItems( bool isFormValueChange, bool isFirstUpdateOfNewFeature )
118✔
695
{
696
  QSet<QUuid> changedFormItems;
118✔
697

698
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
118✔
699
  if ( !layer || !layer->isValid() )
118✔
700
    return;
×
701

702
  if ( !mFeatureLayerPair.feature().isValid() )
118✔
703
    return;
4✔
704

705
  // Create context
706
  QgsFields fields = mFeatureLayerPair.feature().fields();
114✔
707
  QgsExpressionContext expressionContext = layer->createExpressionContext();
114✔
708
  expressionContext << QgsExpressionContextUtils::formScope( mFeatureLayerPair.feature() );
114✔
709
  if ( mVariablesManager )
114✔
710
    expressionContext << mVariablesManager->positionScope();
×
711

712
  expressionContext.setFields( fields );
114✔
713
  expressionContext.setFeature( featureLayerPair().featureRef() );
114✔
714

715
  // Evaluate default values
716
  // it could be recursive, so
717
  // let say try few times
718
  const int LIMIT = 3;
114✔
719
  int tryNumber = 0;
114✔
720
  bool anyValueChanged = true;
114✔
721
  while ( anyValueChanged && tryNumber < LIMIT )
236✔
722
  {
723
    anyValueChanged = recalculateDefaultValues( changedFormItems, expressionContext, isFormValueChange, isFirstUpdateOfNewFeature );
122✔
724
    ++tryNumber;
122✔
725
  }
726
  if ( anyValueChanged )
114✔
727
  {
728
    // ok we cut the loop on limit...
729
    qDebug() << "Evaluation of default values was not finished in " << LIMIT << " tries. Giving up, sorry!";
×
730
  }
731

732
  // Evaluate tab items visiblity
733
  {
734
    QVector<std::shared_ptr<TabItem>>::iterator tabItemsIterator = mTabItems.begin();
114✔
735
    while ( tabItemsIterator != mTabItems.end() )
230✔
736
    {
737
      std::shared_ptr<TabItem> item = *tabItemsIterator;
116✔
738
      QgsExpression exp = item->visibilityExpression();
116✔
739
      exp.prepare( &expressionContext );
116✔
740
      bool visible = true;
116✔
741
      if ( exp.isValid() )
116✔
742
      {
743
        visible = exp.evaluate( &expressionContext ).toBool();
×
744
      }
745

746
      if ( item->isVisible() != visible )
116✔
747
      {
748
        item->setVisible( visible );
17✔
749
        emit tabDataChanged( item->tabIndex() );
17✔
750
      }
751
      ++tabItemsIterator;
116✔
752
    }
116✔
753
  }
754

755
  // Evaluate form items visibility
756
  {
757
    QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
114✔
758
    while ( formItemsIterator != mFormItems.end() )
920✔
759
    {
760
      std::shared_ptr<FormItem> item = formItemsIterator.value();
806✔
761
      bool visible = true;
806✔
762
      if ( item->editorWidgetType() == QLatin1String( "Hidden" ) )
806✔
763
      {
764
        visible = false;
8✔
765
      }
766
      else
767
      {
768
        QgsExpression exp = item->visibilityExpression();
798✔
769
        exp.prepare( &expressionContext );
798✔
770

771
        if ( exp.isValid() )
798✔
772
          visible = exp.evaluate( &expressionContext ).toInt();
×
773
      }
798✔
774

775
      if ( item->visible() != visible )
806✔
776
      {
777
        item->setVisible( visible );
79✔
778
        changedFormItems << item->id();
79✔
779
      }
780
      ++formItemsIterator;
806✔
781
    }
806✔
782
  }
783

784
  // Evaluate form items value state - hard/soft constraints, value validity
785
  {
786
    bool containsValidationError = false;
114✔
787
    {
788
      QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
114✔
789
      while ( formItemsIterator != mFormItems.end() )
920✔
790
      {
791
        std::shared_ptr<FormItem> item = formItemsIterator.value();
806✔
792
        if ( item->type() == FormItem::Field )
806✔
793
        {
794
          QString validationMessage;
803✔
795
          FieldValidator::ValidationStatus validationStatus;
796

797
          validationStatus = FieldValidator::validate( featureLayerPair(), *item, validationMessage );
803✔
798

799
          if ( validationStatus == FieldValidator::Error )
803✔
800
          {
801
            containsValidationError = true;
109✔
802
          }
803

804
          if ( validationMessage != item->validationMessage() )
803✔
805
          {
806
            item->setValidationStatus( validationStatus );
50✔
807
            item->setValidationMessage( validationMessage );
50✔
808
            changedFormItems.insert( item->id() );
50✔
809
          }
810
        }
803✔
811
        ++formItemsIterator;
806✔
812
      }
806✔
813
    }
814
    setHasValidationErrors( containsValidationError );
114✔
815
  }
816

817
  // Check if we have any changes
818
  bool anyChanges = isNewFeature();
114✔
819
  if ( !anyChanges )
114✔
820
  {
821
    QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
13✔
822
    while ( formItemsIterator != mFormItems.end() )
71✔
823
    {
824
      std::shared_ptr<FormItem> item = formItemsIterator.value();
59✔
825
      if ( item->type() == FormItem::Field )
59✔
826
      {
827
        if ( item->originalValue() != mFeatureLayerPair.feature().attribute( item->fieldIndex() ) )
57✔
828
        {
829
          anyChanges = true;
1✔
830
          break;
1✔
831
        }
832
      }
833

834
      ++formItemsIterator;
58✔
835
    }
59✔
836
  }
837
  setHasAnyChanges( anyChanges );
114✔
838

839
  // Emit all signals
840
  QSet<QUuid>::const_iterator i = changedFormItems.constBegin();
114✔
841
  while ( i != changedFormItems.constEnd() )
245✔
842
  {
843
    emit formDataChanged( *i );
131✔
844
    ++i;
131✔
845
  }
846

847
  if ( isFormValueChange )
114✔
848
    emit formRecalculated();
94✔
849
}
118✔
850

851
bool AttributeController::hasValidationErrors() const
10✔
852
{
853
  return mHasValidationErrors;
10✔
854
}
855

856
bool AttributeController::hasTabs() const
5✔
857
{
858
  return mHasTabs;
5✔
859
}
860

861
AttributeTabProxyModel *AttributeController::attributeTabProxyModel() const
4✔
862
{
863
  Q_ASSERT( mAttributeTabProxyModel );
4✔
864
  return mAttributeTabProxyModel.get();
4✔
865
}
866

867
int AttributeController::tabCount() const
54✔
868
{
869
  return mTabItems.size();
54✔
870
}
871

872
AttributeFormProxyModel *AttributeController::attributeFormProxyModelForTab( int tabRow ) const
4✔
873
{
874
  if ( isValidTabId( tabRow ) )
4✔
875
  {
876
    return mAttributeFormProxyModelForTabItem[tabRow];
4✔
877
  }
878
  else
879
  {
880
    return nullptr;
×
881
  }
882
}
883

884
bool AttributeController::deleteFeature()
×
885
{
886
  if ( !mFeatureLayerPair.layer() )
×
887
    return false;
×
888

889
  bool rv = true;
×
890

891
  if ( !startEditing() )
×
892
  {
893
    rv = false;
×
894
  }
895

896
  bool isDeleted = mFeatureLayerPair.layer()->deleteFeature( mFeatureLayerPair.feature().id() );
×
897
  rv = commit();
×
898

899
  if ( !isDeleted || !rv )
×
900
  {
901
    QgsMessageLog::logMessage( tr( "Cannot delete feature" ),
×
902
                               QStringLiteral( "Input" ),
×
903
                               Qgis::Warning );
904
    emit commitFailed();
×
905
  }
906
  else
907
  {
908
    mFeatureLayerPair = FeatureLayerPair();
×
909
    emit featureLayerPairChanged();
×
910
    emit changesCommited();
×
911
  }
912

913
  return rv;
×
914
}
915

916
bool AttributeController::rollback()
×
917
{
918
  if ( !mFeatureLayerPair.layer() )
×
919
    return false;
×
920

921
  if ( !mFeatureLayerPair.layer()->isEditable() )
×
922
  {
923
    return false;
×
924
  }
925

926
  if ( !mFeatureLayerPair.layer()->rollBack() )
×
927
  {
928
    CoreUtils::log( QStringLiteral( "Attribute Controller" ), QStringLiteral( "Could not rollback the changes in form" ) );
×
929
  }
930

931
  mFeatureLayerPair.layer()->triggerRepaint();
×
932
  return true;
×
933
}
934

935
bool AttributeController::save()
1✔
936
{
937
  if ( !mFeatureLayerPair.layer() )
1✔
938
    return false;
×
939

940
  renamePhotos();
1✔
941

942
  if ( !startEditing() )
1✔
943
  {
944
    return false;
×
945
  }
946

947
  bool rv = true;
1✔
948

949
  QgsFeature feat = mFeatureLayerPair.feature();
1✔
950

951
  bool featureIsNotYetAdded = FID_IS_NULL( feat.id() );
1✔
952

953
  if ( featureIsNotYetAdded )
1✔
954
  {
955
    if ( !mFeatureLayerPair.layer()->addFeature( feat ) )
1✔
956
    {
957
      QgsMessageLog::logMessage( tr( "Feature could not be added" ),
×
958
                                 QStringLiteral( "Input" ),
×
959
                                 Qgis::Critical );
960

961
    }
962
    connect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
1✔
963
  }
964
  else
965
  {
966
    // update it instead of adding
967
    if ( !mFeatureLayerPair.layer()->updateFeature( feat, true ) )
×
968
      QgsMessageLog::logMessage( tr( "Cannot update feature" ),
×
969
                                 QStringLiteral( "Input" ),
×
970
                                 Qgis::Warning );
971
  }
972

973
  bool featureIsNew = isNewFeature();
1✔
974

975
  // This calls lower-level I/O functions which shouldn't be used
976
  // in a Q_INVOKABLE because they can make the UI unresponsive.
977
  rv = commit();
1✔
978

979
  if ( rv )
1✔
980
  {
981
    emit changesCommited();
1✔
982
  }
983
  else
984
  {
985
    emit commitFailed();
×
986
  }
987

988
  if ( featureIsNotYetAdded )
1✔
989
  {
990
    disconnect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
1✔
991
  }
992

993
  // Store the feature attributes for future use
994
  if ( featureIsNew && mRememberAttributesController )
1✔
995
  {
996
    mRememberAttributesController->storeFeature( mFeatureLayerPair );
×
997
  }
998

999
  return rv;
1✔
1000
}
1✔
1001

1002
bool AttributeController::startEditing()
1✔
1003
{
1004
  Q_ASSERT( mFeatureLayerPair.layer() );
1✔
1005

1006
  // Already an edit session active
1007
  if ( mFeatureLayerPair.layer()->editBuffer() )
1✔
1008
    return true;
×
1009

1010
  if ( !mFeatureLayerPair.layer()->startEditing() )
1✔
1011
  {
1012
    QgsMessageLog::logMessage( tr( "Cannot start editing" ),
×
1013
                               QStringLiteral( "Input" ),
×
1014
                               Qgis::Warning );
1015
    return false;
×
1016
  }
1017
  else
1018
  {
1019
    return true;
1✔
1020
  }
1021
}
1022

1023
bool AttributeController::commit()
1✔
1024
{
1025
  Q_ASSERT( mFeatureLayerPair.layer() );
1✔
1026

1027

1028
  if ( !mFeatureLayerPair.layer()->commitChanges() )
1✔
1029
  {
1030
    CoreUtils::log( QStringLiteral( "CommitChanges" ),
×
1031
                    QStringLiteral( "Failed to commit changes:\n%1" )
×
1032
                    .arg( mFeatureLayerPair.layer()->commitErrors().join( QLatin1Char( '\n' ) ) ) );
×
1033
    mFeatureLayerPair.layer()->rollBack();
×
1034
    return false;
×
1035
  }
1036
  else
1037
  {
1038
    mFeatureLayerPair.layer()->triggerRepaint();
1✔
1039
    return true;
1✔
1040
  }
1041
}
1042

1043
bool AttributeController::hasAnyChanges() const
×
1044
{
1045
  return mHasAnyChanges;
×
1046
}
1047

1048
void AttributeController::setHasAnyChanges( bool hasChanges )
114✔
1049
{
1050
  if ( hasChanges != mHasAnyChanges )
114✔
1051
  {
1052
    mHasAnyChanges = hasChanges;
10✔
1053
    emit hasAnyChangesChanged();
10✔
1054
  }
1055
}
114✔
1056

1057
void AttributeController::setHasValidationErrors( bool hasErrors )
132✔
1058
{
1059
  if ( mHasValidationErrors != hasErrors )
132✔
1060
  {
1061
    mHasValidationErrors = hasErrors;
23✔
1062
    emit hasValidationErrorsChanged();
23✔
1063
  }
1064
}
132✔
1065

1066
void AttributeController::discoverRelations( QgsAttributeEditorContainer *container )
11✔
1067
{
1068
  QgsRelationManager *rManager = QgsProject::instance()->relationManager();
11✔
1069

1070
  if ( !rManager || !mFeatureLayerPair.layer() )
11✔
1071
    return;
×
1072

1073
  // find relation references (add relation reference widgets)
1074
  const QList<QgsRelation> childRelations = rManager->referencingRelations( mFeatureLayerPair.layer() );
11✔
1075

1076
  for ( const QgsRelation &relation : childRelations )
15✔
1077
  {
1078
    for ( QgsAttributeEditorElement *child : container->children() )
18✔
1079
    {
1080
      if ( child->name() == relation.fieldPairs().at( 0 ).first ) // we are using only one fieldPair!
14✔
1081
      {
1082
        QgsAttributeEditorField *editorField = static_cast<QgsAttributeEditorField *>( child );
4✔
1083

1084
        if ( !editorField )
4✔
1085
          continue;
×
1086

1087
        int fieldIdx = editorField->idx();
4✔
1088
        QgsField field = mFeatureLayerPair.layer()->fields().at( fieldIdx );
4✔
1089

1090
        QVariantMap config = mFeatureLayerPair.layer()->editorWidgetSetup( fieldIdx ).config();
4✔
1091

1092
        // relation reference fields in autogenerated fields might have an empty config, we need to set needed properties here
1093
        if ( config.isEmpty() )
4✔
1094
        {
1095
          QVariantMap neededArgs =
1096
          {
1097
            { QStringLiteral( "ReferencedLayerId" ), relation.referencedLayerId() },
2✔
1098
            { QStringLiteral( "Relation" ), relation.id() }
2✔
1099
          };
5✔
1100

1101
          const QgsEditorWidgetSetup newWidgetSetup = InputUtils::getEditorWidgetSetup( field, QStringLiteral( "RelationReference" ), neededArgs );
2✔
1102
          mFeatureLayerPair.layer()->setEditorWidgetSetup( fieldIdx, newWidgetSetup );
1✔
1103

1104
          CoreUtils::log( QStringLiteral( "DiscoverRelations" ), QStringLiteral( "Changed field %1 to RelationReference" ).arg( field.name() ) );
2✔
1105
        }
1✔
1106
        else
1107
        {
1108
          config.insert( QStringLiteral( "Relation" ), relation.id() );
6✔
1109

1110
          const QgsEditorWidgetSetup newWidgetSetup = QgsEditorWidgetSetup( QStringLiteral( "RelationReference" ), config );
6✔
1111
          mFeatureLayerPair.layer()->setEditorWidgetSetup( fieldIdx, newWidgetSetup );
3✔
1112
        }
3✔
1113
      }
4✔
1114
    }
4✔
1115
  }
1116

1117
  // find relations (add relation widgets)
1118
  const QList<QgsRelation> referencingRelations = rManager->referencedRelations( mFeatureLayerPair.layer() );
11✔
1119

1120
  for ( const QgsRelation &relation : referencingRelations )
15✔
1121
  {
1122
    std::unique_ptr<QgsAttributeEditorRelation> relationEditor = std::unique_ptr<QgsAttributeEditorRelation>( new QgsAttributeEditorRelation( relation, container ) );
4✔
1123
    if ( relationEditor->label().isEmpty() && relation.name().isEmpty() )
4✔
1124
    {
1125
      // relation does not have a name nor field have label, set label based on child layer
1126
      QString label = relation.referencingLayer()->name();
2✔
1127
      relationEditor->setLabel( label );
2✔
1128
    }
2✔
1129
    container->addChildElement( relationEditor.release() );
4✔
1130
  }
4✔
1131
}
11✔
1132

1133
bool AttributeController::isValidFormId( const QUuid &id ) const
580✔
1134
{
1135
  return mFormItems.contains( id );
580✔
1136
}
1137

1138
bool AttributeController::isValidTabId( int id ) const
52✔
1139
{
1140
  return ( id >= 0 ) && ( id < tabCount() );
52✔
1141
}
1142

1143
const FormItem *AttributeController::formItem( const QUuid &id ) const
486✔
1144
{
1145
  if ( isValidFormId( id ) )
486✔
1146
    return mFormItems[id].get();
486✔
1147
  else
1148
    return nullptr;
×
1149
}
1150

1151
const TabItem *AttributeController::tabItem( int tabRow ) const
48✔
1152
{
1153
  if ( isValidTabId( tabRow ) )
48✔
1154
    return mTabItems[tabRow].get();
47✔
1155
  else
1156
    return nullptr;
1✔
1157
}
1158

1159
bool AttributeController::setFormShouldRememberValue( const QUuid &id, bool shouldRememberValue )
×
1160
{
1161
  if ( !mRememberAttributesController )
×
1162
    return true; //noop
×
1163

1164
  if ( isValidFormId( id ) )
×
1165
  {
1166
    std::shared_ptr<FormItem> data = mFormItems[id];
×
1167
    bool changed = mRememberAttributesController->setShouldRememberValue( mFeatureLayerPair.layer(), data->fieldIndex(), shouldRememberValue );
×
1168
    if ( changed )
×
1169
    {
1170
      emit formDataChanged( id );
×
1171
    }
1172
    return true;
×
1173
  }
×
1174
  else
1175
  {
1176
    return false;
×
1177
  }
1178
}
1179

1180
bool AttributeController::formShouldRememberValue( int fieldIndex ) const
×
1181
{
1182
  const QgsFeature feat = mFeatureLayerPair.feature();
×
1183
  const QgsVectorLayer *layer = mFeatureLayerPair.layer();
×
1184

1185
  if ( !mRememberAttributesController )
×
1186
    return false;
×
1187

1188
  return mRememberAttributesController->shouldRememberValue( layer, fieldIndex );
×
1189
}
×
1190

1191
bool AttributeController::setFormValue( const QUuid &id, QVariant value )
94✔
1192
{
1193
  if ( isValidFormId( id ) )
94✔
1194
  {
1195
    std::shared_ptr<FormItem> item = mFormItems[id];
94✔
1196
    QgsField field = item->field();
94✔
1197
    QVariant val( value );
94✔
1198

1199
    item->setRawValue( val );
94✔
1200
    emit formDataChanged( item->id(), { AttributeFormModel::RawValue } );
94✔
1201

1202
    if ( !field.convertCompatible( val ) )
94✔
1203
    {
1204
      QString msg( tr( "Value \"%1\" %4 could not be converted to a compatible value for field %2(%3)." ).arg( value.toString(), field.name(), field.typeName(), value.isNull() ? "NULL" : "NOT NULL" ) );
34✔
1205
      QgsMessageLog::logMessage( msg );
17✔
1206
    }
17✔
1207
    else
1208
    {
1209
      mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), val );
77✔
1210
      emit formDataChanged( item->id(), { AttributeFormModel::AttributeValue, AttributeFormModel::RawValueIsNull } );
77✔
1211
    }
1212
    recalculateDerivedItems( true, false );
94✔
1213
    return true;
94✔
1214
  }
94✔
1215
  else
1216
  {
1217
    return false;
×
1218
  }
1219
}
1220

1221
QVariant AttributeController::formValue( int fieldIndex ) const
11✔
1222
{
1223
  const QgsFeature feat = mFeatureLayerPair.feature();
11✔
1224
  if ( !feat.isValid() ||
22✔
1225
       fieldIndex < 0 ||
22✔
1226
       fieldIndex >= feat.attributeCount()
11✔
1227
     )
1228
    return QVariant();
×
1229

1230
  return mFeatureLayerPair.feature().attribute( fieldIndex );
22✔
1231
}
11✔
1232

1233
AttributeController *AttributeController::parentController() const
×
1234
{
1235
  return mParentController;
×
1236
}
1237

1238
void AttributeController::setParentController( AttributeController *newParentController )
1✔
1239
{
1240
  if ( !newParentController || mParentController == newParentController )
1✔
1241
    return;
×
1242

1243
  mParentController = newParentController;
1✔
1244
  emit parentControllerChanged();
1✔
1245

1246
  prefillRelationReferenceField();
1✔
1247

1248
  if ( mParentController )
1✔
1249
  {
1250
    // When parent's feature Id is changed, we want to update the relation reference field
1251
    connect( mParentController, &AttributeController::featureIdChanged, this, &AttributeController::prefillRelationReferenceField );
1✔
1252
  }
1253
}
1254

1255
const QgsRelation &AttributeController::linkedRelation() const
×
1256
{
1257
  return mLinkedRelation;
×
1258
}
1259

1260
void AttributeController::setLinkedRelation( const QgsRelation &newLinkedRelation )
1✔
1261
{
1262
  if ( mLinkedRelation.id() == newLinkedRelation.id() )
1✔
1263
    return;
×
1264

1265
  mLinkedRelation = newLinkedRelation;
1✔
1266
  emit linkedRelationChanged();
1✔
1267

1268
  prefillRelationReferenceField();
1✔
1269
}
1270

1271
void AttributeController::onFeatureAdded( QgsFeatureId newFeatureId )
1✔
1272
{
1273
  QgsFeature f = mFeatureLayerPair.layer()->getFeature( newFeatureId );
1✔
1274
  setFeatureLayerPair( FeatureLayerPair( f, mFeatureLayerPair.layer() ) );
1✔
1275
  emit featureIdChanged();
1✔
1276
}
1✔
1277

1278
void AttributeController::renamePhotos()
1✔
1279
{
1280
  const QStringList photoNameFormat = QgsProject::instance()->entryList( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoNaming/%1" ).arg( mFeatureLayerPair.layer()->id() ) );
4✔
1281
  if ( photoNameFormat.isEmpty() )
1✔
1282
  {
1283
    return;
×
1284
  }
1285

1286
  QgsExpressionContext expressionContext = mFeatureLayerPair.layer()->createExpressionContext();
1✔
1287
  expressionContext << QgsExpressionContextUtils::formScope( mFeatureLayerPair.feature() );
1✔
1288
  if ( mVariablesManager )
1✔
1289
    expressionContext << mVariablesManager->positionScope();
×
1290

1291
  expressionContext.setFields( mFeatureLayerPair.feature().fields() );
1✔
1292
  expressionContext.setFeature( mFeatureLayerPair.featureRef() );
1✔
1293

1294
  // check for new photos
1295
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
1✔
1296
  while ( formItemsIterator != mFormItems.end() )
5✔
1297
  {
1298
    std::shared_ptr<FormItem> item = formItemsIterator.value();
4✔
1299
    if ( item->type() == FormItem::Field && item->editorWidgetType() == QStringLiteral( "ExternalResource" ) )
8✔
1300
    {
1301
      QVariantMap config = item->editorWidgetConfig();
1✔
1302
      const QgsField field = item->field();
1✔
1303
      if ( !photoNameFormat.contains( field.name() ) )
1✔
1304
      {
1305
        ++formItemsIterator;
×
1306
        continue;
×
1307
      }
1308

1309
      if ( item->originalValue() != mFeatureLayerPair.feature().attribute( item->fieldIndex() ) )
1✔
1310
      {
1311
        QString expString = QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoNaming/%1/%2" ).arg( mFeatureLayerPair.layer()->id() ).arg( field.name() ) );
4✔
1312
        QgsExpression exp( expString );
1✔
1313
        exp.prepare( &expressionContext );
1✔
1314
        if ( exp.hasParserError() )
1✔
1315
        {
1316
          CoreUtils::log( QStringLiteral( "Photo name format" ), QStringLiteral( "Expression for %1:%2 has parser error: %3" ).arg( mFeatureLayerPair.layer()->name() ).arg( field.name() ).arg( exp.parserErrorString() ) );
×
1317
          ++formItemsIterator;
×
1318
          continue;
×
1319
        }
1320

1321
        QVariant value = exp.evaluate( &expressionContext );
1✔
1322
        if ( exp.hasEvalError() )
1✔
1323
        {
1324
          CoreUtils::log( QStringLiteral( "Photo name format" ), QStringLiteral( "Expression for %1:%2 has evaluation error: %3" ).arg( mFeatureLayerPair.layer()->name() ).arg( field.name() ).arg( exp.evalErrorString() ) );
×
1325
          ++formItemsIterator;
×
1326
          continue;
×
1327
        }
1328

1329
        QVariant val( value );
1✔
1330
        if ( !field.convertCompatible( val ) )
1✔
1331
        {
1332
          CoreUtils::log( QStringLiteral( "Photo name format" ), QStringLiteral( "Value \"%1\" %4 could not be converted to a compatible value for field %2 (%3)." ).arg( value.toString() ).arg( field.name() ).arg( field.typeName() ).arg( value.isNull() ? "NULL" : "NOT NULL" ) );
×
1333
          ++formItemsIterator;
×
1334
          continue;
×
1335
        }
1336

1337
        const QString targetDir = InputUtils::resolveTargetDir( QgsProject::instance()->homePath(), config, mFeatureLayerPair, QgsProject::instance() );
1✔
1338
        const QString prefix = InputUtils::resolvePrefixForRelativePath( config[ QStringLiteral( "RelativeStorage" ) ].toInt(), QgsProject::instance()->homePath(), targetDir );
3✔
1339
        const QString src = InputUtils::getAbsolutePath( mFeatureLayerPair.feature().attribute( item->fieldIndex() ).toString(), prefix );
2✔
1340
        QFileInfo fi( src );
1✔
1341
        QString newName = QStringLiteral( "%1.%2" ).arg( val.toString() ).arg( fi.completeSuffix() );
2✔
1342
        const QString dst = InputUtils::getAbsolutePath( newName, prefix );
1✔
1343
        if ( InputUtils::renameFile( src, dst ) )
1✔
1344
        {
1345
          mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), newName );
1✔
1346
          expressionContext.setFeature( featureLayerPair().featureRef() );
1✔
1347
        }
1348
      }
1✔
1349
    }
1✔
1350

1351
    ++formItemsIterator;
4✔
1352
  }
4✔
1353
}
1✔
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