Amarok – versatile and easy to use audio player
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4504 lines
178 KiB

  1. // (c) 2004 Christian Muehlhaeuser <chris@chris.de>
  2. // (c) 2005 Reigo Reinmets <xatax@hot.ee>
  3. // (c) 2005 Mark Kretschmann <markey@web.de>
  4. // (c) 2006 Peter C. Ndikuwera <pndiku@gmail.com>
  5. // (c) 2006 Alexandre Pereira de Oliveira <aleprj@gmail.com>
  6. // (c) 2006 Maximilian Kossick <maximilian.kossick@googlemail.com>
  7. // License: GNU General Public License V2
  8. #define DEBUG_PREFIX "ContextBrowser"
  9. #include "amarok.h"
  10. #include "amarokconfig.h"
  11. #include "app.h"
  12. #include "browserToolBar.h"
  13. #include "debug.h"
  14. #include "clicklineedit.h"
  15. #include "collectiondb.h"
  16. #include "collectionbrowser.h"
  17. #include "colorgenerator.h"
  18. #include "contextbrowser.h"
  19. #include "coverfetcher.h"
  20. #include "covermanager.h"
  21. #include "cuefile.h"
  22. #include "enginecontroller.h"
  23. #include "htmlview.h"
  24. #include "lastfm.h"
  25. #include "mediabrowser.h"
  26. #include "metabundle.h"
  27. #include "mountpointmanager.h"
  28. #include "playlist.h" //appendMedia()
  29. #include "podcastbundle.h"
  30. #include "qstringx.h"
  31. #include "scriptmanager.h"
  32. #include "starmanager.h"
  33. #include "statusbar.h"
  34. #include "tagdialog.h"
  35. #include "threadmanager.h"
  36. #include <tqbuffer.h>
  37. #include <tqdatetime.h>
  38. #include <tqdeepcopy.h>
  39. #include <tqdom.h>
  40. #include <tqimage.h>
  41. #include <tqregexp.h>
  42. #include <tqtextstream.h> // External CSS reading
  43. #include <tqvbox.h> //wiki tab
  44. #include <tqhbox.h>
  45. #include <tqlayout.h>
  46. #include <tqlineedit.h>
  47. #include <tqlistview.h>
  48. #include <tqtimer.h>
  49. #include <tqtooltip.h>
  50. #include <tdeaction.h>
  51. #include <tdeapplication.h> //kapp
  52. #include <kcalendarsystem.h> // for Amarok::verboseTimeSince()
  53. #include <tdeconfig.h> // suggested/related/favorite box visibility
  54. #include <kdialog.h>
  55. #include <tdefiledialog.h>
  56. #include <tdeglobal.h>
  57. #include <kiconloader.h>
  58. #include <tdeio/job.h>
  59. #include <tdeio/jobclasses.h>
  60. #include <kmdcodec.h> // for data: URLs
  61. #include <tdemessagebox.h>
  62. #include <tdepopupmenu.h>
  63. #include <kstandarddirs.h>
  64. #include <ktextedit.h>
  65. #include <tdetoolbarbutton.h>
  66. #include <unistd.h> //usleep()
  67. namespace Amarok
  68. {
  69. TQString escapeHTML( const TQString &s )
  70. {
  71. return TQString(s).replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
  72. // .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
  73. }
  74. TQString escapeHTMLAttr( const TQString &s )
  75. {
  76. return TQString(s).replace( "%", "%25" ).replace( "'", "%27" ).replace( "\"", "%22" ).replace( "#", "%23" ).replace( "?", "%3F" );
  77. }
  78. TQString unescapeHTMLAttr( const TQString &s )
  79. {
  80. return TQString(s).replace( "%3F", "?" ).replace( "%23", "#" ).replace( "%22", "\"" ).replace( "%27", "'" ).replace( "%25", "%" );
  81. }
  82. TQString verboseTimeSince( const TQDateTime &datetime )
  83. {
  84. const TQDateTime now = TQDateTime::currentDateTime();
  85. const int datediff = datetime.daysTo( now );
  86. if( datediff >= 6*7 /*six weeks*/ ) { // return absolute month/year
  87. const KCalendarSystem *cal = TDEGlobal::locale()->calendar();
  88. const TQDate date = datetime.date();
  89. return i18n( "monthname year", "%1 %2" ).arg( cal->monthName(date), cal->yearString(date, false) );
  90. }
  91. //TODO "last week" = maybe within 7 days, but prolly before last sunday
  92. if( datediff >= 7 ) // return difference in weeks
  93. return i18n( "One week ago", "%n weeks ago", (datediff+3)/7 );
  94. if( datediff == -1 )
  95. return i18n( "Tomorrow" );
  96. const int timediff = datetime.secsTo( now );
  97. if( timediff >= 24*60*60 /*24 hours*/ ) // return difference in days
  98. return datediff == 1 ?
  99. i18n( "Yesterday" ) :
  100. i18n( "One day ago", "%n days ago", (timediff+12*60*60)/(24*60*60) );
  101. if( timediff >= 90*60 /*90 minutes*/ ) // return difference in hours
  102. return i18n( "One hour ago", "%n hours ago", (timediff+30*60)/(60*60) );
  103. //TODO are we too specific here? Be more fuzzy? ie, use units of 5 minutes, or "Recently"
  104. if( timediff >= 0 ) // return difference in minutes
  105. return timediff/60 ?
  106. i18n( "One minute ago", "%n minutes ago", (timediff+30)/60 ) :
  107. i18n( "Within the last minute" );
  108. return i18n( "The future" );
  109. }
  110. TQString verboseTimeSince( uint time_t )
  111. {
  112. if( !time_t )
  113. return i18n( "Never" );
  114. TQDateTime dt;
  115. dt.setTime_t( time_t );
  116. return verboseTimeSince( dt );
  117. }
  118. extern TDEConfig *config( const TQString& );
  119. /**
  120. * Function that must be used when separating contextBrowser escaped urls
  121. * detail can contain track/discnumber
  122. */
  123. void albumArtistTrackFromUrl( TQString url, TQString &artist, TQString &album, TQString &detail )
  124. {
  125. if ( !url.contains("@@@") ) return;
  126. //TDEHTML removes the trailing space!
  127. if ( url.endsWith( " @@@" ) )
  128. url += ' ';
  129. const TQStringList list = TQStringList::split( " @@@ ", url, true );
  130. int size = list.count();
  131. Q_ASSERT( size>0 );
  132. artist = size > 0 ? unescapeHTMLAttr( list[0] ) : "";
  133. album = size > 1 ? unescapeHTMLAttr( list[1] ) : "";
  134. detail = size > 2 ? unescapeHTMLAttr( list[2] ) : "";
  135. }
  136. }
  137. using Amarok::QStringx;
  138. using Amarok::escapeHTML;
  139. using Amarok::escapeHTMLAttr;
  140. using Amarok::unescapeHTMLAttr;
  141. static
  142. TQString albumImageTooltip( const TQString &albumImage, int size )
  143. {
  144. if ( albumImage == CollectionDB::instance()->notAvailCover( false, size ) )
  145. return escapeHTMLAttr( i18n( "Click to fetch cover from amazon.%1, right-click for menu." ).arg( CoverManager::amazonTld() ) );
  146. return escapeHTMLAttr( i18n( "Click for information from Amazon, right-click for menu." ) );
  147. }
  148. ContextBrowser *ContextBrowser::s_instance = 0;
  149. TQString ContextBrowser::s_wikiLocale = "en";
  150. ContextBrowser::ContextBrowser( const char *name )
  151. : KTabWidget( 0, name )
  152. , EngineObserver( EngineController::instance() )
  153. , m_dirtyCurrentTrackPage( true )
  154. , m_dirtyLyricsPage( true )
  155. , m_dirtyWikiPage( true )
  156. , m_emptyDB( CollectionDB::instance()->isEmpty() )
  157. , m_wikiBackPopup( new TDEPopupMenu( this ) )
  158. , m_wikiForwardPopup( new TDEPopupMenu( this ) )
  159. , m_wikiJob( NULL )
  160. , m_wikiConfigDialog( NULL )
  161. , m_relatedOpen( true )
  162. , m_suggestionsOpen( true )
  163. , m_favoritesOpen( true )
  164. , m_labelsOpen( true )
  165. , m_showFreshPodcasts( true )
  166. , m_showFavoriteAlbums( true )
  167. , m_showNewestAlbums( true )
  168. , m_browseArtists( false )
  169. , m_browseLabels( false )
  170. , m_cuefile( NULL )
  171. {
  172. s_instance = this;
  173. s_wikiLocale = AmarokConfig::wikipediaLocale();
  174. m_contextTab = new TQVBox(this, "context_tab");
  175. m_currentTrackPage = new HTMLView( m_contextTab, "current_track_page", true /* DNDEnabled */,
  176. true /*JScriptEnabled*/ );
  177. m_lyricsTab = new TQVBox(this, "lyrics_tab");
  178. m_lyricsToolBar = new Browser::ToolBar( m_lyricsTab );
  179. m_lyricsToolBar->setIconText( TDEToolBar::IconTextRight, false );
  180. m_lyricsToolBar->insertButton( Amarok::icon( "refresh" ), LYRICS_REFRESH, true, i18n("Refresh") );
  181. m_lyricsToolBar->insertButton( Amarok::icon( "add_lyrics" ), LYRICS_ADD, true, i18n("Add") );
  182. m_lyricsToolBar->insertButton( Amarok::icon( "edit" ), LYRICS_EDIT, true, i18n("Edit") );
  183. m_lyricsToolBar->setToggle( LYRICS_EDIT, true );
  184. m_lyricsToolBar->insertButton( Amarok::icon( "search" ), LYRICS_SEARCH, true, i18n("Search") );
  185. m_lyricsToolBar->setIconText( TDEToolBar::IconOnly, false );
  186. m_lyricsToolBar->insertButton( Amarok::icon( "external" ), LYRICS_BROWSER, true, i18n("Open in external browser") );
  187. { //Search text inside lyrics. Code inspired/copied from playlistwindow.cpp
  188. m_lyricsTextBar = new TDEToolBar( m_lyricsTab, "NotMainToolBar" );
  189. m_lyricsTextBar->hide();
  190. m_lyricsTextBarShowed=false;
  191. m_lyricsTextBar->setIconSize( 22, false ); //looks more sensible
  192. m_lyricsTextBar->setFlat( true ); //removes the ugly frame
  193. m_lyricsTextBar->setMovingEnabled( false ); //removes the ugly frame
  194. m_lyricsTextBar->boxLayout()->addStretch();
  195. TQWidget *button = new TDEToolBarButton( "locationbar_erase", 1, m_lyricsTextBar );
  196. TQLabel *filter_label = new TQLabel( i18n("S&earch:") + ' ', m_lyricsTextBar );
  197. m_lyricsSearchText = new ClickLineEdit( i18n( "Search in lyrics" ), m_lyricsTextBar );
  198. filter_label->setBuddy( m_lyricsSearchText );
  199. m_lyricsTextBar->setStretchableWidget(m_lyricsSearchText );
  200. m_lyricsSearchText->setFrame( TQFrame::Sunken );
  201. m_lyricsSearchText->installEventFilter( this ); //we intercept keyEvents
  202. connect( button, TQT_SIGNAL(clicked()), m_lyricsSearchText, TQT_SLOT(clear()) );
  203. TQToolTip::add( button, i18n( "Clear search" ) );
  204. TQString filtertip = i18n( "Enter text to search for. Press enter to advance to the next match." );
  205. TQToolTip::add( m_lyricsSearchText, filtertip );
  206. connect ( button, TQT_SIGNAL(clicked()), m_lyricsSearchText, TQT_SLOT(clear()) );
  207. connect ( m_lyricsSearchText, TQT_SIGNAL(textChanged(const TQString &)), this, TQT_SLOT(lyricsSearchText(const TQString & )) );
  208. connect ( m_lyricsSearchText, TQT_SIGNAL(returnPressed()), this, (TQT_SLOT(lyricsSearchTextNext())) );
  209. Amarok::actionCollection()->setAutoConnectShortcuts ( true );
  210. new TDEAction( i18n("Search text in lyrics"), TDEShortcut("/"), TQT_TQOBJECT(this),TQT_SLOT( lyricsSearchTextShow() ), Amarok::actionCollection(), "search_text_lyric");
  211. Amarok::actionCollection()->setAutoConnectShortcuts ( false );
  212. }
  213. m_lyricsPage = new HTMLView( m_lyricsTab, "lyrics_page", true /* DNDEnabled */, false /* JScriptEnabled*/ );
  214. m_lyricsTextEdit = new KTextEdit ( m_lyricsTab, "lyrics_text_edit");
  215. m_lyricsTextEdit->setTextFormat( TQt::PlainText );
  216. m_lyricsTextEdit->hide();
  217. m_wikiTab = new TQVBox(this, "wiki_tab");
  218. m_wikiToolBar = new Browser::ToolBar( m_wikiTab );
  219. m_wikiToolBar->insertButton( "back", WIKI_BACK, false, i18n("Back") );
  220. m_wikiToolBar->insertButton( "forward", WIKI_FORWARD, false, i18n("Forward") );
  221. m_wikiToolBar->insertLineSeparator();
  222. m_wikiToolBar->insertButton( Amarok::icon( "artist" ), WIKI_ARTIST, false, i18n("Artist Page") );
  223. m_wikiToolBar->insertButton( Amarok::icon( "album" ), WIKI_ALBUM, false, i18n("Album Page") );
  224. m_wikiToolBar->insertButton( Amarok::icon( "track" ), WIKI_TITLE, false, i18n("Title Page") );
  225. m_wikiToolBar->insertLineSeparator();
  226. m_wikiToolBar->insertButton( Amarok::icon( "external" ), WIKI_BROWSER, true, i18n("Open in external browser") );
  227. m_wikiToolBar->insertButton( Amarok::icon( "change_language" ), WIKI_CONFIG, true, i18n("Change Locale") );
  228. m_wikiToolBar->setDelayedPopup( WIKI_BACK, m_wikiBackPopup );
  229. m_wikiToolBar->setDelayedPopup( WIKI_FORWARD, m_wikiForwardPopup );
  230. m_wikiPage = new HTMLView( m_wikiTab, "wiki_page", true /* DNDEnabled */, false /* JScriptEnabled */ );
  231. m_cuefile = CueFile::instance();
  232. connect( m_cuefile, TQT_SIGNAL(metaData( const MetaBundle& )),
  233. EngineController::instance(), TQT_SLOT(currentTrackMetaDataChanged( const MetaBundle& )) );
  234. connect( m_cuefile, TQT_SIGNAL(newCuePoint( long, long, long )),
  235. Scrobbler::instance(), TQT_SLOT(subTrack( long, long, long )) );
  236. addTab( m_contextTab, SmallIconSet( Amarok::icon( "music" ) ), i18n( "Music" ) );
  237. addTab( m_lyricsTab, SmallIconSet( Amarok::icon( "lyrics" ) ), i18n( "Lyrics" ) );
  238. addTab( m_wikiTab, SmallIconSet( Amarok::icon( "artist" ) ), i18n( "Artist" ) );
  239. setTabEnabled( m_lyricsTab, false );
  240. setTabEnabled( m_wikiTab, false );
  241. m_showRelated = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowRelated", true );
  242. m_showSuggested = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowSuggested", true );
  243. m_showFaves = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFaves", true );
  244. m_showLabels = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowLabels", true );
  245. m_showFreshPodcasts = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFreshPodcasts", true );
  246. m_showNewestAlbums = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowNewestAlbums", true );
  247. m_showFavoriteAlbums = Amarok::config( "ContextBrowser" )->readBoolEntry( "ShowFavoriteAlbums", true );
  248. // Delete folder with the cached coverimage shadow pixmaps
  249. TDEIO::del( KURL::fromPathOrURL( Amarok::saveLocation( "covershadow-cache/" ) ), false, false );
  250. connect( this, TQT_SIGNAL( currentChanged( TQWidget* ) ), TQT_SLOT( tabChanged( TQWidget* ) ) );
  251. connect( m_currentTrackPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
  252. this, TQT_SLOT( openURLRequest( const KURL & ) ) );
  253. connect( m_lyricsPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
  254. this, TQT_SLOT( openURLRequest( const KURL & ) ) );
  255. connect( m_wikiPage->browserExtension(), TQT_SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
  256. this, TQT_SLOT( openURLRequest( const KURL & ) ) );
  257. connect( m_currentTrackPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
  258. this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
  259. connect( m_lyricsPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
  260. this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
  261. connect( m_wikiPage, TQT_SIGNAL( popupMenu( const TQString&, const TQPoint& ) ),
  262. this, TQT_SLOT( slotContextMenu( const TQString&, const TQPoint& ) ) );
  263. connect( m_lyricsToolBar->getButton( LYRICS_ADD ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsAdd()) );
  264. connect( m_lyricsToolBar->getButton( LYRICS_EDIT ), TQT_SIGNAL(toggled( int )), TQT_SLOT(lyricsEditToggle()) );
  265. connect( m_lyricsToolBar->getButton( LYRICS_SEARCH ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsSearch()) );
  266. connect( m_lyricsToolBar->getButton( LYRICS_REFRESH ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsRefresh()) );
  267. connect( m_lyricsToolBar->getButton( LYRICS_BROWSER ), TQT_SIGNAL(clicked( int )), TQT_SLOT(lyricsExternalPage()) );
  268. connect( m_wikiToolBar->getButton( WIKI_BACK ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiHistoryBack()) );
  269. connect( m_wikiToolBar->getButton( WIKI_FORWARD ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiHistoryForward()) );
  270. connect( m_wikiToolBar->getButton( WIKI_ARTIST ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiArtistPage()) );
  271. connect( m_wikiToolBar->getButton( WIKI_ALBUM ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiAlbumPage()) );
  272. connect( m_wikiToolBar->getButton( WIKI_TITLE ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiTitlePage()) );
  273. connect( m_wikiToolBar->getButton( WIKI_BROWSER ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiExternalPage()) );
  274. connect( m_wikiToolBar->getButton( WIKI_CONFIG ), TQT_SIGNAL(clicked( int )), TQT_SLOT(wikiConfig()) );
  275. connect( m_wikiBackPopup, TQT_SIGNAL(activated( int )), TQT_SLOT(wikiBackPopupActivated( int )) );
  276. connect( m_wikiForwardPopup, TQT_SIGNAL(activated( int )), TQT_SLOT(wikiForwardPopupActivated( int )) );
  277. connect( CollectionDB::instance(), TQT_SIGNAL( scanStarted() ), TQT_SLOT( collectionScanStarted() ) );
  278. connect( CollectionDB::instance(), TQT_SIGNAL( scanDone( bool ) ), TQT_SLOT( collectionScanDone( bool ) ) );
  279. connect( CollectionDB::instance(), TQT_SIGNAL( databaseEngineChanged() ), TQT_SLOT( renderView() ) );
  280. connect( CollectionDB::instance(), TQT_SIGNAL( coverFetched( const TQString&, const TQString& ) ),
  281. this, TQT_SLOT( coverFetched( const TQString&, const TQString& ) ) );
  282. connect( CollectionDB::instance(), TQT_SIGNAL( coverChanged( const TQString&, const TQString& ) ),
  283. this, TQT_SLOT( coverRemoved( const TQString&, const TQString& ) ) );
  284. connect( CollectionDB::instance(), TQT_SIGNAL( similarArtistsFetched( const TQString& ) ),
  285. this, TQT_SLOT( similarArtistsFetched( const TQString& ) ) );
  286. connect( CollectionDB::instance(), TQT_SIGNAL( tagsChanged( const MetaBundle& ) ),
  287. this, TQT_SLOT( tagsChanged( const MetaBundle& ) ) );
  288. connect( CollectionDB::instance(), TQT_SIGNAL( tagsChanged( const TQString&, const TQString& ) ),
  289. this, TQT_SLOT( tagsChanged( const TQString&, const TQString& ) ) );
  290. connect( CollectionDB::instance(), TQT_SIGNAL( ratingChanged( const TQString&, int ) ),
  291. this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
  292. connect( StarManager::instance(), TQT_SIGNAL( ratingsColorsChanged( const TQString& ) ),
  293. this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
  294. connect( CollectionDB::instance(), TQT_SIGNAL( scoreChanged( const TQString&, float ) ),
  295. this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
  296. connect( CollectionDB::instance(), TQT_SIGNAL( labelsChanged( const TQString& ) ),
  297. this, TQT_SLOT( ratingOrScoreOrLabelsChanged( const TQString& ) ) );
  298. connect( CollectionDB::instance(), TQT_SIGNAL( imageFetched( const TQString& ) ),
  299. this, TQT_SLOT( imageFetched( const TQString& ) ) );
  300. connect( App::instance(), TQT_SIGNAL( useScores( bool ) ),
  301. this, TQT_SLOT( refreshCurrentTrackPage() ) );
  302. connect( App::instance(), TQT_SIGNAL( useRatings( bool ) ),
  303. this, TQT_SLOT( refreshCurrentTrackPage() ) );
  304. connect( MountPointManager::instance(), TQT_SIGNAL( mediumConnected( int ) ),
  305. this, TQT_SLOT( renderView() ) );
  306. connect( MountPointManager::instance(), TQT_SIGNAL( mediumRemoved( int ) ),
  307. this, TQT_SLOT( renderView() ) );
  308. showContext( KURL( "current://track" ) );
  309. // setMinimumHeight( AmarokConfig::coverPreviewSize() + (fontMetrics().height()+2)*5 + tabBar()->height() );
  310. }
  311. ContextBrowser::~ContextBrowser()
  312. {
  313. DEBUG_BLOCK
  314. ThreadManager::instance()->abortAllJobsNamed( "CurrentTrackJob" );
  315. // Ensure the TDEHTMLPart dies before its TDEHTMLView dies,
  316. // because TDEHTMLPart's dtoring relies on its TDEHTMLView still being alive
  317. // (see bug 130494).
  318. delete m_currentTrackPage;
  319. delete m_lyricsPage;
  320. delete m_wikiPage;
  321. m_cuefile->clear();
  322. }
  323. //////////////////////////////////////////////////////////////////////////////////////////
  324. // PUBLIC METHODS
  325. //////////////////////////////////////////////////////////////////////////////////////////
  326. void ContextBrowser::setFont( const TQFont &newFont )
  327. {
  328. TQWidget::setFont( newFont );
  329. reloadStyleSheet();
  330. }
  331. //////////////////////////////////////////////////////////////////////////////////////////
  332. // PUBLIC SLOTS
  333. //////////////////////////////////////////////////////////////////////////////////////////
  334. void ContextBrowser::openURLRequest( const KURL &url )
  335. {
  336. TQString artist, album, track;
  337. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );
  338. // All http links should be loaded inside wikipedia tab, as that is the only tab that should contain them.
  339. // Streams should use stream:// protocol.
  340. if ( url.protocol() == "http" )
  341. {
  342. if ( url.hasHTMLRef() )
  343. {
  344. KURL base = url;
  345. base.setRef(TQString());
  346. // Wikipedia also has links to otherpages with Anchors, so we have to check if it's for the current one
  347. if ( m_wikiCurrentUrl == base.url() ) {
  348. m_wikiPage->gotoAnchor( url.htmlRef() );
  349. return;
  350. }
  351. }
  352. // new page
  353. m_dirtyWikiPage = true;
  354. m_wikiCurrentEntry = TQString();
  355. showWikipedia( url.url() );
  356. }
  357. else if ( url.protocol() == "show" )
  358. {
  359. if ( url.path().contains( "suggestLyric-" ) )
  360. {
  361. TQString _url = url.url().mid( url.url().find( TQString( "-" ) ) +1 );
  362. debug() << "Clicked lyrics URL: " << _url << endl;
  363. m_dirtyLyricsPage = true;
  364. showLyrics( _url );
  365. }
  366. else if ( url.path() == "collectionSetup" )
  367. {
  368. CollectionView::instance()->setupDirs();
  369. }
  370. else if ( url.path() == "scriptmanager" )
  371. {
  372. ScriptManager::instance()->show();
  373. ScriptManager::instance()->raise();
  374. }
  375. else if ( url.path() == "editLabels" )
  376. {
  377. showLabelsDialog();
  378. }
  379. // Konqueror sidebar needs these
  380. if (url.path() == "context") { m_dirtyCurrentTrackPage=true; showContext( KURL( "current://track" ) ); saveHtmlData(); }
  381. if (url.path() == "wiki") { m_dirtyWikiPage=true; showWikipedia(); saveHtmlData(); }
  382. if (url.path() == "lyrics") { m_dirtyLyricsPage=true; m_wikiJob=0; showLyrics(); saveHtmlData(); }
  383. }
  384. else if ( url.protocol() == "runscript" )
  385. {
  386. ScriptManager::instance()->runScript( url.path() );
  387. }
  388. // When left-clicking on cover image, open browser with amazon site
  389. else if ( url.protocol() == "fetchcover" )
  390. {
  391. TQString albumPath = CollectionDB::instance()->albumImage(artist, album, false, 0 );
  392. if ( albumPath == CollectionDB::instance()->notAvailCover( false, 0 ) )
  393. {
  394. CollectionDB::instance()->fetchCover( this, artist, album, false );
  395. return;
  396. }
  397. TQImage img( albumPath );
  398. const TQString amazonUrl = img.text( "amazon-url" );
  399. if ( amazonUrl.isEmpty() )
  400. KMessageBox::information( this, i18n( "<p>There is no product information available for this image.<p>Right-click on image for menu." ) );
  401. else
  402. Amarok::invokeBrowser( amazonUrl );
  403. }
  404. /* open konqueror with musicbrainz search result for artist-album */
  405. else if ( url.protocol() == "musicbrainz" )
  406. {
  407. const TQString url = "http://www.musicbrainz.org/taglookup.html?artist=%1&album=%2&track=%3";
  408. Amarok::invokeBrowser( url.arg( KURL::encode_string_no_slash( artist, 106 /*utf-8*/ ),
  409. KURL::encode_string_no_slash( album, 106 /*utf-8*/ ),
  410. KURL::encode_string_no_slash( track, 106 /*utf-8*/ ) ) );
  411. }
  412. else if ( url.protocol() == "externalurl" )
  413. Amarok::invokeBrowser( url.url().replace( TQRegExp( "^externalurl:" ), "http:") );
  414. else if ( url.protocol() == "lastfm" )
  415. {
  416. LastFm::WebService *lfm = LastFm::Controller::instance()->getService();
  417. if ( url.path() == "skip" ) lfm->skip();
  418. else if ( url.path() == "love" ) lfm->love();
  419. else if ( url.path() == "ban" ) lfm->ban();
  420. }
  421. else if ( url.protocol() == "togglebox" )
  422. {
  423. if ( url.path() == "ra" ) m_relatedOpen ^= true;
  424. else if ( url.path() == "ss" ) m_suggestionsOpen ^= true;
  425. else if ( url.path() == "ft" ) m_favoritesOpen ^= true;
  426. else if ( url.path() == "sl" ) m_labelsOpen ^= true;
  427. }
  428. else if ( url.protocol() == "seek" )
  429. {
  430. EngineController::instance()->seek(url.path().toLong());
  431. }
  432. // browse albums of a related artist. Don't do this if we are viewing Home tab
  433. else if ( url.protocol() == "artist" || url.protocol() == "current" || url.protocol() == "showlabel")
  434. {
  435. if( EngineController::engine()->loaded() ) // song must be active
  436. showContext( url );
  437. }
  438. else if( url.protocol() == "artistback" )
  439. {
  440. contextHistoryBack();
  441. }
  442. else if ( url.protocol() == "wikipedia" )
  443. {
  444. m_dirtyWikiPage = true;
  445. TQString entry = unescapeHTMLAttr( url.path() );
  446. showWikipediaEntry( entry );
  447. }
  448. else if( url.protocol() == "ggartist" )
  449. {
  450. const TQString url2 = TQString( "http://www.google.com/musicsearch?q=%1&res=artist" )
  451. .arg( KURL::encode_string_no_slash( unescapeHTMLAttr( url.path() ).replace( " ", "+" ), 106 /*utf-8*/ ) );
  452. Amarok::invokeBrowser( url2 );
  453. }
  454. else if( url.protocol() == "file" )
  455. {
  456. Playlist::instance()->insertMedia( url, Playlist::DefaultOptions );
  457. }
  458. else if( url.protocol() == "stream" )
  459. {
  460. Playlist::instance()->insertMedia( KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) ), Playlist::DefaultOptions );
  461. }
  462. else if( url.protocol() == "compilationdisc" || url.protocol() == "albumdisc" )
  463. {
  464. Playlist::instance()->insertMedia( expandURL( url ) , Playlist::DefaultOptions );
  465. }
  466. else
  467. HTMLView::openURLRequest( url );
  468. }
  469. void ContextBrowser::collectionScanStarted()
  470. {
  471. m_emptyDB = CollectionDB::instance()->isEmpty();
  472. if( m_emptyDB && currentPage() == m_contextTab )
  473. showCurrentTrack();
  474. }
  475. void ContextBrowser::collectionScanDone( bool changed )
  476. {
  477. if ( CollectionDB::instance()->isEmpty() )
  478. {
  479. m_emptyDB = true;
  480. if ( currentPage() == m_contextTab )
  481. showCurrentTrack();
  482. }
  483. else if ( m_emptyDB )
  484. {
  485. m_emptyDB = false;
  486. PlaylistWindow::self()->showBrowser("CollectionBrowser");
  487. }
  488. else if( changed && currentPage() == m_contextTab )
  489. {
  490. m_dirtyCurrentTrackPage = true;
  491. showCurrentTrack();
  492. }
  493. }
  494. void ContextBrowser::renderView()
  495. {
  496. m_dirtyCurrentTrackPage = true;
  497. m_dirtyLyricsPage = true;
  498. m_dirtyWikiPage = true;
  499. m_emptyDB = CollectionDB::instance()->isEmpty();
  500. showCurrentTrack();
  501. }
  502. void ContextBrowser::lyricsChanged( const TQString &url ) {
  503. if ( url == EngineController::instance()->bundle().url().path() ) {
  504. m_dirtyLyricsPage = true;
  505. if ( currentPage() == m_lyricsTab )
  506. showLyrics();
  507. }
  508. }
  509. void ContextBrowser::lyricsScriptChanged() {
  510. m_dirtyLyricsPage = true;
  511. if ( currentPage() == m_lyricsTab )
  512. showLyrics();
  513. }
  514. //////////////////////////////////////////////////////////////////////////////////////////
  515. // PROTECTED
  516. //////////////////////////////////////////////////////////////////////////////////////////
  517. void ContextBrowser::engineNewMetaData( const MetaBundle& bundle, bool trackChanged )
  518. {
  519. bool newMetaData = false;
  520. m_dirtyCurrentTrackPage = true;
  521. m_dirtyLyricsPage = true;
  522. m_wikiJob = 0; //New metadata, so let's forget previous wiki-fetching jobs
  523. if ( MetaBundle( m_currentURL ).artist() != bundle.artist() )
  524. m_dirtyWikiPage = true;
  525. // Prepend stream metadata history item to list
  526. if ( !m_metadataHistory.first().contains( bundle.prettyTitle() ) )
  527. {
  528. newMetaData = true;
  529. const TQString timeString = TDEGlobal::locale()->formatTime( TQTime::currentTime() ).replace(" ", "&nbsp;"); // don't break over lines
  530. m_metadataHistory.prepend( TQString( "<td valign='top'>" + timeString + "&nbsp;</td><td align='left'>" + escapeHTML( bundle.prettyTitle() ) + "</td>" ) );
  531. }
  532. if ( currentPage() == m_contextTab && ( bundle.url() != m_currentURL || newMetaData || !trackChanged ) )
  533. showCurrentTrack();
  534. else if ( currentPage() == m_lyricsTab )
  535. {
  536. EngineController::engine()->isStream() ?
  537. lyricsRefresh() : // can't call showLyrics() because the url hasn't changed
  538. showLyrics() ;
  539. }
  540. else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() )
  541. showCurrentTrack();
  542. if (trackChanged)
  543. {
  544. m_cuefile->clear();
  545. if (bundle.url().isLocalFile())
  546. {
  547. /** The cue file that is provided with the media might have different name than the
  548. * media file itself, hence simply cutting the media extension and adding ".cue"
  549. * is not always enough to find the matching cue file. In such cases we have
  550. * to search for all the cue files in the directory and have a look inside them for
  551. * the matching FILE="" stanza. However the FILE="" stanza does not always
  552. * point at the corresponding media file (e.g. it is quite often set to the misleading
  553. * FILE="audio.wav" WAV). Therfore we also have to check blindly if there is a cue
  554. * file having the same name as the media file played, as described above.
  555. */
  556. // look for the cue file that matches the media file played first
  557. TQString path = bundle.url().path();
  558. TQString cueFile = path.left( path.findRev('.') ) + ".cue";
  559. m_cuefile->setCueFileName( cueFile );
  560. if( m_cuefile->load( bundle.length() ) )
  561. debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly, found and loaded. " << endl;
  562. // if unlucky, let's have a look inside cue files, if any
  563. else
  564. {
  565. debug() << "[CUEFILE]: " << cueFile << " - Shoot blindly and missed, searching for other cue files." << endl;
  566. bool foundCueFile = false;
  567. TQDir dir ( bundle.directory() );
  568. dir.setFilter( TQDir::Files ) ;
  569. dir.setNameFilter( "*.cue *.CUE" ) ;
  570. TQStringList cueFilesList = dir.entryList();
  571. if ( !cueFilesList.empty() )
  572. for ( TQStringList::Iterator it = cueFilesList.begin(); it != cueFilesList.end() && !foundCueFile; ++it )
  573. {
  574. TQFile file ( dir.filePath(*it) );
  575. if( file.open( IO_ReadOnly ) )
  576. {
  577. debug() << "[CUEFILE]: " << *it << " - Opened, looking for the matching FILE stanza." << endl;
  578. TQTextStream stream( &file );
  579. TQString line;
  580. while ( !stream.atEnd() && !foundCueFile)
  581. {
  582. line = stream.readLine().simplifyWhiteSpace();
  583. if( line.startsWith( "file", false ) )
  584. {
  585. line = line.mid( 5 ).remove( '"' );
  586. if ( line.contains( bundle.filename(), false ) )
  587. {
  588. cueFile = dir.filePath(*it);
  589. foundCueFile = true;
  590. m_cuefile->setCueFileName( cueFile );
  591. if( m_cuefile->load( bundle.length() ) )
  592. debug() << "[CUEFILE]: " << cueFile << " - Looked inside cue files, found and loaded proper one" << endl;
  593. }
  594. }
  595. }
  596. file.close();
  597. }
  598. }
  599. if ( !foundCueFile )
  600. debug() << "[CUEFILE]: - Didn't find any matching cue file." << endl;
  601. }
  602. }
  603. }
  604. }
  605. void ContextBrowser::engineStateChanged( Engine::State state, Engine::State oldState )
  606. {
  607. DEBUG_BLOCK
  608. if( state != Engine::Paused /*pause*/ && oldState != Engine::Paused /*resume*/
  609. || state == Engine::Empty )
  610. {
  611. // Pause shouldn't clear everything (but stop should, even when paused)
  612. m_dirtyCurrentTrackPage = true;
  613. m_dirtyLyricsPage = true;
  614. m_wikiJob = 0; //let's forget previous wiki-fetching jobs
  615. }
  616. switch( state )
  617. {
  618. case Engine::Empty:
  619. m_metadataHistory.clear();
  620. if ( currentPage() == m_contextTab || currentPage() == m_lyricsTab )
  621. {
  622. showCurrentTrack();
  623. }
  624. blockSignals( true );
  625. setTabEnabled( m_lyricsTab, false );
  626. if ( currentPage() != m_wikiTab ) {
  627. setTabEnabled( m_wikiTab, false );
  628. m_dirtyWikiPage = true;
  629. }
  630. else // current tab is wikitab, disable some buttons.
  631. {
  632. m_wikiToolBar->setItemEnabled( WIKI_ARTIST, false );
  633. m_wikiToolBar->setItemEnabled( WIKI_ALBUM, false );
  634. m_wikiToolBar->setItemEnabled( WIKI_TITLE, false );
  635. }
  636. blockSignals( false );
  637. break;
  638. case Engine::Playing:
  639. if ( oldState != Engine::Paused )
  640. m_metadataHistory.clear();
  641. blockSignals( true );
  642. setTabEnabled( m_lyricsTab, true );
  643. setTabEnabled( m_wikiTab, true );
  644. m_wikiToolBar->setItemEnabled( WIKI_ARTIST, true );
  645. m_wikiToolBar->setItemEnabled( WIKI_ALBUM, true );
  646. m_wikiToolBar->setItemEnabled( WIKI_TITLE, true );
  647. blockSignals( false );
  648. break;
  649. default:
  650. ;
  651. }
  652. }
  653. void ContextBrowser::saveHtmlData()
  654. {
  655. TQFile exportedDocument( Amarok::saveLocation() + "contextbrowser.html" );
  656. if ( !exportedDocument.open( IO_WriteOnly ) )
  657. warning() << "Failed to open file " << exportedDocument.name()
  658. << " write-only" << endl;
  659. else {
  660. TQTextStream stream( &exportedDocument );
  661. stream.setEncoding( TQTextStream::UnicodeUTF8 );
  662. stream << m_HTMLSource // the pure html data..
  663. .replace( "<html>",
  664. TQString( "<html><head><style type=\"text/css\">"
  665. "%1</style></head>" )
  666. .arg( HTMLView::loadStyleSheet() ) ); // and the
  667. // stylesheet
  668. // code
  669. exportedDocument.close();
  670. }
  671. }
  672. void ContextBrowser::paletteChange( const TQPalette& /* pal */ )
  673. {
  674. // KTabWidget::paletteChange( pal );
  675. HTMLView::paletteChange();
  676. reloadStyleSheet();
  677. }
  678. void ContextBrowser::reloadStyleSheet()
  679. {
  680. m_currentTrackPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
  681. m_lyricsPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
  682. m_wikiPage->setUserStyleSheet( HTMLView::loadStyleSheet() );
  683. }
  684. //////////////////////////////////////////////////////////////////////////////////////////
  685. // PROTECTED SLOTS
  686. //////////////////////////////////////////////////////////////////////////////////////////
  687. //parts of this function from ktabwidget.cpp, copyright (C) 2003 Zack Rusin and Stephan Binner
  688. //setCurrentTab() isn't virtual so we have to override this instead =(
  689. void ContextBrowser::wheelDelta( int delta )
  690. {
  691. if ( count() < 2 || delta == 0 )
  692. return;
  693. int index = currentPageIndex(), start = index;
  694. do
  695. {
  696. if( delta < 0 )
  697. index = (index + 1) % count();
  698. else
  699. {
  700. index = index - 1;
  701. if( index < 0 )
  702. index = count() - 1;
  703. }
  704. if( index == start ) // full circle, none enabled
  705. return;
  706. } while( !isTabEnabled( page( index ) ) );
  707. setCurrentPage( index );
  708. }
  709. //////////////////////////////////////////////////////////////////////////////////////////
  710. // PRIVATE SLOTS
  711. //////////////////////////////////////////////////////////////////////////////////////////
  712. void ContextBrowser::tabChanged( TQWidget *page )
  713. {
  714. DEBUG_FUNC_INFO
  715. setFocusProxy( page ); //so focus is given to a sensible widget when the tab is opened
  716. if ( page == m_contextTab )
  717. showCurrentTrack();
  718. else if ( page == m_lyricsTab )
  719. showLyrics();
  720. else if ( page == m_wikiTab )
  721. showWikipedia();
  722. }
  723. void ContextBrowser::slotContextMenu( const TQString& urlString, const TQPoint& point )
  724. {
  725. enum { APPEND, ASNEXT, MAKE, MEDIA_DEVICE, INFO, TITLE, RELATED, SUGGEST, FAVES, FRESHPODCASTS, NEWALBUMS, FAVALBUMS, LABELS };
  726. debug() << "url string: " << urlString << endl;
  727. if( urlString.startsWith( "musicbrainz" ) ||
  728. urlString.startsWith( "externalurl" ) ||
  729. urlString.startsWith( "show:suggest" ) ||
  730. urlString.startsWith( "http" ) ||
  731. urlString.startsWith( "wikipedia" ) ||
  732. urlString.startsWith( "seek" ) ||
  733. urlString.startsWith( "ggartist" ) ||
  734. urlString.startsWith( "artistback" ) ||
  735. urlString.startsWith( "current" ) ||
  736. urlString.startsWith( "lastfm" ) ||
  737. urlString.startsWith( "showlabel" ) ||
  738. urlString.startsWith( "show:editLabels" ) ||
  739. currentPage() != m_contextTab )
  740. return;
  741. KURL url( urlString );
  742. TDEPopupMenu menu;
  743. KURL::List urls( url );
  744. TQString artist, album, track; // track unused here
  745. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );
  746. if( urlString.isEmpty() )
  747. {
  748. debug() << "url string empty. loaded?" << EngineController::engine()->loaded() << endl;
  749. if( EngineController::engine()->loaded() )
  750. {
  751. menu.setCheckable( true );
  752. menu.insertItem( i18n("Show Labels"), LABELS );
  753. menu.insertItem( i18n("Show Related Artists"), RELATED );
  754. menu.insertItem( i18n("Show Suggested Songs"), SUGGEST );
  755. menu.insertItem( i18n("Show Favorite Tracks"), FAVES );
  756. menu.setItemChecked( RELATED, m_showRelated );
  757. menu.setItemChecked( SUGGEST, m_showSuggested );
  758. menu.setItemChecked( FAVES, m_showFaves );
  759. menu.setItemChecked( LABELS, m_showLabels );
  760. } else {
  761. // the home info page
  762. menu.setCheckable( true );
  763. menu.insertItem( i18n("Show Fresh Podcasts"), FRESHPODCASTS );
  764. menu.insertItem( i18n("Show Newest Albums"), NEWALBUMS );
  765. menu.insertItem( i18n("Show Favorite Albums"), FAVALBUMS );
  766. menu.setItemChecked( FRESHPODCASTS, m_showFreshPodcasts );
  767. menu.setItemChecked( NEWALBUMS, m_showNewestAlbums );
  768. menu.setItemChecked( FAVALBUMS, m_showFavoriteAlbums );
  769. }
  770. }
  771. else if( url.protocol() == "fetchcover" )
  772. {
  773. Amarok::coverContextMenu( this, point, artist, album );
  774. return;
  775. }
  776. else if( url.protocol() == "stream" )
  777. {
  778. url = KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) );
  779. urls = KURL::List( url );
  780. menu.insertTitle( i18n("Podcast"), TITLE );
  781. menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE );
  782. menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
  783. menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Podcast" ), ASNEXT );
  784. //menu.insertSeparator();
  785. //menu.insertItem( SmallIconSet( "go-down" ), i18n( "&Download" ), DOWNLOAD );
  786. }
  787. else if( url.protocol() == "file" || url.protocol() == "artist" || url.protocol() == "album" || url.protocol() == "compilation" || url.protocol() == "albumdisc" || url.protocol() == "compilationdisc")
  788. {
  789. //TODO it would be handy and more usable to have this menu under the cover one too
  790. menu.insertTitle( i18n("Track"), TITLE );
  791. menu.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n( "&Load" ), MAKE );
  792. menu.insertItem( SmallIconSet( Amarok::icon( "add_playlist" ) ), i18n( "&Append to Playlist" ), APPEND );
  793. menu.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n( "&Queue Track" ), ASNEXT );
  794. if( MediaBrowser::isAvailable() )
  795. menu.insertItem( SmallIconSet( Amarok::icon( "device" ) ), i18n( "&Transfer to Media Device" ), MEDIA_DEVICE );
  796. menu.insertSeparator();
  797. menu.insertItem( SmallIconSet( Amarok::icon( "info" ) ), i18n( "Edit Track &Information..." ), INFO );
  798. if ( url.protocol() == "artist" )
  799. {
  800. urls = expandURL( url );
  801. menu.changeTitle( TITLE, i18n("Artist") );
  802. menu.changeItem( INFO, i18n("Edit Artist &Information..." ) );
  803. menu.changeItem( ASNEXT, i18n("&Queue Artist's Songs") );
  804. }
  805. if ( url.protocol() == "album" )
  806. {
  807. urls = expandURL( url );
  808. menu.changeTitle( TITLE, i18n("Album") );
  809. menu.changeItem( INFO, i18n("Edit Album &Information..." ) );
  810. menu.changeItem( ASNEXT, i18n("&Queue Album") );
  811. }
  812. if ( url.protocol() == "albumdisc" )
  813. {
  814. urls = expandURL( url );
  815. menu.changeTitle( TITLE, i18n("Album Disc") );
  816. menu.changeItem( INFO, i18n("Edit Album Disc &Information..." ) );
  817. menu.changeItem( ASNEXT, i18n("&Queue Album Disc") );
  818. }
  819. if ( url.protocol() == "compilation" )
  820. {
  821. urls = expandURL( url );
  822. menu.changeTitle( TITLE, i18n("Compilation") );
  823. menu.changeItem( INFO, i18n("Edit Album &Information..." ) );
  824. menu.changeItem( ASNEXT, i18n("&Queue Album") );
  825. }
  826. if ( url.protocol() == "compilationdisc" )
  827. {
  828. urls = expandURL( url );
  829. menu.changeTitle( TITLE, i18n("Compilation Disc") );
  830. menu.changeItem( INFO, i18n("Edit Compilation Disc &Information..." ) );
  831. menu.changeItem( ASNEXT, i18n("&Queue Compilation Disc") );
  832. }
  833. if( urls.count() == 0 )
  834. {
  835. menu.setItemEnabled( MAKE, false );
  836. menu.setItemEnabled( APPEND, false );
  837. menu.setItemEnabled( ASNEXT, false );
  838. menu.setItemEnabled( MEDIA_DEVICE, false );
  839. menu.setItemEnabled( INFO, false );
  840. }
  841. }
  842. //Not all these are used in the menu, it depends on the context
  843. switch( menu.exec( point ) )
  844. {
  845. case RELATED:
  846. m_showRelated = !menu.isItemChecked( RELATED );
  847. Amarok::config( "ContextBrowser" )->writeEntry( "ShowRelated", m_showRelated );
  848. m_dirtyCurrentTrackPage = true;
  849. showCurrentTrack();
  850. break;
  851. case SUGGEST:
  852. m_showSuggested = !menu.isItemChecked( SUGGEST );
  853. Amarok::config( "ContextBrowser" )->writeEntry( "ShowSuggested", m_showSuggested );
  854. m_dirtyCurrentTrackPage = true;
  855. showCurrentTrack();
  856. break;
  857. case FAVES:
  858. m_showFaves = !menu.isItemChecked( FAVES );
  859. Amarok::config( "ContextBrowser" )->writeEntry( "ShowFaves", m_showFaves );
  860. m_dirtyCurrentTrackPage = true;
  861. showCurrentTrack();
  862. break;
  863. case LABELS:
  864. m_showLabels = !menu.isItemChecked( LABELS );
  865. Amarok::config( "ContextBrowser" )->writeEntry( "ShowLabels", m_showLabels );
  866. m_dirtyCurrentTrackPage = true;
  867. showCurrentTrack();
  868. break;
  869. case FRESHPODCASTS:
  870. m_showFreshPodcasts = !menu.isItemChecked( FRESHPODCASTS );
  871. Amarok::config( "ContextBrowser" )->writeEntry( "ShowFreshPodcasts", m_showFreshPodcasts );
  872. m_dirtyCurrentTrackPage = true;
  873. showCurrentTrack();
  874. break;
  875. case NEWALBUMS:
  876. m_showNewestAlbums = !menu.isItemChecked( NEWALBUMS );
  877. Amarok::config( "ContextBrowser" )->writeEntry( "ShowNewestAlbums", m_showNewestAlbums );
  878. m_dirtyCurrentTrackPage = true;
  879. showCurrentTrack();
  880. break;
  881. case FAVALBUMS:
  882. m_showFavoriteAlbums = !menu.isItemChecked( FAVALBUMS );
  883. Amarok::config( "ContextBrowser" )->writeEntry( "ShowFavoriteAlbums", m_showFavoriteAlbums );
  884. m_dirtyCurrentTrackPage = true;
  885. showCurrentTrack();
  886. break;
  887. case ASNEXT:
  888. Playlist::instance()->insertMedia( urls, Playlist::Queue );
  889. break;
  890. case INFO:
  891. {
  892. if ( urls.count() > 1 )
  893. {
  894. TagDialog* dialog = new TagDialog( urls, instance() );
  895. dialog->show();
  896. }
  897. else if ( !urls.isEmpty() )
  898. {
  899. TagDialog* dialog = new TagDialog( urls.first(), instance() );
  900. dialog->show();
  901. }
  902. break;
  903. }
  904. case MAKE:
  905. Playlist::instance()->clear();
  906. //FALL_THROUGH
  907. case APPEND:
  908. Playlist::instance()->insertMedia( urls, Playlist::Append );
  909. break;
  910. case MEDIA_DEVICE:
  911. MediaBrowser::queue()->addURLs( urls );
  912. break;
  913. }
  914. }
  915. //////////////////////////////////////////////////////////////////////////////////////////
  916. // Current-Tab
  917. //////////////////////////////////////////////////////////////////////////////////////////
  918. /** This is the slowest part of track change, so we thread it */
  919. class CurrentTrackJob : public ThreadManager::DependentJob
  920. {
  921. public:
  922. CurrentTrackJob( ContextBrowser *parent )
  923. : ThreadManager::DependentJob( TQT_TQOBJECT(parent), "CurrentTrackJob" )
  924. , b( parent )
  925. , m_currentTrack( TQDeepCopy<MetaBundle>( EngineController::instance()->bundle() ) )
  926. , m_isStream( EngineController::engine()->isStream() )
  927. {
  928. for( TQStringList::iterator it = b->m_metadataHistory.begin();
  929. it != b->m_metadataHistory.end();
  930. ++it )
  931. {
  932. m_metadataHistory += TQDeepCopy<TQString>( *it );
  933. }
  934. m_amarokIconPath = TQDeepCopy<TQString>(TDEGlobal::iconLoader()->iconPath( "amarok",
  935. -TDEIcon::SizeEnormous ) );
  936. m_musicBrainIconPath = TQDeepCopy<TQString>(locate( "data", "amarok/images/musicbrainz.png" )
  937. );
  938. m_lastfmIcon = "file://" + locate( "data","amarok/images/lastfm.png" );
  939. }
  940. private:
  941. virtual bool doJob();
  942. void addMetaHistory();
  943. void showLastFm( const MetaBundle &currentTrack );
  944. void showStream( const MetaBundle &currentTrack );
  945. void showPodcast( const MetaBundle &currentTrack );
  946. void showBrowseArtistHeader( const TQString &artist );
  947. void showBrowseLabelHeader( const TQString &label );
  948. void showCurrentArtistHeader( const MetaBundle &currentTrack );
  949. void showRelatedArtists( const TQString &artist, const TQStringList &relArtists );
  950. void showSuggestedSongs( const TQStringList &relArtists );
  951. void showSongsWithLabel( const TQString &label );
  952. void showArtistsFaves( const TQString &artistName, uint artist_id );
  953. void showArtistsAlbums( const TQString &artist, uint artist_id, uint album_id );
  954. void showArtistsCompilations( const TQString &artist, uint artist_id, uint album_id );
  955. void showHome();
  956. void showUserLabels( const MetaBundle &currentTrack );
  957. TQString fetchLastfmImage( const TQString& url );
  958. TQStringList showHomeByAlbums();
  959. void constructHTMLAlbums( const TQStringList &albums, TQString &htmlCode, const TQString &idPrefix );
  960. static TQString statsHTML( int score, int rating, bool statsbox = true ); // meh.
  961. virtual void completeJob()
  962. {
  963. // are we still showing the currentTrack page?
  964. // if( b->currentPage() != b->m_contextTab )
  965. // return;
  966. b->m_shownAlbums.clear();
  967. for( TQStringList::iterator it = m_shownAlbums.begin();
  968. it != m_shownAlbums.end();
  969. ++it )
  970. b->m_shownAlbums.append( TQDeepCopy<TQString>( *it ) );
  971. b->m_HTMLSource = TQDeepCopy<TQString>( m_HTMLSource );
  972. b->m_currentTrackPage->set( m_HTMLSource );
  973. b->m_dirtyCurrentTrackPage = false;
  974. b->saveHtmlData(); // Send html code to file
  975. }
  976. TQString m_HTMLSource;
  977. TQString m_amarokIconPath;
  978. TQString m_musicBrainIconPath;
  979. TQString m_lastfmIcon;
  980. ContextBrowser *b;
  981. MetaBundle m_currentTrack;
  982. bool m_isStream;
  983. TQStringList m_shownAlbums;
  984. TQStringList m_metadataHistory;
  985. };
  986. void
  987. ContextBrowser::showContext( const KURL &url, bool fromHistory )
  988. {
  989. if ( currentPage() != m_contextTab )
  990. {
  991. blockSignals( true );
  992. showPage( m_contextTab );
  993. blockSignals( false );
  994. }
  995. m_dirtyCurrentTrackPage = true;
  996. m_contextURL = url.url();
  997. if( url.protocol() == "current" )
  998. {
  999. m_browseArtists = false;
  1000. m_browseLabels = false;
  1001. m_label = TQString();
  1002. m_artist = TQString();
  1003. m_contextBackHistory.clear();
  1004. m_contextBackHistory.push_back( "current://track" );
  1005. }
  1006. else if( url.protocol() == "artist" )
  1007. {
  1008. m_browseArtists = true;
  1009. m_browseLabels = false;
  1010. m_label = TQString();
  1011. m_artist = unescapeHTMLAttr( url.path() );
  1012. }
  1013. else if( url.protocol() == "showlabel" )
  1014. {
  1015. m_browseLabels = true;
  1016. m_browseArtists = false;
  1017. m_artist = TQString();
  1018. m_label = unescapeHTMLAttr( url.path() );
  1019. }
  1020. // Append new URL to history
  1021. if ( !fromHistory ) {
  1022. m_contextBackHistory += m_contextURL.url();
  1023. }
  1024. // Limit number of items in history
  1025. if ( m_contextBackHistory.count() > CONTEXT_MAX_HISTORY )
  1026. m_contextBackHistory.pop_front();
  1027. showCurrentTrack();
  1028. }
  1029. void
  1030. ContextBrowser::contextHistoryBack() //SLOT
  1031. {
  1032. if( m_contextBackHistory.size() > 0 )
  1033. {
  1034. m_contextBackHistory.pop_back();
  1035. m_dirtyCurrentTrackPage = true;
  1036. showContext( KURL( m_contextBackHistory.last() ), true );
  1037. }
  1038. }
  1039. void ContextBrowser::showCurrentTrack() //SLOT
  1040. {
  1041. #if 0
  1042. if( BrowserBar::instance()->currentBrowser() != this )
  1043. {
  1044. debug() << "current browser is not context, aborting showCurrentTrack()" << endl;
  1045. m_dirtyCurrentTrackPage = true;
  1046. m_currentTrackPage->set( TQString( "<html><body><div class='box-body'>%1</div></body></html>" )
  1047. .arg( i18n( "Updating..." ) ) );
  1048. return;
  1049. }
  1050. #endif
  1051. if ( currentPage() != m_contextTab ) {
  1052. blockSignals( true );
  1053. showPage( m_contextTab );
  1054. blockSignals( false );
  1055. }
  1056. // TODO: Show CurrentTrack or Lyric tab if they were selected
  1057. // If it's not a streaming, check for a collection
  1058. if ( !EngineController::engine()->isStream() )
  1059. {
  1060. if ( m_emptyDB && CollectionDB::instance()->isValid() && !MountPointManager::instance()->collectionFolders().isEmpty() )
  1061. {
  1062. showScanning();
  1063. return;
  1064. }
  1065. else if ( CollectionDB::instance()->isEmpty() || !CollectionDB::instance()->isValid() )
  1066. {
  1067. showIntroduction();
  1068. return;
  1069. }
  1070. }
  1071. if( !m_dirtyCurrentTrackPage )
  1072. return;
  1073. m_currentURL = EngineController::instance()->bundle().url();
  1074. m_currentTrackPage->write( TQString() );
  1075. ThreadManager::instance()->onlyOneJob( new CurrentTrackJob( this ) );
  1076. }
  1077. //////////////////////////////////////////////////////////////////////////////////////////
  1078. // Shows the statistics summary when no track is playing
  1079. //////////////////////////////////////////////////////////////////////////////////////////
  1080. void CurrentTrackJob::showHome()
  1081. {
  1082. QueryBuilder qb;
  1083. qb.clear(); //Song count
  1084. qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabSong, QueryBuilder::valURL );
  1085. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1086. TQStringList a = qb.run();
  1087. TQString songCount = a[0];
  1088. qb.clear(); //Artist count
  1089. //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabArtist, QueryBuilder::valID );
  1090. //qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1091. //a = qb.run();
  1092. //TQString artistCount = a[0];
  1093. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1094. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valArtistID );
  1095. //I can't get the correct value w/o suing a subquery, and querybuilder doesn't support those
  1096. TQString artistCount = TQString::number( qb.run().count() );
  1097. qb.clear(); //Album count
  1098. //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabAlbum, QueryBuilder::valID );
  1099. //qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1100. //a = qb.run();
  1101. //TQString albumCount = a[0];
  1102. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1103. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valAlbumID );
  1104. TQString albumCount = TQString::number( qb.run().count() );
  1105. qb.clear(); //Genre count
  1106. //qb.addReturnFunctionValue( QueryBuilder::funcCount, QueryBuilder::tabGenre, QueryBuilder::valID );
  1107. //qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1108. //a = qb.run();
  1109. //TQString genreCount = a[0];
  1110. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1111. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valGenreID );
  1112. TQString genreCount = TQString::number( qb.run().count() );
  1113. qb.clear(); //Total Playtime
  1114. qb.addReturnFunctionValue( QueryBuilder::funcSum, QueryBuilder::tabSong, QueryBuilder::valLength );
  1115. a = qb.run();
  1116. TQString playTime = MetaBundle::fuzzyTime( a[0].toInt() );
  1117. m_HTMLSource.append(
  1118. QStringx(
  1119. "<div id='introduction_box' class='box'>\n"
  1120. "<div id='introduction_box-header-title' class='box-header'>\n"
  1121. "<span id='introduction_box-header-title' class='box-header-title'>\n"
  1122. + i18n( "No Track Playing" ) +
  1123. "</span>\n"
  1124. "</div>\n"
  1125. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1126. "<tr>\n"
  1127. "<td id='current_box-largecover-td'>\n"
  1128. "<a href='%1'><img id='current_box-largecover-image' src='%2' title='Amarok'></a>\n"
  1129. "</td>\n"
  1130. "<td id='current_box-information-td' align='right'>\n"
  1131. "<span>%3</span><br />\n"
  1132. "<span>%4</span><br />\n"
  1133. "<span>%5</span><br />\n"
  1134. "<span>%6</span><br />\n"
  1135. "<span>%7</span><br />\n"
  1136. "</td>\n"
  1137. "</tr>\n"
  1138. "</table>\n"
  1139. "</div>\n" )
  1140. .args( TQStringList()
  1141. << escapeHTMLAttr( "externalurl://amarok.kde.org" )
  1142. << escapeHTMLAttr( m_amarokIconPath )
  1143. << i18n( "1 Track", "%n Tracks", songCount.toInt() )
  1144. << i18n( "1 Artist", "%n Artists", artistCount.toInt() )
  1145. << i18n( "1 Album", "%n Albums", albumCount.toInt() )
  1146. << i18n( "1 Genre", "%n Genres", genreCount.toInt() )
  1147. << i18n( "%1 Play-time" ).arg ( playTime ) ) );
  1148. m_shownAlbums = showHomeByAlbums();
  1149. m_HTMLSource.append(
  1150. "</div></body></html>\n");
  1151. }
  1152. void
  1153. CurrentTrackJob::constructHTMLAlbums( const TQStringList &reqResult, TQString &htmlCode, const TQString &stID )
  1154. {
  1155. // This function create the html code used to display a list of albums. Each album
  1156. // is a 'toggleable' block.
  1157. // Parameter stID is used to differentiate same albums in different album list. So if this function
  1158. // is called multiple time in the same HTML code, stID must be different.
  1159. for ( uint i = 0; i < reqResult.count(); i += 4 )
  1160. {
  1161. QueryBuilder qb;
  1162. qb.clear();
  1163. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  1164. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  1165. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
  1166. qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
  1167. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
  1168. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
  1169. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
  1170. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  1171. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, reqResult[i+1] );
  1172. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, reqResult[i+3] );
  1173. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  1174. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
  1175. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle );
  1176. qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
  1177. TQStringList albumValues = qb.run();
  1178. TQString albumYear;
  1179. if ( !albumValues.isEmpty() )
  1180. {
  1181. albumYear = albumValues[ 3 ];
  1182. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues())
  1183. if ( albumValues[j + 3] != albumYear || albumYear == "0" )
  1184. {
  1185. albumYear = TQString();
  1186. break;
  1187. }
  1188. }
  1189. uint i_albumLength = 0;
  1190. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  1191. i_albumLength += TQString(albumValues[j + 4]).toInt();
  1192. TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );
  1193. htmlCode.append( QStringx (
  1194. "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
  1195. "<td>\n"
  1196. "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
  1197. "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
  1198. "<tr>\n")
  1199. .args( TQStringList()
  1200. << stID + reqResult[i+1] ));
  1201. TQString albumName = escapeHTML( reqResult[ i ].isEmpty() ? i18n( "Unknown album" ) : reqResult[ i ] );
  1202. TQString artistName = albumValues[5].isEmpty() ? i18n( "Unknown artist" ) : albumValues[5];
  1203. TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( albumValues[5], reqResult[ i ], true, 50 ) );
  1204. TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );
  1205. // Album image
  1206. htmlCode.append( QStringx (
  1207. "<td width='1'>\n"
  1208. "<a href='fetchcover:%1 @@@ %2'>\n"
  1209. "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4'/>\n"
  1210. "</a>\n"
  1211. "</td>\n"
  1212. "<td valign='middle' align='left'>\n"
  1213. "<a href='artist:%5'>\n"
  1214. "<span class='album-title'>%6</span>\n"
  1215. "</a>\n"
  1216. "<span class='song-separator'> - </span>\n"
  1217. "<a href='album:%7 @@@ %8'>\n"
  1218. "<span class='album-title'>%9</span>\n"
  1219. "</a>\n" )
  1220. .args( TQStringList()
  1221. << escapeHTMLAttr( albumValues[5] ) // artist name
  1222. << escapeHTMLAttr( reqResult[ i ].isEmpty() ? i18n( "Unknown" ) : reqResult[ i ] ) // album.name
  1223. << albumImageTitleAttr
  1224. << escapeHTMLAttr( albumImage )
  1225. << escapeHTMLAttr( artistName )
  1226. << escapeHTML( artistName )
  1227. << albumValues[6]
  1228. << reqResult[ i + 1 ] //album.id
  1229. << albumName ) );
  1230. // Tracks number, year and length
  1231. htmlCode.append( QStringx (
  1232. "<span class='album-info'>%1</span> "
  1233. "<br />\n"
  1234. "<span class='album-year'>%2</span>\n"
  1235. "<span class='album-length'>%3</span>\n"
  1236. "</td>\n")
  1237. .args( TQStringList()
  1238. << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() )
  1239. << albumYear
  1240. << albumLength) );
  1241. // Begining of the 'toggleable div' that contains the songs
  1242. htmlCode.append( QStringx (
  1243. "</tr>\n"
  1244. "</table>\n"
  1245. "</div>\n"
  1246. "<div class='album-body' style='display:%1;' id='IDA%2'>\n" )
  1247. .args( TQStringList()
  1248. << "none" /* shows it if it's the current track album */
  1249. << stID + reqResult[ i + 1 ] ) );
  1250. TQString discNumber;
  1251. if ( !albumValues.isEmpty() )
  1252. {
  1253. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  1254. {
  1255. TQString newDiscNumber = albumValues[ j + 7 ].stripWhiteSpace();
  1256. if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
  1257. {
  1258. discNumber = newDiscNumber;
  1259. htmlCode.append( QStringx (
  1260. "<div class='disc-separator'>\n"
  1261. "<a href=\"albumdisc: %1 @@@ %2 @@@ %3\">\n"
  1262. "%4"
  1263. "</a>\n"
  1264. "</div>\n" )
  1265. .args( TQStringList()
  1266. << albumValues[6]
  1267. << reqResult[ i + 1 ] //album.id
  1268. << escapeHTMLAttr( discNumber )
  1269. << i18n( "Disc %1" ).arg( discNumber ) ) );
  1270. }
  1271. TQString track = albumValues[j + 2].stripWhiteSpace();
  1272. if( track.length() > 0 )
  1273. {
  1274. if( track.length() == 1 )
  1275. track.prepend( "0" );
  1276. track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
  1277. }
  1278. TQString length;
  1279. if( albumValues[j + 4] != "0" )
  1280. length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";
  1281. htmlCode.append(
  1282. "<div class='album-song'>\n"
  1283. "<a href=\"file:" + escapeHTMLAttr( albumValues[j + 1] ) + "\">\n"
  1284. + track +
  1285. "<span class='album-song-title'>\n" + escapeHTML( albumValues[j] ) + "</span>&nbsp;"
  1286. + length +
  1287. "</a>\n"
  1288. "</div>\n" );
  1289. }
  1290. }
  1291. htmlCode.append(
  1292. "</div>\n"
  1293. "</td>\n"
  1294. "</tr>\n" );
  1295. }
  1296. }
  1297. // return list of albums shown
  1298. TQStringList
  1299. CurrentTrackJob::showHomeByAlbums()
  1300. {
  1301. QueryBuilder qb;
  1302. m_HTMLSource.append( "<table width='100%' cellpadding='0' cellspacing='0' border='0'><tr>\n" );
  1303. // <Fresh Podcasts Information>
  1304. if( ContextBrowser::instance()->m_showFreshPodcasts )
  1305. {
  1306. qb.clear();
  1307. qb.addReturnValue( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valParent );
  1308. qb.addFilter( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valIsNew, CollectionDB::instance()->boolT(), QueryBuilder::modeNormal, true );
  1309. qb.sortBy( QueryBuilder::tabPodcastEpisodes, QueryBuilder::valID, true );
  1310. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  1311. qb.setLimit( 0, 5 );
  1312. TQStringList channels = qb.run();
  1313. if( channels.count() > 0 )
  1314. {
  1315. m_HTMLSource.append(
  1316. "<td valign='top'><div id='least_box' class='box'>\n"
  1317. "<div id='least_box-header' class='box-header'>\n"
  1318. "<span id='least_box-header-title' class='box-header-title'>\n"
  1319. + i18n( "Fresh Podcast Episodes" ) +
  1320. "</span>\n"
  1321. "</div>\n"
  1322. "<table id='least_box-body' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n" );
  1323. uint i = 0;
  1324. for( TQStringList::iterator it = channels.begin();
  1325. it != channels.end();
  1326. it++ )
  1327. {
  1328. PodcastChannelBundle pcb;
  1329. if( !CollectionDB::instance()->getPodcastChannelBundle( *it, &pcb ) )
  1330. continue;
  1331. TQValueList<PodcastEpisodeBundle> episodes = CollectionDB::instance()->getPodcastEpisodes( *it, true /* only new */, 1 );
  1332. if( !episodes.isEmpty() )
  1333. {
  1334. PodcastEpisodeBundle &ep = *episodes.begin();
  1335. TQString date;
  1336. ep.dateTime().isNull() ?
  1337. date = ep.date() :
  1338. date = ep.dateTime().toString();
  1339. TQString image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true, 50 );
  1340. TQString imageAttr = escapeHTMLAttr( i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() ) );
  1341. m_HTMLSource.append( QStringx (
  1342. "<tr class='" + TQString( (i % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
  1343. "<td>\n"
  1344. "<div class='album-header' onClick=\"toggleBlock('IDP%1')\">\n"
  1345. "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
  1346. "<tr>\n"
  1347. "<td width='1'>\n"
  1348. "<a href='%2'>\n"
  1349. "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4' />\n"
  1350. "</a>\n"
  1351. "</td>\n"
  1352. "<td valign='middle' align='left'>\n"
  1353. "<span class='album-info'>%5</span> \n"
  1354. "<a href='%6'><span class='album-title'>%7</span></a>\n"
  1355. "<br />\n"
  1356. "<span class='album-year'>%8</span>\n"
  1357. "</td>\n"
  1358. "</tr>\n"
  1359. "</table>\n"
  1360. "</div>\n"
  1361. "<div class='album-body' style='display:%9;' id='IDP%10'>\n" )
  1362. .args( TQStringList()
  1363. << TQString::number( i )
  1364. << pcb.link().url().replace( TQRegExp( "^http:" ), "externalurl:" )
  1365. << escapeHTMLAttr( imageAttr )
  1366. << escapeHTMLAttr( image )
  1367. << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : TQString( "" ) )
  1368. << ( ep.localUrl().isValid()
  1369. ? ep.localUrl().url()
  1370. : ep.url().url().replace( TQRegExp( "^http:" ), "stream:" ) )
  1371. << escapeHTML( pcb.title() + ": " + ep.title() )
  1372. << escapeHTML( date )
  1373. << "none"
  1374. << TQString::number( i )
  1375. )
  1376. );
  1377. m_HTMLSource.append( QStringx ( "<p>%1</p>\n" ).arg( ep.description() ) );
  1378. m_HTMLSource.append(
  1379. "</div>\n"
  1380. "</td>\n"
  1381. "</tr>\n" );
  1382. i++;
  1383. }
  1384. }
  1385. m_HTMLSource.append(
  1386. "</table>\n"
  1387. "</div>\n"
  1388. "</td>\n"
  1389. "</tr>\n"
  1390. "<tr>\n" );
  1391. }
  1392. }
  1393. // </Fresh Podcasts Information>
  1394. TQStringList albums;
  1395. // <Newest Albums Information>
  1396. if( ContextBrowser::instance()->m_showNewestAlbums )
  1397. {
  1398. qb.clear();
  1399. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
  1400. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
  1401. qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate );
  1402. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
  1403. qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabSong, QueryBuilder::valCreateDate, true );
  1404. qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
  1405. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
  1406. qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID );
  1407. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
  1408. qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
  1409. qb.setLimit( 0, 5 );
  1410. TQStringList recentAlbums = qb.run();
  1411. foreach( recentAlbums )
  1412. {
  1413. albums += *it;
  1414. it++;
  1415. it++;
  1416. it++;
  1417. }
  1418. // toggle html here so we get correct albums
  1419. m_HTMLSource.append(
  1420. "<td valign='top'>\n"
  1421. "<div id='least_box' class='box'>\n"
  1422. "<div id='least_box-header' class='box-header'>\n"
  1423. "<span id='least_box-header-title' class='box-header-title'>\n"
  1424. + i18n( "Your Newest Albums" ) +
  1425. "</span>\n"
  1426. "</div>\n"
  1427. "<table id='least_box-body' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n" );
  1428. constructHTMLAlbums( recentAlbums, m_HTMLSource, "1" );
  1429. m_HTMLSource.append(
  1430. "</table>\n"
  1431. "</div>\n"
  1432. "</td>\n"
  1433. "</tr>\n"
  1434. "<tr>\n" );
  1435. }
  1436. // </Recent Tracks Information>
  1437. // <Favorite Albums Information>
  1438. if( ContextBrowser::instance()->m_showFavoriteAlbums )
  1439. {
  1440. qb.clear();
  1441. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
  1442. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
  1443. qb.sortByFavoriteAvg(); // this function adds return values!
  1444. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valID );
  1445. // only albums with more than 3 tracks
  1446. qb.having( QueryBuilder::tabAlbum, QueryBuilder::valID, QueryBuilder::funcCount, QueryBuilder::modeGreater, "3" );
  1447. qb.excludeMatch( QueryBuilder::tabAlbum, i18n( "Unknown" ) );
  1448. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
  1449. qb.groupBy( QueryBuilder::tabArtist, QueryBuilder::valID );
  1450. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
  1451. qb.setOptions( QueryBuilder::optNoCompilations ); // samplers __need__ to be handled differently
  1452. qb.setLimit( 0, 5 );
  1453. TQStringList faveAlbums = qb.run();
  1454. TQStringList faveResults;
  1455. bool ratings = AmarokConfig::useRatings();
  1456. bool scores = AmarokConfig::useScores();
  1457. foreach( faveAlbums ) {
  1458. albums += *it;
  1459. faveResults += *(it++);
  1460. faveResults += *(it++);
  1461. faveResults += *(it++);
  1462. // sortByFavoriteAvg add some return values, and constructHTMLAlbums expects
  1463. // a specific set of return values, so we might need to skip some values
  1464. if ( ratings )
  1465. it++;
  1466. if ( scores )
  1467. it++;
  1468. faveResults += *(it);
  1469. }
  1470. m_HTMLSource.append(
  1471. "<td valign='top'>\n"
  1472. "<div id='albums_box' class='box'>\n"
  1473. "<div id='albums_box-header' class='box-header'>\n"
  1474. "<span id='albums_box-header-title' class='box-header-title'>\n"
  1475. + i18n( "Favorite Albums" ) +
  1476. "</span>\n"
  1477. "</div>\n"
  1478. "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  1479. if ( faveAlbums.count() == 0 )
  1480. {
  1481. m_HTMLSource.append(
  1482. "<div id='favorites_box-body' class='box-body'><p>\n" +
  1483. (QueryBuilder::valForFavoriteSorting() == QueryBuilder::valRating
  1484. ? i18n( "A list of your favorite albums will appear here, once you have rated a few of your songs." )
  1485. : i18n( "A list of your favorite albums will appear here, once you have played a few of your songs." ) ) +
  1486. "</p></div>\n" );
  1487. }
  1488. else
  1489. {
  1490. constructHTMLAlbums( faveResults, m_HTMLSource, "2" );
  1491. }
  1492. m_HTMLSource.append(
  1493. "</table></div></td>\n" );
  1494. }
  1495. // </Favorite Tracks Information>
  1496. m_HTMLSource.append( "</tr></table>\n" );
  1497. return albums;
  1498. }
  1499. void CurrentTrackJob::showLastFm( const MetaBundle &currentTrack )
  1500. {
  1501. if( !LastFm::Controller::instance()->isPlaying() ) return;
  1502. const LastFm::Bundle *lastFmInfo = currentTrack.lastFmBundle();
  1503. if ( !lastFmInfo ) return;
  1504. const TQString username = AmarokConfig::scrobblerUsername();
  1505. const TQString userpage = "www.last.fm/user/" + username; //no http
  1506. const TQString albumUrl = lastFmInfo->albumUrl();
  1507. const TQString artistUrl = lastFmInfo->artistUrl();
  1508. const TQString titleUrl = lastFmInfo->titleUrl();
  1509. const TQString coverImage = ContextBrowser::getEncodedImage( lastFmInfo->imageUrl() );
  1510. TQPtrList<TQString> newUrls;
  1511. newUrls.append( &albumUrl );
  1512. newUrls.append( &artistUrl );
  1513. newUrls.append( &titleUrl );
  1514. for ( TQString* url = newUrls.first(); url; url = newUrls.next() )
  1515. url->replace( TQRegExp( "^http:" ), "externalurl:" );
  1516. const TQString skipIcon = TDEGlobal::iconLoader()->iconPath( Amarok::icon("next"), -TDEIcon::SizeSmallMedium );
  1517. const TQString loveIcon = TDEGlobal::iconLoader()->iconPath( Amarok::icon("love"), -TDEIcon::SizeSmallMedium );
  1518. const TQString banIcon = TDEGlobal::iconLoader()->iconPath( Amarok::icon("remove"), -TDEIcon::SizeSmallMedium );
  1519. m_HTMLSource.append( QStringx(
  1520. "<div id='current_box' class='box'>\n"
  1521. "<div id='current_box-header' class='box-header'>\n"
  1522. "<span id='current_box-header-stream' class='box-header-title'>%1</span> "
  1523. "</div>\n"
  1524. "<table id='current_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n"
  1525. "<tr class='box-row'>\n"
  1526. "<td id='current_box-information-td' colspan=2>\n"
  1527. "<a href='%2'><b>%3</b></a> - <a href='%4'><b>%5</b></a>"
  1528. "<br />\n"
  1529. "<a href='%6'><b>%7</b></a>"
  1530. "</td>\n"
  1531. "</tr>\n"
  1532. "<tr class='box-row'>\n"
  1533. "<td id='current_box-largecover-td'>\n"
  1534. "<a href='%8'>"
  1535. "<img id='current_box-largecover-image' src='%9' title='%10'>\n"
  1536. "</a></td>\n"
  1537. "<td id='current_box-information-td' align='right'>\n"
  1538. "<div id='musicbrainz-div'>\n"
  1539. "<a id='lastfm-a' href='externalurl://%11'>\n"
  1540. "<img id='lastfm-image' title='%12' src='%13' />\n"
  1541. "</a>\n"
  1542. "</div>\n"
  1543. "<table cellpadding='1'>\n"
  1544. "<tr><td>\n"
  1545. "<a href='lastfm:skip'>%14</a>\n"
  1546. "</td><td>\n"
  1547. "<a href='lastfm:skip'><img id='lastfm-skip-image' src='%15'></a>\n"
  1548. "</td></tr>\n"
  1549. "<tr><td>\n"
  1550. "<a href='lastfm:love'>%16</a>\n"
  1551. "</td><td>\n"
  1552. "<a href='lastfm:love'><img id='lastfm-love-image' src='%17'></a>\n"
  1553. "</td></tr>\n"
  1554. "<tr><td>\n"
  1555. "<a href='lastfm:ban'>%18</a>\n"
  1556. "</td><td>\n"
  1557. "<a href='lastfm:ban'><img id='lastfm-ban-image' src='%19'></a>\n"
  1558. "</td></tr>\n"
  1559. "</table>\n"
  1560. "</td>\n"
  1561. "</tr>\n"
  1562. "</table>\n"
  1563. "</div>\n" )
  1564. .args( TQStringList()
  1565. << escapeHTML( LastFm::Controller::stationDescription() ) //1
  1566. << artistUrl //2
  1567. << escapeHTML( currentTrack.artist() ) //3
  1568. << titleUrl //4
  1569. << escapeHTML( currentTrack.title() ) //5
  1570. << albumUrl //6
  1571. << escapeHTML( currentTrack.album() ) //7
  1572. << albumUrl //8
  1573. << coverImage //9
  1574. << escapeHTMLAttr( currentTrack.album() )//10
  1575. << escapeHTMLAttr( userpage ) //11
  1576. << escapeHTMLAttr( userpage ) //12
  1577. << escapeHTMLAttr( m_lastfmIcon ) //13
  1578. << escapeHTML( i18n( "Skip" ) ) //14
  1579. << escapeHTMLAttr( skipIcon ) //15
  1580. << escapeHTML( i18n( "Love" ) ) //16
  1581. << escapeHTMLAttr( loveIcon ) //17
  1582. << escapeHTML( i18n( "Ban" ) ) //18
  1583. << escapeHTMLAttr( banIcon ) //19
  1584. ) );
  1585. addMetaHistory();
  1586. if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested )
  1587. {
  1588. TQStringList relArtists = CollectionDB::instance()->similarArtists( currentTrack.artist(), 10 );
  1589. if ( !relArtists.isEmpty() )
  1590. {
  1591. if( ContextBrowser::instance()->m_showRelated )
  1592. showRelatedArtists( currentTrack.artist(), relArtists );
  1593. if( ContextBrowser::instance()->m_showSuggested )
  1594. showSuggestedSongs( relArtists );
  1595. }
  1596. }
  1597. const uint artist_id = CollectionDB::instance()->artistID( currentTrack.artist(), false /* don't autocreate */ );
  1598. if( artist_id )
  1599. {
  1600. if( ContextBrowser::instance()->m_showFaves )
  1601. showArtistsFaves( currentTrack.artist(), artist_id );
  1602. const uint album_id = CollectionDB::instance()->albumID ( currentTrack.album(), false /* don't autocreate */ );
  1603. showArtistsAlbums( currentTrack.artist(), artist_id, album_id );
  1604. showArtistsCompilations( currentTrack.artist(), artist_id, album_id );
  1605. }
  1606. m_HTMLSource.append( "</body></html>\n" );
  1607. }
  1608. void CurrentTrackJob::showStream( const MetaBundle &currentTrack )
  1609. {
  1610. m_HTMLSource.append( QStringx(
  1611. "<div id='current_box' class='box'>\n"
  1612. "<div id='current_box-header' class='box-header'>\n"
  1613. "<span id='current_box-header-stream' class='box-header-title'>%1</span> "
  1614. "</div>\n"
  1615. "<table id='current_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n"
  1616. "<tr class='box-row'>\n"
  1617. "<td height='42' valign='top' width='90%'>\n"
  1618. "<b>%2</b>\n"
  1619. "<br />\n"
  1620. "<br />\n"
  1621. "%3"
  1622. "<br />\n"
  1623. "<br />\n"
  1624. "%4"
  1625. "<br />\n"
  1626. "%5 kbps"
  1627. "<br />\n"
  1628. "%6"
  1629. "<br />\n"
  1630. "%7"
  1631. "</td>\n"
  1632. "</tr>\n"
  1633. "</table>\n"
  1634. "</div>\n" )
  1635. .args( TQStringList()
  1636. << i18n( "Stream Details" )
  1637. << escapeHTML( currentTrack.prettyTitle() )
  1638. << escapeHTML( currentTrack.streamName() )
  1639. << escapeHTML( currentTrack.genre() )
  1640. << escapeHTML( currentTrack.prettyBitrate() )
  1641. << escapeHTML( currentTrack.streamUrl() )
  1642. << escapeHTML( currentTrack.prettyURL() ) ) );
  1643. addMetaHistory();
  1644. m_HTMLSource.append( "</body></html>\n" );
  1645. }
  1646. void CurrentTrackJob::addMetaHistory()
  1647. {
  1648. if ( m_metadataHistory.count() > 0 )
  1649. {
  1650. m_HTMLSource.append(
  1651. "<div id='stream-history_box' class='box'>\n"
  1652. "<div id='stream-history_box-header' class='box-header'>\n" + i18n( "Metadata History" ) + "</div>\n"
  1653. "<table id='stream-history_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
  1654. for ( uint i = 0; i < m_metadataHistory.count(); ++i )
  1655. {
  1656. const TQString &str = m_metadataHistory[i];
  1657. m_HTMLSource.append( QStringx( "<tr class='box-row'><td>%1</td></tr>\n" ).arg( str ) );
  1658. }
  1659. m_HTMLSource.append(
  1660. "</table>\n"
  1661. "</div>\n" );
  1662. }
  1663. }
  1664. void CurrentTrackJob::showPodcast( const MetaBundle &currentTrack )
  1665. {
  1666. if( !currentTrack.podcastBundle() )
  1667. return;
  1668. PodcastEpisodeBundle peb = *currentTrack.podcastBundle();
  1669. PodcastChannelBundle pcb;
  1670. bool channelInDB = true;
  1671. if( !CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) )
  1672. {
  1673. pcb.setTitle( i18n( "Unknown Channel (not in Database)" ) );
  1674. channelInDB = false;
  1675. }
  1676. TQString image;
  1677. if( pcb.imageURL().isValid() )
  1678. image = CollectionDB::instance()->podcastImage( pcb.imageURL().url(), true );
  1679. else
  1680. image = CollectionDB::instance()->notAvailCover( true );
  1681. TQString imageAttr = escapeHTMLAttr( pcb.link().isValid()
  1682. ? i18n( "Click to go to podcast website: %1." ).arg( pcb.link().prettyURL() )
  1683. : i18n( "No podcast website." )
  1684. );
  1685. m_HTMLSource.append( QStringx(
  1686. "<div id='current_box' class='box'>\n"
  1687. "<div id='current_box-header' class='box-header'>\n"
  1688. "<span id='current_box-header-artist' class='box-header-title'>%1</span> "
  1689. "<br />\n"
  1690. "<span id='current_box-header-album' class='box-header-title'>%2</span>\n"
  1691. "</div>\n"
  1692. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1693. "<tr>\n"
  1694. "<td id='current_box-largecover-td'>\n"
  1695. "<a id='current_box-largecover-a' href='%3'>\n"
  1696. "<img id='current_box-largecover-image' src='%4' title='%5'>\n"
  1697. "</a>\n"
  1698. "</td>\n"
  1699. "<td id='current_box-information-td' align='right'>\n"
  1700. "%6"
  1701. "%7"
  1702. "</td>\n"
  1703. "</table>\n"
  1704. "</div>\n" )
  1705. .args( TQStringList()
  1706. << escapeHTML( pcb.title() )
  1707. << escapeHTML( peb.title() )
  1708. << ( pcb.link().isValid()
  1709. ? pcb.link().url().replace( TQRegExp( "^http:" ), "externalurl:" )
  1710. : "current://track" )
  1711. << image
  1712. << imageAttr
  1713. << escapeHTML( peb.author().isEmpty()
  1714. ? i18n( "Podcast" )
  1715. : i18n( "Podcast by %1" ).arg( peb.author() ) )
  1716. << ( peb.localUrl().isValid()
  1717. ? "<br />\n" + escapeHTML( i18n( "(Cached)" ) )
  1718. : "" )
  1719. )
  1720. );
  1721. if ( m_isStream && m_metadataHistory.count() > 1 )
  1722. {
  1723. m_HTMLSource.append(
  1724. "<div id='stream-history_box' class='box'>\n"
  1725. "<div id='stream-history_box-header' class='box-header'>\n" + i18n( "Metadata History" ) + "</div>\n"
  1726. "<table id='stream-history_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
  1727. for ( uint i = 0; i < m_metadataHistory.count(); ++i )
  1728. {
  1729. const TQString &str = m_metadataHistory[i];
  1730. m_HTMLSource.append( QStringx( "<tr class='box-row'><td>%1</td></tr>\n" ).arg( str ) );
  1731. }
  1732. m_HTMLSource.append(
  1733. "</table>\n"
  1734. "</div>\n" );
  1735. }
  1736. m_HTMLSource.append(
  1737. "<div id='albums_box' class='box'>\n"
  1738. "<div id='albums_box-header' class='box-header'>\n"
  1739. "<span id='albums_box-header-title' class='box-header-title'>\n"
  1740. + ( channelInDB
  1741. ? i18n( "Episodes from %1" ).arg( escapeHTML( pcb.title() ) )
  1742. : i18n( "Episodes from this Channel" )
  1743. )
  1744. + "</span>\n"
  1745. "</div>\n"
  1746. "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  1747. uint i = 0;
  1748. TQValueList<PodcastEpisodeBundle> episodes = CollectionDB::instance()->getPodcastEpisodes( peb.parent() );
  1749. while( !episodes.isEmpty() )
  1750. {
  1751. PodcastEpisodeBundle &ep = episodes.back();
  1752. TQString date;
  1753. ep.dateTime().isNull() ?
  1754. date = ep.date() :
  1755. date = ep.dateTime().toString();
  1756. m_HTMLSource.append( QStringx (
  1757. "<tr class='" + TQString( (i % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
  1758. "<td>\n"
  1759. "<div class='album-header' onClick=\"toggleBlock('IDE%1')\">\n"
  1760. "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
  1761. "<tr>\n"
  1762. "<td width='1'></td>\n"
  1763. "<td valign='middle' align='left'>\n"
  1764. "<span class='album-info'>%2</span> "
  1765. "<a href='%3'><span class='album-title'>%4</span></a>\n"
  1766. "<br />\n"
  1767. "<span class='album-year'>%5</span>\n"
  1768. "</td>\n"
  1769. "</tr>\n"
  1770. "</table>\n"
  1771. "</div>\n"
  1772. "<div class='album-body' style='display:%6;' id='IDE%7'>\n" )
  1773. .args( TQStringList()
  1774. << TQString::number( i )
  1775. << escapeHTML( ep.duration() ? MetaBundle::prettyTime( ep.duration() ) : TQString( "" ) )
  1776. << ( ep.localUrl().isValid()
  1777. ? ep.localUrl().url()
  1778. : ep.url().url().replace( TQRegExp( "^http:" ), "stream:" ) )
  1779. << escapeHTML( ep.title() )
  1780. << escapeHTML( date )
  1781. << (peb.url() == ep.url() ? "block" : "none" )
  1782. << TQString::number( i )
  1783. )
  1784. );
  1785. m_HTMLSource.append( QStringx ( "<p>%1</p>\n" ).arg( ep.description() ) );
  1786. m_HTMLSource.append(
  1787. "</div>\n"
  1788. "</td>\n"
  1789. "</tr>\n" );
  1790. i++;
  1791. episodes.pop_back();
  1792. }
  1793. m_HTMLSource.append("</body></html>\n" );
  1794. }
  1795. void CurrentTrackJob::showBrowseArtistHeader( const TQString &artist )
  1796. {
  1797. // <Artist>
  1798. bool linkback = ( b->m_contextBackHistory.size() > 0 );
  1799. TQString back = ( linkback
  1800. ? "<a id='artist-back-a' href='artistback://back'>\n"
  1801. + escapeHTML( i18n( "<- Back" ) )
  1802. + "</a>\n"
  1803. : TQString( "" )
  1804. );
  1805. m_HTMLSource.append(
  1806. TQString(
  1807. "<div id='current_box' class='box'>\n"
  1808. "<div id='current_box-header' class='box-header'>\n"
  1809. "<span id='current_box-header-artist' class='box-header-title'>%1</span>\n"
  1810. "<br />\n"
  1811. "<table width='100%' cellpadding='0' cellspacing='0'><tr>\n"
  1812. "<td><span id='current_box-header-album' class='box-header-title'>%2</span></td>\n"
  1813. "<td><div id='current_box-header-nav' class='box-header-nav'>%3</div></td>\n"
  1814. "</tr></table>\n"
  1815. "</div>\n" )
  1816. .arg( escapeHTML( artist ) )
  1817. .arg( escapeHTML( i18n( "Browse Artist" ) ) )
  1818. .arg( back ) );
  1819. m_HTMLSource.append(
  1820. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1821. );
  1822. m_HTMLSource.append(
  1823. "<tr>\n"
  1824. "<td id='context'>\n"
  1825. + TQString( "<a id='context-a=' href='current://track'>\n" )
  1826. + i18n( "Information for Current Track" )
  1827. + "</a>\n"
  1828. "</td>\n"
  1829. "</tr>\n"
  1830. );
  1831. m_HTMLSource.append(
  1832. "<tr>\n"
  1833. "<td id='artist-wikipedia'>\n"
  1834. + TQString( "<a id='artist-wikipedia-a' href='wikipedia:%1'>\n" ).arg( escapeHTMLAttr( artist + b->wikiArtistPostfix() ) )
  1835. + i18n( "Wikipedia Information for %1" ).arg( escapeHTML( artist ) ) +
  1836. "</a>\n"
  1837. "</td>\n"
  1838. "</tr>\n");
  1839. m_HTMLSource.append(
  1840. "<tr>\n"
  1841. "<td id='artist-google'>\n"
  1842. + TQString( "<a id='artist-google-a' href='ggartist:%1'>\n" ).arg( escapeHTMLAttr( artist ) )
  1843. + i18n( "Google Musicsearch for %1" ).arg( escapeHTML( artist ) ) +
  1844. "</a>\n"
  1845. "</td>\n"
  1846. "</tr>\n"
  1847. );
  1848. m_HTMLSource.append(
  1849. "</td>\n"
  1850. "</tr>\n"
  1851. "</table>\n"
  1852. "</div>\n" );
  1853. // </Artist>
  1854. }
  1855. void
  1856. CurrentTrackJob::showBrowseLabelHeader( const TQString &label )
  1857. {
  1858. bool linkback = ( b->m_contextBackHistory.size() > 0 );
  1859. TQString back = ( linkback
  1860. ? "<a id='artist-back-a' href='artistback://back'>\n"
  1861. + escapeHTML( i18n( "<- Back" ) )
  1862. + "</a>\n"
  1863. : TQString( "" )
  1864. );
  1865. m_HTMLSource.append(
  1866. TQString(
  1867. "<div id='current_box' class='box'>\n"
  1868. "<div id='current_box-header' class='box-header'>\n"
  1869. "<span id='current_box-header-artist' class='box-header-title'>%1</span>\n"
  1870. "<br />\n"
  1871. "<table width='100%' cellpadding='0' cellspacing='0'><tr>\n"
  1872. "<td><span id='current_box-header-album' class='box-header-title'>%2</span></td>\n"
  1873. "<td><div id='current_box-header-nav' class='box-header-nav'>%3</div></td>\n"
  1874. "</tr></table>\n"
  1875. "</div>\n" )
  1876. .arg( escapeHTML( label ) )
  1877. .arg( escapeHTML( i18n( "Browse Label" ) ) )
  1878. .arg( back ) );
  1879. m_HTMLSource.append(
  1880. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1881. );
  1882. m_HTMLSource.append(
  1883. "<tr>\n"
  1884. "<td id='context'>\n"
  1885. + TQString( "<a id='context-a=' href='current://track'>\n" )
  1886. + i18n( "Information for Current Track" )
  1887. + "</a>\n"
  1888. "</td>\n"
  1889. "</tr>\n"
  1890. );
  1891. m_HTMLSource.append(
  1892. "<tr>\n"
  1893. "<td id='label-lastfm'>\n"
  1894. + TQString( "<a id='label-lastfm-a' href='externalurl://www.last.fm/tag/%1'>\n" ).arg( escapeHTMLAttr( label ) )
  1895. + i18n( "Last.fm Information for %1" ).arg( escapeHTML( label ) ) +
  1896. "</a>\n"
  1897. "</td>\n"
  1898. "</tr>\n");
  1899. m_HTMLSource.append(
  1900. "</td>\n"
  1901. "</tr>\n"
  1902. "</table>\n"
  1903. "</div>\n" );
  1904. }
  1905. void CurrentTrackJob::showCurrentArtistHeader( const MetaBundle &currentTrack )
  1906. {
  1907. QueryBuilder qb;
  1908. TQStringList values;
  1909. // <Current Track Information>
  1910. qb.clear();
  1911. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valCreateDate );
  1912. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valAccessDate );
  1913. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valPlayCounter );
  1914. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
  1915. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
  1916. qb.addMatch( QueryBuilder::tabStats, QueryBuilder::valURL, currentTrack.url().path() );
  1917. values = qb.run();
  1918. usleep( 10000 );
  1919. //making 2 tables is most probably not the cleanest way to do it, but it works.
  1920. TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( currentTrack, true, 1 ) );
  1921. TQString albumImageTitleAttr = albumImageTooltip( albumImage, 0 );
  1922. bool isCompilation = false;
  1923. if( !currentTrack.album().isEmpty() )
  1924. {
  1925. isCompilation = CollectionDB::instance()->albumIsCompilation(
  1926. TQString::number( CollectionDB::instance()->albumID( currentTrack.album() ) )
  1927. );
  1928. }
  1929. m_HTMLSource.append(
  1930. "<div id='current_box' class='box'>\n"
  1931. "<div id='current_box-header' class='box-header'>\n"
  1932. // Show "Title - Artist \n Album", or only "PrettyTitle" if there's no title tag
  1933. + ( !currentTrack.title().isEmpty()
  1934. ? QStringx(
  1935. "<span id='current_box-header-songname' class='box-header-title'>%1</span> "
  1936. "<span id='current_box-header-separator' class='box-header-title'>-</span> "
  1937. "<span id='current_box-header-artist' class='box-header-title'>%2</span>\n"
  1938. "<br />\n"
  1939. "<span id='current_box-header-album' class='box-header-title'>%3</span>\n"
  1940. "</div>\n"
  1941. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1942. "<tr>\n"
  1943. "<td id='current_box-largecover-td'>\n"
  1944. "<a id='current_box-largecover-a' href='fetchcover:%4 @@@ %5'>\n"
  1945. "<img id='current_box-largecover-image' src='%6' title='%7'>\n"
  1946. "</a>\n"
  1947. "</td>\n"
  1948. "<td id='current_box-information-td' align='right'>\n"
  1949. "<div id='musicbrainz-div'>\n"
  1950. "<a id='musicbrainz-a' title='%8' href='musicbrainz:%9 @@@ %10 @@@ %11'>\n"
  1951. "<img id='musicbrainz-image' src='%12' />\n"
  1952. "</a>\n"
  1953. "</div>\n"
  1954. )
  1955. .args( TQStringList()
  1956. << escapeHTML( currentTrack.title() )
  1957. << escapeHTML( currentTrack.artist() )
  1958. << escapeHTML( currentTrack.album() )
  1959. << ( isCompilation ? "" : escapeHTMLAttr( currentTrack.artist() ) )
  1960. << escapeHTMLAttr( currentTrack.album() )
  1961. << escapeHTMLAttr( albumImage )
  1962. << albumImageTitleAttr
  1963. << i18n( "Look up this track at musicbrainz.org" )
  1964. << escapeHTMLAttr( currentTrack.artist() )
  1965. << escapeHTMLAttr( currentTrack.album() )
  1966. << escapeHTMLAttr( currentTrack.title() )
  1967. << escapeHTML( m_musicBrainIconPath ) )
  1968. : TQString ( //no title
  1969. "<span id='current_box-header-prettytitle' class='box-header-prettytitle'>%1</span> "
  1970. "</div>\n"
  1971. "<table id='current_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>\n"
  1972. "<tr>\n"
  1973. "<td id='current_box-largecover-td'>\n"
  1974. "<a id='current_box-largecover-a' href='fetchcover:%2 @@@ %3'>\n"
  1975. "<img id='current_box-largecover-image' src='%4' title='%5'>\n"
  1976. "</a>\n"
  1977. "</td>\n"
  1978. "<td id='current_box-information-td' align='right'>\n"
  1979. )
  1980. .arg( escapeHTML( currentTrack.prettyTitle() ) )
  1981. .arg( escapeHTMLAttr( currentTrack.artist() ) )
  1982. .arg( escapeHTMLAttr( currentTrack.album() ) )
  1983. .arg( escapeHTMLAttr( albumImage ) )
  1984. .arg( albumImageTitleAttr )
  1985. ) );
  1986. if ( !values.isEmpty() && values[2].toInt() )
  1987. {
  1988. TQDateTime firstPlay = TQDateTime();
  1989. firstPlay.setTime_t( values[0].toUInt() );
  1990. TQDateTime lastPlay = TQDateTime();
  1991. lastPlay.setTime_t( values[1].toUInt() );
  1992. const uint playtimes = values[2].toInt();
  1993. const uint score = static_cast<uint>( values[3].toFloat() );
  1994. const uint rating = values[4].toInt();
  1995. //SAFE = .arg( x, y )
  1996. //UNSAFE = .arg( x ).arg( y )
  1997. m_HTMLSource.append( TQString(
  1998. "<span>%1</span><br />\n"
  1999. "<div>%2</div>\n"
  2000. "<span>%3</span><br />\n"
  2001. "<span>%4</span>\n"
  2002. )
  2003. .arg( i18n( "Track played once", "Track played %n times", playtimes ),
  2004. statsHTML( score, rating, false ),
  2005. i18n( "Last played: %1" ).arg( Amarok::verboseTimeSince( lastPlay ) ),
  2006. i18n( "First played: %1" ).arg( Amarok::verboseTimeSince( firstPlay ) ) ) );
  2007. }
  2008. else
  2009. m_HTMLSource.append( i18n( "Never played before" ) );
  2010. m_HTMLSource.append(
  2011. "</td>\n"
  2012. "</tr>\n"
  2013. "</table>\n"
  2014. "</div>\n" );
  2015. // </Current Track Information>
  2016. if ( currentTrack.url().isLocalFile() && !CollectionDB::instance()->isFileInCollection( currentTrack.url().path() ) )
  2017. {
  2018. m_HTMLSource.append(
  2019. "<div id='notindb_box' class='box'>\n"
  2020. "<div id='notindb_box-header' class='box-header'>\n"
  2021. "<span id='notindb_box-header-title' class='box-header-title'>\n"
  2022. + i18n( "This file is not in your Collection!" ) +
  2023. "</span>\n"
  2024. "</div>\n"
  2025. "<div id='notindb_box-body' class='box-body'>\n"
  2026. "<div class='info'><p>\n"
  2027. + i18n( "If you would like to see contextual information about this track,"
  2028. " you should add it to your Collection." ) +
  2029. "</p></div>\n"
  2030. "<div align='center'>\n"
  2031. "<input type='button' onClick='window.location.href=\"show:collectionSetup\";' value='"
  2032. + i18n( "Change Collection Setup..." ) +
  2033. "' class='button' /></div><br />\n"
  2034. "</div>\n"
  2035. "</div>\n"
  2036. );
  2037. }
  2038. /* cue file code */
  2039. if ( b->m_cuefile && (b->m_cuefile->count() > 0) ) {
  2040. m_HTMLSource.append(
  2041. "<div id='cue_box' class='box'>\n"
  2042. "<div id='cue_box-header' class='box-header'>\n"
  2043. "<span id='cue_box-header-title' class='box-header-title' onClick=\"toggleBlock('T_CC'); window.location.href='togglebox:cc';\" style='cursor: pointer;'>\n"
  2044. + i18n( "Cue File" ) +
  2045. "</span>\n"
  2046. "</div>\n"
  2047. "<table id='cue_box-body' class='box-body' id='T_CC' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
  2048. CueFile::Iterator it;
  2049. uint i = 0;
  2050. for ( it = b->m_cuefile->begin(); it != b->m_cuefile->end(); ++it ) {
  2051. m_HTMLSource.append(
  2052. "<tr class='" + TQString( (i++ % 2) ? "box-row-alt" : "box-row" ) + "'>\n"
  2053. "<td class='song'>\n"
  2054. "<a href=\"seek:" + TQString::number(it.key()) + "\">\n"
  2055. "<span class='album-song-trackno'>\n" + TQString::number(it.data().getTrackNumber()) + "&nbsp;</span>\n"
  2056. "<span class='album-song-title'>\n" + escapeHTML( it.data().getTitle() ) + "</span>\n"
  2057. "<span class='song-separator'>\n"
  2058. + i18n("&#xa0;&#8211; ") +
  2059. "</span>\n"
  2060. "<span class='album-song-title'>\n" + escapeHTML( it.data().getArtist() ) + "</span>\n"
  2061. "<span class='album-song-time'>&nbsp;(" + MetaBundle::prettyTime( it.data().getLength()/1000, false ) + ")</span>\n"
  2062. "</a>\n"
  2063. "</td>\n"
  2064. ""
  2065. );
  2066. }
  2067. m_HTMLSource.append(
  2068. "</table>\n"
  2069. "</div>\n"
  2070. );
  2071. }
  2072. }
  2073. void CurrentTrackJob::showRelatedArtists( const TQString &artist, const TQStringList &relArtists )
  2074. {
  2075. // <Related Artists>
  2076. m_HTMLSource.append( TQString(
  2077. "<div id='related_box' class='box'>\n"
  2078. "<div id='related_box-header' class='box-header' onClick=\"toggleBlock('T_RA'); window.location.href='togglebox:ra';\" style='cursor: pointer;'>\n"
  2079. "<span id='related_box-header-title' class='box-header-title'>%1</span>\n"
  2080. "</div>\n"
  2081. "<table class='box-body' id='T_RA' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" )
  2082. .arg( i18n( "Artists Related to %1" ).arg( escapeHTML( artist ) ) ) );
  2083. m_HTMLSource.append( "<tr><td>\n" );
  2084. for ( uint i = 0; i < relArtists.count(); i += 1 )
  2085. {
  2086. bool isInCollection = !CollectionDB::instance()->albumListOfArtist( relArtists[i] ).isEmpty();
  2087. m_HTMLSource.append(
  2088. ( isInCollection ? "" : "<i>" )
  2089. + TQString( "<a href='artist:" ) + escapeHTMLAttr( relArtists[i] ) + "'>" + escapeHTML( relArtists[i] ) + "</a>"
  2090. + ( isInCollection ? "" : "</i>" )
  2091. );
  2092. if( i != relArtists.count()-1 )
  2093. m_HTMLSource.append( ", \n" );
  2094. }
  2095. m_HTMLSource.append( "</td></tr>\n" );
  2096. m_HTMLSource.append(
  2097. "</table>\n"
  2098. "</div>\n" );
  2099. if ( !b->m_relatedOpen )
  2100. m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_RA');</script>\n" );
  2101. // </Related Artists>
  2102. }
  2103. void CurrentTrackJob::showSuggestedSongs( const TQStringList &relArtists )
  2104. {
  2105. TQString token;
  2106. QueryBuilder qb;
  2107. TQStringList values;
  2108. qb.clear();
  2109. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  2110. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2111. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
  2112. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
  2113. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
  2114. qb.addMatches( QueryBuilder::tabArtist, relArtists );
  2115. qb.sortByFavorite();
  2116. qb.setLimit( 0, 10 );
  2117. values = qb.run();
  2118. // <Suggested Songs>
  2119. if ( !values.isEmpty() )
  2120. {
  2121. m_HTMLSource.append(
  2122. "<div id='suggested_box' class='box'>\n"
  2123. "<div id='suggested_box-header' class='box-header' onClick=\"toggleBlock('T_SS'); window.location.href='togglebox:ss';\" style='cursor: pointer;'>\n"
  2124. "<span id='suggested_box-header-title' class='box-header-title'>\n"
  2125. + i18n( "Suggested Songs" ) +
  2126. "</span>\n"
  2127. "</div>\n"
  2128. "<table class='box-body' id='T_SS' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  2129. for ( uint i = 0; i < values.count(); i += 5 )
  2130. m_HTMLSource.append(
  2131. "<tr class='" + TQString( (i % 8) ? "box-row-alt" : "box-row" ) + "'>\n"
  2132. "<td class='song'>\n"
  2133. "<a href=\"file:" + escapeHTMLAttr ( values[i] ) + "\">\n"
  2134. "<span class='album-song-title'>\n"+ escapeHTML( values[i + 2] ) + "</span>\n"
  2135. "<span class='song-separator'>\n"
  2136. + i18n("&#xa0;&#8211; ") +
  2137. "</span><span class='album-song-title'>\n" + escapeHTML( values[i + 1] ) + "</span>\n"
  2138. "</a>\n"
  2139. "</td>\n"
  2140. "<td>\n" + statsHTML( static_cast<int>( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "</td>\n"
  2141. "<td width='1'></td>\n"
  2142. "</tr>\n" );
  2143. m_HTMLSource.append(
  2144. "</table>\n"
  2145. "</div>\n" );
  2146. if ( !b->m_suggestionsOpen )
  2147. m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_SS');</script>\n" );
  2148. }
  2149. // </Suggested Songs>
  2150. }
  2151. void
  2152. CurrentTrackJob::showSongsWithLabel( const TQString &label )
  2153. {
  2154. QueryBuilder qb;
  2155. TQStringList values;
  2156. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  2157. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2158. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
  2159. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
  2160. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
  2161. qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) );
  2162. qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valName, label );
  2163. qb.sortByFavorite();
  2164. qb.setOptions( QueryBuilder::optRandomize );
  2165. qb.setLimit( 0, 30 );
  2166. values = qb.run();
  2167. if ( !values.isEmpty() )
  2168. {
  2169. m_HTMLSource.append(
  2170. "<div id='suggested_box' class='box'>\n"
  2171. "<div id='suggested_box-header' class='box-header' onClick=\"toggleBlock('T_SS'); window.location.href='togglebox:ss';\" style='cursor: pointer;'>\n"
  2172. "<span id='suggested_box-header-title' class='box-header-title'>\n"
  2173. + i18n( "Songs with label %1" ).arg( label ) +
  2174. "</span>\n"
  2175. "</div>\n"
  2176. "<table class='box-body' id='T_' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  2177. for ( uint i = 0; i < values.count(); i += 5 )
  2178. m_HTMLSource.append(
  2179. "<tr class='" + TQString( (i % 8) ? "box-row-alt" : "box-row" ) + "'>\n"
  2180. "<td class='song'>\n"
  2181. "<a href=\"file:" + escapeHTMLAttr ( values[i] ) + "\">\n"
  2182. "<span class='album-song-title'>\n"+ escapeHTML( values[i + 2] ) + "</span>\n"
  2183. "<span class='song-separator'>\n"
  2184. + i18n("&#xa0;&#8211; ") +
  2185. "</span><span class='album-song-title'>\n" + escapeHTML( values[i + 1] ) + "</span>\n"
  2186. "</a>\n"
  2187. "</td>\n"
  2188. "<td>\n" + statsHTML( static_cast<int>( values[i + 3].toFloat() ), values[i + 4].toInt() ) + "</td>\n"
  2189. "<td width='1'></td>\n"
  2190. "</tr>\n" );
  2191. m_HTMLSource.append(
  2192. "</table>\n"
  2193. "</div>\n" );
  2194. }
  2195. }
  2196. void
  2197. CurrentTrackJob::showUserLabels( const MetaBundle &currentTrack )
  2198. {
  2199. QueryBuilder qb;
  2200. qb.addReturnValue( QueryBuilder::tabLabels, QueryBuilder::valName, true );
  2201. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valURL, currentTrack.url().path() );
  2202. qb.addMatch( QueryBuilder::tabLabels, QueryBuilder::valType, TQString::number( CollectionDB::typeUser ) );
  2203. qb.setLimit( 0, 10 );
  2204. qb.sortBy( QueryBuilder::tabLabels, QueryBuilder::valName, false );
  2205. qb.buildQuery();
  2206. TQStringList values = qb.run();
  2207. TQString title;
  2208. if ( currentTrack.title().isEmpty() )
  2209. title = currentTrack.veryNiceTitle();
  2210. else
  2211. title = currentTrack.title();
  2212. m_HTMLSource.append(
  2213. "<div id='songlabels_box' class='box'>\n"
  2214. "<div id='songlabels-header' class='box-header' onCLick=\"toggleBlock('T_SL');window.location.href='togglebox:sl';\" style='cursor: pointer;'>\n"
  2215. "<span id='songlabels_box-header-title' class='box-header-title'>\n"
  2216. + i18n( " Labels for %1 " ).arg( escapeHTML( title ) ) +
  2217. "</span>\n"
  2218. "</div>\n"
  2219. "<table class='box-body' id='T_SL' width='100%' border='0' cellspacing='0' cellpadding='1'>\n" );
  2220. m_HTMLSource.append( "<tr><td>\n" );
  2221. if ( !values.isEmpty() )
  2222. {
  2223. foreach( values )
  2224. {
  2225. if( it != values.begin() )
  2226. m_HTMLSource.append( ", \n" );
  2227. m_HTMLSource.append( "<a href='showlabel:" + escapeHTMLAttr( *it ) + "'>" + escapeHTML( *it ) + "</a>" );
  2228. }
  2229. }
  2230. m_HTMLSource.append( "</td></tr>\n" );
  2231. m_HTMLSource.append( "<tr><td><a id='songlabels_box_addlabel' href='show:editLabels'>" + i18n( "Add labels to %1" ).arg( escapeHTML( title ) ) + "</a></td></tr>\n" );
  2232. m_HTMLSource.append(
  2233. "</table>\n"
  2234. "</div>\n" );
  2235. if ( !b->m_labelsOpen )
  2236. m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_SL');</script>\n" );
  2237. }
  2238. void CurrentTrackJob::showArtistsFaves( const TQString &artist, uint artist_id )
  2239. {
  2240. TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
  2241. QueryBuilder qb;
  2242. TQStringList values;
  2243. qb.clear();
  2244. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2245. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  2246. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valScore );
  2247. qb.addReturnValue( QueryBuilder::tabStats, QueryBuilder::valRating );
  2248. qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0", QueryBuilder::modeGreater );
  2249. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
  2250. qb.sortByFavorite();
  2251. qb.setLimit( 0, 10 );
  2252. values = qb.run();
  2253. usleep( 10000 );
  2254. if ( !values.isEmpty() )
  2255. {
  2256. m_HTMLSource.append(
  2257. "<div id='favoritesby_box' class='box'>\n"
  2258. "<div id='favoritesby-header' class='box-header' onClick=\"toggleBlock('T_FT'); window.location.href='togglebox:ft';\" style='cursor: pointer;'>\n"
  2259. "<span id='favoritesby_box-header-title' class='box-header-title'>\n"
  2260. + i18n( "Favorite Tracks by %1" ).arg( artistName ) +
  2261. "</span>\n"
  2262. "</div>\n"
  2263. "<table class='box-body' id='T_FT' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  2264. for ( uint i = 0; i < values.count(); i += 4 )
  2265. m_HTMLSource.append(
  2266. "<tr class='" + TQString( (i % 6) ? "box-row-alt" : "box-row" ) + "'>\n"
  2267. "<td class='song'>\n"
  2268. "<a href=\"file:" + escapeHTMLAttr ( values[i + 1] ) + "\">\n"
  2269. "<span class='album-song-title'>\n" + escapeHTML( values[i] ) + "</span>\n"
  2270. "</a>\n"
  2271. "</td>\n"
  2272. "<td>\n" + statsHTML( static_cast<int>( values[i + 2].toFloat() ), values[i + 3].toInt() ) + "</td>\n"
  2273. "<td width='1'></td>\n"
  2274. "</tr>\n"
  2275. );
  2276. m_HTMLSource.append(
  2277. "</table>\n"
  2278. "</div>\n" );
  2279. if ( !b->m_favoritesOpen )
  2280. m_HTMLSource.append( "<script language='JavaScript'>toggleBlock('T_FT');</script>\n" );
  2281. }
  2282. }
  2283. void CurrentTrackJob::showArtistsAlbums( const TQString &artist, uint artist_id, uint album_id )
  2284. {
  2285. DEBUG_BLOCK
  2286. TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
  2287. QueryBuilder qb;
  2288. TQStringList values;
  2289. // <Albums by this artist>
  2290. qb.clear();
  2291. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
  2292. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
  2293. qb.addReturnFunctionValue( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName );
  2294. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
  2295. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
  2296. qb.groupBy( QueryBuilder::tabAlbum, QueryBuilder::valID );
  2297. qb.sortByFunction( QueryBuilder::funcMax, QueryBuilder::tabYear, QueryBuilder::valName, true );
  2298. qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true );
  2299. qb.setOptions( QueryBuilder::optNoCompilations );
  2300. values = qb.run();
  2301. if ( !values.isEmpty() )
  2302. {
  2303. // write the script to toggle blocks visibility
  2304. m_HTMLSource.append(
  2305. "<div id='albums_box' class='box'>\n"
  2306. "<div id='albums_box-header' class='box-header'>\n"
  2307. "<span id='albums_box-header-title' class='box-header-title'>\n"
  2308. + i18n( "Albums by %1" ).arg( artistName ) +
  2309. "</span>\n"
  2310. "</div>\n"
  2311. "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  2312. uint vectorPlace = 0;
  2313. // find album of the current track (if it exists)
  2314. while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) )
  2315. vectorPlace += 3;
  2316. for ( uint i = 0; i < values.count(); i += 3 )
  2317. {
  2318. qb.clear();
  2319. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2320. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  2321. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
  2322. qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
  2323. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
  2324. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  2325. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] );
  2326. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
  2327. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  2328. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
  2329. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2330. qb.setOptions( QueryBuilder::optNoCompilations );
  2331. TQStringList albumValues = qb.run();
  2332. usleep( 10000 );
  2333. TQString albumYear;
  2334. if ( !albumValues.isEmpty() )
  2335. {
  2336. albumYear = albumValues[ 3 ];
  2337. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2338. if ( albumValues[j + 3] != albumYear || albumYear == "0" )
  2339. {
  2340. albumYear = TQString();
  2341. break;
  2342. }
  2343. }
  2344. uint i_albumLength = 0;
  2345. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2346. i_albumLength += TQString(albumValues[j + 4]).toInt();
  2347. TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );
  2348. TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) );
  2349. TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );
  2350. m_HTMLSource.append( QStringx (
  2351. "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
  2352. "<td>\n"
  2353. "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
  2354. "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
  2355. "<tr>\n"
  2356. "<td width='1'>\n"
  2357. "<a href='fetchcover:%2 @@@ %3'>\n"
  2358. "<img class='album-image' align='left' vspace='2' hspace='2' title='%4' src='%5'/>\n"
  2359. "</a>\n"
  2360. "</td>\n"
  2361. "<td valign='middle' align='left'>\n"
  2362. "<span class='album-info'>%6</span> "
  2363. "<a href='album:%7 @@@ %8'><span class='album-title'>%9</span></a>\n"
  2364. "<br />\n"
  2365. "<span class='album-year'>%10</span>\n"
  2366. "<span class='album-length'>%11</span>\n"
  2367. "</td>\n"
  2368. "</tr>\n"
  2369. "</table>\n"
  2370. "</div>\n"
  2371. "<div class='album-body' style='display:%12;' id='IDA%13'>\n" )
  2372. .args( TQStringList()
  2373. << values[ i + 1 ]
  2374. << escapeHTMLAttr( artist ) // artist name
  2375. << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name
  2376. << albumImageTitleAttr
  2377. << escapeHTMLAttr( albumImage )
  2378. << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() )
  2379. << TQString::number( artist_id )
  2380. << values[ i + 1 ] //album.id
  2381. << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] )
  2382. << albumYear
  2383. << albumLength
  2384. << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */
  2385. << values[ i + 1 ] ) );
  2386. TQString discNumber;
  2387. if ( !albumValues.isEmpty() )
  2388. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2389. {
  2390. TQString newDiscNumber = albumValues[ j + 5 ].stripWhiteSpace();
  2391. if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
  2392. {
  2393. discNumber = newDiscNumber;
  2394. m_HTMLSource.append( QStringx (
  2395. "<div class='disc-separator'>\n"
  2396. "<a href=\"albumdisc: %1 @@@ %2 @@@ %3\">\n"
  2397. "%4"
  2398. "</a>\n"
  2399. "</div>\n" )
  2400. .args( TQStringList()
  2401. << TQString::number( artist_id )
  2402. << values[ i + 1 ] //album.id
  2403. << escapeHTMLAttr( discNumber )
  2404. << i18n( "Disc %1" ).arg( discNumber ) ) );
  2405. }
  2406. TQString track = albumValues[j + 2].stripWhiteSpace();
  2407. if( track.length() > 0 ) {
  2408. if( track.length() == 1 )
  2409. track.prepend( "0" );
  2410. track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
  2411. }
  2412. TQString length;
  2413. if( albumValues[j + 4] != "0" )
  2414. length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";
  2415. bool current = false;
  2416. if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
  2417. current = true;
  2418. m_HTMLSource.append(
  2419. "<div class='album-song'>\n"
  2420. "<a href=\"file:" + escapeHTMLAttr ( albumValues[j + 1] ) + "\">\n"
  2421. + track +
  2422. "<span class='album-song-title'>\n" + (current?"<i>":"") + escapeHTML( albumValues[j] ) + (current?"</i>":"") + "</span>&nbsp;"
  2423. + length +
  2424. "</a>\n"
  2425. "</div>\n" );
  2426. }
  2427. m_HTMLSource.append(
  2428. "</div>\n"
  2429. "</td>\n"
  2430. "</tr>\n" );
  2431. }
  2432. m_HTMLSource.append(
  2433. "</table>\n"
  2434. "</div>\n" );
  2435. }
  2436. // </Albums by this artist>
  2437. }
  2438. void CurrentTrackJob::showArtistsCompilations( const TQString &artist, uint artist_id, uint album_id )
  2439. {
  2440. TQString artistName = artist.isEmpty() ? escapeHTML( i18n( "This Artist" ) ) : escapeHTML( artist );
  2441. QueryBuilder qb;
  2442. TQStringList values;
  2443. // <Compilations with this artist>
  2444. qb.clear();
  2445. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valName );
  2446. qb.addReturnValue( QueryBuilder::tabAlbum, QueryBuilder::valID );
  2447. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valArtistID, TQString::number( artist_id ) );
  2448. qb.sortBy( QueryBuilder::tabYear, QueryBuilder::valName, true );
  2449. qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName, true );
  2450. qb.setOptions( QueryBuilder::optRemoveDuplicates );
  2451. qb.setOptions( QueryBuilder::optOnlyCompilations );
  2452. values = qb.run();
  2453. if ( !values.isEmpty() )
  2454. {
  2455. // write the script to toggle blocks visibility
  2456. m_HTMLSource.append(
  2457. "<div id='albums_box' class='box'>\n"
  2458. "<div id='albums_box-header' class='box-header'>\n"
  2459. "<span id='albums_box-header-title' class='box-header-title'>\n"
  2460. + i18n( "Compilations with %1" ).arg( artistName ) +
  2461. "</span>\n"
  2462. "</div>\n"
  2463. "<table id='albums_box-body' class='box-body' width='100%' border='0' cellspacing='0' cellpadding='0'>\n" );
  2464. uint vectorPlace = 0;
  2465. // find album of the current track (if it exists)
  2466. while ( vectorPlace < values.count() && values[ vectorPlace+1 ] != TQString::number( album_id ) )
  2467. vectorPlace += 2;
  2468. for ( uint i = 0; i < values.count(); i += 2 )
  2469. {
  2470. qb.clear();
  2471. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTitle );
  2472. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  2473. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valTrack );
  2474. qb.addReturnValue( QueryBuilder::tabYear, QueryBuilder::valName );
  2475. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valLength );
  2476. qb.addReturnValue( QueryBuilder::tabArtist, QueryBuilder::valName );
  2477. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  2478. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, values[ i + 1 ] );
  2479. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  2480. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
  2481. qb.setOptions( QueryBuilder::optOnlyCompilations );
  2482. TQStringList albumValues = qb.run();
  2483. usleep( 10000 );
  2484. TQString albumYear;
  2485. if ( !albumValues.isEmpty() )
  2486. {
  2487. albumYear = albumValues[ 3 ];
  2488. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2489. if ( albumValues[j + 3] != albumYear || albumYear == "0" )
  2490. {
  2491. albumYear = TQString();
  2492. break;
  2493. }
  2494. }
  2495. uint i_albumLength = 0;
  2496. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2497. i_albumLength += TQString(albumValues[j + 4]).toInt();
  2498. TQString albumLength = ( i_albumLength==0 ? i18n( "Unknown" ) : MetaBundle::prettyTime( i_albumLength, true ) );
  2499. TQString albumImage = ContextBrowser::getEncodedImage( CollectionDB::instance()->albumImage( artist, values[ i ], true, 50 ) );
  2500. TQString albumImageTitleAttr = albumImageTooltip( albumImage, 50 );
  2501. m_HTMLSource.append( QStringx (
  2502. "<tr class='" + TQString( (i % 4) ? "box-row-alt" : "box-row" ) + "'>\n"
  2503. "<td>\n"
  2504. "<div class='album-header' onClick=\"toggleBlock('IDA%1')\">\n"
  2505. "<table width='100%' border='0' cellspacing='0' cellpadding='0'>\n"
  2506. "<tr>\n"
  2507. "<td width='1'>\n"
  2508. "<a href='fetchcover: @@@ %2'>\n"
  2509. "<img class='album-image' align='left' vspace='2' hspace='2' title='%3' src='%4'/>\n"
  2510. "</a>\n"
  2511. "</td>\n"
  2512. "<td valign='middle' align='left'>\n"
  2513. "<span class='album-info'>%5</span> "
  2514. "<a href='compilation:%6'><span class='album-title'>%7</span></a>\n"
  2515. "<br />\n"
  2516. "<span class='album-year'>%8</span>\n"
  2517. "<span class='album-length'>%9</span>\n"
  2518. "</td>\n"
  2519. "</tr>\n"
  2520. "</table>\n"
  2521. "</div>\n"
  2522. "<div class='album-body' style='display:%10;' id='IDA%11'>\n" )
  2523. .args( TQStringList()
  2524. << values[ i + 1 ]
  2525. << escapeHTMLAttr( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] ) // album.name
  2526. << albumImageTitleAttr
  2527. << escapeHTMLAttr( albumImage )
  2528. << i18n( "Single", "%n Tracks", albumValues.count() / qb.countReturnValues() )
  2529. << values[ i + 1 ] //album.id
  2530. << escapeHTML( values[ i ].isEmpty() ? i18n( "Unknown" ) : values[ i ] )
  2531. << albumYear
  2532. << albumLength
  2533. << ( i!=vectorPlace ? "none" : "block" ) /* shows it if it's the current track album */
  2534. << values[ i + 1 ] ) );
  2535. TQString discNumber;
  2536. if ( !albumValues.isEmpty() )
  2537. for ( uint j = 0; j < albumValues.count(); j += qb.countReturnValues() )
  2538. {
  2539. TQString newDiscNumber = albumValues[ j + 6 ].stripWhiteSpace();
  2540. if( discNumber != newDiscNumber && newDiscNumber.toInt() > 0)
  2541. {
  2542. discNumber = newDiscNumber;
  2543. m_HTMLSource.append( QStringx (
  2544. "<div class='disc-separator'>\n"
  2545. "<a href=\"compilationdisc: __discard__ @@@ %1 @@@ %2\">\n"
  2546. "%3"
  2547. "</a>\n"
  2548. "</div>\n" )
  2549. .args( TQStringList()
  2550. << values[ i + 1 ] //album.id
  2551. << escapeHTMLAttr( discNumber )
  2552. << i18n( "Disc %1" ).arg( discNumber ) ) );
  2553. }
  2554. TQString track = albumValues[j + 2].stripWhiteSpace();
  2555. if( track.length() > 0 ) {
  2556. if( track.length() == 1 )
  2557. track.prepend( "0" );
  2558. track = "<span class='album-song-trackno'>\n" + track + "&nbsp;</span>\n";
  2559. }
  2560. TQString length;
  2561. if( albumValues[j + 4] != "0" )
  2562. length = "<span class='album-song-time'>(" + MetaBundle::prettyTime( TQString(albumValues[j + 4]).toInt(), true ) + ")</span>\n";
  2563. TQString tracktitle_formated;
  2564. TQString tracktitle;
  2565. tracktitle = escapeHTML( i18n("%1 - %2").arg( albumValues[j + 5], albumValues[j] ) );
  2566. tracktitle_formated = "<span class='album-song-title'>\n";
  2567. if( i==vectorPlace && albumValues[j + 2].toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
  2568. tracktitle_formated += "<i>\n";
  2569. if ( artist == albumValues[j + 5] )
  2570. tracktitle_formated += "<b>\n";
  2571. tracktitle_formated += tracktitle;
  2572. if ( artist == albumValues[j + 5] )
  2573. tracktitle_formated += "</b>\n";
  2574. if( i==vectorPlace && track.toInt() == m_currentTrack.track() && discNumber.toInt() == m_currentTrack.discNumber() )
  2575. tracktitle_formated += "</i>\n";
  2576. tracktitle_formated += "</span>&nbsp;";
  2577. m_HTMLSource.append(
  2578. "<div class='album-song'>\n"
  2579. "<a href=\"file:" + escapeHTMLAttr ( albumValues[j + 1] ) + "\">\n"
  2580. + track
  2581. + tracktitle_formated
  2582. + length +
  2583. "</a>\n"
  2584. "</div>\n" );
  2585. }
  2586. m_HTMLSource.append(
  2587. "</div>\n"
  2588. "</td>\n"
  2589. "</tr>\n" );
  2590. }
  2591. m_HTMLSource.append(
  2592. "</table>\n"
  2593. "</div>\n" );
  2594. }
  2595. // </Compilations with this artist>
  2596. }
  2597. TQString CurrentTrackJob::statsHTML( int score, int rating, bool statsbox ) //static
  2598. {
  2599. if( !AmarokConfig::useScores() && !AmarokConfig::useRatings() )
  2600. return "";
  2601. if ( rating < 0 )
  2602. rating = 0;
  2603. if ( rating > 10 )
  2604. rating = 10;
  2605. TQString table = TQString( "<table %1 align='right' border='0' cellspacing='0' cellpadding='0' width='100%'>%2</table>\n" )
  2606. .arg( statsbox ? "class='statsBox'" : "" );
  2607. TQString contents;
  2608. if( AmarokConfig::useScores() )
  2609. contents += TQString( "<tr title='%1'>\n" ).arg( i18n( "Score: %1" ).arg( score ) ) +
  2610. "<td class='sbtext' width='100%' align='right'>\n" + TQString::number( score ) + "</td>\n"
  2611. "<td align='left' width='1'>\n"
  2612. "<div class='sbouter'>\n"
  2613. "<div class='sbinner' style='width: "
  2614. + TQString::number( score / 2 ) + "px;'></div>\n"
  2615. "</div>\n"
  2616. "</td>\n"
  2617. "</tr>\n";
  2618. if( AmarokConfig::useRatings() )
  2619. {
  2620. contents += TQString( "<tr title='%1'>\n" ).arg( i18n( "Rating: %1" )
  2621. .arg( MetaBundle::ratingDescription( rating ) ) ) +
  2622. "<td class='ratingBox' align='right' colspan='2'>\n";
  2623. if( rating )
  2624. {
  2625. bool half = rating%2;
  2626. contents += "<nobr>\n";
  2627. TQImageIO fullStarIO;
  2628. fullStarIO.setImage( StarManager::instance()->getStarImage( half ? rating/2 + 1 : rating/2 ) );
  2629. fullStarIO.setFormat( "PNG" );
  2630. TQBuffer fullStarBuf;
  2631. fullStarBuf.open( IO_WriteOnly );
  2632. fullStarIO.setIODevice( TQT_TQIODEVICE(&fullStarBuf) );
  2633. fullStarIO.write();
  2634. fullStarBuf.close();
  2635. TQCString fullStar = KCodecs::base64Encode( fullStarBuf.buffer(), true );
  2636. const TQString img = "<img src='%1' height='13px' class='ratingStar'></img>\n";
  2637. for( int i = 0, n = rating / 2; i < n; ++i )
  2638. contents += img.arg( TQString("data:image/png;base64," + fullStar) );
  2639. if( rating % 2 )
  2640. {
  2641. TQImageIO halfStarIO;
  2642. halfStarIO.setImage( StarManager::instance()->getHalfStarImage( half ? rating/2 + 1 : rating/2 ) );
  2643. halfStarIO.setFormat( "PNG" );
  2644. TQBuffer halfStarBuf;
  2645. halfStarBuf.open( IO_WriteOnly );
  2646. halfStarIO.setIODevice( TQT_TQIODEVICE(&halfStarBuf) );
  2647. halfStarIO.write();
  2648. halfStarBuf.close();
  2649. TQCString halfStar = KCodecs::base64Encode( halfStarBuf.buffer(), true );
  2650. contents += img.arg( TQString("data:image/png;base64," + halfStar) );
  2651. }
  2652. contents += "</nobr>\n";
  2653. }
  2654. else
  2655. contents += i18n( "Not rated" );
  2656. contents += "</td>\n"
  2657. "</tr>\n";
  2658. }
  2659. return table.arg( contents );
  2660. }
  2661. bool CurrentTrackJob::doJob()
  2662. {
  2663. m_HTMLSource.append( "<html><body>\n"
  2664. "<script type='text/javascript'>\n"
  2665. //Toggle visibility of a block. NOTE: if the block ID starts with the T
  2666. //letter, 'Table' display will be used instead of the 'Block' one.
  2667. "function toggleBlock(ID) {"
  2668. "if ( document.getElementById(ID).style.display != 'none' ) {"
  2669. "document.getElementById(ID).style.display = 'none';"
  2670. "} else {"
  2671. "if ( ID[0] != 'T' ) {"
  2672. "document.getElementById(ID).style.display = 'block';"
  2673. "} else {"
  2674. "document.getElementById(ID).style.display = 'table';"
  2675. "}"
  2676. "}"
  2677. "}"
  2678. "</script>\n" );
  2679. if( !b->m_browseArtists )
  2680. {
  2681. if( !EngineController::engine()->loaded() )
  2682. {
  2683. showHome();
  2684. return true;
  2685. }
  2686. MetaBundle mb( m_currentTrack.url() );
  2687. if( mb.podcastBundle() )
  2688. {
  2689. showPodcast( mb );
  2690. return true;
  2691. }
  2692. if( m_currentTrack.url().protocol() == "lastfm" )
  2693. {
  2694. showLastFm( m_currentTrack );
  2695. return true;
  2696. }
  2697. if( m_isStream && m_currentTrack.url().protocol() != "daap" )
  2698. {
  2699. showStream( m_currentTrack );
  2700. return true;
  2701. }
  2702. }
  2703. TQString artist;
  2704. if( b->m_browseArtists )
  2705. {
  2706. artist = b->m_artist;
  2707. if( artist == m_currentTrack.artist() )
  2708. {
  2709. b->m_browseArtists = false;
  2710. b->m_artist = TQString();
  2711. b->m_contextBackHistory.clear();
  2712. b->m_contextBackHistory.push_back( "current://track" );
  2713. }
  2714. }
  2715. else
  2716. artist = m_currentTrack.artist();
  2717. const uint artist_id = CollectionDB::instance()->artistID( artist );
  2718. const uint album_id = CollectionDB::instance()->albumID ( m_currentTrack.album() );
  2719. QueryBuilder qb;
  2720. TQStringList values;
  2721. if( b->m_browseArtists )
  2722. showBrowseArtistHeader( artist );
  2723. else if( b->m_browseLabels )
  2724. {
  2725. showBrowseLabelHeader( b->m_label );
  2726. showSongsWithLabel( b->m_label );
  2727. m_HTMLSource.append( "</body></html>\n" );
  2728. return true;
  2729. }
  2730. else
  2731. showCurrentArtistHeader( m_currentTrack );
  2732. if ( ContextBrowser::instance()->m_showLabels && !b->m_browseArtists )
  2733. showUserLabels( m_currentTrack );
  2734. if( ContextBrowser::instance()->m_showRelated || ContextBrowser::instance()->m_showSuggested )
  2735. {
  2736. TQStringList relArtists = CollectionDB::instance()->similarArtists( artist, 10 );
  2737. if ( !relArtists.isEmpty() )
  2738. {
  2739. if( ContextBrowser::instance()->m_showRelated )
  2740. showRelatedArtists( artist, relArtists );
  2741. if( ContextBrowser::instance()->m_showSuggested )
  2742. showSuggestedSongs( relArtists );
  2743. }
  2744. }
  2745. TQString artistName = artist.isEmpty() ? i18n( "This Artist" ) : artist ;
  2746. if ( !artist.isEmpty() )
  2747. {
  2748. if( ContextBrowser::instance()->m_showFaves )
  2749. showArtistsFaves( artistName, artist_id );
  2750. showArtistsAlbums( artist, artist_id, album_id );
  2751. showArtistsCompilations( artist, artist_id, album_id );
  2752. }
  2753. m_HTMLSource.append( "</body></html>\n" );
  2754. return true;
  2755. }
  2756. void ContextBrowser::showIntroduction()
  2757. {
  2758. if ( currentPage() != m_contextTab )
  2759. {
  2760. blockSignals( true );
  2761. showPage( m_contextTab );
  2762. blockSignals( false );
  2763. }
  2764. // Do we have to rebuild the page? I don't care
  2765. m_HTMLSource = TQString();
  2766. m_HTMLSource.append(
  2767. "<html><body>\n"
  2768. "<div id='introduction_box' class='box'>\n"
  2769. "<div id='introduction_box-header' class='box-header'>\n"
  2770. "<span id='introduction_box-header-title' class='box-header-title'>\n"
  2771. + i18n( "Hello Amarok user!" ) +
  2772. "</span>\n"
  2773. "</div>\n"
  2774. "<div id='introduction_box-body' class='box-body'>\n"
  2775. "<div class='info'><p>\n" +
  2776. i18n( "This is the Context Browser: "
  2777. "it shows you contextual information about the currently playing track. "
  2778. "In order to use this feature of Amarok, you need to build a Collection."
  2779. ) +
  2780. "</p></div>\n"
  2781. "<div align='center'>\n"
  2782. "<input type='button' onClick='window.location.href=\"show:collectionSetup\";' value='" +
  2783. i18n( "Build Collection..." ) +
  2784. "'></div><br />\n"
  2785. "</div>\n"
  2786. "</div>\n"
  2787. "</body></html>\n"
  2788. );
  2789. m_currentTrackPage->set( m_HTMLSource );
  2790. saveHtmlData(); // Send html code to file
  2791. }
  2792. void ContextBrowser::showScanning()
  2793. {
  2794. if ( currentPage() != m_contextTab )
  2795. {
  2796. blockSignals( true );
  2797. showPage( m_contextTab );
  2798. blockSignals( false );
  2799. }
  2800. // Do we have to rebuild the page? I don't care
  2801. m_HTMLSource="";
  2802. m_HTMLSource.append(
  2803. "<html><body>\n"
  2804. "<div id='building_box' class='box'>\n"
  2805. "<div id='building_box-header' class='box-header'>\n"
  2806. "<span id='building_box-header-title' class='box-header-title'>\n"
  2807. + i18n( "Building Collection Database..." ) +
  2808. "</span>\n"
  2809. "</div>\n"
  2810. "<div id='building_box-body' class='box-body'>\n"
  2811. "<div class='info'><p>\n" + i18n( "Please be patient while Amarok scans your music collection. You can watch the progress of this activity in the statusbar." ) + "</p></div>\n"
  2812. "</div>\n"
  2813. "</div>\n"
  2814. "</body></html>\n"
  2815. );
  2816. m_currentTrackPage->set( m_HTMLSource );
  2817. saveHtmlData(); // Send html code to file
  2818. }
  2819. TQString
  2820. ContextBrowser::getEncodedImage( const TQString &imageUrl )
  2821. {
  2822. // Embed cover image in html (encoded string), to get around tdehtml's caching
  2823. //debug() << "Encoding imageUrl: " << imageUrl << endl;
  2824. tqApp->lock();
  2825. const TQImage img( imageUrl, "PNG" );
  2826. tqApp->unlock();
  2827. TQByteArray ba;
  2828. TQBuffer buffer( ba );
  2829. buffer.open( IO_WriteOnly );
  2830. tqApp->lock();
  2831. img.save( &buffer, "PNG" ); // writes image into ba in PNG format
  2832. tqApp->unlock();
  2833. const TQString coverImage = TQString( "data:image/png;base64,%1" ).arg( KCodecs::base64Encode( ba ).data() );
  2834. //debug() << "Encoded imageUrl: " << coverImage << endl;
  2835. return coverImage;
  2836. }
  2837. //////////////////////////////////////////////////////////////////////////////////////////
  2838. // Lyrics-Tab
  2839. //////////////////////////////////////////////////////////////////////////////////////////
  2840. void ContextBrowser::showLyrics( const TQString &url )
  2841. {
  2842. #if 0
  2843. if( BrowserBar::instance()->currentBrowser() != this )
  2844. {
  2845. debug() << "current browser is not context, aborting showLyrics()" << endl;
  2846. m_dirtyLyricsPage = true;
  2847. return;
  2848. }
  2849. #endif
  2850. DEBUG_BLOCK
  2851. if ( currentPage() != m_lyricsTab )
  2852. {
  2853. blockSignals( true );
  2854. showPage( m_lyricsTab );
  2855. blockSignals( false );
  2856. }
  2857. if ( !m_dirtyLyricsPage ) return;
  2858. TQString lyrics = CollectionDB::instance()->getLyrics( EngineController::instance()->bundle().url().path() );
  2859. // don't rely on caching for streams
  2860. const bool cached = !lyrics.isEmpty() && !EngineController::engine()->isStream();
  2861. TQString title = EngineController::instance()->bundle().title();
  2862. TQString artist = EngineController::instance()->bundle().artist();
  2863. if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
  2864. title = title.remove(" (PREVIEW: buy it at www.magnatune.com)");
  2865. if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
  2866. artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)");
  2867. if ( title.isEmpty() ) {
  2868. /* If title is empty, try to use pretty title.
  2869. The fact that it often (but not always) has artist name together, can be bad,
  2870. but at least the user will hopefully get nice suggestions. */
  2871. TQString prettyTitle = EngineController::instance()->bundle().prettyTitle();
  2872. int h = prettyTitle.find( '-' );
  2873. if ( h != -1 )
  2874. {
  2875. title = prettyTitle.mid( h+1 ).stripWhiteSpace();
  2876. if( title.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
  2877. title = title.remove(" (PREVIEW: buy it at www.magnatune.com)");
  2878. if ( artist.isEmpty() ) {
  2879. artist = prettyTitle.mid( 0, h ).stripWhiteSpace();
  2880. if( artist.contains("PREVIEW: buy it at www.magnatune.com", true) >= 1 )
  2881. artist = artist.remove(" (PREVIEW: buy it at www.magnatune.com)");
  2882. }
  2883. }
  2884. }
  2885. m_lyricSearchUrl = TQString( "http://www.google.com/search?ie=UTF-8&q=lyrics+%1+%2" )
  2886. .arg( KURL::encode_string_no_slash( '"' + artist + '"', 106 /*utf-8*/ ),
  2887. KURL::encode_string_no_slash( '"' + title + '"', 106 /*utf-8*/ ) );
  2888. m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled(false);
  2889. if( ( !cached || url == "reload" ) && !ScriptManager::instance()->lyricsScriptRunning() ) {
  2890. const TQStringList scripts = ScriptManager::instance()->lyricsScripts();
  2891. lyrics =
  2892. i18n( "Sorry, no lyrics script running.") + "<br />\n" +
  2893. "<br /><div class='info'>\n"+
  2894. i18n( "Available Lyrics Scripts:" ) + "<br />\n";
  2895. foreach ( scripts ) {
  2896. lyrics += TQString( "<a href=\"runscript:%1\">%2</a><br />\n" ).arg( *it, *it );
  2897. }
  2898. lyrics += "<br />\n" + i18n( "Click on one of the scripts to run it, or use the Script Manager, to be able"
  2899. " to see all the scripts, and download new ones from the Web." );
  2900. lyrics += "<br /><div align='center'>\n"
  2901. "<form><input type='button' onClick=\"window.location='show:scriptmanager'\" value='" +
  2902. i18n( "Run Script Manager..." ) +
  2903. "'></form></div><br /></div>\n";
  2904. m_HTMLSource = TQString (
  2905. "<html><body>\n"
  2906. "<div id='lyrics_box' class='box'>\n"
  2907. "<div id='lyrics_box-header' class='box-header'>\n"
  2908. "<span id='lyrics_box-header-title' class='box-header-title'>\n"
  2909. + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) +
  2910. "</span>\n"
  2911. "</div>\n"
  2912. "<div id='lyrics_box-body' class='box-body'>\n"
  2913. + lyrics +
  2914. "</div>\n"
  2915. "</div>\n"
  2916. "</body></html>\n"
  2917. );
  2918. m_lyricsPage->set( m_HTMLSource );
  2919. m_dirtyLyricsPage = false;
  2920. saveHtmlData(); // Send html code to file
  2921. return;
  2922. }
  2923. if( cached && url.isEmpty() )
  2924. {
  2925. lyricsResult( lyrics.utf8(), true );
  2926. }
  2927. else
  2928. {
  2929. m_HTMLSource = TQString (
  2930. "<html><body>\n"
  2931. "<div id='lyrics_box' class='box'>\n"
  2932. "<div id='lyrics_box-header' class='box-header'>\n"
  2933. "<span id='lyrics_box-header-title' class='box-header-title'>\n"
  2934. + i18n( "Fetching Lyrics" ) +
  2935. "</span>\n"
  2936. "</div>\n"
  2937. "<div id='lyrics_box-body' class='box-body'>\n"
  2938. "<div class='info'><p>\n" + i18n( "Fetching Lyrics..." ) + "</p></div>\n"
  2939. "</div>\n"
  2940. "</div>\n"
  2941. "</body></html>\n"
  2942. );
  2943. m_lyricsPage->set( m_HTMLSource );
  2944. saveHtmlData(); // Send html code to file
  2945. if( url.isNull() || url == "reload" )
  2946. ScriptManager::instance()->notifyFetchLyrics( artist, title );
  2947. else
  2948. ScriptManager::instance()->notifyFetchLyricsByUrl( url );
  2949. }
  2950. }
  2951. void
  2952. ContextBrowser::lyricsResult( TQCString cXmlDoc, bool cached ) //SLOT
  2953. {
  2954. TQDomDocument doc;
  2955. TQString xmldoc = TQString::fromUtf8( cXmlDoc );
  2956. if( !doc.setContent( xmldoc ) )
  2957. {
  2958. m_HTMLSource="";
  2959. m_HTMLSource.append(
  2960. "<html><body>\n"
  2961. "<div id='lyrics_box' class='box'>\n"
  2962. "<div id='lyrics_box-header' class='box-header'>\n"
  2963. "<span id='lyrics_box-header-title' class='box-header-title'>\n"
  2964. + i18n( "Error" ) +
  2965. "</span>\n"
  2966. "</div>\n"
  2967. "<div id='lyrics_box-body' class='box-body'><p>\n"
  2968. + i18n( "Lyrics could not be retrieved because the server was not reachable." ) +
  2969. "</p></div>\n"
  2970. "</div>\n"
  2971. "</body></html>\n"
  2972. );
  2973. m_lyricsPage->set( m_HTMLSource );
  2974. saveHtmlData(); // Send html code to file
  2975. m_dirtyLyricsPage = false;
  2976. return;
  2977. }
  2978. TQString lyrics;
  2979. TQDomElement el = doc.documentElement();
  2980. m_lyricCurrentUrl = el.attribute( "page_url" );
  2981. ScriptManager* const sm = ScriptManager::instance();
  2982. TDEConfig spec( sm->specForScript( sm->lyricsScriptRunning() ), true, false );
  2983. spec.setGroup( "Lyrics" );
  2984. if ( el.attribute( "add_url" ).isEmpty() )
  2985. {
  2986. m_lyricAddUrl = spec.readPathEntry( "add_url" );
  2987. m_lyricAddUrl.replace( "MAGIC_ARTIST", KURL::encode_string_no_slash( EngineController::instance()->bundle().artist() ) );
  2988. m_lyricAddUrl.replace( "MAGIC_TITLE", KURL::encode_string_no_slash( EngineController::instance()->bundle().title() ) );
  2989. m_lyricAddUrl.replace( "MAGIC_ALBUM", KURL::encode_string_no_slash( EngineController::instance()->bundle().album() ) );
  2990. m_lyricAddUrl.replace( "MAGIC_YEAR", KURL::encode_string_no_slash( TQString::number( EngineController::instance()->bundle().year() ) ) );
  2991. }
  2992. else
  2993. m_lyricAddUrl = el.attribute( "add_url" );
  2994. if ( el.tagName() == "suggestions" )
  2995. {
  2996. const TQDomNodeList l = doc.elementsByTagName( "suggestion" );
  2997. if( l.length() ==0 )
  2998. {
  2999. lyrics = i18n( "Lyrics for track not found" );
  3000. }
  3001. else
  3002. {
  3003. lyrics = i18n( "Lyrics for track not found, here are some suggestions:" ) + "<br/><br/>\n";
  3004. for( uint i = 0; i < l.length(); ++i ) {
  3005. const TQString url = l.item( i ).toElement().attribute( "url" );
  3006. const TQString artist = l.item( i ).toElement().attribute( "artist" );
  3007. const TQString title = l.item( i ).toElement().attribute( "title" );
  3008. lyrics += "<a href='show:suggestLyric-" + url + "'>\n" + i18n("%1 - %2").arg( artist, title );
  3009. lyrics += "</a><br/>\n";
  3010. }
  3011. }
  3012. lyrics += i18n( "<p>You can <a href=\"%1\">search for the lyrics</a> on the Web.</p>" )
  3013. .arg( TQString( m_lyricSearchUrl ).replace( TQRegExp( "^http:" ), "externalurl:" ) );
  3014. }
  3015. else {
  3016. lyrics = el.text();
  3017. lyrics.replace( "\n", "<br/>\n" ); // Plaintext -> HTML
  3018. const TQString title = el.attribute( "title" );
  3019. const TQString artist = el.attribute( "artist" );
  3020. const TQString site = el.attribute( "site" ).isEmpty() ? spec.readEntry( "site" ) : el.attribute( "site" );
  3021. const TQString site_url = el.attribute( "site_url" ).isEmpty() ? spec.readEntry( "site_url" ) : el.attribute( "site_url" );
  3022. lyrics.prepend( "<font size='2'><b>\n" + title + "</b><br/><u>\n" + artist+ "</font></u></font><br/>\n" );
  3023. if( !cached ) {
  3024. lyrics.append( "<br/><br/><i>\n" + i18n( "Powered by %1 (%2)" ).arg( site, site_url ) + "</i>\n" );
  3025. CollectionDB::instance()->setLyrics( EngineController::instance()->bundle().url().path(), xmldoc, EngineController::instance()->bundle().uniqueId() );
  3026. }
  3027. }
  3028. m_HTMLSource="";
  3029. m_HTMLSource.append(
  3030. "<html><body>\n"
  3031. "<div id='lyrics_box' class='box'>\n"
  3032. "<div id='lyrics_box-header' class='box-header'>\n"
  3033. "<span id='lyrics_box-header-title' class='box-header-title'>\n"
  3034. + ( cached ? i18n( "Cached Lyrics" ) : i18n( "Lyrics" ) ) +
  3035. "</span>\n"
  3036. "</div>\n"
  3037. "<div id='lyrics_box-body' class='box-body'>\n"
  3038. + lyrics +
  3039. "</div>\n"
  3040. "</div>\n"
  3041. "</body></html>\n"
  3042. );
  3043. m_lyricsPage->set( m_HTMLSource );
  3044. //Reset scroll
  3045. m_lyricsPage->view()->setContentsPos(0, 0);
  3046. saveHtmlData(); // Send html code to file
  3047. m_lyricsToolBar->getButton( LYRICS_BROWSER )->setEnabled( !m_lyricCurrentUrl.isEmpty() );
  3048. m_dirtyLyricsPage = false;
  3049. }
  3050. void
  3051. ContextBrowser::lyricsExternalPage() //SLOT
  3052. {
  3053. Amarok::invokeBrowser( m_lyricCurrentUrl );
  3054. }
  3055. void
  3056. ContextBrowser::lyricsAdd() //SLOT
  3057. {
  3058. Amarok::invokeBrowser( m_lyricAddUrl );
  3059. }
  3060. void
  3061. ContextBrowser::lyricsEditToggle() //SLOT
  3062. {
  3063. if ( m_lyricsToolBar->getButton( LYRICS_EDIT )->isOn() )
  3064. {
  3065. m_lyricsBeingEditedUrl = EngineController::instance()->bundle().url().path();
  3066. m_lyricsBeingEditedArtist = EngineController::instance()->bundle().artist();
  3067. m_lyricsBeingEditedTitle = EngineController::instance()->bundle().title();
  3068. TQString xml = CollectionDB::instance()->getLyrics( m_lyricsBeingEditedUrl ), lyrics;
  3069. TQDomDocument doc;
  3070. if( doc.setContent( xml ) )
  3071. lyrics = doc.documentElement().text();
  3072. else
  3073. lyrics = TQString();
  3074. m_lyricsTextEdit->setText( lyrics );
  3075. m_lyricsPage->hide();
  3076. m_lyricsTextEdit->show();
  3077. }
  3078. else
  3079. {
  3080. m_lyricsTextEdit->hide();
  3081. TQDomDocument doc;
  3082. TQDomElement e = doc.createElement( "lyrics" );
  3083. e.setAttribute( "artist", m_lyricsBeingEditedArtist );
  3084. e.setAttribute( "title", m_lyricsBeingEditedTitle );
  3085. TQDomText t = doc.createTextNode( m_lyricsTextEdit->text() );
  3086. e.appendChild( t );
  3087. doc.appendChild( e );
  3088. CollectionDB::instance()->setLyrics( m_lyricsBeingEditedUrl, doc.toString(), CollectionDB::instance()->uniqueIdFromUrl( KURL( m_lyricsBeingEditedUrl) ) );
  3089. m_lyricsPage->show();
  3090. lyricsChanged( m_lyricsBeingEditedUrl );
  3091. }
  3092. }
  3093. void
  3094. ContextBrowser::lyricsSearch() //SLOT
  3095. {
  3096. Amarok::invokeBrowser( m_lyricSearchUrl );
  3097. }
  3098. void
  3099. ContextBrowser::lyricsRefresh() //SLOT
  3100. {
  3101. m_dirtyLyricsPage = true;
  3102. showLyrics( "reload" );
  3103. }
  3104. void
  3105. ContextBrowser::lyricsSearchText(TQString const &text) //SLOT
  3106. {
  3107. m_lyricsPage->findText( text, 0 );
  3108. lyricsSearchTextNext();
  3109. }
  3110. void
  3111. ContextBrowser::lyricsSearchTextNext() //SLOT
  3112. {
  3113. m_lyricsPage->findTextNext();
  3114. }
  3115. void
  3116. ContextBrowser::lyricsSearchTextShow() //SLOT
  3117. {
  3118. m_lyricsSearchText->setEnabled( true );
  3119. m_lyricsTextBar->show();
  3120. m_lyricsTextBarShowed = true;
  3121. m_lyricsSearchText->setFocus();
  3122. }
  3123. void
  3124. ContextBrowser::lyricsSearchTextHide() //SLOT
  3125. {
  3126. m_lyricsSearchText->setText("");
  3127. m_lyricsSearchText->setEnabled( false );
  3128. m_lyricsTextBar->hide();
  3129. m_lyricsTextBarShowed=false;
  3130. }
  3131. void
  3132. ContextBrowser::lyricsSearchTextToggle() //SLOT
  3133. {
  3134. if ( m_lyricsTextBarShowed )
  3135. {
  3136. lyricsSearchTextHide();
  3137. }
  3138. else
  3139. {
  3140. lyricsSearchTextShow();
  3141. }
  3142. }
  3143. // Wikipedia-Tab
  3144. //////////////////////////////////////////////////////////////////////////////////////////
  3145. TQString
  3146. ContextBrowser::wikiArtistPostfix() const
  3147. {
  3148. if( wikiLocale() == "en" )
  3149. return " (band)";
  3150. else if( wikiLocale() == "de" )
  3151. return " (Band)";
  3152. else
  3153. return "";
  3154. }
  3155. TQString
  3156. ContextBrowser::wikiAlbumPostfix() const
  3157. {
  3158. if( wikiLocale() == "en" )
  3159. return " (album)";
  3160. else
  3161. return "";
  3162. }
  3163. TQString
  3164. ContextBrowser::wikiTrackPostfix() const
  3165. {
  3166. if( wikiLocale() == "en" )
  3167. return " (song)";
  3168. else
  3169. return "";
  3170. }
  3171. void
  3172. ContextBrowser::wikiConfigChanged( int /*activeItem*/ ) // SLOT
  3173. {
  3174. // keep in sync with localeList in wikiConfig
  3175. TQString text = m_wikiLocaleCombo->currentText();
  3176. m_wikiLocaleEdit->setEnabled( text == i18n("Other...") );
  3177. if( text == i18n("English") )
  3178. m_wikiLocaleEdit->setText( "en" );
  3179. else if( text == i18n("German") )
  3180. m_wikiLocaleEdit->setText( "de" );
  3181. else if( text == i18n("French") )
  3182. m_wikiLocaleEdit->setText( "fr" );
  3183. else if( text == i18n("Polish") )
  3184. m_wikiLocaleEdit->setText( "pl" );
  3185. else if( text == i18n("Japanese") )
  3186. m_wikiLocaleEdit->setText( "ja" );
  3187. else if( text == i18n("Spanish") )
  3188. m_wikiLocaleEdit->setText( "es" );
  3189. }
  3190. void
  3191. ContextBrowser::wikiConfigApply() // SLOT
  3192. {
  3193. const bool changed = m_wikiLocaleEdit->text() != wikiLocale();
  3194. setWikiLocale( m_wikiLocaleEdit->text() );
  3195. if ( changed && currentPage() == m_wikiTab && !m_wikiCurrentEntry.isNull() )
  3196. {
  3197. m_dirtyWikiPage = true;
  3198. showWikipediaEntry( m_wikiCurrentEntry );
  3199. }
  3200. showWikipedia();
  3201. }
  3202. void
  3203. ContextBrowser::wikiConfig() // SLOT
  3204. {
  3205. TQStringList localeList;
  3206. localeList
  3207. << i18n( "English" )
  3208. << i18n( "German" )
  3209. << i18n( "French" )
  3210. << i18n( "Polish" )
  3211. << i18n( "Japanese" )
  3212. << i18n( "Spanish" )
  3213. << i18n( "Other..." );
  3214. int index;
  3215. if( wikiLocale() == "en" )
  3216. index = 0;
  3217. else if( wikiLocale() == "de" )
  3218. index = 1;
  3219. else if( wikiLocale() == "fr" )
  3220. index = 2;
  3221. else if( wikiLocale() == "pl" )
  3222. index = 3;
  3223. else if( wikiLocale() == "ja" )
  3224. index = 4;
  3225. else if( wikiLocale() == "es" )
  3226. index = 5;
  3227. else // other
  3228. index = 6;
  3229. m_wikiConfigDialog = new KDialogBase( this, 0, true, 0, KDialogBase::Ok|KDialogBase::Apply|KDialogBase::Cancel );
  3230. kapp->setTopWidget( m_wikiConfigDialog );
  3231. m_wikiConfigDialog->setCaption( kapp->makeStdCaption( i18n( "Wikipedia Locale" ) ) );
  3232. TQVBox *box = m_wikiConfigDialog->makeVBoxMainWidget();
  3233. m_wikiLocaleCombo = new TQComboBox( box );
  3234. m_wikiLocaleCombo->insertStringList( localeList );
  3235. TQHBox *hbox = new TQHBox( box );
  3236. TQLabel *otherLabel = new TQLabel( i18n( "Locale: " ), hbox );
  3237. m_wikiLocaleEdit = new TQLineEdit( "en", hbox );
  3238. otherLabel->setBuddy( m_wikiLocaleEdit );
  3239. TQToolTip::add( m_wikiLocaleEdit, i18n( "2-letter language code for your Wikipedia locale" ) );
  3240. connect( m_wikiLocaleCombo, TQT_SIGNAL( activated(int) ), TQT_SLOT( wikiConfigChanged(int) ) );
  3241. connect( m_wikiConfigDialog, TQT_SIGNAL( applyClicked() ), TQT_SLOT( wikiConfigApply() ) );
  3242. m_wikiLocaleEdit->setText( wikiLocale() );
  3243. m_wikiLocaleCombo->setCurrentItem( index );
  3244. wikiConfigChanged( index ); // a little redundant, but saves ugly code, and ensures the lineedit enabled status is correct
  3245. m_wikiConfigDialog->setInitialSize( TQSize( 240, 100 ) );
  3246. const int result = m_wikiConfigDialog->exec();
  3247. if( result == TQDialog::Accepted )
  3248. wikiConfigApply();
  3249. delete m_wikiConfigDialog;
  3250. }
  3251. TQString
  3252. ContextBrowser::wikiLocale()
  3253. {
  3254. if( s_wikiLocale.isEmpty() )
  3255. return TQString( "en" );
  3256. return s_wikiLocale;
  3257. }
  3258. void
  3259. ContextBrowser::setWikiLocale( const TQString &locale )
  3260. {
  3261. AmarokConfig::setWikipediaLocale( locale );
  3262. s_wikiLocale = locale;
  3263. }
  3264. TQString
  3265. ContextBrowser::wikiURL( const TQString &item )
  3266. {
  3267. // add any special characters to be replaced here
  3268. TQString wStr = TQString(item).replace( "/", " " );
  3269. return TQString( "http://%1.wikipedia.org/wiki/" ).arg( wikiLocale() )
  3270. + KURL::encode_string_no_slash( wStr, 106 /*utf-8*/ );
  3271. }
  3272. void
  3273. ContextBrowser::reloadWikipedia()
  3274. {
  3275. m_wikiJob = NULL;
  3276. showWikipediaEntry( m_wikiCurrentEntry, true );
  3277. }
  3278. void
  3279. ContextBrowser::showWikipediaEntry( const TQString &entry, bool replaceHistory )
  3280. {
  3281. m_wikiCurrentEntry = entry;
  3282. showWikipedia( wikiURL( entry ), false, replaceHistory );
  3283. }
  3284. void
  3285. ContextBrowser::showLabelsDialog()
  3286. {
  3287. DEBUG_BLOCK
  3288. KURL currentUrl = EngineController::instance()->bundle().url();
  3289. TQStringList allLabels = CollectionDB::instance()->labelList();
  3290. TQStringList trackLabels = CollectionDB::instance()->getLabels( currentUrl.path(), CollectionDB::typeUser );
  3291. debug() << "Showing add label dialog" << endl;
  3292. KDialogBase *dialog = new KDialogBase( this, 0, false, TQString(), KDialogBase::Ok|KDialogBase::Cancel );
  3293. dialog->makeVBoxMainWidget();
  3294. TQLabel *labelText = new TQLabel( i18n(
  3295. "<p>Add a new label in the field below and press Enter, or choose labels from the list</p>"),
  3296. dialog->mainWidget() );
  3297. m_addLabelEdit = new ClickLineEdit( i18n( "Add new label" ), dialog->mainWidget() );
  3298. m_addLabelEdit->installEventFilter( this );
  3299. m_addLabelEdit->setFrame( TQFrame::Sunken );
  3300. TQToolTip::add( m_addLabelEdit, i18n( "Enter a new label and press Return to add it" ) );
  3301. dialog->setFocusProxy( m_addLabelEdit );
  3302. labelText->setBuddy( m_addLabelEdit );
  3303. m_labelListView = new TQListView( dialog->mainWidget() );
  3304. m_labelListView->addColumn( i18n( "Label" ) );
  3305. m_labelListView->setSizePolicy( TQSizePolicy::Expanding, TQSizePolicy::Expanding );
  3306. m_labelListView->setColumnWidthMode( 0, TQListView::Maximum );
  3307. foreach( allLabels )
  3308. {
  3309. TQCheckListItem *item = new TQCheckListItem( m_labelListView, *it, TQCheckListItem::CheckBox );
  3310. item->setOn( trackLabels.contains( *it ) );
  3311. }
  3312. if( dialog->exec() == TQDialog::Accepted )
  3313. {
  3314. debug() << "Dialog closed, updating labels" << endl;
  3315. TQStringList newTrackLabels;
  3316. TQListViewItemIterator iter( m_labelListView );
  3317. while( iter.current() )
  3318. {
  3319. TQCheckListItem *item = static_cast<TQCheckListItem*>( iter.current() );
  3320. if( item->isOn() )
  3321. newTrackLabels.append( item->text() );
  3322. iter++;
  3323. }
  3324. CollectionDB::instance()->setLabels( currentUrl.path(),
  3325. newTrackLabels,
  3326. CollectionDB::instance()->uniqueIdFromUrl( currentUrl ),
  3327. CollectionDB::typeUser );
  3328. CollectionDB::instance()->cleanLabels();
  3329. if( newTrackLabels != trackLabels
  3330. && currentUrl == EngineController::instance()->bundle().url() )
  3331. {
  3332. m_dirtyCurrentTrackPage = true;
  3333. showCurrentTrack();
  3334. }
  3335. }
  3336. delete dialog; //deletes children
  3337. m_addLabelEdit = 0;
  3338. m_labelListView = 0;
  3339. }
  3340. bool
  3341. ContextBrowser::eventFilter( TQObject *o, TQEvent *e )
  3342. {
  3343. switch( e->type() )
  3344. {
  3345. case 6/*TQEvent::KeyPress*/:
  3346. #define e TQT_TQKEYEVENT(e)
  3347. if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(m_addLabelEdit) ) //the add label lineedit
  3348. {
  3349. switch( e->key() )
  3350. {
  3351. case Key_Return:
  3352. case Key_Enter:
  3353. {
  3354. TQCheckListItem *item = new TQCheckListItem( m_labelListView, m_addLabelEdit->text(), TQCheckListItem::CheckBox );
  3355. item->setOn( true );
  3356. m_addLabelEdit->setText( TQString() );
  3357. return true;
  3358. }
  3359. default:
  3360. return false;
  3361. }
  3362. }
  3363. if (TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(m_lyricsSearchText))
  3364. {
  3365. switch ( e->key() )
  3366. {
  3367. case Key_Escape:
  3368. {
  3369. lyricsSearchTextHide();
  3370. return true;
  3371. }
  3372. default:
  3373. return false;
  3374. }
  3375. }
  3376. default:
  3377. break;
  3378. }
  3379. return KTabWidget::eventFilter( TQT_TQOBJECT(o), TQT_TQEVENT(e) );
  3380. }
  3381. void ContextBrowser::showWikipedia( const TQString &url, bool fromHistory, bool replaceHistory )
  3382. {
  3383. #if 0
  3384. if( BrowserBar::instance()->currentBrowser() != this )
  3385. {
  3386. debug() << "current browser is not context, aborting showWikipedia()" << endl;
  3387. m_dirtyWikiPage = true;
  3388. return;
  3389. }
  3390. #endif
  3391. if ( currentPage() != m_wikiTab )
  3392. {
  3393. blockSignals( true );
  3394. showPage( m_wikiTab );
  3395. blockSignals( false );
  3396. }
  3397. if ( !m_dirtyWikiPage || m_wikiJob ) return;
  3398. // Disable the Open in a Browser button, because while loading it would open wikipedia main page.
  3399. m_wikiToolBar->setItemEnabled( WIKI_BROWSER, false );
  3400. m_HTMLSource="";
  3401. m_HTMLSource.append(
  3402. "<html><body>\n"
  3403. "<div id='wiki_box' class='box'>\n"
  3404. "<div id='wiki_box-header' class='box-header'>\n"
  3405. "<span id='wiki_box-header-title' class='box-header-title'>\n"
  3406. + i18n( "Wikipedia" ) +
  3407. "</span>\n"
  3408. "</div>\n"
  3409. "<div id='wiki_box-body' class='box-body'>\n"
  3410. "<div class='info'><p>\n" + i18n( "Fetching Wikipedia Information" ) + " ...</p></div>\n"
  3411. "</div>\n"
  3412. "</div>\n"
  3413. "</body></html>\n"
  3414. );
  3415. m_wikiPage->set( m_HTMLSource );
  3416. saveHtmlData(); // Send html code to file
  3417. if ( url.isEmpty() )
  3418. {
  3419. TQString tmpWikiStr;
  3420. if ( (EngineController::instance()->bundle().url().protocol() == "lastfm") ||
  3421. (EngineController::instance()->bundle().url().protocol() == "daap") ||
  3422. !EngineController::engine()->isStream() )
  3423. {
  3424. if ( !EngineController::instance()->bundle().artist().isEmpty() )
  3425. {
  3426. tmpWikiStr = EngineController::instance()->bundle().artist();
  3427. tmpWikiStr += wikiArtistPostfix();
  3428. }
  3429. else if ( !EngineController::instance()->bundle().title().isEmpty() )
  3430. {
  3431. tmpWikiStr = EngineController::instance()->bundle().title();
  3432. }
  3433. else
  3434. {
  3435. tmpWikiStr = EngineController::instance()->bundle().prettyTitle();
  3436. }
  3437. }
  3438. else
  3439. {
  3440. tmpWikiStr = EngineController::instance()->bundle().prettyTitle();
  3441. }
  3442. //Hack to make wiki searches work with magnatune preview tracks
  3443. if (tmpWikiStr.contains( "PREVIEW: buy it at www.magnatune.com" ) >= 1 ) {
  3444. tmpWikiStr = tmpWikiStr.remove(" (PREVIEW: buy it at www.magnatune.com)" );
  3445. int index = tmpWikiStr.find( '-' );
  3446. if ( index != -1 ) {
  3447. tmpWikiStr = tmpWikiStr.left (index - 1);
  3448. }
  3449. }
  3450. m_wikiCurrentEntry = tmpWikiStr;
  3451. m_wikiCurrentUrl = wikiURL( tmpWikiStr );
  3452. }
  3453. else
  3454. {
  3455. m_wikiCurrentUrl = url;
  3456. }
  3457. // Append new URL to history
  3458. if ( replaceHistory )
  3459. {
  3460. m_wikiBackHistory.back() = m_wikiCurrentUrl;
  3461. }
  3462. else if ( !fromHistory ) {
  3463. m_wikiBackHistory += m_wikiCurrentUrl;
  3464. m_wikiForwardHistory.clear();
  3465. }
  3466. // Limit number of items in history
  3467. if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY )
  3468. m_wikiBackHistory.pop_front();
  3469. // Remove all items from the button-menus
  3470. m_wikiBackPopup->clear();
  3471. m_wikiForwardPopup->clear();
  3472. // Populate button menus with URLs from the history
  3473. TQStringList::ConstIterator it;
  3474. uint count;
  3475. // Reverse iterate over both lists
  3476. count = m_wikiBackHistory.count()-1;
  3477. it = m_wikiBackHistory.fromLast();
  3478. if( count > 0 )
  3479. it--;
  3480. for ( uint i=0; i<count; i++, --it )
  3481. m_wikiBackPopup->insertItem( SmallIconSet( "wiki" ), *it, i );
  3482. count = m_wikiForwardHistory.count();
  3483. it = m_wikiForwardHistory.fromLast();
  3484. for ( uint i=0; i<count; i++, --it )
  3485. m_wikiForwardPopup->insertItem( SmallIconSet( "wiki" ), *it, i );
  3486. m_wikiToolBar->setItemEnabled( WIKI_BACK, m_wikiBackHistory.size() > 1 );
  3487. m_wikiToolBar->setItemEnabled( WIKI_FORWARD, m_wikiForwardHistory.size() > 0 );
  3488. m_wikiBaseUrl = m_wikiCurrentUrl.mid(0 , m_wikiCurrentUrl.find("wiki/"));
  3489. m_wikiJob = TDEIO::storedGet( m_wikiCurrentUrl, false, false );
  3490. Amarok::StatusBar::instance()->newProgressOperation( m_wikiJob )
  3491. .setDescription( i18n( "Fetching Wikipedia Information" ) );
  3492. connect( m_wikiJob, TQT_SIGNAL( result( TDEIO::Job* ) ), TQT_SLOT( wikiResult( TDEIO::Job* ) ) );
  3493. }
  3494. void
  3495. ContextBrowser::wikiHistoryBack() //SLOT
  3496. {
  3497. //Disable the button as history may be empty. Reenabled later by showWikipedia.
  3498. m_wikiToolBar->setItemEnabled( WIKI_BACK, false );
  3499. m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false );
  3500. m_wikiForwardHistory += m_wikiBackHistory.last();
  3501. m_wikiBackHistory.pop_back();
  3502. m_dirtyWikiPage = true;
  3503. m_wikiCurrentEntry = TQString();
  3504. showWikipedia( m_wikiBackHistory.last(), true );
  3505. }
  3506. void
  3507. ContextBrowser::wikiHistoryForward() //SLOT
  3508. {
  3509. //Disable the button as history may be empty. Reenabled later by showWikipedia.
  3510. m_wikiToolBar->setItemEnabled( WIKI_FORWARD, false );
  3511. m_wikiToolBar->setItemEnabled( WIKI_BACK, false );
  3512. m_wikiBackHistory += m_wikiForwardHistory.last();
  3513. m_wikiForwardHistory.pop_back();
  3514. m_dirtyWikiPage = true;
  3515. m_wikiCurrentEntry = TQString();
  3516. showWikipedia( m_wikiBackHistory.last(), true );
  3517. }
  3518. void
  3519. ContextBrowser::wikiBackPopupActivated( int id ) //SLOT
  3520. {
  3521. do
  3522. {
  3523. m_wikiForwardHistory += m_wikiBackHistory.last();
  3524. m_wikiBackHistory.pop_back();
  3525. if ( m_wikiForwardHistory.count() > WIKI_MAX_HISTORY )
  3526. m_wikiForwardHistory.pop_front();
  3527. id--;
  3528. } while( id >= 0 );
  3529. m_dirtyWikiPage = true;
  3530. m_wikiCurrentEntry = TQString();
  3531. showWikipedia( m_wikiBackHistory.last(), true );
  3532. }
  3533. void
  3534. ContextBrowser::wikiForwardPopupActivated( int id ) //SLOT
  3535. {
  3536. do
  3537. {
  3538. m_wikiBackHistory += m_wikiForwardHistory.last();
  3539. m_wikiForwardHistory.pop_back();
  3540. if ( m_wikiBackHistory.count() > WIKI_MAX_HISTORY )
  3541. m_wikiBackHistory.pop_front();
  3542. id--;
  3543. } while( id >= 0 );
  3544. m_dirtyWikiPage = true;
  3545. m_wikiCurrentEntry = TQString();
  3546. showWikipedia( m_wikiBackHistory.last(), true );
  3547. }
  3548. void
  3549. ContextBrowser::wikiArtistPage() //SLOT
  3550. {
  3551. m_dirtyWikiPage = true;
  3552. showWikipedia(); // Will fall back to title, if artist is empty(streams!).
  3553. }
  3554. void
  3555. ContextBrowser::wikiAlbumPage() //SLOT
  3556. {
  3557. m_dirtyWikiPage = true;
  3558. showWikipediaEntry( EngineController::instance()->bundle().album() + wikiAlbumPostfix() );
  3559. }
  3560. void
  3561. ContextBrowser::wikiTitlePage() //SLOT
  3562. {
  3563. m_dirtyWikiPage = true;
  3564. showWikipediaEntry( EngineController::instance()->bundle().title() + wikiTrackPostfix() );
  3565. }
  3566. void
  3567. ContextBrowser::wikiExternalPage() //SLOT
  3568. {
  3569. Amarok::invokeBrowser( m_wikiCurrentUrl );
  3570. }
  3571. void
  3572. ContextBrowser::wikiResult( TDEIO::Job* job ) //SLOT
  3573. {
  3574. DEBUG_BLOCK
  3575. if ( !job->error() == 0 )
  3576. {
  3577. m_HTMLSource="";
  3578. m_HTMLSource.append(
  3579. "<div id='wiki_box' class='box'>\n"
  3580. "<div id='wiki_box-header' class='box-header'>\n"
  3581. "<span id='wiki_box-header-title' class='box-header-title'>\n"
  3582. + i18n( "Error" ) +
  3583. "</span>\n"
  3584. "</div>\n"
  3585. "<div id='wiki_box-body' class='box-body'><p>\n"
  3586. + i18n( "Artist information could not be retrieved because the server was not reachable." ) +
  3587. "</p></div>\n"
  3588. "</div>\n"
  3589. );
  3590. m_wikiPage->set( m_HTMLSource );
  3591. m_dirtyWikiPage = false;
  3592. //m_wikiPage = NULL; // FIXME: what for? leads to crashes
  3593. saveHtmlData(); // Send html code to file
  3594. warning() << "[WikiFetcher] TDEIO error! errno: " << job->error() << endl;
  3595. return;
  3596. }
  3597. if ( job != m_wikiJob )
  3598. return; //not the right job, so let's ignore it
  3599. TDEIO::StoredTransferJob* const storedJob = static_cast<TDEIO::StoredTransferJob*>( job );
  3600. m_wiki = TQString( storedJob->data() );
  3601. // Enable the Open in a Brower button, Disabled while loading, guz it would open wikipedia main page.
  3602. m_wikiToolBar->setItemEnabled( WIKI_BROWSER, true );
  3603. // FIXME: Get a safer Regexp here, to match only inside of <head> </head> at least.
  3604. if ( m_wiki.contains( "charset=utf-8", FALSE ) ) {
  3605. m_wiki = TQString::fromUtf8( storedJob->data().data(), storedJob->data().size() );
  3606. }
  3607. if( m_wiki.find( "var wgArticleId = 0" ) != -1 )
  3608. {
  3609. debug() << "Article not found." << endl;
  3610. // article was not found
  3611. if( !wikiArtistPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiArtistPostfix() ) )
  3612. {
  3613. m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiArtistPostfix().length() );
  3614. reloadWikipedia();
  3615. return;
  3616. }
  3617. else if( !wikiAlbumPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiAlbumPostfix() ) )
  3618. {
  3619. m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiAlbumPostfix().length() );
  3620. reloadWikipedia();
  3621. return;
  3622. }
  3623. else if( !wikiTrackPostfix().isEmpty() && m_wikiCurrentEntry.endsWith( wikiTrackPostfix() ) )
  3624. {
  3625. m_wikiCurrentEntry = m_wikiCurrentEntry.left( m_wikiCurrentEntry.length() - wikiTrackPostfix().length() );
  3626. reloadWikipedia();
  3627. return;
  3628. }
  3629. }
  3630. //remove the new-lines and tabs(replace with spaces IS needed).
  3631. m_wiki.replace( "\n", " " );
  3632. m_wiki.replace( "\t", " " );
  3633. m_wikiLanguages = TQString();
  3634. // Get the available language list
  3635. if ( m_wiki.find("<div id=\"p-lang\" class=\"portlet\">") != -1 )
  3636. {
  3637. m_wikiLanguages = m_wiki.mid( m_wiki.find("<div id=\"p-lang\" class=\"portlet\">") );
  3638. m_wikiLanguages = m_wikiLanguages.mid( m_wikiLanguages.find("<ul>") );
  3639. m_wikiLanguages = m_wikiLanguages.mid( 0, m_wikiLanguages.find( "</div>" ) );
  3640. }
  3641. TQString copyright;
  3642. TQString copyrightMark = "<li id=\"f-copyright\">";
  3643. if ( m_wiki.find( copyrightMark ) != -1 )
  3644. {
  3645. copyright = m_wiki.mid( m_wiki.find(copyrightMark) + copyrightMark.length() );
  3646. copyright = copyright.mid( 0, copyright.find( "</li>" ) );
  3647. copyright.replace( "<br />", TQString() );
  3648. //only one br at the beginning
  3649. copyright.prepend( "<br />" );
  3650. }
  3651. // Ok lets remove the top and bottom parts of the page
  3652. m_wiki = m_wiki.mid( m_wiki.find( "<h1 id=\"firstHeading\" class=\"firstHeading\">" ) );
  3653. m_wiki = m_wiki.mid( 0, m_wiki.find( "<div class=\"printfooter\">" ) );
  3654. // Adding back license information
  3655. m_wiki += copyright;
  3656. m_wiki.append( "</div>" );
  3657. m_wiki.replace( TQRegExp("<h3 id=\"siteSub\">[^<]*</h3>"), TQString() );
  3658. m_wiki.replace( TQRegExp( "<span class=\"editsection\"[^>]*>[^<]*<[^>]*>[^<]*<[^>]*>[^<]*</span>" ), TQString() );
  3659. m_wiki.replace( TQRegExp( "<a href=\"[^\"]*\" class=\"new\"[^>]*>([^<]*)</a>" ), "\\1" );
  3660. // Remove anything inside of a class called urlexpansion, as it's pointless for us
  3661. m_wiki.replace( TQRegExp( "<span class= *'urlexpansion'>[^(]*[(][^)]*[)]</span>" ), TQString() );
  3662. // Remove hidden table rows as well
  3663. TQRegExp hidden( "<tr *class= *[\"\']hiddenStructure[\"\']>.*</tr>", false );
  3664. hidden.setMinimal( true ); //greedy behaviour wouldn't be any good!
  3665. m_wiki.replace( hidden, TQString() );
  3666. // we want to keep our own style (we need to modify the stylesheet a bit to handle things nicely)
  3667. m_wiki.replace( TQRegExp( "style= *\"[^\"]*\"" ), TQString() );
  3668. m_wiki.replace( TQRegExp( "class= *\"[^\"]*\"" ), TQString() );
  3669. // let's remove the form elements, we don't want them.
  3670. m_wiki.replace( TQRegExp( "<input[^>]*>" ), TQString() );
  3671. m_wiki.replace( TQRegExp( "<select[^>]*>" ), TQString() );
  3672. m_wiki.replace( "</select>\n" , TQString() );
  3673. m_wiki.replace( TQRegExp( "<option[^>]*>" ), TQString() );
  3674. m_wiki.replace( "</option>\n" , TQString() );
  3675. m_wiki.replace( TQRegExp( "<textarea[^>]*>" ), TQString() );
  3676. m_wiki.replace( "</textarea>" , TQString() );
  3677. //first we convert all the links with protocol to external, as they should all be External Links.
  3678. m_wiki.replace( TQRegExp( "href= *\"http:" ), "href=\"externalurl:" );
  3679. m_wiki.replace( TQRegExp( "href= *\"/" ), "href=\"" +m_wikiBaseUrl );
  3680. m_wiki.replace( TQRegExp( "href= *\"#" ), "href=\"" +m_wikiCurrentUrl + '#' );
  3681. m_HTMLSource = "<html><body>\n";
  3682. m_HTMLSource.append(
  3683. "<div id='wiki_box' class='box'>\n"
  3684. "<div id='wiki_box-header' class='box-header'>\n"
  3685. "<span id='wiki_box-header-title' class='box-header-title'>\n"
  3686. + i18n( "Wikipedia Information" ) +
  3687. "</span>\n"
  3688. "</div>\n"
  3689. "<div id='wiki_box-body' class='box-body'>\n"
  3690. + m_wiki +
  3691. "</div>\n"
  3692. "</div>\n"
  3693. );
  3694. if ( !m_wikiLanguages.isEmpty() )
  3695. {
  3696. m_HTMLSource.append(
  3697. "<div id='wiki_box' class='box'>\n"
  3698. "<div id='wiki_box-header' class='box-header'>\n"
  3699. "<span id='wiki_box-header-title' class='box-header-title'>\n"
  3700. + i18n( "Wikipedia Other Languages" ) +
  3701. "</span>\n"
  3702. "</div>\n"
  3703. "<div id='wiki_box-body' class='box-body'>\n"
  3704. + m_wikiLanguages +
  3705. "</div>\n"
  3706. "</div>\n"
  3707. );
  3708. }
  3709. m_HTMLSource.append( "</body></html>\n" );
  3710. m_wikiPage->set( m_HTMLSource );
  3711. m_dirtyWikiPage = false;
  3712. saveHtmlData(); // Send html code to file
  3713. m_wikiJob = NULL;
  3714. }
  3715. void
  3716. ContextBrowser::coverFetched( const TQString &artist, const TQString &album ) //SLOT
  3717. {
  3718. if ( currentPage() == m_contextTab &&
  3719. !EngineController::engine()->loaded() &&
  3720. !m_browseArtists )
  3721. {
  3722. m_dirtyCurrentTrackPage = true;
  3723. if( m_shownAlbums.contains( album ) )
  3724. showCurrentTrack();
  3725. return;
  3726. }
  3727. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3728. if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
  3729. return;
  3730. if ( currentPage() == m_contextTab &&
  3731. ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty
  3732. {
  3733. m_dirtyCurrentTrackPage = true;
  3734. showCurrentTrack();
  3735. }
  3736. }
  3737. void
  3738. ContextBrowser::coverRemoved( const TQString &artist, const TQString &album ) //SLOT
  3739. {
  3740. if ( currentPage() == m_contextTab &&
  3741. !EngineController::engine()->loaded() &&
  3742. !m_browseArtists )
  3743. {
  3744. m_dirtyCurrentTrackPage = true;
  3745. if( m_shownAlbums.contains( album ) )
  3746. showCurrentTrack();
  3747. return;
  3748. }
  3749. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3750. if ( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() && m_artist.isNull() )
  3751. return;
  3752. if ( currentPage() == m_contextTab &&
  3753. ( currentTrack.artist().string() == artist || m_artist == artist || currentTrack.album().string() == album ) ) // this is for compilations or when artist is empty
  3754. {
  3755. m_dirtyCurrentTrackPage = true;
  3756. showCurrentTrack();
  3757. }
  3758. }
  3759. void
  3760. ContextBrowser::similarArtistsFetched( const TQString &artist ) //SLOT
  3761. {
  3762. if( artist == m_artist || EngineController::instance()->bundle().artist().string() == artist ) {
  3763. m_dirtyCurrentTrackPage = true;
  3764. if ( currentPage() == m_contextTab )
  3765. showCurrentTrack();
  3766. }
  3767. }
  3768. void
  3769. ContextBrowser::imageFetched( const TQString &url ) //SLOT
  3770. {
  3771. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3772. PodcastEpisodeBundle peb;
  3773. if( CollectionDB::instance()->getPodcastEpisodeBundle( currentTrack.url(), &peb ) )
  3774. {
  3775. PodcastChannelBundle pcb;
  3776. if( CollectionDB::instance()->getPodcastChannelBundle( peb.parent(), &pcb ) )
  3777. {
  3778. if( pcb.imageURL().url() == url )
  3779. {
  3780. m_dirtyCurrentTrackPage = true;
  3781. showCurrentTrack();
  3782. }
  3783. }
  3784. }
  3785. }
  3786. void ContextBrowser::ratingOrScoreOrLabelsChanged( const TQString &path ) //SLOT
  3787. {
  3788. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3789. //Always refresh if using ratings, otherwise suggested songs and other songs by artist that
  3790. //have their ratings changed in the playlist won't be reflected until the context browser refreshes
  3791. //which can be confusing, and looks less polished/professional
  3792. //This can be changed if it slows things down too much...
  3793. if( m_browseLabels || ( currentTrack.isFile() && ( currentTrack.url().path() == path || AmarokConfig::useRatings() ) ) ) {
  3794. m_dirtyCurrentTrackPage = true; // will be reloaded when viewed (much faster)
  3795. }
  3796. if( currentPage() == m_contextTab ) {
  3797. refreshCurrentTrackPage();
  3798. }
  3799. }
  3800. void ContextBrowser::tagsChanged( const MetaBundle &bundle ) //SLOT
  3801. {
  3802. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3803. if( !m_shownAlbums.contains( bundle.album() ) && m_artist != bundle.artist() )
  3804. {
  3805. if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
  3806. return;
  3807. if( bundle.artist() != currentTrack.artist() && bundle.album() != currentTrack.album() )
  3808. return;
  3809. }
  3810. refreshCurrentTrackPage();
  3811. }
  3812. void ContextBrowser::tagsChanged( const TQString &oldArtist, const TQString &oldAlbum ) //SLOT
  3813. {
  3814. const MetaBundle &currentTrack = EngineController::instance()->bundle();
  3815. if( !m_shownAlbums.contains( oldAlbum ) && m_artist != oldArtist )
  3816. {
  3817. if( currentTrack.artist().isEmpty() && currentTrack.album().isEmpty() )
  3818. return;
  3819. if( oldArtist != currentTrack.artist() && oldAlbum != currentTrack.album() )
  3820. return;
  3821. }
  3822. refreshCurrentTrackPage();
  3823. }
  3824. void ContextBrowser::refreshCurrentTrackPage() //SLOT
  3825. {
  3826. if ( currentPage() == m_contextTab ) // this is for compilations or when artist is empty
  3827. {
  3828. m_dirtyCurrentTrackPage = true;
  3829. showCurrentTrack();
  3830. }
  3831. }
  3832. bool
  3833. ContextBrowser::hasContextProtocol( const KURL &url )
  3834. {
  3835. TQString protocol = url.protocol();
  3836. return protocol == "album"
  3837. || protocol == "artist"
  3838. || protocol == "stream"
  3839. || protocol == "compilation"
  3840. || protocol == "albumdisc"
  3841. || protocol == "compilationdisc"
  3842. || protocol == "fetchcover";
  3843. }
  3844. KURL::List
  3845. ContextBrowser::expandURL( const KURL &url )
  3846. {
  3847. KURL::List urls;
  3848. TQString protocol = url.protocol();
  3849. if( protocol == "artist" ) {
  3850. uint artist_id = CollectionDB::instance()->artistID( url.path(), false );
  3851. if( artist_id )
  3852. {
  3853. TQStringList trackUrls = CollectionDB::instance()->artistTracks( TQString::number( artist_id ) );
  3854. foreach( trackUrls )
  3855. urls += KURL::fromPathOrURL( *it );
  3856. }
  3857. }
  3858. else if( protocol == "album" ) {
  3859. TQString artist, album, track; // track unused here
  3860. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );
  3861. TQStringList trackUrls = CollectionDB::instance()->albumTracks( artist, album );
  3862. foreach( trackUrls ) {
  3863. urls += KURL::fromPathOrURL( *it );
  3864. }
  3865. }
  3866. else if( protocol == "albumdisc" ) {
  3867. TQString artist, album, discnumber; // discnumber is returned in track number field
  3868. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber );
  3869. TQStringList trackUrls = CollectionDB::instance()->albumDiscTracks( artist, album, discnumber );
  3870. foreach( trackUrls ) {
  3871. urls += KURL::fromPathOrURL( *it );
  3872. }
  3873. }
  3874. else if( protocol == "compilation" ) {
  3875. QueryBuilder qb;
  3876. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  3877. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, url.path() );
  3878. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valDiscNumber );
  3879. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
  3880. qb.setOptions( QueryBuilder::optOnlyCompilations );
  3881. TQStringList values = qb.run();
  3882. for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) {
  3883. urls += KURL::fromPathOrURL( *it );
  3884. }
  3885. }
  3886. else if( protocol == "compilationdisc") {
  3887. TQString artist, album, discnumber; // artist is unused
  3888. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, discnumber );
  3889. QueryBuilder qb;
  3890. qb.addReturnValue( QueryBuilder::tabSong, QueryBuilder::valURL );
  3891. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valAlbumID, album );
  3892. qb.addMatch( QueryBuilder::tabSong, QueryBuilder::valDiscNumber, discnumber );
  3893. qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
  3894. qb.setOptions( QueryBuilder::optOnlyCompilations );
  3895. TQStringList values = qb.run();
  3896. for( TQStringList::ConstIterator it = values.begin(), end = values.end(); it != end; ++it ) {
  3897. urls += KURL::fromPathOrURL( *it );
  3898. }
  3899. }
  3900. else if( protocol == "fetchcover" ) {
  3901. TQString artist, album, track; // track unused here
  3902. Amarok::albumArtistTrackFromUrl( url.path(), artist, album, track );
  3903. TQString artistID = TQString::number( CollectionDB::instance()->artistID( artist ) );
  3904. TQString albumID = TQString::number( CollectionDB::instance()->albumID( album ) );
  3905. TQStringList trackUrls = CollectionDB::instance()->albumTracks( artistID, albumID );
  3906. foreach( trackUrls ) {
  3907. urls += KURL::fromPathOrURL( *it );
  3908. }
  3909. }
  3910. else if( protocol == "stream" ) {
  3911. urls += KURL::fromPathOrURL( url.url().replace( TQRegExp( "^stream:" ), "http:" ) );
  3912. }
  3913. return urls;
  3914. }
  3915. #include "contextbrowser.moc"