QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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 <sqlite3.h>
19
20#include "qgs3dsymbolregistry.h"
21#include "qgsabstract3dsymbol.h"
22#include "qgsapplication.h"
23#include "qgscolorramp.h"
24#include "qgsfillsymbol.h"
25#include "qgsfillsymbollayer.h"
26#include "qgslegendpatchshape.h"
27#include "qgslinestring.h"
28#include "qgslinesymbol.h"
29#include "qgslinesymbollayer.h"
30#include "qgslogger.h"
31#include "qgsmarkersymbol.h"
33#include "qgspolygon.h"
34#include "qgsproject.h"
36#include "qgsreadwritecontext.h"
37#include "qgsruntimeprofiler.h"
38#include "qgssettings.h"
39#include "qgssqliteutils.h"
40#include "qgssymbol.h"
42
43#include <QByteArray>
44#include <QDomDocument>
45#include <QDomElement>
46#include <QDomNode>
47#include <QDomNodeList>
48#include <QFile>
49#include <QFileInfo>
50#include <QTextStream>
51
52#include "moc_qgsstyle.cpp"
53
54#define STYLE_CURRENT_VERSION "2"
55
66
77
78
79QgsStyle *QgsStyle::sDefaultStyle = nullptr;
80
81QgsStyle::QgsStyle( QObject *parent )
82 : QObject( parent )
83{
84 auto simpleMarker = std::make_unique< QgsSimpleMarkerSymbolLayer >( Qgis::MarkerShape::Circle,
85 1.6, 0, Qgis::ScaleMethod::ScaleArea, QColor( 84, 176, 74 ), QColor( 61, 128, 53 ) );
86 simpleMarker->setStrokeWidth( 0.4 );
87 mPatchMarkerSymbol = std::make_unique< QgsMarkerSymbol >( QgsSymbolLayerList() << simpleMarker.release() );
88
89 auto simpleLine = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 84, 176, 74 ), 0.6 );
90 mPatchLineSymbol = std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << simpleLine.release() );
91
92 auto gradientFill = std::make_unique< QgsGradientFillSymbolLayer >( QColor( 66, 150, 63 ), QColor( 84, 176, 74 ) );
93 auto simpleOutline = std::make_unique< QgsSimpleLineSymbolLayer >( QColor( 56, 128, 54 ), 0.26 );
94 mPatchFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << gradientFill.release() << simpleOutline.release() );
95}
96
98{
99 emit aboutToBeDestroyed();
100 clear();
101}
102
103void QgsStyle::setName( const QString &name )
104{
105 mName = name;
106}
107
108QString QgsStyle::name() const
109{
110 return mName;
111}
112
113bool QgsStyle::addEntity( const QString &name, const QgsStyleEntityInterface *entity, bool update )
114{
115 switch ( entity->type() )
116 {
117 case SymbolEntity:
118 if ( !static_cast< const QgsStyleSymbolEntity * >( entity )->symbol() )
119 return false;
120 return addSymbol( name, static_cast< const QgsStyleSymbolEntity * >( entity )->symbol()->clone(), update );
121
122 case ColorrampEntity:
123 if ( !static_cast< const QgsStyleColorRampEntity * >( entity )->ramp() )
124 return false;
125 return addColorRamp( name, static_cast< const QgsStyleColorRampEntity * >( entity )->ramp()->clone(), update );
126
127 case TextFormatEntity:
128 return addTextFormat( name, static_cast< const QgsStyleTextFormatEntity * >( entity )->format(), update );
129
131 return addLabelSettings( name, static_cast< const QgsStyleLabelSettingsEntity * >( entity )->settings(), update );
132
134 return addLegendPatchShape( name, static_cast< const QgsStyleLegendPatchShapeEntity * >( entity )->shape(), update );
135
136 case Symbol3DEntity:
137 return addSymbol3D( name, static_cast< const QgsStyleSymbol3DEntity * >( entity )->symbol()->clone(), update );
138
139 case TagEntity:
140 case SmartgroupEntity:
141 break;
142
143 }
144 return false;
145}
146
147QgsStyle *QgsStyle::defaultStyle( bool initialize ) // static
148{
149 static QString sStyleFilename;
150 if ( !sDefaultStyle )
151 {
152 QgsScopedRuntimeProfile profile( tr( "Load default style database" ) );
153 sStyleFilename = QgsApplication::userStylePath();
154
155 // copy default style if user style doesn't exist
156 if ( !QFile::exists( sStyleFilename ) )
157 {
158 sDefaultStyle = new QgsStyle;
159 sDefaultStyle->createDatabase( sStyleFilename );
160 if ( QFile::exists( QgsApplication::defaultStylePath() ) )
161 {
162 if ( sDefaultStyle->importXml( QgsApplication::defaultStylePath() ) )
163 {
164 sDefaultStyle->createStyleMetadataTableIfNeeded();
165 }
166 }
167 }
168 else
169 {
170 sDefaultStyle = new QgsStyle;
171 sDefaultStyle->mInitialized = false;
172 if ( initialize )
173 {
174 sDefaultStyle->initializeDefaultStyle( sStyleFilename );
175 }
176 }
177 sDefaultStyle->setName( QObject::tr( "Default" ) );
178 }
179 else if ( initialize && !sDefaultStyle->mInitialized )
180 {
181 // lazy initialize
182 sDefaultStyle->initializeDefaultStyle( sStyleFilename );
183 }
184 return sDefaultStyle;
185}
186
187void QgsStyle::initializeDefaultStyle( const QString &filename )
188{
189 Q_ASSERT( this == sDefaultStyle );
190 if ( this != sDefaultStyle )
191 return;
192
193 if ( mInitialized )
194 return;
195
196 QgsScopedRuntimeProfile profile( tr( "Initialize default style database" ) );
197
198 mInitialized = true;
199 load( filename );
200 upgradeIfRequired();
201 emit initialized();
202}
203
205{
206 delete sDefaultStyle;
207 sDefaultStyle = nullptr;
208}
209
211{
212 qDeleteAll( mSymbols );
213 qDeleteAll( mColorRamps );
214 qDeleteAll( m3dSymbols );
215
216 mSymbols.clear();
217 mColorRamps.clear();
218 mTextFormats.clear();
219 m3dSymbols.clear();
220
221 mCachedTags.clear();
222 mCachedFavorites.clear();
223}
224
225bool QgsStyle::addSymbol( const QString &name, QgsSymbol *symbol, bool update )
226{
227 if ( !symbol || name.isEmpty() )
228 return false;
229
230 // delete previous symbol (if any)
231 auto it = mSymbols.constFind( name );
232 if ( it != mSymbols.constEnd() )
233 {
234 // TODO remove groups and tags?
235 delete it.value();
236 mSymbols.insert( name, symbol );
237 if ( update )
238 updateSymbol( SymbolEntity, name );
239 }
240 else
241 {
242 mSymbols.insert( name, symbol );
243 if ( update )
244 saveSymbol( name, symbol, false, QStringList() );
245 }
246
247 return true;
248}
249
250bool QgsStyle::saveSymbol( const QString &name, const QgsSymbol *symbol, bool favorite, const QStringList &tags )
251{
252 // TODO add support for groups
253 QDomDocument doc( QStringLiteral( "dummy" ) );
254 QDomElement symEl = QgsSymbolLayerUtils::saveSymbol( name, symbol, doc, QgsReadWriteContext() );
255 if ( symEl.isNull() )
256 {
257 QgsDebugError( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
258 return false;
259 }
260
261 QByteArray xmlArray;
262 QTextStream stream( &xmlArray );
263#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
264 stream.setCodec( "UTF-8" );
265#endif
266 symEl.save( stream, 4 );
267 QString query = qgs_sqlite3_mprintf( "INSERT INTO symbol VALUES (NULL, '%q', '%q', %d);",
268 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
269
270 if ( !runEmptyQuery( query ) )
271 {
272 QgsDebugError( QStringLiteral( "Couldn't insert symbol into the database!" ) );
273 return false;
274 }
275
276 mCachedFavorites[ SymbolEntity ].insert( name, favorite );
277
279
280 emit symbolSaved( name, symbol );
282
283 return true;
284}
285
286bool QgsStyle::removeSymbol( const QString &name )
287{
289}
290
291bool QgsStyle::renameEntity( QgsStyle::StyleEntity type, const QString &oldName, const QString &newName )
292{
293 switch ( type )
294 {
295 case SymbolEntity:
296 return renameSymbol( oldName, newName );
297
298 case ColorrampEntity:
299 return renameColorRamp( oldName, newName );
300
301 case TextFormatEntity:
302 return renameTextFormat( oldName, newName );
303
305 return renameLabelSettings( oldName, newName );
306
308 return renameLegendPatchShape( oldName, newName );
309
310 case Symbol3DEntity:
311 return renameSymbol3D( oldName, newName );
312
313 case TagEntity:
314 case SmartgroupEntity:
315 return false;
316 }
317 return false;
318}
319
321{
322 const QgsSymbol *symbol = symbolRef( name );
323 if ( !symbol )
324 return nullptr;
325
326 QgsSymbol *newSymbol = symbol->clone();
328
329 return newSymbol;
330}
331
332const QgsSymbol *QgsStyle::symbolRef( const QString &name ) const
333{
334 return mSymbols.value( name );
335}
336
338{
339 return mSymbols.count();
340}
341
342QStringList QgsStyle::symbolNames() const
343{
344 return mSymbols.keys();
345}
346
347
348bool QgsStyle::addColorRamp( const QString &name, QgsColorRamp *colorRamp, bool update )
349{
350 if ( !colorRamp || name.isEmpty() )
351 return false;
352
353 // delete previous color ramps (if any)
354 auto it = mColorRamps.constFind( name );
355 if ( it != mColorRamps.constEnd() )
356 {
357 // TODO remove groups and tags?
358 delete it.value();
359 mColorRamps.insert( name, colorRamp );
360 if ( update )
361 updateSymbol( ColorrampEntity, name );
362 }
363 else
364 {
365 mColorRamps.insert( name, colorRamp );
366 if ( update )
367 saveColorRamp( name, colorRamp, false, QStringList() );
368 }
369
370 return true;
371}
372
373bool QgsStyle::addTextFormat( const QString &name, const QgsTextFormat &format, bool update )
374{
375 // delete previous text format (if any)
376 auto it = mTextFormats.find( name );
377 if ( it != mTextFormats.end() )
378 {
379 // TODO remove groups and tags?
380 mTextFormats.erase( it );
381 mTextFormats.insert( name, format );
382 if ( update )
383 updateSymbol( TextFormatEntity, name );
384 }
385 else
386 {
387 mTextFormats.insert( name, format );
388 if ( update )
389 saveTextFormat( name, format, false, QStringList() );
390 }
391
392 return true;
393}
394
395bool QgsStyle::addLabelSettings( const QString &name, const QgsPalLayerSettings &settings, bool update )
396{
397 // delete previous label settings (if any)
398 auto it = mLabelSettings.find( name );
399 if ( it != mLabelSettings.end() )
400 {
401 // TODO remove groups and tags?
402 mLabelSettings.erase( it );
403 mLabelSettings.insert( name, settings );
404 if ( update )
405 updateSymbol( LabelSettingsEntity, name );
406 }
407 else
408 {
409 mLabelSettings.insert( name, settings );
410 if ( update )
411 saveLabelSettings( name, settings, false, QStringList() );
412 }
413
414 return true;
415}
416
417bool QgsStyle::addLegendPatchShape( const QString &name, const QgsLegendPatchShape &shape, bool update )
418{
419 // delete previous legend patch shape (if any)
420 auto it = mLegendPatchShapes.find( name );
421 if ( it != mLegendPatchShapes.end() )
422 {
423 // TODO remove groups and tags?
424 mLegendPatchShapes.erase( it );
425 mLegendPatchShapes.insert( name, shape );
426 if ( update )
427 updateSymbol( LegendPatchShapeEntity, name );
428 }
429 else
430 {
431 mLegendPatchShapes.insert( name, shape );
432 if ( update )
433 saveLegendPatchShape( name, shape, false, QStringList() );
434 }
435
436 return true;
437}
438
439bool QgsStyle::addSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool update )
440{
441 // delete previous symbol (if any)
442 auto it = m3dSymbols.constFind( name );
443 if ( it != m3dSymbols.constEnd() )
444 {
445 // TODO remove groups and tags?
446 delete it.value();
447 m3dSymbols.insert( name, symbol );
448 if ( update )
449 updateSymbol( Symbol3DEntity, name );
450 }
451 else
452 {
453 m3dSymbols.insert( name, symbol );
454 if ( update )
455 saveSymbol3D( name, symbol, false, QStringList() );
456 }
457
458 return true;
459}
460
461bool QgsStyle::saveColorRamp( const QString &name, const QgsColorRamp *ramp, bool favorite, const QStringList &tags )
462{
463 // insert it into the database
464 QDomDocument doc( QStringLiteral( "dummy" ) );
465 QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( name, ramp, doc );
466
467 if ( rampEl.isNull() )
468 {
469 QgsDebugError( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
470 return false;
471 }
472
473 QByteArray xmlArray;
474 QTextStream stream( &xmlArray );
475#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
476 stream.setCodec( "UTF-8" );
477#endif
478 rampEl.save( stream, 4 );
479 QString query = qgs_sqlite3_mprintf( "INSERT INTO colorramp VALUES (NULL, '%q', '%q', %d);",
480 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
481 if ( !runEmptyQuery( query ) )
482 {
483 QgsDebugError( QStringLiteral( "Couldn't insert colorramp into the database!" ) );
484 return false;
485 }
486
487 mCachedFavorites[ ColorrampEntity ].insert( name, favorite );
488
490
491 emit rampAdded( name );
493
494 return true;
495}
496
497bool QgsStyle::removeColorRamp( const QString &name )
498{
500}
501
502QgsColorRamp *QgsStyle::colorRamp( const QString &name ) const
503{
504 const QgsColorRamp *ramp = colorRampRef( name );
505 return ramp ? ramp->clone() : nullptr;
506}
507
508const QgsColorRamp *QgsStyle::colorRampRef( const QString &name ) const
509{
510 return mColorRamps.value( name );
511}
512
514{
515 return mColorRamps.count();
516}
517
518QStringList QgsStyle::colorRampNames() const
519{
520 return mColorRamps.keys();
521}
522
523void QgsStyle::handleDeferred3DSymbolCreation()
524{
525 for ( auto it = mDeferred3DsymbolElements.constBegin(); it != mDeferred3DsymbolElements.constEnd(); ++it )
526 {
527 const QString symbolType = it.value().attribute( QStringLiteral( "type" ) );
528 std::unique_ptr< QgsAbstract3DSymbol > symbol( QgsApplication::symbol3DRegistry()->createSymbol( symbolType ) );
529 if ( symbol )
530 {
531 symbol->readXml( it.value(), QgsReadWriteContext() );
532 addSymbol3D( it.key(), symbol.release(), false );
533 emit entityAdded( Symbol3DEntity, it.key() );
534 }
535 else
536 {
537 QgsDebugError( "Cannot open 3d symbol " + it.key() );
538 continue;
539 }
540 }
541 mDeferred3DsymbolElements.clear();
542}
543
544bool QgsStyle::openDatabase( const QString &filename )
545{
546 int rc = mCurrentDB.open( filename );
547 if ( rc )
548 {
549 mErrorString = QStringLiteral( "Couldn't open the style database: %1" ).arg( mCurrentDB.errorMessage() );
550 return false;
551 }
552
553 return true;
554}
555
556bool QgsStyle::createDatabase( const QString &filename )
557{
558 mErrorString.clear();
559 if ( !openDatabase( filename ) )
560 {
561 mErrorString = QStringLiteral( "Unable to create database" );
562 QgsDebugError( mErrorString );
563 return false;
564 }
565
566 createTables();
567
568 return true;
569}
570
572{
573 mErrorString.clear();
574 if ( !openDatabase( QStringLiteral( ":memory:" ) ) )
575 {
576 mErrorString = QStringLiteral( "Unable to create temporary memory database" );
577 QgsDebugError( mErrorString );
578 return false;
579 }
580
581 createTables();
582
583 return true;
584}
585
587{
588 QString query = qgs_sqlite3_mprintf( "CREATE TABLE symbol("\
589 "id INTEGER PRIMARY KEY,"\
590 "name TEXT UNIQUE,"\
591 "xml TEXT,"\
592 "favorite INTEGER);"\
593 "CREATE TABLE colorramp("\
594 "id INTEGER PRIMARY KEY,"\
595 "name TEXT UNIQUE,"\
596 "xml TEXT,"\
597 "favorite INTEGER);"\
598 "CREATE TABLE textformat("\
599 "id INTEGER PRIMARY KEY,"\
600 "name TEXT UNIQUE,"\
601 "xml TEXT,"\
602 "favorite INTEGER);"\
603 "CREATE TABLE labelsettings("\
604 "id INTEGER PRIMARY KEY,"\
605 "name TEXT UNIQUE,"\
606 "xml TEXT,"\
607 "favorite INTEGER);"\
608 "CREATE TABLE legendpatchshapes("\
609 "id INTEGER PRIMARY KEY,"\
610 "name TEXT UNIQUE,"\
611 "xml TEXT,"\
612 "favorite INTEGER);"\
613 "CREATE TABLE symbol3d("\
614 "id INTEGER PRIMARY KEY,"\
615 "name TEXT UNIQUE,"\
616 "xml TEXT,"\
617 "favorite INTEGER);"\
618 "CREATE TABLE tag("\
619 "id INTEGER PRIMARY KEY,"\
620 "name TEXT);"\
621 "CREATE TABLE tagmap("\
622 "tag_id INTEGER NOT NULL,"\
623 "symbol_id INTEGER);"\
624 "CREATE TABLE ctagmap("\
625 "tag_id INTEGER NOT NULL,"\
626 "colorramp_id INTEGER);"\
627 "CREATE TABLE tftagmap("\
628 "tag_id INTEGER NOT NULL,"\
629 "textformat_id INTEGER);"\
630 "CREATE TABLE lstagmap("\
631 "tag_id INTEGER NOT NULL,"\
632 "labelsettings_id INTEGER);"\
633 "CREATE TABLE lpstagmap("\
634 "tag_id INTEGER NOT NULL,"\
635 "legendpatchshape_id INTEGER);"\
636 "CREATE TABLE symbol3dtagmap("\
637 "tag_id INTEGER NOT NULL,"\
638 "symbol3d_id INTEGER);"\
639 "CREATE TABLE smartgroup("\
640 "id INTEGER PRIMARY KEY,"\
641 "name TEXT,"\
642 "xml TEXT);" );
643 runEmptyQuery( query );
644}
645
646bool QgsStyle::load( const QString &filename )
647{
648 mErrorString.clear();
649
650 // Open the sqlite database
651 if ( !openDatabase( filename ) )
652 {
653 mErrorString = QStringLiteral( "Unable to open database file specified" );
654 QgsDebugError( mErrorString );
655 return false;
656 }
657
658 // make sure text format table exists
659 QString query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='textformat'" );
661 int rc;
662 statement = mCurrentDB.prepare( query, rc );
663 if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
664 {
665 query = qgs_sqlite3_mprintf( "CREATE TABLE textformat("\
666 "id INTEGER PRIMARY KEY,"\
667 "name TEXT UNIQUE,"\
668 "xml TEXT,"\
669 "favorite INTEGER);"\
670 "CREATE TABLE tftagmap("\
671 "tag_id INTEGER NOT NULL,"\
672 "textformat_id INTEGER);" );
673 runEmptyQuery( query );
674 }
675 // make sure label settings table exists
676 query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='labelsettings'" );
677 statement = mCurrentDB.prepare( query, rc );
678 if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
679 {
680 query = qgs_sqlite3_mprintf( "CREATE TABLE labelsettings("\
681 "id INTEGER PRIMARY KEY,"\
682 "name TEXT UNIQUE,"\
683 "xml TEXT,"\
684 "favorite INTEGER);"\
685 "CREATE TABLE lstagmap("\
686 "tag_id INTEGER NOT NULL,"\
687 "labelsettings_id INTEGER);" );
688 runEmptyQuery( query );
689 }
690 // make sure legend patch shape table exists
691 query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='legendpatchshapes'" );
692 statement = mCurrentDB.prepare( query, rc );
693 if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
694 {
695 query = qgs_sqlite3_mprintf( "CREATE TABLE legendpatchshapes("\
696 "id INTEGER PRIMARY KEY,"\
697 "name TEXT UNIQUE,"\
698 "xml TEXT,"\
699 "favorite INTEGER);"\
700 "CREATE TABLE lpstagmap("\
701 "tag_id INTEGER NOT NULL,"\
702 "legendpatchshape_id INTEGER);" );
703 runEmptyQuery( query );
704 }
705 // make sure 3d symbol table exists
706 query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='symbol3d'" );
707 statement = mCurrentDB.prepare( query, rc );
708 if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
709 {
710 query = qgs_sqlite3_mprintf( "CREATE TABLE symbol3d("\
711 "id INTEGER PRIMARY KEY,"\
712 "name TEXT UNIQUE,"\
713 "xml TEXT,"\
714 "favorite INTEGER);"\
715 "CREATE TABLE symbol3dtagmap("\
716 "tag_id INTEGER NOT NULL,"\
717 "symbol3d_id INTEGER);" );
718 runEmptyQuery( query );
719 }
720
721 // Make sure there are no Null fields in parenting symbols and groups
722 query = qgs_sqlite3_mprintf( "UPDATE symbol SET favorite=0 WHERE favorite IS NULL;"
723 "UPDATE colorramp SET favorite=0 WHERE favorite IS NULL;"
724 "UPDATE textformat SET favorite=0 WHERE favorite IS NULL;"
725 "UPDATE labelsettings SET favorite=0 WHERE favorite IS NULL;"
726 "UPDATE legendpatchshapes SET favorite=0 WHERE favorite IS NULL;"
727 "UPDATE symbol3d SET favorite=0 WHERE favorite IS NULL;"
728 );
729 runEmptyQuery( query );
730
731 {
732 QgsScopedRuntimeProfile profile( tr( "Load symbols" ) );
733 // First create all the main symbols
734 query = qgs_sqlite3_mprintf( "SELECT * FROM symbol" );
735 statement = mCurrentDB.prepare( query, rc );
736
737 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
738 {
739 QDomDocument doc;
740 QString symbolName = statement.columnAsText( static_cast< int >( SymbolTableColumn::Name ) );
741 QgsScopedRuntimeProfile profile( symbolName );
742 QString xmlstring = statement.columnAsText( static_cast< int >( SymbolTableColumn::XML ) );
743 if ( !doc.setContent( xmlstring ) )
744 {
745 QgsDebugError( "Cannot open symbol " + symbolName );
746 continue;
747 }
748
749 QDomElement symElement = doc.documentElement();
750 std::unique_ptr< QgsSymbol >symbol = QgsSymbolLayerUtils::loadSymbol( symElement, QgsReadWriteContext() );
751 if ( symbol )
752 mSymbols.insert( symbolName, symbol.release() );
753 }
754 }
755
756 {
757 QgsScopedRuntimeProfile profile( tr( "Load color ramps" ) );
758 query = qgs_sqlite3_mprintf( "SELECT * FROM colorramp" );
759 statement = mCurrentDB.prepare( query, rc );
760 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
761 {
762 QDomDocument doc;
763 const QString rampName = statement.columnAsText( static_cast< int >( ColorRampTableColumn::Name ) );
764 QgsScopedRuntimeProfile profile( rampName );
765 QString xmlstring = statement.columnAsText( static_cast< int >( ColorRampTableColumn::XML ) );
766 if ( !doc.setContent( xmlstring ) )
767 {
768 QgsDebugError( "Cannot open symbol " + rampName );
769 continue;
770 }
771 QDomElement rampElement = doc.documentElement();
772 std::unique_ptr< QgsColorRamp > ramp = QgsSymbolLayerUtils::loadColorRamp( rampElement );
773 if ( ramp )
774 mColorRamps.insert( rampName, ramp.release() );
775 }
776 }
777
778 {
779 QgsScopedRuntimeProfile profile( tr( "Load text formats" ) );
780 query = qgs_sqlite3_mprintf( "SELECT * FROM textformat" );
781 statement = mCurrentDB.prepare( query, rc );
782 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
783 {
784 QDomDocument doc;
785 const QString formatName = statement.columnAsText( static_cast< int >( TextFormatTableColumn::Name ) );
786 QgsScopedRuntimeProfile profile( formatName );
787 const QString xmlstring = statement.columnAsText( static_cast< int >( TextFormatTableColumn::XML ) );
788 if ( !doc.setContent( xmlstring ) )
789 {
790 QgsDebugError( "Cannot open text format " + formatName );
791 continue;
792 }
793 QDomElement formatElement = doc.documentElement();
794 QgsTextFormat format;
795 format.readXml( formatElement, QgsReadWriteContext() );
796 mTextFormats.insert( formatName, format );
797 }
798 }
799
800 {
801 QgsScopedRuntimeProfile profile( tr( "Load label settings" ) );
802 query = qgs_sqlite3_mprintf( "SELECT * FROM labelsettings" );
803 statement = mCurrentDB.prepare( query, rc );
804 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
805 {
806 QDomDocument doc;
807 const QString settingsName = statement.columnAsText( static_cast< int >( LabelSettingsTableColumn::Name ) );
808 QgsScopedRuntimeProfile profile( settingsName );
809 const QString xmlstring = statement.columnAsText( static_cast< int >( LabelSettingsTableColumn::XML ) );
810 if ( !doc.setContent( xmlstring ) )
811 {
812 QgsDebugError( "Cannot open label settings " + settingsName );
813 continue;
814 }
815 QDomElement settingsElement = doc.documentElement();
816 QgsPalLayerSettings settings;
817 settings.readXml( settingsElement, QgsReadWriteContext() );
818 mLabelSettings.insert( settingsName, settings );
819 }
820 }
821
822 {
823 QgsScopedRuntimeProfile profile( tr( "Load legend patch shapes" ) );
824 query = qgs_sqlite3_mprintf( "SELECT * FROM legendpatchshapes" );
825 statement = mCurrentDB.prepare( query, rc );
826 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
827 {
828 QDomDocument doc;
829 const QString settingsName = statement.columnAsText( LegendPatchTableName );
830 QgsScopedRuntimeProfile profile( settingsName );
831 const QString xmlstring = statement.columnAsText( LegendPatchTableXML );
832 if ( !doc.setContent( xmlstring ) )
833 {
834 QgsDebugError( "Cannot open legend patch shape " + settingsName );
835 continue;
836 }
837 QDomElement settingsElement = doc.documentElement();
839 shape.readXml( settingsElement, QgsReadWriteContext() );
840 mLegendPatchShapes.insert( settingsName, shape );
841 }
842 }
843
844 {
845 QgsScopedRuntimeProfile profile( tr( "Load 3D symbols shapes" ) );
846 query = qgs_sqlite3_mprintf( "SELECT * FROM symbol3d" );
847 statement = mCurrentDB.prepare( query, rc );
848
849 const bool registry3dPopulated = !QgsApplication::symbol3DRegistry()->symbolTypes().empty();
850
851 while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
852 {
853 QDomDocument doc;
854 const QString settingsName = statement.columnAsText( Symbol3DTableName );
855 QgsScopedRuntimeProfile profile( settingsName );
856 const QString xmlstring = statement.columnAsText( Symbol3DTableXML );
857 if ( !doc.setContent( xmlstring ) )
858 {
859 QgsDebugError( "Cannot open 3d symbol " + settingsName );
860 continue;
861 }
862 QDomElement settingsElement = doc.documentElement();
863
864 if ( !registry3dPopulated )
865 {
866 mDeferred3DsymbolElements.insert( settingsName, settingsElement );
867 }
868 else
869 {
870 const QString symbolType = settingsElement.attribute( QStringLiteral( "type" ) );
871 std::unique_ptr< QgsAbstract3DSymbol > symbol( QgsApplication::symbol3DRegistry()->createSymbol( symbolType ) );
872 if ( symbol )
873 {
874 symbol->readXml( settingsElement, QgsReadWriteContext() );
875 m3dSymbols.insert( settingsName, symbol.release() );
876 }
877 else
878 {
879 QgsDebugError( "Cannot open 3d symbol " + settingsName );
880 continue;
881 }
882 }
883 }
884 }
885
886 mFileName = filename;
887 createStyleMetadataTableIfNeeded();
888 return true;
889}
890
891bool QgsStyle::save( const QString &filename )
892{
893 mErrorString.clear();
894
895 if ( !filename.isEmpty() )
896 mFileName = filename;
897
898 return true;
899}
900
901void QgsStyle::setFileName( const QString &filename )
902{
903 mFileName = filename;
904}
905
906bool QgsStyle::renameSymbol( const QString &oldName, const QString &newName )
907{
908 if ( mSymbols.contains( newName ) )
909 {
910 QgsDebugError( QStringLiteral( "Symbol of new name already exists" ) );
911 return false;
912 }
913
914 QgsSymbol *symbol = mSymbols.take( oldName );
915 if ( !symbol )
916 return false;
917
918 mSymbols.insert( newName, symbol );
919
920 if ( !mCurrentDB )
921 {
922 QgsDebugError( QStringLiteral( "Sorry! Cannot open database to tag." ) );
923 return false;
924 }
925
926 int symbolid = symbolId( oldName );
927 if ( !symbolid )
928 {
929 QgsDebugError( QStringLiteral( "No such symbol for tagging in database: " ) + oldName );
930 return false;
931 }
932
933 mCachedTags[ SymbolEntity ].remove( oldName );
934 mCachedFavorites[ SymbolEntity ].remove( oldName );
935
936 const bool result = rename( SymbolEntity, symbolid, newName );
937 if ( result )
938 {
939 emit symbolRenamed( oldName, newName );
940 emit entityRenamed( SymbolEntity, oldName, newName );
941 }
942
943 return result;
944}
945
946bool QgsStyle::renameColorRamp( const QString &oldName, const QString &newName )
947{
948 if ( mColorRamps.contains( newName ) )
949 {
950 QgsDebugError( QStringLiteral( "Color ramp of new name already exists." ) );
951 return false;
952 }
953
954 QgsColorRamp *ramp = mColorRamps.take( oldName );
955 if ( !ramp )
956 return false;
957
958 mColorRamps.insert( newName, ramp );
959 mCachedTags[ ColorrampEntity ].remove( oldName );
960 mCachedFavorites[ ColorrampEntity ].remove( oldName );
961
962 int rampid = 0;
964 QString query = qgs_sqlite3_mprintf( "SELECT id FROM colorramp WHERE name='%q'", oldName.toUtf8().constData() );
965 int nErr;
966 statement = mCurrentDB.prepare( query, nErr );
967 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
968 {
969 rampid = sqlite3_column_int( statement.get(), 0 );
970 }
971 const bool result = rename( ColorrampEntity, rampid, newName );
972 if ( result )
973 {
974 emit rampRenamed( oldName, newName );
975 emit entityRenamed( ColorrampEntity, oldName, newName );
976 }
977
978 return result;
979}
980
981bool QgsStyle::saveTextFormat( const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags )
982{
983 // insert it into the database
984 QDomDocument doc( QStringLiteral( "dummy" ) );
985 QDomElement formatElem = format.writeXml( doc, QgsReadWriteContext() );
986
987 if ( formatElem.isNull() )
988 {
989 QgsDebugError( QStringLiteral( "Couldn't convert text format to valid XML!" ) );
990 return false;
991 }
992
993 QByteArray xmlArray;
994 QTextStream stream( &xmlArray );
995#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
996 stream.setCodec( "UTF-8" );
997#endif
998 formatElem.save( stream, 4 );
999 QString query = qgs_sqlite3_mprintf( "INSERT INTO textformat VALUES (NULL, '%q', '%q', %d);",
1000 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1001 if ( !runEmptyQuery( query ) )
1002 {
1003 QgsDebugError( QStringLiteral( "Couldn't insert text format into the database!" ) );
1004 return false;
1005 }
1006
1007 mCachedFavorites[ TextFormatEntity ].insert( name, favorite );
1008
1010
1011 emit textFormatAdded( name );
1013
1014 return true;
1015}
1016
1017bool QgsStyle::removeTextFormat( const QString &name )
1018{
1020}
1021
1022bool QgsStyle::renameTextFormat( const QString &oldName, const QString &newName )
1023{
1024 if ( mTextFormats.contains( newName ) )
1025 {
1026 QgsDebugError( QStringLiteral( "Text format of new name already exists." ) );
1027 return false;
1028 }
1029
1030 if ( !mTextFormats.contains( oldName ) )
1031 return false;
1032 QgsTextFormat format = mTextFormats.take( oldName );
1033
1034 mTextFormats.insert( newName, format );
1035 mCachedTags[ TextFormatEntity ].remove( oldName );
1036 mCachedFavorites[ TextFormatEntity ].remove( oldName );
1037
1038 int textFormatId = 0;
1040 QString query = qgs_sqlite3_mprintf( "SELECT id FROM textformat WHERE name='%q'", oldName.toUtf8().constData() );
1041 int nErr;
1042 statement = mCurrentDB.prepare( query, nErr );
1043 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1044 {
1045 textFormatId = sqlite3_column_int( statement.get(), 0 );
1046 }
1047 const bool result = rename( TextFormatEntity, textFormatId, newName );
1048 if ( result )
1049 {
1050 emit textFormatRenamed( oldName, newName );
1051 emit entityRenamed( TextFormatEntity, oldName, newName );
1052 }
1053
1054 return result;
1055}
1056
1057bool QgsStyle::saveLabelSettings( const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags )
1058{
1059 // insert it into the database
1060 QDomDocument doc( QStringLiteral( "dummy" ) );
1061 QDomElement settingsElem = settings.writeXml( doc, QgsReadWriteContext() );
1062
1063 if ( settingsElem.isNull() )
1064 {
1065 QgsDebugError( QStringLiteral( "Couldn't convert label settings to valid XML!" ) );
1066 return false;
1067 }
1068
1069 QByteArray xmlArray;
1070 QTextStream stream( &xmlArray );
1071#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1072 stream.setCodec( "UTF-8" );
1073#endif
1074 settingsElem.save( stream, 4 );
1075 QString query = qgs_sqlite3_mprintf( "INSERT INTO labelsettings VALUES (NULL, '%q', '%q', %d);",
1076 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1077 if ( !runEmptyQuery( query ) )
1078 {
1079 QgsDebugError( QStringLiteral( "Couldn't insert label settings into the database!" ) );
1080 return false;
1081 }
1082
1083 mCachedFavorites[ LabelSettingsEntity ].insert( name, favorite );
1084
1086
1087 emit labelSettingsAdded( name );
1089
1090 return true;
1091}
1092
1094{
1096}
1097
1098bool QgsStyle::renameLabelSettings( const QString &oldName, const QString &newName )
1099{
1100 if ( mLabelSettings.contains( newName ) )
1101 {
1102 QgsDebugError( QStringLiteral( "Label settings of new name already exists." ) );
1103 return false;
1104 }
1105
1106 if ( !mLabelSettings.contains( oldName ) )
1107 return false;
1108 QgsPalLayerSettings settings = mLabelSettings.take( oldName );
1109
1110 mLabelSettings.insert( newName, settings );
1111 mCachedTags[ LabelSettingsEntity ].remove( oldName );
1112 mCachedFavorites[ LabelSettingsEntity ].remove( oldName );
1113
1114 int labelSettingsId = 0;
1116 QString query = qgs_sqlite3_mprintf( "SELECT id FROM labelsettings WHERE name='%q'", oldName.toUtf8().constData() );
1117 int nErr;
1118 statement = mCurrentDB.prepare( query, nErr );
1119 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1120 {
1121 labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1122 }
1123 const bool result = rename( LabelSettingsEntity, labelSettingsId, newName );
1124 if ( result )
1125 {
1126 emit labelSettingsRenamed( oldName, newName );
1127 emit entityRenamed( LabelSettingsEntity, oldName, newName );
1128 }
1129
1130 return result;
1131}
1132
1133bool QgsStyle::saveLegendPatchShape( const QString &name, const QgsLegendPatchShape &shape, bool favorite, const QStringList &tags )
1134{
1135 // insert it into the database
1136 QDomDocument doc( QStringLiteral( "dummy" ) );
1137 QDomElement shapeElem = doc.createElement( QStringLiteral( "shape" ) );
1138 shape.writeXml( shapeElem, doc, QgsReadWriteContext() );
1139
1140 QByteArray xmlArray;
1141 QTextStream stream( &xmlArray );
1142#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1143 stream.setCodec( "UTF-8" );
1144#endif
1145 shapeElem.save( stream, 4 );
1146 QString query = qgs_sqlite3_mprintf( "INSERT INTO legendpatchshapes VALUES (NULL, '%q', '%q', %d);",
1147 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1148 if ( !runEmptyQuery( query ) )
1149 {
1150 QgsDebugError( QStringLiteral( "Couldn't insert legend patch shape into the database!" ) );
1151 return false;
1152 }
1153
1154 mCachedFavorites[ LegendPatchShapeEntity ].insert( name, favorite );
1155
1157
1159
1160 return true;
1161}
1162
1163bool QgsStyle::renameLegendPatchShape( const QString &oldName, const QString &newName )
1164{
1165 if ( mLegendPatchShapes.contains( newName ) )
1166 {
1167 QgsDebugError( QStringLiteral( "Legend patch shape of new name already exists." ) );
1168 return false;
1169 }
1170
1171 if ( !mLegendPatchShapes.contains( oldName ) )
1172 return false;
1173 QgsLegendPatchShape shape = mLegendPatchShapes.take( oldName );
1174
1175 mLegendPatchShapes.insert( newName, shape );
1176 mCachedTags[ LegendPatchShapeEntity ].remove( oldName );
1177 mCachedFavorites[ LegendPatchShapeEntity ].remove( oldName );
1178
1179 int labelSettingsId = 0;
1181 QString query = qgs_sqlite3_mprintf( "SELECT id FROM legendpatchshapes WHERE name='%q'", oldName.toUtf8().constData() );
1182 int nErr;
1183 statement = mCurrentDB.prepare( query, nErr );
1184 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1185 {
1186 labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1187 }
1188 const bool result = rename( LegendPatchShapeEntity, labelSettingsId, newName );
1189 if ( result )
1190 {
1191 emit entityRenamed( LegendPatchShapeEntity, oldName, newName );
1192 }
1193
1194 return result;
1195}
1196
1198{
1199 if ( type == Qgis::SymbolType::Hybrid )
1200 return QgsLegendPatchShape();
1201
1202 auto it = mDefaultPatchCache[ static_cast< int >( type ) ].constFind( size );
1203 if ( it != mDefaultPatchCache[ static_cast< int >( type ) ].constEnd() )
1204 return it.value();
1205
1206 QgsGeometry geom;
1207 switch ( type )
1208 {
1210 geom = QgsGeometry( std::make_unique< QgsPoint >( static_cast< int >( size.width() ) / 2, static_cast< int >( size.height() ) / 2 ) );
1211 break;
1212
1214 {
1215 // we're adding 0.5 to get rid of blurred preview:
1216 // drawing antialiased lines of width 1 at (x,0)-(x,100) creates 2px line
1217 double y = static_cast< int >( size.height() ) / 2 + 0.5;
1218 geom = QgsGeometry( std::make_unique< QgsLineString >( ( QVector< double >() << 0 << size.width() ),
1219 ( QVector< double >() << y << y ) ) );
1220 break;
1221 }
1222
1224 {
1225 geom = QgsGeometry( std::make_unique< QgsPolygon >(
1226 new QgsLineString( QVector< double >() << 0 << static_cast< int >( size.width() ) << static_cast< int >( size.width() ) << 0 << 0,
1227 QVector< double >() << static_cast< int >( size.height() ) << static_cast< int >( size.height() ) << 0 << 0 << static_cast< int >( size.height() ) ) ) );
1228 break;
1229 }
1230
1232 break;
1233 }
1234
1235 QgsLegendPatchShape res = QgsLegendPatchShape( type, geom, false );
1236 mDefaultPatchCache[ static_cast< int >( type ) ][size ] = res;
1237 return res;
1238}
1239
1240QList<QList<QPolygonF> > QgsStyle::defaultPatchAsQPolygonF( Qgis::SymbolType type, QSizeF size ) const
1241{
1242 if ( type == Qgis::SymbolType::Hybrid )
1243 return QList<QList<QPolygonF> >();
1244
1245 auto it = mDefaultPatchQPolygonFCache[ static_cast< int >( type ) ].constFind( size );
1246 if ( it != mDefaultPatchQPolygonFCache[ static_cast< int >( type ) ].constEnd() )
1247 return it.value();
1248
1249 QList<QList<QPolygonF> > res = defaultPatch( type, size ).toQPolygonF( type, size );
1250 mDefaultPatchQPolygonFCache[ static_cast< int >( type ) ][size ] = res;
1251 return res;
1252}
1253
1255{
1256 return textFormat( QStringLiteral( "Default" ) );
1257}
1258
1260{
1261 if ( project )
1262 {
1264 if ( defaultTextFormat.isValid() )
1265 {
1266 return defaultTextFormat;
1267 }
1268 }
1269
1270 return QgsStyle::defaultStyle()->defaultTextFormat( context );
1271}
1272
1273bool QgsStyle::saveSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags )
1274{
1275 // insert it into the database
1276 QDomDocument doc( QStringLiteral( "dummy" ) );
1277 QDomElement elem = doc.createElement( QStringLiteral( "symbol" ) );
1278 elem.setAttribute( QStringLiteral( "type" ), symbol->type() );
1279 symbol->writeXml( elem, QgsReadWriteContext() );
1280
1281 QByteArray xmlArray;
1282 QTextStream stream( &xmlArray );
1283#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1284 stream.setCodec( "UTF-8" );
1285#endif
1286 elem.save( stream, 4 );
1287 QString query = qgs_sqlite3_mprintf( "INSERT INTO symbol3d VALUES (NULL, '%q', '%q', %d);",
1288 name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
1289 if ( !runEmptyQuery( query ) )
1290 {
1291 QgsDebugError( QStringLiteral( "Couldn't insert 3d symbol into the database!" ) );
1292 return false;
1293 }
1294
1295 mCachedFavorites[ Symbol3DEntity ].insert( name, favorite );
1296
1298
1300
1301 return true;
1302}
1303
1304bool QgsStyle::renameSymbol3D( const QString &oldName, const QString &newName )
1305{
1306 if ( m3dSymbols.contains( newName ) )
1307 {
1308 QgsDebugError( QStringLiteral( "3d symbol of new name already exists." ) );
1309 return false;
1310 }
1311
1312 if ( !m3dSymbols.contains( oldName ) )
1313 return false;
1314 QgsAbstract3DSymbol *symbol = m3dSymbols.take( oldName );
1315
1316 m3dSymbols.insert( newName, symbol );
1317 mCachedTags[Symbol3DEntity ].remove( oldName );
1318 mCachedFavorites[ Symbol3DEntity ].remove( oldName );
1319
1320 int labelSettingsId = 0;
1322 QString query = qgs_sqlite3_mprintf( "SELECT id FROM symbol3d WHERE name='%q'", oldName.toUtf8().constData() );
1323 int nErr;
1324 statement = mCurrentDB.prepare( query, nErr );
1325 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1326 {
1327 labelSettingsId = sqlite3_column_int( statement.get(), 0 );
1328 }
1329 const bool result = rename( Symbol3DEntity, labelSettingsId, newName );
1330 if ( result )
1331 {
1332 emit entityRenamed( Symbol3DEntity, oldName, newName );
1333 }
1334
1335 return result;
1336}
1337
1338QStringList QgsStyle::symbol3DNames() const
1339{
1340 return m3dSymbols.keys();
1341}
1342
1344{
1345 if ( !mCurrentDB )
1346 {
1347 QgsDebugError( QStringLiteral( "Cannot Open database for getting favorite symbols" ) );
1348 return QStringList();
1349 }
1350
1351 QString query;
1352 switch ( type )
1353 {
1354 case TagEntity:
1355 case SmartgroupEntity:
1356 QgsDebugError( QStringLiteral( "No such style entity" ) );
1357 return QStringList();
1358
1359 default:
1360 query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT name FROM %1 WHERE favorite=1" ).arg( entityTableName( type ) ).toLocal8Bit().data() );
1361 break;
1362 }
1363
1364 int nErr;
1366 statement = mCurrentDB.prepare( query, nErr );
1367
1368 QStringList symbols;
1369 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1370 {
1371 symbols << statement.columnAsText( 0 );
1372 }
1373
1374 return symbols;
1375}
1376
1377QStringList QgsStyle::symbolsWithTag( StyleEntity type, int tagid ) const
1378{
1379 if ( !mCurrentDB )
1380 {
1381 QgsDebugError( QStringLiteral( "Cannot open database to get symbols of tagid %1" ).arg( tagid ) );
1382 return QStringList();
1383 }
1384
1385 QString subquery;
1386 switch ( type )
1387 {
1388 case TagEntity:
1389 case SmartgroupEntity:
1390 QgsDebugError( QStringLiteral( "Unknown Entity" ) );
1391 return QStringList();
1392
1393 default:
1394 subquery = qgs_sqlite3_mprintf( QStringLiteral( "SELECT %1 FROM %2 WHERE tag_id=%d" ).arg( tagmapEntityIdFieldName( type ),
1395 tagmapTableName( type ) ).toLocal8Bit().data(), tagid );
1396 break;
1397 }
1398
1399 int nErr;
1401 statement = mCurrentDB.prepare( subquery, nErr );
1402
1403 // get the symbol <-> tag connection from the tag map table
1404 QStringList symbols;
1405 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1406 {
1407 int id = sqlite3_column_int( statement.get(), 0 );
1408
1409 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT name FROM %1 WHERE id=%d" ).arg( entityTableName( type ) ).toLocal8Bit().data(), id );
1410
1411 int rc;
1413 statement2 = mCurrentDB.prepare( query, rc );
1414 while ( rc == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1415 {
1416 symbols << statement2.columnAsText( 0 );
1417 }
1418 }
1419
1420 return symbols;
1421}
1422
1423int QgsStyle::addTag( const QString &tagname )
1424{
1425 if ( !mCurrentDB )
1426 return 0;
1428
1429 QString query = qgs_sqlite3_mprintf( "INSERT INTO tag VALUES (NULL, '%q')", tagname.toUtf8().constData() );
1430 int nErr;
1431 statement = mCurrentDB.prepare( query, nErr );
1432 if ( nErr == SQLITE_OK )
1433 ( void )sqlite3_step( statement.get() );
1434
1435 QgsSettings settings;
1436 settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1437
1438 emit groupsModified();
1439
1440 return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
1441}
1442
1443QStringList QgsStyle::tags() const
1444{
1445 if ( !mCurrentDB )
1446 return QStringList();
1447
1449
1450 QString query = qgs_sqlite3_mprintf( "SELECT name FROM tag" );
1451 int nError;
1452 statement = mCurrentDB.prepare( query, nError );
1453
1454 QStringList tagList;
1455 while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1456 {
1457 tagList << statement.columnAsText( 0 );
1458 }
1459
1460 return tagList;
1461}
1462
1463bool QgsStyle::rename( StyleEntity type, int id, const QString &newName )
1464{
1465 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET name='%q' WHERE id=%d" ).arg( entityTableName( type ) ).toLocal8Bit().data(), newName.toUtf8().constData(), id );
1466
1467 const bool result = runEmptyQuery( query );
1468 if ( !result )
1469 {
1470 mErrorString = QStringLiteral( "Could not rename!" );
1471 }
1472 else
1473 {
1474 mCachedTags.clear();
1475 mCachedFavorites.clear();
1476
1477 switch ( type )
1478 {
1479 case TagEntity:
1480 {
1481 emit groupsModified();
1482 break;
1483 }
1484
1485 case SmartgroupEntity:
1486 {
1487 emit groupsModified();
1488 break;
1489 }
1490
1491 default:
1492 break;
1493 }
1494 }
1495 return result;
1496}
1497
1498bool QgsStyle::remove( StyleEntity type, int id )
1499{
1500 bool groupRemoved = false;
1501 QString query;
1502 switch ( type )
1503 {
1504 case TagEntity:
1505 query = qgs_sqlite3_mprintf( "DELETE FROM tag WHERE id=%d; DELETE FROM tagmap WHERE tag_id=%d", id, id );
1506 groupRemoved = true;
1507 break;
1508 case SmartgroupEntity:
1509 query = qgs_sqlite3_mprintf( "DELETE FROM smartgroup WHERE id=%d", id );
1510 groupRemoved = true;
1511 break;
1512
1513 default:
1514 query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE id=%d; DELETE FROM %2 WHERE %3=%d" ).arg(
1515 entityTableName( type ),
1516 tagmapTableName( type ),
1517 tagmapEntityIdFieldName( type )
1518 ).toLocal8Bit().data(), id, id );
1519 break;
1520 }
1521
1522 bool result = false;
1523 if ( !runEmptyQuery( query ) )
1524 {
1525 QgsDebugError( QStringLiteral( "Could not delete entity!" ) );
1526 }
1527 else
1528 {
1529 mCachedTags.clear();
1530 mCachedFavorites.clear();
1531
1532 if ( groupRemoved )
1533 {
1534 QgsSettings settings;
1535 settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
1536
1537 emit groupsModified();
1538 }
1539 result = true;
1540 }
1541 return result;
1542}
1543
1545{
1546 switch ( type )
1547 {
1550 return false;
1551
1553 {
1554 std::unique_ptr< QgsSymbol > symbol( mSymbols.take( name ) );
1555 if ( !symbol )
1556 return false;
1557
1558 break;
1559 }
1560
1562 {
1563 std::unique_ptr< QgsAbstract3DSymbol > symbol( m3dSymbols.take( name ) );
1564 if ( !symbol )
1565 return false;
1566
1567 break;
1568 }
1569
1571 {
1572 std::unique_ptr< QgsColorRamp > ramp( mColorRamps.take( name ) );
1573 if ( !ramp )
1574 return false;
1575 break;
1576 }
1577
1579 {
1580 auto it = mTextFormats.find( name );
1581 if ( it == mTextFormats.end() )
1582 return false;
1583
1584 mTextFormats.erase( it );
1585 break;
1586 }
1587
1589 {
1590 auto it = mLabelSettings.find( name );
1591 if ( it == mLabelSettings.end() )
1592 return false;
1593
1594 mLabelSettings.erase( it );
1595 break;
1596 }
1597
1599 {
1600 auto it = mLegendPatchShapes.find( name );
1601 if ( it == mLegendPatchShapes.end() )
1602 return false;
1603
1604 mLegendPatchShapes.erase( it );
1605 break;
1606 }
1607 }
1608
1609 if ( !mCurrentDB )
1610 {
1611 QgsDebugError( QStringLiteral( "Sorry! Cannot open database to modify." ) );
1612 return false;
1613 }
1614
1615 const int id = entityId( type, name );
1616 if ( !id )
1617 {
1618 QgsDebugError( "No matching entity for deleting in database: " + name );
1619 }
1620
1621 const bool result = remove( type, id );
1622 if ( result )
1623 {
1624 mCachedTags[ type ].remove( name );
1625 mCachedFavorites[ type ].remove( name );
1626
1627 switch ( type )
1628 {
1629 case SymbolEntity:
1630 emit symbolRemoved( name );
1631 break;
1632
1633 case ColorrampEntity:
1634 emit rampRemoved( name );
1635 break;
1636
1637 case TextFormatEntity:
1638 emit textFormatRemoved( name );
1639 break;
1640
1642 emit labelSettingsRemoved( name );
1643 break;
1644
1645 default:
1646 // these specific signals should be discouraged -- don't add them for new entity types!
1647 break;
1648 }
1649 emit entityRemoved( type, name );
1650 }
1651 return result;
1652}
1653
1654bool QgsStyle::runEmptyQuery( const QString &query )
1655{
1656 if ( !mCurrentDB )
1657 return false;
1658
1659 char *zErr = nullptr;
1660 int nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
1661
1662 if ( nErr != SQLITE_OK )
1663 {
1664 QgsDebugError( zErr );
1665 sqlite3_free( zErr );
1666 }
1667
1668 return nErr == SQLITE_OK;
1669}
1670
1671bool QgsStyle::addFavorite( StyleEntity type, const QString &name )
1672{
1673 QString query;
1674
1675 switch ( type )
1676 {
1677 case TagEntity:
1678 case SmartgroupEntity:
1679 QgsDebugError( QStringLiteral( "Wrong entity value. cannot apply group" ) );
1680 return false;
1681
1682 default:
1683 query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET favorite=1 WHERE name='%q'" ).arg( entityTableName( type ) ).toLocal8Bit().data(),
1684 name.toUtf8().constData() );
1685 break;
1686 }
1687
1688 const bool res = runEmptyQuery( query );
1689 if ( res )
1690 {
1691 switch ( type )
1692 {
1693 case TagEntity:
1694 case SmartgroupEntity:
1695 break;
1696
1697 default:
1698 mCachedFavorites[ type ].insert( name, true );
1699 break;
1700 }
1701 emit favoritedChanged( type, name, true );
1702 }
1703
1704 return res;
1705}
1706
1707bool QgsStyle::removeFavorite( StyleEntity type, const QString &name )
1708{
1709 QString query;
1710
1711 switch ( type )
1712 {
1713 case TagEntity:
1714 case SmartgroupEntity:
1715 QgsDebugError( QStringLiteral( "Wrong entity value. cannot apply group" ) );
1716 return false;
1717
1718 default:
1719 query = qgs_sqlite3_mprintf( QStringLiteral( "UPDATE %1 SET favorite=0 WHERE name='%q'" ).arg( entityTableName( type ) ).toLocal8Bit().data(), name.toUtf8().constData() );
1720 break;
1721 }
1722
1723 const bool res = runEmptyQuery( query );
1724 if ( res )
1725 {
1726 mCachedFavorites[ type ].insert( name, false );
1727 emit favoritedChanged( type, name, false );
1728 }
1729
1730 return res;
1731}
1732
1733QStringList QgsStyle::findSymbols( StyleEntity type, const QString &qword )
1734{
1735 if ( !mCurrentDB )
1736 {
1737 QgsDebugError( QStringLiteral( "Sorry! Cannot open database to search" ) );
1738 return QStringList();
1739 }
1740
1741 // first find symbols with matching name
1742 QString item;
1743 switch ( type )
1744 {
1745 case TagEntity:
1746 case SmartgroupEntity:
1747 return QStringList();
1748
1749 default:
1750 item = entityTableName( type );
1751 break;
1752 }
1753
1754 QString query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE name LIKE '%%%q%%'",
1755 item.toUtf8().constData(), qword.toUtf8().constData() );
1756
1758 int nErr; statement = mCurrentDB.prepare( query, nErr );
1759
1760 QSet< QString > symbols;
1761 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1762 {
1763 symbols << statement.columnAsText( 0 );
1764 }
1765
1766 // next add symbols with matching tags
1767 query = qgs_sqlite3_mprintf( "SELECT id FROM tag WHERE name LIKE '%%%q%%'", qword.toUtf8().constData() );
1768 statement = mCurrentDB.prepare( query, nErr );
1769
1770 QStringList tagids;
1771 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1772 {
1773 tagids << statement.columnAsText( 0 );
1774 }
1775
1776 QString dummy = tagids.join( QLatin1String( ", " ) );
1777 query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT %1 FROM %2 WHERE tag_id IN (%q)" ).arg( tagmapEntityIdFieldName( type ),
1778 tagmapTableName( type ) ).toLocal8Bit().data(), dummy.toUtf8().constData() );
1779
1780 statement = mCurrentDB.prepare( query, nErr );
1781
1782 QStringList symbolids;
1783 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1784 {
1785 symbolids << statement.columnAsText( 0 );
1786 }
1787
1788 dummy = symbolids.join( QLatin1String( ", " ) );
1789 query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE id IN (%q)",
1790 item.toUtf8().constData(), dummy.toUtf8().constData() );
1791 statement = mCurrentDB.prepare( query, nErr );
1792 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1793 {
1794 symbols << statement.columnAsText( 0 );
1795 }
1796
1797 return QStringList( symbols.constBegin(), symbols.constEnd() );
1798}
1799
1800bool QgsStyle::tagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
1801{
1802 if ( !mCurrentDB )
1803 {
1804 QgsDebugError( QStringLiteral( "Sorry! Cannot open database to tag." ) );
1805 return false;
1806 }
1807
1808 int symbolid = 0;
1809 switch ( type )
1810 {
1811 case TagEntity:
1812 case SmartgroupEntity:
1813 return false;
1814
1815 default:
1816 symbolid = entityId( type, symbol );
1817 break;
1818 }
1819
1820 if ( !symbolid )
1821 {
1822 QgsDebugError( QStringLiteral( "No such symbol for tagging in database: " ) + symbol );
1823 return false;
1824 }
1825
1826 QString tag;
1827 const auto constTags = tags;
1828 for ( const QString &t : constTags )
1829 {
1830 tag = t.trimmed();
1831 if ( !tag.isEmpty() )
1832 {
1833 // sql: gets the id of the tag if present or insert the tag and get the id of the tag
1834 int tagid( tagId( tag ) );
1835 if ( ! tagid )
1836 {
1837 tagid = addTag( tag );
1838 }
1839
1840 // Now map the tag to the symbol if it's not already tagged
1841 if ( !symbolHasTag( type, symbol, tag ) )
1842 {
1843 QString query = qgs_sqlite3_mprintf( QStringLiteral( "INSERT INTO %1 VALUES (%d,%d)" ).arg( tagmapTableName( type ) ).toLocal8Bit().data(), tagid, symbolid );
1844
1845 char *zErr = nullptr;
1846 int nErr;
1847 nErr = sqlite3_exec( mCurrentDB.get(), query.toUtf8().constData(), nullptr, nullptr, &zErr );
1848 if ( nErr )
1849 {
1850 QgsDebugError( zErr );
1851 sqlite3_free( zErr );
1852 }
1853 }
1854 }
1855 }
1856
1857 clearCachedTags( type, symbol );
1858 emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
1859
1860 return true;
1861}
1862
1863bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol, const QStringList &tags )
1864{
1865 if ( !mCurrentDB )
1866 {
1867 QgsDebugError( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1868 return false;
1869 }
1870
1871 switch ( type )
1872 {
1873 case TagEntity:
1874 case SmartgroupEntity:
1875 return false;
1876
1877 default:
1878 break;
1879 }
1880
1881 const int symbolid = entityId( type, symbol );
1882 if ( symbolid == 0 )
1883 return false;
1884
1885 int nErr;
1886 QString query;
1887 const auto constTags = tags;
1888 for ( const QString &tag : constTags )
1889 {
1890 query = qgs_sqlite3_mprintf( "SELECT id FROM tag WHERE name='%q'", tag.toUtf8().constData() );
1891
1893 statement2 = mCurrentDB.prepare( query, nErr );
1894
1895 int tagid = 0;
1896 if ( nErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1897 {
1898 tagid = sqlite3_column_int( statement2.get(), 0 );
1899 }
1900
1901 if ( tagid )
1902 {
1903 // remove from the tagmap
1904 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE tag_id=%d AND %2=%d" ).arg( tagmapTableName( type ), tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), tagid, symbolid );
1905 runEmptyQuery( query );
1906 }
1907 }
1908
1909 clearCachedTags( type, symbol );
1910 emit entityTagsChanged( type, symbol, tagsOfSymbol( type, symbol ) );
1911
1912 // TODO Perform tag cleanup
1913 // check the number of entries for a given tag in the tagmap
1914 // if the count is 0, then remove( TagEntity, tagid )
1915 return true;
1916}
1917
1918bool QgsStyle::detagSymbol( StyleEntity type, const QString &symbol )
1919{
1920 if ( !mCurrentDB )
1921 {
1922 QgsDebugError( QStringLiteral( "Sorry! Cannot open database for detagging." ) );
1923 return false;
1924 }
1925
1926 switch ( type )
1927 {
1928 case TagEntity:
1929 case SmartgroupEntity:
1930 return false;
1931
1932 default:
1933 break;
1934 }
1935
1936 const int symbolid = entityId( type, symbol );
1937 if ( symbolid == 0 )
1938 {
1939 return false;
1940 }
1941
1942 // remove all tags
1943 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "DELETE FROM %1 WHERE %2=%d" ).arg( tagmapTableName( type ),
1944 tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), symbolid );
1945 runEmptyQuery( query );
1946
1947 clearCachedTags( type, symbol );
1948 emit entityTagsChanged( type, symbol, QStringList() );
1949
1950 // TODO Perform tag cleanup
1951 // check the number of entries for a given tag in the tagmap
1952 // if the count is 0, then remove( TagEntity, tagid )
1953 return true;
1954}
1955
1956QStringList QgsStyle::tagsOfSymbol( StyleEntity type, const QString &symbol )
1957{
1958 switch ( type )
1959 {
1960 case TagEntity:
1961 case SmartgroupEntity:
1962 return QStringList();
1963
1964 default:
1965 {
1966 auto it = mCachedTags[ type ].constFind( symbol );
1967 if ( it != mCachedTags[ type ].constEnd() )
1968 return it.value();
1969 break;
1970 }
1971 }
1972
1973 if ( !mCurrentDB )
1974 {
1975 QgsDebugError( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
1976 return QStringList();
1977 }
1978
1979 int symbolid = entityId( type, symbol );
1980 if ( !symbolid )
1981 return QStringList();
1982
1983 // get the ids of tags for the symbol
1984 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT tag_id FROM %1 WHERE %2=%d" ).arg( tagmapTableName( type ),
1985 tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), symbolid );
1986
1988 int nErr; statement = mCurrentDB.prepare( query, nErr );
1989
1990 QStringList tagList;
1991 while ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
1992 {
1993 QString subquery = qgs_sqlite3_mprintf( "SELECT name FROM tag WHERE id=%d", sqlite3_column_int( statement.get(), 0 ) );
1994
1996 int pErr;
1997 statement2 = mCurrentDB.prepare( subquery, pErr );
1998 if ( pErr == SQLITE_OK && sqlite3_step( statement2.get() ) == SQLITE_ROW )
1999 {
2000 tagList << statement2.columnAsText( 0 );
2001 }
2002 }
2003
2004 // update cache
2005 mCachedTags[ type ].insert( symbol, tagList );
2006
2007 return tagList;
2008}
2009
2011{
2012 if ( !mCurrentDB )
2013 {
2014 QgsDebugError( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
2015 return false;
2016 }
2017
2018 switch ( type )
2019 {
2020 case TagEntity:
2021 case SmartgroupEntity:
2022 return false;
2023
2024 default:
2025 {
2026 auto it = mCachedFavorites[ type ].constFind( name );
2027 if ( it != mCachedFavorites[ type ].constEnd() )
2028 return it.value();
2029 break;
2030 }
2031 }
2032
2033 const QStringList names = allNames( type );
2034 if ( !names.contains( name ) )
2035 return false; // entity doesn't exist
2036
2037 // for efficiency, retrieve names of all favorited symbols and store them in cache
2038 const QStringList favorites = symbolsOfFavorite( type );
2039 bool res = false;
2040 for ( const QString &n : names )
2041 {
2042 const bool isFav = favorites.contains( n );
2043 if ( n == name )
2044 res = isFav;
2045
2046 mCachedFavorites[ type ].insert( n, isFav );
2047 }
2048 return res;
2049}
2050
2051bool QgsStyle::symbolHasTag( StyleEntity type, const QString &symbol, const QString &tag )
2052{
2053 if ( !mCurrentDB )
2054 {
2055 QgsDebugError( QStringLiteral( "Sorry! Cannot open database for getting the tags." ) );
2056 return false;
2057 }
2058
2059 int symbolid = 0;
2060 switch ( type )
2061 {
2062 case TagEntity:
2063 case SmartgroupEntity:
2064 return false;
2065
2066 default:
2067 symbolid = entityId( type, symbol );
2068 break;
2069 }
2070
2071 if ( !symbolid )
2072 {
2073 return false;
2074 }
2075 int tagid = tagId( tag );
2076 if ( !tagid )
2077 {
2078 return false;
2079 }
2080
2081 // get the ids of tags for the symbol
2082 const QString query = qgs_sqlite3_mprintf( QStringLiteral( "SELECT tag_id FROM %1 WHERE tag_id=%d AND %2=%d" ).arg( tagmapTableName( type ),
2083 tagmapEntityIdFieldName( type ) ).toLocal8Bit().data(), tagid, symbolid );
2084
2086 int nErr; statement = mCurrentDB.prepare( query, nErr );
2087
2088 return ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW );
2089}
2090
2091QString QgsStyle::tag( int id ) const
2092{
2093 if ( !mCurrentDB )
2094 return QString();
2095
2097
2098 QString query = qgs_sqlite3_mprintf( "SELECT name FROM tag WHERE id=%d", id );
2099 int nError;
2100 statement = mCurrentDB.prepare( query, nError );
2101
2102 QString tag;
2103 if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2104 {
2105 tag = statement.columnAsText( 0 );
2106 }
2107
2108 return tag;
2109}
2110
2111int QgsStyle::getId( const QString &table, const QString &name )
2112{
2113 QString lowerName( name.toLower() );
2114 QString query = qgs_sqlite3_mprintf( "SELECT id FROM %q WHERE LOWER(name)='%q'", table.toUtf8().constData(), lowerName.toUtf8().constData() );
2115
2117 int nErr; statement = mCurrentDB.prepare( query, nErr );
2118
2119 int id = 0;
2120 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2121 {
2122 id = sqlite3_column_int( statement.get(), 0 );
2123 }
2124 else
2125 {
2126 // Try the name without lowercase conversion
2127 QString query = qgs_sqlite3_mprintf( "SELECT id FROM %q WHERE name='%q'", table.toUtf8().constData(), name.toUtf8().constData() );
2128
2129 sqlite3_statement_unique_ptr statement;
2130 int nErr; statement = mCurrentDB.prepare( query, nErr );
2131 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2132 {
2133 id = sqlite3_column_int( statement.get(), 0 );
2134 }
2135 }
2136
2137 return id;
2138}
2139
2140QString QgsStyle::getName( const QString &table, int id ) const
2141{
2142 QString query = qgs_sqlite3_mprintf( "SELECT name FROM %q WHERE id='%q'", table.toUtf8().constData(), QString::number( id ).toUtf8().constData() );
2143
2144 sqlite3_statement_unique_ptr statement;
2145 int nErr; statement = mCurrentDB.prepare( query, nErr );
2146
2147 QString name;
2148 if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2149 {
2150 name = statement.columnAsText( 0 );
2151 }
2152
2153 return name;
2154}
2155
2156int QgsStyle::symbolId( const QString &name )
2157{
2158 return getId( QStringLiteral( "symbol" ), name );
2159}
2160
2162{
2163 return getId( entityTableName( type ), name );
2164}
2165
2166int QgsStyle::colorrampId( const QString &name )
2167{
2168 return getId( QStringLiteral( "colorramp" ), name );
2169}
2170
2172{
2173 return mTextFormats.value( name );
2174}
2175
2177{
2178 return mTextFormats.count();
2179}
2180
2181QStringList QgsStyle::textFormatNames() const
2182{
2183 return mTextFormats.keys();
2184}
2185
2186int QgsStyle::textFormatId( const QString &name )
2187{
2188 return getId( QStringLiteral( "textformat" ), name );
2189}
2190
2192{
2193 return mLabelSettings.value( name );
2194}
2195
2197{
2198 return mLegendPatchShapes.value( name );
2199}
2200
2202{
2203 return mLegendPatchShapes.count();
2204}
2205
2207{
2208 auto it = mLegendPatchShapes.constFind( name );
2209 if ( it == mLegendPatchShapes.constEnd() )
2211
2212 return it.value().symbolType();
2213}
2214
2216{
2217 auto it = m3dSymbols.constFind( name );
2218 if ( it != m3dSymbols.constEnd( ) )
2219 return it.value()->clone();
2220 return nullptr;
2221}
2222
2224{
2225 return m3dSymbols.count();
2226}
2227
2228QList<Qgis::GeometryType> QgsStyle::symbol3DCompatibleGeometryTypes( const QString &name ) const
2229{
2230 auto it = m3dSymbols.constFind( name );
2231 if ( it == m3dSymbols.constEnd() )
2232 return QList<Qgis::GeometryType>();
2233
2234 return it.value()->compatibleGeometryTypes();
2235}
2236
2238{
2239 auto it = mLabelSettings.constFind( name );
2240 if ( it == mLabelSettings.constEnd() )
2242
2243 return it.value().layerType;
2244}
2245
2247{
2248 return mLabelSettings.count();
2249}
2250
2252{
2253 return mLabelSettings.keys();
2254}
2255
2256int QgsStyle::labelSettingsId( const QString &name )
2257{
2258 return getId( QStringLiteral( "labelsettings" ), name );
2259}
2260
2262{
2263 return mLegendPatchShapes.keys();
2264}
2265
2267{
2268 switch ( shape.symbolType() )
2269 {
2271 return mPatchMarkerSymbol.get();
2272
2274 return mPatchLineSymbol.get();
2275
2277 return mPatchFillSymbol.get();
2278
2280 break;
2281 }
2282 return nullptr;
2283}
2284
2285int QgsStyle::tagId( const QString &name )
2286{
2287 return getId( QStringLiteral( "tag" ), name );
2288}
2289
2290int QgsStyle::smartgroupId( const QString &name )
2291{
2292 return getId( QStringLiteral( "smartgroup" ), name );
2293}
2294
2296{
2297 switch ( type )
2298 {
2299 case SymbolEntity:
2300 return symbolNames();
2301
2302 case ColorrampEntity:
2303 return colorRampNames();
2304
2305 case TextFormatEntity:
2306 return textFormatNames();
2307
2309 return labelSettingsNames();
2310
2312 return legendPatchShapeNames();
2313
2314 case Symbol3DEntity:
2315 return symbol3DNames();
2316
2317 case TagEntity:
2318 return tags();
2319
2320 case SmartgroupEntity:
2321 return smartgroupNames();
2322 }
2323 return QStringList();
2324}
2325
2326int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions )
2327{
2328 return addSmartgroup( name, op, conditions.values( QStringLiteral( "tag" ) ),
2329 conditions.values( QStringLiteral( "!tag" ) ),
2330 conditions.values( QStringLiteral( "name" ) ),
2331 conditions.values( QStringLiteral( "!name" ) ) );
2332}
2333
2334int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag, const QStringList &matchName, const QStringList &noMatchName )
2335{
2336 QDomDocument doc( QStringLiteral( "dummy" ) );
2337 QDomElement smartEl = doc.createElement( QStringLiteral( "smartgroup" ) );
2338 smartEl.setAttribute( QStringLiteral( "name" ), name );
2339 smartEl.setAttribute( QStringLiteral( "operator" ), op );
2340
2341 auto addCondition = [&doc, &smartEl]( const QString & constraint, const QStringList & parameters )
2342 {
2343 for ( const QString &param : parameters )
2344 {
2345 QDomElement condEl = doc.createElement( QStringLiteral( "condition" ) );
2346 condEl.setAttribute( QStringLiteral( "constraint" ), constraint );
2347 condEl.setAttribute( QStringLiteral( "param" ), param );
2348 smartEl.appendChild( condEl );
2349 }
2350 };
2351 addCondition( QStringLiteral( "tag" ), matchTag );
2352 addCondition( QStringLiteral( "!tag" ), noMatchTag );
2353 addCondition( QStringLiteral( "name" ), matchName );
2354 addCondition( QStringLiteral( "!name" ), noMatchName );
2355
2356 QByteArray xmlArray;
2357 QTextStream stream( &xmlArray );
2358#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2359 stream.setCodec( "UTF-8" );
2360#endif
2361 smartEl.save( stream, 4 );
2362 QString query = qgs_sqlite3_mprintf( "INSERT INTO smartgroup VALUES (NULL, '%q', '%q')",
2363 name.toUtf8().constData(), xmlArray.constData() );
2364
2365 if ( runEmptyQuery( query ) )
2366 {
2367 QgsSettings settings;
2368 settings.setValue( QStringLiteral( "qgis/symbolsListGroupsIndex" ), 0 );
2369
2370 emit groupsModified();
2371 return static_cast< int >( sqlite3_last_insert_rowid( mCurrentDB.get() ) );
2372 }
2373 else
2374 {
2375 QgsDebugError( QStringLiteral( "Couldn't add the smart group into the database!" ) );
2376 return 0;
2377 }
2378}
2379
2381{
2382 if ( !mCurrentDB )
2383 {
2384 QgsDebugError( QStringLiteral( "Cannot open database for listing groups" ) );
2385 return QgsSymbolGroupMap();
2386 }
2387
2388 QString query = qgs_sqlite3_mprintf( "SELECT * FROM smartgroup" );
2389
2390 // Now run the query and retrieve the group names
2392 int nError;
2393 statement = mCurrentDB.prepare( query, nError );
2394
2395 QgsSymbolGroupMap groupNames;
2396 while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2397 {
2398 QString group = statement.columnAsText( static_cast< int >( SmartGroupTableColumn::Name ) );
2399 groupNames.insert( sqlite3_column_int( statement.get(), static_cast< int >( SmartGroupTableColumn::Id ) ), group );
2400 }
2401
2402 return groupNames;
2403}
2404
2405QStringList QgsStyle::smartgroupNames() const
2406{
2407 if ( !mCurrentDB )
2408 {
2409 QgsDebugError( QStringLiteral( "Cannot open database for listing groups" ) );
2410 return QStringList();
2411 }
2412
2413 QString query = qgs_sqlite3_mprintf( "SELECT name FROM smartgroup" );
2414
2415 // Now run the query and retrieve the group names
2417 int nError;
2418 statement = mCurrentDB.prepare( query, nError );
2419
2420 QStringList groups;
2421 while ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2422 {
2423 groups << statement.columnAsText( 0 );
2424 }
2425
2426 return groups;
2427}
2428
2430{
2431 QStringList symbols;
2432
2433 QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2434
2436 int nErr; statement = mCurrentDB.prepare( query, nErr );
2437 if ( !( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW ) )
2438 {
2439 return QStringList();
2440 }
2441 else
2442 {
2443 QDomDocument doc;
2444 QString xmlstr = statement.columnAsText( 0 );
2445 if ( !doc.setContent( xmlstr ) )
2446 {
2447 QgsDebugError( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2448 }
2449 QDomElement smartEl = doc.documentElement();
2450 QString op = smartEl.attribute( QStringLiteral( "operator" ) );
2451 QDomNodeList conditionNodes = smartEl.childNodes();
2452
2453 bool firstSet = true;
2454 for ( int i = 0; i < conditionNodes.count(); i++ )
2455 {
2456 QDomElement condEl = conditionNodes.at( i ).toElement();
2457 QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
2458 QString param = condEl.attribute( QStringLiteral( "param" ) );
2459
2460 QStringList resultNames;
2461 // perform suitable action for the given constraint
2462 if ( constraint == QLatin1String( "tag" ) )
2463 {
2464 resultNames = symbolsWithTag( type, tagId( param ) );
2465 }
2466 else if ( constraint == QLatin1String( "name" ) )
2467 {
2468 resultNames = allNames( type ).filter( param, Qt::CaseInsensitive );
2469 }
2470 else if ( constraint == QLatin1String( "!tag" ) )
2471 {
2472 resultNames = allNames( type );
2473 const QStringList unwanted = symbolsWithTag( type, tagId( param ) );
2474 for ( const QString &name : unwanted )
2475 {
2476 resultNames.removeAll( name );
2477 }
2478 }
2479 else if ( constraint == QLatin1String( "!name" ) )
2480 {
2481 const QStringList all = allNames( type );
2482 for ( const QString &str : all )
2483 {
2484 if ( !str.contains( param, Qt::CaseInsensitive ) )
2485 resultNames << str;
2486 }
2487 }
2488
2489 // not apply the operator
2490 if ( firstSet )
2491 {
2492 symbols = resultNames;
2493 firstSet = false;
2494 }
2495 else
2496 {
2497 if ( op == QLatin1String( "OR" ) )
2498 {
2499 symbols << resultNames;
2500 }
2501 else if ( op == QLatin1String( "AND" ) )
2502 {
2503 QStringList dummy = symbols;
2504 symbols.clear();
2505 for ( const QString &result : std::as_const( resultNames ) )
2506 {
2507 if ( dummy.contains( result ) )
2508 symbols << result;
2509 }
2510 }
2511 }
2512 } // DOM loop ends here
2513 }
2514
2515 // return sorted, unique list
2516 const QSet< QString > uniqueSet( symbols.constBegin(), symbols.constEnd() );
2517 QStringList unique( uniqueSet.begin(), uniqueSet.end() );
2518 std::sort( unique.begin(), unique.end() );
2519 return unique;
2520}
2521
2523{
2524 if ( !mCurrentDB )
2525 {
2526 QgsDebugError( QStringLiteral( "Cannot open database for listing groups" ) );
2527 return QgsSmartConditionMap();
2528 }
2529
2530 QgsSmartConditionMap condition;
2531
2532 QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2533
2535 int nError;
2536 statement = mCurrentDB.prepare( query, nError );
2537 if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2538 {
2539 QDomDocument doc;
2540 QString xmlstr = statement.columnAsText( 0 );
2541 if ( !doc.setContent( xmlstr ) )
2542 {
2543 QgsDebugError( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2544 }
2545
2546 QDomElement smartEl = doc.documentElement();
2547 QDomNodeList conditionNodes = smartEl.childNodes();
2548
2549 for ( int i = 0; i < conditionNodes.count(); i++ )
2550 {
2551 QDomElement condEl = conditionNodes.at( i ).toElement();
2552 QString constraint = condEl.attribute( QStringLiteral( "constraint" ) );
2553 QString param = condEl.attribute( QStringLiteral( "param" ) );
2554
2555 condition.insert( constraint, param );
2556 }
2557 }
2558
2559 return condition;
2560}
2561
2563{
2564 if ( !mCurrentDB )
2565 {
2566 QgsDebugError( QStringLiteral( "Cannot open database for listing groups" ) );
2567 return QString();
2568 }
2569
2570 QString op;
2571
2572 QString query = qgs_sqlite3_mprintf( "SELECT xml FROM smartgroup WHERE id=%d", id );
2573
2574 int nError;
2576 statement = mCurrentDB.prepare( query, nError );
2577 if ( nError == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
2578 {
2579 QDomDocument doc;
2580 QString xmlstr = statement.columnAsText( 0 );
2581 if ( !doc.setContent( xmlstr ) )
2582 {
2583 QgsDebugError( QStringLiteral( "Cannot open smartgroup id: %1" ).arg( id ) );
2584 }
2585 QDomElement smartEl = doc.documentElement();
2586 op = smartEl.attribute( QStringLiteral( "operator" ) );
2587 }
2588
2589 return op;
2590}
2591
2592bool QgsStyle::exportXml( const QString &filename )
2593{
2594 if ( filename.isEmpty() )
2595 {
2596 QgsDebugError( QStringLiteral( "Invalid filename for style export." ) );
2597 return false;
2598 }
2599
2600 QDomDocument doc( QStringLiteral( "qgis_style" ) );
2601 QDomElement root = doc.createElement( QStringLiteral( "qgis_style" ) );
2602 root.setAttribute( QStringLiteral( "version" ), QStringLiteral( STYLE_CURRENT_VERSION ) );
2603 doc.appendChild( root );
2604
2605 const QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
2606 const QStringList favoriteColorramps = symbolsOfFavorite( ColorrampEntity );
2607 const QStringList favoriteTextFormats = symbolsOfFavorite( TextFormatEntity );
2608 const QStringList favoriteLegendShapes = symbolsOfFavorite( LegendPatchShapeEntity );
2609 const QStringList favorite3DSymbols = symbolsOfFavorite( Symbol3DEntity );
2610
2611 // save symbols and attach tags
2612 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, QStringLiteral( "symbols" ), doc, QgsReadWriteContext() );
2613 QDomNodeList symbolsList = symbolsElem.elementsByTagName( QStringLiteral( "symbol" ) );
2614 int nbSymbols = symbolsList.count();
2615 for ( int i = 0; i < nbSymbols; ++i )
2616 {
2617 QDomElement symbol = symbolsList.at( i ).toElement();
2618 QString name = symbol.attribute( QStringLiteral( "name" ) );
2619 QStringList tags = tagsOfSymbol( SymbolEntity, name );
2620 if ( tags.count() > 0 )
2621 {
2622 symbol.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2623 }
2624 if ( favoriteSymbols.contains( name ) )
2625 {
2626 symbol.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2627 }
2628 }
2629
2630 // save color ramps
2631 QDomElement rampsElem = doc.createElement( QStringLiteral( "colorramps" ) );
2632 for ( QMap<QString, QgsColorRamp *>::const_iterator itr = mColorRamps.constBegin(); itr != mColorRamps.constEnd(); ++itr )
2633 {
2634 QDomElement rampEl = QgsSymbolLayerUtils::saveColorRamp( itr.key(), itr.value(), doc );
2635 QStringList tags = tagsOfSymbol( ColorrampEntity, itr.key() );
2636 if ( tags.count() > 0 )
2637 {
2638 rampEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2639 }
2640 if ( favoriteColorramps.contains( itr.key() ) )
2641 {
2642 rampEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2643 }
2644 rampsElem.appendChild( rampEl );
2645 }
2646
2647 // save text formats
2648 QDomElement textFormatsElem = doc.createElement( QStringLiteral( "textformats" ) );
2649 for ( auto it = mTextFormats.constBegin(); it != mTextFormats.constEnd(); ++it )
2650 {
2651 QDomElement textFormatEl = doc.createElement( QStringLiteral( "textformat" ) );
2652 textFormatEl.setAttribute( QStringLiteral( "name" ), it.key() );
2653 QDomElement textStyleEl = it.value().writeXml( doc, QgsReadWriteContext() );
2654 textFormatEl.appendChild( textStyleEl );
2655 QStringList tags = tagsOfSymbol( TextFormatEntity, it.key() );
2656 if ( tags.count() > 0 )
2657 {
2658 textFormatEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2659 }
2660 if ( favoriteTextFormats.contains( it.key() ) )
2661 {
2662 textFormatEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2663 }
2664 textFormatsElem.appendChild( textFormatEl );
2665 }
2666
2667 // save label settings
2668 QDomElement labelSettingsElem = doc.createElement( QStringLiteral( "labelsettings" ) );
2669 for ( auto it = mLabelSettings.constBegin(); it != mLabelSettings.constEnd(); ++it )
2670 {
2671 QDomElement labelSettingsEl = doc.createElement( QStringLiteral( "labelsetting" ) );
2672 labelSettingsEl.setAttribute( QStringLiteral( "name" ), it.key() );
2673 QDomElement defEl = it.value().writeXml( doc, QgsReadWriteContext() );
2674 labelSettingsEl.appendChild( defEl );
2675 QStringList tags = tagsOfSymbol( LabelSettingsEntity, it.key() );
2676 if ( tags.count() > 0 )
2677 {
2678 labelSettingsEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2679 }
2680 if ( favoriteTextFormats.contains( it.key() ) )
2681 {
2682 labelSettingsEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2683 }
2684 labelSettingsElem.appendChild( labelSettingsEl );
2685 }
2686
2687 // save legend patch shapes
2688 QDomElement legendPatchShapesElem = doc.createElement( QStringLiteral( "legendpatchshapes" ) );
2689 for ( auto it = mLegendPatchShapes.constBegin(); it != mLegendPatchShapes.constEnd(); ++it )
2690 {
2691 QDomElement legendPatchShapeEl = doc.createElement( QStringLiteral( "legendpatchshape" ) );
2692 legendPatchShapeEl.setAttribute( QStringLiteral( "name" ), it.key() );
2693 QDomElement defEl = doc.createElement( QStringLiteral( "definition" ) );
2694 it.value().writeXml( defEl, doc, QgsReadWriteContext() );
2695 legendPatchShapeEl.appendChild( defEl );
2696 QStringList tags = tagsOfSymbol( LegendPatchShapeEntity, it.key() );
2697 if ( tags.count() > 0 )
2698 {
2699 legendPatchShapeEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2700 }
2701 if ( favoriteLegendShapes.contains( it.key() ) )
2702 {
2703 legendPatchShapeEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2704 }
2705 legendPatchShapesElem.appendChild( legendPatchShapeEl );
2706 }
2707
2708 // save symbols and attach tags
2709 QDomElement symbols3DElem = doc.createElement( QStringLiteral( "symbols3d" ) );
2710 for ( auto it = m3dSymbols.constBegin(); it != m3dSymbols.constEnd(); ++it )
2711 {
2712 QDomElement symbolEl = doc.createElement( QStringLiteral( "symbol3d" ) );
2713 symbolEl.setAttribute( QStringLiteral( "name" ), it.key() );
2714 QDomElement defEl = doc.createElement( QStringLiteral( "definition" ) );
2715 defEl.setAttribute( QStringLiteral( "type" ), it.value()->type() );
2716 it.value()->writeXml( defEl, QgsReadWriteContext() );
2717 symbolEl.appendChild( defEl );
2718 QStringList tags = tagsOfSymbol( Symbol3DEntity, it.key() );
2719 if ( tags.count() > 0 )
2720 {
2721 symbolEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
2722 }
2723 if ( favorite3DSymbols.contains( it.key() ) )
2724 {
2725 symbolEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
2726 }
2727 symbols3DElem.appendChild( symbolEl );
2728 }
2729
2730 root.appendChild( symbolsElem );
2731 root.appendChild( rampsElem );
2732 root.appendChild( textFormatsElem );
2733 root.appendChild( labelSettingsElem );
2734 root.appendChild( legendPatchShapesElem );
2735 root.appendChild( symbols3DElem );
2736
2737 // save
2738 QFile f( filename );
2739 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
2740 {
2741 mErrorString = "Couldn't open file for writing: " + filename;
2742 return false;
2743 }
2744
2745 QTextStream ts( &f );
2746#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2747 ts.setCodec( "UTF-8" );
2748#endif
2749 doc.save( ts, 2 );
2750 f.close();
2751
2752 return true;
2753}
2754
2755bool QgsStyle::importXml( const QString &filename )
2756{
2757 return importXml( filename, -1 );
2758}
2759
2760bool QgsStyle::importXml( const QString &filename, int sinceVersion )
2761{
2762 mErrorString = QString();
2763 QDomDocument doc( QStringLiteral( "style" ) );
2764 QFile f( filename );
2765 if ( !f.open( QFile::ReadOnly ) )
2766 {
2767 mErrorString = QStringLiteral( "Unable to open the specified file" );
2768 QgsDebugError( QStringLiteral( "Error opening the style XML file." ) );
2769 return false;
2770 }
2771
2772 if ( !doc.setContent( &f ) )
2773 {
2774 mErrorString = QStringLiteral( "Unable to understand the style file: %1" ).arg( filename );
2775 QgsDebugError( QStringLiteral( "XML Parsing error" ) );
2776 f.close();
2777 return false;
2778 }
2779 f.close();
2780
2781 QDomElement docEl = doc.documentElement();
2782 if ( docEl.tagName() != QLatin1String( "qgis_style" ) )
2783 {
2784 mErrorString = "Incorrect root tag in style: " + docEl.tagName();
2785 return false;
2786 }
2787
2788 const QString version = docEl.attribute( QStringLiteral( "version" ) );
2789 if ( version != QLatin1String( STYLE_CURRENT_VERSION ) && version != QLatin1String( "0" ) && version != QLatin1String( "1" ) )
2790 {
2791 mErrorString = "Unknown style file version: " + version;
2792 return false;
2793 }
2794
2795 QgsSymbolMap symbols;
2796
2797 QDomElement symbolsElement = docEl.firstChildElement( QStringLiteral( "symbols" ) );
2798 QDomElement e = symbolsElement.firstChildElement();
2799
2800 // gain speed by re-grouping the INSERT statements in a transaction
2801 QString query = qgs_sqlite3_mprintf( "BEGIN TRANSACTION;" );
2802 runEmptyQuery( query );
2803
2804 if ( version == QLatin1String( STYLE_CURRENT_VERSION ) || version == QLatin1String( "1" ) )
2805 {
2806 // For the new style, load symbols individually
2807 for ( ; !e.isNull(); e = e.nextSiblingElement() )
2808 {
2809 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2810 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2811 {
2812 // skip the symbol, should already be present
2813 continue;
2814 }
2815
2816 if ( e.tagName() == QLatin1String( "symbol" ) )
2817 {
2818 QString name = e.attribute( QStringLiteral( "name" ) );
2819 QStringList tags;
2820 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2821 {
2822 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2823 }
2824 bool favorite = false;
2825 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2826 {
2827 favorite = true;
2828 }
2829
2830 std::unique_ptr< QgsSymbol > symbol = QgsSymbolLayerUtils::loadSymbol( e, QgsReadWriteContext() );
2831 if ( symbol )
2832 {
2833 QgsSymbol *symbolPtr = symbol.get();
2834 addSymbol( name, symbol.release() );
2835 if ( mCurrentDB )
2836 {
2837 saveSymbol( name, symbolPtr, favorite, tags );
2838 }
2839 }
2840 }
2841 else
2842 {
2843 QgsDebugError( "unknown tag: " + e.tagName() );
2844 }
2845 }
2846 }
2847 else
2848 {
2849 // for the old version, use the utility function to solve @symbol@layer subsymbols
2850 symbols = QgsSymbolLayerUtils::loadSymbols( symbolsElement, QgsReadWriteContext() );
2851
2852 // save the symbols with proper name
2853 for ( QMap<QString, QgsSymbol *>::iterator it = symbols.begin(); it != symbols.end(); ++it )
2854 {
2855 addSymbol( it.key(), it.value() );
2856 }
2857 }
2858
2859 // load color ramps
2860 QDomElement rampsElement = docEl.firstChildElement( QStringLiteral( "colorramps" ) );
2861 e = rampsElement.firstChildElement();
2862 for ( ; !e.isNull(); e = e.nextSiblingElement() )
2863 {
2864 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2865 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2866 {
2867 // skip the ramp, should already be present
2868 continue;
2869 }
2870
2871 if ( e.tagName() == QLatin1String( "colorramp" ) )
2872 {
2873 QString name = e.attribute( QStringLiteral( "name" ) );
2874 QStringList tags;
2875 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2876 {
2877 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2878 }
2879 bool favorite = false;
2880 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2881 {
2882 favorite = true;
2883 }
2884
2885 std::unique_ptr< QgsColorRamp > ramp = QgsSymbolLayerUtils::loadColorRamp( e );
2886 if ( ramp )
2887 {
2888 QgsColorRamp *rampPtr = ramp.get();
2889 addColorRamp( name, ramp.release() );
2890 if ( mCurrentDB )
2891 {
2892 saveColorRamp( name, rampPtr, favorite, tags );
2893 }
2894 }
2895 }
2896 else
2897 {
2898 QgsDebugError( "unknown tag: " + e.tagName() );
2899 }
2900 }
2901
2902 // load text formats
2903
2904 // this is ONLY safe to do if we have a QGuiApplication-- it requires QFontDatabase, which is not available otherwise!
2905 if ( qobject_cast< QGuiApplication * >( QCoreApplication::instance() ) )
2906 {
2907 if ( version == STYLE_CURRENT_VERSION )
2908 {
2909 const QDomElement textFormatElement = docEl.firstChildElement( QStringLiteral( "textformats" ) );
2910 e = textFormatElement.firstChildElement();
2911 for ( ; !e.isNull(); e = e.nextSiblingElement() )
2912 {
2913 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2914 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2915 {
2916 // skip the format, should already be present
2917 continue;
2918 }
2919
2920 if ( e.tagName() == QLatin1String( "textformat" ) )
2921 {
2922 QString name = e.attribute( QStringLiteral( "name" ) );
2923 QStringList tags;
2924 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2925 {
2926 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2927 }
2928 bool favorite = false;
2929 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2930 {
2931 favorite = true;
2932 }
2933
2934 QgsTextFormat format;
2935 const QDomElement styleElem = e.firstChildElement();
2936 format.readXml( styleElem, QgsReadWriteContext() );
2937 addTextFormat( name, format );
2938 if ( mCurrentDB )
2939 {
2940 saveTextFormat( name, format, favorite, tags );
2941 }
2942 }
2943 else
2944 {
2945 QgsDebugError( "unknown tag: " + e.tagName() );
2946 }
2947 }
2948 }
2949
2950 // load label settings
2951 if ( version == STYLE_CURRENT_VERSION )
2952 {
2953 const QDomElement labelSettingsElement = docEl.firstChildElement( QStringLiteral( "labelsettings" ) );
2954 e = labelSettingsElement.firstChildElement();
2955 for ( ; !e.isNull(); e = e.nextSiblingElement() )
2956 {
2957 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
2958 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
2959 {
2960 // skip the settings, should already be present
2961 continue;
2962 }
2963
2964 if ( e.tagName() == QLatin1String( "labelsetting" ) )
2965 {
2966 QString name = e.attribute( QStringLiteral( "name" ) );
2967 QStringList tags;
2968 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
2969 {
2970 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
2971 }
2972 bool favorite = false;
2973 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
2974 {
2975 favorite = true;
2976 }
2977
2978 QgsPalLayerSettings settings;
2979 const QDomElement styleElem = e.firstChildElement();
2980 settings.readXml( styleElem, QgsReadWriteContext() );
2981 addLabelSettings( name, settings );
2982 if ( mCurrentDB )
2983 {
2984 saveLabelSettings( name, settings, favorite, tags );
2985 }
2986 }
2987 else
2988 {
2989 QgsDebugError( "unknown tag: " + e.tagName() );
2990 }
2991 }
2992 }
2993 }
2994
2995 // load legend patch shapes
2996 if ( version == STYLE_CURRENT_VERSION )
2997 {
2998 const QDomElement legendPatchShapesElement = docEl.firstChildElement( QStringLiteral( "legendpatchshapes" ) );
2999 e = legendPatchShapesElement.firstChildElement();
3000 for ( ; !e.isNull(); e = e.nextSiblingElement() )
3001 {
3002 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
3003 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
3004 {
3005 // skip the shape, should already be present
3006 continue;
3007 }
3008
3009 if ( e.tagName() == QLatin1String( "legendpatchshape" ) )
3010 {
3011 QString name = e.attribute( QStringLiteral( "name" ) );
3012 QStringList tags;
3013 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
3014 {
3015 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
3016 }
3017 bool favorite = false;
3018 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
3019 {
3020 favorite = true;
3021 }
3022
3023 QgsLegendPatchShape shape;
3024 const QDomElement shapeElem = e.firstChildElement();
3025 shape.readXml( shapeElem, QgsReadWriteContext() );
3026 addLegendPatchShape( name, shape );
3027 if ( mCurrentDB )
3028 {
3029 saveLegendPatchShape( name, shape, favorite, tags );
3030 }
3031 }
3032 else
3033 {
3034 QgsDebugError( "unknown tag: " + e.tagName() );
3035 }
3036 }
3037 }
3038
3039 // load 3d symbols
3040 if ( version == STYLE_CURRENT_VERSION )
3041 {
3042 const QDomElement symbols3DElement = docEl.firstChildElement( QStringLiteral( "symbols3d" ) );
3043 e = symbols3DElement.firstChildElement();
3044 for ( ; !e.isNull(); e = e.nextSiblingElement() )
3045 {
3046 const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
3047 if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
3048 {
3049 // skip the symbol, should already be present
3050 continue;
3051 }
3052
3053 if ( e.tagName() == QLatin1String( "symbol3d" ) )
3054 {
3055 QString name = e.attribute( QStringLiteral( "name" ) );
3056 QStringList tags;
3057 if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
3058 {
3059 tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
3060 }
3061 bool favorite = false;
3062 if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QLatin1String( "1" ) )
3063 {
3064 favorite = true;
3065 }
3066
3067 const QDomElement symbolElem = e.firstChildElement();
3068 const QString type = symbolElem.attribute( QStringLiteral( "type" ) );
3069 std::unique_ptr< QgsAbstract3DSymbol > sym( QgsApplication::symbol3DRegistry()->createSymbol( type ) );
3070 if ( sym )
3071 {
3072 sym->readXml( symbolElem, QgsReadWriteContext() );
3073 QgsAbstract3DSymbol *newSym = sym.get();
3074 addSymbol3D( name, sym.release() );
3075 if ( mCurrentDB )
3076 {
3077 saveSymbol3D( name, newSym, favorite, tags );
3078 }
3079 }
3080 }
3081 else
3082 {
3083 QgsDebugError( "unknown tag: " + e.tagName() );
3084 }
3085 }
3086 }
3087
3088 query = qgs_sqlite3_mprintf( "COMMIT TRANSACTION;" );
3089 runEmptyQuery( query );
3090
3091 return true;
3092}
3093
3094bool QgsStyle::isXmlStyleFile( const QString &path )
3095{
3096 QFileInfo fileInfo( path );
3097
3098 if ( fileInfo.suffix().compare( QLatin1String( "xml" ), Qt::CaseInsensitive ) != 0 )
3099 return false;
3100
3101 // sniff the first line of the file to see if it's a style file
3102 if ( !QFile::exists( path ) )
3103 return false;
3104
3105 QFile inputFile( path );
3106 if ( !inputFile.open( QIODevice::ReadOnly ) )
3107 return false;
3108
3109 QTextStream stream( &inputFile );
3110 const QString line = stream.readLine();
3111 return line == QLatin1String( "<!DOCTYPE qgis_style>" );
3112}
3113
3118
3120{
3121 return mReadOnly;
3122}
3123
3124void QgsStyle::setReadOnly( bool readOnly )
3125{
3126 mReadOnly = readOnly;
3127}
3128
3129bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
3130{
3131 QDomDocument doc( QStringLiteral( "dummy" ) );
3132 QDomElement symEl;
3133 QByteArray xmlArray;
3134 QTextStream stream( &xmlArray );
3135#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3136 stream.setCodec( "UTF-8" );
3137#endif
3138
3139 QString query;
3140
3141 switch ( type )
3142 {
3143 case SymbolEntity:
3144 {
3145 // check if it is an existing symbol
3146 auto it = mSymbols.constFind( name );
3147 if ( it == mSymbols.constEnd() || !it.value() )
3148 {
3149 QgsDebugError( QStringLiteral( "Update request received for unavailable symbol" ) );
3150 return false;
3151 }
3152
3153 symEl = QgsSymbolLayerUtils::saveSymbol( name, it.value(), doc, QgsReadWriteContext() );
3154 if ( symEl.isNull() )
3155 {
3156 QgsDebugError( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
3157 return false;
3158 }
3159 symEl.save( stream, 4 );
3160 query = qgs_sqlite3_mprintf( "UPDATE symbol SET xml='%q' WHERE name='%q';",
3161 xmlArray.constData(), name.toUtf8().constData() );
3162 break;
3163 }
3164
3165 case Symbol3DEntity:
3166 {
3167 // check if it is an existing symbol
3168 auto it = m3dSymbols.constFind( name );
3169 if ( it == m3dSymbols.constEnd() || !it.value() )
3170 {
3171 QgsDebugError( QStringLiteral( "Update request received for unavailable symbol" ) );
3172 return false;
3173 }
3174
3175 symEl = doc.createElement( QStringLiteral( "symbol" ) );
3176 symEl.setAttribute( QStringLiteral( "type" ), it.value()->type() );
3177 it.value()->writeXml( symEl, QgsReadWriteContext() );
3178 if ( symEl.isNull() )
3179 {
3180 QgsDebugError( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
3181 return false;
3182 }
3183 symEl.save( stream, 4 );
3184 query = qgs_sqlite3_mprintf( "UPDATE symbol3d SET xml='%q' WHERE name='%q';",
3185 xmlArray.constData(), name.toUtf8().constData() );
3186 break;
3187 }
3188
3189 case ColorrampEntity:
3190 {
3191 auto it = mColorRamps.constFind( name );
3192 if ( it == mColorRamps.constEnd() || !it.value() )
3193 {
3194 QgsDebugError( QStringLiteral( "Update requested for unavailable color ramp." ) );
3195 return false;
3196 }
3197
3198 symEl = QgsSymbolLayerUtils::saveColorRamp( name, it.value(), doc );
3199 if ( symEl.isNull() )
3200 {
3201 QgsDebugError( QStringLiteral( "Couldn't convert color ramp to valid XML!" ) );
3202 return false;
3203 }
3204 symEl.save( stream, 4 );
3205 query = qgs_sqlite3_mprintf( "UPDATE colorramp SET xml='%q' WHERE name='%q';",
3206 xmlArray.constData(), name.toUtf8().constData() );
3207 break;
3208 }
3209
3210 case TextFormatEntity:
3211 {
3212 auto it = mTextFormats.constFind( name );
3213 if ( it == mTextFormats.constEnd() )
3214 {
3215 QgsDebugError( QStringLiteral( "Update requested for unavailable text format." ) );
3216 return false;
3217 }
3218
3219 symEl = it.value().writeXml( doc, QgsReadWriteContext() );
3220 if ( symEl.isNull() )
3221 {
3222 QgsDebugError( QStringLiteral( "Couldn't convert text format to valid XML!" ) );
3223 return false;
3224 }
3225 symEl.save( stream, 4 );
3226 query = qgs_sqlite3_mprintf( "UPDATE textformat SET xml='%q' WHERE name='%q';",
3227 xmlArray.constData(), name.toUtf8().constData() );
3228 break;
3229 }
3230
3232 {
3233 auto it = mLabelSettings.constFind( name );
3234 if ( it == mLabelSettings.constEnd() )
3235 {
3236 QgsDebugError( QStringLiteral( "Update requested for unavailable label settings." ) );
3237 return false;
3238 }
3239
3240 symEl = it.value().writeXml( doc, QgsReadWriteContext() );
3241 if ( symEl.isNull() )
3242 {
3243 QgsDebugError( QStringLiteral( "Couldn't convert label settings to valid XML!" ) );
3244 return false;
3245 }
3246 symEl.save( stream, 4 );
3247 query = qgs_sqlite3_mprintf( "UPDATE labelsettings SET xml='%q' WHERE name='%q';",
3248 xmlArray.constData(), name.toUtf8().constData() );
3249 break;
3250 }
3251
3253 {
3254 auto it = mLegendPatchShapes.constFind( name );
3255 if ( it == mLegendPatchShapes.constEnd() )
3256 {
3257 QgsDebugError( QStringLiteral( "Update requested for unavailable legend patch shape." ) );
3258 return false;
3259 }
3260
3261 symEl = doc.createElement( QStringLiteral( "shape" ) );
3262 it.value().writeXml( symEl, doc, QgsReadWriteContext() );
3263 symEl.save( stream, 4 );
3264 query = qgs_sqlite3_mprintf( "UPDATE legendpatchshapes SET xml='%q' WHERE name='%q';",
3265 xmlArray.constData(), name.toUtf8().constData() );
3266 break;
3267 }
3268
3269 case TagEntity:
3270 case SmartgroupEntity:
3271 {
3272 QgsDebugError( QStringLiteral( "Updating the unsupported StyleEntity" ) );
3273 return false;
3274 }
3275 }
3276
3277
3278 if ( !runEmptyQuery( query ) )
3279 {
3280 QgsDebugError( QStringLiteral( "Couldn't update symbol into the database!" ) );
3281 return false;
3282 }
3283 else
3284 {
3285 switch ( type )
3286 {
3287 case SymbolEntity:
3288 emit symbolChanged( name );
3289 break;
3290
3291 case ColorrampEntity:
3292 emit rampChanged( name );
3293 break;
3294
3295 case TextFormatEntity:
3296 emit textFormatChanged( name );
3297 break;
3298
3300 emit labelSettingsChanged( name );
3301 break;
3302
3304 case TagEntity:
3305 case SmartgroupEntity:
3306 case Symbol3DEntity:
3307 break;
3308 }
3309 emit entityChanged( type, name );
3310 }
3311 return true;
3312}
3313
3314void QgsStyle::clearCachedTags( QgsStyle::StyleEntity type, const QString &name )
3315{
3316 mCachedTags[ type ].remove( name );
3317}
3318
3319bool QgsStyle::createStyleMetadataTableIfNeeded()
3320{
3321 // make sure metadata table exists
3322 QString query = qgs_sqlite3_mprintf( "SELECT name FROM sqlite_master WHERE name='stylemetadata'" );
3323 sqlite3_statement_unique_ptr statement;
3324 int rc;
3325 statement = mCurrentDB.prepare( query, rc );
3326
3327 if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
3328 {
3329 // no metadata table
3330 query = qgs_sqlite3_mprintf( "CREATE TABLE stylemetadata("\
3331 "id INTEGER PRIMARY KEY,"\
3332 "key TEXT UNIQUE,"\
3333 "value TEXT);" );
3334 runEmptyQuery( query );
3335 query = qgs_sqlite3_mprintf( "INSERT INTO stylemetadata VALUES (NULL, '%q', '%q')", "version", QString::number( Qgis::versionInt() ).toUtf8().constData() );
3336 runEmptyQuery( query );
3337 return true;
3338 }
3339 else
3340 {
3341 return false;
3342 }
3343}
3344
3345void QgsStyle::upgradeIfRequired()
3346{
3347 // make sure metadata table exists
3348 int dbVersion = 0;
3349 if ( !createStyleMetadataTableIfNeeded() )
3350 {
3351 const QString query = qgs_sqlite3_mprintf( "SELECT value FROM stylemetadata WHERE key='version'" );
3352 int rc;
3353 sqlite3_statement_unique_ptr statement = mCurrentDB.prepare( query, rc );
3354 if ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
3355 {
3356 dbVersion = statement.columnAsText( 0 ).toInt();
3357 }
3358 }
3359
3360 if ( dbVersion < Qgis::versionInt() )
3361 {
3362 // do upgrade
3363 if ( importXml( QgsApplication::defaultStylePath(), dbVersion ) )
3364 {
3365 const QString query = qgs_sqlite3_mprintf( "UPDATE stylemetadata SET value='%q' WHERE key='version'", QString::number( Qgis::versionInt() ).toUtf8().constData() );
3366 runEmptyQuery( query );
3367 }
3368 }
3369}
3370
3371QString QgsStyle::entityTableName( QgsStyle::StyleEntity type )
3372{
3373 switch ( type )
3374 {
3375 case SymbolEntity:
3376 return QStringLiteral( "symbol" );
3377
3378 case ColorrampEntity:
3379 return QStringLiteral( "colorramp" );
3380
3381 case TextFormatEntity:
3382 return QStringLiteral( "textformat" );
3383
3385 return QStringLiteral( "labelsettings" );
3386
3388 return QStringLiteral( "legendpatchshapes" );
3389
3390 case Symbol3DEntity:
3391 return QStringLiteral( "symbol3d" );
3392
3393 case TagEntity:
3394 return QStringLiteral( "tag" );
3395
3396 case SmartgroupEntity:
3397 return QStringLiteral( "smartgroup" );
3398 }
3399 return QString();
3400}
3401
3402QString QgsStyle::tagmapTableName( QgsStyle::StyleEntity type )
3403{
3404 switch ( type )
3405 {
3406 case SymbolEntity:
3407 return QStringLiteral( "tagmap" );
3408
3409 case ColorrampEntity:
3410 return QStringLiteral( "ctagmap" );
3411
3412 case TextFormatEntity:
3413 return QStringLiteral( "tftagmap" );
3414
3416 return QStringLiteral( "lstagmap" );
3417
3419 return QStringLiteral( "lpstagmap" );
3420
3421 case Symbol3DEntity:
3422 return QStringLiteral( "symbol3dtagmap" );
3423
3424 case TagEntity:
3425 case SmartgroupEntity:
3426 break;
3427 }
3428 return QString();
3429}
3430
3431QString QgsStyle::tagmapEntityIdFieldName( QgsStyle::StyleEntity type )
3432{
3433 switch ( type )
3434 {
3435 case SymbolEntity:
3436 return QStringLiteral( "symbol_id" );
3437
3438 case ColorrampEntity:
3439 return QStringLiteral( "colorramp_id" );
3440
3441 case TextFormatEntity:
3442 return QStringLiteral( "textformat_id" );
3443
3445 return QStringLiteral( "labelsettings_id" );
3446
3448 return QStringLiteral( "legendpatchshape_id" );
3449
3450 case Symbol3DEntity:
3451 return QStringLiteral( "symbol3d_id" );
3452
3453 case TagEntity:
3454 case SmartgroupEntity:
3455 break;
3456 }
3457 return QString();
3458}
3459
3464
3469
3474
3479
3484
@ ScaleArea
Calculate scale by the area.
Definition qgis.h:625
@ Circle
Circle.
Definition qgis.h:3089
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:358
@ Unknown
Unknown types.
Definition qgis.h:362
static int versionInt()
Version number used for comparing versions using the "Check QGIS Version" function.
Definition qgis.cpp:682
SymbolType
Symbol types.
Definition qgis.h:610
@ Marker
Marker symbol.
Definition qgis.h:611
@ Line
Line symbol.
Definition qgis.h:612
@ Fill
Fill symbol.
Definition qgis.h:613
@ Hybrid
Hybrid symbol.
Definition qgis.h:614
QStringList symbolTypes() const
Returns a list of all available symbol types.
Abstract base class for 3D symbols that are used by VectorLayer3DRenderer objects.
virtual QgsAbstract3DSymbol * clone() const =0
Returns a new instance of the symbol with the same settings.
static QString userStylePath()
Returns the path to user's style.
static QString defaultStylePath()
Returns the path to default style (works as a starting point).
static Qgs3DSymbolRegistry * symbol3DRegistry()
Returns registry of available 3D symbols.
Abstract base class for color ramps.
virtual double value(int index) const =0
Returns relative value between [0,1] of color at specified index.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
A geometry is the spatial representation of a feature.
Represents a patch shape for use in map legends.
void readXml(const QDomElement &element, const QgsReadWriteContext &context)
Read settings from a DOM element.
QList< QList< QPolygonF > > toQPolygonF(Qgis::SymbolType type, QSizeF size) const
Converts the patch shape to a set of QPolygonF objects representing how the patch should be drawn for...
void writeXml(QDomElement &element, QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
Qgis::SymbolType symbolType() const
Returns the symbol type associated with this patch.
Line string geometry type, with support for z-dimension and m-values.
Contains settings for how a map layer will be labeled.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QgsTextFormat defaultTextFormat() const
Returns the project default text format.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:109
const QgsProjectStyleSettings * styleSettings() const
Returns the project's style settings, which contains settings and properties relating to how a QgsPro...
A container for the context for various read/write operations on objects.
Scoped object for logging of the runtime for a single operation or group of operations.
Stores settings for use within QGIS.
Definition qgssettings.h:65
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
A color ramp entity for QgsStyle databases.
Definition qgsstyle.h:1429
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
An interface for entities which can be placed in a QgsStyle database.
Definition qgsstyle.h:1349
virtual QgsStyle::StyleEntity type() const =0
Returns the type of style entity.
A label settings entity for QgsStyle databases.
Definition qgsstyle.h:1490
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
A legend patch shape entity for QgsStyle databases.
Definition qgsstyle.h:1520
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
A 3d symbol entity for QgsStyle databases.
Definition qgsstyle.h:1550
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1397
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
A text format entity for QgsStyle databases.
Definition qgsstyle.h:1460
QgsStyle::StyleEntity type() const override
Returns the type of style entity.
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:88
bool addEntity(const QString &name, const QgsStyleEntityInterface *entity, bool update=false)
Adds an entity to the style, with the specified name.
Definition qgsstyle.cpp:113
void setFileName(const QString &filename)
Sets the current file name of the style database.
Definition qgsstyle.cpp:901
void labelSettingsChanged(const QString &name)
Emitted whenever a label setting's definition is changed.
int colorRampCount()
Returns count of color ramps.
Definition qgsstyle.cpp:513
bool detagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Detags the symbol with the given list.
QgsTextFormat textFormat(const QString &name) const
Returns the text format with the specified name.
QStringList allNames(StyleEntity type) const
Returns a list of the names of all existing entities of the specified type.
QgsLegendPatchShape defaultPatch(Qgis::SymbolType type, QSizeF size) const
Returns the default legend patch shape for the given symbol type.
bool remove(StyleEntity type, int id)
Removes the specified entity from the database.
bool removeSymbol(const QString &name)
Removes symbol from style (and delete it).
Definition qgsstyle.cpp:286
void entityChanged(QgsStyle::StyleEntity entity, const QString &name)
Emitted whenever an entity's definition is changed.
bool removeLabelSettings(const QString &name)
Removes label settings from the style.
void labelSettingsAdded(const QString &name)
Emitted whenever label settings have been added to the style and the database has been updated as a r...
QStringList tags() const
Returns a list of all tags in the style database.
QString tag(int id) const
Returns the tag name for the given id.
QgsSmartConditionMap smartgroup(int id)
Returns the QgsSmartConditionMap for the given id.
bool renameColorRamp(const QString &oldName, const QString &newName)
Changes ramp's name.
Definition qgsstyle.cpp:946
void rampAdded(const QString &name)
Emitted whenever a color ramp has been added to the style and the database has been updated as a resu...
QStringList symbol3DNames() const
Returns a list of names of 3d symbols in the style.
bool tagSymbol(StyleEntity type, const QString &symbol, const QStringList &tags)
Tags the symbol with the tags in the list.
int entityId(StyleEntity type, const QString &name)
Returns the id in the style database for the given name of the specified entity type.
void rebuildIconPreviews()
Emitted whenever icon previews for entities in the style must be rebuilt.
bool saveLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool favorite, const QStringList &tags)
Adds label settings to the database.
Q_DECL_DEPRECATED bool save(const QString &filename=QString())
Saves style into a file.
Definition qgsstyle.cpp:891
bool symbolHasTag(StyleEntity type, const QString &symbol, const QString &tag)
Returns whether a given tag is associated with the symbol.
void aboutToBeDestroyed()
Emitted just before the style object is destroyed.
QgsTextFormat defaultTextFormat(QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling) const
Returns the default text format to use for new text based objects in the specified context.
@ XML
Label settings definition (as XML).
Definition qgsstyle.h:168
@ Name
Label settings name.
Definition qgsstyle.h:167
bool createDatabase(const QString &filename)
Creates an on-disk database.
Definition qgsstyle.cpp:556
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
const QgsColorRamp * colorRampRef(const QString &name) const
Returns a const pointer to a symbol (doesn't create new instance).
Definition qgsstyle.cpp:508
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
bool addColorRamp(const QString &name, QgsColorRamp *colorRamp, bool update=false)
Adds a color ramp to the style.
Definition qgsstyle.cpp:348
bool removeTextFormat(const QString &name)
Removes a text format from the style.
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition qgsstyle.cpp:320
void labelSettingsRemoved(const QString &name)
Emitted whenever label settings have been removed from the style and the database has been updated as...
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
int labelSettingsCount() const
Returns count of label settings in the style.
StyleEntity
Enum for Entities involved in a style.
Definition qgsstyle.h:204
@ LabelSettingsEntity
Label settings.
Definition qgsstyle.h:210
@ TextFormatEntity
Text formats.
Definition qgsstyle.h:209
@ SmartgroupEntity
Smart groups.
Definition qgsstyle.h:208
@ Symbol3DEntity
3D symbol entity
Definition qgsstyle.h:212
@ SymbolEntity
Symbols.
Definition qgsstyle.h:205
@ TagEntity
Tags.
Definition qgsstyle.h:206
@ ColorrampEntity
Color ramps.
Definition qgsstyle.h:207
@ LegendPatchShapeEntity
Legend patch shape.
Definition qgsstyle.h:211
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
void symbolRenamed(const QString &oldName, const QString &newName)
Emitted whenever a symbol has been renamed from oldName to newName.
QList< Qgis::GeometryType > symbol3DCompatibleGeometryTypes(const QString &name) const
Returns the list of the vector layer geometry types which are compatible with the 3D symbol with the ...
@ XML
Color ramp definition (as XML).
Definition qgsstyle.h:140
void groupsModified()
Emitted every time a tag or smartgroup has been added, removed, or renamed.
void clear()
Removes all contents of the style.
Definition qgsstyle.cpp:210
int smartgroupId(const QString &smartgroup)
Returns the database id for the given smartgroup name.
void rampRemoved(const QString &name)
Emitted whenever a color ramp has been removed from the style and the database has been updated as a ...
void entityRenamed(QgsStyle::StyleEntity entity, const QString &oldName, const QString &newName)
Emitted whenever a entity of the specified type has been renamed from oldName to newName.
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn't create new instance).
Definition qgsstyle.cpp:332
int addSmartgroup(const QString &name, const QString &op, const QgsSmartConditionMap &conditions)
Adds a new smartgroup to the database and returns the id.
QStringList colorRampNames() const
Returns a list of names of color ramps.
Definition qgsstyle.cpp:518
void textFormatChanged(const QString &name)
Emitted whenever a text format's definition is changed.
bool addSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool update=false)
Adds a 3d symbol with the specified name to the style.
Definition qgsstyle.cpp:439
QStringList legendPatchShapeNames() const
Returns a list of names of legend patch shapes in the style.
bool renameLegendPatchShape(const QString &oldName, const QString &newName)
Changes a legend patch shape's name.
static void cleanDefaultStyle()
Deletes the default style. Only to be used by QgsApplication::exitQgis().
Definition qgsstyle.cpp:204
void symbolSaved(const QString &name, const QgsSymbol *symbol)
Emitted every time a new symbol has been added to the database.
void textFormatRenamed(const QString &oldName, const QString &newName)
Emitted whenever a text format has been renamed from oldName to newName.
void triggerIconRebuild()
Triggers emission of the rebuildIconPreviews() signal.
void setName(const QString &name)
Sets the name of the style.
Definition qgsstyle.cpp:103
bool removeEntityByName(StyleEntity type, const QString &name)
Removes the entry of the specified type with matching name from the database.
void labelSettingsRenamed(const QString &oldName, const QString &newName)
Emitted whenever label settings have been renamed from oldName to newName.
void initialized()
Emitted when the style database has been fully initialized.
@ XML
Text format definition (as XML).
Definition qgsstyle.h:154
@ Name
Text format name.
Definition qgsstyle.h:153
int textFormatCount() const
Returns count of text formats in the style.
QStringList findSymbols(StyleEntity type, const QString &qword)
Returns the names of the symbols which have a matching 'substring' in its definition.
int tagId(const QString &tag)
Returns the database id for the given tag name.
void rampRenamed(const QString &oldName, const QString &newName)
Emitted whenever a color ramp has been renamed from oldName to newName.
bool exportXml(const QString &filename)
Exports the style as a XML file.
void createTables()
Creates tables structure for new database.
Definition qgsstyle.cpp:586
bool isReadOnly() const
Returns true if the style is considered a read-only library.
bool addLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool update=false)
Adds a legend patch shape with the specified name to the style.
Definition qgsstyle.cpp:417
int textFormatId(const QString &name)
Returns the ID in the style database for the given text format by name.
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition qgsstyle.cpp:502
int symbolCount()
Returns count of symbols in style.
Definition qgsstyle.cpp:337
bool renameEntity(StyleEntity type, const QString &oldName, const QString &newName)
Renames an entity of the specified type from oldName to newName.
Definition qgsstyle.cpp:291
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:147
@ Name
Smart group name.
Definition qgsstyle.h:181
static bool isXmlStyleFile(const QString &path)
Tests if the file at path is a QGIS style XML file.
int symbol3DCount() const
Returns count of 3D symbols in the style.
bool createMemoryDatabase()
Creates a temporary memory database.
Definition qgsstyle.cpp:571
int colorrampId(const QString &name)
Returns the id in the style database for the given colorramp name returns 0 if not found.
bool saveSymbol(const QString &name, const QgsSymbol *symbol, bool favorite, const QStringList &tags)
Adds the symbol to the database with tags.
Definition qgsstyle.cpp:250
QStringList labelSettingsNames() const
Returns a list of names of label settings in the style.
bool load(const QString &filename)
Loads a file into the style.
Definition qgsstyle.cpp:646
QStringList smartgroupNames() const
Returns the smart groups list.
bool rename(StyleEntity type, int id, const QString &newName)
Renames the given entity with the specified id.
bool renameTextFormat(const QString &oldName, const QString &newName)
Changes a text format's name.
void textFormatAdded(const QString &name)
Emitted whenever a text format has been added to the style and the database has been updated as a res...
bool removeFavorite(StyleEntity type, const QString &name)
Removes the specified symbol from favorites.
static QgsTextFormat defaultTextFormatForProject(QgsProject *project, QgsStyle::TextFormatContext context=QgsStyle::TextFormatContext::Labeling)
Returns the default text format to use for new text based objects for the specified project,...
int legendPatchShapesCount() const
Returns count of legend patch shapes in the style.
QgsSymbolGroupMap smartgroupsListMap()
Returns the smart groups map with id as key and name as value.
bool isFavorite(StyleEntity type, const QString &name)
Returns true if the symbol with matching type and name is marked as a favorite.
int symbolId(const QString &name)
Returns the id in the style database for the given symbol name returns 0 if not found.
bool saveLegendPatchShape(const QString &name, const QgsLegendPatchShape &shape, bool favorite, const QStringList &tags)
Adds a legend patch shape to the database.
QString name() const
Returns the name of the style.
Definition qgsstyle.cpp:108
QgsStyle(QObject *parent=nullptr)
Constructor for QgsStyle, with the specified parent object.
Definition qgsstyle.cpp:81
Qgis::GeometryType labelSettingsLayerType(const QString &name) const
Returns the layer geometry type corresponding to the label settings with the specified name,...
@ XML
Symbol definition (as XML).
Definition qgsstyle.h:102
bool renameLabelSettings(const QString &oldName, const QString &newName)
Changes a label setting's name.
bool renameSymbol3D(const QString &oldName, const QString &newName)
Changes a 3d symbol's name.
bool addTextFormat(const QString &name, const QgsTextFormat &format, bool update=false)
Adds a text format with the specified name to the style.
Definition qgsstyle.cpp:373
bool saveColorRamp(const QString &name, const QgsColorRamp *ramp, bool favorite, const QStringList &tags)
Adds the colorramp to the database.
Definition qgsstyle.cpp:461
void rampChanged(const QString &name)
Emitted whenever a color ramp's definition is changed.
QStringList symbolsOfFavorite(StyleEntity type) const
Returns the symbol names which are flagged as favorite.
void entityTagsChanged(QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags)
Emitted whenever an entity's tags are changed.
bool renameSymbol(const QString &oldName, const QString &newName)
Renames a symbol from oldName to newName.
Definition qgsstyle.cpp:906
bool removeColorRamp(const QString &name)
Removes color ramp from style (and delete it).
Definition qgsstyle.cpp:497
bool saveSymbol3D(const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags)
Adds a 3d symbol to the database.
void favoritedChanged(QgsStyle::StyleEntity entity, const QString &name, bool isFavorite)
Emitted whenever an entity is either favorited or un-favorited.
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
~QgsStyle() override
Definition qgsstyle.cpp:97
void entityRemoved(QgsStyle::StyleEntity entity, const QString &name)
Emitted whenever an entity of the specified type is removed from the style and the database has been ...
int labelSettingsId(const QString &name)
Returns the ID in the style database for the given label settings by name.
Qgis::SymbolType legendPatchShapeSymbolType(const QString &name) const
Returns the symbol type corresponding to the legend patch shape with the specified name,...
QgsAbstract3DSymbol * symbol3D(const QString &name) const
Returns a new copy of the 3D symbol with the specified name.
int addTag(const QString &tagName)
Adds a new tag and returns the tag's id.
const QgsSymbol * previewSymbolForPatchShape(const QgsLegendPatchShape &shape) const
Returns a symbol to use for rendering preview icons for a patch shape.
bool addSymbol(const QString &name, QgsSymbol *symbol, bool update=false)
Adds a symbol to style and takes symbol's ownership.
Definition qgsstyle.cpp:225
QgsLegendPatchShape legendPatchShape(const QString &name) const
Returns the legend patch shape with the specified name.
void entityAdded(QgsStyle::StyleEntity entity, const QString &name)
Emitted every time a new entity has been added to the database.
QStringList symbolNames() const
Returns a list of names of symbols.
Definition qgsstyle.cpp:342
TextFormatContext
Text format context.
Definition qgsstyle.h:811
void symbolRemoved(const QString &name)
Emitted whenever a symbol has been removed from the style and the database has been updated as a resu...
void symbolChanged(const QString &name)
Emitted whenever a symbol's definition is changed.
bool addFavorite(StyleEntity type, const QString &name)
Adds the specified symbol to favorites.
QString smartgroupOperator(int id)
Returns the operator for the smartgroup.
bool saveTextFormat(const QString &name, const QgsTextFormat &format, bool favorite, const QStringList &tags)
Adds a text format to the database.
Definition qgsstyle.cpp:981
QList< QList< QPolygonF > > defaultPatchAsQPolygonF(Qgis::SymbolType type, QSizeF size) const
Returns the default patch geometry for the given symbol type and size as a set of QPolygonF objects (...
void textFormatRemoved(const QString &name)
Emitted whenever a text format has been removed from the style and the database has been updated as a...
void setReadOnly(bool readOnly)
Sets whether the style is considered a read-only library.
bool importXml(const QString &filename)
Imports the symbols and colorramps into the default style database from the given XML file.
bool addLabelSettings(const QString &name, const QgsPalLayerSettings &settings, bool update=false)
Adds label settings with the specified name to the style.
Definition qgsstyle.cpp:395
static std::unique_ptr< QgsColorRamp > loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
static std::unique_ptr< QgsSymbol > loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
static QDomElement saveColorRamp(const QString &name, const QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
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 ...
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.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
Container for all settings relating to text rendering.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
QMultiMap< QString, QString > QgsSmartConditionMap
A multimap to hold the smart group conditions as constraint and parameter pairs.
Definition qgsstyle.h:79
#define QgsDebugError(str)
Definition qgslogger.h:57
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition qgsrenderer.h:50
QString qgs_sqlite3_mprintf(const char *format,...)
Wraps sqlite3_mprintf() by automatically freeing the memory.
LegendPatchTable
Columns available in the legend patch table.
Definition qgsstyle.cpp:60
@ LegendPatchTableId
Legend patch ID.
Definition qgsstyle.cpp:61
@ LegendPatchTableName
Legend patch name.
Definition qgsstyle.cpp:62
@ LegendPatchTableFavoriteId
Legend patch is favorite flag.
Definition qgsstyle.cpp:64
@ LegendPatchTableXML
Legend patch definition (as XML).
Definition qgsstyle.cpp:63
#define STYLE_CURRENT_VERSION
Definition qgsstyle.cpp:54
Symbol3DTable
Columns available in the 3d symbol table.
Definition qgsstyle.cpp:71
@ Symbol3DTableXML
3d symbol definition (as XML)
Definition qgsstyle.cpp:74
@ Symbol3DTableName
3d symbol name
Definition qgsstyle.cpp:73
@ Symbol3DTableFavoriteId
3d symbol is favorite flag
Definition qgsstyle.cpp:75
@ Symbol3DTableId
3d symbol ID
Definition qgsstyle.cpp:72
QMap< int, QString > QgsSymbolGroupMap
Definition qgsstyle.h:42
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30