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
18 #include "qgsnewvectorlayerdialog.h"
19 #include "qgsapplication.h"
20 #include "qgsfilewidget.h"
21 #include "qgis.h"
22 #include "qgslogger.h"
23 #include "qgscoordinatereferencesystem.h"
24 #include "qgsproviderregistry.h"
25 #include "qgsvectordataprovider.h"
26 #include "qgsvectorfilewriter.h"
27 #include "qgssettings.h"
28 #include "qgsogrprovider.h"
29 #include "qgsgui.h"
30 #include "qgsfileutils.h"
31
32 #include <QPushButton>
33 #include <QComboBox>
34 #include <QFileDialog>
35 #include <QMessageBox>
36
QgsNewVectorLayerDialog(QWidget * parent,Qt::WindowFlags fl)37 QgsNewVectorLayerDialog::QgsNewVectorLayerDialog( QWidget *parent, Qt::WindowFlags fl )
38 : QDialog( parent, fl )
39 {
40 setupUi( this );
41 QgsGui::instance()->enableAutoGeometryRestore( this );
42
43 connect( mAddAttributeButton, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::mAddAttributeButton_clicked );
44 connect( mRemoveAttributeButton, &QToolButton::clicked, this, &QgsNewVectorLayerDialog::mRemoveAttributeButton_clicked );
45 connect( mFileFormatComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewVectorLayerDialog::mFileFormatComboBox_currentIndexChanged );
46 connect( mTypeBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsNewVectorLayerDialog::mTypeBox_currentIndexChanged );
47 connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsNewVectorLayerDialog::showHelp );
48
49 mAddAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionNewAttribute.svg" ) ) );
50 mRemoveAttributeButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteAttribute.svg" ) ) );
51
52 mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldText.svg" ) ), tr( "Text Data" ), "String" );
53 mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldInteger.svg" ) ), tr( "Whole Number" ), "Integer" );
54 mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldFloat.svg" ) ), tr( "Decimal Number" ), "Real" );
55 mTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconFieldDate.svg" ) ), tr( "Date" ), "Date" );
56
57 mWidth->setValidator( new QIntValidator( 1, 255, this ) );
58 mPrecision->setValidator( new QIntValidator( 0, 15, this ) );
59
60 mGeometryTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconTableLayer.svg" ) ), tr( "No Geometry" ), QgsWkbTypes::NoGeometry );
61 mGeometryTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconPointLayer.svg" ) ), tr( "Point" ), QgsWkbTypes::Point );
62 mGeometryTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconPointLayer.svg" ) ), tr( "MultiPoint" ), QgsWkbTypes::MultiPoint );
63 mGeometryTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconLineLayer.svg" ) ), tr( "Line" ), QgsWkbTypes::LineString );
64 mGeometryTypeBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "/mIconPolygonLayer.svg" ) ), tr( "Polygon" ), QgsWkbTypes::Polygon );
65 mGeometryTypeBox->setCurrentIndex( -1 );
66
67 mOkButton = buttonBox->button( QDialogButtonBox::Ok );
68 mOkButton->setEnabled( false );
69
70 mFileFormatComboBox->addItem( tr( "ESRI Shapefile" ), "ESRI Shapefile" );
71 #if 0
72 // Disabled until provider properly supports editing the created file formats
73 // When enabling this, adapt the window-title of the dialog and the title of all actions showing this dialog.
74 mFileFormatComboBox->addItem( tr( "Comma Separated Value" ), "Comma Separated Value" );
75 mFileFormatComboBox->addItem( tr( "GML" ), "GML" );
76 mFileFormatComboBox->addItem( tr( "Mapinfo File" ), "Mapinfo File" );
77 #endif
78 if ( mFileFormatComboBox->count() == 1 )
79 {
80 mFileFormatComboBox->setVisible( false );
81 mFileFormatLabel->setVisible( false );
82 }
83
84 mFileFormatComboBox->setCurrentIndex( 0 );
85
86 mFileEncoding->addItems( QgsVectorDataProvider::availableEncodings() );
87
88 // Use default encoding if none supplied
89 QString enc = QgsSettings().value( QStringLiteral( "/UI/encoding" ), "System" ).toString();
90
91 // The specified decoding is added if not existing already, and then set current.
92 // This should select it.
93 int encindex = mFileEncoding->findText( enc );
94 if ( encindex < 0 )
95 {
96 mFileEncoding->insertItem( 0, enc );
97 encindex = 0;
98 }
99 mFileEncoding->setCurrentIndex( encindex );
100
101 mAttributeView->addTopLevelItem( new QTreeWidgetItem( QStringList() << QStringLiteral( "id" ) << QStringLiteral( "Integer" ) << QStringLiteral( "10" ) << QString() ) );
102 connect( mNameEdit, &QLineEdit::textChanged, this, &QgsNewVectorLayerDialog::nameChanged );
103 connect( mAttributeView, &QTreeWidget::itemSelectionChanged, this, &QgsNewVectorLayerDialog::selectionChanged );
104 connect( mGeometryTypeBox, static_cast<void( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, [ = ]( int )
105 {
106 updateExtension();
107 checkOk();
108 } );
109
110 mAddAttributeButton->setEnabled( false );
111 mRemoveAttributeButton->setEnabled( false );
112
113 mFileName->setStorageMode( QgsFileWidget::SaveFile );
114 mFileName->setFilter( QgsVectorFileWriter::filterForDriver( mFileFormatComboBox->currentData( Qt::UserRole ).toString() ) );
115 mFileName->setConfirmOverwrite( false );
116 mFileName->setDialogTitle( tr( "Save Layer As" ) );
117 QgsSettings settings;
118 mFileName->setDefaultRoot( settings.value( QStringLiteral( "UI/lastVectorFileFilterDir" ), QDir::homePath() ).toString() );
119 connect( mFileName, &QgsFileWidget::fileChanged, this, [ = ]
120 {
121 QgsSettings settings;
122 QFileInfo tmplFileInfo( mFileName->filePath() );
123 settings.setValue( QStringLiteral( "UI/lastVectorFileFilterDir" ), tmplFileInfo.absolutePath() );
124 checkOk();
125 } );
126 }
127
mFileFormatComboBox_currentIndexChanged(int index)128 void QgsNewVectorLayerDialog::mFileFormatComboBox_currentIndexChanged( int index )
129 {
130 Q_UNUSED( index )
131 if ( mFileFormatComboBox->currentText() == tr( "ESRI Shapefile" ) )
132 mNameEdit->setMaxLength( 10 );
133 else
134 mNameEdit->setMaxLength( 32767 );
135 }
136
mTypeBox_currentIndexChanged(int index)137 void QgsNewVectorLayerDialog::mTypeBox_currentIndexChanged( int index )
138 {
139 // FIXME: sync with providers/ogr/qgsogrprovider.cpp
140 switch ( index )
141 {
142 case 0: // Text data
143 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 255 )
144 mWidth->setText( QStringLiteral( "80" ) );
145 mPrecision->setEnabled( false );
146 mWidth->setValidator( new QIntValidator( 1, 255, this ) );
147 break;
148
149 case 1: // Whole number
150 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 10 )
151 mWidth->setText( QStringLiteral( "10" ) );
152 mPrecision->setEnabled( false );
153 mWidth->setValidator( new QIntValidator( 1, 10, this ) );
154 break;
155
156 case 2: // Decimal number
157 if ( mWidth->text().toInt() < 1 || mWidth->text().toInt() > 20 )
158 mWidth->setText( QStringLiteral( "20" ) );
159 if ( mPrecision->text().toInt() < 1 || mPrecision->text().toInt() > 15 )
160 mPrecision->setText( QStringLiteral( "6" ) );
161
162 mPrecision->setEnabled( true );
163 mWidth->setValidator( new QIntValidator( 1, 20, this ) );
164 break;
165
166 default:
167 QgsDebugMsg( QStringLiteral( "unexpected index" ) );
168 break;
169 }
170 }
171
selectedType() const172 QgsWkbTypes::Type QgsNewVectorLayerDialog::selectedType() const
173 {
174 QgsWkbTypes::Type wkbType = QgsWkbTypes::Unknown;
175 wkbType = static_cast<QgsWkbTypes::Type>
176 ( mGeometryTypeBox->currentData( Qt::UserRole ).toInt() );
177
178 if ( mGeometryWithZRadioButton->isChecked() )
179 wkbType = QgsWkbTypes::addZ( wkbType );
180
181 if ( mGeometryWithMRadioButton->isChecked() )
182 wkbType = QgsWkbTypes::addM( wkbType );
183
184 return wkbType;
185 }
186
crs() const187 QgsCoordinateReferenceSystem QgsNewVectorLayerDialog::crs() const
188 {
189 return mCrsSelector->crs();
190 }
191
setCrs(const QgsCoordinateReferenceSystem & crs)192 void QgsNewVectorLayerDialog::setCrs( const QgsCoordinateReferenceSystem &crs )
193 {
194 mCrsSelector->setCrs( crs );
195 }
196
mAddAttributeButton_clicked()197 void QgsNewVectorLayerDialog::mAddAttributeButton_clicked()
198 {
199 QString myName = mNameEdit->text();
200 QString myWidth = mWidth->text();
201 QString myPrecision = mPrecision->isEnabled() ? mPrecision->text() : QString();
202 //use userrole to avoid translated type string
203 QString myType = mTypeBox->currentData( Qt::UserRole ).toString();
204 mAttributeView->addTopLevelItem( new QTreeWidgetItem( QStringList() << myName << myType << myWidth << myPrecision ) );
205 checkOk();
206 mNameEdit->clear();
207 }
208
mRemoveAttributeButton_clicked()209 void QgsNewVectorLayerDialog::mRemoveAttributeButton_clicked()
210 {
211 delete mAttributeView->currentItem();
212 checkOk();
213 }
214
attributes(QList<QPair<QString,QString>> & at) const215 void QgsNewVectorLayerDialog::attributes( QList< QPair<QString, QString> > &at ) const
216 {
217 QTreeWidgetItemIterator it( mAttributeView );
218 while ( *it )
219 {
220 QTreeWidgetItem *item = *it;
221 QString type = QStringLiteral( "%1;%2;%3" ).arg( item->text( 1 ), item->text( 2 ), item->text( 3 ) );
222 at.push_back( qMakePair( item->text( 0 ), type ) );
223 QgsDebugMsg( QStringLiteral( "appending %1//%2" ).arg( item->text( 0 ), type ) );
224 ++it;
225 }
226 }
227
selectedFileFormat() const228 QString QgsNewVectorLayerDialog::selectedFileFormat() const
229 {
230 //use userrole to avoid translated type string
231 QString myType = mFileFormatComboBox->currentData( Qt::UserRole ).toString();
232 return myType;
233 }
234
selectedFileEncoding() const235 QString QgsNewVectorLayerDialog::selectedFileEncoding() const
236 {
237 return mFileEncoding->currentText();
238 }
239
nameChanged(const QString & name)240 void QgsNewVectorLayerDialog::nameChanged( const QString &name )
241 {
242 mAddAttributeButton->setDisabled( name.isEmpty() || !mAttributeView->findItems( name, Qt::MatchExactly ).isEmpty() );
243 }
244
selectionChanged()245 void QgsNewVectorLayerDialog::selectionChanged()
246 {
247 mRemoveAttributeButton->setDisabled( mAttributeView->selectedItems().isEmpty() );
248 }
249
filename() const250 QString QgsNewVectorLayerDialog::filename() const
251 {
252 return mFileName->filePath();
253 }
254
setFilename(const QString & filename)255 void QgsNewVectorLayerDialog::setFilename( const QString &filename )
256 {
257 mFileName->setFilePath( filename );
258 }
259
checkOk()260 void QgsNewVectorLayerDialog::checkOk()
261 {
262 bool ok = ( !mFileName->filePath().isEmpty() && mAttributeView->topLevelItemCount() > 0 && mGeometryTypeBox->currentIndex() != -1 );
263 mOkButton->setEnabled( ok );
264 }
265
266 // this is static
runAndCreateLayer(QWidget * parent,QString * pEnc,const QgsCoordinateReferenceSystem & crs,const QString & initialPath)267 QString QgsNewVectorLayerDialog::runAndCreateLayer( QWidget *parent, QString *pEnc, const QgsCoordinateReferenceSystem &crs, const QString &initialPath )
268 {
269 QString error;
270 QString res = execAndCreateLayer( error, parent, initialPath, pEnc, crs );
271 if ( res.isEmpty() && error.isEmpty() )
272 res = QString( "" ); // maintain gross earlier API compatibility
273 return res;
274 }
275
updateExtension()276 void QgsNewVectorLayerDialog::updateExtension()
277 {
278 QString fileName = filename();
279 const QString fileformat = selectedFileFormat();
280 const QgsWkbTypes::Type geometrytype = selectedType();
281 if ( fileformat == QLatin1String( "ESRI Shapefile" ) )
282 {
283 if ( geometrytype != QgsWkbTypes::NoGeometry )
284 {
285 fileName = fileName.replace( fileName.lastIndexOf( QLatin1String( ".dbf" ), -1, Qt::CaseInsensitive ), 4, QLatin1String( ".shp" ) );
286 fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, { QStringLiteral( "shp" ) } );
287 }
288 else
289 {
290 fileName = fileName.replace( fileName.lastIndexOf( QLatin1String( ".shp" ), -1, Qt::CaseInsensitive ), 4, QLatin1String( ".dbf" ) );
291 fileName = QgsFileUtils::ensureFileNameHasExtension( fileName, { QStringLiteral( "dbf" ) } );
292 }
293 }
294 setFilename( fileName );
295
296 }
297
accept()298 void QgsNewVectorLayerDialog::accept()
299 {
300 updateExtension();
301
302 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?" ),
303 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel ) != QMessageBox::Yes )
304 return;
305
306 QDialog::accept();
307 }
308
execAndCreateLayer(QString & errorMessage,QWidget * parent,const QString & initialPath,QString * encoding,const QgsCoordinateReferenceSystem & crs)309 QString QgsNewVectorLayerDialog::execAndCreateLayer( QString &errorMessage, QWidget *parent, const QString &initialPath, QString *encoding, const QgsCoordinateReferenceSystem &crs )
310 {
311 errorMessage.clear();
312 QgsNewVectorLayerDialog geomDialog( parent );
313 geomDialog.setCrs( crs );
314 if ( !initialPath.isEmpty() )
315 geomDialog.setFilename( initialPath );
316 if ( geomDialog.exec() == QDialog::Rejected )
317 {
318 return QString();
319 }
320
321 const QString fileformat = geomDialog.selectedFileFormat();
322 const QgsWkbTypes::Type geometrytype = geomDialog.selectedType();
323 QString fileName = geomDialog.filename();
324
325 const QString enc = geomDialog.selectedFileEncoding();
326 QgsDebugMsg( QStringLiteral( "New file format will be: %1" ).arg( fileformat ) );
327
328 QList< QPair<QString, QString> > attributes;
329 geomDialog.attributes( attributes );
330
331 QgsSettings settings;
332 settings.setValue( QStringLiteral( "UI/lastVectorFileFilterDir" ), QFileInfo( fileName ).absolutePath() );
333 settings.setValue( QStringLiteral( "UI/encoding" ), enc );
334
335 //try to create the new layer with OGRProvider instead of QgsVectorFileWriter
336 if ( geometrytype != QgsWkbTypes::Unknown )
337 {
338 QgsCoordinateReferenceSystem srs = geomDialog.crs();
339 bool success = QgsOgrProviderUtils::createEmptyDataSource( fileName, fileformat, enc, geometrytype, attributes, srs, errorMessage );
340 if ( !success )
341 {
342 return QString();
343 }
344 }
345 else
346 {
347 errorMessage = QObject::tr( "Geometry type not recognised" );
348 QgsDebugMsg( errorMessage );
349 return QString();
350 }
351
352 if ( encoding )
353 *encoding = enc;
354
355 return fileName;
356 }
357
showHelp()358 void QgsNewVectorLayerDialog::showHelp()
359 {
360 QgsHelp::openHelp( QStringLiteral( "managing_data_source/create_layers.html#creating-a-new-shapefile-layer" ) );
361 }
362