1 /****************************************************************************************
2 * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org> *
3 * Copyright (c) 2008 Jeff Mitchell <kde-dev@emailgoeshere.com> *
4 * Copyright (c) 2009-2013 Mark Kretschmann <kretschmann@kde.org> *
5 * *
6 * This program is free software; you can redistribute it and/or modify it under *
7 * the terms of the GNU General Public License as published by the Free Software *
8 * Foundation; either version 2 of the License, or (at your option) any later *
9 * version. *
10 * *
11 * This program is distributed in the hope that it will be useful, but WITHOUT ANY *
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A *
13 * PARTICULAR PURPOSE. See the GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License along with *
16 * this program. If not, see <http://www.gnu.org/licenses/>. *
17 ****************************************************************************************/
18
19 #define DEBUG_PREFIX "SvgHandler"
20
21 #include "SvgHandler.h"
22
23 #include "App.h"
24 #include "EngineController.h"
25 #include "MainWindow.h"
26 #include "PaletteHandler.h"
27 #include "SvgTinter.h"
28 #include "core/meta/Meta.h"
29 #include "core/support/Debug.h"
30 #include "covermanager/CoverCache.h"
31 #include "moodbar/MoodbarManager.h"
32
33 #include <KColorScheme>
34 #include <KColorUtils>
35
36 #include <QHash>
37 #include <QPainter>
38 #include <QPalette>
39 #include <QReadLocker>
40 #include <QStandardPaths>
41 #include <QStyleOptionSlider>
42 #include <QSvgRenderer>
43 #include <QWriteLocker>
44
45
46 namespace The {
47 static SvgHandler* s_SvgHandler_instance = 0;
48
svgHandler()49 SvgHandler* svgHandler()
50 {
51 if( !s_SvgHandler_instance )
52 s_SvgHandler_instance = new SvgHandler();
53
54 return s_SvgHandler_instance;
55 }
56 }
57
SvgHandler(QObject * parent)58 SvgHandler::SvgHandler( QObject* parent )
59 : QObject( parent )
60 , m_cache( new KImageCache( "Amarok-pixmaps", 20 * 1024 ) )
61 , m_themeFile( "amarok/images/default-theme-clean.svg" ) // //use default theme
62 , m_customTheme( false )
63 {
64 DEBUG_BLOCK
65 connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &SvgHandler::discardCache );
66 }
67
~SvgHandler()68 SvgHandler::~SvgHandler()
69 {
70 DEBUG_BLOCK
71 delete m_cache;
72 qDeleteAll( m_renderers );
73 m_renderers.clear();
74
75 The::s_SvgHandler_instance = 0;
76 }
77
78
loadSvg(const QString & name,bool forceCustomTheme)79 bool SvgHandler::loadSvg( const QString& name, bool forceCustomTheme )
80 {
81 const QString &svgFilename = m_customTheme || forceCustomTheme ? name : QStandardPaths::locate( QStandardPaths::GenericDataLocation, name );
82 QSvgRenderer *renderer = new QSvgRenderer( The::svgTinter()->tint( svgFilename ) );
83
84 if ( !renderer->isValid() )
85 {
86 debug() << "Bluddy 'ell mateys, aye canna' load ya Ess Vee Gee at " << svgFilename;
87 delete renderer;
88 return false;
89 }
90 QWriteLocker writeLocker( &m_lock );
91
92 if( m_renderers[name] )
93 delete m_renderers[name];
94
95 m_renderers[name] = renderer;
96 return true;
97 }
98
getRenderer(const QString & name)99 QSvgRenderer* SvgHandler::getRenderer( const QString& name )
100 {
101 QReadLocker readLocker( &m_lock );
102 if( ! m_renderers[name] )
103 {
104 readLocker.unlock();
105 if( !loadSvg( name ) )
106 {
107 QWriteLocker writeLocker( &m_lock );
108 m_renderers[name] = new QSvgRenderer();
109 }
110 readLocker.relock();
111 }
112 return m_renderers[name];
113 }
114
getRenderer()115 QSvgRenderer * SvgHandler::getRenderer()
116 {
117 return getRenderer( m_themeFile );
118 }
119
renderSvg(const QString & name,const QString & keyname,int width,int height,const QString & element,bool skipCache,const qreal opacity)120 QPixmap SvgHandler::renderSvg( const QString &name,
121 const QString& keyname,
122 int width,
123 int height,
124 const QString& element,
125 bool skipCache,
126 const qreal opacity )
127 {
128 QString key;
129 if( !skipCache )
130 {
131 key = QStringLiteral("%1:%2x%3")
132 .arg( keyname )
133 .arg( width )
134 .arg( height );
135 }
136
137 QPixmap pixmap;
138 if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
139 {
140 pixmap = QPixmap( width, height );
141 pixmap.fill( Qt::transparent );
142
143 QReadLocker readLocker( &m_lock );
144 if( ! m_renderers[name] )
145 {
146 readLocker.unlock();
147 if( !loadSvg( name ) )
148 {
149 return pixmap;
150 }
151 readLocker.relock();
152 }
153
154 QPainter pt( &pixmap );
155 pt.setOpacity( opacity );
156
157 if ( element.isEmpty() )
158 m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
159 else
160 m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
161
162 if( !skipCache )
163 m_cache->insertPixmap( key, pixmap );
164 }
165
166 return pixmap;
167 }
168
renderSvg(const QUrl & url,const QString & keyname,int width,int height,const QString & element,bool skipCache,const qreal opacity)169 QPixmap SvgHandler::renderSvg( const QUrl& url, const QString& keyname, int width, int height, const QString& element, bool skipCache, const qreal opacity )
170 {
171 QString key;
172 if( !skipCache )
173 {
174 key = QStringLiteral("%1:%2x%3")
175 .arg( keyname )
176 .arg( width )
177 .arg( height );
178 }
179
180 QPixmap pixmap;
181 if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
182 {
183 pixmap = QPixmap( width, height );
184 pixmap.fill( Qt::transparent );
185
186 QString name = url.isLocalFile() ? url.toLocalFile() : ":" + url.path();
187 QReadLocker readLocker( &m_lock );
188 if( ! m_renderers[name] )
189 {
190 readLocker.unlock();
191 if( !loadSvg( name, true ) )
192 {
193 return pixmap;
194 }
195 readLocker.relock();
196 }
197
198 QPainter pt( &pixmap );
199 pt.setOpacity( opacity );
200
201 if ( element.isEmpty() )
202 m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
203 else
204 m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
205
206 if( !skipCache )
207 m_cache->insertPixmap( key, pixmap );
208 }
209
210 return pixmap;
211 }
212
renderSvg(const QString & keyname,int width,int height,const QString & element,bool skipCache,const qreal opacity)213 QPixmap SvgHandler::renderSvg(const QString & keyname, int width, int height, const QString & element, bool skipCache, const qreal opacity )
214 {
215 return renderSvg( m_themeFile, keyname, width, height, element, skipCache, opacity );
216 }
217
renderSvgWithDividers(const QString & keyname,int width,int height,const QString & element)218 QPixmap SvgHandler::renderSvgWithDividers(const QString & keyname, int width, int height, const QString & element)
219 {
220 const QString key = QStringLiteral("%1:%2x%3-div")
221 .arg( keyname )
222 .arg( width )
223 .arg( height );
224
225 QPixmap pixmap;
226 if ( !m_cache->findPixmap( key, &pixmap ) ) {
227 // debug() << QStringLiteral("svg %1 not in cache...").arg( key );
228
229 pixmap = QPixmap( width, height );
230 pixmap.fill( Qt::transparent );
231
232 QString name = m_themeFile;
233
234 QReadLocker readLocker( &m_lock );
235 if( ! m_renderers[name] )
236 {
237 readLocker.unlock();
238 if( ! loadSvg( name ) )
239 {
240 return pixmap;
241 }
242 readLocker.relock();
243 }
244
245 QPainter pt( &pixmap );
246 if ( element.isEmpty() )
247 m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
248 else
249 m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
250
251
252 //add dividers. 5% spacing on each side
253 int margin = width / 20;
254
255 m_renderers[name]->render( &pt, "divider_top", QRectF( margin, 0 , width - 1 * margin, 1 ) );
256 m_renderers[name]->render( &pt, "divider_bottom", QRectF( margin, height - 1 , width - 2 * margin, 1 ) );
257
258 m_cache->insertPixmap( key, pixmap );
259 }
260
261 return pixmap;
262 }
263
264
reTint()265 void SvgHandler::reTint()
266 {
267 The::svgTinter()->init();
268 if ( !loadSvg( m_themeFile ))
269 warning() << "Unable to load theme file: " << m_themeFile;
270 Q_EMIT retinted();
271 }
272
themeFile()273 QString SvgHandler::themeFile()
274 {
275 return m_themeFile;
276 }
277
setThemeFile(const QString & themeFile)278 void SvgHandler::setThemeFile( const QString & themeFile )
279 {
280 DEBUG_BLOCK
281 debug() << "got new theme file: " << themeFile;
282 m_themeFile = themeFile;
283 m_customTheme = true;
284 discardCache();
285 }
286
discardCache()287 void SvgHandler::discardCache()
288 {
289 //redraw entire app....
290 reTint();
291 m_cache->clear();
292
293 if( auto window = pApp->mainWindow() )
294 window->update();
295 }
296
297 QPixmap
imageWithBorder(Meta::AlbumPtr album,int size,int borderWidth)298 SvgHandler::imageWithBorder( Meta::AlbumPtr album, int size, int borderWidth )
299 {
300 const int imageSize = size - ( borderWidth * 2 );
301 const QString &loc = album->imageLocation( imageSize ).url();
302 const QString &key = !loc.isEmpty() ? loc : album->name();
303 return addBordersToPixmap( The::coverCache()->getCover( album, imageSize ), borderWidth, key );
304 }
305
addBordersToPixmap(const QPixmap & orgPixmap,int borderWidth,const QString & name,bool skipCache)306 QPixmap SvgHandler::addBordersToPixmap( const QPixmap &orgPixmap, int borderWidth, const QString &name, bool skipCache )
307 {
308 int newWidth = orgPixmap.width() + borderWidth * 2;
309 int newHeight = orgPixmap.height() + borderWidth *2;
310
311 QString key;
312 if( !skipCache )
313 {
314 key = QStringLiteral("%1:%2x%3b%4")
315 .arg( name )
316 .arg( newWidth )
317 .arg( newHeight )
318 .arg( borderWidth );
319 }
320
321 QPixmap pixmap;
322 if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
323 {
324 // Cache miss! We need to create the pixmap
325 // if skipCache is true, we might actually already have fetched the image, including borders from the cache....
326 // so we really need to create a blank pixmap here as well, to not pollute the cached pixmap
327 pixmap = QPixmap( newWidth, newHeight );
328 pixmap.fill( Qt::transparent );
329
330 QReadLocker readLocker( &m_lock );
331 if( !m_renderers[m_themeFile] )
332 {
333 readLocker.unlock();
334 if( !loadSvg( m_themeFile ) )
335 {
336 return pixmap;
337 }
338 readLocker.relock();
339 }
340
341 QPainter pt( &pixmap );
342
343 pt.drawPixmap( borderWidth, borderWidth, orgPixmap.width(), orgPixmap.height(), orgPixmap );
344
345 m_renderers[m_themeFile]->render( &pt, "cover_border_topleft", QRectF( 0, 0, borderWidth, borderWidth ) );
346 m_renderers[m_themeFile]->render( &pt, "cover_border_top", QRectF( borderWidth, 0, orgPixmap.width(), borderWidth ) );
347 m_renderers[m_themeFile]->render( &pt, "cover_border_topright", QRectF( newWidth - borderWidth , 0, borderWidth, borderWidth ) );
348 m_renderers[m_themeFile]->render( &pt, "cover_border_right", QRectF( newWidth - borderWidth, borderWidth, borderWidth, orgPixmap.height() ) );
349 m_renderers[m_themeFile]->render( &pt, "cover_border_bottomright", QRectF( newWidth - borderWidth, newHeight - borderWidth, borderWidth, borderWidth ) );
350 m_renderers[m_themeFile]->render( &pt, "cover_border_bottom", QRectF( borderWidth, newHeight - borderWidth, orgPixmap.width(), borderWidth ) );
351 m_renderers[m_themeFile]->render( &pt, "cover_border_bottomleft", QRectF( 0, newHeight - borderWidth, borderWidth, borderWidth ) );
352 m_renderers[m_themeFile]->render( &pt, "cover_border_left", QRectF( 0, borderWidth, borderWidth, orgPixmap.height() ) );
353
354 if( !skipCache )
355 m_cache->insertPixmap( key, pixmap );
356 }
357
358 return pixmap;
359 }
360
361 #if 0
362 void SvgHandler::paintCustomSlider( QPainter *p, int x, int y, int width, int height, qreal percentage, bool active )
363 {
364 int knobSize = height - 4;
365 int sliderRange = width - ( knobSize + 4 );
366 int knobRelPos = x + sliderRange * percentage + 2;
367 int knobY = y + ( height - knobSize ) / 2 + 1;
368
369 int sliderY = y + ( height / 2 ) - 1;
370
371
372 //first draw the played part
373 p->drawPixmap( x, sliderY,
374 renderSvg(
375 "new_slider_top_played",
376 width, 2,
377 "new_slider_top_played" ),
378 0, 0, knobRelPos - x, 2 );
379
380 //and then the unplayed part
381 p->drawPixmap( knobRelPos + 1, sliderY,
382 renderSvg(
383 "new_slider_top",
384 width, 2,
385 "new_slider_top" ),
386 knobRelPos + 1 - x, 0, -1, 2 );
387
388 //and then the bottom
389 p->drawPixmap( x, sliderY + 2,
390 renderSvg(
391 "new_slider_bottom",
392 width, 2,
393 "new_slider_bottom" ) );
394
395 //draw end markers
396 p->drawPixmap( x, y,
397 renderSvg(
398 "new_slider_end",
399 2, height,
400 "new_slider_end" ) );
401
402 p->drawPixmap( x + width - 2, y,
403 renderSvg(
404 "new_slider_end",
405 2, height,
406 "new_slider_endr" ) );
407
408
409 if ( active )
410 p->drawPixmap( knobRelPos, knobY,
411 renderSvg(
412 "new_slider_knob_active",
413 knobSize, knobSize,
414 "new_slider_knob_active" ) );
415 else
416 p->drawPixmap( knobRelPos, knobY,
417 renderSvg(
418 "new_slider_knob",
419 knobSize, knobSize,
420 "new_slider_knob" ) );
421 }
422 #endif
423
sliderKnobRect(const QRect & slider,qreal percent,bool inverse) const424 QRect SvgHandler::sliderKnobRect( const QRect &slider, qreal percent, bool inverse ) const
425 {
426 if ( inverse )
427 percent = 1.0 - percent;
428 const int knobSize = slider.height() - 4;
429 QRect ret( 0, 0, knobSize, knobSize );
430 ret.moveTo( slider.x() + qRound( ( slider.width() - knobSize ) * percent ), slider.y() + 1 );
431 return ret;
432 }
433
434 // Experimental, using a mockup from Nuno Pinheiro (new_slider_nuno)
paintCustomSlider(QPainter * p,QStyleOptionSlider * slider,qreal percentage,bool paintMoodbar)435 void SvgHandler::paintCustomSlider( QPainter *p, QStyleOptionSlider *slider, qreal percentage, bool paintMoodbar )
436 {
437 int sliderHeight = slider->rect.height() - 6;
438 const bool inverse = ( slider->orientation == Qt::Vertical ) ? slider->upsideDown :
439 ( (slider->direction == Qt::RightToLeft) != slider->upsideDown );
440 QRect knob = sliderKnobRect( slider->rect, percentage, inverse );
441 QPoint pt = slider->rect.topLeft() + QPoint( 0, 2 );
442
443 p->setRenderHint( QPainter::SmoothPixmapTransform );
444 //debug() << "rel: " << knobRelPos << ", width: " << width << ", height:" << height << ", %: " << percentage;
445
446 //if we should paint moodbar, paint this as the bottom layer
447 bool moodbarPainted = false;
448 if ( paintMoodbar )
449 {
450 Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
451 if ( currentTrack )
452 {
453 if( The::moodbarManager()->hasMoodbar( currentTrack ) )
454 {
455 QPixmap moodbar = The::moodbarManager()->getMoodbar( currentTrack, slider->rect.width() - sliderHeight, sliderHeight, inverse );
456 p->drawPixmap( pt, renderSvg( "moodbar_end_left", sliderHeight / 2, sliderHeight, "moodbar_end_left" ) );
457
458 pt.rx() += sliderHeight / 2;
459 p->drawPixmap( pt, moodbar );
460
461 pt.rx() += slider->rect.width() - sliderHeight;
462 p->drawPixmap( pt, renderSvg( "moodbar_end_right", sliderHeight / 2, sliderHeight, "moodbar_end_right" ) );
463
464 moodbarPainted = true;
465 }
466 }
467 }
468
469 if( !moodbarPainted )
470 {
471 // Draw the slider background in 3 parts
472
473 p->drawPixmap( pt, renderSvg( "progress_slider_left", sliderHeight, sliderHeight, "progress_slider_left" ) );
474
475 pt.rx() += sliderHeight;
476 QRect midRect(pt, QSize(slider->rect.width() - sliderHeight * 2, sliderHeight) );
477 p->drawTiledPixmap( midRect, renderSvg( "progress_slider_mid", 32, sliderHeight, "progress_slider_mid" ) );
478
479 pt = midRect.topRight() + QPoint( 1, 0 );
480 p->drawPixmap( pt, renderSvg( "progress_slider_right", sliderHeight, sliderHeight, "progress_slider_right" ) );
481
482 //draw the played background.
483
484 int playedBarHeight = sliderHeight - 6;
485
486 int sizeOfLeftPlayed = qBound( 0, inverse ? slider->rect.right() - knob.right() + 2 :
487 knob.x() - 2, playedBarHeight );
488
489 if( sizeOfLeftPlayed > 0 )
490 {
491 QPoint tl, br;
492 if ( inverse )
493 {
494 tl = knob.topRight() + QPoint( -5, 5 ); // 5px x padding to avoid a "gap" between it and the top and bottom of the round knob.
495 br = slider->rect.topRight() + QPoint( -3, 5 + playedBarHeight - 1 );
496 QPixmap rightEnd = renderSvg( "progress_slider_played_right", playedBarHeight, playedBarHeight, "progress_slider_played_right" );
497 p->drawPixmap( br.x() - rightEnd.width() + 1, tl.y(), rightEnd, qMax(0, rightEnd.width() - (sizeOfLeftPlayed + 3)), 0, sizeOfLeftPlayed + 3, playedBarHeight );
498 br.rx() -= playedBarHeight;
499 }
500 else
501 {
502 tl = slider->rect.topLeft() + QPoint( 3, 5 );
503 br = QPoint( knob.x() + 5, tl.y() + playedBarHeight - 1 );
504 QPixmap leftEnd = renderSvg( "progress_slider_played_left", playedBarHeight, playedBarHeight, "progress_slider_played_left" );
505 p->drawPixmap( tl.x(), tl.y(), leftEnd, 0, 0, sizeOfLeftPlayed + 3, playedBarHeight );
506 tl.rx() += playedBarHeight;
507 }
508 if ( sizeOfLeftPlayed == playedBarHeight )
509 p->drawTiledPixmap( QRect(tl, br), renderSvg( "progress_slider_played_mid", 32, playedBarHeight, "progress_slider_played_mid" ) );
510
511 }
512 }
513
514 if ( slider->state & QStyle::State_Enabled )
515 { // Draw the knob (handle)
516 const char *string = ( slider->activeSubControls & QStyle::SC_SliderHandle ) ?
517 "slider_knob_200911_active" : "slider_knob_200911";
518 p->drawPixmap( knob.topLeft(), renderSvg( string, knob.width(), knob.height(), string ) );
519 }
520 }
521
522