QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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"
22 #include "qgsproject.h"
24 #include <QIcon>
25 
26 const double ICON_PADDING_FACTOR = 0.16;
27 
28 QgsStyleModel::QgsStyleModel( QgsStyle *style, QObject *parent )
29  : QAbstractItemModel( parent )
30  , mStyle( style )
31 {
32  Q_ASSERT( mStyle );
33  mSymbolNames = mStyle->symbolNames();
34  mRampNames = mStyle->colorRampNames();
35  mTextFormatNames = mStyle->textFormatNames();
36  mLabelSettingsNames = mStyle->labelSettingsNames();
37 
38  connect( mStyle, &QgsStyle::symbolSaved, this, &QgsStyleModel::onSymbolAdded );
39  connect( mStyle, &QgsStyle::symbolRemoved, this, &QgsStyleModel::onSymbolRemoved );
40  connect( mStyle, &QgsStyle::symbolRenamed, this, &QgsStyleModel::onSymbolRename );
41  connect( mStyle, &QgsStyle::symbolChanged, this, &QgsStyleModel::onSymbolChanged );
42 
43  connect( mStyle, &QgsStyle::rampAdded, this, &QgsStyleModel::onRampAdded );
44  connect( mStyle, &QgsStyle::rampChanged, this, &QgsStyleModel::onRampChanged );
45  connect( mStyle, &QgsStyle::rampRemoved, this, &QgsStyleModel::onRampRemoved );
46  connect( mStyle, &QgsStyle::rampRenamed, this, &QgsStyleModel::onRampRename );
47 
48  connect( mStyle, &QgsStyle::textFormatAdded, this, &QgsStyleModel::onTextFormatAdded );
49  connect( mStyle, &QgsStyle::textFormatChanged, this, &QgsStyleModel::onTextFormatChanged );
50  connect( mStyle, &QgsStyle::textFormatRemoved, this, &QgsStyleModel::onTextFormatRemoved );
51  connect( mStyle, &QgsStyle::textFormatRenamed, this, &QgsStyleModel::onTextFormatRename );
52 
53  connect( mStyle, &QgsStyle::labelSettingsAdded, this, &QgsStyleModel::onLabelSettingsAdded );
54  connect( mStyle, &QgsStyle::labelSettingsChanged, this, &QgsStyleModel::onLabelSettingsChanged );
55  connect( mStyle, &QgsStyle::labelSettingsRemoved, this, &QgsStyleModel::onLabelSettingsRemoved );
56  connect( mStyle, &QgsStyle::labelSettingsRenamed, this, &QgsStyleModel::onLabelSettingsRename );
57 
58  connect( mStyle, &QgsStyle::entityTagsChanged, this, &QgsStyleModel::onTagsChanged );
59 
60  // when a remote svg or image has been fetched, update the model's decorations.
61  // this is required if a symbol utilizes remote svgs, and the current icons
62  // have been generated using the temporary "downloading" svg. In this case
63  // we require the preview to be regenerated to use the correct fetched
64  // svg
65  connect( QgsApplication::svgCache(), &QgsSvgCache::remoteSvgFetched, this, &QgsStyleModel::rebuildSymbolIcons );
66  connect( QgsApplication::imageCache(), &QgsImageCache::remoteImageFetched, this, &QgsStyleModel::rebuildSymbolIcons );
67 
68  // if project color scheme changes, we need to redraw symbols - they may use project colors and accordingly
69  // need updating to reflect the new colors
70  connect( QgsProject::instance(), &QgsProject::projectColorsChanged, this, &QgsStyleModel::rebuildSymbolIcons );
71 }
72 
73 QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
74 {
75  if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) )
76  return QVariant();
77 
78 
79  QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
80 
81  QString name;
82  switch ( entityType )
83  {
85  name = mSymbolNames.value( index.row() );
86  break;
87 
89  name = mRampNames.value( index.row() - mSymbolNames.size() );
90  break;
91 
93  name = mTextFormatNames.value( index.row() - mSymbolNames.size() - mRampNames.size() );
94  break;
95 
97  name = mLabelSettingsNames.value( index.row() - mSymbolNames.size() - mRampNames.size() - mTextFormatNames.size() );
98  break;
99 
100  case QgsStyle::TagEntity:
102  break;
103  }
104 
105  switch ( role )
106  {
107  case Qt::DisplayRole:
108  case Qt::ToolTipRole:
109  case Qt::EditRole:
110  {
111  switch ( index.column() )
112  {
113  case Name:
114  {
115  const QStringList tags = mStyle->tagsOfSymbol( entityType, name );
116 
117  if ( role == Qt::ToolTipRole )
118  {
119  QString tooltip = QStringLiteral( "<h3>%1</h3><p><i>%2</i>" ).arg( name,
120  tags.count() > 0 ? tags.join( QStringLiteral( ", " ) ) : tr( "Not tagged" ) );
121 
122  switch ( entityType )
123  {
125  {
126  // create very large preview image
127  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
128  if ( symbol )
129  {
130  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).width( 'X' ) * 23 );
131  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
132  QPixmap pm = QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( width, height ), height / 20, nullptr, false, mExpressionContext.get() );
133  QByteArray data;
134  QBuffer buffer( &data );
135  pm.save( &buffer, "PNG", 100 );
136  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
137  }
138  break;
139  }
140 
142  {
143  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).width( 'X' ) * 23 );
144  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
145  const QgsTextFormat format = mStyle->textFormat( name );
146  QPixmap pm = QgsTextFormat::textFormatPreviewPixmap( format, QSize( width, height ), QString(), height / 20 );
147  QByteArray data;
148  QBuffer buffer( &data );
149  pm.save( &buffer, "PNG", 100 );
150  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
151  break;
152  }
153 
155  {
156  int width = static_cast< int >( Qgis::UI_SCALE_FACTOR * QFontMetrics( data( index, Qt::FontRole ).value< QFont >() ).width( 'X' ) * 23 );
157  int height = static_cast< int >( width / 1.61803398875 ); // golden ratio
158  const QgsPalLayerSettings settings = mStyle->labelSettings( name );
159  QPixmap pm = QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( width, height ), QString(), height / 20 );
160  QByteArray data;
161  QBuffer buffer( &data );
162  pm.save( &buffer, "PNG", 100 );
163  tooltip += QStringLiteral( "<p><img src='data:image/png;base64, %3'>" ).arg( QString( data.toBase64() ) );
164  break;
165  }
166 
168  case QgsStyle::TagEntity:
170  break;
171  }
172  return tooltip;
173  }
174  else
175  {
176  return name;
177  }
178  }
179  case Tags:
180  return mStyle->tagsOfSymbol( entityType, name ).join( QStringLiteral( ", " ) );
181  }
182  return QVariant();
183  }
184 
185  case Qt::DecorationRole:
186  {
187  // Generate icons at all additional sizes specified for the model.
188  // This allows the model to have size responsive icons.
189 
190  if ( !mExpressionContext )
191  {
192  // build the expression context once, and keep it around. Usually this is a no-no, but in this
193  // case we want to avoid creating potentially thousands of contexts one-by-one (usually one context
194  // is created for a batch of multiple evalutions like this), and we only use a very minimal context
195  // anyway...
196  mExpressionContext = qgis::make_unique< QgsExpressionContext >();
197  mExpressionContext->appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) );
198  }
199 
200  switch ( index.column() )
201  {
202  case Name:
203  switch ( entityType )
204  {
206  {
207  // use cached icon if possible
208  QIcon icon = mSymbolIconCache.value( name );
209  if ( !icon.isNull() )
210  return icon;
211 
212  std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
213  if ( symbol )
214  {
215  if ( mAdditionalSizes.isEmpty() )
216  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( 24, 24 ), 1, nullptr, false, mExpressionContext.get() ) );
217 
218  for ( const QSize &s : mAdditionalSizes )
219  {
220  icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ), nullptr, false, mExpressionContext.get() ) );
221  }
222 
223  }
224  mSymbolIconCache.insert( name, icon );
225  return icon;
226  }
228  {
229  // use cached icon if possible
230  QIcon icon = mColorRampIconCache.value( name );
231  if ( !icon.isNull() )
232  return icon;
233 
234  std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
235  if ( ramp )
236  {
237  if ( mAdditionalSizes.isEmpty() )
238  icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), QSize( 24, 24 ), 1 ) );
239  for ( const QSize &s : mAdditionalSizes )
240  {
241  icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
242  }
243 
244  }
245  mColorRampIconCache.insert( name, icon );
246  return icon;
247  }
248 
250  {
251  // use cached icon if possible
252  QIcon icon = mTextFormatIconCache.value( name );
253  if ( !icon.isNull() )
254  return icon;
255 
256  const QgsTextFormat format( mStyle->textFormat( name ) );
257  if ( mAdditionalSizes.isEmpty() )
258  icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, QSize( 24, 24 ), QString(), 1 ) );
259  for ( const QSize &s : mAdditionalSizes )
260  {
261  icon.addPixmap( QgsTextFormat::textFormatPreviewPixmap( format, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
262  }
263  mTextFormatIconCache.insert( name, icon );
264  return icon;
265  }
266 
268  {
269  // use cached icon if possible
270  QIcon icon = mLabelSettingsIconCache.value( name );
271  if ( !icon.isNull() )
272  return icon;
273 
274  const QgsPalLayerSettings settings( mStyle->labelSettings( name ) );
275  if ( mAdditionalSizes.isEmpty() )
276  icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, QSize( 24, 24 ), QString(), 1 ) );
277  for ( const QSize &s : mAdditionalSizes )
278  {
279  icon.addPixmap( QgsPalLayerSettings::labelSettingsPreviewPixmap( settings, s, QString(), static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
280  }
281  mLabelSettingsIconCache.insert( name, icon );
282  return icon;
283  }
284 
285  case QgsStyle::TagEntity:
287  return QVariant();
288  }
289  break;
290 
291  case Tags:
292  return QVariant();
293  }
294  return QVariant();
295  }
296 
297  case TypeRole:
298  return entityType;
299 
300  case TagRole:
301  return mStyle->tagsOfSymbol( entityType, name );
302 
303  case IsFavoriteRole:
304  return mStyle->isFavorite( entityType, name );
305 
306  case SymbolTypeRole:
307  {
308  if ( entityType != QgsStyle::SymbolEntity )
309  return QVariant();
310 
311  const QgsSymbol *symbol = mStyle->symbolRef( name );
312  return symbol ? symbol->type() : QVariant();
313  }
314 
315  case LayerTypeRole:
316  {
317  if ( entityType != QgsStyle::LabelSettingsEntity )
318  return QVariant();
319 
320  return mStyle->labelSettingsLayerType( name );
321  }
322 
323  default:
324  return QVariant();
325  }
326 #ifndef _MSC_VER // avoid warning
327  return QVariant(); // avoid warning
328 #endif
329 }
330 
331 bool QgsStyleModel::setData( const QModelIndex &index, const QVariant &value, int role )
332 {
333  if ( index.row() < 0 || index.row() >= rowCount( QModelIndex() ) || role != Qt::EditRole )
334  return false;
335 
336  switch ( index.column() )
337  {
338  case Name:
339  {
340  QgsStyle::StyleEntity entityType = entityTypeFromRow( index.row() );
341  QString name;
342  switch ( entityType )
343  {
345  name = mSymbolNames.value( index.row() );
346  break;
347 
349  name = mRampNames.value( index.row() - mSymbolNames.size() );
350  break;
351 
353  name = mTextFormatNames.value( index.row() - mSymbolNames.size() - mRampNames.size() );
354  break;
355 
357  name = mLabelSettingsNames.value( index.row() - mSymbolNames.size() - mRampNames.size() - mTextFormatNames.size() );
358  break;
359 
360  case QgsStyle::TagEntity:
362  break;
363  }
364 
365  const QString newName = value.toString();
366 
367  switch ( entityType )
368  {
370  return mStyle->renameSymbol( name, newName );
371 
373  return mStyle->renameColorRamp( name, newName );
374 
376  return mStyle->renameTextFormat( name, newName );
377 
379  return mStyle->renameLabelSettings( name, newName );
380 
381  case QgsStyle::TagEntity:
383  return false;
384  }
385  break;
386  }
387 
388  case Tags:
389  return false;
390  }
391 
392  return false;
393 }
394 
395 Qt::ItemFlags QgsStyleModel::flags( const QModelIndex &index ) const
396 {
397  Qt::ItemFlags flags = QAbstractItemModel::flags( index );
398  if ( index.isValid() && index.column() == Name )
399  {
400  return flags | Qt::ItemIsEditable;
401  }
402  else
403  {
404  return flags;
405  }
406 }
407 
408 QVariant QgsStyleModel::headerData( int section, Qt::Orientation orientation, int role ) const
409 {
410  if ( role == Qt::DisplayRole )
411  {
412  if ( orientation == Qt::Vertical ) //row
413  {
414  return QVariant( section );
415  }
416  else
417  {
418  switch ( section )
419  {
420  case Name:
421  return QVariant( tr( "Name" ) );
422 
423  case Tags:
424  return QVariant( tr( "Tags" ) );
425 
426  default:
427  return QVariant();
428  }
429  }
430  }
431  else
432  {
433  return QVariant();
434  }
435 }
436 
437 QModelIndex QgsStyleModel::index( int row, int column, const QModelIndex &parent ) const
438 {
439  if ( !hasIndex( row, column, parent ) )
440  return QModelIndex();
441 
442  if ( !parent.isValid() )
443  {
444  return createIndex( row, column );
445  }
446 
447  return QModelIndex();
448 }
449 
450 QModelIndex QgsStyleModel::parent( const QModelIndex & ) const
451 {
452  //all items are top level for now
453  return QModelIndex();
454 }
455 
456 int QgsStyleModel::rowCount( const QModelIndex &parent ) const
457 {
458  if ( !parent.isValid() )
459  {
460  return mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() + mLabelSettingsNames.count();
461  }
462  return 0;
463 }
464 
465 int QgsStyleModel::columnCount( const QModelIndex & ) const
466 {
467  return 2;
468 }
469 
471 {
472  if ( mAdditionalSizes.contains( size ) )
473  return;
474 
475  mAdditionalSizes << size;
476  mSymbolIconCache.clear();
477  mColorRampIconCache.clear();
478  mTextFormatIconCache.clear();
479  mLabelSettingsIconCache.clear();
480 }
481 
482 void QgsStyleModel::onSymbolAdded( const QString &name, QgsSymbol * )
483 {
484  mSymbolIconCache.remove( name );
485  const QStringList oldSymbolNames = mSymbolNames;
486  const QStringList newSymbolNames = mStyle->symbolNames();
487 
488  // find index of newly added symbol
489  const int newNameIndex = newSymbolNames.indexOf( name );
490  if ( newNameIndex < 0 )
491  return; // shouldn't happen
492 
493  beginInsertRows( QModelIndex(), newNameIndex, newNameIndex );
494  mSymbolNames = newSymbolNames;
495  endInsertRows();
496 }
497 
498 void QgsStyleModel::onSymbolRemoved( const QString &name )
499 {
500  mSymbolIconCache.remove( name );
501  const QStringList oldSymbolNames = mSymbolNames;
502  const QStringList newSymbolNames = mStyle->symbolNames();
503 
504  // find index of removed symbol
505  const int oldNameIndex = oldSymbolNames.indexOf( name );
506  if ( oldNameIndex < 0 )
507  return; // shouldn't happen
508 
509  beginRemoveRows( QModelIndex(), oldNameIndex, oldNameIndex );
510  mSymbolNames = newSymbolNames;
511  endRemoveRows();
512 }
513 
514 void QgsStyleModel::onSymbolChanged( const QString &name )
515 {
516  mSymbolIconCache.remove( name );
517 
518  QModelIndex i = index( mSymbolNames.indexOf( name ), Tags );
519  emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
520 }
521 
522 void QgsStyleModel::onSymbolRename( const QString &oldName, const QString &newName )
523 {
524  mSymbolIconCache.remove( oldName );
525  const QStringList oldSymbolNames = mSymbolNames;
526  const QStringList newSymbolNames = mStyle->symbolNames();
527 
528  // find index of removed symbol
529  const int oldNameIndex = oldSymbolNames.indexOf( oldName );
530  if ( oldNameIndex < 0 )
531  return; // shouldn't happen
532 
533  // find index of added symbol
534  const int newNameIndex = newSymbolNames.indexOf( newName );
535  if ( newNameIndex < 0 )
536  return; // shouldn't happen
537 
538  if ( newNameIndex == oldNameIndex )
539  {
540  mSymbolNames = newSymbolNames;
541  return;
542  }
543 
544  beginMoveRows( QModelIndex(), oldNameIndex, oldNameIndex, QModelIndex(), newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex );
545  mSymbolNames = newSymbolNames;
546  endMoveRows();
547 }
548 
549 void QgsStyleModel::onRampAdded( const QString &name )
550 {
551  mColorRampIconCache.remove( name );
552  const QStringList oldRampNames = mRampNames;
553  const QStringList newRampNames = mStyle->colorRampNames();
554 
555  // find index of newly added symbol
556  const int newNameIndex = newRampNames.indexOf( name );
557  if ( newNameIndex < 0 )
558  return; // shouldn't happen
559 
560  beginInsertRows( QModelIndex(), newNameIndex + mSymbolNames.count(), newNameIndex + mSymbolNames.count() );
561  mRampNames = newRampNames;
562  endInsertRows();
563 }
564 
565 void QgsStyleModel::onRampRemoved( const QString &name )
566 {
567  mColorRampIconCache.remove( name );
568  const QStringList oldRampNames = mRampNames;
569  const QStringList newRampNames = mStyle->colorRampNames();
570 
571  // find index of removed symbol
572  const int oldNameIndex = oldRampNames.indexOf( name );
573  if ( oldNameIndex < 0 )
574  return; // shouldn't happen
575 
576  beginRemoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count(), oldNameIndex + mSymbolNames.count() );
577  mRampNames = newRampNames;
578  endRemoveRows();
579 }
580 
581 void QgsStyleModel::onRampChanged( const QString &name )
582 {
583  mColorRampIconCache.remove( name );
584 
585  QModelIndex i = index( mSymbolNames.count() + mRampNames.indexOf( name ), Tags );
586  emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
587 }
588 
589 void QgsStyleModel::onRampRename( const QString &oldName, const QString &newName )
590 {
591  mColorRampIconCache.remove( oldName );
592  const QStringList oldRampNames = mRampNames;
593  const QStringList newRampNames = mStyle->colorRampNames();
594 
595  // find index of removed ramp
596  const int oldNameIndex = oldRampNames.indexOf( oldName );
597  if ( oldNameIndex < 0 )
598  return; // shouldn't happen
599 
600  // find index of newly added ramp
601  const int newNameIndex = newRampNames.indexOf( newName );
602  if ( newNameIndex < 0 )
603  return; // shouldn't happen
604 
605  if ( newNameIndex == oldNameIndex )
606  {
607  mRampNames = newRampNames;
608  return;
609  }
610 
611  beginMoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count(), oldNameIndex + mSymbolNames.count(),
612  QModelIndex(), ( newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex ) + mSymbolNames.count() );
613  mRampNames = newRampNames;
614  endMoveRows();
615 }
616 
617 void QgsStyleModel::onTextFormatAdded( const QString &name )
618 {
619  mTextFormatIconCache.remove( name );
620  const QStringList oldTextFormatNames = mTextFormatNames;
621  const QStringList newTextFormatNames = mStyle->textFormatNames();
622 
623  // find index of newly added symbol
624  const int newNameIndex = newTextFormatNames.indexOf( name );
625  if ( newNameIndex < 0 )
626  return; // shouldn't happen
627 
628  beginInsertRows( QModelIndex(), newNameIndex + mSymbolNames.count() + mRampNames.count(), newNameIndex + mSymbolNames.count() + mRampNames.count() );
629  mTextFormatNames = newTextFormatNames;
630  endInsertRows();
631 }
632 
633 void QgsStyleModel::onTextFormatRemoved( const QString &name )
634 {
635  mTextFormatIconCache.remove( name );
636  const QStringList oldTextFormatNames = mTextFormatNames;
637  const QStringList newTextFormatNames = mStyle->textFormatNames();
638 
639  // find index of removed symbol
640  const int oldNameIndex = oldTextFormatNames.indexOf( name );
641  if ( oldNameIndex < 0 )
642  return; // shouldn't happen
643 
644  beginRemoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count() + mRampNames.count(), oldNameIndex + mSymbolNames.count() + mRampNames.count() );
645  mTextFormatNames = newTextFormatNames;
646  endRemoveRows();
647 }
648 
649 void QgsStyleModel::onTextFormatChanged( const QString &name )
650 {
651  mTextFormatIconCache.remove( name );
652 
653  QModelIndex i = index( mSymbolNames.count() + mRampNames.count() + mTextFormatNames.indexOf( name ), Tags );
654  emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
655 }
656 
657 void QgsStyleModel::onTextFormatRename( const QString &oldName, const QString &newName )
658 {
659  mTextFormatIconCache.remove( oldName );
660  const QStringList oldTextFormatNames = mTextFormatNames;
661  const QStringList newTextFormatNames = mStyle->textFormatNames();
662 
663  // find index of removed format
664  const int oldNameIndex = oldTextFormatNames.indexOf( oldName );
665  if ( oldNameIndex < 0 )
666  return; // shouldn't happen
667 
668  // find index of newly added format
669  const int newNameIndex = newTextFormatNames.indexOf( newName );
670  if ( newNameIndex < 0 )
671  return; // shouldn't happen
672 
673  if ( newNameIndex == oldNameIndex )
674  {
675  mTextFormatNames = newTextFormatNames;
676  return;
677  }
678 
679  beginMoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count() + mRampNames.count(), oldNameIndex + mSymbolNames.count() + mRampNames.count(),
680  QModelIndex(), ( newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex ) + mSymbolNames.count() + mRampNames.count() );
681  mTextFormatNames = newTextFormatNames;
682  endMoveRows();
683 }
684 
685 void QgsStyleModel::onLabelSettingsAdded( const QString &name )
686 {
687  mLabelSettingsIconCache.remove( name );
688  const QStringList oldLabelSettingsNames = mLabelSettingsNames;
689  const QStringList newLabelSettingsNames = mStyle->labelSettingsNames();
690 
691  // find index of newly added symbol
692  const int newNameIndex = newLabelSettingsNames.indexOf( name );
693  if ( newNameIndex < 0 )
694  return; // shouldn't happen
695 
696  beginInsertRows( QModelIndex(), newNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count(), newNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() );
697  mLabelSettingsNames = newLabelSettingsNames;
698  endInsertRows();
699 }
700 
701 void QgsStyleModel::onLabelSettingsRemoved( const QString &name )
702 {
703  mLabelSettingsIconCache.remove( name );
704  const QStringList oldLabelSettingsNames = mLabelSettingsNames;
705  const QStringList newLabelSettingsNames = mStyle->labelSettingsNames();
706 
707  // find index of removed symbol
708  const int oldNameIndex = oldLabelSettingsNames.indexOf( name );
709  if ( oldNameIndex < 0 )
710  return; // shouldn't happen
711 
712  beginRemoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count(), oldNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() );
713  mLabelSettingsNames = newLabelSettingsNames;
714  endRemoveRows();
715 }
716 
717 void QgsStyleModel::onLabelSettingsChanged( const QString &name )
718 {
719  mLabelSettingsIconCache.remove( name );
720 
721  QModelIndex i = index( mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() + mLabelSettingsNames.indexOf( name ), Tags );
722  emit dataChanged( i, i, QVector< int >() << Qt::DecorationRole );
723 }
724 
725 void QgsStyleModel::onLabelSettingsRename( const QString &oldName, const QString &newName )
726 {
727  mLabelSettingsIconCache.remove( oldName );
728  const QStringList oldLabelSettingsNames = mLabelSettingsNames;
729  const QStringList newLabelSettingsNames = mStyle->labelSettingsNames();
730 
731  // find index of removed format
732  const int oldNameIndex = oldLabelSettingsNames.indexOf( oldName );
733  if ( oldNameIndex < 0 )
734  return; // shouldn't happen
735 
736  // find index of newly added format
737  const int newNameIndex = newLabelSettingsNames.indexOf( newName );
738  if ( newNameIndex < 0 )
739  return; // shouldn't happen
740 
741  if ( newNameIndex == oldNameIndex )
742  {
743  mLabelSettingsNames = newLabelSettingsNames;
744  return;
745  }
746 
747  beginMoveRows( QModelIndex(), oldNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count(), oldNameIndex + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count(),
748  QModelIndex(), ( newNameIndex > oldNameIndex ? newNameIndex + 1 : newNameIndex ) + mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() );
749  mLabelSettingsNames = newLabelSettingsNames;
750  endMoveRows();
751 }
752 
753 void QgsStyleModel::onTagsChanged( int entity, const QString &name, const QStringList & )
754 {
755  QModelIndex i;
756  switch ( static_cast< QgsStyle::StyleEntity >( entity ) )
757  {
759  i = index( mSymbolNames.indexOf( name ), Tags );
760  break;
761 
763  i = index( mSymbolNames.count() + mRampNames.indexOf( name ), Tags );
764  break;
765 
767  i = index( mSymbolNames.count() + mRampNames.count() + mTextFormatNames.indexOf( name ), Tags );
768  break;
769 
771  i = index( mSymbolNames.count() + mRampNames.count() + mTextFormatNames.count() + mLabelSettingsNames.indexOf( name ), Tags );
772  break;
773 
774  case QgsStyle::TagEntity:
776  return;
777  }
778  emit dataChanged( i, i );
779 }
780 
781 void QgsStyleModel::rebuildSymbolIcons()
782 {
783  mSymbolIconCache.clear();
784  mExpressionContext.reset();
785  emit dataChanged( index( 0, 0 ), index( mSymbolNames.count() - 1, 0 ), QVector<int>() << Qt::DecorationRole );
786 }
787 
788 QgsStyle::StyleEntity QgsStyleModel::entityTypeFromRow( int row ) const
789 {
790  if ( row >= mStyle->symbolCount() + mStyle->colorRampCount() + + mTextFormatNames.count() )
792  else if ( row >= mStyle->symbolCount() + mStyle->colorRampCount() )
794  else if ( row >= mStyle->symbolCount() )
796  return QgsStyle::SymbolEntity;
797 }
798 
799 //
800 // QgsStyleProxyModel
801 //
802 
804  : QSortFilterProxyModel( parent )
805  , mStyle( style )
806 {
807  mModel = new QgsStyleModel( mStyle, this );
808  initialize();
809 }
810 
811 void QgsStyleProxyModel::initialize()
812 {
813  setSortCaseSensitivity( Qt::CaseInsensitive );
814 // setSortLocaleAware( true );
815  setSourceModel( mModel );
816  setDynamicSortFilter( true );
817  sort( 0 );
818 
819  connect( mStyle, &QgsStyle::entityTagsChanged, this, [ = ]
820  {
821  // update tagged symbols if filtering by tag
822  if ( mTagId >= 0 )
823  setTagId( mTagId );
824  if ( mSmartGroupId >= 0 )
825  setSmartGroupId( mSmartGroupId );
826  } );
827 
828  connect( mStyle, &QgsStyle::favoritedChanged, this, [ = ]
829  {
830  // update favorited symbols if filtering by favorite
831  if ( mFavoritesOnly )
832  setFavoritesOnly( mFavoritesOnly );
833  } );
834 
835  connect( mStyle, &QgsStyle::rampRenamed, this, [ = ]
836  {
837  if ( mSmartGroupId >= 0 )
838  setSmartGroupId( mSmartGroupId );
839  } );
840  connect( mStyle, &QgsStyle::textFormatRenamed, this, [ = ]
841  {
842  if ( mSmartGroupId >= 0 )
843  setSmartGroupId( mSmartGroupId );
844  } );
845  connect( mStyle, &QgsStyle::labelSettingsRenamed, this, [ = ]
846  {
847  if ( mSmartGroupId >= 0 )
848  setSmartGroupId( mSmartGroupId );
849  } );
850  connect( mStyle, &QgsStyle::symbolRenamed, this, [ = ]
851  {
852  if ( mSmartGroupId >= 0 )
853  setSmartGroupId( mSmartGroupId );
854  } );
855 }
856 
858  : QSortFilterProxyModel( parent )
859  , mModel( model )
860  , mStyle( model->style() )
861 {
862  initialize();
863 }
864 
865 bool QgsStyleProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
866 {
867  if ( mFilterString.isEmpty() && !mEntityFilterEnabled && !mSymbolTypeFilterEnabled && mTagId < 0 && mSmartGroupId < 0 && !mFavoritesOnly )
868  return true;
869 
870  QModelIndex index = sourceModel()->index( source_row, 0, source_parent );
871  const QString name = sourceModel()->data( index ).toString();
872  const QStringList tags = sourceModel()->data( index, QgsStyleModel::TagRole ).toStringList();
873 
874  QgsStyle::StyleEntity styleEntityType = static_cast< QgsStyle::StyleEntity >( sourceModel()->data( index, QgsStyleModel::TypeRole ).toInt() );
875  if ( mEntityFilterEnabled && ( mEntityFilters.empty() || !mEntityFilters.contains( styleEntityType ) ) )
876  return false;
877 
878  QgsSymbol::SymbolType symbolType = static_cast< QgsSymbol::SymbolType >( sourceModel()->data( index, QgsStyleModel::SymbolTypeRole ).toInt() );
879  if ( mSymbolTypeFilterEnabled && symbolType != mSymbolType )
880  return false;
881 
882  if ( styleEntityType == QgsStyle::LabelSettingsEntity && mLayerType != QgsWkbTypes::UnknownGeometry &&
883  mLayerType != static_cast< QgsWkbTypes::GeometryType >( sourceModel()->data( index, QgsStyleModel::LayerTypeRole ).toInt() ) )
884  return false;
885 
886  if ( mTagId >= 0 && !mTaggedSymbolNames.contains( name ) )
887  return false;
888 
889  if ( mSmartGroupId >= 0 && !mSmartGroupSymbolNames.contains( name ) )
890  return false;
891 
892  if ( mFavoritesOnly && !sourceModel()->data( index, QgsStyleModel::IsFavoriteRole ).toBool() )
893  return false;
894 
895  if ( !mFilterString.isEmpty() )
896  {
897  // filter by word, in both filter string and style entity name/tags
898  // this allows matching of a filter string "hash line" to the symbol "hashed red lines"
899  const QStringList partsToMatch = mFilterString.trimmed().split( ' ' );
900 
901  QStringList partsToSearch = name.split( ' ' );
902  for ( const QString &tag : tags )
903  {
904  partsToSearch.append( tag.split( ' ' ) );
905  }
906 
907  for ( const QString &part : partsToMatch )
908  {
909  bool found = false;
910  for ( const QString &partToSearch : qgis::as_const( partsToSearch ) )
911  {
912  if ( partToSearch.contains( part, Qt::CaseInsensitive ) )
913  {
914  found = true;
915  break;
916  }
917  }
918  if ( !found )
919  return false; // couldn't find a match for this word, so hide entity
920  }
921  }
922 
923  return true;
924 }
925 
926 void QgsStyleProxyModel::setFilterString( const QString &filter )
927 {
928  mFilterString = filter;
929  invalidateFilter();
930 }
931 
932 
934 {
935  return mFavoritesOnly;
936 }
937 
939 {
940  mFavoritesOnly = favoritesOnly;
941  invalidateFilter();
942 }
943 
945 {
946  mModel->addDesiredIconSize( size );
947 }
948 
950 {
951  return mSymbolTypeFilterEnabled;
952 }
953 
955 {
956  mSymbolTypeFilterEnabled = symbolTypeFilterEnabled;
957  invalidateFilter();
958 }
959 
961 {
962  return mLayerType;
963 }
964 
966 {
967  mLayerType = type;
968  invalidateFilter();
969 }
970 
972 {
973  mTagId = id;
974 
975  if ( mTagId >= 0 )
976  {
977  mTaggedSymbolNames = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mTagId );
978  mTaggedSymbolNames.append( mStyle->symbolsWithTag( QgsStyle::ColorrampEntity, mTagId ) );
979  mTaggedSymbolNames.append( mStyle->symbolsWithTag( QgsStyle::TextFormatEntity, mTagId ) );
980  mTaggedSymbolNames.append( mStyle->symbolsWithTag( QgsStyle::LabelSettingsEntity, mTagId ) );
981  }
982  else
983  {
984  mTaggedSymbolNames.clear();
985  }
986 
987  invalidateFilter();
988 }
989 
991 {
992  return mTagId;
993 }
994 
996 {
997  mSmartGroupId = id;
998 
999  if ( mSmartGroupId >= 0 )
1000  {
1001  mSmartGroupSymbolNames = mStyle->symbolsOfSmartgroup( QgsStyle::SymbolEntity, mSmartGroupId );
1002  mSmartGroupSymbolNames.append( mStyle->symbolsOfSmartgroup( QgsStyle::ColorrampEntity, mSmartGroupId ) );
1003  mSmartGroupSymbolNames.append( mStyle->symbolsOfSmartgroup( QgsStyle::TextFormatEntity, mSmartGroupId ) );
1004  mSmartGroupSymbolNames.append( mStyle->symbolsOfSmartgroup( QgsStyle::LabelSettingsEntity, mSmartGroupId ) );
1005  }
1006  else
1007  {
1008  mSmartGroupSymbolNames.clear();
1009  }
1010 
1011  invalidateFilter();
1012 }
1013 
1015 {
1016  return mSmartGroupId;
1017 }
1018 
1020 {
1021  return mSymbolType;
1022 }
1023 
1025 {
1026  mSymbolType = symbolType;
1027  invalidateFilter();
1028 }
1029 
1031 {
1032  return mEntityFilterEnabled;
1033 }
1034 
1036 {
1037  mEntityFilterEnabled = entityFilterEnabled;
1038  invalidateFilter();
1039 }
1040 
1042 {
1043  return mEntityFilters.empty() ? QgsStyle::SymbolEntity : mEntityFilters.at( 0 );
1044 }
1045 
1047 {
1048  mEntityFilters = QList< QgsStyle::StyleEntity >() << entityFilter;
1049  invalidateFilter();
1050 }
1051 
1052 void QgsStyleProxyModel::setEntityFilters( const QList<QgsStyle::StyleEntity> &filters )
1053 {
1054  mEntityFilters = filters;
1055  invalidateFilter();
1056 }
A QAbstractItemModel subclass for showing symbol and color ramp entities contained within a QgsStyle ...
Definition: qgsstylemodel.h:45
static QgsSvgCache * svgCache()
Returns the application&#39;s SVG cache, used for caching SVG images and handling parameter replacement w...
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QgsStyle * style()
Returns the style managed by the model.
Definition: qgsstylemodel.h:80
void setFilterString(const QString &filter)
Sets a filter string, such that only symbol entities with names matching the specified string will be...
void symbolSaved(const QString &name, QgsSymbol *symbol)
Emitted every time a new symbol has been added to the database.
void textFormatAdded(const QString &name)
Emitted whenever a text format has been added to the style and the database has been updated as a res...
void setSmartGroupId(int id)
Sets a smart group id to filter style entities by.
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:154
QStringList symbolsWithTag(StyleEntity type, int tagid) const
Returns the symbol names with which have the given tag.
Definition: qgsstyle.cpp:933
void entityTagsChanged(QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags)
Emitted whenever an entity&#39;s tags are changed.
QgsStyleModel(QgsStyle *style, QObject *parent=nullptr)
Constructor for QgsStyleModel, for the specified style and parent object.
static QgsImageCache * imageCache()
Returns the application&#39;s image cache, used for caching resampled versions of raster images...
void setTagId(int id)
Sets a tag id to filter style entities by.
QStringList tagsOfSymbol(StyleEntity type, const QString &symbol)
Returns the tags associated with the symbol.
Definition: qgsstyle.cpp:1674
Layer type (for label settings entities)
Definition: qgsstylemodel.h:65
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void setSymbolType(QgsSymbol::SymbolType type)
Sets the symbol type filter.
void projectColorsChanged()
Emitted whenever the project&#39;s color scheme has been changed.
static QPixmap labelSettingsPreviewPixmap(const QgsPalLayerSettings &settings, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for label settings.
void textFormatRemoved(const QString &name)
Emitted whenever a text format has been removed from the style and the database has been updated as a...
void rampChanged(const QString &name)
Emitted whenever a color ramp&#39;s definition is changed.
QgsColorRamp * colorRamp(const QString &name) const
Returns a new copy of the specified color ramp.
Definition: qgsstyle.cpp:356
QgsWkbTypes::GeometryType layerType() const
Returns the layer type filter, or QgsWkbTypes::UnknownGeometry if no layer type filter is present...
void setFavoritesOnly(bool favoritesOnly)
Sets whether the model should show only favorited entities.
void rampAdded(const QString &name)
Emitted whenever a color ramp has been added to the style and the database has been updated as a resu...
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
int tagId() const
Returns the tag id used to filter style entities by.
String list of tags.
Definition: qgsstylemodel.h:62
QModelIndex parent(const QModelIndex &index) const override
StyleEntity
Enum for Entities involved in a style.
Definition: qgsstyle.h:177
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:1806
void setSymbolTypeFilterEnabled(bool enabled)
Sets whether filtering by symbol type is enabled.
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
int symbolCount()
Returns count of symbols in style.
Definition: qgsstyle.cpp:225
void setEntityFilterEnabled(bool enabled)
Sets whether filtering by entity type is enabled.
bool renameSymbol(const QString &oldName, const QString &newName)
Renames a symbol from oldName to newName.
Definition: qgsstyle.cpp:643
QStringList textFormatNames() const
Returns a list of names of text formats in the style.
Definition: qgsstyle.cpp:2038
const double ICON_PADDING_FACTOR
QgsPalLayerSettings labelSettings(const QString &name) const
Returns the label settings with the specified name.
Definition: qgsstyle.cpp:2048
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
void rampRemoved(const QString &name)
Emitted whenever a color ramp has been removed from the style and the database has been updated as a ...
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
void labelSettingsRemoved(const QString &name)
Emitted whenever label settings have been removed from the style and the database has been updated as...
const QgsSymbol * symbolRef(const QString &name) const
Returns a const pointer to a symbol (doesn&#39;t create new instance)
Definition: qgsstyle.cpp:220
QStringList labelSettingsNames() const
Returns a list of names of label settings in the style.
Definition: qgsstyle.cpp:2066
static QPixmap textFormatPreviewPixmap(const QgsTextFormat &format, QSize size, const QString &previewText=QString(), int padding=0)
Returns a pixmap preview for a text format.
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
bool favoritesOnly() const
Returns true if the model is showing only favorited entities.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer&#39;s project and layer.
void symbolRenamed(const QString &oldName, const QString &newName)
Emitted whenever a symbol has been renamed from oldName to newName.
void labelSettingsChanged(const QString &name)
Emitted whenever a label setting&#39;s definition is changed.
QgsTextFormat textFormat(const QString &name) const
Returns the text format with the specified name.
Definition: qgsstyle.cpp:2028
Tags column.
Definition: qgsstylemodel.h:55
void textFormatRenamed(const QString &oldName, const QString &newName)
Emitted whenever a text format has been renamed from oldName to newName.
void setLayerType(QgsWkbTypes::GeometryType type)
Sets the layer type filter.
void favoritedChanged(QgsStyle::StyleEntity entity, const QString &name, bool isFavorite)
Emitted whenever an entity is either favorited or un-favorited.
static QPixmap colorRampPreviewPixmap(QgsColorRamp *ramp, QSize size, int padding=0)
Returns a pixmap preview for a color ramp.
void symbolRemoved(const QString &name)
Emitted whenever a symbol has been removed from the style and the database has been updated as a resu...
QStringList symbolsOfSmartgroup(StyleEntity type, int id)
Returns the symbols for the smartgroup.
Definition: qgsstyle.cpp:2212
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
void remoteImageFetched(const QString &url)
Emitted when the cache has finished retrieving an image file from a remote url.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:139
void addDesiredIconSize(QSize size)
Adds an additional icon size to generate for Qt::DecorationRole data.
void rampRenamed(const QString &oldName, const QString &newName)
Emitted whenever a color ramp has been renamed from oldName to newName.
QgsStyleProxyModel(QgsStyle *style, QObject *parent=nullptr)
Constructor for QgsStyleProxyModel, for the specified style and parent object.
void textFormatChanged(const QString &name)
Emitted whenever a text format&#39;s definition is changed.
Name column.
Definition: qgsstylemodel.h:54
int smartGroupId() const
Returns the smart group id used to filter style entities by.
void symbolChanged(const QString &name)
Emitted whenever a symbol&#39;s definition is changed.
SymbolType type() const
Returns the symbol&#39;s type.
Definition: qgssymbol.h:120
QgsSymbol::SymbolType symbolType() const
Returns the symbol type filter.
bool entityFilterEnabled() const
Returns true if filtering by entity type is enabled.
static QPixmap symbolPreviewPixmap(const QgsSymbol *symbol, QSize size, int padding=0, QgsRenderContext *customContext=nullptr, bool selected=false, const QgsExpressionContext *expressionContext=nullptr)
Returns a pixmap preview for a color ramp.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:442
QStringList colorRampNames() const
Returns a list of names of color ramps.
Definition: qgsstyle.cpp:372
bool renameLabelSettings(const QString &oldName, const QString &newName)
Changes a label setting&#39;s name.
Definition: qgsstyle.cpp:855
bool renameColorRamp(const QString &oldName, const QString &newName)
Changes ramp&#39;s name.
Definition: qgsstyle.cpp:680
QgsWkbTypes::GeometryType labelSettingsLayerType(const QString &name) const
Returns the layer geometry type corresponding to the label settings with the specified name...
Definition: qgsstyle.cpp:2053
int colorRampCount()
Returns count of color ramps.
Definition: qgsstyle.cpp:367
void labelSettingsRenamed(const QString &oldName, const QString &newName)
Emitted whenever label settings have been renamed from oldName to newName.
void remoteSvgFetched(const QString &url)
Emitted when the cache has finished retrieving an SVG file from a remote url.
void labelSettingsAdded(const QString &name)
Emitted whenever label settings have been added to the style and the database has been updated as a r...
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition: qgsstyle.cpp:214
QVariant data(const QModelIndex &index, int role) const override
Whether entity is flagged as a favorite.
Definition: qgsstylemodel.h:64
void setEntityFilter(QgsStyle::StyleEntity filter)
Sets the style entity type filter.
Container for all settings relating to text rendering.
Style entity type, see QgsStyle::StyleEntity.
Definition: qgsstylemodel.h:61
QgsStyle::StyleEntity entityFilter() const
Returns the style entity type filter.
bool symbolTypeFilterEnabled() const
Returns true if filtering by symbol type is enabled.
bool renameTextFormat(const QString &oldName, const QString &newName)
Changes a text format&#39;s name.
Definition: qgsstyle.cpp:768
void setEntityFilters(const QList< QgsStyle::StyleEntity > &filters)
Sets the style entity type filters.
Qt::ItemFlags flags(const QModelIndex &index) const override
Symbol type (for symbol entities)
Definition: qgsstylemodel.h:63
QStringList symbolNames() const
Returns a list of names of symbols.
Definition: qgsstyle.cpp:230