Skip to content

Commit bb2450e

Browse files
committedOct 6, 2016
Fix SVG preview blocks QGIS (fix #14255)
Now SVG preview loading occurs in a background thread so that dialogs can open instantly Also guard against circular symbolic links in SVG selector widget (cherry-picked from c60c4f7)
1 parent 971b413 commit bb2450e

File tree

6 files changed

+466
-217
lines changed

6 files changed

+466
-217
lines changed
 

‎src/core/qgsapplication.cpp

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

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

719731
/*!

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

Lines changed: 259 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 = QgsSymbolLayerV2Utils::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 = QgsSymbolLayerV2Utils::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

‎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/qgssymbollayerv2widget.cpp

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

3839
#include "qgslogger.h"
3940
#include "qgssizescalewidget.h"
@@ -1821,8 +1822,11 @@ QgsSvgMarkerSymbolLayerV2Widget::~QgsSvgMarkerSymbolLayerV2Widget()
18211822

18221823
void QgsSvgMarkerSymbolLayerV2Widget::populateList()
18231824
{
1824-
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( viewGroups );
1825+
QAbstractItemModel* oldModel = viewGroups->model();
1826+
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( viewGroups );
18251827
viewGroups->setModel( g );
1828+
delete oldModel;
1829+
18261830
// Set the tree expanded at the first level
18271831
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
18281832
for ( int i = 0; i < rows; i++ )
@@ -1831,19 +1835,22 @@ void QgsSvgMarkerSymbolLayerV2Widget::populateList()
18311835
}
18321836

18331837
// Initally load the icons in the List view without any grouping
1834-
QgsSvgListModel* m = new QgsSvgListModel( viewImages );
1838+
oldModel = viewImages->model();
1839+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages );
18351840
viewImages->setModel( m );
1841+
delete oldModel;
18361842
}
18371843

18381844
void QgsSvgMarkerSymbolLayerV2Widget::populateIcons( const QModelIndex& idx )
18391845
{
18401846
QString path = idx.data( Qt::UserRole + 1 ).toString();
18411847

1842-
QgsSvgListModel* m = new QgsSvgListModel( viewImages, path );
1848+
QAbstractItemModel* oldModel = viewImages->model();
1849+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( viewImages, path );
18431850
viewImages->setModel( m );
1851+
delete oldModel;
18441852

18451853
connect( viewImages->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setName( const QModelIndex& ) ) );
1846-
emit changed();
18471854
}
18481855

18491856
void QgsSvgMarkerSymbolLayerV2Widget::setGuiForSvg( const QgsSvgMarkerSymbolLayerV2* layer )
@@ -2304,28 +2311,34 @@ void QgsSVGFillSymbolLayerWidget::setFile( const QModelIndex& item )
23042311

23052312
void QgsSVGFillSymbolLayerWidget::insertIcons()
23062313
{
2307-
QgsSvgGroupsModel* g = new QgsSvgGroupsModel( mSvgTreeView );
2314+
QAbstractItemModel* oldModel = mSvgTreeView->model();
2315+
QgsSvgSelectorGroupsModel* g = new QgsSvgSelectorGroupsModel( mSvgTreeView );
23082316
mSvgTreeView->setModel( g );
2317+
delete oldModel;
2318+
23092319
// Set the tree expanded at the first level
23102320
int rows = g->rowCount( g->indexFromItem( g->invisibleRootItem() ) );
23112321
for ( int i = 0; i < rows; i++ )
23122322
{
23132323
mSvgTreeView->setExpanded( g->indexFromItem( g->item( i ) ), true );
23142324
}
23152325

2316-
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView );
2326+
oldModel = mSvgListView->model();
2327+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView );
23172328
mSvgListView->setModel( m );
2329+
delete oldModel;
23182330
}
23192331

23202332
void QgsSVGFillSymbolLayerWidget::populateIcons( const QModelIndex& idx )
23212333
{
23222334
QString path = idx.data( Qt::UserRole + 1 ).toString();
23232335

2324-
QgsSvgListModel* m = new QgsSvgListModel( mSvgListView, path );
2336+
QAbstractItemModel* oldModel = mSvgListView->model();
2337+
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel( mSvgListView, path );
23252338
mSvgListView->setModel( m );
2339+
delete oldModel;
23262340

23272341
connect( mSvgListView->selectionModel(), SIGNAL( currentChanged( const QModelIndex&, const QModelIndex& ) ), this, SLOT( setFile( const QModelIndex& ) ) );
2328-
emit changed();
23292342
}
23302343

23312344

@@ -3222,129 +3235,6 @@ void QgsRasterFillSymbolLayerWidget::updatePreviewImage()
32223235
}
32233236

32243237

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

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

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -734,39 +734,6 @@ class GUI_EXPORT QgsCentroidFillSymbolLayerV2Widget : public QgsSymbolLayerV2Wid
734734
};
735735

736736

737-
///@cond PRIVATE
738-
739-
class QgsSvgListModel : public QAbstractListModel
740-
{
741-
Q_OBJECT
742-
743-
public:
744-
explicit QgsSvgListModel( QObject* parent );
745-
746-
// Constructor to create model for icons in a specific path
747-
QgsSvgListModel( QObject* parent, const QString& path );
748-
749-
int rowCount( const QModelIndex & parent = QModelIndex() ) const override;
750-
751-
QVariant data( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
752-
753-
protected:
754-
QStringList mSvgFiles;
755-
};
756-
757-
class QgsSvgGroupsModel : public QStandardItemModel
758-
{
759-
Q_OBJECT
760-
761-
public:
762-
explicit QgsSvgGroupsModel( QObject* parent );
763-
764-
private:
765-
void createTree( QStandardItem* &parentGroup );
766-
};
767-
768-
///@endcond
769-
770737
#include "ui_qgsgeometrygeneratorwidgetbase.h"
771738

772739
class QgsGeometryGeneratorSymbolLayerV2;

‎src/ui/symbollayer/widget_svgfill.ui

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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>

0 commit comments

Comments
 (0)
Please sign in to comment.