// (c) 2004 Christian Muehlhaeuser // (c) 2005 Reigo Reinmets // (c) 2005 Mark Kretschmann // (c) 2006 Peter C. Ndikuwera // (c) 2006 Alexandre Pereira de Oliveira // (c) 2006 Maximilian Kossick // 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 #include #include #include #include #include #include // External CSS reading #include //wiki tab #include #include #include #include #include #include #include #include //kapp #include // for Amarok::verboseTimeSince() #include // suggested/related/favorite box visibility #include #include #include #include #include #include #include // for data: URLs #include #include #include #include #include #include //usleep() namespace Amarok { TQString escapeHTML( const TQString &s ) { return TQString(s).replace( "&", "&" ).replace( "<", "<" ).replace( ">", ">" ); // .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( "

There is no product information available for this image.

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(" ", " "); // don't break over lines m_metadataHistory.prepend( TQString( "" + timeString + " " + escapeHTML( bundle.prettyTitle() ) + "" ) ); } 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( "", TQString( "" ) .arg( HTMLView::loadStyleSheet() ) ); // and the // stylesheet // code exportedDocument.close(); } } void ContextBrowser::paletteChange( const TQPalette& /* pal */ ) { // KTabWidget::paletteChange( pal ); HTMLView::paletteChange(); reloadStyleSheet(); } void ContextBrowser::reloadStyleSheet() { m_currentTrackPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); m_lyricsPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); m_wikiPage->setUserStyleSheet( HTMLView::loadStyleSheet() ); } ////////////////////////////////////////////////////////////////////////////////////////// // PROTECTED SLOTS ////////////////////////////////////////////////////////////////////////////////////////// //parts of this function from ktabwidget.cpp, copyright (C) 2003 Zack Rusin and Stephan Binner //setCurrentTab() isn't virtual so we have to override this instead =( void ContextBrowser::wheelDelta( int delta ) { if ( count() < 2 || delta == 0 ) return; int index = currentPageIndex(), start = index; do { if( delta < 0 ) index = (index + 1) % count(); else { index = index - 1; if( index < 0 ) index = count() - 1; } if( index == start ) // full circle, none enabled return; } while( !isTabEnabled( page( index ) ) ); setCurrentPage( index ); } ////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE SLOTS ////////////////////////////////////////////////////////////////////////////////////////// void ContextBrowser::tabChanged( TQWidget *page ) { DEBUG_FUNC_INFO setFocusProxy( page ); //so focus is given to a sensible widget when the tab is opened if ( page == m_contextTab ) showCurrentTrack(); else if ( page == m_lyricsTab ) showLyrics(); else if ( page == m_wikiTab ) showWikipedia(); } void ContextBrowser::slotContextMenu( const TQString& urlString, const TQPoint& point ) { enum { APPEND, ASNEXT, MAKE, MEDIA_DEVICE, INFO, TITLE, RELATED, SUGGEST, FAVES, FRESHPODCASTS, NEWALBUMS, FAVALBUMS, LABELS }; debug() << "url string: " << urlString << endl; if( urlString.startsWith( "musicbrainz" ) || urlString.startsWith( "externalurl" ) || urlString.startsWith( "show:suggest" ) || urlString.startsWith( "http" ) || urlString.startsWith( "wikipedia" ) || urlString.startsWith( "seek" ) || urlString.startsWith( "ggartist" ) || urlString.startsWith( "artistback" ) || urlString.startsWith( "current" ) || urlString.startsWith( "lastfm" ) || urlString.startsWith( "showlabel" ) || urlString.startsWith( "show:editLabels" ) || currentPage() != m_contextTab ) return; KURL url( urlString ); 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( 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( *it ); } m_amarokIconPath = TQDeepCopy(KGlobal::iconLoader()->iconPath( "amarok", -KIcon::SizeEnormous ) ); m_musicBrainIconPath = TQDeepCopy(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 ¤tTrack ); void showStream( const MetaBundle ¤tTrack ); void showPodcast( const MetaBundle ¤tTrack ); void showBrowseArtistHeader( const TQString &artist ); void showBrowseLabelHeader( const TQString &label ); void showCurrentArtistHeader( const MetaBundle ¤tTrack ); 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 ¤tTrack ); 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( *it ) ); b->m_HTMLSource = TQDeepCopy( 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( "

