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