QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsfieldcalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsfieldcalculator.cpp
3 ---------------------
4 begin : September 2009
5 copyright : (C) 2009 by Marco Hugentobler
6 email : marco at hugis dot net
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 "qgsfieldcalculator.h"
17
18#include "qgsdistancearea.h"
19#include "qgsexpression.h"
22#include "qgsfeatureiterator.h"
23#include "qgsfields.h"
24#include "qgsgeometry.h"
25#include "qgsgui.h"
26#include "qgsguiutils.h"
27#include "qgsmessagebar.h"
28#include "qgsproject.h"
30#include "qgsvariantutils.h"
32#include "qgsvectorlayer.h"
34
35#include <QMessageBox>
36#include <QString>
37
38#include "moc_qgsfieldcalculator.cpp"
39
40using namespace Qt::StringLiterals;
41
42// FTC = FieldTypeCombo
43constexpr int FTC_TYPE_ROLE_IDX = 0;
44constexpr int FTC_TYPE_NAME_IDX = 1;
45constexpr int FTC_MINLEN_IDX = 2;
46constexpr int FTC_MAXLEN_IDX = 3;
47constexpr int FTC_MINPREC_IDX = 4;
48constexpr int FTC_MAXPREC_IDX = 5;
49constexpr int FTC_SUBTYPE_IDX = 6;
50
52 : QDialog( parent )
53 , mVectorLayer( vl )
54 , mAttributeId( -1 )
55{
56 setupUi( this );
57 connect( mNewFieldGroupBox, &QGroupBox::toggled, this, &QgsFieldCalculator::mNewFieldGroupBox_toggled );
58 connect( mUpdateExistingGroupBox, &QGroupBox::toggled, this, &QgsFieldCalculator::mUpdateExistingGroupBox_toggled );
59 connect( mCreateVirtualFieldCheckbox, &QCheckBox::stateChanged, this, &QgsFieldCalculator::mCreateVirtualFieldCheckbox_stateChanged );
60 connect( mOutputFieldNameLineEdit, &QLineEdit::textChanged, this, &QgsFieldCalculator::mOutputFieldNameLineEdit_textChanged );
61 connect( mOutputFieldTypeComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::activated ), this, &QgsFieldCalculator::mOutputFieldTypeComboBox_activated );
62 connect( mExistingFieldComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsFieldCalculator::mExistingFieldComboBox_currentIndexChanged );
63
65
66 if ( !vl )
67 return;
68 QgsVectorDataProvider *dataProvider = vl->dataProvider();
69 if ( !dataProvider )
70 return;
71
72 const bool layerIsReadOnly { vl->readOnly() };
73 const Qgis::VectorProviderCapabilities caps = dataProvider->capabilities();
74 mCanAddAttribute = !layerIsReadOnly && ( caps & Qgis::VectorProviderCapability::AddAttributes );
75 mCanChangeAttributeValue = !layerIsReadOnly && ( caps & Qgis::VectorProviderCapability::ChangeAttributeValues );
76
78
79 expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( u"row_number"_s, 1, true ) );
80 expContext.setHighlightedVariables( QStringList() << u"row_number"_s );
81
82 populateFields();
83 populateOutputFieldTypes();
84
85 connect( builder, &QgsExpressionBuilderWidget::expressionParsed, this, &QgsFieldCalculator::setDialogButtonState );
86 connect( mOutputFieldWidthSpinBox, &QAbstractSpinBox::editingFinished, this, &QgsFieldCalculator::setPrecisionMinMax );
87 connect( mButtonBox, &QDialogButtonBox::helpRequested, this, &QgsFieldCalculator::showHelp );
88 connect( mButtonBox->button( QDialogButtonBox::Apply ), &QAbstractButton::clicked, this, &QgsFieldCalculator::calculate );
89
90 QgsDistanceArea myDa;
91 myDa.setSourceCrs( vl->crs(), QgsProject::instance()->transformContext() );
92 myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
93 builder->setGeomCalculator( myDa );
94
95 //default values for field width and precision
96 mOutputFieldWidthSpinBox->setValue( 10 );
97 mOutputFieldWidthSpinBox->setClearValue( 10 );
98 mOutputFieldPrecisionSpinBox->setValue( 3 );
99 mOutputFieldPrecisionSpinBox->setClearValue( 3 );
100 setPrecisionMinMax();
101
102 if ( vl->providerType() == "ogr"_L1 && vl->storageType() == "ESRI Shapefile"_L1 )
103 {
104 mOutputFieldNameLineEdit->setMaxLength( 10 );
105 }
106
107 if ( !mCanAddAttribute )
108 {
109 mCreateVirtualFieldCheckbox->setChecked( true );
110 mCreateVirtualFieldCheckbox->setEnabled( false );
111 mOnlyVirtualFieldsInfoLabel->setVisible( true );
112 mInfoIcon->setVisible( true );
113 }
114 else
115 {
116 mOnlyVirtualFieldsInfoLabel->setVisible( false );
117 mInfoIcon->setVisible( false );
118 }
119
120 if ( !mCanChangeAttributeValue )
121 {
122 mUpdateExistingGroupBox->setEnabled( false );
123 mCreateVirtualFieldCheckbox->setChecked( true );
124 mCreateVirtualFieldCheckbox->setEnabled( false );
125 }
126
127 Q_ASSERT( mNewFieldGroupBox->isEnabled() || mUpdateExistingGroupBox->isEnabled() );
128
129 if ( mNewFieldGroupBox->isEnabled() )
130 {
131 mNewFieldGroupBox->setChecked( true );
132 }
133 else
134 {
135 mNewFieldGroupBox->setToolTip( tr( "Not available for layer" ) );
136 mUpdateExistingGroupBox->setChecked( true );
137 mUpdateExistingGroupBox->setCheckable( false );
138 }
139
140 if ( mUpdateExistingGroupBox->isEnabled() )
141 {
142 mUpdateExistingGroupBox->setChecked( !mNewFieldGroupBox->isEnabled() );
143 }
144 else
145 {
146 mUpdateExistingGroupBox->setToolTip( tr( "Not available for layer" ) );
147 mNewFieldGroupBox->setChecked( true );
148 mNewFieldGroupBox->setCheckable( false );
149 }
150
151 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
152 {
153 mEditModeAutoTurnOnLabel->setVisible( false );
154 mInfoIcon->setVisible( false );
155 }
156 else
157 {
158 mInfoIcon->setVisible( true );
159 }
160
161 const bool hasselection = vl->selectedFeatureCount() > 0;
162 mOnlyUpdateSelectedCheckBox->setChecked( mCanChangeAttributeValue && hasselection );
163 mOnlyUpdateSelectedCheckBox->setEnabled( mCanChangeAttributeValue && hasselection );
164 mOnlyUpdateSelectedCheckBox->setText( tr( "Only update %n selected feature(s)", nullptr, vl->selectedFeatureCount() ) );
165
166 builder->initWithLayer( vl, expContext, u"fieldcalc"_s );
167
168 mInfoIcon->setPixmap( style()->standardPixmap( QStyle::SP_MessageBoxInformation ) );
169
170 setWindowTitle( tr( "%1 — Field Calculator" ).arg( mVectorLayer->name() ) );
171
172 // Init the message bar instance
173 mMsgBar = new QgsMessageBar( this );
174 mMsgBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
175 this->vLayout->insertWidget( 0, mMsgBar );
176
177 setDialogButtonState();
178}
179
181{
182 calculate();
183 QDialog::accept();
184}
185
186void QgsFieldCalculator::calculate()
187{
188 builder->expressionTree()->saveToRecent( builder->expressionText(), u"fieldcalc"_s );
189
190 if ( !mVectorLayer )
191 return;
192
193 // Set up QgsDistanceArea each time we (re-)calculate
194 QgsDistanceArea myDa;
195
196 myDa.setSourceCrs( mVectorLayer->crs(), QgsProject::instance()->transformContext() );
197 myDa.setEllipsoid( QgsProject::instance()->ellipsoid() );
198
199 const QString calcString = builder->expressionText();
200 QgsExpression exp( calcString );
201 exp.setGeomCalculator( &myDa );
202 exp.setDistanceUnits( QgsProject::instance()->distanceUnits() );
203 exp.setAreaUnits( QgsProject::instance()->areaUnits() );
204
206
207 if ( !exp.prepare( &expContext ) )
208 {
209 QMessageBox::critical( nullptr, tr( "Evaluation Error" ), exp.evalErrorString() );
210 return;
211 }
212
213 bool updatingGeom = false;
214
215 // Test for creating expression field based on ! mUpdateExistingGroupBox checked rather
216 // than on mNewFieldGroupBox checked, as if the provider does not support adding attributes
217 // then mUpdateExistingGroupBox is set to not checkable, and hence is not checked. This
218 // is a minimum fix to resolve this - better would be some GUI redesign...
219 if ( !mUpdateExistingGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() )
220 {
221 mVectorLayer->addExpressionField( calcString, fieldDefinition() );
222 }
223 else
224 {
225 // If the layer is read-only, show an error
226 // This should never happen because the read-only state is now checked at construction time
227 // and the GUI is updated accordingly, but I'm leaving the safeguard here as a remainder just
228 // in case the GUI will be redesigned in the future.
229 if ( mVectorLayer->readOnly() )
230 {
231 QMessageBox::critical( nullptr, tr( "Read-only layer" ), tr( "The layer was marked read-only in the project properties and cannot be edited." ) );
232 return;
233 }
234
235 if ( !mVectorLayer->isEditable() )
236 mVectorLayer->startEditing();
237
238 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
239
240 mVectorLayer->beginEditCommand( u"Field calculator"_s );
241
242 //update existing field
243 if ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
244 {
245 if ( mExistingFieldComboBox->currentData().toString() == "geom"_L1 )
246 {
247 //update geometry
248 mAttributeId = -1;
249 updatingGeom = true;
250 }
251 else
252 {
253 bool ok = false;
254 const int id = mExistingFieldComboBox->currentData().toInt( &ok );
255 if ( ok )
256 mAttributeId = id;
257 }
258 }
259 else
260 {
261 //create new field
262 const QgsField newField = fieldDefinition();
263
264 if ( !mVectorLayer->addAttribute( newField ) )
265 {
266 cursorOverride.release();
267 QMessageBox::critical( nullptr, tr( "Create New Field" ), tr( "Could not add the new field to the provider." ) );
268 mVectorLayer->destroyEditCommand();
269 return;
270 }
271
272 //get index of the new field
273 const QgsFields &fields = mVectorLayer->fields();
274
275 for ( int idx = 0; idx < fields.count(); ++idx )
276 {
277 if ( fields.at( idx ).name() == mOutputFieldNameLineEdit->text() )
278 {
279 mAttributeId = idx;
280 break;
281 }
282 }
283
284 //update expression context with new fields
285 expContext.setFields( mVectorLayer->fields() );
286 if ( !exp.prepare( &expContext ) )
287 {
288 cursorOverride.release();
289 QMessageBox::critical( nullptr, tr( "Evaluation Error" ), exp.evalErrorString() );
290 return;
291 }
292 }
293
294 if ( mAttributeId == -1 && !updatingGeom )
295 {
296 mVectorLayer->destroyEditCommand();
297 return;
298 }
299
300 //go through all the features and change the new attribute
301 QgsFeature feature;
302 bool calculationSuccess = true;
303 QString error;
304
305 const bool useGeometry = exp.needsGeometry();
306 int rownum = 1;
307
308 const QgsField field = !updatingGeom ? mVectorLayer->fields().at( mAttributeId ) : QgsField();
309
310 const bool newField = !mUpdateExistingGroupBox->isChecked();
311 QVariant emptyAttribute;
312 if ( newField )
313 emptyAttribute = QgsVariantUtils::createNullVariant( field.type() );
314
315 QgsFeatureRequest req = QgsFeatureRequest().setFlags( useGeometry ? Qgis::FeatureRequestFlag::NoFlags : Qgis::FeatureRequestFlag::NoGeometry );
316 QSet<QString> referencedColumns = exp.referencedColumns();
317 referencedColumns.insert( field.name() ); // need existing column value to store old attribute when changing field values
318 req.setSubsetOfAttributes( referencedColumns, mVectorLayer->fields() );
319 if ( mOnlyUpdateSelectedCheckBox->isChecked() )
320 {
321 req.setFilterFids( mVectorLayer->selectedFeatureIds() );
322 }
323 QgsFeatureIterator fit = mVectorLayer->getFeatures( req );
324
325 auto task = std::make_unique<QgsScopedProxyProgressTask>( tr( "Calculating field" ) );
326 const long long count = mOnlyUpdateSelectedCheckBox->isChecked() ? mVectorLayer->selectedFeatureCount() : mVectorLayer->featureCount();
327 long long i = 0;
328 while ( fit.nextFeature( feature ) )
329 {
330 i++;
331 task->setProgress( i / static_cast<double>( count ) * 100 );
332
333 expContext.setFeature( feature );
334 expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( u"row_number"_s, rownum, true ) );
335
336 QVariant value = exp.evaluate( &expContext );
337 if ( exp.hasEvalError() )
338 {
339 calculationSuccess = false;
340 error = exp.evalErrorString();
341 break;
342 }
343 else if ( updatingGeom )
344 {
345 if ( value.userType() == qMetaTypeId<QgsGeometry>() )
346 {
347 QgsGeometry geom = value.value<QgsGeometry>();
348 mVectorLayer->changeGeometry( feature.id(), geom );
349 }
350 }
351 else
352 {
353 ( void ) field.convertCompatible( value );
354 mVectorLayer->changeAttributeValue( feature.id(), mAttributeId, value, newField ? emptyAttribute : feature.attributes().value( mAttributeId ) );
355 }
356
357 rownum++;
358 }
359
360 if ( !calculationSuccess )
361 {
362 cursorOverride.release();
363 task.reset();
364 QMessageBox::critical( nullptr, tr( "Evaluation Error" ), tr( "An error occurred while evaluating the calculation string:\n%1" ).arg( error ) );
365 mVectorLayer->destroyEditCommand();
366 return;
367 }
368
369 mVectorLayer->endEditCommand();
370 if ( mNewFieldGroupBox->isChecked() )
371 {
372 pushMessage( tr( "Field \"%1\" created successfully" ).arg( mOutputFieldNameLineEdit->text() ) );
373 }
374 else if ( mUpdateExistingGroupBox->isChecked() )
375 {
376 pushMessage( tr( "Field \"%1\" updated successfully" ).arg( mExistingFieldComboBox->currentText() ) );
377 }
378 }
379}
380
381void QgsFieldCalculator::populateOutputFieldTypes()
382{
383 if ( !mVectorLayer )
384 {
385 return;
386 }
387
388 QgsVectorDataProvider *provider = mVectorLayer->dataProvider();
389 if ( !provider )
390 {
391 return;
392 }
393
394 const int oldDataType = mOutputFieldTypeComboBox->currentData( Qt::UserRole + FTC_TYPE_ROLE_IDX ).toInt();
395
396 mOutputFieldTypeComboBox->blockSignals( true );
397
398 // Standard subset of fields in case of virtual
399 const QList<QgsVectorDataProvider::NativeType> &typelist = mCreateVirtualFieldCheckbox->isChecked() ? ( QList<QgsVectorDataProvider::NativeType>()
400 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::Int ), u"integer"_s, QMetaType::Type::Int, 0, 10 )
401 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::Double ), u"double precision"_s, QMetaType::Type::Double, -1, -1, -1, -1 )
402 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QString ), u"string"_s, QMetaType::Type::QString )
403 // date time
404 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDate ), u"date"_s, QMetaType::Type::QDate, -1, -1, -1, -1 )
405 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QTime ), u"time"_s, QMetaType::Type::QTime, -1, -1, -1, -1 )
406 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDateTime ), u"datetime"_s, QMetaType::Type::QDateTime, -1, -1, -1, -1 )
407 // string types
408 << QgsVectorDataProvider::NativeType( tr( "Text, unlimited length (text)" ), u"text"_s, QMetaType::Type::QString, -1, -1, -1, -1 )
409 // boolean
410 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::Bool ), u"bool"_s, QMetaType::Type::Bool )
411 // blob
412 << QgsVectorDataProvider::NativeType( QgsVariantUtils::typeToDisplayString( QMetaType::Type::QByteArray ), u"binary"_s, QMetaType::Type::QByteArray ) )
413 : provider->nativeTypes();
414
415 mOutputFieldTypeComboBox->clear();
416 for ( int i = 0; i < typelist.size(); i++ )
417 {
418 mOutputFieldTypeComboBox->addItem( QgsFields::iconForFieldType( typelist[i].mType, typelist[i].mSubType, typelist[i].mTypeName ), typelist[i].mTypeDesc );
419 mOutputFieldTypeComboBox->setItemData( i, static_cast<int>( typelist[i].mType ), Qt::UserRole + FTC_TYPE_ROLE_IDX );
420 mOutputFieldTypeComboBox->setItemData( i, typelist[i].mTypeName, Qt::UserRole + FTC_TYPE_NAME_IDX );
421 mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMinLen, Qt::UserRole + FTC_MINLEN_IDX );
422 mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMaxLen, Qt::UserRole + FTC_MAXLEN_IDX );
423 mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMinPrec, Qt::UserRole + FTC_MINPREC_IDX );
424 mOutputFieldTypeComboBox->setItemData( i, typelist[i].mMaxPrec, Qt::UserRole + FTC_MAXPREC_IDX );
425 mOutputFieldTypeComboBox->setItemData( i, static_cast<int>( typelist[i].mSubType ), Qt::UserRole + FTC_SUBTYPE_IDX );
426 }
427 mOutputFieldTypeComboBox->blockSignals( false );
428
429 const int idx = mOutputFieldTypeComboBox->findData( oldDataType, Qt::UserRole + FTC_TYPE_ROLE_IDX );
430 if ( idx != -1 )
431 {
432 mOutputFieldTypeComboBox->setCurrentIndex( idx );
433 mOutputFieldTypeComboBox_activated( idx );
434 }
435 else
436 {
437 mOutputFieldTypeComboBox->setCurrentIndex( 0 );
438 mOutputFieldTypeComboBox_activated( 0 );
439 }
440}
441
442void QgsFieldCalculator::mNewFieldGroupBox_toggled( bool on )
443{
444 mUpdateExistingGroupBox->setChecked( !on );
445 if ( on && !mCanAddAttribute )
446 {
447 mOnlyVirtualFieldsInfoLabel->setVisible( true );
448 }
449 else
450 {
451 mOnlyVirtualFieldsInfoLabel->setVisible( false );
452 }
453
454 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
455 {
456 mEditModeAutoTurnOnLabel->setVisible( false );
457 }
458 else
459 {
460 mEditModeAutoTurnOnLabel->setVisible( true );
461 }
462
463 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
464}
465
466void QgsFieldCalculator::mUpdateExistingGroupBox_toggled( bool on )
467{
468 mNewFieldGroupBox->setChecked( !on );
469 setDialogButtonState();
470
471 if ( on )
472 {
473 mOnlyUpdateSelectedCheckBox->setEnabled( mVectorLayer->selectedFeatureCount() > 0 );
474 }
475 else
476 {
477 mCreateVirtualFieldCheckbox_stateChanged( mCreateVirtualFieldCheckbox->checkState() );
478 }
479}
480
481void QgsFieldCalculator::mCreateVirtualFieldCheckbox_stateChanged( int state )
482{
483 mOnlyUpdateSelectedCheckBox->setChecked( false );
484 mOnlyUpdateSelectedCheckBox->setEnabled( state != Qt::Checked && mVectorLayer->selectedFeatureCount() > 0 );
485
486 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
487 {
488 mEditModeAutoTurnOnLabel->setVisible( false );
489 }
490 else
491 {
492 mEditModeAutoTurnOnLabel->setVisible( true );
493 }
494 populateOutputFieldTypes();
495 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
496}
497
498
499void QgsFieldCalculator::mOutputFieldNameLineEdit_textChanged( const QString &text )
500{
501 Q_UNUSED( text )
502 setDialogButtonState();
503}
504
505
506void QgsFieldCalculator::mOutputFieldTypeComboBox_activated( int index )
507{
508 mOutputFieldWidthSpinBox->setMinimum( mOutputFieldTypeComboBox->itemData( index, Qt::UserRole + FTC_MINLEN_IDX ).toInt() );
509 mOutputFieldWidthSpinBox->setMaximum( mOutputFieldTypeComboBox->itemData( index, Qt::UserRole + FTC_MAXLEN_IDX ).toInt() );
510 mOutputFieldWidthSpinBox->setEnabled( mOutputFieldWidthSpinBox->minimum() < mOutputFieldWidthSpinBox->maximum() );
511 if ( mOutputFieldWidthSpinBox->value() < mOutputFieldWidthSpinBox->minimum() )
512 mOutputFieldWidthSpinBox->setValue( mOutputFieldWidthSpinBox->minimum() );
513 if ( mOutputFieldWidthSpinBox->value() > mOutputFieldWidthSpinBox->maximum() )
514 mOutputFieldWidthSpinBox->setValue( mOutputFieldWidthSpinBox->maximum() );
515
516 setPrecisionMinMax();
517}
518
519void QgsFieldCalculator::mExistingFieldComboBox_currentIndexChanged( const int index )
520{
521 Q_UNUSED( index )
522 setDialogButtonState();
523}
524
525void QgsFieldCalculator::populateFields()
526{
527 if ( !mVectorLayer )
528 return;
529
530 const QgsFields &fields = mVectorLayer->fields();
531 for ( int idx = 0; idx < fields.count(); ++idx )
532 {
533 switch ( fields.fieldOrigin( idx ) )
534 {
537
538 continue; // can't be edited
539
542 break; // can always be edited
543
545 {
546 // show joined fields (e.g. auxiliary fields) only if they have a non-hidden editor widget.
547 // This enables them to be bulk field-calculated when a user needs to, but hides them by default
548 // (since there's often MANY of these, e.g. after using the label properties tool on a layer)
549 if ( fields.at( idx ).editorWidgetSetup().type() == "Hidden"_L1 )
550 continue;
551
552 // only show editable joins
553 int srcFieldIndex;
554 const QgsVectorLayerJoinInfo *info = mVectorLayer->joinBuffer()->joinForFieldIndex( idx, fields, srcFieldIndex );
555
556 if ( !info || !info->isEditable() )
557 continue; // join is not editable
558
559 break;
560 }
561 }
562
563 const QString fieldName = fields.at( idx ).name();
564
565 //insert into field combo box
566 mExistingFieldComboBox->addItem( fields.iconForField( idx ), fieldName, idx );
567 }
568
569 if ( mVectorLayer->geometryType() != Qgis::GeometryType::Null )
570 {
571 mExistingFieldComboBox->addItem( tr( "<geometry>" ), "geom" );
572
573 QFont font = mExistingFieldComboBox->itemData( mExistingFieldComboBox->count() - 1, Qt::FontRole ).value<QFont>();
574 font.setItalic( true );
575 mExistingFieldComboBox->setItemData( mExistingFieldComboBox->count() - 1, font, Qt::FontRole );
576 }
577 mExistingFieldComboBox->setCurrentIndex( -1 );
578}
579
580void QgsFieldCalculator::setDialogButtonState()
581{
582 QList<QPushButton *> buttons = {
583 mButtonBox->button( QDialogButtonBox::Ok ),
584 mButtonBox->button( QDialogButtonBox::Apply )
585 };
586
587 bool enableButtons = true;
588 QString tooltip;
589
590 if ( ( mNewFieldGroupBox->isChecked() || !mUpdateExistingGroupBox->isEnabled() )
591 && mOutputFieldNameLineEdit->text().isEmpty() )
592 {
593 tooltip = tr( "Please enter a field name" );
594 enableButtons = false;
595 }
596 else if ( ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
597 && mExistingFieldComboBox->currentIndex() == -1 )
598 {
599 tooltip = tr( "Please select a field" );
600 enableButtons = false;
601 }
602 else if ( builder->expressionText().isEmpty() )
603 {
604 tooltip = tr( "Please insert an expression" );
605 enableButtons = false;
606 }
607 else if ( !builder->isExpressionValid() )
608 {
609 tooltip = tr( "The expression is invalid. See \"(more info)\" for details" );
610 enableButtons = false;
611 }
612
613 for ( QPushButton *button : buttons )
614 {
615 if ( button )
616 {
617 button->setEnabled( enableButtons );
618 button->setToolTip( tooltip );
619 }
620 }
621}
622
623void QgsFieldCalculator::setPrecisionMinMax()
624{
625 const int idx = mOutputFieldTypeComboBox->currentIndex();
626 const int minPrecType = mOutputFieldTypeComboBox->itemData( idx, Qt::UserRole + FTC_MINPREC_IDX ).toInt();
627 const int maxPrecType = mOutputFieldTypeComboBox->itemData( idx, Qt::UserRole + FTC_MAXPREC_IDX ).toInt();
628 const bool precisionIsEnabled = minPrecType < maxPrecType;
629 mOutputFieldPrecisionSpinBox->setEnabled( precisionIsEnabled );
630 // Do not set min/max if it's disabled or we'll loose the default value,
631 // see https://github.com/qgis/QGIS/issues/26880 - QGIS saves integer field when
632 // I create a new real field through field calculator (Update field works as intended)
633 if ( precisionIsEnabled )
634 {
635 mOutputFieldPrecisionSpinBox->setMinimum( minPrecType );
636 mOutputFieldPrecisionSpinBox->setMaximum( std::max( minPrecType, std::min( maxPrecType, mOutputFieldWidthSpinBox->value() ) ) );
637 }
638}
639
640void QgsFieldCalculator::showHelp()
641{
642 QgsHelp::openHelp( u"working_with_vector/attribute_table.html#editing-attribute-values"_s );
643}
644
645QgsField QgsFieldCalculator::fieldDefinition()
646{
647 return QgsField( mOutputFieldNameLineEdit->text(), static_cast<QMetaType::Type>( mOutputFieldTypeComboBox->currentData( Qt::UserRole + FTC_TYPE_ROLE_IDX ).toInt() ), mOutputFieldTypeComboBox->currentData( Qt::UserRole + FTC_TYPE_NAME_IDX ).toString(), mOutputFieldWidthSpinBox->value(), mOutputFieldPrecisionSpinBox->isEnabled() ? mOutputFieldPrecisionSpinBox->value() : 0, QString(), static_cast<QMetaType::Type>( mOutputFieldTypeComboBox->currentData( Qt::UserRole + FTC_SUBTYPE_IDX ).toInt() ) );
648}
649
650void QgsFieldCalculator::pushMessage( const QString &text, Qgis::MessageLevel level, int duration )
651{
652 mMsgBar->pushMessage( text, level, duration );
653}
@ AddAttributes
Allows addition of new attributes (fields).
Definition qgis.h:523
@ ChangeAttributeValues
Allows modification of attribute values.
Definition qgis.h:522
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2254
@ NoFlags
No flags are set.
Definition qgis.h:2253
MessageLevel
Level for messages This will be used both for message log and message bar in application.
Definition qgis.h:159
@ Null
No geometry.
Definition qgis.h:370
QFlags< VectorProviderCapability > VectorProviderCapabilities
Vector data provider capabilities.
Definition qgis.h:555
@ Provider
Field originates from the underlying data provider of the vector layer.
Definition qgis.h:1764
@ Edit
Field has been temporarily added in editing mode.
Definition qgis.h:1766
@ Unknown
The field origin has not been specified.
Definition qgis.h:1763
@ Expression
Field is calculated from an expression.
Definition qgis.h:1767
@ Join
Field originates from a joined layer.
Definition qgis.h:1765
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QString type() const
Returns the widget type to use.
void expressionParsed(bool isValid)
Emitted when the user changes the expression in the widget.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Handles parsing and evaluation of expressions (formerly called "search strings").
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsAttributes attributes
Definition qgsfeature.h:69
QgsFeatureId id
Definition qgsfeature.h:68
QgsFieldCalculator(QgsVectorLayer *vl, QWidget *parent=nullptr)
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
QMetaType::Type type
Definition qgsfield.h:63
QString name
Definition qgsfield.h:65
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
Definition qgsfield.cpp:479
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition qgsfield.cpp:751
int count
Definition qgsfields.h:50
static QIcon iconForFieldType(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType, const QString &typeString=QString())
Returns an icon corresponding to a field type.
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
QIcon iconForField(int fieldIdx, bool considerOrigin=false) const
Returns an icon corresponding to a field index, based on the field's type and source.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:224
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:41
QString providerType() const
Returns the provider type (provider key) for this layer.
QgsCoordinateReferenceSystem crs
Definition qgsmaplayer.h:90
A bar for displaying non-blocking messages to the user.
static QgsProject * instance()
Returns the QgsProject singleton instance.
static QString typeToDisplayString(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType)
Returns a user-friendly translated string representing a QVariant type.
static QVariant createNullVariant(QMetaType::Type metaType)
Helper method to properly create a null QVariant from a metaType Returns the created QVariant.
Base class for vector data providers.
virtual Q_INVOKABLE Qgis::VectorProviderCapabilities capabilities() const
Returns flags containing the supported capabilities.
bool isEditable() const
Returns whether joined fields may be edited through the form of the target layer.
Represents a vector layer which manages a vector based dataset.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
QString storageType() const
Returns the permanent storage type for this layer as a friendly name.
QgsVectorDataProvider * dataProvider() final
Returns the layer's data provider, it may be nullptr.
constexpr int FTC_TYPE_NAME_IDX
constexpr int FTC_MINLEN_IDX
constexpr int FTC_TYPE_ROLE_IDX
constexpr int FTC_MAXLEN_IDX
constexpr int FTC_MAXPREC_IDX
constexpr int FTC_MINPREC_IDX
constexpr int FTC_SUBTYPE_IDX
Single variable definition for use within a QgsExpressionContextScope.