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