QGIS API Documentation 3.41.0-Master (88383c3d16f)
Loading...
Searching...
No Matches
qgsnewvectorlayerdialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnewvectorlayerdialog.cpp - description
3 -------------------
4 begin : October 2004
5 copyright : (C) 2004 by Marco Hugentobler
6 email : marco.hugentobler@autoform.ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "moc_qgsnewvectorlayerdialog.cpp"
20#include "qgsapplication.h"
21#include "qgsfilewidget.h"
22#include "qgis.h"
23#include "qgslogger.h"
26#include "qgsvectorfilewriter.h"
27#include "qgssettings.h"
28#include "qgsgui.h"
29#include "qgsiconutils.h"
30#include "qgsfileutils.h"
31#include "qgsvariantutils.h"
32#include "qgsogrproviderutils.h"
33
34#include <gdal.h>
35
36#include <QPushButton>
37#include <QComboBox>
38#include <QFileDialog>
39#include <QMessageBox>
40
41QgsNewVectorLayerDialog::QgsNewVectorLayerDialog( QWidget *parent, Qt::WindowFlags fl )
42 : QDialog( parent, fl )
43{
44 setupUi( this );
46
47 connect( mAddAttributeButton, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::mAddAttributeButton_clicked );
48 connect( mRemoveAttributeButton, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::mRemoveAttributeButton_clicked );
49 connect( mFileFormatComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewVectorLayerDialog::mFileFormatComboBox_currentIndexChanged );
50 connect( mTypeBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewVectorLayerDialog::mTypeBox_currentIndexChanged );
51 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsNewVectorLayerDialog::showHelp );
52 connect( mButtonUp, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::moveFieldsUp );
53 connect( mButtonDown, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::moveFieldsDown );
54
55 mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
56 mRemoveAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
57
58 mTypeBox->addItem( QgsFields::iconForFieldType( QMetaType::Type::QString ), QgsVariantUtils::typeToDisplayString( QMetaType::Type::QString ), "String" );
59 mTypeBox->addItem( QgsFields::iconForFieldType( QMetaType::Type::Int ), QgsVariantUtils::typeToDisplayString( QMetaType::Type::Int ), "Integer" );
60 mTypeBox->addItem( QgsFields::iconForFieldType( QMetaType::Type::Double ), QgsVariantUtils::typeToDisplayString( QMetaType::Type::Double ), "Real" );
61 mTypeBox->addItem( QgsFields::iconForFieldType( QMetaType::Type::QDate ), QgsVariantUtils::typeToDisplayString( QMetaType::Type::QDate ), "Date" );
62#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION( 3, 9, 0 )
63 mTypeBox->addItem( QgsFields::iconForFieldType( QMetaType::Type::Bool ), QgsVariantUtils::typeToDisplayString( QMetaType::Type::Bool ), "bool" );
64#endif
65
66 mWidth->setValidator( new QIntValidator( 1, 255, this ) );
67 mPrecision->setValidator( new QIntValidator( 0, 15, this ) );
68
69 const Qgis::WkbType geomTypes[] = {
75 };
76
77 for ( const auto type : geomTypes )
78 mGeometryTypeBox->addItem( QgsIconUtils::iconForWkbType( type ), QgsWkbTypes::translatedDisplayString( type ), static_cast<quint32>( type ) );
79 mGeometryTypeBox->setCurrentIndex( -1 );
80
81 mOkButton = buttonBox->button( QDialogButtonBox::Ok );
82 mOkButton->setEnabled( false );
83
84 mFileFormatComboBox->addItem( tr( "ESRI Shapefile" ), "ESRI Shapefile" );
85#if 0
86 // Disabled until provider properly supports editing the created file formats
87 // When enabling this, adapt the window-title of the dialog and the title of all actions showing this dialog.
88 mFileFormatComboBox->addItem( tr( "Comma Separated Value" ), "Comma Separated Value" );
89 mFileFormatComboBox->addItem( tr( "GML" ), "GML" );
90 mFileFormatComboBox->addItem( tr( "Mapinfo File" ), "Mapinfo File" );
91#endif
92 if ( mFileFormatComboBox->count() == 1 )
93 {
94 mFileFormatComboBox->setVisible( false );
95 mFileFormatLabel->setVisible( false );
96 }
97
98 mCrsSelector->setShowAccuracyWarnings( true );
99
100 mFileFormatComboBox->setCurrentIndex( 0 );
101
102 mFileEncoding->addItems( QgsVectorDataProvider::availableEncodings() );
103
104 // Use default encoding if none supplied
105 const QString enc = QgsSettings().value( QStringLiteral( "/UI/encoding" ), "System" ).toString();
106
107 // The specified decoding is added if not existing already, and then set current.
108 // This should select it.
109 int encindex = mFileEncoding->findText( enc );
110 if ( encindex < 0 )
111 {
112 mFileEncoding->insertItem( 0, enc );
113 encindex = 0;
114 }
115 mFileEncoding->setCurrentIndex( encindex );
116
117 mAttributeView->addTopLevelItem( new QTreeWidgetItem( QStringList() << QStringLiteral( "id" ) << QStringLiteral( "Integer" ) << QStringLiteral( "10" ) << QString() ) );
118 connect( mNameEdit, &QLineEdit::textChanged, this, &QgsNewVectorLayerDialog::nameChanged );
119 connect( mAttributeView, &QTreeWidget::itemSelectionChanged, this, &QgsNewVectorLayerDialog::selectionChanged );
120 connect( mGeometryTypeBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, [=]( int ) {
121 updateExtension();
122 checkOk();
123 } );
124
125 mAddAttributeButton->setEnabled( false );
126 mRemoveAttributeButton->setEnabled( false );
127 mButtonUp->setEnabled( false );
128 mButtonDown->setEnabled( false );
129
130 mFileName->setStorageMode( QgsFileWidget::SaveFile );
131 mFileName->setFilter( QgsVectorFileWriter::filterForDriver( mFileFormatComboBox->currentData( Qt::UserRole ).toString() ) );
132 mFileName->setConfirmOverwrite( false );
133 mFileName->setDialogTitle( tr( "Save Layer As" ) );
134 const QgsSettings settings;
135 mFileName->setDefaultRoot( settings.value( QStringLiteral( "UI/lastVectorFileFilterDir" ), QDir::homePath() ).toString() );
136 connect( mFileName, &QgsFileWidget::fileChanged, this, [=] {
137 QgsSettings settings;
138 const QFileInfo tmplFileInfo( mFileName->filePath() );
139 settings.setValue( QStringLiteral( "UI/lastVectorFileFilterDir" ), tmplFileInfo.absolutePath() );
140 checkOk();
141 } );
142}
143
144void QgsNewVectorLayerDialog::mFileFormatComboBox_currentIndexChanged( int index )
145{
146 Q_UNUSED( index )
147 if ( mFileFormatComboBox->currentText() == tr( "ESRI Shapefile" ) )
148 mNameEdit->setMaxLength( 10 );
149 else
150 mNameEdit->setMaxLength( 32767 );
151}
152
153void QgsNewVectorLayerDialog::mTypeBox_currentIndexChanged( int index )
154{
155 // FIXME: sync with providers/ogr/qgsogrprovider.cpp
156 switch ( index )
157 {
158 case 0: // Text data
159 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 255 )
160 mWidth->setText( QStringLiteral( "80" ) );
161 mPrecision->setEnabled( false );
162 mWidth->setEnabled( true );
163 mWidth->setValidator( new QIntValidator( 1, 255, this ) );
164 break;
165
166 case 1: // Whole number
167 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 10 )
168 mWidth->setText( QStringLiteral( "10" ) );
169 mPrecision->setEnabled( false );
170 mWidth->setEnabled( true );
171 mWidth->setValidator( new QIntValidator( 1, 10, this ) );
172 break;
173
174 case 2: // Decimal number
175 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 20 )
176 mWidth->setText( QStringLiteral( "20" ) );
177 if ( mPrecision->text().toInt() < 1 || mPrecision->text().toInt() > 15 )
178 mPrecision->setText( QStringLiteral( "6" ) );
179
180 mPrecision->setEnabled( true );
181 mWidth->setEnabled( true );
182 mWidth->setValidator( new QIntValidator( 1, 20, this ) );
183 break;
184
185 default:
186 mPrecision->setEnabled( false );
187 mWidth->setEnabled( false );
188 mWidth->clear();
189 mPrecision->clear();
190 break;
191 }
192}
193
195{
197 wkbType = static_cast<Qgis::WkbType>( mGeometryTypeBox->currentData( Qt::UserRole ).toInt() );
198
199 if ( mGeometryWithZRadioButton->isChecked() )
200 wkbType = QgsWkbTypes::addZ( wkbType );
201
202 if ( mGeometryWithMRadioButton->isChecked() )
203 wkbType = QgsWkbTypes::addM( wkbType );
204
205 return wkbType;
206}
207
209{
210 return mCrsSelector->crs();
211}
212
214{
215 mCrsSelector->setCrs( crs );
216}
217
218void QgsNewVectorLayerDialog::mAddAttributeButton_clicked()
219{
220 const QString myName = mNameEdit->text();
221 const QString myWidth = mWidth->text();
222 const QString myPrecision = mPrecision->isEnabled() ? mPrecision->text() : QString();
223 //use userrole to avoid translated type string
224 const QString myType = mTypeBox->currentData( Qt::UserRole ).toString();
225 mAttributeView->addTopLevelItem( new QTreeWidgetItem( QStringList() << myName << myType << myWidth << myPrecision ) );
226
227 checkOk();
228
229 mNameEdit->clear();
230
231 if ( !mNameEdit->hasFocus() )
232 {
233 mNameEdit->setFocus();
234 }
235}
236
237void QgsNewVectorLayerDialog::mRemoveAttributeButton_clicked()
238{
239 delete mAttributeView->currentItem();
240 checkOk();
241}
242
243void QgsNewVectorLayerDialog::attributes( QList<QPair<QString, QString>> &at ) const
244{
245 QTreeWidgetItemIterator it( mAttributeView );
246 while ( *it )
247 {
248 QTreeWidgetItem *item = *it;
249 const QString type = QStringLiteral( "%1;%2;%3" ).arg( item->text( 1 ), item->text( 2 ), item->text( 3 ) );
250 at.push_back( qMakePair( item->text( 0 ), type ) );
251 QgsDebugMsgLevel( QStringLiteral( "appending %1//%2" ).arg( item->text( 0 ), type ), 2 );
252 ++it;
253 }
254}
255
257{
258 //use userrole to avoid translated type string
259 QString myType = mFileFormatComboBox->currentData( Qt::UserRole ).toString();
260 return myType;
261}
262
264{
265 return mFileEncoding->currentText();
266}
267
268void QgsNewVectorLayerDialog::nameChanged( const QString &name )
269{
270 mAddAttributeButton->setDisabled( name.isEmpty() || !mAttributeView->findItems( name, Qt::MatchExactly ).isEmpty() );
271}
272
273void QgsNewVectorLayerDialog::selectionChanged()
274{
275 mRemoveAttributeButton->setDisabled( mAttributeView->selectedItems().isEmpty() );
276 mButtonUp->setDisabled( mAttributeView->selectedItems().isEmpty() );
277 mButtonDown->setDisabled( mAttributeView->selectedItems().isEmpty() );
278}
279
280void QgsNewVectorLayerDialog::moveFieldsUp()
281{
282 int currentRow = mAttributeView->currentIndex().row();
283 if ( currentRow == 0 )
284 return;
285
286 mAttributeView->insertTopLevelItem( currentRow - 1, mAttributeView->takeTopLevelItem( currentRow ) );
287 mAttributeView->setCurrentIndex( mAttributeView->model()->index( currentRow - 1, 0 ) );
288}
289
290void QgsNewVectorLayerDialog::moveFieldsDown()
291{
292 int currentRow = mAttributeView->currentIndex().row();
293 if ( currentRow == mAttributeView->topLevelItemCount() - 1 )
294 return;
295
296 mAttributeView->insertTopLevelItem( currentRow + 1, mAttributeView->takeTopLevelItem( currentRow ) );
297 mAttributeView->setCurrentIndex( mAttributeView->model()->index( currentRow + 1, 0 ) );
298}
299
301{
302 return mFileName->filePath();
303}
304
305void QgsNewVectorLayerDialog::setFilename( const QString &filename )
306{
307 mFileName->setFilePath( filename );
308}
309
310void QgsNewVectorLayerDialog::checkOk()
311{
312 const bool ok = ( !mFileName->filePath().isEmpty() && mAttributeView->topLevelItemCount() > 0 && mGeometryTypeBox->currentIndex() != -1 );
313 mOkButton->setEnabled( ok );
314}
315
316// this is static
317QString QgsNewVectorLayerDialog::runAndCreateLayer( QWidget *parent, QString *pEnc, const QgsCoordinateReferenceSystem &crs, const QString &initialPath )
318{
319 QString error;
320 QString res = execAndCreateLayer( error, parent, initialPath, pEnc, crs );
321 if ( res.isEmpty() && error.isEmpty() )
322 res = QString( "" ); // maintain gross earlier API compatibility
323 return res;
324}
325
326void QgsNewVectorLayerDialog::updateExtension()
327{
328 QString fileName = filename();
329 const QString fileformat = selectedFileFormat();
330 const Qgis::WkbType geometrytype = selectedType();
331 if ( fileformat == QLatin1String( "ESRI Shapefile" ) )
332 {
333 if ( geometrytype != Qgis::WkbType::NoGeometry )
334 {
335 fileName = fileName.replace( fileName.lastIndexOf( QLatin1String( ".dbf" ), -1, Qt::CaseInsensitive ), 4, QLatin1String( ".shp" ) );
336 fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, { QStringLiteral( "shp" ) } );
337 }
338 else
339 {
340 fileName = fileName.replace( fileName.lastIndexOf( QLatin1String( ".shp" ), -1, Qt::CaseInsensitive ), 4, QLatin1String( ".dbf" ) );
341 fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, { QStringLiteral( "dbf" ) } );
342 }
343 }
344 setFilename( fileName );
345}
346
348{
349 if ( !mNameEdit->text().trimmed().isEmpty() )
350 {
351 const QString currentFieldName = mNameEdit->text();
352 bool currentFound = false;
353 QTreeWidgetItemIterator it( mAttributeView );
354 while ( *it )
355 {
356 QTreeWidgetItem *item = *it;
357 if ( item->text( 0 ) == currentFieldName )
358 {
359 currentFound = true;
360 break;
361 }
362 ++it;
363 }
364
365 if ( !currentFound )
366 {
367 if ( QMessageBox::question( this, windowTitle(), tr( "The field “%1” has not been added to the fields list. Are you sure you want to proceed and discard this field?" ).arg( currentFieldName ), QMessageBox::Ok | QMessageBox::Cancel ) != QMessageBox::Ok )
368 {
369 return;
370 }
371 }
372 }
373
374 updateExtension();
375
376 if ( QFile::exists( filename() ) && QMessageBox::warning( this, tr( "New ShapeFile Layer" ), tr( "The layer already exists. Are you sure you want to overwrite the existing file?" ), QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) != QMessageBox::Yes )
377 return;
378
379 QDialog::accept();
380}
381
382QString QgsNewVectorLayerDialog::execAndCreateLayer( QString &errorMessage, QWidget *parent, const QString &initialPath, QString *encoding, const QgsCoordinateReferenceSystem &crs )
383{
384 errorMessage.clear();
385 QgsNewVectorLayerDialog geomDialog( parent );
386 geomDialog.setCrs( crs );
387 if ( !initialPath.isEmpty() )
388 geomDialog.setFilename( initialPath );
389 if ( geomDialog.exec() == QDialog::Rejected )
390 {
391 return QString();
392 }
393
394 const QString fileformat = geomDialog.selectedFileFormat();
395 const Qgis::WkbType geometrytype = geomDialog.selectedType();
396 QString fileName = geomDialog.filename();
397
398 const QString enc = geomDialog.selectedFileEncoding();
399 QgsDebugMsgLevel( QStringLiteral( "New file format will be: %1" ).arg( fileformat ), 2 );
400
401 QList<QPair<QString, QString>> attributes;
402 geomDialog.attributes( attributes );
403
404 QgsSettings settings;
405 settings.setValue( QStringLiteral( "UI/lastVectorFileFilterDir" ), QFileInfo( fileName ).absolutePath() );
406 settings.setValue( QStringLiteral( "UI/encoding" ), enc );
407
408 //try to create the new layer with OGRProvider instead of QgsVectorFileWriter
409 if ( geometrytype != Qgis::WkbType::Unknown )
410 {
411 const QgsCoordinateReferenceSystem srs = geomDialog.crs();
412 const bool success = QgsOgrProviderUtils::createEmptyDataSource( fileName, fileformat, enc, geometrytype, attributes, srs, errorMessage );
413 if ( !success )
414 {
415 return QString();
416 }
417 }
418 else
419 {
420 errorMessage = QObject::tr( "Geometry type not recognised" );
421 QgsDebugError( errorMessage );
422 return QString();
423 }
424
425 if ( encoding )
426 *encoding = enc;
427
428 return fileName;
429}
430
431void QgsNewVectorLayerDialog::showHelp()
432{
433 QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#creating-a-new-shapefile-layer" ) );
434}
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition qgis.h:256
@ LineString
LineString.
@ MultiPoint
MultiPoint.
@ Polygon
Polygon.
@ NoGeometry
No geometry.
@ Unknown
Unknown.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
This class represents a coordinate reference system (CRS).
static QIcon iconForFieldType(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType, const QString &typeString=QString())
Returns an icon corresponding to a field type.
static QString ensureFileNameHasExtension(const QString &fileName, const QStringList &extensions)
Ensures that a fileName ends with an extension from the provided list of extensions.
@ SaveFile
Select a single new or pre-existing file.
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition qgsgui.cpp:210
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition qgshelp.cpp:39
static QIcon iconForWkbType(Qgis::WkbType type)
Returns the icon for a vector layer whose geometry type is provided.
static Q_DECL_DEPRECATED QString runAndCreateLayer(QWidget *parent=nullptr, QString *enc=nullptr, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem(), const QString &initialPath=QString())
Runs the dialog and creates a layer matching the dialog parameters.
void attributes(QList< QPair< QString, QString > > &at) const
Appends the chosen attribute names and types to at.
QgsCoordinateReferenceSystem crs() const
Returns the selected CRS for the new layer.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs value for the new layer in the dialog.
QString filename() const
Returns the name for the new layer.
QString selectedFileFormat() const
Returns the file format for storage.
QString selectedFileEncoding() const
Returns the file format for storage.
QgsNewVectorLayerDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
New dialog constructor.
static QString execAndCreateLayer(QString &errorMessage, QWidget *parent=nullptr, const QString &initialPath=QString(), QString *encoding=nullptr, const QgsCoordinateReferenceSystem &crs=QgsCoordinateReferenceSystem())
Runs the dialog and creates a layer matching the dialog parameters.
Qgis::WkbType selectedType() const
Returns the selected geometry type.
void setFilename(const QString &filename)
Sets the initial file name to show in the dialog.
This class is a composition of two QSettings instances:
Definition qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString typeToDisplayString(QMetaType::Type type, QMetaType::Type subType=QMetaType::Type::UnknownType)
Returns a user-friendly translated string representing a QVariant type.
static QStringList availableEncodings()
Returns a list of available encodings.
static QString filterForDriver(const QString &driverName)
Creates a filter for an OGR driver key.
static QString translatedDisplayString(Qgis::WkbType type)
Returns a translated display string type for a WKB type, e.g., the geometry name used in WKT geometry...
static Qgis::WkbType addM(Qgis::WkbType type)
Adds the m dimension to a WKB type and returns the new type.
static Qgis::WkbType addZ(Qgis::WkbType type)
Adds the z dimension to a WKB type and returns the new type.
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
const QgsCoordinateReferenceSystem & crs