Skip to content

Commit 0b68363

Browse files
authoredOct 4, 2016
Merge pull request #3565 from nyalldawson/svg_background
Fix SVG preview blocks QGIS (fix #14255)
2 parents 7332dda + 3abded3 commit 0b68363

File tree

6 files changed

+474
-224
lines changed

6 files changed

+474
-224
lines changed
 

‎src/core/qgsapplication.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,20 @@ QStringList QgsApplication::svgPaths()
714714
myPathList = myPaths.split( '|' );
715715
}
716716

717-
myPathList << ABISYM( mDefaultSvgPaths );
718-
return myPathList;
717+
// maintain user set order while stripping duplicates
718+
QStringList paths;
719+
Q_FOREACH ( const QString& path, myPathList )
720+
{
721+
if ( !paths.contains( path ) )
722+
paths.append( path );
723+
}
724+
Q_FOREACH ( const QString& path, ABISYM( mDefaultSvgPaths ) )
725+
{
726+
if ( !paths.contains( path ) )
727+
paths.append( path );
728+
}
729+
730+
return paths;
719731
}
720732

721733
/*!

‎src/gui/symbology-ng/qgssvgselectorwidget.cpp

Lines changed: 260 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,29 +32,238 @@
3232
#include <QStyle>
3333
#include <QTime>
3434

35+
// QgsSvgSelectorLoader
3536

36-
//--- QgsSvgSelectorListModel
37+
///@cond PRIVATE
38+
QgsSvgSelectorLoader::QgsSvgSelectorLoader( QObject* parent )
39+
: QThread( parent )
40+
, mCancelled( false )
41+
, mTimerThreshold( 0 )
42+
{
43+
}
44+
45+
QgsSvgSelectorLoader::~QgsSvgSelectorLoader()
46+
{
47+
stop();
48+
}
49+
50+
void QgsSvgSelectorLoader::run()
51+
{
52+
mCancelled = false;
53+
mQueuedSvgs.clear();
54+
mTraversedPaths.clear();
55+
56+
// start with a small initial timeout (ms)
57+
mTimerThreshold = 10;
58+
mTimer.start();
59+
60+
loadPath( mPath );
61+
62+
if ( !mQueuedSvgs.isEmpty() )
63+
{
64+
// make sure we notify model of any remaining queued svgs (ie svgs added since last foundSvgs() signal was emitted)
65+
emit foundSvgs( mQueuedSvgs );
66+
}
67+
mQueuedSvgs.clear();
68+
}
69+
70+
void QgsSvgSelectorLoader::stop()
71+
{
72+
mCancelled = true;
73+
while ( isRunning() ) {}
74+
}
75+
76+
void QgsSvgSelectorLoader::loadPath( const QString& path )
77+
{
78+
if ( mCancelled )
79+
return;
80+
81+
// QgsDebugMsg( QString( "loading path: %1" ).arg( path ) );
82+
83+
if ( path.isEmpty() )
84+
{
85+
QStringList svgPaths = QgsApplication::svgPaths();
86+
Q_FOREACH ( const QString& svgPath, svgPaths )
87+
{
88+
if ( mCancelled )
89+
return;
90+
91+
loadPath( svgPath );
92+
}
93+
}
94+
else
95+
{
96+
QDir dir( path );
97+
98+
//guard against circular symbolic links
99+
QString canonicalPath = dir.canonicalPath();
100+
if ( mTraversedPaths.contains( canonicalPath ) )
101+
return;
102+
103+
mTraversedPaths.insert( canonicalPath );
104+
105+
loadImages( path );
106+
107+
Q_FOREACH ( const QString& item, dir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
108+
{
109+
if ( mCancelled )
110+
return;
111+
112+
QString newPath = dir.path() + '/' + item;
113+
loadPath( newPath );
114+
// QgsDebugMsg( QString( "added path: %1" ).arg( newPath ) );
115+
}
116+
}
117+
}
118+
119+
void QgsSvgSelectorLoader::loadImages( const QString& path )
120+
{
121+
QDir dir( path );
122+
Q_FOREACH ( const QString& item, dir.entryList( QStringList( "*.svg" ), QDir::Files ) )
123+
{
124+
if ( mCancelled )
125+
return;
126+
127+
// TODO test if it is correct SVG
128+
QString svgPath = dir.path() + '/' + item;
129+
// QgsDebugMsg( QString( "adding svg: %1" ).arg( svgPath ) );
130+
131+
// add it to the list of queued SVGs
132+
mQueuedSvgs << svgPath;
133+
134+
// we need to avoid spamming the model with notifications about new svgs, so foundSvgs
135+
// is only emitted for blocks of SVGs (otherwise the view goes all flickery)
136+
if ( mTimer.elapsed() > mTimerThreshold && !mQueuedSvgs.isEmpty() )
137+
{
138+
emit foundSvgs( mQueuedSvgs );
139+
mQueuedSvgs.clear();
140+
141+
// increase the timer threshold - this ensures that the first lots of svgs loaded are added
142+
// to the view quickly, but as the list grows new svgs are added at a slower rate.
143+
// ie, good for initial responsiveness but avoid being spammy as the list grows.
144+
if ( mTimerThreshold < 1000 )
145+
mTimerThreshold *= 2;
146+
mTimer.restart();
147+
}
148+
}
149+
}
150+
151+
152+
//
153+
// QgsSvgGroupLoader
154+
//
155+
156+
QgsSvgGroupLoader::QgsSvgGroupLoader( QObject* parent )
157+
: QThread( parent )
158+
, mCancelled( false )
159+
{
160+
161+
}
162+
163+
QgsSvgGroupLoader::~QgsSvgGroupLoader()
164+
{
165+
stop();
166+
}
167+
168+
void QgsSvgGroupLoader::run()
169+
{
170+
mCancelled = false;
171+
mTraversedPaths.clear();
172+
173+
while ( !mCancelled && !mParentPaths.isEmpty() )
174+
{
175+
QString parentPath = mParentPaths.takeFirst();
176+
loadGroup( parentPath );
177+
}
178+
}
179+
180+
void QgsSvgGroupLoader::stop()
181+
{
182+
mCancelled = true;
183+
while ( isRunning() ) {}
184+
}
185+
186+
void QgsSvgGroupLoader::loadGroup( const QString& parentPath )
187+
{
188+
QDir parentDir( parentPath );
189+
190+
//guard against circular symbolic links
191+
QString canonicalPath = parentDir.canonicalPath();
192+
if ( mTraversedPaths.contains( canonicalPath ) )
193+
return;
194+
195+
mTraversedPaths.insert( canonicalPath );
196+
197+
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
198+
{
199+
if ( mCancelled )
200+
return;
201+
202+
emit foundPath( parentPath, item );
203+
mParentPaths.append( parentDir.path() + '/' + item );
204+
}
205+
}
206+
207+
///@endcond
208+
209+
//,
210+
// QgsSvgSelectorListModel
211+
//
37212

38213
QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject* parent )
39214
: QAbstractListModel( parent )
215+
, mSvgLoader( new QgsSvgSelectorLoader( this ) )
40216
{
41-
mSvgFiles = QgsSymbolLayerUtils::listSvgFiles();
217+
mSvgLoader->setPath( QString() );
218+
connect( mSvgLoader, SIGNAL( foundSvgs( QStringList ) ), this, SLOT( addSvgs( QStringList ) ) );
219+
mSvgLoader->start();
42220
}
43221

44-
// Constructor to create model for icons in a specific path
45222
QgsSvgSelectorListModel::QgsSvgSelectorListModel( QObject* parent, const QString& path )
46223
: QAbstractListModel( parent )
224+
, mSvgLoader( new QgsSvgSelectorLoader( this ) )
47225
{
48-
mSvgFiles = QgsSymbolLayerUtils::listSvgFilesAt( path );
226+
mSvgLoader->setPath( path );
227+
connect( mSvgLoader, SIGNAL( foundSvgs( QStringList ) ), this, SLOT( addSvgs( QStringList ) ) );
228+
mSvgLoader->start();
49229
}
50230

51-
int QgsSvgSelectorListModel::rowCount( const QModelIndex & parent ) const
231+
int QgsSvgSelectorListModel::rowCount( const QModelIndex& parent ) const
52232
{
53233
Q_UNUSED( parent );
54234
return mSvgFiles.count();
55235
}
56236

57-
QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) const
237+
QPixmap QgsSvgSelectorListModel::createPreview( const QString& entry ) const
238+
{
239+
// render SVG file
240+
QColor fill, outline;
241+
double outlineWidth, fillOpacity, outlineOpacity;
242+
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
243+
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
244+
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
245+
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
246+
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
247+
outlineParam, hasDefaultOutlineColor, outline,
248+
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
249+
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
250+
251+
//if defaults not set in symbol, use these values
252+
if ( !hasDefaultFillColor )
253+
fill = QColor( 200, 200, 200 );
254+
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
255+
if ( !hasDefaultOutlineColor )
256+
outline = Qt::black;
257+
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
258+
if ( !hasDefaultOutlineWidth )
259+
outlineWidth = 0.2;
260+
261+
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
262+
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
263+
return QPixmap::fromImage( img );
264+
}
265+
266+
QVariant QgsSvgSelectorListModel::data( const QModelIndex& index, int role ) const
58267
{
59268
QString entry = mSvgFiles.at( index.row() );
60269

@@ -63,31 +272,7 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
63272
QPixmap pixmap;
64273
if ( !QPixmapCache::find( entry, pixmap ) )
65274
{
66-
// render SVG file
67-
QColor fill, outline;
68-
double outlineWidth, fillOpacity, outlineOpacity;
69-
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
70-
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
71-
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
72-
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
73-
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
74-
outlineParam, hasDefaultOutlineColor, outline,
75-
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
76-
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
77-
78-
//if defaults not set in symbol, use these values
79-
if ( !hasDefaultFillColor )
80-
fill = QColor( 200, 200, 200 );
81-
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
82-
if ( !hasDefaultOutlineColor )
83-
outline = Qt::black;
84-
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
85-
if ( !hasDefaultOutlineWidth )
86-
outlineWidth = 0.2;
87-
88-
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
89-
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
90-
pixmap = QPixmap::fromImage( img );
275+
pixmap = createPreview( entry );
91276
QPixmapCache::insert( entry, pixmap );
92277
}
93278

@@ -101,18 +286,30 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
101286
return QVariant();
102287
}
103288

289+
void QgsSvgSelectorListModel::addSvgs( const QStringList& svgs )
290+
{
291+
beginInsertRows( QModelIndex(), mSvgFiles.count(), mSvgFiles.count() + svgs.size() - 1 );
292+
mSvgFiles.append( svgs );
293+
endInsertRows();
294+
}
295+
296+
297+
298+
104299

105300
//--- QgsSvgSelectorGroupsModel
106301

107302
QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel( QObject* parent )
108303
: QStandardItemModel( parent )
304+
, mLoader( new QgsSvgGroupLoader( this ) )
109305
{
110306
QStringList svgPaths = QgsApplication::svgPaths();
111307
QStandardItem *parentItem = invisibleRootItem();
308+
QStringList parentPaths;
112309

113310
for ( int i = 0; i < svgPaths.size(); i++ )
114311
{
115-
QDir dir( svgPaths[i] );
312+
QDir dir( svgPaths.at( i ) );
116313
QStandardItem *baseGroup;
117314

118315
if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
@@ -127,31 +324,41 @@ QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel( QObject* parent )
127324
{
128325
baseGroup = new QStandardItem( dir.dirName() );
129326
}
130-
baseGroup->setData( QVariant( svgPaths[i] ) );
327+
baseGroup->setData( QVariant( svgPaths.at( i ) ) );
131328
baseGroup->setEditable( false );
132329
baseGroup->setCheckable( false );
133330
baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
134331
baseGroup->setToolTip( dir.path() );
135332
parentItem->appendRow( baseGroup );
136-
createTree( baseGroup );
333+
parentPaths << svgPaths.at( i );
334+
mPathItemHash.insert( svgPaths.at( i ), baseGroup );
137335
QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
138336
}
337+
mLoader->setParentPaths( parentPaths );
338+
connect( mLoader, SIGNAL( foundPath( QString, QString ) ), this, SLOT( addPath( QString, QString ) ) );
339+
mLoader->start();
139340
}
140341

141-
void QgsSvgSelectorGroupsModel::createTree( QStandardItem* &parentGroup )
342+
QgsSvgSelectorGroupsModel::~QgsSvgSelectorGroupsModel()
142343
{
143-
QDir parentDir( parentGroup->data().toString() );
144-
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
145-
{
146-
QStandardItem* group = new QStandardItem( item );
147-
group->setData( QVariant( parentDir.path() + '/' + item ) );
148-
group->setEditable( false );
149-
group->setCheckable( false );
150-
group->setToolTip( parentDir.path() + '/' + item );
151-
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
152-
parentGroup->appendRow( group );
153-
createTree( group );
154-
}
344+
mLoader->stop();
345+
}
346+
347+
void QgsSvgSelectorGroupsModel::addPath( const QString& parentPath, const QString& item )
348+
{
349+
QStandardItem* parentGroup = mPathItemHash.value( parentPath );
350+
if ( !parentGroup )
351+
return;
352+
353+
QString fullPath = parentPath + '/' + item;
354+
QStandardItem* group = new QStandardItem( item );
355+
group->setData( QVariant( fullPath ) );
356+
group->setEditable( false );
357+
group->setCheckable( false );
358+
group->setToolTip( fullPath );
359+
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
360+
parentGroup->appendRow( group );
361+
mPathItemHash.insert( fullPath, group );
155362
}
156363

157364

@@ -250,11 +457,14 @@ void QgsSvgSelectorWidget::populateIcons( const QModelIndex& idx )
250457
{
251458
QString path = idx.data( Qt::UserRole + 1 ).toString();
252459

460+
QAbstractItemModel* oldModel = mImagesListView->model();
253461
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView, path );
254462
mImagesListView->setModel( m );
463+
delete oldModel; //explicitly delete old model to force any background threads to stop
255464

256465
connect( mImagesListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ),
257466
this, SLOT( svgSelectionChanged( const QModelIndex& ) ) );
467+
258468
}
259469

260470
void QgsSvgSelectorWidget::on_mFilePushButton_clicked()
@@ -319,8 +529,10 @@ void QgsSvgSelectorWidget::populateList()
319529
}
320530

321531
// Initally load the icons in the List view without any grouping
532+
QAbstractItemModel* oldModel = mImagesListView->model();
322533
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mImagesListView );
323534
mImagesListView->setModel( m );
535+
delete oldModel; //explicitly delete old model to force any background threads to stop
324536
}
325537

326538
//-- QgsSvgSelectorDialog
@@ -357,3 +569,4 @@ QgsSvgSelectorDialog::~QgsSvgSelectorDialog()
357569
QSettings settings;
358570
settings.setValue( "/Windows/SvgSelectorDialog/geometry", saveGeometry() );
359571
}
572+

‎src/gui/symbology-ng/qgssvgselectorwidget.h

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@
2020
#include "ui_widget_svgselector.h"
2121

2222
#include "qgisgui.h"
23-
2423
#include <QAbstractListModel>
2524
#include <QDialog>
2625
#include <QDialogButtonBox>
2726
#include <QLayout>
2827
#include <QStandardItemModel>
2928
#include <QWidget>
29+
#include <QThread>
30+
#include <QElapsedTimer>
3031

3132
class QCheckBox;
3233
class QLayout;
@@ -35,39 +36,190 @@ class QListView;
3536
class QPushButton;
3637
class QTreeView;
3738

39+
///@cond PRIVATE
40+
41+
/** \ingroup gui
42+
* \class QgsSvgSelectorLoader
43+
* Recursively loads SVG images from a path in a background thread.
44+
* \note added in QGIS 2.18
45+
*/
46+
class GUI_EXPORT QgsSvgSelectorLoader : public QThread
47+
{
48+
Q_OBJECT
49+
50+
public:
51+
52+
/** Constructor for QgsSvgSelectorLoader
53+
* @param parent parent object
54+
*/
55+
QgsSvgSelectorLoader( QObject* parent = nullptr );
56+
57+
~QgsSvgSelectorLoader();
58+
59+
/** Starts the loader finding and generating previews for SVG images. foundSvgs() will be
60+
* emitted as the loader encounters SVG images.
61+
* @brief run
62+
*/
63+
virtual void run() override;
64+
65+
/** Cancels the current loading operation. Waits until the thread has finished operation
66+
* before returning.
67+
*/
68+
virtual void stop();
69+
70+
/** Sets the root path containing SVG images to load. If no path is set, the default SVG
71+
* search paths will be used instead.
72+
*/
73+
void setPath( const QString& path )
74+
{
75+
mPath = path;
76+
}
77+
78+
signals:
79+
80+
/** Emitted when the loader has found a block of SVG images. This signal is emitted with blocks
81+
* of SVG images to prevent spamming any connected model.
82+
* @param svgs list of SVGs and preview images found.
83+
*/
84+
void foundSvgs( QStringList svgs );
85+
86+
private:
87+
88+
QString mPath;
89+
bool mCancelled;
90+
QStringList mQueuedSvgs;
91+
92+
QElapsedTimer mTimer;
93+
int mTimerThreshold;
94+
QSet< QString > mTraversedPaths;
95+
96+
void loadPath( const QString& path );
97+
void loadImages( const QString& path );
98+
99+
};
100+
101+
/** \ingroup gui
102+
* \class QgsSvgGroupLoader
103+
* Recursively loads SVG paths in a background thread.
104+
* \note added in QGIS 2.18
105+
*/
106+
class GUI_EXPORT QgsSvgGroupLoader : public QThread
107+
{
108+
Q_OBJECT
109+
110+
public:
111+
112+
/** Constructor for QgsSvgGroupLoader
113+
* @param parent parent object
114+
*/
115+
QgsSvgGroupLoader( QObject* parent = nullptr );
116+
117+
~QgsSvgGroupLoader();
118+
119+
/** Starts the loader finding folders for SVG images.
120+
* @brief run
121+
*/
122+
virtual void run() override;
123+
124+
/** Cancels the current loading operation. Waits until the thread has finished operation
125+
* before returning.
126+
*/
127+
virtual void stop();
128+
129+
/** Sets the root path containing child paths to find. If no path is set, the default SVG
130+
* search paths will be used instead.
131+
*/
132+
void setParentPaths( const QStringList& parentPaths )
133+
{
134+
mParentPaths = parentPaths;
135+
}
136+
137+
signals:
138+
139+
/** Emitted when the loader has found a block of SVG images. This signal is emitted with blocks
140+
* of SVG images to prevent spamming any connected model.
141+
* @param svgs list of SVGs and preview images found.
142+
*/
143+
void foundPath( const QString& parentPath, const QString& path );
144+
145+
private:
146+
147+
QStringList mParentPaths;
148+
bool mCancelled;
149+
QSet< QString > mTraversedPaths;
150+
151+
void loadGroup( const QString& parentPath );
152+
153+
};
154+
155+
///@endcond
156+
///
157+
38158
/** \ingroup gui
39159
* \class QgsSvgSelectorListModel
160+
* A model for displaying SVG files with a preview icon. Population of the model is performed in
161+
* a background thread to ensure that initial creation of the model is responsive and does
162+
* not block the GUI.
40163
*/
41164
class GUI_EXPORT QgsSvgSelectorListModel : public QAbstractListModel
42165
{
43166
Q_OBJECT
44167

45168
public:
169+
170+
/** Constructor for QgsSvgSelectorListModel. All SVGs in folders from the application SVG
171+
* search paths will be shown.
172+
* @param parent parent object
173+
*/
46174
QgsSvgSelectorListModel( QObject* parent );
47175

48-
// Constructor to create model for icons in a specific path
176+
/** Constructor for creating a model for SVG files in a specific path.
177+
* @param parent parent object
178+
* @param path initial path, which is recursively searched
179+
*/
49180
QgsSvgSelectorListModel( QObject* parent, const QString& path );
50181

51182
int rowCount( const QModelIndex & parent = QModelIndex() ) const override;
52-
53183
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
54184

55185
protected:
56186
QStringList mSvgFiles;
187+
188+
private:
189+
QPixmap createPreview( const QString& entry ) const;
190+
QgsSvgSelectorLoader* mSvgLoader;
191+
192+
private slots:
193+
194+
/** Called to add SVG files to the model.
195+
* @param svgs list of SVG files to add to model.
196+
*/
197+
void addSvgs( const QStringList& svgs );
198+
57199
};
58200

