diff --git a/python/PyQt6/core/auto_generated/dxf/qgsdxfexport.sip.in b/python/PyQt6/core/auto_generated/dxf/qgsdxfexport.sip.in index 98c957ce874b..9ca5450c6730 100644 --- a/python/PyQt6/core/auto_generated/dxf/qgsdxfexport.sip.in +++ b/python/PyQt6/core/auto_generated/dxf/qgsdxfexport.sip.in @@ -27,7 +27,7 @@ Exports QGIS layers to the DXF format. struct DxfLayer { - DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1 ); + DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1, QString overriddenName = QString() ); QgsVectorLayer *layer() const; %Docstring @@ -66,6 +66,13 @@ Returns the maximum number of data defined symbol classes for which blocks are c :return: +.. versionadded:: 3.38 +%End + + QString overriddenName() const; +%Docstring +Returns the overridden layer name to be used in the exported DXF. + .. versionadded:: 3.38 %End diff --git a/python/core/auto_generated/dxf/qgsdxfexport.sip.in b/python/core/auto_generated/dxf/qgsdxfexport.sip.in index 67ede91a94d6..61e474a533bd 100644 --- a/python/core/auto_generated/dxf/qgsdxfexport.sip.in +++ b/python/core/auto_generated/dxf/qgsdxfexport.sip.in @@ -27,7 +27,7 @@ Exports QGIS layers to the DXF format. struct DxfLayer { - DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1 ); + DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1, QString overriddenName = QString() ); QgsVectorLayer *layer() const; %Docstring @@ -66,6 +66,13 @@ Returns the maximum number of data defined symbol classes for which blocks are c :return: +.. versionadded:: 3.38 +%End + + QString overriddenName() const; +%Docstring +Returns the overridden layer name to be used in the exported DXF. + .. versionadded:: 3.38 %End diff --git a/src/analysis/processing/qgsalgorithmdxfexport.cpp b/src/analysis/processing/qgsalgorithmdxfexport.cpp index 123781eb9ba2..9e625715923d 100644 --- a/src/analysis/processing/qgsalgorithmdxfexport.cpp +++ b/src/analysis/processing/qgsalgorithmdxfexport.cpp @@ -47,7 +47,8 @@ QString QgsDxfExportAlgorithm::groupId() const QString QgsDxfExportAlgorithm::shortHelpString() const { - return QObject::tr( "Exports layers to DXF file. For each layer, you can choose a field whose values are used to split features in generated destination layers in the DXF output." ); + return QObject::tr( "Exports layers to DXF file. For each layer, you can choose a field whose values are used to split features in generated destination layers in the DXF output.\n\n" + "If no field is chosen, you can still override the output layer name by directly entering a new output layer name in the Configure Layer panel or by preferring layer title (set in layer properties) to layer name." ); } QgsDxfExportAlgorithm *QgsDxfExportAlgorithm::createInstance() const @@ -70,7 +71,9 @@ void QgsDxfExportAlgorithm::initAlgorithm( const QVariantMap & ) extentParam->setHelp( QObject::tr( "Limit exported features to those with geometries intersecting the provided extent" ) ); addParameter( extentParam.release() ); addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SELECTED_FEATURES_ONLY" ), QObject::tr( "Use only selected features" ), false ) ); - addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_LAYER_TITLE" ), QObject::tr( "Use layer title as name" ), false ) ); + std::unique_ptr useTitleParam = std::make_unique( QStringLiteral( "USE_LAYER_TITLE" ), QObject::tr( "Use layer title as name" ), false ); + useTitleParam->setHelp( QObject::tr( "If no attribute is chosen and layer name is not being overridden, prefer layer title (set in layer properties) to layer name" ) ); + addParameter( useTitleParam.release() ); addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "FORCE_2D" ), QObject::tr( "Force 2D output" ), false ) ); addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "MTEXT" ), QObject::tr( "Export labels as MTEXT elements" ), true ) ); addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "DXF" ), QObject::tr( "DXF Files" ) + " (*.dxf *.DXF)" ) ); diff --git a/src/app/qgsdxfexportdialog.cpp b/src/app/qgsdxfexportdialog.cpp index 9db2a40c3c58..4e58c1bc0118 100644 --- a/src/app/qgsdxfexportdialog.cpp +++ b/src/app/qgsdxfexportdialog.cpp @@ -49,7 +49,24 @@ QWidget *FieldSelectorDelegate::createEditor( QWidget *parent, const QStyleOptio { Q_UNUSED( option ) - if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL ) + QgsVectorLayer *vl = indexToLayer( index.model(), index ); + if ( !vl ) + return nullptr; + + if ( index.column() == LAYER_COL ) + { + QgsFilterLineEdit *le = new QgsFilterLineEdit( parent, vl->name() ); + + return le; + } + else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) + { + QgsFieldComboBox *w = new QgsFieldComboBox( parent ); + w->setLayer( vl ); + w->setAllowEmptyFieldName( true ); + return w; + } + else if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL ) { return nullptr; } @@ -60,44 +77,68 @@ QWidget *FieldSelectorDelegate::createEditor( QWidget *parent, const QStyleOptio return le; } - QgsVectorLayer *vl = indexToLayer( index.model(), index ); - if ( !vl ) - return nullptr; - - QgsFieldComboBox *w = new QgsFieldComboBox( parent ); - w->setLayer( vl ); - w->setAllowEmptyFieldName( true ); - return w; + return nullptr; } void FieldSelectorDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const { - if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL ) + QgsVectorLayer *vl = indexToLayer( index.model(), index ); + if ( !vl ) + return; + + if ( index.column() == LAYER_COL ) + { + QgsFilterLineEdit *le = qobject_cast< QgsFilterLineEdit * >( editor ); + if ( le ) + { + le->setText( index.data().toString() ); + } + } + else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) + { + QgsFieldComboBox *fcb = qobject_cast( editor ); + if ( !fcb ) + return; + + int idx = attributeIndex( index.model(), vl ); + if ( vl->fields().exists( idx ) ) + { + fcb->setField( vl->fields().at( idx ).name() ); + } + } + else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL ) { QLineEdit *le = qobject_cast( editor ); if ( le ) { le->setText( index.data().toString() ); } - return; } +} +void FieldSelectorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const +{ QgsVectorLayer *vl = indexToLayer( index.model(), index ); if ( !vl ) return; - QgsFieldComboBox *fcb = qobject_cast( editor ); - if ( !fcb ) - return; - - int idx = attributeIndex( index.model(), vl ); - if ( vl->fields().exists( idx ) ) - fcb->setField( vl->fields().at( idx ).name() ); -} + if ( index.column() == LAYER_COL ) + { + QgsFilterLineEdit *le = qobject_cast( editor ); + if ( le ) + { + model->setData( index, le->text() ); + } + } + else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) + { + QgsFieldComboBox *fcb = qobject_cast( editor ); + if ( !fcb ) + return; -void FieldSelectorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const -{ - if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL ) + model->setData( index, vl->fields().lookupField( fcb->currentField() ) ); + } + else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL ) { QLineEdit *le = qobject_cast( editor ); if ( le ) @@ -105,16 +146,6 @@ void FieldSelectorDelegate::setModelData( QWidget *editor, QAbstractItemModel *m model->setData( index, le->text().toInt() ); } } - - QgsVectorLayer *vl = indexToLayer( index.model(), index ); - if ( !vl ) - return; - - QgsFieldComboBox *fcb = qobject_cast( editor ); - if ( !fcb ) - return; - - model->setData( index, vl->fields().lookupField( fcb->currentField() ) ); } QgsVectorLayer *FieldSelectorDelegate::indexToLayer( const QAbstractItemModel *model, const QModelIndex &index ) const @@ -167,7 +198,7 @@ Qt::ItemFlags QgsVectorLayerAndAttributeModel::flags( const QModelIndex &index ) QgsVectorLayer *vl = vectorLayer( index ); if ( index.column() == LAYER_COL ) { - return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + return vl ? Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable : Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; } else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) { @@ -229,6 +260,7 @@ QVariant QgsVectorLayerAndAttributeModel::headerData( int section, Qt::Orientati QVariant QgsVectorLayerAndAttributeModel::data( const QModelIndex &idx, int role ) const { QgsVectorLayer *vl = vectorLayer( idx ); + if ( idx.column() == LAYER_COL ) { if ( role == Qt::CheckStateRole ) @@ -277,8 +309,33 @@ QVariant QgsVectorLayerAndAttributeModel::data( const QModelIndex &idx, int role Q_ASSERT( hasUnchecked ); return Qt::Unchecked; } + else if ( role == Qt::DisplayRole && vl && mOverriddenName.contains( vl ) ) + { + return mOverriddenName[ vl ]; + } else + { return QgsLayerTreeModel::data( idx, role ); + } + } + else if ( idx.column() == OUTPUT_LAYER_ATTRIBUTE_COL && vl ) + { + int idx = mAttributeIdx.value( vl, -1 ); + if ( role == Qt::EditRole ) + return idx; + + if ( role == Qt::DisplayRole ) + { + if ( vl->fields().exists( idx ) ) + return vl->fields().at( idx ).name(); + else + return mOverriddenName.contains( vl ) ? mOverriddenName[ vl ] : vl->name(); + } + + if ( role == Qt::ToolTipRole ) + { + return tr( "Attribute containing the name of the destination layer in the DXF output." ); + } } else if ( idx.column() == ALLOW_DD_SYMBOL_BLOCKS_COL ) { @@ -317,74 +374,66 @@ QVariant QgsVectorLayerAndAttributeModel::data( const QModelIndex &idx, int role } } - - if ( idx.column() == OUTPUT_LAYER_ATTRIBUTE_COL && vl ) - { - int idx = mAttributeIdx.value( vl, -1 ); - if ( role == Qt::EditRole ) - return idx; - - if ( role == Qt::DisplayRole ) - { - if ( vl->fields().exists( idx ) ) - return vl->fields().at( idx ).name(); - else - return vl->name(); - } - - if ( role == Qt::ToolTipRole ) - { - return tr( "Attribute containing the name of the destination layer in the DXF output." ); - } - } - return QVariant(); } bool QgsVectorLayerAndAttributeModel::setData( const QModelIndex &index, const QVariant &value, int role ) { - if ( index.column() == LAYER_COL && role == Qt::CheckStateRole ) + QgsVectorLayer *vl = vectorLayer( index ); + + if ( index.column() == LAYER_COL ) { - int i = 0; - for ( i = 0; ; i++ ) + if ( role == Qt::CheckStateRole ) { - QModelIndex child = QgsVectorLayerAndAttributeModel::index( i, 0, index ); - if ( !child.isValid() ) - break; + int i = 0; + for ( i = 0; ; i++ ) + { + QModelIndex child = QgsVectorLayerAndAttributeModel::index( i, 0, index ); + if ( !child.isValid() ) + break; - setData( child, value, role ); - } + setData( child, value, role ); + } + + if ( i == 0 ) + { + if ( value.toInt() == Qt::Checked ) + mCheckedLeafs.insert( index ); + else if ( value.toInt() == Qt::Unchecked ) + mCheckedLeafs.remove( index ); + else + Q_ASSERT( "expected checked or unchecked" ); - if ( i == 0 ) + emit dataChanged( QModelIndex(), index ); + } + + return true; + } + else if ( role == Qt::EditRole ) { - if ( value.toInt() == Qt::Checked ) - mCheckedLeafs.insert( index ); - else if ( value.toInt() == Qt::Unchecked ) - mCheckedLeafs.remove( index ); + if ( !value.toString().trimmed().isEmpty() && value.toString() != vl->name() ) + { + mOverriddenName[ vl ] = value.toString(); + } else - Q_ASSERT( "expected checked or unchecked" ); - - emit dataChanged( QModelIndex(), index ); + { + mOverriddenName.remove( vl ); + } + return true; } - - return true; } - - QgsVectorLayer *vl = vectorLayer( index ); - if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) + else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL ) { if ( role != Qt::EditRole ) return false; - if ( vl ) { mAttributeIdx[ vl ] = value.toInt(); return true; } } - - if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL && role == Qt::CheckStateRole ) + else if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL && role == Qt::CheckStateRole ) { if ( vl ) { @@ -423,7 +472,11 @@ QList< QgsDxfExport::DxfLayer > QgsVectorLayerAndAttributeModel::layers() const if ( !layerIdx.contains( vl->id() ) ) { layerIdx.insert( vl->id(), layers.size() ); - layers << QgsDxfExport::DxfLayer( vl, mAttributeIdx.value( vl, -1 ), mCreateDDBlockInfo.value( vl, true ), mDDBlocksMaxNumberOfClasses.value( vl, -1 ) ); + layers << QgsDxfExport::DxfLayer( vl, + mAttributeIdx.value( vl, -1 ), + mCreateDDBlockInfo.value( vl, true ), + mDDBlocksMaxNumberOfClasses.value( vl, -1 ), + mOverriddenName.value( vl, QString() ) ); } } } @@ -434,7 +487,11 @@ QList< QgsDxfExport::DxfLayer > QgsVectorLayerAndAttributeModel::layers() const if ( !layerIdx.contains( vl->id() ) ) { layerIdx.insert( vl->id(), layers.size() ); - layers << QgsDxfExport::DxfLayer( vl, mAttributeIdx.value( vl, -1 ), mCreateDDBlockInfo.value( vl, true ), mDDBlocksMaxNumberOfClasses.value( vl, -1 ) ); + layers << QgsDxfExport::DxfLayer( vl, + mAttributeIdx.value( vl, -1 ), + mCreateDDBlockInfo.value( vl, true ), + mDDBlocksMaxNumberOfClasses.value( vl, -1 ), + mOverriddenName.value( vl, QString() ) ); } } } @@ -770,7 +827,8 @@ void QgsDxfExportDialog::cleanGroup( QgsLayerTreeNode *node ) { if ( QgsLayerTree::isLayer( child ) && ( QgsLayerTree::toLayer( child )->layer()->type() != Qgis::LayerType::Vector || - ! QgsLayerTree::toLayer( child )->layer()->isSpatial() ) ) + ! QgsLayerTree::toLayer( child )->layer()->isSpatial() || + ! QgsLayerTree::toLayer( child )->layer()->isValid() ) ) { toRemove << child; continue; @@ -1051,7 +1109,7 @@ void QgsDxfExportDialog::saveSettingsToXML( QDomDocument &doc ) const QgsVectorLayerRef vlRef; const QgsReadWriteContext rwContext = QgsReadWriteContext(); - for ( const auto dxfLayer : layers() ) + for ( const auto &dxfLayer : layers() ) { QDomElement layerElement = domDocument.createElement( QStringLiteral( "layer" ) ); vlRef.setLayer( dxfLayer.layer() ); diff --git a/src/app/qgsdxfexportdialog.h b/src/app/qgsdxfexportdialog.h index d58d0059b460..0a696617f653 100644 --- a/src/app/qgsdxfexportdialog.h +++ b/src/app/qgsdxfexportdialog.h @@ -81,6 +81,7 @@ class QgsVectorLayerAndAttributeModel : public QgsLayerTreeModel QHash mAttributeIdx; QHash mCreateDDBlockInfo; QHash mDDBlocksMaxNumberOfClasses; + QHash mOverriddenName; QSet mCheckedLeafs; void applyVisibility( QSet &visibleLayers, QgsLayerTreeNode *node ); diff --git a/src/core/dxf/qgsdxfexport.cpp b/src/core/dxf/qgsdxfexport.cpp index c974fa15fed8..b34c48c0b370 100644 --- a/src/core/dxf/qgsdxfexport.cpp +++ b/src/core/dxf/qgsdxfexport.cpp @@ -89,17 +89,24 @@ void QgsDxfExport::addLayers( const QList &layers ) { mLayerList.clear(); mLayerNameAttribute.clear(); + mLayerOverriddenName.clear(); mLayerList.reserve( layers.size() ); for ( const DxfLayer &dxfLayer : layers ) { mLayerList << dxfLayer.layer(); if ( dxfLayer.layerOutputAttributeIndex() >= 0 ) + { mLayerNameAttribute.insert( dxfLayer.layer()->id(), dxfLayer.layerOutputAttributeIndex() ); + } if ( dxfLayer.buildDataDefinedBlocks() ) { mLayerDDBlockMaxNumberOfClasses.insert( dxfLayer.layer()->id(), dxfLayer.dataDefinedBlocksMaximumNumberOfClasses() ); } + if ( dxfLayer.overriddenName() != QString() ) + { + mLayerOverriddenName.insert( dxfLayer.layer()->id(), dxfLayer.overriddenName() ); + } } } @@ -776,7 +783,7 @@ void QgsDxfExport::writeEntities() while ( featureIt.nextFeature( fet ) ) { mRenderContext.expressionContext().setFeature( fet ); - QString lName( dxfLayerName( job->splitLayerAttribute.isNull() ? job->layerTitle : fet.attribute( job->splitLayerAttribute ).toString() ) ); + QString lName( dxfLayerName( job->splitLayerAttribute.isNull() ? job->layerDerivedName : fet.attribute( job->splitLayerAttribute ).toString() ) ); sctx.setFeature( &fet ); @@ -903,7 +910,7 @@ void QgsDxfExport::prepareRenderers() const QgsFields fields = vl->fields(); if ( splitLayerAttributeIndex >= 0 && splitLayerAttributeIndex < fields.size() ) splitLayerAttribute = fields.at( splitLayerAttributeIndex ).name(); - DxfLayerJob *job = new DxfLayerJob( vl, mMapSettings.layerStyleOverrides().value( vl->id() ), mRenderContext, this, splitLayerAttribute ); + DxfLayerJob *job = new DxfLayerJob( vl, mMapSettings.layerStyleOverrides().value( vl->id() ), mRenderContext, this, splitLayerAttribute, layerName( vl ) ); mJobs.append( job ); } } @@ -2384,9 +2391,18 @@ QStringList QgsDxfExport::encodings() QString QgsDxfExport::layerName( QgsVectorLayer *vl ) const { Q_ASSERT( vl ); - return mLayerTitleAsName && ( !vl->metadata().title().isEmpty() || !vl->serverProperties()->title().isEmpty() ) - ? ( !vl->metadata().title().isEmpty() ? vl->metadata().title() : vl->serverProperties()->title() ) - : vl->name(); + if ( !mLayerOverriddenName.value( vl->id(), QString() ).isEmpty() ) + { + return mLayerOverriddenName.value( vl->id() ); + } + else if ( mLayerTitleAsName && ( !vl->metadata().title().isEmpty() || !vl->serverProperties()->title().isEmpty() ) ) + { + return !vl->metadata().title().isEmpty() ? vl->metadata().title() : vl->serverProperties()->title(); + } + else + { + return vl->name(); + } } void QgsDxfExport::drawLabel( const QString &layerId, QgsRenderContext &context, pal::LabelPosition *label, const QgsPalLayerSettings &settings ) diff --git a/src/core/dxf/qgsdxfexport.h b/src/core/dxf/qgsdxfexport.h index 110ef2f7e1b9..54a213bc4fa9 100644 --- a/src/core/dxf/qgsdxfexport.h +++ b/src/core/dxf/qgsdxfexport.h @@ -73,11 +73,12 @@ class CORE_EXPORT QgsDxfExport : public QgsLabelSink */ struct CORE_EXPORT DxfLayer { - DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1 ) + DxfLayer( QgsVectorLayer *vl, int layerOutputAttributeIndex = -1, bool buildDDBlocks = true, int ddBlocksMaxNumberOfClasses = -1, QString overriddenName = QString() ) : mLayer( vl ) , mLayerOutputAttributeIndex( layerOutputAttributeIndex ) , mBuildDDBlocks( buildDDBlocks ) , mDDBlocksMaxNumberOfClasses( ddBlocksMaxNumberOfClasses ) + , mOverriddenName( overriddenName ) {} //! Returns the layer @@ -112,6 +113,12 @@ class CORE_EXPORT QgsDxfExport : public QgsLabelSink */ int dataDefinedBlocksMaximumNumberOfClasses() const { return mDDBlocksMaxNumberOfClasses; } + /** + * \brief Returns the overridden layer name to be used in the exported DXF. + * \since QGIS 3.38 + */ + QString overriddenName() const { return mOverriddenName; } + private: QgsVectorLayer *mLayer = nullptr; int mLayerOutputAttributeIndex = -1; @@ -125,6 +132,11 @@ class CORE_EXPORT QgsDxfExport : public QgsLabelSink * \brief Limit for the number of data defined symbol block classes (keep only the most used ones). -1 means no limit */ int mDDBlocksMaxNumberOfClasses = -1; + + /** + * \brief Overridden name of the layer to be exported to DXF + */ + QString mOverriddenName; }; //! Export flags @@ -667,6 +679,7 @@ class CORE_EXPORT QgsDxfExport : public QgsLabelSink QList mLayerList; QHash mLayerNameAttribute; QHash mLayerDDBlockMaxNumberOfClasses; + QHash mLayerOverriddenName; double mFactor = 1.0; bool mForce2d = false; diff --git a/src/core/dxf/qgsdxfexport_p.h b/src/core/dxf/qgsdxfexport_p.h index 742323aa3cb4..cb00d13f0be8 100644 --- a/src/core/dxf/qgsdxfexport_p.h +++ b/src/core/dxf/qgsdxfexport_p.h @@ -33,7 +33,7 @@ */ struct DxfLayerJob { - DxfLayerJob( QgsVectorLayer *vl, const QString &layerStyleOverride, QgsRenderContext &renderContext, QgsDxfExport *dxfExport, const QString &splitLayerAttribute ) + DxfLayerJob( QgsVectorLayer *vl, const QString &layerStyleOverride, QgsRenderContext &renderContext, QgsDxfExport *dxfExport, const QString &splitLayerAttribute, const QString &layerDerivedName ) : renderContext( renderContext ) , styleOverride( vl ) , featureSource( vl ) @@ -41,8 +41,7 @@ struct DxfLayerJob , crs( vl->crs() ) , layerName( vl->name() ) , splitLayerAttribute( splitLayerAttribute ) - , layerTitle( !vl->metadata().title().isEmpty() ? vl->metadata().title() - : vl->serverProperties()->title().isEmpty() ? vl->name() : vl->serverProperties()->title() ) + , layerDerivedName( layerDerivedName ) { if ( !layerStyleOverride.isNull() ) { @@ -106,7 +105,7 @@ struct DxfLayerJob QgsLabelSinkProvider *labelProvider = nullptr; QgsRuleBasedLabelSinkProvider *ruleBasedLabelProvider = nullptr; QString splitLayerAttribute; - QString layerTitle; + QString layerDerivedName; // Obtained from overridden name, title or layer name QSet attributes; private: diff --git a/src/core/processing/qgsprocessingparameterdxflayers.cpp b/src/core/processing/qgsprocessingparameterdxflayers.cpp index 816e989363fe..ff9eb832d2ff 100644 --- a/src/core/processing/qgsprocessingparameterdxflayers.cpp +++ b/src/core/processing/qgsprocessingparameterdxflayers.cpp @@ -85,7 +85,7 @@ bool QgsProcessingParameterDxfLayers::checkValueIsAcceptable( const QVariant &in { const QVariantMap layerMap = variantLayer.toMap(); - if ( !layerMap.contains( QStringLiteral( "layer" ) ) && !layerMap.contains( QStringLiteral( "attributeIndex" ) ) ) + if ( !layerMap.contains( QStringLiteral( "layer" ) ) && !layerMap.contains( QStringLiteral( "attributeIndex" ) ) && !layerMap.contains( QStringLiteral( "overriddenLayerName" ) ) ) return false; if ( !context ) @@ -144,9 +144,12 @@ QString QgsProcessingParameterDxfLayers::valueAsPythonString( const QVariant &va { QStringList layerDefParts; layerDefParts << QStringLiteral( "'layer': " ) + QgsProcessingUtils::stringToPythonLiteral( QgsProcessingUtils::normalizeLayerSource( layer.layer()->source() ) ); + if ( layer.layerOutputAttributeIndex() >= -1 ) layerDefParts << QStringLiteral( "'attributeIndex': " ) + QgsProcessingUtils::variantToPythonLiteral( layer.layerOutputAttributeIndex() ); + layerDefParts << QStringLiteral( "'overriddenLayerName': " ) + QgsProcessingUtils::stringToPythonLiteral( layer.overriddenName() ); + const QString layerDef = QStringLiteral( "{%1}" ).arg( layerDefParts.join( ',' ) ); parts << layerDef; } @@ -239,7 +242,11 @@ QgsDxfExport::DxfLayer QgsProcessingParameterDxfLayers::variantMapAsLayer( const // bad } - QgsDxfExport::DxfLayer dxfLayer( inputLayer, layerVariantMap[ QStringLiteral( "attributeIndex" ) ].toInt() ); + QgsDxfExport::DxfLayer dxfLayer( inputLayer, + layerVariantMap[ QStringLiteral( "attributeIndex" ) ].toInt(), + false, + -1, + layerVariantMap[ QStringLiteral( "overriddenLayerName" ) ].toString() ); return dxfLayer; } @@ -251,5 +258,6 @@ QVariantMap QgsProcessingParameterDxfLayers::layerAsVariantMap( const QgsDxfExpo vm[ QStringLiteral( "layer" )] = layer.layer()->id(); vm[ QStringLiteral( "attributeIndex" ) ] = layer.layerOutputAttributeIndex(); + vm[ QStringLiteral( "overriddenLayerName" ) ] = layer.overriddenName(); return vm; } diff --git a/src/gui/processing/qgsprocessingdxflayerswidgetwrapper.cpp b/src/gui/processing/qgsprocessingdxflayerswidgetwrapper.cpp index a3b87d359741..56b4220ffbb6 100644 --- a/src/gui/processing/qgsprocessingdxflayerswidgetwrapper.cpp +++ b/src/gui/processing/qgsprocessingdxflayerswidgetwrapper.cpp @@ -51,14 +51,16 @@ QgsProcessingDxfLayerDetailsWidget::QgsProcessingDxfLayerDetailsWidget( const QV if ( mLayer->fields().exists( layer.layerOutputAttributeIndex() ) ) mFieldsComboBox->setField( mLayer->fields().at( layer.layerOutputAttributeIndex() ).name() ); + mOverriddenName->setText( layer.overriddenName() ); connect( mFieldsComboBox, &QgsFieldComboBox::fieldChanged, this, &QgsPanelWidget::widgetChanged ); + connect( mOverriddenName, &QLineEdit::textChanged, this, &QgsPanelWidget::widgetChanged ); } QVariant QgsProcessingDxfLayerDetailsWidget::value() const { const int index = mLayer->fields().lookupField( mFieldsComboBox->currentField() ); - const QgsDxfExport::DxfLayer layer( mLayer, index ); + const QgsDxfExport::DxfLayer layer( mLayer, index, false, -1, mOverriddenName->text().trimmed() ); return QgsProcessingParameterDxfLayers::layerAsVariantMap( layer ); } @@ -104,6 +106,7 @@ QgsProcessingDxfLayersPanelWidget::QgsProcessingDxfLayersPanelWidget( QVariantMap vm; vm["layer"] = layer->id(); vm["attributeIndex"] = -1; + vm["overriddenLayerName"] = QString(); const QString title = layer->name(); addOption( vm, title, false ); @@ -166,8 +169,19 @@ QString QgsProcessingDxfLayersPanelWidget::titleForLayer( const QgsDxfExport::Dx { QString title = layer.layer()->name(); + // if both options are set, the split attribute takes precedence, + // so hide overridden message to give users a hint on the result. if ( layer.layerOutputAttributeIndex() != -1 ) + { title += tr( " [split attribute: %1]" ).arg( layer.splitLayerAttribute() ); + } + else + { + if ( !layer.overriddenName().isEmpty() ) + { + title += tr( " [overridden name: %1]" ).arg( layer.overriddenName() ); + } + } return title; } diff --git a/src/ui/processing/qgsprocessingdxflayerdetailswidgetbase.ui b/src/ui/processing/qgsprocessingdxflayerdetailswidgetbase.ui index ef88ed6fcd66..542faa8f7484 100644 --- a/src/ui/processing/qgsprocessingdxflayerdetailswidgetbase.ui +++ b/src/ui/processing/qgsprocessingdxflayerdetailswidgetbase.ui @@ -7,28 +7,21 @@ 0 0 393 - 71 + 144 - - - - Attribute - - + + - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + Qt::Vertical @@ -41,6 +34,23 @@ + + + + Attribute + + + + + + + Output layer name + + + + + + diff --git a/src/ui/qgsdxfexportdialogbase.ui b/src/ui/qgsdxfexportdialogbase.ui index 679c46bf1b2c..39f0bd71b904 100644 --- a/src/ui/qgsdxfexportdialogbase.ui +++ b/src/ui/qgsdxfexportdialogbase.ui @@ -171,6 +171,9 @@ + + If no attribute is chosen and layer name is not being overridden, prefer layer title (set in layer properties) to layer name. + Use layer title as name if set diff --git a/tests/src/analysis/testqgsprocessing.cpp b/tests/src/analysis/testqgsprocessing.cpp index 6c50b7bee197..5a640d857db8 100644 --- a/tests/src/analysis/testqgsprocessing.cpp +++ b/tests/src/analysis/testqgsprocessing.cpp @@ -11041,6 +11041,7 @@ void TestQgsProcessing::parameterDxfLayers() QVERIFY( !def->checkValueIsAcceptable( layerList ) ); layerMap["layer"] = "layerName"; layerMap["attributeIndex"] = -1; + layerMap["overriddenLayerName"] = QString(); layerList[0] = layerMap; QVERIFY( def->checkValueIsAcceptable( layerList ) ); QVERIFY( !def->checkValueIsAcceptable( layerList, &context ) ); //no corresponding layer in the context's project @@ -11053,6 +11054,10 @@ void TestQgsProcessing::parameterDxfLayers() layerList[0] = layerMap; QVERIFY( def->checkValueIsAcceptable( layerList, &context ) ); + layerMap["overriddenLayerName"] = QStringLiteral( "My Point Layer" ); + layerList[0] = layerMap; + QVERIFY( def->checkValueIsAcceptable( layerList, &context ) ); + // checkValueIsAcceptable on non-spatial layers QgsVectorLayer *nonSpatialLayer = new QgsVectorLayer( QStringLiteral( "None" ), QStringLiteral( "NonSpatialLayer" ), @@ -11074,15 +11079,16 @@ void TestQgsProcessing::parameterDxfLayers() QVariantMap wrongLayerMap; wrongLayerMap["layer"] = "NonSpatialLayer"; wrongLayerMap["attributeIndex"] = -1; + wrongLayerMap["overriddenLayerName"] = QString(); QVariantList wrongLayerMapList; wrongLayerMapList.append( wrongLayerMap ); QVERIFY( !def->checkValueIsAcceptable( wrongLayerMapList, &context ) ); // Check values const QString valueAsPythonString = def->valueAsPythonString( layerList, context ); - QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1}]" ).arg( vectorLayer->source() ) ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1,'overriddenLayerName': 'My Point Layer'}]" ).arg( vectorLayer->source() ) ); QCOMPARE( QString::fromStdString( QgsJsonUtils::jsonFromVariant( def->valueAsJsonObject( layerList, context ) ).dump() ), - QStringLiteral( "[{\"attributeIndex\":-1,\"layer\":\"memory://%1\"}]" ).arg( vectorLayer->source() ) ); + QStringLiteral( "[{\"attributeIndex\":-1,\"layer\":\"memory://%1\",\"overriddenLayerName\":\"My Point Layer\"}]" ).arg( vectorLayer->source() ) ); bool ok = false; QCOMPARE( def->valueAsString( layerList, context, ok ), QString() ); QVERIFY( !ok ); @@ -11093,16 +11099,23 @@ void TestQgsProcessing::parameterDxfLayers() const QString pythonCode = def->asPythonString(); QCOMPARE( pythonCode, QStringLiteral( "QgsProcessingParameterDxfLayers('dxf input layer', '')" ) ); + // Default values for parameters other than the vector layer + layerMap["overriddenLayerName"] = QString(); + layerList[0] = layerMap; + const QgsDxfExport::DxfLayer dxfLayer( vectorLayer ); QList dxfList = def->parameterAsLayers( QVariant( vectorLayer->source() ), context ); QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() ); QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() ); + QCOMPARE( dxfList.at( 0 ).overriddenName(), dxfLayer.overriddenName() ); dxfList = def->parameterAsLayers( QVariant( QStringList() << vectorLayer->source() ), context ); QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() ); QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() ); + QCOMPARE( dxfList.at( 0 ).overriddenName(), dxfLayer.overriddenName() ); dxfList = def->parameterAsLayers( layerList, context ); QCOMPARE( dxfList.at( 0 ).layer()->source(), dxfLayer.layer()->source() ); QCOMPARE( dxfList.at( 0 ).layerOutputAttributeIndex(), dxfLayer.layerOutputAttributeIndex() ); + QCOMPARE( dxfList.at( 0 ).overriddenName(), dxfLayer.overriddenName() ); } void TestQgsProcessing::parameterAnnotationLayer() diff --git a/tests/src/core/testqgsdxfexport.cpp b/tests/src/core/testqgsdxfexport.cpp index adfe17feb894..f409bbc18e66 100644 --- a/tests/src/core/testqgsdxfexport.cpp +++ b/tests/src/core/testqgsdxfexport.cpp @@ -55,6 +55,7 @@ class TestQgsDxfExport : public QObject void testPoints(); void testPointsDataDefinedSizeAngle(); void testPointsDataDefinedSizeSymbol(); + void testPointsOverriddenName(); void testLines(); void testPolygons(); void testMultiSurface(); @@ -81,6 +82,7 @@ class TestQgsDxfExport : public QObject void testSelectedPolygons(); void testMultipleLayersWithSelection(); void testExtentWithSelection(); + void testOutputLayerNamePrecedence(); private: QgsVectorLayer *mPointLayer = nullptr; @@ -283,6 +285,40 @@ void TestQgsDxfExport::testPointsDataDefinedSizeSymbol() QVERIFY( dxfString.contains( QStringLiteral( "50\n5.0" ) ) ); } +void TestQgsDxfExport::testPointsOverriddenName() +{ + QgsDxfExport d; + d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( mPointLayer, -1, false, -1, QStringLiteral( "My Point Layer" ) ) ); + + QgsMapSettings mapSettings; + const QSize size( 640, 480 ); + mapSettings.setOutputSize( size ); + mapSettings.setExtent( mPointLayer->extent() ); + mapSettings.setLayers( QList() << mPointLayer ); + mapSettings.setOutputDpi( 96 ); + mapSettings.setDestinationCrs( mPointLayer->crs() ); + + d.setMapSettings( mapSettings ); + d.setSymbologyScale( 1000 ); + + const QString file = getTempFileName( "point_overridden_name_dxf" ); + QFile dxfFile( file ); + QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success ); + dxfFile.close(); + + QVERIFY( !fileContainsText( file, QStringLiteral( "nan.0" ) ) ); + QVERIFY( !fileContainsText( file, mPointLayer->name() ) ); // "points" + + // reload and compare + std::unique_ptr< QgsVectorLayer > result = std::make_unique< QgsVectorLayer >( file, "dxf" ); + QVERIFY( result->isValid() ); + QCOMPARE( result->featureCount(), mPointLayer->featureCount() ); + QCOMPARE( result->wkbType(), Qgis::WkbType::Point ); + QgsFeature feature; + result->getFeatures().nextFeature( feature ); + QCOMPARE( feature.attribute( "Layer" ), QStringLiteral( "My Point Layer" ) ); +} + void TestQgsDxfExport::testLines() { QgsDxfExport d; @@ -1773,6 +1809,130 @@ void TestQgsDxfExport::testExtentWithSelection() mPointLayer->removeSelection(); } +void TestQgsDxfExport::testOutputLayerNamePrecedence() +{ + // Test that output layer name precedence is: + // 1) Attribute (if any) + // 2) Overridden name (if any) + // 3) Layer title (if any) + // 4) Layer name + + const QString layerTitle = QStringLiteral( "Point Layer Title" ); + const QString layerOverriddenName = QStringLiteral( "My Point Layer" ); + + // A) All layer name options are set + QgsDxfExport d; + mPointLayer->serverProperties()->setTitle( layerTitle ); + d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( mPointLayer, + 0, // Class attribute, 3 unique values + false, + -1, + layerOverriddenName ) ); + + QgsMapSettings mapSettings; + const QSize size( 640, 480 ); + mapSettings.setOutputSize( size ); + mapSettings.setExtent( mPointLayer->extent() ); + mapSettings.setLayers( QList() << mPointLayer ); + mapSettings.setOutputDpi( 96 ); + mapSettings.setDestinationCrs( mPointLayer->crs() ); + + d.setMapSettings( mapSettings ); + d.setSymbologyScale( 1000 ); + d.setLayerTitleAsName( true ); + + const QString file = getTempFileName( "name_precedence_a_all_set_dxf" ); + QFile dxfFile( file ); + QCOMPARE( d.writeToFile( &dxfFile, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success ); + dxfFile.close(); + + QVERIFY( !fileContainsText( file, QStringLiteral( "nan.0" ) ) ); + QVERIFY( !fileContainsText( file, layerTitle ) ); + QVERIFY( !fileContainsText( file, layerOverriddenName ) ); + QVERIFY( !fileContainsText( file, mPointLayer->name() ) ); + + // reload and compare + std::unique_ptr< QgsVectorLayer > result = std::make_unique< QgsVectorLayer >( file, "dxf" ); + QVERIFY( result->isValid() ); + QCOMPARE( result->featureCount(), mPointLayer->featureCount() ); + QCOMPARE( result->wkbType(), Qgis::WkbType::Point ); + QSet values = result->uniqueValues( 0 ); // "Layer" field + QCOMPARE( values.count(), 3 ); + QVERIFY( values.contains( QVariant( "B52" ) ) ); + QVERIFY( values.contains( QVariant( "Jet" ) ) ); + QVERIFY( values.contains( QVariant( "Biplane" ) ) ); + + // B) No attribute given + d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( mPointLayer, -1, false, -1, layerOverriddenName ) ); // this replaces layers + + const QString file2 = getTempFileName( "name_precedence_b_no_attr_dxf" ); + QFile dxfFile2( file2 ); + QCOMPARE( d.writeToFile( &dxfFile2, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success ); + dxfFile2.close(); + + QVERIFY( !fileContainsText( file2, QStringLiteral( "nan.0" ) ) ); + QVERIFY( !fileContainsText( file2, layerTitle ) ); + QVERIFY( fileContainsText( file2, layerOverriddenName ) ); + QVERIFY( !fileContainsText( file2, mPointLayer->name() ) ); + + // reload and compare + result = std::make_unique< QgsVectorLayer >( file2, "dxf" ); + QVERIFY( result->isValid() ); + QCOMPARE( result->featureCount(), mPointLayer->featureCount() ); + QCOMPARE( result->wkbType(), Qgis::WkbType::Point ); + QgsFeature feature; + result->getFeatures().nextFeature( feature ); + QCOMPARE( feature.attribute( "Layer" ), layerOverriddenName ); + QCOMPARE( result->uniqueValues( 0 ).count(), 1 ); // "Layer" field + + // C) No attribute given, no override + d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( mPointLayer, -1, false, -1 ) ); // this replaces layers + + const QString file3 = getTempFileName( "name_precedence_c_no_attr_no_override_dxf" ); + QFile dxfFile3( file3 ); + QCOMPARE( d.writeToFile( &dxfFile3, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success ); + dxfFile3.close(); + + QVERIFY( !fileContainsText( file3, QStringLiteral( "nan.0" ) ) ); + QVERIFY( fileContainsText( file3, layerTitle ) ); + QVERIFY( !fileContainsText( file3, layerOverriddenName ) ); + QVERIFY( !fileContainsText( file3, mPointLayer->name() ) ); + + // reload and compare + result = std::make_unique< QgsVectorLayer >( file3, "dxf" ); + QVERIFY( result->isValid() ); + QCOMPARE( result->featureCount(), mPointLayer->featureCount() ); + QCOMPARE( result->wkbType(), Qgis::WkbType::Point ); + result->getFeatures().nextFeature( feature ); + QCOMPARE( feature.attribute( "Layer" ), layerTitle ); + QCOMPARE( result->uniqueValues( 0 ).count(), 1 ); // "Layer" field + + // D) No name options given, use default layer name + d.addLayers( QList< QgsDxfExport::DxfLayer >() << QgsDxfExport::DxfLayer( mPointLayer ) ); // This replaces layers + d.setLayerTitleAsName( false ); + + const QString file4 = getTempFileName( "name_precedence_d_no_anything_dxf" ); + QFile dxfFile4( file4 ); + QCOMPARE( d.writeToFile( &dxfFile4, QStringLiteral( "CP1252" ) ), QgsDxfExport::ExportResult::Success ); + dxfFile4.close(); + + QVERIFY( !fileContainsText( file4, QStringLiteral( "nan.0" ) ) ); + QVERIFY( !fileContainsText( file4, layerTitle ) ); + QVERIFY( !fileContainsText( file4, layerOverriddenName ) ); + QVERIFY( fileContainsText( file4, mPointLayer->name() ) ); + + // reload and compare + result = std::make_unique< QgsVectorLayer >( file4, "dxf" ); + QVERIFY( result->isValid() ); + QCOMPARE( result->featureCount(), mPointLayer->featureCount() ); + QCOMPARE( result->wkbType(), Qgis::WkbType::Point ); + result->getFeatures().nextFeature( feature ); + QCOMPARE( feature.attribute( "Layer" ), mPointLayer->name() ); + QCOMPARE( result->uniqueValues( 0 ).count(), 1 ); // "Layer" field + + mPointLayer->serverProperties()->setTitle( QString() ); // Leave the original empty title +} + bool TestQgsDxfExport::fileContainsText( const QString &path, const QString &text, QString *debugInfo ) const { QStringList debugLines; diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index a73b8ba6eb6d..00eb9bf2931d 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -9988,6 +9988,7 @@ void TestProcessingGui::testDxfLayersWrapper() QVariantMap layerMap; layerMap["layer"] = "PointLayer"; layerMap["attributeIndex"] = -1; + layerMap["overriddenLayerName"] = QString(); layerList.append( layerMap ); QVERIFY( definition.checkValueIsAcceptable( layerList, &context ) ); @@ -9998,7 +9999,7 @@ void TestProcessingGui::testDxfLayersWrapper() QVERIFY( definition.checkValueIsAcceptable( value, &context ) ); QString valueAsPythonString = definition.valueAsPythonString( value, context ); - QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1}]" ).arg( vectorLayer->source() ) ); + QCOMPARE( valueAsPythonString, QStringLiteral( "[{'layer': '%1','attributeIndex': -1,'overriddenLayerName': ''}]" ).arg( vectorLayer->source() ) ); } void TestProcessingGui::testAlignRasterLayersWrapper()