1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-02-24 03:43:56 +01:00

Merge branch 'master' into databasegenerator

Conflicts:
	src/libtomahawk/CMakeLists.txt
	src/libtomahawk/database/databasecommand_createplaylist.cpp
	src/libtomahawk/playlist/dynamic/DynamicPlaylist.h
This commit is contained in:
Leo Franchi 2011-08-03 23:31:25 -04:00
commit a9111267e8
50 changed files with 1011 additions and 97 deletions

View File

@ -76,9 +76,9 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
file(GLOB_RECURSE files "${pattern}")
# we can only test for the 128-icon like that - we don't use patterns anymore
foreach (it ${files})
if (it MATCHES ".*128.*" )
if (it MATCHES ".*512.*" )
set (_icon ${it})
endif (it MATCHES ".*128.*")
endif (it MATCHES ".*512.*")
endforeach (it)
if (_icon)
@ -101,8 +101,8 @@ macro (KDE4_ADD_APP_ICON appsources pattern)
set_source_files_properties(${_outfilename}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
else(_icon)
# TODO - try to scale a non-128 icon...? Try to convert an SVG on the fly?
message(STATUS "Unable to find an 128x128 icon that matches pattern ${pattern} for variable ${appsources} - application will not have an application icon!")
# TODO - try to scale a non-512 icon...? Try to convert an SVG on the fly?
message(STATUS "Unable to find an 512x512 icon that matches pattern ${pattern} for variable ${appsources} - application will not have an application icon!")
endif(_icon)
else(SIPS_EXECUTABLE AND TIFF2ICNS_EXECUTABLE)

View File

@ -1,3 +1,12 @@
Version 0.2.0:
* Spotify resolver now honors SOCKS5 proxy settings.
* Fixed a few crashes that could occur when fetching data from Last.fm.
* Made Twitter dialog more readable/understandable.
* Twitter checks for updates less often now, saving user API calls from
running out when using multiple clients.
* Collection scanner now can now run automatically, watching files and
directories for changes. On by default.
Version 0.1.0:
* Fixed stations so they resolve against all available sources instead of
only local and friend's collections.

0
data/images/configure.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
data/images/filter.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

0
data/images/home.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

0
data/images/music-icon.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
data/images/share.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

0
data/images/view-refresh.png Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -8,6 +8,7 @@
<file>./data/images/loved.png</file>
<file>./data/images/not-loved.png</file>
<file>./data/images/no-album-art-placeholder.png</file>
<file>./data/images/no-artist-image-placeholder.png</file>
<file>./data/images/now-playing-panel.png</file>
<file>./data/images/now-playing-speaker.png</file>
<file>./data/images/pause-pressed.png</file>
@ -62,6 +63,7 @@
<file>./data/images/volume-slider-level.png</file>
<file>./data/images/echonest_logo.png</file>
<file>./data/images/loading-animation.gif</file>
<file>./data/images/info.png</file>
<file>./data/images/home.png</file>
<file>./data/images/back.png</file>
<file>./data/images/forward.png</file>

View File

@ -170,6 +170,7 @@ set( libSources
utils/animatedsplitter.cpp
utils/xspfloader.cpp
utils/xspfgenerator.cpp
utils/jspfloader.cpp
widgets/newplaylistwidget.cpp
widgets/searchwidget.cpp
@ -345,6 +346,7 @@ set( libHeaders
utils/animatedsplitter.h
utils/xspfloader.h
utils/xspfgenerator.h
utils/jspfloader.h
widgets/newplaylistwidget.h
widgets/searchwidget.h
@ -433,6 +435,7 @@ IF( APPLE )
SET( libSources ${libSources}
infosystem/infoplugins/mac/adium.mm
infosystem/infoplugins/mac/adiumplugin.cpp
widgets/maclineedit.mm
utils/tomahawkutils_mac.mm )
SET( libHeaders ${libHeaders}

View File

@ -93,6 +93,21 @@ AudioEngine::~AudioEngine()
}
QStringList
AudioEngine::supportedMimeTypes() const
{
if ( m_supportedMimeTypes.isEmpty() )
{
m_supportedMimeTypes = Phonon::BackendCapabilities::availableMimeTypes();
m_supportedMimeTypes << "audio/basic";
return m_supportedMimeTypes;
}
else
return m_supportedMimeTypes;
}
void
AudioEngine::playPause()
{
@ -358,13 +373,19 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result )
}
else
{
QUrl furl = m_currentTrack->url();
if ( m_currentTrack->url().contains( "?" ) )
if ( !isLocalResult( m_currentTrack->url() ) )
{
furl = QUrl( m_currentTrack->url().left( m_currentTrack->url().indexOf( '?' ) ) );
furl.setEncodedQuery( QString( m_currentTrack->url().mid( m_currentTrack->url().indexOf( '?' ) + 1 ) ).toLocal8Bit() );
QUrl furl = m_currentTrack->url();
if ( m_currentTrack->url().contains( "?" ) )
{
furl = QUrl( m_currentTrack->url().left( m_currentTrack->url().indexOf( '?' ) ) );
furl.setEncodedQuery( QString( m_currentTrack->url().mid( m_currentTrack->url().indexOf( '?' ) + 1 ) ).toLocal8Bit() );
}
m_mediaObject->setCurrentSource( furl );
}
m_mediaObject->setCurrentSource( furl );
else
m_mediaObject->setCurrentSource( m_currentTrack->url() );
m_mediaObject->currentSource().setAutoDelete( true );
m_isPlayingHttp = true;
}
@ -457,7 +478,7 @@ AudioEngine::loadNextTrack()
void
AudioEngine::playItem( Tomahawk::PlaylistInterface* playlist, const Tomahawk::result_ptr& result )
{
tDebug( LOGEXTRA ) << Q_FUNC_INFO << result->url();
tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() );
if ( !m_playlist.isNull() )
m_playlist.data()->reset();
@ -509,12 +530,12 @@ AudioEngine::onStateChanged( Phonon::State newState, Phonon::State oldState )
if ( oldState == Phonon::PlayingState )
{
qint64 duration = m_mediaObject->totalTime() > 0 ? m_mediaObject->totalTime() : m_currentTrack->duration() * 1000;
bool stopped = false;
switch ( newState )
{
case Phonon::PausedState:
{
qint64 duration = m_mediaObject->totalTime() > 0 ? m_mediaObject->totalTime() : m_currentTrack->duration() * 1000;
stopped = ( duration - 1000 < m_mediaObject->currentTime() );
if ( !stopped )
setState( Paused );

View File

@ -24,6 +24,7 @@
#include <phonon/MediaObject>
#include <phonon/AudioOutput>
#include <phonon/BackendCapabilities>
#include "infosystem/infosystem.h"
@ -52,6 +53,7 @@ public:
explicit AudioEngine();
~AudioEngine();
QStringList supportedMimeTypes() const;
unsigned int volume() const { return m_audioOutput->volume() * 100.0; } // in percent
AudioState state() const { return m_state; }
@ -148,6 +150,7 @@ private:
bool m_waitingOnNewTrack;
bool m_infoSystemConnected;
mutable QStringList m_supportedMimeTypes;
AudioState m_state;
static AudioEngine* s_instance;

View File

@ -53,6 +53,11 @@ DatabaseCommand_CreatePlaylist::exec( DatabaseImpl* lib )
void
DatabaseCommand_CreatePlaylist::postCommitHook()
{
qDebug() << Q_FUNC_INFO;
if ( source()->isLocal() )
Servent::instance()->triggerDBSync();
if ( m_report == false )
return;
@ -70,9 +75,6 @@ DatabaseCommand_CreatePlaylist::postCommitHook()
{
m_playlist->reportCreated( m_playlist );
}
if ( source()->isLocal() )
Servent::instance()->triggerDBSync();
}

View File

@ -1,5 +1,5 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
@ -36,7 +36,7 @@ public:
virtual QString name() const;
virtual unsigned int weight() const { return m_weight; }
virtual unsigned int preference() const { return 100; }
virtual unsigned int timeout() const { return 2500; }
virtual unsigned int timeout() const { return 0; }
public slots:
virtual void resolve( const Tomahawk::query_ptr& query );

View File

@ -37,6 +37,11 @@
#include "utils/xspfloader.h"
#include "utils/xspfgenerator.h"
#include "utils/logger.h"
#include "utils/tomahawkutils.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include "utils/jspfloader.h"
GlobalActionManager* GlobalActionManager::s_instance = 0;
@ -100,7 +105,7 @@ GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& p
QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == Tomahawk::OnDemand ? "station" : "autoplaylist" ) );
if( playlist->generator()->type() != "echonest" ) {
qDebug() << "Only echonest generators are supported";
tLog() << "Only echonest generators are supported";
return QString();
}
@ -177,7 +182,7 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
if( url.contains( "tomahawk://" ) ) {
QString cmd = url.mid( 11 );
cmd.replace( "%2B", "%20" );
qDebug() << "Parsing tomahawk link command" << cmd;
tLog() << "Parsing tomahawk link command" << cmd;
QString cmdType = cmd.split( "/" ).first();
QUrl u = QUrl::fromEncoded( cmd.toUtf8() );
@ -187,10 +192,18 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
if( u.hasQueryItem( "xspf" ) ) {
QUrl xspf = QUrl::fromUserInput( u.queryItemValue( "xspf" ) );
XSPFLoader* l = new XSPFLoader( true, this );
qDebug() << "Loading spiff:" << xspf.toString();
tDebug() << "Loading spiff:" << xspf.toString();
l->load( xspf );
connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) );
return true;
} else if( u.hasQueryItem( "jspf" ) ) {
QUrl jspf = QUrl::fromUserInput( u.queryItemValue( "jspf" ) );
Tomahawk::JSPFLoader* l = new Tomahawk::JSPFLoader( true, this );
tDebug() << "Loading jspiff:" << jspf.toString();
l->load( jspf );
connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) );
return true;
}
}
@ -214,11 +227,11 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
} else if( cmdType == "open" ) {
return handleOpenCommand( u );
} else {
qDebug() << "Tomahawk link not supported, command not known!" << cmdType << u.path();
tLog() << "Tomahawk link not supported, command not known!" << cmdType << u.path();
return false;
}
} else {
qDebug() << "Not a tomahawk:// link!";
tLog() << "Not a tomahawk:// link!";
return false;
}
}
@ -228,13 +241,13 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific playlist command:" << url.toString();
tLog() << "No specific playlist command:" << url.toString();
return false;
}
if( parts[ 0 ] == "import" ) {
if( !url.hasQueryItem( "xspf" ) ) {
qDebug() << "No xspf to load...";
tDebug() << "No xspf to load...";
return false;
}
QUrl xspf = QUrl( url.queryItemValue( "xspf" ) );
@ -246,14 +259,14 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url )
} else if( parts [ 0 ] == "new" ) {
if( !url.hasQueryItem( "title" ) ) {
qDebug() << "New playlist command needs a title...";
tLog() << "New playlist command needs a title...";
return false;
}
Tomahawk::playlist_ptr pl = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), url.queryItemValue( "title" ), QString(), QString(), false );
ViewManager::instance()->show( pl );
} else if( parts[ 0 ] == "add" ) {
if( !url.hasQueryItem( "playlistid" ) || !url.hasQueryItem( "title" ) || !url.hasQueryItem( "artist" ) ) {
qDebug() << "Add to playlist command needs playlistid, track, and artist..." << url.toString();
tLog() << "Add to playlist command needs playlistid, track, and artist..." << url.toString();
return false;
}
// TODO implement. Let the user select what playlist to add to
@ -268,7 +281,7 @@ GlobalActionManager::handleCollectionCommand( const QUrl& url )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific collection command:" << url.toString();
tLog() << "No specific collection command:" << url.toString();
return false;
}
@ -284,27 +297,39 @@ GlobalActionManager::handleOpenCommand(const QUrl& url)
{
QStringList parts = url.path().split( "/" ).mid( 1 );
if( parts.isEmpty() ) {
qDebug() << "No specific type to open:" << url.toString();
tLog() << "No specific type to open:" << url.toString();
return false;
}
// TODO user configurable in the UI
return doQueueAdd( parts, url.queryItems() );
}
void
GlobalActionManager::handleOpenTrack ( const Tomahawk::query_ptr& q )
{
ViewManager::instance()->queue()->model()->append( q );
ViewManager::instance()->showQueue();
if( !AudioEngine::instance()->isPlaying() ) {
connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
m_waitingToPlay = q;
}
}
bool
GlobalActionManager::handleQueueCommand( const QUrl& url )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific queue command:" << url.toString();
tLog() << "No specific queue command:" << url.toString();
return false;
}
if( parts[ 0 ] == "add" ) {
doQueueAdd( parts.mid( 1 ), url.queryItems() );
} else {
qDebug() << "Only queue/add/track is support at the moment, got:" << parts;
tLog() << "Only queue/add/track is support at the moment, got:" << parts;
return false;
}
@ -330,18 +355,12 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
}
if( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) { // an individual; query to add to queue
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album );
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), false );
if( !urlStr.isEmpty() )
q->setResultHint( urlStr );
Tomahawk::Pipeline::instance()->resolve( q, true );
ViewManager::instance()->queue()->model()->append( q );
ViewManager::instance()->showQueue();
if( !AudioEngine::instance()->isPlaying() ) {
connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
m_waitingToPlay = q;
}
handleOpenTrack( q );
return true;
} else { // a list of urls to add to the queue
@ -354,8 +373,9 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
// TODO
} else { // give it a web result hint
QFileInfo info( track.path() );
Tomahawk::query_ptr q = Tomahawk::Query::get( QString(), info.baseName(), QString() );
Tomahawk::query_ptr q = Tomahawk::Query::get( QString(), info.baseName(), QString(), uuid(), false );
q->setResultHint( track.toString() );
Tomahawk::Pipeline::instance()->resolve( q, true );
ViewManager::instance()->queue()->model()->append( q );
@ -400,13 +420,13 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific station command:" << url.toString();
tLog() << "No specific station command:" << url.toString();
return Tomahawk::dynplaylist_ptr();
}
if( parts[ 0 ] == "create" ) {
if( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) {
qDebug() << "Station create command needs title and type..." << url.toString();
tLog() << "Station create command needs title and type..." << url.toString();
return Tomahawk::dynplaylist_ptr();
}
QString title = url.queryItemValue( "title" );
@ -550,7 +570,7 @@ GlobalActionManager::handlePlayCommand( const QUrl& url )
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific play command:" << url.toString();
tLog() << "No specific play command:" << url.toString();
return false;
}
@ -585,7 +605,7 @@ bool GlobalActionManager::handleBookmarkCommand(const QUrl& url)
{
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) {
qDebug() << "No specific bookmark command:" << url.toString();
tLog() << "No specific bookmark command:" << url.toString();
return false;
}
@ -676,7 +696,7 @@ GlobalActionManager::waitingForResolved( bool success )
return;
}
if( success && !m_waitingToPlay.isNull() && !m_waitingToPlay->results().isEmpty() ) { // play it!
if( !m_waitingToPlay.isNull() && m_waitingToPlay->playable() ) { // play it!
// AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() );
AudioEngine::instance()->play();
@ -689,3 +709,83 @@ GlobalActionManager::hostname() const
{
return QString( "http://toma.hk" );
}
/// SPOTIFY URL HANDLING
bool
GlobalActionManager::parseSpotifyLink( const QString& link )
{
if( !link.contains( "track" ) ) // we only support track links atm
return false;
// we need Spotify URIs such as spotify:track:XXXXXX, so if we by chance get a http://open.spotify.com url, convert it
QString uri = link;
if( link.contains( "open.spotify.com" ) )
{
QString hash = link;
hash.replace( "http://open.spotify.com/track/", "" );
uri = QString( "spotify:track:%1" ).arg( hash );
}
tLog() << "Parsing Spotify Track URI:" << uri;
QUrl url = QUrl( QString( "http://ws.spotify.com/lookup/1/.json?uri=%1" ).arg( uri ) );
tDebug() << "Looking up..." << url.toString();
QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
connect( reply, SIGNAL( finished() ), this, SLOT( spotifyTrackLookupFinished() ) );
return true; // all we know now
}
void
GlobalActionManager::spotifyTrackLookupFinished()
{
QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( r );
if( r->error() == QNetworkReply::NoError )
{
QJson::Parser p;
bool ok;
QVariantMap res = p.parse( r, &ok ).toMap();
if( !ok )
{
tLog() << "Failed to parse json from Spotify track lookup:" << p.errorString() << "On line" << p.errorLine();
return;
} else if( !res.contains( "track" ) )
{
tLog() << "No 'track' item in the spotify track lookup result... not doing anything";
return;
}
// lets parse this baby
QVariantMap t = res.value( "track" ).toMap();
QString title, artist, album;
title = t.value( "name", QString() ).toString();
// TODO for now only take the first artist
if( t.contains( "artists" ) && t[ "artists" ].canConvert< QVariantList >() && t[ "artists" ].toList().size() > 0 )
artist = t[ "artists" ].toList().first().toMap().value( "name", QString() ).toString();
if( t.contains( "album" ) && t[ "album" ].canConvert< QVariantMap >() )
album = t[ "album" ].toMap().value( "name", QString() ).toString();
if( title.isEmpty() && artist.isEmpty() ) // don't have enough...
{
tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album;
return;
}
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), true );
handleOpenTrack( q );
} else
{
tLog() << "Error in network request to Spotify for track decoding:" << r->errorString();
}
}

