QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( const QString &t, tags )
909  {
910  tag = t.trimmed();
911  if ( !tag.isEmpty() )
912  {
913  // sql: gets the id of the tag if present or insert the tag and get the id of the tag
914  int tagid( tagId( tag ) );
915  if ( ! tagid )
916  {
917  tagid = addTag( tag );
918  }
919 
920  // Now map the tag to the symbol if it's not already tagged
921  if ( !symbolHasTag( type, symbol, tag ) )
922  {
923  auto query = type == SymbolEntity
924  ? QgsSqlite3Mprintf( "INSERT INTO tagmap VALUES (%d,%d)", tagid, symbolid )
925  : QgsSqlite3Mprintf( "INSERT INTO ctagmap VALUES (%d,%d)", tagid, symbolid );
926 
927  char *zErr = nullptr;
928  int nErr;
929  nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
930  if ( nErr )
931  {
932  QgsDebugMsg( zErr );
933  sqlite3_free( zErr );
934  }
935  }
936  }
937  }
938 
939  clearCachedTags( type, symbol );
940  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
941 
942  return true;
943 }
944 
945 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
946 {
947  if ( !mCurrentDB )
948  {
949  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detgging." ) );
950  return false;
951  }
952 
953  auto query = type == SymbolEntity
954  ? QgsSqlite3Mprintf( "SELECT id FROM symbol WHERE name='%q'", symbol.toUtf8().constData() )
955  : QgsSqlite3Mprintf( "SELECT id FROM colorramp WHERE name='%q'", symbol.toUtf8().constData() );
957  int nErr; statement = mCurrentDB.prepare( query, nErr );
958 
959  int symbolid = 0;
960  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
961  {
962  symbolid = sqlite3_column_int( statement.get(), 0 );
963  }
964  else
965  {
966  return false;
967  }
968 
969  Q_FOREACH ( const QString &tag, tags )
970  {
971  query = QgsSqlite3Mprintf( "SELECT id FROM tag WHERE name='%q'", tag.toUtf8().constData() );
972 
973  sqlite3_statement_unique_ptr statement2;
974  statement2 = mCurrentDB.prepare( query, nErr );
975 
976  int tagid = 0;
977  if ( nErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
978  {
979  tagid = sqlite3_column_int( statement2.get(), 0 );
980  }
981 
982  if ( tagid )
983  {
984  // remove from the tagmap
985  query = type == SymbolEntity
986  ? QgsSqlite3Mprintf( "DELETE FROM tagmap WHERE tag_id=%d AND symbol_id=%d", tagid, symbolid )
987  : QgsSqlite3Mprintf( "DELETE FROM ctagmap WHERE tag_id=%d AND colorramp_id=%d", tagid, symbolid );
988  runEmptyQuery( query );
989  }
990  }
991 
992  clearCachedTags( type, symbol );
993  emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
994 
995  // TODO Perform tag cleanup
996  // check the number of entries for a given tag in the tagmap
997  // if the count is 0, then remove( TagEntity, tagid )
998  return true;
999 }
1000 
1001 bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol )
1002 {
1003  if ( !mCurrentDB )
1004  {
1005  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1006  return false;
1007  }
1008 
1009  auto query = type == SymbolEntity
1010  ? QgsSqlite3Mprintf( "SELECT id FROM symbol WHERE name='%q'", symbol.toUtf8().constData() )
1011  : QgsSqlite3Mprintf( "SELECT id FROM colorramp WHERE name='%q'", symbol.toUtf8().constData() );
1012  sqlite3_statement_unique_ptr statement;
1013  int nErr;
1014  statement = mCurrentDB.prepare( query, nErr );
1015 
1016  int symbolid = 0;
1017  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1018  {
1019  symbolid = sqlite3_column_int( statement.get(), 0 );
1020  }
1021  else
1022  {
1023  return false;
1024  }
1025 
1026  // remove all tags
1027  query = type == SymbolEntity
1028  ? QgsSqlite3Mprintf( "DELETE FROM tagmap WHERE symbol_id=%d", symbolid )
1029  : QgsSqlite3Mprintf( "DELETE FROM ctagmap WHERE colorramp_id=%d", symbolid );
1030  runEmptyQuery( query );
1031 
1032  clearCachedTags( type, symbol );
1033  emit entityTagsChanged( type, symbol, QStringList() );
1034 
1035  // TODO Perform tag cleanup
1036  // check the number of entries for a given tag in the tagmap
1037  // if the count is 0, then remove( TagEntity, tagid )
1038  return true;
1039 }
1040 
1041 QStringList QgsStyle::tagsOfSymbol( StyleEntity type, const QString &symbol )
1042 {
1043  switch ( type )
1044  {
1045  case SymbolEntity:
1046  if ( mCachedSymbolTags.contains( symbol ) )
1047  return mCachedSymbolTags.value( symbol );
1048  break;
1049 
1050  case ColorrampEntity:
1051  if ( mCachedColorRampTags.contains( symbol ) )
1052  return mCachedColorRampTags.value( symbol );
1053  break;
1054 
1055  case TagEntity:
1056  case SmartgroupEntity:
1057  break;
1058  }
1059 
1060  if ( !mCurrentDB )
1061  {
1062  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1063  return QStringList();
1064  }
1065 
1066  int symbolid = type == SymbolEntity ? symbolId( symbol ) : colorrampId( symbol );
1067  if ( !symbolid )
1068  return QStringList();
1069 
1070  // get the ids of tags for the symbol
1071  auto query = type == SymbolEntity
1072  ? QgsSqlite3Mprintf( "SELECT tag_id FROM tagmap WHERE symbol_id=%d", symbolid )
1073  : QgsSqlite3Mprintf( "SELECT tag_id FROM ctagmap WHERE colorramp_id=%d", symbolid );
1074 
1075  sqlite3_statement_unique_ptr statement;
1076  int nErr; statement = mCurrentDB.prepare( query, nErr );
1077 
1078  QStringList tagList;
1079  while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1080  {
1081  auto subquery = QgsSqlite3Mprintf( "SELECT name FROM tag WHERE id=%d", sqlite3_column_int( statement.get(), 0 ) );
1082 
1083  sqlite3_statement_unique_ptr statement2;
1084  int pErr;
1085  statement2 = mCurrentDB.prepare( subquery, pErr );
1086  if ( pErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1087  {
1088  tagList << statement2.columnAsText( 0 );
1089  }
1090  }
1091 
1092  // update cache
1093  switch ( type )
1094  {
1095  case SymbolEntity:
1096  mCachedSymbolTags[ symbol ] = tagList;
1097  break;
1098 
1099  case ColorrampEntity:
1100  mCachedColorRampTags[ symbol ] = tagList;
1101  break;
1102 
1103  case TagEntity:
1104  case SmartgroupEntity:
1105  break;
1106  }
1107 
1108  return tagList;
1109 }
1110 
1111 bool QgsStyle::symbolHasTag( StyleEntity type, const QString &symbol, const QString &tag )
1112 {
1113  if ( !mCurrentDB )
1114  {
1115  QgsDebugMsg( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1116  return false;
1117  }
1118 
1119  int symbolid = type == SymbolEntity ? symbolId( symbol ) : colorrampId( symbol );
1120  if ( !symbolid )
1121  {
1122  return false;
1123  }
1124  int tagid = tagId( tag );
1125  if ( !tagid )
1126  {
1127  return false;
1128  }
1129 
1130  // get the ids of tags for the symbol
1131  auto query = type == SymbolEntity
1132  ? QgsSqlite3Mprintf( "SELECT tag_id FROM tagmap WHERE tag_id=%d AND symbol_id=%d", tagid, symbolid )
1133  : QgsSqlite3Mprintf( "SELECT tag_id FROM ctagmap WHERE tag_id=%d AND colorramp_id=%d", tagid, symbolid );
1134 
1135  sqlite3_statement_unique_ptr statement;
1136  int nErr; statement = mCurrentDB.prepare( query, nErr );
1137 
1138  return ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW );
1139 }
1140 
1141 QString QgsStyle::tag( int id ) const
1142 {
1143  if ( !mCurrentDB )
1144  return QString();
1145 
1146  sqlite3_statement_unique_ptr statement;
1147 
1148  auto query = QgsSqlite3Mprintf( "SELECT name FROM tag WHERE id=%d", id );
1149  int nError;
1150  statement = mCurrentDB.prepare( query, nError );
1151 
1152  QString tag;
1153  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1154  {
1155  tag = statement.columnAsText( 0 );
1156  }
1157 
1158  return tag;
1159 }
1160 
1161 int QgsStyle::getId( const QString &table, const QString &name )
1162 {
1163  QString lowerName( name.toLower() );
1164  auto query = QgsSqlite3Mprintf( "SELECT id FROM %q WHERE LOWER(name)='%q'", table.toUtf8().constData(), lowerName.toUtf8().constData() );
1165 
1166  sqlite3_statement_unique_ptr statement;
1167  int nErr; statement = mCurrentDB.prepare( query, nErr );
1168 
1169  int id = 0;
1170  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1171  {
1172  id = sqlite3_column_int( statement.get(), 0 );
1173  }
1174  else
1175  {
1176  // Try the name without lowercase conversion
1177  auto query = QgsSqlite3Mprintf( "SELECT id FROM %q WHERE name='%q'", table.toUtf8().constData(), name.toUtf8().constData() );
1178 
1179  sqlite3_statement_unique_ptr statement;
1180  int nErr; statement = mCurrentDB.prepare( query, nErr );
1181  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1182  {
1183  id = sqlite3_column_int( statement.get(), 0 );
1184  }
1185  }
1186 
1187  return id;
1188 }
1189 
1190 QString QgsStyle::getName( const QString &table, int id ) const
1191 {
1192  auto query = QgsSqlite3Mprintf( "SELECT name FROM %q WHERE id='%q'", table.toUtf8().constData(), QString::number( id ).toUtf8().constData() );
1193 
1194  sqlite3_statement_unique_ptr statement;
1195  int nErr; statement = mCurrentDB.prepare( query, nErr );
1196 
1197  QString name;
1198  if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1199  {
1200  name = statement.columnAsText( 0 );
1201  }
1202 
1203  return name;
1204 }
1205 
1206 int QgsStyle::symbolId( const QString &name )
1207 {
1208  return getId( QStringLiteral( "symbol" ), name );
1209 }
1210 
1211 int QgsStyle::colorrampId( const QString &name )
1212 {
1213  return getId( QStringLiteral( "colorramp" ), name );
1214 }
1215 
1216 int QgsStyle::tagId( const QString &name )
1217 {
1218  return getId( QStringLiteral( "tag" ), name );
1219 }
1220 
1221 int QgsStyle::smartgroupId( const QString &name )
1222 {
1223  return getId( QStringLiteral( "smartgroup" ), name );
1224 }
1225 
1226 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions )
1227 {
1228  return addSmartgroup( name, op, conditions.values( QStringLiteral( "tag" ) ),
1229  conditions.values( QStringLiteral( "!tag" ) ),
1230  conditions.values( QStringLiteral( "name" ) ),
1231  conditions.values( QStringLiteral( "!name" ) ) );
1232 }
1233 
1234 int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag, const QStringList &matchName, const QStringList &noMatchName )
1235 {
1236  QDomDocument doc( QStringLiteral( "dummy" ) );
1237  QDomElement smartEl = doc.createElement( QStringLiteral( "smartgroup" ) );
1238  smartEl.setAttribute( QStringLiteral( "name" ), name );
1239  smartEl.setAttribute( QStringLiteral( "operator" ), op );
1240 
1241  auto addCondition = [&doc, &smartEl]( const QString & constraint, const QStringList & parameters )
1242  {
1243  for ( const QString &param : parameters )
1244  {
1245  QDomElement condEl = doc.createElement( QStringLiteral( "condition" ) );
1246  condEl.setAttribute( QStringLiteral( "constraint" ), constraint );
1247  condEl.setAttribute( QStringLiteral( "param" ), param );
1248  smartEl.appendChild( condEl );
1249  }
1250  };
1251  addCondition( QStringLiteral( "tag" ), matchTag );
1252  addCondition( QStringLiteral( "!tag" ), noMatchTag );
1253  addCondition( QStringLiteral( "name" ), matchName );
1254  addCondition( QStringLiteral( "!name" ), noMatchName );
1255 
1256  QByteArray xmlArray;
1257  QTextStream stream( &xmlArray );
1258  stream.setCodec( "UTF-8" );
1259  smartEl.save( stream, 4 );
1260  auto query = QgsSqlite3Mprintf( "INSERT INTO smartgroup VALUES (NULL, '%q', '%q')",
1261  name.toUtf8().constData(), xmlArray.constData() );
1262 
1263  if ( runEmptyQuery( query ) )
1264  {
1265  QgsSettings settings;
1266  settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1267 
1268  emit groupsModified();
1269  return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
1270  }
1271  else
1272  {
1273  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
1274  return 0;
1275  }
1276 }
1277 
1279 {
1280  if ( !mCurrentDB )
1281  {
1282  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1283  return QgsSymbolGroupMap();
1284  }
1285 
1286  auto query = QgsSqlite3Mprintf( "SELECT * FROM smartgroup" );
1287 
1288  // Now run the query and retrieve the group names
1289  sqlite3_statement_unique_ptr statement;
1290  int nError;
1291  statement = mCurrentDB.prepare( query, nError );
1292 
1293  QgsSymbolGroupMap groupNames;
1294  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1295  {
1296  QString group = statement.columnAsText( SmartgroupName );
1297  groupNames.insert( sqlite3_column_int( statement.get(), SmartgroupId ), group );
1298  }
1299 
1300  return groupNames;
1301 }
1302 
1304 {
1305  if ( !mCurrentDB )
1306  {
1307  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1308  return QStringList();
1309  }
1310 
1311  auto query = QgsSqlite3Mprintf( "SELECT name FROM smartgroup" );
1312 
1313  // Now run the query and retrieve the group names
1314  sqlite3_statement_unique_ptr statement;
1315  int nError;
1316  statement = mCurrentDB.prepare( query, nError );
1317 
1318  QStringList groups;
1319  while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1320  {
1321  groups << statement.columnAsText( 0 );
1322  }
1323 
1324  return groups;
1325 }
1326 
1327 QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
1328 {
1329  QStringList symbols;
1330 
1331  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1332 
1333  sqlite3_statement_unique_ptr statement;
1334  int nErr; statement = mCurrentDB.prepare( query, nErr );
1335  if ( !( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW ) )
1336  {
1337  return QStringList();
1338  }
1339  else
1340  {
1341  QDomDocument doc;
1342  QString xmlstr = statement.columnAsText( 0 );
1343  if ( !doc.setContent( xmlstr ) )
1344  {
1345  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1346  }
1347  QDomElement smartEl = doc.documentElement();
1348  QString op = smartEl.attribute( QStringLiteral( "operator" ) );
1349  QDomNodeList conditionNodes = smartEl.childNodes();
1350 
1351  bool firstSet = true;
1352  for ( int i = 0; i < conditionNodes.count(); i++ )
1353  {
1354  QDomElement condEl = conditionNodes.at( i ).toElement();
1355  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
1356  QString param = condEl.attribute( QStringLiteral( "param" ) );
1357 
1358  QStringList resultNames;
1359  // perform suitable action for the given constraint
1360  if ( constraint == QLatin1String( "tag" ) )
1361  {
1362  resultNames = symbolsWithTag( type, tagId( param ) );
1363  }
1364  else if ( constraint == QLatin1String( "name" ) )
1365  {
1366  if ( type == SymbolEntity )
1367  {
1368  resultNames = symbolNames().filter( param, Qt::CaseInsensitive );
1369  }
1370  else
1371  {
1372  resultNames = colorRampNames().filter( param, Qt::CaseInsensitive );
1373  }
1374  }
1375  else if ( constraint == QLatin1String( "!tag" ) )
1376  {
1377  resultNames = type == SymbolEntity ? symbolNames() : colorRampNames();
1378  const QStringList unwanted = symbolsWithTag( type, tagId( param ) );
1379  for ( const QString &name : unwanted )
1380  {
1381  resultNames.removeAll( name );
1382  }
1383  }
1384  else if ( constraint == QLatin1String( "!name" ) )
1385  {
1386  const QStringList all = type == SymbolEntity ? symbolNames() : colorRampNames();
1387  for ( const QString &str : all )
1388  {
1389  if ( !str.contains( param, Qt::CaseInsensitive ) )
1390  resultNames << str;
1391  }
1392  }
1393 
1394  // not apply the operator
1395  if ( firstSet )
1396  {
1397  symbols = resultNames;
1398  firstSet = false;
1399  }
1400  else
1401  {
1402  if ( op == QLatin1String( "OR" ) )
1403  {
1404  symbols << resultNames;
1405  }
1406  else if ( op == QLatin1String( "AND" ) )
1407  {
1408  QStringList dummy = symbols;
1409  symbols.clear();
1410  for ( const QString &result : qgis::as_const( resultNames ) )
1411  {
1412  if ( dummy.contains( result ) )
1413  symbols << result;
1414  }
1415  }
1416  }
1417  } // DOM loop ends here
1418  }
1419 
1420  // return sorted, unique list
1421  QStringList unique = symbols.toSet().toList();
1422  std::sort( unique.begin(), unique.end() );
1423  return unique;
1424 }
1425 
1427 {
1428  if ( !mCurrentDB )
1429  {
1430  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1431  return QgsSmartConditionMap();
1432  }
1433 
1434  QgsSmartConditionMap condition;
1435 
1436  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1437 
1438  sqlite3_statement_unique_ptr statement;
1439  int nError;
1440  statement = mCurrentDB.prepare( query, nError );
1441  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1442  {
1443  QDomDocument doc;
1444  QString xmlstr = statement.columnAsText( 0 );
1445  if ( !doc.setContent( xmlstr ) )
1446  {
1447  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1448  }
1449 
1450  QDomElement smartEl = doc.documentElement();
1451  QDomNodeList conditionNodes = smartEl.childNodes();
1452 
1453  for ( int i = 0; i < conditionNodes.count(); i++ )
1454  {
1455  QDomElement condEl = conditionNodes.at( i ).toElement();
1456  QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
1457  QString param = condEl.attribute( QStringLiteral( "param" ) );
1458 
1459  condition.insert( constraint, param );
1460  }
1461  }
1462 
1463  return condition;
1464 }
1465 
1467 {
1468  if ( !mCurrentDB )
1469  {
1470  QgsDebugMsg( QStringLiteral( "Cannot open database for listing groups" ) );
1471  return QString();
1472  }
1473 
1474  QString op;
1475 
1476  auto query = QgsSqlite3Mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
1477 
1478  int nError;
1479  sqlite3_statement_unique_ptr statement;
1480  statement = mCurrentDB.prepare( query, nError );
1481  if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1482  {
1483  QDomDocument doc;
1484  QString xmlstr = statement.columnAsText( 0 );
1485  if ( !doc.setContent( xmlstr ) )
1486  {
1487  QgsDebugMsg( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
1488  }
1489  QDomElement smartEl = doc.documentElement();
1490  op = smartEl.attribute( QStringLiteral( "operator" ) );
1491  }
1492 
1493  return op;
1494 }
1495 
1496 bool QgsStyle::exportXml( const QString &filename )
1497 {
1498  if ( filename.isEmpty() )
1499  {
1500  QgsDebugMsg( QStringLiteral( "Invalid filename for style export." ) );
1501  return false;
1502  }
1503 
1504  QDomDocument doc( QStringLiteral( "qgis_style" ) );
1505  QDomElement root = doc.createElement( QStringLiteral( "qgis_style" ) );
1506  root.setAttribute( QStringLiteral( "version" ), QStringLiteral( STYLE_CURRENT_VERSION ) );
1507  doc.appendChild( root );
1508 
1509  QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
1510  QStringList favoriteColorramps = symbolsOfFavorite( ColorrampEntity );
1511 
1512  // save symbols and attach tags
1513  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, QStringLiteral( "symbols" ), doc, QgsReadWriteContext() );
1514  QDomNodeList symbolsList = symbolsElem.elementsByTagName( QStringLiteral( "symbol" ) );
1515  int nbSymbols = symbolsList.count();
1516  for ( int i = 0; i < nbSymbols; ++i )
1517  {
1518  QDomElement symbol = symbolsList.at( i ).toElement();
1519  QString name = symbol.attribute( QStringLiteral( "name" ) );
1520  QStringList tags = tagsOfSymbol( SymbolEntity, name );
1521  if ( tags.count() > 0 )
1522  {
1523  symbol.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
1524  }
1525  if ( favoriteSymbols.contains( name ) )
1526  {
1527  symbol.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
1528  }
1529  }
1530 
1531  // save color ramps
1532  QDomElement rampsElem = doc.createElement( QStringLiteral( "colorramps" ) );
1533  for ( QMap<QString, QgsColorRamp *>::const_iterator itr = mColorRamps.constBegin(); itr != mColorRamps.constEnd(); ++itr )
1534  {
1535  QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
1536  QStringList tags = tagsOfSymbol( ColorrampEntity, itr.key() );
1537  if ( tags.count() > 0 )
1538  {
1539  rampEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
1540  }
1541  if ( favoriteColorramps.contains( itr.key() ) )
1542  {
1543  rampEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
1544  }
1545  rampsElem.appendChild( rampEl );
1546  }
1547 
1548  root.appendChild( symbolsElem );
1549  root.appendChild( rampsElem );
1550 
1551  // save
1552  QFile f( filename );
1553  if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
1554  {
1555  mErrorString = "Couldn't open file for writing: " + filename;
1556  return false;
1557  }
1558 
1559  QTextStream ts( &f );
1560  ts.setCodec( "UTF-8" );
1561  doc.save( ts, 2 );
1562  f.close();
1563 
1564  mFileName = filename;
1565  return true;
1566 }
1567 
1568 bool QgsStyle::importXml( const QString &filename )
1569 {
1570  mErrorString = QString();
1571  QDomDocument doc( QStringLiteral( "style" ) );
1572  QFile f( filename );
1573  if ( !f.open( QFile::ReadOnly ) )
1574  {
1575  mErrorString = QStringLiteral( "Unable to open the specified file" );
1576  QgsDebugMsg( QStringLiteral( "Error opening the style XML file." ) );
1577  return false;
1578  }
1579 
1580  if ( !doc.setContent( &f ) )
1581  {
1582  mErrorString = QStringLiteral( "Unable to understand the style file: %1" ).arg( filename );
1583  QgsDebugMsg( QStringLiteral( "XML Parsing error" ) );
1584  f.close();
1585  return false;
1586  }
1587  f.close();
1588 
1589  QDomElement docEl = doc.documentElement();
1590  if ( docEl.tagName() != QLatin1String( "qgis_style" ) )
1591  {
1592  mErrorString = "Incorrect root tag in style: " + docEl.tagName();
1593  return false;
1594  }
1595 
1596  QString version = docEl.attribute( QStringLiteral( "version" ) );
1597  if ( version != QLatin1String( STYLE_CURRENT_VERSION ) && version != QLatin1String( "0" ) )
1598  {
1599  mErrorString = "Unknown style file version: " + version;
1600  return false;
1601  }
1602 
1603  QgsSymbolMap symbols;
1604 
1605  QDomElement symbolsElement = docEl.firstChildElement( QStringLiteral( "symbols" ) );
1606  QDomElement e = symbolsElement.firstChildElement();
1607 
1608  // gain speed by re-grouping the INSERT statements in a transaction
1609  auto query = QgsSqlite3Mprintf( "BEGIN TRANSACTION;" );
1610  runEmptyQuery( query );
1611 
1612  if ( version == QLatin1String( STYLE_CURRENT_VERSION ) )
1613  {
1614  // For the new style, load symbols individually
1615  while ( !e.isNull() )
1616  {
1617  if ( e.tagName() == QLatin1String( "symbol" ) )
1618  {
1619  QString name = e.attribute( QStringLiteral( "name" ) );
1620  QStringList tags;
1621  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
1622  {
1623  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
1624  }
1625  bool favorite = false;
1626  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QStringLiteral( "1" ) )
1627  {
1628  favorite = true;
1629  }
1630 
1632  if ( symbol )
1633  {
1634  addSymbol( name, symbol );
1635  if ( mCurrentDB )
1636  {
1637  saveSymbol( name, symbol, favorite, tags );
1638  }
1639  }
1640  }
1641  else
1642  {
1643  QgsDebugMsg( "unknown tag: " + e.tagName() );
1644  }
1645  e = e.nextSiblingElement();
1646  }
1647  }
1648  else
1649  {
1650  // for the old version, use the utility function to solve @symbol@layer subsymbols
1651  symbols = QgsSymbolLayerUtils::loadSymbols( symbolsElement, QgsReadWriteContext() );
1652 
1653  // save the symbols with proper name
1654  for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
1655  {
1656  addSymbol( it.key(), it.value() );
1657  }
1658  }
1659 
1660  // load color ramps
1661  QDomElement rampsElement = docEl.firstChildElement( QStringLiteral( "colorramps" ) );
1662  e = rampsElement.firstChildElement();
1663  while ( !e.isNull() )
1664  {
1665  if ( e.tagName() == QLatin1String( "colorramp" ) )
1666  {
1667  QString name = e.attribute( QStringLiteral( "name" ) );
1668  QStringList tags;
1669  if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
1670  {
1671  tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
1672  }
1673  bool favorite = false;
1674  if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QStringLiteral( "1" ) )
1675  {
1676  favorite = true;
1677  }
1678 
1680  if ( ramp )
1681  {
1682  addColorRamp( name, ramp );
1683  if ( mCurrentDB )
1684  {
1685  saveColorRamp( name, ramp, favorite, tags );
1686  }
1687  }
1688  }
1689  else
1690  {
1691  QgsDebugMsg( "unknown tag: " + e.tagName() );
1692  }
1693  e = e.nextSiblingElement();
1694  }
1695 
1696  query = QgsSqlite3Mprintf( "COMMIT TRANSACTION;" );
1697  runEmptyQuery( query );
1698 
1699  mFileName = filename;
1700  return true;
1701 }
1702 
1703 bool QgsStyle::isXmlStyleFile( const QString &path )
1704 {
1705  QFileInfo fileInfo( path );
1706 
1707  if ( fileInfo.suffix().compare( QLatin1String( "xml" ), Qt::CaseInsensitive ) != 0 )
1708  return false;
1709 
1710  // sniff the first line of the file to see if it's a style file
1711  if ( !QFile::exists( path ) )
1712  return false;
1713 
1714  QFile inputFile( path );
1715  if ( !inputFile.open( QIODevice::ReadOnly ) )
1716  return false;
1717 
1718  QTextStream stream( &inputFile );
1719  const QString line = stream.readLine();
1720  return line == QLatin1String( "<!DOCTYPE qgis_style>" );
1721 }
1722 
1723 bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
1724 {
1725  QDomDocument doc( QStringLiteral( "dummy" ) );
1726  QDomElement symEl;
1727  QByteArray xmlArray;
1728  QTextStream stream( &xmlArray );
1729  stream.setCodec( "UTF-8" );
1730 
1731  QString query;
1732 
1733  if ( type == SymbolEntity )
1734  {
1735  // check if it is an existing symbol
1736  if ( !symbolNames().contains( name ) )
1737  {
1738  QgsDebugMsg( QStringLiteral( "Update request received for unavailable symbol" ) );
1739  return false;
1740  }
1741 
1742  symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol( name ), doc, QgsReadWriteContext() );
1743  if ( symEl.isNull() )
1744  {
1745  QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
1746  return false;
1747  }
1748  symEl.save( stream, 4 );
1749  query = QgsSqlite3Mprintf( "UPDATE symbol SET xml='%q' WHERE name='%q';",
1750  xmlArray.constData(), name.toUtf8().constData() );
1751  }
1752  else if ( type == ColorrampEntity )
1753  {
1754  if ( !colorRampNames().contains( name ) )
1755  {
1756  QgsDebugMsg( QStringLiteral( "Update requested for unavailable color ramp." ) );
1757  return false;
1758  }
1759 
1760  std::unique_ptr< QgsColorRamp > ramp( colorRamp( name ) );
1761  symEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp.get(), doc );
1762  if ( symEl.isNull() )
1763  {
1764  QgsDebugMsg( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
1765  return false;
1766  }
1767  symEl.save( stream, 4 );
1768  query = QgsSqlite3Mprintf( "UPDATE colorramp SET xml='%q' WHERE name='%q';",
1769  xmlArray.constData(), name.toUtf8().constData() );
1770  }
1771  else
1772  {
1773  QgsDebugMsg( QStringLiteral( "Updating the unsupported StyleEntity" ) );
1774  return false;
1775  }
1776 
1777 
1778  if ( !runEmptyQuery( query ) )
1779  {
1780  QgsDebugMsg( QStringLiteral( "Couldn't insert symbol into the database!" ) );
1781  return false;
1782  }
1783  else
1784  {
1785  switch ( type )
1786  {
1787  case SymbolEntity:
1788  emit symbolChanged( name );
1789  break;
1790 
1791  case ColorrampEntity:
1792  emit rampChanged( name );
1793  break;
1794 
1795  case TagEntity:
1796  case SmartgroupEntity:
1797  break;
1798  }
1799  }
1800  return true;
1801 }
1802 
1803 void QgsStyle::clearCachedTags( QgsStyle::StyleEntity type, const QString &name )
1804 {
1805  switch ( type )
1806  {
1807  case SymbolEntity:
1808  mCachedSymbolTags.remove( name );
1809  break;
1810 
1811  case ColorrampEntity:
1812  mCachedColorRampTags.remove( name );
1813  break;
1814 
1815  case TagEntity:
1816  case SmartgroupEntity:
1817  break;
1818  }
1819 }
bool exportXml(const QString &filename)
Exports the style as a XML file.
Definition: qgsstyle.cpp:1496
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
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:1111
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
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 ...
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition: qgsstyle.cpp:272
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:1041
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
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:181
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
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.
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:578
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
const QgsColorRamp * colorRampRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:278
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).
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 ...
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file...
Definition: qgsstyle.cpp:1568
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()
Is 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
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
Definition: qgsstyle.cpp:542
static void cleanDefaultStyle()
Deletes the default style. Only to be used by QgsApplication::exitQgis()
Definition: qgsstyle.cpp:71
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:1466
static bool isXmlStyleFile(const QString &path)
Tests if the file at path is a QGIS style XML file.
Definition: qgsstyle.cpp:1703
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:1426
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:1327
int smartgroupId(const QString &smartgroup)
Returns the DB id for the given smartgroup name.
Definition: qgsstyle.cpp:1221
QStringList smartgroupNames()
Returns the smart groups list.
Definition: qgsstyle.cpp:1303
int tagId(const QString &tag)
Returns the DB id for the given tag name.
Definition: qgsstyle.cpp:1216
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
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:1226
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:1211
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:1206
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
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
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
QStringList tags() const
Returns a list of all tags in the style database.
Definition: qgsstyle.cpp:647
QString errorMessage() const
Returns the most recent error message encountered by the database.
bool detagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Detags the symbol with the given list.
Definition: qgsstyle.cpp:945
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:1278
~QgsStyle() override
Definition: qgsstyle.cpp:41
QString tag(int id) const
Returns the tag name for the given id.
Definition: qgsstyle.cpp:1141