QGIS API Documentation 3.41.0-Master (cea29feecf2)
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
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 const QUrl url( link );
139 const QFileInfo file( url.toLocalFile() );
140 if ( file.exists() && !file.isDir() )
141 QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
142 else
143 QDesktopServices::openUrl( url );
144 } );
145
146 mModel = new QgsProviderSublayerDialogModel( this );
147 mModel->setSublayerDetails( initialDetails );
148 mProxyModel = new QgsProviderSublayerProxyModel( this );
149 mProxyModel->setSourceModel( mModel );
150 mLayersTree->setModel( mProxyModel );
151
152 mLayersTree->expandAll();
153
154 const QgsSettings settings;
155 const bool addToGroup = settings.value( QStringLiteral( "/qgis/openSublayersInGroup" ), false ).toBool();
156 mCbxAddToGroup->setChecked( addToGroup );
157 mCbxAddToGroup->setVisible( !fileName.isEmpty() );
158
159 // resize columns
160 const QByteArray ba = settings.value( "/Windows/SubLayers/headerState" ).toByteArray();
161 if ( !ba.isNull() )
162 {
163 mLayersTree->header()->restoreState( ba );
164 }
165 else
166 {
167 for ( int i = 0; i < mModel->columnCount(); i++ )
168 mLayersTree->resizeColumnToContents( i );
169 mLayersTree->setColumnWidth( 1, mLayersTree->columnWidth( 1 ) + 10 );
170 }
171
173 {
174 // initial details are incomplete, so fire up a task in the background to fully populate the model...
175 mTask = new QgsProviderSublayerTask( uri, providerKey, true );
176 connect( mTask.data(), &QgsProviderSublayerTask::taskCompleted, this, [=] {
177 QList<QgsProviderSublayerDetails> res = mTask->results();
178 res.erase( std::remove_if( res.begin(), res.end(), [acceptableTypes]( const QgsProviderSublayerDetails &sublayer ) {
179 return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
180 } ),
181 res.end() );
182
183 mModel->setSublayerDetails( res );
184 mModel->setGeometryTypesResolved( true );
185 mTask = nullptr;
186 mLayersTree->expandAll();
187 selectAll();
188 } );
189 QgsApplication::taskManager()->addTask( mTask.data() );
190 }
191
192 connect( mBtnSelectAll, &QAbstractButton::pressed, this, &QgsProviderSublayersDialog::selectAll );
193 connect( mBtnDeselectAll, &QAbstractButton::pressed, this, [=] { mLayersTree->selectionModel()->clear(); } );
194 connect( mLayersTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsProviderSublayersDialog::treeSelectionChanged );
195 connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, mProxyModel, &QgsProviderSublayerProxyModel::setFilterString );
196 connect( mCheckShowSystem, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeSystemTables );
197 connect( mCheckShowEmpty, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeEmptyLayers );
198 connect( mLayersTree, &QTreeView::doubleClicked, this, [=]( const QModelIndex &index ) {
199 const QModelIndex left = mLayersTree->model()->index( index.row(), 0, index.parent() );
200 if ( !( left.flags() & Qt::ItemIsSelectable ) )
201 return;
202
203 mLayersTree->selectionModel()->select( QItemSelection( left, mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::ClearAndSelect );
204 emit layersAdded( selectedLayers() );
205 accept();
206 } );
207 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
208 connect( mButtonBox, &QDialogButtonBox::accepted, this, [=] {
209 emit layersAdded( selectedLayers() );
210 accept();
211 } );
212 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
213 mButtonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add Layers" ) );
214
215 selectAll();
216}
217
218void QgsProviderSublayersDialog::setNonLayerItems( const QList<QgsProviderSublayerModel::NonLayerItem> &items )
219{
220 for ( const QgsProviderSublayerModel::NonLayerItem &item : items )
221 {
222 mModel->addNonLayerItem( item );
223 }
224}
225
227{
228 QgsSettings settings;
229 settings.setValue( "/Windows/SubLayers/headerState", mLayersTree->header()->saveState() );
230 settings.setValue( QStringLiteral( "/qgis/openSublayersInGroup" ), mCbxAddToGroup->isChecked() );
231
232 if ( mTask )
233 mTask->cancel();
234}
235
236QList<QgsProviderSublayerDetails> QgsProviderSublayersDialog::selectedLayers() const
237{
238 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
239 QList<QgsProviderSublayerDetails> selectedSublayers;
240 for ( const QModelIndex &index : selection )
241 {
242 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
243 if ( !mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
244 {
245 selectedSublayers << mModel->indexToSublayer( sourceIndex );
246 }
247 }
248 return selectedSublayers;
249}
250
251QList<QgsProviderSublayerModel::NonLayerItem> QgsProviderSublayersDialog::selectedNonLayerItems() const
252{
253 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
254 QList<QgsProviderSublayerModel::NonLayerItem> selectedItems;
255 for ( const QModelIndex &index : selection )
256 {
257 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
258 if ( mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
259 {
260 selectedItems << mModel->indexToNonLayerItem( sourceIndex );
261 }
262 }
263 return selectedItems;
264}
265
266void QgsProviderSublayersDialog::setGroupName( const QString &groupNameIn )
267{
268 mGroupName = groupNameIn;
269 const QgsSettings settings;
270 if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
271 {
272 mGroupName = QgsMapLayer::formatLayerName( mGroupName );
273 }
274
275 mCbxAddToGroup->setVisible( !mGroupName.isEmpty() );
276}
277
279{
280 if ( !mCbxAddToGroup->isChecked() )
281 return QString();
282 return mGroupName;
283}
284
285void QgsProviderSublayersDialog::treeSelectionChanged( const QItemSelection &selected, const QItemSelection & )
286{
287 if ( mBlockSelectionChanges )
288 return;
289
290 mBlockSelectionChanges = true;
291 bool selectedANonLayerItem = false;
292 QModelIndex firstSelectedNonLayerItem;
293 bool selectedALayerItem = false;
294 for ( const QModelIndex &index : selected.indexes() )
295 {
296 if ( index.column() != 0 )
297 continue;
298
299 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
300 {
301 if ( !selectedANonLayerItem )
302 {
303 selectedANonLayerItem = true;
304 firstSelectedNonLayerItem = index;
305 }
306 else
307 {
308 // only one non-layer item can be selected
309 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
310 }
311 }
312 else
313 {
314 selectedALayerItem = true;
315 }
316 }
317
318 for ( int row = 0; row < mProxyModel->rowCount(); ++row )
319 {
320 const QModelIndex index = mProxyModel->index( row, 0 );
321 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
322 {
323 if ( ( selectedANonLayerItem && index != firstSelectedNonLayerItem ) || selectedALayerItem )
324 {
325 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
326 }
327 }
328 else
329 {
330 if ( selectedANonLayerItem )
331 {
332 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
333 }
334 }
335 }
336 mBlockSelectionChanges = false;
337
338 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( !mLayersTree->selectionModel()->selectedRows().empty() );
339
340 mCbxAddToGroup->setEnabled( !selectedANonLayerItem );
341 mButtonBox->button( QDialogButtonBox::Ok )->setText( selectedANonLayerItem ? tr( "Open" ) : tr( "Add Layers" ) );
342}
343
344void QgsProviderSublayersDialog::selectAll()
345{
346 mLayersTree->selectionModel()->clear();
347
348 std::function<void( const QModelIndex & )> selectAllInParent;
349
350 selectAllInParent = [this, &selectAllInParent]( const QModelIndex &parent ) {
351 for ( int row = 0; row < mProxyModel->rowCount( parent ); ++row )
352 {
353 const QModelIndex index = mProxyModel->index( row, 0, parent );
354 if ( !mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
355 {
356 mLayersTree->selectionModel()->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Select );
357 }
358 selectAllInParent( index );
359 }
360 };
361 selectAllInParent( QModelIndex() );
362
363 mButtonBox->button( QDialogButtonBox::Ok )->setFocus();
364}
@ 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:210
static QgsNative * nativePlatformInterface()
Returns the global native interface, which offers abstraction to the host OS's underlying public inte...
Definition qgsgui.cpp:85
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.