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