// (c) 2004 Christian Muehlhaeuser <chris@chris.de>
// (c) 2005 Reigo Reinmets <xatax@hot.ee>
// (c) 2005 Mark Kretschmann <markey@web.de>
// (c) 2006 Peter C. Ndikuwera <pndiku@gmail.com>
// (c) 2006 Alexandre Pereira de Oliveira <aleprj@gmail.com>
// (c) 2006 Maximilian Kossick <maximilian.kossick@googlemail.com>
// License: GNU General Public License V2


#define DEBUG_PREFIX "ContextBrowser"

#include "amarok.h"
#include "amarokconfig.h"
#include "app.h"
#include "browserToolBar.h"
#include "debug.h"
#include "clicklineedit.h"
#include "collectiondb.h"
#include "collectionbrowser.h"
#include "colorgenerator.h"
#include "contextbrowser.h"
#include "coverfetcher.h"
#include "covermanager.h"
#include "cuefile.h"
#include "enginecontroller.h"
#include "htmlview.h"
#include "lastfm.h"
#include "mediabrowser.h"
#include "metabundle.h"
#include "mountpointmanager.h"
#include "playlist.h"      //appendMedia()
#include "podcastbundle.h"
#include "qstringx.h"
#include "scriptmanager.h"
#include "starmanager.h"
#include "statusbar.h"
#include "tagdialog.h"
#include "threadmanager.h"

#include <tqbuffer.h>
#include <tqdatetime.h>
#include <tqdeepcopy.h>
#include <tqdom.h>
#include <tqimage.h>
#include <tqregexp.h>
#include <tqtextstream.h>  // External CSS reading
#include <tqvbox.h> //wiki tab
#include <tqhbox.h>
#include <tqlayout.h>
#include <tqlineedit.h>
#include <tqlistview.h>
#include <tqtimer.h>
#include <tqtooltip.h>

#include <tdeaction.h>
#include <tdeapplication.h> //kapp
#include <kcalendarsystem.h>  // for Amarok::verboseTimeSince()
#include <tdeconfig.h> // suggested/related/favorite box visibility
#include <kdialog.h>
#include <tdefiledialog.h>
#include <tdeglobal.h>
#include <kiconloader.h>
#include <tdeio/job.h>
#include <tdeio/jobclasses.h>
#include <kmdcodec.h> // for data: URLs
#include <tdemessagebox.h>
#include <tdepopupmenu.h>
#include <kstandarddirs.h>
#include <ktextedit.h>
#include <tdetoolbarbutton.h>

#include <unistd.h> //usleep()

namespace Amarok
{
    TQString escapeHTML( const TQString &s )
    {
        return TQString(s).replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
        // .replace( "%", "%25" ) has to be the first(!) one, otherwise we would do things like converting spaces into %20 and then convert them into %25%20
    }

    TQString escapeHTMLAttr( const TQString &s )
    {
        return TQString(s).replace( "%", "%25" ).replace( "'", "%27" ).replace( "\"", "%22" ).replace( "#", "%23" ).replace( "?", "%3F" );
    }

    TQString unescapeHTMLAttr( const TQString &s )
    {
        return TQString(s).replace( "%3F", "?" ).replace( "%23", "#" ).replace( "%22", "\"" ).replace( "%27", "'" ).replace( "%25", "%" );
    }

    TQString verboseTimeSince( const TQDateTime &datetime )
    {
        const TQDateTime now = TQDateTime::currentDateTime();
        const int datediff = datetime.daysTo( now );

        if( datediff >= 6*7 /*six weeks*/ ) {  // return absolute month/year
            const KCalendarSystem *cal = TDEGlobal::locale()->calendar();
            const TQDate date = datetime.date();
            return i18n( "monthname year", "%1 %2" ).arg( cal->monthName(date), cal->yearString(date, false) );
        }

        //TODO "last week" = maybe within 7 days, but prolly before last sunday

        if( datediff >= 7 )  // return difference in weeks
            return i18n( "One week ago", "%n weeks ago", (datediff+3)/7 );

        if( datediff == -1 )
            return i18n( "Tomorrow" );

        const int timediff = datetime.secsTo( now );

        if( timediff >= 24*60*60 /*24 hours*/ )  // return difference in days
            return datediff == 1 ?
                    i18n( "Yesterday" ) :
                    i18n( "One day ago", "%n days ago", (timediff+12*60*60)/(24*60*60) );

        if( timediff >= 90*60 /*90 minutes*/ )  // return difference in hours
            return i18n( "One hour ago", "%n hours ago", (timediff+30*60)/(60*60) );

        //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently"

        if( timediff >= 0 )  // return difference in minutes
            return timediff/60 ?
                    i18n( "One minute ago", "%n minutes ago", (timediff+30)/60 ) :
                    i18n( "Within the last minute" );

        return i18n( "The future" );
    }

    TQString verboseTimeSince( uint time_t )
    {
        if( !time_t )
            return i18n( "Never" );

        TQDateTime dt;
        dt.setTime_t( time_t );
        return verboseTimeSince( dt );
    }

    extern TDEConfig *config( const TQString& );

    /**
    * Function that must be used when separating contextBrowser escaped urls
    * detail can contain track/discnumber
    */
    void albumArtistTrackFromUrl( TQString url, TQString &artist, TQString &album, TQString &detail )
    {
        if ( !url.contains("@@@") ) return;
        //TDEHTML removes the trailing space!
        if ( url.endsWith( " @@@" ) )
            url += ' ';

        const TQStringList list = TQStringList::split( " @@@ ", url, true );

        int size = list.count();

        Q_ASSERT( size>0 );

        artist = size > 0 ? unescapeHTMLAttr( list[0] ) : "";
        album  = size > 1 ? unescapeHTMLAttr( list[1] ) : "";
        detail = size > 2 ? unescapeHTMLAttr( list[2] ) : "";
    }

}


using Amarok::QStringx;
using Amarok::escapeHTML;
using Amarok::escapeHTMLAttr;
using Amarok::unescapeHTMLAttr;


static
TQString albumImageTooltip( const TQString &albumImage, int size )
{
    if ( albumImage == CollectionDB::instance()->notAvailCover( false, size ) )
        return escapeHTMLAttr( i18n( "Click to fetch cover from amazon.%1, right-click for menu." ).arg( CoverManager::amazonTld() ) );

    return escapeHTMLAttr( i18n( "Click for information from Amazon, right-click for menu." ) );
}


ContextBrowser *ContextBrowser::s_instance = 0;
TQString ContextBrowser::s_wikiLocale = "en";


