1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-08 23:26:40 +02:00

* Interactive create-new-playlist dialog. Work in progress.

This commit is contained in:
Christian Muehlhaeuser
2011-01-03 05:18:12 +01:00
parent 0af3ac8c80
commit a5b495127a
16 changed files with 450 additions and 76 deletions

View File

@@ -92,6 +92,7 @@ private:
AudioEngine* m_audioEngine; AudioEngine* m_audioEngine;
SipHandler* m_sipHandler; SipHandler* m_sipHandler;
Servent* m_servent;
XMPPBot* m_xmppBot; XMPPBot* m_xmppBot;
#ifndef NO_LIBLASTFM #ifndef NO_LIBLASTFM

View File

@@ -96,6 +96,8 @@ SET( tomahawkSourcesGui ${tomahawkSourcesGui}
infowidgets/sourceinfowidget.cpp infowidgets/sourceinfowidget.cpp
widgets/newplaylistwidget.cpp
transferview.cpp transferview.cpp
tomahawkwindow.cpp tomahawkwindow.cpp
tomahawktrayicon.cpp tomahawktrayicon.cpp
@@ -173,6 +175,8 @@ SET( tomahawkHeadersGui ${tomahawkHeadersGui}
infowidgets/sourceinfowidget.h infowidgets/sourceinfowidget.h
widgets/newplaylistwidget.h
transferview.h transferview.h
tomahawkwindow.h tomahawkwindow.h
tomahawktrayicon.h tomahawktrayicon.h
@@ -189,6 +193,7 @@ SET( tomahawkUI ${tomahawkUI}
sourcetree/sourcetreeitemwidget.ui sourcetree/sourcetreeitemwidget.ui
topbar/topbar.ui topbar/topbar.ui
infowidgets/sourceinfowidget.ui infowidgets/sourceinfowidget.ui
widgets/newplaylistwidget.ui
) )
INCLUDE_DIRECTORIES( INCLUDE_DIRECTORIES(

View File

@@ -151,11 +151,7 @@ DatabaseCommand_Resolve::exec( DatabaseImpl* lib )
res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) ); res << Tomahawk::result_ptr( new Tomahawk::Result( m, coll ) );
} }
// return results, if any found
if( res.length() > 0 )
{
emit results( qid, res ); emit results( qid, res );
}
} }

View File

@@ -20,9 +20,7 @@ public:
virtual void exec(DatabaseImpl *lib); virtual void exec(DatabaseImpl *lib);
signals: signals:
void results( Tomahawk::QID qid, QList<Tomahawk::result_ptr> results ); void results( Tomahawk::QID qid, QList<Tomahawk::result_ptr> results );
public slots: public slots:

View File

