31#include <QAbstractListModel> 
   32#include <QSortFilterProxyModel> 
   37#include <QPixmapCache> 
   45QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
 
   50QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
 
   55void QgsSvgSelectorLoader::run()
 
   59  mTraversedPaths.clear();
 
   67  if ( !mQueuedSvgs.isEmpty() )
 
   70    emit foundSvgs( mQueuedSvgs );
 
   75void QgsSvgSelectorLoader::stop()
 
   78  while ( isRunning() ) {}
 
   81void QgsSvgSelectorLoader::loadPath( 
const QString &path )
 
   91    const auto constSvgPaths = svgPaths;
 
   92    for ( 
const QString &svgPath : constSvgPaths )
 
   97      if ( !svgPath.isEmpty() )
 
  108    QString canonicalPath = dir.canonicalPath();
 
  109    if ( mTraversedPaths.contains( canonicalPath ) )
 
  112    mTraversedPaths.insert( canonicalPath );
 
  116    const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  117    for ( 
const QString &item : constEntryList )
 
  122      QString newPath = dir.path() + 
'/' + item;
 
  129void QgsSvgSelectorLoader::loadImages( 
const QString &path )
 
  132  const auto constEntryList = dir.entryList( QStringList( 
"*.svg" ), QDir::Files );
 
  133  for ( 
const QString &item : constEntryList )
 
  139    QString svgPath = dir.path() + 
'/' + item;
 
  143    mQueuedSvgs << svgPath;
 
  147    if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
 
  149      emit foundSvgs( mQueuedSvgs );
 
  155      if ( mTimerThreshold < 1000 )
 
  156        mTimerThreshold *= 2;
 
  167QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
 
  173QgsSvgGroupLoader::~QgsSvgGroupLoader()
 
  178void QgsSvgGroupLoader::run()
 
  181  mTraversedPaths.clear();
 
  183  while ( !mCanceled && !mParentPaths.isEmpty() )
 
  185    QString parentPath = mParentPaths.takeFirst();
 
  186    loadGroup( parentPath );
 
  190void QgsSvgGroupLoader::stop()
 
  193  while ( isRunning() ) {}
 
  196void QgsSvgGroupLoader::loadGroup( 
const QString &parentPath )
 
  198  QDir parentDir( parentPath );
 
  201  QString canonicalPath = parentDir.canonicalPath();
 
  202  if ( mTraversedPaths.contains( canonicalPath ) )
 
  205  mTraversedPaths.insert( canonicalPath );
 
  207  const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  208  for ( 
const QString &item : constEntryList )
 
  213    emit foundPath( parentPath, item );
 
  214    mParentPaths.append( parentDir.path() + 
'/' + item );
 
  224  : QSortFilterProxyModel( parent )
 
  227  setFilterCaseSensitivity( Qt::CaseInsensitive );
 
  228  setSourceModel( mModel );
 
  229  setFilterRole( Qt::UserRole );
 
  241  : QAbstractListModel( parent )
 
  242  , mSvgLoader( new QgsSvgSelectorLoader( this ) )
 
  245  mSvgLoader->setPath( path );
 
  246  connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, 
this, &QgsSvgSelectorListModel::addSvgs );
 
  256QPixmap QgsSvgSelectorListModel::createPreview( 
const QString &entry )
 const 
  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;
 
  265      fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
 
  266      strokeParam, hasDefaultStrokeColor, stroke,
 
  267      strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
 
  268      strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
 
  271  if ( !hasDefaultFillColor )
 
  272    fill = QColor( 200, 200, 200 );
 
  273  fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
 
  274  if ( !hasDefaultStrokeColor )
 
  276  stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
 
  277  if ( !hasDefaultStrokeWidth )
 
  282  return QPixmap::fromImage( img );
 
  287  QString entry = 
mSvgFiles.at( index.row() );
 
  289  if ( role == Qt::DecorationRole ) 
 
  291    QPixmap *pixmap = 
nullptr;
 
  292    if ( !QPixmapCache::find( entry, pixmap ) || !pixmap )
 
  294      QPixmap newPixmap = createPreview( entry );
 
  295      QPixmapCache::insert( entry, newPixmap );
 
  303  else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
 
  311void QgsSvgSelectorListModel::addSvgs( 
const QStringList &svgs )
 
  313  beginInsertRows( QModelIndex(), 
mSvgFiles.count(), 
mSvgFiles.count() + svgs.size() - 1 );
 
  325  : QStandardItemModel( parent )
 
  326  , mLoader( new QgsSvgGroupLoader( this ) )
 
  329  QStandardItem *parentItem = invisibleRootItem();
 
  330  QStringList parentPaths;
 
  331  parentPaths.reserve( svgPaths.size() );
 
  333  for ( 
int i = 0; i < svgPaths.size(); i++ )
 
  335    QDir dir( svgPaths.at( i ) );
 
  336    QStandardItem *baseGroup = 
