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.
3251 lines
105 KiB
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
|
|
{
|
|