1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-19 15:29:42 +01:00

* Basic implementation of artist & album searches.

This commit is contained in:
Christian Muehlhaeuser 2011-12-05 07:51:24 +01:00
parent a00ea52b0b
commit 3517726d92
18 changed files with 354 additions and 38 deletions

View File

@ -185,11 +185,47 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib )
typedef QPair<int, float> scorepair_t;
// STEP 1
QList< QPair<int, float> > artists = lib->searchTable( "artist", m_query->fullTextQuery(), 10 );
QList< QPair<int, float> > tracks = lib->searchTable( "track", m_query->fullTextQuery(), 10 );
QList< QPair<int, float> > albums = lib->searchTable( "album", m_query->fullTextQuery(), 10 );
QList< QPair<int, float> > artistPairs = lib->searchTable( "artist", m_query->fullTextQuery(), 10 );
QList< QPair<int, float> > trackPairs = lib->searchTable( "track", m_query->fullTextQuery(), 10 );
QList< QPair<int, float> > albumPairs = lib->searchTable( "album", m_query->fullTextQuery(), 10 );
if ( artists.length() == 0 && tracks.length() == 0 && albums.length() == 0 )
foreach ( const scorepair_t& artistPair, artistPairs )
{
TomahawkSqlQuery query = lib->newquery();
QString sql = QString( "SELECT name FROM artist WHERE id = %1" ).arg( artistPair.first );
query.prepare( sql );
query.exec();
QList<Tomahawk::artist_ptr> artistList;
while ( query.next() )
{
Tomahawk::artist_ptr artist = Tomahawk::Artist::get( artistPair.first, query.value( 0 ).toString() );
artistList << artist;
}
emit artists( m_query->id(), artistList );
}
foreach ( const scorepair_t& albumPair, albumPairs )
{
TomahawkSqlQuery query = lib->newquery();
QString sql = QString( "SELECT album.name, artist.id, artist.name FROM album, artist WHERE artist.id = album.artist AND album.id = %1" ).arg( albumPair.first );
query.prepare( sql );
query.exec();
QList<Tomahawk::album_ptr> albumList;
while ( query.next() )
{
Tomahawk::artist_ptr artist = Tomahawk::Artist::get( query.value( 1 ).toUInt(), query.value( 2 ).toString() );
Tomahawk::album_ptr album = Tomahawk::Album::get( albumPair.first, query.value( 0 ).toString(), artist );
albumList << album;
}
emit albums( m_query->id(), albumList );
}
if ( artistPairs.length() == 0 && trackPairs.length() == 0 && albumPairs.length() == 0 )
{
qDebug() << "No candidates found in first pass, aborting resolve" << m_query->artist() << m_query->track();
emit results( m_query->id(), res );
@ -200,12 +236,12 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib )
TomahawkSqlQuery files_query = lib->newquery();
QStringList artsl, trksl, albsl;
for ( int k = 0; k < artists.count(); k++ )
artsl.append( QString::number( artists.at( k ).first ) );
for ( int k = 0; k < tracks.count(); k++ )
trksl.append( QString::number( tracks.at( k ).first ) );
for ( int k = 0; k < albums.count(); k++ )
albsl.append( QString::number( albums.at( k ).first ) );
for ( int k = 0; k < artistPairs.count(); k++ )
artsl.append( QString::number( artistPairs.at( k ).first ) );
for ( int k = 0; k < trackPairs.count(); k++ )
trksl.append( QString::number( trackPairs.at( k ).first ) );
for ( int k = 0; k < albumPairs.count(); k++ )
albsl.append( QString::number( albumPairs.at( k ).first ) );
QString artsToken = QString( "file_join.artist IN (%1)" ).arg( artsl.join( "," ) );
QString trksToken = QString( "file_join.track IN (%1)" ).arg( trksl.join( "," ) );
@ -227,12 +263,12 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib )
"track.id = file_join.track AND "
"file.id = file_join.file AND "
"%1" )
.arg( tracks.length() > 0 ? trksToken : QString( "0" ) );
.arg( trackPairs.length() > 0 ? trksToken : QString( "0" ) );
files_query.prepare( sql );
files_query.exec();
while( files_query.next() )
while ( files_query.next() )
{
source_ptr s;
QString url = files_query.value( 0 ).toString();
@ -270,11 +306,11 @@ DatabaseCommand_Resolve::fullTextResolve( DatabaseImpl* lib )
result->setTrackId( files_query.value( 9 ).toUInt() );
result->setYear( files_query.value( 17 ).toUInt() );
for ( int k = 0; k < tracks.count(); k++ )
for ( int k = 0; k < trackPairs.count(); k++ )
{
if ( tracks.at( k ).first == (int)result->trackId() )
if ( trackPairs.at( k ).first == (int)result->trackId() )
{
result->setScore( tracks.at( k ).second );
result->setScore( trackPairs.at( k ).second );
break;
}
}

View File

@ -41,8 +41,8 @@ public:
signals:
void results( Tomahawk::QID qid, QList<Tomahawk::result_ptr> results );
public slots:
void albums( Tomahawk::QID qid, QList<Tomahawk::album_ptr> albums );
void artists( Tomahawk::QID qid, QList<Tomahawk::artist_ptr> artists );
private:
DatabaseCommand_Resolve();

View File

@ -40,6 +40,10 @@ DatabaseResolver::resolve( const Tomahawk::query_ptr& query )
connect( cmd, SIGNAL( results( Tomahawk::QID, QList< Tomahawk::result_ptr > ) ),
SLOT( gotResults( Tomahawk::QID, QList< Tomahawk::result_ptr > ) ), Qt::QueuedConnection );
connect( cmd, SIGNAL( albums( Tomahawk::QID, QList< Tomahawk::album_ptr > ) ),
SLOT( gotAlbums( Tomahawk::QID, QList< Tomahawk::album_ptr > ) ), Qt::QueuedConnection );
connect( cmd, SIGNAL( artists( Tomahawk::QID, QList< Tomahawk::artist_ptr > ) ),
SLOT( gotArtists( Tomahawk::QID, QList< Tomahawk::artist_ptr > ) ), Qt::QueuedConnection );
Database::instance()->enqueue( QSharedPointer<DatabaseCommand>( cmd ) );
@ -55,6 +59,20 @@ DatabaseResolver::gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_p
}
void
DatabaseResolver::gotAlbums( const Tomahawk::QID qid, QList< Tomahawk::album_ptr> albums )
{
Tomahawk::Pipeline::instance()->reportAlbums( qid, albums );
}
void
DatabaseResolver::gotArtists( const Tomahawk::QID qid, QList< Tomahawk::artist_ptr> artists )
{
Tomahawk::Pipeline::instance()->reportArtists( qid, artists );
}
QString
DatabaseResolver::name() const
{

View File

@ -41,6 +41,8 @@ public slots:
private slots:
void gotResults( const Tomahawk::QID qid, QList< Tomahawk::result_ptr> results );
void gotAlbums( const Tomahawk::QID qid, QList< Tomahawk::album_ptr> albums );
void gotArtists( const Tomahawk::QID qid, QList< Tomahawk::artist_ptr> artists );
private:
int m_weight;

View File

@ -286,6 +286,64 @@ Pipeline::reportResults( QID qid, const QList< result_ptr >& results )
}
void
Pipeline::reportAlbums( QID qid, const QList< album_ptr >& albums )
{
if ( !m_running )
return;
if ( !m_qids.contains( qid ) )
{
tDebug() << "Albums arrived too late for:" << qid;
return;
}
const query_ptr& q = m_qids.value( qid );
Q_ASSERT( q->isFullTextQuery() );
QList< album_ptr > cleanAlbums;
foreach( const album_ptr& r, albums )
{
// float score = q->howSimilar( r );
cleanAlbums << r;
}
if ( !cleanAlbums.isEmpty() )
{
q->addAlbums( cleanAlbums );
}
}
void
Pipeline::reportArtists( QID qid, const QList< artist_ptr >& artists )
{
if ( !m_running )
return;
if ( !m_qids.contains( qid ) )
{
tDebug() << "Artists arrived too late for:" << qid;
return;
}
const query_ptr& q = m_qids.value( qid );
Q_ASSERT( q->isFullTextQuery() );
QList< artist_ptr > cleanArtists;
foreach( const artist_ptr& r, artists )
{
// float score = q->howSimilar( r );
cleanArtists << r;
}
if ( !cleanArtists.isEmpty() )
{
q->addArtists( cleanArtists );
}
}
void
Pipeline::shuntNext()
{

View File

@ -52,6 +52,8 @@ public:
unsigned int activeQueryCount() const { return m_qidsState.count(); }
void reportResults( QID qid, const QList< result_ptr >& results );
void reportAlbums( QID qid, const QList< album_ptr >& albums );
void reportArtists( QID qid, const QList< artist_ptr >& artists );
void addExternalResolverFactory( ResolverFactoryFunc resolverFactory );
Tomahawk::ExternalResolver* addScriptResolver( const QString& scriptPath, bool start = true );

View File

@ -75,3 +75,27 @@ AlbumItem::AlbumItem( const Tomahawk::album_ptr& album, AlbumItem* parent, int r
toberemoved = false;
}
AlbumItem::AlbumItem( const Tomahawk::artist_ptr& artist, AlbumItem* parent, int row )
: QObject( parent )
, m_artist( artist )
{
this->parent = parent;
if ( parent )
{
if ( row < 0 )
{
parent->children.append( this );
row = parent->children.count() - 1;
}
else
{
parent->children.insert( row, this );
}
this->model = parent->model;
}
toberemoved = false;
}

View File

@ -1,5 +1,5 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
*
* Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
@ -36,9 +36,11 @@ public:
~AlbumItem();
explicit AlbumItem( AlbumItem* parent = 0, QAbstractItemModel* model = 0 );
explicit AlbumItem( const Tomahawk::artist_ptr& artist, AlbumItem* parent = 0, int row = -1 );
explicit AlbumItem( const Tomahawk::album_ptr& album, AlbumItem* parent = 0, int row = -1 );
const Tomahawk::album_ptr& album() const { return m_album; };
const Tomahawk::artist_ptr& artist() const { return m_artist; }
const Tomahawk::album_ptr& album() const { return m_album; }
void setCover( const QPixmap& cover ) { this->cover = cover; emit dataChanged(); }
AlbumItem* parent;
@ -54,6 +56,7 @@ signals:
void dataChanged();
private:
Tomahawk::artist_ptr m_artist;
Tomahawk::album_ptr m_album;
};

