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