View File

@ -38,6 +38,9 @@ public:
QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const;
QUrl openLink( const QString& title, const QString& artist, const QString& album ) const;
// spotify
bool parseSpotifyLink( const QString& link );
void copyToClipboard( const Tomahawk::query_ptr& query ) const;
QString copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist );
void savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename );
@ -47,12 +50,16 @@ public slots:
void waitingForResolved( bool );
Tomahawk::dynplaylist_ptr loadDynamicPlaylist( const QUrl& url, bool station );
private slots:
void bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl );
void showPlaylist();
void xspfCreated( const QByteArray& xspf );
// SPOTIFY
void spotifyTrackLookupFinished();
private:
explicit GlobalActionManager( QObject* parent = 0 );
void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q );
@ -67,6 +74,8 @@ private:
bool handleBookmarkCommand(const QUrl& url );
bool handleOpenCommand(const QUrl& url );
void handleOpenTrack( const Tomahawk::query_ptr& qry );
bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems );
QString hostname() const;

View File

@ -210,7 +210,7 @@ Pipeline::reportResults( QID qid, const QList< result_ptr >& results )
}
else
{
new FuncTimeout( 100, boost::bind( &Pipeline::timeoutShunt, this, q ), this );
new FuncTimeout( 0, boost::bind( &Pipeline::timeoutShunt, this, q ), this );
}
}
@ -247,7 +247,7 @@ Pipeline::shuntNext()
}
setQIDState( q, rc );
new FuncTimeout( 100, boost::bind( &Pipeline::shunt, this, q ), this );
new FuncTimeout( 0, boost::bind( &Pipeline::shunt, this, q ), this );
}
@ -285,7 +285,9 @@ Pipeline::shunt( const query_ptr& q )
emit resolving( q );
m_qidsTimeout.insert( q->id(), true );
new FuncTimeout( r->timeout(), boost::bind( &Pipeline::timeoutShunt, this, q ), this );
if ( r->timeout() > 0 )
new FuncTimeout( r->timeout(), boost::bind( &Pipeline::timeoutShunt, this, q ), this );
}
else
{

View File

@ -296,7 +296,12 @@ Playlist::createNewRevision( const QString& newrev, const QString& oldrev, const
{
qDebug() << Q_FUNC_INFO << newrev << oldrev << entries.count();
Q_ASSERT( !busy() );
if ( busy() )
{
m_revisionQueue.enqueue( RevisionQueueItem( newrev, oldrev, entries, oldrev == currentrevision() ) );
return;
}
if ( newrev != oldrev )
setBusy( true );
@ -370,6 +375,8 @@ Playlist::setRevision( const QString& rev,
}
else
emit revisionLoaded( pr );
checkRevisionQueue();
}
@ -569,3 +576,17 @@ Playlist::setBusy( bool b )
m_busy = b;
emit changed();
}
void
Playlist::checkRevisionQueue()
{
if ( !m_revisionQueue.isEmpty() )
{
RevisionQueueItem item = m_revisionQueue.dequeue();
if ( item.oldRev != currentrevision() && item.applyToTip ) // this was applied to the then-latest, but the already-running operation changed it so it's out of date now. fix it
{
item.oldRev = currentrevision();
}
createNewRevision( item.newRev, item.oldRev, item.entries );
}
}

View File

@ -30,6 +30,7 @@
#include "playlistinterface.h"
#include "dllmacro.h"
#include <QQueue>
class DatabaseCommand_LoadAllPlaylists;
class DatabaseCommand_SetPlaylistRevision;
@ -98,6 +99,18 @@ struct PlaylistRevision
bool applied; // false if conflict
};
struct RevisionQueueItem
{
public:
QString newRev;
QString oldRev;
QList< plentry_ptr > entries;
bool applyToTip;
RevisionQueueItem( const QString& nRev, const QString& oRev, const QList< plentry_ptr >& e, bool latest ) :
newRev( nRev ), oldRev( oRev), entries( e ), applyToTip( latest ) {}
};
class DLLEXPORT Playlist : public QObject, public PlaylistInterface
{
@ -264,6 +277,7 @@ private:
void init();
void setBusy( bool b );
void checkRevisionQueue();
source_ptr m_source;
QString m_currentrevision;
@ -277,6 +291,8 @@ private:
QList< plentry_ptr > m_initEntries;
QList< plentry_ptr > m_entries;
QQueue<RevisionQueueItem> m_revisionQueue;
bool m_locallyChanged;
bool m_busy;
};

