18#include "moc_qgssvgselectorwidget.cpp" 
   32#include <QAbstractListModel> 
   33#include <QSortFilterProxyModel> 
   38#include <QPixmapCache> 
   46QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
 
   51QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
 
   56void QgsSvgSelectorLoader::run()
 
   60  mTraversedPaths.clear();
 
   68  if ( !mQueuedSvgs.isEmpty() )
 
   71    emit foundSvgs( mQueuedSvgs );
 
   76void QgsSvgSelectorLoader::stop()
 
   84void QgsSvgSelectorLoader::loadPath( 
const QString &path )
 
   94    const auto constSvgPaths = svgPaths;
 
   95    for ( 
const QString &svgPath : constSvgPaths )
 
  100      if ( !svgPath.isEmpty() )
 
  111    QString canonicalPath = dir.canonicalPath();
 
  112    if ( mTraversedPaths.contains( canonicalPath ) )
 
  115    mTraversedPaths.insert( canonicalPath );
 
  119    const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  120    for ( 
const QString &item : constEntryList )
 
  125      QString newPath = dir.path() + 
'/' + item;
 
  132void QgsSvgSelectorLoader::loadImages( 
const QString &path )
 
  135  const auto constEntryList = dir.entryList( QStringList( 
"*.svg" ), QDir::Files );
 
  136  for ( 
const QString &item : constEntryList )
 
  142    QString svgPath = dir.path() + 
'/' + item;
 
  146    mQueuedSvgs << svgPath;
 
  150    if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
 
  152      emit foundSvgs( mQueuedSvgs );
 
  158      if ( mTimerThreshold < 1000 )
 
  159        mTimerThreshold *= 2;
 
  170QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
 
  175QgsSvgGroupLoader::~QgsSvgGroupLoader()
 
  180void QgsSvgGroupLoader::run()
 
  183  mTraversedPaths.clear();
 
  185  while ( !mCanceled && !mParentPaths.isEmpty() )
 
  187    QString parentPath = mParentPaths.takeFirst();
 
  188    loadGroup( parentPath );
 
  192void QgsSvgGroupLoader::stop()
 
  195  while ( isRunning() )
 
  200void QgsSvgGroupLoader::loadGroup( 
const QString &parentPath )
 
  202  QDir parentDir( parentPath );
 
  205  QString canonicalPath = parentDir.canonicalPath();
 
  206  if ( mTraversedPaths.contains( canonicalPath ) )
 
  209  mTraversedPaths.insert( canonicalPath );
 
  211  const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  212  for ( 
const QString &item : constEntryList )
 
  217    emit foundPath( parentPath, item );
 
  218    mParentPaths.append( parentDir.path() + 
'/' + item );
 
  226  : QSortFilterProxyModel( parent )
 
  229  setFilterCaseSensitivity( Qt::CaseInsensitive );
 
  230  setSourceModel( mModel );
 
  231  setFilterRole( Qt::UserRole );
 
 
  243  : QAbstractListModel( parent )
 
  244  , mSvgLoader( new QgsSvgSelectorLoader( this ) )
 
  245  , mIconSize( iconSize )
 
  247  mSvgLoader->setPath( path );
 
  248  connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, 
this, &QgsSvgSelectorListModel::addSvgs );
 
 
  258QPixmap QgsSvgSelectorListModel::createPreview( 
const QString &entry )
 const 
  262  double strokeWidth, fillOpacity, strokeOpacity;
 
  263  bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
 
  264  bool hasDefaultFillColor = 
false, hasDefaultFillOpacity = 
false, hasDefaultStrokeColor = 
false,
 
  265       hasDefaultStrokeWidth = 
false, hasDefaultStrokeOpacity = 
false;
 
  266  QgsApplication::svgCache()->
