You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
amarok/amarok/src/covermanager.cpp

1068 lines
35 KiB

// (c) Pierpaolo Di Panfilo 2004
// (c) 2005 Isaiah Damron <xepo@trifault.net>
// See COPYING file for licensing information
#include "amarok.h"
#include "amarokconfig.h"
#include "browserToolBar.h"
#include "clicklineedit.h"
#include "debug.h"
#include "collectionbrowser.h" //manipulateThe()
#include "collectiondb.h"
#include "config.h"
#include "coverfetcher.h"
#include "covermanager.h"
#include "pixmapviewer.h"
#include "playlist.h"
#include <tqdesktopwidget.h> //ctor: desktop size
#include <tqfile.h>
#include <tqfontmetrics.h> //paintItem()
#include <tqimage.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqobjectlist.h> //used to delete all cover fetchers
#include <tqpainter.h> //paintItem()
#include <tqpalette.h> //paintItem()
#include <tqpixmap.h>
#include <tqpoint.h>
#include <tqprogressdialog.h>
#include <tqrect.h>
#include <tqstringlist.h>
#include <tqtooltip.h>
#include <tqtimer.h> //search filter timer
#include <tqtooltip.h>
#include <tqvbox.h>
#include <tdeapplication.h>
#include <tdeconfig.h>
#include <kcursor.h>
#include <tdefiledialog.h>
#include <kiconloader.h>
#include <tdelistview.h>
#include <tdelocale.h>
#include <tdemessagebox.h> //showCoverMenu()
#include <tdemultipledrag.h>
#include <tdeio/netaccess.h>
#include <tdepopupmenu.h> //showCoverMenu()
#include <kprogress.h>
#include <kpushbutton.h>
#include <ksqueezedtextlabel.h> //status label
#include <kstatusbar.h>
#include <kstringhandler.h> //paintItem
#include <tdetoolbar.h>
#include <tdetoolbarbutton.h> //clear filter button
#include <kurl.h>
#include <kurldrag.h>
#include <twin.h>
static TQString artistToSelectInInitFunction;
CoverManager *CoverManager::s_instance = 0;
class ArtistItem : public TDEListViewItem
{
public:
ArtistItem(TQListView *view, TQListViewItem *item, const TQString &text)
: TDEListViewItem(view, item, text) {}
protected:
int compare( TQListViewItem* i, int col, bool ascending ) const
{
Q_UNUSED(col);
Q_UNUSED(ascending);
TQString a = text(0);
TQString b = i->text(0);
if ( a.startsWith( "the ", false ) )
CollectionView::manipulateThe( a, true );
if ( b.startsWith( "the ", false ) )
CollectionView::manipulateThe( b, true );
return TQString::localeAwareCompare( a.lower(), b.lower() );
}
};
CoverManager::CoverManager()
: TQSplitter( 0, "TheCoverManager" )
, m_timer( new TQTimer( this ) ) //search filter timer
, m_fetchCounter( 0 )
, m_fetchingCovers( 0 )
, m_coversFetched( 0 )
, m_coverErrors( 0 )
{
DEBUG_BLOCK
s_instance = this;
// Sets caption and icon correctly (needed e.g. for GNOME)
kapp->setTopWidget( this );
setCaption( kapp->makeStdCaption( i18n("Cover Manager") ) );
setWFlags( WDestructiveClose );
setMargin( 4 );
//artist listview
m_artistView = new TDEListView( this );
m_artistView->addColumn(i18n( "Albums By" ));
m_artistView->setFullWidth( true );
m_artistView->setSorting( 0 );
m_artistView->setMinimumWidth( 180 );
ArtistItem *item = 0;
//load artists from the collection db
const TQStringList artists = CollectionDB::instance()->artistList( false, false );
foreach( artists )
{
TQString artist = *it;
item = new ArtistItem( m_artistView, item, artist );
item->setPixmap( 0, SmallIcon( Amarok::icon( "artist" ) ) );
}
m_artistView->sort();
m_artistView->setSorting( -1 );
ArtistItem *last = static_cast<ArtistItem *>(m_artistView->lastItem());
item = new ArtistItem( m_artistView, 0, i18n( "All Albums" ) );
item->setPixmap( 0, SmallIcon( Amarok::icon( "album" ) ) );
QueryBuilder qb;
qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.setOptions( QueryBuilder::optOnlyCompilations );
qb.setLimit( 0, 1 );
if ( qb.run().count() ) {
item = new ArtistItem( m_artistView, last, i18n( "Various Artists" ) );
item->setPixmap( 0, SmallIcon("preferences-desktop-personal") );
}
TQVBox *vbox = new TQVBox( this );
TQHBox *hbox = new TQHBox( vbox );
vbox->setSpacing( 4 );
hbox->setSpacing( 4 );
{ //<Search LineEdit>
TQHBox *searchBox = new TQHBox( hbox );
TDEToolBar* searchToolBar = new Browser::ToolBar( searchBox );
TDEToolBarButton *button = new TDEToolBarButton( "locationbar_erase", 0, searchToolBar );
m_searchEdit = new ClickLineEdit( i18n( "Enter search terms here" ), searchToolBar );
m_searchEdit->setFrame( TQFrame::Sunken );
searchToolBar->setStretchableWidget( m_searchEdit );
connect( button, TQT_SIGNAL(clicked()), m_searchEdit, TQT_SLOT(clear()) );
TQToolTip::add( button, i18n( "Clear search field" ) );
TQToolTip::add( m_searchEdit, i18n( "Enter space-separated terms to search in the albums" ) );
hbox->setStretchFactor( searchBox, 1 );
} //</Search LineEdit>
// view menu
m_viewMenu = new TDEPopupMenu( this );
m_viewMenu->insertItem( i18n("All Albums"), AllAlbums );
m_viewMenu->insertItem( i18n("Albums With Cover"), AlbumsWithCover );
m_viewMenu->insertItem( i18n("Albums Without Cover"), AlbumsWithoutCover );
m_viewMenu->setItemChecked( AllAlbums, true );
connect( m_viewMenu, TQT_SIGNAL( activated(int) ), TQT_SLOT( changeView(int) ) );
#ifdef AMAZON_SUPPORT
// amazon locale menu
m_amazonLocaleMenu = new TDEPopupMenu( this );
m_amazonLocaleMenu->insertItem( i18n("International"), CoverFetcher::International );
m_amazonLocaleMenu->insertItem( i18n("Canada"), CoverFetcher::Canada );
m_amazonLocaleMenu->insertItem( i18n("France"), CoverFetcher::France );
m_amazonLocaleMenu->insertItem( i18n("Germany"), CoverFetcher::Germany );
m_amazonLocaleMenu->insertItem( i18n("Japan"), CoverFetcher::Japan);
m_amazonLocaleMenu->insertItem( i18n("United Kingdom"), CoverFetcher::UK );
connect( m_amazonLocaleMenu, TQT_SIGNAL( activated(int) ), TQT_SLOT( changeLocale(int) ) );
#endif
TDEToolBar* toolBar = new TDEToolBar( hbox );
toolBar->setIconText( TDEToolBar::IconTextRight );
toolBar->setFrameShape( TQFrame::NoFrame );
toolBar->insertButton( "view_choose", 1, m_viewMenu, true, i18n( "View" ) );
#ifdef AMAZON_SUPPORT
toolBar->insertButton( "amazon_locale", 2, m_amazonLocaleMenu, true, i18n( "Amazon Locale" ) );
TQString locale = AmarokConfig::amazonLocale();
m_currentLocale = CoverFetcher::localeStringToID( locale );
m_amazonLocaleMenu->setItemChecked( m_currentLocale, true );
//fetch missing covers button
m_fetchButton = new KPushButton( KGuiItem( i18n("Fetch Missing Covers"), Amarok::icon( "download" ) ), hbox );
connect( m_fetchButton, TQT_SIGNAL(clicked()), TQT_SLOT(fetchMissingCovers()) );
#endif
//cover view
m_coverView = new CoverView( vbox );
//status bar
KStatusBar *m_statusBar = new KStatusBar( vbox );
m_statusBar->addWidget( m_statusLabel = new KSqueezedTextLabel( m_statusBar ), 4 );
m_statusLabel->setIndent( 3 );
m_statusBar->addWidget( m_progressBox = new TQHBox( m_statusBar ), 1, true );
KPushButton *stopButton = new KPushButton( KGuiItem(i18n("Abort"), "process-stop"), m_progressBox );
connect( stopButton, TQT_SIGNAL(clicked()), TQT_SLOT(stopFetching()) );
m_progress = new KProgress( m_progressBox );
m_progress->setCenterIndicator( true );
const int h = m_statusLabel->height() + 3;
m_statusLabel->setFixedHeight( h );
m_progressBox->setFixedHeight( h );
m_progressBox->hide();
// signals and slots connections
connect( m_artistView, TQT_SIGNAL(selectionChanged( TQListViewItem* ) ),
TQT_SLOT(slotArtistSelected( TQListViewItem* )) );
connect( m_coverView, TQT_SIGNAL(contextMenuRequested( TQIconViewItem*, const TQPoint& )),
TQT_SLOT(showCoverMenu( TQIconViewItem*, const TQPoint& )) );
connect( m_coverView, TQT_SIGNAL(executed( TQIconViewItem* )),
TQT_SLOT(coverItemExecuted( TQIconViewItem* )) );
connect( m_timer, TQT_SIGNAL(timeout()),
TQT_SLOT(slotSetFilter()) );
connect( m_searchEdit, TQT_SIGNAL(textChanged( const TQString& )),
TQT_SLOT(slotSetFilterTimeout()) );
#ifdef AMAZON_SUPPORT
connect( CollectionDB::instance(), TQT_SIGNAL(coverFetched( const TQString&, const TQString& )),
TQT_SLOT(coverFetched( const TQString&, const TQString& )) );
connect( CollectionDB::instance(), TQT_SIGNAL(coverRemoved( const TQString&, const TQString& )),
TQT_SLOT(coverRemoved( const TQString&, const TQString& )) );
connect( CollectionDB::instance(), TQT_SIGNAL(coverFetcherError( const TQString& )),
TQT_SLOT(coverFetcherError()) );
#endif
m_currentView = AllAlbums;
TQSize size = TQApplication::desktop()->screenGeometry( this ).size() / 1.5;
resize( Amarok::config( "Cover Manager" )->readSizeEntry( "Window Size", &size ) );
show();
TQTimer::singleShot( 0, this, TQT_SLOT(init()) );
}
CoverManager::~CoverManager()
{
DEBUG_BLOCK
Amarok::config( "Cover Manager" )->writeEntry( "Window Size", size() );
s_instance = 0;
}
void CoverManager::init()
{
DEBUG_BLOCK
TQListViewItem *item = 0;
if ( !artistToSelectInInitFunction.isEmpty() )
for ( item = m_artistView->firstChild(); item; item = item->nextSibling() )
if ( item->text( 0 ) == artistToSelectInInitFunction )
break;
if ( item == 0 )
item = m_artistView->firstChild();
m_artistView->setSelected( item, true );
}
CoverViewDialog::CoverViewDialog( const TQString& artist, const TQString& album, TQWidget *parent )
: TQDialog( parent, 0, false, WDestructiveClose | WType_TopLevel | WNoAutoErase )
, m_pixmap( CollectionDB::instance()->albumImage( artist, album, false, 0 ) )
{
KWin::setType( winId(), NET::Utility );
kapp->setTopWidget( this );
setCaption( kapp->makeStdCaption( i18n("%1 - %2").arg( artist, album ) ) );
m_layout = new TQHBoxLayout( this );
m_layout->setAutoAdd( true );
m_pixmapViewer = new PixmapViewer( this, m_pixmap );
setFixedSize( m_pixmapViewer->maximalSize() );
}
void CoverManager::viewCover( const TQString& artist, const TQString& album, TQWidget *parent ) //static
{
//TQDialog means "escape" works as expected
TQDialog *dialog = new CoverViewDialog( artist, album, parent );
dialog->show();
}
TQString CoverManager::amazonTld() //static
{
if (AmarokConfig::amazonLocale() == "us")
return "com";
else if (AmarokConfig::amazonLocale()== "jp")
return "co.jp";
else if (AmarokConfig::amazonLocale() == "uk")
return "co.uk";
else if (AmarokConfig::amazonLocale() == "ca")
return "ca";
else
return AmarokConfig::amazonLocale();
}
void CoverManager::fetchMissingCovers() //SLOT
{
#ifdef AMAZON_SUPPORT
DEBUG_BLOCK
for ( TQIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
CoverViewItem *coverItem = static_cast<CoverViewItem*>( item );
if( !coverItem->hasCover() ) {
m_fetchCovers += coverItem->artist() + " @@@ " + coverItem->album();
m_fetchingCovers++;
}
}
if( !m_fetchCounter ) //loop isn't started yet
fetchCoversLoop();
updateStatusBar();
m_fetchButton->setEnabled( false );
#endif
}
void CoverManager::fetchCoversLoop() //SLOT
{
#ifdef AMAZON_SUPPORT
if( m_fetchCounter < m_fetchCovers.count() ) {
//get artist and album from keyword
const TQStringList values = TQStringList::split( " @@@ ", m_fetchCovers[m_fetchCounter], true );
if( values.count() > 1 )
CollectionDB::instance()->fetchCover( this, values[0], values[1], m_fetchCovers.count() != 1); //edit mode when fetching 1 cover
m_fetchCounter++;
// Wait 1 second, since amazon caps the number of accesses per client
TQTimer::singleShot( 1000, this, TQT_SLOT( fetchCoversLoop() ) );
}
else {
m_fetchCovers.clear();
m_fetchCounter = 0;
}
#endif
}
void CoverManager::showOnce( const TQString &artist )
{
if ( !s_instance ) {
artistToSelectInInitFunction = artist;
new CoverManager(); //shows itself
}
else {
s_instance->setActiveWindow();
s_instance->raise();
}
}
void CoverManager::slotArtistSelected( TQListViewItem *item ) //SLOT
{
if( item->depth() ) //album item
return;
TQString artist = item->text(0);
if( artist.endsWith( ", The" ) )
CollectionView::instance()->manipulateThe( artist, false );
m_coverView->clear();
m_coverItems.clear();
// reset current view mode state to "AllAlbum" which is the default on artist change in left panel
m_currentView = AllAlbums;
m_viewMenu->setItemChecked( AllAlbums, true );
m_viewMenu->setItemChecked( AlbumsWithCover, false );
m_viewMenu->setItemChecked( AlbumsWithoutCover, false );
TQProgressDialog progress( this, 0, true );
progress.setLabelText( i18n("Loading Thumbnails...") );
progress.TQDialog::setCaption( i18n("...") );
//NOTE we MUST show the dialog, otherwise the closeEvents get processed
// in the processEvents() calls below, GRUMBLE! TQt sux0rs
progress.show();
progress.repaint( false ); //ensures the dialog isn't blank
//this is an extra processEvent call for the sake of init() and aesthetics
//it isn't necessary
kapp->processEvents();
//this can be a bit slow
TQApplication::setOverrideCursor( KCursor::waitCursor() );
QueryBuilder qb;
TQStringList albums;
qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.setOptions( QueryBuilder::optRemoveDuplicates );
qb.setOptions( QueryBuilder::optNoCompilations );
if ( item != m_artistView->firstChild() )
qb.addMatches( QueryBuilder::tabArtist, artist );
albums = qb.run();
//also retrieve compilations when we're showing all items (first treenode) or
//"Various Artists" (last treenode)
if ( item == m_artistView->firstChild() || item == m_artistView->lastChild() )
{
TQStringList cl;
qb.clear();
qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.setOptions( QueryBuilder::optRemoveDuplicates );
qb.setOptions( QueryBuilder::optOnlyCompilations );
cl = qb.run();
for ( uint i = 0; i < cl.count(); i++ ) {
albums.append( i18n( "Various Artists" ) );
albums.append( cl[ i ] );
}
}
TQApplication::restoreOverrideCursor();
progress.setTotalSteps( (albums.count()/2) + (albums.count()/10) );
//insert the covers first because the list view is soooo paint-happy
//doing it in the second loop looks really bad, unfortunately
//this is the slowest step in the bit that we can't process events
uint x = 0;
foreach( albums )
{
const TQString artist = *it;
const TQString album = *(++it);
m_coverItems.append( new CoverViewItem( m_coverView, m_coverView->lastItem(), artist, album ) );
if ( ++x % 50 == 0 ) {
progress.setProgress( x / 5 ); // we do it less often due to bug in TQt, ask Max
kapp->processEvents(); // TQProgressDialog also calls this, but not always due to TQt bug!
//only worth testing for after processEvents() is called
if( progress.wasCancelled() )
break;
}
}
//now, load the thumbnails
for( TQIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
progress.setProgress( progress.progress() + 1 );
kapp->processEvents();
if( progress.wasCancelled() )
break;
static_cast<CoverViewItem*>(item)->loadCover();
}
updateStatusBar();
}
void CoverManager::showCoverMenu( TQIconViewItem *item, const TQPoint &p ) //SLOT
{
#define item static_cast<CoverViewItem*>(item)
if( !item ) return;
enum { SHOW, FETCH, CUSTOM, DELETE, APPEND };
TDEPopupMenu menu;
menu.insertTitle( i18n( "Cover Image" ) );
TQPtrList<CoverViewItem> selected = selectedItems();
if( selected.count() > 1 ) {
menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch Selected Covers" ), FETCH );
menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover for Selected Albums" ), CUSTOM );
menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Selected Covers" ), DELETE );
menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
}
else {
menu.insertItem( SmallIconSet( Amarok::icon( "zoom" ) ), i18n( "&Show Fullsize" ), SHOW );
menu.insertItem( SmallIconSet( Amarok::icon( "download" ) ), i18n( "&Fetch From amazon.%1" ).arg( CoverManager::amazonTld() ), FETCH );
menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "Set &Custom Cover" ), CUSTOM );
menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
menu.insertSeparator();
menu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n( "&Unset Cover" ), DELETE );
menu.setItemEnabled( SHOW, item->hasCover() );
menu.setItemEnabled( DELETE, item->canRemoveCover() );
}
#ifndef AMAZON_SUPPORT
menu.setItemEnabled( FETCH, false );
#endif
switch( menu.exec(p) ) {
case SHOW:
viewCover( item->artist(), item->album(), this );
break;
#ifdef AMAZON_SUPPORT
case FETCH:
fetchSelectedCovers();
break;
#endif
case CUSTOM:
{
setCustomSelectedCovers();
break;
}
case DELETE:
deleteSelectedCovers();
break;
case APPEND:
{
CoverViewItem* sel;
for ( sel = selected.first(); sel; sel = selected.next() )
{
TQString artist_id;
TQString album_id;
artist_id.setNum( CollectionDB::instance()->artistID( sel->artist() ) );
album_id.setNum( CollectionDB::instance()->albumID( sel->album() ) );
Playlist::instance()->insertMedia( CollectionDB::instance()->albumTracks( artist_id, album_id ), Playlist::Append );
}
break;
}
default: ;
}
#undef item
}
void CoverManager::coverItemExecuted( TQIconViewItem *item ) //SLOT
{
#define item static_cast<CoverViewItem*>(item)
if( !item ) return;
item->setSelected( true );
if ( item->hasCover() )
viewCover( item->artist(), item->album(), this );
else
fetchSelectedCovers();
#undef item
}
void CoverManager::slotSetFilter() //SLOT
{
m_filter = m_searchEdit->text();
m_coverView->selectAll( false);
TQIconViewItem *item = m_coverView->firstItem();
while ( item )
{
TQIconViewItem *tmp = item->nextItem();
m_coverView->takeItem( item );
item = tmp;
}
m_coverView->setAutoArrange( false );
for( TQIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
{
CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
if( coverItem->album().contains( m_filter, false ) || coverItem->artist().contains( m_filter, false ) )
m_coverView->insertItem( item, m_coverView->lastItem() );
}
m_coverView->setAutoArrange( true );
m_coverView->arrangeItemsInGrid();
updateStatusBar();
}
void CoverManager::slotSetFilterTimeout() //SLOT
{
if ( m_timer->isActive() ) m_timer->stop();
m_timer->start( 180, true );
}
void CoverManager::changeView( int id ) //SLOT
{
if( m_currentView == id ) return;
//clear the iconview without deleting items
m_coverView->selectAll( false);
TQIconViewItem *item = m_coverView->firstItem();
while ( item ) {
TQIconViewItem *tmp = item->nextItem();
m_coverView->takeItem( item );
item = tmp;
}
m_coverView->setAutoArrange(false );
for( TQIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() ) {
bool show = false;
CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
if( !m_filter.isEmpty() ) {
if( !coverItem->album().contains( m_filter, false ) && !coverItem->artist().contains( m_filter, false ) )
continue;
}
if( id == AllAlbums ) //show all albums
show = true;
else if( id == AlbumsWithCover && coverItem->hasCover() ) //show only albums with cover
show = true;
else if( id == AlbumsWithoutCover && !coverItem->hasCover() ) //show only albums without cover
show = true;
if( show ) m_coverView->insertItem( item, m_coverView->lastItem() );
}
m_coverView->setAutoArrange( true );
m_viewMenu->setItemChecked( m_currentView, false );
m_viewMenu->setItemChecked( id, true );
m_coverView->arrangeItemsInGrid();
m_currentView = id;
}
void CoverManager::changeLocale( int id ) //SLOT
{
TQString locale = CoverFetcher::localeIDToString( id );
AmarokConfig::setAmazonLocale( locale );
m_amazonLocaleMenu->setItemChecked( m_currentLocale, false );
m_amazonLocaleMenu->setItemChecked( id, true );
m_currentLocale = id;
}
void CoverManager::coverFetched( const TQString &artist, const TQString &album ) //SLOT
{
loadCover( artist, album );
m_coversFetched++;
updateStatusBar();
}
void CoverManager::coverRemoved( const TQString &artist, const TQString &album ) //SLOT
{
loadCover( artist, album );
m_coversFetched--;
updateStatusBar();
}
void CoverManager::coverFetcherError()
{
DEBUG_FUNC_INFO
m_coverErrors++;
updateStatusBar();
}
void CoverManager::stopFetching()
{
Debug::Block block( __PRETTY_FUNCTION__ );
m_fetchCovers.clear();
m_fetchCounter = 0;
//delete all cover fetchers
TQObjectList* list = queryList( "CoverFetcher" );
for( TQObject *obj = list->first(); obj; obj = list->next() )
obj->deleteLater();
delete list;
m_fetchingCovers = 0;
updateStatusBar();
}
// PRIVATE
void CoverManager::loadCover( const TQString &artist, const TQString &album )
{
for( TQIconViewItem *item = m_coverItems.first(); item; item = m_coverItems.next() )
{
CoverViewItem *coverItem = static_cast<CoverViewItem*>(item);
if ( album == coverItem->album() && ( artist == coverItem->artist() || ( artist.isEmpty() && coverItem->artist().isEmpty() ) ) )
{
coverItem->loadCover();
return;
}
}
}
void CoverManager::setCustomSelectedCovers()
{
//function assumes something is selected
TQPtrList<CoverViewItem> selected = selectedItems();
CoverViewItem* first = selected.getFirst();
TQString artist_id; artist_id.setNum( CollectionDB::instance()->artistID( first->artist() ) );
TQString album_id; album_id.setNum( CollectionDB::instance()->albumID( first->album() ) );
TQStringList values = CollectionDB::instance()->albumTracks( artist_id, album_id );
TQString startPath = ":homedir";
if ( !values.isEmpty() ) {
KURL url;
url.setPath( values.first() );
startPath = url.directory();
}
KURL file = KFileDialog::getImageOpenURL( startPath, this, i18n( "Select Cover Image File" ) );
if ( !file.isEmpty() ) {
tqApp->processEvents(); //it may takes a while so process pending events
TQString tmpFile;
TQImage image = CollectionDB::fetchImage(file, tmpFile);
for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
CollectionDB::instance()->setAlbumImage( item->artist(), item->album(), image );
item->loadCover();
}
TDEIO::NetAccess::removeTempFile( tmpFile );
}
}
void CoverManager::fetchSelectedCovers()
{
TQPtrList<CoverViewItem> selected = selectedItems();
for ( CoverViewItem* item = selected.first(); item; item = selected.next() )
m_fetchCovers += item->artist() + " @@@ " + item->album();
m_fetchingCovers += selected.count();
if( !m_fetchCounter ) //loop isn't started yet
fetchCoversLoop();
updateStatusBar();
}
void CoverManager::deleteSelectedCovers()
{
TQPtrList<CoverViewItem> selected = selectedItems();
int button = KMessageBox::warningContinueCancel( this,
i18n( "Are you sure you want to remove this cover from the Collection?",
"Are you sure you want to delete these %n covers from the Collection?",
selected.count() ),
TQString(),
KStdGuiItem::del() );
if ( button == KMessageBox::Continue ) {
for ( CoverViewItem* item = selected.first(); item; item = selected.next() ) {
tqApp->processEvents();
if ( CollectionDB::instance()->removeAlbumImage( item->artist(), item->album() ) ) //delete selected cover
coverRemoved( item->artist(), item->album() );
}
}
}
TQPtrList<CoverViewItem> CoverManager::selectedItems()
{
TQPtrList<CoverViewItem> selectedItems;
for ( TQIconViewItem* item = m_coverView->firstItem(); item; item = item->nextItem() )
if ( item->isSelected() )
selectedItems.append( static_cast<CoverViewItem*>(item) );
return selectedItems;
}
void CoverManager::updateStatusBar()
{
TQString text;
//cover fetching info
if( m_fetchingCovers ) {
//update the progress bar
m_progress->setTotalSteps( m_fetchingCovers );
m_progress->setProgress( m_coversFetched + m_coverErrors );
if( m_progressBox->isHidden() )
m_progressBox->show();
//update the status text
if( m_coversFetched + m_coverErrors >= m_progress->totalSteps() ) {
//fetching finished
text = i18n( "Finished." );
if( m_coverErrors )
text += i18n( " Cover not found", " <b>%n</b> covers not found", m_coverErrors );
//reset counters
m_fetchingCovers = 0;
m_coversFetched = 0;
m_coverErrors = 0;
TQTimer::singleShot( 2000, this, TQT_SLOT( updateStatusBar() ) );
}
if( m_fetchingCovers == 1 ) {
TQStringList values = TQStringList::split( " @@@ ", m_fetchCovers[0], true ); //get artist and album name
if ( values.count() >= 2 )
{
if( values[0].isEmpty() )
text = i18n( "Fetching cover for %1..." ).arg( values[1] );
else
text = i18n( "Fetching cover for %1 - %2..." ).arg( values[0], values[1] );
}
}
else if( m_fetchingCovers ) {
text = i18n( "Fetching 1 cover: ", "Fetching <b>%n</b> covers... : ", m_fetchingCovers );
if( m_coversFetched )
text += i18n( "1 fetched", "%n fetched", m_coversFetched );
if( m_coverErrors ) {
if( m_coversFetched ) text += i18n(" - ");
text += i18n( "1 not found", "%n not found", m_coverErrors );
}
if( m_coversFetched + m_coverErrors == 0 )
text += i18n( "Connecting..." );
}
}
else {
m_coversFetched = 0;
m_coverErrors = 0;
uint totalCounter = 0, missingCounter = 0;
if( m_progressBox->isShown() )
m_progressBox->hide();
//album info
for( TQIconViewItem *item = m_coverView->firstItem(); item; item = item->nextItem() ) {
totalCounter++;
if( !static_cast<CoverViewItem*>( item )->hasCover() )
missingCounter++; //counter for albums without cover
}
if( !m_filter.isEmpty() )
text = i18n( "1 result for \"%1\"", "%n results for \"%1\"", totalCounter ).arg( m_filter );
else if( m_artistView->selectedItem() ) {
text = i18n( "1 album", "%n albums", totalCounter );
if( m_artistView->selectedItem() != m_artistView->firstChild() ) //showing albums by an artist
{
TQString artist = m_artistView->selectedItem()->text(0);
if( artist.endsWith( ", The" ) )
CollectionView::instance()->manipulateThe( artist, false );
text += i18n( " by " ) + artist;
}
}
if( missingCounter )
text += i18n(" - ( <b>%1</b> without cover )" ).arg( missingCounter );
#ifdef AMAZON_SUPPORT
m_fetchButton->setEnabled( missingCounter );
#endif
}
m_statusLabel->setText( text );
}
void CoverManager::setStatusText( TQString text )
{
m_oldStatusText = m_statusLabel->text();
m_statusLabel->setText( text );
}
//////////////////////////////////////////////////////////////////////
// CLASS CoverView
/////////////////////////////////////////////////////////////////////
CoverView::CoverView( TQWidget *parent, const char *name, WFlags f )
: TDEIconView( parent, name, f )
{
Debug::Block block( __PRETTY_FUNCTION__ );
setArrangement( TQIconView::LeftToRight );
setResizeMode( TQIconView::Adjust );
setSelectionMode( TQIconView::Extended );
arrangeItemsInGrid();
setAutoArrange( true );
setItemsMovable( false );
// as long as TQIconView only shows tooltips when the cursor is over the
// icon (and not the text), we have to create our own tooltips
setShowToolTips( false );
connect( this, TQT_SIGNAL( onItem( TQIconViewItem * ) ), TQT_SLOT( setStatusText( TQIconViewItem * ) ) );
connect( this, TQT_SIGNAL( onViewport() ), CoverManager::instance(), TQT_SLOT( updateStatusBar() ) );
}
TQDragObject *CoverView::dragObject()
{
CoverViewItem *item = static_cast<CoverViewItem*>( currentItem() );
if( !item )
return 0;
const TQString sql = "SELECT tags.url FROM tags, album WHERE album.name %1 AND tags.album = album.id ORDER BY tags.track;";
const TQStringList values = CollectionDB::instance()->query( sql.arg( CollectionDB::likeCondition( item->album() ) ) );
KURL::List urls;
for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it )
urls += *it;
TQString imagePath = CollectionDB::instance()->albumImage( item->artist(), item->album(), false, 1 );
KMultipleDrag *drag = new KMultipleDrag( this );
drag->setPixmap( item->coverPixmap() );
drag->addDragObject( new TQIconDrag( this ) );
drag->addDragObject( new TQImageDrag( TQImage( imagePath ) ) );
drag->addDragObject( new KURLDrag( urls ) );
return drag;
}
void CoverView::setStatusText( TQIconViewItem *item )
{
#define item static_cast<CoverViewItem *>( item )
if ( !item )
return;
bool sampler = false;
//compilations have valDummy for artist. see QueryBuilder::addReturnValue(..) for explanation
//FIXME: Don't rely on other independent code, use an sql query
if( item->artist().isEmpty() ) sampler = true;
TQString tipContent = i18n( "%1 - %2" ).arg( sampler ? i18n("Various Artists") : item->artist() )
.arg( item->album() );
CoverManager::instance()->setStatusText( tipContent );
#undef item
}
//////////////////////////////////////////////////////////////////////
// CLASS CoverViewItem
/////////////////////////////////////////////////////////////////////
CoverViewItem::CoverViewItem( TQIconView *parent, TQIconViewItem *after, const TQString &artist, const TQString &album )
: TDEIconViewItem( parent, after, album )
, m_artist( artist )
, m_album( album )
, m_coverImagePath( CollectionDB::instance()->albumImage( m_artist, m_album, false, 0, &m_embedded ) )
, m_coverPixmap( 0 )
{
setDragEnabled( true );
setDropEnabled( true );
calcRect();
}
bool CoverViewItem::hasCover() const
{
return !m_coverImagePath.endsWith( "nocover.png" ) && TQFile::exists( m_coverImagePath );
}
void CoverViewItem::loadCover()
{
m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 1, &m_embedded );
m_coverPixmap = TQPixmap( m_coverImagePath ); //create the scaled cover
repaint();
}
void CoverViewItem::calcRect( const TQString& )
{
int thumbWidth = AmarokConfig::coverPreviewSize();
TQFontMetrics fm = iconView()->fontMetrics();
TQRect itemPixmapRect( 5, 1, thumbWidth, thumbWidth );
TQRect itemRect = rect();
itemRect.setWidth( thumbWidth + 10 );
itemRect.setHeight( thumbWidth + fm.lineSpacing() + 2 );
TQRect itemTextRect( 0, thumbWidth+2, itemRect.width(), fm.lineSpacing() );
setPixmapRect( itemPixmapRect );
setTextRect( itemTextRect );
setItemRect( itemRect );
}
void CoverViewItem::paintItem(TQPainter* p, const TQColorGroup& cg)
{
TQRect itemRect = rect();
p->save();
p->translate( itemRect.x(), itemRect.y() );
// draw the border
p->setPen( cg.mid() );
p->drawRect( 0, 0, itemRect.width(), pixmapRect().height()+2 );
// draw the cover image
if( !m_coverPixmap.isNull() )
p->drawPixmap( pixmapRect().x() + (pixmapRect().width() - m_coverPixmap.width())/2,
pixmapRect().y() + (pixmapRect().height() - m_coverPixmap.height())/2, m_coverPixmap );
//justify the album name
TQString str = text();
TQFontMetrics fm = p->fontMetrics();
int nameWidth = fm.width( str );
if( nameWidth > textRect().width() )
{
str = KStringHandler::rPixelSqueeze( str, p->fontMetrics(), textRect().width() );
}
p->setPen( cg.text() );
p->drawText( textRect(), TQt::AlignCenter, str );
if( isSelected() ) {
p->setPen( cg.highlight() );
p->drawRect( pixmapRect() );
p->drawRect( pixmapRect().left()+1, pixmapRect().top()+1, pixmapRect().width()-2, pixmapRect().height()-2);
p->drawRect( pixmapRect().left()+2, pixmapRect().top()+2, pixmapRect().width()-4, pixmapRect().height()-4);
}
p->restore();
}
void CoverViewItem::dropped( TQDropEvent *e, const TQValueList<TQIconDragItem> & )
{
if( TQImageDrag::canDecode( e ) ) {
if( hasCover() ) {
int button = KMessageBox::warningContinueCancel( iconView(),
i18n( "Are you sure you want to overwrite this cover?"),
i18n("Overwrite Confirmation"),
i18n("&Overwrite") );
if( button == KMessageBox::Cancel )
return;
}
TQImage img;
TQImageDrag::decode( e, img );
CollectionDB::instance()->setAlbumImage( artist(), album(), img );
m_coverImagePath = CollectionDB::instance()->albumImage( m_artist, m_album, false, 0 );
loadCover();
}
}
void CoverViewItem::dragEntered()
{
setSelected( true );
}
void CoverViewItem::dragLeft()
{
setSelected( false );
}
#include "covermanager.moc"