From ab7d2c7475c735cc573446bcf32717c9913e9161 Mon Sep 17 00:00:00 2001 From: wonder Date: Mon, 7 Dec 2009 17:14:41 +0000 Subject: [PATCH] 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 git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@12350 c8812cc2-4d05-0410-92ff-de0c093fc19c --- python/core/qgsproject.sip | 32 + python/gui/gui.sip | 1 + python/gui/qgsprojectbadlayerguihandler.sip | 21 + src/app/ogr/qgsopenvectorlayerdialog.cpp | 70 +-- src/app/ogr/qgsopenvectorlayerdialog.h | 3 - src/app/qgisapp.cpp | 617 +------------------- src/core/CMakeLists.txt | 1 - src/core/qgsexception.cpp | 27 - src/core/qgsexception.h | 45 -- src/core/qgsproject.cpp | 39 +- src/core/qgsproject.h | 32 +- src/gui/CMakeLists.txt | 1 + src/gui/qgisgui.cpp | 72 +++ src/gui/qgisgui.h | 32 +- src/gui/qgsprojectbadlayerguihandler.cpp | 264 +++++++++ src/gui/qgsprojectbadlayerguihandler.h | 87 +++ 16 files changed, 589 insertions(+), 755 deletions(-) create mode 100644 python/gui/qgsprojectbadlayerguihandler.sip delete mode 100644 src/core/qgsexception.cpp create mode 100644 src/gui/qgsprojectbadlayerguihandler.cpp create mode 100644 src/gui/qgsprojectbadlayerguihandler.h diff --git a/python/core/qgsproject.sip b/python/core/qgsproject.sip index 1898c22e25c3..839e2679729e 100644 --- a/python/core/qgsproject.sip +++ b/python/core/qgsproject.sip @@ -235,6 +235,11 @@ public: @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 @@ public: }; // QgsProject + +/** Interface for classes that handle missing layer files when reading project file. + @note added in 1.4 */ +class QgsProjectBadLayerHandler +{ +%TypeHeaderCode +#include +%End + +public: + virtual void handleBadLayers( QList 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 +%End + +public: + virtual void handleBadLayers( QList layers, QDomDocument projectDom ); + +}; diff --git a/python/gui/gui.sip b/python/gui/gui.sip index e7b086431db6..f8d0286735ec 100644 --- a/python/gui/gui.sip +++ b/python/gui/gui.sip @@ -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 diff --git a/python/gui/qgsprojectbadlayerguihandler.sip b/python/gui/qgsprojectbadlayerguihandler.sip new file mode 100644 index 000000000000..13169e7b8eb1 --- /dev/null +++ b/python/gui/qgsprojectbadlayerguihandler.sip @@ -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 +%End + + public: + QgsProjectBadLayerGuiHandler(); + + /** implementation of the handler */ + virtual void handleBadLayers( QList layers, QDomDocument projectDom ); + +}; diff --git a/src/app/ogr/qgsopenvectorlayerdialog.cpp b/src/app/ogr/qgsopenvectorlayerdialog.cpp index 1c1af27bc647..9af013051d09 100644 --- a/src/app/ogr/qgsopenvectorlayerdialog.cpp +++ b/src/app/ogr/qgsopenvectorlayerdialog.cpp @@ -97,11 +97,11 @@ QStringList QgsOpenVectorLayerDialog::openFile() 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,68 +306,6 @@ void QgsOpenVectorLayerDialog::on_buttonSelectSrc_clicked() } -/** - 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 *****************/ diff --git a/src/app/ogr/qgsopenvectorlayerdialog.h b/src/app/ogr/qgsopenvectorlayerdialog.h index df65410bd2d5..e2bfdc28bd0a 100644 --- a/src/app/ogr/qgsopenvectorlayerdialog.h +++ b/src/app/ogr/qgsopenvectorlayerdialog.h @@ -46,9 +46,6 @@ class QgsOpenVectorLayerDialog : public QDialog, private Ui::QgsOpenVectorLayerD //! 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 */ diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8687112e4b8e..83d139a567ee 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -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,12 +212,6 @@ const int BEFORE_RECENT_PATHS = 123; 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 @@ -426,10 +421,13 @@ QgisApp::QgisApp( QSplashScreen *splash, QWidget * parent, Qt::WFlags fl ) 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,236 +2201,6 @@ static QString createFileFilter_( QString const &longName, QString const &glob ) -/** - 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. @@ -2819,300 +2587,6 @@ void QgisApp::addWmsLayer() -/// 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 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 is "ogr", then it's a file type. - - However, if the layer is a raster, then there won't be a 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 tag has "url=", then - it's URL based. If the 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 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( 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 const & layerNodes ) -{ - - for ( std::list::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 @@ void QgisApp::fileOpen() QgsProject::instance()->setFileName( fullPath ); - try - { - if ( ! QgsProject::instance()->read() ) - { - QMessageBox::critical( this, - tr( "QGIS Project Read Error" ), - QgsProject::instance()->error() ); - mMapCanvas->freeze( false ); - mMapCanvas->refresh(); - return; - } - } - catch ( QgsProjectBadLayerException & e ) + if ( ! QgsProject::instance()->read() ) { 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 @@ bool QgisApp::addProject( QString projectFile ) // 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() ); - - QApplication::restoreOverrideCursor(); - - mMapCanvas->freeze( false ); - mMapCanvas->refresh(); - return false; - } - // Continue after last catch statement - - } - 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 ); + QMessageBox::critical( this, + tr( "Unable to open project" ), + QgsProject::instance()->error() ); - // Tell the legend to update the ordering - mMapLegend->readProject( e.document() ); - } - // Continue after last catch statement + QApplication::restoreOverrideCursor(); + mMapCanvas->freeze( false ); + mMapCanvas->refresh(); + return false; } - // 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 @@ void QgisApp::addRasterLayer() 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() ) { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a11d9d30032f..1737c6822945 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -29,7 +29,6 @@ SET(QGIS_CORE_SRCS qgscoordinatetransform.cpp qgsdatasourceuri.cpp qgsdistancearea.cpp - qgsexception.cpp qgsfeature.cpp qgsfield.cpp qgsgeometry.cpp diff --git a/src/core/qgsexception.cpp b/src/core/qgsexception.cpp deleted file mode 100644 index 800b79b14c4d..000000000000 --- a/src/core/qgsexception.cpp +++ /dev/null @@ -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 -#include "qgsexception.h" - - -const char * const ident_ = "$Id$"; - - - - -const char * QgsProjectBadLayerException::msg_ = "Unable to open one or more project layers"; diff --git a/src/core/qgsexception.h b/src/core/qgsexception.h index 52b2cf92019c..152f4d5f1452 100644 --- a/src/core/qgsexception.h +++ b/src/core/qgsexception.h @@ -56,49 +56,4 @@ class CORE_EXPORT QgsException : public std::exception }; // class QgsException - -/** for files missing from layers while reading project files - -*/ -class QgsProjectBadLayerException : public QgsException -{ - public: - - QgsProjectBadLayerException( std::list const & layers, QDomDocument const & doc = QDomDocument() ) - : QgsException( std::string( msg_ ) ), - mBrokenLayers( layers ), - mProjectDom( doc ) - {} - - ~QgsProjectBadLayerException() throw() - {} - - std::list 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 mBrokenLayers; - - // A default empty document does not contain any extra information - QDomDocument mProjectDom; - - static const char * msg_; - -}; // class QgsProjectBadLayerException - - - #endif diff --git a/src/core/qgsproject.cpp b/src/core/qgsproject.cpp index 0fc0e3f25013..f6e4e7a76107 100644 --- a/src/core/qgsproject.cpp +++ b/src/core/qgsproject.cpp @@ -340,7 +340,7 @@ struct QgsProject::Imp 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() QgsProject::~QgsProject() { + delete mBadLayerHandler; + // note that std::auto_ptr automatically deletes imp_ when it's destroyed } // QgsProject dtor @@ -642,7 +644,7 @@ static QgsProjectVersion _getVersion( QDomDocument const &doc ) */ -std::pair< bool, std::list > QgsProject::_getMapLayers( QDomDocument const &doc ) +QPair< bool, QList > 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 @@ std::pair< bool, std::list > QgsProject::_getMapLayers( QDomDocument c QString wk; - std::list brokenNodes; // a list of Dom nodes corresponding to layers + QList 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 @@ std::pair< bool, std::list > QgsProject::_getMapLayers( QDomDocument c 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 @@ std::pair< bool, std::list > QgsProject::_getMapLayers( QDomDocument c { 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 @@ std::pair< bool, std::list > QgsProject::_getMapLayers( QDomDocument c emit layerLoaded( i + 1, nl.count() ); } - return make_pair( returnStatus, brokenNodes ); + return qMakePair( returnStatus, brokenNodes ); } // _getMapLayers @@ -835,7 +837,7 @@ bool QgsProject::read() // get the map layers - std::pair< bool, std::list > getMapLayersResults = _getMapLayers( *doc ); + QPair< bool, QList > getMapLayersResults = _getMapLayers( *doc ); // review the integrity of the retrieved map layers @@ -843,18 +845,14 @@ bool QgsProject::read() { 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 @@ void QgsProject::clearError() { setError( QString() ); } + +void QgsProject::setBadLayerHandler( QgsProjectBadLayerHandler* handler ) +{ + delete mBadLayerHandler; + mBadLayerHandler = handler; +} + +void QgsProjectBadLayerDefaultHandler::handleBadLayers( QList /*layers*/, QDomDocument /*projectDom*/ ) +{ + // just ignore any bad layers +} diff --git a/src/core/qgsproject.h b/src/core/qgsproject.h index c609a20c2f88..3f871bb5e7b7 100644 --- a/src/core/qgsproject.h +++ b/src/core/qgsproject.h @@ -25,6 +25,8 @@ #include #include "qgsprojectversion.h" #include +#include +#include //#include @@ -32,6 +34,7 @@ class QFileInfo; class QDomDocument; class QDomNode; +class QgsProjectBadLayerHandler; /** \ingroup core * Reads and writes project states. @@ -268,6 +271,11 @@ class CORE_EXPORT QgsProject : public QObject @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 @@ class CORE_EXPORT QgsProject : public QObject static QgsProject * theProject_; - std::pair< bool, std::list > _getMapLayers( QDomDocument const &doc ); + QPair< bool, QList > _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 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 layers, QDomDocument projectDom ); + +}; + #endif diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 6a1a24d0d94d..2f4c5ae50cb0 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -39,6 +39,7 @@ qgsmaptoolemitpoint.cpp qgsmaptoolpan.cpp qgsmaptoolzoom.cpp qgsmessageviewer.cpp +qgsprojectbadlayerguihandler.cpp qgsprojectionselector.cpp qgsquickprint.cpp qgsrubberband.cpp diff --git a/src/gui/qgisgui.cpp b/src/gui/qgisgui.cpp index 3005ef377c2e..9fc7f24a6146 100644 --- a/src/gui/qgisgui.cpp +++ b/src/gui/qgisgui.cpp @@ -14,3 +14,75 @@ ***************************************************************************/ #include "qgisgui.h" +#include +#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 diff --git a/src/gui/qgisgui.h b/src/gui/qgisgui.h index 916649e52148..87fbd0889990 100644 --- a/src/gui/qgisgui.h +++ b/src/gui/qgisgui.h @@ -18,9 +18,11 @@ #include +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 @@ namespace QgisGui 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 diff --git a/src/gui/qgsprojectbadlayerguihandler.cpp b/src/gui/qgsprojectbadlayerguihandler.cpp new file mode 100644 index 000000000000..f0eac762511f --- /dev/null +++ b/src/gui/qgsprojectbadlayerguihandler.cpp @@ -0,0 +1,264 @@ +#include "qgsprojectbadlayerguihandler.h" + +#include +#include +#include +#include + +#include "qgslogger.h" +#include "qgisgui.h" +#include "qgsproviderregistry.h" + +QgsProjectBadLayerGuiHandler::QgsProjectBadLayerGuiHandler() +{ +} + + +void QgsProjectBadLayerGuiHandler::handleBadLayers( QList 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( 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 const & layerNodes ) +{ + + for ( QList::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 + diff --git a/src/gui/qgsprojectbadlayerguihandler.h b/src/gui/qgsprojectbadlayerguihandler.h new file mode 100644 index 000000000000..a193e56fde02 --- /dev/null +++ b/src/gui/qgsprojectbadlayerguihandler.h @@ -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 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 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 is "ogr", then it's a file type. + + However, if the layer is a raster, then there won't be a 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 tag has "url=", then + it's URL based. If the tag has "dbname=">, then the layer data + is in a database. + */ + ProviderType providerType( QDomNode & layerNode ); + + /** set the 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 const & layerNodes ); + +}; + +#endif // QGSPROJECTBADLAYERGUIHANDLER_H