31 #include <QAbstractListModel> 
   34 #include <QFileDialog> 
   35 #include <QModelIndex> 
   36 #include <QPixmapCache> 
   44 QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
 
   49 QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
 
   54 void QgsSvgSelectorLoader::run()
 
   58   mTraversedPaths.clear();
 
   66   if ( !mQueuedSvgs.isEmpty() )
 
   69     emit foundSvgs( mQueuedSvgs );
 
   74 void QgsSvgSelectorLoader::stop()
 
   77   while ( isRunning() ) {}
 
   80 void QgsSvgSelectorLoader::loadPath( 
const QString &path )
 
   90     const auto constSvgPaths = svgPaths;
 
   91     for ( 
const QString &svgPath : constSvgPaths )
 
   96       if ( !svgPath.isEmpty() )
 
  107     QString canonicalPath = dir.canonicalPath();
 
  108     if ( mTraversedPaths.contains( canonicalPath ) )
 
  111     mTraversedPaths.insert( canonicalPath );
 
  115     const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  116     for ( 
const QString &item : constEntryList )
 
  121       QString newPath = dir.path() + 
'/' + item;
 
  128 void QgsSvgSelectorLoader::loadImages( 
const QString &path )
 
  131   const auto constEntryList = dir.entryList( QStringList( 
"*.svg" ), QDir::Files );
 
  132   for ( 
const QString &item : constEntryList )
 
  138     QString svgPath = dir.path() + 
'/' + item;
 
  142     mQueuedSvgs << svgPath;
 
  146     if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
 
  148       emit foundSvgs( mQueuedSvgs );
 
  154       if ( mTimerThreshold < 1000 )
 
  155         mTimerThreshold *= 2;
 
  166 QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
 
  172 QgsSvgGroupLoader::~QgsSvgGroupLoader()
 
  177 void QgsSvgGroupLoader::run()
 
  180   mTraversedPaths.clear();
 
  182   while ( !mCanceled && !mParentPaths.isEmpty() )
 
  184     QString parentPath = mParentPaths.takeFirst();
 
  185     loadGroup( parentPath );
 
  189 void QgsSvgGroupLoader::stop()
 
  192   while ( isRunning() ) {}
 
  195 void QgsSvgGroupLoader::loadGroup( 
const QString &parentPath )
 
  197   QDir parentDir( parentPath );
 
  200   QString canonicalPath = parentDir.canonicalPath();
 
  201   if ( mTraversedPaths.contains( canonicalPath ) )
 
  204   mTraversedPaths.insert( canonicalPath );
 
  206   const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
 
  207   for ( 
const QString &item : constEntryList )
 
  212     emit foundPath( parentPath, item );
 
  213     mParentPaths.append( parentDir.path() + 
'/' + item );
 
  224   : QAbstractListModel( parent )
 
  225   , mSvgLoader( new QgsSvgSelectorLoader( this ) )
 
  228   mSvgLoader->setPath( QString() );
 
  229   connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, 
this, &QgsSvgSelectorListModel::addSvgs );
 
  234   : QAbstractListModel( parent )
 
  235   , mSvgLoader( new QgsSvgSelectorLoader( this ) )
 
  238   mSvgLoader->setPath( path );
 
  239   connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs, 
this, &QgsSvgSelectorListModel::addSvgs );
 
  249 QPixmap QgsSvgSelectorListModel::createPreview( 
const QString &entry )
 const 
  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;
 
  258       fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
 
  259       strokeParam, hasDefaultStrokeColor, stroke,
 
  260       strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
 
  261       strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
 
  264   if ( !hasDefaultFillColor )
 
  265     fill = QColor( 200, 200, 200 );
 
  266   fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
 
  267   if ( !hasDefaultStrokeColor )
 
  269   stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
 
  270   if ( !hasDefaultStrokeWidth )
 
  275   return QPixmap::fromImage( img );
 
  280   QString entry = 
mSvgFiles.at( index.row() );
 
  282   if ( role == Qt::DecorationRole ) 
 
  284     QPixmap *pixmap = 
