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