@@ -36,14 +36,10 @@ Pipeline::databaseReady()
void Pipeline::indexReady() void Pipeline::indexReady()
{ {
qDebug() << Q_FUNC_INFO << "shuting this many pending queries:" << m_queries_pending.size(); qDebug() << Q_FUNC_INFO << "shunting this many pending queries:" << m_queries_pending.size();
m_index_ready = true; m_index_ready = true;
foreach( const query_ptr& q, m_queries_pending )
{ shuntNext();
q->setLastPipelineWeight( 101 );
shunt( q );
}
m_queries_pending.clear();
} }
@@ -77,7 +73,7 @@ Pipeline::addResolver( Resolver* r, bool sort )
void void
Pipeline::add( const QList<query_ptr>& qlist ) Pipeline::add( const QList<query_ptr>& qlist, bool prioritized )
{ {
{ {
QMutexLocker lock( &m_mut ); QMutexLocker lock( &m_mut );
@@ -91,36 +87,28 @@ Pipeline::add( const QList<query_ptr>& qlist )
} }
} }
/* if ( prioritized )
Since resolvers are async, we now dispatch to the highest weighted ones
and after timeout, dispatch to next highest etc, aborting when solved
If index not yet loaded, leave in the pending list instead.
(they are shunted when index is ready)
*/
if( m_index_ready )
{ {
foreach( const query_ptr& q, qlist ) for( int i = qlist.count() - 1; i >= 0; i-- )
{ m_queries_pending.insert( 0, qlist.at( i ) );
q->setLastPipelineWeight( 101 );
shunt( q ); // bump into next stage of pipeline (highest weights are 100)
}
} }
else else
{ {
qDebug() << "Placing query in pending queue - index not ready yet";
m_queries_pending.append( qlist ); m_queries_pending.append( qlist );
} }
if ( m_index_ready )
shuntNext();
} }
void void
Pipeline::add( const query_ptr& q ) Pipeline::add( const query_ptr& q, bool prioritized )
{ {
//qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString(); //qDebug() << Q_FUNC_INFO << (qlonglong)q.data() << q->toString();
QList< query_ptr > qlist; QList< query_ptr > qlist;
qlist << q; qlist << q;
add( qlist ); add( qlist, prioritized );
} }
@@ -135,18 +123,36 @@ Pipeline::reportResults( QID qid, const QList< result_ptr >& results )
return; return;
} }
const query_ptr& q = m_qids.value( qid ); if ( !results.isEmpty() )
{
//qDebug() << Q_FUNC_INFO << qid; //qDebug() << Q_FUNC_INFO << qid;
//qDebug() << "solved query:" << (qlonglong)q.data() << q->toString(); //qDebug() << "solved query:" << (qlonglong)q.data() << q->toString();
const query_ptr& q = m_qids.value( qid );
q->addResults( results ); q->addResults( results );
//qDebug() << "Results for " << q->toString() << ", just added" << results.length();
foreach( const result_ptr& r, q->results() ) foreach( const result_ptr& r, q->results() )
{ {
m_rids.insert( r->id(), r ); m_rids.insert( r->id(), r );
//qDebug() << "* " << (results.contains(r) ? "NEW" : "") << r->toString();
} }
}
}
void
Pipeline::shuntNext()
{
if ( m_queries_pending.isEmpty() )
return;
/*
Since resolvers are async, we now dispatch to the highest weighted ones
and after timeout, dispatch to next highest etc, aborting when solved
*/
query_ptr q = m_queries_pending.takeFirst();
q->setLastPipelineWeight( 101 );
shunt( q ); // bump into next stage of pipeline (highest weights are 100)
} }
@@ -197,6 +203,8 @@ Pipeline::shunt( const query_ptr& q )
//qDebug() << "Reached end of pipeline for:" << q->toString(); //qDebug() << "Reached end of pipeline for:" << q->toString();
// reached end of pipeline // reached end of pipeline
} }
shuntNext();
} }

View File

@@ -49,12 +49,14 @@ public:
} }
public slots: public slots:
void add( const query_ptr& q ); void add( const query_ptr& q, bool prioritized = true );
void add( const QList<query_ptr>& qlist ); void add( const QList<query_ptr>& qlist, bool prioritized = true );
void databaseReady(); void databaseReady();
private slots: private slots:
void shunt( const query_ptr& q ); void shunt( const query_ptr& q );
void shuntNext();
void indexReady(); void indexReady();
private: private:

View File

