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