[QGIS Commit] r12350 - in trunk/qgis: python/core python/gui src/app src/app/ogr src/core src/gui

svn_qgis at osgeo.org svn_qgis at osgeo.org
Mon Dec 7 12:14:42 EST 2009


Author: wonder
Date: 2009-12-07 12:14:41 -0500 (Mon, 07 Dec 2009)
New Revision: 12350

Added:
   trunk/qgis/python/gui/qgsprojectbadlayerguihandler.sip
   trunk/qgis/src/gui/qgsprojectbadlayerguihandler.cpp
   trunk/qgis/src/gui/qgsprojectbadlayerguihandler.h
Removed:
   trunk/qgis/src/core/qgsexception.cpp
Modified:
   trunk/qgis/python/core/qgsproject.sip
   trunk/qgis/python/gui/gui.sip
   trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.cpp
   trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.h
   trunk/qgis/src/app/qgisapp.cpp
   trunk/qgis/src/core/CMakeLists.txt
   trunk/qgis/src/core/qgsexception.h
   trunk/qgis/src/core/qgsproject.cpp
   trunk/qgis/src/core/qgsproject.h
   trunk/qgis/src/gui/CMakeLists.txt
   trunk/qgis/src/gui/qgisgui.cpp
   trunk/qgis/src/gui/qgisgui.h
Log:
Change in handling of missing layers within QgsProject. Instead of throwing an exception, now a custom handler is called
that might try to fix the missing layers. There's a default handler (QgsProjectBadLayerDefaultHandler) which simply ignores
all missing layers. Then there's a GUI handler (QgsProjectBadLayerGuiHandler) in GUI library which asks user about
the path for missing layers. QGIS application automatically installs the GUI handler on startup.

This should allow python plugins/applications to work with QgsProject without a fear of a segfault as there are no more
exceptions thrown during load/save of the project files.

Some further notes:
- removed QgsProjectBadLayerException class and (now empty) qgsexception.cpp file
- openFilesRememberingFilter() moved to QgisGui namespace (was duplicated: QgisApp vs QgsOpenVectorLayerDialog)
- removed deprecated buildVectorFilters_ methods
- added python bindings for new classes/methods


Modified: trunk/qgis/python/core/qgsproject.sip
===================================================================
--- trunk/qgis/python/core/qgsproject.sip	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/python/core/qgsproject.sip	2009-12-07 17:14:41 UTC (rev 12350)
@@ -235,6 +235,11 @@
       @note added in 1.4 */
     QString error() const;
 
+    /** Change handler for missing layers.
+      Deletes old handler and takes ownership of the new one.
+      @note added in 1.4 */
+    void setBadLayerHandler( QgsProjectBadLayerHandler* handler );
+
   protected:
 
     /** Set error message from read/write operation
@@ -259,3 +264,30 @@
 
 }; // QgsProject
 
+
+/** Interface for classes that handle missing layer files when reading project file.
+  @note added in 1.4 */
+class QgsProjectBadLayerHandler
+{
+%TypeHeaderCode
+#include <qgsproject.h>
+%End
+
+public:
+  virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom ) = 0;
+  virtual ~QgsProjectBadLayerHandler();
+};
+
+
+/** Default bad layer handler which ignores any missing layers.
+  @note added in 1.4 */
+class QgsProjectBadLayerDefaultHandler : QgsProjectBadLayerHandler
+{
+%TypeHeaderCode
+#include <qgsproject.h>
+%End
+
+public:
+  virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom );
+
+};

Modified: trunk/qgis/python/gui/gui.sip
===================================================================
--- trunk/qgis/python/gui/gui.sip	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/python/gui/gui.sip	2009-12-07 17:14:41 UTC (rev 12350)
@@ -22,6 +22,7 @@
 %Include qgsmaptoolzoom.sip
 %Include qgsmapoverviewcanvas.sip
 %Include qgsmessageviewer.sip
+%Include qgsprojectbadlayerguihandler.sip
 %Include qgsprojectionselector.sip
 %Include qgsquickprint.sip
 %Include qgsrubberband.sip

Added: trunk/qgis/python/gui/qgsprojectbadlayerguihandler.sip
===================================================================
--- trunk/qgis/python/gui/qgsprojectbadlayerguihandler.sip	                        (rev 0)
+++ trunk/qgis/python/gui/qgsprojectbadlayerguihandler.sip	2009-12-07 17:14:41 UTC (rev 12350)
@@ -0,0 +1,21 @@
+
+/** \ingroup gui
+  Handler for missing layers within project.
+
+  Gives user a chance to select path to the missing layers.
+
+  @note added in 1.4
+ */
+class QgsProjectBadLayerGuiHandler : QObject, QgsProjectBadLayerHandler
+{
+%TypeHeaderCode
+#include <qgsprojectbadlayerguihandler.h>
+%End
+
+  public:
+    QgsProjectBadLayerGuiHandler();
+
+    /** implementation of the handler */
+    virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom );
+
+};

Modified: trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.cpp
===================================================================
--- trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.cpp	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -97,11 +97,11 @@
 
   QStringList selectedFiles;
   QgsDebugMsg( "Vector file filters: " + mVectorFileFilter );
-  QString enc;
+  QString enc = encoding();
   QString title = tr( "Open an OGR Supported Vector Layer" );
-  openFilesRememberingFilter( "lastVectorFileFilter", mVectorFileFilter, selectedFiles,
-                              title );
-  mEnc = enc;
+  QgisGui::openFilesRememberingFilter( "lastVectorFileFilter", mVectorFileFilter, selectedFiles, enc,
+                                       title );
+
   return selectedFiles;
 }
 
@@ -306,70 +306,8 @@
 
 }
 
