QGIS API Documentation 3.99.0-Master (26c88405ac0)
Loading...
Searching...
No Matches
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
21#include "qgsgui.h"
22#include "qgsmapcanvas.h"
23#include "qgsproviderregistry.h"
24#include "qgsvectorlayer.h"
26
27#include <QItemSelectionModel>
28#include <QMenu>
29#include <QPushButton>
30
31#include "moc_qgsdbimportvectorlayerdialog.cpp"
32
34 : QDialog( parent )
35 , mConnection( connection )
36{
37 setupUi( this );
38 setObjectName( "QgsDbImportVectorLayerDialog" );
40
41 mSourceLayerComboBox->setFilters( Qgis::LayerFilter::VectorLayer );
42 connect( mSourceLayerComboBox, &QgsMapLayerComboBox::layerChanged, this, &QgsDbImportVectorLayerDialog::sourceLayerComboChanged );
43 connect( mCrsSelector, &QgsProjectionSelectionWidget::crsChanged, this, [this]( const QgsCoordinateReferenceSystem &crs ) {
44 mExtentGroupBox->setOutputCrs( crs );
45 } );
46
47 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
48 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QgsDbImportVectorLayerDialog::doImport );
49
50 Q_ASSERT( connection );
51
52 mFieldsView->setDestinationEditable( true );
53 try
54 {
55 mFieldsView->setNativeTypes( connection->nativeTypes() );
56 }
58 {
59 QgsDebugError( QStringLiteral( "Could not retrieve connection native types: %1" ).arg( e.what() ) );
60 }
61 connect( mResetButton, &QPushButton::clicked, this, &QgsDbImportVectorLayerDialog::loadFieldsFromLayer );
62 connect( mAddButton, &QPushButton::clicked, this, &QgsDbImportVectorLayerDialog::addField );
63 connect( mDeleteButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::removeSelectedFields );
64 connect( mUpButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsUp );
65 connect( mDownButton, &QPushButton::clicked, mFieldsView, &QgsFieldMappingWidget::moveSelectedFieldsDown );
66
67 mEditButton->setPopupMode( QToolButton::InstantPopup ); // Show menu on button click
68
69 // Create a menu for the dropdown
70 QMenu *menu = new QMenu( mEditButton );
71
72 // Add menu items
73 menu->addAction( tr( "Convert All Field Names to Lowercase" ), this, [this]() {
74 QgsFieldMappingModel *model = mFieldsView->model();
75 for ( int i = 0; i < model->rowCount(); i++ )
76 {
77 const QModelIndex index = model->index( i, static_cast<int>( QgsFieldMappingModel::ColumnDataIndex::DestinationName ) );
78 const QString name = model->data( index, Qt::EditRole ).toString();
79 model->setData( index, name.toLower(), Qt::EditRole );
80 }
81 } );
82
83 menu->addAction( tr( "Convert All Field Names to Uppercase" ), this, [this]() {
84 QgsFieldMappingModel *model = mFieldsView->model();
85 for ( int i = 0; i < model->rowCount(); i++ )
86 {
87 const QModelIndex index = model->index( i, static_cast<int>( QgsFieldMappingModel::ColumnDataIndex::DestinationName ) );
88 const QString name = model->data( index, Qt::EditRole ).toString();
89 model->setData( index, name.toUpper(), Qt::EditRole );
90 }
91 } );
92
93 mEditButton->setMenu( menu );
94
95 const bool supportsSchemas = mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Schemas );
96 if ( supportsSchemas )
97 {
98 std::unique_ptr<QgsAbstractDatabaseProviderConnection> schemeComboConn;
99 QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( mConnection->providerKey() );
100 mSchemaCombo = new QgsDatabaseSchemaComboBox( static_cast<QgsAbstractDatabaseProviderConnection *>( md->createConnection( mConnection->uri(), QVariantMap() ) ) );
101 mLayoutSchemeCombo->addWidget( mSchemaCombo );
102 }
103 else
104 {
105 delete mLabelSchemas;
106 mLabelSchemas = nullptr;
107 delete mLayoutSchemeCombo;
108 mLayoutSchemeCombo = nullptr;
109 }
110
111 const bool supportsPrimaryKeyName = mConnection->tableImportCapabilities().testFlag( Qgis::DatabaseProviderTableImportCapability::SetPrimaryKeyName );
112 if ( !supportsPrimaryKeyName )
113 {
114 delete mLabelPrimaryKey;
115 mLabelPrimaryKey = nullptr;
116 delete mEditPrimaryKey;
117 mEditPrimaryKey = nullptr;
118 }
119
120 const bool supportsGeomColumnName = mConnection->tableImportCapabilities().testFlag( Qgis::DatabaseProviderTableImportCapability::SetGeometryColumnName );
121 if ( !supportsGeomColumnName )
122 {
123 delete mLabelGeometryColumn;
124 mLabelGeometryColumn = nullptr;
125 delete mEditGeometryColumnName;
126 mEditGeometryColumnName = nullptr;
127 }
128
129 const bool supportsTableComments = mConnection->capabilities2().testFlag( Qgis::DatabaseProviderConnectionCapability2::SetTableComment );
130 if ( !supportsTableComments )
131 {
132 delete mLabelComment;
133 mLabelComment = nullptr;
134 delete mEditComment;
135 mEditComment = nullptr;
136 }
137
138 mExtentGroupBox->setTitleBase( tr( "Filter by Extent" ) );
139 mExtentGroupBox->setCheckable( true );
140 mExtentGroupBox->setChecked( false );
141 mExtentGroupBox->setCollapsed( true );
142
143 mFilterExpressionWidget->registerExpressionContextGenerator( this );
144
145 // populate initial layer
146 sourceLayerComboChanged();
147}
148
150{
151 // these widgets all potentially access mOwnedSource, so we need to force
152 // them to be deleted BEFORE the layer
153 delete mSourceLayerComboBox;
154 mSourceLayerComboBox = nullptr;
155 delete mFilterExpressionWidget;
156 mFilterExpressionWidget = nullptr;
157 delete mFieldsView;
158 mFieldsView = nullptr;
159}
160
162{
163 if ( mSchemaCombo )
164 mSchemaCombo->setSchema( schema );
165}
166
168{
169 mOwnedSource.reset();
170 mSourceLayer = nullptr;
171
172 bool owner = false;
173 QString error;
174 QgsVectorLayer *vl = uri.vectorLayer( owner, error );
175 if ( owner )
176 {
177 mOwnedSource.reset( vl );
178 mBlockSourceLayerChanges++;
179 mSourceLayerComboBox->setAdditionalLayers( { vl } );
180 mSourceLayerComboBox->setLayer( vl );
181 mBlockSourceLayerChanges--;
182 setSourceLayer( mOwnedSource.get() );
183 }
184 else if ( vl )
185 {
186 mBlockSourceLayerChanges++;
187 mSourceLayerComboBox->setLayer( vl );
188 mBlockSourceLayerChanges--;
189 setSourceLayer( vl );
190 }
191}
192
193void QgsDbImportVectorLayerDialog::setSourceLayer( QgsVectorLayer *layer )
194{
195 mSourceLayer = layer;
196 if ( !mSourceLayer || !mSourceLayer->dataProvider() )
197 return;
198
199 mEditTable->setText( layer->name() );
200
201 const bool isSpatial = mSourceLayer->isSpatial();
202 if ( mEditGeometryColumnName )
203 mEditGeometryColumnName->setEnabled( isSpatial );
204 if ( mCrsSelector )
205 mCrsSelector->setEnabled( isSpatial );
206
207 mExtentGroupBox->setEnabled( isSpatial );
208 if ( !isSpatial )
209 mExtentGroupBox->setChecked( false );
210
211 const bool extentFilterEnabled = mExtentGroupBox->isChecked();
212 mExtentGroupBox->setOriginalExtent( mSourceLayer->extent(), mSourceLayer->crs() );
213 mExtentGroupBox->setOutputExtentFromOriginal();
214 mExtentGroupBox->setChecked( extentFilterEnabled );
215 mExtentGroupBox->setCollapsed( !extentFilterEnabled );
216
217 mFilterExpressionWidget->setLayer( mSourceLayer );
218
219 if ( mEditPrimaryKey )
220 {
221 // set initial geometry column name. We use the source layer's primary key column name if available,
222 // else fallback to a default value given by the connection
223 const QgsAttributeList pkAttributes = mSourceLayer->dataProvider()->pkAttributeIndexes();
224 QString primaryKey = !pkAttributes.isEmpty() ? mSourceLayer->dataProvider()->fields().at( pkAttributes.at( 0 ) ).name() : QString();
225 if ( primaryKey.isEmpty() )
226 {
227 QgsDataSourceUri dsUri( mSourceLayer->source() );
228 primaryKey = dsUri.keyColumn();
229 }
230 if ( primaryKey.isEmpty() )
231 {
232 primaryKey = mConnection->defaultPrimaryKeyColumnName();
233 }
234
235 mEditPrimaryKey->setText( primaryKey );
236 }
237
238 if ( mEditGeometryColumnName )
239 {
240 // set initial geometry column name. We use the source layer's geometry name if available,
241 // else fallback to a default value given by the connection
242 QString geomColumn = mSourceLayer->dataProvider()->geometryColumnName();
243 if ( geomColumn.isEmpty() )
244 {
245 QgsDataSourceUri dsUri( mSourceLayer->source() );
246 geomColumn = dsUri.geometryColumn();
247 }
248 if ( geomColumn.isEmpty() )
249 {
250 geomColumn = mConnection->defaultGeometryColumnName();
251 }
252
253 mEditGeometryColumnName->setText( geomColumn );
254 }
255
256 if ( mCrsSelector )
257 {
258 mCrsSelector->setCrs( mSourceLayer->crs() );
259 }
260
261 if ( mEditComment )
262 {
263 mEditComment->setPlainText( mSourceLayer->metadata().abstract() );
264 }
265
266 mFieldsView->setSourceLayer( mSourceLayer );
267 mFieldsView->setSourceFields( mSourceLayer->fields() );
268 mFieldsView->setDestinationFields( mSourceLayer->fields() );
269
270 const bool selectedFeatures = mSourceLayer->selectedFeatureCount() > 0;
271 mSourceLayerOnlySelected->setEnabled( selectedFeatures );
272}
273
274void QgsDbImportVectorLayerDialog::loadFieldsFromLayer()
275{
276 if ( mSourceLayer )
277 {
278 mFieldsView->setSourceFields( mSourceLayer->fields() );
279 mFieldsView->setDestinationFields( mSourceLayer->fields() );
280 }
281}
282
283void QgsDbImportVectorLayerDialog::addField()
284{
285 const int rowCount = mFieldsView->model()->rowCount();
286 mFieldsView->appendField( QgsField( QStringLiteral( "new_field" ) ), QStringLiteral( "NULL" ) );
287 const QModelIndex index = mFieldsView->model()->index( rowCount, 0 );
288 mFieldsView->selectionModel()->select(
289 index,
290 QItemSelectionModel::SelectionFlags(
291 QItemSelectionModel::Clear | QItemSelectionModel::Select | QItemSelectionModel::Current | QItemSelectionModel::Rows
292 )
293 );
294 mFieldsView->scrollTo( index );
295}
296
298{
299 return mSchemaCombo ? mSchemaCombo->currentSchema() : QString();
300}
301
303{
304 return mEditTable->text();
305}
306
308{
309 return mEditComment ? mEditComment->toPlainText() : QString();
310}
311
313{
314 if ( canvas )
315 {
316 mExtentGroupBox->setCurrentExtent( canvas->mapSettings().visibleExtent(), canvas->mapSettings().destinationCrs() );
317 mExtentGroupBox->setMapCanvas( canvas, false );
318 }
319}
320
321void QgsDbImportVectorLayerDialog::doImport()
322{
323 // TODO -- validate
324
325 accept();
326}
327
328std::unique_ptr<QgsVectorLayerExporterTask> QgsDbImportVectorLayerDialog::createExporterTask( const QVariantMap &extraProviderOptions )
329{
330 if ( !mSourceLayer || !mSourceLayer->dataProvider() )
331 return nullptr;
332
333 QString destinationUri;
334 QVariantMap providerOptions;
335
337 exporterOptions.layerName = mEditTable->text();
338 if ( mSchemaCombo )
339 exporterOptions.schema = mSchemaCombo->currentSchema();
340 exporterOptions.wkbType = mSourceLayer->wkbType();
341 if ( mEditPrimaryKey && !mEditPrimaryKey->text().trimmed().isEmpty() )
342 exporterOptions.primaryKeyColumns << mEditPrimaryKey->text();
343 if ( mEditGeometryColumnName )
344 exporterOptions.geometryColumn = mEditGeometryColumnName->text();
345
346 try
347 {
348 destinationUri = mConnection->createVectorLayerExporterDestinationUri( exporterOptions, providerOptions );
349 }
351 {
352 return nullptr;
353 }
354
355 // options given to us by createVectorLayerExporterDestinationUri above should overwrite generic ones passed to this method
356 QVariantMap allProviderOptions = extraProviderOptions;
357 for ( auto it = providerOptions.constBegin(); it != providerOptions.constEnd(); ++it )
358 {
359 allProviderOptions.insert( it.key(), it.value() );
360 }
361
362 // overwrite?
363 if ( mChkDropTable->isChecked() )
364 {
365 allProviderOptions.insert( QStringLiteral( "overwrite" ), true );
366 }
367
368 // This flag tells to the provider that field types do not need conversion -- we have already
369 // explicitly set all fields to provider-specific field types and we do not need to treat
370 // them as generic/different provider fields
371 allProviderOptions.insert( QStringLiteral( "skipConvertFields" ), true );
372
374 if ( mCrsSelector )
375 {
376 exportOptions.setDestinationCrs( mCrsSelector->crs() );
377 }
378 exportOptions.setTransformContext( mSourceLayer->transformContext() );
379 if ( !mFilterExpressionWidget->expression().isEmpty() )
380 {
381 exportOptions.setFilterExpression( mFilterExpressionWidget->expression() );
383 }
384
385 if ( mExtentGroupBox->isEnabled() && mExtentGroupBox->isChecked() )
386 {
387 exportOptions.setExtent( QgsReferencedRectangle( mExtentGroupBox->outputExtent(), mExtentGroupBox->outputCrs() ) );
388 }
389
390 if ( mSourceLayerOnlySelected->isEnabled() && mSourceLayerOnlySelected->isChecked() )
391 {
392 exportOptions.setSelectedOnly( true );
393 }
394
395 const QList<QgsFieldMappingModel::Field> fieldMapping = mFieldsView->mapping();
396 QList<QgsVectorLayerExporter::OutputField> outputFields;
397 outputFields.reserve( fieldMapping.size() );
398 for ( const QgsFieldMappingModel::Field &field : fieldMapping )
399 {
400 outputFields.append( QgsVectorLayerExporter::OutputField( field.field, field.expression ) );
401 }
402 exportOptions.setOutputFields( outputFields );
403
404 return std::make_unique<QgsVectorLayerExporterTask>( mSourceLayer->clone(), destinationUri, mConnection->providerKey(), exportOptions, allProviderOptions, true );
405}
406
413
414void QgsDbImportVectorLayerDialog::sourceLayerComboChanged()
415{
416 if ( mBlockSourceLayerChanges )
417 return;
418
419 if ( mSourceLayerComboBox->currentLayer() == mSourceLayer )
420 return;
421
422 setSourceLayer( qobject_cast< QgsVectorLayer * >( mSourceLayerComboBox->currentLayer() ) );
423}
@ SetPrimaryKeyName
Can set the name of the primary key column.
Definition qgis.h:5409
@ SetGeometryColumnName
Can set the name of the geometry column.
Definition qgis.h:5408
@ SetTableComment
Can set comments for tables via setTableComment().
Definition qgis.h:5393
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 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.
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.
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:221
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:84
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:28
#define QgsDebugError(str)
Definition qgslogger.h:57
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.