ContextBrowser::ContextBrowser( const char *name )
        : KTabWidget( 0, name )
        , EngineObserver( EngineController::instance() )
        , m_dirtyCurrentTrackPage( true )
        , m_dirtyLyricsPage( true )
        , m_dirtyWikiPage( true )
        , m_emptyDB( CollectionDB::instance()->isEmpty() )
        , m_wikiBackPopup( new TDEPopupMenu( this ) )
        , m_wikiForwardPopup( new TDEPopupMenu( this ) )
        , m_wikiJob( NULL )
        , m_wikiConfigDialog( NULL )
        , m_relatedOpen( true )
        , m_suggestionsOpen( true )
        , m_favoritesOpen( true )
        , m_labelsOpen( true )
        , m_showFreshPodcasts( true )
        , m_showFavoriteAlbums( true )
        , m_showNewestAlbums( true )
        , m_browseArtists( false )
        , m_browseLabels( false )
        , m_cuefile( NULL )
{
    s_instance = this;
    s_wikiLocale = AmarokConfig::wikipediaLocale();

    m_contextTab = new TQVBox(this, "context_tab");

    m_currentTrackPage = new HTMLView( m_contextTab, "current_track_page", true /* DNDEnabled */,
            true /*JScriptEnabled*/ );

    m_lyricsTab = new TQVBox(this, "lyrics_tab");

    m_lyricsToolBar = new Browser::ToolBar( m_lyricsTab );
    m_lyricsToolBar->setIconText( TDEToolBar::IconTextRight, false );
    m_lyricsToolBar->insertButton( Amarok::icon( "refresh" ), LYRICS_REFRESH, true, i18n("Refresh") );
    m_lyricsToolBar->insertButton( Amarok::icon( "add_lyrics" ), LYRICS_ADD, true, i18n("Add") );
    m_lyricsToolBar->insertButton( Amarok::icon( "edit" ), LYRICS_EDIT, true, i18n("Edit") );
    m_lyricsToolBar->setToggle( LYRICS_EDIT, true );
    m_lyricsToolBar->insertButton( Amarok::icon( "search" ), LYRICS_SEARCH, true, i18n("Search") );
    m_lyricsToolBar->setIconText( TDEToolBar::IconOnly, false );
    m_lyricsToolBar->insertButton( Amarok::icon( "external" ), LYRICS_BROWSER, true, i18n("Open in external browser") );

    { //Search text inside lyrics. Code inspired/copied from playlistwindow.cpp
       m_lyricsTextBar = new TDEToolBar( m_lyricsTab, "NotMainToolBar" );
       m_lyricsTextBar->hide();
       m_lyricsTextBarShowed=false;

       m_lyricsTextBar->setIconSize( 22, false ); //looks more sensible
       m_lyricsTextBar->setFlat( true ); //removes the ugly frame
       m_lyricsTextBar->setMovingEnabled( false ); //removes the ugly frame

       m_lyricsTextBar->boxLayout()->addStretch();

       TQWidget *button = new TDEToolBarButton( "locationbar_erase", 1, m_lyricsTextBar );
       TQLabel *filter_label = new TQLabel( i18n("S&earch:") + ' ', m_lyricsTextBar );
       m_lyricsSearchText = new ClickLineEdit( i18n( "Search in lyrics" ), m_lyricsTextBar );
       filter_label->setBuddy( m_lyricsSearchText );

       m_lyricsTextBar->setStretchableWidget(m_lyricsSearchText );

       m_lyricsSearchText->setFrame( TQFrame::Sunken );
       m_lyricsSearchText->installEventFilter( this ); //we intercept keyEvents

       connect( button, TQ_SIGNAL(clicked()), m_lyricsSearchText, TQ_SLOT(clear()) );

       TQToolTip::add( button, i18n( "Clear search" ) );
       TQString filtertip = i18n( "Enter text to search for. Press enter to advance to the next match." );

       TQToolTip::add( m_lyricsSearchText, filtertip );

       connect ( button, TQ_SIGNAL(clicked()), m_lyricsSearchText, TQ_SLOT(clear()) );
       connect ( m_lyricsSearchText, TQ_SIGNAL(textChanged(const TQString &)), this, TQ_SLOT(lyricsSearchText(const TQString & )) );
       connect ( m_lyricsSearchText, TQ_SIGNAL(returnPressed()), this, (TQ_SLOT(lyricsSearchTextNext())) );
       Amarok::actionCollection()->setAutoConnectShortcuts ( true );
       new TDEAction( i18n("Search text in lyrics"), TDEShortcut("/"), this,TQ_SLOT( lyricsSearchTextShow() ), Amarok::actionCollection(), "search_text_lyric");
       Amarok::actionCollection()->setAutoConnectShortcuts ( false );
    }



    m_lyricsPage = new HTMLView( m_lyricsTab, "lyrics_page", true /* DNDEnabled */, false /* JScriptEnabled*/ );
    m_lyricsTextEdit = new KTextEdit ( m_lyricsTab, "lyrics_text_edit");
    m_lyricsTextEdit->setTextFormat( TQt::PlainText );
    m_lyricsTextEdit->hide();

    m_wikiTab = new TQVBox(this, "wiki_tab");

    m_wikiToolBar = new Browser::ToolBar( m_wikiTab );
    m_wikiToolBar->insertButton( "back", WIKI_BACK, false, i18n("Back") );
    m_wikiToolBar->insertButton( "forward", WIKI_FORWARD, false, i18n("Forward") );
    m_wikiToolBar->insertLineSeparator();
    m_wikiToolBar->insertButton( Amarok::icon( "artist" ), WIKI_ARTIST, false, i18n("Artist Page") );
    m_wikiToolBar->insertButton( Amarok::icon( "album" ), WIKI_ALBUM, false, i18n("Album Page") );
    m_wikiToolBar->insertButton( Amarok::icon( "track" ), WIKI_TITLE, false, i18n("Title Page") );
    m_wikiToolBar->insertLineSeparator();
    m_wikiToolBar->insertButton( Amarok::icon( "external" ), WIKI_BROWSER, true, i18n("Open in external browser") );
    m_wikiToolBar->insertButton( Amarok::icon( "change_language" ), WIKI_CONFIG, true, i18n("Change Locale") );

    m_wikiToolBar->setDelayedPopup( WIKI_BACK, m_wikiBackPopup );
    m_wikiToolBar->setDelayedPopup( WIKI_FORWARD, m_wikiForwardPopup );

    m_wikiPage = new HTMLView( m_wikiTab, "wiki_page", true /* DNDEnabled */, false /* JScriptEnabled */ );

    m_cuefile = CueFile::instance();
    connect( m_cuefile, TQ_SIGNAL(metaData( const MetaBundle& )),
             EngineController::instance(), TQ_SLOT(currentTrackMetaDataChanged( const MetaBundle& )) );
    connect( m_cuefile, TQ_SIGNAL(newCuePoint( long, long, long )),
             Scrobbler::instance(), TQ_SLOT(subTrack( long, long, long )) );

    addTab( m_contextTab, SmallIconSet( Amarok::icon( "music" ) ),  i18n( "Music" ) );
    addTab( m_lyricsTab,  SmallIconSet( Amarok::icon( "lyrics" ) ), i18n( "Lyrics" ) );
    addTab( m_wikiTab,    SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Artist" ) );

    setTabEnabled( m_lyricsTab, false );
    setTabEnabled( m_wikiTab, false );

    m_showRelated   = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowRelated", true );
    m_showSuggested = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowSuggested", true );
    m_showFaves     = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFaves", true );
    m_showLabels    = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowLabels", true );

    m_showFreshPodcasts  = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFreshPodcasts", true );
    m_showNewestAlbums   = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowNewestAlbums", true );
    m_showFavoriteAlbums = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFavoriteAlbums", true );

    // Delete folder with the cached coverimage shadow pixmaps
    TDEIO::del( KURL::fromPathOrURL( Amarok::saveLocation( "covershadow-cache/" ) ), false, false );

    connect( this, TQ_SIGNAL( currentChanged( TQWidget* ) ), TQ_SLOT( tabChanged( TQWidget* ) ) );

    connect( m_currentTrackPage->browserExtension(), TQ_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
             this,                                   TQ_SLOT( openURLRequest( const KURL & ) ) );
    connect( m_lyricsPage->browserExtension(),       TQ_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
             this,                                   TQ_SLOT( openURLRequest( const KURL & ) ) );
    connect( m_wikiPage->browserExtension(),         TQ_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
             this,                                   TQ_SLOT( openURLRequest( const KURL & ) ) );

    connect( m_currentTrackPage, TQ_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
             this,               TQ_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
    connect( m_lyricsPage,       TQ_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
             this,               TQ_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
    connect( m_wikiPage,         TQ_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
             this,               TQ_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );

    connect( m_lyricsToolBar->getButton( LYRICS_ADD ),     TQ_SIGNAL(clicked( int )), TQ_SLOT(lyricsAdd()) );
    connect( m_lyricsToolBar->getButton( LYRICS_EDIT ),    TQ_SIGNAL(toggled( int )), TQ_SLOT(lyricsEditToggle()) );
    connect( m_lyricsToolBar->getButton( LYRICS_SEARCH ),  TQ_SIGNAL(clicked( int )), TQ_SLOT(lyricsSearch()) );
    connect( m_lyricsToolBar->getButton( LYRICS_REFRESH ), TQ_SIGNAL(clicked( int )), TQ_SLOT(lyricsRefresh()) );
    connect( m_lyricsToolBar->getButton( LYRICS_BROWSER ), TQ_SIGNAL(clicked( int )), TQ_SLOT(lyricsExternalPage()) );

    connect( m_wikiToolBar->getButton( WIKI_BACK    ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiHistoryBack()) );
    connect( m_wikiToolBar->getButton( WIKI_FORWARD ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiHistoryForward()) );
    connect( m_wikiToolBar->getButton( WIKI_ARTIST  ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiArtistPage()) );
    connect( m_wikiToolBar->getButton( WIKI_ALBUM   ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiAlbumPage()) );
    connect( m_wikiToolBar->getButton( WIKI_TITLE   ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiTitlePage()) );
    connect( m_wikiToolBar->getButton( WIKI_BROWSER ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiExternalPage()) );
    connect( m_wikiToolBar->getButton( WIKI_CONFIG  ), TQ_SIGNAL(clicked( int )), TQ_SLOT(wikiConfig()) );

    connect( m_wikiBackPopup,    TQ_SIGNAL(activated( int )), TQ_SLOT(wikiBackPopupActivated( int )) );
    connect( m_wikiForwardPopup, TQ_SIGNAL(activated( int )), TQ_SLOT(wikiForwardPopupActivated( int )) );

    connect( CollectionDB::instance(), TQ_SIGNAL( scanStarted() ), TQ_SLOT( collectionScanStarted() ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( scanDone( bool ) ), TQ_SLOT( collectionScanDone( bool ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( databaseEngineChanged() ), TQ_SLOT( renderView() ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( coverFetched( const TQString&, const TQString& ) ),
             this, TQ_SLOT( coverFetched( const TQString&, const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( coverChanged( const TQString&, const TQString& ) ),
             this, TQ_SLOT( coverRemoved( const TQString&, const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( similarArtistsFetched( const TQString& ) ),
             this, TQ_SLOT( similarArtistsFetched( const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( tagsChanged( const MetaBundle& ) ),
             this, TQ_SLOT( tagsChanged( const MetaBundle& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( tagsChanged( const TQString&, const TQString& ) ),
             this, TQ_SLOT( tagsChanged( const TQString&, const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( ratingChanged( const TQString&, int ) ),
             this, TQ_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
    connect( StarManager::instance(), TQ_SIGNAL( ratingsColorsChanged( const TQString& ) ),
             this, TQ_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( scoreChanged( const TQString&, float ) ),
             this, TQ_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( labelsChanged( const TQString& ) ),
             this, TQ_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
    connect( CollectionDB::instance(), TQ_SIGNAL( imageFetched( const TQString& ) ),
             this, TQ_SLOT( imageFetched( const TQString& ) ) );

    connect( App::instance(), TQ_SIGNAL( useScores( bool ) ),
             this, TQ_SLOT( refreshCurrentTrackPage() ) );
    connect( App::instance(), TQ_SIGNAL( useRatings( bool ) ),
             this, TQ_SLOT( refreshCurrentTrackPage() ) );

    connect( MountPointManager::instance(), TQ_SIGNAL( mediumConnected( int ) ),
             this, TQ_SLOT( renderView() ) );
    connect( MountPointManager::instance(), TQ_SIGNAL( mediumRemoved( int ) ),
             this, TQ_SLOT( renderView() ) );

    showContext( KURL( "current://track" ) );

//     setMinimumHeight( AmarokConfig::coverPreviewSize() + (fontMetrics().height()+2)*5 + tabBar()->height() );
}


ContextBrowser::~ContextBrowser()
{
    DEBUG_BLOCK

    ThreadManager::instance()->abortAllJobsNamed( "CurrentTrackJob" );

    // Ensure the TDEHTMLPart dies before its TDEHTMLView dies,
    // because TDEHTMLPart's dtoring relies on its TDEHTMLView still being alive
    // (see bug 130494).
    delete m_currentTrackPage;
    delete m_lyricsPage;
    delete m_wikiPage;
    m_cuefile->clear();
}


//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////////////////////

void ContextBrowser::setFont( const TQFont &newFont )
{
    TQWidget::setFont( newFont );
    reloadStyleSheet();
}


//////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC SLOTS
//////////////////////////////////////////////////////////////////////////////////////////

void ContextBrowser::openURLRequest( const KURL &url )
{
    TQString artist, album, track;
    Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );

    // All http links should be loaded inside wikipedia tab, as that is the only tab that should contain them.
    // Streams should use stream:// protocol.
    if ( url.protocol() == "http" )
    {
        if ( url.hasHTMLRef() )
        {
            KURL base = url;
            base.setRef(TQString());
            // Wikipedia also has links to otherpages with Anchors, so we have to check if it's for the current one
            if ( m_wikiCurrentUrl == base.url() ) {
                m_wikiPage->gotoAnchor( url.htmlRef() );
                return;
            }
        }
        // new page
        m_dirtyWikiPage = true;
        m_wikiCurrentEntry = TQString();
        showWikipedia( url.url() );
    }
    else if ( url.protocol() == "show" )
    {
        if ( url.path().contains( "suggestLyric-" ) )
        {
            TQString _url = url.url().mid( url.url().find( TQString( "-" ) ) +1 );
            debug() << "Clicked lyrics URL: " << _url << endl;
            m_dirtyLyricsPage = true;
            showLyrics( _url );
        }
        else if ( url.path() == "collectionSetup" )
        {
            CollectionView::instance()->setupDirs();
        }
        else if ( url.path() == "scriptmanager" )
        {
            ScriptManager::instance()->show();
            ScriptManager::instance()->raise();
        }
        else if ( url.path() == "editLabels" )
        {
            showLabelsDialog();
        }
        // Konqueror sidebar needs these
        if (url.path() == "context") { m_dirtyCurrentTrackPage=true; showContext( KURL( "current://track" ) ); saveHtmlData(); }
        if (url.path() == "wiki") { m_dirtyWikiPage=true; showWikipedia(); saveHtmlData(); }
        if (url.path() == "lyrics") { m_dirtyLyricsPage=true; m_wikiJob=0; showLyrics(); saveHtmlData(); }
    }

    else if ( url.protocol() == "runscript" )
    {
        ScriptManager::instance()->runScript( url.path() );
    }

    // When left-clicking on cover image, open browser with amazon site
    else if ( url.protocol() == "fetchcover" )
    {
        TQString albumPath = CollectionDB::instance()->albumImage(artist, album, false, 0 );
        if ( albumPath == CollectionDB::instance()->notAvailCover( false, 0 ) )
        {
            CollectionDB::instance()->fetchCover( this, artist, album, false );
            return;
        }

        TQImage img( albumPath );
        const TQString amazonUrl = img.text( "amazon-url" );

        if ( amazonUrl.isEmpty() )
            KMessageBox::information( this, i18n( "<p>There is no product information available for this image.<p>Right-click on image for menu." ) );
        else
            Amarok::invokeBrowser( amazonUrl );
    }

    /* open konqueror with musicbrainz search result for artist-album */
    else if ( url.protocol() == "musicbrainz" )
    {
        const TQString url = "http://www.musicbrainz.org/taglookup.html?artist=%1&album=%2&track=%3";
        Amarok::invokeBrowser( url.arg( KURL::encode_string_no_slash( artist, 106 /*utf-8*/ ),
        KURL::encode_string_no_slash( album, 106 /*utf-8*/ ),
        KURL::encode_string_no_slash( track, 106 /*utf-8*/ ) ) );
    }

    else if ( url.protocol() == "externalurl" )
        Amarok::invokeBrowser( url.url().replace( TQRegExp( "^externalurl:" ), "http:") );

    else if ( url.protocol() == "lastfm" )
    {
        LastFm::WebService *lfm = LastFm::Controller::instance()->getService();
        if      ( url.path() == "skip" ) lfm->skip();
        else if ( url.path() == "love" ) lfm->love();
        else if ( url.path() == "ban"  ) lfm->ban();
    }
    else if ( url.protocol() == "togglebox" )
    {
        if      ( url.path() == "ra" ) m_relatedOpen     ^= true;
        else if ( url.path() == "ss" ) m_suggestionsOpen ^= true;
        else if ( url.path() == "ft" ) m_favoritesOpen  ^= true;
        else if ( url.path() == "sl" ) m_labelsOpen ^= true;
    }

    else if ( url.protocol() == "seek" )
    {
        EngineController::instance()->seek(url.path().toLong());
    }

    // browse albums of a related artist.  Don't do this if we are viewing Home tab
    else if ( url.protocol() == "artist" || url.protocol() == "current" || url.protocol() == "showlabel")
    {
        if( EngineController::engine()->loaded() ) // song must be active
            showContext( url );
    }

    else if( url.protocol() == "artistback" )
    {
        contextHistoryBack();
    }

    else if ( url.protocol() == "wikipedia" )
    {
        m_dirtyWikiPage = true;
        TQString entry = unescapeHTMLAttr( url.path() );
        showWikipediaEntry( entry );
    }

    else if( url.protocol() == "ggartist" )
    {
        const TQString url2 = TQString( "http://www.google.com/musicsearch?q=%1&res=artist" )
            .arg( KURL::encode_string_no_slash( unescapeHTMLAttr( url.path() ).replace( " ", "+" ), 106 /*utf-8*/ ) );
        Amarok::invokeBrowser( url2 );
    }

    else if( url.protocol() == "file" )
    {
        Playlist::instance()->insertMedia( url, Playlist::DefaultOptions );
    }

    else if( url.protocol() == "stream" )
    {
        Playlist::instance()->insertMedia( KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) ), Playlist::DefaultOptions );
    }

    else if( url.protocol() == "compilationdisc" || url.protocol() == "albumdisc" )
    {
        Playlist::instance()->insertMedia( expandURL( url ) , Playlist::DefaultOptions );
    }

    else
        HTMLView::openURLRequest( url );
}


void ContextBrowser::collectionScanStarted()
{
    m_emptyDB = CollectionDB::instance()->isEmpty();
    if( m_emptyDB && currentPage() == m_contextTab )
        showCurrentTrack();
}


void ContextBrowser::collectionScanDone( bool changed )
{
    if ( CollectionDB::instance()->isEmpty() )
    {
        m_emptyDB = true;
        if ( currentPage() == m_contextTab )
            showCurrentTrack();
    }
    else if ( m_emptyDB )
    {
        m_emptyDB = false;
        PlaylistWindow::self()->showBrowser("CollectionBrowser");
    }
    else if( changed && currentPage() == m_contextTab )
    {
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
    }
}


void ContextBrowser::renderView()
{
    m_dirtyCurrentTrackPage = true;
    m_dirtyLyricsPage = true;
    m_dirtyWikiPage = true;
    m_emptyDB = CollectionDB::instance()->isEmpty();

    showCurrentTrack();
}


void ContextBrowser::lyricsChanged( const TQString &url ) {
    if ( url == EngineController::instance()->bundle().url().path() ) {
        m_dirtyLyricsPage = true;
        if ( currentPage() == m_lyricsTab )
            showLyrics();
    }
}

void ContextBrowser::lyricsScriptChanged() {
    m_dirtyLyricsPage = true;
    if ( currentPage() == m_lyricsTab )
        showLyrics();
}

//////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED
//////////////////////////////////////////////////////////////////////////////////////////

void ContextBrowser::engineNewMetaData( const MetaBundle& bundle, bool trackChanged )
{
    bool newMetaData = false;
    m_dirtyCurrentTrackPage = true;
    m_dirtyLyricsPage = true;
    m_wikiJob = 0; //New metadata, so let's forget previous wiki-fetching jobs

    if ( MetaBundle( m_currentURL ).artist() != bundle.artist() )
        m_dirtyWikiPage = true;
    // Prepend stream metadata history item to list
    if ( !m_metadataHistory.first().contains( bundle.prettyTitle() ) )
    {
        newMetaData = true;
        const TQString timeString = TDEGlobal::locale()->formatTime( TQTime::currentTime() ).replace(" ", "&nbsp;"); // don't break over lines
        m_metadataHistory.prepend( TQString( "<td valign='top'>" + timeString + "&nbsp;</td><td align='left'>" + escapeHTML( bundle.prettyTitle() ) + "</td>" ) );
    }

    if ( currentPage() == m_contextTab && ( bundle.url() != m_currentURL || newMetaData || !trackChanged ) )
        showCurrentTrack();
    else if ( currentPage() == m_lyricsTab )
    {
        EngineController::engine()->isStream() ?
                lyricsRefresh() : // can't call showLyrics() because the url hasn't changed
                showLyrics()    ;
    }
    else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() )
        showCurrentTrack();

    if (trackChanged)
    {
        m_cuefile->clear();

        if (bundle.url().isLocalFile())
        {

            /** The cue file that is provided with the media might have different name than the
             * media file itself, hence simply cutting the media extension and adding ".cue"
             * is not always enough to find the matching cue file. In such cases we have
             * to search for all the cue files in the directory and have a look inside them for
             * the matching FILE="" stanza. However the FILE="" stanza does not always
             * point at the corresponding media file (e.g. it is quite often set to the misleading
             * FILE="audio.wav" WAV). Therfore we also have to check blindly if there is a cue
             * file having the same name as the media file played, as described above.
             */

            // look for the cue file that matches the media file played first
            TQString path    = bundle.url().path();
            TQString cueFile = path.left( path.findRev('.') ) + ".cue";

            m_cuefile->setCueFileName( cueFile );

            if( m_cuefile->load( bundle.length() ) )
                debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly, found and loaded. " << endl;

            // if unlucky, let's have a look inside cue files, if any
            else
            {
                debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly and missed, searching for other cue files." << endl;

                bool foundCueFile = false;
                TQDir dir ( bundle.directory() );
                dir.setFilter( TQDir::Files ) ;
                dir.setNameFilter( "*.cue *.CUE" ) ;

                TQStringList cueFilesList = dir.entryList();

                if ( !cueFilesList.empty() )
                    for ( TQStringList::Iterator it = cueFilesList.begin(); it != cueFilesList.end() && !foundCueFile; ++it )
                    {
                        TQFile file ( dir.filePath(*it) );
                        if( file.open( IO_ReadOnly ) )
                        {
                            debug() << "[CUEFILE]: " << *it << " - Opened, looking for the matching FILE stanza." << endl;
                            TQTextStream stream( &file );
                            TQString line;

                            while ( !stream.atEnd() && !foundCueFile)
                            {
                                line = stream.readLine().simplifyWhiteSpace();

                                if( line.startsWith( "file", false ) )
                                {
                                    line = line.mid( 5 ).remove( '"' );

                                    if ( line.contains( bundle.filename(), false ) )
                                    {
                                        cueFile = dir.filePath(*it);
                                        foundCueFile = true;
                                        m_cuefile->setCueFileName( cueFile );
                                        if( m_cuefile->load( bundle.length() ) )
                                            debug() << "[CUEFILE]: " << cueFile << " - Looked inside cue files, found and loaded proper one" << endl;
                                    }
                                }
                            }

                            file.close();
                        }
                    }

                if ( !foundCueFile )
                    debug() << "[CUEFILE]: - Didn't find any matching cue file." << endl;
            }
        }
    }
}


void ContextBrowser::engineStateChanged( Engine::State state, Engine::State oldState )
{
    DEBUG_BLOCK

    if( state != Engine::Paused /*pause*/ && oldState != Engine::Paused /*resume*/
        || state == Engine::Empty )
    {
        // Pause shouldn't clear everything (but stop should, even when paused)
        m_dirtyCurrentTrackPage = true;
        m_dirtyLyricsPage = true;
        m_wikiJob = 0; //let's forget previous wiki-fetching jobs
    }

    switch( state )
    {
        case Engine::Empty:
            m_metadataHistory.clear();
            if ( currentPage() == m_contextTab || currentPage() == m_lyricsTab )
            {
                showCurrentTrack();
            }
            blockSignals( true );
            setTabEnabled( m_lyricsTab, false );
            if ( currentPage() != m_wikiTab ) {
                setTabEnabled( m_wikiTab, false );
                m_dirtyWikiPage = true;
            }
            else // current tab is wikitab, disable some buttons.
            {
                m_wikiToolBar->setItemEnabled( WIKI_ARTIST, false );
                m_wikiToolBar->setItemEnabled( WIKI_ALBUM, false );
                m_wikiToolBar->setItemEnabled( WIKI_TITLE, false );
            }
            blockSignals( false );
            break;

        case Engine::Playing:
            if ( oldState != Engine::Paused )
                m_metadataHistory.clear();
            blockSignals( true );
            setTabEnabled( m_lyricsTab, true );
            setTabEnabled( m_wikiTab, true );
            m_wikiToolBar->setItemEnabled( WIKI_ARTIST, true );
            m_wikiToolBar->setItemEnabled( WIKI_ALBUM, true );
            m_wikiToolBar->setItemEnabled( WIKI_TITLE, true );
            blockSignals( false );
            break;

        default:
            ;
    }
}

void ContextBrowser::saveHtmlData()
{
    TQFile exportedDocument( Amarok::saveLocation() + "contextbrowser.html" );
    if ( !exportedDocument.open( IO_WriteOnly ) )
        warning() << "Failed to open file " << exportedDocument.name()
        << " write-only" << endl;
    else {
        TQTextStream stream( &exportedDocument );
        stream.setEncoding( TQTextStream::UnicodeUTF8 );
        stream << m_HTMLSource // the pure html data..
            .replace( "<html>",
                      TQString( "<html><head><style type=\"text/css\">"
                               "%1</style></head>" )
                          .arg( HTMLView::loadStyleSheet() ) ); // and the
                                                                // stylesheet
                                                                // code
        exportedDocument.close();
    }
}


void ContextBrowser::paletteChange( const TQPalette& /* pal */ )
{
//     KTabWidget::paletteChange( pal );
    HTMLView::paletteChange();
    reloadStyleSheet();
}

void ContextBrowser::reloadStyleSheet()
{
    m_currentTrackPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
    m_lyricsPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
    m_wikiPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
}

//////////////////////////////////////////////////////////////////////////////////////////
// PROTECTED SLOTS
//////////////////////////////////////////////////////////////////////////////////////////

//parts of this function from ktabwidget.cpp, copyright (C) 2003 Zack Rusin and Stephan Binner
//setCurrentTab() isn't virtual so we have to override this instead =(
void ContextBrowser::wheelDelta( int delta )
{
    if ( count() < 2 || delta == 0 )
        return;

    int index = currentPageIndex(), start = index;
    do
    {
        if( delta < 0 )
            index = (index + 1) % count();
        else
        {
            index = index - 1;
            if( index < 0 )
                index = count() - 1;
        }
        if( index == start ) // full circle, none enabled
            return;
    } while( !isTabEnabled( page( index ) ) );
    setCurrentPage( index );
}


//////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE SLOTS
//////////////////////////////////////////////////////////////////////////////////////////

void ContextBrowser::tabChanged( TQWidget *page )
{
DEBUG_FUNC_INFO
    setFocusProxy( page ); //so focus is given to a sensible widget when the tab is opened

    if ( page == m_contextTab )
        showCurrentTrack();
    else if ( page == m_lyricsTab )
        showLyrics();
    else if ( page == m_wikiTab )
        showWikipedia();
}

void ContextBrowser::slotContextMenu( const TQString& urlString, const TQPoint& point )
{
    enum { APPEND, ASNEXT, MAKE, MEDIA_DEVICE, INFO, TITLE, RELATED, SUGGEST, FAVES, FRESHPODCASTS, NEWALBUMS, FAVALBUMS, LABELS };
    debug() << "url string: " << urlString << endl;

    if( urlString.startsWith( "musicbrainz"     ) ||
        urlString.startsWith( "externalurl"     ) ||
        urlString.startsWith( "show:suggest"    ) ||
        urlString.startsWith( "http"            ) ||
        urlString.startsWith( "wikipedia"       ) ||
        urlString.startsWith( "seek"            ) ||
        urlString.startsWith( "ggartist"        ) ||
        urlString.startsWith( "artistback"      ) ||
        urlString.startsWith( "current"         ) ||
        urlString.startsWith( "lastfm"          ) ||
        urlString.startsWith( "showlabel"       ) ||
        urlString.startsWith( "show:editLabels" ) ||
        currentPage() != m_contextTab  )
        return;

    KURL url( urlString );

    TDEPopupMenu menu;
    KURL::List urls( url );
    TQString artist, album, track; // track unused here
    Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );

    if( urlString.isEmpty() )
    {
        debug() << "url string empty. loaded?" << EngineController::engine()->loaded() << endl;
        if( EngineController::engine()->loaded() )
        {
            menu.setCheckable( true );
            menu.insertItem( i18n("Show Labels"), LABELS );
            menu.insertItem( i18n("Show Related Artists"), RELATED );
            menu.insertItem( i18n("Show Suggested Songs"), SUGGEST );
            menu.insertItem( i18n("Show Favorite Tracks"), FAVES );

            menu.setItemChecked( RELATED, m_showRelated );
            menu.setItemChecked( SUGGEST, m_showSuggested );
            menu.setItemChecked( FAVES,   m_showFaves );
            menu.setItemChecked( LABELS, m_showLabels );
        } else {
            // the home info page
            menu.setCheckable( true );
            menu.insertItem( i18n("Show Fresh Podcasts"), FRESHPODCASTS );
            menu.insertItem( i18n("Show Newest Albums"), NEWALBUMS );
            menu.insertItem( i18n("Show Favorite Albums"), FAVALBUMS );

            menu.setItemChecked( FRESHPODCASTS, m_showFreshPodcasts );
            menu.setItemChecked( NEWALBUMS, m_showNewestAlbums );
            menu.setItemChecked( FAVALBUMS, m_showFavoriteAlbums );
        }
    }
    else if( url.protocol() == "fetchcover" )
    {
        Amarok::coverContextMenu( this, point, artist, album );
        return;
    }
    else if( url.protocol() == "stream" )
    {
        url = KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) );
        urls = KURL::List( url );
        menu.insertTitle( i18n("Podcast"), TITLE );
        menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE );
        menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
        menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Podcast" ), ASNEXT );
        //menu.insertSeparator();
        //menu.insertItem( SmallIconSet( "go-down" ), i18n( "&Download" ), DOWNLOAD );
    }
    else if( url.protocol() == "file" || url.protocol() == "artist" || url.protocol() == "album" || url.protocol() == "compilation" || url.protocol() == "albumdisc" || url.protocol() == "compilationdisc")
    {
        //TODO it would be handy and more usable to have this menu under the cover one too

        menu.insertTitle( i18n("Track"), TITLE );
        menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE );
        menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
        menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), ASNEXT );
        if( MediaBrowser::isAvailable() )
            menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MEDIA_DEVICE );

        menu.insertSeparator();
        menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), INFO );

        if ( url.protocol() == "artist" )
        {
            urls = expandURL( url );

            menu.changeTitle( TITLE, i18n("Artist") );
            menu.changeItem( INFO,   i18n("Edit Artist &Information..." ) );
            menu.changeItem( ASNEXT, i18n("&Queue Artist's Songs") );
        }
        if ( url.protocol() == "album" )
        {
            urls = expandURL( url );

            menu.changeTitle( TITLE, i18n("Album") );
            menu.changeItem( INFO,   i18n("Edit Album &Information..." ) );
            menu.changeItem( ASNEXT, i18n("&Queue Album") );
        }
        if ( url.protocol() == "albumdisc" )
        {
            urls = expandURL( url );

            menu.changeTitle( TITLE, i18n("Album Disc") );
            menu.changeItem( INFO,   i18n("Edit Album Disc &Information..." ) );
            menu.changeItem( ASNEXT, i18n("&Queue Album Disc") );
        }
        if ( url.protocol() == "compilation" )
        {
            urls = expandURL( url );

            menu.changeTitle( TITLE, i18n("Compilation") );
            menu.changeItem( INFO,   i18n("Edit Album &Information..." ) );
            menu.changeItem( ASNEXT, i18n("&Queue Album") );
        }
        if ( url.protocol() == "compilationdisc" )
        {
            urls = expandURL( url );

            menu.changeTitle( TITLE, i18n("Compilation Disc") );
            menu.changeItem( INFO,   i18n("Edit Compilation Disc &Information..." ) );
            menu.changeItem( ASNEXT, i18n("&Queue Compilation Disc") );
        }

        if( urls.count() == 0 )
        {
            menu.setItemEnabled( MAKE, false );
            menu.setItemEnabled( APPEND, false );
            menu.setItemEnabled( ASNEXT, false );
            menu.setItemEnabled( MEDIA_DEVICE, false );
            menu.setItemEnabled( INFO, false );
        }
    }

    //Not all these are used in the menu, it depends on the context
    switch( menu.exec( point ) )
    {

    case RELATED:
        m_showRelated = !menu.isItemChecked( RELATED );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowRelated", m_showRelated );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

    case SUGGEST:
        m_showSuggested = !menu.isItemChecked( SUGGEST );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowSuggested", m_showSuggested );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

    case FAVES:
        m_showFaves = !menu.isItemChecked( FAVES );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowFaves", m_showFaves );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

   case LABELS:
        m_showLabels = !menu.isItemChecked( LABELS );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowLabels", m_showLabels );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

   case FRESHPODCASTS:
        m_showFreshPodcasts = !menu.isItemChecked( FRESHPODCASTS );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowFreshPodcasts", m_showFreshPodcasts );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

   case NEWALBUMS:
        m_showNewestAlbums = !menu.isItemChecked( NEWALBUMS );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowNewestAlbums", m_showNewestAlbums );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

   case FAVALBUMS:
        m_showFavoriteAlbums = !menu.isItemChecked( FAVALBUMS );
        Amarok::config( "ContextBrowser" )->writeEntry( "ShowFavoriteAlbums", m_showFavoriteAlbums );
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
        break;

    case ASNEXT:
        Playlist::instance()->insertMedia( urls, Playlist::Queue );
        break;

    case INFO:
    {
        if ( urls.count() > 1 )
        {
            TagDialog* dialog = new TagDialog( urls, instance() );
            dialog->show();
        }
        else if ( !urls.isEmpty() )
        {
            TagDialog* dialog = new TagDialog( urls.first(), instance() );
            dialog->show();
        }
        break;
    }

    case MAKE:
        Playlist::instance()->clear();
        //FALL_THROUGH

    case APPEND:
        Playlist::instance()->insertMedia( urls, Playlist::Append );
        break;

    case MEDIA_DEVICE:
        MediaBrowser::queue()->addURLs( urls );
        break;

    }
}



//////////////////////////////////////////////////////////////////////////////////////////
// Current-Tab
//////////////////////////////////////////////////////////////////////////////////////////

/** This is the slowest part of track change, so we thread it */
class CurrentTrackJob : public ThreadManager::DependentJob
{
public:
    CurrentTrackJob( ContextBrowser *parent )
        : ThreadManager::DependentJob( parent, "CurrentTrackJob" )
        , b( parent )
        , m_currentTrack( TQDeepCopy<MetaBundle>( EngineController::instance()->bundle() ) )
        , m_isStream( EngineController::engine()->isStream() )
    {
        for( TQStringList::iterator it = b->m_metadataHistory.begin();
                it != b->m_metadataHistory.end();
                ++it )
        {
            m_metadataHistory += TQDeepCopy<TQString>( *it );
        }


        m_amarokIconPath = TQDeepCopy<TQString>(TDEGlobal::iconLoader()->iconPath( "amarok",
                    -TDEIcon::SizeEnormous ) );
        m_musicBrainIconPath = TQDeepCopy<TQString>(locate( "data", "amarok/images/musicbrainz.png" )
                );
        m_lastfmIcon = "file://" + locate( "data","amarok/images/lastfm.png" );
    }

private:
    virtual bool doJob();
    void addMetaHistory();
    void showLastFm( const MetaBundle &currentTrack );
    void showStream( const MetaBundle &currentTrack );
    void showPodcast( const MetaBundle &currentTrack );
    void showBrowseArtistHeader( const TQString &artist );
    void showBrowseLabelHeader( const TQString &label );
    void showCurrentArtistHeader( const MetaBundle &currentTrack );
    void showRelatedArtists( const TQString &artist, const TQStringList &relArtists );
    void showSuggestedSongs( const TQStringList &relArtists );
    void showSongsWithLabel( const TQString &label );
    void showArtistsFaves( const TQString &artistName, uint artist_id );
    void showArtistsAlbums( const TQString &artist, uint artist_id, uint album_id );
    void showArtistsCompilations( const TQString &artist, uint artist_id, uint album_id );
    void showHome();
    void showUserLabels( const MetaBundle &currentTrack );
    TQString fetchLastfmImage( const TQString& url );
    TQStringList showHomeByAlbums();
    void constructHTMLAlbums( const TQStringList &albums, TQString &htmlCode, const TQString &idPrefix );
    static TQString statsHTML( int score, int rating, bool statsbox = true ); // meh.

    virtual void completeJob()
    {
        // are we still showing the currentTrack page?
//         if( b->currentPage() != b->m_contextTab )
//             return;

        b->m_shownAlbums.clear();
        for( TQStringList::iterator it = m_shownAlbums.begin();
                it != m_shownAlbums.end();
                ++it )
            b->m_shownAlbums.append( TQDeepCopy<TQString>( *it ) );
        b->m_HTMLSource = TQDeepCopy<TQString>( m_HTMLSource );
        b->m_currentTrackPage->set( m_HTMLSource );
        b->m_dirtyCurrentTrackPage = false;
        b->saveHtmlData(); // Send html code to file
    }
    TQString m_HTMLSource;
    TQString m_amarokIconPath;
    TQString m_musicBrainIconPath;
    TQString m_lastfmIcon;

    ContextBrowser *b;
    MetaBundle m_currentTrack;
    bool m_isStream;
    TQStringList m_shownAlbums;
    TQStringList m_metadataHistory;
};

void
ContextBrowser::showContext( const KURL &url, bool fromHistory )
{
    if ( currentPage() != m_contextTab )
    {
        blockSignals( true );
        showPage( m_contextTab );
        blockSignals( false );
    }

    m_dirtyCurrentTrackPage = true;
    m_contextURL = url.url();

    if( url.protocol() == "current" )
    {
        m_browseArtists = false;
        m_browseLabels = false;
        m_label = TQString();
        m_artist = TQString();
        m_contextBackHistory.clear();
        m_contextBackHistory.push_back( "current://track" );
    }
    else if( url.protocol() == "artist" )
    {
        m_browseArtists = true;
        m_browseLabels = false;
        m_label = TQString();
        m_artist = unescapeHTMLAttr( url.path() );
    }
    else if( url.protocol() == "showlabel" )
    {
        m_browseLabels = true;
        m_browseArtists = false;
        m_artist = TQString();
        m_label = unescapeHTMLAttr( url.path() );
    }

    // Append new URL to history
    if ( !fromHistory ) {
        m_contextBackHistory += m_contextURL.url();
    }
    // Limit number of items in history
    if ( m_contextBackHistory.count() > CONTEXT_MAX_HISTORY )
        m_contextBackHistory.pop_front();

    showCurrentTrack();
}

void
ContextBrowser::contextHistoryBack() //SLOT
{
    if( m_contextBackHistory.size() > 0 )
    {
        m_contextBackHistory.pop_back();

        m_dirtyCurrentTrackPage = true;

        showContext( KURL( m_contextBackHistory.last() ), true );
    }
}


void ContextBrowser::showCurrentTrack() //SLOT
{
#if 0
    if( BrowserBar::instance()->currentBrowser() != this )
    {
        debug() << "current browser is not context, aborting showCurrentTrack()" << endl;
        m_dirtyCurrentTrackPage = true;
        m_currentTrackPage->set( TQString( "<html><body><div class='box-body'>%1</div></body></html>" )
                                 .arg( i18n( "Updating..." ) ) );
        return;
    }
#endif
    if ( currentPage() != m_contextTab ) {
        blockSignals( true );
        showPage( m_contextTab );
        blockSignals( false );
    }

    // TODO: Show CurrentTrack or Lyric tab if they were selected
    // If it's not a streaming, check for a collection
    if ( !EngineController::engine()->isStream() )
    {
        if ( m_emptyDB && CollectionDB::instance()->isValid() && !MountPointManager::instance()->collectionFolders().isEmpty() )
        {
            showScanning();
            return;
        }
        else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() )
        {
            showIntroduction();
            return;
        }
    }
    if( !m_dirtyCurrentTrackPage )
        return;
    m_currentURL = EngineController::instance()->bundle().url();
    m_currentTrackPage->write( TQString() );
    ThreadManager::instance()->onlyOneJob( new CurrentTrackJob( this ) );
}



//////////////////////////////////////////////////////////////////////////////////////////
// Shows the statistics summary when no track is playing
//////////////////////////////////////////////////////////////////////////////////////////

void CurrentTrackJob::showHome()
{
    QueryBuilder qb;

    qb.clear(); //Song count
    qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valURL );
    qb.setOptions( QueryBuilder::optRemoveDuplicates );
    TQStringList a = qb.run();
    TQString songCount = a[0];

    qb.clear(); //Artist count
    //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabArtist, QueryBuilder::valID );
    //qb.setOptions( QueryBuilder::optRemoveDuplicates );
    //a = qb.run();
    //TQString artistCount = a[0];
    qb.setOptions( QueryBuilder::optRemoveDuplicates );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valArtistID );
    //I can't get the correct value w/o suing a subquery, and querybuilder doesn't support those
    TQString artistCount = TQString::number( qb.run().count() );

    qb.clear(); //Album count
    //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
    //qb.setOptions( QueryBuilder::optRemoveDuplicates );
    //a = qb.run();
    //TQString albumCount = a[0];
    qb.setOptions( QueryBuilder::optRemoveDuplicates );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valAlbumID );
    TQString albumCount = TQString::number( qb.run().count() );

    qb.clear(); //Genre count
    //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabGenre, QueryBuilder::valID );
    //qb.setOptions( QueryBuilder::optRemoveDuplicates );
    //a = qb.run();
    //TQString genreCount = a[0];
    qb.setOptions( QueryBuilder::optRemoveDuplicates );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valGenreID );
    TQString genreCount = TQString::number( qb.run().count() );

    qb.clear(); //Total Playtime
    qb.addReturnFunctionValue( QueryBuilder::funcSum, QueryBuilder::tabSong, QueryBuilder::valLength );
    a = qb.run();
    TQString playTime = MetaBundle::fuzzyTime( a[0].toInt() );

    m_HTMLSource.append(
            QStringx(
                "<div id='introduction_box' class='box'>\n"
                "<div id='introduction_box-header-title' class='box-header'>\n"
                "<span id='introduction_box-header-title' class='box-header-title'>\n"
                + i18n( "No Track Playing" ) +
                "</span>\n"
                "</div>\n"
                "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
                "<tr>\n"
                "<td id='current_box-largecover-td'>\n"
                "<a href='%1'><img id='current_box-largecover-image' src='%2' title='Amarok'></a>\n"
                "</td>\n"
                "<td id='current_box-information-td' align='right'>\n"
                "<span>%3</span><br />\n"
                "<span>%4</span><br />\n"
                "<span>%5</span><br />\n"
                "<span>%6</span><br />\n"
                "<span>%7</span><br />\n"
                "</td>\n"
                "</tr>\n"
                "</table>\n"
                "</div>\n" )
            .args( TQStringList()
                    << escapeHTMLAttr( "externalurl://amarok.kde.org" )
                    << escapeHTMLAttr( m_amarokIconPath )
                    << i18n( "1 Track",  "%n Tracks",  songCount.toInt() )
                    << i18n( "1 Artist", "%n Artists", artistCount.toInt() )
                    << i18n( "1 Album",  "%n Albums",  albumCount.toInt() )
                    << i18n( "1 Genre",  "%n Genres",  genreCount.toInt() )
                    << i18n( "%1 Play-time" ).arg ( playTime ) ) );

    m_shownAlbums = showHomeByAlbums();

    m_HTMLSource.append(
            "</div></body></html>\n");
}


