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

MerginMaps / input / 6689164577

30 Oct 2023 06:30AM UTC coverage: 62.209% (-0.03%) from 62.234%
6689164577

push

github

web-flow
Merge pull request #2881 from MerginMaps/fix-tracking-locale

Fix position tracking (0,0) coordinates on Android - fix locale when writing decimals

7480 of 12024 relevant lines covered (62.21%)

123.27 hits per line

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

76.78
/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 "qgsattributeeditorspacerelement.h"
31
#include "qgsattributeeditortextelement.h"
32
#include "qgsattributeeditorhtmlelement.h"
33
#include "qgsvectorlayereditbuffer.h"
34
#include "qgsexpressioncontextutils.h"
35
#include "qgsrelation.h"
36
#include "qgsmessagelog.h"
37
#include "inpututils.h"
38
#include "coreutils.h"
39

40
AttributeController::AttributeController( QObject *parent )
20✔
41
  : QObject( parent )
42
  , mAttributeTabProxyModel( new AttributeTabProxyModel() )
20✔
43
{
44
}
20✔
45

46
void AttributeController::reset()
×
47
{
48
  setFeatureLayerPair( FeatureLayerPair() );
×
49
}
×
50

51
AttributeController::~AttributeController() = default;
20✔
52

53

54
FeatureLayerPair AttributeController::featureLayerPair() const
994✔
55
{
56
  return mFeatureLayerPair;
994✔
57
}
58

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

68
    bool hasLayerChanged = mFeatureLayerPair.layer() != pair.layer();
25✔
69
    // Set new active pair
70
    mFeatureLayerPair = pair;
25✔
71
    if ( hasLayerChanged )
25✔
72
    {
73
      // layer changed!
74
      updateOnLayerChange();
19✔
75
    }
76

77
    // feature changed!
78
    updateOnFeatureChange();
25✔
79

80
    // Done, emit signals
81
    blockSignals( false );
25✔
82
    if ( hasLayerChanged )
25✔
83
    {
84
      emit attributeTabProxyModelChanged();
19✔
85
      emit hasTabsChanged();
19✔
86
    }
87
    emit featureLayerPairChanged();
25✔
88
    emit hasAnyChangesChanged();
25✔
89
    emit hasValidationErrorsChanged();
25✔
90
  }
91
}
25✔
92

93
QgsAttributeEditorContainer *AttributeController::autoLayoutTabContainer() const  //#spellok
11✔
94
{
95
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
11✔
96
  Q_ASSERT( layer );
11✔
97

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

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

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

121
void AttributeController::prefillRelationReferenceField()
2✔
122
{
123
  if ( !mParentController || !mLinkedRelation.isValid() )
2✔
124
    return;
1✔
125

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

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

158
  return !container->children().isEmpty();
1✔
159
}
160

161
VariablesManager *AttributeController::variablesManager() const
×
162
{
163
  return mVariablesManager;
×
164
}
165

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

175
RememberAttributesController *AttributeController::rememberAttributesController() const
×
176
{
177
  return mRememberAttributesController;
×
178
}
179

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

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

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

217
        flatten( container, parentTabRow, visibilityExpression, items );
6✔
218
        break;
6✔
219
      }
6✔
220

221
      case Qgis::AttributeEditorType::Field:
88✔
222
      {
223
        QUuid fieldUuid = QUuid::createUuid();
88✔
224

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

236
        if ( !expression.isEmpty() )
88✔
237
        {
238
          expressions << field.constraints().constraintExpression();
1✔
239
        }
240

241
        bool isReadOnly = ( layer->editFormConfig().readOnly( fieldIndex ) ) ||
264✔
242
                          ( !field.defaultValueDefinition().expression().isEmpty() && field.defaultValueDefinition().applyOnUpdate() );
176✔
243

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

261
        mFormItems[formItemData->id()] = formItemData;
88✔
262

263

264
        items.append( fieldUuid );
88✔
265
        break;
88✔
266
      }
88✔
267

268
      case Qgis::AttributeEditorType::Relation:
4✔
269
      {
270
        QUuid widgetUuid = QUuid::createUuid();
4✔
271

272
        QgsAttributeEditorRelation *relationField = static_cast<QgsAttributeEditorRelation *>( element );
4✔
273
        QgsRelation associatedRelation = relationField->relation();
4✔
274

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

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

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

300
        std::shared_ptr<FormItem> formItemData =
301
          std::shared_ptr<FormItem>(
302
            FormItem::createRelationItem(
303
              widgetUuid,
304
              groupName,
305
              parentTabRow,
306
              label,
307
              relationField->showLabel(),
4✔
308
              parentVisibilityExpressions, // relation field doesn't have visibility expression itself
309
              associatedRelation
310
            )
311
          );
8✔
312

313
        mFormItems[formItemData->id()] = formItemData;
4✔
314
        items.append( widgetUuid );
4✔
315

316
        break;
4✔
317
      }
8✔
318
      case Qgis::AttributeEditorType::SpacerElement:
4✔
319
      {
320
        QUuid fieldUuid = QUuid::createUuid();
4✔
321

322
        QgsAttributeEditorSpacerElement *spacerElement = static_cast<QgsAttributeEditorSpacerElement *>( element );
4✔
323

324
        const QString groupName = container->isGroupBox() ? container->name() : QString();
4✔
325
        std::shared_ptr<FormItem> formItemData =
326
          std::shared_ptr<FormItem>(
327
            FormItem::createSpacerItem(
328
              fieldUuid,
329
              groupName,
330
              parentTabRow,
331
              spacerElement->name(),
4✔
332
              spacerElement->drawLine(),
4✔
333
              parentVisibilityExpressions // spacer doesn't have visibility expression itself
334
            )
335
          );
8✔
336

337
        mFormItems[formItemData->id()] = formItemData;
4✔
338

339

340
        items.append( fieldUuid );
4✔
341
        break;
4✔
342
      }
4✔
343
      case Qgis::AttributeEditorType::TextElement:
2✔
344
      {
345
        QUuid fieldUuid = QUuid::createUuid();
2✔
346

347
        QgsAttributeEditorTextElement *textElement = static_cast<QgsAttributeEditorTextElement *>( element );
2✔
348

349

350
        const QString groupName = container->isGroupBox() ? container->name() : QString();
2✔
351
        std::shared_ptr<FormItem> formItemData =
352
          std::shared_ptr<FormItem>(
353
            FormItem::createRichTextItem(
354
              fieldUuid,
355
              groupName,
356
              parentTabRow,
357
              textElement->name(),
2✔
358
              textElement->showLabel(),
2✔
359
              textElement->text(),
4✔
360
              false,
361
              parentVisibilityExpressions // text doesn't have visibility expression itself
362
            )
363
          );
4✔
364

365
        mFormItems[formItemData->id()] = formItemData;
2✔
366

367
        items.append( fieldUuid );
2✔
368
        break;
2✔
369
      }
