From fc95cee6ced0ee7cf7fb1b57f1e912baecd0f2b3 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 28 Mar 2012 14:17:20 -0400 Subject: [PATCH 01/14] Dynamically enable/disable the web API, and change the wording for the option to make it more enticing --- src/stackedsettingsdialog.ui | 2 +- src/tomahawkapp.cpp | 48 ++++++++++++++++++++++++++++++------ src/tomahawkapp.h | 7 +++--- 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/stackedsettingsdialog.ui b/src/stackedsettingsdialog.ui index 704c60c40..3ee15c7fe 100644 --- a/src/stackedsettingsdialog.ui +++ b/src/stackedsettingsdialog.ui @@ -644,7 +644,7 @@ Qt::RightToLeft - Playdar HTTP API + Allow web browsers to detect Tomahawk true diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index d15440343..22bcad592 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -255,6 +255,7 @@ TomahawkApp::init() { initHTTP(); } + connect( TomahawkSettings::instance(), SIGNAL( changed() ), SLOT( initHTTP() ) ); #ifndef ENABLE_HEADLESS if ( !s->hasScannerPaths() ) @@ -296,6 +297,11 @@ TomahawkApp::~TomahawkApp() { tLog() << "Shutting down Tomahawk..."; + if ( !m_session.isNull() ) + delete m_session.data(); + if ( !m_connector.isNull() ) + delete m_connector.data(); + Pipeline::instance()->stop(); if ( !m_servent.isNull() ) @@ -458,15 +464,43 @@ TomahawkApp::initDatabase() void TomahawkApp::initHTTP() { - m_session.setPort( 60210 ); //TODO config - m_session.setListenInterface( QHostAddress::LocalHost ); - m_session.setConnector( &m_connector ); + if ( !TomahawkSettings::instance()->httpEnabled() ) + { + tLog() << "Stopping HTTPd, not enabled"; + if ( !m_session.isNull() ) + delete m_session.data(); + if ( !m_connector.isNull() ) + delete m_connector.data(); + return; + } - Api_v1* api = new Api_v1( &m_session ); - m_session.setStaticContentService( api ); + if ( m_session ) + { + tLog() << "HTTPd session already exists, returning"; + return; + } + + m_session = QWeakPointer< QxtHttpSessionManager >( new QxtHttpSessionManager() ); + m_connector = QWeakPointer< QxtHttpServerConnector >( new QxtHttpServerConnector ); + if ( m_session.isNull() || m_connector.isNull() ) + { + if ( !m_session.isNull() ) + delete m_session.data(); + if ( !m_connector.isNull() ) + delete m_connector.data(); + tLog() << "Failed to start HTTPd, could not create object"; + return; + } + + m_session.data()->setPort( 60210 ); //TODO config + m_session.data()->setListenInterface( QHostAddress::LocalHost ); + m_session.data()->setConnector( m_connector.data() ); - tLog() << "Starting HTTPd on" << m_session.listenInterface().toString() << m_session.port(); - m_session.start(); + Api_v1* api = new Api_v1( m_session.data() ); + m_session.data()->setStaticContentService( api ); + + tLog() << "Starting HTTPd on" << m_session.data()->listenInterface().toString() << m_session.data()->port(); + m_session.data()->start(); } diff --git a/src/tomahawkapp.h b/src/tomahawkapp.h index e97e359b8..f78a05b30 100644 --- a/src/tomahawkapp.h +++ b/src/tomahawkapp.h @@ -101,6 +101,7 @@ public slots: private slots: void initServent(); void initSIP(); + void initHTTP(); void spotifyApiCheckFinished(); @@ -114,8 +115,6 @@ private: void initLocalCollection(); void initPipeline(); - void initHTTP(); - QWeakPointer m_database; QWeakPointer m_scanManager; QWeakPointer m_audioEngine; @@ -135,8 +134,8 @@ private: bool m_headless, m_loaded; - QxtHttpServerConnector m_connector; - QxtHttpSessionManager m_session; + QWeakPointer< QxtHttpServerConnector > m_connector; + QWeakPointer< QxtHttpSessionManager > m_session; }; Q_DECLARE_METATYPE( QPersistentModelIndex ) From 43c4daa4e367e0c015380a86247678548a348766 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 28 Mar 2012 14:20:29 -0400 Subject: [PATCH 02/14] Change wording --- src/stackedsettingsdialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stackedsettingsdialog.ui b/src/stackedsettingsdialog.ui index 3ee15c7fe..355a6b4a3 100644 --- a/src/stackedsettingsdialog.ui +++ b/src/stackedsettingsdialog.ui @@ -644,7 +644,7 @@ Qt::RightToLeft - Allow web browsers to detect Tomahawk + Allow web browsers to interact with Tomahawk true From 04864c7d796f4197f49308676f95d8d47c63da8e Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 14 Mar 2012 16:14:25 -0400 Subject: [PATCH 03/14] Set scanning threads idle priority (cherry picked from commit 5a57e285ec2a25fe71877a7828b87bb65a31cf3e) --- src/musicscanner.cpp | 1 + src/scanmanager.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/musicscanner.cpp b/src/musicscanner.cpp index 14f619aeb..9f064b4ed 100644 --- a/src/musicscanner.cpp +++ b/src/musicscanner.cpp @@ -173,6 +173,7 @@ MusicScanner::scan() SLOT( commitBatch( QVariantList, QVariantList ) ), Qt::DirectConnection ); m_dirListerThreadController = new QThread( this ); + m_dirListerThreadController->setPriority( QThread::IdlePriority ); m_dirLister = QWeakPointer< DirLister >( new DirLister( m_dirs ) ); m_dirLister.data()->moveToThread( m_dirListerThreadController ); diff --git a/src/scanmanager.cpp b/src/scanmanager.cpp index 1209f58ce..adad60807 100644 --- a/src/scanmanager.cpp +++ b/src/scanmanager.cpp @@ -195,6 +195,7 @@ ScanManager::runDirScan() { m_scanTimer->stop(); m_musicScannerThreadController = new QThread( this ); + m_musicScannerThreadController->setPriority( QThread::IdlePriority ); m_scanner = QWeakPointer< MusicScanner >( new MusicScanner( paths ) ); m_scanner.data()->moveToThread( m_musicScannerThreadController ); connect( m_scanner.data(), SIGNAL( finished() ), SLOT( scannerFinished() ) ); From deb0eb819c828b538a998e972629d3467ab7b28f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 17 Mar 2012 01:33:03 +0100 Subject: [PATCH 04/14] * Fetch square covers from Last.fm. (cherry picked from commit d5aed7b6df11634ea6d86691feea94e8a3462a36) --- .../infosystem/infoplugins/generic/lastfmplugin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp index df14af143..bf2d0134c 100644 --- a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp @@ -456,7 +456,7 @@ LastFmPlugin::notInCacheSlot( QHash criteria, Tomahawk::InfoSy imgurl.addEncodedQueryItem( "artist", QUrl::toPercentEncoding( artistName, "", "+" ) ); imgurl.addEncodedQueryItem( "album", QUrl::toPercentEncoding( albumName, "", "+" ) ); imgurl.addQueryItem( "autocorrect", QString::number( 1 ) ); - imgurl.addQueryItem( "size", "large" ); + imgurl.addQueryItem( "size", "largesquare" ); imgurl.addQueryItem( "api_key", "7a90f6672a04b809ee309af169f34b8b" ); QNetworkRequest req( imgurl ); @@ -475,7 +475,7 @@ LastFmPlugin::notInCacheSlot( QHash criteria, Tomahawk::InfoSy imgurl.addQueryItem( "method", "artist.imageredirect" ); imgurl.addEncodedQueryItem( "artist", QUrl::toPercentEncoding( artistName, "", "+" ) ); imgurl.addQueryItem( "autocorrect", QString::number( 1 ) ); - imgurl.addQueryItem( "size", "large" ); + imgurl.addQueryItem( "size", "largesquare" ); imgurl.addQueryItem( "api_key", "7a90f6672a04b809ee309af169f34b8b" ); QNetworkRequest req( imgurl ); From e5bdd2242fa670cac9646600d0db1cdc4d5dd3ad Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 17 Mar 2012 05:42:25 +0100 Subject: [PATCH 05/14] * Fixed race condition during resolving. (cherry picked from commit b70114a22557be39d998b017fd82c4553a40bdfb) --- .../database/databasecommand_resolve.cpp | 40 ++++++++++--------- src/libtomahawk/result.cpp | 18 ++++++++- src/libtomahawk/result.h | 6 ++- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 38149e83d..4f13539a5 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -55,9 +55,6 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) qDebug() << "Using result-hint to speed up resolving:" << m_query->resultHint(); Tomahawk::result_ptr result = lib->resultFromHint( m_query ); - /* qDebug() << "Result null:" << result.isNull(); - * qDebug() << "Collection null:" << result->collection().isNull(); - * qDebug() << "Source null:" << result->collection()->source().isNull();*/ if ( !result.isNull() && !result->collection().isNull() && result->collection()->source()->isOnline() ) { QList res; @@ -137,7 +134,7 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) else { s = SourceList::instance()->get( files_query.value( 16 ).toUInt() ); - if( s.isNull() ) + if ( s.isNull() ) { qDebug() << "Could not find source" << files_query.value( 16 ).toUInt(); continue; @@ -147,12 +144,15 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) } Tomahawk::result_ptr result = Tomahawk::Result::get( url ); - Tomahawk::artist_ptr artist = - Tomahawk::Artist::get( files_query.value( 18 ).toUInt(), files_query.value( 12 ).toString() ); - Tomahawk::album_ptr album = - Tomahawk::Album::get( files_query.value( 19 ).toUInt(), files_query.value( 13 ).toString(), artist ); - Tomahawk::artist_ptr composer = - Tomahawk::Artist::get( files_query.value( 20 ).toUInt(), files_query.value( 15 ).toString() ); + if ( result->isValid() ) + { + qDebug() << "Result already cached:" << result->toString(); + continue; + } + + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( files_query.value( 18 ).toUInt(), files_query.value( 12 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( files_query.value( 19 ).toUInt(), files_query.value( 13 ).toString(), artist ); + Tomahawk::artist_ptr composer = Tomahawk::Artist::get( files_query.value( 20 ).toUInt(), files_query.value( 15 ).toString() ); result->setModificationTime( files_query.value( 1 ).toUInt() ); result->setSize( files_query.value( 2 ).toUInt() ); @@ -181,6 +181,7 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) result->setAttributes( attr ); result->setCollection( s->collection() ); + res << result; } @@ -270,7 +271,7 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) else { s = SourceList::instance()->get( files_query.value( 16 ).toUInt() ); - if( s.isNull() ) + if ( s.isNull() ) { qDebug() << "Could not find source" << files_query.value( 16 ).toUInt(); continue; @@ -280,12 +281,15 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) } Tomahawk::result_ptr result = Tomahawk::Result::get( url ); - Tomahawk::artist_ptr artist = - Tomahawk::Artist::get( files_query.value( 18 ).toUInt(), files_query.value( 12 ).toString() ); - Tomahawk::album_ptr album = - Tomahawk::Album::get( files_query.value( 19 ).toUInt(), files_query.value( 13 ).toString(), artist ); - Tomahawk::artist_ptr composer = - Tomahawk::Artist::get( files_query.value( 20 ).toUInt(), files_query.value( 15 ).toString() ); + if ( result->isValid() ) + { + qDebug() << "Result already cached:" << result->toString(); + continue; + } + + Tomahawk::artist_ptr artist = Tomahawk::Artist::get( files_query.value( 18 ).toUInt(), files_query.value( 12 ).toString() ); + Tomahawk::album_ptr album = Tomahawk::Album::get( files_query.value( 19 ).toUInt(), files_query.value( 13 ).toString(), artist ); + Tomahawk::artist_ptr composer = Tomahawk::Artist::get( files_query.value( 20 ).toUInt(), files_query.value( 15 ).toString() ); result->setModificationTime( files_query.value( 1 ).toUInt() ); result->setSize( files_query.value( 2 ).toUInt() ); @@ -322,8 +326,8 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) } result->setAttributes( attr ); - result->setCollection( s->collection() ); + res << result; } diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp index 06dd7a472..2e00e8953 100644 --- a/src/libtomahawk/result.cpp +++ b/src/libtomahawk/result.cpp @@ -43,7 +43,7 @@ Result::get( const QString& url ) return s_results.value( url ); } - result_ptr r = result_ptr( new Result( url ), &QObject::deleteLater ); + result_ptr r = result_ptr( new Result( url ), &Result::deleteLater ); s_results.insert( url, r ); return r; @@ -68,12 +68,28 @@ Result::Result( const QString& url ) Result::~Result() +{ +} + + +void +Result::deleteLater() { QMutexLocker lock( &s_mutex ); + if ( s_results.contains( m_url ) ) { s_results.remove( m_url ); } + + QObject::deleteLater(); +} + + +bool +Result::isValid() const +{ + return !m_rid.isEmpty(); } diff --git a/src/libtomahawk/result.h b/src/libtomahawk/result.h index 2d743e4dd..95bafcd74 100644 --- a/src/libtomahawk/result.h +++ b/src/libtomahawk/result.h @@ -58,6 +58,7 @@ public: static Tomahawk::result_ptr get( const QString& url ); virtual ~Result(); + bool isValid() const; QVariant toVariant() const; QString toString() const; Tomahawk::query_ptr toQuery(); @@ -108,6 +109,9 @@ public: unsigned int trackId() const { return m_trackId; } unsigned int fileId() const { return m_fileId; } +public slots: + void deleteLater(); + signals: // emitted when the collection this result comes from is going offline/online: void statusChanged(); @@ -115,7 +119,7 @@ signals: private slots: void onOffline(); void onOnline(); - + private: // private constructor explicit Result( const QString& url ); From 95f1162b6e3d481365c9e6c52310acd488edf661 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 17 Mar 2012 05:49:43 +0100 Subject: [PATCH 06/14] * Fixed cached resolving. (cherry picked from commit bd098e3ff267dbe636192464683314679e1f225d) --- src/libtomahawk/database/databasecommand_resolve.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 4f13539a5..354208105 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -147,6 +147,7 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) if ( result->isValid() ) { qDebug() << "Result already cached:" << result->toString(); + res << result; continue; } @@ -284,6 +285,7 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) if ( result->isValid() ) { qDebug() << "Result already cached:" << result->toString(); + res << result; continue; } From ace18dfa1fc04f15a4f93f861f9f21effc5f988e Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 17 Mar 2012 12:10:09 +0100 Subject: [PATCH 07/14] * Prevent race condition. (cherry picked from commit ebbedb2b991f1f02b862b91af14ad6eaa9574c70) --- .../database/databasecommand_resolve.cpp | 6 ++++-- src/libtomahawk/result.cpp | 15 ++++++++------- src/libtomahawk/result.h | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 354208105..9d0e4199c 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -143,8 +143,9 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) url = QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url ); } + bool cached = Tomahawk::Result::isCached( url ); Tomahawk::result_ptr result = Tomahawk::Result::get( url ); - if ( result->isValid() ) + if ( cached ) { qDebug() << "Result already cached:" << result->toString(); res << result; @@ -281,8 +282,9 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) url = QString( "servent://%1\t%2" ).arg( s->userName() ).arg( url ); } + bool cached = Tomahawk::Result::isCached( url ); Tomahawk::result_ptr result = Tomahawk::Result::get( url ); - if ( result->isValid() ) + if ( cached ) { qDebug() << "Result already cached:" << result->toString(); res << result; diff --git a/src/libtomahawk/result.cpp b/src/libtomahawk/result.cpp index 2e00e8953..8c53a45c4 100644 --- a/src/libtomahawk/result.cpp +++ b/src/libtomahawk/result.cpp @@ -50,6 +50,14 @@ Result::get( const QString& url ) } +bool +Result::isCached( const QString& url ) +{ + QMutexLocker lock( &s_mutex ); + return ( s_results.contains( url ) ); +} + + Result::Result( const QString& url ) : QObject() , m_url( url ) @@ -86,13 +94,6 @@ Result::deleteLater() } -bool -Result::isValid() const -{ - return !m_rid.isEmpty(); -} - - artist_ptr Result::artist() const { diff --git a/src/libtomahawk/result.h b/src/libtomahawk/result.h index 95bafcd74..72f3f3825 100644 --- a/src/libtomahawk/result.h +++ b/src/libtomahawk/result.h @@ -56,9 +56,9 @@ friend class ::DatabaseCommand_LoadFile; public: static Tomahawk::result_ptr get( const QString& url ); + static bool isCached( const QString& url ); virtual ~Result(); - bool isValid() const; QVariant toVariant() const; QString toString() const; Tomahawk::query_ptr toQuery(); From bee6485475dcc2c5fc0b37e3a34b3bbcca5b8edf Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 17 Mar 2012 17:09:57 +0100 Subject: [PATCH 08/14] * Fixed crash situation in AudioControls. (cherry picked from commit a2bfd73d555ac1a8e8b6107bba4cee89a0ca701f) Conflicts: src/audiocontrols.cpp --- src/audiocontrols.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index f6211ca06..85e4e2411 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -277,7 +277,7 @@ void AudioControls::onSocialActionsLoaded() { Query* query = qobject_cast< Query* >( sender() ); - if ( !query ) + if ( !query || !m_currentTrack || query != m_currentTrack->toQuery().data() ) return; query_ptr currentQuery = m_currentTrack->toQuery(); From 565217a53a6f42235bb972c8634a7bce5d36825e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 10:42:11 -0400 Subject: [PATCH 09/14] TWK-725: Some extra pointer safety --- .../qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp b/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp index c444018e3..e02ba562a 100644 --- a/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp +++ b/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp @@ -575,9 +575,14 @@ void QxtHttpSessionManager::processEvents() */ void QxtHttpSessionManager::chunkReadyRead(int requestID) { + if (!connector()) return; + const QSharedPointer& dataSource = connector()->getRequestDataSource( requestID ); if (!dataSource->bytesAvailable()) return; + QIODevice* device = connector()->getRequestConnection(requestID); + if (!device) return; + if (!device->bytesToWrite() || qxt_d().connectionState[device].readyRead == false) { qxt_d().connectionState[device].readyRead = true; @@ -590,6 +595,9 @@ void QxtHttpSessionManager::chunkReadyRead(int requestID) */ void QxtHttpSessionManager::sendNextChunk(int requestID) { + if ( !connector() ) + return; + const QSharedPointer& dataSource = connector()->getRequestDataSource( requestID ); QIODevice* device = connector()->getRequestConnection(requestID); QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device]; From 907dad95e1febb564ffb7d522b089e1c1d016c9f Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 12:09:59 -0400 Subject: [PATCH 10/14] TWK-721: Don't flicker from officialtracks to supercollection tracks. If the second infosystem albumtracks request comes back empty but the first one came back with tracks, this is not a failure and so keep the official tracks --- src/libtomahawk/playlist/treemodel.cpp | 19 +++++++++++++------ .../widgets/infowidgets/AlbumInfoWidget.cpp | 5 +++-- .../widgets/infowidgets/AlbumInfoWidget.h | 1 + 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 98100d65d..b933f9231 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - === * * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2012, 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 @@ -622,7 +623,7 @@ TreeModel::addAlbums( const artist_ptr& artist, const QModelIndex& parent, bool requestData.caller = m_infoId; requestData.customData["row"] = parent.row(); requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["refetch"] = autoRefetch; requestData.type = Tomahawk::InfoSystem::InfoArtistReleases; Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } @@ -660,8 +661,8 @@ TreeModel::addTracks( const album_ptr& album, const QModelIndex& parent, bool au m_receivedInfoData.removeAll( artistInfo ); Tomahawk::InfoSystem::InfoRequestData requestData; requestData.caller = m_infoId; - requestData.customData["rows"] = QVariant( rows ); - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["rows"] = rows; + requestData.customData["refetch"] = autoRefetch; requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); requestData.type = Tomahawk::InfoSystem::InfoAlbumSongs; requestData.timeoutMillis = 0; @@ -796,7 +797,7 @@ TreeModel::onAlbumsAdded( const QList& albums, const QModel albumitem = new TreeModelItem( album, parentItem ); albumitem->index = createIndex( parentItem->children.count() - 1, 0, albumitem ); connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - + getCover( albumitem->index ); } @@ -886,7 +887,7 @@ TreeModel::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QV QModelIndex idx = index( requestData.customData[ "row" ].toInt(), 0, QModelIndex() ); - if ( requestData.customData[ "refetch" ].toInt() > 0 && !al.count() ) + if ( requestData.customData[ "refetch" ].toBool() && !al.count() ) { setMode( DatabaseMode ); @@ -940,7 +941,13 @@ TreeModel::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QV } else if ( m_receivedInfoData.count() == 2 /* FIXME */ ) { - if ( requestData.customData[ "refetch" ].toInt() > 0 ) + // If the second load got no data, but the first load did, don't do anything + QList< QVariant > rows = requestData.customData[ "rows" ].toList(); + QModelIndex idx = index( rows.first().toUInt(), 0, index( rows.at( 1 ).toUInt(), 0, QModelIndex() ) ); + if ( rowCount( idx ) ) + return; + + if ( requestData.customData[ "refetch" ].toBool() ) { setMode( DatabaseMode ); diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index af76743c7..ca0c8bf00 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -1,6 +1,7 @@ /* === 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 @@ -231,7 +232,7 @@ AlbumInfoWidget::loadAlbums( bool autoRefetch ) artistInfo["artist"] = m_album->artist()->name(); Tomahawk::InfoSystem::InfoRequestData requestData; - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["refetch"] = autoRefetch; requestData.caller = m_infoId; requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); requestData.type = Tomahawk::InfoSystem::InfoArtistReleases; @@ -308,7 +309,7 @@ AlbumInfoWidget::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestDa tDebug() << "Adding" << al.count() << "albums"; gotAlbums( al ); } - else if ( requestData.customData[ "refetch" ].toInt() > 0 ) + else if ( requestData.customData[ "refetch" ].toBool() ) { tDebug() << "Auto refetching"; m_buttonAlbums->setChecked( false ); diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h index 23aafb046..fe92e760f 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h @@ -1,6 +1,7 @@ /* === 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 From d623bbefc20918f7048f652ad80a897f782d97a2 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 13:27:36 -0400 Subject: [PATCH 11/14] Fix crash on exit --- src/libtomahawk/resolvers/scriptresolver.cpp | 8 +++++++- src/libtomahawk/resolvers/scriptresolver.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/resolvers/scriptresolver.cpp b/src/libtomahawk/resolvers/scriptresolver.cpp index 0cdd371a6..aea82067c 100644 --- a/src/libtomahawk/resolvers/scriptresolver.cpp +++ b/src/libtomahawk/resolvers/scriptresolver.cpp @@ -41,6 +41,7 @@ ScriptResolver::ScriptResolver( const QString& exe ) , m_ready( false ) , m_stopped( true ) , m_configSent( false ) + , m_deleting( false ) , m_error( Tomahawk::ExternalResolver::NoError ) { tLog() << Q_FUNC_INFO << "Created script resolver:" << exe; @@ -61,9 +62,10 @@ ScriptResolver::ScriptResolver( const QString& exe ) ScriptResolver::~ScriptResolver() { disconnect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( cmdExited( int, QProcess::ExitStatus ) ) ); + m_deleting = true; m_proc.kill(); - m_proc.waitForFinished(); + m_proc.waitForFinished(); // might call handleMsg Tomahawk::Pipeline::instance()->removeResolver( this ); @@ -207,6 +209,10 @@ ScriptResolver::handleMsg( const QByteArray& msg ) { // qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii( msg ); + // Might be called from waitForFinished() in ~ScriptResolver, no database in that case, abort. + if ( m_deleting ) + return; + bool ok; QVariant v = m_parser.parse( msg, &ok ); if ( !ok || v.type() != QVariant::Map ) diff --git a/src/libtomahawk/resolvers/scriptresolver.h b/src/libtomahawk/resolvers/scriptresolver.h index f8e94b0ff..f894ece7e 100644 --- a/src/libtomahawk/resolvers/scriptresolver.h +++ b/src/libtomahawk/resolvers/scriptresolver.h @@ -85,7 +85,7 @@ private: quint32 m_msgsize; QByteArray m_msg; - bool m_ready, m_stopped, m_configSent; + bool m_ready, m_stopped, m_configSent, m_deleting; ExternalResolver::ErrorState m_error; QJson::Parser m_parser; From 73d7ba03f500ea04717bb6640b67b9badd32f6b9 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 13:34:48 -0400 Subject: [PATCH 12/14] TWK-815: Try using foreground text color when drawing grey bg, white on grey is hard to read --- src/libtomahawk/playlist/playlistitemdelegate.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index ba932a8c1..684e1e249 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -104,10 +104,13 @@ PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItemV4* option, const if ( item->isPlaying() ) { option->palette.setColor( QPalette::Highlight, option->palette.color( QPalette::Mid ) ); - option->state |= QStyle::State_Selected; + + option->backgroundBrush = option->palette.color( QPalette::Mid ); + option->palette.setColor( QPalette::Text, option->palette.color( QPalette::Text ) ); + } - if ( option->state & QStyle::State_Selected ) + if ( option->state & QStyle::State_Selected && !item->isPlaying() ) { option->palette.setColor( QPalette::Text, option->palette.color( QPalette::HighlightedText ) ); } From 9f5215302cc0df28ba77066f0da1031f56534826 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 14:32:01 -0400 Subject: [PATCH 13/14] various updater fixes --- src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp | 2 -- src/libtomahawk/playlist/XspfUpdater.cpp | 7 +++++++ src/libtomahawk/utils/xspfloader.cpp | 6 +++++- src/libtomahawk/utils/xspfloader.h | 3 ++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp b/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp index da7e2f75d..c057ceb59 100644 --- a/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp +++ b/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp @@ -64,8 +64,6 @@ PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl ) connect( m_timer, SIGNAL( timeout() ), this, SLOT( updateNow() ) ); QTimer::singleShot( 0, this, SLOT( doSave() ) ); - - setAutoUpdate( m_autoUpdate ); } PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl, int interval, bool autoUpdate ) diff --git a/src/libtomahawk/playlist/XspfUpdater.cpp b/src/libtomahawk/playlist/XspfUpdater.cpp index 2eb1d7a1e..d00991d20 100644 --- a/src/libtomahawk/playlist/XspfUpdater.cpp +++ b/src/libtomahawk/playlist/XspfUpdater.cpp @@ -55,8 +55,15 @@ XspfUpdater::~XspfUpdater() void XspfUpdater::updateNow() { + if ( m_url.isEmpty() ) + { + qWarning() << "XspfUpdater not updating because we have an empty url..."; + return; + } + XSPFLoader* l = new XSPFLoader( false, false ); l->setAutoResolveTracks( false ); + l->setErrorTitle( playlist()->title() ); l->load( m_url ); connect( l, SIGNAL( tracks( QList ) ), this, SLOT( playlistLoaded( QList ) ) ); } diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 9fd0c7369..f9a70851b 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -122,7 +122,11 @@ XSPFLoader::reportError() { emit error( FetchError ); #ifndef ENABLE_HEADLESS - JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorToString( FetchError) ) ); + const QString errorMsg = errorToString( FetchError); + if ( !m_errorTitle.isEmpty() ) + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( QString( "%1: %2" ).arg( m_errorTitle ).arg( errorMsg ) ) ); + else + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMsg ) ); #endif deleteLater(); } diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index e4e912618..7369a51b8 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -49,6 +49,7 @@ public: void setOverrideTitle( const QString& newTitle ); void setAutoResolveTracks( bool autoResolve ) { m_autoResolve = autoResolve; } void setAutoDelete( bool autoDelete ) { m_autoDelete = autoDelete; } + void setErrorTitle( const QString& error ) { m_errorTitle = error; } static QString errorToString( XSPFErrorCode error ); @@ -73,7 +74,7 @@ private: bool m_autoCreate, m_autoUpdate, m_autoResolve, m_autoDelete; QString m_NS,m_overrideTitle; QList< Tomahawk::query_ptr > m_entries; - QString m_title, m_info, m_creator; + QString m_title, m_info, m_creator, m_errorTitle; QUrl m_url; QByteArray m_body; From 79bfdec895f4b04af30d89660e6fa82f8fa42930 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Fri, 30 Mar 2012 17:14:08 -0400 Subject: [PATCH 14/14] TWK-798: Start playing first resolvable track when double-clicking on a playlist item --- src/libtomahawk/CMakeLists.txt | 2 + src/libtomahawk/playlist/trackview.cpp | 71 +++++++- src/libtomahawk/playlist/trackview.h | 10 ++ src/libtomahawk/utils/closure.cpp | 89 ++++++++++ src/libtomahawk/utils/closure.h | 225 +++++++++++++++++++++++++ src/sourcetree/items/playlistitems.cpp | 12 ++ src/sourcetree/items/playlistitems.h | 5 +- src/sourcetree/items/sourcetreeitem.h | 1 + src/sourcetree/sourcedelegate.cpp | 23 ++- src/sourcetree/sourcedelegate.h | 2 + src/sourcetree/sourcetreeview.cpp | 13 ++ src/sourcetree/sourcetreeview.h | 1 + 12 files changed, 451 insertions(+), 3 deletions(-) create mode 100644 src/libtomahawk/utils/closure.cpp create mode 100644 src/libtomahawk/utils/closure.h diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 55fbc8da8..bde94eb52 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -116,6 +116,7 @@ set( libGuiSources utils/dropjobnotifier.cpp utils/proxystyle.cpp utils/tomahawkutilsgui.cpp + utils/closure.cpp widgets/checkdirtree.cpp widgets/querylabel.cpp @@ -240,6 +241,7 @@ set( libGuiHeaders utils/rdioparser.h utils/shortenedlinkparser.h utils/dropjobnotifier.h + utils/closure.h widgets/checkdirtree.h widgets/querylabel.h diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index 7111cd68c..603d38895 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -32,6 +32,7 @@ #include "dynamic/widgets/LoadingSpinner.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include "utils/closure.h" #include "dropjob.h" #include "artist.h" #include "album.h" @@ -152,6 +153,41 @@ TrackView::setTrackModel( TrackModel* model ) } +void +TrackView::startPlayingFromStart() +{ + if ( m_proxyModel->rowCount() == 0 ) + return; + + const QModelIndex index = m_proxyModel->index( 0, 0 ); + startAutoPlay( index ); +} + + +void +TrackView::autoPlayResolveFinished( const query_ptr& query, int row ) +{ + Q_ASSERT( !query.isNull() ); + Q_ASSERT( row >= 0 ); + + if ( query.isNull() || row < 0 || query != m_autoPlaying ) + return; + + const QModelIndex index = m_proxyModel->index( row, 0 ); + if ( query->playable() ) + { + onItemActivated( index ); + return; + } + + // Try the next one.. + const QModelIndex sib = index.sibling( index.row() + 1, index.column() ); + if ( sib.isValid() ) + startAutoPlay( sib ); + +} + + void TrackView::currentChanged( const QModelIndex& current, const QModelIndex& previous ) { @@ -174,15 +210,48 @@ TrackView::onItemActivated( const QModelIndex& index ) if ( !index.isValid() ) return; + tryToPlayItem( index ); + emit itemActivated( index ); +} + + +void +TrackView::startAutoPlay( const QModelIndex& index ) +{ + if ( tryToPlayItem( index ) ) + return; + + // item isn't playable but still resolving + TrackModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); + if ( item && !item->query().isNull() && !item->query()->resolvingFinished() ) + { + m_autoPlaying = item->query(); // So we can kill it if user starts autoplaying this playlist again + NewClosure( item->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( autoPlayResolveFinished( Tomahawk::query_ptr, int ) ), + item->query(), index.row() ); + return; + } + + // not playable at all, try next + const QModelIndex sib = index.sibling( index.row() + 1, index.column() ); + if ( sib.isValid() ) + startAutoPlay( sib ); +} + + +bool +TrackView::tryToPlayItem( const QModelIndex& index ) +{ TrackModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); if ( item && !item->query().isNull() && item->query()->numResults() ) { tDebug() << "Result activated:" << item->query()->toString() << item->query()->results().first()->url(); m_proxyModel->setCurrentIndex( index ); AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), item->query()->results().first() ); + + return true; } - emit itemActivated( index ); + return false; } diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index 45bb1b23c..8e8724da1 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -64,6 +64,9 @@ public: bool updatesContextView() const { return m_updateContextView; } void setUpdatesContextView( bool b ) { m_updateContextView = b; } + // Starts playing from the beginning if resolved, or waits until a track is playable + void startPlayingFromStart(); + public slots: virtual void onItemActivated( const QModelIndex& index ); @@ -98,7 +101,11 @@ private slots: void onCustomContextMenu( const QPoint& pos ); + void autoPlayResolveFinished( const Tomahawk::query_ptr& query, int row ); + private: + void startAutoPlay( const QModelIndex& index ); + bool tryToPlayItem( const QModelIndex& index ); void updateHoverIndex( const QPoint& pos ); QString m_guid; @@ -117,6 +124,9 @@ private: QModelIndex m_hoveredIndex; QModelIndex m_contextMenuIndex; + + Tomahawk::query_ptr m_autoPlaying; + Tomahawk::ContextMenu* m_contextMenu; }; diff --git a/src/libtomahawk/utils/closure.cpp b/src/libtomahawk/utils/closure.cpp new file mode 100644 index 000000000..8a68b63e1 --- /dev/null +++ b/src/libtomahawk/utils/closure.cpp @@ -0,0 +1,89 @@ +/* This file is part of Clementine. + Copyright 2011, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "closure.h" + +namespace _detail { + +Closure::Closure(QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const ClosureArgumentWrapper* val0, + const ClosureArgumentWrapper* val1, + const ClosureArgumentWrapper* val2, + const ClosureArgumentWrapper* val3) + : QObject(receiver), + callback_(NULL), + val0_(val0), + val1_(val1), + val2_(val2), + val3_(val3) { + const QMetaObject* meta_receiver = receiver->metaObject(); + + QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1); + const int index = meta_receiver->indexOfSlot(normalised_slot.constData()); + Q_ASSERT(index != -1); + slot_ = meta_receiver->method(index); + + Connect(sender, signal); +} + +Closure::Closure(QObject* sender, + const char* signal, + std::tr1::function callback) + : callback_(callback) { + Connect(sender, signal); +} + +Closure::~Closure() { +} + +void Closure::Connect(QObject* sender, const char* signal) { + bool success = connect(sender, signal, SLOT(Invoked())); + Q_ASSERT(success); + success = connect(sender, SIGNAL(destroyed()), SLOT(Cleanup())); + Q_ASSERT(success); + Q_UNUSED(success); +} + +void Closure::Invoked() { + if (callback_) { + callback_(); + } else { + slot_.invoke( + parent(), + val0_ ? val0_->arg() : QGenericArgument(), + val1_ ? val1_->arg() : QGenericArgument(), + val2_ ? val2_->arg() : QGenericArgument(), + val3_ ? val3_->arg() : QGenericArgument()); + } + deleteLater(); +} + +void Closure::Cleanup() { + disconnect(); + deleteLater(); +} + +} // namespace _detail + +_detail::Closure* NewClosure( + QObject* sender, const char* signal, + QObject* receiver, const char* slot) { + return new _detail::Closure(sender, signal, receiver, slot); +} diff --git a/src/libtomahawk/utils/closure.h b/src/libtomahawk/utils/closure.h new file mode 100644 index 000000000..8458bbb36 --- /dev/null +++ b/src/libtomahawk/utils/closure.h @@ -0,0 +1,225 @@ +/* This file is part of Clementine. + Copyright 2011, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef CLOSURE_H +#define CLOSURE_H + +#include + +#include +#include +#include + +#include +#include + +namespace _detail { + +class ClosureArgumentWrapper { + public: + virtual ~ClosureArgumentWrapper() {} + + virtual QGenericArgument arg() const = 0; +}; + +template +class ClosureArgument : public ClosureArgumentWrapper { + public: + explicit ClosureArgument(const T& data) : data_(data) {} + + virtual QGenericArgument arg() const { + return Q_ARG(T, data_); + } + + private: + T data_; +}; + +class Closure : public QObject, boost::noncopyable { + Q_OBJECT + + public: + Closure(QObject* sender, const char* signal, + QObject* receiver, const char* slot, + const ClosureArgumentWrapper* val0 = 0, + const ClosureArgumentWrapper* val1 = 0, + const ClosureArgumentWrapper* val2 = 0, + const ClosureArgumentWrapper* val3 = 0); + + Closure(QObject* sender, const char* signal, + std::tr1::function callback); + + virtual ~Closure(); + + private slots: + void Invoked(); + void Cleanup(); + + private: + void Connect(QObject* sender, const char* signal); + + QMetaMethod slot_; + std::tr1::function callback_; + + boost::scoped_ptr val0_; + boost::scoped_ptr val1_; + boost::scoped_ptr val2_; + boost::scoped_ptr val3_; +}; + +class SharedPointerWrapper { + public: + virtual ~SharedPointerWrapper() {} + virtual QObject* data() const = 0; +}; + +template +class SharedPointer : public SharedPointerWrapper { + public: + explicit SharedPointer(QSharedPointer ptr) + : ptr_(ptr) { + } + + QObject* data() const { + return ptr_.data(); + } + + private: + QSharedPointer ptr_; +}; + +// For use with a QSharedPointer as a sender. +class SharedClosure : public Closure { + Q_OBJECT + + public: + SharedClosure(SharedPointerWrapper* sender, const char* signal, + QObject* receiver, const char* slot, + const ClosureArgumentWrapper* val0 = 0, + const ClosureArgumentWrapper* val1 = 0, + const ClosureArgumentWrapper* val2 = 0, + const ClosureArgumentWrapper* val3 = 0) + : Closure(sender->data(), signal, + receiver, slot, + val0, val1, val2, val3), + shared_sender_(sender) { + } + + private: + boost::scoped_ptr shared_sender_; +}; + +} // namespace _detail + +#define C_ARG(type, data) new _detail::ClosureArgument(data) + +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot); + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T& val0) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T, val0)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1, + const T2& val2) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1, + const T2& val2, + const T3& val3) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2), C_ARG(T3, val3)); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot, + C_ARG(T0, val0)); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1)); +} + +#endif // CLOSURE_H diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 1df14c381..3f0cb4b2e 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -24,6 +24,7 @@ #include "query.h" #include "viewmanager.h" #include "playlist/dynamic/GeneratorInterface.h" +#include "playlist/playlistview.h" #include "categoryitems.h" #include "sourceitem.h" #include "utils/tomahawkutils.h" @@ -136,6 +137,17 @@ PlaylistItem::activate() } +void +PlaylistItem::doubleClicked() +{ + ViewPage* p = ViewManager::instance()->currentPage(); + if ( PlaylistView* view = dynamic_cast< PlaylistView* >( p ) ) + { + view->startPlayingFromStart(); + } +} + + void PlaylistItem::setLoaded( bool loaded ) { diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index dad5da151..860eeffe7 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -31,7 +31,6 @@ public: virtual QString text() const; virtual Tomahawk::playlist_ptr playlist() const; virtual Qt::ItemFlags flags() const; - virtual void activate(); virtual bool willAcceptDrag( const QMimeData* data ) const; virtual DropTypes supportedDropTypes( const QMimeData* data ) const; virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action ); @@ -43,6 +42,10 @@ public: virtual SourceTreeItem* activateCurrent(); +public slots: + virtual void activate(); + virtual void doubleClicked(); + protected: void setLoaded( bool loaded ); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index adde55609..9fb0299cf 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -78,6 +78,7 @@ public: public slots: virtual void activate() {} + virtual void doubleClicked() {} signals: void updated(); diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 6cd00ce65..ccd27c673 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -43,6 +43,7 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) : QStyledItemDelegate( parent ) , m_parent( parent ) + , m_lastClicked( -1 ) { m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum ); @@ -631,7 +632,27 @@ SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QSt // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here wemake sure we only // emit if we really want to if ( event->type() == QEvent::MouseButtonRelease ) - emit clicked( index ); + { + if ( m_lastClicked == -1 ) + { + m_lastClicked = QDateTime::currentMSecsSinceEpoch(); + emit clicked( index ); + } + else + { + qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastClicked; + if ( elapsed < QApplication::doubleClickInterval() ) + { + m_lastClicked = -1; + emit doubleClicked( index ); + } else + { + m_lastClicked = QDateTime::currentMSecsSinceEpoch(); + emit clicked( index ); + } + } + + } return QStyledItemDelegate::editorEvent ( event, model, option, index ); } diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index d7a1d3ef6..051c0c0db 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -43,6 +43,7 @@ public: signals: void clicked( const QModelIndex& idx ); + void doubleClicked( const QModelIndex& idx ); void latchOn( const Tomahawk::source_ptr& idx ); void latchOff( const Tomahawk::source_ptr& idx ); void toggleRealtimeLatch( const Tomahawk::source_ptr& idx, bool realtime ); @@ -72,6 +73,7 @@ private: mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() QMap< QModelIndex, AnimationHelper* > m_expandedMap; QPixmap m_headphonesOn, m_headphonesOff, m_realtimeLocked, m_realtimeUnlocked, m_nowPlayingSpeaker, m_nowPlayingSpeakerDark; + qint64 m_lastClicked; QMap< int, SourceTreeItem::DropType > m_dropTypeMap; QMap< int, QString > m_dropTypeTextMap; diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index ced9bda00..6e1d08ddc 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -78,6 +78,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) sortByColumn( 0, Qt::AscendingOrder ); setVerticalScrollMode( QTreeView::ScrollPerPixel ); setMouseTracking( true ); + setEditTriggers( NoEditTriggers ); // TODO animation conflicts with the expanding-playlists-when-collection-is-null // so investigate @@ -88,6 +89,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) connect( m_delegate, SIGNAL( latchOff( Tomahawk::source_ptr ) ), SLOT( latchOff( Tomahawk::source_ptr ) ) ); connect( m_delegate, SIGNAL( toggleRealtimeLatch( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr,bool ) ) ); connect( m_delegate, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); + connect( m_delegate, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemDoubleClicked( QModelIndex ) ) ); setItemDelegate( m_delegate ); @@ -230,6 +232,17 @@ SourceTreeView::onItemActivated( const QModelIndex& index ) } +void +SourceTreeView::onItemDoubleClicked( const QModelIndex& idx ) +{ + if ( !selectionModel()->selectedIndexes().contains( idx ) ) + onItemActivated( idx ); + + SourceTreeItem* item = itemFromIndex< SourceTreeItem >( idx ); + item->doubleClicked(); +} + + void SourceTreeView::onItemExpanded( const QModelIndex& idx ) { diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 0d6a9b7b1..4a33f7c21 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -66,6 +66,7 @@ private slots: void selectRequest( const QPersistentModelIndex& idx ); void expandRequest( const QPersistentModelIndex& idx ); void toggleExpandRequest( const QPersistentModelIndex& idx ); + void onItemDoubleClicked( const QModelIndex& idx ); void loadPlaylist(); void deletePlaylist( const QModelIndex& = QModelIndex() );