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 );