nullptr;
 
  285     if ( !QPixmapCache::find( entry, pixmap ) || !pixmap )
 
  287       QPixmap newPixmap = createPreview( entry );
 
  288       QPixmapCache::insert( entry, newPixmap );
 
  296   else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
 
  304 void QgsSvgSelectorListModel::addSvgs( 
const QStringList &svgs )
 
  306   beginInsertRows( QModelIndex(), 
mSvgFiles.count(), 
mSvgFiles.count() + svgs.size() - 1 );
 
  318   : QStandardItemModel( parent )
 
  319   , mLoader( new QgsSvgGroupLoader( this ) )
 
  322   QStandardItem *parentItem = invisibleRootItem();
 
  323   QStringList parentPaths;
 
  324   parentPaths.reserve( svgPaths.size() );
 
  326   for ( 
int i = 0; i < svgPaths.size(); i++ )
 
  328     QDir dir( svgPaths.at( i ) );
 
  329     QStandardItem *baseGroup = 
nullptr;
 
  333       baseGroup = 
new QStandardItem( tr( 
"App Symbols" ) );
 
  337       baseGroup = 
new QStandardItem( tr( 
"User Symbols" ) );
 
  341       baseGroup = 
new QStandardItem( dir.dirName() );
 
  343     baseGroup->setData( QVariant( svgPaths.at( i ) ) );
 
  344     baseGroup->setEditable( 
false );
 
  345     baseGroup->setCheckable( 
false );
 
  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() ) );
 
  353   mLoader->setParentPaths( parentPaths );
 
  354   connect( mLoader, &QgsSvgGroupLoader::foundPath, 
this, &QgsSvgSelectorGroupsModel::addPath );
 
  363 void QgsSvgSelectorGroupsModel::addPath( 
const QString &parentPath, 
const QString &item )
 
  365   QStandardItem *parentGroup = mPathItemHash.value( parentPath );
 
  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 );
 
  376   parentGroup->appendRow( group );
 
  377   mPathItemHash.insert( fullPath, group );
 
  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 ) ) );
 
  394   mIconSize = std::max( 30, 
static_cast< int >( std::round( 
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 
'X' ) * 3 ) ) );
 
  396   mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
 
  397   mImagesListView->setUniformItemSizes( 
false );
 
  399   mGroupsTreeView->setHeaderHidden( 
true );
 
  402   mParametersModel = 
new QgsSvgParametersModel( 
this );
 
  403   mParametersTreeView->setModel( mParametersModel );
 
  404   mParametersGroupBox->setVisible( mAllowParameters );
 
  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 );
 
  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, [ = ]()
 
  419     const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
 
  420     if ( selectedRows.count() > 0 )
 
  421       mParametersModel->removeParameters( selectedRows );
 
  427   mParametersModel->setExpressionContextGenerator( generator );
 
  428   mParametersModel->setLayer( layer );
 
  433   mCurrentSvgPath = svgPath;
 
  437   mImagesListView->selectionModel()->blockSignals( 
true );
 
  438   QAbstractItemModel *m = mImagesListView->model();
 
  439   QItemSelectionModel *selModel = mImagesListView->selectionModel();
 
  440   for ( 
int i = 0; i < m->rowCount(); i++ )
 
  442     QModelIndex idx( m->index( i, 0 ) );
 
  443     if ( m->data( idx ).toString() == svgPath )
 
  445       selModel->select( idx, QItemSelectionModel::SelectCurrent );
 
  446       selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
 
  447       mImagesListView->scrollTo( idx );
 
  451   mImagesListView->selectionModel()->blockSignals( 
false );
 
  456   mParametersModel->setParameters( parameters );
 
  461   return mCurrentSvgPath;
 
  466   if ( mAllowParameters == allow )
 
  469   mAllowParameters = allow;
 
  470   mParametersGroupBox->setVisible( allow );
 
  473 void QgsSvgSelectorWidget::updateCurrentSvgPath( 
const QString &svgPath )
 
  475   mCurrentSvgPath = svgPath;
 
  479 void QgsSvgSelectorWidget::svgSelectionChanged( 
const QModelIndex &idx )
 
  481   QString filePath = idx.data( Qt::UserRole ).toString();
 
  483   updateCurrentSvgPath( filePath );
 
  486 void QgsSvgSelectorWidget::populateIcons( 
const QModelIndex &idx )
 
  488   QString path = idx.data( Qt::UserRole + 1 ).toString();
 
  490   QAbstractItemModel *oldModel = mImagesListView->model();
 
  492   mImagesListView->setModel( m );
 
  495   connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
 
  496            this, &QgsSvgSelectorWidget::svgSelectionChanged );
 
  499 void QgsSvgSelectorWidget::svgSourceChanged( 
const QString &text )
 
  502   bool validSVG = !resolvedPath.isNull();
 
  504   updateCurrentSvgPath( validSVG ? resolvedPath : text );
 
  510   mGroupsTreeView->setModel( g );
 
  512   int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
 
  513   for ( 
int i = 0; i < rows; i++ )
 
  515     mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ), 