View File

@ -167,7 +167,12 @@ DynamicPlaylist::createNewRevision( const QString& newrev,
const QList< dyncontrol_ptr>& controls,
const QList< plentry_ptr >& entries )
{
Q_ASSERT( !busy() );
if ( busy() )
{
m_revisionQueue.enqueue( DynQueueItem( newrev, oldrev, type, controls, (int)Static, entries, oldrev == currentrevision() ) );
return;
}
setBusy( true );
// get the newly added tracks
@ -207,6 +212,12 @@ DynamicPlaylist::createNewRevision( const QString& newrev,
const QString& type,
const QList< dyncontrol_ptr>& controls )
{
if ( busy() )
{
m_revisionQueue.enqueue( DynQueueItem( newrev, oldrev, type, controls, (int)OnDemand, QList< plentry_ptr >(), oldrev == currentrevision() ) );
return;
}
setBusy( true );
// can skip the entry stuff. just overwrite with new info
@ -496,3 +507,20 @@ DynamicPlaylist::variantsToControl( const QList< QVariantMap >& controlsV )
return realControls;
}
void
DynamicPlaylist::checkRevisionQueue()
{
if ( !m_revisionQueue.isEmpty() )
{
DynQueueItem item = m_revisionQueue.dequeue();
if ( item.oldRev != currentrevision() && item.applyToTip ) // this was applied to the then-latest, but the already-running operation changed it so it's out of date now. fix it
{
item.oldRev = currentrevision();
}
if( item.mode == Static )
createNewRevision( item.newRev, item.oldRev, item.type, item.controls, item.entries );
else
createNewRevision( item.newRev, item.oldRev, item.type, item.controls );
}
}

View File

@ -45,6 +45,8 @@ class DatabaseCommand_LoadDynamicPlaylist;
struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision
{
public:
QList< dyncontrol_ptr > controls;
Tomahawk::GeneratorMode mode;
QString type;
@ -62,6 +64,16 @@ struct DLLEXPORT DynamicPlaylistRevision : PlaylistRevision
DynamicPlaylistRevision() {}
};
struct DynQueueItem : RevisionQueueItem
{
QString type;
QList <dyncontrol_ptr> controls;
int mode;
DynQueueItem( const QString& nRev, const QString& oRev, const QString& typ, const QList< dyncontrol_ptr >& ctrls, int m, const QList< plentry_ptr >& e, bool latest ) :
RevisionQueueItem( nRev, oRev, e, latest ), type( typ ), controls( ctrls ), mode( m ) {}
};
class DLLEXPORT DynamicPlaylist : public Playlist
{
Q_OBJECT
@ -193,10 +205,14 @@ private:
bool shared,
bool autoLoad = true );
void checkRevisionQueue();
QList< dyncontrol_ptr > variantsToControl( const QList< QVariantMap >& controlsV );
geninterface_ptr m_generator;
bool m_autoLoad;
QQueue<DynQueueItem> m_revisionQueue;
};
}; // namespace

