25 #include "qgssettings.h"
31 #include <QAbstractListModel>
32 #include <QSortFilterProxyModel>
35 #include <QFileDialog>
36 #include <QModelIndex>
37 #include <QPixmapCache>
45 QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
50 QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
55 void QgsSvgSelectorLoader::run()
59 mTraversedPaths.clear();
67 if ( !mQueuedSvgs.isEmpty() )
70 emit foundSvgs( mQueuedSvgs );
75 void QgsSvgSelectorLoader::stop()
78 while ( isRunning() ) {}
81 void 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;
129 void 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;
167 QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
173 QgsSvgGroupLoader::~QgsSvgGroupLoader()
178 void QgsSvgGroupLoader::run()
181 mTraversedPaths.clear();
183 while ( !mCanceled && !mParentPaths.isEmpty() )
185 QString parentPath = mParentPaths.takeFirst();
186 loadGroup( parentPath );
190 void QgsSvgGroupLoader::stop()
193 while ( isRunning() ) {}
196 void 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 ) )
245 mSvgLoader->setPath( path );
246 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
256 QPixmap 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 )
311 void 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 QgsDebugMsg( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
360 mLoader->setParentPaths( parentPaths );
361 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
370 void 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 );
440 mParametersModel->setExpressionContextGenerator( generator );
441 mParametersModel->setLayer( layer );
446 mCurrentSvgPath = svgPath;
450 mImagesListView->selectionModel()->blockSignals(
true );
451 QAbstractItemModel *m = mImagesListView->model();
452 QItemSelectionModel *selModel = mImagesListView->selectionModel();
453 for (
int i = 0; i < m->rowCount(); i++ )
455 QModelIndex idx( m->index( i, 0 ) );
456 if ( m->data( idx ).toString() == svgPath )
458 selModel->select( idx, QItemSelectionModel::SelectCurrent );
459 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
460 mImagesListView->scrollTo( idx );
464 mImagesListView->selectionModel()->blockSignals(
false );
469 mParametersModel->setParameters( parameters );
474 return mCurrentSvgPath;
479 if ( mAllowParameters == allow )
482 mAllowParameters = allow;
483 mParametersGroupBox->setVisible( allow );
488 if ( mBrowserVisible == visible )
491 mBrowserVisible = visible;
492 mSvgBrowserGroupBox->setVisible( visible );
497 return mSourceLineEdit->propertyOverrideToolButton();
500 void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
502 mCurrentSvgPath = svgPath;
506 void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
508 QString filePath = idx.data( Qt::UserRole ).toString();
510 updateCurrentSvgPath( filePath );
513 void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
515 QString path = idx.data( Qt::UserRole + 1 ).toString();
517 QAbstractItemModel *oldModel = mImagesListView->model();
519 mImagesListView->setModel( m );
520 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
523 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
524 this, &QgsSvgSelectorWidget::svgSelectionChanged );
527 void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
530 bool validSVG = !resolvedPath.isNull();
532 updateCurrentSvgPath( validSVG ? resolvedPath : text );
538 mGroupsTreeView->setModel( g );
540 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
541 for (
int i = 0; i < rows; i++ )
543 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
547 QAbstractItemModel *oldModel = mImagesListView->model();
549 mImagesListView->setModel( m );
556 QDialogButtonBox::StandardButtons buttons,
557 Qt::Orientation orientation )
558 : QDialog( parent, fl )
561 Q_UNUSED( orientation )
564 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
565 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
566 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
568 setMinimumSize( 480, 320 );
583 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
584 : QAbstractTableModel( parent )
586 connect(
this, &QAbstractTableModel::rowsInserted,
this, [ = ]() {emit parametersChanged( parameters() );} );
587 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [ = ]() {emit parametersChanged( parameters() );} );
588 connect(
this, &QAbstractTableModel::dataChanged,
this, [ = ]() {emit parametersChanged( parameters() );} );
591 void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
595 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
596 for ( ; paramIt != parameters.constEnd(); ++paramIt )
598 mParameters << Parameter( paramIt.key(), paramIt.value() );
603 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
605 QMap<QString, QgsProperty> params;
606 for (
const Parameter ¶m : std::as_const( mParameters ) )
608 if ( !param.name.isEmpty() )
609 params.insert( param.name, param.property );
614 void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
616 if ( !indexList.count() )
619 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex & i1,
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
621 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
622 for (
const QModelIndex &index : indexList )
623 mParameters.removeAt( index.row() );
634 mExpressionContextGenerator = generator;
637 int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
640 return mParameters.count();
643 int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
649 QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
651 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
652 if ( role == Qt::DisplayRole )
656 case QgsSvgParametersModel::Column::NameColumn:
657 return mParameters.at( index.row() ).name;
658 case QgsSvgParametersModel::Column::ExpressionColumn:
659 return mParameters.at( index.row() ).property.expressionString();
666 bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
668 if ( !index.isValid() || role != Qt::EditRole )
671 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
674 case QgsSvgParametersModel::Column::NameColumn:
676 QString oldName = mParameters.at( index.row() ).name;
677 QString newName = value.toString();
678 for (
const Parameter ¶m : std::as_const( mParameters ) )
680 if ( param.name == newName && param.name != oldName )
686 mParameters[index.row()].name = newName;
687 emit dataChanged( index, index );
691 case QgsSvgParametersModel::Column::ExpressionColumn:
693 emit dataChanged( index, index );
700 QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
702 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
704 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
707 case QgsSvgParametersModel::Column::NameColumn:
709 case QgsSvgParametersModel::Column::ExpressionColumn:
710 return tr(
"Expression" );
717 void QgsSvgParametersModel::addParameter()
719 int c = rowCount( QModelIndex() );
720 beginInsertRows( QModelIndex(),
c,
c );
722 QStringList currentNames;
723 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter & parameter ) {return parameter.name;} );
724 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
726 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
731 Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
734 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
738 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
742 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
748 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
757 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
765 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
768 editor->setGeometry( option.rect );
static const double UI_SCALE_FACTOR
UI scaling factor.
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.
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window's toolbar icons.
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.