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