37#include "moc_qgsfieldcalculator.cpp"
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 );
69 const bool layerIsReadOnly { vl->
readOnly() };
77 expContext.setHighlightedVariables( QStringList() << QStringLiteral(
"row_number" ) );
80 populateOutputFieldTypes();
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 );
90 builder->setGeomCalculator( myDa );
93 mOutputFieldWidthSpinBox->setValue( 10 );
94 mOutputFieldWidthSpinBox->setClearValue( 10 );
95 mOutputFieldPrecisionSpinBox->setValue( 3 );
96 mOutputFieldPrecisionSpinBox->setClearValue( 3 );
101 mOutputFieldNameLineEdit->setMaxLength( 10 );
104 if ( !mCanAddAttribute )
106 mCreateVirtualFieldCheckbox->setChecked(
true );
107 mCreateVirtualFieldCheckbox->setEnabled(
false );
108 mOnlyVirtualFieldsInfoLabel->setVisible(
true );
109 mInfoIcon->setVisible(
true );
113 mOnlyVirtualFieldsInfoLabel->setVisible(
false );
114 mInfoIcon->setVisible(
false );
117 if ( !mCanChangeAttributeValue )
119 mUpdateExistingGroupBox->setEnabled(
false );
120 mCreateVirtualFieldCheckbox->setChecked(
true );
121 mCreateVirtualFieldCheckbox->setEnabled(
false );
124 Q_ASSERT( mNewFieldGroupBox->isEnabled() || mUpdateExistingGroupBox->isEnabled() );
126 if ( mNewFieldGroupBox->isEnabled() )
128 mNewFieldGroupBox->setChecked(
true );
132 mNewFieldGroupBox->setToolTip( tr(
"Not available for layer" ) );
133 mUpdateExistingGroupBox->setChecked(
true );
134 mUpdateExistingGroupBox->setCheckable(
false );
137 if ( mUpdateExistingGroupBox->isEnabled() )
139 mUpdateExistingGroupBox->setChecked( !mNewFieldGroupBox->isEnabled() );
143 mUpdateExistingGroupBox->setToolTip( tr(
"Not available for layer" ) );
144 mNewFieldGroupBox->setChecked(
true );
145 mNewFieldGroupBox->setCheckable(
false );
148 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
150 mEditModeAutoTurnOnLabel->setVisible(
false );
151 mInfoIcon->setVisible(
false );
155 mInfoIcon->setVisible(
true );
159 mOnlyUpdateSelectedCheckBox->setChecked( mCanChangeAttributeValue && hasselection );
160 mOnlyUpdateSelectedCheckBox->setEnabled( mCanChangeAttributeValue && hasselection );
161 mOnlyUpdateSelectedCheckBox->setText( tr(
"Only update %n selected feature(s)",
nullptr, vl->
selectedFeatureCount() ) );
163 builder->initWithLayer( vl, expContext, QStringLiteral(
"fieldcalc" ) );
165 mInfoIcon->setPixmap( style()->standardPixmap( QStyle::SP_MessageBoxInformation ) );
167 setWindowTitle( tr(
"%1 — Field Calculator" ).arg( mVectorLayer->name() ) );
171 mMsgBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
172 this->vLayout->insertWidget( 0, mMsgBar );
174 setDialogButtonState();
183void QgsFieldCalculator::calculate()
185 builder->expressionTree()->saveToRecent( builder->expressionText(), QStringLiteral(
"fieldcalc" ) );
196 const QString calcString = builder->expressionText();
198 exp.setGeomCalculator( &myDa );
204 if ( !exp.prepare( &expContext ) )
206 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), exp.evalErrorString() );
210 bool updatingGeom =
false;
216 if ( !mUpdateExistingGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() )
218 mVectorLayer->addExpressionField( calcString, fieldDefinition() );
226 if ( mVectorLayer->readOnly() )
228 QMessageBox::critical(
nullptr, tr(
"Read-only layer" ), tr(
"The layer was marked read-only in the project properties and cannot be edited." ) );
232 if ( !mVectorLayer->isEditable() )
233 mVectorLayer->startEditing();
235 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
237 mVectorLayer->beginEditCommand( QStringLiteral(
"Field calculator" ) );
240 if ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
242 if ( mExistingFieldComboBox->currentData().toString() == QLatin1String(
"geom" ) )
251 const int id = mExistingFieldComboBox->currentData().toInt( &ok );
259 const QgsField newField = fieldDefinition();
261 if ( !mVectorLayer->addAttribute( newField ) )
263 cursorOverride.release();
264 QMessageBox::critical(
nullptr, tr(
"Create New Field" ), tr(
"Could not add the new field to the provider." ) );
265 mVectorLayer->destroyEditCommand();
270 const QgsFields &fields = mVectorLayer->fields();
272 for (
int idx = 0; idx < fields.
count(); ++idx )
274 if ( fields.
at( idx ).
name() == mOutputFieldNameLineEdit->text() )
282 expContext.setFields( mVectorLayer->fields() );
283 if ( !exp.prepare( &expContext ) )
285 cursorOverride.release();
286 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), exp.evalErrorString() );
291 if ( mAttributeId == -1 && !updatingGeom )
293 mVectorLayer->destroyEditCommand();
299 bool calculationSuccess =
true;
302 const bool useGeometry = exp.needsGeometry();
305 const QgsField field = !updatingGeom ? mVectorLayer->fields().at( mAttributeId ) : QgsField();
307 const bool newField = !mUpdateExistingGroupBox->isChecked();
308 QVariant emptyAttribute;
313 QSet<QString> referencedColumns = exp.referencedColumns();
314 referencedColumns.insert( field.
name() );
316 if ( mOnlyUpdateSelectedCheckBox->isChecked() )
320 QgsFeatureIterator fit = mVectorLayer->getFeatures( req );
322 auto task = std::make_unique<QgsScopedProxyProgressTask>( tr(
"Calculating field" ) );
323 const long long count = mOnlyUpdateSelectedCheckBox->isChecked() ? mVectorLayer->selectedFeatureCount() : mVectorLayer->featureCount();
328 task->setProgress( i /
static_cast<double>( count ) * 100 );
330 expContext.setFeature( feature );
331 expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral(
"row_number" ), rownum,
true ) );
333 QVariant value = exp.evaluate( &expContext );
334 if ( exp.hasEvalError() )
336 calculationSuccess =
false;
337 error = exp.evalErrorString();
340 else if ( updatingGeom )
342 if ( value.userType() == qMetaTypeId<QgsGeometry>() )
344 QgsGeometry geom = value.value<QgsGeometry>();
345 mVectorLayer->changeGeometry( feature.
id(), geom );
351 mVectorLayer->changeAttributeValue( feature.
id(), mAttributeId, value, newField ? emptyAttribute : feature.
attributes().value( mAttributeId ) );
357 if ( !calculationSuccess )
359 cursorOverride.release();
361 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), tr(
"An error occurred while evaluating the calculation string:\n%1" ).arg( error ) );
362 mVectorLayer->destroyEditCommand();
366 mVectorLayer->endEditCommand();
367 if ( mNewFieldGroupBox->isChecked() )
369 pushMessage( tr(
"Field \"%1\" created successfully" ).arg( mOutputFieldNameLineEdit->text() ) );
371 else if ( mUpdateExistingGroupBox->isChecked() )
373 pushMessage( tr(
"Field \"%1\" updated successfully" ).arg( mExistingFieldComboBox->currentText() ) );
378void QgsFieldCalculator::populateOutputFieldTypes()
385 QgsVectorDataProvider *provider = mVectorLayer->dataProvider();
391 const int oldDataType = mOutputFieldTypeComboBox->currentData( Qt::UserRole +
FTC_TYPE_ROLE_IDX ).toInt();
393 mOutputFieldTypeComboBox->blockSignals(
true );
396 const QList<QgsVectorDataProvider::NativeType> &typelist = mCreateVirtualFieldCheckbox->isChecked() ? ( QList<QgsVectorDataProvider::NativeType>()
398 << QgsVectorDataProvider::NativeType(
QgsVariantUtils::typeToDisplayString( QMetaType::Type::Double ), QStringLiteral(
"double precision" ), QMetaType::Type::Double, -1, -1, -1, -1 )
403 << QgsVectorDataProvider::NativeType(
QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDateTime ), QStringLiteral(
"datetime" ), QMetaType::Type::QDateTime, -1, -1, -1, -1 )
405 << QgsVectorDataProvider::NativeType( tr(
"Text, unlimited length (text)" ), QStringLiteral(
"text" ), QMetaType::Type::QString, -1, -1, -1, -1 )
410 : provider->nativeTypes();
412 mOutputFieldTypeComboBox->clear();
413 for (
int i = 0; i < typelist.size(); i++ )
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 );
424 mOutputFieldTypeComboBox->blockSignals(
false );
426 const int idx = mOutputFieldTypeComboBox->findData( oldDataType, Qt::UserRole +
FTC_TYPE_ROLE_IDX );
429 mOutputFieldTypeComboBox->setCurrentIndex( idx );
430 mOutputFieldTypeComboBox_activated( idx );
434 mOutputFieldTypeComboBox->setCurrentIndex( 0 );
435 mOutputFieldTypeComboBox_activated( 0 );
439void QgsFieldCalculator::mNewFieldGroupBox_toggled(
bool on )
441 mUpdateExistingGroupBox->setChecked( !on );
442 if ( on && !mCanAddAttribute )
444 mOnlyVirtualFieldsInfoLabel->setVisible(
true );
448 mOnlyVirtualFieldsInfoLabel->setVisible(
false );
451 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
453 mEditModeAutoTurnOnLabel->setVisible(
false );
457 mEditModeAutoTurnOnLabel->setVisible(
true );
460 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
463void QgsFieldCalculator::mUpdateExistingGroupBox_toggled(
bool on )
465 mNewFieldGroupBox->setChecked( !on );
466 setDialogButtonState();
470 mOnlyUpdateSelectedCheckBox->setEnabled( mVectorLayer->selectedFeatureCount() > 0 );
474 mCreateVirtualFieldCheckbox_stateChanged( mCreateVirtualFieldCheckbox->checkState() );
478void QgsFieldCalculator::mCreateVirtualFieldCheckbox_stateChanged(
int state )
480 mOnlyUpdateSelectedCheckBox->setChecked(
false );
481 mOnlyUpdateSelectedCheckBox->setEnabled( state != Qt::Checked && mVectorLayer->selectedFeatureCount() > 0 );
483 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
485 mEditModeAutoTurnOnLabel->setVisible(
false );
489 mEditModeAutoTurnOnLabel->setVisible(
true );
491 populateOutputFieldTypes();
492 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
496void QgsFieldCalculator::mOutputFieldNameLineEdit_textChanged(
const QString &text )
499 setDialogButtonState();
503void QgsFieldCalculator::mOutputFieldTypeComboBox_activated(
int index )
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() );
513 setPrecisionMinMax();
516void QgsFieldCalculator::mExistingFieldComboBox_currentIndexChanged(
const int index )
519 setDialogButtonState();
522void QgsFieldCalculator::populateFields()
527 const QgsFields &fields = mVectorLayer->fields();
528 for (
int idx = 0; idx < fields.
count(); ++idx )
551 const QgsVectorLayerJoinInfo *info = mVectorLayer->joinBuffer()->joinForFieldIndex( idx, fields, srcFieldIndex );
560 const QString fieldName = fields.
at( idx ).
name();
563 mExistingFieldComboBox->addItem( fields.
iconForField( idx ), fieldName, idx );
568 mExistingFieldComboBox->addItem( tr(
"<geometry>" ),
"geom" );
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 );
574 mExistingFieldComboBox->setCurrentIndex( -1 );
577void QgsFieldCalculator::setDialogButtonState()
579 QList<QPushButton *> buttons = {
580 mButtonBox->button( QDialogButtonBox::Ok ),
581 mButtonBox->button( QDialogButtonBox::Apply )
584 bool enableButtons =
true;
587 if ( ( mNewFieldGroupBox->isChecked() || !mUpdateExistingGroupBox->isEnabled() )
588 && mOutputFieldNameLineEdit->text().isEmpty() )
590 tooltip = tr(
"Please enter a field name" );
591 enableButtons =
false;
593 else if ( ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
594 && mExistingFieldComboBox->currentIndex() == -1 )
596 tooltip = tr(
"Please select a field" );
597 enableButtons =
false;
599 else if ( builder->expressionText().isEmpty() )
601 tooltip = tr(
"Please insert an expression" );
602 enableButtons =
false;
604 else if ( !builder->isExpressionValid() )
606 tooltip = tr(
"The expression is invalid. See \"(more info)\" for details" );
607 enableButtons =
false;
610 for ( QPushButton *button : buttons )
614 button->setEnabled( enableButtons );
615 button->setToolTip( tooltip );
620void QgsFieldCalculator::setPrecisionMinMax()
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 );
630 if ( precisionIsEnabled )
632 mOutputFieldPrecisionSpinBox->setMinimum( minPrecType );
633 mOutputFieldPrecisionSpinBox->setMaximum( std::max( minPrecType, std::min( maxPrecType, mOutputFieldWidthSpinBox->value() ) ) );
637void QgsFieldCalculator::showHelp()
639 QgsHelp::openHelp( QStringLiteral(
"working_with_vector/attribute_table.html#editing-attribute-values" ) );
642QgsField QgsFieldCalculator::fieldDefinition()
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() ) );
647void QgsFieldCalculator::pushMessage(
const QString &text,
Qgis::MessageLevel level,
int duration )
649 mMsgBar->pushMessage( text, level, duration );
@ AddAttributes
Allows addition of new attributes (fields).
@ ChangeAttributeValues
Allows modification of attribute values.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFlags
No flags are set.
MessageLevel
Level for messages This will be used both for message log and message bar in application.
QFlags< VectorProviderCapability > VectorProviderCapabilities
Vector data provider capabilities.
@ Provider
Field originates from the underlying data provider of the vector layer.
@ Edit
Field has been temporarily added in editing mode.
@ Unknown
The field origin has not been specified.
@ Expression
Field is calculated from an expression.
@ Join
Field originates from a joined layer.
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.
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.
QgsFieldCalculator(QgsVectorLayer *vl, QWidget *parent=nullptr)
Encapsulate a field in an attribute table or data source.
bool convertCompatible(QVariant &v, QString *errorMessage=nullptr) const
Converts the provided variant to a compatible format.
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
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...
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
QString providerType() const
Returns the provider type (provider key) for this layer.
QgsCoordinateReferenceSystem crs
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.