File indexing completed on 2024-04-21 04:47:53

0001 /****************************************************************************************
0002  * Copyright (c) 2008 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0003  * Copyright (c) 2008 Jeff Mitchell <kde-dev@emailgoeshere.com>                         *
0004  * Copyright (c) 2009-2013 Mark Kretschmann <kretschmann@kde.org>                       *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) any later           *
0009  * version.                                                                             *
0010  *                                                                                      *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0014  *                                                                                      *
0015  * You should have received a copy of the GNU General Public License along with         *
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0017  ****************************************************************************************/
0018 
0019 #define DEBUG_PREFIX "SvgHandler"
0020  
0021 #include "SvgHandler.h"
0022 
0023 #include "App.h"
0024 #include "EngineController.h"
0025 #include "MainWindow.h"
0026 #include "PaletteHandler.h"
0027 #include "SvgTinter.h"
0028 #include "core/meta/Meta.h"
0029 #include "core/support/Debug.h"
0030 #include "covermanager/CoverCache.h"
0031 #include "moodbar/MoodbarManager.h"
0032 
0033 #include <KColorScheme>
0034 #include <KColorUtils>
0035 
0036 #include <QHash>
0037 #include <QPainter>
0038 #include <QPalette>
0039 #include <QReadLocker>
0040 #include <QStandardPaths>
0041 #include <QStyleOptionSlider>
0042 #include <QSvgRenderer>
0043 #include <QWriteLocker>
0044 
0045 
0046 namespace The {
0047     static SvgHandler* s_SvgHandler_instance = nullptr;
0048 
0049     SvgHandler* svgHandler()
0050     {
0051         if( !s_SvgHandler_instance )
0052             s_SvgHandler_instance = new SvgHandler();
0053 
0054         return s_SvgHandler_instance;
0055     }
0056 }
0057 
0058 SvgHandler::SvgHandler( QObject* parent )
0059     : QObject( parent )
0060     , m_cache( new KImageCache( "Amarok-pixmaps", 20 * 1024 ) )
0061     , m_themeFile( "amarok/images/default-theme-clean.svg" )  // //use default theme
0062     , m_customTheme( false )
0063 {
0064     DEBUG_BLOCK
0065     connect( The::paletteHandler(), &PaletteHandler::newPalette, this, &SvgHandler::discardCache );
0066 }
0067 
0068 SvgHandler::~SvgHandler()
0069 {
0070     DEBUG_BLOCK
0071     delete m_cache;
0072     qDeleteAll( m_renderers );
0073     m_renderers.clear();
0074 
0075     The::s_SvgHandler_instance = nullptr;
0076 }
0077 
0078 
0079 bool SvgHandler::loadSvg( const QString& name, bool forceCustomTheme )
0080 {
0081     const QString &svgFilename = m_customTheme || forceCustomTheme ? name : QStandardPaths::locate( QStandardPaths::GenericDataLocation, name );
0082     QSvgRenderer *renderer = new QSvgRenderer( The::svgTinter()->tint( svgFilename ) );
0083 
0084     if ( !renderer->isValid() )
0085     {
0086         debug() << "Bluddy 'ell mateys, aye canna' load ya Ess Vee Gee at " << svgFilename;
0087         delete renderer;
0088         return false;
0089     }
0090     QWriteLocker writeLocker( &m_lock );
0091 
0092     if( m_renderers[name] )
0093         delete m_renderers[name];
0094 
0095     m_renderers[name] = renderer;
0096     return true;
0097 }
0098 
0099 QSvgRenderer* SvgHandler::getRenderer( const QString& name )
0100 {
0101     QReadLocker readLocker( &m_lock );
0102     if( ! m_renderers[name] )
0103     {
0104         readLocker.unlock();
0105         if( !loadSvg( name ) )
0106         {
0107             QWriteLocker writeLocker( &m_lock );
0108             m_renderers[name] = new QSvgRenderer();
0109         }
0110         readLocker.relock();
0111     }
0112     return m_renderers[name];
0113 }
0114 
0115 QSvgRenderer * SvgHandler::getRenderer()
0116 {
0117     return getRenderer( m_themeFile );
0118 }
0119 
0120 QPixmap SvgHandler::renderSvg( const QString &name,
0121                                const QString& keyname,
0122                                int width,
0123                                int height,
0124                                const QString& element,
0125                                bool skipCache,
0126                                const qreal opacity )
0127 {
0128     QString key;
0129     if( !skipCache )
0130     {
0131         key = QStringLiteral("%1:%2x%3")
0132             .arg( keyname )
0133             .arg( width )
0134             .arg( height );
0135     }
0136 
0137     QPixmap pixmap;
0138     if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
0139     {
0140         pixmap = QPixmap( width, height );
0141         pixmap.fill( Qt::transparent );
0142 
0143         QReadLocker readLocker( &m_lock );
0144         if( ! m_renderers[name] )
0145         {
0146             readLocker.unlock();
0147             if( !loadSvg( name ) )
0148             {
0149                 return pixmap;
0150             }
0151             readLocker.relock();
0152         }
0153 
0154         QPainter pt( &pixmap );
0155         pt.setOpacity( opacity );
0156 
0157         if ( element.isEmpty() )
0158             m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
0159         else
0160             m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
0161   
0162         if( !skipCache )
0163             m_cache->insertPixmap( key, pixmap );
0164     }
0165 
0166     return pixmap;
0167 }
0168 
0169 QPixmap SvgHandler::renderSvg( const QUrl& url, const QString& keyname, int width, int height, const QString& element, bool skipCache, const qreal opacity )
0170 {
0171     QString key;
0172     if( !skipCache )
0173     {
0174         key = QStringLiteral("%1:%2x%3")
0175         .arg( keyname )
0176         .arg( width )
0177         .arg( height );
0178     }
0179 
0180     QPixmap pixmap;
0181     if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
0182     {
0183         pixmap = QPixmap( width, height );
0184         pixmap.fill( Qt::transparent );
0185 
0186         QString name = url.isLocalFile() ? url.toLocalFile() : ":" + url.path();
0187         QReadLocker readLocker( &m_lock );
0188         if( ! m_renderers[name] )
0189         {
0190             readLocker.unlock();
0191             if( !loadSvg( name, true ) )
0192             {
0193                 return pixmap;
0194             }
0195             readLocker.relock();
0196         }
0197 
0198         QPainter pt( &pixmap );
0199         pt.setOpacity( opacity );
0200 
0201         if ( element.isEmpty() )
0202             m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
0203         else
0204             m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
0205 
0206         if( !skipCache )
0207             m_cache->insertPixmap( key, pixmap );
0208     }
0209 
0210     return pixmap;
0211 }
0212 
0213 QPixmap SvgHandler::renderSvg(const QString & keyname, int width, int height, const QString & element, bool skipCache, const qreal opacity )
0214 {
0215     return renderSvg( m_themeFile, keyname, width, height, element, skipCache, opacity );
0216 }
0217 
0218 QPixmap SvgHandler::renderSvgWithDividers(const QString & keyname, int width, int height, const QString & element)
0219 {
0220     const QString key = QStringLiteral("%1:%2x%3-div")
0221             .arg( keyname )
0222             .arg( width )
0223             .arg( height );
0224 
0225     QPixmap pixmap;
0226     if ( !m_cache->findPixmap( key, &pixmap ) ) {
0227 //         debug() << QStringLiteral("svg %1 not in cache...").arg( key );
0228 
0229         pixmap = QPixmap( width, height );
0230         pixmap.fill( Qt::transparent );
0231 
0232         QString name = m_themeFile;
0233         
0234         QReadLocker readLocker( &m_lock );
0235         if( ! m_renderers[name] )
0236         {
0237             readLocker.unlock();
0238             if( ! loadSvg( name ) )
0239             {
0240                 return pixmap;
0241             }
0242             readLocker.relock();
0243         }
0244         
0245         QPainter pt( &pixmap );
0246         if ( element.isEmpty() )
0247             m_renderers[name]->render( &pt, QRectF( 0, 0, width, height ) );
0248         else
0249             m_renderers[name]->render( &pt, element, QRectF( 0, 0, width, height ) );
0250 
0251 
0252         //add dividers. 5% spacing on each side
0253         int margin = width / 20;
0254 
0255         m_renderers[name]->render( &pt, "divider_top", QRectF( margin, 0 , width - 1 * margin, 1 ) );
0256         m_renderers[name]->render( &pt, "divider_bottom", QRectF( margin, height - 1 , width - 2 * margin, 1 ) );
0257     
0258         m_cache->insertPixmap( key, pixmap );
0259     }
0260 
0261     return pixmap;
0262 }
0263 
0264 
0265 void SvgHandler::reTint()
0266 {
0267     The::svgTinter()->init();
0268     if ( !loadSvg( m_themeFile ))
0269         warning() << "Unable to load theme file: " << m_themeFile;
0270     Q_EMIT retinted();
0271 }
0272 
0273 QString SvgHandler::themeFile()
0274 {
0275     return m_themeFile;
0276 }
0277 
0278 void SvgHandler::setThemeFile( const QString & themeFile )
0279 {
0280     DEBUG_BLOCK
0281     debug() << "got new theme file: " << themeFile;
0282     m_themeFile = themeFile;
0283     m_customTheme = true;
0284     discardCache();
0285 }
0286 
0287 void SvgHandler::discardCache()
0288 {
0289     //redraw entire app....
0290     reTint();
0291     m_cache->clear();
0292 
0293     if( auto window = pApp->mainWindow() )
0294         window->update();
0295 }
0296 
0297 QPixmap
0298 SvgHandler::imageWithBorder( Meta::AlbumPtr album, int size, int borderWidth )
0299 {
0300     const int imageSize = size - ( borderWidth * 2 );
0301     const QString &loc  = album->imageLocation( imageSize ).url();
0302     const QString &key  = !loc.isEmpty() ? loc : album->name();
0303     return addBordersToPixmap( The::coverCache()->getCover( album, imageSize ), borderWidth, key );
0304 }
0305 
0306 QPixmap SvgHandler::addBordersToPixmap( const QPixmap &orgPixmap, int borderWidth, const QString &name, bool skipCache )
0307 {
0308     int newWidth = orgPixmap.width() + borderWidth * 2;
0309     int newHeight = orgPixmap.height() + borderWidth *2;
0310 
0311     QString key;
0312     if( !skipCache )
0313     {
0314         key = QStringLiteral("%1:%2x%3b%4")
0315             .arg( name )
0316             .arg( newWidth )
0317             .arg( newHeight )
0318             .arg( borderWidth );
0319     }
0320 
0321     QPixmap pixmap;
0322     if( skipCache || !m_cache->findPixmap( key, &pixmap ) )
0323     {
0324         // Cache miss! We need to create the pixmap
0325         // if skipCache is true, we might actually already have fetched the image, including borders from the cache....
0326         // so we really need to create a blank pixmap here as well, to not pollute the cached pixmap
0327         pixmap = QPixmap( newWidth, newHeight );
0328         pixmap.fill( Qt::transparent );
0329 
0330         QReadLocker readLocker( &m_lock );
0331         if( !m_renderers[m_themeFile] )
0332         {
0333             readLocker.unlock();
0334             if( !loadSvg( m_themeFile ) )
0335             {
0336                 return pixmap;
0337             }
0338             readLocker.relock();
0339         }
0340 
0341         QPainter pt( &pixmap );
0342 
0343         pt.drawPixmap( borderWidth, borderWidth, orgPixmap.width(), orgPixmap.height(), orgPixmap );
0344 
0345         m_renderers[m_themeFile]->render( &pt, "cover_border_topleft", QRectF( 0, 0, borderWidth, borderWidth ) );
0346         m_renderers[m_themeFile]->render( &pt, "cover_border_top", QRectF( borderWidth, 0, orgPixmap.width(), borderWidth ) );
0347         m_renderers[m_themeFile]->render( &pt, "cover_border_topright", QRectF( newWidth - borderWidth , 0, borderWidth, borderWidth ) );
0348         m_renderers[m_themeFile]->render( &pt, "cover_border_right", QRectF( newWidth - borderWidth, borderWidth, borderWidth, orgPixmap.height() ) );
0349         m_renderers[m_themeFile]->render( &pt, "cover_border_bottomright", QRectF( newWidth - borderWidth, newHeight - borderWidth, borderWidth, borderWidth ) );
0350         m_renderers[m_themeFile]->render( &pt, "cover_border_bottom", QRectF( borderWidth, newHeight - borderWidth, orgPixmap.width(), borderWidth ) );
0351         m_renderers[m_themeFile]->render( &pt, "cover_border_bottomleft", QRectF( 0, newHeight - borderWidth, borderWidth, borderWidth ) );
0352         m_renderers[m_themeFile]->render( &pt, "cover_border_left", QRectF( 0, borderWidth, borderWidth, orgPixmap.height() ) );
0353     
0354         if( !skipCache )
0355             m_cache->insertPixmap( key, pixmap );
0356     }
0357 
0358     return pixmap;
0359 }
0360 
0361 #if 0
0362 void SvgHandler::paintCustomSlider( QPainter *p, int x, int y, int width, int height, qreal percentage, bool active )
0363 {
0364     int knobSize = height - 4;
0365     int sliderRange = width - ( knobSize + 4 );
0366     int knobRelPos = x + sliderRange * percentage + 2;
0367     int knobY = y + ( height - knobSize ) / 2 + 1;
0368 
0369     int sliderY = y + ( height / 2 ) - 1;
0370 
0371 
0372     //first draw the played part
0373     p->drawPixmap( x, sliderY,
0374                    renderSvg(
0375                    "new_slider_top_played",
0376                    width, 2,
0377                    "new_slider_top_played" ),
0378                    0, 0, knobRelPos - x, 2 );
0379 
0380     //and then the unplayed part
0381     p->drawPixmap( knobRelPos + 1, sliderY,
0382                    renderSvg(
0383                    "new_slider_top",
0384                    width, 2,
0385                    "new_slider_top" ),
0386                    knobRelPos + 1 - x, 0, -1, 2 );
0387 
0388     //and then the bottom
0389     p->drawPixmap( x, sliderY + 2,
0390                    renderSvg(
0391                    "new_slider_bottom",
0392                    width, 2,
0393                    "new_slider_bottom" ) );
0394 
0395     //draw end markers
0396     p->drawPixmap( x, y,
0397                    renderSvg(
0398                    "new_slider_end",
0399                    2, height,
0400                    "new_slider_end" ) );
0401 
0402     p->drawPixmap( x + width - 2, y,
0403                    renderSvg(
0404                    "new_slider_end",
0405                    2, height,
0406                    "new_slider_endr" ) );
0407 
0408 
0409     if ( active )
0410         p->drawPixmap( knobRelPos, knobY,
0411                        renderSvg(
0412                        "new_slider_knob_active",
0413                        knobSize, knobSize,
0414                        "new_slider_knob_active" ) );
0415     else
0416         p->drawPixmap( knobRelPos, knobY,
0417                        renderSvg(
0418                        "new_slider_knob",
0419                        knobSize, knobSize,
0420                        "new_slider_knob" ) );
0421 }
0422 #endif
0423 
0424 QRect SvgHandler::sliderKnobRect( const QRect &slider, qreal percent, bool inverse ) const
0425 {
0426     if ( inverse )
0427         percent = 1.0 - percent;
0428     const int knobSize = slider.height() - 4;
0429     QRect ret( 0, 0, knobSize, knobSize );
0430     ret.moveTo( slider.x() + qRound( ( slider.width() - knobSize ) * percent ), slider.y() + 1 );
0431     return ret;
0432 }
0433 
0434 // Experimental, using a mockup from Nuno Pinheiro (new_slider_nuno)
0435 void SvgHandler::paintCustomSlider( QPainter *p, QStyleOptionSlider *slider, qreal percentage, bool paintMoodbar )
0436 {
0437     int sliderHeight = slider->rect.height() - 6;
0438     const bool inverse = ( slider->orientation == Qt::Vertical ) ? slider->upsideDown :
0439                          ( (slider->direction == Qt::RightToLeft) != slider->upsideDown );
0440     QRect knob = sliderKnobRect( slider->rect, percentage, inverse );
0441     QPoint pt = slider->rect.topLeft() + QPoint( 0, 2 );
0442 
0443     p->setRenderHint( QPainter::SmoothPixmapTransform );
0444     //debug() << "rel: " << knobRelPos << ", width: " << width << ", height:" << height << ", %: " << percentage;
0445 
0446     //if we should paint moodbar, paint this as the bottom layer
0447     bool moodbarPainted = false;
0448     if ( paintMoodbar )
0449     {
0450         Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
0451         if ( currentTrack )
0452         {
0453             if( The::moodbarManager()->hasMoodbar( currentTrack ) )
0454             {
0455                 QPixmap moodbar = The::moodbarManager()->getMoodbar( currentTrack, slider->rect.width() - sliderHeight, sliderHeight, inverse );
0456                 p->drawPixmap( pt, renderSvg( "moodbar_end_left", sliderHeight / 2, sliderHeight, "moodbar_end_left" ) );
0457 
0458                 pt.rx() += sliderHeight / 2;
0459                 p->drawPixmap( pt, moodbar );
0460 
0461                 pt.rx() += slider->rect.width() - sliderHeight;
0462                 p->drawPixmap( pt, renderSvg( "moodbar_end_right", sliderHeight / 2, sliderHeight, "moodbar_end_right" ) );
0463 
0464                 moodbarPainted = true;
0465             }
0466         }
0467     }
0468 
0469     if( !moodbarPainted )
0470     {
0471         // Draw the slider background in 3 parts
0472 
0473         p->drawPixmap( pt, renderSvg( "progress_slider_left", sliderHeight, sliderHeight, "progress_slider_left" ) );
0474 
0475         pt.rx() += sliderHeight;
0476         QRect midRect(pt, QSize(slider->rect.width() - sliderHeight * 2, sliderHeight) );
0477         p->drawTiledPixmap( midRect, renderSvg( "progress_slider_mid", 32, sliderHeight, "progress_slider_mid" ) );
0478 
0479         pt = midRect.topRight() + QPoint( 1, 0 );
0480         p->drawPixmap( pt, renderSvg( "progress_slider_right", sliderHeight, sliderHeight, "progress_slider_right" ) );
0481 
0482         //draw the played background.
0483 
0484         int playedBarHeight = sliderHeight - 6;
0485 
0486         int sizeOfLeftPlayed = qBound( 0, inverse ? slider->rect.right() - knob.right() + 2 :
0487                                                     knob.x() - 2, playedBarHeight );
0488 
0489         if( sizeOfLeftPlayed > 0 )
0490         {
0491             QPoint tl, br;
0492             if ( inverse )
0493             {
0494                 tl = knob.topRight() + QPoint( -5, 5 ); // 5px x padding to avoid a "gap" between it and the top and bottom of the round knob.
0495                 br = slider->rect.topRight() + QPoint( -3, 5 + playedBarHeight - 1 );
0496                 QPixmap rightEnd = renderSvg( "progress_slider_played_right", playedBarHeight, playedBarHeight, "progress_slider_played_right" );
0497                 p->drawPixmap( br.x() - rightEnd.width() + 1, tl.y(), rightEnd, qMax(0, rightEnd.width() - (sizeOfLeftPlayed + 3)), 0, sizeOfLeftPlayed + 3, playedBarHeight );
0498                 br.rx() -= playedBarHeight;
0499             }
0500             else
0501             {
0502                 tl = slider->rect.topLeft() + QPoint( 3, 5 );
0503                 br = QPoint( knob.x() + 5, tl.y() + playedBarHeight - 1 );
0504                 QPixmap leftEnd = renderSvg( "progress_slider_played_left", playedBarHeight, playedBarHeight, "progress_slider_played_left" );
0505                 p->drawPixmap( tl.x(), tl.y(), leftEnd, 0, 0, sizeOfLeftPlayed + 3, playedBarHeight );
0506                 tl.rx() += playedBarHeight;
0507             }
0508             if ( sizeOfLeftPlayed == playedBarHeight )
0509                 p->drawTiledPixmap( QRect(tl, br), renderSvg( "progress_slider_played_mid", 32, playedBarHeight, "progress_slider_played_mid" ) );
0510 
0511         }
0512     }
0513 
0514     if ( slider->state & QStyle::State_Enabled )
0515     {   // Draw the knob (handle)
0516         const char *string = ( slider->activeSubControls & QStyle::SC_SliderHandle ) ?
0517                              "slider_knob_200911_active" : "slider_knob_200911";
0518         p->drawPixmap( knob.topLeft(), renderSvg( string, knob.width(), knob.height(), string ) );
0519     }
0520 }
0521