2✔
370
      case Qgis::AttributeEditorType::HtmlElement:
2✔
371
      {
372
        QUuid fieldUuid = QUuid::createUuid();
2✔
373

374
        QgsAttributeEditorHtmlElement *htmlElement = static_cast<QgsAttributeEditorHtmlElement *>( element );
2✔
375

376

377
        const QString groupName = container->isGroupBox() ? container->name() : QString();
2✔
378
        std::shared_ptr<FormItem> formItemData =
379
          std::shared_ptr<FormItem>(
380
            FormItem::createRichTextItem(
381
              fieldUuid,
382
              groupName,
383
              parentTabRow,
384
              htmlElement->name(),
2✔
385
              htmlElement->showLabel(),
2✔
386
              htmlElement->htmlCode(),
4✔
387
              true,
388
              parentVisibilityExpressions // text doesn't have visibility expression itself
389
            )
390
          );
4✔
391

392
        mFormItems[formItemData->id()] = formItemData;
2✔
393

394
        items.append( fieldUuid );
2✔
395
        break;
2✔
396
      }
2✔
397

398
      default:
×
399
        // Invalid, Action, QmlElement
400
        // are not supported at the moment
401
        break;
×
402
    }
403
  }
26✔
404
}
26✔
405

406
void AttributeController::createTab( QgsAttributeEditorContainer *container )
20✔
407
{
408
  Q_ASSERT( container );
20✔
409

410
  int tabRow = mTabItems.size();
20✔
411

412
  QgsExpression expr;
20✔
413
  if ( container->visibilityExpression().enabled() )
20✔
414
  {
415
    expr = container->visibilityExpression().data();
×
416
  }
417

418
  QVector<QUuid> formItemsUuids;
20✔
419
  flatten( container, tabRow, QString(), formItemsUuids );
20✔
420

421
  std::shared_ptr<TabItem> tabItem( new TabItem( tabRow,
422
                                    container->name(),
20✔
423
                                    formItemsUuids,
424
                                    expr )
20✔
425
                                  );
20✔
426

427
  mTabItems.push_back( tabItem );
20✔
428
}
20✔
429

430
void AttributeController::clearAll()
19✔
431
{
432
  mAttributeFormProxyModelForTabItem.clear();
19✔
433
  mAttributeTabProxyModel.reset( new AttributeTabProxyModel() );
19✔
434
  setHasValidationErrors( false );
19✔
435
  mFormItems.clear();
19✔
436
  mTabItems.clear();
19✔
437
  mExpressionFieldsOutsideForm.clear();
19✔
438
  mHasTabs = false;
19✔
439
}
19✔
440

441
void AttributeController::updateOnLayerChange()
19✔
442
{
443
  clearAll();
19✔
444

445
  // 1) DATA
446
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
19✔
447
  if ( layer )
19✔
448
  {
449
    if ( layer->editFormConfig().layout() == Qgis::AttributeFormLayout::DragAndDrop )
19✔
450
    {
451
      QgsAttributeEditorContainer *root = layer->editFormConfig().invisibleRootContainer();
8✔
452
      if ( root->columnCount() > 1 )
8✔
453
      {
454
        qDebug() << "root tab in manual config has multiple columns. not supported on mobile devices!";
×
455
        root->setColumnCount( 1 );
×
456
      }
457

458
      mHasTabs = allowTabs( root );
8✔
459
      if ( mHasTabs )
8✔
460
      {
461
        for ( QgsAttributeEditorElement *element : root->children() )
3✔
462
        {
463
          if ( element->type() == Qgis::AttributeEditorType::Container )
2✔
464
          {
465
            QgsAttributeEditorContainer *container = static_cast<QgsAttributeEditorContainer *>( element );
2✔
466
            if ( container->columnCount() > 1 )
2✔
467
            {
468
              qDebug() << "tab " << container->name() << " in manual config has multiple columns. not supported on mobile devices!";
×
469
              container->setColumnCount( 1 );
×
470
            }
471
            createTab( container );
2✔
472
          }
473
        }
1✔
474
      }
475
      else
476
      {
477
        createTab( root );
7✔
478
      }
479
    }
480
    else
481
    {
482
      // Auto-Generated Layout
483
      // We create fake root tab
484
      QgsAttributeEditorContainer *tab = autoLayoutTabContainer();
11✔
485

486
      // We need to look for relations and include them into form,
487
      // in auto-generated layout they are not included in form config
488
      discoverRelations( tab );
11✔
489

490
      createTab( tab );
11✔
491
    }
492

493
    if ( mRememberAttributesController )
19✔
494
      mRememberAttributesController->storeLayerFields( layer );
×
495
  }
496

497
  // 2) MODELS
498
  // for all other models, ownership is managed by Qt parent system
499
  AttributeTabModel *tabModel = new AttributeTabModel( mAttributeTabProxyModel.get(), this, mTabItems.size() );
19✔
500
  mAttributeTabProxyModel->setSourceModel( tabModel );
19✔
501
  mAttributeFormProxyModelForTabItem.resize( mTabItems.size() );
19✔
502
  QVector<std::shared_ptr<TabItem>>::iterator tabItemsIterator = mTabItems.begin();
19✔
503
  while ( tabItemsIterator != mTabItems.end() )
39✔
504
  {
505
    std::shared_ptr<TabItem> item = *tabItemsIterator;
20✔
506
    const QVector<QUuid> &formItems = item->formItems();
20✔
507

508
    AttributeFormProxyModel *proxyFormModel = new AttributeFormProxyModel( mAttributeTabProxyModel.get() );
20✔
509
    AttributeFormModel *formModel = new AttributeFormModel( mAttributeTabProxyModel.get(), this, formItems );
20✔
510
    proxyFormModel->setSourceModel( formModel );
20✔
511
    mAttributeFormProxyModelForTabItem[item->tabIndex()] = proxyFormModel;
20✔
512
    ++tabItemsIterator;
20✔
513
  }
20✔
514

515
  // collect fields which have default value expression and are not in the form
516
  QSet<int> fieldIndexes;
19✔
517
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
19✔
518
  while ( formItemsIterator != mFormItems.end() )
119✔
519
  {
520
    std::shared_ptr<FormItem> item = formItemsIterator.value();
100✔
521
    if ( item->type() == FormItem::Field )
100✔
522
    {
523
      fieldIndexes << item->fieldIndex();
88✔
524
    }
525

526
    ++formItemsIterator;
100✔
527
  }
100✔
528

529
  for ( int i = 0; i < mFeatureLayerPair.layer()->fields().count(); i++ )
113✔
530
  {
531
    if ( !fieldIndexes.contains( i ) )
94✔
532
    {
533
      QgsField f = mFeatureLayerPair.layer()->fields().at( i );
6✔
534
      const QgsDefaultValue defaultDefinition = f.defaultValueDefinition();
6✔
535

536
      if ( !defaultDefinition.expression().isEmpty() )
6✔
537
      {
538
        mExpressionFieldsOutsideForm << i;
4✔
539
      }
540
    }
6✔
541
  }
542

543
}
19✔
544

