18#include "moc_qgssvgselectorwidget.cpp"
32#include <QAbstractListModel>
33#include <QSortFilterProxyModel>
38#include <QPixmapCache>
46QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
51QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
56void QgsSvgSelectorLoader::run()
60 mTraversedPaths.clear();
68 if ( !mQueuedSvgs.isEmpty() )
71 emit foundSvgs( mQueuedSvgs );
76void QgsSvgSelectorLoader::stop()
84void QgsSvgSelectorLoader::loadPath(
const QString &path )
94 const auto constSvgPaths = svgPaths;
95 for (
const QString &svgPath : constSvgPaths )
100 if ( !svgPath.isEmpty() )
111 QString canonicalPath = dir.canonicalPath();
112 if ( mTraversedPaths.contains( canonicalPath ) )
115 mTraversedPaths.insert( canonicalPath );
119 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
120 for (
const QString &item : constEntryList )
125 QString newPath = dir.path() +
'/' + item;
132void QgsSvgSelectorLoader::loadImages(
const QString &path )
135 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
136 for (
const QString &item : constEntryList )
142 QString svgPath = dir.path() +
'/' + item;
146 mQueuedSvgs << svgPath;
150 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
152 emit foundSvgs( mQueuedSvgs );
158 if ( mTimerThreshold < 1000 )
159 mTimerThreshold *= 2;
170QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
175QgsSvgGroupLoader::~QgsSvgGroupLoader()
180void QgsSvgGroupLoader::run()
183 mTraversedPaths.clear();
185 while ( !mCanceled && !mParentPaths.isEmpty() )
187 QString parentPath = mParentPaths.takeFirst();
188 loadGroup( parentPath );
192void QgsSvgGroupLoader::stop()
195 while ( isRunning() )
200void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
202 QDir parentDir( parentPath );
205 QString canonicalPath = parentDir.canonicalPath();
206 if ( mTraversedPaths.contains( canonicalPath ) )
209 mTraversedPaths.insert( canonicalPath );
211 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
212 for (
const QString &item : constEntryList )
217 emit foundPath( parentPath, item );
218 mParentPaths.append( parentDir.path() +
'/' + item );
226 : QSortFilterProxyModel( parent )
229 setFilterCaseSensitivity( Qt::CaseInsensitive );
230 setSourceModel( mModel );
231 setFilterRole( Qt::UserRole );
243 : QAbstractListModel( parent )
244 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
245 , mIconSize( iconSize )
247 mSvgLoader->setPath( path );
248 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
258QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
262 double strokeWidth, fillOpacity, strokeOpacity;
263 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
264 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false,
265 hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
266 QgsApplication::svgCache()->
containsParams( entry, fillParam, hasDefaultFillColor, fill, fillOpacityParam, hasDefaultFillOpacity, fillOpacity, strokeParam, hasDefaultStrokeColor, stroke, strokeWidthParam, hasDefaultStrokeWidth, strokeWidth, strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
269 if ( !hasDefaultFillColor )
270 fill = QColor( 200, 200, 200 );
271 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
272 if ( !hasDefaultStrokeColor )
274 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
275 if ( !hasDefaultStrokeWidth )
280 return QPixmap::fromImage( img );
285 QString entry =
mSvgFiles.at( index.row() );
287 if ( role == Qt::DecorationRole )
290 if ( !QPixmapCache::find( entry, &pixmap ) )
292 QPixmap newPixmap = createPreview( entry );
293 QPixmapCache::insert( entry, newPixmap );
301 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
309void QgsSvgSelectorListModel::addSvgs(
const QStringList &svgs )
311 beginInsertRows( QModelIndex(),
mSvgFiles.count(),
mSvgFiles.count() + svgs.size() - 1 );
320 : QStandardItemModel( parent )
321 , mLoader( new QgsSvgGroupLoader( this ) )
324 QStandardItem *parentItem = invisibleRootItem();
325 QStringList parentPaths;
326 parentPaths.reserve( svgPaths.size() );
328 for (
int i = 0; i < svgPaths.size(); i++ )
330 QDir dir( svgPaths.at( i ) );
331 QStandardItem *baseGroup =
nullptr;
335 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
339 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
343 baseGroup =
new QStandardItem( dir.dirName() );
345 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
346 baseGroup->setEditable(
false );
347 baseGroup->setCheckable(
false );
349 baseGroup->setToolTip( dir.path() );
350 parentItem->appendRow( baseGroup );
351 parentPaths << svgPaths.at( i );
352 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
353 QgsDebugMsgLevel( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
355 mLoader->setParentPaths( parentPaths );
356 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
365void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
367 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
371 QString fullPath = parentPath +
'/' + item;
372 QStandardItem *group =
new QStandardItem( item );
373 group->setData( QVariant( fullPath ) );
374 group->setEditable(
false );
375 group->setCheckable(
false );
376 group->setToolTip( fullPath );
378 parentGroup->appendRow( group );
379 mPathItemHash.insert( fullPath, group );
391 mIconSize = std::max( 30,
static_cast<int>( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 3 ) ) );
392 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
393 mImagesListView->setUniformItemSizes(
false );
395 mGroupsTreeView->setHeaderHidden(
true );
398 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, [=](
const QString &filterText ) {
399 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
401 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
402 mImagesListView->selectionModel()->clearSelection();
403 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
405 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
409 mParametersModel =
new QgsSvgParametersModel(
this );
410 mParametersTreeView->setModel( mParametersModel );
411 mParametersGroupBox->setVisible( mAllowParameters );
413 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
414 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
415 mParametersTreeView->header()->setStretchLastSection(
true );
416 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
417 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
418 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
421 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
422 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
423 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
424 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [=]() {
425 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
426 if ( selectedRows.count() > 0 )
427 mParametersModel->removeParameters( selectedRows );
435 mParametersModel->setExpressionContextGenerator( generator );
436 mParametersModel->setLayer( layer );
441 mCurrentSvgPath = svgPath;
445 mImagesListView->selectionModel()->blockSignals(
true );
446 QAbstractItemModel *m = mImagesListView->model();
447 QItemSelectionModel *selModel = mImagesListView->selectionModel();
448 for (
int i = 0; i < m->rowCount(); i++ )
450 QModelIndex idx( m->index( i, 0 ) );
451 if ( m->data( idx ).toString() == svgPath )
453 selModel->select( idx, QItemSelectionModel::SelectCurrent );
454 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
455 mImagesListView->scrollTo( idx );
459 mImagesListView->selectionModel()->blockSignals(
false );
464 mParametersModel->setParameters( parameters );
469 return mCurrentSvgPath;
474 if ( mAllowParameters == allow )
477 mAllowParameters = allow;
478 mParametersGroupBox->setVisible( allow );
483 if ( mBrowserVisible == visible )
486 mBrowserVisible = visible;
487 mSvgBrowserGroupBox->setVisible( visible );
492 return mSourceLineEdit->propertyOverrideToolButton();
495void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
497 mCurrentSvgPath = svgPath;
501void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
503 QString filePath = idx.data( Qt::UserRole ).toString();
505 updateCurrentSvgPath( filePath );
508void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
510 QString path = idx.data( Qt::UserRole + 1 ).toString();
512 QAbstractItemModel *oldModel = mImagesListView->model();
514 mImagesListView->setModel( m );
515 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
518 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
521void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
524 bool validSVG = !resolvedPath.isNull();
526 updateCurrentSvgPath( validSVG ? resolvedPath : text );
532 mGroupsTreeView->setModel( g );
534 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
535 for (
int i = 0; i < rows; i++ )
537 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
541 QAbstractItemModel *oldModel = mImagesListView->model();
543 mImagesListView->setModel( m );
550 : QDialog( parent, fl )
553 Q_UNUSED( orientation )
556 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
557 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
558 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
560 setMinimumSize( 480, 320 );
575QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
576 : QAbstractTableModel( parent )
578 connect(
this, &QAbstractTableModel::rowsInserted,
this, [=]() { emit parametersChanged( parameters() ); } );
579 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [=]() { emit parametersChanged( parameters() ); } );
580 connect(
this, &QAbstractTableModel::dataChanged,
this, [=]() { emit parametersChanged( parameters() ); } );
583void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
587 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
588 for ( ; paramIt != parameters.constEnd(); ++paramIt )
590 mParameters << Parameter( paramIt.key(), paramIt.value() );
595QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
597 QMap<QString, QgsProperty> params;
598 for (
const Parameter ¶m : std::as_const( mParameters ) )
600 if ( !param.name.isEmpty() )
601 params.insert( param.name, param.property );
606void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
608 if ( indexList.isEmpty() )
611 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex &i1,
const QModelIndex &i2 ) { return i1.row() < i2.row(); } );
613 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
614 for (
const QModelIndex &index : indexList )
615 mParameters.removeAt( index.row() );
626 mExpressionContextGenerator = generator;
629int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
632 return mParameters.count();
635int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
641QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
643 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
644 if ( role == Qt::DisplayRole )
648 case QgsSvgParametersModel::Column::NameColumn:
649 return mParameters.at( index.row() ).name;
650 case QgsSvgParametersModel::Column::ExpressionColumn:
651 return mParameters.at( index.row() ).property.expressionString();
658bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
660 if ( !index.isValid() || role != Qt::EditRole )
663 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
666 case QgsSvgParametersModel::Column::NameColumn:
668 QString oldName = mParameters.at( index.row() ).name;
669 QString newName = value.toString();
670 for (
const Parameter ¶m : std::as_const( mParameters ) )
672 if ( param.name == newName && param.name != oldName )
678 mParameters[index.row()].name = newName;
679 emit dataChanged( index, index );
683 case QgsSvgParametersModel::Column::ExpressionColumn:
685 emit dataChanged( index, index );
692QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
694 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
696 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
699 case QgsSvgParametersModel::Column::NameColumn:
701 case QgsSvgParametersModel::Column::ExpressionColumn:
702 return tr(
"Expression" );
709void QgsSvgParametersModel::addParameter()
711 int c = rowCount( QModelIndex() );
712 beginInsertRows( QModelIndex(),
c,
c );
714 QStringList currentNames;
715 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter ¶meter ) { return parameter.name; } );
716 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
718 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
723Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
726 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
730QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
734 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
740void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
749void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
757void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
760 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)