View File

@ -41,6 +41,7 @@ InfoBar::InfoBar( QWidget* parent )
boldFont.setPixelSize( 18 );
boldFont.setBold( true );
ui->captionLabel->setFont( boldFont );
ui->captionLabel->setElideMode( Qt::ElideNone );
boldFont.setPixelSize( 12 );
ui->descriptionLabel->setFont( boldFont );

View File

@ -36,7 +36,7 @@
#include "utils/logger.h"
#define PLAYING_ICON QString( RESPATH "images/now-playing-speaker.png" )
#define ARROW_ICON QString( RESPATH "images/forward.png" )
#define ARROW_ICON QString( RESPATH "images/info.png" )
using namespace Tomahawk;
@ -47,7 +47,16 @@ PlaylistItemDelegate::PlaylistItemDelegate( TrackView* parent, TrackProxyModel*
, m_model( proxy )
{
m_nowPlayingIcon = QPixmap( PLAYING_ICON );
m_arrowIcon = QPixmap( ARROW_ICON );
m_arrowIcon = QPixmap( ARROW_ICON ).scaled( 14, 14, Qt::KeepAspectRatio, Qt::SmoothTransformation );
m_topOption = QTextOption( Qt::AlignTop );
m_topOption.setWrapMode( QTextOption::NoWrap );
m_bottomOption = QTextOption( Qt::AlignBottom );
m_bottomOption.setWrapMode( QTextOption::NoWrap );
m_centerOption = QTextOption( Qt::AlignVCenter );
m_centerOption.setWrapMode( QTextOption::NoWrap );
}
@ -85,11 +94,10 @@ PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem&
void
PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItemV4* option, const QModelIndex& index ) const
PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItemV4* option, const QModelIndex& index, TrackModelItem* item ) const
{
initStyleOption( option, index );
TrackModelItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
if ( item->isPlaying() )
{
option->palette.setColor( QPalette::Highlight, option->palette.color( QPalette::Mid ) );
@ -138,7 +146,7 @@ PlaylistItemDelegate::paintShort( QPainter* painter, const QStyleOptionViewItem&
Q_ASSERT( item );
QStyleOptionViewItemV4 opt = option;
prepareStyleOption( &opt, index );
prepareStyleOption( &opt, index, item );
opt.text.clear();
qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter );
@ -215,16 +223,13 @@ PlaylistItemDelegate::paintShort( QPainter* painter, const QStyleOptionViewItem&
boldFont.setBold( true );
r.adjust( ir.width() + 12, 0, -12, 0 );
QTextOption to( Qt::AlignTop );
to.setWrapMode( QTextOption::NoWrap );
painter->setFont( boldFont );
QString text = painter->fontMetrics().elidedText( upperText, Qt::ElideRight, r.width() );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, to );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, m_topOption );
to.setAlignment( Qt::AlignBottom );
painter->setFont( opt.font );
text = painter->fontMetrics().elidedText( lowerText, Qt::ElideRight, r.width() );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, to );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, m_bottomOption );
}
painter->restore();
}
@ -237,7 +242,7 @@ PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewIt
Q_ASSERT( item );
QStyleOptionViewItemV4 opt = option;
prepareStyleOption( &opt, index );
prepareStyleOption( &opt, index, item );
opt.text.clear();
qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter );
@ -245,7 +250,7 @@ PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewIt
( index.column() == TrackModel::Artist || index.column() == TrackModel::Album ) )
{
opt.rect.setWidth( opt.rect.width() - 16 );
QRect arrowRect( opt.rect.x() + opt.rect.width(), opt.rect.y(), 14, opt.rect.height() );
QRect arrowRect( opt.rect.x() + opt.rect.width(), opt.rect.y() + 1, opt.rect.height() - 2, opt.rect.height() - 2 );
painter->drawPixmap( arrowRect, m_arrowIcon );
}
@ -286,19 +291,15 @@ PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewIt
}
painter->setPen( opt.palette.text().color() );
QTextOption to( Qt::AlignVCenter );
QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, r.width() - 3 );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, to );
painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, m_centerOption );
}
}
else
{
painter->setPen( opt.palette.text().color() );
QTextOption to( Qt::AlignVCenter );
QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, opt.rect.width() - 3 );
painter->drawText( opt.rect.adjusted( 3, 1, 0, 0 ), text, to );
painter->drawText( opt.rect.adjusted( 3, 1, 0, 0 ), text, m_centerOption );
}
painter->restore();

