QGIS API Documentation 3.29.0-Master (8c80f25a4f)
qgsnewdatabasetablenamewidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsnewdatabasetablenamewidget.cpp - QgsNewDatabaseTableNameWidget
3
4 ---------------------
5 begin : 9.3.2020
6 copyright : (C) 2020 by Alessandro Pasotti
7 email : elpaso at itopen dot it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include <QTreeWidgetItemIterator>
18
20#include "qgsapplication.h"
22#include "qgsdataitemprovider.h"
23#include "qgsproviderregistry.h"
24#include "qgsprovidermetadata.h"
25#include "qgssettings.h"
26#include "qgsguiutils.h"
29
30#include <QRegularExpression>
31#include <QDialogButtonBox>
32#include <QPushButton>
33
34// List of data item provider keys that are filesystem based
35QStringList QgsNewDatabaseTableNameWidget::FILESYSTEM_BASED_DATAITEM_PROVIDERS { QStringLiteral( "GPKG" ), QStringLiteral( "spatialite" ) };
36
38 QgsBrowserGuiModel *browserModel,
39 const QStringList &providersFilter,
40 QWidget *parent )
41 : QgsPanelWidget( parent )
42{
43
44 // Initialize the browser
45 if ( ! browserModel )
46 {
47 mBrowserModel = new QgsBrowserGuiModel( this );
48 mBrowserModel->initialize();
49 }
50 else
51 {
52 mBrowserModel = browserModel;
53 mBrowserModel->initialize();
54 }
55
56 setupUi( this );
57
58 mOkButton->hide();
59 mOkButton->setEnabled( false );
60
61 QStringList shownDataItemProvidersFilter;
62
63 const auto providerList { QgsApplication::dataItemProviderRegistry()->providers() };
64 for ( const auto &provider : providerList )
65 {
66 if ( provider->dataProviderKey().isEmpty() )
67 {
68 continue;
69 }
70 if ( ! QgsProviderRegistry::instance()->providerMetadata( provider->dataProviderKey() ) )
71 {
72 continue;
73 }
74 if ( provider->capabilities() & QgsDataProvider::DataCapability::Database )
75 {
76 if ( providersFilter.isEmpty() || providersFilter.contains( provider->dataProviderKey() ) )
77 {
78 mShownProviders.insert( provider->dataProviderKey() );
79 shownDataItemProvidersFilter.push_back( provider->name() );
80 }
81 }
82 }
83
84 mBrowserToolbar->setIconSize( QgsGuiUtils::iconSize( true ) );
85
86 mBrowserProxyModel.setBrowserModel( mBrowserModel );
87 // If a filter was specified but the data provider could not be found
88 // this makes sure no providers are shown instead of ALL of them
89 if ( ! providersFilter.isEmpty() && shownDataItemProvidersFilter.isEmpty() )
90 {
91 shownDataItemProvidersFilter = providersFilter;
92 }
93 mBrowserProxyModel.setShownDataItemProviderKeyFilter( shownDataItemProvidersFilter );
94 mBrowserProxyModel.setShowLayers( false );
95 mBrowserTreeView->setHeaderHidden( true );
96 mBrowserTreeView->setModel( &mBrowserProxyModel );
97 mBrowserTreeView->setBrowserModel( mBrowserModel );
98
99 // Connections
100 connect( mNewTableName, &QLineEdit::textChanged, this, [ = ]
101 {
102 mTableName = mNewTableName->text();
103 emit tableNameChanged( mTableName );
104 updateUri();
105 validate();
106 } );
107
108 connect( mActionRefresh, &QAction::triggered, this, [ = ]
109 {
110 refreshModel( QModelIndex() );
111 } );
112
113 connect( mBrowserTreeView, &QgsBrowserTreeView::clicked, this, [ = ]( const QModelIndex & index )
114 {
115 if ( index.isValid() )
116 {
117 if ( const QgsDataItem *dataItem = mBrowserProxyModel.dataItem( index ) )
118 {
119 if ( const QgsDataCollectionItem *collectionItem = qobject_cast<const QgsDataCollectionItem *>( dataItem ) )
120 {
121 const QString providerKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( dataItem->providerKey() ) };
122 bool validationRequired { false };
123 const QString oldSchema { mSchemaName };
124
125 if ( mDataProviderKey != providerKey )
126 {
127 mSchemaName.clear();
128 mDataProviderKey = providerKey;
129 emit providerKeyChanged( providerKey );
130 validationRequired = true;
131 }
132
133 if ( collectionItem->layerCollection( ) )
134 {
135 mIsFilePath = FILESYSTEM_BASED_DATAITEM_PROVIDERS.contains( collectionItem->providerKey() );
136 // Data items for filesystem based items are in the form gpkg://path/to/file.gpkg
137 mSchemaName = mIsFilePath ? collectionItem->path().remove( QRegularExpression( QStringLiteral( "^[A-z]+:/" ) ) ) : collectionItem->name(); // it may be cleared
138 mConnectionName = mIsFilePath ? collectionItem->name() : collectionItem->parent()->name();
139 if ( oldSchema != mSchemaName )
140 {
141 emit schemaNameChanged( mSchemaName );
142 // Store last viewed item
143 QgsSettings().setValue( QStringLiteral( "newDatabaseTableNameWidgetLastSelectedItem" ),
144 mBrowserProxyModel.data( index, QgsBrowserGuiModel::PathRole ).toString(), QgsSettings::Section::Gui );
145 validationRequired = true;
146 }
147 }
148
149 if ( validationRequired )
150 {
151 updateUri();
152 validate();
153 }
154 }
155 }
156 }
157 } );
158
159 connect( this, &QgsNewDatabaseTableNameWidget::validationChanged, mOkButton, &QWidget::setEnabled );
160 connect( mOkButton, &QPushButton::clicked, this, &QgsNewDatabaseTableNameWidget::accepted );
161
162 validate();
163}
164
166{
167 mOkButton->setVisible( visible );
168}
169
170void QgsNewDatabaseTableNameWidget::refreshModel( const QModelIndex &index )
171{
172
173 QgsDataItem *item = mBrowserModel->dataItem( index );
174
175 if ( item && ( item->capabilities2() & Qgis::BrowserItemCapability::Fertile ) )
176 {
177 mBrowserModel->refresh( index );
178 }
179
180 for ( int i = 0; i < mBrowserModel->rowCount( index ); i++ )
181 {
182 const QModelIndex idx = mBrowserModel->index( i, 0, index );
183 const QModelIndex proxyIdx = mBrowserProxyModel.mapFromSource( idx );
184 QgsDataItem *child = mBrowserModel->dataItem( idx );
185
186 // Check also expanded descendants so that the whole expanded path does not get collapsed if one item is collapsed.
187 // Fast items (usually root items) are refreshed so that when collapsed, it is obvious they are if empty (no expand symbol).
188 if ( mBrowserTreeView->isExpanded( proxyIdx ) || mBrowserTreeView->hasExpandedDescendant( proxyIdx ) || ( child && child->capabilities2() & Qgis::BrowserItemCapability::Fast ) )
189 {
190 refreshModel( idx );
191 }
192 else
193 {
194 if ( child && ( child->capabilities2() & Qgis::BrowserItemCapability::Fertile ) )
195 {
196 child->depopulate();
197 }
198 }
199 }
200}
201
202void QgsNewDatabaseTableNameWidget::updateUri()
203{
204 const QString oldUri { mUri };
205 QgsProviderMetadata *dataProviderMetadata { QgsProviderRegistry::instance()->providerMetadata( mDataProviderKey ) };
206 if ( dataProviderMetadata )
207 {
208 QgsAbstractProviderConnection *conn { dataProviderMetadata->findConnection( mConnectionName ) };
209 if ( conn )
210 {
211 QVariantMap uriParts = dataProviderMetadata->decodeUri( conn->uri() );
212 uriParts[ QStringLiteral( "layerName" ) ] = mTableName;
213 uriParts[ QStringLiteral( "schema" ) ] = mSchemaName;
214 uriParts[ QStringLiteral( "table" ) ] = mTableName;
215 if ( mIsFilePath )
216 {
217 uriParts[ QStringLiteral( "dbname" ) ] = mSchemaName;
218 }
219 mUri = dataProviderMetadata->encodeUri( uriParts );
220 }
221 else
222 {
223 mUri = QString();
224 }
225 }
226 else
227 {
228 mUri = QString();
229 }
230
231 if ( mUri != oldUri )
232 {
233 emit uriChanged( mUri );
234 }
235}
236
238{
239 return mSchemaName;
240}
241
243{
244 return mUri;
245}
246
248{
249 return mTableName;
250}
251
253{
254 return mDataProviderKey;
255}
256
257void QgsNewDatabaseTableNameWidget::validate()
258{
259 const bool wasValid { mIsValid };
260 // Check table uniqueness
261 mIsValid = ! mDataProviderKey.isEmpty() &&
262 mShownProviders.contains( mDataProviderKey ) &&
263 ! mSchemaName.isEmpty() &&
264 ! mTableName.isEmpty() &&
265 ! tableNames( ).contains( mTableName );
266
267 mValidationError.clear();
268
269 // Whether to show it red
270 bool isError { false };
271
272 if ( ! mIsValid )
273 {
274 if ( mTableName.isEmpty() && mSchemaName.isEmpty() )
275 {
276 mValidationError = tr( "Select a database schema and enter a unique name for the new table" );
277 }
278 else if ( ! mTableName.isEmpty() &&
279 ! mSchemaName.isEmpty() &&
280 tableNames( ).contains( mTableName ) )
281 {
282 isError = true;
283 mValidationError = tr( "A table named '%1' already exists" ).arg( mTableName );
284 }
285 else if ( mSchemaName.isEmpty() )
286 {
287 mValidationError = tr( "Select a database schema" );
288 }
289 else if ( mTableName.isEmpty() )
290 {
291 mValidationError = tr( "Enter a unique name for the new table" );
292 }
293 else if ( tableNames( ).contains( mTableName ) )
294 {
295 mValidationError = tr( "A table named '%1' already exists" ).arg( mTableName );
296 }
297 else
298 {
299 mValidationError = tr( "Select a database schema and enter a unique name for the new table" );
300 }
301 }
302
303 mValidationResults->setStyleSheet( isError ?
304 QStringLiteral( "* { color: red; }" ) :
305 QString() );
306
307 mValidationResults->setText( mValidationError );
308 mValidationResults->setVisible( ! mIsValid );
309 if ( wasValid != mIsValid )
310 {
311 emit validationChanged( mIsValid );
312 }
313}
314
315QStringList QgsNewDatabaseTableNameWidget::tableNames()
316{
317 QStringList tableNames;
318 const QModelIndex index { mBrowserTreeView->currentIndex() };
319 if ( index.isValid() )
320 {
321 QgsDataItem *dataItem { mBrowserProxyModel.dataItem( index ) };
322 if ( dataItem )
323 {
324 const QString dataProviderKey { QgsApplication::dataItemProviderRegistry()->dataProviderKey( dataItem->providerKey() ) };
325 if ( ! dataProviderKey.isEmpty() )
326 {
328 if ( metadata )
329 {
330 QgsDataItem *parentDataItem { mIsFilePath ? dataItem : dataItem->parent() };
331 if ( parentDataItem )
332 {
333 QgsAbstractProviderConnection *conn { metadata->findConnection( parentDataItem->name() ) };
334 if ( conn )
335 {
336 const QString cacheKey { conn->uri() + dataItem->name() };
337 if ( mTableNamesCache.contains( cacheKey ) )
338 {
339 tableNames = mTableNamesCache.value( cacheKey );
340 }
341 else if ( conn && static_cast<QgsAbstractDatabaseProviderConnection *>( conn ) )
342 {
343 const auto tables { static_cast<QgsAbstractDatabaseProviderConnection *>( conn )->tables( dataItem->name() ) };
344 for ( const auto &tp : tables )
345 {
346 tableNames.push_back( tp.tableName() );
347 }
348 mTableNamesCache[ cacheKey ] = tableNames;
349 }
350 }
351 }
352 }
353 }
354 }
355 }
356 return tableNames;
357}
358
360{
361 return mIsValid;
362}
363
365{
366 return mValidationError;
367}
368
370{
371 QWidget::showEvent( e );
372 const QString lastSelectedPath( QgsSettings().value( QStringLiteral( "newDatabaseTableNameWidgetLastSelectedItem" ),
373 QString(), QgsSettings::Section::Gui ).toString() );
374 if ( ! lastSelectedPath.isEmpty() )
375 {
376 const QModelIndexList items = mBrowserProxyModel.match(
377 mBrowserProxyModel.index( 0, 0 ),
379 QVariant::fromValue( lastSelectedPath ),
380 1,
381 Qt::MatchRecursive );
382 if ( items.count( ) > 0 )
383 {
384 const QModelIndex expandIndex = items.at( 0 );
385 if ( expandIndex.isValid() )
386 {
387 mBrowserTreeView->scrollTo( expandIndex, QgsBrowserTreeView::ScrollHint::PositionAtTop );
388 mBrowserTreeView->expand( expandIndex );
389 }
390 }
391 }
392}
393
394//
395// QgsNewDatabaseTableNameDialog
396//
397QgsNewDatabaseTableNameDialog::QgsNewDatabaseTableNameDialog( QgsBrowserGuiModel *browserModel, const QStringList &providersFilter, QWidget *parent )
398 : QDialog( parent )
399{
400 mWidget = new QgsNewDatabaseTableNameWidget( browserModel, providersFilter );
401 QVBoxLayout *vl = new QVBoxLayout();
402 vl->addWidget( mWidget, 1 );
403 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
404 connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
405 connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
406 buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
407 connect( mWidget, &QgsNewDatabaseTableNameWidget::validationChanged, buttonBox->button( QDialogButtonBox::Ok ), &QWidget::setEnabled );
408 vl->addWidget( buttonBox );
409 setLayout( vl );
410}
411
413{
414 return mWidget->schema();
415}
416
418{
419 return mWidget->uri();
420}
421
423{
424 return mWidget->table();
425}
426
428{
429 return mWidget->dataProviderKey();
430}
431
433{
434 return mWidget->isValid();
435}
436
438{
439 return mWidget->validationError();
440}
@ Fertile
Can create children. Even items without this capability may have children, but cannot create them,...
@ Fast
CreateChildren() is fast enough to be run in main thread when refreshing items, most root items (wms,...
The QgsAbstractDatabaseProviderConnection class provides common functionality for DB based connection...
The QgsAbstractProviderConnection provides an interface for data provider connections.
QString uri() const
Returns the connection data source URI string representation.
static QgsDataItemProviderRegistry * dataItemProviderRegistry()
Returns the application's data item provider registry, which keeps a list of data item providers that...
A model for showing available data sources and other items in a structured tree.
QgsDataItem * dataItem(const QModelIndex &idx) const
Returns the data item at the specified index, or nullptr if no item exists at the index.
void refresh(const QString &path)
Refresh item specified by path.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void initialize()
Delayed initialization, needed because the provider registry must be already populated.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
@ PathRole
Item path used to access path in the tree, see QgsDataItem::mPath.
void setShowLayers(bool showLayers)
Sets show layers to showLayers.
void setShownDataItemProviderKeyFilter(const QStringList &shownItemsFilter)
Sets a filter to show data items based on QgsDataItem::providerKey() associated with the item.
QgsDataItem * dataItem(const QModelIndex &index) const
Returns the data item at the specified proxy index, or nullptr if no item exists at the index.
void setBrowserModel(QgsBrowserModel *model)
Sets the underlying browser model.
QList< QgsDataItemProvider * > providers() const
Returns the list of available providers.
QString dataProviderKey(const QString &dataItemProviderName)
Returns the (possibly blank) data provider key for a given data item provider name.
Base class for all items in the model.
Definition: qgsdataitem.h:46
virtual Qgis::BrowserItemCapabilities capabilities2() const
Returns the capabilities for the data item.
Definition: qgsdataitem.h:303
virtual void depopulate()
Remove children recursively and set as not populated. This is used when refreshing collapsed items.
QString uri() const
Returns the (possibly blank) string representation of the new table data source URI.
QString dataProviderKey() const
Returns the currently selected data item provider key.
QString schema() const
Returns the currently selected schema or file path (in case of filesystem-based DBs like spatialite o...
QString validationError() const
Returns the validation error or an empty string is the widget status is valid.
QgsNewDatabaseTableNameDialog(QgsBrowserGuiModel *browserModel=nullptr, const QStringList &providersFilter=QStringList(), QWidget *parent=nullptr)
Constructs a new QgsNewDatabaseTableNameDialog.
bool isValid() const
Returns true if the widget contains a valid new table name.
QString table() const
Returns the current name of the new table.
The QgsNewDatabaseTableNameWidget class embeds the browser view to select a DB schema and a new table...
void uriChanged(const QString &uri)
This signal is emitted when the URI of the new table changes, whether or not it is a valid one.
bool isValid() const
Returns true if the widget contains a valid new table name.
QString table() const
Returns the current name of the new table.
void showEvent(QShowEvent *e) override
Scroll to last selected index and expand it's children.
QString dataProviderKey() const
Returns the currently selected data item provider key.
void setAcceptButtonVisible(bool visible)
Sets whether the optional "Ok"/accept button should be visible.
QString validationError() const
Returns the validation error or an empty string is the widget status is valid.
QString schema() const
Returns the currently selected schema or file path (in case of filesystem-based DBs like spatialite o...
QgsNewDatabaseTableNameWidget(QgsBrowserGuiModel *browserModel=nullptr, const QStringList &providersFilter=QStringList(), QWidget *parent=nullptr)
Constructs a new QgsNewDatabaseTableNameWidget.
void tableNameChanged(const QString &tableName)
This signal is emitted when the user enters a table name.
void accepted()
Emitted when the OK/accept button is clicked.
void validationChanged(bool isValid)
This signal is emitted whenever the validation status of the widget changes.
QString uri() const
Returns the (possibly blank) string representation of the new table data source URI.
Base class for any widget that can be shown as a inline panel.
Holds data provider key, description, and associated shared library file or function pointer informat...
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.