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