-/**
-  Open files, preferring to have the default file selector be the
-  last one used, if any; also, prefer to start in the last directory
-  associated with filterName.
 
-  @param filterName the name of the filter; used for persistent store
-  key
-  @param filters    the file filters used for QFileDialog
 
-  @param selectedFiles string list of selected files; will be empty
-  if none selected
-  @param title      the title for the dialog
-  @note
-
-  Stores persistent settings under /UI/.  The sub-keys will be
-  filterName and filterName + "Dir".
-
-  Opens dialog on last directory associated with the filter name, or
-  the current working directory if this is the first time invoked
-  with the current filter name.
-
-*/
-void QgsOpenVectorLayerDialog::openFilesRememberingFilter( QString const &filterName,
-    QString const &filters, QStringList & selectedFiles, QString &title )
-{
-
-  bool haveLastUsedFilter = false; // by default, there is no last
-  // used filter
-
-  QSettings settings;         // where we keep last used filter in
-
-  // persistant state
-
-  haveLastUsedFilter = settings.contains( "/UI/" + filterName );
-  QString lastUsedFilter = settings.value( "/UI/" + filterName,
-                           QVariant( QString::null ) ).toString();
-
-  QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", "." ).toString();
-  QgsDebugMsg( "Opening file dialog with filters: " + filters );
-
-  if ( haveLastUsedFilter )
-  {
-    selectedFiles = QFileDialog::getOpenFileNames( 0, title, lastUsedDir, filters, &lastUsedFilter );
-  }
-  else
-  {
-    selectedFiles = QFileDialog::getOpenFileNames( 0, title, lastUsedDir, filters );
-  }
-
-  if ( !selectedFiles.isEmpty() )
-  {
-    QString myFirstFileName = selectedFiles.first();
-    QFileInfo myFI( myFirstFileName );
-    QString myPath = myFI.path();
-
-    QgsDebugMsg( "Writing last used dir: " + myPath );
-
-    settings.setValue( "/UI/" + filterName, lastUsedFilter );
-    settings.setValue( "/UI/" + filterName + "Dir", myPath );
-  }
-}   // openFilesRememberingFilter_
-
-
-
 //********************auto connected slots *****************/
 void QgsOpenVectorLayerDialog::on_buttonBox_accepted()
 {

Modified: trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.h
===================================================================
--- trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.h	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/app/ogr/qgsopenvectorlayerdialog.h	2009-12-07 17:14:41 UTC (rev 12350)
@@ -46,9 +46,6 @@
     //! Returns the connection type
     QString dataSourceType();
   private:
-    //! Shows a dialog remembering the last directory and filter selected */
-    void openFilesRememberingFilter( QString const &filterName,
-                                     QString const &filters, QStringList & selectedFiles, QString &title );
     //! Stores the file vector filters */
     QString mVectorFileFilter;
     //! Stores the selected datasources */

Modified: trunk/qgis/src/app/qgisapp.cpp
===================================================================
--- trunk/qgis/src/app/qgisapp.cpp	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/app/qgisapp.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -126,6 +126,7 @@
 #include "qgspluginregistry.h"
 #include "qgspoint.h"
 #include "qgsproject.h"
+#include "qgsprojectbadlayerguihandler.h"
 #include "qgsprojectproperties.h"
 #include "qgsproviderregistry.h"
 #include "qgsrasterlayer.h"
@@ -211,13 +212,7 @@
 const int AFTER_RECENT_PATHS = 321;
 
 
-/// build the vector file filter string for a QFileDialog
-/*
-   called in ctor for initializing mVectorFileFilter
-   */
-static void buildSupportedVectorFileFilter_( QString & fileFilters );
 
-
 /** set the application title bar text
 
   If the current project title is null
@@ -426,10 +421,13 @@
   mSplash->showMessage( tr( "Initializing file filters" ), Qt::AlignHCenter | Qt::AlignBottom );
   qApp->processEvents();
   // now build vector file filter
-  buildSupportedVectorFileFilter_( mVectorFileFilter );
-
+  mVectorFileFilter = QgsProviderRegistry::instance()->fileVectorFilters();
   // now build raster file filter
   QgsRasterLayer::buildSupportedRasterFileFilter( mRasterFileFilter );
+
+  // set handler for missing layers (will be owned by QgsProject)
+  QgsProject::instance()->setBadLayerHandler( new QgsProjectBadLayerGuiHandler() );
+
 #if 0
   // Set the background colour for toolbox and overview as they default to
   // white instead of the window color
@@ -2203,238 +2201,8 @@
 
 
 
-/**
-  Builds the list of file filter strings to later be used by
-  QgisApp::addVectorLayer()
 
-  We query OGR for a list of supported vector formats; we then build a list
-  of file filter strings from that list.  We return a string that contains
-  this list that is suitable for use in a a QFileDialog::getOpenFileNames()
-  call.
-
-  XXX Most of the file name filters need to be filled in; however we
-  XXX may want to wait until we've tested each format before committing
-  XXX them permanently instead of blindly relying on OGR to properly
-  XXX supply all needed spatial data.
-
-*/
-static void buildSupportedVectorFileFilter_( QString & fileFilters )
-{
-
-#ifdef DEPRECATED
-  static QString myFileFilters;
-
-  // if we've already built the supported vector string, just return what
-  // we've already built
-  if ( !( myFileFilters.isEmpty() || myFileFilters.isNull() ) )
-  {
-    fileFilters = myFileFilters;
-
-    return;
-  }
-
-  // then iterate through all of the supported drivers, adding the
-  // corresponding file filter
-
-  OGRSFDriverH driver;          // current driver
-
-  QString driverName;           // current driver name
-
-  // Grind through all the drivers and their respective metadata.
-  // We'll add a file filter for those drivers that have a file
-  // extension defined for them; the others, welll, even though
-  // theoreticaly we can open those files because there exists a
-  // driver for them, the user will have to use the "All Files" to
-  // open datasets with no explicitly defined file name extension.
-  QgsDebugMsg( "Driver count: " + QString::number( driverRegistrar->GetDriverCount() ) );
-
-  for ( int i = 0; i < OGRGetDriverCount(); ++i )
-  {
-    driver = OGRGetDriver( i );
-
-    Q_CHECK_PTR( driver );
-
-    if ( !driver )
-    {
-      QgsDebugMsg( QString( "unable to get driver %1" ).arg( i ) );
-      continue;
-    }
-
-    driverName = OGR_Dr_GetName( driver );
-
-    if ( driverName.startsWith( "ESRI" ) )
-    {
-      myFileFilters += createFileFilter_( "ESRI Shapefiles", "*.shp" );
-    }
-    else if ( driverName.startsWith( "UK" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "SDTS" ) )
-    {
-      myFileFilters += createFileFilter_( "Spatial Data Transfer Standard",
-                                          "*catd.ddf" );
-    }
-    else if ( driverName.startsWith( "TIGER" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "S57" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "MapInfo" ) )
-    {
-      myFileFilters += createFileFilter_( "MapInfo", "*.mif *.tab" );
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "DGN" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "VRT" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "AVCBin" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "REC" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "Memory" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "Jis" ) )
-    {
-      // XXX needs file filter extension
-    }
-    else if ( driverName.startsWith( "GML" ) )
-    {
-      // XXX not yet supported; post 0.1 release task
-      myFileFilters += createFileFilter_( "Geography Markup Language",
-                                          "*.gml" );
-    }
-    else
-    {
-      // NOP, we don't know anything about the current driver
-      // with regards to a proper file filter string
-      QgsDebugMsg( "unknown driver " + driverName );
-    }
-
-  }                           // each loaded GDAL driver
-
-  QgsDebugMsg( myFileFilters );
-
-  // can't forget the default case
-
-  myFileFilters += "All files (*.*)";
-  fileFilters = myFileFilters;
-
-#endif // DEPRECATED
-
-  fileFilters = QgsProviderRegistry::instance()->fileVectorFilters();
-  //QgsDebugMsg("Vector file filters: " + fileFilters);
-
-}                               // buildSupportedVectorFileFilter_()
-
-
-
-
 /**
-  Open files, preferring to have the default file selector be the
-  last one used, if any; also, prefer to start in the last directory
-  associated with filterName.
-
-  @param filterName the name of the filter; used for persistent store
-  key
-  @param filters    the file filters used for QFileDialog
-
-  @param selectedFiles string list of selected files; will be empty
-  if none selected
-  @param enc        encoding?
-  @param title      the title for the dialog
-  @note
-
-  Stores persistent settings under /UI/.  The sub-keys will be
-  filterName and filterName + "Dir".
-
-  Opens dialog on last directory associated with the filter name, or
-  the current working directory if this is the first time invoked
-  with the current filter name.
-
-  This method returns true if cancel all was clicked, otherwise false
-
-*/
-
-static bool openFilesRememberingFilter_( QString const &filterName,
-    QString const &filters, QStringList & selectedFiles, QString& enc, QString &title,
-    bool cancelAll = false )
-{
-
-  bool haveLastUsedFilter = false; // by default, there is no last
-  // used filter
-
-  QSettings settings;         // where we keep last used filter in
-  // persistant state
-
-  haveLastUsedFilter = settings.contains( "/UI/" + filterName );
-  QString lastUsedFilter = settings.value( "/UI/" + filterName,
-                           QVariant( QString::null ) ).toString();
-
-  QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", "." ).toString();
-
-  QgsDebugMsg( "Opening file dialog with filters: " + filters );
-  if ( !cancelAll )
-  {
-    selectedFiles = QFileDialog::getOpenFileNames( 0, title, lastUsedDir, filters, &lastUsedFilter );
-  }
-  else //we have to use non-native dialog to add cancel all button
-  {
-    QgsEncodingFileDialog* openFileDialog = new QgsEncodingFileDialog( 0, title, lastUsedDir, filters, QString( "" ) );
-    // allow for selection of more than one file
-    openFileDialog->setFileMode( QFileDialog::ExistingFiles );
-    if ( haveLastUsedFilter )     // set the filter to the last one used
-    {
-      openFileDialog->selectFilter( lastUsedFilter );
-    }
-    openFileDialog->addCancelAll();
-    if ( openFileDialog->exec() == QDialog::Accepted )
-    {
-      selectedFiles = openFileDialog->selectedFiles();
-    }
-    else
-    {
-      //cancel or cancel all?
-      if ( openFileDialog->cancelAll() )
-      {
-        return true;
-      }
-    }
-  }
-
-  if ( !selectedFiles.isEmpty() )
-  {
-    // Fix by Tim - getting the dirPath from the dialog
-    // directly truncates the last node in the dir path.
-    // This is a workaround for that
-    QString myFirstFileName = selectedFiles.first();
-    QFileInfo myFI( myFirstFileName );
-    QString myPath = myFI.path();
-
-    QgsDebugMsg( "Writing last used dir: " + myPath );
-
-    settings.setValue( "/UI/" + filterName, lastUsedFilter );
-    settings.setValue( "/UI/" + filterName + "Dir", myPath );
-  }
-  return false;
-}   // openFilesRememberingFilter_
-
-
-/**
   This method prompts the user for a list of vector file names  with a dialog.
   */
 void QgisApp::addVectorLayer()
