diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp new file mode 100644 index 000000000..c4b149df1 --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.cpp @@ -0,0 +1,266 @@ +#include "DynamicQmlWidget.h" + +#include "playlist/dynamic/DynamicModel.h" +#include "playlist/PlayableProxyModel.h" +#include "playlist/dynamic/DynamicModel.h" +#include "playlist/dynamic/echonest/EchonestControl.h" +#include "playlist/dynamic/GeneratorInterface.h" +#include "playlist/PlayableItem.h" +#include "Source.h" +#include "SourceList.h" +#include "audio/AudioEngine.h" +#include "database/Database.h" +#include "database/DatabaseCommand_PlaybackCharts.h" +#include "widgets/DeclarativeCoverArtProvider.h" +#include "utils/TomahawkUtilsGui.h" +#include "utils/Logger.h" + +#include +#include +#include +#include + +namespace Tomahawk +{ + +DynamicQmlWidget::DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* parent ) + : DeclarativeView( parent ) + , m_playlist( playlist ) + , m_runningOnDemand( false ) + , m_activePlaylist( false ) + , m_playNextResolved( false ) +{ + m_model = new DynamicModel( this ); + + m_proxyModel = new PlayableProxyModel( this ); + m_proxyModel->setSourcePlayableModel( m_model ); + m_proxyModel->setShowOfflineResults( false ); + + m_model->loadPlaylist( m_playlist ); + + m_artistChartsModel = new PlayableModel( this ); + + + qmlRegisterUncreatableType("tomahawk", 1, 0, "Generator", "you cannot create it on your own - should be set in context"); + + rootContext()->setContextProperty( "dynamicModel", m_proxyModel ); + rootContext()->setContextProperty( "artistChartsModel", m_artistChartsModel ); + rootContext()->setContextProperty( "generator", m_playlist->generator().data() ); + rootContext()->setContextProperty( "currentlyPlayedIndex", QVariant::fromValue( 0 ) ); + + setSource( QUrl( "qrc" RESPATH "qml/StationView.qml" ) ); + + connect( m_model, SIGNAL( currentIndexChanged()), SLOT( currentIndexChanged() ) ); + connect( m_model, SIGNAL( loadingStarted() ), SIGNAL(loadingChanged() ) ); + connect( m_model, SIGNAL( loadingFinished() ), SIGNAL(loadingChanged() ) ); + connect( m_model, SIGNAL( changed() ), SIGNAL( titleChanged() ) ); + connect( m_playlist->generator().data(), SIGNAL( generated( QList ) ), this, SLOT( tracksGenerated( QList ) ) ); + connect( m_playlist->generator().data(), SIGNAL( nextTrackGenerated( Tomahawk::query_ptr ) ), this, SLOT( nextTrackGenerated( Tomahawk::query_ptr ) ) ); + connect( m_playlist.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ), this, SLOT( onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ) ); + connect( m_playlist->generator().data(), SIGNAL( error( QString, QString )), SLOT( error(QString,QString) ) ); + + connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( trackStarted() ) ); + connect( AudioEngine::instance(), SIGNAL( playlistChanged( Tomahawk::playlistinterface_ptr ) ), this, SLOT( playlistChanged( Tomahawk::playlistinterface_ptr ) ) ); + + // m_playlist->generator()->generate( 20 ); + loadArtistCharts(); +} + + +DynamicQmlWidget::~DynamicQmlWidget() +{ +} + + +Tomahawk::playlistinterface_ptr +DynamicQmlWidget::playlistInterface() const +{ + return m_proxyModel->playlistInterface(); +} + + +QString +DynamicQmlWidget::title() const +{ + return m_model->title(); +} + + +QString +DynamicQmlWidget::description() const +{ + return m_model->description(); +} + + +QString +DynamicQmlWidget::iconSource() const +{ + return QLatin1String( RESPATH "images/station.png" ); +} + + +bool +DynamicQmlWidget::jumpToCurrentTrack() +{ + return true; +} + +playlist_ptr DynamicQmlWidget::playlist() const +{ + return m_model->playlist(); +} + +bool DynamicQmlWidget::loading() +{ + // Why does isLoading() not reset to true when cleared and station started again? +// return m_model->isLoading(); + return m_playNextResolved && m_proxyModel->rowCount() == 0; +} + +bool DynamicQmlWidget::configured() +{ + return !m_playlist->generator()->controls().isEmpty(); +} + +void DynamicQmlWidget::playItem(int index) +{ + tDebug() << "playItem called for cover" << index; + AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), m_proxyModel->itemFromIndex( index )->result() ); +} + +void DynamicQmlWidget::pause() +{ + AudioEngine::instance()->pause(); +} + +void DynamicQmlWidget::startStationFromArtist(const QString &artist) +{ + m_model->clear(); + m_playNextResolved = true; + m_playlist->generator()->startFromArtist(Artist::get(artist)); + emit loadingChanged(); + emit configuredChanged(); +} + +void DynamicQmlWidget::startStationFromGenre(const QString &genre) +{ + tDebug() << "should start startion from genre" << genre; + m_model->clear(); + m_playNextResolved = true; + m_playlist->generator()->startFromGenre( genre ); + emit loadingChanged(); + emit configuredChanged(); +} + +void DynamicQmlWidget::currentIndexChanged() +{ + tDebug() << "current index is" << m_model->currentItem().row(); + rootContext()->setContextProperty( "currentlyPlayedIndex", m_proxyModel->mapFromSource( m_model->currentItem() ).row() ); +} + +void +DynamicQmlWidget::tracksGenerated( const QList< query_ptr >& queries ) +{ + m_model->tracksGenerated( queries, queries.count() ); + m_playlist->resolve(); +} + +void DynamicQmlWidget::nextTrackGenerated(const query_ptr &track) +{ + m_model->tracksGenerated( QList() << track ); + m_playlist->resolve(); + + connect( track.data(), SIGNAL( resolvingFinished( bool )), SLOT( resolvingFinished( bool ) ) ); + +} + +void DynamicQmlWidget::error(const QString &title, const QString &body) +{ + qDebug() << "got a generator error:" << title << body; + +// m_playlist->generator()->fetchNext(); + +} + +void DynamicQmlWidget::onRevisionLoaded(DynamicPlaylistRevision) +{ + m_playlist->resolve(); +} + +void DynamicQmlWidget::resolvingFinished(bool hasResults) +{ + Q_UNUSED(hasResults) + qDebug() << "next track generated" << m_proxyModel->rowCount() << m_proxyModel->currentIndex().row(); + if( m_proxyModel->rowCount() <= m_proxyModel->currentIndex().row() + 8 ) { + qDebug() << "fetching next one"; + m_playlist->generator()->fetchNext(); + } + + if( m_playNextResolved && m_proxyModel->rowCount() > 0 ) { + playItem( 0 ); + m_playNextResolved = false; + } +} + +void DynamicQmlWidget::trackStarted() +{ + if ( m_activePlaylist && !m_playlist.isNull() && + m_playlist->mode() == OnDemand && !m_runningOnDemand ) + { + + startStation(); + } +} + +void +DynamicQmlWidget::playlistChanged( Tomahawk::playlistinterface_ptr pl ) +{ + if ( pl == m_proxyModel->playlistInterface() ) // same playlist + m_activePlaylist = true; + else + { + m_activePlaylist = false; + + // user started playing something somewhere else, so give it a rest + if ( m_runningOnDemand ) + { + stopStation( false ); + } + } +} + +void +DynamicQmlWidget::stopStation( bool stopPlaying ) +{ + m_model->stopOnDemand( stopPlaying ); + m_runningOnDemand = false; + +} + +void +DynamicQmlWidget::startStation() +{ + m_runningOnDemand = true; + m_model->startOnDemand(); +} + + +void +DynamicQmlWidget::loadArtistCharts() +{ + DatabaseCommand_PlaybackCharts* cmd = new DatabaseCommand_PlaybackCharts( SourceList::instance()->getLocal(), this ); + cmd->setLimit( 15 ); + connect( cmd, SIGNAL( artists( QList ) ), SLOT( onArtistCharts( QList< Tomahawk::artist_ptr > ) ), Qt::UniqueConnection ); + Database::instance()->enqueue( QSharedPointer( cmd ) ); +} + + +void +DynamicQmlWidget::onArtistCharts( const QList< Tomahawk::artist_ptr >& artists ) +{ + m_artistChartsModel->clear(); + m_artistChartsModel->appendArtists( artists ); + +} +} diff --git a/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h new file mode 100644 index 000000000..382bf314a --- /dev/null +++ b/src/libtomahawk/playlist/dynamic/widgets/DynamicQmlWidget.h @@ -0,0 +1,109 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Michael Zanetti + * + * 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 DYNAMIC_QML_WIDGET_H +#define DYNAMIC_QML_WIDGET_H + +#include "ViewPage.h" +#include "Typedefs.h" +#include "widgets/DeclarativeView.h" + +#include + +class PlayableModel; +class PlayableProxyModel; + +namespace Tomahawk +{ + +class DynamicModel; + +class DynamicQmlWidget : public DeclarativeView, public Tomahawk::ViewPage +{ + Q_OBJECT + + Q_PROPERTY(QString title READ title NOTIFY titleChanged) + Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) + Q_PROPERTY(bool configured READ configured NOTIFY configuredChanged) + +public: + explicit DynamicQmlWidget( const dynplaylist_ptr& playlist, QWidget* parent = 0 ); + virtual ~DynamicQmlWidget(); + + virtual QWidget* widget() { return this; } + virtual Tomahawk::playlistinterface_ptr playlistInterface() const; + + virtual QString title() const; + virtual QString description() const; + virtual QString iconSource() const; + + virtual bool showInfoBar() const { return false; } + virtual bool showModes() const { return false; } + virtual bool showFilter() const { return false; } + + virtual bool jumpToCurrentTrack(); + + playlist_ptr playlist() const; + + bool loading(); + bool configured(); + +signals: + void loadingChanged(); + void configuredChanged(); + void titleChanged(); + +public slots: + void playItem(int index); + void pause(); + void startStationFromArtist(const QString &artist); + void startStationFromGenre(const QString &genre); + +private slots: + void currentIndexChanged(); + void tracksGenerated( const QList< Tomahawk::query_ptr>& queries ); + void nextTrackGenerated( const Tomahawk::query_ptr& track ); + void error( const QString& title, const QString& body); + + void onRevisionLoaded( Tomahawk::DynamicPlaylistRevision ); + void playlistChanged( Tomahawk::playlistinterface_ptr pl ); + + void resolvingFinished( bool hasResults ); + + void trackStarted(); + void startStation(); + void stopStation( bool stopPlaying ); + + void loadArtistCharts(); + void onArtistCharts( const QList< Tomahawk::artist_ptr >& artists ); + +private: + DynamicModel* m_model; + PlayableProxyModel* m_proxyModel; + dynplaylist_ptr m_playlist; + + PlayableModel* m_artistChartsModel; + + bool m_runningOnDemand; + bool m_activePlaylist; + bool m_playNextResolved; +}; + +} + +#endif // DYNAMIC_QML_WIDGET_H diff --git a/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp new file mode 100644 index 000000000..49e743c86 --- /dev/null +++ b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.cpp @@ -0,0 +1,142 @@ +#include "DeclarativeCoverArtProvider.h" +#include "playlist/PlayableItem.h" +#include "playlist/PlayableProxyModel.h" +#include "Query.h" +#include "Album.h" +#include "Artist.h" +#include "utils/TomahawkUtilsGui.h" +#include "utils/Logger.h" + +#include +#include +#include +#include + +namespace Tomahawk +{ + +DeclarativeCoverArtProvider::DeclarativeCoverArtProvider( ) + : QDeclarativeImageProvider( QDeclarativeImageProvider::Pixmap ) +{ + +} + +DeclarativeCoverArtProvider::~DeclarativeCoverArtProvider() +{ +} + +QPixmap DeclarativeCoverArtProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) +{ + // We always can generate it in the requested size + int width = requestedSize.width() > 0 ? requestedSize.width() : 230; + int height = requestedSize.height() > 0 ? requestedSize.height() : 230; + + if( size ) + *size = QSize( width, height ); + + QPixmap cover; + + tDebug() << "DeclarativeCoverArtprovider: Getting album art by id:" << id << requestedSize; + + bool mirrored = false; + bool labeled = false; + + QString coverId = id; + if(coverId.contains("-mirror")) { + coverId.remove("-mirror"); + mirrored = true; + } + if(coverId.contains("-labels")) { + coverId.remove("-labels"); + labeled = true; + } + + artist_ptr artist = Artist::getByCoverId( coverId ); + if ( !artist.isNull() ) + { + tDebug() << "Returning artist cover:" << artist->cover( *size ).isNull(); + cover = artist->cover( *size ); + if ( cover.isNull() ) + { + tDebug() << Q_FUNC_INFO << "Returning default artist image"; + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultArtistImage, TomahawkUtils::Original, *size ); + } + } + + if ( cover.isNull() ) + { + album_ptr album = Album::getByCoverId( coverId ); + if ( !album.isNull() ) + { + tDebug() << "Returning album cover:" << album->cover( *size ).isNull(); + cover = album->cover( *size ); + if ( cover.isNull() ) + { + tDebug() << Q_FUNC_INFO << "Returning default album image"; + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultAlbumCover, TomahawkUtils::Original, *size ); + } + } + } + + if ( cover.isNull() ) + { + tDebug() << Q_FUNC_INFO << "Returning default track image"; + cover = TomahawkUtils::defaultPixmap( TomahawkUtils::DefaultTrackImage, TomahawkUtils::Original, *size ); + } + + QImage image(*size, QImage::Format_ARGB32); + + if(labeled) { + QImage coverImage(*size, QImage::Format_RGB32); + QPainter bgPainter(&coverImage); + bgPainter.drawPixmap(0, 0, size->width(), size->height(), cover); + + QColor c1; + c1.setRgb( 0, 0, 0 ); + c1.setAlphaF( 0.00 ); + QColor c2; + c2.setRgb( 0, 0, 0 ); + c2.setAlphaF( 0.88 ); + + QLinearGradient gradient( QPointF( 0, 0 ), QPointF( 0, 1 ) ); + gradient.setCoordinateMode( QGradient::ObjectBoundingMode ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.6, c2 ); + gradient.setColorAt( 1.0, c2 ); + + bgPainter.setPen( Qt::transparent ); + bgPainter.setBrush(QBrush(gradient)); + bgPainter.drawRect(0, size->height() * 0.7, size->width(), size->height() * 0.3); + cover = QPixmap::fromImage(coverImage); + } + + QPainter painter(&image); + if(!mirrored) { + image.fill(Qt::white); + painter.drawPixmap(0, 0, size->width(), size->height(), cover); + } else { + image.fill(QColor(0, 0, 0, 0)); + + // Lets paint half of the image in a fragment per line + int mirrorHeight = size->height() / 2; + int fragmentCount = mirrorHeight; + int fragmentHeight = mirrorHeight / fragmentCount; + + QPainter::PixmapFragment fragments[fragmentCount]; + + qreal fragmentOpacity = 0; + int fragmentStartY = size->height() - mirrorHeight; + for(int i = 0; i < fragmentCount; ++i) { + QPointF point = QPointF(size->width() / 2, fragmentStartY + (fragmentHeight / 2)); + QRectF sourceRect = QRectF(0, fragmentStartY, size->width(), fragmentHeight); + fragments[i] = QPainter::PixmapFragment::create(point, sourceRect, 1, 1, 0, fragmentOpacity); + fragmentOpacity += 0.5 / fragmentCount; + fragmentStartY += fragmentHeight; + } + painter.drawPixmapFragments(fragments, fragmentCount, cover); + } + + return QPixmap::fromImage(image); +} + +} diff --git a/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h new file mode 100644 index 000000000..361407095 --- /dev/null +++ b/src/libtomahawk/widgets/DeclarativeCoverArtProvider.h @@ -0,0 +1,23 @@ +#ifndef DECLARATIVECOVERARTPROVIDER_H +#define DECLARATIVECOVERARTPROVIDER_H + + +#include "playlist/PlayableProxyModel.h" + +#include + + +namespace Tomahawk +{ + +class DeclarativeCoverArtProvider: public QDeclarativeImageProvider +{ +public: + DeclarativeCoverArtProvider(); + ~DeclarativeCoverArtProvider(); + + QPixmap requestPixmap( const QString &id, QSize *size, const QSize &requestedSize ); +}; + +} +#endif // DECLARATIVECOVERARTPROVIDER_H diff --git a/src/libtomahawk/widgets/DeclarativeView.cpp b/src/libtomahawk/widgets/DeclarativeView.cpp new file mode 100644 index 000000000..9804b70aa --- /dev/null +++ b/src/libtomahawk/widgets/DeclarativeView.cpp @@ -0,0 +1,58 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Michael Zanetti + * + * 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 "DeclarativeView.h" +#include "playlist/PlayableItem.h" +#include "DeclarativeCoverArtProvider.h" +#include "utils/TomahawkUtilsGui.h" + +#include +#include +#include + +namespace Tomahawk +{ + +DeclarativeView::DeclarativeView( QWidget *parent ): + QDeclarativeView( parent ) +{ + + // Needed to make the QML contents scale with tomahawk + setResizeMode( QDeclarativeView::SizeRootObjectToView ); + + // This types seem to be needed everywhere anyways, lets the register here + qmlRegisterType( "tomahawk", 1, 0, "PlayableItem"); +// qmlRegisterType("tomahawk", 1, 0, "SearchField"); + + // QML image providers will be deleted by the view + engine()->addImageProvider( "albumart", new DeclarativeCoverArtProvider() ); + + // Register the view itself to make it easy to invoke the view's slots from QML + rootContext()->setContextProperty( "mainView", this ); + + rootContext()->setContextProperty( "defaultFontSize", TomahawkUtils::defaultFontSize() ); + rootContext()->setContextProperty( "defaultFontHeight", TomahawkUtils::defaultFontHeight() ); + +} + +DeclarativeView::~DeclarativeView() +{ + +} + +} diff --git a/src/libtomahawk/widgets/DeclarativeView.h b/src/libtomahawk/widgets/DeclarativeView.h new file mode 100644 index 000000000..06e36e790 --- /dev/null +++ b/src/libtomahawk/widgets/DeclarativeView.h @@ -0,0 +1,61 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2012, Michael Zanetti + * + * 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 DECLARATIVEVIEW_H +#define DECLARATIVEVIEW_H + +#include + +class QAbstractItemModel; + +/** + * @class This is the main class for Tomahawk's declarative views + * + * DeclarativeView inherits from QDeclarativeView and registers some + * common types, properties and functions used by all of Tomhawk's + * declarative views: + * + * Registered Types: + * - PlayableItem + * + * Set context properties: + * - mainView: This view, so you can invoke this view's slots from QML + * - defaultFontSize: system default font point size + * - defaultFontHeight: system default font pixel height + * + * It also registers an albumart image provider. You can access album art + * in QML with the source url "image://albumart/". + * The cover id can be obtained by the CoverIdRole in PlayableModels + * + * After subclassing this, all you have to do is call setSource() to + * load the QML file and optionally setModel(). + */ + +namespace Tomahawk +{ + +class DeclarativeView: public QDeclarativeView +{ + Q_OBJECT +public: + DeclarativeView(QWidget *parent = 0); + ~DeclarativeView(); +}; + +} +#endif