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