@@ -2819,300 +2587,6 @@
 
 
 
-/// file data representation
-enum dataType { IS_VECTOR, IS_RASTER, IS_BOGUS };
-
-
-
-/** returns data type associated with the given QgsProject file Dom node
-
-  The Dom node should represent the state associated with a specific layer.
-  */
-static
-dataType
-dataType_( QDomNode & layerNode )
-{
-  QString type = layerNode.toElement().attribute( "type" );
-
-  if ( QString::null == type )
-  {
-    QgsDebugMsg( "cannot find ``type'' attribute" );
-
-    return IS_BOGUS;
-  }
-
-  if ( "raster" == type )
-  {
-    QgsDebugMsg( "is a raster" );
-
-    return IS_RASTER;
-  }
-  else if ( "vector" == type )
-  {
-    QgsDebugMsg( "is a vector" );
-
-    return IS_VECTOR;
-  }
-
-  QgsDebugMsg( "is unknown type " + type );
-
-  return IS_BOGUS;
-} // dataType_( QDomNode & layerNode )
-
-
-/** return the data source for the given layer
-
-  The QDomNode is a QgsProject Dom node corresponding to a map layer state.
-
-  Essentially dumps <datasource> tag.
-
-*/
-static
-QString
-dataSource_( QDomNode & layerNode )
-{
-  QDomNode dataSourceNode = layerNode.namedItem( "datasource" );
-
-  if ( dataSourceNode.isNull() )
-  {
-    QgsDebugMsg( "cannot find datasource node" );
-
-    return QString::null;
-  }
-
-  return dataSourceNode.toElement().text();
-
-} // dataSource_( QDomNode & layerNode )
-
-
-
-/// the three flavors for data
-typedef enum { IS_FILE, IS_DATABASE, IS_URL, IS_Unknown } providerType;
-
-
-/** return the physical storage type associated with the given layer
-
-  The QDomNode is a QgsProject Dom node corresponding to a map layer state.
-
-  If the <provider> is "ogr", then it's a file type.
-
-  However, if the layer is a raster, then there won't be a <provider> tag.  It
-  will always have an associated file.
-
-  If the layer doesn't fall into either of the previous two categories, then
-  it's either a database or URL.  If the <datasource> tag has "url=", then
-  it's URL based.  If the <datasource> tag has "dbname=">, then the layer data
-  is in a database.
-
-*/
-static
-providerType
-providerType_( QDomNode & layerNode )
-{
-  // XXX but what about rasters that can be URLs?  _Can_ they be URLs?
-
-  switch ( dataType_( layerNode ) )
-  {
-    case IS_VECTOR:
-    {
-      QString dataSource = dataSource_( layerNode );
-
-      QgsDebugMsg( "datasource is " + dataSource );
-
-      if ( dataSource.contains( "host=" ) )
-      {
-        return IS_URL;
-      }
-#ifdef HAVE_POSTGRESQL
-      else if ( dataSource.contains( "dbname=" ) )
-      {
-        return IS_DATABASE;
-      }
-#endif
-      // be default, then, this should be a file based layer data source
-      // XXX is this a reasonable assumption?
-
-      return IS_FILE;
-    }
-
-    case IS_RASTER:         // rasters are currently only accessed as
-      // physical files
-      return IS_FILE;
-
-    default:
-      QgsDebugMsg( "unknown ``type'' attribute" );
-  }
-
-  return IS_Unknown;
-
-} // providerType_
-
-
-
-/** set the <datasource> to the new value
-*/
-static
-void
-setDataSource_( QDomNode & layerNode, QString const & dataSource )
-{
-  QDomNode dataSourceNode = layerNode.namedItem( "datasource" );
-  QDomElement dataSourceElement = dataSourceNode.toElement();
-  QDomText dataSourceText = dataSourceElement.firstChild().toText();
-
-  QgsDebugMsg( "datasource changed from " + dataSourceText.data() );
-
-  dataSourceText.setData( dataSource );
-
-  QgsDebugMsg( "to " + dataSourceText.data() );
-} // setDataSource_
-
-
-
-
-/** this is used to locate files that have moved or otherwise are missing
-
-*/
-static
-bool
-findMissingFile_( QString const & fileFilters, QDomNode & layerNode )
-{
-  // Prepend that file name to the valid file format filter list since it
-  // makes it easier for the user to not only find the original file, but to
-  // perhaps find a similar file.
-
-  QFileInfo originalDataSource( dataSource_( layerNode ) );
-
-  QString memoryQualifier;    // to differentiate between last raster and
-  // vector directories
-
-  switch ( dataType_( layerNode ) )
-  {
-    case IS_VECTOR:
-    {
-      memoryQualifier = "lastVectorFileFilter";
-
-      break;
-    }
-    case IS_RASTER:
-    {
-      memoryQualifier = "lastRasterFileFilter";
-
-      break;
-    }
-    default:
-      QgsDebugMsg( "unable to determine data type" );
-      return false;
-  }
-
-  // Prepend the original data source base name to make it easier to pick it
-  // out from a list of other files; however the appropriate filter strings
-  // for the file type will also be added in case the file name itself has
-  // changed, too.
-
-  QString myFileFilters = originalDataSource.fileName() + ";;" + fileFilters;
-
-  QStringList selectedFiles;
-  QString enc;
-  QString title = QObject::tr( "Where is '%1' (original location: %2)?" )
-                  .arg( originalDataSource.fileName() )
-                  .arg( originalDataSource.absoluteFilePath() );
-
-  bool retVal = openFilesRememberingFilter_( memoryQualifier,
-                myFileFilters,
-                selectedFiles,
-                enc,
-                title,
-                true );
-
-  if ( selectedFiles.isEmpty() )
-  {
-    return retVal;
-  }
-  else
-  {
-    setDataSource_( layerNode, selectedFiles.first() );
-    if ( ! QgsProject::instance()->read( layerNode ) )
-    {
-      QgsDebugMsg( "unable to re-read layer" );
-    }
-  }
-  return retVal;
-} // findMissingFile_
-
-
-
-
-/** find relocated data source for the given layer
-
-  This QDom object represents a QgsProject node that maps to a specific layer.
-
-  @param layerNode QDom node containing layer project information
-
-  @todo
-
-  XXX Only implemented for file based layers.  It will need to be extended for
-  XXX other data source types such as databases.
-
-*/
-static
-bool
-findLayer_( QString const & fileFilters, QDomNode const & constLayerNode )
-{
-  // XXX actually we could possibly get away with a copy of the node
-  QDomNode & layerNode = const_cast<QDomNode&>( constLayerNode );
-
-  bool retVal = false;
-
-  switch ( providerType_( layerNode ) )
-  {
-    case IS_FILE:
-      QgsDebugMsg( "layer is file based" );
-      retVal = findMissingFile_( fileFilters, layerNode );
-      break;
-
-    case IS_DATABASE:
-      QgsDebugMsg( "layer is database based" );
-      break;
-
-    case IS_URL:
-      QgsDebugMsg( "layer is URL based" );
-      break;
-
-    case IS_Unknown:
-      QgsDebugMsg( "layer has an unkown type" );
-      break;
-  }
-  return retVal;
-} // findLayer_
-
-
-
-
-/** find relocated data sources for given layers
-
-  These QDom objects represent QgsProject nodes that map to specific layers.
-
-*/
-static
-void
-findLayers_( QString const & fileFilters, std::list<QDomNode> const & layerNodes )
-{
-
-  for ( std::list<QDomNode>::const_iterator i = layerNodes.begin();
-        i != layerNodes.end();
-        ++i )
-  {
-    if ( findLayer_( fileFilters, *i ) )
-    {
-      // If findLayer returns true, the user hit Cancel All button
-      break;
-    }
-  }
-
-} // findLayers_
-
-
-
 void QgisApp::fileExit()
 {
   if ( mMapCanvas && mMapCanvas->isDrawing() )
@@ -3366,31 +2840,14 @@
 
     QgsProject::instance()->setFileName( fullPath );
 
-    try
+    if ( ! QgsProject::instance()->read() )
     {
-      if ( ! QgsProject::instance()->read() )
-      {
-        QMessageBox::critical( this,
-                               tr( "QGIS Project Read Error" ),
-                               QgsProject::instance()->error() );
-        mMapCanvas->freeze( false );
-        mMapCanvas->refresh();
-        return;
-      }
-    }
-    catch ( QgsProjectBadLayerException & e )
-    {
       QMessageBox::critical( this,
                              tr( "QGIS Project Read Error" ),
-                             QString::fromLocal8Bit( e.what() ) );
-      QgsDebugMsg( QString( "%1 bad layers found" ).arg( e.layers().size() ) );
-
-      // attempt to find the new locations for missing layers
-      // XXX vector file hard-coded -- but what if it's raster?
-      findLayers_( mVectorFileFilter, e.layers() );
-
-      // Tell the legend to update the ordering
-      mMapLegend->readProject( e.document() );
+                             QgsProject::instance()->error() );
+      mMapCanvas->freeze( false );
+      mMapCanvas->refresh();
+      return;
     }
 
     setTitleBarText_( *this );
