31#include <QAbstractListModel>
37#include <QPixmapCache>
38#include <QSortFilterProxyModel>
43#include "moc_qgssvgselectorwidget.cpp"
45using namespace Qt::StringLiterals;
50QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
55QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
60void QgsSvgSelectorLoader::run()
64 mTraversedPaths.clear();
72 if ( !mQueuedSvgs.isEmpty() )
75 emit foundSvgs( mQueuedSvgs );
80void QgsSvgSelectorLoader::stop()
88void QgsSvgSelectorLoader::loadPath(
const QString &path )
98 const auto constSvgPaths = svgPaths;
99 for (
const QString &svgPath : constSvgPaths )
104 if ( !svgPath.isEmpty() )
115 QString canonicalPath = dir.canonicalPath();
116 if ( mTraversedPaths.contains( canonicalPath ) )
119 mTraversedPaths.insert( canonicalPath );
123 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
124 for (
const QString &item : constEntryList )
129 QString newPath = dir.path() +
'/' + item;
136void QgsSvgSelectorLoader::loadImages(
const QString &path )
139 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
140 for (
const QString &item : constEntryList )
146 QString svgPath = dir.path() +
'/' + item;
150 mQueuedSvgs << svgPath;
154 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
156 emit foundSvgs( mQueuedSvgs );
162 if ( mTimerThreshold < 1000 )
163 mTimerThreshold *= 2;
174QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
179QgsSvgGroupLoader::~QgsSvgGroupLoader()
184void QgsSvgGroupLoader::run()
187 mTraversedPaths.clear();
189 while ( !mCanceled && !mParentPaths.isEmpty() )
191 QString parentPath = mParentPaths.takeFirst();
192 loadGroup( parentPath );
196void QgsSvgGroupLoader::stop()
199 while ( isRunning() )
204void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
206 QDir parentDir( parentPath );
209 QString canonicalPath = parentDir.canonicalPath();
210 if ( mTraversedPaths.contains( canonicalPath ) )
213 mTraversedPaths.insert( canonicalPath );
215 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
216 for (
const QString &item : constEntryList )
221 emit foundPath( parentPath, item );
222 mParentPaths.append( parentDir.path() +
'/' + item );
230 : QSortFilterProxyModel( parent )
233 setFilterCaseSensitivity( Qt::CaseInsensitive );
234 setSourceModel( mModel );
235 setFilterRole( Qt::UserRole );
247 : QAbstractListModel( parent )
248 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
249 , mIconSize( iconSize )
251 mSvgLoader->setPath( path );
252 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
262QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
266 double strokeWidth, fillOpacity, strokeOpacity;
267 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
268 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false,
269 hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
270 QgsApplication::svgCache()->
containsParams( entry, fillParam, hasDefaultFillColor, fill, fillOpacityParam, hasDefaultFillOpacity, fillOpacity, strokeParam, hasDefaultStrokeColor, stroke, strokeWidthParam, hasDefaultStrokeWidth, strokeWidth, strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
273 if ( !hasDefaultFillColor )
274 fill = QColor( 200, 200, 200 );
275 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
276 if ( !hasDefaultStrokeColor )
278 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
279 if ( !hasDefaultStrokeWidth )
284 return QPixmap::fromImage( img );
289 QString entry =
mSvgFiles.at( index.row() );
291 if ( role == Qt::DecorationRole )
294 if ( !QPixmapCache::find( entry, &pixmap ) )
296 QPixmap newPixmap = createPreview( entry );
297 QPixmapCache::insert( entry, newPixmap );
305 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
313void QgsSvgSelectorListModel::addSvgs(
const QStringList &svgs )
315 beginInsertRows( QModelIndex(),
mSvgFiles.count(),
mSvgFiles.count() + svgs.size() - 1 );
324 : QStandardItemModel( parent )
325 , mLoader( new QgsSvgGroupLoader( this ) )
328 QStandardItem *parentItem = invisibleRootItem();
329 QStringList parentPaths;
330 parentPaths.reserve( svgPaths.size() );
332 for (
int i = 0; i < svgPaths.size(); i++ )
334 QDir dir( svgPaths.at( i ) );
335 QStandardItem *baseGroup =
nullptr;
339 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
343 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
347 baseGroup =
new QStandardItem( dir.dirName() );
349 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
350 baseGroup->setEditable(
false );
351 baseGroup->setCheckable(
false );
353 baseGroup->setToolTip( dir.path() );
354 parentItem->appendRow( baseGroup );
355 parentPaths << svgPaths.at( i );
356 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
357 QgsDebugMsgLevel( u
"SVG base path %1: %2"_s.arg( i ).arg( baseGroup->data().toString() ), 2 );
359 mLoader->setParentPaths( parentPaths );
360 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
369void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
371 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
375 QString fullPath = parentPath +
'/' + item;
376 QStandardItem *group =
new QStandardItem( item );
377 group->setData( QVariant( fullPath ) );
378 group->setEditable(
false );
379 group->setCheckable(
false );
380 group->setToolTip( fullPath );
382 parentGroup->appendRow( group );
383 mPathItemHash.insert( fullPath, group );
395 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 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, [
this](
const QString &filterText ) {
403 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
405 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
406 mImagesListView->selectionModel()->clearSelection();
407 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
409 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
413 mParametersModel =
new QgsSvgParametersModel(
this );
414 mParametersTreeView->setModel( mParametersModel );
415 mParametersGroupBox->setVisible( mAllowParameters );
417 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
418 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
419 mParametersTreeView->header()->setStretchLastSection(
true );
420 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
421 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
422 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
425 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
426 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
427 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
428 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [
this]() {
429 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
430 if ( selectedRows.count() > 0 )
431 mParametersModel->removeParameters( selectedRows );
439 mParametersModel->setExpressionContextGenerator( generator );
440 mParametersModel->setLayer( layer );
445 mCurrentSvgPath = svgPath;
449 mImagesListView->selectionModel()->blockSignals(
true );
450 QAbstractItemModel *m = mImagesListView->model();
451 QItemSelectionModel *selModel = mImagesListView->selectionModel();
452 for (
int i = 0; i < m->rowCount(); i++ )
454 QModelIndex idx( m->index( i, 0 ) );
455 if ( m->data( idx ).toString() == svgPath )
457 selModel->select( idx, QItemSelectionModel::SelectCurrent );
458 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
459 mImagesListView->scrollTo( idx );
463 mImagesListView->selectionModel()->blockSignals(
false );
468 mParametersModel->setParameters( parameters );
473 return mCurrentSvgPath;
478 if ( mAllowParameters == allow )
481 mAllowParameters = allow;
482 mParametersGroupBox->setVisible( allow );
487 if ( mBrowserVisible == visible )
490 mBrowserVisible = visible;
491 mSvgBrowserGroupBox->setVisible( visible );
496 return mSourceLineEdit->propertyOverrideToolButton();
499void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
501 mCurrentSvgPath = svgPath;
505void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
507 QString filePath = idx.data( Qt::UserRole ).toString();
509 updateCurrentSvgPath( filePath );
512void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
514 QString path = idx.data( Qt::UserRole + 1 ).toString();
516 QAbstractItemModel *oldModel = mImagesListView->model();
517 QgsSvgSelectorFilterModel *m =
new QgsSvgSelectorFilterModel( mImagesListView, path, mIconSize );
518 mImagesListView->setModel( m );
519 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
522 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
525void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
528 bool validSVG = !resolvedPath.isNull();
530 updateCurrentSvgPath( validSVG ? resolvedPath : text );
536 mGroupsTreeView->setModel( g );
538 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
539 for (
int i = 0; i < rows; i++ )
541 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
545 QAbstractItemModel *oldModel = mImagesListView->model();
547 mImagesListView->setModel( m );
554 : QDialog( parent, fl )
557 Q_UNUSED( orientation )
560 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
561 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
562 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
564 setMinimumSize( 480, 320 );
579QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
580 : QAbstractTableModel( parent )
582 connect(
this, &QAbstractTableModel::rowsInserted,
this, [
this]() { emit parametersChanged( parameters() ); } );
583 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [
this]() { emit parametersChanged( parameters() ); } );
584 connect(
this, &QAbstractTableModel::dataChanged,
this, [
this]() { emit parametersChanged( parameters() ); } );
587void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
591 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
592 for ( ; paramIt != parameters.constEnd(); ++paramIt )
594 mParameters << Parameter( paramIt.key(), paramIt.value() );
599QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
601 QMap<QString, QgsProperty> params;
602 for (
const Parameter ¶m : std::as_const( mParameters ) )
604 if ( !param.name.isEmpty() )
605 params.insert( param.name, param.property );
610void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
612 if ( indexList.isEmpty() )
615 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex &i1,
const QModelIndex &i2 ) { return i1.row() < i2.row(); } );
617 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
618 for (
const QModelIndex &index : indexList )
619 mParameters.removeAt( index.row() );
630 mExpressionContextGenerator = generator;
633int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
636 return mParameters.count();
639int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
645QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
647 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
648 if ( role == Qt::DisplayRole )
652 case QgsSvgParametersModel::Column::NameColumn:
653 return mParameters.at( index.row() ).name;
654 case QgsSvgParametersModel::Column::ExpressionColumn:
655 return mParameters.at( index.row() ).property.expressionString();
662bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
664 if ( !index.isValid() || role != Qt::EditRole )
667 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
670 case QgsSvgParametersModel::Column::NameColumn:
672 QString oldName = mParameters.at( index.row() ).name;
673 QString newName = value.toString();
674 for (
const Parameter ¶m : std::as_const( mParameters ) )
676 if ( param.name == newName && param.name != oldName )
682 mParameters[index.row()].name = newName;
683 emit dataChanged( index, index );
687 case QgsSvgParametersModel::Column::ExpressionColumn:
689 emit dataChanged( index, index );
696QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
698 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
700 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
703 case QgsSvgParametersModel::Column::NameColumn:
705 case QgsSvgParametersModel::Column::ExpressionColumn:
706 return tr(
"Expression" );
713void QgsSvgParametersModel::addParameter()
715 int c = rowCount( QModelIndex() );
716 beginInsertRows( QModelIndex(),
c,
c );
718 QStringList currentNames;
719 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter ¶meter ) { return parameter.name; } );
720 while ( currentNames.contains( u
"param%1"_s.arg( i ) ) )
722 mParameters.append( Parameter( u
"param%1"_s.arg( i ),
QgsProperty() ) );
727Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
730 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
734QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
738 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
744void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
753void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
761void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
764 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)