diff --git a/CMakeLists.txt b/CMakeLists.txt index c63ef54c7..6b91c8371 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ PROJECT( tomahawk ) -CMAKE_MINIMUM_REQUIRED( VERSION 2.8 ) +CMAKE_MINIMUM_REQUIRED( VERSION 2.8.6 ) SET( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules" ) IF( ${CMAKE_VERSION} VERSION_GREATER 2.8.3 ) @@ -15,11 +15,13 @@ SET( TOMAHAWK_APPLICATION_NAME "Tomahawk" ) SET( TOMAHAWK_DESCRIPTION_SUMMARY "The social media player" ) SET( TOMAHAWK_VERSION_MAJOR 0 ) -SET( TOMAHAWK_VERSION_MINOR 3 ) +SET( TOMAHAWK_VERSION_MINOR 4 ) SET( TOMAHAWK_VERSION_PATCH 99 ) #SET( TOMAHAWK_VERSION_RC 0 ) +# enforce proper symbol exporting on all platforms +add_definitions( "-fvisibility=hidden" ) # build options option(BUILD_GUI "Build Tomahawk with GUI" ON) @@ -120,19 +122,6 @@ macro_log_feature(LIBJREEN_FOUND "Jreen" "Qt XMPP Library" "https://github.com/e macro_optional_find_package(QTweetLib) macro_log_feature(QTWEETLIB_FOUND "QTweetLib" "Qt Twitter Library" "https://github.com/minimoog/QTweetLib" FALSE "" "QTweetLib is needed for the Twitter SIP plugin.\n") - -IF( NOT QuaZip_FOUND ) - add_subdirectory( ${CMAKE_SOURCE_DIR}/src/libtomahawk/thirdparty/quazip ) - SET( QuaZip_INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/libtomahawk/thirdparty/quazip ) - SET( QuaZip_LIBRARY quazip ) - SET( QuaZip_LIBRARIES ${QuaZip_LIBRARY} ) - SET( QuaZip_FOUND true ) - macro_log_feature(QuaZip_FOUND "QuaZip" "Provides support for extracting downloaded resolvers automatically. Building internal copy" "http://quazip.sourceforge.net/" FALSE "" "") - - # copy headers to build/quazip so we can use proper includes inside the code - FILE( COPY ${CMAKE_SOURCE_DIR}/src/libtomahawk/thirdparty/quazip/quazip/ DESTINATION ${CMAKE_BINARY_DIR}/libtomahawk/thirdparty/quazip ) -ENDIF() - # required #While we distribute our own liblastfm2, don't need to look for it #macro_optional_find_package(LibLastFm 0.3.3) diff --git a/ChangeLog b/ChangeLog index fa612aac7..d7433e89b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +Version 0.4.0: + * Added visual notification for database indexing job. + * Fixed icons not appearing in resolvers list. + * Fixed various UI glitches and stray error messages in stations. + * Fixed bug where album page would resolve bottom-to-top. + * Fixed bug where Footnotes would not update when changing selected album in Album View. + * Fixed dragging albums and artists from charts, album, and artist views. + * Fixed bug where filter text would be one step behind filter value. + * Fixed bug where resolvers would enable themselves after auto-updating. + * Fixed occasional crash when dropping tracks onto New Station item. + * Added jump-to-current-track support for search results page. + * Fixed out of sync Show/Hide menu items on OS X when hidden with cmd-h. + * Fixed non-resolving tracks when dragging from album view. + * Fixed /Volumes directory not showing up on OS X. + * Fixed fetching album covers for albums with special characters. + * Show errors and continue gracefully when resolved audio is not available. + * Fixed various crashes on exit. + * Added basic command-line options for playback control. + * Bumped up web api timeouts to allow web clients to finish resolving. + * Added filename suggestion when exporting a playlist. + * Cleaned up highlighting of artist names in album view. + * Cleaned up alignment of playlist items. + * Fixed potential crash when searching. + * Added support for disc number. + * Added SoundCloudWall.com charts. + * Added ability to "lock on" to a user when listening along, to skip along. + * Fixed bug where loved tracks would be refreshed much too often. + * Fixed startup crash on OS X. + * Fixed some font size issues. + * Sped up Tomahawk startup by moving chart loading into a separate thread. + * Added support for parsing Grooveshark and Tinysong tracks and playlists. + * Reorganized sidebar to follow more logical item groupings. + * Added artist and album results to global searches. + * Fixed style and contrast issues when using GTK styles. + * Fixed paths to artwork when using MPRIS2 interface. + Version 0.3.3: * Automatically load Super Collection tracks when no official release information is available. diff --git a/README b/README index 65df9b01f..8ada27b9c 100644 --- a/README +++ b/README @@ -39,6 +39,7 @@ Dependencies The following dependencies are optional, but recommended: Attica 0.2.0 - ftp://ftp.kde.org/pub/kde/stable/attica/ + QuaZip 0.4.3 - http://quazip.sourceforge.net/ Jreen 1.0.1 - https://github.com/euroelessar/jreen QTweetLib 0.3.0 - https://github.com/minimoog/QTweetLib @@ -46,6 +47,5 @@ Dependencies MiniUPnP 1.6 - http://miniupnp.free.fr/ liblastfm 0.4.0 - http://github.com/jonocole/liblastfm/ - QuaZip 0.4.3 - http://quazip.sourceforge.net/ Enjoy! diff --git a/admin/mac/macdeploy.py b/admin/mac/macdeploy.py index 2c2e0e221..f5ce32875 100755 --- a/admin/mac/macdeploy.py +++ b/admin/mac/macdeploy.py @@ -491,7 +491,7 @@ def FindVLCPlugin(name): FixBinary(binary) for plugin in VLC_PLUGINS: - FixVLCPlugin(FindVLCPlugin(plugin), '.') + FixVLCPlugin(FindVLCPlugin(plugin), '../Frameworks/vlc/plugins') for plugin in TOMAHAWK_PLUGINS: FixPlugin(plugin, '../MacOS') diff --git a/data/images/grooveshark.png b/data/images/grooveshark.png new file mode 100644 index 000000000..d7cd43b33 Binary files /dev/null and b/data/images/grooveshark.png differ diff --git a/data/images/no-album-art-placeholder.png b/data/images/no-album-art-placeholder.png index a117306e2..d7f2bb28c 100644 Binary files a/data/images/no-album-art-placeholder.png and b/data/images/no-album-art-placeholder.png differ diff --git a/data/images/no-album-no-case.png b/data/images/no-album-no-case.png index 1fbbbb8bf..963cdaa3d 100644 Binary files a/data/images/no-album-no-case.png and b/data/images/no-album-no-case.png differ diff --git a/data/images/no-artist-image-placeholder.png b/data/images/no-artist-image-placeholder.png index 4b0eba323..559a858d4 100644 Binary files a/data/images/no-artist-image-placeholder.png and b/data/images/no-artist-image-placeholder.png differ diff --git a/data/images/process-stop.png b/data/images/process-stop.png new file mode 100644 index 000000000..e8b995f74 Binary files /dev/null and b/data/images/process-stop.png differ diff --git a/resources.qrc b/resources.qrc index 134cccd5d..ad8118fc5 100644 --- a/resources.qrc +++ b/resources.qrc @@ -133,7 +133,9 @@ <file>data/images/headphones-bigger.png</file> <file>data/images/no-album-no-case.png</file> <file>data/images/rdio.png</file> + <file>data/images/grooveshark.png</file> <file>data/images/lastfm-icon.png</file> <file>data/sql/dbmigrate-27_to_28.sql</file> + <file>data/images/process-stop.png</file> </qresource> </RCC> diff --git a/src/AccountDelegate.cpp b/src/AccountDelegate.cpp index 4af815b76..278b29497 100644 --- a/src/AccountDelegate.cpp +++ b/src/AccountDelegate.cpp @@ -34,10 +34,10 @@ #define PADDING_BETWEEN_STARS 2 #define STAR_SIZE 12 -#ifdef Q_WS_MAC -#define TOPLEVEL_ACCOUNT_HEIGHT 72 +#ifdef Q_OS_MAC +#define ROW_HEIGHT_MULTIPLIER 4.9 #else -#define TOPLEVEL_ACCOUNT_HEIGHT 68 +#define ROW_HEIGHT_MULTIPLIER 5.7 #endif #define ICONSIZE 40 @@ -52,6 +52,7 @@ using namespace Accounts; AccountDelegate::AccountDelegate( QObject* parent ) : QStyledItemDelegate ( parent ) + , m_accountRowHeight( -1 ) { m_defaultCover.load( RESPATH "images/sipplugin-online.png" ); @@ -75,20 +76,31 @@ AccountDelegate::AccountDelegate( QObject* parent ) QSize -AccountDelegate::sizeHint( const QStyleOptionViewItem&, const QModelIndex& index ) const +AccountDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { AccountModel::RowType rowType = static_cast< AccountModel::RowType >( index.data( AccountModel::RowTypeRole ).toInt() ); + if ( m_accountRowHeight < 0 ) + { + // Haven't calculated normal item height yet, do it once and save it + QStyleOptionViewItemV4 opt( option ); + initStyleOption( &opt, index ); + m_accountRowHeight = ROW_HEIGHT_MULTIPLIER * opt.fontMetrics.height(); + } + if ( rowType == AccountModel::TopLevelAccount || rowType == AccountModel::UniqueFactory || rowType == AccountModel::CustomAccount ) - return QSize( 200, TOPLEVEL_ACCOUNT_HEIGHT ); + { + + return QSize( 200, m_accountRowHeight ); + } else if ( rowType == AccountModel::TopLevelFactory ) { // Make more space for each account we have to show. AccountFactory* fac = qobject_cast< AccountFactory* >( index.data( AccountModel::AccountData ).value< QObject* >() ); if ( fac->isUnique() ) - return QSize( 200, TOPLEVEL_ACCOUNT_HEIGHT ); + return QSize( 200, m_accountRowHeight ); const QList< Account* > accts = index.data( AccountModel::ChildrenOfFactoryRole ).value< QList< Tomahawk::Accounts::Account* > >(); - const QSize s = QSize( 200, TOPLEVEL_ACCOUNT_HEIGHT + 12 * accts.size()-1 ); + const QSize s = QSize( 200, m_accountRowHeight + 12 * accts.size()-1 ); if ( s != m_sizeHints[ index ] ) const_cast< AccountDelegate* >( this )->sizeHintChanged( index ); // FU KTHBBQ @@ -200,7 +212,6 @@ AccountDelegate::paint ( QPainter* painter, const QStyleOptionViewItem& option, { painter->save(); painter->setFont( installFont ); - int oldRightEdge = rightEdge; rightEdge = drawAccountList( painter, opt, accts, rightEdge ); painter->restore(); diff --git a/src/AccountDelegate.h b/src/AccountDelegate.h index 6787489b4..01c7f6a9c 100644 --- a/src/AccountDelegate.h +++ b/src/AccountDelegate.h @@ -64,6 +64,7 @@ private: mutable QHash< QPersistentModelIndex, QRect > m_cachedStarRects; mutable QHash< QPersistentModelIndex, QRect > m_cachedConfigRects; mutable QHash< QPersistentModelIndex, QSize > m_sizeHints; + mutable int m_accountRowHeight; }; } diff --git a/src/AccountFactoryWrapper.cpp b/src/AccountFactoryWrapper.cpp index f5f270b06..862239fa8 100644 --- a/src/AccountFactoryWrapper.cpp +++ b/src/AccountFactoryWrapper.cpp @@ -19,7 +19,8 @@ #include "AccountFactoryWrapper.h" #include "accounts/Account.h" -#include <accounts/AccountManager.h> +#include "accounts/AccountManager.h" +#include "guihelpers.h" #include "AccountFactoryWrapperDelegate.h" #include "delegateconfigwrapper.h" #include "ui_AccountFactoryWrapper.h" @@ -29,7 +30,6 @@ AccountFactoryWrapper::AccountFactoryWrapper( AccountFactory* factory, QWidget* : QDialog( parent, Qt::Sheet ) , m_factory( factory ) , m_ui( new Ui_AccountFactoryWrapper ) - , m_createAccount( false ) { m_ui->setupUi( this ); @@ -92,40 +92,10 @@ AccountFactoryWrapper::load() void AccountFactoryWrapper::openAccountConfig( Account* account ) { - if( account->configurationWidget() ) - { -#ifndef Q_WS_MAC - DelegateConfigWrapper dialog( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), this ); - QWeakPointer< DelegateConfigWrapper > watcher( &dialog ); - int ret = dialog.exec(); - if( !watcher.isNull() && ret == QDialog::Accepted ) - { - // send changed config to resolver - account->saveConfig(); - } -#else - // on osx a sheet needs to be non-modal - DelegateConfigWrapper* dialog = new DelegateConfigWrapper( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), this, Qt::Sheet ); - dialog->setProperty( "accountplugin", QVariant::fromValue< QObject* >( account ) ); - connect( dialog, SIGNAL( finished( int ) ), this, SLOT( accountConfigClosed( int ) ) ); - - dialog->show(); -#endif - } + TomahawkUtils::openAccountConfig( account, this, false ); } -void -AccountFactoryWrapper::accountConfigClosed( int value ) -{ - if( value == QDialog::Accepted ) - { - DelegateConfigWrapper* dialog = qobject_cast< DelegateConfigWrapper* >( sender() ); - Account* account = qobject_cast< Account* >( dialog->property( "accountplugin" ).value< QObject* >() ); - account->saveConfig(); - } -} - void AccountFactoryWrapper::removeAccount( Tomahawk::Accounts::Account* acct ) { @@ -139,9 +109,7 @@ AccountFactoryWrapper::buttonClicked( QAbstractButton* button ) { if ( button == m_addButton ) { - m_createAccount = true; - emit createAccount( m_factory ); - return; + TomahawkUtils::createAccountFromFactory( m_factory, this ); } else reject(); diff --git a/src/AccountFactoryWrapper.h b/src/AccountFactoryWrapper.h index 02c8ec335..e69cf7166 100644 --- a/src/AccountFactoryWrapper.h +++ b/src/AccountFactoryWrapper.h @@ -42,14 +42,8 @@ public: explicit AccountFactoryWrapper( Tomahawk::Accounts::AccountFactory* factory, QWidget* parent = 0 ); - bool doCreateAccount() const { return m_createAccount; } - -signals: - void createAccount( Tomahawk::Accounts::AccountFactory* factory ); - public slots: void openAccountConfig( Tomahawk::Accounts::Account* ); - void accountConfigClosed( int value ); void removeAccount( Tomahawk::Accounts::Account* ); private slots: @@ -60,7 +54,6 @@ private: Tomahawk::Accounts::AccountFactory* m_factory; Ui_AccountFactoryWrapper* m_ui; QPushButton* m_addButton; - bool m_createAccount; }; #endif // ACCOUNTFACTORYWRAPPER_H diff --git a/src/CMakeLists.osx.cmake b/src/CMakeLists.osx.cmake index 9239931eb..f7a502f47 100644 --- a/src/CMakeLists.osx.cmake +++ b/src/CMakeLists.osx.cmake @@ -58,4 +58,7 @@ if (APPLE) FILE(COPY ${CMAKE_SOURCE_DIR}/admin/mac/sparkle_pub.pem DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/Resources") + FILE(COPY /usr/bin/SetFile DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/MacOS") + FILE(COPY /usr/bin/GetFileInfo DESTINATION "${CMAKE_BINARY_DIR}/tomahawk.app/Contents/MacOS") + endif (APPLE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ee8ac9b0b..8cc5dd1bf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -70,63 +70,21 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} breakpad/BreakPad.cpp + utils/guihelpers.cpp + tomahawktrayicon.cpp audiocontrols.cpp settingsdialog.cpp diagnosticsdialog.cpp AccountDelegate.cpp settingslistdelegate.cpp + delegateconfigwrapper.cpp tomahawkwindow.cpp LoadXSPFDialog.cpp AccountFactoryWrapper.cpp AccountFactoryWrapperDelegate.cpp ) -SET( tomahawkHeaders ${tomahawkHeaders} - tomahawkapp.h - - web/api_v1.h - - musicscanner.h - scanmanager.h - ubuntuunityhack.h - shortcuthandler.h -) - -IF(LIBLASTFM_FOUND) - SET(tomahawkHeaders ${tomahawkHeaders} - scrobbler.h - ) -ENDIF(LIBLASTFM_FOUND) - -SET( tomahawkHeadersGui ${tomahawkHeadersGui} - sourcetree/sourcesmodel.h - sourcetree/sourcesproxymodel.h - sourcetree/sourcetreeview.h - sourcetree/sourcedelegate.h - sourcetree/animationhelper.h - sourcetree/items/sourcetreeitem.h - sourcetree/items/sourceitem.h - sourcetree/items/playlistitems.h - sourcetree/items/categoryitems.h - sourcetree/items/genericpageitems.h - sourcetree/items/temporarypageitem.h - sourcetree/items/groupitem.h - sourcetree/items/historyitem.h - - tomahawktrayicon.h - audiocontrols.h - settingsdialog.h - diagnosticsdialog.h - AccountDelegate.h - settingslistdelegate.h - delegateconfigwrapper.h - tomahawkwindow.h - LoadXSPFDialog.h - AccountFactoryWrapper.h - AccountFactoryWrapperDelegate.h -) - SET( tomahawkUI ${tomahawkUI} tomahawkwindow.ui diagnosticsdialog.ui @@ -180,17 +138,11 @@ ENDIF( UNIX ) IF( APPLE ) INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/thirdparty/SPMediaKeyTap ) - SET( tomahawkHeaders ${tomahawkHeaders} mac/tomahawkapp_mac.h mac/macshortcuthandler.h ) SET( tomahawkSources ${tomahawkSources} mac/tomahawkapp_mac.mm mac/macshortcuthandler.cpp ) - - IF(HAVE_SPARKLE) - SET( tomahawkHeaders ${tomahawkHeaders} ${SPARKLE}/Headers ) - ENDIF(HAVE_SPARKLE) ENDIF( APPLE ) IF(GLOOX_FOUND) INCLUDE_DIRECTORIES( ${GLOOX_INCLUDE_DIR} ) - SET( tomahawkHeaders ${tomahawkHeaders} xmppbot/xmppbot.h ) SET( tomahawkSources ${tomahawkSources} xmppbot/xmppbot.cpp ) ENDIF(GLOOX_FOUND) @@ -206,17 +158,16 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in include( ${CMAKE_SOURCE_DIR}/lang/translations.cmake ) -SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${tomahawkHeaders} ${trans_outfile}) +SET( final_src ${final_src} ${tomahawkMoc} ${tomahawkSources} ${trans_outfile}) IF( BUILD_GUI ) - LIST(APPEND tomahawkHeaders ${tomahawkHeadersGui}) LIST(APPEND tomahawkSources ${tomahawkSourcesGui}) qt4_wrap_ui( tomahawkUI_H ${tomahawkUI} ) ENDIF() kde4_add_app_icon( tomahawkSources "${CMAKE_SOURCE_DIR}/data/icons/tomahawk-icon-*.png" ) qt4_add_resources( RC_SRCS "../resources.qrc" ) -qt4_wrap_cpp( tomahawkMoc ${tomahawkHeaders} ) + SET( final_src ${final_src} ${tomahawkUI_H} ${tomahawkMoc} ${tomahawkSources} ${RC_SRCS} ) IF( UNIX AND NOT APPLE ) @@ -224,13 +175,14 @@ IF( UNIX AND NOT APPLE ) ENDIF( UNIX AND NOT APPLE ) IF( APPLE ) ADD_EXECUTABLE( tomahawk MACOSX_BUNDLE ${final_src} ) - SET_TARGET_PROPERTIES(tomahawk PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist" -) + SET_TARGET_PROPERTIES(tomahawk PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_BINARY_DIR}/Info.plist") ENDIF( APPLE ) IF( WIN32 ) ADD_EXECUTABLE( tomahawk WIN32 ${final_src} ) ENDIF( WIN32 ) +SET_TARGET_PROPERTIES(tomahawk PROPERTIES AUTOMOC TRUE) + MESSAGE( STATUS "OS_SPECIFIC_LINK_LIBRARIES: ${OS_SPECIFIC_LINK_LIBRARIES}" ) SET(LINK_LIBRARIES "") diff --git a/src/audiocontrols.cpp b/src/audiocontrols.cpp index f536be7c6..f6211ca06 100644 --- a/src/audiocontrols.cpp +++ b/src/audiocontrols.cpp @@ -100,8 +100,6 @@ AudioControls::AudioControls( QWidget* parent ) m_sliderTimeLine.setCurveShape( QTimeLine::LinearCurve ); ui->seekSlider->setTimeLine( &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 ) ) ); @@ -264,14 +262,14 @@ 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 ); + ui->coverImage->setPixmap( TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::ScaledCover, ui->coverImage->size() ) ); } @@ -279,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/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/delegateconfigwrapper.cpp b/src/delegateconfigwrapper.cpp new file mode 100644 index 000000000..a7d8cb6a9 --- /dev/null +++ b/src/delegateconfigwrapper.cpp @@ -0,0 +1,114 @@ +/* === 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 "delegateconfigwrapper.h" + + +DelegateConfigWrapper::DelegateConfigWrapper( QWidget* conf, const QString& title, QWidget* parent, Qt::WindowFlags flags ) + : QDialog( parent, flags ) + , m_widget( conf ) + , m_deleted( false ) +{ + m_widget->setWindowFlags( Qt::Sheet ); +#ifdef Q_WS_MAC + m_widget->setVisible( true ); +#endif + setWindowTitle( title ); + QVBoxLayout* v = new QVBoxLayout( this ); + v->setContentsMargins( 0, 0, 0, 0 ); + v->addWidget( m_widget ); + + m_buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this ); + m_okButton = m_buttons->button( QDialogButtonBox::Ok ); + connect( m_buttons, SIGNAL( clicked( QAbstractButton*) ), this, SLOT( closed( QAbstractButton* ) ) ); + connect( this, SIGNAL( rejected() ), this, SLOT( rejected() ) ); + v->addWidget( m_buttons ); + + setLayout( v ); + +#ifdef Q_WS_MAC + setSizeGripEnabled( false ); + setMinimumSize( sizeHint() ); + setMaximumSize( sizeHint() ); // to remove the resize grip on osx this is the only way + + if( conf->metaObject()->indexOfSignal( "sizeHintChanged()" ) > -1 ) + connect( conf, SIGNAL( sizeHintChanged() ), this, SLOT( updateSizeHint() ) ); +#else + m_widget->setVisible( true ); +#endif + +} + + +void +DelegateConfigWrapper::setShowDelete( bool del ) +{ + if ( del ) + m_deleteButton = m_buttons->addButton( tr( "Delete Account" ), QDialogButtonBox::DestructiveRole ); +} + + +void +DelegateConfigWrapper::toggleOkButton( bool dataError ) +{ + // if dataError is True we want to set the button enabled to false + m_okButton->setEnabled( !dataError ); +} + + +void +DelegateConfigWrapper::closed( QAbstractButton* b ) +{ + // let the config widget live to see another day + layout()->removeWidget( m_widget ); + m_widget->setParent( 0 ); + m_widget->setVisible( false ); + + QDialogButtonBox* buttons = qobject_cast< QDialogButtonBox* >( sender() ); + if ( buttons->standardButton( b ) == QDialogButtonBox::Ok ) + done( QDialog::Accepted ); + else if ( b == m_deleteButton ) + { + m_deleted = true; + emit closedWithDelete(); + reject(); + } + else + done( QDialog::Rejected ); +} + + +void +DelegateConfigWrapper::rejected() +{ + layout()->removeWidget( m_widget ); + m_widget->setParent( 0 ); + m_widget->setVisible( false ); +} + + +void +DelegateConfigWrapper::updateSizeHint() +{ + hide(); + setSizeGripEnabled( false ); + setMinimumSize( sizeHint() ); + setMaximumSize( sizeHint() ); + + show(); +} + diff --git a/src/delegateconfigwrapper.h b/src/delegateconfigwrapper.h index 4a2d4bfd1..d934c481e 100644 --- a/src/delegateconfigwrapper.h +++ b/src/delegateconfigwrapper.h @@ -28,91 +28,23 @@ class DelegateConfigWrapper : public QDialog { Q_OBJECT public: - DelegateConfigWrapper( QWidget* conf, const QString& title, QWidget* parent, Qt::WindowFlags flags = 0 ) : QDialog( parent, flags ), m_widget( conf ), m_deleted( false ) - { - m_widget->setWindowFlags( Qt::Sheet ); -#ifdef Q_WS_MAC - m_widget->setVisible( true ); -#endif - setWindowTitle( title ); - QVBoxLayout* v = new QVBoxLayout( this ); - v->setContentsMargins( 0, 0, 0, 0 ); - v->addWidget( m_widget ); - - m_buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this ); - m_okButton = m_buttons->button( QDialogButtonBox::Ok ); - connect( m_buttons, SIGNAL( clicked( QAbstractButton*) ), this, SLOT( closed( QAbstractButton* ) ) ); - connect( this, SIGNAL( rejected() ), this, SLOT( rejected() ) ); - v->addWidget( m_buttons ); - - setLayout( v ); - -#ifdef Q_WS_MAC - setSizeGripEnabled( false ); - setMinimumSize( sizeHint() ); - setMaximumSize( sizeHint() ); // to remove the resize grip on osx this is the only way - - if( conf->metaObject()->indexOfSignal( "sizeHintChanged()" ) > -1 ) - connect( conf, SIGNAL( sizeHintChanged() ), this, SLOT( updateSizeHint() ) ); -#else - m_widget->setVisible( true ); -#endif - - } + DelegateConfigWrapper( QWidget* conf, const QString& title, QWidget* parent, Qt::WindowFlags flags = 0 ); ~DelegateConfigWrapper() {} - void setShowDelete( bool del ) - { - if ( del ) - m_deleteButton = m_buttons->addButton( tr( "Delete Account" ), QDialogButtonBox::DestructiveRole ); - } + void setShowDelete( bool del ); bool deleted() const { return m_deleted; } public slots: void toggleOkButton( bool dataError ) - { - // if dataError is True we want to set the button enabled to false - m_okButton->setEnabled( !dataError ); - } - void closed( QAbstractButton* b ) - { - // let the config widget live to see another day - layout()->removeWidget( m_widget ); - m_widget->setParent( 0 ); - m_widget->setVisible( false ); - - QDialogButtonBox* buttons = qobject_cast< QDialogButtonBox* >( sender() ); - if( buttons->standardButton( b ) == QDialogButtonBox::Ok ) - done( QDialog::Accepted ); - else if ( b == m_deleteButton ) - { - m_deleted = true; - emit closedWithDelete(); - reject(); - } - else - done( QDialog::Rejected ); - } + ; + void closed( QAbstractButton* b ); // we get a rejected() signal emitted if the user presses escape (and no clicked() signal ) - void rejected() - { - layout()->removeWidget( m_widget ); - m_widget->setParent( 0 ); - m_widget->setVisible( false ); - } + void rejected(); - void updateSizeHint() - { - hide(); - setSizeGripEnabled( false ); - setMinimumSize( sizeHint() ); - setMaximumSize( sizeHint() ); - - show(); - } + void updateSizeHint(); signals: void closedWithDelete(); diff --git a/src/libtomahawk/AtticaManager.cpp b/src/libtomahawk/AtticaManager.cpp index bdfbeb770..2ecbc6fe3 100644 --- a/src/libtomahawk/AtticaManager.cpp +++ b/src/libtomahawk/AtticaManager.cpp @@ -78,7 +78,10 @@ AtticaManager::loadPixmapsFromCache() } QPixmap* icon = new QPixmap( cacheDir.absoluteFilePath( file ) ); - m_resolverStates[ info.baseName() ].pixmap = icon; + if ( !icon->isNull() ) + { + m_resolverStates[ info.baseName() ].pixmap = icon; + } } } @@ -95,14 +98,21 @@ AtticaManager::savePixmapsToCache() foreach( const QString& id, m_resolverStates.keys() ) { - if ( !m_resolverStates[ id ].pixmap ) + if ( !m_resolverStates[ id ].pixmap || !m_resolverStates[ id ].pixmapDirty ) continue; const QString filename = cacheDir.absoluteFilePath( QString( "%1.png" ).arg( id ) ); - if ( !m_resolverStates[ id ].pixmap->save( filename ) ) + QFile f( filename ); + if ( !f.open( QIODevice::WriteOnly ) ) { tLog() << "Failed to open cache file for writing:" << filename; - continue; + } + else + { + if ( !m_resolverStates[ id ].pixmap->save( &f ) ) + { + tLog() << "Failed to save pixmap into opened file for writing:" << filename; + } } } } @@ -315,6 +325,7 @@ AtticaManager::resolverIconFetched() QPixmap* icon = new QPixmap; icon->loadFromData( data ); m_resolverStates[ resolverId ].pixmap = icon; + m_resolverStates[ resolverId ].pixmapDirty = true; } @@ -389,7 +400,7 @@ AtticaManager::upgradeResolver( const Content& resolver ) emit resolverStateChanged( resolver.id() ); uninstallResolver( resolver ); - installResolver( resolver ); + installResolver( resolver, false ); } @@ -447,6 +458,7 @@ AtticaManager::payloadFetched() // Do the install / add to tomahawk Tomahawk::Accounts::Account* resolver = Tomahawk::Accounts::ResolverAccountFactory::createFromPath( resolverPath, true ); Tomahawk::Accounts::AccountManager::instance()->addAccount( resolver ); + TomahawkSettings::instance()->addAccount( resolver->accountId() ); } m_resolverStates[ resolverId ].state = Installed; diff --git a/src/libtomahawk/AtticaManager.h b/src/libtomahawk/AtticaManager.h index 0b6fcfef6..09b441dd5 100644 --- a/src/libtomahawk/AtticaManager.h +++ b/src/libtomahawk/AtticaManager.h @@ -26,16 +26,12 @@ #include <QPixmap> #include "dllmacro.h" +#include "accounts/Account.h" #include <attica/provider.h> #include <attica/providermanager.h> #include <attica/content.h> -namespace Tomahawk { -namespace Accounts { -class Account; -} -} class DLLEXPORT AtticaManager : public QObject { @@ -56,9 +52,12 @@ public: ResolverState state; QPixmap* pixmap; + // internal + bool pixmapDirty; + Resolver( const QString& v, const QString& path, int userR, ResolverState s ) - : version( v ), scriptPath( path ), userRating( userR ), state( s ), pixmap( 0 ) {} - Resolver() : userRating( -1 ), state( Uninstalled ), pixmap( 0 ) {} + : version( v ), scriptPath( path ), userRating( userR ), state( s ), pixmap( 0 ), pixmapDirty( false ) {} + Resolver() : userRating( -1 ), state( Uninstalled ), pixmap( 0 ), pixmapDirty( false ) {} }; typedef QHash< QString, AtticaManager::Resolver > StateHash; @@ -137,6 +136,19 @@ private: static AtticaManager* s_instance; }; +class DLLEXPORT CustomAtticaAccount : public Tomahawk::Accounts::Account +{ + Q_OBJECT +public: + virtual ~CustomAtticaAccount() {} + + virtual Attica::Content atticaContent() const = 0; + +protected: + // No, you can't. + CustomAtticaAccount( const QString& id ) : Tomahawk::Accounts::Account( id ) {} +}; + Q_DECLARE_METATYPE( Attica::Content ); #endif // ATTICAMANAGER_H diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 9aa842e64..e0e73ce14 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -35,9 +35,12 @@ set( libGuiSources jobview/JobStatusView.cpp jobview/JobStatusModel.cpp jobview/JobStatusDelegate.cpp + jobview/JobStatusItem.cpp jobview/PipelineStatusItem.cpp jobview/TransferStatusItem.cpp jobview/LatchedStatusItem.cpp + jobview/ErrorStatusMessage.cpp + jobview/IndexingJobItem.cpp infobar/infobar.cpp @@ -113,6 +116,7 @@ set( libGuiSources utils/proxystyle.cpp utils/tomahawkutilsgui.cpp + widgets/animatedcounterlabel.cpp widgets/checkdirtree.cpp widgets/querylabel.cpp widgets/imagebutton.cpp @@ -145,137 +149,6 @@ IF(QCA2_FOUND) set( libGuiSources ${libGuiSources} utils/groovesharkparser.cpp ) ENDIF(QCA2_FOUND) -set( libGuiHeaders - actioncollection.h - - contextmenu.h - dropjob.h - viewmanager.h - globalactionmanager.h - LatchManager.h - TomahawkSettingsGui.h - - context/ContextPage.h - context/ContextWidget.h - context/pages/TopTracksContext.h - context/pages/RelatedArtistsContext.h - context/pages/WikipediaContext.h - context/pages/WebContext.h - - infobar/infobar.h - - playlist/topbar/topbar.h - playlist/topbar/clearbutton.h - playlist/topbar/searchlineedit.h - playlist/topbar/lineedit.h - playlist/topbar/lineedit_p.h - playlist/topbar/searchbutton.h - - playlist/treemodel.h - playlist/treeproxymodel.h - playlist/treeproxymodelplaylistinterface.h - playlist/treeheader.h - playlist/treeitemdelegate.h - playlist/collectionproxymodel.h - playlist/collectionproxymodelplaylistinterface.h - playlist/collectionflatmodel.h - playlist/collectionview.h - playlist/playlistmodel.h - playlist/playlistproxymodel.h - playlist/playlistproxymodelplaylistinterface.h - playlist/playlistview.h - playlist/playlistitemdelegate.h - playlist/queueproxymodel.h - playlist/queueproxymodelplaylistinterface.h - playlist/queueview.h - playlist/trackmodel.h - playlist/trackmodelitem.h - playlist/trackproxymodel.h - playlist/trackproxymodelplaylistinterface.h - playlist/trackview.h - playlist/trackheader.h - playlist/treemodelitem.h - playlist/albumitem.h - playlist/albummodel.h - playlist/albumproxymodel.h - playlist/albumproxymodelplaylistinterface.h - playlist/albumitemdelegate.h - playlist/albumview.h - playlist/artistview.h - playlist/customplaylistview.h - playlist/ViewHeader.h - - playlist/dynamic/DynamicPlaylist.h - playlist/dynamic/GeneratorInterface.h - playlist/dynamic/DynamicView.h - playlist/dynamic/DynamicModel.h - playlist/dynamic/echonest/EchonestGenerator.h - playlist/dynamic/echonest/EchonestControl.h - playlist/dynamic/echonest/EchonestSteerer.h - playlist/dynamic/widgets/DynamicWidget.h - playlist/dynamic/widgets/DynamicControlWrapper.h - playlist/dynamic/widgets/DynamicControlList.h - playlist/dynamic/widgets/ReadOrWriteWidget.h - playlist/dynamic/widgets/MiscControlWidgets.h - playlist/dynamic/widgets/CollapsibleControls.h - playlist/dynamic/widgets/DynamicSetupWidget.h - playlist/dynamic/widgets/LoadingSpinner.h - - ExternalResolverGui.h - resolvers/scriptresolver.h - resolvers/qtscriptresolver.h - - utils/widgetdragfilter.h - utils/xspfgenerator.h - utils/jspfloader.h - utils/spotifyparser.h - utils/m3uloader.h - utils/itunesparser.h - utils/rdioparser.h - utils/shortenedlinkparser.h - utils/dropjobnotifier.h - - widgets/checkdirtree.h - widgets/querylabel.h - widgets/animatedcounterlabel.h - widgets/imagebutton.h - widgets/animatedsplitter.h - widgets/elidedlabel.h - widgets/newplaylistwidget.h - widgets/searchwidget.h - widgets/SeekSlider.h - widgets/playlisttypeselectordlg.h - widgets/welcomewidget.h - widgets/whatshotwidget.h - widgets/whatshotwidget_p.h - widgets/ChartDataLoader.h - widgets/RecentlyPlayedPlaylistsModel.h - widgets/RecentPlaylistsModel.h - widgets/OverlayButton.h - widgets/overlaywidget.h - widgets/HeaderLabel.h - widgets/HeaderWidget.h - widgets/combobox.h - widgets/ToggleButton.h - widgets/SocialPlaylistWidget.h - widgets/infowidgets/sourceinfowidget.h - widgets/infowidgets/ArtistInfoWidget.h - widgets/infowidgets/ArtistInfoWidget_p.h - widgets/infowidgets/AlbumInfoWidget.h - widgets/Breadcrumb.h - widgets/BreadcrumbButton.h - - jobview/JobStatusView.h - jobview/JobStatusModel.h - jobview/JobStatusDelegate.h - jobview/JobStatusItem.h - jobview/PipelineStatusItem.h - jobview/TransferStatusItem.h - jobview/LatchedStatusItem.h - - thirdparty/Qocoa/qsearchfield.h -) - IF(QCA2_FOUND) set( libGuiHeaders ${libGuiHeaders} utils/groovesharkparser.h ) ENDIF(QCA2_FOUND) @@ -291,6 +164,7 @@ set( libSources album.cpp albumplaylistinterface.cpp collection.cpp + functimeout.cpp playlist.cpp playlistplaylistinterface.cpp resolver.cpp @@ -423,156 +297,6 @@ set( libSources thirdparty/kdsingleapplicationguard/kdlockedsharedmemorypointer.cpp ) -set( libHeaders - tomahawksettings.h - sourcelist.h - pipeline.h - functimeout.h - - playlistinterface.h - - aclsystem.h - collection.h - query.h - resolver.h - ExternalResolver.h - result.h - source.h - sourceplaylistinterface.h - - artist.h - artistplaylistinterface.h - album.h - albumplaylistinterface.h - playlist.h - playlistplaylistinterface.h - viewpage.h - - accounts/Account.h - accounts/AccountManager.h - accounts/AccountModel.h - accounts/AccountModelFilterProxy.h - accounts/ResolverAccount.h - accounts/LastFmAccount.h - accounts/LastFmConfig.h - accounts/SpotifyAccount.h - EchonestCatalogSynchronizer.h - - sip/SipPlugin.h - sip/SipHandler.h - sip/sipinfo.h - - audio/audioengine.h - - database/database.h - database/fuzzyindex.h - database/databaseworker.h - database/databaseimpl.h - database/databaseresolver.h - database/databasecommand.h - database/databasecommandloggable.h - database/databasecommand_resolve.h - database/databasecommand_allartists.h - database/databasecommand_allalbums.h - database/databasecommand_alltracks.h - database/databasecommand_addfiles.h - database/databasecommand_deletefiles.h - database/databasecommand_dirmtimes.h - database/databasecommand_filemtimes.h - database/databasecommand_loadfiles.h - database/databasecommand_logplayback.h - database/databasecommand_addsource.h - database/databasecommand_sourceoffline.h - database/databasecommand_collectionstats.h - database/databasecommand_loadplaylistentries.h - database/databasecommand_modifyplaylist.h - database/databasecommand_playbackhistory.h - database/databasecommand_setplaylistrevision.h - database/databasecommand_loadallplaylists.h - database/databasecommand_loadallsortedplaylists.h - database/databasecommand_loadallsources.h - database/databasecommand_createplaylist.h - database/databasecommand_deleteplaylist.h - database/databasecommand_renameplaylist.h - database/databasecommand_loadops.h - database/databasecommand_updatesearchindex.h - database/databasecollection.h - database/localcollection.h - database/databasecommand_setdynamicplaylistrevision.h - database/databasecommand_createdynamicplaylist.h - database/databasecommand_loaddynamicplaylist.h - database/databasecommand_loaddynamicplaylistentries.h - database/databasecommand_deletedynamicplaylist.h - database/databasecommand_loadallautoplaylists.h - database/databasecommand_loadallstations.h - database/databasecommand_addclientauth.h - database/databasecommand_clientauthvalid.h - database/databasecommand_socialaction.h - database/databasecommand_loadsocialactions.h - database/databasecommand_genericselect.h - database/databasecommand_setcollectionattributes.h - database/databasecommand_collectionattributes.h - database/databasecommand_trackattributes.h - database/databasecommand_settrackattributes.h - - infosystem/infosystem.h - infosystem/infosystem.h - infosystem/infosystemworker.h - infosystem/infosystemcache.h - - infosystem/infoplugins/generic/echonestplugin.h - infosystem/infoplugins/generic/lastfmplugin.h - infosystem/infoplugins/generic/chartsplugin.h - infosystem/infoplugins/generic/spotifyPlugin.h - infosystem/infoplugins/generic/hypemPlugin.h - infosystem/infoplugins/generic/musixmatchplugin.h - infosystem/infoplugins/generic/musicbrainzPlugin.h - infosystem/infoplugins/generic/RoviPlugin.h - - network/bufferiodevice.h - network/msgprocessor.h - network/remotecollection.h - network/streamconnection.h - network/dbsyncconnection.h - network/servent.h - network/connection.h - network/controlconnection.h - network/portfwdthread.h - - playlist/PlaylistUpdaterInterface.h - playlist/dynamic/DynamicPlaylist.h - playlist/dynamic/GeneratorInterface.h - playlist/dynamic/GeneratorFactory.h - playlist/XspfUpdater.h - playlist/dynamic/database/DatabaseGenerator.h - playlist/dynamic/database/DatabaseControl.h - playlist/dynamic/DynamicControl.h - - thirdparty/kdsingleapplicationguard/kdsingleapplicationguard.h - - utils/tomahawkutilsgui.h - utils/xspfloader.h - utils/qnr_iodevicestream.h -) - -set( libHeaders_NoMOC - viewpage.h - - accounts/Account.h - - infosystem/infoplugins/unix/imageconverter.h - - taghandlers/tag.h - taghandlers/apetag.h - taghandlers/asftag.h - taghandlers/id3v1tag.h - taghandlers/id3v2tag.h - taghandlers/mp4tag.h - taghandlers/oggtag.h - - utils/tomahawkutils.h -) - set( libUI ${libUI} widgets/playlisttypeselectordlg.ui widgets/newplaylistwidget.ui @@ -665,9 +389,7 @@ IF( APPLE ) utils/tomahawkutils_mac.mm thirdparty/Qocoa/qsearchfield_mac.mm ) - SET( libHeaders ${libHeaders} - infosystem/infoplugins/mac/adium.h - infosystem/infoplugins/mac/adiumplugin.h ) + SET_SOURCE_FILES_PROPERTIES(utils/tomahawkutils_mac.mm PROPERTIES COMPILE_FLAGS "-fvisibility=default") SET( OS_SPECIFIC_LINK_LIBRARIES ${OS_SPECIFIC_LINK_LIBRARIES} @@ -689,15 +411,14 @@ ENDIF(LIBLASTFM_FOUND) IF(BUILD_GUI) LIST(APPEND libSources ${libGuiSources} ) - LIST(APPEND libHeaders ${libGuiHeaders} ) ENDIF() qt4_wrap_ui( libUI_H ${libUI} ) -qt4_wrap_cpp( libMoc ${libHeaders} ) -SET( libSources ${libSources} ${libUI_H} ${libHeaders_NoMOC} ) +SET( libSources ${libSources} ${libUI_H} ) -ADD_LIBRARY( tomahawklib SHARED ${libSources} ${libMoc} ) +ADD_LIBRARY( tomahawklib SHARED ${libSources}) +set_target_properties(tomahawklib PROPERTIES AUTOMOC TRUE) IF(QCA2_FOUND) SET(LINK_LIBRARIES ${LINK_LIBRARIES} ${QCA2_LIBRARIES} ) diff --git a/src/libtomahawk/accounts/Account.cpp b/src/libtomahawk/accounts/Account.cpp index 6fdaf8315..208cc7bac 100644 --- a/src/libtomahawk/accounts/Account.cpp +++ b/src/libtomahawk/accounts/Account.cpp @@ -55,6 +55,13 @@ Account::Account( const QString& accountId ) loadFromConfig( accountId ); } + +Account::~Account() +{ + sync(); +} + + QWidget* Account::configurationWidget() { @@ -192,7 +199,7 @@ Account::types() const types |= SipType; if ( m_types.contains( "ResolverType" ) ) types |= ResolverType; - if ( m_types.contains( "StatusPushTypeType" ) ) + if ( m_types.contains( "StatusPushType" ) ) types |= StatusPushType; return types; diff --git a/src/libtomahawk/accounts/Account.h b/src/libtomahawk/accounts/Account.h index d02bdceab..73a7dedee 100644 --- a/src/libtomahawk/accounts/Account.h +++ b/src/libtomahawk/accounts/Account.h @@ -73,7 +73,7 @@ public: enum ConnectionState { Disconnected, Connecting, Connected, Disconnecting }; explicit Account( const QString &accountId ); - virtual ~Account() {} + virtual ~Account(); QString accountServiceName() const { QMutexLocker locker( &m_mutex ); return m_accountServiceName; } // e.g. "Twitter", "Last.fm" QString accountFriendlyName() const { QMutexLocker locker( &m_mutex ); return m_accountFriendlyName; } // e.g. screen name on the service, JID, etc. diff --git a/src/libtomahawk/accounts/AccountManager.cpp b/src/libtomahawk/accounts/AccountManager.cpp index 6c5715113..ae0b96f99 100644 --- a/src/libtomahawk/accounts/AccountManager.cpp +++ b/src/libtomahawk/accounts/AccountManager.cpp @@ -182,7 +182,7 @@ AccountManager::loadPluginFactory( const QString& path ) void AccountManager::enableAccount( Account* account ) { - if ( account->isAuthenticated() ) + if ( account->enabled() ) return; account->authenticate(); @@ -197,7 +197,7 @@ AccountManager::enableAccount( Account* account ) void AccountManager::disableAccount( Account* account ) { - if ( !account->isAuthenticated() ) + if ( !account->enabled() ) return; account->deauthenticate(); @@ -215,8 +215,6 @@ AccountManager::connectAll() { acc->authenticate(); m_enabledAccounts << acc; -// if ( acc->types() & Accounts::SipType && acc->sipPlugin() ) -// acc->sipPlugin()->connectPlugin(); } m_connected = true; @@ -409,4 +407,4 @@ AccountManager::onStateChanged( Account::ConnectionState state ) }; -}; \ No newline at end of file +}; diff --git a/src/libtomahawk/accounts/AccountModel.cpp b/src/libtomahawk/accounts/AccountModel.cpp index 17c1a14b6..9c8b78f80 100644 --- a/src/libtomahawk/accounts/AccountModel.cpp +++ b/src/libtomahawk/accounts/AccountModel.cpp @@ -29,6 +29,7 @@ using namespace Tomahawk; using namespace Accounts; +#define ACCOUNTMODEL_DEBUG 0 AccountModel::AccountModel( QObject* parent ) : QAbstractListModel( parent ) { @@ -52,6 +53,11 @@ AccountModel::loadData() // Add all factories QList< AccountFactory* > factories = AccountManager::instance()->factories(); QList< Account* > allAccounts = AccountManager::instance()->accounts(); +#if ACCOUNTMODEL_DEBUG + qDebug() << "All accounts:"; + foreach ( Account* acct, allAccounts ) + qDebug() << acct->accountFriendlyName() << "\t" << acct->accountId(); +#endif foreach ( AccountFactory* fac, factories ) { if ( !fac->allowUserCreation() ) @@ -76,16 +82,28 @@ AccountModel::loadData() if ( AtticaManager::instance()->hasCustomAccountForAttica( content.id() ) ) { Account* acct = AtticaManager::instance()->customAccountForAttica( content.id() ); - m_accounts << new AccountModelNode( acct ); - allAccounts.removeAll( acct ); + Q_ASSERT( acct ); + if ( acct ) + { + m_accounts << new AccountModelNode( acct ); + const int removed = allAccounts.removeAll( acct ); +#if ACCOUNTMODEL_DEBUG + qDebug() << "Removed custom account from misc accounts list, found:" << removed; + qDebug() << "All accounts after remove:"; + foreach ( Account* acct, allAccounts ) + qDebug() << acct->accountFriendlyName() << "\t" << acct->accountId(); // All other accounts we haven't dealt with yet +#endif + } } else { m_accounts << new AccountModelNode( content ); foreach ( Account* acct, AccountManager::instance()->accounts( Accounts::ResolverType ) ) { +// qDebug() << "Found ResolverAccount" << acct->accountFriendlyName(); if ( AtticaResolverAccount* resolver = qobject_cast< AtticaResolverAccount* >( acct ) ) { +// qDebug() << "Which is an attica resolver with id:" << resolver->atticaId(); if ( resolver->atticaId() == content.id() ) { allAccounts.removeAll( acct ); @@ -95,9 +113,14 @@ AccountModel::loadData() } } - // All other accounts we haven't dealt with yet +#if ACCOUNTMODEL_DEBUG + qDebug() << "All accounts left:"; + foreach ( Account* acct, allAccounts ) + qDebug() << acct->accountFriendlyName() << "\t" << acct->accountId(); // All other accounts we haven't dealt with yet +#endif foreach ( Account* acct, allAccounts ) { + qDebug() << "Resolver is left over:" << acct->accountFriendlyName(); Q_ASSERT( !qobject_cast< AtticaResolverAccount* >( acct ) ); // This should be caught above in the attica list if ( qobject_cast< ResolverAccount* >( acct ) && !qobject_cast< AtticaResolverAccount* >( acct ) ) @@ -314,6 +337,12 @@ AccountModel::data( const QModelIndex& index, int role ) const Q_ASSERT( node->factory ); Account* account = node->customAccount; + // This is sort of ugly. CustomAccounts are pure Account*, but we know that + // some might also be linked to attica resolvers (not always). If that is the case + // they have a Attica::Content set on the node, so we use that to display some + // extra metadata and rating + const bool hasAttica = !node->atticaContent.id().isEmpty(); + switch ( role ) { case Qt::DisplayRole: @@ -324,9 +353,15 @@ AccountModel::data( const QModelIndex& index, int role ) const return ShippedWithTomahawk; case Qt::ToolTipRole: case DescriptionRole: - return node->factory->description(); + return hasAttica ? node->atticaContent.description() : node->factory->description(); case CanRateRole: - return false; + return hasAttica; + case AuthorRole: + return hasAttica ? node->atticaContent.author() : QString(); + case RatingRole: + return hasAttica ? node->atticaContent.rating() / 20 : 0; // rating is out of 100 + case DownloadCounterRole: + return hasAttica ? node->atticaContent.downloads() : QVariant(); case RowTypeRole: return CustomAccount; case AccountData: @@ -364,15 +399,24 @@ AccountModel::setData( const QModelIndex& index, const QVariant& value, int role switch ( node->type ) { case AccountModelNode::UniqueFactoryType: + { + const Qt::CheckState state = static_cast< Qt::CheckState >( value.toInt() ); if ( node->accounts.isEmpty() ) { + Q_ASSERT( state == Qt::Checked ); // How could we have a checked unique factory w/ no account?? // No account for this unique factory, create it // Don't add it to node->accounts here, slot attached to accountmanager::accountcreated will do it for us acct = node->factory->createAccount(); AccountManager::instance()->addAccount( acct ); TomahawkSettings::instance()->addAccount( acct->accountId() ); } + else + { + Q_ASSERT( node->accounts.size() == 1 ); + acct = node->accounts.first(); + } break; + } case AccountModelNode::AtticaType: { // This may or may not be installed. if it's not installed yet, install it, then go ahead and enable it @@ -397,6 +441,12 @@ AccountModel::setData( const QModelIndex& index, const QVariant& value, int role } else { + if ( m_waitingForAtticaInstall.contains( resolver.id() ) ) + { + // in progress, ignore + return true; + } + qDebug() << "Kicked off fetch+install, now waiting"; m_waitingForAtticaInstall.insert( resolver.id() ); diff --git a/src/libtomahawk/accounts/AccountModelNode.h b/src/libtomahawk/accounts/AccountModelNode.h index 795e17b6f..fd6d2c933 100644 --- a/src/libtomahawk/accounts/AccountModelNode.h +++ b/src/libtomahawk/accounts/AccountModelNode.h @@ -22,6 +22,7 @@ #include "Account.h" #include "AccountManager.h" #include "ResolverAccount.h" +#include "AtticaManager.h" #include <attica/content.h> @@ -86,7 +87,7 @@ struct AccountModelNode { { if ( AccountManager::instance()->factoryForAccount( acct ) == fac ) { - qDebug() << "Found account for factory:" << acct->accountFriendlyName(); +// qDebug() << "Found account for factory:" << acct->accountFriendlyName(); accounts.append( acct ); } } @@ -97,7 +98,7 @@ struct AccountModelNode { init(); atticaContent = cnt; - qDebug() << "Creating attica model node for resolver:" << cnt.id(); +// qDebug() << "Creating attica model node for resolver:" << cnt.id(); foreach ( Account* acct, AccountManager::instance()->accounts( Accounts::ResolverType ) ) { @@ -105,7 +106,7 @@ struct AccountModelNode { { if ( resolver->atticaId() == atticaContent.id() ) { - qDebug() << "found atticaaccount :" << resolver->accountFriendlyName(); +// qDebug() << "found atticaaccount :" << resolver->accountFriendlyName(); atticaAccount = resolver; break; } @@ -124,6 +125,9 @@ struct AccountModelNode { init(); customAccount = account; factory = AccountManager::instance()->factoryForAccount( account ); + + if ( CustomAtticaAccount* customAtticaAccount = qobject_cast< CustomAtticaAccount* >( account ) ) + atticaContent = customAtticaAccount->atticaContent(); } void init() diff --git a/src/libtomahawk/accounts/LastFmAccount.cpp b/src/libtomahawk/accounts/LastFmAccount.cpp index fae2f3132..fb4c97ec4 100644 --- a/src/libtomahawk/accounts/LastFmAccount.cpp +++ b/src/libtomahawk/accounts/LastFmAccount.cpp @@ -52,7 +52,7 @@ LastFmAccountFactory::icon() const LastFmAccount::LastFmAccount( const QString& accountId ) - : Account( accountId ) + : CustomAtticaAccount( accountId ) { m_infoPlugin = QWeakPointer< LastFmPlugin >( new LastFmPlugin( this ) ); @@ -86,13 +86,19 @@ LastFmAccount::authenticate() const Attica::Content res = AtticaManager::instance()->resolverForId( "lastfm" ); const AtticaManager::ResolverState state = AtticaManager::instance()->resolverState( res ); - if ( state == AtticaManager::Installed ) + qDebug() << "Last.FM account authenticating..."; + if ( m_resolver.isNull() && state == AtticaManager::Installed ) { hookupResolver(); - } else + } + else if ( m_resolver.isNull() ) { AtticaManager::instance()->installResolver( res, false ); } + else + { + m_resolver.data()->start(); + } emit connectionStateChanged( connectionState() ); } @@ -101,7 +107,7 @@ LastFmAccount::authenticate() void LastFmAccount::deauthenticate() { - if ( m_resolver.data()->running() ) + if ( !m_resolver.isNull() && m_resolver.data()->running() ) m_resolver.data()->stop(); emit connectionStateChanged( connectionState() ); @@ -121,7 +127,7 @@ LastFmAccount::configurationWidget() Account::ConnectionState LastFmAccount::connectionState() const { - return m_authenticated ? Account::Connected : Account::Disconnected; + return (!m_resolver.isNull() && m_resolver.data()->running()) ? Account::Connected : Account::Disconnected; } @@ -141,7 +147,7 @@ LastFmAccount::infoPlugin() bool LastFmAccount::isAuthenticated() const { - return m_authenticated; + return !m_resolver.isNull() && m_resolver.data()->running(); } @@ -169,7 +175,7 @@ LastFmAccount::password() const void LastFmAccount::setPassword( const QString& password ) { - QVariantHash creds; + QVariantHash creds = credentials(); creds[ "password" ] = password; setCredentials( creds ); } @@ -184,7 +190,7 @@ LastFmAccount::sessionKey() const void LastFmAccount::setSessionKey( const QString& sessionkey ) { - QVariantHash creds; + QVariantHash creds = credentials(); creds[ "sessionkey" ] = sessionkey; setCredentials( creds ); } @@ -200,7 +206,7 @@ LastFmAccount::username() const void LastFmAccount::setUsername( const QString& username ) { - QVariantHash creds; + QVariantHash creds = credentials(); creds[ "username" ] = username; setCredentials( creds ); } @@ -253,3 +259,10 @@ LastFmAccount::hookupResolver() m_resolver = QWeakPointer< ExternalResolverGui >( qobject_cast< ExternalResolverGui* >( Pipeline::instance()->addScriptResolver( data.scriptPath, enabled() ) ) ); connect( m_resolver.data(), SIGNAL( changed() ), this, SLOT( resolverChanged() ) ); } + + +Attica::Content +LastFmAccount::atticaContent() const +{ + return AtticaManager::instance()->resolverForId( "lastfm" ); +} diff --git a/src/libtomahawk/accounts/LastFmAccount.h b/src/libtomahawk/accounts/LastFmAccount.h index 015afce8e..3e7aaf651 100644 --- a/src/libtomahawk/accounts/LastFmAccount.h +++ b/src/libtomahawk/accounts/LastFmAccount.h @@ -20,6 +20,9 @@ #define LASTFMACCOUNT_H #include "accounts/Account.h" +#include "AtticaManager.h" + +#include <attica/content.h> #include <QObject> @@ -58,7 +61,7 @@ private: * but the user can install the attica resolver on-demand. So we take care of both there. * */ -class LastFmAccount : public Account +class LastFmAccount : public CustomAtticaAccount { Q_OBJECT public: @@ -88,6 +91,8 @@ public: bool scrobble() const; void setScrobble( bool scrobble ); + Attica::Content atticaContent() const; + private slots: void resolverInstalled( const QString& resolverId ); @@ -95,7 +100,6 @@ private slots: private: void hookupResolver(); - bool m_authenticated; QWeakPointer<Tomahawk::ExternalResolverGui> m_resolver; QWeakPointer<Tomahawk::InfoSystem::LastFmPlugin> m_infoPlugin; QWeakPointer<LastFmConfig> m_configWidget; diff --git a/src/libtomahawk/accounts/ResolverAccount.cpp b/src/libtomahawk/accounts/ResolverAccount.cpp index 501add13f..95acbe284 100644 --- a/src/libtomahawk/accounts/ResolverAccount.cpp +++ b/src/libtomahawk/accounts/ResolverAccount.cpp @@ -48,6 +48,7 @@ ResolverAccountFactory::createAccount( const QString& accountId ) Account* ResolverAccountFactory::createFromPath( const QString& path, bool isAttica ) { + qDebug() << "Creating ResolverAccount from path:" << path << "is attica" << isAttica; if ( isAttica ) { QFileInfo info( path ); @@ -200,6 +201,8 @@ ResolverAccount::resolverChanged() AtticaResolverAccount::AtticaResolverAccount( const QString& accountId ) : ResolverAccount( accountId ) { + TomahawkSettings::instance()->setValue( QString( "accounts/%1/atticaresolver" ).arg( accountId ), true ); + m_atticaId = configuration().value( "atticaId" ).toString(); loadIcon(); } @@ -209,9 +212,11 @@ AtticaResolverAccount::AtticaResolverAccount( const QString& accountId, const QS , m_atticaId( atticaId ) { QVariantHash conf = configuration(); - conf[ "atticaid" ] = atticaId; + conf[ "atticaId" ] = atticaId; setConfiguration( conf ); + TomahawkSettings::instance()->setValue( QString( "accounts/%1/atticaresolver" ).arg( accountId ), true ); + loadIcon(); } diff --git a/src/libtomahawk/accounts/ResolverAccount.h b/src/libtomahawk/accounts/ResolverAccount.h index 04da6d283..e02a7bfe1 100644 --- a/src/libtomahawk/accounts/ResolverAccount.h +++ b/src/libtomahawk/accounts/ResolverAccount.h @@ -35,7 +35,7 @@ public: ResolverAccountFactory() {} virtual ~ResolverAccountFactory() {} - virtual Account* createAccount(const QString& accountId = QString()); + virtual Account* createAccount( const QString& accountId = QString() ); virtual QString factoryId() const { return "resolveraccount"; } virtual QString description() const { return QString(); } virtual QString prettyName() const { return QString(); } // Internal, not displayed diff --git a/src/libtomahawk/accounts/SpotifyAccount.cpp b/src/libtomahawk/accounts/SpotifyAccount.cpp index b806a3de5..e9b1dc8f1 100644 --- a/src/libtomahawk/accounts/SpotifyAccount.cpp +++ b/src/libtomahawk/accounts/SpotifyAccount.cpp @@ -22,26 +22,54 @@ #include "PlaylistUpdaterInterface.h" #include "sourcelist.h" +#include <QPixmap> + using namespace Tomahawk; using namespace Accounts; -SpotifyResolverAccount::SpotifyResolverAccount() +static QPixmap* s_icon = 0; + +Account* +SpotifyAccountFactory::createAccount( const QString& accountId ) { - qDebug() << Q_FUNC_INFO; -} -SpotifyResolverAccount::~SpotifyResolverAccount() -{ - qDebug() << Q_FUNC_INFO; + return new SpotifyAccount( accountId ); } +QPixmap +SpotifyAccountFactory::icon() const +{ + if ( !s_icon ) + s_icon = new QPixmap( RESPATH "images/spotify-logo.png" ); + + return *s_icon; +} + + +SpotifyAccount::SpotifyAccount( const QString& accountId ) + : ResolverAccount( accountId ) +{ + +} + + +QPixmap +SpotifyAccount::icon() const +{ + if ( !s_icon ) + s_icon = new QPixmap( RESPATH "images/spotify-logo.png" ); + + return *s_icon; +} + + void -SpotifyResolverAccount::addPlaylist(const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks) +SpotifyAccount::addPlaylist( const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks ) { Sync sync; sync.id_ = qid; int index = m_syncPlaylists.indexOf( sync ); - if( !m_syncPlaylists.contains( sync ) ) + if( !m_syncPlaylists.contains( sync ) ) { qDebug() << Q_FUNC_INFO << "Adding playlist to sync" << qid; playlist_ptr pl; @@ -56,11 +84,12 @@ SpotifyResolverAccount::addPlaylist(const QString &qid, const QString& title, QL sync.uuid = pl->guid(); m_syncPlaylists.append( sync ); } - else{ + else + { qDebug() << Q_FUNC_INFO << "Found playlist"; - if( index != -1 && !tracks.isEmpty()) + if ( index != -1 && !tracks.isEmpty()) { qDebug() << Q_FUNC_INFO << "Got pl" << m_syncPlaylists[ index ].playlist->guid(); @@ -71,11 +100,14 @@ SpotifyResolverAccount::addPlaylist(const QString &qid, const QString& title, QL qDebug() << Q_FUNC_INFO << "tracks" << currTracks; - QList< query_ptr > mergedTracks = TomahawkUtils::mergePlaylistChanges( currTracks, tracks ); - - QList<Tomahawk::plentry_ptr> el = m_syncPlaylists[ index ].playlist->entriesFromQueries( mergedTracks, true ); - m_syncPlaylists[ index ].playlist->createNewRevision( uuid(), m_syncPlaylists[ index ].playlist->currentrevision(), el ); + bool changed = false; + QList< query_ptr > mergedTracks = TomahawkUtils::mergePlaylistChanges( currTracks, tracks, changed ); + if ( changed ) + { + QList<Tomahawk::plentry_ptr> el = m_syncPlaylists[ index ].playlist->entriesFromQueries( mergedTracks, true ); + m_syncPlaylists[ index ].playlist->createNewRevision( uuid(), m_syncPlaylists[ index ].playlist->currentrevision(), el ); + } } } @@ -84,9 +116,9 @@ SpotifyResolverAccount::addPlaylist(const QString &qid, const QString& title, QL -bool operator==(SpotifyResolverAccount::Sync one, SpotifyResolverAccount::Sync two) +bool operator==( SpotifyAccount::Sync one, SpotifyAccount::Sync two ) { - if(one.id_ == two.id_) + if( one.id_ == two.id_ ) return true; return false; } diff --git a/src/libtomahawk/accounts/SpotifyAccount.h b/src/libtomahawk/accounts/SpotifyAccount.h index db27dffd8..687315a96 100644 --- a/src/libtomahawk/accounts/SpotifyAccount.h +++ b/src/libtomahawk/accounts/SpotifyAccount.h @@ -23,6 +23,7 @@ #include "utils/tomahawkutils.h" #include "sourcelist.h" #include "ResolverAccount.h" + class QTimer; namespace Tomahawk { @@ -31,24 +32,40 @@ class ExternalResolverGui; namespace Accounts { -class SpotifyResolverAccount : public QObject // : public AccountFactory + +class SpotifyAccountFactory : public AccountFactory { Q_OBJECT public: - SpotifyResolverAccount(); - virtual ~SpotifyResolverAccount(); + SpotifyAccountFactory() {} - /*virtual Account* createAccount(const QString& accountId = QString()); - virtual QString description() const { return tr( "Play and sync your playlists with Spotify" ); } + virtual Account* createAccount( const QString& accountId = QString() ); + virtual QString description() const { return tr( "Play music from and sync your playlists with Spotify" ); } virtual QString factoryId() const { return "spotifyaccount"; } virtual QString prettyName() const { return "Spotify"; } virtual AccountTypes types() const { return AccountTypes( ResolverType ); } virtual bool allowUserCreation() const { return false; } - virtual QPixmap icon() const { return m_icon; } + virtual QPixmap icon() const; virtual bool isUnique() const { return true; } - */ - void addPlaylist(const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks); + +}; + +class SpotifyAccount : public ResolverAccount +{ + Q_OBJECT +public: + SpotifyAccount( const QString& accountId ); + virtual ~SpotifyAccount() {} + + virtual QPixmap icon() const; + + virtual QWidget* aclWidget() { return 0; } + virtual InfoSystem::InfoPlugin* infoPlugin() { return 0; } + virtual SipPlugin* sipPlugin() { return 0; } + + void addPlaylist( const QString &qid, const QString& title, QList< Tomahawk::query_ptr > tracks ); + struct Sync { QString id_; QString uuid; diff --git a/src/libtomahawk/album.cpp b/src/libtomahawk/album.cpp index fd56e8409..fbf362427 100644 --- a/src/libtomahawk/album.cpp +++ b/src/libtomahawk/album.cpp @@ -22,7 +22,6 @@ #include "albumplaylistinterface.h" #include "database/database.h" #include "database/databaseimpl.h" -#include "database/databasecommand_alltracks.h" #include "query.h" #include "utils/logger.h" @@ -32,12 +31,16 @@ using namespace Tomahawk; Album::~Album() { + delete m_cover; } album_ptr Album::get( const Tomahawk::artist_ptr& artist, const QString& name, bool autoCreate ) { + if ( !Database::instance() || !Database::instance()->impl() ) + return album_ptr(); + int albid = Database::instance()->impl()->albumId( artist->id(), name, autoCreate ); if ( albid < 1 && autoCreate ) return album_ptr(); @@ -71,6 +74,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 +101,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 +124,31 @@ Album::cover() const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - return m_cover; + if ( !m_cover && !m_coverBuffer.isEmpty() ) + { + m_cover = new QPixmap(); + m_cover->loadFromData( m_coverBuffer ); + } + + if ( m_cover && !m_cover->isNull() && !size.isEmpty() ) + { + if ( m_coverCache.contains( size.width() ) ) + { + return m_coverCache.value( size.width() ); + } + + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + return scaledCover; + } + + if ( m_cover ) + return *m_cover; + else + return QPixmap(); } +#endif void @@ -137,7 +167,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 <QtCore/QObject> #include <QtCore/QSharedPointer> +#ifndef ENABLE_HEADLESS + #include <QtGui/QPixmap> +#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/albumplaylistinterface.h b/src/libtomahawk/albumplaylistinterface.h index 3cc11a387..880e3c29e 100644 --- a/src/libtomahawk/albumplaylistinterface.h +++ b/src/libtomahawk/albumplaylistinterface.h @@ -68,7 +68,6 @@ signals: void nextTrackReady(); private: - Q_DISABLE_COPY( AlbumPlaylistInterface ) AlbumPlaylistInterface(); QList< Tomahawk::query_ptr > m_queries; diff --git a/src/libtomahawk/artist.cpp b/src/libtomahawk/artist.cpp index 0c0ba332c..cdefc2505 100644 --- a/src/libtomahawk/artist.cpp +++ b/src/libtomahawk/artist.cpp @@ -31,12 +31,16 @@ using namespace Tomahawk; Artist::~Artist() { + delete m_cover; } artist_ptr Artist::get( const QString& name, bool autoCreate ) { + if ( !Database::instance() || !Database::instance()->impl() ) + return artist_ptr(); + int artid = Database::instance()->impl()->artistId( name, autoCreate ); if ( artid < 1 && autoCreate ) return artist_ptr(); @@ -69,6 +73,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 +94,14 @@ Artist::onTracksAdded( const QList<Tomahawk::query_ptr>& 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 +116,31 @@ Artist::cover() const Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } - return m_cover; + if ( !m_cover && !m_coverBuffer.isEmpty() ) + { + m_cover = new QPixmap(); + m_cover->loadFromData( m_coverBuffer ); + } + + if ( m_cover && !m_cover->isNull() && !size.isEmpty() ) + { + if ( m_coverCache.contains( size.width() ) ) + { + return m_coverCache.value( size.width() ); + } + + QPixmap scaledCover; + scaledCover = m_cover->scaled( size, Qt::KeepAspectRatio, Qt::SmoothTransformation ); + m_coverCache.insert( size.width(), scaledCover ); + return scaledCover; + } + + if ( m_cover ) + return *m_cover; + else + return QPixmap(); } +#endif void @@ -128,7 +159,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 <QtCore/QObject> #include <QtCore/QSharedPointer> +#ifndef ENABLE_HEADLESS + #include <QtGui/QPixmap> +#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/artistplaylistinterface.cpp b/src/libtomahawk/artistplaylistinterface.cpp index 4ab49bc79..01754c259 100644 --- a/src/libtomahawk/artistplaylistinterface.cpp +++ b/src/libtomahawk/artistplaylistinterface.cpp @@ -89,8 +89,8 @@ ArtistPlaylistInterface::tracks() cmd->setArtist( m_artist ); cmd->setSortOrder( DatabaseCommand_AllTracks::Album ); - connect( cmd, SIGNAL( tracks( QList<Tomahawk::query_ptr>, QVariant ) ), - SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) ); + connect( cmd, SIGNAL( tracks( QList<Tomahawk::query_ptr>, QVariant ) ), + m_artist.data(), SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) ); Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); } diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 226d99a3f..f19f662f0 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -18,6 +18,8 @@ #include "audioengine.h" +#include "config.h" + #include <QtCore/QUrl> #include <QtNetwork/QNetworkReply> @@ -328,13 +330,15 @@ AudioEngine::sendWaitingNotificationSlot() const void AudioEngine::sendNowPlayingNotification() { +#ifndef ENABLE_HEADLESS if ( m_currentTrack->album().isNull() || m_currentTrack->album()->infoLoaded() ) onNowPlayingInfoReady(); else { connect( m_currentTrack->album().data(), SIGNAL( updated() ), SLOT( onNowPlayingInfoReady() ), Qt::UniqueConnection ); - m_currentTrack->album()->cover(); + m_currentTrack->album()->cover( QSize( 0, 0 ) ); } +#endif } @@ -357,9 +361,11 @@ AudioEngine::onNowPlayingInfoReady() if ( !m_currentTrack->album().isNull() ) { +#ifndef ENABLE_HEADLESS QImage cover; - cover.loadFromData( m_currentTrack->album()->cover() ); + cover = m_currentTrack->album()->cover( QSize( 0, 0 ) ).toImage(); playInfo["image"] = QVariant( cover ); +#endif } Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( @@ -584,10 +590,10 @@ AudioEngine::onStateChanged( Phonon::State newState, Phonon::State oldState ) if ( newState == Phonon::ErrorState ) { + stop(); + tLog() << "Phonon Error:" << m_mediaObject->errorString() << m_mediaObject->errorType(); emit error( UnknownError ); - - stop(); return; } if ( newState == Phonon::PlayingState ) 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<Tomahawk::query_ptr>& 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/database/databasecommand_allalbums.cpp b/src/libtomahawk/database/databasecommand_allalbums.cpp index f0b731291..5de1f6be0 100644 --- a/src/libtomahawk/database/databasecommand_allalbums.cpp +++ b/src/libtomahawk/database/databasecommand_allalbums.cpp @@ -169,7 +169,6 @@ DatabaseCommand_AllAlbums::execForCollection( DatabaseImpl* dbi ) void DatabaseCommand_AllAlbums::exec( DatabaseImpl* dbi ) { - return; if ( !m_artist.isNull() ) { execForArtist( dbi ); diff --git a/src/libtomahawk/database/databasecommand_resolve.cpp b/src/libtomahawk/database/databasecommand_resolve.cpp index 1384f828e..38149e83d 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 ) @@ -76,11 +81,9 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) typedef QPair<int, float> scorepair_t; // STEP 1 - QList< QPair<int, float> > artists = lib->searchTable( "artist", m_query->artist() ); - QList< QPair<int, float> > tracks = lib->searchTable( "track", m_query->track() ); - QList< QPair<int, float> > albums = lib->searchTable( "album", m_query->album() ); + QList< QPair<int, float> > tracks = lib->search( m_query ); - if ( artists.length() == 0 || tracks.length() == 0 ) + if ( tracks.length() == 0 ) { qDebug() << "No candidates found in first pass, aborting resolve" << m_query->artist() << m_query->track(); emit results( m_query->id(), res ); @@ -90,13 +93,10 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) // STEP 2 TomahawkSqlQuery files_query = lib->newquery(); - QStringList artsl, trksl; - for ( int k = 0; k < artists.count(); k++ ) - artsl.append( QString::number( artists.at( k ).first ) ); + QStringList trksl; for ( int k = 0; k < tracks.count(); k++ ) trksl.append( QString::number( tracks.at( k ).first ) ); - QString artsToken = QString( "file_join.artist IN (%1)" ).arg( artsl.join( "," ) ); QString trksToken = QString( "file_join.track IN (%1)" ).arg( trksl.join( "," ) ); QString sql = QString( "SELECT " @@ -119,8 +119,7 @@ DatabaseCommand_Resolve::resolve( DatabaseImpl* lib ) "artist.id = file_join.artist AND " "track.id = file_join.track AND " "file.id = file_join.file AND " - "(%1 AND %2)" ) - .arg( artsToken ) + "(%1)" ) .arg( trksToken ); files_query.prepare( sql ); @@ -196,27 +195,9 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) typedef QPair<int, float> scorepair_t; // STEP 1 - QList< QPair<int, float> > artistPairs = lib->searchTable( "artist", m_query->fullTextQuery(), 20 ); - QList< QPair<int, float> > trackPairs = lib->searchTable( "track", m_query->fullTextQuery(), 20 ); - QList< QPair<int, float> > albumPairs = lib->searchTable( "album", m_query->fullTextQuery(), 20 ); + QList< QPair<int, float> > trackPairs = lib->search( m_query ); + QList< QPair<int, float> > albumPairs = lib->searchAlbum( m_query, 20 ); - foreach ( const scorepair_t& artistPair, artistPairs ) - { - TomahawkSqlQuery query = lib->newquery(); - - QString sql = QString( "SELECT name FROM artist WHERE id = %1" ).arg( artistPair.first ); - query.prepare( sql ); - query.exec(); - - QList<Tomahawk::artist_ptr> artistList; - while ( query.next() ) - { - Tomahawk::artist_ptr artist = Tomahawk::Artist::get( artistPair.first, query.value( 0 ).toString() ); - artistList << artist; - } - - emit artists( m_query->id(), artistList ); - } foreach ( const scorepair_t& albumPair, albumPairs ) { TomahawkSqlQuery query = lib->newquery(); @@ -235,10 +216,10 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) emit albums( m_query->id(), albumList ); } - - if ( artistPairs.length() == 0 && trackPairs.length() == 0 && albumPairs.length() == 0 ) + + if ( trackPairs.length() == 0 ) { - qDebug() << "No candidates found in first pass, aborting resolve" << m_query->artist() << m_query->track(); + qDebug() << "No candidates found in first pass, aborting resolve" << m_query->fullTextQuery(); emit results( m_query->id(), res ); return; } @@ -246,18 +227,11 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) // STEP 2 TomahawkSqlQuery files_query = lib->newquery(); - QStringList artsl, trksl, albsl; - for ( int k = 0; k < artistPairs.count(); k++ ) - artsl.append( QString::number( artistPairs.at( k ).first ) ); + QStringList trksl; for ( int k = 0; k < trackPairs.count(); k++ ) trksl.append( QString::number( trackPairs.at( k ).first ) ); - for ( int k = 0; k < albumPairs.count(); k++ ) - albsl.append( QString::number( albumPairs.at( k ).first ) ); - QString artsToken = QString( "file_join.artist IN (%1)" ).arg( artsl.join( "," ) ); QString trksToken = QString( "file_join.track IN (%1)" ).arg( trksl.join( "," ) ); - QString albsToken = QString( "file_join.album IN (%1)" ).arg( albsl.join( "," ) ); - QString sql = QString( "SELECT " "url, mtime, size, md5, mimetype, duration, bitrate, " //0 "file_join.artist, file_join.album, file_join.track, " //7 @@ -279,7 +253,7 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib ) "track.id = file_join.track AND " "file.id = file_join.file AND " "%1" ) - .arg( trackPairs.length() > 0 ? trksToken : QString( "0" ) ); + .arg( trksl.length() > 0 ? trksToken : QString( "0" ) ); files_query.prepare( sql ); files_query.exec(); diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.cpp b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp index 6fe5e18a4..5c3899ccd 100644 --- a/src/libtomahawk/database/databasecommand_updatesearchindex.cpp +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.cpp @@ -21,32 +21,26 @@ #include "databaseimpl.h" #include "tomahawksqlquery.h" #include "utils/logger.h" +#include "jobview/IndexingJobItem.h" +#include "jobview/JobStatusView.h" +#include "jobview/JobStatusModel.h" + +#include <QSqlRecord> DatabaseCommand_UpdateSearchIndex::DatabaseCommand_UpdateSearchIndex() : DatabaseCommand() + , m_statusJob( new IndexingJobItem ) { tLog() << Q_FUNC_INFO << "Updating index."; + + JobStatusView::instance()->model()->addJob( m_statusJob ); } -void -DatabaseCommand_UpdateSearchIndex::indexTable( DatabaseImpl* db, const QString& table ) +DatabaseCommand_UpdateSearchIndex::~DatabaseCommand_UpdateSearchIndex() { - qDebug() << Q_FUNC_INFO; - - TomahawkSqlQuery query = db->newquery(); - qDebug() << "Building index for" << table; - query.exec( QString( "SELECT id, name FROM %1" ).arg( table ) ); - - QMap< unsigned int, QString > fields; - while ( query.next() ) - { - fields.insert( query.value( 0 ).toUInt(), query.value( 1 ).toString() ); - } - - db->m_fuzzyIndex->appendFields( table, fields ); - qDebug() << "Building index for" << table << "finished."; + m_statusJob->done(); } @@ -55,9 +49,35 @@ DatabaseCommand_UpdateSearchIndex::exec( DatabaseImpl* db ) { db->m_fuzzyIndex->beginIndexing(); - indexTable( db, "artist" ); - indexTable( db, "album" ); - indexTable( db, "track" ); + QMap< unsigned int, QMap< QString, QString > > data; + TomahawkSqlQuery q = db->newquery(); + + q.exec( "SELECT track.id, track.name, artist.name, artist.id FROM track, artist WHERE artist.id = track.artist" ); + while ( q.next() ) + { + QMap< QString, QString > track; + track.insert( "track", q.value( 1 ).toString() ); + track.insert( "artist", q.value( 2 ).toString() ); + track.insert( "artistid", q.value( 3 ).toString() ); + + data.insert( q.value( 0 ).toUInt(), track ); + } + + db->m_fuzzyIndex->appendFields( data ); + data.clear(); + + q.exec( "SELECT album.id, album.name FROM album" ); + while ( q.next() ) + { + QMap< QString, QString > album; + album.insert( "album", q.value( 1 ).toString() ); + + data.insert( q.value( 0 ).toUInt(), album ); + } + + db->m_fuzzyIndex->appendFields( data ); + + qDebug() << "Building index finished."; db->m_fuzzyIndex->endIndexing(); } diff --git a/src/libtomahawk/database/databasecommand_updatesearchindex.h b/src/libtomahawk/database/databasecommand_updatesearchindex.h index 91c2203d1..09aeba89c 100644 --- a/src/libtomahawk/database/databasecommand_updatesearchindex.h +++ b/src/libtomahawk/database/databasecommand_updatesearchindex.h @@ -22,23 +22,21 @@ #include "databasecommand.h" #include "dllmacro.h" +class IndexingJobItem; + class DLLEXPORT DatabaseCommand_UpdateSearchIndex : public DatabaseCommand { Q_OBJECT public: explicit DatabaseCommand_UpdateSearchIndex(); + virtual ~DatabaseCommand_UpdateSearchIndex(); virtual QString commandname() const { return "updatesearchindex"; } virtual bool doesMutates() const { return true; } virtual void exec( DatabaseImpl* db ); -signals: - void indexUpdated(); - private: - void indexTable( DatabaseImpl* db, const QString& table ); - - QString table; + IndexingJobItem* m_statusJob; }; #endif // DATABASECOMMAND_UPDATESEARCHINDEX_H diff --git a/src/libtomahawk/database/databaseimpl.cpp b/src/libtomahawk/database/databaseimpl.cpp index 847685457..54e3e993f 100644 --- a/src/libtomahawk/database/databaseimpl.cpp +++ b/src/libtomahawk/database/databaseimpl.cpp @@ -79,6 +79,9 @@ DatabaseImpl::DatabaseImpl( const QString& dbname, Database* parent ) query.exec( "UPDATE source SET isonline = 'false'" ); m_fuzzyIndex = new FuzzyIndex( *this, schemaUpdated ); + if ( schemaUpdated ) + QTimer::singleShot( 0, this, SLOT( updateIndex() ) ); + tDebug( LOGVERBOSE ) << "Loaded index:" << t.elapsed(); if ( qApp->arguments().contains( "--dumpdb" ) ) @@ -405,13 +408,36 @@ DatabaseImpl::albumId( int artistid, const QString& name_orig, bool autoCreate ) QList< QPair<int, float> > -DatabaseImpl::searchTable( const QString& table, const QString& name, uint limit ) +DatabaseImpl::search( const Tomahawk::query_ptr& query, uint limit ) { QList< QPair<int, float> > resultslist; - if ( table != "artist" && table != "track" && table != "album" ) + + QMap< int, float > resultsmap = m_fuzzyIndex->search( query ); + foreach ( int i, resultsmap.keys() ) + { + resultslist << QPair<int, float>( i, (float)resultsmap.value( i ) ); + } + qSort( resultslist.begin(), resultslist.end(), DatabaseImpl::scorepairSorter ); + + if ( !limit ) return resultslist; - QMap< int, float > resultsmap = m_fuzzyIndex->search( table, name ); + QList< QPair<int, float> > resultscapped; + for ( int i = 0; i < (int)limit && i < resultsmap.count(); i++ ) + { + resultscapped << resultslist.at( i ); + } + + return resultscapped; +} + + +QList< QPair<int, float> > +DatabaseImpl::searchAlbum( const Tomahawk::query_ptr& query, uint limit ) +{ + QList< QPair<int, float> > resultslist; + + QMap< int, float > resultsmap = m_fuzzyIndex->searchAlbum( query ); foreach ( int i, resultsmap.keys() ) { resultslist << QPair<int, float>( i, (float)resultsmap.value( i ) ); @@ -696,3 +722,11 @@ DatabaseImpl::openDatabase( const QString& dbname ) return schemaUpdated; } + + +void +DatabaseImpl::updateIndex() +{ + DatabaseCommand* cmd = new DatabaseCommand_UpdateSearchIndex(); + Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) ); +} diff --git a/src/libtomahawk/database/databaseimpl.h b/src/libtomahawk/database/databaseimpl.h index 805dbab17..052ae68aa 100644 --- a/src/libtomahawk/database/databaseimpl.h +++ b/src/libtomahawk/database/databaseimpl.h @@ -56,7 +56,8 @@ public: int trackId( int artistid, const QString& name_orig, bool autoCreate ); int albumId( int artistid, const QString& name_orig, bool autoCreate ); - QList< QPair<int, float> > searchTable( const QString& table, const QString& name, uint limit = 0 ); + QList< QPair<int, float> > search( const Tomahawk::query_ptr& query, uint limit = 0 ); + QList< QPair<int, float> > searchAlbum( const Tomahawk::query_ptr& query, uint limit = 0 ); QList< int > getTrackFids( int tid ); static QString sortname( const QString& str, bool replaceArticle = false ); @@ -79,7 +80,8 @@ public: signals: void indexReady(); -public slots: +private slots: + void updateIndex(); private: QString cleanSql( const QString& sql ); diff --git a/src/libtomahawk/database/fuzzyindex.cpp b/src/libtomahawk/database/fuzzyindex.cpp index b5d4b15a9..f9cf8802c 100644 --- a/src/libtomahawk/database/fuzzyindex.cpp +++ b/src/libtomahawk/database/fuzzyindex.cpp @@ -22,12 +22,14 @@ #include <QTime> #include <CLucene.h> +#include <CLucene/queryParser/MultiFieldQueryParser.h> #include "databaseimpl.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" using namespace lucene::analysis; +using namespace lucene::analysis::standard; using namespace lucene::document; using namespace lucene::store; using namespace lucene::index; @@ -83,7 +85,7 @@ FuzzyIndex::beginIndexing() } qDebug() << "Creating new index writer."; - IndexWriter luceneWriter = IndexWriter( m_luceneDir, m_analyzer, true ); + IndexWriter luceneWriter( m_luceneDir, m_analyzer, true ); } catch( CLuceneError& error ) { @@ -102,38 +104,55 @@ FuzzyIndex::endIndexing() void -FuzzyIndex::appendFields( const QString& table, const QMap< unsigned int, QString >& fields ) +FuzzyIndex::appendFields( const QMap< unsigned int, QMap< QString, QString > >& trackData ) { try { - qDebug() << "Appending to index:" << fields.count(); + tDebug() << "Appending to index:" << trackData.count(); bool create = !IndexReader::indexExists( TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ).toStdString().c_str() ); - IndexWriter luceneWriter = IndexWriter( m_luceneDir, m_analyzer, create ); + IndexWriter luceneWriter( m_luceneDir, m_analyzer, create ); Document doc; - QMapIterator< unsigned int, QString > it( fields ); + QMapIterator< unsigned int, QMap< QString, QString > > it( trackData ); while ( it.hasNext() ) { it.next(); unsigned int id = it.key(); - QString name = it.value(); + QMap< QString, QString > values = it.value(); + if ( values.contains( "track" ) ) { - Field* field = _CLNEW Field( table.toStdWString().c_str(), DatabaseImpl::sortname( name ).toStdWString().c_str(), - Field::STORE_YES | Field::INDEX_UNTOKENIZED ); - doc.add( *field ); - } + doc.add( *( _CLNEW Field( _T( "fulltext" ), DatabaseImpl::sortname( QString( "%1 %2" ).arg( values.value( "artist" ) ).arg( values.value( "track" ) ) ).toStdWString().c_str(), + Field::STORE_NO | Field::INDEX_UNTOKENIZED ) ) ); - { - Field* field = _CLNEW Field( _T( "id" ), QString::number( id ).toStdWString().c_str(), - Field::STORE_YES | Field::INDEX_NO ); - doc.add( *field ); + doc.add( *( _CLNEW Field( _T( "track" ), DatabaseImpl::sortname( values.value( "track" ) ).toStdWString().c_str(), + Field::STORE_NO | Field::INDEX_UNTOKENIZED ) ) ); + + doc.add( *( _CLNEW Field( _T( "artist" ), DatabaseImpl::sortname( values.value( "artist" ) ).toStdWString().c_str(), + Field::STORE_NO | Field::INDEX_UNTOKENIZED ) ) ); + + doc.add( *( _CLNEW Field( _T( "artistid" ), values.value( "artistid" ).toStdWString().c_str(), + Field::STORE_YES | Field::INDEX_NO ) ) ); + + doc.add( *( _CLNEW Field( _T( "trackid" ), QString::number( id ).toStdWString().c_str(), + Field::STORE_YES | Field::INDEX_NO ) ) ); } + else if ( values.contains( "album" ) ) + { + doc.add( *( _CLNEW Field( _T( "album" ), DatabaseImpl::sortname( values.value( "album" ) ).toStdWString().c_str(), + Field::STORE_NO | Field::INDEX_UNTOKENIZED ) ) ); + + doc.add( *( _CLNEW Field( _T( "albumid" ), QString::number( id ).toStdWString().c_str(), + Field::STORE_YES | Field::INDEX_NO ) ) ); + } + else + Q_ASSERT( false ); luceneWriter.addDocument( &doc ); doc.clear(); } + luceneWriter.optimize(); luceneWriter.close(); } catch( CLuceneError& error ) @@ -152,7 +171,7 @@ FuzzyIndex::loadLuceneIndex() QMap< int, float > -FuzzyIndex::search( const QString& table, const QString& name ) +FuzzyIndex::search( const Tomahawk::query_ptr& query ) { QMutexLocker lock( &m_mutex ); @@ -171,33 +190,112 @@ FuzzyIndex::search( const QString& table, const QString& name ) m_luceneSearcher = _CLNEW IndexSearcher( m_luceneReader ); } - if ( name.isEmpty() ) - return resultsmap; + float minScore; + const TCHAR** fields = 0; + MultiFieldQueryParser parser( fields, m_analyzer ); + BooleanQuery* qry = _CLNEW BooleanQuery(); - SimpleAnalyzer analyzer; - QueryParser parser( table.toStdWString().c_str(), m_analyzer ); - Hits* hits = 0; + if ( query->isFullTextQuery() ) + { + QString escapedQuery = QString::fromWCharArray( parser.escape( DatabaseImpl::sortname( query->fullTextQuery() ).toStdWString().c_str() ) ); + + Term* term = _CLNEW Term( _T( "track" ), escapedQuery.toStdWString().c_str() ); + Query* fqry = _CLNEW FuzzyQuery( term ); + qry->add( fqry, true, BooleanClause::SHOULD ); - FuzzyQuery* qry = _CLNEW FuzzyQuery( _CLNEW Term( table.toStdWString().c_str(), DatabaseImpl::sortname( name ).toStdWString().c_str() ) ); - hits = m_luceneSearcher->search( qry ); + term = _CLNEW Term( _T( "artist" ), escapedQuery.toStdWString().c_str() ); + fqry = _CLNEW FuzzyQuery( term ); + qry->add( fqry, true, BooleanClause::SHOULD ); + term = _CLNEW Term( _T( "fulltext" ), escapedQuery.toStdWString().c_str() ); + fqry = _CLNEW FuzzyQuery( term ); + qry->add( fqry, true, BooleanClause::SHOULD ); + + minScore = 0.00; + } + else + { + QString track = QString::fromWCharArray( parser.escape( DatabaseImpl::sortname( query->track() ).toStdWString().c_str() ) ); + QString artist = QString::fromWCharArray( parser.escape( DatabaseImpl::sortname( query->artist() ).toStdWString().c_str() ) ); +// QString album = QString::fromWCharArray( parser.escape( query->album().toStdWString().c_str() ) ); + + Term* term = _CLNEW Term( _T( "track" ), track.toStdWString().c_str() ); + Query* fqry = _CLNEW FuzzyQuery( term ); + qry->add( fqry, true, BooleanClause::MUST ); + + term = _CLNEW Term( _T( "artist" ), artist.toStdWString().c_str() ); + fqry = _CLNEW FuzzyQuery( term ); + qry->add( fqry, true, BooleanClause::MUST ); + + minScore = 0.05; + } + + Hits* hits = m_luceneSearcher->search( qry ); for ( uint i = 0; i < hits->length(); i++ ) { Document* d = &hits->doc( i ); float score = hits->score( i ); - int id = QString::fromWCharArray( d->get( _T( "id" ) ) ).toInt(); - QString result = QString::fromWCharArray( d->get( table.toStdWString().c_str() ) ); + int id = QString::fromWCharArray( d->get( _T( "trackid" ) ) ).toInt(); - if ( DatabaseImpl::sortname( result ) == DatabaseImpl::sortname( name ) ) - score = 1.0; - else - score = qMin( score, (float)0.99 ); - - if ( score > 0.05 ) + if ( score > minScore ) { resultsmap.insert( id, score ); -// qDebug() << "Hitres:" << result << id << score << table << name; +// tDebug() << "Index hit:" << id << score << QString::fromWCharArray( ((Query*)qry)->toString() ); + } + } + + delete hits; + delete qry; + } + catch( CLuceneError& error ) + { + tDebug() << "Caught CLucene error:" << error.what(); + Q_ASSERT( false ); + } + + return resultsmap; +} + + +QMap< int, float > +FuzzyIndex::searchAlbum( const Tomahawk::query_ptr& query ) +{ + Q_ASSERT( query->isFullTextQuery() ); + + QMutexLocker lock( &m_mutex ); + + QMap< int, float > resultsmap; + try + { + if ( !m_luceneReader ) + { + if ( !IndexReader::indexExists( TomahawkUtils::appDataDir().absoluteFilePath( "tomahawk.lucene" ).toStdString().c_str() ) ) + { + qDebug() << Q_FUNC_INFO << "index didn't exist."; + return resultsmap; + } + + m_luceneReader = IndexReader::open( m_luceneDir ); + m_luceneSearcher = _CLNEW IndexSearcher( m_luceneReader ); + } + + QueryParser parser( _T( "album" ), m_analyzer ); + QString escapedName = QString::fromWCharArray( parser.escape( DatabaseImpl::sortname( query->fullTextQuery() ).toStdWString().c_str() ) ); + + Query* qry = _CLNEW FuzzyQuery( _CLNEW Term( _T( "album" ), escapedName.toStdWString().c_str() ) ); + Hits* hits = m_luceneSearcher->search( qry ); + for ( uint i = 0; i < hits->length(); i++ ) + { + Document* d = &hits->doc( i ); + + float score = hits->score( i ); + int id = QString::fromWCharArray( d->get( _T( "albumid" ) ) ).toInt(); + + if ( score > 0.30 ) + { + resultsmap.insert( id, score ); +// tDebug() << "Index hit:" << id << score; } } diff --git a/src/libtomahawk/database/fuzzyindex.h b/src/libtomahawk/database/fuzzyindex.h index 3e93fbb23..2938a1f55 100644 --- a/src/libtomahawk/database/fuzzyindex.h +++ b/src/libtomahawk/database/fuzzyindex.h @@ -25,6 +25,8 @@ #include <QString> #include <QMutex> +#include "query.h" + namespace lucene { namespace analysis @@ -58,7 +60,7 @@ public: void beginIndexing(); void endIndexing(); - void appendFields( const QString& table, const QMap< unsigned int, QString >& fields ); + void appendFields( const QMap< unsigned int, QMap< QString, QString > >& trackData ); signals: void indexReady(); @@ -66,7 +68,8 @@ signals: public slots: void loadLuceneIndex(); - QMap< int, float > search( const QString& table, const QString& name ); + QMap< int, float > search( const Tomahawk::query_ptr& query ); + QMap< int, float > searchAlbum( const Tomahawk::query_ptr& query ); private: DatabaseImpl& m_db; diff --git a/src/libtomahawk/dropjob.cpp b/src/libtomahawk/dropjob.cpp index 6c0ed2f9f..45c9ece2d 100644 --- a/src/libtomahawk/dropjob.cpp +++ b/src/libtomahawk/dropjob.cpp @@ -35,6 +35,9 @@ #include "utils/xspfloader.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" +#include "pipeline.h" + #ifdef QCA2_FOUND #include "utils/groovesharkparser.h" #endif //QCA2_FOUND @@ -43,7 +46,7 @@ using namespace Tomahawk; bool DropJob::s_canParseSpotifyPlaylists = false; - +static QString s_dropJobInfoId = "dropjob"; DropJob::DropJob( QObject *parent ) : QObject( parent ) @@ -124,7 +127,7 @@ DropJob::acceptsMimeData( const QMimeData* data, DropJob::DropTypes acceptedType // Not the most elegant if ( url.contains( "spotify" ) && url.contains( "playlist" ) && s_canParseSpotifyPlaylists ) return true; - + if ( url.contains( "grooveshark.com" ) && url.contains( "playlist" ) ) return true; } @@ -571,7 +574,7 @@ DropJob::handleGroovesharkUrls ( const QString& urlsRaw ) #ifdef QCA2_FOUND QStringList urls = urlsRaw.split( QRegExp( "\\s+" ), QString::SkipEmptyParts ); tDebug() << "Got Grooveshark urls!" << urls; - + if ( dropAction() == Default ) setDropAction( Create ); @@ -691,6 +694,67 @@ DropJob::onTracksAdded( const QList<Tomahawk::query_ptr>& tracksList ) } +void +DropJob::tracksFromDB( const QList< query_ptr >& tracks ) +{ + // Tracks that we get from databasecommand_alltracks are resolved only against the database and explicitly marked + // as finished. if the source they resolve to is offline they will not resolve against any resolver. + // explicitly resolve them if they fall in that case first + foreach( const query_ptr& track, tracks ) + { + if ( !track->playable() && !track->solved() && track->results().size() ) // we have offline results + { + track->setResolveFinished( false ); + Pipeline::instance()->resolve( track ); + } + } + + album_ptr albumPtr; + artist_ptr artistPtr; + if ( Tomahawk::Album* album = qobject_cast< Tomahawk::Album* >( sender() ) ) + { + foreach ( const album_ptr& ptr, m_albumsToKeep ) + if ( ptr.data() == album ) + { + albumPtr = ptr; + m_albumsToKeep.remove( ptr ); + } + } + else if ( Tomahawk::Artist* artist = qobject_cast< Tomahawk::Artist* >( sender() ) ) + { + foreach ( const artist_ptr& ptr, m_artistsToKeep ) + if ( ptr.data() == artist ) + { + artistPtr = ptr; + m_artistsToKeep.remove( ptr ); + } + } + + // If we have no tracks, this means no sources in our network have the give request (artist or album) + // Since we really do want to try to drop them, we ask the infosystem as well. + if ( tracks.isEmpty() ) + { + if ( !albumPtr.isNull() && !albumPtr->artist().isNull() ) + { + Q_ASSERT( artistPtr.isNull() ); + --m_queryCount; // This query is done. New query is infosystem query + getAlbumFromInfoystem( albumPtr->artist()->name(), albumPtr->name() ); + } + else if ( !artistPtr.isNull() ) + { + Q_ASSERT( albumPtr.isNull() ); + --m_queryCount; + getTopTen( artistPtr->name() ); + } + } + else + { + onTracksAdded( tracks ); + } + +} + + void DropJob::removeDuplicates() { @@ -699,10 +763,18 @@ DropJob::removeDuplicates() { bool contains = false; foreach( const Tomahawk::query_ptr &tmpItem, list ) + { if ( item->album() == tmpItem->album() && item->artist() == tmpItem->artist() && item->track() == tmpItem->track() ) + { + if ( item->playable() && !tmpItem->playable() ) + list.replace( list.indexOf( tmpItem ), item ); + contains = true; + break; + } + } if ( !contains ) list.append( item ); } @@ -733,28 +805,31 @@ DropJob::removeRemoteSources() void DropJob::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ) { - if ( requestData.caller == "changeme" ) + if ( requestData.caller == s_dropJobInfoId ) { - Tomahawk::InfoSystem::InfoStringHash artistInfo; + const Tomahawk::InfoSystem::InfoStringHash info = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >(); - artistInfo = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >(); + const QString artist = info["artist"]; + const QString album = info["album"]; - QString artist = artistInfo["artist"]; - - qDebug() << "Got requestData response for artist" << artist << output; + qDebug() << "Got requestData response for artist" << artist << "and album:" << album << output; QList< query_ptr > results; int i = 0; foreach ( const QVariant& title, output.toMap().value( "tracks" ).toList() ) { - qDebug() << "got title" << title; results << Query::get( artist, title.toString(), QString(), uuid() ); if ( ++i == 10 ) // Only getting top ten for now. Would make sense to make it configurable break; } + if ( results.isEmpty() ) + { + const QString which = album.isEmpty() ? "artist" : "album"; + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "No tracks found for given %1" ).arg( which ), 5 ) ); + } onTracksAdded( results ); } } @@ -766,8 +841,10 @@ DropJob::getArtist( const QString &artist ) artist_ptr artistPtr = Artist::get( artist ); if ( artistPtr->playlistInterface()->tracks().isEmpty() ) { + m_artistsToKeep.insert( artistPtr ); + connect( artistPtr.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr> ) ), - SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) ); + SLOT( tracksFromDB( QList<Tomahawk::query_ptr> ) ) ); m_queryCount++; return QList< query_ptr >(); } @@ -787,9 +864,14 @@ DropJob::getAlbum(const QString &artist, const QString &album) if ( albumPtr->playlistInterface()->tracks().isEmpty() ) { + // For albums that don't exist until this moment, we are the main shared pointer holding on. + // fetching the tracks is asynchronous, so the resulting signal is queued. when we go out of scope we delete + // the artist_ptr which means we never get the signal delivered. so we hold on to the album pointer till we're done + m_albumsToKeep.insert( albumPtr ); + m_dropJob = new DropJobNotifier( QPixmap( RESPATH "images/album-icon.png" ), Album ); connect( albumPtr.data(), SIGNAL( tracksAdded( QList<Tomahawk::query_ptr> ) ), - SLOT( onTracksAdded( QList<Tomahawk::query_ptr> ) ) ); + SLOT( tracksFromDB( QList<Tomahawk::query_ptr> ) ) ); JobStatusView::instance()->model()->addJob( m_dropJob ); m_queryCount++; @@ -811,7 +893,7 @@ DropJob::getTopTen( const QString &artist ) artistInfo["artist"] = artist; Tomahawk::InfoSystem::InfoRequestData requestData; - requestData.caller = "changeme"; + requestData.caller = s_dropJobInfoId; requestData.customData = QVariantMap(); requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); @@ -821,3 +903,28 @@ DropJob::getTopTen( const QString &artist ) m_queryCount++; } + + +void +DropJob::getAlbumFromInfoystem( const QString& artist, const QString& album ) +{ + connect( Tomahawk::InfoSystem::InfoSystem::instance(), + SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), + SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); + + Tomahawk::InfoSystem::InfoStringHash artistInfo; + artistInfo["artist"] = artist; + artistInfo["album"] = album; + + Tomahawk::InfoSystem::InfoRequestData requestData; + requestData.caller = s_dropJobInfoId; + requestData.customData = QVariantMap(); + + requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); + + requestData.type = Tomahawk::InfoSystem::InfoAlbumSongs; + Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); + + m_queryCount++; +} + diff --git a/src/libtomahawk/dropjob.h b/src/libtomahawk/dropjob.h index 71ca32787..02e474a6c 100644 --- a/src/libtomahawk/dropjob.h +++ b/src/libtomahawk/dropjob.h @@ -23,6 +23,8 @@ #include "query.h" #include "infosystem/infosystem.h" +#include "utils/xspfloader.h" + #include <QObject> #include <QStringList> #include <QMimeData> @@ -120,9 +122,9 @@ private slots: void onTracksAdded( const QList<Tomahawk::query_ptr>& ); void infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ); + void tracksFromDB( const QList< Tomahawk::query_ptr >& ); private: /// handle parsing mime data - void handleAllUrls( const QString& urls ); void handleTrackUrls( const QString& urls ); QList< Tomahawk::query_ptr > tracksFromQueryList( const QMimeData* d ); @@ -135,6 +137,7 @@ private: QList< Tomahawk::query_ptr > getAlbum( const QString& artist, const QString& album ); void getTopTen( const QString& artist ); + void getAlbumFromInfoystem( const QString& artist, const QString& album ); void removeDuplicates(); void removeRemoteSources(); @@ -151,6 +154,8 @@ private: Tomahawk::DropJobNotifier* m_dropJob; QList< Tomahawk::query_ptr > m_resultList; + QSet< Tomahawk::album_ptr > m_albumsToKeep; + QSet< Tomahawk::artist_ptr > m_artistsToKeep; static bool s_canParseSpotifyPlaylists; }; diff --git a/src/libtomahawk/functimeout.cpp b/src/libtomahawk/functimeout.cpp new file mode 100644 index 000000000..9f3d83f6e --- /dev/null +++ b/src/libtomahawk/functimeout.cpp @@ -0,0 +1,47 @@ +/* === 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 + * 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 "functimeout.h" + +#include <QTimer> + + +using namespace Tomahawk; + + +FuncTimeout::FuncTimeout( int ms, boost::function< void() > func, QObject* besafe ) + : m_func( func ) + , m_watch( QWeakPointer< QObject >( besafe ) ) +{ + //qDebug() << Q_FUNC_INFO; + QTimer::singleShot( ms, this, SLOT( exec() ) ); +} + + +FuncTimeout::~FuncTimeout() +{ +} + + +void FuncTimeout::exec() +{ + if( !m_watch.isNull() ) + m_func(); + this->deleteLater(); +} diff --git a/src/libtomahawk/functimeout.h b/src/libtomahawk/functimeout.h index 060a2fe9f..e2b74dbfe 100644 --- a/src/libtomahawk/functimeout.h +++ b/src/libtomahawk/functimeout.h @@ -20,7 +20,6 @@ #define FUNCTIMEOUT_H #include <QObject> -#include <QTimer> #include <QWeakPointer> #include "boost/function.hpp" @@ -43,26 +42,12 @@ class DLLEXPORT FuncTimeout : public QObject Q_OBJECT public: - FuncTimeout( int ms, boost::function<void()> func, QObject* besafe ) - : m_func( func ) - , m_watch( QWeakPointer< QObject >( besafe ) ) - { - //qDebug() << Q_FUNC_INFO; - QTimer::singleShot( ms, this, SLOT( exec() ) ); - }; + FuncTimeout( int ms, boost::function<void()> func, QObject* besafe ); - ~FuncTimeout() - { - //qDebug() << Q_FUNC_INFO; - }; + ~FuncTimeout(); public slots: - void exec() - { - if( !m_watch.isNull() ) - m_func(); - this->deleteLater(); - }; + void exec(); private: boost::function<void()> m_func; diff --git a/src/libtomahawk/infosystem/infoplugins/generic/chartsplugin.cpp b/src/libtomahawk/infosystem/infoplugins/generic/chartsplugin.cpp index 6b19566d5..ef3ae6691 100644 --- a/src/libtomahawk/infosystem/infoplugins/generic/chartsplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/generic/chartsplugin.cpp @@ -45,9 +45,9 @@ ChartsPlugin::ChartsPlugin() , m_chartsFetchJobs( 0 ) { /// Add resources here - m_chartResources << "billboard" << "itunes" << "rdio" << "wearehunted" << "ex.fm" << "soundcloudwall.com"; + m_chartResources << "billboard" << "itunes" << "rdio" << "wearehunted" << "ex.fm" << "soundcloudwall"; /// If you add resource, update version aswell - m_chartVersion = "1.0"; + m_chartVersion = "2.1"; m_supportedGetTypes << InfoChart << InfoChartCapabilities; } @@ -332,7 +332,9 @@ ChartsPlugin::chartTypes() if( source == "itunes" ){ chartName = "iTunes"; } - + if( source == "soundcloudwall" ){ + chartName = "SoundCloudWall"; + } if( source == "wearehunted" ){ chartName = "WeAreHunted"; } diff --git a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp index b2d5b7a32..5a9ede354 100644 --- a/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/generic/lastfmplugin.cpp @@ -451,8 +451,15 @@ LastFmPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSy QString artistName = criteria["artist"]; QString albumName = criteria["album"]; - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=album.imageredirect&artist=%1&album=%2&autocorrect=1&size=large&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( artistName ).arg( albumName ) ); + QUrl imgurl( "http://ws.audioscrobbler.com/2.0/" ); + imgurl.addQueryItem( "method", "album.imageredirect" ); + imgurl.addEncodedQueryItem( "artist", QUrl::toPercentEncoding( artistName, "", "+" ) ); + imgurl.addEncodedQueryItem( "album", QUrl::toPercentEncoding( albumName, "", "+" ) ); + imgurl.addQueryItem( "autocorrect", QString::number( 1 ) ); + imgurl.addQueryItem( "size", "large" ); + imgurl.addQueryItem( "api_key", "7a90f6672a04b809ee309af169f34b8b" ); + + QNetworkRequest req( imgurl ); QNetworkReply* reply = TomahawkUtils::nam()->get( req ); reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) ); @@ -464,8 +471,14 @@ LastFmPlugin::notInCacheSlot( QHash<QString, QString> criteria, Tomahawk::InfoSy { QString artistName = criteria["artist"]; - QString imgurl = "http://ws.audioscrobbler.com/2.0/?method=artist.imageredirect&artist=%1&autocorrect=1&size=large&api_key=7a90f6672a04b809ee309af169f34b8b"; - QNetworkRequest req( imgurl.arg( artistName ) ); + QUrl imgurl( "http://ws.audioscrobbler.com/2.0/" ); + imgurl.addQueryItem( "method", "artist.imageredirect" ); + imgurl.addEncodedQueryItem( "artist", QUrl::toPercentEncoding( artistName, "", "+" ) ); + imgurl.addQueryItem( "autocorrect", QString::number( 1 ) ); + imgurl.addQueryItem( "size", "large" ); + imgurl.addQueryItem( "api_key", "7a90f6672a04b809ee309af169f34b8b" ); + + QNetworkRequest req( imgurl ); QNetworkReply* reply = TomahawkUtils::nam()->get( req ); reply->setProperty( "requestData", QVariant::fromValue< Tomahawk::InfoSystem::InfoRequestData >( requestData ) ); @@ -693,14 +706,14 @@ LastFmPlugin::artistImagesReturned() void LastFmPlugin::settingsChanged() { - if ( !m_scrobbler && m_account->enabled() ) + if ( !m_scrobbler && m_account->scrobble() ) { // can simply create the scrobbler - lastfm::ws::Username = m_account->username(); - m_pw = m_account->password(); + lastfm::ws::Username = m_account->username(); + m_pw = m_account->password(); createScrobbler(); } - else if ( m_scrobbler && !m_account->enabled() ) + else if ( m_scrobbler && !m_account->scrobble() ) { delete m_scrobbler; m_scrobbler = 0; @@ -748,7 +761,7 @@ LastFmPlugin::onAuthenticated() m_account->setSessionKey( lastfm::ws::SessionKey.toLatin1() ); // qDebug() << "Got session key from last.fm"; - if ( m_account->enabled() ) + if ( m_account->scrobble() ) m_scrobbler = new lastfm::Audioscrobbler( "thk" ); } } 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<QList< InfoStringHash > >( chart_types ) ); - } QVariantMap defaultMap; defaultMap[ "spotify" ] = QStringList() << "United States" << "Top Albums"; m_allChartsMap[ "defaults" ] = defaultMap; m_allChartsMap.insert( "Spotify", QVariant::fromValue<QVariantMap>( charts ) ); - } else { @@ -281,13 +274,12 @@ SpotifyPlugin::chartTypes() } m_cachedRequests.clear(); } - } + void SpotifyPlugin::chartReturned() { - /// Chart request returned something! Woho QNetworkReply* reply = qobject_cast<QNetworkReply*>( 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/infosystem/infoplugins/unix/mprisplugin.cpp b/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.cpp index 58a514d49..80f079b88 100644 --- a/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.cpp +++ b/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.cpp @@ -42,8 +42,6 @@ MprisPlugin::MprisPlugin() : InfoPlugin() , m_coverTempFile( 0 ) { - qDebug() << Q_FUNC_INFO; - // init m_playbackStatus = "Stopped"; @@ -54,79 +52,84 @@ MprisPlugin::MprisPlugin() new MprisPluginRootAdaptor( this ); new MprisPluginPlayerAdaptor( this ); QDBusConnection dbus = QDBusConnection::sessionBus(); - dbus.registerObject("/org/mpris/MediaPlayer2", this); - dbus.registerService("org.mpris.MediaPlayer2.tomahawk"); + dbus.registerObject( "/org/mpris/MediaPlayer2", this ); + dbus.registerService( "org.mpris.MediaPlayer2.tomahawk" ); // Listen to volume changes connect( AudioEngine::instance(), SIGNAL( volumeChanged( int ) ), - SLOT( onVolumeChanged( int ) ) ); + SLOT( onVolumeChanged( int ) ) ); // When the playlist changes, signals for several properties are sent connect( AudioEngine::instance(), SIGNAL( playlistChanged( Tomahawk::playlistinterface_ptr ) ), - SLOT( onPlaylistChanged( Tomahawk::playlistinterface_ptr ) ) ); + SLOT( onPlaylistChanged( Tomahawk::playlistinterface_ptr ) ) ); // When a track is added or removed, CanGoNext updated signal is sent Tomahawk::playlistinterface_ptr playlist = AudioEngine::instance()->playlist(); - if( !playlist.isNull() ) + if ( !playlist.isNull() ) + { connect( playlist.data(), SIGNAL( trackCountChanged( unsigned int ) ), - SLOT( onTrackCountChanged( unsigned int ) ) ); + SLOT( onTrackCountChanged( unsigned int ) ) ); + } // Connect to AudioEngine's seeked signal connect( AudioEngine::instance(), SIGNAL( seeked( qint64 ) ), - SLOT( onSeeked( qint64 ) ) ); + SLOT( onSeeked( qint64 ) ) ); // Connect to the InfoSystem (we need to get album covers via getInfo) connect( Tomahawk::InfoSystem::InfoSystem::instance(), - SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), - SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); - - connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) ); + SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ), + SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) ); + connect( Tomahawk::InfoSystem::InfoSystem::instance(), + SIGNAL( finished( QString ) ), + SLOT( infoSystemFinished( QString ) ) ); } MprisPlugin::~MprisPlugin() { - qDebug() << Q_FUNC_INFO; delete m_coverTempFile; } + // org.mpris.MediaPlayer2 bool MprisPlugin::canQuit() const { - qDebug() << Q_FUNC_INFO; return true; } + bool MprisPlugin::canRaise() const { - qDebug() << Q_FUNC_INFO; return false; } + bool MprisPlugin::hasTrackList() const { - qDebug() << Q_FUNC_INFO; return false; } + QString MprisPlugin::identity() const { - return QString("Tomahawk"); + return QString( "Tomahawk" ); } + QString MprisPlugin::desktopEntry() const { - return QString("tomahawk"); + return QString( "tomahawk" ); } + QStringList MprisPlugin::supportedUriSchemes() const { @@ -135,23 +138,27 @@ MprisPlugin::supportedUriSchemes() const return uriSchemes; } + QStringList MprisPlugin::supportedMimeTypes() const { return QStringList(); } + void MprisPlugin::Raise() { } + void MprisPlugin::Quit() { QApplication::quit(); } + // org.mpris.MediaPlayer2.Player bool @@ -160,24 +167,28 @@ MprisPlugin::canControl() const return true; } + bool MprisPlugin::canGoNext() const { return AudioEngine::instance()->canGoNext(); } + bool MprisPlugin::canGoPrevious() const { return AudioEngine::instance()->canGoPrevious(); } + bool MprisPlugin::canPause() const { return AudioEngine::instance()->currentTrack(); } + bool MprisPlugin::canPlay() const { @@ -186,6 +197,7 @@ MprisPlugin::canPlay() const return AudioEngine::instance()->currentTrack() || ( !p.isNull() && p->trackCount() ); } + bool MprisPlugin::canSeek() const { @@ -196,6 +208,7 @@ MprisPlugin::canSeek() const } + QString MprisPlugin::loopStatus() const { @@ -215,39 +228,42 @@ MprisPlugin::loopStatus() const return "None"; break; default: - return QString("None"); + return "None"; break; } - return QString("None"); + return QString( "None" ); } + void -MprisPlugin::setLoopStatus( const QString &value ) +MprisPlugin::setLoopStatus( const QString& value ) { Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist(); if ( p.isNull() ) return; - if( value == "Track") + if ( value == "Track" ) p->setRepeatMode( PlaylistInterface::RepeatOne ); - else if( value == "Playlist" ) + else if ( value == "Playlist" ) p->setRepeatMode( PlaylistInterface::RepeatAll ); - else if( value == "None" ) + else if ( value == "None" ) p->setRepeatMode( PlaylistInterface::NoRepeat ); } + double MprisPlugin::maximumRate() const { return 1.0; } + QVariantMap MprisPlugin::metadata() const { QVariantMap metadataMap; Tomahawk::result_ptr track = AudioEngine::instance()->currentTrack(); - if( track ) + if ( track ) { metadataMap.insert( "mpris:trackid", QString( "/track/" ) + track->id().replace( "-", "" ) ); metadataMap.insert( "mpris:length", track->duration() ); @@ -256,9 +272,11 @@ MprisPlugin::metadata() const metadataMap.insert( "xesam:title", track->track() ); // Only return art if tempfile exists, and if its name contains the same "artist_album_tomahawk_cover.png" - if( m_coverTempFile && m_coverTempFile->exists() && - m_coverTempFile->fileName().contains( track->artist()->name() + "_" + track->album()->name() + "_tomahawk_cover.png" ) ) + if ( m_coverTempFile && m_coverTempFile->exists() && + m_coverTempFile->fileName().contains( track->artist()->name() + "_" + track->album()->name() + "_tomahawk_cover.png" ) ) + { metadataMap.insert( "mpris:artUrl", QString( QUrl::fromLocalFile( QFileInfo( *m_coverTempFile ).absoluteFilePath() ).toEncoded() ) ); + } else { // Need to fetch the album cover @@ -280,18 +298,21 @@ MprisPlugin::metadata() const return metadataMap; } + double MprisPlugin::minimumRate() const { return 1.0; } + QString MprisPlugin::playbackStatus() const { return m_playbackStatus; } + qlonglong MprisPlugin::position() const { @@ -299,18 +320,21 @@ MprisPlugin::position() const return (qlonglong) ( AudioEngine::instance()->currentTime() * 1000 ); } + double MprisPlugin::rate() const { return 1.0; } + void MprisPlugin::setRate( double value ) { Q_UNUSED( value ); } + bool MprisPlugin::shuffle() const { @@ -320,6 +344,7 @@ MprisPlugin::shuffle() const return p->shuffled(); } + void MprisPlugin::setShuffle( bool value ) { @@ -329,70 +354,76 @@ MprisPlugin::setShuffle( bool value ) return p->setShuffled( value ); } + double MprisPlugin::volume() const { return AudioEngine::instance()->volume(); } + void MprisPlugin::setVolume( double value ) { AudioEngine::instance()->setVolume( value ); } + void MprisPlugin::Next() { AudioEngine::instance()->next(); } + void -MprisPlugin::OpenUri( const QString &Uri ) +MprisPlugin::OpenUri( const QString& Uri ) { - if( Uri.contains( "tomahawk://" ) ) + if ( Uri.contains( "tomahawk://" ) ) GlobalActionManager::instance()->parseTomahawkLink( Uri ); - else if( Uri.contains( "spotify:" ) ) + else if ( Uri.contains( "spotify:" ) ) GlobalActionManager::instance()->openSpotifyLink( Uri ); } + void MprisPlugin::Pause() { AudioEngine::instance()->pause(); } + void MprisPlugin::Play() { AudioEngine::instance()->play(); } + void MprisPlugin::PlayPause() { AudioEngine::instance()->playPause(); } + void MprisPlugin::Previous() { AudioEngine::instance()->previous(); } + void MprisPlugin::Seek( qlonglong Offset ) { - qDebug() << Q_FUNC_INFO; - - if( !canSeek() ) + if ( !canSeek() ) return; qlonglong seekTime = position() + Offset; - qDebug() << "seekTime: " << seekTime; - if( seekTime < 0 ) + if ( seekTime < 0 ) AudioEngine::instance()->seek( 0 ); - else if( seekTime > AudioEngine::instance()->currentTrackTotalTime()*1000 ) + else if ( seekTime > AudioEngine::instance()->currentTrackTotalTime()*1000 ) Next(); // seekTime is in microseconds, but we work internally in milliseconds else @@ -400,50 +431,46 @@ MprisPlugin::Seek( qlonglong Offset ) } + void -MprisPlugin::SetPosition( const QDBusObjectPath &TrackId, qlonglong Position ) +MprisPlugin::SetPosition( const QDBusObjectPath& TrackId, qlonglong Position ) { - qDebug() << Q_FUNC_INFO; - if( !canSeek() ) + if ( !canSeek() ) return; - qDebug() << "path: " << TrackId.path(); - qDebug() << "position: " << Position; - - if( TrackId.path() != QString("/track/") + AudioEngine::instance()->currentTrack()->id().replace( "-", "" ) ) + if ( TrackId.path() != QString( "/track/" ) + AudioEngine::instance()->currentTrack()->id().replace( "-", "" ) ) return; - if( ( Position < 0) || ( Position > AudioEngine::instance()->currentTrackTotalTime()*1000 ) ) + if ( ( Position < 0) || ( Position > AudioEngine::instance()->currentTrackTotalTime()*1000 ) ) return; - qDebug() << "seeking to: " << Position/1000 << "ms"; - AudioEngine::instance()->seek( (qint64) (Position / 1000 ) ); } + void MprisPlugin::Stop() { AudioEngine::instance()->stop(); } + // InfoPlugin Methods void MprisPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ) { Q_UNUSED( requestData ); - qDebug() << Q_FUNC_INFO; return; } + void MprisPlugin::pushInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input ) { Q_UNUSED( caller ); - qDebug() << Q_FUNC_INFO; bool isPlayingInfo = false; switch ( type ) @@ -469,11 +496,11 @@ MprisPlugin::pushInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVar break; } - if( isPlayingInfo ) - notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "PlaybackStatus"); - + if ( isPlayingInfo ) + notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "PlaybackStatus" ); } + void MprisPlugin::stateChanged( AudioState newState, AudioState oldState ) { @@ -481,12 +508,11 @@ MprisPlugin::stateChanged( AudioState newState, AudioState oldState ) Q_UNUSED( oldState ); } + /** Audio state slots */ void -MprisPlugin::audioStarted( const QVariant &input ) +MprisPlugin::audioStarted( const QVariant& input ) { - qDebug() << Q_FUNC_INFO; - if ( !input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() ) return; @@ -495,64 +521,55 @@ MprisPlugin::audioStarted( const QVariant &input ) return; m_playbackStatus = "Playing"; - notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata"); - - //hash["artist"]; - //hash["title"]; - //QString nowPlaying = ""; - //qDebug() << "nowPlaying: " << nowPlaying; + notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" ); } + void -MprisPlugin::audioFinished( const QVariant &input ) +MprisPlugin::audioFinished( const QVariant& input ) { Q_UNUSED( input ); - //qDebug() << Q_FUNC_INFO; } + void MprisPlugin::audioStopped() { - qDebug() << Q_FUNC_INFO; m_playbackStatus = "Stopped"; } + void MprisPlugin::audioPaused() { - qDebug() << Q_FUNC_INFO; m_playbackStatus = "Paused"; } + void -MprisPlugin::audioResumed( const QVariant &input ) +MprisPlugin::audioResumed( const QVariant& input ) { - qDebug() << Q_FUNC_INFO; audioStarted( input ); } + void MprisPlugin::onVolumeChanged( int volume ) { Q_UNUSED( volume ); - notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Volume"); + notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Volume" ); } + void MprisPlugin::onPlaylistChanged( Tomahawk::playlistinterface_ptr playlist ) { - qDebug() << Q_FUNC_INFO; disconnect( this, SLOT( onTrackCountChanged( unsigned int ) ) ); - qDebug() << "disconnected"; - if( !playlist.isNull() ) - qDebug() << "playlist not null"; - if( !playlist.isNull() ) + if ( !playlist.isNull() ) connect( playlist.data(), SIGNAL( trackCountChanged( unsigned int ) ), SLOT( onTrackCountChanged( unsigned int ) ) ); - qDebug() << "connected new playlist"; - // Notify relevant changes notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "LoopStatus" ); notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Shuffle" ); @@ -560,6 +577,7 @@ MprisPlugin::onPlaylistChanged( Tomahawk::playlistinterface_ptr playlist ) onTrackCountChanged( 0 ); } + void MprisPlugin::onTrackCountChanged( unsigned int tracks ) { @@ -569,12 +587,14 @@ MprisPlugin::onTrackCountChanged( unsigned int tracks ) } - void - MprisPlugin::onSeeked( qint64 ms ) - { + +void +MprisPlugin::onSeeked( qint64 ms ) +{ qlonglong us = (qlonglong) ( ms*1000 ); emit Seeked( us ); - } +} + void MprisPlugin::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output ) @@ -603,7 +623,7 @@ MprisPlugin::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, image.loadFromData( ba ); // Pull out request data for album+artist - if( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() ) + if ( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() ) { qDebug() << "Cannot convert metadata input to album cover retrieval"; return; @@ -612,41 +632,33 @@ MprisPlugin::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, Tomahawk::InfoSystem::InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash>(); // delete the old tempfile and make new one, to avoid caching of filename by mpris clients - if( m_coverTempFile ) + if ( m_coverTempFile ) + { delete m_coverTempFile; - m_coverTempFile = new QTemporaryFile( QDir::toNativeSeparators( - QDir::tempPath() + "/" + hash["artist"] + "_" + hash["album"] + "_tomahawk_cover.png" ) ); - if( !m_coverTempFile->open() ) + m_coverTempFile = 0; + } + + if ( image.isNull() ) + return; + + m_coverTempFile = new QTemporaryFile( QDir::toNativeSeparators( QDir::tempPath() + "/" + hash["artist"] + "_" + hash["album"] + "_tomahawk_cover.png" ) ); + if ( !m_coverTempFile->open() ) { qDebug() << "WARNING: could not write temporary file for cover art!"; } // Finally, save the image to the new temp file - //if( image.save( QFileInfo( *m_coverTempFile ).absoluteFilePath(), "PNG" ) ) - if( image.save( m_coverTempFile, "PNG") ) + if ( image.save( m_coverTempFile, "PNG" ) ) { - qDebug() << Q_FUNC_INFO << "Image saving successful, notifying"; - qDebug() << "Saving to: " << QFileInfo( *m_coverTempFile ).absoluteFilePath(); + qDebug() << "Saving cover image to:" << QFileInfo( *m_coverTempFile ).absoluteFilePath(); m_coverTempFile->close(); notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" ); } else { - qDebug() << Q_FUNC_INFO << " failed to save image!"; + tDebug() << Q_FUNC_INFO << "failed to save cover image!"; m_coverTempFile->close(); } - - - - /* - if( m_coverTempFile->open() ) - { - QTextStream out( m_coverTempFile ); - out << ba; - m_coverTempFile->close(); - notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" ); - } - */ } } @@ -657,14 +669,14 @@ MprisPlugin::infoSystemFinished( QString target ) Q_UNUSED( target ); } + void -MprisPlugin::notifyPropertyChanged( const QString& interface, - const QString& propertyName ) +MprisPlugin::notifyPropertyChanged( const QString& interface, const QString& propertyName ) { QDBusMessage signal = QDBusMessage::createSignal( "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties", - "PropertiesChanged"); + "PropertiesChanged" ); signal << interface; QVariantMap changedProps; changedProps.insert(propertyName, property(propertyName.toAscii())); diff --git a/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.h b/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.h index db87ca37c..f0f9864ff 100644 --- a/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.h +++ b/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.h @@ -87,7 +87,7 @@ public: Q_PROPERTY( QString LoopStatus READ loopStatus WRITE setLoopStatus ) QString loopStatus() const; - void setLoopStatus( const QString &value ); + void setLoopStatus( const QString& value ); Q_PROPERTY( double MaximumRate READ maximumRate ) double maximumRate() const; @@ -129,16 +129,15 @@ public slots: // org.mpris.MediaPlayer2.Player void Next(); - void OpenUri( const QString &Uri ); + void OpenUri( const QString& Uri ); void Pause(); void Play(); void PlayPause(); void Previous(); void Seek( qlonglong Offset ); - void SetPosition( const QDBusObjectPath &TrackId, qlonglong Position ); + void SetPosition( const QDBusObjectPath& TrackId, qlonglong Position ); void Stop(); - protected slots: void getInfo( Tomahawk::InfoSystem::InfoRequestData requestData ); void pushInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input ); @@ -160,16 +159,16 @@ private: // Get Info // Push Info - void audioStarted( const QVariant &input ); - void audioFinished( const QVariant &input ); + void audioStarted( const QVariant& input ); + void audioFinished( const QVariant& input ); void audioStopped(); void audioPaused(); - void audioResumed( const QVariant &input ); + void audioResumed( const QVariant& input ); // DBus void notifyPropertyChanged( const QString& interface, const QString& propertyName ); QString m_playbackStatus; - QTemporaryFile *m_coverTempFile; + QTemporaryFile* m_coverTempFile; }; }; diff --git a/src/libtomahawk/jobview/ErrorStatusMessage.cpp b/src/libtomahawk/jobview/ErrorStatusMessage.cpp new file mode 100644 index 000000000..38f1082a1 --- /dev/null +++ b/src/libtomahawk/jobview/ErrorStatusMessage.cpp @@ -0,0 +1,56 @@ +/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === + * + * Copyright 2012, 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 "ErrorStatusMessage.h" + +#include "utils/tomahawkutils.h" + +#include <QTimer> + +QPixmap* ErrorStatusMessage::s_pixmap = 0; + +ErrorStatusMessage::ErrorStatusMessage( const QString& message, int timeoutSecs ) + : JobStatusItem() + , m_message( message ) +{ + m_timer = new QTimer( this ); + m_timer->setInterval( timeoutSecs * 1000 ); + m_timer->setSingleShot( true ); + + connect( m_timer, SIGNAL( timeout() ), this, SIGNAL( finished() ) ); + + if ( !s_pixmap ) + s_pixmap = new QPixmap( RESPATH "images/process-stop.png" ); + + m_timer->start(); +} + + +QPixmap +ErrorStatusMessage::icon() const +{ + Q_ASSERT( s_pixmap ); + return *s_pixmap; +} + + +QString +ErrorStatusMessage::mainText() const +{ + return m_message; +} diff --git a/src/libtomahawk/jobview/ErrorStatusMessage.h b/src/libtomahawk/jobview/ErrorStatusMessage.h new file mode 100644 index 000000000..7bf4f2fe2 --- /dev/null +++ b/src/libtomahawk/jobview/ErrorStatusMessage.h @@ -0,0 +1,49 @@ +/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === + * + * Copyright 2012, 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 ERRORSTATUSMESSAGE_H +#define ERRORSTATUSMESSAGE_H + +#include "JobStatusItem.h" +#include "dllmacro.h" + +#include <QPixmap> + +class QTimer; + +class DLLEXPORT ErrorStatusMessage : public JobStatusItem +{ + Q_OBJECT +public: + explicit ErrorStatusMessage( const QString& errorMessage, int defaultTimeoutSecs = 8 ); + + QString type() const { return "errormessage"; } + QString rightColumnText() const { return QString(); } + + QPixmap icon() const; + QString mainText() const; + + bool allowMultiLine() const { return true; } +private: + QString m_message; + QTimer* m_timer; + + static QPixmap* s_pixmap; +}; + +#endif // ERRORSTATUSMESSAGE_H diff --git a/src/libtomahawk/jobview/IndexingJobItem.cpp b/src/libtomahawk/jobview/IndexingJobItem.cpp new file mode 100644 index 000000000..c9ec6928f --- /dev/null +++ b/src/libtomahawk/jobview/IndexingJobItem.cpp @@ -0,0 +1,49 @@ +/* === 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 "IndexingJobItem.h" + +#include "utils/tomahawkutils.h" + +#include <QPixmap> + +static QPixmap* s_indexIcon = 0; + + +QString +IndexingJobItem::mainText() const +{ + return tr( "Indexing database" ); +} + +QPixmap +IndexingJobItem::icon() const +{ + if ( s_indexIcon == 0 ) + s_indexIcon = new QPixmap( RESPATH "images/view-refresh.png" ); + + return *s_indexIcon; + +} + + +void IndexingJobItem::done() +{ + emit finished(); +} + diff --git a/src/libtomahawk/jobview/IndexingJobItem.h b/src/libtomahawk/jobview/IndexingJobItem.h new file mode 100644 index 000000000..61bf5a05f --- /dev/null +++ b/src/libtomahawk/jobview/IndexingJobItem.h @@ -0,0 +1,39 @@ +/* === 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 INDEXINGJOBITEM_H +#define INDEXINGJOBITEM_H + +#include <jobview/JobStatusItem.h> + + +class IndexingJobItem : public JobStatusItem +{ + Q_OBJECT +public: + explicit IndexingJobItem() {} + + void done(); + + virtual QString rightColumnText() const { return QString(); } + virtual QString mainText() const; + virtual QPixmap icon() const; + virtual QString type() const { return "indexerjob"; } +}; + +#endif // INDEXINGJOBITEM_H diff --git a/src/libtomahawk/jobview/JobStatusDelegate.cpp b/src/libtomahawk/jobview/JobStatusDelegate.cpp index c279687ee..9c686c15d 100644 --- a/src/libtomahawk/jobview/JobStatusDelegate.cpp +++ b/src/libtomahawk/jobview/JobStatusDelegate.cpp @@ -23,14 +23,16 @@ #include <QPainter> #include <QApplication> +#include <QListView> #define ROW_HEIGHT 20 #define ICON_PADDING 1 #define PADDING 2 JobStatusDelegate::JobStatusDelegate( QObject* parent ) : QStyledItemDelegate ( parent ) + , m_parentView( qobject_cast< QListView* >( parent ) ) { - + Q_ASSERT( m_parentView ); } JobStatusDelegate::~JobStatusDelegate() @@ -45,6 +47,7 @@ JobStatusDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, QStyleOptionViewItemV4 opt = option; initStyleOption( &opt, index ); QFontMetrics fm( opt.font ); + const bool allowMultiLine = index.data( JobStatusModel::AllowMultiLineRole ).toBool(); opt.state &= ~QStyle::State_MouseOver; QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget ); @@ -52,7 +55,9 @@ JobStatusDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, // painter->drawLine( opt.rect.topLeft(), opt.rect.topRight() ); painter->setRenderHint( QPainter::Antialiasing ); - const QRect iconRect( ICON_PADDING, ICON_PADDING + opt.rect.y(), ROW_HEIGHT - 2*ICON_PADDING, ROW_HEIGHT - 2*ICON_PADDING ); + QRect iconRect( ICON_PADDING, ICON_PADDING + opt.rect.y(), ROW_HEIGHT - 2*ICON_PADDING, ROW_HEIGHT - 2*ICON_PADDING ); + if ( allowMultiLine ) + iconRect.moveTop( opt.rect.top() + opt.rect.height() / 2 - iconRect.height() / 2); QPixmap p = index.data( Qt::DecorationRole ).value< QPixmap >(); p = p.scaledToHeight( iconRect.height(), Qt::SmoothTransformation ); painter->drawPixmap( iconRect, p ); @@ -71,15 +76,34 @@ JobStatusDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const int mainW = rightEdge - 3*PADDING - iconRect.right(); QString mainText = index.data( Qt::DisplayRole ).toString(); - mainText = fm.elidedText( mainText, Qt::ElideRight, mainW ); - painter->drawText( QRect( iconRect.right() + 2*PADDING, PADDING + opt.rect.y(), mainW, opt.rect.height() - 2*PADDING ), Qt::AlignLeft | Qt::AlignVCenter, mainText ); + QTextOption to( Qt::AlignLeft | Qt::AlignVCenter ); + if ( !allowMultiLine ) + mainText = fm.elidedText( mainText, Qt::ElideRight, mainW ); + else + to.setWrapMode( QTextOption::WrapAtWordBoundaryOrAnywhere ); + painter->drawText( QRect( iconRect.right() + 2*PADDING, PADDING + opt.rect.y(), mainW, opt.rect.height() - 2*PADDING ), mainText, to ); } QSize JobStatusDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const { -// return QStyledItemDelegate::sizeHint( option, index ); - const int w = QStyledItemDelegate::sizeHint ( option, index ).width(); - return QSize( w, ROW_HEIGHT ); + const bool allowMultiLine = index.data( JobStatusModel::AllowMultiLineRole ).toBool(); + + if ( !allowMultiLine ) + return QSize( QStyledItemDelegate::sizeHint ( option, index ).width(), ROW_HEIGHT ); + else if ( m_cachedMultiLineHeights.contains( index ) ) + return QSize( QStyledItemDelegate::sizeHint ( option, index ).width(), m_cachedMultiLineHeights[ index ] ); + + // Don't elide, but stretch across as many rows as required + QStyleOptionViewItemV4 opt = option; + initStyleOption( &opt, index ); + + const QString text = index.data( Qt::DisplayRole ).toString(); + const int leftEdge = ICON_PADDING + ROW_HEIGHT + 2*PADDING; + const QRect rect = opt.fontMetrics.boundingRect( leftEdge, opt.rect.top(), m_parentView->width() - leftEdge, 200, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, text ); + + m_cachedMultiLineHeights.insert( index, rect.height() + 4*PADDING ); + + return QSize( QStyledItemDelegate::sizeHint ( option, index ).width(), rect.height() + 4*PADDING ); } diff --git a/src/libtomahawk/jobview/JobStatusDelegate.h b/src/libtomahawk/jobview/JobStatusDelegate.h index fc2752350..08a355db4 100644 --- a/src/libtomahawk/jobview/JobStatusDelegate.h +++ b/src/libtomahawk/jobview/JobStatusDelegate.h @@ -22,6 +22,7 @@ #include <QStyledItemDelegate> class QPainter; +class QListView; class JobStatusDelegate : public QStyledItemDelegate { @@ -33,6 +34,10 @@ public: virtual void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const; virtual QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const; + +private: + mutable QHash< QPersistentModelIndex, int > m_cachedMultiLineHeights; + QListView* m_parentView; }; #endif // JOBSTATUSDELEGATE_H diff --git a/src/libtomahawk/jobview/JobStatusItem.cpp b/src/libtomahawk/jobview/JobStatusItem.cpp new file mode 100644 index 000000000..092f140d4 --- /dev/null +++ b/src/libtomahawk/jobview/JobStatusItem.cpp @@ -0,0 +1,42 @@ +/* === 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 "JobStatusItem.h" + + +JobStatusItem::JobStatusItem() + : QObject() +{ +} + + +JobStatusItem::~JobStatusItem() +{ +} + + +bool JobStatusItem::allowMultiLine() const +{ + return false; +} + +bool JobStatusItem::collapseItem() const +{ + return false; +} diff --git a/src/libtomahawk/jobview/JobStatusItem.h b/src/libtomahawk/jobview/JobStatusItem.h index 77f0dbb6e..a8190f269 100644 --- a/src/libtomahawk/jobview/JobStatusItem.h +++ b/src/libtomahawk/jobview/JobStatusItem.h @@ -20,7 +20,8 @@ #define JOB_STATUS_ITEM #include <QObject> -#include <QPixmap> + +class QPixmap; /** * Implement your own JobStatusItem if you want to add some sort of job status entry in the JobStatusView. @@ -37,8 +38,8 @@ class JobStatusItem : public QObject { Q_OBJECT public: - explicit JobStatusItem() : QObject() {} - virtual ~JobStatusItem() {} + explicit JobStatusItem(); + virtual ~JobStatusItem(); virtual QString type() const = 0; @@ -52,7 +53,8 @@ public: * instead of showing each individually. In this case, the right column from the item will be ignored * and a count will be shown instead. */ - virtual bool collapseItem() const { return false; } + virtual bool collapseItem() const; + virtual bool allowMultiLine() const; signals: /// Ask for an update diff --git a/src/libtomahawk/jobview/JobStatusModel.cpp b/src/libtomahawk/jobview/JobStatusModel.cpp index a50c8a4e3..b22594a59 100644 --- a/src/libtomahawk/jobview/JobStatusModel.cpp +++ b/src/libtomahawk/jobview/JobStatusModel.cpp @@ -21,6 +21,7 @@ #include "JobStatusItem.h" #include "utils/logger.h" +#include <QPixmap> JobStatusModel::JobStatusModel( QObject* parent ) : QAbstractListModel ( parent ) @@ -100,6 +101,8 @@ JobStatusModel::data( const QModelIndex& index, int role ) const else return item->rightColumnText(); } + case AllowMultiLineRole: + return item->allowMultiLine(); } return QVariant(); diff --git a/src/libtomahawk/jobview/JobStatusModel.h b/src/libtomahawk/jobview/JobStatusModel.h index c3611cfd3..539189337 100644 --- a/src/libtomahawk/jobview/JobStatusModel.h +++ b/src/libtomahawk/jobview/JobStatusModel.h @@ -31,7 +31,8 @@ public: enum JobRoles { // DecorationRole is icon // DisplayRole is main col - RightColumnRole = Qt::UserRole + 1 + RightColumnRole = Qt::UserRole + 1, + AllowMultiLineRole = Qt::UserRole + 2 }; explicit JobStatusModel( QObject* parent = 0 ); diff --git a/src/libtomahawk/jobview/JobStatusView.cpp b/src/libtomahawk/jobview/JobStatusView.cpp index fa121a1f0..4b10f09e2 100644 --- a/src/libtomahawk/jobview/JobStatusView.cpp +++ b/src/libtomahawk/jobview/JobStatusView.cpp @@ -40,6 +40,7 @@ JobStatusView* JobStatusView::s_instance = 0; JobStatusView::JobStatusView( AnimatedSplitter* parent ) : AnimatedWidget( parent ) , m_parent( parent ) + , m_cachedHeight( -1 ) { s_instance = this; @@ -56,9 +57,7 @@ JobStatusView::JobStatusView( AnimatedSplitter* parent ) m_view->setFrameShape( QFrame::NoFrame ); m_view->setAttribute( Qt::WA_MacShowFocusRect, 0 ); - -// new QTreeWidgetItem( m_tree ); - m_view->setUniformItemSizes( true ); + m_view->setUniformItemSizes( false ); #ifndef Q_WS_WIN QFont f = font(); @@ -86,12 +85,14 @@ JobStatusView::setModel( JobStatusModel* m ) connect( m_view->model(), SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( checkCount() ) ); connect( m_view->model(), SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( checkCount() ) ); + connect( m_view->model(), SIGNAL( modelReset() ), this, SLOT( checkCount() ) ); } void JobStatusView::checkCount() { + m_cachedHeight = -1; if ( m_view->model()->rowCount() == 0 && !isHidden() ) emit hideWidget(); else @@ -102,15 +103,21 @@ JobStatusView::checkCount() QSize JobStatusView::sizeHint() const { + if ( m_cachedHeight >= 0 ) + return QSize( 0, m_cachedHeight ); + unsigned int y = 0; -// y += m_tree->header()->height(); y += m_view->contentsMargins().top() + m_view->contentsMargins().bottom(); if ( m_view->model()->rowCount() ) { - unsigned int rowheight = m_view->sizeHintForRow( 0 ); - y += rowheight * m_view->model()->rowCount() + 2; + for ( int i = 0; i < m_view->model()->rowCount(); i++ ) + { + y += m_view->sizeHintForRow( i ); + } + y += 2; // some padding } + m_cachedHeight = y; return QSize( 0, y ); } diff --git a/src/libtomahawk/jobview/JobStatusView.h b/src/libtomahawk/jobview/JobStatusView.h index 1cbc5a7c2..5d07c16b7 100644 --- a/src/libtomahawk/jobview/JobStatusView.h +++ b/src/libtomahawk/jobview/JobStatusView.h @@ -56,6 +56,7 @@ private: QListView* m_view; JobStatusModel* m_model; AnimatedSplitter* m_parent; + mutable int m_cachedHeight; static JobStatusView* s_instance; }; diff --git a/src/libtomahawk/jobview/LatchedStatusItem.h b/src/libtomahawk/jobview/LatchedStatusItem.h index 7f7cb3f8a..6841c3240 100644 --- a/src/libtomahawk/jobview/LatchedStatusItem.h +++ b/src/libtomahawk/jobview/LatchedStatusItem.h @@ -21,7 +21,9 @@ #include "JobStatusItem.h" #include "typedefs.h" + #include <QHash> +#include <QPixmap> class LatchedStatusManager; diff --git a/src/libtomahawk/jobview/PipelineStatusItem.h b/src/libtomahawk/jobview/PipelineStatusItem.h index 5072c506f..04fe70bc1 100644 --- a/src/libtomahawk/jobview/PipelineStatusItem.h +++ b/src/libtomahawk/jobview/PipelineStatusItem.h @@ -22,6 +22,8 @@ #include "jobview/JobStatusItem.h" #include "query.h" +#include <QPixmap> + class PipelineStatusItem : public JobStatusItem { Q_OBJECT diff --git a/src/libtomahawk/jobview/TransferStatusItem.h b/src/libtomahawk/jobview/TransferStatusItem.h index 2387580ec..1670343c2 100644 --- a/src/libtomahawk/jobview/TransferStatusItem.h +++ b/src/libtomahawk/jobview/TransferStatusItem.h @@ -21,6 +21,8 @@ #include "JobStatusItem.h" +#include <QPixmap> + class StreamConnection; class TransferStatusManager : public QObject diff --git a/src/libtomahawk/network/portfwdthread.cpp b/src/libtomahawk/network/portfwdthread.cpp index 6006699bf..c0097d90b 100644 --- a/src/libtomahawk/network/portfwdthread.cpp +++ b/src/libtomahawk/network/portfwdthread.cpp @@ -43,7 +43,7 @@ PortFwdThread::~PortFwdThread() { qDebug() << Q_FUNC_INFO << "waiting for event loop to finish..."; quit(); - wait( 1000 ); + wait( 6000 ); delete m_portfwd; } diff --git a/src/libtomahawk/pipeline.cpp b/src/libtomahawk/pipeline.cpp index 2b25caa14..a1cc86970 100644 --- a/src/libtomahawk/pipeline.cpp +++ b/src/libtomahawk/pipeline.cpp @@ -28,6 +28,8 @@ #include "utils/logger.h" +#include "boost/bind.hpp" + #define DEFAULT_CONCURRENT_QUERIES 4 #define MAX_CONCURRENT_QUERIES 16 #define CLEANUP_TIMEOUT 5 * 60 * 1000 @@ -61,6 +63,7 @@ Pipeline::Pipeline( QObject* parent ) Pipeline::~Pipeline() { + tDebug() << Q_FUNC_INFO; m_running = false; // stop script resolvers @@ -418,10 +421,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/libtomahawk/pipeline.h b/src/libtomahawk/pipeline.h index 5fa88a2d7..50e33ab57 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/libtomahawk/playlist/XspfUpdater.cpp b/src/libtomahawk/playlist/XspfUpdater.cpp index 796efb17e..a9d794334 100644 --- a/src/libtomahawk/playlist/XspfUpdater.cpp +++ b/src/libtomahawk/playlist/XspfUpdater.cpp @@ -71,7 +71,11 @@ XspfUpdater::playlistLoaded() foreach ( const plentry_ptr ple, playlist()->entries() ) tracks << ple->query(); - QList< query_ptr > mergedTracks = TomahawkUtils::mergePlaylistChanges( tracks, loader->entries() ); + bool changed = false; + QList< query_ptr > mergedTracks = TomahawkUtils::mergePlaylistChanges( tracks, loader->entries(), changed ); + + if ( !changed ) + return; QList<Tomahawk::plentry_ptr> el = playlist()->entriesFromQueries( mergedTracks, true ); playlist()->createNewRevision( uuid(), playlist()->currentrevision(), el ); diff --git a/src/libtomahawk/playlist/albumitemdelegate.cpp b/src/libtomahawk/playlist/albumitemdelegate.cpp index e28208248..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" ); } @@ -89,21 +88,19 @@ 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() ); + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::CoverInCase, 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 +120,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..7d29f6c81 100644 --- a/src/libtomahawk/playlist/albumitemdelegate.h +++ b/src/libtomahawk/playlist/albumitemdelegate.h @@ -49,12 +49,10 @@ private: QAbstractItemView* m_view; AlbumProxyModel* m_model; - mutable QHash< qint64, QPixmap > m_cache; mutable QHash< QPersistentModelIndex, QRect > m_artistNameRects; QPersistentModelIndex m_hoveringOver; QPixmap m_shadowPixmap; - QPixmap m_defaultCover; }; #endif // ALBUMITEMDELEGATE_H diff --git a/src/libtomahawk/playlist/albummodel.cpp b/src/libtomahawk/playlist/albummodel.cpp index 9c552e070..08cc3f653 100644 --- a/src/libtomahawk/playlist/albummodel.cpp +++ b/src/libtomahawk/playlist/albummodel.cpp @@ -304,7 +304,18 @@ AlbumModel::addAlbums( const QList<Tomahawk::album_ptr>& albums ) if ( m_overwriteOnAdd ) clear(); - if ( !albums.count() ) + QList<Tomahawk::album_ptr> trimmedAlbums; + foreach ( const album_ptr& album, albums ) + { + if ( !album.isNull() && album->name().length() ) + { + if ( findItem( album ) || trimmedAlbums.contains( album ) ) + continue; + trimmedAlbums << album; + } + } + + if ( !trimmedAlbums.count() ) { emit itemCountChanged( rowCount( QModelIndex() ) ); return; @@ -313,12 +324,12 @@ AlbumModel::addAlbums( const QList<Tomahawk::album_ptr>& albums ) int c = rowCount( QModelIndex() ); QPair< int, int > crows; crows.first = c; - crows.second = c + albums.count() - 1; + crows.second = c + trimmedAlbums.count() - 1; emit beginInsertRows( QModelIndex(), crows.first, crows.second ); AlbumItem* albumitem; - foreach( const album_ptr& album, albums ) + foreach( const album_ptr& album, trimmedAlbums ) { albumitem = new AlbumItem( album, m_rootItem ); albumitem->index = createIndex( m_rootItem->children.count() - 1, 0, albumitem ); @@ -339,7 +350,18 @@ AlbumModel::addArtists( const QList<Tomahawk::artist_ptr>& artists ) if ( m_overwriteOnAdd ) clear(); - if ( !artists.count() ) + QList<Tomahawk::artist_ptr> trimmedArtists; + foreach ( const artist_ptr& artist, artists ) + { + if ( !artist.isNull() && artist->name().length() ) + { + if ( findItem( artist ) || trimmedArtists.contains( artist ) ) + continue; + trimmedArtists << artist; + } + } + + if ( !trimmedArtists.count() ) { emit itemCountChanged( rowCount( QModelIndex() ) ); return; @@ -348,12 +370,12 @@ AlbumModel::addArtists( const QList<Tomahawk::artist_ptr>& artists ) int c = rowCount( QModelIndex() ); QPair< int, int > crows; crows.first = c; - crows.second = c + artists.count() - 1; + crows.second = c + trimmedArtists.count() - 1; emit beginInsertRows( QModelIndex(), crows.first, crows.second ); AlbumItem* albumitem; - foreach( const artist_ptr& artist, artists ) + foreach ( const artist_ptr& artist, trimmedArtists ) { albumitem = new AlbumItem( artist, m_rootItem ); albumitem->index = createIndex( m_rootItem->children.count() - 1, 0, albumitem ); @@ -396,3 +418,35 @@ AlbumModel::onDataChanged() AlbumItem* p = (AlbumItem*)sender(); emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount( QModelIndex() ) - 1 ) ); } + + +AlbumItem* +AlbumModel::findItem( const artist_ptr& artist ) const +{ + for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + AlbumItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + if ( !item->artist().isNull() && item->artist() == artist ) + { + return item; + } + } + + return 0; +} + + +AlbumItem* +AlbumModel::findItem( const album_ptr& album ) const +{ + for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + AlbumItem* item = itemFromIndex( index( i, 0, QModelIndex() ) ); + if ( !item->album().isNull() && item->album() == album ) + { + return item; + } + } + + return 0; +} diff --git a/src/libtomahawk/playlist/albummodel.h b/src/libtomahawk/playlist/albummodel.h index 01d8f0569..e163db437 100644 --- a/src/libtomahawk/playlist/albummodel.h +++ b/src/libtomahawk/playlist/albummodel.h @@ -71,6 +71,9 @@ public: virtual void setTitle( const QString& title ) { m_title = title; } virtual void setDescription( const QString& description ) { m_description = description; } + AlbumItem* findItem( const Tomahawk::artist_ptr& artist ) const; + AlbumItem* findItem( const Tomahawk::album_ptr& album ) const; + AlbumItem* itemFromIndex( const QModelIndex& index ) const { if ( index.isValid() ) diff --git a/src/libtomahawk/playlist/albumview.cpp b/src/libtomahawk/playlist/albumview.cpp index 6e4ba2ee9..a0a73dcb2 100644 --- a/src/libtomahawk/playlist/albumview.cpp +++ b/src/libtomahawk/playlist/albumview.cpp @@ -25,6 +25,7 @@ #include <qmath.h> #include "audio/audioengine.h" +#include "context/ContextWidget.h" #include "tomahawksettings.h" #include "artist.h" #include "albumitem.h" @@ -113,6 +114,20 @@ AlbumView::setAlbumModel( AlbumModel* model ) } +void +AlbumView::currentChanged( const QModelIndex& current, const QModelIndex& previous ) +{ + QListView::currentChanged( current, previous ); + + AlbumItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( current ) ); + if ( item ) + { + if ( !item->album().isNull() ) + ViewManager::instance()->context()->setAlbum( item->album() ); + } +} + + void AlbumView::onItemActivated( const QModelIndex& index ) { diff --git a/src/libtomahawk/playlist/albumview.h b/src/libtomahawk/playlist/albumview.h index 61cc14d43..80f38981b 100644 --- a/src/libtomahawk/playlist/albumview.h +++ b/src/libtomahawk/playlist/albumview.h @@ -71,6 +71,9 @@ protected: void paintEvent( QPaintEvent* event ); void resizeEvent( QResizeEvent* event ); +protected slots: + virtual void currentChanged( const QModelIndex& current, const QModelIndex& previous ); + private slots: void onItemCountChanged( unsigned int items ); diff --git a/src/libtomahawk/playlist/artistview.cpp b/src/libtomahawk/playlist/artistview.cpp index 808b6c8c5..af00b8715 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 ) ) ); @@ -128,7 +133,8 @@ ArtistView::setTreeModel( TreeModel* model ) connect( m_proxyModel, SIGNAL( filteringFinished() ), m_loadingSpinner, SLOT( fadeOut() ) ); connect( m_model, SIGNAL( itemCountChanged( unsigned int ) ), SLOT( onItemCountChanged( unsigned int ) ) ); - connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) ); + connect( m_proxyModel, SIGNAL( filteringFinished() ), SLOT( onFilterChangeFinished() ) ); + connect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( onViewChanged() ) ); guid(); // this will set the guid on the header @@ -145,6 +151,44 @@ 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 ) { @@ -236,7 +280,7 @@ ArtistView::onItemCountChanged( unsigned int items ) void -ArtistView::onFilterChanged( const QString& ) +ArtistView::onFilterChangeFinished() { if ( selectedIndexes().count() ) scrollTo( selectedIndexes().at( 0 ), QAbstractItemView::PositionAtCenter ); diff --git a/src/libtomahawk/playlist/artistview.h b/src/libtomahawk/playlist/artistview.h index 8167f9b73..30245534f 100644 --- a/src/libtomahawk/playlist/artistview.h +++ b/src/libtomahawk/playlist/artistview.h @@ -93,8 +93,10 @@ protected slots: private slots: void onItemCountChanged( unsigned int items ); - void onFilterChanged( const QString& filter ); + void onFilterChangeFinished(); 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/customplaylistview.cpp b/src/libtomahawk/playlist/customplaylistview.cpp index 1cb9517cd..b3b2c2ba5 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; } @@ -104,7 +104,11 @@ CustomPlaylistView::generateTracks() void CustomPlaylistView::tracksGenerated( QList< query_ptr > tracks ) { - QList< query_ptr > newTracks = TomahawkUtils::mergePlaylistChanges( m_model->queries(), tracks ); + bool changed = false; + QList< query_ptr > newTracks = TomahawkUtils::mergePlaylistChanges( m_model->queries(), tracks, changed); + + if ( !changed ) + return; m_model->clear(); m_model->append( newTracks ); 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/playlist/dynamic/DynamicModel.cpp b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp index fdae6f5ca..6584a6149 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicModel.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicModel.cpp @@ -54,19 +54,22 @@ DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist, bool load { Q_UNUSED( loadEntries ); - if( !m_playlist.isNull() ) { + if ( !m_playlist.isNull() ) + { disconnect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); } + const int oldCount = rowCount( QModelIndex() ); + m_playlist = playlist; m_deduper.clear(); - if( m_playlist->mode() == OnDemand ) + if ( m_playlist->mode() == OnDemand ) setFilterUnresolvable( true ); connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( newTrackGenerated( Tomahawk::query_ptr ) ) ); PlaylistModel::loadPlaylist( m_playlist, m_playlist->mode() == Static ); - if( m_playlist->mode() == OnDemand ) + if ( m_playlist->mode() == OnDemand && oldCount != rowCount( QModelIndex() ) ) emit trackCountChanged( rowCount( QModelIndex() ) ); } @@ -74,7 +77,7 @@ DynamicModel::loadPlaylist( const Tomahawk::dynplaylist_ptr& playlist, bool load QString DynamicModel::description() const { - if( !m_playlist.isNull() && !m_playlist->generator().isNull() ) + if ( !m_playlist.isNull() && !m_playlist->generator().isNull() ) return m_playlist->generator()->sentenceSummary(); else return QString(); @@ -95,7 +98,8 @@ DynamicModel::startOnDemand() void DynamicModel::newTrackGenerated( const Tomahawk::query_ptr& query ) { - if( m_onDemandRunning ) { + if ( m_onDemandRunning ) + { bool isDuplicate = false; for ( int i = 0; i < m_deduper.size(); i++ ) { @@ -125,7 +129,7 @@ void DynamicModel::stopOnDemand( bool stopPlaying ) { m_onDemandRunning = false; - if( stopPlaying ) + if ( stopPlaying ) AudioEngine::instance()->stop(); disconnect( AudioEngine::instance(), SIGNAL( loading( Tomahawk::result_ptr ) ), this, SLOT( newTrackLoading() ) ); @@ -135,7 +139,7 @@ DynamicModel::stopOnDemand( bool stopPlaying ) void DynamicModel::changeStation() { - if( m_onDemandRunning ) + if ( m_onDemandRunning ) m_changeOnNext = true; else // if we're not running, just start m_playlist->generator()->startOnDemand(); @@ -171,7 +175,7 @@ DynamicModel::trackResolveFinished( bool success ) { qDebug() << "Got successful resolved track:" << q->track() << q->artist() << m_lastResolvedRow << m_currentAttempts; - if( m_currentAttempts > 0 ) { + if ( m_currentAttempts > 0 ) { qDebug() << "EMITTING AN ASK FOR COLLAPSE:" << m_lastResolvedRow << m_currentAttempts; emit collapseFromTo( m_lastResolvedRow, m_currentAttempts ); } @@ -188,11 +192,14 @@ void DynamicModel::newTrackLoading() { qDebug() << "Got NEW TRACK LOADING signal"; - if( m_changeOnNext ) { // reset instead of getting the next one + if ( m_changeOnNext ) + { // reset instead of getting the next one m_lastResolvedRow = rowCount( QModelIndex() ); m_searchingForNext = true; m_playlist->generator()->startOnDemand(); - } else if( m_onDemandRunning && m_currentAttempts == 0 && !m_searchingForNext ) { // if we're in dynamic mode and we're also currently idle + } + else if ( m_onDemandRunning && m_currentAttempts == 0 && !m_searchingForNext ) + { // if we're in dynamic mode and we're also currently idle m_lastResolvedRow = rowCount( QModelIndex() ); m_searchingForNext = true; qDebug() << "IDLE fetching new track!"; @@ -204,13 +211,17 @@ DynamicModel::newTrackLoading() void DynamicModel::tracksGenerated( const QList< query_ptr > entries, int limitResolvedTo ) { - if( m_filterUnresolvable && m_playlist->mode() == OnDemand ) { // wait till we get them resolved (for previewing stations) + if ( m_filterUnresolvable && m_playlist->mode() == OnDemand ) + { // wait till we get them resolved (for previewing stations) m_limitResolvedTo = limitResolvedTo; filterUnresolved( entries ); - } else { + } + else + { addToPlaylist( entries, m_playlist->mode() == OnDemand ); // if ondemand, we're previewing, so clear old - if( m_playlist->mode() == OnDemand ) { + if ( m_playlist->mode() == OnDemand ) + { m_lastResolvedRow = rowCount( QModelIndex() ); } } @@ -224,9 +235,9 @@ DynamicModel::filterUnresolved( const QList< query_ptr >& entries ) { m_toResolveList = entries; - foreach( const query_ptr& q, entries ) { + foreach ( const query_ptr& q, entries ) connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( filteringTrackResolved( bool ) ) ); - } + Pipeline::instance()->resolve( entries, true ); } @@ -240,7 +251,8 @@ DynamicModel::filteringTrackResolved( bool successful ) // if meantime the user began the station, abort qDebug() << "Got filtering resolved finished for track, was it successful?:" << q->track() << q->artist() << successful << q->playable(); - if( m_onDemandRunning ) { + if ( m_onDemandRunning ) + { m_toResolveList.clear(); m_resolvedList.clear(); @@ -248,8 +260,10 @@ DynamicModel::filteringTrackResolved( bool successful ) } query_ptr realptr; - foreach( const query_ptr& qptr, m_toResolveList ) { - if( qptr.data() == q ) { + foreach ( const query_ptr& qptr, m_toResolveList ) + { + if ( qptr.data() == q ) + { realptr = qptr; break; } @@ -259,25 +273,30 @@ DynamicModel::filteringTrackResolved( bool successful ) m_toResolveList.removeAll( realptr ); - if( realptr->playable() ) { + if ( realptr->playable() ) + { m_resolvedList << realptr; // append and update internal lastResolvedRow addToPlaylist( QList< query_ptr >() << realptr, false ); - if( m_playlist->mode() == OnDemand ) { + if ( m_playlist->mode() == OnDemand ) + { m_lastResolvedRow = rowCount( QModelIndex() ); } - if( m_toResolveList.isEmpty() || m_resolvedList.size() == m_limitResolvedTo ) { // done + if ( m_toResolveList.isEmpty() || m_resolvedList.size() == m_limitResolvedTo ) + { // done m_toResolveList.clear(); m_resolvedList.clear(); } - } else { + } + else + { qDebug() << "Got unsuccessful resolve request for this track" << realptr->track() << realptr->artist(); } - if( m_toResolveList.isEmpty() && rowCount( QModelIndex() ) == 0 ) // we failed + if ( m_toResolveList.isEmpty() && rowCount( QModelIndex() ) == 0 ) // we failed emit trackGenerationFailure( tr( "Could not find a playable track.\n\nPlease change the filters or try again." ) ); } @@ -285,16 +304,20 @@ DynamicModel::filteringTrackResolved( bool successful ) void DynamicModel::addToPlaylist( const QList< query_ptr >& entries, bool clearFirst ) { - if( clearFirst ) + if ( clearFirst ) clear(); foreach ( const query_ptr& q, entries ) m_deduper.append( QPair< QString, QString >( q->track(), q->artist() ) ); - if( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) { + if ( m_playlist->author()->isLocal() && m_playlist->mode() == Static ) + { m_playlist->addEntries( entries, m_playlist->currentrevision() ); - } else { // read-only, so add tracks only in the GUI, not to the playlist itself - foreach( const query_ptr& query, entries ) { + } + else + { // read-only, so add tracks only in the GUI, not to the playlist itself + foreach ( const query_ptr& query, entries ) + { append( query ); } } @@ -310,12 +333,15 @@ DynamicModel::remove(const QModelIndex& idx, bool moreToCome) return; qDebug() << Q_FUNC_INFO << "DYNAMIC MODEL REMOVIN!" << moreToCome << ( idx == index( rowCount( QModelIndex() ) - 1, 0, QModelIndex() ) ); - if( m_playlist->mode() == OnDemand ) { - if( !moreToCome && idx == index( rowCount( QModelIndex() ) - 1, 0, QModelIndex() ) ) { // if the user is manually removing the last one, re-add as we're a station + if ( m_playlist->mode() == OnDemand ) + { + if ( !moreToCome && idx == index( rowCount( QModelIndex() ) - 1, 0, QModelIndex() ) ) + { // if the user is manually removing the last one, re-add as we're a station newTrackLoading(); } TrackModel::remove( idx ); - } else + } + else PlaylistModel::remove( idx, moreToCome ); // don't call onPlaylistChanged. diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.cpp b/src/libtomahawk/playlist/dynamic/DynamicView.cpp index eda9a224b..014471187 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.cpp +++ b/src/libtomahawk/playlist/dynamic/DynamicView.cpp @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * - * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 diff --git a/src/libtomahawk/playlist/dynamic/DynamicView.h b/src/libtomahawk/playlist/dynamic/DynamicView.h index 39826fe97..d39e60118 100644 --- a/src/libtomahawk/playlist/dynamic/DynamicView.h +++ b/src/libtomahawk/playlist/dynamic/DynamicView.h @@ -1,6 +1,6 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === - * - * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -32,33 +32,33 @@ namespace Tomahawk class DynamicModel; - + class DynamicView : public PlaylistView { Q_OBJECT public: explicit DynamicView( QWidget* parent = 0 ); virtual ~DynamicView(); - + virtual void setDynamicModel( DynamicModel* model ); - + void setOnDemand( bool onDemand ); void setReadOnly( bool readOnly ); - + void setDynamicWorking( bool working ); - + virtual void paintEvent( QPaintEvent* event ); - + public slots: void showMessageTimeout( const QString& title, const QString& body ); void showMessage( const QString& message ); - + // collapse and animate the transition // there MUST be a row *after* startRow + num. that is, you can't collapse // entries unless there is at least one entry after the last collapsed row // optionally you can specify how many rows are past the block of collapsed rows void collapseEntries( int startRow, int num, int numToKeep = 1 ); - + private slots: void onTrackCountChanged( unsigned int ); void checkForOverflow(); @@ -70,7 +70,7 @@ private: DynamicModel* m_model; QString m_title; QString m_body; - + bool m_onDemand; bool m_readOnly; bool m_checkOnCollapse; @@ -88,7 +88,7 @@ private: QTimeLine m_fadeOutAnim; QTimeLine m_slideAnim; }; - + }; diff --git a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp index cc3a7b28b..43073bf68 100644 --- a/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp +++ b/src/libtomahawk/playlist/dynamic/echonest/EchonestControl.cpp @@ -181,6 +181,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); + m_data.first = m_currentType; } else if( selectedType() == "Artist Description" ) { m_currentType = Echonest::DynamicPlaylist::Description; @@ -199,6 +200,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); + m_data.first = m_currentType; } else if( selectedType() == "User Radio" ) { m_currentType = Echonest::DynamicPlaylist::SourceCatalog; @@ -246,6 +248,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); + m_data.first = m_currentType; } else if( selectedType() == "Variety" ) { m_currentType = Echonest::DynamicPlaylist::Variety; @@ -266,6 +269,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); + m_data.first = m_currentType; } else if( selectedType() == "Adventurousness" ) { m_currentType = Echonest::DynamicPlaylist::Adventurousness; @@ -287,6 +291,7 @@ Tomahawk::EchonestControl::updateWidgets() input->hide(); m_match = QWeakPointer< QWidget >( match ); m_input = QWeakPointer< QWidget >( input ); + m_data.first = m_currentType; } else if( selectedType() == "Tempo" ) { m_currentType = Echonest::DynamicPlaylist::MinTempo; diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp index b4a07acba..22861c420 100644 --- a/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicWidget.cpp @@ -118,32 +118,34 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) { // special case: if we have launched multiple setRevision calls, and the number of controls is different, it means that we're getting an intermediate setRevision // called after the user has already created more revisions. ignore in that case. - if( m_playlist.data() == playlist.data() && m_seqRevLaunched > 0 + if ( m_playlist.data() == playlist.data() && m_seqRevLaunched > 0 && m_controls->controls().size() != playlist->generator()->controls().size() // different number of controls - && qAbs( m_playlist->generator()->controls().size() - playlist->generator()->controls().size() ) < m_seqRevLaunched ) { // difference in controls has to be less than how many revisions we launched + && qAbs( m_playlist->generator()->controls().size() - playlist->generator()->controls().size() ) < m_seqRevLaunched ) + { // difference in controls has to be less than how many revisions we launched return; } m_seqRevLaunched = 0; // if we're being told to load the same dynamic playlist over again, only do it if the controls have a different number - if( !m_playlist.isNull() && ( m_playlist.data() == playlist.data() ) // same playlist pointer - && m_playlist->generator()->controls().size() == playlist->generator()->controls().size() ) { + if ( !m_playlist.isNull() && ( m_playlist.data() == playlist.data() ) // same playlist pointer + && m_playlist->generator()->controls().size() == playlist->generator()->controls().size() ) + { // we can skip our work. just let the dynamiccontrollist show the difference m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); m_playlist = playlist; - if( !m_runningOnDemand ) { + if ( !m_runningOnDemand ) m_model->loadPlaylist( m_playlist ); - } else if( !m_controlsChanged ) { // if the controls changed, we already dealt with that and don't want to change station yet + else if ( !m_controlsChanged ) // if the controls changed, we already dealt with that and don't want to change station yet m_model->changeStation(); - } m_controlsChanged = false; return; } - if( !m_playlist.isNull() ) { + if ( !m_playlist.isNull() ) + { disconnect( m_playlist->generator().data(), SIGNAL( generated( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksGenerated( QList<Tomahawk::query_ptr> ) ) ); disconnect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ), this, SLOT(onRevisionLoaded( Tomahawk::DynamicPlaylistRevision) ) ); disconnect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); @@ -160,23 +162,22 @@ DynamicWidget::loadDynamicPlaylist( const Tomahawk::dynplaylist_ptr& playlist ) m_setup->setPlaylist( m_playlist ); - if( !m_playlist->author()->isLocal() ) { // hide controls, as we show the description in the summary + if ( !m_playlist->author()->isLocal() ) // hide controls, as we show the description in the summary m_layout->removeWidget( m_controls ); - } else if( m_layout->indexOf( m_controls ) == -1 ) { + else if ( m_layout->indexOf( m_controls ) == -1 ) m_layout->insertWidget( 0, m_controls ); - } - - if( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() ) - showPreview(); - - if( !m_playlist.isNull() ) - m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); connect( m_playlist->generator().data(), SIGNAL( generated( QList<Tomahawk::query_ptr> ) ), this, SLOT( tracksGenerated( QList<Tomahawk::query_ptr> ) ) ); connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); connect( m_playlist->generator().data(), SIGNAL( error( QString, QString ) ), this, SLOT( generatorError( QString, QString ) ) ); connect( m_playlist.data(), SIGNAL( deleted( Tomahawk::dynplaylist_ptr ) ), this, SLOT( onDeleted() ) ); connect( m_playlist.data(), SIGNAL( changed() ), this, SLOT( onChanged() ) ); + + if ( m_playlist->mode() == OnDemand && !m_playlist->generator()->controls().isEmpty() ) + showPreview(); + + if ( !m_playlist.isNull() ) + m_controls->setControls( m_playlist, m_playlist->author()->isLocal() ); } @@ -226,12 +227,15 @@ DynamicWidget::resizeEvent(QResizeEvent* ) void DynamicWidget::layoutFloatingWidgets() { - if( !m_runningOnDemand ) { + if ( !m_runningOnDemand ) + { int x = ( width() / 2 ) - ( m_setup->size().width() / 2 ); int y = height() - m_setup->size().height() - 40; // padding m_setup->move( x, y ); - } else if( m_runningOnDemand && m_steering ) { + } + else if( m_runningOnDemand && m_steering ) + { int x = ( width() / 2 ) - ( m_steering->size().width() / 2 ); int y = height() - m_steering->size().height() - 40; // padding @@ -243,13 +247,15 @@ DynamicWidget::layoutFloatingWidgets() void DynamicWidget::playlistChanged( Tomahawk::playlistinterface_ptr pl ) { - if( pl == m_view->proxyModel()->playlistInterface() ) { // same playlist + if ( pl == m_view->proxyModel()->playlistInterface() ) // same playlist m_activePlaylist = true; - } else { + else + { m_activePlaylist = false; // user started playing something somewhere else, so give it a rest - if( m_runningOnDemand ) { + if ( m_runningOnDemand ) + { stopStation( false ); } } @@ -259,9 +265,8 @@ DynamicWidget::playlistChanged( Tomahawk::playlistinterface_ptr pl ) void DynamicWidget::showEvent(QShowEvent* ) { - if( !m_playlist.isNull() && !m_runningOnDemand ) { + if ( !m_playlist.isNull() && !m_runningOnDemand ) m_setup->fadeIn(); - } } @@ -289,8 +294,9 @@ DynamicWidget::stationFailed( const QString& msg ) void DynamicWidget::trackStarted() { - if( m_activePlaylist && !m_playlist.isNull() && - m_playlist->mode() == OnDemand && !m_runningOnDemand ) { + if ( m_activePlaylist && !m_playlist.isNull() && + m_playlist->mode() == OnDemand && !m_runningOnDemand ) + { startStation(); } @@ -300,7 +306,7 @@ DynamicWidget::trackStarted() void DynamicWidget::tracksAdded() { - if( m_playlist->mode() == OnDemand && m_runningOnDemand && m_setup->isVisible() ) + if ( m_playlist->mode() == OnDemand && m_runningOnDemand && m_setup->isVisible() ) m_setup->fadeOut(); } @@ -325,7 +331,8 @@ DynamicWidget::startStation() m_setup->fadeOut(); // show the steering controls - if( m_playlist->generator()->onDemandSteerable() ) { + if ( m_playlist->generator()->onDemandSteerable() ) + { // position it horizontally centered, above the botton. m_steering = m_playlist->generator()->steeringWidget(); Q_ASSERT( m_steering ); @@ -361,7 +368,7 @@ DynamicWidget::tracksGenerated( const QList< query_ptr >& queries ) { m_resolveOnNextLoad = true; } - else if( m_playlist->mode() == OnDemand ) + else if ( m_playlist->mode() == OnDemand ) { limit = 5; } @@ -380,7 +387,7 @@ DynamicWidget::controlsChanged( bool added ) // when playing a station just ignore it till we're ready and get a controlChanged() m_controlsChanged = true; - if( !m_playlist->author()->isLocal() ) + if ( !m_playlist->author()->isLocal() ) return; m_playlist->createNewRevision(); m_seqRevLaunched++; @@ -396,7 +403,7 @@ void DynamicWidget::controlChanged( const Tomahawk::dyncontrol_ptr& control ) { Q_UNUSED( control ); - if( !m_playlist->author()->isLocal() ) + if ( !m_playlist->author()->isLocal() ) return; m_playlist->createNewRevision(); m_seqRevLaunched++; @@ -443,8 +450,7 @@ DynamicWidget::steeringChanged() void DynamicWidget::showPreview() { - if ( m_playlist->mode() == OnDemand && - !m_runningOnDemand ) + if ( m_playlist->mode() == OnDemand && !m_runningOnDemand ) { // if this is a not running station, preview matching tracks m_model->clear(); @@ -456,12 +462,16 @@ DynamicWidget::showPreview() void DynamicWidget::generatorError( const QString& title, const QString& content ) { - if( m_runningOnDemand ) { - stopStation( false ); - } m_view->setDynamicWorking( false ); m_loading->fadeOut(); - m_view->showMessageTimeout( title, content ); + + if ( m_runningOnDemand ) + { + stopStation( false ); + m_view->showMessage( tr( "Station ran out of tracks!\n\nTry tweaking the filters for a new set of songs to play." ) ); + } + else + m_view->showMessageTimeout( title, content ); } @@ -528,7 +538,7 @@ DynamicWidget::onDeleted() void DynamicWidget::onChanged() { - if( !m_playlist.isNull() && - ViewManager::instance()->currentPage() == this ) - emit nameChanged( m_playlist->title() ); + if ( !m_playlist.isNull() && + ViewManager::instance()->currentPage() == this ) + emit nameChanged( m_playlist->title() ); } diff --git a/src/libtomahawk/playlist/treeitemdelegate.cpp b/src/libtomahawk/playlist/treeitemdelegate.cpp index 90fbe4aa7..685a6389e 100644 --- a/src/libtomahawk/playlist/treeitemdelegate.cpp +++ b/src/libtomahawk/playlist/treeitemdelegate.cpp @@ -155,32 +155,18 @@ 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 ); + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::ScaledCover, r.size() ); } else if ( !item->artist().isNull() ) { - cover.loadFromData( item->artist()->cover() ); + cover = item->artist()->cover( r.size(), false ); + if ( cover.isNull() ) + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::ScaledCover, r.size() ); } - QPixmap scover; - 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() ); - } - 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..98100d65d 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -20,6 +20,7 @@ #include <QMimeData> +#include "pipeline.h" #include "source.h" #include "sourcelist.h" #include "audio/audioengine.h" @@ -87,6 +88,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 ) { @@ -783,6 +796,8 @@ TreeModel::onAlbumsAdded( const QList<Tomahawk::album_ptr>& 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(); @@ -915,10 +930,11 @@ TreeModel::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QV foreach ( const QString& trackName, tracks ) { - query_ptr query = Query::get( inputInfo[ "artist" ], trackName, inputInfo[ "album" ], uuid() ); + query_ptr query = Query::get( inputInfo[ "artist" ], trackName, inputInfo[ "album" ] ); query->setAlbumPos( trackNo++ ); ql << query; } + Pipeline::instance()->resolve( ql ); onTracksAdded( ql, idx ); } 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/query.cpp b/src/libtomahawk/query.cpp index 8df9cd6f7..113ad7b8c 100644 --- a/src/libtomahawk/query.cpp +++ b/src/libtomahawk/query.cpp @@ -104,7 +104,9 @@ Query::Query( const QString& query, const QID& qid ) Query::~Query() { + QMutexLocker lock( &m_mutex ); m_ownRef.clear(); + m_results.clear(); } @@ -459,7 +461,15 @@ Query::howSimilar( const Tomahawk::result_ptr& r ) if ( isFullTextQuery() ) { + const QString artistTrackname = DatabaseImpl::sortname( fullTextQuery() ); + const QString rArtistTrackname = DatabaseImpl::sortname( r->artist()->name() + " " + r->track() ); + + int atrdist = levenshtein( artistTrackname, rArtistTrackname ); + int mlatr = qMax( artistTrackname.length(), rArtistTrackname.length() ); + float dcatr = (float)( mlatr - atrdist ) / mlatr; + float res = qMax( dcart, dcalb ); + res = qMax( res, dcatr ); return qMax( res, dctrk ); } else @@ -489,7 +499,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<DatabaseCommand>(cmd) ); } diff --git a/src/libtomahawk/resolver.cpp b/src/libtomahawk/resolver.cpp index e69de29bb..89f9b8565 100644 --- a/src/libtomahawk/resolver.cpp +++ b/src/libtomahawk/resolver.cpp @@ -0,0 +1,19 @@ +/* === 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 + * 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 "resolver.h" diff --git a/src/libtomahawk/resolvers/scriptresolver.cpp b/src/libtomahawk/resolvers/scriptresolver.cpp index 5f5fb2193..a70ed5746 100644 --- a/src/libtomahawk/resolvers/scriptresolver.cpp +++ b/src/libtomahawk/resolvers/scriptresolver.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -55,7 +56,6 @@ ScriptResolver::ScriptResolver( const QString& exe ) // set the name to the binary, if we launch properly we'll get the name the resolver reports m_name = QFileInfo( filePath() ).baseName(); - m_account = new Tomahawk::Accounts::SpotifyResolverAccount(); } @@ -283,9 +283,6 @@ ScriptResolver::handleMsg( const QByteArray& msg ) Tomahawk::query_ptr q = Tomahawk::Query::get( m.value( "artist" ).toString() , m.value( "track" ).toString() , QString(), uuid(), false ); tracks << q; } - - if(m_account) - m_account->addPlaylist( qid, title, tracks); } } } @@ -309,7 +306,7 @@ ScriptResolver::cmdExited( int code, QProcess::ExitStatus status ) return; } - if ( m_num_restarts < 10 ) + if ( m_num_restarts < 0 ) { m_num_restarts++; tLog() << "*** Restart num" << m_num_restarts; diff --git a/src/libtomahawk/resolvers/scriptresolver.h b/src/libtomahawk/resolvers/scriptresolver.h index 037a29376..56fbff8d4 100644 --- a/src/libtomahawk/resolvers/scriptresolver.h +++ b/src/libtomahawk/resolvers/scriptresolver.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -90,7 +91,6 @@ private: QJson::Parser m_parser; QJson::Serializer m_serializer; - Tomahawk::Accounts::SpotifyResolverAccount *m_account; }; #endif // SCRIPTRESOLVER_H diff --git a/src/libtomahawk/source.cpp b/src/libtomahawk/source.cpp index 9d96792db..ebf795a08 100644 --- a/src/libtomahawk/source.cpp +++ b/src/libtomahawk/source.cpp @@ -350,6 +350,12 @@ Source::trackTimerFired() void Source::addCommand( const QSharedPointer<DatabaseCommand>& command ) { + if ( QThread::currentThread() != thread() ) + { + QMetaObject::invokeMethod( this, "addCommand", Qt::QueuedConnection, Q_ARG( const QSharedPointer<DatabaseCommand>, command ) ); + return; + } + m_cmds << command; if ( !command->singletonCmd() ) m_lastCmdGuid = command->guid(); @@ -363,7 +369,6 @@ Source::executeCommands() { if ( QThread::currentThread() != thread() ) { - tDebug() << "Reinvoking in correct thread:" << Q_FUNC_INFO; QMetaObject::invokeMethod( this, "executeCommands", Qt::QueuedConnection ); return; } diff --git a/src/libtomahawk/source.h b/src/libtomahawk/source.h index f3361a720..396094c0e 100644 --- a/src/libtomahawk/source.h +++ b/src/libtomahawk/source.h @@ -129,9 +129,9 @@ private slots: void trackTimerFired(); void executeCommands(); + void addCommand( const QSharedPointer<DatabaseCommand>& command ); private: - void addCommand( const QSharedPointer<DatabaseCommand>& command ); void updateTracks(); void reportSocialAttributesChanged( DatabaseCommand_SocialAction* action ); diff --git a/src/libtomahawk/thirdparty/Qocoa/qsearchfield_mac.mm b/src/libtomahawk/thirdparty/Qocoa/qsearchfield_mac.mm index 120a8c291..efa05f631 100644 --- a/src/libtomahawk/thirdparty/Qocoa/qsearchfield_mac.mm +++ b/src/libtomahawk/thirdparty/Qocoa/qsearchfield_mac.mm @@ -22,6 +22,7 @@ THE SOFTWARE. */ #include "qsearchfield.h" +#include "moc_qsearchfield.cpp" #include "qocoa_mac.h" diff --git a/src/libtomahawk/tomahawksettings.cpp b/src/libtomahawk/tomahawksettings.cpp index aa9baa5eb..8b93906dd 100644 --- a/src/libtomahawk/tomahawksettings.cpp +++ b/src/libtomahawk/tomahawksettings.cpp @@ -46,19 +46,38 @@ 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", TOMAHAWK_SETTINGS_VERSION ); doInitialSetup(); } - else if( value( "configversion" ).toUInt() != TOMAHAWK_SETTINGS_VERSION ) + else if ( value( "configversion" ).toUInt() != TOMAHAWK_SETTINGS_VERSION ) { qDebug() << "Config version outdated, old:" << value( "configversion" ).toUInt() << "new:" << TOMAHAWK_SETTINGS_VERSION - << "Doing upgrade, if any..."; + << "Doing upgrade, if any, and backing up"; +// QString newname = QString( "%1.v%2" ).arg( dbname ).arg( version ); + if ( format() == IniFormat || + ( format() == NativeFormat +#ifdef Q_OS_WIN + && false +#endif + ) ) + { + qDebug() << "Backing up old ini-style config file"; + const QString path = fileName(); + const QString newname = path + QString( ".v%1" ).arg( value( "configversion" ).toString() ); + QFile::copy( path, newname ); + + } int current = value( "configversion" ).toUInt(); - while( current < TOMAHAWK_SETTINGS_VERSION ) + while ( current < TOMAHAWK_SETTINGS_VERSION ) { doUpgrade( current, current + 1 ); @@ -67,7 +86,6 @@ TomahawkSettings::TomahawkSettings( QObject* parent ) // insert upgrade code here as required setValue( "configversion", TOMAHAWK_SETTINGS_VERSION ); } - } @@ -82,6 +100,16 @@ TomahawkSettings::doInitialSetup() { // by default we add a local network resolver addAccount( "sipzeroconf_autocreated" ); + + // Add a last.fm account for scrobbling and infosystem + const QString accountKey = QString( "lastfmaccount_%1" ).arg( QUuid::createUuid().toString().mid( 1, 8 ) ); + addAccount( accountKey ); + + beginGroup( "accounts/" + accountKey ); + setValue( "enabled", false ); + setValue( "autoconnect", true ); + setValue( "types", QStringList() << "ResolverType" << "StatusPushType" ); + endGroup(); } @@ -90,22 +118,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" ) ); @@ -122,7 +151,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" ) ); @@ -143,7 +172,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" ) ) { @@ -182,11 +212,12 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) tDebug() << "UPGRADING AND DELETING:" << resolverDir.absolutePath(); TomahawkUtils::removeDirectory( resolverDir.absolutePath() ); } - } else if ( oldVersion == 4 ) + } + else if ( oldVersion == 4 || oldVersion == 5 ) { // 0.3.0 contained a bug which prevent indexing local files. Force a reindex. QTimer::singleShot( 0, this, SLOT( updateIndex() ) ); - } else if ( oldVersion == 5 ) + } else if ( oldVersion == 6 ) { // Migrate to accounts from sipplugins. // collect old connected and enabled sip plugins @@ -215,10 +246,13 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) QVariantHash credentials; credentials[ "username" ] = value( sipPlugin + "/username" ); credentials[ "password" ] = value( sipPlugin + "/password" ); - credentials[ "port" ] = value( sipPlugin + "/port" ); - credentials[ "server" ] = value( sipPlugin + "/server" ); + + QVariantHash configuration; + configuration[ "port" ] = value( sipPlugin + "/port" ); + configuration[ "server" ] = value( sipPlugin + "/server" ); setValue( QString( "accounts/%1/credentials" ).arg( accountKey ), credentials ); + setValue( QString( "accounts/%1/configuration" ).arg( accountKey ), configuration ); setValue( QString( "accounts/%1/accountfriendlyname" ).arg( accountKey ), value( sipPlugin + "/username" ) ); } @@ -271,6 +305,10 @@ TomahawkSettings::doUpgrade( int oldVersion, int newVersion ) foreach ( const QString& resolver, allResolvers ) { + // We handle last.fm resolvers differently. + if ( resolver.contains( "lastfm" ) ) + continue; + const QString accountKey = QString( "resolveraccount_%1" ).arg( QUuid::createUuid().toString().mid( 1, 8 ) ); accounts << accountKey; @@ -1011,6 +1049,7 @@ TomahawkSettings::setNowPlayingEnabled( bool enable ) setValue( "adium/enablenowplaying", enable ); } + TomahawkSettings::PrivateListeningMode TomahawkSettings::privateListeningMode() const { diff --git a/src/libtomahawk/tomahawksettings.h b/src/libtomahawk/tomahawksettings.h index 52788b15c..1a1d67100 100644 --- a/src/libtomahawk/tomahawksettings.h +++ b/src/libtomahawk/tomahawksettings.h @@ -27,7 +27,7 @@ #include "dllmacro.h" -#define TOMAHAWK_SETTINGS_VERSION 6 +#define TOMAHAWK_SETTINGS_VERSION 7 /** * Convenience wrapper around QSettings for tomahawk-specific config diff --git a/src/libtomahawk/utils/dropjobnotifier.h b/src/libtomahawk/utils/dropjobnotifier.h index 6119f0ad8..ff38faf9b 100644 --- a/src/libtomahawk/utils/dropjobnotifier.h +++ b/src/libtomahawk/utils/dropjobnotifier.h @@ -27,7 +27,8 @@ #include <QObject> #include <QSet> -#include <QtCore/QStringList> +#include <QStringList> +#include <QPixmap> class QNetworkReply; namespace Tomahawk diff --git a/src/libtomahawk/utils/groovesharkparser.cpp b/src/libtomahawk/utils/groovesharkparser.cpp index dcc28e620..d8353dfb9 100644 --- a/src/libtomahawk/utils/groovesharkparser.cpp +++ b/src/libtomahawk/utils/groovesharkparser.cpp @@ -27,6 +27,7 @@ #include "dropjob.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" #include "dropjobnotifier.h" #include "viewmanager.h" @@ -38,6 +39,10 @@ #include <QtNetwork/QNetworkAccessManager> #include <QtNetwork/QNetworkReply> +#include <QWebPage> +#include <QWebFrame> +#include <QWebElement> + using namespace Tomahawk; QPixmap* GroovesharkParser::s_pixmap = 0; @@ -71,15 +76,17 @@ GroovesharkParser::~GroovesharkParser() void GroovesharkParser::lookupUrl( const QString& link ) { - if( link.contains( "playlist" ) ) + if ( link.contains( "playlist" ) ) { - if( !m_createNewPlaylist ) + if ( !m_createNewPlaylist ) m_trackMode = true; else m_trackMode = false; lookupGroovesharkPlaylist( link ); } + else if ( link.contains( "grooveshark.com/s/" ) || link.contains( "grooveshark.com/#/s/" ) ) + lookupGroovesharkTrack( link ); else return; @@ -96,7 +103,7 @@ GroovesharkParser::lookupGroovesharkPlaylist( const QString& linkRaw ) tDebug() << "no fragment, setting fragment to path"; urlFragment = QUrl(linkRaw).path(); } - + tDebug() << urlFragment; int paramStartingPostition = urlFragment.indexOf( "?" ); @@ -107,22 +114,22 @@ GroovesharkParser::lookupGroovesharkPlaylist( const QString& linkRaw ) bool ok; QStringList urlParts = urlFragment.split( "/", QString::SkipEmptyParts ); - + tDebug() << urlParts; - + int playlistID = urlParts.at( 2 ).toInt( &ok, 10 ); if (!ok) { tDebug() << "incorrect grooveshark url"; return; } - - - + + + m_title = urlParts.at( 1 ); - + tDebug() << "should get playlist " << playlistID; - + DropJob::DropType type; type = DropJob::Playlist; @@ -152,6 +159,53 @@ GroovesharkParser::lookupGroovesharkPlaylist( const QString& linkRaw ) m_queries.insert( reply ); } + +void +GroovesharkParser::lookupGroovesharkTrack( const QString& track ) +{ + tLog() << "Parsing Grooveshark Track Page:" << track; + + QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( QUrl( track ) ) ); + connect( reply, SIGNAL( finished() ), this, SLOT( trackPageFetchFinished() ) ); + + m_browseJob = new DropJobNotifier( pixmap(), "Grooveshark", DropJob::Track, reply ); + JobStatusView::instance()->model()->addJob( m_browseJob ); + + m_queries << reply; +} + + +void +GroovesharkParser::trackPageFetchFinished() +{ + QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() ); + Q_ASSERT( r ); + + m_queries.remove( r ); + r->deleteLater(); + + QWebPage page; + page.settings()->setAttribute( QWebSettings::JavascriptEnabled, false ); + page.settings()->setAttribute( QWebSettings::PluginsEnabled, false ); + page.settings()->setAttribute( QWebSettings::JavaEnabled, false ); + page.settings()->setAttribute( QWebSettings::AutoLoadImages, false ); + page.mainFrame()->setHtml( QString::fromUtf8( r->readAll() ) ); + QWebElement title = page.mainFrame()->findFirstElement("span[itemprop='name']"); + QWebElement artist = page.mainFrame()->findFirstElement("noscript span[itemprop='byArtist']"); + QWebElement album = page.mainFrame()->findFirstElement("noscript span[itemprop='inAlbum']"); + + if ( !title.toPlainText().isEmpty() && !artist.toPlainText().isEmpty() ) + { + tDebug() << "Got track info from grooveshark, enough to create a query:" << title.toPlainText() << artist.toPlainText() << album.toPlainText(); + + Tomahawk::query_ptr q = Tomahawk::Query::get( artist.toPlainText(), title.toPlainText(), album.toPlainText(), uuid(), true ); + m_tracks << q; + } + + checkTrackFinished(); +} + + void GroovesharkParser::groovesharkLookupFinished() { @@ -178,7 +232,7 @@ GroovesharkParser::groovesharkLookupFinished() foreach (const QVariant& var, list) { QVariantMap trackResult = var.toMap(); - + QString title, artist, album; title = trackResult.value( "SongName", QString() ).toString(); @@ -194,10 +248,11 @@ GroovesharkParser::groovesharkLookupFinished() Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), m_trackMode ); m_tracks << q; } - + } else { + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Error fetching Grooveshark information from the network!" ) ) ); tLog() << "Error in network request to grooveshark for track decoding:" << r->errorString(); } @@ -229,7 +284,7 @@ GroovesharkParser::checkPlaylistFinished() return; } - + emit tracks( m_tracks ); deleteLater(); diff --git a/src/libtomahawk/utils/groovesharkparser.h b/src/libtomahawk/utils/groovesharkparser.h index a387ed928..af04dac3d 100644 --- a/src/libtomahawk/utils/groovesharkparser.h +++ b/src/libtomahawk/utils/groovesharkparser.h @@ -42,7 +42,7 @@ class QNetworkReply; namespace Tomahawk { - + class DropJobNotifier; class DLLEXPORT GroovesharkParser : public QObject @@ -58,6 +58,7 @@ signals: private slots: void groovesharkLookupFinished(); + void trackPageFetchFinished(); void playlistCreated(); private: @@ -65,6 +66,8 @@ private: void lookupUrl( const QString& url ); void lookupGroovesharkPlaylist( const QString& playlist ); + void lookupGroovesharkTrack( const QString& track ); + void checkTrackFinished(); void checkPlaylistFinished(); int m_limit; @@ -79,7 +82,7 @@ private: QCA::SymmetricKey m_apiKey; static QPixmap* s_pixmap; - + }; } diff --git a/src/libtomahawk/utils/itunesparser.cpp b/src/libtomahawk/utils/itunesparser.cpp index 1afb1cfaf..f4f97060d 100644 --- a/src/libtomahawk/utils/itunesparser.cpp +++ b/src/libtomahawk/utils/itunesparser.cpp @@ -25,6 +25,7 @@ #include "sourcelist.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" #include <qjson/parser.h> @@ -166,6 +167,7 @@ ItunesParser::itunesResponseLookupFinished() } else { + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Error fetching iTunes information from the network!" ) ) ); tLog() << "Error in network request to Itunes for track decoding:" << r->errorString(); } diff --git a/src/libtomahawk/utils/rdioparser.cpp b/src/libtomahawk/utils/rdioparser.cpp index a0621f23c..3d8416177 100644 --- a/src/libtomahawk/utils/rdioparser.cpp +++ b/src/libtomahawk/utils/rdioparser.cpp @@ -26,6 +26,7 @@ #include "dropjob.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" #include "dropjobnotifier.h" #include "viewmanager.h" #include "sourcelist.h" @@ -189,6 +190,7 @@ RdioParser::rdioReturned() } else { + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Error fetching Rdio information from the network!" ) ) ); tLog() << "Error in network request to Rdio for track decoding:" << r->errorString(); } diff --git a/src/libtomahawk/utils/shortenedlinkparser.cpp b/src/libtomahawk/utils/shortenedlinkparser.cpp index 6d89aba69..e28b1a1d3 100644 --- a/src/libtomahawk/utils/shortenedlinkparser.cpp +++ b/src/libtomahawk/utils/shortenedlinkparser.cpp @@ -21,7 +21,11 @@ #include "utils/logger.h" #include "utils/tomahawkutils.h" +#include "dropjobnotifier.h" #include "query.h" +#include "jobview/ErrorStatusMessage.h" +#include "jobview/JobStatusModel.h" +#include "jobview/JobStatusView.h" #include <qjson/parser.h> @@ -30,6 +34,7 @@ using namespace Tomahawk; +QPixmap* ShortenedLinkParser::s_pixmap = 0; ShortenedLinkParser::ShortenedLinkParser ( const QStringList& urls, QObject* parent ) : QObject( parent ) @@ -58,6 +63,8 @@ ShortenedLinkParser::handlesUrl( const QString& url ) url.contains( "itun.es" ) || url.contains( "tinyurl.com" ) || url.contains( "tinysong.com" ) || + url.contains( "grooveshark.com/s/~/" ) || // These redirect to the 'real' grooveshark track url + url.contains( "grooveshark.com/#/s/~/" ) || url.contains( "rd.io" ) ); } @@ -65,11 +72,18 @@ void ShortenedLinkParser::lookupUrl ( const QString& url ) { tDebug() << "Looking up..." << url; + QString cleaned = url; + if ( cleaned.contains( "/#/s/" ) ) + cleaned.replace( "/#", "" ); - QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( QUrl( url ) ) ); + QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( QUrl( cleaned ) ) ); connect( reply, SIGNAL( finished() ), this, SLOT( lookupFinished() ) ); m_queries.insert( reply ); + + m_expandJob = new DropJobNotifier( pixmap(), "shortened", DropJob::Track, reply ); + JobStatusView::instance()->model()->addJob( m_expandJob ); + } void @@ -78,6 +92,9 @@ ShortenedLinkParser::lookupFinished() QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() ); Q_ASSERT( r ); + if ( r->error() != QNetworkReply::NoError ) + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Network error parsing shortened link!" ) ) ); + QVariant redir = r->attribute( QNetworkRequest::RedirectionTargetAttribute ); if ( redir.isValid() && !redir.toUrl().isEmpty() ) { @@ -89,7 +106,7 @@ ShortenedLinkParser::lookupFinished() else { tLog() << "Got a redirected url:" << r->url().toString(); - m_links << r->url().toString(); + m_links << r->url().toString(); m_queries.remove( r ); r->deleteLater(); checkFinished(); @@ -108,3 +125,13 @@ ShortenedLinkParser::checkFinished() deleteLater(); } } + + +QPixmap +ShortenedLinkParser::pixmap() +{ + if ( !s_pixmap ) + s_pixmap = new QPixmap( RESPATH "images/add.png" ); + + return *s_pixmap; +} diff --git a/src/libtomahawk/utils/shortenedlinkparser.h b/src/libtomahawk/utils/shortenedlinkparser.h index 4fd358749..8c503acd3 100644 --- a/src/libtomahawk/utils/shortenedlinkparser.h +++ b/src/libtomahawk/utils/shortenedlinkparser.h @@ -26,12 +26,15 @@ #include <QObject> #include <QSet> #include <QStringList> +#include <QPixmap> class QNetworkReply; namespace Tomahawk { +class DropJobNotifier; + /** * Small class to parse whitelisted shortened links into the redirected urls * @@ -58,8 +61,13 @@ private: void lookupUrl( const QString& url ); void checkFinished(); + static QPixmap pixmap(); + QStringList m_links; QSet< QNetworkReply* > m_queries; + DropJobNotifier* m_expandJob; + + static QPixmap* s_pixmap; }; } diff --git a/src/libtomahawk/utils/spotifyparser.cpp b/src/libtomahawk/utils/spotifyparser.cpp index cba486f2d..49c86081a 100644 --- a/src/libtomahawk/utils/spotifyparser.cpp +++ b/src/libtomahawk/utils/spotifyparser.cpp @@ -26,6 +26,7 @@ #include "dropjob.h" #include "jobview/JobStatusView.h" #include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" #include "dropjobnotifier.h" #include "viewmanager.h" @@ -220,6 +221,7 @@ SpotifyParser::spotifyBrowseFinished() } else { + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( tr( "Error fetching Spotify information from the network!" ) ) ); tLog() << "Error in network request to Spotify for track decoding:" << r->errorString(); } diff --git a/src/libtomahawk/utils/tomahawkutils.cpp b/src/libtomahawk/utils/tomahawkutils.cpp index 367164955..640b075a5 100644 --- a/src/libtomahawk/utils/tomahawkutils.cpp +++ b/src/libtomahawk/utils/tomahawkutils.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -533,10 +534,12 @@ newerVersion( const QString& oldVersion, const QString& newVersion ) QList< Tomahawk::query_ptr > -mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tomahawk::query_ptr >& newTracks ) +mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tomahawk::query_ptr >& newTracks, bool& changed ) { int sameCount = 0; QList< Tomahawk::query_ptr > tosave = newTracks; + changed = false; + foreach ( const Tomahawk::query_ptr& newquery, newTracks ) { foreach ( const Tomahawk::query_ptr& oldq, orig ) @@ -558,6 +561,7 @@ mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tom if ( orig.size() == newTracks.size() && sameCount == orig.size() ) return orig; + changed = true; return tosave; } diff --git a/src/libtomahawk/utils/tomahawkutils.h b/src/libtomahawk/utils/tomahawkutils.h index fc2dcf4a1..8b3252a98 100644 --- a/src/libtomahawk/utils/tomahawkutils.h +++ b/src/libtomahawk/utils/tomahawkutils.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -42,6 +43,18 @@ namespace TomahawkUtils MediaTypeTrack }; + enum ImageType + { + DefaultAlbumCover, + DefaultArtistImage + }; + enum ImageMode + { + NoDefaultCover, + CoverInCase, + ScaledCover + }; + class DLLEXPORT NetworkProxyFactory : public QNetworkProxyFactory { public: @@ -98,7 +111,7 @@ namespace TomahawkUtils * * \return true if some changes were made, false if the new tracks are the same as the current tracks in \param orig */ - DLLEXPORT QList< Tomahawk::query_ptr > mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tomahawk::query_ptr >& newTracks ); + DLLEXPORT QList< Tomahawk::query_ptr > mergePlaylistChanges( const QList< Tomahawk::query_ptr >& orig, const QList< Tomahawk::query_ptr >& newTracks, bool& changed ); DLLEXPORT void crash(); } diff --git a/src/libtomahawk/utils/tomahawkutilsgui.cpp b/src/libtomahawk/utils/tomahawkutilsgui.cpp index 4d1d419d5..168e5915c 100644 --- a/src/libtomahawk/utils/tomahawkutilsgui.cpp +++ b/src/libtomahawk/utils/tomahawkutilsgui.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -19,6 +20,7 @@ #include "config.h" #include "tomahawkutilsgui.h" +#include "logger.h" #include <QtGui/QLayout> #include <QtGui/QPainter> #include <QtGui/QPixmap> @@ -36,6 +38,7 @@ #include <windowsx.h> #endif + namespace TomahawkUtils { static int s_headerHeight = 0; @@ -83,15 +86,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 +306,60 @@ 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..1790aeb9b 100644 --- a/src/libtomahawk/utils/tomahawkutilsgui.h +++ b/src/libtomahawk/utils/tomahawkutilsgui.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@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 @@ -19,6 +20,8 @@ #ifndef TOMAHAWKUTILSGUI_H #define TOMAHAWKUTILSGUI_H +#include <QSize> + #include "tomahawkutils.h" #include "dllmacro.h" @@ -46,6 +49,9 @@ 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/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 570213c41..29fdbfe67 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -1,7 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> - * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> + * Copyright 2011-2012, 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 @@ -26,6 +26,12 @@ #include "utils/tomahawkutils.h" #include "utils/logger.h" +#ifndef ENABLE_HEADLESS +#include "jobview/JobStatusView.h" +#include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" +#endif + #include "sourcelist.h" #include "playlist.h" #include <XspfUpdater.h> @@ -33,6 +39,22 @@ using namespace Tomahawk; +QString +XSPFLoader::errorToString( XSPFErrorCode error ) +{ + switch ( error ) + { + case ParseError: + return tr( "Failed to parse contents of XSPF playlist" ); + case InvalidTrackError: + return tr( "Some playlist entries were found without artist and track name, they will be omitted"); + case FetchError: + return tr( "Failed to fetch the desired playlist from the network, or the desired file does not exist" ); + default: + return QString(); + } +} + XSPFLoader::XSPFLoader( bool autoCreate, bool autoUpdate, QObject *parent ) : QObject( parent ) , m_autoCreate( autoCreate ) @@ -98,6 +120,9 @@ void XSPFLoader::reportError() { emit error( FetchError ); +#ifndef ENABLE_HEADLESS + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorToString( FetchError) ) ); +#endif deleteLater(); } diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index 055e76dbf..eee77de96 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -1,7 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> - * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> + * Copyright 2011-2012, 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 @@ -49,6 +49,8 @@ public: void setOverrideTitle( const QString& newTitle ); void setAutoResolveTracks( bool autoResolve ) { m_autoResolve = autoResolve; } + static QString errorToString( XSPFErrorCode error ); + signals: void error( XSPFLoader::XSPFErrorCode error ); void ok( const Tomahawk::playlist_ptr& ); diff --git a/src/libtomahawk/viewmanager.cpp b/src/libtomahawk/viewmanager.cpp index 0816d8203..ef8008773 100644 --- a/src/libtomahawk/viewmanager.cpp +++ b/src/libtomahawk/viewmanager.cpp @@ -126,6 +126,10 @@ ViewManager::ViewManager( QObject* parent ) ViewManager::~ViewManager() { saveCurrentPlaylistSettings(); + delete m_whatsHotWidget; + delete m_welcomeWidget; + delete m_topLovedWidget; + delete m_contextWidget; delete m_widget; } @@ -371,7 +375,8 @@ ViewManager::showSuperCollection() } } - m_superCollectionModel->setTitle( tr( "All available tracks" ) ); + m_superCollectionModel->setTitle( tr( "SuperCollection" ) ); + m_superCollectionModel->setDescription( tr( "Combined libraries of all your online friends" ) ); m_superAlbumModel->setTitle( tr( "All available albums" ) ); ViewPage* shown = 0; @@ -432,7 +437,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/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/libtomahawk/widgets/animatedcounterlabel.cpp b/src/libtomahawk/widgets/animatedcounterlabel.cpp new file mode 100644 index 000000000..f3b46f516 --- /dev/null +++ b/src/libtomahawk/widgets/animatedcounterlabel.cpp @@ -0,0 +1,106 @@ +/* === 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 + * 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 "animatedcounterlabel.h" + + +AnimatedCounterLabel::AnimatedCounterLabel(QWidget* parent, Qt::WindowFlags f) + : QLabel( parent, f ) + , m_displayed( 0 ) + , m_val( 0 ) + , m_oldval( 0 ) + , m_format( "%L1" ) +{ + connect( &m_timer, SIGNAL( frameChanged( int ) ), SLOT( frame( int ) ) ); + connect( &m_timer, SIGNAL( finished() ), SLOT( showDiff() ) ); +} + + +void AnimatedCounterLabel::setFormat(const QString& f) +{ + m_format = f; + setText( m_format.arg( m_displayed ) ); +} + + +void AnimatedCounterLabel::setVisible(bool b) +{ + QLabel::setVisible( b ); + if ( !m_diff.isNull() ) + m_diff.data()->setVisible( b ); +} + + +void AnimatedCounterLabel::frame(int f) +{ + m_displayed = f; + QLabel::setText( m_format.arg( m_displayed ) ); + QLabel::update(); +} + + +void AnimatedCounterLabel::setVal(unsigned int v) +{ + if( v == m_val ) + return; + + m_oldval = m_val; + m_val = v; + m_timer.stop(); + unsigned int dur = 1000; + unsigned int r = abs( v - m_oldval ); + + if( r > 1000 ) dur = 1500; + else if( r > 10000 ) dur = 2000; + else if( r > 25000 ) dur = 2250; + else if( r > 50000 ) dur = 2750; + else if( r > 100000 ) dur = 3000; + else if( r > 500000 ) dur = 5000; + + m_timer.setDuration( dur ); + m_timer.setFrameRange( m_displayed, v ); + m_timer.setEasingCurve( QEasingCurve( QEasingCurve::OutCubic ) ); + m_timer.start(); +} + + +void AnimatedCounterLabel::showDiff() +{ + int differ = m_val - m_oldval; + m_diff = new QLabel( QString("%1 %L2" ).arg( differ > 0 ? "+" : "" ) + .arg( (int)m_val - (int)m_oldval ), + this->parentWidget() ); + + m_diff.data()->setStyleSheet( "font-size:9px; color:grey;" ); + m_diff.data()->move( QPoint( this->pos().x(), this->pos().y() ) ); + QPropertyAnimation* a = new QPropertyAnimation( m_diff.data(), "pos" ); + a->setEasingCurve( QEasingCurve( QEasingCurve::InQuad ) ); + a->setStartValue( m_diff.data()->pos() + QPoint( 0, -10 ) ); + a->setEndValue( QPoint( m_diff.data()->pos().x(), m_diff.data()->pos().y() - 25 ) ); + a->setDuration( 1000 ); + // qDebug() << "ANIMATING DIFF:" << a->startValue() << a->endValue(); + + connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( hide() ) ); + connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( deleteLater() ) ); + connect( a, SIGNAL( finished() ), a, SLOT( deleteLater() ) ); + + m_diff.data()->show(); + m_diff.data()->setVisible( this->isVisible() ); + a->start(); +} diff --git a/src/libtomahawk/widgets/animatedcounterlabel.h b/src/libtomahawk/widgets/animatedcounterlabel.h index 39e102140..a934b31df 100644 --- a/src/libtomahawk/widgets/animatedcounterlabel.h +++ b/src/libtomahawk/widgets/animatedcounterlabel.h @@ -32,86 +32,15 @@ class DLLEXPORT AnimatedCounterLabel : public QLabel Q_OBJECT public: - explicit AnimatedCounterLabel( QWidget* parent = 0, Qt::WindowFlags f = 0 ) - : QLabel( parent, f ) - , m_displayed( 0 ) - , m_val( 0 ) - , m_oldval( 0 ) - , m_format( "%L1" ) - { - connect( &m_timer, SIGNAL( frameChanged( int ) ), SLOT( frame( int ) ) ); - connect( &m_timer, SIGNAL( finished() ), SLOT( showDiff() ) ); - } + explicit AnimatedCounterLabel( QWidget* parent = 0, Qt::WindowFlags f = 0 ); - void setFormat( const QString& f ) - { - m_format = f; - setText( m_format.arg( m_displayed ) ); - } + void setFormat( const QString& f ); public slots: - void setVisible( bool b ) - { - QLabel::setVisible( b ); - if ( !m_diff.isNull() ) - m_diff.data()->setVisible( b ); - } - - void frame( int f ) - { - m_displayed = f; - QLabel::setText( m_format.arg( m_displayed ) ); - QLabel::update(); - } - - void setVal( unsigned int v ) - { - if( v == m_val ) - return; - - m_oldval = m_val; - m_val = v; - m_timer.stop(); - unsigned int dur = 1000; - unsigned int r = abs( v - m_oldval ); - - if( r > 1000 ) dur = 1500; - else if( r > 10000 ) dur = 2000; - else if( r > 25000 ) dur = 2250; - else if( r > 50000 ) dur = 2750; - else if( r > 100000 ) dur = 3000; - else if( r > 500000 ) dur = 5000; - - m_timer.setDuration( dur ); - m_timer.setFrameRange( m_displayed, v ); - m_timer.setEasingCurve( QEasingCurve( QEasingCurve::OutCubic ) ); - m_timer.start(); - } - - void showDiff() - { - int differ = m_val - m_oldval; - m_diff = new QLabel( QString("%1 %L2" ).arg( differ > 0 ? "+" : "" ) - .arg( (int)m_val - (int)m_oldval ), - this->parentWidget() ); - - m_diff.data()->setStyleSheet( "font-size:9px; color:grey;" ); - m_diff.data()->move( QPoint( this->pos().x(), this->pos().y() ) ); - QPropertyAnimation* a = new QPropertyAnimation( m_diff.data(), "pos" ); - a->setEasingCurve( QEasingCurve( QEasingCurve::InQuad ) ); - a->setStartValue( m_diff.data()->pos() + QPoint( 0, -10 ) ); - a->setEndValue( QPoint( m_diff.data()->pos().x(), m_diff.data()->pos().y() - 25 ) ); - a->setDuration( 1000 ); - // qDebug() << "ANIMATING DIFF:" << a->startValue() << a->endValue(); - - connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( hide() ) ); - connect( a, SIGNAL( finished() ), m_diff.data(), SLOT( deleteLater() ) ); - connect( a, SIGNAL( finished() ), a, SLOT( deleteLater() ) ); - - m_diff.data()->show(); - m_diff.data()->setVisible( this->isVisible() ); - a->start(); - } + void setVisible( bool b ); + void frame( int f ); + void setVal( unsigned int v ); + void showDiff(); private: QTimeLine m_timer; diff --git a/src/libtomahawk/widgets/checkdirtree.cpp b/src/libtomahawk/widgets/checkdirtree.cpp index ee4bdc86f..2f5df909f 100644 --- a/src/libtomahawk/widgets/checkdirtree.cpp +++ b/src/libtomahawk/widgets/checkdirtree.cpp @@ -22,6 +22,7 @@ #include "utils/logger.h" #include "tomahawksettings.h" +#include <QCoreApplication> #include <QProcess> static QString s_macVolumePath = "/Volumes"; @@ -30,40 +31,49 @@ CheckDirModel::CheckDirModel( QWidget* parent ) : QFileSystemModel( parent ) , m_shownVolumes( false ) { -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC + m_setFilePath = QString( "%1/SetFile" ) .arg( QCoreApplication::applicationDirPath() ); + m_getFileInfoPath = QString( "%1/GetFileInfo" ).arg( QCoreApplication::applicationDirPath() ); + QProcess* checkVolumeVisible = new QProcess( this ); connect( checkVolumeVisible, SIGNAL( readyReadStandardOutput() ), this, SLOT( getFileInfoResult() ) ); - checkVolumeVisible->start( "GetFileInfo", QStringList() << "-aV" << s_macVolumePath ); + qDebug() << "Running GetFileInfo:" << m_getFileInfoPath << "-aV" << s_macVolumePath; + checkVolumeVisible->start( m_getFileInfoPath, QStringList() << "-aV" << s_macVolumePath ); #endif } CheckDirModel::~CheckDirModel() { -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC // reset to previous state if ( m_shownVolumes ) - QProcess::startDetached( QString( "SetFile -a V %1" ).arg( s_macVolumePath ) ); + QProcess::startDetached( QString( "%1 -a V %2" ).arg( m_setFilePath).arg( s_macVolumePath ) ); #endif } void CheckDirModel::getFileInfoResult() { -#ifdef Q_WS_MAC +#ifdef Q_OS_MAC QProcess* p = qobject_cast< QProcess* >( sender() ); Q_ASSERT( p ); QByteArray res = p->readAll().trimmed(); + qDebug() << "Got output from GetFileInfo:" << res; // 1 means /Volumes is hidden, so we show it while the dialog is visible if ( res == "1" ) { // Remove the hidden flag for the /Volumnes folder so all mount points are visible in the default (Q)FileSystemModel - QProcess* p = new QProcess( this ); - connect( p, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( volumeShowFinished() ) ); - p->start( QString( "SetFile -a v %1" ).arg( s_macVolumePath ) ); + QProcess* showProcess = new QProcess( this ); + qDebug() << "Running SetFile:" << QString( "%1 -a v %2" ).arg( m_setFilePath ).arg( s_macVolumePath ); + showProcess->start( QString( "%1 -a v %2" ).arg( m_setFilePath ).arg( s_macVolumePath ) ); + connect( showProcess, SIGNAL( readyReadStandardError() ), this, SLOT( processErrorOutput() ) ); m_shownVolumes = true; + + QTimer::singleShot( 500, this, SLOT( volumeShowFinished() ) ); } + p->terminate(); p->deleteLater(); #endif } @@ -74,6 +84,16 @@ CheckDirModel::volumeShowFinished() reset(); } + +void +CheckDirModel::processErrorOutput() +{ + QProcess* p = qobject_cast< QProcess* >( sender() ); + Q_ASSERT( p ); + qDebug() << "Got ERROR OUTPUT from subprocess in CheckDirModel:" << p->readAll(); +} + + Qt::ItemFlags CheckDirModel::flags( const QModelIndex& index ) const { diff --git a/src/libtomahawk/widgets/checkdirtree.h b/src/libtomahawk/widgets/checkdirtree.h index 3c8119534..4b1871d2f 100644 --- a/src/libtomahawk/widgets/checkdirtree.h +++ b/src/libtomahawk/widgets/checkdirtree.h @@ -46,11 +46,13 @@ signals: private slots: void getFileInfoResult(); void volumeShowFinished(); - + void processErrorOutput(); private: QHash<QPersistentModelIndex, Qt::CheckState> m_checkTable; bool m_shownVolumes; + QString m_setFilePath; + QString m_getFileInfoPath; }; diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index 62b048704..af76743c7 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -61,20 +61,20 @@ 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::DefaultAlbumCover, TomahawkUtils::ScaledCover, QSize( 48, 48 ) ); m_button = new OverlayButton( ui->tracksView ); m_button->setCheckable( true ); m_button->setChecked( m_tracksModel->mode() == InfoSystemMode ); if ( m_button->isChecked() ) - m_button->setText( tr( "Click to show Super Collection Tracks" ) ); + m_button->setText( tr( "Click to show SuperCollection Tracks" ) ); else m_button->setText( tr( "Click to show Official Tracks" ) ); m_buttonAlbums = new OverlayButton( ui->albumsView ); m_buttonAlbums->setCheckable( true ); m_buttonAlbums->setChecked( true ); - m_buttonAlbums->setText( tr( "Click to show Super Collection Albums" ) ); + m_buttonAlbums->setText( tr( "Click to show SuperCollection Albums" ) ); m_buttonAlbums->show(); connect( m_button, SIGNAL( clicked() ), SLOT( onModeToggle() ) ); @@ -113,7 +113,7 @@ AlbumInfoWidget::setMode( ModelMode mode ) onModeToggle(); if ( mode == InfoSystemMode ) - m_button->setText( tr( "Click to show Super Collection Tracks" ) ); + m_button->setText( tr( "Click to show SuperCollection Tracks" ) ); else m_button->setText( tr( "Click to show Official Tracks" ) ); } @@ -131,7 +131,7 @@ void AlbumInfoWidget::onAlbumsModeToggle() { if ( m_buttonAlbums->isChecked() ) - m_buttonAlbums->setText( tr( "Click to show Super Collection Albums" ) ); + m_buttonAlbums->setText( tr( "Click to show SuperCollection Albums" ) ); else m_buttonAlbums->setText( tr( "Click to show Official Albums" ) ); @@ -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..bde6dd54e 100644 --- a/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/ArtistInfoWidget.cpp @@ -36,6 +36,8 @@ #include "widgets/OverlayButton.h" #include "widgets/overlaywidget.h" +#include "pipeline.h" + using namespace Tomahawk; @@ -77,10 +79,10 @@ 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" ) ); + m_button->setText( tr( "Click to show SuperCollection Albums" ) ); m_button->setCheckable( true ); m_button->setChecked( true ); @@ -119,7 +121,7 @@ ArtistInfoWidget::setMode( ModelMode mode ) onModeToggle(); if ( mode == InfoSystemMode ) - m_button->setText( tr( "Click to show Super Collection Albums" ) ); + m_button->setText( tr( "Click to show SuperCollection Albums" ) ); else m_button->setText( tr( "Click to show Official Albums" ) ); } @@ -260,7 +262,8 @@ ArtistInfoWidget::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestD int i = 0; foreach ( const QString& track, tracks ) { - queries << Query::get( m_artist->name(), track, QString(), uuid() ); + queries << Query::get( m_artist->name(), track, QString() ); + Pipeline::instance()->resolve( queries ); if ( ++i == 15 ) break; @@ -289,10 +292,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/libtomahawk/widgets/searchwidget.cpp b/src/libtomahawk/widgets/searchwidget.cpp index c7a547333..0ccbda4ab 100644 --- a/src/libtomahawk/widgets/searchwidget.cpp +++ b/src/libtomahawk/widgets/searchwidget.cpp @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> + * Copyright 2012 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 @@ -112,9 +113,26 @@ SearchWidget::changeEvent( QEvent* e ) } +Tomahawk::playlistinterface_ptr +SearchWidget::playlistInterface() const +{ + return ui->resultsView->playlistInterface(); +} + + +bool +SearchWidget::jumpToCurrentTrack() +{ + return ui->resultsView->jumpToCurrentTrack(); +} + + void SearchWidget::onResultsFound( const QList<Tomahawk::result_ptr>& results ) { + QList<Tomahawk::artist_ptr> artists; + QList<Tomahawk::album_ptr> albums; + foreach( const Tomahawk::result_ptr& result, results ) { if ( !result->collection().isNull() && !result->isOnline() ) @@ -128,7 +146,13 @@ SearchWidget::onResultsFound( const QList<Tomahawk::result_ptr>& results ) q->addResults( rl ); m_resultsModel->append( q ); + + artists << result->artist(); + albums << result->album(); } + + m_artistsModel->addArtists( artists ); + m_albumsModel->addAlbums( albums ); } diff --git a/src/libtomahawk/widgets/searchwidget.h b/src/libtomahawk/widgets/searchwidget.h index b16f3611f..2da9eeb0d 100644 --- a/src/libtomahawk/widgets/searchwidget.h +++ b/src/libtomahawk/widgets/searchwidget.h @@ -1,6 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> + * Copyright 2012 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 @@ -46,7 +47,7 @@ public: ~SearchWidget(); virtual QWidget* widget() { return this; } - virtual Tomahawk::playlistinterface_ptr playlistInterface() const { return Tomahawk::playlistinterface_ptr(); } + virtual Tomahawk::playlistinterface_ptr playlistInterface() const; virtual bool isTemporaryPage() const { return true; } virtual QString title() const { return QString( tr( "Search: %1" ) ).arg( m_search ); } @@ -55,7 +56,7 @@ public: virtual bool showStatsBar() const { return false; } - virtual bool jumpToCurrentTrack() { return false; } + virtual bool jumpToCurrentTrack(); protected: void changeEvent( QEvent* e ); diff --git a/src/libtomahawk/widgets/whatshotwidget.cpp b/src/libtomahawk/widgets/whatshotwidget.cpp index bedad456a..c4c9ecfb4 100644 --- a/src/libtomahawk/widgets/whatshotwidget.cpp +++ b/src/libtomahawk/widgets/whatshotwidget.cpp @@ -108,6 +108,8 @@ WhatsHotWidget::WhatsHotWidget( QWidget* parent ) WhatsHotWidget::~WhatsHotWidget() { + qDeleteAll( m_workers ); + m_workers.clear(); m_workerThread->exit(0); m_playlistInterface.clear(); delete ui; @@ -485,6 +487,7 @@ WhatsHotWidget::chartArtistsLoaded( ChartDataLoader* loader, const QList< artist } } + m_workers.remove( loader ); loader->deleteLater(); } @@ -502,6 +505,7 @@ WhatsHotWidget::chartTracksLoaded( ChartDataLoader* loader, const QList< query_p m_trackModels[ chartId ]->append( tracks ); } + m_workers.remove( loader ); loader->deleteLater(); } @@ -515,5 +519,6 @@ WhatsHotWidget::chartAlbumsLoaded( ChartDataLoader* loader, const QList< album_p if ( m_albumModels.contains( chartId ) ) m_albumModels[ chartId ]->addAlbums( albums ); + m_workers.remove( loader ); loader->deleteLater(); } diff --git a/src/libtomahawk/widgets/whatshotwidget.h b/src/libtomahawk/widgets/whatshotwidget.h index 6e6801059..55e30e283 100644 --- a/src/libtomahawk/widgets/whatshotwidget.h +++ b/src/libtomahawk/widgets/whatshotwidget.h @@ -50,6 +50,7 @@ namespace Tomahawk { class ChartDataLoader; class ChartsPlaylistInterface; + class ChartDataLoader; } /** @@ -111,6 +112,7 @@ private: // {Artist,Album,Track}::get() calls are all synchronous db calls // and we don't want to lock up out UI in case the db is busy (e.g. on startup) QThread* m_workerThread; + QSet< Tomahawk::ChartDataLoader* > m_workers; // Cache our model data QHash< QString, AlbumModel* > m_albumModels; diff --git a/src/main.cpp b/src/main.cpp index 4ad4ca4d4..f25a34a58 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -124,7 +124,7 @@ main( int argc, char *argv[] ) #endif #ifndef ENABLE_HEADLESS - // new BreakPad( QDir::tempPath(), TomahawkSettings::instance()->crashReporterEnabled() ); + new BreakPad( QDir::tempPath(), TomahawkSettings::instance()->crashReporterEnabled() ); #endif KDSingleApplicationGuard guard( &a, KDSingleApplicationGuard::AutoKillOtherInstances ); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index ddd954efa..62e03e36f 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -20,8 +20,6 @@ #include "settingsdialog.h" #include "config.h" -#include "utils/tomahawkutilsgui.h" - #include <QDesktopServices> #include <QFileDialog> #include <QMessageBox> @@ -38,6 +36,8 @@ #include "pipeline.h" #include "resolver.h" #include "ExternalResolverGui.h" +#include "utils/tomahawkutilsgui.h" +#include "guihelpers.h" #include "scanmanager.h" #include "settingslistdelegate.h" #include "AccountDelegate.h" @@ -123,7 +123,7 @@ SettingsDialog::SettingsDialog( QWidget *parent ) ui->accountsFilterCombo->addItem( tr( "All" ), Accounts::NoType ); ui->accountsFilterCombo->addItem( accountTypeToString( SipType ), SipType ); ui->accountsFilterCombo->addItem( accountTypeToString( ResolverType ), ResolverType ); - ui->accountsFilterCombo->addItem( accountTypeToString( InfoType ), InfoType ); + ui->accountsFilterCombo->addItem( accountTypeToString( StatusPushType ), StatusPushType ); connect( ui->accountsFilterCombo, SIGNAL( activated( int ) ), this, SLOT( accountsFilterChanged( int ) ) ); @@ -234,7 +234,7 @@ SettingsDialog::createIcons() QFontMetrics fm( font() ); QListWidgetItem *accountsButton = new QListWidgetItem( ui->listWidget ); accountsButton->setIcon( QIcon( RESPATH "images/account-settings.png" ) ); - accountsButton->setText( tr( "Accounts" ) ); + accountsButton->setText( tr( "Services" ) ); accountsButton->setTextAlignment( Qt::AlignHCenter ); accountsButton->setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled ); maxlen = fm.width( accountsButton->text() ); @@ -343,61 +343,6 @@ SettingsDialog::accountsFilterChanged( int ) } -void -SettingsDialog::openAccountConfig( Account* account, bool showDelete ) -{ - if( account->configurationWidget() ) - { -#ifndef Q_OS_MAC - DelegateConfigWrapper dialog( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), this ); - dialog.setShowDelete( showDelete ); - QWeakPointer< DelegateConfigWrapper > watcher( &dialog ); - int ret = dialog.exec(); - if ( !watcher.isNull() && dialog.deleted() ) - { - AccountManager::instance()->removeAccount( account ); - } - else if( !watcher.isNull() && ret == QDialog::Accepted ) - { - // send changed config to resolver - account->saveConfig(); - } -#else - // on osx a sheet needs to be non-modal - DelegateConfigWrapper* dialog = new DelegateConfigWrapper( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), this, Qt::Sheet ); - dialog->setShowDelete( showDelete ); - dialog->setProperty( "accountplugin", QVariant::fromValue< QObject* >( account ) ); - connect( dialog, SIGNAL( finished( int ) ), this, SLOT( accountConfigClosed( int ) ) ); - connect( dialog, SIGNAL( closedWithDelete() ), this, SLOT( accountConfigDelete() ) ); - - dialog->show(); -#endif - } -} - - -void -SettingsDialog::accountConfigClosed( int value ) -{ - if( value == QDialog::Accepted ) - { - DelegateConfigWrapper* dialog = qobject_cast< DelegateConfigWrapper* >( sender() ); - Account* account = qobject_cast< Account* >( dialog->property( "accountplugin" ).value< QObject* >() ); - account->saveConfig(); - } -} - - -void -SettingsDialog::accountConfigDelete() -{ - DelegateConfigWrapper* dialog = qobject_cast< DelegateConfigWrapper* >( sender() ); - Account* account = qobject_cast< Account* >( dialog->property( "accountplugin" ).value< QObject* >() ); - Q_ASSERT( account ); - AccountManager::instance()->removeAccount( account ); -} - - void SettingsDialog::openAccountFactoryConfig( AccountFactory* factory ) { @@ -421,14 +366,10 @@ SettingsDialog::openAccountFactoryConfig( AccountFactory* factory ) AccountFactoryWrapper dialog( factory, this ); QWeakPointer< AccountFactoryWrapper > watcher( &dialog ); - int ret = dialog.exec(); - if ( !watcher.isNull() && dialog.doCreateAccount() ) - createAccountFromFactory( factory ); + dialog.exec(); #else // on osx a sheet needs to be non-modal AccountFactoryWrapper* dialog = new AccountFactoryWrapper( factory, this ); - connect( dialog, SIGNAL( createAccount( Tomahawk::Accounts::AccountFactory* ) ), this, SLOT( createAccountFromFactory( Tomahawk::Accounts::AccountFactory* ) ) ); - dialog->show(); #endif } @@ -437,84 +378,14 @@ SettingsDialog::openAccountFactoryConfig( AccountFactory* factory ) void SettingsDialog::createAccountFromFactory( AccountFactory* factory ) { -#ifdef Q_WS_MAC - // On mac we need to close the dialog we came from before showing another dialog - Q_ASSERT( sender() && qobject_cast< AccountFactoryWrapper* >( sender() ) ); - AccountFactoryWrapper* dialog = qobject_cast< AccountFactoryWrapper* >( sender() ); - dialog->accept(); -#endif - - //if exited with OK, create it, if not, delete it immediately! - Account* account = factory->createAccount(); - bool added = false; - if( account->configurationWidget() ) - { -#ifdef Q_WS_MAC - // on osx a sheet needs to be non-modal - DelegateConfigWrapper* dialog = new DelegateConfigWrapper( account->configurationWidget(), QString("%1 Config" ).arg( account->accountFriendlyName() ), this, Qt::Sheet ); - dialog->setProperty( "accountplugin", QVariant::fromValue< QObject* >( account ) ); - connect( dialog, SIGNAL( finished( int ) ), this, SLOT( accountCreateConfigClosed( int ) ) ); - - if( account->configurationWidget()->metaObject()->indexOfSignal( "dataError(bool)" ) > -1 ) - connect( account->configurationWidget(), SIGNAL( dataError( bool ) ), dialog, SLOT( toggleOkButton( bool ) ), Qt::UniqueConnection ); - - dialog->show(); -#else - DelegateConfigWrapper dialog( account->configurationWidget(), QString("%1 Config" ).arg( account->accountFriendlyName() ), this ); - QWeakPointer< DelegateConfigWrapper > watcher( &dialog ); - - if( account->configurationWidget()->metaObject()->indexOfSignal( "dataError(bool)" ) > -1 ) - connect( account->configurationWidget(), SIGNAL( dataError( bool ) ), &dialog, SLOT( toggleOkButton( bool ) ), Qt::UniqueConnection ); - - int ret = dialog.exec(); - if( !watcher.isNull() && ret == QDialog::Accepted ) // send changed config to account - added = true; - else // canceled, delete it - added = false; - - handleAccountAdded( account, added ); -#endif - } - else - { - // no config, so just add it - added = true; - handleAccountAdded( account, added ); - } + TomahawkUtils::createAccountFromFactory( factory, this ); } void -SettingsDialog::accountCreateConfigClosed( int finished ) +SettingsDialog::openAccountConfig( Account* account, bool showDelete ) { - DelegateConfigWrapper* dialog = qobject_cast< DelegateConfigWrapper* >( sender() ); - Account* account = qobject_cast< Account* >( dialog->property( "accountplugin" ).value< QObject* >() ); - Q_ASSERT( account ); - - bool added = ( finished == QDialog::Accepted ); - - handleAccountAdded( account, added ); -} - - -void -SettingsDialog::handleAccountAdded( Account* account, bool added ) -{ - if ( added ) - { - account->setEnabled( true ); - account->setAutoConnect( true ); - account->saveConfig(); - - TomahawkSettings::instance()->addAccount( account->accountId() ); - AccountManager::instance()->addAccount( account ); - AccountManager::instance()->hookupAndEnable( account ); - } - else - { - // user pressed cancel - delete account; - } + TomahawkUtils::openAccountConfig( account, this, showDelete ); } diff --git a/src/settingsdialog.h b/src/settingsdialog.h index ca146efb3..cb02666bd 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -89,12 +89,8 @@ private slots: void accountsFilterChanged( int ); void createAccountFromFactory( Tomahawk::Accounts::AccountFactory* ); - void openAccountConfig( Tomahawk::Accounts::Account*, bool showDelete = false ); void openAccountFactoryConfig( Tomahawk::Accounts::AccountFactory* ); - void accountConfigClosed( int value ); - void accountConfigDelete(); - void accountCreateConfigClosed( int value ); void installFromFile(); void scrollTo( const QModelIndex& ); @@ -108,7 +104,6 @@ private slots: private: void createIcons(); - void handleAccountAdded( Tomahawk::Accounts::Account* p, bool added ); Ui_StackedSettingsDialog* ui; diff --git a/src/sourcetree/items/categoryitems.cpp b/src/sourcetree/items/categoryitems.cpp index cd9a89db9..4be8e182f 100644 --- a/src/sourcetree/items/categoryitems.cpp +++ b/src/sourcetree/items/categoryitems.cpp @@ -120,6 +120,7 @@ CategoryAddItem::flags() const case SourcesModel::PlaylistsCategory: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; case SourcesModel::StationsCategory: + return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; default: return Qt::ItemIsEnabled; break; diff --git a/src/sourcetree/items/groupitem.cpp b/src/sourcetree/items/groupitem.cpp index 01804c0ba..20d0a20f1 100644 --- a/src/sourcetree/items/groupitem.cpp +++ b/src/sourcetree/items/groupitem.cpp @@ -28,12 +28,10 @@ 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 ) + , m_defaultExpanded( true ) { - // expand by default - QTimer::singleShot( 0, this, SLOT( requestExpanding() ) ); } @@ -56,6 +54,18 @@ GroupItem::requestExpanding() } +void +GroupItem::checkExpandedState() +{ + if ( m_defaultExpanded ) + { + // only default expand once + m_defaultExpanded = false; + requestExpanding(); + } +} + + QString GroupItem::text() const { diff --git a/src/sourcetree/items/groupitem.h b/src/sourcetree/items/groupitem.h index 5f173ac0b..823f991a7 100644 --- a/src/sourcetree/items/groupitem.h +++ b/src/sourcetree/items/groupitem.h @@ -36,9 +36,11 @@ 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; } + void checkExpandedState(); + void setDefaultExpanded( bool b ) { m_defaultExpanded = b; } + public slots: virtual void activate(); @@ -50,7 +52,7 @@ private slots: private: QString m_text; - int m_peerSortValue; + bool m_defaultExpanded; }; #endif diff --git a/src/sourcetree/items/sourceitem.cpp b/src/sourcetree/items/sourceitem.cpp index f4084b38e..82a977f7c 100644 --- a/src/sourcetree/items/sourceitem.cpp +++ b/src/sourcetree/items/sourceitem.cpp @@ -124,7 +124,7 @@ SourceItem::source() const QString SourceItem::text() const { - return m_source.isNull() ? tr( "Super Collection" ) : m_source->friendlyName(); + return m_source.isNull() ? tr( "SuperCollection" ) : m_source->friendlyName(); } @@ -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; 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..6cd00ce65 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() ) @@ -128,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 ); @@ -191,7 +195,7 @@ SourceDelegate::paintCollection( QPainter* painter, const QStyleOptionViewItem& painter->setFont( normal ); textRect = option.rect.adjusted( iconRect.width() + 8, option.rect.height() / 2, -figWidth - 24, -6 ); - + bool privacyOn = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::FullyPrivate; if ( !colItem->source().isNull() && colItem->source()->isLocal() && privacyOn ) { @@ -238,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 ) { @@ -305,7 +311,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 +331,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 ); @@ -400,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 ); @@ -511,6 +515,9 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co else { o.state &= ~QStyle::State_MouseOver; + if ( !index.parent().parent().isValid() ) + o.rect.adjust( 7, 0, 0, 0 ); + QStyledItemDelegate::paint( painter, o, index ); if ( type == SourcesModel::TemporaryPage ) @@ -532,6 +539,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co } } + paintDecorations( painter, o3, index ); + painter->restore(); } @@ -618,6 +627,12 @@ SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QSt } } + // We emit our own clicked() signal instead of relying on QTreeView's, because that is fired whether or not a delegate accepts + // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here wemake sure we only + // emit if we really want to + if ( event->type() == QEvent::MouseButtonRelease ) + emit clicked( index ); + return QStyledItemDelegate::editorEvent ( event, model, option, index ); } diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index 1f6cea627..d7a1d3ef6 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -42,6 +42,7 @@ public: SourceTreeItem::DropType hoveredDropType() const; signals: + void clicked( const QModelIndex& idx ); void latchOn( const Tomahawk::source_ptr& idx ); void latchOff( const Tomahawk::source_ptr& idx ); void toggleRealtimeLatch( const Tomahawk::source_ptr& idx, bool realtime ); diff --git a/src/sourcetree/sourcesmodel.cpp b/src/sourcetree/sourcesmodel.cpp index dfe877d96..b4dca6e55 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(); } @@ -295,10 +300,10 @@ SourcesModel::appendGroups() void SourcesModel::appendItem( const Tomahawk::source_ptr& source ) { - SourceTreeItem* parent; + GroupItem* parent; if ( !source.isNull() && source->isLocal() ) { - parent = m_rootItem; + parent = m_myMusicGroup; } else { @@ -309,6 +314,8 @@ SourcesModel::appendItem( const Tomahawk::source_ptr& source ) beginInsertRows( idx, rowCount( idx ), rowCount( idx ) ); new SourceItem( this, parent, source ); endInsertRows(); + + parent->checkExpandedState(); } 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; diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 330e8aeee..ced9bda00 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -87,6 +87,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) connect( m_delegate, SIGNAL( latchOn( Tomahawk::source_ptr ) ), SLOT( latchOnOrCatchUp( Tomahawk::source_ptr ) ) ); connect( m_delegate, SIGNAL( latchOff( Tomahawk::source_ptr ) ), SLOT( latchOff( Tomahawk::source_ptr ) ) ); connect( m_delegate, SIGNAL( toggleRealtimeLatch( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr,bool ) ) ); + connect( m_delegate, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); setItemDelegate( m_delegate ); @@ -104,7 +105,6 @@ SourceTreeView::SourceTreeView( QWidget* parent ) header()->setStretchLastSection( false ); header()->setResizeMode( 0, QHeaderView::Stretch ); - connect( this, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); connect( this, SIGNAL( expanded( QModelIndex ) ), SLOT( onItemExpanded( QModelIndex ) ) ); // connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged() ) ); @@ -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 ); @@ -431,11 +431,11 @@ SourceTreeView::latchModeToggled( bool checked ) qDebug() << Q_FUNC_INFO; if ( !m_contextMenuIndex.isValid() ) return; - + SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt(); if( type != SourcesModel::Collection ) return; - + const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex ); const source_ptr source = item->source(); emit latchModeChangeRequest( source, checked ); diff --git a/src/stackedsettingsdialog.ui b/src/stackedsettingsdialog.ui index dacee0c24..6d08a1a2b 100644 --- a/src/stackedsettingsdialog.ui +++ b/src/stackedsettingsdialog.ui @@ -85,7 +85,7 @@ <item> <widget class="QStackedWidget" name="stackedWidget"> <property name="currentIndex"> - <number>2</number> + <number>0</number> </property> <widget class="QWidget" name="accountsPage"> <layout class="QVBoxLayout" name="verticalLayout_11"> @@ -95,7 +95,7 @@ <item> <widget class="QGroupBox" name="groupBox_4"> <property name="title"> - <string>Internet Sources</string> + <string>Internet Services</string> </property> <layout class="QVBoxLayout" name="verticalLayout_8"> <property name="margin"> @@ -136,7 +136,11 @@ </layout> </item> <item> - <widget class="QListView" name="accountsView"/> + <widget class="QListView" name="accountsView"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + </widget> </item> </layout> </widget> diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index 900d56182..669a3383d 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -61,7 +61,6 @@ #include "utils/logger.h" #include "utils/tomahawkutilsgui.h" -#include <lastfm/ws.h> #include "config.h" #ifndef ENABLE_HEADLESS @@ -170,7 +169,7 @@ TomahawkApp::init() m_scanManager = QWeakPointer<ScanManager>( new ScanManager( this ) ); // init pipeline and resolver factories - new Pipeline( this ); + new Pipeline(); #ifndef ENABLE_HEADLESS Pipeline::instance()->addExternalResolverFactory( boost::bind( &QtScriptResolver::factory, _1 ) ); @@ -300,6 +299,8 @@ TomahawkApp::~TomahawkApp() { tLog() << "Shutting down Tomahawk..."; + Pipeline::instance()->stop(); + if ( !m_servent.isNull() ) delete m_servent.data(); if ( !m_scanManager.isNull() ) @@ -308,15 +309,8 @@ TomahawkApp::~TomahawkApp() if ( !m_audioEngine.isNull() ) delete m_audioEngine.data(); - if ( !m_infoSystem.isNull() ) - delete m_infoSystem.data(); - - //FIXME: delete GeneratorFactory::registerFactory( "echonest", new EchonestFactory ); ? - delete Tomahawk::Accounts::AccountManager::instance(); - Pipeline::instance()->stop(); - #ifndef ENABLE_HEADLESS delete m_mainwindow; delete AtticaManager::instance(); @@ -327,6 +321,9 @@ TomahawkApp::~TomahawkApp() delete Pipeline::instance(); + if ( !m_infoSystem.isNull() ) + delete m_infoSystem.data(); + tLog() << "Finished shutdown."; } @@ -352,6 +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( "\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" ); } @@ -465,7 +469,6 @@ TomahawkApp::initHTTP() tLog() << "Starting HTTPd on" << m_session.listenInterface().toString() << m_session.port(); m_session.start(); - } @@ -512,6 +515,7 @@ TomahawkApp::initServent() } } + // Called after Servent emits ready() void TomahawkApp::initSIP() @@ -540,10 +544,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 } @@ -603,5 +604,19 @@ TomahawkApp::instanceStarted( KDSingleApplicationGuard::Instance instance ) return; QString arg1 = instance.arguments[ 1 ]; - loadUrl( arg1 ); + if ( loadUrl( arg1 ) ) + return; + + 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(); } diff --git a/src/tomahawktrayicon.cpp b/src/tomahawktrayicon.cpp index 6a58a4e6f..b8d927ec2 100644 --- a/src/tomahawktrayicon.cpp +++ b/src/tomahawktrayicon.cpp @@ -58,6 +58,8 @@ TomahawkTrayIcon::TomahawkTrayIcon( QObject* parent ) m_showWindowAction = m_contextMenu->addAction( tr( "Hide Tomahawk Window" ) ); m_showWindowAction->setData( true ); connect( m_showWindowAction, SIGNAL( triggered() ), this, SLOT( showWindow() ) ); + + connect( m_contextMenu, SIGNAL( aboutToShow() ), this, SLOT( menuAboutToShow() ) ); #endif m_contextMenu->addSeparator(); @@ -118,6 +120,16 @@ TomahawkTrayIcon::showWindow() } +void +TomahawkTrayIcon::menuAboutToShow() +{ + // When using Cmd-H on mac to hide a window, it is an OS-level hide that is different from QWidget::hide(). + // Qt returns isVisible() == true for windows that are hidden with Cmd-H, which is weird. isActiveWindow() returns + // the proper information though. + setShowHideWindow( APP->mainWindow()->isActiveWindow() ); +} + + void TomahawkTrayIcon::setResult( const Tomahawk::result_ptr& result ) { @@ -188,6 +200,12 @@ TomahawkTrayIcon::onActivated( QSystemTrayIcon::ActivationReason reason ) } break; + case QSystemTrayIcon::MiddleClick: + { + AudioEngine::instance()->playPause(); + } + break; + default: break; } diff --git a/src/tomahawktrayicon.h b/src/tomahawktrayicon.h index 3e5de54e1..be984ae43 100644 --- a/src/tomahawktrayicon.h +++ b/src/tomahawktrayicon.h @@ -46,6 +46,7 @@ private slots: void enablePlay(); void enablePause(); + void menuAboutToShow(); private: void refreshToolTip(); ~TomahawkTrayIcon(); diff --git a/src/tomahawkwindow.cpp b/src/tomahawkwindow.cpp index 54b9fefb3..8d8c086b8 100644 --- a/src/tomahawkwindow.cpp +++ b/src/tomahawkwindow.cpp @@ -1,7 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> - * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> + * Copyright 2010-2012, 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 @@ -58,6 +58,8 @@ #include "tomahawksettings.h" #include "sourcelist.h" #include "jobview/JobStatusView.h" +#include "jobview/JobStatusModel.h" +#include "jobview/ErrorStatusMessage.h" #include "tomahawktrayicon.h" #include "scanmanager.h" #include "tomahawkapp.h" @@ -80,6 +82,7 @@ TomahawkWindow::TomahawkWindow( QWidget* parent ) , m_searchWidget( 0 ) , m_audioControls( new AudioControls( this ) ) , m_trayIcon( new TomahawkTrayIcon( this ) ) + , m_audioRetryCounter( 0 ) { setWindowIcon( QIcon( RESPATH "icons/tomahawk-icon-128x128.png" ) ); @@ -570,11 +573,17 @@ TomahawkWindow::onXSPFError( XSPFLoader::XSPFErrorCode error ) void TomahawkWindow::onAudioEngineError( AudioEngine::AudioErrorCode /* error */ ) { + QString msg; #ifdef Q_WS_X11 - QMessageBox::warning( this, tr( "Playback Error" ), tr( "Sorry, there is a problem accessing your audio device. Make sure you have a suitable Phonon backend and required plugins installed." ), QMessageBox::Ok ); + msg = tr( "Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped. Make sure you have a suitable Phonon backend and required plugins installed." ); #else - QMessageBox::warning( this, tr( "Playback Error" ), tr( "Sorry, there is a problem accessing your audio device." ), QMessageBox::Ok ); + msg = tr( "Sorry, there is a problem accessing your audio device or the desired track, current track will be skipped." ); #endif + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( msg, 15 ) ); + + if ( m_audioRetryCounter < 3 ) + AudioEngine::instance()->play(); + m_audioRetryCounter++; } @@ -654,6 +663,7 @@ TomahawkWindow::playlistCreateDialogFinished( int ret ) void TomahawkWindow::audioStarted() { + m_audioRetryCounter = 0; ui->actionPlay->setText( tr( "Pause" ) ); } diff --git a/src/tomahawkwindow.h b/src/tomahawkwindow.h index 9c8401832..adc3d98f0 100644 --- a/src/tomahawkwindow.h +++ b/src/tomahawkwindow.h @@ -1,7 +1,7 @@ /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> === * * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org> - * Copyright 2010-2011, Leo Franchi <lfranchi@kde.org> + * Copyright 2010-2012, 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 @@ -145,6 +145,7 @@ private: Tomahawk::result_ptr m_currentTrack; QString m_windowTitle; + int m_audioRetryCounter; }; #endif // TOMAHAWKWINDOW_H diff --git a/src/utils/guihelpers.cpp b/src/utils/guihelpers.cpp new file mode 100644 index 000000000..e7345e4f2 --- /dev/null +++ b/src/utils/guihelpers.cpp @@ -0,0 +1,172 @@ +/* === 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 "guihelpers.h" + +#include <QObject> + +#include "accounts/Account.h" +#include "accounts/AccountManager.h" +#include "delegateconfigwrapper.h" + +namespace TomahawkUtils +{ + +void +handleAccountAdded( Tomahawk::Accounts::Account* account, bool added ) +{ + if ( added ) + { + account->setEnabled( true ); + account->setAutoConnect( true ); + account->saveConfig(); + + TomahawkSettings::instance()->addAccount( account->accountId() ); + Tomahawk::Accounts::AccountManager::instance()->addAccount( account ); + Tomahawk::Accounts::AccountManager::instance()->hookupAndEnable( account ); + } + else + { + // user pressed cancel + delete account; + } +} + + +class UtilsObject : public QObject +{ + Q_OBJECT +public: + UtilsObject( DelegateConfigWrapper* w ) : QObject( w ), m_w( w ) {} + +public slots: + void + accountCreateConfigClosed( int ret ) + { + Tomahawk::Accounts::Account* account = qobject_cast< Tomahawk::Accounts::Account* >( m_w->property( "accountplugin" ).value< QObject* >() ); + Q_ASSERT( account ); + + bool added = ( ret == QDialog::Accepted ); + + handleAccountAdded( account, added ); + } + + void + accountConfigClosed( int ret ) + { + if( ret == QDialog::Accepted ) + { + Tomahawk::Accounts::Account* account = qobject_cast< Tomahawk::Accounts::Account* >( m_w->property( "accountplugin" ).value< QObject* >() ); + account->saveConfig(); + } + } + + void + accountConfigDelete() + { + Tomahawk::Accounts::Account* account = qobject_cast< Tomahawk::Accounts::Account* >( m_w->property( "accountplugin" ).value< QObject* >() ); + Q_ASSERT( account ); + Tomahawk::Accounts::AccountManager::instance()->removeAccount( account ); + } +private: + DelegateConfigWrapper* m_w; + +}; + + +void +createAccountFromFactory( Tomahawk::Accounts::AccountFactory* factory, QWidget* parent ) +{ + //if exited with OK, create it, if not, delete it immediately! + Tomahawk::Accounts::Account* account = factory->createAccount(); + bool added = false; + if( account->configurationWidget() ) + { + #ifdef Q_WS_MAC + // on osx a sheet needs to be non-modal + DelegateConfigWrapper* dialog = new DelegateConfigWrapper( account->configurationWidget(), QString("%1 Config" ).arg( account->accountFriendlyName() ), parent, Qt::Sheet ); + dialog->setProperty( "accountplugin", QVariant::fromValue< QObject* >( account ) ); + + UtilsObject* obj = new UtilsObject( dialog ); + QObject::connect( dialog, SIGNAL( finished( int ) ), obj, SLOT( accountCreateConfigClosed( int ) ) ); + + if( account->configurationWidget()->metaObject()->indexOfSignal( "dataError(bool)" ) > -1 ) + QObject::connect( account->configurationWidget(), SIGNAL( dataError( bool ) ), dialog, SLOT( toggleOkButton( bool ) ), Qt::UniqueConnection ); + + dialog->show(); + #else + DelegateConfigWrapper dialog( account->configurationWidget(), QString("%1 Config" ).arg( account->accountFriendlyName() ), parent ); + QWeakPointer< DelegateConfigWrapper > watcher( &dialog ); + + if( account->configurationWidget()->metaObject()->indexOfSignal( "dataError(bool)" ) > -1 ) + QObject::connect( account->configurationWidget(), SIGNAL( dataError( bool ) ), &dialog, SLOT( toggleOkButton( bool ) ), Qt::UniqueConnection ); + + int ret = dialog.exec(); + if( !watcher.isNull() && ret == QDialog::Accepted ) // send changed config to account + added = true; + else // canceled, delete it + added = false; + + handleAccountAdded( account, added ); + #endif + } + else + { + // no config, so just add it + added = true; + handleAccountAdded( account, added ); + } +} + +void +openAccountConfig( Tomahawk::Accounts::Account* account, QWidget* parent, bool showDelete ) +{ + if( account->configurationWidget() ) + { + #ifndef Q_OS_MAC + DelegateConfigWrapper dialog( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), parent ); + dialog.setShowDelete( showDelete ); + QWeakPointer< DelegateConfigWrapper > watcher( &dialog ); + int ret = dialog.exec(); + if ( !watcher.isNull() && dialog.deleted() ) + { + Tomahawk::Accounts::AccountManager::instance()->removeAccount( account ); + } + else if( !watcher.isNull() && ret == QDialog::Accepted ) + { + // send changed config to resolver + account->saveConfig(); + } + #else + // on osx a sheet needs to be non-modal + DelegateConfigWrapper* dialog = new DelegateConfigWrapper( account->configurationWidget(), QString("%1 Configuration" ).arg( account->accountFriendlyName() ), parent, Qt::Sheet ); + dialog->setShowDelete( showDelete ); + dialog->setProperty( "accountplugin", QVariant::fromValue< QObject* >( account ) ); + UtilsObject* obj = new UtilsObject( dialog ); + + QObject::connect( dialog, SIGNAL( finished( int ) ), obj, SLOT( accountConfigClosed( int ) ) ); + QObject::connect( dialog, SIGNAL( closedWithDelete() ), obj, SLOT( accountConfigDelete() ) ); + + dialog->show(); + #endif + } +} + +} // namespace TomahawkUtils + +#include "guihelpers.moc" \ No newline at end of file diff --git a/src/utils/guihelpers.h b/src/utils/guihelpers.h new file mode 100644 index 000000000..f17ccd43c --- /dev/null +++ b/src/utils/guihelpers.h @@ -0,0 +1,36 @@ +/* === 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 TOMAHAWK_GUI_HELPERS_H +#define TOMAHAWK_GUI_HELPERS_H + +class QWidget; +namespace Tomahawk { + namespace Accounts { + class AccountFactory; + class Account; + } +} + +namespace TomahawkUtils +{ + void createAccountFromFactory( Tomahawk::Accounts::AccountFactory*, QWidget* parent ); + void openAccountConfig( Tomahawk::Accounts::Account*, QWidget* parent, bool showDelete = false ); +} + +#endif \ No newline at end of file diff --git a/thirdparty/SPMediaKeyTap/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m b/thirdparty/SPMediaKeyTap/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m index 8ba4adb8f..851ea0385 100644 --- a/thirdparty/SPMediaKeyTap/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m +++ b/thirdparty/SPMediaKeyTap/SPInvocationGrabbing/NSObject+SPInvocationGrabbing.m @@ -88,11 +88,11 @@ } -(void)printBacktrace; { - int x; - for(x = 3; x < frameCount; x++) { - if(frameStrings[x] == NULL) { break; } - printf("%s\n", frameStrings[x]); - } + int x; + for(x = 3; x < frameCount; x++) { + if(frameStrings[x] == NULL) { break; } + printf("%s\n", frameStrings[x]); + } } @end diff --git a/thirdparty/SPMediaKeyTap/SPMediaKeyTap.h b/thirdparty/SPMediaKeyTap/SPMediaKeyTap.h index f33ad7040..aa974d238 100644 --- a/thirdparty/SPMediaKeyTap/SPMediaKeyTap.h +++ b/thirdparty/SPMediaKeyTap/SPMediaKeyTap.h @@ -31,4 +31,13 @@ -(void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event; @end -extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; \ No newline at end of file +#ifdef __cplusplus +extern "C" { +#endif + +extern NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey; +extern NSString *kIgnoreMediaKeysDefaultsKey; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/thirdparty/SPMediaKeyTap/SPMediaKeyTap.m b/thirdparty/SPMediaKeyTap/SPMediaKeyTap.m index a349f5922..665edc27f 100644 --- a/thirdparty/SPMediaKeyTap/SPMediaKeyTap.m +++ b/thirdparty/SPMediaKeyTap/SPMediaKeyTap.m @@ -28,6 +28,9 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv [self startWatchingAppSwitching]; singleton = self; _mediaKeyAppList = [NSMutableArray new]; + _tapThreadRL=nil; + _eventPort=nil; + _eventPortSource=nil; return self; } -(void)dealloc; @@ -58,6 +61,9 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv } -(void)startWatchingMediaKeys;{ + // Prevent having multiple mediaKeys threads + [self stopWatchingMediaKeys]; + [self setShouldInterceptMediaKeyEvents:YES]; // Add an event tap to intercept the system defined media key events @@ -78,6 +84,22 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv -(void)stopWatchingMediaKeys; { // TODO<nevyn>: Shut down thread, remove event tap port and source + + if(_tapThreadRL){ + CFRunLoopStop(_tapThreadRL); + _tapThreadRL=nil; + } + + if(_eventPort){ + CFMachPortInvalidate(_eventPort); + CFRelease(_eventPort); + _eventPort=nil; + } + + if(_eventPortSource){ + CFRelease(_eventPortSource); + _eventPortSource=nil; + } } #pragma mark - @@ -90,7 +112,9 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv return NO; #else // XXX(nevyn): MediaKey event tap doesn't work on 10.4, feel free to figure out why if you have the energy. - return floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/; + return + ![[NSUserDefaults standardUserDefaults] boolForKey:kIgnoreMediaKeysDefaultsKey] + && floor(NSAppKitVersionNumber) >= 949/*NSAppKitVersionNumber10_5*/; #endif } @@ -108,6 +132,14 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv @"com.apple.Aperture", @"com.plexsquared.Plex", @"com.soundcloud.desktop", + @"org.niltsh.MPlayerX", + @"com.ilabs.PandorasHelper", + @"com.mahasoftware.pandabar", + @"com.bitcartel.pandorajam", + @"org.clementine-player.clementine", + @"fm.last.Last.fm", + @"com.beatport.BeatportPro", + @"com.Timenut.SongKey", @"com.macromedia.fireworks", // the tap messes up their mouse input nil ]; @@ -213,6 +245,8 @@ static CGEventRef tapEventCallback(CGEventTapProxy proxy, CGEventType type, CGEv #pragma mark Task switching callbacks NSString *kMediaKeyUsingBundleIdentifiersDefaultsKey = @"SPApplicationsNeedingMediaKeys"; +NSString *kIgnoreMediaKeysDefaultsKey = @"SPIgnoreMediaKeys"; + -(void)mediaKeyAppListChanged; diff --git a/thirdparty/liblastfm2/src/CMakeLists.txt b/thirdparty/liblastfm2/src/CMakeLists.txt index f379776e0..56c203c14 100644 --- a/thirdparty/liblastfm2/src/CMakeLists.txt +++ b/thirdparty/liblastfm2/src/CMakeLists.txt @@ -87,6 +87,7 @@ endif(WIN32) qt4_wrap_cpp(MOC_SOURCES ${MOC_HEADERS}) IF( WIN32 ) + add_definitions( -DLASTFM_LIB ) add_library(tomahawk_lastfm2 SHARED ${SOURCES} ${MOC_SOURCES} diff --git a/thirdparty/liblastfm2/src/global.h b/thirdparty/liblastfm2/src/global.h index d297ae702..8a513d802 100644 --- a/thirdparty/liblastfm2/src/global.h +++ b/thirdparty/liblastfm2/src/global.h @@ -30,25 +30,21 @@ #include <QtGlobal> -#ifdef Q_CC_MSVC - #ifdef LASTFM_LIB - #define LASTFM_DLLEXPORT __declspec(dllexport) - #else - #define LASTFM_DLLEXPORT __declspec(dllimport) - #endif - #ifdef LASTFM_FINGERPRINT_LIB - #define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllexport) - #else - #define LASTFM_FINGERPRINT_DLLEXPORT __declspec(dllimport) - #endif -#elif __GNUC__ >= 4 - #define LASTFM_DLLEXPORT __attribute__ ((visibility("default"))) - #define LASTFM_FINGERPRINT_DLLEXPORT __attribute__ ((visibility("default"))) +#ifndef LASTFM_LIB_STATIC +#ifdef LASTFM_LIB + #define LASTFM_DLLEXPORT Q_DECL_EXPORT #else - #define LASTFM_DLLEXPORT - #define LASTFM_FINGERPRINT_DLLEXPORT + #define LASTFM_DLLEXPORT Q_DECL_IMPORT #endif - +#ifdef LASTFM_FINGERPRINT_LIB + #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_EXPORT +#else + #define LASTFM_FINGERPRINT_DLLEXPORT Q_DECL_IMPORT +#endif +#else // LASTFM_LIB_STATIC + #define LASTFM_DLLEXPORT + #define LASTFM_FINGERPRINT_DLLEXPORT +#endif // LASTFM_LIB_STATIC #include <QMetaEnum>