View File

@ -134,8 +134,14 @@ AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
QRect textRect = option.rect.adjusted( 0, option.rect.height() - 32, 0, -2 );
QString name;
if ( !item->album().isNull() )
name = item->album()->name();
else if ( !item->artist().isNull() )
name = item->artist()->name();
bool oneLiner = false;
if ( item->album()->artist().isNull() )
if ( item->album().isNull() || item->album()->artist().isNull() )
oneLiner = true;
else
oneLiner = ( textRect.height() / 2 < painter->fontMetrics().boundingRect( item->album()->name() ).height() ||
@ -144,7 +150,7 @@ AlbumItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option,
if ( oneLiner )
{
to.setAlignment( Qt::AlignHCenter | Qt::AlignVCenter );
text = painter->fontMetrics().elidedText( item->album()->name(), Qt::ElideRight, textRect.width() - 3 );
text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() - 3 );
painter->drawText( textRect, text, to );
}
else

View File

@ -40,8 +40,6 @@ AlbumModel::AlbumModel( QObject* parent )
, m_rootItem( new AlbumItem( 0, this ) )
, m_overwriteOnAdd( false )
{
qDebug() << Q_FUNC_INFO;
connect( Tomahawk::InfoSystem::InfoSystem::instance(),
SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
@ -133,11 +131,16 @@ AlbumModel::data( const QModelIndex& index, int role ) const
if ( role != Qt::DisplayRole ) // && role != Qt::ToolTipRole )
return QVariant();
const album_ptr& album = entry->album();
QString name;
if ( !entry->album().isNull() )
name = entry->album()->name();
else if ( !entry->artist().isNull() )
name = entry->artist()->name();
switch( index.column() )
{
case 0:
return album->name();
return name;
break;
}
@ -342,6 +345,41 @@ AlbumModel::addAlbums( const QList<Tomahawk::album_ptr>& albums )
}
void
AlbumModel::addArtists( const QList<Tomahawk::artist_ptr>& artists )
{
emit loadingFinished();
if ( m_overwriteOnAdd )
clear();
if ( !artists.count() )
{
emit itemCountChanged( rowCount( QModelIndex() ) );
return;
}
int c = rowCount( QModelIndex() );
QPair< int, int > crows;
crows.first = c;
crows.second = c + artists.count() - 1;
emit beginInsertRows( QModelIndex(), crows.first, crows.second );
AlbumItem* albumitem;
foreach( const artist_ptr& artist, artists )
{
albumitem = new AlbumItem( artist, m_rootItem );
albumitem->index = createIndex( m_rootItem->children.count() - 1, 0, albumitem );
connect( albumitem, SIGNAL( dataChanged() ), SLOT( onDataChanged() ) );
}
emit endInsertRows();
emit itemCountChanged( rowCount( QModelIndex() ) );
}
void
AlbumModel::onSourceAdded( const Tomahawk::source_ptr& source )
{
@ -374,15 +412,26 @@ AlbumModel::getCover( const QModelIndex& index )
return false;
Tomahawk::InfoSystem::InfoStringHash trackInfo;
if ( !item->album()->artist().isNull() )
trackInfo["artist"] = item->album()->artist()->name();
trackInfo["album"] = item->album()->name();
trackInfo["pptr"] = QString::number( (qlonglong)item );
m_coverHash.insert( (qlonglong)item, index );
Tomahawk::InfoSystem::InfoRequestData requestData;
if ( !item->artist().isNull() )
{
requestData.type = Tomahawk::InfoSystem::InfoArtistImages;
trackInfo["artist"] = item->artist()->name();
}
else if ( !item->album().isNull() && !item->album()->artist().isNull() )
{
requestData.type = Tomahawk::InfoSystem::InfoAlbumCoverArt;
trackInfo["artist"] = item->album()->artist()->name();
trackInfo["album"] = item->album()->name();
}
m_coverHash.insert( (qlonglong)item, index );
trackInfo["pptr"] = QString::number( (qlonglong)item );
requestData.caller = s_tmInfoIdentifier;
requestData.type = Tomahawk::InfoSystem::InfoAlbumCoverArt;
requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
requestData.customData = QVariantMap();

View File

@ -89,6 +89,7 @@ public slots:
virtual void setShuffled( bool /*shuffled*/ ) {}
void addAlbums( const QList<Tomahawk::album_ptr>& albums );
void addArtists( const QList<Tomahawk::artist_ptr>& artists );
signals:
void repeatModeChanged( Tomahawk::PlaylistInterface::RepeatMode mode );

View File

@ -46,6 +46,7 @@ AlbumView::AlbumView( QWidget* parent )
, m_delegate( 0 )
, m_loadingSpinner( new LoadingSpinner( this ) )
, m_overlay( new OverlayWidget( this ) )
, m_inited( false )
{
setDragEnabled( true );
setDropIndicatorShown( false );
@ -59,6 +60,7 @@ AlbumView::AlbumView( QWidget* parent )
setViewMode( IconMode );
setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
setAutoFitItems( true );
setProxyModel( new AlbumProxyModel( this ) );
m_timer.setInterval( SCROLL_TIMEOUT );
@ -129,7 +131,10 @@ AlbumView::onItemActivated( const QModelIndex& index )
// qDebug() << "Result activated:" << item->album()->tracks().first()->toString() << item->album()->tracks().first()->results().first()->url();
// APP->audioEngine()->playItem( item->album().data(), item->album()->tracks().first()->results().first() );
ViewManager::instance()->show( item->album() );
if ( !item->album().isNull() )
ViewManager::instance()->show( item->album() );
else if ( !item->artist().isNull() )
ViewManager::instance()->show( item->artist() );
}
}
@ -203,7 +208,8 @@ AlbumView::onScrollTimeout()
void
AlbumView::paintEvent( QPaintEvent* event )
{
QListView::paintEvent( event );
if ( m_inited )
QListView::paintEvent( event );
}
@ -212,6 +218,10 @@ AlbumView::resizeEvent( QResizeEvent* event )
{
QListView::resizeEvent( event );
m_inited = true;
if ( !autoFitItems() )
return;
#ifdef Q_WS_X11
int scrollbar = !verticalScrollBar()->isVisible() ? verticalScrollBar()->rect().width() : 0;
#else

View File

@ -46,6 +46,9 @@ public:
AlbumProxyModel* proxyModel() const { return m_proxyModel; }
// PlaylistItemDelegate* delegate() { return m_delegate; }
bool autoFitItems() const { return m_autoFitItems; }
void setAutoFitItems( bool b ) { m_autoFitItems = b; }
void setAlbumModel( AlbumModel* model );
void setModel( QAbstractItemModel* model );
@ -83,6 +86,9 @@ private:
LoadingSpinner* m_loadingSpinner;
OverlayWidget* m_overlay;
bool m_inited;
bool m_autoFitItems;
QTimer m_timer;
};

View File

@ -176,6 +176,30 @@ Query::addResults( const QList< Tomahawk::result_ptr >& newresults )
}
void
Query::addAlbums( const QList< Tomahawk::album_ptr >& newalbums )
{
{
QMutexLocker lock( &m_mutex );
m_albums << newalbums;
}
emit albumsAdded( newalbums );
}
void
Query::addArtists( const QList< Tomahawk::artist_ptr >& newartists )
{
{
QMutexLocker lock( &m_mutex );
m_artists << newartists;
}
emit artistsAdded( newartists );
}
void
Query::refreshResults()
{

View File

@ -109,6 +109,9 @@ signals:
void resultsAdded( const QList<Tomahawk::result_ptr>& );
void resultsRemoved( const Tomahawk::result_ptr& );
void albumsAdded( const QList<Tomahawk::album_ptr>& );
void artistsAdded( const QList<Tomahawk::artist_ptr>& );
void resultsChanged();
void solvedStateChanged( bool state );
void playableStateChanged( bool state );
@ -119,6 +122,9 @@ public slots:
void addResults( const QList< Tomahawk::result_ptr >& );
void removeResult( const Tomahawk::result_ptr& );
void addAlbums( const QList< Tomahawk::album_ptr >& );
void addArtists( const QList< Tomahawk::artist_ptr >& );
void onResolvingFinished();
// resolve if not solved()
@ -141,6 +147,8 @@ private:
void updateSortNames();
static int levenshtein( const QString& source, const QString& target );
QList< Tomahawk::artist_ptr > m_artists;
QList< Tomahawk::album_ptr > m_albums;
QList< Tomahawk::result_ptr > m_results;
bool m_solved;
bool m_playable;

View File

@ -25,6 +25,7 @@
#include "sourcelist.h"
#include "viewmanager.h"
#include "dynamic/widgets/LoadingSpinner.h"
#include "playlist/albummodel.h"
#include "playlist/playlistmodel.h"
#include "widgets/overlaywidget.h"
@ -45,7 +46,30 @@ SearchWidget::SearchWidget( const QString& search, QWidget* parent )
ui->resultsView->overlay()->setEnabled( false );
ui->resultsView->sortByColumn( PlaylistModel::Score, Qt::DescendingOrder );
m_albumsModel = new AlbumModel( ui->albumView );
ui->albumView->setAlbumModel( m_albumsModel );
m_artistsModel = new AlbumModel( ui->artistView );
ui->artistView->setAlbumModel( m_artistsModel );
ui->artistView->setAutoFitItems( false );
ui->albumView->setAutoFitItems( false );
ui->artistView->setSpacing( 8 );
ui->albumView->setSpacing( 8 );
ui->artistView->proxyModel()->sort( -1 );
ui->albumView->proxyModel()->sort( -1 );
TomahawkUtils::unmarginLayout( ui->verticalLayout );
ui->artistView->setContentsMargins( 0, 0, 0, 0 );
ui->artistView->setFrameShape( QFrame::NoFrame );
ui->artistView->setAttribute( Qt::WA_MacShowFocusRect, 0 );
ui->albumView->setContentsMargins( 0, 0, 0, 0 );
ui->albumView->setFrameShape( QFrame::NoFrame );
ui->albumView->setAttribute( Qt::WA_MacShowFocusRect, 0 );
ui->resultsView->setContentsMargins( 0, 0, 0, 0 );
ui->resultsView->setFrameShape( QFrame::NoFrame );
ui->resultsView->setAttribute( Qt::WA_MacShowFocusRect, 0 );
@ -53,8 +77,13 @@ SearchWidget::SearchWidget( const QString& search, QWidget* parent )
ui->resultsView->loadingSpinner()->fadeIn();
m_queries << Tomahawk::Query::get( search, uuid() );
ui->splitter_2->setStretchFactor( 0, 0 );
ui->splitter_2->setStretchFactor( 1, 1 );
foreach ( const Tomahawk::query_ptr& query, m_queries )
{
connect( query.data(), SIGNAL( artistsAdded( QList<Tomahawk::artist_ptr> ) ), SLOT( onArtistsFound( QList<Tomahawk::artist_ptr> ) ) );
connect( query.data(), SIGNAL( albumsAdded( QList<Tomahawk::album_ptr> ) ), SLOT( onAlbumsFound( QList<Tomahawk::album_ptr> ) ) );
connect( query.data(), SIGNAL( resultsAdded( QList<Tomahawk::result_ptr> ) ), SLOT( onResultsFound( QList<Tomahawk::result_ptr> ) ) );
connect( query.data(), SIGNAL( resolvingFinished( bool ) ), SLOT( onQueryFinished() ) );
}
@ -103,6 +132,20 @@ SearchWidget::onResultsFound( const QList<Tomahawk::result_ptr>& results )
}
void
SearchWidget::onAlbumsFound( const QList<Tomahawk::album_ptr>& albums )
{
m_albumsModel->addAlbums( albums );
}
void
SearchWidget::onArtistsFound( const QList<Tomahawk::artist_ptr>& artists )
{
m_artistsModel->addArtists( artists );
}
void
SearchWidget::onQueryFinished()
{

View File

@ -29,6 +29,7 @@
#include "dllmacro.h"
class QPushButton;
class AlbumModel;
class PlaylistModel;
namespace Ui
@ -64,6 +65,9 @@ signals:
private slots:
void onResultsFound( const QList<Tomahawk::result_ptr>& results );
void onAlbumsFound( const QList<Tomahawk::album_ptr>& albums );
void onArtistsFound( const QList<Tomahawk::artist_ptr>& artists );
void onQueryFinished();
private:
@ -71,6 +75,8 @@ private:
QString m_search;
AlbumModel* m_artistsModel;
AlbumModel* m_albumsModel;
PlaylistModel* m_resultsModel;
QList< Tomahawk::query_ptr > m_queries;
};

View File

@ -14,11 +14,26 @@
<enum>Qt::TabFocus</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="PlaylistView" name="resultsView"/>
<widget class="QSplitter" name="splitter_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="handleWidth">
<number>1</number>
</property>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>1</number>
</property>
<widget class="AlbumView" name="artistView"/>
<widget class="AlbumView" name="albumView"/>
</widget>
<widget class="PlaylistView" name="resultsView"/>
</widget>
</item>
</layout>
</widget>
@ -28,6 +43,11 @@
<extends>QTreeView</extends>
<header>playlist/playlistview.h</header>
</customwidget>
<customwidget>
<class>AlbumView</class>
<extends>QListView</extends>
<header>playlist/albumview.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>