545
void AttributeController::updateOnFeatureChange()
25✔
546
{
547
  const QgsFeature feature = mFeatureLayerPair.feature();
25✔
548

549
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
25✔
550
  while ( formItemsIterator != mFormItems.end() )
167✔
551
  {
552
    std::shared_ptr<FormItem> itemData = formItemsIterator.value();
142✔
553
    if ( itemData->type() == FormItem::Field )
142✔
554
    {
555
      int fieldIndex = itemData->fieldIndex();
129✔
556
      const QVariant newVal = feature.attribute( fieldIndex );
129✔
557
      mFormItems[itemData->id()]->setOriginalValue( newVal );
129✔
558
      mFormItems[itemData->id()]->setRawValue( newVal ); // we need to set raw value as well, as we use it in form now
129✔
559
      if ( mRememberAttributesController && isNewFeature() ) // this is a new feature
129✔
560
      {
561
        QVariant rememberedValue;
×
562
        bool shouldUseRememberedValue = mRememberAttributesController->rememberedValue(
×
563
                                          mFeatureLayerPair.layer(),
×
564
                                          fieldIndex,
565
                                          rememberedValue
566
                                        );
567
        if ( shouldUseRememberedValue )
×
568
        {
569
          mFeatureLayerPair.featureRef().setAttribute( fieldIndex, rememberedValue );
×
570
          itemData->setRawValue( rememberedValue );
×
571
        }
572
      }
×
573
    }
129✔
574
    ++formItemsIterator;
142✔
575
  }
142✔
576

577
  bool formValueChange = false;
25✔
578
  // if feature geometry was changed we also need recalculate defaults
579
  // as some attributes may contain expressions which use feature geometry
580
  if ( mFeatureLayerPair.layer()->isEditable() )
25✔
581
  {
582
    formValueChange = mFeatureLayerPair.layer()->editBuffer()->changedGeometries().contains( feature.id() );
1✔
583
  }
584
  recalculateDerivedItems( formValueChange, isNewFeature() );
25✔
585
}
25✔
586

587
bool AttributeController::isNewFeature() const
142✔
588
{
589
  QgsFeatureId id = mFeatureLayerPair.feature().id();
142✔
590
  return FID_IS_NEW( id ) || FID_IS_NULL( id );
142✔
591
}
592

593
void AttributeController::acquireId()
×
594
{
595
  if ( !mFeatureLayerPair.layer() )
×
596
    return;
×
597

598
  QgsFeature feat = mFeatureLayerPair.feature();
×
599
  bool featureIsNotYetAdded = FID_IS_NULL( feat.id() );
×
600

601
  startEditing();
×
602

603
  if ( featureIsNotYetAdded )
×
604
  {
605
    if ( !mFeatureLayerPair.layer()->addFeature( feat ) )
×
606
    {
607
      QgsMessageLog::logMessage( tr( "Feature could not be added" ),
×
608
                                 QStringLiteral( "Input" ),
×
609
                                 Qgis::Critical );
610

611
    }
612
  }
613
  else
614
  {
615
    if ( !mFeatureLayerPair.layer()->updateFeature( feat ) )
×
616
      QgsMessageLog::logMessage( tr( "Cannot update feature" ),
×
617
                                 QStringLiteral( "Input" ),
×
618
                                 Qgis::Warning );
619
  }
620

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

623
  if ( !commit() )
×
624
  {
625
    emit commitFailed();
×
626
  }
627

628
  disconnect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
×
629
}
×
630

631
bool AttributeController::recalculateDefaultValues(
126✔
632
  QSet<QUuid> &changedFormItems,
633
  QgsExpressionContext &expressionContext,
634
  bool isFormValueChange,
635
  bool isFirstUpdateOfNewFeature
636
)
637
{
638
  // update default values for fields which are not in the form
639
  for ( const int idx : mExpressionFieldsOutsideForm )
155✔
640
  {
641
    QgsField f = mFeatureLayerPair.layer()->fields().at( idx );
29✔
642
    const QgsDefaultValue defaultDefinition = f.defaultValueDefinition();
29✔
643

644
    bool shouldApplyDefaultValue =
645
      !defaultDefinition.expression().isEmpty() &&
77✔
646
      ( isFirstUpdateOfNewFeature || ( isFormValueChange && defaultDefinition.applyOnUpdate() ) );
48✔
647

648
    if ( shouldApplyDefaultValue )
29✔
649
    {
650
      QgsExpression exp( defaultDefinition.expression() );
15✔
651
      exp.prepare( &expressionContext );
15✔
652
      if ( exp.hasParserError() )
15✔
653
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg(
×
654
                                     mFeatureLayerPair.layer()->name(),
×
655
                                     f.name(),
×
656
                                     exp.parserErrorString() ),
×
657
                                   QStringLiteral( "Input" ),
×
658
                                   Qgis::Warning );
659

660
      QVariant value = exp.evaluate( &expressionContext );
15✔
661

662
      if ( exp.hasEvalError() )
15✔
663
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg(
×
664
                                     mFeatureLayerPair.layer()->name(),
×
665
                                     f.name(),
×
666
                                     exp.evalErrorString() ),
×
667
                                   QStringLiteral( "Input" ),
×
668
                                   Qgis::Warning );
669
      else
670
      {
671
        QVariant val( value );
15✔
672
        if ( !f.convertCompatible( val ) )
15✔
673
        {
674
          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" ) );
×
675
          QgsMessageLog::logMessage( msg );
×
676
        }
×
677
        else
678
        {
679
          mFeatureLayerPair.featureRef().setAttribute( idx, val );
15✔
680
          expressionContext.setFeature( featureLayerPair().featureRef() );
15✔
681
        }
682
      }
15✔
683
    }
15✔
684
  }
29✔
685

686
  // evaluate default values for fields in the form
687
  bool hasChanges = false;
126✔
688
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
126✔
689
  while ( formItemsIterator != mFormItems.end() )