@@ -66,6 +66,8 @@ PlaylistManager::PlaylistManager( QObject* parent )
m_currentInterface = m_superCollectionView->proxyModel(); m_currentInterface = m_superCollectionView->proxyModel();
connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) ); connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( applyFilter() ) );
linkPlaylist();
} }
@@ -248,6 +250,27 @@ PlaylistManager::show( const Tomahawk::source_ptr& source )
} }
bool
PlaylistManager::show( QWidget* widget )
{
unlinkPlaylist();
connect( widget, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ) );
m_stack->addWidget( widget );
m_stack->setCurrentWidget( widget );
m_superCollectionVisible = false;
m_statsAvailable = false;
m_modesAvailable = false;
m_currentInterface = 0;
linkPlaylist();
return true;
}
bool bool
PlaylistManager::showSuperCollection() PlaylistManager::showSuperCollection()
{ {
@@ -414,6 +437,9 @@ PlaylistManager::linkPlaylist()
connect( m_currentInterface->object(), SIGNAL( shuffleModeChanged( bool ) ), connect( m_currentInterface->object(), SIGNAL( shuffleModeChanged( bool ) ),
this, SIGNAL( shuffleModeChanged( bool ) ) ); this, SIGNAL( shuffleModeChanged( bool ) ) );
m_interfaceHistory.removeAll( m_currentInterface );
m_interfaceHistory << m_currentInterface;
} }
applyFilter(); applyFilter();
@@ -433,6 +459,29 @@ PlaylistManager::linkPlaylist()
} }
void
PlaylistManager::onWidgetDestroyed( QWidget* widget )
{
qDebug() << "Destroyed child:" << widget;
bool resetWidget = ( m_stack->currentWidget() == widget );
m_stack->removeWidget( widget );
if ( resetWidget && m_interfaceHistory.count() )
{
unlinkPlaylist();
m_currentInterface = m_interfaceHistory.last();
qDebug() << "Last interface:" << m_currentInterface << m_currentInterface->widget();
if ( m_currentInterface->widget() )
m_stack->setCurrentWidget( m_currentInterface->widget() );
linkPlaylist();
}
}
void void
PlaylistManager::setRepeatMode( PlaylistInterface::RepeatMode mode ) PlaylistManager::setRepeatMode( PlaylistInterface::RepeatMode mode )
{ {

View File

@@ -39,8 +39,10 @@ public:
bool show( const Tomahawk::album_ptr& album ); bool show( const Tomahawk::album_ptr& album );
bool show( const Tomahawk::collection_ptr& collection ); bool show( const Tomahawk::collection_ptr& collection );
bool show( const Tomahawk::source_ptr& source ); bool show( const Tomahawk::source_ptr& source );
bool showSuperCollection();
bool show( QWidget* widget );
bool showSuperCollection();
void showCurrentTrack(); void showCurrentTrack();
signals: signals:
@@ -71,6 +73,8 @@ public slots:
private slots: private slots:
void applyFilter(); void applyFilter();
void onWidgetDestroyed( QWidget* widget );
private: private:
void unlinkPlaylist(); void unlinkPlaylist();
void linkPlaylist(); void linkPlaylist();
@@ -97,6 +101,7 @@ private:
QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews; QHash< Tomahawk::source_ptr, SourceInfoWidget* > m_sourceViews;
PlaylistInterface* m_currentInterface; PlaylistInterface* m_currentInterface;
QList<PlaylistInterface*> m_interfaceHistory;
QWidget* m_currentInfoWidget; QWidget* m_currentInfoWidget;

View File

@@ -110,7 +110,7 @@ TomahawkApp::TomahawkApp( int& argc, char *argv[] )
new Pipeline( this ); new Pipeline( this );
new SourceList( this ); new SourceList( this );
new Servent( this ); m_servent = new Servent( this );
#ifdef TOMAHAWK_HEADLESS #ifdef TOMAHAWK_HEADLESS
m_headless = true; m_headless = true;
@@ -212,6 +212,7 @@ TomahawkApp::~TomahawkApp()
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
delete m_sipHandler; delete m_sipHandler;
delete m_servent;
#ifndef TOMAHAWK_HEADLESS #ifndef TOMAHAWK_HEADLESS
delete m_mainwindow; delete m_mainwindow;

View File

@@ -21,6 +21,8 @@
#include "sip/SipHandler.h" #include "sip/SipHandler.h"
#include "widgets/newplaylistwidget.h"
#include "audiocontrols.h" #include "audiocontrols.h"
#include "network/controlconnection.h" #include "network/controlconnection.h"
#include "database/database.h" #include "database/database.h"
@@ -324,7 +326,9 @@ TomahawkWindow::createPlaylist()
{ {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
bool ok; playlistManager()->show( new NewPlaylistWidget() );
/* bool ok;
QString name = QInputDialog::getText( this, "Create New Playlist", "Name:", QLineEdit::Normal, "New Playlist", &ok ); QString name = QInputDialog::getText( this, "Create New Playlist", "Name:", QLineEdit::Normal, "New Playlist", &ok );
if ( !ok || name.isEmpty() ) if ( !ok || name.isEmpty() )
return; return;
@@ -333,7 +337,7 @@ TomahawkWindow::createPlaylist()
QString id = uuid(); QString id = uuid();
QString info = ""; // FIXME QString info = ""; // FIXME
QString creator = "someone"; // FIXME QString creator = "someone"; // FIXME
Playlist::create( author, id, name, info, creator, false /* shared */ ); Playlist::create( author, id, name, info, creator, false );*/
} }

View File

@@ -0,0 +1,131 @@
#include "newplaylistwidget.h"
#include "ui_newplaylistwidget.h"
#include <QPushButton>
#include <QDialogButtonBox>
#include "tomahawk/tomahawkapp.h"
#include "utils/tomahawkutils.h"
#include "playlist/playlistmanager.h"
#include "playlist/playlistmodel.h"
#include "pipeline.h"
#include "xspfloader.h"
#include "sourcelist.h"
#define FILTER_TIMEOUT 280
NewPlaylistWidget::NewPlaylistWidget( QWidget* parent )
: QWidget( parent )
, ui( new Ui::NewPlaylistWidget )
{
ui->setupUi( this );
QPushButton* saveButton = new QPushButton( tr( "&Create Playlist" ) );
saveButton->setDefault( true );
ui->buttonBox->addButton( saveButton, QDialogButtonBox::AcceptRole );
connect( ui->tagEdit, SIGNAL( textChanged( QString ) ), SLOT( tagChanged() ) );
connect( ui->buttonBox, SIGNAL( accepted() ), SLOT( savePlaylist() ) );
connect( ui->buttonBox, SIGNAL( rejected() ), SLOT( cancel() ) );
m_suggestionsModel = new PlaylistModel( ui->suggestionsView );
ui->suggestionsView->setModel( m_suggestionsModel );
connect( &m_filterTimer, SIGNAL( timeout() ), SLOT( updateSuggestions() ) );
}
NewPlaylistWidget::~NewPlaylistWidget()
{
delete ui;
}
void
NewPlaylistWidget::changeEvent( QEvent* e )
{
QWidget::changeEvent( e );
switch ( e->type() )
{
case QEvent::LanguageChange:
ui->retranslateUi( this );
break;
default:
break;
}
}
void
NewPlaylistWidget::tagChanged()
{
m_tag = ui->tagEdit->text();
m_filterTimer.stop();
m_filterTimer.setInterval( FILTER_TIMEOUT );
m_filterTimer.setSingleShot( true );
m_filterTimer.start();
}
void
NewPlaylistWidget::updateSuggestions()
{
QUrl url( QString( "http://ws.audioscrobbler.com/1.0/tag/%1/toptracks.xspf" ).arg( m_tag ) );
XSPFLoader* loader = new XSPFLoader( false );
connect( loader, SIGNAL( ok( Tomahawk::playlist_ptr ) ), SLOT( suggestionsFound() ) );
loader->load( url );
}
void
NewPlaylistWidget::suggestionsFound()
{
XSPFLoader* loader = qobject_cast<XSPFLoader*>( sender() );
m_entries = loader->entries();
delete m_suggestionsModel;
m_suggestionsModel = new PlaylistModel( ui->suggestionsView );
ui->suggestionsView->setModel( m_suggestionsModel );
QList<Tomahawk::query_ptr> ql;
foreach( const Tomahawk::plentry_ptr& entry, m_entries )
{
m_suggestionsModel->appendTrack( entry->query() );
ql.append( entry->query() );
}
Tomahawk::Pipeline::instance()->add( ql );
loader->deleteLater();
}
void
NewPlaylistWidget::savePlaylist()
{
Tomahawk::playlist_ptr playlist;
playlist = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), ui->titleEdit->text(), "", "", false );
playlist->createNewRevision( uuid(), playlist->currentrevision(), m_entries );
APP->playlistManager()->show( playlist );
cancel();
}
void
NewPlaylistWidget::cancel()
{
emit destroyed( this );
deleteLater();
}

