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