View File

@ -20,6 +20,7 @@
#define PLAYLISTITEMDELEGATE_H
#include <QStyledItemDelegate>
#include <QTextOption>
#include "trackmodel.h"
@ -46,7 +47,7 @@ protected:
QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
private:
void prepareStyleOption( QStyleOptionViewItemV4* option, const QModelIndex& index ) const;
void prepareStyleOption( QStyleOptionViewItemV4* option, const QModelIndex& index, TrackModelItem* item ) const;
void paintDetailed( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
void paintShort( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const;
@ -57,6 +58,10 @@ private:
QPixmap m_nowPlayingIcon;
QPixmap m_arrowIcon;
QTextOption m_topOption;
QTextOption m_centerOption;
QTextOption m_bottomOption;
TrackView* m_view;
TrackProxyModel* m_model;
};

View File

@ -67,4 +67,3 @@ SearchButton *SearchLineEdit::searchButton() const
{
return m_searchButton;
}

View File

@ -33,6 +33,7 @@
class ClearButton;
class SearchButton;
class DLLEXPORT SearchLineEdit : public LineEdit
{
Q_OBJECT
@ -47,7 +48,6 @@ private:
void init();
ClearButton *m_clearButton;
SearchButton *m_searchButton;
};
#endif // SEARCHLINEEDIT_H

View File

@ -51,6 +51,7 @@ TopBar::TopBar( QWidget* parent )
#ifdef Q_WS_MAC
ui->filterEdit->setAttribute( Qt::WA_MacShowFocusRect, 0 );
#endif
ui->filterEdit->setInactiveText( tr( "Filter" ) );
// initialise dudes
for( int i = 0; i < MAXDUDES; ++i )

View File

@ -462,7 +462,8 @@ TrackView::updateHoverIndex( const QPoint& pos )
}
}
setCursor( Qt::ArrowCursor );
if ( cursor().shape() != Qt::ArrowCursor )
setCursor( Qt::ArrowCursor );
}
@ -470,7 +471,12 @@ void
TrackView::wheelEvent( QWheelEvent* event )
{
QTreeView::wheelEvent( event );
updateHoverIndex( event->pos() );
if ( m_hoveredIndex.isValid() )
{
m_hoveredIndex = QModelIndex();
repaint();
}
}

View File

@ -40,7 +40,8 @@ TreeItemDelegate::TreeItemDelegate( ArtistView* parent, TreeProxyModel* proxy )
, m_model( proxy )
{
m_nowPlayingIcon = QPixmap( RESPATH "images/now-playing-speaker.png" );
m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" );
m_defaultAlbumCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" );
m_defaultArtistImage = QPixmap( RESPATH "images/no-artist-image-placeholder.png" );
}
@ -134,7 +135,15 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
// painter->drawPixmap( r, QPixmap( RESPATH "images/cover-shadow.png" ) );
QPixmap scover;
QPixmap cover = item->cover.isNull() ? m_defaultCover : item->cover;
QPixmap cover = item->cover;
if ( cover.isNull() )
{
if ( !item->artist().isNull() )
cover = m_defaultArtistImage;
else
cover = m_defaultAlbumCover;
}
if ( m_cache.contains( cover.cacheKey() ) )
{
scover = m_cache.value( cover.cacheKey() );

View File

@ -46,7 +46,8 @@ private:
mutable QHash< qint64, QPixmap > m_cache;
QPixmap m_nowPlayingIcon;
QPixmap m_defaultCover;
QPixmap m_defaultAlbumCover;
QPixmap m_defaultArtistImage;
};
#endif // TREEITEMDELEGATE_H

View File

@ -28,6 +28,7 @@
#include "pipeline.h"
#include "resolver.h"
#include "sourcelist.h"
#include "audio/audioengine.h"
#include "utils/logger.h"
@ -104,7 +105,19 @@ Query::addResults( const QList< Tomahawk::result_ptr >& newresults )
{
{
QMutexLocker lock( &m_mutex );
m_results.append( newresults );
/* const QStringList smt = AudioEngine::instance()->supportedMimeTypes();
foreach ( const Tomahawk::result_ptr& result, newresults )
{
if ( !smt.contains( result->mimetype() ) )
{
tDebug() << "Won't accept result, unsupported mimetype" << result->toString() << result->mimetype();
}
else
m_results.append( result );
}*/
m_results << newresults;
qStableSort( m_results.begin(), m_results.end(), Query::resultSorter );
// hook up signals, and check solved status

View File

@ -61,6 +61,8 @@ AnimatedSplitter::addWidget( AnimatedWidget* widget )
connect( widget, SIGNAL( showWidget() ), SLOT( onShowRequest() ) );
connect( widget, SIGNAL( hideWidget() ), SLOT( onHideRequest() ) );
connect( widget, SIGNAL( sizeChanged( QSize) ), SLOT( onSizeChanged( QSize ) ) );
connect( this, SIGNAL( shown( QWidget*, bool ) ), widget, SLOT( onShown( QWidget*, bool ) ) );
connect( this, SIGNAL( hidden( QWidget*, bool ) ), widget, SLOT( onHidden( QWidget*, bool ) ) );
}
@ -88,17 +90,51 @@ AnimatedSplitter::onHideRequest()
}
void
AnimatedSplitter::onSizeChanged( const QSize& size )
{
AnimatedWidget* w = (AnimatedWidget*)(sender());
int wi = indexOf( w );
QList< int > sizes;
for ( int i = 0; i < count(); i ++ )
{
int j = 0;
if ( i == m_greedyIndex )
{
j = height() - size.height();
}
else if ( i == wi )
{
j = size.height();
}
else
{
j = widget( i )->height();
}
sizes << j;
}
setSizes( sizes );
}
void
AnimatedSplitter::setGreedyWidget( int index )
{
m_greedyIndex = index;
if( !widget( index ) )
return;
m_greedyIndex = index;
QSizePolicy policy = widget( m_greedyIndex )->sizePolicy();
if( orientation() == Qt::Horizontal )
policy.setHorizontalStretch( 1 );
else
policy.setVerticalStretch( 1 );
widget( m_greedyIndex )->setSizePolicy( policy );
}
@ -180,6 +216,9 @@ void
AnimatedWidget::onAnimationStep( int frame )
{
setFixedHeight( frame );
QSize s( 0, frame ); //FIXME
emit sizeChanged( s );
}

View File

@ -49,6 +49,8 @@ private slots:
void onShowRequest();
void onHideRequest();
void onSizeChanged( const QSize& size );
private:
int m_greedyIndex;
};
@ -73,6 +75,7 @@ signals:
void showWidget();
void hideWidget();
void sizeChanged( const QSize& size );
void hiddenSizeChanged();
private slots:

View File