void
CurrentTrackJob::constructHTMLAlbums( const TQStringList &reqResult, TQString &htmlCode, const TQString &stID )
{
    // This function create the html code used to display a list of albums. Each album
    // is a 'toggleable' block.
    // Parameter stID is used to differentiate same albums in different album list. So if this function
    // is called multiple time in the same HTML code, stID must be different.
    for ( uint i = 0; i < reqResult.count(); i += 4 )
    {
        QueryBuilder qb;
        qb.clear();
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
        qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
        qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
        qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
        qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, reqResult[i+1] );
        qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, reqResult[i+3] );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle );
        qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
        TQStringList albumValues = qb.run();

        TQString albumYear;
        if ( !albumValues.isEmpty() )
        {
            albumYear = albumValues[ 3 ];
            for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues())
                if ( albumValues[j + 3] != albumYear || albumYear == "0" )
                {
                    albumYear = TQString();
                    break;
                }
        }

        uint i_albumLength = 0;
        for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
            i_albumLength += TQString(albumValues[j + 4]).toInt();

        TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );

        htmlCode.append( QStringx (
                    "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td>\n"
                    "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
                    "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
                    "<tr>\n")
                .args( TQStringList()
                    << stID + reqResult[i+1] ));

        TQString albumName = escapeHTML( reqResult[ i ].isEmpty() ? i18n( "Unknown album" ) : reqResult[ i ] );

        TQString artistName = albumValues[5].isEmpty() ? i18n( "Unknown artist" ) : albumValues[5];

        TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( albumValues[5], reqResult[ i ], true, 50 ) );
        TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );

        // Album image
        htmlCode.append( QStringx (
                    "<td width='1'>\n"
                    "<a href='fetchcover:%1 @@@ %2'>\n"
                    "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4'/>\n"
                    "</a>\n"
                    "</td>\n"
                    "<td valign='middle' align='left'>\n"
                    "<a href='artist:%5'>\n"
                    "<span class='album-title'>%6</span>\n"
                    "</a>\n"
                    "<span class='song-separator'> - </span>\n"
                    "<a href='album:%7 @@@ %8'>\n"
                    "<span class='album-title'>%9</span>\n"
                    "</a>\n" )
                .args( TQStringList()
                    << escapeHTMLAttr( albumValues[5] ) // artist name
                    << escapeHTMLAttr( reqResult[ i ].isEmpty() ? i18n( "Unknown" ) : reqResult[ i ] ) // album.name
                    << albumImageTitleAttr
                    << escapeHTMLAttr( albumImage )
                    << escapeHTMLAttr( artistName )
                    << escapeHTML( artistName )
                    << albumValues[6]
                    << reqResult[ i + 1 ] //album.id
                    << albumName ) );

        // Tracks number, year and length
        htmlCode.append( QStringx (
                    "<span class='album-info'>%1</span> "
                    "<br />\n"
                    "<span class='album-year'>%2</span>\n"
                    "<span class='album-length'>%3</span>\n"
                    "</td>\n")
                .args( TQStringList()
                    << i18n( "Single", "%n Tracks",  albumValues.count() / qb.countReturnValues() )
                    << albumYear
                    << albumLength) );

        // Begining of the 'toggleable div' that contains the songs
        htmlCode.append( QStringx (
                    "</tr>\n"
                    "</table>\n"
                    "</div>\n"
                    "<div class='album-body' style='display:%1;' id='IDA%2'>\n" )
                .args( TQStringList()
                    << "none" /* shows it if it's the current track album */
                    << stID + reqResult[ i + 1 ] ) );

        TQString discNumber;

        if ( !albumValues.isEmpty() )
        {
            for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
            {
                TQString newDiscNumber = albumValues[ j + 7 ].stripWhiteSpace();
                if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
                {
                    discNumber = newDiscNumber;
                    htmlCode.append( QStringx (
                                "<div class='disc-separator'>\n"
                                "<a href=\"albumdisc: %1 @@@ %2 @@@ %3\">\n"
                                "%4"
                                "</a>\n"
                                "</div>\n" )
                            .args( TQStringList()
                                << albumValues[6]
                                << reqResult[ i + 1 ] //album.id
                                << escapeHTMLAttr( discNumber )
                                << i18n( "Disc %1" ).arg( discNumber ) ) );
                }
                TQString track = albumValues[j + 2].stripWhiteSpace();
                if( track.length() > 0 )
                {
                    if( track.length() == 1 )
                        track.prepend( "0" );

                    track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
                }

                TQString length;
                if( albumValues[j + 4] != "0" )
                    length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";

                htmlCode.append(
                        "<div class='album-song'>\n"
                        "<a href=\"file:" + escapeHTMLAttr( albumValues[j + 1] ) + "\">\n"
                        + track +
                        "<span class='album-song-title'>\n" + escapeHTML( albumValues[j] ) + "</span>&nbsp;"
                        + length +
                        "</a>\n"
                        "</div>\n" );
            }
        }

        htmlCode.append(
                "</div>\n"
                "</td>\n"
                "</tr>\n" );
    }
}