@@ -3425,51 +2882,19 @@
   // clear the map canvas
   removeAllLayers();
 
-  try
+  if ( ! QgsProject::instance()->read( projectFile ) )
   {
-    if ( ! QgsProject::instance()->read( projectFile ) )
-    {
-      QMessageBox::critical( this,
-                             tr( "Unable to open project" ),
-                             QgsProject::instance()->error() );
+    QMessageBox::critical( this,
+                           tr( "Unable to open project" ),
+                           QgsProject::instance()->error() );
 
-      QApplication::restoreOverrideCursor();
+    QApplication::restoreOverrideCursor();
 
-      mMapCanvas->freeze( false );
-      mMapCanvas->refresh();
-      return false;
-    }
-    // Continue after last catch statement
-
+    mMapCanvas->freeze( false );
+    mMapCanvas->refresh();
+    return false;
   }
-  catch ( QgsProjectBadLayerException & e )
-  {
-    QgsDebugMsg( QString( "%1 bad layers found" ).arg( e.layers().size() ) );
 
-    if ( QMessageBox::Ok == QMessageBox::critical( this,
-         tr( "QGIS Project Read Error" ),
-         tr( "%1\nTry to find missing layers?" ).arg( QString::fromLocal8Bit( e.what() ) ),
-         QMessageBox::Ok | QMessageBox::Cancel ) )
-    {
-      QgsDebugMsg( "want to find missing layers is true" );
-
-      // attempt to find the new locations for missing layers
-      // XXX vector file hard-coded -- but what if it's raster?
-      QApplication::restoreOverrideCursor();
-
-      findLayers_( mVectorFileFilter, e.layers() );
-
-      QApplication::setOverrideCursor( Qt::WaitCursor );
-
-      // Tell the legend to update the ordering
-      mMapLegend->readProject( e.document() );
-    }
-    // Continue after last catch statement
-
-  }
-
-  // Continue, now with layers found (hopefully)
-
   setTitleBarText_( *this );
   int  myRedInt = QgsProject::instance()->readNumEntry( "Gui", "/CanvasColorRedPart", 255 );
   int  myGreenInt = QgsProject::instance()->readNumEntry( "Gui", "/CanvasColorGreenPart", 255 );
