QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 "1"
38 
39 QgsStyle *QgsStyle::sDefaultStyle = nullptr;
40 
42 {
43  clear();
44 }
45 
47 {
48  if ( !sDefaultStyle )
49  {
50  QString styleFilename = QgsApplication::userStylePath();
51 
52  // copy default style if user style doesn't exist
53  if ( !QFile::exists( styleFilename ) )
54  {
55  sDefaultStyle = new QgsStyle;
56  sDefaultStyle->createDatabase( styleFilename );
57  if ( QFile::exists( QgsApplication::defaultStylePath() ) )
58  {
59  sDefaultStyle->importXml( QgsApplication::defaultStylePath() );
60  }
61  }
62  else
63  {
64  sDefaultStyle = new QgsStyle;
65  sDefaultStyle->load( styleFilename );
66  }
67  }
68  return sDefaultStyle;
69 }
70 
72 {
73  delete sDefaultStyle;
74  sDefaultStyle = nullptr;
75 }
76 
78 {
79  qDeleteAll( mSymbols );
80  qDeleteAll( mColorRamps );
81 
82  mSymbols.clear();
83  mColorRamps.clear();
84  mCachedColorRampTags.clear();
85  mCachedSymbolTags.clear();
86 }
87 
88 bool QgsStyle::addSymbol( const QString &name, QgsSymbol *symbol, bool update )
89 {
90  if ( !symbol || name.isEmpty() )
91  return false;
92 
93  // delete previous symbol (if any)
94  if ( mSymbols.contains( name ) )
95  {
96  // TODO remove groups and tags?
97  delete mSymbols.value( name );
98  mSymbols.insert( name, symbol );
99  if ( update )
100  updateSymbol( SymbolEntity, name );
101  }
102  else
103  {
104  mSymbols.insert( name, symbol );
105  if ( update )
106  saveSymbol( name, symbol, false, QStringList() );
107  }
108 
109  return true;
110 }
111 
112 bool QgsStyle::saveSymbol( const QString &name, QgsSymbol *symbol, bool favorite, const QStringList &tags )
113 {
114  // TODO add support for groups
115  QDomDocument doc( QStringLiteral( "dummy" ) );
116  QDomElement symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol, doc, QgsReadWriteContext() );
117  if ( symEl.isNull() )
118  {
119  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
120  return false;
121  }
122 
123  QByteArray xmlArray;
124  QTextStream stream( &xmlArray );
125  stream.setCodec( "UTF-8" );
126  symEl.save( stream, 4 );
127  auto query = QgsSqlite3Mprintf( "INSERT INTO symbol VALUES (NULL, '%q', '%q', %d);",
128  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
129 
130  if ( !runEmptyQuery( query ) )
131  {
132  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
133  return false;
134  }
135 
136  tagSymbol( SymbolEntity, name, tags );
137 
138  emit symbolSaved( name, symbol );
139 
140  return true;
141 }
142 
143 bool QgsStyle::removeSymbol( const QString &name )
144 {
145  QgsSymbol *symbol = mSymbols.take( name );
146  if ( !symbol )
147  return false;
148 
149  // remove from map and delete
150  delete symbol;
151 
152  // TODO
153  // Simplify this work here, its STUPID to run two DB queries for the sake of remove()
154  if ( !mCurrentDB )
155  {
156  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to tag." ) );
157  return false;
158  }
159 
160  int symbolid = symbolId( name );
161  if ( !symbolid )
162  {
163  QgsDebugMsg( "No such symbol for deleting in database: " + name + ". Cheers." );
164  }
165 
166  const bool result = remove( SymbolEntity, symbolid );
167  if ( result )
168  {
169  mCachedSymbolTags.remove( name );
170  emit symbolRemoved( name );
171  }
172  return result;
173 }
174 
175 QgsSymbol *QgsStyle::symbol( const QString &name )
176 {
177  const QgsSymbol *symbol = symbolRef( name );
178  return symbol ? symbol->clone() : nullptr;
179 }
180 
181 const QgsSymbol *QgsStyle::symbolRef( const QString &name ) const
182 {
183  return mSymbols.value( name );
184 }
185 
187 {
188  return mSymbols.count();
189 }
190 
192 {
193  return mSymbols.keys();
194 }
195 
196 
197 bool QgsStyle::addColorRamp( const QString &name, QgsColorRamp *colorRamp, bool update )
198 {
199  if ( !colorRamp || name.isEmpty() )
200  return false;
201 
202  // delete previous color ramps (if any)
203  if ( mColorRamps.contains( name ) )
204  {
205  // TODO remove groups and tags?
206  delete mColorRamps.value( name );
207  mColorRamps.insert( name, colorRamp );
208  if ( update )
209  updateSymbol( ColorrampEntity, name );
210  }
211  else
212  {
213  mColorRamps.insert( name, colorRamp );
214  if ( update )
215  saveColorRamp( name, colorRamp, false, QStringList() );
216  }
217 
218  return true;
219 }
220 
221 bool QgsStyle::saveColorRamp( const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags )
222 {
223  // insert it into the database
224  QDomDocument doc( QStringLiteral( "dummy" ) );
225  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp, doc );
226 
227  if ( rampEl.isNull() )
228  {
229  QgsDebugMsg( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
230  return false;
231  }
232 
233  QByteArray xmlArray;
234  QTextStream stream( &xmlArray );
235  stream.setCodec( "UTF-8" );
236  rampEl.save( stream, 4 );
237  auto query = QgsSqlite3Mprintf( "INSERT INTO colorramp VALUES (NULL, '%q', '%q', %d);",
238  name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
239  if ( !runEmptyQuery( query ) )
240  {
241  QgsDebugMsg( QStringLiteral( "Couldn't insert colorramp into the database!" ) );
242  return false;
243  }
244 
245  tagSymbol( ColorrampEntity, name, tags );
246 
247  emit rampAdded( name );
248 
249  return true;
250 }
251 
252 bool QgsStyle::removeColorRamp( const QString &name )
253 {
254  std::unique_ptr< QgsColorRamp > ramp( mColorRamps.take( name ) );
255  if ( !ramp )
256  return false;
257 
258  auto query = QgsSqlite3Mprintf( "DELETE FROM colorramp WHERE name='%q'", name.toUtf8().constData() );
259  if ( !runEmptyQuery( query ) )
260  {
261  QgsDebugMsg( QStringLiteral( "Couldn't remove color ramp from the database." ) );
262  return false;
263  }
264 
265  mCachedColorRampTags.remove( name );
266 
267  emit rampRemoved( name );
268 
269  return true;
270 }
271 
272 QgsColorRamp *QgsStyle::colorRamp( const QString &name ) const
273 {
274  const QgsColorRamp *ramp = colorRampRef( name );
275  return ramp ? ramp->clone() : nullptr;
276 }
277 
278 const QgsColorRamp *QgsStyle::colorRampRef( const QString &name ) const
279 {
280  return mColorRamps.value( name );
281 }
282 
284 {
285  return mColorRamps.count();
286 }
287 
289 {
290  return mColorRamps.keys();
291 }
292 
293 bool QgsStyle::openDatabase( const QString &filename )
294 {
295  int rc = mCurrentDB.open( filename );
296  if ( rc )
297  {
298  mErrorString = QStringLiteral( "Couldn't open the style database: %1" ).arg( mCurrentDB.errorMessage() );
299  return false;
300  }
301 
302  return true;
303 }
304 
305 bool QgsStyle::createDatabase( const QString &filename )
306 {
307  mErrorString.clear();
308  if ( !openDatabase( filename ) )
309  {
310  mErrorString = QStringLiteral( "Unable to create database" );
311  QgsDebugMsg( mErrorString );
312  return false;
313  }
314 
315  createTables();
316 
317  return true;
318 }
319 
321 {
322  mErrorString.clear();
323  if ( !openDatabase( QStringLiteral( ":memory:" ) ) )
324  {
325  mErrorString = QStringLiteral( "Unable to create temporary memory database" );
326  QgsDebugMsg( mErrorString );
327  return false;
328  }
329 
330  createTables();
331 
332  return true;
333 }
334 
336 {
337  auto query = QgsSqlite3Mprintf( "CREATE TABLE symbol("\
338  "id INTEGER PRIMARY KEY,"\
339  "name TEXT UNIQUE,"\
340  "xml TEXT,"\
341  "favorite INTEGER);"\
342  "CREATE TABLE colorramp("\
343  "id INTEGER PRIMARY KEY,"\
344  "name TEXT UNIQUE,"\
345  "xml TEXT,"\
346  "favorite INTEGER);"\
347  "CREATE TABLE tag("\
348  "id INTEGER PRIMARY KEY,"\
349  "name TEXT);"\
350  "CREATE TABLE tagmap("\
351  "tag_id INTEGER NOT NULL,"\
352  "symbol_id INTEGER);"\
353  "CREATE TABLE ctagmap("\
354  "tag_id INTEGER NOT NULL,"\
355  "colorramp_id INTEGER);"\
356  "CREATE TABLE smartgroup("\
357  "id INTEGER PRIMARY KEY,"\
358  "name TEXT,"\
359  "xml TEXT);" );
360  runEmptyQuery( query );
361 }
362 
363 bool QgsStyle::load( const QString &filename )
364 {
365  mErrorString.clear();
366 
367  // Open the sqlite database
368  if ( !openDatabase( filename ) )
369  {
370  mErrorString = QStringLiteral( "Unable to open database file specified" );
371  QgsDebugMsg( mErrorString );
372  return false;
373  }
374 
375  // Make sure there are no Null fields in parenting symbols and groups
376  auto query = QgsSqlite3Mprintf( "UPDATE symbol SET favorite=0 WHERE favorite IS NULL;"
377  "UPDATE colorramp SET favorite=0 WHERE favorite IS NULL;"
378  );
379  runEmptyQuery( query );
380 
381  // First create all the main symbols
382  query = QgsSqlite3Mprintf( "SELECT * FROM symbol" );
383 
385  int rc;
386  statement = mCurrentDB.prepare( query, rc );
387 
388  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
389  {
390  QDomDocument doc;
391  QString symbol_name = statement.columnAsText( SymbolName );
392  QString xmlstring = statement.columnAsText( SymbolXML );
393  if ( !doc.setContent( xmlstring ) )
394  {
395  QgsDebugMsg( "Cannot open symbol " + symbol_name );
396  continue;
397  }
398 
399  QDomElement symElement = doc.documentElement();
401  if ( symbol )
402  mSymbols.insert( symbol_name, symbol );
403  }
404 
405  query = QgsSqlite3Mprintf( "SELECT * FROM colorramp" );
406  statement = mCurrentDB.prepare( query, rc );
407  while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
408  {
409  QDomDocument doc;
410  QString ramp_name = statement.columnAsText( ColorrampName );
411  QString xmlstring = statement.columnAsText( ColorrampXML );
412  if ( !doc.setContent( xmlstring ) )
413  {
414  QgsDebugMsg( "Cannot open symbol " + ramp_name );
415  continue;
416  }
417  QDomElement rampElement = doc.documentElement();
418  QgsColorRamp *ramp = QgsSymbolLayerUtils::loadColorRamp( rampElement );
419  if ( ramp )
420  mColorRamps.insert( ramp_name, ramp );
421  }
422 
423  mFileName = filename;
424  return true;
425 }
426 
427 
428 
429 bool QgsStyle::save( QString filename )
430 {
431  mErrorString.clear();
432 
433  if ( filename.isEmpty() )
434  filename = mFileName;
435 
436  // TODO evaluate the requirement of this function and change implementation accordingly
437  // TODO remove QEXPECT_FAIL from TestStyle::testSaveLoad() when done
438 #if 0
439  QDomDocument doc( "qgis_style" );
440  QDomElement root = doc.createElement( "qgis_style" );
441  root.setAttribute( "version", STYLE_CURRENT_VERSION );
442  doc.appendChild( root );
443 
444  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, "symbols", doc );
445 
446  QDomElement rampsElem = doc.createElement( "colorramps" );
447 
448  // save color ramps
449  for ( QMap<QString, QgsColorRamp *>::iterator itr = mColorRamps.begin(); itr != mColorRamps.end(); ++itr )
450  {
451  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
452  rampsElem.appendChild( rampEl );
453  }
454 
455  root.appendChild( symbolsElem );
456  root.appendChild( rampsElem );
457 
458  // save
459  QFile f( filename );
460  if ( !f.open( QFile::WriteOnly ) )
461  {
462  mErrorString = "Couldn't open file for writing: " + filename;
463  return false;
464  }
465  QTextStream ts( &f );
466  ts.setCodec( "UTF-8" );
467  doc.save( ts, 2 );
468  f.close();
469 #endif
470 
471  mFileName = filename;
472  return true;
473 }
474 
475 bool QgsStyle::renameSymbol( const QString &oldName, const QString &newName )
476 {
477  if ( mSymbols.contains( newName ) )
478  {
479  QgsDebugMsg( QStringLiteral( "Symbol of new name already exists" ) );
480  return false;
481  }
482 
483  QgsSymbol *symbol = mSymbols.take( oldName );
484  if ( !symbol )
485  return false;
486 
487  mSymbols.insert( newName, symbol );
488 
489  if ( !mCurrentDB )
490  {
491  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to tag." ) );
492  return false;
493  }
494 
495  int symbolid = symbolId( oldName );
496  if ( !symbolid )
497  {
498  QgsDebugMsg( QStringLiteral( "No such symbol for tagging in database: " ) + oldName );
499  return false;
500  }
501 
502  mCachedSymbolTags.remove( oldName );
503 
504  const bool result = rename( SymbolEntity, symbolid, newName );
505  if ( result )
506  emit symbolRenamed( oldName, newName );
507 
508  return result;
509 }
510 
511 bool QgsStyle::renameColorRamp( const QString &oldName, const QString &newName )
512 {
513  if ( mColorRamps.contains( newName ) )
514  {
515  QgsDebugMsg( QStringLiteral( "Color ramp of new name already exists." ) );
516  return false;
517  }
518 
519  QgsColorRamp *ramp = mColorRamps.take( oldName );
520  if ( !ramp )
521  return false;
522 
523  mColorRamps.insert( newName, ramp );
524  mCachedColorRampTags.remove( oldName );
525 
526  int rampid = 0;
528  auto query = QgsSqlite3Mprintf( "SELECT id FROM colorramp WHERE name='%q'", oldName.toUtf8().constData() );
529  int nErr;
530  statement = mCurrentDB.prepare( query, nErr );
531  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
532  {
533  rampid = sqlite3_column_int( statement.get(), 0 );
534  }
535  const bool result = rename( ColorrampEntity, rampid, newName );
536  if ( result )
537  emit rampRenamed( oldName, newName );
538 
539  return result;
540 }
541 
542 QStringList QgsStyle::symbolsOfFavorite( StyleEntity type ) const
543 {
544  if ( !mCurrentDB )
545  {
546  QgsDebugMsg( QStringLiteral( "Cannot Open database for getting favorite symbols" ) );
547  return QStringList();
548  }
549 
550  QString query;
551  if ( type == SymbolEntity )
552  {
553  query = QgsSqlite3Mprintf( "SELECT name FROM symbol WHERE favorite=1" );
554  }
555  else if ( type == ColorrampEntity )
556  {
557  query = QgsSqlite3Mprintf( "SELECT name FROM colorramp WHERE favorite=1" );
558  }
559  else
560  {
561  QgsDebugMsg( QStringLiteral( "No such style entity" ) );
562  return QStringList();
563  }
564 
565  int nErr;
567  statement = mCurrentDB.prepare( query, nErr );
568 
569  QStringList symbols;
570  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
571  {
572  symbols << statement.columnAsText( 0 );
573  }
574 
575  return symbols;
576 }
577 
578 QStringList QgsStyle::symbolsWithTag( StyleEntity type, int tagid ) const
579 {
580  if ( !mCurrentDB )
581  {
582  QgsDebugMsg( QStringLiteral( "Cannot open database to get symbols of tagid %1" ).arg( tagid ) );
583  return QStringList();
584  }
585 
586  QString subquery;;
587  if ( type == SymbolEntity )
588  {
589  subquery = QgsSqlite3Mprintf( "SELECT symbol_id FROM tagmap WHERE tag_id=%d", tagid );
590  }
591  else if ( type == ColorrampEntity )
592  {
593  subquery = QgsSqlite3Mprintf( "SELECT colorramp_id FROM ctagmap WHERE tag_id=%d", tagid );
594  }
595  else
596  {
597  QgsDebugMsg( QStringLiteral( "Unknown Entity" ) );
598  return QStringList();
599  }
600 
601  int nErr;
603  statement = mCurrentDB.prepare( subquery, nErr );
604 
605  // get the symbol <-> tag connection from table 'tagmap'/'ctagmap'
606  QStringList symbols;
607  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
608  {
609  int id = sqlite3_column_int( statement.get(), 0 );
610 
611  auto query = type == SymbolEntity
612  ? QgsSqlite3Mprintf( "SELECT name FROM symbol WHERE id=%d", id )
613  : QgsSqlite3Mprintf( "SELECT name FROM colorramp WHERE id=%d", id );
614 
615  int rc;
616  sqlite3_statement_unique_ptr statement2;
617  statement2 = mCurrentDB.prepare( query, rc );
618  while ( rc == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
619  {
620  symbols << statement2.columnAsText( 0 );
621  }
622  }
623 
624  return symbols;
625 }
626 
627 int QgsStyle::addTag( const QString &tagname )
628 {
629  if ( !mCurrentDB )
630  return 0;
632 
633  auto query = QgsSqlite3Mprintf( "INSERT INTO tag VALUES (NULL, '%q')", tagname.toUtf8().constData() );
634  int nErr;
635  statement = mCurrentDB.prepare( query, nErr );
636  if ( nErr == SQLITE_OK )
637  ( void )sqlite3_step( statement.get() );
638 
639  QgsSettings settings;
640  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
641 
642  emit groupsModified();
643 
644  return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
645 }
646 
647 QStringList QgsStyle::tags() const
648 {
649  if ( !mCurrentDB )
650  return QStringList();
651 
653 
654  auto query = QgsSqlite3Mprintf( "SELECT name FROM tag" );
655  int nError;
656  statement = mCurrentDB.prepare( query, nError );
657 
658  QStringList tagList;
659  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
660  {
661  tagList << statement.columnAsText( 0 );
662  }
663 
664  return tagList;
665 }
666 
667 bool QgsStyle::rename( StyleEntity type, int id, const QString &newName )
668 {
669  QString query;
670  switch ( type )
671  {
672  case SymbolEntity:
673  query = QgsSqlite3Mprintf( "UPDATE symbol SET name='%q' WHERE id=%d", newName.toUtf8().constData(), id );
674  break;
675  case ColorrampEntity:
676  query = QgsSqlite3Mprintf( "UPDATE colorramp SET name='%q' WHERE id=%d", newName.toUtf8().constData(), id );
677  break;
678  case TagEntity:
679  query = QgsSqlite3Mprintf( "UPDATE tag SET name='%q' WHERE id=%d", newName.toUtf8().constData(), id );
680  break;
681  case SmartgroupEntity:
682  query = QgsSqlite3Mprintf( "UPDATE smartgroup SET name='%q' WHERE id=%d", newName.toUtf8().constData(), id );
683  break;
684  }
685  const bool result = runEmptyQuery( query );
686  if ( !result )
687  {
688  mErrorString = QStringLiteral( "Could not rename!" );
689  }
690  else
691  {
692  mCachedColorRampTags.clear();
693  mCachedSymbolTags.clear();
694 
695  switch ( type )
696  {
697  case TagEntity:
698  {
699  emit groupsModified();
700  break;
701  }
702 
703  case SmartgroupEntity:
704  {
705  emit groupsModified();
706  break;
707  }
708 
709  case ColorrampEntity:
710  case SymbolEntity:
711  break;
712  }
713  }
714  return result;
715 }
716 
717 bool QgsStyle::remove( StyleEntity type, int id )
718 {
719  bool groupRemoved = false;
720  QString query;
721  switch ( type )
722  {
723  case SymbolEntity:
724  query = QgsSqlite3Mprintf( "DELETE FROM symbol WHERE id=%d; DELETE FROM tagmap WHERE symbol_id=%d", id, id );
725  break;
726  case ColorrampEntity:
727  query = QgsSqlite3Mprintf( "DELETE FROM colorramp WHERE id=%d", id );
728  break;
729  case TagEntity:
730  query = QgsSqlite3Mprintf( "DELETE FROM tag WHERE id=%d; DELETE FROM tagmap WHERE tag_id=%d", id, id );
731  groupRemoved = true;
732  break;
733  case SmartgroupEntity:
734  query = QgsSqlite3Mprintf( "DELETE FROM smartgroup WHERE id=%d", id );
735  groupRemoved = true;
736  break;
737  }
738 
739  bool result = false;
740  if ( !runEmptyQuery( query ) )
741  {
742  QgsDebugMsg( QStringLiteral( "Could not delete entity!" ) );
743  }
744  else
745  {
746  mCachedColorRampTags.clear();
747  mCachedSymbolTags.clear();
748 
749  if ( groupRemoved )
750  {
751  QgsSettings settings;
752  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
753 
754  emit groupsModified();
755  }
756  result = true;
757  }
758  return result;
759 }
760 
761 bool QgsStyle::runEmptyQuery( const QString &query )
762 {
763  if ( !mCurrentDB )
764  return false;
765 
766  char *zErr = nullptr;
767  int nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
768 
769  if ( nErr != SQLITE_OK )
770  {
771  QgsDebugMsg( zErr );
772  sqlite3_free( zErr );
773  }
774 
775  return nErr == SQLITE_OK;
776 }
777 
778 bool QgsStyle::addFavorite( StyleEntity type, const QString &name )
779 {
780  QString query;
781 
782  switch ( type )
783  {
784  case SymbolEntity:
785  query = QgsSqlite3Mprintf( "UPDATE symbol SET favorite=1 WHERE name='%q'", name.toUtf8().constData() );
786  break;
787  case ColorrampEntity:
788  query = QgsSqlite3Mprintf( "UPDATE colorramp SET favorite=1 WHERE name='%q'", name.toUtf8().constData() );
789  break;
790 
791  default:
792  QgsDebugMsg( QStringLiteral( "Wrong entity value. cannot apply group" ) );
793  return false;
794  }
795 
796  const bool res = runEmptyQuery( query );
797  if ( res )
798  emit favoritedChanged( type, name, true );
799 
800  return res;
801 }
802 
803 bool QgsStyle::removeFavorite( StyleEntity type, const QString &name )
804 {
805  QString query;
806 
807  switch ( type )
808  {
809  case SymbolEntity:
810  query = QgsSqlite3Mprintf( "UPDATE symbol SET favorite=0 WHERE name='%q'", name.toUtf8().constData() );
811  break;
812  case ColorrampEntity:
813  query = QgsSqlite3Mprintf( "UPDATE colorramp SET favorite=0 WHERE name='%q'", name.toUtf8().constData() );
814  break;
815 
816  default:
817  QgsDebugMsg( QStringLiteral( "Wrong entity value. cannot apply group" ) );
818  return false;
819  }
820 
821  const bool res = runEmptyQuery( query );
822  if ( res )
823  emit favoritedChanged( type, name, false );
824 
825  return res;
826 }
827 
828 QStringList QgsStyle::findSymbols( StyleEntity type, const QString &qword )
829 {
830  if ( !mCurrentDB )
831  {
832  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to search" ) );
833  return QStringList();
834  }
835 
836  // first find symbols with matching name
837  QString item = ( type == SymbolEntity ) ? QStringLiteral( "symbol" ) : QStringLiteral( "colorramp" );
838  auto query = QgsSqlite3Mprintf( "SELECT name FROM %q WHERE name LIKE '%%%q%%'",
839  item.toUtf8().constData(), qword.toUtf8().constData() );
840 
842  int nErr; statement = mCurrentDB.prepare( query, nErr );
843 
844  QSet< QString > symbols;
845  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
846  {
847  symbols << statement.columnAsText( 0 );
848  }
849 
850  // next add symbols with matching tags
851  query = QgsSqlite3Mprintf( "SELECT id FROM tag WHERE name LIKE '%%%q%%'", qword.toUtf8().constData() );
852  statement = mCurrentDB.prepare( query, nErr );
853 
854  QStringList tagids;
855  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
856  {
857  tagids << statement.columnAsText( 0 );
858  }
859 
860  QString dummy = tagids.join( QStringLiteral( ", " ) );
861 
862  if ( type == SymbolEntity )
863  {
864  query = QgsSqlite3Mprintf( "SELECT symbol_id FROM tagmap WHERE tag_id IN (%q)",
865  dummy.toUtf8().constData() );
866  }
867  else
868  {
869  query = QgsSqlite3Mprintf( "SELECT colorramp_id FROM ctagmap WHERE tag_id IN (%q)",
870  dummy.toUtf8().constData() );
871  }
872  statement = mCurrentDB.prepare( query, nErr );
873 
874  QStringList symbolids;
875  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
876  {
877  symbolids << statement.columnAsText( 0 );
878  }
879 
880  dummy = symbolids.join( QStringLiteral( ", " ) );
881  query = QgsSqlite3Mprintf( "SELECT name FROM %q WHERE id IN (%q)",
882  item.toUtf8().constData(), dummy.toUtf8().constData() );
883  statement = mCurrentDB.prepare( query, nErr );
884  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
885  {
886  symbols << statement.columnAsText( 0 );
887  }
888 
889  return symbols.toList();
890 }
891 
892 bool QgsStyle::tagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
893 {
894  if ( !mCurrentDB )
895  {
896  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database to tag." ) );
897  return false;
898  }
899 
900  int symbolid = type == SymbolEntity ? symbolId( symbol ) : colorrampId( symbol );
901  if ( !symbolid )
902  {
903  QgsDebugMsg( QStringLiteral( "No such symbol for tagging in database: " ) + symbol );
904  return false;
905  }
906 
907  QString tag;
908  const auto constTags = tags;
909  for ( const QString &t : constTags )
910  {
911  tag = t.trimmed();
912  if ( !tag.isEmpty() )
913  {
914  // sql: gets the id of the tag if present or insert the tag and get the id of the tag
915  int tagid( tagId( tag ) );
916  if ( ! tagid )
917  {
918  tagid = addTag( tag );
919  }
920 
921  // Now map the tag to the symbol if it's not already tagged
922  if ( !symbolHasTag( type, symbol, tag ) )
923  {
924  auto query = type == SymbolEntity
925  ? QgsSqlite3Mprintf( "INSERT INTO tagmap VALUES (%d,%d)", tagid, symbolid )
926  : QgsSqlite3Mprintf( "INSERT INTO ctagmap VALUES (%d,%d)", tagid, symbolid );
927 
928  char *zErr = nullptr;
929  int nErr;
930  nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
931  if ( nErr )
932  {
933  QgsDebugMsg( zErr );
934  sqlite3_free( zErr );
935  }
936  }
937  }
938  }
939 
940  clearCachedTags( type, symbol );
941  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
942 
943  return true;
944 }
945 
946 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
947 {
948  if ( !mCurrentDB )
949  {
950  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detgging." ) );
951  return false;
952  }
953 
954  auto query = type == SymbolEntity
955  ? QgsSqlite3Mprintf( "SELECT id FROM symbol WHERE name='%q'", symbol.toUtf8().constData() )
956  : QgsSqlite3Mprintf( "SELECT id FROM colorramp WHERE name='%q'", symbol.toUtf8().constData() );
958  int nErr; statement = mCurrentDB.prepare( query, nErr );
959 
960  int symbolid = 0;
961  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
962  {
963  symbolid = sqlite3_column_int( statement.get(), 0 );
964  }
965  else
966  {
967  return false;
968  }
969 
970  const auto constTags = tags;
971  for ( const QString &tag : constTags )
972  {
973  query = QgsSqlite3Mprintf( "SELECT id FROM tag WHERE name='%q'", tag.toUtf8().constData() );
974 
975  sqlite3_statement_unique_ptr statement2;
976  statement2 = mCurrentDB.prepare( query, nErr );
977 
978  int tagid = 0;
979  if ( nErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
980  {
981  tagid = sqlite3_column_int( statement2.get(), 0 );
982  }
983 
984  if ( tagid )
985  {
986  // remove from the tagmap
987  query = type == SymbolEntity
988  ? QgsSqlite3Mprintf( "DELETE FROM tagmap WHERE tag_id=%d AND symbol_id=%d", tagid, symbolid )
989  : QgsSqlite3Mprintf( "DELETE FROM ctagmap WHERE tag_id=%d AND colorramp_id=%d", tagid, symbolid );
990  runEmptyQuery( query );
991  }
992  }
993 
994  clearCachedTags( type, symbol );
995  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
996 
997  // TODO Perform tag cleanup
998  // check the number of entries for a given tag in the tagmap
999  // if the count is 0, then remove( TagEntity, tagid )
1000  return true;
1001 }
1002 
1003 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol )
1004 {
1005  if ( !mCurrentDB )
1006  {
1007  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1008  return false;
1009  }
1010 
1011  auto query = type == SymbolEntity
1012  ? QgsSqlite3Mprintf( "SELECT id FROM symbol WHERE name='%q'", symbol.toUtf8().constData() )
1013  : QgsSqlite3Mprintf( "SELECT id FROM colorramp WHERE name='%q'", symbol.toUtf8().constData() );
1014  sqlite3_statement_unique_ptr statement;
1015  int nErr;
1016  statement = mCurrentDB.prepare( query, nErr );
1017 
1018  int symbolid = 0;
1019  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1020  {
1021  symbolid = sqlite3_column_int( statement.get(), 0 );
1022  }
1023  else
1024  {
1025  return false;
1026  }
1027 
1028  // remove all tags
1029  query = type == SymbolEntity
1030  ? QgsSqlite3Mprintf( "DELETE FROM tagmap WHERE symbol_id=%d", symbolid )
1031  : QgsSqlite3Mprintf( "DELETE FROM ctagmap WHERE colorramp_id=%d", symbolid );
1032  runEmptyQuery( query );
1033 
1034  clearCachedTags( type, symbol );
1035  emit entityTagsChanged( type, symbol, QStringList() );
1036 
1037  // TODO Perform tag cleanup
1038  // check the number of entries for a given tag in the tagmap
1039  // if the count is 0, then remove( TagEntity, tagid )
1040  return true;
1041 }
1042 
1043 QStringList QgsStyle::tagsOfSymbol( StyleEntity type, const QString &symbol )
1044 {
1045  switch ( type )
1046  {
1047  case SymbolEntity:
1048  if ( mCachedSymbolTags.contains( symbol ) )
1049  return mCachedSymbolTags.value( symbol );
1050  break;
1051 
1052  case ColorrampEntity:
1053  if ( mCachedColorRampTags.contains( symbol ) )
1054  return mCachedColorRampTags.value( symbol );
1055  break;
1056 
1057  case TagEntity:
1058  case SmartgroupEntity:
1059  break;
1060  }
1061 
1062  if ( !mCurrentDB )
1063  {
1064  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1065  return QStringList();
1066  }
1067 
1068  int symbolid = type == SymbolEntity ? symbolId( symbol ) : colorrampId( symbol );
1069  if ( !symbolid )
1070  return QStringList();
1071 
1072  // get the ids of tags for the symbol
1073  auto query = type == SymbolEntity
1074  ? QgsSqlite3Mprintf( "SELECT tag_id FROM tagmap WHERE symbol_id=%d", symbolid )
1075  : QgsSqlite3Mprintf( "SELECT tag_id FROM ctagmap WHERE colorramp_id=%d", symbolid );
1076 
1077  sqlite3_statement_unique_ptr statement;
1078  int nErr; statement = mCurrentDB.prepare( query, nErr );
1079 
1080  QStringList tagList;
1081  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1082  {
1083  auto subquery = QgsSqlite3Mprintf( "SELECT name FROM tag WHERE id=%d", sqlite3_column_int( statement.get(), 0 ) );
1084 
1085  sqlite3_statement_unique_ptr statement2;
1086  int pErr;
1087  statement2 = mCurrentDB.prepare( subquery, pErr );
1088  if ( pErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1089  {
1090  tagList << statement2.columnAsText( 0 );
1091  }
1092  }
1093 
1094  // update cache
1095  switch ( type )
1096  {
1097  case SymbolEntity:
1098  mCachedSymbolTags[ symbol ] = tagList;
1099  break;
1100 
1101  case ColorrampEntity:
1102  mCachedColorRampTags[ symbol ] = tagList;
1103  break;
1104 
1105  case TagEntity:
1106  case SmartgroupEntity:
1107  break;
1108  }
1109 
1110  return tagList;
1111 }
1112 
1113 bool QgsStyle::symbolHasTag( StyleEntity type, const QString &symbol, const QString &tag )
1114 {
1115  if ( !mCurrentDB )
1116  {
1117  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1118  return false;
1119  }
1120 
1121  int symbolid = type == SymbolEntity ? symbolId( symbol ) : colorrampId( symbol );
1122  if ( !symbolid )
1123  {
1124  return false;
1125  }
1126  int tagid = tagId( tag );
1127  if ( !tagid )
1128  {
1129  return false;
1130  }
1131 
1132  // get the ids of tags for the symbol
1133  auto query = type == SymbolEntity
1134  ? QgsSqlite3Mprintf( "SELECT tag_id FROM tagmap WHERE tag_id=%d AND symbol_id=%d", tagid, symbolid )
1135  : QgsSqlite3Mprintf( "SELECT tag_id FROM ctagmap WHERE tag_id=%d AND colorramp_id=%d", tagid, symbolid );
1136 
1137  sqlite3_statement_unique_ptr statement;
1138  int nErr; statement = mCurrentDB.prepare( query, nErr );
1139 
1140  return ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW );
1141 }
1142 
1143 QString QgsStyle::tag( int id ) const
1144 {
1145  if ( !mCurrentDB )
1146  return QString();
1147 
1148  sqlite3_statement_unique_ptr statement;
1149 
1150  auto query = QgsSqlite3Mprintf( "SELECT name FROM tag WHERE id=%d", id );
1151  int nError;
1152  statement = mCurrentDB.prepare( query, nError );
1153 
1154  QString tag;
1155  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1156  {
1157  tag = statement.columnAsText( 0 );
1158  }
1159 
1160  return tag;
1161 }
1162 
1163 int QgsStyle::getId( const QString &table, const QString &name )
1164 {
1165  QString lowerName( name.toLower() );
1166  auto query = QgsSqlite3Mprintf( "SELECT id FROM %q WHERE LOWER(name)='%q'", table.toUtf8().constData(), lowerName.toUtf8().constData() );
1167 
1168  sqlite3_statement_unique_ptr statement;
1169  int nErr; statement = mCurrentDB.prepare( query, nErr );
1170 
1171  int id = 0;
1172  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1173  {
1174  id = sqlite3_column_int( statement.get(), 0 );
1175  }
1176  else
1177  {
1178  // Try the name without lowercase conversion
1179  auto query = QgsSqlite3Mprintf( "SELECT id FROM %q WHERE name='%q'", table.toUtf8().constData(), name.toUtf8().constData() );
1180 
1181  sqlite3_statement_unique_ptr statement;
1182  int nErr; statement = mCurrentDB.prepare( query, nErr );
1183  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1184  {
1185  id = sqlite3_column_int( statement.get(), 0 );
1186  }
1187  }
1188 
1189  return id;
1190 }
1191 
1192 QString QgsStyle::getName( const QString &table, int id ) const
1193 {
1194  auto query = QgsSqlite3Mprintf( "SELECT name FROM %q WHERE id='%q'", table.toUtf8().constData(), QString::number( id ).toUtf8().constData() );
1195 
1196  sqlite3_statement_unique_ptr statement;
1197  int nErr; statement = mCurrentDB.prepare( query, nErr );
1198 
1199  QString name;
1200  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1201  {
1202  name = statement.columnAsText( 0 );
1203  }
1204 
1205  return name;
1206 }
1207 
1208 int QgsStyle::symbolId( const QString &name )
1209 {
1210  return getId( QStringLiteral( "symbol" ), name );
1211 }
1212 
1213 int QgsStyle::colorrampId( const QString &name )
1214 {
1215  return getId( QStringLiteral( "colorramp" ), name );
1216 }
1217 
1218 int QgsStyle::tagId( const QString &name )
1219 {
1220  return getId( QStringLiteral( "tag" ), name );
1221 }
1222 
1223 int QgsStyle::smartgroupId( const QString &name )
1224 {
1225  return getId( QStringLiteral( "smartgroup" ), name );
1226 }
1227 
1228 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions )
1229 {
1230  return addSmartgroup( name, op, conditions.values( QStringLiteral( "tag" ) ),
1231  conditions.values( QStringLiteral( "!tag" ) ),
1232  conditions.values( QStringLiteral( "name" ) ),
1233  conditions.values( QStringLiteral( "!name" ) ) );
1234 }
1235 
1236 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag, const QStringList &matchName, const QStringList &noMatchName )
1237 {
1238  QDomDocument doc( QStringLiteral( "dummy" ) );
1239  QDomElement smartEl = doc.createElement( QStringLiteral( "smartgroup" ) );
1240  smartEl.setAttribute( QStringLiteral( "name" ), name );
1241  smartEl.setAttribute( QStringLiteral( "operator" ), op );
1242 
1243  auto addCondition = [&doc, &smartEl]( const QString & constraint, const QStringList & parameters )
1244  {
1245  for ( const QString &param : parameters )
1246  {
1247  QDomElement condEl = doc.createElement( QStringLiteral( "condition" ) );
1248  condEl.setAttribute( QStringLiteral( "constraint" ), constraint );
1249  condEl.setAttribute( QStringLiteral( "param" ), param );
1250  smartEl.appendChild( condEl );
1251  }
1252  };
1253  addCondition( QStringLiteral( "tag" ), matchTag );
1254  addCondition( QStringLiteral( "!tag" ), noMatchTag );
1255  addCondition( QStringLiteral( "name" ), matchName );
1256  addCondition( QStringLiteral( "!name" ), noMatchName );
1257 
1258  QByteArray xmlArray;
1259  QTextStream stream( &xmlArray );
1260  stream.setCodec( "UTF-8" );
1261  smartEl.save( stream, 4 );
1262  auto query = QgsSqlite3Mprintf( "INSERT INTO smartgroup VALUES (NULL, '%q', '%q')",
1263  name.toUtf8().constData(), xmlArray.constData() );
1264 
1265  if ( runEmptyQuery( query ) )
1266  {
1267  QgsSettings settings;
1268  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1269 
1270  emit groupsModified();
1271  return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
1272  }
1273  else
1274  {
1275  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
1276  return 0;
1277  }
1278 }
1279 
1281 {
1282  if ( !mCurrentDB )
1283  {
1284  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1285  return QgsSymbolGroupMap();
1286  }
1287 
1288  auto query = QgsSqlite3Mprintf( "SELECT * FROM smartgroup" );
1289 
1290  // Now run the query and retrieve the group names
1291  sqlite3_statement_unique_ptr statement;
1292  int nError;
1293  statement = mCurrentDB.prepare( query, nError );
1294 
1295  QgsSymbolGroupMap groupNames;
1296  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1297  {
1298  QString group = statement.columnAsText( SmartgroupName );
1299  groupNames.insert( sqlite3_column_int( statement.get(), SmartgroupId ), group );
1300  }
1301 
1302  return groupNames;
1303 }
1304 
1306 {
1307  if ( !mCurrentDB )
1308  {
1309  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1310  return QStringList();
1311  }
1312 
1313  auto query = QgsSqlite3Mprintf( "SELECT name FROM smartgroup" );
1314 
1315  // Now run the query and retrieve the group names
1316  sqlite3_statement_unique_ptr statement;
1317  int nError;
1318  statement = mCurrentDB.prepare( query, nError );
1319 
1320  QStringList groups;
1321  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1322  {
1323  groups << statement.columnAsText( 0 );
1324  }
1325 
1326  return groups;
1327 }
1328 
1329 QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
1330 {
1331  QStringList symbols;
1332 
1333  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1334 
1335  sqlite3_statement_unique_ptr statement;
1336  int nErr; statement = mCurrentDB.prepare( query, nErr );
1337  if ( !( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW ) )
1338  {
1339  return QStringList();
1340  }
1341  else
1342  {
1343  QDomDocument doc;
1344  QString xmlstr = statement.columnAsText( 0 );
1345  if ( !doc.setContent( xmlstr ) )
1346  {
1347  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1348  }
1349  QDomElement smartEl = doc.documentElement();
1350  QString op = smartEl.attribute( QStringLiteral( "operator" ) );
1351  QDomNodeList conditionNodes = smartEl.childNodes();
1352 
1353  bool firstSet = true;
1354  for ( int i = 0; i < conditionNodes.count(); i++ )
1355  {
1356  QDomElement condEl = conditionNodes.at( i ).toElement();
1357  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
1358  QString param = condEl.attribute( QStringLiteral( "param" ) );
1359 
1360  QStringList resultNames;
1361  // perform suitable action for the given constraint
1362  if ( constraint == QLatin1String( "tag" ) )
1363  {
1364  resultNames = symbolsWithTag( type, tagId( param ) );
1365  }
1366  else if ( constraint == QLatin1String( "name" ) )
1367  {
1368  if ( type == SymbolEntity )
1369  {
1370  resultNames = symbolNames().filter( param, Qt::CaseInsensitive );
1371  }
1372  else
1373  {
1374  resultNames = colorRampNames().filter( param, Qt::CaseInsensitive );
1375  }
1376  }
1377  else if ( constraint == QLatin1String( "!tag" ) )
1378  {
1379  resultNames = type == SymbolEntity ? symbolNames() : colorRampNames();
1380  const QStringList unwanted = symbolsWithTag( type, tagId( param ) );
1381  for ( const QString &name : unwanted )
1382  {
1383  resultNames.removeAll( name );
1384  }
1385  }
1386  else if ( constraint == QLatin1String( "!name" ) )
1387  {
1388  const QStringList all = type == SymbolEntity ? symbolNames() : colorRampNames();
1389  for ( const QString &str : all )
1390  {
1391  if ( !str.contains( param, Qt::CaseInsensitive ) )
1392  resultNames << str;
1393  }
1394  }
1395 
1396  // not apply the operator
1397  if ( firstSet )
1398  {
1399  symbols = resultNames;
1400  firstSet = false;
1401  }
1402  else
1403  {
1404  if ( op == QLatin1String( "OR" ) )
1405  {
1406  symbols << resultNames;
1407  }
1408  else if ( op == QLatin1String( "AND" ) )
1409  {
1410  QStringList dummy = symbols;
1411  symbols.clear();
1412  for ( const QString &result : qgis::as_const( resultNames ) )
1413  {
1414  if ( dummy.contains( result ) )
1415  symbols << result;
1416  }
1417  }
1418  }
1419  } // DOM loop ends here
1420  }
1421 
1422  // return sorted, unique list
1423  QStringList unique = symbols.toSet().toList();
1424  std::sort( unique.begin(), unique.end() );
1425  return unique;
1426 }
1427 
1429 {
1430  if ( !mCurrentDB )
1431  {
1432  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1433  return QgsSmartConditionMap();
1434  }
1435 
1436  QgsSmartConditionMap condition;
1437 
1438  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1439 
1440  sqlite3_statement_unique_ptr statement;
1441  int nError;
1442  statement = mCurrentDB.prepare( query, nError );
1443  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1444  {
1445  QDomDocument doc;
1446  QString xmlstr = statement.columnAsText( 0 );
1447  if ( !doc.setContent( xmlstr ) )
1448  {
1449  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1450  }
1451 
1452  QDomElement smartEl = doc.documentElement();
1453  QDomNodeList conditionNodes = smartEl.childNodes();
1454 
1455  for ( int i = 0; i < conditionNodes.count(); i++ )
1456  {
1457  QDomElement condEl = conditionNodes.at( i ).toElement();
1458  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
1459  QString param = condEl.attribute( QStringLiteral( "param" ) );
1460 
1461  condition.insert( constraint, param );
1462  }
1463  }
1464 
1465  return condition;
1466 }
1467 
1469 {
1470  if ( !mCurrentDB )
1471  {
1472  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1473  return QString();
1474  }
1475 
1476  QString op;
1477 
1478  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1479 
1480  int nError;
1481  sqlite3_statement_unique_ptr statement;
1482  statement = mCurrentDB.prepare( query, nError );
1483  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1484  {
1485  QDomDocument doc;
1486  QString xmlstr = statement.columnAsText( 0 );
1487  if ( !doc.setContent( xmlstr ) )
1488  {
1489  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1490  }
1491  QDomElement smartEl = doc.documentElement();
1492  op = smartEl.attribute( QStringLiteral( "operator" ) );
1493  }
1494 
1495  return op;
1496 }
1497 
1498 bool QgsStyle::exportXml( const QString &filename )
1499 {
1500  if ( filename.isEmpty() )
1501  {
1502  QgsDebugMsg( QStringLiteral( "Invalid filename for style export." ) );
1503  return false;
1504  }
1505 
1506  QDomDocument doc( QStringLiteral( "qgis_style" ) );
1507  QDomElement root = doc.createElement( QStringLiteral( "qgis_style" ) );
1508  root.setAttribute( QStringLiteral( "version" ), QStringLiteral( STYLE_CURRENT_VERSION ) );
1509  doc.appendChild( root );
1510 
1511  QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
1512  QStringList favoriteColorramps = symbolsOfFavorite( ColorrampEntity );
1513 
1514  // save symbols and attach tags
1515  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, QStringLiteral( "symbols" ), doc, QgsReadWriteContext() );
1516  QDomNodeList symbolsList = symbolsElem.elementsByTagName( QStringLiteral( "symbol" ) );
1517  int nbSymbols = symbolsList.count();
1518  for ( int i = 0; i < nbSymbols; ++i )
1519  {
1520  QDomElement symbol = symbolsList.at( i ).toElement();
1521  QString name = symbol.attribute( QStringLiteral( "name" ) );
1522  QStringList tags = tagsOfSymbol( SymbolEntity, name );
1523  if ( tags.count() > 0 )
1524  {
1525  symbol.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
1526  }
1527  if ( favoriteSymbols.contains( name ) )
1528  {
1529  symbol.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
1530  }
1531  }
1532 
1533  // save color ramps
1534  QDomElement rampsElem = doc.createElement( QStringLiteral( "colorramps" ) );
1535  for ( QMap<QString, QgsColorRamp *>::const_iterator itr = mColorRamps.constBegin(); itr != mColorRamps.constEnd(); ++itr )
1536  {
1537  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
1538  QStringList tags = tagsOfSymbol( ColorrampEntity, itr.key() );
1539  if ( tags.count() > 0 )
1540  {
1541  rampEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
1542  }
1543  if ( favoriteColorramps.contains( itr.key() ) )
1544  {
1545  rampEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
1546  }
1547  rampsElem.appendChild( rampEl );
1548  }
1549 
1550  root.appendChild( symbolsElem );
1551  root.appendChild( rampsElem );
1552 
1553  // save
1554  QFile f( filename );
1555  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
1556  {
1557  mErrorString = "Couldn't open file for writing: " + filename;
1558  return false;
1559  }
1560 
1561  QTextStream ts( &f );
1562  ts.setCodec( "UTF-8" );
1563  doc.save( ts, 2 );
1564  f.close();
1565 
1566  mFileName = filename;
1567  return true;
1568 }
1569 
1570 bool QgsStyle::importXml( const QString &filename )
1571 {
1572  mErrorString = QString();
1573  QDomDocument doc( QStringLiteral( "style" ) );
1574  QFile f( filename );
1575  if ( !f.open( QFile::ReadOnly ) )
1576  {
1577  mErrorString = QStringLiteral( "Unable to open the specified file" );
1578  QgsDebugMsg( QStringLiteral( "Error opening the style XML file." ) );
1579  return false;
1580  }
1581 
1582  if ( !doc.setContent( &f ) )
1583  {
1584  mErrorString = QStringLiteral( "Unable to understand the style file: %1" ).arg( filename );
1585  QgsDebugMsg( QStringLiteral( "XML Parsing error" ) );
1586  f.close();
1587  return false;
1588  }
1589  f.close();
1590 
1591  QDomElement docEl = doc.documentElement();
1592  if ( docEl.tagName() != QLatin1String( "qgis_style" ) )
1593  {
1594  mErrorString = "Incorrect root tag in style: " + docEl.tagName();
1595  return false;
1596  }
1597 
1598  QString version = docEl.attribute( QStringLiteral( "version" ) );
1599  if ( version != QLatin1String( STYLE_CURRENT_VERSION ) && version != QLatin1String( "0" ) )
1600  {
1601  mErrorString = "Unknown style file version: " + version;
1602  return false;
1603  }
1604 
1605  QgsSymbolMap symbols;
1606 
1607  QDomElement symbolsElement = docEl.firstChildElement( QStringLiteral( "symbols" ) );
1608  QDomElement e = symbolsElement.firstChildElement();
1609 
1610  // gain speed by re-grouping the INSERT statements in a transaction
1611  auto query = QgsSqlite3Mprintf( "BEGIN TRANSACTION;" );
1612  runEmptyQuery( query );
1613 
1614  if ( version == QLatin1String( STYLE_CURRENT_VERSION ) )
1615  {
1616  // For the new style, load symbols individually
1617  while ( !e.isNull() )
1618  {
1619  if ( e.tagName() == QLatin1String( "symbol" ) )
1620  {
1621  QString name = e.attribute( QStringLiteral( "name" ) );
1622  QStringList tags;
1623  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
1624  {
1625  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
1626  }
1627  bool favorite = false;
1628  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QStringLiteral( "1" ) )
1629  {
1630  favorite = true;
1631  }
1632 
1634  if ( symbol )
1635  {
1636  addSymbol( name, symbol );
1637  if ( mCurrentDB )
1638  {
1639  saveSymbol( name, symbol, favorite, tags );
1640  }
1641  }
1642  }
1643  else
1644  {
1645  QgsDebugMsg( "unknown tag: " + e.tagName() );
1646  }
1647  e = e.nextSiblingElement();
1648  }
1649  }
1650  else
1651  {
1652  // for the old version, use the utility function to solve @symbol@layer subsymbols
1653  symbols = QgsSymbolLayerUtils::loadSymbols( symbolsElement, QgsReadWriteContext() );
1654 
1655  // save the symbols with proper name
1656  for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
1657  {
1658  addSymbol( it.key(), it.value() );
1659  }
1660  }
1661 
1662  // load color ramps
1663  QDomElement rampsElement = docEl.firstChildElement( QStringLiteral( "colorramps" ) );
1664  e = rampsElement.firstChildElement();
1665  while ( !e.isNull() )
1666  {
1667  if ( e.tagName() == QLatin1String( "colorramp" ) )
1668  {
1669  QString name = e.attribute( QStringLiteral( "name" ) );
1670  QStringList tags;
1671  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
1672  {
1673  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
1674  }
1675  bool favorite = false;
1676  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QStringLiteral( "1" ) )
1677  {
1678  favorite = true;
1679  }
1680 
1682  if ( ramp )
1683  {
1684  addColorRamp( name, ramp );
1685  if ( mCurrentDB )
1686  {
1687  saveColorRamp( name, ramp, favorite, tags );
1688  }
1689  }
1690  }
1691  else
1692  {
1693  QgsDebugMsg( "unknown tag: " + e.tagName() );
1694  }
1695  e = e.nextSiblingElement();
1696  }
1697 
1698  query = QgsSqlite3Mprintf( "COMMIT TRANSACTION;" );
1699  runEmptyQuery( query );
1700 
1701  mFileName = filename;
1702  return true;
1703 }
1704 
1705 bool QgsStyle::isXmlStyleFile( const QString &path )
1706 {
1707  QFileInfo fileInfo( path );
1708 
1709  if ( fileInfo.suffix().compare( QLatin1String( "xml" ), Qt::CaseInsensitive ) != 0 )
1710  return false;
1711 
1712  // sniff the first line of the file to see if it's a style file
1713  if ( !QFile::exists( path ) )
1714  return false;
1715 
1716  QFile inputFile( path );
1717  if ( !inputFile.open( QIODevice::ReadOnly ) )
1718  return false;
1719 
1720  QTextStream stream( &inputFile );
1721  const QString line = stream.readLine();
1722  return line == QLatin1String( "<!DOCTYPE qgis_style>" );
1723 }
1724 
1725 bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
1726 {
1727  QDomDocument doc( QStringLiteral( "dummy" ) );
1728  QDomElement symEl;
1729  QByteArray xmlArray;
1730  QTextStream stream( &xmlArray );
1731  stream.setCodec( "UTF-8" );
1732 
1733  QString query;
1734 
1735  if ( type == SymbolEntity )
1736  {
1737  // check if it is an existing symbol
1738  if ( !symbolNames().contains( name ) )
1739  {
1740  QgsDebugMsg( QStringLiteral( "Update request received for unavailable symbol" ) );
1741  return false;
1742  }
1743 
1744  symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol( name ), doc, QgsReadWriteContext() );
1745  if ( symEl.isNull() )
1746  {
1747  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
1748  return false;
1749  }
1750  symEl.save( stream, 4 );
1751  query = QgsSqlite3Mprintf( "UPDATE symbol SET xml='%q' WHERE name='%q';",
1752  xmlArray.constData(), name.toUtf8().constData() );
1753  }
1754  else if ( type == ColorrampEntity )
1755  {
1756  if ( !colorRampNames().contains( name ) )
1757  {
1758  QgsDebugMsg( QStringLiteral( "Update requested for unavailable color ramp." ) );
1759  return false;
1760  }
1761 
1762  std::unique_ptr< QgsColorRamp > ramp( colorRamp( name ) );
1763  symEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp.get(), doc );
1764  if ( symEl.isNull() )
1765  {
1766  QgsDebugMsg( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
1767  return false;
1768  }
1769  symEl.save( stream, 4 );
1770  query = QgsSqlite3Mprintf( "UPDATE colorramp SET xml='%q' WHERE name='%q';",
1771  xmlArray.constData(), name.toUtf8().constData() );
1772  }
1773  else
1774  {
1775  QgsDebugMsg( QStringLiteral( "Updating the unsupported StyleEntity" ) );
1776  return false;
1777  }
1778 
1779 
1780  if ( !runEmptyQuery( query ) )
1781  {
1782  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
1783  return false;
1784  }
1785  else
1786  {
1787  switch ( type )
1788  {
1789  case SymbolEntity:
1790  emit symbolChanged( name );
1791  break;
1792 
1793  case ColorrampEntity:
1794  emit rampChanged( name );
1795  break;
1796 
1797  case TagEntity:
1798  case SmartgroupEntity:
1799  break;
1800  }
1801  }
1802  return true;
1803 }
1804 
1805 void QgsStyle::clearCachedTags( QgsStyle::StyleEntity type, const QString &name )
1806 {
1807  switch ( type )
1808  {
1809  case SymbolEntity:
1810  mCachedSymbolTags.remove( name );
1811  break;
1812 
1813  case ColorrampEntity:
1814  mCachedColorRampTags.remove( name );
1815  break;
1816 
1817  case TagEntity:
1818  case SmartgroupEntity:
1819  break;
1820  }
1821 }
bool exportXml(const QString &filename)
Exports the style as a XML file.
Definition: qgsstyle.cpp:1498
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:77
const QgsColorRamp * colorRampRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:278
bool addColorRamp(const QString &name, QgsColorRamp *colorRamp, bool update=false)
Adds a color ramp to the style.
Definition: qgsstyle.cpp:197
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:1113
bool save(QString filename=QString())
Saves style into a file (will use current filename if empty string is passed)
Definition: qgsstyle.cpp:429
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
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:578
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:335
bool load(const QString &filename)
Loads a file into the style.
Definition: qgsstyle.cpp:363
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
Definition: qgsstyle.cpp:1043
Abstract base class for color ramps.
Definition: qgscolorramp.h:31
QMap< int, QString > QgsSymbolGroupMap
Definition: qgsstyle.h:38
bool saveColorRamp(const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags)
Adds the colorramp to the DB.
Definition: qgsstyle.cpp:221
static QDomElement saveColorRamp(const QString &name, QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp&#39;s settings to an XML element.
bool tagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Tags the symbol with the tags in the list.
Definition: qgsstyle.cpp:892
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:272
bool removeColorRamp(const QString &name)
Removes color ramp from style (and delete it)
Definition: qgsstyle.cpp:252
bool rename(StyleEntity type, int id, const QString &newName)
Renames the given entity with the specified id.
Definition: qgsstyle.cpp:667
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:46
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:95
static QString defaultStylePath()
Returns the path to default style (works as a starting point).
QString errorMessage() const
Returns the most recent error message encountered by the database.
int symbolCount()
Returns count of symbols in style.
Definition: qgsstyle.cpp:186
bool renameSymbol(const QString &oldName, const QString &newName)
Renames a symbol from oldName to newName.
Definition: qgsstyle.cpp:475
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:1143
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
Definition: qgsstyle.cpp:1570
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:181
QStringList symbolNames()
Returns a list of names of symbols.
Definition: qgsstyle.cpp:191
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.
bool removeFavorite(StyleEntity type, const QString &name)
Removes the specified symbol from favorites.
Definition: qgsstyle.cpp:803
bool remove(StyleEntity type, int id)
Removes the specified entity from the db.
Definition: qgsstyle.cpp:717
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:828
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:627
static void cleanDefaultStyle()
Deletes the default style. Only to be used by QgsApplication::exitQgis()
Definition: qgsstyle.cpp:71
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:320
QString smartgroupOperator(int id)
Returns the operator for the smartgroup clumsy implementation TODO create a class for smartgroups...
Definition: qgsstyle.cpp:1468
static bool isXmlStyleFile(const QString &path)
Tests if the file at path is a QGIS style XML file.
Definition: qgsstyle.cpp:1705
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 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:1428
QStringList colorRampNames()
Returns a list of names of color ramps.
Definition: qgsstyle.cpp:288
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:1329
int smartgroupId(const QString &smartgroup)
Returns the DB id for the given smartgroup name.
Definition: qgsstyle.cpp:1223
QStringList smartgroupNames()
Returns the smart groups list.
Definition: qgsstyle.cpp:1305
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:1218
bool addSymbol(const QString &name, QgsSymbol *symbol, bool update=false)
Adds a symbol to style and takes symbol&#39;s ownership.
Definition: qgsstyle.cpp:88
QStringList tags() const
Returns a list of all tags in the style database.
Definition: qgsstyle.cpp:647
void rampRenamed(const QString &oldName, const QString &newName)
Emitted whenever a color ramp has been renamed from oldName to newName.
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:1228
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:305
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:1213
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:1208
QMap< QString, QgsSymbol *> QgsSymbolMap
Definition: qgsrenderer.h:44
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
bool renameColorRamp(const QString &oldName, const QString &newName)
Changes ramp&#39;s name.
Definition: qgsstyle.cpp:511
int colorRampCount()
Returns count of color ramps.
Definition: qgsstyle.cpp:283
QString QgsSqlite3Mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:175
bool saveSymbol(const QString &name, QgsSymbol *symbol, bool favorite, const QStringList &tags)
Adds the symbol to the DB with the tags.
Definition: qgsstyle.cpp:112
bool removeSymbol(const QString &name)
Removes symbol from style (and delete it)
Definition: qgsstyle.cpp:143
bool addFavorite(StyleEntity type, const QString &name)
Adds the specified symbol to favorites.
Definition: qgsstyle.cpp:778
bool detagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Detags the symbol with the given list.
Definition: qgsstyle.cpp:946
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
Definition: qgsstyle.cpp:542
QMultiMap< QString, QString > QgsSmartConditionMap
A multimap to hold the smart group conditions as constraint and parameter pairs.
Definition: qgsstyle.h:63
QgsSymbolGroupMap smartgroupsListMap()
Returns the smart groups map with id as key and name as value.
Definition: qgsstyle.cpp:1280
~QgsStyle() override
Definition: qgsstyle.cpp:41