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