QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 #include "qgsdatacollectionitem.h"
29 
30 #include <QRegularExpression>
31 #include <QDialogButtonBox>
32 #include <QPushButton>
33 
34 // List of data item provider keys that are filesystem based
35 QStringList 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 
170 void 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  QModelIndex idx = mBrowserModel->index( i, 0, index );
183  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 
202 void 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 
257 void 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 
315 QStringList QgsNewDatabaseTableNameWidget::tableNames()
316 {
317  QStringList tableNames;
318  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  QString lastSelectedPath( QgsSettings().value( QStringLiteral( "newDatabaseTableNameWidgetLastSelectedItem" ),
373  QString(), QgsSettings::Section::Gui ).toString() );
374  if ( ! lastSelectedPath.isEmpty() )
375  {
376  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  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 //
397 QgsNewDatabaseTableNameDialog::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:295
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.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.