From 413052bf8e76c007cbc9c620a329ca1e47f2cdbf Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 22 Feb 2012 02:09:24 +0100 Subject: [PATCH 01/20] * Destroy Pipeline properly and deactivate it earlier during shutdown. --- .../database/databasecommand_resolve.cpp | 7 ++++++- src/libtomahawk/pipeline.cpp | 1 + src/libtomahawk/pipeline.h | 2 ++ src/tomahawkapp.cpp | 14 ++++++-------- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 1384f828e..ae6e92910 100644 --- a/src/libtomahawk/database/databasecommand_resolve.cpp +++ b/src/libtomahawk/database/databasecommand_resolve.cpp @@ -20,6 +20,7 @@ #include "artist.h" #include "album.h" +#include "pipeline.h" #include "sourcelist.h" #include "utils/logger.h" @@ -30,10 +31,14 @@ DatabaseCommand_Resolve::DatabaseCommand_Resolve( const query_ptr& query ) : DatabaseCommand() , m_query( query ) { + Q_ASSERT( Pipeline::instance()->isRunning() ); } + DatabaseCommand_Resolve::~DatabaseCommand_Resolve() -{} +{ +} + void DatabaseCommand_Resolve::exec( DatabaseImpl* lib ) diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index 73d14fce4..18eb69347 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -61,6 +61,7 @@ Pipeline::Pipeline( QObject* parent ) Pipeline::~Pipeline() { + tDebug() << Q_FUNC_INFO; m_running = false; // stop script resolvers diff --git a/src/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h index 8ef9a607b..fdbd519d6 100644 --- a/src/libtomahawk/pipeline.h +++ b/src/libtomahawk/pipeline.h @@ -48,6 +48,8 @@ public: explicit Pipeline( QObject* parent = 0 ); virtual ~Pipeline(); + bool isRunning() const { return m_running; } + unsigned int pendingQueryCount() const { return m_queries_pending.count(); } unsigned int activeQueryCount() const { return m_qidsState.count(); } diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 138d6827f..9b28e3f50 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -19,8 +19,6 @@ #include "tomahawkapp.h" -#include - #include #include #include @@ -170,7 +168,7 @@ TomahawkApp::init() m_scanManager = QWeakPointer( new ScanManager( this ) ); // init pipeline and resolver factories - new Pipeline( this ); + new Pipeline(); #ifndef ENABLE_HEADLESS Pipeline::instance()->addExternalResolverFactory( boost::bind( &QtScriptResolver::factory, _1 ) ); @@ -298,6 +296,8 @@ TomahawkApp::~TomahawkApp() { tLog() << "Shutting down Tomahawk..."; + Pipeline::instance()->stop(); + if ( !m_servent.isNull() ) delete m_servent.data(); if ( !m_scanManager.isNull() ) @@ -313,8 +313,6 @@ TomahawkApp::~TomahawkApp() delete SipHandler::instance(); - Pipeline::instance()->stop(); - #ifndef ENABLE_HEADLESS delete m_mainwindow; #ifdef LIBATTICA_FOUND @@ -322,11 +320,11 @@ TomahawkApp::~TomahawkApp() #endif #endif + delete Pipeline::instance(); + if ( !m_database.isNull() ) delete m_database.data(); - delete Pipeline::instance(); - tLog() << "Finished shutdown."; } @@ -464,7 +462,6 @@ TomahawkApp::initHTTP() tLog() << "Starting HTTPd on" << m_session.listenInterface().toString() << m_session.port(); m_session.start(); - } @@ -518,6 +515,7 @@ TomahawkApp::initServent() } } + // Called after Servent emits ready() void TomahawkApp::initSIP() From 165276912f257793997b4928017059ec7401122a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 22 Feb 2012 02:42:42 +0100 Subject: [PATCH 02/20] * Fixed Windows compiling. --- src/tomahawkapp.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 9b28e3f50..058b7bed1 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -19,6 +19,8 @@ #include "tomahawkapp.h" +#include + #include #include #include @@ -59,7 +61,6 @@ #include "utils/logger.h" #include "utils/tomahawkutilsgui.h" -#include #include "config.h" #ifndef ENABLE_HEADLESS @@ -309,8 +310,6 @@ TomahawkApp::~TomahawkApp() if ( !m_infoSystem.isNull() ) delete m_infoSystem.data(); - //FIXME: delete GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); ? - delete SipHandler::instance(); #ifndef ENABLE_HEADLESS From 79d8b081d773c6ff37da8761c7f7910ea4d4e4bf Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 03:21:21 +0100 Subject: [PATCH 03/20] * Speed up cover loading in TreeView. --- src/audiocontrols.cpp | 6 +-- src/libtomahawk/album.cpp | 36 ++++++++++++++-- src/libtomahawk/album.h | 14 +++++- src/libtomahawk/artist.cpp | 36 ++++++++++++++-- src/libtomahawk/artist.h | 14 +++++- src/libtomahawk/audio/audioengine.cpp | 4 +- .../playlist/albumitemdelegate.cpp | 19 ++------ src/libtomahawk/playlist/albumitemdelegate.h | 1 - src/libtomahawk/playlist/artistview.cpp | 43 +++++++++++++++++++ src/libtomahawk/playlist/artistview.h | 3 ++ src/libtomahawk/playlist/treeitemdelegate.cpp | 16 ++----- src/libtomahawk/playlist/treeitemdelegate.h | 2 - src/libtomahawk/playlist/treemodel.cpp | 12 ++++++ src/libtomahawk/playlist/treemodel.h | 2 + .../widgets/infowidgets/AlbumInfoWidget.cpp | 4 +- .../widgets/infowidgets/ArtistInfoWidget.cpp | 4 +- src/tomahawkapp.cpp | 5 +-- 17 files changed, 165 insertions(+), 56 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index f536be7c6..20c7637c9 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -264,11 +264,11 @@ AudioControls::onAlbumCoverUpdated() void AudioControls::setAlbumCover() { - if ( !m_currentTrack->album()->cover().isNull() ) + if ( !m_currentTrack->album()->cover( ui->coverImage->size() ).isNull() ) { QPixmap cover; - cover.loadFromData( m_currentTrack->album()->cover() ); - ui->coverImage->setPixmap( cover.scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); + cover = m_currentTrack->album()->cover( ui->coverImage->size() ); + ui->coverImage->setPixmap( cover ); } else ui->coverImage->setPixmap( m_defaultCover ); diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp index fd56e8409..846dc15a6 100644 --- a/src/libtomahawk/album.cpp +++ b/src/libtomahawk/album.cpp @@ -32,6 +32,7 @@ using namespace Tomahawk; Album::~Album() { + delete m_cover; } @@ -71,6 +72,7 @@ Album::Album( unsigned int id, const QString& name, const Tomahawk::artist_ptr& , m_id( id ) , m_name( name ) , m_artist( artist ) + , m_cover( 0 ) , m_infoLoaded( false ) { connect( Tomahawk::InfoSystem::InfoSystem::instance(), @@ -97,11 +99,14 @@ Album::artist() const } -QByteArray -Album::cover() const +#ifndef ENABLE_HEADLESS +QPixmap +Album::cover( const QSize& size, bool forceLoad ) const { if ( !m_infoLoaded ) { + if ( !forceLoad ) + return QPixmap(); m_uuid = uuid(); Tomahawk::InfoSystem::InfoStringHash trackInfo; @@ -117,8 +122,31 @@ Album::cover() const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - return m_cover; + if ( !m_cover ) + m_cover = new QPixmap(); + + if ( m_cover->isNull() && !m_coverBuffer.isEmpty() ) + { + m_cover->loadFromData( m_coverBuffer ); + } + + if ( !m_cover->isNull() && !size.isEmpty() ) + { + if ( m_coverCache.contains( size.width() ) ) + { + return m_coverCache.value( size.width() ); + } + else + { + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + } + } + + return *m_cover; } +#endif void @@ -137,7 +165,7 @@ Album::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVaria const QByteArray ba = returnedData["imgbytes"].toByteArray(); if ( ba.length() ) { - m_cover = ba; + m_coverBuffer = ba; } } diff --git a/src/libtomahawk/album.h b/src/libtomahawk/album.h index b929e9b49..741c7d65a 100644 --- a/src/libtomahawk/album.h +++ b/src/libtomahawk/album.h @@ -19,8 +19,13 @@ #ifndef TOMAHAWKALBUM_H #define TOMAHAWKALBUM_H +#include "config.h" + #include #include +#ifndef ENABLE_HEADLESS + #include +#endif #include "typedefs.h" #include "playlistinterface.h" @@ -44,7 +49,9 @@ public: unsigned int id() const { return m_id; } QString name() const { return m_name; } artist_ptr artist() const; - QByteArray cover() const; +#ifndef ENABLE_HEADLESS + QPixmap cover( const QSize& size, bool forceLoad = true ) const; +#endif bool infoLoaded() const { return m_infoLoaded; } Tomahawk::playlistinterface_ptr playlistInterface(); @@ -64,10 +71,13 @@ private: unsigned int m_id; QString m_name; artist_ptr m_artist; - QByteArray m_cover; + QByteArray m_coverBuffer; + mutable QPixmap* m_cover; bool m_infoLoaded; mutable QString m_uuid; + mutable QHash< int, QPixmap > m_coverCache; + Tomahawk::playlistinterface_ptr m_playlistInterface; }; diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp index 0c0ba332c..534e15825 100644 --- a/src/libtomahawk/artist.cpp +++ b/src/libtomahawk/artist.cpp @@ -31,6 +31,7 @@ using namespace Tomahawk; Artist::~Artist() { + delete m_cover; } @@ -69,6 +70,7 @@ Artist::Artist( unsigned int id, const QString& name ) : QObject() , m_id( id ) , m_name( name ) + , m_cover( 0 ) , m_infoLoaded( false ) { m_sortname = DatabaseImpl::sortname( name, true ); @@ -89,11 +91,14 @@ Artist::onTracksAdded( const QList& tracks ) } -QByteArray -Artist::cover() const +#ifndef ENABLE_HEADLESS +QPixmap +Artist::cover( const QSize& size, bool forceLoad ) const { if ( !m_infoLoaded ) { + if ( !forceLoad ) + return QPixmap(); m_uuid = uuid(); Tomahawk::InfoSystem::InfoStringHash trackInfo; @@ -108,8 +113,31 @@ Artist::cover() const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - return m_cover; + if ( !m_cover ) + m_cover = new QPixmap(); + + if ( m_cover->isNull() && !m_coverBuffer.isEmpty() ) + { + m_cover->loadFromData( m_coverBuffer ); + } + + if ( !m_cover->isNull() && !size.isEmpty() ) + { + if ( m_coverCache.contains( size.width() ) ) + { + return m_coverCache.value( size.width() ); + } + else + { + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + } + } + + return *m_cover; } +#endif void @@ -128,7 +156,7 @@ Artist::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVari const QByteArray ba = returnedData["imgbytes"].toByteArray(); if ( ba.length() ) { - m_cover = ba; + m_coverBuffer = ba; } } diff --git a/src/libtomahawk/artist.h b/src/libtomahawk/artist.h index 3e1442023..05efe48eb 100644 --- a/src/libtomahawk/artist.h +++ b/src/libtomahawk/artist.h @@ -19,8 +19,13 @@ #ifndef TOMAHAWKARTIST_H #define TOMAHAWKARTIST_H +#include "config.h" + #include #include +#ifndef ENABLE_HEADLESS + #include +#endif #include "typedefs.h" #include "dllmacro.h" @@ -43,7 +48,9 @@ public: unsigned int id() const { return m_id; } QString name() const { return m_name; } QString sortname() const { return m_sortname; } - QByteArray cover() const; +#ifndef ENABLE_HEADLESS + QPixmap cover( const QSize& size, bool forceLoad = true ) const; +#endif bool infoLoaded() const { return m_infoLoaded; } Tomahawk::playlistinterface_ptr playlistInterface(); @@ -63,10 +70,13 @@ private: unsigned int m_id; QString m_name; QString m_sortname; - QByteArray m_cover; + QByteArray m_coverBuffer; + mutable QPixmap* m_cover; bool m_infoLoaded; mutable QString m_uuid; + mutable QHash< int, QPixmap > m_coverCache; + Tomahawk::playlistinterface_ptr m_playlistInterface; }; diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 226d99a3f..256b8603f 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -333,7 +333,7 @@ AudioEngine::sendNowPlayingNotification() else { connect( m_currentTrack->album().data(), SIGNAL( updated() ), SLOT( onNowPlayingInfoReady() ), Qt::UniqueConnection ); - m_currentTrack->album()->cover(); + m_currentTrack->album()->cover( QSize( 0, 0 ) ); } } @@ -358,7 +358,7 @@ AudioEngine::onNowPlayingInfoReady() if ( !m_currentTrack->album().isNull() ) { QImage cover; - cover.loadFromData( m_currentTrack->album()->cover() ); + cover = m_currentTrack->album()->cover( QSize( 0, 0 ) ).toImage(); playInfo["image"] = QVariant( cover ); } diff --git a/src/libtomahawk/playlist/albumitemdelegate.cpp b/src/libtomahawk/playlist/albumitemdelegate.cpp index e28208248..f6baeceba 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.cpp +++ b/src/libtomahawk/playlist/albumitemdelegate.cpp @@ -89,21 +89,20 @@ AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, painter->drawLine( shadowRect.bottomLeft() + QPoint( 0, 4 ), shadowRect.bottomRight() + QPoint( 0, 4 ) ); } + QRect r = option.rect.adjusted( 6, 5, -6, -41 ); QPixmap cover; if ( !item->album().isNull() ) { - cover.loadFromData( item->album()->cover() ); + cover = item->album()->cover( r.size() ); } else if ( !item->artist().isNull() ) { - cover.loadFromData( item->artist()->cover() ); + cover = item->artist()->cover( r.size() ); } if ( cover.isNull() ) cover = m_defaultCover; - QRect r = option.rect.adjusted( 6, 5, -6, -41 ); - if ( option.state & QStyle::State_Selected ) { #if defined(Q_WS_MAC) || defined(Q_WS_WIN) @@ -123,17 +122,7 @@ AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, #endif } - QPixmap scover; - if ( m_cache.contains( cover.cacheKey() ) ) - { - scover = m_cache.value( cover.cacheKey() ); - } - else - { - scover = cover.scaled( r.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); - m_cache.insert( cover.cacheKey(), scover ); - } - painter->drawPixmap( r, scover ); + painter->drawPixmap( r, cover ); painter->setPen( opt.palette.color( QPalette::Text ) ); QTextOption to; diff --git a/src/libtomahawk/playlist/albumitemdelegate.h b/src/libtomahawk/playlist/albumitemdelegate.h index 86b31bdf5..43de6e0e8 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.h +++ b/src/libtomahawk/playlist/albumitemdelegate.h @@ -49,7 +49,6 @@ private: QAbstractItemView* m_view; AlbumProxyModel* m_model; - mutable QHash< qint64, QPixmap > m_cache; mutable QHash< QPersistentModelIndex, QRect > m_artistNameRects; QPersistentModelIndex m_hoveringOver; diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index 808b6c8c5..ff83b2eeb 100644 --- a/src/libtomahawk/playlist/artistview.cpp +++ b/src/libtomahawk/playlist/artistview.cpp @@ -80,6 +80,11 @@ ArtistView::ArtistView( QWidget* parent ) setFont( f ); #endif + m_timer.setInterval( SCROLL_TIMEOUT ); + connect( verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), SLOT( onViewChanged() ) ); + connect( verticalScrollBar(), SIGNAL( valueChanged( int ) ), SLOT( onViewChanged() ) ); + connect( &m_timer, SIGNAL( timeout() ), SLOT( onScrollTimeout() ) ); + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) ); connect( m_contextMenu, SIGNAL( triggered( int ) ), SLOT( onMenuTriggered( int ) ) ); @@ -129,6 +134,7 @@ ArtistView::setTreeModel( TreeModel* model ) connect( m_model, SIGNAL( itemCountChanged( unsigned int ) ), SLOT( onItemCountChanged( unsigned int ) ) ); connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); + connect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( onViewChanged() ) ); guid(); // this will set the guid on the header @@ -145,6 +151,43 @@ ArtistView::setTreeModel( TreeModel* model ) } +void +ArtistView::onViewChanged() +{ + if ( m_timer.isActive() ) + m_timer.stop(); + + m_timer.start(); +} + + +void +ArtistView::onScrollTimeout() +{ + if ( m_timer.isActive() ) + m_timer.stop(); + + QModelIndex left = indexAt( viewport()->rect().topLeft() ); + while ( left.isValid() && left.parent().isValid() ) + left = left.parent(); + + QModelIndex right = indexAt( viewport()->rect().bottomLeft() ); + while ( right.isValid() && right.parent().isValid() ) + right = right.parent(); + + int max = m_proxyModel->playlistInterface()->trackCount(); + if ( right.isValid() ) + max = right.row() + 1; + + if ( !max ) + return; + + for ( int i = left.row(); i < max; i++ ) + { + m_model->getCover( m_proxyModel->mapToSource( m_proxyModel->index( i, 0 ) ) ); + } +} + void ArtistView::currentChanged( const QModelIndex& current, const QModelIndex& previous ) { diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h index 8167f9b73..061bca781 100644 --- a/src/libtomahawk/playlist/artistview.h +++ b/src/libtomahawk/playlist/artistview.h @@ -95,6 +95,8 @@ private slots: void onItemCountChanged( unsigned int items ); void onFilterChanged( const QString& filter ); void onFilteringStarted(); + void onViewChanged(); + void onScrollTimeout(); void onCustomContextMenu( const QPoint& pos ); void onMenuTriggered( int action ); @@ -113,6 +115,7 @@ private: Tomahawk::ContextMenu* m_contextMenu; bool m_showModes; + QTimer m_timer; mutable QString m_guid; }; diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index 90fbe4aa7..22fc81247 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -155,14 +155,13 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, QPixmap cover; if ( !item->album().isNull() ) { - cover.loadFromData( item->album()->cover() ); + cover = item->album()->cover( r.size(), false ); } else if ( !item->artist().isNull() ) { - cover.loadFromData( item->artist()->cover() ); + cover = item->artist()->cover( r.size(), false ); } - QPixmap scover; if ( cover.isNull() ) { if ( !item->artist().isNull() ) @@ -171,16 +170,7 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, cover = m_defaultAlbumCover; } - if ( m_cache.contains( cover.cacheKey() ) ) - { - scover = m_cache.value( cover.cacheKey() ); - } - else - { - scover = cover.scaled( r.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); - m_cache.insert( cover.cacheKey(), scover ); - } - painter->drawPixmap( r, scover ); + painter->drawPixmap( r, cover ); QTextOption to; to.setAlignment( Qt::AlignVCenter ); diff --git a/src/libtomahawk/playlist/treeitemdelegate.h b/src/libtomahawk/playlist/treeitemdelegate.h index 07fff9423..da84fcec9 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.h +++ b/src/libtomahawk/playlist/treeitemdelegate.h @@ -43,8 +43,6 @@ private: ArtistView* m_view; TreeProxyModel* m_model; - mutable QHash< qint64, QPixmap > m_cache; - QPixmap m_nowPlayingIcon; QPixmap m_defaultAlbumCover; QPixmap m_defaultArtistImage; diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 24e3073e0..63f40b3b2 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -87,6 +87,18 @@ TreeModel::collection() const } +void +TreeModel::getCover( const QModelIndex& index ) +{ + TreeModelItem* item = itemFromIndex( index ); + + if ( !item->artist().isNull() && !item->artist()->infoLoaded() ) + item->artist()->cover( QSize( 0, 0 ) ); + else if ( !item->album().isNull() && !item->album()->infoLoaded() ) + item->album()->cover( QSize( 0, 0 ) ); +} + + void TreeModel::setCurrentItem( const QModelIndex& index ) { diff --git a/src/libtomahawk/playlist/treemodel.h b/src/libtomahawk/playlist/treemodel.h index 53578bc8b..fb645cf28 100644 --- a/src/libtomahawk/playlist/treemodel.h +++ b/src/libtomahawk/playlist/treemodel.h @@ -98,6 +98,8 @@ public: void addAlbums( const Tomahawk::artist_ptr& artist, const QModelIndex& parent, bool autoRefetch = false ); void addTracks( const Tomahawk::album_ptr& album, const QModelIndex& parent, bool autoRefetch = false ); + void getCover( const QModelIndex& index ); + ColumnStyle columnStyle() const { return m_columnStyle; } void setColumnStyle( ColumnStyle style ); diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index 62b048704..c91cb37ab 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -243,10 +243,10 @@ AlbumInfoWidget::loadAlbums( bool autoRefetch ) void AlbumInfoWidget::onAlbumCoverUpdated() { - if ( m_album->cover().isNull() ) + if ( m_album->cover( QSize( 0, 0 ) ).isNull() ) return; - m_pixmap.loadFromData( m_album->cover() ); + m_pixmap = m_album->cover( QSize( 0, 0 ) ); emit pixmapChanged( m_pixmap ); } diff --git a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp index 178713c04..96e4dfc8b 100644 --- a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp @@ -289,10 +289,10 @@ ArtistInfoWidget::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestD void ArtistInfoWidget::onArtistImageUpdated() { - if ( m_artist->cover().isNull() ) + if ( m_artist->cover( QSize( 0, 0 ) ).isNull() ) return; - m_pixmap.loadFromData( m_artist->cover() ); + m_pixmap = m_artist->cover( QSize( 0, 0 ) ); emit pixmapChanged( m_pixmap ); } diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 058b7bed1..5855a2b37 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -543,10 +543,7 @@ TomahawkApp::spotifyApiCheckFinished() QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() ); Q_ASSERT( reply ); - if ( reply->error() ) - DropJob::setCanParseSpotifyPlaylists( false ); - else - DropJob::setCanParseSpotifyPlaylists( true ); + DropJob::setCanParseSpotifyPlaylists( !reply->error() ); #endif } From 6b51872c3e21616261f3cad23d5fa98d793465b3 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 03:30:22 +0100 Subject: [PATCH 04/20] * Unbreak headless. --- src/libtomahawk/audio/audioengine.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 256b8603f..fbd6c7cd7 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -18,6 +18,8 @@ #include "audioengine.h" +#include "config.h" + #include #include @@ -328,6 +330,7 @@ AudioEngine::sendWaitingNotificationSlot() const void AudioEngine::sendNowPlayingNotification() { +#ifndef ENABLE_HEADLESS if ( m_currentTrack->album().isNull() || m_currentTrack->album()->infoLoaded() ) onNowPlayingInfoReady(); else @@ -335,6 +338,7 @@ AudioEngine::sendNowPlayingNotification() connect( m_currentTrack->album().data(), SIGNAL( updated() ), SLOT( onNowPlayingInfoReady() ), Qt::UniqueConnection ); m_currentTrack->album()->cover( QSize( 0, 0 ) ); } +#endif } From dbcbffb6c4f6b4fb152408addcf0728aace0de12 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 04:05:06 +0100 Subject: [PATCH 05/20] * Auto load covers after expanding an artist. --- src/libtomahawk/playlist/treemodel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 63f40b3b2..bb2026041 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -795,6 +795,8 @@ TreeModel::onAlbumsAdded( const QList& albums, const QModel albumitem = new TreeModelItem( album, parentItem ); albumitem->index = createIndex( parentItem->children.count() - 1, 0, albumitem ); connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + + getCover( albumitem->index ); } emit endInsertRows(); From 55e7d6383b59ec688ea838503ef947832e8f9903 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 04:08:14 +0100 Subject: [PATCH 06/20] * Unbreak headless more. --- src/libtomahawk/audio/audioengine.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index fbd6c7cd7..5d4be8afc 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -361,9 +361,11 @@ AudioEngine::onNowPlayingInfoReady() if ( !m_currentTrack->album().isNull() ) { +#ifndef ENABLE_HEADLESS QImage cover; cover = m_currentTrack->album()->cover( QSize( 0, 0 ) ).toImage(); playInfo["image"] = QVariant( cover ); +#endif } Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( From 396b5cd6e059f30ae4d8fd4a3eeabe16639c3efc Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 04:36:30 +0100 Subject: [PATCH 07/20] * Modify Tomahawk's config file to be only accessible by the owner. --- src/libtomahawk/tomahawksettings.cpp | 30 ++++++++++++++++++---------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index ff1d3b626..9009b3d9a 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -47,19 +47,24 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) { s_instance = this; - if( !contains( "configversion") ) + #ifdef Q_OS_LINUX + QFile file( fileName() ); + file.setPermissions( file.permissions() & ~(QFile::ReadGroup | QFile::WriteGroup | QFile::ExeGroup | QFile::ReadOther | QFile::WriteOther | QFile::ExeOther ) ); + #endif + + if ( !contains( "configversion" ) ) { setValue( "configversion", VERSION ); doInitialSetup(); } - else if( value( "configversion" ).toUInt() != VERSION ) + else if ( value( "configversion" ).toUInt() != VERSION ) { qDebug() << "Config version outdated, old:" << value( "configversion" ).toUInt() << "new:" << VERSION << "Doing upgrade, if any..."; int current = value( "configversion" ).toUInt(); - while( current < VERSION ) + while ( current < VERSION ) { doUpgrade( current, current + 1 ); @@ -68,7 +73,6 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) // insert upgrade code here as required setValue( "configversion", VERSION ); } - } @@ -91,22 +95,23 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) { Q_UNUSED( newVersion ); - if( oldVersion == 1 ) + if ( oldVersion == 1 ) { qDebug() << "Migrating config from verson 1 to 2: script resolver config name"; if( contains( "script/resolvers" ) ) { setValue( "script/loadedresolvers", value( "script/resolvers" ) ); remove( "script/resolvers" ); } - } else if( oldVersion == 2 ) + } + else if ( oldVersion == 2 ) { qDebug() << "Migrating config from version 2 to 3: Converting jabber and twitter accounts to new SIP Factory approach"; // migrate old accounts to new system. only jabber and twitter, and max one each. create a new plugin for each if needed // not pretty as we hardcode a plugin id and assume that we know how the config layout is, but hey, this is migration after all - if( contains( "jabber/username" ) && contains( "jabber/password" ) ) + if ( contains( "jabber/username" ) && contains( "jabber/password" ) ) { QString sipName = "sipjabber"; - if( value( "jabber/username" ).toString().contains( "@gmail" ) ) + if ( value( "jabber/username" ).toString().contains( "@gmail" ) ) sipName = "sipgoogle"; setValue( QString( "%1_legacy/username" ).arg( sipName ), value( "jabber/username" ) ); @@ -123,7 +128,7 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) remove( "jabber/server" ); remove( "jabber/port" ); } - if( contains( "twitter/ScreenName" ) && contains( "twitter/OAuthToken" ) ) + if ( contains( "twitter/ScreenName" ) && contains( "twitter/OAuthToken" ) ) { setValue( "siptwitter_legacy/ScreenName", value( "twitter/ScreenName" ) ); setValue( "siptwitter_legacy/OAuthToken", value( "twitter/OAuthToken" ) ); @@ -144,7 +149,8 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) } // create a zeroconf plugin too addSipPlugin( "sipzeroconf_legacy" ); - } else if ( oldVersion == 3 ) + } + else if ( oldVersion == 3 ) { if ( contains( "script/atticaresolverstates" ) ) { @@ -183,7 +189,8 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) tDebug() << "UPGRADING AND DELETING:" << resolverDir.absolutePath(); TomahawkUtils::removeDirectory( resolverDir.absolutePath() ); } - } else if ( oldVersion == 4 ) + } + else if ( oldVersion == 4 ) { // 0.3.0 contained a bug which prevent indexing local files. Force a reindex. QTimer::singleShot( 0, this, SLOT( updateIndex() ) ); @@ -935,6 +942,7 @@ TomahawkSettings::setNowPlayingEnabled( bool enable ) setValue( "adium/enablenowplaying", enable ); } + TomahawkSettings::PrivateListeningMode TomahawkSettings::privateListeningMode() const { From d1ecf6d748cca37f72323ac3584de58323062339 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 06:22:47 +0100 Subject: [PATCH 08/20] * More work on sidebar reorganization. --- src/libtomahawk/viewmanager.h | 1 + src/sourcetree/items/groupitem.cpp | 3 +-- src/sourcetree/items/groupitem.h | 2 -- src/sourcetree/items/sourcetreeitem.cpp | 3 ++- src/sourcetree/items/sourcetreeitem.h | 5 +++-- src/sourcetree/sourcedelegate.cpp | 10 ++++++--- src/sourcetree/sourcesmodel.cpp | 27 +++++++++++++++---------- src/sourcetree/sourcesmodel.h | 1 + src/sourcetree/sourcesproxymodel.cpp | 2 +- 9 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/libtomahawk/viewmanager.h b/src/libtomahawk/viewmanager.h index adce43cc6..4dc2148b8 100644 --- a/src/libtomahawk/viewmanager.h +++ b/src/libtomahawk/viewmanager.h @@ -133,6 +133,7 @@ signals: void hideQueueRequested(); void tomahawkLoaded(); + public slots: Tomahawk::ViewPage* showSuperCollection(); Tomahawk::ViewPage* showWelcomePage(); diff --git a/src/sourcetree/items/groupitem.cpp b/src/sourcetree/items/groupitem.cpp index 01804c0ba..f45f76b09 100644 --- a/src/sourcetree/items/groupitem.cpp +++ b/src/sourcetree/items/groupitem.cpp @@ -28,9 +28,8 @@ using namespace Tomahawk; GroupItem::GroupItem( SourcesModel* model, SourceTreeItem* parent, const QString& text, int peerSortValue ) - : SourceTreeItem( model, parent, SourcesModel::Group ) + : SourceTreeItem( model, parent, SourcesModel::Group, peerSortValue ) , m_text( text ) - , m_peerSortValue( peerSortValue ) { // expand by default QTimer::singleShot( 0, this, SLOT( requestExpanding() ) ); diff --git a/src/sourcetree/items/groupitem.h b/src/sourcetree/items/groupitem.h index 5f173ac0b..3be4e16ee 100644 --- a/src/sourcetree/items/groupitem.h +++ b/src/sourcetree/items/groupitem.h @@ -36,7 +36,6 @@ public: virtual QString text() const; virtual bool willAcceptDrag( const QMimeData* data ) const { Q_UNUSED( data ); return false; } virtual QIcon icon() const { return QIcon(); } - virtual int peerSortValue() const { return m_peerSortValue; } virtual bool isBeingPlayed() const { return false; } public slots: @@ -50,7 +49,6 @@ private slots: private: QString m_text; - int m_peerSortValue; }; #endif diff --git a/src/sourcetree/items/sourcetreeitem.cpp b/src/sourcetree/items/sourcetreeitem.cpp index 01a0224f8..644331472 100644 --- a/src/sourcetree/items/sourcetreeitem.cpp +++ b/src/sourcetree/items/sourcetreeitem.cpp @@ -23,11 +23,12 @@ using namespace Tomahawk; -SourceTreeItem::SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int index ) +SourceTreeItem::SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int peerSortValue, int index ) : QObject() , m_type( thisType ) , m_parent( parent ) , m_model( model ) + , m_peerSortValue( peerSortValue ) { connect( this, SIGNAL( beginChildRowsAdded( int, int ) ), m_model, SLOT( onItemRowsAddedBegin( int, int ) ) ); connect( this, SIGNAL( beginChildRowsRemoved( int, int ) ), m_model, SLOT( onItemRowsRemovedBegin( int, int ) ) ); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index 7e174b789..adde55609 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -43,7 +43,7 @@ public: Q_DECLARE_FLAGS( DropTypes, DropType ) SourceTreeItem() : m_type( SourcesModel::Invalid ), m_parent( 0 ), m_model( 0 ) {} - SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int index = -1 ); // if index is -1, append at end of parent's child list + SourceTreeItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::RowType thisType, int peerSortValue = 0, int index = -1 ); // if index is -1, append at end of parent's child list virtual ~SourceTreeItem(); // generic info used by the tree model @@ -63,7 +63,7 @@ public: virtual bool willAcceptDrag( const QMimeData* ) const { return false; } virtual bool dropMimeData( const QMimeData*, Qt::DropAction ) { return false; } virtual bool setData( const QVariant&, bool ) { return false; } - virtual int peerSortValue() const { return 0; } // How to sort relative to peers in the tree. + virtual int peerSortValue() const { return m_peerSortValue; } // How to sort relative to peers in the tree. virtual int IDValue() const { return 0; } virtual DropTypes supportedDropTypes( const QMimeData* mimeData ) const { Q_UNUSED( mimeData ); return DropTypesNone; } virtual void setDropType( DropType type ) { m_dropType = type; } @@ -101,6 +101,7 @@ private: SourceTreeItem* m_parent; QList< SourceTreeItem* > m_children; SourcesModel* m_model; + int m_peerSortValue; DropType m_dropType; }; diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 61362ae6f..4ecdd9e96 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -82,7 +82,7 @@ SourceDelegate::~SourceDelegate() QSize SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { - SourceTreeItem *item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); + SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >(); SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); if ( type == SourcesModel::Collection ) @@ -93,6 +93,10 @@ SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& { return QSize( option.rect.width(), 6 ); } + else if ( type == SourcesModel::Group && index.row() > 0 ) + { + return QSize( option.rect.width(), 24 ); + } else if ( m_expandedMap.contains( index ) ) { if ( !m_expandedMap.value( index )->initialized() ) @@ -305,7 +309,7 @@ SourceDelegate::paintGroup( QPainter* painter, const QStyleOptionViewItem& optio font.setBold( true ); painter->setFont( font ); - QTextOption to( Qt::AlignVCenter ); + QTextOption to( Qt::AlignBottom ); painter->setPen( option.palette.color( QPalette::Base ) ); painter->setBrush( option.palette.color( QPalette::Base ) ); @@ -325,7 +329,7 @@ SourceDelegate::paintGroup( QPainter* painter, const QStyleOptionViewItem& optio font.setPixelSize( font.pixelSize() - 1 ); painter->setFont( font ); - QTextOption to( Qt::AlignVCenter | Qt::AlignRight ); + QTextOption to( Qt::AlignBottom | Qt::AlignRight ); // draw close icon painter->setPen( Qt::white ); diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index dfe877d96..188b2b5c5 100644 --- a/src/sourcetree/sourcesmodel.cpp +++ b/src/sourcetree/sourcesmodel.cpp @@ -39,6 +39,7 @@ #include "globalactionmanager.h" #include "dropjob.h" #include "items/playlistitems.h" +#include "playlist/artistview.h" #include "playlist/playlistview.h" #include "playlist/dynamic/widgets/DynamicWidget.h" @@ -53,8 +54,6 @@ SourcesModel::SourcesModel( QObject* parent ) m_rootItem = new SourceTreeItem( this, 0, Invalid ); appendGroups(); - appendItem( source_ptr() ); - onSourcesAdded( SourceList::instance()->sources() ); connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); @@ -265,28 +264,34 @@ SourcesModel::appendGroups() { beginInsertRows( QModelIndex(), rowCount(), rowCount() + 2 ); - new SourceTreeItem( this, m_rootItem, SourcesModel::Divider, 0 ); - new HistoryItem( this, m_rootItem, tr( "History" ), 5 ); - GroupItem* browse = new GroupItem( this, m_rootItem, tr( "Browse" ), 10 ); + GroupItem* browse = new GroupItem( this, m_rootItem, tr( "Browse" ), 0 ); + new HistoryItem( this, m_rootItem, tr( "Search History" ), 1 ); +// new SourceTreeItem( this, m_rootItem, SourcesModel::Divider, 2 ); + m_myMusicGroup = new GroupItem( this, m_rootItem, tr( "My Music" ), 3 ); // super collection + GenericPageItem* sc = new GenericPageItem( this, browse, tr( "SuperCollection" ), QIcon( RESPATH "images/supercollection.png" ), + boost::bind( &ViewManager::showSuperCollection, ViewManager::instance() ), + boost::bind( &ViewManager::superCollectionView, ViewManager::instance() ) ); + sc->setSortValue( 1 ); + + // browse section GenericPageItem* loved = new GenericPageItem( this, browse, tr( "Top Loved Tracks" ), QIcon( RESPATH "images/loved_playlist.png" ), boost::bind( &ViewManager::showTopLovedPage, ViewManager::instance() ), boost::bind( &ViewManager::topLovedWidget, ViewManager::instance() ) ); - loved->setSortValue( -250 ); + loved->setSortValue( 2 ); - // add misc children of root node GenericPageItem* recent = new GenericPageItem( this, browse, tr( "Dashboard" ), QIcon( RESPATH "images/dashboard.png" ), boost::bind( &ViewManager::showWelcomePage, ViewManager::instance() ), boost::bind( &ViewManager::welcomeWidget, ViewManager::instance() ) ); - recent->setSortValue( -300 ); + recent->setSortValue( 0 ); GenericPageItem* hot = new GenericPageItem( this, browse, tr( "Charts" ), QIcon( RESPATH "images/charts.png" ), boost::bind( &ViewManager::showWhatsHotPage, ViewManager::instance() ), boost::bind( &ViewManager::whatsHotWidget, ViewManager::instance() ) ); - hot->setSortValue( -300 ); + hot->setSortValue( 3 ); - m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 15 ); + m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 4 ); endInsertRows(); } @@ -298,7 +303,7 @@ SourcesModel::appendItem( const Tomahawk::source_ptr& source ) SourceTreeItem* parent; if ( !source.isNull() && source->isLocal() ) { - parent = m_rootItem; + parent = m_myMusicGroup; } else { diff --git a/src/sourcetree/sourcesmodel.h b/src/sourcetree/sourcesmodel.h index 969067396..694e4f8b6 100644 --- a/src/sourcetree/sourcesmodel.h +++ b/src/sourcetree/sourcesmodel.h @@ -139,6 +139,7 @@ private: SourceTreeItem* m_rootItem; GroupItem* m_collectionsGroup; + GroupItem* m_myMusicGroup; QList< Tomahawk::source_ptr > m_sourcesWithViewPage; QHash< Tomahawk::source_ptr, SourceTreeItem* > m_sourcesWithViewPageItems; diff --git a/src/sourcetree/sourcesproxymodel.cpp b/src/sourcetree/sourcesproxymodel.cpp index a0db5e2ea..de36f87e5 100644 --- a/src/sourcetree/sourcesproxymodel.cpp +++ b/src/sourcetree/sourcesproxymodel.cpp @@ -66,7 +66,7 @@ SourcesProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourcePar if ( item && item->type() != SourcesModel::Divider && item->parent()->parent() == 0 && !item->children().count() ) return false; - + if ( !m_filtered ) return true; From 2a27ef88f6d1bb02c95cbf1ccd4ae6d64fbc3971 Mon Sep 17 00:00:00 2001 From: ubertaco Date: Thu, 23 Feb 2012 13:48:51 -0500 Subject: [PATCH 09/20] Added initial support for TWK-595 ( command-line audio controls ) --- src/tomahawkapp.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 5855a2b37..07ae4795e 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -139,6 +139,7 @@ TomahawkApp::init() ::exit( 0 ); } + qDebug() << "TomahawkApp thread:" << thread(); Logger::setupLogfile(); qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); @@ -349,6 +350,11 @@ TomahawkApp::printHelp() echo( " --testdb Use a test database instead of real collection\n" ); echo( " --noupnp Disable UPnP\n" ); echo( " --nosip Disable SIP\n" ); + echo( "Playback Controls:" ); + echo( " --playpause Toggle playing/paused state" ); + echo( " --play Start/resume playback" ); + echo( " --pause Pause playback" ); + echo( " --stop Stop playback" ); echo( "\nurl is a tomahawk:// command or alternatively a url that Tomahawk can recognize.\n" ); echo( "For more documentation, see http://wiki.tomahawk-player.org/mediawiki/index.php/Tomahawk://_Links\n" ); } @@ -604,4 +610,43 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) QString arg1 = instance.arguments[ 1 ]; loadUrl( arg1 ); + + if ( instance.arguments.contains("--next") || instance.arguments.contains("--prev") || instance.arguments.contains( "--playpause" ) || instance.arguments.contains( "--play" ) || instance.arguments.contains( "--pause" ) || instance.arguments.contains( "--stop" ) ) + { + if ( instance.arguments.contains( "--next" ) ) + { + AudioEngine::instance()->next(); + } + + if ( instance.arguments.contains( "--prev" ) ) + { + AudioEngine::instance()->previous(); + } + + if ( instance.arguments.contains( "--playpause" ) ) + { + AudioEngine::instance()->playPause(); + } + + if ( instance.arguments.contains( "--play" ) ) + { + AudioEngine::instance()->play(); + } + + if ( instance.arguments.contains( "--pause" ) ) + { + AudioEngine::instance()->pause(); + } + + if ( instance.arguments.contains( "--stop" ) ) + { + AudioEngine::instance()->stop(); + } + + if ( instance.arguments.contains( "--stop" ) ) + { + AudioEngine::instance()->stop(); + } + + } } From 9b1fc4cd24bbdd5e191986f0b5e1634f22feb4e2 Mon Sep 17 00:00:00 2001 From: ubertaco Date: Thu, 23 Feb 2012 13:52:11 -0500 Subject: [PATCH 10/20] added helpstrings --- src/tomahawkapp.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 07ae4795e..888636d3d 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -352,9 +352,11 @@ TomahawkApp::printHelp() echo( " --nosip Disable SIP\n" ); echo( "Playback Controls:" ); echo( " --playpause Toggle playing/paused state" ); - echo( " --play Start/resume playback" ); - echo( " --pause Pause playback" ); - echo( " --stop Stop playback" ); + echo( " --play Start/resume playback" ); + echo( " --pause Pause playback" ); + echo( " --stop Stop playback" ); + echo( " --next Advances to the next track (if available)" ); + echo( " --prev Advances to the next track (if available)" ); echo( "\nurl is a tomahawk:// command or alternatively a url that Tomahawk can recognize.\n" ); echo( "For more documentation, see http://wiki.tomahawk-player.org/mediawiki/index.php/Tomahawk://_Links\n" ); } From a0383b666405faa70f5ef3cf14edebc07f55dd02 Mon Sep 17 00:00:00 2001 From: ubertaco Date: Thu, 23 Feb 2012 14:04:24 -0500 Subject: [PATCH 11/20] fixed helpstrings and inconsistent spacing --- src/tomahawkapp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 888636d3d..73e6e1bf7 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -356,7 +356,7 @@ TomahawkApp::printHelp() echo( " --pause Pause playback" ); echo( " --stop Stop playback" ); echo( " --next Advances to the next track (if available)" ); - echo( " --prev Advances to the next track (if available)" ); + echo( " --prev Returns to the previous track (if available)" ); echo( "\nurl is a tomahawk:// command or alternatively a url that Tomahawk can recognize.\n" ); echo( "For more documentation, see http://wiki.tomahawk-player.org/mediawiki/index.php/Tomahawk://_Links\n" ); } @@ -613,7 +613,7 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) QString arg1 = instance.arguments[ 1 ]; loadUrl( arg1 ); - if ( instance.arguments.contains("--next") || instance.arguments.contains("--prev") || instance.arguments.contains( "--playpause" ) || instance.arguments.contains( "--play" ) || instance.arguments.contains( "--pause" ) || instance.arguments.contains( "--stop" ) ) + if ( instance.arguments.contains( "--next" ) || instance.arguments.contains( "--prev" ) || instance.arguments.contains( "--playpause" ) || instance.arguments.contains( "--play" ) || instance.arguments.contains( "--pause" ) || instance.arguments.contains( "--stop" ) ) { if ( instance.arguments.contains( "--next" ) ) { From 458d32ed7c71347689122963d74bd6b92ee84edf Mon Sep 17 00:00:00 2001 From: ubertaco Date: Thu, 23 Feb 2012 14:05:44 -0500 Subject: [PATCH 12/20] removed duplicate --stop --- src/tomahawkapp.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 73e6e1bf7..9b48e1723 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -645,10 +645,5 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) AudioEngine::instance()->stop(); } - if ( instance.arguments.contains( "--stop" ) ) - { - AudioEngine::instance()->stop(); - } - } } From b17b8356ad306a1e9f95193ec85a299ee325386d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 20:34:23 +0100 Subject: [PATCH 13/20] * Fixed a few issues with controlling playback via CLI args. --- src/tomahawkapp.cpp | 63 +++++++++++++++------------------------------ 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 9b48e1723..4c649736f 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -139,7 +139,6 @@ TomahawkApp::init() ::exit( 0 ); } - qDebug() << "TomahawkApp thread:" << thread(); Logger::setupLogfile(); qsrand( QTime( 0, 0, 0 ).secsTo( QTime::currentTime() ) ); @@ -350,13 +349,13 @@ TomahawkApp::printHelp() echo( " --testdb Use a test database instead of real collection\n" ); echo( " --noupnp Disable UPnP\n" ); echo( " --nosip Disable SIP\n" ); - echo( "Playback Controls:" ); - echo( " --playpause Toggle playing/paused state" ); - echo( " --play Start/resume playback" ); - echo( " --pause Pause playback" ); - echo( " --stop Stop playback" ); - echo( " --next Advances to the next track (if available)" ); - echo( " --prev Returns to the previous track (if available)" ); + echo( "\nPlayback Controls:\n" ); + echo( " --playpause Toggle playing/paused state\n" ); + echo( " --play Start/resume playback\n" ); + echo( " --pause Pause playback\n" ); + echo( " --stop Stop playback\n" ); + echo( " --next Advances to the next track (if available)\n" ); + echo( " --prev Returns to the previous track (if available)\n" ); echo( "\nurl is a tomahawk:// command or alternatively a url that Tomahawk can recognize.\n" ); echo( "For more documentation, see http://wiki.tomahawk-player.org/mediawiki/index.php/Tomahawk://_Links\n" ); } @@ -611,39 +610,19 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) return; QString arg1 = instance.arguments[ 1 ]; - loadUrl( arg1 ); + if ( loadUrl( arg1 ) ) + return; - if ( instance.arguments.contains( "--next" ) || instance.arguments.contains( "--prev" ) || instance.arguments.contains( "--playpause" ) || instance.arguments.contains( "--play" ) || instance.arguments.contains( "--pause" ) || instance.arguments.contains( "--stop" ) ) - { - if ( instance.arguments.contains( "--next" ) ) - { - AudioEngine::instance()->next(); - } - - if ( instance.arguments.contains( "--prev" ) ) - { - AudioEngine::instance()->previous(); - } - - if ( instance.arguments.contains( "--playpause" ) ) - { - AudioEngine::instance()->playPause(); - } - - if ( instance.arguments.contains( "--play" ) ) - { - AudioEngine::instance()->play(); - } - - if ( instance.arguments.contains( "--pause" ) ) - { - AudioEngine::instance()->pause(); - } - - if ( instance.arguments.contains( "--stop" ) ) - { - AudioEngine::instance()->stop(); - } - - } + if ( instance.arguments.contains( "--next" ) ) + AudioEngine::instance()->next(); + else if ( instance.arguments.contains( "--prev" ) ) + AudioEngine::instance()->previous(); + else if ( instance.arguments.contains( "--playpause" ) ) + AudioEngine::instance()->playPause(); + else if ( instance.arguments.contains( "--play" ) ) + AudioEngine::instance()->play(); + else if ( instance.arguments.contains( "--pause" ) ) + AudioEngine::instance()->pause(); + else if ( instance.arguments.contains( "--stop" ) ) + AudioEngine::instance()->stop(); } From c2a79d4cbf4c1f8792b0f8e14f1ba5d30ac16769 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 23 Feb 2012 21:06:17 +0100 Subject: [PATCH 14/20] * Don't wordwrap track names in sidebar. --- src/sourcetree/sourcedelegate.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 4ecdd9e96..c2ef76c31 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -242,9 +242,11 @@ SourceDelegate::paintCollection( QPainter* painter, const QStyleOptionViewItem& } } - text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() ); + textRect.adjust( 0, 0, 0, 2 ); + text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() - 4 ); QTextOption to( Qt::AlignVCenter ); - painter->drawText( textRect.adjusted( 0, 0, 0, 2 ), text, to ); + to.setWrapMode( QTextOption::NoWrap ); + painter->drawText( textRect, text, to ); if ( status ) { From 325bb0bf8fee12550d465c274fae542e19321f77 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 04:13:37 +0100 Subject: [PATCH 15/20] * Added global common image cache. --- data/images/no-album-art-placeholder.png | Bin 69076 -> 15182 bytes data/images/no-album-no-case.png | Bin 69076 -> 7233 bytes data/images/no-artist-image-placeholder.png | Bin 93598 -> 12152 bytes src/audiocontrols.cpp | 4 +- src/audiocontrols.h | 2 - src/libtomahawk/album.cpp | 24 +++--- src/libtomahawk/artist.cpp | 24 +++--- .../playlist/albumitemdelegate.cpp | 6 +- src/libtomahawk/playlist/albumitemdelegate.h | 1 - src/libtomahawk/playlist/treeitemdelegate.cpp | 12 +-- src/libtomahawk/utils/tomahawkutils.h | 12 +++ src/libtomahawk/utils/tomahawkutilsgui.cpp | 75 +++++++++++++++--- src/libtomahawk/utils/tomahawkutilsgui.h | 4 + .../widgets/infowidgets/AlbumInfoWidget.cpp | 2 +- .../widgets/infowidgets/ArtistInfoWidget.cpp | 2 +- 15 files changed, 115 insertions(+), 53 deletions(-) diff --git a/data/images/no-album-art-placeholder.png b/data/images/no-album-art-placeholder.png index a117306e28a0c91c6e2e27ab66543108aaa9f411..d7f2bb28ca0d5839f89ae86a7ddf41ed4388dfec 100644 GIT binary patch literal 15182 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 F`hVxaUPk}` literal 69076 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 P_-)^}%{5yceA@p3Md!Hz diff --git a/data/images/no-album-no-case.png b/data/images/no-album-no-case.png index 1fbbbb8bf251037b1336cd9ea0e3d801a183d147..963cdaa3d1828f2a1d1902e0bb0ca84bbc1d1914 100644 GIT binary patch literal 7233 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 P00000NkvXXu0mjf?Ht9G literal 69076 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 Dl`-J$ diff --git a/data/images/no-artist-image-placeholder.png b/data/images/no-artist-image-placeholder.png index 4b0eba323099d908d4026d9affda2a129349d7b9..559a858d43299a10b5f3cda621963225cc677d34 100644 GIT binary patch literal 12152 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 &m_sliderTimeLine ); - m_defaultCover = QPixmap( RESPATH "images/no-album-no-case.png" ).scaled( ui->coverImage->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); - connect( &m_phononTickCheckTimer, SIGNAL( timeout() ), SLOT( phononTickCheckTimeout() ) ); connect( &m_sliderTimeLine, SIGNAL( frameChanged( int ) ), ui->seekSlider, SLOT( setValue( int ) ) ); @@ -271,7 +269,7 @@ AudioControls::setAlbumCover() ui->coverImage->setPixmap( cover ); } else - ui->coverImage->setPixmap( m_defaultCover ); + ui->coverImage->setPixmap( TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::ScaledCover, ui->coverImage->size() ) ); } diff --git a/src/audiocontrols.h b/src/audiocontrols.h index 7d7de87c6..651f1dbf8 100644 --- a/src/audiocontrols.h +++ b/src/audiocontrols.h @@ -91,8 +91,6 @@ private: Ui::AudioControls *ui; - QPixmap m_defaultCover; - Tomahawk::result_ptr m_currentTrack; Tomahawk::PlaylistInterface::RepeatMode m_repeatMode; bool m_shuffled; diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp index 846dc15a6..c877b80a5 100644 --- a/src/libtomahawk/album.cpp +++ b/src/libtomahawk/album.cpp @@ -122,29 +122,29 @@ Album::cover( const QSize& size, bool forceLoad ) const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - if ( !m_cover ) - m_cover = new QPixmap(); - - if ( m_cover->isNull() && !m_coverBuffer.isEmpty() ) + if ( !m_cover && !m_coverBuffer.isEmpty() ) { + m_cover = new QPixmap(); m_cover->loadFromData( m_coverBuffer ); } - if ( !m_cover->isNull() && !size.isEmpty() ) + if ( m_cover && !m_cover->isNull() && !size.isEmpty() ) { if ( m_coverCache.contains( size.width() ) ) { return m_coverCache.value( size.width() ); } - else - { - QPixmap scaledCover; - scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); - m_coverCache.insert( size.width(), scaledCover ); - } + + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + return scaledCover; } - return *m_cover; + if ( m_cover ) + return *m_cover; + else + return QPixmap(); } #endif diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp index 534e15825..18937c4a8 100644 --- a/src/libtomahawk/artist.cpp +++ b/src/libtomahawk/artist.cpp @@ -113,29 +113,29 @@ Artist::cover( const QSize& size, bool forceLoad ) const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - if ( !m_cover ) - m_cover = new QPixmap(); - - if ( m_cover->isNull() && !m_coverBuffer.isEmpty() ) + if ( !m_cover && !m_coverBuffer.isEmpty() ) { + m_cover = new QPixmap(); m_cover->loadFromData( m_coverBuffer ); } - if ( !m_cover->isNull() && !size.isEmpty() ) + if ( m_cover && !m_cover->isNull() && !size.isEmpty() ) { if ( m_coverCache.contains( size.width() ) ) { return m_coverCache.value( size.width() ); } - else - { - QPixmap scaledCover; - scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); - m_coverCache.insert( size.width(), scaledCover ); - } + + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + return scaledCover; } - return *m_cover; + if ( m_cover ) + return *m_cover; + else + return QPixmap(); } #endif diff --git a/src/libtomahawk/playlist/albumitemdelegate.cpp b/src/libtomahawk/playlist/albumitemdelegate.cpp index f6baeceba..b8c9072d2 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.cpp +++ b/src/libtomahawk/playlist/albumitemdelegate.cpp @@ -40,7 +40,6 @@ AlbumItemDelegate::AlbumItemDelegate( QAbstractItemView* parent, AlbumProxyModel , m_view( parent ) , m_model( proxy ) { - m_defaultCover = QPixmap( RESPATH "images/no-album-art-placeholder.png" ); } @@ -94,15 +93,14 @@ AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, if ( !item->album().isNull() ) { cover = item->album()->cover( r.size() ); + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::CoverInCase, r.size() ); } else if ( !item->artist().isNull() ) { cover = item->artist()->cover( r.size() ); } - if ( cover.isNull() ) - cover = m_defaultCover; - if ( option.state & QStyle::State_Selected ) { #if defined(Q_WS_MAC) || defined(Q_WS_WIN) diff --git a/src/libtomahawk/playlist/albumitemdelegate.h b/src/libtomahawk/playlist/albumitemdelegate.h index 43de6e0e8..7d29f6c81 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.h +++ b/src/libtomahawk/playlist/albumitemdelegate.h @@ -53,7 +53,6 @@ private: QPersistentModelIndex m_hoveringOver; QPixmap m_shadowPixmap; - QPixmap m_defaultCover; }; #endif // ALBUMITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index 22fc81247..685a6389e 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -156,18 +156,14 @@ TreeItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, if ( !item->album().isNull() ) { cover = item->album()->cover( r.size(), false ); + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::ScaledCover, r.size() ); } else if ( !item->artist().isNull() ) { cover = item->artist()->cover( r.size(), false ); - } - - if ( cover.isNull() ) - { - if ( !item->artist().isNull() ) - cover = m_defaultArtistImage; - else - cover = m_defaultAlbumCover; + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::ScaledCover, r.size() ); } painter->drawPixmap( r, cover ); diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h index fc2dcf4a1..6c4b32dfa 100644 --- a/src/libtomahawk/utils/tomahawkutils.h +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -42,6 +42,18 @@ namespace TomahawkUtils MediaTypeTrack }; + enum ImageType + { + DefaultAlbumCover, + DefaultArtistImage + }; + enum ImageMode + { + NoDefaultCover, + CoverInCase, + ScaledCover + }; + class DLLEXPORT NetworkProxyFactory : public QNetworkProxyFactory { public: diff --git a/src/libtomahawk/utils/tomahawkutilsgui.cpp b/src/libtomahawk/utils/tomahawkutilsgui.cpp index 4d1d419d5..637f73cc4 100644 --- a/src/libtomahawk/utils/tomahawkutilsgui.cpp +++ b/src/libtomahawk/utils/tomahawkutilsgui.cpp @@ -36,6 +36,8 @@ #include #endif +#include "logger.h" + namespace TomahawkUtils { static int s_headerHeight = 0; @@ -83,15 +85,15 @@ createDragPixmap( MediaType type, int itemCount ) QPixmap pixmap; switch ( type ) { - case MediaTypeArtist: - pixmap = QPixmap( ":/data/images/artist-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); - break; - case MediaTypeAlbum: - pixmap = QPixmap( ":/data/images/album-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); - break; - case MediaTypeTrack: - pixmap = QPixmap( QString( ":/data/images/track-icon-%2x%2.png" ).arg( size ) ); - break; + case MediaTypeArtist: + pixmap = QPixmap( ":/data/images/artist-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); + break; + case MediaTypeAlbum: + pixmap = QPixmap( ":/data/images/album-icon.png" ).scaledToWidth( size, Qt::SmoothTransformation ); + break; + case MediaTypeTrack: + pixmap = QPixmap( QString( ":/data/images/track-icon-%2x%2.png" ).arg( size ) ); + break; } int x = 0; @@ -303,4 +305,59 @@ alphaBlend( const QColor& colorFrom, const QColor& colorTo, float opacity ) return QColor( r, g, b ); } + +QPixmap +defaultPixmap( ImageType type, ImageMode mode, const QSize& size ) +{ + QPixmap pixmap; + QHash< int, QPixmap > subsubcache; + QHash< int, QHash< int, QPixmap > > subcache; + static QHash< int, QHash< int, QHash< int, QPixmap > > > cache; + + if ( cache.contains( type ) ) + { + subcache = cache.value( type ); + + if ( subcache.contains( mode ) ) + { + subsubcache = subcache.value( mode ); + + if ( subsubcache.contains( size.width() ) ) + return subsubcache.value( size.width() ); + } + } + + switch ( type ) + { + case DefaultAlbumCover: + if ( mode == CoverInCase ) + pixmap = QPixmap( RESPATH "images/no-album-art-placeholder.png" ); + else + pixmap = QPixmap( RESPATH "images/no-album-no-case.png" ); + break; + + case DefaultArtistImage: + pixmap = QPixmap( RESPATH "images/no-artist-image-placeholder.png" ); + break; + + default: + break; + } + + if ( pixmap.isNull() ) + { + Q_ASSERT( false ); + return QPixmap(); + } + + if ( !size.isNull() ) + pixmap = pixmap.scaled( size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ); + + subsubcache.insert( size.width(), pixmap ); + subcache.insert( mode, subsubcache ); + cache.insert( type, subcache ); + + return pixmap; +} + } // ns diff --git a/src/libtomahawk/utils/tomahawkutilsgui.h b/src/libtomahawk/utils/tomahawkutilsgui.h index 6f8f08674..f353608de 100644 --- a/src/libtomahawk/utils/tomahawkutilsgui.h +++ b/src/libtomahawk/utils/tomahawkutilsgui.h @@ -19,6 +19,8 @@ #ifndef TOMAHAWKUTILSGUI_H #define TOMAHAWKUTILSGUI_H +#include + #include "tomahawkutils.h" #include "dllmacro.h" @@ -46,6 +48,8 @@ namespace TomahawkUtils DLLEXPORT int headerHeight(); DLLEXPORT void setHeaderHeight( int height ); + + DLLEXPORT QPixmap defaultPixmap( ImageType type, ImageMode mode, const QSize& size = QSize( 0, 0 ) ); } #endif // TOMAHAWKUTILSGUI_H diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index c91cb37ab..7d92d644d 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -61,7 +61,7 @@ AlbumInfoWidget::AlbumInfoWidget( const Tomahawk::album_ptr& album, ModelMode st ui->tracksView->setTreeModel( m_tracksModel ); ui->tracksView->setRootIsDecorated( false ); - m_pixmap = QPixmap( RESPATH "images/no-album-art-placeholder.png" ).scaledToWidth( 48, Qt::SmoothTransformation ); + m_pixmap = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::ScaledCover, QSize( 48, 48 ) ); m_button = new OverlayButton( ui->tracksView ); m_button->setCheckable( true ); diff --git a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp index 96e4dfc8b..b663b8a72 100644 --- a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp @@ -77,7 +77,7 @@ ArtistInfoWidget::ArtistInfoWidget( const Tomahawk::artist_ptr& artist, QWidget* ui->topHits->setTrackModel( m_topHitsModel ); ui->topHits->setSortingEnabled( false ); - m_pixmap = QPixmap( RESPATH "images/no-album-no-case.png" ).scaledToWidth( 48, Qt::SmoothTransformation ); + m_pixmap = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::ScaledCover, QSize( 48, 48 ) ); m_button = new OverlayButton( ui->albums ); m_button->setText( tr( "Click to show Super Collection Albums" ) ); From a04d384ac4d7433df2309cc52123ee159ab6aa5a Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 04:15:28 +0100 Subject: [PATCH 16/20] * Show default album, not artist cover on AlbumInfoWidget. --- src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index 7d92d644d..40ba83ec5 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -61,7 +61,7 @@ AlbumInfoWidget::AlbumInfoWidget( const Tomahawk::album_ptr& album, ModelMode st ui->tracksView->setTreeModel( m_tracksModel ); ui->tracksView->setRootIsDecorated( false ); - m_pixmap = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::ScaledCover, QSize( 48, 48 ) ); + m_pixmap = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::ScaledCover, QSize( 48, 48 ) ); m_button = new OverlayButton( ui->tracksView ); m_button->setCheckable( true ); From b2fc0935a449abf75a955bc0ad09988422d2856c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 05:18:55 +0100 Subject: [PATCH 17/20] * Show now playing speaker next to current playlist again. --- src/sourcetree/sourcedelegate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index c2ef76c31..1a391536b 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -132,7 +132,7 @@ SourceDelegate::paintDecorations( QPainter* painter, const QStyleOptionViewItem& if ( playable && playing && item->isBeingPlayed() ) { const int iconW = option.rect.height() - 4; - QRect iconRect = QRect( option.rect.x() - iconW - 4, option.rect.y() + 2, iconW, iconW ); + QRect iconRect = QRect( 4, option.rect.y() + 2, iconW, iconW ); QPixmap speaker = option.state & QStyle::State_Selected ? m_nowPlayingSpeaker : m_nowPlayingSpeakerDark; speaker = speaker.scaledToHeight( iconW, Qt::SmoothTransformation ); painter->drawPixmap( iconRect, speaker ); @@ -406,8 +406,6 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co if ( type != SourcesModel::Group && type != SourcesModel::Category && type != SourcesModel::Divider ) QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter ); - paintDecorations( painter, o3, index ); - if ( type == SourcesModel::Collection ) { paintCollection( painter, o, index ); @@ -538,6 +536,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } } + paintDecorations( painter, o3, index ); + painter->restore(); } From 47a403a9c88e6ad471ced285185e8bb3a5bbd7f8 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 05:40:00 +0100 Subject: [PATCH 18/20] * Update social actions everytime you open the contextual menu of a track. --- src/audiocontrols.cpp | 10 ++++++++-- src/libtomahawk/contextmenu.cpp | 1 + src/libtomahawk/query.cpp | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index a5baeb916..f6211ca06 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -277,10 +277,16 @@ void AudioControls::onSocialActionsLoaded() { Query* query = qobject_cast< Query* >( sender() ); - if ( !query || query != m_currentTrack->toQuery().data() ) + if ( !query ) return; - setSocialActions(); + query_ptr currentQuery = m_currentTrack->toQuery(); + if ( query->artist() == currentQuery->artist() && + query->track() == currentQuery->track() && + query->album() == currentQuery->album() ) + { + setSocialActions(); + } } diff --git a/src/libtomahawk/contextmenu.cpp b/src/libtomahawk/contextmenu.cpp index 558b9cb29..de5122a05 100644 --- a/src/libtomahawk/contextmenu.cpp +++ b/src/libtomahawk/contextmenu.cpp @@ -88,6 +88,7 @@ ContextMenu::setQueries( const QList& queries ) m_sigmap->setMapping( m_loveAction, ActionLove ); connect( queries.first().data(), SIGNAL( socialActionsLoaded() ), SLOT( onSocialActionsLoaded() ) ); + m_queries.first()->loadSocialActions(); onSocialActionsLoaded(); } diff --git a/src/libtomahawk/query.cpp b/src/libtomahawk/query.cpp index 8df9cd6f7..40eb1d515 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -489,7 +489,7 @@ Query::loadSocialActions() query_ptr q = m_ownRef.toStrongRef(); DatabaseCommand_LoadSocialActions* cmd = new DatabaseCommand_LoadSocialActions( q ); - connect( cmd, SIGNAL( finished() ), SLOT( onSocialActionsLoaded() )); + connect( cmd, SIGNAL( finished() ), SLOT( onSocialActionsLoaded() ) ); Database::instance()->enqueue( QSharedPointer(cmd) ); } From 6ad9e820e36470148243ad2be5a34d1b6973edeb Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 05:52:02 +0100 Subject: [PATCH 19/20] * Only show top 50 loved tracks for the SuperCollection. --- src/libtomahawk/playlist/customplaylistview.cpp | 8 ++++---- src/libtomahawk/playlist/customplaylistview.h | 2 +- src/libtomahawk/viewmanager.cpp | 2 +- src/sourcetree/items/sourceitem.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libtomahawk/playlist/customplaylistview.cpp b/src/libtomahawk/playlist/customplaylistview.cpp index 1cb9517cd..5e81cd6ca 100644 --- a/src/libtomahawk/playlist/customplaylistview.cpp +++ b/src/libtomahawk/playlist/customplaylistview.cpp @@ -42,7 +42,7 @@ CustomPlaylistView::CustomPlaylistView( CustomPlaylistView::PlaylistType type, c if ( m_type == SourceLovedTracks ) connect( m_source.data(), SIGNAL( socialAttributesChanged( QString ) ), this, SLOT( socialAttributesChanged( QString ) ) ); - else if ( m_type == AllLovedTracks ) + else if ( m_type == TopLovedTracks ) { connect( SourceList::instance()->getLocal().data(), SIGNAL( socialAttributesChanged( QString ) ), this, SLOT( socialAttributesChanged( QString ) ) ); foreach ( const source_ptr& s, SourceList::instance()->sources( true ) ) @@ -86,12 +86,12 @@ CustomPlaylistView::generateTracks() "GROUP BY track.id " "ORDER BY counter DESC, social_attributes.timestamp DESC " ).arg( m_source->isLocal() ? "IS NULL" : QString( "= %1" ).arg( m_source->id() ) ); break; - case AllLovedTracks: + case TopLovedTracks: sql = QString( "SELECT track.name, artist.name, source, COUNT(*) as counter " "FROM social_attributes, track, artist " - "WHERE social_attributes.id = track.id AND artist.id = track.artist AND social_attributes.k = 'Love' AND social_attributes.v = 'true'" + "WHERE social_attributes.id = track.id AND artist.id = track.artist AND social_attributes.k = 'Love' AND social_attributes.v = 'true' " "GROUP BY track.id " - "ORDER BY counter DESC, social_attributes.timestamp DESC " ); + "ORDER BY counter DESC, social_attributes.timestamp DESC LIMIT 0, 50" ); break; } diff --git a/src/libtomahawk/playlist/customplaylistview.h b/src/libtomahawk/playlist/customplaylistview.h index f03ede49b..692980e40 100644 --- a/src/libtomahawk/playlist/customplaylistview.h +++ b/src/libtomahawk/playlist/customplaylistview.h @@ -33,7 +33,7 @@ class DLLEXPORT CustomPlaylistView : public PlaylistView public: enum PlaylistType { SourceLovedTracks, - AllLovedTracks + TopLovedTracks }; explicit CustomPlaylistView( PlaylistType type, const source_ptr& s, QWidget* parent = 0 ); diff --git a/src/libtomahawk/viewmanager.cpp b/src/libtomahawk/viewmanager.cpp index 0816d8203..d87f9038a 100644 --- a/src/libtomahawk/viewmanager.cpp +++ b/src/libtomahawk/viewmanager.cpp @@ -432,7 +432,7 @@ Tomahawk::ViewPage* ViewManager::showTopLovedPage() { if ( !m_topLovedWidget ) - m_topLovedWidget = new CustomPlaylistView( CustomPlaylistView::AllLovedTracks, source_ptr(), m_widget ); + m_topLovedWidget = new CustomPlaylistView( CustomPlaylistView::TopLovedTracks, source_ptr(), m_widget ); return show( m_topLovedWidget ); } diff --git a/src/sourcetree/items/sourceitem.cpp b/src/sourcetree/items/sourceitem.cpp index f4084b38e..ede45229e 100644 --- a/src/sourcetree/items/sourceitem.cpp +++ b/src/sourcetree/items/sourceitem.cpp @@ -486,7 +486,7 @@ ViewPage* SourceItem::lovedTracksClicked() { if ( !m_lovedTracksPage ) - m_lovedTracksPage = new CustomPlaylistView( m_source.isNull() ? CustomPlaylistView::AllLovedTracks : CustomPlaylistView::SourceLovedTracks, m_source, ViewManager::instance()->widget() ); + m_lovedTracksPage = new CustomPlaylistView( m_source.isNull() ? CustomPlaylistView::TopLovedTracks : CustomPlaylistView::SourceLovedTracks, m_source, ViewManager::instance()->widget() ); ViewManager::instance()->show( m_lovedTracksPage ); return m_lovedTracksPage; From cfe42d10ead43ac476b766f6184adb2ea08e26ee Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 24 Feb 2012 06:29:32 +0100 Subject: [PATCH 20/20] * Minor cleanups. --- .../infoplugins/generic/spotifyPlugin.cpp | 29 +++++-------------- src/libtomahawk/pipeline.cpp | 5 ++-- src/sourcetree/sourcetreeview.cpp | 4 +-- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/src/libtomahawk/infosystem/infoplugins/generic/spotifyPlugin.cpp b/src/libtomahawk/infosystem/infoplugins/generic/spotifyPlugin.cpp index f91476752..bcff0d24b 100644 --- a/src/libtomahawk/infosystem/infoplugins/generic/spotifyPlugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/generic/spotifyPlugin.cpp @@ -43,7 +43,6 @@ SpotifyPlugin::SpotifyPlugin() : InfoPlugin() , m_chartsFetchJobs( 0 ) { - m_supportedGetTypes << InfoChart << InfoChartCapabilities; } @@ -71,7 +70,6 @@ SpotifyPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ) InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >(); - switch ( requestData.type ) { case InfoChart: @@ -87,6 +85,7 @@ SpotifyPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ) case InfoChartCapabilities: fetchChartCapabilities( requestData ); break; + default: dataError( requestData ); } @@ -110,7 +109,6 @@ SpotifyPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData ) tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Hash did not contain required params!"; dataError( requestData ); return; - } /// Set the criterias for current chart criteria["chart_id"] = hash["chart_id"]; @@ -118,6 +116,8 @@ SpotifyPlugin::fetchChart( Tomahawk::InfoSystem::InfoRequestData requestData ) emit getCachedInfo( criteria, 86400000 /* Expire chart cache in 1 day */, requestData ); } + + void SpotifyPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData requestData ) { @@ -132,12 +132,12 @@ SpotifyPlugin::fetchChartCapabilities( Tomahawk::InfoSystem::InfoRequestData req emit getCachedInfo( criteria, 604800000, requestData ); } + void SpotifyPlugin::notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, Tomahawk::InfoSystem::InfoRequestData requestData ) { switch ( requestData.type ) { - case InfoChart: { /// Fetch the chart, we need source and id @@ -149,8 +149,6 @@ SpotifyPlugin::notInCacheSlot( Tomahawk::InfoSystem::InfoStringHash criteria, To reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) ); connect( reply, SIGNAL( finished() ), SLOT( chartReturned() ) ); return; - - } case InfoChartCapabilities: { @@ -212,9 +210,8 @@ SpotifyPlugin::chartTypes() } QVariantMap charts; - foreach(QVariant geos, chartObj.value("Charts").toList().takeLast().toMap().value("geo").toList() ) + foreach( QVariant geos, chartObj.value( "Charts" ).toList().takeLast().toMap().value( "geo" ).toList() ) { - const QString geo = geos.toMap().value( "name" ).toString(); const QString geoId = geos.toMap().value( "id" ).toString(); QString country; @@ -225,7 +222,6 @@ SpotifyPlugin::chartTypes() country = geo; else { - QLocale l( QString( "en_%1" ).arg( geo ) ); country = Tomahawk::CountryUtils::fullCountryFromCode( geo ); @@ -240,7 +236,7 @@ SpotifyPlugin::chartTypes() } QList< InfoStringHash > chart_types; - foreach(QVariant types, chartObj.value("Charts").toList().takeFirst().toMap().value("types").toList() ) + foreach( QVariant types, chartObj.value( "Charts" ).toList().takeFirst().toMap().value( "types" ).toList() ) { QString type = types.toMap().value( "id" ).toString(); QString label = types.toMap().value( "name" ).toString(); @@ -251,18 +247,15 @@ SpotifyPlugin::chartTypes() c[ "type" ] = type; chart_types.append( c ); - } charts.insert( country.toUtf8(), QVariant::fromValue >( chart_types ) ); - } QVariantMap defaultMap; defaultMap[ "spotify" ] = QStringList() << "United States" << "Top Albums"; m_allChartsMap[ "defaults" ] = defaultMap; m_allChartsMap.insert( "Spotify", QVariant::fromValue( charts ) ); - } else { @@ -281,13 +274,12 @@ SpotifyPlugin::chartTypes() } m_cachedRequests.clear(); } - } + void SpotifyPlugin::chartReturned() { - /// Chart request returned something! Woho QNetworkReply* reply = qobject_cast( sender() ); QString url = reply->url().toString(); @@ -318,14 +310,13 @@ SpotifyPlugin::chartReturned() else setChartType( None ); - foreach(QVariant result, res.value("toplist").toMap().value("result").toList() ) + foreach( QVariant result, res.value( "toplist" ).toMap().value( "result" ).toList() ) { QString title, artist; QVariantMap chartMap = result.toMap(); if ( !chartMap.isEmpty() ) { - title = chartMap.value( "title" ).toString(); artist = chartMap.value( "artist" ).toString(); @@ -341,7 +332,6 @@ SpotifyPlugin::chartReturned() if( chartType() == Album ) { - InfoStringHash pair; pair["artist"] = artist; pair["album"] = title; @@ -351,10 +341,8 @@ SpotifyPlugin::chartReturned() if( chartType() == Artist ) { - top_artists << chartMap.value( "name" ).toString(); qDebug() << "SpotifyChart type is artist"; - } } } @@ -393,5 +381,4 @@ SpotifyPlugin::chartReturned() } else qDebug() << "Network error in fetching chart:" << reply->url().toString(); - } diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index 18eb69347..2560edbb1 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -413,10 +413,11 @@ Pipeline::shunt( const query_ptr& q ) r->resolve( q ); emit resolving( q ); - m_qidsTimeout.insert( q->id(), true ); - if ( r->timeout() > 0 ) + { + m_qidsTimeout.insert( q->id(), true ); new FuncTimeout( r->timeout(), boost::bind( &Pipeline::timeoutShunt, this, q ), this ); + } } else { diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 330e8aeee..a00deaec2 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -256,14 +256,14 @@ SourceTreeView::selectRequest( const QPersistentModelIndex& idx ) void -SourceTreeView::expandRequest( const QPersistentModelIndex &idx ) +SourceTreeView::expandRequest( const QPersistentModelIndex& idx ) { expand( idx ); } void -SourceTreeView::toggleExpandRequest( const QPersistentModelIndex &idx ) +SourceTreeView::toggleExpandRequest( const QPersistentModelIndex& idx ) { if ( isExpanded( idx ) ) collapse( idx );