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