@@ -5973,8 +5398,8 @@
   QStringList selectedFiles;
   QString e;//only for parameter correctness
   QString title = tr( "Open a GDAL Supported Raster Data Source" );
-  openFilesRememberingFilter_( "lastRasterFileFilter", mRasterFileFilter, selectedFiles, e,
-                               title );
+  QgisGui::openFilesRememberingFilter( "lastRasterFileFilter", mRasterFileFilter, selectedFiles, e,
+                                       title );
 
   if ( selectedFiles.isEmpty() )
   {

Modified: trunk/qgis/src/core/CMakeLists.txt
===================================================================
--- trunk/qgis/src/core/CMakeLists.txt	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/core/CMakeLists.txt	2009-12-07 17:14:41 UTC (rev 12350)
@@ -29,7 +29,6 @@
   qgscoordinatetransform.cpp
   qgsdatasourceuri.cpp
   qgsdistancearea.cpp
-  qgsexception.cpp
   qgsfeature.cpp
   qgsfield.cpp
   qgsgeometry.cpp

Deleted: trunk/qgis/src/core/qgsexception.cpp
===================================================================
--- trunk/qgis/src/core/qgsexception.cpp	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/core/qgsexception.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -1,27 +0,0 @@
-/***************************************************************************
-                          qgsexception.cpp  -  description
-                             -------------------
-    begin                : Time-stamp: <2005-04-28 14:30:58 mcoletti>
-    copyright            : (C) 2002 by Mark Coletti
-    email                : mcoletti at gmail.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 <QObject>
-#include "qgsexception.h"
-
-
-const char * const ident_ = "$Id$";
-
-
-
-
-const char * QgsProjectBadLayerException::msg_ = "Unable to open one or more project layers";

Modified: trunk/qgis/src/core/qgsexception.h
===================================================================
--- trunk/qgis/src/core/qgsexception.h	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/core/qgsexception.h	2009-12-07 17:14:41 UTC (rev 12350)
@@ -56,49 +56,4 @@
 }; // class QgsException
 
 
-
-/** for files missing from layers while reading project files
-
-*/
-class QgsProjectBadLayerException : public QgsException
-{
-  public:
-
-    QgsProjectBadLayerException( std::list<QDomNode> const & layers, QDomDocument const & doc = QDomDocument() )
-        : QgsException( std::string( msg_ ) ),
-        mBrokenLayers( layers ),
-        mProjectDom( doc )
-    {}
-
-    ~QgsProjectBadLayerException() throw()
-    {}
-
-    std::list<QDomNode> const & layers() const
-    {
-      return mBrokenLayers;
-    }
-
-    QDomDocument const & document() const
-    {
-      return mProjectDom;
-    }
-  private:
-
-    /** QDomNodes representing the state of a layer that couldn't be loaded
-
-    The layer data was either relocated or deleted.  The Dom node also
-    contains ancillary data such as line widths and the like.
-
-     */
-    std::list<QDomNode> mBrokenLayers;
-
-    // A default empty document does not contain any extra information
-    QDomDocument mProjectDom;
-
-    static const char * msg_;
-
-}; // class QgsProjectBadLayerException
-
-
-
 #endif

Modified: trunk/qgis/src/core/qgsproject.cpp
===================================================================
--- trunk/qgis/src/core/qgsproject.cpp	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/core/qgsproject.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -340,7 +340,7 @@
 
 
 QgsProject::QgsProject()
-    : imp_( new QgsProject::Imp )
+    : imp_( new QgsProject::Imp ), mBadLayerHandler( new QgsProjectBadLayerDefaultHandler() )
 {
   // Set some default project properties
   // XXX THESE SHOULD BE MOVED TO STATUSBAR RELATED SOURCE
@@ -355,6 +355,8 @@
 
 QgsProject::~QgsProject()
 {
+  delete mBadLayerHandler;
+
   // note that std::auto_ptr automatically deletes imp_ when it's destroyed
 } // QgsProject dtor
 
@@ -642,7 +644,7 @@
    </maplayer>
 
 */
-std::pair< bool, std::list<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
+QPair< bool, QList<QDomNode> > QgsProject::_getMapLayers( QDomDocument const &doc )
 {
   // Layer order is set by the restoring the legend settings from project file.
   // This is done on the 'readProject( ... ) signal
@@ -653,7 +655,7 @@
 
   QString wk;
 
-  std::list<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
+  QList<QDomNode> brokenNodes; // a list of Dom nodes corresponding to layers
   // that we were unable to load; this could be
   // because the layers were removed or
   // re-located after the project was last saved
@@ -662,7 +664,7 @@
 
   if ( 0 == nl.count() )      // if we have no layers to process, bail
   {
-    return make_pair( true, brokenNodes ); // Decided to return "true" since it's
+    return qMakePair( true, brokenNodes ); // Decided to return "true" since it's
     // possible for there to be a project with no
     // layers; but also, more imporantly, this
     // would cause the tests/qgsproject to fail
@@ -700,7 +702,7 @@
     {
       QgsDebugMsg( "Unable to create layer" );
 
-      return make_pair( false, brokenNodes );
+      return qMakePair( false, brokenNodes );
     }
 
     // have the layer restore state that is stored in Dom node
@@ -722,7 +724,7 @@
     emit layerLoaded( i + 1, nl.count() );
   }
 
-  return make_pair( returnStatus, brokenNodes );
+  return qMakePair( returnStatus, brokenNodes );
 
 } // _getMapLayers
 
@@ -835,7 +837,7 @@
 
 
   // get the map layers
-  std::pair< bool, std::list<QDomNode> > getMapLayersResults =  _getMapLayers( *doc );
+  QPair< bool, QList<QDomNode> > getMapLayersResults =  _getMapLayers( *doc );
 
   // review the integrity of the retrieved map layers
 
@@ -843,18 +845,14 @@
   {
     QgsDebugMsg( "Unable to get map layers from project file." );
 
-    if ( ! getMapLayersResults.second.empty() )
+    if ( ! getMapLayersResults.second.isEmpty() )
     {
       QgsDebugMsg( "there are " + QString::number( getMapLayersResults.second.size() ) + " broken layers" );
     }
 
-    // Since we could be executing this from the test harness which
-    // doesn't *have* layers -- nor a GUI for that matter -- we'll just
-    // leave in the whining and boldly stomp on.
-    emit readProject( *doc );
-    throw QgsProjectBadLayerException( getMapLayersResults.second, *doc );
-
-//         return false;
+    // we let a custom handler to decide what to do with missing layers
+    // (default implementation ignores them, there's also a GUI handler that lets user choose correct path)
+    mBadLayerHandler->handleBadLayers( getMapLayersResults.second, *doc );
   }
 
   // read the project: used by map canvas and legend
@@ -1480,3 +1478,14 @@
 {
   setError( QString() );
 }
+
+void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler* handler )
+{
+  delete mBadLayerHandler;
+  mBadLayerHandler = handler;
+}
+
+void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList<QDomNode> /*layers*/, QDomDocument /*projectDom*/ )
+{
+  // just ignore any bad layers
+}

