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