QGIS API Documentation  3.27.0-Master (aef1b1ec20)
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"
23 #include <QIcon>
24 #include <QBuffer>
25 #include <QDir>
26 
27 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
28 #include "qgscombinedstylemodel.h"
29 #endif
30 
31 const double ICON_PADDING_FACTOR = 0.16;
32 
34 
35 QgsAbstractStyleEntityIconGenerator *QgsStyleModel::sIconGenerator = nullptr;
36 
37 //
38 // QgsAbstractStyleEntityIconGenerator
39 //
40 
42  : QObject( parent )
43 {
44 
45 }
46 
47 void QgsAbstractStyleEntityIconGenerator::setIconSizes( const QList<QSize> &sizes )
48 {
49  mIconSizes = sizes;
50 }
51 
53 {
54  return mIconSizes;
55 }
56 
57 
58 //
59 // QgsStyleModel
60 //
61 
62 QgsStyleModel::QgsStyleModel( QgsStyle *style, QObject *parent )
63  : QAbstractItemModel( parent )
64  , mStyle( style )
65 {
66  Q_ASSERT( mStyle );
67 
68  for ( QgsStyle::StyleEntity entity : ENTITIES )
69  {
70  mEntityNames.insert( entity, mStyle->allNames( entity ) );
71  }
72 
73  connect( mStyle, &QgsStyle::entityAdded, this, &QgsStyleModel::onEntityAdded );
74  connect( mStyle, &QgsStyle::entityRemoved, this, &QgsStyleModel::onEntityRemoved );
75  connect( mStyle, &QgsStyle::entityRenamed, this, &QgsStyleModel::onEntityRename );
76  connect( mStyle, &QgsStyle::entityChanged, this, &QgsStyleModel::onEntityChanged );
77  connect( mStyle, &QgsStyle::favoritedChanged, this, &QgsStyleModel::onFavoriteChanged );
78  connect( mStyle, &QgsStyle::entityTagsChanged, this, &QgsStyleModel::onTagsChanged );
79  connect( mStyle, &QgsStyle::rebuildIconPreviews, this, &QgsStyleModel::rebuildSymbolIcons );
80 
81  // when a remote svg or image has been fetched, update the model's decorations.
82  // this is required if a symbol utilizes remote svgs, and the current icons
83  // have been generated using the temporary "downloading" svg. In this case
84  // we require the preview to be regenerated to use the correct fetched
85  // svg
86  connect( QgsApplication::svgCache(), &QgsSvgCache::remoteSvgFetched, this, &QgsStyleModel::rebuildSymbolIcons );
87  connect( QgsApplication::imageCache(), &QgsImageCache::remoteImageFetched, this, &QgsStyleModel::rebuildSymbolIcons );
88 
89  if ( sIconGenerator )
90  connect( sIconGenerator, &QgsAbstractStyleEntityIconGenerator::iconGenerated, this, &QgsStyleModel::iconGenerated, Qt::QueuedConnection );
91 }
92 
93 QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
94 {
95  if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
96  return QVariant();
97 
98 
99  QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
100 
101  QString name;
102  switch ( entityType )
103  {
104  case QgsStyle::TagEntity:
106  break;
107 
108  default:
109  name = mEntityNames[ entityType ].value( index.row() - offsetForEntity( entityType ) );
110  break;
111  }
112 
113  switch ( role )
114  {
115  case Qt::DisplayRole:
116  case Qt::ToolTipRole:
117  case Qt::EditRole:
118  {
119  switch ( index.column() )
120  {
121  case Name:
122  {
123  const QStringList tags = mStyle->tagsOfSymbol( entityType, name );
124 
125  if ( role == Qt::ToolTipRole )
126  {
127  QString tooltip = QStringLiteral( "<h3>%1</h3><p><i>%2</i>" ).arg( name,
128  tags.count() > 0 ? tags.join( QLatin1String( ", " ) ) : tr( "Not tagged" ) );
129 
130  switch ( entityType )
131  {
133  {
134  // create very large preview image
135  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
136  if ( symbol )
137  {
138  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
139  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
140  QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( width, height ), height / 20, nullptr, false, mExpressionContext.get() );
141  QByteArray data;
142  QBuffer buffer( &data );
143  pm.save( &buffer, "PNG", 100 );
144  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
145  }
146  break;
147  }
148 
150  {
151  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
152  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
153  const QgsTextFormat format = mStyle->textFormat( name );
154  QPixmap pm = QgsTextFormat::textFormatPreviewPixmap( format, QSize( width, height ), QString(), height / 20 );
155  QByteArray data;
156  QBuffer buffer( &data );
157  pm.save( &buffer, "PNG", 100 );
158  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
159  break;
160  }
161 
163  {
164  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
165  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
166  const QgsPalLayerSettings settings = mStyle->labelSettings( name );
167  QPixmap pm = QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( width, height ), QString(), height / 20 );
168  QByteArray data;
169  QBuffer buffer( &data );
170  pm.save( &buffer, "PNG", 100 );
171  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
172  break;
173  }
174 
176  {
177  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).horizontalAdvance( 'X' ) * 23 );
178  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
179 
180  const QgsLegendPatchShape shape = mStyle->legendPatchShape( name );
181  if ( const QgsSymbol *symbol = mStyle->previewSymbolForPatchShape( shape ) )
182  {
183  QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, QSize( width, height ), height / 20, nullptr, false, nullptr, &shape );
184  QByteArray data;
185  QBuffer buffer( &data );
186  pm.save( &buffer, "PNG", 100 );
187  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
188  }
189  break;
190  }
191 
193  case QgsStyle::TagEntity:
196  break;
197  }
198  return tooltip;
199  }
200  else
201  {
202  return name;
203  }
204  }
205  case Tags:
206  return mStyle->tagsOfSymbol( entityType, name ).join( QLatin1String( ", " ) );
207  }
208  return QVariant();
209  }
210 
211  case Qt::DecorationRole:
212  {
213  // Generate icons at all additional sizes specified for the model.
214  // This allows the model to have size responsive icons.
215 
216  if ( !mExpressionContext )
217  {
218  // build the expression context once, and keep it around. Usually this is a no-no, but in this
219  // case we want to avoid creating potentially thousands of contexts one-by-one (usually one context
220  // is created for a batch of multiple evalutions like this), and we only use a very minimal context
221  // anyway...
222  mExpressionContext = std::make_unique< QgsExpressionContext >();
223  mExpressionContext->appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
224  }
225 
226  switch ( index.column() )
227  {
228  case Name:
229  switch ( entityType )
230  {
232  {
233  // use cached icon if possible
234  QIcon icon = mIconCache[ entityType ].value( name );
235  if ( !icon.isNull() )
236  return icon;
237 
238  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
239  if ( symbol )
240  {
241  if ( mAdditionalSizes.isEmpty() )
242  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( 24, 24 ), 1, nullptr, false, mExpressionContext.get() ) );
243 
244  for ( const QSize &s : mAdditionalSizes )
245  {
246  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ), nullptr, false, mExpressionContext.get() ) );
247  }
248 
249  }
250  mIconCache[ entityType ].insert( name, icon );
251  return icon;
252  }
254  {
255  // use cached icon if possible
256  QIcon icon = mIconCache[ entityType ].value( name );
257  if ( !icon.isNull() )
258  return icon;
259 
260  std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
261  if ( ramp )
262  {
263  if ( mAdditionalSizes.isEmpty() )
264  icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), QSize( 24, 24 ), 1 ) );
265  for ( const QSize &s : mAdditionalSizes )
266  {
267  icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
268  }
269 
270  }
271  mIconCache[ entityType ].insert( name, icon );
272  return icon;
273  }
274 
276  {
277  // use cached icon if possible
278  QIcon icon = mIconCache[ entityType ].value( name );
279  if ( !icon.isNull() )
280  return icon;
281 
282  const QgsTextFormat format( mStyle->textFormat( name ) );
283  if ( mAdditionalSizes.isEmpty() )
284  icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, QSize( 24, 24 ), QString(), 1 ) );
285  for ( const QSize &s : mAdditionalSizes )
286  {
287  icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
288  }
289  mIconCache[ entityType ].insert( name, icon );
290  return icon;
291  }
292 
294  {
295  // use cached icon if possible
296  QIcon icon = mIconCache[ entityType ].value( name );
297  if ( !icon.isNull() )
298  return icon;
299 
300  const QgsPalLayerSettings settings( mStyle->labelSettings( name ) );
301  if ( mAdditionalSizes.isEmpty() )
302  icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( 24, 24 ), QString(), 1 ) );
303  for ( const QSize &s : mAdditionalSizes )
304  {
305  icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
306  }
307  mIconCache[ entityType ].insert( name, icon );
308  return icon;
309  }
310 
312  {
313  // use cached icon if possible
314  QIcon icon = mIconCache[ entityType ].value( name );
315  if ( !icon.isNull() )
316  return icon;
317 
318  const QgsLegendPatchShape shape = mStyle->legendPatchShape( name );
319  if ( !shape.isNull() )
320  {
321  if ( const QgsSymbol *symbol = mStyle->previewSymbolForPatchShape( shape ) )
322  {
323  if ( mAdditionalSizes.isEmpty() )
324  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, QSize( 24, 24 ), 1, nullptr, false, mExpressionContext.get(), &shape ) );
325 
326  for ( const QSize &s : mAdditionalSizes )
327  {
328  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol, s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ), nullptr, false, mExpressionContext.get(), &shape ) );
329  }
330  }
331  }
332  mIconCache[ entityType ].insert( name, icon );
333  return icon;
334  }
335 
337  {
338  // hack for now -- we just use a generic "3d icon" svg file.
339  // TODO - render proper thumbnails
340 
341  // use cached icon if possible
342  QIcon icon = mIconCache[ entityType ].value( name );
343  if ( !icon.isNull() )
344  return icon;
345 
346  if ( sIconGenerator && !mPending3dSymbolIcons.contains( name ) )
347  {
348  mPending3dSymbolIcons.insert( name );
349  sIconGenerator->generateIcon( mStyle, QgsStyle::Symbol3DEntity, name );
350  }
351 
352  // TODO - use hourglass icon
353  if ( mAdditionalSizes.isEmpty() )
354  icon.addFile( QgsApplication::defaultThemePath() + QDir::separator() + QStringLiteral( "3d.svg" ), QSize( 24, 24 ) );
355  for ( const QSize &s : mAdditionalSizes )
356  {
357  icon.addFile( QgsApplication::defaultThemePath() + QDir::separator() + QStringLiteral( "3d.svg" ), s );
358  }
359  mIconCache[ entityType ].insert( name, icon );
360  return icon;
361  }
362 
363  case QgsStyle::TagEntity:
365  return QVariant();
366  }
367  break;
368 
369  case Tags:
370  return QVariant();
371  }
372  return QVariant();
373  }
374 
375  case TypeRole:
376  return entityType;
377 
378  case TagRole:
379  return mStyle->tagsOfSymbol( entityType, name );
380 
381  case IsFavoriteRole:
382  return mStyle->isFavorite( entityType, name );
383 
384  case SymbolTypeRole:
385  {
386  switch ( entityType )
387  {
389  {
390  const QgsSymbol *symbol = mStyle->symbolRef( name );
391  return symbol ? static_cast< int >( symbol->type() ) : QVariant();
392  }
393 
395  return static_cast< int >( mStyle->legendPatchShapeSymbolType( name ) );
396 
397  case QgsStyle::TagEntity:
403  return QVariant();
404  }
405  return QVariant();
406  }
407 
408  case LayerTypeRole:
409  {
410  switch ( entityType )
411  {
413  return mStyle->labelSettingsLayerType( name );
414 
418  case QgsStyle::TagEntity:
422  return QVariant();
423  }
424  return QVariant();
425  }
426 
428  {
429  switch ( entityType )
430  {
432  {
433  QVariantList res;
434  const QList< QgsWkbTypes::GeometryType > types = mStyle->symbol3DCompatibleGeometryTypes( name );
435  res.reserve( types.size() );
436  for ( QgsWkbTypes::GeometryType type : types )
437  {
438  res << static_cast< int >( type );
439  }
440  return res;
441  }
442 
446  case QgsStyle::TagEntity:
450  return QVariant();
451  }
452  return QVariant();
453  }
454 
455  case EntityName:
456  return name;
457 
458  case StyleName:
459  return mStyle->name();
460 
461  case StyleFileName:
462  return mStyle->fileName();
463 
464  default:
465  return QVariant();
466  }
467 #ifndef _MSC_VER // avoid warning
468  return QVariant(); // avoid warning
469 #endif
470 }
471 
472 bool QgsStyleModel::setData( const QModelIndex &index, const QVariant &value, int role )
473 {
474  if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) || role != Qt::EditRole )
475  return false;
476 
477  switch ( index.column() )
478  {
479  case Name:
480  {
481  QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
482  QString name;
483  switch ( entityType )
484  {
485  case QgsStyle::TagEntity:
487  return false;
488 
489  default:
490  name = mEntityNames[ entityType ].value( index.row() - offsetForEntity( entityType ) );
491  break;
492  }
493 
494  const QString newName = value.toString();
495  return mStyle->renameEntity( entityType, name, newName );
496  }
497 
498  case Tags:
499  return false;
500  }
501 
502  return false;
503 }
504 
505 Qt::ItemFlags QgsStyleModel::flags( const QModelIndex &index ) const
506 {
507  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
508  if ( index.isValid() && index.column() == Name )
509  {
510  return flags | Qt::ItemIsEditable;
511  }
512  else
513  {
514  return flags;
515  }
516 }
517 
518 QVariant QgsStyleModel::headerData( int section, Qt::Orientation orientation, int role ) const
519 {
520  return headerDataStatic( section, orientation, role );
521 }
522 
523 QVariant QgsStyleModel::headerDataStatic( int section, Qt::Orientation orientation, int role )
524 {
525  if ( role == Qt::DisplayRole )
526  {
527  if ( orientation == Qt::Vertical ) //row
528  {
529  return QVariant( section );
530  }
531  else
532  {
533  switch ( section )
534  {
535  case Name:
536  return QVariant( tr( "Name" ) );
537 
538  case Tags:
539  return QVariant( tr( "Tags" ) );
540 
541  default:
542  return QVariant();
543  }
544  }
545  }
546  else
547  {
548  return QVariant();
549  }
550 }
551 
552 QModelIndex QgsStyleModel::index( int row, int column, const QModelIndex &parent ) const
553 {
554  if ( !hasIndex( row, column, parent ) )
555  return QModelIndex();
556 
557  if ( !parent.isValid() )
558  {
559  return createIndex( row, column );
560  }
561 
562  return QModelIndex();
563 }
564 
565 QModelIndex QgsStyleModel::parent( const QModelIndex & ) const
566 {
567  //all items are top level for now
568  return QModelIndex();
569 }
570 
571 int QgsStyleModel::rowCount( const QModelIndex &parent ) const
572 {
573  if ( !parent.isValid() )
574  {
575  int count = 0;
576  for ( QgsStyle::StyleEntity type : ENTITIES )
577  count += mEntityNames[ type ].size();
578  return count;
579  }
580  return 0;
581 }
582 
583 int QgsStyleModel::columnCount( const QModelIndex & ) const
584 {
585  return 2;
586 }
587 
589 {
590  if ( mAdditionalSizes.contains( size ) )
591  return;
592 
593  mAdditionalSizes << size;
594 
595  if ( sIconGenerator )
596  sIconGenerator->setIconSizes( mAdditionalSizes );
597 
598  mIconCache.clear();
599 }
600 
602 {
603  sIconGenerator = generator;
604  connect( sIconGenerator, &QgsAbstractStyleEntityIconGenerator::iconGenerated, QgsApplication::defaultStyleModel(), &QgsStyleModel::iconGenerated, Qt::QueuedConnection );
605 }
606 
607 void QgsStyleModel::onEntityAdded( QgsStyle::StyleEntity type, const QString &name )
608 {
609  mIconCache[ type ].remove( name );
610  const QStringList oldSymbolNames = mEntityNames[ type ];
611  const QStringList newSymbolNames = mStyle->allNames( type );
612 
613  // find index of newly added symbol
614  const int newNameIndex = newSymbolNames.indexOf( name );
615  if ( newNameIndex < 0 )
616  return; // shouldn't happen
617 
618  const int offset = offsetForEntity( type );
619  beginInsertRows( QModelIndex(), newNameIndex + offset, newNameIndex + offset );
620  mEntityNames[ type ] = newSymbolNames;
621  endInsertRows();
622 }
623 
624 void QgsStyleModel::onEntityRemoved( QgsStyle::StyleEntity type, const QString &name )
625 {
626  mIconCache[ type ].remove( name );
627  const QStringList oldSymbolNames = mEntityNames[ type ];
628  const QStringList newSymbolNames = mStyle->allNames( type );
629 
630  // find index of removed symbol
631  const int oldNameIndex = oldSymbolNames.indexOf( name );
632  if ( oldNameIndex < 0 )
633  return; // shouldn't happen
634 
635  const int offset = offsetForEntity( type );
636  beginRemoveRows( QModelIndex(), oldNameIndex + offset, oldNameIndex + offset );
637  mEntityNames[ type ] = newSymbolNames;
638  endRemoveRows();
639 }
640 
641 void QgsStyleModel::onEntityChanged( QgsStyle::StyleEntity type, const QString &name )
642 {
643  mIconCache[ type ].remove( name );
644 
645  const int offset = offsetForEntity( type );
646  QModelIndex i = index( offset + mEntityNames[ type ].indexOf( name ), Tags );
647  emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
648 }
649 
650 void QgsStyleModel::onFavoriteChanged( QgsStyle::StyleEntity type, const QString &name, bool )
651 {
652  const int offset = offsetForEntity( type );
653  QModelIndex i = index( offset + mEntityNames[ type ].indexOf( name ), Name );
654  emit dataChanged( i, i, QVector< int >() << Role::IsFavoriteRole );
655 }
656 
657 void QgsStyleModel::onEntityRename( QgsStyle::StyleEntity type, const QString &oldName, const QString &newName )
658 {
659  mIconCache[ type ].remove( oldName );
660  const QStringList oldSymbolNames = mEntityNames[ type ];
661  const QStringList newSymbolNames = mStyle->allNames( type );
662 
663  // find index of removed symbol
664  const int oldNameIndex = oldSymbolNames.indexOf( oldName );
665  if ( oldNameIndex < 0 )
666  return; // shouldn't happen
667 
668  // find index of added symbol
669  const int newNameIndex = newSymbolNames.indexOf( newName );
670  if ( newNameIndex < 0 )
671  return; // shouldn't happen
672 
673  if ( newNameIndex == oldNameIndex )
674  {
675  mEntityNames[ type ] = newSymbolNames;
676  return;
677  }
678 
679  const int offset = offsetForEntity( type );
680  beginMoveRows( QModelIndex(), oldNameIndex + offset, oldNameIndex + offset, QModelIndex(), ( newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex ) + offset );
681  mEntityNames[ type ] = newSymbolNames;
682  endMoveRows();
683 }
684 
685 void QgsStyleModel::onTagsChanged( int entity, const QString &name, const QStringList & )
686 {
687  QgsStyle::StyleEntity type = static_cast< QgsStyle::StyleEntity >( entity );
688  int row = mEntityNames[type].indexOf( name ) + offsetForEntity( type );
689  switch ( static_cast< QgsStyle::StyleEntity >( entity ) )
690  {
691  case QgsStyle::TagEntity:
693  return;
694 
695  default:
696  break;
697  }
698  emit dataChanged( index( row, Name ), index( row, Tags ) );
699 }
700 
701 void QgsStyleModel::rebuildSymbolIcons()
702 {
703  mIconCache[ QgsStyle::SymbolEntity ].clear();
704  mExpressionContext.reset();
705  emit dataChanged( index( 0, 0 ), index( mEntityNames[ QgsStyle::SymbolEntity ].count() - 1, 0 ), QVector<int>() << Qt::DecorationRole );
706 }
707 
708 void QgsStyleModel::iconGenerated( QgsStyle::StyleEntity type, const QString &name, const QIcon &icon )
709 {
710  int row = mEntityNames[type].indexOf( name ) + offsetForEntity( type );
711 
712  switch ( type )
713  {
715  mPending3dSymbolIcons.remove( name );
716  mIconCache[ QgsStyle::Symbol3DEntity ].insert( name, icon );
717  emit dataChanged( index( row, 0 ), index( row, 0 ) );
718  break;
719 
721  case QgsStyle::TagEntity:
727  break;
728  }
729 }
730 
731 QgsStyle::StyleEntity QgsStyleModel::entityTypeFromRow( int row ) const
732 {
733  int maxRowForEntity = 0;
734  for ( QgsStyle::StyleEntity type : ENTITIES )
735  {
736  maxRowForEntity += mEntityNames[ type ].size();
737  if ( row < maxRowForEntity )
738  return type;
739  }
740 
741  // should never happen
742  Q_ASSERT( false );
743  return QgsStyle::SymbolEntity;
744 }
745 
746 int QgsStyleModel::offsetForEntity( QgsStyle::StyleEntity entity ) const
747 {
748  int offset = 0;
749  for ( QgsStyle::StyleEntity type : ENTITIES )
750  {
751  if ( type == entity )
752  return offset;
753 
754  offset += mEntityNames[ type ].size();
755  }
756  return 0;
757 }
758 
759 //
760 // QgsStyleProxyModel
761 //
762 
764  : QSortFilterProxyModel( parent )
765  , mStyle( style )
766 {
767  mModel = new QgsStyleModel( mStyle, this );
768  setSourceModel( mModel );
769  initialize();
770 }
771 
772 void QgsStyleProxyModel::initialize()
773 {
774  setSortCaseSensitivity( Qt::CaseInsensitive );
775 // setSortLocaleAware( true );
776  setDynamicSortFilter( true );
777  sort( 0 );
778 
779  if ( mStyle )
780  {
781  connect( mStyle, &QgsStyle::entityTagsChanged, this, [ = ]
782  {
783  // update tagged symbols if filtering by tag
784  if ( mTagId >= 0 )
785  setTagId( mTagId );
786  if ( mSmartGroupId >= 0 )
787  setSmartGroupId( mSmartGroupId );
788  } );
789 
790  connect( mStyle, &QgsStyle::entityRenamed, this, [ = ]( QgsStyle::StyleEntity entity, const QString &, const QString & )
791  {
792  switch ( entity )
793  {
795  case QgsStyle::TagEntity:
796  return;
797 
798  default:
799  break;
800  }
801 
802  if ( mSmartGroupId >= 0 )
803  setSmartGroupId( mSmartGroupId );
804  } );
805  }
806 }
807 
809  : QSortFilterProxyModel( parent )
810  , mModel( model )
811  , mStyle( model->style() )
812 {
813  setSourceModel( mModel );
814  initialize();
815 }
816 
817 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
819  : QSortFilterProxyModel( parent )
820  , mCombinedModel( model )
821 {
822  setSourceModel( mCombinedModel );
823  initialize();
824 }
825 #endif
826 
827 bool QgsStyleProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
828 {
829  if ( mFilterString.isEmpty() && !mEntityFilterEnabled && !mSymbolTypeFilterEnabled && mTagId < 0 && mSmartGroupId < 0 && !mFavoritesOnly && mTagFilter.isEmpty() )
830  return true;
831 
832  const QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
833 
834  if ( sourceModel()->data( index, QgsStyleModel::IsTitleRole ).toBool() )
835  return true;
836 
837  const QString name = sourceModel()->data( index ).toString();
838  const QStringList tags = sourceModel()->data( index, QgsStyleModel::TagRole ).toStringList();
839 
840  QgsStyle::StyleEntity styleEntityType = static_cast< QgsStyle::StyleEntity >( sourceModel()->data( index, QgsStyleModel::TypeRole ).toInt() );
841  if ( mEntityFilterEnabled && ( mEntityFilters.empty() || !mEntityFilters.contains( styleEntityType ) ) )
842  return false;
843 
844  Qgis::SymbolType symbolType = static_cast< Qgis::SymbolType >( sourceModel()->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
845  if ( mSymbolTypeFilterEnabled && symbolType != mSymbolType )
846  return false;
847 
848  if ( mLayerType != QgsWkbTypes::UnknownGeometry )
849  {
850  switch ( styleEntityType )
851  {
854  case QgsStyle::TagEntity:
858  break;
859 
861  {
862  if ( mLayerType != static_cast< QgsWkbTypes::GeometryType >( sourceModel()->data( index, QgsStyleModel::LayerTypeRole ).toInt() ) )
863  return false;
864  break;
865  }
866 
868  {
869  const QVariantList types = sourceModel()->data( index, QgsStyleModel::CompatibleGeometryTypesRole ).toList();
870  if ( !types.empty() && !types.contains( mLayerType ) )
871  return false;
872  break;
873  }
874  }
875  }
876 
877  if ( mTagId >= 0 && !mTaggedSymbolNames.contains( name ) )
878  return false;
879 
880  if ( mSmartGroupId >= 0 && !mSmartGroupSymbolNames.contains( name ) )
881  return false;
882 
883  if ( !mTagFilter.isEmpty() && !tags.contains( mTagFilter, Qt::CaseInsensitive ) )
884  return false;
885 
886  if ( mFavoritesOnly && !sourceModel()->data( index, QgsStyleModel::IsFavoriteRole ).toBool() )
887  return false;
888 
889  if ( !mFilterString.isEmpty() )
890  {
891  // filter by word, in both filter string and style entity name/tags
892  // this allows matching of a filter string "hash line" to the symbol "hashed red lines"
893  const QStringList partsToMatch = mFilterString.trimmed().split( ' ' );
894 
895  QStringList partsToSearch = name.split( ' ' );
896  for ( const QString &tag : tags )
897  {
898  partsToSearch.append( tag.split( ' ' ) );
899  }
900 
901  for ( const QString &part : partsToMatch )
902  {
903  bool found = false;
904  for ( const QString &partToSearch : std::as_const( partsToSearch ) )
905  {
906  if ( partToSearch.contains( part, Qt::CaseInsensitive ) )
907  {
908  found = true;
909  break;
910  }
911  }
912  if ( !found )
913  return false; // couldn't find a match for this word, so hide entity
914  }
915  }
916 
917  return true;
918 }
919 
920 bool QgsStyleProxyModel::lessThan( const QModelIndex &left, const QModelIndex &right ) const
921 {
922  const QString leftSource = sourceModel()->data( left, QgsStyleModel::StyleFileName ).toString();
923  const QString rightSource = sourceModel()->data( right, QgsStyleModel::StyleFileName ).toString();
924  if ( leftSource != rightSource )
925  return QString::localeAwareCompare( leftSource, rightSource ) < 0;
926 
927  const QString leftName = sourceModel()->data( left, QgsStyleModel::EntityName ).toString();
928  const QString rightName = sourceModel()->data( right, QgsStyleModel::EntityName ).toString();
929  return QString::localeAwareCompare( leftName, rightName ) < 0;
930 }
931 
932 void QgsStyleProxyModel::setFilterString( const QString &filter )
933 {
934  mFilterString = filter;
935  invalidateFilter();
936 }
937 
938 
940 {
941  return mFavoritesOnly;
942 }
943 
944 void QgsStyleProxyModel::setFavoritesOnly( bool favoritesOnly )
945 {
946  mFavoritesOnly = favoritesOnly;
947  invalidateFilter();
948 }
949 
951 {
952  if ( mModel )
953  mModel->addDesiredIconSize( size );
954 #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
955  if ( mCombinedModel )
956  mCombinedModel->addDesiredIconSize( size );
957 #endif
958 }
959 
961 {
962  return mSymbolTypeFilterEnabled;
963 }
964 
966 {
967  mSymbolTypeFilterEnabled = enabled;
968  invalidateFilter();
969 }
970 
972 {
973  return mLayerType;
974 }
975 
977 {
978  mLayerType = type;
979  invalidateFilter();
980 }
981 
983 {
984  if ( !mStyle )
985  return;
986  mTagId = id;
987 
988  mTaggedSymbolNames.clear();
989  if ( mTagId >= 0 )
990  {
991  for ( QgsStyle::StyleEntity entity : ENTITIES )
992  mTaggedSymbolNames.append( mStyle->symbolsWithTag( entity, mTagId ) );
993  }
994 
995  invalidateFilter();
996 }
997 
999 {
1000  return mTagId;
1001 }
1002 
1003 void QgsStyleProxyModel::setTagString( const QString &tag )
1004 {
1005  mTagFilter = tag;
1006 
1007  invalidateFilter();
1008 }
1009 
1011 {
1012  return mTagFilter;
1013 }
1014 
1016 {
1017  if ( !mStyle )
1018  return;
1019 
1020  mSmartGroupId = id;
1021 
1022  mSmartGroupSymbolNames.clear();
1023  if ( mSmartGroupId >= 0 )
1024  {
1025  for ( QgsStyle::StyleEntity entity : ENTITIES )
1026  mSmartGroupSymbolNames.append( mStyle->symbolsOfSmartgroup( entity, mSmartGroupId ) );
1027  }
1028 
1029  invalidateFilter();
1030 }
1031 
1033 {
1034  return mSmartGroupId;
1035 }
1036 
1038 {
1039  return mSymbolType;
1040 }
1041 
1043 {
1044  mSymbolType = symbolType;
1045  invalidateFilter();
1046 }
1047 
1049 {
1050  return mEntityFilterEnabled;
1051 }
1052 
1053 void QgsStyleProxyModel::setEntityFilterEnabled( bool entityFilterEnabled )
1054 {
1055  mEntityFilterEnabled = entityFilterEnabled;
1056  invalidateFilter();
1057 }
1058 
1060 {
1061  return mEntityFilters.empty() ? QgsStyle::SymbolEntity : mEntityFilters.at( 0 );
1062 }
1063 
1065 {
1066  mEntityFilters = QList< QgsStyle::StyleEntity >() << entityFilter;
1067  invalidateFilter();
1068 }
1069 
1070 void QgsStyleProxyModel::setEntityFilters( const QList<QgsStyle::StyleEntity> &filters )
1071 {
1072  mEntityFilters = filters;
1073  invalidateFilter();
1074 }
1075 
SymbolType
Symbol types.
Definition: qgis.h:206
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2039
An abstract base class for icon generators for a QgsStyleModel.
Definition: qgsstylemodel.h:45
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.
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 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)
Returns a pixmap preview for label settings.
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
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
@ StyleName
Name of associated QgsStyle (QgsStyle::name()) (since QGIS 3.26)
@ LayerTypeRole
Layer type (for label settings entities)
@ StyleFileName
File name of associated QgsStyle (QgsStyle::fileName()) (since QGIS 3.26)
@ EntityName
Entity name (since QGIS 3.26)
@ TypeRole
Style entity type, see QgsStyle::StyleEntity.
@ CompatibleGeometryTypesRole
Compatible layer geometry types (for 3D symbols)
@ IsFavoriteRole
Whether entity is flagged as a favorite.
@ SymbolTypeRole
Symbol type (for symbol or legend patch shape entities)
@ IsTitleRole
True if the index corresponds to a title item (since QGIS 3.26)
@ TagRole
String list of tags.
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.
QgsWkbTypes::GeometryType layerType() const
Returns the layer type filter, or QgsWkbTypes::UnknownGeometry if no layer type filter is present.
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 setLayerType(QgsWkbTypes::GeometryType type)
Sets the layer type filter.
void setSymbolType(Qgis::SymbolType type)
Sets the symbol type filter.
bool entityFilterEnabled() const
Returns true if filtering by entity type is enabled.
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate 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...
QgsTextFormat textFormat(const QString &name) const
Returns the text format with the specified name.
Definition: qgsstyle.cpp:2120
QStringList allNames(StyleEntity type) const
Returns a list of the names of all existing entities of the specified type.
Definition: qgsstyle.cpp:2238
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.
Definition: qgsstyle.cpp:1335
QList< QgsWkbTypes::GeometryType > symbol3DCompatibleGeometryTypes(const QString &name) const
Returns the list of the vector layer geometry types which are compatible with the 3D symbol with the ...
Definition: qgsstyle.cpp:2173
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:291
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:2372
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:179
@ LabelSettingsEntity
Label settings.
Definition: qgsstyle.h:185
@ TextFormatEntity
Text formats.
Definition: qgsstyle.h:184
@ SmartgroupEntity
Smart groups.
Definition: qgsstyle.h:183
@ Symbol3DEntity
3D symbol entity (since QGIS 3.14)
Definition: qgsstyle.h:187
@ SymbolEntity
Symbols.
Definition: qgsstyle.h:180
@ TagEntity
Tags.
Definition: qgsstyle.h:181
@ ColorrampEntity
Color ramps.
Definition: qgsstyle.h:182
@ LegendPatchShapeEntity
Legend patch shape (since QGIS 3.14)
Definition: qgsstyle.h:186
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
Definition: qgsstyle.cpp:1911
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:297
QString fileName() const
Returns the current file name of the style database.
Definition: qgsstyle.h:888
QgsWkbTypes::GeometryType labelSettingsLayerType(const QString &name) const
Returns the layer geometry type corresponding to the label settings with the specified name,...
Definition: qgsstyle.cpp:2181
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition: qgsstyle.cpp:462
bool renameEntity(StyleEntity type, const QString &oldName, const QString &newName)
Renames an entity of the specified type from oldName to newName.
Definition: qgsstyle.cpp:262
bool isFavorite(StyleEntity type, const QString &name)
Returns true if the symbol with matching type and name is marked as a favorite.
Definition: qgsstyle.cpp:1962
QString name() const
Returns the name of the style.
Definition: qgsstyle.cpp:106
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.
Definition: qgsstyle.cpp:2140
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,...
Definition: qgsstyle.cpp:2155
const QgsSymbol * previewSymbolForPatchShape(const QgsLegendPatchShape &shape) const
Returns a symbol to use for rendering preview icons for a patch shape.
Definition: qgsstyle.cpp:2209
QgsLegendPatchShape legendPatchShape(const QString &name) const
Returns the legend patch shape with the specified name.
Definition: qgsstyle.cpp:2145
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)
Returns a pixmap preview for a color ramp.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:93
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
Container for all settings relating to text rendering.
Definition: qgstextformat.h:41
static QPixmap textFormatPreviewPixmap(const QgsTextFormat &format, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for a text format.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:141
const double ICON_PADDING_FACTOR
const auto ENTITIES