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