32
32
#include < QStyle>
33
33
#include < QTime>
34
34
35
+ // QgsSvgSelectorLoader
35
36
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
+
55
+ // start with a small initial timeout (ms)
56
+ mTimerThreshold = 10 ;
57
+ mTimer .start ();
58
+
59
+ loadPath ( mPath );
60
+
61
+ if ( !mQueuedSvgs .isEmpty () )
62
+ {
63
+ // make sure we notify model of any remaining queued svgs (ie svgs added since last foundSvgs() signal was emitted)
64
+ emit foundSvgs ( mQueuedSvgs );
65
+ }
66
+ mQueuedSvgs .clear ();
67
+ }
68
+
69
+ void QgsSvgSelectorLoader::stop ()
70
+ {
71
+ mCancelled = true ;
72
+ while ( isRunning () ) {}
73
+ }
74
+
75
+ void QgsSvgSelectorLoader::loadPath ( const QString& path )
76
+ {
77
+ if ( mCancelled )
78
+ return ;
79
+
80
+ // QgsDebugMsg( QString( "loading path: %1" ).arg( path ) );
81
+
82
+ if ( path.isEmpty () )
83
+ {
84
+ QStringList svgPaths = QgsApplication::svgPaths ();
85
+ Q_FOREACH ( const QString& svgPath, svgPaths )
86
+ {
87
+ if ( mCancelled )
88
+ return ;
89
+
90
+ loadPath ( svgPath );
91
+ }
92
+ }
93
+ else
94
+ {
95
+ loadImages ( path );
96
+
97
+ QDir dir ( path );
98
+ Q_FOREACH ( const QString& item, dir.entryList ( QDir::Dirs | QDir::NoDotAndDotDot ) )
99
+ {
100
+ if ( mCancelled )
101
+ return ;
102
+
103
+ QString newPath = dir.path () + ' /' + item;
104
+ loadPath ( newPath );
105
+ // QgsDebugMsg( QString( "added path: %1" ).arg( newPath ) );
106
+ }
107
+ }
108
+ }
109
+
110
+ void QgsSvgSelectorLoader::loadImages ( const QString& path )
111
+ {
112
+ QDir dir ( path );
113
+ Q_FOREACH ( const QString& item, dir.entryList ( QStringList ( " *.svg" ), QDir::Files ) )
114
+ {
115
+ if ( mCancelled )
116
+ return ;
117
+
118
+ // TODO test if it is correct SVG
119
+ QString svgPath = dir.path () + ' /' + item;
120
+ // QgsDebugMsg( QString( "adding svg: %1" ).arg( svgPath ) );
121
+
122
+ // add it to the list of queued SVGs
123
+ mQueuedSvgs << svgPath;
124
+
125
+ // we need to avoid spamming the model with notifications about new svgs, so foundSvgs
126
+ // is only emitted for blocks of SVGs (otherwise the view goes all flickery)
127
+ if ( mTimer .elapsed () > mTimerThreshold )
128
+ {
129
+ emit foundSvgs ( mQueuedSvgs );
130
+ mQueuedSvgs .clear ();
131
+
132
+ // increase the timer threshold - this ensures that the first lots of svgs loaded are added
133
+ // to the view quickly, but as the list grows new svgs are added at a slower rate.
134
+ // ie, good for initial responsiveness but avoid being spammy as the list grows.
135
+ if ( mTimerThreshold < 1000 )
136
+ mTimerThreshold *= 2 ;
137
+ mTimer .restart ();
138
+ }
139
+ }
140
+ }
141
+
142
+
143
+ //
144
+ // QgsSvgGroupLoader
145
+ //
146
+
147
+ QgsSvgGroupLoader::QgsSvgGroupLoader ( QObject* parent )
148
+ : QThread( parent )
149
+ , mCancelled( false )
150
+ {
151
+
152
+ }
153
+
154
+ QgsSvgGroupLoader::~QgsSvgGroupLoader ()
155
+ {
156
+ stop ();
157
+ }
158
+
159
+ void QgsSvgGroupLoader::run ()
160
+ {
161
+ mCancelled = false ;
162
+
163
+ while ( !mCancelled && !mParentPaths .isEmpty () )
164
+ {
165
+ QString parentPath = mParentPaths .takeFirst ();
166
+ loadGroup ( parentPath );
167
+ }
168
+ }
169
+
170
+ void QgsSvgGroupLoader::stop ()
171
+ {
172
+ mCancelled = true ;
173
+ while ( isRunning () ) {}
174
+ }
175
+
176
+ void QgsSvgGroupLoader::loadGroup ( const QString& parentPath )
177
+ {
178
+ QDir parentDir ( parentPath );
179
+
180
+ Q_FOREACH ( const QString& item, parentDir.entryList ( QDir::Dirs | QDir::NoDotAndDotDot ) )
181
+ {
182
+ if ( mCancelled )
183
+ return ;
184
+
185
+ emit foundPath ( parentPath, item );
186
+ mParentPaths .append ( parentDir.path () + ' /' + item );
187
+ }
188
+ }
189
+
190
+ // /@endcond
191
+
192
+ // ,
193
+ // QgsSvgSelectorListModel
194
+ //
37
195
38
196
QgsSvgSelectorListModel::QgsSvgSelectorListModel ( QObject* parent )
39
197
: QAbstractListModel( parent )
198
+ , mSvgLoader( new QgsSvgSelectorLoader( this ) )
40
199
{
41
- mSvgFiles = QgsSymbolLayerUtils::listSvgFiles ();
200
+ mSvgLoader ->setPath ( QString () );
201
+ connect ( mSvgLoader , SIGNAL ( foundSvgs ( QStringList ) ), this , SLOT ( addSvgs ( QStringList ) ) );
202
+ mSvgLoader ->start ();
42
203
}
43
204
44
- // Constructor to create model for icons in a specific path
45
205
QgsSvgSelectorListModel::QgsSvgSelectorListModel ( QObject* parent, const QString& path )
46
206
: QAbstractListModel( parent )
207
+ , mSvgLoader( new QgsSvgSelectorLoader( this ) )
47
208
{
48
- mSvgFiles = QgsSymbolLayerUtils::listSvgFilesAt ( path );
209
+ mSvgLoader ->setPath ( path );
210
+ connect ( mSvgLoader , SIGNAL ( foundSvgs ( QStringList ) ), this , SLOT ( addSvgs ( QStringList ) ) );
211
+ mSvgLoader ->start ();
49
212
}
50
213
51
- int QgsSvgSelectorListModel::rowCount ( const QModelIndex & parent ) const
214
+ int QgsSvgSelectorListModel::rowCount ( const QModelIndex& parent ) const
52
215
{
53
216
Q_UNUSED ( parent );
54
217
return mSvgFiles .count ();
55
218
}
56
219
57
- QVariant QgsSvgSelectorListModel::data ( const QModelIndex & index, int role ) const
220
+ QPixmap QgsSvgSelectorListModel::createPreview ( const QString& entry ) const
221
+ {
222
+ // render SVG file
223
+ QColor fill, outline ;
224
+ double outlineWidth, fillOpacity, outlineOpacity;
225
+ bool fillParam, fillOpacityParam, outlineParam, outlineWidthParam, outlineOpacityParam;
226
+ bool hasDefaultFillColor = false , hasDefaultFillOpacity = false , hasDefaultOutlineColor = false ,
227
+ hasDefaultOutlineWidth = false , hasDefaultOutlineOpacity = false ;
228
+ QgsSvgCache::instance ()->containsParams ( entry, fillParam, hasDefaultFillColor, fill,
229
+ fillOpacityParam, hasDefaultFillOpacity, fillOpacity,
230
+ outlineParam, hasDefaultOutlineColor, outline ,
231
+ outlineWidthParam, hasDefaultOutlineWidth, outlineWidth,
232
+ outlineOpacityParam, hasDefaultOutlineOpacity, outlineOpacity );
233
+
234
+ // if defaults not set in symbol, use these values
235
+ if ( !hasDefaultFillColor )
236
+ fill = QColor ( 200 , 200 , 200 );
237
+ fill.setAlphaF ( hasDefaultFillOpacity ? fillOpacity : 1.0 );
238
+ if ( !hasDefaultOutlineColor )
239
+ outline = Qt::black;
240
+ outline .setAlphaF ( hasDefaultOutlineOpacity ? outlineOpacity : 1.0 );
241
+ if ( !hasDefaultOutlineWidth )
242
+ outlineWidth = 0.2 ;
243
+
244
+ bool fitsInCache; // should always fit in cache at these sizes (i.e. under 559 px ^ 2, or half cache size)
245
+ const QImage& img = QgsSvgCache::instance ()->svgAsImage ( entry, 30.0 , fill, outline , outlineWidth, 3.5 /* appr. 88 dpi*/ , 1.0 , fitsInCache );
246
+ return QPixmap::fromImage ( img );
247
+ }
248
+
249
+ QVariant QgsSvgSelectorListModel::data ( const QModelIndex& index, int role ) const
58
250
{
59
251
QString entry = mSvgFiles .at ( index .row () );
60
252
@@ -63,31 +255,7 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
63
255
QPixmap pixmap;
64
256
if ( !QPixmapCache::find ( entry, pixmap ) )
65
257
{
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 );
258
+ pixmap = createPreview ( entry );
91
259
QPixmapCache::insert ( entry, pixmap );
92
260
}
93
261
@@ -101,18 +269,30 @@ QVariant QgsSvgSelectorListModel::data( const QModelIndex & index, int role ) co
101
269
return QVariant ();
102
270
}
103
271
272
+ void QgsSvgSelectorListModel::addSvgs ( const QStringList& svgs )
273
+ {
274
+ beginInsertRows ( QModelIndex (), mSvgFiles .count (), mSvgFiles .count () + svgs.size () - 1 );
275
+ mSvgFiles .append ( svgs );
276
+ endInsertRows ();
277
+ }
278
+
279
+
280
+
281
+
104
282
105
283
// --- QgsSvgSelectorGroupsModel
106
284
107
285
QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel ( QObject* parent )
108
286
: QStandardItemModel( parent )
287
+ , mLoader( new QgsSvgGroupLoader( this ) )
109
288
{
110
289
QStringList svgPaths = QgsApplication::svgPaths ();
111
290
QStandardItem *parentItem = invisibleRootItem ();
291
+ QStringList parentPaths;
112
292
113
293
for ( int i = 0 ; i < svgPaths.size (); i++ )
114
294
{
115
- QDir dir ( svgPaths[i] );
295
+ QDir dir ( svgPaths. at ( i ) );
116
296
QStandardItem *baseGroup;
117
297
118
298
if ( dir.path ().contains ( QgsApplication::pkgDataPath () ) )
@@ -127,31 +307,41 @@ QgsSvgSelectorGroupsModel::QgsSvgSelectorGroupsModel( QObject* parent )
127
307
{
128
308
baseGroup = new QStandardItem ( dir.dirName () );
129
309
}
130
- baseGroup->setData ( QVariant ( svgPaths[i] ) );
310
+ baseGroup->setData ( QVariant ( svgPaths. at ( i ) ) );
131
311
baseGroup->setEditable ( false );
132
312
baseGroup->setCheckable ( false );
133
313
baseGroup->setIcon ( QgsApplication::style ()->standardIcon ( QStyle::SP_DirIcon ) );
134
314
baseGroup->setToolTip ( dir.path () );
135
315
parentItem->appendRow ( baseGroup );
136
- createTree ( baseGroup );
316
+ parentPaths << svgPaths.at ( i );
317
+ mPathItemHash .insert ( svgPaths.at ( i ), baseGroup );
137
318
QgsDebugMsg ( QString ( " SVG base path %1: %2" ).arg ( i ).arg ( baseGroup->data ().toString () ) );
138
319
}
320
+ mLoader ->setParentPaths ( parentPaths );
321
+ connect ( mLoader , SIGNAL ( foundPath ( QString, QString ) ), this , SLOT ( addPath ( QString, QString ) ) );
322
+ mLoader ->start ();
139
323
}
140
324
141
- void QgsSvgSelectorGroupsModel::createTree ( QStandardItem* &parentGroup )
325
+ QgsSvgSelectorGroupsModel::~QgsSvgSelectorGroupsModel ( )
142
326
{
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
- }
327
+ mLoader ->stop ();
328
+ }
329
+
330
+ void QgsSvgSelectorGroupsModel::addPath ( const QString& parentPath, const QString& item )
331
+ {
332
+ QStandardItem* parentGroup = mPathItemHash .value ( parentPath );
333
+ if ( !parentGroup )
334
+ return ;
335
+
336
+ QString fullPath = parentPath + ' /' + item;
337
+ QStandardItem* group = new QStandardItem ( item );
338
+ group->setData ( QVariant ( fullPath ) );
339
+ group->setEditable ( false );
340
+ group->setCheckable ( false );
341
+ group->setToolTip ( fullPath );
342
+ group->setIcon ( QgsApplication::style ()->standardIcon ( QStyle::SP_DirIcon ) );
343
+ parentGroup->appendRow ( group );
344
+ mPathItemHash .insert ( fullPath, group );
155
345
}
156
346
157
347
@@ -250,11 +440,14 @@ void QgsSvgSelectorWidget::populateIcons( const QModelIndex& idx )
250
440
{
251
441
QString path = idx.data ( Qt::UserRole + 1 ).toString ();
252
442
443
+ QAbstractItemModel* oldModel = mImagesListView ->model ();
253
444
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel ( mImagesListView , path );
254
445
mImagesListView ->setModel ( m );
446
+ delete oldModel; // explicitly delete old model to force any background threads to stop
255
447
256
448
connect ( mImagesListView ->selectionModel (), SIGNAL ( currentChanged ( const QModelIndex&, const QModelIndex& ) ),
257
449
this , SLOT ( svgSelectionChanged ( const QModelIndex& ) ) );
450
+
258
451
}
259
452
260
453
void QgsSvgSelectorWidget::on_mFilePushButton_clicked ()
@@ -319,8 +512,10 @@ void QgsSvgSelectorWidget::populateList()
319
512
}
320
513
321
514
// Initally load the icons in the List view without any grouping
515
+ QAbstractItemModel* oldModel = mImagesListView ->model ();
322
516
QgsSvgSelectorListModel* m = new QgsSvgSelectorListModel ( mImagesListView );
323
517
mImagesListView ->setModel ( m );
518
+ delete oldModel; // explicitly delete old model to force any background threads to stop
324
519
}
325
520
326
521
// -- QgsSvgSelectorDialog
@@ -357,3 +552,4 @@ QgsSvgSelectorDialog::~QgsSvgSelectorDialog()
357
552
QSettings settings;
358
553
settings.setValue ( " /Windows/SvgSelectorDialog/geometry" , saveGeometry () );
359
554
}
555
+
0 commit comments