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