1,040✔
690
  {
691
    std::shared_ptr<FormItem> item = formItemsIterator.value();
914✔
692
    const QgsField field = item->field();
914✔
693
    const QgsDefaultValue defaultDefinition = field.defaultValueDefinition();
914✔
694

695
    bool shouldApplyDefaultValue =
696
      !defaultDefinition.expression().isEmpty() &&
1,877✔
697
      ( isFirstUpdateOfNewFeature || ( isFormValueChange && defaultDefinition.applyOnUpdate() ) );
963✔
698

699
    if ( shouldApplyDefaultValue )
914✔
700
    {
701
      QgsExpression exp( field.defaultValueDefinition().expression() );
132✔
702
      exp.prepare( &expressionContext );
66✔
703
      if ( exp.hasParserError() )
66✔
704
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has parser error: %3" ).arg(
×
705
                                     mFeatureLayerPair.layer()->name(),
×
706
                                     field.name(),
×
707
                                     exp.parserErrorString() ),
×
708
                                   QStringLiteral( "Input" ),
×
709
                                   Qgis::Warning );
710

711
      QVariant value = exp.evaluate( &expressionContext );
66✔
712

713
      if ( exp.hasEvalError() )
66✔
714
        QgsMessageLog::logMessage( tr( "Default value expression for %1:%2 has evaluation error: %3" ).arg(
×
715
                                     mFeatureLayerPair.layer()->name(),
×
716
                                     field.name(),
×
717
                                     exp.evalErrorString() ),
×
718
                                   QStringLiteral( "Input" ),
×
719
                                   Qgis::Warning );
720
      else
721
      {
722
        QVariant val( value );
66✔
723
        if ( !field.convertCompatible( val ) )
66✔
724
        {
725
          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" ) );
×
726
          QgsMessageLog::logMessage( msg );
×
727
        }
×
728
        else
729
        {
730
          QVariant oldVal = mFeatureLayerPair.feature().attribute( item->fieldIndex() );
66✔
731

732
          if ( val != oldVal )
66✔
733
          {
734
            mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), val );
16✔
735
            item->setRawValue( val );
16✔
736
            emit formDataChanged( item->id(), { AttributeFormModel::RawValue, AttributeFormModel::RawValueIsNull } );
16✔
737
            // Update also expression context after an attribute change
738
            expressionContext.setFeature( featureLayerPair().featureRef() );
16✔
739
            changedFormItems.insert( item->id() );
16✔
740
            hasChanges = true;
16✔
741
          }
742
        }
66✔
743
      }
66✔
744
    }
66✔
745

746
    if ( isFirstUpdateOfNewFeature )
914✔
747
    {
748
      // check if item is a slider, if so and it does not have default value,
749
      // set it's initial value from NULL to zero or minimum
750
      bool isSlider = item->editorWidgetType() == QStringLiteral( "Range" ) &&
339✔
751
                      item->editorWidgetConfig().value( QStringLiteral( "Style" ) ) == QStringLiteral( "Slider" );
128✔
752
      bool hasDefaultValueDefinition = !defaultDefinition.expression().isEmpty();
110✔
753

754
      if ( isSlider && !hasDefaultValueDefinition )
110✔
755
      {
756
        double min = item->editorWidgetConfig().value( "Min" ).toDouble();
1✔
757
        double max = item->editorWidgetConfig().value( "Max" ).toDouble();
1✔
758
        double valueToSet = 0;
1✔
759

760
        if ( !( min <= valueToSet && valueToSet <= max ) )
1✔
761
        {
762
          valueToSet = qMin( min, max );
×
763
        }
764

765
        mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), valueToSet );
1✔
766
        item->setRawValue( valueToSet );
1✔
767
        emit formDataChanged( item->id(), { AttributeFormModel::RawValue } );
1✔
768
        changedFormItems.insert( item->id() );
1✔
769
      }
770
    }
771

772
    ++formItemsIterator;
914✔
773
  }
914✔
774
  return hasChanges;
126✔
775
}
776

