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

0001 /****************************************************************************************
0002  * Copyright (c) 2009 Thomas Luebking <thomas.luebking@web.de>                          *
0003  * Copyright (c) 2010 Mark Kretschmann <kretschmann@kde.org>                            *
0004  *                                                                                      *
0005  * This program is free software; you can redistribute it and/or modify it under        *
0006  * the terms of the GNU General Public License as published by the Free Software        *
0007  * Foundation; either version 2 of the License, or (at your option) any later           *
0008  * version.                                                                             *
0009  *                                                                                      *
0010  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0011  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0012  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0013  *                                                                                      *
0014  * You should have received a copy of the GNU General Public License along with         *
0015  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0016  ****************************************************************************************/
0017 
0018 #define DEBUG_PREFIX "MainToolbar"
0019 
0020 #include "MainToolbar.h"
0021 
0022 #include "App.h"
0023 #include "ActionClasses.h"
0024 #include "core/support/Amarok.h"
0025 #include "core/support/Debug.h"
0026 #include "EngineController.h"
0027 #include "GlobalCurrentTrackActions.h"
0028 #include "MainWindow.h"
0029 #include "SvgHandler.h"
0030 #include "amarokconfig.h"
0031 #include "amarokurls/AmarokUrl.h"
0032 #include "amarokurls/AmarokUrlHandler.h"
0033 #include "core/capabilities/ActionsCapability.h"
0034 #include "core/capabilities/BookmarkThisCapability.h"
0035 #include "core/meta/Meta.h"
0036 #include "core/meta/support/MetaUtility.h"
0037 #include "core-impl/capabilities/timecode/TimecodeLoadCapability.h"
0038 #include "playlist/PlaylistActions.h"
0039 #include "playlist/PlaylistModelStack.h"
0040 #include "playlist/PlaylistController.h"
0041 #include "widgets/AnimatedLabelStack.h"
0042 #include "widgets/PlayPauseButton.h"
0043 #include "widgets/SliderWidget.h"
0044 #include "widgets/TrackActionButton.h"
0045 #include "widgets/VolumeDial.h"
0046 
0047 #include <QEvent>
0048 #include <QHBoxLayout>
0049 #include <QLabel>
0050 #include <QMouseEvent>
0051 #include <QPainter>
0052 #include <QPaintEvent>
0053 #include <QResizeEvent>
0054 #include <QSlider>
0055 #include <QTimer>
0056 #include <QVBoxLayout>
0057 
0058 // #define prev_next_role QPalette::Link
0059 #define prev_next_role foregroundRole()
0060 static const int prevOpacity = 128;
0061 static const int nextOpacity = 160;
0062 
0063 static const int icnSize = 48;
0064 
0065 static const int leftRightSpacer = 15;
0066 static const int timeLabelMargin = 6;
0067 static const int skipPadding = 6;
0068 static const int skipMargin = 20;
0069 static const int constant_progress_ratio_minimum_width = 640;
0070 static const int space_between_tracks_and_slider = 2;
0071 static const float track_fontsize_factor = 1.1f;
0072 static const int track_action_spacing = 6;
0073 
0074 
0075 MainToolbar::MainToolbar( QWidget *parent )
0076     : QToolBar( i18n( "Main Toolbar" ), parent )
0077     , m_lastTime( -1 )
0078     , m_trackBarAnimationTimer( 0 )
0079 {
0080     DEBUG_BLOCK
0081     setObjectName( "MainToolbar" );
0082 
0083     m_promoString = i18n( "Rediscover Your Music" );
0084 
0085     // control padding between buttons and labels, it's style controlled by default
0086     layout()->setSpacing( 0 );
0087 
0088     EngineController *engine = The::engineController();
0089 
0090     setIconSize( QSize( icnSize, icnSize ) );
0091 
0092     QWidget *spacerWidget = new QWidget(this);
0093     spacerWidget->setFixedWidth( leftRightSpacer );
0094     addWidget( spacerWidget );
0095 
0096     m_playPause = new PlayPauseButton;
0097     m_playPause->setPlaying( engine->isPlaying() );
0098     m_playPause->setFixedSize( icnSize, icnSize );
0099     addWidget( m_playPause );
0100     connect( m_playPause, &PlayPauseButton::toggled, engine, &EngineController::playPause );
0101 
0102     QWidget *info = new QWidget(this);
0103     QVBoxLayout *vl = new QVBoxLayout( info );
0104 
0105     QFont fnt = QApplication::font(); // don't use the toolbar font. Often small to support icons only.
0106     if( fnt.pointSize() > 0 )
0107         fnt.setPointSize( qRound(fnt.pointSize() * track_fontsize_factor) );
0108     const int fntH = QFontMetrics( QApplication::font() ).height()*2; //double size svg render for scaled high dpi
0109 
0110     m_skip_left = The::svgHandler()->renderSvg( "tiny_skip_left", 80*fntH/128, fntH, "tiny_skip_left" );
0111     m_skip_right = The::svgHandler()->renderSvg( "tiny_skip_right", 80*fntH/128, fntH, "tiny_skip_right" );
0112 
0113     m_prev.key = nullptr;
0114     m_prev.label = new AnimatedLabelStack(QStringList(), info);
0115     m_prev.label->setFont( fnt );
0116     if( layoutDirection() == Qt::LeftToRight )
0117         m_prev.label->setPadding( m_skip_right.width()/2 + skipPadding + skipMargin, 0 );
0118     else
0119         m_prev.label->setPadding( 0, m_skip_right.width()/2 + skipPadding + skipMargin );
0120     m_prev.label->setAlign( Qt::AlignLeft );
0121     m_prev.label->setAnimated( false );
0122     m_prev.label->setOpacity( prevOpacity );
0123     m_prev.label->installEventFilter( this );
0124     m_prev.label->setForegroundRole( prev_next_role );
0125     connect( m_prev.label, &AnimatedLabelStack::clicked, The::playlistActions(), &Playlist::Actions::back );
0126 
0127     m_current.label = new AnimatedLabelStack( QStringList( m_promoString ), info );
0128     m_current.label->setFont( fnt );
0129     m_current.label->setPadding( 24, 24 );
0130     m_current.label->setBold( true );
0131     m_current.label->setLayout( new QHBoxLayout );
0132     m_current.label->installEventFilter( this );
0133     connect( m_current.label, &AnimatedLabelStack::clicked,
0134              Amarok::actionCollection()->action("show_active_track"), &QAction::trigger );
0135 
0136     m_next.key = nullptr;
0137     m_next.label = new AnimatedLabelStack(QStringList(), info);
0138     m_next.label->setFont( fnt );
0139     if( layoutDirection() == Qt::LeftToRight )
0140         m_next.label->setPadding( 0, m_skip_left.width()/2 + skipPadding + skipMargin );
0141     else
0142         m_next.label->setPadding( m_skip_left.width()/2 + skipPadding + skipMargin, 0 );
0143     m_next.label->setAlign( Qt::AlignRight );
0144     m_next.label->setAnimated( false );
0145     m_next.label->setOpacity( nextOpacity );
0146     m_next.label->installEventFilter( this );
0147     m_next.label->setForegroundRole( prev_next_role );
0148     connect( m_next.label, &AnimatedLabelStack::clicked, The::playlistActions(), &Playlist::Actions::next );
0149 
0150     m_dummy.label = new AnimatedLabelStack(QStringList(), info);
0151     m_next.label->setFont( fnt );
0152     m_dummy.label->hide();
0153 
0154     vl->addItem( m_trackBarSpacer = new QSpacerItem(0, m_current.label->minimumHeight(), QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
0155 
0156     vl->addSpacing( space_between_tracks_and_slider );
0157 
0158     connect( m_prev.label, &AnimatedLabelStack::pulsing, m_current.label, &AnimatedLabelStack::setStill );
0159     connect( m_next.label, &AnimatedLabelStack::pulsing, m_current.label, &AnimatedLabelStack::setStill );
0160 
0161     m_timeLabel = new QLabel( info );
0162     m_timeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight );
0163 
0164     m_slider = new Amarok::TimeSlider( info );
0165     connect( m_slider, &Amarok::TimeSlider::sliderReleased, The::engineController(), &EngineController::seekTo );
0166     connect( m_slider, &Amarok::TimeSlider::valueChanged, this, &MainToolbar::setLabelTime );
0167     connect( pApp, &App::settingsChanged, this, &MainToolbar::layoutProgressBar );
0168 
0169     m_remainingTimeLabel = new QLabel( info );
0170     m_remainingTimeLabel->setAlignment( Qt::AlignVCenter | Qt::AlignLeft );
0171 
0172     const int pbsH = qMax( m_timeLabel->sizeHint().height(), m_slider->sizeHint().height() );
0173     vl->addItem( m_progressBarSpacer = new QSpacerItem(0, pbsH, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed ) );
0174 
0175     addWidget( info );
0176 
0177     m_volume = new VolumeDial( this );
0178     m_volume->setRange( 0, 100);
0179     m_volume->setValue( engine->volume() );
0180     m_volume->setMuted( engine->isMuted() );
0181     m_volume->setFixedSize( icnSize, icnSize );
0182     m_volume->addWheelProxies( QList<QWidget*>() << this << info
0183                                                  << m_prev.label << m_current.label << m_next.label
0184                                                  << m_timeLabel << m_remainingTimeLabel );
0185 
0186     addWidget( m_volume );
0187     connect( engine, &EngineController::volumeChanged, m_volume, &VolumeDial::setValue );
0188     connect( m_volume, &VolumeDial::valueChanged, engine, &EngineController::setVolume );
0189     connect( m_volume, &VolumeDial::muteToggled, engine, &EngineController::setMuted );
0190 
0191     spacerWidget = new QWidget(this);
0192     spacerWidget->setFixedWidth( leftRightSpacer );
0193     addWidget( spacerWidget );
0194 }
0195 
0196 void
0197 MainToolbar::addBookmark( const QString &name, int milliSeconds )
0198 {
0199     if( m_slider )
0200         m_slider->drawTriangle( name, milliSeconds, false );
0201 }
0202 
0203 // Moves the label towards its target position by 2/3rds of the remaining distance
0204 static void adjustLabelPos( QWidget *label, int targetX )
0205 {
0206     QRect r = label->geometry();
0207     int d = targetX - r.x();
0208     if( d )
0209     {
0210         r.translate( qMin( qAbs(d), r.width()/6 ) * (d > 0 ? 1 : -1), 0 );
0211         label->setGeometry( r );
0212     }
0213 }
0214 
0215 void
0216 MainToolbar::animateTrackLabels()
0217 {
0218     bool done = true;
0219 
0220     int off = -m_current.label->parentWidget()->geometry().x();
0221     adjustLabelPos( m_prev.label, m_prev.rect.x() + off );
0222     m_prev.label->setOpacity( prevOpacity );
0223     if(done)
0224         done = m_prev.label->geometry().x() == m_prev.rect.x() + off;
0225 
0226     adjustLabelPos( m_current.label, m_current.rect.x() + off );
0227     if(done)
0228         done = m_current.label->geometry().x() == m_current.rect.x() + off;
0229 
0230     adjustLabelPos( m_next.label, m_next.rect.x() + off );
0231     m_next.label->setOpacity( nextOpacity );
0232     if(done)
0233         done = m_next.label->geometry().x() == m_next.rect.x() + off;
0234 
0235     adjustLabelPos( m_dummy.label, m_dummy.targetX );
0236     if( m_dummy.label->geometry().x() == m_dummy.targetX )
0237         m_dummy.label->hide();
0238     else
0239         done = false;
0240 
0241     if( done )
0242     {
0243         killTimer( m_trackBarAnimationTimer );
0244         setCurrentTrackActionsVisible( true );
0245         m_trackBarAnimationTimer = 0;
0246     }
0247 }
0248 
0249 void
0250 MainToolbar::stopped()
0251 {
0252     m_slider->setValue( m_slider->minimum() );
0253     m_slider->update(); // necessary to clean the moodbar...
0254     setLabelTime( -1 );
0255     m_playPause->setPlaying( false );
0256 }
0257 
0258 void
0259 MainToolbar::paused()
0260 {
0261     m_playPause->setPlaying( false );
0262 }
0263 
0264 void
0265 MainToolbar::playing()
0266 {
0267     m_playPause->setPlaying( true );
0268 }
0269 
0270 void
0271 MainToolbar::volumeChanged( int percent )
0272 {
0273     m_volume->setValue( percent );
0274 }
0275 
0276 void
0277 MainToolbar::muteStateChanged( bool mute )
0278 {
0279     m_volume->setMuted( mute );
0280 }
0281 
0282 void
0283 MainToolbar::layoutProgressBar()
0284 {
0285     const int limit = constant_progress_ratio_minimum_width;
0286     const QRect r = m_progressBarSpacer->geometry();
0287 
0288     const int bw = AmarokConfig::showMoodbarInSlider() ? 10 : 6;
0289     int w = bw;
0290     if( size().width() < limit )
0291     {
0292         w = (limit<<7)/size().width();
0293         w = w*w*w*bw;
0294         w /= (1<<21);
0295     }
0296 
0297     w = r.width() / w;
0298     int tlW = m_timeLabel->width();
0299     if( tlW + timeLabelMargin > w )
0300         w = tlW;
0301     int rtlW = m_remainingTimeLabel->width();
0302     if( rtlW + timeLabelMargin > w )
0303         w = rtlW;
0304 
0305     QRect pb = r.adjusted( w, 0, -w, 0 );
0306     m_slider->setGeometry( pb );
0307 
0308     QRect tlR( 0, 0, tlW, r.height() );
0309     QRect rtlR( 0, 0, rtlW, r.height() );
0310 
0311     if( layoutDirection() == Qt::LeftToRight )
0312     {
0313         tlR.moveTopRight( pb.topLeft() - QPoint( timeLabelMargin, 0 ) );
0314         rtlR.moveTopLeft( pb.topRight() + QPoint( timeLabelMargin, 0 )  );
0315     }
0316     else
0317     {
0318         rtlR.moveTopRight( pb.topLeft() - QPoint( timeLabelMargin, 0 ) );
0319         tlR.moveTopLeft( pb.topRight() + QPoint( timeLabelMargin, 0 )  );
0320     }
0321 
0322     m_timeLabel->setGeometry( tlR );
0323     m_remainingTimeLabel->setGeometry( rtlR );
0324 }
0325 
0326 void
0327 MainToolbar::layoutTrackBar()
0328 {
0329     m_dummy.label->hide();
0330     // this is the label parenting widget ("info") offset
0331     const QPoint off = m_current.label->parentWidget()->geometry().topLeft();
0332     QRect r = m_trackBarSpacer->geometry();
0333     r.setWidth( r.width() / 3);
0334     int d = r.width();
0335 
0336     if( layoutDirection() == Qt::RightToLeft )
0337     {
0338         d = -d;
0339         r.moveRight( m_trackBarSpacer->geometry().right() );
0340     }
0341 
0342     m_prev.rect = r.translated( off );
0343     m_prev.label->setGeometry( r );
0344     m_prev.label->setOpacity( prevOpacity );
0345 
0346     r.translate( d, 0 );
0347     m_current.rect = r.translated( off );
0348     m_current.label->setGeometry( r );
0349 
0350     r.translate( d, 0 );
0351     m_next.rect = r.translated( off );
0352     m_next.label->setGeometry( r );
0353     m_next.label->setOpacity( nextOpacity );
0354 
0355     setCurrentTrackActionsVisible( true );
0356 }
0357 
0358 void
0359 MainToolbar::updateCurrentTrackActions()
0360 {
0361     // wipe layout ================
0362     QLayoutItem *item;
0363     while ( (item = m_current.label->layout()->takeAt(0)) )
0364     {
0365         delete item->widget();
0366         delete item;
0367     }
0368 
0369     // collect actions ================
0370     QList<QAction*> actions;
0371 
0372     foreach( QAction* action, The::globalCurrentTrackActions()->actions() )
0373         actions << action;
0374 
0375     Meta::TrackPtr track = The::engineController()->currentTrack();
0376     if( track )
0377     {
0378         QScopedPointer< Capabilities::ActionsCapability > ac( track->create<Capabilities::ActionsCapability>() );
0379         if( ac )
0380             actions << ac->actions();
0381 
0382         QScopedPointer< Capabilities::BookmarkThisCapability > btc( track->create<Capabilities::BookmarkThisCapability>() );
0383         if( btc && btc->bookmarkAction() )
0384             actions << btc->bookmarkAction();
0385     }
0386 
0387     QHBoxLayout *hbl = static_cast<QHBoxLayout*>( m_current.label->layout() );
0388     hbl->setContentsMargins( 0, 0, 0, 0 );
0389     hbl->setSpacing( track_action_spacing );
0390 
0391     TrackActionButton *btn;
0392     const int n = actions.count() / 2;
0393     for ( int i = 0; i < actions.count(); ++i )
0394     {
0395         if( i == n )
0396             hbl->addStretch( 10 );
0397         btn = new TrackActionButton( m_current.label, actions.at(i) );
0398         if( !actions.at(i)->parent() ) // see documentation of ActionsCapability::actions
0399             actions.at(i)->setParent(btn);
0400         btn->installEventFilter( this );
0401         hbl->addWidget( btn );
0402     }
0403 }
0404 
0405 #define HAS_TAG(_TAG_) track->_TAG_() && !track->_TAG_()->name().isEmpty()
0406 #define TAG(_TAG_) track->_TAG_()->prettyName()
0407 #define CONTAINS_TAG(_TAG_) contains( TAG(_TAG_), Qt::CaseInsensitive )
0408 
0409 static QStringList metadata( Meta::TrackPtr track )
0410 {
0411     QStringList list;
0412     if( track )
0413     {
0414         bool noTags = false;
0415         QString title = track->prettyName();
0416         if(( noTags = title.isEmpty() )) // should not happen
0417             title = track->prettyUrl();
0418         if(( noTags = title.isEmpty() )) // should never happen
0419             title = track->playableUrl().url();
0420         if(( noTags = title.isEmpty() )) // sth's MEGA wrong ;-)
0421             title = "???";
0422 
0423         // no tags -> probably filename. try to strip the suffix
0424         if( noTags || track->name().isEmpty() )
0425         {
0426             noTags = true;
0427             int dot = title.lastIndexOf('.');
0428             if( dot > 0 && title.length() - dot < 6 )
0429                 title = title.left( dot );
0430         }
0431 
0432         // the track has no tags, is long or contains tags other than the titlebar
0433         // ==> separate
0434         if( noTags || title.length() > 50 ||
0435              (HAS_TAG(artist) && title.CONTAINS_TAG(artist)) ||
0436              (HAS_TAG(composer) && title.CONTAINS_TAG(composer)) ||
0437              (HAS_TAG(album) && title.CONTAINS_TAG(album)) )
0438         {
0439             // this will split "all-in-one" filename tags
0440             QRegExp rx("(\\s+-\\s+|\\s*;\\s*|\\s*:\\s*)");
0441             list << title.split( rx, Qt::SkipEmptyParts );
0442             QList<QString>::iterator i = list.begin();
0443             bool ok;
0444             while ( i != list.end() )
0445             {
0446                 // check whether this entry is only a number, i.e. probably year or track #
0447                 i->toInt( &ok );
0448                 if( ok )
0449                     i = list.erase( i );
0450                 else
0451                     ++i;
0452             }
0453         }
0454         else // plain title
0455         {
0456             list << title;
0457         }
0458 
0459         if( HAS_TAG(artist) && !list.CONTAINS_TAG(artist) )
0460             list << TAG(artist);
0461         else if( HAS_TAG(composer) && !list.CONTAINS_TAG(composer) )
0462             list << TAG(composer);
0463         if( HAS_TAG(album) && !list.CONTAINS_TAG(album) )
0464             list << TAG(album);
0465 
0466         /* other tags
0467         string year
0468         string genre
0469         double score
0470         int rating
0471         qint64 length // ms
0472         int sampleRate
0473         int bitrate
0474         int trackNumber
0475         int discNumber
0476         uint lastPlayed
0477         uint firstPlayed
0478         int playCount
0479         QString type
0480         bool inCollection
0481         */
0482     }
0483     return list;
0484 }
0485 
0486 #undef HAS_TAG
0487 #undef TAG
0488 #undef CONTAINS_TAG
0489 
0490 void
0491 MainToolbar::updatePrevAndNext()
0492 {
0493     if( !The::engineController()->currentTrack() )
0494     {
0495         m_prev.key = nullptr;
0496         m_prev.label->setForegroundRole( foregroundRole() );
0497         m_prev.label->setOpacity( 96 );
0498         m_prev.label->setData( QStringList() ); // << "[ " + i18n("Previous") + " ]" );
0499         m_prev.label->setCursor( Qt::ArrowCursor );
0500         m_next.key = nullptr;
0501         m_next.label->setForegroundRole( foregroundRole() );
0502         m_next.label->setOpacity( 96 );
0503         m_next.label->setData( QStringList() ); // << "[ " + i18n("Next") + " ]"  );
0504         m_next.label->setCursor( Qt::ArrowCursor );
0505         m_current.label->setUpdatesEnabled( true );
0506         return;
0507     }
0508 
0509     // NOTICE: i don't like this, but the order is important.
0510     // Reason is the (current) behaviour of the RandomTrackNavigator
0511     // when the playlist is completed it will clear the history and reshuffle
0512     // to be able to sneakpeak the next track, it's necessary to trigger this with this query
0513     // if we'd query the previous track first, we'd get a track that's actually no more present after
0514     // the next track query. by this order we'll get a 0L track, what's also the navigators opinion
0515     // about its queue :-\ //
0516     bool needUpdate = false;
0517     bool hadKey = bool(m_next.key);
0518     Meta::TrackPtr track = The::playlistActions()->likelyNextTrack();
0519     m_next.key = track ? track.data() : nullptr;
0520     m_next.label->setForegroundRole( prev_next_role );
0521     m_next.label->setOpacity( nextOpacity );
0522     m_next.label->setData( metadata( track ) );
0523     m_next.label->setCursor( track ? Qt::PointingHandCursor : Qt::ArrowCursor );
0524     if( hadKey != bool(m_next.key) )
0525         needUpdate = true;
0526 
0527     hadKey = bool(m_prev.key);
0528     track = The::playlistActions()->likelyPrevTrack();
0529     m_prev.key = track ? track.data() : nullptr;
0530     m_prev.label->setForegroundRole( prev_next_role );
0531     m_next.label->setOpacity( prevOpacity );
0532     m_prev.label->setData( metadata( track ) );
0533     m_prev.label->setCursor( track ? Qt::PointingHandCursor : Qt::ArrowCursor );
0534     if( hadKey != bool(m_prev.key) )
0535         needUpdate = true;
0536 
0537     // we may have disabled it as otherwise the current label gets updated one eventcycle before prev & next
0538     // see ::engineTrackChanged()
0539     m_current.label->setUpdatesEnabled( true );
0540 
0541     if( needUpdate )
0542         update();
0543 
0544     // unanimated change, probably by sliding the bar - fix label positions
0545     if( !m_trackBarAnimationTimer )
0546         layoutTrackBar();
0547 }
0548 
0549 void
0550 MainToolbar::updateBookmarks( const QString *BookmarkName )
0551 {
0552     // DEBUG_BLOCK
0553     m_slider->clearTriangles();
0554     if( Meta::TrackPtr track = The::engineController()->currentTrack() )
0555     {
0556         if( track->has<Capabilities::TimecodeLoadCapability>() )
0557         {
0558             Capabilities::TimecodeLoadCapability *tcl = track->create<Capabilities::TimecodeLoadCapability>();
0559             BookmarkList list = tcl->loadTimecodes();
0560             // debug() << "found " << list.count() << " timecodes on this track";
0561             foreach( AmarokUrlPtr url, list )
0562             {
0563                 if( url->command() == "play" && url->args().keys().contains( "pos" ) )
0564                 {
0565                     int pos = url->args().value( "pos" ).toDouble() * 1000;
0566                     debug() << "showing timecode: " << url->name() << " at " << pos ;
0567                     m_slider->drawTriangle( url->name(), pos, ( BookmarkName && BookmarkName == url->name() ) );
0568                 }
0569             }
0570             delete tcl;
0571         }
0572     }
0573 }
0574 
0575 void
0576 MainToolbar::trackChanged( Meta::TrackPtr track )
0577 {
0578     if( !isVisible() || (m_trackBarAnimationTimer && track && track.data() == m_current.key) )
0579         return;
0580     if( m_trackBarAnimationTimer )
0581     {
0582         killTimer( m_trackBarAnimationTimer );
0583         m_trackBarAnimationTimer = 0;
0584     }
0585 
0586     if( track )
0587     {
0588         m_current.key = track.data();
0589         m_current.uidUrl = track->uidUrl();
0590         m_current.label->setUpdatesEnabled( false );
0591         m_current.label->setData( metadata( track ) );
0592         m_current.label->setCursor( Qt::PointingHandCursor );
0593 
0594         // If all labels are in position and this is a single step for or back, we perform a slide
0595         // on the other two labels, i.e. e.g. move the prev to current label position and current
0596         // to the next and the animate the move into their target positions
0597         QRect r = m_trackBarSpacer->geometry();
0598         r.setWidth( r.width() / 3 );
0599         int d = r.width();
0600 
0601         if( layoutDirection() == Qt::RightToLeft )
0602         {
0603             d = -d;
0604             r.moveRight( m_trackBarSpacer->geometry().right() );
0605         }
0606 
0607         if( isVisible() &&  m_current.label->geometry().x() == r.x() + d )
0608         {
0609             if( m_current.key == m_next.key && m_current.key != m_prev.key )
0610             {
0611                 setCurrentTrackActionsVisible( false );
0612 
0613                 // left
0614                 m_dummy.targetX = r.x() - d;
0615 //                 if( d < 0 ) // rtl
0616 //                     m_dummy.targetX -= d;
0617                 m_dummy.label->setGeometry( r );
0618                 m_dummy.label->setData( m_prev.label->data() );
0619                 m_dummy.label->show();
0620                 // center
0621                 r.translate( d, 0 );
0622                 m_prev.label->setGeometry( r );
0623                 // right
0624                 r.translate( d, 0 );
0625                 m_current.label->setGeometry( r );
0626                 m_next.label->setGeometry( r );
0627                 m_next.label->setOpacity( 0 );
0628                 m_next.label->raise();
0629 
0630                 animateTrackLabels();
0631                 m_trackBarAnimationTimer = startTimer( 40 );
0632             }
0633             else if( m_current.key == m_prev.key )
0634             {
0635                 setCurrentTrackActionsVisible( false );
0636 
0637                 // left
0638                 m_prev.label->setGeometry( r );
0639                 m_current.label->setGeometry( r );
0640                 // center
0641                 r.translate( d, 0 );
0642                 m_next.label->setGeometry( r );
0643 
0644                 // right
0645                 r.translate( d, 0 );
0646                 m_dummy.targetX = r.x() + d;
0647                 m_dummy.label->setGeometry( r );
0648                 m_dummy.label->setData( m_next.label->data() );
0649                 m_dummy.label->show();
0650 
0651                 m_prev.label->setOpacity( 0 );
0652                 m_prev.label->raise();
0653 
0654                 animateTrackLabels();
0655                 m_trackBarAnimationTimer = startTimer( 40 );
0656             }
0657         }
0658         trackLengthChanged( The::engineController()->trackLength() );
0659     }
0660     else
0661     {
0662         // no track
0663         setLabelTime( -1 );
0664         m_slider->setValue( m_slider->minimum() );
0665         m_current.key = nullptr;
0666         m_current.uidUrl.clear();
0667         m_current.label->setData( QStringList( m_promoString ) );
0668         m_current.label->setCursor( Qt::ArrowCursor );
0669     }
0670 
0671     updateCurrentTrackActions();
0672 
0673     m_trackBarSpacer->changeSize(0, m_current.label->minimumHeight(), QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
0674     const int pbsH = qMax( m_timeLabel->sizeHint().height(), m_slider->sizeHint().height() );
0675     m_progressBarSpacer->changeSize(0, pbsH, QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
0676 
0677     QTimer::singleShot( 0, this, &MainToolbar::updatePrevAndNext );
0678 }
0679 
0680 void
0681 MainToolbar::trackLengthChanged( qint64 ms )
0682 {
0683     m_slider->setRange( 0, ms );
0684     m_slider->setEnabled( ms > 0 );
0685 
0686     // get the urlid of the current track as the engine might stop and start several times
0687     // when skipping last.fm tracks, so we need to know if we are still on the same track...
0688     if( Meta::TrackPtr track = The::engineController()->currentTrack() )
0689         m_current.uidUrl = track->uidUrl();
0690 
0691     updateBookmarks( nullptr );
0692 }
0693 
0694 void
0695 MainToolbar::trackPositionChanged( qint64 position, bool /*userSeek*/ )
0696 {
0697     if( m_slider->isEnabled() )
0698         m_slider->setSliderValue( position );
0699     else
0700         setLabelTime( position );
0701 }
0702 
0703 void
0704 MainToolbar::showEvent( QShowEvent *ev )
0705 {
0706     EngineController *engine = The::engineController();
0707 
0708     connect( engine, &EngineController::stopped,
0709              this, &MainToolbar::stopped );
0710     connect( engine, &EngineController::paused,
0711              this, &MainToolbar::paused );
0712     connect( engine, &EngineController::trackPlaying,
0713              this, &MainToolbar::playing );
0714 
0715     connect( engine, &EngineController::trackChanged,
0716              this, &MainToolbar::trackChanged );
0717     connect( engine, &EngineController::trackMetadataChanged,
0718              this, &MainToolbar::trackChanged );
0719     connect( engine, &EngineController::trackLengthChanged,
0720              this, &MainToolbar::trackLengthChanged );
0721     connect( engine, &EngineController::trackPositionChanged,
0722              this, &MainToolbar::trackPositionChanged );
0723     connect( engine, &EngineController::volumeChanged,
0724              this, &MainToolbar::volumeChanged );
0725     connect( engine, &EngineController::muteStateChanged,
0726              this, &MainToolbar::muteStateChanged );
0727 
0728     // We need the three changed signals:
0729     // 1. the playlist changes (PlaylistController)
0730     // 2. the sorting of the playlist changed (Playlist)
0731     // 3. The navigator changed to e.g. dynamic mode (PlaylistActions)
0732 
0733     connect( The::playlistController(), &Playlist::Controller::changed,
0734              this, &MainToolbar::updatePrevAndNext );
0735 
0736     connect( The::playlist()->qaim(), &QAbstractItemModel::dataChanged, //FIXME: dataChanged used to be queueChanged
0737              this, &MainToolbar::updatePrevAndNext );
0738 
0739     connect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
0740              this, &MainToolbar::updatePrevAndNext );
0741 
0742     connect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodesUpdated,
0743              this, &MainToolbar::updateBookmarks );
0744     connect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodeAdded,
0745              this, &MainToolbar::addBookmark );
0746 
0747     QToolBar::showEvent( ev );
0748     trackChanged( The::engineController()->currentTrack() );
0749     layoutTrackBar();
0750     layoutProgressBar();
0751     m_playPause->setPlaying( The::engineController()->isPlaying() );
0752     trackPositionChanged( engine->trackPositionMs(), false ); // Refresh slider on restore
0753 }
0754 
0755 
0756 void
0757 MainToolbar::hideEvent( QHideEvent *ev )
0758 {
0759     QToolBar::hideEvent( ev );
0760 
0761     disconnect( The::engineController(), nullptr, this, nullptr );
0762 
0763     disconnect( The::playlistController(), &Playlist::Controller::changed,
0764                 this, &MainToolbar::updatePrevAndNext );
0765 
0766     disconnect( The::playlist()->qaim(), &QAbstractItemModel::dataChanged, //FIXME: dataChanged used to be queueChanged
0767                 this, &MainToolbar::updatePrevAndNext );
0768 
0769     disconnect( The::playlistActions(), &Playlist::Actions::navigatorChanged,
0770                 this, &MainToolbar::updatePrevAndNext );
0771 
0772     disconnect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodesUpdated,
0773                 this, &MainToolbar::updateBookmarks );
0774     disconnect( The::amarokUrlHandler(), &AmarokUrlHandler::timecodeAdded,
0775                 this, &MainToolbar::addBookmark );
0776 }
0777 
0778 void
0779 MainToolbar::paintEvent( QPaintEvent *ev )
0780 {
0781     // let the style paint draghandles and in doubt override the shadow from above
0782     QToolBar::paintEvent( ev );
0783 
0784     // skip icons
0785     QPainter p( this );
0786     Skip *left = &m_prev;
0787     Skip *right = &m_next;
0788 
0789     if( layoutDirection() == Qt::RightToLeft )
0790     {
0791         left = &m_next;
0792         right = &m_prev;
0793     }
0794 
0795     p.setRenderHint( QPainter::SmoothPixmapTransform );
0796     if( left->key )
0797         p.drawPixmap( left->rect.left() + skipMargin,
0798                       left->rect.y() + ( left->rect.height() - m_skip_left.height()/2 ) /2,
0799                       m_skip_left.width()/2, m_skip_left.height()/2,
0800                       m_skip_left );
0801     if( right->key )
0802         p.drawPixmap( right->rect.right() - ( m_skip_right.width()/2 + skipMargin ),
0803                       right->rect.y() + ( right->rect.height() - m_skip_right.height()/2 ) /2,
0804                       m_skip_right.width()/2, m_skip_right.height()/2,
0805                       m_skip_right );
0806     p.end();
0807 }
0808 
0809 
0810 void
0811 MainToolbar::resizeEvent( QResizeEvent *ev )
0812 {
0813     if( ev->size().width() > 0 && ev->size().width() != ev->oldSize().width() )
0814     {
0815         layoutProgressBar();
0816         layoutTrackBar();
0817     }
0818 }
0819 
0820 void
0821 MainToolbar::setCurrentTrackActionsVisible( bool vis )
0822 {
0823     if( m_current.actionsVisible == vis )
0824         return;
0825     m_current.actionsVisible = vis;
0826     QLayoutItem *item;
0827     for ( int i = 0; i < m_current.label->layout()->count(); ++i )
0828     {
0829         item = m_current.label->layout()->itemAt( i );
0830         if( item->widget() )
0831             item->widget()->setVisible( vis );
0832     }
0833 }
0834 
0835 const char * timeString[4] = { "3:33", "33:33", "3:33:33", "33:33:33" };
0836 
0837 static inline int
0838 timeFrame( int secs )
0839 {
0840     if( secs < 10*60 ) // 9:59
0841         return 0;
0842     if( secs < 60*60 ) // 59:59
0843         return 1;
0844     if( secs < 10*60*60 ) // 9:59:59
0845         return 2;
0846     return 3; // 99:59:59
0847 }
0848 
0849 void
0850 MainToolbar::setLabelTime( int ms )
0851 {
0852     bool relayout = false;
0853     if( ms < 0 ) // clear
0854     {
0855         m_timeLabel->hide();
0856         m_remainingTimeLabel->hide();
0857         m_lastTime = -1;
0858         m_lastRemainingTime = -1;
0859         relayout = true;
0860     }
0861     else if( isVisible() ) // no need to do expensive stuff - it's updated every second anyway
0862     {
0863 
0864         const int secs = ms/1000;
0865         const int remainingSecs =  m_slider->maximum() > 0 ? (m_slider->maximum() - ms) / 1000 : 0;
0866 
0867         if( secs == m_lastTime && remainingSecs == m_lastRemainingTime )
0868             return;
0869 
0870         m_timeLabel->setText( Meta::secToPrettyTime( secs ) );
0871 
0872         // -- determine fix the timeLabel width at a sensible maximum
0873         int tf = timeFrame( secs );
0874         if( m_lastTime < 0 || tf != timeFrame( m_lastTime ) )
0875         {
0876             const int w = QFontMetrics( m_timeLabel->font() ).horizontalAdvance( timeString[tf] );
0877             m_timeLabel->setFixedWidth( w );
0878             relayout = true;
0879         }
0880         m_timeLabel->show();
0881 
0882         if( remainingSecs > 0 )
0883         {
0884             m_remainingTimeLabel->setText( '-' + Meta::secToPrettyTime( remainingSecs ) );
0885             tf = timeFrame( remainingSecs );
0886             if( m_lastRemainingTime < 0 || tf != timeFrame( m_lastRemainingTime ) )
0887             {
0888                 const int w = QFontMetrics( m_remainingTimeLabel->font() ).horizontalAdvance( QStringLiteral("-") + timeString[tf] );
0889                 m_remainingTimeLabel->setFixedWidth( w );
0890                 relayout = true;
0891             }
0892             m_remainingTimeLabel->show();
0893         }
0894         else
0895             m_remainingTimeLabel->hide();
0896 
0897         m_lastTime = secs;
0898         m_lastRemainingTime = remainingSecs;
0899     }
0900 
0901     if(relayout)
0902         layoutProgressBar();
0903 }
0904 
0905 void
0906 MainToolbar::timerEvent( QTimerEvent *ev )
0907 {
0908     if( ev->timerId() == m_trackBarAnimationTimer )
0909         animateTrackLabels();
0910     else
0911         QToolBar::timerEvent( ev );
0912 }
0913 
0914 bool
0915 MainToolbar::eventFilter( QObject *o, QEvent *ev )
0916 {
0917     if( ev->type() == QEvent::MouseMove )
0918     {
0919         QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
0920         if( mev->buttons() & Qt::LeftButton )
0921         if( o == m_current.label || o == m_prev.label || o == m_next.label )
0922         {
0923             setCurrentTrackActionsVisible( false );
0924             const int x = mev->globalPos().x();
0925             int d = x - m_drag.lastX;
0926             m_drag.lastX = x;
0927             const int globalDist = qAbs( x - m_drag.startX );
0928             if( globalDist > m_drag.max )
0929                 m_drag.max = globalDist;
0930             if( globalDist > m_prev.label->width() )
0931                 return false; // constrain to one item width
0932 
0933             m_current.label->setGeometry( m_current.label->geometry().translated( d, 0 ) );
0934             m_prev.label->setGeometry( m_prev.label->geometry().translated( d, 0 ) );
0935             m_next.label->setGeometry( m_next.label->geometry().translated( d, 0 ) );
0936         }
0937         return false;
0938     }
0939     if( ev->type() == QEvent::MouseButtonPress )
0940     {
0941         QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
0942         if( mev->button() == Qt::LeftButton )
0943         if( o == m_current.label || o == m_prev.label || o == m_next.label )
0944         {
0945             static_cast<QWidget*>(o)->setCursor( Qt::SizeHorCursor );
0946             m_drag.max = 0;
0947             m_drag.lastX = m_drag.startX = mev->globalPos().x();
0948         }
0949         return false;
0950     }
0951     if( ev->type() == QEvent::MouseButtonRelease )
0952     {
0953         QMouseEvent *mev = static_cast<QMouseEvent*>(ev);
0954         if( mev->button() == Qt::LeftButton )
0955         if( o == m_current.label || o == m_prev.label || o == m_next.label )
0956         {
0957             const int x = mev->globalPos().x();
0958             const int d = m_drag.startX - x;
0959             QRect r = m_trackBarSpacer->geometry();
0960             const int limit = r.width()/5; // 1/3 is too much, 1/6 to few
0961 
0962             // reset cursor
0963             AnimatedLabelStack *l = static_cast<AnimatedLabelStack*>(o);
0964             l->setCursor( l->data().isEmpty() ? Qt::ArrowCursor : Qt::PointingHandCursor );
0965 
0966             // if this was a _real_ drag, silently release the mouse
0967             const bool silentRelease = m_drag.max > 25;
0968             if( silentRelease )
0969             {   // this is a drag, release secretly
0970                 o->blockSignals( true );
0971                 o->removeEventFilter( this );
0972                 QMouseEvent mre( QEvent::MouseButtonRelease, mev->pos(), mev->globalPos(),
0973                                  Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
0974                 QCoreApplication::sendEvent( o, &mre );
0975                 o->installEventFilter( this );
0976                 o->blockSignals( false );
0977             }
0978 
0979             // if moved "far enough" jump to prev/next track
0980             // NOTICE the labels shall snap back _after_ the track has changed (in case)
0981             // as this is not reliable, we force a timered snapback sa well
0982             if( d > limit )
0983             {
0984                 The::playlistActions()->next();
0985                 QTimer::singleShot(500, this, &MainToolbar::layoutTrackBar );
0986             }
0987             else if( d < -limit )
0988             {
0989                 The::playlistActions()->back();
0990                 QTimer::singleShot(500, this, &MainToolbar::layoutTrackBar );
0991             }
0992             else
0993                 layoutTrackBar();
0994 
0995             return silentRelease;
0996         }
0997         return false;
0998     }
0999 
1000     if( ev->type() == QEvent::Enter )
1001     {
1002         if(o == m_next.label && m_next.key)
1003             m_next.label->setOpacity( 255 );
1004         else if(o == m_prev.label && m_prev.key)
1005             m_prev.label->setOpacity( 255 );
1006         else if( o->parent() == m_current.label ) // trackaction
1007         {
1008             QEvent e( QEvent::Leave );
1009             QCoreApplication::sendEvent( m_current.label, &e );
1010         }
1011         return false;
1012     }
1013     if( ev->type() == QEvent::Leave )
1014     {
1015         if(o == m_next.label && m_next.key)
1016             m_next.label->setOpacity( nextOpacity );
1017         else if(o == m_prev.label && m_prev.key)
1018             m_prev.label->setOpacity( prevOpacity );
1019         else if( o->parent() == m_current.label &&  // trackaction
1020                   m_current.label->rect().contains( m_current.label->mapFromGlobal(QCursor::pos()) ) )
1021         {
1022             QEvent e( QEvent::Enter );
1023             QCoreApplication::sendEvent( m_current.label, &e );
1024         }
1025         return false;
1026     }
1027     return false;
1028 }
1029 
1030