@ -0,0 +1,191 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "jspfloader.h"
#include <QApplication>
#include <QDomDocument>
#include <QMessageBox>
#include <qjson/parser.h>
#include "utils/tomahawkutils.h"
#include "utils/logger.h"
#include "sourcelist.h"
#include "playlist.h"
using namespace Tomahawk;
void
JSPFLoader::load( const QUrl& url )
{
QNetworkRequest request( url );
Q_ASSERT( TomahawkUtils::nam() != 0 );
QNetworkReply* reply = TomahawkUtils::nam()->get( request );
// isn't there a race condition here? something could happen before we connect()
// no---the event loop is needed to make the request, i think (leo)
connect( reply, SIGNAL( finished() ),
SLOT( networkLoadFinished() ) );
connect( reply, SIGNAL( error( QNetworkReply::NetworkError ) ),
SLOT( networkError( QNetworkReply::NetworkError ) ) );
}
void
JSPFLoader::load( QFile& file )
{
if( file.open( QFile::ReadOnly ) )
{
m_body = file.readAll();
gotBody();
}
else
{
tLog() << "Failed to open jspf file";
reportError();
}
}
void
JSPFLoader::reportError()
{
emit failed();
deleteLater();
}
void
JSPFLoader::networkLoadFinished()
{
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
m_body = reply->readAll();
gotBody();
}
void
JSPFLoader::networkError( QNetworkReply::NetworkError e )
{
tLog() << Q_FUNC_INFO << "Network error loading jspf" << e;
reportError();
}
void
JSPFLoader::gotBody()
{
QJson::Parser p;
bool retOk;
QVariantMap wrapper = p.parse( m_body, &retOk ).toMap();
if ( !retOk )
{
tLog() << "Failed to parse jspf json:" << p.errorString() << "on line" << p.errorLine();
return;
}
if ( !wrapper.contains( "playlist" ) )
{
tLog() << "No playlist element in JSPF!";
return;
}
QVariantMap pl = wrapper.value( "playlist" ).toMap();
QString origTitle = pl.value( "title" ).toString();
m_info = pl.value( "info" ).toString();
m_creator = pl.value( "creator" ).toString();
m_title = origTitle;
if ( m_title.isEmpty() )
m_title = tr( "New Playlist" );
if ( !m_overrideTitle.isEmpty() )
m_title = m_overrideTitle;
if ( pl.contains( "track" ) )
{
QVariantList tracks = pl.value( "track" ).toList();
bool shownError = false;
foreach ( const QVariant& track, tracks )
{
QVariantMap tM = track.toMap();
QString artist, album, track, duration, annotation, url;
artist = tM.value( "creator" ).toString();
album = tM.value( "album" ).toString();
track = tM.value( "title" ).toString();
duration = tM.value( "duration" ).toString();
annotation = tM.value( "annotation" ).toString();
if ( tM.value( "location" ).toList().size() > 0 )
url = tM.value( "location" ).toList().first().toString();
if( artist.isEmpty() || track.isEmpty() )
{
if( !shownError )
{
QMessageBox::warning( 0, tr( "Failed to save tracks" ), tr( "Some tracks in the playlist do not contain an artist and a title. They will be ignored." ), QMessageBox::Ok );
shownError = true;
}
continue;
}
query_ptr q = Tomahawk::Query::get( artist, track, album, uuid() );
q->setDuration( duration.toInt() / 1000 );
if( !url.isEmpty() )
q->setResultHint( url );
m_entries << q;
}
}
if ( origTitle.isEmpty() && m_entries.isEmpty() )
{
if ( m_autoCreate )
{
QMessageBox::critical( 0, tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) );
deleteLater();
return;
}
else
{
emit failed();
return;
}
}
if ( m_autoCreate )
{
m_playlist = Playlist::create( SourceList::instance()->getLocal(),
uuid(),
m_title,
m_info,
m_creator,
false,
m_entries );
deleteLater();
}
emit ok( m_playlist );
}

View File

@ -0,0 +1,77 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Leo Franchi <lfranchi@kde.org
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef JSPFLOADER_H
#define JSPFLOADER_H
#include <QObject>
#include <QUrl>
#include <QFile>
#include <QNetworkReply>
#include <QNetworkRequest>
#include "playlist.h"
#include "typedefs.h"
#include "dllmacro.h"
namespace Tomahawk
{
class DLLEXPORT JSPFLoader : public QObject
{
Q_OBJECT
public:
explicit JSPFLoader( bool autoCreate = true, QObject* parent = 0 )
: QObject( parent )
, m_autoCreate( autoCreate )
{}
virtual ~JSPFLoader() {}
QList< Tomahawk::query_ptr > entries() const { return m_entries; }
void setOverrideTitle( const QString& newTitle ) { m_overrideTitle = newTitle; }
signals:
void failed();
void ok( const Tomahawk::playlist_ptr& );
public slots:
void load( const QUrl& url );
void load( QFile& file );
private slots:
void networkLoadFinished();
void networkError( QNetworkReply::NetworkError e );
private:
void reportError();
void gotBody();
bool m_autoCreate;
QList< Tomahawk::query_ptr > m_entries;
QString m_title, m_info, m_creator, m_overrideTitle;
QByteArray m_body;
Tomahawk::playlist_ptr m_playlist;
};
}
#endif // JSPFLOADER_H

View File

@ -719,6 +719,7 @@ ViewManager::updateView()
else
m_topbar->setVisible( true );
m_infobar->setVisible( currentPage()->showInfoBar() );
m_infobar->setCaption( currentPage()->title() );
m_infobar->setDescription( currentPage()->description() );
m_infobar->setLongDescription( currentPage()->longDescription() );

View File

@ -46,6 +46,7 @@ public:
virtual QPixmap pixmap() const { return QPixmap( RESPATH "icons/tomahawk-icon-128x128.png" ); }
virtual bool showStatsBar() const { return true; }
virtual bool showInfoBar() const { return true; }
virtual bool showModes() const { return false; }
virtual bool showFilter() const { return false; }
virtual bool queueVisible() const { return true; }

View File

