[QGIS Commit] r14335 - in trunk/qgis/src: core plugins
plugins/offline_editing
svn_qgis at osgeo.org
svn_qgis at osgeo.org
Tue Oct 5 04:51:41 EDT 2010
Author: mhugent
Date: 2010-10-05 08:51:41 +0000 (Tue, 05 Oct 2010)
New Revision: 14335
Added:
trunk/qgis/src/plugins/offline_editing/
trunk/qgis/src/plugins/offline_editing/CMakeLists.txt
trunk/qgis/src/plugins/offline_editing/offline_editing.cpp
trunk/qgis/src/plugins/offline_editing/offline_editing.h
trunk/qgis/src/plugins/offline_editing/offline_editing_copy.png
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.cpp
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.h
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.qrc
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.cpp
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.h
trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_guibase.ui
trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.cpp
trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.h
trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog_base.ui
trunk/qgis/src/plugins/offline_editing/offline_editing_sync.png
Modified:
trunk/qgis/src/core/qgsvectorlayer.cpp
trunk/qgis/src/core/qgsvectorlayer.h
trunk/qgis/src/plugins/CMakeLists.txt
Log:
[FEATURE]: offline editing plugin from Mathias Walker
Modified: trunk/qgis/src/core/qgsvectorlayer.cpp
===================================================================
--- trunk/qgis/src/core/qgsvectorlayer.cpp 2010-10-05 07:24:08 UTC (rev 14334)
+++ trunk/qgis/src/core/qgsvectorlayer.cpp 2010-10-05 08:51:41 UTC (rev 14335)
@@ -3228,6 +3228,9 @@
if (( cap & QgsVectorDataProvider::DeleteAttributes ) && mDataProvider->deleteAttributes( mDeletedAttributeIds ) )
{
mCommitErrors << tr( "SUCCESS: %n attribute(s) deleted.", "deleted attributes count", mDeletedAttributeIds.size() );
+
+ emit committedAttributesDeleted( getLayerID(), mDeletedAttributeIds );
+
mDeletedAttributeIds.clear();
attributesChanged = true;
}
@@ -3250,6 +3253,9 @@
if (( cap & QgsVectorDataProvider::AddAttributes ) && mDataProvider->addAttributes( addedAttributes ) )
{
mCommitErrors << tr( "SUCCESS: %n attribute(s) added.", "added attributes count", mAddedAttributeIds.size() );
+
+ emit committedAttributesAdded( getLayerID(), addedAttributes );
+
mAddedAttributeIds.clear();
attributesChanged = true;
}
@@ -3366,6 +3372,9 @@
if (( cap & QgsVectorDataProvider::ChangeAttributeValues ) && mDataProvider->changeAttributeValues( mChangedAttributeValues ) )
{
mCommitErrors << tr( "SUCCESS: %n attribute value(s) changed.", "changed attribute values count", mChangedAttributeValues.size() );
+
+ emit committedAttributeValuesChanges( getLayerID(), mChangedAttributeValues );
+
mChangedAttributeValues.clear();
}
else
@@ -3404,6 +3413,9 @@
if (( cap & QgsVectorDataProvider::AddFeatures ) && mDataProvider->addFeatures( mAddedFeatures ) )
{
mCommitErrors << tr( "SUCCESS: %n feature(s) added.", "added features count", mAddedFeatures.size() );
+
+ emit committedFeaturesAdded( getLayerID(), mAddedFeatures );
+
mAddedFeatures.clear();
}
else
@@ -3422,6 +3434,9 @@
if (( cap & QgsVectorDataProvider::ChangeGeometries ) && mDataProvider->changeGeometryValues( mChangedGeometries ) )
{
mCommitErrors << tr( "SUCCESS: %n geometries were changed.", "changed geometries count", mChangedGeometries.size() );
+
+ emit committedGeometriesChanges( getLayerID(), mChangedGeometries );
+
mChangedGeometries.clear();
}
else
@@ -3444,6 +3459,9 @@
mChangedAttributeValues.remove( *it );
mChangedGeometries.remove( *it );
}
+
+ emit committedFeaturesRemoved( getLayerID(), mDeletedFeatureIds );
+
mDeletedFeatureIds.clear();
}
else
Modified: trunk/qgis/src/core/qgsvectorlayer.h
===================================================================
--- trunk/qgis/src/core/qgsvectorlayer.h 2010-10-05 07:24:08 UTC (rev 14334)
+++ trunk/qgis/src/core/qgsvectorlayer.h 2010-10-05 08:51:41 UTC (rev 14335)
@@ -594,6 +594,14 @@
void attributeValueChanged( int fid, int idx, const QVariant & );
+ /** Signals emitted after committing changes */
+ void committedAttributesDeleted( const QString& layerId, const QgsAttributeIds& deletedAttributeIds );
+ void committedAttributesAdded( const QString& layerId, const QList<QgsField>& addedAttributes );
+ void committedFeaturesAdded( const QString& layerId, const QgsFeatureList& addedFeatures );
+ void committedFeaturesRemoved( const QString& layerId, const QgsFeatureIds& deletedFeatureIds );
+ void committedAttributeValuesChanges( const QString& layerId, const QgsChangedAttributesMap& changedAttributesValues );
+ void committedGeometriesChanges( const QString& layerId, const QgsGeometryMap& changedGeometries );
+
private: // Private methods
/** vector layers are not copyable */
Modified: trunk/qgis/src/plugins/CMakeLists.txt
===================================================================
--- trunk/qgis/src/plugins/CMakeLists.txt 2010-10-05 07:24:08 UTC (rev 14334)
+++ trunk/qgis/src/plugins/CMakeLists.txt 2010-10-05 08:51:41 UTC (rev 14335)
@@ -13,6 +13,7 @@
evis
point_displacement_renderer
spatialquery
+ offline_editing
)
IF (POSTGRES_FOUND)
Added: trunk/qgis/src/plugins/offline_editing/CMakeLists.txt
===================================================================
--- trunk/qgis/src/plugins/offline_editing/CMakeLists.txt (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/CMakeLists.txt 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,56 @@
+
+########################################################
+# Files
+
+SET (offline_editing_plugin_SRCS
+ offline_editing_plugin.cpp
+ offline_editing_plugin_gui.cpp
+ offline_editing.cpp
+ offline_editing_progress_dialog.cpp
+)
+
+SET (offline_editing_plugin_UIS
+ offline_editing_plugin_guibase.ui
+ offline_editing_progress_dialog_base.ui
+)
+
+SET (offline_editing_plugin_MOC_HDRS
+ offline_editing_plugin.h
+ offline_editing_plugin_gui.h
+ offline_editing.h
+ offline_editing_progress_dialog.h
+)
+
+SET (offline_editing_plugin_RCCS offline_editing_plugin.qrc)
+
+########################################################
+# Build
+
+QT4_WRAP_UI (offline_editing_plugin_UIS_H ${offline_editing_plugin_UIS})
+
+QT4_WRAP_CPP (offline_editing_plugin_MOC_SRCS ${offline_editing_plugin_MOC_HDRS})
+
+QT4_ADD_RESOURCES(offline_editing_plugin_RCC_SRCS ${offline_editing_plugin_RCCS})
+
+ADD_LIBRARY (offlineeditingplugin MODULE ${offline_editing_plugin_SRCS} ${offline_editing_plugin_MOC_SRCS} ${offline_editing_plugin_RCC_SRCS} ${offline_editing_plugin_UIS_H})
+
+INCLUDE_DIRECTORIES(
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${SQLITE3_INCLUDE_DIR} ${SPATIALITE_INCLUDE_DIR}
+ ../../core ../../core/raster ../../core/renderer ../../core/symbology
+ ../../gui
+ ..
+)
+
+TARGET_LINK_LIBRARIES(offlineeditingplugin
+ qgis_core
+ qgis_gui
+)
+
+
+########################################################
+# Install
+
+INSTALL(TARGETS offlineeditingplugin
+ RUNTIME DESTINATION ${QGIS_PLUGIN_DIR}
+ LIBRARY DESTINATION ${QGIS_PLUGIN_DIR})
Added: trunk/qgis/src/plugins/offline_editing/offline_editing.cpp
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing.cpp (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing.cpp 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,1136 @@
+/***************************************************************************
+ offline_editing.cpp
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 22-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * 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 "offline_editing.h"
+#include "offline_editing_progress_dialog.h"
+
+#include <qgsapplication.h>
+#include <qgsdatasourceuri.h>
+#include <qgsgeometry.h>
+#include <qgslegendinterface.h>
+#include <qgsmaplayer.h>
+#include <qgsmaplayerregistry.h>
+#include <qgsproject.h>
+#include <qgsvectordataprovider.h>
+
+#include <QDir>
+#include <QDomDocument>
+#include <QDomNode>
+#include <QFile>
+#include <QMessageBox>
+
+extern "C"
+{
+#include <sqlite3.h>
+#include <spatialite.h>
+}
+
+// TODO: DEBUG
+#include <QDebug>
+// END
+
+#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
+#define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
+#define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
+#define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
+#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
+
+QgsOfflineEditing::QgsOfflineEditing( QgsOfflineEditingProgressDialog* progressDialog )
+{
+ mProgressDialog = progressDialog;
+ connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
+}
+
+QgsOfflineEditing::~QgsOfflineEditing()
+{
+ if ( mProgressDialog != NULL )
+ {
+ delete mProgressDialog;
+ }
+ disconnect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( layerAdded( QgsMapLayer* ) ) );
+}
+
+/**
+ * convert current project to offline project
+ * returns offline project file path
+ */
+bool QgsOfflineEditing::convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds )
+{
+ if ( layerIds.isEmpty() )
+ {
+ return false;
+ }
+ QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
+ if ( createSpatialiteDB( dbPath ) )
+ {
+ spatialite_init( 0 );
+ sqlite3* db;
+ int rc = sqlite3_open( dbPath.toStdString().c_str(), &db );
+ if ( rc != SQLITE_OK )
+ {
+ showWarning( tr( "Could not open the spatialite database" ) );
+ }
+ else
+ {
+ // create logging tables
+ createLoggingTables( db );
+
+ mProgressDialog->setTitle( "Converting to offline project" );
+ mProgressDialog->show();
+
+ // copy selected vector layers to SpatiaLite
+ for ( int i = 0; i < layerIds.count(); i++ )
+ {
+ mProgressDialog->setCurrentLayer( i + 1, layerIds.count() );
+
+ QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( layerIds.at( i ) );
+ copyVectorLayer( qobject_cast<QgsVectorLayer*>( layer ), db, dbPath );
+ }
+
+ mProgressDialog->hide();
+
+ sqlite3_close( db );
+
+ // save offline project
+ QString projectTitle = QgsProject::instance()->title();
+ if ( projectTitle.isEmpty() )
+ {
+ projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
+ }
+ projectTitle += " (offline)";
+ QgsProject::instance()->title( projectTitle );
+
+ QgsProject::instance()->writeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH, dbPath );
+
+ return true;
+ }
+ }
+
+ return false;
+
+ // Workflow:
+ // copy layers to spatialite
+ // create spatialite db at offlineDataPath
+ // create table for each layer
+ // add new spatialite layer
+ // copy features
+ // save as offline project
+ // mark offline layers
+ // remove remote layers
+ // mark as offline project
+}
+
+bool QgsOfflineEditing::isOfflineProject()
+{
+ return !QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH ).isEmpty();
+}
+
+void QgsOfflineEditing::synchronize( QgsLegendInterface* legendInterface )
+{
+ // open logging db
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ mProgressDialog->setTitle( "Synchronizing to remote layers" );
+ mProgressDialog->show();
+
+ // restore and sync remote layers
+ QList<QgsMapLayer*> offlineLayers;
+ QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
+ for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
+ {
+ QgsMapLayer* layer = layer_it.value();
+ if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
+ {
+ offlineLayers << layer;
+ }
+ }
+
+ for ( int l = 0; l < offlineLayers.count(); l++ )
+ {
+ QgsMapLayer* layer = offlineLayers[l];
+
+ mProgressDialog->setCurrentLayer( l + 1, offlineLayers.count() );
+
+ QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
+ QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
+ QString remoteName = layer->name();
+ remoteName.remove( QRegExp( " \\(offline\\)$" ) );
+
+ QgsVectorLayer* remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider );
+ if ( remoteLayer->isValid() )
+ {
+ // TODO: only add remote layer if there are log entries?
+
+ QgsVectorLayer* offlineLayer = qobject_cast<QgsVectorLayer*>( layer );
+
+ // copy style
+ copySymbology( offlineLayer, remoteLayer );
+
+ // register this layer with the central layers registry
+ QgsMapLayerRegistry::instance()->addMapLayer( remoteLayer, true );
+
+ // apply layer edit log
+ QString qgisLayerId = layer->getLayerID();
+ QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
+ int layerId = sqlQueryInt( db, sql, -1 );
+ if ( layerId != -1 )
+ {
+ remoteLayer->startEditing();
+
+ // TODO: only get commitNos of this layer?
+ int commitNo = getCommitNo( db );
+ for ( int i = 0; i < commitNo; i++ )
+ {
+ // apply commits chronologically
+ applyAttributesAdded( remoteLayer, db, layerId, i );
+ applyAttributeValueChanges( offlineLayer, remoteLayer, db, layerId, i );
+ applyGeometryChanges( remoteLayer, db, layerId, i );
+ }
+
+ applyFeaturesAdded( offlineLayer, remoteLayer, db, layerId );
+ applyFeaturesRemoved( remoteLayer, db, layerId );
+
+ if ( remoteLayer->commitChanges() )
+ {
+ // update fid lookup
+ updateFidLookup( remoteLayer, db, layerId );
+
+ // clear edit log for this layer
+ sql = QString( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
+ sqlExec( db, sql );
+ sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
+ sqlExec( db, sql );
+ sql = QString( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
+ sqlExec( db, sql );
+ sql = QString( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
+ sqlExec( db, sql );
+ sql = QString( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
+ sqlExec( db, sql );
+
+ // reset commitNo
+ QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
+ sqlExec( db, sql );
+ }
+ else
+ {
+ showWarning( remoteLayer->commitErrors().join( "\n" ) );
+ }
+ }
+
+ // remove offline layer
+ QgsMapLayerRegistry::instance()->removeMapLayer( qgisLayerId, true );
+
+ // disable offline project
+ QString projectTitle = QgsProject::instance()->title();
+ projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
+ QgsProject::instance()->title( projectTitle );
+ QgsProject::instance()->removeEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
+ remoteLayer->reload(); //update with other changes
+ }
+ }
+
+ mProgressDialog->hide();
+
+ sqlite3_close( db );
+}
+
+bool QgsOfflineEditing::createSpatialiteDB( const QString& offlineDbPath )
+{
+ QFile newDb( offlineDbPath );
+ if ( newDb.exists() )
+ {
+ QFile::remove( offlineDbPath );
+ }
+
+ // see also QgsNewSpatialiteLayerDialog::createDb()
+
+ // copy the spatialite template to the user specified path
+ QString spatialiteTemplate = QgsApplication::qgisSpatialiteDbTemplatePath();
+ QFile spatialiteTemplateDb( spatialiteTemplate );
+
+ QFileInfo fullPath = QFileInfo( offlineDbPath );
+ QDir path = fullPath.dir();
+
+ // Must be sure there is destination directory ~/.qgis
+ QDir().mkpath( path.absolutePath( ) );
+
+ // now copy the template db file into the chosen location
+ if ( !spatialiteTemplateDb.copy( newDb.fileName() ) )
+ {
+ showWarning( tr( "Could not copy the template database to new location" ) );
+ return false;
+ }
+
+ return true;
+}
+
+void QgsOfflineEditing::createLoggingTables( sqlite3* db )
+{
+ // indices
+ QString sql = "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)";
+ sqlExec( db, sql );
+
+ sql = "INSERT INTO 'log_indices' VALUES ('commit_no', 0)";
+ sqlExec( db, sql );
+
+ sql = "INSERT INTO 'log_indices' VALUES ('layer_id', 0)";
+ sqlExec( db, sql );
+
+ // layername <-> layer id
+ sql = "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)";
+ sqlExec( db, sql );
+
+ // offline fid <-> remote fid
+ sql = "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)";
+ sqlExec( db, sql );
+
+ // added attributes
+ sql = "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, ";
+ sql += "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)";
+ sqlExec( db, sql );
+
+ // added features
+ sql = "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)";
+ sqlExec( db, sql );
+
+ // removed features
+ sql = "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)";
+ sqlExec( db, sql );
+
+ // feature updates
+ sql = "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)";
+ sqlExec( db, sql );
+
+ // geometry updates
+ sql = "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)";
+ sqlExec( db, sql );
+
+ /* TODO: other logging tables
+ - attr delete (not supported by SpatiaLite provider)
+ */
+}
+
+void QgsOfflineEditing::copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath )
+{
+ if ( layer == NULL )
+ {
+ return;
+ }
+
+ QString tableName = layer->name();
+
+ // create table
+ QString sql = QString( "CREATE TABLE '%1' (" ).arg( tableName );
+ QString delim = "";
+ const QgsFieldMap& fields = layer->dataProvider()->fields();
+ for ( QgsFieldMap::const_iterator it = fields.begin(); it != fields.end() ; ++it )
+ {
+ QString dataType = "";
+ QVariant::Type type = it.value().type();
+ if ( type == QVariant::Int )
+ {
+ dataType = "INTEGER";
+ }
+ else if ( type == QVariant::Double )
+ {
+ dataType = "REAL";
+ }
+ else if ( type == QVariant::String )
+ {
+ dataType = "TEXT";
+ }
+ else
+ {
+ showWarning( tr( "Unknown data type %1" ).arg( type ) );
+ }
+
+ sql += delim + QString( "'%1' %2" ).arg( it.value().name() ).arg( dataType );
+ delim = ",";
+ }
+ sql += ")";
+
+ // add geometry column
+ QString geomType = "";
+ switch ( layer->geometryType() )
+ {
+ case QGis::Point:
+ geomType = "POINT";
+ break;
+ case QGis::Line:
+ geomType = "LINESTRING";
+ break;
+ case QGis::Polygon:
+ geomType = "POLYGON";
+ break;
+ default:
+ showWarning( tr( "Unknown QGIS geometry type %1" ).arg( layer->geometryType() ) );
+ break;
+ };
+ QString sqlAddGeom = QString( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', 2)" )
+ .arg( tableName )
+ .arg( layer->crs().epsg() )
+ .arg( geomType );
+
+ // create spatial index
+ QString sqlCreateIndex = QString( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
+
+ int rc = sqlExec( db, sql );
+ if ( rc == SQLITE_OK )
+ {
+ rc = sqlExec( db, sqlAddGeom );
+ if ( rc == SQLITE_OK )
+ {
+ rc = sqlExec( db, sqlCreateIndex );
+ }
+ }
+
+ if ( rc == SQLITE_OK )
+ {
+ // add new layer
+ QgsVectorLayer* newLayer = new QgsVectorLayer( QString( "dbname='%1' table='%2'(Geometry) sql=" )
+ .arg( offlineDbPath ).arg( tableName ), tableName + " (offline)", "spatialite" );
+ if ( newLayer->isValid() )
+ {
+ // mark as offline layer
+ newLayer->setCustomProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, true );
+
+ // store original layer source
+ newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, layer->source() );
+ newLayer->setCustomProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, layer->providerType() );
+
+ // copy style
+ bool hasLabels = layer->hasLabelsEnabled();
+ if ( !hasLabels )
+ {
+ // NOTE: copy symbology before adding the layer so it is displayed correctly
+ copySymbology( layer, newLayer );
+ }
+
+ // register this layer with the central layers registry
+ QgsMapLayerRegistry::instance()->addMapLayer( newLayer );
+
+ if ( hasLabels )
+ {
+ // NOTE: copy symbology of layers with labels enabled after adding to project, as it will crash otherwise (WORKAROUND)
+ copySymbology( layer, newLayer );
+ }
+
+ // TODO: layer order
+
+ // copy features
+ newLayer->startEditing();
+ QgsFeature f;
+
+ // NOTE: force feature recount for PostGIS layer, else only visible features are counted, before iterating over all features (WORKAROUND)
+ layer->setSubsetString( "" );
+
+ layer->select( layer->pendingAllAttributesList(), QgsRectangle(), true, false );
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m features copied" ), layer->featureCount() );
+ int featureCount = 1;
+
+ QList<int> remoteFeatureIds;
+ while ( layer->nextFeature( f ) )
+ {
+ remoteFeatureIds << f.id();
+
+ // NOTE: Spatialite provider ignores position of geometry column
+ // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
+ int column = 0;
+ QgsAttributeMap newAttrMap;
+ QgsAttributeMap attrMap = f.attributeMap();
+ for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
+ {
+ newAttrMap.insert( column++, it.value() );
+ }
+ f.setAttributeMap( newAttrMap );
+
+ newLayer->addFeature( f, false );
+
+ mProgressDialog->setProgressValue( featureCount++ );
+ }
+ if ( newLayer->commitChanges() )
+ {
+ mProgressDialog->setupProgressBar( tr( "%v / %m features processed" ), layer->featureCount() );
+ featureCount = 1;
+
+ // update feature id lookup
+ int layerId = getOrCreateLayerId( db, newLayer->getLayerID() );
+ QList<int> offlineFeatureIds;
+ newLayer->select( QgsAttributeList(), QgsRectangle(), false, false );
+ while ( newLayer->nextFeature( f ) )
+ {
+ offlineFeatureIds << f.id();
+ }
+
+ // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
+ sqlExec( db, "BEGIN" );
+ for ( int i = 0; i < remoteFeatureIds.size(); i++ )
+ {
+ addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
+
+ mProgressDialog->setProgressValue( featureCount++ );
+ }
+ sqlExec( db, "COMMIT" );
+ }
+ else
+ {
+ showWarning( newLayer->commitErrors().join( "\n" ) );
+ }
+
+ // remove remote layer
+ QgsMapLayerRegistry::instance()->removeMapLayer( layer->getLayerID() );
+ }
+ }
+}
+
+void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
+{
+ QString sql = QString( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
+ QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
+
+ const QgsVectorDataProvider* provider = remoteLayer->dataProvider();
+ QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
+
+ // NOTE: uses last matching QVariant::Type of nativeTypes
+ QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
+ for ( int i = 0; i < nativeTypes.size(); i++ )
+ {
+ QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
+ typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
+ }
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m fields added" ), fields.size() );
+
+ for ( int i = 0; i < fields.size(); i++ )
+ {
+ // lookup typename from layer provider
+ QgsField field = fields[i];
+ if ( typeNameLookup.contains( field.type() ) )
+ {
+ QString typeName = typeNameLookup[ field.type()];
+ field.setTypeName( typeName );
+ remoteLayer->addAttribute( field );
+ }
+ else
+ {
+ showWarning( QString( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
+ }
+
+ mProgressDialog->setProgressValue( i + 1 );
+ }
+}
+
+void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
+{
+ QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
+ QList<int> newFeatureIds = sqlQueryInts( db, sql );
+
+ // get new features from offline layer
+ QgsFeatureList features;
+ for ( int i = 0; i < newFeatureIds.size(); i++ )
+ {
+ QgsFeature feature;
+ if ( offlineLayer->featureAtId( newFeatureIds.at( i ), feature, true, true ) )
+ {
+ features << feature;
+ }
+ }
+
+ // copy features to remote layer
+ mProgressDialog->setupProgressBar( tr( "%v / %m features added" ), features.size() );
+
+ int i = 1;
+ for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
+ {
+ QgsFeature f = *it;
+
+ // NOTE: Spatialite provider ignores position of geometry column
+ // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
+ QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
+ QgsAttributeMap newAttrMap;
+ QgsAttributeMap attrMap = f.attributeMap();
+ for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
+ {
+ newAttrMap.insert( attrLookup[ it.key()], it.value() );
+ }
+ f.setAttributeMap( newAttrMap );
+
+ remoteLayer->addFeature( f, false );
+
+ mProgressDialog->setProgressValue( i++ );
+ }
+}
+
+void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
+{
+ QString sql = QString( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
+ QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m features removed" ), values.size() );
+
+ int i = 1;
+ for ( QgsFeatureIds::const_iterator it = values.begin(); it != values.end(); ++it )
+ {
+ int fid = remoteFid( db, layerId, *it );
+ remoteLayer->deleteFeature( fid );
+
+ mProgressDialog->setProgressValue( i++ );
+ }
+}
+
+void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
+{
+ QString sql = QString( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
+ AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m feature updates" ), values.size() );
+
+ QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
+
+ for ( int i = 0; i < values.size(); i++ )
+ {
+ int fid = remoteFid( db, layerId, values.at( i ).fid );
+
+ remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value, false );
+
+ mProgressDialog->setProgressValue( i + 1 );
+ }
+}
+
+void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo )
+{
+ QString sql = QString( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
+ GeometryChanges values = sqlQueryGeometryChanges( db, sql );
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m feature geometry updates" ), values.size() );
+
+ for ( int i = 0; i < values.size(); i++ )
+ {
+ int fid = remoteFid( db, layerId, values.at( i ).fid );
+ remoteLayer->changeGeometry( fid, QgsGeometry::fromWkt( values.at( i ).geom_wkt ) );
+
+ mProgressDialog->setProgressValue( i + 1 );
+ }
+}
+
+void QgsOfflineEditing::updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId )
+{
+ // update fid lookup for added features
+
+ // get remote added fids
+ // NOTE: use QMap for sorted fids
+ QMap < int, bool /*dummy*/ > newRemoteFids;
+ QgsFeature f;
+ remoteLayer->select( QgsAttributeList(), QgsRectangle(), false, false );
+
+ mProgressDialog->setupProgressBar( tr( "%v / %m features processed" ), remoteLayer->featureCount() );
+
+ int i = 1;
+ while ( remoteLayer->nextFeature( f ) )
+ {
+ if ( offlineFid( db, layerId, f.id() ) == -1 )
+ {
+ newRemoteFids[ f.id()] = true;
+ }
+
+ mProgressDialog->setProgressValue( i++ );
+ }
+
+ // get local added fids
+ // NOTE: fids are sorted
+ QString sql = QString( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
+ QList<int> newOfflineFids = sqlQueryInts( db, sql );
+
+ if ( newRemoteFids.size() != newOfflineFids.size() )
+ {
+ //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
+ }
+ else
+ {
+ // add new fid lookups
+ i = 0;
+ sqlExec( db, "BEGIN" );
+ for ( QMap<int, bool>::const_iterator it = newRemoteFids.begin(); it != newRemoteFids.end(); ++it )
+ {
+ addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
+ }
+ sqlExec( db, "COMMIT" );
+ }
+}
+
+void QgsOfflineEditing::copySymbology( const QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer )
+{
+ QString error;
+ QDomDocument doc;
+ QDomElement node = doc.createElement( "symbology" );
+ doc.appendChild( node );
+ sourceLayer->writeSymbology( node, doc, error );
+
+ if ( error.isEmpty() )
+ {
+ targetLayer->readSymbology( node, error );
+ }
+ if ( !error.isEmpty() )
+ {
+ showWarning( error );
+ }
+}
+
+// NOTE: use this to map column indices in case the remote geometry column is not last
+QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer )
+{
+ const QgsAttributeList& offlineAttrs = offlineLayer->pendingAllAttributesList();
+ const QgsAttributeList& remoteAttrs = remoteLayer->pendingAllAttributesList();
+
+ QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
+ // NOTE: use size of remoteAttrs, as offlineAttrs can have new attributes not yet synced
+ for ( int i = 0; i < remoteAttrs.size(); i++ )
+ {
+ attrLookup.insert( offlineAttrs.at( i ), remoteAttrs.at( i ) );
+ }
+
+ return attrLookup;
+}
+
+void QgsOfflineEditing::showWarning( const QString& message )
+{
+ QMessageBox::warning( NULL, tr( "Offline Editing Plugin" ), message );
+}
+
+sqlite3* QgsOfflineEditing::openLoggingDb()
+{
+ sqlite3* db = NULL;
+ QString dbPath = QgsProject::instance()->readEntry( PROJECT_ENTRY_SCOPE_OFFLINE, PROJECT_ENTRY_KEY_OFFLINE_DB_PATH );
+ if ( !dbPath.isEmpty() )
+ {
+ int rc = sqlite3_open( dbPath.toStdString().c_str(), &db );
+ if ( rc != SQLITE_OK )
+ {
+ showWarning( tr( "Could not open the spatialite logging database" ) );
+ sqlite3_close( db );
+ db = NULL;
+ }
+ }
+ return db;
+}
+
+int QgsOfflineEditing::getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId )
+{
+ QString sql = QString( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
+ int layerId = sqlQueryInt( db, sql, -1 );
+ if ( layerId == -1 )
+ {
+ // next layer id
+ sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'";
+ int newLayerId = sqlQueryInt( db, sql, -1 );
+
+ // insert layer
+ sql = QString( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
+ sqlExec( db, sql );
+
+ // increase layer_id
+ // TODO: use trigger for auto increment?
+ sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
+ sqlExec( db, sql );
+
+ layerId = newLayerId;
+ }
+
+ return layerId;
+}
+
+int QgsOfflineEditing::getCommitNo( sqlite3* db )
+{
+ QString sql = "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'";
+ return sqlQueryInt( db, sql, -1 );
+}
+
+void QgsOfflineEditing::increaseCommitNo( sqlite3* db )
+{
+ QString sql = QString( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
+ sqlExec( db, sql );
+}
+
+void QgsOfflineEditing::addFidLookup( sqlite3* db, int layerId, int offlineFid, int remoteFid )
+{
+ QString sql = QString( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
+ sqlExec( db, sql );
+}
+
+int QgsOfflineEditing::remoteFid( sqlite3* db, int layerId, int offlineFid )
+{
+ QString sql = QString( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
+ return sqlQueryInt( db, sql, -1 );
+}
+
+int QgsOfflineEditing::offlineFid( sqlite3* db, int layerId, int remoteFid )
+{
+ QString sql = QString( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
+ return sqlQueryInt( db, sql, -1 );
+}
+
+bool QgsOfflineEditing::isAddedFeature( sqlite3* db, int layerId, int fid )
+{
+ QString sql = QString( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
+ return ( sqlQueryInt( db, sql, 0 ) > 0 );
+}
+
+int QgsOfflineEditing::sqlExec( sqlite3* db, const QString& sql )
+{
+ char * errmsg;
+ int rc = sqlite3_exec( db, sql.toUtf8(), NULL, NULL, &errmsg );
+ if ( rc != SQLITE_OK )
+ {
+ showWarning( errmsg );
+ }
+ return rc;
+}
+
+int QgsOfflineEditing::sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue )
+{
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return defaultValue;
+ }
+
+ int value = defaultValue;
+ int ret = sqlite3_step( stmt );
+ if ( ret == SQLITE_ROW )
+ {
+ value = sqlite3_column_int( stmt, 0 );
+ }
+ sqlite3_finalize( stmt );
+
+ return value;
+}
+
+QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3* db, const QString& sql )
+{
+ QList<int> values;
+
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return values;
+ }
+
+ int ret = sqlite3_step( stmt );
+ while ( ret == SQLITE_ROW )
+ {
+ values << sqlite3_column_int( stmt, 0 );
+
+ ret = sqlite3_step( stmt );
+ }
+ sqlite3_finalize( stmt );
+
+ return values;
+}
+
+QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3* db, const QString& sql )
+{
+ QList<QgsField> values;
+
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return values;
+ }
+
+ int ret = sqlite3_step( stmt );
+ while ( ret == SQLITE_ROW )
+ {
+ QgsField field( QString(( const char* )sqlite3_column_text( stmt, 0 ) ),
+ ( QVariant::Type )sqlite3_column_int( stmt, 1 ),
+ "", // typeName
+ sqlite3_column_int( stmt, 2 ),
+ sqlite3_column_int( stmt, 3 ),
+ QString(( const char* )sqlite3_column_text( stmt, 4 ) ) );
+ values << field;
+
+ ret = sqlite3_step( stmt );
+ }
+ sqlite3_finalize( stmt );
+
+ return values;
+}
+
+QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql )
+{
+ QgsFeatureIds values;
+
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return values;
+ }
+
+ int ret = sqlite3_step( stmt );
+ while ( ret == SQLITE_ROW )
+ {
+ values << sqlite3_column_int( stmt, 0 );
+
+ ret = sqlite3_step( stmt );
+ }
+ sqlite3_finalize( stmt );
+
+ return values;
+}
+
+QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql )
+{
+ AttributeValueChanges values;
+
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return values;
+ }
+
+ int ret = sqlite3_step( stmt );
+ while ( ret == SQLITE_ROW )
+ {
+ AttributeValueChange change;
+ change.fid = sqlite3_column_int( stmt, 0 );
+ change.attr = sqlite3_column_int( stmt, 1 );
+ change.value = QString(( const char* )sqlite3_column_text( stmt, 2 ) );
+ values << change;
+
+ ret = sqlite3_step( stmt );
+ }
+ sqlite3_finalize( stmt );
+
+ return values;
+}
+
+QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3* db, const QString& sql )
+{
+ GeometryChanges values;
+
+ sqlite3_stmt* stmt = NULL;
+ if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, NULL ) != SQLITE_OK )
+ {
+ showWarning( sqlite3_errmsg( db ) );
+ return values;
+ }
+
+ int ret = sqlite3_step( stmt );
+ while ( ret == SQLITE_ROW )
+ {
+ GeometryChange change;
+ change.fid = sqlite3_column_int( stmt, 0 );
+ change.geom_wkt = QString(( const char* )sqlite3_column_text( stmt, 1 ) );
+ values << change;
+
+ ret = sqlite3_step( stmt );
+ }
+ sqlite3_finalize( stmt );
+
+ return values;
+}
+
+void QgsOfflineEditing::layerAdded( QgsMapLayer* layer )
+{
+ // detect offline layer
+ if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
+ {
+ // enable logging
+ connect( layer, SIGNAL( committedAttributesAdded( const QString&, const QList<QgsField>& ) ),
+ this, SLOT( committedAttributesAdded( const QString&, const QList<QgsField>& ) ) );
+ connect( layer, SIGNAL( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ),
+ this, SLOT( committedFeaturesAdded( const QString&, const QgsFeatureList& ) ) );
+ connect( layer, SIGNAL( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ),
+ this, SLOT( committedFeaturesRemoved( const QString&, const QgsFeatureIds& ) ) );
+ connect( layer, SIGNAL( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ),
+ this, SLOT( committedAttributeValuesChanges( const QString&, const QgsChangedAttributesMap& ) ) );
+ connect( layer, SIGNAL( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ),
+ this, SLOT( committedGeometriesChanges( const QString&, const QgsGeometryMap& ) ) );
+ }
+}
+
+void QgsOfflineEditing::committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes )
+{
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ // insert log
+ int layerId = getOrCreateLayerId( db, qgisLayerId );
+ int commitNo = getCommitNo( db );
+
+ for ( QList<QgsField>::const_iterator it = addedAttributes.begin(); it != addedAttributes.end(); ++it )
+ {
+ QgsField field = *it;
+ QString sql = QString( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
+ .arg( layerId )
+ .arg( commitNo )
+ .arg( field.name() )
+ .arg( field.type() )
+ .arg( field.length() )
+ .arg( field.precision() )
+ .arg( field.comment() );
+ sqlExec( db, sql );
+ }
+
+ increaseCommitNo( db );
+ sqlite3_close( db );
+}
+
+void QgsOfflineEditing::committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures )
+{
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ // insert log
+ int layerId = getOrCreateLayerId( db, qgisLayerId );
+
+ // get new feature ids from db
+ QgsMapLayer* layer = QgsMapLayerRegistry::instance()->mapLayer( qgisLayerId );
+ QgsDataSourceURI uri = QgsDataSourceURI( layer->source() );
+
+ // only store feature ids
+ QString sql = QString( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( uri.table() ).arg( addedFeatures.size() );
+ QList<int> newFeatureIds = sqlQueryInts( db, sql );
+ for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
+ {
+ QString sql = QString( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
+ .arg( layerId )
+ .arg( newFeatureIds.at( i ) );
+ sqlExec( db, sql );
+ }
+
+ sqlite3_close( db );
+}
+
+void QgsOfflineEditing::committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds )
+{
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ // insert log
+ int layerId = getOrCreateLayerId( db, qgisLayerId );
+
+ for ( QgsFeatureIds::const_iterator it = deletedFeatureIds.begin(); it != deletedFeatureIds.end(); ++it )
+ {
+ if ( isAddedFeature( db, layerId, *it ) )
+ {
+ // remove from added features log
+ QString sql = QString( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( *it );
+ sqlExec( db, sql );
+ }
+ else
+ {
+ QString sql = QString( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
+ .arg( layerId )
+ .arg( *it );
+ sqlExec( db, sql );
+ }
+ }
+
+ sqlite3_close( db );
+}
+
+void QgsOfflineEditing::committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap )
+{
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ // insert log
+ int layerId = getOrCreateLayerId( db, qgisLayerId );
+ int commitNo = getCommitNo( db );
+
+ for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
+ {
+ int fid = cit.key();
+ if ( isAddedFeature( db, layerId, fid ) )
+ {
+ // skip added features
+ continue;
+ }
+ QgsAttributeMap attrMap = cit.value();
+ for ( QgsAttributeMap::const_iterator it = attrMap.begin(); it != attrMap.end(); ++it )
+ {
+ QString sql = QString( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
+ .arg( layerId )
+ .arg( commitNo )
+ .arg( fid )
+ .arg( it.key() ) // attr
+ .arg( it.value().toString() ); // value
+ sqlExec( db, sql );
+ }
+ }
+
+ increaseCommitNo( db );
+ sqlite3_close( db );
+}
+
+void QgsOfflineEditing::committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries )
+{
+ sqlite3* db = openLoggingDb();
+ if ( db == NULL )
+ {
+ return;
+ }
+
+ // insert log
+ int layerId = getOrCreateLayerId( db, qgisLayerId );
+ int commitNo = getCommitNo( db );
+
+ for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
+ {
+ int fid = it.key();
+ if ( isAddedFeature( db, layerId, fid ) )
+ {
+ // skip added features
+ continue;
+ }
+ QgsGeometry geom = it.value();
+ QString sql = QString( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
+ .arg( layerId )
+ .arg( commitNo )
+ .arg( fid )
+ .arg( geom.exportToWkt() );
+ sqlExec( db, sql );
+
+ // TODO: use WKB instead of WKT?
+ }
+
+ increaseCommitNo( db );
+ sqlite3_close( db );
+}
Added: trunk/qgis/src/plugins/offline_editing/offline_editing.h
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing.h (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing.h 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,106 @@
+/***************************************************************************
+ offline_editing.h
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 22-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGS_OFFLINE_EDITING_H
+#define QGS_OFFLINE_EDITING_H
+
+#include <qgsfeature.h>
+#include <qgsvectorlayer.h>
+
+#include <QObject>
+#include <QString>
+
+class QgsLegendInterface;
+class QgsMapLayer;
+class QgsOfflineEditingProgressDialog;
+class QgsVectorLayer;
+class sqlite3;
+
+class QgsOfflineEditing : public QObject
+{
+ Q_OBJECT
+
+ public:
+ QgsOfflineEditing( QgsOfflineEditingProgressDialog* progressDialog );
+ ~QgsOfflineEditing();
+
+ bool convertToOfflineProject( const QString& offlineDataPath, const QString& offlineDbFile, const QStringList& layerIds );
+ bool isOfflineProject();
+ void synchronize( QgsLegendInterface* legendInterface );
+
+ private:
+ bool createSpatialiteDB( const QString& offlineDbPath );
+ void createLoggingTables( sqlite3* db );
+ void copyVectorLayer( QgsVectorLayer* layer, sqlite3* db, const QString& offlineDbPath );
+
+ void applyAttributesAdded( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo );
+ void applyFeaturesAdded( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId );
+ void applyFeaturesRemoved( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId );
+ void applyAttributeValueChanges( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo );
+ void applyGeometryChanges( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId, int commitNo );
+ void updateFidLookup( QgsVectorLayer* remoteLayer, sqlite3* db, int layerId );
+ void copySymbology( const QgsVectorLayer* sourceLayer, QgsVectorLayer* targetLayer );
+ QMap<int, int> attributeLookup( QgsVectorLayer* offlineLayer, QgsVectorLayer* remoteLayer );
+
+ void showWarning( const QString& message );
+
+ sqlite3* openLoggingDb();
+ int getOrCreateLayerId( sqlite3* db, const QString& qgisLayerId );
+ int getCommitNo( sqlite3* db );
+ void increaseCommitNo( sqlite3* db );
+ void addFidLookup( sqlite3* db, int layerId, int offlineFid, int remoteFid );
+ int remoteFid( sqlite3* db, int layerId, int offlineFid );
+ int offlineFid( sqlite3* db, int layerId, int remoteFid );
+ bool isAddedFeature( sqlite3* db, int layerId, int fid );
+
+ int sqlExec( sqlite3* db, const QString& sql );
+ int sqlQueryInt( sqlite3* db, const QString& sql, int defaultValue );
+ QList<int> sqlQueryInts( sqlite3* db, const QString& sql );
+
+ QList<QgsField> sqlQueryAttributesAdded( sqlite3* db, const QString& sql );
+ QgsFeatureIds sqlQueryFeaturesRemoved( sqlite3* db, const QString& sql );
+
+ struct AttributeValueChange
+ {
+ int fid;
+ int attr;
+ QString value;
+ };
+ typedef QList<AttributeValueChange> AttributeValueChanges;
+ AttributeValueChanges sqlQueryAttributeValueChanges( sqlite3* db, const QString& sql );
+
+ struct GeometryChange
+ {
+ int fid;
+ QString geom_wkt;
+ };
+ typedef QList<GeometryChange> GeometryChanges;
+ GeometryChanges sqlQueryGeometryChanges( sqlite3* db, const QString& sql );
+
+ QgsOfflineEditingProgressDialog* mProgressDialog;
+
+ private slots:
+ void layerAdded( QgsMapLayer* layer );
+ void committedAttributesAdded( const QString& qgisLayerId, const QList<QgsField>& addedAttributes );
+ void committedFeaturesAdded( const QString& qgisLayerId, const QgsFeatureList& addedFeatures );
+ void committedFeaturesRemoved( const QString& qgisLayerId, const QgsFeatureIds& deletedFeatureIds );
+ void committedAttributeValuesChanges( const QString& qgisLayerId, const QgsChangedAttributesMap& changedAttrsMap );
+ void committedGeometriesChanges( const QString& qgisLayerId, const QgsGeometryMap& changedGeometries );
+};
+
+#endif // QGS_OFFLINE_EDITING_H
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_copy.png
===================================================================
(Binary files differ)
Property changes on: trunk/qgis/src/plugins/offline_editing/offline_editing_copy.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.cpp
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.cpp (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.cpp 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,181 @@
+/***************************************************************************
+ offline_editing_plugin.cpp
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * 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 "offline_editing_plugin.h"
+#include "offline_editing_plugin_gui.h"
+#include "offline_editing_progress_dialog.h"
+#include "offline_editing.h"
+
+#include <qgisinterface.h>
+#include <qgisgui.h>
+#include <qgsmaplayerregistry.h>
+#include <qgsproject.h>
+
+#include <QAction>
+
+static const QString sName = QObject::tr( "OfflineEditing" );
+static const QString sDescription = QObject::tr( "Allow offline editing and synchronizing with database" );
+static const QString sPluginVersion = QObject::tr( "Version 0.1" );
+static const QgisPlugin::PLUGINTYPE sPluginType = QgisPlugin::UI;
+
+QgsOfflineEditingPlugin::QgsOfflineEditingPlugin( QgisInterface* theQgisInterface )
+ : QgisPlugin( sName, sDescription, sPluginVersion, sPluginType ),
+ mQGisIface( theQgisInterface ),
+ mActionConvertProject( NULL ),
+ mActionSynchronize( NULL ),
+ mOfflineEditing( NULL )
+{
+}
+
+QgsOfflineEditingPlugin::~QgsOfflineEditingPlugin()
+{
+ if ( mOfflineEditing != NULL )
+ {
+ delete mOfflineEditing;
+ }
+}
+
+void QgsOfflineEditingPlugin::initGui()
+{
+ // Create the action for tool
+ mActionConvertProject = new QAction( QIcon( ":/offline_editing/offline_editing_copy.png" ), tr( "Convert to offline project" ), this );
+ // Set the what's this text
+ mActionConvertProject->setWhatsThis( tr( "Create offline copies of selected layers and save as offline project" ) );
+ // Connect the action to the run
+ connect( mActionConvertProject, SIGNAL( triggered() ), this, SLOT( convertProject() ) );
+ // Add the icon to the toolbar
+ mQGisIface->addToolBarIcon( mActionConvertProject );
+ mQGisIface->addPluginToMenu( tr( "&Offline Editing" ), mActionConvertProject );
+ mActionConvertProject->setEnabled( false );
+
+ mActionSynchronize = new QAction( QIcon( ":/offline_editing/offline_editing_sync.png" ), tr( "Synchronize" ), this );
+ mActionSynchronize->setWhatsThis( tr( "Synchronize offline project with remote layers" ) );
+ connect( mActionSynchronize, SIGNAL( triggered() ), this, SLOT( synchronize() ) );
+ mQGisIface->addToolBarIcon( mActionSynchronize );
+ mQGisIface->addPluginToMenu( tr( "&Offline Editing" ), mActionSynchronize );
+ mActionSynchronize->setEnabled( false );
+
+ mOfflineEditing = new QgsOfflineEditing( new QgsOfflineEditingProgressDialog( mQGisIface->mainWindow(), QgisGui::ModalDialogFlags ) );
+
+ connect( mQGisIface->mainWindow(), SIGNAL( projectRead() ), this, SLOT( updateActions() ) );
+ connect( mQGisIface->mainWindow(), SIGNAL( newProject() ), this, SLOT( updateActions() ) );
+ connect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ), this, SLOT( updateActions() ) );
+ connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( updateActions() ) );
+ connect( QgsMapLayerRegistry::instance(), SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( updateActions() ) );
+ updateActions();
+}
+
+void QgsOfflineEditingPlugin::convertProject()
+{
+ QgsOfflineEditingPluginGui* myPluginGui = new QgsOfflineEditingPluginGui( mQGisIface->mainWindow(), QgisGui::ModalDialogFlags );
+ myPluginGui->show();
+
+ if ( myPluginGui->exec() == 1 )
+ {
+ // convert current project for offline editing
+
+ QStringList selectedLayerIds = myPluginGui->selectedLayerIds();
+ if ( selectedLayerIds.isEmpty() )
+ {
+ return;
+ }
+
+ if ( mOfflineEditing->convertToOfflineProject( myPluginGui->offlineDataPath(), myPluginGui->offlineDbFile(), selectedLayerIds ) )
+ {
+ updateActions();
+ }
+ }
+
+ delete myPluginGui;
+}
+
+void QgsOfflineEditingPlugin::synchronize()
+{
+ mOfflineEditing->synchronize( mQGisIface->legendInterface() );
+ updateActions();
+}
+
+void QgsOfflineEditingPlugin::unload()
+{
+ disconnect( mQGisIface->mainWindow(), SIGNAL( projectRead() ), this, SLOT( updateActions() ) );
+ disconnect( mQGisIface->mainWindow(), SIGNAL( newProject() ), this, SLOT( updateActions() ) );
+ disconnect( QgsProject::instance(), SIGNAL( writeProject( QDomDocument & ) ), this, SLOT( updateActions() ) );
+
+ // remove the GUI
+ mQGisIface->removePluginMenu( "&Offline Editing", mActionConvertProject );
+ mQGisIface->removeToolBarIcon( mActionConvertProject );
+ mQGisIface->removePluginMenu( "&Offline Editing", mActionSynchronize );
+ mQGisIface->removeToolBarIcon( mActionSynchronize );
+ delete mActionConvertProject;
+ delete mActionSynchronize;
+}
+
+void QgsOfflineEditingPlugin::help()
+{
+ // TODO: help
+}
+
+void QgsOfflineEditingPlugin::updateActions()
+{
+ bool hasLayers = QgsMapLayerRegistry::instance()->count() > 0;
+ bool isOfflineProject = mOfflineEditing->isOfflineProject();
+ mActionConvertProject->setEnabled( hasLayers && !isOfflineProject );
+ mActionSynchronize->setEnabled( hasLayers && isOfflineProject );
+}
+
+/**
+ * Required extern functions needed for every plugin
+ * These functions can be called prior to creating an instance
+ * of the plugin class
+ */
+// Class factory to return a new instance of the plugin class
+QGISEXTERN QgisPlugin * classFactory( QgisInterface * theQgisInterfacePointer )
+{
+ return new QgsOfflineEditingPlugin( theQgisInterfacePointer );
+}
+
+// Return the name of the plugin - note that we do not user class members as
+// the class may not yet be insantiated when this method is called.
+QGISEXTERN QString name()
+{
+ return sName;
+}
+
+// Return the description
+QGISEXTERN QString description()
+{
+ return sDescription;
+}
+
+// Return the type (either UI or MapLayer plugin)
+QGISEXTERN int type()
+{
+ return sPluginType;
+}
+
+// Return the version number for the plugin
+QGISEXTERN QString version()
+{
+ return sPluginVersion;
+}
+
+// Delete ourself
+QGISEXTERN void unload( QgisPlugin * thePluginPointer )
+{
+ delete thePluginPointer;
+}
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.h
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.h (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.h 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,62 @@
+/***************************************************************************
+ offline_editing.h
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGS_OFFLINE_EDITING_PLUGIN_H
+#define QGS_OFFLINE_EDITING_PLUGIN_H
+
+#include "../qgisplugin.h"
+#include <QObject>
+
+class QAction;
+class QgisInterface;
+class QgsOfflineEditing;
+
+class QgsOfflineEditingPlugin : public QObject, public QgisPlugin
+{
+ Q_OBJECT
+
+ public:
+ QgsOfflineEditingPlugin( QgisInterface* theQgisInterface );
+ virtual ~QgsOfflineEditingPlugin();
+
+ public slots:
+ //! init the gui
+ virtual void initGui();
+ //! actions
+ void convertProject();
+ void synchronize();
+ //! unload the plugin
+ void unload();
+ //! show the help document
+ void help();
+
+ private:
+ int mPluginType;
+ //! Pointer to the QGIS interface object
+ QgisInterface* mQGisIface;
+ //!pointer to the qaction for this plugin
+ QAction* mActionConvertProject;
+ QAction* mActionSynchronize;
+
+ QgsOfflineEditing* mOfflineEditing;
+
+ private slots:
+ void updateActions();
+};
+
+#endif // QGS_OFFLINE_EDITING_PLUGIN_H
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.qrc
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.qrc (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin.qrc 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,7 @@
+<RCC>
+ <qresource prefix="/offline_editing/" >
+ <file>offline_editing_copy.png</file>
+ <file>offline_editing_sync.png</file>
+ </qresource>
+</RCC>
+
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.cpp
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.cpp (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.cpp 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,126 @@
+/***************************************************************************
+ offline_editing_plugin_gui.cpp
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * 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 "offline_editing_plugin_gui.h"
+
+#include <qgscontexthelp.h>
+#include <qgsmaplayer.h>
+#include <qgsmaplayerregistry.h>
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QSettings>
+
+#define SETTINGS_OFFLINE_DATA_PATH "Plugin-OfflineEditing/offline_data_path"
+
+QgsOfflineEditingPluginGui::QgsOfflineEditingPluginGui( QWidget* parent /*= 0*/, Qt::WFlags fl /*= 0*/ )
+ : QDialog( parent, fl )
+{
+ setupUi( this );
+
+ QDir dir;
+ QSettings settings;
+ mOfflineDataPath = settings.value( SETTINGS_OFFLINE_DATA_PATH, dir.absolutePath() ).toString();
+ mOfflineDbFile = "offline.sqlite";
+ ui_offlineDataPath->setText( QDir( mOfflineDataPath ).absoluteFilePath( mOfflineDbFile ) );
+
+ QMap<QString, QgsMapLayer*> mapLayers = QgsMapLayerRegistry::instance()->mapLayers();
+ for ( QMap<QString, QgsMapLayer*>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
+ {
+ if ( layer_it.value()->type() == QgsMapLayer::VectorLayer )
+ {
+ QListWidgetItem* item = new QListWidgetItem( layer_it.value()->name(), ui_layerList );
+ item->setData( Qt::UserRole, QVariant( layer_it.key() ) );
+ }
+ }
+}
+
+QgsOfflineEditingPluginGui::~QgsOfflineEditingPluginGui()
+{
+}
+
+QString QgsOfflineEditingPluginGui::offlineDataPath()
+{
+ return mOfflineDataPath;
+}
+
+QString QgsOfflineEditingPluginGui::offlineDbFile()
+{
+ return mOfflineDbFile;
+}
+
+QStringList& QgsOfflineEditingPluginGui::selectedLayerIds()
+{
+ return mSelectedLayerIds;
+}
+
+void QgsOfflineEditingPluginGui::on_butBrowse_clicked()
+{
+ QString fileName = QFileDialog::getSaveFileName( this,
+ tr( "Select target database for offline data" ),
+ QDir( mOfflineDataPath ).absoluteFilePath( mOfflineDbFile ),
+ tr( "SpatiaLite DB(*.sqlite);;All files(*.*)" )
+ );
+
+ if ( !fileName.isEmpty() )
+ {
+ mOfflineDbFile = QFileInfo( fileName ).fileName();
+ mOfflineDataPath = QFileInfo( fileName ).absolutePath();
+ ui_offlineDataPath->setText( fileName );
+ }
+}
+
+void QgsOfflineEditingPluginGui::on_buttonBox_accepted()
+{
+ if ( QFile( QDir( mOfflineDataPath ).absoluteFilePath( mOfflineDbFile ) ).exists() )
+ {
+ QMessageBox msgBox;
+ msgBox.setWindowTitle( tr( "Offline Editing Plugin" ) );
+ msgBox.setText( tr( "Converting to offline project." ) );
+ msgBox.setInformativeText( tr( "Offline database file '%1' exists. Overwrite?" ).arg( mOfflineDbFile ) );
+ msgBox.setStandardButtons( QMessageBox::Yes | QMessageBox::Cancel );
+ msgBox.setDefaultButton( QMessageBox::Cancel );
+ if ( msgBox.exec() != QMessageBox::Yes )
+ {
+ return;
+ }
+ }
+
+ mSelectedLayerIds.clear();
+ QList<QListWidgetItem*> layers = ui_layerList->selectedItems();
+ for ( QList<QListWidgetItem*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
+ {
+ mSelectedLayerIds.append(( *it )->data( Qt::UserRole ).toString() );
+ }
+
+ QSettings settings;
+ settings.setValue( SETTINGS_OFFLINE_DATA_PATH, mOfflineDataPath );
+
+ accept();
+}
+
+void QgsOfflineEditingPluginGui::on_buttonBox_rejected()
+{
+ reject();
+}
+
+// TODO: help
+void QgsOfflineEditingPluginGui::on_buttonBox_helpRequested()
+{
+ QgsContextHelp::run( context_id );
+}
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.h
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.h (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_gui.h 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,50 @@
+/***************************************************************************
+ offline_editing_plugin_gui.h
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGS_OFFLINE_EDITING_PLUGIN_GUI_H
+#define QGS_OFFLINE_EDITING_PLUGIN_GUI_H
+
+#include <QDialog>
+#include <ui_offline_editing_plugin_guibase.h>
+
+class QgsOfflineEditingPluginGui : public QDialog, private Ui::QgsOfflineEditingPluginGuiBase
+{
+ Q_OBJECT
+
+ public:
+ QgsOfflineEditingPluginGui( QWidget* parent = 0, Qt::WFlags fl = 0 );
+ virtual ~QgsOfflineEditingPluginGui();
+
+ QString offlineDataPath();
+ QString offlineDbFile();
+ QStringList& selectedLayerIds();
+
+ private:
+ static const int context_id = 0;
+ QString mOfflineDataPath;
+ QString mOfflineDbFile;
+ QStringList mSelectedLayerIds;
+
+ private slots:
+ void on_butBrowse_clicked();
+ void on_buttonBox_accepted();
+ void on_buttonBox_rejected();
+ void on_buttonBox_helpRequested();
+};
+
+#endif // QGS_OFFLINE_EDITING_PLUGIN_GUI_H
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_guibase.ui
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_guibase.ui (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_plugin_guibase.ui 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QgsOfflineEditingPluginGuiBase</class>
+ <widget class="QDialog" name="QgsOfflineEditingPluginGuiBase">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>435</width>
+ <height>270</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Create offline project</string>
+ </property>
+ <property name="windowIcon">
+ <iconset>
+ <normaloff/>
+ </iconset>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Offline data</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="ui_offlineDataPath">
+ <property name="readOnly">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="butBrowse">
+ <property name="text">
+ <string>Browse...</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Select remote layers</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="ui_layerList">
+ <property name="selectionMode">
+ <enum>QAbstractItemView::MultiSelection</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <resources/>
+ <connections/>
+</ui>
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.cpp
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.cpp (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.cpp 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,62 @@
+/***************************************************************************
+ progress_dialog.h
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * 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 "offline_editing_progress_dialog.h"
+
+QgsOfflineEditingProgressDialog::QgsOfflineEditingProgressDialog( QWidget* parent /*= 0*/, Qt::WFlags fl /*= 0*/ )
+ : QDialog( parent, fl )
+{
+ setupUi( this );
+}
+
+QgsOfflineEditingProgressDialog::~QgsOfflineEditingProgressDialog()
+{
+}
+
+void QgsOfflineEditingProgressDialog::setTitle( const QString& title )
+{
+ setWindowTitle( title );
+}
+
+void QgsOfflineEditingProgressDialog::setCurrentLayer( int layer, int numLayers )
+{
+ label->setText( tr( "Layer %1 of %2.." ).arg( layer ).arg( numLayers ) );
+ progressBar->reset();
+}
+
+void QgsOfflineEditingProgressDialog::setupProgressBar( const QString& format, int maximum )
+{
+ progressBar->setFormat( format );
+ progressBar->setRange( 0, maximum );
+ progressBar->reset();
+
+ mProgressUpdate = maximum / 100;
+ if ( mProgressUpdate == 0 )
+ {
+ mProgressUpdate = 1;
+ }
+}
+
+void QgsOfflineEditingProgressDialog::setProgressValue( int value )
+{
+ // update progress every nth feature for faster processing
+ if ( value == progressBar->maximum() || value % mProgressUpdate == 0 )
+ {
+ progressBar->setValue( value );
+ }
+}
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.h
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.h (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog.h 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,43 @@
+/***************************************************************************
+ progress_dialog.h
+
+ Offline Editing Plugin
+ a QGIS plugin
+ --------------------------------------
+ Date : 08-Jul-2010
+ Copyright : (C) 2010 by Sourcepole
+ Email : info at sourcepole.ch
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef QGS_OFFLINE_EDITING_PROGRESS_DIALOG_H
+#define QGS_OFFLINE_EDITING_PROGRESS_DIALOG_H
+
+#include <QDialog>
+#include "ui_offline_editing_progress_dialog_base.h"
+
+class QgsOfflineEditingProgressDialog : public QDialog, private Ui::QgsOfflineEditingProgressDialogBase
+{
+ Q_OBJECT
+
+ public:
+ QgsOfflineEditingProgressDialog( QWidget* parent = 0, Qt::WFlags fl = 0 );
+ virtual ~QgsOfflineEditingProgressDialog();
+
+ void setTitle( const QString& title );
+ void setCurrentLayer( int layer, int numLayers );
+ void setupProgressBar( const QString& format, int maximum );
+ void setProgressValue( int value );
+
+ private:
+ int mProgressUpdate;
+};
+
+#endif // QGS_OFFLINE_EDITING_PROGRESS_DIALOG_H
+
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog_base.ui
===================================================================
--- trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog_base.ui (rev 0)
+++ trunk/qgis/src/plugins/offline_editing/offline_editing_progress_dialog_base.ui 2010-10-05 08:51:41 UTC (rev 14335)
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QgsOfflineEditingProgressDialogBase</class>
+ <widget class="QDialog" name="QgsOfflineEditingProgressDialogBase">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>55</height>
+ </rect>
+ </property>
+ <property name="contextMenuPolicy">
+ <enum>Qt::NoContextMenu</enum>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>TextLabel</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QProgressBar" name="progressBar">
+ <property name="value">
+ <number>24</number>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
Added: trunk/qgis/src/plugins/offline_editing/offline_editing_sync.png
===================================================================
(Binary files differ)
Property changes on: trunk/qgis/src/plugins/offline_editing/offline_editing_sync.png
___________________________________________________________________
Added: svn:mime-type
+ application/octet-stream
More information about the QGIS-commit
mailing list