31#include <QAbstractListModel>
37#include <QPixmapCache>
38#include <QSortFilterProxyModel>
43#include "moc_qgssvgselectorwidget.cpp"
45using namespace Qt::StringLiterals;
50QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
54QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
59void QgsSvgSelectorLoader::run()
63 mTraversedPaths.clear();
71 if ( !mQueuedSvgs.isEmpty() )
74 emit foundSvgs( mQueuedSvgs );
79void QgsSvgSelectorLoader::stop()
87void QgsSvgSelectorLoader::loadPath(
const QString &path )
97 const auto constSvgPaths = svgPaths;
98 for (
const QString &svgPath : constSvgPaths )
103 if ( !svgPath.isEmpty() )
114 QString canonicalPath = dir.canonicalPath();
115 if ( mTraversedPaths.contains( canonicalPath ) )
118 mTraversedPaths.insert( canonicalPath );
122 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
123 for (
const QString &item : constEntryList )
128 QString newPath = dir.path() +
'/' + item;
135void QgsSvgSelectorLoader::loadImages(
const QString &path )
138 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
139 for (
const QString &item : constEntryList )
145 QString svgPath = dir.path() +
'/' + item;
149 mQueuedSvgs << svgPath;
153 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
155 emit foundSvgs( mQueuedSvgs );
161 if ( mTimerThreshold < 1000 )
162 mTimerThreshold *= 2;
173QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
177QgsSvgGroupLoader::~QgsSvgGroupLoader()
182void QgsSvgGroupLoader::run()
185 mTraversedPaths.clear();
187 while ( !mCanceled && !mParentPaths.isEmpty() )
189 QString parentPath = mParentPaths.takeFirst();
190 loadGroup( parentPath );
194void QgsSvgGroupLoader::stop()
197 while ( isRunning() )
202void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
204 QDir parentDir( parentPath );
207 QString canonicalPath = parentDir.canonicalPath();
208 if ( mTraversedPaths.contains( canonicalPath ) )
211 mTraversedPaths.insert( canonicalPath );
213 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
214 for (
const QString &item : constEntryList )
219 emit foundPath( parentPath, item );
220 mParentPaths.append( parentDir.path() +
'/' + item );
228 : QSortFilterProxyModel( parent )
231 setFilterCaseSensitivity( Qt::CaseInsensitive );
232 setSourceModel( mModel );
233 setFilterRole( Qt::UserRole );
245 : QAbstractListModel( parent )
246 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
247 , mIconSize( iconSize )
249 mSvgLoader->setPath( path );
250 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
260QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
264 double strokeWidth, fillOpacity, strokeOpacity;
265 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
266 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false, hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
268 ->
containsParams( entry, fillParam, hasDefaultFillColor, fill, fillOpacityParam, hasDefaultFillOpacity, fillOpacity, strokeParam, hasDefaultStrokeColor, stroke, strokeWidthParam, hasDefaultStrokeWidth, strokeWidth, 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 )
292 if ( !QPixmapCache::find( entry, &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 );
322 : QStandardItemModel( parent )
323 , mLoader( new QgsSvgGroupLoader( this ) )
326 QStandardItem *parentItem = invisibleRootItem();
327 QStringList parentPaths;
328 parentPaths.reserve( svgPaths.size() );
330 for (
int i = 0; i < svgPaths.size(); i++ )
332 QDir dir( svgPaths.at( i ) );
333 QStandardItem *baseGroup =
nullptr;
337 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
341 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
345 baseGroup =
new QStandardItem( dir.dirName() );
347 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
348 baseGroup->setEditable(
false );
349 baseGroup->setCheckable(
false );
351 baseGroup->setToolTip( dir.path() );
352 parentItem->appendRow( baseGroup );
353 parentPaths << svgPaths.at( i );
354 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
355 QgsDebugMsgLevel( u
"SVG base path %1: %2"_s.arg( i ).arg( baseGroup->data().toString() ), 2 );
357 mLoader->setParentPaths( parentPaths );
358 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
367void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
369 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
373 QString fullPath = parentPath +
'/' + item;
374 QStandardItem *group =
new QStandardItem( item );
375 group->setData( QVariant( fullPath ) );
376 group->setEditable(
false );
377 group->setCheckable(
false );
378 group->setToolTip( fullPath );
380 parentGroup->appendRow( group );
381 mPathItemHash.insert( fullPath, group );
393 mIconSize = std::max( 30,
static_cast<int>( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 3 ) ) );
394 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
395 mImagesListView->setUniformItemSizes(
false );
397 mGroupsTreeView->setHeaderHidden(
true );
400 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, [
this](
const QString &filterText ) {
401 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
403 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
404 mImagesListView->selectionModel()->clearSelection();
405 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
407 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
411 mParametersModel =
new QgsSvgParametersModel(
this );
412 mParametersTreeView->setModel( mParametersModel );
413 mParametersGroupBox->setVisible( mAllowParameters );
415 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
416 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
417 mParametersTreeView->header()->setStretchLastSection(
true );
418 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
419 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
420 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
423 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
424 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
425 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
426 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [
this]() {
427 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
428 if ( selectedRows.count() > 0 )
429 mParametersModel->removeParameters( selectedRows );
437 mParametersModel->setExpressionContextGenerator( generator );
438 mParametersModel->setLayer( layer );
443 mCurrentSvgPath = svgPath;
447 mImagesListView->selectionModel()->blockSignals(
true );
448 QAbstractItemModel *m = mImagesListView->model();
449 QItemSelectionModel *selModel = mImagesListView->selectionModel();
450 for (
int i = 0; i < m->rowCount(); i++ )
452 QModelIndex idx( m->index( i, 0 ) );
453 if ( m->data( idx ).toString() == svgPath )
455 selModel->select( idx, QItemSelectionModel::SelectCurrent );
456 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
457 mImagesListView->scrollTo( idx );
461 mImagesListView->selectionModel()->blockSignals(
false );
466 mParametersModel->setParameters( parameters );
471 return mCurrentSvgPath;
476 if ( mAllowParameters == allow )
479 mAllowParameters = allow;
480 mParametersGroupBox->setVisible( allow );
485 if ( mBrowserVisible == visible )
488 mBrowserVisible = visible;
489 mSvgBrowserGroupBox->setVisible( visible );
494 return mSourceLineEdit->propertyOverrideToolButton();
497void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
499 mCurrentSvgPath = svgPath;
503void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
505 QString filePath = idx.data( Qt::UserRole ).toString();
507 updateCurrentSvgPath( filePath );
510void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
512 QString path = idx.data( Qt::UserRole + 1 ).toString();
514 QAbstractItemModel *oldModel = mImagesListView->model();
515 QgsSvgSelectorFilterModel *m =
new QgsSvgSelectorFilterModel( mImagesListView, path, mIconSize );
516 mImagesListView->setModel( m );
517 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
520 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
523void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
526 bool validSVG = !resolvedPath.isNull();
528 updateCurrentSvgPath( validSVG ? resolvedPath : text );
534 mGroupsTreeView->setModel( g );
536 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
537 for (
int i = 0; i < rows; i++ )
539 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
543 QAbstractItemModel *oldModel = mImagesListView->model();
545 mImagesListView->setModel( m );
552 : QDialog( parent, fl )
555 Q_UNUSED( orientation )
558 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
559 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
560 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
562 setMinimumSize( 480, 320 );
577QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
578 : QAbstractTableModel( parent )
580 connect(
this, &QAbstractTableModel::rowsInserted,
this, [
this]() { emit parametersChanged( parameters() ); } );
581 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [
this]() { emit parametersChanged( parameters() ); } );
582 connect(
this, &QAbstractTableModel::dataChanged,
this, [
this]() { emit parametersChanged( parameters() ); } );
585void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
589 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
590 for ( ; paramIt != parameters.constEnd(); ++paramIt )
592 mParameters << Parameter( paramIt.key(), paramIt.value() );
597QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
599 QMap<QString, QgsProperty> params;
600 for (
const Parameter ¶m : std::as_const( mParameters ) )
602 if ( !param.name.isEmpty() )
603 params.insert( param.name, param.property );
608void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
610 if ( indexList.isEmpty() )
613 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex &i1,
const QModelIndex &i2 ) { return i1.row() < i2.row(); } );
615 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
616 for (
const QModelIndex &index : indexList )
617 mParameters.removeAt( index.row() );
628 mExpressionContextGenerator = generator;
631int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
634 return mParameters.count();
637int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
643QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
645 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
646 if ( role == Qt::DisplayRole )
650 case QgsSvgParametersModel::Column::NameColumn:
651 return mParameters.at( index.row() ).name;
652 case QgsSvgParametersModel::Column::ExpressionColumn:
653 return mParameters.at( index.row() ).property.expressionString();
660bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
662 if ( !index.isValid() || role != Qt::EditRole )
665 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
668 case QgsSvgParametersModel::Column::NameColumn:
670 QString oldName = mParameters.at( index.row() ).name;
671 QString newName = value.toString();
672 for (
const Parameter ¶m : std::as_const( mParameters ) )
674 if ( param.name == newName && param.name != oldName )
680 mParameters[index.row()].name = newName;
681 emit dataChanged( index, index );
685 case QgsSvgParametersModel::Column::ExpressionColumn:
687 emit dataChanged( index, index );
694QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
696 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
698 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
701 case QgsSvgParametersModel::Column::NameColumn:
703 case QgsSvgParametersModel::Column::ExpressionColumn:
704 return tr(
"Expression" );
711void QgsSvgParametersModel::addParameter()
713 int c = rowCount( QModelIndex() );
714 beginInsertRows( QModelIndex(),
c,
c );
716 QStringList currentNames;
717 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter ¶meter ) { return parameter.name; } );
718 while ( currentNames.contains( u
"param%1"_s.arg( i ) ) )
720 mParameters.append( Parameter( u
"param%1"_s.arg( i ),
QgsProperty() ) );
725Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
728 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
732QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
736 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
742void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
751void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
759void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
762 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)