@ -51,6 +51,8 @@ ArtistInfoWidget::ArtistInfoWidget( const Tomahawk::artist_ptr& artist, QWidget*
ui->topHits->setAttribute( Qt::WA_MacShowFocusRect, 0 );
TomahawkUtils::unmarginLayout( layout() );
TomahawkUtils::unmarginLayout( ui->layoutWidget->layout() );
TomahawkUtils::unmarginLayout( ui->layoutWidget1->layout() );
m_albumsModel = new TreeModel( ui->albums );
ui->albums->setTreeModel( m_albumsModel );

View File

@ -43,7 +43,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="layoutWidget">
<widget class="QWidget" name="layoutWidget1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="HeaderLabel" name="label_2">

View File

@ -0,0 +1,82 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MACLINEEDIT_H
#define MACLINEEDIT_H
#include <QMacCocoaViewContainer>
class SearchTargetWrapper;
class LineEditInterface {
public:
LineEditInterface(QWidget* widget) : widget_(widget) {}
QWidget* widget() const { return widget_; }
virtual ~LineEditInterface() {}
virtual void clear() { set_text(QString()); }
virtual void set_focus() = 0;
virtual QString text() const = 0;
virtual void set_text(const QString& text) = 0;
virtual QString hint() const = 0;
virtual void set_hint(const QString& hint) = 0;
virtual void clear_hint() = 0;
virtual void set_enabled(bool enabled) = 0;
protected:
QWidget* widget_;
};
class MacLineEdit : public QMacCocoaViewContainer, public LineEditInterface {
Q_OBJECT
Q_PROPERTY(QString hint READ hint WRITE set_hint);
public:
MacLineEdit(QWidget* parent = 0);
~MacLineEdit();
QString hint() const { return hint_; }
void set_hint(const QString& hint);
void clear_hint() { set_hint(QString()); }
void paintEvent(QPaintEvent* e);
void set_text(const QString&);
QString text() const;
void set_focus() {}
void set_enabled(bool enabled);
signals:
void textChanged(const QString& text);
void textEdited(const QString& text);
private:
// Called by NSSearchFieldCell when the text changes.
void TextChanged(const QString& text);
QString hint_;
friend class SearchTargetWrapper;
SearchTargetWrapper* wrapper_;
};
#endif // MACLINEEDIT_H

View File

@ -0,0 +1,140 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "maclineedit.h"
#import <Foundation/NSAutoreleasePool.h>
#include <QtDebug>
@interface SearchTarget : NSObject {
SearchTargetWrapper* wrapper_;
}
- (id) initWithWrapper: (SearchTargetWrapper*)wrapper;
- (void) action;
@end
class SearchTargetWrapper {
public:
explicit SearchTargetWrapper(NSSearchField* search, MacLineEdit* lineedit);
void TextChanged();
QString text() const;
void setText(const QString& text);
void SetHint(const QString& hint);
void SetEnabled(bool enabled);
private:
NSSearchField* search_;
SearchTarget* target_;
MacLineEdit* lineedit_;
};
@implementation SearchTarget
- (id) initWithWrapper: (SearchTargetWrapper*)wrapper {
wrapper_ = wrapper;
return self;
}
- (void) action {
wrapper_->TextChanged();
}
@end
SearchTargetWrapper::SearchTargetWrapper(NSSearchField* search, MacLineEdit* lineedit)
: search_(search),
lineedit_(lineedit) {
target_ = [[SearchTarget alloc] initWithWrapper:this];
[[search cell] setSendsWholeSearchString:true];
[[search cell] setTarget:target_];
[[search cell] setAction:@selector(action)];
}
void SearchTargetWrapper::TextChanged() {
NSString* text = [[search_ cell] stringValue];
lineedit_->TextChanged(QString::fromUtf8([text UTF8String]));
}
QString SearchTargetWrapper::text() const {
NSString* text = [[search_ cell] stringValue];
return QString::fromUtf8([text UTF8String]);
}
void SearchTargetWrapper::setText(const QString& text) {
NSString* t = [[NSString alloc] initWithUTF8String:text.toUtf8().constData()];
[[search_ cell] setStringValue:t];
[t release];
}
void SearchTargetWrapper::SetHint(const QString& hint) {
NSString* t = [[NSString alloc] initWithUTF8String:hint.toUtf8().constData()];
[[search_ cell] setPlaceholderString:t];
[t release];
}
void SearchTargetWrapper::SetEnabled(bool enabled) {
[search_ setEnabled:enabled];
}
MacLineEdit::MacLineEdit(QWidget* parent)
: QMacCocoaViewContainer(0, parent),
LineEditInterface(this) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSSearchField* search = [[NSSearchField alloc] init];
setCocoaView(search);
wrapper_ = new SearchTargetWrapper(search, this);
[search release];
[pool release]; // Pool's closed.
}
MacLineEdit::~MacLineEdit() {
delete wrapper_;
}
void MacLineEdit::paintEvent(QPaintEvent* e) {
QMacCocoaViewContainer::paintEvent(e);
}
void MacLineEdit::TextChanged(const QString& text) {
emit textChanged(text);
emit textEdited(text);
}
QString MacLineEdit::text() const {
return wrapper_->text();
}
void MacLineEdit::set_text(const QString& text) {
wrapper_->setText(text);
}
void MacLineEdit::set_enabled(bool enabled) {
wrapper_->SetEnabled(enabled);
}
void MacLineEdit::set_hint(const QString& hint) {
wrapper_->SetHint(hint);
}

View File

@ -87,6 +87,7 @@ public:
virtual QString description() const { return QString(); }
virtual bool showStatsBar() const { return false; }
virtual bool showInfoBar() const { return false; }
virtual bool jumpToCurrentTrack() { return false; }

View File

@ -19,6 +19,8 @@
#include "scriptresolver.h"
#include <QtEndian>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkProxy>
#include "artist.h"
#include "album.h"
@ -52,6 +54,11 @@ ScriptResolver::ScriptResolver( const QString& exe )
m_error = Tomahawk::ExternalResolver::FileNotFound;
else
m_proc.start( filePath() );
if ( !TomahawkUtils::nam() )
return;
sendConfig();
}
@ -65,6 +72,31 @@ ScriptResolver::~ScriptResolver()
delete m_configWidget.data();
}
void
ScriptResolver::sendConfig()
{
// Send a configutaion message with any information the resolver might need
// For now, only the proxy information is sent
QVariantMap m;
m.insert( "_msgtype", "config" );
TomahawkUtils::NetworkProxyFactory* factory = dynamic_cast<TomahawkUtils::NetworkProxyFactory*>( TomahawkUtils::nam()->proxyFactory() );
QNetworkProxy proxy = factory->proxy();
QString proxyType = ( proxy.type() == QNetworkProxy::Socks5Proxy ? "socks5" : "none" );
m.insert( "proxytype", proxyType );
m.insert( "proxyhost", proxy.hostName() );
m.insert( "proxyport", proxy.port() );
m.insert( "proxyuser", proxy.user() );
m.insert( "proxypass", proxy.password() );
// QJson sucks
QVariantList hosts;
foreach ( const QString& host, factory->noProxyHosts() )
hosts << host;
m.insert( "noproxyhosts", hosts );
QByteArray data = m_serializer.serialize( m );
sendMsg( data );
}
void
ScriptResolver::reload()
@ -75,6 +107,8 @@ ScriptResolver::reload()
{
m_proc.start( filePath() );
m_error = Tomahawk::ExternalResolver::NoError;
sendConfig();
}
}
@ -124,7 +158,7 @@ ScriptResolver::readStdout()
void
ScriptResolver::sendMsg( const QByteArray& msg )
{
// qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length();
// qDebug() << Q_FUNC_INFO << m_ready << msg << msg.length();
if( !m_proc.isOpen() )
return;
@ -226,6 +260,7 @@ ScriptResolver::cmdExited( int code, QProcess::ExitStatus status )
m_num_restarts++;
tLog() << "*** Restart num" << m_num_restarts;
m_proc.start( filePath() );
sendConfig();
}
else
{
@ -266,7 +301,7 @@ ScriptResolver::doSetup( const QVariantMap& m )
m_name = m.value( "name" ).toString();
m_weight = m.value( "weight", 0 ).toUInt();
m_timeout = m.value( "timeout", 25 ).toUInt() * 1000;
m_timeout = m.value( "timeout", 5 ).toUInt() * 1000;
qDebug() << "SCRIPT" << filePath() << "READY," << "name" << m_name << "weight" << m_weight << "timeout" << m_timeout;
m_ready = true;

View File

@ -63,6 +63,8 @@ private slots:
void cmdExited( int code, QProcess::ExitStatus status );
private:
void sendConfig();
void handleMsg( const QByteArray& msg );
void sendMsg( const QByteArray& msg );
void doSetup( const QVariantMap& m );

View File

@ -170,11 +170,19 @@ CollectionItem::playlistsAddedInternal( SourceTreeItem* parent, const QList< dyn
items << plItem;
if( p->mode() == Static ) {
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onAutoPlaylistDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
if( m_source->isLocal() )
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onAutoPlaylistDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
else
connect( p.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onAutoPlaylistDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
} else {
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onStationDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
if( m_source->isLocal() )
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onStationDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
else
connect( p.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ),
SLOT( onStationDeleted( Tomahawk::dynplaylist_ptr ) ), Qt::QueuedConnection );
}
}
parent->endRowsAdded();
@ -196,6 +204,24 @@ CollectionItem::playlistDeletedInternal( SourceTreeItem* parent, const T& p )
break;
}
}
if( ( parent == m_playlists || parent == m_stations ) &&
parent->children().isEmpty() && parent->parent() ) // Don't leave an empty Playlist or Station category
{
int idx = parent->parent()->children().indexOf( parent );
if( idx < 0 )
return;
parent->parent()->beginRowsRemoved( idx, idx );
parent->parent()->removeChild( parent );
parent->parent()->endRowsRemoved();
if( parent == m_playlists )
m_playlists = 0;
else if( parent == m_stations )
m_stations = 0;
delete parent;
}
}
@ -228,8 +254,12 @@ CollectionItem::onPlaylistsAdded( const QList< playlist_ptr >& playlists )
p->loadRevision();
items << plItem;
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::playlist_ptr ) ),
SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ), Qt::QueuedConnection );
if( m_source->isLocal() )
connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::playlist_ptr ) ),
SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ), Qt::QueuedConnection );
else
connect( p.data(), SIGNAL( deleted( Tomahawk::playlist_ptr ) ),
SLOT( onPlaylistDeleted( Tomahawk::playlist_ptr ) ), Qt::QueuedConnection );
}
m_playlists->endRowsAdded();

