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