QGIS API Documentation  3.17.0-Master (a84647cf30)
qgsstyle.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsstyle.cpp
3  ---------------------
4  begin : November 2009
5  copyright : (C) 2009 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsstyle.h"
17 
18 #include "qgssymbol.h"
19 #include "qgscolorramp.h"
20 #include "qgssymbollayerregistry.h"
21 #include "qgsapplication.h"
22 #include "qgslogger.h"
23 #include "qgsreadwritecontext.h"
24 #include "qgssettings.h"
25 #include "qgslegendpatchshape.h"
26 #include "qgslinestring.h"
27 #include "qgspolygon.h"
28 #include "qgsmarkersymbollayer.h"
29 #include "qgslinesymbollayer.h"
30 #include "qgsfillsymbollayer.h"
31 #include "qgsruntimeprofiler.h"
32 #include "qgsabstract3dsymbol.h"
33 #include "qgs3dsymbolregistry.h"
34 
35 #include <QDomDocument>
36 #include <QDomElement>
37 #include <QDomNode>
38 #include <QDomNodeList>
39 #include <QFile>
40 #include <QTextStream>
41 #include <QByteArray>
42 
43 #include <sqlite3.h>
44 #include "qgssqliteutils.h"
45 
46 #define STYLE_CURRENT_VERSION "2"
47 
52 {
57 };
58 
63 {
68 };
69 
70 
71 QgsStyle *QgsStyle::sDefaultStyle = nullptr;
72 
74 {
75  std::unique_ptr< QgsSimpleMarkerSymbolLayer > simpleMarker = qgis::make_unique< QgsSimpleMarkerSymbolLayer >( QgsSimpleMarkerSymbolLayerBase::Circle,
76  1.6, 0, QgsSymbol::ScaleArea, QColor( 84, 176, 74 ), QColor( 61, 128, 53 ) );
77  simpleMarker->setStrokeWidth( 0.4 );
78  mPatchMarkerSymbol = qgis::make_unique< QgsMarkerSymbol >( QgsSymbolLayerList() << simpleMarker.release() );
79 
80  std::unique_ptr< QgsSimpleLineSymbolLayer > simpleLine = qgis::make_unique< QgsSimpleLineSymbolLayer >( QColor( 84, 176, 74 ), 0.6 );
81  mPatchLineSymbol = qgis::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << simpleLine.release() );
82 
83  std::unique_ptr< QgsGradientFillSymbolLayer > gradientFill = qgis::make_unique< QgsGradientFillSymbolLayer >( QColor( 66, 150, 63 ), QColor( 84, 176, 74 ) );
84  std::unique_ptr< QgsSimpleLineSymbolLayer > simpleOutline = qgis::make_unique< QgsSimpleLineSymbolLayer >( QColor( 56, 128, 54 ), 0.26 );
85  mPatchFillSymbol = qgis::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << gradientFill.release() << simpleOutline.release() );
86 }
87 
89 {
90  clear();
91 }
92 
93 bool QgsStyle::addEntity( const QString &name, const QgsStyleEntityInterface *entity, bool update )
94 {
95  switch ( entity->type() )
96  {
97  case SymbolEntity:
98  if ( !static_cast< const QgsStyleSymbolEntity * >( entity )->symbol() )
99  return false;
100  return addSymbol( name, static_cast< const QgsStyleSymbolEntity * >( entity )->symbol()->clone(), update );
101 
102  case ColorrampEntity:
103  if ( !static_cast< const QgsStyleColorRampEntity * >( entity )->ramp() )
104  return false;
105  return addColorRamp( name, static_cast< const QgsStyleColorRampEntity * >( entity )->ramp()->clone(), update );
106 
107  case TextFormatEntity:
108  return addTextFormat( name, static_cast< const QgsStyleTextFormatEntity * >( entity )->format(), update );
109 
110  case LabelSettingsEntity:
111  return addLabelSettings( name, static_cast< const QgsStyleLabelSettingsEntity * >( entity )->settings(), update );
112 
114  return addLegendPatchShape( name, static_cast< const QgsStyleLegendPatchShapeEntity * >( entity )->shape(), update );
115 
116  case Symbol3DEntity:
117  return addSymbol3D( name, static_cast< const QgsStyleSymbol3DEntity * >( entity )->symbol()->clone(), update );
118 
119  case TagEntity:
120  case SmartgroupEntity:
121  break;
122 
123  }
124  return false;
125 }
126 
128 {
129  if ( !sDefaultStyle )
130  {
131  QgsScopedRuntimeProfile profile( tr( "Load default style database" ) );
132  QString styleFilename = QgsApplication::userStylePath();
133 
134  // copy default style if user style doesn't exist
135  if ( !QFile::exists( styleFilename ) )
136  {
137  sDefaultStyle = new QgsStyle;
138  sDefaultStyle->createDatabase( styleFilename );
139  if ( QFile::exists( QgsApplication::defaultStylePath() ) )
140  {
141  sDefaultStyle->importXml( QgsApplication::defaultStylePath() );
142  }
143  }
144  else
145  {
146  sDefaultStyle = new QgsStyle;
147  if ( sDefaultStyle->load( styleFilename ) )
148  {
149  sDefaultStyle->upgradeIfRequired();
150  }
151  }
152  }
153  return sDefaultStyle;
154 }
155 
157 {
158  delete sDefaultStyle;
159  sDefaultStyle = nullptr;
160 }
161 
163 {
164  qDeleteAll( mSymbols );
165  qDeleteAll( mColorRamps );
166  qDeleteAll( m3dSymbols );
167 
168  mSymbols.clear();
169  mColorRamps.clear();
170  mTextFormats.clear();
171  m3dSymbols.clear();
172 
173  mCachedTags.clear();
174  mCachedFavorites.clear();
175 }
176 
177 bool QgsStyle::addSymbol( const QString &name, QgsSymbol *symbol, bool update )
178 {
179  if ( !symbol || name.isEmpty() )
180  return false;
181 
182  // delete previous symbol (if any)
183  if ( mSymbols.contains( name ) )
184  {
185  // TODO remove groups and tags?
186  delete mSymbols.value( name );
187  mSymbols.insert( name, symbol );
188  if ( update )
189  updateSymbol( SymbolEntity, name );
190  }
191  else
192  {
193  mSymbols.insert( name, symbol );
194  if ( update )
195  saveSymbol( name, symbol, false, QStringList() );
196  }
197 
198  return true;
199 }
200 
201 bool QgsStyle::saveSymbol( const QString &name, QgsSymbol *symbol, bool favorite, const QStringList &tags )
202 {
203  // TODO add support for groups
204  QDomDocument doc( QStringLiteral( "dummy" ) );
205  QDomElement symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol, doc, QgsReadWriteContext() );
206  if ( symEl.isNull() )
207  {
208  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
209  return false;
210  }
211 
212  QByteArray xmlArray;
213  QTextStream stream( &xmlArray );
214  stream.setCodec( "UTF-8" );
215  symEl.save( stream, 4 );
216  QString query = qgs_sqlite3_mprintf( "INSERT INTO symbol VALUES (NULL, '%q', '%q', %d);",
217  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
218 
219  if ( !runEmptyQuery( query ) )
220  {
221  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
222  return false;
223  }
224 
225  mCachedFavorites[ SymbolEntity ].insert( name, favorite );
226 
227  tagSymbol( SymbolEntity, name, tags );
228 
229  emit symbolSaved( name, symbol );
230  emit entityAdded( SymbolEntity, name );
231 
232  return true;
233 }
234 
235 bool QgsStyle::removeSymbol( const QString &name )
236 {
237  return removeEntityByName( SymbolEntity, name );
238 }
239 
240 bool QgsStyle::renameEntity( QgsStyle::StyleEntity type, const QString &oldName, const QString &newName )
241 {
242  switch ( type )
243  {
244  case SymbolEntity:
245  return renameSymbol( oldName, newName );
246 
247  case ColorrampEntity:
248  return renameColorRamp( oldName, newName );
249 
250  case TextFormatEntity:
251  return renameTextFormat( oldName, newName );
252 
253  case LabelSettingsEntity:
254  return renameLabelSettings( oldName, newName );
255 
257  return renameLegendPatchShape( oldName, newName );
258 
259  case Symbol3DEntity:
260  return renameSymbol3D( oldName, newName );
261 
262  case TagEntity:
263  case SmartgroupEntity:
264  return false;
265  }
266  return false;
267 }
268 
269 QgsSymbol *QgsStyle::symbol( const QString &name )
270 {
271  const QgsSymbol *symbol = symbolRef( name );
272  return symbol ? symbol->clone() : nullptr;
273 }
274 
275 const QgsSymbol *QgsStyle::symbolRef( const QString &name ) const
276 {
277  return mSymbols.value( name );
278 }
279 
281 {
282  return mSymbols.count();
283 }
284 
285 QStringList QgsStyle::symbolNames() const
286 {
287  return mSymbols.keys();
288 }
289 
290 
291 bool QgsStyle::addColorRamp( const QString &name, QgsColorRamp *colorRamp, bool update )
292 {
293  if ( !colorRamp || name.isEmpty() )
294  return false;
295 
296  // delete previous color ramps (if any)
297  if ( mColorRamps.contains( name ) )
298  {
299  // TODO remove groups and tags?
300  delete mColorRamps.value( name );
301  mColorRamps.insert( name, colorRamp );
302  if ( update )
303  updateSymbol( ColorrampEntity, name );
304  }
305  else
306  {
307  mColorRamps.insert( name, colorRamp );
308  if ( update )
309  saveColorRamp( name, colorRamp, false, QStringList() );
310  }
311 
312  return true;
313 }
314 
315 bool QgsStyle::addTextFormat( const QString &name, const QgsTextFormat &format, bool update )
316 {
317  // delete previous text format (if any)
318  if ( mTextFormats.contains( name ) )
319  {
320  // TODO remove groups and tags?
321  mTextFormats.remove( name );
322  mTextFormats.insert( name, format );
323  if ( update )
324  updateSymbol( TextFormatEntity, name );
325  }
326  else
327  {
328  mTextFormats.insert( name, format );
329  if ( update )
330  saveTextFormat( name, format, false, QStringList() );
331  }
332 
333  return true;
334 }
335 
336 bool QgsStyle::addLabelSettings( const QString &name, const QgsPalLayerSettings &settings, bool update )
337 {
338  // delete previous label settings (if any)
339  if ( mLabelSettings.contains( name ) )
340  {
341  // TODO remove groups and tags?
342  mLabelSettings.remove( name );
343  mLabelSettings.insert( name, settings );
344  if ( update )
345  updateSymbol( LabelSettingsEntity, name );
346  }
347  else
348  {
349  mLabelSettings.insert( name, settings );
350  if ( update )
351  saveLabelSettings( name, settings, false, QStringList() );
352  }
353 
354  return true;
355 }
356 
357 bool QgsStyle::addLegendPatchShape( const QString &name, const QgsLegendPatchShape &shape, bool update )
358 {
359  // delete previous legend patch shape (if any)
360  if ( mLegendPatchShapes.contains( name ) )
361  {
362  // TODO remove groups and tags?
363  mLegendPatchShapes.remove( name );
364  mLegendPatchShapes.insert( name, shape );
365  if ( update )
366  updateSymbol( LegendPatchShapeEntity, name );
367  }
368  else
369  {
370  mLegendPatchShapes.insert( name, shape );
371  if ( update )
372  saveLegendPatchShape( name, shape, false, QStringList() );
373  }
374 
375  return true;
376 }
377 
378 bool QgsStyle::addSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool update )
379 {
380  // delete previous symbol (if any)
381  if ( m3dSymbols.contains( name ) )
382  {
383  // TODO remove groups and tags?
384  delete m3dSymbols.take( name );
385  m3dSymbols.insert( name, symbol );
386  if ( update )
387  updateSymbol( Symbol3DEntity, name );
388  }
389  else
390  {
391  m3dSymbols.insert( name, symbol );
392  if ( update )
393  saveSymbol3D( name, symbol, false, QStringList() );
394  }
395 
396  return true;
397 }
398 
399 bool QgsStyle::saveColorRamp( const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags )
400 {
401  // insert it into the database
402  QDomDocument doc( QStringLiteral( "dummy" ) );
403  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp, doc );
404 
405  if ( rampEl.isNull() )
406  {
407  QgsDebugMsg( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
408  return false;
409  }
410 
411  QByteArray xmlArray;
412  QTextStream stream( &xmlArray );
413  stream.setCodec( "UTF-8" );
414  rampEl.save( stream, 4 );
415  QString query = qgs_sqlite3_mprintf( "INSERT INTO colorramp VALUES (NULL, '%q', '%q', %d);",
416  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
417  if ( !runEmptyQuery( query ) )
418  {
419  QgsDebugMsg( QStringLiteral( "Couldn't insert colorramp into the database!" ) );
420  return false;
421  }
422 
423  mCachedFavorites[ ColorrampEntity ].insert( name, favorite );
424 
425  tagSymbol( ColorrampEntity, name, tags );
426 
427  emit rampAdded( name );
428  emit entityAdded( ColorrampEntity, name );
429 
430  return true;
431 }
432 
433 bool QgsStyle::removeColorRamp( const QString &name )
434 {
435  return removeEntityByName( ColorrampEntity, name );
436 }
437 
438 QgsColorRamp *QgsStyle::colorRamp( const QString &name ) const
439 {
440  const QgsColorRamp *ramp = colorRampRef( name );
441  return ramp ? ramp->clone() : nullptr;
442 }
443 
444 const QgsColorRamp *QgsStyle::colorRampRef( const QString &name ) const
445 {
446  return mColorRamps.value( name );
447 }
448 
450 {
451  return mColorRamps.count();
452 }
453 
454 QStringList QgsStyle::colorRampNames() const
455 {
456  return mColorRamps.keys();
457 }
458 
459 void QgsStyle::handleDeferred3DSymbolCreation()
460 {
461  for ( auto it = mDeferred3DsymbolElements.constBegin(); it != mDeferred3DsymbolElements.constEnd(); ++it )
462  {
463  const QString symbolType = it.value().attribute( QStringLiteral( "type" ) );
464  std::unique_ptr< QgsAbstract3DSymbol > symbol( QgsApplication::symbol3DRegistry()->createSymbol( symbolType ) );
465  if ( symbol )
466  {
467  symbol->readXml( it.value(), QgsReadWriteContext() );
468  addSymbol3D( it.key(), symbol.release(), false );
469  emit entityAdded( Symbol3DEntity, it.key() );
470  }
471  else
472  {
473  QgsDebugMsg( "Cannot open 3d symbol " + it.key() );
474  continue;
475  }
476  }
477  mDeferred3DsymbolElements.clear();
478 }
479 
480 bool QgsStyle::openDatabase( const QString &filename )
481 {
482  int rc = mCurrentDB.open( filename );
483  if ( rc )
484  {
485  mErrorString = QStringLiteral( "Couldn't open the style database: %1" ).arg( mCurrentDB.errorMessage() );
486  return false;
487  }
488 
489  return true;
490 }
491 
492 bool QgsStyle::createDatabase( const QString &filename )
493 {
494  mErrorString.clear();
495  if ( !openDatabase( filename ) )
496  {
497  mErrorString = QStringLiteral( "Unable to create database" );
498  QgsDebugMsg( mErrorString );
499  return false;
500  }
501 
502  createTables();
503 
504  return true;
505 }
506 
508 {
509  mErrorString.clear();
510  if ( !openDatabase( QStringLiteral( ":memory:" ) ) )
511  {
512  mErrorString = QStringLiteral( "Unable to create temporary memory database" );
513  QgsDebugMsg( mErrorString );
514  return false;
515  }
516 
517  createTables();
518 
519  return true;
520 }
521 
523 {
524  QString query = qgs_sqlite3_mprintf( "CREATE TABLE symbol("\
525  "id INTEGER PRIMARY KEY,"\
526  "name TEXT UNIQUE,"\
527  "xml TEXT,"\
528  "favorite INTEGER);"\
529  "CREATE TABLE colorramp("\
530  "id INTEGER PRIMARY KEY,"\
531  "name TEXT UNIQUE,"\
532  "xml TEXT,"\
533  "favorite INTEGER);"\
534  "CREATE TABLE textformat("\
535  "id INTEGER PRIMARY KEY,"\
536  "name TEXT UNIQUE,"\
537  "xml TEXT,"\
538  "favorite INTEGER);"\
539  "CREATE TABLE labelsettings("\
540  "id INTEGER PRIMARY KEY,"\
541  "name TEXT UNIQUE,"\
542  "xml TEXT,"\
543  "favorite INTEGER);"\
544  "CREATE TABLE legendpatchshapes("\
545  "id INTEGER PRIMARY KEY,"\
546  "name TEXT UNIQUE,"\
547  "xml TEXT,"\
548  "favorite INTEGER);"\
549  "CREATE TABLE symbol3d("\
550  "id INTEGER PRIMARY KEY,"\
551  "name TEXT UNIQUE,"\
552  "xml TEXT,"\
553  "favorite INTEGER);"\
554  "CREATE TABLE tag("\
555  "id INTEGER PRIMARY KEY,"\
556  "name TEXT);"\
557  "CREATE TABLE tagmap("\
558  "tag_id INTEGER NOT NULL,"\
559  "symbol_id INTEGER);"\
560  "CREATE TABLE ctagmap("\
561  "tag_id INTEGER NOT NULL,"\
562  "colorramp_id INTEGER);"\
563  "CREATE TABLE tftagmap("\
564  "tag_id INTEGER NOT NULL,"\
565  "textformat_id INTEGER);"\
566  "CREATE TABLE lstagmap("\
567  "tag_id INTEGER NOT NULL,"\
568  "labelsettings_id INTEGER);"\
569  "CREATE TABLE lpstagmap("\
570  "tag_id INTEGER NOT NULL,"\
571  "legendpatchshape_id INTEGER);"\
572  "CREATE TABLE symbol3dtagmap("\
573  "tag_id INTEGER NOT NULL,"\
574  "symbol3d_id INTEGER);"\
575  "CREATE TABLE smartgroup("\
576  "id INTEGER PRIMARY KEY,"\
577  "name TEXT,"\
578  "xml TEXT);" );
579  runEmptyQuery( query );
580 }
581 
582 bool QgsStyle::load( const QString &filename )
583 {
584  mErrorString.clear();
585 
586  // Open the sqlite database
587  if ( !openDatabase( filename ) )
588  {
589  mErrorString = QStringLiteral( "Unable to open database file specified" );
590  QgsDebugMsg( mErrorString );
591  return false;
592  }
593 
594  // make sure text format table exists
595  QString query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='textformat'" );
597  int rc;
598  statement = mCurrentDB.prepare( query, rc );
599  if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
600  {
601  query = qgs_sqlite3_mprintf( "CREATE TABLE textformat("\
602  "id INTEGER PRIMARY KEY,"\
603  "name TEXT UNIQUE,"\
604  "xml TEXT,"\
605  "favorite INTEGER);"\
606  "CREATE TABLE tftagmap("\
607  "tag_id INTEGER NOT NULL,"\
608  "textformat_id INTEGER);" );
609  runEmptyQuery( query );
610  }
611  // make sure label settings table exists
612  query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='labelsettings'" );
613  statement = mCurrentDB.prepare( query, rc );
614  if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
615  {
616  query = qgs_sqlite3_mprintf( "CREATE TABLE labelsettings("\
617  "id INTEGER PRIMARY KEY,"\
618  "name TEXT UNIQUE,"\
619  "xml TEXT,"\
620  "favorite INTEGER);"\
621  "CREATE TABLE lstagmap("\
622  "tag_id INTEGER NOT NULL,"\
623  "labelsettings_id INTEGER);" );
624  runEmptyQuery( query );
625  }
626  // make sure legend patch shape table exists
627  query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='legendpatchshapes'" );
628  statement = mCurrentDB.prepare( query, rc );
629  if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
630  {
631  query = qgs_sqlite3_mprintf( "CREATE TABLE legendpatchshapes("\
632  "id INTEGER PRIMARY KEY,"\
633  "name TEXT UNIQUE,"\
634  "xml TEXT,"\
635  "favorite INTEGER);"\
636  "CREATE TABLE lpstagmap("\
637  "tag_id INTEGER NOT NULL,"\
638  "legendpatchshape_id INTEGER);" );
639  runEmptyQuery( query );
640  }
641  // make sure 3d symbol table exists
642  query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='symbol3d'" );
643  statement = mCurrentDB.prepare( query, rc );
644  if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
645  {
646  query = qgs_sqlite3_mprintf( "CREATE TABLE symbol3d("\
647  "id INTEGER PRIMARY KEY,"\
648  "name TEXT UNIQUE,"\
649  "xml TEXT,"\
650  "favorite INTEGER);"\
651  "CREATE TABLE symbol3dtagmap("\
652  "tag_id INTEGER NOT NULL,"\
653  "symbol3d_id INTEGER);" );
654  runEmptyQuery( query );
655  }
656 
657  // Make sure there are no Null fields in parenting symbols and groups
658  query = qgs_sqlite3_mprintf( "UPDATE symbol SET favorite=0 WHERE favorite IS NULL;"
659  "UPDATE colorramp SET favorite=0 WHERE favorite IS NULL;"
660  "UPDATE textformat SET favorite=0 WHERE favorite IS NULL;"
661  "UPDATE labelsettings SET favorite=0 WHERE favorite IS NULL;"
662  "UPDATE legendpatchshapes SET favorite=0 WHERE favorite IS NULL;"
663  "UPDATE symbol3d SET favorite=0 WHERE favorite IS NULL;"
664  );
665  runEmptyQuery( query );
666 
667  {
668  QgsScopedRuntimeProfile profile( tr( "Load symbols" ) );
669  // First create all the main symbols
670  query = qgs_sqlite3_mprintf( "SELECT * FROM symbol" );
671  statement = mCurrentDB.prepare( query, rc );
672 
673  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
674  {
675  QDomDocument doc;
676  QString symbolName = statement.columnAsText( SymbolName );
677  QgsScopedRuntimeProfile profile( symbolName );
678  QString xmlstring = statement.columnAsText( SymbolXML );
679  if ( !doc.setContent( xmlstring ) )
680  {
681  QgsDebugMsg( "Cannot open symbol " + symbolName );
682  continue;
683  }
684 
685  QDomElement symElement = doc.documentElement();
687  if ( symbol )
688  mSymbols.insert( symbolName, symbol );
689  }
690  }
691 
692  {
693  QgsScopedRuntimeProfile profile( tr( "Load color ramps" ) );
694  query = qgs_sqlite3_mprintf( "SELECT * FROM colorramp" );
695  statement = mCurrentDB.prepare( query, rc );
696  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
697  {
698  QDomDocument doc;
699  const QString rampName = statement.columnAsText( ColorrampName );
700  QgsScopedRuntimeProfile profile( rampName );
701  QString xmlstring = statement.columnAsText( ColorrampXML );
702  if ( !doc.setContent( xmlstring ) )
703  {
704  QgsDebugMsg( "Cannot open symbol " + rampName );
705  continue;
706  }
707  QDomElement rampElement = doc.documentElement();
708  QgsColorRamp *ramp = QgsSymbolLayerUtils::loadColorRamp( rampElement );
709  if ( ramp )
710  mColorRamps.insert( rampName, ramp );
711  }
712  }
713 
714  {
715  QgsScopedRuntimeProfile profile( tr( "Load text formats" ) );
716  query = qgs_sqlite3_mprintf( "SELECT * FROM textformat" );
717  statement = mCurrentDB.prepare( query, rc );
718  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
719  {
720  QDomDocument doc;
721  const QString formatName = statement.columnAsText( TextFormatName );
722  QgsScopedRuntimeProfile profile( formatName );
723  const QString xmlstring = statement.columnAsText( TextFormatXML );
724  if ( !doc.setContent( xmlstring ) )
725  {
726  QgsDebugMsg( "Cannot open text format " + formatName );
727  continue;
728  }
729  QDomElement formatElement = doc.documentElement();
730  QgsTextFormat format;
731  format.readXml( formatElement, QgsReadWriteContext() );
732  mTextFormats.insert( formatName, format );
733  }
734  }
735 
736  {
737  QgsScopedRuntimeProfile profile( tr( "Load label settings" ) );
738  query = qgs_sqlite3_mprintf( "SELECT * FROM labelsettings" );
739  statement = mCurrentDB.prepare( query, rc );
740  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
741  {
742  QDomDocument doc;
743  const QString settingsName = statement.columnAsText( LabelSettingsName );
744  QgsScopedRuntimeProfile profile( settingsName );
745  const QString xmlstring = statement.columnAsText( LabelSettingsXML );
746  if ( !doc.setContent( xmlstring ) )
747  {
748  QgsDebugMsg( "Cannot open label settings " + settingsName );
749  continue;
750  }
751  QDomElement settingsElement = doc.documentElement();
752  QgsPalLayerSettings settings;
753  settings.readXml( settingsElement, QgsReadWriteContext() );
754  mLabelSettings.insert( settingsName, settings );
755  }
756  }
757 
758  {
759  QgsScopedRuntimeProfile profile( tr( "Load legend patch shapes" ) );
760  query = qgs_sqlite3_mprintf( "SELECT * FROM legendpatchshapes" );
761  statement = mCurrentDB.prepare( query, rc );
762  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
763  {
764  QDomDocument doc;
765  const QString settingsName = statement.columnAsText( LegendPatchTableName );
766  QgsScopedRuntimeProfile profile( settingsName );
767  const QString xmlstring = statement.columnAsText( LegendPatchTableXML );
768  if ( !doc.setContent( xmlstring ) )
769  {
770  QgsDebugMsg( "Cannot open legend patch shape " + settingsName );
771  continue;
772  }
773  QDomElement settingsElement = doc.documentElement();
774  QgsLegendPatchShape shape;
775  shape.readXml( settingsElement, QgsReadWriteContext() );
776  mLegendPatchShapes.insert( settingsName, shape );
777  }
778  }
779 
780  {
781  QgsScopedRuntimeProfile profile( tr( "Load 3D symbols shapes" ) );
782  query = qgs_sqlite3_mprintf( "SELECT * FROM symbol3d" );
783  statement = mCurrentDB.prepare( query, rc );
784 
785  const bool registry3dPopulated = !QgsApplication::symbol3DRegistry()->symbolTypes().empty();
786 
787  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
788  {
789  QDomDocument doc;
790  const QString settingsName = statement.columnAsText( Symbol3DTableName );
791  QgsScopedRuntimeProfile profile( settingsName );
792  const QString xmlstring = statement.columnAsText( Symbol3DTableXML );
793  if ( !doc.setContent( xmlstring ) )
794  {
795  QgsDebugMsg( "Cannot open 3d symbol " + settingsName );
796  continue;
797  }
798  QDomElement settingsElement = doc.documentElement();
799 
800  if ( !registry3dPopulated )
801  {
802  mDeferred3DsymbolElements.insert( settingsName, settingsElement );
803  }
804  else
805  {
806  const QString symbolType = settingsElement.attribute( QStringLiteral( "type" ) );
807  std::unique_ptr< QgsAbstract3DSymbol > symbol( QgsApplication::symbol3DRegistry()->createSymbol( symbolType ) );
808  if ( symbol )
809  {
810  symbol->readXml( settingsElement, QgsReadWriteContext() );
811  m3dSymbols.insert( settingsName, symbol.release() );
812  }
813  else
814  {
815  QgsDebugMsg( "Cannot open 3d symbol " + settingsName );
816  continue;
817  }
818  }
819  }
820  }
821 
822  mFileName = filename;
823  return true;
824 }
825 
826 
827 
828 bool QgsStyle::save( QString filename )
829 {
830  mErrorString.clear();
831 
832  if ( filename.isEmpty() )
833  filename = mFileName;
834 
835  // TODO evaluate the requirement of this function and change implementation accordingly
836  // TODO remove QEXPECT_FAIL from TestStyle::testSaveLoad() when done
837 #if 0
838  QDomDocument doc( "qgis_style" );
839  QDomElement root = doc.createElement( "qgis_style" );
840  root.setAttribute( "version", STYLE_CURRENT_VERSION );
841  doc.appendChild( root );
842 
843  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, "symbols", doc );
844 
845  QDomElement rampsElem = doc.createElement( "colorramps" );
846 
847  // save color ramps
848  for ( QMap<QString, QgsColorRamp *>::iterator itr = mColorRamps.begin(); itr != mColorRamps.end(); ++itr )
849  {
850  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
851  rampsElem.appendChild( rampEl );
852  }
853 
854  root.appendChild( symbolsElem );
855  root.appendChild( rampsElem );
856 
857  // save
858  QFile f( filename );
859  if ( !f.open( QFile::WriteOnly ) )
860  {
861  mErrorString = "Couldn't open file for writing: " + filename;
862  return false;
863  }
864  QTextStream ts( &f );
865  ts.setCodec( "UTF-8" );
866  doc.save( ts, 2 );
867  f.close();
868 #endif
869 
870  mFileName = filename;
871  return true;
872 }
873 
874 bool QgsStyle::renameSymbol( const QString &oldName, const QString &newName )
875 {
876  if ( mSymbols.contains( newName ) )
877  {
878  QgsDebugMsg( QStringLiteral( "Symbol of new name already exists" ) );
879  return false;
880  }
881 
882  QgsSymbol *symbol = mSymbols.take( oldName );
883  if ( !symbol )
884  return false;
885 
886  mSymbols.insert( newName, symbol );
887 
888  if ( !mCurrentDB )
889  {
890  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to tag." ) );
891  return false;
892  }
893 
894  int symbolid = symbolId( oldName );
895  if ( !symbolid )
896  {
897  QgsDebugMsg( QStringLiteral( "No such symbol for tagging in database: " ) + oldName );
898  return false;
899  }
900 
901  mCachedTags[ SymbolEntity ].remove( oldName );
902  mCachedFavorites[ SymbolEntity ].remove( oldName );
903 
904  const bool result = rename( SymbolEntity, symbolid, newName );
905  if ( result )
906  {
907  emit symbolRenamed( oldName, newName );
908  emit entityRenamed( SymbolEntity, oldName, newName );
909  }
910 
911  return result;
912 }
913 
914 bool QgsStyle::renameColorRamp( const QString &oldName, const QString &newName )
915 {
916  if ( mColorRamps.contains( newName ) )
917  {
918  QgsDebugMsg( QStringLiteral( "Color ramp of new name already exists." ) );
919  return false;
920  }
921 
922  QgsColorRamp *ramp = mColorRamps.take( oldName );
923  if ( !ramp )
924  return false;
925 
926  mColorRamps.insert( newName, ramp );
927  mCachedTags[ ColorrampEntity ].remove( oldName );
928  mCachedFavorites[ ColorrampEntity ].remove( oldName );
929 
930  int rampid = 0;
932  QString query = qgs_sqlite3_mprintf( "SELECT id FROM colorramp WHERE name='%q'", oldName.toUtf8().constData() );
933  int nErr;
934  statement = mCurrentDB.prepare( query, nErr );
935  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
936  {
937  rampid = sqlite3_column_int( statement.get(), 0 );
938  }
939  const bool result = rename( ColorrampEntity, rampid, newName );
940  if ( result )
941  {
942  emit rampRenamed( oldName, newName );
943  emit entityRenamed( ColorrampEntity, oldName, newName );
944  }
945 
946  return result;
947 }
948 
949 bool QgsStyle::saveTextFormat( const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags )
950 {
951  // insert it into the database
952  QDomDocument doc( QStringLiteral( "dummy" ) );
953  QDomElement formatElem = format.writeXml( doc, QgsReadWriteContext() );
954 
955  if ( formatElem.isNull() )
956  {
957  QgsDebugMsg( QStringLiteral( "Couldn't convert text format to valid XML!" ) );
958  return false;
959  }
960 
961  QByteArray xmlArray;
962  QTextStream stream( &xmlArray );
963  stream.setCodec( "UTF-8" );
964  formatElem.save( stream, 4 );
965  QString query = qgs_sqlite3_mprintf( "INSERT INTO textformat VALUES (NULL, '%q', '%q', %d);",
966  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
967  if ( !runEmptyQuery( query ) )
968  {
969  QgsDebugMsg( QStringLiteral( "Couldn't insert text format into the database!" ) );
970  return false;
971  }
972 
973  mCachedFavorites[ TextFormatEntity ].insert( name, favorite );
974 
975  tagSymbol( TextFormatEntity, name, tags );
976 
977  emit textFormatAdded( name );
978  emit entityAdded( TextFormatEntity, name );
979 
980  return true;
981 }
982 
983 bool QgsStyle::removeTextFormat( const QString &name )
984 {
985  return removeEntityByName( TextFormatEntity, name );
986 }
987 
988 bool QgsStyle::renameTextFormat( const QString &oldName, const QString &newName )
989 {
990  if ( mTextFormats.contains( newName ) )
991  {
992  QgsDebugMsg( QStringLiteral( "Text format of new name already exists." ) );
993  return false;
994  }
995 
996  if ( !mTextFormats.contains( oldName ) )
997  return false;
998  QgsTextFormat format = mTextFormats.take( oldName );
999 
1000  mTextFormats.insert( newName, format );
1001  mCachedTags[ TextFormatEntity ].remove( oldName );
1002  mCachedFavorites[ TextFormatEntity ].remove( oldName );
1003 
1004  int textFormatId = 0;
1005  sqlite3_statement_unique_ptr statement;
1006  QString query = qgs_sqlite3_mprintf( "SELECT id FROM textformat WHERE name='%q'", oldName.toUtf8().constData() );
1007  int nErr;
1008  statement = mCurrentDB.prepare( query, nErr );
1009  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1010  {
1011  textFormatId = sqlite3_column_int( statement.get(), 0 );
1012  }
1013  const bool result = rename( TextFormatEntity, textFormatId, newName );
1014  if ( result )
1015  {
1016  emit textFormatRenamed( oldName, newName );
1017  emit entityRenamed( TextFormatEntity, oldName, newName );
1018  }
1019 
1020  return result;
1021 }
1022 
1023 bool QgsStyle::saveLabelSettings( const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags )
1024 {
1025  // insert it into the database
1026  QDomDocument doc( QStringLiteral( "dummy" ) );
1027  QDomElement settingsElem = settings.writeXml( doc, QgsReadWriteContext() );
1028 
1029  if ( settingsElem.isNull() )
1030  {
1031  QgsDebugMsg( QStringLiteral( "Couldn't convert label settings to valid XML!" ) );
1032  return false;
1033  }
1034 
1035  QByteArray xmlArray;
1036  QTextStream stream( &xmlArray );
1037  stream.setCodec( "UTF-8" );
1038  settingsElem.save( stream, 4 );
1039  QString query = qgs_sqlite3_mprintf( "INSERT INTO labelsettings VALUES (NULL, '%q', '%q', %d);",
1040  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1041  if ( !runEmptyQuery( query ) )
1042  {
1043  QgsDebugMsg( QStringLiteral( "Couldn't insert label settings into the database!" ) );
1044  return false;
1045  }
1046 
1047  mCachedFavorites[ LabelSettingsEntity ].insert( name, favorite );
1048 
1049  tagSymbol( LabelSettingsEntity, name, tags );
1050 
1051  emit labelSettingsAdded( name );
1052  emit entityAdded( LabelSettingsEntity, name );
1053 
1054  return true;
1055 }
1056 
1057 bool QgsStyle::removeLabelSettings( const QString &name )
1058 {
1059  return removeEntityByName( LabelSettingsEntity, name );
1060 }
1061 
1062 bool QgsStyle::renameLabelSettings( const QString &oldName, const QString &newName )
1063 {
1064  if ( mLabelSettings.contains( newName ) )
1065  {
1066  QgsDebugMsg( QStringLiteral( "Label settings of new name already exists." ) );
1067  return false;
1068  }
1069 
1070  if ( !mLabelSettings.contains( oldName ) )
1071  return false;
1072  QgsPalLayerSettings settings = mLabelSettings.take( oldName );
1073 
1074  mLabelSettings.insert( newName, settings );
1075  mCachedTags[ LabelSettingsEntity ].remove( oldName );
1076  mCachedFavorites[ LabelSettingsEntity ].remove( oldName );
1077 
1078  int labelSettingsId = 0;
1079  sqlite3_statement_unique_ptr statement;
1080  QString query = qgs_sqlite3_mprintf( "SELECT id FROM labelsettings WHERE name='%q'", oldName.toUtf8().constData() );
1081  int nErr;
1082  statement = mCurrentDB.prepare( query, nErr );
1083  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1084  {
1085  labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1086  }
1087  const bool result = rename( LabelSettingsEntity, labelSettingsId, newName );
1088  if ( result )
1089  {
1090  emit labelSettingsRenamed( oldName, newName );
1091  emit entityRenamed( LabelSettingsEntity, oldName, newName );
1092  }
1093 
1094  return result;
1095 }
1096 
1097 bool QgsStyle::saveLegendPatchShape( const QString &name, const QgsLegendPatchShape &shape, bool favorite, const QStringList &tags )
1098 {
1099  // insert it into the database
1100  QDomDocument doc( QStringLiteral( "dummy" ) );
1101  QDomElement shapeElem = doc.createElement( QStringLiteral( "shape" ) );
1102  shape.writeXml( shapeElem, doc, QgsReadWriteContext() );
1103 
1104  QByteArray xmlArray;
1105  QTextStream stream( &xmlArray );
1106  stream.setCodec( "UTF-8" );
1107  shapeElem.save( stream, 4 );
1108  QString query = qgs_sqlite3_mprintf( "INSERT INTO legendpatchshapes VALUES (NULL, '%q', '%q', %d);",
1109  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1110  if ( !runEmptyQuery( query ) )
1111  {
1112  QgsDebugMsg( QStringLiteral( "Couldn't insert legend patch shape into the database!" ) );
1113  return false;
1114  }
1115 
1116  mCachedFavorites[ LegendPatchShapeEntity ].insert( name, favorite );
1117 
1118  tagSymbol( LegendPatchShapeEntity, name, tags );
1119 
1120  emit entityAdded( LegendPatchShapeEntity, name );
1121 
1122  return true;
1123 }
1124 
1125 bool QgsStyle::renameLegendPatchShape( const QString &oldName, const QString &newName )
1126 {
1127  if ( mLegendPatchShapes.contains( newName ) )
1128  {
1129  QgsDebugMsg( QStringLiteral( "Legend patch shape of new name already exists." ) );
1130  return false;
1131  }
1132 
1133  if ( !mLegendPatchShapes.contains( oldName ) )
1134  return false;
1135  QgsLegendPatchShape shape = mLegendPatchShapes.take( oldName );
1136 
1137  mLegendPatchShapes.insert( newName, shape );
1138  mCachedTags[ LegendPatchShapeEntity ].remove( oldName );
1139  mCachedFavorites[ LegendPatchShapeEntity ].remove( oldName );
1140 
1141  int labelSettingsId = 0;
1142  sqlite3_statement_unique_ptr statement;
1143  QString query = qgs_sqlite3_mprintf( "SELECT id FROM legendpatchshapes WHERE name='%q'", oldName.toUtf8().constData() );
1144  int nErr;
1145  statement = mCurrentDB.prepare( query, nErr );
1146  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1147  {
1148  labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1149  }
1150  const bool result = rename( LegendPatchShapeEntity, labelSettingsId, newName );
1151  if ( result )
1152  {
1153  emit entityRenamed( LegendPatchShapeEntity, oldName, newName );
1154  }
1155 
1156  return result;
1157 }
1158 
1160 {
1161  if ( type == QgsSymbol::Hybrid )
1162  return QgsLegendPatchShape();
1163 
1164  if ( mDefaultPatchCache[ type ].contains( size ) )
1165  return mDefaultPatchCache[ type ].value( size );
1166 
1167  QgsGeometry geom;
1168  switch ( type )
1169  {
1170  case QgsSymbol::Marker:
1171  geom = QgsGeometry( qgis::make_unique< QgsPoint >( static_cast< int >( size.width() ) / 2, static_cast< int >( size.height() ) / 2 ) );
1172  break;
1173 
1174  case QgsSymbol::Line:
1175  {
1176  // we're adding 0.5 to get rid of blurred preview:
1177  // drawing antialiased lines of width 1 at (x,0)-(x,100) creates 2px line
1178  double y = static_cast< int >( size.height() ) / 2 + 0.5;
1179  geom = QgsGeometry( qgis::make_unique< QgsLineString >( ( QVector< double >() << 0 << size.width() ),
1180  ( QVector< double >() << y << y ) ) );
1181  break;
1182  }
1183 
1184  case QgsSymbol::Fill:
1185  {
1186  geom = QgsGeometry( qgis::make_unique< QgsPolygon >(
1187  new QgsLineString( QVector< double >() << 0 << static_cast< int >( size.width() ) << static_cast< int >( size.width() ) << 0 << 0,
1188  QVector< double >() << static_cast< int >( size.height() ) << static_cast< int >( size.height() ) << 0 << 0 << static_cast< int >( size.height() ) ) ) );
1189  break;
1190  }
1191 
1192  case QgsSymbol::Hybrid:
1193  break;
1194  }
1195 
1196  QgsLegendPatchShape res = QgsLegendPatchShape( type, geom, false );
1197  mDefaultPatchCache[ type ][size ] = res;
1198  return res;
1199 }
1200 
1201 QList<QList<QPolygonF> > QgsStyle::defaultPatchAsQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const
1202 {
1203  if ( type == QgsSymbol::Hybrid )
1204  return QList<QList<QPolygonF> >();
1205 
1206  if ( mDefaultPatchQPolygonFCache[ type ].contains( size ) )
1207  return mDefaultPatchQPolygonFCache[ type ].value( size );
1208 
1209  QList<QList<QPolygonF> > res = defaultPatch( type, size ).toQPolygonF( type, size );
1210  mDefaultPatchQPolygonFCache[ type ][size ] = res;
1211  return res;
1212 }
1213 
1214 bool QgsStyle::saveSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags )
1215 {
1216  // insert it into the database
1217  QDomDocument doc( QStringLiteral( "dummy" ) );
1218  QDomElement elem = doc.createElement( QStringLiteral( "symbol" ) );
1219  elem.setAttribute( QStringLiteral( "type" ), symbol->type() );
1220  symbol->writeXml( elem, QgsReadWriteContext() );
1221 
1222  QByteArray xmlArray;
1223  QTextStream stream( &xmlArray );
1224  stream.setCodec( "UTF-8" );
1225  elem.save( stream, 4 );
1226  QString query = qgs_sqlite3_mprintf( "INSERT INTO symbol3d VALUES (NULL, '%q', '%q', %d);",
1227  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1228  if ( !runEmptyQuery( query ) )
1229  {
1230  QgsDebugMsg( QStringLiteral( "Couldn't insert 3d symbol into the database!" ) );
1231  return false;
1232  }
1233 
1234  mCachedFavorites[ Symbol3DEntity ].insert( name, favorite );
1235 
1236  tagSymbol( Symbol3DEntity, name, tags );
1237 
1238  emit entityAdded( Symbol3DEntity, name );
1239 
1240  return true;
1241 }
1242 
1243 bool QgsStyle::renameSymbol3D( const QString &oldName, const QString &newName )
1244 {
1245  if ( m3dSymbols.contains( newName ) )
1246  {
1247  QgsDebugMsg( QStringLiteral( "3d symbol of new name already exists." ) );
1248  return false;
1249  }
1250 
1251  if ( !m3dSymbols.contains( oldName ) )
1252  return false;
1253  QgsAbstract3DSymbol *symbol = m3dSymbols.take( oldName );
1254 
1255  m3dSymbols.insert( newName, symbol );
1256  mCachedTags[Symbol3DEntity ].remove( oldName );
1257  mCachedFavorites[ Symbol3DEntity ].remove( oldName );
1258 
1259  int labelSettingsId = 0;
1260  sqlite3_statement_unique_ptr statement;
1261  QString query = qgs_sqlite3_mprintf( "SELECT id FROM symbol3d WHERE name='%q'", oldName.toUtf8().constData() );
1262  int nErr;
1263  statement = mCurrentDB.prepare( query, nErr );
1264  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1265  {
1266  labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1267  }
1268  const bool result = rename( Symbol3DEntity, labelSettingsId, newName );
1269  if ( result )
1270  {
1271  emit entityRenamed( Symbol3DEntity, oldName, newName );
1272  }
1273 
1274  return result;
1275 }
1276 
1277 QStringList QgsStyle::symbol3DNames() const
1278 {
1279  return m3dSymbols.keys();
1280 }
1281 
1282 QStringList QgsStyle::symbolsOfFavorite( StyleEntity type ) const
1283 {
1284  if ( !mCurrentDB )
1285  {
1286  QgsDebugMsg( QStringLiteral( "Cannot Open database for getting favorite symbols" ) );
1287  return QStringList();
1288  }
1289 
1290  QString query;
1291  switch ( type )
1292  {
1293  case TagEntity:
1294  case SmartgroupEntity:
1295  QgsDebugMsg( QStringLiteral( "No such style entity" ) );
1296  return QStringList();
1297 
1298  default:
1299  query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT name FROM %1 WHERE favorite=1" ).arg( entityTableName( type ) ).toLocal8Bit().data() );
1300  break;
1301  }
1302 
1303  int nErr;
1304  sqlite3_statement_unique_ptr statement;
1305  statement = mCurrentDB.prepare( query, nErr );
1306 
1307  QStringList symbols;
1308  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1309  {
1310  symbols << statement.columnAsText( 0 );
1311  }
1312 
1313  return symbols;
1314 }
1315 
1316 QStringList QgsStyle::symbolsWithTag( StyleEntity type, int tagid ) const
1317 {
1318  if ( !mCurrentDB )
1319  {
1320  QgsDebugMsg( QStringLiteral( "Cannot open database to get symbols of tagid %1" ).arg( tagid ) );
1321  return QStringList();
1322  }
1323 
1324  QString subquery;
1325  switch ( type )
1326  {
1327  case TagEntity:
1328  case SmartgroupEntity:
1329  QgsDebugMsg( QStringLiteral( "Unknown Entity" ) );
1330  return QStringList();
1331 
1332  default:
1333  subquery = qgs_sqlite3_mprintf( QStringLiteral( "SELECT %1 FROM %2 WHERE tag_id=%d" ).arg( tagmapEntityIdFieldName( type ),
1334  tagmapTableName( type ) ).toLocal8Bit().data(), tagid );
1335  break;
1336  }
1337 
1338  int nErr;
1339  sqlite3_statement_unique_ptr statement;
1340  statement = mCurrentDB.prepare( subquery, nErr );
1341 
1342  // get the symbol <-> tag connection from the tag map table
1343  QStringList symbols;
1344  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1345  {
1346  int id = sqlite3_column_int( statement.get(), 0 );
1347 
1348  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT name FROM %1 WHERE id=%d" ).arg( entityTableName( type ) ).toLocal8Bit().data(), id );
1349 
1350  int rc;
1351  sqlite3_statement_unique_ptr statement2;
1352  statement2 = mCurrentDB.prepare( query, rc );
1353  while ( rc == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1354  {
1355  symbols << statement2.columnAsText( 0 );
1356  }
1357  }
1358 
1359  return symbols;
1360 }
1361 
1362 int QgsStyle::addTag( const QString &tagname )
1363 {
1364  if ( !mCurrentDB )
1365  return 0;
1366  sqlite3_statement_unique_ptr statement;
1367 
1368  QString query = qgs_sqlite3_mprintf( "INSERT INTO tag VALUES (NULL, '%q')", tagname.toUtf8().constData() );
1369  int nErr;
1370  statement = mCurrentDB.prepare( query, nErr );
1371  if ( nErr == SQLITE_OK )
1372  ( void )sqlite3_step( statement.get() );
1373 
1374  QgsSettings settings;
1375  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1376 
1377  emit groupsModified();
1378 
1379  return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
1380 }
1381 
1382 QStringList QgsStyle::tags() const
1383 {
1384  if ( !mCurrentDB )
1385  return QStringList();
1386 
1387  sqlite3_statement_unique_ptr statement;
1388 
1389  QString query = qgs_sqlite3_mprintf( "SELECT name FROM tag" );
1390  int nError;
1391  statement = mCurrentDB.prepare( query, nError );
1392 
1393  QStringList tagList;
1394  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1395  {
1396  tagList << statement.columnAsText( 0 );
1397  }
1398 
1399  return tagList;
1400 }
1401 
1402 bool QgsStyle::rename( StyleEntity type, int id, const QString &newName )
1403 {
1404  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET name='%q' WHERE id=%d" ).arg( entityTableName( type ) ).toLocal8Bit().data(), newName.toUtf8().constData(), id );
1405 
1406  const bool result = runEmptyQuery( query );
1407  if ( !result )
1408  {
1409  mErrorString = QStringLiteral( "Could not rename!" );
1410  }
1411  else
1412  {
1413  mCachedTags.clear();
1414  mCachedFavorites.clear();
1415 
1416  switch ( type )
1417  {
1418  case TagEntity:
1419  {
1420  emit groupsModified();
1421  break;
1422  }
1423 
1424  case SmartgroupEntity:
1425  {
1426  emit groupsModified();
1427  break;
1428  }
1429 
1430  default:
1431  break;
1432  }
1433  }
1434  return result;
1435 }
1436 
1437 bool QgsStyle::remove( StyleEntity type, int id )
1438 {
1439  bool groupRemoved = false;
1440  QString query;
1441  switch ( type )
1442  {
1443  case TagEntity:
1444  query = qgs_sqlite3_mprintf( "DELETE FROM tag WHERE id=%d; DELETE FROM tagmap WHERE tag_id=%d", id, id );
1445  groupRemoved = true;
1446  break;
1447  case SmartgroupEntity:
1448  query = qgs_sqlite3_mprintf( "DELETE FROM smartgroup WHERE id=%d", id );
1449  groupRemoved = true;
1450  break;
1451 
1452  default:
1453  query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE id=%d; DELETE FROM %2 WHERE %3=%d" ).arg(
1454  entityTableName( type ),
1455  tagmapTableName( type ),
1456  tagmapEntityIdFieldName( type )
1457  ).toLocal8Bit().data(), id, id );
1458  break;
1459  }
1460 
1461  bool result = false;
1462  if ( !runEmptyQuery( query ) )
1463  {
1464  QgsDebugMsg( QStringLiteral( "Could not delete entity!" ) );
1465  }
1466  else
1467  {
1468  mCachedTags.clear();
1469  mCachedFavorites.clear();
1470 
1471  if ( groupRemoved )
1472  {
1473  QgsSettings settings;
1474  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1475 
1476  emit groupsModified();
1477  }
1478  result = true;
1479  }
1480  return result;
1481 }
1482 
1483 bool QgsStyle::removeEntityByName( QgsStyle::StyleEntity type, const QString &name )
1484 {
1485  switch ( type )
1486  {
1487  case QgsStyle::TagEntity:
1489  return false;
1490 
1492  {
1493  std::unique_ptr< QgsSymbol > symbol( mSymbols.take( name ) );
1494  if ( !symbol )
1495  return false;
1496 
1497  break;
1498  }
1499 
1501  {
1502  std::unique_ptr< QgsAbstract3DSymbol > symbol( m3dSymbols.take( name ) );
1503  if ( !symbol )
1504  return false;
1505 
1506  break;
1507  }
1508 
1510  {
1511  std::unique_ptr< QgsColorRamp > ramp( mColorRamps.take( name ) );
1512  if ( !ramp )
1513  return false;
1514  break;
1515  }
1516 
1518  {
1519  if ( !mTextFormats.contains( name ) )
1520  return false;
1521 
1522  mTextFormats.remove( name );
1523  break;
1524  }
1525 
1527  {
1528  if ( !mLabelSettings.contains( name ) )
1529  return false;
1530 
1531  mLabelSettings.remove( name );
1532  break;
1533  }
1534 
1536  {
1537  if ( !mLegendPatchShapes.contains( name ) )
1538  return false;
1539 
1540  mLegendPatchShapes.remove( name );
1541  break;
1542  }
1543  }
1544 
1545  if ( !mCurrentDB )
1546  {
1547  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to modify." ) );
1548  return false;
1549  }
1550 
1551  const int id = entityId( type, name );
1552  if ( !id )
1553  {
1554  QgsDebugMsg( "No matching entity for deleting in database: " + name );
1555  }
1556 
1557  const bool result = remove( type, id );
1558  if ( result )
1559  {
1560  mCachedTags[ type ].remove( name );
1561  mCachedFavorites[ type ].remove( name );
1562 
1563  switch ( type )
1564  {
1565  case SymbolEntity:
1566  emit symbolRemoved( name );
1567  break;
1568 
1569  case ColorrampEntity:
1570  emit rampRemoved( name );
1571  break;
1572 
1573  case TextFormatEntity:
1574  emit textFormatRemoved( name );
1575  break;
1576 
1577  case LabelSettingsEntity:
1578  emit labelSettingsRemoved( name );
1579  break;
1580 
1581  default:
1582  // these specific signals should be discouraged -- don't add them for new entity types!
1583  break;
1584  }
1585  emit entityRemoved( type, name );
1586  }
1587  return result;
1588 }
1589 
1590 bool QgsStyle::runEmptyQuery( const QString &query )
1591 {
1592  if ( !mCurrentDB )
1593  return false;
1594 
1595  char *zErr = nullptr;
1596  int nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
1597 
1598  if ( nErr != SQLITE_OK )
1599  {
1600  QgsDebugMsg( zErr );
1601  sqlite3_free( zErr );
1602  }
1603 
1604  return nErr == SQLITE_OK;
1605 }
1606 
1607 bool QgsStyle::addFavorite( StyleEntity type, const QString &name )
1608 {
1609  QString query;
1610 
1611  switch ( type )
1612  {
1613  case TagEntity:
1614  case SmartgroupEntity:
1615  QgsDebugMsg( QStringLiteral( "Wrong entity value. cannot apply group" ) );
1616  return false;
1617 
1618  default:
1619  query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET favorite=1 WHERE name='%q'" ).arg( entityTableName( type ) ).toLocal8Bit().data(),
1620  name.toUtf8().constData() );
1621  break;
1622  }
1623 
1624  const bool res = runEmptyQuery( query );
1625  if ( res )
1626  {
1627  switch ( type )
1628  {
1629  case TagEntity:
1630  case SmartgroupEntity:
1631  break;
1632 
1633  default:
1634  mCachedFavorites[ type ].insert( name, true );
1635  break;
1636  }
1637  emit favoritedChanged( type, name, true );
1638  }
1639 
1640  return res;
1641 }
1642 
1643 bool QgsStyle::removeFavorite( StyleEntity type, const QString &name )
1644 {
1645  QString query;
1646 
1647  switch ( type )
1648  {
1649  case TagEntity:
1650  case SmartgroupEntity:
1651  QgsDebugMsg( QStringLiteral( "Wrong entity value. cannot apply group" ) );
1652  return false;
1653 
1654  default:
1655  query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET favorite=0 WHERE name='%q'" ).arg( entityTableName( type ) ).toLocal8Bit().data(), name.toUtf8().constData() );
1656  break;
1657  }
1658 
1659  const bool res = runEmptyQuery( query );
1660  if ( res )
1661  {
1662  mCachedFavorites[ type ].insert( name, false );
1663  emit favoritedChanged( type, name, false );
1664  }
1665 
1666  return res;
1667 }
1668 
1669 QStringList QgsStyle::findSymbols( StyleEntity type, const QString &qword )
1670 {
1671  if ( !mCurrentDB )
1672  {
1673  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to search" ) );
1674  return QStringList();
1675  }
1676 
1677  // first find symbols with matching name
1678  QString item;
1679  switch ( type )
1680  {
1681  case TagEntity:
1682  case SmartgroupEntity:
1683  return QStringList();
1684 
1685  default:
1686  item = entityTableName( type );
1687  break;
1688  }
1689 
1690  QString query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE name LIKE '%%%q%%'",
1691  item.toUtf8().constData(), qword.toUtf8().constData() );
1692 
1693  sqlite3_statement_unique_ptr statement;
1694  int nErr; statement = mCurrentDB.prepare( query, nErr );
1695 
1696  QSet< QString > symbols;
1697  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1698  {
1699  symbols << statement.columnAsText( 0 );
1700  }
1701 
1702  // next add symbols with matching tags
1703  query = qgs_sqlite3_mprintf( "SELECT id FROM tag WHERE name LIKE '%%%q%%'", qword.toUtf8().constData() );
1704  statement = mCurrentDB.prepare( query, nErr );
1705 
1706  QStringList tagids;
1707  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1708  {
1709  tagids << statement.columnAsText( 0 );
1710  }
1711 
1712  QString dummy = tagids.join( QLatin1String( ", " ) );
1713  query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT %1 FROM %2 WHERE tag_id IN (%q)" ).arg( tagmapEntityIdFieldName( type ),
1714  tagmapTableName( type ) ).toLocal8Bit().data(), dummy.toUtf8().constData() );
1715 
1716  statement = mCurrentDB.prepare( query, nErr );
1717 
1718  QStringList symbolids;
1719  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1720  {
1721  symbolids << statement.columnAsText( 0 );
1722  }
1723 
1724  dummy = symbolids.join( QLatin1String( ", " ) );
1725  query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE id IN (%q)",
1726  item.toUtf8().constData(), dummy.toUtf8().constData() );
1727  statement = mCurrentDB.prepare( query, nErr );
1728  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1729  {
1730  symbols << statement.columnAsText( 0 );
1731  }
1732 
1733  return qgis::setToList( symbols );
1734 }
1735 
1736 bool QgsStyle::tagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
1737 {
1738  if ( !mCurrentDB )
1739  {
1740  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to tag." ) );
1741  return false;
1742  }
1743 
1744  int symbolid = 0;
1745  switch ( type )
1746  {
1747  case TagEntity:
1748  case SmartgroupEntity:
1749  return false;
1750 
1751  default:
1752  symbolid = entityId( type, symbol );
1753  break;
1754  }
1755 
1756  if ( !symbolid )
1757  {
1758  QgsDebugMsg( QStringLiteral( "No such symbol for tagging in database: " ) + symbol );
1759  return false;
1760  }
1761 
1762  QString tag;
1763  const auto constTags = tags;
1764  for ( const QString &t : constTags )
1765  {
1766  tag = t.trimmed();
1767  if ( !tag.isEmpty() )
1768  {
1769  // sql: gets the id of the tag if present or insert the tag and get the id of the tag
1770  int tagid( tagId( tag ) );
1771  if ( ! tagid )
1772  {
1773  tagid = addTag( tag );
1774  }
1775 
1776  // Now map the tag to the symbol if it's not already tagged
1777  if ( !symbolHasTag( type, symbol, tag ) )
1778  {
1779  QString query = qgs_sqlite3_mprintf( QStringLiteral( "INSERT INTO %1 VALUES (%d,%d)" ).arg( tagmapTableName( type ) ).toLocal8Bit().data(), tagid, symbolid );
1780 
1781  char *zErr = nullptr;
1782  int nErr;
1783  nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
1784  if ( nErr )
1785  {
1786  QgsDebugMsg( zErr );
1787  sqlite3_free( zErr );
1788  }
1789  }
1790  }
1791  }
1792 
1793  clearCachedTags( type, symbol );
1794  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
1795 
1796  return true;
1797 }
1798 
1799 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
1800 {
1801  if ( !mCurrentDB )
1802  {
1803  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1804  return false;
1805  }
1806 
1807  switch ( type )
1808  {
1809  case TagEntity:
1810  case SmartgroupEntity:
1811  return false;
1812 
1813  default:
1814  break;
1815  }
1816 
1817  const int symbolid = entityId( type, symbol );
1818  if ( symbolid == 0 )
1819  return false;
1820 
1821  int nErr;
1822  QString query;
1823  const auto constTags = tags;
1824  for ( const QString &tag : constTags )
1825  {
1826  query = qgs_sqlite3_mprintf( "SELECT id FROM tag WHERE name='%q'", tag.toUtf8().constData() );
1827 
1828  sqlite3_statement_unique_ptr statement2;
1829  statement2 = mCurrentDB.prepare( query, nErr );
1830 
1831  int tagid = 0;
1832  if ( nErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1833  {
1834  tagid = sqlite3_column_int( statement2.get(), 0 );
1835  }
1836 
1837  if ( tagid )
1838  {
1839  // remove from the tagmap
1840  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE tag_id=%d AND %2=%d" ).arg( tagmapTableName( type ), tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), tagid, symbolid );
1841  runEmptyQuery( query );
1842  }
1843  }
1844 
1845  clearCachedTags( type, symbol );
1846  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
1847 
1848  // TODO Perform tag cleanup
1849  // check the number of entries for a given tag in the tagmap
1850  // if the count is 0, then remove( TagEntity, tagid )
1851  return true;
1852 }
1853 
1854 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol )
1855 {
1856  if ( !mCurrentDB )
1857  {
1858  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1859  return false;
1860  }
1861 
1862  switch ( type )
1863  {
1864  case TagEntity:
1865  case SmartgroupEntity:
1866  return false;
1867 
1868  default:
1869  break;
1870  }
1871 
1872  const int symbolid = entityId( type, symbol );
1873  if ( symbolid == 0 )
1874  {
1875  return false;
1876  }
1877 
1878  // remove all tags
1879  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE %2=%d" ).arg( tagmapTableName( type ),
1880  tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), symbolid );
1881  runEmptyQuery( query );
1882 
1883  clearCachedTags( type, symbol );
1884  emit entityTagsChanged( type, symbol, QStringList() );
1885 
1886  // TODO Perform tag cleanup
1887  // check the number of entries for a given tag in the tagmap
1888  // if the count is 0, then remove( TagEntity, tagid )
1889  return true;
1890 }
1891 
1892 QStringList QgsStyle::tagsOfSymbol( StyleEntity type, const QString &symbol )
1893 {
1894  switch ( type )
1895  {
1896  case TagEntity:
1897  case SmartgroupEntity:
1898  return QStringList();
1899 
1900  default:
1901  if ( mCachedTags[ type ].contains( symbol ) )
1902  return mCachedTags[ type ].value( symbol );
1903  break;
1904  }
1905 
1906  if ( !mCurrentDB )
1907  {
1908  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1909  return QStringList();
1910  }
1911 
1912  int symbolid = entityId( type, symbol );
1913  if ( !symbolid )
1914  return QStringList();
1915 
1916  // get the ids of tags for the symbol
1917  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT tag_id FROM %1 WHERE %2=%d" ).arg( tagmapTableName( type ),
1918  tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), symbolid );
1919 
1920  sqlite3_statement_unique_ptr statement;
1921  int nErr; statement = mCurrentDB.prepare( query, nErr );
1922 
1923  QStringList tagList;
1924  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1925  {
1926  QString subquery = qgs_sqlite3_mprintf( "SELECT name FROM tag WHERE id=%d", sqlite3_column_int( statement.get(), 0 ) );
1927 
1928  sqlite3_statement_unique_ptr statement2;
1929  int pErr;
1930  statement2 = mCurrentDB.prepare( subquery, pErr );
1931  if ( pErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1932  {
1933  tagList << statement2.columnAsText( 0 );
1934  }
1935  }
1936 
1937  // update cache
1938  mCachedTags[ type ].insert( symbol, tagList );
1939 
1940  return tagList;
1941 }
1942 
1943 bool QgsStyle::isFavorite( QgsStyle::StyleEntity type, const QString &name )
1944 {
1945  if ( !mCurrentDB )
1946  {
1947  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1948  return false;
1949  }
1950 
1951  switch ( type )
1952  {
1953  case TagEntity:
1954  case SmartgroupEntity:
1955  return false;
1956 
1957  default:
1958  if ( mCachedFavorites[ type ].contains( name ) )
1959  return mCachedFavorites[ type ].value( name );
1960  break;
1961  }
1962 
1963  const QStringList names = allNames( type );
1964  if ( !names.contains( name ) )
1965  return false; // entity doesn't exist
1966 
1967  // for efficiency, retrieve names of all favorited symbols and store them in cache
1968  const QStringList favorites = symbolsOfFavorite( type );
1969  bool res = false;
1970  for ( const QString &n : names )
1971  {
1972  const bool isFav = favorites.contains( n );
1973  if ( n == name )
1974  res = isFav;
1975 
1976  mCachedFavorites[ type ].insert( n, isFav );
1977  }
1978  return res;
1979 }
1980 
1981 bool QgsStyle::symbolHasTag( StyleEntity type, const QString &symbol, const QString &tag )
1982 {
1983  if ( !mCurrentDB )
1984  {
1985  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1986  return false;
1987  }
1988 
1989  int symbolid = 0;
1990  switch ( type )
1991  {
1992  case TagEntity:
1993  case SmartgroupEntity:
1994  return false;
1995 
1996  default:
1997  symbolid = entityId( type, symbol );
1998  break;
1999  }
2000 
2001  if ( !symbolid )
2002  {
2003  return false;
2004  }
2005  int tagid = tagId( tag );
2006  if ( !tagid )
2007  {
2008  return false;
2009  }
2010 
2011  // get the ids of tags for the symbol
2012  const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT tag_id FROM %1 WHERE tag_id=%d AND %2=%d" ).arg( tagmapTableName( type ),
2013  tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), tagid, symbolid );
2014 
2015  sqlite3_statement_unique_ptr statement;
2016  int nErr; statement = mCurrentDB.prepare( query, nErr );
2017 
2018  return ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW );
2019 }
2020 
2021 QString QgsStyle::tag( int id ) const
2022 {
2023  if ( !mCurrentDB )
2024  return QString();
2025 
2026  sqlite3_statement_unique_ptr statement;
2027 
2028  QString query = qgs_sqlite3_mprintf( "SELECT name FROM tag WHERE id=%d", id );
2029  int nError;
2030  statement = mCurrentDB.prepare( query, nError );
2031 
2032  QString tag;
2033  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2034  {
2035  tag = statement.columnAsText( 0 );
2036  }
2037 
2038  return tag;
2039 }
2040 
2041 int QgsStyle::getId( const QString &table, const QString &name )
2042 {
2043  QString lowerName( name.toLower() );
2044  QString query = qgs_sqlite3_mprintf( "SELECT id FROM %q WHERE LOWER(name)='%q'", table.toUtf8().constData(), lowerName.toUtf8().constData() );
2045 
2046  sqlite3_statement_unique_ptr statement;
2047  int nErr; statement = mCurrentDB.prepare( query, nErr );
2048 
2049  int id = 0;
2050  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2051  {
2052  id = sqlite3_column_int( statement.get(), 0 );
2053  }
2054  else
2055  {
2056  // Try the name without lowercase conversion
2057  QString query = qgs_sqlite3_mprintf( "SELECT id FROM %q WHERE name='%q'", table.toUtf8().constData(), name.toUtf8().constData() );
2058 
2059  sqlite3_statement_unique_ptr statement;
2060  int nErr; statement = mCurrentDB.prepare( query, nErr );
2061  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2062  {
2063  id = sqlite3_column_int( statement.get(), 0 );
2064  }
2065  }
2066 
2067  return id;
2068 }
2069 
2070 QString QgsStyle::getName( const QString &table, int id ) const
2071 {
2072  QString query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE id='%q'", table.toUtf8().constData(), QString::number( id ).toUtf8().constData() );
2073 
2074  sqlite3_statement_unique_ptr statement;
2075  int nErr; statement = mCurrentDB.prepare( query, nErr );
2076 
2077  QString name;
2078  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2079  {
2080  name = statement.columnAsText( 0 );
2081  }
2082 
2083  return name;
2084 }
2085 
2086 int QgsStyle::symbolId( const QString &name )
2087 {
2088  return getId( QStringLiteral( "symbol" ), name );
2089 }
2090 
2091 int QgsStyle::entityId( QgsStyle::StyleEntity type, const QString &name )
2092 {
2093  return getId( entityTableName( type ), name );
2094 }
2095 
2096 int QgsStyle::colorrampId( const QString &name )
2097 {
2098  return getId( QStringLiteral( "colorramp" ), name );
2099 }
2100 
2101 QgsTextFormat QgsStyle::textFormat( const QString &name ) const
2102 {
2103  return mTextFormats.value( name );
2104 }
2105 
2107 {
2108  return mTextFormats.count();
2109 }
2110 
2111 QStringList QgsStyle::textFormatNames() const
2112 {
2113  return mTextFormats.keys();
2114 }
2115 
2116 int QgsStyle::textFormatId( const QString &name )
2117 {
2118  return getId( QStringLiteral( "textformat" ), name );
2119 }
2120 
2121 QgsPalLayerSettings QgsStyle::labelSettings( const QString &name ) const
2122 {
2123  return mLabelSettings.value( name );
2124 }
2125 
2127 {
2128  return mLegendPatchShapes.value( name );
2129 }
2130 
2132 {
2133  return mLegendPatchShapes.count();
2134 }
2135 
2137 {
2138  if ( !mLegendPatchShapes.contains( name ) )
2139  return QgsSymbol::Hybrid;
2140 
2141  return mLegendPatchShapes.value( name ).symbolType();
2142 }
2143 
2144 QgsAbstract3DSymbol *QgsStyle::symbol3D( const QString &name ) const
2145 {
2146  return m3dSymbols.contains( name ) ? m3dSymbols.value( name )->clone() : nullptr;
2147 }
2148 
2150 {
2151  return m3dSymbols.count();
2152 }
2153 
2154 QList<QgsWkbTypes::GeometryType> QgsStyle::symbol3DCompatibleGeometryTypes( const QString &name ) const
2155 {
2156  if ( !m3dSymbols.contains( name ) )
2157  return QList<QgsWkbTypes::GeometryType>();
2158 
2159  return m3dSymbols.value( name )->compatibleGeometryTypes();
2160 }
2161 
2163 {
2164  if ( !mLabelSettings.contains( name ) )
2166 
2167  return mLabelSettings.value( name ).layerType;
2168 }
2169 
2171 {
2172  return mLabelSettings.count();
2173 }
2174 
2175 QStringList QgsStyle::labelSettingsNames() const
2176 {
2177  return mLabelSettings.keys();
2178 }
2179 
2180 int QgsStyle::labelSettingsId( const QString &name )
2181 {
2182  return getId( QStringLiteral( "labelsettings" ), name );
2183 }
2184 
2186 {
2187  return mLegendPatchShapes.keys();
2188 }
2189 
2191 {
2192  switch ( shape.symbolType() )
2193  {
2194  case QgsSymbol::Marker:
2195  return mPatchMarkerSymbol.get();
2196 
2197  case QgsSymbol::Line:
2198  return mPatchLineSymbol.get();
2199 
2200  case QgsSymbol::Fill:
2201  return mPatchFillSymbol.get();
2202 
2203  case QgsSymbol::Hybrid:
2204  break;
2205  }
2206  return nullptr;
2207 }
2208 
2209 int QgsStyle::tagId( const QString &name )
2210 {
2211  return getId( QStringLiteral( "tag" ), name );
2212 }
2213 
2214 int QgsStyle::smartgroupId( const QString &name )
2215 {
2216  return getId( QStringLiteral( "smartgroup" ), name );
2217 }
2218 
2220 {
2221  switch ( type )
2222  {
2223  case SymbolEntity:
2224  return symbolNames();
2225 
2226  case ColorrampEntity:
2227  return colorRampNames();
2228 
2229  case TextFormatEntity:
2230  return textFormatNames();
2231 
2232  case LabelSettingsEntity:
2233  return labelSettingsNames();
2234 
2236  return legendPatchShapeNames();
2237 
2238  case Symbol3DEntity:
2239  return symbol3DNames();
2240 
2241  case TagEntity:
2242  return tags();
2243 
2244  case SmartgroupEntity:
2245  return smartgroupNames();
2246  }
2247  return QStringList();
2248 }
2249 
2250 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions )
2251 {
2252  return addSmartgroup( name, op, conditions.values( QStringLiteral( "tag" ) ),
2253  conditions.values( QStringLiteral( "!tag" ) ),
2254  conditions.values( QStringLiteral( "name" ) ),
2255  conditions.values( QStringLiteral( "!name" ) ) );
2256 }
2257 
2258 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag, const QStringList &matchName, const QStringList &noMatchName )
2259 {
2260  QDomDocument doc( QStringLiteral( "dummy" ) );
2261  QDomElement smartEl = doc.createElement( QStringLiteral( "smartgroup" ) );
2262  smartEl.setAttribute( QStringLiteral( "name" ), name );
2263  smartEl.setAttribute( QStringLiteral( "operator" ), op );
2264 
2265  auto addCondition = [&doc, &smartEl]( const QString & constraint, const QStringList & parameters )
2266  {
2267  for ( const QString &param : parameters )
2268  {
2269  QDomElement condEl = doc.createElement( QStringLiteral( "condition" ) );
2270  condEl.setAttribute( QStringLiteral( "constraint" ), constraint );
2271  condEl.setAttribute( QStringLiteral( "param" ), param );
2272  smartEl.appendChild( condEl );
2273  }
2274  };
2275  addCondition( QStringLiteral( "tag" ), matchTag );
2276  addCondition( QStringLiteral( "!tag" ), noMatchTag );
2277  addCondition( QStringLiteral( "name" ), matchName );
2278  addCondition( QStringLiteral( "!name" ), noMatchName );
2279 
2280  QByteArray xmlArray;
2281  QTextStream stream( &xmlArray );
2282  stream.setCodec( "UTF-8" );
2283  smartEl.save( stream, 4 );
2284  QString query = qgs_sqlite3_mprintf( "INSERT INTO smartgroup VALUES (NULL, '%q', '%q')",
2285  name.toUtf8().constData(), xmlArray.constData() );
2286 
2287  if ( runEmptyQuery( query ) )
2288  {
2289  QgsSettings settings;
2290  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
2291 
2292  emit groupsModified();
2293  return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
2294  }
2295  else
2296  {
2297  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
2298  return 0;
2299  }
2300 }
2301 
2303 {
2304  if ( !mCurrentDB )
2305  {
2306  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
2307  return QgsSymbolGroupMap();
2308  }
2309 
2310  QString query = qgs_sqlite3_mprintf( "SELECT * FROM smartgroup" );
2311 
2312  // Now run the query and retrieve the group names
2313  sqlite3_statement_unique_ptr statement;
2314  int nError;
2315  statement = mCurrentDB.prepare( query, nError );
2316 
2317  QgsSymbolGroupMap groupNames;
2318  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2319  {
2320  QString group = statement.columnAsText( SmartgroupName );
2321  groupNames.insert( sqlite3_column_int( statement.get(), SmartgroupId ), group );
2322  }
2323 
2324  return groupNames;
2325 }
2326 
2327 QStringList QgsStyle::smartgroupNames() const
2328 {
2329  if ( !mCurrentDB )
2330  {
2331  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
2332  return QStringList();
2333  }
2334 
2335  QString query = qgs_sqlite3_mprintf( "SELECT name FROM smartgroup" );
2336 
2337  // Now run the query and retrieve the group names
2338  sqlite3_statement_unique_ptr statement;
2339  int nError;
2340  statement = mCurrentDB.prepare( query, nError );
2341 
2342  QStringList groups;
2343  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2344  {
2345  groups << statement.columnAsText( 0 );
2346  }
2347 
2348  return groups;
2349 }
2350 
2351 QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
2352 {
2353  QStringList symbols;
2354 
2355  QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2356 
2357  sqlite3_statement_unique_ptr statement;
2358  int nErr; statement = mCurrentDB.prepare( query, nErr );
2359  if ( !( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW ) )
2360  {
2361  return QStringList();
2362  }
2363  else
2364  {
2365  QDomDocument doc;
2366  QString xmlstr = statement.columnAsText( 0 );
2367  if ( !doc.setContent( xmlstr ) )
2368  {
2369  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2370  }
2371  QDomElement smartEl = doc.documentElement();
2372  QString op = smartEl.attribute( QStringLiteral( "operator" ) );
2373  QDomNodeList conditionNodes = smartEl.childNodes();
2374 
2375  bool firstSet = true;
2376  for ( int i = 0; i < conditionNodes.count(); i++ )
2377  {
2378  QDomElement condEl = conditionNodes.at( i ).toElement();
2379  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
2380  QString param = condEl.attribute( QStringLiteral( "param" ) );
2381 
2382  QStringList resultNames;
2383  // perform suitable action for the given constraint
2384  if ( constraint == QLatin1String( "tag" ) )
2385  {
2386  resultNames = symbolsWithTag( type, tagId( param ) );
2387  }
2388  else if ( constraint == QLatin1String( "name" ) )
2389  {
2390  resultNames = allNames( type ).filter( param, Qt::CaseInsensitive );
2391  }
2392  else if ( constraint == QLatin1String( "!tag" ) )
2393  {
2394  resultNames = allNames( type );
2395  const QStringList unwanted = symbolsWithTag( type, tagId( param ) );
2396  for ( const QString &name : unwanted )
2397  {
2398  resultNames.removeAll( name );
2399  }
2400  }
2401  else if ( constraint == QLatin1String( "!name" ) )
2402  {
2403  const QStringList all = allNames( type );
2404  for ( const QString &str : all )
2405  {
2406  if ( !str.contains( param, Qt::CaseInsensitive ) )
2407  resultNames << str;
2408  }
2409  }
2410 
2411  // not apply the operator
2412  if ( firstSet )
2413  {
2414  symbols = resultNames;
2415  firstSet = false;
2416  }
2417  else
2418  {
2419  if ( op == QLatin1String( "OR" ) )
2420  {
2421  symbols << resultNames;
2422  }
2423  else if ( op == QLatin1String( "AND" ) )
2424  {
2425  QStringList dummy = symbols;
2426  symbols.clear();
2427  for ( const QString &result : qgis::as_const( resultNames ) )
2428  {
2429  if ( dummy.contains( result ) )
2430  symbols << result;
2431  }
2432  }
2433  }
2434  } // DOM loop ends here
2435  }
2436 
2437  // return sorted, unique list
2438  QStringList unique = qgis::setToList( qgis::listToSet( symbols ) );
2439  std::sort( unique.begin(), unique.end() );
2440  return unique;
2441 }
2442 
2444 {
2445  if ( !mCurrentDB )
2446  {
2447  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
2448  return QgsSmartConditionMap();
2449  }
2450 
2451  QgsSmartConditionMap condition;
2452 
2453  QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2454 
2455  sqlite3_statement_unique_ptr statement;
2456  int nError;
2457  statement = mCurrentDB.prepare( query, nError );
2458  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2459  {
2460  QDomDocument doc;
2461  QString xmlstr = statement.columnAsText( 0 );
2462  if ( !doc.setContent( xmlstr ) )
2463  {
2464  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2465  }
2466 
2467  QDomElement smartEl = doc.documentElement();
2468  QDomNodeList conditionNodes = smartEl.childNodes();
2469 
2470  for ( int i = 0; i < conditionNodes.count(); i++ )
2471  {
2472  QDomElement condEl = conditionNodes.at( i ).toElement();
2473  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
2474  QString param = condEl.attribute( QStringLiteral( "param" ) );
2475 
2476  condition.insert( constraint, param );
2477  }
2478  }
2479 
2480  return condition;
2481 }
2482 
2484 {
2485  if ( !mCurrentDB )
2486  {
2487  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
2488  return QString();
2489  }
2490 
2491  QString op;
2492 
2493  QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2494 
2495  int nError;
2496  sqlite3_statement_unique_ptr statement;
2497  statement = mCurrentDB.prepare( query, nError );
2498  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2499  {
2500  QDomDocument doc;
2501  QString xmlstr = statement.columnAsText( 0 );
2502  if ( !doc.setContent( xmlstr ) )
2503  {
2504  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2505  }
2506  QDomElement smartEl = doc.documentElement();
2507  op = smartEl.attribute( QStringLiteral( "operator" ) );
2508  }
2509 
2510  return op;
2511 }
2512 
2513 bool QgsStyle::exportXml( const QString &filename )
2514 {
2515  if ( filename.isEmpty() )
2516  {
2517  QgsDebugMsg( QStringLiteral( "Invalid filename for style export." ) );
2518  return false;
2519  }
2520 
2521  QDomDocument doc( QStringLiteral( "qgis_style" ) );
2522  QDomElement root = doc.createElement( QStringLiteral( "qgis_style" ) );
2523  root.setAttribute( QStringLiteral( "version" ), QStringLiteral( STYLE_CURRENT_VERSION ) );
2524  doc.appendChild( root );
2525 
2526  const QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
2527  const QStringList favoriteColorramps = symbolsOfFavorite( ColorrampEntity );
2528  const QStringList favoriteTextFormats = symbolsOfFavorite( TextFormatEntity );
2529  const QStringList favoriteLegendShapes = symbolsOfFavorite( LegendPatchShapeEntity );
2530  const QStringList favorite3DSymbols = symbolsOfFavorite( Symbol3DEntity );
2531 
2532  // save symbols and attach tags
2533  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, QStringLiteral( "symbols" ), doc, QgsReadWriteContext() );
2534  QDomNodeList symbolsList = symbolsElem.elementsByTagName( QStringLiteral( "symbol" ) );
2535  int nbSymbols = symbolsList.count();
2536  for ( int i = 0; i < nbSymbols; ++i )
2537  {
2538  QDomElement symbol = symbolsList.at( i ).toElement();
2539  QString name = symbol.attribute( QStringLiteral( "name" ) );
2540  QStringList tags = tagsOfSymbol( SymbolEntity, name );
2541  if ( tags.count() > 0 )
2542  {
2543  symbol.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2544  }
2545  if ( favoriteSymbols.contains( name ) )
2546  {
2547  symbol.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2548  }
2549  }
2550 
2551  // save color ramps
2552  QDomElement rampsElem = doc.createElement( QStringLiteral( "colorramps" ) );
2553  for ( QMap<QString, QgsColorRamp *>::const_iterator itr = mColorRamps.constBegin(); itr != mColorRamps.constEnd(); ++itr )
2554  {
2555  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
2556  QStringList tags = tagsOfSymbol( ColorrampEntity, itr.key() );
2557  if ( tags.count() > 0 )
2558  {
2559  rampEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2560  }
2561  if ( favoriteColorramps.contains( itr.key() ) )
2562  {
2563  rampEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2564  }
2565  rampsElem.appendChild( rampEl );
2566  }
2567 
2568  // save text formats
2569  QDomElement textFormatsElem = doc.createElement( QStringLiteral( "textformats" ) );
2570  for ( auto it = mTextFormats.constBegin(); it != mTextFormats.constEnd(); ++it )
2571  {
2572  QDomElement textFormatEl = doc.createElement( QStringLiteral( "textformat" ) );
2573  textFormatEl.setAttribute( QStringLiteral( "name" ), it.key() );
2574  QDomElement textStyleEl = it.value().writeXml( doc, QgsReadWriteContext() );
2575  textFormatEl.appendChild( textStyleEl );
2576  QStringList tags = tagsOfSymbol( TextFormatEntity, it.key() );
2577  if ( tags.count() > 0 )
2578  {
2579  textFormatEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2580  }
2581  if ( favoriteTextFormats.contains( it.key() ) )
2582  {
2583  textFormatEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2584  }
2585  textFormatsElem.appendChild( textFormatEl );
2586  }
2587 
2588  // save label settings
2589  QDomElement labelSettingsElem = doc.createElement( QStringLiteral( "labelsettings" ) );
2590  for ( auto it = mLabelSettings.constBegin(); it != mLabelSettings.constEnd(); ++it )
2591  {
2592  QDomElement labelSettingsEl = doc.createElement( QStringLiteral( "labelsetting" ) );
2593  labelSettingsEl.setAttribute( QStringLiteral( "name" ), it.key() );
2594  QDomElement defEl = it.value().writeXml( doc, QgsReadWriteContext() );
2595  labelSettingsEl.appendChild( defEl );
2596  QStringList tags = tagsOfSymbol( LabelSettingsEntity, it.key() );
2597  if ( tags.count() > 0 )
2598  {
2599  labelSettingsEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2600  }
2601  if ( favoriteTextFormats.contains( it.key() ) )
2602  {
2603  labelSettingsEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2604  }
2605  labelSettingsElem.appendChild( labelSettingsEl );
2606  }
2607 
2608  // save legend patch shapes
2609  QDomElement legendPatchShapesElem = doc.createElement( QStringLiteral( "legendpatchshapes" ) );
2610  for ( auto it = mLegendPatchShapes.constBegin(); it != mLegendPatchShapes.constEnd(); ++it )
2611  {
2612  QDomElement legendPatchShapeEl = doc.createElement( QStringLiteral( "legendpatchshape" ) );
2613  legendPatchShapeEl.setAttribute( QStringLiteral( "name" ), it.key() );
2614  QDomElement defEl = doc.createElement( QStringLiteral( "definition" ) );
2615  it.value().writeXml( defEl, doc, QgsReadWriteContext() );
2616  legendPatchShapeEl.appendChild( defEl );
2617  QStringList tags = tagsOfSymbol( LegendPatchShapeEntity, it.key() );
2618  if ( tags.count() > 0 )
2619  {
2620  legendPatchShapeEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2621  }
2622  if ( favoriteLegendShapes.contains( it.key() ) )
2623  {
2624  legendPatchShapeEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2625  }
2626  legendPatchShapesElem.appendChild( legendPatchShapeEl );
2627  }
2628 
2629  // save symbols and attach tags
2630  QDomElement symbols3DElem = doc.createElement( QStringLiteral( "symbols3d" ) );
2631  for ( auto it = m3dSymbols.constBegin(); it != m3dSymbols.constEnd(); ++it )
2632  {
2633  QDomElement symbolEl = doc.createElement( QStringLiteral( "symbol3d" ) );
2634  symbolEl.setAttribute( QStringLiteral( "name" ), it.key() );
2635  QDomElement defEl = doc.createElement( QStringLiteral( "definition" ) );
2636  defEl.setAttribute( QStringLiteral( "type" ), it.value()->type() );
2637  it.value()->writeXml( defEl, QgsReadWriteContext() );
2638  symbolEl.appendChild( defEl );
2639  QStringList tags = tagsOfSymbol( Symbol3DEntity, it.key() );
2640  if ( tags.count() > 0 )
2641  {
2642  symbolEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2643  }
2644  if ( favorite3DSymbols.contains( it.key() ) )
2645  {
2646  symbolEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2647  }
2648  symbols3DElem.appendChild( symbolEl );
2649  }
2650 
2651  root.appendChild( symbolsElem );
2652  root.appendChild( rampsElem );
2653  root.appendChild( textFormatsElem );
2654  root.appendChild( labelSettingsElem );
2655  root.appendChild( legendPatchShapesElem );
2656  root.appendChild( symbols3DElem );
2657 
2658  // save
2659  QFile f( filename );
2660  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
2661  {
2662  mErrorString = "Couldn't open file for writing: " + filename;
2663  return false;
2664  }
2665 
2666  QTextStream ts( &f );
2667  ts.setCodec( "UTF-8" );
2668  doc.save( ts, 2 );
2669  f.close();
2670 
2671  mFileName = filename;
2672  return true;
2673 }
2674 
2675 bool QgsStyle::importXml( const QString &filename )
2676 {
2677  return importXml( filename, -1 );
2678 }
2679 
2680 bool QgsStyle::importXml( const QString &filename, int sinceVersion )
2681 {
2682  mErrorString = QString();
2683  QDomDocument doc( QStringLiteral( "style" ) );
2684  QFile f( filename );
2685  if ( !f.open( QFile::ReadOnly ) )
2686  {
2687  mErrorString = QStringLiteral( "Unable to open the specified file" );
2688  QgsDebugMsg( QStringLiteral( "Error opening the style XML file." ) );
2689  return false;
2690  }
2691 
2692  if ( !doc.setContent( &f ) )
2693  {
2694  mErrorString = QStringLiteral( "Unable to understand the style file: %1" ).arg( filename );
2695  QgsDebugMsg( QStringLiteral( "XML Parsing error" ) );
2696  f.close();
2697  return false;
2698  }
2699  f.close();
2700 
2701  QDomElement docEl = doc.documentElement();
2702  if ( docEl.tagName() != QLatin1String( "qgis_style" ) )
2703  {
2704  mErrorString = "Incorrect root tag in style: " + docEl.tagName();
2705  return false;
2706  }
2707 
2708  const QString version = docEl.attribute( QStringLiteral( "version" ) );
2709  if ( version != QLatin1String( STYLE_CURRENT_VERSION ) && version != QLatin1String( "0" ) && version != QLatin1String( "1" ) )
2710  {
2711  mErrorString = "Unknown style file version: " + version;
2712  return false;
2713  }
2714 
2715  QgsSymbolMap symbols;
2716 
2717  QDomElement symbolsElement = docEl.firstChildElement( QStringLiteral( "symbols" ) );
2718  QDomElement e = symbolsElement.firstChildElement();
2719 
2720  // gain speed by re-grouping the INSERT statements in a transaction
2721  QString query = qgs_sqlite3_mprintf( "BEGIN TRANSACTION;" );
2722  runEmptyQuery( query );
2723 
2724  if ( version == QLatin1String( STYLE_CURRENT_VERSION ) || version == QLatin1String( "1" ) )
2725  {
2726  // For the new style, load symbols individually
2727  while ( !e.isNull() )
2728  {
2729  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2730  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2731  {
2732  // skip the symbol, should already be present
2733  continue;
2734  }
2735 
2736  if ( e.tagName() == QLatin1String( "symbol" ) )
2737  {
2738  QString name = e.attribute( QStringLiteral( "name" ) );
2739  QStringList tags;
2740  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2741  {
2742  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2743  }
2744  bool favorite = false;
2745  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2746  {
2747  favorite = true;
2748  }
2749 
2751  if ( symbol )
2752  {
2753  addSymbol( name, symbol );
2754  if ( mCurrentDB )
2755  {
2756  saveSymbol( name, symbol, favorite, tags );
2757  }
2758  }
2759  }
2760  else
2761  {
2762  QgsDebugMsg( "unknown tag: " + e.tagName() );
2763  }
2764  e = e.nextSiblingElement();
2765  }
2766  }
2767  else
2768  {
2769  // for the old version, use the utility function to solve @[email protected] subsymbols
2770  symbols = QgsSymbolLayerUtils::loadSymbols( symbolsElement, QgsReadWriteContext() );
2771 
2772  // save the symbols with proper name
2773  for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
2774  {
2775  addSymbol( it.key(), it.value() );
2776  }
2777  }
2778 
2779  // load color ramps
2780  QDomElement rampsElement = docEl.firstChildElement( QStringLiteral( "colorramps" ) );
2781  e = rampsElement.firstChildElement();
2782  while ( !e.isNull() )
2783  {
2784  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2785  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2786  {
2787  // skip the ramp, should already be present
2788  continue;
2789  }
2790 
2791  if ( e.tagName() == QLatin1String( "colorramp" ) )
2792  {
2793  QString name = e.attribute( QStringLiteral( "name" ) );
2794  QStringList tags;
2795  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2796  {
2797  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2798  }
2799  bool favorite = false;
2800  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2801  {
2802  favorite = true;
2803  }
2804 
2806  if ( ramp )
2807  {
2808  addColorRamp( name, ramp );
2809  if ( mCurrentDB )
2810  {
2811  saveColorRamp( name, ramp, favorite, tags );
2812  }
2813  }
2814  }
2815  else
2816  {
2817  QgsDebugMsg( "unknown tag: " + e.tagName() );
2818  }
2819  e = e.nextSiblingElement();
2820  }
2821 
2822  // load text formats
2823  if ( version == STYLE_CURRENT_VERSION )
2824  {
2825  const QDomElement textFormatElement = docEl.firstChildElement( QStringLiteral( "textformats" ) );
2826  e = textFormatElement.firstChildElement();
2827  while ( !e.isNull() )
2828  {
2829  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2830  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2831  {
2832  // skip the format, should already be present
2833  continue;
2834  }
2835 
2836  if ( e.tagName() == QLatin1String( "textformat" ) )
2837  {
2838  QString name = e.attribute( QStringLiteral( "name" ) );
2839  QStringList tags;
2840  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2841  {
2842  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2843  }
2844  bool favorite = false;
2845  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2846  {
2847  favorite = true;
2848  }
2849 
2850  QgsTextFormat format;
2851  const QDomElement styleElem = e.firstChildElement();
2852  format.readXml( styleElem, QgsReadWriteContext() );
2853  addTextFormat( name, format );
2854  if ( mCurrentDB )
2855  {
2856  saveTextFormat( name, format, favorite, tags );
2857  }
2858  }
2859  else
2860  {
2861  QgsDebugMsg( "unknown tag: " + e.tagName() );
2862  }
2863  e = e.nextSiblingElement();
2864  }
2865  }
2866 
2867  // load label settings
2868  if ( version == STYLE_CURRENT_VERSION )
2869  {
2870  const QDomElement labelSettingsElement = docEl.firstChildElement( QStringLiteral( "labelsettings" ) );
2871  e = labelSettingsElement.firstChildElement();
2872  while ( !e.isNull() )
2873  {
2874  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2875  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2876  {
2877  // skip the settings, should already be present
2878  continue;
2879  }
2880 
2881  if ( e.tagName() == QLatin1String( "labelsetting" ) )
2882  {
2883  QString name = e.attribute( QStringLiteral( "name" ) );
2884  QStringList tags;
2885  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2886  {
2887  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2888  }
2889  bool favorite = false;
2890  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2891  {
2892  favorite = true;
2893  }
2894 
2895  QgsPalLayerSettings settings;
2896  const QDomElement styleElem = e.firstChildElement();
2897  settings.readXml( styleElem, QgsReadWriteContext() );
2898  addLabelSettings( name, settings );
2899  if ( mCurrentDB )
2900  {
2901  saveLabelSettings( name, settings, favorite, tags );
2902  }
2903  }
2904  else
2905  {
2906  QgsDebugMsg( "unknown tag: " + e.tagName() );
2907  }
2908  e = e.nextSiblingElement();
2909  }
2910  }
2911 
2912  // load legend patch shapes
2913  if ( version == STYLE_CURRENT_VERSION )
2914  {
2915  const QDomElement legendPatchShapesElement = docEl.firstChildElement( QStringLiteral( "legendpatchshapes" ) );
2916  e = legendPatchShapesElement.firstChildElement();
2917  while ( !e.isNull() )
2918  {
2919  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2920  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2921  {
2922  // skip the shape, should already be present
2923  continue;
2924  }
2925 
2926  if ( e.tagName() == QLatin1String( "legendpatchshape" ) )
2927  {
2928  QString name = e.attribute( QStringLiteral( "name" ) );
2929  QStringList tags;
2930  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2931  {
2932  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2933  }
2934  bool favorite = false;
2935  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2936  {
2937  favorite = true;
2938  }
2939 
2940  QgsLegendPatchShape shape;
2941  const QDomElement shapeElem = e.firstChildElement();
2942  shape.readXml( shapeElem, QgsReadWriteContext() );
2943  addLegendPatchShape( name, shape );
2944  if ( mCurrentDB )
2945  {
2946  saveLegendPatchShape( name, shape, favorite, tags );
2947  }
2948  }
2949  else
2950  {
2951  QgsDebugMsg( "unknown tag: " + e.tagName() );
2952  }
2953  e = e.nextSiblingElement();
2954  }
2955  }
2956 
2957  // load 3d symbols
2958  if ( version == STYLE_CURRENT_VERSION )
2959  {
2960  const QDomElement symbols3DElement = docEl.firstChildElement( QStringLiteral( "symbols3d" ) );
2961  e = symbols3DElement.firstChildElement();
2962  while ( !e.isNull() )
2963  {
2964  const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2965  if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2966  {
2967  // skip the symbol, should already be present
2968  continue;
2969  }
2970 
2971  if ( e.tagName() == QLatin1String( "symbol3d" ) )
2972  {
2973  QString name = e.attribute( QStringLiteral( "name" ) );
2974  QStringList tags;
2975  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2976  {
2977  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2978  }
2979  bool favorite = false;
2980  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2981  {
2982  favorite = true;
2983  }
2984 
2985  const QDomElement symbolElem = e.firstChildElement();
2986  const QString type = symbolElem.attribute( QStringLiteral( "type" ) );
2987  std::unique_ptr< QgsAbstract3DSymbol > sym( QgsApplication::symbol3DRegistry()->createSymbol( type ) );
2988  if ( sym )
2989  {
2990  sym->readXml( symbolElem, QgsReadWriteContext() );
2991  QgsAbstract3DSymbol *newSym = sym.get();
2992  addSymbol3D( name, sym.release() );
2993  if ( mCurrentDB )
2994  {
2995  saveSymbol3D( name, newSym, favorite, tags );
2996  }
2997  }
2998  }
2999  else
3000  {
3001  QgsDebugMsg( "unknown tag: " + e.tagName() );
3002  }
3003  e = e.nextSiblingElement();
3004  }
3005  }
3006 
3007  query = qgs_sqlite3_mprintf( "COMMIT TRANSACTION;" );
3008  runEmptyQuery( query );
3009 
3010  mFileName = filename;
3011  return true;
3012 }
3013 
3014 bool QgsStyle::isXmlStyleFile( const QString &path )
3015 {
3016  QFileInfo fileInfo( path );
3017 
3018  if ( fileInfo.suffix().compare( QLatin1String( "xml" ), Qt::CaseInsensitive ) != 0 )
3019  return false;
3020 
3021  // sniff the first line of the file to see if it's a style file
3022  if ( !QFile::exists( path ) )
3023  return false;
3024 
3025  QFile inputFile( path );
3026  if ( !inputFile.open( QIODevice::ReadOnly ) )
3027  return false;
3028 
3029  QTextStream stream( &inputFile );
3030  const QString line = stream.readLine();
3031  return line == QLatin1String( "<!DOCTYPE qgis_style>" );
3032 }
3033 
3034 bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
3035 {
3036  QDomDocument doc( QStringLiteral( "dummy" ) );
3037  QDomElement symEl;
3038  QByteArray xmlArray;
3039  QTextStream stream( &xmlArray );
3040  stream.setCodec( "UTF-8" );
3041 
3042  QString query;
3043 
3044  switch ( type )
3045  {
3046  case SymbolEntity:
3047  {
3048  // check if it is an existing symbol
3049  if ( !symbolNames().contains( name ) )
3050  {
3051  QgsDebugMsg( QStringLiteral( "Update request received for unavailable symbol" ) );
3052  return false;
3053  }
3054 
3055  symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol( name ), doc, QgsReadWriteContext() );
3056  if ( symEl.isNull() )
3057  {
3058  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
3059  return false;
3060  }
3061  symEl.save( stream, 4 );
3062  query = qgs_sqlite3_mprintf( "UPDATE symbol SET xml='%q' WHERE name='%q';",
3063  xmlArray.constData(), name.toUtf8().constData() );
3064  break;
3065  }
3066 
3067  case Symbol3DEntity:
3068  {
3069  // check if it is an existing symbol
3070  if ( !symbol3DNames().contains( name ) )
3071  {
3072  QgsDebugMsg( QStringLiteral( "Update request received for unavailable symbol" ) );
3073  return false;
3074  }
3075 
3076  symEl = doc.createElement( QStringLiteral( "symbol" ) );
3077  symEl.setAttribute( QStringLiteral( "type" ), m3dSymbols.value( name )->type() );
3078  m3dSymbols.value( name )->writeXml( symEl, QgsReadWriteContext() );
3079  if ( symEl.isNull() )
3080  {
3081  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
3082  return false;
3083  }
3084  symEl.save( stream, 4 );
3085  query = qgs_sqlite3_mprintf( "UPDATE symbol3d SET xml='%q' WHERE name='%q';",
3086  xmlArray.constData(), name.toUtf8().constData() );
3087  break;
3088  }
3089 
3090  case ColorrampEntity:
3091  {
3092  if ( !colorRampNames().contains( name ) )
3093  {
3094  QgsDebugMsg( QStringLiteral( "Update requested for unavailable color ramp." ) );
3095  return false;
3096  }
3097 
3098  std::unique_ptr< QgsColorRamp > ramp( colorRamp( name ) );
3099  symEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp.get(), doc );
3100  if ( symEl.isNull() )
3101  {
3102  QgsDebugMsg( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
3103  return false;
3104  }
3105  symEl.save( stream, 4 );
3106  query = qgs_sqlite3_mprintf( "UPDATE colorramp SET xml='%q' WHERE name='%q';",
3107  xmlArray.constData(), name.toUtf8().constData() );
3108  break;
3109  }
3110 
3111  case TextFormatEntity:
3112  {
3113  if ( !textFormatNames().contains( name ) )
3114  {
3115  QgsDebugMsg( QStringLiteral( "Update requested for unavailable text format." ) );
3116  return false;
3117  }
3118 
3119  QgsTextFormat format( textFormat( name ) );
3120  symEl = format.writeXml( doc, QgsReadWriteContext() );
3121  if ( symEl.isNull() )
3122  {
3123  QgsDebugMsg( QStringLiteral( "Couldn't convert text format to valid XML!" ) );
3124  return false;
3125  }
3126  symEl.save( stream, 4 );
3127  query = qgs_sqlite3_mprintf( "UPDATE textformat SET xml='%q' WHERE name='%q';",
3128  xmlArray.constData(), name.toUtf8().constData() );
3129  break;
3130  }
3131 
3132  case LabelSettingsEntity:
3133  {
3134  if ( !labelSettingsNames().contains( name ) )
3135  {
3136  QgsDebugMsg( QStringLiteral( "Update requested for unavailable label settings." ) );
3137  return false;
3138  }
3139 
3140  QgsPalLayerSettings settings( labelSettings( name ) );
3141  symEl = settings.writeXml( doc, QgsReadWriteContext() );
3142  if ( symEl.isNull() )
3143  {
3144  QgsDebugMsg( QStringLiteral( "Couldn't convert label settings to valid XML!" ) );
3145  return false;
3146  }
3147  symEl.save( stream, 4 );
3148  query = qgs_sqlite3_mprintf( "UPDATE labelsettings SET xml='%q' WHERE name='%q';",
3149  xmlArray.constData(), name.toUtf8().constData() );
3150  break;
3151  }
3152 
3154  {
3155  if ( !legendPatchShapeNames().contains( name ) )
3156  {
3157  QgsDebugMsg( QStringLiteral( "Update requested for unavailable legend patch shape." ) );
3158  return false;
3159  }
3160 
3161  QgsLegendPatchShape shape( legendPatchShape( name ) );
3162  symEl = doc.createElement( QStringLiteral( "shape" ) );
3163  shape.writeXml( symEl, doc, QgsReadWriteContext() );
3164  symEl.save( stream, 4 );
3165  query = qgs_sqlite3_mprintf( "UPDATE legendpatchshapes SET xml='%q' WHERE name='%q';",
3166  xmlArray.constData(), name.toUtf8().constData() );
3167  break;
3168  }
3169 
3170  case TagEntity:
3171  case SmartgroupEntity:
3172  {
3173  QgsDebugMsg( QStringLiteral( "Updating the unsupported StyleEntity" ) );
3174  return false;
3175  }
3176  }
3177 
3178 
3179  if ( !runEmptyQuery( query ) )
3180  {
3181  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
3182  return false;
3183  }
3184  else
3185  {
3186  switch ( type )
3187  {
3188  case SymbolEntity:
3189  emit symbolChanged( name );
3190  break;
3191 
3192  case ColorrampEntity:
3193  emit rampChanged( name );
3194  break;
3195 
3196  case TextFormatEntity:
3197  emit textFormatChanged( name );
3198  break;
3199 
3200  case LabelSettingsEntity:
3201  emit labelSettingsChanged( name );
3202  break;
3203 
3205  case TagEntity:
3206  case SmartgroupEntity:
3207  case Symbol3DEntity:
3208  break;
3209  }
3210  emit entityChanged( type, name );
3211  }
3212  return true;
3213 }
3214 
3215 void QgsStyle::clearCachedTags( QgsStyle::StyleEntity type, const QString &name )
3216 {
3217  mCachedTags[ type ].remove( name );
3218 }
3219 
3220 void QgsStyle::upgradeIfRequired()
3221 {
3222  // make sure metadata table exists
3223  QString query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='stylemetadata'" );
3224  sqlite3_statement_unique_ptr statement;
3225  int rc;
3226  int dbVersion = 0;
3227  statement = mCurrentDB.prepare( query, rc );
3228 
3229  if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
3230  {
3231  // no metadata table
3232  query = qgs_sqlite3_mprintf( "CREATE TABLE stylemetadata("\
3233  "id INTEGER PRIMARY KEY,"\
3234  "key TEXT UNIQUE,"\
3235  "value TEXT);" );
3236  runEmptyQuery( query );
3237  query = qgs_sqlite3_mprintf( "INSERT INTO stylemetadata VALUES (NULL, '%q', '%q')", "version", "31200" );
3238  runEmptyQuery( query );
3239 
3240  dbVersion = 31200;
3241  }
3242  else
3243  {
3244  query = qgs_sqlite3_mprintf( "SELECT value FROM stylemetadata WHERE key='version'" );
3245  statement = mCurrentDB.prepare( query, rc );
3246  if ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
3247  {
3248  dbVersion = statement.columnAsText( 0 ).toInt();
3249  }
3250  }
3251 
3252  if ( dbVersion < Qgis::versionInt() )
3253  {
3254  // do upgrade
3255  if ( importXml( QgsApplication::defaultStylePath(), dbVersion ) )
3256  {
3257  query = qgs_sqlite3_mprintf( "UPDATE stylemetadata SET value='%q' WHERE key='version'", QString::number( Qgis::versionInt() ).toUtf8().constData() );
3258  runEmptyQuery( query );
3259  }
3260  }
3261 }
3262 
3263 QString QgsStyle::entityTableName( QgsStyle::StyleEntity type )
3264 {
3265  switch ( type )
3266  {
3267  case SymbolEntity:
3268  return QStringLiteral( "symbol" );
3269 
3270  case ColorrampEntity:
3271  return QStringLiteral( "colorramp" );
3272 
3273  case TextFormatEntity:
3274  return QStringLiteral( "textformat" );
3275 
3276  case LabelSettingsEntity:
3277  return QStringLiteral( "labelsettings" );
3278 
3280  return QStringLiteral( "legendpatchshapes" );
3281 
3282  case Symbol3DEntity:
3283  return QStringLiteral( "symbol3d" );
3284 
3285  case TagEntity:
3286  return QStringLiteral( "tag" );
3287 
3288  case SmartgroupEntity:
3289  return QStringLiteral( "smartgroup" );
3290  }
3291  return QString();
3292 }
3293 
3294 QString QgsStyle::tagmapTableName( QgsStyle::StyleEntity type )
3295 {
3296  switch ( type )
3297  {
3298  case SymbolEntity:
3299  return QStringLiteral( "tagmap" );
3300 
3301  case ColorrampEntity:
3302  return QStringLiteral( "ctagmap" );
3303 
3304  case TextFormatEntity:
3305  return QStringLiteral( "tftagmap" );
3306 
3307  case LabelSettingsEntity:
3308  return QStringLiteral( "lstagmap" );
3309 
3311  return QStringLiteral( "lpstagmap" );
3312 
3313  case Symbol3DEntity:
3314  return QStringLiteral( "symbol3dtagmap" );
3315 
3316  case TagEntity:
3317  case SmartgroupEntity:
3318  break;
3319  }
3320  return QString();
3321 }
3322 
3323 QString QgsStyle::tagmapEntityIdFieldName( QgsStyle::StyleEntity type )
3324 {
3325  switch ( type )
3326  {
3327  case SymbolEntity:
3328  return QStringLiteral( "symbol_id" );
3329 
3330  case ColorrampEntity:
3331  return QStringLiteral( "colorramp_id" );
3332 
3333  case TextFormatEntity:
3334  return QStringLiteral( "textformat_id" );
3335 
3336  case LabelSettingsEntity:
3337  return QStringLiteral( "labelsettings_id" );
3338 
3340  return QStringLiteral( "legendpatchshape_id" );
3341 
3342  case Symbol3DEntity:
3343  return QStringLiteral( "symbol3d_id" );
3344 
3345  case TagEntity:
3346  case SmartgroupEntity:
3347  break;
3348  }
3349  return QString();
3350 }
3351 
3353 {
3354  return QgsStyle::SymbolEntity;
3355 }
3356 
3358 {
3360 }
3361 
3363 {
3365 }
3366 
3368 {
3370 }
3371 
3373 {
3375 }
3376 
3378 {
3379  return QgsStyle::Symbol3DEntity;
3380 }
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool exportXml(const QString &filename)
Exports the style as a XML file.
Definition: qgsstyle.cpp:2513
QStringList allNames(StyleEntity type) const
Returns a list of the names of all existing entities of the specified type.
Definition: qgsstyle.cpp:2219
The class is used as a container of context for various read/write operations on other objects...
void clear()
Removes all contents of the style.
Definition: qgsstyle.cpp:162
const QgsColorRamp * colorRampRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:444
bool addEntity(const QString &name, const QgsStyleEntityInterface *entity, bool update=false)
Adds an entity to the style, with the specified name.
Definition: qgsstyle.cpp:93
bool addTextFormat(const QString &name, const QgsTextFormat &format, bool update=false)
Adds a text format with the specified name to the style.
Definition: qgsstyle.cpp:315
bool addColorRamp(const QString &name, QgsColorRamp *colorRamp, bool update=false)
Adds a color ramp to the style.
Definition: qgsstyle.cpp:291
void symbolSaved(const QString &name, QgsSymbol *symbol)
Emitted every time a new symbol has been added to the database.
Scoped object for logging of the runtime for a single operation or group of operations.
static QString userStylePath()
Returns the path to user&#39;s style.
bool symbolHasTag(StyleEntity type, const QString &symbol, const QString &tag)
Returns whether a given tag is associated with the symbol.
Definition: qgsstyle.cpp:1981
bool save(QString filename=QString())
Saves style into a file (will use current filename if empty string is passed)
Definition: qgsstyle.cpp:828
void textFormatAdded(const QString &name)
Emitted whenever a text format has been added to the style and the database has been updated as a res...
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:64
An interface for entities which can be placed in a QgsStyle database.
Definition: qgsstyle.h:1152
void entityAdded(QgsStyle::StyleEntity entity, const QString &name)
Emitted every time a new entity has been added to the database.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:1316
void entityTagsChanged(QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags)
Emitted whenever an entity&#39;s tags are changed.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QgsLegendPatchShape defaultPatch(QgsSymbol::SymbolType type, QSizeF size) const
Returns the default legend patch shape for the given symbol type.
Definition: qgsstyle.cpp:1159
int legendPatchShapesCount() const
Returns count of legend patch shapes in the style.
Definition: qgsstyle.cpp:2131
void createTables()
Creates tables structure for new database.
Definition: qgsstyle.cpp:522
bool load(const QString &filename)
Loads a file into the style.
Definition: qgsstyle.cpp:582
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
bool addLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool update=false)
Adds label settings with the specified name to the style.
Definition: qgsstyle.cpp:336
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
Definition: qgsstyle.cpp:1892
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
QMap< int, QString > QgsSymbolGroupMap
Definition: qgsstyle.h:42
bool saveColorRamp(const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags)
Adds the colorramp to the database.
Definition: qgsstyle.cpp:399
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp&#39;s settings to an XML element.
int textFormatCount() const
Returns count of text formats in the style.
Definition: qgsstyle.cpp:2106
bool tagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Tags the symbol with the tags in the list.
Definition: qgsstyle.cpp:1736
3d symbol name
Definition: qgsstyle.cpp:65
QList< QList< QPolygonF > > defaultPatchAsQPolygonF(QgsSymbol::SymbolType type, QSizeF size) const
Returns the default patch geometry for the given symbol type and size as a set of QPolygonF objects (...
Definition: qgsstyle.cpp:1201
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:123
3d symbol ID
Definition: qgsstyle.cpp:64
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
bool addLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool update=false)
Adds a legend patch shape with the specified name to the style.
Definition: qgsstyle.cpp:357
Line symbol.
Definition: qgssymbol.h:89
Symbol definition (as XML)
Definition: qgsstyle.h:90
int entityId(StyleEntity type, const QString &name)
Returns the id in the style database for the given name of the specified entity type.
Definition: qgsstyle.cpp:2091
void textFormatRemoved(const QString &name)
Emitted whenever a text format has been removed from the style and the database has been updated as a...
int labelSettingsId(const QString &name)
Returns the ID in the style database for the given label settings by name.
Definition: qgsstyle.cpp:2180
static QgsSymbol * loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
void rampChanged(const QString &name)
Emitted whenever a color ramp&#39;s definition is changed.
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition: qgsstyle.cpp:438
QgsAbstract3DSymbol * symbol3D(const QString &name) const
Returns a new copy of the 3D symbol with the specified name.
Definition: qgsstyle.cpp:2144
bool removeColorRamp(const QString &name)
Removes color ramp from style (and delete it)
Definition: qgsstyle.cpp:433
bool rename(StyleEntity type, int id, const QString &newName)
Renames the given entity with the specified id.
Definition: qgsstyle.cpp:1402
Represents a patch shape for use in map legends.
void rampAdded(const QString &name)
Emitted whenever a color ramp has been added to the style and the database has been updated as a resu...
virtual void writeXml(QDomElement &elem, const QgsReadWriteContext &context) const =0
Writes symbol configuration to the given DOM element.
3 Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
#define STYLE_CURRENT_VERSION
Definition: qgsstyle.cpp:46
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:127
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:178
int labelSettingsCount() const
Returns count of label settings in the style.
Definition: qgsstyle.cpp:2170
static QString defaultStylePath()
Returns the path to default style (works as a starting point).
bool isFavorite(StyleEntity type, const QString &name)
Returns true if the symbol with matching type and name is marked as a favorite.
Definition: qgsstyle.cpp:1943
QString errorMessage() const
Returns the most recent error message encountered by the database.
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3367
SymbolType
Type of the symbol.
Definition: qgssymbol.h:86
3d symbol definition (as XML)
Definition: qgsstyle.cpp:66
int symbolCount()
Returns count of symbols in style.
Definition: qgsstyle.cpp:280
static int versionInt()
Version number used for comparing versions using the "Check QGIS Version" function.
Definition: qgis.cpp:281
bool renameSymbol(const QString &oldName, const QString &newName)
Renames a symbol from oldName to newName.
Definition: qgsstyle.cpp:874
Smart group name.
Definition: qgsstyle.h:151
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
Definition: qgsstyle.cpp:2111
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
Definition: qgsstyle.cpp:2121
LegendPatchTable
Columns available in the legend patch table.
Definition: qgsstyle.cpp:51
void rampRemoved(const QString &name)
Emitted whenever a color ramp has been removed from the style and the database has been updated as a ...
QList< QList< QPolygonF > > toQPolygonF(QgsSymbol::SymbolType type, QSizeF size) const
Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for...
QString tag(int id) const
Returns the tag name for the given id.
Definition: qgsstyle.cpp:2021
void labelSettingsRemoved(const QString &name)
Emitted whenever label settings have been removed from the style and the database has been updated as...
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
Definition: qgsstyle.cpp:2675
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:275
const QgsSymbol * previewSymbolForPatchShape(const QgsLegendPatchShape &shape) const
Returns a symbol to use for rendering preview icons for a patch shape.
Definition: qgsstyle.cpp:2190
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
QStringList labelSettingsNames() const
Returns a list of names of label settings in the style.
Definition: qgsstyle.cpp:2175
bool removeFavorite(StyleEntity type, const QString &name)
Removes the specified symbol from favorites.
Definition: qgsstyle.cpp:1643
Calculate scale by the area.
Definition: qgssymbol.h:99
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QgsStyle()
Constructor for QgsStyle.
Definition: qgsstyle.cpp:73
int textFormatId(const QString &name)
Returns the ID in the style database for the given text format by name.
Definition: qgsstyle.cpp:2116
bool remove(StyleEntity type, int id)
Removes the specified entity from the database.
Definition: qgsstyle.cpp:1437
QStringList findSymbols(StyleEntity type, const QString &qword)
Returns the names of the symbols which have a matching &#39;substring&#39; in its definition.
Definition: qgsstyle.cpp:1669
void groupsModified()
Emitted every time a tag or smartgroup has been added, removed, or renamed.
int addTag(const QString &tagName)
Adds a new tag and returns the tag&#39;s id.
Definition: qgsstyle.cpp:1362
bool removeEntityByName(StyleEntity type, const QString &name)
Removes the entry of the specified type with matching name from the database.
Definition: qgsstyle.cpp:1483
static void cleanDefaultStyle()
Deletes the default style. Only to be used by QgsApplication::exitQgis()
Definition: qgsstyle.cpp:156
QStringList legendPatchShapeNames() const
Returns a list of names of legend patch shapes in the style.
Definition: qgsstyle.cpp:2185
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:54
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
static QgsColorRamp * loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
Legend patch shape (since QGIS 3.14)
Definition: qgsstyle.h:186
bool createMemoryDatabase()
Creates a temporary memory database.
Definition: qgsstyle.cpp:507
QString smartgroupOperator(int id)
Returns the operator for the smartgroup.
Definition: qgsstyle.cpp:2483
static bool isXmlStyleFile(const QString &path)
Tests if the file at path is a QGIS style XML file.
Definition: qgsstyle.cpp:3014
virtual QString type() const =0
Returns identifier of symbol type. Each 3D symbol implementation should return a different type...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
void symbolRenamed(const QString &oldName, const QString &newName)
Emitted whenever a symbol has been renamed from oldName to newName.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
int open(const QString &path)
Opens the database at the specified file path.
void labelSettingsChanged(const QString &name)
Emitted whenever a label setting&#39;s definition is changed.
Label settings name.
Definition: qgsstyle.h:140
Legend patch name.
Definition: qgsstyle.cpp:54
QgsTextFormat textFormat(const QString &name) const
Returns the text format with the specified name.
Definition: qgsstyle.cpp:2101
void textFormatRenamed(const QString &oldName, const QString &newName)
Emitted whenever a text format has been renamed from oldName to newName.
3d symbol is favorite flag
Definition: qgsstyle.cpp:67
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
void favoritedChanged(QgsStyle::StyleEntity entity, const QString &name, bool isFavorite)
Emitted whenever an entity is either favorited or un-favorited.
QgsLegendPatchShape legendPatchShape(const QString &name) const
Returns the legend patch shape with the specified name.
Definition: qgsstyle.cpp:2126
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
QgsSmartConditionMap smartgroup(int id)
Returns the QgsSmartConditionMap for the given id.
Definition: qgsstyle.cpp:2443
void symbolRemoved(const QString &name)
Emitted whenever a symbol has been removed from the style and the database has been updated as a resu...
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:2351
int smartgroupId(const QString &smartgroup)
Returns the database id for the given smartgroup name.
Definition: qgsstyle.cpp:2214
bool renameEntity(StyleEntity type, const QString &oldName, const QString &newName)
Renames an entity of the specified type from oldName to newName.
Definition: qgsstyle.cpp:240
int tagId(const QString &tag)
Returns the database id for the given tag name.
Definition: qgsstyle.cpp:2209
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3357
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:140
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
bool saveTextFormat(const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags)
Adds a text format to the database.
Definition: qgsstyle.cpp:949
Text format name.
Definition: qgsstyle.h:129
Marker symbol.
Definition: qgssymbol.h:88
bool addSymbol(const QString &name, QgsSymbol *symbol, bool update=false)
Adds a symbol to style and takes symbol&#39;s ownership.
Definition: qgsstyle.cpp:177
bool removeTextFormat(const QString &name)
Removes a text format from the style.
Definition: qgsstyle.cpp:983
QStringList tags() const
Returns a list of all tags in the style database.
Definition: qgsstyle.cpp:1382
Fill symbol.
Definition: qgssymbol.h:90
void rampRenamed(const QString &oldName, const QString &newName)
Emitted whenever a color ramp has been renamed from oldName to newName.
bool renameSymbol3D(const QString &oldName, const QString &newName)
Changes a 3d symbol&#39;s name.
Definition: qgsstyle.cpp:1243
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3377
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
void entityRenamed(QgsStyle::StyleEntity entity, const QString &oldName, const QString &newName)
Emitted whenever a entity of the specified type has been renamed from oldName to newName.
void textFormatChanged(const QString &name)
Emitted whenever a text format&#39;s definition is changed.
int addSmartgroup(const QString &name, const QString &op, const QgsSmartConditionMap &conditions)
Adds a new smartgroup to the database and returns the id.
Definition: qgsstyle.cpp:2250
Text format definition (as XML)
Definition: qgsstyle.h:130
void symbolChanged(const QString &name)
Emitted whenever a symbol&#39;s definition is changed.
void entityRemoved(QgsStyle::StyleEntity entity, const QString &name)
Emitted whenever an entity of the specified type is removed from the style and the database has been ...
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
bool createDatabase(const QString &filename)
Creates an on-disk database.
Definition: qgsstyle.cpp:492
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
void writeXml(QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
int colorrampId(const QString &name)
Returns the id in the style database for the given colorramp name returns 0 if not found...
Definition: qgsstyle.cpp:2096
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
int symbolId(const QString &name)
Returns the id in the style database for the given symbol name returns 0 if not found.
Definition: qgsstyle.cpp:2086
void entityChanged(QgsStyle::StyleEntity entity, const QString &name)
Emitted whenever an entity&#39;s definition is changed.
Symbol3DTable
Columns available in the 3d symbol table.
Definition: qgsstyle.cpp:62
QStringList colorRampNames() const
Returns a list of names of color ramps.
Definition: qgsstyle.cpp:454
Legend patch definition (as XML)
Definition: qgsstyle.cpp:55
QMap< QString, QgsSymbol *> QgsSymbolMap
Definition: qgsrenderer.h:46
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3352
bool addSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool update=false)
Adds a 3d symbol with the specified name to the style.
Definition: qgsstyle.cpp:378
int symbol3DCount() const
Returns count of 3D symbols in the style.
Definition: qgsstyle.cpp:2149
3D symbol entity (since QGIS 3.14)
Definition: qgsstyle.h:187
bool renameLabelSettings(const QString &oldName, const QString &newName)
Changes a label setting&#39;s name.
Definition: qgsstyle.cpp:1062
bool renameColorRamp(const QString &oldName, const QString &newName)
Changes ramp&#39;s name.
Definition: qgsstyle.cpp:914
QgsWkbTypes::GeometryType labelSettingsLayerType(const QString &name) const
Returns the layer geometry type corresponding to the label settings with the specified name...
Definition: qgsstyle.cpp:2162
QList< QgsWkbTypes::GeometryType > symbol3DCompatibleGeometryTypes(const QString &name) const
Returns the list of the vector layer geometry types which are compatible with the 3D symbol with the ...
Definition: qgsstyle.cpp:2154
int colorRampCount()
Returns count of color ramps.
Definition: qgsstyle.cpp:449
QgsSymbol::SymbolType symbolType() const
Returns the symbol type associated with this patch.
void labelSettingsRenamed(const QString &oldName, const QString &newName)
Emitted whenever label settings have been renamed from oldName to newName.
bool renameLegendPatchShape(const QString &oldName, const QString &newName)
Changes a legend patch shape&#39;s name.
Definition: qgsstyle.cpp:1125
bool saveSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags)
Adds a 3d symbol to the database.
Definition: qgsstyle.cpp:1214
QStringList symbol3DNames() const
Returns a list of names of 3d symbols in the style.
Definition: qgsstyle.cpp:1277
Smart group ID.
Definition: qgsstyle.h:150
void labelSettingsAdded(const QString &name)
Emitted whenever label settings have been added to the style and the database has been updated as a r...
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:269
bool saveSymbol(const QString &name, QgsSymbol *symbol, bool favorite, const QStringList &tags)
Adds the symbol to the database with tags.
Definition: qgsstyle.cpp:201
Symbol Name.
Definition: qgsstyle.h:89
bool saveLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool favorite, const QStringList &tags)
Adds a legend patch shape to the database.
Definition: qgsstyle.cpp:1097
bool removeSymbol(const QString &name)
Removes symbol from style (and delete it)
Definition: qgsstyle.cpp:235
Color ramp name.
Definition: qgsstyle.h:118
Container for all settings relating to text rendering.
Definition: qgstextformat.h:39
bool saveLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags)
Adds label settings to the database.
Definition: qgsstyle.cpp:1023
bool addFavorite(StyleEntity type, const QString &name)
Adds the specified symbol to favorites.
Definition: qgsstyle.cpp:1607
Label settings definition (as XML)
Definition: qgsstyle.h:141
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3372
bool removeLabelSettings(const QString &name)
Removes label settings from the style.
Definition: qgsstyle.cpp:1057
Legend patch is favorite flag.
Definition: qgsstyle.cpp:56
Hybrid symbol.
Definition: qgssymbol.h:91
bool renameTextFormat(const QString &oldName, const QString &newName)
Changes a text format&#39;s name.
Definition: qgsstyle.cpp:988
Legend patch ID.
Definition: qgsstyle.cpp:53
bool detagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Detags the symbol with the given list.
Definition: qgsstyle.cpp:1799
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
Definition: qgsstyle.cpp:1282
Color ramp definition (as XML)
Definition: qgsstyle.h:119
QMultiMap< QString, QString > QgsSmartConditionMap
A multimap to hold the smart group conditions as constraint and parameter pairs.
Definition: qgsstyle.h:79
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
Definition: qgsstyle.cpp:3362
QgsSymbol::SymbolType legendPatchShapeSymbolType(const QString &name) const
Returns the symbol type corresponding to the legend patch shape with the specified name...
Definition: qgsstyle.cpp:2136
QStringList symbolTypes() const
Returns a list of all available symbol types.
QgsSymbolGroupMap smartgroupsListMap()
Returns the smart groups map with id as key and name as value.
Definition: qgsstyle.cpp:2302
~QgsStyle() override
Definition: qgsstyle.cpp:88
QStringList smartgroupNames() const
Returns the smart groups list.
Definition: qgsstyle.cpp:2327
QStringList symbolNames() const
Returns a list of names of symbols.
Definition: qgsstyle.cpp:285