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