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