From 9d88c9a0f7113c75347c8f3d13ab6e8d5edf9a16 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 3 Aug 2011 23:25:31 -0400 Subject: [PATCH 01/28] Add support for JSPF playlists, remote and local. TWK-303 --- src/libtomahawk/CMakeLists.txt | 4 +- src/libtomahawk/globalactionmanager.cpp | 9 ++ src/libtomahawk/utils/jspfloader.cpp | 191 ++++++++++++++++++++++++ src/libtomahawk/utils/jspfloader.h | 77 ++++++++++ src/tomahawkapp.cpp | 11 +- 5 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/libtomahawk/utils/jspfloader.cpp create mode 100644 src/libtomahawk/utils/jspfloader.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 459cdc881..ff3a500f2 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -166,6 +166,7 @@ set( libSources utils/animatedsplitter.cpp utils/xspfloader.cpp utils/xspfgenerator.cpp + utils/jspfloader.cpp widgets/newplaylistwidget.cpp widgets/searchwidget.cpp @@ -336,6 +337,7 @@ set( libHeaders utils/animatedsplitter.h utils/xspfloader.h utils/xspfgenerator.h + utils/jspfloader.h widgets/newplaylistwidget.h widgets/searchwidget.h @@ -419,7 +421,7 @@ IF( APPLE ) FIND_LIBRARY( SCRIPTINGBRIDGE_LIBRARY ScriptingBridge ) MARK_AS_ADVANCED( COREAUDIO_LIBRARY COREFOUNDATION_LIBRARY FOUNDATION_LIBRARY SCRIPTINGBRIDGE_LIBRARY ) - SET( libSources ${libSources} + SET( libSources ${libSources} infosystem/infoplugins/mac/adium.mm infosystem/infoplugins/mac/adiumplugin.cpp widgets/maclineedit.mm diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index 2b28be069..b83238c98 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -41,6 +41,7 @@ #include #include +#include "utils/jspfloader.h" GlobalActionManager* GlobalActionManager::s_instance = 0; @@ -195,6 +196,14 @@ GlobalActionManager::parseTomahawkLink( const QString& url ) l->load( xspf ); connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); + return true; + } else if( u.hasQueryItem( "jspf" ) ) { + QUrl jspf = QUrl::fromUserInput( u.queryItemValue( "jspf" ) ); + Tomahawk::JSPFLoader* l = new Tomahawk::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 ) ) ); + return true; } } diff --git a/src/libtomahawk/utils/jspfloader.cpp b/src/libtomahawk/utils/jspfloader.cpp new file mode 100644 index 000000000..1bd896bf8 --- /dev/null +++ b/src/libtomahawk/utils/jspfloader.cpp @@ -0,0 +1,191 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi . + */ + + +#include "jspfloader.h" + + +#include +#include +#include + +#include + +#include "utils/tomahawkutils.h" +#include "utils/logger.h" + +#include "sourcelist.h" +#include "playlist.h" + +using namespace Tomahawk; + +void +JSPFLoader::load( const QUrl& url ) +{ + QNetworkRequest request( url ); + Q_ASSERT( TomahawkUtils::nam() != 0 ); + QNetworkReply* reply = TomahawkUtils::nam()->get( request ); + + // isn't there a race condition here? something could happen before we connect() + // no---the event loop is needed to make the request, i think (leo) + connect( reply, SIGNAL( finished() ), + SLOT( networkLoadFinished() ) ); + + connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ), + SLOT( networkError( QNetworkReply::NetworkError ) ) ); +} + + +void +JSPFLoader::load( QFile& file ) +{ + if( file.open( QFile::ReadOnly ) ) + { + m_body = file.readAll(); + gotBody(); + } + else + { + tLog() << "Failed to open jspf file"; + reportError(); + } +} + + +void +JSPFLoader::reportError() +{ + emit failed(); + deleteLater(); +} + + +void +JSPFLoader::networkLoadFinished() +{ + QNetworkReply* reply = qobject_cast(sender()); + m_body = reply->readAll(); + gotBody(); +} + + +void +JSPFLoader::networkError( QNetworkReply::NetworkError e ) +{ + tLog() << Q_FUNC_INFO << "Network error loading jspf" << e; + reportError(); +} + + +void +JSPFLoader::gotBody() +{ + QJson::Parser p; + bool retOk; + QVariantMap wrapper = p.parse( m_body, &retOk ).toMap(); + + if ( !retOk ) + { + tLog() << "Failed to parse jspf json:" << p.errorString() << "on line" << p.errorLine(); + return; + } + + if ( !wrapper.contains( "playlist" ) ) + { + tLog() << "No playlist element in JSPF!"; + return; + } + + QVariantMap pl = wrapper.value( "playlist" ).toMap(); + QString origTitle = pl.value( "title" ).toString(); + m_info = pl.value( "info" ).toString(); + m_creator = pl.value( "creator" ).toString(); + + m_title = origTitle; + if ( m_title.isEmpty() ) + m_title = tr( "New Playlist" ); + if ( !m_overrideTitle.isEmpty() ) + m_title = m_overrideTitle; + + if ( pl.contains( "track" ) ) + { + QVariantList tracks = pl.value( "track" ).toList(); + + bool shownError = false; + foreach ( const QVariant& track, tracks ) + { + QVariantMap tM = track.toMap(); + QString artist, album, track, duration, annotation, url; + + artist = tM.value( "creator" ).toString(); + album = tM.value( "album" ).toString(); + track = tM.value( "title" ).toString(); + duration = tM.value( "duration" ).toString(); + annotation = tM.value( "annotation" ).toString(); + if ( tM.value( "location" ).toList().size() > 0 ) + url = tM.value( "location" ).toList().first().toString(); + + if( artist.isEmpty() || track.isEmpty() ) + { + if( !shownError ) + { + QMessageBox::warning( 0, tr( "Failed to save tracks" ), tr( "Some tracks in the playlist do not contain an artist and a title. They will be ignored." ), QMessageBox::Ok ); + shownError = true; + } + continue; + } + + query_ptr q = Tomahawk::Query::get( artist, track, album, uuid() ); + q->setDuration( duration.toInt() / 1000 ); + if( !url.isEmpty() ) + q->setResultHint( url ); + + m_entries << q; + } + } + + if ( origTitle.isEmpty() && m_entries.isEmpty() ) + { + if ( m_autoCreate ) + { + QMessageBox::critical( 0, tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) ); + deleteLater(); + return; + } + else + { + emit failed(); + return; + } + } + + if ( m_autoCreate ) + { + m_playlist = Playlist::create( SourceList::instance()->getLocal(), + uuid(), + m_title, + m_info, + m_creator, + false, + m_entries ); + + deleteLater(); + } + + emit ok( m_playlist ); +} diff --git a/src/libtomahawk/utils/jspfloader.h b/src/libtomahawk/utils/jspfloader.h new file mode 100644 index 000000000..1228eac2f --- /dev/null +++ b/src/libtomahawk/utils/jspfloader.h @@ -0,0 +1,77 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Leo Franchi . + */ + + +#ifndef JSPFLOADER_H +#define JSPFLOADER_H + +#include +#include +#include +#include +#include + +#include "playlist.h" +#include "typedefs.h" + +#include "dllmacro.h" +namespace Tomahawk +{ + +class DLLEXPORT JSPFLoader : public QObject +{ +Q_OBJECT + +public: + explicit JSPFLoader( bool autoCreate = true, QObject* parent = 0 ) + : QObject( parent ) + , m_autoCreate( autoCreate ) + {} + + virtual ~JSPFLoader() {} + + QList< Tomahawk::query_ptr > entries() const { return m_entries; } + void setOverrideTitle( const QString& newTitle ) { m_overrideTitle = newTitle; } + +signals: + void failed(); + void ok( const Tomahawk::playlist_ptr& ); + +public slots: + void load( const QUrl& url ); + void load( QFile& file ); + +private slots: + void networkLoadFinished(); + void networkError( QNetworkReply::NetworkError e ); + +private: + void reportError(); + void gotBody(); + + bool m_autoCreate; + QList< Tomahawk::query_ptr > m_entries; + QString m_title, m_info, m_creator, m_overrideTitle; + + QByteArray m_body; + Tomahawk::playlist_ptr m_playlist; +}; + +} + +#endif // JSPFLOADER_H diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 76a332a00..cd2c7a502 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -54,6 +54,7 @@ #include "audio/audioengine.h" #include "utils/xspfloader.h" +#include "utils/jspfloader.h" #include "utils/logger.h" #include "utils/tomahawkutils.h" @@ -520,11 +521,19 @@ TomahawkApp::loadUrl( const QString& url ) { QFile f( url ); QFileInfo info( f ); - if ( f.exists() && info.suffix() == "xspf" ) { + if ( info.suffix() == "xspf" ) + { XSPFLoader* l = new XSPFLoader( true, this ); tDebug( LOGINFO ) << "Loading spiff:" << url; l->load( QUrl::fromUserInput( url ) ); + return true; + } else if ( info.suffix() == "jspf" ) + { + JSPFLoader* l = new JSPFLoader( true, this ); + tDebug( LOGINFO ) << "Loading j-spiff:" << url; + l->load( QUrl::fromUserInput( url ) ); + return true; } } From 4a34b97e8c3046baf54635555e90897ea37608b8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 4 Aug 2011 06:11:14 +0200 Subject: [PATCH 02/28] * This should fix the Windows local file playback issues. --- src/libtomahawk/audio/audioengine.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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; From 18628defa432413ce76a183ce052e89030c69c65 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 4 Aug 2011 06:34:36 +0200 Subject: [PATCH 03/28] * Fixed info.png's size. --- src/libtomahawk/globalactionmanager.cpp | 8 +++++--- src/libtomahawk/playlist/playlistitemdelegate.cpp | 5 ++++- src/libtomahawk/playlist/playlistitemdelegate.h | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index b83238c98..b99100f52 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -688,15 +688,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(); 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; From 2fdab5c9a1957af102a00139afcad6ad8dd7bd97 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 07:51:24 -0400 Subject: [PATCH 04/28] New 16x16 info.png. May play around with different icons, but scales well now. --- data/images/info.png | Bin 4086 -> 4346 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/images/info.png b/data/images/info.png index 8119348e039aa8e67be78d64e73417a0818a3e76..4fd9be9041a4640d6be4a55abc1929742bfde382 100644 GIT binary patch delta 1063 zcmZXT&ubGw6vt=d5=w1NDxzSOY!DIDKfr6_N{xkLOTG95{E-|)XsErl!G(Z@9#qyG zlS5B>Qs|*xY)^vz0TvG)Y75?r2o@^s`uWc8(5NrGd2hb&`+YOBGx=58EteOskB<)a zpX@ir3|6m(lfobKzQtNWH^+Q_8UX~0RDivH4eH?av_^)S#cw2 z^)BoaC$6yaH42u{zTqD|2hsEPf%R?P(PvC_ozwoE1i){$C;a2Q{f8oI?ug>qE%RjT umMKVh2+|zIkQm^qZ8BdaU0_#x;>*ZQWA@AGH@D}fG_PJ658qbqw*CNYN&i{^ delta 894 zcmY+CPe_w-7{`CVcSCfzG|@}3jb%tBo`!hvpyezFIYolXT|8{(E`fL&)JegUBEohu zMDifct9aJ4Qf zeNa6Bo}s#^9Q%y&G5IPO1FukPBc4d5QnhNex^MIO{8b8jG~y7nl!Tj1zOR6xS?U$V ze(w|cNZ8t%l)$tp+XA)BvcRWne zOg@<+5JEa0^ku6Ds%3)l=0FX#K`S z_3m=WHgA2BW>p#{GI34H3My|C|7s12QBTu!prGh;D{AUZ4}*>|--#D_k*|Jj9elz2 z2X@G$4R0wnHc45G2*sa_eGW?5Li~$(80-b>V2-KV!7FeL6A%~h9{fLptfxGnILo#1 zLqH9&oWp Date: Thu, 4 Aug 2011 10:12:15 -0400 Subject: [PATCH 05/28] de-biggified the info.png icon by adding padding to image --- data/images/info.png | Bin 4346 -> 4346 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/data/images/info.png b/data/images/info.png index 4fd9be9041a4640d6be4a55abc1929742bfde382..9af61a45fa5a9ca86951a55a7f05e8a4f544ddbe 100644 GIT binary patch delta 854 zcmZ9Lzb`{k6vyAIGBhH7g`ox#5{X}fL~K%FDA7g5($Gk2@kcbVnz~rph}IxN#3I7P zU=V|ZNGuWwm2|;ksc3z^x9-z8>Gz#;zu$Atz3;r+QctPp=)AF`DNw)ByMOOE{$O}2 z6diAIoBi)!>GVi?yW9NQQm*rBiA2Je&1TEVWU>J&0b*dZQmNG8ufdM%y4g%7Q?j{m zj@Lj7iaT+86itxq;GbE4fFU5efM29Z(X3KDeJ0{Tbv+fkwi@ac%yO(@`~i?nC`Y-~ zWlaN!6%b;JvP2R;agU0zx2kibh0w^={+M?J;uzJc6tB!aaLIPZ_*Gy8yi5}^=C=Wb z18M+l(@o+l^fu5A#%eZ54cJ1j0|ZSf>R>c{7xP-vg28JNV@BYC<{igwC-UyM_*;oI z3sKmu7(O5k&P+(}e*@$u)nAVEg3!RK)@bb)*Q13 zPrX(2pcfT{g8l(Y4?Xw;dKXVZO0xZYCp%E;z}xrc`#$f@%+79es5!KA{HXkJY;a^g z_sf`IU^1VbeVw*_!Ob6Q7{y}I#Bpq*C{mmzUPm6sGMKT}Ug3_gTBFgpDHICQ46(7A zXe7lUVTlG|_@pX^u?bi9SW2Q;gi|=NF`7(GBW#JHJ5?&jD2Y{#W0XMng8L)}bIahr z0lmX3KVaS)^IH>!hrR+VTh7>nc&pxcUoCJ0prSy7F3S7iShmm`P*(}VXVo=`J(P2V zU5|OM%?b<MT*Bk0{y>oE79fr z3Z9AMUUw(vP_1ip(U$x*s^Tu$malXlh1e8V*CeQ7O1gIp`~&?1b2m8XIl*W$)v{u% zs?~SlzG?g(E59M(fcjj}KMZJX8(HsWpFVGe>zwZYkpN$*PX|xKtp^_kYVJXCZp(Z! uZp$Q8*afsoGLO$-%3U!(B>c>-TH-Z`O?l<#^Y065vzkv%Oy#$8i?zQJKJl;s From fe762d4fc5381b65bfd779170401142c16b926d8 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 14:35:31 -0300 Subject: [PATCH 06/28] Changelogifying --- ChangeLog | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog b/ChangeLog index b8bdc5633..e18551c3d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,7 @@ Version 0.2.0: running out when using multiple clients. * Collection scanner now can now run automatically, watching files and directories for changes. On by default. + * Lots of new icons and design tweaks Version 0.1.0: * Fixed stations so they resolve against all available sources instead of From e4893be5011246da01feec4125cf93e7c0bd1a40 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 15:23:12 -0400 Subject: [PATCH 07/28] More changelogifying. Take a look and see if I missed anything or added too much. --- ChangeLog | 49 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/ChangeLog b/ChangeLog index e18551c3d..3e4bfd696 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,12 +1,55 @@ 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. + * Ship with libpng to ensure compatibility with certain OS X builds + * 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. - * Lots of new icons and design tweaks + * 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 From 04bff6270535f4c2a7bec1692caf7975882dd917 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 15:26:44 -0400 Subject: [PATCH 08/28] Couple of edits to changelog. Added desktop notifications (linux) and removed libpng entry. --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 3e4bfd696..f54761965 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,7 @@ Version 0.2.0: * Scroll per pixel to making scrolling feel more natural * Filter out duplicate tracks in Collection tree view * Spotify resolver now honors SOCKS5 proxy settings. + * Added desktop notifications (Linux) * 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 @@ -31,7 +32,6 @@ Version 0.2.0: * 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. - * Ship with libpng to ensure compatibility with certain OS X builds * 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 From 1d13bc2e402f5a7134395dc7a22b84e4ef2a3d03 Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 15:30:55 -0400 Subject: [PATCH 09/28] Stop repeating myself in changelog --- ChangeLog | 1 - 1 file changed, 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index f54761965..67c7d3d0f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,7 +7,6 @@ Version 0.2.0: * Scroll per pixel to making scrolling feel more natural * Filter out duplicate tracks in Collection tree view * Spotify resolver now honors SOCKS5 proxy settings. - * Added desktop notifications (Linux) * 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 From 0ad60299ef9034fd03d5b18e5cc616af8f22f46a Mon Sep 17 00:00:00 2001 From: Jason Herskowitz Date: Thu, 4 Aug 2011 18:29:59 -0300 Subject: [PATCH 10/28] Cleaned up formatting a bit. --- ChangeLog | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 67c7d3d0f..162da795d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,7 +15,9 @@ Version 0.2.0: * 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. - * 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 + * 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 From 4a3bca9bf477af5483170a100637694d684a62cf Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Thu, 4 Aug 2011 19:01:41 -0400 Subject: [PATCH 11/28] Factor out dragndrop support across tomahawk into GlobalActionManager, add spotify support --- src/libtomahawk/CMakeLists.txt | 4 +- src/libtomahawk/globalactionmanager.cpp | 269 ++++++++++-------- src/libtomahawk/globalactionmanager.h | 37 ++- .../playlist/dynamic/DynamicModel.cpp | 2 +- .../playlist/dynamic/DynamicModel.h | 2 +- src/libtomahawk/playlist/playlistmodel.cpp | 88 +++--- src/libtomahawk/playlist/playlistmodel.h | 9 + src/libtomahawk/playlist/trackview.cpp | 24 +- src/libtomahawk/utils/spotifyparser.cpp | 147 ++++++++++ src/libtomahawk/utils/spotifyparser.h | 64 +++++ src/sourcetree/items/categoryitems.cpp | 99 ++----- src/sourcetree/items/categoryitems.h | 3 + src/sourcetree/items/playlistitems.cpp | 56 +--- src/sourcetree/items/playlistitems.h | 1 + src/sourcetree/sourcesmodel.cpp | 6 +- src/sourcetree/sourcetreeview.cpp | 6 +- src/tomahawkapp.cpp | 2 +- 17 files changed, 494 insertions(+), 325 deletions(-) create mode 100644 src/libtomahawk/utils/spotifyparser.cpp create mode 100644 src/libtomahawk/utils/spotifyparser.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index ff3a500f2..a808526bd 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -167,6 +167,7 @@ set( libSources utils/xspfloader.cpp utils/xspfgenerator.cpp utils/jspfloader.cpp + utils/spotifyparser.cpp widgets/newplaylistwidget.cpp widgets/searchwidget.cpp @@ -338,6 +339,7 @@ set( libHeaders utils/xspfloader.h utils/xspfgenerator.h utils/jspfloader.h + utils/spotifyparser.h widgets/newplaylistwidget.h widgets/searchwidget.h @@ -427,7 +429,7 @@ IF( APPLE ) widgets/maclineedit.mm utils/tomahawkutils_mac.mm ) - SET( libHeaders ${libHeaders} + SET( libHeaders ${libHeaders} infosystem/infoplugins/mac/adiumplugin.h widgets/maclineedit.h ) diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index b99100f52..e55f4e968 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -22,6 +22,7 @@ #include #include +#include "artist.h" #include "album.h" #include "sourcelist.h" #include "pipeline.h" @@ -42,9 +43,13 @@ #include #include #include "utils/jspfloader.h" +#include +#include "utils/spotifyparser.h" GlobalActionManager* GlobalActionManager::s_instance = 0; +using namespace Tomahawk; + GlobalActionManager* GlobalActionManager::instance() { @@ -54,18 +59,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 +104,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 +116,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 +146,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 +173,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 +203,7 @@ 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 +266,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 +309,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 +359,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 +377,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 +419,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 +559,7 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station ) return pl; } - return Tomahawk::dynplaylist_ptr(); + return dynplaylist_ptr(); } @@ -587,10 +591,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 +626,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 +651,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 +672,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; @@ -712,82 +716,111 @@ 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 ) +{ + 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 data + if ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "spotify" ) ) + 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(); + QString plainData = QString::fromUtf8( data->data( "text/plain" ).constData() ); + tDebug() << "Got text/plain mime data:" << data->data( "text/plain" ) << "decoded to:" << plainData; + if( plainData.contains( "open.spotify.com") || + plainData.contains( "spotify:track" ) ) + { + QStringList tracks = plainData.split( "\n" ); - 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; + tDebug() << "Got a list of spotify urls!" << tracks; + SpotifyParser* spot = new SpotifyParser( tracks, this ); + connect( spot, SIGNAL( tracks( QList ) ), this, SIGNAL( tracks( QList ) ) ); } - - // 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(); } } +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..5f647847e 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 ); + 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 ); +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 handleOpenTrack( const Tomahawk::query_ptr& qry ); 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,19 @@ 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 + 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/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 121ef9f82..82a31316c 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,12 @@ PlaylistModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int r } emit endInsertRows(); - if ( action == Qt::CopyAction ) + if ( m_dropStorage.action == Qt::CopyAction ) onPlaylistChanged( true ); } - 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..6e379e8bc 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,9 +237,7 @@ 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(); @@ -274,20 +271,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/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/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..ff73ba253 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(); } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 970e4f268..3b77c6dab 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -431,8 +431,7 @@ 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(); @@ -460,8 +459,7 @@ 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(); diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index cd2c7a502..c15aed9d0 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -516,7 +516,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 ); From e476251c5e81639f7eb7fd84ce11f94ebb36f153 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Fri, 5 Aug 2011 02:56:38 +0200 Subject: [PATCH 12/28] Remove ugly dash from rc version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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} ) From 89cec23815a0325fd0bb2521b7011d8e520f1448 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 07:53:30 -0400 Subject: [PATCH 13/28] On OS X drops from outside the apps are Qt::MoveActions.... --- src/sourcetree/sourcesmodel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index ff73ba253..94bd3e332 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -214,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 } From 0e2dd37559ef55f8fbda3de83014ff04f4f401aa Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 10:39:08 -0400 Subject: [PATCH 14/28] allow dropping onto AudioControls to start playing --- src/audiocontrols.cpp | 50 +++++++++++++++++++++++++++ src/audiocontrols.h | 7 ++++ src/libtomahawk/globalactionmanager.h | 3 +- 3 files changed, 58 insertions(+), 2 deletions(-) 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/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index 5f647847e..e5a1cfaf3 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -66,6 +66,7 @@ public slots: 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 ); @@ -75,8 +76,6 @@ private slots: void showPlaylist(); void xspfCreated( const QByteArray& xspf ); - - void handleOpenTrack( const Tomahawk::query_ptr& qry ); private: explicit GlobalActionManager( QObject* parent = 0 ); void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q ); From 57dfd6b4b29020275465a203f45a21ad6728fb09 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 11:14:08 -0400 Subject: [PATCH 15/28] Set drop rect from left edge to right edge always, don't rely on QAIV for native drag-drops --- src/libtomahawk/playlist/trackview.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 6e379e8bc..5d1f45b58 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -246,11 +246,10 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) if ( index.isValid() ) { const QRect rect = visualRect( index ); - m_dropRect = rect; // 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 ); + m_dropRect = QRect( 0, rect.top() - gap / 2, width(), gap ); event->acceptProposedAction(); } From ae48c151d04f5ee667e25fbe51ece951556271fc Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 11:21:26 -0400 Subject: [PATCH 16/28] Show indicator on osx too. NO CLUE why this fixes it.. --- src/libtomahawk/playlist/trackview.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 5d1f45b58..d7c751d72 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -246,6 +246,7 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) if ( index.isValid() ) { const QRect rect = visualRect( index ); + m_dropRect = rect; // indicate that the item will be inserted above the current place const int gap = 5; // FIXME constant From ec028544908490396e1e3273f494794316403bcb Mon Sep 17 00:00:00 2001 From: Alejandro Wainzinger Date: Fri, 5 Aug 2011 17:31:31 +0200 Subject: [PATCH 17/28] Do not backwards the signals connect for next/previous. * Fixed TWK-313 - fix signal/slot connection for next/previous actionmenu items. --- src/tomahawkwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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() ) ); From 7feb9d5249f628226616b50cbf45d22ccafc2506 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 11:44:45 -0400 Subject: [PATCH 18/28] Draw proper drop index, since on some platforms (OS X :( ) visualRect() and indexAt() aren't commutative... --- src/sourcetree/sourcetreeview.cpp | 8 ++++++-- src/sourcetree/sourcetreeview.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 3b77c6dab..b7f4226d9 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -435,6 +435,7 @@ SourceTreeView::dragEnterEvent( QDragEnterEvent* event ) { m_dragging = true; m_dropRect = QRect(); + m_dropIndex = QPersistentModelIndex(); qDebug() << "Accepting Drag Event"; event->setDropAction( Qt::CopyAction ); @@ -450,6 +451,8 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) m_dragging = false; setDirtyRegion( m_dropRect ); + + m_dropIndex = QPersistentModelIndex(); } @@ -464,6 +467,7 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) setDirtyRegion( m_dropRect ); const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); + m_dropIndex = QPersistentModelIndex( index ); if ( index.isValid() ) { @@ -497,6 +501,7 @@ SourceTreeView::dropEvent( QDropEvent* event ) { QTreeView::dropEvent( event ); m_dragging = false; + m_dropIndex = QPersistentModelIndex(); } @@ -527,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 From 4d31eac5f615b2ab99e8a7ccc50e5ee23fe8b2fb Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 11:57:42 -0400 Subject: [PATCH 19/28] Emit trackCountChanged when dropping tracks in a playlist. Handle Qt/Mac bug where drops from outside the app are Qt::MoveAction not Qt::CopyAction --- src/libtomahawk/playlist/playlistmodel.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 82a31316c..43cd42b00 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -420,8 +420,18 @@ PlaylistModel::parsedDroppedTracks( QList< query_ptr > tracks ) } emit endInsertRows(); - if ( m_dropStorage.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() ) ); + } + } m_dropStorage.parent = QPersistentModelIndex(); From 453bf6206ce720490119118c95caa1698b9242d0 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 12:00:45 -0400 Subject: [PATCH 20/28] Only accept spotify tracks by default --- src/libtomahawk/globalactionmanager.cpp | 9 +++++---- src/libtomahawk/globalactionmanager.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index e55f4e968..5d4795084 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -726,7 +726,7 @@ GlobalActionManager::mimeTypes() const bool -GlobalActionManager::acceptsMimeData( const QMimeData* data ) +GlobalActionManager::acceptsMimeData( const QMimeData* data, bool tracksOnly ) { if ( data->hasFormat( "application/tomahawk.query.list" ) || data->hasFormat( "application/tomahawk.plentry.list" ) @@ -735,8 +735,9 @@ GlobalActionManager::acceptsMimeData( const QMimeData* data ) return true; } - // crude check for spotify data - if ( data->hasFormat( "text/plain" ) && data->data( "text/plain" ).contains( "spotify" ) ) + // 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; return false; @@ -754,7 +755,7 @@ GlobalActionManager::tracksFromMimeData( const QMimeData* data ) { QString plainData = QString::fromUtf8( data->data( "text/plain" ).constData() ); tDebug() << "Got text/plain mime data:" << data->data( "text/plain" ) << "decoded to:" << plainData; - if( plainData.contains( "open.spotify.com") || + if( plainData.contains( "open.spotify.com/track") || plainData.contains( "spotify:track" ) ) { QStringList tracks = plainData.split( "\n" ); diff --git a/src/libtomahawk/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index e5a1cfaf3..af5dedd5c 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -56,7 +56,7 @@ public: * * Connect to tracks( QList< query_ptr> ); for the extracted tracks. */ - bool acceptsMimeData( const QMimeData* data ); + bool acceptsMimeData( const QMimeData* data, bool tracksOnly = true ); void tracksFromMimeData( const QMimeData* data ); QStringList mimeTypes() const; From 7df1f7a8b8f55ce98b0b28edd4cda7214a3f92c7 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 12:38:16 -0400 Subject: [PATCH 21/28] Expand bit.ly, t.co, and j.mp links to spotify tracks --- src/libtomahawk/CMakeLists.txt | 2 + src/libtomahawk/globalactionmanager.cpp | 34 ++++++- src/libtomahawk/globalactionmanager.h | 1 + src/libtomahawk/utils/shortenedlinkparser.cpp | 89 +++++++++++++++++++ src/libtomahawk/utils/shortenedlinkparser.h | 61 +++++++++++++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/libtomahawk/utils/shortenedlinkparser.cpp create mode 100644 src/libtomahawk/utils/shortenedlinkparser.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index a808526bd..d0ff77f72 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -168,6 +168,7 @@ set( libSources utils/xspfgenerator.cpp utils/jspfloader.cpp utils/spotifyparser.cpp + utils/shortenedlinkparser.cpp widgets/newplaylistwidget.cpp widgets/searchwidget.cpp @@ -340,6 +341,7 @@ set( libHeaders utils/xspfgenerator.h utils/jspfloader.h utils/spotifyparser.h + utils/shortenedlinkparser.h widgets/newplaylistwidget.h widgets/searchwidget.h diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index 5d4795084..89f354d7b 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -45,6 +45,7 @@ #include "utils/jspfloader.h" #include #include "utils/spotifyparser.h" +#include "utils/shortenedlinkparser.h" GlobalActionManager* GlobalActionManager::s_instance = 0; @@ -740,6 +741,13 @@ GlobalActionManager::acceptsMimeData( const QMimeData* data, bool tracksOnly ) ( 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; } @@ -755,7 +763,7 @@ GlobalActionManager::tracksFromMimeData( const QMimeData* data ) { QString plainData = QString::fromUtf8( data->data( "text/plain" ).constData() ); tDebug() << "Got text/plain mime data:" << data->data( "text/plain" ) << "decoded to:" << plainData; - if( plainData.contains( "open.spotify.com/track") || + if ( plainData.contains( "open.spotify.com/track") || plainData.contains( "spotify:track" ) ) { QStringList tracks = plainData.split( "\n" ); @@ -763,10 +771,34 @@ GlobalActionManager::tracksFromMimeData( const QMimeData* data ) 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 ( plainData.contains( "bit.ly" ) || + plainData.contains( "j.mp" ) || + plainData.contains( "t.co" ) ) + { + QStringList tracks = plainData.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 ) +{ + QStringList spotifyUrls; + foreach ( const QString& url, urls ) + { + if( url.contains( "open.spotify.com/track") || url.contains( "spotify:track" ) ) + spotifyUrls << url; + } + + SpotifyParser* spot = new SpotifyParser( spotifyUrls, this ); + connect( spot, SIGNAL( tracks( QList ) ), this, SIGNAL( tracks( QList ) ) ); +} + + QList< query_ptr > GlobalActionManager::tracksFromQueryList( const QMimeData* data ) { diff --git a/src/libtomahawk/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index af5dedd5c..ce18d7297 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -76,6 +76,7 @@ private slots: void showPlaylist(); void xspfCreated( const QByteArray& xspf ); + void expandedUrls( QStringList ); private: explicit GlobalActionManager( QObject* parent = 0 ); void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q ); 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 From 0877b17a58c97dcfcbee59390ce60263e004522d Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 12:46:37 -0400 Subject: [PATCH 22/28] Handle multiply-shortened urls. --- src/libtomahawk/globalactionmanager.cpp | 53 ++++++++++++------------- src/libtomahawk/globalactionmanager.h | 1 + 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/libtomahawk/globalactionmanager.cpp b/src/libtomahawk/globalactionmanager.cpp index 89f354d7b..bfccb918a 100644 --- a/src/libtomahawk/globalactionmanager.cpp +++ b/src/libtomahawk/globalactionmanager.cpp @@ -763,39 +763,38 @@ GlobalActionManager::tracksFromMimeData( const QMimeData* data ) { QString plainData = QString::fromUtf8( data->data( "text/plain" ).constData() ); tDebug() << "Got text/plain mime data:" << data->data( "text/plain" ) << "decoded to:" << plainData; - if ( plainData.contains( "open.spotify.com/track") || - plainData.contains( "spotify:track" ) ) - { - QStringList tracks = plainData.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 ( plainData.contains( "bit.ly" ) || - plainData.contains( "j.mp" ) || - plainData.contains( "t.co" ) ) - { - QStringList tracks = plainData.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 ) ) ); - } + 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 ) { - QStringList spotifyUrls; - foreach ( const QString& url, urls ) - { - if( url.contains( "open.spotify.com/track") || url.contains( "spotify:track" ) ) - spotifyUrls << url; - } - - SpotifyParser* spot = new SpotifyParser( spotifyUrls, this ); - connect( spot, SIGNAL( tracks( QList ) ), this, SIGNAL( tracks( QList ) ) ); + handleTrackUrls( urls.join( "\n" ) ); } diff --git a/src/libtomahawk/globalactionmanager.h b/src/libtomahawk/globalactionmanager.h index ce18d7297..c8dd48a1f 100644 --- a/src/libtomahawk/globalactionmanager.h +++ b/src/libtomahawk/globalactionmanager.h @@ -94,6 +94,7 @@ private: 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 ); From 8834bf81fa54073aac45d43392c6db998e37b86a Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 14:06:39 -0400 Subject: [PATCH 23/28] save styles/moods to file, instead of loading on demand sillyness. Fixes TWK-308 --- .../dynamic/echonest/EchonestControl.cpp | 2 +- .../dynamic/echonest/EchonestGenerator.cpp | 78 +++++++++++++++---- .../dynamic/echonest/EchonestGenerator.h | 11 ++- 3 files changed, 71 insertions(+), 20 deletions(-) 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; From b67623e649045622bb2a4e3d363c079c82928e51 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 14:14:59 -0400 Subject: [PATCH 24/28] Don't redefine signals --- src/sip/twitter/twitter.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/sip/twitter/twitter.h b/src/sip/twitter/twitter.h index 1846e0b8a..cdeaa8306 100644 --- a/src/sip/twitter/twitter.h +++ b/src/sip/twitter/twitter.h @@ -72,9 +72,6 @@ public: virtual QIcon icon() const; virtual QWidget* configWidget(); -signals: - void avatarReceived( QString, QPixmap ); - public slots: virtual bool connectPlugin( bool startup ); void disconnectPlugin(); From c7c12eef93adb1571c98c2601403df800746ec47 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 14:22:34 -0400 Subject: [PATCH 25/28] Show drop indicator after last track if it's dropped below --- src/libtomahawk/playlist/trackview.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index d7c751d72..443ee1296 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -241,7 +241,14 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) { 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() ) { @@ -250,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( 0, rect.top() - gap / 2, width(), gap ); + int yHeight = ( pastLast ? rect.bottom() : rect.top() ) - gap / 2; + m_dropRect = QRect( 0, yHeight, width(), gap ); event->acceptProposedAction(); } From f151b59431cec1bf21b3d441edf6cef4b8d67ea1 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 14:36:52 -0400 Subject: [PATCH 26/28] Show search and artist pages as temporary pages in sidebar Remove margins and spacing from searchwidget Remove "Cancel" button as no other view pages have cancel buttons, and we can't delete a page if we show it as a temporary page anymore --- .../widgets/infowidgets/ArtistInfoWidget.h | 1 + src/libtomahawk/widgets/searchwidget.cpp | 15 +++++---------- src/libtomahawk/widgets/searchwidget.h | 3 +-- src/libtomahawk/widgets/searchwidget.ui | 10 +++------- 4 files changed, 10 insertions(+), 19 deletions(-) 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 - - - From 5d5b5fa3afd35b7abcffc58239ff567bfd519a6b Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 14:55:39 -0400 Subject: [PATCH 27/28] Paint dynamic controls the same hue as overlay widget. TWK-309 --- .../playlist/dynamic/echonest/EchonestSteerer.cpp | 2 +- .../playlist/dynamic/widgets/DynamicSetupWidget.cpp | 2 +- src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) 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..fb23be2fc 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp @@ -90,7 +90,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 ); From b2deebe0c25232955cbbf5bb5847557d1c615306 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 5 Aug 2011 15:36:04 -0400 Subject: [PATCH 28/28] Hide generator combobox until we have more than one generator --- .../playlist/dynamic/widgets/DynamicSetupWidget.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicSetupWidget.cpp index fb23be2fc..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() ) {