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

4503 lines
178 KiB

// (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 <kaction.h>
#include <kapplication.h> //kapp
#include <kcalendarsystem.h> // for Amarok::verboseTimeSince()
#include <kconfig.h> // suggested/related/favorite box visibility
#include <kdialog.h>
#include <kfiledialog.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <kio/job.h>
#include <kio/jobclasses.h>
#include <kmdcodec.h> // for data: URLs
#include <kmessagebox.h>
#include <kpopupmenu.h>
#include <kstandarddirs.h>
#include <ktextedit.h>
#include <ktoolbarbutton.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 = KGlobal::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 KConfig *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;
//KHTML 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 KPopupMenu( this ) )
, m_wikiForwardPopup( new KPopupMenu( 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( KToolBar::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( KToolBar::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 KToolBar( 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 KToolBarButton( "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, TQT_SIGNAL(clicked()), m_lyricsSearchText, TQT_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, TQT_SIGNAL(clicked()), m_lyricsSearchText, TQT_SLOT(clear()) );
connect ( m_lyricsSearchText, TQT_SIGNAL(textChanged(const TQString &)), this, TQT_SLOT(lyricsSearchText(const TQString & )) );
connect ( m_lyricsSearchText, TQT_SIGNAL(returnPressed()), this, (TQT_SLOT(lyricsSearchTextNext())) );
Amarok::actionCollection()->setAutoConnectShortcuts ( true );
new KAction( i18n("Search text in lyrics"), KShortcut("/"), TQT_TQOBJECT(this),TQT_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, TQT_SIGNAL(metaData( const MetaBundle& )),
EngineController::instance(), TQT_SLOT(currentTrackMetaDataChanged( const MetaBundle& )) );
connect( m_cuefile, TQT_SIGNAL(newCuePoint( long, long, long )),
Scrobbler::instance(), TQT_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
KIO::del( KURL::fromPathOrURL( Amarok::saveLocation( "covershadow-cache/" ) ), false, false );
connect( this, TQT_SIGNAL( currentChanged( TQWidget* ) ), TQT_SLOT( tabChanged( TQWidget* ) ) );
connect( m_currentTrackPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
this, TQT_SLOT( openURLRequest( const KURL & ) ) );
connect( m_lyricsPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
this, TQT_SLOT( openURLRequest( const KURL & ) ) );
connect( m_wikiPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
this, TQT_SLOT( openURLRequest( const KURL & ) ) );
connect( m_currentTrackPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
connect( m_lyricsPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
connect( m_wikiPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
connect( m_lyricsToolBar->getButton( LYRICS_ADD ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsAdd()) );
connect( m_lyricsToolBar->getButton( LYRICS_EDIT ), TQT_SIGNAL(toggled( int )), TQT_SLOT(lyricsEditToggle()) );
connect( m_lyricsToolBar->getButton( LYRICS_SEARCH ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsSearch()) );
connect( m_lyricsToolBar->getButton( LYRICS_REFRESH ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsRefresh()) );
connect( m_lyricsToolBar->getButton( LYRICS_BROWSER ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsExternalPage()) );
connect( m_wikiToolBar->getButton( WIKI_BACK ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiHistoryBack()) );
connect( m_wikiToolBar->getButton( WIKI_FORWARD ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiHistoryForward()) );
connect( m_wikiToolBar->getButton( WIKI_ARTIST ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiArtistPage()) );
connect( m_wikiToolBar->getButton( WIKI_ALBUM ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiAlbumPage()) );
connect( m_wikiToolBar->getButton( WIKI_TITLE ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiTitlePage()) );
connect( m_wikiToolBar->getButton( WIKI_BROWSER ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiExternalPage()) );
connect( m_wikiToolBar->getButton( WIKI_CONFIG ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiConfig()) );
connect( m_wikiBackPopup, TQT_SIGNAL(activated( int )), TQT_SLOT(wikiBackPopupActivated( int )) );
connect( m_wikiForwardPopup, TQT_SIGNAL(activated( int )), TQT_SLOT(wikiForwardPopupActivated( int )) );
connect( CollectionDB::instance(), TQT_SIGNAL( scanStarted() ), TQT_SLOT( collectionScanStarted() ) );
connect( CollectionDB::instance(), TQT_SIGNAL( scanDone( bool ) ), TQT_SLOT( collectionScanDone( bool ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( databaseEngineChanged() ), TQT_SLOT( renderView() ) );
connect( CollectionDB::instance(), TQT_SIGNAL( coverFetched( const TQString&, const TQString& ) ),
this, TQT_SLOT( coverFetched( const TQString&, const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( coverChanged( const TQString&, const TQString& ) ),
this, TQT_SLOT( coverRemoved( const TQString&, const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( similarArtistsFetched( const TQString& ) ),
this, TQT_SLOT( similarArtistsFetched( const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( tagsChanged( const MetaBundle& ) ),
this, TQT_SLOT( tagsChanged( const MetaBundle& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( tagsChanged( const TQString&, const TQString& ) ),
this, TQT_SLOT( tagsChanged( const TQString&, const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( ratingChanged( const TQString&, int ) ),
this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
connect( StarManager::instance(), TQT_SIGNAL( ratingsColorsChanged() ),
this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( scoreChanged( const TQString&, float ) ),
this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( labelsChanged( const TQString& ) ),
this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( imageFetched( const TQString& ) ),
this, TQT_SLOT( imageFetched( const TQString& ) ) );
connect( App::instance(), TQT_SIGNAL( useScores( bool ) ),
this, TQT_SLOT( refreshCurrentTrackPage() ) );
connect( App::instance(), TQT_SIGNAL( useRatings( bool ) ),
this, TQT_SLOT( refreshCurrentTrackPage() ) );
connect( MountPointManager::instance(), TQT_SIGNAL( mediumConnected( int ) ),
this, TQT_SLOT( renderView() ) );
connect( MountPointManager::instance(), TQT_SIGNAL( mediumRemoved( int ) ),
this, TQT_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 KHTMLPart dies before its KHTMLView dies,
// because KHTMLPart's dtoring relies on its KHTMLView 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=false; 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 = KGlobal::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
//fucking 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 );
KPopupMenu 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( "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( TQT_TQOBJECT(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>(KGlobal::iconLoader()->iconPath( "amarok",
-KIcon::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 = KGlobal::iconLoader()->iconPath( Amarok::icon("next"), -KIcon::SizeSmallMedium );
const TQString loveIcon = KGlobal::iconLoader()->iconPath( Amarok::icon("love"), -KIcon::SizeSmallMedium );
const TQString banIcon = KGlobal::iconLoader()->iconPath( Amarok::icon("remove"), -KIcon::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"