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

3251 lines
105 KiB

/***************************************************************************
* copyright : (c) 2004 Pierpaolo Di Panfilo *
* (c) 2004 Mark Kretschmann <markey@web.de> *
* (c) 2005-2006 Seb Ruiz <me@sebruiz.net> *
* (c) 2005 Gábor Lehel <illissius@gmail.com> *
* (c) 2005 Christian Muehlhaeuser <chris@chris.de> *
* (c) 2006 Alexandre Oliveira <aleprj@gmail.com> *
* (c) 2006 Adam Pigg <adam@piggz.co.uk> *
* See COPYING file for licensing information *
***************************************************************************/
#define DEBUG_PREFIX "PlaylistBrowser"
#include "amarok.h" //actionCollection()
#include "browserToolBar.h"
#include "collectiondb.h" //smart playlists
#include "debug.h"
#include "htmlview.h"
#include "k3bexporter.h"
#include "mediabrowser.h"
#include "dynamicmode.h"
#include "lastfm.h"
#include "playlist.h"
#include "playlistbrowser.h"
#include "playlistbrowseritem.h"
#include "playlistselection.h"
#include "podcastbundle.h"
#include "podcastsettings.h"
#include "scancontroller.h"
#include "smartplaylisteditor.h"
#include "tagdialog.h" //showContextMenu()
#include "threadmanager.h"
#include "statusbar.h"
#include "contextbrowser.h"
#include "xspfplaylist.h"
#include <tqevent.h> //customEvent()
#include <tqheader.h> //mousePressed()
#include <tqlabel.h>
#include <tqpainter.h> //paintCell()
#include <tqpixmap.h> //paintCell()
#include <tqtextstream.h> //loadPlaylists(), saveM3U(), savePLS()
#include <kaction.h>
#include <kactionclasses.h>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kfiledialog.h> //openPlaylist()
#include <kio/job.h> //deleteSelectedPlaylists()
#include <kiconloader.h> //smallIcon
#include <kinputdialog.h>
#include <klineedit.h> //rename()
#include <klocale.h>
#include <kmessagebox.h> //renamePlaylist(), deleteSelectedPlaylist()
#include <kmimetype.h>
#include <kmultipledrag.h> //dragObject()
#include <kpopupmenu.h>
#include <kpushbutton.h>
#include <kstandarddirs.h> //KGlobal::dirs()
#include <kurldrag.h> //dragObject()
#include <cstdio> //rename() in renamePlaylist()
namespace Amarok {
TQListViewItem*
findItemByPath( TQListView *view, TQString name )
{
const static TQString escaped( "\\/" );
const static TQChar sep( '/' );
debug() << "Searching " << name << endl;
TQStringList path = splitPath( name );
TQListViewItem *prox = view->firstChild();
TQListViewItem *item = 0;
foreach( path ) {
item = prox;
TQString text( *it );
text.replace( escaped, sep );
for ( ; item; item = item->nextSibling() ) {
if ( text == item->text(0) ) {
break;
}
}
if ( !item )
return 0;
prox = item->firstChild();
}
return item;
}
TQStringList
splitPath( TQString path ) {
TQStringList list;
const static TQChar sep( '/' );
int bOffset = 0, sOffset = 0;
int pos = path.find( sep, bOffset );
while ( pos != -1 ) {
if ( pos > sOffset && pos <= (int)path.length() ) {
if ( pos > 0 && path[pos-1] != '\\' ) {
list << path.mid( sOffset, pos - sOffset );
sOffset = pos + 1;
}
}
bOffset = pos + 1;
pos = path.find( sep, bOffset );
}
int length = path.length() - 1;
if ( path.mid( sOffset, length - sOffset + 1 ).length() > 0 )
list << path.mid( sOffset, length - sOffset + 1 );
return list;
}
}
inline TQString
fileExtension( const TQString &fileName )
{
return Amarok::extension( fileName );
}
PlaylistBrowser *PlaylistBrowser::s_instance = 0;
PlaylistBrowser::PlaylistBrowser( const char *name )
: TQVBox( 0, name )
, m_polished( false )
, m_playlistCategory( 0 )
, m_streamsCategory( 0 )
, m_smartCategory( 0 )
, m_dynamicCategory( 0 )
, m_podcastCategory( 0 )
, m_coolStreams( 0 )
, m_smartDefaults( 0 )
, m_lastfmCategory( 0 )
, m_shoutcastCategory( 0 )
, m_lastPlaylist( 0 )
, m_coolStreamsOpen( false )
, m_smartDefaultsOpen( false )
, m_lastfmOpen( false )
, m_ac( new KActionCollection( this ) )
, m_podcastTimer( new TQTimer( this ) )
{
s_instance = this;
TQVBox *browserBox = new TQVBox( this );
browserBox->setSpacing( 3 );
//<Toolbar>
addMenuButton = new KActionMenu( i18n("Add"), Amarok::icon( "add_playlist" ), m_ac );
addMenuButton->setDelayed( false );
KPopupMenu *playlistMenu = new KPopupMenu( this );
playlistMenu->insertItem( i18n("New..."), PLAYLIST );
playlistMenu->insertItem( i18n("Import Existing..."), PLAYLIST_IMPORT );
connect( playlistMenu, TQT_SIGNAL( activated(int) ), TQT_SLOT( slotAddPlaylistMenu(int) ) );
KPopupMenu *addMenu = addMenuButton->popupMenu();
addMenu->insertItem( i18n("Playlist"), playlistMenu );
addMenu->insertItem( i18n("Smart Playlist..."), SMARTPLAYLIST );
addMenu->insertItem( i18n("Dynamic Playlist..."), ADDDYNAMIC);
addMenu->insertItem( i18n("Radio Stream..."), STREAM );
addMenu->insertItem( i18n("Podcast..."), PODCAST );
connect( addMenu, TQT_SIGNAL( activated(int) ), TQT_SLOT( slotAddMenu(int) ) );
renameButton = new KAction( i18n("Rename"), "editclear", 0, TQT_TQOBJECT(this), TQT_SLOT( renameSelectedItem() ), m_ac );
removeButton = new KAction( i18n("Delete"), Amarok::icon( "remove" ), 0, TQT_TQOBJECT(this), TQT_SLOT( removeSelectedItems() ), m_ac );
m_toolbar = new Browser::ToolBar( browserBox );
m_toolbar->setIconText( KToolBar::IconTextRight, false ); //we want the open button to have text on right
addMenuButton->plug( m_toolbar );
m_toolbar->setIconText( KToolBar::IconOnly, false ); //default appearance
m_toolbar->insertLineSeparator();
renameButton->plug( m_toolbar);
removeButton->plug( m_toolbar );
renameButton->setEnabled( false );
removeButton->setEnabled( false );
//</Toolbar>
m_splitter = new TQSplitter( Qt::Vertical, browserBox );
m_splitter->setChildrenCollapsible( false ); // hiding the InfoPane entirely can only be confusing
m_listview = new PlaylistBrowserView( m_splitter );
int sort = Amarok::config( "PlaylistBrowser" )->readNumEntry( "Sorting", TQt::Ascending );
m_listview->setSorting( 0, sort == TQt::Ascending ? true : false );
m_podcastTimerInterval = Amarok::config( "PlaylistBrowser" )->readNumEntry( "Podcast Interval", 14400000 );
connect( m_podcastTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(scanPodcasts()) );
// signals and slots connections
connect( m_listview, TQT_SIGNAL( contextMenuRequested( TQListViewItem *, const TQPoint &, int ) ),
this, TQT_SLOT( showContextMenu( TQListViewItem *, const TQPoint &, int ) ) );
connect( m_listview, TQT_SIGNAL( doubleClicked( TQListViewItem *, const TQPoint &, int ) ),
this, TQT_SLOT( invokeItem( TQListViewItem *, const TQPoint &, int ) ) );
connect( m_listview, TQT_SIGNAL( itemRenamed( TQListViewItem*, const TQString&, int ) ),
this, TQT_SLOT( renamePlaylist( TQListViewItem*, const TQString&, int ) ) );
connect( m_listview, TQT_SIGNAL( currentChanged( TQListViewItem * ) ),
this, TQT_SLOT( currentItemChanged( TQListViewItem * ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( scanDone( bool ) ), TQT_SLOT( collectionScanDone() ) );
setMinimumWidth( m_toolbar->sizeHint().width() );
m_infoPane = new InfoPane( m_splitter );
m_podcastCategory = loadPodcasts();
setSpacing( 4 );
setFocusProxy( m_listview );
}
void
PlaylistBrowser::polish()
{
// we make startup faster by doing the slow bits for this
// only when we are shown on screen
DEBUG_FUNC_INFO
Amarok::OverrideCursor cursor;
// blockSignals( true );
// BrowserBar::instance()->restoreWidth();
// blockSignals( false );
TQVBox::polish();
/// Podcasting is always initialised in the ctor because of autoscanning
m_polished = true;
m_playlistCategory = loadPlaylists();
if( !CollectionDB::instance()->isEmpty() )
{
m_smartCategory = loadSmartPlaylists();
loadDefaultSmartPlaylists();
}
#define config Amarok::config( "PlaylistBrowser" )
m_dynamicCategory = loadDynamics();
m_randomDynamic = new DynamicEntry( m_dynamicCategory, 0, i18n("Random Mix") );
m_randomDynamic->setKept( false ); //don't save it
m_randomDynamic->setCycleTracks( config->readBoolEntry( "Dynamic Random Remove Played", true ) );
m_randomDynamic->setUpcomingCount( config->readNumEntry ( "Dynamic Random Upcoming Count", 15 ) );
m_randomDynamic->setPreviousCount( config->readNumEntry ( "Dynamic Random Previous Count", 5 ) );
m_suggestedDynamic = new DynamicEntry( m_dynamicCategory, m_randomDynamic, i18n("Suggested Songs" ) );
m_suggestedDynamic->setKept( false ); //don't save it
m_suggestedDynamic->setAppendType( DynamicMode::SUGGESTION );
m_suggestedDynamic->setCycleTracks( config->readBoolEntry( "Dynamic Suggest Remove Played", true ) );
m_suggestedDynamic->setUpcomingCount( config->readNumEntry ( "Dynamic Suggest Upcoming Count", 15 ) );
m_suggestedDynamic->setPreviousCount( config->readNumEntry ( "Dynamic Suggest Previous Count", 5 ) );
#undef config
m_streamsCategory = loadStreams();
loadCoolStreams();
m_shoutcastCategory = new ShoutcastBrowser( m_streamsCategory );
if( !AmarokConfig::scrobblerUsername().isEmpty() )
{
const bool subscriber = Amarok::config( "Scrobbler" )->readBoolEntry( "Subscriber", false );
loadLastfmStreams( subscriber );
}
markDynamicEntries();
// ListView item state restoration:
// First we check if the number of items in the listview is the same as it was on last
// application exit. If true, we iterate over all items and restore their open/closed state.
// Note: We ignore podcast items, because they are added dynamically added to the ListView.
TQValueList<int> stateList = Amarok::config( "PlaylistBrowser" )->readIntListEntry( "Item State" );
TQListViewItemIterator it( m_listview );
uint count = 0;
while ( it.current() ) {
if( !isPodcastEpisode( it.current() ) )
++count;
++it;
}
if ( count == stateList.count() ) {
uint index = 0;
it = TQListViewItemIterator( m_listview );
while ( it.current() ) {
if( !isPodcastEpisode( it.current() ) ) {
it.current()->setOpen( stateList[index] );
++index;
}
++it;
}
}
// Set height of InfoPane
m_infoPane->setStoredHeight( Amarok::config( "PlaylistBrowser" )->readNumEntry( "InfoPane Height", 200 ) );
}
PlaylistBrowser::~PlaylistBrowser()
{
DEBUG_BLOCK
s_instance = 0;
if( m_polished )
{
savePlaylists();
saveSmartPlaylists();
saveDynamics();
saveStreams();
saveLastFm();
savePodcastFolderStates( m_podcastCategory );
TQStringList list;
for( uint i=0; i < m_dynamicEntries.count(); i++ )
{
TQListViewItem *item = m_dynamicEntries.at( i );
list.append( item->text(0) );
}
Amarok::config( "PlaylistBrowser" )->writeEntry( "Sorting", m_listview->sortOrder() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Podcast Interval", m_podcastTimerInterval );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Podcast Folder Open", m_podcastCategory->isOpen() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "InfoPane Height", m_infoPane->getHeight() );
}
}
void
PlaylistBrowser::setInfo( const TQString &title, const TQString &info )
{
m_infoPane->setInfo( title, info );
}
void
PlaylistBrowser::resizeEvent( TQResizeEvent * )
{
if( TQT_TQWIDGET( m_infoPane->child( "container" ) )->isShown() )
m_infoPane->setMaximumHeight( ( int )( m_splitter->height() / 1.5 ) );
}
void
PlaylistBrowser::markDynamicEntries()
{
if( Amarok::dynamicMode() )
{
TQStringList playlists = Amarok::dynamicMode()->items();
for( uint i=0; i < playlists.count(); i++ )
{
PlaylistBrowserEntry *item = dynamic_cast<PlaylistBrowserEntry*>( Amarok::findItemByPath( m_listview, playlists[i] ) );
if( item )
{
m_dynamicEntries.append( item );
if( item->rtti() == PlaylistEntry::RTTI )
static_cast<PlaylistEntry*>( item )->setDynamic( true );
if( item->rtti() == SmartPlaylist::RTTI )
static_cast<SmartPlaylist*>( item )->setDynamic( true );
}
}
}
}
/**
*************************************************************************
* STREAMS
*************************************************************************
**/
TQString PlaylistBrowser::streamBrowserCache() const
{
return Amarok::saveLocation() + "streambrowser_save.xml";
}
PlaylistCategory* PlaylistBrowser::loadStreams()
{
TQFile file( streamBrowserCache() );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
TQDomElement e;
TQListViewItem *after = m_dynamicCategory;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create an empty element*/
return new PlaylistCategory( m_listview, after , i18n("Radio Streams") );
}
else {
e = d.namedItem( "category" ).toElement();
if ( e.attribute("formatversion") =="1.1" ) {
PlaylistCategory* p = new PlaylistCategory( m_listview, after, e );
p->setText(0, i18n("Radio Streams") );
return p;
}
else { // Old unversioned format
PlaylistCategory* p = new PlaylistCategory( m_listview, after, i18n("Radio Streams") );
TQListViewItem *last = 0;
TQDomNode n = d.namedItem( "streambrowser" ).namedItem("stream");
for( ; !n.isNull(); n = n.nextSibling() ) {
last = new StreamEntry( p, last, n.toElement() );
}
return p;
}
}
}
void PlaylistBrowser::loadCoolStreams()
{
TQFile file( locate( "data","amarok/data/Cool-Streams.xml" ) );
if( !file.open( IO_ReadOnly ) )
return;
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
if( !d.setContent( stream.read() ) )
{
error() << "Bad Cool Streams XML file" << endl;
return;
}
m_coolStreams = new PlaylistCategory( m_streamsCategory, 0, i18n("Cool-Streams") );
m_coolStreams->setOpen( m_coolStreamsOpen );
m_coolStreams->setKept( false );
StreamEntry *last = 0;
TQDomNode n = d.namedItem( "coolstreams" ).firstChild();
for( ; !n.isNull(); n = n.nextSibling() )
{
TQDomElement e = n.toElement();
TQString name = e.attribute( "name" );
e = n.namedItem( "url" ).toElement();
KURL url( e.text() );
last = new StreamEntry( m_coolStreams, last, url, name );
last->setKept( false );
}
}
void PlaylistBrowser::addStream( TQListViewItem *parent )
{
StreamEditor dialog( this, i18n( "Radio Stream" ), TQString() );
dialog.setCaption( i18n( "Add Radio Stream" ) );
if( !parent ) parent = static_cast<TQListViewItem*>(m_streamsCategory);
if( dialog.exec() == TQDialog::Accepted )
{
new StreamEntry( parent, 0, dialog.url(), dialog.name() );
parent->sortChildItems( 0, true );
parent->setOpen( true );
saveStreams();
}
}
void PlaylistBrowser::editStreamURL( StreamEntry *item, const bool readonly )
{
StreamEditor dialog( this, item->title(), item->url().prettyURL(), readonly );
dialog.setCaption( readonly ? i18n( "Radio Stream" ) : i18n( "Edit Radio Stream" ) );
if( dialog.exec() == TQDialog::Accepted )
{
item->setTitle( dialog.name() );
item->setURL( dialog.url() );
item->setText(0, dialog.name() );
}
}
void PlaylistBrowser::saveStreams()
{
TQFile file( streamBrowserCache() );
TQDomDocument doc;
TQDomElement streamB = m_streamsCategory->xml();
streamB.setAttribute( "product", "Amarok" );
streamB.setAttribute( "version", APP_VERSION );
streamB.setAttribute( "formatversion", "1.1" );
TQDomNode streamsNode = doc.importNode( streamB, true );
doc.appendChild( streamsNode );
TQString temp( doc.toString() );
// Only open the file after all data is ready. If it crashes, data is not lost!
if ( !file.open( IO_WriteOnly ) ) return;
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
stream << temp;
}
/**
*************************************************************************
* LAST.FM
*************************************************************************
**/
void PlaylistBrowser::loadLastfmStreams( const bool subscriber /*false*/ )
{
TQFile file( Amarok::saveLocation() + "lastfmbrowser_save.xml" );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
TQDomElement e;
TQListViewItem *after = m_streamsCategory;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create an empty element*/
m_lastfmCategory = new PlaylistCategory( m_listview, after , i18n("Last.fm Radio") );
}
else {
e = d.namedItem( "category" ).toElement();
m_lastfmCategory = new PlaylistCategory( m_listview, after, e );
m_lastfmCategory->setText( 0, i18n("Last.fm Radio") );
}
/// Load the default items
TQStringList globaltags;
globaltags << "Alternative" << "Ambient" << "Chill Out" << "Classical" << "Dance"
<< "Electronica" << "Favorites" << "Heavy Metal" << "Hip Hop" << "Indie Rock"
<< "Industrial" << "Japanese" << "Pop" << "Psytrance" << "Rap" << "Rock"
<< "Soundtrack" << "Techno" << "Trance";
PlaylistCategory *tagsFolder = new PlaylistCategory( m_lastfmCategory, 0, i18n("Global Tags") );
tagsFolder->setKept( false );
LastFmEntry *last = 0;
foreach( globaltags )
{
const KURL url( "lastfm://globaltags/" + *it );
last = new LastFmEntry( tagsFolder, last, url, *it );
last->setKept( false );
}
TQString user = AmarokConfig::scrobblerUsername();
KURL url( TQString("lastfm://user/%1/neighbours").arg( user ) );
last = new LastFmEntry( m_lastfmCategory, tagsFolder, url, i18n( "Neighbor Radio" ) );
last->setKept( false );
url = KURL::fromPathOrURL( TQString("lastfm://user/%1/recommended/100").arg( user ) );
last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Recommended Radio" ) );
last->setKept( false );
if( subscriber )
{
url = KURL::fromPathOrURL( TQString("lastfm://user/%1/personal").arg( user ) );
last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Personal Radio" ) );
last->setKept( false );
url = KURL::fromPathOrURL( TQString("lastfm://user/%1/loved").arg( user ) );
last = new LastFmEntry( m_lastfmCategory, last, url, i18n( "Loved Radio" ) );
last->setKept( false );
}
}
void PlaylistBrowser::addLastFmRadio( TQListViewItem *parent )
{
StreamEditor dialog( this, i18n( "Last.fm Radio" ), TQString() );
dialog.setCaption( i18n( "Add Last.fm Radio" ) );
if( !parent ) parent = static_cast<TQListViewItem*>(m_lastfmCategory);
if( dialog.exec() == TQDialog::Accepted )
{
new LastFmEntry( parent, 0, dialog.url(), dialog.name() );
parent->sortChildItems( 0, true );
parent->setOpen( true );
saveLastFm();
}
}
void PlaylistBrowser::addLastFmCustomRadio( TQListViewItem *parent )
{
TQString token = LastFm::Controller::createCustomStation();
if( token.isEmpty() ) return;
token.replace( "/", "%252" );
const TQString text = "lastfm://artistnames/" + token;
const KURL url( text );
TQString name = LastFm::Controller::stationDescription( text );
name.replace( "%252", "/" );
new LastFmEntry( parent, 0, url, name );
saveLastFm();
}
void PlaylistBrowser::saveLastFm()
{
if ( !m_lastfmCategory )
return;
TQFile file( Amarok::saveLocation() + "lastfmbrowser_save.xml" );
TQDomDocument doc;
TQDomElement lastfmB = m_lastfmCategory->xml();
lastfmB.setAttribute( "product", "Amarok" );
lastfmB.setAttribute( "version", APP_VERSION );
lastfmB.setAttribute( "formatversion", "1.1" );
TQDomNode lastfmNode = doc.importNode( lastfmB, true );
doc.appendChild( lastfmNode );
TQString temp( doc.toString() );
// Only open the file after all data is ready. If it crashes, data is not lost!
if ( !file.open( IO_WriteOnly ) ) return;
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
stream << temp;
}
/**
*************************************************************************
* SMART-PLAYLISTS
*************************************************************************
**/
TQString PlaylistBrowser::smartplaylistBrowserCache() const
{
return Amarok::saveLocation() + "smartplaylistbrowser_save.xml";
}
void PlaylistBrowser::addSmartPlaylist( TQListViewItem *parent ) //SLOT
{
if( CollectionDB::instance()->isEmpty() || !m_smartCategory )
return;
if( !parent ) parent = static_cast<TQListViewItem*>(m_smartCategory);
SmartPlaylistEditor dialog( i18n("Untitled"), this );
if( dialog.exec() == TQDialog::Accepted ) {
PlaylistCategory *category = dynamic_cast<PlaylistCategory*>(parent);
for( TQListViewItem *item = category->firstChild(); item; item = item->nextSibling() ) {
SmartPlaylist *sp = dynamic_cast<SmartPlaylist*>(item);
if ( sp && sp->title() == dialog.name() ) {
if( KMessageBox::warningContinueCancel(
PlaylistWindow::self(),
i18n( "A Smart Playlist named \"%1\" already exists. Do you want to overwrite it?" ).arg( dialog.name() ),
i18n( "Overwrite Playlist?" ), i18n( "Overwrite" ) ) == KMessageBox::Continue )
{
delete item;
break;
}
else
return;
}
}
new SmartPlaylist( parent, 0, dialog.result() );
parent->sortChildItems( 0, true );
parent->setOpen( true );
saveSmartPlaylists();
}
}
PlaylistCategory* PlaylistBrowser::loadSmartPlaylists()
{
TQFile file( smartplaylistBrowserCache() );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQListViewItem *after = m_playlistCategory;
TQDomDocument d;
TQDomElement e;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create an empty element*/
return new PlaylistCategory(m_listview, after, i18n("Smart Playlists") );
}
else {
e = d.namedItem( "category" ).toElement();
TQString version = e.attribute("formatversion");
float fversion = e.attribute("formatversion").toFloat();
if ( version == "1.8" )
{
PlaylistCategory* p = new PlaylistCategory(m_listview, after, e );
p->setText( 0, i18n("Smart Playlists") );
return p;
}
else if ( fversion > 1.0f )
{
PlaylistCategory* p = new PlaylistCategory(m_listview, after, e );
p->setText( 0, i18n("Smart Playlists") );
debug() << "loading old format smart playlists, converted to new format" << endl;
updateSmartPlaylists( p );
saveSmartPlaylists( p );
return p;
}
else { // Old unversioned format
PlaylistCategory* p = new PlaylistCategory(m_listview, after , i18n("Smart Playlists") );
TQListViewItem *last = 0;
TQDomNode n = d.namedItem( "smartplaylists" ).namedItem("smartplaylist");
for( ; !n.isNull(); n = n.nextSibling() ) {
last = new SmartPlaylist( p, last, n.toElement() );
}
return p;
}
}
}
void PlaylistBrowser::updateSmartPlaylists( TQListViewItem *p )
{
if( !p )
return;
for( TQListViewItem *it = p->firstChild();
it;
it = it->nextSibling() )
{
SmartPlaylist *spl = dynamic_cast<SmartPlaylist *>( it );
if( spl )
{
TQDomElement xml = spl->xml();
TQDomElement query = xml.namedItem( "sqlquery" ).toElement();
TQDomElement expandBy = xml.namedItem( "expandby" ).toElement();
updateSmartPlaylistElement( query );
updateSmartPlaylistElement( expandBy );
spl->setXml( xml );
}
else
updateSmartPlaylists( it );
}
}
void PlaylistBrowser::updateSmartPlaylistElement( TQDomElement& query )
{
TQRegExp limitSearch( "LIMIT.*(\\d+)\\s*,\\s*(\\d+)" );
TQRegExp selectFromSearch( "SELECT[^'\"]*FROM" );
for(TQDomNode child = query.firstChild();
!child.isNull();
child = child.nextSibling() )
{
if( child.isText() )
{
//HACK this should be refactored to just regenerate the SQL from the <criteria>'s
TQDomText text = child.toText();
TQString sql = text.data();
if ( selectFromSearch.search( sql ) != -1 )
sql.replace( selectFromSearch, "SELECT (*ListOfFields*) FROM" );
if ( limitSearch.search( sql ) != -1 )
sql.replace( limitSearch,
TQString( "LIMIT %1 OFFSET %2").arg( limitSearch.capturedTexts()[2].toInt() ).arg( limitSearch.capturedTexts()[1].toInt() ) );
text.setData( sql );
break;
}
}
}
void PlaylistBrowser::loadDefaultSmartPlaylists()
{
DEBUG_BLOCK
const TQStringList genres = CollectionDB::instance()->query( "SELECT DISTINCT name FROM genre;" );
const TQStringList artists = CollectionDB::instance()->artistList();
SmartPlaylist *item;
QueryBuilder qb;
SmartPlaylist *last = 0;
m_smartDefaults = new PlaylistCategory( m_smartCategory, 0, i18n("Collection") );
m_smartDefaults->setOpen( m_smartDefaultsOpen );
m_smartDefaults->setKept( false );
/********** All Collection **************/
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
item = new SmartPlaylist( m_smartDefaults, 0, i18n( "All Collection" ), qb.query() );
item->setPixmap( 0, SmallIcon( Amarok::icon( "collection" ) ) );
item->setKept( false );
/********** Favorite Tracks **************/
qb.initSQLDrag();
qb.sortByFavorite();
qb.setLimit( 0, 15 );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Favorite Tracks" ), qb.query() );
item->setKept( false );
last = 0;
qb.initSQLDrag();
qb.sortByFavorite();
qb.setLimit( 0, 15 );
foreach( artists ) {
QueryBuilder qbTemp( qb );
qbTemp.addMatch( QueryBuilder::tabArtist, *it );
last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query() );
last->setKept( false );
}
/********** Most Played **************/
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, true );
qb.setLimit( 0, 15 );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Most Played" ), qb.query() );
item->setKept( false );
last = 0;
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, true );
qb.setLimit( 0, 15 );
foreach( artists ) {
QueryBuilder qbTemp( qb );
qbTemp.addMatch( QueryBuilder::tabArtist, *it );
last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query() );
last->setKept( false );
}
/********** Newest Tracks **************/
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valCreateDate, true );
qb.setLimit( 0, 15 );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Newest Tracks" ), qb.query() );
item->setKept( false );
last = 0;
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valCreateDate, true );
qb.setLimit( 0, 15 );
foreach( artists ) {
QueryBuilder qbTemp( qb );
qbTemp.addMatch( QueryBuilder::tabArtist, *it );
last = new SmartPlaylist( item, last, i18n( "By %1" ).arg( *it ), qbTemp.query( true ) );
last->setKept( false );
}
/********** Last Played **************/
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valAccessDate, true );
qb.setLimit( 0, 15 );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Last Played" ), qb.query( true ) );
item->setKept( false );
/********** Never Played **************/
qb.initSQLDrag();
qb.addNumericFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "0" );
qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Never Played" ), qb.query( true ) );
item->setKept( false );
/********** Ever Played **************/
qb.initSQLDrag();
qb.excludeFilter( QueryBuilder::tabStats, QueryBuilder::valPlayCounter, "1", QueryBuilder::modeLess );
qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
qb.sortBy( QueryBuilder::tabStats, QueryBuilder::valScore );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Ever Played" ), qb.query( true ) );
item->setKept( false );
/********** Genres **************/
item = new SmartPlaylist( m_smartDefaults, item, i18n( "Genres" ), TQString() );
item->setKept( false );
last = 0;
qb.initSQLDrag();
qb.sortBy( QueryBuilder::tabArtist, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabAlbum, QueryBuilder::valName );
qb.sortBy( QueryBuilder::tabSong, QueryBuilder::valTrack );
foreach( genres ) {
QueryBuilder qbTemp( qb );
qbTemp.addMatch( QueryBuilder::tabGenre, *it );
last = new SmartPlaylist( item, last, i18n( "%1" ).arg( *it ), qbTemp.query( true ) );
last->setKept( false );
}
/********** 50 Random Tracks **************/
qb.initSQLDrag();
qb.setOptions( QueryBuilder::optRandomize );
qb.setLimit( 0, 50 );
item = new SmartPlaylist( m_smartDefaults, item, i18n( "50 Random Tracks" ), qb.query( true ) );
item->setKept( false );
}
void PlaylistBrowser::editSmartPlaylist( SmartPlaylist* item )
{
SmartPlaylistEditor dialog( this, item->xml() );
if( dialog.exec() == TQDialog::Accepted )
{
item->setXml ( dialog.result() );
item->setText( 0, dialog.name() );
if( item->isDynamic() ) // rebuild the cache if the smart playlist has changed
Playlist::instance()->rebuildDynamicModeCache();
}
}
void PlaylistBrowser::saveSmartPlaylists( PlaylistCategory *smartCategory )
{
TQFile file( smartplaylistBrowserCache() );
if( !smartCategory )
smartCategory = m_smartCategory;
// If the user hadn't set a collection, we didn't create the Smart Playlist Item
if( !smartCategory ) return;
TQDomDocument doc;
TQDomElement smartB = smartCategory->xml();
smartB.setAttribute( "product", "Amarok" );
smartB.setAttribute( "version", APP_VERSION );
smartB.setAttribute( "formatversion", "1.8" );
TQDomNode smartplaylistsNode = doc.importNode( smartB, true );
doc.appendChild( smartplaylistsNode );
TQString temp( doc.toString() );
// Only open the file after all data is ready. If it crashes, data is not lost!
if ( !file.open( IO_WriteOnly ) ) return;
TQTextStream smart( &file );
smart.setEncoding( TQTextStream::UnicodeUTF8 );
smart << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
smart << temp;
}
/**
*************************************************************************
* PARTIES
*************************************************************************
**/
TQString PlaylistBrowser::dynamicBrowserCache() const
{
return Amarok::saveLocation() + "dynamicbrowser_save.xml";
}
PlaylistCategory* PlaylistBrowser::loadDynamics()
{
TQFile file( dynamicBrowserCache() );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
TQDomElement e;
PlaylistCategory *after = m_smartCategory;
if( CollectionDB::instance()->isEmpty() || !m_smartCategory ) // incase of no collection
after = m_playlistCategory;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create some defaults*/
PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Dynamic Playlists") );
return p;
}
else {
e = d.namedItem( "category" ).toElement();
TQString version = e.attribute("formatversion");
if ( version == "1.2" ) {
PlaylistCategory* p = new PlaylistCategory( m_listview, after, e );
p->setText( 0, i18n("Dynamic Playlists") );
return p;
}
else if ( version == "1.1" ) {
// In 1.1, playlists would be referred only by its name.
// TODO: We can *try* to convert by using findItem
PlaylistCategory* p = new PlaylistCategory( m_listview, after, e );
p->setText( 0, i18n("Dynamic Playlists") );
fixDynamicPlaylistPath( p );
return p;
}
else { // Old unversioned format
PlaylistCategory* p = new PlaylistCategory( m_listview, after, i18n("Dynamic Playlists") );
TQListViewItem *last = 0;
TQDomNode n = d.namedItem( "dynamicbrowser" ).namedItem("dynamic");
for( ; !n.isNull(); n = n.nextSibling() ) {
last = new DynamicEntry( p, last, n.toElement() );
}
return p;
}
}
}
void
PlaylistBrowser::fixDynamicPlaylistPath( TQListViewItem *item )
{
DynamicEntry *entry = dynamic_cast<DynamicEntry*>( item );
if ( entry ) {
TQStringList names = entry->items();
TQStringList paths;
foreach( names ) {
TQString path = guessPathFromPlaylistName( *it );
if ( !path.isNull() )
paths+=path;
}
entry->setItems( paths );
}
PlaylistCategory *cat = dynamic_cast<PlaylistCategory*>( item );
if ( cat ) {
TQListViewItem *it = cat->firstChild();
for( ; it; it = it->nextSibling() ) {
fixDynamicPlaylistPath( it );
}
}
}
TQString
PlaylistBrowser::guessPathFromPlaylistName( TQString name )
{
TQListViewItem *item = m_listview->findItem( name, 0, TQt::ExactMatch );
PlaylistBrowserEntry *entry = dynamic_cast<PlaylistBrowserEntry*>( item );
if ( entry )
return entry->name();
return TQString();
}
void PlaylistBrowser::saveDynamics()
{
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Remove Played", m_randomDynamic->cycleTracks() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Upcoming Count", m_randomDynamic->upcomingCount() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Random Previous Count", m_randomDynamic->previousCount() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Remove Played", m_suggestedDynamic->cycleTracks() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Upcoming Count", m_suggestedDynamic->upcomingCount() );
Amarok::config( "PlaylistBrowser" )->writeEntry( "Dynamic Suggest Previous Count", m_suggestedDynamic->previousCount() );
TQFile file( dynamicBrowserCache() );
TQTextStream stream( &file );
TQDomDocument doc;
TQDomElement dynamicB = m_dynamicCategory->xml();
dynamicB.setAttribute( "product", "Amarok" );
dynamicB.setAttribute( "version", APP_VERSION );
dynamicB.setAttribute( "formatversion", "1.2" );
TQDomNode dynamicsNode = doc.importNode( dynamicB, true );
doc.appendChild( dynamicsNode );
TQString temp( doc.toString() );
// Only open the file after all data is ready. If it crashes, data is not lost!
if ( !file.open( IO_WriteOnly ) ) return;
stream.setEncoding( TQTextStream::UnicodeUTF8 );
stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
stream << temp;
}
void PlaylistBrowser::loadDynamicItems()
{
// Make sure all items are unmarked
for( uint i=0; i < m_dynamicEntries.count(); i++ )
{
TQListViewItem *it = m_dynamicEntries.at( i );
if( it )
static_cast<PlaylistBrowserEntry*>(it)->setDynamic( false );
}
m_dynamicEntries.clear(); // Don't use remove(), since we do i++, which would cause skip overs!!!
// Mark appropriate items as used
markDynamicEntries();
}
/**
*************************************************************************
* PODCASTS
*************************************************************************
**/
TQString PlaylistBrowser::podcastBrowserCache() const
{
//returns the playlists stats cache file
return Amarok::saveLocation() + "podcastbrowser_save.xml";
}
PlaylistCategory* PlaylistBrowser::loadPodcasts()
{
DEBUG_BLOCK
TQFile file( podcastBrowserCache() );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
TQDomElement e;
TQListViewItem *after = 0;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create an empty element*/
PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Podcasts") );
p->setId( 0 );
loadPodcastsFromDatabase( p );
return p;
}
else {
e = d.namedItem( "category" ).toElement();
if ( e.attribute("formatversion") == "1.1" ) {
debug() << "Podcasts are being moved to the database..." << endl;
m_podcastItemsToScan.clear();
PlaylistCategory *p = new PlaylistCategory( m_listview, after, e );
p->setId( 0 );
//delete the file, it is deprecated
KIO::del( KURL::fromPathOrURL( podcastBrowserCache() ) );
if( !m_podcastItemsToScan.isEmpty() )
m_podcastTimer->start( m_podcastTimerInterval );
return p;
}
}
PlaylistCategory *p = new PlaylistCategory( m_listview, after, i18n("Podcasts") );
p->setId( 0 );
return p;
}
void PlaylistBrowser::loadPodcastsFromDatabase( PlaylistCategory *p )
{
DEBUG_BLOCK
if( !p ) p = m_podcastCategory;
m_podcastItemsToScan.clear();
while( p->firstChild() )
delete p->firstChild();
TQMap<int,PlaylistCategory*> folderMap = loadPodcastFolders( p );
TQValueList<PodcastChannelBundle> channels;
channels = CollectionDB::instance()->getPodcastChannels();
PodcastChannel *channel = 0;
foreachType( TQValueList<PodcastChannelBundle>, channels )
{
PlaylistCategory *parent = p;
const int parentId = (*it).parentId();
if( parentId > 0 && folderMap.find( parentId ) != folderMap.end() )
parent = folderMap[parentId];
channel = new PodcastChannel( parent, channel, *it );
bool hasNew = CollectionDB::instance()->query( TQString("SELECT COUNT(parent) FROM podcastepisodes WHERE ( parent='%1' AND isNew=%2 ) LIMIT 1" )
.arg( (*it).url().url(), CollectionDB::instance()->boolT() ) )
.first().toInt() > 0;
channel->setNew( hasNew );
if( channel->autoscan() )
m_podcastItemsToScan.append( channel );
}
if( !m_podcastItemsToScan.isEmpty() )
m_podcastTimer->start( m_podcastTimerInterval );
}
TQMap<int,PlaylistCategory*>
PlaylistBrowser::loadPodcastFolders( PlaylistCategory *p )
{
DEBUG_BLOCK
TQString sql = "SELECT * FROM podcastfolders ORDER BY parent ASC;";
TQStringList values = CollectionDB::instance()->query( sql );
// store the folder and IDs so finding a parent is fast
TQMap<int,PlaylistCategory*> folderMap;
PlaylistCategory *folder = 0;
foreach( values )
{
const int id = (*it).toInt();
const TQString t = *++it;
const int parentId = (*++it).toInt();
const bool isOpen = ( (*++it) == CollectionDB::instance()->boolT() ? true : false );
PlaylistCategory *parent = p;
if( parentId > 0 && folderMap.find( parentId ) != folderMap.end() )
parent = folderMap[parentId];
folder = new PlaylistCategory( parent, folder, t, id );
folder->setOpen( isOpen );
folderMap[id] = folder;
}
// check if the base folder exists
p->setOpen( Amarok::config( "PlaylistBrowser" )->readBoolEntry( "Podcast Folder Open", true ) );
return folderMap;
}
void PlaylistBrowser::savePodcastFolderStates( PlaylistCategory *folder )
{
if( !folder ) return;
PlaylistCategory *child = static_cast<PlaylistCategory*>(folder->firstChild());
while( child )
{
if( isCategory( child ) )
savePodcastFolderStates( child );
else
break;
child = static_cast<PlaylistCategory*>(child->nextSibling());
}
if( folder != m_podcastCategory )
{
if( folder->id() < 0 ) // probably due to a 1.3->1.4 migration
{ // we add the folder to the db, set the id and then update all the children
int parentId = static_cast<PlaylistCategory*>(folder->parent())->id();
int newId = CollectionDB::instance()->addPodcastFolder( folder->text(0), parentId, folder->isOpen() );
folder->setId( newId );
PodcastChannel *chan = static_cast<PodcastChannel*>(folder->firstChild());
while( chan )
{
if( isPodcastChannel( chan ) )
// will update the database so child has correct parentId.
chan->setParent( folder );
chan = static_cast<PodcastChannel*>(chan->nextSibling());
}
}
else
{
CollectionDB::instance()->updatePodcastFolder( folder->id(), folder->text(0),
static_cast<PlaylistCategory*>(folder->parent())->id(), folder->isOpen() );
}
}
}
void PlaylistBrowser::scanPodcasts()
{
//don't want to restart timer unnecessarily. addPodcast will start it if it is necessary
if( m_podcastItemsToScan.isEmpty() ) return;
for( uint i=0; i < m_podcastItemsToScan.count(); i++ )
{
TQListViewItem *item = m_podcastItemsToScan.at( i );
PodcastChannel *pc = static_cast<PodcastChannel*>(item);
pc->rescan();
}
//restart timer
m_podcastTimer->start( m_podcastTimerInterval );
}
void PlaylistBrowser::refreshPodcasts( TQListViewItem *parent )
{
for( TQListViewItem *child = parent->firstChild();
child;
child = child->nextSibling() )
{
if( isPodcastChannel( child ) )
static_cast<PodcastChannel*>( child )->rescan();
else if( isCategory( child ) )
refreshPodcasts( child );
}
}
void PlaylistBrowser::addPodcast( TQListViewItem *parent )
{
bool ok;
const TQString name = KInputDialog::getText(i18n("Add Podcast"), i18n("Enter Podcast URL:"), TQString(), &ok, this);
if( ok && !name.isEmpty() )
{
addPodcast( KURL::fromPathOrURL( name ), parent );
}
}
void PlaylistBrowser::configurePodcasts( TQListViewItem *parent )
{
TQPtrList<PodcastChannel> podcastChannelList;
for( TQListViewItem *child = parent->firstChild();
child;
child = child->nextSibling() )
{
if( isPodcastChannel( child ) )
{
podcastChannelList.append( static_cast<PodcastChannel*>( child ) );
}
}
if( !podcastChannelList.isEmpty() )
configurePodcasts( podcastChannelList, i18n( "Podcasts contained in %1", "All in %1").arg( parent->text( 0 ) ) );
}
void PlaylistBrowser::configureSelectedPodcasts()
{
TQPtrList<PodcastChannel> selected;
TQListViewItemIterator it( m_listview, TQListViewItemIterator::Selected);
for( ; it.current(); ++it )
{
if( isPodcastChannel( (*it) ) )
selected.append( static_cast<PodcastChannel*>(*it) );
}
if (selected.isEmpty() )
return; //shouldn't happen
if( selected.count() == 1 )
selected.getFirst()->configure();
else
configurePodcasts( selected, i18n("1 Podcast", "%n Podcasts", selected.count() ) );
if( m_podcastItemsToScan.isEmpty() )
m_podcastTimer->stop();
else if( m_podcastItemsToScan.count() == 1 )
m_podcastTimer->start( m_podcastTimerInterval );
// else timer is already running
}
void PlaylistBrowser::configurePodcasts( TQPtrList<PodcastChannel> &podcastChannelList,
const TQString &caption )
{
if( podcastChannelList.isEmpty() )
{
debug() << "BUG: podcastChannelList is empty" << endl;
return;
}
TQPtrList<PodcastSettings> podcastSettingsList;
foreachType( TQPtrList<PodcastChannel>, podcastChannelList)
{
podcastSettingsList.append( (*it)->getSettings() );
}
PodcastSettingsDialog *dialog = new PodcastSettingsDialog( podcastSettingsList, caption );
if( dialog->configure() )
{
PodcastChannel *channel = podcastChannelList.first();
foreachType( TQPtrList<PodcastSettings>, podcastSettingsList )
{
if ( (*it)->title() == channel->title() )
{
channel->setSettings( *it );
}
else
debug() << " BUG in playlistbrowser.cpp:configurePodcasts( )" << endl;
channel = podcastChannelList.next();
}
}
}
PodcastChannel *
PlaylistBrowser::findPodcastChannel( const KURL &feed, TQListViewItem *parent ) const
{
if( !parent ) parent = static_cast<TQListViewItem*>(m_podcastCategory);
for( TQListViewItem *it = parent->firstChild();
it;
it = it->nextSibling() )
{
if( isPodcastChannel( it ) )
{
PodcastChannel *channel = static_cast<PodcastChannel *>( it );
if( channel->url().prettyURL() == feed.prettyURL() )
{
return channel;
}
}
else if( isCategory( it ) )
{
PodcastChannel *channel = findPodcastChannel( feed, it );
if( channel )
return channel;
}
}
return 0;
}
PodcastEpisode *
PlaylistBrowser::findPodcastEpisode( const KURL &episode, const KURL &feed ) const
{
PodcastChannel *channel = findPodcastChannel( feed );
if( !channel )
return 0;
if( !channel->isPolished() )
channel->load();
TQListViewItem *child = channel->firstChild();
while( child )
{
#define child static_cast<PodcastEpisode*>(child)
if( child->url() == episode )
return child;
#undef child
child = child->nextSibling();
}
return 0;
}
void PlaylistBrowser::addPodcast( const KURL& origUrl, TQListViewItem *parent )
{
if( !parent ) parent = static_cast<TQListViewItem*>(m_podcastCategory);
KURL url( origUrl );
if( url.protocol() == "itpc" || url.protocol() == "pcast" )
url.setProtocol( "http" );
PodcastChannel *channel = findPodcastChannel( url );
if( channel )
{
Amarok::StatusBar::instance()->longMessage(
i18n( "Already subscribed to feed %1 as %2" )
.arg( url.prettyURL(), channel->title() ),
KDE::StatusBar::Sorry );
return;
}
PodcastChannel *pc = new PodcastChannel( parent, 0, url );
if( m_podcastItemsToScan.isEmpty() )
{
m_podcastItemsToScan.append( pc );
m_podcastTimer->start( m_podcastTimerInterval );
}
else
{
m_podcastItemsToScan.append( pc );
}
parent->sortChildItems( 0, true );
parent->setOpen( true );
}
void PlaylistBrowser::changePodcastInterval()
{
double time = static_cast<double>(m_podcastTimerInterval / ( 60 * 60 * 1000 ));
bool ok;
double interval = KInputDialog::getDouble( i18n("Download Interval"),
i18n("Scan interval (hours):"), time,
0.5, 100.0, .5, 1, // min, max, step, base
&ok, this);
int milliseconds = static_cast<int>(interval*60.0*60.0*1000.0);
if( ok )
{
if( milliseconds != m_podcastTimerInterval )
{
m_podcastTimerInterval = milliseconds;
m_podcastTimer->changeInterval( m_podcastTimerInterval );
}
}
}
bool PlaylistBrowser::deleteSelectedPodcastItems( const bool removeItem, const bool silent )
{
KURL::List urls;
TQListViewItemIterator it( m_podcastCategory, TQListViewItemIterator::Selected );
TQPtrList<PodcastEpisode> erasedItems;
for( ; it.current(); ++it )
{
if( isPodcastEpisode( *it ) )
{
#define item static_cast<PodcastEpisode*>(*it)
if( item->isOnDisk() ) {
urls.append( item->localUrl() );
erasedItems.append( item );
}
#undef item
}
}
if( urls.isEmpty() ) return false;
int button;
if( !silent )
button = KMessageBox::warningContinueCancel( this,
i18n( "<p>You have selected 1 podcast episode to be <b>irreversibly</b> deleted. ",
"<p>You have selected %n podcast episodes to be <b>irreversibly</b> deleted. ",
urls.count() ), TQString(), KStdGuiItem::del() );
if( silent || button != KMessageBox::Continue )
return false;
KIO::Job *job = KIO::del( urls );
PodcastEpisode *item;
for ( item = erasedItems.first(); item; item = erasedItems.next() )
{
if( removeItem )
{
CollectionDB::instance()->removePodcastEpisode( item->dBId() );
delete item;
}
else
connect( job, TQT_SIGNAL( result( KIO::Job* ) ), item, TQT_SLOT( isOnDisk() ) );;
}
return true;
}
bool PlaylistBrowser::deletePodcasts( TQPtrList<PodcastChannel> items )
{
if( items.isEmpty() ) return false;
KURL::List urls;
foreachType( TQPtrList<PodcastChannel>, items )
{
for( TQListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() )
{
#define ch static_cast<PodcastEpisode*>(ch)
if( ch->isOnDisk() )
{
//delete downloaded media
urls.append( ch->localUrl() );
}
#undef ch
/// we don't need to delete from the database, because removing the channel from the database
/// automatically removes the children as well.
m_podcastItemsToScan.remove( static_cast<PodcastChannel*>(*it) );
}
CollectionDB::instance()->removePodcastChannel( static_cast<PodcastChannel*>(*it)->url() );
}
// TODO We need to check which files have been deleted successfully
if ( urls.count() )
KIO::del( urls );
return true;
}
void PlaylistBrowser::downloadSelectedPodcasts()
{
TQListViewItemIterator it( m_listview, TQListViewItemIterator::Selected );
for( ; it.current(); ++it )
{
if( isPodcastEpisode( *it ) )
{
#define item static_cast<PodcastEpisode*>(*it)
if( !item->isOnDisk() )
m_podcastDownloadQueue.append( item );
#undef item
}
}
downloadPodcastQueue();
}
void PlaylistBrowser::downloadPodcastQueue() //SLOT
{
if( m_podcastDownloadQueue.isEmpty() ) return;
PodcastEpisode *first = m_podcastDownloadQueue.first();
first->downloadMedia();
m_podcastDownloadQueue.removeFirst();
connect( first, TQT_SIGNAL( downloadFinished() ), this, TQT_SLOT( downloadPodcastQueue() ) );
connect( first, TQT_SIGNAL( downloadAborted() ), this, TQT_SLOT( abortPodcastQueue() ) );
}
void PlaylistBrowser::abortPodcastQueue() //SLOT
{
m_podcastDownloadQueue.clear();
}
void PlaylistBrowser::registerPodcastSettings( const TQString &title, const PodcastSettings *settings )
{
m_podcastSettings.insert( title, settings );
}
/**
*************************************************************************
* PLAYLISTS
*************************************************************************
**/
TQString PlaylistBrowser::playlistBrowserCache() const
{
//returns the playlists stats cache file
return Amarok::saveLocation() + "playlistbrowser_save.xml";
}
PlaylistCategory* PlaylistBrowser::loadPlaylists()
{
TQFile file( playlistBrowserCache() );
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
TQDomDocument d;
TQDomElement e;
if( !file.open( IO_ReadOnly ) || !d.setContent( stream.read() ) )
{ /*Couldn't open the file or it had invalid content, so let's create an empty element*/
return new PlaylistCategory(m_listview, 0 , i18n("Playlists") );
}
else {
e = d.namedItem( "category" ).toElement();
if ( e.attribute("formatversion") =="1.1" )
{
PlaylistCategory* p = new PlaylistCategory( m_listview, 0 , e );
p->setText( 0, i18n("Playlists") );
return p;
}
else { // Old unversioned format
PlaylistCategory* p = new PlaylistCategory( m_listview, 0 , i18n("Playlists") );
TQListViewItem *last = 0;
TQDomNode n = d.namedItem( "playlistbrowser" ).namedItem("playlist");
for ( ; !n.isNull(); n = n.nextSibling() )
last = new PlaylistEntry( p, last, n.toElement() );
return p;
}
}
}
TQListViewItem *
PlaylistBrowser::findItemInTree( const TQString &searchstring, int c ) const
{
TQStringList list = TQStringList::split( "/", searchstring, true );
// select the 1st level
TQStringList::Iterator it = list.begin();
TQListViewItem *pli = findItem (*it, c);
if ( !pli ) return pli;
for ( ++it ; it != list.end(); ++it )
{
TQListViewItemIterator it2( pli );
for( ++it2 ; it2.current(); ++it2 )
{
if ( *it == (*it2)->text(0) )
{
pli = *it2;
break;
}
// test, to not go over into the next category
if ( isCategory( *it2 ) && (pli->nextSibling() == *it2) )
return 0;
}
if ( ! it2.current() )
return 0;
}
return pli;
}
DynamicMode *PlaylistBrowser::findDynamicModeByTitle( const TQString &title )
{
if( !m_polished )
polish();
for ( TQListViewItem *item = m_dynamicCategory->firstChild(); item; item = item->nextSibling() )
{
DynamicEntry *entry = dynamic_cast<DynamicEntry *>( item );
if ( entry && entry->title() == title )
return entry;
}
return 0;
}
PlaylistEntry *
PlaylistBrowser::findPlaylistEntry( const TQString &url, TQListViewItem *parent ) const
{
if( !parent ) parent = static_cast<TQListViewItem*>(m_playlistCategory);
for( TQListViewItem *it = parent->firstChild();
it;
it = it->nextSibling() )
{
if( isPlaylist( it ) )
{
PlaylistEntry *pl = static_cast<PlaylistEntry*>( it );
debug() << pl->url().path() << " == " << url << endl;
if( pl->url().path() == url )
{
debug() << "ok!" << endl;
return pl;
}
}
else if( isCategory( it ) )
{
PlaylistEntry *pl = findPlaylistEntry( url, it );
if( pl )
return pl;
}
}
return 0;
}
int PlaylistBrowser::loadPlaylist( const TQString &playlist, bool /*force*/ )
{
// roland
DEBUG_BLOCK
TQListViewItem *pli = findItemInTree( playlist, 0 );
if ( ! pli ) return -1;
slotDoubleClicked( pli );
return 0;
// roland
}
void PlaylistBrowser::addPlaylist( const TQString &path, TQListViewItem *parent, bool force, bool imported )
{
// this function adds a playlist to the playlist browser
if( !m_polished )
polish();
TQFile file( path );
if( !file.exists() ) return;
PlaylistEntry *playlist = findPlaylistEntry( path );
if( playlist && force )
playlist->load(); //reload the playlist
if( imported ) {
TQListViewItem *playlistImports = 0;
//First try and find the imported folder
for ( TQListViewItem *it = m_playlistCategory->firstChild(); it; it = it->nextSibling() )
{
if ( dynamic_cast<PlaylistCategory*>( it ) && static_cast<PlaylistCategory*>( it )->isFolder() &&
it->text( 0 ) == i18n( "Imported" ) )
{
playlistImports = it;
break;
}
}
if ( !playlistImports ) //We didn't find the Imported folder, so create it.
playlistImports = new PlaylistCategory( m_playlistCategory, 0, i18n("Imported") );
parent = playlistImports;
}
else if( !parent ) parent = static_cast<TQListViewItem*>(m_playlistCategory);
if( !playlist ) {
if( !m_playlistCategory || !m_playlistCategory->childCount() ) { //first child
removeButton->setEnabled( true );
renameButton->setEnabled( true );
}
KURL auxKURL;
auxKURL.setPath(path);
m_lastPlaylist = playlist = new PlaylistEntry( parent, 0, auxKURL );
}
parent->setOpen( true );
parent->sortChildItems( 0, true );
m_listview->clearSelection();
playlist->setSelected( true );
}
bool PlaylistBrowser::savePlaylist( const TQString &path, const TQValueList<KURL> &in_urls,
const TQValueList<TQString> &titles, const TQValueList<int> &lengths,
bool relative )
{
if( path.isEmpty() )
return false;
TQFile file( path );
if( !file.open( IO_WriteOnly ) )
{
KMessageBox::sorry( PlaylistWindow::self(), i18n( "Cannot write playlist (%1).").arg(path) );
return false;
}
TQTextStream stream( &file );
stream << "#EXTM3U\n";
KURL::List urls;
for( int i = 0, n = in_urls.count(); i < n; ++i )
{
const KURL &url = in_urls[i];
if( url.isLocalFile() && TQFileInfo( url.path() ).isDir() )
urls += recurse( url );
else
urls += url;
}
for( int i = 0, n = urls.count(); i < n; ++i )
{
const KURL &url = urls[i];
if( !titles.isEmpty() && !lengths.isEmpty() )
{
stream << "#EXTINF:";
stream << TQString::number( lengths[i] );
stream << ',';
stream << titles[i];
stream << '\n';
}
if (url.protocol() == "file" ) {
if ( relative ) {
const TQFileInfo fi(file);
stream << KURL::relativePath(fi.dirPath(), url.path());
} else
stream << url.path();
} else {
stream << url.url();
}
stream << "\n";
}
file.close(); // Flushes the file, before we read it
PlaylistBrowser::instance()->addPlaylist( path, 0, true );
return true;
}
void PlaylistBrowser::openPlaylist( TQListViewItem *parent ) //SLOT
{
// open a file selector to add playlists to the playlist browser
TQStringList files;
files = KFileDialog::getOpenFileNames( TQString(), "*.m3u *.pls *.xspf|" + i18n("Playlist Files"), this, i18n("Import Playlists") );
const TQStringList::ConstIterator end = files.constEnd();
for( TQStringList::ConstIterator it = files.constBegin(); it != end; ++it )
addPlaylist( *it, parent );
savePlaylists();
}
void PlaylistBrowser::savePlaylists()
{
TQFile file( playlistBrowserCache() );
TQDomDocument doc;
TQDomElement playlistsB = m_playlistCategory->xml();
playlistsB.setAttribute( "product", "Amarok" );
playlistsB.setAttribute( "version", APP_VERSION );
playlistsB.setAttribute( "formatversion", "1.1" );
TQDomNode playlistsNode = doc.importNode( playlistsB, true );
doc.appendChild( playlistsNode );
TQString temp( doc.toString() );
// Only open the file after all data is ready. If it crashes, data is not lost!
if ( !file.open( IO_WriteOnly ) ) return;
TQTextStream stream( &file );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
stream << temp;
}
bool PlaylistBrowser::deletePlaylists( TQPtrList<PlaylistEntry> items )
{
KURL::List urls;
foreachType( TQPtrList<PlaylistEntry>, items )
{
urls.append( (*it)->url() );
}
if( !urls.isEmpty() )
return deletePlaylists( urls );
return false;
}
bool PlaylistBrowser::deletePlaylists( KURL::List items )
{
if ( items.isEmpty() ) return false;
// TODO We need to check which files have been deleted successfully
// Avoid deleting dirs. See bug #122480
for ( KURL::List::iterator it = items.begin(), end = items.end(); it != end; ++it ) {
if ( TQFileInfo( (*it).path() ).isDir() ) {
it = items.remove( it );
continue;
}
}
KIO::del( items );
return true;
}
void PlaylistBrowser::savePlaylist( PlaylistEntry *item )
{
bool append = false;
if( item->trackList().count() == 0 ) //the playlist hasn't been loaded so we append the dropped tracks
append = true;
//save the modified playlist in m3u or pls format
const TQString ext = fileExtension( item->url().path() );
if( ext.lower() == "m3u" )
saveM3U( item, append );
else if ( ext.lower() == "xspf" )
saveXSPF( item, append );
else
savePLS( item, append );
}
/**
*************************************************************************
* General Methods
*************************************************************************
**/
PlaylistBrowserEntry *
PlaylistBrowser::findItem( TQString &t, int c ) const
{
return static_cast<PlaylistBrowserEntry *>( m_listview->findItem( t, c, TQt::ExactMatch ) );
}
bool PlaylistBrowser::createPlaylist( TQListViewItem *parent, bool current, TQString title )
{
if( title.isEmpty() ) title = i18n("Untitled");
const TQString path = PlaylistDialog::getSaveFileName( title );
if( path.isEmpty() )
return false;
if( !parent )
parent = static_cast<TQListViewItem *>( m_playlistCategory );
if( current )
{
if ( !Playlist::instance()->saveM3U( path ) ) {
return false;
}
}
else
{
//Remove any items in Listview that have the same path as this one
// Should only happen when overwriting a playlist
TQListViewItem *item = parent->firstChild();
while( item )
{
if( static_cast<PlaylistEntry*>( item )->url() == path )
{
TQListViewItem *todelete = item;
item = item->nextSibling();
delete todelete;
}
else
item = item->nextSibling();
}
//Remove existing playlist if it exists
if ( TQFileInfo( path ).exists() )
TQFileInfo( path ).dir().remove( path );
m_lastPlaylist = new PlaylistEntry( parent, 0, path );
parent->sortChildItems( 0, true );
}
savePlaylists();
return true;
}
void PlaylistBrowser::addSelectedToPlaylist( int options )
{
if ( options == -1 )
options = Playlist::Unique | Playlist::Append;
KURL::List list;
TQListViewItemIterator it( m_listview, TQListViewItemIterator::Selected );
for( ; it.current(); ++it )
{
#define item (*it)
if ( isPlaylist( item ) )
list << static_cast<PlaylistEntry*>(item)->url();
else if( isLastFm( item ) )
list << static_cast<LastFmEntry*>(item)->url();
else if ( isStream( item ) )
list << static_cast<StreamEntry*>(item)->url();
else if ( isPodcastChannel( item ) )
{
#define channel static_cast<PodcastChannel*>(item)
if( !channel->isPolished() )
channel->load();
#undef channel
KURL::List _list;
TQListViewItem *child = item->firstChild();
while( child )
{
#define child static_cast<PodcastEpisode *>(child)
child->isOnDisk() ?
_list.prepend( child->localUrl() ):
_list.prepend( child->url() );
#undef child
child = child->nextSibling();
}
list += _list ;
}
else if ( isPodcastEpisode( item ) )
{
#define pod static_cast<PodcastEpisode*>(item)
if( pod->isOnDisk() )
list << pod->localUrl();
else
list << pod->url();
#undef pod
}
else if ( isPlaylistTrackItem( item ) )
list << static_cast<PlaylistTrackItem*>(item)->url();
#undef item
}
if( !list.isEmpty() )
Playlist::instance()->insertMedia( list, options );
}
void
PlaylistBrowser::invokeItem( TQListViewItem* i, const TQPoint& point, int column ) //SLOT
{
if( column == -1 )
return;
PlaylistBrowserView *view = getListView();
TQPoint p = mapFromGlobal( point );
if ( p.x() > view->header()->sectionPos( view->header()->mapToIndex( 0 ) ) + view->treeStepSize() * ( i->depth() + ( view->rootIsDecorated() ? 1 : 0) ) + view->itemMargin()
|| p.x() < view->header()->sectionPos( view->header()->mapToIndex( 0 ) ) )
slotDoubleClicked( i );
}
void PlaylistBrowser::slotDoubleClicked( TQListViewItem *item ) //SLOT
{
if( !item ) return;
PlaylistBrowserEntry *entry = dynamic_cast<PlaylistBrowserEntry*>(item);
if ( entry )
entry->slotDoubleClicked();
}
void PlaylistBrowser::collectionScanDone()
{
if( !m_polished || CollectionDB::instance()->isEmpty() )
{
return;
}
else if( !m_smartCategory )
{
m_smartCategory = loadSmartPlaylists();
loadDefaultSmartPlaylists();
m_smartCategory->setOpen( true );
}
}
void PlaylistBrowser::removeSelectedItems() //SLOT
{
// this function remove selected playlists and tracks
int playlistCount = 0;
int trackCount = 0;
int streamCount = 0;
int smartyCount = 0;
int dynamicCount = 0;
int podcastCount = 0;
int folderCount = 0;
int lastfmCount = 0;
TQPtrList<PlaylistEntry> playlistsToDelete;
TQPtrList<PodcastChannel> podcastsToDelete;
TQPtrList<PlaylistCategory> playlistFoldersToDelete;
TQPtrList<PlaylistCategory> podcastFoldersToDelete;
//remove currentItem, no matter if selected or not
m_listview->setSelected( m_listview->currentItem(), true );
TQPtrList<TQListViewItem> selected;
TQListViewItemIterator it( m_listview, TQListViewItemIterator::Selected );
for( ; it.current(); ++it )
{
if( !static_cast<PlaylistBrowserEntry*>(*it)->isKept() )
continue;
if( isCategory( *it ) && !static_cast<PlaylistCategory*>(*it)->isFolder() ) //its a base category
continue;
// if the playlist containing this item is already selected the current item will be skipped
// it will be deleted from the parent
TQListViewItem *parent = it.current()->parent();
if( parent && parent->isSelected() ) //parent will remove children
continue;
if (parent) {
while( parent->parent() && static_cast<PlaylistBrowserEntry*>(parent)->isKept() )
parent = parent->parent();
}
if( parent && !static_cast<PlaylistBrowserEntry*>(parent)->isKept() )
continue;
switch( (*it)->rtti() )
{
case PlaylistEntry::RTTI:
playlistsToDelete.append( static_cast<PlaylistEntry*>(*it) );
playlistCount++;
continue; // don't add the folder to selected, else it will be deleted twice
case PlaylistTrackItem::RTTI:
trackCount++;
break;
case LastFmEntry::RTTI:
lastfmCount++;
break;
case StreamEntry::RTTI:
streamCount++;
break;
case DynamicEntry::RTTI:
dynamicCount++;
break;
case SmartPlaylist::RTTI:
smartyCount++;
break;
case PodcastChannel::RTTI:
podcastCount++;
podcastsToDelete.append( static_cast<PodcastChannel*>(*it) );
case PodcastEpisode::RTTI: //episodes can't be removed
continue; // don't add the folder to selected, else it will be deleted twice
case PlaylistCategory::RTTI:
folderCount++;
if( parent == m_playlistCategory )
{
for( TQListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() )
{
if( isCategory( ch ) )
{
folderCount++;
playlistFoldersToDelete.append( static_cast<PlaylistCategory*>(ch) );
}
else
{
playlistCount++;
playlistsToDelete.append( static_cast<PlaylistEntry*>(ch) );
}
}
playlistFoldersToDelete.append( static_cast<PlaylistCategory*>(*it) );
continue; // don't add the folder to selected, else it will be deleted twice
}
else if( parent == m_podcastCategory )
{
for( TQListViewItem *ch = (*it)->firstChild(); ch; ch = ch->nextSibling() )
{
if( isCategory( ch ) )
{
folderCount++;
podcastFoldersToDelete.append( static_cast<PlaylistCategory*>(ch) );
}
else
{
podcastCount++;
podcastsToDelete.append( static_cast<PodcastChannel*>(ch) );
}
}
podcastFoldersToDelete.append( static_cast<PlaylistCategory*>(*it) );
continue; // don't add the folder to selected, else it will be deleted twice
}
default:
break;
}
selected.append( it.current() );
}
int totalCount = playlistCount + smartyCount + dynamicCount +
streamCount + podcastCount + folderCount + lastfmCount;
if( selected.isEmpty() && !totalCount ) return;
TQString message = i18n( "<p>You have selected:<ul>" );
if( playlistCount ) message += "<li>" + i18n( "1 playlist", "%n playlists", playlistCount ) + "</li>";
if( smartyCount ) message += "<li>" + i18n( "1 smart playlist", "%n smart playlists", smartyCount ) + "</li>";
if( dynamicCount ) message += "<li>" + i18n( "1 dynamic playlist", "%n dynamic playlists", dynamicCount ) + "</li>";
if( streamCount ) message += "<li>" + i18n( "1 stream", "%n streams", streamCount ) + "</li>";
if( podcastCount ) message += "<li>" + i18n( "1 podcast", "%n podcasts", podcastCount ) + "</li>";
if( folderCount ) message += "<li>" + i18n( "1 folder", "%n folders", folderCount ) + "</li>";
if( lastfmCount ) message += "<li>" + i18n( "1 last.fm stream", "%n last.fm streams", lastfmCount ) + "</li>";
message += i18n( "</ul><br>to be <b>irreversibly</b> deleted.</p>" );
if( podcastCount )
message += i18n( "<br><p>All downloaded podcast episodes will also be deleted.</p>" );
if( totalCount > 0 )
{
int button = KMessageBox::warningContinueCancel( this, message, TQString(), KStdGuiItem::del() );
if( button != KMessageBox::Continue )
return;
}
foreachType( TQPtrList<TQListViewItem>, selected )
{
if ( isPlaylistTrackItem( *it ) )
{
static_cast<PlaylistEntry*>( (*it)->parent() )->removeTrack( (*it) );
continue;
}
if ( isDynamic( *it ) )
static_cast<DynamicEntry*>( *it )->deleting();
delete (*it);
}
// used for deleting playlists first, then folders.
if( playlistCount )
{
if( deletePlaylists( playlistsToDelete ) )
{
foreachType( TQPtrList<PlaylistEntry>, playlistsToDelete )
{
m_dynamicEntries.remove(*it);
delete (*it);
}
}
}
if( podcastCount )
{
if( deletePodcasts( podcastsToDelete ) )
foreachType( TQPtrList<PodcastChannel>, podcastsToDelete )
delete (*it);
}
foreachType( TQPtrList<PlaylistCategory>, playlistFoldersToDelete )
delete (*it);
foreachType( TQPtrList<PlaylistCategory>, podcastFoldersToDelete )
removePodcastFolder( *it );
if( playlistCount || trackCount )
savePlaylists();
if( streamCount ) saveStreams();
if( smartyCount ) saveSmartPlaylists();
if( dynamicCount ) saveDynamics();
if( lastfmCount ) saveLastFm();
}
// remove podcast folders. we need to do this recursively to ensure all children are removed from the db
void PlaylistBrowser::removePodcastFolder( PlaylistCategory *item )
{
if( !item ) return;
if( !item->childCount() )
{
CollectionDB::instance()->removePodcastFolder( item->id() );
delete item;
return;
}
TQListViewItem *child = item->firstChild();
while( child )
{
TQListViewItem *nextChild = 0;
if( isPodcastChannel( child ) )
{
#define child static_cast<PodcastChannel*>(child)
nextChild = child->nextSibling();
CollectionDB::instance()->removePodcastChannel( child->url() );
m_podcastItemsToScan.remove( child );
#undef child
}
else if( isCategory( child ) )
{
nextChild = child->nextSibling();
removePodcastFolder( static_cast<PlaylistCategory*>(child) );
}
child = nextChild;
}
CollectionDB::instance()->removePodcastFolder( item->id() );
delete item;
}
void PlaylistBrowser::renameSelectedItem() //SLOT
{
TQListViewItem *item = m_listview->currentItem();
if( !item ) return;
if( item == m_randomDynamic || item == m_suggestedDynamic )
return;
PlaylistBrowserEntry *entry = dynamic_cast<PlaylistBrowserEntry*>( item );
if ( entry )
entry->slotRenameItem();
}
void PlaylistBrowser::renamePlaylist( TQListViewItem* item, const TQString& newName, int ) //SLOT
{
PlaylistBrowserEntry *entry = dynamic_cast<PlaylistBrowserEntry*>( item );
if ( entry )
entry->slotPostRenameItem( newName );
}
void PlaylistBrowser::saveM3U( PlaylistEntry *item, bool append )
{
TQFile file( item->url().path() );
if( append ? file.open( IO_WriteOnly | IO_Append ) : file.open( IO_WriteOnly ) )
{
TQTextStream stream( &file );
if( !append )
stream << "#EXTM3U\n";
TQPtrList<TrackItemInfo> trackList = append ? item->droppedTracks() : item->trackList();
for( TrackItemInfo *info = trackList.first(); info; info = trackList.next() )
{
stream << "#EXTINF:";
stream << info->length();
stream << ',';
stream << info->title();
stream << '\n';
stream << (info->url().protocol() == "file" ? info->url().path() : info->url().url());
stream << "\n";
}
file.close();
}
}
void PlaylistBrowser::saveXSPF( PlaylistEntry *item, bool append )
{
XSPFPlaylist* playlist = new XSPFPlaylist();
playlist->setCreator( "Amarok" );
playlist->setTitle( item->text(0) );
XSPFtrackList list;
TQPtrList<TrackItemInfo> trackList = append ? item->droppedTracks() : item->trackList();
for( TrackItemInfo *info = trackList.first(); info; info = trackList.next() )
{
XSPFtrack track;
MetaBundle b( info->url() );
track.creator = b.artist();
track.title = b.title();
track.location = b.url().url();
list.append( track );
}
playlist->setTrackList( list, append );
TQFile file( item->url().path() );
if ( !file.open( IO_WriteOnly ) )
warning() << "Could not open file " << file.name()
<< " write-only" << endl;
else {
TQTextStream stream ( &file );
playlist->save( stream, 2 );
file.close();
}
}
void PlaylistBrowser::savePLS( PlaylistEntry *item, bool append )
{
TQFile file( item->url().path() );
if( append ? file.open( IO_WriteOnly | IO_Append ) : file.open( IO_WriteOnly ) )
{
TQTextStream stream( &file );
TQPtrList<TrackItemInfo> trackList = append ? item->droppedTracks() : item->trackList();
stream << "NumberOfEntries=" << trackList.count() << endl;
int c=1;
for( TrackItemInfo *info = trackList.first(); info; info = trackList.next(), ++c )
{
stream << "File" << c << "=";
stream << (info->url().protocol() == "file" ? info->url().path() : info->url().url());
stream << "\nTitle" << c << "=";
stream << info->title();
stream << "\nLength" << c << "=";
stream << info->length();
stream << "\n";
}
stream << "Version=2\n";
file.close();
}
}
#include <kdirlister.h>
#include <tqeventloop.h>
#include "playlistloader.h"
//this function (C) Copyright 2003-4 Max Howell, (C) Copyright 2004 Mark Kretschmann
KURL::List PlaylistBrowser::recurse( const KURL &url )
{
typedef TQMap<TQString, KURL> FileMap;
KDirLister lister( false );
lister.setAutoUpdate( false );
lister.setAutoErrorHandlingEnabled( false, 0 );
lister.openURL( url );
while( !lister.isFinished() )
kapp->eventLoop()->processEvents( TQEventLoop::ExcludeUserInput );
KFileItemList items = lister.items(); //returns TQPtrList, so we MUST only do it once!
KURL::List urls;
FileMap files;
for( KFileItem *item = items.first(); item; item = items.next() ) {
if( item->isFile() ) { files[item->name()] = item->url(); continue; }
if( item->isDir() ) urls += recurse( item->url() );
}
foreachType( FileMap, files )
// users often have playlist files that reflect directories
// higher up, or stuff in this directory. Don't add them as
// it produces double entries
if( !PlaylistFile::isPlaylistFile( (*it).fileName() ) )
urls += *it;
return urls;
}
void PlaylistBrowser::currentItemChanged( TQListViewItem *item ) //SLOT
{
// rename remove and delete buttons are disabled if there are no playlists
// rename and delete buttons are disabled for track items
bool enable_remove = false;
bool enable_rename = false;
if ( !item )
goto enable_buttons;
if ( isCategory( item ) )
{
if( static_cast<PlaylistCategory*>(item)->isFolder() &&
static_cast<PlaylistCategory*>(item)->isKept() )
enable_remove = enable_rename = true;
}
else if ( isPodcastChannel( item ) )
{
enable_remove = true;
enable_rename = false;
}
else if ( !isPodcastEpisode( item ) )
enable_remove = enable_rename = static_cast<PlaylistCategory*>(item)->isKept();
static_cast<PlaylistBrowserEntry*>(item)->updateInfo();
enable_buttons:
removeButton->setEnabled( enable_remove );
renameButton->setEnabled( enable_rename );
}
void PlaylistBrowser::customEvent( TQCustomEvent *e )
{
// If a playlist is found in collection folders it will be automatically added to the playlist browser
// The ScanController sends a PlaylistFoundEvent when a playlist is found.
ScanController::PlaylistFoundEvent* p = static_cast<ScanController::PlaylistFoundEvent*>( e );
addPlaylist( p->path(), 0, false, true );
}
void PlaylistBrowser::slotAddMenu( int id ) //SLOT
{