containsParams( entry, fillParam, hasDefaultFillColor, fill, fillOpacityParam, hasDefaultFillOpacity, fillOpacity, strokeParam, hasDefaultStrokeColor, stroke, strokeWidthParam, hasDefaultStrokeWidth, strokeWidth, strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
 
  269  if ( !hasDefaultFillColor )
 
  270    fill = QColor( 200, 200, 200 );
 
  271  fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
 
  272  if ( !hasDefaultStrokeColor )
 
  274  stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
 
  275  if ( !hasDefaultStrokeWidth )
 
  280  return QPixmap::fromImage( img );
 
  285  QString entry = 
mSvgFiles.at( index.row() );
 
  287  if ( role == Qt::DecorationRole ) 
 
  290    if ( !QPixmapCache::find( entry, &pixmap ) )
 
  292      QPixmap newPixmap = createPreview( entry );
 
  293      QPixmapCache::insert( entry, newPixmap );
 
  301  else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
 
 
  309void QgsSvgSelectorListModel::addSvgs( 
const QStringList &svgs )
 
  311  beginInsertRows( QModelIndex(), 
mSvgFiles.count(), 
mSvgFiles.count() + svgs.size() - 1 );
 
  320  : QStandardItemModel( parent )
 
  321  , mLoader( new QgsSvgGroupLoader( this ) )
 
  324  QStandardItem *parentItem = invisibleRootItem();
 
  325  QStringList parentPaths;
 
  326  parentPaths.reserve( svgPaths.size() );
 
  328  for ( 
int i = 0; i < svgPaths.size(); i++ )
 
  330    QDir dir( svgPaths.at( i ) );
 
  331    QStandardItem *baseGroup = 
nullptr;
 
  335      baseGroup = 
new QStandardItem( tr( 
"App Symbols" ) );
 
  339      baseGroup = 
new QStandardItem( tr( 
"User Symbols" ) );
 
  343      baseGroup = 
new QStandardItem( dir.dirName() );
 
  345    baseGroup->setData( QVariant( svgPaths.at( i ) ) );
 
  346    baseGroup->setEditable( 
false );
 
  347    baseGroup->setCheckable( 
false );
 
  349    baseGroup->setToolTip( dir.path() );
 
  350    parentItem->appendRow( baseGroup );
 
  351    parentPaths << svgPaths.at( i );
 
  352    mPathItemHash.insert( svgPaths.at( i ), baseGroup );
 
  353    QgsDebugMsgLevel( QStringLiteral( 
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
 
  355  mLoader->setParentPaths( parentPaths );
 
  356  connect( mLoader, &QgsSvgGroupLoader::foundPath, 
this, &QgsSvgSelectorGroupsModel::addPath );
 
 
  365void QgsSvgSelectorGroupsModel::addPath( 
const QString &parentPath, 
const QString &item )
 
  367  QStandardItem *parentGroup = mPathItemHash.value( parentPath );
 
  371  QString fullPath = parentPath + 
'/' + item;
 
  372  QStandardItem *group = 
new QStandardItem( item );
 
  373  group->setData( QVariant( fullPath ) );
 
  374  group->setEditable( 
false );
 
  375  group->setCheckable( 
false );
 
  376  group->setToolTip( fullPath );
 
  378  parentGroup->appendRow( group );
 
  379  mPathItemHash.insert( fullPath, group );
 
  391  mIconSize = std::max( 30, 
static_cast<int>( std::round( 
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 
'X' ) * 3 ) ) );
 
  392  mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
 
  393  mImagesListView->setUniformItemSizes( 
false );
 
  395  mGroupsTreeView->setHeaderHidden( 
true );
 
  398  connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, 
this, [
this]( 
const QString &filterText ) {
 
  399    if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
 
  401      disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  402      mImagesListView->selectionModel()->clearSelection();
 
  403      connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  405    qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
 
  409  mParametersModel = 
new QgsSvgParametersModel( 
this );
 
  410  mParametersTreeView->setModel( mParametersModel );
 
  411  mParametersGroupBox->setVisible( mAllowParameters );
 
  413  mParametersTreeView->setItemDelegateForColumn( 
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ), 
new QgsSvgParameterValueDelegate( 
this ) );
 
  414  mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
 
  415  mParametersTreeView->header()->setStretchLastSection( 
true );
 
  416  mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
 
  417  mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
 
  418  mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
 
  421  connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, 
this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  422  connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged, 
this, &QgsSvgSelectorWidget::populateIcons );
 
  423  connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
 
  424  connect( mRemoveParameterButton, &QToolButton::clicked, 
this, [
this]() {
 
  425    const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
 
  426    if ( selectedRows.count() > 0 )
 
  427      mParametersModel->removeParameters( selectedRows );
 
 
  435  mParametersModel->setExpressionContextGenerator( generator );
 
  436  mParametersModel->setLayer( layer );
 
 
  441  mCurrentSvgPath = svgPath;
 
  445  mImagesListView->selectionModel()->blockSignals( 
true );
 
  446  QAbstractItemModel *m = mImagesListView->model();
 
  447  QItemSelectionModel *selModel = mImagesListView->selectionModel();
 
  448  for ( 
int i = 0; i < m->rowCount(); i++ )
 
  450    QModelIndex idx( m->index( i, 0 ) );
 
  451    if ( m->data( idx ).toString() == svgPath )
 
  453      selModel->select( idx, QItemSelectionModel::SelectCurrent );
 
  454      selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
 
  455      mImagesListView->scrollTo( idx );
 
  459  mImagesListView->selectionModel()->blockSignals( 
false );
 
 
  464  mParametersModel->setParameters( parameters );
 
 
  469  return mCurrentSvgPath;
 
 
  474  if ( mAllowParameters == allow )
 
  477  mAllowParameters = allow;
 
  478  mParametersGroupBox->setVisible( allow );
 
 
  483  if ( mBrowserVisible == visible )
 
  486  mBrowserVisible = visible;
 
  487  mSvgBrowserGroupBox->setVisible( visible );
 
 
  492  return mSourceLineEdit->propertyOverrideToolButton();
 
 
  495void QgsSvgSelectorWidget::updateCurrentSvgPath( 
const QString &svgPath )
 
  497  mCurrentSvgPath = svgPath;
 
  501void QgsSvgSelectorWidget::svgSelectionChanged( 
const QModelIndex &idx )
 
  503  QString filePath = idx.data( Qt::UserRole ).toString();
 
  505  updateCurrentSvgPath( filePath );
 
  508void QgsSvgSelectorWidget::populateIcons( 
const QModelIndex &idx )
 
  510  QString path = idx.data( Qt::UserRole + 1 ).toString();
 
  512  QAbstractItemModel *oldModel = mImagesListView->model();
 
  514  mImagesListView->setModel( m );
 
  515  connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
 
  518  connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, 
this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  521void QgsSvgSelectorWidget::svgSourceChanged( 
const QString &text )
 
  524  bool validSVG = !resolvedPath.isNull();
 
  526  updateCurrentSvgPath( validSVG ? resolvedPath : text );
 
  532  mGroupsTreeView->setModel( g );
 
  534  int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
 
  535  for ( 
int i = 0; i < rows; i++ )
 
  537    mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), 
true );
 
  541  QAbstractItemModel *oldModel = mImagesListView->model();
 
  543  mImagesListView->setModel( m );
 
 
  550  : QDialog( parent, fl )
 
  553  Q_UNUSED( orientation )
 
  556  mButtonBox = 
new QDialogButtonBox( buttons, orientation, 
this );
 
  557  connect( 
mButtonBox, &QDialogButtonBox::accepted, 
this, &QDialog::accept );
 
  558  connect( 
mButtonBox, &QDialogButtonBox::rejected, 
this, &QDialog::reject );
 
  560  setMinimumSize( 480, 320 );
 
 
  575QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
 
  576  : QAbstractTableModel( parent )
 
  578  connect( 
this, &QAbstractTableModel::rowsInserted, 
this, [
this]() { emit parametersChanged( parameters() ); } );
 
  579  connect( 
this, &QAbstractTableModel::rowsRemoved, 
this, [
this]() { emit parametersChanged( parameters() ); } );
 
  580  connect( 
this, &QAbstractTableModel::dataChanged, 
this, [
this]() { emit parametersChanged( parameters() ); } );
 
  583void QgsSvgParametersModel::setParameters( 
const QMap<QString, QgsProperty> ¶meters )
 
  587  QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
 
  588  for ( ; paramIt != parameters.constEnd(); ++paramIt )
 
  590    mParameters << Parameter( paramIt.key(), paramIt.value() );
 
  595QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
 const 
  597  QMap<QString, QgsProperty> params;
 
  598  for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  600    if ( !param.name.isEmpty() )
 
  601      params.insert( param.name, param.property );
 
  606void QgsSvgParametersModel::removeParameters( 
const QModelIndexList &indexList )
 
  608  if ( indexList.isEmpty() )
 
  611  auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( 
const QModelIndex &i1, 
const QModelIndex &i2 ) { return i1.row() < i2.row(); } );
 
  613  beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
 
  614  for ( 
const QModelIndex &index : indexList )
 
  615    mParameters.removeAt( index.row() );
 
  626  mExpressionContextGenerator = generator;
 
  629int QgsSvgParametersModel::rowCount( 
const QModelIndex &parent )
 const 
  632  return mParameters.count();
 
  635int QgsSvgParametersModel::columnCount( 
const QModelIndex &parent )
 const 
  641QVariant QgsSvgParametersModel::data( 
const QModelIndex &index, 
int role )
 const 
  643  QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  644  if ( role == Qt::DisplayRole )
 
  648      case QgsSvgParametersModel::Column::NameColumn:
 
  649        return mParameters.at( index.row() ).name;
 
  650      case QgsSvgParametersModel::Column::ExpressionColumn:
 
  651        return mParameters.at( index.row() ).property.expressionString();
 
  658bool QgsSvgParametersModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  660  if ( !index.isValid() || role != Qt::EditRole )
 
  663  QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  666    case QgsSvgParametersModel::Column::NameColumn:
 
  668      QString oldName = mParameters.at( index.row() ).name;
 
  669      QString newName = value.toString();
 
  670      for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  672        if ( param.name == newName && param.name != oldName )
 
  678      mParameters[index.row()].name = newName;
 
  679      emit dataChanged( index, index );
 
  683    case QgsSvgParametersModel::Column::ExpressionColumn:
 
  685      emit dataChanged( index, index );
 
  692QVariant QgsSvgParametersModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  694  if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
 
  696    QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( section );
 
  699      case QgsSvgParametersModel::Column::NameColumn:
 
  701      case QgsSvgParametersModel::Column::ExpressionColumn:
 
  702        return tr( 
"Expression" );
 
  709void QgsSvgParametersModel::addParameter()
 
  711  int c = rowCount( QModelIndex() );
 
  712  beginInsertRows( QModelIndex(), 
c, 
c );
 
  714  QStringList currentNames;
 
  715  std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), []( 
const Parameter ¶meter ) { return parameter.name; } );
 
  716  while ( currentNames.contains( QStringLiteral( 
"param%1" ).arg( i ) ) )
 
  718  mParameters.append( Parameter( QStringLiteral( 
"param%1" ).arg( i ), 
QgsProperty() ) );
 
  723Qt::ItemFlags QgsSvgParametersModel::flags( 
const QModelIndex &index )
 const 
  726  return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
 
  730QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  734  const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
 
  740void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, 
const QModelIndex &index )
 const 
  749void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, 
const QModelIndex &index )
 const 
  757void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  760  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 dataset.
 
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
 
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
 
#define QgsDebugMsgLevel(str, level)