diff --git a/resources.qrc b/resources.qrc index 333b83349..5f43a73b5 100644 --- a/resources.qrc +++ b/resources.qrc @@ -136,6 +136,7 @@ data/images/grooveshark.png data/images/lastfm-icon.png data/images/playlist-header-tiled.png + data/images/share.png data/sql/dbmigrate-27_to_28.sql data/images/process-stop.png data/icons/tomahawk-icon-128x128-grayscale.png diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bc677d1e4..a2aba674d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -87,6 +87,7 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} LoadXSPFDialog.cpp AccountFactoryWrapper.cpp AccountFactoryWrapperDelegate.cpp + SocialWidget.cpp ) IF( WITH_BREAKPAD ) @@ -102,10 +103,9 @@ SET( tomahawkUI ${tomahawkUI} accounts/lastfm/LastFmConfig.ui audiocontrols.ui - LoadXSPFDialog.ui - AccountFactoryWrapper.ui + SocialWidget.ui ) INCLUDE_DIRECTORIES( diff --git a/src/SocialWidget.cpp b/src/SocialWidget.cpp new file mode 100644 index 000000000..0b491e0cf --- /dev/null +++ b/src/SocialWidget.cpp @@ -0,0 +1,262 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "SocialWidget.h" +#include "ui_SocialWidget.h" + +#include +#include +#include + +#include "globalactionmanager.h" +#include "utils/logger.h" + +#define CORNER_ROUNDNESS 8.0 +#define FADING_DURATION 500 +#define FONT_SIZE 16 +#define OPACITY 0.70 + + +SocialWidget::SocialWidget( QWidget* parent ) + : QWidget( parent ) // this is on purpose! + , ui( new Ui::SocialWidget ) + , m_opacity( 0.00 ) + , m_parent( parent ) + , m_parentRect( parent->rect() ) +{ + ui->setupUi( this ); + + setAttribute( Qt::WA_TranslucentBackground, true ); + setOpacity( m_opacity ); + + m_timer.setSingleShot( true ); + connect( &m_timer, SIGNAL( timeout() ), this, SLOT( hide() ) ); + +#ifdef Q_WS_MAC + QFont f( font() ); + f.setPointSize( f.pointSize() - 2 ); + setFont( f ); +#endif + + ui->charsLeftLabel->setForegroundRole( QPalette::HighlightedText ); + + m_parent->installEventFilter( this ); + + connect( ui->buttonBox, SIGNAL( accepted() ), SLOT( accept() ) ); + connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( deleteLater() ) ); + connect( ui->textEdit, SIGNAL( textChanged() ), SLOT( onChanged() ) ); + connect( ui->facebookButton, SIGNAL( clicked( bool ) ), SLOT( onChanged() ) ); + connect( ui->twitterButton, SIGNAL( clicked( bool ) ), SLOT( onChanged() ) ); + connect( GlobalActionManager::instance(), SIGNAL( shortLinkReady( QUrl, QUrl, QVariant ) ), SLOT( onShortLinkReady( QUrl, QUrl, QVariant ) ) ); + + onChanged(); +} + + +SocialWidget::~SocialWidget() +{ + delete ui; +} + + +void +SocialWidget::setOpacity( qreal opacity ) +{ + m_opacity = opacity; + + if ( m_opacity == 0.00 && !isHidden() ) + { + QWidget::hide(); + } + else if ( m_opacity > 0.00 && isHidden() ) + { + QWidget::show(); + } + + repaint(); +} + + +void +SocialWidget::show( int timeoutSecs ) +{ + if ( !isEnabled() ) + return; + + QPropertyAnimation* animation = new QPropertyAnimation( this, "opacity" ); + animation->setDuration( FADING_DURATION ); + animation->setEndValue( 1.0 ); + animation->start(); + + if( timeoutSecs > 0 ) + m_timer.start( timeoutSecs * 1000 ); +} + + +void +SocialWidget::hide() +{ + if ( !isEnabled() ) + return; + + QPropertyAnimation* animation = new QPropertyAnimation( this, "opacity" ); + animation->setDuration( FADING_DURATION ); + animation->setEndValue( 0.00 ); + animation->start(); +} + + +bool +SocialWidget::shown() const +{ + if ( !isEnabled() ) + return false; + + return m_opacity == OPACITY; +} + + +void +SocialWidget::paintEvent( QPaintEvent* event ) +{ + Q_UNUSED( event ); + + QPoint position( m_position - QPoint( size().width(), size().height() ) ); + if ( position != pos() ) + { + move( position ); + return; + } + + QPainter p( this ); + QRect r = contentsRect(); + + p.setBackgroundMode( Qt::TransparentMode ); + p.setRenderHint( QPainter::Antialiasing ); + p.setOpacity( m_opacity ); + + QPen pen( palette().dark().color(), .5 ); + p.setPen( pen ); + p.setBrush( QColor( 30, 30, 30, 255.0 * OPACITY ) ); + + p.drawRoundedRect( r, CORNER_ROUNDNESS, CORNER_ROUNDNESS ); + + QWidget::paintEvent( event ); + return; + + QTextOption to( Qt::AlignCenter ); + to.setWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ); + + // shrink to fit if needed + QFont f( font() ); + f.setPointSize( FONT_SIZE ); + f.setBold( true ); + + QRectF textRect = r.adjusted( 8, 8, -8, -8 ); + qreal availHeight = textRect.height(); + + QFontMetricsF fm( f ); + qreal textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, "SocialWidget" ).height(); + while( textHeight > availHeight ) + { + if( f.pointSize() <= 4 ) // don't try harder + break; + + f.setPointSize( f.pointSize() - 1 ); + fm = QFontMetricsF( f ); + textHeight = fm.boundingRect( textRect, Qt::AlignCenter | Qt::TextWordWrap, "SocialWidget" ).height(); + } + + p.setFont( f ); + p.setPen( palette().highlightedText().color() ); + p.drawText( r.adjusted( 8, 8, -8, -8 ), "SocialWidget", to ); +} + + +void +SocialWidget::onShortLinkReady( const QUrl& longUrl, const QUrl& shortUrl, const QVariant& callbackObj ) +{ + ui->textEdit->setText( tr( "Listening to \"%1\" by %2 on \"%3\" and loving it! %4" ).arg( m_query->track() ).arg( m_query->artist() ).arg( m_query->album() ).arg( shortUrl.toString() ) ); +} + + +void +SocialWidget::setQuery( const Tomahawk::query_ptr& query ) +{ + m_query = query; + ui->coverImage->setPixmap( query->cover( ui->coverImage->size() ) ); + onChanged(); + + QUrl longUrl = GlobalActionManager::instance()->openLinkFromQuery( query ); + GlobalActionManager::instance()->shortenLink( longUrl ); +} + + +void +SocialWidget::onChanged() +{ + const int remaining = charsAvailable() - ui->textEdit->toPlainText().length(); + ui->charsLeftLabel->setText( tr( "%1 characters left" ).arg( remaining ) ); + ui->buttonBox->button( QDialogButtonBox::Ok )->setEnabled( remaining >= 0 && ( ui->facebookButton->isChecked() || ui->twitterButton->isChecked() ) ); +} + + +void +SocialWidget::accept() +{ + tDebug() << "Sharing social link!"; + + deleteLater(); +} + + +unsigned int +SocialWidget::charsAvailable() const +{ + if ( ui->twitterButton->isChecked() ) + return 140; + + return 420; // facebook max length +} + + +void +SocialWidget::onGeometryUpdate() +{ + QPoint p( m_parent->rect().width() - m_parentRect.width(), m_parent->rect().height() - m_parentRect.height() ); + m_position += p; + m_parentRect = m_parent->rect(); + + QPoint position( m_position - QPoint( size().width(), size().height() ) ); + if ( position != pos() ) + { + move( position ); + } +} + + +bool +SocialWidget::eventFilter( QObject* object, QEvent* event ) +{ + if ( event->type() == QEvent::Resize ) + { + onGeometryUpdate(); + } + + return QObject::eventFilter( object, event ); +} diff --git a/src/SocialWidget.h b/src/SocialWidget.h new file mode 100644 index 000000000..57f57b2af --- /dev/null +++ b/src/SocialWidget.h @@ -0,0 +1,84 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef SOCIALWIDGET_H +#define SOCIALWIDGET_H + +#include +#include +#include + +#include "query.h" + +namespace Ui +{ + class SocialWidget; +} + +class SocialWidget : public QWidget +{ +Q_OBJECT +Q_PROPERTY( qreal opacity READ opacity WRITE setOpacity ) + +public: + SocialWidget( QWidget* parent ); + ~SocialWidget(); + + Tomahawk::query_ptr query() const { return m_query; } + void setQuery( const Tomahawk::query_ptr& query ); + + qreal opacity() const { return m_opacity; } + void setOpacity( qreal opacity ); + + QPoint position() const { return m_position; } + void setPosition( QPoint position ) { m_position = position; } + + bool shown() const; + +public slots: + void show( int timeoutSecs = 0 ); + void hide(); + +protected: +// void changeEvent( QEvent* e ); + void paintEvent( QPaintEvent* event ); + bool eventFilter( QObject* object, QEvent* event ); + +private slots: + void accept(); + void onChanged(); + void onShortLinkReady( const QUrl& longUrl, const QUrl& shortUrl, const QVariant& callbackObj ); + + void onGeometryUpdate(); + +private: + unsigned int charsAvailable() const; + + Ui::SocialWidget* ui; + + Tomahawk::query_ptr m_query; + + qreal m_opacity; + QPoint m_position; + + QWidget* m_parent; + QRect m_parentRect; + QTimer m_timer; +}; + +#endif // SOCIALWIDGET_H diff --git a/src/SocialWidget.ui b/src/SocialWidget.ui new file mode 100644 index 000000000..d8da77af0 --- /dev/null +++ b/src/SocialWidget.ui @@ -0,0 +1,118 @@ + + + SocialWidget + + + + 0 + 0 + 365 + 190 + + + + Form + + + + 8 + + + 8 + + + 8 + + + + + + + Facebook + + + true + + + + + + + Twitter + + + true + + + + + + + + + + + + 96 + 96 + + + + + 96 + 96 + + + + Cover + + + + + + + + 16777215 + 96 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + TextLabel + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 6a54e9144..d981f67ce 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -32,6 +32,7 @@ #include "utils/logger.h" #include "album.h" #include "dropjob.h" +#include "SocialWidget.h" #include "globalactionmanager.h" #include "viewmanager.h" @@ -44,6 +45,7 @@ AudioControls::AudioControls( QWidget* parent ) , m_repeatMode( PlaylistInterface::NoRepeat ) , m_shuffled( false ) , m_lastSliderCheck( 0 ) + , m_parent( parent ) { ui->setupUi( this ); setAcceptDrops( true ); @@ -82,6 +84,7 @@ AudioControls::AudioControls( QWidget* parent ) ui->repeatButton->setPixmap( RESPATH "images/repeat-off-pressed.png", QIcon::Off, QIcon::Active ); ui->volumeLowButton->setPixmap( RESPATH "images/volume-icon-muted.png" ); ui->volumeHighButton->setPixmap( RESPATH "images/volume-icon-full.png" ); + ui->socialButton->setPixmap( RESPATH "images/share.png" ); ui->loveButton->setPixmap( RESPATH "images/not-loved.png" ); ui->loveButton->setCheckable( true ); @@ -118,8 +121,9 @@ AudioControls::AudioControls( QWidget* parent ) connect( ui->shuffleButton, SIGNAL( clicked() ), SLOT( onShuffleClicked() ) ); connect( ui->artistTrackLabel, SIGNAL( clickedArtist() ), SLOT( onArtistClicked() ) ); - connect( ui->artistTrackLabel, SIGNAL( clickedTrack() ), SLOT( onTrackClicked() ) ); - connect( ui->albumLabel, SIGNAL( clickedAlbum() ), SLOT( onAlbumClicked() ) ); + connect( ui->artistTrackLabel, SIGNAL( clickedTrack() ), SLOT( onTrackClicked() ) ); + connect( ui->albumLabel, SIGNAL( clickedAlbum() ), SLOT( onAlbumClicked() ) ); + connect( ui->socialButton, SIGNAL( clicked() ), SLOT( onSocialButtonClicked() ) ); connect( ui->loveButton, SIGNAL( clicked( bool ) ), SLOT( onLoveButtonClicked( bool ) ) ); // @@ -247,6 +251,8 @@ AudioControls::onPlaybackLoading( const Tomahawk::result_ptr& result ) ui->loveButton->setEnabled( true ); ui->loveButton->setVisible( true ); + ui->socialButton->setEnabled( true ); + ui->socialButton->setVisible( true ); setCover(); setSocialActions(); @@ -319,7 +325,6 @@ AudioControls::onPlaybackResumed() { tDebug( LOGEXTRA ) << Q_FUNC_INFO; ui->stackedLayout->setCurrentWidget( ui->pauseButton ); - ui->loveButton->setVisible( true ); m_sliderTimeLine.resume(); } @@ -354,6 +359,8 @@ AudioControls::onPlaybackStopped() ui->stackedLayout->setCurrentWidget( ui->playPauseButton ); ui->loveButton->setEnabled( false ); ui->loveButton->setVisible( false ); + ui->socialButton->setEnabled( false ); + ui->socialButton->setVisible( false ); } @@ -590,6 +597,16 @@ AudioControls::droppedTracks( QList< query_ptr > tracks ) } +void +AudioControls::onSocialButtonClicked() +{ + SocialWidget* sw = new SocialWidget( m_parent ); + sw->setPosition( QCursor::pos() ); + sw->setQuery( m_currentTrack->toQuery() ); + sw->show(); +} + + void AudioControls::onLoveButtonClicked( bool checked ) { diff --git a/src/audiocontrols.h b/src/audiocontrols.h index b7adf31b8..a5eac1554 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -78,6 +78,7 @@ private slots: void onArtistClicked(); void onAlbumClicked(); void onTrackClicked(); + void onSocialButtonClicked(); void onLoveButtonClicked( bool ); void droppedTracks( QList ); @@ -89,7 +90,7 @@ private: void setCover(); void setSocialActions(); - Ui::AudioControls *ui; + Ui::AudioControls* ui; Tomahawk::result_ptr m_currentTrack; Tomahawk::PlaylistInterface::RepeatMode m_repeatMode; @@ -101,6 +102,8 @@ private: qint64 m_lastSliderCheck; bool m_noTimeChange; qint64 m_lastTextSecondShown; + + QWidget* m_parent; }; #endif // AUDIOCONTROLS_H diff --git a/src/audiocontrols.ui b/src/audiocontrols.ui index f07c1d784..fd4f871a3 100644 --- a/src/audiocontrols.ui +++ b/src/audiocontrols.ui @@ -287,6 +287,22 @@ + + + + + 0 + 0 + + + + PointingHandCursor + + + social + + + diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index ffde78745..e42c2f953 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -1,6 +1,7 @@ /* Copyright (C) 2011 Leo Franchi Copyright (C) 2011, Jeff Mitchell + Copyright (C) 2011-2012, Christian Muehlhaeuser This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,7 +46,6 @@ #include "playlist/topbar/topbar.h" #include "playlist/playlistview.h" - #include #include @@ -123,7 +123,7 @@ GlobalActionManager::openLink( const QString& title, const QString& artist, cons void -GlobalActionManager::shortenLink( const QUrl& url, const QVariant &callbackObj ) +GlobalActionManager::shortenLink( const QUrl& url, const QVariant& callbackObj ) { if ( QThread::currentThread() != thread() ) { @@ -148,7 +148,8 @@ GlobalActionManager::copyPlaylistToClipboard( const dynplaylist_ptr& playlist ) { QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == OnDemand ? "station" : "autoplaylist" ) ); - if( playlist->generator()->type() != "echonest" ) { + if ( playlist->generator()->type() != "echonest" ) + { tLog() << "Only echonest generators are supported"; return QString(); } @@ -157,19 +158,25 @@ GlobalActionManager::copyPlaylistToClipboard( const dynplaylist_ptr& playlist ) link.addQueryItem( "title", playlist->title() ); QList< dyncontrol_ptr > controls = playlist->generator()->controls(); - foreach( const dyncontrol_ptr& c, controls ) { - if( c->selectedType() == "Artist" ) { - if( c->match().toInt() == Echonest::DynamicPlaylist::ArtistType ) + foreach ( const dyncontrol_ptr& c, controls ) + { + if ( c->selectedType() == "Artist" ) + { + if ( c->match().toInt() == Echonest::DynamicPlaylist::ArtistType ) link.addQueryItem( "artist_limitto", c->input() ); else link.addQueryItem( "artist", c->input() ); - } else if( c->selectedType() == "Artist Description" ) { + } + else if ( c->selectedType() == "Artist Description" ) + { link.addQueryItem( "description", c->input() ); - } else { + } + else + { QString name = c->selectedType().toLower().replace( " ", "_" ); Echonest::DynamicPlaylist::PlaylistParam p = static_cast< Echonest::DynamicPlaylist::PlaylistParam >( c->match().toInt() ); // if it is a max, set that too - if( p == Echonest::DynamicPlaylist::MaxTempo || p == Echonest::DynamicPlaylist::MaxDuration || p == Echonest::DynamicPlaylist::MaxLoudness + if ( p == Echonest::DynamicPlaylist::MaxTempo || p == Echonest::DynamicPlaylist::MaxDuration || p == Echonest::DynamicPlaylist::MaxLoudness || p == Echonest::DynamicPlaylist::MaxDanceability || p == Echonest::DynamicPlaylist::MaxEnergy || p == Echonest::DynamicPlaylist::ArtistMaxFamiliarity || p == Echonest::DynamicPlaylist::ArtistMaxHotttnesss || p == Echonest::DynamicPlaylist::SongMaxHotttnesss || p == Echonest::DynamicPlaylist::ArtistMaxLatitude || p == Echonest::DynamicPlaylist::ArtistMaxLongitude ) @@ -204,7 +211,8 @@ GlobalActionManager::xspfCreated( const QByteArray& xspf ) QString filename = sender()->property( "filename" ).toString(); QFile f( filename ); - if( !f.open( QIODevice::WriteOnly ) ) { + if ( !f.open( QIODevice::WriteOnly ) ) + { qWarning() << "Failed to open file to save XSPF:" << filename; return; } @@ -220,7 +228,7 @@ void GlobalActionManager::copyToClipboard( const query_ptr& query ) { m_clipboardLongUrl = openLinkFromQuery( query ); - GlobalActionManager::instance()->shortenLink( m_clipboardLongUrl ); + shortenLink( m_clipboardLongUrl ); } @@ -228,10 +236,11 @@ bool GlobalActionManager::parseTomahawkLink( const QString& urlIn ) { QString url = urlIn; - if( urlIn.startsWith( "http://toma.hk" ) ) + if ( urlIn.startsWith( "http://toma.hk" ) ) url.replace( "http://toma.hk/", "tomahawk://" ); - if( url.contains( "tomahawk://" ) ) { + if ( url.contains( "tomahawk://" ) ) + { QString cmd = url.mid( 11 ); cmd.replace( "%2B", "%20" ); tLog() << "Parsing tomahawk link command" << cmd; @@ -240,8 +249,10 @@ GlobalActionManager::parseTomahawkLink( const QString& urlIn ) QUrl u = QUrl::fromEncoded( cmd.toUtf8() ); // for backwards compatibility - if( cmdType == "load" ) { - if( u.hasQueryItem( "xspf" ) ) { + if ( cmdType == "load" ) + { + if ( u.hasQueryItem( "xspf" ) ) + { QUrl xspf = QUrl::fromUserInput( u.queryItemValue( "xspf" ) ); XSPFLoader* l = new XSPFLoader( true, this ); tDebug() << "Loading spiff:" << xspf.toString(); @@ -249,7 +260,9 @@ GlobalActionManager::parseTomahawkLink( const QString& urlIn ) connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); return true; - } else if( u.hasQueryItem( "jspf" ) ) { + } + else if ( u.hasQueryItem( "jspf" ) ) + { QUrl jspf = QUrl::fromUserInput( u.queryItemValue( "jspf" ) ); JSPFLoader* l = new JSPFLoader( true, this ); @@ -261,31 +274,54 @@ GlobalActionManager::parseTomahawkLink( const QString& urlIn ) } } - if( cmdType == "playlist" ) { + if ( cmdType == "playlist" ) + { return handlePlaylistCommand( u ); - } else if( cmdType == "collection" ) { + } + else if ( cmdType == "collection" ) + { return handleCollectionCommand( u ); - } else if( cmdType == "queue" ) { + } + else if ( cmdType == "queue" ) + { return handleQueueCommand( u ); - } else if( cmdType == "station" ) { + } + else if ( cmdType == "station" ) + { return handleStationCommand( u ); - } else if( cmdType == "autoplaylist" ) { + } + else if ( cmdType == "autoplaylist" ) + { return handleAutoPlaylistCommand( u ); - } else if( cmdType == "search" ) { + } + else if ( cmdType == "search" ) + { return handleSearchCommand( u ); - } else if( cmdType == "play" ) { + } + else if ( cmdType == "play" ) + { return handlePlayCommand( u ); - } else if( cmdType == "bookmark" ) { + } + else if ( cmdType == "bookmark" ) + { return handlePlayCommand( u ); - } else if( cmdType == "open" ) { + } + else if ( cmdType == "open" ) + { return handleOpenCommand( u ); - } else if( cmdType == "view" ) { + } + else if ( cmdType == "view" ) + { return handleViewCommand( u ); - } else { + } + else + { tLog() << "Tomahawk link not supported, command not known!" << cmdType << u.path(); return false; } - } else { + } + else + { tLog() << "Not a tomahawk:// link!"; return false; } @@ -296,13 +332,16 @@ bool GlobalActionManager::handlePlaylistCommand( const QUrl& url ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific playlist command:" << url.toString(); return false; } - if( parts[ 0 ] == "import" ) { - if( !url.hasQueryItem( "xspf" ) ) { + if ( parts[ 0 ] == "import" ) + { + if ( !url.hasQueryItem( "xspf" ) ) + { tDebug() << "No xspf to load..."; return false; } @@ -313,15 +352,21 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url ) l->load( xspf ); connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), this, SLOT( playlistCreatedToShow( Tomahawk::playlist_ptr) ) ); - } else if( parts [ 0 ] == "new" ) { - if( !url.hasQueryItem( "title" ) ) { + } + else if ( parts [ 0 ] == "new" ) + { + if ( !url.hasQueryItem( "title" ) ) + { tLog() << "New playlist command needs a title..."; return false; } playlist_ptr pl = Playlist::create( SourceList::instance()->getLocal(), uuid(), url.queryItemValue( "title" ), QString(), QString(), false ); ViewManager::instance()->show( pl ); - } else if( parts[ 0 ] == "add" ) { - if( !url.hasQueryItem( "playlistid" ) || !url.hasQueryItem( "title" ) || !url.hasQueryItem( "artist" ) ) { + } + else if ( parts[ 0 ] == "add" ) + { + if ( !url.hasQueryItem( "playlistid" ) || !url.hasQueryItem( "title" ) || !url.hasQueryItem( "artist" ) ) + { tLog() << "Add to playlist command needs playlistid, track, and artist..." << url.toString(); return false; } @@ -353,12 +398,14 @@ bool GlobalActionManager::handleCollectionCommand( const QUrl& url ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific collection command:" << url.toString(); return false; } - if( parts[ 0 ] == "add" ) { + if ( parts[ 0 ] == "add" ) + { // TODO implement } @@ -370,7 +417,8 @@ bool GlobalActionManager::handleOpenCommand(const QUrl& url) { QStringList parts = url.path().split( "/" ).mid( 1 ); - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific type to open:" << url.toString(); return false; } @@ -385,7 +433,8 @@ GlobalActionManager::handleOpenTrack ( const query_ptr& q ) ViewManager::instance()->queue()->model()->append( q ); ViewManager::instance()->showQueue(); - if( !AudioEngine::instance()->isPlaying() && !AudioEngine::instance()->isPaused() ) { + if ( !AudioEngine::instance()->isPlaying() && !AudioEngine::instance()->isPaused() ) + { connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) ); m_waitingToPlay = q; } @@ -403,14 +452,18 @@ bool GlobalActionManager::handleQueueCommand( const QUrl& url ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific queue command:" << url.toString(); return false; } - if( parts[ 0 ] == "add" ) { + if ( parts[ 0 ] == "add" ) + { doQueueAdd( parts.mid( 1 ), url.queryItems() ); - } else { + } + else + { tLog() << "Only queue/add/track is support at the moment, got:" << parts; return false; } @@ -422,46 +475,55 @@ GlobalActionManager::handleQueueCommand( const QUrl& url ) bool GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems ) { - if( parts.size() && parts[ 0 ] == "track" ) { + if ( parts.size() && parts[ 0 ] == "track" ) + { - if( queueSpotify( parts, queryItems ) ) + if ( queueSpotify( parts, queryItems ) ) return true; - else if( queueRdio( parts, queryItems ) ) + else if ( queueRdio( parts, queryItems ) ) return true; QPair< QString, QString > pair; QString title, artist, album, urlStr; - foreach( pair, queryItems ) { + foreach ( pair, queryItems ) { pair.second = pair.second.replace( "+", " " ); // QUrl::queryItems doesn't decode + to a space :( - if( pair.first == "title" ) + if ( pair.first == "title" ) title = pair.second; - else if( pair.first == "artist" ) + else if ( pair.first == "artist" ) artist = pair.second; - else if( pair.first == "album" ) + else if ( pair.first == "album" ) album = pair.second; - else if( pair.first == "url" ) + else if ( pair.first == "url" ) urlStr = pair.second; } - if( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) { // an individual; query to add to queue + if ( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) + { + // an individual; query to add to queue query_ptr q = Query::get( artist, title, album, uuid(), false ); - if( !urlStr.isEmpty() ) + if ( !urlStr.isEmpty() ) q->setResultHint( urlStr ); Pipeline::instance()->resolve( q, true ); handleOpenTrack( q ); return true; - - } else { // a list of urls to add to the queue - foreach( pair, queryItems ) { - if( pair.first != "url" ) + } + else + { // a list of urls to add to the queue + foreach ( pair, queryItems ) + { + if ( pair.first != "url" ) continue; QUrl track = QUrl::fromUserInput( pair.second ); //FIXME: isLocalFile is Qt 4.8 - if( track.toString().startsWith( "file://" ) ) { // it's local, so we see if it's in the DB and load it if so + if ( track.toString().startsWith( "file://" ) ) + { + // it's local, so we see if it's in the DB and load it if so // TODO - } else { // give it a web result hint + } + else + { // give it a web result hint QFileInfo info( track.path() ); query_ptr q = Query::get( QString(), info.baseName(), QString(), uuid(), false ); q->setResultHint( track.toString() ); @@ -485,14 +547,15 @@ GlobalActionManager::queueSpotify( const QStringList& , const QList< QPair< QStr QString url; QPair< QString, QString > pair; - foreach( pair, queryItems ) { - if( pair.first == "spotifyURL" ) + foreach ( pair, queryItems ) + { + if ( pair.first == "spotifyURL" ) url = pair.second; - else if( pair.first == "spotifyURI" ) + else if ( pair.first == "spotifyURI" ) url = pair.second; } - if( url.isEmpty() ) + if ( url.isEmpty() ) return false; openSpotifyLink( url ); @@ -507,14 +570,15 @@ GlobalActionManager::queueRdio( const QStringList& , const QList< QPair< QString QString url; QPair< QString, QString > pair; - foreach( pair, queryItems ) { - if( pair.first == "rdioURL" ) + foreach ( pair, queryItems ) + { + if ( pair.first == "rdioURL" ) url = pair.second; - else if( pair.first == "rdioURI" ) + else if ( pair.first == "rdioURI" ) url = pair.second; } - if( url.isEmpty() ) + if ( url.isEmpty() ) return false; openRdioLink( url ); @@ -533,16 +597,16 @@ GlobalActionManager::handleSearchCommand( const QUrl& url ) else { QStringList query; - if( url.hasQueryItem( "artist" ) ) + if ( url.hasQueryItem( "artist" ) ) query << url.queryItemValue( "artist" ); - if( url.hasQueryItem( "album" ) ) + if ( url.hasQueryItem( "album" ) ) query << url.queryItemValue( "album" ); - if( url.hasQueryItem( "title" ) ) + if ( url.hasQueryItem( "title" ) ) query << url.queryItemValue( "title" ); queryStr = query.join( " " ); } - if( queryStr.trimmed().isEmpty() ) + if ( queryStr.trimmed().isEmpty() ) return false; ViewManager::instance()->show( new SearchWidget( queryStr.trimmed() ) ); @@ -554,7 +618,7 @@ bool GlobalActionManager::handleViewCommand( const QUrl& url ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) { tLog() << "No specific view command:" << url.toString(); return false; } @@ -604,135 +668,177 @@ Tomahawk::dynplaylist_ptr GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific station command:" << url.toString(); return Tomahawk::dynplaylist_ptr(); } - if( parts[ 0 ] == "create" ) { - if( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) { + if ( parts[ 0 ] == "create" ) + { + if ( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) + { tLog() << "Station create command needs title and type..." << url.toString(); return Tomahawk::dynplaylist_ptr(); } QString title = url.queryItemValue( "title" ); QString type = url.queryItemValue( "type" ); GeneratorMode m = Static; - if( station ) + if ( station ) m = OnDemand; dynplaylist_ptr pl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), title, QString(), QString(), m, false, type ); pl->setMode( m ); QList< dyncontrol_ptr > controls; QPair< QString, QString > param; - foreach( param, url.queryItems() ) { - if( param.first == "artist" ) { + foreach ( param, url.queryItems() ) + { + if ( param.first == "artist" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Artist" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistRadioType ) ); controls << c; - } else if( param.first == "artist_limitto" ) { + } + else if ( param.first == "artist_limitto" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Artist" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistType ) ); controls << c; - } else if( param.first == "description" ) { + } + else if ( param.first == "description" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Artist Description" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistDescriptionType ) ); controls << c; - } else if( param.first == "variety" ) { + } + else if ( param.first == "variety" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Variety" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Variety ) ); controls << c; - } else if( param.first.startsWith( "tempo" ) ) { + } + else if ( param.first.startsWith( "tempo" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Tempo" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinTempo + extra ) ); controls << c; - } else if( param.first.startsWith( "duration" ) ) { + } + else if ( param.first.startsWith( "duration" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Duration" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinDuration + extra ) ); controls << c; - } else if( param.first.startsWith( "loudness" ) ) { + } + else if ( param.first.startsWith( "loudness" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Loudness" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinLoudness + extra ) ); controls << c; - } else if( param.first.startsWith( "danceability" ) ) { + } + else if ( param.first.startsWith( "danceability" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Danceability" ); int extra = param.first.endsWith( "_max" ) ? 1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinDanceability + extra ) ); controls << c; - } else if( param.first.startsWith( "energy" ) ) { + } + else if ( param.first.startsWith( "energy" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Energy" ); int extra = param.first.endsWith( "_max" ) ? 1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::MinEnergy + extra ) ); controls << c; - } else if( param.first.startsWith( "artist_familiarity" ) ) { + } + else if ( param.first.startsWith( "artist_familiarity" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Artist Familiarity" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinFamiliarity + extra ) ); controls << c; - } else if( param.first.startsWith( "artist_hotttnesss" ) ) { + } + else if ( param.first.startsWith( "artist_hotttnesss" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Artist Hotttnesss" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinHotttnesss + extra ) ); controls << c; - } else if( param.first.startsWith( "song_hotttnesss" ) ) { + } + else if ( param.first.startsWith( "song_hotttnesss" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Song Hotttnesss" ); int extra = param.first.endsWith( "_max" ) ? -1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::SongMinHotttnesss + extra ) ); controls << c; - } else if( param.first.startsWith( "longitude" ) ) { + } + else if ( param.first.startsWith( "longitude" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Longitude" ); int extra = param.first.endsWith( "_max" ) ? 1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinLongitude + extra ) ); controls << c; - } else if( param.first.startsWith( "latitude" ) ) { + } + else if ( param.first.startsWith( "latitude" ) ) + { dyncontrol_ptr c = pl->generator()->createControl( "Latitude" ); int extra = param.first.endsWith( "_max" ) ? 1 : 0; c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::ArtistMinLatitude + extra ) ); controls << c; - } else if( param.first == "key" ) { + } + else if ( param.first == "key" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Key" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Key ) ); controls << c; - } else if( param.first == "mode" ) { + } + else if ( param.first == "mode" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Mode" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Mode ) ); controls << c; - } else if( param.first == "mood" ) { + } + else if ( param.first == "mood" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Mood" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Mood ) ); controls << c; - } else if( param.first == "style" ) { + } + else if ( param.first == "style" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Style" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::Style ) ); controls << c; - } else if( param.first == "song" ) { + } + else if ( param.first == "song" ) + { dyncontrol_ptr c = pl->generator()->createControl( "Song" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::SongRadioType ) ); controls << c; } } - if( m == OnDemand ) + + if ( m == OnDemand ) pl->createNewRevision( uuid(), pl->currentrevision(), type, controls ); else pl->createNewRevision( uuid(), pl->currentrevision(), type, controls, pl->entries() ); @@ -756,31 +862,35 @@ bool GlobalActionManager::handlePlayCommand( const QUrl& url ) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific play command:" << url.toString(); return false; } - if( parts[ 0 ] == "track" ) { - if( playSpotify( url ) ) + if ( parts[ 0 ] == "track" ) + { + if ( playSpotify( url ) ) return true; - else if( playRdio( url ) ) + else if ( playRdio( url ) ) return true; QPair< QString, QString > pair; QString title, artist, album, urlStr; - foreach( pair, url.queryItems() ) { - if( pair.first == "title" ) + foreach ( pair, url.queryItems() ) + { + if ( pair.first == "title" ) title = pair.second; - else if( pair.first == "artist" ) + else if ( pair.first == "artist" ) artist = pair.second; - else if( pair.first == "album" ) + else if ( pair.first == "album" ) album = pair.second; - else if( pair.first == "url" ) + else if ( pair.first == "url" ) urlStr = pair.second; } + query_ptr q = Query::get( artist, title, album ); - if( !urlStr.isEmpty() ) + if ( !urlStr.isEmpty() ) q->setResultHint( urlStr ); playNow( q ); @@ -794,7 +904,7 @@ GlobalActionManager::handlePlayCommand( const QUrl& url ) bool GlobalActionManager::playSpotify( const QUrl& url ) { - if( !url.hasQueryItem( "spotifyURI" ) && !url.hasQueryItem( "spotifyURL" ) ) + if ( !url.hasQueryItem( "spotifyURI" ) && !url.hasQueryItem( "spotifyURL" ) ) return false; QString spotifyUrl = url.hasQueryItem( "spotifyURI" ) ? url.queryItemValue( "spotifyURI" ) : url.queryItemValue( "spotifyURL" ); @@ -829,10 +939,9 @@ GlobalActionManager::playOrQueueNow( const query_ptr& q ) bool GlobalActionManager::playRdio( const QUrl& url ) { - if( !url.hasQueryItem( "rdioURI" ) && !url.hasQueryItem( "rdioURL" ) ) + if ( !url.hasQueryItem( "rdioURI" ) && !url.hasQueryItem( "rdioURL" ) ) return false; - QString rdioUrl = url.hasQueryItem( "rdioURI" ) ? url.queryItemValue( "spotifyURI" ) : url.queryItemValue( "rdioURL" ); RdioParser* p = new RdioParser( this ); p->parse( rdioUrl ); @@ -845,33 +954,36 @@ GlobalActionManager::playRdio( const QUrl& url ) bool GlobalActionManager::handleBookmarkCommand(const QUrl& url) { QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command - if( parts.isEmpty() ) { + if ( parts.isEmpty() ) + { tLog() << "No specific bookmark command:" << url.toString(); return false; } - if( parts[ 0 ] == "track" ) { + if ( parts[ 0 ] == "track" ) + { QPair< QString, QString > pair; QString title, artist, album, urlStr; - foreach( pair, url.queryItems() ) { - if( pair.first == "title" ) + foreach ( pair, url.queryItems() ) + { + if ( pair.first == "title" ) title = pair.second; - else if( pair.first == "artist" ) + else if ( pair.first == "artist" ) artist = pair.second; - else if( pair.first == "album" ) + else if ( pair.first == "album" ) album = pair.second; - else if( pair.first == "url" ) + else if ( pair.first == "url" ) urlStr = pair.second; } query_ptr q = Query::get( artist, title, album ); - if( !urlStr.isEmpty() ) + if ( !urlStr.isEmpty() ) q->setResultHint( urlStr ); Pipeline::instance()->resolve( q, true ); // now we add it to the special "bookmarks" playlist, creating it if it doesn't exist. if nothing is playing, start playing the track QSharedPointer< LocalCollection > col = SourceList::instance()->getLocal()->collection().dynamicCast< LocalCollection >(); playlist_ptr bookmarkpl = col->bookmarksPlaylist(); - if( bookmarkpl.isNull() ) { // create it and do the deed then + if ( bookmarkpl.isNull() ) { // create it and do the deed then m_waitingToBookmark = q; col->createBookmarksPlaylist(); connect( col.data(), SIGNAL( bookmarkPlaylistCreated( Tomahawk::playlist_ptr ) ), this, SLOT( bookmarkPlaylistCreated( Tomahawk::playlist_ptr ) ), Qt::UniqueConnection ); @@ -894,7 +1006,7 @@ GlobalActionManager::shortenLinkRequestFinished() bool error = false; // NOTE: this should never happen - if( !reply ) + if ( !reply ) { emit shortLinkReady( QUrl( "" ), QUrl( "" ), QVariantMap() ); return; @@ -908,14 +1020,14 @@ GlobalActionManager::shortenLinkRequestFinished() QVariant urlVariant = reply->attribute( QNetworkRequest::RedirectionTargetAttribute ); // NOTE: this should never happen - if( urlVariant.isNull() || !urlVariant.isValid() ) + if ( urlVariant.isNull() || !urlVariant.isValid() ) error = true; QUrl longUrl = reply->request().url(); QUrl shortUrl = urlVariant.toUrl(); // NOTE: this should never happen - if( !shortUrl.isValid() ) + if ( !shortUrl.isValid() ) error = true; // Success! Here is the short link @@ -950,7 +1062,7 @@ GlobalActionManager::shortenLinkRequestError( QNetworkReply::NetworkError error QNetworkReply *reply = qobject_cast( sender() ); // NOTE: this should never happen - if( !reply ) + if ( !reply ) { emit shortLinkReady( QUrl( "" ), QUrl( "" ), QVariantMap() ); return; @@ -999,7 +1111,7 @@ GlobalActionManager::doBookmark( const playlist_ptr& pl, const query_ptr& q ) void GlobalActionManager::showPlaylist() { - if( m_toShow.isNull() ) + if ( m_toShow.isNull() ) return; ViewManager::instance()->show( m_toShow ); diff --git a/src/libtomahawk/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index 5b0a72017..6a3f1b304 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -67,7 +67,7 @@ public slots: void handlePlayTrack( const Tomahawk::query_ptr& qry ); signals: - void shortLinkReady( QUrl longUrl, QUrl shortUrl, QVariant callbackObj ) const; + void shortLinkReady( const QUrl& longUrl, const QUrl& shortUrl, const QVariant& callbackObj ); private slots: void shortenLinkRequestFinished(); @@ -83,6 +83,7 @@ private slots: void playlistCreatedToShow( const Tomahawk::playlist_ptr& pl ); void playlistReadyToShow(); + private: explicit GlobalActionManager( QObject* parent = 0 ); void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q );