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