QGIS API Documentation  3.14.0-Pi (9f7028fd23)
qgsofflineediting.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  offline_editing.cpp
3 
4  Offline Editing Plugin
5  a QGIS plugin
6  --------------------------------------
7  Date : 22-Jul-2010
8  Copyright : (C) 2010 by Sourcepole
9  Email : info at sourcepole.ch
10  ***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
18 
19 
20 #include "qgsapplication.h"
21 #include "qgsdatasourceuri.h"
22 #include "qgsgeometry.h"
23 #include "qgslayertreegroup.h"
24 #include "qgslayertreelayer.h"
25 #include "qgsmaplayer.h"
26 #include "qgsofflineediting.h"
27 #include "qgsproject.h"
28 #include "qgsvectordataprovider.h"
31 #include "qgsspatialiteutils.h"
32 #include "qgsfeatureiterator.h"
33 #include "qgslogger.h"
34 #include "qgsvectorlayerutils.h"
35 #include "qgsrelationmanager.h"
36 #include "qgsmapthemecollection.h"
37 #include "qgslayertree.h"
38 #include "qgsogrutils.h"
39 #include "qgsvectorfilewriter.h"
40 #include "qgsvectorlayer.h"
41 #include "qgsproviderregistry.h"
42 #include "qgsprovidermetadata.h"
44 
45 #include <QDir>
46 #include <QDomDocument>
47 #include <QDomNode>
48 #include <QFile>
49 #include <QMessageBox>
50 
51 #include <ogr_srs_api.h>
52 
53 extern "C"
54 {
55 #include <sqlite3.h>
56 #include <spatialite.h>
57 }
58 
59 #define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE "isOfflineEditable"
60 #define CUSTOM_PROPERTY_REMOTE_SOURCE "remoteSource"
61 #define CUSTOM_PROPERTY_REMOTE_PROVIDER "remoteProvider"
62 #define CUSTOM_SHOW_FEATURE_COUNT "showFeatureCount"
63 #define PROJECT_ENTRY_SCOPE_OFFLINE "OfflineEditingPlugin"
64 #define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH "/OfflineDbPath"
65 
67 {
68  connect( QgsProject::instance(), &QgsProject::layerWasAdded, this, &QgsOfflineEditing::layerAdded );
69 }
70 
87 bool QgsOfflineEditing::convertToOfflineProject( const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected, ContainerType containerType )
88 {
89  if ( layerIds.isEmpty() )
90  {
91  return false;
92  }
93 
94  QString dbPath = QDir( offlineDataPath ).absoluteFilePath( offlineDbFile );
95  if ( createOfflineDb( dbPath, containerType ) )
96  {
98  int rc = database.open( dbPath );
99  if ( rc != SQLITE_OK )
100  {
101  showWarning( tr( "Could not open the SpatiaLite database" ) );
102  }
103  else
104  {
105  // create logging tables
106  createLoggingTables( database.get() );
107 
108  emit progressStarted();
109 
110  QMap<QString, QgsVectorJoinList > joinInfoBuffer;
111  QMap<QString, QgsVectorLayer *> layerIdMapping;
112 
113  for ( const QString &layerId : layerIds )
114  {
115  QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerId );
116  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
117  if ( !vl )
118  continue;
119  QgsVectorJoinList joins = vl->vectorJoins();
120 
121  // Layer names will be appended an _offline suffix
122  // Join fields are prefixed with the layer name and we do not want the
123  // field name to change so we stabilize the field name by defining a
124  // custom prefix with the layername without _offline suffix.
125  QgsVectorJoinList::iterator joinIt = joins.begin();
126  while ( joinIt != joins.end() )
127  {
128  if ( joinIt->prefix().isNull() )
129  {
130  QgsVectorLayer *vl = joinIt->joinLayer();
131 
132  if ( vl )
133  joinIt->setPrefix( vl->name() + '_' );
134  }
135  ++joinIt;
136  }
137  joinInfoBuffer.insert( vl->id(), joins );
138  }
139 
141 
142  // copy selected vector layers to offline layer
143  for ( int i = 0; i < layerIds.count(); i++ )
144  {
145  emit layerProgressUpdated( i + 1, layerIds.count() );
146 
147  QgsMapLayer *layer = QgsProject::instance()->mapLayer( layerIds.at( i ) );
148  QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer );
149  if ( vl )
150  {
151  QString origLayerId = vl->id();
152  QgsVectorLayer *newLayer = copyVectorLayer( vl, database.get(), dbPath, onlySelected, containerType );
153  if ( newLayer )
154  {
155  layerIdMapping.insert( origLayerId, newLayer );
156  //append individual layer setting on snapping settings
157  snappingConfig.setIndividualLayerSettings( newLayer, snappingConfig.individualLayerSettings( vl ) );
158  snappingConfig.removeLayers( QList<QgsMapLayer *>() << vl );
159 
160  // remove remote layer
162  QStringList() << origLayerId );
163  }
164  }
165  }
166 
167  QgsProject::instance()->setSnappingConfig( snappingConfig );
168 
169  // restore join info on new offline layer
170  QMap<QString, QgsVectorJoinList >::ConstIterator it;
171  for ( it = joinInfoBuffer.constBegin(); it != joinInfoBuffer.constEnd(); ++it )
172  {
173  QgsVectorLayer *newLayer = layerIdMapping.value( it.key() );
174 
175  if ( newLayer )
176  {
177  const QList<QgsVectorLayerJoinInfo> joins = it.value();
178  for ( QgsVectorLayerJoinInfo join : joins )
179  {
180  QgsVectorLayer *newJoinedLayer = layerIdMapping.value( join.joinLayerId() );
181  if ( newJoinedLayer )
182  {
183  // If the layer has been offline'd, update join information
184  join.setJoinLayer( newJoinedLayer );
185  }
186  newLayer->addJoin( join );
187  }
188  }
189  }
190 
191  emit progressStopped();
192 
193  // save offline project
194  QString projectTitle = QgsProject::instance()->title();
195  if ( projectTitle.isEmpty() )
196  {
197  projectTitle = QFileInfo( QgsProject::instance()->fileName() ).fileName();
198  }
199  projectTitle += QLatin1String( " (offline)" );
200  QgsProject::instance()->setTitle( projectTitle );
201 
203 
204  return true;
205  }
206  }
207 
208  return false;
209 }
210 
212 {
214 }
215 
217 {
218  // open logging db
219  sqlite3_database_unique_ptr database = openLoggingDb();
220  if ( !database )
221  {
222  return;
223  }
224 
225  emit progressStarted();
226 
228 
229  // restore and sync remote layers
230  QList<QgsMapLayer *> offlineLayers;
231  QMap<QString, QgsMapLayer *> mapLayers = QgsProject::instance()->mapLayers();
232  for ( QMap<QString, QgsMapLayer *>::iterator layer_it = mapLayers.begin() ; layer_it != mapLayers.end(); ++layer_it )
233  {
234  QgsMapLayer *layer = layer_it.value();
235  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
236  {
237  offlineLayers << layer;
238  }
239  }
240 
241  QgsDebugMsgLevel( QStringLiteral( "Found %1 offline layers" ).arg( offlineLayers.count() ), 4 );
242  for ( int l = 0; l < offlineLayers.count(); l++ )
243  {
244  QgsMapLayer *layer = offlineLayers.at( l );
245 
246  emit layerProgressUpdated( l + 1, offlineLayers.count() );
247 
248  QString remoteSource = layer->customProperty( CUSTOM_PROPERTY_REMOTE_SOURCE, "" ).toString();
249  QString remoteProvider = layer->customProperty( CUSTOM_PROPERTY_REMOTE_PROVIDER, "" ).toString();
250  QString remoteName = layer->name();
251  remoteName.remove( QRegExp( " \\(offline\\)$" ) );
253  QgsVectorLayer *remoteLayer = new QgsVectorLayer( remoteSource, remoteName, remoteProvider, options );
254  if ( remoteLayer->isValid() )
255  {
256  // Rebuild WFS cache to get feature id<->GML fid mapping
257  if ( remoteLayer->providerType().contains( QLatin1String( "WFS" ), Qt::CaseInsensitive ) )
258  {
259  QgsFeatureIterator fit = remoteLayer->getFeatures();
260  QgsFeature f;
261  while ( fit.nextFeature( f ) )
262  {
263  }
264  }
265  // TODO: only add remote layer if there are log entries?
266 
267  QgsVectorLayer *offlineLayer = qobject_cast<QgsVectorLayer *>( layer );
268 
269  // register this layer with the central layers registry
270  QgsProject::instance()->addMapLayers( QList<QgsMapLayer *>() << remoteLayer, true );
271 
272  // copy style
273  copySymbology( offlineLayer, remoteLayer );
274  updateRelations( offlineLayer, remoteLayer );
275  updateMapThemes( offlineLayer, remoteLayer );
276  updateLayerOrder( offlineLayer, remoteLayer );
277 
278  //append individual layer setting on snapping settings
279  snappingConfig.setIndividualLayerSettings( remoteLayer, snappingConfig.individualLayerSettings( offlineLayer ) );
280  snappingConfig.removeLayers( QList<QgsMapLayer *>() << offlineLayer );
281 
282  //set QgsLayerTreeNode properties back
283  QgsLayerTreeLayer *layerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( offlineLayer->id() );
284  QgsLayerTreeLayer *newLayerTreeLayer = QgsProject::instance()->layerTreeRoot()->findLayer( remoteLayer->id() );
286 
287  // apply layer edit log
288  QString qgisLayerId = layer->id();
289  QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
290  int layerId = sqlQueryInt( database.get(), sql, -1 );
291  if ( layerId != -1 )
292  {
293  remoteLayer->startEditing();
294 
295  // TODO: only get commitNos of this layer?
296  int commitNo = getCommitNo( database.get() );
297  QgsDebugMsgLevel( QStringLiteral( "Found %1 commits" ).arg( commitNo ), 4 );
298  for ( int i = 0; i < commitNo; i++ )
299  {
300  QgsDebugMsgLevel( QStringLiteral( "Apply commits chronologically" ), 4 );
301  // apply commits chronologically
302  applyAttributesAdded( remoteLayer, database.get(), layerId, i );
303  applyAttributeValueChanges( offlineLayer, remoteLayer, database.get(), layerId, i );
304  applyGeometryChanges( remoteLayer, database.get(), layerId, i );
305  }
306 
307  applyFeaturesAdded( offlineLayer, remoteLayer, database.get(), layerId );
308  applyFeaturesRemoved( remoteLayer, database.get(), layerId );
309 
310  if ( remoteLayer->commitChanges() )
311  {
312  // update fid lookup
313  updateFidLookup( remoteLayer, database.get(), layerId );
314 
315  // clear edit log for this layer
316  sql = QStringLiteral( "DELETE FROM 'log_added_attrs' WHERE \"layer_id\" = %1" ).arg( layerId );
317  sqlExec( database.get(), sql );
318  sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
319  sqlExec( database.get(), sql );
320  sql = QStringLiteral( "DELETE FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
321  sqlExec( database.get(), sql );
322  sql = QStringLiteral( "DELETE FROM 'log_feature_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
323  sqlExec( database.get(), sql );
324  sql = QStringLiteral( "DELETE FROM 'log_geometry_updates' WHERE \"layer_id\" = %1" ).arg( layerId );
325  sqlExec( database.get(), sql );
326  }
327  else
328  {
329  showWarning( remoteLayer->commitErrors().join( QStringLiteral( "\n" ) ) );
330  }
331  }
332  else
333  {
334  QgsDebugMsg( QStringLiteral( "Could not find the layer id in the edit logs!" ) );
335  }
336  // Invalidate the connection to force a reload if the project is put offline
337  // again with the same path
338  offlineLayer->dataProvider()->invalidateConnections( QgsDataSourceUri( offlineLayer->source() ).database() );
339  // remove offline layer
340  QgsProject::instance()->removeMapLayers( QStringList() << qgisLayerId );
341 
342 
343  // disable offline project
344  QString projectTitle = QgsProject::instance()->title();
345  projectTitle.remove( QRegExp( " \\(offline\\)$" ) );
346  QgsProject::instance()->setTitle( projectTitle );
348  remoteLayer->reload(); //update with other changes
349  }
350  else
351  {
352  QgsDebugMsg( QStringLiteral( "Remote layer is not valid!" ) );
353  }
354  }
355 
356  // reset commitNo
357  QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = 0 WHERE \"name\" = 'commit_no'" );
358  sqlExec( database.get(), sql );
359 
360  QgsProject::instance()->setSnappingConfig( snappingConfig );
361 
362  emit progressStopped();
363 }
364 
365 void QgsOfflineEditing::initializeSpatialMetadata( sqlite3 *sqlite_handle )
366 {
367  // attempting to perform self-initialization for a newly created DB
368  if ( !sqlite_handle )
369  return;
370  // checking if this DB is really empty
371  char **results = nullptr;
372  int rows, columns;
373  int ret = sqlite3_get_table( sqlite_handle, "select count(*) from sqlite_master", &results, &rows, &columns, nullptr );
374  if ( ret != SQLITE_OK )
375  return;
376  int count = 0;
377  if ( rows >= 1 )
378  {
379  for ( int i = 1; i <= rows; i++ )
380  count = atoi( results[( i * columns ) + 0] );
381  }
382 
383  sqlite3_free_table( results );
384 
385  if ( count > 0 )
386  return;
387 
388  bool above41 = false;
389  ret = sqlite3_get_table( sqlite_handle, "select spatialite_version()", &results, &rows, &columns, nullptr );
390  if ( ret == SQLITE_OK && rows == 1 && columns == 1 )
391  {
392  QString version = QString::fromUtf8( results[1] );
393  QStringList parts = version.split( ' ', QString::SkipEmptyParts );
394  if ( !parts.empty() )
395  {
396  QStringList verparts = parts.at( 0 ).split( '.', QString::SkipEmptyParts );
397  above41 = verparts.size() >= 2 && ( verparts.at( 0 ).toInt() > 4 || ( verparts.at( 0 ).toInt() == 4 && verparts.at( 1 ).toInt() >= 1 ) );
398  }
399  }
400 
401  sqlite3_free_table( results );
402 
403  // all right, it's empty: proceeding to initialize
404  char *errMsg = nullptr;
405  ret = sqlite3_exec( sqlite_handle, above41 ? "SELECT InitSpatialMetadata(1)" : "SELECT InitSpatialMetadata()", nullptr, nullptr, &errMsg );
406 
407  if ( ret != SQLITE_OK )
408  {
409  QString errCause = tr( "Unable to initialize SpatialMetadata:\n" );
410  errCause += QString::fromUtf8( errMsg );
411  showWarning( errCause );
412  sqlite3_free( errMsg );
413  return;
414  }
415  spatial_ref_sys_init( sqlite_handle, 0 );
416 }
417 
418 bool QgsOfflineEditing::createOfflineDb( const QString &offlineDbPath, ContainerType containerType )
419 {
420  int ret;
421  char *errMsg = nullptr;
422  QFile newDb( offlineDbPath );
423  if ( newDb.exists() )
424  {
425  QFile::remove( offlineDbPath );
426  }
427 
428  // see also QgsNewSpatialiteLayerDialog::createDb()
429 
430  QFileInfo fullPath = QFileInfo( offlineDbPath );
431  QDir path = fullPath.dir();
432 
433  // Must be sure there is destination directory ~/.qgis
434  QDir().mkpath( path.absolutePath() );
435 
436  // creating/opening the new database
437  QString dbPath = newDb.fileName();
438 
439  // creating geopackage
440  switch ( containerType )
441  {
442  case GPKG:
443  {
444  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
445  if ( !hGpkgDriver )
446  {
447  showWarning( tr( "Creation of database failed. GeoPackage driver not found." ) );
448  return false;
449  }
450 
451  gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, dbPath.toUtf8().constData(), nullptr ) );
452  if ( !hDS )
453  {
454  showWarning( tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
455  return false;
456  }
457  break;
458  }
459  case SpatiaLite:
460  {
461  break;
462  }
463  }
464 
466  ret = database.open_v2( dbPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
467  if ( ret )
468  {
469  // an error occurred
470  QString errCause = tr( "Could not create a new database\n" );
471  errCause += database.errorMessage();
472  showWarning( errCause );
473  return false;
474  }
475  // activating Foreign Key constraints
476  ret = sqlite3_exec( database.get(), "PRAGMA foreign_keys = 1", nullptr, nullptr, &errMsg );
477  if ( ret != SQLITE_OK )
478  {
479  showWarning( tr( "Unable to activate FOREIGN_KEY constraints" ) );
480  sqlite3_free( errMsg );
481  return false;
482  }
483  initializeSpatialMetadata( database.get() );
484  return true;
485 }
486 
487 void QgsOfflineEditing::createLoggingTables( sqlite3 *db )
488 {
489  // indices
490  QString sql = QStringLiteral( "CREATE TABLE 'log_indices' ('name' TEXT, 'last_index' INTEGER)" );
491  sqlExec( db, sql );
492 
493  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('commit_no', 0)" );
494  sqlExec( db, sql );
495 
496  sql = QStringLiteral( "INSERT INTO 'log_indices' VALUES ('layer_id', 0)" );
497  sqlExec( db, sql );
498 
499  // layername <-> layer id
500  sql = QStringLiteral( "CREATE TABLE 'log_layer_ids' ('id' INTEGER, 'qgis_id' TEXT)" );
501  sqlExec( db, sql );
502 
503  // offline fid <-> remote fid
504  sql = QStringLiteral( "CREATE TABLE 'log_fids' ('layer_id' INTEGER, 'offline_fid' INTEGER, 'remote_fid' INTEGER)" );
505  sqlExec( db, sql );
506 
507  // added attributes
508  sql = QStringLiteral( "CREATE TABLE 'log_added_attrs' ('layer_id' INTEGER, 'commit_no' INTEGER, " );
509  sql += QLatin1String( "'name' TEXT, 'type' INTEGER, 'length' INTEGER, 'precision' INTEGER, 'comment' TEXT)" );
510  sqlExec( db, sql );
511 
512  // added features
513  sql = QStringLiteral( "CREATE TABLE 'log_added_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
514  sqlExec( db, sql );
515 
516  // removed features
517  sql = QStringLiteral( "CREATE TABLE 'log_removed_features' ('layer_id' INTEGER, 'fid' INTEGER)" );
518  sqlExec( db, sql );
519 
520  // feature updates
521  sql = QStringLiteral( "CREATE TABLE 'log_feature_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'attr' INTEGER, 'value' TEXT)" );
522  sqlExec( db, sql );
523 
524  // geometry updates
525  sql = QStringLiteral( "CREATE TABLE 'log_geometry_updates' ('layer_id' INTEGER, 'commit_no' INTEGER, 'fid' INTEGER, 'geom_wkt' TEXT)" );
526  sqlExec( db, sql );
527 
528  /* TODO: other logging tables
529  - attr delete (not supported by SpatiaLite provider)
530  */
531 }
532 
533 QgsVectorLayer *QgsOfflineEditing::copyVectorLayer( QgsVectorLayer *layer, sqlite3 *db, const QString &offlineDbPath, bool onlySelected, ContainerType containerType )
534 {
535  if ( !layer )
536  return nullptr;
537 
538  QString tableName = layer->id();
539  QgsDebugMsgLevel( QStringLiteral( "Creating offline table %1 ..." ).arg( tableName ), 4 );
540 
541  // new layer
542  QgsVectorLayer *newLayer = nullptr;
543 
544  switch ( containerType )
545  {
546  case SpatiaLite:
547  {
548  // create table
549  QString sql = QStringLiteral( "CREATE TABLE '%1' (" ).arg( tableName );
550  QString delim;
551  const QgsFields providerFields = layer->dataProvider()->fields();
552  for ( const auto &field : providerFields )
553  {
554  QString dataType;
555  QVariant::Type type = field.type();
556  if ( type == QVariant::Int || type == QVariant::LongLong )
557  {
558  dataType = QStringLiteral( "INTEGER" );
559  }
560  else if ( type == QVariant::Double )
561  {
562  dataType = QStringLiteral( "REAL" );
563  }
564  else if ( type == QVariant::String )
565  {
566  dataType = QStringLiteral( "TEXT" );
567  }
568  else
569  {
570  showWarning( tr( "%1: Unknown data type %2. Not using type affinity for the field." ).arg( field.name(), QVariant::typeToName( type ) ) );
571  }
572 
573  sql += delim + QStringLiteral( "'%1' %2" ).arg( field.name(), dataType );
574  delim = ',';
575  }
576  sql += ')';
577 
578  int rc = sqlExec( db, sql );
579 
580  // add geometry column
581  if ( layer->isSpatial() )
582  {
583  const QgsWkbTypes::Type sourceWkbType = layer->wkbType();
584 
585  QString geomType;
586  switch ( QgsWkbTypes::flatType( sourceWkbType ) )
587  {
588  case QgsWkbTypes::Point:
589  geomType = QStringLiteral( "POINT" );
590  break;
592  geomType = QStringLiteral( "MULTIPOINT" );
593  break;
595  geomType = QStringLiteral( "LINESTRING" );
596  break;
598  geomType = QStringLiteral( "MULTILINESTRING" );
599  break;
601  geomType = QStringLiteral( "POLYGON" );
602  break;
604  geomType = QStringLiteral( "MULTIPOLYGON" );
605  break;
606  default:
607  showWarning( tr( "Layer %1 has unsupported geometry type %2." ).arg( layer->name(), QgsWkbTypes::displayString( layer->wkbType() ) ) );
608  break;
609  };
610 
611  QString zmInfo = QStringLiteral( "XY" );
612 
613  if ( QgsWkbTypes::hasZ( sourceWkbType ) )
614  zmInfo += 'Z';
615  if ( QgsWkbTypes::hasM( sourceWkbType ) )
616  zmInfo += 'M';
617 
618  QString epsgCode;
619 
620  if ( layer->crs().authid().startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
621  {
622  epsgCode = layer->crs().authid().mid( 5 );
623  }
624  else
625  {
626  epsgCode = '0';
627  showWarning( tr( "Layer %1 has unsupported Coordinate Reference System (%2)." ).arg( layer->name(), layer->crs().authid() ) );
628  }
629 
630  QString sqlAddGeom = QStringLiteral( "SELECT AddGeometryColumn('%1', 'Geometry', %2, '%3', '%4')" )
631  .arg( tableName, epsgCode, geomType, zmInfo );
632 
633  // create spatial index
634  QString sqlCreateIndex = QStringLiteral( "SELECT CreateSpatialIndex('%1', 'Geometry')" ).arg( tableName );
635 
636  if ( rc == SQLITE_OK )
637  {
638  rc = sqlExec( db, sqlAddGeom );
639  if ( rc == SQLITE_OK )
640  {
641  rc = sqlExec( db, sqlCreateIndex );
642  }
643  }
644  }
645 
646  if ( rc != SQLITE_OK )
647  {
648  showWarning( tr( "Filling SpatiaLite for layer %1 failed" ).arg( layer->name() ) );
649  return nullptr;
650  }
651 
652  // add new layer
653  QString connectionString = QStringLiteral( "dbname='%1' table='%2'%3 sql=" )
654  .arg( offlineDbPath,
655  tableName, layer->isSpatial() ? "(Geometry)" : "" );
657  newLayer = new QgsVectorLayer( connectionString,
658  layer->name() + " (offline)", QStringLiteral( "spatialite" ), options );
659  break;
660  }
661  case GPKG:
662  {
663  // Set options
664  char **options = nullptr;
665 
666  options = CSLSetNameValue( options, "OVERWRITE", "YES" );
667  options = CSLSetNameValue( options, "IDENTIFIER", tr( "%1 (offline)" ).arg( layer->id() ).toUtf8().constData() );
668  options = CSLSetNameValue( options, "DESCRIPTION", layer->dataComment().toUtf8().constData() );
669 
670  //the FID-name should not exist in the original data
671  QString fidBase( QStringLiteral( "fid" ) );
672  QString fid = fidBase;
673  int counter = 1;
674  while ( layer->dataProvider()->fields().lookupField( fid ) >= 0 && counter < 10000 )
675  {
676  fid = fidBase + '_' + QString::number( counter );
677  counter++;
678  }
679  if ( counter == 10000 )
680  {
681  showWarning( tr( "Cannot make FID-name for GPKG " ) );
682  return nullptr;
683  }
684 
685  options = CSLSetNameValue( options, "FID", fid.toUtf8().constData() );
686 
687  if ( layer->isSpatial() )
688  {
689  options = CSLSetNameValue( options, "GEOMETRY_COLUMN", "geom" );
690  options = CSLSetNameValue( options, "SPATIAL_INDEX", "YES" );
691  }
692 
693  OGRSFDriverH hDriver = nullptr;
694  OGRSpatialReferenceH hSRS = OSRNewSpatialReference( layer->crs().toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL ).toLocal8Bit().data() );
695  gdal::ogr_datasource_unique_ptr hDS( OGROpen( offlineDbPath.toUtf8().constData(), true, &hDriver ) );
696  OGRLayerH hLayer = OGR_DS_CreateLayer( hDS.get(), tableName.toUtf8().constData(), hSRS, static_cast<OGRwkbGeometryType>( layer->wkbType() ), options );
697  CSLDestroy( options );
698  if ( hSRS )
699  OSRRelease( hSRS );
700  if ( !hLayer )
701  {
702  showWarning( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
703  return nullptr;
704  }
705 
706  const QgsFields providerFields = layer->dataProvider()->fields();
707  for ( const auto &field : providerFields )
708  {
709  const QString fieldName( field.name() );
710  const QVariant::Type type = field.type();
711  OGRFieldType ogrType( OFTString );
712  OGRFieldSubType ogrSubType = OFSTNone;
713  if ( type == QVariant::Int )
714  ogrType = OFTInteger;
715  else if ( type == QVariant::LongLong )
716  ogrType = OFTInteger64;
717  else if ( type == QVariant::Double )
718  ogrType = OFTReal;
719  else if ( type == QVariant::Time )
720  ogrType = OFTTime;
721  else if ( type == QVariant::Date )
722  ogrType = OFTDate;
723  else if ( type == QVariant::DateTime )
724  ogrType = OFTDateTime;
725  else if ( type == QVariant::Bool )
726  {
727  ogrType = OFTInteger;
728  ogrSubType = OFSTBoolean;
729  }
730  else
731  ogrType = OFTString;
732 
733  int ogrWidth = field.length();
734 
735  gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( fieldName.toUtf8().constData(), ogrType ) );
736  OGR_Fld_SetWidth( fld.get(), ogrWidth );
737  if ( ogrSubType != OFSTNone )
738  OGR_Fld_SetSubType( fld.get(), ogrSubType );
739 
740  if ( OGR_L_CreateField( hLayer, fld.get(), true ) != OGRERR_NONE )
741  {
742  showWarning( tr( "Creation of field %1 failed (OGR error: %2)" )
743  .arg( fieldName, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
744  return nullptr;
745  }
746  }
747 
748  // In GDAL >= 2.0, the driver implements a deferred creation strategy, so
749  // issue a command that will force table creation
750  CPLErrorReset();
751  OGR_L_ResetReading( hLayer );
752  if ( CPLGetLastErrorType() != CE_None )
753  {
754  QString msg( tr( "Creation of layer failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
755  showWarning( msg );
756  return nullptr;
757  }
758  hDS.reset();
759 
760  QString uri = QStringLiteral( "%1|layername=%2" ).arg( offlineDbPath, tableName );
762  newLayer = new QgsVectorLayer( uri, layer->name() + " (offline)", QStringLiteral( "ogr" ), layerOptions );
763  break;
764  }
765  }
766 
767  if ( newLayer->isValid() )
768  {
769 
770  // copy features
771  newLayer->startEditing();
772  QgsFeature f;
773 
774  QgsFeatureRequest req;
775 
776  if ( onlySelected )
777  {
778  QgsFeatureIds selectedFids = layer->selectedFeatureIds();
779  if ( !selectedFids.isEmpty() )
780  req.setFilterFids( selectedFids );
781  }
782 
783  QgsFeatureIterator fit = layer->dataProvider()->getFeatures( req );
784 
786  {
788  }
789  else
790  {
792  }
793  int featureCount = 1;
794 
795  QList<QgsFeatureId> remoteFeatureIds;
796  while ( fit.nextFeature( f ) )
797  {
798  remoteFeatureIds << f.id();
799 
800  // NOTE: SpatiaLite provider ignores position of geometry column
801  // fill gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
802  int column = 0;
803  QgsAttributes attrs = f.attributes();
804  // on GPKG newAttrs has an addition FID attribute, so we have to add a dummy in the original set
805  QgsAttributes newAttrs( containerType == GPKG ? attrs.count() + 1 : attrs.count() );
806  for ( int it = 0; it < attrs.count(); ++it )
807  {
808  newAttrs[column++] = attrs.at( it );
809  }
810  f.setAttributes( newAttrs );
811 
812  newLayer->addFeature( f );
813 
814  emit progressUpdated( featureCount++ );
815  }
816  if ( newLayer->commitChanges() )
817  {
819  featureCount = 1;
820 
821  // update feature id lookup
822  int layerId = getOrCreateLayerId( db, newLayer->id() );
823  QList<QgsFeatureId> offlineFeatureIds;
824 
825  QgsFeatureIterator fit = newLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
826  while ( fit.nextFeature( f ) )
827  {
828  offlineFeatureIds << f.id();
829  }
830 
831  // NOTE: insert fids in this loop, as the db is locked during newLayer->nextFeature()
832  sqlExec( db, QStringLiteral( "BEGIN" ) );
833  int remoteCount = remoteFeatureIds.size();
834  for ( int i = 0; i < remoteCount; i++ )
835  {
836  // Check if the online feature has been fetched (WFS download aborted for some reason)
837  if ( i < offlineFeatureIds.count() )
838  {
839  addFidLookup( db, layerId, offlineFeatureIds.at( i ), remoteFeatureIds.at( i ) );
840  }
841  else
842  {
843  showWarning( tr( "Feature cannot be copied to the offline layer, please check if the online layer '%1' is still accessible." ).arg( layer->name() ) );
844  return nullptr;
845  }
846  emit progressUpdated( featureCount++ );
847  }
848  sqlExec( db, QStringLiteral( "COMMIT" ) );
849  }
850  else
851  {
852  showWarning( newLayer->commitErrors().join( QStringLiteral( "\n" ) ) );
853  }
854 
855  // copy the custom properties from original layer
856  newLayer->setCustomProperties( layer->customProperties() );
857 
858  // mark as offline layer
860 
861  // store original layer source
864 
865  // register this layer with the central layers registry
867  QList<QgsMapLayer *>() << newLayer );
868 
869  // copy style
870  copySymbology( layer, newLayer );
871 
872  //remove constrainst of fields that use defaultValueClauses from provider on original
873  const auto fields = layer->fields();
874  for ( const QgsField &field : fields )
875  {
876  if ( !layer->dataProvider()->defaultValueClause( layer->fields().fieldOriginIndex( layer->fields().indexOf( field.name() ) ) ).isEmpty() )
877  {
878  newLayer->removeFieldConstraint( newLayer->fields().indexOf( field.name() ), QgsFieldConstraints::ConstraintNotNull );
879  }
880  }
881 
883  // Find the parent group of the original layer
884  QgsLayerTreeLayer *layerTreeLayer = layerTreeRoot->findLayer( layer->id() );
885  if ( layerTreeLayer )
886  {
887  QgsLayerTreeGroup *parentTreeGroup = qobject_cast<QgsLayerTreeGroup *>( layerTreeLayer->parent() );
888  if ( parentTreeGroup )
889  {
890  int index = parentTreeGroup->children().indexOf( layerTreeLayer );
891  // Move the new layer from the root group to the new group
892  QgsLayerTreeLayer *newLayerTreeLayer = layerTreeRoot->findLayer( newLayer->id() );
893  if ( newLayerTreeLayer )
894  {
895  QgsLayerTreeNode *newLayerTreeLayerClone = newLayerTreeLayer->clone();
896  //copy the showFeatureCount property to the new node
897  newLayerTreeLayerClone->setCustomProperty( CUSTOM_SHOW_FEATURE_COUNT, layerTreeLayer->customProperty( CUSTOM_SHOW_FEATURE_COUNT ) );
898  newLayerTreeLayerClone->setItemVisibilityChecked( layerTreeLayer->isVisible() );
899  QgsLayerTreeGroup *grp = qobject_cast<QgsLayerTreeGroup *>( newLayerTreeLayer->parent() );
900  parentTreeGroup->insertChildNode( index, newLayerTreeLayerClone );
901  if ( grp )
902  grp->removeChildNode( newLayerTreeLayer );
903  }
904  }
905  }
906 
907  updateRelations( layer, newLayer );
908  updateMapThemes( layer, newLayer );
909  updateLayerOrder( layer, newLayer );
910 
911 
912 
913  }
914  return newLayer;
915 }
916 
917 void QgsOfflineEditing::applyAttributesAdded( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
918 {
919  QString sql = QStringLiteral( "SELECT \"name\", \"type\", \"length\", \"precision\", \"comment\" FROM 'log_added_attrs' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
920  QList<QgsField> fields = sqlQueryAttributesAdded( db, sql );
921 
922  const QgsVectorDataProvider *provider = remoteLayer->dataProvider();
923  QList<QgsVectorDataProvider::NativeType> nativeTypes = provider->nativeTypes();
924 
925  // NOTE: uses last matching QVariant::Type of nativeTypes
926  QMap < QVariant::Type, QString /*typeName*/ > typeNameLookup;
927  for ( int i = 0; i < nativeTypes.size(); i++ )
928  {
929  QgsVectorDataProvider::NativeType nativeType = nativeTypes.at( i );
930  typeNameLookup[ nativeType.mType ] = nativeType.mTypeName;
931  }
932 
933  emit progressModeSet( QgsOfflineEditing::AddFields, fields.size() );
934 
935  for ( int i = 0; i < fields.size(); i++ )
936  {
937  // lookup typename from layer provider
938  QgsField field = fields[i];
939  if ( typeNameLookup.contains( field.type() ) )
940  {
941  QString typeName = typeNameLookup[ field.type()];
942  field.setTypeName( typeName );
943  remoteLayer->addAttribute( field );
944  }
945  else
946  {
947  showWarning( QStringLiteral( "Could not add attribute '%1' of type %2" ).arg( field.name() ).arg( field.type() ) );
948  }
949 
950  emit progressUpdated( i + 1 );
951  }
952 }
953 
954 void QgsOfflineEditing::applyFeaturesAdded( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
955 {
956  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
957  const QList<int> featureIdInts = sqlQueryInts( db, sql );
958  QgsFeatureIds newFeatureIds;
959  for ( int id : featureIdInts )
960  {
961  newFeatureIds << id;
962  }
963 
964  QgsExpressionContext context = remoteLayer->createExpressionContext();
965 
966  // get new features from offline layer
967  QgsFeatureList features;
968  QgsFeatureIterator it = offlineLayer->getFeatures( QgsFeatureRequest().setFilterFids( newFeatureIds ) );
969  QgsFeature feature;
970  while ( it.nextFeature( feature ) )
971  {
972  features << feature;
973  }
974 
975  // copy features to remote layer
976  emit progressModeSet( QgsOfflineEditing::AddFeatures, features.size() );
977 
978  int i = 1;
979  int newAttrsCount = remoteLayer->fields().count();
980  for ( QgsFeatureList::iterator it = features.begin(); it != features.end(); ++it )
981  {
982  // NOTE: SpatiaLite provider ignores position of geometry column
983  // restore gap in QgsAttributeMap if geometry column is not last (WORKAROUND)
984  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
985  QgsAttributes newAttrs( newAttrsCount );
986  QgsAttributes attrs = it->attributes();
987  for ( int it = 0; it < attrs.count(); ++it )
988  {
989  newAttrs[ attrLookup[ it ] ] = attrs.at( it );
990  }
991 
992  // respect constraints and provider default values
993  QgsFeature f = QgsVectorLayerUtils::createFeature( remoteLayer, it->geometry(), newAttrs.toMap(), &context );
994  remoteLayer->addFeature( f );
995 
996  emit progressUpdated( i++ );
997  }
998 }
999 
1000 void QgsOfflineEditing::applyFeaturesRemoved( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
1001 {
1002  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_removed_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1003  QgsFeatureIds values = sqlQueryFeaturesRemoved( db, sql );
1004 
1005  emit progressModeSet( QgsOfflineEditing::RemoveFeatures, values.size() );
1006 
1007  int i = 1;
1008  for ( QgsFeatureIds::const_iterator it = values.constBegin(); it != values.constEnd(); ++it )
1009  {
1010  QgsFeatureId fid = remoteFid( db, layerId, *it );
1011  remoteLayer->deleteFeature( fid );
1012 
1013  emit progressUpdated( i++ );
1014  }
1015 }
1016 
1017 void QgsOfflineEditing::applyAttributeValueChanges( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1018 {
1019  QString sql = QStringLiteral( "SELECT \"fid\", \"attr\", \"value\" FROM 'log_feature_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2 " ).arg( layerId ).arg( commitNo );
1020  AttributeValueChanges values = sqlQueryAttributeValueChanges( db, sql );
1021 
1022  emit progressModeSet( QgsOfflineEditing::UpdateFeatures, values.size() );
1023 
1024  QMap<int, int> attrLookup = attributeLookup( offlineLayer, remoteLayer );
1025 
1026  for ( int i = 0; i < values.size(); i++ )
1027  {
1028  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
1029  QgsDebugMsgLevel( QStringLiteral( "Offline changeAttributeValue %1 = %2" ).arg( QString( attrLookup[ values.at( i ).attr ] ), values.at( i ).value ), 4 );
1030  remoteLayer->changeAttributeValue( fid, attrLookup[ values.at( i ).attr ], values.at( i ).value );
1031 
1032  emit progressUpdated( i + 1 );
1033  }
1034 }
1035 
1036 void QgsOfflineEditing::applyGeometryChanges( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId, int commitNo )
1037 {
1038  QString sql = QStringLiteral( "SELECT \"fid\", \"geom_wkt\" FROM 'log_geometry_updates' WHERE \"layer_id\" = %1 AND \"commit_no\" = %2" ).arg( layerId ).arg( commitNo );
1039  GeometryChanges values = sqlQueryGeometryChanges( db, sql );
1040 
1042 
1043  for ( int i = 0; i < values.size(); i++ )
1044  {
1045  QgsFeatureId fid = remoteFid( db, layerId, values.at( i ).fid );
1046  QgsGeometry newGeom = QgsGeometry::fromWkt( values.at( i ).geom_wkt );
1047  remoteLayer->changeGeometry( fid, newGeom );
1048 
1049  emit progressUpdated( i + 1 );
1050  }
1051 }
1052 
1053 void QgsOfflineEditing::updateFidLookup( QgsVectorLayer *remoteLayer, sqlite3 *db, int layerId )
1054 {
1055  // update fid lookup for added features
1056 
1057  // get remote added fids
1058  // NOTE: use QMap for sorted fids
1059  QMap < QgsFeatureId, bool /*dummy*/ > newRemoteFids;
1060  QgsFeature f;
1061 
1062  QgsFeatureIterator fit = remoteLayer->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ).setNoAttributes() );
1063 
1065 
1066  int i = 1;
1067  while ( fit.nextFeature( f ) )
1068  {
1069  if ( offlineFid( db, layerId, f.id() ) == -1 )
1070  {
1071  newRemoteFids[ f.id()] = true;
1072  }
1073 
1074  emit progressUpdated( i++ );
1075  }
1076 
1077  // get local added fids
1078  // NOTE: fids are sorted
1079  QString sql = QStringLiteral( "SELECT \"fid\" FROM 'log_added_features' WHERE \"layer_id\" = %1" ).arg( layerId );
1080  QList<int> newOfflineFids = sqlQueryInts( db, sql );
1081 
1082  if ( newRemoteFids.size() != newOfflineFids.size() )
1083  {
1084  //showWarning( QString( "Different number of new features on offline layer (%1) and remote layer (%2)" ).arg(newOfflineFids.size()).arg(newRemoteFids.size()) );
1085  }
1086  else
1087  {
1088  // add new fid lookups
1089  i = 0;
1090  sqlExec( db, QStringLiteral( "BEGIN" ) );
1091  for ( QMap<QgsFeatureId, bool>::const_iterator it = newRemoteFids.constBegin(); it != newRemoteFids.constEnd(); ++it )
1092  {
1093  addFidLookup( db, layerId, newOfflineFids.at( i++ ), it.key() );
1094  }
1095  sqlExec( db, QStringLiteral( "COMMIT" ) );
1096  }
1097 }
1098 
1099 void QgsOfflineEditing::copySymbology( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1100 {
1101  targetLayer->styleManager()->copyStylesFrom( sourceLayer->styleManager() );
1102 
1103  QString error;
1104  QDomDocument doc;
1105  QgsReadWriteContext context;
1106  QgsMapLayer::StyleCategories categories = static_cast<QgsMapLayer::StyleCategories>( QgsMapLayer::AllStyleCategories ) & ~QgsMapLayer::CustomProperties;
1107  sourceLayer->exportNamedStyle( doc, error, context, categories );
1108 
1109  if ( error.isEmpty() )
1110  {
1111  targetLayer->importNamedStyle( doc, error, categories );
1112  }
1113  if ( !error.isEmpty() )
1114  {
1115  showWarning( error );
1116  }
1117 }
1118 
1119 void QgsOfflineEditing::updateRelations( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1120 {
1122  const QList<QgsRelation> referencedRelations = relationManager->referencedRelations( sourceLayer );
1123 
1124  for ( QgsRelation relation : referencedRelations )
1125  {
1126  relationManager->removeRelation( relation );
1127  relation.setReferencedLayer( targetLayer->id() );
1128  relationManager->addRelation( relation );
1129  }
1130 
1131  const QList<QgsRelation> referencingRelations = relationManager->referencingRelations( sourceLayer );
1132 
1133  for ( QgsRelation relation : referencingRelations )
1134  {
1135  relationManager->removeRelation( relation );
1136  relation.setReferencingLayer( targetLayer->id() );
1137  relationManager->addRelation( relation );
1138  }
1139 }
1140 
1141 void QgsOfflineEditing::updateMapThemes( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1142 {
1144  const QStringList mapThemeNames = mapThemeCollection->mapThemes();
1145 
1146  for ( const QString &mapThemeName : mapThemeNames )
1147  {
1148  QgsMapThemeCollection::MapThemeRecord record = mapThemeCollection->mapThemeState( mapThemeName );
1149 
1150  const auto layerRecords = record.layerRecords();
1151 
1152  for ( QgsMapThemeCollection::MapThemeLayerRecord layerRecord : layerRecords )
1153  {
1154  if ( layerRecord.layer() == sourceLayer )
1155  {
1156  layerRecord.setLayer( targetLayer );
1157  record.removeLayerRecord( sourceLayer );
1158  record.addLayerRecord( layerRecord );
1159  }
1160  }
1161 
1162  QgsProject::instance()->mapThemeCollection()->update( mapThemeName, record );
1163  }
1164 }
1165 
1166 void QgsOfflineEditing::updateLayerOrder( QgsVectorLayer *sourceLayer, QgsVectorLayer *targetLayer )
1167 {
1168  QList<QgsMapLayer *> layerOrder = QgsProject::instance()->layerTreeRoot()->customLayerOrder();
1169 
1170  auto iterator = layerOrder.begin();
1171 
1172  while ( iterator != layerOrder.end() )
1173  {
1174  if ( *iterator == targetLayer )
1175  {
1176  iterator = layerOrder.erase( iterator );
1177  if ( iterator == layerOrder.end() )
1178  break;
1179  }
1180 
1181  if ( *iterator == sourceLayer )
1182  {
1183  *iterator = targetLayer;
1184  }
1185 
1186  ++iterator;
1187  }
1188 
1190 }
1191 
1192 // NOTE: use this to map column indices in case the remote geometry column is not last
1193 QMap<int, int> QgsOfflineEditing::attributeLookup( QgsVectorLayer *offlineLayer, QgsVectorLayer *remoteLayer )
1194 {
1195  const QgsAttributeList &offlineAttrs = offlineLayer->attributeList();
1196 
1197  QMap < int /*offline attr*/, int /*remote attr*/ > attrLookup;
1198  // NOTE: though offlineAttrs can have new attributes not yet synced, we take the amount of offlineAttrs
1199  // because we anyway only add mapping for the fields existing in remoteLayer (this because it could contain fid on 0)
1200  for ( int i = 0; i < offlineAttrs.size(); i++ )
1201  {
1202  if ( remoteLayer->fields().lookupField( offlineLayer->fields().field( i ).name() ) >= 0 )
1203  attrLookup.insert( offlineAttrs.at( i ), remoteLayer->fields().indexOf( offlineLayer->fields().field( i ).name() ) );
1204  }
1205 
1206  return attrLookup;
1207 }
1208 
1209 void QgsOfflineEditing::showWarning( const QString &message )
1210 {
1211  emit warning( tr( "Offline Editing Plugin" ), message );
1212 }
1213 
1214 sqlite3_database_unique_ptr QgsOfflineEditing::openLoggingDb()
1215 {
1216  sqlite3_database_unique_ptr database;
1218  if ( !dbPath.isEmpty() )
1219  {
1220  QString absoluteDbPath = QgsProject::instance()->readPath( dbPath );
1221  int rc = database.open( absoluteDbPath );
1222  if ( rc != SQLITE_OK )
1223  {
1224  QgsDebugMsg( QStringLiteral( "Could not open the SpatiaLite logging database" ) );
1225  showWarning( tr( "Could not open the SpatiaLite logging database" ) );
1226  }
1227  }
1228  else
1229  {
1230  QgsDebugMsg( QStringLiteral( "dbPath is empty!" ) );
1231  }
1232  return database;
1233 }
1234 
1235 int QgsOfflineEditing::getOrCreateLayerId( sqlite3 *db, const QString &qgisLayerId )
1236 {
1237  QString sql = QStringLiteral( "SELECT \"id\" FROM 'log_layer_ids' WHERE \"qgis_id\" = '%1'" ).arg( qgisLayerId );
1238  int layerId = sqlQueryInt( db, sql, -1 );
1239  if ( layerId == -1 )
1240  {
1241  // next layer id
1242  sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'layer_id'" );
1243  int newLayerId = sqlQueryInt( db, sql, -1 );
1244 
1245  // insert layer
1246  sql = QStringLiteral( "INSERT INTO 'log_layer_ids' VALUES (%1, '%2')" ).arg( newLayerId ).arg( qgisLayerId );
1247  sqlExec( db, sql );
1248 
1249  // increase layer_id
1250  // TODO: use trigger for auto increment?
1251  sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'layer_id'" ).arg( newLayerId + 1 );
1252  sqlExec( db, sql );
1253 
1254  layerId = newLayerId;
1255  }
1256 
1257  return layerId;
1258 }
1259 
1260 int QgsOfflineEditing::getCommitNo( sqlite3 *db )
1261 {
1262  QString sql = QStringLiteral( "SELECT \"last_index\" FROM 'log_indices' WHERE \"name\" = 'commit_no'" );
1263  return sqlQueryInt( db, sql, -1 );
1264 }
1265 
1266 void QgsOfflineEditing::increaseCommitNo( sqlite3 *db )
1267 {
1268  QString sql = QStringLiteral( "UPDATE 'log_indices' SET 'last_index' = %1 WHERE \"name\" = 'commit_no'" ).arg( getCommitNo( db ) + 1 );
1269  sqlExec( db, sql );
1270 }
1271 
1272 void QgsOfflineEditing::addFidLookup( sqlite3 *db, int layerId, QgsFeatureId offlineFid, QgsFeatureId remoteFid )
1273 {
1274  QString sql = QStringLiteral( "INSERT INTO 'log_fids' VALUES ( %1, %2, %3 )" ).arg( layerId ).arg( offlineFid ).arg( remoteFid );
1275  sqlExec( db, sql );
1276 }
1277 
1278 QgsFeatureId QgsOfflineEditing::remoteFid( sqlite3 *db, int layerId, QgsFeatureId offlineFid )
1279 {
1280  QString sql = QStringLiteral( "SELECT \"remote_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"offline_fid\" = %2" ).arg( layerId ).arg( offlineFid );
1281  return sqlQueryInt( db, sql, -1 );
1282 }
1283 
1284 QgsFeatureId QgsOfflineEditing::offlineFid( sqlite3 *db, int layerId, QgsFeatureId remoteFid )
1285 {
1286  QString sql = QStringLiteral( "SELECT \"offline_fid\" FROM 'log_fids' WHERE \"layer_id\" = %1 AND \"remote_fid\" = %2" ).arg( layerId ).arg( remoteFid );
1287  return sqlQueryInt( db, sql, -1 );
1288 }
1289 
1290 bool QgsOfflineEditing::isAddedFeature( sqlite3 *db, int layerId, QgsFeatureId fid )
1291 {
1292  QString sql = QStringLiteral( "SELECT COUNT(\"fid\") FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( fid );
1293  return ( sqlQueryInt( db, sql, 0 ) > 0 );
1294 }
1295 
1296 int QgsOfflineEditing::sqlExec( sqlite3 *db, const QString &sql )
1297 {
1298  char *errmsg = nullptr;
1299  int rc = sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, &errmsg );
1300  if ( rc != SQLITE_OK )
1301  {
1302  showWarning( errmsg );
1303  }
1304  return rc;
1305 }
1306 
1307 int QgsOfflineEditing::sqlQueryInt( sqlite3 *db, const QString &sql, int defaultValue )
1308 {
1309  sqlite3_stmt *stmt = nullptr;
1310  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1311  {
1312  showWarning( sqlite3_errmsg( db ) );
1313  return defaultValue;
1314  }
1315 
1316  int value = defaultValue;
1317  int ret = sqlite3_step( stmt );
1318  if ( ret == SQLITE_ROW )
1319  {
1320  value = sqlite3_column_int( stmt, 0 );
1321  }
1322  sqlite3_finalize( stmt );
1323 
1324  return value;
1325 }
1326 
1327 QList<int> QgsOfflineEditing::sqlQueryInts( sqlite3 *db, const QString &sql )
1328 {
1329  QList<int> values;
1330 
1331  sqlite3_stmt *stmt = nullptr;
1332  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1333  {
1334  showWarning( sqlite3_errmsg( db ) );
1335  return values;
1336  }
1337 
1338  int ret = sqlite3_step( stmt );
1339  while ( ret == SQLITE_ROW )
1340  {
1341  values << sqlite3_column_int( stmt, 0 );
1342 
1343  ret = sqlite3_step( stmt );
1344  }
1345  sqlite3_finalize( stmt );
1346 
1347  return values;
1348 }
1349 
1350 QList<QgsField> QgsOfflineEditing::sqlQueryAttributesAdded( sqlite3 *db, const QString &sql )
1351 {
1352  QList<QgsField> values;
1353 
1354  sqlite3_stmt *stmt = nullptr;
1355  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1356  {
1357  showWarning( sqlite3_errmsg( db ) );
1358  return values;
1359  }
1360 
1361  int ret = sqlite3_step( stmt );
1362  while ( ret == SQLITE_ROW )
1363  {
1364  QgsField field( QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) ) ),
1365  static_cast< QVariant::Type >( sqlite3_column_int( stmt, 1 ) ),
1366  QString(), // typeName
1367  sqlite3_column_int( stmt, 2 ),
1368  sqlite3_column_int( stmt, 3 ),
1369  QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 4 ) ) ) );
1370  values << field;
1371 
1372  ret = sqlite3_step( stmt );
1373  }
1374  sqlite3_finalize( stmt );
1375 
1376  return values;
1377 }
1378 
1379 QgsFeatureIds QgsOfflineEditing::sqlQueryFeaturesRemoved( sqlite3 *db, const QString &sql )
1380 {
1381  QgsFeatureIds values;
1382 
1383  sqlite3_stmt *stmt = nullptr;
1384  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1385  {
1386  showWarning( sqlite3_errmsg( db ) );
1387  return values;
1388  }
1389 
1390  int ret = sqlite3_step( stmt );
1391  while ( ret == SQLITE_ROW )
1392  {
1393  values << sqlite3_column_int( stmt, 0 );
1394 
1395  ret = sqlite3_step( stmt );
1396  }
1397  sqlite3_finalize( stmt );
1398 
1399  return values;
1400 }
1401 
1402 QgsOfflineEditing::AttributeValueChanges QgsOfflineEditing::sqlQueryAttributeValueChanges( sqlite3 *db, const QString &sql )
1403 {
1404  AttributeValueChanges values;
1405 
1406  sqlite3_stmt *stmt = nullptr;
1407  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1408  {
1409  showWarning( sqlite3_errmsg( db ) );
1410  return values;
1411  }
1412 
1413  int ret = sqlite3_step( stmt );
1414  while ( ret == SQLITE_ROW )
1415  {
1416  AttributeValueChange change;
1417  change.fid = sqlite3_column_int( stmt, 0 );
1418  change.attr = sqlite3_column_int( stmt, 1 );
1419  change.value = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) ) );
1420  values << change;
1421 
1422  ret = sqlite3_step( stmt );
1423  }
1424  sqlite3_finalize( stmt );
1425 
1426  return values;
1427 }
1428 
1429 QgsOfflineEditing::GeometryChanges QgsOfflineEditing::sqlQueryGeometryChanges( sqlite3 *db, const QString &sql )
1430 {
1431  GeometryChanges values;
1432 
1433  sqlite3_stmt *stmt = nullptr;
1434  if ( sqlite3_prepare_v2( db, sql.toUtf8().constData(), -1, &stmt, nullptr ) != SQLITE_OK )
1435  {
1436  showWarning( sqlite3_errmsg( db ) );
1437  return values;
1438  }
1439 
1440  int ret = sqlite3_step( stmt );
1441  while ( ret == SQLITE_ROW )
1442  {
1443  GeometryChange change;
1444  change.fid = sqlite3_column_int( stmt, 0 );
1445  change.geom_wkt = QString( reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) ) );
1446  values << change;
1447 
1448  ret = sqlite3_step( stmt );
1449  }
1450  sqlite3_finalize( stmt );
1451 
1452  return values;
1453 }
1454 
1455 void QgsOfflineEditing::committedAttributesAdded( const QString &qgisLayerId, const QList<QgsField> &addedAttributes )
1456 {
1457  sqlite3_database_unique_ptr database = openLoggingDb();
1458  if ( !database )
1459  return;
1460 
1461  // insert log
1462  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1463  int commitNo = getCommitNo( database.get() );
1464 
1465  for ( const QgsField &field : addedAttributes )
1466  {
1467  QString sql = QStringLiteral( "INSERT INTO 'log_added_attrs' VALUES ( %1, %2, '%3', %4, %5, %6, '%7' )" )
1468  .arg( layerId )
1469  .arg( commitNo )
1470  .arg( field.name() )
1471  .arg( field.type() )
1472  .arg( field.length() )
1473  .arg( field.precision() )
1474  .arg( field.comment() );
1475  sqlExec( database.get(), sql );
1476  }
1477 
1478  increaseCommitNo( database.get() );
1479 }
1480 
1481 void QgsOfflineEditing::committedFeaturesAdded( const QString &qgisLayerId, const QgsFeatureList &addedFeatures )
1482 {
1483  sqlite3_database_unique_ptr database = openLoggingDb();
1484  if ( !database )
1485  return;
1486 
1487  // insert log
1488  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1489 
1490  // get new feature ids from db
1491  QgsMapLayer *layer = QgsProject::instance()->mapLayer( qgisLayerId );
1492  QString dataSourceString = layer->source();
1493  QgsDataSourceUri uri = QgsDataSourceUri( dataSourceString );
1494 
1496  QString tableName;
1497 
1498  if ( !offlinePath.contains( ".gpkg" ) )
1499  {
1500  tableName = uri.table();
1501  }
1502  else
1503  {
1504  QgsProviderMetadata *ogrProviderMetaData = QgsProviderRegistry::instance()->providerMetadata( QStringLiteral( "ogr" ) );
1505  QVariantMap decodedUri = ogrProviderMetaData->decodeUri( dataSourceString );
1506  tableName = decodedUri.value( QStringLiteral( "layerName" ) ).toString();
1507  if ( tableName.isEmpty() )
1508  {
1509  showWarning( tr( "Could not deduce table name from data source %1." ).arg( dataSourceString ) );
1510  }
1511  }
1512 
1513  // only store feature ids
1514  QString sql = QStringLiteral( "SELECT ROWID FROM '%1' ORDER BY ROWID DESC LIMIT %2" ).arg( tableName ).arg( addedFeatures.size() );
1515  QList<int> newFeatureIds = sqlQueryInts( database.get(), sql );
1516  for ( int i = newFeatureIds.size() - 1; i >= 0; i-- )
1517  {
1518  QString sql = QStringLiteral( "INSERT INTO 'log_added_features' VALUES ( %1, %2 )" )
1519  .arg( layerId )
1520  .arg( newFeatureIds.at( i ) );
1521  sqlExec( database.get(), sql );
1522  }
1523 }
1524 
1525 void QgsOfflineEditing::committedFeaturesRemoved( const QString &qgisLayerId, const QgsFeatureIds &deletedFeatureIds )
1526 {
1527  sqlite3_database_unique_ptr database = openLoggingDb();
1528  if ( !database )
1529  return;
1530 
1531  // insert log
1532  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1533 
1534  for ( QgsFeatureId id : deletedFeatureIds )
1535  {
1536  if ( isAddedFeature( database.get(), layerId, id ) )
1537  {
1538  // remove from added features log
1539  QString sql = QStringLiteral( "DELETE FROM 'log_added_features' WHERE \"layer_id\" = %1 AND \"fid\" = %2" ).arg( layerId ).arg( id );
1540  sqlExec( database.get(), sql );
1541  }
1542  else
1543  {
1544  QString sql = QStringLiteral( "INSERT INTO 'log_removed_features' VALUES ( %1, %2)" )
1545  .arg( layerId )
1546  .arg( id );
1547  sqlExec( database.get(), sql );
1548  }
1549  }
1550 }
1551 
1552 void QgsOfflineEditing::committedAttributeValuesChanges( const QString &qgisLayerId, const QgsChangedAttributesMap &changedAttrsMap )
1553 {
1554  sqlite3_database_unique_ptr database = openLoggingDb();
1555  if ( !database )
1556  return;
1557 
1558  // insert log
1559  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1560  int commitNo = getCommitNo( database.get() );
1561 
1562  for ( QgsChangedAttributesMap::const_iterator cit = changedAttrsMap.begin(); cit != changedAttrsMap.end(); ++cit )
1563  {
1564  QgsFeatureId fid = cit.key();
1565  if ( isAddedFeature( database.get(), layerId, fid ) )
1566  {
1567  // skip added features
1568  continue;
1569  }
1570  QgsAttributeMap attrMap = cit.value();
1571  for ( QgsAttributeMap::const_iterator it = attrMap.constBegin(); it != attrMap.constEnd(); ++it )
1572  {
1573  QString sql = QStringLiteral( "INSERT INTO 'log_feature_updates' VALUES ( %1, %2, %3, %4, '%5' )" )
1574  .arg( layerId )
1575  .arg( commitNo )
1576  .arg( fid )
1577  .arg( it.key() ) // attr
1578  .arg( it.value().toString() ); // value
1579  sqlExec( database.get(), sql );
1580  }
1581  }
1582 
1583  increaseCommitNo( database.get() );
1584 }
1585 
1586 void QgsOfflineEditing::committedGeometriesChanges( const QString &qgisLayerId, const QgsGeometryMap &changedGeometries )
1587 {
1588  sqlite3_database_unique_ptr database = openLoggingDb();
1589  if ( !database )
1590  return;
1591 
1592  // insert log
1593  int layerId = getOrCreateLayerId( database.get(), qgisLayerId );
1594  int commitNo = getCommitNo( database.get() );
1595 
1596  for ( QgsGeometryMap::const_iterator it = changedGeometries.begin(); it != changedGeometries.end(); ++it )
1597  {
1598  QgsFeatureId fid = it.key();
1599  if ( isAddedFeature( database.get(), layerId, fid ) )
1600  {
1601  // skip added features
1602  continue;
1603  }
1604  QgsGeometry geom = it.value();
1605  QString sql = QStringLiteral( "INSERT INTO 'log_geometry_updates' VALUES ( %1, %2, %3, '%4' )" )
1606  .arg( layerId )
1607  .arg( commitNo )
1608  .arg( fid )
1609  .arg( geom.asWkt() );
1610  sqlExec( database.get(), sql );
1611 
1612  // TODO: use WKB instead of WKT?
1613  }
1614 
1615  increaseCommitNo( database.get() );
1616 }
1617 
1618 void QgsOfflineEditing::startListenFeatureChanges()
1619 {
1620  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1621  // enable logging, check if editBuffer is not null
1622  if ( vLayer->editBuffer() )
1623  {
1624  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1626  this, &QgsOfflineEditing::committedAttributesAdded );
1628  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1630  this, &QgsOfflineEditing::committedGeometriesChanges );
1631  }
1632  connect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1633  this, &QgsOfflineEditing::committedFeaturesAdded );
1634  connect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1635  this, &QgsOfflineEditing::committedFeaturesRemoved );
1636 }
1637 
1638 void QgsOfflineEditing::stopListenFeatureChanges()
1639 {
1640  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( sender() );
1641  // disable logging, check if editBuffer is not null
1642  if ( vLayer->editBuffer() )
1643  {
1644  QgsVectorLayerEditBuffer *editBuffer = vLayer->editBuffer();
1645  disconnect( editBuffer, &QgsVectorLayerEditBuffer::committedAttributesAdded,
1646  this, &QgsOfflineEditing::committedAttributesAdded );
1648  this, &QgsOfflineEditing::committedAttributeValuesChanges );
1650  this, &QgsOfflineEditing::committedGeometriesChanges );
1651  }
1652  disconnect( vLayer, &QgsVectorLayer::committedFeaturesAdded,
1653  this, &QgsOfflineEditing::committedFeaturesAdded );
1654  disconnect( vLayer, &QgsVectorLayer::committedFeaturesRemoved,
1655  this, &QgsOfflineEditing::committedFeaturesRemoved );
1656 }
1657 
1658 void QgsOfflineEditing::layerAdded( QgsMapLayer *layer )
1659 {
1660  // detect offline layer
1661  if ( layer->customProperty( CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE, false ).toBool() )
1662  {
1663  QgsVectorLayer *vLayer = qobject_cast<QgsVectorLayer *>( layer );
1664  connect( vLayer, &QgsVectorLayer::editingStarted, this, &QgsOfflineEditing::startListenFeatureChanges );
1665  connect( vLayer, &QgsVectorLayer::editingStopped, this, &QgsOfflineEditing::stopListenFeatureChanges );
1666  }
1667 }
1668 
1669 
QgsFeatureRequest::NoGeometry
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition: qgsfeaturerequest.h:107
QgsVectorLayer::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Definition: qgsvectorlayer.cpp:993
QgsExpressionContext
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Definition: qgsexpressioncontext.h:369
QgsProject::relationManager
QgsRelationManager relationManager
Definition: qgsproject.h:103
QgsMapLayer::crs
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:88
QgsVectorLayer::editBuffer
Q_INVOKABLE QgsVectorLayerEditBuffer * editBuffer()
Buffer with uncommitted editing operations. Only valid after editing has been turned on.
Definition: qgsvectorlayer.h:1942
QgsProject::writeEntry
bool writeEntry(const QString &scope, const QString &key, bool value)
Write a boolean entry to the project file.
Definition: qgsproject.cpp:2366
QgsLayerTreeGroup::findLayer
QgsLayerTreeLayer * findLayer(QgsMapLayer *layer) const
Find layer node representing the map layer.
Definition: qgslayertreegroup.cpp:195
QgsProject::title
QString title() const
Returns the project's title.
Definition: qgsproject.cpp:479
spatialite_database_unique_ptr::open
int open(const QString &path)
Opens the database at the specified file path.
Definition: qgsspatialiteutils.cpp:41
QgsMapThemeCollection::MapThemeRecord::addLayerRecord
void addLayerRecord(const QgsMapThemeCollection::MapThemeLayerRecord &record)
Add a new record for a layer.
Definition: qgsmapthemecollection.cpp:729
QgsDataSourceUri
Definition: qgsdatasourceuri.h:35
QgsMapThemeCollection::mapThemes
QStringList mapThemes
Definition: qgsmapthemecollection.h:48
QgsLayerTreeNode
Definition: qgslayertreenode.h:74
qgsmaplayerstylemanager.h
sqlite3_database_unique_ptr::open
int open(const QString &path)
Opens the database at the specified file path.
Definition: qgssqliteutils.cpp:78
QgsRelationManager
Definition: qgsrelationmanager.h:34
QgsVectorLayer::wkbType
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
Definition: qgsvectorlayer.cpp:664
QgsOfflineEditing::synchronize
void synchronize()
Synchronize to remote layers.
Definition: qgsofflineediting.cpp:216
QgsWkbTypes::Point
@ Point
Definition: qgswkbtypes.h:71
QgsLayerTreeGroup::removeChildNode
void removeChildNode(QgsLayerTreeNode *node)
Remove a child node from this group.
Definition: qgslayertreegroup.cpp:132
QgsVectorLayer::dataProvider
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
Definition: qgsvectorlayer.cpp:627
QgsLayerTreeNode::setItemVisibilityChecked
void setItemVisibilityChecked(bool checked)
Check or uncheck a node (independently of its ancestors or children)
Definition: qgslayertreenode.cpp:78
QgsReadWriteContext
Definition: qgsreadwritecontext.h:34
QgsWkbTypes::MultiPolygon
@ MultiPolygon
Definition: qgswkbtypes.h:77
qgsmapthemecollection.h
QgsField::length
int length
Definition: qgsfield.h:55
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsMapLayer::isValid
bool isValid() const
Returns the status of the layer.
Definition: qgsmaplayer.cpp:656
QgsCoordinateReferenceSystem::WKT_PREFERRED_GDAL
@ WKT_PREFERRED_GDAL
Preferred format for conversion of CRS to WKT for use with the GDAL library.
Definition: qgscoordinatereferencesystem.h:680
QgsVectorLayerEditBuffer::committedGeometriesChanges
void committedGeometriesChanges(const QString &layerId, const QgsGeometryMap &changedGeometries)
sqlite3
struct sqlite3 sqlite3
Definition: qgscoordinatereferencesystem.h:55
QgsOfflineEditing::isOfflineProject
bool isOfflineProject() const
Returns true if current project is offline.
Definition: qgsofflineediting.cpp:211
QgsProject::mapLayers
QMap< QString, QgsMapLayer * > mapLayers(const bool validOnly=false) const
Returns a map of all registered layers by layer ID.
Definition: qgsproject.cpp:3347
QgsMapLayer::setCustomProperty
Q_INVOKABLE void setCustomProperty(const QString &key, const QVariant &value)
Set a custom property for layer.
Definition: qgsmaplayer.cpp:1703
QgsVectorDataProvider::fields
QgsFields fields() const override=0
Returns the fields associated with this data provider.
QgsLayerTree::customLayerOrder
QList< QgsMapLayer * > customLayerOrder() const
The order in which layers will be rendered on the canvas.
Definition: qgslayertree.cpp:35
QgsMapLayer::importNamedStyle
virtual bool importNamedStyle(QDomDocument &doc, QString &errorMsg, QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories)
Import the properties of this layer from a QDomDocument.
Definition: qgsmaplayer.cpp:1034
QgsFeatureRequest::filterType
FilterType filterType() const
Returns the filter type which is currently set on this request.
Definition: qgsfeaturerequest.h:325
gdal::ogr_datasource_unique_ptr
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:114
QgsMapThemeCollection::MapThemeRecord
Definition: qgsmapthemecollection.h:120
QgsWkbTypes::LineString
@ LineString
Definition: qgswkbtypes.h:72
qgsfeatureiterator.h
QgsFields::count
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
QgsDataProvider::invalidateConnections
virtual void invalidateConnections(const QString &connection)
Invalidate connections corresponding to specified name.
Definition: qgsdataprovider.h:412
QgsFields
Definition: qgsfields.h:44
QgsFeatureRequest::FilterFids
@ FilterFids
Filter using feature IDs.
Definition: qgsfeaturerequest.h:108
QgsMapLayer::setCustomProperties
void setCustomProperties(const QgsObjectCustomProperties &properties)
Set custom properties for layer.
Definition: qgsmaplayer.cpp:1708
gdal::ogr_field_def_unique_ptr
std::unique_ptr< std::remove_pointer< OGRFieldDefnH >::type, OGRFldDeleter > ogr_field_def_unique_ptr
Scoped OGR field definition.
Definition: qgsogrutils.h:124
qgslayertreelayer.h
QgsLayerTree::setCustomLayerOrder
void setCustomLayerOrder(const QList< QgsMapLayer * > &customLayerOrder)
The order in which layers will be rendered on the canvas.
Definition: qgslayertree.cpp:40
PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
#define PROJECT_ENTRY_KEY_OFFLINE_DB_PATH
Definition: qgsofflineediting.cpp:64
QgsMapLayer::styleManager
QgsMapLayerStyleManager * styleManager() const
Gets access to the layer's style manager.
Definition: qgsmaplayer.cpp:1797
QgsProject::transformContext
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:99
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:458
CUSTOM_PROPERTY_REMOTE_SOURCE
#define CUSTOM_PROPERTY_REMOTE_SOURCE
Definition: qgsofflineediting.cpp:60
QgsVectorLayer::isSpatial
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
Definition: qgsvectorlayer.cpp:3591
qgsogrutils.h
QgsMapThemeCollection::MapThemeRecord::layerRecords
QList< QgsMapThemeCollection::MapThemeLayerRecord > layerRecords() const
Returns a list of records for all visible layer belonging to the theme.
Definition: qgsmapthemecollection.h:136
CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
#define CUSTOM_PROPERTY_IS_OFFLINE_EDITABLE
Definition: qgsofflineediting.cpp:59
QgsOfflineEditing::convertToOfflineProject
bool convertToOfflineProject(const QString &offlineDataPath, const QString &offlineDbFile, const QStringList &layerIds, bool onlySelected=false, ContainerType containerType=SpatiaLite)
Convert current project for offline editing.
Definition: qgsofflineediting.cpp:87
QgsWkbTypes::Type
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:68
QgsProject::layerWasAdded
void layerWasAdded(QgsMapLayer *layer)
Emitted when a layer was added to the registry.
QgsMapLayerStyleManager::copyStylesFrom
void copyStylesFrom(QgsMapLayerStyleManager *other)
Copies all styles from other.
Definition: qgsmaplayerstylemanager.cpp:234
QgsVectorLayer::startEditing
Q_INVOKABLE bool startEditing()
Makes the layer editable.
Definition: qgsvectorlayer.cpp:1411
QgsChangedAttributesMap
QMap< QgsFeatureId, QgsAttributeMap > QgsChangedAttributesMap
Definition: qgsfeature.h:558
QgsWkbTypes::hasZ
static bool hasZ(Type type)
Tests whether a WKB type contains the z-dimension.
Definition: qgswkbtypes.h:1042
QgsOfflineEditing::GPKG
@ GPKG
Definition: qgsofflineediting.h:55
QgsProject::readEntry
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Definition: qgsproject.cpp:2448
QgsOfflineEditing::warning
void warning(const QString &title, const QString &message)
Emitted when a warning needs to be displayed.
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsVectorDataProvider::NativeType
Definition: qgsvectordataprovider.h:460
QgsProject::mapLayer
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
Definition: qgsproject.cpp:3124
QgsProject::removeMapLayers
void removeMapLayers(const QStringList &layerIds)
Remove a set of layers from the registry by layer ID.
Definition: qgsproject.cpp:3301
QgsAttributeList
QList< int > QgsAttributeList
Definition: qgsfield.h:26
QgsVectorLayer::featureCount
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
Definition: qgsvectorlayer.cpp:751
QgsRelationManager::removeRelation
void removeRelation(const QString &id)
Remove a relation.
Definition: qgsrelationmanager.cpp:84
QgsField::name
QString name
Definition: qgsfield.h:59
QgsFieldConstraints::ConstraintNotNull
@ ConstraintNotNull
Field may not be null.
Definition: qgsfieldconstraints.h:45
QgsVectorLayer::committedFeaturesAdded
void committedFeaturesAdded(const QString &layerId, const QgsFeatureList &addedFeatures)
Emitted when features are added to the provider.
QgsVectorLayerEditBuffer::committedAttributesAdded
void committedAttributesAdded(const QString &layerId, const QList< QgsField > &addedAttributes)
QgsVectorLayer::changeAttributeValue
bool changeAttributeValue(QgsFeatureId fid, int field, const QVariant &newValue, const QVariant &oldValue=QVariant(), bool skipDefaultValues=false)
Changes an attribute value for a feature (but does not immediately commit the changes).
Definition: qgsvectorlayer.cpp:2965
QgsVectorLayer::attributeList
QgsAttributeList attributeList() const
Returns list of attribute indexes.
Definition: qgsvectorlayer.h:1654
spatialite_database_unique_ptr::open_v2
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Definition: qgsspatialiteutils.cpp:61
QgsMapLayer::providerType
QString providerType() const
Returns the provider type (provider key) for this layer.
Definition: qgsmaplayer.cpp:1614
QgsVectorJoinList
QList< QgsVectorLayerJoinInfo > QgsVectorJoinList
Definition: qgsvectorlayerjoinbuffer.h:30
QgsOfflineEditing::UpdateFeatures
@ UpdateFeatures
Definition: qgsofflineediting.h:47
qgsapplication.h
OGRSpatialReferenceH
void * OGRSpatialReferenceH
Definition: qgscoordinatereferencesystem.h:60
QgsOfflineEditing::SpatiaLite
@ SpatiaLite
Definition: qgsofflineediting.h:54
QgsOfflineEditing::progressStarted
void progressStarted()
Emitted when the process has started.
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3280
QgsField::precision
int precision
Definition: qgsfield.h:56
QgsMapLayer::customProperties
const QgsObjectCustomProperties & customProperties() const
Read all custom properties from layer.
Definition: qgsmaplayer.cpp:1713
QgsProject::addMapLayers
QList< QgsMapLayer * > addMapLayers(const QList< QgsMapLayer * > &mapLayers, bool addToLegend=true, bool takeOwnership=true)
Add a list of layers to the map of loaded layers.
Definition: qgsproject.cpp:3252
QgsVectorLayer::changeGeometry
bool changeGeometry(QgsFeatureId fid, QgsGeometry &geometry, bool skipDefaultValue=false)
Changes a feature's geometry within the layer's edit buffer (but does not immediately commit the chan...
Definition: qgsvectorlayer.cpp:2941
QgsFeature::id
QgsFeatureId id
Definition: qgsfeature.h:68
QgsFeatureRequest
Definition: qgsfeaturerequest.h:75
QgsWkbTypes::MultiLineString
@ MultiLineString
Definition: qgswkbtypes.h:76
QgsVectorDataProvider::nativeTypes
QList< QgsVectorDataProvider::NativeType > nativeTypes() const
Returns the names of the supported types.
Definition: qgsvectordataprovider.cpp:338
QgsProject::layerTreeRoot
QgsLayerTree * layerTreeRoot() const
Returns pointer to the root (invisible) node of the project's layer tree.
Definition: qgsproject.cpp:3016
QgsOfflineEditing::UpdateGeometries
@ UpdateGeometries
Definition: qgsofflineediting.h:48
QgsVectorLayer::dataComment
QString dataComment() const
Returns a description for this layer as defined in the data provider.
Definition: qgsvectorlayer.cpp:359
qgsprovidermetadata.h
QgsVectorLayer::editingStarted
void editingStarted()
Emitted when editing on this layer has started.
QgsField::setTypeName
void setTypeName(const QString &typeName)
Set the field type.
Definition: qgsfield.cpp:190
QgsSnappingConfig::removeLayers
bool removeLayers(const QList< QgsMapLayer * > &layers)
Removes the specified layers from the individual layer configuration.
Definition: qgssnappingconfig.cpp:569
QgsVectorLayer::removeFieldConstraint
void removeFieldConstraint(int index, QgsFieldConstraints::Constraint constraint)
Removes a constraint for a specified field index.
Definition: qgsvectorlayer.cpp:5431
QgsVectorDataProvider::featureCount
long featureCount() const override=0
Number of features in the layer.
QgsOfflineEditing::QgsOfflineEditing
QgsOfflineEditing()
Definition: qgsofflineediting.cpp:66
QgsVectorLayer::commitErrors
QStringList commitErrors() const
Returns a list containing any error messages generated when attempting to commit changes to the layer...
Definition: qgsvectorlayer.cpp:3374
qgsproviderregistry.h
QgsRelationManager::referencedRelations
QList< QgsRelation > referencedRelations(const QgsVectorLayer *layer=nullptr) const
Gets all relations where this layer is the referenced part (i.e.
Definition: qgsrelationmanager.cpp:160
QgsVectorLayerEditBuffer::committedAttributeValuesChanges
void committedAttributeValuesChanges(const QString &layerId, const QgsChangedAttributesMap &changedAttributesValues)
qgsvectorlayerjoinbuffer.h
QgsOfflineEditing::AddFields
@ AddFields
Definition: qgsofflineediting.h:44
qgsdatasourceuri.h
QgsVectorLayer::vectorJoins
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
Definition: qgsvectorlayer.cpp:3724
qgslayertreegroup.h
QgsLayerTreeLayer
Definition: qgslayertreelayer.h:43
qgsmaplayer.h
QgsVectorLayer::selectedFeatureIds
const Q_INVOKABLE QgsFeatureIds & selectedFeatureIds() const
Returns a list of the selected features IDs in this layer.
Definition: qgsvectorlayer.cpp:3433
QgsCoordinateReferenceSystem::authid
QString authid() const
Returns the authority identifier for the CRS.
Definition: qgscoordinatereferencesystem.cpp:1299
PROJECT_ENTRY_SCOPE_OFFLINE
#define PROJECT_ENTRY_SCOPE_OFFLINE
Definition: qgsofflineediting.cpp:63
QgsField::comment
QString comment
Definition: qgsfield.h:58
QgsProject::readPath
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Definition: qgsproject.cpp:2611
QgsLayerTreeGroup
Definition: qgslayertreegroup.h:34
qgsvectordataprovider.h
QgsVectorLayer::reload
void reload() FINAL
Synchronises with changes in the datasource.
Definition: qgsvectorlayer.cpp:378
QgsOfflineEditing::AddFeatures
@ AddFeatures
Definition: qgsofflineediting.h:45
QgsFeatureList
QList< QgsFeature > QgsFeatureList
Definition: qgsfeature.h:572
QgsVectorLayer::commitChanges
Q_INVOKABLE bool commitChanges()
Attempts to commit to the underlying data provider any buffered changes made since the last to call t...
Definition: qgsvectorlayer.cpp:3329
QgsCoordinateReferenceSystem::toWkt
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Definition: qgscoordinatereferencesystem.cpp:1931
QgsAttributeMap
QMap< int, QVariant > QgsAttributeMap
Definition: qgsattributes.h:38
qgsvectorlayerutils.h
QgsVectorDataProvider::NativeType::mTypeName
QString mTypeName
Definition: qgsvectordataprovider.h:474
spatialite_database_unique_ptr
Definition: qgsspatialiteutils.h:56
QgsMapLayer::id
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Definition: qgsmaplayer.cpp:148
QgsFeatureRequest::setFilterFids
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets feature IDs that should be fetched.
Definition: qgsfeaturerequest.cpp:110
QgsSnappingConfig
Definition: qgssnappingconfig.h:33
QgsOfflineEditing::progressStopped
void progressStopped()
Emitted when the processing of all layers has finished.
QgsProviderRegistry::providerMetadata
QgsProviderMetadata * providerMetadata(const QString &providerKey) const
Returns metadata of the provider or nullptr if not found.
Definition: qgsproviderregistry.cpp:722
typeName
const QString & typeName
Definition: qgswfsgetfeature.cpp:109
QgsProject::mapThemeCollection
QgsMapThemeCollection mapThemeCollection
Definition: qgsproject.h:101
QgsVectorLayerJoinInfo
Definition: qgsvectorlayerjoininfo.h:33
QgsMapLayer::exportNamedStyle
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg, const QgsReadWriteContext &context=QgsReadWriteContext(), QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories) const
Export the properties of this layer as named style in a QDomDocument.
Definition: qgsmaplayer.cpp:1095
QgsProject::setSnappingConfig
void setSnappingConfig(const QgsSnappingConfig &snappingConfig)
The snapping configuration for this project.
Definition: qgsproject.cpp:1013
QgsRelationManager::addRelation
void addRelation(const QgsRelation &relation)
Add a relation.
Definition: qgsrelationmanager.cpp:60
qgslayertree.h
qgsrelationmanager.h
QgsVectorDataProvider::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const override=0
Query the provider for features specified in request.
QgsFeatureIds
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
QgsOfflineEditing::RemoveFeatures
@ RemoveFeatures
Definition: qgsofflineediting.h:46
QgsGeometry::fromWkt
static QgsGeometry fromWkt(const QString &wkt)
Creates a new geometry from a WKT string.
Definition: qgsgeometry.cpp:154
QgsFeature::attributes
QgsAttributes attributes
Definition: qgsfeature.h:69
QgsProject::setTitle
void setTitle(const QString &title)
Sets the project's title.
Definition: qgsproject.cpp:467
QgsFields::field
QgsField field(int fieldIdx) const
Gets field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:168
QgsProviderMetadata
Definition: qgsprovidermetadata.h:126
CUSTOM_SHOW_FEATURE_COUNT
#define CUSTOM_SHOW_FEATURE_COUNT
Definition: qgsofflineediting.cpp:62
QgsVectorLayerUtils::createFeature
static QgsFeature createFeature(const QgsVectorLayer *layer, const QgsGeometry &geometry=QgsGeometry(), const QgsAttributeMap &attributes=QgsAttributeMap(), QgsExpressionContext *context=nullptr)
Creates a new feature ready for insertion into a layer.
Definition: qgsvectorlayerutils.cpp:475
QgsSnappingConfig::setIndividualLayerSettings
void setIndividualLayerSettings(QgsVectorLayer *vl, const QgsSnappingConfig::IndividualLayerSettings &individualLayerSettings)
Sets individual layer snappings settings (applied if mode is AdvancedConfiguration)
Definition: qgssnappingconfig.cpp:376
qgsvectorlayer.h
QgsLayerTreeGroup::insertChildNode
void insertChildNode(int index, QgsLayerTreeNode *node)
Insert existing node at specified position.
Definition: qgslayertreegroup.cpp:92
QgsVectorLayer::LayerOptions
Setting options for loading vector layers.
Definition: qgsvectorlayer.h:423
QgsOfflineEditing::ContainerType
ContainerType
Type of offline database container file.
Definition: qgsofflineediting.h:52
QgsMapLayer::source
QString source() const
Returns the source for the layer.
Definition: qgsmaplayer.cpp:192
QgsWkbTypes::hasM
static bool hasM(Type type)
Tests whether a WKB type contains m values.
Definition: qgswkbtypes.h:1092
QgsVectorDataProvider::NativeType::mType
QVariant::Type mType
Definition: qgsvectordataprovider.h:475
QgsGeometry::asWkt
QString asWkt(int precision=17) const
Exports the geometry to WKT.
Definition: qgsgeometry.cpp:1303
QgsVectorLayer::editingStopped
void editingStopped()
Emitted when edited changes have been successfully written to the data provider.
QgsMapLayer::customProperty
Q_INVOKABLE QVariant customProperty(const QString &value, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer.
Definition: qgsmaplayer.cpp:1718
qgsgeometry.h
spatialite_database_unique_ptr::errorMessage
QString errorMessage() const
Returns the most recent error message encountered by the database.
Definition: qgsspatialiteutils.cpp:86
QgsMapThemeCollection::MapThemeLayerRecord
Definition: qgsmapthemecollection.h:58
QgsOfflineEditing::ProcessFeatures
@ ProcessFeatures
Definition: qgsofflineediting.h:43
QgsFeatureIterator::nextFeature
bool nextFeature(QgsFeature &f)
Definition: qgsfeatureiterator.h:373
QgsGeometry
Definition: qgsgeometry.h:122
QgsLayerTreeNode::setCustomProperty
void setCustomProperty(const QString &key, const QVariant &value)
Sets a custom property for the node. Properties are stored in a map and saved in project file.
Definition: qgslayertreenode.cpp:180
QgsVectorLayerEditBuffer
Definition: qgsvectorlayereditbuffer.h:36
QgsSnappingConfig::individualLayerSettings
QHash< QgsVectorLayer *, QgsSnappingConfig::IndividualLayerSettings > individualLayerSettings() const
Returns individual snapping settings for all layers.
Definition: qgssnappingconfig.cpp:358
QgsVectorLayer
Definition: qgsvectorlayer.h:385
QgsLayerTreeNode::customProperty
QVariant customProperty(const QString &key, const QVariant &defaultValue=QVariant()) const
Read a custom property from layer. Properties are stored in a map and saved in project file.
Definition: qgslayertreenode.cpp:189
QgsMapLayer
Definition: qgsmaplayer.h:81
QgsRelationManager::referencingRelations
QList< QgsRelation > referencingRelations(const QgsVectorLayer *layer=nullptr, int fieldIdx=-2) const
Gets all relations where the specified layer (and field) is the referencing part (i....
Definition: qgsrelationmanager.cpp:121
QgsWkbTypes::displayString
static QString displayString(Type type)
Returns a display string type for a WKB type, e.g., the geometry name used in WKT geometry representa...
Definition: qgswkbtypes.cpp:145
QgsWkbTypes::Polygon
@ Polygon
Definition: qgswkbtypes.h:73
qgsspatialiteutils.h
QgsProject::snappingConfig
QgsSnappingConfig snappingConfig
Definition: qgsproject.h:102
QgsWkbTypes::MultiPoint
@ MultiPoint
Definition: qgswkbtypes.h:75
QgsOfflineEditing::layerProgressUpdated
void layerProgressUpdated(int layer, int numLayers)
Emitted whenever a new layer is being processed.
QgsLayerTreeNode::children
QList< QgsLayerTreeNode * > children()
Gets list of children of the node. Children are owned by the parent.
Definition: qgslayertreenode.h:112
qgsofflineediting.h
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:85
QgsLayerTreeLayer::clone
QgsLayerTreeLayer * clone() const override
Create a copy of the node. Returns new instance.
Definition: qgslayertreelayer.cpp:188
QgsDataSourceUri::database
QString database() const
Returns the database name stored in the URI.
Definition: qgsdatasourceuri.cpp:279
QgsFields::fieldOriginIndex
int fieldOriginIndex(int fieldIdx) const
Gets field's origin index (its meaning is specific to each type of origin)
Definition: qgsfields.cpp:197
QgsRelation
Definition: qgsrelation.h:41
QgsMapLayer::CustomProperties
@ CustomProperties
Custom properties (by plugins for instance)
Definition: qgsmaplayer.h:167
QgsVectorDataProvider::defaultValueClause
virtual QString defaultValueClause(int fieldIndex) const
Returns any default value clauses which are present at the provider for a specified field index.
Definition: qgsvectordataprovider.cpp:142
QgsOfflineEditing::progressUpdated
void progressUpdated(int progress)
Emitted with the progress of the current mode.
QgsAttributes
Definition: qgsattributes.h:57
QgsGeometryMap
QMap< QgsFeatureId, QgsGeometry > QgsGeometryMap
Definition: qgsfeature.h:567
QgsVectorDataProvider
Definition: qgsvectordataprovider.h:58
QgsMapThemeCollection::MapThemeRecord::removeLayerRecord
void removeLayerRecord(QgsMapLayer *layer)
Removes a record for layer if present.
Definition: qgsmapthemecollection.cpp:720
QgsLayerTreeNode::parent
QgsLayerTreeNode * parent()
Gets pointer to the parent. If parent is nullptr, the node is a root node.
Definition: qgslayertreenode.h:110
QgsFeature
Definition: qgsfeature.h:55
QgsVectorLayer::deleteFeature
bool deleteFeature(QgsFeatureId fid, DeleteContext *context=nullptr)
Deletes a feature from the layer (but does not commit it).
Definition: qgsvectorlayer.cpp:3248
QgsMapThemeCollection
Container class that allows storage of map themes consisting of visible map layers and layer styles.
Definition: qgsmapthemecollection.h:44
qgslogger.h
QgsLayerTreeNode::isVisible
bool isVisible() const
Returns whether a node is really visible (ie checked and all its ancestors checked as well)
Definition: qgslayertreenode.cpp:98
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:127
qgsvectorfilewriter.h
QgsFields::lookupField
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:324
QgsVectorLayer::createExpressionContext
QgsExpressionContext createExpressionContext() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Definition: qgsvectorlayer.cpp:4933
QgsMapLayer::AllStyleCategories
@ AllStyleCategories
Definition: qgsmaplayer.h:171
QgsOfflineEditing::CopyFeatures
@ CopyFeatures
Definition: qgsofflineediting.h:42
QgsDataSourceUri::table
QString table() const
Returns the table name stored in the URI.
Definition: qgsdatasourceuri.cpp:314
sqlite3_database_unique_ptr
Definition: qgssqliteutils.h:118
QgsFeatureIterator
Definition: qgsfeatureiterator.h:263
QgsVectorLayer::addFeature
bool addFeature(QgsFeature &feature, QgsFeatureSink::Flags flags=QgsFeatureSink::Flags()) FINAL
Adds a single feature to the sink.
Definition: qgsvectorlayer.cpp:1011
QgsProviderRegistry::instance
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
Definition: qgsproviderregistry.cpp:48
QgsMapThemeCollection::mapThemeState
QgsMapThemeCollection::MapThemeRecord mapThemeState(const QString &name) const
Returns the recorded state of a map theme.
Definition: qgsmapthemecollection.h:285
qgsproject.h
CUSTOM_PROPERTY_REMOTE_PROVIDER
#define CUSTOM_PROPERTY_REMOTE_PROVIDER
Definition: qgsofflineediting.cpp:61
QgsField::type
QVariant::Type type
Definition: qgsfield.h:57
qgsvectorlayereditbuffer.h
QgsProviderMetadata::decodeUri
virtual QVariantMap decodeUri(const QString &uri)
Breaks a provider data source URI into its component paths (e.g.
Definition: qgsprovidermetadata.cpp:126
QgsOfflineEditing::progressModeSet
void progressModeSet(QgsOfflineEditing::ProgressMode mode, int maximum)
Emitted when the mode for the progress of the current operation is set.
QgsWkbTypes::flatType
static Type flatType(Type type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:701
QgsMapThemeCollection::update
void update(const QString &name, const QgsMapThemeCollection::MapThemeRecord &state)
Updates a map theme within the collection.
Definition: qgsmapthemecollection.cpp:272
QgsProject::removeEntry
bool removeEntry(const QString &scope, const QString &key)
Remove the given key.
Definition: qgsproject.cpp:2539
QgsVectorLayer::committedFeaturesRemoved
void committedFeaturesRemoved(const QString &layerId, const QgsFeatureIds &deletedFeatureIds)
Emitted when features are deleted from the provider.
QgsFields::indexOf
int indexOf(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:207
QgsFeatureId
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
QgsVectorLayer::addAttribute
bool addAttribute(const QgsField &field)
Add an attribute field (but does not commit it) returns true if the field was added.
Definition: qgsvectorlayer.cpp:3054
QgsVectorLayer::addJoin
bool addJoin(const QgsVectorLayerJoinInfo &joinInfo)
Joins another vector layer to this layer.
Definition: qgsvectorlayer.cpp:3713
QgsField
Definition: qgsfield.h:49