From 3d9a1f4a917b3d4ed77d7b1784e678b4dbac726d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Feb 2011 12:06:44 +0100 Subject: [PATCH 01/10] * Added more debug output to crack a bug. --- src/libtomahawk/sourcelist.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index 20be81298..531bad488 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -86,8 +86,7 @@ SourceList::setLocal( const Tomahawk::source_ptr& localSrc ) void SourceList::add( const source_ptr& source ) { - Q_ASSERT( source->id() ); - + qDebug() << "Adding to sources:" << source->userName() << source->id(); m_sources.insert( source->userName(), source ); m_sources_id2name.insert( source->id(), source->userName() ); connect( source.data(), SIGNAL( syncedWithDatabase() ), SLOT( sourceSynced() ) ); @@ -163,6 +162,10 @@ SourceList::sourceSynced() { Source* src = qobject_cast< Source* >( sender() ); + qDebug() << "Finding in sources:" << src->userName() << src->id(); + qDebug() << "Current sources values:" << m_sources_id2name.values(); + qDebug() << "Current sources keys:" << m_sources_id2name.keys(); + Q_ASSERT( m_sources_id2name.values().contains( src->userName() ) ); m_sources_id2name.remove( m_sources_id2name.key( src->userName() ) ); m_sources_id2name.insert( src->id(), src->userName() ); From 763aad9539b2aa0dc291743df7b6d92ef1bcdce9 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 24 Feb 2011 12:50:30 +0100 Subject: [PATCH 02/10] * Potential fix for assert. --- src/libtomahawk/sourcelist.cpp | 2 +- src/libtomahawk/sourcelist.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index 531bad488..d900be28e 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -131,7 +131,7 @@ SourceList::sources( bool onlyOnline ) const source_ptr -SourceList::get( unsigned int id ) const +SourceList::get( int id ) const { QMutexLocker lock( &m_mut ); return m_sources.value( m_sources_id2name.value( id ) ); diff --git a/src/libtomahawk/sourcelist.h b/src/libtomahawk/sourcelist.h index aeabad234..8b1861476 100644 --- a/src/libtomahawk/sourcelist.h +++ b/src/libtomahawk/sourcelist.h @@ -28,7 +28,7 @@ public: unsigned int count() const; Tomahawk::source_ptr get( const QString& username, const QString& friendlyName = QString() ); - Tomahawk::source_ptr get( unsigned int id ) const; + Tomahawk::source_ptr get( int id ) const; signals: void ready(); @@ -45,7 +45,7 @@ private: void add( const Tomahawk::source_ptr& source ); QMap< QString, Tomahawk::source_ptr > m_sources; - QMap< unsigned int, QString > m_sources_id2name; + QMap< int, QString > m_sources_id2name; Tomahawk::source_ptr m_local; mutable QMutex m_mut; // mutable so const methods can use a lock From b5f52f6c0420433d03a05657ff224852c8d4ae2e Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Wed, 23 Feb 2011 01:21:31 -0500 Subject: [PATCH 03/10] Add auth support to Playdar procol handling. Needs graphic design BADLY! add part one of auth fix stage two of auth as well Fix API. --- data/www/auth.html | 64 +++++++++ data/www/auth.na.html | 44 ++++++ data/www/playdar_auth_logo.gif | Bin 0 -> 16828 bytes resources.qrc | 2 + src/libtomahawk/CMakeLists.txt | 4 + .../databasecommand_addclientauth.cpp | 46 +++++++ .../database/databasecommand_addclientauth.h | 45 ++++++ .../databasecommand_clientauthvalid.cpp | 42 ++++++ .../databasecommand_clientauthvalid.h | 49 +++++++ src/libtomahawk/database/databaseimpl.cpp | 3 +- src/libtomahawk/database/schema.sql | 12 +- src/libtomahawk/database/schema.sql.h | 12 +- src/tomahawk.protocol | 12 ++ src/web/api_v1.h | 129 +++++++++++++++++- 14 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 data/www/auth.html create mode 100644 data/www/auth.na.html create mode 100644 data/www/playdar_auth_logo.gif create mode 100644 src/libtomahawk/database/databasecommand_addclientauth.cpp create mode 100644 src/libtomahawk/database/databasecommand_addclientauth.h create mode 100644 src/libtomahawk/database/databasecommand_clientauthvalid.cpp create mode 100644 src/libtomahawk/database/databasecommand_clientauthvalid.h create mode 100644 src/tomahawk.protocol diff --git a/data/www/auth.html b/data/www/auth.html new file mode 100644 index 000000000..a8aac4c37 --- /dev/null +++ b/data/www/auth.html @@ -0,0 +1,64 @@ + + + + + Allow Tomahawk Access + + + + + Tomahawk - Powered by Playdar + + +
+
+

Allow access to Tomahawk from <%NAME%>

+

+ + +

+ + + + +
+
+ + diff --git a/data/www/auth.na.html b/data/www/auth.na.html new file mode 100644 index 000000000..450a2b114 --- /dev/null +++ b/data/www/auth.na.html @@ -0,0 +1,44 @@ + + + + + Allow Tomahawk Access + + + + + Tomahawk - Powered by Playdar + + +
+

You have allowed access to Tomahawk from <%NAME%>

+

Copy and paste this authentication token into the status bar then close this window.

+

Token: + + diff --git a/data/www/playdar_auth_logo.gif b/data/www/playdar_auth_logo.gif new file mode 100644 index 0000000000000000000000000000000000000000..22b06bfa34ac6c89cf511ca499a269fb7319dfd8 GIT binary patch literal 16828 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 M);u*62n6W;7oLyL2><{9 literal 0 HcmV?d00001 diff --git a/resources.qrc b/resources.qrc index e1bc45cf5..ea5df7969 100644 --- a/resources.qrc +++ b/resources.qrc @@ -80,5 +80,7 @@ ./data/icons/audio-x-generic-22x22.png ./data/icons/audio-x-generic-32x32.png ./data/icons/audio-x-generic-16x16.png +./data/www/auth.html +./data/www/auth.na.html diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 84afbc167..6465a3ec6 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -68,6 +68,8 @@ set( libSources database/databasecommand_loaddynamicplaylist.cpp database/databasecommand_loadalldynamicplaylists.cpp database/databasecommand_deletedynamicplaylist.cpp + database/databasecommand_addclientauth.cpp + database/databasecommand_clientauthvalid.cpp database/database.cpp playlist/collectionmodel.cpp @@ -207,6 +209,8 @@ set( libHeaders database/databasecommand_loaddynamicplaylist.h database/databasecommand_deletedynamicplaylist.h database/databasecommand_loadalldynamicplaylists.h + database/databasecommand_addclientauth.h + database/databasecommand_clientauthvalid.h network/bufferiodevice.h network/msgprocessor.h diff --git a/src/libtomahawk/database/databasecommand_addclientauth.cpp b/src/libtomahawk/database/databasecommand_addclientauth.cpp new file mode 100644 index 000000000..d8e705952 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addclientauth.cpp @@ -0,0 +1,46 @@ +/**************************************************************************************** + * Copyright (c) 2011 Leo Franchi * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program 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 * + * this program. If not, see . * + ****************************************************************************************/ + +#include "databasecommand_addclientauth.h" + +DatabaseCommand_AddClientAuth::DatabaseCommand_AddClientAuth( const QString& clientToken, + const QString& website, + const QString& name, + const QString& userAgent, + QObject* parent ) + : DatabaseCommand( parent ) + , m_clientToken( clientToken ) + , m_website( website ) + , m_name( name ) + , m_userAgent( userAgent ) +{ +} + +void DatabaseCommand_AddClientAuth::exec(DatabaseImpl* lib) +{ + TomahawkSqlQuery q = lib->newquery(); + q.prepare( "INSERT INTO http_client_auth (token, website, name, ua, mtime, permissions) VALUES (?, ?, ?, ?, ?, ?)" ); + q.addBindValue( m_clientToken ); + q.addBindValue( m_website ); + q.addBindValue( m_name ); + q.addBindValue( m_userAgent ); + q.addBindValue( 0 ); + q.addBindValue( "*" ); + + if( !q.exec() ) { + qWarning() << "Failed to insert http client into auth table!"; + } +} diff --git a/src/libtomahawk/database/databasecommand_addclientauth.h b/src/libtomahawk/database/databasecommand_addclientauth.h new file mode 100644 index 000000000..fccec5947 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_addclientauth.h @@ -0,0 +1,45 @@ +/**************************************************************************************** + * Copyright (c) 2011 Leo Franchi * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program 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 * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef DATABASECOMMAND_ADDCLIENTAUTH_H +#define DATABASECOMMAND_ADDCLIENTAUTH_H + +#include "databaseimpl.h" +#include "databasecommand.h" +#include "dllmacro.h" + +#include + +class DLLEXPORT DatabaseCommand_AddClientAuth : public DatabaseCommand +{ + Q_OBJECT +public: + explicit DatabaseCommand_AddClientAuth( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + explicit DatabaseCommand_AddClientAuth( const QString& clientToken, const QString& website, const QString& name, const QString& userAgent, QObject* parent = 0 ); + + QString commandname() const { return "addclientauth"; } + + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return true; } + +private: + QString m_clientToken, m_website, m_name, m_userAgent; +}; + +#endif // DATABASECOMMAND_ADDCLIENTAUTH_H diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.cpp b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp new file mode 100644 index 000000000..e751748bf --- /dev/null +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.cpp @@ -0,0 +1,42 @@ +/**************************************************************************************** + * Copyright (c) 2011 Leo Franchi * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program 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 * + * this program. If not, see . * + ****************************************************************************************/ + +#include "databasecommand_clientauthvalid.h" + +DatabaseCommand_ClientAuthValid::DatabaseCommand_ClientAuthValid( const QString& clientToken, QObject* parent ) + : DatabaseCommand( parent ) + , m_clientToken( clientToken ) +{ + +} + +void DatabaseCommand_ClientAuthValid::exec(DatabaseImpl* lib) +{ + TomahawkSqlQuery q = lib->newquery(); + q.prepare( "SELECT name FROM http_client_auth WHERE token = ?" ); + q.addBindValue( m_clientToken ); + + if( q.exec() ) { + if( q.next() ) { + QString name = q.value( 0 ).toString(); + emit authValid( m_clientToken, name, true ); + } else { + emit authValid( m_clientToken, QString(), false ); + } + } else { + qWarning() << "Failed to query http auth table for client:" << m_clientToken; + } +} diff --git a/src/libtomahawk/database/databasecommand_clientauthvalid.h b/src/libtomahawk/database/databasecommand_clientauthvalid.h new file mode 100644 index 000000000..efb3c7bb1 --- /dev/null +++ b/src/libtomahawk/database/databasecommand_clientauthvalid.h @@ -0,0 +1,49 @@ +/**************************************************************************************** + * Copyright (c) 2011 Leo Franchi * + * * + * This program is free software; you can redistribute it and/or modify it under * + * the terms of the GNU General Public License as published by the Free Software * + * Foundation; either version 2 of the License, or (at your option) any later * + * version. * + * * + * This program 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 * + * this program. If not, see . * + ****************************************************************************************/ + +#ifndef DATABASECOMMAND_CLIENTAUTHVALID_H +#define DATABASECOMMAND_CLIENTAUTHVALID_H + +#include "databaseimpl.h" +#include "databasecommand.h" +#include "dllmacro.h" + +#include + +class DLLEXPORT DatabaseCommand_ClientAuthValid : public DatabaseCommand +{ + Q_OBJECT +public: + explicit DatabaseCommand_ClientAuthValid( QObject* parent = 0 ) + : DatabaseCommand( parent ) + {} + + explicit DatabaseCommand_ClientAuthValid( const QString& clientToken, QObject* parent = 0 ); + + QString commandname() const { return "clientauthvalid"; } + + virtual void exec( DatabaseImpl* lib ); + virtual bool doesMutates() const { return false; } + +signals: + // if auth is invalid name is empty + void authValid( const QString& clientToken, const QString& name, bool valid ); + +private: + QString m_clientToken; +}; + +#endif // DATABASECOMMAND_CLIENTAUTHVALID_H diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index 7746f8a0f..5652dfae8 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -16,8 +16,7 @@ */ #include "schema.sql.h" -#define CURRENT_SCHEMA_VERSION 20 - +#define CURRENT_SCHEMA_VERSION 21 DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) : QObject( (QObject*) parent ) diff --git a/src/libtomahawk/database/schema.sql b/src/libtomahawk/database/schema.sql index ad8b000d1..c30132ab9 100644 --- a/src/libtomahawk/database/schema.sql +++ b/src/libtomahawk/database/schema.sql @@ -241,6 +241,16 @@ CREATE TABLE IF NOT EXISTS playback_log ( CREATE INDEX playback_log_source ON playback_log(source); CREATE INDEX playback_log_track ON playback_log(track); +-- auth information for http clients + +CREATE TABLE IF NOT EXISTS http_client_auth ( + token TEXT NOT NULL PRIMARY KEY, + website TEXT NOT NULL, + name TEXT NOT NULL, + ua TEXT, + mtime INTEGER, + permissions TEXT NOT NULL +); -- Schema version, and misc tomahawk settings relating to the collection db @@ -250,4 +260,4 @@ CREATE TABLE IF NOT EXISTS settings ( v TEXT NOT NULL DEFAULT '' ); -INSERT INTO settings(k,v) VALUES('schema_version', '20'); +INSERT INTO settings(k,v) VALUES('schema_version', '21'); diff --git a/src/libtomahawk/database/schema.sql.h b/src/libtomahawk/database/schema.sql.h index a580c1851..9283091f3 100644 --- a/src/libtomahawk/database/schema.sql.h +++ b/src/libtomahawk/database/schema.sql.h @@ -1,5 +1,5 @@ /* - This file was automatically generated from ./schema.sql on Wed Feb 23 12:39:07 CET 2011. + This file was automatically generated from schema.sql on Thu Feb 24 19:05:46 EST 2011. */ static const char * tomahawk_schema_sql = @@ -161,11 +161,19 @@ static const char * tomahawk_schema_sql = ");" "CREATE INDEX playback_log_source ON playback_log(source);" "CREATE INDEX playback_log_track ON playback_log(track);" +"CREATE TABLE IF NOT EXISTS http_client_auth (" +" token TEXT NOT NULL PRIMARY KEY," +" website TEXT NOT NULL," +" name TEXT NOT NULL," +" ua TEXT," +" mtime INTEGER," +" permissions TEXT NOT NULL" +");" "CREATE TABLE IF NOT EXISTS settings (" " k TEXT NOT NULL PRIMARY KEY," " v TEXT NOT NULL DEFAULT ''" ");" -"INSERT INTO settings(k,v) VALUES('schema_version', '20');" +"INSERT INTO settings(k,v) VALUES('schema_version', '21');" ; const char * get_tomahawk_sql() diff --git a/src/tomahawk.protocol b/src/tomahawk.protocol new file mode 100644 index 000000000..3a393aa61 --- /dev/null +++ b/src/tomahawk.protocol @@ -0,0 +1,12 @@ +[Protocol] +exec=/home/leo/kde/tomahawk/build/tomahawk "%u" +protocol=tomahawk +input=none +output=none +helper=true +listing= +reading=false +writing=false +makedir=false +deleting=false + diff --git a/src/web/api_v1.h b/src/web/api_v1.h index 6d89beddd..daaacc4fb 100644 --- a/src/web/api_v1.h +++ b/src/web/api_v1.h @@ -17,8 +17,15 @@ #include #include +#include #include "network/servent.h" +#include "tomahawkutils.h" +#include "tomahawk/tomahawkapp.h" +#include +#include +#include +#include class Api_v1 : public QxtWebSlotService { @@ -32,7 +39,81 @@ public: } public slots: + + // authenticating uses /auth_1 + // we redirect to /auth_2 for the callback + void auth_1( QxtWebRequestEvent* event ) { + qDebug() << "AUTH_1 HTTP" << event->url.toString(); + + if( !event->url.hasQueryItem( "website" ) || !event->url.hasQueryItem( "name" ) ) { + qDebug() << "Malformed HTTP resolve request"; + send404( event ); + } + + QString formToken = uuid(); + + if( event->url.hasQueryItem( "json" ) ) { // JSON response + QVariantMap m; + m[ "formtoken" ] = formToken; + sendJSON( m, event ); + } else { // webpage request + QString authPage = RESPATH "www/auth.html"; + QHash< QString, QString > args; + if( event->url.hasQueryItem( "receiverurl" ) ) + args[ "url" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "receiverurl" ).toUtf8() ); + args[ "formtoken" ] = formToken; + args[ "website" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "website" ).toUtf8() ); + args[ "name" ] = QUrl::fromPercentEncoding( event->url.queryItemValue( "name" ).toUtf8() ); + sendWebpageWithArgs( event, authPage, args ); + } + } + + void auth_2( QxtWebRequestEvent* event ) { + + qDebug() << "AUTH_2 HTTP" << event->url.toString(); + QUrl url = event->url; + url.setEncodedQuery( event->content->readAll() ); + if( !url.hasQueryItem( "website" ) || !url.hasQueryItem( "name" ) || !url.hasQueryItem( "formtoken" ) ) { + qDebug() << "Malformed HTTP resolve request"; + qDebug() << url.hasQueryItem( "website" ) << url.hasQueryItem( "name" ) << url.hasQueryItem( "formtoken" ); + send404( event ); + return; + } + + QString website = QUrl::fromPercentEncoding( url.queryItemValue( "website" ).toUtf8() ); + QString name = QUrl::fromPercentEncoding( url.queryItemValue( "name" ).toUtf8() ); + QByteArray authtoken = uuid().toLatin1(); + qDebug() << "HEADERS:" << event->headers; + if( !url.hasQueryItem( "receiverurl" ) && url.queryItemValue( "receiverurl" ).isEmpty() ) { //no receiver url, so do it ourselves + QString receiverUrl = QUrl::fromPercentEncoding( url.queryItemValue( "receiverurl" ).toUtf8() ); + if( url.hasQueryItem( "json" ) ) { + QVariantMap m; + m[ "authtoken" ] = authtoken; + + sendJSON( m, event ); + } else { + QString authPage = RESPATH "www/auth.na.html"; + QHash< QString, QString > args; + args[ "authcode" ] = authPage; + args[ "website" ] = QUrl::fromPercentEncoding( url.queryItemValue( "website" ).toUtf8() ); + args[ "name" ] = QUrl::fromPercentEncoding( url.queryItemValue( "name" ).toUtf8() ); + sendWebpageWithArgs( event, authPage, args ); + } + } else { // do what the client wants + QUrl receiverurl = QUrl( url.queryItemValue( "receiverurl" ).toUtf8(), QUrl::TolerantMode ); + receiverurl.addEncodedQueryItem( "authtoken", "#" + authtoken ); + qDebug() << "Got receiver url:" << receiverurl.toString(); + + QxtWebRedirectEvent* e = new QxtWebRedirectEvent( event->sessionID, event->requestID, receiverurl.toString() ); + postEvent( e ); + // TODO validation of receiverurl? + } + + DatabaseCommand_AddClientAuth* dbcmd = new DatabaseCommand_AddClientAuth( authtoken, website, name, event->headers.key( "ua" ) ); + Database::instance()->enqueue( QSharedPointer(dbcmd) ); + } + // all v1 api calls go to /api/ void api(QxtWebRequestEvent* event) { @@ -80,18 +161,35 @@ public slots: qDebug() << "404" << event->url.toString(); QxtWebPageEvent* wpe = new QxtWebPageEvent(event->sessionID, event->requestID, "

Not Found

"); wpe->status = 404; - wpe->statusMessage = "not found"; + wpe->statusMessage = "not feventound"; postEvent( wpe ); } void stat( QxtWebRequestEvent* event ) { + qDebug() << "Got Stat request:" << event->url.toString(); + m_storedEvent = event; + if( !event->content.isNull() ) + qDebug() << "BODY:" << event->content->readAll(); + if( event->url.hasQueryItem( "auth" ) ) { // check for auth status + DatabaseCommand_ClientAuthValid* dbcmd = new DatabaseCommand_ClientAuthValid( event->url.queryItemValue( "auth" ), this ); + connect( dbcmd, SIGNAL( authValid( QString, QString, bool ) ), this, SLOT( statResult( QString, QString, bool ) ) ); + Database::instance()->enqueue( QSharedPointer(dbcmd) ); + + } else { + statResult( QString(), QString(), false ); + } + } + + void statResult( const QString& clientToken, const QString& name, bool valid ) { QVariantMap m; m.insert( "name", "playdar" ); m.insert( "version", "0.1.1" ); // TODO (needs to be >=0.1.1 for JS to work) - m.insert( "authenticated", true ); // TODO + m.insert( "authenticated", valid ); // TODO m.insert( "capabilities", QVariantList() ); - sendJSON( m, event ); + sendJSON( m, m_storedEvent ); + + m_storedEvent = 0; } void resolve( QxtWebRequestEvent* event ) @@ -119,6 +217,12 @@ public slots: sendJSON( r, event ); } + void staticdata( QxtWebRequestEvent* event ) { + if( event->url.path().contains( "playdar_auth_logo.gif" ) ) { + // TODO handle + } + } + void get_results( QxtWebRequestEvent* event ) { if( !event->url.hasQueryItem("qid") ) @@ -174,6 +278,23 @@ public slots: qDebug() << "JSON response" << event->url.toString() << body; } + // load an html template from a file, replace args from map + // then serve + void sendWebpageWithArgs( QxtWebRequestEvent* event, const QString& filenameSource, const QHash< QString, QString >& args ) { + if( !QFile::exists( filenameSource ) ) + qWarning() << "Passed invalid file for html source:" << filenameSource; + + QFile f( filenameSource ); + f.open( QIODevice::ReadOnly ); + QByteArray html = f.readAll(); + + foreach( const QString& param, args.keys() ) { + html.replace( QString( "<%%1%>" ).arg( param.toUpper() ), args.value( param ).toUtf8() ); + } + + QxtWebPageEvent* e = new QxtWebPageEvent( event->sessionID, event->requestID, html ); + postEvent( e ); + } void index(QxtWebRequestEvent* event) { @@ -182,6 +303,8 @@ public slots: } +private: + QxtWebRequestEvent* m_storedEvent; }; #endif From 4d1afb74bf4d804b801cd6ad0a37be4cf57c9464 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 25 Feb 2011 22:36:11 +0100 Subject: [PATCH 04/10] * Fixed SourceList assert. Fixed filesize calculation. --- src/libtomahawk/network/dbsyncconnection.cpp | 2 +- src/libtomahawk/source.h | 4 ++-- src/libtomahawk/sourcelist.cpp | 19 ++++++------------- src/libtomahawk/utils/tomahawkutils.cpp | 2 +- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 3172a5252..1ce79484d 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -195,7 +195,7 @@ DBSyncConnection::handleMsg( msg_ptr msg ) QVariantMap m = msg->json().toMap(); if ( m.empty() ) { - qDebug() << "Failed to parse msg in dbsync"; + qDebug() << "Failed to parse msg in dbsync" << m_source->id() << m_source->friendlyName(); Q_ASSERT( false ); return; } diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index 3e598c4aa..f1719cfce 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -42,7 +42,7 @@ public: void addCollection( const Tomahawk::collection_ptr& c ); void removeCollection( const Tomahawk::collection_ptr& c ); - unsigned int id() const { return m_id; } + int id() const { return m_id; } ControlConnection* controlConnection() const { return m_cc; } void setControlConnection( ControlConnection* cc ); @@ -90,7 +90,7 @@ private: bool m_isLocal; bool m_online; QString m_username, m_friendlyname; - unsigned int m_id; + int m_id; QList< QSharedPointer > m_collections; QVariantMap m_stats; QString m_lastOpGuid; diff --git a/src/libtomahawk/sourcelist.cpp b/src/libtomahawk/sourcelist.cpp index d900be28e..45a2c74ce 100644 --- a/src/libtomahawk/sourcelist.cpp +++ b/src/libtomahawk/sourcelist.cpp @@ -88,7 +88,9 @@ SourceList::add( const source_ptr& source ) { qDebug() << "Adding to sources:" << source->userName() << source->id(); m_sources.insert( source->userName(), source ); - m_sources_id2name.insert( source->id(), source->userName() ); + + if ( source->id() > 0 ) + m_sources_id2name.insert( source->id(), source->userName() ); connect( source.data(), SIGNAL( syncedWithDatabase() ), SLOT( sourceSynced() ) ); collection_ptr coll( new RemoteCollection( source ) ); @@ -101,14 +103,11 @@ SourceList::add( const source_ptr& source ) void SourceList::removeAllRemote() { - foreach( source_ptr s, m_sources ) + foreach( const source_ptr& s, m_sources ) { - if( s != m_local ) + if ( !s->isLocal() && s->controlConnection() ) { - if ( s->controlConnection() ) - { - s->controlConnection()->shutdown( true ); - } + s->controlConnection()->shutdown( true ); } } } @@ -162,12 +161,6 @@ SourceList::sourceSynced() { Source* src = qobject_cast< Source* >( sender() ); - qDebug() << "Finding in sources:" << src->userName() << src->id(); - qDebug() << "Current sources values:" << m_sources_id2name.values(); - qDebug() << "Current sources keys:" << m_sources_id2name.keys(); - - Q_ASSERT( m_sources_id2name.values().contains( src->userName() ) ); - m_sources_id2name.remove( m_sources_id2name.key( src->userName() ) ); m_sources_id2name.insert( src->id(), src->userName() ); } diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 45238a542..1b5db86fe 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -205,7 +205,7 @@ filesizeToString( unsigned int size ) if ( mb ) { - return QString( "%1.%2 Mb" ).arg( mb ).arg( int( ( kb % 1024 ) / 100 ) ); + return QString( "%1.%2 Mb" ).arg( mb ).arg( int( ( kb % 1024 ) / 102.4 ) ); } else if ( kb ) { From fa6a2533d0476bf9f36e9e1012fa13222b4c2995 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 26 Feb 2011 01:21:27 +0100 Subject: [PATCH 05/10] * Scanning status should have priority over now-playing information. --- src/libtomahawk/source.cpp | 6 +-- src/sourcetree/sourcesmodel.cpp | 14 ++++--- src/sourcetree/sourcetreeview.cpp | 66 +++++++++++++++---------------- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index e6400344f..0c24deb26 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -159,7 +159,7 @@ Source::scanningProgress( unsigned int files ) void Source::scanningFinished( unsigned int files ) { - m_textStatus = tr( "Online" ); + m_textStatus = QString(); emit stateChanged(); } @@ -183,14 +183,14 @@ Source::onStateChanged( DBSyncConnection::State newstate, DBSyncConnection::Stat msg = tr( "Saving" ); break; case DBSyncConnection::SYNCED: - msg = tr( "Online" ); + msg = QString(); break; case DBSyncConnection::SCANNING: msg = tr( "Scanning (%L1 tracks)" ).arg( info ); break; default: - msg = "???"; + msg = QString(); } m_textStatus = msg; diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index 3a8cf60ce..a84c7da7a 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -124,13 +124,15 @@ SourcesModel::appendItem( const source_ptr& source ) // qDebug() << "Appending source item:" << item->source()->username(); invisibleRootItem()->appendRow( item->columns() ); -// m_parent->setIndexWidget( m_parent->model()->index( rowCount() - 1, 0 ), item->widget() ); - connect( source.data(), SIGNAL( offline() ), SLOT( onSourceChanged() ) ); - connect( source.data(), SIGNAL( online() ), SLOT( onSourceChanged() ) ); - connect( source.data(), SIGNAL( stats( QVariantMap ) ), SLOT( onSourceChanged() ) ); - connect( source.data(), SIGNAL( playbackStarted( Tomahawk::query_ptr ) ), SLOT( onSourceChanged() ) ); - connect( source.data(), SIGNAL( stateChanged() ), SLOT( onSourceChanged() ) ); + if ( !source.isNull() ) + { + connect( source.data(), SIGNAL( offline() ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( online() ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( stats( QVariantMap ) ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( playbackStarted( Tomahawk::query_ptr ) ), SLOT( onSourceChanged() ) ); + connect( source.data(), SIGNAL( stateChanged() ), SLOT( onSourceChanged() ) ); + } return true; // FIXME } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index dbee09d92..916d725a0 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -469,7 +469,7 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co QString desc = status ? sti->source()->textStatus() : tr( "Offline" ); if ( sti->source().isNull() ) desc = tr( "All available tracks" ); - if ( status && !sti->source()->currentTrack().isNull() ) + if ( status && desc.isEmpty() && !sti->source()->currentTrack().isNull() ) desc = sti->source()->currentTrack()->artist() + " - " + sti->source()->currentTrack()->track(); if ( desc.isEmpty() ) desc = tr( "Online" ); @@ -479,41 +479,39 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); painter->drawText( textRect, text ); - if ( !status ) + if ( status ) { - painter->restore(); - return; + painter->setRenderHint( QPainter::Antialiasing ); + + QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 18, 0, -10, -o.rect.height() + 16 ); + int hd = ( option.rect.height() - figRect.height() ) / 2; + figRect.adjust( 0, hd, 0, hd ); + + QColor figColor( 167, 183, 211 ); + painter->setPen( figColor ); + painter->setBrush( figColor ); + + QPen origpen = painter->pen(); + QPen pen = origpen; + pen.setWidth( 1.0 ); + painter->setPen( pen ); + painter->drawRect( figRect ); + + QPainterPath ppath; + ppath.moveTo( QPoint( figRect.x(), figRect.y() ) ); + ppath.quadTo( QPoint( figRect.x() - 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x(), figRect.y() + figRect.height() ) ); + painter->drawPath( ppath ); + ppath.moveTo( QPoint( figRect.x() + figRect.width(), figRect.y() ) ); + ppath.quadTo( QPoint( figRect.x() + figRect.width() + 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x() + figRect.width(), figRect.y() + figRect.height() ) ); + painter->drawPath( ppath ); + + painter->setPen( origpen ); + + QTextOption to( Qt::AlignCenter ); + painter->setFont( bold ); + painter->setPen( Qt::white ); + painter->drawText( figRect, tracks, to ); } - painter->setRenderHint( QPainter::Antialiasing ); - - QRect figRect = o.rect.adjusted( o.rect.width() - figWidth - 18, 0, -10, -o.rect.height() + 16 ); - int hd = ( option.rect.height() - figRect.height() ) / 2; - figRect.adjust( 0, hd, 0, hd ); - - QColor figColor( 167, 183, 211 ); - painter->setPen( figColor ); - painter->setBrush( figColor ); - - QPen origpen = painter->pen(); - QPen pen = origpen; - pen.setWidth( 1.0 ); - painter->setPen( pen ); - painter->drawRect( figRect ); - - QPainterPath ppath; - ppath.moveTo( QPoint( figRect.x(), figRect.y() ) ); - ppath.quadTo( QPoint( figRect.x() - 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x(), figRect.y() + figRect.height() ) ); - painter->drawPath( ppath ); - ppath.moveTo( QPoint( figRect.x() + figRect.width(), figRect.y() ) ); - ppath.quadTo( QPoint( figRect.x() + figRect.width() + 8, figRect.y() + figRect.height() / 2 ), QPoint( figRect.x() + figRect.width(), figRect.y() + figRect.height() ) ); - painter->drawPath( ppath ); - - painter->setPen( origpen ); - - QTextOption to( Qt::AlignCenter ); - painter->setFont( bold ); - painter->setPen( Qt::white ); - painter->drawText( figRect, tracks, to ); painter->restore(); } From f9c4eac734a94d01e6f38468d9f19faf114a5d5f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sat, 26 Feb 2011 03:11:58 +0100 Subject: [PATCH 06/10] * Added home button to toolbar and linked it to PlaylistManager's showWelcomePage(). --- data/images/home.png | Bin 0 -> 3682 bytes resources.qrc | 1 + src/libtomahawk/network/controlconnection.cpp | 20 ++++++---------- src/libtomahawk/network/dbsyncconnection.cpp | 2 +- src/libtomahawk/network/servent.cpp | 4 ++-- src/libtomahawk/playlist/infobar/infobar.cpp | 4 +++- src/libtomahawk/playlist/playlistmanager.cpp | 22 ++++++++++++++---- src/libtomahawk/playlist/playlistmanager.h | 12 ++++++---- src/tomahawkwindow.cpp | 13 ++++++----- 9 files changed, 46 insertions(+), 32 deletions(-) create mode 100644 data/images/home.png diff --git a/data/images/home.png b/data/images/home.png new file mode 100644 index 0000000000000000000000000000000000000000..e36f187f6c60028b52c601eaba6dcb04834f8b8f GIT binary patch literal 3682 zcmV-o4xRCdP)ZRg!)jg~ zssf1!VNeAWsZ`y+Kp+Sp@Bm>^ftOyn_H;Mm-%Pi0QiwT>n8e}wRu6AaTYd2leqrH@ z2#g)7>YkIY3BU66Nz4c_Hq^)eT}HCDfmi6BA5gL2r;J$+{-xf5O88a&-_jhLHzSf z&b;(bE8!-F4B~_rV~2lTn40>{-~Hsw#)t&~IEUI#UQ z#?k^p0cZwJIoiv?pZgqS^|7nTqYoztOg`sVOmg7t!S>59!;Pc@eDNsEv%MJWTMmKA zXCqSnhtJu|7?A*0E`7i2^F9TtZxWbmcL|xPxMA2fb$n`$_Lsvp>e1HP2k!F z`#HlN;Bzm&dWdj;x^KpQVy0~lkjO-Y(+D&M=X5jA%x^~S`t{BD%g)GeS!4L;pZWOv zp8x3Lj5R`RLxR{zSOYf;1w_Cc0chfM|99Wo`02m>@}+4z+p*8iwvB%OQn$C!b*^C9 zGlf8t*g^(EVx}W9-8TMr^DztI!q?8;+S-a#W2aDL(+b}~&?tHiU1UHXR*0!K36TTV zIdreACy6Sc$fs?IKy!#O+tuL2u4&7=$;b*Bi4bKlRDebyic*BPTL2||8}S3^0Yr9GfZze{ zF9dsS0V0u)&;lMn0SG0sB)USP3}M&>HwopQs(QZy+#o@Zq5y^|piz{ybyekd?yVKM zE}#e{2^FBJRA1aVb==OqHNsU8EK`gS_Z{=If^AlBIox+>nE)svT(cr(l@yj4K|*oA zUAaV^39fTAp)pa@s2vAjw`L|EEWn#@#-_>q%sEc!%mL!=OW=B5Qd@#GZV zTuJb)^AR?CdVdRm5F@f|w|^l>q2PXmKHh)G;$w>z9n0M5hCrWFk60X=w|MPB4?nyT zL(xnKK@s#A;Z>_DOEfGsfl#7pJTy)Gs4w@A>=SU0qb+?*S>VSWYvVC5oNv4l;rgbc zage0~5HU-34(&@36EP(wP!)|qxZT&S4+CLQvUeYUVmF8P8~p6)Y5dQ{7~g#-M&g>3 z|MCiubXrSPRlM-mtJi0zJGY7j;EtjJ9NH&%?oo?#DvTXEkgI&IJ z>Lhu9T_#X(a+`v2-x2qO{nPB5s)KVCOkQnfu)NB2Eb!xxwea|1i?5uGuzE`;!2?i$ zTki@$xK4s(z5u8gD@K5)j#@l%)Ii7zC46kj8@@6ig~XBl#Lqv|!FS&FCh@KDUss?M z?sO=E^*aJE#5DpUgsbDO@WT&Df1xwi;TTGBwdV}~`pkWf2>r&=kBkS6uLeC_Tyx{B z0L)w9I)FxSediRgN`fBbK@y{;)8F44-z!bcr^%_%GrSNXsHz>MI`M?3hzQi89a zi?P*HjGDq+stU=~9UcJJK#;t*Hw1MP#W&8!{mA3f&vihKs^s(Dl>GD$s=!};bql2s zBZmbQ00f1){B8>nxhhZ*7W53F7E}RBg{9B9Cx!|_=xKd-Q?sB5C_#u^d$$Epud=WL zC`r_UQ$Sdj1P+(rF^R-E1?v(3m7Y9t2t}{n5r7r9q!i{>03TAn08&_Kc>@mN8HN-> zQey=W<_p{rB=yQ20YpiAsohG7Od#w0$=8JBC#s4;WR)ZiKXN7xwL$jD^#|?PUcMuM z-}@Q6@+be6^%>y`qEIGx) zIr-s7&YZ`cqDBu;FqR79mwwI0y98i}{7b@$`b^+}{}_+a8{Pvj!@s!N1R!};JIysx z?9R)BB-?rA#8XoR7};FjhlSpOBmFvw14vGw2hc$Q2-y`uJEsp*Tzyjj_u}L$_jitA zJe(j8AfN~`QDv3@3OoSSM~wm)s^s&%^>@NEoM}v4ogN^+Zws=F_MFoByu3NkA|Bn69unioL>5Ue~EeEXdkue}|k3ePNBoIY-mQfZ2C zsuMW|f>{CpdnAhtMU5VSBRS31u(Bz$h5+B&;>al{(gaGT018@w5FqOG0M*AgFhc+W z5Y?~(NLBx_(*vL$g!u+kC3_6KRd5NBwVFVVpqCS{rn)I>$$MWLJFUq)m*;G2KmbN` z9b~2egj%ZyU{xcQn@mE8sx6>2*<}6>Lx>tE%$Y#+dPW7{DvGWiP-|WU4ZjXHQoRCL zDH33@IZbL@1%tW%gCGY5V8~Sk1VoaxR&=$kf)!--@=pxXs@wwd+Xl(fubC(&ZxyT{ zg=-mm0KGd3}GJ#hKP?_l{sde5G z#8FxK$&YO zYO4SQGfsbnQE<31u)@{Ex~G8hoC(0K1WN+~c;c|Q*RLc!&G7(}w+XCn2JH}IAW?<- z1waBh6~KMwv}|O)eLRIdf>~m!w*pjI`4*Jqbfl|31`Z`&)@T8ke<3Kv-u<8gt-%*l zsrRnnA@9;H=;vd2zr-5H!1A0C=;jK*5M7rc01#523k1IKT7*$SRO9bnjW7&EO%Ot2 z00Z=L1wcVh6uVGWA%O7K!OeqEA?dIKK~W0;y5v9^GvN)Rnu!8N2$%K4q4G>%lqAGX zVyBX5P(Txj9pr8U)s_MQc%s;07rg|MI0TwTnmZ7?DG~rP1)!$q!du2Q5lPfga2Evv zP+}u?paPmYKz3s%y-)(02`LoWbvF;CU<_1;b*EsQC;+9Zve!#Yd|Ln-DnX;(E=FO* zLs9{%5keI=G6g`-zkDxRcg~nztWMR54|aR8buJY^$bT~+`Xo*)g@zRM;b_UkxtnB* z>bWBTdH|SnXJ7y4gQuSS<@L=tR_Ba!PPPRA4cTv`3g|4zmWVitBnPIfee>KKuK=(l zWA)I-Z2?UAIQWm>TKo5RHrhv`UQFkluLj8s0*$3*&4!31?UvQ!v(c4LJ#*kE0WAC2 zRFBu-I+oLl zu+K+3{aAt!8kKmL<}?EO&VM=0?X6T4diNOE^Re#Z9e7ePEclpBD?uw%gGQbDo{z0G z;n#dzPIJ57e=LQ!dptd9l4pGE_i@0-ypOr`@7n3(5(F^O0&r=9BOjaT>0kA+=Hq&L zuzTv+C4imdV4CcXkLffzJ1T%6Fv++b6`<>5GmUO4ibOpF72qxrFio_%9l--M(gP$r z!uinK$&gO zZ AegFUf literal 0 HcmV?d00001 diff --git a/resources.qrc b/resources.qrc index ea5df7969..fe25dfcaa 100644 --- a/resources.qrc +++ b/resources.qrc @@ -70,6 +70,7 @@ ./data/images/volume-slider-level.png ./data/images/echonest_logo.png ./data/images/loading-animation.gif +./data/images/home.png ./data/topbar-radiobuttons.css ./data/icons/tomahawk-icon-16x16.png ./data/icons/tomahawk-icon-32x32.png diff --git a/src/libtomahawk/network/controlconnection.cpp b/src/libtomahawk/network/controlconnection.cpp index d2cf61004..cb7fcb647 100644 --- a/src/libtomahawk/network/controlconnection.cpp +++ b/src/libtomahawk/network/controlconnection.cpp @@ -104,7 +104,7 @@ ControlConnection::registerSource() void ControlConnection::setupDbSyncConnection( bool ondemand ) { - if( m_dbsyncconn != NULL || ! m_registered ) + if( m_dbsyncconn != NULL || !m_registered ) return; qDebug() << Q_FUNC_INFO << ondemand << m_source->id(); @@ -115,12 +115,6 @@ ControlConnection::setupDbSyncConnection( bool ondemand ) qDebug() << "Connecting to DBSync offer from peer..."; m_dbsyncconn = new DBSyncConnection( m_servent, m_source ); - connect( m_dbsyncconn, SIGNAL( finished() ), - m_dbsyncconn, SLOT( deleteLater() ) ); - - connect( m_dbsyncconn, SIGNAL( destroyed( QObject* ) ), - SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); - m_servent->createParallelConnection( this, m_dbsyncconn, m_dbconnkey ); m_dbconnkey.clear(); } @@ -129,12 +123,6 @@ ControlConnection::setupDbSyncConnection( bool ondemand ) qDebug() << "Offering a DBSync key to peer..."; m_dbsyncconn = new DBSyncConnection( m_servent, m_source ); - connect( m_dbsyncconn, SIGNAL( finished() ), - m_dbsyncconn, SLOT( deleteLater()) ); - - connect( m_dbsyncconn, SIGNAL( destroyed(QObject* ) ), - SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); - QString key = uuid(); m_servent->registerOffer( key, m_dbsyncconn ); QVariantMap m; @@ -142,6 +130,12 @@ ControlConnection::setupDbSyncConnection( bool ondemand ) m.insert( "key", key ); sendMsg( m ); } + + connect( m_dbsyncconn, SIGNAL( finished() ), + m_dbsyncconn, SLOT( deleteLater() ) ); + + connect( m_dbsyncconn, SIGNAL( destroyed( QObject* ) ), + SLOT( dbSyncConnFinished( QObject* ) ), Qt::DirectConnection ); } diff --git a/src/libtomahawk/network/dbsyncconnection.cpp b/src/libtomahawk/network/dbsyncconnection.cpp index 1ce79484d..334fe86d9 100644 --- a/src/libtomahawk/network/dbsyncconnection.cpp +++ b/src/libtomahawk/network/dbsyncconnection.cpp @@ -92,7 +92,7 @@ DBSyncConnection::trigger() qDebug() << Q_FUNC_INFO; // if we're still setting up the connection, do nothing - we sync on first connect anyway: - if ( !this->isRunning() ) + if ( !isRunning() ) return; QMetaObject::invokeMethod( this, "sendMsg", Qt::QueuedConnection, diff --git a/src/libtomahawk/network/servent.cpp b/src/libtomahawk/network/servent.cpp index e3eb71a9e..24407f087 100644 --- a/src/libtomahawk/network/servent.cpp +++ b/src/libtomahawk/network/servent.cpp @@ -707,8 +707,8 @@ Servent::triggerDBSync() QList sources = SourceList::instance()->sources(); foreach( const source_ptr& src, sources ) { - // local src doesnt have a control connection, skip it: - if( src.isNull() || src->isLocal() ) + // skip local source + if ( src.isNull() || src->isLocal() ) continue; if ( src->controlConnection() ) // source online? diff --git a/src/libtomahawk/playlist/infobar/infobar.cpp b/src/libtomahawk/playlist/infobar/infobar.cpp index bb2121e09..8ebd5893f 100644 --- a/src/libtomahawk/playlist/infobar/infobar.cpp +++ b/src/libtomahawk/playlist/infobar/infobar.cpp @@ -7,6 +7,8 @@ #include "utils/tomahawkutils.h" +#define IMAGE_HEIGHT 64 + InfoBar::InfoBar( QWidget* parent ) : QWidget( parent ) @@ -58,7 +60,7 @@ InfoBar::setDescription( const QString& s ) void InfoBar::setPixmap( const QPixmap& p ) { - ui->imageLabel->setPixmap( p.scaledToHeight( ui->imageLabel->height(), Qt::SmoothTransformation ) ); + ui->imageLabel->setPixmap( p.scaledToHeight( IMAGE_HEIGHT, Qt::SmoothTransformation ) ); } diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 6b5fac73a..40ee565d3 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -7,6 +7,7 @@ #include "infobar/infobar.h" #include "topbar/topbar.h" #include "widgets/infowidgets/sourceinfowidget.h" +#include "widgets/welcomewidget.h" #include "collectionmodel.h" #include "collectionflatmodel.h" @@ -42,6 +43,7 @@ PlaylistManager::instance() PlaylistManager::PlaylistManager( QObject* parent ) : QObject( parent ) , m_widget( new QWidget() ) + , m_welcomeWidget( new WelcomeWidget() ) , m_currentInterface( 0 ) , m_currentMode( 0 ) , m_superCollectionVisible( true ) @@ -100,7 +102,7 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_widget->layout()->setContentsMargins( 0, 0, 0, 0 ); m_widget->layout()->setMargin( 0 ); m_widget->layout()->setSpacing( 0 ); - + connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); connect( m_topbar, SIGNAL( filterTextChanged( QString ) ), @@ -424,14 +426,16 @@ PlaylistManager::show( QWidget* widget, const QString& title, const QString& des { unlinkPlaylist(); - connect( widget, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); + if ( m_stack->indexOf( widget ) < 0 ) + { + connect( widget, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); + m_stack->addWidget( widget ); + } - m_stack->addWidget( widget ); m_stack->setCurrentWidget( widget ); - m_infobar->setCaption( title ); m_infobar->setDescription( desc ); - m_infobar->setPixmap( pixmap ); + m_infobar->setPixmap( pixmap.isNull() ? QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ) : pixmap ); m_queueView->show(); m_superCollectionVisible = false; @@ -483,6 +487,14 @@ PlaylistManager::showSuperCollection() } +void +PlaylistManager::showWelcomePage() +{ + qDebug() << Q_FUNC_INFO; + show( m_welcomeWidget, tr( "Welcome to Tomahawk!" ) ); +} + + void PlaylistManager::setTableMode() { diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index 0c327e494..3923b6f4c 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -25,6 +25,7 @@ class TrackView; class SourceInfoWidget; class InfoBar; class TopBar; +class WelcomeWidget; namespace Tomahawk { class DynamicWidget; @@ -56,9 +57,6 @@ public: bool show( QWidget* widget, const QString& title = QString(), const QString& desc = QString(), const QPixmap& pixmap = QPixmap() ); - bool showSuperCollection(); - void showCurrentTrack(); - signals: void numSourcesChanged( unsigned int sources ); void numTracksChanged( unsigned int tracks ); @@ -73,7 +71,12 @@ signals: void playClicked(); void pauseClicked(); + public slots: + bool showSuperCollection(); + void showWelcomePage(); + void showCurrentTrack(); + void setTreeMode(); void setTableMode(); void setAlbumMode(); @@ -115,7 +118,8 @@ private: AlbumModel* m_superAlbumModel; AlbumView* m_superAlbumView; CollectionFlatModel* m_superCollectionFlatModel; - CollectionView* m_superCollectionView; + CollectionView* m_superCollectionView; + WelcomeWidget* m_welcomeWidget; QList< Tomahawk::collection_ptr > m_superCollections; diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 0767af108..b873accac 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -29,7 +29,6 @@ #include "utils/widgetdragfilter.h" #include "utils/xspfloader.h" #include "widgets/newplaylistwidget.h" -#include "widgets/welcomewidget.h" #include "audiocontrols.h" #include "settingsdialog.h" @@ -115,12 +114,15 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) ui->splitter->setCollapsible( 1, false ); ui->splitter->setHandleWidth( 1 ); -/* QToolBar* toolbar = addToolBar( "TomahawkToolbar" ); + QToolBar* toolbar = addToolBar( "TomahawkToolbar" ); toolbar->setObjectName( "TomahawkToolbar" ); - toolbar->addWidget( m_topbar ); toolbar->setMovable( false ); toolbar->setFloatable( false ); - toolbar->installEventFilter( new WidgetDragFilter( toolbar ) );*/ + toolbar->setIconSize( QSize( 32, 32 ) ); + toolbar->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); + + toolbar->addAction( QIcon( RESPATH "images/home.png" ), tr( "Home" ), PlaylistManager::instance(), SLOT( showWelcomePage() ) ); statusBar()->addPermanentWidget( m_audioControls, 1 ); @@ -135,8 +137,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) loadSettings(); setupSignals(); - - PlaylistManager::instance()->show( new WelcomeWidget(), tr( "Welcome to Tomahawk!" ), QString(), QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ) ); + PlaylistManager::instance()->showWelcomePage(); } From 83ec026b34e5f17298eaf3c72ae8a62f0b4f0165 Mon Sep 17 00:00:00 2001 From: Leo Franchi Date: Sat, 26 Feb 2011 15:26:04 -0500 Subject: [PATCH 07/10] shrink font size on osx --- src/tomahawkapp.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 45b4053c8..4d68b8aa7 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -175,6 +175,10 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] ) Tomahawk::setShortcutHandler( static_cast( m_shortcutHandler) ); Tomahawk::setApplicationHandler( this ); + + QFont f( QApplication::font() ); + f.setPointSize( f.pointSize() - 2 ); + QApplication::setFont( f ); #endif // Connect up shortcuts From 844a455824fed9ca78e670dd6e8cbfbb3fde3ad3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 27 Feb 2011 03:04:58 +0100 Subject: [PATCH 08/10] * Added ViewPage, ViewPage-history and more consistent sidebar behaviour. --- data/images/back.png | Bin 0 -> 9960 bytes data/images/forward.png | Bin 0 -> 9668 bytes resources.qrc | 2 + src/libtomahawk/CMakeLists.txt | 3 +- src/libtomahawk/playlist/albummodel.cpp | 4 + src/libtomahawk/playlist/albummodel.h | 11 +- src/libtomahawk/playlist/albumproxymodel.h | 3 +- src/libtomahawk/playlist/albumview.cpp | 2 - src/libtomahawk/playlist/albumview.h | 23 +- .../playlist/collectionflatmodel.cpp | 5 + .../playlist/collectionproxymodel.h | 2 + src/libtomahawk/playlist/collectionview.cpp | 8 +- src/libtomahawk/playlist/collectionview.h | 15 +- .../playlist/dynamic/DynamicView.h | 2 +- .../dynamic/widgets/DynamicWidget.cpp | 6 + .../playlist/dynamic/widgets/DynamicWidget.h | 21 +- src/libtomahawk/playlist/playlistmanager.cpp | 536 ++++++++++-------- src/libtomahawk/playlist/playlistmanager.h | 47 +- src/libtomahawk/playlist/playlistmodel.cpp | 4 +- src/libtomahawk/playlist/playlistview.cpp | 8 +- src/libtomahawk/playlist/playlistview.h | 13 +- src/libtomahawk/playlist/topbar/topbar.cpp | 68 ++- src/libtomahawk/playlist/topbar/topbar.h | 7 + src/libtomahawk/playlist/trackmodel.h | 8 + src/libtomahawk/playlist/trackview.cpp | 1 - src/libtomahawk/playlistinterface.h | 10 +- src/libtomahawk/viewpage.cpp | 6 + src/libtomahawk/viewpage.h | 38 ++ .../widgets/infowidgets/sourceinfowidget.cpp | 2 + .../widgets/infowidgets/sourceinfowidget.h | 16 +- src/libtomahawk/widgets/newplaylistwidget.h | 13 +- src/libtomahawk/widgets/newplaylistwidget.ui | 23 +- src/libtomahawk/widgets/welcomewidget.h | 13 +- src/sip/jabber/jabber_p.cpp | 2 +- src/sourcetree/sourcesmodel.cpp | 72 ++- src/sourcetree/sourcesmodel.h | 4 + src/sourcetree/sourcetreeitem.cpp | 4 +- src/sourcetree/sourcetreeview.cpp | 68 ++- src/sourcetree/sourcetreeview.h | 6 + src/tomahawkwindow.cpp | 20 +- src/tomahawkwindow.h | 7 +- 41 files changed, 786 insertions(+), 317 deletions(-) create mode 100644 data/images/back.png create mode 100644 data/images/forward.png create mode 100644 src/libtomahawk/viewpage.cpp create mode 100644 src/libtomahawk/viewpage.h diff --git a/data/images/back.png b/data/images/back.png new file mode 100644 index 0000000000000000000000000000000000000000..88db1dc23691f750f221ed8ef117214a42a1a0a6 GIT binary patch literal 9960 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 P|6|Zl(NV5ZL`VD|&wewg literal 0 HcmV?d00001 diff --git a/data/images/forward.png b/data/images/forward.png new file mode 100644 index 0000000000000000000000000000000000000000..3e0c424763c163dbc633d437d87e99bd8136f205 GIT binary patch literal 9668 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 literal 0 HcmV?d00001 diff --git a/resources.qrc b/resources.qrc index fe25dfcaa..fc3705795 100644 --- a/resources.qrc +++ b/resources.qrc @@ -71,6 +71,8 @@ ./data/images/echonest_logo.png ./data/images/loading-animation.gif ./data/images/home.png +./data/images/back.png +./data/images/forward.png ./data/topbar-radiobuttons.css ./data/icons/tomahawk-icon-16x16.png ./data/icons/tomahawk-icon-32x32.png diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 6465a3ec6..d92d048ff 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -24,10 +24,10 @@ set( libSources query.cpp result.cpp source.cpp + viewpage.cpp sip/SipPlugin.cpp - audio/madtranscode.cpp audio/vorbistranscode.cpp audio/flactranscode.cpp @@ -161,6 +161,7 @@ set( libHeaders resolver.h result.h source.h + viewpage.h artist.h album.h diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index f22d389d8..c7efad856 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -225,6 +225,8 @@ AlbumModel::addCollection( const collection_ptr& collection ) SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All albums from %1" ).arg( collection->source()->friendlyName() ); } @@ -245,6 +247,8 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in SLOT( onAlbumsAdded( QList, Tomahawk::collection_ptr ) ) ); Database::instance()->enqueue( QSharedPointer( cmd ) ); + + m_title = tr( "All albums by %1" ).arg( collection->source()->friendlyName() ); } diff --git a/src/libtomahawk/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h index 445538b86..f25724ad4 100644 --- a/src/libtomahawk/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -40,9 +40,6 @@ public: virtual void removeIndex( const QModelIndex& index ); virtual void removeIndexes( const QList& indexes ); - virtual PlaylistInterface::RepeatMode repeatMode() const { return PlaylistInterface::NoRepeat; } - virtual bool shuffled() const { return false; } - virtual QMimeData* mimeData( const QModelIndexList& indexes ) const; virtual QStringList mimeTypes() const; virtual Qt::ItemFlags flags( const QModelIndex& index ) const; @@ -50,6 +47,11 @@ public: void addCollection( const Tomahawk::collection_ptr& collection ); void addFilteredCollection( const Tomahawk::collection_ptr& collection, unsigned int amount, DatabaseCommand_AllAlbums::SortOrder order ); + virtual QString title() const { return m_title; } + virtual QString description() const { return m_description; } + virtual void setTitle( const QString& title ) { m_title = title; } + virtual void setDescription( const QString& description ) { m_description = description; } + AlbumItem* itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) @@ -81,6 +83,9 @@ private: QPersistentModelIndex m_currentIndex; AlbumItem* m_rootItem; QPixmap m_defaultCover; + + QString m_title; + QString m_description; }; #endif // ALBUMMODEL_H diff --git a/src/libtomahawk/playlist/albumproxymodel.h b/src/libtomahawk/playlist/albumproxymodel.h index d87c855db..3e694e2a6 100644 --- a/src/libtomahawk/playlist/albumproxymodel.h +++ b/src/libtomahawk/playlist/albumproxymodel.h @@ -33,7 +33,8 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const { return m_repeatMode; } virtual bool shuffled() const { return m_shuffled; } - + virtual PlaylistInterface::ViewMode viewMode() const { return PlaylistInterface::Album; } + signals: void repeatModeChanged( PlaylistInterface::RepeatMode mode ); void shuffleModeChanged( bool enabled ); diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 9e954cc7f..83ecb28b9 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -10,8 +10,6 @@ #include "tomahawksettings.h" #include "albumitemdelegate.h" -#include "albummodel.h" -#include "albumproxymodel.h" #include "playlistmanager.h" using namespace Tomahawk; diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h index a2445971f..d4616cd3c 100644 --- a/src/libtomahawk/playlist/albumview.h +++ b/src/libtomahawk/playlist/albumview.h @@ -4,12 +4,13 @@ #include #include +#include "albummodel.h" +#include "albumproxymodel.h" +#include "viewpage.h" + #include "dllmacro.h" -class AlbumModel; -class AlbumProxyModel; - -class DLLEXPORT AlbumView : public QListView +class DLLEXPORT AlbumView : public QListView, public Tomahawk::ViewPage { Q_OBJECT @@ -19,12 +20,22 @@ public: void setProxyModel( AlbumProxyModel* model ); - AlbumModel* model() { return m_model; } - AlbumProxyModel* proxyModel() { return m_proxyModel; } + AlbumModel* model() const { return m_model; } + AlbumProxyModel* proxyModel() const { return m_proxyModel; } // PlaylistItemDelegate* delegate() { return m_delegate; } void setModel( AlbumModel* model ); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return m_model->title(); } + virtual QString description() const { return m_model->description(); } + + virtual bool showModes() const { return true; } + + virtual bool jumpToCurrentTrack() { return false; } + public slots: void onItemActivated( const QModelIndex& index ); diff --git a/src/libtomahawk/playlist/collectionflatmodel.cpp b/src/libtomahawk/playlist/collectionflatmodel.cpp index 54c4bdc04..6e63feeba 100644 --- a/src/libtomahawk/playlist/collectionflatmodel.cpp +++ b/src/libtomahawk/playlist/collectionflatmodel.cpp @@ -60,6 +60,11 @@ CollectionFlatModel::addCollection( const collection_ptr& collection ) SLOT( onTracksAdded( QList, Tomahawk::collection_ptr ) ) ); connect( collection.data(), SIGNAL( tracksFinished( Tomahawk::collection_ptr ) ), SLOT( onTracksAddingFinished( Tomahawk::collection_ptr ) ) ); + + if ( collection->source()->isLocal() ) + setTitle( tr( "Your Collection" ) ); + else + setTitle( tr( "Collection of %1" ).arg( collection->source()->friendlyName() ) ); } diff --git a/src/libtomahawk/playlist/collectionproxymodel.h b/src/libtomahawk/playlist/collectionproxymodel.h index e6ea9311d..af46be8c4 100644 --- a/src/libtomahawk/playlist/collectionproxymodel.h +++ b/src/libtomahawk/playlist/collectionproxymodel.h @@ -12,6 +12,8 @@ Q_OBJECT public: explicit CollectionProxyModel( QObject* parent = 0 ); + virtual PlaylistInterface::ViewMode viewMode() const { return PlaylistInterface::Flat; } + protected: bool lessThan( const QModelIndex& left, const QModelIndex& right ) const; }; diff --git a/src/libtomahawk/playlist/collectionview.cpp b/src/libtomahawk/playlist/collectionview.cpp index dc1490595..09b557611 100644 --- a/src/libtomahawk/playlist/collectionview.cpp +++ b/src/libtomahawk/playlist/collectionview.cpp @@ -45,7 +45,6 @@ CollectionView::setModel( TrackModel* model ) void CollectionView::dragEnterEvent( QDragEnterEvent* event ) { - qDebug() << Q_FUNC_INFO; event->ignore(); } @@ -94,3 +93,10 @@ CollectionView::onTrackCountChanged( unsigned int tracks ) else overlay()->hide(); } + + +bool +CollectionView::jumpToCurrentTrack() +{ + scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); +} diff --git a/src/libtomahawk/playlist/collectionview.h b/src/libtomahawk/playlist/collectionview.h index 9fd3f3483..cddd1e98c 100644 --- a/src/libtomahawk/playlist/collectionview.h +++ b/src/libtomahawk/playlist/collectionview.h @@ -3,11 +3,14 @@ #include +#include "trackproxymodel.h" +#include "trackmodel.h" #include "trackview.h" +#include "viewpage.h" #include "dllmacro.h" -class DLLEXPORT CollectionView : public TrackView +class DLLEXPORT CollectionView : public TrackView, public Tomahawk::ViewPage { Q_OBJECT @@ -17,6 +20,16 @@ public: virtual void setModel( TrackModel* model ); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return model()->title(); } + virtual QString description() const { return model()->description(); } + + virtual bool showModes() const { return true; } + + virtual bool jumpToCurrentTrack(); + private slots: void onCustomContextMenu( const QPoint& pos ); void onTrackCountChanged( unsigned int tracks ); diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h index d7ad8103e..26a63e4f1 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.h +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -45,7 +45,7 @@ public: void setDynamicWorking( bool working ); - virtual void paintEvent(QPaintEvent* event); + virtual void paintEvent( QPaintEvent* event ); public slots: void showMessageTimeout( const QString& title, const QString& body ); diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index 8bea35d94..20ec27727 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -386,3 +386,9 @@ DynamicWidget::paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qre p.setPen( pen ); p.drawRoundedRect( r, 10, 10 ); } + +bool +DynamicWidget::jumpToCurrentTrack() +{ + m_view->scrollTo( m_view->proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h index 17d31d882..e8653b2b7 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.h @@ -20,8 +20,11 @@ #include #include "typedefs.h" +#include "viewpage.h" + #include "dynamic/DynamicPlaylist.h" #include "dynamic/DynamicControl.h" +#include "dynamic/DynamicModel.h" class LoadingSpinner; class QShowEvent; @@ -43,20 +46,15 @@ namespace Tomahawk class DynamicSetupWidget; - -class DynamicModel; - - class DynamicView; - class CollapsibleControls; /** * This class contains the dynamic playlist config and the playlist view itself */ -class DynamicWidget : public QWidget +class DynamicWidget : public QWidget, public Tomahawk::ViewPage { Q_OBJECT public: @@ -65,7 +63,7 @@ public: void loadDynamicPlaylist( const dynplaylist_ptr& playlist ); - PlaylistInterface* playlistInterface() const; + virtual PlaylistInterface* playlistInterface() const; virtual QSize sizeHint() const; virtual void resizeEvent( QResizeEvent* ); @@ -73,6 +71,14 @@ public: virtual void showEvent(QShowEvent* ); static void paintRoundedFilledRect( QPainter& p, QPalette& pal, QRect& r, qreal opacity = .95 ); + + virtual QWidget* widget() { return this; } + + virtual QString title() const { return m_model->title(); } + virtual QString description() const { return m_model->description(); } + + virtual bool jumpToCurrentTrack(); + public slots: void onRevisionLoaded( const Tomahawk::DynamicPlaylistRevision& rev ); void playlistTypeChanged(QString); @@ -94,6 +100,7 @@ private slots: void controlChanged( const Tomahawk::dyncontrol_ptr& control ); void layoutFloatingWidgets(); + private: dynplaylist_ptr m_playlist; QVBoxLayout* m_layout; diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index 40ee565d3..c2425aa78 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -30,6 +30,8 @@ #define FILTER_TIMEOUT 280 +using namespace Tomahawk; + PlaylistManager* PlaylistManager::s_instance = 0; @@ -44,14 +46,12 @@ PlaylistManager::PlaylistManager( QObject* parent ) : QObject( parent ) , m_widget( new QWidget() ) , m_welcomeWidget( new WelcomeWidget() ) - , m_currentInterface( 0 ) , m_currentMode( 0 ) , m_superCollectionVisible( true ) - , m_statsAvailable( false ) - , m_modesAvailable( false ) { s_instance = this; + setHistoryPosition( -1 ); m_widget->setLayout( new QVBoxLayout() ); m_topbar = new TopBar(); @@ -91,11 +91,8 @@ PlaylistManager::PlaylistManager( QObject* parent ) m_superAlbumView = new AlbumView(); m_superAlbumModel = new AlbumModel( m_superAlbumView ); m_superAlbumView->setModel( m_superAlbumModel ); - - m_stack->addWidget( m_superCollectionView ); - m_stack->addWidget( m_superAlbumView ); - - m_currentInterface = m_superCollectionView->proxyModel(); + m_superAlbumView->setFrameShape( QFrame::NoFrame ); + m_superAlbumView->setAttribute( Qt::WA_MacShowFocusRect, 0 ); m_stack->setContentsMargins( 0, 0, 0, 0 ); m_widget->setContentsMargins( 0, 0, 0, 0 ); @@ -106,34 +103,16 @@ PlaylistManager::PlaylistManager( QObject* parent ) connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); connect( m_topbar, SIGNAL( filterTextChanged( QString ) ), - this, SLOT( setFilter( QString ) ) ); - - connect( this, SIGNAL( numSourcesChanged( unsigned int ) ), - m_topbar, SLOT( setNumSources( unsigned int ) ) ); - - connect( this, SIGNAL( numTracksChanged( unsigned int ) ), - m_topbar, SLOT( setNumTracks( unsigned int ) ) ); - - connect( this, SIGNAL( numArtistsChanged( unsigned int ) ), - m_topbar, SLOT( setNumArtists( unsigned int ) ) ); - - connect( this, SIGNAL( numShownChanged( unsigned int ) ), - m_topbar, SLOT( setNumShown( unsigned int ) ) ); + SLOT( setFilter( QString ) ) ); connect( m_topbar, SIGNAL( flatMode() ), - this, SLOT( setTableMode() ) ); - + SLOT( setTableMode() ) ); + connect( m_topbar, SIGNAL( artistMode() ), - this, SLOT( setTreeMode() ) ); - + SLOT( setTreeMode() ) ); + connect( m_topbar, SIGNAL( albumMode() ), - this, SLOT( setAlbumMode() ) ); - - connect( this, SIGNAL( statsAvailable( bool ) ), - m_topbar, SLOT( setStatsVisible( bool ) ) ); - - connect( this, SIGNAL( modesAvailable( bool ) ), - m_topbar, SLOT( setModesVisible( bool ) ) ); + SLOT( setAlbumMode() ) ); } @@ -153,11 +132,10 @@ PlaylistManager::queue() const bool PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) { - unlinkPlaylist(); - + PlaylistView* view; if ( !m_playlistViews.contains( playlist ) ) { - PlaylistView* view = new PlaylistView(); + view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); view->setModel( model ); view->setFrameShape( QFrame::NoFrame ); @@ -165,27 +143,15 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) model->loadPlaylist( playlist ); playlist->resolve(); - m_currentInterface = view->proxyModel(); m_playlistViews.insert( playlist, view ); - - m_stack->addWidget( view ); - m_stack->setCurrentWidget( view ); } else { - PlaylistView* view = m_playlistViews.value( playlist ); - m_stack->setCurrentWidget( view ); - m_currentInterface = view->proxyModel(); + view = m_playlistViews.value( playlist ); } - m_queueView->show(); - m_infobar->setCaption( playlist->title() ); - m_infobar->setDescription( tr( "A playlist by %1" ).arg( playlist->author()->isLocal() ? tr( "you" ) : playlist->author()->friendlyName() ) ); - + setPage( view ); m_superCollectionVisible = false; - m_statsAvailable = true; - m_modesAvailable = false; - linkPlaylist(); TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); @@ -197,34 +163,25 @@ PlaylistManager::show( const Tomahawk::playlist_ptr& playlist ) bool PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) { - unlinkPlaylist(); - - if( !m_dynamicWidgets.contains( playlist ) ) + if ( !m_dynamicWidgets.contains( playlist ) ) { m_dynamicWidgets[ playlist ] = new Tomahawk::DynamicWidget( playlist, m_stack ); - m_stack->addWidget( m_dynamicWidgets[ playlist ] ); + playlist->resolve(); } - m_stack->setCurrentWidget( m_dynamicWidgets.value( playlist ) ); - m_currentInterface = m_dynamicWidgets.value( playlist )->playlistInterface(); + setPage( m_dynamicWidgets.value( playlist ) ); - m_infobar->setCaption( playlist->title() ); - m_infobar->setDescription( tr( "A playlist by %1" ).arg( playlist->author()->isLocal() ? tr( "you" ) : playlist->author()->friendlyName() ) ); - - if( playlist->mode() == Tomahawk::OnDemand ) + if ( playlist->mode() == Tomahawk::OnDemand ) m_queueView->hide(); - + else + m_queueView->show(); m_superCollectionVisible = false; - m_statsAvailable = true; - m_modesAvailable = false; - linkPlaylist(); TomahawkSettings::instance()->appendRecentlyPlayedPlaylist( playlist ); emit numSourcesChanged( SourceList::instance()->count() ); - return true; } @@ -232,39 +189,26 @@ PlaylistManager::show( const Tomahawk::dynplaylist_ptr& playlist ) bool PlaylistManager::show( const Tomahawk::artist_ptr& artist ) { - qDebug() << Q_FUNC_INFO << &artist << artist.data(); - unlinkPlaylist(); + PlaylistView* view; if ( !m_artistViews.contains( artist ) ) { - PlaylistView* view = new PlaylistView(); + view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); view->setModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->append( artist ); - m_currentInterface = view->proxyModel(); m_artistViews.insert( artist, view ); - - m_stack->addWidget( view ); - m_stack->setCurrentWidget( view ); } else { - PlaylistView* view = m_artistViews.value( artist ); - m_stack->setCurrentWidget( view ); - m_currentInterface = view->proxyModel(); + view = m_artistViews.value( artist ); } - m_queueView->show(); - m_infobar->setCaption( tr( "All tracks by %1" ).arg( artist->name() ) ); - m_infobar->setDescription( "" ); - + setPage( view ); m_superCollectionVisible = false; - m_statsAvailable = false; - m_modesAvailable = false; - linkPlaylist(); emit numSourcesChanged( 1 ); return true; @@ -274,39 +218,25 @@ PlaylistManager::show( const Tomahawk::artist_ptr& artist ) bool PlaylistManager::show( const Tomahawk::album_ptr& album ) { - qDebug() << Q_FUNC_INFO << &album << album.data(); - unlinkPlaylist(); - + PlaylistView* view; if ( !m_albumViews.contains( album ) ) { - PlaylistView* view = new PlaylistView(); + view = new PlaylistView(); PlaylistModel* model = new PlaylistModel(); view->setModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->append( album ); - m_currentInterface = view->proxyModel(); m_albumViews.insert( album, view ); - - m_stack->addWidget( view ); - m_stack->setCurrentWidget( view ); } else { - PlaylistView* view = m_albumViews.value( album ); - m_stack->setCurrentWidget( view ); - m_currentInterface = view->proxyModel(); + view = m_albumViews.value( album ); } - m_queueView->show(); - m_infobar->setCaption( tr( "All tracks on %1 by %2" ).arg( album->name() ).arg( album->artist()->name() ) ); - m_infobar->setDescription( "" ); - + setPage( view ); m_superCollectionVisible = false; - m_statsAvailable = false; - m_modesAvailable = false; - linkPlaylist(); emit numSourcesChanged( 1 ); return true; @@ -316,70 +246,52 @@ PlaylistManager::show( const Tomahawk::album_ptr& album ) bool PlaylistManager::show( const Tomahawk::collection_ptr& collection ) { - unlinkPlaylist(); - m_currentCollection = collection; if ( m_currentMode == 0 ) { + CollectionView* view; if ( !m_collectionViews.contains( collection ) ) { - CollectionView* view = new CollectionView(); + view = new CollectionView(); CollectionFlatModel* model = new CollectionFlatModel(); view->setModel( model ); view->setFrameShape( QFrame::NoFrame ); view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); model->addCollection( collection ); - m_currentInterface = view->proxyModel(); m_collectionViews.insert( collection, view ); - - m_stack->addWidget( view ); - m_stack->setCurrentWidget( view ); } else { - CollectionView* view = m_collectionViews.value( collection ); - m_stack->setCurrentWidget( view ); - m_currentInterface = view->proxyModel(); + view = m_collectionViews.value( collection ); } + + setPage( view ); } if ( m_currentMode == 2 ) { + AlbumView* aview; if ( !m_collectionAlbumViews.contains( collection ) ) { - AlbumView* aview = new AlbumView(); + aview = new AlbumView(); AlbumModel* amodel = new AlbumModel( aview ); aview->setModel( amodel ); aview->setFrameShape( QFrame::NoFrame ); aview->setAttribute( Qt::WA_MacShowFocusRect, 0 ); amodel->addCollection( collection ); - m_currentInterface = aview->proxyModel(); m_collectionAlbumViews.insert( collection, aview ); - - m_stack->addWidget( aview ); - m_stack->setCurrentWidget( aview ); } else { - AlbumView* view = m_collectionAlbumViews.value( collection ); - m_stack->setCurrentWidget( view ); - m_currentInterface = view->proxyModel(); + aview = m_collectionAlbumViews.value( collection ); } + + setPage( aview ); } - m_infobar->setDescription( "" ); - if ( collection->source()->isLocal() ) - m_infobar->setCaption( tr( "Your Collection" ) ); - else - m_infobar->setCaption( tr( "Collection of %1" ).arg( collection->source()->friendlyName() ) ); - - m_queueView->show(); m_superCollectionVisible = false; - m_statsAvailable = ( m_currentMode == 0 ); - m_modesAvailable = true; - linkPlaylist(); emit numSourcesChanged( 1 ); return true; @@ -389,32 +301,19 @@ PlaylistManager::show( const Tomahawk::collection_ptr& collection ) bool PlaylistManager::show( const Tomahawk::source_ptr& source ) { - unlinkPlaylist(); - - m_currentInterface = 0; - + SourceInfoWidget* swidget; if ( !m_sourceViews.contains( source ) ) { - SourceInfoWidget* swidget = new SourceInfoWidget( source ); - m_currentInfoWidget = swidget; - m_stack->addWidget( m_currentInfoWidget ); + swidget = new SourceInfoWidget( source ); m_sourceViews.insert( source, swidget ); } else { - m_currentInfoWidget = m_sourceViews.value( source ); + swidget = m_sourceViews.value( source ); } - m_infobar->setCaption( tr( "Info about %1" ).arg( source->isLocal() ? tr( "Your Collection" ) : source->friendlyName() ) ); - m_infobar->setDescription( "" ); - - m_queueView->show(); - m_stack->setCurrentWidget( m_currentInfoWidget ); + setPage( swidget ); m_superCollectionVisible = false; - m_statsAvailable = false; - m_modesAvailable = false; - - linkPlaylist(); emit numSourcesChanged( 1 ); return true; @@ -422,28 +321,15 @@ PlaylistManager::show( const Tomahawk::source_ptr& source ) bool -PlaylistManager::show( QWidget* widget, const QString& title, const QString& desc, const QPixmap& pixmap ) +PlaylistManager::show( ViewPage* page ) { - unlinkPlaylist(); - - if ( m_stack->indexOf( widget ) < 0 ) + if ( m_stack->indexOf( page->widget() ) < 0 ) { - connect( widget, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); - m_stack->addWidget( widget ); + connect( page->widget(), SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) ); } - m_stack->setCurrentWidget( widget ); - m_infobar->setCaption( title ); - m_infobar->setDescription( desc ); - m_infobar->setPixmap( pixmap.isNull() ? QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ) : pixmap ); - - m_queueView->show(); + setPage( page ); m_superCollectionVisible = false; - m_statsAvailable = false; - m_modesAvailable = false; - m_currentInterface = 0; - - linkPlaylist(); return true; } @@ -460,27 +346,21 @@ PlaylistManager::showSuperCollection() m_superCollectionFlatModel->addCollection( source->collection() ); m_superAlbumModel->addCollection( source->collection() ); } + + m_superCollectionFlatModel->setTitle( tr( "All available tracks" ) ); + m_superAlbumModel->setTitle( tr( "All available albums" ) ); } if ( m_currentMode == 0 ) { - m_stack->setCurrentWidget( m_superCollectionView ); - m_currentInterface = m_superCollectionView->proxyModel(); + setPage( m_superCollectionView ); } else if ( m_currentMode == 2 ) { - m_stack->setCurrentWidget( m_superAlbumView ); - m_currentInterface = m_superAlbumView->proxyModel(); + setPage( m_superAlbumView ); } - m_infobar->setCaption( tr( "Super Collection" ) ); - m_infobar->setDescription( tr( "All available tracks" ) ); - - m_queueView->show(); m_superCollectionVisible = true; - m_statsAvailable = ( m_currentMode == 0 ); - m_modesAvailable = true; - linkPlaylist(); emit numSourcesChanged( m_superCollections.count() ); return true; @@ -490,8 +370,7 @@ PlaylistManager::showSuperCollection() void PlaylistManager::showWelcomePage() { - qDebug() << Q_FUNC_INFO; - show( m_welcomeWidget, tr( "Welcome to Tomahawk!" ) ); + show( m_welcomeWidget ); } @@ -545,8 +424,7 @@ PlaylistManager::showQueue() if ( QThread::currentThread() != thread() ) { qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; - QMetaObject::invokeMethod( this, "showQueue", - Qt::QueuedConnection ); + QMetaObject::invokeMethod( this, "showQueue", Qt::QueuedConnection ); return; } @@ -560,8 +438,7 @@ PlaylistManager::hideQueue() if ( QThread::currentThread() != thread() ) { qDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; - QMetaObject::invokeMethod( this, "hideQueue", - Qt::QueuedConnection ); + QMetaObject::invokeMethod( this, "hideQueue", Qt::QueuedConnection ); return; } @@ -569,6 +446,42 @@ PlaylistManager::hideQueue() } +void +PlaylistManager::historyBack() +{ + if ( m_historyPosition < 1 ) + return; + + showHistory( m_historyPosition - 1 ); +} + + +void +PlaylistManager::historyForward() +{ + if ( m_historyPosition >= m_pageHistory.count() - 1 ) + return; + + showHistory( m_historyPosition + 1 ); +} + + +void +PlaylistManager::showHistory( int historyPosition ) +{ + if ( historyPosition < 0 || historyPosition >= m_pageHistory.count() ) + { + qDebug() << "History position out of bounds!" << historyPosition << m_pageHistory.count(); + Q_ASSERT( false ); + return; + } + + setHistoryPosition( historyPosition ); + ViewPage* page = m_pageHistory.at( historyPosition ); + setPage( page, false ); +} + + void PlaylistManager::setFilter( const QString& filter ) { @@ -586,67 +499,111 @@ PlaylistManager::applyFilter() { qDebug() << Q_FUNC_INFO; - if ( m_currentInterface && m_currentInterface->filter() != m_filter ) - m_currentInterface->setFilter( m_filter ); + if ( currentPlaylistInterface() && currentPlaylistInterface()->filter() != m_filter ) + currentPlaylistInterface()->setFilter( m_filter ); +} + + +void +PlaylistManager::setPage( ViewPage* page, bool trackHistory ) +{ + unlinkPlaylist(); + + if ( !m_pageHistory.contains( page ) ) + { + m_stack->addWidget( page->widget() ); + } + else + { + if ( trackHistory ) + m_pageHistory.removeAll( page ); + } + + if ( trackHistory ) + { + m_pageHistory << page; + setHistoryPosition( m_pageHistory.count() - 1 ); + } + + if ( playlistForInterface( currentPlaylistInterface() ) ) + emit playlistActivated( playlistForInterface( currentPlaylistInterface() ) ); + if ( dynamicPlaylistForInterface( currentPlaylistInterface() ) ) + emit dynamicPlaylistActivated( dynamicPlaylistForInterface( currentPlaylistInterface() ) ); + if ( collectionForInterface( currentPlaylistInterface() ) ) + emit collectionActivated( collectionForInterface( currentPlaylistInterface() ) ); + if ( !currentPlaylistInterface() ) + emit tempPageActivated(); + + m_stack->setCurrentWidget( page->widget() ); + updateView(); } void PlaylistManager::unlinkPlaylist() { - if ( m_currentInterface ) + if ( currentPlaylistInterface() ) { - disconnect( m_currentInterface->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), - this, SIGNAL( numTracksChanged( unsigned int ) ) ); + disconnect( currentPlaylistInterface()->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), + this, SIGNAL( numTracksChanged( unsigned int ) ) ); - disconnect( m_currentInterface->object(), SIGNAL( trackCountChanged( unsigned int ) ), - this, SIGNAL( numShownChanged( unsigned int ) ) ); + disconnect( currentPlaylistInterface()->object(), SIGNAL( trackCountChanged( unsigned int ) ), + this, SIGNAL( numShownChanged( unsigned int ) ) ); - disconnect( m_currentInterface->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), - this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); + disconnect( currentPlaylistInterface()->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), + this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); - disconnect( m_currentInterface->object(), SIGNAL( shuffleModeChanged( bool ) ), - this, SIGNAL( shuffleModeChanged( bool ) ) ); + disconnect( currentPlaylistInterface()->object(), SIGNAL( shuffleModeChanged( bool ) ), + this, SIGNAL( shuffleModeChanged( bool ) ) ); } } void -PlaylistManager::linkPlaylist() +PlaylistManager::updateView() { - if ( m_currentInterface ) + if ( currentPlaylistInterface() ) { - connect( m_currentInterface->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), - this, SIGNAL( numTracksChanged( unsigned int ) ) ); + connect( currentPlaylistInterface()->object(), SIGNAL( sourceTrackCountChanged( unsigned int ) ), + SIGNAL( numTracksChanged( unsigned int ) ) ); - connect( m_currentInterface->object(), SIGNAL( trackCountChanged( unsigned int ) ), - this, SIGNAL( numShownChanged( unsigned int ) ) ); + connect( currentPlaylistInterface()->object(), SIGNAL( trackCountChanged( unsigned int ) ), + SIGNAL( numShownChanged( unsigned int ) ) ); - connect( m_currentInterface->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), - this, SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); + connect( currentPlaylistInterface()->object(), SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ), + SIGNAL( repeatModeChanged( PlaylistInterface::RepeatMode ) ) ); - connect( m_currentInterface->object(), SIGNAL( shuffleModeChanged( bool ) ), - this, SIGNAL( shuffleModeChanged( bool ) ) ); + connect( currentPlaylistInterface()->object(), SIGNAL( shuffleModeChanged( bool ) ), + SIGNAL( shuffleModeChanged( bool ) ) ); - m_interfaceHistory.removeAll( m_currentInterface ); - m_interfaceHistory << m_currentInterface; + m_topbar->setFilter( currentPlaylistInterface()->filter() ); } - AudioEngine::instance()->setPlaylist( m_currentInterface ); - - if ( m_currentInterface && m_statsAvailable ) + if ( currentPage()->showStatsBar() && currentPlaylistInterface() ) { - m_topbar->setFilter( m_currentInterface->filter() ); - - emit numTracksChanged( m_currentInterface->unfilteredTrackCount() ); - emit numShownChanged( m_currentInterface->trackCount() ); - emit repeatModeChanged( m_currentInterface->repeatMode() ); - emit shuffleModeChanged( m_currentInterface->shuffled() ); - + emit numTracksChanged( currentPlaylistInterface()->unfilteredTrackCount() ); + emit numShownChanged( currentPlaylistInterface()->trackCount() ); + emit repeatModeChanged( currentPlaylistInterface()->repeatMode() ); + emit shuffleModeChanged( currentPlaylistInterface()->shuffled() ); + emit modeChanged( currentPlaylistInterface()->viewMode() ); } - emit statsAvailable( m_statsAvailable ); - emit modesAvailable( m_modesAvailable ); + if ( currentPage()->queueVisible() ) + m_queueView->show(); + else + m_queueView->hide(); + + emit statsAvailable( currentPage()->showStatsBar() ); + emit modesAvailable( currentPage()->showModes() ); + + if ( !currentPage()->showStatsBar() && !currentPage()->showModes() ) + m_topbar->setVisible( false ); + else + m_topbar->setVisible( true ); + + m_infobar->setCaption( currentPage()->title() ); + m_infobar->setDescription( currentPage()->description() ); + m_infobar->setPixmap( currentPage()->pixmap() ); } @@ -658,17 +615,20 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) bool resetWidget = ( m_stack->currentWidget() == widget ); m_stack->removeWidget( widget ); - if ( resetWidget && m_interfaceHistory.count() ) + for ( int i = 0; i < m_pageHistory.count(); i++ ) { - unlinkPlaylist(); + ViewPage* page = m_pageHistory.at( i ); + if ( page->widget() == widget ) + { + m_pageHistory.removeAt( i ); + break; + } + } - m_currentInterface = m_interfaceHistory.last(); - qDebug() << "Last interface:" << m_currentInterface << m_currentInterface->widget(); - - if ( m_currentInterface->widget() ) - m_stack->setCurrentWidget( m_currentInterface->widget() ); - - linkPlaylist(); + if ( resetWidget ) + { + if ( m_pageHistory.count() ) + showHistory( m_pageHistory.count() - 1 ); } } @@ -676,16 +636,16 @@ PlaylistManager::onWidgetDestroyed( QWidget* widget ) void PlaylistManager::setRepeatMode( PlaylistInterface::RepeatMode mode ) { - if ( m_currentInterface ) - m_currentInterface->setRepeatMode( mode ); + if ( currentPlaylistInterface() ) + currentPlaylistInterface()->setRepeatMode( mode ); } void PlaylistManager::setShuffled( bool enabled ) { - if ( m_currentInterface ) - m_currentInterface->setShuffled( enabled ); + if ( currentPlaylistInterface() ) + currentPlaylistInterface()->setShuffled( enabled ); } @@ -709,20 +669,120 @@ PlaylistManager::createDynamicPlaylist( const Tomahawk::source_ptr& src, } +ViewPage* +PlaylistManager::pageForInterface( PlaylistInterface* interface ) const +{ + for ( int i = 0; i < m_pageHistory.count(); i++ ) + { + ViewPage* page = m_pageHistory.at( i ); + if ( page->playlistInterface() == interface ) + return page; + } + + return 0; +} + + +int +PlaylistManager::positionInHistory( ViewPage* page ) const +{ + for ( int i = 0; i < m_pageHistory.count(); i++ ) + { + if ( page == m_pageHistory.at( i ) ) + return i; + } + + return -1; +} + + +PlaylistInterface* +PlaylistManager::currentPlaylistInterface() const +{ + if ( currentPage() ) + return currentPage()->playlistInterface(); + else + return 0; +} + + +Tomahawk::ViewPage* +PlaylistManager::currentPage() const +{ + if ( m_historyPosition >= 0 ) + return m_pageHistory.at( m_historyPosition ); + else + return 0; +} + + +void +PlaylistManager::setHistoryPosition( int position ) +{ + m_historyPosition = position; + + emit historyBackAvailable( m_historyPosition > 0 ); + emit historyForwardAvailable( m_historyPosition < m_pageHistory.count() - 1 ); +} + + +Tomahawk::playlist_ptr +PlaylistManager::playlistForInterface( PlaylistInterface* interface ) const +{ + foreach ( PlaylistView* view, m_playlistViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_playlistViews.key( view ); + } + } + + return playlist_ptr(); +} + + +Tomahawk::dynplaylist_ptr +PlaylistManager::dynamicPlaylistForInterface( PlaylistInterface* interface ) const +{ + foreach ( DynamicWidget* view, m_dynamicWidgets.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_dynamicWidgets.key( view ); + } + } + + return dynplaylist_ptr(); +} + + +Tomahawk::collection_ptr +PlaylistManager::collectionForInterface( PlaylistInterface* interface ) const +{ + foreach ( CollectionView* view, m_collectionViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_collectionViews.key( view ); + } + } + foreach ( AlbumView* view, m_collectionAlbumViews.values() ) + { + if ( view->playlistInterface() == interface ) + { + return m_collectionAlbumViews.key( view ); + } + } + + return collection_ptr(); +} + + void PlaylistManager::showCurrentTrack() { - unlinkPlaylist(); - - m_currentInterface = AudioEngine::instance()->currentTrackPlaylist(); - - if ( m_currentInterface->widget() ) - m_stack->setCurrentWidget( m_currentInterface->widget() ); - - linkPlaylist(); - -/* if ( m_currentView && m_currentProxyModel ) - m_currentView->scrollTo( m_currentProxyModel->currentItem(), QAbstractItemView::PositionAtCenter );*/ + ViewPage* page = pageForInterface( AudioEngine::instance()->currentTrackPlaylist() ); + setPage( page ); } @@ -737,4 +797,4 @@ void PlaylistManager::onPauseClicked() { emit pauseClicked(); -} \ No newline at end of file +} diff --git a/src/libtomahawk/playlist/playlistmanager.h b/src/libtomahawk/playlist/playlistmanager.h index 3923b6f4c..722f7286e 100644 --- a/src/libtomahawk/playlist/playlistmanager.h +++ b/src/libtomahawk/playlist/playlistmanager.h @@ -7,6 +7,7 @@ #include "collection.h" #include "playlistinterface.h" +#include "viewpage.h" #include "dllmacro.h" @@ -27,7 +28,8 @@ class InfoBar; class TopBar; class WelcomeWidget; -namespace Tomahawk { +namespace Tomahawk +{ class DynamicWidget; } @@ -46,7 +48,10 @@ public: bool isSuperCollectionVisible() const { return true; } - PlaylistInterface* currentPlaylistInterface() const { return m_currentInterface; } + PlaylistInterface* currentPlaylistInterface() const; + Tomahawk::ViewPage* currentPage() const; + Tomahawk::ViewPage* pageForInterface( PlaylistInterface* interface ) const; + int positionInHistory( Tomahawk::ViewPage* page ) const; bool show( const Tomahawk::playlist_ptr& playlist ); bool show( const Tomahawk::dynplaylist_ptr& playlist ); @@ -55,7 +60,7 @@ public: bool show( const Tomahawk::collection_ptr& collection ); bool show( const Tomahawk::source_ptr& source ); - bool show( QWidget* widget, const QString& title = QString(), const QString& desc = QString(), const QPixmap& pixmap = QPixmap() ); + bool show( Tomahawk::ViewPage* page ); signals: void numSourcesChanged( unsigned int sources ); @@ -68,15 +73,29 @@ signals: void statsAvailable( bool b ); void modesAvailable( bool b ); + void modeChanged( PlaylistInterface::ViewMode mode ); void playClicked(); void pauseClicked(); + void historyBackAvailable( bool avail ); + void historyForwardAvailable( bool avail ); + + void tempPageActivated(); + void superCollectionActivated(); + void collectionActivated( const Tomahawk::collection_ptr& collection ); + void playlistActivated( const Tomahawk::playlist_ptr& playlist ); + void dynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ); + public slots: bool showSuperCollection(); void showWelcomePage(); void showCurrentTrack(); - + + void historyBack(); + void historyForward(); + void showHistory( int historyPosition ); + void setTreeMode(); void setTableMode(); void setAlbumMode(); @@ -103,9 +122,15 @@ private slots: void onWidgetDestroyed( QWidget* widget ); private: + void setHistoryPosition( int position ); + void setPage( Tomahawk::ViewPage* page, bool trackHistory = true ); + void updateView(); void unlinkPlaylist(); - void linkPlaylist(); + Tomahawk::playlist_ptr playlistForInterface( PlaylistInterface* interface ) const; + Tomahawk::dynplaylist_ptr dynamicPlaylistForInterface( PlaylistInterface* interface ) const; + Tomahawk::collection_ptr collectionForInterface( PlaylistInterface* interface ) const; + QWidget* m_widget; InfoBar* m_infobar; TopBar* m_topbar; @@ -130,19 +155,15 @@ private: QHash< Tomahawk::album_ptr, PlaylistView* > m_albumViews; QHash< Tomahawk::playlist_ptr, PlaylistView* > m_playlistViews; QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews; - - PlaylistInterface* m_currentInterface; - QList m_interfaceHistory; - - QWidget* m_currentInfoWidget; + + QList m_pageHistory; + int m_historyPosition; Tomahawk::collection_ptr m_currentCollection; int m_currentMode; bool m_superCollectionVisible; - bool m_statsAvailable; - bool m_modesAvailable; - + QTimer m_filterTimer; QString m_filter; diff --git a/src/libtomahawk/playlist/playlistmodel.cpp b/src/libtomahawk/playlist/playlistmodel.cpp index 620cc240b..660e9a1a7 100644 --- a/src/libtomahawk/playlist/playlistmodel.cpp +++ b/src/libtomahawk/playlist/playlistmodel.cpp @@ -67,8 +67,10 @@ PlaylistModel::loadPlaylist( const Tomahawk::playlist_ptr& playlist, bool loadEn connect( playlist.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ), SLOT( onRevisionLoaded( Tomahawk::PlaylistRevision ) ) ); setReadOnly( !m_playlist->author()->isLocal() ); + setTitle( playlist->title() ); + setDescription( tr( "A playlist by %1" ).arg( playlist->author()->isLocal() ? tr( "you" ) : playlist->author()->friendlyName() ) ); - if( !loadEntries ) + if ( !loadEntries ) return; PlItem* plitem; diff --git a/src/libtomahawk/playlist/playlistview.cpp b/src/libtomahawk/playlist/playlistview.cpp index b2a367dfc..18abb0d01 100644 --- a/src/libtomahawk/playlist/playlistview.cpp +++ b/src/libtomahawk/playlist/playlistview.cpp @@ -4,7 +4,6 @@ #include #include -#include "playlist/playlistmodel.h" #include "playlist/playlistproxymodel.h" #include "widgets/overlaywidget.h" @@ -129,3 +128,10 @@ PlaylistView::onTrackCountChanged( unsigned int tracks ) else overlay()->hide(); } + + +bool +PlaylistView::jumpToCurrentTrack() +{ + scrollTo( proxyModel()->currentItem(), QAbstractItemView::PositionAtCenter ); +} diff --git a/src/libtomahawk/playlist/playlistview.h b/src/libtomahawk/playlist/playlistview.h index 1e7c80c70..26a543316 100644 --- a/src/libtomahawk/playlist/playlistview.h +++ b/src/libtomahawk/playlist/playlistview.h @@ -3,13 +3,16 @@ #include +#include "playlist/trackproxymodel.h" +#include "playlist/playlistmodel.h" #include "trackview.h" +#include "viewpage.h" #include "dllmacro.h" class PlaylistModel; -class DLLEXPORT PlaylistView : public TrackView +class DLLEXPORT PlaylistView : public TrackView, public Tomahawk::ViewPage { Q_OBJECT @@ -20,6 +23,14 @@ public: PlaylistModel* playlistModel() const { return m_model; } virtual void setModel( PlaylistModel* model ); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return proxyModel(); } + + virtual QString title() const { return playlistModel()->title(); } + virtual QString description() const { return m_model->description(); } + + virtual bool jumpToCurrentTrack(); + protected: void keyPressEvent( QKeyEvent* event ); diff --git a/src/libtomahawk/playlist/topbar/topbar.cpp b/src/libtomahawk/playlist/topbar/topbar.cpp index dd6705bfd..3aa584bf5 100644 --- a/src/libtomahawk/playlist/topbar/topbar.cpp +++ b/src/libtomahawk/playlist/topbar/topbar.cpp @@ -65,7 +65,28 @@ TopBar::TopBar( QWidget* parent ) setNumArtists( 0 ); setNumShown( 0 ); - ui->radioNormal->setChecked( true ); + onFlatMode(); + + connect( PlaylistManager::instance(), SIGNAL( numSourcesChanged( unsigned int ) ), + SLOT( setNumSources( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numTracksChanged( unsigned int ) ), + SLOT( setNumTracks( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numArtistsChanged( unsigned int ) ), + SLOT( setNumArtists( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( numShownChanged( unsigned int ) ), + SLOT( setNumShown( unsigned int ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( statsAvailable( bool ) ), + SLOT( setStatsVisible( bool ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( modesAvailable( bool ) ), + SLOT( setModesVisible( bool ) ) ); + + connect( PlaylistManager::instance(), SIGNAL( modeChanged( PlaylistInterface::ViewMode ) ), + SLOT( onModeChanged( PlaylistInterface::ViewMode ) ) ); } @@ -238,3 +259,48 @@ TopBar::setFilter( const QString& filter ) { ui->filterEdit->setText( filter ); } + + +void +TopBar::onModeChanged( PlaylistInterface::ViewMode mode ) +{ + qDebug() << Q_FUNC_INFO << mode; + switch ( mode ) + { + case PlaylistInterface::Flat: + onFlatMode(); + break; + + case PlaylistInterface::Tree: + onArtistMode(); + break; + + case PlaylistInterface::Album: + onAlbumMode(); + break; + + default: + break; + } +} + + +void +TopBar::onFlatMode() +{ + ui->radioNormal->setChecked( true ); +} + + +void +TopBar::onArtistMode() +{ + ui->radioDetailed->setChecked( true ); +} + + +void +TopBar::onAlbumMode() +{ + ui->radioCloud->setChecked( true ); +} diff --git a/src/libtomahawk/playlist/topbar/topbar.h b/src/libtomahawk/playlist/topbar/topbar.h index f51b27e30..439768029 100644 --- a/src/libtomahawk/playlist/topbar/topbar.h +++ b/src/libtomahawk/playlist/topbar/topbar.h @@ -5,6 +5,7 @@ #include #include +#include "playlist/playlistmanager.h" #include "sourcelist.h" #include "dllmacro.h" @@ -42,6 +43,12 @@ public slots: void setFilter( const QString& filter ); +private slots: + void onModeChanged( PlaylistInterface::ViewMode mode ); + void onFlatMode(); + void onArtistMode(); + void onAlbumMode(); + protected: void changeEvent( QEvent* e ); void resizeEvent( QResizeEvent* e ); diff --git a/src/libtomahawk/playlist/trackmodel.h b/src/libtomahawk/playlist/trackmodel.h index ad43006be..f67b6631a 100644 --- a/src/libtomahawk/playlist/trackmodel.h +++ b/src/libtomahawk/playlist/trackmodel.h @@ -35,6 +35,11 @@ public: virtual bool isReadOnly() const { return m_readOnly; } + virtual QString title() const { return m_title; } + virtual void setTitle( const QString& title ) { m_title = title; } + virtual QString description() const { return m_description; } + virtual void setDescription( const QString& description ) { m_description = description; } + virtual int trackCount() const { return rowCount( QModelIndex() ); } virtual int rowCount( const QModelIndex& parent ) const; @@ -84,6 +89,9 @@ private slots: private: QPersistentModelIndex m_currentIndex; bool m_readOnly; + + QString m_title; + QString m_description; }; #endif // TRACKMODEL_H diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index c2a972ed1..bdc139773 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -73,7 +73,6 @@ void TrackView::setProxyModel( TrackProxyModel* model ) { m_proxyModel = model; - m_proxyModel->setWidget( this ); m_delegate = new PlaylistItemDelegate( this, m_proxyModel ); setItemDelegate( m_delegate ); diff --git a/src/libtomahawk/playlistinterface.h b/src/libtomahawk/playlistinterface.h index 7ef4fefd1..f8a56fa32 100644 --- a/src/libtomahawk/playlistinterface.h +++ b/src/libtomahawk/playlistinterface.h @@ -13,8 +13,9 @@ class DLLEXPORT PlaylistInterface { public: enum RepeatMode { NoRepeat, RepeatOne, RepeatAll }; - - PlaylistInterface( QObject* parent = 0 ) : m_widget( 0 ), m_object( parent ) {} + enum ViewMode { Unknown, Flat, Tree, Album }; + + PlaylistInterface( QObject* parent = 0 ) : m_object( parent ) {} virtual ~PlaylistInterface() {} virtual QList< Tomahawk::query_ptr > tracks() = 0; @@ -28,13 +29,11 @@ public: virtual PlaylistInterface::RepeatMode repeatMode() const = 0; virtual bool shuffled() const = 0; + virtual PlaylistInterface::ViewMode viewMode() const { return Unknown; } virtual QString filter() const { return m_filter; } virtual void setFilter( const QString& pattern ) { m_filter = pattern; } - QWidget* widget() const { return m_widget; } - void setWidget( QWidget* widget ) { m_widget = widget; } - QObject* object() const { return m_object; } public slots: @@ -48,7 +47,6 @@ signals: virtual void sourceTrackCountChanged( unsigned int tracks ) = 0; private: - QWidget* m_widget; QObject* m_object; QString m_filter; diff --git a/src/libtomahawk/viewpage.cpp b/src/libtomahawk/viewpage.cpp new file mode 100644 index 000000000..321bbbdf8 --- /dev/null +++ b/src/libtomahawk/viewpage.cpp @@ -0,0 +1,6 @@ +#include "viewpage.h" + +#include + +using namespace Tomahawk; + diff --git a/src/libtomahawk/viewpage.h b/src/libtomahawk/viewpage.h new file mode 100644 index 000000000..af087c669 --- /dev/null +++ b/src/libtomahawk/viewpage.h @@ -0,0 +1,38 @@ +#ifndef VIEWPAGE_H +#define VIEWPAGE_H + +#include + +#include "typedefs.h" +#include "playlistinterface.h" +#include "utils/tomahawkutils.h" + +#include "dllmacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT ViewPage +{ +public: + ViewPage() {} + + virtual QWidget* widget() = 0; + virtual PlaylistInterface* playlistInterface() const = 0; + + virtual QString title() const = 0; + virtual QString description() const = 0; + virtual QPixmap pixmap() const { return QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ); } + + virtual bool showStatsBar() const { return true; } + virtual bool showModes() const { return false; } + virtual bool queueVisible() const { return true; } + + virtual bool jumpToCurrentTrack() = 0; + +private: +}; + +}; // ns + +#endif //VIEWPAGE_H diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp index ad57103d2..80469499e 100644 --- a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.cpp @@ -43,6 +43,8 @@ SourceInfoWidget::SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* m_recentAlbumModel = new AlbumModel( ui->recentAlbumView ); ui->recentAlbumView->setModel( m_recentAlbumModel ); m_recentAlbumModel->addFilteredCollection( source->collection(), 20, DatabaseCommand_AllAlbums::ModificationTime ); + + m_title = tr( "Info about %1" ).arg( source->isLocal() ? tr( "Your Collection" ) : source->friendlyName() ); } diff --git a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h index 8d369a97e..f12fd942d 100644 --- a/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h +++ b/src/libtomahawk/widgets/infowidgets/sourceinfowidget.h @@ -6,6 +6,7 @@ #include "album.h" #include "result.h" #include "playlistinterface.h" +#include "viewpage.h" #include "dllmacro.h" @@ -18,7 +19,7 @@ namespace Ui class SourceInfoWidget; } -class DLLEXPORT SourceInfoWidget : public QWidget +class DLLEXPORT SourceInfoWidget : public QWidget, public Tomahawk::ViewPage { Q_OBJECT @@ -26,6 +27,16 @@ public: SourceInfoWidget( const Tomahawk::source_ptr& source, QWidget* parent = 0 ); ~SourceInfoWidget(); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return m_title; } + virtual QString description() const { return m_description; } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + protected: void changeEvent( QEvent* e ); @@ -38,6 +49,9 @@ private: CollectionFlatModel* m_recentCollectionModel; PlaylistModel* m_historyModel; AlbumModel* m_recentAlbumModel; + + QString m_title; + QString m_description; }; #endif // SOURCEINFOWIDGET_H diff --git a/src/libtomahawk/widgets/newplaylistwidget.h b/src/libtomahawk/widgets/newplaylistwidget.h index f19590aa7..dfe54a988 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.h +++ b/src/libtomahawk/widgets/newplaylistwidget.h @@ -7,6 +7,7 @@ #include "album.h" #include "result.h" #include "playlistinterface.h" +#include "viewpage.h" #include "dllmacro.h" @@ -18,7 +19,7 @@ namespace Ui class NewPlaylistWidget; } -class DLLEXPORT NewPlaylistWidget : public QWidget +class DLLEXPORT NewPlaylistWidget : public QWidget, public Tomahawk::ViewPage { Q_OBJECT @@ -26,6 +27,16 @@ public: NewPlaylistWidget( QWidget* parent = 0 ); ~NewPlaylistWidget(); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return tr( "Create a new playlist" ); } + virtual QString description() const { return QString(); } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + protected: void changeEvent( QEvent* e ); diff --git a/src/libtomahawk/widgets/newplaylistwidget.ui b/src/libtomahawk/widgets/newplaylistwidget.ui index 8b30f4780..6a2701ee8 100644 --- a/src/libtomahawk/widgets/newplaylistwidget.ui +++ b/src/libtomahawk/widgets/newplaylistwidget.ui @@ -11,28 +11,11 @@ - - - - - 20 - 75 - true - - - - Create A New Playlist - - - false - - - - 14 + 13 @@ -54,7 +37,7 @@ - 14 + 13 @@ -69,7 +52,7 @@ - 14 + 13 diff --git a/src/libtomahawk/widgets/welcomewidget.h b/src/libtomahawk/widgets/welcomewidget.h index 79f700448..9d14859e8 100644 --- a/src/libtomahawk/widgets/welcomewidget.h +++ b/src/libtomahawk/widgets/welcomewidget.h @@ -9,6 +9,7 @@ #include "playlist.h" #include "result.h" +#include "viewpage.h" #include "utils/tomahawkutils.h" @@ -73,7 +74,7 @@ private: }; -class DLLEXPORT WelcomeWidget : public QWidget +class DLLEXPORT WelcomeWidget : public QWidget, public Tomahawk::ViewPage { Q_OBJECT @@ -81,6 +82,16 @@ public: WelcomeWidget( QWidget* parent = 0 ); ~WelcomeWidget(); + virtual QWidget* widget() { return this; } + virtual PlaylistInterface* playlistInterface() const { return 0; } + + virtual QString title() const { return tr( "Welcome to Tomahawk" ); } + virtual QString description() const { return QString(); } + + virtual bool showStatsBar() const { return false; } + + virtual bool jumpToCurrentTrack() { return false; } + protected: void changeEvent( QEvent* e ); diff --git a/src/sip/jabber/jabber_p.cpp b/src/sip/jabber/jabber_p.cpp index a891973a9..cf090c107 100644 --- a/src/sip/jabber/jabber_p.cpp +++ b/src/sip/jabber/jabber_p.cpp @@ -526,7 +526,7 @@ Jabber_p::handleRosterPresence( const RosterItem& item, const std::string& resou // convert to QString to get proper regex support QString res( jid.resource().c_str() ); QRegExp regex( "tomahawk\\d+" ); - if( res != "tomahawk-tomahawk" && !res.contains( regex ) ) + if( res != "tomahawk-tomahawk" && !res.startsWith( "tomahawk" ) ) { //qDebug() << "not considering resource of" << res; // Disco them to check if they are tomahawk-capable diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index a84c7da7a..e2a95de0c 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -58,7 +58,8 @@ SourcesModel::flags( const QModelIndex& index ) const playlist_ptr playlist = indexToPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) defaultFlags |= Qt::ItemIsEditable; - } else if ( indexType( index ) == DynamicPlaylistSource ) + } + else if ( indexType( index ) == DynamicPlaylistSource ) { dynplaylist_ptr playlist = indexToDynamicPlaylist( index ); if ( !playlist.isNull() && playlist->author()->isLocal() ) @@ -261,6 +262,75 @@ SourcesModel::indexToTreeItem( const QModelIndex& index ) } +QModelIndex +SourcesModel::playlistToIndex( const Tomahawk::playlist_ptr& playlist ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex pidx = index( i, 0 ); + + for ( int j = 0; j < rowCount( pidx ); j++ ) + { + QModelIndex idx = index( j, 0, pidx ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + + if ( type == SourcesModel::PlaylistSource ) + { + playlist_ptr p = SourcesModel::indexToPlaylist( idx ); + if ( playlist.data() == p.data() ) + return idx; + } + } + } + + return QModelIndex(); +} + + +QModelIndex +SourcesModel::dynamicPlaylistToIndex( const Tomahawk::dynplaylist_ptr& playlist ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex pidx = index( i, 0 ); + + for ( int j = 0; j < rowCount( pidx ); j++ ) + { + QModelIndex idx = index( j, 0, pidx ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + + if ( type == SourcesModel::DynamicPlaylistSource ) + { + playlist_ptr p = SourcesModel::indexToDynamicPlaylist( idx ); + if ( playlist.data() == p.data() ) + return idx; + } + } + } + + return QModelIndex(); +} + + +QModelIndex +SourcesModel::collectionToIndex( const Tomahawk::collection_ptr& collection ) +{ + for ( int i = 0; i < rowCount(); i++ ) + { + QModelIndex idx = index( i, 0 ); + SourcesModel::SourceType type = SourcesModel::indexType( idx ); + if ( type == SourcesModel::CollectionSource ) + { + SourceTreeItem* sti = SourcesModel::indexToTreeItem( idx ); + if ( sti && !sti->source().isNull() && sti->source()->collection().data() == collection.data() ) + return idx; + } + } + + return QModelIndex(); +} + + bool SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role ) { diff --git a/src/sourcetree/sourcesmodel.h b/src/sourcetree/sourcesmodel.h index 2b761637b..e258e73ff 100644 --- a/src/sourcetree/sourcesmodel.h +++ b/src/sourcetree/sourcesmodel.h @@ -37,6 +37,10 @@ public: static Tomahawk::dynplaylist_ptr indexToDynamicPlaylist( const QModelIndex& index ); static SourceTreeItem* indexToTreeItem( const QModelIndex& index ); + QModelIndex playlistToIndex( const Tomahawk::playlist_ptr& playlist ); + QModelIndex dynamicPlaylistToIndex( const Tomahawk::dynplaylist_ptr& playlist ); + QModelIndex collectionToIndex( const Tomahawk::collection_ptr& collection ); + signals: void clicked( const QModelIndex& ); diff --git a/src/sourcetree/sourcetreeitem.cpp b/src/sourcetree/sourcetreeitem.cpp index 0e4ec8748..96fa06a8d 100644 --- a/src/sourcetree/sourcetreeitem.cpp +++ b/src/sourcetree/sourcetreeitem.cpp @@ -237,8 +237,8 @@ void SourceTreeItem::playlistAddedInternal( qlonglong ptr, const Tomahawk::playl subitem->setData( (qlonglong)this, SourceItemPointer ); m_columns.at( 0 )->appendRow( subitem ); - Q_ASSERT( qobject_cast((parent()->parent()) ) ); - qobject_cast((parent()->parent()))->expandAll(); +// Q_ASSERT( qobject_cast((parent()->parent()) ) ); +// qobject_cast((parent()->parent()))->expandAll(); p->loadRevision(); } diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 916d725a0..177482a61 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -67,7 +67,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) setItemDelegate( new SourceDelegate( this ) ); setContextMenuPolicy( Qt::CustomContextMenu ); - connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); + connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) ); m_model = new SourcesModel( this ); m_proxyModel = new SourcesProxyModel( m_model, this ); @@ -77,14 +77,25 @@ SourceTreeView::SourceTreeView( QWidget* parent ) header()->setResizeMode( 0, QHeaderView::Stretch ); connect( m_model, SIGNAL( clicked( QModelIndex ) ), SIGNAL( clicked( QModelIndex ) ) ); - connect( this, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); + connect( this, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); - connect( selectionModel(), SIGNAL( selectionChanged( const QItemSelection&, const QItemSelection& ) ), SLOT( onSelectionChanged() ) ); + connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged() ) ); connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceOffline( Tomahawk::source_ptr ) ) ); m_model->appendItem( source_ptr() ); hideOfflineSources(); + + connect( PlaylistManager::instance(), SIGNAL( playlistActivated( Tomahawk::playlist_ptr ) ), + SLOT( onPlaylistActivated( Tomahawk::playlist_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( dynamicPlaylistActivated( Tomahawk::dynplaylist_ptr ) ), + SLOT( onDynamicPlaylistActivated( Tomahawk::dynplaylist_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( collectionActivated( Tomahawk::collection_ptr ) ), + SLOT( onCollectionActivated( Tomahawk::collection_ptr ) ) ); + connect( PlaylistManager::instance(), SIGNAL( superCollectionActivated() ), + SLOT( onSuperCollectionActivated() ) ); + connect( PlaylistManager::instance(), SIGNAL( tempPageActivated() ), + SLOT( onTempPageActivated() ) ); } @@ -143,6 +154,57 @@ SourceTreeView::onSourceOffline( Tomahawk::source_ptr src ) } +void +SourceTreeView::onPlaylistActivated( const Tomahawk::playlist_ptr& playlist ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->playlistToIndex( playlist ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onDynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->dynamicPlaylistToIndex( playlist ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onCollectionActivated( const Tomahawk::collection_ptr& collection ) +{ + QModelIndex idx = m_proxyModel->mapFromSource( m_model->collectionToIndex( collection ) ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onSuperCollectionActivated() +{ + QModelIndex idx = m_proxyModel->index( 0, 0 ); + if ( idx.isValid() ) + { + setCurrentIndex( idx ); + } +} + + +void +SourceTreeView::onTempPageActivated() +{ + clearSelection(); +} + + void SourceTreeView::onItemActivated( const QModelIndex& index ) { diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 10df5d54d..077fb2937 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -27,6 +27,12 @@ signals: void onOffline( const QModelIndex& index ); private slots: + void onPlaylistActivated( const Tomahawk::playlist_ptr& playlist ); + void onDynamicPlaylistActivated( const Tomahawk::dynplaylist_ptr& playlist ); + void onCollectionActivated( const Tomahawk::collection_ptr& collection ); + void onSuperCollectionActivated(); + void onTempPageActivated(); + void onItemActivated( const QModelIndex& index ); void onSelectionChanged(); diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index b873accac..0fb8fd67d 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -56,6 +56,8 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) #endif PlaylistManager* pm = new PlaylistManager( this ); + connect( pm, SIGNAL( historyBackAvailable( bool ) ), SLOT( onHistoryBackAvailable( bool ) ) ); + connect( pm, SIGNAL( historyForwardAvailable( bool ) ), SLOT( onHistoryForwardAvailable( bool ) ) ); connect( m_audioControls, SIGNAL( playPressed() ), pm, SLOT( onPlayClicked() ) ); connect( m_audioControls, SIGNAL( pausePressed() ), pm, SLOT( onPauseClicked() ) ); @@ -119,9 +121,11 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) toolbar->setMovable( false ); toolbar->setFloatable( false ); toolbar->setIconSize( QSize( 32, 32 ) ); - toolbar->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolbar->setToolButtonStyle( Qt::ToolButtonFollowStyle ); toolbar->installEventFilter( new WidgetDragFilter( toolbar ) ); + m_backAvailable = toolbar->addAction( QIcon( RESPATH "images/back.png" ), tr( "Back" ), PlaylistManager::instance(), SLOT( historyBack() ) ); + m_forwardAvailable = toolbar->addAction( QIcon( RESPATH "images/forward.png" ), tr( "Forward" ), PlaylistManager::instance(), SLOT( historyForward() ) ); toolbar->addAction( QIcon( RESPATH "images/home.png" ), tr( "Home" ), PlaylistManager::instance(), SLOT( showWelcomePage() ) ); statusBar()->addPermanentWidget( m_audioControls, 1 ); @@ -355,6 +359,20 @@ TomahawkWindow::onPlaybackLoading( const Tomahawk::result_ptr& result ) } +void +TomahawkWindow::onHistoryBackAvailable( bool avail ) +{ + m_backAvailable->setEnabled( avail ); +} + + +void +TomahawkWindow::onHistoryForwardAvailable( bool avail ) +{ + m_forwardAvailable->setEnabled( avail ); +} + + void TomahawkWindow::onSipConnected() { diff --git a/src/tomahawkwindow.h b/src/tomahawkwindow.h index 5e6a24472..766066cd8 100644 --- a/src/tomahawkwindow.h +++ b/src/tomahawkwindow.h @@ -54,7 +54,9 @@ private slots: void addPeerManually(); void onPlaybackLoading( const Tomahawk::result_ptr& result ); - + void onHistoryBackAvailable( bool avail ); + void onHistoryForwardAvailable( bool avail ); + void showAboutTomahawk(); private: @@ -68,6 +70,9 @@ private: QNetworkAccessManager m_nam; QPushButton* m_statusButton; + QAction* m_backAvailable; + QAction* m_forwardAvailable; + Tomahawk::result_ptr m_currentTrack; QString m_windowTitle; }; From 6422353d54643656091a4570fe8b949d8199264f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 27 Feb 2011 03:12:33 +0100 Subject: [PATCH 09/10] * Only activate a playlist when manually starting a track from it. --- src/libtomahawk/playlist/playlistmanager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index c2425aa78..b9eb4b5ae 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -533,7 +533,10 @@ PlaylistManager::setPage( ViewPage* page, bool trackHistory ) emit collectionActivated( collectionForInterface( currentPlaylistInterface() ) ); if ( !currentPlaylistInterface() ) emit tempPageActivated(); - + + if ( !AudioEngine::instance()->playlist() ) + AudioEngine::instance()->setPlaylist( currentPlaylistInterface() ); + m_stack->setCurrentWidget( page->widget() ); updateView(); } From 5ac6a0309c25735313fb7140e56455893e987693 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Sun, 27 Feb 2011 03:26:52 +0100 Subject: [PATCH 10/10] * Fixed jumping to current track when clicking track title in AudioControls. --- src/libtomahawk/playlist/albummodel.cpp | 2 +- src/libtomahawk/playlist/playlistmanager.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index c7efad856..fbbad77bd 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -248,7 +248,7 @@ AlbumModel::addFilteredCollection( const collection_ptr& collection, unsigned in Database::instance()->enqueue( QSharedPointer( cmd ) ); - m_title = tr( "All albums by %1" ).arg( collection->source()->friendlyName() ); + m_title = tr( "All albums from %1" ).arg( collection->source()->friendlyName() ); } diff --git a/src/libtomahawk/playlist/playlistmanager.cpp b/src/libtomahawk/playlist/playlistmanager.cpp index b9eb4b5ae..7eceac460 100644 --- a/src/libtomahawk/playlist/playlistmanager.cpp +++ b/src/libtomahawk/playlist/playlistmanager.cpp @@ -786,6 +786,7 @@ PlaylistManager::showCurrentTrack() { ViewPage* page = pageForInterface( AudioEngine::instance()->currentTrackPlaylist() ); setPage( page ); + page->jumpToCurrentTrack(); }