// return list of albums shown
TQStringList
CurrentTrackJob::showHomeByAlbums()
{
    QueryBuilder qb;

    m_HTMLSource.append( "<table width='100%' cellpadding='0' cellspacing='0' border='0'><tr>\n" );

    // <Fresh Podcasts Information>
    if( ContextBrowser::instance()->m_showFreshPodcasts )
    {
        qb.clear();
        qb.addReturnValue( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valParent );
        qb.addFilter( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valIsNew, CollectionDB::instance()->boolT(), QueryBuilder::modeNormal, true );
        qb.sortBy( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valID, true );
        qb.setOptions( QueryBuilder::optRemoveDuplicates );
        qb.setLimit( 0, 5 );
        TQStringList channels = qb.run();

        if( channels.count() > 0 )
        {
            m_HTMLSource.append(
                    "<td valign='top'><div id='least_box' class='box'>\n"
                    "<div id='least_box-header' class='box-header'>\n"
                    "<span id='least_box-header-title' class='box-header-title'>\n"
                    + i18n( "Fresh Podcast Episodes" ) +
                    "</span>\n"
                    "</div>\n"
                    "<table id='least_box-body' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n" );

            uint i = 0;
            for( TQStringList::iterator it = channels.begin();
                    it != channels.end();
                    it++ )
            {
                PodcastChannelBundle pcb;
                if( !CollectionDB::instance()->getPodcastChannelBundle( *it, &pcb ) )
                    continue;

                TQValueList<PodcastEpisodeBundle> episodes = CollectionDB::instance()->getPodcastEpisodes( *it, true /* only new */, 1 );
                if( !episodes.isEmpty() )
                {
                    PodcastEpisodeBundle &ep = *episodes.begin();

                    TQString date;
                    ep.dateTime().isNull() ?
                            date = ep.date() :
                            date = ep.dateTime().toString();

                    TQString image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true, 50 );
                    TQString imageAttr = escapeHTMLAttr( i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() ) );

                    m_HTMLSource.append( QStringx (
                                "<tr class='" + TQString( (i % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
                                "<td>\n"
                                "<div class='album-header' onClick=\"toggleBlock('IDP%1')\">\n"
                                "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
                                "<tr>\n"
                                "<td width='1'>\n"
                                "<a href='%2'>\n"
                                "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4' />\n"
                                "</a>\n"
                                "</td>\n"
                                "<td valign='middle' align='left'>\n"
                                "<span class='album-info'>%5</span> \n"
                                "<a href='%6'><span class='album-title'>%7</span></a>\n"
                                "<br />\n"
                                "<span class='album-year'>%8</span>\n"
                                "</td>\n"
                                "</tr>\n"
                                "</table>\n"
                                "</div>\n"
                                "<div class='album-body' style='display:%9;' id='IDP%10'>\n" )
                                .args( TQStringList()
                                        << TQString::number( i )
                                        << pcb.link().url().replace( TQRegExp( "^http:" ), "externalurl:" )
                                        << escapeHTMLAttr( imageAttr )
                                        << escapeHTMLAttr( image )
                                        << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : TQString( "" ) )
                                        << ( ep.localUrl().isValid()
                                            ? ep.localUrl().url()
                                            : ep.url().url().replace( TQRegExp( "^http:" ), "stream:" ) )
                                        << escapeHTML( pcb.title() + ": " + ep.title() )
                                        << escapeHTML( date )
                                        << "none"
                                        << TQString::number( i )
                                     )
                                );

                    m_HTMLSource.append( QStringx ( "<p>%1</p>\n" ).arg( ep.description() ) );

                    m_HTMLSource.append(
                            "</div>\n"
                            "</td>\n"
                            "</tr>\n" );
                    i++;
                }
            }
            m_HTMLSource.append(
                    "</table>\n"
                    "</div>\n"
                    "</td>\n"
                    "</tr>\n"
                    "<tr>\n" );
        }
    }
    // </Fresh Podcasts Information>

    TQStringList albums;
    // <Newest Albums Information>
    if( ContextBrowser::instance()->m_showNewestAlbums )
    {
        qb.clear();
        qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
        qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
        qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate );
        qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
        qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate, true );
        qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
        qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
        qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID );
        qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
        qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
        qb.setLimit( 0, 5 );
        TQStringList recentAlbums = qb.run();

        foreach( recentAlbums )
        {
            albums += *it;
            it++;
            it++;
            it++;
        }
        // toggle html here so we get correct albums
        m_HTMLSource.append(
                "<td valign='top'>\n"
                "<div id='least_box' class='box'>\n"
                "<div id='least_box-header' class='box-header'>\n"
                "<span id='least_box-header-title' class='box-header-title'>\n"
                + i18n( "Your Newest Albums" ) +
                "</span>\n"
                "</div>\n"
                "<table id='least_box-body' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n" );
        constructHTMLAlbums( recentAlbums, m_HTMLSource, "1" );

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n"
                "</td>\n"
                "</tr>\n"
                "<tr>\n" );
    }
    // </Recent Tracks Information>

    // <Favorite Albums Information>
    if( ContextBrowser::instance()->m_showFavoriteAlbums )
    {
        qb.clear();
        qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
        qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
        qb.sortByFavoriteAvg(); // this function adds return values!
        qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
        // only albums with more than 3 tracks
        qb.having( QueryBuilder::tabAlbum, QueryBuilder::valID, QueryBuilder::funcCount, QueryBuilder::modeGreater, "3" );
        qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
        qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
        qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID );
        qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
        qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
        qb.setLimit( 0, 5 );
        TQStringList faveAlbums = qb.run();
        TQStringList faveResults;

        bool ratings = AmarokConfig::useRatings();
        bool scores = AmarokConfig::useScores();

        foreach( faveAlbums ) {
            albums += *it;
            faveResults += *(it++);
            faveResults += *(it++);
            faveResults += *(it++);
            // sortByFavoriteAvg add some return values, and constructHTMLAlbums expects
            // a specific set of return values, so we might need to skip some values
            if ( ratings )
                it++;
            if ( scores )
                it++;
            faveResults += *(it);
        }

        m_HTMLSource.append(
                "<td valign='top'>\n"
                "<div id='albums_box' class='box'>\n"
                "<div id='albums_box-header' class='box-header'>\n"
                "<span id='albums_box-header-title' class='box-header-title'>\n"
                + i18n( "Favorite Albums" ) +
                "</span>\n"
                "</div>\n"
                "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        if ( faveAlbums.count() == 0 )
        {
            m_HTMLSource.append(
                    "<div id='favorites_box-body' class='box-body'><p>\n" +
                    (QueryBuilder::valForFavoriteSorting() == QueryBuilder::valRating
                        ? i18n( "A list of your favorite albums will appear here, once you have rated a few of your songs." )
                        : i18n( "A list of your favorite albums will appear here, once you have played a few of your songs." ) ) +
                    "</p></div>\n" );
        }
        else
        {
            constructHTMLAlbums( faveResults, m_HTMLSource, "2" );
        }

        m_HTMLSource.append(
                "</table></div></td>\n" );
    }
    // </Favorite Tracks Information>

    m_HTMLSource.append( "</tr></table>\n" );

    return albums;
}


void CurrentTrackJob::showLastFm( const MetaBundle &currentTrack )
{
    if( !LastFm::Controller::instance()->isPlaying() ) return;

    const LastFm::Bundle *lastFmInfo = currentTrack.lastFmBundle();
    if ( !lastFmInfo ) return;

    const TQString username   = AmarokConfig::scrobblerUsername();
    const TQString userpage   = "www.last.fm/user/" + username; //no http
    const TQString albumUrl   = lastFmInfo->albumUrl();
    const TQString artistUrl  = lastFmInfo->artistUrl();
    const TQString titleUrl   = lastFmInfo->titleUrl();

    const TQString coverImage = ContextBrowser::getEncodedImage( lastFmInfo->imageUrl() );

    TQPtrList<TQString> newUrls;
    newUrls.append( &albumUrl  );
    newUrls.append( &artistUrl );
    newUrls.append( &titleUrl  );

    for ( TQString* url = newUrls.first(); url; url = newUrls.next() )
        url->replace( TQRegExp( "^http:" ), "externalurl:" );

    const TQString skipIcon = TDEGlobal::iconLoader()->iconPath( Amarok::icon("next"),   -TDEIcon::SizeSmallMedium );
    const TQString loveIcon = TDEGlobal::iconLoader()->iconPath( Amarok::icon("love"),   -TDEIcon::SizeSmallMedium );
    const TQString banIcon  = TDEGlobal::iconLoader()->iconPath( Amarok::icon("remove"), -TDEIcon::SizeSmallMedium );


    m_HTMLSource.append( QStringx(
            "<div id='current_box' class='box'>\n"
            "<div id='current_box-header' class='box-header'>\n"
            "<span id='current_box-header-stream' class='box-header-title'>%1</span> "
            "</div>\n"
            "<table id='current_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n"
            "<tr class='box-row'>\n"
            "<td id='current_box-information-td' colspan=2>\n"
            "<a href='%2'><b>%3</b></a> - <a href='%4'><b>%5</b></a>"
            "<br />\n"
            "<a href='%6'><b>%7</b></a>"
            "</td>\n"
            "</tr>\n"
            "<tr class='box-row'>\n"
            "<td id='current_box-largecover-td'>\n"
            "<a href='%8'>"
            "<img id='current_box-largecover-image' src='%9' title='%10'>\n"
            "</a></td>\n"
            "<td id='current_box-information-td' align='right'>\n"
            "<div id='musicbrainz-div'>\n"
            "<a id='lastfm-a' href='externalurl://%11'>\n"
            "<img id='lastfm-image' title='%12' src='%13' />\n"
            "</a>\n"
            "</div>\n"
            "<table cellpadding='1'>\n"
            "<tr><td>\n"
            "<a href='lastfm:skip'>%14</a>\n"
            "</td><td>\n"
            "<a href='lastfm:skip'><img id='lastfm-skip-image' src='%15'></a>\n"
            "</td></tr>\n"
            "<tr><td>\n"
            "<a href='lastfm:love'>%16</a>\n"
            "</td><td>\n"
            "<a href='lastfm:love'><img id='lastfm-love-image' src='%17'></a>\n"
            "</td></tr>\n"
            "<tr><td>\n"
            "<a href='lastfm:ban'>%18</a>\n"
            "</td><td>\n"
            "<a href='lastfm:ban'><img id='lastfm-ban-image' src='%19'></a>\n"
            "</td></tr>\n"
            "</table>\n"
            "</td>\n"
            "</tr>\n"
            "</table>\n"
            "</div>\n" )
            .args( TQStringList()
            << escapeHTML( LastFm::Controller::stationDescription() )  //1
            << artistUrl  //2
            << escapeHTML( currentTrack.artist() ) //3
            << titleUrl //4
            << escapeHTML( currentTrack.title() ) //5
            << albumUrl //6
            << escapeHTML( currentTrack.album() ) //7
            << albumUrl //8
            << coverImage //9
            << escapeHTMLAttr( currentTrack.album() )//10
            << escapeHTMLAttr( userpage ) //11
            << escapeHTMLAttr( userpage ) //12
            << escapeHTMLAttr( m_lastfmIcon ) //13
            << escapeHTML( i18n( "Skip" ) ) //14
            << escapeHTMLAttr( skipIcon ) //15
            << escapeHTML( i18n( "Love" ) ) //16
            << escapeHTMLAttr( loveIcon ) //17
            << escapeHTML( i18n( "Ban" ) ) //18
            << escapeHTMLAttr( banIcon ) //19
                ) );

    addMetaHistory();

    if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested )
    {
        TQStringList relArtists = CollectionDB::instance()->similarArtists( currentTrack.artist(), 10 );
        if ( !relArtists.isEmpty() )
        {
            if( ContextBrowser::instance()->m_showRelated )
                showRelatedArtists( currentTrack.artist(), relArtists );

            if( ContextBrowser::instance()->m_showSuggested )
                showSuggestedSongs( relArtists );
        }
    }

    const uint artist_id = CollectionDB::instance()->artistID( currentTrack.artist(), false /* don't autocreate */ );
    if( artist_id )
    {
        if( ContextBrowser::instance()->m_showFaves )
            showArtistsFaves( currentTrack.artist(), artist_id );

        const uint album_id  = CollectionDB::instance()->albumID ( currentTrack.album(), false /* don't autocreate */ );
        showArtistsAlbums( currentTrack.artist(), artist_id, album_id );
        showArtistsCompilations( currentTrack.artist(), artist_id, album_id );
    }

    m_HTMLSource.append( "</body></html>\n" );
}


void CurrentTrackJob::showStream( const MetaBundle &currentTrack )
{
    m_HTMLSource.append( QStringx(
                "<div id='current_box' class='box'>\n"
                "<div id='current_box-header' class='box-header'>\n"
                "<span id='current_box-header-stream' class='box-header-title'>%1</span> "
                "</div>\n"
                "<table id='current_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n"
                "<tr class='box-row'>\n"
                "<td height='42' valign='top' width='90%'>\n"
                "<b>%2</b>\n"
                "<br />\n"
                "<br />\n"
                "%3"
                "<br />\n"
                "<br />\n"
                "%4"
                "<br />\n"
                "%5 kbps"
                "<br />\n"
                "%6"
                "<br />\n"
                "%7"
                "</td>\n"
                "</tr>\n"
                "</table>\n"
                "</div>\n" )
                .args( TQStringList()
                        << i18n( "Stream Details" )
                        << escapeHTML( currentTrack.prettyTitle() )
                        << escapeHTML( currentTrack.streamName() )
                        << escapeHTML( currentTrack.genre() )
                        << escapeHTML( currentTrack.prettyBitrate() )
                        << escapeHTML( currentTrack.streamUrl() )
                        << escapeHTML( currentTrack.prettyURL() ) ) );

    addMetaHistory();

    m_HTMLSource.append( "</body></html>\n" );
}

void CurrentTrackJob::addMetaHistory()
{
    if ( m_metadataHistory.count() > 0 )
    {
        m_HTMLSource.append(
                "<div id='stream-history_box' class='box'>\n"
                "<div id='stream-history_box-header' class='box-header'>\n" + i18n( "Metadata History" ) + "</div>\n"
                "<table id='stream-history_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );

        for ( uint i = 0; i < m_metadataHistory.count(); ++i )
        {
            const TQString &str = m_metadataHistory[i];
            m_HTMLSource.append( QStringx( "<tr class='box-row'><td>%1</td></tr>\n" ).arg( str ) );
        }

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );
    }
}