View File

@@ -0,0 +1,50 @@
#ifndef NEWPLAYLISTWIDGET_H
#define NEWPLAYLISTWIDGET_H
#include <QWidget>
#include <QTimer>
#include "album.h"
#include "result.h"
#include "playlistinterface.h"
class PlaylistModel;
namespace Ui
{
class NewPlaylistWidget;
}
class NewPlaylistWidget : public QWidget
{
Q_OBJECT
public:
NewPlaylistWidget( QWidget* parent = 0 );
~NewPlaylistWidget();
protected:
void changeEvent( QEvent* e );
signals:
void destroyed( QWidget* widget );
private slots:
void tagChanged();
void updateSuggestions();
void suggestionsFound();
void savePlaylist();
void cancel();
private:
Ui::NewPlaylistWidget *ui;
PlaylistModel* m_suggestionsModel;
QList< Tomahawk::plentry_ptr > m_entries;
QTimer m_filterTimer;
QString m_tag;
};
#endif // NEWPLAYLISTWIDGET_H

View File

@@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewPlaylistWidget</class>
<widget class="QWidget" name="NewPlaylistWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>985</width>
<height>460</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="captionLabel">
<property name="font">
<font>
<pointsize>20</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Create A New Playlist</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Enter a title for the new playlist:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="titleEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>26</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Tomahawk offers a variety of ways to help you create playlists and find music you enjoy!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>Just enter a genre or tag name and Tomahawk will suggest a few songs to get you started with your new playlist:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="tagEdit">
<property name="minimumSize">
<size>
<width>0</width>
<height>26</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="PlaylistView" name="suggestionsView"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PlaylistView</class>
<extends>QTreeView</extends>
<header>playlistview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -148,10 +148,7 @@ void XMPPBot::handleMessage(const Message& msg, MessageSession* session)
connect( q.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ), connect( q.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ),
SLOT( onResultsAdded( QList<Tomahawk::result_ptr> ) ) ); SLOT( onResultsAdded( QList<Tomahawk::result_ptr> ) ) );
QList<Tomahawk::query_ptr> ql; Tomahawk::Pipeline::instance()->add( q );
ql.append( q );
Tomahawk::Pipeline::instance()->add( ql );
return; return;
} }
else if ( body.startsWith( "stop" ) ) else if ( body.startsWith( "stop" ) )

