QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 }
437 
439 {
440  mParametersModel->setExpressionContextGenerator( generator );
441  mParametersModel->setLayer( layer );
442 }
443 
444 void QgsSvgSelectorWidget::setSvgPath( const QString &svgPath )
445 {
446  mCurrentSvgPath = svgPath;
447 
448  whileBlocking( mSourceLineEdit )->setSource( svgPath );
449 
450  mImagesListView->selectionModel()->blockSignals( true );
451  QAbstractItemModel *m = mImagesListView->model();
452  QItemSelectionModel *selModel = mImagesListView->selectionModel();
453  for ( int i = 0; i < m->rowCount(); i++ )
454  {
455  QModelIndex idx( m->index( i, 0 ) );
456  if ( m->data( idx ).toString() == svgPath )
457  {
458  selModel->select( idx, QItemSelectionModel::SelectCurrent );
459  selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
460  mImagesListView->scrollTo( idx );
461  break;
462  }
463  }
464  mImagesListView->selectionModel()->blockSignals( false );
465 }
466 
467 void QgsSvgSelectorWidget::setSvgParameters( const QMap<QString, QgsProperty> &parameters )
468 {
469  mParametersModel->setParameters( parameters );
470 }
471 
473 {
474  return mCurrentSvgPath;
475 }
476 
478 {
479  if ( mAllowParameters == allow )
480  return;
481 
482  mAllowParameters = allow;
483  mParametersGroupBox->setVisible( allow );
484 }
485 
487 {
488  if ( mBrowserVisible == visible )
489  return;
490 
491  mBrowserVisible = visible;
492  mSvgBrowserGroupBox->setVisible( visible );
493 }
494 
496 {
497  return mSourceLineEdit->propertyOverrideToolButton();
498 }
499 
500 void QgsSvgSelectorWidget::updateCurrentSvgPath( const QString &svgPath )
501 {
502  mCurrentSvgPath = svgPath;
503  emit svgSelected( currentSvgPath() );
504 }
505 
506 void QgsSvgSelectorWidget::svgSelectionChanged( const QModelIndex &idx )
507 {
508  QString filePath = idx.data( Qt::UserRole ).toString();
509  whileBlocking( mSourceLineEdit )->setSource( filePath );
510  updateCurrentSvgPath( filePath );
511 }
512 
513 void QgsSvgSelectorWidget::populateIcons( const QModelIndex &idx )
514 {
515  QString path = idx.data( Qt::UserRole + 1 ).toString();
516 
517  QAbstractItemModel *oldModel = mImagesListView->model();
518  QgsSvgSelectorFilterModel *m = new QgsSvgSelectorFilterModel( mImagesListView, path, mIconSize );
519  mImagesListView->setModel( m );
520  connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
521  delete oldModel; //explicitly delete old model to force any background threads to stop
522 
523  connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
524  this, &QgsSvgSelectorWidget::svgSelectionChanged );
525 }
526 
527 void QgsSvgSelectorWidget::svgSourceChanged( const QString &text )
528 {
529  QString resolvedPath = QgsSymbolLayerUtils::svgSymbolNameToPath( text, QgsProject::instance()->pathResolver() );
530  bool validSVG = !resolvedPath.isNull();
531 
532  updateCurrentSvgPath( validSVG ? resolvedPath : text );
533 }
534 
536 {
537  QgsSvgSelectorGroupsModel *g = new QgsSvgSelectorGroupsModel( mGroupsTreeView );
538  mGroupsTreeView->setModel( g );
539  // Set the tree expanded at the first level
540  int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
541  for ( int i = 0; i < rows; i++ )
542  {
543  mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
544  }
545 
546  // Initially load the icons in the List view without any grouping
547  QAbstractItemModel *oldModel = mImagesListView->model();
548  QgsSvgSelectorFilterModel *m = new QgsSvgSelectorFilterModel( mImagesListView );
549  mImagesListView->setModel( m );
550  delete oldModel; //explicitly delete old model to force any background threads to stop
551 }
552 
553 //-- QgsSvgSelectorDialog
554 
555 QgsSvgSelectorDialog::QgsSvgSelectorDialog( QWidget *parent, Qt::WindowFlags fl,
556  QDialogButtonBox::StandardButtons buttons,
557  Qt::Orientation orientation )
558  : QDialog( parent, fl )
559 {
560  // TODO: pass 'orientation' to QgsSvgSelectorWidget for customizing its layout, once implemented
561  Q_UNUSED( orientation )
562 
563  // create buttonbox
564  mButtonBox = new QDialogButtonBox( buttons, orientation, this );
565  connect( mButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
566  connect( mButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
567 
568  setMinimumSize( 480, 320 );
569 
570  // dialog's layout
571  mLayout = new QVBoxLayout();
572  mSvgSelector = new QgsSvgSelectorWidget( this );
573  mLayout->addWidget( mSvgSelector );
574 
575  mLayout->addWidget( mButtonBox );
576  setLayout( mLayout );
577 }
578 
579 
581 
582 
583 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
584  : QAbstractTableModel( parent )
585 {
586  connect( this, &QAbstractTableModel::rowsInserted, this, [ = ]() {emit parametersChanged( parameters() );} );
587  connect( this, &QAbstractTableModel::rowsRemoved, this, [ = ]() {emit parametersChanged( parameters() );} );
588  connect( this, &QAbstractTableModel::dataChanged, this, [ = ]() {emit parametersChanged( parameters() );} );
589 }
590 
591 void QgsSvgParametersModel::setParameters( const QMap<QString, QgsProperty> &parameters )
592 {
593  beginResetModel();
594  mParameters.clear();
595  QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
596  for ( ; paramIt != parameters.constEnd(); ++paramIt )
597  {
598  mParameters << Parameter( paramIt.key(), paramIt.value() );
599  }
600  endResetModel();
601 }
602 
603 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters() const
604 {
605  QMap<QString, QgsProperty> params;
606  for ( const Parameter &param : std::as_const( mParameters ) )
607  {
608  if ( !param.name.isEmpty() )
609  params.insert( param.name, param.property );
610  }
611  return params;
612 }
613 
614 void QgsSvgParametersModel::removeParameters( const QModelIndexList &indexList )
615 {
616  if ( !indexList.count() )
617  return;
618 
619  auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( const QModelIndex & i1, const QModelIndex & i2 ) {return i1.row() < i2.row();} );
620 
621  beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
622  for ( const QModelIndex &index : indexList )
623  mParameters.removeAt( index.row() );
624  endRemoveRows();
625 }
626 
627 void QgsSvgParametersModel::setLayer( QgsVectorLayer *layer )
628 {
629  mLayer = layer;
630 }
631 
632 void QgsSvgParametersModel::setExpressionContextGenerator( const QgsExpressionContextGenerator *generator )
633 {
634  mExpressionContextGenerator = generator;
635 }
636 
637 int QgsSvgParametersModel::rowCount( const QModelIndex &parent ) const
638 {
639  Q_UNUSED( parent )
640  return mParameters.count();
641 }
642 
643 int QgsSvgParametersModel::columnCount( const QModelIndex &parent ) const
644 {
645  Q_UNUSED( parent )
646  return 2;
647 }
648 
649 QVariant QgsSvgParametersModel::data( const QModelIndex &index, int role ) const
650 {
651  QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( index.column() );
652  if ( role == Qt::DisplayRole )
653  {
654  switch ( col )
655  {
656  case QgsSvgParametersModel::Column::NameColumn:
657  return mParameters.at( index.row() ).name;
658  case QgsSvgParametersModel::Column::ExpressionColumn:
659  return mParameters.at( index.row() ).property.expressionString();
660  }
661  }
662 
663  return QVariant();
664 }
665 
666 bool QgsSvgParametersModel::setData( const QModelIndex &index, const QVariant &value, int role )
667 {
668  if ( !index.isValid() || role != Qt::EditRole )
669  return false;
670 
671  QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( index.column() );
672  switch ( col )
673  {
674  case QgsSvgParametersModel::Column::NameColumn:
675  {
676  QString oldName = mParameters.at( index.row() ).name;
677  QString newName = value.toString();
678  for ( const Parameter &param : std::as_const( mParameters ) )
679  {
680  if ( param.name == newName && param.name != oldName )
681  {
682  // names must be unique!
683  return false;
684  }
685  }
686  mParameters[index.row()].name = newName;
687  emit dataChanged( index, index );
688  return true;
689  }
690 
691  case QgsSvgParametersModel::Column::ExpressionColumn:
692  mParameters[index.row()].property = QgsProperty::fromExpression( value.toString() );
693  emit dataChanged( index, index );
694  return true;
695  }
696 
697  return false;
698 }
699 
700 QVariant QgsSvgParametersModel::headerData( int section, Qt::Orientation orientation, int role ) const
701 {
702  if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
703  {
704  QgsSvgParametersModel::Column col = static_cast<QgsSvgParametersModel::Column>( section );
705  switch ( col )
706  {
707  case QgsSvgParametersModel::Column::NameColumn:
708  return tr( "Name" );
709  case QgsSvgParametersModel::Column::ExpressionColumn:
710  return tr( "Expression" );
711  }
712  }
713 
714  return QVariant();
715 }
716 
717 void QgsSvgParametersModel::addParameter()
718 {
719  int c = rowCount( QModelIndex() );
720  beginInsertRows( QModelIndex(), c, c );
721  int i = 1;
722  QStringList currentNames;
723  std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), []( const Parameter & parameter ) {return parameter.name;} );
724  while ( currentNames.contains( QStringLiteral( "param%1" ).arg( i ) ) )
725  i++;
726  mParameters.append( Parameter( QStringLiteral( "param%1" ).arg( i ), QgsProperty() ) );
727  endResetModel();
728 }
729 
730 
731 Qt::ItemFlags QgsSvgParametersModel::flags( const QModelIndex &index ) const
732 {
733  Q_UNUSED( index )
734  return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
735 }
736 
737 
738 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index ) const
739 {
740  Q_UNUSED( option )
742  const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
743  w->registerExpressionContextGenerator( model->expressionContextGenerator() );
744  w->setLayer( model->layer() );
745  return w;
746 }
747 
748 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, const QModelIndex &index ) const
749 {
750  QgsFieldExpressionWidget *w = qobject_cast<QgsFieldExpressionWidget *>( editor );
751  if ( !w )
752  return;
753 
754  w->setExpression( index.model()->data( index ).toString() );
755 }
756 
757 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const
758 {
759  QgsFieldExpressionWidget *w = qobject_cast<QgsFieldExpressionWidget *>( editor );
760  if ( !w )
761  return;
762  model->setData( index, w->currentField() );
763 }
764 
765 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index ) const
766 {
767  Q_UNUSED( index )
768  editor->setGeometry( option.rect );
769 }
770 
772 
773 
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:416
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:467
A button for controlling property overrides which may apply to a widget.
A store for object properties.
Definition: qgsproperty.h:232
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:537
#define QgsDebugMsg(str)
Definition: qgslogger.h:38