void CurrentTrackJob::showPodcast( const MetaBundle &currentTrack )
{
    if( !currentTrack.podcastBundle() )
        return;

    PodcastEpisodeBundle peb = *currentTrack.podcastBundle();
    PodcastChannelBundle pcb;
    bool channelInDB = true;
    if( !CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) )
    {
        pcb.setTitle( i18n( "Unknown Channel (not in Database)" ) );
        channelInDB = false;
    }

    TQString image;
    if( pcb.imageURL().isValid() )
       image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true );
    else
       image = CollectionDB::instance()->notAvailCover( true );

    TQString imageAttr = escapeHTMLAttr( pcb.link().isValid()
            ? i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() )
            : i18n( "No podcast website." )
            );

    m_HTMLSource.append( QStringx(
                "<div id='current_box' class='box'>\n"
                "<div id='current_box-header' class='box-header'>\n"
                "<span id='current_box-header-artist' class='box-header-title'>%1</span> "
                "<br />\n"
                "<span id='current_box-header-album' class='box-header-title'>%2</span>\n"
                "</div>\n"

                "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
                "<tr>\n"
                "<td id='current_box-largecover-td'>\n"
                "<a id='current_box-largecover-a' href='%3'>\n"
                "<img id='current_box-largecover-image' src='%4' title='%5'>\n"
                "</a>\n"
                "</td>\n"
                "<td id='current_box-information-td' align='right'>\n"
                "%6"
                "%7"
                "</td>\n"
                "</table>\n"
                "</div>\n" )
            .args( TQStringList()
                << escapeHTML( pcb.title() )
                << escapeHTML( peb.title() )
                << ( pcb.link().isValid()
                    ? pcb.link().url().replace( TQRegExp( "^http:" ), "externalurl:" )
                    : "current://track" )
                << image
                << imageAttr
                << escapeHTML( peb.author().isEmpty()
                    ? i18n( "Podcast" )
                    : i18n( "Podcast by %1" ).arg( peb.author() ) )
                << ( peb.localUrl().isValid()
                    ? "<br />\n" + escapeHTML( i18n( "(Cached)" ) )
                    : "" )
                )
            );

    if ( m_isStream && m_metadataHistory.count() > 1 )
    {
        m_HTMLSource.append(
                "<div id='stream-history_box' class='box'>\n"
                "<div id='stream-history_box-header' class='box-header'>\n" + i18n( "Metadata History" ) + "</div>\n"
                "<table id='stream-history_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );

        for ( uint i = 0; i < m_metadataHistory.count(); ++i )
        {
            const TQString &str = m_metadataHistory[i];
            m_HTMLSource.append( QStringx( "<tr class='box-row'><td>%1</td></tr>\n" ).arg( str ) );
        }

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );
    }

    m_HTMLSource.append(
            "<div id='albums_box' class='box'>\n"
            "<div id='albums_box-header' class='box-header'>\n"
            "<span id='albums_box-header-title' class='box-header-title'>\n"
            + ( channelInDB
                ? i18n( "Episodes from %1" ).arg( escapeHTML( pcb.title() ) )
                : i18n( "Episodes from this Channel" )
              )
            + "</span>\n"
            "</div>\n"
            "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

    uint i = 0;
    TQValueList<PodcastEpisodeBundle> episodes = CollectionDB::instance()->getPodcastEpisodes( peb.parent() );
    while( !episodes.isEmpty() )
    {
        PodcastEpisodeBundle &ep = episodes.back();
        TQString date;

        ep.dateTime().isNull() ?
                date = ep.date() :
                date = ep.dateTime().toString();

        m_HTMLSource.append( QStringx (
                    "<tr class='" + TQString( (i % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td>\n"
                    "<div class='album-header' onClick=\"toggleBlock('IDE%1')\">\n"
                    "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
                    "<tr>\n"
                    "<td width='1'></td>\n"
                    "<td valign='middle' align='left'>\n"
                    "<span class='album-info'>%2</span> "
                    "<a href='%3'><span class='album-title'>%4</span></a>\n"
                    "<br />\n"
                    "<span class='album-year'>%5</span>\n"
                    "</td>\n"
                    "</tr>\n"
                    "</table>\n"
                    "</div>\n"
                    "<div class='album-body' style='display:%6;' id='IDE%7'>\n" )
                .args( TQStringList()
                    << TQString::number( i )
                    << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : TQString( "" ) )
                    << ( ep.localUrl().isValid()
                        ? ep.localUrl().url()
                        : ep.url().url().replace( TQRegExp( "^http:" ), "stream:" ) )
                    << escapeHTML( ep.title() )
                    << escapeHTML( date )
                    << (peb.url() == ep.url() ? "block" : "none" )
                    << TQString::number( i )
                    )
                );

        m_HTMLSource.append( QStringx ( "<p>%1</p>\n" ).arg( ep.description() ) );

        m_HTMLSource.append(
                "</div>\n"
                "</td>\n"
                "</tr>\n" );
        i++;
        episodes.pop_back();
    }

    m_HTMLSource.append("</body></html>\n" );
}

void CurrentTrackJob::showBrowseArtistHeader( const TQString &artist )
{
    // <Artist>
    bool linkback = ( b->m_contextBackHistory.size() > 0 );
    TQString back = ( linkback
            ? "<a id='artist-back-a' href='artistback://back'>\n"
            + escapeHTML( i18n( "<- Back" ) )
            + "</a>\n"
            : TQString( "" )
            );
    m_HTMLSource.append(
            TQString(
                "<div id='current_box' class='box'>\n"
                "<div id='current_box-header' class='box-header'>\n"
                "<span id='current_box-header-artist' class='box-header-title'>%1</span>\n"
                "<br />\n"
                "<table width='100%' cellpadding='0' cellspacing='0'><tr>\n"
                "<td><span id='current_box-header-album' class='box-header-title'>%2</span></td>\n"
                "<td><div id='current_box-header-nav' class='box-header-nav'>%3</div></td>\n"
                "</tr></table>\n"
                "</div>\n" )
            .arg( escapeHTML( artist ) )
            .arg( escapeHTML( i18n( "Browse Artist" ) ) )
            .arg( back ) );
    m_HTMLSource.append(
            "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
            );

    m_HTMLSource.append(
            "<tr>\n"
            "<td id='context'>\n"
            + TQString( "<a id='context-a=' href='current://track'>\n" )
            + i18n( "Information for Current Track" )
            + "</a>\n"
            "</td>\n"
            "</tr>\n"
            );

    m_HTMLSource.append(
            "<tr>\n"
            "<td id='artist-wikipedia'>\n"
            + TQString( "<a id='artist-wikipedia-a' href='wikipedia:%1'>\n" ).arg( escapeHTMLAttr( artist + b->wikiArtistPostfix() ) )
            + i18n( "Wikipedia Information for %1" ).arg( escapeHTML( artist ) ) +
            "</a>\n"
            "</td>\n"
            "</tr>\n");
    m_HTMLSource.append(
            "<tr>\n"
            "<td id='artist-google'>\n"
            + TQString( "<a id='artist-google-a' href='ggartist:%1'>\n" ).arg( escapeHTMLAttr( artist ) )
            + i18n( "Google Musicsearch for %1" ).arg( escapeHTML( artist ) ) +
            "</a>\n"
            "</td>\n"
            "</tr>\n"
            );

    m_HTMLSource.append(
            "</td>\n"
            "</tr>\n"
            "</table>\n"
            "</div>\n" );
    // </Artist>
}

void
CurrentTrackJob::showBrowseLabelHeader( const TQString &label )
{
    bool linkback = ( b->m_contextBackHistory.size() > 0 );
    TQString back = ( linkback
            ? "<a id='artist-back-a' href='artistback://back'>\n"
            + escapeHTML( i18n( "<- Back" ) )
            + "</a>\n"
            : TQString( "" )
            );
    m_HTMLSource.append(
            TQString(
                "<div id='current_box' class='box'>\n"
                "<div id='current_box-header' class='box-header'>\n"
                "<span id='current_box-header-artist' class='box-header-title'>%1</span>\n"
                "<br />\n"
                "<table width='100%' cellpadding='0' cellspacing='0'><tr>\n"
                "<td><span id='current_box-header-album' class='box-header-title'>%2</span></td>\n"
                "<td><div id='current_box-header-nav' class='box-header-nav'>%3</div></td>\n"
                "</tr></table>\n"
                "</div>\n" )
            .arg( escapeHTML( label ) )
            .arg( escapeHTML( i18n( "Browse Label" ) ) )
            .arg( back ) );
    m_HTMLSource.append(
            "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
            );

    m_HTMLSource.append(
            "<tr>\n"
            "<td id='context'>\n"
            + TQString( "<a id='context-a=' href='current://track'>\n" )
            + i18n( "Information for Current Track" )
            + "</a>\n"
            "</td>\n"
            "</tr>\n"
            );

    m_HTMLSource.append(
            "<tr>\n"
            "<td id='label-lastfm'>\n"
            + TQString( "<a id='label-lastfm-a' href='externalurl://www.last.fm/tag/%1'>\n" ).arg( escapeHTMLAttr( label ) )
            + i18n( "Last.fm Information for %1" ).arg( escapeHTML( label ) ) +
            "</a>\n"
            "</td>\n"
            "</tr>\n");

    m_HTMLSource.append(
            "</td>\n"
            "</tr>\n"
            "</table>\n"
            "</div>\n" );
}

void CurrentTrackJob::showCurrentArtistHeader( const MetaBundle &currentTrack )
{
    QueryBuilder qb;
    TQStringList values;
    // <Current Track Information>
    qb.clear();
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valCreateDate );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valAccessDate );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valPlayCounter );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
    qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, currentTrack.url().path() );
    values = qb.run();
    usleep( 10000 );

    //making 2 tables is most probably not the cleanest way to do it, but it works.
    TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( currentTrack, true, 1 ) );
    TQString albumImageTitleAttr = albumImageTooltip( albumImage, 0 );

    bool isCompilation = false;
    if( !currentTrack.album().isEmpty() )
    {
        isCompilation = CollectionDB::instance()->albumIsCompilation(
                TQString::number( CollectionDB::instance()->albumID( currentTrack.album() ) )
                );
    }

    m_HTMLSource.append(
            "<div id='current_box' class='box'>\n"
            "<div id='current_box-header' class='box-header'>\n"
            // Show "Title - Artist \n Album", or only "PrettyTitle" if there's no title tag
            + ( !currentTrack.title().isEmpty()
                ? QStringx(
                    "<span id='current_box-header-songname' class='box-header-title'>%1</span> "
                    "<span id='current_box-header-separator' class='box-header-title'>-</span> "
                    "<span id='current_box-header-artist' class='box-header-title'>%2</span>\n"
                    "<br />\n"
                    "<span id='current_box-header-album' class='box-header-title'>%3</span>\n"
                    "</div>\n"
                    "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
                    "<tr>\n"
                    "<td id='current_box-largecover-td'>\n"
                    "<a id='current_box-largecover-a' href='fetchcover:%4 @@@ %5'>\n"
                    "<img id='current_box-largecover-image' src='%6' title='%7'>\n"
                    "</a>\n"
                    "</td>\n"
                    "<td id='current_box-information-td' align='right'>\n"
                    "<div id='musicbrainz-div'>\n"
                    "<a id='musicbrainz-a' title='%8' href='musicbrainz:%9 @@@ %10 @@@ %11'>\n"
                    "<img id='musicbrainz-image' src='%12' />\n"
                    "</a>\n"
                    "</div>\n"
                    )
                .args( TQStringList()
                        << escapeHTML( currentTrack.title() )
                        << escapeHTML( currentTrack.artist() )
                        << escapeHTML( currentTrack.album() )
                        << ( isCompilation ? "" : escapeHTMLAttr( currentTrack.artist() ) )
                        << escapeHTMLAttr( currentTrack.album() )
                        << escapeHTMLAttr( albumImage )
                        << albumImageTitleAttr
                        << i18n( "Look up this track at musicbrainz.org" )
                        << escapeHTMLAttr( currentTrack.artist() )
                        << escapeHTMLAttr( currentTrack.album() )
                        << escapeHTMLAttr( currentTrack.title() )
                        << escapeHTML( m_musicBrainIconPath ) )
                : TQString ( //no title
                        "<span id='current_box-header-prettytitle' class='box-header-prettytitle'>%1</span> "
                        "</div>\n"
                        "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
                        "<tr>\n"
                        "<td id='current_box-largecover-td'>\n"
                        "<a id='current_box-largecover-a' href='fetchcover:%2 @@@ %3'>\n"
                        "<img id='current_box-largecover-image' src='%4' title='%5'>\n"
                        "</a>\n"
                        "</td>\n"
                        "<td id='current_box-information-td' align='right'>\n"
                        )
                .arg( escapeHTML( currentTrack.prettyTitle() ) )
                .arg( escapeHTMLAttr( currentTrack.artist() ) )
                .arg( escapeHTMLAttr( currentTrack.album() ) )
                .arg( escapeHTMLAttr( albumImage ) )
                .arg( albumImageTitleAttr )
                ) );

    if ( !values.isEmpty() && values[2].toInt() )
    {
        TQDateTime firstPlay = TQDateTime();
        firstPlay.setTime_t( values[0].toUInt() );
        TQDateTime lastPlay = TQDateTime();
        lastPlay.setTime_t( values[1].toUInt() );

        const uint playtimes = values[2].toInt();
        const uint score = static_cast<uint>( values[3].toFloat() );
        const uint rating = values[4].toInt();

        //SAFE   = .arg( x, y )
        //UNSAFE = .arg( x ).arg( y )
        m_HTMLSource.append( TQString(
                    "<span>%1</span><br />\n"
                    "<div>%2</div>\n"
                    "<span>%3</span><br />\n"
                    "<span>%4</span>\n"
                    )
                .arg( i18n( "Track played once", "Track played %n times", playtimes ),
                      statsHTML( score, rating, false ),
                      i18n( "Last played: %1" ).arg( Amarok::verboseTimeSince( lastPlay ) ),
                      i18n( "First played: %1" ).arg( Amarok::verboseTimeSince( firstPlay ) ) ) );
    }
    else
        m_HTMLSource.append( i18n( "Never played before" ) );

    m_HTMLSource.append(
            "</td>\n"
            "</tr>\n"
            "</table>\n"
            "</div>\n" );
    // </Current Track Information>

    if ( currentTrack.url().isLocalFile() && !CollectionDB::instance()->isFileInCollection( currentTrack.url().path() ) )
    {
        m_HTMLSource.append(
                "<div id='notindb_box' class='box'>\n"
                "<div id='notindb_box-header' class='box-header'>\n"
                "<span id='notindb_box-header-title' class='box-header-title'>\n"
                + i18n( "This file is not in your Collection!" ) +
                "</span>\n"
                "</div>\n"
                "<div id='notindb_box-body' class='box-body'>\n"
                "<div class='info'><p>\n"
                + i18n( "If you would like to see contextual information about this track,"
                    " you should add it to your Collection." ) +
                "</p></div>\n"
                "<div align='center'>\n"
                "<input type='button' onClick='window.location.href=\"show:collectionSetup\";' value='"
                + i18n( "Change Collection Setup..." ) +
                "' class='button' /></div><br />\n"
                "</div>\n"
                "</div>\n"
                );
    }

    /* cue file code */
    if ( b->m_cuefile && (b->m_cuefile->count() > 0) ) {
        m_HTMLSource.append(
                "<div id='cue_box' class='box'>\n"
                "<div id='cue_box-header' class='box-header'>\n"
                "<span id='cue_box-header-title' class='box-header-title' onClick=\"toggleBlock('T_CC'); window.location.href='togglebox:cc';\" style='cursor: pointer;'>\n"
                + i18n( "Cue File" ) +
                "</span>\n"
                "</div>\n"
                "<table id='cue_box-body' class='box-body' id='T_CC' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
        CueFile::Iterator it;
        uint i = 0;
        for ( it = b->m_cuefile->begin(); it != b->m_cuefile->end(); ++it ) {
            m_HTMLSource.append(
                    "<tr class='" + TQString( (i++ % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td class='song'>\n"
                    "<a href=\"seek:" + TQString::number(it.key()) + "\">\n"
                    "<span class='album-song-trackno'>\n" + TQString::number(it.data().getTrackNumber()) + "&nbsp;</span>\n"
                    "<span class='album-song-title'>\n" + escapeHTML( it.data().getTitle() ) + "</span>\n"
                    "<span class='song-separator'>\n"
                    + i18n("&#xa0;&#8211; ") +
                    "</span>\n"
                    "<span class='album-song-title'>\n" + escapeHTML( it.data().getArtist() ) + "</span>\n"
                    "<span class='album-song-time'>&nbsp;(" + MetaBundle::prettyTime( it.data().getLength()/1000, false ) + ")</span>\n"
                    "</a>\n"
                    "</td>\n"
                    ""
                    );
        }
        m_HTMLSource.append(
                "</table>\n"
                "</div>\n"
                );
    }
}


void CurrentTrackJob::showRelatedArtists( const TQString &artist, const TQStringList &relArtists )
{
    // <Related Artists>
    m_HTMLSource.append( TQString(
                "<div id='related_box' class='box'>\n"
                "<div id='related_box-header' class='box-header' onClick=\"toggleBlock('T_RA'); window.location.href='togglebox:ra';\" style='cursor: pointer;'>\n"
                "<span id='related_box-header-title' class='box-header-title'>%1</span>\n"
                "</div>\n"
                "<table class='box-body' id='T_RA' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" )
            .arg( i18n( "Artists Related to %1" ).arg( escapeHTML( artist ) ) ) );
    m_HTMLSource.append( "<tr><td>\n" );
    for ( uint i = 0; i < relArtists.count(); i += 1 )
    {
        bool isInCollection = !CollectionDB::instance()->albumListOfArtist( relArtists[i] ).isEmpty();
        m_HTMLSource.append(
                ( isInCollection ? "" : "<i>" )
                + TQString( "<a href='artist:" ) + escapeHTMLAttr( relArtists[i] ) + "'>" + escapeHTML( relArtists[i] ) + "</a>"
                + ( isInCollection ? "" : "</i>" )
                );
        if( i != relArtists.count()-1 )
            m_HTMLSource.append( ", \n" );
    }

    m_HTMLSource.append( "</td></tr>\n" );
    m_HTMLSource.append(
            "</table>\n"
            "</div>\n" );

    if ( !b->m_relatedOpen )
        m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_RA');</script>\n" );
    // </Related Artists>
}

void CurrentTrackJob::showSuggestedSongs( const TQStringList &relArtists )
{
    TQString token;

    QueryBuilder qb;
    TQStringList values;
    qb.clear();
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
    qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
    qb.addMatches( QueryBuilder::tabArtist, relArtists );
    qb.sortByFavorite();
    qb.setLimit( 0, 10 );
    values = qb.run();

    // <Suggested Songs>
    if ( !values.isEmpty() )
    {
        m_HTMLSource.append(
                "<div id='suggested_box' class='box'>\n"
                "<div id='suggested_box-header' class='box-header' onClick=\"toggleBlock('T_SS'); window.location.href='togglebox:ss';\" style='cursor: pointer;'>\n"
                "<span id='suggested_box-header-title' class='box-header-title'>\n"
                + i18n( "Suggested Songs" ) +
                "</span>\n"
                "</div>\n"
                "<table class='box-body' id='T_SS' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        for ( uint i = 0; i < values.count(); i += 5 )
            m_HTMLSource.append(
                    "<tr class='" + TQString( (i % 8) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td class='song'>\n"
                    "<a href=\"file:" + escapeHTMLAttr ( values[i] ) + "\">\n"
                    "<span class='album-song-title'>\n"+ escapeHTML( values[i + 2] ) + "</span>\n"
                    "<span class='song-separator'>\n"
                    + i18n("&#xa0;&#8211; ") +
                    "</span><span class='album-song-title'>\n" + escapeHTML( values[i + 1] ) + "</span>\n"
                    "</a>\n"
                    "</td>\n"
                    "<td>\n" + statsHTML( static_cast<int>( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "</td>\n"
                    "<td width='1'></td>\n"
                    "</tr>\n" );

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );

        if ( !b->m_suggestionsOpen )
            m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_SS');</script>\n" );
    }
    // </Suggested Songs>
}

void
CurrentTrackJob::showSongsWithLabel( const TQString &label )
{
    QueryBuilder qb;
    TQStringList values;
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
    qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
    qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) );
    qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valName, label );
    qb.sortByFavorite();
    qb.setOptions( QueryBuilder::optRandomize );
    qb.setLimit( 0, 30 );
    values = qb.run();

    if ( !values.isEmpty() )
    {
        m_HTMLSource.append(
                "<div id='suggested_box' class='box'>\n"
                "<div id='suggested_box-header' class='box-header' onClick=\"toggleBlock('T_SS'); window.location.href='togglebox:ss';\" style='cursor: pointer;'>\n"
                "<span id='suggested_box-header-title' class='box-header-title'>\n"
                + i18n( "Songs with label %1" ).arg( label ) +
                "</span>\n"
                "</div>\n"
                "<table class='box-body' id='T_' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        for ( uint i = 0; i < values.count(); i += 5 )
            m_HTMLSource.append(
                    "<tr class='" + TQString( (i % 8) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td class='song'>\n"
                    "<a href=\"file:" + escapeHTMLAttr ( values[i] ) + "\">\n"
                    "<span class='album-song-title'>\n"+ escapeHTML( values[i + 2] ) + "</span>\n"
                    "<span class='song-separator'>\n"
                    + i18n("&#xa0;&#8211; ") +
                    "</span><span class='album-song-title'>\n" + escapeHTML( values[i + 1] ) + "</span>\n"
                    "</a>\n"
                    "</td>\n"
                    "<td>\n" + statsHTML( static_cast<int>( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "</td>\n"
                    "<td width='1'></td>\n"
                    "</tr>\n" );

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );
    }
}

void
CurrentTrackJob::showUserLabels( const MetaBundle &currentTrack )
{
    QueryBuilder qb;
    qb.addReturnValue( QueryBuilder::tabLabels, QueryBuilder::valName, true );
    qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, currentTrack.url().path() );
    qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) );
    qb.setLimit( 0, 10 );
    qb.sortBy( QueryBuilder::tabLabels, QueryBuilder::valName, false );
    qb.buildQuery();
    TQStringList values = qb.run();

    TQString title;
    if ( currentTrack.title().isEmpty() )
        title = currentTrack.veryNiceTitle();
    else
        title = currentTrack.title();

     m_HTMLSource.append(
                "<div id='songlabels_box' class='box'>\n"
                "<div id='songlabels-header' class='box-header' onCLick=\"toggleBlock('T_SL');window.location.href='togglebox:sl';\" style='cursor: pointer;'>\n"
                "<span id='songlabels_box-header-title' class='box-header-title'>\n"
                + i18n( " Labels for %1 " ).arg( escapeHTML( title ) ) +
                "</span>\n"
                "</div>\n"
                "<table class='box-body' id='T_SL' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
    m_HTMLSource.append( "<tr><td>\n" );
    if ( !values.isEmpty() )
    {
        foreach( values )
        {
            if( it != values.begin() )
                m_HTMLSource.append( ", \n" );
            m_HTMLSource.append( "<a href='showlabel:" + escapeHTMLAttr( *it ) + "'>" + escapeHTML( *it ) + "</a>" );
        }
    }
    m_HTMLSource.append( "</td></tr>\n" );
    m_HTMLSource.append( "<tr><td><a id='songlabels_box_addlabel' href='show:editLabels'>" + i18n( "Add labels to %1" ).arg( escapeHTML( title ) ) + "</a></td></tr>\n" );
    m_HTMLSource.append(
            "</table>\n"
            "</div>\n" );

    if ( !b->m_labelsOpen )
        m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_SL');</script>\n" );
}

void CurrentTrackJob::showArtistsFaves( const TQString &artist, uint artist_id )
{
    TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
    QueryBuilder qb;
    TQStringList values;

    qb.clear();
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
    qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
    qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
    qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0", QueryBuilder::modeGreater );
    qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
    qb.sortByFavorite();
    qb.setLimit( 0, 10 );
    values = qb.run();
    usleep( 10000 );

    if ( !values.isEmpty() )
    {
        m_HTMLSource.append(
                "<div id='favoritesby_box' class='box'>\n"
                "<div id='favoritesby-header' class='box-header' onClick=\"toggleBlock('T_FT'); window.location.href='togglebox:ft';\" style='cursor: pointer;'>\n"
                "<span id='favoritesby_box-header-title' class='box-header-title'>\n"
                + i18n( "Favorite Tracks by %1" ).arg( artistName ) +
                "</span>\n"
                "</div>\n"
                "<table class='box-body' id='T_FT' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        for ( uint i = 0; i < values.count(); i += 4 )
            m_HTMLSource.append(
                    "<tr class='" + TQString( (i % 6) ? "box-row-alt" : "box-row" ) + "'>\n"
                    "<td class='song'>\n"
                    "<a href=\"file:" + escapeHTMLAttr ( values[i + 1] ) + "\">\n"
                    "<span class='album-song-title'>\n" + escapeHTML( values[i] ) + "</span>\n"
                    "</a>\n"
                    "</td>\n"
                    "<td>\n" + statsHTML( static_cast<int>( values[i + 2].toFloat() ), values[i + 3].toInt() ) + "</td>\n"
                    "<td width='1'></td>\n"
                    "</tr>\n"
                    );

        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );

        if ( !b->m_favoritesOpen )
            m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_FT');</script>\n" );

    }
}

void CurrentTrackJob::showArtistsAlbums( const TQString &artist, uint artist_id, uint album_id )
{
    DEBUG_BLOCK
    TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
    QueryBuilder qb;
    TQStringList values;
    // <Albums by this artist>
    qb.clear();
    qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
    qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
    qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName );
    qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
    qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
    qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
    qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName, true );
    qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true );
    qb.setOptions( QueryBuilder::optNoCompilations );
    values = qb.run();

    if ( !values.isEmpty() )
    {
        // write the script to toggle blocks visibility
        m_HTMLSource.append(
                "<div id='albums_box' class='box'>\n"
                "<div id='albums_box-header' class='box-header'>\n"
                "<span id='albums_box-header-title' class='box-header-title'>\n"
                + i18n( "Albums by %1" ).arg( artistName ) +
                "</span>\n"
                "</div>\n"
                "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        uint vectorPlace = 0;
        // find album of the current track (if it exists)
        while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) )
            vectorPlace += 3;
        for ( uint i = 0; i < values.count(); i += 3 )
        {
            qb.clear();
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
            qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
            qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] );
            qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
            qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
            qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
            qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle );
            qb.setOptions( QueryBuilder::optNoCompilations );
            TQStringList albumValues = qb.run();
            usleep( 10000 );

            TQString albumYear;
            if ( !albumValues.isEmpty() )
            {
                albumYear = albumValues[ 3 ];
                for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                    if ( albumValues[j + 3] != albumYear || albumYear == "0" )
                    {
                        albumYear = TQString();
                        break;
                    }
            }

            uint i_albumLength = 0;
            for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                i_albumLength += TQString(albumValues[j + 4]).toInt();

            TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );
            TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) );
            TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );

            m_HTMLSource.append( QStringx (
                        "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
                        "<td>\n"
                        "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
                        "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
                        "<tr>\n"
                        "<td width='1'>\n"
                        "<a href='fetchcover:%2 @@@ %3'>\n"
                        "<img class='album-image' align='left' vspace='2' hspace='2' title='%4' src='%5'/>\n"
                        "</a>\n"
                        "</td>\n"
                        "<td valign='middle' align='left'>\n"
                        "<span class='album-info'>%6</span> "
                        "<a href='album:%7 @@@ %8'><span class='album-title'>%9</span></a>\n"
                        "<br />\n"
                        "<span class='album-year'>%10</span>\n"
                        "<span class='album-length'>%11</span>\n"
                        "</td>\n"
                        "</tr>\n"
                        "</table>\n"
                        "</div>\n"
                        "<div class='album-body' style='display:%12;' id='IDA%13'>\n" )
                    .args( TQStringList()
                        << values[ i + 1 ]
                        << escapeHTMLAttr( artist ) // artist name
                        << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name
                        << albumImageTitleAttr
                        << escapeHTMLAttr( albumImage )
                        << i18n( "Single", "%n Tracks",  albumValues.count() / qb.countReturnValues() )
                        << TQString::number( artist_id )
                        << values[ i + 1 ] //album.id
                        << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] )
                        << albumYear
                        << albumLength
                        << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */
                        << values[ i + 1 ] ) );

            TQString discNumber;
            if ( !albumValues.isEmpty() )
                for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                {
                    TQString newDiscNumber = albumValues[ j + 5 ].stripWhiteSpace();
                    if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
                    {
                        discNumber = newDiscNumber;
                        m_HTMLSource.append( QStringx (
                                                 "<div class='disc-separator'>\n"
                                                 "<a href=\"albumdisc: %1 @@@ %2 @@@ %3\">\n"
                                                 "%4"
                                                 "</a>\n"
                                                 "</div>\n" )
                                             .args( TQStringList()
                                                    << TQString::number( artist_id )
                                                    << values[ i + 1 ] //album.id
                                                    << escapeHTMLAttr( discNumber )
                                                    << i18n( "Disc %1" ).arg( discNumber ) ) );
                    }
                    TQString track = albumValues[j + 2].stripWhiteSpace();
                    if( track.length() > 0 ) {
                        if( track.length() == 1 )
                            track.prepend( "0" );

                        track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
                    }

                    TQString length;
                    if( albumValues[j + 4] != "0" )
                        length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";

                    bool current = false;
                    if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
                        current = true;
                    m_HTMLSource.append(
                            "<div class='album-song'>\n"
                            "<a href=\"file:" + escapeHTMLAttr ( albumValues[j + 1] ) + "\">\n"
                            + track +
                            "<span class='album-song-title'>\n" + (current?"<i>":"") + escapeHTML( albumValues[j] ) + (current?"</i>":"") + "</span>&nbsp;"
                            + length +
                            "</a>\n"
                            "</div>\n" );
                }

            m_HTMLSource.append(
                    "</div>\n"
                    "</td>\n"
                    "</tr>\n" );
        }
        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );
    }
    // </Albums by this artist>
}