View File

@@ -77,23 +77,14 @@ XSPFLoader::gotBody()
xmldoc.setContent( m_body ); xmldoc.setContent( m_body );
QDomElement docElement( xmldoc.documentElement() ); QDomElement docElement( xmldoc.documentElement() );
QString origTitle, title, info, creator; QString origTitle;
origTitle = docElement.firstChildElement( "title" ).text(); origTitle = docElement.firstChildElement( "title" ).text();
info = docElement.firstChildElement( "creator" ).text(); m_info = docElement.firstChildElement( "creator" ).text();
creator = docElement.firstChildElement( "info" ).text(); m_creator = docElement.firstChildElement( "info" ).text();
title = origTitle; m_title = origTitle;
if ( title.isEmpty() ) if ( m_title.isEmpty() )
title = tr( "New Playlist" ); m_title = tr( "New Playlist" );
m_playlist = Playlist::create( SourceList::instance()->getLocal(),
uuid(),
title,
info,
creator,
false );
QList< plentry_ptr > entries;
QDomNodeList tracklist = docElement.elementsByTagName( "track" ); QDomNodeList tracklist = docElement.elementsByTagName( "track" );
for ( unsigned int i = 0; i < tracklist.length(); i++ ) for ( unsigned int i = 0; i < tracklist.length(); i++ )
@@ -113,18 +104,36 @@ XSPFLoader::gotBody()
v.insert( "track", e.firstChildElement( "title" ).text() ); v.insert( "track", e.firstChildElement( "title" ).text() );
p->setQuery( Tomahawk::query_ptr(new Tomahawk::Query(v)) ); p->setQuery( Tomahawk::query_ptr(new Tomahawk::Query(v)) );
entries << p; m_entries << p;
} }
if ( origTitle.isEmpty() && entries.isEmpty() ) if ( origTitle.isEmpty() && m_entries.isEmpty() )
{
if ( m_autoCreate )
{ {
QMessageBox::critical( APP->mainWindow(), tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) ); QMessageBox::critical( APP->mainWindow(), tr( "XSPF Error" ), tr( "This is not a valid XSPF playlist." ) );
deleteLater(); deleteLater();
return; return;
} }
else
{
emit failed();
return;
}
}
m_playlist->createNewRevision( uuid(), m_playlist->currentrevision(), entries ); if ( m_autoCreate )
emit ok( m_playlist ); {
m_playlist = Playlist::create( SourceList::instance()->getLocal(),
uuid(),
m_title,
m_info,
m_creator,
false );
m_playlist->createNewRevision( uuid(), m_playlist->currentrevision(), m_entries );
deleteLater(); deleteLater();
}
emit ok( m_playlist );
} }

View File

@@ -20,8 +20,9 @@ class XSPFLoader : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit XSPFLoader( QObject* parent = 0 ) explicit XSPFLoader( bool autoCreate = true, QObject* parent = 0 )
: QObject( parent ) : QObject( parent )
, m_autoCreate( autoCreate )
{} {}
virtual ~XSPFLoader() virtual ~XSPFLoader()
@@ -29,6 +30,8 @@ public:
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
} }
QList< Tomahawk::plentry_ptr > entries() const { return m_entries; }
signals: signals:
void failed(); void failed();
void ok( const Tomahawk::playlist_ptr& ); void ok( const Tomahawk::playlist_ptr& );
@@ -45,6 +48,10 @@ private:
void reportError(); void reportError();
void gotBody(); void gotBody();
bool m_autoCreate;
QList< Tomahawk::plentry_ptr > m_entries;
QString m_title, m_info, m_creator;
QByteArray m_body; QByteArray m_body;
Tomahawk::playlist_ptr m_playlist; Tomahawk::playlist_ptr m_playlist;
}; };