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