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 );