Skip to content
Permalink
Browse files
Create new QgsProviderSublayersDialog class
Based on QgsProviderSublayerDetails, this new dialog nicely handles
mixed layer type files (e.g. mixed vector/raster/mesh layer formats).

It also:

- Resolves geometry types in a background thread, to avoid lengthy
application hangs whenever a full table scan is required to determine
available geometry types
- Shows a handy search filter box for filtering sublayers to matching
strings
- Shows icons representing sublayer types
- Has an interactive label showing the file path which can be clicked
to open a file explorer focused on the file
  • Loading branch information
nyalldawson committed Jul 12, 2021
1 parent 3ff6789 commit 9796aa3b8887cfc5ccc7d9bc3280143b54dd6a72
@@ -196,6 +196,10 @@ Adds a non-layer item (e.g. an embedded QGIS project item) to the model.
virtual QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const;


protected:



};


@@ -59,6 +59,7 @@ set(QGIS_APP_SRCS
qgsmapcanvasdockwidget.cpp
qgsmapsavedialog.cpp
qgsprojectlistitemdelegate.cpp
qgsprovidersublayersdialog.cpp
qgspuzzlewidget.cpp
qgsversionmigration.cpp
qgssnappinglayertreemodel.cpp
@@ -0,0 +1,188 @@
/***************************************************************************
qgsprovidersublayersdialog.h
---------------------
begin : July 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsprovidersublayersdialog.h"
#include "qgssettings.h"
#include "qgsgui.h"
#include "qgsprovidersublayermodel.h"
#include "qgsproviderutils.h"
#include "qgsprovidersublayertask.h"
#include "qgsapplication.h"
#include "qgstaskmanager.h"
#include "qgsnative.h"

#include <QPushButton>
#include <QFileInfo>
#include <QDir>
#include <QDesktopServices>

QgsProviderSublayerDialogModel::QgsProviderSublayerDialogModel( QObject *parent )
: QgsProviderSublayerModel( parent )
{

}

QVariant QgsProviderSublayerDialogModel::data( const QModelIndex &index, int role ) const
{
if ( !index.isValid() )
return QVariant();

if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
return QVariant();

if ( index.row() < mSublayers.count() )
{
const QgsProviderSublayerDetails details = mSublayers.at( index.row() );

if ( details.type() == QgsMapLayerType::VectorLayer && details.wkbType() == QgsWkbTypes::Unknown )
{
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
{
if ( index.column() == static_cast< int >( Column::Description ) )
return tr( "Scanning…" );
break;
}

case Qt::FontRole:
{
QFont f = QgsProviderSublayerModel::data( index, role ).value< QFont >();
f.setItalic( true );
return f;
}
}
}
}
return QgsProviderSublayerModel::data( index, role );
}

Qt::ItemFlags QgsProviderSublayerDialogModel::flags( const QModelIndex &index ) const
{
if ( !index.isValid() )
return QgsProviderSublayerModel::flags( index );

if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
return QgsProviderSublayerModel::flags( index );

if ( index.row() < mSublayers.count() )
{
const QgsProviderSublayerDetails details = mSublayers.at( index.row() );

if ( details.type() == QgsMapLayerType::VectorLayer && details.wkbType() == QgsWkbTypes::Unknown )
{
// unknown geometry item can't be selected
return Qt::ItemFlags();
}
}
return QgsProviderSublayerModel::flags( index );
}

QgsProviderSublayersDialog::QgsProviderSublayersDialog( const QString &uri, const QList<QgsProviderSublayerDetails> initialDetails, QWidget *parent, Qt::WindowFlags fl )
: QDialog( parent, fl )
{
setupUi( this );
QgsGui::enableAutoGeometryRestore( this );

const QFileInfo fileInfo( uri );
const QString filePath = fileInfo.isFile() && fileInfo.exists() ? uri : QString();
const QString fileName = !filePath.isEmpty() ? fileInfo.fileName() : QString();

setWindowTitle( fileName.isEmpty() ? tr( "Select Items to Add" ) : QStringLiteral( "%1 | %2" ).arg( tr( "Select Items to Add" ), fileName ) );

mLblFilePath->setText( QStringLiteral( "<a href=\"%1\">%2</a>" )
.arg( QUrl::fromLocalFile( filePath ).toString(), QDir::toNativeSeparators( QFileInfo( filePath ).canonicalFilePath() ) ) );
mLblFilePath->setVisible( ! fileName.isEmpty() );
mLblFilePath->setWordWrap( true );
mLblFilePath->setTextInteractionFlags( Qt::TextBrowserInteraction );
connect( mLblFilePath, &QLabel::linkActivated, this, [ = ]( const QString & link )
{
const QUrl url( link );
QFileInfo file( url.toLocalFile() );
if ( file.exists() && !file.isDir() )
QgsGui::instance()->nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
else
QDesktopServices::openUrl( url );
} );

mModel = new QgsProviderSublayerDialogModel( this );
mModel->setSublayerDetails( initialDetails );
mProxyModel = new QgsProviderSublayerProxyModel( this );
mProxyModel->setSourceModel( mModel );
mLayersTree->setModel( mProxyModel );

// resize columns
QgsSettings settings;
QByteArray ba = settings.value( "/Windows/SubLayers/headerState" ).toByteArray();
if ( !ba.isNull() )
{
mLayersTree->header()->restoreState( ba );
}
else
{
for ( int i = 0; i < mModel->columnCount(); i++ )
mLayersTree->resizeColumnToContents( i );
mLayersTree->setColumnWidth( 1, mLayersTree->columnWidth( 1 ) + 10 );
}

if ( QgsProviderUtils::sublayerDetailsAreIncomplete( initialDetails, false ) )
{
// initial details are incomplete, so fire up a task in the background to fully populate the model...
mTask = new QgsProviderSublayerTask( uri );
connect( mTask.data(), &QgsProviderSublayerTask::taskCompleted, this, [ = ]
{
mModel->setSublayerDetails( mTask->results() );
mTask = nullptr;
} );
QgsApplication::taskManager()->addTask( mTask.data() );
}

connect( mBtnSelectAll, &QAbstractButton::pressed, mLayersTree, &QTreeView::selectAll );
connect( mBtnDeselectAll, &QAbstractButton::pressed, this, [ = ] { mLayersTree->selectionModel()->clear(); } );
connect( mLayersTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &QgsProviderSublayersDialog::treeSelectionChanged );
connect( mSearchLineEdit, &QgsFilterLineEdit::textChanged, mProxyModel, &QgsProviderSublayerProxyModel::setFilterString );

connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
connect( mButtonBox, &QDialogButtonBox::accepted, this, [ = ]
{
const QModelIndexList selection = mLayersTree->selectionModel()->selectedRows();
QList< QgsProviderSublayerDetails > selectedSublayers;
for ( const QModelIndex &index : selection )
{
const QModelIndex sourceIndex = mProxyModel->mapToSource( index );
if ( !mModel->data( sourceIndex, static_cast< int >( QgsProviderSublayerModel::Role::IsNonLayerItem ) ).toBool() )
{
selectedSublayers << mModel->indexToSublayer( sourceIndex );
}
}
emit layersAdded( selectedSublayers );
accept();
} );
mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
}

QgsProviderSublayersDialog::~QgsProviderSublayersDialog()
{
QgsSettings settings;
settings.setValue( "/Windows/SubLayers/headerState", mLayersTree->header()->saveState() );
if ( mTask )
mTask->cancel();
}

void QgsProviderSublayersDialog::treeSelectionChanged( const QItemSelection &, const QItemSelection & )
{
mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( !mLayersTree->selectionModel()->selectedRows().empty() );
}
@@ -0,0 +1,75 @@
/***************************************************************************
qgsprovidersublayersdialog.h
---------------------
begin : July 2021
copyright : (C) 2021 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSPROVIDERSUBLAYERSDIALOG_H
#define QGSPROVIDERSUBLAYERSDIALOG_H

#include <QDialog>
#include <QCheckBox>
#include <QPointer>

#include "ui_qgsprovidersublayersdialogbase.h"
#include "qgsprovidersublayerdetails.h"
#include "qgsprovidersublayermodel.h"

class QgsProviderSublayerModel;
class QgsProviderSublayerProxyModel;
class QgsProviderSublayerTask;

class QgsProviderSublayerDialogModel : public QgsProviderSublayerModel
{
Q_OBJECT

public:

QgsProviderSublayerDialogModel( QObject *parent = nullptr );

QVariant data( const QModelIndex &index, int role ) const override;
Qt::ItemFlags flags( const QModelIndex &index ) const override;


};

class QgsProviderSublayersDialog : public QDialog, private Ui::QgsProviderSublayersDialogBase
{
Q_OBJECT
public:

QgsProviderSublayersDialog( const QString &uri,
const QList< QgsProviderSublayerDetails> initialDetails = QList< QgsProviderSublayerDetails>(),
QWidget *parent SIP_TRANSFERTHIS = nullptr,
Qt::WindowFlags fl = Qt::WindowFlags() );

~QgsProviderSublayersDialog() override;

signals:

/**
* Emitted when sublayers selected from the dialog should be added to the project.
*/
void layersAdded( const QList< QgsProviderSublayerDetails > &layers );

private slots:
void treeSelectionChanged( const QItemSelection &, const QItemSelection & );

private:

QgsProviderSublayerModel *mModel = nullptr;
QgsProviderSublayerProxyModel *mProxyModel = nullptr;
QPointer< QgsProviderSublayerTask > mTask;

};

#endif // QGSPROVIDERSUBLAYERSDIALOG_H
@@ -422,7 +422,7 @@ bool QgsProviderSublayerProxyModel::lessThan( const QModelIndex &source_left, co
const QString leftName = sourceModel()->data( source_left, static_cast< int >( QgsProviderSublayerModel::Role::Name ) ).toString();
const QString rightName = sourceModel()->data( source_right, static_cast< int >( QgsProviderSublayerModel::Role::Name ) ).toString();

return leftName.compare( rightName, Qt::CaseInsensitive );
return QString::localeAwareCompare( leftName, rightName ) < 0;
}

QString QgsProviderSublayerProxyModel::filterString() const
@@ -202,9 +202,12 @@ class CORE_EXPORT QgsProviderSublayerModel: public QAbstractItemModel
QVariant data( const QModelIndex &index, int role ) const override;
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;

private:
protected:

//! Sublayer list
QList<QgsProviderSublayerDetails> mSublayers;

//! Non layer item list
QList<NonLayerItem> mNonLayerItems;

};

0 comments on commit 9796aa3

Please sign in to comment.