QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 <QUrl>
32
33#include "moc_qgsprovidersublayersdialog.cpp"
34
39
40QVariant QgsProviderSublayerDialogModel::data( const QModelIndex &index, int role ) const
41{
42 if ( !index.isValid() )
43 return QVariant();
44
45 QgsProviderSublayerModelNode *node = index2node( index );
46 if ( !node )
47 return QVariant();
48
49 if ( QgsProviderSublayerModelSublayerNode *sublayerNode = dynamic_cast<QgsProviderSublayerModelSublayerNode *>( node ) )
50 {
51 const QgsProviderSublayerDetails details = sublayerNode->sublayer();
52
53 if ( details.type() == Qgis::LayerType::Vector && details.wkbType() == Qgis::WkbType::Unknown && !mGeometryTypesResolved )
54 {
55 switch ( role )
56 {
57 case Qt::DisplayRole:
58 case Qt::ToolTipRole:
59 {
60 if ( index.column() == static_cast<int>( Column::Description ) )
61 return tr( "Scanning…" );
62 break;
63 }
64
65 case Qt::FontRole:
66 {
67 QFont f = QgsProviderSublayerModel::data( index, role ).value<QFont>();
68 f.setItalic( true );
69 return f;
70 }
71 }
72 }
73 else if ( details.flags() & Qgis::SublayerFlag::SystemTable )
74 {
75 switch ( role )
76 {
77 case Qt::FontRole:
78 {
79 QFont f = QgsProviderSublayerModel::data( index, role ).value<QFont>();
80 f.setItalic( true );
81 return f;
82 }
83 }
84 }
85 }
86
88}
89
90Qt::ItemFlags QgsProviderSublayerDialogModel::flags( const QModelIndex &index ) const
91{
92 if ( !index.isValid() )
94
95 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
97
98 if ( index.row() < mSublayers.count() )
99 {
100 const QgsProviderSublayerDetails details = mSublayers.at( index.row() );
101
102 if ( details.type() == Qgis::LayerType::Vector && details.wkbType() == Qgis::WkbType::Unknown && !mGeometryTypesResolved )
103 {
104 // unknown geometry item can't be selected
105 return Qt::ItemFlags();
106 }
107 }
109}
110
112{
113 mGeometryTypesResolved = resolved;
114 emit dataChanged( index( 0, 0 ), index( rowCount( QModelIndex() ), columnCount() ) );
115}
116
117QgsProviderSublayersDialog::QgsProviderSublayersDialog( const QString &uri, const QString &providerKey, const QString &filePathIn, const QList<QgsProviderSublayerDetails> initialDetails, const QList<Qgis::LayerType> &acceptableTypes, QWidget *parent, Qt::WindowFlags fl )
118 : QDialog( parent, fl )
119{
120 setupUi( this );
122
123 const QFileInfo fileInfo( filePathIn );
124 const QString filePath = ( fileInfo.isFile() || fileInfo.isDir() ) && fileInfo.exists() ? filePathIn : QString();
125 const QString fileName = !filePath.isEmpty() ? QgsProviderUtils::suggestLayerNameFromFilePath( filePath ) : QString();
126
127 if ( !fileName.isEmpty() )
128 {
129 setGroupName( fileName );
130 }
131
132 setWindowTitle( fileName.isEmpty() ? tr( "Select Items to Add" ) : QStringLiteral( "%1 | %2" ).arg( tr( "Select Items to Add" ), fileName ) );
133
134 mLblFilePath->setText( QStringLiteral( "<a href=\"%1\">%2</a>" )
135 .arg( QUrl::fromLocalFile( filePath ).toString(), QDir::toNativeSeparators( QFileInfo( filePath ).canonicalFilePath() ) ) );
136 mLblFilePath->setVisible( !filePath.isEmpty() );
137 mLblFilePath->setWordWrap( true );
138 mLblFilePath->setTextInteractionFlags( Qt::TextBrowserInteraction );
139 connect( mLblFilePath, &QLabel::linkActivated, this, []( const QString &link ) {
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, [this, acceptableTypes] {
179 QList<QgsProviderSublayerDetails> res = mTask->results();
180 res.erase( std::remove_if( res.begin(), res.end(), [acceptableTypes]( const QgsProviderSublayerDetails &sublayer ) {
181 return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() );
182 } ),
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, [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, [this]( const QModelIndex &index ) {
201 const QModelIndex left = mLayersTree->model()->index( index.row(), 0, index.parent() );
202 if ( !( left.flags() & Qt::ItemIsSelectable ) )
203 return;
204
205 mLayersTree->selectionModel()->select( QItemSelection( left, mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::ClearAndSelect );
206 emit layersAdded( selectedLayers() );
207 accept();
208 } );
209 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
210 connect( mButtonBox, &QDialogButtonBox::accepted, this, [this] {
211 emit layersAdded( selectedLayers() );
212 accept();
213 } );
214 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
215 mButtonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add Layers" ) );
216
217 selectAll();
218}
219
220void QgsProviderSublayersDialog::setNonLayerItems( const QList<QgsProviderSublayerModel::NonLayerItem> &items )
221{
222 for ( const QgsProviderSublayerModel::NonLayerItem &item : items )
223 {
224 mModel->addNonLayerItem( item );
225 }
226}
227
229{
230 QgsSettings settings;
231 settings.setValue( "/Windows/SubLayers/headerState", mLayersTree->header()->saveState() );
232 settings.setValue( QStringLiteral( "/qgis/openSublayersInGroup" ), mCbxAddToGroup->isChecked() );
233
234 if ( mTask )
235 mTask->cancel();
236}
237
238QList<QgsProviderSublayerDetails> QgsProviderSublayersDialog::selectedLayers() const
239{
240 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
241 QList<QgsProviderSublayerDetails> selectedSublayers;
242 for ( const QModelIndex &index : selection )
243 {
244 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
245 if ( !mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
246 {
247 selectedSublayers << mModel->indexToSublayer( sourceIndex );
248 }
249 }
250 return selectedSublayers;
251}
252
253QList<QgsProviderSublayerModel::NonLayerItem> QgsProviderSublayersDialog::selectedNonLayerItems() const
254{
255 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
256 QList<QgsProviderSublayerModel::NonLayerItem> selectedItems;
257 for ( const QModelIndex &index : selection )
258 {
259 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
260 if ( mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
261 {
262 selectedItems << mModel->indexToNonLayerItem( sourceIndex );
263 }
264 }
265 return selectedItems;
266}
267
268void QgsProviderSublayersDialog::setGroupName( const QString &groupNameIn )
269{
270 mGroupName = groupNameIn;
271 const QgsSettings settings;
272 if ( settings.value( QStringLiteral( "qgis/formatLayerName" ), false ).toBool() )
273 {
274 mGroupName = QgsMapLayer::formatLayerName( mGroupName );
275 }
276
277 mCbxAddToGroup->setVisible( !mGroupName.isEmpty() );
278}
279
281{
282 if ( !mCbxAddToGroup->isChecked() )
283 return QString();
284 return mGroupName;
285}
286
287void QgsProviderSublayersDialog::treeSelectionChanged( const QItemSelection &selected, const QItemSelection & )
288{
289 if ( mBlockSelectionChanges )
290 return;
291
292 mBlockSelectionChanges = true;
293 bool selectedANonLayerItem = false;
294 QModelIndex firstSelectedNonLayerItem;
295 bool selectedALayerItem = false;
296 for ( const QModelIndex &index : selected.indexes() )
297 {
298 if ( index.column() != 0 )
299 continue;
300
301 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
302 {
303 if ( !selectedANonLayerItem )
304 {
305 selectedANonLayerItem = true;
306 firstSelectedNonLayerItem = index;
307 }
308 else
309 {
310 // only one non-layer item can be selected
311 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 );
312 }
313 }
314 else
315 {
316 selectedALayerItem = true;
317 }
318 }
319
320 for ( int row = 0; row < mProxyModel->rowCount(); ++row )
321 {
322 const QModelIndex index = mProxyModel->index( row, 0 );
323 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
324 {
325 if ( ( selectedANonLayerItem && index != firstSelectedNonLayerItem ) || selectedALayerItem )
326 {
327 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 );
328 }
329 }
330 else
331 {
332 if ( selectedANonLayerItem )
333 {
334 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 );
335 }
336 }
337 }
338 mBlockSelectionChanges = false;
339
340 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( !mLayersTree->selectionModel()->selectedRows().empty() );
341
342 mCbxAddToGroup->setEnabled( !selectedANonLayerItem );
343 mButtonBox->button( QDialogButtonBox::Ok )->setText( selectedANonLayerItem ? tr( "Open" ) : tr( "Add Layers" ) );
344}
345
346void QgsProviderSublayersDialog::selectAll()
347{
348 mLayersTree->selectionModel()->clear();
349
350 std::function<void( const QModelIndex & )> selectAllInParent;
351
352 selectAllInParent = [this, &selectAllInParent]( const QModelIndex &parent ) {
353 for ( int row = 0; row < mProxyModel->rowCount( parent ); ++row )
354 {
355 const QModelIndex index = mProxyModel->index( row, 0, parent );
356 if ( !mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
357 {
358 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 );
359 }
360 selectAllInParent( index );
361 }
362 };
363 selectAllInParent( QModelIndex() );
364
365 mButtonBox->button( QDialogButtonBox::Ok )->setFocus();
366}
@ SystemTable
Sublayer is a system or internal table, which should be hidden by default.
Definition qgis.h:1410
@ Vector
Vector layer.
Definition qgis.h:191
@ Unknown
Unknown.
Definition qgis.h:278
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:221
static QgsNative * nativePlatformInterface()
Returns the global native interface, which offers abstraction to the host OS's underlying public inte...
Definition qgsgui.cpp:96
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:65
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.