QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsprovidersublayersdialog.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprovidersublayersdialog.h
3 ---------------------
4 begin : July 2021
5 copyright : (C) 2021 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#include "qgssettings.h"
19#include "qgsproviderutils.h"
21#include "qgsapplication.h"
22#include "qgstaskmanager.h"
23#include "qgsnative.h"
24#include "qgsgui.h"
25
26#include <QPushButton>
27#include <QFileInfo>
28#include <QDir>
29#include <QDesktopServices>
30#include <QUrl>
31
33 : QgsProviderSublayerModel( parent )
34{
35
36}
37
38QVariant QgsProviderSublayerDialogModel::data( const QModelIndex &index, int role ) const
39{
40 if ( !index.isValid() )
41 return QVariant();
42
43 QgsProviderSublayerModelNode *node = index2node( index );
44 if ( !node )
45 return QVariant();
46
47 if ( QgsProviderSublayerModelSublayerNode *sublayerNode = dynamic_cast<QgsProviderSublayerModelSublayerNode *>( node ) )
48 {
49 const QgsProviderSublayerDetails details = sublayerNode->sublayer();
50
51 if ( details.type() == Qgis::LayerType::Vector && details.wkbType() == Qgis::WkbType::Unknown && !mGeometryTypesResolved )
52 {
53 switch ( role )
54 {
55 case Qt::DisplayRole:
56 case Qt::ToolTipRole:
57 {
58 if ( index.column() == static_cast< int >( Column::Description ) )
59 return tr( "Scanning…" );
60 break;
61 }
62
63 case Qt::FontRole:
64 {
65 QFont f = QgsProviderSublayerModel::data( index, role ).value< QFont >();
66 f.setItalic( true );
67 return f;
68 }
69 }
70 }
71 else if ( details.flags() & Qgis::SublayerFlag::SystemTable )
72 {
73 switch ( role )
74 {
75 case Qt::FontRole:
76 {
77 QFont f = QgsProviderSublayerModel::data( index, role ).value< QFont >();
78 f.setItalic( true );
79 return f;
80 }
81 }
82 }
83 }
84
86}
87
88Qt::ItemFlags QgsProviderSublayerDialogModel::flags( const QModelIndex &index ) const
89{
90 if ( !index.isValid() )
92
93 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
95
96 if ( index.row() < mSublayers.count() )
97 {
98 const QgsProviderSublayerDetails details = mSublayers.at( index.row() );
99
100 if ( details.type() == Qgis::LayerType::Vector && details.wkbType() == Qgis::WkbType::Unknown && !mGeometryTypesResolved )
101 {
102 // unknown geometry item can't be selected
103 return Qt::ItemFlags();
104 }
105 }
107}
108
110{
111 mGeometryTypesResolved = resolved;
112 emit dataChanged( index( 0, 0 ), index( rowCount( QModelIndex() ), columnCount() ) );
113}
114
115QgsProviderSublayersDialog::QgsProviderSublayersDialog( const QString &uri, const QString &providerKey, const QString &filePathIn, const QList<QgsProviderSublayerDetails> initialDetails, const QList<Qgis::LayerType> &acceptableTypes, QWidget *parent, Qt::WindowFlags fl )
116 : QDialog( parent, fl )
117{
118 setupUi( this );
120
121 const QFileInfo fileInfo( filePathIn );
122 const QString filePath = ( fileInfo.isFile() || fileInfo.isDir() ) && fileInfo.exists() ? filePathIn : QString();
123 const QString fileName = !filePath.isEmpty() ? QgsProviderUtils::suggestLayerNameFromFilePath( filePath ) : QString();
124
125 if ( !fileName.isEmpty() )
126 {
127 setGroupName( fileName );
128 }
129
130 setWindowTitle( fileName.isEmpty() ? tr( "Select Items to Add" ) : QStringLiteral( "%1 | %2" ).arg( tr( "Select Items to Add" ), fileName ) );
131
132 mLblFilePath->setText( QStringLiteral( "<a href=\"%1\">%2</a>" )
133 .arg( QUrl::fromLocalFile( filePath ).toString(), QDir::toNativeSeparators( QFileInfo( filePath ).canonicalFilePath() ) ) );
134 mLblFilePath->setVisible( !filePath.isEmpty() );
135 mLblFilePath->setWordWrap( true );
136 mLblFilePath->setTextInteractionFlags( Qt::TextBrowserInteraction );
137 connect( mLblFilePath, &QLabel::linkActivated, this, [ = ]( const QString & link )
138 {
139 const QUrl url( link );
140 const QFileInfo file( url.toLocalFile() );
141 if ( file.exists() && !file.isDir() )
142 QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
143 else
144 QDesktopServices::openUrl( url );
145 } );
146
147 mModel = new QgsProviderSublayerDialogModel( this );
148 mModel->setSublayerDetails( initialDetails );
149 mProxyModel = new QgsProviderSublayerProxyModel( this );
150 mProxyModel->setSourceModel( mModel );
151 mLayersTree->setModel( mProxyModel );
152
153 mLayersTree->expandAll();
154
155 const QgsSettings settings;
156 const bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), false ).toBool();
157 mCbxAddToGroup->setChecked( addToGroup );
158 mCbxAddToGroup->setVisible( !fileName.isEmpty() );
159
160 // resize columns
161 const QByteArray ba = settings.value( "/Windows/SubLayers/headerState" ).toByteArray();
162 if ( !ba.isNull() )
163 {
164 mLayersTree->header()->restoreState( ba );
165 }
166 else
167 {
168 for ( int i = 0; i < mModel->columnCount(); i++ )
169 mLayersTree->resizeColumnToContents( i );
170 mLayersTree->setColumnWidth( 1, mLayersTree->columnWidth( 1 ) + 10 );
171 }
172
174 {
175 // initial details are incomplete, so fire up a task in the background to fully populate the model...
176 mTask = new QgsProviderSublayerTask( uri, providerKey, true );
177 connect( mTask.data(), &QgsProviderSublayerTask::taskCompleted, this, [ = ]
178 {
179 QList< QgsProviderSublayerDetails > res = mTask->results();
180 res.erase( std::remove_if( res.begin(), res.end(), [acceptableTypes]( const QgsProviderSublayerDetails & sublayer )
181 {
182 return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
183 } ), res.end() );
184
185 mModel->setSublayerDetails( res );
186 mModel->setGeometryTypesResolved( true );
187 mTask = nullptr;
188 mLayersTree->expandAll();
189 selectAll();
190 } );
191 QgsApplication::taskManager()->addTask( mTask.data() );
192 }
193
194 connect( mBtnSelectAll, &QAbstractButton::pressed, this, &QgsProviderSublayersDialog::selectAll );
195 connect( mBtnDeselectAll, &QAbstractButton::pressed, this, [ = ] { mLayersTree->selectionModel()->clear(); } );
196 connect( mLayersTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsProviderSublayersDialog::treeSelectionChanged );
197 connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, mProxyModel, &QgsProviderSublayerProxyModel::setFilterString );
198 connect( mCheckShowSystem, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeSystemTables );
199 connect( mCheckShowEmpty, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeEmptyLayers );
200 connect( mLayersTree, &QTreeView::doubleClicked, this, [ = ]( const QModelIndex & index )
201 {
202 const QModelIndex left = mLayersTree->model()->index( index.row(), 0, index.parent() );
203 if ( !( left.flags() & Qt::ItemIsSelectable ) )
204 return;
205
206 mLayersTree->selectionModel()->select( QItemSelection( left,
207 mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ),
208 QItemSelectionModel::ClearAndSelect );
209 emit layersAdded( selectedLayers() );
210 accept();
211 } );
212 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
213 connect( mButtonBox, &QDialogButtonBox::accepted, this, [ = ]
214 {
215 emit layersAdded( selectedLayers() );
216 accept();
217 } );
218 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
219 mButtonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add Layers" ) );
220
221 selectAll();
222}
223
224void QgsProviderSublayersDialog::setNonLayerItems( const QList<QgsProviderSublayerModel::NonLayerItem> &items )
225{
226 for ( const QgsProviderSublayerModel::NonLayerItem &item : items )
227 {
228 mModel->addNonLayerItem( item );
229 }
230}
231
233{
234 QgsSettings settings;
235 settings.setValue( "/Windows/SubLayers/headerState", mLayersTree->header()->saveState() );
236 settings.setValue( QStringLiteral( "/qgis/openSublayersInGroup" ), mCbxAddToGroup->isChecked() );
237
238 if ( mTask )
239 mTask->cancel();
240}
241
242QList<QgsProviderSublayerDetails> QgsProviderSublayersDialog::selectedLayers() const
243{
244 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
245 QList< QgsProviderSublayerDetails > selectedSublayers;
246 for ( const QModelIndex &index : selection )
247 {
248 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
249 if ( !mModel->data( sourceIndex, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
250 {
251 selectedSublayers << mModel->indexToSublayer( sourceIndex );
252 }
253 }
254 return selectedSublayers;
255}
256
257QList<QgsProviderSublayerModel::NonLayerItem> QgsProviderSublayersDialog::selectedNonLayerItems() const
258{
259 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
260 QList< QgsProviderSublayerModel::NonLayerItem > selectedItems;
261 for ( const QModelIndex &index : selection )
262 {
263 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
264 if ( mModel->data( sourceIndex, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
265 {
266 selectedItems << mModel->indexToNonLayerItem( sourceIndex );
267 }
268 }
269 return selectedItems;
270}
271
272void QgsProviderSublayersDialog::setGroupName( const QString &groupNameIn )
273{
274 mGroupName = groupNameIn;
275 const QgsSettings settings;
276 if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
277 {
278 mGroupName = QgsMapLayer::formatLayerName( mGroupName );
279 }
280
281 mCbxAddToGroup->setVisible( !mGroupName.isEmpty() );
282}
283
285{
286 if ( !mCbxAddToGroup->isChecked() )
287 return QString();
288 return mGroupName;
289}
290
291void QgsProviderSublayersDialog::treeSelectionChanged( const QItemSelection &selected, const QItemSelection & )
292{
293 if ( mBlockSelectionChanges )
294 return;
295
296 mBlockSelectionChanges = true;
297 bool selectedANonLayerItem = false;
298 QModelIndex firstSelectedNonLayerItem;
299 bool selectedALayerItem = false;
300 for ( const QModelIndex &index : selected.indexes() )
301 {
302 if ( index.column() != 0 )
303 continue;
304
305 if ( mProxyModel->data( index, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
306 {
307 if ( !selectedANonLayerItem )
308 {
309 selectedANonLayerItem = true;
310 firstSelectedNonLayerItem = index;
311 }
312 else
313 {
314 // only one non-layer item can be selected
315 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ),
316 mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ),
317 QItemSelectionModel::Deselect );
318 }
319 }
320 else
321 {
322 selectedALayerItem = true;
323 }
324 }
325
326 for ( int row = 0; row < mProxyModel->rowCount(); ++row )
327 {
328 const QModelIndex index = mProxyModel->index( row, 0 );
329 if ( mProxyModel->data( index, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
330 {
331 if ( ( selectedANonLayerItem && index != firstSelectedNonLayerItem ) || selectedALayerItem )
332 {
333 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ),
334 mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ),
335 QItemSelectionModel::Deselect );
336 }
337 }
338 else
339 {
340 if ( selectedANonLayerItem )
341 {
342 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ),
343 mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ),
344 QItemSelectionModel::Deselect );
345 }
346 }
347 }
348 mBlockSelectionChanges = false;
349
350 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( !mLayersTree->selectionModel()->selectedRows().empty() );
351
352 mCbxAddToGroup->setEnabled( !selectedANonLayerItem );
353 mButtonBox->button( QDialogButtonBox::Ok )->setText( selectedANonLayerItem ? tr( "Open" ) : tr( "Add Layers" ) );
354}
355
356void QgsProviderSublayersDialog::selectAll()
357{
358 mLayersTree->selectionModel()->clear();
359
360 std::function< void( const QModelIndex & ) > selectAllInParent;
361
362 selectAllInParent = [this, &selectAllInParent]( const QModelIndex & parent )
363 {
364 for ( int row = 0; row < mProxyModel->rowCount( parent ); ++row )
365 {
366 const QModelIndex index = mProxyModel->index( row, 0, parent );
367 if ( !mProxyModel->data( index, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
368 {
369 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ),
370 mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ),
371 QItemSelectionModel::Select );
372 }
373 selectAllInParent( index );
374 }
375 };
376 selectAllInParent( QModelIndex() );
377
378 mButtonBox->button( QDialogButtonBox::Ok )->setFocus();
379}
@ SystemTable
Sublayer is a system or internal table, which should be hidden by default.
@ Vector
Vector layer.
@ Unknown
Unknown.
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
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:194
static QgsNative * nativePlatformInterface()
Returns the global native interface, which offers abstraction to the host OS's underlying public inte...
Definition: qgsgui.cpp:79
static QString formatLayerName(const QString &name)
A convenience function to capitalize and format a layer name.
Contains details about a sub layer available from a dataset.
Qgis::LayerType type() const
Returns the layer type.
Qgis::WkbType wkbType() const
Returns the layer's WKB type, or QgsWkbTypes::Unknown if the WKB type is not application or unknown.
Qgis::SublayerFlags flags() const
Returns the layer's flags, which indicate properties of the layer.
A model for representing the sublayers present in a URI for the QgsProviderSublayersDialog.
QVariant data(const QModelIndex &index, int role) const override
QgsProviderSublayerDialogModel(QObject *parent=nullptr)
Constructor.
void setGeometryTypesResolved(bool resolved)
Sets whether geometry types are resolved.
Qt::ItemFlags flags(const QModelIndex &index) const override
Contains details for a non-sublayer item to include in a QgsProviderSublayerModel.
A model for representing the sublayers present in a URI.
QList< QgsProviderSublayerDetails > mSublayers
Sublayer list.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role) const override
QgsProviderSublayerDetails indexToSublayer(const QModelIndex &index) const
Returns the sublayer corresponding to the given index.
@ IsNonLayerItem
true if item is a non-sublayer item (e.g. an embedded project)
QgsProviderSublayerModel::NonLayerItem indexToNonLayerItem(const QModelIndex &index) const
Returns the non layer item corresponding to the given index.
void addNonLayerItem(const QgsProviderSublayerModel::NonLayerItem &item)
Adds a non-layer item (e.g.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
int rowCount(const QModelIndex &parent) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
@ Description
Layer description.
A QSortFilterProxyModel for filtering and sorting a QgsProviderSublayerModel.
void setIncludeSystemTables(bool include)
Sets whether system and internal tables will be shown in the model.
void setFilterString(const QString &filter)
Sets the filter string used for filtering items in the model.
void setIncludeEmptyLayers(bool include)
Sets whether empty tables will be shown in the model.
A QgsTask which retrieves sublayer details for a URI.
void setGroupName(const QString &groupNameIn)
Sets an appropriate name for the layer group.
QString groupName() const
Returns an appropriate name for the layer group.
void setNonLayerItems(const QList< QgsProviderSublayerModel::NonLayerItem > &items)
Set list of non-layer items (e.g.
QList< QgsProviderSublayerModel::NonLayerItem > selectedNonLayerItems() const
Returns the list of selected non-layer items (e.g.
QList< QgsProviderSublayerDetails > selectedLayers() const
Returns the list of selected layers.
QgsProviderSublayersDialog(const QString &uri, const QString &providerKey, const QString &filePath, const QList< QgsProviderSublayerDetails > initialDetails=QList< QgsProviderSublayerDetails >(), const QList< Qgis::LayerType > &acceptableTypes=QList< Qgis::LayerType >(), QWidget *parent SIP_TRANSFERTHIS=nullptr, Qt::WindowFlags fl=Qt::WindowFlags())
Constructor.
static bool sublayerDetailsAreIncomplete(const QList< QgsProviderSublayerDetails > &details, QgsProviderUtils::SublayerCompletenessFlags flags=QgsProviderUtils::SublayerCompletenessFlags())
Returns true if the sublayer details are incomplete, and require a more in-depth scan.
static QString suggestLayerNameFromFilePath(const QString &path)
Suggests a suitable layer name given only a file path.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskCompleted()
Will be emitted by task to indicate its successful completion.