23#include <cpl_string.h>
24#include <gdal_version.h>
26#include <ogr_srs_api.h>
49#include "moc_qgsnewgeopackagelayerdialog.cpp"
51using namespace Qt::StringLiterals;
53#define DEFAULT_OGR_FID_COLUMN_TITLE "fid"
56 : QDialog( parent, fl )
59 setObjectName( u
"QgsNewGeoPackageLayerDialog"_s );
62 connect( mAddAttributeButton, &QToolButton::clicked,
this, &QgsNewGeoPackageLayerDialog::mAddAttributeButton_clicked );
63 connect( mRemoveAttributeButton, &QToolButton::clicked,
this, &QgsNewGeoPackageLayerDialog::mRemoveAttributeButton_clicked );
64 connect( mFieldTypeBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsNewGeoPackageLayerDialog::mFieldTypeBox_currentIndexChanged );
65 connect( mGeometryTypeBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsNewGeoPackageLayerDialog::mGeometryTypeBox_currentIndexChanged );
66 connect( mTableNameEdit, &QLineEdit::textChanged,
this, &QgsNewGeoPackageLayerDialog::mTableNameEdit_textChanged );
67 connect( mTableNameEdit, &QLineEdit::textEdited,
this, &QgsNewGeoPackageLayerDialog::mTableNameEdit_textEdited );
68 connect( mLayerIdentifierEdit, &QLineEdit::textEdited,
this, &QgsNewGeoPackageLayerDialog::mLayerIdentifierEdit_textEdited );
69 connect( buttonBox, &QDialogButtonBox::accepted,
this, &QgsNewGeoPackageLayerDialog::buttonBox_accepted );
70 connect( buttonBox, &QDialogButtonBox::rejected,
this, &QgsNewGeoPackageLayerDialog::buttonBox_rejected );
71 connect( buttonBox, &QDialogButtonBox::helpRequested,
this, &QgsNewGeoPackageLayerDialog::showHelp );
72 connect( mButtonUp, &QToolButton::clicked,
this, &QgsNewGeoPackageLayerDialog::moveFieldsUp );
73 connect( mButtonDown, &QToolButton::clicked,
this, &QgsNewGeoPackageLayerDialog::moveFieldsDown );
78 const auto addGeomItem = [
this]( OGRwkbGeometryType ogrGeomType ) {
83 addGeomItem( wkbNone );
84 addGeomItem( wkbPoint );
85 addGeomItem( wkbLineString );
86 addGeomItem( wkbPolygon );
87 addGeomItem( wkbMultiPoint );
88 addGeomItem( wkbMultiLineString );
89 addGeomItem( wkbMultiPolygon );
93 addGeomItem( wkbCircularString );
95 addGeomItem( wkbCompoundCurve );
96 addGeomItem( wkbCurvePolygon );
97 addGeomItem( wkbMultiCurve );
98 addGeomItem( wkbMultiSurface );
99 addGeomItem( wkbPolyhedralSurface );
100 addGeomItem( wkbTIN );
101 mGeometryTypeBox->setCurrentIndex( -1 );
103 mGeometryWithZCheckBox->setEnabled(
false );
104 mGeometryWithMCheckBox->setEnabled(
false );
105 mGeometryColumnEdit->setEnabled(
false );
106 mGeometryColumnEdit->setText( u
"geometry"_s );
108 mCheckBoxCreateSpatialIndex->setEnabled(
false );
109 mCrsSelector->setEnabled(
false );
110 mCrsSelector->setShowAccuracyWarnings(
true );
122 mOkButton = buttonBox->button( QDialogButtonBox::Ok );
123 mOkButton->setEnabled(
false );
125 connect( mFieldNameEdit, &QLineEdit::textChanged,
this, &QgsNewGeoPackageLayerDialog::fieldNameChanged );
126 connect( mAttributeView, &QTreeWidget::itemSelectionChanged,
this, &QgsNewGeoPackageLayerDialog::selectionChanged );
127 connect( mTableNameEdit, &QLineEdit::textChanged,
this, &QgsNewGeoPackageLayerDialog::checkOk );
128 connect( mGeometryTypeBox,
static_cast<void ( QComboBox::* )(
int )
>( &QComboBox::currentIndexChanged ),
this, &QgsNewGeoPackageLayerDialog::checkOk );
130 mAddAttributeButton->setEnabled(
false );
131 mRemoveAttributeButton->setEnabled(
false );
132 mButtonUp->setEnabled(
false );
133 mButtonDown->setEnabled(
false );
135 mCheckBoxCreateSpatialIndex->setChecked(
true );
139 mFileName->setFilter( tr(
"GeoPackage" ) +
" (*.gpkg)" );
140 mFileName->setDialogTitle( tr(
"Select Existing or Create a New GeoPackage Database File…" ) );
141 mFileName->setDefaultRoot( settings.
value( u
"UI/lastVectorFileFilterDir"_s, QDir::homePath() ).toString() );
142 mFileName->setConfirmOverwrite(
false );
145 const QFileInfo tmplFileInfo( filePath );
146 settings.
setValue( u
"UI/lastVectorFileFilterDir"_s, tmplFileInfo.absolutePath() );
147 if ( !filePath.isEmpty() && !mTableNameEdited )
149 const QFileInfo fileInfo( filePath );
150 mTableNameEdit->setText( fileInfo.baseName() );
157 QCompleter *completer =
new QCompleter(
this );
158 completer->setModel( ogrProviderModel );
160 completer->setCompletionMode( QCompleter::PopupCompletion );
161 completer->setFilterMode( Qt::MatchContains );
162 mFileName->lineEdit()->setCompleter( completer );
167 mCrsSelector->setCrs( crs );
172 mFileName->setReadOnly(
true );
175void QgsNewGeoPackageLayerDialog::mFieldTypeBox_currentIndexChanged(
int )
177 const QString myType = mFieldTypeBox->currentData( Qt::UserRole ).toString();
178 mFieldLengthEdit->setEnabled( myType ==
"text"_L1 );
179 if ( myType !=
"text"_L1 )
180 mFieldLengthEdit->clear();
184void QgsNewGeoPackageLayerDialog::mGeometryTypeBox_currentIndexChanged(
int )
186 const OGRwkbGeometryType geomType =
static_cast<OGRwkbGeometryType
>( mGeometryTypeBox->currentData( Qt::UserRole ).toInt() );
187 const bool isSpatial = geomType != wkbNone;
188 mGeometryWithZCheckBox->setEnabled( isSpatial );
189 mGeometryWithMCheckBox->setEnabled( isSpatial );
190 mGeometryColumnEdit->setEnabled( isSpatial );
191 mCheckBoxCreateSpatialIndex->setEnabled( isSpatial );
192 mCrsSelector->setEnabled( isSpatial );
195void QgsNewGeoPackageLayerDialog::mTableNameEdit_textChanged(
const QString &text )
197 mTableNameEdited = !text.isEmpty();
198 if ( !text.isEmpty() && !mLayerIdentifierEdited )
200 mLayerIdentifierEdit->setText( text );
204void QgsNewGeoPackageLayerDialog::mTableNameEdit_textEdited(
const QString &text )
207 mTableNameEdited = !text.isEmpty();
210void QgsNewGeoPackageLayerDialog::mLayerIdentifierEdit_textEdited(
const QString &text )
213 mLayerIdentifierEdited = !text.isEmpty();
216void QgsNewGeoPackageLayerDialog::checkOk()
218 const bool ok = !mFileName->filePath().isEmpty() && !mTableNameEdit->text().isEmpty() && mGeometryTypeBox->currentIndex() != -1;
220 mOkButton->setEnabled( ok );
223void QgsNewGeoPackageLayerDialog::mAddAttributeButton_clicked()
225 if ( !mFieldNameEdit->text().isEmpty() )
227 const QString myName = mFieldNameEdit->text();
228 const QString featureId = mFeatureIdColumnEdit->text().isEmpty() ? QStringLiteral(
DEFAULT_OGR_FID_COLUMN_TITLE ) : mFeatureIdColumnEdit->text();
229 if ( myName.compare( featureId, Qt::CaseInsensitive ) == 0 )
231 QMessageBox::critical( this, tr(
"Add Field" ), tr(
"The field cannot have the same name as the feature identifier." ) );
236 const QString myType = mFieldTypeBox->currentData( Qt::UserRole ).toString();
237 const QString length = mFieldLengthEdit->text();
238 mAttributeView->addTopLevelItem(
new QTreeWidgetItem( QStringList() << myName << myType << length ) );
242 mFieldNameEdit->clear();
244 if ( !mFieldNameEdit->hasFocus() )
246 mFieldNameEdit->setFocus();
251void QgsNewGeoPackageLayerDialog::mRemoveAttributeButton_clicked()
253 delete mAttributeView->currentItem();
258void QgsNewGeoPackageLayerDialog::fieldNameChanged(
const QString &name )
260 mAddAttributeButton->setDisabled( name.isEmpty() || !mAttributeView->findItems( name, Qt::MatchExactly ).isEmpty() );
263void QgsNewGeoPackageLayerDialog::selectionChanged()
265 mRemoveAttributeButton->setDisabled( mAttributeView->selectedItems().isEmpty() );
266 mButtonUp->setDisabled( mAttributeView->selectedItems().isEmpty() );
267 mButtonDown->setDisabled( mAttributeView->selectedItems().isEmpty() );
270void QgsNewGeoPackageLayerDialog::buttonBox_accepted()
276void QgsNewGeoPackageLayerDialog::buttonBox_rejected()
281void QgsNewGeoPackageLayerDialog::moveFieldsUp()
283 int currentRow = mAttributeView->currentIndex().row();
284 if ( currentRow == 0 )
287 mAttributeView->insertTopLevelItem( currentRow - 1, mAttributeView->takeTopLevelItem( currentRow ) );
288 mAttributeView->setCurrentIndex( mAttributeView->model()->index( currentRow - 1, 0 ) );
291void QgsNewGeoPackageLayerDialog::moveFieldsDown()
293 int currentRow = mAttributeView->currentIndex().row();
294 if ( currentRow == mAttributeView->topLevelItemCount() - 1 )
297 mAttributeView->insertTopLevelItem( currentRow + 1, mAttributeView->takeTopLevelItem( currentRow ) );
298 mAttributeView->setCurrentIndex( mAttributeView->model()->index( currentRow + 1, 0 ) );
301bool QgsNewGeoPackageLayerDialog::apply()
303 if ( !mFieldNameEdit->text().trimmed().isEmpty() )
305 const QString currentFieldName = mFieldNameEdit->text();
306 bool currentFound =
false;
307 QTreeWidgetItemIterator it( mAttributeView );
310 QTreeWidgetItem *item = *it;
311 if ( item->text( 0 ) == currentFieldName )
321 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 )
328 QString fileName( mFileName->filePath() );
329 if ( !fileName.endsWith(
".gpkg"_L1, Qt::CaseInsensitive ) )
330 fileName +=
".gpkg"_L1;
332 bool createNewDb =
false;
334 if ( QFile::exists( fileName ) )
336 bool overwrite =
false;
343 msgBox.setIcon( QMessageBox::Question );
344 msgBox.setWindowTitle( tr(
"New GeoPackage Layer" ) );
345 msgBox.setText( tr(
"The File already exists. Do you want to overwrite the existing file with a new database or add a new layer to it?" ) );
346 QPushButton *overwriteButton = msgBox.addButton( tr(
"Overwrite" ), QMessageBox::ActionRole );
347 QPushButton *addNewLayerButton = msgBox.addButton( tr(
"Add New Layer" ), QMessageBox::ActionRole );
348 msgBox.setStandardButtons( QMessageBox::Cancel );
349 msgBox.setDefaultButton( addNewLayerButton );
351 if ( property(
"hideDialogs" ).toBool() )
353 overwrite = property(
"question_existing_db_answer_overwrite" ).toBool();
355 cancel = !property(
"question_existing_db_answer_add_new_layer" ).toBool();
359 const int ret = msgBox.exec();
360 if ( ret == QMessageBox::Cancel )
362 if ( msgBox.clickedButton() == overwriteButton )
383 QFile( fileName ).remove();
392 OGRSFDriverH hGpkgDriver = OGRGetDriverByName(
"GPKG" );
395 if ( !property(
"hideDialogs" ).toBool() )
396 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), tr(
"Layer creation failed. GeoPackage driver not found." ) );
403 hDS.reset( OGR_Dr_CreateDataSource( hGpkgDriver, fileName.toUtf8().constData(),
nullptr ) );
406 const QString msg( tr(
"Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
407 if ( !property(
"hideDialogs" ).toBool() )
408 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), msg );
414 OGRSFDriverH hDriver =
nullptr;
415 hDS.reset( OGROpen( fileName.toUtf8().constData(),
true, &hDriver ) );
418 const QString msg( tr(
"Opening of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
419 if ( !property(
"hideDialogs" ).toBool() )
420 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), msg );
423 if ( hDriver != hGpkgDriver )
425 const QString msg( tr(
"Opening of file succeeded, but this is not a GeoPackage database." ) );
426 if ( !property(
"hideDialogs" ).toBool() )
427 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), msg );
432 const QString tableName( mTableNameEdit->text() );
434 bool overwriteTable =
false;
435 if ( OGR_DS_GetLayerByName( hDS.get(), tableName.toUtf8().constData() ) )
437 if ( property(
"hideDialogs" ).toBool() )
439 overwriteTable = property(
"question_existing_layer_answer_overwrite" ).toBool();
441 else if ( QMessageBox::question(
this, tr(
"New GeoPackage Layer" ), tr(
"A table with the same name already exists. Do you want to overwrite it?" ), QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) == QMessageBox::Yes )
443 overwriteTable =
true;
446 if ( !overwriteTable )
452 const QString layerIdentifier( mLayerIdentifierEdit->text() );
453 const QString layerDescription( mLayerDescriptionEdit->text() );
455 OGRwkbGeometryType wkbType =
static_cast<OGRwkbGeometryType
>( mGeometryTypeBox->currentData( Qt::UserRole ).toInt() );
458 if ( mGeometryWithZCheckBox->isChecked() )
459 wkbType = OGR_GT_SetZ( wkbType );
461 if ( mGeometryWithMCheckBox->isChecked() )
462 wkbType = OGR_GT_SetM( wkbType );
466 bool isNonStandardGeomType =
false;
472 OGRSpatialReferenceH hSRS =
nullptr;
474 const QgsCoordinateReferenceSystem srs = mCrsSelector->crs();
475 if ( wkbType != wkbNone && srs.
isValid() )
481 char **options =
nullptr;
483 if ( overwriteTable )
484 options = CSLSetNameValue( options,
"OVERWRITE",
"YES" );
485 if ( !layerIdentifier.isEmpty() )
486 options = CSLSetNameValue( options,
"IDENTIFIER", layerIdentifier.toUtf8().constData() );
487 if ( !layerDescription.isEmpty() )
488 options = CSLSetNameValue( options,
"DESCRIPTION", layerDescription.toUtf8().constData() );
490 const QString featureId( mFeatureIdColumnEdit->text() );
491 if ( !featureId.isEmpty() )
492 options = CSLSetNameValue( options,
"FID", featureId.toUtf8().constData() );
494 const QString geometryColumn( mGeometryColumnEdit->text() );
495 if ( wkbType != wkbNone && !geometryColumn.isEmpty() )
496 options = CSLSetNameValue( options,
"GEOMETRY_COLUMN", geometryColumn.toUtf8().constData() );
498 if ( wkbType != wkbNone )
499 options = CSLSetNameValue( options,
"SPATIAL_INDEX", mCheckBoxCreateSpatialIndex->isChecked() ?
"YES" :
"NO" );
501 OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, wkbType, options );
502 CSLDestroy( options );
507 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
508 if ( !property(
"hideDialogs" ).toBool() )
509 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), msg );
513 QTreeWidgetItemIterator it( mAttributeView );
516 const QString fieldName( ( *it )->text( 0 ) );
517 const QString fieldType( ( *it )->text( 1 ) );
518 const QString fieldWidth( ( *it )->text( 2 ) );
520 OGRFieldType ogrType( OFTString );
521 OGRFieldSubType ogrSubType = OFSTNone;
522 if ( fieldType ==
"text"_L1 )
524 else if ( fieldType ==
"integer"_L1 )
525 ogrType = OFTInteger;
526 else if ( fieldType ==
"integer64"_L1 )
527 ogrType = OFTInteger64;
528 else if ( fieldType ==
"real"_L1 )
530 else if ( fieldType ==
"date"_L1 )
532 else if ( fieldType ==
"datetime"_L1 )
533 ogrType = OFTDateTime;
534 else if ( fieldType ==
"bool"_L1 )
536 ogrType = OFTInteger;
537 ogrSubType = OFSTBoolean;
539 else if ( fieldType ==
"binary"_L1 )
541 else if ( fieldType ==
"json"_L1 )
544 ogrSubType = OFSTJSON;
547 const int ogrWidth = fieldWidth.toInt();
550 if ( ogrSubType != OFSTNone )
551 OGR_Fld_SetSubType( fld.get(), ogrSubType );
553 if ( ogrType != OFTBinary )
554 OGR_Fld_SetWidth( fld.get(), ogrWidth );
556 if ( OGR_L_CreateField( hLayer, fld.get(),
true ) != OGRERR_NONE )
558 if ( !property(
"hideDialogs" ).toBool() )
560 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), tr(
"Creation of field %1 failed (OGR error: %2)" ).arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
571 OGR_L_ResetReading( hLayer );
572 const CPLErr errorType = CPLGetLastErrorType();
573 if ( errorType == CE_Failure || errorType == CE_Fatal )
575 const QString msg( tr(
"Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
576 if ( !property(
"hideDialogs" ).toBool() )
577 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), msg );
580 else if ( errorType == CE_Warning && !isNonStandardGeomType )
583 const QString msg( tr(
"Layer created with warning (OGR warning: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
584 if ( !property(
"hideDialogs" ).toBool() )
585 QMessageBox::warning(
this, tr(
"New GeoPackage Layer" ), msg );
589 const QString uri( u
"%1|layername=%2"_s.arg( fileName, tableName ) );
590 const QString userVisiblelayerName( layerIdentifier.isEmpty() ? tableName : layerIdentifier );
592 auto layer = std::make_unique<QgsVectorLayer>( uri, userVisiblelayerName, u
"ogr"_s, layerOptions );
593 if ( layer->isValid() )
598 QList<QgsMapLayer *> myList;
599 myList << layer.release();
612 if ( !property(
"hideDialogs" ).toBool() )
613 QMessageBox::critical(
this, tr(
"New GeoPackage Layer" ), tr(
"%1 is an invalid layer and cannot be loaded." ).arg( tableName ) );
621 mBehavior = behavior;
626 mAddToProject = addToProject;
629void QgsNewGeoPackageLayerDialog::showHelp()
631 QgsHelp::openHelp( u
"managing_data_source/create_layers.html#creating-a-new-geopackage-layer"_s );
WkbType
The WKB type describes the number of dimensions a geometry has.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
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 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...
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
static QIcon iconForWkbType(Qgis::WkbType type)
Returns the icon for a vector layer whose geometry type is provided.
OverwriteBehavior
Behavior to use when an existing geopackage already exists.
@ AddNewLayer
Keep existing contents and add new layer.
@ Overwrite
Overwrite whole geopackage.
@ Prompt
Prompt user for action.
void setAddToProject(bool addToProject)
Sets whether a newly created layer should automatically be added to the current project.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs value for the new layer in the dialog.
void lockDatabasePath()
Sets the database path widgets to a locked and read-only mode.
QgsNewGeoPackageLayerDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags)
Constructor.
void setOverwriteBehavior(OverwriteBehavior behavior)
Sets the behavior to use when a path to an existing geopackage file is used.
static Qgis::WkbType ogrGeometryTypeToQgsWkbType(OGRwkbGeometryType ogrGeomType)
Converts a OGRwkbGeometryType to QgsWkbTypes::Type.
static OGRSpatialReferenceH crsToOGRSpatialReference(const QgsCoordinateReferenceSystem &crs)
Returns a OGRSpatialReferenceH corresponding to the specified crs object.
static QgsProject * instance()
Returns the QgsProject singleton instance.
QgsCoordinateTransformContext transformContext
A model containing registered connection names for a specific data provider.
@ Uri
Connection URI string.
Stores settings for use within QGIS.
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 Q_INVOKABLE QString translatedDisplayString(Qgis::WkbType type)
Returns a translated display string type for a WKB type, e.g., the geometry name used in WKT geometry...
bool warnAboutNonStandardGeoPackageGeometryType(Qgis::WkbType wkbType, QWidget *parent, const QString &dialogTitle, bool showDialog, bool *isNonStandard)
Checks if the given wkbType is a non-standard GeoPackage geometry type (PolyhedralSurface,...
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
#define DEFAULT_OGR_FID_COLUMN_TITLE