nullptr;
 
  340      baseGroup = 
new QStandardItem( tr( 
"App Symbols" ) );
 
  344      baseGroup = 
new QStandardItem( tr( 
"User Symbols" ) );
 
  348      baseGroup = 
new QStandardItem( dir.dirName() );
 
  350    baseGroup->setData( QVariant( svgPaths.at( i ) ) );
 
  351    baseGroup->setEditable( 
false );
 
  352    baseGroup->setCheckable( 
false );
 
  354    baseGroup->setToolTip( dir.path() );
 
  355    parentItem->appendRow( baseGroup );
 
  356    parentPaths << svgPaths.at( i );
 
  357    mPathItemHash.insert( svgPaths.at( i ), baseGroup );
 
  358    QgsDebugMsgLevel( QStringLiteral( 
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
 
  360  mLoader->setParentPaths( parentPaths );
 
  361  connect( mLoader, &QgsSvgGroupLoader::foundPath, 
this, &QgsSvgSelectorGroupsModel::addPath );
 
  370void QgsSvgSelectorGroupsModel::addPath( 
const QString &parentPath, 
const QString &item )
 
  372  QStandardItem *parentGroup = mPathItemHash.value( parentPath );
 
  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 );
 
  383  parentGroup->appendRow( group );
 
  384  mPathItemHash.insert( fullPath, group );
 
  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 );
 
  400  mGroupsTreeView->setHeaderHidden( 
true );
 
  403  connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, 
this, [ = ]( 
const QString & filterText )
 
  405    if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
 
  407      disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  408      mImagesListView->selectionModel()->clearSelection();
 
  409      connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  411    qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
 
  415  mParametersModel = 
new QgsSvgParametersModel( 
this );
 
  416  mParametersTreeView->setModel( mParametersModel );
 
  417  mParametersGroupBox->setVisible( mAllowParameters );
 
  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 );
 
  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, [ = ]()
 
  432    const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
 
  433    if ( selectedRows.count() > 0 )
 
  434      mParametersModel->removeParameters( selectedRows );
 
  442  mParametersModel->setExpressionContextGenerator( generator );
 
  443  mParametersModel->setLayer( layer );
 
  448  mCurrentSvgPath = svgPath;
 
  452  mImagesListView->selectionModel()->blockSignals( 
true );
 
  453  QAbstractItemModel *m = mImagesListView->model();
 
  454  QItemSelectionModel *selModel = mImagesListView->selectionModel();
 
  455  for ( 
int i = 0; i < m->rowCount(); i++ )
 
  457    QModelIndex idx( m->index( i, 0 ) );
 
  458    if ( m->data( idx ).toString() == svgPath )
 
  460      selModel->select( idx, QItemSelectionModel::SelectCurrent );
 
  461      selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
 
  462      mImagesListView->scrollTo( idx );
 
  466  mImagesListView->selectionModel()->blockSignals( 
false );
 
  471  mParametersModel->setParameters( parameters );
 
  476  return mCurrentSvgPath;
 
  481  if ( mAllowParameters == allow )
 
  484  mAllowParameters = allow;
 
  485  mParametersGroupBox->setVisible( allow );
 
  490  if ( mBrowserVisible == visible )
 
  493  mBrowserVisible = visible;
 
  494  mSvgBrowserGroupBox->setVisible( visible );
 
  499  return mSourceLineEdit->propertyOverrideToolButton();
 
  502void QgsSvgSelectorWidget::updateCurrentSvgPath( 
const QString &svgPath )
 
  504  mCurrentSvgPath = svgPath;
 
  508void QgsSvgSelectorWidget::svgSelectionChanged( 
const QModelIndex &idx )
 
  510  QString filePath = idx.data( Qt::UserRole ).toString();
 
  512  updateCurrentSvgPath( filePath );
 
  515void QgsSvgSelectorWidget::populateIcons( 
const QModelIndex &idx )
 
  517  QString path = idx.data( Qt::UserRole + 1 ).toString();
 
  519  QAbstractItemModel *oldModel = mImagesListView->model();
 
  521  mImagesListView->setModel( m );
 
  522  connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
 
  525  connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
 
  526           this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  529void QgsSvgSelectorWidget::svgSourceChanged( 
const QString &text )
 
  532  bool validSVG = !resolvedPath.isNull();
 
  534  updateCurrentSvgPath( validSVG ? resolvedPath : text );
 
  540  mGroupsTreeView->setModel( g );
 
  542  int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
 
  543  for ( 
int i = 0; i < rows; i++ )
 
  545    mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), 
