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()
79 while ( isRunning() ) {}
82void QgsSvgSelectorLoader::loadPath(
const QString &path )
92 const auto constSvgPaths = svgPaths;
93 for (
const QString &svgPath : constSvgPaths )
98 if ( !svgPath.isEmpty() )
109 QString canonicalPath = dir.canonicalPath();
110 if ( mTraversedPaths.contains( canonicalPath ) )
113 mTraversedPaths.insert( canonicalPath );
117 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
118 for (
const QString &item : constEntryList )
123 QString newPath = dir.path() +
'/' + item;
130void QgsSvgSelectorLoader::loadImages(
const QString &path )
133 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
134 for (
const QString &item : constEntryList )
140 QString svgPath = dir.path() +
'/' + item;
144 mQueuedSvgs << svgPath;
148 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
150 emit foundSvgs( mQueuedSvgs );
156 if ( mTimerThreshold < 1000 )
157 mTimerThreshold *= 2;
168QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
174QgsSvgGroupLoader::~QgsSvgGroupLoader()
179void QgsSvgGroupLoader::run()
182 mTraversedPaths.clear();
184 while ( !mCanceled && !mParentPaths.isEmpty() )
186 QString parentPath = mParentPaths.takeFirst();
187 loadGroup( parentPath );
191void QgsSvgGroupLoader::stop()
194 while ( isRunning() ) {}
197void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
199 QDir parentDir( parentPath );
202 QString canonicalPath = parentDir.canonicalPath();
203 if ( mTraversedPaths.contains( canonicalPath ) )
206 mTraversedPaths.insert( canonicalPath );
208 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
209 for (
const QString &item : constEntryList )
214 emit foundPath( parentPath, item );
215 mParentPaths.append( parentDir.path() +
'/' + item );
225 : QSortFilterProxyModel( parent )
228 setFilterCaseSensitivity( Qt::CaseInsensitive );
229 setSourceModel( mModel );
230 setFilterRole( Qt::UserRole );
242 : QAbstractListModel( parent )
243 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
244 , mIconSize( iconSize )
246 mSvgLoader->setPath( path );
247 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
257QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
261 double strokeWidth, fillOpacity, strokeOpacity;
262 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
263 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false,
264 hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
266 fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
267 strokeParam, hasDefaultStrokeColor, stroke,
268 strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
269 strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
272 if ( !hasDefaultFillColor )
273 fill = QColor( 200, 200, 200 );
274 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
275 if ( !hasDefaultStrokeColor )
277 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
278 if ( !hasDefaultStrokeWidth )
283 return QPixmap::fromImage( img );
288 QString entry =
mSvgFiles.at( index.row() );
290 if ( role == Qt::DecorationRole )
293 if ( !QPixmapCache::find( entry, &pixmap ) )
295 QPixmap newPixmap = createPreview( entry );
296 QPixmapCache::insert( entry, newPixmap );
304 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
312void QgsSvgSelectorListModel::addSvgs(
const QStringList &svgs )
314 beginInsertRows( QModelIndex(),
mSvgFiles.count(),
mSvgFiles.count() + svgs.size() - 1 );
326 : QStandardItemModel( parent )
327 , mLoader( new QgsSvgGroupLoader( this ) )
330 QStandardItem *parentItem = invisibleRootItem();
331 QStringList parentPaths;
332 parentPaths.reserve( svgPaths.size() );
334 for (
int i = 0; i < svgPaths.size(); i++ )
336 QDir dir( svgPaths.at( i ) );
337 QStandardItem *baseGroup =
nullptr;
341 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
345 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
349 baseGroup =
new QStandardItem( dir.dirName() );
351 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
352 baseGroup->setEditable(
false );
353 baseGroup->setCheckable(
false );
355 baseGroup->setToolTip( dir.path() );
356 parentItem->appendRow( baseGroup );
357 parentPaths << svgPaths.at( i );
358 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
359 QgsDebugMsgLevel( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
361 mLoader->setParentPaths( parentPaths );
362 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
371void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
373 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
377 QString fullPath = parentPath +
'/' + item;
378 QStandardItem *group =
new QStandardItem( item );
379 group->setData( QVariant( fullPath ) );
380 group->setEditable(
false );
381 group->setCheckable(
false );
382 group->setToolTip( fullPath );
384 parentGroup->appendRow( group );
385 mPathItemHash.insert( fullPath, group );
397 mIconSize = std::max( 30,
static_cast< int >( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 3 ) ) );
398 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
399 mImagesListView->setUniformItemSizes(
false );
401 mGroupsTreeView->setHeaderHidden(
true );
404 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, [ = ](
const QString & filterText )
406 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
408 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
409 mImagesListView->selectionModel()->clearSelection();
410 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged, this, &QgsSvgSelectorWidget::svgSelectionChanged );
412 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
416 mParametersModel =
new QgsSvgParametersModel(
this );
417 mParametersTreeView->setModel( mParametersModel );
418 mParametersGroupBox->setVisible( mAllowParameters );
420 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
421 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
422 mParametersTreeView->header()->setStretchLastSection(
true );
423 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
424 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
425 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
428 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
429 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
430 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
431 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [ = ]()
433 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
434 if ( selectedRows.count() > 0 )
435 mParametersModel->removeParameters( selectedRows );
443 mParametersModel->setExpressionContextGenerator( generator );
444 mParametersModel->setLayer( layer );
449 mCurrentSvgPath = svgPath;
453 mImagesListView->selectionModel()->blockSignals(
true );
454 QAbstractItemModel *m = mImagesListView->model();
455 QItemSelectionModel *selModel = mImagesListView->selectionModel();
456 for (
int i = 0; i < m->rowCount(); i++ )
458 QModelIndex idx( m->index( i, 0 ) );
459 if ( m->data( idx ).toString() == svgPath )
461 selModel->select( idx, QItemSelectionModel::SelectCurrent );
462 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
463 mImagesListView->scrollTo( idx );
467 mImagesListView->selectionModel()->blockSignals(
false );
472 mParametersModel->setParameters( parameters );
477 return mCurrentSvgPath;
482 if ( mAllowParameters == allow )
485 mAllowParameters = allow;
486 mParametersGroupBox->setVisible( allow );
491 if ( mBrowserVisible == visible )
494 mBrowserVisible = visible;
495 mSvgBrowserGroupBox->setVisible( visible );
500 return mSourceLineEdit->propertyOverrideToolButton();
503void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
505 mCurrentSvgPath = svgPath;
509void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
511 QString filePath = idx.data( Qt::UserRole ).toString();
513 updateCurrentSvgPath( filePath );
516void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
518 QString path = idx.data( Qt::UserRole + 1 ).toString();
520 QAbstractItemModel *oldModel = mImagesListView->model();
522 mImagesListView->setModel( m );
523 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
526 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
527 this, &QgsSvgSelectorWidget::svgSelectionChanged );
530void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
533 bool validSVG = !resolvedPath.isNull();
535 updateCurrentSvgPath( validSVG ? resolvedPath : text );
541 mGroupsTreeView->setModel( g );
543 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
544 for (
int i = 0; i < rows; i++ )
546 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
550 QAbstractItemModel *oldModel = mImagesListView->model();
552 mImagesListView->setModel( m );
559 QDialogButtonBox::StandardButtons buttons,
560 Qt::Orientation orientation )
561 : QDialog( parent, fl )
564 Q_UNUSED( orientation )
567 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
568 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
569 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
571 setMinimumSize( 480, 320 );
586QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
587 : QAbstractTableModel( parent )
589 connect(
this, &QAbstractTableModel::rowsInserted,
this, [ = ]() {emit parametersChanged( parameters() );} );
590 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [ = ]() {emit parametersChanged( parameters() );} );
591 connect(
this, &QAbstractTableModel::dataChanged,
this, [ = ]() {emit parametersChanged( parameters() );} );
594void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
598 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
599 for ( ; paramIt != parameters.constEnd(); ++paramIt )
601 mParameters << Parameter( paramIt.key(), paramIt.value() );
606QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
608 QMap<QString, QgsProperty> params;
609 for (
const Parameter ¶m : std::as_const( mParameters ) )
611 if ( !param.name.isEmpty() )
612 params.insert( param.name, param.property );
617void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
619 if ( indexList.isEmpty() )
622 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex & i1,
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
624 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
625 for (
const QModelIndex &index : indexList )
626 mParameters.removeAt( index.row() );
637 mExpressionContextGenerator = generator;
640int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
643 return mParameters.count();
646int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
652QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
654 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
655 if ( role == Qt::DisplayRole )
659 case QgsSvgParametersModel::Column::NameColumn:
660 return mParameters.at( index.row() ).name;
661 case QgsSvgParametersModel::Column::ExpressionColumn:
662 return mParameters.at( index.row() ).property.expressionString();
669bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
671 if ( !index.isValid() || role != Qt::EditRole )
674 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
677 case QgsSvgParametersModel::Column::NameColumn:
679 QString oldName = mParameters.at( index.row() ).name;
680 QString newName = value.toString();
681 for (
const Parameter ¶m : std::as_const( mParameters ) )
683 if ( param.name == newName && param.name != oldName )
689 mParameters[index.row()].name = newName;
690 emit dataChanged( index, index );
694 case QgsSvgParametersModel::Column::ExpressionColumn:
696 emit dataChanged( index, index );
703QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
705 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
707 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
710 case QgsSvgParametersModel::Column::NameColumn:
712 case QgsSvgParametersModel::Column::ExpressionColumn:
713 return tr(
"Expression" );
720void QgsSvgParametersModel::addParameter()
722 int c = rowCount( QModelIndex() );
723 beginInsertRows( QModelIndex(),
c,
c );
725 QStringList currentNames;
726 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter & parameter ) {return parameter.name;} );
727 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
729 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
734Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
737 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
741QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
745 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
751void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
760void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
768void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
771 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)