777
void AttributeController::recalculateDerivedItems( bool isFormValueChange, bool isFirstUpdateOfNewFeature )
120✔
778
{
779
  QSet<QUuid> changedFormItems;
120✔
780

781
  QgsVectorLayer *layer = mFeatureLayerPair.layer();
120✔
782
  if ( !layer || !layer->isValid() )
120✔
783
    return;
×
784

785
  if ( !mFeatureLayerPair.feature().isValid() )
120✔
786
    return;
4✔
787

788
  // Create context
789
  QgsFields fields = mFeatureLayerPair.feature().fields();
116✔
790
  QgsExpressionContext expressionContext = layer->createExpressionContext();
116✔
791
  expressionContext << QgsExpressionContextUtils::formScope( mFeatureLayerPair.feature() );
116✔
792
  if ( mVariablesManager )
116✔
793
    expressionContext << mVariablesManager->positionScope();
×
794

795
  expressionContext.setFields( fields );
116✔
796
  expressionContext.setFeature( featureLayerPair().featureRef() );
116✔
797

798
  // Evaluate default values
799
  // it could be recursive, so
800
  // let say try few times
801
  const int LIMIT = 3;
116✔
802
  int tryNumber = 0;
116✔
803
  bool anyValueChanged = true;
116✔
804
  while ( anyValueChanged && tryNumber < LIMIT )
242✔
805
  {
806
    anyValueChanged = recalculateDefaultValues( changedFormItems, expressionContext, isFormValueChange, isFirstUpdateOfNewFeature );
126✔
807
    ++tryNumber;
126✔
808
  }
809
  if ( anyValueChanged )
116✔
810
  {
811
    // ok we cut the loop on limit...
812
    qDebug() << "Evaluation of default values was not finished in " << LIMIT << " tries. Giving up, sorry!";
×
813
  }
814

815
  // Evaluate HTML and Text element expressions
816
  recalculateRichTextWidgets( changedFormItems, expressionContext );
116✔
817

818
  // Evaluate tab items visiblity
819
  {
820
    QVector<std::shared_ptr<TabItem>>::iterator tabItemsIterator = mTabItems.begin();
116✔
821
    while ( tabItemsIterator != mTabItems.end() )
234✔
822
    {
823
      std::shared_ptr<TabItem> item = *tabItemsIterator;
118✔
824
      QgsExpression exp = item->visibilityExpression();
118✔
825
      exp.prepare( &expressionContext );
118✔
826
      bool visible = true;
118✔
827
      if ( exp.isValid() )
118✔
828
      {
829
        visible = exp.evaluate( &expressionContext ).toBool();
×
830
      }
831

832
      if ( item->isVisible() != visible )
118✔
833
      {
834
        item->setVisible( visible );
18✔
835
        emit tabDataChanged( item->tabIndex() );
18✔
836
      }
837
      ++tabItemsIterator;
118✔
838
    }
118✔
839
  }
840

841
  // Evaluate form items visibility
842
  {
843
    QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
116✔
844
    while ( formItemsIterator != mFormItems.end() )
948✔
845
    {
846
      std::shared_ptr<FormItem> item = formItemsIterator.value();
832✔
847
      bool visible = true;
832✔
848
      if ( item->editorWidgetType() == QLatin1String( "Hidden" ) )
832✔
849
      {
850
        visible = false;
8✔
851
      }
852
      else
853
      {
854
        QgsExpression exp = item->visibilityExpression();
824✔
855
        exp.prepare( &expressionContext );
824✔
856

857
        if ( exp.isValid() )
824✔
858
          visible = exp.evaluate( &expressionContext ).toInt();
×
859
      }
824✔
860

861
      if ( item->visible() != visible )
832✔
862
      {
863
        item->setVisible( visible );
90✔
864
        changedFormItems << item->id();
90✔
865
      }
866
      ++formItemsIterator;
832✔
867
    }
832✔
868
  }
869

870
  // Evaluate form items value state - hard/soft constraints, value validity
871
  {
872
    bool containsValidationError = false;
116✔
873
    {
874
      QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
116✔
875
      while ( formItemsIterator != mFormItems.end() )
948✔
876
      {
877
        std::shared_ptr<FormItem> item = formItemsIterator.value();
832✔
878
        if ( item->type() == FormItem::Field )
832✔
879
        {
880
          QString validationMessage;
809✔
881
          FieldValidator::ValidationStatus validationStatus;
882

883
          validationStatus = FieldValidator::validate( featureLayerPair(), *item, validationMessage );
809✔
884

885
          if ( validationStatus == FieldValidator::Error )
809✔
886
          {
887
            containsValidationError = true;
109✔
888
          }
889

890
          if ( validationMessage != item->validationMessage() )
809✔
891
          {
892
            item->setValidationStatus( validationStatus );
50✔
893
            item->setValidationMessage( validationMessage );
50✔
894
            changedFormItems.insert( item->id() );
50✔
895
          }
896
        }
809✔
897
        ++formItemsIterator;
832✔
898
      }
832✔
899
    }
900
    setHasValidationErrors( containsValidationError );
116✔
901
  }
902

903
  // Check if we have any changes
904
  bool anyChanges = isNewFeature();
116✔
905
  if ( !anyChanges )
116✔
906
  {
907
    QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
13✔
908
    while ( formItemsIterator != mFormItems.end() )
75✔
909
    {
910
      std::shared_ptr<FormItem> item = formItemsIterator.value();
63✔
911
      if ( item->type() == FormItem::Field )
63✔
912
      {
913
        if ( item->originalValue() != mFeatureLayerPair.feature().attribute( item->fieldIndex() ) )
61✔
914
        {
915
          anyChanges = true;
1✔
916
          break;
1✔
917
        }
918
      }
919

920
      ++formItemsIterator;
62✔
921
    }
63✔
922
  }
923
  setHasAnyChanges( anyChanges );
116✔
924

925
  // Emit all signals
926
  QSet<QUuid>::const_iterator i = changedFormItems.constBegin();
116✔
927
  while ( i != changedFormItems.constEnd() )
263✔
928
  {
929
    emit formDataChanged( *i );
147✔
930
    ++i;
147✔
931
  }
932

933
  if ( isFormValueChange )
116✔
934
    emit formRecalculated();
95✔
935
}
120✔
936

937
void AttributeController::recalculateRichTextWidgets( QSet<QUuid> &changedFormItems, QgsExpressionContext &context )
116✔
938
{
939
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
116✔
940
  while ( formItemsIterator != mFormItems.end() )
948✔
941
  {
942
    std::shared_ptr<FormItem> itemData = formItemsIterator.value();
832✔
943
    if ( itemData->type() == FormItem::RichText )
832✔
944
    {
945
      QString newValue;
10✔
946
      QString definition = itemData->editorWidgetConfig().value( QStringLiteral( "Definition" ) ).toString();
30✔
947
      bool isHTML = itemData->editorWidgetConfig().value( QStringLiteral( "UseHtml" ) ).toBool();
20✔
948
      if ( isHTML )
10✔
949
      {
950
        // evaluate texts like: <script>document.write(expression.evaluate("\TextField\""));</script>
951

952
        // QML Text does not support document.write, so just remove it
953
        const thread_local QRegularExpression sRegEx1( "<script>\\s*document\\.write\\(\\s*(.*)\\s*\\)\\s*;\\s*</script>", QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption );
5✔
954
        QRegularExpressionMatch match1 = sRegEx1.match( definition );
5✔
955
        while ( match1.hasMatch() )
10✔
956
        {
957
          QString expression = match1.captured( 1 );
5✔
958
          definition = QStringLiteral( "<span>%1</span>" ).arg( definition.mid( 0, match1.capturedStart( 0 ) ) + expression + definition.mid( match1.capturedEnd( 0 ) ) );
5✔
959
          match1 = sRegEx1.match( definition );
5✔
960
        }
5✔
961

962
        // Not evaluate expression with the engine
963
        const thread_local QRegularExpression sRegEx( "expression\\.evaluate\\(\\s*\\\"(.*?[^\\\\])\\\"\\s*\\)", QRegularExpression::MultilineOption | QRegularExpression::DotMatchesEverythingOption );
5✔
964
        QRegularExpressionMatch match = sRegEx.match( definition );
5✔
965
        while ( match.hasMatch() )
10✔
966
        {
967
          QString expression = match.captured( 1 );
5✔
968
          expression = expression.replace( QStringLiteral( "\\\"" ), QStringLiteral( "\"" ) );
10✔
969

970
          QgsExpression exp = QgsExpression( expression );
5✔
971
          exp.prepare( &context );
5✔
972
          QString resultString = exp.evaluate( &context ).toString();
5✔
973
          definition = definition.mid( 0, match.capturedStart( 0 ) ) + resultString + definition.mid( match.capturedEnd( 0 ) );
5✔
974
          match = sRegEx.match( definition );
5✔
975
        }
5✔
976
        newValue = definition;
5✔
977
      }
5✔
978
      else
979
      {
980
        newValue = QgsExpression::replaceExpressionText( definition, &context );
5✔
981
      }
982
      if ( itemData->rawValue() != newValue )
10✔
983
      {
984
        changedFormItems.insert( itemData->id() );
8✔
985
        itemData->setRawValue( newValue );
8✔
986
      }
987
    }
10✔
988
    ++formItemsIterator;
832✔
989
  }
832✔
990

991
}
116✔
992

993
bool AttributeController::hasValidationErrors() const
11✔
994
{
995
  return mHasValidationErrors;
11✔
996
}
997