View File

@ -92,6 +92,10 @@ SourceTreeView::SourceTreeView( QWidget* parent )
setSortingEnabled( true );
sortByColumn( 0, Qt::AscendingOrder );
#ifdef Q_OS_MAC
setVerticalScrollMode( QTreeView::ScrollPerPixel );
#endif
// TODO animation conflicts with the expanding-playlists-when-collection-is-null
// so investigate
// setAnimated( true );

View File

@ -55,6 +55,7 @@
#include "audio/audioengine.h"
#include "utils/xspfloader.h"
#include "utils/jspfloader.h"
#include "utils/logger.h"
#include "utils/tomahawkutils.h"
@ -517,15 +518,25 @@ TomahawkApp::loadUrl( const QString& url )
activate();
if ( url.startsWith( "tomahawk://" ) )
return GlobalActionManager::instance()->parseTomahawkLink( url );
else if ( url.contains( "open.spotify.com" ) || url.contains( "spotify:track" ) )
return GlobalActionManager::instance()->parseSpotifyLink( url );
else
{
QFile f( url );
QFileInfo info( f );
if ( f.exists() && info.suffix() == "xspf" ) {
if ( info.suffix() == "xspf" )
{
XSPFLoader* l = new XSPFLoader( true, this );
tDebug( LOGINFO ) << "Loading spiff:" << url;
l->load( QUrl::fromUserInput( url ) );
return true;
} else if ( info.suffix() == "jspf" )
{
JSPFLoader* l = new JSPFLoader( true, this );
tDebug( LOGINFO ) << "Loading j-spiff:" << url;
l->load( QUrl::fromUserInput( url ) );
return true;
}
}

View File

@ -65,6 +65,9 @@
#ifdef Q_OS_WIN32
#include <qtsparkle/Updater>
#endif
#ifdef Q_OS_MAC
#include "widgets/maclineedit.h"
#endif
#include "utils/logger.h"
@ -210,35 +213,50 @@ TomahawkWindow::setupSideBar()
ui->splitter->setHandleWidth( 1 );
ui->actionShowOfflineSources->setChecked( TomahawkSettings::instance()->showOfflineSources() );
}
void
TomahawkWindow::setupToolBar()
{
m_searchBox = new QWidget();
m_searchWidget->setupUi( m_searchBox );
QToolBar* toolbar = addToolBar( "TomahawkToolbar" );
toolbar->setObjectName( "TomahawkToolbar" );
toolbar->setMovable( false );
toolbar->setFloatable( false );
toolbar->setIconSize( QSize( 32, 32 ) );
toolbar->setIconSize( QSize( 28, 28 ) );
toolbar->setToolButtonStyle( Qt::ToolButtonFollowStyle );
toolbar->installEventFilter( new WidgetDragFilter( toolbar ) );
toolbar->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
m_backAvailable = toolbar->addAction( QIcon( RESPATH "images/back.png" ), tr( "Back" ), ViewManager::instance(), SLOT( historyBack() ) );
m_backAvailable->setToolTip( tr( "Go back one page" ) );
m_forwardAvailable = toolbar->addAction( QIcon( RESPATH "images/forward.png" ), tr( "Forward" ), ViewManager::instance(), SLOT( historyForward() ) );
m_forwardAvailable->setToolTip( tr( "Go forward one page" ) );
m_searchBox = new QWidget( toolbar );
#ifdef Q_OS_MAC
QWidget *spacerWidget = new QWidget( this );
spacerWidget->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
spacerWidget->setVisible( true );
toolbar->addWidget( spacerWidget );
m_searchBox->setLayout( new QHBoxLayout() );
MacLineEdit* lineEdit = new MacLineEdit( m_searchBox );
lineEdit->setFixedSize( 256, 28 );
lineEdit->set_hint( tr( "Search" ) );
lineEdit->setVisible( true );
m_searchBox->layout()->addWidget( lineEdit );
connect( lineEdit, SIGNAL( textChanged( QString ) ), SLOT( onSearch( QString ) ) );
#else
m_searchWidget->setupUi( m_searchBox );
m_searchWidget->searchEdit->setStyleSheet( "QLineEdit { border: 1px solid gray; border-radius: 6px; margin-right: 2px; }" );
#ifdef Q_WS_MAC
m_searchWidget->searchEdit->setAttribute( Qt::WA_MacShowFocusRect, 0 );
connect( m_searchWidget->searchEdit, SIGNAL( returnPressed() ), SLOT( onFilterEdited() ) );
#endif
connect( m_searchWidget->searchEdit, SIGNAL( returnPressed() ), SLOT( onSearch() ) );
toolbar->addWidget( m_searchBox );
}
@ -677,9 +695,17 @@ TomahawkWindow::checkForUpdates()
void
TomahawkWindow::onSearch()
TomahawkWindow::onSearch( const QString& search )
{
ViewManager::instance()->show( new SearchWidget( m_searchWidget->searchEdit->text(), this ) );
if ( !search.trimmed().isEmpty() )
ViewManager::instance()->show( new SearchWidget( search, this ) );
}
void
TomahawkWindow::onFilterEdited()
{
onSearch( m_searchWidget->searchEdit->text() );
m_searchWidget->searchEdit->clear();
}

View File

@ -93,7 +93,8 @@ private slots:
void onSipPluginAdded( SipPlugin* p );
void onSipPluginRemoved( SipPlugin* p );
void onSearch();
void onSearch( const QString& search );
void onFilterEdited();
void minimize();
void maximize();