void CurrentTrackJob::showArtistsCompilations( const TQString &artist, uint artist_id, uint album_id )
{
    TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
    QueryBuilder qb;
    TQStringList values;
    // <Compilations with this artist>
    qb.clear();
    qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
    qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
    qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
    qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName, true );
    qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true );
    qb.setOptions( QueryBuilder::optRemoveDuplicates );
    qb.setOptions( QueryBuilder::optOnlyCompilations );
    values = qb.run();

    if ( !values.isEmpty() )
    {
        // write the script to toggle blocks visibility
        m_HTMLSource.append(
                "<div id='albums_box' class='box'>\n"
                "<div id='albums_box-header' class='box-header'>\n"
                "<span id='albums_box-header-title' class='box-header-title'>\n"
                + i18n( "Compilations with %1" ).arg( artistName ) +
                "</span>\n"
                "</div>\n"
                "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );

        uint vectorPlace = 0;
        // find album of the current track (if it exists)
        while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) )
            vectorPlace += 2;
        for ( uint i = 0; i < values.count(); i += 2 )
        {
            qb.clear();
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
            qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
            qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
            qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
            qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] );
            qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
            qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
            qb.setOptions( QueryBuilder::optOnlyCompilations );
            TQStringList albumValues = qb.run();
            usleep( 10000 );

            TQString albumYear;
            if ( !albumValues.isEmpty() )
            {
                albumYear = albumValues[ 3 ];
                for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                    if ( albumValues[j + 3] != albumYear || albumYear == "0" )
                    {
                        albumYear = TQString();
                        break;
                    }
            }

            uint i_albumLength = 0;
            for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                i_albumLength += TQString(albumValues[j + 4]).toInt();

            TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );
            TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) );
            TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );

            m_HTMLSource.append( QStringx (
                        "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
                        "<td>\n"
                        "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
                        "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
                        "<tr>\n"
                        "<td width='1'>\n"
                        "<a href='fetchcover: @@@ %2'>\n"
                        "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4'/>\n"
                        "</a>\n"
                        "</td>\n"
                        "<td valign='middle' align='left'>\n"
                        "<span class='album-info'>%5</span> "
                        "<a href='compilation:%6'><span class='album-title'>%7</span></a>\n"
                        "<br />\n"
                        "<span class='album-year'>%8</span>\n"
                        "<span class='album-length'>%9</span>\n"
                        "</td>\n"
                        "</tr>\n"
                        "</table>\n"
                        "</div>\n"
                        "<div class='album-body' style='display:%10;' id='IDA%11'>\n" )
                    .args( TQStringList()
                        << values[ i + 1 ]
                        << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name
                        << albumImageTitleAttr
                        << escapeHTMLAttr( albumImage )
                        << i18n( "Single", "%n Tracks",  albumValues.count() / qb.countReturnValues() )
                        << values[ i + 1 ] //album.id
                        << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] )
                        << albumYear
                        << albumLength
                        << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */
                        << values[ i + 1 ] ) );

            TQString discNumber;
            if ( !albumValues.isEmpty() )
                for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
                {
                    TQString newDiscNumber = albumValues[ j + 6 ].stripWhiteSpace();
                    if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
                    {
                        discNumber = newDiscNumber;
                        m_HTMLSource.append( QStringx (
                                                 "<div class='disc-separator'>\n"
                                                 "<a href=\"compilationdisc: __discard__ @@@ %1 @@@ %2\">\n"
                                                 "%3"
                                                 "</a>\n"
                                                 "</div>\n" )
                                             .args( TQStringList()
                                                    << values[ i + 1 ] //album.id
                                                    << escapeHTMLAttr( discNumber )
                                                    << i18n( "Disc %1" ).arg( discNumber ) ) );
                    }

                    TQString track = albumValues[j + 2].stripWhiteSpace();
                    if( track.length() > 0 ) {
                        if( track.length() == 1 )
                            track.prepend( "0" );

                        track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
                    }

                    TQString length;
                    if( albumValues[j + 4] != "0" )
                        length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";

                    TQString tracktitle_formated;
                    TQString tracktitle;
                    tracktitle = escapeHTML( i18n("%1 - %2").arg( albumValues[j + 5], albumValues[j] ) );
                    tracktitle_formated = "<span class='album-song-title'>\n";
                    if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
                        tracktitle_formated += "<i>\n";
                    if ( artist == albumValues[j + 5] )
                        tracktitle_formated += "<b>\n";
                    tracktitle_formated += tracktitle;
                    if ( artist == albumValues[j + 5] )
                        tracktitle_formated += "</b>\n";
                    if( i==vectorPlace && track.toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
                        tracktitle_formated += "</i>\n";
                    tracktitle_formated += "</span>&nbsp;";
                    m_HTMLSource.append(
                            "<div class='album-song'>\n"
                            "<a href=\"file:" + escapeHTMLAttr ( albumValues[j + 1] ) + "\">\n"
                            + track
                            + tracktitle_formated
                            + length +
                            "</a>\n"
                            "</div>\n" );
                }

            m_HTMLSource.append(
                    "</div>\n"
                    "</td>\n"
                    "</tr>\n" );
        }
        m_HTMLSource.append(
                "</table>\n"
                "</div>\n" );
    }
    // </Compilations with this artist>
}

TQString CurrentTrackJob::statsHTML( int score, int rating, bool statsbox ) //static
{
    if( !AmarokConfig::useScores() && !AmarokConfig::useRatings() )
        return "";

    if ( rating < 0 )
        rating = 0;
    if ( rating > 10 )
        rating = 10;

    TQString table = TQString( "<table %1 align='right' border='0' cellspacing='0' cellpadding='0' width='100%'>%2</table>\n" )
                          .arg( statsbox ? "class='statsBox'" : "" );
    TQString contents;

    if( AmarokConfig::useScores() )
        contents += TQString( "<tr title='%1'>\n" ).arg( i18n( "Score: %1" ).arg( score ) ) +
                    "<td class='sbtext' width='100%' align='right'>\n" + TQString::number( score ) + "</td>\n"
                    "<td align='left' width='1'>\n"
                    "<div class='sbouter'>\n"
                    "<div class='sbinner' style='width: "
                    + TQString::number( score / 2 ) + "px;'></div>\n"
                    "</div>\n"
                    "</td>\n"
                    "</tr>\n";

    if( AmarokConfig::useRatings() )
    {
        contents += TQString( "<tr title='%1'>\n" ).arg( i18n( "Rating: %1" )
                                                      .arg( MetaBundle::ratingDescription( rating ) ) ) +
                    "<td class='ratingBox' align='right' colspan='2'>\n";
        if( rating )
        {
            bool half = rating%2;
            contents += "<nobr>\n";

            TQImageIO fullStarIO;
            fullStarIO.setImage( StarManager::instance()->getStarImage( half ? rating/2 + 1 : rating/2 ) );
            fullStarIO.setFormat( "PNG" );
            TQBuffer fullStarBuf;
            fullStarBuf.open( IO_WriteOnly );
            fullStarIO.setIODevice( &fullStarBuf );
            fullStarIO.write();
            fullStarBuf.close();
            TQCString fullStar = KCodecs::base64Encode( fullStarBuf.buffer(), true );

            const TQString img = "<img src='%1' height='13px' class='ratingStar'></img>\n";
            for( int i = 0, n = rating / 2; i < n; ++i )
                contents += img.arg( TQString("data:image/png;base64," + fullStar) );
            if( rating % 2 )
            {
                TQImageIO halfStarIO;
                halfStarIO.setImage( StarManager::instance()->getHalfStarImage( half ? rating/2 + 1 : rating/2 ) );
                halfStarIO.setFormat( "PNG" );
                TQBuffer halfStarBuf;
                halfStarBuf.open( IO_WriteOnly );
                halfStarIO.setIODevice( &halfStarBuf );
                halfStarIO.write();
                halfStarBuf.close();
                TQCString halfStar = KCodecs::base64Encode( halfStarBuf.buffer(), true );
                contents += img.arg( TQString("data:image/png;base64," + halfStar) );
            }
            contents += "</nobr>\n";
        }
        else
            contents += i18n( "Not rated" );
        contents += "</td>\n"
                    "</tr>\n";
    }

    return table.arg( contents );
}

bool CurrentTrackJob::doJob()
{
    m_HTMLSource.append( "<html><body>\n"
                    "<script type='text/javascript'>\n"
                      //Toggle visibility of a block. NOTE: if the block ID starts with the T
                      //letter, 'Table' display will be used instead of the 'Block' one.
                      "function toggleBlock(ID) {"
                        "if ( document.getElementById(ID).style.display != 'none' ) {"
                          "document.getElementById(ID).style.display = 'none';"
                        "} else {"
                          "if ( ID[0] != 'T' ) {"
                            "document.getElementById(ID).style.display = 'block';"
                          "} else {"
                            "document.getElementById(ID).style.display = 'table';"
                          "}"
                        "}"
                      "}"
                    "</script>\n" );

    if( !b->m_browseArtists )
    {
        if( !EngineController::engine()->loaded() )
        {
            showHome();
            return true;
        }
        MetaBundle mb( m_currentTrack.url() );
        if( mb.podcastBundle() )
        {
            showPodcast( mb );
            return true;
        }

        if( m_currentTrack.url().protocol() == "lastfm" )
        {
            showLastFm( m_currentTrack );
            return true;
        }

        if( m_isStream && m_currentTrack.url().protocol() != "daap" )
        {
            showStream( m_currentTrack );
            return true;
        }
    }

    TQString artist;
    if( b->m_browseArtists )
    {
        artist = b->m_artist;
        if( artist == m_currentTrack.artist() )
        {
            b->m_browseArtists = false;
            b->m_artist = TQString();
            b->m_contextBackHistory.clear();
            b->m_contextBackHistory.push_back( "current://track" );
        }
    }
    else
        artist = m_currentTrack.artist();

    const uint artist_id = CollectionDB::instance()->artistID( artist );
    const uint album_id  = CollectionDB::instance()->albumID ( m_currentTrack.album() );
    QueryBuilder qb;
    TQStringList values;
    if( b->m_browseArtists )
        showBrowseArtistHeader( artist );
    else if( b->m_browseLabels )
    {
        showBrowseLabelHeader( b->m_label );
        showSongsWithLabel( b->m_label );
        m_HTMLSource.append( "</body></html>\n" );

        return true;
    }
    else
        showCurrentArtistHeader( m_currentTrack );

    if ( ContextBrowser::instance()->m_showLabels && !b->m_browseArtists )
        showUserLabels( m_currentTrack );

    if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested )
    {
        TQStringList relArtists = CollectionDB::instance()->similarArtists( artist, 10 );
        if ( !relArtists.isEmpty() )
        {
            if( ContextBrowser::instance()->m_showRelated )
                showRelatedArtists( artist, relArtists );

            if( ContextBrowser::instance()->m_showSuggested )
                showSuggestedSongs( relArtists );
        }
    }

    TQString artistName = artist.isEmpty() ? i18n( "This Artist" ) : artist ;
    if ( !artist.isEmpty() )
    {
        if( ContextBrowser::instance()->m_showFaves )
            showArtistsFaves( artistName, artist_id );

        showArtistsAlbums( artist, artist_id, album_id );
        showArtistsCompilations( artist, artist_id, album_id );
    }
    m_HTMLSource.append( "</body></html>\n" );

    return true;
}


void ContextBrowser::showIntroduction()
{
    if ( currentPage() != m_contextTab )
    {
        blockSignals( true );
        showPage( m_contextTab );
        blockSignals( false );
    }

    // Do we have to rebuild the page? I don't care
    m_HTMLSource = TQString();
    m_HTMLSource.append(
            "<html><body>\n"
            "<div id='introduction_box' class='box'>\n"
                "<div id='introduction_box-header' class='box-header'>\n"
                    "<span id='introduction_box-header-title' class='box-header-title'>\n"
                    + i18n( "Hello Amarok user!" ) +
                    "</span>\n"
                "</div>\n"
                "<div id='introduction_box-body' class='box-body'>\n"
                    "<div class='info'><p>\n" +
                    i18n( "This is the Context Browser: "
                          "it shows you contextual information about the currently playing track. "
                          "In order to use this feature of Amarok, you need to build a Collection."
                        ) +
                    "</p></div>\n"
                    "<div align='center'>\n"
                    "<input type='button' onClick='window.location.href=\"show:collectionSetup\";' value='" +
                    i18n( "Build Collection..." ) +
                    "'></div><br />\n"
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
                       );

    m_currentTrackPage->set( m_HTMLSource );
    saveHtmlData(); // Send html code to file
}


void ContextBrowser::showScanning()
{
    if ( currentPage() != m_contextTab )
    {
        blockSignals( true );
        showPage( m_contextTab );
        blockSignals( false );
    }

    // Do we have to rebuild the page? I don't care
    m_HTMLSource="";
    m_HTMLSource.append(
            "<html><body>\n"
            "<div id='building_box' class='box'>\n"
                "<div id='building_box-header' class='box-header'>\n"
                    "<span id='building_box-header-title' class='box-header-title'>\n"
                    + i18n( "Building Collection Database..." ) +
                    "</span>\n"
                "</div>\n"
                "<div id='building_box-body' class='box-body'>\n"
                    "<div class='info'><p>\n" + i18n( "Please be patient while Amarok scans your music collection. You can watch the progress of this activity in the statusbar." ) + "</p></div>\n"
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
                       );

    m_currentTrackPage->set( m_HTMLSource );
    saveHtmlData(); // Send html code to file
}

TQString
ContextBrowser::getEncodedImage( const TQString &imageUrl )
{
    // Embed cover image in html (encoded string), to get around tdehtml's caching
    //debug() << "Encoding imageUrl: " << imageUrl << endl;
    tqApp->lock();
    const TQImage img( imageUrl, "PNG" );
    tqApp->unlock();
    TQByteArray ba;
    TQBuffer buffer( ba );
    buffer.open( IO_WriteOnly );
    tqApp->lock();
    img.save( &buffer, "PNG" ); // writes image into ba in PNG format
    tqApp->unlock();
    const TQString coverImage = TQString( "data:image/png;base64,%1" ).arg( KCodecs::base64Encode( ba ).data() );
    //debug() << "Encoded imageUrl: " << coverImage << endl;
    return coverImage;
}

//////////////////////////////////////////////////////////////////////////////////////////
// Lyrics-Tab
//////////////////////////////////////////////////////////////////////////////////////////

void ContextBrowser::showLyrics( const TQString &url )
{
    #if 0
    if( BrowserBar::instance()->currentBrowser() != this )
    {
        debug() << "current browser is not context, aborting showLyrics()" << endl;
        m_dirtyLyricsPage = true;
        return;
    }
    #endif

    DEBUG_BLOCK

    if ( currentPage() != m_lyricsTab )
    {
        blockSignals( true );
        showPage( m_lyricsTab );
        blockSignals( false );
    }
    if ( !m_dirtyLyricsPage ) return;

    TQString lyrics = CollectionDB::instance()->getLyrics( EngineController::instance()->bundle().url().path() );
    // don't rely on caching for streams
    const bool cached = !lyrics.isEmpty() && !EngineController::engine()->isStream();
    TQString title  = EngineController::instance()->bundle().title();
    TQString artist = EngineController::instance()->bundle().artist();

    if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
        title = title.remove(" (PREVIEW: buy it at www.magnatune.com)");
    if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
        artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)");

    if ( title.isEmpty() ) {
        /* If title is empty, try to use pretty title.
           The fact that it often (but not always) has artist name together, can be bad,
           but at least the user will hopefully get nice suggestions. */
        TQString prettyTitle = EngineController::instance()->bundle().prettyTitle();
        int h = prettyTitle.find( '-' );
        if ( h != -1 )
        {
            title = prettyTitle.mid( h+1 ).stripWhiteSpace();
            if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
                title = title.remove(" (PREVIEW: buy it at www.magnatune.com)");
            if ( artist.isEmpty() ) {
                artist = prettyTitle.mid( 0, h ).stripWhiteSpace();
                if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
                    artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)");
            }

        }
    }

    m_lyricSearchUrl = TQString( "http://www.google.com/search?ie=UTF-8&q=lyrics+%1+%2" )
        .arg( KURL::encode_string_no_slash( '"' + artist + '"', 106 /*utf-8*/ ),
              KURL::encode_string_no_slash( '"' + title  + '"', 106 /*utf-8*/ ) );

    m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled(false);

    if( ( !cached || url == "reload" ) && !ScriptManager::instance()->lyricsScriptRunning() ) {
        const TQStringList scripts = ScriptManager::instance()->lyricsScripts();
        lyrics =
              i18n( "Sorry, no lyrics script running.") + "<br />\n" +
              "<br /><div class='info'>\n"+
              i18n( "Available Lyrics Scripts:" ) + "<br />\n";
        foreach ( scripts ) {
            lyrics += TQString( "<a href=\"runscript:%1\">%2</a><br />\n" ).arg( *it, *it );
        }
        lyrics += "<br />\n" + i18n( "Click on one of the scripts to run it, or use the Script Manager, to be able"
                        " to see all the scripts, and download new ones from the Web." );
        lyrics += "<br /><div align='center'>\n"
                  "<form><input type='button' onClick=\"window.location='show:scriptmanager'\" value='" +
                    i18n( "Run Script Manager..." ) +
                  "'></form></div><br /></div>\n";

        m_HTMLSource = TQString (
            "<html><body>\n"
            "<div id='lyrics_box' class='box'>\n"
                "<div id='lyrics_box-header' class='box-header'>\n"
                    "<span id='lyrics_box-header-title' class='box-header-title'>\n"
                    + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) +
                    "</span>\n"
                "</div>\n"
                "<div id='lyrics_box-body' class='box-body'>\n"
                    + lyrics +
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
            );
        m_lyricsPage->set( m_HTMLSource );

        m_dirtyLyricsPage = false;
        saveHtmlData(); // Send html code to file

        return;
    }

    if( cached && url.isEmpty() )
    {
        lyricsResult( lyrics.utf8(), true );
    }
    else
    {
        m_HTMLSource = TQString (
            "<html><body>\n"
            "<div id='lyrics_box' class='box'>\n"
                "<div id='lyrics_box-header' class='box-header'>\n"
                    "<span id='lyrics_box-header-title' class='box-header-title'>\n"
                    + i18n( "Fetching Lyrics" ) +
                    "</span>\n"
                "</div>\n"
                "<div id='lyrics_box-body' class='box-body'>\n"
                    "<div class='info'><p>\n" + i18n( "Fetching Lyrics..." ) + "</p></div>\n"
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
            );
        m_lyricsPage->set( m_HTMLSource );
        saveHtmlData(); // Send html code to file


        if( url.isNull() || url == "reload" )
            ScriptManager::instance()->notifyFetchLyrics( artist, title );
        else
            ScriptManager::instance()->notifyFetchLyricsByUrl( url );
    }
}