998
bool AttributeController::hasTabs() const
5✔
999
{
1000
  return mHasTabs;
5✔
1001
}
1002

1003
AttributeTabProxyModel *AttributeController::attributeTabProxyModel() const
4✔
1004
{
1005
  Q_ASSERT( mAttributeTabProxyModel );
4✔
1006
  return mAttributeTabProxyModel.get();
4✔
1007
}
1008

1009
int AttributeController::tabCount() const
56✔
1010
{
1011
  return mTabItems.size();
56✔
1012
}
1013

1014
AttributeFormProxyModel *AttributeController::attributeFormProxyModelForTab( int tabRow ) const
4✔
1015
{
1016
  if ( isValidTabId( tabRow ) )
4✔
1017
  {
1018
    return mAttributeFormProxyModelForTabItem[tabRow];
4✔
1019
  }
1020
  else
1021
  {
1022
    return nullptr;
×
1023
  }
1024
}
1025

1026
bool AttributeController::deleteFeature()
×
1027
{
1028
  if ( !mFeatureLayerPair.layer() )
×
1029
    return false;
×
1030

1031
  bool rv = true;
×
1032

1033
  if ( !startEditing() )
×
1034
  {
1035
    rv = false;
×
1036
  }
1037

1038
  bool isDeleted = mFeatureLayerPair.layer()->deleteFeature( mFeatureLayerPair.feature().id() );
×
1039
  rv = commit();
×
1040

1041
  if ( !isDeleted || !rv )
×
1042
  {
1043
    QgsMessageLog::logMessage( tr( "Cannot delete feature" ),
×
1044
                               QStringLiteral( "Input" ),
×
1045
                               Qgis::Warning );
1046
    emit commitFailed();
×
1047
  }
1048
  else
1049
  {
1050
    mFeatureLayerPair = FeatureLayerPair();
×
1051
    emit featureLayerPairChanged();
×
1052
    emit changesCommited();
×
1053
  }
1054

1055
  return rv;
×
1056
}
1057

1058
bool AttributeController::rollback()
×
1059
{
1060
  if ( !mFeatureLayerPair.layer() )
×
1061
    return false;
×
1062

1063
  if ( !mFeatureLayerPair.layer()->isEditable() )
×
1064
  {
1065
    return false;
×
1066
  }
1067

1068
  if ( !mFeatureLayerPair.layer()->rollBack() )
×
1069
  {
1070
    CoreUtils::log( QStringLiteral( "Attribute Controller" ), QStringLiteral( "Could not rollback the changes in form" ) );
×
1071
  }
1072

1073
  mFeatureLayerPair.layer()->triggerRepaint();
×
1074
  return true;
×
1075
}
1076

1077
bool AttributeController::save()
1✔
1078
{
1079
  if ( !mFeatureLayerPair.layer() )
1✔
1080
    return false;
×
1081

1082
  renamePhotos();
1✔
1083

1084
  if ( !startEditing() )
1✔
1085
  {
1086
    return false;
×
1087
  }
1088

1089
  bool rv = true;
1✔
1090

1091
  QgsFeature feat = mFeatureLayerPair.feature();
1✔
1092

1093
  bool featureIsNotYetAdded = FID_IS_NULL( feat.id() );
1✔
1094

1095
  if ( featureIsNotYetAdded )
1✔
1096
  {
1097
    if ( !mFeatureLayerPair.layer()->addFeature( feat ) )
1✔
1098
    {
1099
      QgsMessageLog::logMessage( tr( "Feature could not be added" ),
×
1100
                                 QStringLiteral( "Input" ),
×
1101
                                 Qgis::Critical );
1102

1103
    }
1104
    connect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
1✔
1105
  }
1106
  else
1107
  {
1108
    // update it instead of adding
1109
    if ( !mFeatureLayerPair.layer()->updateFeature( feat, true ) )
×
1110
      QgsMessageLog::logMessage( tr( "Cannot update feature" ),
×
1111
                                 QStringLiteral( "Input" ),
×
1112
                                 Qgis::Warning );
1113
  }
1114

1115
  bool featureIsNew = isNewFeature();
1✔
1116

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

1121
  if ( rv )
1✔
1122
  {
1123
    emit changesCommited();
1✔
1124
  }
1125
  else
1126
  {
1127
    emit commitFailed();
×
1128
  }
1129

1130
  if ( featureIsNotYetAdded )
1✔
1131
  {
1132
    disconnect( mFeatureLayerPair.layer(), &QgsVectorLayer::featureAdded, this, &AttributeController::onFeatureAdded );
1✔
1133
  }
1134

1135
  // Store the feature attributes for future use
1136
  if ( featureIsNew && mRememberAttributesController )
1✔
1137
  {
1138
    mRememberAttributesController->storeFeature( mFeatureLayerPair );
×
1139
  }
1140

1141
  return rv;
1✔
1142
}
1✔
1143

1144
bool AttributeController::startEditing()
1✔
1145
{
1146
  Q_ASSERT( mFeatureLayerPair.layer() );
1✔
1147

1148
  // Already an edit session active
1149
  if ( mFeatureLayerPair.layer()->editBuffer() )
1✔
1150
    return true;
×
1151

1152
  if ( !mFeatureLayerPair.layer()->startEditing() )
1✔
1153
  {
1154
    QgsMessageLog::logMessage( tr( "Cannot start editing" ),
×
1155
                               QStringLiteral( "Input" ),
×
1156
                               Qgis::Warning );
1157
    return false;
×
1158
  }
1159
  else
1160
  {
1161
    return true;
1✔
1162
  }
1163
}
1164

1165
bool AttributeController::commit()
1✔
1166
{
1167
  Q_ASSERT( mFeatureLayerPair.layer() );
1✔
1168

1169

1170
  if ( !mFeatureLayerPair.layer()->commitChanges() )
1✔
1171
  {
1172
    CoreUtils::log( QStringLiteral( "CommitChanges" ),
×
1173
                    QStringLiteral( "Failed to commit changes:\n%1" )
×
1174
                    .arg( mFeatureLayerPair.layer()->commitErrors().join( QLatin1Char( '\n' ) ) ) );
×
1175
    mFeatureLayerPair.layer()->rollBack();
×
1176
    return false;
×
1177
  }
1178
  else
1179
  {
1180
    mFeatureLayerPair.layer()->triggerRepaint();
1✔
1181
    return true;
1✔
1182
  }
1183
}
1184

1185
bool AttributeController::hasAnyChanges() const
×
1186
{
1187
  return mHasAnyChanges;
×
1188
}
1189

