diff --git a/CMakeLists.txt b/CMakeLists.txt index e386260c8..d219e04f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ IF( ${TOMAHAWK_VERSION_TWEAK} GREATER 0) SET( TOMAHAWK_VERSION ${TOMAHAWK_VERSION}.${TOMAHAWK_VERSION_TWEAK} ) ENDIF() IF( TOMAHAWK_VERSION_RC ) - SET( CMake_VERSION ${TOMAHAWK_VERSION}-rc${TOMAHAWK_VERSION_RC} ) + SET( CMake_VERSION ${TOMAHAWK_VERSION}rc${TOMAHAWK_VERSION_RC} ) ENDIF() IF( CMAKE_VERSION_SOURCE ) SET( TOMAHAWK_VERSION ${TOMAHAWK_VERSION}-${CMAKE_VERSION_SOURCE} ) diff --git a/ChangeLog b/ChangeLog index b8bdc5633..162da795d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,11 +1,56 @@ Version 0.2.0: + * Added support for "playing" Spotify track URLs (does metadata lookup then resolves to available sources) + * Added support for JSPF playlists (XSPF via JSON) + * Removed big "Welcome to Tomahawk" info bar from welcome screen + * Clean up some bugs around deleted playlists and stations + * Rename filter box in collection track view to "filter" (from "search") + * Scroll per pixel to making scrolling feel more natural + * Filter out duplicate tracks in Collection tree view * Spotify resolver now honors SOCKS5 proxy settings. + * On OS X don't quit on window close to conform to best practices + * Made "show offline sources" option in menu be a toggle instead of two different entries + * Properly decode escaped characters coming from HTTP API + * Fixes and updates to German translation + * Handle/display resolver file paths becoming invalid in Preferences + * Fixed track skipping-on-pause bug when using Gstreamer backend * Fixed a few crashes that could occur when fetching data from Last.fm. * Made Twitter dialog more readable/understandable. - * Twitter checks for updates less often now, saving user API calls from - running out when using multiple clients. + * Added Artist pages (and links to them from now playing metadata and from collection track list views) + with artist bio snippet, related artists, top tracks and view of all tracks/albums from that + artist in user's Super Collection + * Fixed streaming from https:// links + * Desktop notifications for now playing (Linux only) + * Implement new javascript resolver API and ability to present configuration options for resolvers + * Prettified header labels + * Show when a track was played in the Recently Played tracks history + * Added context menus for Artists and Albums + * Added some tool tips + * Fixed playlist bugs that would cause track to stop playing when dropping/moving items in the same playlist + * New home screen (aka "Recently Played" under Super Collection) + * Set volume to 75% on startup + * Lots of new icons and design tweaks + * Added "listen along" feature (and ability to "catch up") + * Optimize resolver pipeline + * Added "love" feature - also "loves" track on Last.fm (if last.fm account is available/set) + * Twitter checks for updates less often now, saving user API calls from running out when using multiple clients. + * Added score column and score bars to indicate resolver match certainty + * Fix a bunch of playlist deletion bugs + * Created "New Additions" page for each source + * Improve tomahawk:// link handling. Added support for http://toma.hk/ links so browser/link-shortener friendly + * Fix sorting by file size + * Improve the way Adium status is updated with now-playing information - add preference to enable/disable + * Fix so stations can be created only given mood and/or style parameters + * Re-resolve tracks when a resolver is added/removed/enabled/disabled + * Don't require @foo.com domains for jabber and google + * Added global search function that searches all available sources (super collection and all enabled resolvers) + * Cleaned up Preference Window and styling * Collection scanner now can now run automatically, watching files and directories for changes. On by default. + * Added Pipeline view window + * Allow seeking in songs - if possible + * Added ability to make a copy of a sources' playlists and add to your own playlists + * Added MediaKey support on OS X + * Shuffle and Repeat settings are stored on a playlist by playlist basis Version 0.1.0: * Fixed stations so they resolve against all available sources instead of diff --git a/data/images/info.png b/data/images/info.png index 8119348e0..9af61a45f 100644 Binary files a/data/images/info.png and b/data/images/info.png differ diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index 637035197..eabed1b87 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -20,9 +20,12 @@ #include "ui_audiocontrols.h" #include +#include +#include #include "audio/audioengine.h" #include "viewmanager.h" +#include "playlist/playlistview.h" #include "database/database.h" #include "database/databasecommand_socialaction.h" @@ -31,6 +34,7 @@ #include "utils/imagebutton.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include using namespace Tomahawk; @@ -44,6 +48,7 @@ AudioControls::AudioControls( QWidget* parent ) , m_shuffled( false ) { ui->setupUi( this ); + setAcceptDrops( true ); ui->buttonAreaLayout->setSpacing( 2 ); @@ -503,6 +508,51 @@ AudioControls::onTrackClicked() ViewManager::instance()->showCurrentTrack(); } +void +AudioControls::dragEnterEvent( QDragEnterEvent* e ) +{ + if ( GlobalActionManager::instance()->acceptsMimeData( e->mimeData() ) ) + e->acceptProposedAction(); +} + +void +AudioControls::dragMoveEvent( QDragMoveEvent* e ) +{ +// if ( GlobalActionManager::instance()->acceptsMimeData( e->mimeData() ) ) +// e->acceptProposedAction(); +} + +void +AudioControls::dropEvent( QDropEvent* e ) +{ + tDebug() << "AudioControls got drop:" << e->mimeData()->formats(); + if ( GlobalActionManager::instance()->acceptsMimeData( e->mimeData() ) ) + { + connect( GlobalActionManager::instance(), SIGNAL( tracks( QList ) ), this, SLOT( droppedTracks( QList ) ) ); + GlobalActionManager::instance()->tracksFromMimeData( e->mimeData() ); + + e->accept(); + } +} + +void +AudioControls::droppedTracks( QList< query_ptr > tracks ) +{ + disconnect( GlobalActionManager::instance(), SIGNAL( tracks( QList ) ), this, SLOT( droppedTracks( QList ) ) ); + + if ( !tracks.isEmpty() ) + { + // queue and play the first if nothign is playing + GlobalActionManager::instance()->handleOpenTrack( tracks.first() ); + + // just queue the rest + for ( int i = 1; i < tracks.size(); i++ ) + { + ViewManager::instance()->queue()->model()->append( tracks[ i ] ); + } + } +} + void AudioControls::onLoveButtonClicked( bool checked ) diff --git a/src/audiocontrols.h b/src/audiocontrols.h index b887d51f1..77fd766c2 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -25,6 +25,9 @@ #include "playlistinterface.h" #include "infosystem/infosystem.h" +class QDropEvent; +class QDragEnterEvent; +class QDragMoveEvent; namespace Ui { class AudioControls; @@ -48,6 +51,9 @@ public slots: protected: void changeEvent( QEvent* e ); + void dragEnterEvent ( QDragEnterEvent* ); + void dragMoveEvent ( QDragMoveEvent* ); + void dropEvent ( QDropEvent* ); private slots: void onPlaybackStarted( const Tomahawk::result_ptr& result ); @@ -70,6 +76,7 @@ private slots: void infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ); void infoSystemFinished( QString target ); + void droppedTracks ( QList ); private: Ui::AudioControls *ui; diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 1bc53f6a0..1b5a435bb 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -171,6 +171,8 @@ set( libSources utils/xspfloader.cpp utils/xspfgenerator.cpp utils/jspfloader.cpp + utils/spotifyparser.cpp + utils/shortenedlinkparser.cpp widgets/newplaylistwidget.cpp widgets/searchwidget.cpp @@ -347,6 +349,8 @@ set( libHeaders utils/xspfloader.h utils/xspfgenerator.h utils/jspfloader.h + utils/spotifyparser.h + utils/shortenedlinkparser.h widgets/newplaylistwidget.h widgets/searchwidget.h @@ -439,8 +443,13 @@ IF( APPLE ) utils/tomahawkutils_mac.mm ) SET( libHeaders ${libHeaders} +<<<<<<< HEAD infosystem/infoplugins/mac/adium.h infosystem/infoplugins/mac/adiumplugin.h ) +======= + infosystem/infoplugins/mac/adiumplugin.h + widgets/maclineedit.h ) +>>>>>>> master SET( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index a1e0e6821..49a55ca14 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -384,7 +384,14 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) m_mediaObject->setCurrentSource( furl ); } else - m_mediaObject->setCurrentSource( m_currentTrack->url() ); + { + QString furl = m_currentTrack->url(); +#ifdef Q_OS_WIN32 + if ( furl.startsWith( "file://" ) ) + furl = furl.right( furl.length() - 7 ); +#endif + m_mediaObject->setCurrentSource( furl ); + } m_mediaObject->currentSource().setAutoDelete( true ); m_isPlayingHttp = true; diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index b83238c98..3b7477168 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -20,8 +20,12 @@ #include #include +#include #include +#include +#include +#include "artist.h" #include "album.h" #include "sourcelist.h" #include "pipeline.h" @@ -39,12 +43,14 @@ #include "utils/logger.h" #include "utils/tomahawkutils.h" -#include -#include #include "utils/jspfloader.h" +#include "utils/spotifyparser.h" +#include "utils/shortenedlinkparser.h" GlobalActionManager* GlobalActionManager::s_instance = 0; +using namespace Tomahawk; + GlobalActionManager* GlobalActionManager::instance() { @@ -54,18 +60,17 @@ GlobalActionManager::instance() return s_instance; } - GlobalActionManager::GlobalActionManager( QObject* parent ) : QObject( parent ) { - + m_mimeTypes << "application/tomahawk.query.list" << "application/tomahawk.plentry.list" << "application/tomahawk.result.list" << "text/plain"; } GlobalActionManager::~GlobalActionManager() {} QUrl -GlobalActionManager::openLinkFromQuery( const Tomahawk::query_ptr& query ) const +GlobalActionManager::openLinkFromQuery( const query_ptr& query ) const { QString title, artist, album; @@ -100,9 +105,9 @@ GlobalActionManager::openLink( const QString& title, const QString& artist, cons } QString -GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist ) +GlobalActionManager::copyPlaylistToClipboard( const dynplaylist_ptr& playlist ) { - QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == Tomahawk::OnDemand ? "station" : "autoplaylist" ) ); + QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == OnDemand ? "station" : "autoplaylist" ) ); if( playlist->generator()->type() != "echonest" ) { tLog() << "Only echonest generators are supported"; @@ -112,8 +117,8 @@ GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& p link.addEncodedQueryItem( "type", "echonest" ); link.addQueryItem( "title", playlist->title() ); - QList< Tomahawk::dyncontrol_ptr > controls = playlist->generator()->controls(); - foreach( const Tomahawk::dyncontrol_ptr& c, controls ) { + QList< dyncontrol_ptr > controls = playlist->generator()->controls(); + foreach( const dyncontrol_ptr& c, controls ) { if( c->selectedType() == "Artist" ) { if( c->match().toInt() == Echonest::DynamicPlaylist::ArtistType ) link.addQueryItem( "artist_limitto", c->input() ); @@ -142,7 +147,7 @@ GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& p } void -GlobalActionManager::savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename ) +GlobalActionManager::savePlaylistToFile( const playlist_ptr& playlist, const QString& filename ) { XSPFGenerator* g = new XSPFGenerator( playlist, this ); g->setProperty( "filename", filename ); @@ -169,7 +174,7 @@ GlobalActionManager::xspfCreated( const QByteArray& xspf ) void -GlobalActionManager::copyToClipboard( const Tomahawk::query_ptr& query ) const +GlobalActionManager::copyToClipboard( const query_ptr& query ) const { QClipboard* cb = QApplication::clipboard(); cb->setText( openLinkFromQuery( query ).toEncoded() ); @@ -199,7 +204,8 @@ GlobalActionManager::parseTomahawkLink( const QString& url ) return true; } else if( u.hasQueryItem( "jspf" ) ) { QUrl jspf = QUrl::fromUserInput( u.queryItemValue( "jspf" ) ); - Tomahawk::JSPFLoader* l = new Tomahawk::JSPFLoader( true, this ); + JSPFLoader* l = new JSPFLoader( true, this ); + tDebug() << "Loading jspiff:" << jspf.toString(); l->load( jspf ); connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); @@ -262,7 +268,7 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url ) tLog() << "New playlist command needs a title..."; return false; } - Tomahawk::playlist_ptr pl = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), url.queryItemValue( "title" ), QString(), QString(), 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" ) ) { @@ -305,7 +311,7 @@ GlobalActionManager::handleOpenCommand(const QUrl& url) } void -GlobalActionManager::handleOpenTrack ( const Tomahawk::query_ptr& q ) +GlobalActionManager::handleOpenTrack ( const query_ptr& q ) { ViewManager::instance()->queue()->model()->append( q ); ViewManager::instance()->showQueue(); @@ -355,10 +361,10 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q } if( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) { // an individual; query to add to queue - Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), false ); + query_ptr q = Query::get( artist, title, album, uuid(), false ); if( !urlStr.isEmpty() ) q->setResultHint( urlStr ); - Tomahawk::Pipeline::instance()->resolve( q, true ); + Pipeline::instance()->resolve( q, true ); handleOpenTrack( q ); return true; @@ -373,10 +379,10 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q // TODO } else { // give it a web result hint QFileInfo info( track.path() ); - Tomahawk::query_ptr q = Tomahawk::Query::get( QString(), info.baseName(), QString(), uuid(), false ); + query_ptr q = Query::get( QString(), info.baseName(), QString(), uuid(), false ); q->setResultHint( track.toString() ); - Tomahawk::Pipeline::instance()->resolve( q, true ); + Pipeline::instance()->resolve( q, true ); ViewManager::instance()->queue()->model()->append( q ); ViewManager::instance()->showQueue(); @@ -415,139 +421,139 @@ GlobalActionManager::handleAutoPlaylistCommand( const QUrl& url ) return !loadDynamicPlaylist( url, false ).isNull(); } -Tomahawk::dynplaylist_ptr +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() ) { tLog() << "No specific station command:" << url.toString(); - return Tomahawk::dynplaylist_ptr(); + return dynplaylist_ptr(); } 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(); + return dynplaylist_ptr(); } QString title = url.queryItemValue( "title" ); QString type = url.queryItemValue( "type" ); - Tomahawk::GeneratorMode m = Tomahawk::Static; + GeneratorMode m = Static; if( station ) - m = Tomahawk::OnDemand; + m = OnDemand; - Tomahawk::dynplaylist_ptr pl = Tomahawk::DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), title, QString(), QString(), m, false, type ); + dynplaylist_ptr pl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), title, QString(), QString(), m, false, type ); pl->setMode( m ); - QList< Tomahawk::dyncontrol_ptr > controls; + QList< dyncontrol_ptr > controls; QPair< QString, QString > param; foreach( param, url.queryItems() ) { if( param.first == "artist" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "Artist" ); + 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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "Artist 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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "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" ) { - Tomahawk::dyncontrol_ptr c = pl->generator()->createControl( "Song" ); + dyncontrol_ptr c = pl->generator()->createControl( "Song" ); c->setInput( param.second ); c->setMatch( QString::number( (int)Echonest::DynamicPlaylist::SongRadioType ) ); controls << c; } } - if( m == Tomahawk::OnDemand ) + if( m == OnDemand ) pl->createNewRevision( uuid(), pl->currentrevision(), type, controls ); else pl->createNewRevision( uuid(), pl->currentrevision(), type, controls, pl->entries() ); @@ -555,7 +561,7 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station ) return pl; } - return Tomahawk::dynplaylist_ptr(); + return dynplaylist_ptr(); } @@ -587,10 +593,10 @@ GlobalActionManager::handlePlayCommand( const QUrl& url ) else if( pair.first == "url" ) urlStr = pair.second; } - Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album ); + query_ptr q = Query::get( artist, title, album ); if( !urlStr.isEmpty() ) q->setResultHint( urlStr ); - Tomahawk::Pipeline::instance()->resolve( q, true ); + Pipeline::instance()->resolve( q, true ); m_waitingToPlay = q; connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) ); @@ -622,14 +628,14 @@ bool GlobalActionManager::handleBookmarkCommand(const QUrl& url) else if( pair.first == "url" ) urlStr = pair.second; } - Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album ); + query_ptr q = Query::get( artist, title, album ); if( !urlStr.isEmpty() ) q->setResultHint( urlStr ); - Tomahawk::Pipeline::instance()->resolve( q, true ); + 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 >(); - Tomahawk::playlist_ptr bookmarkpl = col->bookmarksPlaylist(); + playlist_ptr bookmarkpl = col->bookmarksPlaylist(); if( bookmarkpl.isNull() ) { // create it and do the deed then m_waitingToBookmark = q; col->createBookmarksPlaylist(); @@ -647,16 +653,16 @@ bool GlobalActionManager::handleBookmarkCommand(const QUrl& url) void -GlobalActionManager::bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl ) +GlobalActionManager::bookmarkPlaylistCreated( const playlist_ptr& pl ) { Q_ASSERT( !m_waitingToBookmark.isNull() ); doBookmark( pl, m_waitingToBookmark ); } void -GlobalActionManager::doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q ) +GlobalActionManager::doBookmark( const playlist_ptr& pl, const query_ptr& q ) { - Tomahawk::plentry_ptr e( new Tomahawk::PlaylistEntry ); + plentry_ptr e( new PlaylistEntry ); e->setGuid( uuid() ); if ( q->results().count() ) @@ -668,7 +674,7 @@ GlobalActionManager::doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahaw e->setAnnotation( "" ); // FIXME e->setQuery( q ); - pl->createNewRevision( uuid(), pl->currentrevision(), QList< Tomahawk::plentry_ptr >( pl->entries() ) << e ); + pl->createNewRevision( uuid(), pl->currentrevision(), QList< plentry_ptr >( pl->entries() ) << e ); connect( pl.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), this, SLOT( showPlaylist() ) ); m_toShow = pl; @@ -688,15 +694,17 @@ GlobalActionManager::showPlaylist() } void -GlobalActionManager::waitingForResolved( bool success ) +GlobalActionManager::waitingForResolved( bool /* success */ ) { - if( m_waitingToPlay.data() != sender() ) + if ( m_waitingToPlay.data() != sender() ) { m_waitingToPlay.clear(); return; } - if( !m_waitingToPlay.isNull() && m_waitingToPlay->playable() ) { // play it! + if ( !m_waitingToPlay.isNull() && m_waitingToPlay->playable() ) + { + // play it! // AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() ); AudioEngine::instance()->play(); @@ -710,82 +718,142 @@ GlobalActionManager::hostname() const return QString( "http://toma.hk" ); } -/// SPOTIFY URL HANDLING +/// QMIMEDATA HANDLING -bool -GlobalActionManager::parseSpotifyLink( const QString& link ) +QStringList +GlobalActionManager::mimeTypes() const { - if( !link.contains( "track" ) ) // we only support track links atm - return false; - - // we need Spotify URIs such as spotify:track:XXXXXX, so if we by chance get a http://open.spotify.com url, convert it - QString uri = link; - if( link.contains( "open.spotify.com" ) ) - { - QString hash = link; - hash.replace( "http://open.spotify.com/track/", "" ); - uri = QString( "spotify:track:%1" ).arg( hash ); - } - - tLog() << "Parsing Spotify Track URI:" << uri; - - QUrl url = QUrl( QString( "http://ws.spotify.com/lookup/1/.json?uri=%1" ).arg( uri ) ); - tDebug() << "Looking up..." << url.toString(); - - QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) ); - connect( reply, SIGNAL( finished() ), this, SLOT( spotifyTrackLookupFinished() ) ); - - return true; // all we know now + return m_mimeTypes; } +bool +GlobalActionManager::acceptsMimeData( const QMimeData* data, bool tracksOnly ) +{ + if ( data->hasFormat( "application/tomahawk.query.list" ) + || data->hasFormat( "application/tomahawk.plentry.list" ) + || data->hasFormat( "application/tomahawk.result.list" ) ) + { + return true; + } + + // crude check for spotify tracks + if ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "spotify" ) && + ( tracksOnly ? data->data( "text/plain" ).contains( "track" ) : true ) ) + return true; + + // We whitelist t.co and bit.ly (and j.mp) since they do some link checking. Often playable (e.g. spotify..) links hide behind them, + // so we do an extra level of lookup + if ( ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "bit.ly" ) ) || + ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "j.mp" ) ) || + ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "t.co" ) ) ) + return true; + + return false; +} + void -GlobalActionManager::spotifyTrackLookupFinished() +GlobalActionManager::tracksFromMimeData( const QMimeData* data ) { - QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() ); - Q_ASSERT( r ); - - if( r->error() == QNetworkReply::NoError ) + if ( data->hasFormat( "application/tomahawk.query.list" ) ) + emit tracks( tracksFromQueryList( data ) ); + else if ( data->hasFormat( "application/tomahawk.result.list" ) ) + emit tracks( tracksFromResultList( data ) ); + else if ( data->hasFormat( "text/plain" ) ) { - QJson::Parser p; - bool ok; - QVariantMap res = p.parse( r, &ok ).toMap(); - - if( !ok ) - { - tLog() << "Failed to parse json from Spotify track lookup:" << p.errorString() << "On line" << p.errorLine(); - return; - } else if( !res.contains( "track" ) ) - { - tLog() << "No 'track' item in the spotify track lookup result... not doing anything"; - return; - } - - // lets parse this baby - QVariantMap t = res.value( "track" ).toMap(); - - QString title, artist, album; - - title = t.value( "name", QString() ).toString(); - // TODO for now only take the first artist - if( t.contains( "artists" ) && t[ "artists" ].canConvert< QVariantList >() && t[ "artists" ].toList().size() > 0 ) - artist = t[ "artists" ].toList().first().toMap().value( "name", QString() ).toString(); - if( t.contains( "album" ) && t[ "album" ].canConvert< QVariantMap >() ) - album = t[ "album" ].toMap().value( "name", QString() ).toString(); - - if( title.isEmpty() && artist.isEmpty() ) // don't have enough... - { - tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album; - return; - } - - Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), true ); - handleOpenTrack( q ); - - } else - { - tLog() << "Error in network request to Spotify for track decoding:" << r->errorString(); + QString plainData = QString::fromUtf8( data->data( "text/plain" ).constData() ); + tDebug() << "Got text/plain mime data:" << data->data( "text/plain" ) << "decoded to:" << plainData; + handleTrackUrls ( plainData ); } } +void +GlobalActionManager::handleTrackUrls( const QString& urls ) +{ + if ( urls.contains( "open.spotify.com/track") || + urls.contains( "spotify:track" ) ) + { + QStringList tracks = urls.split( "\n" ); + + tDebug() << "Got a list of spotify urls!" << tracks; + SpotifyParser* spot = new SpotifyParser( tracks, this ); + connect( spot, SIGNAL( tracks( QList ) ), this, SIGNAL( tracks( QList ) ) ); + } else if ( urls.contains( "bit.ly" ) || + urls.contains( "j.mp" ) || + urls.contains( "t.co" ) ) + { + QStringList tracks = urls.split( "\n" ); + + tDebug() << "Got a list of shortened urls!" << tracks; + ShortenedLinkParser* parser = new ShortenedLinkParser( tracks, this ); + connect( parser, SIGNAL( urls( QStringList ) ), this, SLOT( expandedUrls( QStringList ) ) ); + } +} + + +void +GlobalActionManager::expandedUrls( QStringList urls ) +{ + handleTrackUrls( urls.join( "\n" ) ); +} + + +QList< query_ptr > +GlobalActionManager::tracksFromQueryList( const QMimeData* data ) +{ + QList< query_ptr > queries; + QByteArray itemData = data->data( "application/tomahawk.query.list" ); + QDataStream stream( &itemData, QIODevice::ReadOnly ); + + while ( !stream.atEnd() ) + { + qlonglong qptr; + stream >> qptr; + + query_ptr* query = reinterpret_cast(qptr); + if ( query && !query->isNull() ) + { + tDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); + queries << *query; + } + } + + return queries; +} + +QList< query_ptr > +GlobalActionManager::tracksFromResultList( const QMimeData* data ) +{ + QList< query_ptr > queries; + QByteArray itemData = data->data( "application/tomahawk.result.list" ); + QDataStream stream( &itemData, QIODevice::ReadOnly ); + + while ( !stream.atEnd() ) + { + qlonglong qptr; + stream >> qptr; + + result_ptr* result = reinterpret_cast(qptr); + if ( result && !result->isNull() ) + { + tDebug() << "Dropped result item:" << result->data()->artist()->name() << "-" << result->data()->track(); + query_ptr q = result->data()->toQuery(); + q->addResults( QList< result_ptr >() << *result ); + queries << q; + } + } + + return queries; +} + +/// SPOTIFY URL HANDLING + +bool +GlobalActionManager::openSpotifyLink( const QString& link ) +{ + SpotifyParser* spot = new SpotifyParser( link, this ); + connect( spot, SIGNAL( track( Tomahawk::query_ptr ) ), this, SLOT( handleOpenTrack( Tomahawk::query_ptr ) ) ); + + return true; +} diff --git a/src/libtomahawk/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index d6440555a..c8dd48a1f 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -28,6 +28,9 @@ #include #include +/** + * Handles global actions such as parsing and creation of links, mime data handling, etc + */ class DLLEXPORT GlobalActionManager : public QObject { Q_OBJECT @@ -38,32 +41,47 @@ public: QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const; QUrl openLink( const QString& title, const QString& artist, const QString& album ) const; - // spotify - bool parseSpotifyLink( const QString& link ); + /// Takes a spotify link and performs the default open action on it + bool openSpotifyLink( const QString& link ); void copyToClipboard( const Tomahawk::query_ptr& query ) const; QString copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist ); void savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename ); + /** + * QMimeData helpers + * + * Call this to parse the tracks in a QMimeData object to query_ptrs. This will parse internal tomahawk + * data as well as all other formats supported (spotify, etc). + * + * Connect to tracks( QList< query_ptr> ); for the extracted tracks. + */ + bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); + void tracksFromMimeData( const QMimeData* data ); + QStringList mimeTypes() const; + public slots: bool parseTomahawkLink( const QString& link ); void waitingForResolved( bool ); Tomahawk::dynplaylist_ptr loadDynamicPlaylist( const QUrl& url, bool station ); + void handleOpenTrack( const Tomahawk::query_ptr& qry ); +signals: + /// QMimeData parsing results + void tracks( const QList< Tomahawk::query_ptr >& tracks ); + private slots: void bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl ); void showPlaylist(); void xspfCreated( const QByteArray& xspf ); - - // SPOTIFY - void spotifyTrackLookupFinished(); - + void expandedUrls( QStringList ); private: explicit GlobalActionManager( QObject* parent = 0 ); void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q ); + /// handle opening of urls bool handlePlaylistCommand( const QUrl& url ); bool handleCollectionCommand(const QUrl& url ); bool handleQueueCommand(const QUrl& url ); @@ -73,16 +91,20 @@ private: bool handlePlayCommand(const QUrl& url ); bool handleBookmarkCommand(const QUrl& url ); bool handleOpenCommand(const QUrl& url ); - - void handleOpenTrack( const Tomahawk::query_ptr& qry ); - bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems ); + + /// handle parsing mime data + void handleTrackUrls( const QString& urls ); + QList< Tomahawk::query_ptr > tracksFromQueryList( const QMimeData* d ); + QList< Tomahawk::query_ptr > tracksFromResultList( const QMimeData* d ); + QString hostname() const; Tomahawk::playlist_ptr m_toShow; Tomahawk::query_ptr m_waitingToBookmark; Tomahawk::query_ptr m_waitingToPlay; + QStringList m_mimeTypes; static GlobalActionManager* s_instance; }; diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index 1f5807d70..1cf9267c9 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - === * - * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/libtomahawk/playlist/dynamic/DynamicModel.h b/src/libtomahawk/playlist/dynamic/DynamicModel.h index 5c56d613f..243edd4f2 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.h +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.h @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - === * - * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp index c182c986b..cca7ad8a7 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp @@ -748,7 +748,7 @@ Tomahawk::EchonestControl::checkForMoodsOrStylesFetched() bool Tomahawk::EchonestControl::insertMoodsAndStyles() { - QVector< QString > src = selectedType() == "Mood" ? EchonestGenerator::moods() : EchonestGenerator::styles(); + QStringList src = selectedType() == "Mood" ? EchonestGenerator::moods() : EchonestGenerator::styles(); QComboBox* combo = qobject_cast< QComboBox* >( m_input.data() ); if( !combo ) return false; diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp index d6ff1d0e5..985a7fc7f 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.cpp @@ -22,12 +22,14 @@ #include "query.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include +#include using namespace Tomahawk; -QVector< QString > EchonestGenerator::s_moods = QVector< QString >(); -QVector< QString > EchonestGenerator::s_styles = QVector< QString >(); +QStringList EchonestGenerator::s_moods = QStringList(); +QStringList EchonestGenerator::s_styles = QStringList(); QNetworkReply* EchonestGenerator::s_moodsJob = 0; QNetworkReply* EchonestGenerator::s_stylesJob = 0; @@ -69,15 +71,7 @@ EchonestGenerator::EchonestGenerator ( QObject* parent ) m_mode = OnDemand; m_logo.load( RESPATH "/images/echonest_logo.png" ); - if( !s_stylesJob && s_styles.isEmpty() ) { - // fetch style and moods - s_stylesJob = Echonest::Artist::listTerms( "style" ); - connect( s_stylesJob, SIGNAL( finished() ), this, SLOT( stylesReceived() ) ); - } else if( !s_moodsJob && s_moods.isEmpty() ) { - s_moodsJob = Echonest::Artist::listTerms( "mood" ); - connect( s_moodsJob, SIGNAL( finished() ), this, SLOT( moodsReceived() ) ); - } - + loadStylesAndMoods(); // qDebug() << "ECHONEST:" << m_logo.size(); } @@ -540,8 +534,56 @@ EchonestGenerator::sentenceSummary() return sentence; } +void +EchonestGenerator::loadStylesAndMoods() +{ + if( !s_styles.isEmpty() || !s_moods.isEmpty() ) + return; -QVector< QString > + QFile dataFile( TomahawkUtils::appDataDir().absoluteFilePath( "echonest_stylesandmoods.dat" ) ); + if( !dataFile.exists() ) // load + { + s_stylesJob = Echonest::Artist::listTerms( "style" ); + connect( s_stylesJob, SIGNAL( finished() ), this, SLOT( stylesReceived() ) ); + s_moodsJob = Echonest::Artist::listTerms( "mood" ); + connect( s_moodsJob, SIGNAL( finished() ), this, SLOT( moodsReceived() ) ); + } else + { + if( !dataFile.open( QIODevice::ReadOnly ) ) + { + tLog() << "Failed to open for reading styles/moods db file:" << dataFile.fileName(); + return; + } + + QString allData = QString::fromUtf8( dataFile.readAll() ); + QStringList parts = allData.split( "\n" ); + if( parts.size() != 2 ) + { + tLog() << "Didn't get both moods and styles in file...:" << allData; + return; + } + s_moods = parts[ 0 ].split( "|" ); + s_styles = parts[ 1 ].split( "|" ); + } +} + +void +EchonestGenerator::saveStylesAndMoods() +{ + QFile dataFile( TomahawkUtils::appDataDir().absoluteFilePath( "echonest_stylesandmoods.dat" ) ); + if( !dataFile.open( QIODevice::WriteOnly ) ) + { + tLog() << "Failed to open styles and moods data file for saving:" << dataFile.errorString() << dataFile.fileName(); + return; + } + + QByteArray data = QString( "%1\n%2" ).arg( s_moods.join( "|" ) ).arg( s_styles.join( "|" ) ).toUtf8(); + dataFile.write( data ); +} + + + +QStringList EchonestGenerator::moods() { return s_moods; @@ -555,15 +597,18 @@ EchonestGenerator::moodsReceived() Q_ASSERT( r ); try { - s_moods = Echonest::Artist::parseTermList( r ); + s_moods = Echonest::Artist::parseTermList( r ).toList(); } catch( Echonest::ParseError& e ) { qWarning() << "Echonest failed to parse moods list"; } s_moodsJob = 0; + + if( !s_styles.isEmpty() ) + saveStylesAndMoods(); } -QVector< QString > +QStringList EchonestGenerator::styles() { return s_styles; @@ -577,9 +622,12 @@ EchonestGenerator::stylesReceived() Q_ASSERT( r ); try { - s_styles = Echonest::Artist::parseTermList( r ); + s_styles = Echonest::Artist::parseTermList( r ).toList(); } catch( Echonest::ParseError& e ) { qWarning() << "Echonest failed to parse styles list"; } s_stylesJob = 0; + + if( !s_moods.isEmpty() ) + saveStylesAndMoods(); } diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h index aac93b25b..f9147f900 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestGenerator.h @@ -59,8 +59,8 @@ public: virtual bool onDemandSteerable() const { return true; } virtual QWidget* steeringWidget(); - static QVector< QString > styles(); - static QVector< QString > moods(); + static QStringList styles(); + static QStringList moods(); signals: void paramsGenerated( const Echonest::DynamicPlaylist::PlaylistParams& ); @@ -90,11 +90,14 @@ private: Echonest::DynamicPlaylist::ArtistTypeEnum appendRadioType( Echonest::DynamicPlaylist::PlaylistParams& params ) const throw( std::runtime_error ); bool onlyThisArtistType( Echonest::DynamicPlaylist::ArtistTypeEnum type ) const throw( std::runtime_error ); + void loadStylesAndMoods(); + void saveStylesAndMoods(); + Echonest::DynamicPlaylist* m_dynPlaylist; QPixmap m_logo; - static QVector< QString > s_styles; - static QVector< QString > s_moods; + static QStringList s_styles; + static QStringList s_moods; static QNetworkReply* s_stylesJob; static QNetworkReply* s_moodsJob; diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp index 1678a1fbe..c9fcc1aa4 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestSteerer.cpp @@ -118,7 +118,7 @@ EchonestSteerer::EchonestSteerer( QWidget* parent ) m_fadeAnim = new QPropertyAnimation( this, "opacity", this ); m_fadeAnim->setDuration( ANIM_DURATION ); m_fadeAnim->setStartValue( 0 ); - m_fadeAnim->setEndValue( .86 ); + m_fadeAnim->setEndValue( .7 ); resize( sizeHint() ); } diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp index d3c05aff3..88e2bf25b 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp @@ -59,6 +59,9 @@ DynamicSetupWidget::DynamicSetupWidget( const Tomahawk::dynplaylist_ptr& playlis m_generatorCombo->setLabel( playlist->generator()->type().replace( 0, 1, playlist->generator()->type().at( 0 ).toUpper() ) ); m_layout->addWidget( m_generatorCombo ); + // TODO until there are more... no point in choices + m_headerText->hide(); + m_generatorCombo->hide(); m_generateButton = new QPushButton( tr( "Generate" ), this ); m_generateButton->setAttribute( Qt::WA_LayoutUsesWidgetRect ); @@ -76,7 +79,8 @@ DynamicSetupWidget::DynamicSetupWidget( const Tomahawk::dynplaylist_ptr& playlis else m_layout->addWidget( m_genNumber ); - m_layout->addSpacing( 30 ); + if( m_playlist->mode() == Static ) + m_layout->addSpacing( 30 ); m_logo = new QLabel( this ); if( !m_playlist->generator()->logo().isNull() ) { @@ -90,7 +94,7 @@ DynamicSetupWidget::DynamicSetupWidget( const Tomahawk::dynplaylist_ptr& playlis m_fadeAnim = new QPropertyAnimation( this, "opacity" ); m_fadeAnim->setDuration( 250 ); m_fadeAnim->setStartValue( 0.00 ); - m_fadeAnim->setEndValue( .86 ); + m_fadeAnim->setEndValue( .70 ); setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ); resize( sizeHint() ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index f3248eccc..0098a927f 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -413,9 +413,11 @@ DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qre p.setRenderHint( QPainter::Antialiasing ); p.setOpacity( opacity ); - QPen pen( pal.dark().color(), .5 ); + QColor c( 30, 30, 30 ); + + QPen pen( c.darker(), .5 ); p.setPen( pen ); - p.setBrush( pal.highlight() ); + p.setBrush( c ); p.drawRoundedRect( r, 10, 10 ); diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index 6ba559f95..fddeca85a 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -47,7 +47,7 @@ PlaylistItemDelegate::PlaylistItemDelegate( TrackView* parent, TrackProxyModel* , m_model( proxy ) { m_nowPlayingIcon = QPixmap( PLAYING_ICON ); - m_arrowIcon = QPixmap( ARROW_ICON ).scaled( 14, 14, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_arrowIcon = QPixmap( ARROW_ICON ); m_topOption = QTextOption( Qt::AlignTop ); m_topOption.setWrapMode( QTextOption::NoWrap ); @@ -251,6 +251,9 @@ PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewIt { opt.rect.setWidth( opt.rect.width() - 16 ); QRect arrowRect( opt.rect.x() + opt.rect.width(), opt.rect.y() + 1, opt.rect.height() - 2, opt.rect.height() - 2 ); + + if ( m_arrowIcon.height() != arrowRect.height() ) + m_arrowIcon = m_arrowIcon.scaled( arrowRect.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); painter->drawPixmap( arrowRect, m_arrowIcon ); } diff --git a/src/libtomahawk/playlist/playlistitemdelegate.h b/src/libtomahawk/playlist/playlistitemdelegate.h index 4d1cd1f27..6d9bcae7c 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.h +++ b/src/libtomahawk/playlist/playlistitemdelegate.h @@ -56,7 +56,7 @@ private: mutable QHash< qint64, QPixmap > m_cache; QPixmap m_nowPlayingIcon; - QPixmap m_arrowIcon; + mutable QPixmap m_arrowIcon; QTextOption m_topOption; QTextOption m_centerOption; diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 121ef9f82..43cd42b00 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -27,6 +27,7 @@ #include "database/databasecommand_playbackhistory.h" #include "dynamic/GeneratorInterface.h" #include "utils/logger.h" +#include "globalactionmanager.h" using namespace Tomahawk; @@ -38,6 +39,9 @@ PlaylistModel::PlaylistModel( QObject* parent ) { qDebug() << Q_FUNC_INFO; + m_dropStorage.parent = QPersistentModelIndex(); + m_dropStorage.row = -10; + setReadOnly( false ); } @@ -168,7 +172,7 @@ PlaylistModel::clear() { if ( rowCount( QModelIndex() ) ) { - emit loadingFinished();; + emit loadingFinished(); emit beginResetModel(); delete m_rootItem; @@ -355,16 +359,32 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r if ( action == Qt::IgnoreAction || isReadOnly() ) return true; - if ( !data->hasFormat( "application/tomahawk.query.list" ) - && !data->hasFormat( "application/tomahawk.plentry.list" ) - && !data->hasFormat( "application/tomahawk.result.list" ) ) + if ( !GlobalActionManager::instance()->acceptsMimeData( data ) ) return false; + m_dropStorage.row = row; + m_dropStorage.parent = QPersistentModelIndex( parent ); + m_dropStorage.action = action; + connect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + GlobalActionManager::instance()->tracksFromMimeData( data ); + + return true; +} + +void +PlaylistModel::parsedDroppedTracks( QList< query_ptr > tracks ) +{ + + if ( m_dropStorage.row == -10 ) // nope + return; + + disconnect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + int beginRow; - if ( row != -1 ) - beginRow = row; - else if ( parent.isValid() ) - beginRow = parent.row(); + if ( m_dropStorage.row != -1 ) + beginRow = m_dropStorage.row; + else if ( m_dropStorage.parent.isValid() ) + beginRow = m_dropStorage.parent.row(); else beginRow = rowCount( QModelIndex() ); @@ -373,51 +393,10 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r if ( currentItem().isValid() ) currentuuid = itemFromIndex( currentItem() )->query()->id(); - QList queries; - if ( data->hasFormat( "application/tomahawk.result.list" ) ) + if ( tracks.count() ) { - QByteArray itemData = data->data( "application/tomahawk.result.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); - - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; - - Tomahawk::result_ptr* result = reinterpret_cast(qptr); - if ( result && !result->isNull() ) - { - qDebug() << "Dropped result item:" << result->data()->artist() << "-" << result->data()->track(); - query_ptr q = result->data()->toQuery(); - q->addResults( QList< result_ptr >() << *result ); - queries << q; - } - } - } - - if ( data->hasFormat( "application/tomahawk.query.list" ) ) - { - QByteArray itemData = data->data( "application/tomahawk.query.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); - - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; - - Tomahawk::query_ptr* query = reinterpret_cast(qptr); - if ( query && !query->isNull() ) - { - qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track() << action; - queries << *query; - } - } - } - - if ( queries.count() ) - { - emit beginInsertRows( QModelIndex(), beginRow, beginRow + queries.count() - 1 ); - foreach( const Tomahawk::query_ptr& query, queries ) + emit beginInsertRows( QModelIndex(), beginRow, beginRow + tracks.count() - 1 ); + foreach( const Tomahawk::query_ptr& query, tracks ) { plentry_ptr e( new PlaylistEntry() ); e->setGuid( uuid() ); @@ -441,11 +420,22 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r } emit endInsertRows(); - if ( action == Qt::CopyAction ) + // Work around Qt-on-mac bug where drags from outside the app are Qt::MoveAction + // instead of Qt::CopyAction +#ifdef Q_OS_MAC + if ( m_dropStorage.action & Qt::CopyAction || m_dropStorage.action & Qt::MoveAction ) +#else + if ( m_dropStorage.action & Qt::CopyAction ) +#endif + { onPlaylistChanged( true ); + emit trackCountChanged( rowCount( QModelIndex() ) ); + } + } - return true; + m_dropStorage.parent = QPersistentModelIndex(); + m_dropStorage.row = -10; } diff --git a/src/libtomahawk/playlist/playlistmodel.h b/src/libtomahawk/playlist/playlistmodel.h index 4ca461fd3..11fc55b2d 100644 --- a/src/libtomahawk/playlist/playlistmodel.h +++ b/src/libtomahawk/playlist/playlistmodel.h @@ -39,6 +39,12 @@ class DLLEXPORT PlaylistModel : public TrackModel { Q_OBJECT +typedef struct { + int row; + QPersistentModelIndex parent; + Qt::DropAction action; +} DropStorageData; + public: explicit PlaylistModel( QObject* parent = 0 ); ~PlaylistModel(); @@ -84,6 +90,7 @@ private slots: void onTracksAdded( const QList& tracks ); void onTracksInserted( unsigned int row, const QList& tracks ); + void parsedDroppedTracks( QList ); void trackResolved( bool ); @@ -93,6 +100,8 @@ private: Tomahawk::playlist_ptr m_playlist; bool m_waitForUpdate, m_isTemporary; QList< Tomahawk::Query* > m_waitingForResolved; + + DropStorageData m_dropStorage; }; #endif // PLAYLISTMODEL_H diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index bbb3bb0e9..443ee1296 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -34,6 +34,7 @@ #include "dynamic/widgets/LoadingSpinner.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include using namespace Tomahawk; @@ -214,9 +215,7 @@ TrackView::dragEnterEvent( QDragEnterEvent* event ) qDebug() << Q_FUNC_INFO; QTreeView::dragEnterEvent( event ); - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) + if ( GlobalActionManager::instance()->acceptsMimeData( event->mimeData() ) ) { m_dragging = true; m_dropRect = QRect(); @@ -238,13 +237,18 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) return; } - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.plentry.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) + if ( GlobalActionManager::instance()->acceptsMimeData( event->mimeData() ) ) { setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); - const QModelIndex index = indexAt( pos ); + QModelIndex index = indexAt( pos ); + bool pastLast = false; + + if ( !index.isValid() && proxyModel()->rowCount( QModelIndex() ) > 0 ) + { + index = proxyModel()->index( proxyModel()->rowCount( QModelIndex() ) - 1, 0, QModelIndex() ); + pastLast = true; + } if ( index.isValid() ) { @@ -253,7 +257,8 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) // indicate that the item will be inserted above the current place const int gap = 5; // FIXME constant - m_dropRect = QRect( rect.left(), rect.top() - gap / 2, rect.width(), gap ); + int yHeight = ( pastLast ? rect.bottom() : rect.top() ) - gap / 2; + m_dropRect = QRect( 0, yHeight, width(), gap ); event->acceptProposedAction(); } @@ -274,20 +279,7 @@ TrackView::dropEvent( QDropEvent* event ) } else { - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) ) - { - const QPoint pos = event->pos(); - const QModelIndex index = indexAt( pos ); - - qDebug() << "Drop Event accepted at row:" << index.row(); - event->acceptProposedAction(); - - if ( !model()->isReadOnly() ) - { - model()->dropMimeData( event->mimeData(), event->proposedAction(), index.row(), 0, index.parent() ); - } - } - else if ( event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) + if ( GlobalActionManager::instance()->acceptsMimeData( event->mimeData() ) ) { const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); diff --git a/src/libtomahawk/utils/shortenedlinkparser.cpp b/src/libtomahawk/utils/shortenedlinkparser.cpp new file mode 100644 index 000000000..6b1cade6c --- /dev/null +++ b/src/libtomahawk/utils/shortenedlinkparser.cpp @@ -0,0 +1,89 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * 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 "shortenedlinkparser.h" + +#include "utils/logger.h" +#include "utils/tomahawkutils.h" +#include "query.h" + +#include + +#include +#include + +using namespace Tomahawk; + +ShortenedLinkParser::ShortenedLinkParser ( const QStringList& urls, QObject* parent ) + : QObject( parent ) +{ + foreach ( const QString& url, urls ) + lengthenUrl( url ); +} + +ShortenedLinkParser::~ShortenedLinkParser() {} + +void +ShortenedLinkParser::lengthenUrl( const QString& url ) +{ + // Whitelisted links + if ( !( url.contains( "t.co" ) || + url.contains( "bit.ly" ) || + url.contains( "j.mp" ) ) ) + return; + + tDebug() << "Looking up..." << url; + + QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( QUrl( url ) ) ); + connect( reply, SIGNAL( finished() ), this, SLOT( lookupFinished() ) ); + + m_queries.insert( reply ); +} + +void +ShortenedLinkParser::lookupFinished() +{ + QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() ); + Q_ASSERT( r ); + + QVariant redir = r->attribute( QNetworkRequest::RedirectionTargetAttribute ); + if ( redir.isValid() && !redir.toUrl().isEmpty() ) + { + tLog() << "Got a redirected url:" << redir.toUrl().toString(); + m_links << redir.toUrl().toString(); + } + + r->deleteLater(); + + m_queries.remove( r ); + checkFinished(); +} + + +void +ShortenedLinkParser::checkFinished() +{ + if ( m_queries.isEmpty() ) // we're done + { + qDebug() << "DONE and found redirected urls:" << m_links; + emit urls( m_links ); + + deleteLater(); + } + +} diff --git a/src/libtomahawk/utils/shortenedlinkparser.h b/src/libtomahawk/utils/shortenedlinkparser.h new file mode 100644 index 000000000..6a61eec9c --- /dev/null +++ b/src/libtomahawk/utils/shortenedlinkparser.h @@ -0,0 +1,61 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * 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 SNORTENED_LINK_PARSER_H +#define SNORTENED_LINK_PARSER_H + +#include "dllmacro.h" +#include "typedefs.h" + +#include +#include +#include + +class QNetworkReply; +namespace Tomahawk +{ + + /** + * Small class to parse whitelisted shortened links into the redirected urls + * + * Connect to urls() to get the result + * + */ + class DLLEXPORT ShortenedLinkParser : public QObject + { + Q_OBJECT + public: + explicit ShortenedLinkParser( const QStringList& urls, QObject* parent = 0 ); + virtual ~ShortenedLinkParser(); + + signals: + void urls( const QStringList& urls ); + + private: + void lengthenUrl( const QString& url ); + void checkFinished(); + + QStringList m_links; + QSet< QNetworkReply* > m_queries; + public slots: + void lookupFinished(); + }; + +} + +#endif diff --git a/src/libtomahawk/utils/spotifyparser.cpp b/src/libtomahawk/utils/spotifyparser.cpp new file mode 100644 index 000000000..fb2e7993c --- /dev/null +++ b/src/libtomahawk/utils/spotifyparser.cpp @@ -0,0 +1,147 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * 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 "spotifyparser.h" + +#include "utils/logger.h" +#include "utils/tomahawkutils.h" +#include "query.h" + +#include + +#include +#include + +using namespace Tomahawk; + +SpotifyParser::SpotifyParser( const QStringList& trackUrls, QObject* parent ) + : QObject ( parent ) + , m_single( false ) +{ + foreach ( const QString& url, trackUrls ) + lookupTrack( url ); +} + +SpotifyParser::SpotifyParser( const QString& trackUrl, QObject* parent ) + : QObject ( parent ) + , m_single( true ) +{ + lookupTrack( trackUrl ); +} + +SpotifyParser::~SpotifyParser() +{ + +} + +void +SpotifyParser::lookupTrack( const QString& link ) +{ + if ( !link.contains( "track" ) ) // we only support track links atm + return; + + // we need Spotify URIs such as spotify:track:XXXXXX, so if we by chance get a http://open.spotify.com url, convert it + QString uri = link; + if ( link.contains( "open.spotify.com" ) ) + { + QString hash = link; + hash.replace( "http://open.spotify.com/track/", "" ); + uri = QString( "spotify:track:%1" ).arg( hash ); + } + + tLog() << "Parsing Spotify Track URI:" << uri; + + QUrl url = QUrl( QString( "http://ws.spotify.com/lookup/1/.json?uri=%1" ).arg( uri ) ); + tDebug() << "Looking up..." << url.toString(); + + QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) ); + connect( reply, SIGNAL( finished() ), this, SLOT( spotifyTrackLookupFinished() ) ); + + m_queries.insert( reply ); + +} + +void +SpotifyParser::spotifyTrackLookupFinished() +{ + QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() ); + Q_ASSERT( r ); + m_queries.remove( r ); + r->deleteLater(); + + if ( r->error() == QNetworkReply::NoError ) + { + QJson::Parser p; + bool ok; + QVariantMap res = p.parse( r, &ok ).toMap(); + + if ( !ok ) + { + tLog() << "Failed to parse json from Spotify track lookup:" << p.errorString() << "On line" << p.errorLine(); + checkFinished(); + return; + } else if ( !res.contains( "track" ) ) + { + tLog() << "No 'track' item in the spotify track lookup result... not doing anything"; + checkFinished(); + return; + } + + // lets parse this baby + QVariantMap t = res.value( "track" ).toMap(); + + QString title, artist, album; + + title = t.value( "name", QString() ).toString(); + // TODO for now only take the first artist + if ( t.contains( "artists" ) && t[ "artists" ].canConvert< QVariantList >() && t[ "artists" ].toList().size() > 0 ) + artist = t[ "artists" ].toList().first().toMap().value( "name", QString() ).toString(); + if ( t.contains( "album" ) && t[ "album" ].canConvert< QVariantMap >() ) + album = t[ "album" ].toMap().value( "name", QString() ).toString(); + + if ( title.isEmpty() && artist.isEmpty() ) // don't have enough... + { + tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album; + return; + } + + Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), true ); + m_tracks << q; + + } else + { + tLog() << "Error in network request to Spotify for track decoding:" << r->errorString(); + } + + checkFinished(); +} + +void +SpotifyParser::checkFinished() +{ + if ( m_queries.isEmpty() ) // we're done + { + if ( m_single && !m_tracks.isEmpty() ) + emit track( m_tracks.first() ); + else if ( !m_single && !m_tracks.isEmpty() ) + emit tracks( m_tracks ); + + deleteLater(); + } + +} diff --git a/src/libtomahawk/utils/spotifyparser.h b/src/libtomahawk/utils/spotifyparser.h new file mode 100644 index 000000000..0a7a7a511 --- /dev/null +++ b/src/libtomahawk/utils/spotifyparser.h @@ -0,0 +1,64 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi + * + * 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 SPOTIFY_PARSER_H +#define SPOTIFY_PARSER_H + +#include "dllmacro.h" +#include "typedefs.h" + +#include +#include +#include + +class QNetworkReply; +namespace Tomahawk +{ + +/** + * Small class to parse spotify links into query_ptrs + * + * Connect to the signals to get the results + */ +class DLLEXPORT SpotifyParser : public QObject +{ + Q_OBJECT +public: + explicit SpotifyParser( const QString& trackUrl, QObject* parent = 0 ); + explicit SpotifyParser( const QStringList& trackUrls, QObject* parent = 0 ); + virtual ~SpotifyParser(); + +signals: + void track( const Tomahawk::query_ptr& track ); + void tracks( const QList< Tomahawk::query_ptr > tracks ); + +private slots: + void spotifyTrackLookupFinished(); + +private: + void lookupTrack( const QString& track ); + void checkFinished(); + + bool m_single; + QList< query_ptr > m_tracks; + QSet< QNetworkReply* > m_queries; +}; + +} + +#endif diff --git a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h index c31f48059..c3e557bf4 100644 --- a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h +++ b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.h @@ -73,6 +73,7 @@ public: virtual QString longDescription() const { return m_longDescription; } virtual QPixmap pixmap() const { if ( m_pixmap.isNull() ) return Tomahawk::ViewPage::pixmap(); else return m_pixmap; } + virtual bool isTemporaryPage() const { return true; } virtual bool showStatsBar() const { return false; } virtual bool jumpToCurrentTrack() { return false; } diff --git a/src/libtomahawk/widgets/searchwidget.cpp b/src/libtomahawk/widgets/searchwidget.cpp index 483920172..58b81a7f9 100644 --- a/src/libtomahawk/widgets/searchwidget.cpp +++ b/src/libtomahawk/widgets/searchwidget.cpp @@ -43,14 +43,17 @@ SearchWidget::SearchWidget( const QString& search, QWidget* parent ) ui->resultsView->overlay()->setEnabled( false ); ui->resultsView->sortByColumn( PlaylistModel::Score, Qt::DescendingOrder ); + TomahawkUtils::unmarginLayout( ui->verticalLayout ); + ui->resultsView->setContentsMargins( 0, 0, 0, 0 ); + ui->resultsView->setFrameShape( QFrame::NoFrame ); + ui->resultsView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); + m_queries << Tomahawk::Query::get( search, uuid() ); foreach ( const Tomahawk::query_ptr& query, m_queries ) { connect( query.data(), SIGNAL( resultsAdded( QList ) ), SLOT( onResultsFound( QList ) ) ); } - - connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( cancel() ) ); } @@ -94,11 +97,3 @@ SearchWidget::onResultsFound( const QList& results ) m_resultsModel->append( q ); } } - - -void -SearchWidget::cancel() -{ - emit destroyed( this ); - deleteLater(); -} diff --git a/src/libtomahawk/widgets/searchwidget.h b/src/libtomahawk/widgets/searchwidget.h index e8bbd08aa..83308e482 100644 --- a/src/libtomahawk/widgets/searchwidget.h +++ b/src/libtomahawk/widgets/searchwidget.h @@ -46,6 +46,7 @@ public: virtual QWidget* widget() { return this; } virtual Tomahawk::PlaylistInterface* playlistInterface() const { return 0; } + virtual bool isTemporaryPage() const { return true; } virtual QString title() const { return tr( "Search" ); } virtual QString description() const { return tr( "Results for '%1'" ).arg( m_search ); } @@ -63,8 +64,6 @@ signals: private slots: void onResultsFound( const QList& results ); - void cancel(); - private: Ui::SearchWidget *ui; diff --git a/src/libtomahawk/widgets/searchwidget.ui b/src/libtomahawk/widgets/searchwidget.ui index 7bace8edb..c259bced0 100644 --- a/src/libtomahawk/widgets/searchwidget.ui +++ b/src/libtomahawk/widgets/searchwidget.ui @@ -14,16 +14,12 @@ Qt::TabFocus + + 0 + - - - - QDialogButtonBox::Cancel - - - diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index 36f57401b..336de6fe1 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -28,6 +28,7 @@ #include "widgets/playlisttypeselectordlg.h" #include #include "utils/logger.h" +#include using namespace Tomahawk; @@ -121,12 +122,7 @@ CategoryAddItem::icon() const bool CategoryAddItem::willAcceptDrag( const QMimeData* data ) const { - if ( ( m_categoryType == SourcesModel::PlaylistsCategory || m_categoryType == SourcesModel::StationsCategory ) && - ( - data->hasFormat( "application/tomahawk.query.list" ) || - data->hasFormat( "application/tomahawk.result.list" ) - ) - ) + if ( ( m_categoryType == SourcesModel::PlaylistsCategory || m_categoryType == SourcesModel::StationsCategory ) && GlobalActionManager::instance()->acceptsMimeData( data ) ) { return true; } @@ -138,78 +134,43 @@ bool CategoryAddItem::dropMimeData( const QMimeData* data, Qt::DropAction ) { // Create a new playlist seeded with these items - if ( data->hasFormat( "application/tomahawk.query.list" ) || data->hasFormat( "application/tomahawk.result.list" ) ) { + connect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + GlobalActionManager::instance()->tracksFromMimeData( data ); - QList< Tomahawk::query_ptr > queries; + return true; +} - if ( data->hasFormat( "application/tomahawk.query.list" ) ) { - QByteArray itemData = data->data( "application/tomahawk.query.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); +void +CategoryAddItem::parsedDroppedTracks( const QList< query_ptr >& tracks ) +{ + disconnect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + if( m_categoryType == SourcesModel::PlaylistsCategory ) { - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; + playlist_ptr newpl = Playlist::create( SourceList::instance()->getLocal(), uuid(), "New Playlist", "", SourceList::instance()->getLocal()->friendlyName(), false, tracks ); + ViewManager::instance()->show( newpl ); - Tomahawk::query_ptr* query = reinterpret_cast(qptr); - if ( query && !query->isNull() ) - { - qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); - queries << *query; - } - } - } else if( data->hasFormat( "application/tomahawk.result.list" ) ) { - QByteArray itemData = data->data( "application/tomahawk.result.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); + // Give a shot to try to rename it. The playlist has to be created first. ugly. + QTimer::singleShot( 300, APP->mainWindow()->sourceTreeView(), SLOT( renamePlaylist() ) ); + } else if( m_categoryType == SourcesModel::StationsCategory ) { + // seed the playlist with these song filters + QString name = tracks.isEmpty() ? tr( "New Station" ) : tr( "%1 Station" ).arg( tracks.first()->track() ); + dynplaylist_ptr newpl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), name, "", SourceList::instance()->getLocal()->friendlyName(), OnDemand, false ); + newpl->setMode( OnDemand ); - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; - - Tomahawk::result_ptr* result = reinterpret_cast(qptr); - if ( result && !result->isNull() ) - { - qDebug() << "Dropped result item:" << result->data()->artist() << "-" << result->data()->track(); - query_ptr q = result->data()->toQuery(); - q->addResults( QList< result_ptr >() << *result ); - queries << q; - } - } + // now we want to add each query as a song filter... + QList< dyncontrol_ptr > contrls; + foreach( const Tomahawk::query_ptr& q, tracks ) { + dyncontrol_ptr c = newpl->generator()->createControl( "Song" ); + c->setInput( QString( "%1 %2" ).arg( q->track() ).arg( q->artist() ) ); + contrls << c; } - if( m_categoryType == SourcesModel::PlaylistsCategory ) { + newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), contrls ); - playlist_ptr newpl = Playlist::create( SourceList::instance()->getLocal(), uuid(), "New Playlist", "", SourceList::instance()->getLocal()->friendlyName(), false, queries ); - ViewManager::instance()->show( newpl ); - - // Give a shot to try to rename it. The playlist has to be created first. ugly. - QTimer::singleShot( 300, APP->mainWindow()->sourceTreeView(), SLOT( renamePlaylist() ) ); - } else if( m_categoryType == SourcesModel::StationsCategory ) { - // seed the playlist with these song filters - QString name = queries.isEmpty() ? tr( "New Station" ) : tr( "%1 Station" ).arg( queries.first()->track() ); - dynplaylist_ptr newpl = DynamicPlaylist::create( SourceList::instance()->getLocal(), uuid(), name, "", SourceList::instance()->getLocal()->friendlyName(), OnDemand, false ); - newpl->setMode( OnDemand ); - - // now we want to add each query as a song filter... - QList< dyncontrol_ptr > contrls; - foreach( const Tomahawk::query_ptr& q, queries ) { - dyncontrol_ptr c = newpl->generator()->createControl( "Song" ); - c->setInput( QString( "%1 %2" ).arg( q->track() ).arg( q->artist() ) ); - contrls << c; - } - - newpl->createNewRevision( uuid(), newpl->currentrevision(), newpl->type(), contrls ); - - - ViewManager::instance()->show( newpl ); - // Give a shot to try to rename it. The playlist has to be created first. ugly. - QTimer::singleShot( 300, APP->mainWindow()->sourceTreeView(), SLOT( renamePlaylist() ) ); - } - - return true; + ViewManager::instance()->show( newpl ); + // Give a shot to try to rename it. The playlist has to be created first. ugly. + QTimer::singleShot( 300, APP->mainWindow()->sourceTreeView(), SLOT( renamePlaylist() ) ); } - return false; } diff --git a/src/sourcetree/items/categoryitems.h b/src/sourcetree/items/categoryitems.h index 2ad4b2801..52468ff06 100644 --- a/src/sourcetree/items/categoryitems.h +++ b/src/sourcetree/items/categoryitems.h @@ -35,6 +35,9 @@ public: virtual bool willAcceptDrag(const QMimeData* data) const; virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action); +private slots: + void parsedDroppedTracks( const QList< Tomahawk::query_ptr >& tracks ); + private: SourcesModel::CategoryType m_categoryType; }; diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index c94dc5c0e..2bef4cce8 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -25,6 +25,7 @@ #include "collectionitem.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include "globalactionmanager.h" using namespace Tomahawk; @@ -138,56 +139,23 @@ PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) data->data( "application/tomahawk.playlist.id" ) == m_playlist->guid() ) return false; // don't allow dropping on ourselves - if ( data->hasFormat( "application/tomahawk.result.list" ) ) - { - QByteArray itemData = data->data( "application/tomahawk.result.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); + connect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + GlobalActionManager::instance()->tracksFromMimeData( data ); - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; + // TODO cant' know if it works or not yet... + return true; +} - Tomahawk::result_ptr* result = reinterpret_cast(qptr); - if ( result && !result->isNull() ) - { - qDebug() << "Dropped result item:" << result->data()->artist() << "-" << result->data()->track(); - query_ptr q = result->data()->toQuery(); - q->addResults( QList< result_ptr >() << *result ); - queries << q; - } - } - } - - if ( data->hasFormat( "application/tomahawk.query.list" ) ) - { - QByteArray itemData = data->data( "application/tomahawk.query.list" ); - QDataStream stream( &itemData, QIODevice::ReadOnly ); - - while ( !stream.atEnd() ) - { - qlonglong qptr; - stream >> qptr; - - Tomahawk::query_ptr* query = reinterpret_cast(qptr); - if ( query && !query->isNull() ) - { - qDebug() << "Dropped query item:" << query->data()->artist() << "-" << query->data()->track(); - queries << *query; - } - } - } - - if ( queries.count() && !m_playlist.isNull() && m_playlist->author()->isLocal() ) +void +PlaylistItem::parsedDroppedTracks( const QList< query_ptr >& tracks) +{ + disconnect( GlobalActionManager::instance(), SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + if ( tracks.count() && !m_playlist.isNull() && m_playlist->author()->isLocal() ) { qDebug() << "on playlist:" << m_playlist->title() << m_playlist->guid() << m_playlist->currentrevision(); - m_playlist->addEntries( queries, m_playlist->currentrevision() ); - - return true; + m_playlist->addEntries( tracks, m_playlist->currentrevision() ); } - - return false; } diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index ec974b74f..42ce35e00 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -43,6 +43,7 @@ protected: private slots: void onPlaylistLoaded( Tomahawk::PlaylistRevision revision ); void onPlaylistChanged(); + void parsedDroppedTracks( const QList& tracks ); private: bool m_loaded; diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index 1eb4fb386..94bd3e332 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -32,6 +32,7 @@ #include "viewmanager.h" #include "utils/logger.h" +#include "globalactionmanager.h" using namespace Tomahawk; @@ -179,10 +180,7 @@ SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role QStringList SourcesModel::mimeTypes() const { - QStringList types; - types << "application/tomahawk.query.list"; - types << "application/tomahawk.result.list"; - return types; + return GlobalActionManager::instance()->mimeTypes(); } @@ -216,7 +214,11 @@ SourcesModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int ro Qt::DropActions SourcesModel::supportedDropActions() const { +#ifdef Q_OS_MAC + return Qt::CopyAction | Qt::MoveAction; +#else return Qt::CopyAction; +#endif } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 970e4f268..b7f4226d9 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -431,11 +431,11 @@ SourceTreeView::dragEnterEvent( QDragEnterEvent* event ) qDebug() << Q_FUNC_INFO; QTreeView::dragEnterEvent( event ); - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) + if ( GlobalActionManager::instance()->acceptsMimeData( event->mimeData() ) ) { m_dragging = true; m_dropRect = QRect(); + m_dropIndex = QPersistentModelIndex(); qDebug() << "Accepting Drag Event"; event->setDropAction( Qt::CopyAction ); @@ -451,6 +451,8 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); + + m_dropIndex = QPersistentModelIndex(); } @@ -460,12 +462,12 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) bool accept = false; QTreeView::dragMoveEvent( event ); - if ( event->mimeData()->hasFormat( "application/tomahawk.query.list" ) - || event->mimeData()->hasFormat( "application/tomahawk.result.list" ) ) + if ( GlobalActionManager::instance()->acceptsMimeData( event->mimeData() ) ) { setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); + m_dropIndex = QPersistentModelIndex( index ); if ( index.isValid() ) { @@ -499,6 +501,7 @@ SourceTreeView::dropEvent( QDropEvent* event ) { QTreeView::dropEvent( event ); m_dragging = false; + m_dropIndex = QPersistentModelIndex(); } @@ -529,8 +532,7 @@ SourceTreeView::paintEvent( QPaintEvent* event ) if ( m_dragging && !m_dropRect.isEmpty() ) { QPainter painter( viewport() ); - const QModelIndex index = indexAt( m_dropRect.topLeft() ); - const QRect itemRect = visualRect( index ); + const QRect itemRect = visualRect( m_dropIndex ); QStyleOptionViewItemV4 opt; opt.initFrom( this ); diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 54054732a..0e62deab6 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -94,6 +94,7 @@ private: bool m_dragging; QRect m_dropRect; + QPersistentModelIndex m_dropIndex; }; #endif // SOURCETREEVIEW_H diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index a69fd6481..3fade3256 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -519,7 +519,7 @@ TomahawkApp::loadUrl( const QString& url ) if ( url.startsWith( "tomahawk://" ) ) return GlobalActionManager::instance()->parseTomahawkLink( url ); else if ( url.contains( "open.spotify.com" ) || url.contains( "spotify:track" ) ) - return GlobalActionManager::instance()->parseSpotifyLink( url ); + return GlobalActionManager::instance()->openSpotifyLink( url ); else { QFile f( url ); diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 7554bca2b..c6e7fcc7b 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -324,8 +324,8 @@ TomahawkWindow::setupSignals() connect( ui->actionShowOfflineSources, SIGNAL( triggered() ), SLOT( showOfflineSources() ) ); connect( ui->actionPlay, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( playPause() ) ); - connect( ui->actionNext, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( previous() ) ); - connect( ui->actionPrevious, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( next() ) ); + connect( ui->actionNext, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( next() ) ); + connect( ui->actionPrevious, SIGNAL( triggered() ), AudioEngine::instance(), SLOT( previous() ) ); #if defined( Q_OS_DARWIN ) connect( ui->actionMinimize, SIGNAL( triggered() ), SLOT( minimize() ) );