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