File indexing completed on 2024-05-05 04:49:27

0001 /****************************************************************************************
0002  * Copyright (c) 2004 Christian Muehlhaeuser <chris@chris.de>                           *
0003  * Copyright (c) 2004-2006 Seb Ruiz <ruiz@kde.org>                                      *
0004  * Copyright (c) 2004,2005 Max Howell <max.howell@methylblue.com>                       *
0005  * Copyright (c) 2005 Gabor Lehel <illissius@gmail.com>                                 *
0006  * Copyright (c) 2008-2013 Mark Kretschmann <kretschmann@kde.org>                       *
0007  *                                                                                      *
0008  * This program is free software; you can redistribute it and/or modify it under        *
0009  * the terms of the GNU General Public License as published by the Free Software        *
0010  * Foundation; either version 2 of the License, or (at your option) any later           *
0011  * version.                                                                             *
0012  *                                                                                      *
0013  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0014  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0015  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0016  *                                                                                      *
0017  * You should have received a copy of the GNU General Public License along with         *
0018  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0019  ****************************************************************************************/
0020 
0021 #define DEBUG_PREFIX "OSD"
0022 
0023 #include "Osd.h"
0024 
0025 #include "EngineController.h"
0026 #include "KNotificationBackend.h"
0027 #include "PaletteHandler.h"
0028 #include "SvgHandler.h"
0029 #include "amarokconfig.h"
0030 #include "core/meta/Meta.h"
0031 #include "core/meta/Statistics.h"
0032 #include "core/meta/support/MetaUtility.h"
0033 #include "core/support/Amarok.h"
0034 #include "core/support/Debug.h"
0035 #include "widgets/StarManager.h"
0036 
0037 #include <QApplication>
0038 #include <QIcon>
0039 #include <KLocalizedString>
0040 #include <KWindowSystem>
0041 #include <KIconLoader>
0042 
0043 #include <QDesktopWidget>
0044 #include <QMouseEvent>
0045 #include <QPainter>
0046 #include <QPixmap>
0047 #include <QRegExp>
0048 #include <QTimeLine>
0049 #include <QTimer>
0050 
0051 namespace ShadowEngine
0052 {
0053     QImage makeShadow( const QPixmap &textPixmap, const QColor &bgColor );
0054 }
0055 
0056 namespace Amarok
0057 {
0058     inline QImage icon() { return QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ); }
0059 }
0060 
0061 OSDWidget::OSDWidget( QWidget *parent, const char *name )
0062         : QWidget( parent )
0063         , m_duration( 2000 )
0064         , m_timer( new QTimer( this ) )
0065         , m_alignment( Middle )
0066         , m_screen( 0 )
0067         , m_yOffset( MARGIN )
0068         , m_rating( 0 )
0069         , m_volume( The::engineController()->volume() )
0070         , m_showVolume( false )
0071         , m_hideWhenFullscreenWindowIsActive( false )
0072         , m_fadeTimeLine( new QTimeLine( FADING_DURATION, this ) )
0073 {
0074     Qt::WindowFlags flags;
0075     flags = Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint;
0076     // The best of both worlds.  On Windows, setting the widget as a popup avoids a task manager entry.  On linux, a popup steals focus.
0077     // Therefore we go need to do it platform specific :(
0078 
0079     //This is no longer true. Qt::Window steals focus on X11, Qt:Tool does not. Not sure if we even need the ifdefs any more...
0080     #ifdef Q_OS_WIN
0081     flags |= Qt::Tool;
0082     #else
0083     flags |= Qt::Tool | Qt::X11BypassWindowManagerHint;
0084     #endif
0085     setWindowFlags( flags );
0086     setObjectName( name );
0087     setFocusPolicy( Qt::NoFocus );
0088 
0089     #ifdef Q_WS_X11
0090     KWindowSystem::setType( winId(), NET::Notification );
0091     #endif
0092 
0093     m_timer->setSingleShot( true );
0094     connect( m_timer, &QTimer::timeout, this, &OSDWidget::hide );
0095 
0096     m_fadeTimeLine->setUpdateInterval( 30 ); //~33 frames per second 
0097     connect( m_fadeTimeLine, &QTimeLine::valueChanged, this, &OSDWidget::setFadeOpacity );
0098 
0099     //or crashes, KWindowSystem bug I think, crashes in QWidget::icon()
0100     //kapp->setTopWidget( this );
0101 }
0102 
0103 OSDWidget::~OSDWidget()
0104 {
0105     DEBUG_BLOCK
0106 }
0107 
0108 void
0109 OSDWidget::show( const QString &text, const QImage &newImage )
0110 {
0111     DEBUG_BLOCK
0112     m_showVolume = false;
0113     if ( !newImage.isNull() )
0114     {
0115         m_cover = newImage;
0116         int w = m_scaledCover.width();
0117         int h = m_scaledCover.height();
0118         m_scaledCover = QPixmap::fromImage( m_cover.scaled( w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) );
0119     }
0120     else
0121         m_cover = Amarok::icon();
0122 
0123     m_text = text;
0124     show();
0125 }
0126 
0127 void
0128 OSDWidget::show()
0129 {
0130     if ( !isTemporaryDisabled() )
0131     {
0132         QWidget::show();
0133 
0134         if( windowOpacity() == 0.0 && KWindowSystem::compositingActive() )
0135         {
0136             m_fadeTimeLine->setDirection( QTimeLine::Forward );
0137             m_fadeTimeLine->start();
0138         }
0139         // Skip fading if OSD is already visible or if compositing is disabled
0140         else
0141         {
0142             m_fadeTimeLine->stop();
0143             setWindowOpacity( maxOpacity() );
0144         }
0145     }
0146 }
0147 
0148 void
0149 OSDWidget::hide()
0150 {
0151     if( KWindowSystem::compositingActive() )
0152     {
0153         m_fadeTimeLine->setDirection( QTimeLine::Backward );
0154         m_fadeTimeLine->start();
0155     }
0156     else
0157     {
0158         QWidget::hide();
0159     }
0160 }
0161 
0162 bool
0163 OSDWidget::isTemporaryDisabled() const
0164 {
0165     // Check if the OSD should not be shown,
0166     // if a fullscreen window is focused.
0167     if ( m_hideWhenFullscreenWindowIsActive )
0168     {
0169         return Amarok::KNotificationBackend::instance()->isFullscreenWindowActive();
0170     }
0171 
0172     return false;
0173 }
0174 
0175 void
0176 OSDWidget::ratingChanged( const QString& path, int rating )
0177 {
0178     Meta::TrackPtr track = The::engineController()->currentTrack();
0179     if( !track )
0180         return;
0181     if( track->playableUrl().isLocalFile() && track->playableUrl().path() == path )
0182         ratingChanged( rating );
0183 }
0184 
0185 void
0186 OSDWidget::ratingChanged( const short rating )
0187 {
0188     m_text = QLatin1Char('\n') + i18n( "Rating changed" );
0189     setRating( rating ); //Checks isEnabled() before doing anything
0190 
0191     show();
0192 }
0193 
0194 void
0195 OSDWidget::volumeChanged( int volume )
0196 {
0197     m_volume = volume;
0198 
0199     if ( isEnabled() )
0200     {
0201         m_showVolume = true;
0202         m_text = The::engineController()->isMuted() ? i18n( "Volume: %1% (muted)", m_volume) : i18n( "Volume: %1%", m_volume);
0203 
0204         show();
0205     }
0206 }
0207 
0208 void
0209 OSDWidget::setVisible( bool visible )
0210 {
0211     if ( visible )
0212     {
0213         if ( !isEnabled() || m_text.isEmpty() )
0214             return;
0215 
0216         const uint margin = fontMetrics().horizontalAdvance( 'x' );
0217 
0218         const QRect newGeometry = determineMetrics( margin );
0219 
0220         if( newGeometry.width() > 0 && newGeometry.height() > 0 )
0221         {
0222             m_margin = margin;
0223             m_size = newGeometry.size();
0224             setGeometry( newGeometry );
0225             QWidget::setVisible( visible );
0226 
0227             if( m_duration ) //duration 0 -> stay forever
0228                 m_timer->start( m_duration ); //calls hide()
0229         }
0230         else
0231             warning() << "Attempted to make an invalid sized OSD\n";
0232 
0233         update();
0234     }
0235     else
0236         QWidget::setVisible( visible );
0237 }
0238 
0239 QRect
0240 OSDWidget::determineMetrics( const int M )
0241 {
0242     // sometimes we only have a tiddly cover
0243     const QSize minImageSize = m_cover.size().boundedTo( QSize( 100, 100 ) );
0244 
0245     // determine a sensible maximum size, don't cover the whole desktop or cross the screen
0246     const QSize margin( ( M + MARGIN ) * 2, ( M + MARGIN ) * 2 ); //margins
0247     const QSize image = m_cover.isNull() ? QSize( 0, 0 ) : minImageSize;
0248     const QSize max = QApplication::desktop()->screen( m_screen )->size() - margin;
0249 
0250     // If we don't do that, the boundingRect() might not be suitable for drawText() (Qt issue N67674)
0251     m_text.replace( QRegExp( " +\n" ), "\n" );
0252     // remove consecutive line breaks
0253     m_text.replace( QRegExp( "\n+" ), "\n" );
0254 
0255     // The osd cannot be larger than the screen
0256     QRect rect = fontMetrics().boundingRect( 0, 0, max.width() - image.width(), max.height(),
0257         Qt::AlignCenter, m_text );
0258     rect.adjust( 0, 0, SHADOW_SIZE * 2, SHADOW_SIZE * 2 ); // the shadow needs some space
0259 
0260     if( m_showVolume )
0261     {
0262         static const QString tmp = QString ("******").insert( 3,
0263             ( i18n("Volume: 100% (muted)" ) ) );
0264 
0265         QRect tmpRect = fontMetrics().boundingRect( 0, 0,
0266             max.width() - image.width(), max.height() - fontMetrics().height(),
0267             Qt::AlignCenter, tmp );
0268         tmpRect.setHeight( tmpRect.height() + fontMetrics().height() / 2 );
0269 
0270         rect = tmpRect;
0271 
0272         if ( The::engineController()->isMuted() )
0273             m_cover = The::svgHandler()->renderSvg( "Muted", 100, 100, "Muted" ).toImage();
0274         else if( m_volume > 66 )
0275             m_cover = The::svgHandler()->renderSvg( "Volume", 100, 100, "Volume" ).toImage();
0276         else if ( m_volume > 33 )
0277             m_cover = The::svgHandler()->renderSvg( "Volume_mid", 100, 100, "Volume_mid" ).toImage();
0278         else
0279             m_cover = The::svgHandler()->renderSvg( "Volume_low", 100, 100, "Volume_low" ).toImage();
0280     }
0281     // Don't show both volume and rating
0282     else if( m_rating )
0283     {
0284         QPixmap* star = StarManager::instance()->getStar( 1 );
0285         if( rect.width() < star->width() * 5 )
0286             rect.setWidth( star->width() * 5 ); //changes right edge position
0287         rect.setHeight( rect.height() + star->height() + M ); //changes bottom edge pos
0288     }
0289 
0290     if( !m_cover.isNull() )
0291     {
0292         const int availableWidth = max.width() - rect.width() - M; //WILL be >= (minImageSize.width() - M)
0293 
0294         m_scaledCover = QPixmap::fromImage(
0295                 m_cover.scaled(
0296                     qMin( availableWidth, m_cover.width() ),
0297                     qMin( rect.height(), m_cover.height() ),
0298                     Qt::KeepAspectRatio, Qt::SmoothTransformation
0299                               )
0300                                           ); //this will force us to be with our bounds
0301 
0302 
0303         const int widthIncludingImage = rect.width()
0304                 + m_scaledCover.width()
0305                 + M; //margin between text + image
0306 
0307         rect.setWidth( widthIncludingImage );
0308     }
0309 
0310     // expand in all directions by M
0311     rect.adjust( -M, -M, M, M );
0312 
0313     const QSize newSize = rect.size();
0314     const QRect screen = QApplication::desktop()->screenGeometry( m_screen );
0315     QPoint newPos( MARGIN, m_yOffset );
0316 
0317     switch( m_alignment )
0318     {
0319         case Left:
0320             break;
0321 
0322         case Right:
0323             newPos.rx() = screen.width() - MARGIN - newSize.width();
0324             break;
0325 
0326         case Center:
0327             newPos.ry() = ( screen.height() - newSize.height() ) / 2;
0328 
0329             Q_FALLTHROUGH();
0330 
0331         case Middle:
0332             newPos.rx() = ( screen.width() - newSize.width() ) / 2;
0333             break;
0334     }
0335 
0336     //ensure we don't dip below the screen
0337     if ( newPos.y() + newSize.height() > screen.height() - MARGIN )
0338         newPos.ry() = screen.height() - MARGIN - newSize.height();
0339 
0340     // correct for screen position
0341     newPos += screen.topLeft();
0342 
0343     return QRect( newPos, rect.size() );
0344 }
0345 
0346 void
0347 OSDWidget::paintEvent( QPaintEvent *e )
0348 {
0349     QRect rect( QPoint(), m_size );
0350 
0351     QColor shadowColor;
0352     {
0353         int h, s, v;
0354         palette().color( QPalette::Normal, QPalette::WindowText ).getHsv( &h, &s, &v );
0355         shadowColor = v > 128 ? Qt::black : Qt::white;
0356     }
0357 
0358     const int align = Qt::AlignCenter;
0359 
0360     QPainter p( this );
0361     p.setRenderHints( QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform | QPainter::HighQualityAntialiasing );
0362     p.setClipRect( e->rect() );
0363 
0364     QPixmap background = The::svgHandler()->renderSvgWithDividers( "service_list_item", width(), height(), "service_list_item" );
0365     p.drawPixmap( 0, 0, background );
0366 
0367     //p.setPen( Qt::white ); // Revert this when the background can be colorized again.
0368     rect.adjust( m_margin, m_margin, -m_margin, -m_margin ); // subtract margins
0369 
0370     if( !m_cover.isNull() )
0371     {
0372         QRect r( rect );
0373         r.setTop( ( m_size.height() - m_scaledCover.height() ) / 2 );
0374         r.setSize( m_scaledCover.size() );
0375 
0376         p.drawPixmap( r.topLeft(), m_scaledCover );
0377 
0378         rect.setLeft( rect.left() + m_scaledCover.width() + m_margin );
0379     }
0380 
0381     int graphicsHeight = 0;
0382 
0383     if( !m_showVolume && m_rating > 0 && !m_paused )
0384     {
0385         // TODO: Check if we couldn't use a KRatingPainter instead
0386         QPixmap* star = StarManager::instance()->getStar( m_rating/2 );
0387         QRect r( rect );
0388 
0389         //Align to center...
0390         r.setLeft( ( rect.left() + rect.width() / 2 ) - star->width() * m_rating / 4 );
0391         r.setTop( rect.bottom() - star->height() );
0392         graphicsHeight += star->height() + m_margin;
0393 
0394         const bool half = m_rating % 2;
0395 
0396         if( half )
0397         {
0398             QPixmap* halfStar = StarManager::instance()->getHalfStar( m_rating / 2 + 1 );
0399             p.drawPixmap( r.left() + star->width() * ( m_rating / 2 ), r.top(), *halfStar );
0400             star = StarManager::instance()->getStar( m_rating / 2 + 1 );
0401         }
0402 
0403         for( int i = 0; i < m_rating / 2; i++ )
0404         {
0405             p.drawPixmap( r.left() + i * star->width(), r.top(), *star );
0406         }
0407     }
0408 
0409     rect.setBottom( rect.bottom() - graphicsHeight );
0410 
0411     // Draw "shadow" text effect (black outline) (currently it's up to five pixel in every dir.)
0412     QPixmap pixmap( rect.size() );
0413     pixmap.fill( Qt::black );
0414 
0415     QPainter p2( &pixmap );
0416     p2.setFont( font() );
0417     p2.setPen( Qt::white );
0418     p2.setBrush( Qt::white );
0419     p2.drawText( QRect( QPoint( SHADOW_SIZE, SHADOW_SIZE ),
0420                         QSize( rect.size().width() - SHADOW_SIZE * 2,
0421                                rect.size().height() - SHADOW_SIZE * 2 ) ),
0422                  align, m_text );
0423     p2.end();
0424 
0425     p.drawImage( rect.topLeft(), ShadowEngine::makeShadow( pixmap, shadowColor ) );
0426 
0427     p.setPen( palette().color( QPalette::Active, QPalette::WindowText ) );
0428 
0429     p.drawText( rect.adjusted( SHADOW_SIZE, SHADOW_SIZE,
0430                                -SHADOW_SIZE, -SHADOW_SIZE ), align, m_text );
0431 }
0432 
0433 void
0434 OSDWidget::changeEvent( QEvent *event )
0435 {
0436     QWidget::changeEvent( event );
0437 
0438     if( event->type() == QEvent::PaletteChange )
0439         if( !AmarokConfig::osdUseCustomColors() )
0440             unsetColors(); // Use new palette's colors
0441 }
0442 
0443 void
0444 OSDWidget::mousePressEvent( QMouseEvent* )
0445 {
0446     hide();
0447 }
0448 
0449 void
0450 OSDWidget::unsetColors()
0451 {
0452     setPalette( The::paletteHandler()->palette() );
0453 }
0454 
0455 void
0456 OSDWidget::setTextColor(const QColor& color)
0457 {
0458     QPalette palette = this->palette();
0459     palette.setColor( QPalette::Active, QPalette::WindowText, color );
0460     setPalette(palette);
0461 }
0462 
0463 void
0464 OSDWidget::setScreen( int screen )
0465 {
0466     const int n = QApplication::desktop()->numScreens();
0467     m_screen = ( screen >= n ) ? n - 1 : screen;
0468 }
0469 
0470 void
0471 OSDWidget::setFadeOpacity( qreal value )
0472 {
0473     setWindowOpacity( value * maxOpacity() );
0474 
0475     if( value == 0.0 )
0476     {
0477         QWidget::hide();
0478     }
0479 }
0480 
0481 void
0482 OSDWidget::setFontScale( int scale )
0483 {
0484     double fontScale = static_cast<double>( scale ) / 100.0;
0485 
0486     // update font, reuse old one
0487     QFont newFont( font() );
0488     newFont.setPointSizeF( defaultPointSize() * fontScale );
0489     setFont( newFont );
0490 }
0491 
0492 void
0493 OSDWidget::setHideWhenFullscreenWindowIsActive( bool hide )
0494 {
0495     m_hideWhenFullscreenWindowIsActive = hide;
0496 }
0497 
0498 
0499 /////////////////////////////////////////////////////////////////////////////////////////
0500 // Class OSDPreviewWidget
0501 /////////////////////////////////////////////////////////////////////////////////////////
0502 
0503 OSDPreviewWidget::OSDPreviewWidget( QWidget *parent )
0504         : OSDWidget( parent )
0505         , m_dragging( false )
0506 {
0507     setObjectName( "osdpreview" );
0508     setDuration( 0 );
0509     setImage( Amarok::icon() );
0510     setTranslucent( AmarokConfig::osdUseTranslucency() );
0511     setText( i18n( "On-Screen-Display preview\nDrag to reposition" ) );
0512 }
0513 
0514 void
0515 OSDPreviewWidget::mousePressEvent( QMouseEvent *event )
0516 {
0517     m_dragYOffset = event->pos();
0518 
0519     if( event->button() == Qt::LeftButton && !m_dragging )
0520     {
0521         grabMouse( Qt::SizeAllCursor );
0522         m_dragging = true;
0523     }
0524 }
0525 
0526 void
0527 OSDPreviewWidget::setUseCustomColors(const bool use, const QColor& fg)
0528 {
0529     if( use )
0530         setTextColor( fg );
0531     else
0532         unsetColors();
0533 }
0534 
0535 void
0536 OSDPreviewWidget::mouseReleaseEvent( QMouseEvent * /*event*/ )
0537 {
0538     if( m_dragging )
0539     {
0540         m_dragging = false;
0541         releaseMouse();
0542 
0543         Q_EMIT positionChanged();
0544     }
0545 }
0546 
0547 void
0548 OSDPreviewWidget::mouseMoveEvent( QMouseEvent *e )
0549 {
0550     if( m_dragging && this == mouseGrabber() )
0551     {
0552         // Here we implement a "snap-to-grid" like positioning system for the preview widget
0553 
0554         const QRect screenRect  = QApplication::desktop()->screenGeometry( screen() );
0555         const uint  hcenter     = screenRect.width() / 2;
0556         const uint  eGlobalPosX = e->globalPos().x() - screenRect.left();
0557         const uint  snapZone    = screenRect.width() / 24;
0558 
0559         QPoint destination = e->globalPos() - m_dragYOffset - screenRect.topLeft();
0560         int maxY = screenRect.height() - height() - MARGIN;
0561         if( destination.y() < MARGIN )
0562             destination.ry() = MARGIN;
0563         if( destination.y() > maxY )
0564             destination.ry() = maxY;
0565 
0566         if( eGlobalPosX < ( hcenter - snapZone ) )
0567         {
0568             setAlignment(Left);
0569             destination.rx() = MARGIN;
0570         }
0571         else if( eGlobalPosX > ( hcenter + snapZone ) )
0572         {
0573             setAlignment(Right);
0574             destination.rx() = screenRect.width() - MARGIN - width();
0575         }
0576         else {
0577             const uint eGlobalPosY = e->globalPos().y() - screenRect.top();
0578             const uint vcenter     = screenRect.height() / 2;
0579 
0580             destination.rx() = hcenter - width() / 2;
0581 
0582             if( eGlobalPosY >= ( vcenter - snapZone ) && eGlobalPosY <= ( vcenter + snapZone ) )
0583             {
0584                 setAlignment(Center);
0585                 destination.ry() = vcenter - height() / 2;
0586             }
0587             else
0588                 setAlignment(Middle);
0589         }
0590 
0591         destination += screenRect.topLeft();
0592         move( destination );
0593 
0594         // compute current Position && Y-offset
0595         QDesktopWidget *desktop = QApplication::desktop();
0596         const int currentScreen = desktop->screenNumber( pos() );
0597 
0598         // set new data
0599         OSDWidget::setScreen( currentScreen );
0600         setYOffset( y() );
0601     }
0602 }
0603 
0604 
0605 /////////////////////////////////////////////////////////////////////////////////////////
0606 // Class OSD
0607 /////////////////////////////////////////////////////////////////////////////////////////
0608 
0609 Amarok::OSD* Amarok::OSD::s_instance = nullptr;
0610 
0611 Amarok::OSD*
0612 Amarok::OSD::instance()
0613 {
0614     return s_instance ? s_instance : new OSD();
0615 }
0616 
0617 void
0618 Amarok::OSD::destroy()
0619 {
0620     if ( s_instance )
0621     {
0622         delete s_instance;
0623         s_instance = nullptr;
0624     }
0625 }
0626 
0627 Amarok::OSD::OSD()
0628     : OSDWidget( nullptr )
0629 {
0630     s_instance = this;
0631 
0632     EngineController* const engine = The::engineController();
0633 
0634     if( engine->isPlaying() )
0635         trackPlaying( engine->currentTrack() );
0636 
0637     connect( engine, &EngineController::trackPlaying,
0638              this, &Amarok::OSD::trackPlaying );
0639     connect( engine, &EngineController::stopped,
0640              this, &Amarok::OSD::stopped );
0641     connect( engine, &EngineController::paused,
0642              this, &Amarok::OSD::paused );
0643 
0644     connect( engine, &EngineController::trackMetadataChanged,
0645              this, &Amarok::OSD::metadataChanged );
0646     connect( engine, &EngineController::albumMetadataChanged,
0647              this, &Amarok::OSD::metadataChanged );
0648 
0649     connect( engine, &EngineController::volumeChanged,
0650              this, &Amarok::OSD::volumeChanged );
0651 
0652     connect( engine, &EngineController::muteStateChanged,
0653              this, &Amarok::OSD::muteStateChanged );
0654 
0655 }
0656 
0657 Amarok::OSD::~OSD()
0658 {}
0659 
0660 void
0661 Amarok::OSD::show( Meta::TrackPtr track ) //slot
0662 {
0663     setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
0664     setYOffset( AmarokConfig::osdYOffset() );
0665 
0666     QString text;
0667     if( !track || track->playableUrl().isEmpty() )
0668     {
0669         text = i18n( "No track playing" );
0670         setRating( 0 ); // otherwise stars from last rating change are visible
0671     }
0672     else
0673     {
0674         setRating( track->statistics()->rating() );
0675         text = track->prettyName();
0676         if( track->artist() && !track->artist()->prettyName().isEmpty() )
0677             text = track->artist()->prettyName() + " - " + text;
0678         if( track->album() && !track->album()->prettyName().isEmpty() )
0679             text += "\n (" + track->album()->prettyName() + ") ";
0680         else
0681             text += '\n';
0682         if( track->length() > 0 )
0683             text += Meta::msToPrettyTime( track->length() );
0684     }
0685 
0686     if( text.isEmpty() )
0687         text =  track->playableUrl().fileName();
0688 
0689     if( text.startsWith( "- " ) ) //When we only have a title tag, _something_ prepends a fucking hyphen. Remove that.
0690         text = text.mid( 2 );
0691 
0692     if( text.isEmpty() ) //still
0693         text = i18n("No information available for this track");
0694 
0695     QImage image;
0696     if( track && track->album() )
0697         image = The::svgHandler()->imageWithBorder( track->album(), 100, 5 ).toImage();
0698 
0699     OSDWidget::show( text, image );
0700 }
0701 
0702 void
0703 Amarok::OSD::applySettings()
0704 {
0705     DEBUG_BLOCK
0706 
0707     setAlignment( static_cast<OSDWidget::Alignment>( AmarokConfig::osdAlignment() ) );
0708     setDuration( AmarokConfig::osdDuration() );
0709     setEnabled( AmarokConfig::osdEnabled() );
0710     setYOffset( AmarokConfig::osdYOffset() );
0711     setScreen( AmarokConfig::osdScreen() );
0712     setFontScale( AmarokConfig::osdFontScaling() );
0713     setHideWhenFullscreenWindowIsActive( AmarokConfig::osdHideOnFullscreen() );
0714 
0715     if( AmarokConfig::osdUseCustomColors() )
0716         setTextColor( AmarokConfig::osdTextColor() );
0717     else
0718         unsetColors();
0719 
0720     setTranslucent( AmarokConfig::osdUseTranslucency() );
0721 }
0722 
0723 void
0724 Amarok::OSD::forceToggleOSD()
0725 {
0726     if ( !isVisible() )
0727     {
0728         const bool b = isEnabled();
0729         setEnabled( true );
0730         show( The::engineController()->currentTrack() );
0731         setEnabled( b );
0732     }
0733     else
0734         hide();
0735 }
0736 
0737 void
0738 Amarok::OSD::muteStateChanged( bool mute )
0739 {
0740     Q_UNUSED( mute )
0741 
0742     volumeChanged( The::engineController()->volume() );
0743 }
0744 
0745 void
0746 Amarok::OSD::trackPlaying( const Meta::TrackPtr &track )
0747 {
0748     m_currentTrack = track;
0749 
0750     setPaused(false);
0751     show( m_currentTrack );
0752 }
0753 
0754 void
0755 Amarok::OSD::stopped()
0756 {
0757     setImage( QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
0758     setRating( 0 ); // otherwise stars from last rating change are visible
0759     OSDWidget::show( i18n( "Stopped" ) );
0760     setPaused(false);
0761 }
0762 
0763 void
0764 Amarok::OSD::paused()
0765 {
0766     setImage( QImage( KIconLoader::global()->iconPath( "amarok", -KIconLoader::SizeHuge ) ) );
0767     setRating( 0 ); // otherwise stars from last rating change are visible
0768     OSDWidget::show( i18n( "Paused" ) );
0769     setPaused(true);
0770 }
0771 
0772 void
0773 Amarok::OSD::metadataChanged()
0774 {
0775     // this also covers all cases where a stream get's new metadata.
0776     show( m_currentTrack );
0777 }
0778 
0779 
0780 /* Code copied from kshadowengine.cpp
0781  *
0782  * Copyright (C) 2003 Laur Ivan <laurivan@eircom.net>
0783  *
0784  * Many thanks to:
0785  *  - Bernardo Hung <deciare@gta.igs.net> for the enhanced shadow
0786  *    algorithm (currently used)
0787  *  - Tim Jansen <tim@tjansen.de> for the API updates and fixes.
0788  *
0789  * This library is free software; you can redistribute it and/or
0790  * modify it under the terms of the GNU Library General Public
0791  * License version 2 as published by the Free Software Foundation.
0792  *
0793  * This library is distributed in the hope that it will be useful,
0794  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0795  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0796  * Library General Public License for more details.
0797  *
0798  * You should have received a copy of the GNU Library General Public License
0799  * along with this library; see the file COPYING.LIB.  If not, write to
0800  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0801  * Boston, MA 02110-1301, USA.
0802  */
0803 
0804 namespace ShadowEngine
0805 {
0806     // Not sure, doesn't work above 10
0807     static const int    MULTIPLICATION_FACTOR = 3;
0808     // Multiplication factor for pixels directly above, under, or next to the text
0809     static const double AXIS_FACTOR = 2.0;
0810     // Multiplication factor for pixels diagonal to the text
0811     static const double DIAGONAL_FACTOR = 0.1;
0812     // Self explanatory
0813     static const int    MAX_OPACITY = 200;
0814 
0815     double decay( QImage&, int, int );
0816 
0817     QImage makeShadow( const QPixmap& textPixmap, const QColor &bgColor )
0818     {
0819         const int w   = textPixmap.width();
0820         const int h   = textPixmap.height();
0821         const int bgr = bgColor.red();
0822         const int bgg = bgColor.green();
0823         const int bgb = bgColor.blue();
0824 
0825         int alphaShadow;
0826 
0827         // This is the source pixmap
0828         QImage img = textPixmap.toImage();
0829 
0830         QImage result( w, h, QImage::Format_ARGB32 );
0831         result.fill( 0 ); // fill with black
0832 
0833         static const int M = OSDWidget::SHADOW_SIZE;
0834         for( int i = M; i < w - M; i++) {
0835             for( int j = M; j < h - M; j++ )
0836             {
0837                 alphaShadow = (int) decay( img, i, j );
0838 
0839                 result.setPixel( i,j, qRgba( bgr, bgg , bgb, qMin( MAX_OPACITY, alphaShadow ) ) );
0840             }
0841         }
0842 
0843         return result;
0844     }
0845 
0846     double decay( QImage& source, int i, int j )
0847     {
0848         //if ((i < 1) || (j < 1) || (i > source.width() - 2) || (j > source.height() - 2))
0849         //    return 0;
0850 
0851         double alphaShadow;
0852         alphaShadow =(qGray(source.pixel(i-1,j-1)) * DIAGONAL_FACTOR +
0853                 qGray(source.pixel(i-1,j  )) * AXIS_FACTOR +
0854                 qGray(source.pixel(i-1,j+1)) * DIAGONAL_FACTOR +
0855                 qGray(source.pixel(i  ,j-1)) * AXIS_FACTOR +
0856                 0                         +
0857                 qGray(source.pixel(i  ,j+1)) * AXIS_FACTOR +
0858                 qGray(source.pixel(i+1,j-1)) * DIAGONAL_FACTOR +
0859                 qGray(source.pixel(i+1,j  )) * AXIS_FACTOR +
0860                 qGray(source.pixel(i+1,j+1)) * DIAGONAL_FACTOR) / MULTIPLICATION_FACTOR;
0861 
0862         return alphaShadow;
0863     }
0864 }
0865