%1
" ) .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( "
\n" "
\n" "\n" + i18n( "No Track Playing" ) + "\n" "
\n" "\n" "\n" "\n" "\n" "\n" "
\n" "\n" "\n" "%3
\n" "%4
\n" "%5
\n" "%6
\n" "%7
\n" "
\n" "
\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( "\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 ( "\n" "\n" "
\n" "\n" "\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 ( "\n" "\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 ( "\n" "
\n" "\n" "\n" "\n" "\n" "\n" "%6\n" "\n" " - \n" "\n" "%9\n" "\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 ( "%1 " "
\n" "%2\n" "%3\n" "
\n" "
\n" "
\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 ( "
\n" "\n" "%4" "\n" "
\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 = "\n" + track + " \n"; } TQString length; if( albumValues[j + 4] != "0" ) length = "(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")\n"; htmlCode.append( "\n" ); } } htmlCode.append( "
\n" "\n" "\n" ); } } // return list of albums shown TQStringList CurrentTrackJob::showHomeByAlbums() { QueryBuilder qb; m_HTMLSource.append( "\n" ); // 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( "\n" "\n" "\n" ); } } // TQStringList albums; // 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( "\n" "\n" "\n" ); } // // 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( "\n" ); } // m_HTMLSource.append( "
\n" "
\n" "\n" + i18n( "Fresh Podcast Episodes" ) + "\n" "
\n" "\n" ); uint i = 0; for( TQStringList::iterator it = channels.begin(); it != channels.end(); it++ ) { PodcastChannelBundle pcb; if( !CollectionDB::instance()->getPodcastChannelBundle( *it, &pcb ) ) continue; TQValueList 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 ( "\n" "\n" "\n" ); i++; } } m_HTMLSource.append( "
\n" "
\n" "\n" "\n" "\n" "\n" "\n" "
\n" "\n" "\n" "\n" "\n" "%5 \n" "%7\n" "
\n" "%8\n" "
\n" "
\n" "
\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 ( "

%1

\n" ).arg( ep.description() ) ); m_HTMLSource.append( "
\n" "
\n" "
\n" "
\n" "
\n" "
\n" "\n" + i18n( "Your Newest Albums" ) + "\n" "
\n" "\n" ); constructHTMLAlbums( recentAlbums, m_HTMLSource, "1" ); m_HTMLSource.append( "
\n" "
\n" "
\n" "
\n" "
\n" "\n" + i18n( "Favorite Albums" ) + "\n" "
\n" "\n" ); if ( faveAlbums.count() == 0 ) { m_HTMLSource.append( "

\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." ) ) + "

\n" ); } else { constructHTMLAlbums( faveResults, m_HTMLSource, "2" ); } m_HTMLSource.append( "
\n" ); return albums; } void CurrentTrackJob::showLastFm( const MetaBundle ¤tTrack ) { 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 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( "
\n" "
\n" "%1 " "
\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "\n" "
\n" "%3 - %5" "
\n" "%7" "
\n" "" "\n" "\n" "
\n" "\n" "\n" "\n" "
\n" "\n" "\n" "\n" "\n" "
\n" "%14\n" "\n" "\n" "
\n" "%16\n" "\n" "\n" "
\n" "%18\n" "\n" "\n" "
\n" "
\n" "
\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( "\n" ); } void CurrentTrackJob::showStream( const MetaBundle ¤tTrack ) { m_HTMLSource.append( QStringx( "
\n" "
\n" "%1 " "
\n" "\n" "\n" "\n" "\n" "
\n" "%2\n" "
\n" "
\n" "%3" "
\n" "
\n" "%4" "
\n" "%5 kbps" "
\n" "%6" "
\n" "%7" "
\n" "
\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( "\n" ); } void CurrentTrackJob::addMetaHistory() { if ( m_metadataHistory.count() > 0 ) { m_HTMLSource.append( "
\n" "
\n" + i18n( "Metadata History" ) + "
\n" "\n" ); for ( uint i = 0; i < m_metadataHistory.count(); ++i ) { const TQString &str = m_metadataHistory[i]; m_HTMLSource.append( QStringx( "\n" ).arg( str ) ); } m_HTMLSource.append( "
%1
\n" "
\n" ); } } void CurrentTrackJob::showPodcast( const MetaBundle ¤tTrack ) { 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( "
\n" "
\n" "%1 " "
\n" "%2\n" "
\n" "\n" "\n" "\n" "\n" "
\n" "\n" "\n" "\n" "\n" "%6" "%7" "
\n" "
\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() ? "
\n" + escapeHTML( i18n( "(Cached)" ) ) : "" ) ) ); if ( m_isStream && m_metadataHistory.count() > 1 ) { m_HTMLSource.append( "
\n" "
\n" + i18n( "Metadata History" ) + "
\n" "\n" ); for ( uint i = 0; i < m_metadataHistory.count(); ++i ) { const TQString &str = m_metadataHistory[i]; m_HTMLSource.append( QStringx( "\n" ).arg( str ) ); } m_HTMLSource.append( "
%1
\n" "
\n" ); } m_HTMLSource.append( "
\n" "
\n" "\n" + ( channelInDB ? i18n( "Episodes from %1" ).arg( escapeHTML( pcb.title() ) ) : i18n( "Episodes from this Channel" ) ) + "\n" "
\n" "\n" ); uint i = 0; TQValueList 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 ( "\n" "\n" "\n" ); i++; episodes.pop_back(); } m_HTMLSource.append("\n" ); } void CurrentTrackJob::showBrowseArtistHeader( const TQString &artist ) { // bool linkback = ( b->m_contextBackHistory.size() > 0 ); TQString back = ( linkback ? "\n" + escapeHTML( i18n( "<- Back" ) ) + "\n" : TQString( "" ) ); m_HTMLSource.append( TQString( "
\n" "
\n" "%1\n" "
\n" "
\n" "
\n" "\n" "\n" "\n" "\n" "\n" "
\n" "%2 " "%4\n" "
\n" "%5\n" "
\n" "
\n" "
\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 ( "

%1

\n" ).arg( ep.description() ) ); m_HTMLSource.append( "
\n" "
\n" "\n" "\n" "
%2
%3
\n" "
\n" ) .arg( escapeHTML( artist ) ) .arg( escapeHTML( i18n( "Browse Artist" ) ) ) .arg( back ) ); m_HTMLSource.append( "\n" ); m_HTMLSource.append( "\n" "\n" "\n" ); m_HTMLSource.append( "\n" "\n" "\n"); m_HTMLSource.append( "\n" "\n" "\n" ); m_HTMLSource.append( "\n" "\n" "
\n" + TQString( "\n" ) + i18n( "Information for Current Track" ) + "\n" "
\n" + TQString( "\n" ).arg( escapeHTMLAttr( artist + b->wikiArtistPostfix() ) ) + i18n( "Wikipedia Information for %1" ).arg( escapeHTML( artist ) ) + "\n" "
\n" + TQString( "\n" ).arg( escapeHTMLAttr( artist ) ) + i18n( "Google Musicsearch for %1" ).arg( escapeHTML( artist ) ) + "\n" "
\n" "\n" ); // } void CurrentTrackJob::showBrowseLabelHeader( const TQString &label ) { bool linkback = ( b->m_contextBackHistory.size() > 0 ); TQString back = ( linkback ? "\n" + escapeHTML( i18n( "<- Back" ) ) + "\n" : TQString( "" ) ); m_HTMLSource.append( TQString( "
\n" "
\n" "%1\n" "
\n" "\n" "\n" "\n" "
%2
%3
\n" "
\n" ) .arg( escapeHTML( label ) ) .arg( escapeHTML( i18n( "Browse Label" ) ) ) .arg( back ) ); m_HTMLSource.append( "\n" ); m_HTMLSource.append( "\n" "\n" "\n" ); m_HTMLSource.append( "\n" "\n" "\n"); m_HTMLSource.append( "\n" "\n" "
\n" + TQString( "\n" ) + i18n( "Information for Current Track" ) + "\n" "
\n" + TQString( "\n" ).arg( escapeHTMLAttr( label ) ) + i18n( "Last.fm Information for %1" ).arg( escapeHTML( label ) ) + "\n" "
\n" "
\n" ); } void CurrentTrackJob::showCurrentArtistHeader( const MetaBundle ¤tTrack ) { QueryBuilder qb; TQStringList values; // 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( "
\n" "
\n" // Show "Title - Artist \n Album", or only "PrettyTitle" if there's no title tag + ( !currentTrack.title().isEmpty() ? QStringx( "%1 " "- " "%2\n" "
\n" "%3\n" "
\n" "\n" "\n" "\n" "\n" ).arg( i18n( "Score: %1" ).arg( score ) ) + "\n" "\n" "\n"; if( AmarokConfig::useRatings() ) { contents += TQString( "\n" ).arg( i18n( "Rating: %1" ) .arg( MetaBundle::ratingDescription( rating ) ) ) + "\n" "\n"; } return table.arg( contents ); } bool CurrentTrackJob::doJob() { m_HTMLSource.append( "\n" "\n" ); if( !b->m_browseArtists ) { if( !EngineController::engine()->loaded() ) { showHome(); return true; } MetaBundle mb( m_currentTrack.url() ); if( mb.podcastBundle() ) { showPodcast( mb ); return true; } if( m_currentTrack.url().protocol() == "lastfm" ) { showLastFm( m_currentTrack ); return true; } if( m_isStream && m_currentTrack.url().protocol() != "daap" ) { showStream( m_currentTrack ); return true; } } TQString artist; if( b->m_browseArtists ) { artist = b->m_artist; if( artist == m_currentTrack.artist() ) { b->m_browseArtists = false; b->m_artist = TQString(); b->m_contextBackHistory.clear(); b->m_contextBackHistory.push_back( "current://track" ); } } else artist = m_currentTrack.artist(); const uint artist_id = CollectionDB::instance()->artistID( artist ); const uint album_id = CollectionDB::instance()->albumID ( m_currentTrack.album() ); QueryBuilder qb; TQStringList values; if( b->m_browseArtists ) showBrowseArtistHeader( artist ); else if( b->m_browseLabels ) { showBrowseLabelHeader( b->m_label ); showSongsWithLabel( b->m_label ); m_HTMLSource.append( "\n" ); return true; } else showCurrentArtistHeader( m_currentTrack ); if ( ContextBrowser::instance()->m_showLabels && !b->m_browseArtists ) showUserLabels( m_currentTrack ); if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested ) { TQStringList relArtists = CollectionDB::instance()->similarArtists( artist, 10 ); if ( !relArtists.isEmpty() ) { if( ContextBrowser::instance()->m_showRelated ) showRelatedArtists( artist, relArtists ); if( ContextBrowser::instance()->m_showSuggested ) showSuggestedSongs( relArtists ); } } TQString artistName = artist.isEmpty() ? i18n( "This Artist" ) : artist ; if ( !artist.isEmpty() ) { if( ContextBrowser::instance()->m_showFaves ) showArtistsFaves( artistName, artist_id ); showArtistsAlbums( artist, artist_id, album_id ); showArtistsCompilations( artist, artist_id, album_id ); } m_HTMLSource.append( "\n" ); return true; } void ContextBrowser::showIntroduction() { if ( currentPage() != m_contextTab ) { blockSignals( true ); showPage( m_contextTab ); blockSignals( false ); } // Do we have to rebuild the page? I don't care m_HTMLSource = TQString(); m_HTMLSource.append( "\n" "
\n" "
\n" "\n" + i18n( "Hello Amarok user!" ) + "\n" "
\n" "
\n" "

\n" + i18n( "This is the Context Browser: " "it shows you contextual information about the currently playing track. " "In order to use this feature of Amarok, you need to build a Collection." ) + "

\n" "
\n" "

\n" "
\n" "
\n" "\n" ); m_currentTrackPage->set( m_HTMLSource ); saveHtmlData(); // Send html code to file } void ContextBrowser::showScanning() { if ( currentPage() != m_contextTab ) { blockSignals( true ); showPage( m_contextTab ); blockSignals( false ); } // Do we have to rebuild the page? I don't care m_HTMLSource=""; m_HTMLSource.append( "\n" "
\n" "
\n" "\n" + i18n( "Building Collection Database..." ) + "\n" "
\n" "
\n" "

\n" + i18n( "Please be patient while Amarok scans your music collection. You can watch the progress of this activity in the statusbar." ) + "

\n" "
\n" "
\n" "\n" ); m_currentTrackPage->set( m_HTMLSource ); saveHtmlData(); // Send html code to file } TQString ContextBrowser::getEncodedImage( const TQString &imageUrl ) { // Embed cover image in html (encoded string), to get around khtml's caching //debug() << "Encoding imageUrl: " << imageUrl << endl; tqApp->lock(); const TQImage img( imageUrl, "PNG" ); tqApp->unlock(); TQByteArray ba; TQBuffer buffer( ba ); buffer.open( IO_WriteOnly ); tqApp->lock(); img.save( &buffer, "PNG" ); // writes image into ba in PNG format tqApp->unlock(); const TQString coverImage = TQString( "data:image/png;base64,%1" ).arg( KCodecs::base64Encode( ba ).data() ); //debug() << "Encoded imageUrl: " << coverImage << endl; return coverImage; } ////////////////////////////////////////////////////////////////////////////////////////// // Lyrics-Tab ////////////////////////////////////////////////////////////////////////////////////////// void ContextBrowser::showLyrics( const TQString &url ) { #if 0 if( BrowserBar::instance()->currentBrowser() != this ) { debug() << "current browser is not context, aborting showLyrics()" << endl; m_dirtyLyricsPage = true; return; } #endif DEBUG_BLOCK if ( currentPage() != m_lyricsTab ) { blockSignals( true ); showPage( m_lyricsTab ); blockSignals( false ); } if ( !m_dirtyLyricsPage ) return; TQString lyrics = CollectionDB::instance()->getLyrics( EngineController::instance()->bundle().url().path() ); // don't rely on caching for streams const bool cached = !lyrics.isEmpty() && !EngineController::engine()->isStream(); TQString title = EngineController::instance()->bundle().title(); TQString artist = EngineController::instance()->bundle().artist(); if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) title = title.remove(" (PREVIEW: buy it at www.magnatune.com)"); if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)"); if ( title.isEmpty() ) { /* If title is empty, try to use pretty title. The fact that it often (but not always) has artist name together, can be bad, but at least the user will hopefully get nice suggestions. */ TQString prettyTitle = EngineController::instance()->bundle().prettyTitle(); int h = prettyTitle.find( '-' ); if ( h != -1 ) { title = prettyTitle.mid( h+1 ).stripWhiteSpace(); if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) title = title.remove(" (PREVIEW: buy it at www.magnatune.com)"); if ( artist.isEmpty() ) { artist = prettyTitle.mid( 0, h ).stripWhiteSpace(); if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 ) artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)"); } } } m_lyricSearchUrl = TQString( "http://www.google.com/search?ie=UTF-8&q=lyrics+%1+%2" ) .arg( KURL::encode_string_no_slash( '"' + artist + '"', 106 /*utf-8*/ ), KURL::encode_string_no_slash( '"' + title + '"', 106 /*utf-8*/ ) ); m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled(false); if( ( !cached || url == "reload" ) && !ScriptManager::instance()->lyricsScriptRunning() ) { const TQStringList scripts = ScriptManager::instance()->lyricsScripts(); lyrics = i18n( "Sorry, no lyrics script running.") + "
\n" + "
\n"+ i18n( "Available Lyrics Scripts:" ) + "
\n"; foreach ( scripts ) { lyrics += TQString( "%2
\n" ).arg( *it, *it ); } lyrics += "
\n" + i18n( "Click on one of the scripts to run it, or use the Script Manager, to be able" " to see all the scripts, and download new ones from the Web." ); lyrics += "
\n" "

\n"; m_HTMLSource = TQString ( "\n" "
\n" "
\n" "\n" + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) + "\n" "
\n" "
\n" + lyrics + "
\n" "
\n" "\n" ); m_lyricsPage->set( m_HTMLSource ); m_dirtyLyricsPage = false; saveHtmlData(); // Send html code to file return; } if( cached && url.isEmpty() ) { lyricsResult( lyrics.utf8(), true ); } else { m_HTMLSource = TQString ( "\n" "
\n" "
\n" "\n" + i18n( "Fetching Lyrics" ) + "\n" "
\n" "
\n" "

\n" + i18n( "Fetching Lyrics..." ) + "

\n" "
\n" "
\n" "\n" ); m_lyricsPage->set( m_HTMLSource ); saveHtmlData(); // Send html code to file if( url.isNull() || url == "reload" ) ScriptManager::instance()->notifyFetchLyrics( artist, title ); else ScriptManager::instance()->notifyFetchLyricsByUrl( url ); } } void ContextBrowser::lyricsResult( TQCString cXmlDoc, bool cached ) //SLOT { TQDomDocument doc; TQString xmldoc = TQString::fromUtf8( cXmlDoc ); if( !doc.setContent( xmldoc ) ) { m_HTMLSource=""; m_HTMLSource.append( "\n" "
\n" "
\n" "\n" + i18n( "Error" ) + "\n" "
\n" "

\n" + i18n( "Lyrics could not be retrieved because the server was not reachable." ) + "

\n" "
\n" "\n" ); m_lyricsPage->set( m_HTMLSource ); saveHtmlData(); // Send html code to file m_dirtyLyricsPage = false; return; } TQString lyrics; TQDomElement el = doc.documentElement(); m_lyricCurrentUrl = el.attribute( "page_url" ); ScriptManager* const sm = ScriptManager::instance(); KConfig spec( sm->specForScript( sm->lyricsScriptRunning() ), true, false ); spec.setGroup( "Lyrics" ); if ( el.attribute( "add_url" ).isEmpty() ) { m_lyricAddUrl = spec.readPathEntry( "add_url" ); m_lyricAddUrl.replace( "MAGIC_ARTIST", KURL::encode_string_no_slash( EngineController::instance()->bundle().artist() ) ); m_lyricAddUrl.replace( "MAGIC_TITLE", KURL::encode_string_no_slash( EngineController::instance()->bundle().title() ) ); m_lyricAddUrl.replace( "MAGIC_ALBUM", KURL::encode_string_no_slash( EngineController::instance()->bundle().album() ) ); m_lyricAddUrl.replace( "MAGIC_YEAR", KURL::encode_string_no_slash( TQString::number( EngineController::instance()->bundle().year() ) ) ); } else m_lyricAddUrl = el.attribute( "add_url" ); if ( el.tagName() == "suggestions" ) { const TQDomNodeList l = doc.elementsByTagName( "suggestion" ); if( l.length() ==0 ) { lyrics = i18n( "Lyrics for track not found" ); } else { lyrics = i18n( "Lyrics for track not found, here are some suggestions:" ) + "

\n"; for( uint i = 0; i < l.length(); ++i ) { const TQString url = l.item( i ).toElement().attribute( "url" ); const TQString artist = l.item( i ).toElement().attribute( "artist" ); const TQString title = l.item( i ).toElement().attribute( "title" ); lyrics += "\n" + i18n("%1 - %2").arg( artist, title ); lyrics += "
\n"; } } lyrics += i18n( "

You can search for the lyrics on the Web.

" ) .arg( TQString( m_lyricSearchUrl ).replace( TQRegExp( "^http:" ), "externalurl:" ) ); } else { lyrics = el.text(); lyrics.replace( "\n", "
\n" ); // Plaintext -> HTML const TQString title = el.attribute( "title" ); const TQString artist = el.attribute( "artist" ); const TQString site = el.attribute( "site" ).isEmpty() ? spec.readEntry( "site" ) : el.attribute( "site" ); const TQString site_url = el.attribute( "site_url" ).isEmpty() ? spec.readEntry( "site_url" ) : el.attribute( "site_url" ); lyrics.prepend( "\n" + title + "
\n" + artist+ "

\n" ); if( !cached ) { lyrics.append( "

\n" + i18n( "Powered by %1 (%2)" ).arg( site, site_url ) + "\n" ); CollectionDB::instance()->setLyrics( EngineController::instance()->bundle().url().path(), xmldoc, EngineController::instance()->bundle().uniqueId() ); } } m_HTMLSource=""; m_HTMLSource.append( "\n" "
\n" "
\n" "\n" + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) + "\n" "
\n" "
\n" + lyrics + "
\n" "
\n" "\n" ); m_lyricsPage->set( m_HTMLSource ); //Reset scroll m_lyricsPage->view()->setContentsPos(0, 0); saveHtmlData(); // Send html code to file m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled( !m_lyricCurrentUrl.isEmpty() ); m_dirtyLyricsPage = false; } void ContextBrowser::lyricsExternalPage() //SLOT { Amarok::invokeBrowser( m_lyricCurrentUrl ); } void ContextBrowser::lyricsAdd() //SLOT { Amarok::invokeBrowser( m_lyricAddUrl ); } void ContextBrowser::lyricsEditToggle() //SLOT { if ( m_lyricsToolBar->getButton( LYRICS_EDIT )->isOn() ) { m_lyricsBeingEditedUrl = EngineController::instance()->bundle().url().path(); m_lyricsBeingEditedArtist = EngineController::instance()->bundle().artist(); m_lyricsBeingEditedTitle = EngineController::instance()->bundle().title(); TQString xml = CollectionDB::instance()->getLyrics( m_lyricsBeingEditedUrl ), lyrics; TQDomDocument doc; if( doc.setContent( xml ) ) lyrics = doc.documentElement().text(); else lyrics = TQString(); m_lyricsTextEdit->setText( lyrics ); m_lyricsPage->hide(); m_lyricsTextEdit->show(); } else { m_lyricsTextEdit->hide(); TQDomDocument doc; TQDomElement e = doc.createElement( "lyrics" ); e.setAttribute( "artist", m_lyricsBeingEditedArtist ); e.setAttribute( "title", m_lyricsBeingEditedTitle ); TQDomText t = doc.createTextNode( m_lyricsTextEdit->text() ); e.appendChild( t ); doc.appendChild( e ); CollectionDB::instance()->setLyrics( m_lyricsBeingEditedUrl, doc.toString(), CollectionDB::instance()->uniqueIdFromUrl( KURL( m_lyricsBeingEditedUrl) ) ); m_lyricsPage->show(); lyricsChanged( m_lyricsBeingEditedUrl ); } } void ContextBrowser::lyricsSearch() //SLOT { Amarok::invokeBrowser( m_lyricSearchUrl ); } void ContextBrowser::lyricsRefresh() //SLOT { m_dirtyLyricsPage = true; showLyrics( "reload" ); } void ContextBrowser::lyricsSearchText(TQString const &text) //SLOT { m_lyricsPage->findText( text, 0 ); lyricsSearchTextNext(); } void ContextBrowser::lyricsSearchTextNext() //SLOT { m_lyricsPage->findTextNext(); } void ContextBrowser::lyricsSearchTextShow() //SLOT { m_lyricsSearchText->setEnabled( true ); m_lyricsTextBar->show(); m_lyricsTextBarShowed = true; m_lyricsSearchText->setFocus(); } void ContextBrowser::lyricsSearchTextHide() //SLOT { m_lyricsSearchText->setText(""); m_lyricsSearchText->setEnabled( false ); m_lyricsTextBar->hide(); m_lyricsTextBarShowed=false; } void ContextBrowser::lyricsSearchTextToggle() //SLOT { if ( m_lyricsTextBarShowed ) { lyricsSearchTextHide(); } else { lyricsSearchTextShow(); } } // Wikipedia-Tab ////////////////////////////////////////////////////////////////////////////////////////// TQString ContextBrowser::wikiArtistPostfix() const { if( wikiLocale() == "en" ) return " (band)"; else if( wikiLocale() == "de" ) return " (Band)"; else return ""; } TQString ContextBrowser::wikiAlbumPostfix() const { if( wikiLocale() == "en" ) return " (album)"; else return ""; } TQString ContextBrowser::wikiTrackPostfix() const { if( wikiLocale() == "en" ) return " (song)"; else return ""; } void ContextBrowser::wikiConfigChanged( int /*activeItem*/ ) // SLOT { // keep in sync with localeList in wikiConfig TQString text = m_wikiLocaleCombo->currentText(); m_wikiLocaleEdit->setEnabled( text == i18n("Other...") ); if( text == i18n("English") ) m_wikiLocaleEdit->setText( "en" ); else if( text == i18n("German") ) m_wikiLocaleEdit->setText( "de" ); else if( text == i18n("French") ) m_wikiLocaleEdit->setText( "fr" ); else if( text == i18n("Polish") ) m_wikiLocaleEdit->setText( "pl" ); else if( text == i18n("Japanese") ) m_wikiLocaleEdit->setText( "ja" ); else if( text == i18n("Spanish") ) m_wikiLocaleEdit->setText( "es" ); } void ContextBrowser::wikiConfigApply() // SLOT { const bool changed = m_wikiLocaleEdit->text() != wikiLocale(); setWikiLocale( m_wikiLocaleEdit->text() ); if ( changed && currentPage() == m_wikiTab && !m_wikiCurrentEntry.isNull() ) { m_dirtyWikiPage = true; showWikipediaEntry( m_wikiCurrentEntry ); } showWikipedia(); } void ContextBrowser::wikiConfig() // SLOT { TQStringList localeList; localeList << i18n( "English" ) << i18n( "German" ) << i18n( "French" ) << i18n( "Polish" ) << i18n( "Japanese" ) << i18n( "Spanish" ) << i18n( "Other..." ); int index; if( wikiLocale() == "en" ) index = 0; else if( wikiLocale() == "de" ) index = 1; else if( wikiLocale() == "fr" ) index = 2; else if( wikiLocale() == "pl" ) index = 3; else if( wikiLocale() == "ja" ) index = 4; else if( wikiLocale() == "es" ) index = 5; else // other index = 6; m_wikiConfigDialog = new KDialogBase( this, 0, true, 0, KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel ); kapp->setTopWidget( m_wikiConfigDialog ); m_wikiConfigDialog->setCaption( kapp->makeStdCaption( i18n( "Wikipedia Locale" ) ) ); TQVBox *box = m_wikiConfigDialog->makeVBoxMainWidget(); m_wikiLocaleCombo = new TQComboBox( box ); m_wikiLocaleCombo->insertStringList( localeList ); TQHBox *hbox = new TQHBox( box ); TQLabel *otherLabel = new TQLabel( i18n( "Locale: " ), hbox ); m_wikiLocaleEdit = new TQLineEdit( "en", hbox ); otherLabel->setBuddy( m_wikiLocaleEdit ); TQToolTip::add( m_wikiLocaleEdit, i18n( "2-letter language code for your Wikipedia locale" ) ); connect( m_wikiLocaleCombo, TQT_SIGNAL( activated(int) ), TQT_SLOT( wikiConfigChanged(int) ) ); connect( m_wikiConfigDialog, TQT_SIGNAL( applyClicked() ), TQT_SLOT( wikiConfigApply() ) ); m_wikiLocaleEdit->setText( wikiLocale() ); m_wikiLocaleCombo->setCurrentItem( index ); wikiConfigChanged( index ); // a little redundant, but saves ugly code, and ensures the lineedit enabled status is correct m_wikiConfigDialog->setInitialSize( TQSize( 240, 100 ) ); const int result = m_wikiConfigDialog->exec(); if( result == TQDialog::Accepted ) wikiConfigApply(); delete m_wikiConfigDialog; } TQString ContextBrowser::wikiLocale() { if( s_wikiLocale.isEmpty() ) return TQString( "en" ); return s_wikiLocale; } void ContextBrowser::setWikiLocale( const TQString &locale ) { AmarokConfig::setWikipediaLocale( locale ); s_wikiLocale = locale; } TQString ContextBrowser::wikiURL( const TQString &item ) { // add any special characters to be replaced here TQString wStr = TQString(item).replace( "/", " " ); return TQString( "http://%1.wikipedia.org/wiki/" ).arg( wikiLocale() ) + KURL::encode_string_no_slash( wStr, 106 /*utf-8*/ ); } void ContextBrowser::reloadWikipedia() { m_wikiJob = NULL; showWikipediaEntry( m_wikiCurrentEntry, true ); } void ContextBrowser::showWikipediaEntry( const TQString &entry, bool replaceHistory ) { m_wikiCurrentEntry = entry; showWikipedia( wikiURL( entry ), false, replaceHistory ); } void ContextBrowser::showLabelsDialog() { DEBUG_BLOCK KURL currentUrl = EngineController::instance()->bundle().url(); TQStringList allLabels = CollectionDB::instance()->labelList(); TQStringList trackLabels = CollectionDB::instance()->getLabels( currentUrl.path(), CollectionDB::typeUser ); debug() << "Showing add label dialog" << endl; KDialogBase *dialog = new KDialogBase( this, 0, false, TQString(), KDialogBase::Ok|KDialogBase::Cancel ); dialog->makeVBoxMainWidget(); TQLabel *labelText = new TQLabel( i18n( "

Add a new label in the field below and press Enter, or choose labels from the list

"), dialog->mainWidget() ); m_addLabelEdit = new ClickLineEdit( i18n( "Add new label" ), dialog->mainWidget() ); m_addLabelEdit->installEventFilter( this ); m_addLabelEdit->setFrame( TQFrame::Sunken ); TQToolTip::add( m_addLabelEdit, i18n( "Enter a new label and press Return to add it" ) ); dialog->setFocusProxy( m_addLabelEdit ); labelText->setBuddy( m_addLabelEdit ); m_labelListView = new TQListView( dialog->mainWidget() ); m_labelListView->addColumn( i18n( "Label" ) ); m_labelListView->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding ); m_labelListView->setColumnWidthMode( 0, TQListView::Maximum ); foreach( allLabels ) { TQCheckListItem *item = new TQCheckListItem( m_labelListView, *it, TQCheckListItem::CheckBox ); item->setOn( trackLabels.contains( *it ) ); } if( dialog->exec() == TQDialog::Accepted ) { debug() << "Dialog closed, updating labels" << endl; TQStringList newTrackLabels; TQListViewItemIterator iter( m_labelListView ); while( iter.current() ) { TQCheckListItem *item = static_cast( iter.current() ); if( item->isOn() ) newTrackLabels.append( item->text() ); iter++; } CollectionDB::instance()->setLabels( currentUrl.path(), newTrackLabels, CollectionDB::instance()->uniqueIdFromUrl( currentUrl ), CollectionDB::typeUser ); CollectionDB::instance()->cleanLabels(); if( newTrackLabels != trackLabels && currentUrl == EngineController::instance()->bundle().url() ) { m_dirtyCurrentTrackPage = true; showCurrentTrack(); } } delete dialog; //deletes children m_addLabelEdit = 0; m_labelListView = 0; } bool ContextBrowser::eventFilter( TQObject *o, TQEvent *e ) { switch( e->type() ) { case 6/*TQEvent::KeyPress*/: #define e TQT_TQKEYEVENT(e) if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(m_addLabelEdit) ) //the add label lineedit { switch( e->key() ) { case Key_Return: case Key_Enter: { TQCheckListItem *item = new TQCheckListItem( m_labelListView, m_addLabelEdit->text(), TQCheckListItem::CheckBox ); item->setOn( true ); m_addLabelEdit->setText( TQString() ); return true; } default: return false; } } if (TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(m_lyricsSearchText)) { switch ( e->key() ) { case Key_Escape: { lyricsSearchTextHide(); return true; } default: return false; } } default: break; } return KTabWidget::eventFilter( TQT_TQOBJECT(o), TQT_TQEVENT(e) ); } void ContextBrowser::showWikipedia( const TQString &url, bool fromHistory, bool replaceHistory ) { #if 0 if( BrowserBar::instance()->currentBrowser() != this ) { debug() << "current browser is not context, aborting showWikipedia()" << endl; m_dirtyWikiPage = true; return; } #endif if ( currentPage() != m_wikiTab ) { blockSignals( true ); showPage( m_wikiTab ); blockSignals( false ); } if ( !m_dirtyWikiPage || m_wikiJob ) return; // Disable the Open in a Browser button, because while loading it would open wikipedia main page. m_wikiToolBar->setItemEnabled( WIKI_BROWSER, false ); m_HTMLSource=""; m_HTMLSource.append( "\n" "
\n" "
\n" "\n" + i18n( "Wikipedia" ) + "\n" "
\n" "
\n" "

\n" + i18n( "Fetching Wikipedia Information" ) + " ...

\n" "
\n" "
\n" "\n" ); m_wikiPage->set( m_HTMLSource ); saveHtmlData(); // Send html code to file if ( url.isEmpty() ) { TQString tmpWikiStr; if ( (EngineController::instance()->bundle().url().protocol() == "lastfm") || (EngineController::instance()->bundle().url().protocol() == "daap") || !EngineController::engine()->isStream() ) { if ( !EngineController::instance()->bundle().artist().isEmpty() ) { tmpWikiStr = EngineController::instance()->bundle().artist(); tmpWikiStr += wikiArtistPostfix(); } else if ( !EngineController::instance()->bundle().title().isEmpty() ) { tmpWikiStr = EngineController::instance()->bundle().title(); } else { tmpWikiStr = EngineController::instance()->bundle().prettyTitle(); } } else { tmpWikiStr = EngineController::instance()->bundle().prettyTitle(); } //Hack to make wiki searches work with magnatune preview tracks if (tmpWikiStr.contains( "PREVIEW: buy it at www.magnatune.com" ) >= 1 ) { tmpWikiStr = tmpWikiStr.remove(" (PREVIEW: buy it at www.magnatune.com)" ); int index = tmpWikiStr.find( '-' ); if ( index != -1 ) { tmpWikiStr = tmpWikiStr.left (index - 1); } } m_wikiCurrentEntry = tmpWikiStr; m_wikiCurrentUrl = wikiURL( tmpWikiStr ); } else { m_wikiCurrentUrl = url; } // Append new URL to history if ( replaceHistory ) { m_wikiBackHistory.back() = m_wikiCurrentUrl; } else if ( !fromHistory ) { m_wikiBackHistory += m_wikiCurrentUrl; m_wikiForwardHistory.clear(); } // Limit number of items in history if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY ) m_wikiBackHistory.pop_front(); // Remove all items from the button-menus m_wikiBackPopup->clear(); m_wikiForwardPopup->clear(); // Populate button menus with URLs from the history TQStringList::ConstIterator it; uint count; // Reverse iterate over both lists count = m_wikiBackHistory.count()-1; it = m_wikiBackHistory.fromLast(); if( count > 0 ) it--; for ( uint i=0; iinsertItem( SmallIconSet( "wiki" ), *it, i ); count = m_wikiForwardHistory.count(); it = m_wikiForwardHistory.fromLast(); for ( uint i=0; iinsertItem( SmallIconSet( "wiki" ), *it, i ); m_wikiToolBar->setItemEnabled( WIKI_BACK, m_wikiBackHistory.size() > 1 ); m_wikiToolBar->setItemEnabled( WIKI_FORWARD, m_wikiForwardHistory.size() > 0 ); m_wikiBaseUrl = m_wikiCurrentUrl.mid(0 , m_wikiCurrentUrl.find("wiki/")); m_wikiJob = KIO::storedGet( m_wikiCurrentUrl, false, false ); Amarok::StatusBar::instance()->newProgressOperation( m_wikiJob ) .setDescription( i18n( "Fetching Wikipedia Information" ) ); connect( m_wikiJob, TQT_SIGNAL( result( KIO::Job* ) ), TQT_SLOT( wikiResult( KIO::Job* ) ) ); } void ContextBrowser::wikiHistoryBack() //SLOT { //Disable the button as history may be empty. Reenabled later by showWikipedia. m_wikiToolBar->setItemEnabled( WIKI_BACK, false ); m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false ); m_wikiForwardHistory += m_wikiBackHistory.last(); m_wikiBackHistory.pop_back(); m_dirtyWikiPage = true; m_wikiCurrentEntry = TQString(); showWikipedia( m_wikiBackHistory.last(), true ); } void ContextBrowser::wikiHistoryForward() //SLOT { //Disable the button as history may be empty. Reenabled later by showWikipedia. m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false ); m_wikiToolBar->setItemEnabled( WIKI_BACK, false ); m_wikiBackHistory += m_wikiForwardHistory.last(); m_wikiForwardHistory.pop_back(); m_dirtyWikiPage = true; m_wikiCurrentEntry = TQString(); showWikipedia( m_wikiBackHistory.last(), true ); } void ContextBrowser::wikiBackPopupActivated( int id ) //SLOT { do { m_wikiForwardHistory += m_wikiBackHistory.last(); m_wikiBackHistory.pop_back(); if ( m_wikiForwardHistory.count() > WIKI_MAX_HISTORY ) m_wikiForwardHistory.pop_front(); id--; } while( id >= 0 ); m_dirtyWikiPage = true; m_wikiCurrentEntry = TQString(); showWikipedia( m_wikiBackHistory.last(), true ); } void ContextBrowser::wikiForwardPopupActivated( int id ) //SLOT { do { m_wikiBackHistory += m_wikiForwardHistory.last(); m_wikiForwardHistory.pop_back(); if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY ) m_wikiBackHistory.pop_front(); id--; } while( id >= 0 ); m_dirtyWikiPage = true; m_wikiCurrentEntry = TQString(); showWikipedia( m_wikiBackHistory.last(), true ); } void ContextBrowser::wikiArtistPage() //SLOT { m_dirtyWikiPage = true; showWikipedia(); // Will fall back to title, if artist is empty(streams!). } void ContextBrowser::wikiAlbumPage() //SLOT { m_dirtyWikiPage = true; showWikipediaEntry( EngineController::instance()->bundle().album() + wikiAlbumPostfix() ); } void ContextBrowser::wikiTitlePage() //SLOT { m_dirtyWikiPage = true; showWikipediaEntry( EngineController::instance()->bundle().title() + wikiTrackPostfix() ); } void ContextBrowser::wikiExternalPage() //SLOT { Amarok::invokeBrowser( m_wikiCurrentUrl ); } void ContextBrowser::wikiResult( KIO::Job* job ) //SLOT { DEBUG_BLOCK if ( !job->error() == 0 ) { m_HTMLSource=""; m_HTMLSource.append( "
\n" "
\n" "\n" + i18n( "Error" ) + "\n" "
\n" "

\n" + i18n( "Artist information could not be retrieved because the server was not reachable." ) + "

\n" "
\n" ); m_wikiPage->set( m_HTMLSource ); m_dirtyWikiPage = false; //m_wikiPage = NULL; // FIXME: what for? leads to crashes saveHtmlData(); // Send html code to file warning() << "[WikiFetcher] KIO error! errno: " << job->error() << endl; return; } if ( job != m_wikiJob ) return; //not the right job, so let's ignore it KIO::StoredTransferJob* const storedJob = static_cast( job ); m_wiki = TQString( storedJob->data() ); // Enable the Open in a Brower button, Disabled while loading, guz it would open wikipedia main page. m_wikiToolBar->setItemEnabled( WIKI_BROWSER, true ); // FIXME: Get a safer Regexp here, to match only inside of at least. if ( m_wiki.contains( "charset=utf-8", FALSE ) ) { m_wiki = TQString::fromUtf8( storedJob->data().data(), storedJob->data().size() ); } if( m_wiki.find( "var wgArticleId = 0" ) != -1 ) { debug() << "Article not found." << endl; // article was not found if( !wikiArtistPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiArtistPostfix() ) ) { m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiArtistPostfix().length() ); reloadWikipedia(); return; } else if( !wikiAlbumPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiAlbumPostfix() ) ) { m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiAlbumPostfix().length() ); reloadWikipedia(); return; } else if( !wikiTrackPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiTrackPostfix() ) ) { m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiTrackPostfix().length() ); reloadWikipedia(); return; } } //remove the new-lines and tabs(replace with spaces IS needed). m_wiki.replace( "\n", " " ); m_wiki.replace( "\t", " " ); m_wikiLanguages = TQString(); // Get the available language list if ( m_wiki.find("
") != -1 ) { m_wikiLanguages = m_wiki.mid( m_wiki.find("
") ); m_wikiLanguages = m_wikiLanguages.mid( m_wikiLanguages.find("
    ") ); m_wikiLanguages = m_wikiLanguages.mid( 0, m_wikiLanguages.find( "
" ) ); } TQString copyright; TQString copyrightMark = "
  • "; if ( m_wiki.find( copyrightMark ) != -1 ) { copyright = m_wiki.mid( m_wiki.find(copyrightMark) + copyrightMark.length() ); copyright = copyright.mid( 0, copyright.find( "
  • " ) ); copyright.replace( "
    ", TQString() ); //only one br at the beginning copyright.prepend( "
    " ); } // Ok lets remove the top and bottom parts of the page m_wiki = m_wiki.mid( m_wiki.find( "

    " ) ); m_wiki = m_wiki.mid( 0, m_wiki.find( "
    " ) ); // Adding back license information m_wiki += copyright; m_wiki.append( "
    " ); m_wiki.replace( TQRegExp("

    [^<]*

    "), TQString() ); m_wiki.replace( TQRegExp( "]*>[^<]*<[^>]*>[^<]*<[^>]*>[^<]*" ), TQString() ); m_wiki.replace( TQRegExp( "]*>([^<]*)" ), "\\1" ); // Remove anything inside of a class called urlexpansion, as it's pointless for us m_wiki.replace( TQRegExp( "[^(]*[(][^)]*[)]" ), TQString() ); // Remove hidden table rows as well TQRegExp hidden( "
    .*", false ); hidden.setMinimal( true ); //greedy behaviour wouldn't be any good! m_wiki.replace( hidden, TQString() ); // we want to keep our own style (we need to modify the stylesheet a bit to handle things nicely) m_wiki.replace( TQRegExp( "style= *\"[^\"]*\"" ), TQString() ); m_wiki.replace( TQRegExp( "class= *\"[^\"]*\"" ), TQString() ); // let's remove the form elements, we don't want them. m_wiki.replace( TQRegExp( "]*>" ), TQString() ); m_wiki.replace( TQRegExp( "]*>" ), TQString() ); m_wiki.replace( "\n" , TQString() ); m_wiki.replace( TQRegExp( "]*>" ), TQString() ); m_wiki.replace( "\n" , TQString() ); m_wiki.replace( TQRegExp( "]*>" ), TQString() ); m_wiki.replace( "" , TQString() ); //first we convert all the links with protocol to external, as they should all be External Links. m_wiki.replace( TQRegExp( "href= *\"http:" ), "href=\"externalurl:" ); m_wiki.replace( TQRegExp( "href= *\"/" ), "href=\"" +m_wikiBaseUrl ); m_wiki.replace( TQRegExp( "href= *\"#" ), "href=\"" +m_wikiCurrentUrl + '#' ); m_HTMLSource = "\n"; m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Wikipedia Information" ) + "\n" "
    \n" "
    \n" + m_wiki + "
    \n" "
    \n" ); if ( !m_wikiLanguages.isEmpty() ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Wikipedia Other Languages" ) + "\n" "
    \n" "
    \n" + m_wikiLanguages + "
    \n" "
    \n" ); } m_HTMLSource.append( "\n" ); m_wikiPage->set( m_HTMLSource ); m_dirtyWikiPage = false; saveHtmlData(); // Send html code to file m_wikiJob = NULL; } void ContextBrowser::coverFetched( const TQString &artist, const TQString &album ) //SLOT { if ( currentPage() == m_contextTab && !EngineController::engine()->loaded() && !m_browseArtists ) { m_dirtyCurrentTrackPage = true; if( m_shownAlbums.contains( album ) ) showCurrentTrack(); return; } const MetaBundle ¤tTrack = EngineController::instance()->bundle(); if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) return; if ( currentPage() == m_contextTab && ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty { m_dirtyCurrentTrackPage = true; showCurrentTrack(); } } void ContextBrowser::coverRemoved( const TQString &artist, const TQString &album ) //SLOT { if ( currentPage() == m_contextTab && !EngineController::engine()->loaded() && !m_browseArtists ) { m_dirtyCurrentTrackPage = true; if( m_shownAlbums.contains( album ) ) showCurrentTrack(); return; } const MetaBundle ¤tTrack = EngineController::instance()->bundle(); if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() && m_artist.isNull() ) return; if ( currentPage() == m_contextTab && ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty { m_dirtyCurrentTrackPage = true; showCurrentTrack(); } } void ContextBrowser::similarArtistsFetched( const TQString &artist ) //SLOT { if( artist == m_artist || EngineController::instance()->bundle().artist().string() == artist ) { m_dirtyCurrentTrackPage = true; if ( currentPage() == m_contextTab ) showCurrentTrack(); } } void ContextBrowser::imageFetched( const TQString &url ) //SLOT { const MetaBundle ¤tTrack = EngineController::instance()->bundle(); PodcastEpisodeBundle peb; if( CollectionDB::instance()->getPodcastEpisodeBundle( currentTrack.url(), &peb ) ) { PodcastChannelBundle pcb; if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) ) { if( pcb.imageURL().url() == url ) { m_dirtyCurrentTrackPage = true; showCurrentTrack(); } } } } void ContextBrowser::ratingOrScoreOrLabelsChanged( const TQString &path ) //SLOT { const MetaBundle ¤tTrack = EngineController::instance()->bundle(); //Always refresh if using ratings, otherwise suggested songs and other songs by artist that //have their ratings changed in the playlist won't be reflected until the context browser refreshes //which can be confusing, and looks less polished/professional //This can be changed if it slows things down too much... if( m_browseLabels || ( currentTrack.isFile() && ( currentTrack.url().path() == path || AmarokConfig::useRatings() ) ) ) m_dirtyCurrentTrackPage = true; // will be reloaded when viewed (much faster) if( currentPage() == m_contextTab ) refreshCurrentTrackPage(); } void ContextBrowser::tagsChanged( const MetaBundle &bundle ) //SLOT { const MetaBundle ¤tTrack = EngineController::instance()->bundle(); if( !m_shownAlbums.contains( bundle.album() ) && m_artist != bundle.artist() ) { if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) return; if( bundle.artist() != currentTrack.artist() && bundle.album() != currentTrack.album() ) return; } refreshCurrentTrackPage(); } void ContextBrowser::tagsChanged( const TQString &oldArtist, const TQString &oldAlbum ) //SLOT { const MetaBundle ¤tTrack = EngineController::instance()->bundle(); if( !m_shownAlbums.contains( oldAlbum ) && m_artist != oldArtist ) { if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() ) return; if( oldArtist != currentTrack.artist() && oldAlbum != currentTrack.album() ) return; } refreshCurrentTrackPage(); } void ContextBrowser::refreshCurrentTrackPage() //SLOT { if ( currentPage() == m_contextTab ) // this is for compilations or when artist is empty { m_dirtyCurrentTrackPage = true; showCurrentTrack(); } } bool ContextBrowser::hasContextProtocol( const KURL &url ) { TQString protocol = url.protocol(); return protocol == "album" || protocol == "artist" || protocol == "stream" || protocol == "compilation" || protocol == "albumdisc" || protocol == "compilationdisc" || protocol == "fetchcover"; } KURL::List ContextBrowser::expandURL( const KURL &url ) { KURL::List urls; TQString protocol = url.protocol(); if( protocol == "artist" ) { uint artist_id = CollectionDB::instance()->artistID( url.path(), false ); if( artist_id ) { TQStringList trackUrls = CollectionDB::instance()->artistTracks( TQString::number( artist_id ) ); foreach( trackUrls ) urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "album" ) { TQString artist, album, track; // track unused here Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); TQStringList trackUrls = CollectionDB::instance()->albumTracks( artist, album ); foreach( trackUrls ) { urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "albumdisc" ) { TQString artist, album, discnumber; // discnumber is returned in track number field Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber ); TQStringList trackUrls = CollectionDB::instance()->albumDiscTracks( artist, album, discnumber ); foreach( trackUrls ) { urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "compilation" ) { QueryBuilder qb; qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, url.path() ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.setOptions( QueryBuilder::optOnlyCompilations ); TQStringList values = qb.run(); for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) { urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "compilationdisc") { TQString artist, album, discnumber; // artist is unused Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber ); QueryBuilder qb; qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, album ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valDiscNumber, discnumber ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.setOptions( QueryBuilder::optOnlyCompilations ); TQStringList values = qb.run(); for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) { urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "fetchcover" ) { TQString artist, album, track; // track unused here Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track ); TQString artistID = TQString::number( CollectionDB::instance()->artistID( artist ) ); TQString albumID = TQString::number( CollectionDB::instance()->albumID( album ) ); TQStringList trackUrls = CollectionDB::instance()->albumTracks( artistID, albumID ); foreach( trackUrls ) { urls += KURL::fromPathOrURL( *it ); } } else if( protocol == "stream" ) { urls += KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) ); } return urls; } #include "contextbrowser.moc"
    \n" "\n" "\n" "\n" "\n" "
    \n" "\n" "\n" "\n" "
    \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 "%1 " "\n" "\n" "\n" "\n" "\n" "\n" "
    \n" "\n" "\n" "\n" "\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( values[3].toFloat() ); const uint rating = values[4].toInt(); //SAFE = .arg( x, y ) //UNSAFE = .arg( x ).arg( y ) m_HTMLSource.append( TQString( "%1
    \n" "
    %2
    \n" "%3
    \n" "%4\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( "
    \n" "\n" ); // if ( currentTrack.url().isLocalFile() && !CollectionDB::instance()->isFileInCollection( currentTrack.url().path() ) ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "This file is not in your Collection!" ) + "\n" "
    \n" "
    \n" "

    \n" + i18n( "If you would like to see contextual information about this track," " you should add it to your Collection." ) + "

    \n" "
    \n" "

    \n" "
    \n" "
    \n" ); } /* cue file code */ if ( b->m_cuefile && (b->m_cuefile->count() > 0) ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Cue File" ) + "\n" "
    \n" "\n" ); CueFile::Iterator it; uint i = 0; for ( it = b->m_cuefile->begin(); it != b->m_cuefile->end(); ++it ) { m_HTMLSource.append( "\n" "\n" "" ); } m_HTMLSource.append( "
    \n" "\n" "\n" + TQString::number(it.data().getTrackNumber()) + " \n" "\n" + escapeHTML( it.data().getTitle() ) + "\n" "\n" + i18n(" – ") + "\n" "\n" + escapeHTML( it.data().getArtist() ) + "\n" " (" + MetaBundle::prettyTime( it.data().getLength()/1000, false ) + ")\n" "\n" "
    \n" "
    \n" ); } } void CurrentTrackJob::showRelatedArtists( const TQString &artist, const TQStringList &relArtists ) { // m_HTMLSource.append( TQString( "\n" ); if ( !b->m_relatedOpen ) m_HTMLSource.append( "\n" ); // } void CurrentTrackJob::showSuggestedSongs( const TQStringList &relArtists ) { TQString token; QueryBuilder qb; TQStringList values; qb.clear(); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); qb.addMatches( QueryBuilder::tabArtist, relArtists ); qb.sortByFavorite(); qb.setLimit( 0, 10 ); values = qb.run(); // if ( !values.isEmpty() ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Suggested Songs" ) + "\n" "
    \n" "\n" ); for ( uint i = 0; i < values.count(); i += 5 ) m_HTMLSource.append( "\n" "\n" "\n" "\n" "\n" ); m_HTMLSource.append( "
    \n" "\n" "\n"+ escapeHTML( values[i + 2] ) + "\n" "\n" + i18n(" – ") + "\n" + escapeHTML( values[i + 1] ) + "\n" "\n" "\n" + statsHTML( static_cast( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "
    \n" "
    \n" ); if ( !b->m_suggestionsOpen ) m_HTMLSource.append( "\n" ); } //
    } void CurrentTrackJob::showSongsWithLabel( const TQString &label ) { QueryBuilder qb; TQStringList values; qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) ); qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valName, label ); qb.sortByFavorite(); qb.setOptions( QueryBuilder::optRandomize ); qb.setLimit( 0, 30 ); values = qb.run(); if ( !values.isEmpty() ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Songs with label %1" ).arg( label ) + "\n" "
    \n" "\n" ); for ( uint i = 0; i < values.count(); i += 5 ) m_HTMLSource.append( "\n" "\n" "\n" "\n" "\n" ); m_HTMLSource.append( "
    \n" "\n" "\n"+ escapeHTML( values[i + 2] ) + "\n" "\n" + i18n(" – ") + "\n" + escapeHTML( values[i + 1] ) + "\n" "\n" "\n" + statsHTML( static_cast( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "
    \n" "
    \n" ); } } void CurrentTrackJob::showUserLabels( const MetaBundle ¤tTrack ) { QueryBuilder qb; qb.addReturnValue( QueryBuilder::tabLabels, QueryBuilder::valName, true ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, currentTrack.url().path() ); qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) ); qb.setLimit( 0, 10 ); qb.sortBy( QueryBuilder::tabLabels, QueryBuilder::valName, false ); qb.buildQuery(); TQStringList values = qb.run(); TQString title; if ( currentTrack.title().isEmpty() ) title = currentTrack.veryNiceTitle(); else title = currentTrack.title(); m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( " Labels for %1 " ).arg( escapeHTML( title ) ) + "\n" "
    \n" "\n" ); m_HTMLSource.append( "\n" ); m_HTMLSource.append( "\n" ); m_HTMLSource.append( "
    \n" ); if ( !values.isEmpty() ) { foreach( values ) { if( it != values.begin() ) m_HTMLSource.append( ", \n" ); m_HTMLSource.append( "" + escapeHTML( *it ) + "" ); } } m_HTMLSource.append( "
    " + i18n( "Add labels to %1" ).arg( escapeHTML( title ) ) + "
    \n" "
    \n" ); if ( !b->m_labelsOpen ) m_HTMLSource.append( "\n" ); } void CurrentTrackJob::showArtistsFaves( const TQString &artist, uint artist_id ) { TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); QueryBuilder qb; TQStringList values; qb.clear(); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore ); qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating ); qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0", QueryBuilder::modeGreater ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) ); qb.sortByFavorite(); qb.setLimit( 0, 10 ); values = qb.run(); usleep( 10000 ); if ( !values.isEmpty() ) { m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Favorite Tracks by %1" ).arg( artistName ) + "\n" "
    \n" "\n" ); for ( uint i = 0; i < values.count(); i += 4 ) m_HTMLSource.append( "\n" "\n" "\n" "\n" "\n" ); m_HTMLSource.append( "
    \n" "\n" "\n" + escapeHTML( values[i] ) + "\n" "\n" "\n" + statsHTML( static_cast( values[i + 2].toFloat() ), values[i + 3].toInt() ) + "
    \n" "
    \n" ); if ( !b->m_favoritesOpen ) m_HTMLSource.append( "\n" ); } } void CurrentTrackJob::showArtistsAlbums( const TQString &artist, uint artist_id, uint album_id ) { DEBUG_BLOCK TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); QueryBuilder qb; TQStringList values; // qb.clear(); qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) ); qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName ); qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID ); qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName, true ); qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true ); qb.setOptions( QueryBuilder::optNoCompilations ); values = qb.run(); if ( !values.isEmpty() ) { // write the script to toggle blocks visibility m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Albums by %1" ).arg( artistName ) + "\n" "
    \n" "\n" ); uint vectorPlace = 0; // find album of the current track (if it exists) while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) ) vectorPlace += 3; for ( uint i = 0; i < values.count(); i += 3 ) { qb.clear(); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.setOptions( QueryBuilder::optNoCompilations ); TQStringList albumValues = qb.run(); usleep( 10000 ); TQString albumYear; if ( !albumValues.isEmpty() ) { albumYear = albumValues[ 3 ]; for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) if ( albumValues[j + 3] != albumYear || albumYear == "0" ) { albumYear = TQString(); break; } } uint i_albumLength = 0; for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) i_albumLength += TQString(albumValues[j + 4]).toInt(); TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) ); TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) ); TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 ); m_HTMLSource.append( QStringx ( "\n" "\n" "\n" ); } m_HTMLSource.append( "
    \n" "
    \n" "\n" "\n" "\n" "\n" "\n" "
    \n" "\n" "\n" "\n" "\n" "%6 " "%9\n" "
    \n" "%10\n" "%11\n" "
    \n" "
    \n" "
    \n" ) .args( TQStringList() << values[ i + 1 ] << escapeHTMLAttr( artist ) // artist name << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name << albumImageTitleAttr << escapeHTMLAttr( albumImage ) << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() ) << TQString::number( artist_id ) << values[ i + 1 ] //album.id << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) << albumYear << albumLength << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */ << values[ i + 1 ] ) ); TQString discNumber; if ( !albumValues.isEmpty() ) for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) { TQString newDiscNumber = albumValues[ j + 5 ].stripWhiteSpace(); if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0) { discNumber = newDiscNumber; m_HTMLSource.append( QStringx ( "
    \n" "\n" "%4" "\n" "
    \n" ) .args( TQStringList() << TQString::number( artist_id ) << values[ i + 1 ] //album.id << escapeHTMLAttr( discNumber ) << i18n( "Disc %1" ).arg( discNumber ) ) ); } TQString track = albumValues[j + 2].stripWhiteSpace(); if( track.length() > 0 ) { if( track.length() == 1 ) track.prepend( "0" ); track = "\n" + track + " \n"; } TQString length; if( albumValues[j + 4] != "0" ) length = "(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")\n"; bool current = false; if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) current = true; m_HTMLSource.append( "\n" ); } m_HTMLSource.append( "
    \n" "
    \n" "
    \n" ); } //
    } void CurrentTrackJob::showArtistsCompilations( const TQString &artist, uint artist_id, uint album_id ) { TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist ); QueryBuilder qb; TQStringList values; // qb.clear(); qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) ); qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName, true ); qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true ); qb.setOptions( QueryBuilder::optRemoveDuplicates ); qb.setOptions( QueryBuilder::optOnlyCompilations ); values = qb.run(); if ( !values.isEmpty() ) { // write the script to toggle blocks visibility m_HTMLSource.append( "
    \n" "
    \n" "\n" + i18n( "Compilations with %1" ).arg( artistName ) + "\n" "
    \n" "\n" ); uint vectorPlace = 0; // find album of the current track (if it exists) while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) ) vectorPlace += 2; for ( uint i = 0; i < values.count(); i += 2 ) { qb.clear(); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength ); qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName ); qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber ); qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack ); qb.setOptions( QueryBuilder::optOnlyCompilations ); TQStringList albumValues = qb.run(); usleep( 10000 ); TQString albumYear; if ( !albumValues.isEmpty() ) { albumYear = albumValues[ 3 ]; for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) if ( albumValues[j + 3] != albumYear || albumYear == "0" ) { albumYear = TQString(); break; } } uint i_albumLength = 0; for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) i_albumLength += TQString(albumValues[j + 4]).toInt(); TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) ); TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) ); TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 ); m_HTMLSource.append( QStringx ( "\n" "\n" "\n" ); } m_HTMLSource.append( "
    \n" "
    \n" "\n" "\n" "\n" "\n" "\n" "
    \n" "\n" "\n" "\n" "\n" "%5 " "%7\n" "
    \n" "%8\n" "%9\n" "
    \n" "
    \n" "
    \n" ) .args( TQStringList() << values[ i + 1 ] << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name << albumImageTitleAttr << escapeHTMLAttr( albumImage ) << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() ) << values[ i + 1 ] //album.id << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) << albumYear << albumLength << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */ << values[ i + 1 ] ) ); TQString discNumber; if ( !albumValues.isEmpty() ) for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() ) { TQString newDiscNumber = albumValues[ j + 6 ].stripWhiteSpace(); if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0) { discNumber = newDiscNumber; m_HTMLSource.append( QStringx ( "
    \n" "\n" "%3" "\n" "
    \n" ) .args( TQStringList() << values[ i + 1 ] //album.id << escapeHTMLAttr( discNumber ) << i18n( "Disc %1" ).arg( discNumber ) ) ); } TQString track = albumValues[j + 2].stripWhiteSpace(); if( track.length() > 0 ) { if( track.length() == 1 ) track.prepend( "0" ); track = "\n" + track + " \n"; } TQString length; if( albumValues[j + 4] != "0" ) length = "(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")\n"; TQString tracktitle_formated; TQString tracktitle; tracktitle = escapeHTML( i18n("%1 - %2").arg( albumValues[j + 5], albumValues[j] ) ); tracktitle_formated = "\n"; if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) tracktitle_formated += "\n"; if ( artist == albumValues[j + 5] ) tracktitle_formated += "\n"; tracktitle_formated += tracktitle; if ( artist == albumValues[j + 5] ) tracktitle_formated += "\n"; if( i==vectorPlace && track.toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() ) tracktitle_formated += "\n"; tracktitle_formated += " "; m_HTMLSource.append( "\n" ); } m_HTMLSource.append( "
    \n" "
    \n" "
    \n" ); } //
    } TQString CurrentTrackJob::statsHTML( int score, int rating, bool statsbox ) //static { if( !AmarokConfig::useScores() && !AmarokConfig::useRatings() ) return ""; if ( rating < 0 ) rating = 0; if ( rating > 10 ) rating = 10; TQString table = TQString( "%2
    \n" ) .arg( statsbox ? "class='statsBox'" : "" ); TQString contents; if( AmarokConfig::useScores() ) contents += TQString( "
    \n" + TQString::number( score ) + "\n" "
    \n" "
    \n" "
    \n" "
    \n"; if( rating ) { bool half = rating%2; contents += "\n"; TQImageIO fullStarIO; fullStarIO.setImage( StarManager::instance()->getStarImage( half ? rating/2 + 1 : rating/2 ) ); fullStarIO.setFormat( "PNG" ); TQBuffer fullStarBuf; fullStarBuf.open( IO_WriteOnly ); fullStarIO.setIODevice( TQT_TQIODEVICE(&fullStarBuf) ); fullStarIO.write(); fullStarBuf.close(); TQCString fullStar = KCodecs::base64Encode( fullStarBuf.buffer(), true ); const TQString img = "\n"; for( int i = 0, n = rating / 2; i < n; ++i ) contents += img.arg( TQString("data:image/png;base64," + fullStar) ); if( rating % 2 ) { TQImageIO halfStarIO; halfStarIO.setImage( StarManager::instance()->getHalfStarImage( half ? rating/2 + 1 : rating/2 ) ); halfStarIO.setFormat( "PNG" ); TQBuffer halfStarBuf; halfStarBuf.open( IO_WriteOnly ); halfStarIO.setIODevice( TQT_TQIODEVICE(&halfStarBuf) ); halfStarIO.write(); halfStarBuf.close(); TQCString halfStar = KCodecs::base64Encode( halfStarBuf.buffer(), true ); contents += img.arg( TQString("data:image/png;base64," + halfStar) ); } contents += "\n"; } else contents += i18n( "Not rated" ); contents += "