true );
 
  519   QAbstractItemModel *oldModel = mImagesListView->model();
 
  521   mImagesListView->setModel( m );
 
  528     QDialogButtonBox::StandardButtons buttons,
 
  529     Qt::Orientation orientation )
 
  530   : QDialog( parent, fl )
 
  533   Q_UNUSED( orientation )
 
  536   mButtonBox = 
new QDialogButtonBox( buttons, orientation, 
this );
 
  537   connect( 
mButtonBox, &QDialogButtonBox::accepted, 
this, &QDialog::accept );
 
  538   connect( 
mButtonBox, &QDialogButtonBox::rejected, 
this, &QDialog::reject );
 
  540   setMinimumSize( 480, 320 );
 
  555 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
 
  556   : QAbstractTableModel( parent )
 
  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() );} );
 
  563 void QgsSvgParametersModel::setParameters( 
const QMap<QString, QgsProperty> ¶meters )
 
  567   QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
 
  568   for ( ; paramIt != parameters.constEnd(); ++paramIt )
 
  570     mParameters << Parameter( paramIt.key(), paramIt.value() );
 
  575 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
 const 
  577   QMap<QString, QgsProperty> params;
 
  578   for ( 
const Parameter ¶m : qgis::as_const( mParameters ) )
 
  580     if ( !param.name.isEmpty() )
 
  581       params.insert( param.name, param.property );
 
  586 void QgsSvgParametersModel::removeParameters( 
const QModelIndexList &indexList )
 
  588   if ( !indexList.count() )
 
  591   auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), []( 
const QModelIndex & i1, 
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
 
  593   beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
 
  594   for ( 
const QModelIndex &index : indexList )
 
  595     mParameters.removeAt( index.row() );
 
  606   mExpressionContextGenerator = generator;
 
  609 int QgsSvgParametersModel::rowCount( 
const QModelIndex &parent )
 const 
  612   return mParameters.count();
 
  615 int QgsSvgParametersModel::columnCount( 
const QModelIndex &parent )
 const 
  621 QVariant QgsSvgParametersModel::data( 
const QModelIndex &index, 
int role )
 const 
  623   QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  624   if ( role == Qt::DisplayRole )
 
  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();
 
  638 bool QgsSvgParametersModel::setData( 
const QModelIndex &index, 
const QVariant &value, 
int role )
 
  640   if ( !index.isValid() || role != Qt::EditRole )
 
  643   QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( index.column() );
 
  646     case QgsSvgParametersModel::Column::NameColumn:
 
  648       QString oldName = mParameters.at( index.row() ).name;
 
  649       QString newName = value.toString();
 
  650       for ( 
const Parameter ¶m : qgis::as_const( mParameters ) )
 
  652         if ( param.name == newName && param.name != oldName )
 
  658       mParameters[index.row()].name = newName;
 
  659       emit dataChanged( index, index );
 
  663     case QgsSvgParametersModel::Column::ExpressionColumn:
 
  665       emit dataChanged( index, index );
 
  672 QVariant QgsSvgParametersModel::headerData( 
int section, Qt::Orientation orientation, 
int role )
 const 
  674   if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
 
  676     QgsSvgParametersModel::Column col = 
static_cast<QgsSvgParametersModel::Column
>( section );
 
  679       case QgsSvgParametersModel::Column::NameColumn:
 
  681       case QgsSvgParametersModel::Column::ExpressionColumn:
 
  682         return tr( 
"Expression" );
 
  689 void QgsSvgParametersModel::addParameter()
 
  691   int c = rowCount( QModelIndex() );
 
  692   beginInsertRows( QModelIndex(), 
c, 
c );
 
  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 ) ) )
 
  698   mParameters.append( Parameter( QStringLiteral( 
"param%1" ).arg( i ), 
QgsProperty() ) );
 
  703 Qt::ItemFlags QgsSvgParametersModel::flags( 
const QModelIndex &index )
 const 
  706   return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
 
  710 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  714   const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
 
  720 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor, 
const QModelIndex &index )
 const 
  729 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, 
const QModelIndex &index )
 const 
  737 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor, 
const QStyleOptionViewItem &option, 
const QModelIndex &index )
 const 
  740   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 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.
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 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.