QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgssvgselectorwidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgssvgselectorwidget.cpp - group and preview selector for SVG files
3 built off of work in qgssymbollayerwidget
4
5 ---------------------
6 begin : April 2, 2013
7 copyright : (C) 2013 by Larry Shaffer
8 email : larrys at dakcarto dot com
9 ***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
18
19#include "qgsapplication.h"
20#include "qgslogger.h"
21#include "qgspathresolver.h"
22#include "qgsproject.h"
23#include "qgssvgcache.h"
24#include "qgssymbollayerutils.h"
25#include "qgssettings.h"
26#include "qgsgui.h"
29#include "qgsvectorlayer.h"
30
31#include <QAbstractListModel>
32#include <QSortFilterProxyModel>
33#include <QCheckBox>
34#include <QDir>
35#include <QFileDialog>
36#include <QModelIndex>
37#include <QPixmapCache>
38#include <QStyle>
39#include <QTime>
40#include <QMenu>
41
42// QgsSvgSelectorLoader
43
45QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
46 : QThread( parent )
47{
48}
49
50QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
51{
52 stop();
53}
54
55void QgsSvgSelectorLoader::run()
56{
57 mCanceled = false;
58 mQueuedSvgs.clear();
59 mTraversedPaths.clear();
60
61 // start with a small initial timeout (ms)
62 mTimerThreshold = 10;
63 mTimer.start();
64
65 loadPath( mPath );
66
67 if ( !mQueuedSvgs.isEmpty() )
68 {
69 // make sure we notify model of any remaining queued svgs (ie svgs added since last foundSvgs() signal was emitted)
70 emit foundSvgs( mQueuedSvgs );
71 }
72 mQueuedSvgs.clear();
73}
74
75void QgsSvgSelectorLoader::stop()
76{
77 mCanceled = true;
78 while ( isRunning() ) {}
79}
80
81void QgsSvgSelectorLoader::loadPath( const QString &path )
82{
83 if ( mCanceled )
84 return;
85
86 QgsDebugMsgLevel( QStringLiteral( "loading path: %1" ).arg( path ), 2 );
87
88 if ( path.isEmpty() )
89 {
90 QStringList svgPaths = QgsApplication::svgPaths();
91 const auto constSvgPaths = svgPaths;
92 for ( const QString &svgPath : constSvgPaths )
93 {
94 if ( mCanceled )
95 return;
96
97 if ( !svgPath.isEmpty() )
98 {
99 loadPath( svgPath );
100 }
101 }
102 }
103 else
104 {
105 QDir dir( path );
106
107 //guard against circular symbolic links
108 QString canonicalPath = dir.canonicalPath();
109 if ( mTraversedPaths.contains( canonicalPath ) )
110 return;
111
112 mTraversedPaths.insert( canonicalPath );
113
114 loadImages( path );
115
116 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
117 for ( const QString &item : constEntryList )
118 {
119 if ( mCanceled )
120 return;
121
122 QString newPath = dir.path() + '/' + item;
123 loadPath( newPath );
124 QgsDebugMsgLevel( QStringLiteral( "added path: %1" ).arg( newPath ), 2 );
125 }
126 }
127}
128
129void QgsSvgSelectorLoader::loadImages( const QString &path )
130{
131 QDir dir( path );
132 const auto constEntryList = dir.entryList( QStringList( "*.svg" ), QDir::Files );
133 for ( const QString &item : constEntryList )
134 {
135 if ( mCanceled )
136 return;
137
138 // TODO test if it is correct SVG
139 QString svgPath = dir.path() + '/' + item;
140 // QgsDebugMsgLevel( QStringLiteral( "adding svg: %1" ).arg( svgPath ), 2 );
141
142 // add it to the list of queued SVGs
143 mQueuedSvgs << svgPath;
144
145 // we need to avoid spamming the model with notifications about new svgs, so foundSvgs
146 // is only emitted for blocks of SVGs (otherwise the view goes all flickery)
147 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
148 {
149 emit foundSvgs( mQueuedSvgs );
150 mQueuedSvgs.clear();
151
152 // increase the timer threshold - this ensures that the first lots of svgs loaded are added
153 // to the view quickly, but as the list grows new svgs are added at a slower rate.
154 // ie, good for initial responsiveness but avoid being spammy as the list grows.
155 if ( mTimerThreshold < 1000 )
156 mTimerThreshold *= 2;
157 mTimer.restart();
158 }
159 }
160}
161
162
163//
164// QgsSvgGroupLoader
165//
166
167QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
168 : QThread( parent )
169{
170
171}
172
173QgsSvgGroupLoader::~QgsSvgGroupLoader()
174{
175 stop();
176}
177
178void QgsSvgGroupLoader::run()
179{
180 mCanceled = false;
181 mTraversedPaths.clear();
182
183 while ( !mCanceled && !mParentPaths.isEmpty() )
184 {
185 QString parentPath = mParentPaths.takeFirst();
186 loadGroup( parentPath );
187 }
188}
189
190void QgsSvgGroupLoader::stop()
191{
192 mCanceled = true;
193 while ( isRunning() ) {}
194}
195
196void QgsSvgGroupLoader::loadGroup( const QString &parentPath )
197{
198 QDir parentDir( parentPath );
199
200 //guard against circular symbolic links
201 QString canonicalPath = parentDir.canonicalPath();
202 if ( mTraversedPaths.contains( canonicalPath ) )
203 return;
204
205 mTraversedPaths.insert( canonicalPath );
206
207 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
208 for ( const QString &item : constEntryList )
209 {
210 if ( mCanceled )
211 return;
212
213 emit foundPath( parentPath, item );
214 mParentPaths.append( parentDir.path() + '/' + item );
215 }
216}
217
219
220
221
222
223QgsSvgSelectorFilterModel::QgsSvgSelectorFilterModel( QObject *parent, const QString &path, int iconSize )
224 : QSortFilterProxyModel( parent )
225{
226 mModel = new QgsSvgSelectorListModel( parent, path, iconSize );
227 setFilterCaseSensitivity( Qt::CaseInsensitive );
228 setSourceModel( mModel );
229 setFilterRole( Qt::UserRole );
230}
231
232//,
233// QgsSvgSelectorListModel
234//
235
237 : QgsSvgSelectorListModel( parent, QString(), iconSize )
238{}
239
240QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject *parent, const QString &path, int iconSize )
241 : QAbstractListModel( parent )
242 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
243 , mIconSize( iconSize )
244{
245 mSvgLoader->setPath( path );
246 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, this, &QgsSvgSelectorListModel::addSvgs );
247 mSvgLoader->start();
248}
249
250int QgsSvgSelectorListModel::rowCount( const QModelIndex &parent ) const
251{
252 Q_UNUSED( parent )
253 return mSvgFiles.count();
254}
255
256QPixmap QgsSvgSelectorListModel::createPreview( const QString &entry ) const
257{
258 // render SVG file
259 QColor fill, stroke;
260 double strokeWidth, fillOpacity, strokeOpacity;
261 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
262 bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultStrokeColor = false,
263 hasDefaultStrokeWidth = false, hasDefaultStrokeOpacity = false;
264 QgsApplication::svgCache()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
265 fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
266 strokeParam, hasDefaultStrokeColor, stroke,
267 strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
268 strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
269
270 //if defaults not set in symbol, use these values
271 if ( !hasDefaultFillColor )
272 fill = QColor( 200, 200, 200 );
273 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
274 if ( !hasDefaultStrokeColor )
275 stroke = Qt::black;
276 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
277 if ( !hasDefaultStrokeWidth )
278 strokeWidth = 0.2;
279
280 bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
281 QImage img = QgsApplication::svgCache()->svgAsImage( entry, mIconSize, fill, stroke, strokeWidth, 3.5 /*appr. 88 dpi*/, fitsInCache );
282 return QPixmap::fromImage( img );
283}
284
285QVariant QgsSvgSelectorListModel::data( const QModelIndex &index, int role ) const
286{
287 QString entry = mSvgFiles.at( index.row() );
288
289 if ( role == Qt::DecorationRole ) // icon
290 {
291 QPixmap *pixmap = nullptr;
292 if ( !QPixmapCache::find( entry, pixmap ) || !pixmap )
293 {
294 QPixmap newPixmap = createPreview( entry );
295 QPixmapCache::insert( entry, newPixmap );
296 return newPixmap;
297 }
298 else
299 {
300 return *pixmap;
301 }
302 }
303 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
304 {
305 return entry;
306 }
307
308 return QVariant();
309}
310
311void QgsSvgSelectorListModel::addSvgs( const QStringList &svgs )
312{
313 beginInsertRows( QModelIndex(), mSvgFiles.count(), mSvgFiles.count() + svgs.size() - 1 );
314 mSvgFiles.append( svgs );
315 endInsertRows();
316}
317
318
319
320
321
322//--- QgsSvgSelectorGroupsModel
323
325 : QStandardItemModel( parent )
326 , mLoader( new QgsSvgGroupLoader( this ) )
327{
328 QStringList svgPaths = QgsApplication::svgPaths();
329 QStandardItem *parentItem = invisibleRootItem();
330 QStringList parentPaths;
331 parentPaths.reserve( svgPaths.size() );
332
333 for ( int i = 0; i < svgPaths.size(); i++ )
334 {
335 QDir dir( svgPaths.at( i ) );
336 QStandardItem *baseGroup = nullptr;
337
338 if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
339 {
340 baseGroup = new QStandardItem( tr( "App Symbols" ) );
341 }
342 else if ( dir.path().contains( QgsApplication::qgisSettingsDirPath() ) )
343 {
344 baseGroup = new QStandardItem( tr( "User Symbols" ) );
345 }
346 else
347 {
348 baseGroup = new QStandardItem( dir.dirName() );
349 }
350 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
351 baseGroup->setEditable( false );
352 baseGroup->setCheckable( false );
353 baseGroup->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconFolder.svg" ) ) );
354 baseGroup->setToolTip( dir.path() );
355 parentItem->appendRow( baseGroup );
356 parentPaths << svgPaths.at( i );
357 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
358 QgsDebugMsgLevel( QStringLiteral( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
359 }
360 mLoader->setParentPaths( parentPaths );
361 connect( mLoader, &QgsSvgGroupLoader::foundPath, this, &QgsSvgSelectorGroupsModel::addPath );
362 mLoader->start();
363}
364
366{
367 mLoader->stop();
368}
369
370void QgsSvgSelectorGroupsModel::addPath( const QString &parentPath, const QString &item )
371{
372 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
373 if ( !parentGroup )
374 return;
375
376 QString fullPath = parentPath + '/' + item;
377 QStandardItem *group = new QStandardItem( item );
378 group->setData( QVariant( fullPath ) );
379 group->setEditable( false );
380 group->setCheckable( false );
381 group->setToolTip( fullPath );
382 group->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mIconFolder.svg" ) ) );
383 parentGroup->appendRow( group );
384 mPathItemHash.insert( fullPath, group );
385}
386
387
388//-- QgsSvgSelectorWidget
389
391 : QWidget( parent )
392{
393 // TODO: in-code gui setup with option to vertically or horizontally stack SVG groups/images widgets
394 setupUi( this );
395
396 mIconSize = std::max( 30, static_cast< int >( std::round( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 3 ) ) );
397 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
398 mImagesListView->setUniformItemSizes( false );
399
400 mGroupsTreeView->setHeaderHidden( true );
401 populateList();
402
403 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, this, [ = ]( const QString & filterText )
404 {
405 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
406 {
407 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
408 mImagesListView->selectionModel()->clearSelection();
409 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
410 }
411 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
412 } );
413
414
415 mParametersModel = new QgsSvgParametersModel( this );
416 mParametersTreeView->setModel( mParametersModel );
417 mParametersGroupBox->setVisible( mAllowParameters );
418
419 mParametersTreeView->setItemDelegateForColumn( static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ), new QgsSvgParameterValueDelegate( this ) );
420 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
421 mParametersTreeView->header()->setStretchLastSection( true );
422 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
423 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
424 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
425
426 connect( mParametersModel, &QgsSvgParametersModel::parametersChanged, this, &QgsSvgSelectorWidget::svgParametersChanged );
427 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
428 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::populateIcons );
429 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
430 connect( mRemoveParameterButton, &QToolButton::clicked, this, [ = ]()
431 {
432 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
433 if ( selectedRows.count() > 0 )
434 mParametersModel->removeParameters( selectedRows );
435 } );
436
437 connect( mSourceLineEdit, &QgsPictureSourceLineEditBase::sourceChanged, this, &QgsSvgSelectorWidget::updateCurrentSvgPath );
438}
439
441{
442 mParametersModel->setExpressionContextGenerator( generator );
443 mParametersModel->setLayer( layer );
444}
445
446void QgsSvgSelectorWidget::setSvgPath( const QString &svgPath )
447{
448 mCurrentSvgPath = svgPath;
449
450 whileBlocking( mSourceLineEdit )->setSource( svgPath );
451
452 mImagesListView->selectionModel()->blockSignals( true );
453 QAbstractItemModel *m = mImagesListView->model();
454 QItemSelectionModel *selModel = mImagesListView->selectionModel();
455 for ( int i = 0; i < m->rowCount(); i++ )
456 {
457 QModelIndex idx( m->index( i, 0 ) );
458 if ( m->data( idx ).toString() == svgPath )
459 {
460 selModel->select( idx, QItemSelectionModel::SelectCurrent );
461 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
462 mImagesListView->scrollTo( idx );
463 break;
464 }
465 }
466 mImagesListView->selectionModel()->blockSignals( false );
467}
468
469void QgsSvgSelectorWidget::setSvgParameters( const QMap<QString, QgsProperty> &parameters )
470{
471 mParametersModel->setParameters( parameters );
472}
473
475{
476 return mCurrentSvgPath;
477}
478
480{
481 if ( mAllowParameters == allow )
482 return;
483
484 mAllowParameters = allow;
485 mParametersGroupBox->setVisible( allow );
486}
487
489{
490 if ( mBrowserVisible == visible )
491 return;
492
493 mBrowserVisible = visible;
494 mSvgBrowserGroupBox->setVisible( visible );
495}
496
498{
499 return mSourceLineEdit->propertyOverrideToolButton();
500}
501
502void QgsSvgSelectorWidget::updateCurrentSvgPath( const QString &svgPath )
503{
504 mCurrentSvgPath = svgPath;
505 emit svgSelected( currentSvgPath() );
506}
507
508void QgsSvgSelectorWidget::svgSelectionChanged( const QModelIndex &idx )
509{
510 QString filePath = idx.data( Qt::UserRole ).toString();
511 whileBlocking( mSourceLineEdit )->setSource( filePath );
512 updateCurrentSvgPath( filePath );
513}
514
515void QgsSvgSelectorWidget::populateIcons( const QModelIndex &idx )
516{
517 QString path = idx.data( Qt::UserRole + 1 ).toString();
518
519 QAbstractItemModel *oldModel = mImagesListView->model();
520 QgsSvgSelectorFilterModel *m = new QgsSvgSelectorFilterModel( mImagesListView, path, mIconSize );
521 mImagesListView->setModel( m );
522 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
523 delete oldModel; //explicitly delete old model to force any background threads to stop
524
525 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
526 this, &QgsSvgSelectorWidget::svgSelectionChanged );
527}
528
529void QgsSvgSelectorWidget::svgSourceChanged( const QString &text )
530{
531 QString resolvedPath = QgsSymbolLayerUtils::svgSymbolNameToPath( text, QgsProject::instance()->pathResolver() );
532 bool validSVG = !resolvedPath.isNull();
533
534 updateCurrentSvgPath( validSVG ? resolvedPath : text );
535}
536
538{
539 QgsSvgSelectorGroupsModel *g = new QgsSvgSelectorGroupsModel( mGroupsTreeView );
540 mGroupsTreeView->setModel( g );
541 // Set the tree expanded at the first level
542 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
543 for ( int i = 0; i < rows; i++ )
544 {
545 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
546 }
547
548 // Initially load the icons in the List view without any grouping
549 QAbstractItemModel *oldModel = mImagesListView->model();
550 QgsSvgSelectorFilterModel *m = new QgsSvgSelectorFilterModel( mImagesListView );
551 mImagesListView->setModel( m );
552 delete oldModel; //explicitly delete old model to force any background threads to stop
553}
554
555//-- QgsSvgSelectorDialog
556
557QgsSvgSelectorDialog::QgsSvgSelectorDialog( QWidget *parent, Qt::WindowFlags fl,
558 QDialogButtonBox::StandardButtons buttons,
559 Qt::Orientation orientation )
560 : QDialog( parent, fl )
561{
562 // TODO: pass 'orientation' to QgsSvgSelectorWidget for customizing its layout, once implemented
563 Q_UNUSED( orientation )
564
565 // create buttonbox
566 mButtonBox = new QDialogButtonBox( buttons, orientation, this );
567 connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
568 connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
569
570 setMinimumSize( 480, 320 );
571
572 // dialog's layout
573 mLayout = new QVBoxLayout();
575 mLayout->addWidget( mSvgSelector );
576
577 mLayout->addWidget( mButtonBox );
578 setLayout( mLayout );
579}
580
581
583
584
585QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
586 : QAbstractTableModel( parent )
587{
588 connect( this, &QAbstractTableModel::rowsInserted, this, [ = ]() {emit parametersChanged( parameters() );} );
589 connect( this, &QAbstractTableModel::rowsRemoved, this, [ = ]() {emit parametersChanged( parameters() );} );
590 connect( this, &QAbstractTableModel::dataChanged, this, [ = ]() {emit parametersChanged( parameters() );} );
591}
592
593void QgsSvgParametersModel::setParameters( const QMap<QString, QgsProperty> &parameters )
594{
595 beginResetModel();
596 mParameters.clear();
597 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
598 for ( ; paramIt != parameters.constEnd(); ++paramIt )
599 {
600 mParameters << Parameter( paramIt.key(), paramIt.value() );
601 }
602 endResetModel();
603}
604
605QMap<QString, QgsProperty> QgsSvgParametersModel::parameters() const
606{
607 QMap<QString, QgsProperty> params;
608 for ( const Parameter &param : std::as_const( mParameters ) )
609 {
610 if ( !param.name.isEmpty() )
611 params.insert( param.name, param.property );
612 }
613 return params;
614}
615
616void QgsSvgParametersModel::removeParameters( const QModelIndexList &indexList )
617{
618 if ( indexList.isEmpty() )
619 return;
620
621 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( const QModelIndex & i1, const QModelIndex & i2 ) {return i1.row() < i2.row();} );
622
623 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
624 for ( const QModelIndex &index : indexList )
625 mParameters.removeAt( index.row() );
626 endRemoveRows();
627}
628
629void QgsSvgParametersModel::setLayer( QgsVectorLayer *layer )
630{
631 mLayer = layer;
632}
633
634void QgsSvgParametersModel::setExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
635{
636 mExpressionContextGenerator = generator;
637}
638
639int QgsSvgParametersModel::rowCount( const QModelIndex &parent ) const
640{
641 Q_UNUSED( parent )
642 return mParameters.count();
643}
644
645int QgsSvgParametersModel::columnCount( const QModelIndex &parent ) const
646{
647 Q_UNUSED( parent )
648 return 2;
649}
650
651QVariant QgsSvgParametersModel::data( const QModelIndex &index, int role ) const
652{
653 QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( index.column() );
654 if ( role == Qt::DisplayRole )
655 {
656 switch ( col )
657 {
658 case QgsSvgParametersModel::Column::NameColumn:
659 return mParameters.at( index.row() ).name;
660 case QgsSvgParametersModel::Column::ExpressionColumn:
661 return mParameters.at( index.row() ).property.expressionString();
662 }
663 }
664
665 return QVariant();
666}
667
668bool QgsSvgParametersModel::setData( const QModelIndex &index, const QVariant &value, int role )
669{
670 if ( !index.isValid() || role != Qt::EditRole )
671 return false;
672
673 QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( index.column() );
674 switch ( col )
675 {
676 case QgsSvgParametersModel::Column::NameColumn:
677 {
678 QString oldName = mParameters.at( index.row() ).name;
679 QString newName = value.toString();
680 for ( const Parameter &param : std::as_const( mParameters ) )
681 {
682 if ( param.name == newName && param.name != oldName )
683 {
684 // names must be unique!
685 return false;
686 }
687 }
688 mParameters[index.row()].name = newName;
689 emit dataChanged( index, index );
690 return true;
691 }
692
693 case QgsSvgParametersModel::Column::ExpressionColumn:
694 mParameters[index.row()].property = QgsProperty::fromExpression( value.toString() );
695 emit dataChanged( index, index );
696 return true;
697 }
698
699 return false;
700}
701
702QVariant QgsSvgParametersModel::headerData( int section, Qt::Orientation orientation, int role ) const
703{
704 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
705 {
706 QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( section );
707 switch ( col )
708 {
709 case QgsSvgParametersModel::Column::NameColumn:
710 return tr( "Name" );
711 case QgsSvgParametersModel::Column::ExpressionColumn:
712 return tr( "Expression" );
713 }
714 }
715
716 return QVariant();
717}
718
719void QgsSvgParametersModel::addParameter()
720{
721 int c = rowCount( QModelIndex() );
722 beginInsertRows( QModelIndex(), c, c );
723 int i = 1;
724 QStringList currentNames;
725 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), []( const Parameter & parameter ) {return parameter.name;} );
726 while ( currentNames.contains( QStringLiteral( "param%1" ).arg( i ) ) )
727 i++;
728 mParameters.append( Parameter( QStringLiteral( "param%1" ).arg( i ), QgsProperty() ) );
729 endResetModel();
730}
731
732
733Qt::ItemFlags QgsSvgParametersModel::flags( const QModelIndex &index ) const
734{
735 Q_UNUSED( index )
736 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
737}
738
739
740QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
741{
742 Q_UNUSED( option )
744 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
745 w->registerExpressionContextGenerator( model->expressionContextGenerator() );
746 w->setLayer( model->layer() );
747 return w;
748}
749
750void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
751{
752 QgsFieldExpressionWidget *w = qobject_cast<QgsFieldExpressionWidget *>( editor );
753 if ( !w )
754 return;
755
756 w->setExpression( index.model()->data( index ).toString() );
757}
758
759void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
760{
761 QgsFieldExpressionWidget *w = qobject_cast<QgsFieldExpressionWidget *>( editor );
762 if ( !w )
763 return;
764 model->setData( index, w->currentField() );
765}
766
767void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index ) const
768{
769 Q_UNUSED( index )
770 editor->setGeometry( option.rect );
771}
772
774
775
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:4927
void sourceChanged(const QString &source)
Emitted whenever the file source is changed in the widget.
static QString pkgDataPath()
Returns the common root path of all application data directories.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QStringList svgPaths()
Returns the paths to svg directories.
static QgsSvgCache * svgCache()
Returns the application's SVG cache, used for caching SVG images and handling parameter replacement w...
static QString qgisSettingsDirPath()
Returns the path to the settings directory in user's home dir.
Abstract interface for generating an expression context.
The QgsFieldExpressionWidget class creates a widget to choose fields and edit expressions It contains...
void setExpression(const QString &expression)
Sets the current expression text and if applicable also the field.
void setLayer(QgsMapLayer *layer)
Sets the layer used to display the fields and expression.
void registerExpressionContextGenerator(const QgsExpressionContextGenerator *generator)
Register an expression context generator class that will be used to retrieve an expression context fo...
QString currentField(bool *isExpression=nullptr, bool *isValid=nullptr) const
currentField returns the currently selected field or expression if allowed
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:481
A button for controlling property overrides which may apply to a widget.
A store for object properties.
Definition: qgsproperty.h:228
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
void containsParams(const QString &path, bool &hasFillParam, QColor &defaultFillColor, bool &hasStrokeParam, QColor &defaultStrokeColor, bool &hasStrokeWidthParam, double &defaultStrokeWidth, bool blocking=false) const
Tests if an SVG file contains parameters for fill, stroke color, stroke width.
QImage svgAsImage(const QString &path, double size, const QColor &fill, const QColor &stroke, double strokeWidth, double widthScaleFactor, bool &fitsInCache, double fixedAspectRatio=0, bool blocking=false, const QMap< QString, QString > &parameters=QMap< QString, QString >())
Returns an SVG drawing as a QImage.
QgsSvgSelectorDialog(QWidget *parent=nullptr, Qt::WindowFlags fl=QgsGuiUtils::ModalDialogFlags, QDialogButtonBox::StandardButtons buttons=QDialogButtonBox::Close|QDialogButtonBox::Ok, Qt::Orientation orientation=Qt::Horizontal)
Constructor for QgsSvgSelectorDialog.
QDialogButtonBox * mButtonBox
QgsSvgSelectorWidget * mSvgSelector
A model for displaying SVG files with a preview icon which can be filtered by file name.
QgsSvgSelectorFilterModel(QObject *parent, const QString &path=QString(), int iconSize=30)
Constructor for creating a model for SVG files in a specific path.
A model for displaying SVG search paths.
QgsSvgSelectorGroupsModel(QObject *parent)
A model for displaying SVG files with a preview icon.
int rowCount(const QModelIndex &parent=QModelIndex()) const override
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QgsSvgSelectorListModel(QObject *parent, int iconSize=30)
Constructor for QgsSvgSelectorListModel.
void setAllowParameters(bool allow)
Defines if the group box to fill parameters is visible.
void initParametersModel(const QgsExpressionContextGenerator *generator, QgsVectorLayer *layer=nullptr)
Initialize the parameters model so the context and the layer are referenced.
void svgParametersChanged(const QMap< QString, QgsProperty > &parameters)
Emitted when the parameters have changed.
void setBrowserVisible(bool visible)
Defines if the SVG browser should be visible.
void setSvgPath(const QString &svgPath)
Accepts absolute paths.
void setSvgParameters(const QMap< QString, QgsProperty > &parameters)
Sets the dynamic parameters.
void svgSelected(const QString &path)
QgsSvgSelectorWidget(QWidget *parent=nullptr)
Constructor for QgsSvgSelectorWidget.
QgsPropertyOverrideButton * propertyOverrideToolButton() const
Returns the property override tool button of the file line edit.
static QString svgSymbolNameToPath(const QString &name, const QgsPathResolver &pathResolver)
Determines an SVG symbol's path from its name.
Represents a vector layer which manages a vector based data sets.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:5111
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39