25 #include "qgssettings.h" 
   31 #include <QAbstractListModel> 
   32 #include <QSortFilterProxyModel> 
   35 #include <QFileDialog> 
   36 #include <QModelIndex> 
   37 #include <QPixmapCache> 
   45 QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
 
   50 QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
 
   55 void QgsSvgSelectorLoader::run()
 
   59   mTraversedPaths.clear();
 
   67   if ( !mQueuedSvgs.isEmpty() )
 
   70     emit foundSvgs( mQueuedSvgs );
 
   75 void QgsSvgSelectorLoader::stop()
 
   78   while ( isRunning() ) {}
 
   81 void 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;
 
  129 void 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;
 
  167 QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
 
  173 QgsSvgGroupLoader::~QgsSvgGroupLoader()
 
  178 void QgsSvgGroupLoader::run()
 
  181   mTraversedPaths.clear();
 
  183   while ( !mCanceled && !mParentPaths.isEmpty() )
 
  185     QString parentPath = mParentPaths.takeFirst();
 
  186     loadGroup( parentPath );
 
  190 void QgsSvgGroupLoader::stop()
 
  193   while ( isRunning() ) {}
 
  196 void 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 );
 
  256 QPixmap 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 )
 
  311 void 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     QgsDebugMsg( QStringLiteral( 
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
 
  360   mLoader->setParentPaths( parentPaths );
 
  361   connect( mLoader, &QgsSvgGroupLoader::foundPath, 
this, &QgsSvgSelectorGroupsModel::addPath );
 
  370 void 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 );
 
  440   mParametersModel->setExpressionContextGenerator( generator );
 
  441   mParametersModel->setLayer( layer );
 
  446   mCurrentSvgPath = svgPath;
 
  450   mImagesListView->selectionModel()->blockSignals( 
true );
 
  451   QAbstractItemModel *m = mImagesListView->model();
 
  452   QItemSelectionModel *selModel = mImagesListView->selectionModel();
 
  453   for ( 
int i = 0; i < m->rowCount(); i++ )
 
  455     QModelIndex idx( m->index( i, 0 ) );
 
  456     if ( m->data( idx ).toString() == svgPath )
 
  458       selModel->select( idx, QItemSelectionModel::SelectCurrent );
 
  459       selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
 
  460       mImagesListView->scrollTo( idx );
 
  464   mImagesListView->selectionModel()->blockSignals( 
false );
 
  469   mParametersModel->setParameters( parameters );
 
  474   return mCurrentSvgPath;
 
  479   if ( mAllowParameters == allow )
 
  482   mAllowParameters = allow;
 
  483   mParametersGroupBox->setVisible( allow );
 
  488   if ( mBrowserVisible == visible )
 
  491   mBrowserVisible = visible;
 
  492   mSvgBrowserGroupBox->setVisible( visible );
 
  497   return mSourceLineEdit->propertyOverrideToolButton();
 
  500 void QgsSvgSelectorWidget::updateCurrentSvgPath( 
const QString &svgPath )
 
  502   mCurrentSvgPath = svgPath;
 
  506 void QgsSvgSelectorWidget::svgSelectionChanged( 
const QModelIndex &idx )
 
  508   QString filePath = idx.data( Qt::UserRole ).toString();
 
  510   updateCurrentSvgPath( filePath );
 
  513 void QgsSvgSelectorWidget::populateIcons( 
const QModelIndex &idx )
 
  515   QString path = idx.data( Qt::UserRole + 1 ).toString();
 
  517   QAbstractItemModel *oldModel = mImagesListView->model();
 
  519   mImagesListView->setModel( m );
 
  520   connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
 
  523   connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
 
  524            this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  527 void QgsSvgSelectorWidget::svgSourceChanged( 
const QString &text )
 
  530   bool validSVG = !resolvedPath.isNull();
 
  532   updateCurrentSvgPath( validSVG ? resolvedPath : text );
 
  538   mGroupsTreeView->setModel( g );
 
  540   int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
 
  541   for ( 
int i = 0; i < rows; i++ )
 
  543     mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), 
