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.
3269 lines
106 KiB
3269 lines
106 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 <tdeaction.h>
|
|
#include <tdeactionclasses.h>
|
|
#include <tdeactioncollection.h>
|
|
#include <tdeapplication.h>
|
|
#include <tdefiledialog.h> //openPlaylist()
|
|
#include <tdeio/job.h> //deleteSelectedPlaylists()
|
|
#include <kiconloader.h> //smallIcon
|
|
#include <kinputdialog.h>
|
|
#include <klineedit.h> //rename()
|
|
#include <tdelocale.h>
|
|
#include <tdemessagebox.h> //renamePlaylist(), deleteSelectedPlaylist()
|
|
#include <kmimetype.h>
|
|
#include <tdemultipledrag.h> //dragObject()
|
|
#include <tdepopupmenu.h>
|
|
#include <kpushbutton.h>
|
|
#include <kstandarddirs.h> //TDEGlobal::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 TDEActionCollection( this ) )
|
|
, m_podcastTimer( new TQTimer( this ) )
|
|
|
|
|
|
{
|
|
s_instance = this;
|
|
|
|
TQVBox *browserBox = new TQVBox( this );
|
|
browserBox->setSpacing( 3 );
|
|
|
|
//<Toolbar>
|
|
addMenuButton = new TDEActionMenu( i18n("Add"), Amarok::icon( "add_playlist" ), m_ac );
|
|
addMenuButton->setDelayed( false );
|
|
|
|
TDEPopupMenu *playlistMenu = new TDEPopupMenu( this );
|
|
playlistMenu->insertItem( i18n("New..."), PLAYLIST );
|
|
playlistMenu->insertItem( i18n("Import Existing..."), PLAYLIST_IMPORT );
|
|
connect( playlistMenu, TQT_SIGNAL( activated(int) ), TQT_SLOT( slotAddPlaylistMenu(int) ) );
|
|
|
|
TDEPopupMenu *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 TDEAction( i18n("Rename"), "editclear", 0, TQT_TQOBJECT(this), TQT_SLOT( renameSelectedItem() ), m_ac );
|
|
removeButton = new TDEAction( i18n("Delete"), Amarok::icon( "remove" ), 0, TQT_TQOBJECT(this), TQT_SLOT( removeSelectedItems() ), m_ac );
|
|
|
|
m_toolbar = new Browser::ToolBar( browserBox );
|
|
m_toolbar->setIconText( TDEToolBar::IconTextRight, false ); //we want the open button to have text on right
|
|
addMenuButton->plug( m_toolbar );
|
|
|
|
m_toolbar->setIconText( TDEToolBar::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
|
|
TDEIO::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;
|
|
|
|
TDEIO::Job *job = TDEIO::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( TDEIO::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() )
|
|
TDEIO::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;
|
|
}
|
|
}
|
|
TDEIO::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
|
|
{
|
|
switch( id )
|
|
{
|
|
case STREAM:
|
|
addStream();
|
|
break;
|
|
|
|
case SMARTPLAYLIST:
|
|
addSmartPlaylist();
|
|
break;
|
|
|
|
case PODCAST:
|
|
addPodcast();
|
|
break;
|
|
|
|
case ADDDYNAMIC:
|
|
ConfigDynamic::dynamicDialog(this);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void PlaylistBrowser::slotAddPlaylistMenu( int id ) //SLOT
|
|
{
|
|
switch( id )
|
|
{
|
|
case PLAYLIST:
|
|
createPlaylist( 0/*base cat*/, false/*make empty*/ );
|
|
break;
|
|
|
|
case PLAYLIST_IMPORT:
|
|
openPlaylist();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
************************
|
|
* Context Menu Entries
|
|
************************
|
|
**/
|
|
|
|
void PlaylistBrowser::showContextMenu( TQListViewItem *item, const TQPoint &p, int ) //SLOT
|
|
{
|
|
if( !item ) return;
|
|
|
|
PlaylistBrowserEntry *entry = dynamic_cast<PlaylistBrowserEntry*>( item );
|
|
if ( entry )
|
|
entry->showContextMenu( p );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CLASS PlaylistBrowserView
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
PlaylistBrowserView::PlaylistBrowserView( TQWidget *parent, const char *name )
|
|
: TDEListView( parent, name )
|
|
, m_marker( 0 )
|
|
{
|
|
addColumn( i18n("Playlists") );
|
|
|
|
setSelectionMode( TQListView::Extended );
|
|
setResizeMode( TQListView::AllColumns );
|
|
setShowSortIndicator( true );
|
|
setRootIsDecorated( true );
|
|
|
|
setDropVisualizer( true ); //the visualizer (a line marker) is drawn when dragging over tracks
|
|
setDropHighlighter( true ); //and the highligther (a focus rect) is drawn when dragging over playlists
|
|
setDropVisualizerWidth( 3 );
|
|
setAcceptDrops( true );
|
|
|
|
setTreeStepSize( 20 );
|
|
|
|
connect( this, TQT_SIGNAL( mouseButtonPressed ( int, TQListViewItem *, const TQPoint &, int ) ),
|
|
this, TQT_SLOT( mousePressed( int, TQListViewItem *, const TQPoint &, int ) ) );
|
|
|
|
//TODO moving tracks
|
|
//connect( this, TQT_SIGNAL( moved(TQListViewItem *, TQListViewItem *, TQListViewItem * )),
|
|
// this, TQT_SLOT( itemMoved(TQListViewItem *, TQListViewItem *, TQListViewItem * )));
|
|
}
|
|
|
|
PlaylistBrowserView::~PlaylistBrowserView() { }
|
|
|
|
void PlaylistBrowserView::contentsDragEnterEvent( TQDragEnterEvent *e )
|
|
{
|
|
e->accept( e->source() == viewport() || KURLDrag::canDecode( e ) );
|
|
}
|
|
|
|
void PlaylistBrowserView::contentsDragMoveEvent( TQDragMoveEvent* e )
|
|
{
|
|
//Get the closest item _before_ the cursor
|
|
const TQPoint p = contentsToViewport( e->pos() );
|
|
TQListViewItem *item = itemAt( p );
|
|
if( !item ) {
|
|
eraseMarker();
|
|
return;
|
|
}
|
|
|
|
//only for track items (for playlist items we draw the highlighter)
|
|
if( isPlaylistTrackItem( item ) )
|
|
item = item->itemAbove();
|
|
|
|
if( item != m_marker )
|
|
{
|
|
eraseMarker();
|
|
m_marker = item;
|
|
viewportPaintEvent( 0 );
|
|
}
|
|
}
|
|
|
|
void PlaylistBrowserView::contentsDragLeaveEvent( TQDragLeaveEvent* )
|
|
{
|
|
eraseMarker();
|
|
}
|
|
|
|
|
|
void PlaylistBrowserView::contentsDropEvent( TQDropEvent *e )
|
|
{
|
|
TQListViewItem *parent = 0;
|
|
TQListViewItem *after;
|
|
|
|
const TQPoint p = contentsToViewport( e->pos() );
|
|
TQListViewItem *item = itemAt( p );
|
|
if( !item ) {
|
|
eraseMarker();
|
|
return;
|
|
}
|
|
|
|
if( !isPlaylist( item ) )
|
|
findDrop( e->pos(), parent, after );
|
|
|
|
eraseMarker();
|
|
|
|
if( e->source() == this )
|
|
{
|
|
moveSelectedItems( item ); // D&D sucks, do it ourselves
|
|
}
|
|
else {
|
|
KURL::List decodedList;
|
|
TQValueList<MetaBundle> bundles;
|
|
if( KURLDrag::decode( e, decodedList ) )
|
|
{
|
|
KURL::List::ConstIterator it = decodedList.begin();
|
|
MetaBundle first( *it );
|
|
const TQString album = first.album();
|
|
const TQString artist = first.artist();
|
|
|
|
int suggestion = !album.stripWhiteSpace().isEmpty() ? 1 : !artist.stripWhiteSpace().isEmpty() ? 2 : 3;
|
|
|
|
for ( ; it != decodedList.end(); ++it )
|
|
{
|
|
if( isCategory(item) )
|
|
{ // check if it is podcast category
|
|
TQListViewItem *cat = item;
|
|
while( isCategory(cat) && cat!=PlaylistBrowser::instance()->podcastCategory() )
|
|
cat = cat->parent();
|
|
|
|
if( cat == PlaylistBrowser::instance()->podcastCategory() )
|
|
PlaylistBrowser::instance()->addPodcast(*it, item);
|
|
continue;
|
|
}
|
|
|
|
TQString filename = (*it).fileName();
|
|
|
|
if( filename.endsWith("m3u") || filename.endsWith("pls") )
|
|
PlaylistBrowser::instance()->addPlaylist( (*it).path() );
|
|
else if( ContextBrowser::hasContextProtocol( *it ) )
|
|
{
|
|
KURL::List urls = ContextBrowser::expandURL( *it );
|
|
for( KURL::List::iterator i = urls.begin();
|
|
i != urls.end();
|
|
i++ )
|
|
{
|
|
MetaBundle mb(*i);
|
|
bundles.append( mb );
|
|
}
|
|
}
|
|
else //TODO: check canDecode ?
|
|
{
|
|
MetaBundle mb(*it);
|
|
bundles.append( mb );
|
|
if( suggestion == 1 && mb.album()->lower().stripWhiteSpace() != album.lower().stripWhiteSpace() )
|
|
suggestion = 2;
|
|
if( suggestion == 2 && mb.artist()->lower().stripWhiteSpace() != artist.lower().stripWhiteSpace() )
|
|
suggestion = 3;
|
|
}
|
|
}
|
|
|
|
if( bundles.isEmpty() ) return;
|
|
|
|
if( parent && isPlaylist( parent ) ) {
|
|
//insert the dropped tracks
|
|
PlaylistEntry *playlist = static_cast<PlaylistEntry *>( parent );
|
|
playlist->insertTracks( after, bundles );
|
|
}
|
|
else //dropped on a playlist item
|
|
{
|
|
TQListViewItem *parent = item;
|
|
bool isPlaylistFolder = false;
|
|
|
|
while( parent )
|
|
{
|
|
if( parent == PlaylistBrowser::instance()->m_playlistCategory )
|
|
{
|
|
isPlaylistFolder = true;
|
|
break;
|
|
}
|
|
parent = parent->parent();
|
|
}
|
|
|
|
if( isPlaylist( item ) ) {
|
|
PlaylistEntry *playlist = static_cast<PlaylistEntry *>( item );
|
|
//append the dropped tracks
|
|
playlist->insertTracks( 0, bundles );
|
|
}
|
|
else if( isCategory( item ) && isPlaylistFolder )
|
|
{
|
|
PlaylistBrowser *pb = PlaylistBrowser::instance();
|
|
TQString title = suggestion == 1 ? album
|
|
: suggestion == 2 ? artist
|
|
: TQString();
|
|
if ( pb->createPlaylist( item, false, title ) )
|
|
pb->m_lastPlaylist->insertTracks( 0, bundles );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
e->ignore();
|
|
}
|
|
|
|
}
|
|
|
|
void PlaylistBrowserView::eraseMarker() //SLOT
|
|
{
|
|
if( m_marker )
|
|
{
|
|
TQRect spot;
|
|
if( isPlaylist( m_marker ) )
|
|
spot = drawItemHighlighter( 0, m_marker );
|
|
else
|
|
spot = drawDropVisualizer( 0, 0, m_marker );
|
|
|
|
m_marker = 0;
|
|
viewport()->repaint( spot, false );
|
|
}
|
|
}
|
|
|
|
void PlaylistBrowserView::viewportPaintEvent( TQPaintEvent *e )
|
|
{
|
|
if( e ) TDEListView::viewportPaintEvent( e ); //we call with 0 in contentsDropEvent()
|
|
|
|
if( m_marker )
|
|
{
|
|
TQPainter painter( viewport() );
|
|
if( isPlaylist( m_marker ) ) //when dragging on a playlist we draw a focus rect
|
|
drawItemHighlighter( &painter, m_marker );
|
|
else //when dragging on a track we draw a line marker
|
|
painter.fillRect( drawDropVisualizer( 0, 0, m_marker ),
|
|
TQBrush( colorGroup().highlight(), TQBrush::Dense4Pattern ) );
|
|
}
|
|
}
|
|
|
|
void PlaylistBrowserView::mousePressed( int button, TQListViewItem *item, const TQPoint &pnt, int ) //SLOT
|
|
{
|
|
// this function expande/collapse the playlist if the +/- symbol has been pressed
|
|
// and show the save menu if the save icon has been pressed
|
|
|
|
if( !item || button != Qt::LeftButton ) return;
|
|
|
|
if( isPlaylist( item ) )
|
|
{
|
|
TQPoint p = mapFromGlobal( pnt );
|
|
p.setY( p.y() - header()->height() );
|
|
|
|
TQRect itemrect = itemRect( item );
|
|
|
|
TQRect expandRect = TQRect( 4, itemrect.y() + (item->height()/2) - 5, 15, 15 );
|
|
if( expandRect.contains( p ) ) { //expand symbol clicked
|
|
setOpen( item, !item->isOpen() );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlaylistBrowserView::moveSelectedItems( TQListViewItem *newParent )
|
|
{
|
|
if( !newParent )
|
|
return;
|
|
|
|
TQListViewItem *after=0;
|
|
if( isDynamic( newParent ) || isPodcastChannel( newParent ) ||
|
|
isSmartPlaylist( newParent ) || isPodcastEpisode( newParent ) || isStream( newParent ) )
|
|
{
|
|
after = newParent;
|
|
newParent = newParent->parent();
|
|
}
|
|
|
|
#define newParent static_cast<PlaylistBrowserEntry*>(newParent)
|
|
if( !newParent->isKept() )
|
|
return;
|
|
#undef newParent
|
|
|
|
TQPtrList<TQListViewItem> selected;
|
|
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
|
|
for( ; it.current(); ++it )
|
|
{
|
|
if( !(*it)->parent() ) //must be a base category we are draggin'
|
|
continue;
|
|
|
|
selected.append( *it );
|
|
}
|
|
|
|
for( TQListViewItem *item = selected.first(); item; item = selected.next() )
|
|
{
|
|
TQListViewItem *itemParent = item->parent();
|
|
if( isPlaylistTrackItem( item ) )
|
|
{
|
|
if( isPlaylistTrackItem( newParent ) )
|
|
{
|
|
if( !after && newParent != newParent->parent()->firstChild() )
|
|
after = newParent->itemAbove();
|
|
|
|
newParent = static_cast<PlaylistEntry*>(newParent->parent());
|
|
}
|
|
else if( !isPlaylist( newParent ) )
|
|
continue;
|
|
|
|
|
|
#define newParent static_cast<PlaylistEntry*>(newParent)
|
|
newParent->insertTracks( after, KURL::List( static_cast<PlaylistTrackItem*>(item)->url() ));
|
|
#undef newParent
|
|
#define itemParent static_cast<PlaylistEntry*>(itemParent)
|
|
itemParent->removeTrack( static_cast<PlaylistTrackItem*>(item) );
|
|
#undef itemParent
|
|
continue;
|
|
}
|
|
else if( !isCategory( newParent ) )
|
|
continue;
|
|
|
|
TQListViewItem *base = newParent;
|
|
while( base->parent() )
|
|
base = base->parent();
|
|
|
|
if( base == PlaylistBrowser::instance()->m_playlistCategory && isPlaylist( item ) ||
|
|
base == PlaylistBrowser::instance()->m_streamsCategory && isStream( item ) ||
|
|
base == PlaylistBrowser::instance()->m_smartCategory && isSmartPlaylist( item ) ||
|
|
base == PlaylistBrowser::instance()->m_dynamicCategory && isDynamic( item ) )
|
|
{
|
|
// if the item is from the cool streams dir, copy it.
|
|
if( item->parent() == PlaylistBrowser::instance()->m_coolStreams )
|
|
{
|
|
#define item static_cast<StreamEntry*>(item)
|
|
new StreamEntry( newParent, after, item->url(), item->title() );
|
|
#undef item
|
|
}
|
|
else // otherwise, we move it
|
|
{
|
|
itemParent->takeItem( item );
|
|
newParent->insertItem( item );
|
|
}
|
|
newParent->sortChildItems( 0, true );
|
|
}
|
|
else if( base == PlaylistBrowser::instance()->m_podcastCategory && isPodcastChannel( item ) )
|
|
{
|
|
#define item static_cast<PodcastChannel*>(item)
|
|
item->setParent( static_cast<PlaylistCategory*>(newParent) );
|
|
#undef item
|
|
}
|
|
}
|
|
}
|
|
|
|
void PlaylistBrowserView::rename( TQListViewItem *item, int c )
|
|
{
|
|
TDEListView::rename( item, c );
|
|
|
|
TQRect rect( itemRect( item ) );
|
|
int fieldX = rect.x() + treeStepSize() + 2;
|
|
int fieldW = rect.width() - treeStepSize() - 2;
|
|
|
|
KLineEdit *renameEdit = renameLineEdit();
|
|
renameEdit->setGeometry( fieldX, rect.y(), fieldW, rect.height() );
|
|
renameEdit->show();
|
|
}
|
|
|
|
void PlaylistBrowserView::keyPressEvent( TQKeyEvent *e )
|
|
{
|
|
switch( e->key() ) {
|
|
case Key_Enter:
|
|
case Key_Return:
|
|
{
|
|
if (e->state() & TQt::ShiftButton) {
|
|
// load and play
|
|
PlaylistBrowser::instance()->slotDoubleClicked( currentItem() );
|
|
}
|
|
else {
|
|
// load
|
|
TQListViewItem *item = currentItem();
|
|
if (item) {
|
|
Playlist::instance()->clear();
|
|
Playlist::instance()->setPlaylistName( item->text(0), true );
|
|
PlaylistBrowser::instance()->addSelectedToPlaylist( Playlist::Append );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case Key_Delete: // delete
|
|
if (e->state() & TQt::ShiftButton) {
|
|
PlaylistBrowser::instance()->removeSelectedItems();
|
|
break;
|
|
}
|
|
|
|
case Key_F2:
|
|
if (e->state() & TQt::ShiftButton) {
|
|
//rename
|
|
PlaylistBrowser::instance()->renameSelectedItem();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
TDEListView::keyPressEvent( e );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void PlaylistBrowserView::startDrag()
|
|
{
|
|
KURL::List urls;
|
|
KURL::List itemList; // this is for CollectionDB::createDragPixmap()
|
|
KURL::List podList; // used to add podcast episodes of the same channel in reverse order (usability)
|
|
PodcastEpisode *lastPodcastEpisode = 0; // keep track of the last podcastepisode we visited.
|
|
KMultipleDrag *drag = new KMultipleDrag( this );
|
|
|
|
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
|
|
TQString pixText = TQString();
|
|
uint count = 0;
|
|
|
|
for( ; it.current(); ++it )
|
|
{
|
|
if( !isPodcastEpisode( *it ) && !podList.isEmpty() )
|
|
{ // we left the podcast channel, so append those items we iterated over
|
|
urls += podList;
|
|
podList.clear();
|
|
}
|
|
|
|
if( isPlaylist( *it ) )
|
|
{
|
|
urls += static_cast<PlaylistEntry*>(*it)->url();
|
|
itemList += static_cast<PlaylistEntry*>(*it)->url();
|
|
pixText = (*it)->text(0);
|
|
}
|
|
|
|
else if( isStream( *it ) )
|
|
{
|
|
urls += static_cast<StreamEntry*>(*it)->url();
|
|
itemList += KURL::fromPathOrURL( "stream://" );
|
|
pixText = (*it)->text(0);
|
|
}
|
|
|
|
else if( isLastFm( *it ) )
|
|
{
|
|
urls += static_cast<LastFmEntry*>(*it)->url();
|
|
itemList += static_cast<LastFmEntry*>(*it)->url();
|
|
pixText = (*it)->text(0);
|
|
}
|
|
|
|
else if( isPodcastEpisode( *it ) )
|
|
{
|
|
if( (*it)->parent()->isSelected() ) continue;
|
|
if( !podList.isEmpty() && lastPodcastEpisode && lastPodcastEpisode->TQListViewItem::parent() != (*it)->parent() )
|
|
{ // we moved onto a new podcast channel
|
|
urls += podList;
|
|
podList.clear();
|
|
}
|
|
#define item static_cast<PodcastEpisode *>(*it)
|
|
if( item->isOnDisk() )
|
|
{
|
|
podList.prepend( item->localUrl() );
|
|
itemList += item->url();
|
|
}
|
|
else
|
|
{
|
|
podList.prepend( item->url() );
|
|
itemList += item->url();
|
|
}
|
|
lastPodcastEpisode = item;
|
|
pixText = (*it)->text(0);
|
|
#undef item
|
|
}
|
|
else if( isPodcastChannel( *it ) )
|
|
{
|
|
#define item static_cast<PodcastChannel *>(*it)
|
|
if( !item->isPolished() )
|
|
item->load();
|
|
|
|
TQListViewItem *child = item->firstChild();
|
|
KURL::List tmp;
|
|
// we add the podcasts in reverse, its much nicer to add them chronologically :)
|
|
while( child )
|
|
{
|
|
PodcastEpisode *pe = static_cast<PodcastEpisode*>( child );
|
|
if( pe->isOnDisk() )
|
|
tmp.prepend( pe->localUrl() );
|
|
else
|
|
tmp.prepend( pe->url() );
|
|
child = child->nextSibling();
|
|
}
|
|
urls += tmp;
|
|
itemList += KURL::fromPathOrURL( item->url().url() );
|
|
pixText = (*it)->text(0);
|
|
#undef item
|
|
}
|
|
|
|
else if( isSmartPlaylist( *it ) )
|
|
{
|
|
SmartPlaylist *item = static_cast<SmartPlaylist*>( *it );
|
|
|
|
if( !item->query().isEmpty() )
|
|
{
|
|
TQTextDrag *textdrag = new TQTextDrag( item->text(0) + '\n' + item->query(), 0 );
|
|
textdrag->setSubtype( "amarok-sql" );
|
|
drag->addDragObject( textdrag );
|
|
}
|
|
itemList += KURL::fromPathOrURL( TQString("smartplaylist://%1").arg( item->text(0) ) );
|
|
pixText = (*it)->text(0);
|
|
}
|
|
|
|
else if( isDynamic( *it ) )
|
|
{
|
|
DynamicEntry *item = static_cast<DynamicEntry*>( *it );
|
|
|
|
// Serialize pointer to string
|
|
const TQString str = TQString::number( reinterpret_cast<TQ_ULLONG>( item ) );
|
|
|
|
TQTextDrag *textdrag = new TQTextDrag( str, 0 );
|
|
textdrag->setSubtype( "dynamic" );
|
|
drag->addDragObject( textdrag );
|
|
itemList += KURL::fromPathOrURL( TQString("dynamic://%1").arg( item->text(0) ) );
|
|
pixText = (*it)->text(0);
|
|
}
|
|
|
|
else if( isPlaylistTrackItem( *it ) )
|
|
{
|
|
if( (*it)->parent()->isSelected() ) continue;
|
|
urls += static_cast<PlaylistTrackItem*>(*it)->url();
|
|
itemList += static_cast<PlaylistTrackItem*>(*it)->url();
|
|
}
|
|
count++;
|
|
}
|
|
|
|
if( !podList.isEmpty() )
|
|
urls += podList;
|
|
|
|
if( count > 1 ) pixText = TQString();
|
|
|
|
drag->addDragObject( new KURLDrag( urls, viewport() ) );
|
|
drag->setPixmap( CollectionDB::createDragPixmap( itemList, pixText ),
|
|
TQPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
|
|
drag->dragCopy();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CLASS PlaylistDialog
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
TQString PlaylistDialog::getSaveFileName( const TQString &suggestion, bool proposeOverwriting ) //static
|
|
{
|
|
PlaylistDialog dialog;
|
|
if( !suggestion.isEmpty() )
|
|
{
|
|
TQString path = Amarok::saveLocation("playlists/") + "%1" + ".m3u";
|
|
if( TQFileInfo( path.arg( suggestion ) ).exists() && !proposeOverwriting )
|
|
{
|
|
int n = 2;
|
|
while( TQFileInfo( path.arg( i18n( "%1 (%2)" ).arg( suggestion, TQString::number( n ) ) ) ).exists() )
|
|
n++;
|
|
dialog.edit->setText( i18n( "%1 (%2)" ).arg( suggestion, TQString::number( n ) ) );
|
|
}
|
|
else
|
|
dialog.edit->setText( suggestion );
|
|
}
|
|
if( dialog.exec() == Accepted )
|
|
return dialog.result;
|
|
return TQString();
|
|
}
|
|
|
|
PlaylistDialog::PlaylistDialog()
|
|
: KDialogBase( PlaylistWindow::self(), "saveplaylist", true /*modal*/,
|
|
i18n( "Save Playlist" ), Ok | Cancel | User1, Ok, false /*separator*/,
|
|
KGuiItem( i18n( "Save to location..." ), SmallIconSet( Amarok::icon( "files" ) ) ) )
|
|
, customChosen( false )
|
|
{
|
|
TQVBox *vbox = makeVBoxMainWidget();
|
|
TQLabel *label = new TQLabel( i18n( "&Enter a name for the playlist:" ), vbox );
|
|
edit = new KLineEdit( vbox );
|
|
edit->setFocus();
|
|
label->setBuddy( edit );
|
|
enableButtonOK( false );
|
|
connect( edit, TQT_SIGNAL( textChanged( const TQString & ) ),
|
|
this, TQT_SLOT( slotTextChanged( const TQString& ) ) );
|
|
connect( this, TQT_SIGNAL( user1Clicked() ), TQT_SLOT( slotCustomPath() ) );
|
|
}
|
|
|
|
void PlaylistDialog::slotOk()
|
|
{
|
|
// TODO Remove this hack for 1.2. It's needed because playlists was a file once.
|
|
TQString folder = Amarok::saveLocation( "playlists" );
|
|
TQFileInfo info( folder );
|
|
if ( !info.isDir() ) TQFile::remove( folder );
|
|
|
|
if( !customChosen && !edit->text().isEmpty() )
|
|
result = Amarok::saveLocation( "playlists/" ) + edit->text() + ".m3u";
|
|
|
|
if( !TQFileInfo( result ).exists() ||
|
|
KMessageBox::warningContinueCancel(
|
|
PlaylistWindow::self(),
|
|
i18n( "A playlist named \"%1\" already exists. Do you want to overwrite it?" ).arg( edit->text() ),
|
|
i18n( "Overwrite Playlist?" ), i18n( "Overwrite" ) ) == KMessageBox::Continue )
|
|
{
|
|
KDialogBase::slotOk();
|
|
}
|
|
}
|
|
|
|
void PlaylistDialog::slotTextChanged( const TQString &s )
|
|
{
|
|
enableButtonOK( !s.isEmpty() );
|
|
}
|
|
|
|
void PlaylistDialog::slotCustomPath()
|
|
{
|
|
result = KFileDialog::getSaveFileName( ":saveplaylists", "*.m3u" );
|
|
if( !result.isNull() )
|
|
{
|
|
edit->setText( result );
|
|
edit->setReadOnly( true );
|
|
enableButtonOK( true );
|
|
customChosen = true;
|
|
}
|
|
}
|
|
|
|
|
|
InfoPane::InfoPane( TQWidget *parent )
|
|
: TQVBox( parent ),
|
|
m_enable( false ),
|
|
m_storedHeight( 100 )
|
|
{
|
|
TQFrame *container = new TQVBox( this, "container" );
|
|
container->hide();
|
|
|
|
{
|
|
TQFrame *box = new TQHBox( container );
|
|
box->setMargin( 3 );
|
|
box->setBackgroundMode( TQt::PaletteBase );
|
|
|
|
m_infoBrowser = new HTMLView( box, "extended_info", false /*DNDEnabled*/, false /*JS
|
|
enabled*/ );
|
|
|
|
container->setFrameStyle( TQFrame::StyledPanel );
|
|
container->setMargin( 3 );
|
|
container->setBackgroundMode( TQt::PaletteBase );
|
|
}
|
|
|
|
m_pushButton = new KPushButton( KGuiItem( i18n("&Show Extended Info"), "info" ), this );
|
|
m_pushButton->setToggleButton( true );
|
|
m_pushButton->setEnabled( m_enable );
|
|
connect( m_pushButton, TQT_SIGNAL(toggled( bool )), TQT_SLOT(toggle( bool )) );
|
|
|
|
//Set the height to fixed. The button shouldn't be resized.
|
|
setFixedHeight( m_pushButton->sizeHint().height() );
|
|
}
|
|
|
|
InfoPane::~InfoPane()
|
|
{
|
|
// Ensure the TDEHTMLPart dies before its TDEHTMLView dies,
|
|
// because TDEHTMLPart's dtoring relies on its TDEHTMLView still being alive
|
|
// (see bug 130494).
|
|
delete m_infoBrowser;
|
|
}
|
|
|
|
int
|
|
InfoPane::getHeight()
|
|
{
|
|
if( TQT_TQWIDGET( child( "container" ) )->isShown() )
|
|
{
|
|
//If the InfoPane is shown, return true height.
|
|
return static_cast<TQSplitter*>( parentWidget() )->sizes().last();
|
|
}
|
|
|
|
return m_storedHeight;
|
|
}
|
|
|
|
void
|
|
InfoPane::setStoredHeight( const int newHeight ) {
|
|
m_storedHeight = newHeight;
|
|
}
|
|
|
|
void
|
|
InfoPane::toggle( bool toggled )
|
|
{
|
|
TQSplitter *splitter = static_cast<TQSplitter*>( parentWidget() );
|
|
|
|
if ( !toggled )
|
|
{
|
|
//Save the height for later
|
|
setStoredHeight( splitter->sizes().last() );
|
|
|
|
//Set the height to fixed. The button shouldn't be resized.
|
|
setFixedHeight( m_pushButton->sizeHint().height() );
|
|
|
|
//Now the info pane is not shown, we can disable the button if necessary
|
|
m_pushButton->setEnabled( m_enable );
|
|
}
|
|
else {
|
|
setMaximumHeight( ( int )( parentWidget()->height() / 1.5 ) );
|
|
|
|
//Restore the height of the InfoPane (change the splitter properties)
|
|
//Done every time since the pane forgets its height if you try to resize it while the info is hidden.
|
|
TQValueList<int> sizes = splitter->sizes();
|
|
const int sizeOffset = getHeight() - sizes.last();
|
|
sizes.first() -= sizeOffset;
|
|
sizes.last() += sizeOffset;
|
|
splitter->setSizes( sizes );
|
|
|
|
setMinimumHeight( 150 );
|
|
}
|
|
|
|
TQT_TQWIDGET( child( "container" ) )->setShown( toggled );
|
|
}
|
|
|
|
void
|
|
InfoPane::setInfo( const TQString &title, const TQString &info )
|
|
{
|
|
//If the info pane is not shown, we can enable or disable the button depending on
|
|
//whether there is content to show. Otherwise, just remember what we wanted to do
|
|
//so we can do it later, when the user does hide the pane.
|
|
m_enable = !( info.isEmpty() && title.isEmpty() );
|
|
if ( !TQT_TQWIDGET(child("container"))->isShown() )
|
|
m_pushButton->setEnabled( m_enable );
|
|
|
|
if( m_pushButton->isOn() )
|
|
toggle( !(info.isEmpty() && title.isEmpty()) );
|
|
|
|
TQString info_ = info;
|
|
info_.replace( "\n", "<br/>" );
|
|
|
|
m_infoBrowser->set(
|
|
m_enable ?
|
|
TQString( "<div id='extended_box' class='box'>"
|
|
"<div id='extended_box-header-title' class='box-header'>"
|
|
"<span id='extended_box-header-title' class='box-header-title'>"
|
|
" %1 "
|
|
"</span>"
|
|
"</div>"
|
|
"<table id='extended_box-table' class='box-body' width='100%' cellpadding='0' cellspacing='0'>"
|
|
"<tr>"
|
|
"<td id='extended_box-information-td'>"
|
|
" %2 "
|
|
"</td>"
|
|
"</tr>"
|
|
"</table>"
|
|
"</div>" ).arg( title, info_ ) :
|
|
TQString() );
|
|
}
|
|
|
|
#include "playlistbrowser.moc"
|