QGIS/src/app/qgsdxfexportdialog.cpp

1272 lines
44 KiB
C++

/***************************************************************************
qgsdxfexportdialog.cpp
----------------------
begin : September 2013
copyright : (C) 2013 by Marco Hugentobler
email : marco at sourcepole dot ch
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsdxfexportdialog.h"
#include "qgsmaplayer.h"
#include "qgslayertree.h"
#include "qgslayertreegroup.h"
#include "qgsvectorlayer.h"
#include "qgsproject.h"
#include "qgshelp.h"
#include "qgis.h"
#include "qgsfieldcombobox.h"
#include "qgisapp.h"
#include "qgsmapthemecollection.h"
#include "qgsmapcanvas.h"
#include "qgssettings.h"
#include "qgsgui.h"
#include <QFileDialog>
#include <QPushButton>
#include <QMessageBox>
const int LAYER_COL = 0;
const int OUTPUT_LAYER_ATTRIBUTE_COL = 1;
const int ALLOW_DD_SYMBOL_BLOCKS_COL = 2;
const int MAXIMUM_DD_SYMBOL_BLOCKS_COL = 3;
FieldSelectorDelegate::FieldSelectorDelegate( QObject *parent )
: QItemDelegate( parent )
{
}
QWidget *FieldSelectorDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
Q_UNUSED( option )
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;
}
else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL )
{
QLineEdit *le = new QLineEdit( parent );
le->setValidator( new QIntValidator( le ) );
return le;
}
return nullptr;
}
void FieldSelectorDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
{
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<QgsFieldComboBox *>( 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<QLineEdit *>( editor );
if ( le )
{
le->setText( index.data().toString() );
}
}
}
void FieldSelectorDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
{
QgsVectorLayer *vl = indexToLayer( index.model(), index );
if ( !vl )
return;
if ( index.column() == LAYER_COL )
{
QgsFilterLineEdit *le = qobject_cast<QgsFilterLineEdit *>( editor );
if ( le )
{
model->setData( index, le->text() );
}
}
else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL )
{
QgsFieldComboBox *fcb = qobject_cast<QgsFieldComboBox *>( editor );
if ( !fcb )
return;
model->setData( index, vl->fields().lookupField( fcb->currentField() ) );
}
else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL )
{
QLineEdit *le = qobject_cast<QLineEdit *>( editor );
if ( le )
{
model->setData( index, le->text().toInt() );
}
}
}
QgsVectorLayer *FieldSelectorDelegate::indexToLayer( const QAbstractItemModel *model, const QModelIndex &index ) const
{
const QgsLayerTreeProxyModel *proxy = qobject_cast< const QgsLayerTreeProxyModel *>( model );
Q_ASSERT( proxy );
const QgsVectorLayerAndAttributeModel *m = qobject_cast< const QgsVectorLayerAndAttributeModel *>( proxy->sourceModel() );
Q_ASSERT( m );
return m->vectorLayer( proxy->mapToSource( index ) );
}
int FieldSelectorDelegate::attributeIndex( const QAbstractItemModel *model, const QgsVectorLayer *vl ) const
{
const QgsLayerTreeProxyModel *proxy = qobject_cast< const QgsLayerTreeProxyModel *>( model );
Q_ASSERT( proxy );
const QgsVectorLayerAndAttributeModel *m = qobject_cast< const QgsVectorLayerAndAttributeModel *>( proxy->sourceModel() );
Q_ASSERT( m );
return m->attributeIndex( vl );
}
QgsVectorLayerAndAttributeModel::QgsVectorLayerAndAttributeModel( QgsLayerTree *rootNode, QObject *parent )
: QgsLayerTreeModel( rootNode, parent )
{
//init mCreateDDBlockInfo, mDDBlocksMaxNumberOfClasses
QSet<QString> layerIds;
retrieveAllLayers( rootNode, layerIds );
for ( const auto &id : std::as_const( layerIds ) )
{
const QgsVectorLayer *vLayer = qobject_cast< const QgsVectorLayer *>( QgsProject::instance()->mapLayer( id ) );
if ( vLayer )
{
mCreateDDBlockInfo[vLayer] = true;
mDDBlocksMaxNumberOfClasses[vLayer] = -1;
}
}
}
int QgsVectorLayerAndAttributeModel::columnCount( const QModelIndex &parent ) const
{
Q_UNUSED( parent )
return 4;
}
Qt::ItemFlags QgsVectorLayerAndAttributeModel::flags( const QModelIndex &index ) const
{
QgsVectorLayer *vl = vectorLayer( index );
if ( index.column() == LAYER_COL )
{
return vl ? Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable : Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
}
else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL )
{
return vl ? Qt::ItemIsEnabled | Qt::ItemIsEditable : Qt::ItemIsEnabled;
}
else if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL )
{
return ( vl && vl->geometryType() == Qgis::GeometryType::Point ) ? Qt::ItemIsEnabled | Qt::ItemIsUserCheckable : Qt::ItemIsEnabled ;
}
else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL )
{
return vl && vl->geometryType() == Qgis::GeometryType::Point ? Qt::ItemIsEnabled | Qt::ItemIsEditable : Qt::ItemIsEnabled;
}
return Qt::ItemIsEnabled;
}
QgsVectorLayer *QgsVectorLayerAndAttributeModel::vectorLayer( const QModelIndex &idx ) const
{
QgsLayerTreeNode *n = index2node( idx );
if ( !n || !QgsLayerTree::isLayer( n ) )
return nullptr;
return qobject_cast<QgsVectorLayer *>( QgsLayerTree::toLayer( n )->layer() );
}
int QgsVectorLayerAndAttributeModel::attributeIndex( const QgsVectorLayer *vl ) const
{
return mAttributeIdx.value( vl, -1 );
}
QVariant QgsVectorLayerAndAttributeModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
if ( orientation == Qt::Horizontal )
{
if ( role == Qt::DisplayRole )
{
if ( section == LAYER_COL )
return tr( "Layer" );
else if ( section == OUTPUT_LAYER_ATTRIBUTE_COL )
return tr( "Output Layer Attribute" );
else if ( section == ALLOW_DD_SYMBOL_BLOCKS_COL )
{
return tr( "Allow data defined symbol blocks" );
}
else if ( section == MAXIMUM_DD_SYMBOL_BLOCKS_COL )
{
return tr( "Maximum number of symbol blocks" );
}
}
else if ( role == Qt::ToolTipRole )
{
if ( section == OUTPUT_LAYER_ATTRIBUTE_COL )
return tr( "Attribute containing the name of the destination layer in the DXF output." );
}
}
return QVariant();
}
QVariant QgsVectorLayerAndAttributeModel::data( const QModelIndex &idx, int role ) const
{
QgsVectorLayer *vl = vectorLayer( idx );
if ( idx.column() == LAYER_COL )
{
if ( role == Qt::CheckStateRole )
{
if ( !idx.isValid() )
return QVariant();
if ( mCheckedLeafs.contains( idx ) )
return Qt::Checked;
bool hasChecked = false, hasUnchecked = false;
int n;
for ( n = 0; !hasChecked || !hasUnchecked; n++ )
{
QVariant v = data( index( n, 0, idx ), role );
if ( !v.isValid() )
break;
switch ( v.toInt() )
{
case Qt::PartiallyChecked:
// parent of partially checked child shared state
return Qt::PartiallyChecked;
case Qt::Checked:
hasChecked = true;
break;
case Qt::Unchecked:
hasUnchecked = true;
break;
}
}
// unchecked leaf
if ( n == 0 )
return Qt::Unchecked;
// both
if ( hasChecked && hasUnchecked )
return Qt::PartiallyChecked;
if ( hasChecked )
return Qt::Checked;
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 )
{
if ( !vl || vl->geometryType() != Qgis::GeometryType::Point )
{
return QVariant();
}
bool checked = mCreateDDBlockInfo.contains( vl ) ? mCreateDDBlockInfo[vl] : true;
if ( role == Qt::CheckStateRole )
{
return checked ? Qt::Checked : Qt::Unchecked;
}
else
{
return QgsLayerTreeModel::data( idx, role );
}
}
else if ( idx.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL )
{
if ( !vl || vl->geometryType() != Qgis::GeometryType::Point )
{
return QVariant();
}
if ( role == Qt::DisplayRole )
{
if ( !mDDBlocksMaxNumberOfClasses.contains( vl ) )
{
return QVariant( -1 );
}
else
{
return QVariant( mDDBlocksMaxNumberOfClasses[vl] );
}
}
}
return QVariant();
}
bool QgsVectorLayerAndAttributeModel::setData( const QModelIndex &index, const QVariant &value, int role )
{
QgsVectorLayer *vl = vectorLayer( index );
if ( index.column() == LAYER_COL )
{
if ( role == Qt::CheckStateRole )
{
int i = 0;
for ( i = 0; ; i++ )
{
QModelIndex child = QgsVectorLayerAndAttributeModel::index( i, 0, index );
if ( !child.isValid() )
break;
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" );
emit dataChanged( QModelIndex(), index );
}
return true;
}
else if ( role == Qt::EditRole )
{
if ( !value.toString().trimmed().isEmpty() && value.toString() != vl->name() )
{
mOverriddenName[ vl ] = value.toString();
}
else
{
mOverriddenName.remove( vl );
}
return true;
}
}
else if ( index.column() == OUTPUT_LAYER_ATTRIBUTE_COL )
{
if ( role != Qt::EditRole )
return false;
if ( vl )
{
mAttributeIdx[ vl ] = value.toInt();
return true;
}
}
else if ( index.column() == ALLOW_DD_SYMBOL_BLOCKS_COL && role == Qt::CheckStateRole )
{
if ( vl )
{
mCreateDDBlockInfo[ vl ] = value.toBool();
return true;
}
}
else if ( index.column() == MAXIMUM_DD_SYMBOL_BLOCKS_COL && role == Qt::EditRole )
{
if ( vl )
{
mDDBlocksMaxNumberOfClasses[ vl ] = value.toInt();
return true;
}
}
return QgsLayerTreeModel::setData( index, value, role );
}
QList< QgsDxfExport::DxfLayer > QgsVectorLayerAndAttributeModel::layers() const
{
QList< QgsDxfExport::DxfLayer > layers;
QHash< QString, int > layerIdx;
for ( const QModelIndex &idx : std::as_const( mCheckedLeafs ) )
{
QgsLayerTreeNode *node = index2node( idx );
if ( QgsLayerTree::isGroup( node ) )
{
const auto childLayers = QgsLayerTree::toGroup( node )->findLayers();
for ( QgsLayerTreeLayer *treeLayer : childLayers )
{
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( treeLayer->layer() );
Q_ASSERT( vl );
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 ),
mOverriddenName.value( vl, QString() ) );
}
}
}
else if ( QgsLayerTree::isLayer( node ) )
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( QgsLayerTree::toLayer( node )->layer() );
Q_ASSERT( vl );
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 ),
mOverriddenName.value( vl, QString() ) );
}
}
}
QList< QgsDxfExport::DxfLayer > layersInROrder;
QList<QgsMapLayer *> layerOrder = mRootNode->layerOrder();
QList<QgsMapLayer *>::ConstIterator layerIterator = layerOrder.constEnd();
while ( layerIterator != layerOrder.constBegin() )
{
--layerIterator;
QgsMapLayer *l = *layerIterator;
int idx = layerIdx.value( l->id(), -1 );
if ( idx < 0 )
continue;
layersInROrder << layers[idx];
}
Q_ASSERT( layersInROrder.size() == layers.size() );
return layersInROrder;
}
void QgsVectorLayerAndAttributeModel::applyVisibilityPreset( const QString &name )
{
QSet<QString> visibleLayers;
if ( name.isEmpty() )
{
const auto constLayers = QgisApp::instance()->mapCanvas()->layers( true );
for ( const QgsMapLayer *ml : constLayers )
{
const QgsVectorLayer *vl = qobject_cast<const QgsVectorLayer *>( ml );
if ( !vl )
continue;
visibleLayers.insert( vl->id() );
}
}
else
{
visibleLayers = qgis::listToSet( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayerIds( name ) );
}
if ( visibleLayers.isEmpty() )
return;
mCheckedLeafs.clear();
applyVisibility( visibleLayers, rootGroup() );
}
void QgsVectorLayerAndAttributeModel::applyVisibility( QSet<QString> &visibleLayers, QgsLayerTreeNode *node )
{
QgsLayerTreeGroup *group = QgsLayerTree::isGroup( node ) ? QgsLayerTree::toGroup( node ) : nullptr;
if ( !group )
return;
const auto constChildren = node->children();
for ( QgsLayerTreeNode *child : constChildren )
{
if ( QgsLayerTree::isLayer( child ) )
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( QgsLayerTree::toLayer( child )->layer() );
if ( vl )
{
QModelIndex idx = node2index( child );
if ( visibleLayers.contains( vl->id() ) )
{
visibleLayers.remove( vl->id() );
mCheckedLeafs.insert( idx );
}
emit dataChanged( idx, idx, QVector<int>() << Qt::CheckStateRole );
}
continue;
}
applyVisibility( visibleLayers, child );
}
}
void QgsVectorLayerAndAttributeModel::loadLayersOutputAttribute( QgsLayerTreeNode *node )
{
const auto constChildren = node->children();
for ( QgsLayerTreeNode *child : constChildren )
{
if ( QgsLayerTree::isLayer( child ) )
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( QgsLayerTree::toLayer( child )->layer() );
if ( vl )
{
const int attributeIndex = vl->fields().lookupField( vl->customProperty( QStringLiteral( "lastDxfOutputAttribute" ), -1 ).toString() );
if ( attributeIndex > -1 )
{
mAttributeIdx[vl] = attributeIndex;
QModelIndex idx = node2index( child );
idx = index( idx.row(), 1, idx.parent() );
emit dataChanged( idx, idx, QVector<int>() << Qt::EditRole );
}
}
}
else if ( QgsLayerTree::isGroup( child ) )
{
loadLayersOutputAttribute( child );
}
}
}
void QgsVectorLayerAndAttributeModel::saveLayersOutputAttribute( QgsLayerTreeNode *node )
{
const auto constChildren = node->children();
for ( QgsLayerTreeNode *child : constChildren )
{
if ( QgsLayerTree::isLayer( child ) )
{
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( QgsLayerTree::toLayer( child )->layer() );
if ( vl )
{
QModelIndex idx = node2index( child );
const int attributeIndex = data( index( idx.row(), 1, idx.parent() ), Qt::EditRole ).toInt();
const QgsFields fields = vl->fields();
if ( attributeIndex > -1 && attributeIndex < fields.count() )
{
vl->setCustomProperty( QStringLiteral( "lastDxfOutputAttribute" ), fields.at( attributeIndex ).name() );
}
else
{
vl->removeCustomProperty( QStringLiteral( "lastDxfOutputAttribute" ) );
}
}
}
else if ( QgsLayerTree::isGroup( child ) )
{
saveLayersOutputAttribute( child );
}
}
}
void QgsVectorLayerAndAttributeModel::retrieveAllLayers( QgsLayerTreeNode *node, QSet<QString> &set )
{
if ( QgsLayerTree::isLayer( node ) )
{
set << QgsLayerTree::toLayer( node )->layer()->id();
}
else if ( QgsLayerTree::isGroup( node ) )
{
const auto constChildren = QgsLayerTree::toGroup( node )->children();
for ( QgsLayerTreeNode *child : constChildren )
{
retrieveAllLayers( child, set );
}
}
}
void QgsVectorLayerAndAttributeModel::selectAll()
{
mCheckedLeafs.clear();
QSet<QString> allLayers;
retrieveAllLayers( rootGroup(), allLayers );
applyVisibility( allLayers, rootGroup() );
emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ) );
}
void QgsVectorLayerAndAttributeModel::deSelectAll()
{
mCheckedLeafs.clear();
QSet<QString> noLayers;
applyVisibility( noLayers, rootGroup() );
emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ) );
}
void QgsVectorLayerAndAttributeModel::selectDataDefinedBlocks()
{
enableDataDefinedBlocks( true );
}
void QgsVectorLayerAndAttributeModel::deselectDataDefinedBlocks()
{
enableDataDefinedBlocks( false );
}
void QgsVectorLayerAndAttributeModel::enableDataDefinedBlocks( bool enabled )
{
QHash<const QgsVectorLayer *, bool>::const_iterator it = mCreateDDBlockInfo.constBegin();
for ( ; it != mCreateDDBlockInfo.constEnd(); ++it )
{
mCreateDDBlockInfo[it.key()] = enabled;
}
emit dataChanged( index( 0, 0 ), index( rowCount() - 1, 0 ) );
}
QgsDxfExportLayerTreeView::QgsDxfExportLayerTreeView( QWidget *parent )
: QgsLayerTreeView( parent )
{
}
void QgsDxfExportLayerTreeView::resizeEvent( QResizeEvent *event )
{
header()->setMinimumSectionSize( viewport()->width() / 2 );
header()->setMaximumSectionSize( viewport()->width() / 2 );
QTreeView::resizeEvent( event ); // NOLINT(bugprone-parent-virtual-call) clazy:exclude=skipped-base-method
}
QgsDxfExportDialog::QgsDxfExportDialog( QWidget *parent, Qt::WindowFlags f )
: QDialog( parent, f )
{
setupUi( this );
mTreeView = new QgsDxfExportLayerTreeView( this );
mTreeViewContainer->layout()->addWidget( mTreeView );
QgsGui::enableAutoGeometryRestore( this );
connect( mVisibilityPresets, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsDxfExportDialog::mVisibilityPresets_currentIndexChanged );
connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, &QgsDxfExportDialog::mCrsSelector_crsChanged );
QgsSettings settings;
mLayerTreeGroup = QgsProject::instance()->layerTreeRoot()->clone();
cleanGroup( mLayerTreeGroup );
mFieldSelectorDelegate = new FieldSelectorDelegate( this );
mTreeView->setEditTriggers( QAbstractItemView::AllEditTriggers );
mTreeView->setItemDelegate( mFieldSelectorDelegate );
mModel = new QgsVectorLayerAndAttributeModel( mLayerTreeGroup, this );
mModel->setFlags( QgsLayerTreeModel::Flags() );
mTreeView->setModel( mModel );
mTreeView->header()->show();
mFileName->setStorageMode( QgsFileWidget::SaveFile );
mFileName->setFilter( tr( "DXF files" ) + " (*.dxf *.DXF)" );
mFileName->setDialogTitle( tr( "Export as DXF" ) );
mFileName->setDefaultRoot( settings.value( QStringLiteral( "qgis/lastDxfDir" ), QDir::homePath() ).toString() );
connect( this, &QDialog::accepted, this, &QgsDxfExportDialog::saveSettings );
connect( mSelectAllButton, &QAbstractButton::clicked, this, &QgsDxfExportDialog::selectAll );
connect( mDeselectAllButton, &QAbstractButton::clicked, this, &QgsDxfExportDialog::deSelectAll );
connect( mSelectDataDefinedBlocks, &QAbstractButton::clicked, this, &QgsDxfExportDialog::selectDataDefinedBlocks );
connect( mDeselectDataDefinedBlocks, &QAbstractButton::clicked, this, &QgsDxfExportDialog::deselectDataDefinedBlocks );
connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsDxfExportDialog::showHelp );
connect( mFileName, &QgsFileWidget::fileChanged, this, [ = ]( const QString & filePath )
{
QgsSettings settings;
QFileInfo tmplFileInfo( filePath );
settings.setValue( QStringLiteral( "qgis/lastDxfDir" ), tmplFileInfo.absolutePath() );
setOkEnabled();
} );
mSymbologyModeComboBox->addItem( tr( "No Symbology" ), QVariant::fromValue( Qgis::FeatureSymbologyExport::NoSymbology ) );
mSymbologyModeComboBox->addItem( tr( "Feature Symbology" ), QVariant::fromValue( Qgis::FeatureSymbologyExport::PerFeature ) );
mSymbologyModeComboBox->addItem( tr( "Symbol Layer Symbology" ), QVariant::fromValue( Qgis::FeatureSymbologyExport::PerSymbolLayer ) );
//last dxf symbology mode
mSymbologyModeComboBox->setCurrentIndex( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfSymbologyMode" ), settings.value( QStringLiteral( "qgis/lastDxfSymbologyMode" ), "2" ).toString() ).toInt() );
//last symbol scale
mScaleWidget->setMapCanvas( QgisApp::instance()->mapCanvas() );
double oldScale = QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastSymbologyExportScale" ), settings.value( QStringLiteral( "qgis/lastSymbologyExportScale" ), "1/50000" ).toString() ).toDouble();
if ( oldScale != 0.0 )
mScaleWidget->setScale( 1.0 / oldScale );
mLayerTitleAsName->setChecked( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfLayerTitleAsName" ), settings.value( QStringLiteral( "qgis/lastDxfLayerTitleAsName" ), "false" ).toString() ) != QLatin1String( "false" ) );
mMapExtentCheckBox->setChecked( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfMapRectangle" ), settings.value( QStringLiteral( "qgis/lastDxfMapRectangle" ), "false" ).toString() ) != QLatin1String( "false" ) );
mSelectedFeaturesOnly->setChecked( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfSelectedFeaturesOnly" ), settings.value( QStringLiteral( "qgis/lastDxfSelectedFeaturesOnly" ), "false" ).toString() ) != QLatin1String( "false" ) );
mMTextCheckBox->setChecked( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfUseMText" ), settings.value( QStringLiteral( "qgis/lastDxfUseMText" ), "true" ).toString() ) != QLatin1String( "false" ) );
mForce2d->setChecked( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfForce2d" ), settings.value( QStringLiteral( "qgis/lastDxfForce2d" ), "false" ).toString() ) != QLatin1String( "false" ) );
QStringList ids = QgsProject::instance()->mapThemeCollection()->mapThemes();
ids.prepend( QString() );
mVisibilityPresets->addItems( ids );
mVisibilityPresets->setCurrentIndex( mVisibilityPresets->findText( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastVisibilityPreset" ), QString() ) ) );
buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
long crsid = QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfCrs" ),
settings.value( QStringLiteral( "qgis/lastDxfCrs" ), QString::number( QgsProject::instance()->crs().srsid() ) ).toString()
).toLong();
mCRS = QgsCoordinateReferenceSystem::fromSrsId( crsid );
mCrsSelector->setCrs( mCRS );
mCrsSelector->setLayerCrs( mCRS );
mCrsSelector->setShowAccuracyWarnings( true );
mCrsSelector->setMessage( tr( "Select the coordinate reference system for the dxf file. "
"The data points will be transformed from the layer coordinate reference system." ) );
mEncoding->addItems( QgsDxfExport::encodings() );
mEncoding->setCurrentIndex( mEncoding->findText( QgsProject::instance()->readEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfEncoding" ), settings.value( QStringLiteral( "qgis/lastDxfEncoding" ), "CP1252" ).toString() ) ) );
QPushButton *btnLoadSaveSettings = new QPushButton( tr( "Settings" ), this );
QMenu *menuSettings = new QMenu( this );
menuSettings->addAction( tr( "Load Settings from File…" ), this, &QgsDxfExportDialog::loadSettingsFromFile );
menuSettings->addAction( tr( "Save Settings to File…" ), this, &QgsDxfExportDialog::saveSettingsToFile );
btnLoadSaveSettings->setMenu( menuSettings );
buttonBox->addButton( btnLoadSaveSettings, QDialogButtonBox::ResetRole );
mMessageBar = new QgsMessageBar();
mMessageBar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
mainLayout->insertWidget( 0, mMessageBar );
mModel->loadLayersOutputAttribute( mModel->rootGroup() );
}
QgsDxfExportDialog::~QgsDxfExportDialog()
{
delete mLayerTreeGroup;
}
void QgsDxfExportDialog::mVisibilityPresets_currentIndexChanged( int index )
{
Q_UNUSED( index )
mModel->applyVisibilityPreset( mVisibilityPresets->currentText() );
}
void QgsDxfExportDialog::cleanGroup( QgsLayerTreeNode *node )
{
QgsLayerTreeGroup *group = QgsLayerTree::isGroup( node ) ? QgsLayerTree::toGroup( node ) : nullptr;
if ( !group )
return;
QList<QgsLayerTreeNode *> toRemove;
const auto constChildren = node->children();
for ( QgsLayerTreeNode *child : constChildren )
{
if ( QgsLayerTree::isLayer( child ) &&
( QgsLayerTree::toLayer( child )->layer()->type() != Qgis::LayerType::Vector ||
! QgsLayerTree::toLayer( child )->layer()->isSpatial() ||
! QgsLayerTree::toLayer( child )->layer()->isValid() ) )
{
toRemove << child;
continue;
}
cleanGroup( child );
if ( QgsLayerTree::isGroup( child ) && child->children().isEmpty() )
toRemove << child;
}
const auto constToRemove = toRemove;
for ( QgsLayerTreeNode *child : constToRemove )
group->removeChildNode( child );
}
void QgsDxfExportDialog::selectAll()
{
mModel->selectAll();
}
void QgsDxfExportDialog::deSelectAll()
{
mModel->deSelectAll();
}
void QgsDxfExportDialog::selectDataDefinedBlocks()
{
mModel->selectDataDefinedBlocks();
}
void QgsDxfExportDialog::deselectDataDefinedBlocks()
{
mModel->deselectDataDefinedBlocks();
}
void QgsDxfExportDialog::loadSettingsFromFile()
{
const QString fileName = QFileDialog::getOpenFileName( this, tr( "Load DXF Export Settings" ),
QgsDxfExportDialog::settingsDxfLastSettingsDir->value(),
tr( "XML file" ) + " (*.xml)" );
if ( fileName.isNull() )
{
return;
}
bool resultFlag = false;
QDomDocument domDocument( QStringLiteral( "qgis" ) );
// location of problem associated with errorMsg
int line, column;
QString errorMessage;
QFile file( fileName );
if ( file.open( QFile::ReadOnly ) )
{
QgsDebugMsgLevel( QStringLiteral( "file found %1" ).arg( fileName ), 2 );
// read file
resultFlag = domDocument.setContent( &file, &errorMessage, &line, &column );
if ( !resultFlag )
errorMessage = tr( "%1 at line %2 column %3" ).arg( errorMessage ).arg( line ).arg( column );
file.close();
}
if ( QMessageBox::question( this,
tr( "DXF Export - Load from XML File" ),
tr( "Are you sure you want to load settings from XML? This will change some values in the DXF Export dialog." ) ) == QMessageBox::Yes )
{
resultFlag = loadSettingsFromXML( domDocument, errorMessage );
if ( !resultFlag )
{
mMessageBar->pushWarning( tr( "Load DXF Export Settings" ), tr( "Failed to load DXF Export settings file as %1. Details: %2" ).arg( fileName, errorMessage ) );
}
else
{
QgsDxfExportDialog::settingsDxfLastSettingsDir->setValue( QFileInfo( fileName ).path() );
mMessageBar->pushMessage( QString(), tr( "DXF Export settings loaded!" ), Qgis::MessageLevel::Success, 0 );
}
}
}
bool QgsDxfExportDialog::loadSettingsFromXML( QDomDocument &doc, QString &errorMessage ) const
{
const QDomElement rootElement = doc.firstChildElement( QStringLiteral( "qgis" ) );
if ( rootElement.isNull() )
{
errorMessage = tr( "Root &lt;qgis&gt; element could not be found." );
return false;
}
const QDomElement dxfElement = rootElement.firstChildElement( QStringLiteral( "dxf_settings" ) );
if ( dxfElement.isNull() )
{
errorMessage = tr( "The XML file does not correspond to DXF Export settings. It must have a &lt;dxf-settings&gt; element." );
return false;
}
QDomElement element;
QVariant value;
element = dxfElement.namedItem( QStringLiteral( "symbology_mode" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mSymbologyModeComboBox->setCurrentIndex( value.toInt() );
element = dxfElement.namedItem( QStringLiteral( "symbology_scale" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mScaleWidget->setScale( value.toDouble() );
element = dxfElement.namedItem( QStringLiteral( "encoding" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mEncoding->setCurrentText( value.toString() );
element = dxfElement.namedItem( QStringLiteral( "crs" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mCrsSelector->setCrs( value.value< QgsCoordinateReferenceSystem >() );
element = dxfElement.namedItem( QStringLiteral( "map_theme" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mVisibilityPresets->setCurrentText( value.toString() );
// layer settings
element = dxfElement.namedItem( QStringLiteral( "layers" ) ).toElement();
QDomNodeList layerNodeList = element.elementsByTagName( QStringLiteral( "layer" ) );
const QgsReadWriteContext rwContext = QgsReadWriteContext();
QgsVectorLayer *vl;
QgsVectorLayerRef vlRef;
for ( int i = 0; i < layerNodeList.length(); i++ )
{
element = layerNodeList.at( i ).toElement();
if ( vlRef.readXml( element, rwContext ) )
{
vl = vlRef.resolveWeakly( QgsProject::instance() );
if ( vl )
{
QgsLayerTreeLayer *treeNode = mLayerTreeGroup->findLayer( vl );
QModelIndex idx = mModel->node2index( treeNode );
idx = mModel->index( idx.row(), OUTPUT_LAYER_ATTRIBUTE_COL, idx.parent() );
mModel->setData( idx, element.attribute( QStringLiteral( "attribute-index" ), QStringLiteral( "-1" ) ) );
idx = mModel->index( idx.row(), ALLOW_DD_SYMBOL_BLOCKS_COL, idx.parent() );
mModel->setData( idx, element.attribute( QStringLiteral( "use_symbol_blocks" ), QStringLiteral( "0" ) ), Qt::CheckStateRole );
idx = mModel->index( idx.row(), MAXIMUM_DD_SYMBOL_BLOCKS_COL, idx.parent() );
mModel->setData( idx, element.attribute( QStringLiteral( "max_number_of_classes" ), QStringLiteral( "-1" ) ) );
}
else
{
QgsDebugMsgLevel( QStringLiteral( " Layer '%1' found in the DXF Export settings XML file, but not present in the project." ).arg( element.attribute( QStringLiteral( "name" ) ) ), 1 );
}
}
}
element = dxfElement.namedItem( QStringLiteral( "use_layer_title" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mLayerTitleAsName->setChecked( value == true );
element = dxfElement.namedItem( QStringLiteral( "use_map_extent" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mMapExtentCheckBox->setChecked( value == true );
element = dxfElement.namedItem( QStringLiteral( "force_2d" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mForce2d->setChecked( value == true );
element = dxfElement.namedItem( QStringLiteral( "mtext" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mMTextCheckBox->setChecked( value == true );
element = dxfElement.namedItem( QStringLiteral( "selected_features_only" ) ).toElement();
value = QgsXmlUtils::readVariant( element.firstChildElement() );
if ( !value.isNull() )
mSelectedFeaturesOnly->setChecked( value == true );
return true;
}
void QgsDxfExportDialog::saveSettingsToFile()
{
QString outputFileName = QFileDialog::getSaveFileName( this, tr( "Save DXF Export Settings as XML" ),
QgsDxfExportDialog::settingsDxfLastSettingsDir->value(),
tr( "XML file" ) + " (*.xml)" );
// return dialog focus on Mac
activateWindow();
raise();
if ( outputFileName.isEmpty() )
{
return;
}
//ensure the user never omitted the extension from the file name
if ( !outputFileName.endsWith( QStringLiteral( ".xml" ), Qt::CaseInsensitive ) )
{
outputFileName += QStringLiteral( ".xml" );
}
QDomDocument domDocument;
saveSettingsToXML( domDocument );
const QFileInfo fileInfo( outputFileName );
const QFileInfo dirInfo( fileInfo.path() ); //excludes file name
if ( !dirInfo.isWritable() )
{
mMessageBar->pushInfo( tr( "Save DXF Export Settings" ), tr( "The directory containing your dataset needs to be writable!" ) );
return;
}
QFile file( outputFileName );
if ( file.open( QFile::WriteOnly | QFile::Truncate ) )
{
QTextStream fileStream( &file );
// save as utf-8 with 2 spaces for indents
domDocument.save( fileStream, 2 );
file.close();
mMessageBar->pushSuccess( tr( "Save DXF Export Settings" ), tr( "Created DXF Export settings file as %1" ).arg( outputFileName ) );
QgsDxfExportDialog::settingsDxfLastSettingsDir->setValue( QFileInfo( outputFileName ).absolutePath() );
return;
}
else
{
mMessageBar->pushWarning( tr( "Save DXF Export Settings" ), tr( "Failed to created DXF Export settings file as %1. Check file permissions and retry." ).arg( outputFileName ) );
return;
}
}
void QgsDxfExportDialog::saveSettingsToXML( QDomDocument &doc ) const
{
QDomImplementation DomImplementation;
const QDomDocumentType documentType = DomImplementation.createDocumentType( QStringLiteral( "qgis" ), QStringLiteral( "http://mrcc.com/qgis.dtd" ), QStringLiteral( "SYSTEM" ) );
QDomDocument domDocument( documentType );
QDomElement rootElement = domDocument.createElement( QStringLiteral( "qgis" ) );
rootElement.setAttribute( QStringLiteral( "version" ), Qgis::version() );
domDocument.appendChild( rootElement );
QDomElement dxfElement = domDocument.createElement( QStringLiteral( "dxf_settings" ) );
rootElement.appendChild( dxfElement );
QDomElement symbologyModeElement = domDocument.createElement( QStringLiteral( "symbology_mode" ) );
symbologyModeElement.appendChild( QgsXmlUtils::writeVariant( static_cast<int>( symbologyMode() ), doc ) );
dxfElement.appendChild( symbologyModeElement );
QDomElement symbologyScaleElement = domDocument.createElement( QStringLiteral( "symbology_scale" ) );
symbologyScaleElement.appendChild( QgsXmlUtils::writeVariant( symbologyScale(), doc ) );
dxfElement.appendChild( symbologyScaleElement );
QDomElement encodingElement = domDocument.createElement( QStringLiteral( "encoding" ) );
encodingElement.appendChild( QgsXmlUtils::writeVariant( encoding(), doc ) );
dxfElement.appendChild( encodingElement );
QDomElement crsElement = domDocument.createElement( QStringLiteral( "crs" ) );
crsElement.appendChild( QgsXmlUtils::writeVariant( crs(), doc ) );
dxfElement.appendChild( crsElement );
QDomElement mapThemeElement = domDocument.createElement( QStringLiteral( "map_theme" ) );
mapThemeElement.appendChild( QgsXmlUtils::writeVariant( mapTheme(), doc ) );
dxfElement.appendChild( mapThemeElement );
QDomElement layersElement = domDocument.createElement( QStringLiteral( "layers" ) );
QgsVectorLayerRef vlRef;
const QgsReadWriteContext rwContext = QgsReadWriteContext();
for ( const auto &dxfLayer : layers() )
{
QDomElement layerElement = domDocument.createElement( QStringLiteral( "layer" ) );
vlRef.setLayer( dxfLayer.layer() );
vlRef.writeXml( layerElement, rwContext );
layerElement.setAttribute( QStringLiteral( "attribute-index" ), dxfLayer.layerOutputAttributeIndex() ) ;
layerElement.setAttribute( QStringLiteral( "use_symbol_blocks" ), dxfLayer.buildDataDefinedBlocks() ) ;
layerElement.setAttribute( QStringLiteral( "max_number_of_classes" ), dxfLayer.dataDefinedBlocksMaximumNumberOfClasses() ) ;
layersElement.appendChild( layerElement );
}
dxfElement.appendChild( layersElement );
QDomElement titleAsNameElement = domDocument.createElement( QStringLiteral( "use_layer_title" ) );
titleAsNameElement.appendChild( QgsXmlUtils::writeVariant( layerTitleAsName(), doc ) );
dxfElement.appendChild( titleAsNameElement );
QDomElement useMapExtentElement = domDocument.createElement( QStringLiteral( "use_map_extent" ) );
useMapExtentElement.appendChild( QgsXmlUtils::writeVariant( exportMapExtent(), doc ) );
dxfElement.appendChild( useMapExtentElement );
QDomElement force2dElement = domDocument.createElement( QStringLiteral( "force_2d" ) );
force2dElement.appendChild( QgsXmlUtils::writeVariant( force2d(), doc ) );
dxfElement.appendChild( force2dElement );
QDomElement useMTextElement = domDocument.createElement( QStringLiteral( "mtext" ) );
useMTextElement.appendChild( QgsXmlUtils::writeVariant( useMText(), doc ) );
dxfElement.appendChild( useMTextElement );
QDomElement selectedFeatures = domDocument.createElement( QStringLiteral( "selected_features_only" ) );
selectedFeatures.appendChild( QgsXmlUtils::writeVariant( selectedFeaturesOnly(), doc ) );
dxfElement.appendChild( selectedFeatures );
doc = domDocument;
}
QList< QgsDxfExport::DxfLayer > QgsDxfExportDialog::layers() const
{
return mModel->layers();
}
double QgsDxfExportDialog::symbologyScale() const
{
double scale = mScaleWidget->scale();
if ( qgsDoubleNear( scale, 0.0 ) )
return 1.0;
return scale;
}
QString QgsDxfExportDialog::saveFile() const
{
return mFileName->filePath();
}
Qgis::FeatureSymbologyExport QgsDxfExportDialog::symbologyMode() const
{
return mSymbologyModeComboBox->currentData().value< Qgis::FeatureSymbologyExport >();
}
void QgsDxfExportDialog::setOkEnabled()
{
QPushButton *btn = buttonBox->button( QDialogButtonBox::Ok );
QString filePath = mFileName->filePath();
if ( filePath.isEmpty() )
{
btn->setEnabled( false );
return;
}
QFileInfo fi( filePath );
bool ok = ( fi.absoluteDir().exists() && !fi.baseName().isEmpty() );
btn->setEnabled( ok );
}
bool QgsDxfExportDialog::exportMapExtent() const
{
return mMapExtentCheckBox->isChecked();
}
bool QgsDxfExportDialog::selectedFeaturesOnly() const
{
return mSelectedFeaturesOnly->isChecked();
}
bool QgsDxfExportDialog::layerTitleAsName() const
{
return mLayerTitleAsName->isChecked();
}
bool QgsDxfExportDialog::force2d() const
{
return mForce2d->isChecked();
}
bool QgsDxfExportDialog::useMText() const
{
return mMTextCheckBox->isChecked();
}
void QgsDxfExportDialog::saveSettings()
{
QgsSettings settings;
QFileInfo dxfFileInfo( mFileName->filePath() );
settings.setValue( QStringLiteral( "qgis/lastDxfDir" ), dxfFileInfo.absolutePath() );
settings.setValue( QStringLiteral( "qgis/lastDxfSymbologyMode" ), mSymbologyModeComboBox->currentIndex() );
settings.setValue( QStringLiteral( "qgis/lastSymbologyExportScale" ), mScaleWidget->scale() != 0 ? 1.0 / mScaleWidget->scale() : 0 );
settings.setValue( QStringLiteral( "qgis/lastDxfMapRectangle" ), mMapExtentCheckBox->isChecked() );
settings.setValue( QStringLiteral( "qgis/lastDxfSelectedFeaturesOnly" ), mSelectedFeaturesOnly->isChecked() );
settings.setValue( QStringLiteral( "qgis/lastDxfLayerTitleAsName" ), mLayerTitleAsName->isChecked() );
settings.setValue( QStringLiteral( "qgis/lastDxfEncoding" ), mEncoding->currentText() );
settings.setValue( QStringLiteral( "qgis/lastDxfCrs" ), QString::number( mCRS.srsid() ) );
settings.setValue( QStringLiteral( "qgis/lastDxfUseMText" ), mMTextCheckBox->isChecked() );
settings.setValue( QStringLiteral( "qgis/lastDxfForce2d" ), mForce2d->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfSymbologyMode" ), mSymbologyModeComboBox->currentIndex() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastSymbologyExportScale" ), mScaleWidget->scale() != 0 ? 1.0 / mScaleWidget->scale() : 0 );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfLayerTitleAsName" ), mLayerTitleAsName->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfMapRectangle" ), mMapExtentCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfSelectedFeaturesOnly" ), mSelectedFeaturesOnly->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfEncoding" ), mEncoding->currentText() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastVisibilityPreset" ), mVisibilityPresets->currentText() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfCrs" ), QString::number( mCRS.srsid() ) );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfUseMText" ), mMTextCheckBox->isChecked() );
QgsProject::instance()->writeEntry( QStringLiteral( "dxf" ), QStringLiteral( "/lastDxfForce2d" ), mForce2d->isChecked() );
mModel->saveLayersOutputAttribute( mModel->rootGroup() );
}
QString QgsDxfExportDialog::encoding() const
{
return mEncoding->currentText();
}
void QgsDxfExportDialog::mCrsSelector_crsChanged( const QgsCoordinateReferenceSystem &crs )
{
mCRS = crs;
}
QgsCoordinateReferenceSystem QgsDxfExportDialog::crs() const
{
return mCRS;
}
QString QgsDxfExportDialog::mapTheme() const
{
return mVisibilityPresets->currentText();
}
void QgsDxfExportDialog::showHelp()
{
QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#create-dxf-files" ) );
}