Modified: trunk/qgis/src/core/qgsproject.h
===================================================================
--- trunk/qgis/src/core/qgsproject.h	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/core/qgsproject.h	2009-12-07 17:14:41 UTC (rev 12350)
@@ -25,6 +25,8 @@
 #include <memory>
 #include "qgsprojectversion.h"
 #include <QObject>
+#include <QList>
+#include <QPair>
 
 //#include <QDomDocument>
 
@@ -32,6 +34,7 @@
 class QDomDocument;
 class QDomNode;
 
+class QgsProjectBadLayerHandler;
 
 /** \ingroup core
  * Reads and writes project states.
@@ -268,6 +271,11 @@
       @note added in 1.4 */
     QString error() const;
 
+    /** Change handler for missing layers.
+      Deletes old handler and takes ownership of the new one.
+      @note added in 1.4 */
+    void setBadLayerHandler( QgsProjectBadLayerHandler* handler );
+
   protected:
 
     /** Set error message from read/write operation
@@ -307,10 +315,32 @@
 
     static QgsProject * theProject_;
 
-    std::pair< bool, std::list<QDomNode> > _getMapLayers( QDomDocument const &doc );
+    QPair< bool, QList<QDomNode> > _getMapLayers( QDomDocument const &doc );
 
     QString mErrorMessage;
 
+    QgsProjectBadLayerHandler* mBadLayerHandler;
+
 }; // QgsProject
 
+
+/** Interface for classes that handle missing layer files when reading project file.
+  @note added in 1.4 */
+class CORE_EXPORT QgsProjectBadLayerHandler
+{
+  public:
+    virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom ) = 0;
+    virtual ~QgsProjectBadLayerHandler() {}
+};
+
+
+/** Default bad layer handler which ignores any missing layers.
+  @note added in 1.4 */
+class CORE_EXPORT QgsProjectBadLayerDefaultHandler : public QgsProjectBadLayerHandler
+{
+  public:
+    virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom );
+
+};
+
 #endif

Modified: trunk/qgis/src/gui/CMakeLists.txt
===================================================================
--- trunk/qgis/src/gui/CMakeLists.txt	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/gui/CMakeLists.txt	2009-12-07 17:14:41 UTC (rev 12350)
@@ -39,6 +39,7 @@
 qgsmaptoolpan.cpp
 qgsmaptoolzoom.cpp
 qgsmessageviewer.cpp
+qgsprojectbadlayerguihandler.cpp
 qgsprojectionselector.cpp
 qgsquickprint.cpp
 qgsrubberband.cpp

Modified: trunk/qgis/src/gui/qgisgui.cpp
===================================================================
--- trunk/qgis/src/gui/qgisgui.cpp	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/gui/qgisgui.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -14,3 +14,75 @@
  ***************************************************************************/
 #include "qgisgui.h"
 
+#include <QSettings>
+#include "qgsencodingfiledialog.h"
+#include "qgslogger.h"
+
+namespace QgisGui
+{
+
+  bool openFilesRememberingFilter( QString const &filterName,
+                                   QString const &filters, QStringList & selectedFiles, QString& enc, QString &title,
+                                   bool cancelAll )
+  {
+
+    bool haveLastUsedFilter = false; // by default, there is no last
+    // used filter
+
+    QSettings settings;         // where we keep last used filter in
+    // persistant state
+
+    haveLastUsedFilter = settings.contains( "/UI/" + filterName );
+    QString lastUsedFilter = settings.value( "/UI/" + filterName,
+                             QVariant( QString::null ) ).toString();
+
+    QString lastUsedDir = settings.value( "/UI/" + filterName + "Dir", "." ).toString();
+
+    QgsDebugMsg( "Opening file dialog with filters: " + filters );
+    if ( !cancelAll )
+    {
+      selectedFiles = QFileDialog::getOpenFileNames( 0, title, lastUsedDir, filters, &lastUsedFilter );
+    }
+    else //we have to use non-native dialog to add cancel all button
+    {
+      QgsEncodingFileDialog* openFileDialog = new QgsEncodingFileDialog( 0, title, lastUsedDir, filters, QString( "" ) );
+      // allow for selection of more than one file
+      openFileDialog->setFileMode( QFileDialog::ExistingFiles );
+      if ( haveLastUsedFilter )     // set the filter to the last one used
+      {
+        openFileDialog->selectFilter( lastUsedFilter );
+      }
+      openFileDialog->addCancelAll();
+      if ( openFileDialog->exec() == QDialog::Accepted )
+      {
+        selectedFiles = openFileDialog->selectedFiles();
+      }
+      else
+      {
+        //cancel or cancel all?
+        if ( openFileDialog->cancelAll() )
+        {
+          return true;
+        }
+      }
+    }
+
+    if ( !selectedFiles.isEmpty() )
+    {
+      // Fix by Tim - getting the dirPath from the dialog
+      // directly truncates the last node in the dir path.
+      // This is a workaround for that
+      QString myFirstFileName = selectedFiles.first();
+      QFileInfo myFI( myFirstFileName );
+      QString myPath = myFI.path();
+
+      QgsDebugMsg( "Writing last used dir: " + myPath );
+
+      settings.setValue( "/UI/" + filterName, lastUsedFilter );
+      settings.setValue( "/UI/" + filterName + "Dir", myPath );
+    }
+    return false;
+  }
+
+
+} // end of QgisGui namespace

