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