31#include <QAbstractListModel>
32#include <QSortFilterProxyModel>
37#include <QPixmapCache>
45QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
50QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
55void QgsSvgSelectorLoader::run()
59 mTraversedPaths.clear();
67 if ( !mQueuedSvgs.isEmpty() )
70 emit foundSvgs( mQueuedSvgs );
75void QgsSvgSelectorLoader::stop()
78 while ( isRunning() ) {}
81void 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;
129void 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;
167QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
173QgsSvgGroupLoader::~QgsSvgGroupLoader()
178void QgsSvgGroupLoader::run()
181 mTraversedPaths.clear();
183 while ( !mCanceled && !mParentPaths.isEmpty() )
185 QString parentPath = mParentPaths.takeFirst();
186 loadGroup( parentPath );
190void QgsSvgGroupLoader::stop()
193 while ( isRunning() ) {}
196void 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 ) )
243 , mIconSize( iconSize )
245 mSvgLoader->setPath( path );
246 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
256QPixmap 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 )
311void 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 QgsDebugMsgLevel( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
360 mLoader->setParentPaths( parentPaths );
361 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
370void 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 );
442 mParametersModel->setExpressionContextGenerator( generator );
443 mParametersModel->setLayer( layer );
448 mCurrentSvgPath = svgPath;
452 mImagesListView->selectionModel()->blockSignals(
true );
453 QAbstractItemModel *m = mImagesListView->model();
454 QItemSelectionModel *selModel = mImagesListView->selectionModel();
455 for (
int i = 0; i < m->rowCount(); i++ )
457 QModelIndex idx( m->index( i, 0 ) );
458 if ( m->data( idx ).toString() == svgPath )
460 selModel->select( idx, QItemSelectionModel::SelectCurrent );
461 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
462 mImagesListView->scrollTo( idx );
466 mImagesListView->selectionModel()->blockSignals(
false );
471 mParametersModel->setParameters( parameters );
476 return mCurrentSvgPath;
481 if ( mAllowParameters == allow )
484 mAllowParameters = allow;
485 mParametersGroupBox->setVisible( allow );
490 if ( mBrowserVisible == visible )
493 mBrowserVisible = visible;
494 mSvgBrowserGroupBox->setVisible( visible );
499 return mSourceLineEdit->propertyOverrideToolButton();
502void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
504 mCurrentSvgPath = svgPath;
508void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
510 QString filePath = idx.data( Qt::UserRole ).toString();
512 updateCurrentSvgPath( filePath );
515void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
517 QString path = idx.data( Qt::UserRole + 1 ).toString();
519 QAbstractItemModel *oldModel = mImagesListView->model();
521 mImagesListView->setModel( m );
522 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
525 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
526 this, &QgsSvgSelectorWidget::svgSelectionChanged );
529void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
532 bool validSVG = !resolvedPath.isNull();
534 updateCurrentSvgPath( validSVG ? resolvedPath : text );
540 mGroupsTreeView->setModel( g );
542 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
543 for (
int i = 0; i < rows; i++ )
545 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
549 QAbstractItemModel *oldModel = mImagesListView->model();
551 mImagesListView->setModel( m );
558 QDialogButtonBox::StandardButtons buttons,
559 Qt::Orientation orientation )
560 : QDialog( parent, fl )
563 Q_UNUSED( orientation )
566 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
567 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
568 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
570 setMinimumSize( 480, 320 );
585QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
586 : QAbstractTableModel( parent )
588 connect(
this, &QAbstractTableModel::rowsInserted,
this, [ = ]() {emit parametersChanged( parameters() );} );
589 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [ = ]() {emit parametersChanged( parameters() );} );
590 connect(
this, &QAbstractTableModel::dataChanged,
this, [ = ]() {emit parametersChanged( parameters() );} );
593void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
597 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
598 for ( ; paramIt != parameters.constEnd(); ++paramIt )
600 mParameters << Parameter( paramIt.key(), paramIt.value() );
605QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
607 QMap<QString, QgsProperty> params;
608 for (
const Parameter ¶m : std::as_const( mParameters ) )
610 if ( !param.name.isEmpty() )
611 params.insert( param.name, param.property );
616void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
618 if ( indexList.isEmpty() )
621 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex & i1,
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
623 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
624 for (
const QModelIndex &index : indexList )
625 mParameters.removeAt( index.row() );
636 mExpressionContextGenerator = generator;
639int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
642 return mParameters.count();
645int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
651QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
653 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
654 if ( role == Qt::DisplayRole )
658 case QgsSvgParametersModel::Column::NameColumn:
659 return mParameters.at( index.row() ).name;
660 case QgsSvgParametersModel::Column::ExpressionColumn:
661 return mParameters.at( index.row() ).property.expressionString();
668bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
670 if ( !index.isValid() || role != Qt::EditRole )
673 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
676 case QgsSvgParametersModel::Column::NameColumn:
678 QString oldName = mParameters.at( index.row() ).name;
679 QString newName = value.toString();
680 for (
const Parameter ¶m : std::as_const( mParameters ) )
682 if ( param.name == newName && param.name != oldName )
688 mParameters[index.row()].name = newName;
689 emit dataChanged( index, index );
693 case QgsSvgParametersModel::Column::ExpressionColumn:
695 emit dataChanged( index, index );
702QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
704 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
706 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
709 case QgsSvgParametersModel::Column::NameColumn:
711 case QgsSvgParametersModel::Column::ExpressionColumn:
712 return tr(
"Expression" );
719void QgsSvgParametersModel::addParameter()
721 int c = rowCount( QModelIndex() );
722 beginInsertRows( QModelIndex(),
c,
c );
724 QStringList currentNames;
725 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter & parameter ) {return parameter.name;} );
726 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
728 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
733Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
736 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
740QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
744 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
750void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
759void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
767void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
770 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 data sets.
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)