void
ContextBrowser::lyricsResult( TQCString cXmlDoc, bool cached ) //SLOT
{
    TQDomDocument doc;
    TQString xmldoc = TQString::fromUtf8( cXmlDoc );
    if( !doc.setContent( xmldoc ) )
    {
        m_HTMLSource="";
        m_HTMLSource.append(
                "<html><body>\n"
                "<div id='lyrics_box' class='box'>\n"
                    "<div id='lyrics_box-header' class='box-header'>\n"
                        "<span id='lyrics_box-header-title' class='box-header-title'>\n"
                        + i18n( "Error" ) +
                        "</span>\n"
                    "</div>\n"
                    "<div id='lyrics_box-body' class='box-body'><p>\n"
                        + i18n( "Lyrics could not be retrieved because the server was not reachable." ) +
                    "</p></div>\n"
                "</div>\n"
                "</body></html>\n"
                        );
        m_lyricsPage->set( m_HTMLSource );
        saveHtmlData(); // Send html code to file

        m_dirtyLyricsPage = false;

        return;
    }

    TQString lyrics;

    TQDomElement el = doc.documentElement();
    m_lyricCurrentUrl = el.attribute( "page_url" );

    ScriptManager* const sm = ScriptManager::instance();
    TDEConfig spec( sm->specForScript( sm->lyricsScriptRunning() ), true, false );
    spec.setGroup( "Lyrics" );

    if ( el.attribute( "add_url" ).isEmpty() )
    {
        m_lyricAddUrl = spec.readPathEntry( "add_url" );
        m_lyricAddUrl.replace( "MAGIC_ARTIST", KURL::encode_string_no_slash( EngineController::instance()->bundle().artist() ) );
        m_lyricAddUrl.replace( "MAGIC_TITLE", KURL::encode_string_no_slash( EngineController::instance()->bundle().title() ) );
        m_lyricAddUrl.replace( "MAGIC_ALBUM", KURL::encode_string_no_slash( EngineController::instance()->bundle().album() ) );
        m_lyricAddUrl.replace( "MAGIC_YEAR", KURL::encode_string_no_slash( TQString::number( EngineController::instance()->bundle().year() ) ) );
    }
    else
        m_lyricAddUrl = el.attribute( "add_url" );

    if ( el.tagName() == "suggestions" )
    {


        const TQDomNodeList l = doc.elementsByTagName( "suggestion" );

        if( l.length() ==0 )
        {
            lyrics = i18n( "Lyrics for track not found" );
        }
        else
        {
            lyrics = i18n( "Lyrics for track not found, here are some suggestions:" ) + "<br/><br/>\n";
            for( uint i = 0; i < l.length(); ++i ) {
                const TQString url    = l.item( i ).toElement().attribute( "url" );
                const TQString artist = l.item( i ).toElement().attribute( "artist" );
                const TQString title  = l.item( i ).toElement().attribute( "title" );

                lyrics += "<a href='show:suggestLyric-" + url + "'>\n" + i18n("%1 - %2").arg( artist, title );
                lyrics += "</a><br/>\n";
            }
        }
        lyrics += i18n( "<p>You can <a href=\"%1\">search for the lyrics</a> on the Web.</p>" )
                  .arg( TQString( m_lyricSearchUrl ).replace( TQRegExp( "^http:" ), "externalurl:" ) );
    }
    else {
        lyrics = el.text();
        lyrics.replace( "\n", "<br/>\n" ); // Plaintext -> HTML

        const TQString title    = el.attribute( "title" );
        const TQString artist   = el.attribute( "artist" );
        const TQString site     = el.attribute( "site" ).isEmpty() ? spec.readEntry( "site" ) : el.attribute( "site" );
        const TQString site_url = el.attribute( "site_url" ).isEmpty() ? spec.readEntry( "site_url" ) : el.attribute( "site_url" );

        lyrics.prepend( "<font size='2'><b>\n" + title + "</b><br/><u>\n" + artist+ "</font></u></font><br/>\n" );

        if( !cached ) {
            lyrics.append( "<br/><br/><i>\n" + i18n( "Powered by %1 (%2)" ).arg( site, site_url ) + "</i>\n" );
            CollectionDB::instance()->setLyrics( EngineController::instance()->bundle().url().path(), xmldoc, EngineController::instance()->bundle().uniqueId() );
        }
    }

    m_HTMLSource="";
    m_HTMLSource.append(
            "<html><body>\n"
            "<div id='lyrics_box' class='box'>\n"
                "<div id='lyrics_box-header' class='box-header'>\n"
                    "<span id='lyrics_box-header-title' class='box-header-title'>\n"
                    + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) +
                    "</span>\n"
                "</div>\n"
                "<div id='lyrics_box-body' class='box-body'>\n"
                    + lyrics +
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
    );


    m_lyricsPage->set( m_HTMLSource );
    //Reset scroll
    m_lyricsPage->view()->setContentsPos(0, 0);
    saveHtmlData(); // Send html code to file

    m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled( !m_lyricCurrentUrl.isEmpty() );
    m_dirtyLyricsPage = false;
}


void
ContextBrowser::lyricsExternalPage() //SLOT
{
    Amarok::invokeBrowser( m_lyricCurrentUrl );
}


void
ContextBrowser::lyricsAdd() //SLOT
{
    Amarok::invokeBrowser( m_lyricAddUrl );
}

void
ContextBrowser::lyricsEditToggle() //SLOT
{
    if ( m_lyricsToolBar->getButton( LYRICS_EDIT )->isOn() )
    {
        m_lyricsBeingEditedUrl    = EngineController::instance()->bundle().url().path();
        m_lyricsBeingEditedArtist = EngineController::instance()->bundle().artist();
        m_lyricsBeingEditedTitle  = EngineController::instance()->bundle().title();
        TQString xml = CollectionDB::instance()->getLyrics( m_lyricsBeingEditedUrl ), lyrics;
        TQDomDocument doc;
        if( doc.setContent( xml ) )
            lyrics = doc.documentElement().text();
        else
            lyrics = TQString();
        m_lyricsTextEdit->setText( lyrics );
        m_lyricsPage->hide();
        m_lyricsTextEdit->show();
    }
    else
    {
        m_lyricsTextEdit->hide();

        TQDomDocument doc;
        TQDomElement e = doc.createElement( "lyrics" );
        e.setAttribute( "artist", m_lyricsBeingEditedArtist );
        e.setAttribute( "title", m_lyricsBeingEditedTitle );
        TQDomText t = doc.createTextNode( m_lyricsTextEdit->text() );
        e.appendChild( t );
        doc.appendChild( e );
        CollectionDB::instance()->setLyrics( m_lyricsBeingEditedUrl, doc.toString(), CollectionDB::instance()->uniqueIdFromUrl( KURL( m_lyricsBeingEditedUrl) ) );
        m_lyricsPage->show();
        lyricsChanged( m_lyricsBeingEditedUrl );
    }
}

void
ContextBrowser::lyricsSearch() //SLOT
{
    Amarok::invokeBrowser( m_lyricSearchUrl );
}


void
ContextBrowser::lyricsRefresh() //SLOT
{
    m_dirtyLyricsPage = true;
    showLyrics( "reload" );
}

void
ContextBrowser::lyricsSearchText(TQString const &text) //SLOT
{
    m_lyricsPage->findText( text, 0 );
    lyricsSearchTextNext();
}

void
ContextBrowser::lyricsSearchTextNext() //SLOT
{
    m_lyricsPage->findTextNext();
}

void
ContextBrowser::lyricsSearchTextShow() //SLOT
{
    m_lyricsSearchText->setEnabled( true );
    m_lyricsTextBar->show();
    m_lyricsTextBarShowed = true;
    m_lyricsSearchText->setFocus();
}


void
ContextBrowser::lyricsSearchTextHide() //SLOT
{
    m_lyricsSearchText->setText("");
    m_lyricsSearchText->setEnabled( false );
    m_lyricsTextBar->hide();
    m_lyricsTextBarShowed=false;
}


void
ContextBrowser::lyricsSearchTextToggle() //SLOT
{
    if ( m_lyricsTextBarShowed )
    {
        lyricsSearchTextHide();
    }
    else
    {
        lyricsSearchTextShow();
    }
}

// Wikipedia-Tab
//////////////////////////////////////////////////////////////////////////////////////////

TQString
ContextBrowser::wikiArtistPostfix() const
{
    if( wikiLocale() == "en" )
        return " (band)";
    else if( wikiLocale() == "de" )
        return " (Band)";
    else
        return "";
}

TQString
ContextBrowser::wikiAlbumPostfix() const
{
    if( wikiLocale() == "en" )
        return " (album)";
    else
        return "";
}

TQString
ContextBrowser::wikiTrackPostfix() const
{
    if( wikiLocale() == "en" )
        return " (song)";
    else
        return "";
}

void
ContextBrowser::wikiConfigChanged( int /*activeItem*/ ) // SLOT
{
    // keep in sync with localeList in wikiConfig
    TQString text = m_wikiLocaleCombo->currentText();

    m_wikiLocaleEdit->setEnabled( text == i18n("Other...") );

    if( text == i18n("English") )
        m_wikiLocaleEdit->setText( "en" );

    else if( text == i18n("German") )
        m_wikiLocaleEdit->setText( "de" );

    else if( text == i18n("French") )
        m_wikiLocaleEdit->setText( "fr" );

    else if( text == i18n("Polish") )
        m_wikiLocaleEdit->setText( "pl" );

    else if( text == i18n("Japanese") )
        m_wikiLocaleEdit->setText( "ja" );

    else if( text == i18n("Spanish") )
        m_wikiLocaleEdit->setText( "es" );
}

void
ContextBrowser::wikiConfigApply() // SLOT
{
    const bool changed = m_wikiLocaleEdit->text() != wikiLocale();
    setWikiLocale( m_wikiLocaleEdit->text() );

    if ( changed && currentPage() == m_wikiTab && !m_wikiCurrentEntry.isNull() )
    {
        m_dirtyWikiPage = true;
        showWikipediaEntry( m_wikiCurrentEntry );
    }

    showWikipedia();
}

void
ContextBrowser::wikiConfig() // SLOT
{
    TQStringList localeList;
    localeList
        << i18n( "English" )
        << i18n( "German" )
        << i18n( "French" )
        << i18n( "Polish" )
        << i18n( "Japanese" )
        << i18n( "Spanish" )
        << i18n( "Other..." );

    int index;

    if( wikiLocale() == "en" )
        index = 0;
    else if( wikiLocale() == "de" )
        index = 1;
    else if( wikiLocale() == "fr" )
        index = 2;
    else if( wikiLocale() == "pl" )
        index = 3;
    else if( wikiLocale() == "ja" )
        index = 4;
    else if( wikiLocale() == "es" )
        index = 5;
    else // other
        index = 6;

    m_wikiConfigDialog = new KDialogBase( this, 0, true, 0, KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel );
    kapp->setTopWidget( m_wikiConfigDialog );
    m_wikiConfigDialog->setCaption( kapp->makeStdCaption( i18n( "Wikipedia Locale" ) ) );
    TQVBox *box = m_wikiConfigDialog->makeVBoxMainWidget();

    m_wikiLocaleCombo = new TQComboBox( box );
    m_wikiLocaleCombo->insertStringList( localeList );

    TQHBox  *hbox       = new TQHBox( box );
    TQLabel *otherLabel = new TQLabel( i18n( "Locale: " ), hbox );
    m_wikiLocaleEdit   = new TQLineEdit( "en", hbox );

    otherLabel->setBuddy( m_wikiLocaleEdit );
    TQToolTip::add( m_wikiLocaleEdit, i18n( "2-letter language code for your Wikipedia locale" ) );

    connect( m_wikiLocaleCombo,  TQ_SIGNAL( activated(int) ), TQ_SLOT( wikiConfigChanged(int) ) );
    connect( m_wikiConfigDialog, TQ_SIGNAL( applyClicked() ), TQ_SLOT( wikiConfigApply() ) );

    m_wikiLocaleEdit->setText( wikiLocale() );
    m_wikiLocaleCombo->setCurrentItem( index );
    wikiConfigChanged( index ); // a little redundant, but saves ugly code, and ensures the lineedit enabled status is correct

    m_wikiConfigDialog->setInitialSize( TQSize( 240, 100 ) );
    const int result = m_wikiConfigDialog->exec();


    if( result == TQDialog::Accepted )
        wikiConfigApply();

    delete m_wikiConfigDialog;
}

TQString
ContextBrowser::wikiLocale()
{
    if( s_wikiLocale.isEmpty() )
        return TQString( "en" );

    return s_wikiLocale;
}

void
ContextBrowser::setWikiLocale( const TQString &locale )
{
    AmarokConfig::setWikipediaLocale( locale );
    s_wikiLocale = locale;
}

TQString
ContextBrowser::wikiURL( const TQString &item )
{
    // add any special characters to be replaced here
    TQString wStr = TQString(item).replace( "/", " " );

    return TQString( "http://%1.wikipedia.org/wiki/" ).arg( wikiLocale() )
        + KURL::encode_string_no_slash( wStr, 106 /*utf-8*/ );
}

void
ContextBrowser::reloadWikipedia()
{
    m_wikiJob = NULL;
    showWikipediaEntry( m_wikiCurrentEntry, true );
}

void
ContextBrowser::showWikipediaEntry( const TQString &entry, bool replaceHistory )
{
    m_wikiCurrentEntry = entry;
    showWikipedia( wikiURL( entry ), false, replaceHistory );
}

void
ContextBrowser::showLabelsDialog()
{
    DEBUG_BLOCK
    KURL currentUrl = EngineController::instance()->bundle().url();
    TQStringList allLabels = CollectionDB::instance()->labelList();
    TQStringList trackLabels = CollectionDB::instance()->getLabels( currentUrl.path(), CollectionDB::typeUser );
    debug() << "Showing add label dialog" << endl;
    KDialogBase *dialog = new KDialogBase( this, 0, false, TQString(), KDialogBase::Ok|KDialogBase::Cancel );
    dialog->makeVBoxMainWidget();

    TQLabel *labelText = new TQLabel( i18n( 
		"<p>Add a new label in the field below and press Enter, or choose labels from the list</p>"),
	    			dialog->mainWidget() );
    m_addLabelEdit = new ClickLineEdit( i18n( "Add new label" ), dialog->mainWidget() );
    m_addLabelEdit->installEventFilter( this );
    m_addLabelEdit->setFrame( TQFrame::Sunken );
    TQToolTip::add( m_addLabelEdit, i18n( "Enter a new label and press Return to add it" ) );
    dialog->setFocusProxy( m_addLabelEdit );
    labelText->setBuddy( m_addLabelEdit );

    m_labelListView = new TQListView( dialog->mainWidget() );
    m_labelListView->addColumn( i18n( "Label" ) );
    m_labelListView->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding );
    m_labelListView->setColumnWidthMode( 0, TQListView::Maximum );

    foreach( allLabels )
    {
        TQCheckListItem *item = new TQCheckListItem( m_labelListView, *it, TQCheckListItem::CheckBox );
        item->setOn( trackLabels.contains( *it ) );
    }
    if( dialog->exec() == TQDialog::Accepted )
    {
        debug() << "Dialog closed, updating labels" << endl;
        TQStringList newTrackLabels;
        TQListViewItemIterator iter( m_labelListView );
        while( iter.current() )
        {
            TQCheckListItem *item = static_cast<TQCheckListItem*>( iter.current() );
            if( item->isOn() )
                newTrackLabels.append( item->text() );
            iter++;
        }
        CollectionDB::instance()->setLabels( currentUrl.path(),
                                             newTrackLabels,
                                             CollectionDB::instance()->uniqueIdFromUrl( currentUrl ),
                                             CollectionDB::typeUser );
        CollectionDB::instance()->cleanLabels();
        if( newTrackLabels != trackLabels
                && currentUrl == EngineController::instance()->bundle().url() )
        {
            m_dirtyCurrentTrackPage = true;
            showCurrentTrack();
        }
    }
    delete dialog; //deletes children
    m_addLabelEdit = 0;
    m_labelListView = 0;
}

bool
ContextBrowser::eventFilter( TQObject *o, TQEvent *e )
{
    switch( e->type() )
    {
    case 6/*TQEvent::KeyPress*/:
        #define e static_cast<TQKeyEvent*>(e)

        if( o == m_addLabelEdit ) //the add label lineedit
        {
            switch( e->key() )
            {
            case Key_Return:
            case Key_Enter:
            {
                TQCheckListItem *item = new TQCheckListItem( m_labelListView, m_addLabelEdit->text(), TQCheckListItem::CheckBox );
                item->setOn( true );
                m_addLabelEdit->setText( TQString() );
                return true;
            }

            default:
                return false;
            }
        }

	if (o == m_lyricsSearchText)
	{
	   switch ( e->key() )
	   {
	   case Key_Escape:
	   {
	   	lyricsSearchTextHide();
		return true;
	   }
	   default:
	       return false;
	   }
       }

    default:
        break;
    }

    return KTabWidget::eventFilter( o, e );
}

