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