201+
59202
/** \ingroup gui
60203
* \class QgsSvgSelectorGroupsModel
204+
* A model for displaying SVG search paths. Population of the model is performed in
205+
* a background thread to ensure that initial creation of the model is responsive and does
206+
* not block the GUI.
61207
*/
62208
class GUI_EXPORT QgsSvgSelectorGroupsModel : public QStandardItemModel
63209
{
64210
Q_OBJECT
65211

66212
public:
67213
QgsSvgSelectorGroupsModel( QObject* parent );
214+
~QgsSvgSelectorGroupsModel();
68215

69216
private:
70-
void createTree( QStandardItem* &parentGroup );
217+
QgsSvgGroupLoader* mLoader;
218+
QHash< QString, QStandardItem* > mPathItemHash;
219+
220+
private slots:
221+
222+
void addPath( const QString& parentPath, const QString& path );
71223
};
72224

73225
/** \ingroup gui
@@ -114,6 +266,7 @@ class GUI_EXPORT QgsSvgSelectorWidget : public QWidget, private Ui::WidgetSvgSel
114266

115267
private:
116268
QString mCurrentSvgPath; // always stored as absolute path
269+
117270
};
118271

119272
/** \ingroup gui

‎src/gui/symbology-ng/qgssymbollayerwidget.cpp

Lines changed: 21 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "qgsmapcanvas.h"
3535
#include "qgsapplication.h"
3636
#include "qgsvectorlayer.h"
37+
#include "qgssvgselectorwidget.h"
3738

3839
#include "qgslogger.h"
3940
#include "qgssizescalewidget.h"
@@ -1802,8 +1803,11 @@ QgsSvgMarkerSymbolLayerWidget::~QgsSvgMarkerSymbolLayerWidget()
18021803

18031804
void QgsSvgMarkerSymbolLayerWidget::populateList()
18041805
{
1805-
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( viewGroups );
1806+
QAbstractItemModel* oldModel = viewGroups->model();
1807+
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( viewGroups );
18061808
viewGroups->setModel( g );
1809+
delete oldModel;
1810+
18071811
// Set the tree expanded at the first level
18081812
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
18091813
for ( int i = 0; i < rows; i++ )
@@ -1812,19 +1816,22 @@ void QgsSvgMarkerSymbolLayerWidget::populateList()
18121816
}
18131817

18141818
// Initally load the icons in the List view without any grouping
1815-
QgsSvgListModel* m = new QgsSvgListModel( viewImages );
1819+
oldModel = viewImages->model();
1820+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages );
18161821
viewImages->setModel( m );
1822+
delete oldModel;
18171823
}
18181824

18191825
void QgsSvgMarkerSymbolLayerWidget::populateIcons( const QModelIndex& idx )
18201826
{
18211827
QString path = idx.data( Qt::UserRole + 1 ).toString();
18221828

1823-
QgsSvgListModel* m = new QgsSvgListModel( viewImages, path );
1829+
QAbstractItemModel* oldModel = viewImages->model();
1830+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages, path );
18241831
viewImages->setModel( m );
1832+
delete oldModel;
18251833

18261834
connect( viewImages->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setName( const QModelIndex& ) ) );
1827-
emit changed();
18281835
}
18291836

18301837
void QgsSvgMarkerSymbolLayerWidget::setGuiForSvg( const QgsSvgMarkerSymbolLayer* layer )
@@ -2285,28 +2292,34 @@ void QgsSVGFillSymbolLayerWidget::setFile( const QModelIndex& item )
22852292

22862293
void QgsSVGFillSymbolLayerWidget::insertIcons()
22872294
{
2288-
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( mSvgTreeView );
2295+
QAbstractItemModel* oldModel = mSvgTreeView->model();
2296+
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( mSvgTreeView );
22892297
mSvgTreeView->setModel( g );
2298+
delete oldModel;
2299+
22902300
// Set the tree expanded at the first level
22912301
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
22922302
for ( int i = 0; i < rows; i++ )
22932303
{
22942304
mSvgTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
22952305
}
22962306

2297-
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView );
2307+
oldModel = mSvgListView->model();
2308+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView );
22982309
mSvgListView->setModel( m );
2310+
delete oldModel;
22992311
}
23002312

23012313
void QgsSVGFillSymbolLayerWidget::populateIcons( const QModelIndex& idx )
23022314
{
23032315
QString path = idx.data( Qt::UserRole + 1 ).toString();
23042316

2305-
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView, path );
2317+
QAbstractItemModel* oldModel = mSvgListView->model();
2318+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView, path );
23062319
mSvgListView->setModel( m );
2320+
delete oldModel;
23072321

23082322
connect( mSvgListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setFile( const QModelIndex& ) ) );
2309-
emit changed();
23102323
}
23112324

23122325

@@ -3203,129 +3216,6 @@ void QgsRasterFillSymbolLayerWidget::updatePreviewImage()
32033216
}
32043217

32053218

3206-
/// @cond PRIVATE
3207-
3208-
QgsSvgListModel::QgsSvgListModel( QObject* parent ) : QAbstractListModel( parent )
3209-
{
3210-
mSvgFiles = QgsSymbolLayerUtils::listSvgFiles();
3211-
}
3212-
3213-
QgsSvgListModel::QgsSvgListModel( QObject* parent, const QString& path ) : QAbstractListModel( parent )
3214-
{
3215-
mSvgFiles = QgsSymbolLayerUtils::listSvgFilesAt( path );
3216-
}
3217-
3218-
int QgsSvgListModel::rowCount( const QModelIndex& parent ) const
3219-
{
3220-
Q_UNUSED( parent );
3221-
return mSvgFiles.count();
3222-
}
3223-
3224-
QVariant QgsSvgListModel::data( const QModelIndex& index, int role ) const
3225-
{
3226-
QString entry = mSvgFiles.at( index.row() );
3227-
3228-
if ( role == Qt::DecorationRole ) // icon
3229-
{
3230-
QPixmap pixmap;
3231-
if ( !QPixmapCache::find( entry, pixmap ) )
3232-
{
3233-
// render SVG file
3234-
QColor fill, outline;
3235-
double outlineWidth, fillOpacity, outlineOpacity;
3236-
bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
3237-
bool hasDefaultFillColor = false, hasDefaultFillOpacity = false, hasDefaultOutlineColor = false,
3238-
hasDefaultOutlineWidth = false, hasDefaultOutlineOpacity = false;
3239-
QgsSvgCache::instance()->containsParams( entry, fillParam, hasDefaultFillColor, fill,
3240-
fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
3241-
outlineParam, hasDefaultOutlineColor, outline,
3242-
outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
3243-
outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
3244-
3245-
//if defaults not set in symbol, use these values
3246-
if ( !hasDefaultFillColor )
3247-
fill = QColor( 200, 200, 200 );
3248-
fill.setAlphaF( hasDefaultFillOpacity ? fillOpacity : 1.0 );
3249-
if ( !hasDefaultOutlineColor )
3250-
outline = Qt::black;
3251-
outline.setAlphaF( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
3252-
if ( !hasDefaultOutlineWidth )
3253-
outlineWidth = 0.6;
3254-
3255-
bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
3256-
const QImage& img = QgsSvgCache::instance()->svgAsImage( entry, 30.0, fill, outline, outlineWidth, 3.5 /*appr. 88 dpi*/, 1.0, fitsInCache );
3257-
pixmap = QPixmap::fromImage( img );
3258-
QPixmapCache::insert( entry, pixmap );
3259-
}
3260-
3261-
return pixmap;
3262-
}
3263-
else if ( role == Qt::UserRole || role == Qt::ToolTipRole )
3264-
{
3265-
return entry;
3266-
}
3267-
3268-
return QVariant();
3269-
}
3270-
3271-
3272-
QgsSvgGroupsModel::QgsSvgGroupsModel( QObject* parent ) : QStandardItemModel( parent )
3273-
{
3274-
QStringList svgPaths = QgsApplication::svgPaths();
3275-
QStandardItem *parentItem = invisibleRootItem();
3276-
3277-
for ( int i = 0; i < svgPaths.size(); i++ )
3278-
{
3279-
QDir dir( svgPaths[i] );
3280-
QStandardItem *baseGroup;
3281-
3282-
if ( dir.path().contains( QgsApplication::pkgDataPath() ) )
3283-
{
3284-
baseGroup = new QStandardItem( QString( "App Symbols" ) );
3285-
}
3286-
else if ( dir.path().contains( QgsApplication::qgisSettingsDirPath() ) )
3287-
{
3288-
baseGroup = new QStandardItem( QString( "User Symbols" ) );
3289-
}
3290-
else
3291-
{
3292-
baseGroup = new QStandardItem( dir.dirName() );
3293-
}
3294-
baseGroup->setData( QVariant( svgPaths[i] ) );
3295-
baseGroup->setEditable( false );
3296-
baseGroup->setCheckable( false );
3297-
baseGroup->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
3298-
baseGroup->setToolTip( dir.path() );
3299-
parentItem->appendRow( baseGroup );
3300-
createTree( baseGroup );
3301-
QgsDebugMsg( QString( "SVG base path %1: %2" ).arg( i ).arg( baseGroup->data().toString() ) );
3302-
}
3303-
}
3304-
3305-
void QgsSvgGroupsModel::createTree( QStandardItem*& parentGroup )
3306-
{
3307-
QDir parentDir( parentGroup->data().toString() );
3308-
Q_FOREACH ( const QString& item, parentDir.entryList( QDir::Dirs | QDir::NoDotAndDotDot ) )
3309-
{
3310-
QStandardItem* group = new QStandardItem( item );
3311-
group->setData( QVariant( parentDir.path() + '/' + item ) );
3312-
group->setEditable( false );
3313-
group->setCheckable( false );
3314-
group->setToolTip( parentDir.path() + '/' + item );
3315-
group->setIcon( QgsApplication::style()->standardIcon( QStyle::SP_DirIcon ) );
3316-
parentGroup->appendRow( group );
3317-
createTree( group );
3318-
}
3319-
}
3320-
3321-
3322-
/// @endcond
3323-
3324-
3325-
3326-
3327-
3328-
33293219
QgsGeometryGeneratorSymbolLayerWidget::QgsGeometryGeneratorSymbolLayerWidget( const QgsVectorLayer* vl, QWidget* parent )
33303220
: QgsSymbolLayerWidget( parent, vl )
33313221
, mLayer( nullptr )