void ContextBrowser::showWikipedia( const TQString &url, bool fromHistory, bool replaceHistory )
{
    #if 0
    if( BrowserBar::instance()->currentBrowser() != this )
    {
        debug() << "current browser is not context, aborting showWikipedia()" << endl;
        m_dirtyWikiPage = true;
        return;
    }
    #endif

    if ( currentPage() != m_wikiTab )
    {
        blockSignals( true );
        showPage( m_wikiTab );
        blockSignals( false );
    }
    if ( !m_dirtyWikiPage || m_wikiJob ) return;

    // Disable the Open in a Browser button, because while loading it would open wikipedia main page.
    m_wikiToolBar->setItemEnabled( WIKI_BROWSER, false );

    m_HTMLSource="";
    m_HTMLSource.append(
            "<html><body>\n"
            "<div id='wiki_box' class='box'>\n"
                "<div id='wiki_box-header' class='box-header'>\n"
                    "<span id='wiki_box-header-title' class='box-header-title'>\n"
                    + i18n( "Wikipedia" ) +
                    "</span>\n"
                "</div>\n"
                "<div id='wiki_box-body' class='box-body'>\n"
                    "<div class='info'><p>\n" + i18n( "Fetching Wikipedia Information" ) + " ...</p></div>\n"
                "</div>\n"
            "</div>\n"
            "</body></html>\n"
                    );

    m_wikiPage->set( m_HTMLSource );
    saveHtmlData(); // Send html code to file

    if ( url.isEmpty() )
    {
        TQString tmpWikiStr;

        if ( (EngineController::instance()->bundle().url().protocol() == "lastfm") ||
             (EngineController::instance()->bundle().url().protocol() == "daap") ||
             !EngineController::engine()->isStream() )
        {
            if ( !EngineController::instance()->bundle().artist().isEmpty() )
            {
                tmpWikiStr = EngineController::instance()->bundle().artist();
                tmpWikiStr += wikiArtistPostfix();
            }
            else if ( !EngineController::instance()->bundle().title().isEmpty() )
            {
                tmpWikiStr = EngineController::instance()->bundle().title();
            }
            else
            {
                tmpWikiStr = EngineController::instance()->bundle().prettyTitle();
            }
        }
        else
        {
            tmpWikiStr = EngineController::instance()->bundle().prettyTitle();
        }

        //Hack to make wiki searches work with magnatune preview tracks
        if (tmpWikiStr.contains( "PREVIEW: buy it at www.magnatune.com" ) >= 1 ) {
            tmpWikiStr = tmpWikiStr.remove(" (PREVIEW: buy it at www.magnatune.com)" );
            int index = tmpWikiStr.find( '-' );
            if ( index != -1 ) {
                tmpWikiStr = tmpWikiStr.left (index - 1);
            }

        }
        m_wikiCurrentEntry = tmpWikiStr;

        m_wikiCurrentUrl = wikiURL( tmpWikiStr );
    }
    else
    {
        m_wikiCurrentUrl = url;
    }

    // Append new URL to history
    if ( replaceHistory )
    {
        m_wikiBackHistory.back() = m_wikiCurrentUrl;
    }
    else if ( !fromHistory ) {
        m_wikiBackHistory += m_wikiCurrentUrl;
        m_wikiForwardHistory.clear();
    }
    // Limit number of items in history
    if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY )
        m_wikiBackHistory.pop_front();

    // Remove all items from the button-menus
    m_wikiBackPopup->clear();
    m_wikiForwardPopup->clear();

    // Populate button menus with URLs from the history
    TQStringList::ConstIterator it;
    uint count;
    // Reverse iterate over both lists
    count = m_wikiBackHistory.count()-1;
    it = m_wikiBackHistory.fromLast();
    if( count > 0 )
        it--;
    for ( uint i=0; i<count; i++, --it )
        m_wikiBackPopup->insertItem( SmallIconSet( "wiki" ), *it, i );
    count = m_wikiForwardHistory.count();
    it = m_wikiForwardHistory.fromLast();
    for ( uint i=0; i<count; i++, --it )
        m_wikiForwardPopup->insertItem( SmallIconSet( "wiki" ), *it, i );

    m_wikiToolBar->setItemEnabled( WIKI_BACK, m_wikiBackHistory.size() > 1 );
    m_wikiToolBar->setItemEnabled( WIKI_FORWARD, m_wikiForwardHistory.size() > 0 );

    m_wikiBaseUrl = m_wikiCurrentUrl.mid(0 , m_wikiCurrentUrl.find("wiki/"));
    m_wikiJob = TDEIO::storedGet( m_wikiCurrentUrl, false, false );

    Amarok::StatusBar::instance()->newProgressOperation( m_wikiJob )
            .setDescription( i18n( "Fetching Wikipedia Information" ) );

    connect( m_wikiJob, TQ_SIGNAL( result( TDEIO::Job* ) ), TQ_SLOT( wikiResult( TDEIO::Job* ) ) );
}


void
ContextBrowser::wikiHistoryBack() //SLOT
{
    //Disable the button as history may be empty. Reenabled later by showWikipedia.
    m_wikiToolBar->setItemEnabled( WIKI_BACK, false );
    m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false );

    m_wikiForwardHistory += m_wikiBackHistory.last();
    m_wikiBackHistory.pop_back();

    m_dirtyWikiPage = true;
    m_wikiCurrentEntry = TQString();
    showWikipedia( m_wikiBackHistory.last(), true );
}


void
ContextBrowser::wikiHistoryForward() //SLOT
{
    //Disable the button as history may be empty. Reenabled later by showWikipedia.
    m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false );
    m_wikiToolBar->setItemEnabled( WIKI_BACK, false );

    m_wikiBackHistory += m_wikiForwardHistory.last();
    m_wikiForwardHistory.pop_back();

    m_dirtyWikiPage = true;
    m_wikiCurrentEntry = TQString();
    showWikipedia( m_wikiBackHistory.last(), true );
}


void
ContextBrowser::wikiBackPopupActivated( int id ) //SLOT
{
    do
    {
        m_wikiForwardHistory += m_wikiBackHistory.last();
        m_wikiBackHistory.pop_back();
        if ( m_wikiForwardHistory.count() > WIKI_MAX_HISTORY )
            m_wikiForwardHistory.pop_front();
        id--;
    } while( id >= 0 );

    m_dirtyWikiPage = true;
    m_wikiCurrentEntry = TQString();
    showWikipedia( m_wikiBackHistory.last(), true );
}

void
ContextBrowser::wikiForwardPopupActivated( int id ) //SLOT
{
    do
    {
        m_wikiBackHistory += m_wikiForwardHistory.last();
        m_wikiForwardHistory.pop_back();
        if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY )
            m_wikiBackHistory.pop_front();

        id--;
    } while( id >= 0 );

    m_dirtyWikiPage = true;
    m_wikiCurrentEntry = TQString();
    showWikipedia( m_wikiBackHistory.last(), true );
}


void
ContextBrowser::wikiArtistPage() //SLOT
{
    m_dirtyWikiPage = true;
    showWikipedia(); // Will fall back to title, if artist is empty(streams!).
}


void
ContextBrowser::wikiAlbumPage() //SLOT
{
    m_dirtyWikiPage = true;
    showWikipediaEntry( EngineController::instance()->bundle().album() + wikiAlbumPostfix() );
}


void
ContextBrowser::wikiTitlePage() //SLOT
{
    m_dirtyWikiPage = true;
    showWikipediaEntry( EngineController::instance()->bundle().title() + wikiTrackPostfix() );
}


void
ContextBrowser::wikiExternalPage() //SLOT
{
    Amarok::invokeBrowser( m_wikiCurrentUrl );
}


void
ContextBrowser::wikiResult( TDEIO::Job* job ) //SLOT
{
    DEBUG_BLOCK

    if ( !job->error() == 0 )
    {
        m_HTMLSource="";
        m_HTMLSource.append(
            "<div id='wiki_box' class='box'>\n"
                "<div id='wiki_box-header' class='box-header'>\n"
                    "<span id='wiki_box-header-title' class='box-header-title'>\n"
                    + i18n( "Error" ) +
                    "</span>\n"
                "</div>\n"
                "<div id='wiki_box-body' class='box-body'><p>\n"
                    + i18n( "Artist information could not be retrieved because the server was not reachable." ) +
                "</p></div>\n"
            "</div>\n"
                        );
        m_wikiPage->set( m_HTMLSource );

        m_dirtyWikiPage = false;
        //m_wikiPage = NULL; // FIXME: what for? leads to crashes
        saveHtmlData(); // Send html code to file

        warning() << "[WikiFetcher] TDEIO error! errno: " << job->error() << endl;
        return;
    }
    if ( job != m_wikiJob )
        return; //not the right job, so let's ignore it

    TDEIO::StoredTransferJob* const storedJob = static_cast<TDEIO::StoredTransferJob*>( job );
    m_wiki = TQString( storedJob->data() );

    // Enable the Open in a Brower button, Disabled while loading, guz it would open wikipedia main page.
    m_wikiToolBar->setItemEnabled( WIKI_BROWSER, true );

    // FIXME: Get a safer Regexp here, to match only inside of <head> </head> at least.
    if ( m_wiki.contains( "charset=utf-8", FALSE ) ) {
         m_wiki = TQString::fromUtf8( storedJob->data().data(), storedJob->data().size() );
    }

    if( m_wiki.find( "var wgArticleId = 0" ) != -1 )
    {
        debug() << "Article not found." << endl;
        
        // article was not found
        if( !wikiArtistPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiArtistPostfix() ) )
        {
            m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiArtistPostfix().length() );
            reloadWikipedia();
            return;
        }
        else if( !wikiAlbumPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiAlbumPostfix() ) )
        {
            m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiAlbumPostfix().length() );
            reloadWikipedia();
            return;
        }
        else if( !wikiTrackPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiTrackPostfix() ) )
        {
            m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiTrackPostfix().length() );
            reloadWikipedia();
            return;
        }
    }

    //remove the new-lines and tabs(replace with spaces IS needed).
    m_wiki.replace( "\n", " " );
    m_wiki.replace( "\t", " " );

    m_wikiLanguages = TQString();
    // Get the available language list
    if ( m_wiki.find("<div id=\"p-lang\" class=\"portlet\">") != -1 )
    {
        m_wikiLanguages = m_wiki.mid( m_wiki.find("<div id=\"p-lang\" class=\"portlet\">") );
        m_wikiLanguages = m_wikiLanguages.mid( m_wikiLanguages.find("<ul>") );
        m_wikiLanguages = m_wikiLanguages.mid( 0, m_wikiLanguages.find( "</div>" ) );
    }

    TQString copyright;
    TQString copyrightMark = "<li id=\"f-copyright\">";
    if ( m_wiki.find( copyrightMark ) != -1 )
    {
        copyright = m_wiki.mid( m_wiki.find(copyrightMark) + copyrightMark.length() );
        copyright = copyright.mid( 0, copyright.find( "</li>" ) );
        copyright.replace( "<br />", TQString() );
        //only one br at the beginning
        copyright.prepend( "<br />" );
    }

    // Ok lets remove the top and bottom parts of the page
    m_wiki = m_wiki.mid( m_wiki.find( "<h1 id=\"firstHeading\" class=\"firstHeading\">" ) );
    m_wiki = m_wiki.mid( 0, m_wiki.find( "<div class=\"printfooter\">" ) );
    // Adding back license information
    m_wiki += copyright;
    m_wiki.append( "</div>" );
    m_wiki.replace( TQRegExp("<h3 id=\"siteSub\">[^<]*</h3>"), TQString() );

    m_wiki.replace( TQRegExp( "<span class=\"editsection\"[^>]*>[^<]*<[^>]*>[^<]*<[^>]*>[^<]*</span>" ), TQString() );

    m_wiki.replace( TQRegExp( "<a href=\"[^\"]*\" class=\"new\"[^>]*>([^<]*)</a>" ), "\\1" );

    // Remove anything inside of a class called urlexpansion, as it's pointless for us
    m_wiki.replace( TQRegExp( "<span class= *'urlexpansion'>[^(]*[(][^)]*[)]</span>" ), TQString() );

    // Remove hidden table rows as well
    TQRegExp hidden( "<tr *class= *[\"\']hiddenStructure[\"\']>.*</tr>", false );
    hidden.setMinimal( true ); //greedy behaviour wouldn't be any good!
    m_wiki.replace( hidden, TQString() );

    // we want to keep our own style (we need to modify the stylesheet a bit to handle things nicely)
    m_wiki.replace( TQRegExp( "style= *\"[^\"]*\"" ), TQString() );
    m_wiki.replace( TQRegExp( "class= *\"[^\"]*\"" ), TQString() );
    // let's remove the form elements, we don't want them.
    m_wiki.replace( TQRegExp( "<input[^>]*>" ), TQString() );
    m_wiki.replace( TQRegExp( "<select[^>]*>" ), TQString() );
    m_wiki.replace( "</select>\n" , TQString() );
    m_wiki.replace( TQRegExp( "<option[^>]*>" ), TQString() );
    m_wiki.replace( "</option>\n" , TQString() );
    m_wiki.replace( TQRegExp( "<textarea[^>]*>" ), TQString() );
    m_wiki.replace( "</textarea>" , TQString() );

    //first we convert all the links with protocol to external, as they should all be External Links.
    m_wiki.replace( TQRegExp( "href= *\"http:" ), "href=\"externalurl:" );
    m_wiki.replace( TQRegExp( "href= *\"/" ), "href=\"" +m_wikiBaseUrl );
    m_wiki.replace( TQRegExp( "href= *\"#" ), "href=\"" +m_wikiCurrentUrl + '#' );

    m_HTMLSource = "<html><body>\n";
    m_HTMLSource.append(
            "<div id='wiki_box' class='box'>\n"
                "<div id='wiki_box-header' class='box-header'>\n"
                    "<span id='wiki_box-header-title' class='box-header-title'>\n"
                    + i18n( "Wikipedia Information" ) +
                    "</span>\n"
                "</div>\n"
                "<div id='wiki_box-body' class='box-body'>\n"
                    + m_wiki +
                "</div>\n"
            "</div>\n"
                       );
    if ( !m_wikiLanguages.isEmpty() )
    {
        m_HTMLSource.append(
                "<div id='wiki_box' class='box'>\n"
                    "<div id='wiki_box-header' class='box-header'>\n"
                        "<span id='wiki_box-header-title' class='box-header-title'>\n"
                        + i18n( "Wikipedia Other Languages" ) +
                        "</span>\n"
                    "</div>\n"
                    "<div id='wiki_box-body' class='box-body'>\n"
                        + m_wikiLanguages +
                    "</div>\n"
                "</div>\n"
        );
    }
    m_HTMLSource.append( "</body></html>\n" );
    m_wikiPage->set( m_HTMLSource );

    m_dirtyWikiPage = false;
    saveHtmlData(); // Send html code to file
    m_wikiJob = NULL;
}


void
ContextBrowser::coverFetched( const TQString &artist, const TQString &album ) //SLOT
{
    if ( currentPage() == m_contextTab &&
            !EngineController::engine()->loaded() &&
            !m_browseArtists )
    {
        m_dirtyCurrentTrackPage = true;
        if( m_shownAlbums.contains( album ) )
            showCurrentTrack();
        return;
    }

    const MetaBundle &currentTrack = EngineController::instance()->bundle();
    if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
        return;

    if ( currentPage() == m_contextTab &&
       ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty
    {
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
    }
}


void
ContextBrowser::coverRemoved( const TQString &artist, const TQString &album ) //SLOT
{
    if ( currentPage() == m_contextTab &&
            !EngineController::engine()->loaded() &&
            !m_browseArtists )
    {
        m_dirtyCurrentTrackPage = true;
        if( m_shownAlbums.contains( album ) )
            showCurrentTrack();
        return;
    }

    const MetaBundle &currentTrack = EngineController::instance()->bundle();
    if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() && m_artist.isNull() )
        return;

    if ( currentPage() == m_contextTab &&
       ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty
    {
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
    }
}


void
ContextBrowser::similarArtistsFetched( const TQString &artist ) //SLOT
{
    if( artist == m_artist || EngineController::instance()->bundle().artist().string() == artist ) {
        m_dirtyCurrentTrackPage = true;
        if ( currentPage() == m_contextTab )
            showCurrentTrack();
    }
}

void
ContextBrowser::imageFetched( const TQString &url ) //SLOT
{
    const MetaBundle &currentTrack = EngineController::instance()->bundle();
    PodcastEpisodeBundle peb;
    if( CollectionDB::instance()->getPodcastEpisodeBundle( currentTrack.url(), &peb ) )
    {
        PodcastChannelBundle pcb;
        if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) )
        {
            if( pcb.imageURL().url() == url )
            {
                m_dirtyCurrentTrackPage = true;
                showCurrentTrack();
            }
        }
    }
}

void ContextBrowser::ratingOrScoreOrLabelsChanged( const TQString &path ) //SLOT
{
    const MetaBundle &currentTrack = EngineController::instance()->bundle();

    //Always refresh if using ratings, otherwise suggested songs and other songs by artist that
    //have their ratings changed in the playlist won't be reflected until the context browser refreshes
    //which can be confusing, and looks less polished/professional
    //This can be changed if it slows things down too much...
    if( m_browseLabels || ( currentTrack.isFile() && ( currentTrack.url().path() == path || AmarokConfig::useRatings() ) ) ) {
        m_dirtyCurrentTrackPage = true; // will be reloaded when viewed (much faster)
    }
    if( currentPage() == m_contextTab ) {
        refreshCurrentTrackPage();
    }
}

void ContextBrowser::tagsChanged( const MetaBundle &bundle ) //SLOT
{
    const MetaBundle &currentTrack = EngineController::instance()->bundle();

    if( !m_shownAlbums.contains( bundle.album() ) && m_artist != bundle.artist() )
    {
        if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
            return;

        if( bundle.artist() != currentTrack.artist() && bundle.album() != currentTrack.album() )
            return;
    }

    refreshCurrentTrackPage();
}

void ContextBrowser::tagsChanged( const TQString &oldArtist, const TQString &oldAlbum ) //SLOT
{
    const MetaBundle &currentTrack = EngineController::instance()->bundle();

    if( !m_shownAlbums.contains( oldAlbum ) && m_artist != oldArtist )
    {
        if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
            return;

        if( oldArtist != currentTrack.artist() && oldAlbum != currentTrack.album() )
            return;
    }

    refreshCurrentTrackPage();
}


void ContextBrowser::refreshCurrentTrackPage() //SLOT
{
    if ( currentPage() == m_contextTab ) // this is for compilations or when artist is empty
    {
        m_dirtyCurrentTrackPage = true;
        showCurrentTrack();
    }
}


bool
ContextBrowser::hasContextProtocol( const KURL &url )
{
    TQString protocol = url.protocol();
    return protocol == "album"
        || protocol == "artist"
        || protocol == "stream"
        || protocol == "compilation"
        || protocol == "albumdisc"
        || protocol == "compilationdisc"
        || protocol == "fetchcover";
}

KURL::List
ContextBrowser::expandURL( const KURL &url )
{
    KURL::List urls;
    TQString protocol = url.protocol();

    if( protocol == "artist" ) {
        uint artist_id = CollectionDB::instance()->artistID( url.path(), false );
        if( artist_id )
        {
            TQStringList trackUrls = CollectionDB::instance()->artistTracks( TQString::number( artist_id ) );
            foreach( trackUrls )
                urls += KURL::fromPathOrURL( *it );
        }
    }
    else if( protocol == "album" ) {
        TQString artist, album, track; // track unused here
        Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );

        TQStringList trackUrls = CollectionDB::instance()->albumTracks( artist, album );
        foreach( trackUrls ) {
            urls += KURL::fromPathOrURL( *it );
        }
    }
    else if( protocol == "albumdisc" ) {
        TQString artist, album, discnumber; // discnumber is returned in track number field
        Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber );

        TQStringList trackUrls = CollectionDB::instance()->albumDiscTracks( artist, album, discnumber );
        foreach( trackUrls ) {
            urls += KURL::fromPathOrURL( *it );
        }
    }
    else if( protocol == "compilation" ) {
        QueryBuilder qb;
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
        qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, url.path() );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
        qb.setOptions( QueryBuilder::optOnlyCompilations );
        TQStringList values = qb.run();

        for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) {
            urls += KURL::fromPathOrURL( *it );
        }
    }
    else if( protocol == "compilationdisc") {
        TQString artist, album, discnumber; // artist is unused
        Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber );

        QueryBuilder qb;
        qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
        qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, album );
        qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valDiscNumber, discnumber );
        qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
        qb.setOptions( QueryBuilder::optOnlyCompilations );
        TQStringList values = qb.run();

        for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) {
            urls += KURL::fromPathOrURL( *it );
        }
    }

    else if( protocol == "fetchcover" ) {
        TQString artist, album, track; // track unused here
        Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );

        TQString artistID = TQString::number( CollectionDB::instance()->artistID( artist ) );
        TQString albumID = TQString::number( CollectionDB::instance()->albumID( album ) );

        TQStringList trackUrls = CollectionDB::instance()->albumTracks( artistID, albumID );
        foreach( trackUrls ) {
            urls += KURL::fromPathOrURL( *it );
        }
    }

    else if( protocol == "stream" ) {
        urls += KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) );
    }

    return urls;
}


#include "contextbrowser.moc"
