QGIS API Documentation 3.43.0-Master (6c62b930b02)
qgsdbimportvectorlayerdialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdbimportvectorlayerdialog.cpp
3 --------------------------------------
4 Date : March 2025
5 Copyright : (C) 2025 by Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17#include "moc_qgsdbimportvectorlayerdialog.cpp"
19#include "qgsgui.h"
20#include "qgsvectorlayer.h"
22#include "qgsmapcanvas.h"
25#include "qgsproviderregistry.h"
26#include <QPushButton>
27#include <QItemSelectionModel>
28#include <QMenu>
29
31 : QDialog( parent )
32 , mConnection( connection )
33{
34 setupUi( this );
35 setObjectName( "QgsDbImportVectorLayerDialog" );
37
38 mSourceLayerComboBox->setFilters( Qgis::LayerFilter::VectorLayer );
39 connect( mSourceLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsDbImportVectorLayerDialog::sourceLayerComboChanged );
40 connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, [this]( const QgsCoordinateReferenceSystem &crs ) {
41 mExtentGroupBox->setOutputCrs( crs );
42 } );
43
44 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
45 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsDbImportVectorLayerDialog::doImport );
46
47 Q_ASSERT( connection );
48
49 mFieldsView->setDestinationEditable( true );
50 try
51 {
52 mFieldsView->setNativeTypes( connection->nativeTypes() );
53 }
55 {
56 QgsDebugError( QStringLiteral( "Could not retrieve connection native types: %1" ).arg( e.what() ) );
57 }
58 connect( mResetButton, &QPushButton::clicked, this, &QgsDbImportVectorLayerDialog::loadFieldsFromLayer );
59 connect( mAddButton, &QPushButton::clicked, this, &QgsDbImportVectorLayerDialog::addField );
60 connect( mDeleteButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::removeSelectedFields );
61 connect( mUpButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsUp );
62 connect( mDownButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsDown );
63
64 mEditButton->setPopupMode( QToolButton::InstantPopup ); // Show menu on button click
65
66 // Create a menu for the dropdown
67 QMenu *menu = new QMenu( mEditButton );
68
69 // Add menu items
70 menu->addAction( tr( "Convert All Field Names to Lowercase" ), this, [=]() {
71 QgsFieldMappingModel *model = mFieldsView->model();
72 for ( int i = 0; i < model->rowCount(); i++ )
73 {
74 const QModelIndex index = model->index( i, static_cast<int>( QgsFieldMappingModel::ColumnDataIndex::DestinationName ) );
75 const QString name = model->data( index, Qt::EditRole ).toString();
76 model->setData( index, name.toLower(), Qt::EditRole );
77 }
78 } );
79
80 menu->addAction( tr( "Convert All Field Names to Uppercase" ), this, [=]() {
81 QgsFieldMappingModel *model = mFieldsView->model();
82 for ( int i = 0; i < model->rowCount(); i++ )
83 {
84 const QModelIndex index = model->index( i, static_cast<int>( QgsFieldMappingModel::ColumnDataIndex::DestinationName ) );
85 const QString name = model->data( index, Qt::EditRole ).toString();
86 model->setData( index, name.toUpper(), Qt::EditRole );
87 }
88 } );
89
90 mEditButton->setMenu( menu );
91
92 const bool supportsSchemas = mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Schemas );
93 if ( supportsSchemas )
94 {
95 std::unique_ptr<QgsAbstractDatabaseProviderConnection> schemeComboConn;
96 QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( mConnection->providerKey() );
97 mSchemaCombo = new QgsDatabaseSchemaComboBox( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( mConnection->uri(), QVariantMap() ) ) );
98 mLayoutSchemeCombo->addWidget( mSchemaCombo );
99 }
100 else
101 {
102 delete mLabelSchemas;
103 mLabelSchemas = nullptr;
104 delete mLayoutSchemeCombo;
105 mLayoutSchemeCombo = nullptr;
106 }
107
108 const bool supportsPrimaryKeyName = mConnection->tableImportCapabilities().testFlag( Qgis::DatabaseProviderTableImportCapability::SetPrimaryKeyName );
109 if ( !supportsPrimaryKeyName )
110 {
111 delete mLabelPrimaryKey;
112 mLabelPrimaryKey = nullptr;
113 delete mEditPrimaryKey;
114 mEditPrimaryKey = nullptr;
115 }
116
117 const bool supportsGeomColumnName = mConnection->tableImportCapabilities().testFlag( Qgis::DatabaseProviderTableImportCapability::SetGeometryColumnName );
118 if ( !supportsGeomColumnName )
119 {
120 delete mLabelGeometryColumn;
121 mLabelGeometryColumn = nullptr;
122 delete mEditGeometryColumnName;
123 mEditGeometryColumnName = nullptr;
124 }
125
126 const bool supportsTableComments = mConnection->capabilities2().testFlag( Qgis::DatabaseProviderConnectionCapability2::SetTableComment );
127 if ( !supportsTableComments )
128 {
129 delete mLabelComment;
130 mLabelComment = nullptr;
131 delete mEditComment;
132 mEditComment = nullptr;
133 }
134
135 mExtentGroupBox->setTitleBase( tr( "Filter by Extent" ) );
136 mExtentGroupBox->setCheckable( true );
137 mExtentGroupBox->setChecked( false );
138 mExtentGroupBox->setCollapsed( true );
139
140 mFilterExpressionWidget->registerExpressionContextGenerator( this );
141
142 // populate initial layer
143 sourceLayerComboChanged();
144}
145
147{
148 // these widgets all potentially access mOwnedSource, so we need to force
149 // them to be deleted BEFORE the layer
150 delete mSourceLayerComboBox;
151 mSourceLayerComboBox = nullptr;
152 delete mFilterExpressionWidget;
153 mFilterExpressionWidget = nullptr;
154 delete mFieldsView;
155 mFieldsView = nullptr;
156}
157
159{
160 if ( mSchemaCombo )
161 mSchemaCombo->setSchema( schema );
162}
163
165{
166 mOwnedSource.reset();
167 mSourceLayer = nullptr;
168
169 bool owner = false;
170 QString error;
171 QgsVectorLayer *vl = uri.vectorLayer( owner, error );
172 if ( owner )
173 {
174 mOwnedSource.reset( vl );
175 mBlockSourceLayerChanges++;
176 mSourceLayerComboBox->setAdditionalLayers( { vl } );
177 mSourceLayerComboBox->setLayer( vl );
178 mBlockSourceLayerChanges--;
179 setSourceLayer( mOwnedSource.get() );
180 }
181 else if ( vl )
182 {
183 mBlockSourceLayerChanges++;
184 mSourceLayerComboBox->setLayer( vl );
185 mBlockSourceLayerChanges--;
186 setSourceLayer( vl );
187 }
188}
189
190void QgsDbImportVectorLayerDialog::setSourceLayer( QgsVectorLayer *layer )
191{
192 mSourceLayer = layer;
193 if ( !mSourceLayer || !mSourceLayer->dataProvider() )
194 return;
195
196 mEditTable->setText( layer->name() );
197
198 const bool isSpatial = mSourceLayer->isSpatial();
199 if ( mEditGeometryColumnName )
200 mEditGeometryColumnName->setEnabled( isSpatial );
201 if ( mCrsSelector )
202 mCrsSelector->setEnabled( isSpatial );
203
204 mExtentGroupBox->setEnabled( isSpatial );
205 if ( !isSpatial )
206 mExtentGroupBox->setChecked( false );
207
208 const bool extentFilterEnabled = mExtentGroupBox->isChecked();
209 mExtentGroupBox->setOriginalExtent( mSourceLayer->extent(), mSourceLayer->crs() );
210 mExtentGroupBox->setOutputExtentFromOriginal();
211 mExtentGroupBox->setChecked( extentFilterEnabled );
212 mExtentGroupBox->setCollapsed( !extentFilterEnabled );
213
214 mFilterExpressionWidget->setLayer( mSourceLayer );
215
216 if ( mEditPrimaryKey )
217 {
218 // set initial geometry column name. We use the source layer's primary key column name if available,
219 // else fallback to a default value given by the connection
220 const QgsAttributeList pkAttributes = mSourceLayer->dataProvider()->pkAttributeIndexes();
221 QString primaryKey = !pkAttributes.isEmpty() ? mSourceLayer->dataProvider()->fields().at( pkAttributes.at( 0 ) ).name() : QString();
222 if ( primaryKey.isEmpty() )
223 {
224 QgsDataSourceUri dsUri( mSourceLayer->source() );
225 primaryKey = dsUri.keyColumn();
226 }
227 if ( primaryKey.isEmpty() )
228 {
229 primaryKey = mConnection->defaultPrimaryKeyColumnName();
230 }
231
232 mEditPrimaryKey->setText( primaryKey );
233 }
234
235 if ( mEditGeometryColumnName )
236 {
237 // set initial geometry column name. We use the source layer's geometry name if available,
238 // else fallback to a default value given by the connection
239 QString geomColumn = mSourceLayer->dataProvider()->geometryColumnName();
240 if ( geomColumn.isEmpty() )
241 {
242 QgsDataSourceUri dsUri( mSourceLayer->source() );
243 geomColumn = dsUri.geometryColumn();
244 }
245 if ( geomColumn.isEmpty() )
246 {
247 geomColumn = mConnection->defaultGeometryColumnName();
248 }
249
250 mEditGeometryColumnName->setText( geomColumn );
251 }
252
253 if ( mCrsSelector )
254 {
255 mCrsSelector->setCrs( mSourceLayer->crs() );
256 }
257
258 if ( mEditComment )
259 {
260 mEditComment->setPlainText( mSourceLayer->metadata().abstract() );
261 }
262
263 mFieldsView->setSourceLayer( mSourceLayer );
264 mFieldsView->setSourceFields( mSourceLayer->fields() );
265 mFieldsView->setDestinationFields( mSourceLayer->fields() );
266
267 const bool selectedFeatures = mSourceLayer->selectedFeatureCount() > 0;
268 mSourceLayerOnlySelected->setEnabled( selectedFeatures );
269}
270
271void QgsDbImportVectorLayerDialog::loadFieldsFromLayer()
272{
273 if ( mSourceLayer )
274 {
275 mFieldsView->setSourceFields( mSourceLayer->fields() );
276 mFieldsView->setDestinationFields( mSourceLayer->fields() );
277 }
278}
279
280void QgsDbImportVectorLayerDialog::addField()
281{
282 const int rowCount = mFieldsView->model()->rowCount();
283 mFieldsView->appendField( QgsField( QStringLiteral( "new_field" ) ), QStringLiteral( "NULL" ) );
284 const QModelIndex index = mFieldsView->model()->index( rowCount, 0 );
285 mFieldsView->selectionModel()->select(
286 index,
287 QItemSelectionModel::SelectionFlags(
288 QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows
289 )
290 );
291 mFieldsView->scrollTo( index );
292}
293
295{
296 return mSchemaCombo ? mSchemaCombo->currentSchema() : QString();
297}
298
300{
301 return mEditTable->text();
302}
303
305{
306 return mEditComment ? mEditComment->toPlainText() : QString();
307}
308
310{
311 if ( canvas )
312 {
313 mExtentGroupBox->setCurrentExtent( canvas->mapSettings().visibleExtent(), canvas->mapSettings().destinationCrs() );
314 mExtentGroupBox->setMapCanvas( canvas, false );
315 }
316}
317
318void QgsDbImportVectorLayerDialog::doImport()
319{
320 // TODO -- validate
321
322 accept();
323}
324
325std::unique_ptr<QgsVectorLayerExporterTask> QgsDbImportVectorLayerDialog::createExporterTask( const QVariantMap &extraProviderOptions )
326{
327 if ( !mSourceLayer || !mSourceLayer->dataProvider() )
328 return nullptr;
329
330 QString destinationUri;
331 QVariantMap providerOptions;
332
334 exporterOptions.layerName = mEditTable->text();
335 if ( mSchemaCombo )
336 exporterOptions.schema = mSchemaCombo->currentSchema();
337 exporterOptions.wkbType = mSourceLayer->wkbType();
338 if ( mEditPrimaryKey && !mEditPrimaryKey->text().trimmed().isEmpty() )
339 exporterOptions.primaryKeyColumns << mEditPrimaryKey->text();
340 if ( mEditGeometryColumnName )
341 exporterOptions.geometryColumn = mEditGeometryColumnName->text();
342
343 try
344 {
345 destinationUri = mConnection->createVectorLayerExporterDestinationUri( exporterOptions, providerOptions );
346 }
348 {
349 return nullptr;
350 }
351
352 // options given to us by createVectorLayerExporterDestinationUri above should overwrite generic ones passed to this method
353 QVariantMap allProviderOptions = extraProviderOptions;
354 for ( auto it = providerOptions.constBegin(); it != providerOptions.constEnd(); ++it )
355 {
356 allProviderOptions.insert( it.key(), it.value() );
357 }
358
359 // overwrite?
360 if ( mChkDropTable->isChecked() )
361 {
362 allProviderOptions.insert( QStringLiteral( "overwrite" ), true );
363 }
364
365 // This flag tells to the provider that field types do not need conversion -- we have already
366 // explicitly set all fields to provider-specific field types and we do not need to treat
367 // them as generic/different provider fields
368 allProviderOptions.insert( QStringLiteral( "skipConvertFields" ), true );
369
371 if ( mCrsSelector )
372 {
373 exportOptions.setDestinationCrs( mCrsSelector->crs() );
374 }
375 exportOptions.setTransformContext( mSourceLayer->transformContext() );
376 if ( !mFilterExpressionWidget->expression().isEmpty() )
377 {
378 exportOptions.setFilterExpression( mFilterExpressionWidget->expression() );
380 }
381
382 if ( mExtentGroupBox->isEnabled() && mExtentGroupBox->isChecked() )
383 {
384 exportOptions.setExtent( QgsReferencedRectangle( mExtentGroupBox->outputExtent(), mExtentGroupBox->outputCrs() ) );
385 }
386
387 if ( mSourceLayerOnlySelected->isEnabled() && mSourceLayerOnlySelected->isChecked() )
388 {
389 exportOptions.setSelectedOnly( true );
390 }
391
392 const QList<QgsFieldMappingModel::Field> fieldMapping = mFieldsView->mapping();
393 QList<QgsVectorLayerExporter::OutputField> outputFields;
394 outputFields.reserve( fieldMapping.size() );
395 for ( const QgsFieldMappingModel::Field &field : fieldMapping )
396 {
397 outputFields.append( QgsVectorLayerExporter::OutputField( field.field, field.expression ) );
398 }
399 exportOptions.setOutputFields( outputFields );
400
401 return std::make_unique<QgsVectorLayerExporterTask>( mSourceLayer->clone(), destinationUri, mConnection->providerKey(), exportOptions, allProviderOptions, true );
402}
403
410
411void QgsDbImportVectorLayerDialog::sourceLayerComboChanged()
412{
413 if ( mBlockSourceLayerChanges )
414 return;
415
416 if ( mSourceLayerComboBox->currentLayer() == mSourceLayer )
417 return;
418
419 setSourceLayer( qobject_cast< QgsVectorLayer * >( mSourceLayerComboBox->currentLayer() ) );
420}
@ SetPrimaryKeyName
Can set the name of the primary key column.
@ SetGeometryColumnName
Can set the name of the geometry column.
@ SetTableComment
Can set comments for tables via setTableComment()
Provides common functionality for database based connections.
@ Schemas
Can list schemas (if not set, the connection does not support schemas)
virtual QList< QgsVectorDataProvider::NativeType > nativeTypes() const =0
Returns a list of native types supported by the connection.
Represents a coordinate reference system (CRS).
Stores the component parts of a data source URI (e.g.
A combo box which displays the list of schemas for a specific database connection.
void setSchema(const QString &schema)
Sets the current schema selected in the combo box.
QString currentSchema() const
Returns the name of the current schema selected in the combo box.
void setDestinationSchema(const QString &schema)
Sets the destination schema for the new table.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
std::unique_ptr< QgsVectorLayerExporterTask > createExporterTask(const QVariantMap &extraProviderOptions=QVariantMap())
Creates a new exporter task to match the settings defined in the dialog.
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas to associate with the dialog.
void setSourceUri(const QgsMimeDataUtils::Uri &uri)
Sets the source table uri.
QString schema() const
Returns the destination schema.
QString tableName() const
Returns the destination table name.
QString tableComment() const
Returns the optional comment to use for the new table.
QgsDbImportVectorLayerDialog(QgsAbstractDatabaseProviderConnection *connection, QWidget *parent=nullptr)
Constructor for QgsDbImportVectorLayerDialog.
QString what() const
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
Holds mapping information for mapping from one set of QgsFields to another.
@ DestinationName
Destination field name.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
bool removeSelectedFields()
Removes the currently selected field from the model.
bool moveSelectedFieldsDown()
Moves down the currently selected field.
bool moveSelectedFieldsUp()
Moves up currently selected field.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
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:210
Map canvas is a class for displaying all GIS data types on a canvas.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
void layerChanged(QgsMapLayer *layer)
Emitted whenever the currently selected layer changes.
QString name
Definition qgsmaplayer.h:81
QgsRectangle visibleExtent() const
Returns the actual extent derived from requested extent that takes output image size into account.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system for the map render.
void crsChanged(const QgsCoordinateReferenceSystem &crs)
Emitted when the selected CRS is changed.
Custom exception class for provider connection related exceptions.
Holds data provider key, description, and associated shared library file or function pointer informat...
virtual QgsAbstractProviderConnection * createConnection(const QString &uri, const QVariantMap &configuration)
Creates a new connection from uri and configuration, the newly created connection is not automaticall...
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
A QgsRectangle with associated coordinate reference system.
Encapsulates options for use with QgsVectorLayerExporter.
void setExtent(const QgsReferencedRectangle &extent)
Sets an extent filter for the features to export.
void setOutputFields(const QList< QgsVectorLayerExporter::OutputField > &fields)
Sets the output field definitions for the destination table.
void setSelectedOnly(bool selected)
Sets whether the export should only include selected features.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system to use for exported features.
void setFilterExpression(const QString &expression)
Set the filter expression for the features to export.
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context to use when a filterExpression() is set.
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the coordinate transform context to use when transforming exported features.
Represents a vector layer which manages a vector based dataset.
QList< int > QgsAttributeList
Definition qgsfield.h:27
#define QgsDebugError(str)
Definition qgslogger.h:40
const QgsCoordinateReferenceSystem & crs
Stores all information required to create a QgsVectorLayerExporter for the backend.
QStringList primaryKeyColumns
List of primary key column names. Note that some providers may ignore this if not supported.
QString schema
Optional schema for the new layer. May not be supported by all providers.
QString geometryColumn
Preferred name for the geometry column, if required. Note that some providers may ignore this if a sp...
The Field struct holds information about a mapped field.
QgsVectorLayer * vectorLayer(bool &owner, QString &error) const
Gets vector layer from uri if possible, otherwise returns nullptr and error is set.
Encapsulates output field definition.