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