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

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"), "edit-clear", 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"