Modified: trunk/qgis/src/gui/qgisgui.h
===================================================================
--- trunk/qgis/src/gui/qgisgui.h	2009-12-07 14:22:37 UTC (rev 12349)
+++ trunk/qgis/src/gui/qgisgui.h	2009-12-07 17:14:41 UTC (rev 12350)
@@ -18,9 +18,11 @@
 
 #include <Qt>
 
+class QStringList;
+
 /** \ingroup gui
  * /namespace QgisGui
- * The QgisGui namespace contains constants used throughout the QGIS GUI.
+ * The QgisGui namespace contains constants and helper functions used throughout the QGIS GUI.
  */
 namespace QgisGui
 {
@@ -49,6 +51,34 @@
     0;
 #endif
 
+  /**
+    Open files, preferring to have the default file selector be the
+    last one used, if any; also, prefer to start in the last directory
+    associated with filterName.
+
+    @param filterName the name of the filter; used for persistent store key
+    @param filters    the file filters used for QFileDialog
+    @param selectedFiles string list of selected files; will be empty if none selected
+    @param enc        encoding?
+    @param title      the title for the dialog
+    @note
+
+    Stores persistent settings under /UI/.  The sub-keys will be
+    filterName and filterName + "Dir".
+
+    Opens dialog on last directory associated with the filter name, or
+    the current working directory if this is the first time invoked
+    with the current filter name.
+
+    This method returns true if cancel all was clicked, otherwise false
+
+    @note added in 1.4
+  */
+
+  bool openFilesRememberingFilter( QString const &filterName,
+                                   QString const &filters, QStringList & selectedFiles, QString& enc, QString &title,
+                                   bool cancelAll = false );
+
 }
 
 #endif

Added: trunk/qgis/src/gui/qgsprojectbadlayerguihandler.cpp
===================================================================
--- trunk/qgis/src/gui/qgsprojectbadlayerguihandler.cpp	                        (rev 0)
+++ trunk/qgis/src/gui/qgsprojectbadlayerguihandler.cpp	2009-12-07 17:14:41 UTC (rev 12350)
@@ -0,0 +1,264 @@
+#include "qgsprojectbadlayerguihandler.h"
+
+#include <QApplication>
+#include <QDomDocument>
+#include <QFileInfo>
+#include <QMessageBox>
+
+#include "qgslogger.h"
+#include "qgisgui.h"
+#include "qgsproviderregistry.h"
+
+QgsProjectBadLayerGuiHandler::QgsProjectBadLayerGuiHandler()
+{
+}
+
+
+void QgsProjectBadLayerGuiHandler::handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom )
+{
+
+  QgsDebugMsg( QString( "%1 bad layers found" ).arg( layers.size() ) );
+
+  // make sure we have arrow cursor (and not a wait cursor)
+  QApplication::setOverrideCursor( Qt::ArrowCursor );
+
+  if ( QMessageBox::Ok == QMessageBox::critical( NULL,
+       tr( "QGIS Project Read Error" ),
+       tr( "Unable to open one or more project layers\nTry to find missing layers?" ),
+       QMessageBox::Ok | QMessageBox::Cancel ) )
+  {
+    QgsDebugMsg( "want to find missing layers is true" );
+
+    // attempt to find the new locations for missing layers
+    // XXX vector file hard-coded -- but what if it's raster?
+
+    QString filter = QgsProviderRegistry::instance()->fileVectorFilters();
+    findLayers( filter, layers );
+  }
+
+  QApplication::restoreOverrideCursor();
+}
+
+
+QgsProjectBadLayerGuiHandler::DataType QgsProjectBadLayerGuiHandler::dataType( QDomNode & layerNode )
+{
+  QString type = layerNode.toElement().attribute( "type" );
+
+  if ( QString::null == type )
+  {
+    QgsDebugMsg( "cannot find ``type'' attribute" );
+
+    return IS_BOGUS;
+  }
+
+  if ( "raster" == type )
+  {
+    QgsDebugMsg( "is a raster" );
+
+    return IS_RASTER;
+  }
+  else if ( "vector" == type )
+  {
+    QgsDebugMsg( "is a vector" );
+
+    return IS_VECTOR;
+  }
+
+  QgsDebugMsg( "is unknown type " + type );
+
+  return IS_BOGUS;
+} // dataType_( QDomNode & layerNode )
+
+
+QString QgsProjectBadLayerGuiHandler::dataSource( QDomNode & layerNode )
+{
+  QDomNode dataSourceNode = layerNode.namedItem( "datasource" );
+
+  if ( dataSourceNode.isNull() )
+  {
+    QgsDebugMsg( "cannot find datasource node" );
+
+    return QString::null;
+  }
+
+  return dataSourceNode.toElement().text();
+
+} // dataSource( QDomNode & layerNode )
+
+
+
+
+QgsProjectBadLayerGuiHandler::ProviderType QgsProjectBadLayerGuiHandler::providerType( QDomNode & layerNode )
+{
+  // XXX but what about rasters that can be URLs?  _Can_ they be URLs?
+
+  switch ( dataType( layerNode ) )
+  {
+    case IS_VECTOR:
+    {
+      QString ds = dataSource( layerNode );
+
+      QgsDebugMsg( "datasource is " + ds );
+
+      if ( ds.contains( "host=" ) )
+      {
+        return IS_URL;
+      }
+#ifdef HAVE_POSTGRESQL
+      else if ( ds.contains( "dbname=" ) )
+      {
+        return IS_DATABASE;
+      }
+#endif
+      // be default, then, this should be a file based layer data source
+      // XXX is this a reasonable assumption?
+
+      return IS_FILE;
+    }
+
+    case IS_RASTER:         // rasters are currently only accessed as
+      // physical files
+      return IS_FILE;
+
+    default:
+      QgsDebugMsg( "unknown ``type'' attribute" );
+  }
+
+  return IS_Unknown;
+
+} // providerType
+
+
+
+void QgsProjectBadLayerGuiHandler::setDataSource( QDomNode & layerNode, QString const & dataSource )
+{
+  QDomNode dataSourceNode = layerNode.namedItem( "datasource" );
+  QDomElement dataSourceElement = dataSourceNode.toElement();
+  QDomText dataSourceText = dataSourceElement.firstChild().toText();
+
+  QgsDebugMsg( "datasource changed from " + dataSourceText.data() );
+
+  dataSourceText.setData( dataSource );
+
+  QgsDebugMsg( "to " + dataSourceText.data() );
+} // setDataSource
+
+
+
+
+bool QgsProjectBadLayerGuiHandler::findMissingFile( QString const & fileFilters, QDomNode & layerNode )
+{
+  // Prepend that file name to the valid file format filter list since it
+  // makes it easier for the user to not only find the original file, but to
+  // perhaps find a similar file.
+
+  QFileInfo originalDataSource( dataSource( layerNode ) );
+
+  QString memoryQualifier;    // to differentiate between last raster and
+  // vector directories
+
+  switch ( dataType( layerNode ) )
+  {
+    case IS_VECTOR:
+    {
+      memoryQualifier = "lastVectorFileFilter";
+
+      break;
+    }
+    case IS_RASTER:
+    {
+      memoryQualifier = "lastRasterFileFilter";
+
+      break;
+    }
+    default:
+      QgsDebugMsg( "unable to determine data type" );
+      return false;
+  }
+
+  // Prepend the original data source base name to make it easier to pick it
+  // out from a list of other files; however the appropriate filter strings
+  // for the file type will also be added in case the file name itself has
+  // changed, too.
+
+  QString myFileFilters = originalDataSource.fileName() + ";;" + fileFilters;
+
+  QStringList selectedFiles;
+  QString enc;
+  QString title = QObject::tr( "Where is '%1' (original location: %2)?" )
+                  .arg( originalDataSource.fileName() )
+                  .arg( originalDataSource.absoluteFilePath() );
+
+  bool retVal = QgisGui::openFilesRememberingFilter( memoryQualifier,
+                myFileFilters,
+                selectedFiles,
+                enc,
+                title,
+                true );
+
+  if ( selectedFiles.isEmpty() )
+  {
+    return retVal;
+  }
+  else
+  {
+    setDataSource( layerNode, selectedFiles.first() );
+    if ( ! QgsProject::instance()->read( layerNode ) )
+    {
+      QgsDebugMsg( "unable to re-read layer" );
+    }
+  }
+  return retVal;
+} // findMissingFile
+
+
+
+
+bool QgsProjectBadLayerGuiHandler::findLayer( QString const & fileFilters, QDomNode const & constLayerNode )
+{
+  // XXX actually we could possibly get away with a copy of the node
+  QDomNode & layerNode = const_cast<QDomNode&>( constLayerNode );
+
+  bool retVal = false;
+
+  switch ( providerType( layerNode ) )
+  {
+    case IS_FILE:
+      QgsDebugMsg( "layer is file based" );
+      retVal = findMissingFile( fileFilters, layerNode );
+      break;
+
+    case IS_DATABASE:
+      QgsDebugMsg( "layer is database based" );
+      break;
+
+    case IS_URL:
+      QgsDebugMsg( "layer is URL based" );
+      break;
+
+    case IS_Unknown:
+      QgsDebugMsg( "layer has an unkown type" );
+      break;
+  }
+  return retVal;
+} // findLayer
+
+
+
+
+void QgsProjectBadLayerGuiHandler::findLayers( QString const & fileFilters, QList<QDomNode> const & layerNodes )
+{
+
+  for ( QList<QDomNode>::const_iterator i = layerNodes.begin();
+        i != layerNodes.end();
+        ++i )
+  {
+    if ( findLayer( fileFilters, *i ) )
+    {
+      // If findLayer returns true, the user hit Cancel All button
+      break;
+    }
+  }
+
+} // findLayers
+

