31 #include <QAbstractListModel>
34 #include <QFileDialog>
35 #include <QModelIndex>
36 #include <QPixmapCache>
44 QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject *parent )
49 QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
54 void QgsSvgSelectorLoader::run()
58 mTraversedPaths.clear();
66 if ( !mQueuedSvgs.isEmpty() )
69 emit foundSvgs( mQueuedSvgs );
74 void QgsSvgSelectorLoader::stop()
77 while ( isRunning() ) {}
80 void QgsSvgSelectorLoader::loadPath(
const QString &path )
90 const auto constSvgPaths = svgPaths;
91 for (
const QString &svgPath : constSvgPaths )
96 if ( !svgPath.isEmpty() )
107 QString canonicalPath = dir.canonicalPath();
108 if ( mTraversedPaths.contains( canonicalPath ) )
111 mTraversedPaths.insert( canonicalPath );
115 const auto constEntryList = dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
116 for (
const QString &item : constEntryList )
121 QString newPath = dir.path() +
'/' + item;
128 void QgsSvgSelectorLoader::loadImages(
const QString &path )
131 const auto constEntryList = dir.entryList( QStringList(
"*.svg" ), QDir::Files );
132 for (
const QString &item : constEntryList )
138 QString svgPath = dir.path() +
'/' + item;
142 mQueuedSvgs << svgPath;
146 if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
148 emit foundSvgs( mQueuedSvgs );
154 if ( mTimerThreshold < 1000 )
155 mTimerThreshold *= 2;
166 QgsSvgGroupLoader::QgsSvgGroupLoader( QObject *parent )
172 QgsSvgGroupLoader::~QgsSvgGroupLoader()
177 void QgsSvgGroupLoader::run()
180 mTraversedPaths.clear();
182 while ( !mCanceled && !mParentPaths.isEmpty() )
184 QString parentPath = mParentPaths.takeFirst();
185 loadGroup( parentPath );
189 void QgsSvgGroupLoader::stop()
192 while ( isRunning() ) {}
195 void QgsSvgGroupLoader::loadGroup(
const QString &parentPath )
197 QDir parentDir( parentPath );
200 QString canonicalPath = parentDir.canonicalPath();
201 if ( mTraversedPaths.contains( canonicalPath ) )
204 mTraversedPaths.insert( canonicalPath );
206 const auto constEntryList = parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot );
207 for (
const QString &item : constEntryList )
212 emit foundPath( parentPath, item );
213 mParentPaths.append( parentDir.path() +
'/' + item );
224 : QAbstractListModel( parent )
225 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
228 mSvgLoader->setPath( QString() );
229 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
234 : QAbstractListModel( parent )
235 , mSvgLoader( new QgsSvgSelectorLoader( this ) )
238 mSvgLoader->setPath( path );
239 connect( mSvgLoader, &QgsSvgSelectorLoader::foundSvgs,
this, &QgsSvgSelectorListModel::addSvgs );
249 QPixmap QgsSvgSelectorListModel::createPreview(
const QString &entry )
const
253 double strokeWidth, fillOpacity, strokeOpacity;
254 bool fillParam, fillOpacityParam, strokeParam, strokeWidthParam, strokeOpacityParam;
255 bool hasDefaultFillColor =
false, hasDefaultFillOpacity =
false, hasDefaultStrokeColor =
false,
256 hasDefaultStrokeWidth =
false, hasDefaultStrokeOpacity =
false;
258 fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
259 strokeParam, hasDefaultStrokeColor, stroke,
260 strokeWidthParam, hasDefaultStrokeWidth, strokeWidth,
261 strokeOpacityParam, hasDefaultStrokeOpacity, strokeOpacity );
264 if ( !hasDefaultFillColor )
265 fill = QColor( 200, 200, 200 );
266 fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
267 if ( !hasDefaultStrokeColor )
269 stroke.setAlphaF( hasDefaultStrokeOpacity ? strokeOpacity : 1.0 );
270 if ( !hasDefaultStrokeWidth )
275 return QPixmap::fromImage( img );
280 QString entry =
mSvgFiles.at( index.row() );
282 if ( role == Qt::DecorationRole )
284 QPixmap *pixmap =
nullptr;
285 if ( !QPixmapCache::find( entry, pixmap ) || !pixmap )
287 QPixmap newPixmap = createPreview( entry );
288 QPixmapCache::insert( entry, newPixmap );
296 else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
304 void QgsSvgSelectorListModel::addSvgs(
const QStringList &svgs )
306 beginInsertRows( QModelIndex(),
mSvgFiles.count(),
mSvgFiles.count() + svgs.size() - 1 );
318 : QStandardItemModel( parent )
319 , mLoader( new QgsSvgGroupLoader( this ) )
322 QStandardItem *parentItem = invisibleRootItem();
323 QStringList parentPaths;
324 parentPaths.reserve( svgPaths.size() );
326 for (
int i = 0; i < svgPaths.size(); i++ )
328 QDir dir( svgPaths.at( i ) );
329 QStandardItem *baseGroup =
nullptr;
333 baseGroup =
new QStandardItem( tr(
"App Symbols" ) );
337 baseGroup =
new QStandardItem( tr(
"User Symbols" ) );
341 baseGroup =
new QStandardItem( dir.dirName() );
343 baseGroup->setData( QVariant( svgPaths.at( i ) ) );
344 baseGroup->setEditable(
false );
345 baseGroup->setCheckable(
false );
347 baseGroup->setToolTip( dir.path() );
348 parentItem->appendRow( baseGroup );
349 parentPaths << svgPaths.at( i );
350 mPathItemHash.insert( svgPaths.at( i ), baseGroup );
351 QgsDebugMsg( QStringLiteral(
"SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
353 mLoader->setParentPaths( parentPaths );
354 connect( mLoader, &QgsSvgGroupLoader::foundPath,
this, &QgsSvgSelectorGroupsModel::addPath );
363 void QgsSvgSelectorGroupsModel::addPath(
const QString &parentPath,
const QString &item )
365 QStandardItem *parentGroup = mPathItemHash.value( parentPath );
369 QString fullPath = parentPath +
'/' + item;
370 QStandardItem *group =
new QStandardItem( item );
371 group->setData( QVariant( fullPath ) );
372 group->setEditable(
false );
373 group->setCheckable(
false );
374 group->setToolTip( fullPath );
376 parentGroup->appendRow( group );
377 mPathItemHash.insert( fullPath, group );
391 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
392 mIconSize = std::max( 30,
static_cast< int >( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().width(
'X' ) * 3 ) ) );
394 mIconSize = std::max( 30,
static_cast< int >( std::round(
Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance(
'X' ) * 3 ) ) );
396 mImagesListView->setGridSize( QSize( mIconSize * 1.2, mIconSize * 1.2 ) );
397 mImagesListView->setUniformItemSizes(
false );
399 mGroupsTreeView->setHeaderHidden(
true );
402 mParametersModel =
new QgsSvgParametersModel(
this );
403 mParametersTreeView->setModel( mParametersModel );
404 mParametersGroupBox->setVisible( mAllowParameters );
406 mParametersTreeView->setItemDelegateForColumn(
static_cast<int>( QgsSvgParametersModel::Column::ExpressionColumn ),
new QgsSvgParameterValueDelegate(
this ) );
407 mParametersTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents );
408 mParametersTreeView->header()->setStretchLastSection(
true );
409 mParametersTreeView->setSelectionBehavior( QAbstractItemView::SelectRows );
410 mParametersTreeView->setSelectionMode( QAbstractItemView::MultiSelection );
411 mParametersTreeView->setEditTriggers( QAbstractItemView::DoubleClicked );
414 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::svgSelectionChanged );
415 connect( mGroupsTreeView->selectionModel(), &QItemSelectionModel::currentChanged,
this, &QgsSvgSelectorWidget::populateIcons );
416 connect( mAddParameterButton, &QToolButton::clicked, mParametersModel, &QgsSvgParametersModel::addParameter );
417 connect( mRemoveParameterButton, &QToolButton::clicked,
this, [ = ]()
419 const QModelIndexList selectedRows = mParametersTreeView->selectionModel()->selectedRows();
420 if ( selectedRows.count() > 0 )
421 mParametersModel->removeParameters( selectedRows );
427 mParametersModel->setExpressionContextGenerator( generator );
428 mParametersModel->setLayer( layer );
433 mCurrentSvgPath = svgPath;
437 mImagesListView->selectionModel()->blockSignals(
true );
438 QAbstractItemModel *m = mImagesListView->model();
439 QItemSelectionModel *selModel = mImagesListView->selectionModel();
440 for (
int i = 0; i < m->rowCount(); i++ )
442 QModelIndex idx( m->index( i, 0 ) );
443 if ( m->data( idx ).toString() == svgPath )
445 selModel->select( idx, QItemSelectionModel::SelectCurrent );
446 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
447 mImagesListView->scrollTo( idx );
451 mImagesListView->selectionModel()->blockSignals(
false );
456 mParametersModel->setParameters( parameters );
461 return mCurrentSvgPath;
466 if ( mAllowParameters == allow )
469 mAllowParameters = allow;
470 mParametersGroupBox->setVisible( allow );
473 void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
475 mCurrentSvgPath = svgPath;
479 void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
481 QString filePath = idx.data( Qt::UserRole ).toString();
483 updateCurrentSvgPath( filePath );
486 void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
488 QString path = idx.data( Qt::UserRole + 1 ).toString();
490 QAbstractItemModel *oldModel = mImagesListView->model();
492 mImagesListView->setModel( m );
495 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
496 this, &QgsSvgSelectorWidget::svgSelectionChanged );
499 void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
502 bool validSVG = !resolvedPath.isNull();
504 updateCurrentSvgPath( validSVG ? resolvedPath : text );
510 mGroupsTreeView->setModel( g );
512 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
513 for (
int i = 0; i < rows; i++ )
515 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
519 QAbstractItemModel *oldModel = mImagesListView->model();
521 mImagesListView->setModel( m );
528 QDialogButtonBox::StandardButtons buttons,
529 Qt::Orientation orientation )
530 : QDialog( parent, fl )
533 Q_UNUSED( orientation )
536 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
537 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
538 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
540 setMinimumSize( 480, 320 );
555 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
556 : QAbstractTableModel( parent )
558 connect(
this, &QAbstractTableModel::rowsInserted,
this, [ = ]() {emit parametersChanged( parameters() );} );
559 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [ = ]() {emit parametersChanged( parameters() );} );
560 connect(
this, &QAbstractTableModel::dataChanged,
this, [ = ]() {emit parametersChanged( parameters() );} );
563 void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
567 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
568 for ( ; paramIt != parameters.constEnd(); ++paramIt )
570 mParameters << Parameter( paramIt.key(), paramIt.value() );
575 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
577 QMap<QString, QgsProperty> params;
578 for (
const Parameter ¶m : qgis::as_const( mParameters ) )
580 if ( !param.name.isEmpty() )
581 params.insert( param.name, param.property );
586 void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
588 if ( !indexList.count() )
591 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex & i1,
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
593 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
594 for (
const QModelIndex &index : indexList )
595 mParameters.removeAt( index.row() );
606 mExpressionContextGenerator = generator;
609 int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
612 return mParameters.count();
615 int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
621 QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
623 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
624 if ( role == Qt::DisplayRole )
628 case QgsSvgParametersModel::Column::NameColumn:
629 return mParameters.at( index.row() ).name;
630 case QgsSvgParametersModel::Column::ExpressionColumn:
631 return mParameters.at( index.row() ).property.expressionString();
638 bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
640 if ( !index.isValid() || role != Qt::EditRole )
643 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
646 case QgsSvgParametersModel::Column::NameColumn:
648 QString oldName = mParameters.at( index.row() ).name;
649 QString newName = value.toString();
650 for (
const Parameter ¶m : qgis::as_const( mParameters ) )
652 if ( param.name == newName && param.name != oldName )
658 mParameters[index.row()].name = newName;
659 emit dataChanged( index, index );
663 case QgsSvgParametersModel::Column::ExpressionColumn:
665 emit dataChanged( index, index );
672 QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
674 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
676 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
679 case QgsSvgParametersModel::Column::NameColumn:
681 case QgsSvgParametersModel::Column::ExpressionColumn:
682 return tr(
"Expression" );
689 void QgsSvgParametersModel::addParameter()
691 int c = rowCount( QModelIndex() );
692 beginInsertRows( QModelIndex(),
c,
c );
694 QStringList currentNames;
695 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter & parameter ) {return parameter.name;} );
696 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
698 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
703 Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
706 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
710 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
714 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
720 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
729 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
737 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
740 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 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.
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
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 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.