true );
 
  547   QAbstractItemModel *oldModel = mImagesListView->model();
 
  549   mImagesListView->setModel( m );
 
  556     QDialogButtonBox::StandardButtons buttons,
 
  557     Qt::Orientation orientation )
 
  558   : QDialog( parent, fl )
 
  561   Q_UNUSED( orientation )
 
  564   mButtonBox = 
new QDialogButtonBox( buttons, orientation, 
this );
 
  565   connect( 
mButtonBox, &QDialogButtonBox::accepted, 
this, &QDialog::accept );
 
  566   connect( 
mButtonBox, &QDialogButtonBox::rejected, 
this, &QDialog::reject );
 
  568   setMinimumSize( 480, 320 );
 
  583 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
 
  584   : QAbstractTableModel( parent )
 
  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() );} );
 
  591 void QgsSvgParametersModel::setParameters( 
const QMap<QString, QgsProperty> ¶meters )
 
  595   QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
 
  596   for ( ; paramIt != parameters.constEnd(); ++paramIt )
 
  598     mParameters << Parameter( paramIt.key(), paramIt.value() );
 
  603 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
 const 
  605   QMap<QString, QgsProperty> params;
 
  606   for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  608     if ( !param.name.isEmpty() )
 
  609       params.insert( param.name, param.property );
 
  614 void QgsSvgParametersModel::removeParameters( 
const QModelIndexList &indexList )
 
  616   if ( !indexList.count() )
 
  619   auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( 
const QModelIndex & i1, 
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
 
  621   beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
 
  622   for ( 
const QModelIndex &index : indexList )
 
  623     mParameters.removeAt( index.row() );
 
  634   mExpressionContextGenerator = generator;
 
  637 int QgsSvgParametersModel::rowCount( 
const QModelIndex &parent )
 const 
  640   return mParameters.count();
 
  643 int QgsSvgParametersModel::columnCount( 
const QModelIndex &parent )
 const 
  649 QVariant QgsSvgParametersModel::data( 
const QModelIndex &index, 
int role )
 const 
  651   QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  652   if ( role == Qt::DisplayRole )
 
  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();
 
  666 bool QgsSvgParametersModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  668   if ( !index.isValid() || role != Qt::EditRole )
 
  671   QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  674     case QgsSvgParametersModel::Column::NameColumn:
 
  676       QString oldName = mParameters.at( index.row() ).name;
 
  677       QString newName = value.toString();
 
  678       for ( 
const Parameter ¶m : std::as_const( mParameters ) )
 
  680         if ( param.name == newName && param.name != oldName )
 
  686       mParameters[index.row()].name = newName;
 
  687       emit dataChanged( index, index );
 
  691     case QgsSvgParametersModel::Column::ExpressionColumn:
 
  693       emit dataChanged( index, index );
 
  700 QVariant QgsSvgParametersModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  702   if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
 
  704     QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( section );
 
  707       case QgsSvgParametersModel::Column::NameColumn:
 
  709       case QgsSvgParametersModel::Column::ExpressionColumn:
 
  710         return tr( 
"Expression" );
 
  717 void QgsSvgParametersModel::addParameter()
 
  719   int c = rowCount( QModelIndex() );
 
  720   beginInsertRows( QModelIndex(), 
c, 
c );
 
  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 ) ) )
 
  726   mParameters.append( Parameter( QStringLiteral( 
"param%1" ).arg( i ), 
QgsProperty() ) );
 
  731 Qt::ItemFlags QgsSvgParametersModel::flags( 
const QModelIndex &index )
 const 
  734   return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
 
  738 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  742   const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
 
  748 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, 
const QModelIndex &index )
 const 
  757 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, 
const QModelIndex &index )
 const 
  765 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  768   editor->setGeometry( option.rect );
 
static const double UI_SCALE_FACTOR
UI scaling factor.
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.