QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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  ***************************************************************************/
17 #include "qgssvgselectorwidget.h"
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"
28 #include "qgssymbollayerwidget.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 
45 QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
46  : QThread( parent )
47 {
48 }
49 
50 QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
51 {
52  stop();
53 }
54 
55 void 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 
75 void QgsSvgSelectorLoader::stop()
76 {
77  mCanceled = true;
78  while ( isRunning() ) {}
79 }
80 
81 void QgsSvgSelectorLoader::loadPath( const QString &path )
82 {
83  if ( mCanceled )
84  return;
85 
86  // QgsDebugMsg( QStringLiteral( "loading path: %1" ).arg( path ) );
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  // QgsDebugMsg( QStringLiteral( "added path: %1" ).arg( newPath ) );
125  }
126  }
127 }
128 
129 void 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  // QgsDebugMsg( QStringLiteral( "adding svg: %1" ).arg( svgPath ) );
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 
167 QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
168  : QThread( parent )
169 {
170 
171 }
172 
173 QgsSvgGroupLoader::~QgsSvgGroupLoader()
174 {
175  stop();
176 }
177 
178 void 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 
190 void QgsSvgGroupLoader::stop()
191 {
192  mCanceled = true;
193  while ( isRunning() ) {}
194 }
195 
196 void 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 
223 QgsSvgSelectorFilterModel::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 
240 QgsSvgSelectorListModel::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 
250 int QgsSvgSelectorListModel::rowCount( const QModelIndex &parent ) const
251 {
252  Q_UNUSED( parent )
253  return mSvgFiles.count();
254 }
255 
256 QPixmap 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 
285 QVariant 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 
311 void 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  QgsDebugMsg( QStringLiteral( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
359  }
360  mLoader->setParentPaths( parentPaths );
361  connect( mLoader, &QgsSvgGroupLoader::foundPath, this, &QgsSvgSelectorGroupsModel::addPath );
362  mLoader->start();
363 }
364 
366 {
367  mLoader->stop();
368 }
369 
370 void 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 
438 }
439 
441 {
442  mParametersModel->setExpressionContextGenerator( generator );
443  mParametersModel->setLayer( layer );
444 }
445 
446 void 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 
469 void 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 
502 void QgsSvgSelectorWidget::updateCurrentSvgPath( const QString &svgPath )
503 {
504  mCurrentSvgPath = svgPath;
505  emit svgSelected( currentSvgPath() );
506 }
507 
508 void QgsSvgSelectorWidget::svgSelectionChanged( const QModelIndex &idx )
509 {
510  QString filePath = idx.data( Qt::UserRole ).toString();
511  whileBlocking( mSourceLineEdit )->setSource( filePath );
512  updateCurrentSvgPath( filePath );
513 }
514 
515 void 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 
529 void 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 
557 QgsSvgSelectorDialog::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();
574  mSvgSelector = new QgsSvgSelectorWidget( this );
575  mLayout->addWidget( mSvgSelector );
576 
577  mLayout->addWidget( mButtonBox );
578  setLayout( mLayout );
579 }
580 
581 
583 
584 
585 QgsSvgParametersModel::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 
593 void 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 
605 QMap<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 
616 void QgsSvgParametersModel::removeParameters( const QModelIndexList &indexList )
617 {
618  if ( !indexList.count() )
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 
629 void QgsSvgParametersModel::setLayer( QgsVectorLayer *layer )
630 {
631  mLayer = layer;
632 }
633 
634 void QgsSvgParametersModel::setExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
635 {
636  mExpressionContextGenerator = generator;
637 }
638 
639 int QgsSvgParametersModel::rowCount( const QModelIndex &parent ) const
640 {
641  Q_UNUSED( parent )
642  return mParameters.count();
643 }
644 
645 int QgsSvgParametersModel::columnCount( const QModelIndex &parent ) const
646 {
647  Q_UNUSED( parent )
648  return 2;
649 }
650 
651 QVariant 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 
668 bool 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 
702 QVariant 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 
719 void 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 
733 Qt::ItemFlags QgsSvgParametersModel::flags( const QModelIndex &index ) const
734 {
735  Q_UNUSED( index )
736  return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
737 }
738 
739 
740 QWidget *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 
750 void 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 
759 void 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 
767 void 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:1380
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 reates 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:470
A button for controlling property overrides which may apply to a widget.
A store for object properties.
Definition: qgsproperty.h:231
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:1517
#define QgsDebugMsg(str)
Definition: qgslogger.h:38