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 );
442 mParametersModel->setExpressionContextGenerator( generator );
443 mParametersModel->setLayer( layer );
448 mCurrentSvgPath = svgPath;
452 mImagesListView->selectionModel()->blockSignals(
true );
453 QAbstractItemModel *m = mImagesListView->model();
454 QItemSelectionModel *selModel = mImagesListView->selectionModel();
455 for (
int i = 0; i < m->rowCount(); i++ )
457 QModelIndex idx( m->index( i, 0 ) );
458 if ( m->data( idx ).toString() == svgPath )
460 selModel->select( idx, QItemSelectionModel::SelectCurrent );
461 selModel->setCurrentIndex( idx, QItemSelectionModel::SelectCurrent );
462 mImagesListView->scrollTo( idx );
466 mImagesListView->selectionModel()->blockSignals(
false );
471 mParametersModel->setParameters( parameters );
476 return mCurrentSvgPath;
481 if ( mAllowParameters == allow )
484 mAllowParameters = allow;
485 mParametersGroupBox->setVisible( allow );
490 if ( mBrowserVisible == visible )
493 mBrowserVisible = visible;
494 mSvgBrowserGroupBox->setVisible( visible );
499 return mSourceLineEdit->propertyOverrideToolButton();
502 void QgsSvgSelectorWidget::updateCurrentSvgPath(
const QString &svgPath )
504 mCurrentSvgPath = svgPath;
508 void QgsSvgSelectorWidget::svgSelectionChanged(
const QModelIndex &idx )
510 QString filePath = idx.data( Qt::UserRole ).toString();
512 updateCurrentSvgPath( filePath );
515 void QgsSvgSelectorWidget::populateIcons(
const QModelIndex &idx )
517 QString path = idx.data( Qt::UserRole + 1 ).toString();
519 QAbstractItemModel *oldModel = mImagesListView->model();
521 mImagesListView->setModel( m );
522 connect( mSvgFilterLineEdit, &QgsFilterLineEdit::textChanged, m, &QSortFilterProxyModel::setFilterFixedString );
525 connect( mImagesListView->selectionModel(), &QItemSelectionModel::currentChanged,
526 this, &QgsSvgSelectorWidget::svgSelectionChanged );
529 void QgsSvgSelectorWidget::svgSourceChanged(
const QString &text )
532 bool validSVG = !resolvedPath.isNull();
534 updateCurrentSvgPath( validSVG ? resolvedPath : text );
540 mGroupsTreeView->setModel( g );
542 int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
543 for (
int i = 0; i < rows; i++ )
545 mGroupsTreeView->setExpanded( g->indexFromItem( g->item( i ) ),
true );
549 QAbstractItemModel *oldModel = mImagesListView->model();
551 mImagesListView->setModel( m );
558 QDialogButtonBox::StandardButtons buttons,
559 Qt::Orientation orientation )
560 : QDialog( parent, fl )
563 Q_UNUSED( orientation )
566 mButtonBox =
new QDialogButtonBox( buttons, orientation,
this );
567 connect(
mButtonBox, &QDialogButtonBox::accepted,
this, &QDialog::accept );
568 connect(
mButtonBox, &QDialogButtonBox::rejected,
this, &QDialog::reject );
570 setMinimumSize( 480, 320 );
585 QgsSvgParametersModel::QgsSvgParametersModel( QObject *parent )
586 : QAbstractTableModel( parent )
588 connect(
this, &QAbstractTableModel::rowsInserted,
this, [ = ]() {emit parametersChanged( parameters() );} );
589 connect(
this, &QAbstractTableModel::rowsRemoved,
this, [ = ]() {emit parametersChanged( parameters() );} );
590 connect(
this, &QAbstractTableModel::dataChanged,
this, [ = ]() {emit parametersChanged( parameters() );} );
593 void QgsSvgParametersModel::setParameters(
const QMap<QString, QgsProperty> ¶meters )
597 QMap<QString, QgsProperty>::const_iterator paramIt = parameters.constBegin();
598 for ( ; paramIt != parameters.constEnd(); ++paramIt )
600 mParameters << Parameter( paramIt.key(), paramIt.value() );
605 QMap<QString, QgsProperty> QgsSvgParametersModel::parameters()
const
607 QMap<QString, QgsProperty> params;
608 for (
const Parameter ¶m : std::as_const( mParameters ) )
610 if ( !param.name.isEmpty() )
611 params.insert( param.name, param.property );
616 void QgsSvgParametersModel::removeParameters(
const QModelIndexList &indexList )
618 if ( indexList.isEmpty() )
621 auto mm = std::minmax_element( indexList.constBegin(), indexList.constEnd(), [](
const QModelIndex & i1,
const QModelIndex & i2 ) {return i1.row() < i2.row();} );
623 beginRemoveRows( QModelIndex(), ( *mm.first ).row(), ( *mm.second ).row() );
624 for (
const QModelIndex &index : indexList )
625 mParameters.removeAt( index.row() );
636 mExpressionContextGenerator = generator;
639 int QgsSvgParametersModel::rowCount(
const QModelIndex &parent )
const
642 return mParameters.count();
645 int QgsSvgParametersModel::columnCount(
const QModelIndex &parent )
const
651 QVariant QgsSvgParametersModel::data(
const QModelIndex &index,
int role )
const
653 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
654 if ( role == Qt::DisplayRole )
658 case QgsSvgParametersModel::Column::NameColumn:
659 return mParameters.at( index.row() ).name;
660 case QgsSvgParametersModel::Column::ExpressionColumn:
661 return mParameters.at( index.row() ).property.expressionString();
668 bool QgsSvgParametersModel::setData(
const QModelIndex &index,
const QVariant &value,
int role )
670 if ( !index.isValid() || role != Qt::EditRole )
673 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( index.column() );
676 case QgsSvgParametersModel::Column::NameColumn:
678 QString oldName = mParameters.at( index.row() ).name;
679 QString newName = value.toString();
680 for (
const Parameter ¶m : std::as_const( mParameters ) )
682 if ( param.name == newName && param.name != oldName )
688 mParameters[index.row()].name = newName;
689 emit dataChanged( index, index );
693 case QgsSvgParametersModel::Column::ExpressionColumn:
695 emit dataChanged( index, index );
702 QVariant QgsSvgParametersModel::headerData(
int section, Qt::Orientation orientation,
int role )
const
704 if ( role == Qt::DisplayRole && orientation == Qt::Horizontal )
706 QgsSvgParametersModel::Column col =
static_cast<QgsSvgParametersModel::Column
>( section );
709 case QgsSvgParametersModel::Column::NameColumn:
711 case QgsSvgParametersModel::Column::ExpressionColumn:
712 return tr(
"Expression" );
719 void QgsSvgParametersModel::addParameter()
721 int c = rowCount( QModelIndex() );
722 beginInsertRows( QModelIndex(),
c,
c );
724 QStringList currentNames;
725 std::transform( mParameters.begin(), mParameters.end(), std::back_inserter( currentNames ), [](
const Parameter & parameter ) {return parameter.name;} );
726 while ( currentNames.contains( QStringLiteral(
"param%1" ).arg( i ) ) )
728 mParameters.append( Parameter( QStringLiteral(
"param%1" ).arg( i ),
QgsProperty() ) );
733 Qt::ItemFlags QgsSvgParametersModel::flags(
const QModelIndex &index )
const
736 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
740 QWidget *QgsSvgParameterValueDelegate::createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
744 const QgsSvgParametersModel *model = qobject_cast<const QgsSvgParametersModel *>( index.model() );
750 void QgsSvgParameterValueDelegate::setEditorData( QWidget *editor,
const QModelIndex &index )
const
759 void QgsSvgParameterValueDelegate::setModelData( QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index )
const
767 void QgsSvgParameterValueDelegate::updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index )
const
770 editor->setGeometry( option.rect );