QGIS API Documentation 4.1.0-Master (376402f9aeb)
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"
26#include "qgstaskmanager.h"
27
28#include <QDesktopServices>
29#include <QDir>
30#include <QFileInfo>
31#include <QPushButton>
32#include <QString>
33#include <QUrl>
34
35#include "moc_qgsprovidersublayersdialog.cpp"
36
37using namespace Qt::StringLiterals;
38
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
121 const QString &uri,
122 const QString &providerKey,
123 const QString &filePathIn,
124 const QList<QgsProviderSublayerDetails> initialDetails,
125 const QList<Qgis::LayerType> &acceptableTypes,
126 QWidget *parent,
127 Qt::WindowFlags fl
128)
129 : QDialog( parent, fl )
130{
131 setupUi( this );
133
134 const QFileInfo fileInfo( filePathIn );
135 const QString filePath = ( fileInfo.isFile() || fileInfo.isDir() ) && fileInfo.exists() ? filePathIn : QString();
136 const QString fileName = !filePath.isEmpty() ? QgsProviderUtils::suggestLayerNameFromFilePath( filePath ) : QString();
137
138 if ( !fileName.isEmpty() )
139 {
140 setGroupName( fileName );
141 }
142
143 setWindowTitle( fileName.isEmpty() ? tr( "Select Items to Add" ) : u"%1 | %2"_s.arg( tr( "Select Items to Add" ), fileName ) );
144
145 mLblFilePath->setText( u"<a href=\"%1\">%2</a>"_s.arg( QUrl::fromLocalFile( filePath ).toString(), QDir::toNativeSeparators( QFileInfo( filePath ).canonicalFilePath() ) ) );
146 mLblFilePath->setVisible( !filePath.isEmpty() );
147 mLblFilePath->setWordWrap( true );
148 mLblFilePath->setTextInteractionFlags( Qt::TextBrowserInteraction );
149 connect( mLblFilePath, &QLabel::linkActivated, this, []( const QString &link ) {
150 const QUrl url( link );
151 const QFileInfo file( url.toLocalFile() );
152 if ( file.exists() && !file.isDir() )
153 QgsGui::nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
154 else
155 QDesktopServices::openUrl( url );
156 } );
157
158 mModel = new QgsProviderSublayerDialogModel( this );
159 mModel->setSublayerDetails( initialDetails );
160 mProxyModel = new QgsProviderSublayerProxyModel( this );
161 mProxyModel->setSourceModel( mModel );
162 mLayersTree->setModel( mProxyModel );
163
164 mLayersTree->expandAll();
165
166 const QgsSettings settings;
167 const bool addToGroup = QgsSettingsRegistryGui::settingsOpenSublayersInGroup->value();
168 mCbxAddToGroup->setChecked( addToGroup );
169 mCbxAddToGroup->setVisible( !fileName.isEmpty() );
170
171 // resize columns
172 const QByteArray ba = settings.value( "/Windows/SubLayers/headerState" ).toByteArray();
173 if ( !ba.isNull() )
174 {
175 mLayersTree->header()->restoreState( ba );
176 }
177 else
178 {
179 for ( int i = 0; i < mModel->columnCount(); i++ )
180 mLayersTree->resizeColumnToContents( i );
181 mLayersTree->setColumnWidth( 1, mLayersTree->columnWidth( 1 ) + 10 );
182 }
183
185 {
186 // initial details are incomplete, so fire up a task in the background to fully populate the model...
187 mTask = new QgsProviderSublayerTask( uri, providerKey, true );
188 connect( mTask.data(), &QgsProviderSublayerTask::taskCompleted, this, [this, acceptableTypes] {
189 QList<QgsProviderSublayerDetails> res = mTask->results();
190 res.erase(
191 std::remove_if( res.begin(), res.end(), [acceptableTypes]( const QgsProviderSublayerDetails &sublayer ) { return !acceptableTypes.empty() && !acceptableTypes.contains( sublayer.type() ); } ),
192 res.end()
193 );
194
195 mModel->setSublayerDetails( res );
196 mModel->setGeometryTypesResolved( true );
197 mTask = nullptr;
198 mLayersTree->expandAll();
199 selectAll();
200 } );
201 QgsApplication::taskManager()->addTask( mTask.data() );
202 }
203
204 connect( mBtnSelectAll, &QAbstractButton::pressed, this, &QgsProviderSublayersDialog::selectAll );
205 connect( mBtnDeselectAll, &QAbstractButton::pressed, this, [this] { mLayersTree->selectionModel()->clear(); } );
206 connect( mLayersTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsProviderSublayersDialog::treeSelectionChanged );
207 connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, mProxyModel, &QgsProviderSublayerProxyModel::setFilterString );
208 connect( mCheckShowSystem, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeSystemTables );
209 connect( mCheckShowEmpty, &QCheckBox::toggled, mProxyModel, &QgsProviderSublayerProxyModel::setIncludeEmptyLayers );
210 connect( mLayersTree, &QTreeView::doubleClicked, this, [this]( const QModelIndex &index ) {
211 const QModelIndex left = mLayersTree->model()->index( index.row(), 0, index.parent() );
212 if ( !( left.flags() & Qt::ItemIsSelectable ) )
213 return;
214
215 mLayersTree->selectionModel()->select( QItemSelection( left, mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::ClearAndSelect );
216 emit layersAdded( selectedLayers() );
217 accept();
218 } );
219 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
220 connect( mButtonBox, &QDialogButtonBox::accepted, this, [this] {
221 emit layersAdded( selectedLayers() );
222 accept();
223 } );
224 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
225 mButtonBox->button( QDialogButtonBox::Ok )->setText( tr( "Add Layers" ) );
226
227 selectAll();
228}
229
230void QgsProviderSublayersDialog::setNonLayerItems( const QList<QgsProviderSublayerModel::NonLayerItem> &items )
231{
232 for ( const QgsProviderSublayerModel::NonLayerItem &item : items )
233 {
234 mModel->addNonLayerItem( item );
235 }
236}
237
239{
240 QgsSettings settings;
241 settings.setValue( "/Windows/SubLayers/headerState", mLayersTree->header()->saveState() );
242 QgsSettingsRegistryGui::settingsOpenSublayersInGroup->setValue( mCbxAddToGroup->isChecked() );
243
244 if ( mTask )
245 mTask->cancel();
246}
247
248QList<QgsProviderSublayerDetails> QgsProviderSublayersDialog::selectedLayers() const
249{
250 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
251 QList<QgsProviderSublayerDetails> selectedSublayers;
252 for ( const QModelIndex &index : selection )
253 {
254 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
255 if ( !mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
256 {
257 selectedSublayers << mModel->indexToSublayer( sourceIndex );
258 }
259 }
260 return selectedSublayers;
261}
262
263QList<QgsProviderSublayerModel::NonLayerItem> QgsProviderSublayersDialog::selectedNonLayerItems() const
264{
265 const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
266 QList<QgsProviderSublayerModel::NonLayerItem> selectedItems;
267 for ( const QModelIndex &index : selection )
268 {
269 const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
270 if ( mModel->data( sourceIndex, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
271 {
272 selectedItems << mModel->indexToNonLayerItem( sourceIndex );
273 }
274 }
275 return selectedItems;
276}
277
278void QgsProviderSublayersDialog::setGroupName( const QString &groupNameIn )
279{
280 mGroupName = groupNameIn;
281 const QgsSettings settings;
283 {
284 mGroupName = QgsMapLayer::formatLayerName( mGroupName );
285 }
286
287 mCbxAddToGroup->setVisible( !mGroupName.isEmpty() );
288}
289
291{
292 if ( !mCbxAddToGroup->isChecked() )
293 return QString();
294 return mGroupName;
295}
296
297void QgsProviderSublayersDialog::treeSelectionChanged( const QItemSelection &selected, const QItemSelection & )
298{
299 if ( mBlockSelectionChanges )
300 return;
301
302 mBlockSelectionChanges = true;
303 bool selectedANonLayerItem = false;
304 QModelIndex firstSelectedNonLayerItem;
305 bool selectedALayerItem = false;
306 for ( const QModelIndex &index : selected.indexes() )
307 {
308 if ( index.column() != 0 )
309 continue;
310
311 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
312 {
313 if ( !selectedANonLayerItem )
314 {
315 selectedANonLayerItem = true;
316 firstSelectedNonLayerItem = index;
317 }
318 else
319 {
320 // only one non-layer item can be selected
321 mLayersTree->selectionModel()
322 ->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
323 }
324 }
325 else
326 {
327 selectedALayerItem = true;
328 }
329 }
330
331 for ( int row = 0; row < mProxyModel->rowCount(); ++row )
332 {
333 const QModelIndex index = mProxyModel->index( row, 0 );
334 if ( mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
335 {
336 if ( ( selectedANonLayerItem && index != firstSelectedNonLayerItem ) || selectedALayerItem )
337 {
338 mLayersTree->selectionModel()
339 ->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
340 }
341 }
342 else
343 {
344 if ( selectedANonLayerItem )
345 {
346 mLayersTree->selectionModel()
347 ->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), QItemSelectionModel::Deselect );
348 }
349 }
350 }
351 mBlockSelectionChanges = false;
352
353 mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( !mLayersTree->selectionModel()->selectedRows().empty() );
354
355 mCbxAddToGroup->setEnabled( !selectedANonLayerItem );
356 mButtonBox->button( QDialogButtonBox::Ok )->setText( selectedANonLayerItem ? tr( "Open" ) : tr( "Add Layers" ) );
357}
358
359void QgsProviderSublayersDialog::selectAll()
360{
361 mLayersTree->selectionModel()->clear();
362
363 std::function<void( const QModelIndex & )> selectAllInParent;
364
365 selectAllInParent = [this, &selectAllInParent]( const QModelIndex &parent ) {
366 for ( int row = 0; row < mProxyModel->rowCount( parent ); ++row )
367 {
368 const QModelIndex index = mProxyModel->index( row, 0, parent );
369 if ( !mProxyModel->data( index, static_cast<int>( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
370 {
371 mLayersTree->selectionModel()
372 ->select( QItemSelection( mLayersTree->model()->index( index.row(), 0, index.parent() ), mLayersTree->model()->index( index.row(), mLayersTree->model()->columnCount() - 1, index.parent() ) ), 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.
Definition qgis.h:1529
@ 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.
static const QgsSettingsEntryBool * settingsOpenSublayersInGroup
Settings entry whether to open sublayers in a group.
static const QgsSettingsEntryBool * settingsFormatLayerName
Settings entry whether to format layer names.
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.