31#include <QAbstractListModel>
37#include <QPixmapCache>
38#include <QSortFilterProxyModel>
42#include "moc_qgssvgselectorwidget.cpp"
47QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
52QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
57void QgsSvgSelectorLoader::run()
61 mTraversedPaths.clear();
69 if ( !mQueuedSvgs.isEmpty() )
72 emit foundSvgs( mQueuedSvgs );
77void QgsSvgSelectorLoader::stop()
85void QgsSvgSelectorLoader::loadPath(
const QString &path )
95 const auto constSvgPaths = svgPaths;
96 for (
const QString &svgPath : constSvgPaths )
101 if ( !svgPath.isEmpty() )
112 QString canonicalPath = dir.canonicalPath();
113 if ( mTraversedPaths.contains( canonicalPath ) )
116 mTraversedPaths.insert( canonicalPath );
120 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
121 for (
const QString &item : constEntryList )
126 QString newPath = dir.path() +
'/' + item;
133void QgsSvgSelectorLoader::loadImages(
const QString &path )
136 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
137 for (
const QString &item : constEntryList )
143 QString svgPath = dir.path() +
'/' + item;
147 mQueuedSvgs << svgPath;
151 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
153 emit foundSvgs( mQueuedSvgs );
159 if ( mTimerThreshold < 1000 )
160 mTimerThreshold *= 2;
171QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
176QgsSvgGroupLoader::~QgsSvgGroupLoader()
181void QgsSvgGroupLoader::run()
184 mTraversedPaths.clear();
186 while ( !mCanceled && !mParentPaths.isEmpty() )
188 QString parentPath = mParentPaths.takeFirst();
189 loadGroup( parentPath );
193void QgsSvgGroupLoader::stop()
196 while ( isRunning() )
201void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
203 QDir parentDir( parentPath );
206 QString canonicalPath = parentDir.canonicalPath();
207 if ( mTraversedPaths.contains( canonicalPath ) )
210 mTraversedPaths.insert( canonicalPath );
212 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
213 for (
const QString &item : constEntryList )
218 emit foundPath( parentPath, item );
219 mParentPaths.append( parentDir.path() +
'/' + item );
227 : QSortFilterProxyModel( parent )
230 setFilterCaseSensitivity( Qt::CaseInsensitive );
231 setSourceModel( mModel );
232 setFilterRole( Qt::UserRole );
244 : QAbstractListModel( parent )
245 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
246 , mIconSize( iconSize )
248 mSvgLoader->setPath( path );
249 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
259QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
263 double strokeWidth, fillOpacity, strokeOpacity;
264 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
265 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false,
266 hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
267 QgsApplication::svgCache()->
containsParams( entry, fillParam, hasDefaultFillColor, fill, fillOpacityParam, hasDefaultFillOpacity, fillOpacity, strokeParam, hasDefaultStrokeColor, stroke, strokeWidthParam, hasDefaultStrokeWidth, strokeWidth, strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
270 if ( !hasDefaultFillColor )
271 fill = QColor( 200, 200, 200 );
272 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
273 if ( !hasDefaultStrokeColor )
275 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
276 if ( !hasDefaultStrokeWidth )
281 return QPixmap::fromImage( img );
286 QString entry =
mSvgFiles.at( index.row() );
288 if ( role == Qt::DecorationRole )
291 if ( !QPixmapCache::find( entry, &pixmap ) )
293 QPixmap newPixmap = createPreview( entry );
294 QPixmapCache::insert( entry, newPixmap );
302 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
310void QgsSvgSelectorListModel::addSvgs(
const QStringList &svgs )
312 beginInsertRows( QModelIndex(),
mSvgFiles.count(),
mSvgFiles.count() + svgs.size() - 1 );
321 : QStandardItemModel( parent )
322 , mLoader( new QgsSvgGroupLoader( this ) )
325 QStandardItem *parentItem = invisibleRootItem();
326 QStringList parentPaths;
327 parentPaths.reserve( svgPaths.size() );
329 for (
int i = 0; i < svgPaths.size(); i++ )
331 QDir dir( svgPaths.at( i ) );
332 QStandardItem *baseGroup =
nullptr;
336 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
340 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
344 baseGroup =
new QStandardItem( dir.dirName() );
346 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
347 baseGroup->setEditable(
false );
348 baseGroup->setCheckable(
false );
350 baseGroup->setToolTip( dir.path() );
351 parentItem->appendRow( baseGroup );
352 parentPaths << svgPaths.at( i );
353 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
354 QgsDebugMsgLevel( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ), 2 );
356 mLoader->setParentPaths( parentPaths );
357 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
366void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
368 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
372 QString fullPath = parentPath +
'/' + item;
373 QStandardItem *group =
new QStandardItem( item );
374 group->setData( QVariant( fullPath ) );
375 group->setEditable(
false );
376 group->setCheckable(
false );
377 group->setToolTip( fullPath );
379 parentGroup->appendRow( group );
380 mPathItemHash.insert( fullPath, group );
392 mIconSize = std::max( 30,
static_cast<int>( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 3 ) ) );
393 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
394 mImagesListView->setUniformItemSizes(
false );
396 mGroupsTreeView->setHeaderHidden(
true );
399 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged,
this, [
this](
const QString &filterText ) {
400 if ( !mImagesListView->selectionModel()->selectedIndexes().isEmpty() )
402 disconnect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
403 mImagesListView->selectionModel()->clearSelection();
404 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
406 qobject_cast<QgsSvgSelectorFilterModel *>( mImagesListView->model() )->setFilterFixedString( filterText );
410 mParametersModel =
new QgsSvgParametersModel(
this );
411 mParametersTreeView->setModel( mParametersModel );
412 mParametersGroupBox->setVisible( mAllowParameters );
414 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
415 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
416 mParametersTreeView->header()->setStretchLastSection(
true );
417 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
418 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
419 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
422 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
423 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
424 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
425 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [
this]() {
426 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
427 if ( selectedRows.count() > 0 )
428 mParametersModel->removeParameters( selectedRows );
436 mParametersModel->setExpressionContextGenerator( generator );
437 mParametersModel->setLayer( layer );
442 mCurrentSvgPath = svgPath;
446 mImagesListView->selectionModel()->blockSignals(
true );
447 QAbstractItemModel *m = mImagesListView->model();
448 QItemSelectionModel *selModel = mImagesListView->selectionModel();
449 for (
int i = 0; i < m->rowCount(); i++ )
451 QModelIndex idx( m->index( i, 0 ) );
452 if ( m->data( idx ).toString() == svgPath )
454 selModel->select( idx, QItemSelectionModel::SelectCurrent );
455 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
456 mImagesListView->scrollTo( idx );
460 mImagesListView->selectionModel()->blockSignals(
false );
465 mParametersModel->setParameters( parameters );
470 return mCurrentSvgPath;
475 if ( mAllowParameters == allow )
478 mAllowParameters = allow;
479 mParametersGroupBox->setVisible( allow );
484 if ( mBrowserVisible == visible )
487 mBrowserVisible = visible;
488 mSvgBrowserGroupBox->setVisible( visible );
493 return mSourceLineEdit->propertyOverrideToolButton();
496void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
498 mCurrentSvgPath = svgPath;
502void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
504 QString filePath = idx.data( Qt::UserRole ).toString();
506 updateCurrentSvgPath( filePath );
509void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
511 QString path = idx.data( Qt::UserRole + 1 ).toString();
513 QAbstractItemModel *oldModel = mImagesListView->model();
514 QgsSvgSelectorFilterModel *m =
new QgsSvgSelectorFilterModel( mImagesListView, path, mIconSize );
515 mImagesListView->setModel( m );
516 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
519 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
522void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
525 bool validSVG = !resolvedPath.isNull();
527 updateCurrentSvgPath( validSVG ? resolvedPath : text );
533 mGroupsTreeView->setModel( g );
535 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
536 for (
int i = 0; i < rows; i++ )
538 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
542 QAbstractItemModel *oldModel = mImagesListView->model();
544 mImagesListView->setModel( m );
551 : QDialog( parent, fl )
554 Q_UNUSED( orientation )
557 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
558 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
559 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
561 setMinimumSize( 480, 320 );
576QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
577 : QAbstractTableModel( parent )
579 connect(
this, &QAbstractTableModel::rowsInserted,
this, [
this]() { emit parametersChanged( parameters() ); } );
580 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [
this]() { emit parametersChanged( parameters() ); } );
581 connect(
this, &QAbstractTableModel::dataChanged,
this, [
this]() { emit parametersChanged( parameters() ); } );
584void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
588 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
589 for ( ; paramIt != parameters.constEnd(); ++paramIt )
591 mParameters << Parameter( paramIt.key(), paramIt.value() );
596QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
598 QMap<QString, QgsProperty> params;
599 for (
const Parameter ¶m : std::as_const( mParameters ) )
601 if ( !param.name.isEmpty() )
602 params.insert( param.name, param.property );
607void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
609 if ( indexList.isEmpty() )
612 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex &i1,
const QModelIndex &i2 ) { return i1.row() < i2.row(); } );
614 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
615 for (
const QModelIndex &index : indexList )
616 mParameters.removeAt( index.row() );
627 mExpressionContextGenerator = generator;
630int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
633 return mParameters.count();
636int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
642QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
644 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
645 if ( role == Qt::DisplayRole )
649 case QgsSvgParametersModel::Column::NameColumn:
650 return mParameters.at( index.row() ).name;
651 case QgsSvgParametersModel::Column::ExpressionColumn:
652 return mParameters.at( index.row() ).property.expressionString();
659bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
661 if ( !index.isValid() || role != Qt::EditRole )
664 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
667 case QgsSvgParametersModel::Column::NameColumn:
669 QString oldName = mParameters.at( index.row() ).name;
670 QString newName = value.toString();
671 for (
const Parameter ¶m : std::as_const( mParameters ) )
673 if ( param.name == newName && param.name != oldName )
679 mParameters[index.row()].name = newName;
680 emit dataChanged( index, index );
684 case QgsSvgParametersModel::Column::ExpressionColumn:
686 emit dataChanged( index, index );
693QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
695 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
697 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
700 case QgsSvgParametersModel::Column::NameColumn:
702 case QgsSvgParametersModel::Column::ExpressionColumn:
703 return tr(
"Expression" );
710void QgsSvgParametersModel::addParameter()
712 int c = rowCount( QModelIndex() );
713 beginInsertRows( QModelIndex(),
c,
c );
715 QStringList currentNames;
716 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter ¶meter ) { return parameter.name; } );
717 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
719 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
724Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
727 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
731QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
735 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
741void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
750void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
758void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
761 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)