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