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