diff --git a/data/images/party.png b/data/images/party.png new file mode 100644 index 0000000000..ec227f9afc Binary files /dev/null and b/data/images/party.png differ diff --git a/resources.qrc b/resources.qrc index 30b751ba56..933d697087 100644 --- a/resources.qrc +++ b/resources.qrc @@ -168,5 +168,6 @@ data/www/css/font-awesome.css data/www/css/style.css data/www/js/html5shim.js + data/images/party.png diff --git a/src/libtomahawk/ActionCollection.cpp b/src/libtomahawk/ActionCollection.cpp index 617fc5ca90..2d48476f01 100644 --- a/src/libtomahawk/ActionCollection.cpp +++ b/src/libtomahawk/ActionCollection.cpp @@ -101,6 +101,10 @@ ActionCollection::initActions() m_actionCollection[ "quit" ]->setShortcutContext( Qt::ApplicationShortcut ); m_actionCollection[ "quit" ]->setMenuRole( QAction::QuitRole ); + // party actions + m_actionCollection[ "renameParty" ] = new QAction( tr( "&Rename Party" ), this ); + m_actionCollection[ "disbandParty" ] = new QAction( tr( "&Disband Party" ), this ); + // connect actions to AudioEngine AudioEngine *ae = AudioEngine::instance(); connect( m_actionCollection[ "playPause" ], SIGNAL( triggered() ), ae, SLOT( playPause() ), Qt::UniqueConnection ); diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index 9002682cf2..d76634bfea 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -162,6 +162,9 @@ set( libGuiSources widgets/SearchWidget.cpp widgets/SeekSlider.cpp widgets/SourceTreePopupDialog.cpp + widgets/PartyWidget.cpp + widgets/PartyCommandWidget.cpp + widgets/PartyCurrentTrackWidget.cpp widgets/StatsGauge.cpp widgets/ToggleButton.cpp widgets/WhatsHotWidget.cpp @@ -198,6 +201,8 @@ list(APPEND libSources AlbumPlaylistInterface.cpp CountryUtils.cpp FuncTimeout.cpp + Party.cpp +# PartyModel.cpp Playlist.cpp PlaylistEntry.cpp PlaylistPlaylistInterface.cpp @@ -291,6 +296,7 @@ list(APPEND libSources database/DatabaseCommand_SetDynamicPlaylistRevision.cpp database/DatabaseCommand_SetPlaylistRevision.cpp database/DatabaseCommand_SetTrackAttributes.cpp + database/DatabaseCommand_PartyInfo.cpp database/DatabaseCommand_ShareTrack.cpp database/DatabaseCommand_SocialAction.cpp database/DatabaseCommand_SourceOffline.cpp diff --git a/src/libtomahawk/LatchManager.cpp b/src/libtomahawk/LatchManager.cpp index 83f5b506d8..ab1c965574 100644 --- a/src/libtomahawk/LatchManager.cpp +++ b/src/libtomahawk/LatchManager.cpp @@ -34,9 +34,24 @@ #include "Result.h" #endif - using namespace Tomahawk; +LatchManager* LatchManager::s_instance = 0; + + +LatchManager* +LatchManager::instance() +{ + if ( !s_instance ) + { + s_instance = new LatchManager(); + } + + return s_instance; +} + + + LatchManager::LatchManager( QObject* parent ) : QObject( parent ) , m_state( NotLatched ) @@ -45,6 +60,7 @@ LatchManager::LatchManager( QObject* parent ) connect( AudioEngine::instance(), SIGNAL( paused() ), SLOT( audioPaused() ) ); } + LatchManager::~LatchManager() { diff --git a/src/libtomahawk/LatchManager.h b/src/libtomahawk/LatchManager.h index 7f9650a90d..7359171507 100644 --- a/src/libtomahawk/LatchManager.h +++ b/src/libtomahawk/LatchManager.h @@ -35,7 +35,7 @@ class DLLEXPORT LatchManager : public QObject { Q_OBJECT public: - explicit LatchManager( QObject* parent = 0 ); + static LatchManager* instance(); virtual ~LatchManager(); bool isLatched( const source_ptr& src ); @@ -51,6 +51,8 @@ private slots: void audioPaused(); private: + explicit LatchManager( QObject* parent = 0 ); + enum State { NotLatched = 0, Latching, @@ -61,6 +63,8 @@ private slots: source_ptr m_latchedOnTo; source_ptr m_waitingForLatch; playlistinterface_ptr m_latchedInterface; + + static LatchManager* s_instance; }; } diff --git a/src/libtomahawk/Party.cpp b/src/libtomahawk/Party.cpp new file mode 100644 index 0000000000..80b28eae59 --- /dev/null +++ b/src/libtomahawk/Party.cpp @@ -0,0 +1,391 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2011, Leo Franchi + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2012-2013, Teo Mrnjavac + * + * 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 . + */ + +#include "Party.h" + +#include "database/Database.h" +#include "database/DatabaseCommand_PartyInfo.h" + +#include "Pipeline.h" +#include "Source.h" +#include "SourceList.h" +#include "SourcePlaylistInterface.h" +#include "widgets/SourceTreePopupDialog.h" +#include "utils/Logger.h" + + +using namespace Tomahawk; + + +Party::Party( const source_ptr& author ) + : m_source( author ) +{} + + +Party::Party( const source_ptr& author, + const QString& guid, + const playlist_ptr& basePlaylist ) + : QObject() + , m_source( author ) + , m_guid( guid ) + , m_playlist( basePlaylist ) + , m_createdOn( 0 ) //will be set later + , m_currentRow( -1 ) +{ + init(); +} + + +Party::~Party() +{} + + +party_ptr +Party::createFromPlaylist( const playlist_ptr& basePlaylist ) +{ + Q_ASSERT( basePlaylist->author()->isLocal() ); + if ( !basePlaylist->author()->isLocal() ) + { + tDebug() << "Error: cannot create Party from someone else's playlist."; + return party_ptr(); + } + + party_ptr party( new Party( SourceList::instance()->getLocal(), + uuid(), + basePlaylist ), &QObject::deleteLater ); + party->setWeakSelf( party.toWeakRef() ); + + // Since the listening party isn't added to Source and hooked up to a model yet, we must prepare + // the dbcmd manually rather than calling pushUpdate(). + DatabaseCommand_PartyInfo* cmd = + DatabaseCommand_PartyInfo::broadcastParty( basePlaylist->author(), party ); + connect( cmd, SIGNAL( finished() ), party.data(), SIGNAL( created() ) ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); + + party->reportCreated( party ); + + //This DBcmd has a postCommitHook in all peers which calls SourceList::createParty and + //deserializes the variant to it. + + return party; +} + + +party_ptr +Party::createNew( const QString& title, const QList& queries ) +{ + playlist_ptr basePlaylist = Playlist::create( SourceList::instance()->getLocal(), + uuid(), + title, + QString(), + SourceList::instance()->getLocal()->friendlyName(), + true, + queries ); + + return createFromPlaylist( basePlaylist ); +} + + +void +Party::init() +{ + m_locallyChanged = false; + m_deleted = false; +} + + +party_ptr +Party::load( const QString& guid ) +{ + party_ptr p; + + foreach( const Tomahawk::source_ptr& source, SourceList::instance()->sources() ) + { + if ( !source->hasParty() ) + continue; + + p = source->party(); + if ( p->guid() == guid ) + return p; + } + + return p; +} + + +void +Party::remove( const party_ptr& party ) +{ + party->aboutToBeDeleted( party ); + + DatabaseCommand_PartyInfo* cmd = + DatabaseCommand_PartyInfo::disbandParty( party->author(), party->guid() ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); +} + + +void +Party::updateFrom( const party_ptr& other ) +{ + Q_ASSERT( !other.isNull() ); + Q_ASSERT( other->guid() == guid() ); + + bool isChanged = false; + + if ( m_createdOn != other->m_createdOn ) + { + setCreatedOn( other->m_createdOn ); + isChanged = true; + } + + if ( m_currentRow != other->m_currentRow ) + { + setCurrentRow( other->m_currentRow ); + isChanged = true; + } + + if ( m_playlist->guid() != other->m_playlist->guid() ) + { + setPlaylistByGuid( other->m_playlist->guid() ); + isChanged = true; + } + + m_listenerIds.clear(); + m_listenerIds.append( other->m_listenerIds ); + isChanged = true; + + if ( isChanged ) + emit changed(); +} + + +source_ptr +Party::author() const +{ + return m_source; +} + + +QString +Party::guid() const +{ + return m_guid; +} + + +uint +Party::createdOn() const +{ + return m_createdOn; +} + + +int +Party::currentRow() const +{ + return m_currentRow; +} + + +playlist_ptr +Party::playlist() const +{ + return m_playlist; +} + + +void +Party::setGuid( const QString& s ) +{ + m_guid = s; +} + + +void +Party::setCreatedOn( uint createdOn ) +{ + m_createdOn = createdOn; +} + + +void +Party::setListenerIdsV( const QVariantList& v ) +{ + m_listenerIds.clear(); + foreach ( const QVariant &e, v ) + { + QString listenerId = e.toString(); + + if ( !listenerId.isEmpty() ) + m_listenerIds << listenerId; + } + + emit listenersChanged(); +} + + +QVariantList +Party::listenerIdsV() const +{ + QVariantList v; + foreach ( const QString &e, m_listenerIds ) + { + v.append( QVariant::fromValue( e ) ); + } + return v; +} + + +void +Party::setCurrentRow( int row ) +{ + m_currentRow = row; +} + + +void +Party::setPlaylistByGuid( const QString& guid ) +{ + m_playlist = Playlist::get( guid ); +} + + +QString +Party::playlistGuid() const +{ + if ( !m_playlist.isNull() ) + return m_playlist->guid(); + return QString(); +} + + +QStringList +Party::listenerIds() const +{ + return m_listenerIds; +} + + +void +Party::reportCreated( const party_ptr& self ) +{ + Q_ASSERT( self.data() == this ); + setWeakSelf( self.toWeakRef() ); + m_source->setParty( self ); //or not if the guid is the same! +} + + +void +Party::reportDeleted( const party_ptr& self ) +{ + Q_ASSERT( self.data() == this ); + m_source->removeParty(); + m_playlist = Tomahawk::playlist_ptr(); + emit deleted( self ); +} + + +void +Party::onDeleteResult( SourceTreePopupDialog* dialog ) +{ + dialog->deleteLater(); + + //TODO: implement! +} + + +void +Party::addListener( const Tomahawk::source_ptr& listener ) +{ + QString listenerId = listener->nodeId(); + if ( m_listenerIds.contains( listenerId ) ) + return; + + m_listenerIds.append( listenerId ); + m_listenerIds.sort(); + + pushUpdate(); + + connect( listener.data(), SIGNAL( offline() ), this, SLOT( onListenerOffline() ) ); + + emit listenersChanged(); +} + + +void +Party::removeListener( const Tomahawk::source_ptr& listener ) +{ + QString listenerId = listener->nodeId(); + m_listenerIds.removeAll( listenerId ); + + pushUpdate(); + + disconnect( listener.data(), SIGNAL( offline() ), this, SLOT( onListenerOffline() ) ); + + emit listenersChanged(); +} + + +void +Party::onListenerOffline() +{ + Source* s = qobject_cast< Source* >( sender() ); + if ( s ) + { + source_ptr sp = SourceList::instance()->get( s->id() ); + Q_ASSERT( !sp.isNull() ); + removeListener( sp ); + } +} + + +void +Party::setWeakSelf( QWeakPointer< Party > self ) +{ + m_weakSelf = self; +} + + +void +Party::pushUpdate() +{ + Tomahawk::party_ptr thisParty = m_weakSelf.toStrongRef(); + + Q_ASSERT( author()->isLocal() ); + if ( !thisParty.isNull() && author()->isLocal() ) //only the DJ can push updates! + { + DatabaseCommand_PartyInfo* cmd = + DatabaseCommand_PartyInfo::broadcastParty( author(), thisParty ); + Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); + } +} + + +Tomahawk::playlistinterface_ptr +Party::playlistInterface() +{ + if ( m_playlistInterface.isNull() ) + { + m_playlistInterface = m_source->playlistInterface(); + } + + return m_playlistInterface; +} diff --git a/src/libtomahawk/Party.h b/src/libtomahawk/Party.h new file mode 100644 index 0000000000..4872a6d7c2 --- /dev/null +++ b/src/libtomahawk/Party.h @@ -0,0 +1,147 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2011, Leo Franchi + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2012-2013, Teo Mrnjavac + * + * 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 . + */ + +#ifndef PARTY_H +#define PARTY_H + +#include +#include +#include +#include + +#include "Typedefs.h" +#include "DllMacro.h" + +class SourceTreePopupDialog; +class DatabaseCommand_PartyInfo; +class PartyModel; + +namespace Tomahawk +{ + + +class DLLEXPORT Party : public QObject +{ + Q_OBJECT + Q_PROPERTY( QString guid READ guid WRITE setGuid ) + Q_PROPERTY( uint createdon READ createdOn WRITE setCreatedOn ) + Q_PROPERTY( QVariantList listenerIds READ listenerIdsV WRITE setListenerIdsV ) + Q_PROPERTY( int currentRow READ currentRow WRITE setCurrentRow ) + Q_PROPERTY( QString playlistGuid READ playlistGuid WRITE setPlaylistByGuid ) + + friend class ::DatabaseCommand_PartyInfo; + friend class ::PartyModel; + +public: + virtual ~Party(); + + // User-callable named ctors. On a peer that isn't a host, these should not be used. + static Tomahawk::party_ptr createFromPlaylist( const Tomahawk::playlist_ptr& playlist ); + + static Tomahawk::party_ptr createNew( const QString& title, + const QList& queries = QList< Tomahawk::query_ptr >() ); + + static Tomahawk::party_ptr load( const QString& guid ); + + static void remove( const party_ptr& party ); + + void updateFrom( const Tomahawk::party_ptr& other ); + + source_ptr author() const; + QString guid() const; + uint createdOn() const; + int currentRow() const; + playlist_ptr playlist() const; + + // + // these need to exist and be public for the json serialization stuff + // you SHOULD NOT call them. They are used for an alternate CTOR method from json. + // maybe friend QObjectHelper and make them private? + explicit Party( const source_ptr& author ); + void setGuid( const QString& s ); + void setCreatedOn( uint createdOn ); + void setListenerIdsV( const QVariantList& v ); + QVariantList listenerIdsV() const; + void setCurrentRow( int row ); + void setPlaylistByGuid( const QString& guid ); + QString playlistGuid() const; + // + + QStringList listenerIds() const; + + Tomahawk::playlistinterface_ptr playlistInterface(); + +signals: + void created(); + + void changed(); + + void aboutToBeDeleted( const Tomahawk::party_ptr& lr ); + void deleted( const Tomahawk::party_ptr& lr ); + + void listenersChanged(); + +public slots: + void reportCreated( const Tomahawk::party_ptr& self ); + void reportDeleted( const Tomahawk::party_ptr& self ); + + // Only ever used by the LR host. Listeners do not add or remove themselves here, they just + // latch on/off and the host takes care of adding them here and notifying everybody. + void addListener( const Tomahawk::source_ptr& listener ); + void removeListener( const Tomahawk::source_ptr& listener ); + void onListenerOffline(); + + void setWeakSelf( QWeakPointer< Party > self ); +private slots: + // Only ever used by the LR host! + void pushUpdate(); + + void onDeleteResult( SourceTreePopupDialog* dialog ); + +private: + explicit Party( const source_ptr& author, + const QString& guid, + const playlist_ptr& basePlaylist ); + + + Party(); + void init(); + + QWeakPointer< Party > m_weakSelf; + + bool m_locallyChanged; + bool m_deleted; + + source_ptr m_source; + QString m_guid; + uint m_createdOn; + Tomahawk::playlist_ptr m_playlist; + QStringList m_listenerIds; + int m_currentRow; + + Tomahawk::playlistinterface_ptr m_playlistInterface; +}; + +} //namespace Tomahawk + +Q_DECLARE_METATYPE( Tomahawk::party_ptr ) + +#endif // PARTY_H diff --git a/src/libtomahawk/PartyModel.cpp b/src/libtomahawk/PartyModel.cpp new file mode 100644 index 0000000000..c67a0b6e40 --- /dev/null +++ b/src/libtomahawk/PartyModel.cpp @@ -0,0 +1,561 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#include "PartyModel.h" + +#include "Album.h" +#include "Artist.h" +#include "DropJob.h" +#include "Party.h" +#include "Pipeline.h" +#include "playlist/PlayableItem.h" +#include "PlaylistInterface.h" +#include "Source.h" +#include "SourceList.h" +#include "utils/TomahawkUtils.h" +#include "utils/Logger.h" + +#include +#include + +using namespace Tomahawk; + + +PartyModel::PartyModel( QObject* parent ) + : PlayableModel( parent ) + , m_changesOngoing( false ) + , m_isLoading( false ) + , m_savedInsertPos( -1 ) +{ + m_dropStorage.parent = QPersistentModelIndex(); + m_dropStorage.row = -10; + + setReadOnly( true ); +} + + +PartyModel::~PartyModel() +{ +} + + +QMimeData* +PartyModel::mimeData( const QModelIndexList& indexes ) const +{ + QMimeData* d = PlayableModel::mimeData( indexes ); + if ( !m_party.isNull() ) + d->setData( "application/tomahawk.party.id", m_party->guid().toLatin1() ); + + return d; +} + + +bool +PartyModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ) +{ + Q_UNUSED( column ); + + if ( action == Qt::IgnoreAction || isReadOnly() ) + return true; + + if ( !DropJob::acceptsMimeData( data ) ) + return false; + + m_dropStorage.row = row; + m_dropStorage.parent = QPersistentModelIndex( parent ); + m_dropStorage.action = action; + + DropJob* dj = new DropJob(); + + if ( !DropJob::acceptsMimeData( data, DropJob::Track | DropJob::Playlist | DropJob::Album | DropJob::Artist ) ) + return false; + + dj->setDropTypes( DropJob::Track | DropJob::Playlist | DropJob::Album | DropJob::Artist ); + dj->setDropAction( DropJob::Append ); + +#ifdef Q_WS_MAC + // On mac, drags from outside the app are still Qt::MoveActions instead of Qt::CopyAction by default + // so check if the drag originated in this party to determine whether or not to copy + if ( !data->hasFormat( "application/tomahawk.party.id" ) || + ( !m_party.isNull() && data->data( "application/tomahawk.party.id" ) != m_party->guid() ) ) + { + dj->setDropAction( DropJob::Append ); + } +#endif + + connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), + SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + dj->tracksFromMimeData( data ); + + return true; +} + + +void +PartyModel::parsedDroppedTracks( QList< query_ptr > tracks ) +{ + if ( m_dropStorage.row == -10 ) //null value + return; + + int beginRow; + if ( m_dropStorage.row != -1 ) + beginRow = m_dropStorage.row; + else if ( m_dropStorage.parent.isValid() ) + beginRow = m_dropStorage.parent.row(); + else + beginRow = rowCount( QModelIndex() ); //append + + if ( tracks.count() ) + { + bool update = ( m_dropStorage.action & Qt::CopyAction || + m_dropStorage.action & Qt::MoveAction ); + if ( update ) + beginRoomChanges(); + + insertQueries( tracks, beginRow ); + + if ( update && m_dropStorage.action & Qt::CopyAction ) + endRoomChanges(); + } + + m_dropStorage.parent = QPersistentModelIndex(); + m_dropStorage.row = -10; +} + + +void +PartyModel::beginRoomChanges() +{ + if ( m_party.isNull() || !m_party->author()->isLocal() ) + return; + + Q_ASSERT( !m_changesOngoing ); + m_changesOngoing = true; +} + + +void +PartyModel::endRoomChanges() +{ + if ( m_party.isNull() || !m_party->author()->isLocal() ) + return; + + if ( m_changesOngoing ) + { + m_changesOngoing = false; + } + else + { + tDebug() << "Called" << Q_FUNC_INFO << "unexpectedly!"; + Q_ASSERT( false ); + } + + + if ( m_savedInsertPos >= 0 && !m_savedInsertTracks.isEmpty() && + !m_savedRemoveTracks.isEmpty() ) + { + // If we have *both* an insert and remove, then it's a move action + // However, since we got the insert before the remove (Qt...), the index we have as the saved + // insert position is no longer valid. Find the proper one by finding the location of the first inserted + // track + for ( int i = 0; i < rowCount( QModelIndex() ); i++ ) + { + const QModelIndex idx = index( i, 0, QModelIndex() ); + if ( !idx.isValid() ) + continue; + const PlayableItem* item = itemFromIndex( idx ); + if ( !item || item->lrentry().isNull() ) + continue; + +// qDebug() << "Checking for equality:" << (item->lrentry() == m_savedInsertTracks.first()) << m_savedInsertTracks.first()->query()->track() << m_savedInsertTracks.first()->query()->artist(); + if ( item->lrentry() == m_savedInsertTracks.first() ) + { + // Found our index + m_party->moveEntries( m_savedInsertTracks, i ); + break; + } + } + m_savedInsertPos = -1; + m_savedInsertTracks.clear(); + m_savedRemoveTracks.clear(); + } + else if ( m_savedInsertPos >= 0 ) //only insertion + { + QList< query_ptr > qs; + foreach ( const lrentry_ptr& e, m_savedInsertTracks ) + { + qs.append( e->query() ); + } + + m_party->insertEntries( qs, m_savedInsertPos ); + m_savedInsertPos = -1; + m_savedInsertTracks.clear(); + } + else if ( !m_savedRemoveTracks.isEmpty() ) + { + m_party->removeEntries( m_savedRemoveTracks ); + m_savedRemoveTracks.clear(); + } +} + + +QList< lrentry_ptr > +PartyModel::partyEntries() const +{ + QList< lrentry_ptr > l; + for ( int i = 0; i < rowCount( QModelIndex() ); ++i ) + { + QModelIndex idx = index( i, 0, QModelIndex() ); + if ( !idx.isValid() ) + continue; + + PlayableItem* item = itemFromIndex( idx ); + if ( item ) + l << item->lrentry(); + } + + return l; +} + + +void +PartyModel::loadParty( const Tomahawk::party_ptr& party, bool loadEntries ) +{ + if ( !m_party.isNull() ) //NOTE: LR already loaded, does this ever happen? + { + disconnect( m_party.data(), SIGNAL( deleted( Tomahawk::party_ptr ) ), + this, SIGNAL( partyDeleted() ) ); + disconnect( m_party.data(), SIGNAL( changed() ), + this, SLOT( reload() ) ); + disconnect( m_party.data(), SIGNAL( listenersChanged() ), + this, SLOT( reloadRoomMetadata() ) ); + } + + m_isLoading = true; + + if ( loadEntries ) + clear(); + + m_party = party; + connect( m_party.data(), SIGNAL( deleted( Tomahawk::party_ptr ) ), + this, SIGNAL( partyDeleted() ) ); + connect( m_party.data(), SIGNAL( changed() ), + this, SLOT( reload() ) ); + connect( m_party.data(), SIGNAL( listenersChanged() ), + this, SLOT( reloadRoomMetadata() ) ); + + setReadOnly( !m_party->author()->isLocal() ); + + reloadRoomMetadata(); + + m_isLoading = false; + + if ( !loadEntries ) + { + return; + } + + reload(); +} + + +void +PartyModel::reloadRoomMetadata() +{ + setTitle( m_party->title() ); + QString age = TomahawkUtils::ageToString( QDateTime::fromTime_t( m_party->createdOn() ), true ); + + //set the description + QString desc; + int n = m_party->listenerIds().count(); + if ( m_party->author()->isLocal() ) + desc = tr( "A party you are hosting, with %n listener(s).", "", n ); + else + desc = tr( "A party hosted by %1, with %n listener(s).", "", n ) + .arg( m_party->author()->friendlyName() ); + setDescription( desc ); + + emit listenersChanged(); +} + +void +PartyModel::reload() +{ + m_isLoading = true; + + reloadRoomMetadata(); + + QList< lrentry_ptr > entries = m_party->entries(); + foreach ( const lrentry_ptr& p, entries ) + tDebug() << p->guid() << p->query()->track()->track() << p->query()->track()->artist(); + + // Since LR dbcmds are all singletons, they give all listeners the complete LR data every time, + // even if the actual list of tracks has not changed. + // To avoid unnecessary model/view reload, we make sure that we only reload if stuff has changed. + // It is an expensive O(n) entry-by-entry search for changes, but it surely beats clearing and + // reloading the whole model and view every time. + bool hasChanged = false; + if ( entries.count() != rowCount( QModelIndex() ) ) + { + hasChanged = true; + } + else //could be unchanged + { + for ( int i = 0; i < entries.count(); ++i ) + { + QModelIndex idx = index( i, 0, QModelIndex() ); + if ( !idx.isValid() ) + { + hasChanged = true; + break; + } + + PlayableItem* item = itemFromIndex( idx ); + if ( item && !item->lrentry().isNull() ) + { + if ( item->lrentry()->query() != entries.at( i )->query() ) + { + hasChanged = true; + break; + } + } + else + { + hasChanged = true; + break; + } + } + } + + if ( hasChanged ) + { + clear(); + appendEntries( entries ); //emits signals and stuff + } + + bool hasRowChanged = false; + if ( currentItem().row() != m_party->currentRow() ) + { + hasRowChanged = true; + int row = m_party->currentRow(); + if ( row >= 0 && row < rowCount( QModelIndex() ) ) + setCurrentItem( index( row, 0, QModelIndex() ) ); + } + + if ( hasChanged || hasRowChanged ) + emit currentRowChanged(); + + m_isLoading = false; +} + + +void +PartyModel::clear() +{ + PlayableModel::clear(); + m_waitingForResolved.clear(); +} + + +void +PartyModel::appendEntries( const QList< lrentry_ptr >& entries ) +{ + insertEntriesPrivate( entries, rowCount( QModelIndex() ) ); +} + + +void +PartyModel::insertAlbums( const QList< album_ptr >& albums, int row ) +{ + foreach ( const album_ptr& album, albums ) + { + if ( album.isNull() ) + return; + + connect( album.data(), SIGNAL( tracksAdded( QList< Tomahawk::query_ptr >, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ), + SLOT( appendQueries( QList< Tomahawk::query_ptr > ) ) ); + + appendQueries( album->playlistInterface( Mixed )->tracks() ); + } +} + + +void +PartyModel::insertArtists( const QList< artist_ptr >& artists, int row ) +{ + foreach ( const artist_ptr& artist, artists ) + { + if ( artist.isNull() ) + return; + + connect( artist.data(), SIGNAL( tracksAdded( QList< Tomahawk::query_ptr >, Tomahawk::ModelMode, Tomahawk::collection_ptr ) ), + SLOT( appendQueries( QList< Tomahawk::query_ptr > ) ) ); + + appendQueries( artist->playlistInterface( Mixed )->tracks() ); + } +} + + +void +PartyModel::insertQueries( const QList< query_ptr >& queries, int row ) +{ + QList< Tomahawk::lrentry_ptr > entries; + foreach ( const query_ptr& query, queries ) + { + lrentry_ptr entry = lrentry_ptr( new PartyEntry() ); + + entry->setDuration( query->queryTrack()->duration() ); + entry->setLastmodified( 0 ); + //TODO: annotations? do we need them here? + entry->setQuery( query ); + entry->setGuid( uuid() ); + + entries << entry; + } + + insertEntriesPrivate( entries, row ); +} + + +void +PartyModel::insertEntriesFromView( const QList< lrentry_ptr >& entries, int row ) +{ + if ( entries.isEmpty() || !m_party->author()->isLocal() ) + return; + + beginRoomChanges(); + insertEntriesPrivate( entries, row ); + endRoomChanges(); +} + + +void +PartyModel::insertEntriesPrivate( const QList< lrentry_ptr >& entries, int row ) +{ + if ( !entries.count() ) + { + emit itemCountChanged( rowCount( QModelIndex() ) ); + finishLoading(); + return; + } + + int c = row; + QPair< int, int > crows; + crows.first = c; + crows.second = c + entries.count() - 1; + + if ( !m_isLoading ) + { + m_savedInsertPos = row; + m_savedInsertTracks = entries; + } + + emit beginInsertRows( QModelIndex(), crows.first, crows.second ); + + QList< Tomahawk::query_ptr > queries; + int i = 0; + PlayableItem* plitem; + foreach( const lrentry_ptr& entry, entries ) + { + plitem = new PlayableItem( entry, rootItem(), row + i ); + plitem->index = createIndex( row + i, 0, plitem ); + i++; + + if ( !entry->query()->resolvingFinished() && !entry->query()->playable() ) + { + queries << entry->query(); + m_waitingForResolved.append( entry->query().data() ); + connect( entry->query().data(), SIGNAL( resolvingFinished( bool ) ), SLOT( trackResolved( bool ) ) ); + } + + connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); + } + + if ( !m_waitingForResolved.isEmpty() ) + { + Pipeline::instance()->resolve( queries ); + emit loadingStarted(); + } + + emit endInsertRows(); + emit itemCountChanged( rowCount( QModelIndex() ) ); + finishLoading(); +} + + +void +PartyModel::trackResolved( bool ) +{ + Query* q = qobject_cast< Query* >( sender() ); + if ( !q ) + return; + + if ( m_waitingForResolved.contains( q ) ) + { + m_waitingForResolved.removeAll( q ); + disconnect( q, SIGNAL( resolvingFinished( bool ) ), + this, SLOT( trackResolved( bool ) ) ); + } + + if ( m_waitingForResolved.isEmpty() ) + { + emit loadingFinished(); + } +} + + +void +PartyModel::removeIndex( const QModelIndex& index, bool moreToCome ) +{ + PlayableItem* item = itemFromIndex( index ); + + //if removing something not resolved, let's terminate the query resolution first + if ( item && m_waitingForResolved.contains( item->query().data() ) ) + { + disconnect( item->query().data(), SIGNAL( resolvingFinished( bool ) ), + this, SLOT( trackResolved( bool ) ) ); + + m_waitingForResolved.removeAll( item->query().data() ); + if ( m_waitingForResolved.isEmpty() ) + emit loadingFinished(); + } + + if ( !m_changesOngoing ) + beginRoomChanges(); + + if ( item && !m_isLoading ) + m_savedRemoveTracks << item->lrentry(); + + PlayableModel::removeIndex( index, moreToCome ); + + if ( !moreToCome ) + endRoomChanges(); +} + + +void +PartyModel::setCurrentItem( const QModelIndex& index ) +{ + PlayableModel::setCurrentIndex( index ); + if ( !m_party.isNull() && m_party->author() == SourceList::instance()->getLocal() ) + { + m_party->setCurrentRow( index.row() ); + m_party->pushUpdate(); + } +} diff --git a/src/libtomahawk/PartyModel.h b/src/libtomahawk/PartyModel.h new file mode 100644 index 0000000000..0e3de8c67e --- /dev/null +++ b/src/libtomahawk/PartyModel.h @@ -0,0 +1,92 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#ifndef PARTYMODEL_H +#define PARTYMODEL_H + +#include "playlist/PlayableModel.h" + +class DLLEXPORT PartyModel : public PlayableModel +{ + Q_OBJECT + + typedef struct { + int row; + QPersistentModelIndex parent; + Qt::DropAction action; + } DropStorageData; + +public: + explicit PartyModel( QObject* parent = 0 ); + virtual ~PartyModel(); + + QMimeData* mimeData( const QModelIndexList& indexes ) const; + bool dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent ); + + Tomahawk::party_ptr party() const { return m_party; } + +public slots: + virtual void loadParty( const Tomahawk::party_ptr& party, bool loadEntries = true ); + + void clear(); + + virtual void appendEntries( const QList< Tomahawk::lrentry_ptr >& entries ); + + void insertAlbums( const QList< Tomahawk::album_ptr >& albums, int row = 0 ); + void insertArtists( const QList< Tomahawk::artist_ptr >& artists, int row = 0 ); + void insertQueries( const QList< Tomahawk::query_ptr >& queries, int row = 0 ); + void insertEntriesFromView( const QList< Tomahawk::lrentry_ptr >& entries, int row = 0 ); + + void removeIndex( const QModelIndex& index, bool moreToCome = false ); + + void setCurrentItem( const QModelIndex& index ); + +signals: + void partyDeleted(); + void currentRowChanged(); + void listenersChanged(); + +protected: + QList partyEntries() const; + +private slots: + void parsedDroppedTracks( QList ); + void trackResolved( bool ); + void reload(); + void reloadRoomMetadata(); + void insertEntriesPrivate( const QList< Tomahawk::lrentry_ptr >& entries, int row = 0 ); + +private: + void beginRoomChanges(); + void endRoomChanges(); + + Tomahawk::party_ptr m_party; + + bool m_isLoading; + bool m_changesOngoing; + QList< Tomahawk::Query* > m_waitingForResolved; + + int m_savedInsertPos; + QList< Tomahawk::lrentry_ptr > m_savedInsertTracks; + QList< Tomahawk::lrentry_ptr > m_savedRemoveTracks; + + DropStorageData m_dropStorage; +}; + +#endif // PARTYMODEL_H diff --git a/src/libtomahawk/Source.cpp b/src/libtomahawk/Source.cpp index 08f15fcd9c..9e167e5a85 100644 --- a/src/libtomahawk/Source.cpp +++ b/src/libtomahawk/Source.cpp @@ -23,6 +23,7 @@ #include "collection/Collection.h" #include "SourceList.h" #include "SourcePlaylistInterface.h" +#include "Party.h" #include "accounts/AccountManager.h" #include "network/ControlConnection.h" @@ -391,6 +392,60 @@ Source::removeCollection( const collection_ptr& c ) emit collectionRemoved( c ); } + +party_ptr +Source::party() const +{ + Q_D( const Source ); + + return d->party; +} + + +bool +Source::hasParty() const +{ + Q_D( const Source ); + return !d->party.isNull(); +} + +void +Source::setParty( const party_ptr& p ) +{ + Q_D( Source ); + if ( d->party.isNull() ) + { + d->party = p; + emit partyAdded( p ); + + return; + } + + Q_ASSERT( d->party->guid() == p->guid() ); //disallow replacing + + if( d->party->guid() == p->guid() ); //update instead of replace + { + if ( !d->party->author()->isLocal() ) + d->party->updateFrom( p ); + return; + } +} + + +void +Source::removeParty() +{ + Q_D( Source ); + if ( d->party.isNull() ) + return; + + Tomahawk::party_ptr p = d->party; + d->party = Tomahawk::party_ptr(); + p->playlist()->rename( tr( "Past party: %1" ).arg( p->playlist()->title() ) ); + emit partyRemoved( p ); +} + + int Source::id() const { @@ -743,6 +798,7 @@ void Source::reportSocialAttributesChanged( DatabaseCommand_SocialAction* action ) { Q_ASSERT( action ); + Q_D( Source ); emit socialAttributesChanged( action->action() ); @@ -750,13 +806,33 @@ Source::reportSocialAttributesChanged( DatabaseCommand_SocialAction* action ) { const source_ptr to = SourceList::instance()->get( action->comment() ); if ( !to.isNull() ) + { + if ( to->isLocal() ) //somebody just latched onto me! + { + if ( to->hasParty() ) + { + to->party()->addListener( SourceList::instance()->get( d->id ) ); + } + } + emit latchedOn( to ); + } } else if ( action->action() == "latchOff" ) { const source_ptr from = SourceList::instance()->get( action->comment() ); if ( !from.isNull() ) + { + if ( from->isLocal() ) //somebody just latched off from me! + { + if ( from->hasParty() ) + { + from->party()->removeListener( SourceList::instance()->get( d->id ) ); + } + } + emit latchedOff( from ); + } } } diff --git a/src/libtomahawk/Source.h b/src/libtomahawk/Source.h index e53b782c8f..085e55dadd 100644 --- a/src/libtomahawk/Source.h +++ b/src/libtomahawk/Source.h @@ -61,6 +61,8 @@ friend class DatabaseCommand_DeleteFiles; friend class DatabaseCommand_LoadAllSources; friend class DatabaseCommand_LogPlayback; friend class DatabaseCommand_SocialAction; +friend class DatabaseCommand_PartyInfo; +friend class Party; //TODO: remove me friend class ::MusicScanner; public: @@ -89,6 +91,10 @@ friend class ::MusicScanner; void addCollection( const Tomahawk::collection_ptr& c ); void removeCollection( const Tomahawk::collection_ptr& c ); + Tomahawk::party_ptr party() const; + bool hasParty() const; + void removeParty(); + int id() const; ControlConnection* controlConnection() const; bool setControlConnection( ControlConnection* cc ); @@ -118,6 +124,9 @@ friend class ::MusicScanner; void collectionAdded( const Tomahawk::collection_ptr& collection ); void collectionRemoved( const Tomahawk::collection_ptr& collection ); + void partyAdded( const Tomahawk::party_ptr& ); + void partyRemoved( const Tomahawk::party_ptr& ); + void stats( const QVariantMap& ); void playbackStarted( const Tomahawk::track_ptr& track ); @@ -157,6 +166,8 @@ private slots: Q_DECLARE_PRIVATE( Source ) SourcePrivate* d_ptr; + void setParty( const Tomahawk::party_ptr& p ); + static bool friendlyNamesLessThan( const QString& first, const QString& second ); //lessThan for sorting void updateTracks(); diff --git a/src/libtomahawk/SourceList.cpp b/src/libtomahawk/SourceList.cpp index 4c2c8c828f..93af96e045 100644 --- a/src/libtomahawk/SourceList.cpp +++ b/src/libtomahawk/SourceList.cpp @@ -27,6 +27,7 @@ #include "infosystem/InfoSystemCache.h" #include "resolvers/ExternalResolver.h" #include "resolvers/ScriptCollection.h" +#include "Party.h" #include "utils/Logger.h" @@ -239,6 +240,19 @@ SourceList::createDynamicPlaylist( const Tomahawk::source_ptr& src, const QVaria } +void +SourceList::createPartyFromVariant( const Tomahawk::source_ptr& src, const QVariant& contents ) +{ + Q_ASSERT( !src->isLocal() ); + //This method does deserialization for incoming parties, and it is in + //SourceList only for convenience. + //The next step is Party::reportCreated(party_ptr) on both local and remote. + Tomahawk::party_ptr p = Tomahawk::party_ptr( new Tomahawk::Party( src ) ); + QJson::QObjectHelper::qvariant2qobject( contents.toMap(), p.data() ); + p->reportCreated( p ); + tDebug() << Q_FUNC_INFO << "party created"; +} + void SourceList::sourceSynced() { diff --git a/src/libtomahawk/SourceList.h b/src/libtomahawk/SourceList.h index c38e8cbeab..a98a13c15d 100644 --- a/src/libtomahawk/SourceList.h +++ b/src/libtomahawk/SourceList.h @@ -61,6 +61,7 @@ public slots: // called by the playlist creation dbcmds void createPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); void createDynamicPlaylist( const Tomahawk::source_ptr& src, const QVariant& contents ); + void createPartyFromVariant( const Tomahawk::source_ptr& src, const QVariant& contents ); void onResolverAdded( Tomahawk::Resolver* resolver ); void onResolverRemoved( Tomahawk::Resolver* resolver ); diff --git a/src/libtomahawk/Source_p.h b/src/libtomahawk/Source_p.h index 76207f8cb6..26630514e7 100644 --- a/src/libtomahawk/Source_p.h +++ b/src/libtomahawk/Source_p.h @@ -50,6 +50,7 @@ class SourcePrivate private: QList< QSharedPointer > collections; + Tomahawk::party_ptr party; QVariantMap stats; bool isLocal; diff --git a/src/libtomahawk/Typedefs.h b/src/libtomahawk/Typedefs.h index c81109fbbd..44ca31faa2 100644 --- a/src/libtomahawk/Typedefs.h +++ b/src/libtomahawk/Typedefs.h @@ -41,6 +41,7 @@ namespace Tomahawk class Artist; class Album; class Collection; + class Party; class Playlist; class PlaylistEntry; class PlaylistInterface; @@ -57,6 +58,7 @@ namespace Tomahawk class DatabaseCommand; typedef QSharedPointer collection_ptr; + typedef QSharedPointer party_ptr; typedef QSharedPointer playlist_ptr; typedef QSharedPointer plentry_ptr; typedef QSharedPointer playlistinterface_ptr; @@ -276,6 +278,7 @@ inline static QString uuid() Q_DECLARE_METATYPE( QModelIndex ) Q_DECLARE_METATYPE( QPersistentModelIndex ) Q_DECLARE_METATYPE( QNetworkReply* ) +Q_DECLARE_METATYPE( Tomahawk::source_ptr ) Q_DECLARE_METATYPE( Tomahawk::ACLStatus::Type ) #endif // TYPEDEFS_H diff --git a/src/libtomahawk/ViewManager.cpp b/src/libtomahawk/ViewManager.cpp index 4b3f05495a..caa2d593bb 100644 --- a/src/libtomahawk/ViewManager.cpp +++ b/src/libtomahawk/ViewManager.cpp @@ -2,8 +2,8 @@ * * Copyright 2010-2013, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell - * Copyright 2010-2012, Leo Franchi - * Copyright 2013, Teo Mrnjavac + * Copyright 2010-2012, Leo Franchi + * Copyright 2012-2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ #include "playlist/TreeWidget.h" #include "playlist/GridView.h" #include "playlist/AlbumModel.h" +#include "Party.h" #include "SourceList.h" #include "TomahawkSettings.h" @@ -54,6 +55,7 @@ #include "widgets/infowidgets/TrackInfoWidget.h" #include "widgets/NewPlaylistWidget.h" #include "widgets/AnimatedSplitter.h" +#include "widgets/PartyWidget.h" #include "utils/Logger.h" #include "utils/TomahawkUtilsGui.h" @@ -156,6 +158,32 @@ ViewManager::createPageForPlaylist( const playlist_ptr& playlist ) return view; } +PartyWidget* +ViewManager::createPageForParty( const party_ptr& party ) +{ + /* + PartyWidget* w = new PartyWidget(); + PartyModel* model = new PartyModel(); + model->loadParty( party ); + w->setModel( model ); + party->resolve(); + */ + + /* + PlaylistView* w = new PlaylistView(); + PlaylistModel* model = new PlaylistModel(); + + model->loadPlaylist( party->playlist() ); + w->setPlaylistModel( model ); + party->playlist()->resolve(); + */ + PartyWidget* w = new PartyWidget(); + w->setParty( party ); + party->playlist()->resolve(); + + return w; +} + FlexibleView* ViewManager::createPageForList( const QString& title, const QList< query_ptr >& queries ) @@ -218,6 +246,24 @@ ViewManager::show( const Tomahawk::playlist_ptr& playlist ) } +Tomahawk::ViewPage* +ViewManager::show( const Tomahawk::party_ptr& party ) +{ + PartyWidget* w; + if ( !m_partyWidgets.contains( party ) || m_partyWidgets.value( party ).isNull() ) + { + w = createPageForParty( party ); + m_partyWidgets.insert( party, w ); + } + else + { + w = m_partyWidgets.value( party ).data(); + } + setPage( w ); + return w; +} + + Tomahawk::ViewPage* ViewManager::show( const Tomahawk::dynplaylist_ptr& playlist ) { @@ -750,19 +796,26 @@ ViewManager::onWidgetDestroyed( QWidget* widget ) ViewPage* -ViewManager::pageForDynPlaylist(const dynplaylist_ptr& pl) const +ViewManager::pageForDynPlaylist( const dynplaylist_ptr& pl ) const { return m_dynamicWidgets.value( pl ).data(); } ViewPage* -ViewManager::pageForPlaylist(const playlist_ptr& pl) const +ViewManager::pageForPlaylist( const playlist_ptr& pl ) const { return m_playlistViews.value( pl ).data(); } +ViewPage* +ViewManager::pageForParty( const party_ptr& lr ) const +{ + return m_partyWidgets.value( lr ).data(); +} + + ViewPage* ViewManager::pageForInterface( Tomahawk::playlistinterface_ptr interface ) const { diff --git a/src/libtomahawk/ViewManager.h b/src/libtomahawk/ViewManager.h index 0a7ad623f4..ddec239323 100644 --- a/src/libtomahawk/ViewManager.h +++ b/src/libtomahawk/ViewManager.h @@ -64,6 +64,7 @@ class InboxModel; namespace Tomahawk { class DynamicWidget; + class PartyWidget; } class DLLEXPORT ViewManager : public QObject @@ -106,6 +107,8 @@ Q_OBJECT Tomahawk::ViewPage* pageForPlaylist( const Tomahawk::playlist_ptr& pl ) const; Tomahawk::ViewPage* pageForDynPlaylist( const Tomahawk::dynplaylist_ptr& pl ) const; + Tomahawk::ViewPage* pageForParty( const Tomahawk::party_ptr& lr ) const; + /// Get a playlist (or dynamic playlist ) from a ViewPage* if the page is PlaylistView or DynamicWidget. /// Lives here but used by SourcesModel Tomahawk::playlist_ptr playlistForPage( Tomahawk::ViewPage* ) const; @@ -114,6 +117,8 @@ Q_OBJECT // linked to the sidebar. call it right after creating the playlist FlexibleView* createPageForPlaylist( const Tomahawk::playlist_ptr& playlist ); + Tomahawk::PartyWidget* createPageForParty( const Tomahawk::party_ptr& party ); + FlexibleView* createPageForList( const QString& title, const QList< Tomahawk::query_ptr >& queries ); void addDynamicPage( Tomahawk::ViewPagePlugin* viewPage, const QString& pageName = QString() ); @@ -157,6 +162,7 @@ public slots: Tomahawk::ViewPage* show( const Tomahawk::query_ptr& query ); Tomahawk::ViewPage* show( const Tomahawk::collection_ptr& collection ); Tomahawk::ViewPage* show( const Tomahawk::source_ptr& source ); + Tomahawk::ViewPage* show( const Tomahawk::party_ptr& party ); void historyBack(); void historyForward(); @@ -212,6 +218,7 @@ private slots: QHash< Tomahawk::query_ptr, QPointer > m_trackViews; QHash< Tomahawk::playlist_ptr, QPointer > m_playlistViews; QHash< Tomahawk::source_ptr, QPointer > m_sourceViews; + QHash< Tomahawk::party_ptr, QPointer > m_partyWidgets; QList m_pageHistoryBack; QList m_pageHistoryFwd; diff --git a/src/libtomahawk/ViewPage.h b/src/libtomahawk/ViewPage.h index 4f407b44d1..cfed3c4ad0 100644 --- a/src/libtomahawk/ViewPage.h +++ b/src/libtomahawk/ViewPage.h @@ -92,6 +92,6 @@ class DLLEXPORT ViewPage QString m_filter; }; -}; // ns +} // namespace Tomahawk #endif //VIEWPAGE_H diff --git a/src/libtomahawk/database/Database.cpp b/src/libtomahawk/database/Database.cpp index 2885d651f6..76e6bff8b3 100644 --- a/src/libtomahawk/database/Database.cpp +++ b/src/libtomahawk/database/Database.cpp @@ -41,6 +41,7 @@ #include "DatabaseCommand_ShareTrack.h" #include "DatabaseCommand_SetCollectionAttributes.h" #include "DatabaseCommand_SetTrackAttributes.h" +#include "DatabaseCommand_PartyInfo.h" // Forward Declarations breaking QSharedPointer #if QT_VERSION < QT_VERSION_CHECK( 5, 0, 0 ) @@ -109,6 +110,7 @@ Database::Database( const QString& dbname, QObject* parent ) registerCommand(); registerCommand(); registerCommand(); + registerCommand(); if ( MAX_WORKER_THREADS < DEFAULT_WORKER_THREADS ) m_maxConcurrentThreads = MAX_WORKER_THREADS; diff --git a/src/libtomahawk/database/DatabaseCommand.h b/src/libtomahawk/database/DatabaseCommand.h index ce5ef927fb..435278407f 100644 --- a/src/libtomahawk/database/DatabaseCommand.h +++ b/src/libtomahawk/database/DatabaseCommand.h @@ -53,6 +53,8 @@ Q_PROPERTY( QString guid READ guid WRITE setGuid ) virtual ~DatabaseCommand(); virtual QString commandname() const { return "DatabaseCommand"; } + + // doesMutates = true => makes changes to the db, like an insert or update command virtual bool doesMutates() const { return true; } State state() const; @@ -71,8 +73,15 @@ Q_PROPERTY( QString guid READ guid WRITE setGuid ) const Tomahawk::source_ptr& source() const; virtual bool loggable() const { return false; } + + // groupable = true => aggregate all commands of this type into a single + // database transaction when executing it virtual bool groupable() const { return false; } + + // singletonCmd = true => only store the last command of that type in the + // database, all previous commands are ignored virtual bool singletonCmd() const { return false; } + virtual bool localOnly() const { return false; } virtual QVariant data() const; diff --git a/src/libtomahawk/database/DatabaseCommand_PartyInfo.cpp b/src/libtomahawk/database/DatabaseCommand_PartyInfo.cpp new file mode 100644 index 0000000000..dbc71b2709 --- /dev/null +++ b/src/libtomahawk/database/DatabaseCommand_PartyInfo.cpp @@ -0,0 +1,162 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2012-2013, Teo Mrnjavac + * + * 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 . + */ + +#include "DatabaseCommand_PartyInfo.h" + +#include "network/Servent.h" +#include "Source.h" +#include "SourceList.h" +#include "Party.h" +#include "utils/Logger.h" + +#include + + +namespace Tomahawk +{ + +DatabaseCommand_PartyInfo::DatabaseCommand_PartyInfo( QObject* parent ) + : DatabaseCommandLoggable( parent ) +{ + tDebug() << Q_FUNC_INFO << "dbcmd partyinfo received"; +} + +/*private*/ +DatabaseCommand_PartyInfo::DatabaseCommand_PartyInfo( Action action, + const source_ptr& author ) + : DatabaseCommandLoggable( author ) + , m_action( action ) +{} + + +DatabaseCommand_PartyInfo* +DatabaseCommand_PartyInfo::broadcastParty( const source_ptr& author, + const party_ptr& party ) +{ + DatabaseCommand_PartyInfo* cmd = + new DatabaseCommand_PartyInfo( Broadcast, author ); + cmd->m_party = party; + cmd->m_guid = party->guid(); + + return cmd; +} + +DatabaseCommand_PartyInfo* +DatabaseCommand_PartyInfo::disbandParty( const Tomahawk::source_ptr& author, + const QString& partyGuid ) +{ + DatabaseCommand_PartyInfo* cmd = + new DatabaseCommand_PartyInfo( Disband, author ); + cmd->m_guid = partyGuid; + + return cmd; +} + + +void +DatabaseCommand_PartyInfo::exec( DatabaseImpl* lib ) +{ + Q_UNUSED( lib ); + Q_ASSERT( !source().isNull() ); + if ( m_action == Broadcast ) + { + tDebug() << Q_FUNC_INFO << "with action Info"; + Q_ASSERT( !( m_party.isNull() && m_v.isNull() ) ); + + uint now = 0; + if ( m_party.isNull() ) //we don't have the unserialized version, so we're remote + { + now = m_v.toMap()[ "createdon" ].toUInt(); + } + else //we're executing locally + { + if ( m_party->createdOn() == 0 ) //creating it locally now + now = QDateTime::currentDateTime().toTime_t(); + else + now = m_party->createdOn(); + m_party->setCreatedOn( now ); + } + } + else if ( m_action == Disband ) + { + tDebug() << Q_FUNC_INFO << "with action Disband"; + Q_ASSERT( !m_guid.isEmpty() ); + //postCommitHook does all the work for Disband + } +} + + +QVariant +DatabaseCommand_PartyInfo::partyV() const +{ + //QVariant conversion only happens when serializing the DBcmd + if ( m_action == Broadcast && m_v.isNull() ) + return QJson::QObjectHelper::qobject2qvariant( (QObject*)m_party.data() ); + else + return m_v; +} + + +void +DatabaseCommand_PartyInfo::postCommitHook() +{ + tDebug() << Q_FUNC_INFO << "about to visibly create party."; + + if ( source()->isLocal() ) + { + Servent::instance()->triggerDBSync(); + } + + if ( m_action == Broadcast ) + { + if ( m_party.isNull() ) //if I'm not local + { + source_ptr src = source(); + // db commands run on separate threads, which is good! + // But one must be careful what he does there, so to create something on the main thread, + // you either emit a signal or do a queued invoke. + QMetaObject::invokeMethod( SourceList::instance(), + "createPartyFromVariant", + Qt::BlockingQueuedConnection, + QGenericArgument( "Tomahawk::source_ptr", (const void*)&src ), + Q_ARG( QVariant, m_v ) ); + } + else + { + m_party->reportCreated( m_party ); + } + } + else if ( m_action == Disband ) + { + if ( source().isNull() ) + return; + + party_ptr party = source()->party(); + if ( party.isNull() ) + { + tDebug() << "The Party does not exist or has already been disbanded."; + return; + } + source()->removeParty(); + party->reportDeleted( party ); + } +} + +} //ns + diff --git a/src/libtomahawk/database/DatabaseCommand_PartyInfo.h b/src/libtomahawk/database/DatabaseCommand_PartyInfo.h new file mode 100644 index 0000000000..f2d1e1e820 --- /dev/null +++ b/src/libtomahawk/database/DatabaseCommand_PartyInfo.h @@ -0,0 +1,93 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2012-2013, Teo Mrnjavac + * + * 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 . + */ + +#ifndef DATABASECOMMAND_PARTYINFO_H +#define DATABASECOMMAND_PARTYINFO_H + +#include "Typedefs.h" +#include "DatabaseCommandLoggable.h" +#include "qjson/qobjecthelper.h" + +#include "DllMacro.h" + +namespace Tomahawk +{ + +class DLLEXPORT DatabaseCommand_PartyInfo : public DatabaseCommandLoggable +{ + Q_OBJECT + Q_PROPERTY( QVariant party READ partyV WRITE setPartyV ) + Q_PROPERTY( QString partyGuid READ partyGuid WRITE setPartyGuid ) + Q_PROPERTY( int action READ action WRITE setAction ) + +public: + //Rationale: dbcmd_lri is a singleton, so only the last of its type gets executed. + //For this reason it must have Actions, and if the last action in a non-trivial queue + //is Delete the LR is never even shown + + enum Action + { + Broadcast = 1, + Disband + }; + + //Do not construct through this ctor + explicit DatabaseCommand_PartyInfo( QObject* parent = 0 ); + + //Named ctors, use these! + static DatabaseCommand_PartyInfo* broadcastParty( const Tomahawk::source_ptr& author, + const Tomahawk::party_ptr& party ); + + static DatabaseCommand_PartyInfo* disbandParty( const Tomahawk::source_ptr& author, + const QString& partyGuid ); + + virtual ~DatabaseCommand_PartyInfo() {} + + QString commandname() const { return "partyinfo"; } + + virtual void exec( DatabaseImpl* lib ); + virtual void postCommitHook(); + virtual bool singletonCmd() const { return true; } + + QVariant partyV() const; + void setPartyV( const QVariant& v ) { m_v = v; } + + QString partyGuid() const { return m_guid; } + void setPartyGuid( const QString& guid ) { m_guid = guid; } + + int action() const { return m_action; } + void setAction( int a ) { m_action = static_cast< Action >( a ); } + +protected: + void setParty( const Tomahawk::party_ptr& party ); + + QVariant m_v; + QString m_guid; + +private: + explicit DatabaseCommand_PartyInfo( Action action, + const Tomahawk::source_ptr& author ); //used by named ctors + + Tomahawk::party_ptr m_party; + Action m_action; +}; + +} //ns + +#endif // DATABASECOMMAND_PARTYINFO_H diff --git a/src/libtomahawk/playlist/PlayableProxyModel.cpp b/src/libtomahawk/playlist/PlayableProxyModel.cpp index 0f8f05425d..66008db479 100644 --- a/src/libtomahawk/playlist/PlayableProxyModel.cpp +++ b/src/libtomahawk/playlist/PlayableProxyModel.cpp @@ -38,6 +38,8 @@ PlayableProxyModel::PlayableProxyModel( QObject* parent ) , m_hideDupeItems( false ) , m_maxVisibleItems( -1 ) , m_style( Detailed ) + , m_cutoffDirection( ShowAfter ) + , m_cutoffRow( -1 ) { m_playlistInterface = Tomahawk::playlistinterface_ptr( new Tomahawk::PlayableProxyModelPlaylistInterface( this ) ); @@ -132,6 +134,18 @@ PlayableProxyModel::setSourcePlayableModel( PlayableModel* sourceModel ) bool PlayableProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const { + // Before applying further filtering, we check if the cutoff lets this row through. + switch ( m_cutoffDirection ) + { + case ShowBefore: + if ( sourceRow >= m_cutoffRow ) + return false; + break; + case ShowAfter: + if ( sourceRow <= m_cutoffRow ) + return false; + } + PlayableItem* pi = itemFromIndex( sourceModel()->index( sourceRow, 0, sourceParent ) ); if ( !pi ) return false; @@ -610,6 +624,26 @@ PlayableProxyModel::updateDetailedInfo( const QModelIndex& index ) } +void +PlayableProxyModel::setFilterCutoff( PlayableProxyModel::CutoffDirection direction, int row ) +{ + Q_ASSERT( row >= -1 && row <= m_model->rowCount( QModelIndex() ) ); + bool isChanged = false; + if ( direction != m_cutoffDirection ) + { + m_cutoffDirection = direction; + isChanged = true; + } + if ( row != m_cutoffRow ) + { + m_cutoffRow = row; + isChanged = true; + } + if ( isChanged ) + invalidateFilter(); +} + + void PlayableProxyModel::setFilter( const QString& pattern ) { diff --git a/src/libtomahawk/playlist/PlayableProxyModel.h b/src/libtomahawk/playlist/PlayableProxyModel.h index db024337b9..6a58fcdca7 100644 --- a/src/libtomahawk/playlist/PlayableProxyModel.h +++ b/src/libtomahawk/playlist/PlayableProxyModel.h @@ -38,6 +38,9 @@ Q_OBJECT enum PlayableProxyModelRole { StyleRole = Qt::UserRole + 1, TypeRole }; + enum CutoffDirection + { ShowBefore, ShowAfter }; + explicit PlayableProxyModel ( QObject* parent = 0 ); virtual ~PlayableProxyModel() {} @@ -84,6 +87,22 @@ Q_OBJECT virtual void setFilter( const QString& pattern ); virtual void updateDetailedInfo( const QModelIndex& index ); + /** + * @brief setFilterRange sets an interval of rows that should be let through by the proxy. + * The range criterion is applied before other filtering criteria, i.e. the row number refers to + * the base model. + * This filter method effectively chops off the lower OR the upper part of a model, splitting it + * in two. + * @param direction either ShowBefore or ShowAfter, to choose whether to let through the first + * or the last portion of the base model. + * @param row the row number at which to cut off the model. + * @note The CutoffDirection is to be intended as a strict less-than or greater-than. Thus, e.g. + * in a model with 10 rows, ( ShowAfter, 3 ) lets through rows 4-9 and ( ShowBefore, 3 ) lets + * through rows 0-2. + * By default, the filter cutoff is ( ShowAfter, -1 ) to let everything through. + */ + virtual void setFilterCutoff( CutoffDirection direction, int row ); + signals: void filterChanged( const QString& filter ); @@ -120,6 +139,9 @@ private slots: QHash< PlayableItemStyle, QList > m_headerStyle; PlayableItemStyle m_style; + + CutoffDirection m_cutoffDirection; + int m_cutoffRow; }; #endif // TRACKPROXYMODEL_H diff --git a/src/libtomahawk/playlist/TrackView.cpp b/src/libtomahawk/playlist/TrackView.cpp index 8ac95af86b..06e4ff8f16 100644 --- a/src/libtomahawk/playlist/TrackView.cpp +++ b/src/libtomahawk/playlist/TrackView.cpp @@ -64,6 +64,8 @@ TrackView::TrackView( QWidget* parent ) , m_updateContextView( true ) , m_alternatingRowColors( true ) , m_contextMenu( new ContextMenu( this ) ) + , m_readOnly( false ) + , m_manualProgression( false ) { setFrameShape( QFrame::NoFrame ); setAttribute( Qt::WA_MacShowFocusRect, 0 ); @@ -307,6 +309,18 @@ TrackView::startPlayingFromStart() startAutoPlay( index ); } +void +TrackView::setReadOnly( bool readOnly ) +{ + m_readOnly = readOnly; +} + +void +TrackView::setManualProgression( bool manual ) +{ + m_manualProgression = manual; +} + void TrackView::autoPlayResolveFinished( const query_ptr& query, int row ) @@ -353,7 +367,8 @@ TrackView::onItemActivated( const QModelIndex& index ) if ( !index.isValid() ) return; - tryToPlayItem( index ); + if ( !m_readOnly && !m_manualProgression ) + tryToPlayItem( index ); emit itemActivated( index ); } @@ -479,7 +494,7 @@ TrackView::dragMoveEvent( QDragMoveEvent* event ) { QTreeView::dragMoveEvent( event ); - if ( model()->isReadOnly() ) + if ( model()->isReadOnly() || m_readOnly ) { event->ignore(); return; @@ -548,7 +563,7 @@ TrackView::dropEvent( QDropEvent* event ) tDebug() << Q_FUNC_INFO << "Drop Event accepted at row:" << index.row(); event->acceptProposedAction(); - if ( !model()->isReadOnly() ) + if ( !model()->isReadOnly() && !m_readOnly ) //both model and view must be non-readOnly { model()->dropMimeData( event->mimeData(), event->proposedAction(), index.row(), 0, index.parent() ); } @@ -687,7 +702,7 @@ TrackView::onCustomContextMenu( const QPoint& pos ) if ( !idx.isValid() ) return; - if ( model() && !model()->isReadOnly() ) + if ( model() && !model()->isReadOnly() && !m_readOnly ) m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionDelete ); if ( model() && qobject_cast< InboxModel* >( model() ) ) m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionMarkListened @@ -785,7 +800,7 @@ TrackView::setFilter( const QString& filter ) void TrackView::deleteSelectedItems() { - if ( !model()->isReadOnly() ) + if ( !model()->isReadOnly() && !m_readOnly ) { proxyModel()->removeIndexes( selectedIndexes() ); } diff --git a/src/libtomahawk/playlist/TrackView.h b/src/libtomahawk/playlist/TrackView.h index 0398c0dfa6..55228a0d81 100644 --- a/src/libtomahawk/playlist/TrackView.h +++ b/src/libtomahawk/playlist/TrackView.h @@ -89,6 +89,12 @@ Q_OBJECT // Starts playing from the beginning if resolved, or waits until a track is playable void startPlayingFromStart(); + void setReadOnly( bool readOnly = true ); //default: false, used by listening party history view + bool isReadOnly() const { return m_readOnly; } + + void setManualProgression( bool manual = true ); //default: false, used by listening party main view + bool isManualProgression() const { return m_manualProgression; } + public slots: virtual void onItemActivated( const QModelIndex& index ); @@ -150,6 +156,8 @@ private slots: bool m_resizing; bool m_dragging; QRect m_dropRect; + bool m_readOnly; + bool m_manualProgression; bool m_updateContextView; bool m_autoResize; diff --git a/src/libtomahawk/widgets/PartyCommandWidget.cpp b/src/libtomahawk/widgets/PartyCommandWidget.cpp new file mode 100644 index 0000000000..ebc3065966 --- /dev/null +++ b/src/libtomahawk/widgets/PartyCommandWidget.cpp @@ -0,0 +1,144 @@ +/* + * Copyright 2012, 2013 Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PartyCommandWidget.h" + +#include "SourceList.h" +#include "utils/TomahawkUtilsGui.h" + +#include +#include +#include + +namespace Tomahawk +{ + +PartyCommandWidget::PartyCommandWidget( QWidget* parent ) + : QWidget( parent ) + , m_buttonState( Disband ) //just so the first setting gets applied +{ + QVBoxLayout *mainLayout = new QVBoxLayout; + setLayout( mainLayout ); + + QHBoxLayout* controlsLayout = new QHBoxLayout; + mainLayout->addLayout( controlsLayout ); + m_joinLeaveButton = new QPushButton( this ); + controlsLayout->addWidget( m_joinLeaveButton ); + controlsLayout->addStretch(); + + m_listenersWidget = new QWidget( this ); + mainLayout->addWidget( m_listenersWidget ); + + m_buttonStrings[ Join ] = tr( "Join", "Button for a listener to join a party" ); + m_buttonStrings[ Leave ] = tr( "Leave", "Button for a listener to leave a party" ); + m_buttonStrings[ Disband ] = tr( "Disband", "Button for a DJ to disband a party" ); + + m_buttonIcons[ Join ] = QIcon( RESPATH "images/list-add.png" ); + m_buttonIcons[ Leave ] = QIcon( RESPATH "images/list-remove.png" ); + m_buttonIcons[ Disband ] = QIcon( RESPATH "images/delete.png" ); + + setButtonState( Join ); + + connect( m_joinLeaveButton, SIGNAL( clicked() ), + this, SLOT( onJoinLeaveButtonClicked() ) ); + + QBoxLayout* listenersWidgetLayout = new QVBoxLayout; + m_listenersWidget->setLayout( listenersWidgetLayout ); + m_avatarsLayout = new QHBoxLayout; //avatars go in here! + m_avatarsLayout->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); + listenersWidgetLayout->addLayout( m_avatarsLayout ); + m_unnamedListenersLabel = new QLabel( m_listenersWidget ); + QPalette pal = palette(); + pal.setColor( QPalette::Foreground, Qt::white ); + m_unnamedListenersLabel->setPalette( pal ); + QFont font = m_unnamedListenersLabel->font(); + font.setPointSize( TomahawkUtils::defaultFontSize() + 1 ); + m_unnamedListenersLabel->setFont( font ); + m_unnamedListenersLabel->setAlignment( Qt::AlignVCenter | Qt::AlignRight ); + + listenersWidgetLayout->addWidget( m_unnamedListenersLabel ); +} + + +void +PartyCommandWidget::setListeners( const QStringList& listenerDbids ) +{ + qDeleteAll( m_avatarLabels ); + m_avatarLabels.clear(); + m_unnamedListeners = 0; + foreach ( const QString& dbid, listenerDbids ) + { + const Tomahawk::source_ptr& s = SourceList::instance()->get( dbid ); + + if ( s.isNull() ) //means we don't know this listener + { + ++m_unnamedListeners; + } + else + { + QLabel* avatar = new QLabel( m_listenersWidget ); + QPixmap pxmp = s->avatar( TomahawkUtils::RoundedCorners ); + if ( pxmp.isNull() ) + { + if ( m_defaultAvatar.isNull() ) + m_defaultAvatar = TomahawkUtils::createRoundedImage( QPixmap( RESPATH "images/user-avatar.png" ), QSize( 32, 32 ) ); + pxmp = m_defaultAvatar; + } + avatar->setPixmap( pxmp ); + avatar->setToolTip( s->friendlyName() ); + m_avatarLabels.insert( dbid, avatar ); + } + } + + fillListeners(); +} + + +void +PartyCommandWidget::setButtonState( PartyCommandWidget::ButtonState state ) +{ + if ( state == m_buttonState ) + return; + + m_buttonState = state; + + m_joinLeaveButton->setText( m_buttonStrings[ state ] ); + m_joinLeaveButton->setIcon( m_buttonIcons[ state ] ); +} + + +void +PartyCommandWidget::fillListeners() +{ + if ( m_unnamedListeners ) + m_unnamedListenersLabel->setText( tr( "and %n other listener(s).", "", m_unnamedListeners ) ); + else + m_unnamedListenersLabel->setText( "" ); + foreach ( QLabel* avatar, m_avatarLabels ) + { + m_avatarsLayout->addWidget( avatar ); + } +} + + +void +PartyCommandWidget::onJoinLeaveButtonClicked() +{ + emit joinLeaveButtonClicked( m_buttonState ); +} +} diff --git a/src/libtomahawk/widgets/PartyCommandWidget.h b/src/libtomahawk/widgets/PartyCommandWidget.h new file mode 100644 index 0000000000..d7bd1cfc11 --- /dev/null +++ b/src/libtomahawk/widgets/PartyCommandWidget.h @@ -0,0 +1,80 @@ +/* + * Copyright 2012, 2013 Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PARTYCOMMANDWIDGET_H +#define PARTYCOMMANDWIDGET_H + +#include "Typedefs.h" + +#include +#include +#include + +class QBoxLayout; +class QLabel; +class QPushButton; + +namespace Tomahawk +{ + +class PartyCommandWidget : public QWidget +{ + Q_OBJECT +public: + enum ButtonState + { + Join, + Leave, + Disband + }; + + explicit PartyCommandWidget(QWidget *parent = 0); + virtual ~PartyCommandWidget() {} + +public slots: + void setListeners( const QStringList& listenerDbids ); + void setButtonState( ButtonState state ); + +signals: + void joinLeaveButtonClicked( PartyCommandWidget::ButtonState ); + +private slots: + void onJoinLeaveButtonClicked(); + +private: + void fillListeners(); + + ButtonState m_buttonState; + QMap< ButtonState, QIcon > m_buttonIcons; + QMap< ButtonState, QString > m_buttonStrings; + + QList< Tomahawk::source_ptr > m_listeners; + QPixmap m_defaultAvatar; + + QWidget* m_listenersWidget; + QMap< QString, QLabel* > m_avatarLabels; + int m_unnamedListeners; + QBoxLayout* m_avatarsLayout; + QLabel* m_unnamedListenersLabel; + + QPushButton* m_joinLeaveButton; +}; + +} + +#endif // PARTYCOMMANDWIDGET_H diff --git a/src/libtomahawk/widgets/PartyCurrentTrackWidget.cpp b/src/libtomahawk/widgets/PartyCurrentTrackWidget.cpp new file mode 100644 index 0000000000..169f115172 --- /dev/null +++ b/src/libtomahawk/widgets/PartyCurrentTrackWidget.cpp @@ -0,0 +1,137 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#include "PartyCurrentTrackWidget.h" + +#include "ElidedLabel.h" +#include "playlist/PlayableItem.h" +#include "playlist/PlaylistModel.h" +#include "Album.h" +#include "Artist.h" +#include "utils/TomahawkUtilsGui.h" +#include "Source.h" +#include "Result.h" + +#include +#include + +namespace Tomahawk +{ + +PartyCurrentTrackWidget::PartyCurrentTrackWidget( QWidget* parent ) + : QWidget( parent ) +{ + QHBoxLayout* mainLayout = new QHBoxLayout; + setLayout( mainLayout ); + + m_albumArtLabel = new QLabel( this ); + mainLayout->addWidget( m_albumArtLabel ); + m_albumArtLabel->setFixedSize( 80, 80 ); + + QVBoxLayout* vLayout = new QVBoxLayout; + mainLayout->addLayout( vLayout ); + + m_trackLabel = new ElidedLabel( this ); + m_trackLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + m_artistLabel = new ElidedLabel( this ); + m_artistLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + m_lovesLabel = new QLabel( this ); + m_lovesLabel->setAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + + QFont font = m_trackLabel->font(); + font.setBold( true ); + font.setPointSize( TomahawkUtils::defaultFontSize() + 5 ); + m_trackLabel->setFont( font ); + + font = m_artistLabel->font(); + font.setBold( true ); + font.setPointSize( TomahawkUtils::defaultFontSize() + 2 ); + m_artistLabel->setFont( font ); + + vLayout->addWidget( m_trackLabel ); + vLayout->addWidget( m_artistLabel ); + vLayout->addWidget( m_lovesLabel ); + + m_trackLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); + m_artistLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); + m_lovesLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); + + m_durationLabel = new QLabel( this ); + m_durationLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter ); + m_avatarLabel = new QLabel( this ); + font = m_durationLabel->font(); + font.setBold( true ); + m_durationLabel->setFont( font ); + + mainLayout->addWidget( m_durationLabel ); + mainLayout->addWidget( m_avatarLabel ); + + TomahawkUtils::unmarginLayout( mainLayout ); + mainLayout->setMargin( 8 ); + mainLayout->setSpacing( 8 ); + + m_albumArtPlaceholder.load( RESPATH "images/no-album-art-placeholder.png" ); + m_albumArtPlaceholder = m_albumArtPlaceholder.scaled( QSize( 80, 80 ), Qt::KeepAspectRatio, Qt::SmoothTransformation ); +} + + +void +PartyCurrentTrackWidget::setItem( const QPersistentModelIndex& idx ) +{ + int row = idx.row(); + const PlaylistModel* model = qobject_cast< const PlaylistModel* >( idx.model() ); + + PlayableItem* item = model->itemFromIndex( idx ); + + const Tomahawk::track_ptr q = item->query()->track(); + + m_trackLabel->setText( q->track() ); + m_artistLabel->setText( q->artist() ); + + QPixmap albumArtPixmap; + QSize aaSize = QSize( 80, 80 ); + if ( item->query()->track()->coverLoaded() ) + albumArtPixmap = TomahawkUtils::createRoundedImage( q->cover( aaSize ), aaSize ); + else + albumArtPixmap = TomahawkUtils::createRoundedImage( m_albumArtPlaceholder, aaSize ); + + m_albumArtLabel->setPixmap( albumArtPixmap ); + + QString lovesString = item->query()->queryTrack()->socialActionDescription( "Love", Tomahawk::Track::Detailed ); + m_lovesLabel->setText( lovesString ); + + unsigned int duration = q->duration(); + + // the following doesn't really work as it should, maybe because the model is reloaded on changes, + // and queries resolved again and again which doesn't leave enough time before the next track is + // popped up to this widget + // TODO: investigate + if ( !item->query()->results().isEmpty() && + !item->query()->results().first()->sourceIcon( TomahawkUtils::DropShadow, QSize( 32, 32 ) ).isNull() ) + { + const QPixmap sourceIcon = item->query()->results().first()->sourceIcon( TomahawkUtils::DropShadow, QSize( 32, 32 ) ); + m_avatarLabel->setPixmap( sourceIcon ); + } + + if ( duration > 0 ) + { + m_durationLabel->setText( TomahawkUtils::timeToString( duration ) ); + } +} + +} diff --git a/src/libtomahawk/widgets/PartyCurrentTrackWidget.h b/src/libtomahawk/widgets/PartyCurrentTrackWidget.h new file mode 100644 index 0000000000..3ac22e8c34 --- /dev/null +++ b/src/libtomahawk/widgets/PartyCurrentTrackWidget.h @@ -0,0 +1,53 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#ifndef PARTYCURRENTTRACKWIDGET_H +#define PARTYCURRENTTRACKWIDGET_H + +#include + + +class QLabel; +class ElidedLabel; +class QPersistentModelIndex; + +namespace Tomahawk +{ + +class PartyCurrentTrackWidget : public QWidget +{ + Q_OBJECT +public: + explicit PartyCurrentTrackWidget( QWidget* parent = 0 ); + + void setItem( const QPersistentModelIndex& idx ); + +private: + QLabel* m_albumArtLabel; + ElidedLabel* m_trackLabel; + ElidedLabel* m_artistLabel; + QLabel* m_lovesLabel; + QLabel* m_durationLabel; + QLabel* m_avatarLabel; + + QPixmap m_albumArtPlaceholder; +}; + +} + +#endif // PARTYCURRENTTRACKWIDGET_H diff --git a/src/libtomahawk/widgets/PartyWidget.cpp b/src/libtomahawk/widgets/PartyWidget.cpp new file mode 100644 index 0000000000..3f746f9c2d --- /dev/null +++ b/src/libtomahawk/widgets/PartyWidget.cpp @@ -0,0 +1,437 @@ +/* + * Copyright 2012, 2013 Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PartyWidget.h" + +#include "HeaderLabel.h" +#include "playlist/TrackView.h" +#include "playlist/PlaylistLargeItemDelegate.h" //TODO: make nice delegate for parties! +#include "playlist/PlayableProxyModel.h" +#include "playlist/PlayableItem.h" +#include "playlist/PlaylistModel.h" +#include "Source.h" +#include "utils/TomahawkUtilsGui.h" +#include "Typedefs.h" +#include "utils/Closure.h" +#include "database/Database.h" +#include "database/DatabaseImpl.h" +#include "LatchManager.h" +#include "Party.h" +#include "PartyCurrentTrackWidget.h" + +#include +#include +#include +#include +#include +#include + +#include "SourceList.h" + +namespace Tomahawk +{ + +PartyWidget::PartyWidget( QWidget* parent ) + : QWidget( parent ) + , m_drawerShown( false ) + , m_downArrow( QIcon( RESPATH "images/arrow-down-double.png" ) ) + , m_upArrow( QIcon( RESPATH "images/arrow-up-double.png" ) ) + , m_currentRow( -1 ) +{ + QBoxLayout* mainLayout = new QVBoxLayout; + setLayout( mainLayout ); + + QBoxLayout* mainHLayout = new QHBoxLayout; + QBoxLayout* innerMainLayout = new QVBoxLayout; + QBoxLayout* sidebarLayout = new QVBoxLayout; + + m_header = new BasicHeader( this ); + m_historyDrawer = new QWidget( this ); + m_body = new QWidget( this ); + m_commandWidget = new PartyCommandWidget( this ); + + mainLayout->addWidget( m_header ); + mainLayout->addLayout( mainHLayout ); + + mainHLayout->addLayout( innerMainLayout, 2 ); + mainHLayout->addLayout( sidebarLayout, 1 ); + + innerMainLayout->addWidget( m_historyDrawer ); + innerMainLayout->addWidget( m_body ); + + sidebarLayout->addWidget( m_commandWidget ); + + m_historyDrawer->setMaximumHeight( 0 ); + + TomahawkUtils::unmarginLayout( mainLayout ); + + // m_historyDrawer + QVBoxLayout* historyLayout = new QVBoxLayout; + m_historyDrawer->setLayout( historyLayout ); + TomahawkUtils::unmarginLayout( historyLayout ); + + m_historyView = new TrackView( m_historyDrawer ); + historyLayout->addWidget( m_historyView ); + + PlaylistLargeItemDelegate* historyDelegate = + new PlaylistLargeItemDelegate( PlaylistLargeItemDelegate::LovedTracks, + m_historyView, + m_historyView->proxyModel() ); + connect( historyDelegate, SIGNAL( updateIndex( QModelIndex ) ), + m_historyView, SLOT( update( QModelIndex ) ) ); + m_historyView->setItemDelegate( historyDelegate ); + m_historyView->proxyModel()->setStyle( PlayableProxyModel::Large ); + m_historyView->setReadOnly( true ); + connect ( m_historyView, SIGNAL( itemActivated( QModelIndex ) ), + this, SLOT( onHistoryItemActivated( QModelIndex ) ) ); + + // m_body + QVBoxLayout* bodyLayout = new QVBoxLayout; + m_body->setLayout( bodyLayout ); + TomahawkUtils::unmarginLayout( bodyLayout ); + + //TODO: replace with a "currently playing track" widget + m_showTracksString = tr( "Show previous tracks" ); + m_hideTracksString = tr( "Hide previous tracks" ); + m_previousTracksButton = new QPushButton( m_body ); + m_previousTracksButton->setText( m_showTracksString ); + m_previousTracksButton->setIcon( m_downArrow ); + connect( m_previousTracksButton, SIGNAL( clicked() ), + this, SLOT( toggleHistoryDrawer() ) ); + QFontMetrics fm = m_previousTracksButton->fontMetrics(); + int pixelsWide = qMax( fm.width( m_showTracksString ), fm.width( m_hideTracksString ) ) + + m_previousTracksButton->iconSize().width(); + m_previousTracksButton->setFixedWidth( pixelsWide + 24 /*a bit of padding*/ ); + + m_timeline = new QTimeLine( 250, this ); + m_timeline->setUpdateInterval( 5 ); + m_drawerH = -1; + connect( m_timeline, SIGNAL( frameChanged( int ) ), SLOT( onAnimationStep( int ) ) ); + connect( m_timeline, SIGNAL( finished() ), SLOT( onAnimationFinished() ) ); + + + QHBoxLayout* previousTracksLayout = new QHBoxLayout; + bodyLayout->addLayout( previousTracksLayout ); + previousTracksLayout->addStretch(); + previousTracksLayout->addWidget( m_previousTracksButton ); + + m_currentTrackWidget = new PartyCurrentTrackWidget( m_body ); + bodyLayout->addWidget( m_currentTrackWidget ); + m_currentTrackWidget->setFixedHeight( 80 + 2*8 ); + + HeaderLabel* upcomingTracksLabel = new HeaderLabel( m_body ); + upcomingTracksLabel->setText( tr( "Upcoming tracks" ) ); + upcomingTracksLabel->setStyleSheet( "border-bottom-left-radius: 16px;"); + bodyLayout->addWidget( upcomingTracksLabel ); + + m_view = new TrackView( m_body ); + bodyLayout->addWidget( m_view ); + + m_pixmap = QIcon( RESPATH "images/party.png" ).pixmap( 64 ); + + PlaylistLargeItemDelegate* delegate = + new PlaylistLargeItemDelegate( PlaylistLargeItemDelegate::LovedTracks, + m_view, + m_view->proxyModel() ); + connect( delegate, SIGNAL( updateIndex( QModelIndex ) ), + m_view, SLOT( update( QModelIndex ) ) ); + m_view->setItemDelegate( delegate ); + m_view->proxyModel()->setStyle( PlayableProxyModel::Large ); + connect( m_view, SIGNAL( itemActivated( QModelIndex ) ), + this, SLOT( onMainViewItemActivated( QModelIndex ) ) ); + + connect( m_commandWidget, SIGNAL( joinLeaveButtonClicked( PartyCommandWidget::ButtonState ) ), + this, SLOT( onJoinLeaveButtonClicked( PartyCommandWidget::ButtonState ) ) ); +} + + +QString +PartyWidget::title() const +{ + return m_view->title(); +} + + +QString +PartyWidget::description() const +{ + //TODO: implement! + return m_view->description(); +} + + +QPixmap +PartyWidget::pixmap() const +{ + return m_pixmap; +} + + +void +PartyWidget::setParty( const party_ptr& party ) +{ + Q_ASSERT( m_party.isNull() ); //TODO: does it ever happen that m_party is already assigned? + if ( m_party ) + return; + + m_party = party; + + PlaylistModel* model = new PlaylistModel(); + model->loadPlaylist( party->playlist() ); + + m_historyView->setPlayableModel( model ); + m_historyView->setSortingEnabled( false ); + + m_view->setPlayableModel( model ); + m_view->setSortingEnabled( false ); + if ( party->author()->isLocal() ) + { + m_view->setManualProgression( true ); + } + else + { + m_view->setReadOnly( true ); + } + + m_header->setCaption( model->title() ); + m_header->setDescription( model->description() ); + m_header->setPixmap( m_pixmap ); + m_view->setEmptyTip( tr( "This party is currently empty.\n" + "Add some tracks to it and enjoy the music with your friends!" ) ); + + connect( m_party.data(), SIGNAL( listenersChanged() ), + this, SLOT( onListenersChanged() ) ); + onListenersChanged(); + + connect( model, SIGNAL( dataChanged( QModelIndex, QModelIndex ) ), + this, SLOT( onDataChanged( QModelIndex, QModelIndex ) ) ); +} + + +void +PartyWidget::resizeEvent( QResizeEvent* e ) +{ + QWidget::resizeEvent( e ); + if ( m_drawerShown ) + m_historyDrawer->setFixedHeight( height() * 0.3 ); +} + + +void +PartyWidget::toggleHistoryDrawer() +{ + m_timeline->setEasingCurve( QEasingCurve::OutBack ); + m_timeline->setFrameRange( 0, height() * 0.3 ); + + if ( !m_drawerShown ) + { + m_previousTracksButton->setText( m_hideTracksString ); + m_previousTracksButton->setIcon( m_upArrow ); + + m_timeline->setDirection( QTimeLine::Forward ); + m_timeline->start(); + + m_drawerShown = true; + } + else + { + m_previousTracksButton->setText( m_showTracksString ); + m_previousTracksButton->setIcon( m_downArrow ); + + m_timeline->setDirection( QTimeLine::Backward ); + m_timeline->start(); + + m_drawerShown = false; + } +} + +void +PartyWidget::onAnimationStep( int step ) +{ + m_drawerH = step; + m_historyDrawer->setFixedHeight( m_drawerH ); +} + +void +PartyWidget::onAnimationFinished() +{ + if ( m_drawerShown ) + m_historyDrawer->setFixedHeight( m_drawerH ); + else + m_historyDrawer->setFixedHeight( 0 ); + m_drawerH = -1; +} + + +void +PartyWidget::onListenersChanged() +{ + if ( m_party ) + { + m_commandWidget->setListeners( m_party->listenerIds() ); + m_header->setDescription( m_view->model()->description() ); + + // If I'm the DJ + if ( m_party->author() == SourceList::instance()->getLocal() ) + { + m_commandWidget->setButtonState( PartyCommandWidget::Disband ); + } + // If I'm one of the listeners + else + { + // I need to ask the DatabaseImpl directly because Source::userName() answers just + // "My Collection" instead of the dbid if the source is local. + // This is probably a FIXME. + if ( m_party->listenerIds().contains( Tomahawk::Database::instance()->impl()->dbid() ) ) + { + m_commandWidget->setButtonState( PartyCommandWidget::Leave ); + } + else + { + m_commandWidget->setButtonState( PartyCommandWidget::Join ); + } + } + } +} + + +void +PartyWidget::onJoinLeaveButtonClicked( PartyCommandWidget::ButtonState state ) +{ + Tomahawk::LatchManager* lman = Tomahawk::LatchManager::instance(); + Tomahawk::source_ptr lrSource = m_party->author(); + + switch ( state ) + { + case PartyCommandWidget::Join: + if ( lman->isLatched( lrSource ) ) + lman->catchUpRequest(); + else + lman->latchRequest( lrSource ); + + lman->latchModeChangeRequest( lrSource, + true /*always latch on realtime if we are a party*/ ); + + break; + case PartyCommandWidget::Leave: + lman->unlatchRequest( lrSource ); + break; + case PartyCommandWidget::Disband: + qDebug() << "Doing delete of party:" << m_view->model()->title(); + Tomahawk::Party::remove( m_party ); + } +} + + +void +PartyWidget::onDataChanged( const QModelIndex& topLeft, const QModelIndex& bottomRight ) +{ + Q_UNUSED( topLeft ); + Q_UNUSED( bottomRight ); + + PlayableModel* model = m_view->model(); + if ( model->currentItem().row() != m_currentRow ) + { + m_currentRow = ( model->currentItem() == QModelIndex() ) ? -1 : model->currentItem().row(); + m_view->proxyModel()->setFilterCutoff( PlayableProxyModel::ShowAfter, m_currentRow ); + m_historyView->proxyModel()->setFilterCutoff( PlayableProxyModel::ShowBefore, m_currentRow ); + if ( m_currentRow > -1 ) + m_currentTrackWidget->setItem( model->currentItem() ); + } +} + + +void +PartyWidget::onHistoryItemActivated( const QModelIndex& idx ) +{ + Q_ASSERT( !m_party.isNull() ); + Q_ASSERT( !m_party->author().isNull() ); + + PlaylistModel* model = qobject_cast< PlaylistModel* >( m_view->model() ); + Q_ASSERT( model ); + + if ( m_party->author()->isLocal() ) + { + PlayableItem* item = model->itemFromIndex( m_historyView->proxyModel()->mapToSource( idx ) ); + if ( !item->entry().isNull() ) + { + QList< Tomahawk::plentry_ptr > entries; + const Tomahawk::plentry_ptr& pe = item->entry(); + entries.append( pe ); + model->insertEntries( entries, m_currentRow + 1 ); + playlistInterface()->nextResult(); + } + } + else + { + Tomahawk::LatchManager* lman = Tomahawk::LatchManager::instance(); + if ( !lman->isLatched( m_party->author() ) ) + { + onJoinLeaveButtonClicked( PartyCommandWidget::Join ); + } + } +} + +void +PartyWidget::onMainViewItemActivated( const QModelIndex& idx ) +{ + Q_ASSERT( !m_party.isNull() ); + Q_ASSERT( !m_party->author().isNull() ); + + PlaylistModel* model = qobject_cast< PlaylistModel* >( m_view->model() ); + Q_ASSERT( model ); + + if ( m_party->author()->isLocal() ) + { + PlayableItem* item = model->itemFromIndex( m_view->proxyModel()->mapToSource( idx ) ); + if ( !item->entry().isNull() ) + { + QList< Tomahawk::plentry_ptr > entries; + const Tomahawk::plentry_ptr& pe = item->entry(); + entries.append( pe ); + + model->removeIndex( m_view->proxyModel()->mapToSource( idx ) ); + model->insertEntries( entries, m_currentRow + 1 ); + m_view->startPlayingFromStart(); + } + } + else + { + Tomahawk::LatchManager* lman = Tomahawk::LatchManager::instance(); + if ( !lman->isLatched( m_party->author() ) ) + { + onJoinLeaveButtonClicked( PartyCommandWidget::Join ); + } + } +} + + +Tomahawk::playlistinterface_ptr +PartyWidget::playlistInterface() const +{ + if ( !m_view->model() ) + { + return Tomahawk::playlistinterface_ptr(); + } + else + { + return m_view->proxyModel()->playlistInterface(); + } +} + +} diff --git a/src/libtomahawk/widgets/PartyWidget.h b/src/libtomahawk/widgets/PartyWidget.h new file mode 100644 index 0000000000..91b84588dc --- /dev/null +++ b/src/libtomahawk/widgets/PartyWidget.h @@ -0,0 +1,106 @@ +/* + * Copyright 2012, 2013 Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PARTYWIDGET_H +#define PARTYWIDGET_H + +#include + +#include "Typedefs.h" +#include "Party.h" +#include "PartyCommandWidget.h" +#include "BasicHeader.h" +#include "ViewPage.h" + +#include + +class QTimeLine; +class QPushButton; +class QModelIndex; +class TrackView; + +namespace Tomahawk +{ + +class PartyCurrentTrackWidget; + +class DLLEXPORT PartyWidget : public QWidget, public Tomahawk::ViewPage +{ + Q_OBJECT +public: + explicit PartyWidget( QWidget* parent = 0 ); + virtual ~PartyWidget() {} + + QWidget* widget() { return this; } + Tomahawk::playlistinterface_ptr playlistInterface() const; + + QString title() const; + QString description() const; + QPixmap pixmap() const; + + bool isTemporaryPage() const { return false; } + bool showInfoBar() const { return false; } //we take care of our own header + + bool jumpToCurrentTrack() { return true; } //FIXME: what does this do? + + void setParty( const party_ptr& party ); + +protected: + void resizeEvent( QResizeEvent* e ); + +private slots: + void toggleHistoryDrawer(); + void onAnimationStep( int ); + void onAnimationFinished(); + void onListenersChanged(); + void onJoinLeaveButtonClicked( PartyCommandWidget::ButtonState ); + + void onDataChanged( const QModelIndex&, const QModelIndex& ); + + void onHistoryItemActivated( const QModelIndex& idx ); + void onMainViewItemActivated( const QModelIndex& idx ); + +private: + BasicHeader *m_header; + PartyCommandWidget *m_commandWidget; + QWidget* m_historyDrawer; + QPushButton* m_previousTracksButton; + TrackView* m_historyView; + QWidget* m_body; + PartyCurrentTrackWidget* m_currentTrackWidget; + + TrackView* m_view; + Tomahawk::party_ptr m_party; + + QPixmap m_pixmap; + + //history drawer animation + QTimeLine* m_timeline; + int m_drawerH; + bool m_drawerShown; + QIcon m_downArrow; + QIcon m_upArrow; + QString m_showTracksString; + QString m_hideTracksString; + + int m_currentRow; +}; + +} + +#endif // PARTYWIDGET_H diff --git a/src/tomahawk/CMakeLists.txt b/src/tomahawk/CMakeLists.txt index 1d18ea3e17..0fb16d597c 100644 --- a/src/tomahawk/CMakeLists.txt +++ b/src/tomahawk/CMakeLists.txt @@ -49,6 +49,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui} sourcetree/items/GroupItem.cpp sourcetree/items/HistoryItem.cpp sourcetree/items/InboxItem.cpp + sourcetree/items/PartiesCategoryItem.cpp + sourcetree/items/PartyItem.cpp TomahawkTrayIcon.cpp AudioControls.cpp diff --git a/src/tomahawk/TomahawkApp.cpp b/src/tomahawk/TomahawkApp.cpp index ff151fc7dc..f307c97f62 100644 --- a/src/tomahawk/TomahawkApp.cpp +++ b/src/tomahawk/TomahawkApp.cpp @@ -415,6 +415,7 @@ TomahawkApp::registerMetaTypes() qRegisterMetaType< Tomahawk::playlisttemplate_ptr >("Tomahawk::playlisttemplate_ptr"); qRegisterMetaType< Tomahawk::dynplaylist_ptr >("Tomahawk::dynplaylist_ptr"); qRegisterMetaType< Tomahawk::geninterface_ptr >("Tomahawk::geninterface_ptr"); + qRegisterMetaType< Tomahawk::party_ptr >("Tomahawk::party_ptr"); qRegisterMetaType< Tomahawk::PlaybackLog >("Tomahawk::PlaybackLog"); qRegisterMetaType< QList >("QList"); qRegisterMetaType< QList >("QList"); diff --git a/src/tomahawk/TomahawkWindow.cpp b/src/tomahawk/TomahawkWindow.cpp index 9123b7c7c8..f372a29431 100644 --- a/src/tomahawk/TomahawkWindow.cpp +++ b/src/tomahawk/TomahawkWindow.cpp @@ -65,6 +65,7 @@ #include "filemetadata/ScanManager.h" #include "Playlist.h" +#include "Party.h" #include "Query.h" #include "Artist.h" #include "ViewManager.h" @@ -1130,6 +1131,17 @@ TomahawkWindow::createPlaylist() } +void +TomahawkWindow::createParty() +{ + QString creator = SourceList::instance()->getLocal()->nodeId(); + QString partyName = tr( "%1's party" ).arg( creator ); + + party_ptr party = Tomahawk::Party::createNew( partyName ); + ViewManager::instance()->show( party ); +} + + void TomahawkWindow::audioStarted() { diff --git a/src/tomahawk/TomahawkWindow.h b/src/tomahawk/TomahawkWindow.h index bbf910e1c5..dbbca6180e 100644 --- a/src/tomahawk/TomahawkWindow.h +++ b/src/tomahawk/TomahawkWindow.h @@ -94,6 +94,7 @@ Q_OBJECT public slots: void createStation(); void createPlaylist(); + void createParty(); void loadSpiff(); void showSettingsDialog(); void showDiagnosticsDialog(); diff --git a/src/tomahawk/sourcetree/SourceDelegate.cpp b/src/tomahawk/sourcetree/SourceDelegate.cpp index edc7ccd9b0..ca92f89d52 100644 --- a/src/tomahawk/sourcetree/SourceDelegate.cpp +++ b/src/tomahawk/sourcetree/SourceDelegate.cpp @@ -4,7 +4,7 @@ * Copyright 2011-2012, Leo Franchi * Copyright 2011, Michael Zanetti * Copyright 2010-2012, Jeff Mitchell - * Copyright 2013, Teo Mrnjavac + * Copyright 2012-2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include "items/PlaylistItems.h" #include "items/CategoryItems.h" #include "items/TemporaryPageItem.h" +#include "items/PartyItem.h" #include "items/ScriptCollectionItem.h" #include "items/InboxItem.h" @@ -45,6 +46,7 @@ #include "utils/Logger.h" #include +#include #include #include #include @@ -174,11 +176,12 @@ SourceDelegate::paintDecorations( QPainter* painter, const QStyleOptionViewItem& // Paint the speaker icon next to the currently-playing playlist const bool playable = ( type == SourcesModel::StaticPlaylist || - type == SourcesModel::AutomaticPlaylist || - type == SourcesModel::Station || - type == SourcesModel::TemporaryPage || - type == SourcesModel::LovedTracksPage || - type == SourcesModel::GenericPage ); + type == SourcesModel::AutomaticPlaylist || + type == SourcesModel::Station || + type == SourcesModel::TemporaryPage || + type == SourcesModel::LovedTracksPage || + type == SourcesModel::GenericPage || + type == SourcesModel::Party ); const bool playing = ( AudioEngine::instance()->isPlaying() || AudioEngine::instance()->isPaused() ); if ( playable && playing && item->isBeingPlayed() ) @@ -564,7 +567,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co { paintCollection( painter, optIndentation, index ); } - else if ( ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) && + else if ( ( type == SourcesModel::StaticPlaylist || + type == SourcesModel::CategoryAdd ) && m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 ) { optIndentation.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() ); @@ -778,7 +782,8 @@ SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, co void SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const { - SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); + SourcesModel::RowType type = static_cast< SourcesModel::RowType >( + index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() ); if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station ) @@ -792,6 +797,9 @@ SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewIte #endif editor->setGeometry( newGeometry ); } + else if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == + SourcesModel::Party ) + editor->setGeometry( option.rect.adjusted( 16 /*icon! hardcoded!*/, 0, 0, 0 ) ); //TODO: make smarter else QStyledItemDelegate::updateEditorGeometry( editor, option, index ); @@ -997,6 +1005,23 @@ SourceDelegate::hoveredDropType() const return m_hoveredDropType; } +void +SourceDelegate::setEditorData( QWidget* editor, const QModelIndex& index ) const +{ + //We need to special-case the initial editor text for Parties because an item shows not + //only the party's title but also other non-editable data such as the name of the owner (i.e. the + //name of the source the party is hosted by). + if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::Party ) + { + PartyItem* lrItem = qobject_cast< PartyItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() ); + QLineEdit* textEditor = qobject_cast< QLineEdit* >( editor ); + if ( textEditor ) + textEditor->setText( lrItem->editorText() ); + } + else + QStyledItemDelegate::setEditorData( editor, index ); +} + void SourceDelegate::hovered( const QModelIndex& index, const QMimeData* mimeData ) diff --git a/src/tomahawk/sourcetree/SourceDelegate.h b/src/tomahawk/sourcetree/SourceDelegate.h index b926e96336..5ebe6f2c59 100644 --- a/src/tomahawk/sourcetree/SourceDelegate.h +++ b/src/tomahawk/sourcetree/SourceDelegate.h @@ -41,6 +41,7 @@ class SourceDelegate : public QStyledItemDelegate void dragLeaveEvent(); SourceTreeItem::DropType hoveredDropType() const; + virtual void setEditorData( QWidget* editor, const QModelIndex& index ) const; signals: void clicked( const QModelIndex& idx ); diff --git a/src/tomahawk/sourcetree/SourceTreeView.cpp b/src/tomahawk/sourcetree/SourceTreeView.cpp index 28f136fb26..79a7778e54 100644 --- a/src/tomahawk/sourcetree/SourceTreeView.cpp +++ b/src/tomahawk/sourcetree/SourceTreeView.cpp @@ -3,7 +3,7 @@ * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell * Copyright 2010-2012, Leo Franchi - * Copyright 2013, Teo Mrnjavac + * Copyright 2012-2013, Teo Mrnjavac * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,12 +23,14 @@ #include "ActionCollection.h" #include "Playlist.h" +#include "Party.h" #include "ViewManager.h" #include "SourcesProxyModel.h" #include "SourceList.h" #include "SourceDelegate.h" #include "sourcetree/items/PlaylistItems.h" #include "sourcetree/items/SourceItem.h" +#include "sourcetree/items/PartyItem.h" #include "SourcePlaylistInterface.h" #include "TomahawkSettings.h" #include "DropJob.h" @@ -64,9 +66,10 @@ using namespace Tomahawk; SourceTreeView::SourceTreeView( QWidget* parent ) : QTreeView( parent ) - , m_latchManager( new LatchManager( this ) ) , m_dragging( false ) { + m_latchManager = LatchManager::instance(); + setProperty( "flattenBranches", QVariant( true ) ); setFrameShape( QFrame::NoFrame ); @@ -153,6 +156,8 @@ SourceTreeView::SourceTreeView( QWidget* parent ) connect( renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) ); ViewManager::instance()->showDynamicPage( Tomahawk::Widgets::DASHBOARD_VIEWPAGE_NAME ); + + QTimer::singleShot( 0, m_model, SLOT( performDefaultExpandForTopLevelItems() ) ); } @@ -168,6 +173,8 @@ SourceTreeView::setupMenus() m_roPlaylistMenu.clear(); m_latchMenu.clear(); m_privacyMenu.clear(); + m_partyMenu.clear(); + m_roPartyMenu.clear(); bool readonly = true; SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt(); @@ -182,6 +189,16 @@ SourceTreeView::setupMenus() readonly = !playlist->author()->isLocal(); } } + else if ( type == SourcesModel::Party ) + { + const PartyItem* item = itemFromIndex< PartyItem >( m_contextMenuIndex ); + const party_ptr party = item->party(); + + if ( !party.isNull() ) + { + readonly = !party->author()->isLocal(); + } + } QAction* latchOnAction = ActionCollection::instance()->getAction( "latchOn" ); m_latchMenu.addAction( latchOnAction ); @@ -273,10 +290,21 @@ SourceTreeView::setupMenus() connect( loadPlaylistAction, SIGNAL( triggered() ), SLOT( loadPlaylist() ) ); connect( renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) ); - connect( deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylist() ) ); + connect( deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylistOrParty() ) ); connect( copyPlaylistAction, SIGNAL( triggered() ), SLOT( copyPlaylistLink() ) ); connect( addToLocalAction, SIGNAL( triggered() ), SLOT( addToLocal() ) ); connect( latchOnAction, SIGNAL( triggered() ), SLOT( latchOnOrCatchUp() ) ); + + QAction* renamePartyAction = ActionCollection::instance()->getAction( "renameParty" ); + QAction* disbandPartyAction = ActionCollection::instance()->getAction( "disbandParty" ); + + m_partyMenu.addAction( renamePartyAction ); + m_partyMenu.addAction( disbandPartyAction ); + + connect( renamePartyAction, SIGNAL( triggered() ), SLOT( renameParty() ) ); + connect( disbandPartyAction, SIGNAL( triggered() ), SLOT( deletePlaylistOrParty() ) ); + + //TODO: add actions to read-only LR menu m_roPartyMenu } @@ -373,7 +401,7 @@ SourceTreeView::loadPlaylist() void -SourceTreeView::deletePlaylist( const QModelIndex& idxIn ) +SourceTreeView::deletePlaylistOrParty( const QModelIndex& idxIn ) { QModelIndex idx = idxIn.isValid() ? idxIn : m_contextMenuIndex; if ( !idx.isValid() ) @@ -381,49 +409,70 @@ SourceTreeView::deletePlaylist( const QModelIndex& idxIn ) SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( idx, SourcesModel::SourceTreeItemTypeRole ).toInt(); QString typeDesc; + QString actionDesc; + QString buttonDesc; switch ( type ) { case SourcesModel::StaticPlaylist: typeDesc = tr( "playlist" ); + actionDesc = tr( "delete" ); + buttonDesc = tr( "Delete" ); break; case SourcesModel::AutomaticPlaylist: typeDesc = tr( "automatic playlist" ); + actionDesc = tr( "delete" ); + buttonDesc = tr( "Delete" ); break; case SourcesModel::Station: typeDesc = tr( "station" ); + actionDesc = tr( "delete" ); + buttonDesc = tr( "Delete" ); + break; + + case SourcesModel::Party: + typeDesc = tr( "party" ); + actionDesc = tr( "disband" ); + buttonDesc = tr( "Disband" ); break; default: Q_ASSERT( false ); } - PlaylistItem* item = itemFromIndex< PlaylistItem >( idx ); - playlist_ptr playlist = item->playlist(); QPoint rightCenter = viewport()->mapToGlobal( visualRect( idx ).topRight() + QPoint( 0, visualRect( idx ).height() / 2 ) ); Tomahawk::PlaylistDeleteQuestions questions; - if ( playlist->hasCustomDeleter() ) + // corner case, if it's a playlist i.e. not a party + if ( type == SourcesModel::StaticPlaylist || + type == SourcesModel::AutomaticPlaylist || + type == SourcesModel::Station ) { - foreach ( Tomahawk::PlaylistUpdaterInterface* updater, playlist->updaters() ) + PlaylistItem* item = itemFromIndex< PlaylistItem >( idx ); + playlist_ptr playlist = item->playlist(); + + if ( playlist->hasCustomDeleter() ) { - if ( updater->deleteQuestions().isEmpty() ) - continue; + foreach ( Tomahawk::PlaylistUpdaterInterface* updater, playlist->updaters() ) + { + if ( updater->deleteQuestions().isEmpty() ) + continue; - questions.append( updater->deleteQuestions() ); + questions.append( updater->deleteQuestions() ); + } } } if ( m_popupDialog.isNull() ) { m_popupDialog = QPointer< SourceTreePopupDialog >( new SourceTreePopupDialog() ); - connect( m_popupDialog.data(), SIGNAL( result( bool ) ), this, SLOT( onDeletePlaylistResult( bool ) ) ); + connect( m_popupDialog.data(), SIGNAL( result( bool ) ), this, SLOT( onDeletePlaylistOrPartyResult( bool ) ) ); } - m_popupDialog.data()->setMainText( tr( "Would you like to delete the %1 \"%2\"?", "e.g. Would you like to delete the playlist named Foobar?" ) - .arg( typeDesc ).arg( idx.data().toString() ) ); - m_popupDialog.data()->setOkButtonText( tr( "Delete" ) ); + m_popupDialog.data()->setMainText( tr( "Would you like to %1 the %2 \"%3\"?", "e.g. Would you like to delete the playlist named Foobar?" ) + .arg( actionDesc ).arg( typeDesc ).arg( idx.data().toString() ) ); + m_popupDialog.data()->setOkButtonText( buttonDesc ); m_popupDialog.data()->setProperty( "idx", QVariant::fromValue< QModelIndex >( idx ) ); if ( !questions.isEmpty() ) @@ -431,12 +480,11 @@ SourceTreeView::deletePlaylist( const QModelIndex& idxIn ) m_popupDialog.data()->move( rightCenter.x() - m_popupDialog.data()->offset(), rightCenter.y() - m_popupDialog.data()->sizeHint().height() / 2. ); m_popupDialog.data()->show(); - } void -SourceTreeView::onDeletePlaylistResult( bool result ) +SourceTreeView::onDeletePlaylistOrPartyResult( bool result ) { Q_ASSERT( !m_popupDialog.isNull() ); @@ -472,6 +520,13 @@ SourceTreeView::onDeletePlaylistResult( bool result ) tDebug() << Q_FUNC_INFO << "Deleting dynamic playlist:" << playlist->guid() << playlist->title(); DynamicPlaylist::removalHandler()->remove( playlist ); } + else if ( type == SourcesModel::Party ) + { + PartyItem* item = itemFromIndex< PartyItem >( idx ); + party_ptr party = item->party(); + qDebug() << "Doing delete of party:" << party->playlist()->title(); + Party::remove( party ); + } } @@ -572,6 +627,15 @@ SourceTreeView::addToLocal() } } +void +SourceTreeView::renameParty() +{ + if ( !m_contextMenuIndex.isValid() && !selectionModel()->selectedIndexes().isEmpty() ) + edit( selectionModel()->selectedIndexes().first() ); + else + edit( m_contextMenuIndex ); +} + void SourceTreeView::latchOnOrCatchUp() @@ -687,6 +751,14 @@ SourceTreeView::onCustomContextMenu( const QPoint& pos ) else if ( !item->source().isNull() ) m_privacyMenu.exec( mapToGlobal( pos ) ); } + else if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Party ) + { + PartyItem* item = itemFromIndex< PartyItem >( m_contextMenuIndex ); + if ( item->party()->author()->isLocal() ) + m_partyMenu.exec( mapToGlobal( pos ) ); + else + m_roPartyMenu.exec( mapToGlobal( pos ) ); + } else if ( !customActions.isEmpty() ) { QMenu customMenu; @@ -718,6 +790,7 @@ SourceTreeView::dragEnterEvent( QDragEnterEvent* event ) void SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) { + tDebug() << Q_FUNC_INFO; QTreeView::dragLeaveEvent( event ); m_dragging = false; @@ -732,6 +805,7 @@ SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event ) void SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) { + tDebug() << Q_FUNC_INFO; bool accept = false; // Don't highlight the drop for a playlist, as it won't get added to the playlist but created generally @@ -809,8 +883,10 @@ SourceTreeView::dragMoveEvent( QDragMoveEvent* event ) void SourceTreeView::dropEvent( QDropEvent* event ) { + tDebug() << Q_FUNC_INFO; const QPoint pos = event->pos(); const QModelIndex index = indexAt( pos ); + tDebug() << "Dropped on item " << itemFromIndex< SourceTreeItem >( index )->text(); if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::LovedTracksPage @@ -869,7 +945,7 @@ SourceTreeView::keyPressEvent( QKeyEvent* event ) Q_ASSERT( item ); if ( item->playlist()->author()->isLocal() ) - deletePlaylist( idx ); + deletePlaylistOrParty( idx ); } event->accept(); } diff --git a/src/tomahawk/sourcetree/SourceTreeView.h b/src/tomahawk/sourcetree/SourceTreeView.h index d08454f8d5..91815841e9 100644 --- a/src/tomahawk/sourcetree/SourceTreeView.h +++ b/src/tomahawk/sourcetree/SourceTreeView.h @@ -71,10 +71,11 @@ private slots: void onItemDoubleClicked( const QModelIndex& idx ); void loadPlaylist(); - void deletePlaylist( const QModelIndex& = QModelIndex() ); + void deletePlaylistOrParty( const QModelIndex& = QModelIndex() ); void copyPlaylistLink(); void exportPlaylist(); void addToLocal(); + void renameParty(); void latchOnOrCatchUp(); void latchOff(); @@ -85,7 +86,7 @@ private slots: void onCustomContextMenu( const QPoint& pos ); void onSelectionChanged(); - void onDeletePlaylistResult( bool result ); + void onDeletePlaylistOrPartyResult( bool result ); void shortLinkReady( const Tomahawk::playlist_ptr& playlist, const QUrl& shortUrl ); @@ -118,6 +119,8 @@ private slots: QMenu m_roPlaylistMenu; QMenu m_latchMenu; QMenu m_privacyMenu; + QMenu m_partyMenu; + QMenu m_roPartyMenu; bool m_dragging; QRect m_dropRect; diff --git a/src/tomahawk/sourcetree/SourcesModel.cpp b/src/tomahawk/sourcetree/SourcesModel.cpp index b1f40bbc9f..e27384f828 100644 --- a/src/tomahawk/sourcetree/SourcesModel.cpp +++ b/src/tomahawk/sourcetree/SourcesModel.cpp @@ -23,9 +23,11 @@ #include "sourcetree/items/ScriptCollectionItem.h" #include "sourcetree/items/SourceTreeItem.h" #include "sourcetree/items/SourceItem.h" +#include "sourcetree/items/CategoryItems.h" #include "sourcetree/items/GroupItem.h" #include "sourcetree/items/GenericPageItems.h" #include "sourcetree/items/HistoryItem.h" +#include "sourcetree/items/PartiesCategoryItem.h" #include "sourcetree/items/LovedTracksItem.h" #include "sourcetree/items/InboxItem.h" #include "SourceList.h" @@ -327,8 +329,10 @@ SourcesModel::appendGroups() boost::bind( &ViewManager::recentPlaysWidget, ViewManager::instance() ) ); recent->setSortValue( 7 ); - m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 4 ); - m_cloudGroup = new GroupItem( this, m_rootItem, tr( "Cloud" ), 5 ); + m_partiesGroup = new PartiesCategoryItem( this, m_rootItem, 8 ); + + m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 9 ); + m_cloudGroup = new GroupItem( this, m_rootItem, tr( "Cloud" ), 10 ); endInsertRows(); @@ -777,3 +781,9 @@ SourcesModel::sourcesWithViewPage() const { return m_sourcesWithViewPage; } + + +void SourcesModel::performDefaultExpandForTopLevelItems() +{ + m_partiesGroup->checkExpandedState(); +} diff --git a/src/tomahawk/sourcetree/SourcesModel.h b/src/tomahawk/sourcetree/SourcesModel.h index 2d61b6bbbc..44df8367b3 100644 --- a/src/tomahawk/sourcetree/SourcesModel.h +++ b/src/tomahawk/sourcetree/SourcesModel.h @@ -34,6 +34,7 @@ class QMimeData; class SourceTreeItem; class GroupItem; +class PartiesCategoryItem; namespace Tomahawk { class Source; @@ -61,15 +62,19 @@ class SourcesModel : public QAbstractItemModel GenericPage = 6, TemporaryPage = 7, + LovedTracksPage = 10, ScriptCollection = 11, - Inbox = 12 + Inbox = 12, + + Party = 13 }; enum CategoryType { PlaylistsCategory = 0, - StationsCategory = 1 + StationsCategory = 1, + PartiesCategory = 2 }; enum Roles { @@ -128,6 +133,8 @@ public slots: void itemExpandRequest( SourceTreeItem* item ); void itemToggleExpandRequest( SourceTreeItem* item ); + void performDefaultExpandForTopLevelItems(); + signals: void selectRequest( const QPersistentModelIndex& idx ); void expandRequest( const QPersistentModelIndex& idx ); @@ -160,6 +167,7 @@ private slots: GroupItem* m_browse; GroupItem* m_collectionsGroup; GroupItem* m_myMusicGroup; + PartiesCategoryItem* m_partiesGroup; GroupItem* m_cloudGroup; QList< Tomahawk::source_ptr > m_sourcesWithViewPage; diff --git a/src/tomahawk/sourcetree/SourcesProxyModel.cpp b/src/tomahawk/sourcetree/SourcesProxyModel.cpp index e504889839..143df0f5dc 100644 --- a/src/tomahawk/sourcetree/SourcesProxyModel.cpp +++ b/src/tomahawk/sourcetree/SourcesProxyModel.cpp @@ -23,6 +23,8 @@ #include "SourceList.h" #include "SourcesModel.h" #include "sourcetree/items/SourceItem.h" +#include "sourcetree/items/PlaylistItems.h" +#include "Party.h" #include "utils/Logger.h" @@ -67,6 +69,20 @@ SourcesProxyModel::filterAcceptsRow( int sourceRow, const QModelIndex& sourcePar if ( item && item->type() != SourcesModel::Divider && item->parent()->parent() == 0 && !item->children().count() ) return false; + if ( item && item->type() == SourcesModel::StaticPlaylist ) + { + PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item ); + if ( plItem ) + { + foreach ( const Tomahawk::source_ptr& src, SourceList::instance()->sources( false ) ) + { + if ( !src->party().isNull() && + plItem->playlist() == src->party()->playlist() ) + return false; + } + } + } + if ( !m_filtered ) return true; diff --git a/src/tomahawk/sourcetree/items/CategoryItems.cpp b/src/tomahawk/sourcetree/items/CategoryItems.cpp index 3696baa263..db0aa12c77 100644 --- a/src/tomahawk/sourcetree/items/CategoryItems.cpp +++ b/src/tomahawk/sourcetree/items/CategoryItems.cpp @@ -35,6 +35,8 @@ #include "widgets/NewPlaylistWidget.h" #include "utils/ImageRegistry.h" #include "utils/Logger.h" +#include "Party.h" + using namespace Tomahawk; @@ -44,6 +46,7 @@ using namespace Tomahawk; CategoryAddItem::CategoryAddItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::CategoryType type ) : SourceTreeItem( model, parent, SourcesModel::CategoryAdd ) , m_categoryType( type ) + , m_enabled( true ) { } @@ -63,6 +66,9 @@ CategoryAddItem::text() const case SourcesModel::StationsCategory: return tr( "Create new Station" ); + + case SourcesModel::PartiesCategory: + return tr( "Start new Party" ); } return QString(); @@ -81,6 +87,10 @@ CategoryAddItem::activate() case SourcesModel::StationsCategory: APP->mainWindow()->createStation(); break; + + case SourcesModel::PartiesCategory: + APP->mainWindow()->createParty(); + break; } } @@ -88,17 +98,25 @@ CategoryAddItem::activate() Qt::ItemFlags CategoryAddItem::flags() const { - switch ( m_categoryType ) + if ( m_enabled ) { - case SourcesModel::PlaylistsCategory: - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; + switch ( m_categoryType ) + { + case SourcesModel::PlaylistsCategory: + case SourcesModel::PartiesCategory: + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled; - case SourcesModel::StationsCategory: - return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; + case SourcesModel::StationsCategory: + return Qt::ItemIsEnabled | Qt::ItemIsDropEnabled; - default: - return Qt::ItemIsEnabled; - break; + default: + return Qt::ItemIsEnabled; + break; + } + } + else + { + return 0; } } @@ -113,7 +131,9 @@ CategoryAddItem::icon() const bool CategoryAddItem::willAcceptDrag( const QMimeData* data ) const { - if ( ( m_categoryType == SourcesModel::PlaylistsCategory || m_categoryType == SourcesModel::StationsCategory ) && DropJob::acceptsMimeData( data ) ) + if ( ( m_categoryType == SourcesModel::PlaylistsCategory || + m_categoryType == SourcesModel::StationsCategory || + m_categoryType == SourcesModel::PartiesCategory ) && DropJob::acceptsMimeData( data ) ) { return true; } @@ -308,6 +328,11 @@ CategoryAddItem::parsedDroppedTracks( const QList< query_ptr >& tracks ) ViewManager::instance()->show( newpl ); connect( newpl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( playlistToRenameLoaded() ) ); } + else if ( m_categoryType == SourcesModel::PartiesCategory ) + { + party_ptr newlr = Party::createNew( "New Party", tracks ); + ViewManager::instance()->show( newlr ); + } } @@ -320,17 +345,23 @@ CategoryAddItem::peerSortValue() const /// CategoryItem -CategoryItem::CategoryItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::CategoryType category, bool showAddItem ) - : SourceTreeItem( model, parent, SourcesModel::Category ) +CategoryItem::CategoryItem( SourcesModel* model, + SourceTreeItem* parent, + SourcesModel::CategoryType category, + bool showAddItem, + int peerSortValue ) + : SourceTreeItem( model, parent, SourcesModel::Category, peerSortValue ) + , m_showAdd( showAddItem ) + , m_enableAdd( true ) , m_category( category ) , m_addItem( 0 ) - , m_showAdd( showAddItem ) { // in the constructor we're still being added to the parent, so we don't exist to have rows addded yet. so this is safe. // beginRowsAdded( 0, 0 ); if ( m_showAdd ) { m_addItem = new CategoryAddItem( model, this, m_category ); + setAddItemVisible( m_enableAdd ); } // endRowsAdded(); } @@ -388,6 +419,8 @@ CategoryItem::text() const return tr( "Playlists" ); case SourcesModel::StationsCategory: return tr( "Stations" ); + case SourcesModel::PartiesCategory: + return tr( "Parties" ); } return QString(); } @@ -405,3 +438,13 @@ CategoryItem::categoryType() { return m_category; } + +void +CategoryItem::setAddItemVisible( bool visible ) +{ + if ( m_enableAdd != visible ) + { + m_enableAdd = visible; + m_addItem->setEnabled( m_enableAdd ); + } +} diff --git a/src/tomahawk/sourcetree/items/CategoryItems.h b/src/tomahawk/sourcetree/items/CategoryItems.h index 1f63ed63c6..612f984f12 100644 --- a/src/tomahawk/sourcetree/items/CategoryItems.h +++ b/src/tomahawk/sourcetree/items/CategoryItems.h @@ -38,6 +38,9 @@ class CategoryAddItem : public SourceTreeItem virtual DropTypes supportedDropTypes(const QMimeData* data) const; virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action); + void setEnabled( bool enabled ) { m_enabled = enabled; } + bool isEnabled() const { return m_enabled; } + private slots: void parsedDroppedTracks( const QList< Tomahawk::query_ptr >& tracks ); @@ -46,13 +49,18 @@ private slots: private: SourcesModel::CategoryType m_categoryType; + bool m_enabled; }; class CategoryItem : public SourceTreeItem { Q_OBJECT public: - CategoryItem( SourcesModel* model, SourceTreeItem* parent, SourcesModel::CategoryType category, bool showAddItem ); + CategoryItem( SourcesModel* model, + SourceTreeItem* parent, + SourcesModel::CategoryType category, + bool showAddItem, + int peerSortValue = 0 ); virtual QString text() const; virtual void activate(); @@ -65,10 +73,15 @@ class CategoryItem : public SourceTreeItem SourcesModel::CategoryType categoryType(); + void setAddItemVisible( bool visible ); + +protected: + bool m_showAdd; + bool m_enableAdd; + private: SourcesModel::CategoryType m_category; CategoryAddItem* m_addItem; - bool m_showAdd; }; diff --git a/src/tomahawk/sourcetree/items/PartiesCategoryItem.cpp b/src/tomahawk/sourcetree/items/PartiesCategoryItem.cpp new file mode 100644 index 0000000000..d1e7309229 --- /dev/null +++ b/src/tomahawk/sourcetree/items/PartiesCategoryItem.cpp @@ -0,0 +1,168 @@ +/* + * Copyright 2012, Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PartiesCategoryItem.h" + +#include "Party.h" +#include "PartyItem.h" +#include "SourceList.h" +#include "utils/Closure.h" +#include "utils/Logger.h" + + +void +GroupCategoryItem::checkExpandedState() +{ + if ( m_defaultExpanded ) + { + m_defaultExpanded = false; + requestExpanding(); + } +} + + +PartiesCategoryItem::PartiesCategoryItem( SourcesModel* model, + SourceTreeItem* parent, + int peerSortValue ) + : GroupCategoryItem( model, parent, SourcesModel::PartiesCategory, true, peerSortValue ) +{ +//useless because it's all empty at startup + foreach ( const Tomahawk::source_ptr& src, SourceList::instance()->sources( true ) ) + { + connect( src.data(), SIGNAL( partyAdded( Tomahawk::party_ptr ) ), + SLOT( onPartyAdded( Tomahawk::party_ptr ) ), Qt::QueuedConnection ); + } + + connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), + SLOT( onSourceAdded( Tomahawk::source_ptr ) ) ); +} + + +void +PartiesCategoryItem::onSourceAdded( const Tomahawk::source_ptr& src ) +{ + tDebug() << "Begin" << Q_FUNC_INFO; + + connect( src.data(), SIGNAL( partyAdded( Tomahawk::party_ptr ) ), + SLOT( onPartyAdded( Tomahawk::party_ptr ) ), Qt::QueuedConnection ); + + _detail::Closure* srcRemovedClosure = + NewClosure( src.data(), SIGNAL( offline() ), + this, SLOT( onSourceRemoved( Tomahawk::source_ptr ) ), src ); + srcRemovedClosure->setAutoDelete( false ); + + connect( src.data(), SIGNAL( partyRemoved( Tomahawk::party_ptr ) ), + SLOT( onPartyCountChanged() ) ); + + tDebug() << "End" << Q_FUNC_INFO; +} + +void +PartiesCategoryItem::onSourceRemoved( const Tomahawk::source_ptr& src ) +{ + tDebug() << "Begin" << Q_FUNC_INFO; + if ( src->isOnline() ) + return; + tDebug() << Q_FUNC_INFO; + tDebug() << "Removed source " << src->friendlyName(); + for ( int i = 0; i < children().count(); ) + { + PartyItem* lrItem = qobject_cast< PartyItem* >( children().at( i ) ); + if( lrItem && lrItem->party()->author()->id() == src->id() ) + { + beginRowsRemoved( i, i ); + removeChild( lrItem ); + endRowsRemoved(); + + src->removeParty(); + } + else + ++i; + } + tDebug() << "End" << Q_FUNC_INFO; +} + + +void +PartiesCategoryItem::onPartyAdded( const Tomahawk::party_ptr& p ) +{ + tDebug() << "Begin" << Q_FUNC_INFO; + if ( p.isNull() ) + return; + + + int count = children().count(); + if ( m_showAdd ) + --count; //if an add item exists, it should always appear at the end of the list + + beginRowsAdded( count, count ); + PartyItem* lrItem = new PartyItem( model(), this, p ); //inserts child too + endRowsAdded(); + + if ( p->author()->isLocal() ) + connect( p.data(), SIGNAL( aboutToBeDeleted( Tomahawk::party_ptr ) ), + SLOT( onPartyDeleted( Tomahawk::party_ptr ) ), Qt::QueuedConnection ); + else + connect( p.data(), SIGNAL( deleted( Tomahawk::party_ptr ) ), + SLOT( onPartyDeleted( Tomahawk::party_ptr ) ), Qt::QueuedConnection ); + + tDebug() << "party added to model." << p->playlist()->title(); + onPartyCountChanged(); + tDebug() << "End" << Q_FUNC_INFO; +} + + +void +PartiesCategoryItem::onPartyDeleted( const Tomahawk::party_ptr& p ) +{ + tDebug() << "Begin" << Q_FUNC_INFO; + Q_ASSERT( p ); + + int count = children().count(); + for ( int i = 0; i < count; ++i ) + { + PartyItem* lrItem = qobject_cast< PartyItem* >( children().at( i ) ); + if( lrItem && lrItem->party() == p ) + { + beginRowsRemoved( i, i ); + removeChild( lrItem ); + endRowsRemoved(); + + delete lrItem; + break; + } + } + //onPartyCountChanged() is called afterwards through a signal from Source + tDebug() << "End" << Q_FUNC_INFO << "\n onPartyCountChanged will be notified by Source."; +} + + +void +PartiesCategoryItem::onPartyCountChanged() +{ + tDebug() << "Begin" << Q_FUNC_INFO; + if ( SourceList::instance()->getLocal()->hasParty() ) //if this change affects my own LR + { + setAddItemVisible( false ); + } + else + { + setAddItemVisible( true ); + } + tDebug() << "End" << Q_FUNC_INFO; +} diff --git a/src/tomahawk/sourcetree/items/PartiesCategoryItem.h b/src/tomahawk/sourcetree/items/PartiesCategoryItem.h new file mode 100644 index 0000000000..45c06b0eb3 --- /dev/null +++ b/src/tomahawk/sourcetree/items/PartiesCategoryItem.h @@ -0,0 +1,88 @@ +/* + * Copyright 2012, Teo Mrnjavac + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PARTIESCATEGORYITEM_H +#define PARTIESCATEGORYITEM_H + +#include "CategoryItems.h" +#include "SourceTreeItem.h" + +/** + * @brief The GroupCategoryItem class creates something like a GroupItem, i.e. + * a source view item that's usually top-level and acts as a grouping item to + * show/hide its contents; which also happens to handle its contents dynamically + * like a CategoryItem. + */ +class GroupCategoryItem : public CategoryItem +{ + Q_OBJECT +public: + GroupCategoryItem( SourcesModel* model, + SourceTreeItem* parent, + SourcesModel::CategoryType category, + bool showAddItem, + int peerSortValue = 0 ) + : CategoryItem( model, parent, category, showAddItem, peerSortValue ) + , m_defaultExpanded( true ) + { + setRowType( SourcesModel::Group ); + } + + virtual bool willAcceptDrag( const QMimeData* ) const { return false; } + virtual QIcon icon() const { return QIcon(); } + virtual bool isBeingPlayed() const { return false; } + virtual int peerSortValue() const { return SourceTreeItem::peerSortValue(); } + + void checkExpandedState(); + void setDefaultExpanded( bool b ) { m_defaultExpanded = b; } + +public slots: + virtual void activate() { emit toggleExpandRequest( this ); } + +signals: + void activated(); + +private slots: + void requestExpanding() { emit expandRequest( this ); } + +private: + bool m_defaultExpanded; +}; + + +class PartiesCategoryItem : public GroupCategoryItem +{ + Q_OBJECT +public: + PartiesCategoryItem( SourcesModel* model, + SourceTreeItem* parent, + int peerSortValue = 0 ); + +public slots: + void onSourceRemoved( const Tomahawk::source_ptr& src ); + +private slots: + void onSourceAdded( const Tomahawk::source_ptr& src ); + void onPartyAdded( const Tomahawk::party_ptr& p ); + void onPartyDeleted( const Tomahawk::party_ptr& p ); + void onPartyCountChanged(); +}; + + + +#endif // PARTIESCATEGORYITEM_H diff --git a/src/tomahawk/sourcetree/items/PartyItem.cpp b/src/tomahawk/sourcetree/items/PartyItem.cpp new file mode 100644 index 0000000000..3612745d10 --- /dev/null +++ b/src/tomahawk/sourcetree/items/PartyItem.cpp @@ -0,0 +1,263 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#include "PartyItem.h" + +#include + +#include "DropJob.h" +#include "Party.h" +#include "SourcesModel.h" +#include "Query.h" +#include "ViewManager.h" +#include "ViewPage.h" +#include "utils/Logger.h" + + +PartyItem::PartyItem( SourcesModel* mdl, + SourceTreeItem* parent, + const Tomahawk::party_ptr& lr, + int index ) + : SourceTreeItem( mdl, parent, SourcesModel::Party, index ) + , m_party( lr ) +{ + tDebug() << Q_FUNC_INFO << "le wild Party item appears."; + Q_ASSERT( lr ); + + m_icon = QIcon( RESPATH "images/party.png" ); + + connect( lr.data(), SIGNAL( changed() ), SLOT( onUpdated() ), Qt::QueuedConnection ); + + if ( ViewManager::instance()->pageForParty( lr ) ) + model()->linkSourceItemToPage( this, ViewManager::instance()->pageForParty( lr ) ); +} + + +QString +PartyItem::text() const +{ + return tr( "%1 (%2)", "the name of a party, followed by the current host inside ()" ) + .arg( m_party->playlist()->title() ) + .arg( m_party->playlist()->author()->friendlyName() ); +} + +QString +PartyItem::editorText() const +{ + return m_party->playlist()->title(); +} + + +Tomahawk::party_ptr +PartyItem::party() const +{ + return m_party; +} + + +Qt::ItemFlags +PartyItem::flags() const +{ + Qt::ItemFlags flags = SourceTreeItem::flags(); + flags |= Qt::ItemIsEditable; + return flags; +} + + +bool +PartyItem::willAcceptDrag( const QMimeData* data ) const +{ + return !m_party.isNull() && + m_party->author()->isLocal() && + DropJob::acceptsMimeData( data, DropJob::Track ) /*&& + !m_party->busy()*/; + //FIXME: use busy or not! +} + + +PartyItem::DropTypes +PartyItem::supportedDropTypes( const QMimeData* data ) const +{ + //same as PlaylistItem::supportedDropTypes + if ( data->hasFormat( "application/tomahawk.mixed" ) ) + { + // If this is mixed but only queries/results, we can still handle them + bool mixedQueries = true; + + QByteArray itemData = data->data( "application/tomahawk.mixed" ); + QDataStream stream( &itemData, QIODevice::ReadOnly ); + QString mimeType; + qlonglong val; + + while ( !stream.atEnd() ) + { + stream >> mimeType; + if ( mimeType != "application/tomahawk.query.list" && + mimeType != "application/tomahawk.result.list" ) + { + mixedQueries = false; + break; + } + stream >> val; + } + + if ( mixedQueries ) + return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else + return DropTypesNone; + } + + if ( data->hasFormat( "application/tomahawk.query.list" ) ) + return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.result.list" ) ) + return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + return DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + return DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50; + else if ( data->hasFormat( "text/plain" ) ) + { + return DropTypesNone; + } + return DropTypesNone; +} + + +bool +PartyItem::dropMimeData( const QMimeData* data, Qt::DropAction action ) +{ + tDebug() << Q_FUNC_INFO; + Q_UNUSED( action ); + +// if ( m_party->busy() ) +// return false; + + QList< Tomahawk::query_ptr > queries; + + if ( data->hasFormat( "application/tomahawk.party.id" ) && + data->data( "application/tomahawk.party.id" ) == m_party->guid() ) + return false; // don't allow dropping on ourselves + + if ( !DropJob::acceptsMimeData( data, DropJob::Track ) ) + return false; + + DropJob *dj = new DropJob(); + dj->setDropTypes( DropJob::Track ); + dj->setDropAction( DropJob::Append ); + + connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) ); + + if ( dropType() == DropTypeAllFromArtist ) + dj->setGetWholeArtists( true ); + if ( dropType() == DropTypeThisAlbum ) + dj->setGetWholeAlbums( true ); + + if ( dropType() == DropTypeLocalItems ) + { + dj->setGetWholeArtists( true ); + dj->tracksFromMimeData( data, false, true ); + } + else if ( dropType() == DropTypeTop50 ) + { + dj->setGetWholeArtists( true ); + dj->tracksFromMimeData( data, false, false, true ); + } + else + dj->tracksFromMimeData( data, false, false ); + + // TODO can't know if it works or not yet... + return true; +} + + +QIcon +PartyItem::icon() const +{ + return m_icon; +} + + +bool +PartyItem::setData(const QVariant &v, bool role) +{ + Q_UNUSED( role ); + + if ( m_party->author()->isLocal() ) //if this is MY listening party + { + //FIXME: add party rename code + return true; + } + return false; +} + + +int +PartyItem::peerSortValue() const +{ + return 0; +} + + +int +PartyItem::IDValue() const +{ + return m_party->createdOn(); +} + + +SourceTreeItem* +PartyItem::activateCurrent() +{ + if ( ViewManager::instance()->pageForParty( m_party ) == + ViewManager::instance()->currentPage() ) + { + model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() ); + emit selectRequest( this ); + + return this; + } + + return 0; +} + + +void +PartyItem::activate() +{ + Tomahawk::ViewPage* p = ViewManager::instance()->show( m_party ); + model()->linkSourceItemToPage( this, p ); +} + +void +PartyItem::onUpdated() +{ + emit updated(); +} + +void +PartyItem::parsedDroppedTracks( const QList< Tomahawk::query_ptr >& tracks ) +{ + qDebug() << "adding" << tracks.count() << "tracks"; + if ( tracks.count() && !m_party.isNull() && m_party->author()->isLocal() ) + { + qDebug() << "on party:" << m_party->playlist()->title() << m_party->guid(); + + m_party->playlist()->addEntries( tracks ); + } +} + diff --git a/src/tomahawk/sourcetree/items/PartyItem.h b/src/tomahawk/sourcetree/items/PartyItem.h new file mode 100644 index 0000000000..0bf1be0b67 --- /dev/null +++ b/src/tomahawk/sourcetree/items/PartyItem.h @@ -0,0 +1,60 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Teo Mrnjavac + * + * 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 . + */ + +#ifndef PARTYITEM_H +#define PARTYITEM_H + +#include "SourceTreeItem.h" + +class PartyItem : public SourceTreeItem +{ + Q_OBJECT +public: + explicit PartyItem( SourcesModel* mdl, + SourceTreeItem* parent, + const Tomahawk::party_ptr& lr, + int index = -1 ); + + QString text() const; + virtual QString editorText() const; + virtual Tomahawk::party_ptr party() const; + Qt::ItemFlags flags() const; + bool willAcceptDrag( const QMimeData* data ) const; + DropTypes supportedDropTypes( const QMimeData* data ) const; + bool dropMimeData( const QMimeData* data, Qt::DropAction action ); + QIcon icon() const; + bool setData(const QVariant& v, bool role); + int peerSortValue() const; + int IDValue() const; + //bool isBeingPlayed() const; + + virtual SourceTreeItem* activateCurrent(); + +public slots: + virtual void activate(); + +private slots: + void onUpdated(); + void parsedDroppedTracks( const QList< Tomahawk::query_ptr >& tracks ); + +private: + QIcon m_icon; + Tomahawk::party_ptr m_party; +}; + +#endif // PARTYITEM_H diff --git a/src/tomahawk/sourcetree/items/PlaylistItems.h b/src/tomahawk/sourcetree/items/PlaylistItems.h index 65aabdb8b5..f14600df85 100644 --- a/src/tomahawk/sourcetree/items/PlaylistItems.h +++ b/src/tomahawk/sourcetree/items/PlaylistItems.h @@ -73,7 +73,6 @@ private slots: QPixmap m_subscribedOnIcon, m_subscribedOffIcon; QList m_overlaidUpdaters; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(PlaylistItem::DropTypes) // can be a station or an auto playlist class DynamicPlaylistItem : public PlaylistItem diff --git a/src/tomahawk/sourcetree/items/SourceTreeItem.h b/src/tomahawk/sourcetree/items/SourceTreeItem.h index 91a9c9e540..788e29c7d0 100644 --- a/src/tomahawk/sourcetree/items/SourceTreeItem.h +++ b/src/tomahawk/sourcetree/items/SourceTreeItem.h @@ -113,5 +113,6 @@ private slots: }; Q_DECLARE_METATYPE( SourceTreeItem* ); +Q_DECLARE_OPERATORS_FOR_FLAGS(SourceTreeItem::DropTypes) //for PlaylistItem and PartyItem #endif // SOURCETREEITEM_H