Added: trunk/qgis/src/gui/qgsprojectbadlayerguihandler.h
===================================================================
--- trunk/qgis/src/gui/qgsprojectbadlayerguihandler.h	                        (rev 0)
+++ trunk/qgis/src/gui/qgsprojectbadlayerguihandler.h	2009-12-07 17:14:41 UTC (rev 12350)
@@ -0,0 +1,87 @@
+#ifndef QGSPROJECTBADLAYERGUIHANDLER_H
+#define QGSPROJECTBADLAYERGUIHANDLER_H
+
+#include "qgsproject.h"
+
+/** \ingroup gui
+  Handler for missing layers within project.
+
+  Gives user a chance to select path to the missing layers.
+
+  @note added in 1.4
+ */
+class GUI_EXPORT QgsProjectBadLayerGuiHandler : public QObject, public QgsProjectBadLayerHandler
+{
+  public:
+    QgsProjectBadLayerGuiHandler();
+
+    /** implementation of the handler */
+    virtual void handleBadLayers( QList<QDomNode> layers, QDomDocument projectDom );
+
+  protected:
+
+    //! file data representation
+    enum DataType { IS_VECTOR, IS_RASTER, IS_BOGUS };
+
+    //! the three flavors for data
+    enum ProviderType { IS_FILE, IS_DATABASE, IS_URL, IS_Unknown };
+
+
+    /** returns data type associated with the given QgsProject file Dom node
+
+      The Dom node should represent the state associated with a specific layer.
+      */
+    DataType dataType( QDomNode & layerNode );
+
+    /** return the data source for the given layer
+
+      The QDomNode is a QgsProject Dom node corresponding to a map layer state.
+
+      Essentially dumps <datasource> tag.
+    */
+    QString dataSource( QDomNode & layerNode );
+
+    /** return the physical storage type associated with the given layer
+
+      The QDomNode is a QgsProject Dom node corresponding to a map layer state.
+
+      If the <provider> is "ogr", then it's a file type.
+
+      However, if the layer is a raster, then there won't be a <provider> tag.  It
+      will always have an associated file.
+
+      If the layer doesn't fall into either of the previous two categories, then
+      it's either a database or URL.  If the <datasource> tag has "url=", then
+      it's URL based.  If the <datasource> tag has "dbname=">, then the layer data
+      is in a database.
+    */
+    ProviderType providerType( QDomNode & layerNode );
+
+    /** set the <datasource> to the new value */
+    void setDataSource( QDomNode & layerNode, QString const & dataSource );
+
+    /** this is used to locate files that have moved or otherwise are missing */
+    bool findMissingFile( QString const & fileFilters, QDomNode & layerNode );
+
+    /** find relocated data source for the given layer
+
+      This QDom object represents a QgsProject node that maps to a specific layer.
+
+      @param layerNode QDom node containing layer project information
+
+      @todo
+
+      XXX Only implemented for file based layers.  It will need to be extended for
+      XXX other data source types such as databases.
+    */
+    bool findLayer( QString const & fileFilters, QDomNode const & constLayerNode );
+
+    /** find relocated data sources for given layers
+
+      These QDom objects represent QgsProject nodes that map to specific layers.
+    */
+    void findLayers( QString const & fileFilters, QList<QDomNode> const & layerNodes );
+
+};
+
+#endif // QGSPROJECTBADLAYERGUIHANDLER_H



More information about the QGIS-commit mailing list