1190
void AttributeController::setHasAnyChanges( bool hasChanges )
116✔
1191
{
1192
  if ( hasChanges != mHasAnyChanges )
116✔
1193
  {
1194
    mHasAnyChanges = hasChanges;
11✔
1195
    emit hasAnyChangesChanged();
11✔
1196
  }
1197
}
116✔
1198

1199
void AttributeController::setHasValidationErrors( bool hasErrors )
135✔
1200
{
1201
  if ( mHasValidationErrors != hasErrors )
135✔
1202
  {
1203
    mHasValidationErrors = hasErrors;
23✔
1204
    emit hasValidationErrorsChanged();
23✔
1205
  }
1206
}
135✔
1207

1208
void AttributeController::discoverRelations( QgsAttributeEditorContainer *container )
11✔
1209
{
1210
  QgsRelationManager *rManager = QgsProject::instance()->relationManager();
11✔
1211

1212
  if ( !rManager || !mFeatureLayerPair.layer() )
11✔
1213
    return;
×
1214

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

1218
  for ( const QgsRelation &relation : childRelations )
15✔
1219
  {
1220
    for ( QgsAttributeEditorElement *child : container->children() )
18✔
1221
    {
1222
      if ( child->name() == relation.fieldPairs().at( 0 ).first ) // we are using only one fieldPair!
14✔
1223
      {
1224
        QgsAttributeEditorField *editorField = static_cast<QgsAttributeEditorField *>( child );
4✔
1225

1226
        if ( !editorField )
4✔
1227
          continue;
×
1228

1229
        int fieldIdx = editorField->idx();
4✔
1230
        QgsField field = mFeatureLayerPair.layer()->fields().at( fieldIdx );
4✔
1231

1232
        QVariantMap config = mFeatureLayerPair.layer()->editorWidgetSetup( fieldIdx ).config();
4✔
1233

1234
        // relation reference fields in autogenerated fields might have an empty config, we need to set needed properties here
1235
        if ( config.isEmpty() )
4✔
1236
        {
1237
          QVariantMap neededArgs =
1238
          {
1239
            { QStringLiteral( "ReferencedLayerId" ), relation.referencedLayerId() },
2✔
1240
            { QStringLiteral( "Relation" ), relation.id() }
2✔
1241
          };
5✔
1242

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

1246
          CoreUtils::log( QStringLiteral( "DiscoverRelations" ), QStringLiteral( "Changed field %1 to RelationReference" ).arg( field.name() ) );
2✔
1247
        }
1✔
1248
        else
1249
        {
1250
          config.insert( QStringLiteral( "Relation" ), relation.id() );
6✔
1251

1252
          const QgsEditorWidgetSetup newWidgetSetup = QgsEditorWidgetSetup( QStringLiteral( "RelationReference" ), config );
6✔
1253
          mFeatureLayerPair.layer()->setEditorWidgetSetup( fieldIdx, newWidgetSetup );
3✔
1254
        }
3✔
1255
      }
4✔
1256
    }
4✔
1257
  }
1258

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

1262
  for ( const QgsRelation &relation : referencingRelations )
15✔
1263
  {
1264
    std::unique_ptr<QgsAttributeEditorRelation> relationEditor = std::unique_ptr<QgsAttributeEditorRelation>( new QgsAttributeEditorRelation( relation, container ) );
4✔
1265
    if ( relationEditor->label().isEmpty() && relation.name().isEmpty() )
4✔
1266
    {
1267
      // relation does not have a name nor field have label, set label based on child layer
1268
      QString label = relation.referencingLayer()->name();
2✔
1269
      relationEditor->setLabel( label );
2✔
1270
    }
2✔
1271
    container->addChildElement( relationEditor.release() );
4✔
1272
  }
4✔
1273
}
11✔
1274

1275
bool AttributeController::isValidFormId( const QUuid &id ) const
605✔
1276
{
1277
  return mFormItems.contains( id );
605✔
1278
}
1279

1280
bool AttributeController::isValidTabId( int id ) const
54✔
1281
{
1282
  return ( id >= 0 ) && ( id < tabCount() );
54✔
1283
}
1284

1285
const FormItem *AttributeController::formItem( const QUuid &id ) const
510✔
1286
{
1287
  if ( isValidFormId( id ) )
510✔
1288
    return mFormItems[id].get();
510✔
1289
  else
1290
    return nullptr;
×
1291
}
1292

1293
const TabItem *AttributeController::tabItem( int tabRow ) const
50✔
1294
{
1295
  if ( isValidTabId( tabRow ) )
50✔
1296
    return mTabItems[tabRow].get();
49✔
1297
  else
1298
    return nullptr;
1✔
1299
}
1300

1301
bool AttributeController::setFormShouldRememberValue( const QUuid &id, bool shouldRememberValue )
×
1302
{
1303
  if ( !mRememberAttributesController )
×
1304
    return true; //noop
×
1305

1306
  if ( isValidFormId( id ) )
×
1307
  {
1308
    std::shared_ptr<FormItem> data = mFormItems[id];
×
1309
    bool changed = mRememberAttributesController->setShouldRememberValue( mFeatureLayerPair.layer(), data->fieldIndex(), shouldRememberValue );
×
1310
    if ( changed )
×
1311
    {
1312
      emit formDataChanged( id );
×
1313
    }
1314
    return true;
×
1315
  }
×
1316
  else
1317
  {
1318
    return false;
×
1319
  }
1320
}
1321

1322
bool AttributeController::formShouldRememberValue( int fieldIndex ) const
×
1323
{
1324
  const QgsFeature feat = mFeatureLayerPair.feature();
×
1325
  const QgsVectorLayer *layer = mFeatureLayerPair.layer();
×
1326

1327
  if ( !mRememberAttributesController )
×
1328
    return false;
×
1329

1330
  return mRememberAttributesController->shouldRememberValue( layer, fieldIndex );
×
1331
}
×
1332

1333
bool AttributeController::setFormValue( const QUuid &id, QVariant value )
95✔
1334
{
1335
  if ( isValidFormId( id ) )
95✔
1336
  {
1337
    std::shared_ptr<FormItem> item = mFormItems[id];
95✔
1338
    QgsField field = item->field();
95✔
1339
    QVariant val( value );
95✔
1340

1341
    item->setRawValue( val );
95✔
1342
    emit formDataChanged( item->id(), { AttributeFormModel::RawValue } );
95✔
1343

1344
    if ( !field.convertCompatible( val ) )
95✔
1345
    {
1346
      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✔
1347
      QgsMessageLog::logMessage( msg );
17✔
1348
    }
17✔
1349
    else
1350
    {
1351
      mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), val );
78✔
1352
      emit formDataChanged( item->id(), { AttributeFormModel::AttributeValue, AttributeFormModel::RawValueIsNull } );
78✔
1353
    }
