Merge branch 'master' into databasegenerator
Conflicts: src/libtomahawk/CMakeLists.txt src/libtomahawk/database/databasecommand_createplaylist.cpp src/libtomahawk/playlist/dynamic/DynamicPlaylist.h
@ -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)
|
||||
|
@ -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
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
data/images/filter.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
0
data/images/home.png
Executable file → Normal file
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.0 KiB |
0
data/images/music-icon.png
Executable file → Normal file
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
BIN
data/images/share.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
0
data/images/view-refresh.png
Executable file → Normal file
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@ -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>
|
||||
|
@ -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}
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -67,4 +67,3 @@ SearchButton *SearchLineEdit::searchButton() const
|
||||
{
|
||||
return m_searchButton;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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() );
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
191
src/libtomahawk/utils/jspfloader.cpp
Normal 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 );
|
||||
}
|
77
src/libtomahawk/utils/jspfloader.h
Normal 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
|
@ -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() );
|
||||
|
@ -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; }
|
||||
|
@ -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 );
|
||||
|
@ -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">
|
||||
|
82
src/libtomahawk/widgets/maclineedit.h
Normal 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
|
140
src/libtomahawk/widgets/maclineedit.mm
Normal 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);
|
||||
}
|
@ -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; }
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 );
|
||||
|
@ -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();
|
||||
|
@ -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 );
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|