38#include "moc_qgsfieldcalculator.cpp"
40using namespace Qt::StringLiterals;
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 );
72 const bool layerIsReadOnly { vl->
readOnly() };
80 expContext.setHighlightedVariables( QStringList() << u
"row_number"_s );
83 populateOutputFieldTypes();
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 );
93 builder->setGeomCalculator( myDa );
96 mOutputFieldWidthSpinBox->setValue( 10 );
97 mOutputFieldWidthSpinBox->setClearValue( 10 );
98 mOutputFieldPrecisionSpinBox->setValue( 3 );
99 mOutputFieldPrecisionSpinBox->setClearValue( 3 );
100 setPrecisionMinMax();
104 mOutputFieldNameLineEdit->setMaxLength( 10 );
107 if ( !mCanAddAttribute )
109 mCreateVirtualFieldCheckbox->setChecked(
true );
110 mCreateVirtualFieldCheckbox->setEnabled(
false );
111 mOnlyVirtualFieldsInfoLabel->setVisible(
true );
112 mInfoIcon->setVisible(
true );
116 mOnlyVirtualFieldsInfoLabel->setVisible(
false );
117 mInfoIcon->setVisible(
false );
120 if ( !mCanChangeAttributeValue )
122 mUpdateExistingGroupBox->setEnabled(
false );
123 mCreateVirtualFieldCheckbox->setChecked(
true );
124 mCreateVirtualFieldCheckbox->setEnabled(
false );
127 Q_ASSERT( mNewFieldGroupBox->isEnabled() || mUpdateExistingGroupBox->isEnabled() );
129 if ( mNewFieldGroupBox->isEnabled() )
131 mNewFieldGroupBox->setChecked(
true );
135 mNewFieldGroupBox->setToolTip( tr(
"Not available for layer" ) );
136 mUpdateExistingGroupBox->setChecked(
true );
137 mUpdateExistingGroupBox->setCheckable(
false );
140 if ( mUpdateExistingGroupBox->isEnabled() )
142 mUpdateExistingGroupBox->setChecked( !mNewFieldGroupBox->isEnabled() );
146 mUpdateExistingGroupBox->setToolTip( tr(
"Not available for layer" ) );
147 mNewFieldGroupBox->setChecked(
true );
148 mNewFieldGroupBox->setCheckable(
false );
151 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
153 mEditModeAutoTurnOnLabel->setVisible(
false );
154 mInfoIcon->setVisible(
false );
158 mInfoIcon->setVisible(
true );
162 mOnlyUpdateSelectedCheckBox->setChecked( mCanChangeAttributeValue && hasselection );
163 mOnlyUpdateSelectedCheckBox->setEnabled( mCanChangeAttributeValue && hasselection );
164 mOnlyUpdateSelectedCheckBox->setText( tr(
"Only update %n selected feature(s)",
nullptr, vl->
selectedFeatureCount() ) );
166 builder->initWithLayer( vl, expContext, u
"fieldcalc"_s );
168 mInfoIcon->setPixmap( style()->standardPixmap( QStyle::SP_MessageBoxInformation ) );
170 setWindowTitle( tr(
"%1 — Field Calculator" ).arg( mVectorLayer->name() ) );
174 mMsgBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
175 this->vLayout->insertWidget( 0, mMsgBar );
177 setDialogButtonState();
186void QgsFieldCalculator::calculate()
188 builder->expressionTree()->saveToRecent( builder->expressionText(), u
"fieldcalc"_s );
199 const QString calcString = builder->expressionText();
201 exp.setGeomCalculator( &myDa );
207 if ( !exp.prepare( &expContext ) )
209 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), exp.evalErrorString() );
213 bool updatingGeom =
false;
219 if ( !mUpdateExistingGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() )
221 mVectorLayer->addExpressionField( calcString, fieldDefinition() );
229 if ( mVectorLayer->readOnly() )
231 QMessageBox::critical(
nullptr, tr(
"Read-only layer" ), tr(
"The layer was marked read-only in the project properties and cannot be edited." ) );
235 if ( !mVectorLayer->isEditable() )
236 mVectorLayer->startEditing();
238 QgsTemporaryCursorOverride cursorOverride( Qt::WaitCursor );
240 mVectorLayer->beginEditCommand( u
"Field calculator"_s );
243 if ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
245 if ( mExistingFieldComboBox->currentData().toString() ==
"geom"_L1 )
254 const int id = mExistingFieldComboBox->currentData().toInt( &ok );
262 const QgsField newField = fieldDefinition();
264 if ( !mVectorLayer->addAttribute( newField ) )
266 cursorOverride.release();
267 QMessageBox::critical(
nullptr, tr(
"Create New Field" ), tr(
"Could not add the new field to the provider." ) );
268 mVectorLayer->destroyEditCommand();
273 const QgsFields &fields = mVectorLayer->fields();
275 for (
int idx = 0; idx < fields.
count(); ++idx )
277 if ( fields.
at( idx ).
name() == mOutputFieldNameLineEdit->text() )
285 expContext.setFields( mVectorLayer->fields() );
286 if ( !exp.prepare( &expContext ) )
288 cursorOverride.release();
289 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), exp.evalErrorString() );
294 if ( mAttributeId == -1 && !updatingGeom )
296 mVectorLayer->destroyEditCommand();
302 bool calculationSuccess =
true;
305 const bool useGeometry = exp.needsGeometry();
308 const QgsField field = !updatingGeom ? mVectorLayer->fields().at( mAttributeId ) : QgsField();
310 const bool newField = !mUpdateExistingGroupBox->isChecked();
311 QVariant emptyAttribute;
316 QSet<QString> referencedColumns = exp.referencedColumns();
317 referencedColumns.insert( field.
name() );
319 if ( mOnlyUpdateSelectedCheckBox->isChecked() )
323 QgsFeatureIterator fit = mVectorLayer->getFeatures( req );
325 auto task = std::make_unique<QgsScopedProxyProgressTask>( tr(
"Calculating field" ) );
326 const long long count = mOnlyUpdateSelectedCheckBox->isChecked() ? mVectorLayer->selectedFeatureCount() : mVectorLayer->featureCount();
331 task->setProgress( i /
static_cast<double>( count ) * 100 );
333 expContext.setFeature( feature );
334 expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( u
"row_number"_s, rownum,
true ) );
336 QVariant value = exp.evaluate( &expContext );
337 if ( exp.hasEvalError() )
339 calculationSuccess =
false;
340 error = exp.evalErrorString();
343 else if ( updatingGeom )
345 if ( value.userType() == qMetaTypeId<QgsGeometry>() )
347 QgsGeometry geom = value.value<QgsGeometry>();
348 mVectorLayer->changeGeometry( feature.
id(), geom );
354 mVectorLayer->changeAttributeValue( feature.
id(), mAttributeId, value, newField ? emptyAttribute : feature.
attributes().value( mAttributeId ) );
360 if ( !calculationSuccess )
362 cursorOverride.release();
364 QMessageBox::critical(
nullptr, tr(
"Evaluation Error" ), tr(
"An error occurred while evaluating the calculation string:\n%1" ).arg( error ) );
365 mVectorLayer->destroyEditCommand();
369 mVectorLayer->endEditCommand();
370 if ( mNewFieldGroupBox->isChecked() )
372 pushMessage( tr(
"Field \"%1\" created successfully" ).arg( mOutputFieldNameLineEdit->text() ) );
374 else if ( mUpdateExistingGroupBox->isChecked() )
376 pushMessage( tr(
"Field \"%1\" updated successfully" ).arg( mExistingFieldComboBox->currentText() ) );
381void QgsFieldCalculator::populateOutputFieldTypes()
388 QgsVectorDataProvider *provider = mVectorLayer->dataProvider();
394 const int oldDataType = mOutputFieldTypeComboBox->currentData( Qt::UserRole +
FTC_TYPE_ROLE_IDX ).toInt();
396 mOutputFieldTypeComboBox->blockSignals(
true );
399 const QList<QgsVectorDataProvider::NativeType> &typelist = mCreateVirtualFieldCheckbox->isChecked() ? ( QList<QgsVectorDataProvider::NativeType>()
408 << QgsVectorDataProvider::NativeType( tr(
"Text, unlimited length (text)" ), u
"text"_s, QMetaType::Type::QString, -1, -1, -1, -1 )
413 : provider->nativeTypes();
415 mOutputFieldTypeComboBox->clear();
416 for (
int i = 0; i < typelist.size(); i++ )
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 );
427 mOutputFieldTypeComboBox->blockSignals(
false );
429 const int idx = mOutputFieldTypeComboBox->findData( oldDataType, Qt::UserRole +
FTC_TYPE_ROLE_IDX );
432 mOutputFieldTypeComboBox->setCurrentIndex( idx );
433 mOutputFieldTypeComboBox_activated( idx );
437 mOutputFieldTypeComboBox->setCurrentIndex( 0 );
438 mOutputFieldTypeComboBox_activated( 0 );
442void QgsFieldCalculator::mNewFieldGroupBox_toggled(
bool on )
444 mUpdateExistingGroupBox->setChecked( !on );
445 if ( on && !mCanAddAttribute )
447 mOnlyVirtualFieldsInfoLabel->setVisible(
true );
451 mOnlyVirtualFieldsInfoLabel->setVisible(
false );
454 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
456 mEditModeAutoTurnOnLabel->setVisible(
false );
460 mEditModeAutoTurnOnLabel->setVisible(
true );
463 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
466void QgsFieldCalculator::mUpdateExistingGroupBox_toggled(
bool on )
468 mNewFieldGroupBox->setChecked( !on );
469 setDialogButtonState();
473 mOnlyUpdateSelectedCheckBox->setEnabled( mVectorLayer->selectedFeatureCount() > 0 );
477 mCreateVirtualFieldCheckbox_stateChanged( mCreateVirtualFieldCheckbox->checkState() );
481void QgsFieldCalculator::mCreateVirtualFieldCheckbox_stateChanged(
int state )
483 mOnlyUpdateSelectedCheckBox->setChecked(
false );
484 mOnlyUpdateSelectedCheckBox->setEnabled( state != Qt::Checked && mVectorLayer->selectedFeatureCount() > 0 );
486 if ( ( mNewFieldGroupBox->isChecked() && mCreateVirtualFieldCheckbox->isChecked() ) || mVectorLayer->isEditable() )
488 mEditModeAutoTurnOnLabel->setVisible(
false );
492 mEditModeAutoTurnOnLabel->setVisible(
true );
494 populateOutputFieldTypes();
495 mInfoIcon->setVisible( mOnlyVirtualFieldsInfoLabel->isVisible() || mEditModeAutoTurnOnLabel->isVisible() );
499void QgsFieldCalculator::mOutputFieldNameLineEdit_textChanged(
const QString &text )
502 setDialogButtonState();
506void QgsFieldCalculator::mOutputFieldTypeComboBox_activated(
int index )
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() );
516 setPrecisionMinMax();
519void QgsFieldCalculator::mExistingFieldComboBox_currentIndexChanged(
const int index )
522 setDialogButtonState();
525void QgsFieldCalculator::populateFields()
530 const QgsFields &fields = mVectorLayer->fields();
531 for (
int idx = 0; idx < fields.
count(); ++idx )
554 const QgsVectorLayerJoinInfo *info = mVectorLayer->joinBuffer()->joinForFieldIndex( idx, fields, srcFieldIndex );
563 const QString fieldName = fields.
at( idx ).
name();
566 mExistingFieldComboBox->addItem( fields.
iconForField( idx ), fieldName, idx );
571 mExistingFieldComboBox->addItem( tr(
"<geometry>" ),
"geom" );
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 );
577 mExistingFieldComboBox->setCurrentIndex( -1 );
580void QgsFieldCalculator::setDialogButtonState()
582 QList<QPushButton *> buttons = {
583 mButtonBox->button( QDialogButtonBox::Ok ),
584 mButtonBox->button( QDialogButtonBox::Apply )
587 bool enableButtons =
true;
590 if ( ( mNewFieldGroupBox->isChecked() || !mUpdateExistingGroupBox->isEnabled() )
591 && mOutputFieldNameLineEdit->text().isEmpty() )
593 tooltip = tr(
"Please enter a field name" );
594 enableButtons =
false;
596 else if ( ( mUpdateExistingGroupBox->isChecked() || !mNewFieldGroupBox->isEnabled() )
597 && mExistingFieldComboBox->currentIndex() == -1 )
599 tooltip = tr(
"Please select a field" );
600 enableButtons =
false;
602 else if ( builder->expressionText().isEmpty() )
604 tooltip = tr(
"Please insert an expression" );
605 enableButtons =
false;
607 else if ( !builder->isExpressionValid() )
609 tooltip = tr(
"The expression is invalid. See \"(more info)\" for details" );
610 enableButtons =
false;
613 for ( QPushButton *button : buttons )
617 button->setEnabled( enableButtons );
618 button->setToolTip( tooltip );
623void QgsFieldCalculator::setPrecisionMinMax()
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 );
633 if ( precisionIsEnabled )
635 mOutputFieldPrecisionSpinBox->setMinimum( minPrecType );
636 mOutputFieldPrecisionSpinBox->setMaximum( std::max( minPrecType, std::min( maxPrecType, mOutputFieldWidthSpinBox->value() ) ) );
640void QgsFieldCalculator::showHelp()
642 QgsHelp::openHelp( u
"working_with_vector/attribute_table.html#editing-attribute-values"_s );
645QgsField QgsFieldCalculator::fieldDefinition()
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() ) );
650void QgsFieldCalculator::pushMessage(
const QString &text,
Qgis::MessageLevel level,
int duration )
652 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.