true );
 
  549  QAbstractItemModel *oldModel = mImagesListView->model();
 
  551  mImagesListView->setModel( m );
 
  558    QDialogButtonBox::StandardButtons buttons,
 
  559    Qt::Orientation orientation )
 
  560  : QDialog( parent, fl )
 
  563  Q_UNUSED( orientation )
 
  566  mButtonBox = 
new QDialogButtonBox( buttons, orientation, 
this );
 
  567  connect( 
mButtonBox, &QDialogButtonBox::accepted, 
this, &QDialog::accept );
 
  568  connect( 
mButtonBox, &QDialogButtonBox::rejected, 
this, &QDialog::reject );
 
  570  setMinimumSize( 480, 320 );
 
  585QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
 
  586  : QAbstractTableModel( parent )
 
  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() );} );
 
  593void QgsSvgParametersModel::setParameters( 
const QMap<QString, QgsProperty> ¶meters )
 
  597  QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
 
  598  for ( ; paramIt != parameters.constEnd(); ++paramIt )
 
  600    mParameters << Parameter( paramIt.key(), paramIt.value() );
 
  605QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
 const 
  607  QMap<QString, QgsProperty> params;
 
  608  for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  610    if ( !param.name.isEmpty() )
 
  611      params.insert( param.name, param.property );
 
  616void QgsSvgParametersModel::removeParameters( 
const QModelIndexList &indexList )
 
  618  if ( indexList.isEmpty() )
 
  621  auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( 
const QModelIndex & i1, 
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
 
  623  beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
 
  624  for ( 
const QModelIndex &index : indexList )
 
  625    mParameters.removeAt( index.row() );
 
  636  mExpressionContextGenerator = generator;
 
  639int QgsSvgParametersModel::rowCount( 
const QModelIndex &parent )
 const 
  642  return mParameters.count();
 
  645int QgsSvgParametersModel::columnCount( 
const QModelIndex &parent )
 const 
  651QVariant QgsSvgParametersModel::data( 
const QModelIndex &index, 
int role )
 const 
  653  QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  654  if ( role == Qt::DisplayRole )
 
  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();
 
  668bool QgsSvgParametersModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  670  if ( !index.isValid() || role != Qt::EditRole )
 
  673  QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  676    case QgsSvgParametersModel::Column::NameColumn:
 
  678      QString oldName = mParameters.at( index.row() ).name;
 
  679      QString newName = value.toString();
 
  680      for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  682        if ( param.name == newName && param.name != oldName )
 
  688      mParameters[index.row()].name = newName;
 
  689      emit dataChanged( index, index );
 
  693    case QgsSvgParametersModel::Column::ExpressionColumn:
 
  695      emit dataChanged( index, index );
 
  702QVariant QgsSvgParametersModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  704  if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
 
  706    QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( section );
 
  709      case QgsSvgParametersModel::Column::NameColumn:
 
  711      case QgsSvgParametersModel::Column::ExpressionColumn:
 
  712        return tr( 
"Expression" );
 
  719void QgsSvgParametersModel::addParameter()
 
  721  int c = rowCount( QModelIndex() );
 
  722  beginInsertRows( QModelIndex(), 
c, 
c );
 
  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 ) ) )
 
  728  mParameters.append( Parameter( QStringLiteral( 
"param%1" ).arg( i ), 
QgsProperty() ) );
 
  733Qt::ItemFlags QgsSvgParametersModel::flags( 
const QModelIndex &index )
 const 
  736  return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
 
  740QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  744  const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
 
  750void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, 
const QModelIndex &index )
 const 
  759void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, 
const QModelIndex &index )
 const 
  767void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  770  editor->setGeometry( option.rect );
 
static const double UI_SCALE_FACTOR
UI scaling factor.
 
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.
 
static QgsProject * instance()
Returns the QgsProject singleton instance.
 
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 > ¶meters=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() override
 
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.
 
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.
 
#define QgsDebugMsgLevel(str, level)