mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
Fix SVG preview blocks QGIS (fix #14255)
Now SVG preview loading occurs in a background thread so that dialogs can open instantly
This commit is contained in:
parent
d3f8763bd9
commit
c60c4f7f0c
@ -715,7 +715,7 @@ QStringList QgsApplication::svgPaths()
|
||||
}
|
||||
|
||||
myPathList << ABISYM( mDefaultSvgPaths );
|
||||
return myPathList;
|
||||
return myPathList.toSet().toList();
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -32,29 +32,221 @@
|
||||
#include <QStyle>
|
||||
#include <QTime>
|
||||
|
||||
// QgsSvgSelectorLoader
|
||||
|
||||
//--- QgsSvgSelectorListModel
|
||||
///@cond PRIVATE
|
||||
QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject* parent )
|
||||
: QThread( parent )
|
||||
, mCancelled( false )
|
||||
, mTimerThreshold( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void QgsSvgSelectorLoader::run()
|
||||
{
|
||||
mCancelled = false;
|
||||
mQueuedSvgs.clear();
|
||||
|
||||
// start with a small initial timeout (ms)
|
||||
mTimerThreshold = 10;
|
||||
mTimer.start();
|
||||
|
||||
loadPath( mPath );
|
||||
|
||||
if ( !mQueuedSvgs.isEmpty() )
|
||||
{
|
||||
// make sure we notify model of any remaining queued svgs (ie svgs added since last foundSvgs() signal was emitted)
|
||||
emit foundSvgs( mQueuedSvgs );
|
||||
}
|
||||
mQueuedSvgs.clear();
|
||||
}
|
||||
|
||||
void QgsSvgSelectorLoader::stop()
|
||||
{
|
||||
mCancelled = true;
|
||||
while ( isRunning() ) {}
|
||||
}
|
||||
|
||||
void QgsSvgSelectorLoader::loadPath( const QString& path )
|
||||
{
|
||||
if ( mCancelled )
|
||||
return;
|
||||
|
||||
// QgsDebugMsg( QString( "loading path: %1" ).arg( path ) );
|
||||
|
||||
if ( path.isEmpty() )
|
||||
{
|
||||
QStringList svgPaths = QgsApplication::svgPaths();
|
||||
Q_FOREACH ( const QString& svgPath, svgPaths )
|
||||
{
|
||||
if ( mCancelled )
|
||||
return;
|
||||
|
||||
loadPath( svgPath );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
loadImages( path );
|
||||
|
||||
QDir dir( path );
|
||||
Q_FOREACH ( const QString& item, dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
|
||||
{
|
||||
if ( mCancelled )
|
||||
return;
|
||||
|
||||
QString newPath = dir.path() + '/' + item;
|
||||
loadPath( newPath );
|
||||
// QgsDebugMsg( QString( "added path: %1" ).arg( newPath ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSvgSelectorLoader::loadImages( const QString& path )
|
||||
{
|
||||
QDir dir( path );
|
||||
Q_FOREACH ( const QString& item, dir.entryList( QStringList( "*.svg" ), QDir::Files ) )
|
||||
{
|
||||
if ( mCancelled )
|
||||
return;
|
||||
|
||||
// TODO test if it is correct SVG
|
||||
QString svgPath = dir.path() + '/' + item;
|
||||
// QgsDebugMsg( QString( "adding svg: %1" ).arg( svgPath ) );
|
||||
|
||||
// add it to the list of queued SVGs
|
||||
mQueuedSvgs << svgPath;
|
||||
|
||||
// we need to avoid spamming the model with notifications about new svgs, so foundSvgs
|
||||
// is only emitted for blocks of SVGs (otherwise the view goes all flickery)
|
||||
if ( mTimer.elapsed() > mTimerThreshold )
|
||||
{
|
||||
emit foundSvgs( mQueuedSvgs );
|
||||
mQueuedSvgs.clear();
|
||||
|
||||
// increase the timer threshold - this ensures that the first lots of svgs loaded are added
|
||||
// to the view quickly, but as the list grows new svgs are added at a slower rate.
|
||||
// ie, good for initial responsiveness but avoid being spammy as the list grows.
|
||||
if ( mTimerThreshold < 1000 )
|
||||
mTimerThreshold *= 2;
|
||||
mTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsSvgGroupLoader
|
||||
//
|
||||
|
||||
QgsSvgGroupLoader::QgsSvgGroupLoader( QObject* parent )
|
||||
: QThread( parent )
|
||||
, mCancelled( false )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QgsSvgGroupLoader::~QgsSvgGroupLoader()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void QgsSvgGroupLoader::run()
|
||||
{
|
||||
mCancelled = false;
|
||||
|
||||
while ( !mCancelled && !mParentPaths.isEmpty() )
|
||||
{
|
||||
QString parentPath = mParentPaths.takeFirst();
|
||||
loadGroup( parentPath );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSvgGroupLoader::stop()
|
||||
{
|
||||
mCancelled = true;
|
||||
while ( isRunning() ) {}
|
||||
}
|
||||
|
||||
void QgsSvgGroupLoader::loadGroup( const QString& parentPath )
|
||||
{
|
||||
QDir parentDir( parentPath );
|
||||
|
||||
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
|
||||
{
|
||||
if ( mCancelled )
|
||||
return;
|
||||
|
||||
emit foundPath( parentPath, item );
|
||||
mParentPaths.append( parentDir.path() + '/' + item );
|
||||
}
|
||||
}
|
||||
|
||||
///@endcond
|
||||
|
||||
//,
|
||||
// QgsSvgSelectorListModel
|
||||
//
|
||||
|
||||
QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject* parent )
|
||||
: QAbstractListModel( parent )
|
||||
, mSvgLoader( new QgsSvgSelectorLoader( this ) )
|
||||
{
|
||||
mSvgFiles = QgsSymbolLayerUtils::listSvgFiles();
|
||||
mSvgLoader->setPath( QString() );
|
||||
connect( mSvgLoader, SIGNAL( foundSvgs( QStringList ) ), this, SLOT( addSvgs( QStringList ) ) );
|
||||
mSvgLoader->start();
|
||||
}
|
||||
|
||||
// Constructor to create model for icons in a specific path
|
||||
QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject* parent, const QString& path )
|
||||
: QAbstractListModel( parent )
|
||||
, mSvgLoader( new QgsSvgSelectorLoader( this ) )
|
||||
{
|
||||
mSvgFiles = QgsSymbolLayerUtils::listSvgFilesAt( path );
|
||||
mSvgLoader->setPath( path );
|
||||
connect( mSvgLoader, SIGNAL( foundSvgs( QStringList ) ), this, SLOT( addSvgs( QStringList ) ) );
|
||||
mSvgLoader->start();
|
||||
}
|
||||
|
||||
int QgsSvgSelectorListModel::rowCount( const QModelIndex & parent ) const
|
||||
int QgsSvgSelectorListModel::rowCount( const QModelIndex& parent ) const
|
||||
{
|
||||
Q_UNUSED( parent );
|
||||
return mSvgFiles.count();
|
||||
}
|
||||
|
||||
QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) const
|
||||
QPixmap QgsSvgSelectorListModel::createPreview( const QString& entry ) const
|
||||
{
|
||||
// render SVG file
|
||||
QColor fill, outline;
|
||||
double outlineWidth, fillOpacity, outlineOpacity;
|
||||
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
|
||||
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
|
||||
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
|
||||
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
|
||||
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
|
||||
outlineParam, hasDefaultOutlineColor, outline,
|
||||
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
|
||||
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
|
||||
|
||||
//if defaults not set in symbol, use these values
|
||||
if ( !hasDefaultFillColor )
|
||||
fill = QColor( 200, 200, 200 );
|
||||
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineColor )
|
||||
outline = Qt::black;
|
||||
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineWidth )
|
||||
outlineWidth = 0.2;
|
||||
|
||||
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
|
||||
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
|
||||
return QPixmap::fromImage( img );
|
||||
}
|
||||
|
||||
QVariant QgsSvgSelectorListModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
QString entry = mSvgFiles.at( index.row() );
|
||||
|
||||
@ -63,31 +255,7 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
|
||||
QPixmap pixmap;
|
||||
if ( !QPixmapCache::find( entry, pixmap ) )
|
||||
{
|
||||
// render SVG file
|
||||
QColor fill, outline;
|
||||
double outlineWidth, fillOpacity, outlineOpacity;
|
||||
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
|
||||
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
|
||||
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
|
||||
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
|
||||
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
|
||||
outlineParam, hasDefaultOutlineColor, outline,
|
||||
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
|
||||
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
|
||||
|
||||
//if defaults not set in symbol, use these values
|
||||
if ( !hasDefaultFillColor )
|
||||
fill = QColor( 200, 200, 200 );
|
||||
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineColor )
|
||||
outline = Qt::black;
|
||||
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineWidth )
|
||||
outlineWidth = 0.2;
|
||||
|
||||
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
|
||||
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
|
||||
pixmap = QPixmap::fromImage( img );
|
||||
pixmap = createPreview( entry );
|
||||
QPixmapCache::insert( entry, pixmap );
|
||||
}
|
||||
|
||||
@ -101,18 +269,30 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void QgsSvgSelectorListModel::addSvgs( const QStringList& svgs )
|
||||
{
|
||||
beginInsertRows( QModelIndex(), mSvgFiles.count(), mSvgFiles.count() + svgs.size() - 1 );
|
||||
mSvgFiles.append( svgs );
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//--- QgsSvgSelectorGroupsModel
|
||||
|
||||
QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel( QObject* parent )
|
||||
: QStandardItemModel( parent )
|
||||
, mLoader( new QgsSvgGroupLoader( this ) )
|
||||
{
|
||||
QStringList svgPaths = QgsApplication::svgPaths();
|
||||
QStandardItem *parentItem = invisibleRootItem();
|
||||
QStringList parentPaths;
|
||||
|
||||
for ( int i = 0; i < svgPaths.size(); i++ )
|
||||
{
|
||||
QDir dir( svgPaths[i] );
|
||||
QDir dir( svgPaths.at( i ) );
|
||||
QStandardItem *baseGroup;
|
||||
|
||||
if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
|
||||
@ -127,31 +307,41 @@ QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel( QObject* parent )
|
||||
{
|
||||
baseGroup = new QStandardItem( dir.dirName() );
|
||||
}
|
||||
baseGroup->setData( QVariant( svgPaths[i] ) );
|
||||
baseGroup->setData( QVariant( svgPaths.at( i ) ) );
|
||||
baseGroup->setEditable( false );
|
||||
baseGroup->setCheckable( false );
|
||||
baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
|
||||
baseGroup->setToolTip( dir.path() );
|
||||
parentItem->appendRow( baseGroup );
|
||||
createTree( baseGroup );
|
||||
parentPaths << svgPaths.at( i );
|
||||
mPathItemHash.insert( svgPaths.at( i ), baseGroup );
|
||||
QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
|
||||
}
|
||||
mLoader->setParentPaths( parentPaths );
|
||||
connect( mLoader, SIGNAL( foundPath( QString, QString ) ), this, SLOT( addPath( QString, QString ) ) );
|
||||
mLoader->start();
|
||||
}
|
||||
|
||||
void QgsSvgSelectorGroupsModel::createTree( QStandardItem* &parentGroup )
|
||||
QgsSvgSelectorGroupsModel::~QgsSvgSelectorGroupsModel()
|
||||
{
|
||||
QDir parentDir( parentGroup->data().toString() );
|
||||
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
|
||||
{
|
||||
QStandardItem* group = new QStandardItem( item );
|
||||
group->setData( QVariant( parentDir.path() + '/' + item ) );
|
||||
group->setEditable( false );
|
||||
group->setCheckable( false );
|
||||
group->setToolTip( parentDir.path() + '/' + item );
|
||||
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
|
||||
parentGroup->appendRow( group );
|
||||
createTree( group );
|
||||
}
|
||||
mLoader->stop();
|
||||
}
|
||||
|
||||
void QgsSvgSelectorGroupsModel::addPath( const QString& parentPath, const QString& item )
|
||||
{
|
||||
QStandardItem* parentGroup = mPathItemHash.value( parentPath );
|
||||
if ( !parentGroup )
|
||||
return;
|
||||
|
||||
QString fullPath = parentPath + '/' + item;
|
||||
QStandardItem* group = new QStandardItem( item );
|
||||
group->setData( QVariant( fullPath ) );
|
||||
group->setEditable( false );
|
||||
group->setCheckable( false );
|
||||
group->setToolTip( fullPath );
|
||||
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
|
||||
parentGroup->appendRow( group );
|
||||
mPathItemHash.insert( fullPath, group );
|
||||
}
|
||||
|
||||
|
||||
@ -250,11 +440,14 @@ void QgsSvgSelectorWidget::populateIcons( const QModelIndex& idx )
|
||||
{
|
||||
QString path = idx.data( Qt::UserRole + 1 ).toString();
|
||||
|
||||
QAbstractItemModel* oldModel = mImagesListView->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView, path );
|
||||
mImagesListView->setModel( m );
|
||||
delete oldModel; //explicitly delete old model to force any background threads to stop
|
||||
|
||||
connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
|
||||
this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
|
||||
|
||||
}
|
||||
|
||||
void QgsSvgSelectorWidget::on_mFilePushButton_clicked()
|
||||
@ -319,8 +512,10 @@ void QgsSvgSelectorWidget::populateList()
|
||||
}
|
||||
|
||||
// Initally load the icons in the List view without any grouping
|
||||
QAbstractItemModel* oldModel = mImagesListView->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView );
|
||||
mImagesListView->setModel( m );
|
||||
delete oldModel; //explicitly delete old model to force any background threads to stop
|
||||
}
|
||||
|
||||
//-- QgsSvgSelectorDialog
|
||||
@ -357,3 +552,4 @@ QgsSvgSelectorDialog::~QgsSvgSelectorDialog()
|
||||
QSettings settings;
|
||||
settings.setValue( "/Windows/SvgSelectorDialog/geometry", saveGeometry() );
|
||||
}
|
||||
|
||||
|
@ -20,13 +20,14 @@
|
||||
#include "ui_widget_svgselector.h"
|
||||
|
||||
#include "qgisgui.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLayout>
|
||||
#include <QStandardItemModel>
|
||||
#include <QWidget>
|
||||
#include <QThread>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
class QCheckBox;
|
||||
class QLayout;
|
||||
@ -35,29 +36,172 @@ class QListView;
|
||||
class QPushButton;
|
||||
class QTreeView;
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
/** \ingroup gui
|
||||
* \class QgsSvgSelectorLoader
|
||||
* Recursively loads SVG images from a path in a background thread.
|
||||
* \note added in QGIS 2.18
|
||||
*/
|
||||
class GUI_EXPORT QgsSvgSelectorLoader : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor for QgsSvgSelectorLoader
|
||||
* @param parent parent object
|
||||
*/
|
||||
QgsSvgSelectorLoader( QObject* parent = nullptr );
|
||||
|
||||
~QgsSvgSelectorLoader();
|
||||
|
||||
/** Starts the loader finding and generating previews for SVG images. foundSvgs() will be
|
||||
* emitted as the loader encounters SVG images.
|
||||
* @brief run
|
||||
*/
|
||||
virtual void run() override;
|
||||
|
||||
/** Cancels the current loading operation. Waits until the thread has finished operation
|
||||
* before returning.
|
||||
*/
|
||||
virtual void stop();
|
||||
|
||||
/** Sets the root path containing SVG images to load. If no path is set, the default SVG
|
||||
* search paths will be used instead.
|
||||
*/
|
||||
void setPath( const QString& path )
|
||||
{
|
||||
mPath = path;
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
/** Emitted when the loader has found a block of SVG images. This signal is emitted with blocks
|
||||
* of SVG images to prevent spamming any connected model.
|
||||
* @param svgs list of SVGs and preview images found.
|
||||
*/
|
||||
void foundSvgs( QStringList svgs );
|
||||
|
||||
private:
|
||||
|
||||
QString mPath;
|
||||
bool mCancelled;
|
||||
QStringList mQueuedSvgs;
|
||||
|
||||
QElapsedTimer mTimer;
|
||||
int mTimerThreshold;
|
||||
|
||||
void loadPath( const QString& path );
|
||||
void loadImages( const QString& path );
|
||||
|
||||
};
|
||||
|
||||
/** \ingroup gui
|
||||
* \class QgsSvgGroupLoader
|
||||
* Recursively loads SVG paths in a background thread.
|
||||
* \note added in QGIS 2.18
|
||||
*/
|
||||
class GUI_EXPORT QgsSvgGroupLoader : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor for QgsSvgGroupLoader
|
||||
* @param parent parent object
|
||||
*/
|
||||
QgsSvgGroupLoader( QObject* parent = nullptr );
|
||||
|
||||
~QgsSvgGroupLoader();
|
||||
|
||||
/** Starts the loader finding folders for SVG images.
|
||||
* @brief run
|
||||
*/
|
||||
virtual void run() override;
|
||||
|
||||
/** Cancels the current loading operation. Waits until the thread has finished operation
|
||||
* before returning.
|
||||
*/
|
||||
virtual void stop();
|
||||
|
||||
/** Sets the root path containing child paths to find. If no path is set, the default SVG
|
||||
* search paths will be used instead.
|
||||
*/
|
||||
void setParentPaths( const QStringList& parentPaths )
|
||||
{
|
||||
mParentPaths = parentPaths;
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
/** Emitted when the loader has found a block of SVG images. This signal is emitted with blocks
|
||||
* of SVG images to prevent spamming any connected model.
|
||||
* @param svgs list of SVGs and preview images found.
|
||||
*/
|
||||
void foundPath( const QString& parentPath, const QString& path );
|
||||
|
||||
private:
|
||||
|
||||
QStringList mParentPaths;
|
||||
bool mCancelled;
|
||||
|
||||
void loadGroup( const QString& parentPath );
|
||||
|
||||
};
|
||||
|
||||
///@endcond
|
||||
///
|
||||
|
||||
/** \ingroup gui
|
||||
* \class QgsSvgSelectorListModel
|
||||
* A model for displaying SVG files with a preview icon. Population of the model is performed in
|
||||
* a background thread to ensure that initial creation of the model is responsive and does
|
||||
* not block the GUI.
|
||||
*/
|
||||
class GUI_EXPORT QgsSvgSelectorListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/** Constructor for QgsSvgSelectorListModel. All SVGs in folders from the application SVG
|
||||
* search paths will be shown.
|
||||
* @param parent parent object
|
||||
*/
|
||||
QgsSvgSelectorListModel( QObject* parent );
|
||||
|
||||
// Constructor to create model for icons in a specific path
|
||||
/** Constructor for creating a model for SVG files in a specific path.
|
||||
* @param parent parent object
|
||||
* @param path initial path, which is recursively searched
|
||||
*/
|
||||
QgsSvgSelectorListModel( QObject* parent, const QString& path );
|
||||
|
||||
int rowCount( const QModelIndex & parent = QModelIndex() ) const override;
|
||||
|
||||
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
|
||||
|
||||
protected:
|
||||
QStringList mSvgFiles;
|
||||
|
||||
private:
|
||||
QPixmap createPreview( const QString& entry ) const;
|
||||
QgsSvgSelectorLoader* mSvgLoader;
|
||||
|
||||
private slots:
|
||||
|
||||
/** Called to add SVG files to the model.
|
||||
* @param svgs list of SVG files to add to model.
|
||||
*/
|
||||
void addSvgs( const QStringList& svgs );
|
||||
|
||||
};
|
||||
|
||||
|
||||
/** \ingroup gui
|
||||
* \class QgsSvgSelectorGroupsModel
|
||||
* A model for displaying SVG search paths. Population of the model is performed in
|
||||
* a background thread to ensure that initial creation of the model is responsive and does
|
||||
* not block the GUI.
|
||||
*/
|
||||
class GUI_EXPORT QgsSvgSelectorGroupsModel : public QStandardItemModel
|
||||
{
|
||||
@ -65,9 +209,15 @@ class GUI_EXPORT QgsSvgSelectorGroupsModel : public QStandardItemModel
|
||||
|
||||
public:
|
||||
QgsSvgSelectorGroupsModel( QObject* parent );
|
||||
~QgsSvgSelectorGroupsModel();
|
||||
|
||||
private:
|
||||
void createTree( QStandardItem* &parentGroup );
|
||||
QgsSvgGroupLoader* mLoader;
|
||||
QHash< QString, QStandardItem* > mPathItemHash;
|
||||
|
||||
private slots:
|
||||
|
||||
void addPath( const QString& parentPath, const QString& path );
|
||||
};
|
||||
|
||||
/** \ingroup gui
|
||||
@ -114,6 +264,7 @@ class GUI_EXPORT QgsSvgSelectorWidget : public QWidget, private Ui::WidgetSvgSel
|
||||
|
||||
private:
|
||||
QString mCurrentSvgPath; // always stored as absolute path
|
||||
|
||||
};
|
||||
|
||||
/** \ingroup gui
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "qgsmapcanvas.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgsvectorlayer.h"
|
||||
#include "qgssvgselectorwidget.h"
|
||||
|
||||
#include "qgslogger.h"
|
||||
#include "qgssizescalewidget.h"
|
||||
@ -1802,8 +1803,11 @@ QgsSvgMarkerSymbolLayerWidget::~QgsSvgMarkerSymbolLayerWidget()
|
||||
|
||||
void QgsSvgMarkerSymbolLayerWidget::populateList()
|
||||
{
|
||||
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( viewGroups );
|
||||
QAbstractItemModel* oldModel = viewGroups->model();
|
||||
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( viewGroups );
|
||||
viewGroups->setModel( g );
|
||||
delete oldModel;
|
||||
|
||||
// Set the tree expanded at the first level
|
||||
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
|
||||
for ( int i = 0; i < rows; i++ )
|
||||
@ -1812,19 +1816,22 @@ void QgsSvgMarkerSymbolLayerWidget::populateList()
|
||||
}
|
||||
|
||||
// Initally load the icons in the List view without any grouping
|
||||
QgsSvgListModel* m = new QgsSvgListModel( viewImages );
|
||||
oldModel = viewImages->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages );
|
||||
viewImages->setModel( m );
|
||||
delete oldModel;
|
||||
}
|
||||
|
||||
void QgsSvgMarkerSymbolLayerWidget::populateIcons( const QModelIndex& idx )
|
||||
{
|
||||
QString path = idx.data( Qt::UserRole + 1 ).toString();
|
||||
|
||||
QgsSvgListModel* m = new QgsSvgListModel( viewImages, path );
|
||||
QAbstractItemModel* oldModel = viewImages->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages, path );
|
||||
viewImages->setModel( m );
|
||||
delete oldModel;
|
||||
|
||||
connect( viewImages->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setName( const QModelIndex& ) ) );
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void QgsSvgMarkerSymbolLayerWidget::setGuiForSvg( const QgsSvgMarkerSymbolLayer* layer )
|
||||
@ -2285,8 +2292,11 @@ void QgsSVGFillSymbolLayerWidget::setFile( const QModelIndex& item )
|
||||
|
||||
void QgsSVGFillSymbolLayerWidget::insertIcons()
|
||||
{
|
||||
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( mSvgTreeView );
|
||||
QAbstractItemModel* oldModel = mSvgTreeView->model();
|
||||
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( mSvgTreeView );
|
||||
mSvgTreeView->setModel( g );
|
||||
delete oldModel;
|
||||
|
||||
// Set the tree expanded at the first level
|
||||
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
|
||||
for ( int i = 0; i < rows; i++ )
|
||||
@ -2294,19 +2304,22 @@ void QgsSVGFillSymbolLayerWidget::insertIcons()
|
||||
mSvgTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
|
||||
}
|
||||
|
||||
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView );
|
||||
oldModel = mSvgListView->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView );
|
||||
mSvgListView->setModel( m );
|
||||
delete oldModel;
|
||||
}
|
||||
|
||||
void QgsSVGFillSymbolLayerWidget::populateIcons( const QModelIndex& idx )
|
||||
{
|
||||
QString path = idx.data( Qt::UserRole + 1 ).toString();
|
||||
|
||||
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView, path );
|
||||
QAbstractItemModel* oldModel = mSvgListView->model();
|
||||
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView, path );
|
||||
mSvgListView->setModel( m );
|
||||
delete oldModel;
|
||||
|
||||
connect( mSvgListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setFile( const QModelIndex& ) ) );
|
||||
emit changed();
|
||||
}
|
||||
|
||||
|
||||
@ -3203,129 +3216,6 @@ void QgsRasterFillSymbolLayerWidget::updatePreviewImage()
|
||||
}
|
||||
|
||||
|
||||
/// @cond PRIVATE
|
||||
|
||||
QgsSvgListModel::QgsSvgListModel( QObject* parent ) : QAbstractListModel( parent )
|
||||
{
|
||||
mSvgFiles = QgsSymbolLayerUtils::listSvgFiles();
|
||||
}
|
||||
|
||||
QgsSvgListModel::QgsSvgListModel( QObject* parent, const QString& path ) : QAbstractListModel( parent )
|
||||
{
|
||||
mSvgFiles = QgsSymbolLayerUtils::listSvgFilesAt( path );
|
||||
}
|
||||
|
||||
int QgsSvgListModel::rowCount( const QModelIndex& parent ) const
|
||||
{
|
||||
Q_UNUSED( parent );
|
||||
return mSvgFiles.count();
|
||||
}
|
||||
|
||||
QVariant QgsSvgListModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
QString entry = mSvgFiles.at( index.row() );
|
||||
|
||||
if ( role == Qt::DecorationRole ) // icon
|
||||
{
|
||||
QPixmap pixmap;
|
||||
if ( !QPixmapCache::find( entry, pixmap ) )
|
||||
{
|
||||
// render SVG file
|
||||
QColor fill, outline;
|
||||
double outlineWidth, fillOpacity, outlineOpacity;
|
||||
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
|
||||
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
|
||||
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
|
||||
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
|
||||
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
|
||||
outlineParam, hasDefaultOutlineColor, outline,
|
||||
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
|
||||
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
|
||||
|
||||
//if defaults not set in symbol, use these values
|
||||
if ( !hasDefaultFillColor )
|
||||
fill = QColor( 200, 200, 200 );
|
||||
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineColor )
|
||||
outline = Qt::black;
|
||||
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
|
||||
if ( !hasDefaultOutlineWidth )
|
||||
outlineWidth = 0.6;
|
||||
|
||||
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
|
||||
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
|
||||
pixmap = QPixmap::fromImage( img );
|
||||
QPixmapCache::insert( entry, pixmap );
|
||||
}
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
QgsSvgGroupsModel::QgsSvgGroupsModel( QObject* parent ) : QStandardItemModel( parent )
|
||||
{
|
||||
QStringList svgPaths = QgsApplication::svgPaths();
|
||||
QStandardItem *parentItem = invisibleRootItem();
|
||||
|
||||
for ( int i = 0; i < svgPaths.size(); i++ )
|
||||
{
|
||||
QDir dir( svgPaths[i] );
|
||||
QStandardItem *baseGroup;
|
||||
|
||||
if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
|
||||
{
|
||||
baseGroup = new QStandardItem( QString( "App Symbols" ) );
|
||||
}
|
||||
else if ( dir.path().contains( QgsApplication::qgisSettingsDirPath() ) )
|
||||
{
|
||||
baseGroup = new QStandardItem( QString( "User Symbols" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
baseGroup = new QStandardItem( dir.dirName() );
|
||||
}
|
||||
baseGroup->setData( QVariant( svgPaths[i] ) );
|
||||
baseGroup->setEditable( false );
|
||||
baseGroup->setCheckable( false );
|
||||
baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
|
||||
baseGroup->setToolTip( dir.path() );
|
||||
parentItem->appendRow( baseGroup );
|
||||
createTree( baseGroup );
|
||||
QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSvgGroupsModel::createTree( QStandardItem*& parentGroup )
|
||||
{
|
||||
QDir parentDir( parentGroup->data().toString() );
|
||||
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
|
||||
{
|
||||
QStandardItem* group = new QStandardItem( item );
|
||||
group->setData( QVariant( parentDir.path() + '/' + item ) );
|
||||
group->setEditable( false );
|
||||
group->setCheckable( false );
|
||||
group->setToolTip( parentDir.path() + '/' + item );
|
||||
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
|
||||
parentGroup->appendRow( group );
|
||||
createTree( group );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @endcond
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QgsGeometryGeneratorSymbolLayerWidget::QgsGeometryGeneratorSymbolLayerWidget( const QgsVectorLayer* vl, QWidget* parent )
|
||||
: QgsSymbolLayerWidget( parent, vl )
|
||||
, mLayer( nullptr )
|
||||
|
@ -707,39 +707,6 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerWidget : public QgsSymbolLayerWidget,
|
||||
};
|
||||
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
class QgsSvgListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QgsSvgListModel( QObject* parent );
|
||||
|
||||
// Constructor to create model for icons in a specific path
|
||||
QgsSvgListModel( QObject* parent, const QString& path );
|
||||
|
||||
int rowCount( const QModelIndex & parent = QModelIndex() ) const override;
|
||||
|
||||
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
|
||||
|
||||
protected:
|
||||
QStringList mSvgFiles;
|
||||
};
|
||||
|
||||
class QgsSvgGroupsModel : public QStandardItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QgsSvgGroupsModel( QObject* parent );
|
||||
|
||||
private:
|
||||
void createTree( QStandardItem* &parentGroup );
|
||||
};
|
||||
|
||||
///@endcond
|
||||
|
||||
#include "ui_qgsgeometrygeneratorwidgetbase.h"
|
||||
|
||||
class QgsGeometryGeneratorSymbolLayer;
|
||||
|
Loading…
x
Reference in New Issue
Block a user