From b03d61d5d693776725978c9344f7bf386524e7a0 Mon Sep 17 00:00:00 2001 From: Alex <roya0045@users.noreply.github.com> Date: Wed, 20 Mar 2019 20:01:50 -0400 Subject: [PATCH] base path handler --- src/app/qgshandlebadlayers.cpp | 954 +++++++++++++++++---------------- src/app/qgshandlebadlayers.h | 159 +++--- 2 files changed, 573 insertions(+), 540 deletions(-) diff --git a/src/app/qgshandlebadlayers.cpp b/src/app/qgshandlebadlayers.cpp index 81a28c3491f..670fa1524c4 100644 --- a/src/app/qgshandlebadlayers.cpp +++ b/src/app/qgshandlebadlayers.cpp @@ -1,461 +1,493 @@ -/*************************************************************************** - qgshandlebadlayers.cpp - description - ------------------- - begin : Sat 5 Mar 2011 - copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH - email : jef at norbit dot de - ***************************************************************************/ - -/*************************************************************************** - * * - * 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 "qgshandlebadlayers.h" -#include "qgisapp.h" -#include "qgsauthconfigselect.h" -#include "qgsdataprovider.h" -#include "qgsguiutils.h" -#include "qgsdatasourceuri.h" -#include "qgslogger.h" -#include "qgsrasterlayer.h" -#include "qgsproviderregistry.h" -#include "qgsmessagebar.h" -#include "qgssettings.h" -#include "qgslayertreeregistrybridge.h" -#include "qgsapplication.h" - -#include <QDomDocument> -#include <QDomElement> -#include <QFileDialog> -#include <QPushButton> -#include <QToolButton> -#include <QMessageBox> -#include <QDialogButtonBox> -#include <QUrl> - -void QgsHandleBadLayersHandler::handleBadLayers( const QList<QDomNode> &layers ) -{ - QApplication::setOverrideCursor( Qt::ArrowCursor ); - QgsHandleBadLayers *dialog = new QgsHandleBadLayers( layers ); - - dialog->buttonBox->button( QDialogButtonBox::Ignore )->setToolTip( tr( "Import all unavailable layers unmodified (you can fix them later)." ) ); - dialog->buttonBox->button( QDialogButtonBox::Ignore )->setText( tr( "Keep Unavailable Layers" ) ); - dialog->buttonBox->button( QDialogButtonBox::Discard )->setToolTip( tr( "Remove all unavailable layers from the project" ) ); - dialog->buttonBox->button( QDialogButtonBox::Discard )->setText( tr( "Remove Unavailable Layers" ) ); - dialog->buttonBox->button( QDialogButtonBox::Discard )->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) ); - - if ( dialog->layerCount() < layers.size() ) - QgisApp::instance()->messageBar()->pushMessage( - tr( "Handle unavailable layers" ), - tr( "%1 of %2 unavailable layers were not fixable." ) - .arg( layers.size() - dialog->layerCount() ) - .arg( layers.size() ), - Qgis::Warning, QgisApp::instance()->messageTimeout() ); - - if ( dialog->layerCount() > 0 ) - { - if ( dialog->exec() == dialog->Accepted ) - { - emit layersChanged(); - } - } - - delete dialog; - QApplication::restoreOverrideCursor(); -} - - -QgsHandleBadLayers::QgsHandleBadLayers( const QList<QDomNode> &layers ) - : QDialog( QgisApp::instance() ) - , mLayers( layers ) -{ - setupUi( this ); - - mVectorFileFilter = QgsProviderRegistry::instance()->fileVectorFilters(); - mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters(); - - mBrowseButton = new QPushButton( tr( "Browse" ) ); - buttonBox->addButton( mBrowseButton, QDialogButtonBox::ActionRole ); - mBrowseButton->setDisabled( true ); - mApplyButton = new QPushButton( tr( "Apply Changes" ) ); - mApplyButton->setToolTip( tr( "Apply fixes to unavailable layers (remaining unavailable layers will be removed from the project)." ) ); - buttonBox->addButton( mApplyButton, QDialogButtonBox::ActionRole ); - - connect( mLayerList, &QTableWidget::itemSelectionChanged, this, &QgsHandleBadLayers::selectionChanged ); - connect( mBrowseButton, &QAbstractButton::clicked, this, &QgsHandleBadLayers::browseClicked ); - connect( mApplyButton, &QAbstractButton::clicked, this, &QgsHandleBadLayers::apply ); - connect( buttonBox->button( QDialogButtonBox::Ignore ), &QPushButton::clicked, this, &QgsHandleBadLayers::reject ); - connect( buttonBox->button( QDialogButtonBox::Discard ), &QPushButton::clicked, this, &QgsHandleBadLayers::accept ); - - mLayerList->clear(); - mLayerList->setSortingEnabled( true ); - mLayerList->setSelectionBehavior( QAbstractItemView::SelectRows ); - mLayerList->setColumnCount( 5 ); - mLayerList->setColumnWidth( 3, 75 ); - - mLayerList->setHorizontalHeaderLabels( QStringList() - << tr( "Layer name" ) - << tr( "Type" ) - << tr( "Provider" ) - << tr( "Auth config" ) - << tr( "Datasource" ) - ); - - int j = 0; - for ( int i = 0; i < mLayers.size(); i++ ) - { - const QDomNode &node = mLayers[i]; - - QString name = node.namedItem( QStringLiteral( "layername" ) ).toElement().text(); - QString type = node.toElement().attribute( QStringLiteral( "type" ) ); - QString datasource = node.namedItem( QStringLiteral( "datasource" ) ).toElement().text(); - QString provider = node.namedItem( QStringLiteral( "provider" ) ).toElement().text(); - QString vectorProvider = type == QLatin1String( "vector" ) ? provider : tr( "none" ); - bool providerFileBased = ( QgsProviderRegistry::instance()->providerCapabilities( provider ) & QgsDataProvider::File ) != 0; - - QgsDebugMsg( QStringLiteral( "name=%1 type=%2 provider=%3 datasource='%4'" ) - .arg( name, - type, - vectorProvider, - datasource ) ); - - mLayerList->setRowCount( j + 1 ); - - QTableWidgetItem *item = nullptr; - - item = new QTableWidgetItem( name ); - item->setData( Qt::UserRole + 0, i ); - item->setFlags( item->flags() & ~Qt::ItemIsEditable ); - mLayerList->setItem( j, 0, item ); - - item = new QTableWidgetItem( type ); - item->setData( Qt::UserRole + 0, providerFileBased ); - item->setFlags( item->flags() & ~Qt::ItemIsEditable ); - mLayerList->setItem( j, 1, item ); - - item = new QTableWidgetItem( vectorProvider ); - item->setFlags( item->flags() & ~Qt::ItemIsEditable ); - mLayerList->setItem( j, 2, item ); - - if ( QgsAuthConfigUriEdit::hasConfigId( datasource ) ) - { - QToolButton *btn = new QToolButton( this ); - btn->setMaximumWidth( 75 ); - btn->setMinimumHeight( 24 ); - btn->setText( tr( "Edit" ) ); - btn->setProperty( "row", j ); - connect( btn, &QAbstractButton::clicked, this, &QgsHandleBadLayers::editAuthCfg ); - mLayerList->setCellWidget( j, 3, btn ); - } - else - { - item = new QTableWidgetItem( QString() ); - mLayerList->setItem( j, 3, item ); - } - - item = new QTableWidgetItem( datasource ); - mLayerList->setItem( j, 4, item ); - - j++; - } - - // mLayerList->resizeColumnsToContents(); -} - -void QgsHandleBadLayers::selectionChanged() -{ - - mRows.clear(); - - Q_FOREACH ( QTableWidgetItem *item, mLayerList->selectedItems() ) - { - if ( item->column() != 0 ) - continue; - - bool providerFileBased = mLayerList->item( item->row(), 1 )->data( Qt::UserRole + 0 ).toBool(); - if ( !providerFileBased ) - continue; - - mRows << item->row(); - } - - mBrowseButton->setEnabled( !mRows.isEmpty() ); -} - -QString QgsHandleBadLayers::filename( int row ) -{ - QString type = mLayerList->item( row, 1 )->text(); - QString provider = mLayerList->item( row, 2 )->text(); - QString datasource = mLayerList->item( row, 4 )->text(); - - if ( type == QLatin1String( "vector" ) ) - { - const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( provider, datasource ); - // if parts is empty then provider doesn't handle this method! - return parts.empty() ? datasource : parts.value( QStringLiteral( "path" ) ).toString(); - } - else - { - return datasource; - } -} - -void QgsHandleBadLayers::setFilename( int row, const QString &filename ) -{ - if ( !QFileInfo::exists( filename ) ) - return; - - QString type = mLayerList->item( row, 1 )->text(); - QString provider = mLayerList->item( row, 2 )->text(); - QTableWidgetItem *item = mLayerList->item( row, 4 ); - - QString datasource = item->text(); - - if ( type == QLatin1String( "vector" ) ) - { - if ( provider == QLatin1String( "spatialite" ) ) - { - QgsDataSourceUri uri( datasource ); - uri.setDatabase( filename ); - datasource = uri.uri(); - } - else if ( provider == QLatin1String( "ogr" ) ) - { - QStringList theURIParts = datasource.split( '|' ); - theURIParts[0] = filename; - datasource = theURIParts.join( QStringLiteral( "|" ) ); - } - else if ( provider == QLatin1String( "delimitedtext" ) ) - { - QUrl uriSource = QUrl::fromEncoded( datasource.toLatin1() ); - QUrl uriDest = QUrl::fromLocalFile( filename ); - uriDest.setQueryItems( uriSource.queryItems() ); - datasource = QString::fromLatin1( uriDest.toEncoded() ); - } - } - else - { - datasource = filename; - } - - item->setText( datasource ); -} - -void QgsHandleBadLayers::browseClicked() -{ - - if ( mRows.size() == 1 ) - { - int row = mRows.at( 0 ); - QString type = mLayerList->item( row, 1 )->text(); - - QString memoryQualifier, fileFilter; - if ( type == QLatin1String( "vector" ) ) - { - memoryQualifier = QStringLiteral( "lastVectorFileFilter" ); - fileFilter = mVectorFileFilter; - } - else - { - memoryQualifier = QStringLiteral( "lastRasterFileFilter" ); - fileFilter = mRasterFileFilter; - } - - QString fn = filename( row ); - if ( fn.isNull() ) - return; - - QStringList selectedFiles; - QString enc; - QString title = tr( "Select File to Replace '%1'" ).arg( fn ); - - QgsGuiUtils::openFilesRememberingFilter( memoryQualifier, fileFilter, selectedFiles, enc, title ); - if ( selectedFiles.size() != 1 ) - { - QMessageBox::information( this, title, tr( "Please select exactly one file." ) ); - return; - } - - setFilename( row, selectedFiles[0] ); - } - else if ( mRows.size() > 1 ) - { - QString title = tr( "Select New Directory of Selected Files" ); - - QgsSettings settings; - QString lastDir = settings.value( QStringLiteral( "UI/missingDirectory" ), QDir::homePath() ).toString(); - QString selectedFolder = QFileDialog::getExistingDirectory( this, title, lastDir ); - if ( selectedFolder.isEmpty() ) - { - return; - } - - QDir dir( selectedFolder ); - if ( !dir.exists() ) - { - return; - } - - Q_FOREACH ( int row, mRows ) - { - bool providerFileBased = mLayerList->item( row, 1 )->data( Qt::UserRole + 0 ).toBool(); - if ( !providerFileBased ) - continue; - - QString fn = filename( row ); - if ( fn.isEmpty() ) - continue; - - QFileInfo fi( fn ); - fi.setFile( dir, fi.fileName() ); - if ( !fi.exists() ) - continue; - - setFilename( row, fi.absoluteFilePath() ); - } - } -} - -void QgsHandleBadLayers::editAuthCfg() -{ - QToolButton *btn = qobject_cast<QToolButton *>( sender() ); - int row = -1; - for ( int i = 0; i < mLayerList->rowCount(); i++ ) - { - if ( mLayerList->cellWidget( i, 3 ) == btn ) - { - row = i; - break; - } - } - - if ( row == -1 ) - return; - - QString provider = mLayerList->item( row, 2 )->text(); - if ( provider == QLatin1String( "none" ) ) - provider.clear(); - - QString prevuri = mLayerList->item( row, 4 )->text(); - - QgsAuthConfigUriEdit *dlg = new QgsAuthConfigUriEdit( this, prevuri, provider ); - dlg->setWindowModality( Qt::WindowModal ); - dlg->resize( 500, 500 ); - if ( dlg->exec() ) - { - QString newuri( dlg->dataSourceUri() ); - if ( newuri != prevuri ) - { - mLayerList->item( row, 4 )->setText( newuri ); - } - } - dlg->deleteLater(); -} - -void QgsHandleBadLayers::apply() -{ - QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true ); - buttonBox->button( QDialogButtonBox::Ignore )->setEnabled( false ); - - for ( int i = 0; i < mLayerList->rowCount(); i++ ) - { - int idx = mLayerList->item( i, 0 )->data( Qt::UserRole ).toInt(); - QDomNode &node = const_cast<QDomNode &>( mLayers[ idx ] ); - - QTableWidgetItem *item = mLayerList->item( i, 4 ); - QString datasource = item->text(); - - bool dataSourceChanged { false }; - const QString layerId { node.namedItem( QStringLiteral( "id" ) ).toElement().text() }; - const QString provider { node.namedItem( QStringLiteral( "provider" ) ).toElement().text() }; - const QString name { mLayerList->item( i, 0 )->text() }; - - // Try first to change the datasource of the existing layers, this will - // maintain the current status (checked/unchecked) and group - if ( QgsProject::instance()->mapLayer( layerId ) ) - { - QgsDataProvider::ProviderOptions options; - QgsMapLayer *mapLayer = QgsProject::instance()->mapLayer( layerId ); - if ( mapLayer ) - { - mapLayer->setDataSource( datasource, name, provider, options ); - dataSourceChanged = mapLayer->isValid(); - } - } - - // If the data source was changed successfully, remove the bad layer from the dialog - // otherwise, try to set the new datasource in the XML node and reload the layer, - // finally marks with red all remaining bad layers. - if ( dataSourceChanged ) - { - mLayerList->removeRow( i-- ); - } - else - { - node.namedItem( QStringLiteral( "datasource" ) ).toElement().firstChild().toText().setData( datasource ); - if ( QgsProject::instance()->readLayer( node ) ) - { - mLayerList->removeRow( i-- ); - } - else - { - item->setForeground( QBrush( Qt::red ) ); - } - } - } - - // Final cleanup: remove any bad layer (it should not be any btw) - if ( mLayerList->rowCount() == 0 ) - { - QList<QgsMapLayer *> toRemove; - const auto mapLayers = QgsProject::instance()->mapLayers(); - for ( const auto &l : mapLayers ) - { - if ( ! l->isValid() ) - toRemove << l; - } - QgsProject::instance()->removeMapLayers( toRemove ); - accept(); - } - - QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false ); - -} - -void QgsHandleBadLayers::accept() -{ - - if ( mLayerList->rowCount() > 0 && - QMessageBox::warning( this, - tr( "Unhandled layer will be lost." ), - tr( "There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again.", - "unhandled layers", - mLayerList->rowCount() ), - QMessageBox::Ok | QMessageBox::Cancel, - QMessageBox::Cancel ) == QMessageBox::Cancel ) - { - return; - } - QList<QgsMapLayer *> toRemove; - for ( const auto &l : QgsProject::instance()->mapLayers( ) ) - { - if ( ! l->isValid() ) - toRemove << l; - } - QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true ); - QgsProject::instance()->removeMapLayers( toRemove ); - QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false ); - mLayerList->clear(); - - QDialog::accept(); -} - -int QgsHandleBadLayers::layerCount() -{ - return mLayerList->rowCount(); -} +/*************************************************************************** + qgshandlebadlayers.cpp - description + ------------------- + begin : Sat 5 Mar 2011 + copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "qgshandlebadlayers.h" +#include "qgisapp.h" +#include "qgsauthconfigselect.h" +#include "qgsdataprovider.h" +#include "qgsguiutils.h" +#include "qgsdatasourceuri.h" +#include "qgslogger.h" +#include "qgsrasterlayer.h" +#include "qgsproviderregistry.h" +#include "qgsmessagebar.h" +#include "qgssettings.h" +#include "qgslayertreeregistrybridge.h" +#include "qgsapplication.h" + +#include <QDomDocument> +#include <QDomElement> +#include <QFileDialog> +#include <QPushButton> +#include <QToolButton> +#include <QMessageBox> +#include <QDialogButtonBox> +#include <QUrl> + +void QgsHandleBadLayersHandler::handleBadLayers( const QList<QDomNode> &layers ) +{ + QApplication::setOverrideCursor( Qt::ArrowCursor ); + QgsHandleBadLayers *dialog = new QgsHandleBadLayers( layers ); + + dialog->buttonBox->button( QDialogButtonBox::Ignore )->setToolTip( tr( "Import all unavailable layers unmodified (you can fix them later)." ) ); + dialog->buttonBox->button( QDialogButtonBox::Ignore )->setText( tr( "Keep Unavailable Layers" ) ); + dialog->buttonBox->button( QDialogButtonBox::Discard )->setToolTip( tr( "Remove all unavailable layers from the project" ) ); + dialog->buttonBox->button( QDialogButtonBox::Discard )->setText( tr( "Remove Unavailable Layers" ) ); + dialog->buttonBox->button( QDialogButtonBox::Discard )->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionDeleteSelected.svg" ) ) ); + + if ( dialog->layerCount() < layers.size() ) + QgisApp::instance()->messageBar()->pushMessage( + tr( "Handle unavailable layers" ), + tr( "%1 of %2 unavailable layers were not fixable." ) + .arg( layers.size() - dialog->layerCount() ) + .arg( layers.size() ), + Qgis::Warning, QgisApp::instance()->messageTimeout() ); + + if ( dialog->layerCount() > 0 ) + { + if ( dialog->exec() == dialog->Accepted ) + { + emit layersChanged(); + } + } + + delete dialog; + QApplication::restoreOverrideCursor(); +} + + +QgsHandleBadLayers::QgsHandleBadLayers( const QList<QDomNode> &layers ) + : QDialog( QgisApp::instance() ) + , mLayers( layers ) +{ + setupUi( this ); + + mVectorFileFilter = QgsProviderRegistry::instance()->fileVectorFilters(); + mRasterFileFilter = QgsProviderRegistry::instance()->fileRasterFilters(); + + mBrowseButton = new QPushButton( tr( "Browse" ) ); + buttonBox->addButton( mBrowseButton, QDialogButtonBox::ActionRole ); + mBrowseButton->setDisabled( true ); + mApplyButton = new QPushButton( tr( "Apply Changes" ) ); + mApplyButton->setToolTip( tr( "Apply fixes to unavailable layers (remaining unavailable layers will be removed from the project)." ) ); + buttonBox->addButton( mApplyButton, QDialogButtonBox::ActionRole ); + + connect( mLayerList, &QTableWidget::itemSelectionChanged, this, &QgsHandleBadLayers::selectionChanged ); + connect( mBrowseButton, &QAbstractButton::clicked, this, &QgsHandleBadLayers::browseClicked ); + connect( mApplyButton, &QAbstractButton::clicked, this, &QgsHandleBadLayers::apply ); + connect( buttonBox->button( QDialogButtonBox::Ignore ), &QPushButton::clicked, this, &QgsHandleBadLayers::reject ); + connect( buttonBox->button( QDialogButtonBox::Discard ), &QPushButton::clicked, this, &QgsHandleBadLayers::accept ); + + mLayerList->clear(); + mLayerList->setSortingEnabled( true ); + mLayerList->setSelectionBehavior( QAbstractItemView::SelectRows ); + mLayerList->setColumnCount( 5 ); + mLayerList->setColumnWidth( 3, 75 ); + + mLayerList->setHorizontalHeaderLabels( QStringList() + << tr( "Layer name" ) + << tr( "Type" ) + << tr( "Provider" ) + << tr( "Auth config" ) + << tr( "Datasource" ) + ); + + int j = 0; + for ( int i = 0; i < mLayers.size(); i++ ) + { + const QDomNode &node = mLayers[i]; + + QString name = node.namedItem( QStringLiteral( "layername" ) ).toElement().text(); + QString type = node.toElement().attribute( QStringLiteral( "type" ) ); + QString datasource = node.namedItem( QStringLiteral( "datasource" ) ).toElement().text(); + QString provider = node.namedItem( QStringLiteral( "provider" ) ).toElement().text(); + QString vectorProvider = type == QLatin1String( "vector" ) ? provider : tr( "none" ); + bool providerFileBased = ( QgsProviderRegistry::instance()->providerCapabilities( provider ) & QgsDataProvider::File ) != 0; + + mFileBase[name].append( const datasource.left( datasource.lastIndexOf('/') ) ); + + QgsDebugMsg( QStringLiteral( "name=%1 type=%2 provider=%3 datasource='%4'" ) + .arg( name, + type, + vectorProvider, + datasource ) ); + + mLayerList->setRowCount( j + 1 ); + + QTableWidgetItem *item = nullptr; + + item = new QTableWidgetItem( name ); + item->setData( Qt::UserRole + 0, i ); + item->setFlags( item->flags() & ~Qt::ItemIsEditable ); + mLayerList->setItem( j, 0, item ); + + item = new QTableWidgetItem( type ); + item->setData( Qt::UserRole + 0, providerFileBased ); + item->setFlags( item->flags() & ~Qt::ItemIsEditable ); + mLayerList->setItem( j, 1, item ); + + item = new QTableWidgetItem( vectorProvider ); + item->setFlags( item->flags() & ~Qt::ItemIsEditable ); + mLayerList->setItem( j, 2, item ); + + if ( QgsAuthConfigUriEdit::hasConfigId( datasource ) ) + { + QToolButton *btn = new QToolButton( this ); + btn->setMaximumWidth( 75 ); + btn->setMinimumHeight( 24 ); + btn->setText( tr( "Edit" ) ); + btn->setProperty( "row", j ); + connect( btn, &QAbstractButton::clicked, this, &QgsHandleBadLayers::editAuthCfg ); + mLayerList->setCellWidget( j, 3, btn ); + } + else + { + item = new QTableWidgetItem( QString() ); + mLayerList->setItem( j, 3, item ); + } + + item = new QTableWidgetItem( datasource ); + mLayerList->setItem( j, 4, item ); + + j++; + } + + // mLayerList->resizeColumnsToContents(); +} + +void QgsHandleBadLayers::selectionChanged() +{ + + mRows.clear(); + + Q_FOREACH ( QTableWidgetItem *item, mLayerList->selectedItems() ) + { + if ( item->column() != 0 ) + continue; + + bool providerFileBased = mLayerList->item( item->row(), 1 )->data( Qt::UserRole + 0 ).toBool(); + if ( !providerFileBased ) + continue; + + mRows << item->row(); + } + + mBrowseButton->setEnabled( !mRows.isEmpty() ); +} + +QString QgsHandleBadLayers::filename( int row ) +{ + QString type = mLayerList->item( row, 1 )->text(); + QString provider = mLayerList->item( row, 2 )->text(); + QString datasource = mLayerList->item( row, 4 )->text(); + + if ( type == QLatin1String( "vector" ) ) + { + const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( provider, datasource ); + // if parts is empty then provider doesn't handle this method! + return parts.empty() ? datasource : parts.value( QStringLiteral( "path" ) ).toString(); + } + else + { + return datasource; + } +} + +void QgsHandleBadLayers::setFilename( int row, const QString &filename ) +{ + if ( !QFileInfo::exists( filename ) ) + return; + + QString type = mLayerList->item( row, 1 )->text(); + QString provider = mLayerList->item( row, 2 )->text(); + QTableWidgetItem *item = mLayerList->item( row, 4 ); + + QString datasource = item->text(); + + if ( type == QLatin1String( "vector" ) ) + { + if ( provider == QLatin1String( "spatialite" ) ) + { + QgsDataSourceUri uri( datasource ); + uri.setDatabase( filename ); + datasource = uri.uri(); + } + else if ( provider == QLatin1String( "ogr" ) ) + { + QStringList theURIParts = datasource.split( '|' ); + theURIParts[0] = filename; + datasource = theURIParts.join( QStringLiteral( "|" ) ); + } + else if ( provider == QLatin1String( "delimitedtext" ) ) + { + QUrl uriSource = QUrl::fromEncoded( datasource.toLatin1() ); + QUrl uriDest = QUrl::fromLocalFile( filename ); + uriDest.setQueryItems( uriSource.queryItems() ); + datasource = QString::fromLatin1( uriDest.toEncoded() ); + } + } + else + { + datasource = filename; + } + + item->setText( datasource ); +} + +void QgsHandleBadLayers::browseClicked() +{ + + if ( mRows.size() == 1 ) + { + int row = mRows.at( 0 ); + QString type = mLayerList->item( row, 1 )->text(); + + QString memoryQualifier, fileFilter; + if ( type == QLatin1String( "vector" ) ) + { + memoryQualifier = QStringLiteral( "lastVectorFileFilter" ); + fileFilter = mVectorFileFilter; + } + else + { + memoryQualifier = QStringLiteral( "lastRasterFileFilter" ); + fileFilter = mRasterFileFilter; + } + + QString fn = filename( row ); + if ( fn.isNull() ) + return; + + QStringList selectedFiles; + QString enc; + QString title = tr( "Select File to Replace '%1'" ).arg( fn ); + + QgsGuiUtils::openFilesRememberingFilter( memoryQualifier, fileFilter, selectedFiles, enc, title ); + if ( selectedFiles.size() != 1 ) + { + QMessageBox::information( this, title, tr( "Please select exactly one file." ) ); + return; + } + + setFilename( row, selectedFiles[0] ); + } + else if ( mRows.size() > 1 ) + { + QString title = tr( "Select New Directory of Selected Files" ); + + QgsSettings settings; + QString lastDir = settings.value( QStringLiteral( "UI/missingDirectory" ), QDir::homePath() ).toString(); + QString selectedFolder = QFileDialog::getExistingDirectory( this, title, lastDir ); + if ( selectedFolder.isEmpty() ) + { + return; + } + + QDir dir( selectedFolder ); + if ( !dir.exists() ) + { + return; + } + + Q_FOREACH ( int row, mRows ) + { + bool providerFileBased = mLayerList->item( row, 1 )->data( Qt::UserRole + 0 ).toBool(); + if ( !providerFileBased ) + continue; + + QString fn = filename( row ); + if ( fn.isEmpty() ) + continue; + + QFileInfo fi( fn ); + fi.setFile( dir, fi.fileName() ); + if ( !fi.exists() ) + continue; + + setFilename( row, fi.absoluteFilePath() ); + } + } +} + +void QgsHandleBadLayers::editAuthCfg() +{ + QToolButton *btn = qobject_cast<QToolButton *>( sender() ); + int row = -1; + for ( int i = 0; i < mLayerList->rowCount(); i++ ) + { + if ( mLayerList->cellWidget( i, 3 ) == btn ) + { + row = i; + break; + } + } + + if ( row == -1 ) + return; + + QString provider = mLayerList->item( row, 2 )->text(); + if ( provider == QLatin1String( "none" ) ) + provider.clear(); + + QString prevuri = mLayerList->item( row, 4 )->text(); + + QgsAuthConfigUriEdit *dlg = new QgsAuthConfigUriEdit( this, prevuri, provider ); + dlg->setWindowModality( Qt::WindowModal ); + dlg->resize( 500, 500 ); + if ( dlg->exec() ) + { + QString newuri( dlg->dataSourceUri() ); + if ( newuri != prevuri ) + { + mLayerList->item( row, 4 )->setText( newuri ); + } + } + dlg->deleteLater(); +} + +void QgsHandleBadLayers::apply() +{ + QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true ); + buttonBox->button( QDialogButtonBox::Ignore )->setEnabled( false ); + QHash<QString, QString> baseChange; + + for ( int i = 0; i < mLayerList->rowCount(); i++ ) + { + int idx = mLayerList->item( i, 0 )->data( Qt::UserRole ).toInt(); + QDomNode &node = const_cast<QDomNode &>( mLayers[ idx ] ); + + QTableWidgetItem *item = mLayerList->item( i, 4 ); + QString datasource = item->text(); + const QString basepath = datasource.left( datasource.lastIndexOf('/') ) + bool changed = false; + + if ( mFileBase[ name ].size() == 1 ) + { + if ( mFileBase[ name ][0] != basepath && !baseChange.contains( mFileBase[ name ][0] ) ) + { + baseChange[ mFileBase[ name ][0] ] = basepath; + changed = true; + } + } + else if ( mFileBase[ name ].size() > 1 ) + { + if ( mFileBase[ name ].indexOf( basepath ) == -1 ) + { + const QList fileBases = mFileBase[ name ]; + for ( QString fileBase : fileBases ) + { + if ( !baseChange.contains( fileBase ) ) + { + baseChange[ fileBase ] = basepath; + changed = true; + } + } + } + } + if ( !changed && baseChange.contains( basepath ) ) + datasource = datasource.replace( basepath, baseChange( basepath ) ); + + + bool dataSourceChanged { false }; + const QString layerId { node.namedItem( QStringLiteral( "id" ) ).toElement().text() }; + const QString provider { node.namedItem( QStringLiteral( "provider" ) ).toElement().text() }; + const QString name { mLayerList->item( i, 0 )->text() }; + + // Try first to change the datasource of the existing layers, this will + // maintain the current status (checked/unchecked) and group + if ( QgsProject::instance()->mapLayer( layerId ) ) + { + QgsDataProvider::ProviderOptions options; + QgsMapLayer *mapLayer = QgsProject::instance()->mapLayer( layerId ); + if ( mapLayer ) + { + mapLayer->setDataSource( datasource, name, provider, options ); + dataSourceChanged = mapLayer->isValid(); + } + } + + // If the data source was changed successfully, remove the bad layer from the dialog + // otherwise, try to set the new datasource in the XML node and reload the layer, + // finally marks with red all remaining bad layers. + if ( dataSourceChanged ) + { + mLayerList->removeRow( i-- ); + } + else + { + node.namedItem( QStringLiteral( "datasource" ) ).toElement().firstChild().toText().setData( datasource ); + if ( QgsProject::instance()->readLayer( node ) ) + { + mLayerList->removeRow( i-- ); + } + else + { + item->setForeground( QBrush( Qt::red ) ); + } + } + } + + // Final cleanup: remove any bad layer (it should not be any btw) + if ( mLayerList->rowCount() == 0 ) + { + QList<QgsMapLayer *> toRemove; + const auto mapLayers = QgsProject::instance()->mapLayers(); + for ( const auto &l : mapLayers ) + { + if ( ! l->isValid() ) + toRemove << l; + } + QgsProject::instance()->removeMapLayers( toRemove ); + accept(); + } + + QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false ); + +} + +void QgsHandleBadLayers::accept() +{ + + if ( mLayerList->rowCount() > 0 && + QMessageBox::warning( this, + tr( "Unhandled layer will be lost." ), + tr( "There are still %n unhandled layer(s). If they are not fixed, they will be disabled/deactivated until the project is opened again.", + "unhandled layers", + mLayerList->rowCount() ), + QMessageBox::Ok | QMessageBox::Cancel, + QMessageBox::Cancel ) == QMessageBox::Cancel ) + { + return; + } + QList<QgsMapLayer *> toRemove; + for ( const auto &l : QgsProject::instance()->mapLayers( ) ) + { + if ( ! l->isValid() ) + toRemove << l; + } + QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( true ); + QgsProject::instance()->removeMapLayers( toRemove ); + QgsProject::instance()->layerTreeRegistryBridge()->setEnabled( false ); + mLayerList->clear(); + + QDialog::accept(); +} + +int QgsHandleBadLayers::layerCount() +{ + return mLayerList->rowCount(); +} \ No newline at end of file diff --git a/src/app/qgshandlebadlayers.h b/src/app/qgshandlebadlayers.h index 796a8722ab7..21838c4891c 100644 --- a/src/app/qgshandlebadlayers.h +++ b/src/app/qgshandlebadlayers.h @@ -1,79 +1,80 @@ -/*************************************************************************** - qgshandlebadlayers.h - description - ------------------- - begin : Sat 05 Mar 2011 - copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH - email : jef at norbit dot de - ***************************************************************************/ - -/*************************************************************************** - * * - * 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 QGSHANDLEBADLAYERS_H -#define QGSHANDLEBADLAYERS_H - -#include "ui_qgshandlebadlayersbase.h" -#include "qgsprojectbadlayerhandler.h" -#include "qgis_app.h" - -class APP_EXPORT QgsHandleBadLayersHandler - : public QObject - , public QgsProjectBadLayerHandler -{ - Q_OBJECT - - public: - QgsHandleBadLayersHandler() = default; - - //! Implementation of the handler - void handleBadLayers( const QList<QDomNode> &layers ) override; - - signals: - - /** - * Emitted when layers have changed - * \since QGIS 3.6 - */ - void layersChanged(); - -}; - - -class QPushButton; - -class APP_EXPORT QgsHandleBadLayers - : public QDialog - , public Ui::QgsHandleBadLayersBase -{ - Q_OBJECT - - public: - QgsHandleBadLayers( const QList<QDomNode> &layers ); - - int layerCount(); - - private slots: - void selectionChanged(); - void browseClicked(); - void editAuthCfg(); - void apply(); - void accept() override; - - private: - QPushButton *mBrowseButton = nullptr; - QPushButton *mApplyButton = nullptr; - const QList<QDomNode> &mLayers; - QList<int> mRows; - QString mVectorFileFilter; - QString mRasterFileFilter; - - QString filename( int row ); - void setFilename( int row, const QString &filename ); -}; - -#endif +/*************************************************************************** + qgshandlebadlayers.h - description + ------------------- + begin : Sat 05 Mar 2011 + copyright : (C) 2011 by Juergen E. Fischer, norBIT GmbH + email : jef at norbit dot de + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 QGSHANDLEBADLAYERS_H +#define QGSHANDLEBADLAYERS_H + +#include "ui_qgshandlebadlayersbase.h" +#include "qgsprojectbadlayerhandler.h" +#include "qgis_app.h" + +class APP_EXPORT QgsHandleBadLayersHandler + : public QObject + , public QgsProjectBadLayerHandler +{ + Q_OBJECT + + public: + QgsHandleBadLayersHandler() = default; + + //! Implementation of the handler + void handleBadLayers( const QList<QDomNode> &layers ) override; + + signals: + + /** + * Emitted when layers have changed + * \since QGIS 3.6 + */ + void layersChanged(); + +}; + + +class QPushButton; + +class APP_EXPORT QgsHandleBadLayers + : public QDialog + , public Ui::QgsHandleBadLayersBase +{ + Q_OBJECT + + public: + QgsHandleBadLayers( const QList<QDomNode> &layers ); + + int layerCount(); + + private slots: + void selectionChanged(); + void browseClicked(); + void editAuthCfg(); + void apply(); + void accept() override; + + private: + QPushButton *mBrowseButton = nullptr; + QPushButton *mApplyButton = nullptr; + const QList<QDomNode> &mLayers; + QList<int> mRows; + QString mVectorFileFilter; + QString mRasterFileFilter; + QHash <QString, QList<QString> > mFileBase; + + QString filename( int row ); + void setFilename( int row, const QString &filename ); +}; + +#endif \ No newline at end of file