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