diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index aafb63b74..e915bc8e4 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -119,6 +119,7 @@ set( libGuiSources utils/dropjobnotifier.cpp utils/proxystyle.cpp utils/tomahawkutilsgui.cpp + utils/closure.cpp widgets/animatedcounterlabel.cpp widgets/checkdirtree.cpp diff --git a/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp b/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp index da7e2f75d..c057ceb59 100644 --- a/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp +++ b/src/libtomahawk/playlist/PlaylistUpdaterInterface.cpp @@ -64,8 +64,6 @@ PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl ) connect( m_timer, SIGNAL( timeout() ), this, SLOT( updateNow() ) ); QTimer::singleShot( 0, this, SLOT( doSave() ) ); - - setAutoUpdate( m_autoUpdate ); } PlaylistUpdaterInterface::PlaylistUpdaterInterface( const playlist_ptr& pl, int interval, bool autoUpdate ) diff --git a/src/libtomahawk/playlist/XspfUpdater.cpp b/src/libtomahawk/playlist/XspfUpdater.cpp index 2eb1d7a1e..d00991d20 100644 --- a/src/libtomahawk/playlist/XspfUpdater.cpp +++ b/src/libtomahawk/playlist/XspfUpdater.cpp @@ -55,8 +55,15 @@ XspfUpdater::~XspfUpdater() void XspfUpdater::updateNow() { + if ( m_url.isEmpty() ) + { + qWarning() << "XspfUpdater not updating because we have an empty url..."; + return; + } + XSPFLoader* l = new XSPFLoader( false, false ); l->setAutoResolveTracks( false ); + l->setErrorTitle( playlist()->title() ); l->load( m_url ); connect( l, SIGNAL( tracks( QList ) ), this, SLOT( playlistLoaded( QList ) ) ); } diff --git a/src/libtomahawk/playlist/playlistitemdelegate.cpp b/src/libtomahawk/playlist/playlistitemdelegate.cpp index fe18db54d..696ad2ad2 100644 --- a/src/libtomahawk/playlist/playlistitemdelegate.cpp +++ b/src/libtomahawk/playlist/playlistitemdelegate.cpp @@ -97,10 +97,13 @@ PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItemV4* option, const if ( item->isPlaying() ) { option->palette.setColor( QPalette::Highlight, option->palette.color( QPalette::Mid ) ); - option->state |= QStyle::State_Selected; + + option->backgroundBrush = option->palette.color( QPalette::Mid ); + option->palette.setColor( QPalette::Text, option->palette.color( QPalette::Text ) ); + } - if ( option->state & QStyle::State_Selected ) + if ( option->state & QStyle::State_Selected && !item->isPlaying() ) { option->palette.setColor( QPalette::Text, option->palette.color( QPalette::HighlightedText ) ); } diff --git a/src/libtomahawk/playlist/trackview.cpp b/src/libtomahawk/playlist/trackview.cpp index bd8e17740..af79f853d 100644 --- a/src/libtomahawk/playlist/trackview.cpp +++ b/src/libtomahawk/playlist/trackview.cpp @@ -33,6 +33,7 @@ #include "dynamic/widgets/LoadingSpinner.h" #include "utils/tomahawkutils.h" #include "utils/logger.h" +#include "utils/closure.h" #include "dropjob.h" #include "artist.h" #include "album.h" @@ -205,6 +206,41 @@ TrackView::onScrollTimeout() } +void +TrackView::startPlayingFromStart() +{ + if ( m_proxyModel->rowCount() == 0 ) + return; + + const QModelIndex index = m_proxyModel->index( 0, 0 ); + startAutoPlay( index ); +} + + +void +TrackView::autoPlayResolveFinished( const query_ptr& query, int row ) +{ + Q_ASSERT( !query.isNull() ); + Q_ASSERT( row >= 0 ); + + if ( query.isNull() || row < 0 || query != m_autoPlaying ) + return; + + const QModelIndex index = m_proxyModel->index( row, 0 ); + if ( query->playable() ) + { + onItemActivated( index ); + return; + } + + // Try the next one.. + const QModelIndex sib = index.sibling( index.row() + 1, index.column() ); + if ( sib.isValid() ) + startAutoPlay( sib ); + +} + + void TrackView::currentChanged( const QModelIndex& current, const QModelIndex& previous ) { @@ -227,15 +263,48 @@ TrackView::onItemActivated( const QModelIndex& index ) if ( !index.isValid() ) return; + tryToPlayItem( index ); + emit itemActivated( index ); +} + + +void +TrackView::startAutoPlay( const QModelIndex& index ) +{ + if ( tryToPlayItem( index ) ) + return; + + // item isn't playable but still resolving + TrackModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); + if ( item && !item->query().isNull() && !item->query()->resolvingFinished() ) + { + m_autoPlaying = item->query(); // So we can kill it if user starts autoplaying this playlist again + NewClosure( item->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( autoPlayResolveFinished( Tomahawk::query_ptr, int ) ), + item->query(), index.row() ); + return; + } + + // not playable at all, try next + const QModelIndex sib = index.sibling( index.row() + 1, index.column() ); + if ( sib.isValid() ) + startAutoPlay( sib ); +} + + +bool +TrackView::tryToPlayItem( const QModelIndex& index ) +{ TrackModelItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); if ( item && !item->query().isNull() && item->query()->numResults() ) { tDebug() << "Result activated:" << item->query()->toString() << item->query()->results().first()->url(); m_proxyModel->setCurrentIndex( index ); AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), item->query()->results().first() ); + + return true; } - emit itemActivated( index ); + return false; } diff --git a/src/libtomahawk/playlist/trackview.h b/src/libtomahawk/playlist/trackview.h index ae9a1bd93..4b1b4e527 100644 --- a/src/libtomahawk/playlist/trackview.h +++ b/src/libtomahawk/playlist/trackview.h @@ -66,6 +66,9 @@ public: bool updatesContextView() const { return m_updateContextView; } void setUpdatesContextView( bool b ) { m_updateContextView = b; } + // Starts playing from the beginning if resolved, or waits until a track is playable + void startPlayingFromStart(); + public slots: virtual void onItemActivated( const QModelIndex& index ); @@ -103,7 +106,11 @@ private slots: void onCustomContextMenu( const QPoint& pos ); + void autoPlayResolveFinished( const Tomahawk::query_ptr& query, int row ); + private: + void startAutoPlay( const QModelIndex& index ); + bool tryToPlayItem( const QModelIndex& index ); void updateHoverIndex( const QPoint& pos ); QString m_guid; @@ -122,6 +129,9 @@ private: QModelIndex m_hoveredIndex; QModelIndex m_contextMenuIndex; + + Tomahawk::query_ptr m_autoPlaying; + Tomahawk::ContextMenu* m_contextMenu; QTimer m_timer; diff --git a/src/libtomahawk/playlist/treemodel.cpp b/src/libtomahawk/playlist/treemodel.cpp index 0165e62a6..5f60a2eb5 100644 --- a/src/libtomahawk/playlist/treemodel.cpp +++ b/src/libtomahawk/playlist/treemodel.cpp @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2012, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -623,7 +624,7 @@ TreeModel::addAlbums( const artist_ptr& artist, const QModelIndex& parent, bool requestData.caller = m_infoId; requestData.customData["row"] = parent.row(); requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["refetch"] = autoRefetch; requestData.type = Tomahawk::InfoSystem::InfoArtistReleases; Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData ); } @@ -661,8 +662,8 @@ TreeModel::addTracks( const album_ptr& album, const QModelIndex& parent, bool au m_receivedInfoData.removeAll( artistInfo ); Tomahawk::InfoSystem::InfoRequestData requestData; requestData.caller = m_infoId; - requestData.customData["rows"] = QVariant( rows ); - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["rows"] = rows; + requestData.customData["refetch"] = autoRefetch; requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); requestData.type = Tomahawk::InfoSystem::InfoAlbumSongs; requestData.timeoutMillis = 0; @@ -797,7 +798,7 @@ TreeModel::onAlbumsAdded( const QList& albums, const QModel albumitem = new TreeModelItem( album, parentItem ); albumitem->index = createIndex( parentItem->children.count() - 1, 0, albumitem ); connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) ); - + getCover( albumitem->index ); } @@ -887,7 +888,7 @@ TreeModel::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QV QModelIndex idx = index( requestData.customData[ "row" ].toInt(), 0, QModelIndex() ); - if ( requestData.customData[ "refetch" ].toInt() > 0 && !al.count() ) + if ( requestData.customData[ "refetch" ].toBool() && !al.count() ) { setMode( DatabaseMode ); @@ -941,7 +942,13 @@ TreeModel::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QV } else if ( m_receivedInfoData.count() == 2 /* FIXME */ ) { - if ( requestData.customData[ "refetch" ].toInt() > 0 ) + // If the second load got no data, but the first load did, don't do anything + QList< QVariant > rows = requestData.customData[ "rows" ].toList(); + QModelIndex idx = index( rows.first().toUInt(), 0, index( rows.at( 1 ).toUInt(), 0, QModelIndex() ) ); + if ( rowCount( idx ) ) + return; + + if ( requestData.customData[ "refetch" ].toBool() ) { setMode( DatabaseMode ); diff --git a/src/libtomahawk/resolvers/scriptresolver.cpp b/src/libtomahawk/resolvers/scriptresolver.cpp index bc72bb1f1..ea89f0053 100644 --- a/src/libtomahawk/resolvers/scriptresolver.cpp +++ b/src/libtomahawk/resolvers/scriptresolver.cpp @@ -42,6 +42,7 @@ ScriptResolver::ScriptResolver( const QString& exe ) , m_ready( false ) , m_stopped( true ) , m_configSent( false ) + , m_deleting( false ) , m_error( Tomahawk::ExternalResolver::NoError ) { tLog() << Q_FUNC_INFO << "Created script resolver:" << exe; @@ -62,9 +63,10 @@ ScriptResolver::ScriptResolver( const QString& exe ) ScriptResolver::~ScriptResolver() { disconnect( &m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ), this, SLOT( cmdExited( int, QProcess::ExitStatus ) ) ); + m_deleting = true; m_proc.kill(); - m_proc.waitForFinished(); + m_proc.waitForFinished(); // might call handleMsg Tomahawk::Pipeline::instance()->removeResolver( this ); @@ -208,6 +210,10 @@ ScriptResolver::handleMsg( const QByteArray& msg ) { // qDebug() << Q_FUNC_INFO << msg.size() << QString::fromAscii( msg ); + // Might be called from waitForFinished() in ~ScriptResolver, no database in that case, abort. + if ( m_deleting ) + return; + bool ok; QVariant v = m_parser.parse( msg, &ok ); if ( !ok || v.type() != QVariant::Map ) diff --git a/src/libtomahawk/resolvers/scriptresolver.h b/src/libtomahawk/resolvers/scriptresolver.h index ffe4e5ae5..fc25eb5c1 100644 --- a/src/libtomahawk/resolvers/scriptresolver.h +++ b/src/libtomahawk/resolvers/scriptresolver.h @@ -85,7 +85,7 @@ private: quint32 m_msgsize; QByteArray m_msg; - bool m_ready, m_stopped, m_configSent; + bool m_ready, m_stopped, m_configSent, m_deleting; ExternalResolver::ErrorState m_error; QJson::Parser m_parser; diff --git a/src/libtomahawk/utils/closure.cpp b/src/libtomahawk/utils/closure.cpp new file mode 100644 index 000000000..8a68b63e1 --- /dev/null +++ b/src/libtomahawk/utils/closure.cpp @@ -0,0 +1,89 @@ +/* This file is part of Clementine. + Copyright 2011, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#include "closure.h" + +namespace _detail { + +Closure::Closure(QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const ClosureArgumentWrapper* val0, + const ClosureArgumentWrapper* val1, + const ClosureArgumentWrapper* val2, + const ClosureArgumentWrapper* val3) + : QObject(receiver), + callback_(NULL), + val0_(val0), + val1_(val1), + val2_(val2), + val3_(val3) { + const QMetaObject* meta_receiver = receiver->metaObject(); + + QByteArray normalised_slot = QMetaObject::normalizedSignature(slot + 1); + const int index = meta_receiver->indexOfSlot(normalised_slot.constData()); + Q_ASSERT(index != -1); + slot_ = meta_receiver->method(index); + + Connect(sender, signal); +} + +Closure::Closure(QObject* sender, + const char* signal, + std::tr1::function callback) + : callback_(callback) { + Connect(sender, signal); +} + +Closure::~Closure() { +} + +void Closure::Connect(QObject* sender, const char* signal) { + bool success = connect(sender, signal, SLOT(Invoked())); + Q_ASSERT(success); + success = connect(sender, SIGNAL(destroyed()), SLOT(Cleanup())); + Q_ASSERT(success); + Q_UNUSED(success); +} + +void Closure::Invoked() { + if (callback_) { + callback_(); + } else { + slot_.invoke( + parent(), + val0_ ? val0_->arg() : QGenericArgument(), + val1_ ? val1_->arg() : QGenericArgument(), + val2_ ? val2_->arg() : QGenericArgument(), + val3_ ? val3_->arg() : QGenericArgument()); + } + deleteLater(); +} + +void Closure::Cleanup() { + disconnect(); + deleteLater(); +} + +} // namespace _detail + +_detail::Closure* NewClosure( + QObject* sender, const char* signal, + QObject* receiver, const char* slot) { + return new _detail::Closure(sender, signal, receiver, slot); +} diff --git a/src/libtomahawk/utils/closure.h b/src/libtomahawk/utils/closure.h new file mode 100644 index 000000000..8458bbb36 --- /dev/null +++ b/src/libtomahawk/utils/closure.h @@ -0,0 +1,225 @@ +/* This file is part of Clementine. + Copyright 2011, David Sansome + + Clementine 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. + + Clementine 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 Clementine. If not, see . +*/ + +#ifndef CLOSURE_H +#define CLOSURE_H + +#include + +#include +#include +#include + +#include +#include + +namespace _detail { + +class ClosureArgumentWrapper { + public: + virtual ~ClosureArgumentWrapper() {} + + virtual QGenericArgument arg() const = 0; +}; + +template +class ClosureArgument : public ClosureArgumentWrapper { + public: + explicit ClosureArgument(const T& data) : data_(data) {} + + virtual QGenericArgument arg() const { + return Q_ARG(T, data_); + } + + private: + T data_; +}; + +class Closure : public QObject, boost::noncopyable { + Q_OBJECT + + public: + Closure(QObject* sender, const char* signal, + QObject* receiver, const char* slot, + const ClosureArgumentWrapper* val0 = 0, + const ClosureArgumentWrapper* val1 = 0, + const ClosureArgumentWrapper* val2 = 0, + const ClosureArgumentWrapper* val3 = 0); + + Closure(QObject* sender, const char* signal, + std::tr1::function callback); + + virtual ~Closure(); + + private slots: + void Invoked(); + void Cleanup(); + + private: + void Connect(QObject* sender, const char* signal); + + QMetaMethod slot_; + std::tr1::function callback_; + + boost::scoped_ptr val0_; + boost::scoped_ptr val1_; + boost::scoped_ptr val2_; + boost::scoped_ptr val3_; +}; + +class SharedPointerWrapper { + public: + virtual ~SharedPointerWrapper() {} + virtual QObject* data() const = 0; +}; + +template +class SharedPointer : public SharedPointerWrapper { + public: + explicit SharedPointer(QSharedPointer ptr) + : ptr_(ptr) { + } + + QObject* data() const { + return ptr_.data(); + } + + private: + QSharedPointer ptr_; +}; + +// For use with a QSharedPointer as a sender. +class SharedClosure : public Closure { + Q_OBJECT + + public: + SharedClosure(SharedPointerWrapper* sender, const char* signal, + QObject* receiver, const char* slot, + const ClosureArgumentWrapper* val0 = 0, + const ClosureArgumentWrapper* val1 = 0, + const ClosureArgumentWrapper* val2 = 0, + const ClosureArgumentWrapper* val3 = 0) + : Closure(sender->data(), signal, + receiver, slot, + val0, val1, val2, val3), + shared_sender_(sender) { + } + + private: + boost::scoped_ptr shared_sender_; +}; + +} // namespace _detail + +#define C_ARG(type, data) new _detail::ClosureArgument(data) + +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot); + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T& val0) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T, val0)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1, + const T2& val2) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2)); +} + +template +_detail::Closure* NewClosure( + QObject* sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1, + const T2& val2, + const T3& val3) { + return new _detail::Closure( + sender, signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1), C_ARG(T2, val2), C_ARG(T3, val3)); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot, + C_ARG(T0, val0)); +} + +template +_detail::Closure* NewClosure( + QSharedPointer sender, + const char* signal, + QObject* receiver, + const char* slot, + const T0& val0, + const T1& val1) { + return new _detail::SharedClosure( + new _detail::SharedPointer(sender), signal, receiver, slot, + C_ARG(T0, val0), C_ARG(T1, val1)); +} + +#endif // CLOSURE_H diff --git a/src/libtomahawk/utils/xspfloader.cpp b/src/libtomahawk/utils/xspfloader.cpp index 4aba871b6..02defe64c 100644 --- a/src/libtomahawk/utils/xspfloader.cpp +++ b/src/libtomahawk/utils/xspfloader.cpp @@ -123,7 +123,11 @@ XSPFLoader::reportError() { emit error( FetchError ); #ifndef ENABLE_HEADLESS - JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorToString( FetchError) ) ); + const QString errorMsg = errorToString( FetchError); + if ( !m_errorTitle.isEmpty() ) + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( QString( "%1: %2" ).arg( m_errorTitle ).arg( errorMsg ) ) ); + else + JobStatusView::instance()->model()->addJob( new ErrorStatusMessage( errorMsg ) ); #endif deleteLater(); } diff --git a/src/libtomahawk/utils/xspfloader.h b/src/libtomahawk/utils/xspfloader.h index e4e912618..7369a51b8 100644 --- a/src/libtomahawk/utils/xspfloader.h +++ b/src/libtomahawk/utils/xspfloader.h @@ -49,6 +49,7 @@ public: void setOverrideTitle( const QString& newTitle ); void setAutoResolveTracks( bool autoResolve ) { m_autoResolve = autoResolve; } void setAutoDelete( bool autoDelete ) { m_autoDelete = autoDelete; } + void setErrorTitle( const QString& error ) { m_errorTitle = error; } static QString errorToString( XSPFErrorCode error ); @@ -73,7 +74,7 @@ private: bool m_autoCreate, m_autoUpdate, m_autoResolve, m_autoDelete; QString m_NS,m_overrideTitle; QList< Tomahawk::query_ptr > m_entries; - QString m_title, m_info, m_creator; + QString m_title, m_info, m_creator, m_errorTitle; QUrl m_url; QByteArray m_body; diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp index 72a347552..c089da08a 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.cpp @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2012, Jeff Mitchell + * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -232,7 +233,7 @@ AlbumInfoWidget::loadAlbums( bool autoRefetch ) artistInfo["artist"] = m_album->artist()->name(); Tomahawk::InfoSystem::InfoRequestData requestData; - requestData.customData["refetch"] = QVariant( autoRefetch ); + requestData.customData["refetch"] = autoRefetch; requestData.caller = m_infoId; requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo ); requestData.type = Tomahawk::InfoSystem::InfoArtistReleases; @@ -309,7 +310,7 @@ AlbumInfoWidget::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestDa tDebug() << "Adding" << al.count() << "albums"; gotAlbums( al ); } - else if ( requestData.customData[ "refetch" ].toInt() > 0 ) + else if ( requestData.customData[ "refetch" ].toBool() ) { tDebug() << "Auto refetching"; m_buttonAlbums->setChecked( false ); diff --git a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h index d8d983b31..c59b2b2c3 100644 --- a/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h +++ b/src/libtomahawk/widgets/infowidgets/AlbumInfoWidget.h @@ -2,6 +2,7 @@ * * Copyright 2010-2011, Christian Muehlhaeuser * Copyright 2010-2011, Jeff Mitchell + * Copyright 2010-2011, Leo Franchi * * Tomahawk is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/sourcetree/items/playlistitems.cpp b/src/sourcetree/items/playlistitems.cpp index 1df14c381..3f0cb4b2e 100644 --- a/src/sourcetree/items/playlistitems.cpp +++ b/src/sourcetree/items/playlistitems.cpp @@ -24,6 +24,7 @@ #include "query.h" #include "viewmanager.h" #include "playlist/dynamic/GeneratorInterface.h" +#include "playlist/playlistview.h" #include "categoryitems.h" #include "sourceitem.h" #include "utils/tomahawkutils.h" @@ -136,6 +137,17 @@ PlaylistItem::activate() } +void +PlaylistItem::doubleClicked() +{ + ViewPage* p = ViewManager::instance()->currentPage(); + if ( PlaylistView* view = dynamic_cast< PlaylistView* >( p ) ) + { + view->startPlayingFromStart(); + } +} + + void PlaylistItem::setLoaded( bool loaded ) { diff --git a/src/sourcetree/items/playlistitems.h b/src/sourcetree/items/playlistitems.h index dad5da151..860eeffe7 100644 --- a/src/sourcetree/items/playlistitems.h +++ b/src/sourcetree/items/playlistitems.h @@ -31,7 +31,6 @@ public: virtual QString text() const; virtual Tomahawk::playlist_ptr playlist() const; virtual Qt::ItemFlags flags() const; - virtual void activate(); virtual bool willAcceptDrag( const QMimeData* data ) const; virtual DropTypes supportedDropTypes( const QMimeData* data ) const; virtual bool dropMimeData( const QMimeData* data, Qt::DropAction action ); @@ -43,6 +42,10 @@ public: virtual SourceTreeItem* activateCurrent(); +public slots: + virtual void activate(); + virtual void doubleClicked(); + protected: void setLoaded( bool loaded ); diff --git a/src/sourcetree/items/sourcetreeitem.h b/src/sourcetree/items/sourcetreeitem.h index adde55609..9fb0299cf 100644 --- a/src/sourcetree/items/sourcetreeitem.h +++ b/src/sourcetree/items/sourcetreeitem.h @@ -78,6 +78,7 @@ public: public slots: virtual void activate() {} + virtual void doubleClicked() {} signals: void updated(); diff --git a/src/sourcetree/sourcedelegate.cpp b/src/sourcetree/sourcedelegate.cpp index 799a51ee2..704bf7884 100644 --- a/src/sourcetree/sourcedelegate.cpp +++ b/src/sourcetree/sourcedelegate.cpp @@ -44,6 +44,7 @@ SourceDelegate::SourceDelegate( QAbstractItemView* parent ) : QStyledItemDelegate( parent ) , m_parent( parent ) + , m_lastClicked( -1 ) { m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack ); m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum ); @@ -632,7 +633,27 @@ SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QSt // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here wemake sure we only // emit if we really want to if ( event->type() == QEvent::MouseButtonRelease ) - emit clicked( index ); + { + if ( m_lastClicked == -1 ) + { + m_lastClicked = QDateTime::currentMSecsSinceEpoch(); + emit clicked( index ); + } + else + { + qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastClicked; + if ( elapsed < QApplication::doubleClickInterval() ) + { + m_lastClicked = -1; + emit doubleClicked( index ); + } else + { + m_lastClicked = QDateTime::currentMSecsSinceEpoch(); + emit clicked( index ); + } + } + + } return QStyledItemDelegate::editorEvent ( event, model, option, index ); } diff --git a/src/sourcetree/sourcedelegate.h b/src/sourcetree/sourcedelegate.h index 7a432292a..fc65e85e5 100644 --- a/src/sourcetree/sourcedelegate.h +++ b/src/sourcetree/sourcedelegate.h @@ -44,6 +44,7 @@ public: signals: void clicked( const QModelIndex& idx ); + void doubleClicked( const QModelIndex& idx ); void latchOn( const Tomahawk::source_ptr& idx ); void latchOff( const Tomahawk::source_ptr& idx ); void toggleRealtimeLatch( const Tomahawk::source_ptr& idx, bool realtime ); @@ -73,6 +74,7 @@ private: mutable SourceTreeItem::DropType m_hoveredDropType; // Hack to keep easily track of the current highlighted DropType in paint() QMap< QModelIndex, AnimationHelper* > m_expandedMap; QPixmap m_headphonesOn, m_headphonesOff, m_realtimeLocked, m_realtimeUnlocked, m_nowPlayingSpeaker, m_nowPlayingSpeakerDark; + qint64 m_lastClicked; QMap< int, SourceTreeItem::DropType > m_dropTypeMap; QMap< int, QString > m_dropTypeTextMap; diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 6e76edb05..733059a8e 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -79,6 +79,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) sortByColumn( 0, Qt::AscendingOrder ); setVerticalScrollMode( QTreeView::ScrollPerPixel ); setMouseTracking( true ); + setEditTriggers( NoEditTriggers ); // TODO animation conflicts with the expanding-playlists-when-collection-is-null // so investigate @@ -89,6 +90,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) connect( m_delegate, SIGNAL( latchOff( Tomahawk::source_ptr ) ), SLOT( latchOff( Tomahawk::source_ptr ) ) ); connect( m_delegate, SIGNAL( toggleRealtimeLatch( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr,bool ) ) ); connect( m_delegate, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); + connect( m_delegate, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemDoubleClicked( QModelIndex ) ) ); setItemDelegate( m_delegate ); @@ -231,6 +233,17 @@ SourceTreeView::onItemActivated( const QModelIndex& index ) } +void +SourceTreeView::onItemDoubleClicked( const QModelIndex& idx ) +{ + if ( !selectionModel()->selectedIndexes().contains( idx ) ) + onItemActivated( idx ); + + SourceTreeItem* item = itemFromIndex< SourceTreeItem >( idx ); + item->doubleClicked(); +} + + void SourceTreeView::onItemExpanded( const QModelIndex& idx ) { diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 2517c4581..39a2a8dbd 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -67,6 +67,7 @@ private slots: void selectRequest( const QPersistentModelIndex& idx ); void expandRequest( const QPersistentModelIndex& idx ); void toggleExpandRequest( const QPersistentModelIndex& idx ); + void onItemDoubleClicked( const QModelIndex& idx ); void loadPlaylist(); void deletePlaylist( const QModelIndex& = QModelIndex() ); diff --git a/src/tomahawkapp.cpp b/src/tomahawkapp.cpp index e7f3869b1..5abe3352a 100644 --- a/src/tomahawkapp.cpp +++ b/src/tomahawkapp.cpp @@ -314,7 +314,7 @@ TomahawkApp::~TomahawkApp() delete m_session.data(); if ( !m_connector.isNull() ) delete m_connector.data(); - + Pipeline::instance()->stop(); if ( !m_servent.isNull() ) @@ -491,7 +491,7 @@ TomahawkApp::initHTTP() tLog() << "HTTPd session already exists, returning"; return; } - + m_session = QWeakPointer< QxtHttpSessionManager >( new QxtHttpSessionManager() ); m_connector = QWeakPointer< QxtHttpServerConnector >( new QxtHttpServerConnector ); if ( m_session.isNull() || m_connector.isNull() ) @@ -503,7 +503,7 @@ TomahawkApp::initHTTP() tLog() << "Failed to start HTTPd, could not create object"; return; } - + m_session.data()->setPort( 60210 ); //TODO config m_session.data()->setListenInterface( QHostAddress::LocalHost ); m_session.data()->setConnector( m_connector.data() ); diff --git a/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp b/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp index c444018e3..e02ba562a 100644 --- a/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp +++ b/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp @@ -575,9 +575,14 @@ void QxtHttpSessionManager::processEvents() */ void QxtHttpSessionManager::chunkReadyRead(int requestID) { + if (!connector()) return; + const QSharedPointer& dataSource = connector()->getRequestDataSource( requestID ); if (!dataSource->bytesAvailable()) return; + QIODevice* device = connector()->getRequestConnection(requestID); + if (!device) return; + if (!device->bytesToWrite() || qxt_d().connectionState[device].readyRead == false) { qxt_d().connectionState[device].readyRead = true; @@ -590,6 +595,9 @@ void QxtHttpSessionManager::chunkReadyRead(int requestID) */ void QxtHttpSessionManager::sendNextChunk(int requestID) { + if ( !connector() ) + return; + const QSharedPointer& dataSource = connector()->getRequestDataSource( requestID ); QIODevice* device = connector()->getRequestConnection(requestID); QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];