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