1354
    recalculateDerivedItems( true, false );
95✔
1355
    return true;
95✔
1356
  }
95✔
1357
  else
1358
  {
1359
    return false;
×
1360
  }
1361
}
1362

1363
QVariant AttributeController::formValue( int fieldIndex ) const
11✔
1364
{
1365
  const QgsFeature feat = mFeatureLayerPair.feature();
11✔
1366
  if ( !feat.isValid() ||
22✔
1367
       fieldIndex < 0 ||
22✔
1368
       fieldIndex >= feat.attributeCount()
11✔
1369
     )
1370
    return QVariant();
×
1371

1372
  return mFeatureLayerPair.feature().attribute( fieldIndex );
22✔
1373
}
11✔
1374

1375
AttributeController *AttributeController::parentController() const
×
1376
{
1377
  return mParentController;
×
1378
}
1379

1380
void AttributeController::setParentController( AttributeController *newParentController )
1✔
1381
{
1382
  if ( !newParentController || mParentController == newParentController )
1✔
1383
    return;
×
1384

1385
  mParentController = newParentController;
1✔
1386
  emit parentControllerChanged();
1✔
1387

1388
  prefillRelationReferenceField();
1✔
1389

1390
  if ( mParentController )
1✔
1391
  {
1392
    // When parent's feature Id is changed, we want to update the relation reference field
1393
    connect( mParentController, &AttributeController::featureIdChanged, this, &AttributeController::prefillRelationReferenceField );
1✔
1394
  }
1395
}
1396

1397
const QgsRelation &AttributeController::linkedRelation() const
×
1398
{
1399
  return mLinkedRelation;
×
1400
}
1401

1402
void AttributeController::setLinkedRelation( const QgsRelation &newLinkedRelation )
1✔
1403
{
1404
  if ( mLinkedRelation.id() == newLinkedRelation.id() )
1✔
1405
    return;
×
1406

1407
  mLinkedRelation = newLinkedRelation;
1✔
1408
  emit linkedRelationChanged();
1✔
1409

1410
  prefillRelationReferenceField();
1✔
1411
}
1412

1413
void AttributeController::onFeatureAdded( QgsFeatureId newFeatureId )
1✔
1414
{
1415
  QgsFeature f = mFeatureLayerPair.layer()->getFeature( newFeatureId );
1✔
1416
  setFeatureLayerPair( FeatureLayerPair( f, mFeatureLayerPair.layer() ) );
1✔
1417
  emit featureIdChanged();
1✔
1418
}
1✔
1419

1420
void AttributeController::renamePhotos()
1✔
1421
{
1422
  const QStringList photoNameFormat = QgsProject::instance()->entryList( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoNaming/%1" ).arg( mFeatureLayerPair.layer()->id() ) );
4✔
1423
  if ( photoNameFormat.isEmpty() )
1✔
1424
  {
1425
    return;
×
1426
  }
1427

1428
  QgsExpressionContext expressionContext = mFeatureLayerPair.layer()->createExpressionContext();
1✔
1429
  expressionContext << QgsExpressionContextUtils::formScope( mFeatureLayerPair.feature() );
1✔
1430
  if ( mVariablesManager )
1✔
1431
    expressionContext << mVariablesManager->positionScope();
×
1432

1433
  expressionContext.setFields( mFeatureLayerPair.feature().fields() );
1✔
1434
  expressionContext.setFeature( mFeatureLayerPair.featureRef() );
1✔
1435

1436
  // check for new photos
1437
  QMap<QUuid, std::shared_ptr<FormItem>>::iterator formItemsIterator = mFormItems.begin();
1✔
1438
  while ( formItemsIterator != mFormItems.end() )
5✔
1439
  {
1440
    std::shared_ptr<FormItem> item = formItemsIterator.value();
4✔
1441
    if ( item->type() == FormItem::Field && item->editorWidgetType() == QStringLiteral( "ExternalResource" ) )
8✔
1442
    {
1443
      QVariantMap config = item->editorWidgetConfig();
1✔
1444
      const QgsField field = item->field();
1✔
1445
      if ( !photoNameFormat.contains( field.name() ) )
1✔
1446
      {
1447
        ++formItemsIterator;
×
1448
        continue;
×
1449
      }
1450

1451
      if ( item->originalValue() != mFeatureLayerPair.feature().attribute( item->fieldIndex() ) )
1✔
1452
      {
1453
        QString expString = QgsProject::instance()->readEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoNaming/%1/%2" ).arg( mFeatureLayerPair.layer()->id() ).arg( field.name() ) );
4✔
1454
        QgsExpression exp( expString );
1✔
1455
        exp.prepare( &expressionContext );
1✔
1456
        if ( exp.hasParserError() )
1✔
1457
        {
1458
          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() ) );
×
1459
          ++formItemsIterator;
×
1460
          continue;
×
1461
        }
1462

1463
        QVariant value = exp.evaluate( &expressionContext );
1✔
1464
        if ( exp.hasEvalError() )
1✔
1465
        {
1466
          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() ) );
×
1467
          ++formItemsIterator;
×
1468
          continue;
×
1469
        }
1470

1471
        QVariant val( value );
1✔
1472
        if ( !field.convertCompatible( val ) )
1✔
1473
        {
1474
          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" ) );
×
1475
          ++formItemsIterator;
×
1476
          continue;
×
1477
        }
1478

1479
        const QString targetDir = InputUtils::resolveTargetDir( QgsProject::instance()->homePath(), config, mFeatureLayerPair, QgsProject::instance() );
1✔
1480
        const QString prefix = InputUtils::resolvePrefixForRelativePath( config[ QStringLiteral( "RelativeStorage" ) ].toInt(), QgsProject::instance()->homePath(), targetDir );
3✔
1481
        const QString src = InputUtils::getAbsolutePath( mFeatureLayerPair.feature().attribute( item->fieldIndex() ).toString(), prefix );
2✔
1482
        QFileInfo fi( src );
1✔
1483
        QString newName = QStringLiteral( "%1.%2" ).arg( val.toString() ).arg( fi.completeSuffix() );
2✔
1484
        const QString dst = InputUtils::getAbsolutePath( newName, prefix );
1✔
1485
        if ( InputUtils::renameFile( src, dst ) )
1✔
1486
        {
1487
          mFeatureLayerPair.featureRef().setAttribute( item->fieldIndex(), newName );
1✔
1488
          expressionContext.setFeature( featureLayerPair().featureRef() );
1✔
1489
        }
1490
      }
1✔
1491
    }
1✔
1492

1493
    ++formItemsIterator;
4✔
1494
  }
4✔
1495
}
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