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