QGIS API Documentation 3.99.0-Master (8e76e220402)
Loading...
Searching...
No Matches
qgsstylemodel.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsstylemodel.cpp
3 ---------------
4 begin : September 2018
5 copyright : (C) 2018 by Nyall Dawson
6 email : nyall dot dawson 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 "qgsstylemodel.h"
17
18#include "qgsapplication.h"
21#include "qgsimagecache.h"
22#include "qgsstyle.h"
23#include "qgssvgcache.h"
24#include "qgssymbollayerutils.h"
25
26#include <QBuffer>
27#include <QDir>
28#include <QIcon>
29#include <QString>
30
31#include "moc_qgsstylemodel.cpp"
32
33using namespace Qt::StringLiterals;
34
35const double ICON_PADDING_FACTOR = 0.16;
36
38
39QgsAbstractStyleEntityIconGenerator *QgsStyleModel::sIconGenerator = nullptr;
40
41//
42// QgsAbstractStyleEntityIconGenerator
43//
44
46 : QObject( parent )
47{
48}
49
51{
52 mIconSizes = sizes;
53}
54
56{
57 return mIconSizes;
58}
59
60void QgsAbstractStyleEntityIconGenerator::setTargetScreenProperties( const QSet<QgsScreenProperties> &properties )
61{
62 mTargetScreenProperties = properties;
63}
64
66{
67 return mTargetScreenProperties;
68}
69
70
71//
72// QgsStyleModel
73//
74
76 : QAbstractItemModel( parent )
77 , mStyle( style )
78{
79 Q_ASSERT( mStyle );
80
81 if ( mStyle->isInitialized() )
82 {
83 initStyleModel();
84 }
85 else
86 {
87 // lazy initialized style
88 connect( mStyle, &QgsStyle::initialized, this, &QgsStyleModel::initStyleModel );
89 }
90}
91
92void QgsStyleModel::initStyleModel()
93{
94 beginResetModel();
95 for ( QgsStyle::StyleEntity entity : ENTITIES )
96 {
97 mEntityNames.insert( entity, mStyle->allNames( entity ) );
98 }
99 endResetModel();
100
101 // ensure we always generate icons using default screen properties
102 // in addition to actual target screen properties (ie device pixel ratio of 1, 96 dpi)
103 mTargetScreenProperties.insert( QgsScreenProperties() );
104
105 connect( mStyle, &QgsStyle::entityAdded, this, &QgsStyleModel::onEntityAdded );
106 connect( mStyle, &QgsStyle::entityRemoved, this, &QgsStyleModel::onEntityRemoved );
107 connect( mStyle, &QgsStyle::entityRenamed, this, &QgsStyleModel::onEntityRename );
108 connect( mStyle, &QgsStyle::entityChanged, this, &QgsStyleModel::onEntityChanged );
109 connect( mStyle, &QgsStyle::favoritedChanged, this, &QgsStyleModel::onFavoriteChanged );
110 connect( mStyle, &QgsStyle::entityTagsChanged, this, &QgsStyleModel::onTagsChanged );
111 connect( mStyle, &QgsStyle::rebuildIconPreviews, this, &QgsStyleModel::rebuildSymbolIcons );
112
113 // when a remote svg or image has been fetched, update the model's decorations.
114 // this is required if a symbol utilizes remote svgs, and the current icons
115 // have been generated using the temporary "downloading" svg. In this case
116 // we require the preview to be regenerated to use the correct fetched
117 // svg
118 connect( QgsApplication::svgCache(), &QgsSvgCache::remoteSvgFetched, this, &QgsStyleModel::rebuildSymbolIcons );
119 connect( QgsApplication::imageCache(), &QgsImageCache::remoteImageFetched, this, &QgsStyleModel::rebuildSymbolIcons );
120
121 if ( sIconGenerator )
122 connect( sIconGenerator, &QgsAbstractStyleEntityIconGenerator::iconGenerated, this, &QgsStyleModel::iconGenerated, Qt::QueuedConnection );
123}
124
125QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
126{
127 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
128 return QVariant();
129
130
131 QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
132
133 QString name;
134 switch ( entityType )
135 {
138 break;
139
140 default:
141 name = mEntityNames[ entityType ].value( index.row() - offsetForEntity( entityType ) );
142 break;
143 }
144
145 switch ( role )
146 {
147 case Qt::DisplayRole:
148 case Qt::ToolTipRole:
149 case Qt::EditRole:
150 {
151 switch ( index.column() )
152 {
153 case Name:
154 {
155 const QStringList tags = mStyle->tagsOfSymbol( entityType, name );
156
157 if ( role == Qt::ToolTipRole )
158 {
159 QString tooltip = u"<h3>%1</h3><p><i>%2</i>"_s.arg( name,
160 tags.count() > 0 ? tags.join( ", "_L1 ) : tr( "Not tagged" ) );
161
162 // generate tooltips for the largest device pixel ratio for all attached screens
163 QgsScreenProperties maxDevicePixelRatioScreen;
164 for ( auto it = mTargetScreenProperties.constBegin(); it != mTargetScreenProperties.constEnd(); ++it )
165 {
166 if ( !maxDevicePixelRatioScreen.isValid() || it->devicePixelRatio() > maxDevicePixelRatioScreen.devicePixelRatio() )
167 maxDevicePixelRatioScreen = *it;
168 }
169
170 switch ( entityType )
171 {
173 {
174 // create very large preview image
175 std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
176 if ( symbol )
177 {
178 int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
179 int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
180 QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( width, height ), height / 20, nullptr, false, mExpressionContext.get(), nullptr, maxDevicePixelRatioScreen );
181 QByteArray data;
182 QBuffer buffer( &data );
183 pm.save( &buffer, "PNG", 100 );
184 tooltip += u"<p><img src='data:image/png;base64, %3' width=\"%4\">"_s.arg( QString( data.toBase64() ) ).arg( width );
185 }
186 break;
187 }
188
190 {
191 int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
192 int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
193 const QgsTextFormat format = mStyle->textFormat( name );
194 QPixmap pm = QgsTextFormat::textFormatPreviewPixmap( format, QSize( width, height ), QString(), height / 20, maxDevicePixelRatioScreen );
195 QByteArray data;
196 QBuffer buffer( &data );
197 pm.save( &buffer, "PNG", 100 );
198 tooltip += u"<p><img src='data:image/png;base64, %3' width=\"%4\">"_s.arg( QString( data.toBase64() ) ).arg( width );
199 break;
200 }
201
203 {
204 int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
205 int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
206 const QgsPalLayerSettings settings = mStyle->labelSettings( name );
207 QPixmap pm = QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( width, height ), QString(), height / 20, maxDevicePixelRatioScreen );
208 QByteArray data;
209 QBuffer buffer( &data );
210 pm.save( &buffer, "PNG", 100 );
211 tooltip += u"<p><img src='data:image/png;base64, %3' width=\"%4\">"_s.arg( QString( data.toBase64() ) ).arg( width );
212 break;
213 }
214
216 {
217 int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
218 int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
219
220 const QgsLegendPatchShape shape = mStyle->legendPatchShape( name );
221 if ( const QgsSymbol *symbol = mStyle->previewSymbolForPatchShape( shape ) )
222 {
223 QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, QSize( width, height ), height / 20, nullptr, false, nullptr, &shape, maxDevicePixelRatioScreen );
224 QByteArray data;
225 QBuffer buffer( &data );
226 pm.save( &buffer, "PNG", 100 );
227 tooltip += u"<p><img src='data:image/png;base64, %3' width=\"%4\">"_s.arg( QString( data.toBase64() ) ).arg( width );
228 }
229 break;
230 }
231
236 break;
237 }
238 return tooltip;
239 }
240 else
241 {
242 return name;
243 }
244 }
245 case Tags:
246 return mStyle->tagsOfSymbol( entityType, name ).join( ", "_L1 );
247
248 default:
249 break;
250 }
251 return QVariant();
252 }
253
254 case Qt::DecorationRole:
255 {
256 // Generate icons at all additional sizes specified for the model.
257 // This allows the model to have size responsive icons.
258
259 if ( !mExpressionContext )
260 {
261 // build the expression context once, and keep it around. Usually this is a no-no, but in this
262 // case we want to avoid creating potentially thousands of contexts one-by-one (usually one context
263 // is created for a batch of multiple evalutions like this), and we only use a very minimal context
264 // anyway...
265 mExpressionContext = std::make_unique< QgsExpressionContext >();
266 mExpressionContext->appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
267 }
268
269 switch ( index.column() )
270 {
271 case Name:
272 switch ( entityType )
273 {
275 {
276 // use cached icon if possible
277 QIcon icon = mIconCache[ entityType ].value( name );
278 if ( !icon.isNull() )
279 return icon;
280
281 std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
282 if ( symbol )
283 {
284 for ( auto it = mTargetScreenProperties.constBegin(); it != mTargetScreenProperties.constEnd(); ++it )
285 {
286 if ( mAdditionalSizes.isEmpty() )
287 icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( 24, 24 ), 1, nullptr, false, mExpressionContext.get(), nullptr, *it ) );
288
289 for ( const QSize &s : mAdditionalSizes )
290 {
291 icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ), nullptr, false, mExpressionContext.get(), nullptr, *it ) );
292 }
293 }
294
295 }
296 mIconCache[ entityType ].insert( name, icon );
297 return icon;
298 }
300 {
301 // use cached icon if possible
302 QIcon icon = mIconCache[ entityType ].value( name );
303 if ( !icon.isNull() )
304 return icon;
305
306 std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
307 if ( ramp )
308 {
309 if ( mAdditionalSizes.isEmpty() )
310 icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), QSize( 24, 24 ), 1 ) );
311 for ( const QSize &s : mAdditionalSizes )
312 {
313 icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
314 }
315
316 }
317 mIconCache[ entityType ].insert( name, icon );
318 return icon;
319 }
320
322 {
323 // use cached icon if possible
324 QIcon icon = mIconCache[ entityType ].value( name );
325 if ( !icon.isNull() )
326 return icon;
327
328 const QgsTextFormat format( mStyle->textFormat( name ) );
329 for ( auto it = mTargetScreenProperties.constBegin(); it != mTargetScreenProperties.constEnd(); ++it )
330 {
331 if ( mAdditionalSizes.isEmpty() )
332 icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, QSize( 24, 24 ), QString(), 1, *it ) );
333 for ( const QSize &s : mAdditionalSizes )
334 {
335 icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ), *it ) );
336 }
337 }
338 mIconCache[ entityType ].insert( name, icon );
339 return icon;
340 }
341
343 {
344 // use cached icon if possible
345 QIcon icon = mIconCache[ entityType ].value( name );
346 if ( !icon.isNull() )
347 return icon;
348
349 const QgsPalLayerSettings settings( mStyle->labelSettings( name ) );
350 for ( auto it = mTargetScreenProperties.constBegin(); it != mTargetScreenProperties.constEnd(); ++it )
351 {
352 if ( mAdditionalSizes.isEmpty() )
353 icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( 24, 24 ), QString(), 1, *it ) );
354 for ( const QSize &s : mAdditionalSizes )
355 {
356 icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ), *it ) );
357 }
358 }
359 mIconCache[ entityType ].insert( name, icon );
360 return icon;
361 }
362
364 {
365 // use cached icon if possible
366 QIcon icon = mIconCache[ entityType ].value( name );
367 if ( !icon.isNull() )
368 return icon;
369
370 const QgsLegendPatchShape shape = mStyle->legendPatchShape( name );
371 if ( !shape.isNull() )
372 {
373 for ( auto it = mTargetScreenProperties.constBegin(); it != mTargetScreenProperties.constEnd(); ++it )
374 {
375 if ( const QgsSymbol *symbol = mStyle->previewSymbolForPatchShape( shape ) )
376 {
377 if ( mAdditionalSizes.isEmpty() )
378 icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, QSize( 24, 24 ), 1, nullptr, false, mExpressionContext.get(), &shape, *it ) );
379
380 for ( const QSize &s : mAdditionalSizes )
381 {
382 icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ), nullptr, false, mExpressionContext.get(), &shape, *it ) );
383 }
384 }
385 }
386 }
387 mIconCache[ entityType ].insert( name, icon );
388 return icon;
389 }
390
392 {
393 // hack for now -- we just use a generic "3d icon" svg file.
394 // TODO - render proper thumbnails
395
396 // use cached icon if possible
397 QIcon icon = mIconCache[ entityType ].value( name );
398 if ( !icon.isNull() )
399 return icon;
400
401 if ( sIconGenerator && !mPending3dSymbolIcons.contains( name ) )
402 {
403 mPending3dSymbolIcons.insert( name );
404 sIconGenerator->generateIcon( mStyle, QgsStyle::Symbol3DEntity, name );
405 }
406
407 // TODO - use hourglass icon
408 if ( mAdditionalSizes.isEmpty() )
409 icon.addFile( QgsApplication::defaultThemePath() + QDir::separator() + u"3d.svg"_s, QSize( 24, 24 ) );
410 for ( const QSize &s : mAdditionalSizes )
411 {
412 icon.addFile( QgsApplication::defaultThemePath() + QDir::separator() + u"3d.svg"_s, s );
413 }
414 mIconCache[ entityType ].insert( name, icon );
415 return icon;
416 }
417
420 return QVariant();
421 }
422 break;
423
424 case Tags:
425 return QVariant();
426
427 default:
428 break;
429 }
430 return QVariant();
431 }
432
433 case static_cast< int >( CustomRole::Type ):
434 return entityType;
435
436 case static_cast< int >( CustomRole::Tag ):
437 return mStyle->tagsOfSymbol( entityType, name );
438
439 case static_cast< int >( CustomRole::IsFavorite ):
440 return mStyle->isFavorite( entityType, name );
441
442 case static_cast< int >( CustomRole::SymbolType ):
443 {
444 switch ( entityType )
445 {
447 {
448 const QgsSymbol *symbol = mStyle->symbolRef( name );
449 return symbol ? static_cast< int >( symbol->type() ) : QVariant();
450 }
451
453 return static_cast< int >( mStyle->legendPatchShapeSymbolType( name ) );
454
461 return QVariant();
462 }
463 return QVariant();
464 }
465
466 case static_cast< int >( CustomRole::LayerType ):
467 {
468 switch ( entityType )
469 {
471 return static_cast< int >( mStyle->labelSettingsLayerType( name ) );
472
480 return QVariant();
481 }
482 return QVariant();
483 }
484
485 case static_cast< int >( CustomRole::CompatibleGeometryTypes ):
486 {
487 switch ( entityType )
488 {
490 {
491 QVariantList res;
492 const QList< Qgis::GeometryType > types = mStyle->symbol3DCompatibleGeometryTypes( name );
493 res.reserve( types.size() );
494 for ( Qgis::GeometryType type : types )
495 {
496 res << static_cast< int >( type );
497 }
498 return res;
499 }
500
508 return QVariant();
509 }
510 return QVariant();
511 }
512
513 case static_cast< int >( CustomRole::EntityName ):
514 return name;
515
516 case static_cast< int >( CustomRole::StyleName ):
517 return mStyle->name();
518
519 case static_cast< int >( CustomRole::StyleFileName ):
520 return mStyle->fileName();
521
522 default:
523 return QVariant();
524 }
525#ifndef _MSC_VER // avoid warning
526 return QVariant(); // avoid warning
527#endif
528}
529
530bool QgsStyleModel::setData( const QModelIndex &index, const QVariant &value, int role )
531{
532 if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) || role != Qt::EditRole )
533 return false;
534
535 switch ( index.column() )
536 {
537 case Name:
538 {
539 QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
540 QString name;
541 switch ( entityType )
542 {
545 return false;
546
547 default:
548 name = mEntityNames[ entityType ].value( index.row() - offsetForEntity( entityType ) );
549 break;
550 }
551
552 const QString newName = value.toString();
553 return mStyle->renameEntity( entityType, name, newName );
554 }
555
556 case Tags:
557 return false;
558
559 default:
560 break;
561 }
562
563 return false;
564}
565
566Qt::ItemFlags QgsStyleModel::flags( const QModelIndex &index ) const
567{
568 Qt::ItemFlags flags = QAbstractItemModel::flags( index );
569 if ( index.isValid() && index.column() == Name )
570 {
571 return flags | Qt::ItemIsEditable;
572 }
573 else
574 {
575 return flags;
576 }
577}
578
579QVariant QgsStyleModel::headerData( int section, Qt::Orientation orientation, int role ) const
580{
581 return headerDataStatic( section, orientation, role );
582}
583
584QVariant QgsStyleModel::headerDataStatic( int section, Qt::Orientation orientation, int role )
585{
586 if ( role == Qt::DisplayRole )
587 {
588 if ( orientation == Qt::Vertical ) //row
589 {
590 return QVariant( section );
591 }
592 else
593 {
594 switch ( section )
595 {
596 case Name:
597 return QVariant( tr( "Name" ) );
598
599 case Tags:
600 return QVariant( tr( "Tags" ) );
601
602 default:
603 return QVariant();
604 }
605 }
606 }
607 else
608 {
609 return QVariant();
610 }
611}
612
613QModelIndex QgsStyleModel::index( int row, int column, const QModelIndex &parent ) const
614{
615 if ( !hasIndex( row, column, parent ) )
616 return QModelIndex();
617
618 if ( !parent.isValid() )
619 {
620 return createIndex( row, column );
621 }
622
623 return QModelIndex();
624}
625
626QModelIndex QgsStyleModel::parent( const QModelIndex & ) const
627{
628 //all items are top level for now
629 return QModelIndex();
630}
631
632int QgsStyleModel::rowCount( const QModelIndex &parent ) const
633{
634 if ( !parent.isValid() )
635 {
636 int count = 0;
637 for ( QgsStyle::StyleEntity type : ENTITIES )
638 count += mEntityNames[ type ].size();
639 return count;
640 }
641 return 0;
642}
643
644int QgsStyleModel::columnCount( const QModelIndex & ) const
645{
646 return 2;
647}
648
650{
651 if ( mAdditionalSizes.contains( size ) )
652 return;
653
654 mAdditionalSizes << size;
655
656 if ( sIconGenerator )
657 sIconGenerator->setIconSizes( mAdditionalSizes );
658
659 mIconCache.clear();
660}
661
663{
664 if ( mTargetScreenProperties.contains( properties ) )
665 return;
666
667 mTargetScreenProperties.insert( properties );
668
669 if ( sIconGenerator )
670 sIconGenerator->setTargetScreenProperties( mTargetScreenProperties );
671
672 mIconCache.clear();
673}
674
676{
677 sIconGenerator = generator;
678 connect( sIconGenerator, &QgsAbstractStyleEntityIconGenerator::iconGenerated, QgsApplication::defaultStyleModel(), &QgsStyleModel::iconGenerated, Qt::QueuedConnection );
679}
680
681void QgsStyleModel::onEntityAdded( QgsStyle::StyleEntity type, const QString &name )
682{
683 mIconCache[ type ].remove( name );
684 const QStringList newSymbolNames = mStyle->allNames( type );
685
686 // find index of newly added symbol
687 const int newNameIndex = newSymbolNames.indexOf( name );
688 if ( newNameIndex < 0 )
689 return; // shouldn't happen
690
691 const int offset = offsetForEntity( type );
692 beginInsertRows( QModelIndex(), newNameIndex + offset, newNameIndex + offset );
693 mEntityNames[ type ] = newSymbolNames;
694 endInsertRows();
695}
696
697void QgsStyleModel::onEntityRemoved( QgsStyle::StyleEntity type, const QString &name )
698{
699 mIconCache[ type ].remove( name );
700 const QStringList oldSymbolNames = mEntityNames[ type ];
701 const QStringList newSymbolNames = mStyle->allNames( type );
702
703 // find index of removed symbol
704 const int oldNameIndex = oldSymbolNames.indexOf( name );
705 if ( oldNameIndex < 0 )
706 return; // shouldn't happen
707
708 const int offset = offsetForEntity( type );
709 beginRemoveRows( QModelIndex(), oldNameIndex + offset, oldNameIndex + offset );
710 mEntityNames[ type ] = newSymbolNames;
711 endRemoveRows();
712}
713
714void QgsStyleModel::onEntityChanged( QgsStyle::StyleEntity type, const QString &name )
715{
716 mIconCache[ type ].remove( name );
717
718 const int offset = offsetForEntity( type );
719 QModelIndex i = index( offset + mEntityNames[ type ].indexOf( name ), Tags );
720 emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
721}
722
723void QgsStyleModel::onFavoriteChanged( QgsStyle::StyleEntity type, const QString &name, bool )
724{
725 const int offset = offsetForEntity( type );
726 QModelIndex i = index( offset + mEntityNames[ type ].indexOf( name ), Name );
727 emit dataChanged( i, i, QVector< int >() << static_cast< int >( CustomRole::IsFavorite ) );
728}
729
730void QgsStyleModel::onEntityRename( QgsStyle::StyleEntity type, const QString &oldName, const QString &newName )
731{
732 mIconCache[ type ].remove( oldName );
733 const QStringList oldSymbolNames = mEntityNames[ type ];
734 const QStringList newSymbolNames = mStyle->allNames( type );
735
736 // find index of removed symbol
737 const int oldNameIndex = oldSymbolNames.indexOf( oldName );
738 if ( oldNameIndex < 0 )
739 return; // shouldn't happen
740
741 // find index of added symbol
742 const int newNameIndex = newSymbolNames.indexOf( newName );
743 if ( newNameIndex < 0 )
744 return; // shouldn't happen
745
746 if ( newNameIndex == oldNameIndex )
747 {
748 mEntityNames[ type ] = newSymbolNames;
749 return;
750 }
751
752 const int offset = offsetForEntity( type );
753 beginMoveRows( QModelIndex(), oldNameIndex + offset, oldNameIndex + offset, QModelIndex(), ( newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex ) + offset );
754 mEntityNames[ type ] = newSymbolNames;
755 endMoveRows();
756}
757
758void QgsStyleModel::onTagsChanged( int entity, const QString &name, const QStringList & )
759{
760 QgsStyle::StyleEntity type = static_cast< QgsStyle::StyleEntity >( entity );
761 int row = mEntityNames[type].indexOf( name ) + offsetForEntity( type );
762 switch ( static_cast< QgsStyle::StyleEntity >( entity ) )
763 {
766 return;
767
768 default:
769 break;
770 }
771 emit dataChanged( index( row, Name ), index( row, Tags ) );
772}
773
774void QgsStyleModel::rebuildSymbolIcons()
775{
776 mIconCache[ QgsStyle::SymbolEntity ].clear();
777 mExpressionContext.reset();
778 const int lastRow = mEntityNames[ QgsStyle::SymbolEntity ].count() - 1;
779 if ( lastRow >= 0 )
780 {
781 emit dataChanged( index( 0, 0 ), index( lastRow, 0 ), QVector<int>() << Qt::DecorationRole );
782 }
783}
784
785void QgsStyleModel::iconGenerated( QgsStyle::StyleEntity type, const QString &name, const QIcon &icon )
786{
787 int row = mEntityNames[type].indexOf( name ) + offsetForEntity( type );
788
789 switch ( type )
790 {
792 mPending3dSymbolIcons.remove( name );
793 mIconCache[ QgsStyle::Symbol3DEntity ].insert( name, icon );
794 emit dataChanged( index( row, 0 ), index( row, 0 ) );
795 break;
796
804 break;
805 }
806}
807
808QgsStyle::StyleEntity QgsStyleModel::entityTypeFromRow( int row ) const
809{
810 int maxRowForEntity = 0;
811 for ( QgsStyle::StyleEntity type : ENTITIES )
812 {
813 maxRowForEntity += mEntityNames[ type ].size();
814 if ( row < maxRowForEntity )
815 return type;
816 }
817
818 // should never happen
819 Q_ASSERT( false );
821}
822
823int QgsStyleModel::offsetForEntity( QgsStyle::StyleEntity entity ) const
824{
825 int offset = 0;
826 for ( QgsStyle::StyleEntity type : ENTITIES )
827 {
828 if ( type == entity )
829 return offset;
830
831 offset += mEntityNames[ type ].size();
832 }
833 return 0;
834}
835
836//
837// QgsStyleProxyModel
838//
839
841 : QSortFilterProxyModel( parent )
842 , mStyle( style )
843{
844 mModel = new QgsStyleModel( mStyle, this );
845 setSourceModel( mModel );
846 initialize();
847}
848
849void QgsStyleProxyModel::initialize()
850{
851 setSortCaseSensitivity( Qt::CaseInsensitive );
852// setSortLocaleAware( true );
853 setDynamicSortFilter( true );
854 sort( 0 );
855
856 if ( mStyle )
857 {
858 connect( mStyle, &QgsStyle::entityTagsChanged, this, [this]
859 {
860 // update tagged symbols if filtering by tag
861 if ( mTagId >= 0 )
862 setTagId( mTagId );
863 if ( mSmartGroupId >= 0 )
864 setSmartGroupId( mSmartGroupId );
865 } );
866
867 connect( mStyle, &QgsStyle::entityRenamed, this, [this]( QgsStyle::StyleEntity entity, const QString &, const QString & )
868 {
869 switch ( entity )
870 {
873 return;
874
875 default:
876 break;
877 }
878
879 if ( mSmartGroupId >= 0 )
880 setSmartGroupId( mSmartGroupId );
881 } );
882 }
883}
884
886 : QSortFilterProxyModel( parent )
887 , mModel( model )
888 , mStyle( model->style() )
889{
890 setSourceModel( mModel );
891 initialize();
892}
893
895 : QSortFilterProxyModel( parent )
896 , mCombinedModel( model )
897{
898 setSourceModel( mCombinedModel );
899 initialize();
900}
901
902bool QgsStyleProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
903{
904 if ( mFilterString.isEmpty() && !mEntityFilterEnabled && !mSymbolTypeFilterEnabled && mTagId < 0 && mSmartGroupId < 0 && !mFavoritesOnly && mTagFilter.isEmpty() )
905 return true;
906
907 const QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
908
909 if ( sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::IsTitle ) ).toBool() )
910 return true;
911
912 const QString name = sourceModel()->data( index ).toString();
913 const QStringList tags = sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::Tag ) ).toStringList();
914
915 QgsStyle::StyleEntity styleEntityType = static_cast< QgsStyle::StyleEntity >( sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::Type ) ).toInt() );
916 if ( mEntityFilterEnabled && ( mEntityFilters.empty() || !mEntityFilters.contains( styleEntityType ) ) )
917 return false;
918
919 Qgis::SymbolType symbolType = static_cast< Qgis::SymbolType >( sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::SymbolType ) ).toInt() );
920 if ( mSymbolTypeFilterEnabled && symbolType != mSymbolType )
921 return false;
922
923 if ( mLayerType != Qgis::GeometryType::Unknown )
924 {
925 switch ( styleEntityType )
926 {
933 break;
934
936 {
937 if ( mLayerType != static_cast< Qgis::GeometryType >( sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::LayerType ) ).toInt() ) )
938 return false;
939 break;
940 }
941
943 {
944 const QVariantList types = sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::CompatibleGeometryTypes ) ).toList();
945 if ( !types.empty() && !types.contains( QVariant::fromValue( mLayerType ) ) )
946 return false;
947 break;
948 }
949 }
950 }
951
952 if ( mTagId >= 0 && !mTaggedSymbolNames.contains( name ) )
953 return false;
954
955 if ( mSmartGroupId >= 0 && !mSmartGroupSymbolNames.contains( name ) )
956 return false;
957
958 if ( !mTagFilter.isEmpty() && !tags.contains( mTagFilter, Qt::CaseInsensitive ) )
959 return false;
960
961 if ( mFavoritesOnly && !sourceModel()->data( index, static_cast< int >( QgsStyleModel::CustomRole::IsFavorite ) ).toBool() )
962 return false;
963
964 if ( !mFilterString.isEmpty() )
965 {
966 // filter by word, in both filter string and style entity name/tags
967 // this allows matching of a filter string "hash line" to the symbol "hashed red lines"
968 const QStringList partsToMatch = mFilterString.trimmed().split( ' ' );
969
970 QStringList partsToSearch = name.split( ' ' );
971 for ( const QString &tag : tags )
972 {
973 partsToSearch.append( tag.split( ' ' ) );
974 }
975
976 for ( const QString &part : partsToMatch )
977 {
978 bool found = false;
979 for ( const QString &partToSearch : std::as_const( partsToSearch ) )
980 {
981 if ( partToSearch.contains( part, Qt::CaseInsensitive ) )
982 {
983 found = true;
984 break;
985 }
986 }
987 if ( !found )
988 return false; // couldn't find a match for this word, so hide entity
989 }
990 }
991
992 return true;
993}
994
995bool QgsStyleProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
996{
997 const QString leftSource = sourceModel()->data( left, static_cast< int >( QgsStyleModel::CustomRole::StyleFileName ) ).toString();
998 const QString rightSource = sourceModel()->data( right, static_cast< int >( QgsStyleModel::CustomRole::StyleFileName ) ).toString();
999 if ( leftSource != rightSource )
1000 return QString::localeAwareCompare( leftSource, rightSource ) < 0;
1001
1002 const QString leftName = sourceModel()->data( left, static_cast< int >( QgsStyleModel::CustomRole::EntityName ) ).toString();
1003 const QString rightName = sourceModel()->data( right, static_cast< int >( QgsStyleModel::CustomRole::EntityName ) ).toString();
1004 return QString::localeAwareCompare( leftName, rightName ) < 0;
1005}
1006
1007void QgsStyleProxyModel::setFilterString( const QString &filter )
1008{
1009 mFilterString = filter;
1010 invalidateFilter();
1011}
1012
1013
1015{
1016 return mFavoritesOnly;
1017}
1018
1020{
1021 mFavoritesOnly = favoritesOnly;
1022 invalidateFilter();
1023}
1024
1026{
1027 if ( mModel )
1028 mModel->addDesiredIconSize( size );
1029 if ( mCombinedModel )
1030 mCombinedModel->addDesiredIconSize( size );
1031}
1032
1034{
1035 if ( mModel )
1036 mModel->addTargetScreenProperties( properties );
1037 if ( mCombinedModel )
1038 mCombinedModel->addTargetScreenProperties( properties );
1039}
1040
1042{
1043 return mSymbolTypeFilterEnabled;
1044}
1045
1047{
1048 mSymbolTypeFilterEnabled = enabled;
1049 invalidateFilter();
1050}
1051
1053{
1054 return mLayerType;
1055}
1056
1058{
1059 mLayerType = type;
1060 invalidateFilter();
1061}
1062
1064{
1065 if ( !mStyle )
1066 return;
1067 mTagId = id;
1068
1069 mTaggedSymbolNames.clear();
1070 if ( mTagId >= 0 )
1071 {
1072 for ( QgsStyle::StyleEntity entity : ENTITIES )
1073 mTaggedSymbolNames.append( mStyle->symbolsWithTag( entity, mTagId ) );
1074 }
1075
1076 invalidateFilter();
1077}
1078
1080{
1081 return mTagId;
1082}
1083
1084void QgsStyleProxyModel::setTagString( const QString &tag )
1085{
1086 mTagFilter = tag;
1087
1088 invalidateFilter();
1089}
1090
1092{
1093 return mTagFilter;
1094}
1095
1097{
1098 if ( !mStyle )
1099 return;
1100
1101 mSmartGroupId = id;
1102
1103 mSmartGroupSymbolNames.clear();
1104 if ( mSmartGroupId >= 0 )
1105 {
1106 for ( QgsStyle::StyleEntity entity : ENTITIES )
1107 mSmartGroupSymbolNames.append( mStyle->symbolsOfSmartgroup( entity, mSmartGroupId ) );
1108 }
1109
1110 invalidateFilter();
1111}
1112
1114{
1115 return mSmartGroupId;
1116}
1117
1119{
1120 return mSymbolType;
1121}
1122
1124{
1125 mSymbolType = symbolType;
1126 invalidateFilter();
1127}
1128
1130{
1131 return mEntityFilterEnabled;
1132}
1133
1135{
1136 mEntityFilterEnabled = entityFilterEnabled;
1137 invalidateFilter();
1138}
1139
1141{
1142 return mEntityFilters.empty() ? QgsStyle::SymbolEntity : mEntityFilters.at( 0 );
1143}
1144
1146{
1147 mEntityFilters = QList< QgsStyle::StyleEntity >() << entityFilter;
1148 invalidateFilter();
1149}
1150
1151void QgsStyleProxyModel::setEntityFilters( const QList<QgsStyle::StyleEntity> &filters )
1152{
1153 mEntityFilters = filters;
1154 invalidateFilter();
1155}
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Unknown
Unknown types.
Definition qgis.h:369
SymbolType
Symbol types.
Definition qgis.h:629
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:6534
An abstract base class for icon generators for a QgsStyleModel.
void setTargetScreenProperties(const QSet< QgsScreenProperties > &properties)
Sets the target screen properties to use when generating icons.
void setIconSizes(const QList< QSize > &sizes)
Sets the list of icon sizes to generate.
QgsAbstractStyleEntityIconGenerator(QObject *parent)
Constructor for QgsAbstractStyleEntityIconGenerator, with the specified parent object.
QSet< QgsScreenProperties > targetScreenProperties() const
Returns the target screen properties to use when generating icons.
QList< QSize > iconSizes() const
Returns the list of icon sizes to generate.
void iconGenerated(QgsStyle::StyleEntity type, const QString &name, const QIcon &icon)
Emitted when the icon for the style entity with matching type and name has been generated.
static QString defaultThemePath()
Returns the path to the default theme directory.
static QgsImageCache * imageCache()
Returns the application's image cache, used for caching resampled versions of raster images.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QgsStyleModel * defaultStyleModel()
Returns a shared QgsStyleModel containing the default style library (see QgsStyle::defaultStyle()).
A model which contains entities from multiple QgsStyle databases.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
Represents a patch shape for use in map legends.
bool isNull() const
Returns true if the patch shape is a null QgsLegendPatchShape, which indicates that the default legen...
Contains settings for how a map layer will be labeled.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for label settings.
Stores properties relating to a screen.
double devicePixelRatio() const
Returns the ratio between physical pixels and device-independent pixels for the screen.
bool isValid() const
Returns true if the properties are valid.
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
@ IsFavorite
Whether entity is flagged as a favorite.
@ LayerType
Layer type (for label settings entities).
@ IsTitle
True if the index corresponds to a title item.
@ SymbolType
Symbol type (for symbol or legend patch shape entities).
@ StyleFileName
File name of associated QgsStyle (QgsStyle::fileName()).
@ Type
Style entity type, see QgsStyle::StyleEntity.
@ CompatibleGeometryTypes
Compatible layer geometry types (for 3D symbols).
@ Tag
String list of tags.
@ StyleName
Name of associated QgsStyle (QgsStyle::name()).
Qt::ItemFlags flags(const QModelIndex &index) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role) const override
static void setIconGenerator(QgsAbstractStyleEntityIconGenerator *generator)
Sets the icon generator to use for deferred style entity icon generation.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
QModelIndex parent(const QModelIndex &index) const override
void addTargetScreenProperties(const QgsScreenProperties &properties)
Adds additional target screen properties to use when generating icons for Qt::DecorationRole data.
QgsStyleModel(QgsStyle *style, QObject *parent=nullptr)
Constructor for QgsStyleModel, for the specified style and parent object.
QgsStyle * style()
Returns the style managed by the model.
@ Name
Name column.
@ Tags
Tags column.
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
void setEntityFilter(QgsStyle::StyleEntity filter)
Sets the style entity type filter.
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override
bool favoritesOnly() const
Returns true if the model is showing only favorited entities.
void setSymbolTypeFilterEnabled(bool enabled)
Sets whether filtering by symbol type is enabled.
void setTagId(int id)
Sets a tag id to filter style entities by.
QString tagString() const
Returns the tag string used to filter style entities by.
Qgis::SymbolType symbolType() const
Returns the symbol type filter.
void setTagString(const QString &tag)
Sets a tag to filter style entities by.
QgsStyle::StyleEntity entityFilter() const
Returns the style entity type filter.
QgsStyleProxyModel(QgsStyle *style, QObject *parent=nullptr)
Constructor for QgsStyleProxyModel, for the specified style and parent object.
int tagId() const
Returns the tag id used to filter style entities by.
void setEntityFilters(const QList< QgsStyle::StyleEntity > &filters)
Sets the style entity type filters.
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
void setFavoritesOnly(bool favoritesOnly)
Sets whether the model should show only favorited entities.
void setSymbolType(Qgis::SymbolType type)
Sets the symbol type filter.
bool entityFilterEnabled() const
Returns true if filtering by entity type is enabled.
Qgis::GeometryType layerType() const
Returns the layer type filter, or Qgis::GeometryType::Unknown if no layer type filter is present.
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
void addTargetScreenProperties(const QgsScreenProperties &properties)
Adds additional target screen properties to use when generating icons for Qt::DecorationRole data.
void setEntityFilterEnabled(bool enabled)
Sets whether filtering by entity type is enabled.
void setSmartGroupId(int id)
Sets a smart group id to filter style entities by.
bool symbolTypeFilterEnabled() const
Returns true if filtering by symbol type is enabled.
int smartGroupId() const
Returns the smart group id used to filter style entities by.
void setFilterString(const QString &filter)
Sets a filter string, such that only symbol entities with names matching the specified string will be...
void setLayerType(Qgis::GeometryType type)
Sets the layer type filter.
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:89
QStringList allNames(StyleEntity type) const
Returns a list of the names of all existing entities of the specified type.
void entityChanged(QgsStyle::StyleEntity entity, const QString &name)
Emitted whenever an entity's definition is changed.
void rebuildIconPreviews()
Emitted whenever icon previews for entities in the style must be rebuilt.
StyleEntity
Enum for Entities involved in a style.
Definition qgsstyle.h:205
@ LabelSettingsEntity
Label settings.
Definition qgsstyle.h:211
@ TextFormatEntity
Text formats.
Definition qgsstyle.h:210
@ SmartgroupEntity
Smart groups.
Definition qgsstyle.h:209
@ Symbol3DEntity
3D symbol entity
Definition qgsstyle.h:213
@ SymbolEntity
Symbols.
Definition qgsstyle.h:206
@ TagEntity
Tags.
Definition qgsstyle.h:207
@ ColorrampEntity
Color ramps.
Definition qgsstyle.h:208
@ LegendPatchShapeEntity
Legend patch shape.
Definition qgsstyle.h:212
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.
void initialized()
Emitted when the style database has been fully initialized.
void entityTagsChanged(QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags)
Emitted whenever an entity's tags are changed.
void favoritedChanged(QgsStyle::StyleEntity entity, const QString &name, bool isFavorite)
Emitted whenever an entity is either favorited or un-favorited.
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 ...
void entityAdded(QgsStyle::StyleEntity entity, const QString &name)
Emitted every time a new entity has been added to the database.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
static QPixmap colorRampPreviewPixmap(QgsColorRamp *ramp, QSize size, int padding=0, Qt::Orientation direction=Qt::Horizontal, bool flipDirection=false, bool drawTransparentBackground=true)
Returns a pixmap preview for a color ramp.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr, const QgsLegendPatchShape *shape=nullptr, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a color ramp.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:231
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
Container for all settings relating to text rendering.
static QPixmap textFormatPreviewPixmap(const QgsTextFormat &format, QSize size, const QString &previewText=QString(), int padding=0, const QgsScreenProperties &screen=QgsScreenProperties())
Returns a pixmap preview for a text format.
const double ICON_PADDING_FACTOR
const auto ENTITIES