‎src/gui/symbology-ng/qgssymbollayerwidget.h

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -707,39 +707,6 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerWidget : public QgsSymbolLayerWidget,
707707
};
708708

709709

710-
///@cond PRIVATE
711-
712-
class QgsSvgListModel : public QAbstractListModel
713-
{
714-
Q_OBJECT
715-
716-
public:
717-
explicit QgsSvgListModel( QObject* parent );
718-
719-
// Constructor to create model for icons in a specific path
720-
QgsSvgListModel( QObject* parent, const QString& path );
721-
722-
int rowCount( const QModelIndex & parent = QModelIndex() ) const override;
723-
724-
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
725-
726-
protected:
727-
QStringList mSvgFiles;
728-
};
729-
730-
class QgsSvgGroupsModel : public QStandardItemModel
731-
{
732-
Q_OBJECT
733-
734-
public:
735-
explicit QgsSvgGroupsModel( QObject* parent );
736-
737-
private:
738-
void createTree( QStandardItem* &parentGroup );
739-
};
740-
741-
///@endcond
742-
743710
#include "ui_qgsgeometrygeneratorwidgetbase.h"
744711

745712
class QgsGeometryGeneratorSymbolLayer;

‎src/ui/symbollayer/widget_svgfill.ui

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<rect>
77
<x>0</x>
88
<y>0</y>
9-
<width>270</width>
9+
<width>300</width>
1010
<height>459</height>
1111
</rect>
1212
</property>
@@ -280,12 +280,27 @@
280280
<verstretch>0</verstretch>
281281
</sizepolicy>
282282
</property>
283+
<property name="iconSize">
284+
<size>
285+
<width>32</width>
286+
<height>32</height>
287+
</size>
288+
</property>
283289
<property name="flow">
284290
<enum>QListView::LeftToRight</enum>
285291
</property>
292+
<property name="resizeMode">
293+
<enum>QListView::Adjust</enum>
294+
</property>
286295
<property name="layoutMode">
287296
<enum>QListView::Batched</enum>
288297
</property>
298+
<property name="gridSize">
299+
<size>
300+
<width>36</width>
301+
<height>36</height>
302+
</size>
303+
</property>
289304
<property name="viewMode">
290305
<enum>QListView::IconMode</enum>
291306
</property>
@@ -306,12 +321,6 @@
306321
</layout>
307322
</widget>
308323
<customwidgets>
309-
<customwidget>
310-
<class>QgsColorButton</class>
311-
<extends>QToolButton</extends>
312-
<header>qgscolorbutton.h</header>
313-
<container>1</container>
314-
</customwidget>
315324
<customwidget>
316325
<class>QgsDataDefinedButton</class>
317326
<extends>QToolButton</extends>
@@ -328,6 +337,12 @@
328337
<header>qgsunitselectionwidget.h</header>
329338
<container>1</container>
330339
</customwidget>
340+
<customwidget>
341+
<class>QgsColorButton</class>
342+
<extends>QToolButton</extends>
343+
<header>qgscolorbutton.h</header>
344+
<container>1</container>
345+
</customwidget>
331346
</customwidgets>
332347
<tabstops>
333348
<tabstop>mTextureWidthSpinBox</tabstop>

0 commit comments

Comments
 (0)
Please sign in to comment.