[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