1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-10-04 09:31:41 +02:00
Files
tomahawk/src/libtomahawk/playlist/PlayableModel.cpp
Christian Muehlhaeuser c2388829bc * Forgot very bad match.
2012-12-23 21:08:30 +01:00

958 lines
22 KiB
C++

/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
* Copyright 2011 Leo Franchi <lfranchi@kde.org>
* Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "PlayableModel.h"
#include <QDateTime>
#include <QMimeData>
#include <QTreeView>
#include "audio/AudioEngine.h"
#include "utils/TomahawkUtils.h"
#include "Source.h"
#include "Artist.h"
#include "Album.h"
#include "Pipeline.h"
#include "PlayableItem.h"
#include "utils/Logger.h"
using namespace Tomahawk;
PlayableModel::PlayableModel( QObject* parent, bool loading )
: QAbstractItemModel( parent )
, m_rootItem( new PlayableItem( 0 ) )
, m_readOnly( true )
, m_loading( loading )
{
connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( onPlaybackStarted( Tomahawk::result_ptr ) ), Qt::DirectConnection );
connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackStopped() ), Qt::DirectConnection );
m_header << tr( "Artist" ) << tr( "Title" ) << tr( "Composer" ) << tr( "Album" ) << tr( "Track" ) << tr( "Duration" )
<< tr( "Bitrate" ) << tr( "Age" ) << tr( "Year" ) << tr( "Size" ) << tr( "Origin" ) << tr( "Accuracy" ) << tr( "Name" );
}
PlayableModel::~PlayableModel()
{
}
QModelIndex
PlayableModel::createIndex( int row, int column, PlayableItem* item ) const
{
if ( item->query() )
{
connect( item->query().data(), SIGNAL( playableStateChanged( bool ) ), SLOT( onQueryBecamePlayable( bool ) ), Qt::UniqueConnection );
connect( item->query().data(), SIGNAL( resolvingFinished( bool ) ), SLOT( onQueryResolved( bool ) ), Qt::UniqueConnection );
}
return QAbstractItemModel::createIndex( row, column, item );
}
QModelIndex
PlayableModel::index( int row, int column, const QModelIndex& parent ) const
{
if ( !m_rootItem || row < 0 || column < 0 )
return QModelIndex();
PlayableItem* parentItem = itemFromIndex( parent );
PlayableItem* childItem = parentItem->children.value( row );
if ( !childItem )
return QModelIndex();
return createIndex( row, column, childItem );
}
int
PlayableModel::rowCount( const QModelIndex& parent ) const
{
if ( parent.column() > 0 )
return 0;
PlayableItem* parentItem = itemFromIndex( parent );
if ( !parentItem )
return 0;
return parentItem->children.count();
}
int
PlayableModel::columnCount( const QModelIndex& parent ) const
{
Q_UNUSED( parent );
return 12;
}
bool
PlayableModel::hasChildren( const QModelIndex& parent ) const
{
PlayableItem* parentItem = itemFromIndex( parent );
if ( !parentItem )
return false;
if ( parentItem == m_rootItem )
return true;
return ( !parentItem->artist().isNull() || !parentItem->album().isNull() );
}
QModelIndex
PlayableModel::parent( const QModelIndex& child ) const
{
PlayableItem* entry = itemFromIndex( child );
if ( !entry )
return QModelIndex();
PlayableItem* parentEntry = entry->parent();
if ( !parentEntry )
return QModelIndex();
PlayableItem* grandparentEntry = parentEntry->parent();
if ( !grandparentEntry )
return QModelIndex();
int row = grandparentEntry->children.indexOf( parentEntry );
return createIndex( row, 0, parentEntry );
}
QVariant
PlayableModel::artistData( const artist_ptr& artist, int role ) const
{
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
return artist->name();
}
QVariant
PlayableModel::albumData( const album_ptr& album, int role ) const
{
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
return album->name();
}
QVariant
PlayableModel::queryData( const query_ptr& query, int column, int role ) const
{
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
switch ( column )
{
case Artist:
return query->artist();
break;
case Name:
case Track:
return query->track();
break;
case Album:
return query->album();
break;
case Composer:
return query->composer();
break;
case Duration:
return TomahawkUtils::timeToString( query->duration() );
break;
case AlbumPos:
{
QString tPos;
if ( query->albumpos() != 0 )
{
tPos = QString::number( query->albumpos() );
if ( query->discnumber() == 0 )
return tPos;
else
return QString( "%1.%2" ).arg( QString::number( query->discnumber() ) )
.arg( tPos );
}
}
break;
default:
break;
}
if ( query->numResults() )
{
switch ( column )
{
case Bitrate:
if ( query->results().first()->bitrate() > 0 )
return query->results().first()->bitrate();
break;
case Age:
return TomahawkUtils::ageToString( QDateTime::fromTime_t( query->results().first()->modificationTime() ) );
break;
case Year:
if ( query->results().first()->year() != 0 )
return query->results().first()->year();
break;
case Filesize:
return TomahawkUtils::filesizeToString( query->results().first()->size() );
break;
case Origin:
return query->results().first()->friendlySource();
break;
case Score:
{
float score = query->results().first()->score();
if ( score == 1.0 )
return tr( "Perfect match" );
if ( score > 0.9 )
return tr( "Very good match" );
if ( score > 0.7 )
return tr( "Good match" );
if ( score > 0.5 )
return tr( "Vague match" );
if ( score > 0.3 )
return tr( "Bad match" );
if ( score > 0.0 )
return tr( "Very bad match" );
return tr( "Not available" );
break;
}
default:
break;
}
}
return QVariant();
}
QVariant
PlayableModel::data( const QModelIndex& index, int role ) const
{
PlayableItem* entry = itemFromIndex( index );
if ( !entry )
return QVariant();
if ( role == Qt::DecorationRole )
{
return QVariant();
}
if ( role == Qt::TextAlignmentRole )
{
return QVariant( columnAlignment( index.column() ) );
}
if ( !entry->query().isNull() )
{
return queryData( entry->query()->displayQuery(), index.column(), role );
}
else if ( !entry->artist().isNull() )
{
return artistData( entry->artist(), role );
}
else if ( !entry->album().isNull() )
{
return albumData( entry->album(), role );
}
return QVariant();
}
QVariant
PlayableModel::headerData( int section, Qt::Orientation orientation, int role ) const
{
Q_UNUSED( orientation );
if ( role == Qt::DisplayRole && section >= 0 )
{
if ( section < m_header.count() )
return m_header.at( section );
else
return tr( "Name" );
}
if ( role == Qt::TextAlignmentRole )
{
return QVariant( columnAlignment( section ) );
}
return QVariant();
}
void
PlayableModel::setCurrentIndex( const QModelIndex& index )
{
PlayableItem* oldEntry = itemFromIndex( m_currentIndex );
if ( oldEntry )
{
oldEntry->setIsPlaying( false );
}
PlayableItem* entry = itemFromIndex( index );
if ( index.isValid() && entry && !entry->query().isNull() )
{
m_currentIndex = index;
m_currentUuid = entry->query()->id();
entry->setIsPlaying( true );
}
else
{
m_currentIndex = QModelIndex();
m_currentUuid = QString();
}
emit currentIndexChanged();
}
Qt::DropActions
PlayableModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
Qt::ItemFlags
PlayableModel::flags( const QModelIndex& index ) const
{
Qt::ItemFlags defaultFlags = QAbstractItemModel::flags( index );
if ( index.isValid() && index.column() == 0 )
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}
QStringList
PlayableModel::mimeTypes() const
{
QStringList types;
types << "application/tomahawk.mixed";
return types;
}
QMimeData*
PlayableModel::mimeData( const QModelIndexList &indexes ) const
{
qDebug() << Q_FUNC_INFO;
QByteArray resultData;
QDataStream resultStream( &resultData, QIODevice::WriteOnly );
// lets try with artist only
bool fail = false;
foreach ( const QModelIndex& i, indexes )
{
if ( i.column() > 0 || indexes.contains( i.parent() ) )
continue;
PlayableItem* item = itemFromIndex( i );
if ( !item )
continue;
if ( !item->artist().isNull() )
{
const artist_ptr& artist = item->artist();
resultStream << artist->name();
}
else
{
fail = true;
break;
}
}
if ( !fail )
{
QMimeData* mimeData = new QMimeData();
mimeData->setData( "application/tomahawk.metadata.artist", resultData );
return mimeData;
}
// lets try with album only
fail = false;
resultData.clear();
foreach ( const QModelIndex& i, indexes )
{
if ( i.column() > 0 || indexes.contains( i.parent() ) )
continue;
PlayableItem* item = itemFromIndex( i );
if ( !item )
continue;
if ( !item->album().isNull() )
{
const album_ptr& album = item->album();
resultStream << album->artist()->name();
resultStream << album->name();
}
else
{
fail = true;
break;
}
}
if ( !fail )
{
QMimeData* mimeData = new QMimeData();
mimeData->setData( "application/tomahawk.metadata.album", resultData );
return mimeData;
}
// lets try with tracks only
fail = false;
resultData.clear();
foreach ( const QModelIndex& i, indexes )
{
if ( i.column() > 0 || indexes.contains( i.parent() ) )
continue;
PlayableItem* item = itemFromIndex( i );
if ( !item )
continue;
if ( !item->result().isNull() )
{
const result_ptr& result = item->result();
resultStream << qlonglong( &result );
}
else
{
fail = true;
break;
}
}
if ( !fail )
{
QMimeData* mimeData = new QMimeData();
mimeData->setData( "application/tomahawk.result.list", resultData );
return mimeData;
}
// Ok... we have to use mixed
resultData.clear();
QDataStream mixedStream( &resultData, QIODevice::WriteOnly );
foreach ( const QModelIndex& i, indexes )
{
if ( i.column() > 0 || indexes.contains( i.parent() ) )
continue;
PlayableItem* item = itemFromIndex( i );
if ( !item )
continue;
if ( !item->artist().isNull() )
{
const artist_ptr& artist = item->artist();
mixedStream << QString( "application/tomahawk.metadata.artist" ) << artist->name();
}
else if ( !item->album().isNull() )
{
const album_ptr& album = item->album();
mixedStream << QString( "application/tomahawk.metadata.album" ) << album->artist()->name() << album->name();
}
else if ( !item->result().isNull() )
{
const result_ptr& result = item->result();
mixedStream << QString( "application/tomahawk.result.list" ) << qlonglong( &result );
}
else if ( !item->query().isNull() )
{
const query_ptr& query = item->query();
mixedStream << QString( "application/tomahawk.query.list" ) << qlonglong( &query );
}
}
QMimeData* mimeData = new QMimeData();
mimeData->setData( "application/tomahawk.mixed", resultData );
return mimeData;
}
void
PlayableModel::clear()
{
if ( rowCount( QModelIndex() ) )
{
finishLoading();
emit beginResetModel();
delete m_rootItem;
m_rootItem = 0;
m_rootItem = new PlayableItem( 0 );
emit endResetModel();
}
}
QList< query_ptr >
PlayableModel::queries() const
{
Q_ASSERT( m_rootItem );
QList< query_ptr > tracks;
foreach ( PlayableItem* item, m_rootItem->children )
{
tracks << item->query();
}
return tracks;
}
template <typename T>
void
PlayableModel::insertInternal( const QList< T >& items, int row )
{
if ( !items.count() )
{
emit itemCountChanged( rowCount( QModelIndex() ) );
finishLoading();
return;
}
int c = row;
QPair< int, int > crows;
crows.first = c;
crows.second = c + items.count() - 1;
emit beginInsertRows( QModelIndex(), crows.first, crows.second );
int i = 0;
PlayableItem* plitem;
foreach ( const T& item, items )
{
plitem = new PlayableItem( item, m_rootItem, row + i );
plitem->index = createIndex( row + i, 0, plitem );
i++;
/* if ( item->id() == currentItemUuid() )
setCurrentItem( plitem->index );*/
connect( plitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) );
}
emit endInsertRows();
emit itemCountChanged( rowCount( QModelIndex() ) );
finishLoading();
}
void
PlayableModel::remove( int row, bool moreToCome )
{
removeIndex( index( row, 0, QModelIndex() ), moreToCome );
}
void
PlayableModel::removeIndex( const QModelIndex& index, bool moreToCome )
{
if ( QThread::currentThread() != thread() )
{
QMetaObject::invokeMethod( this, "remove",
Qt::QueuedConnection,
Q_ARG(const QModelIndex, index),
Q_ARG(bool, moreToCome) );
return;
}
if ( index.column() > 0 )
return;
PlayableItem* item = itemFromIndex( index );
if ( item )
{
if ( index == m_currentIndex )
setCurrentIndex( QModelIndex() );
emit beginRemoveRows( index.parent(), index.row(), index.row() );
delete item;
emit endRemoveRows();
}
if ( !moreToCome )
emit itemCountChanged( rowCount( QModelIndex() ) );
}
void
PlayableModel::removeIndexes( const QList<QModelIndex>& indexes )
{
QList<QPersistentModelIndex> pil;
foreach ( const QModelIndex& idx, indexes )
{
pil << idx;
}
removeIndexes( pil );
}
void
PlayableModel::removeIndexes( const QList<QPersistentModelIndex>& indexes )
{
QList<QPersistentModelIndex> finalIndexes;
foreach ( const QPersistentModelIndex index, indexes )
{
if ( index.column() > 0 )
continue;
finalIndexes << index;
}
for ( int i = 0; i < finalIndexes.count(); i++ )
{
removeIndex( finalIndexes.at( i ), i + 1 != finalIndexes.count() );
}
}
void
PlayableModel::onPlaybackStarted( const Tomahawk::result_ptr& result )
{
PlayableItem* oldEntry = itemFromIndex( m_currentIndex );
if ( oldEntry && ( oldEntry->query().isNull() || !oldEntry->query()->numResults() || oldEntry->query()->results().first().data() != result.data() ) )
{
oldEntry->setIsPlaying( false );
}
}
void
PlayableModel::onPlaybackStopped()
{
PlayableItem* oldEntry = itemFromIndex( m_currentIndex );
if ( oldEntry )
{
oldEntry->setIsPlaying( false );
}
}
void
PlayableModel::ensureResolved()
{
for( int i = 0; i < rowCount( QModelIndex() ); i++ )
{
query_ptr query = itemFromIndex( index( i, 0, QModelIndex() ) )->query();
if ( !query->resolvingFinished() )
Pipeline::instance()->resolve( query );
}
}
Qt::Alignment
PlayableModel::columnAlignment( int column ) const
{
switch ( column )
{
case Age:
case AlbumPos:
case Bitrate:
case Duration:
case Filesize:
case Score:
case Year:
return Qt::AlignHCenter;
break;
default:
return Qt::AlignLeft;
}
}
void
PlayableModel::onDataChanged()
{
PlayableItem* p = (PlayableItem*)sender();
if ( p && p->index.isValid() )
emit dataChanged( p->index, p->index.sibling( p->index.row(), columnCount() - 1 ) );
}
void
PlayableModel::startLoading()
{
m_loading = true;
emit loadingStarted();
}
void
PlayableModel::finishLoading()
{
m_loading = false;
emit loadingFinished();
}
PlayableItem*
PlayableModel::itemFromIndex( const QModelIndex& index ) const
{
if ( index.isValid() )
{
return static_cast<PlayableItem*>( index.internalPointer() );
}
else
{
return m_rootItem;
}
}
void
PlayableModel::appendArtist( const Tomahawk::artist_ptr& artist )
{
QList< artist_ptr > artists;
artists << artist;
appendArtists( artists );
}
void
PlayableModel::appendAlbum( const Tomahawk::album_ptr& album )
{
QList< album_ptr > albums;
albums << album;
appendAlbums( albums );
}
void
PlayableModel::appendQuery( const Tomahawk::query_ptr& query )
{
QList< query_ptr > queries;
queries << query;
appendQueries( queries );
}
void
PlayableModel::appendArtists( const QList< Tomahawk::artist_ptr >& artists )
{
insertArtists( artists, rowCount( QModelIndex() ) );
}
void
PlayableModel::appendAlbums( const QList< Tomahawk::album_ptr >& albums )
{
insertAlbums( albums, rowCount( QModelIndex() ) );
}
void
PlayableModel::appendQueries( const QList< Tomahawk::query_ptr >& queries )
{
insertQueries( queries, rowCount( QModelIndex() ) );
}
void
PlayableModel::insertArtist( const Tomahawk::artist_ptr& artist, int row )
{
QList< artist_ptr > artists;
artists << artist;
insertArtists( artists, row );
}
void
PlayableModel::insertAlbum( const Tomahawk::album_ptr& album, int row )
{
QList< album_ptr > albums;
albums << album;
insertAlbums( albums, row );
}
void
PlayableModel::insertQuery( const Tomahawk::query_ptr& query, int row )
{
QList< query_ptr > queries;
queries << query;
insertQueries( queries, row );
}
void
PlayableModel::insertArtists( const QList< Tomahawk::artist_ptr >& artists, int row )
{
insertInternal( artists, row );
}
void
PlayableModel::insertAlbums( const QList< Tomahawk::album_ptr >& albums, int row )
{
insertInternal( albums, row );
}
void
PlayableModel::insertQueries( const QList< Tomahawk::query_ptr >& queries, int row )
{
insertInternal( queries, row );
}
void
PlayableModel::setTitle( const QString& title )
{
m_title = title;
emit changed();
}
void
PlayableModel::setDescription( const QString& description )
{
m_description = description;
emit changed();
}
void
PlayableModel::setIcon( const QPixmap& pixmap )
{
m_icon = pixmap;
emit changed();
}
void
PlayableModel::onQueryBecamePlayable( bool playable )
{
Q_UNUSED( playable );
Tomahawk::Query* q = qobject_cast< Query* >( sender() );
if ( !q )
{
// Track has been removed from the playlist by now
return;
}
Tomahawk::query_ptr query = q->weakRef().toStrongRef();
PlayableItem* item = itemFromQuery( query );
if ( item )
{
emit indexPlayable( item->index );
}
}
void
PlayableModel::onQueryResolved( bool hasResults )
{
Q_UNUSED( hasResults );
Tomahawk::Query* q = qobject_cast< Query* >( sender() );
if ( !q )
{
// Track has been removed from the playlist by now
return;
}
Tomahawk::query_ptr query = q->weakRef().toStrongRef();
PlayableItem* item = itemFromQuery( query );
if ( item )
{
emit indexResolved( item->index );
}
}
PlayableItem*
PlayableModel::itemFromQuery( const Tomahawk::query_ptr& query ) const
{
if ( !query )
return 0;
for ( int i = 0; i < rowCount( QModelIndex() ); i++ )
{
QModelIndex idx = index( i, 0, QModelIndex() );
PlayableItem* item = itemFromIndex( idx );
if ( item && item->query() == query )
{
return item;
}
}
tDebug() << "Could not find item for query:" << query->toString();
return 0;
}
PlayableItem*
PlayableModel::itemFromResult( const Tomahawk::result_ptr& result ) const
{
if ( !result )
return 0;
for ( int i = 0; i < rowCount( QModelIndex() ); i++ )
{
QModelIndex idx = index( i, 0, QModelIndex() );
PlayableItem* item = itemFromIndex( idx );
if ( item && item->result() == result )
{
return item;
}
}
tDebug() << "Could not find item for result:" << result->toString();
return 0;
}