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/playlist.cpp

4982 lines
157 KiB

/* Copyright 2002-2004 Mark Kretschmann, Max Howell, Christian Muehlhaeuser
* Copyright 2005-2006 Seb Ruiz, Mike Diehl, Ian Monroe, Gábor Lehel, Alexandre Pereira de Oliveira
* Licensed as described in the COPYING file found in the root of this distribution
* Maintainer: Max Howell <max.howell@methylblue.com>
* NOTES
*
* The PlaylistWindow handles some Playlist events. Thanks!
* This class has a TQOBJECT but it's private so you can only connect via PlaylistWindow::PlaylistWindow
* Mostly it's sensible to implement playlist functionality in this class
* TODO Obtaining information about the playlist is currently hard, we need the playlist to be globally
* available and have some more useful public functions
*/
#define DEBUG_PREFIX "Playlist"
#include <config.h>
#include "amarok.h"
#include "amarokconfig.h"
#include "app.h"
#include "debug.h"
#include "collectiondb.h"
#include "collectionbrowser.h"
#include "columnlist.h"
#include "deletedialog.h"
#include "enginecontroller.h"
#include "expression.h"
#include "k3bexporter.h"
#include "metabundle.h"
#include "mountpointmanager.h"
#include "osd.h"
#include "playerwindow.h"
#include "playlistitem.h"
#include "playlistbrowser.h"
#include "playlistbrowseritem.h" //for stream editor dialog
#include "playlistloader.h"
#include "playlistselection.h"
#include "queuemanager.h"
#include "prettypopupmenu.h"
#include "scriptmanager.h"
#include "sliderwidget.h"
#include "starmanager.h"
#include "statusbar.h" //for status messages
#include "tagdialog.h"
#include "threadmanager.h"
#include "xspfplaylist.h"
#include <cmath> //for pow() in playNextTrack()
#include <tqbuffer.h>
#include <tqclipboard.h> //copyToClipboard(), slotMouseButtonPressed()
#include <tqcolor.h>
#include <tqevent.h>
#include <tqfile.h> //undo system
#include <tqheader.h> //eventFilter()
#include <tqlabel.h> //showUsageMessage()
#include <tqpainter.h>
#include <tqpen.h> //slotGlowTimer()
#include <tqsimplerichtext.h> //toolTipText()
#include <tqsortedlist.h>
#include <tqtimer.h>
#include <tqtooltip.h>
#include <tqvaluelist.h> //addHybridTracks()
#include <tqvaluevector.h> //playNextTrack()
#include <tqlayout.h>
#include <tdeaction.h>
#include <tdeapplication.h>
#include <kcursor.h> //setOverrideCursor()
#include <kdialogbase.h>
#include <tdeglobalsettings.h> //rename()
#include <kiconeffect.h>
#include <kiconloader.h> //slotShowContextMenu()
#include <tdeio/job.h> //deleteSelectedFiles()
#include <klineedit.h> //setCurrentTrack()
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdepopupmenu.h>
#include <krandomsequence.h> //random Mode
#include <kstandarddirs.h> //TDEGlobal::dirs()
#include <kstdaction.h>
#include <kstringhandler.h> //::showContextMenu()
#include <kurldrag.h>
#include <cstdlib> // abs
extern "C"
{
#if TDE_VERSION < TDE_MAKE_VERSION(3,3,91)
#include <X11/Xlib.h> //ControlMask in contentsDragMoveEvent()
#endif
}
#include "playlist.h"
namespace Amarok
{
const DynamicMode *dynamicMode() { return Playlist::instance() ? Playlist::instance()->dynamicMode() : 0; }
}
typedef PlaylistIterator MyIt;
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS TagWriter : Threaded tag-updating
//////////////////////////////////////////////////////////////////////////////////////////
class TagWriter : public ThreadManager::Job
{ //TODO make this do all tags at once when you split playlist.cpp up
public:
TagWriter( PlaylistItem*, const TQString &oldTag, const TQString &newTag, const int, const bool updateView = true );
~TagWriter();
bool doJob();
void completeJob();
private:
PlaylistItem* const m_item;
bool m_failed;
TQString m_oldTagString;
TQString m_newTagString;
int m_tagType;
bool m_updateView;
};
//////////////////////////////////////////////////////////////////////////////////////////
/// Glow
//////////////////////////////////////////////////////////////////////////////////////////
namespace Glow
{
namespace Text
{
static float dr, dg, db;
static int r, g, b;
}
namespace Base
{
static float dr, dg, db;
static int r, g, b;
}
static const uint STEPS = 13;
static uint counter;
static TQTimer timer;
inline void startTimer()
{
counter = 0;
timer.start( 40 );
}
inline void reset()
{
counter = 0;
timer.stop();
}
}
//////////////////////////////////////////////////////////////////////////////////////////
/// CLASS Playlist
//////////////////////////////////////////////////////////////////////////////////////////
TQMutex* Playlist::s_dynamicADTMutex = new TQMutex();
Playlist *Playlist::s_instance = 0;
Playlist::Playlist( TQWidget *parent )
: TDEListView( parent, "ThePlaylist" )
, EngineObserver( EngineController::instance() )
, m_startupTime_t( TQDateTime::currentDateTime().toTime_t() )
, m_oldestTime_t( CollectionDB::instance()->query( "SELECT MIN( createdate ) FROM statistics;" ).first().toInt() )
, m_currentTrack( 0 )
, m_marker( 0 )
, m_hoveredRating( 0 )
, m_firstColumn( 0 )
, m_totalCount( 0 )
, m_totalLength( 0 )
, m_selCount( 0 )
, m_selLength( 0 )
, m_visCount( 0 )
, m_visLength( 0 )
, m_total( 0 )
, m_itemCountDirty( false )
, m_undoButton( 0 )
, m_redoButton( 0 )
, m_clearButton( 0 )
, m_undoDir( Amarok::saveLocation( "undo/" ) )
, m_undoCounter( 0 )
, m_dynamicMode( 0 )
, m_stopAfterTrack( 0 )
, m_stopAfterMode( DoNotStop )
, m_showHelp( true )
, m_dynamicDirt( false )
, m_queueDirt( false )
, m_undoDirt( false )
, m_insertFromADT( 0 )
, m_itemToReallyCenter( 0 )
, m_renameItem( 0 )
, m_lockStack( 0 )
, m_columnFraction( PlaylistItem::NUM_COLUMNS, 0 )
, m_oldRandom( 0 )
, m_oldRepeat( 0 )
, m_playlistName( i18n( "Untitled" ) )
, m_proposeOverwriting( false )
, m_urlIndex( &PlaylistItem::url )
{
s_instance = this;
connect( CollectionDB::instance(), TQT_SIGNAL(fileMoved(const TQString&,
const TQString&, const TQString&)), TQT_SLOT(updateEntriesUrl(const TQString&,
const TQString&, const TQString&)) );
connect( CollectionDB::instance(), TQT_SIGNAL(uniqueIdChanged(const TQString&,
const TQString&, const TQString&)), TQT_SLOT(updateEntriesUniqueId(const TQString&,
const TQString&, const TQString&)) );
connect( CollectionDB::instance(), TQT_SIGNAL(fileDeleted(const TQString&,
const TQString&)), TQT_SLOT(updateEntriesStatusDeleted(const TQString&, const TQString&)) );
connect( CollectionDB::instance(), TQT_SIGNAL(fileAdded(const TQString&,
const TQString&)), TQT_SLOT(updateEntriesStatusAdded(const TQString&, const TQString&)) );
connect( CollectionDB::instance(), TQT_SIGNAL(filesAdded(const TQMap<TQString,TQString>&)),
TQT_SLOT(updateEntriesStatusAdded(const TQMap<TQString,TQString>&)) );
initStarPixmaps();
EngineController* const ec = EngineController::instance();
connect( ec, TQT_SIGNAL(orderPrevious()), TQT_SLOT(playPrevTrack()) );
connect( ec, TQT_SIGNAL(orderNext( const bool )), TQT_SLOT(playNextTrack( const bool )) );
connect( ec, TQT_SIGNAL(orderCurrent()), TQT_SLOT(playCurrentTrack()) );
connect( this, TQT_SIGNAL( itemCountChanged( int, int, int, int, int, int ) ), ec, TQT_SLOT( playlistChanged() ) );
setShowSortIndicator( true );
setDropVisualizer( false ); //we handle the drawing for ourselves
setDropVisualizerWidth( 3 );
// FIXME: This doesn't work, and steals focus when an item is clicked twice.
//setItemsRenameable( true );
setAcceptDrops( true );
setSelectionMode( TQListView::Extended );
setAllColumnsShowFocus( true );
//setItemMargin( 1 ); //aesthetics
setMouseTracking( true );
#if KDE_IS_VERSION( 3, 3, 91 )
setShadeSortColumn( true );
#endif
for( int i = 0; i < MetaBundle::NUM_COLUMNS; ++i )
{
addColumn( PlaylistItem::prettyColumnName( i ), 0 );
switch( i )
{
case PlaylistItem::Title:
case PlaylistItem::Artist:
case PlaylistItem::Composer:
case PlaylistItem::Year:
case PlaylistItem::Album:
case PlaylistItem::DiscNumber:
case PlaylistItem::Track:
case PlaylistItem::Bpm:
case PlaylistItem::Genre:
case PlaylistItem::Comment:
case PlaylistItem::Score:
case PlaylistItem::Rating:
setRenameable( i, true );
continue;
default:
setRenameable( i, false );
}
}
setColumnWidth( PlaylistItem::Title, 200 );
setColumnWidth( PlaylistItem::Artist, 100 );
setColumnWidth( PlaylistItem::Album, 100 );
setColumnWidth( PlaylistItem::Length, 80 );
if( AmarokConfig::showMoodbar() )
setColumnWidth( PlaylistItem::Mood, 120 );
if( AmarokConfig::useRatings() )
setColumnWidth( PlaylistItem::Rating, PlaylistItem::ratingColumnWidth() );
setColumnAlignment( PlaylistItem::Length, TQt::AlignRight );
setColumnAlignment( PlaylistItem::Track, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::DiscNumber, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::Bpm, TQt::AlignRight );
setColumnAlignment( PlaylistItem::Year, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::Bitrate, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::SampleRate, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::Filesize, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::Score, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::Type, TQt::AlignCenter );
setColumnAlignment( PlaylistItem::PlayCount, TQt::AlignCenter );
connect( this, TQT_SIGNAL( doubleClicked( TQListViewItem* ) ),
this, TQT_SLOT( doubleClicked( TQListViewItem* ) ) );
connect( this, TQT_SIGNAL( returnPressed( TQListViewItem* ) ),
this, TQT_SLOT( activate( TQListViewItem* ) ) );
connect( this, TQT_SIGNAL( mouseButtonPressed( int, TQListViewItem*, const TQPoint&, int ) ),
this, TQT_SLOT( slotMouseButtonPressed( int, TQListViewItem*, const TQPoint&, int ) ) );
connect( this, TQT_SIGNAL( queueChanged( const PLItemList &, const PLItemList & ) ),
this, TQT_SLOT( slotQueueChanged( const PLItemList &, const PLItemList & ) ) );
connect( this, TQT_SIGNAL( itemRenamed( TQListViewItem*, const TQString&, int ) ),
this, TQT_SLOT( writeTag( TQListViewItem*, const TQString&, int ) ) );
connect( this, TQT_SIGNAL( aboutToClear() ),
this, TQT_SLOT( saveUndoState() ) );
connect( CollectionDB::instance(), TQT_SIGNAL( scoreChanged( const TQString&, float ) ),
this, TQT_SLOT( scoreChanged( const TQString&, float ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( ratingChanged( const TQString&, int ) ),
this, TQT_SLOT( ratingChanged( const TQString&, int ) ) );
connect( CollectionDB::instance(), TQT_SIGNAL( fileMoved( const TQString&, const TQString& ) ),
this, TQT_SLOT( fileMoved( const TQString&, const TQString& ) ) );
connect( header(), TQT_SIGNAL( indexChange( int, int, int ) ),
this, TQT_SLOT( columnOrderChanged() ) ),
connect( &Glow::timer, TQT_SIGNAL(timeout()), TQT_SLOT(slotGlowTimer()) );
TDEActionCollection* const ac = Amarok::actionCollection();
TDEAction *copy = KStdAction::copy( TQT_TQOBJECT(this), TQT_SLOT( copyToClipboard() ), ac, "playlist_copy" );
KStdAction::selectAll( TQT_TQOBJECT(this), TQT_SLOT( selectAll() ), ac, "playlist_select_all" );
m_clearButton = new TDEAction( i18n( "clear playlist", "&Clear" ), Amarok::icon( "playlist_clear" ), 0, TQT_TQOBJECT(this), TQT_SLOT( clear() ), ac, "playlist_clear" );
m_undoButton = KStdAction::undo( TQT_TQOBJECT(this), TQT_SLOT( undo() ), ac, "playlist_undo" );
m_redoButton = KStdAction::redo( TQT_TQOBJECT(this), TQT_SLOT( redo() ), ac, "playlist_redo" );
m_undoButton ->setIcon( Amarok::icon( "undo" ) );
m_redoButton ->setIcon( Amarok::icon( "redo" ) );
new TDEAction( i18n( "&Repopulate" ), Amarok::icon( "playlist_refresh" ), 0, TQT_TQOBJECT(this), TQT_SLOT( repopulate() ), ac, "repopulate" );
new TDEAction( i18n( "S&huffle" ), "rebuild", CTRL+Key_H, TQT_TQOBJECT(this), TQT_SLOT( shuffle() ), ac, "playlist_shuffle" );
TDEAction *gotoCurrent = new TDEAction( i18n( "&Go To Current Track" ), Amarok::icon( "music" ), CTRL+Key_J, TQT_TQOBJECT(this), TQT_SLOT( showCurrentTrack() ), ac, "playlist_show" );
new TDEAction( i18n( "&Remove Duplicate && Dead Entries" ), 0, TQT_TQOBJECT(this), TQT_SLOT( removeDuplicates() ), ac, "playlist_remove_duplicates" );
new TDEAction( i18n( "&Queue Selected Tracks" ), Amarok::icon( "queue_track" ), CTRL+Key_D, TQT_TQOBJECT(this), TQT_SLOT( queueSelected() ), ac, "queue_selected" );
TDEToggleAction *stopafter = new TDEToggleAction( i18n( "&Stop Playing After Track" ), Amarok::icon( "stop" ), CTRL+ALT+Key_V,
TQT_TQOBJECT(this), TQT_SLOT( toggleStopAfterCurrentItem() ), ac, "stop_after" );
{ // TDEAction idiocy -- shortcuts don't work until they've been plugged into a menu
TDEPopupMenu asdf;
copy->plug( &asdf );
stopafter->plug( &asdf );
gotoCurrent->plug( &asdf );
copy->unplug( &asdf );
stopafter->unplug( &asdf );
gotoCurrent->unplug( &asdf );
}
//ensure we update action enabled states when repeat Playlist is toggled
connect( ac->action( "repeat" ), TQT_SIGNAL(activated( int )), TQT_SLOT(updateNextPrev()) );
connect( ac->action( "repeat" ), TQT_SIGNAL( activated( int ) ), TQT_SLOT( generateInfo() ) );
connect( ac->action( "favor_tracks" ), TQT_SIGNAL( activated( int ) ), TQT_SLOT( generateInfo() ) );
connect( ac->action( "random_mode" ), TQT_SIGNAL( activated( int ) ), TQT_SLOT( generateInfo() ) );
// undostates are written in chronological order, so this is a clever way to get them back in the correct order :)
TQStringList undos = m_undoDir.entryList( TQString("*.xml"), TQDir::Files, TQDir::Time );
foreach( undos )
m_undoList.append( m_undoDir.absPath() + '/' + (*it) );
m_undoCounter = m_undoList.count();
m_undoButton->setEnabled( !m_undoList.isEmpty() );
m_redoButton->setEnabled( false );
engineStateChanged( EngineController::engine()->state() ); //initialise state of UI
paletteChange( palette() ); //sets up glowColors
restoreLayout( TDEGlobal::config(), "PlaylistColumnsLayout" );
// Sorting must be disabled when current.xml is being loaded. See BUG 113042
TDEListView::setSorting( NO_SORT ); //use base so we don't saveUndoState() too
setDynamicMode( 0 );
m_smartResizing = Amarok::config( "PlaylistWindow" )->readBoolEntry( "Smart Resizing", true );
columnOrderChanged();
//cause the column fractions to be updated, but in a safe way, ie no specific column
columnResizeEvent( header()->count(), 0, 0 );
//do after you resize all the columns
connect( header(), TQT_SIGNAL(sizeChange( int, int, int )), TQT_SLOT(columnResizeEvent( int, int, int )) );
connect( this, TQT_SIGNAL( contentsMoving( int, int ) ), TQT_SLOT( slotContentsMoving() ) );
connect( App::instance(), TQT_SIGNAL( useScores( bool ) ), this, TQT_SLOT( slotUseScores( bool ) ) );
connect( App::instance(), TQT_SIGNAL( useRatings( bool ) ), this, TQT_SLOT( slotUseRatings( bool ) ) );
connect( App::instance(), TQT_SIGNAL( moodbarPrefs( bool, bool, int, bool ) ),
this, TQT_SLOT( slotMoodbarPrefs( bool, bool, int, bool ) ) );
Amarok::ToolTip::add( this, viewport() );
header()->installEventFilter( this );
renameLineEdit()->installEventFilter( this );
setTabOrderedRenaming( false );
m_filtertimer = new TQTimer( this );
connect( m_filtertimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(setDelayedFilter()) );
connect( MountPointManager::instance(), TQT_SIGNAL(mediumConnected( int )),
TQT_SLOT(mediumChange( int )) );
connect( MountPointManager::instance(), TQT_SIGNAL(mediumRemoved( int )),
TQT_SLOT(mediumChange( int )) );
m_clicktimer = new TQTimer( this );
connect( m_clicktimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotSingleClick()) );
}
Playlist::~Playlist()
{
saveLayout( TDEGlobal::config(), "PlaylistColumnsLayout" );
if( AmarokConfig::savePlaylist() && m_lockStack == 0 ) saveXML( defaultPlaylistPath() );
//speed up quit a little
safeClear(); //our implementation is slow
Amarok::ToolTip::remove( viewport() );
blockSignals( true ); //might help
s_instance = 0;
}
////////////////////////////////////////////////////////////////////////////////
/// Media Handling
////////////////////////////////////////////////////////////////////////////////
void
Playlist::mediumChange( int deviceid ) // SLOT
{
Q_UNUSED( deviceid );
for( TQListViewItem *it = firstChild();
it;
it = it->nextSibling() )
{
PlaylistItem *p = dynamic_cast<PlaylistItem *>( it );
if( p )
{
bool exist = p->exists();
if( exist != p->checkExists() )
{
p->setFilestatusEnabled( p->checkExists() );
p->update();
}
}
}
}
void
Playlist::insertMedia( const KURL::List &list, int options )
{
if( list.isEmpty() ) {
Amarok::StatusBar::instance()->shortMessage( i18n("Attempted to insert nothing into playlist.") );
return; // don't add empty items
}
const bool isPlaying = EngineController::engine()->state() == Engine::Playing;
if( isPlaying )
options &= ~Playlist::StartPlay;
bool directPlay = options & (Playlist::DirectPlay | Playlist::StartPlay);
if( options & Replace )
clear();
else
options |= Playlist::Colorize;
PlaylistItem *after = lastItem();
KURL::List addMe;
TQPtrList<PlaylistItem> alreadyHave;
// Filter out duplicates
foreachType( KURL::List, list ) {
PlaylistItem *item = m_urlIndex.getFirst( *it );
if ( item )
alreadyHave.append( item );
else
addMe.append( *it );
}
if( options & Queue )
{
if ( addMe.isEmpty() ) // all songs to be queued are already in the playlist
{
// queue all the songs
foreachType( TQPtrList<PlaylistItem>, alreadyHave )
queue( *it, false, false );
return;
} else {
// We add the track after the last track on queue, or after current if the queue is empty
after = m_nextTracks.isEmpty() ? currentTrack() : m_nextTracks.getLast();
// If there's no tracks on the queue, and there's no current track, fall back to the last item
if ( !after )
after = lastItem();
}
}
else if( options & Unique ) {
int alreadyOnPlaylist = alreadyHave.count();
if ( alreadyOnPlaylist )
{
if (directPlay) activate( alreadyHave.getFirst() );
Amarok::StatusBar::instance()->shortMessage(
i18n("One track was already in the playlist, so it was not added.",
"%n tracks were already in the playlist, so they were not added.",
alreadyOnPlaylist ) );
}
}
if( options & Unique || options & Queue )
insertMediaInternal( addMe, after, options );
else
insertMediaInternal( list, after, options );
}
void
Playlist::insertMediaInternal( const KURL::List &list, PlaylistItem *after, int options )
{
if ( !list.isEmpty() ) {
setSorting( NO_SORT );
// prevent association with something that is about to be deleted
// TODO improve the playlist with a list of items that are volatile or something
while( after && after->url().isEmpty() )
after = static_cast<PlaylistItem*>( after->itemAbove() );
ThreadManager::instance()->queueJob( new UrlLoader( list, after, options ) );
ScriptManager::instance()->notifyPlaylistChange("changed");
}
}
void
Playlist::insertMediaSql( const TQString& sql, int options )
{
const bool isPlaying = EngineController::engine()->state() == Engine::Playing;
if( isPlaying )
options &= ~Playlist::StartPlay;
// TODO Implement more options
PlaylistItem *after = 0;
if ( options & Replace )
clear();
if ( options & Append )
after = lastItem();
setSorting( NO_SORT );
ThreadManager::instance()->queueJob( new SqlLoader( sql, after, options ) );
ScriptManager::instance()->notifyPlaylistChange("changed");
}
void
Playlist::addDynamicModeTracks( uint songCount )
{
if( songCount < 1 ) return;
int currentPos = 0;
for( MyIt it( this, MyIt::Visible ); *it; ++it )
{
if( m_currentTrack && *it == m_currentTrack )
break;
else if( !m_currentTrack && (*it)->isDynamicEnabled() )
break;
++currentPos;
}
currentPos++;
int required = currentPos + dynamicMode()->upcomingCount(); // currentPos handles currentTrack
int remainder = totalTrackCount();
if( required > remainder )
songCount = required - remainder;
DynamicMode *m = modifyDynamicMode();
KURL::List tracksToInsert = m->retrieveTracks( songCount );
Playlist::instance()->finishedModifying( m );
insertMedia( tracksToInsert, Playlist::Unique );
}
/**
* @param songCount : Number of tracks to be shown after the current track
*/
void
Playlist::adjustDynamicUpcoming( bool saveUndo )
{
/**
* If m_currentTrack exists, we iterate until we find it
* Else, we iterate until we find an item which is enabled
**/
MyIt it( this, MyIt::Visible ); //Notice we'll use this up to the end of the function!
//Skip previously played
for( ; *it; ++it )
{
if( m_currentTrack && *it == m_currentTrack )
break;
else if( !m_currentTrack && (*it)->isDynamicEnabled() )
break;
}
//Skip current
if( m_currentTrack )
++it;
int x = 0;
for ( ; *it && x < dynamicMode()->upcomingCount() ; ++it, ++x );
if ( x < dynamicMode()->upcomingCount() )
{
addDynamicModeTracks( dynamicMode()->upcomingCount() - x );
ScriptManager::instance()->notifyPlaylistChange("changed");
}
if( saveUndo )
saveUndoState();
}
/**
* @param songCount : Number of tracks to be shown before the current track
*/
void
Playlist::adjustDynamicPrevious( uint songCount, bool saveUndo )
{
int current = currentTrackIndex();
int x = current - songCount;
TQPtrList<TQListViewItem> list;
int y=0;
for( TQListViewItemIterator it( firstChild() ); y < x ; list.prepend( *it ), ++it, y++ );
if( list.isEmpty() ) return;
if ( saveUndo )
saveUndoState();
//remove the items
for( TQListViewItem *item = list.first(); item; item = list.next() )
{
removeItem( static_cast<PlaylistItem*>( item ) );
delete item;
}
ScriptManager::instance()->notifyPlaylistChange("changed");
}
void
Playlist::setDynamicHistory( bool enable /*false*/ )
{
if( !m_currentTrack )
return;
for( PlaylistIterator it( this, PlaylistIterator::All ) ; *it ; ++it )
{
if( *it == m_currentTrack ) break;
//avoid repainting if we can.
if( (*it)->isDynamicEnabled() == enable )
{
(*it)->setDynamicEnabled( !enable );
(*it)->update();
}
}
}
TQString
Playlist::defaultPlaylistPath() //static
{
return Amarok::saveLocation() + "current.xml";
}
void
Playlist::restoreSession()
{
KURL url;
if ( Amarok::config()->readBoolEntry( "First 1.4 Run", true ) ) {
// On first startup of 1.4, we load a special playlist with an intro track
url.setPath( locate( "data", "amarok/data/firstrun.m3u" ) );
Amarok::config()->writeEntry( "First 1.4 Run", false );
}
else
url.setPath( Amarok::saveLocation() + "current.xml" );
// check it exists, because on the first ever run it doesn't and
// it looks bad to show "some URLs were not suitable.." on the
// first ever-run
if( TQFile::exists( url.path() ) )
{
ThreadManager::instance()->queueJob( new UrlLoader( url, 0, 0 ) );
}
}
/*
The following two functions (saveLayout(), restoreLayout()), taken from tdelistview.cpp, are largely
Copyright (C) 2000 Reginald Stadlbauer <reggie@kde.org>
Copyright (C) 2000,2003 Charles Samuels <charles@kde.org>
Copyright (C) 2000 Peter Putzer
*/
void Playlist::saveLayout(TDEConfig *config, const TQString &group) const
{
TDEConfigGroupSaver saver(config, group);
TQStringList names, widths, order;
const int colCount = columns();
TQHeader* const thisHeader = header();
for (int i = 0; i < colCount; ++i)
{
names << PlaylistItem::exactColumnName(i);
widths << TQString::number(columnWidth(i));
order << TQString::number(thisHeader->mapToIndex(i));
}
config->writeEntry("ColumnsVersion", 1);
config->writeEntry("ColumnNames", names);
config->writeEntry("ColumnWidths", widths);
config->writeEntry("ColumnOrder", order);
config->writeEntry("SortColumn", columnSorted());
config->writeEntry("SortAscending", ascendingSort());
}
void Playlist::restoreLayout(TDEConfig *config, const TQString &group)
{
TDEConfigGroupSaver saver(config, group);
int version = config->readNumEntry("ColumnsVersion", 0);
TQValueList<int> iorder; //internal ordering
if( version )
{
TQStringList names = config->readListEntry("ColumnNames");
for( int i = 0, n = names.count(); i < n; ++i )
{
bool found = false;
for( int ii = i; ii < PlaylistItem::NUM_COLUMNS; ++ii ) //most likely, it's where we left it
{
if( names[i] == PlaylistItem::exactColumnName(ii) )
{
iorder.append(ii);
found = true;
break;
}
}
if( !found )
{
for( int ii = 0; ii < i; ++ii ) //but maybe it's not
if( names[i] == PlaylistItem::exactColumnName(ii) )
{
iorder.append(ii);
found = true;
break;
}
}
if( !found )
return; //oops? -- revert to the default.
}
}
else
{
int oldorder[] = { 0, 1, 2, 5, 4, 9, 8, 7, 10, 12, 13, 15, 16, 11, 17, 18, 19, 3, 6, 20 };
for( int i = 0; i != 20; ++i )
iorder.append(oldorder[i]);
}
TQStringList cols = config->readListEntry("ColumnWidths");
int i = 0;
{ // scope the iterators
TQStringList::ConstIterator it = cols.constBegin();
const TQStringList::ConstIterator itEnd = cols.constEnd();
for (; it != itEnd; ++it)
setColumnWidth(iorder[i++], (*it).toInt());
}
// move sections in the correct sequence: from lowest to highest index position
// otherwise we move a section from an index, which modifies
// all index numbers to the right of the moved one
cols = config->readListEntry("ColumnOrder");
const int colCount = columns();
for (i = 0; i < colCount; ++i) // final index positions from lowest to highest
{
TQStringList::ConstIterator it = cols.constBegin();
const TQStringList::ConstIterator itEnd = cols.constEnd();
int section = 0;
for (; (it != itEnd) && (iorder[(*it).toInt()] != i); ++it, ++section) ;
if ( it != itEnd ) {
// found the section to move to position i
header()->moveSection(iorder[section], i);
}
}
if ( config->hasKey("SortColumn") )
{
const int sort = config->readNumEntry("SortColumn");
if( sort >= 0 && uint(sort) < iorder.count() )
setSorting(iorder[config->readNumEntry("SortColumn")], config->readBoolEntry("SortAscending", true));
}
if( !AmarokConfig::useScores() )
hideColumn( PlaylistItem::Score );
if( !AmarokConfig::useRatings() )
hideColumn( PlaylistItem::Rating );
if( !AmarokConfig::showMoodbar() )
hideColumn( PlaylistItem::Mood );
}
void
Playlist::addToUniqueMap( const TQString uniqueid, PlaylistItem* item )
{
TQPtrList<PlaylistItem> *list;
if( m_uniqueMap.contains( uniqueid ) )
list = m_uniqueMap[uniqueid];
else
list = new TQPtrList<PlaylistItem>();
list->append( item );
if( !m_uniqueMap.contains( uniqueid ) )
m_uniqueMap[uniqueid] = list;
}
void
Playlist::removeFromUniqueMap( const TQString uniqueid, PlaylistItem* item )
{
if( !m_uniqueMap.contains( uniqueid ) )
return;
TQPtrList<PlaylistItem> *list;
list = m_uniqueMap[uniqueid];
list->remove( item ); //don't care about return value
if( list->isEmpty() )
{
delete list;
m_uniqueMap.remove( uniqueid );
}
}
void
Playlist::updateEntriesUrl( const TQString &oldUrl, const TQString &newUrl, const TQString &uniqueid )
{
// Make sure the MoodServer gets this signal first!
MoodServer::instance()->slotFileMoved( oldUrl, newUrl );
TQPtrList<PlaylistItem> *list;
if( m_uniqueMap.contains( uniqueid ) )
{
list = m_uniqueMap[uniqueid];
PlaylistItem *item;
for( item = list->first(); item; item = list->next() )
{
item->setUrl( KURL( newUrl ) );
item->setFilestatusEnabled( item->checkExists() );
}
}
}
void
Playlist::updateEntriesUniqueId( const TQString &/*url*/, const TQString &oldid, const TQString &newid )
{
TQPtrList<PlaylistItem> *list, *oldlist;
if( m_uniqueMap.contains( oldid ) )
{
list = m_uniqueMap[oldid];
m_uniqueMap.remove( oldid );
PlaylistItem *item;
for( item = list->first(); item; item = list->next() )
{
item->setUniqueId( newid );
item->readTags();
}
if( !m_uniqueMap.contains( newid ) )
m_uniqueMap[newid] = list;
else
{
oldlist = m_uniqueMap[newid];
for( item = list->first(); item; item = list->next() )
oldlist->append( item );
delete list;
}
}
}
void
Playlist::updateEntriesStatusDeleted( const TQString &/*absPath*/, const TQString &uniqueid )
{
TQPtrList<PlaylistItem> *list;
if( m_uniqueMap.contains( uniqueid ) )
{
list = m_uniqueMap[uniqueid];
PlaylistItem *item;
for( item = list->first(); item; item = list->next() )
item->setFilestatusEnabled( false );
}
}
void
Playlist::updateEntriesStatusAdded( const TQString &absPath, const TQString &uniqueid )
{
TQPtrList<PlaylistItem> *list;
if( m_uniqueMap.contains( uniqueid ) )
{
list = m_uniqueMap[uniqueid];
if( !list )
return;
PlaylistItem *item;
for( item = list->first(); item; item = list->next() )
{
if( absPath != item->url().path() )
item->setPath( absPath ); //in case the UID was the same, but the path has changed
item->setFilestatusEnabled( true );
}
}
}
void
Playlist::updateEntriesStatusAdded( const TQMap<TQString,TQString> &map )
{
TQMap<TQString,TQPtrList<PlaylistItem>*> uniquecopy( m_uniqueMap );
TQMap<TQString,TQPtrList<PlaylistItem>*>::Iterator it;
for( it = uniquecopy.begin(); it != uniquecopy.end(); ++it )
{
if( map.contains( it.key() ))
{
updateEntriesStatusAdded( map[it.key()], it.key() );
uniquecopy.remove( it );
}
}
for( it = uniquecopy.begin(); it != uniquecopy.end(); ++it )
updateEntriesStatusDeleted( TQString(), it.key() );
}
////////////////////////////////////////////////////////////////////////////////
/// Current Track Handling
////////////////////////////////////////////////////////////////////////////////
void
Playlist::playNextTrack( bool forceNext )
{
PlaylistItem *item = currentTrack();
if( !m_visCount || stopAfterMode() == StopAfterCurrent )
{
if( dynamicMode() && m_visCount )
{
item->setDynamicEnabled( false );
advanceDynamicTrack();
m_dynamicDirt = false;
}
EngineController::instance()->stop();
setStopAfterMode( DoNotStop );
if( !AmarokConfig::randomMode() ) {
item = MyIt::nextVisible( item );
while( item && ( !checkFileStatus( item ) || !item->exists() ) )
item = MyIt::nextVisible( item );
setCurrentTrack( item );
}
return;
}
if( !Amarok::repeatTrack() || forceNext )
{
if( !m_nextTracks.isEmpty() )
{
item = m_nextTracks.first();
m_nextTracks.remove();
if ( dynamicMode() )
// move queued track to the top of the playlist, to prevent it from being played twice
// this is done automatically by most queue changing functions, but not if the user manually moves the track
moveItem( item, 0, m_currentTrack );
emit queueChanged( PLItemList(), PLItemList( item ) );
}
else if( Amarok::entireAlbums() && m_currentTrack && m_currentTrack->nextInAlbum() )
item = m_currentTrack->nextInAlbum();
else if( Amarok::repeatAlbum() &&
repeatAlbumTrackCount() && ( repeatAlbumTrackCount() > 1 || !forceNext ) )
item = m_currentTrack->m_album->tracks.getFirst();
else if( AmarokConfig::randomMode() )
{
TQValueVector<PlaylistItem*> tracks;
//make a list of everything we can play
if( Amarok::randomAlbums() ) // add the first visible track from every unplayed album
{
for( ArtistAlbumMap::const_iterator it = m_albums.constBegin(), end = m_albums.constEnd(); it != end; ++it )
for( AlbumMap::const_iterator it2 = (*it).constBegin(), end2 = (*it).constEnd(); it2 != end2; ++it2 )
if( m_prevAlbums.findRef( *it2 ) == -1 ) {
if ( (*it2)->tracks.getFirst() )
tracks.append( (*it2)->tracks.getFirst() );
}
}
else
for( MyIt it( this ); *it; ++it )
if ( !m_prevTracks.containsRef( *it ) && checkFileStatus( *it ) && (*it)->exists() )
tracks.push_back( *it );
if( tracks.isEmpty() )
{
//we have played everything
item = 0;
if( Amarok::randomAlbums() )
{
if ( m_prevAlbums.count() <= 8 ) {
m_prevAlbums.first();
while( m_prevAlbums.count() )
removeFromPreviousAlbums();
if( m_currentTrack )
{
// don't add it to previous albums if we only have one album in the playlist
// would loop infinitely otherwise
TQPtrList<PlaylistAlbum> albums;
for( PlaylistIterator it( this, PlaylistIterator::Visible ); *it && albums.count() <= 1; ++it )
if( albums.findRef( (*it)->m_album ) == -1 )
albums.append( (*it)->m_album );
if ( albums.count() > 1 )
appendToPreviousAlbums( m_currentTrack->m_album );
}
}
else {
m_prevAlbums.first(); //set's current item to first item
//keep 80 tracks in the previous list so item time user pushes play
//we don't risk playing anything too recent
while( m_prevAlbums.count() > 8 )
removeFromPreviousAlbums(); //removes current item
}
}
else
{
if ( m_prevTracks.count() <= 80 ) {
m_prevTracks.first();
while( m_prevTracks.count() )
removeFromPreviousTracks();
if( m_currentTrack )
{
// don't add it to previous tracks if we only have one file in the playlist
// would loop infinitely otherwise
int count = 0;
for( PlaylistIterator it( this, PlaylistIterator::Visible ); *it && count <= 1; ++it )
++count;
if ( count > 1 )
appendToPreviousTracks( m_currentTrack );
}
}
else {
m_prevTracks.first(); //set's current item to first item
//keep 80 tracks in the previous list so item time user pushes play
//we don't risk playing anything too recent
while( m_prevTracks.count() > 80 )
removeFromPreviousTracks(); //removes current item
}
}
if( Amarok::repeatPlaylist() )
{
playNextTrack();
return;
}
//else we stop via activate( 0 ) below
}
else
{
if( Amarok::favorNone() )
item = tracks.at( TDEApplication::random() % tracks.count() ); //is O(1)
else
{
const uint currenttime_t = TQDateTime::currentDateTime().toTime_t();
TQValueVector<int> weights( tracks.size() );
TQ_INT64 total = m_total;
if( Amarok::randomAlbums() )
{
for( int i = 0, n = tracks.count(); i < n; ++i )
{
weights[i] = tracks.at( i )->m_album->total;
if( Amarok::favorLastPlay() )
{
const int inc = int( float( ( currenttime_t - m_startupTime_t )
* tracks.at( i )->m_album->tracks.count() + 0.5 )
/ tracks.at( i )->m_album->tracks.count() );
weights[i] += inc;
total += inc;
}
}
}
else
{
for( int i = 0, n = tracks.count(); i < n; ++i )
{
weights[i] = tracks.at( i )->totalIncrementAmount();
if( Amarok::favorLastPlay() )
weights[i] += currenttime_t - m_startupTime_t;
}
if( Amarok::favorLastPlay() )
total += ( currenttime_t - m_startupTime_t ) * weights.count();
}
TQ_INT64 random;
if( Amarok::favorLastPlay() ) //really big huge numbers
{
TQ_INT64 r = TQ_INT64( ( TDEApplication::random() / pow( 2, sizeof( int ) * 8 ) )
* pow( 2, 64 ) );
random = r % total;
}
else
random = TDEApplication::random() % total;
int i = 0;
for( int n = tracks.count(); i < n && random >= 0; ++i )
random -= weights.at( i );
item = tracks.at( i-1 );
}
}
}
else if( item )
{
item = MyIt::nextVisible( item );
while( item && ( !checkFileStatus( item ) || !item->exists() ) )
item = MyIt::nextVisible( item );
}
else
{
item = *MyIt( this ); //ie. first visible item
while( item && ( !checkFileStatus( item ) || !item->exists() ) )
item = item->nextSibling();
}
if ( dynamicMode() && item != firstChild() )
{
if( currentTrack() )
currentTrack()->setDynamicEnabled( false );
advanceDynamicTrack();
}
if ( !item && Amarok::repeatPlaylist() )
item = *MyIt( this ); //ie. first visible item
}
if ( EngineController::engine()->loaded() )
activate( item );
else
setCurrentTrack( item );
}
//This is called before setCurrentItem( item );
void
Playlist::advanceDynamicTrack()
{
int x = currentTrackIndex();
bool didDelete = false;
if( dynamicMode()->cycleTracks() )
{
if( x >= dynamicMode()->previousCount() )
{
PlaylistItem *first = firstChild();
removeItem( first );
delete first;
didDelete = true;
}
}
const int upcomingTracks = childCount() - x - 1;
// Just starting to play from stopped, don't append something needlessely
// or, we have more than enough items in the queue.
bool dontAppend = ( !didDelete &&
( EngineController::instance()->engine()->state() == Engine::Empty ) ) ||
upcomingTracks > dynamicMode()->upcomingCount();
//keep upcomingTracks requirement, this seems to break StopAfterCurrent
if( !dontAppend && stopAfterMode() != StopAfterCurrent )
{
s_dynamicADTMutex->lock();
m_insertFromADT++;
s_dynamicADTMutex->unlock();
addDynamicModeTracks( 1 );
}
m_dynamicDirt = true;
}
void
Playlist::playPrevTrack()
{
PlaylistItem *item = currentTrack();
if( Amarok::entireAlbums() )
{
item = 0;
if( m_currentTrack )
{
item = m_currentTrack->prevInAlbum();
if( !item && Amarok::repeatAlbum() && m_currentTrack->m_album->tracks.count() )
item = m_currentTrack->m_album->tracks.getLast();
}
if( !item )
{
PlaylistAlbum* a = m_prevAlbums.last();
while( a && !a->tracks.count() )
{
removeFromPreviousAlbums();
a = m_prevAlbums.last();
}
if( a )
{
item = a->tracks.getLast();
removeFromPreviousAlbums();
}
}
if( !item )
{
item = *static_cast<MyIt&>(--MyIt( item ));
while( item && !checkFileStatus( item ) )
item = *static_cast<MyIt&>(--MyIt( item ));
}
}
else
{
if( dynamicMode() )
{
}
else if( !AmarokConfig::randomMode() || m_prevTracks.count() <= 1 )
{
if( item )
{
item = MyIt::prevVisible( item );
while( item && ( !checkFileStatus( item ) || !item->isEnabled() ) )
item = MyIt::prevVisible( item );
}
else
{
item = *MyIt( this ); //ie. first visible item
while( item && ( !checkFileStatus( item ) || !item->isEnabled() ) )
item = item->nextSibling();
}
}
else {
// if enough songs in buffer, jump to the previous one
m_prevTracks.last();
removeFromPreviousTracks(); //remove the track playing now
item = m_prevTracks.last();
// we need to remove this item now, since it will be added in activate() again
removeFromPreviousTracks();
}
}
if ( !item && Amarok::repeatPlaylist() )
item = *MyIt( lastItem() ); //TODO check this works!
if ( EngineController::engine()->loaded() )
activate( item );
else
setCurrentTrack( item );
}
void
Playlist::playCurrentTrack()
{
if ( !currentTrack() )
playNextTrack( Amarok::repeatTrack() );
//we must do this even if the above is correct
//since the engine is not loaded the first time the user presses play
//then calling the next() function wont play it
activate( currentTrack() );
}
void
Playlist::setSelectedRatings( int rating )
{
if( !m_selCount && currentItem() && currentItem()->isVisible() )
CollectionDB::instance()->setSongRating( currentItem()->url().path(), rating, true );
else
for( MyIt it( this, MyIt::Selected ); *it; ++it )
CollectionDB::instance()->setSongRating( (*it)->url().path(), rating, true );
}
void
Playlist::queueSelected()
{
PLItemList in, out;
TQPtrList<TQListViewItem> dynamicList;
for( MyIt it( this, MyIt::Selected ); *it; ++it )
{
// Dequeuing selection with dynamic doesn't work due to the moving of the track after the last queued
if( dynamicMode() )
{
( !m_nextTracks.containsRef( *it ) ? in : out ).append( *it );
dynamicList.append( *it );
}
else
{
queue( *it, true );
( m_nextTracks.containsRef( *it ) ? in : out ).append( *it );
}
}
if( dynamicMode() )
{
TQListViewItem *item = dynamicList.first();
if( m_nextTracks.containsRef( static_cast<PlaylistItem*>(item) ) )
{
for( item = dynamicList.last(); item; item = dynamicList.prev() )
queue( item, true );
}
else
{
for( ; item; item = dynamicList.next() )
queue( item, true );
}
}
emit queueChanged( in, out );
}
void
Playlist::queue( TQListViewItem *item, bool multi, bool invertQueue )
{
#define item static_cast<PlaylistItem*>(item)
const int queueIndex = m_nextTracks.findRef( item );
const bool isQueued = queueIndex != -1;
if( isQueued )
{
if( invertQueue )
{
//remove the item, this is better way than remove( item )
m_nextTracks.remove( queueIndex ); //sets current() to next item
if( dynamicMode() ) // we move the item after the last queued item to preserve the ordered 'queue'.
{
PlaylistItem *after = m_nextTracks.last();
if( after )
moveItem( item, 0, after );
}
}
}
else if( !dynamicMode() )
m_nextTracks.append( item );
else // Dynamic mode
{
PlaylistItem *after;
m_nextTracks.isEmpty() ?
after = m_currentTrack :
after = m_nextTracks.last();
if( !after )
{
after = firstChild();
while( after && !after->isDynamicEnabled() )
{
if( after->nextSibling()->isDynamicEnabled() )
break;
after = after->nextSibling();
}
}
if( item->isDynamicEnabled() && item != m_currentTrack )
{
this->moveItem( item, 0, after );
m_nextTracks.append( item );
}
else
{
/// we do the actual queuing through customEvent, since insertMedia is threaded
m_queueDirt = true;
insertMediaInternal( item->url(), after );
}
}
if( !multi )
{
if( isQueued ) //no longer
{
if( invertQueue )
emit queueChanged( PLItemList(), PLItemList( item ) );
}
else
emit queueChanged( PLItemList( item ), PLItemList() );
}
#undef item
}
void
Playlist::sortQueuedItems() // used by dynamic mode
{
PlaylistItem *last = m_currentTrack;
for( PlaylistItem *item = m_nextTracks.first(); item; item = m_nextTracks.next() )
{
if( item->itemAbove() != last )
item->moveItem( last );
last = item;
}
}
void Playlist::setStopAfterCurrent( bool on )
{
PlaylistItem *prev_stopafter = m_stopAfterTrack;
if( on ) {
setStopAfterItem( m_currentTrack );
}
else {
setStopAfterMode( DoNotStop );
}
if( m_stopAfterTrack )
m_stopAfterTrack->update();
if( prev_stopafter )
prev_stopafter->update();
}
void Playlist::setStopAfterItem( PlaylistItem *item )
{
if( !item ) {
setStopAfterMode( DoNotStop );
return;
}
else if( item == m_currentTrack )
setStopAfterMode( StopAfterCurrent );
else if( item == m_nextTracks.getLast() )
setStopAfterMode( StopAfterQueue );
else
setStopAfterMode( StopAfterQueue );
m_stopAfterTrack = item;
}
void Playlist::toggleStopAfterCurrentItem()
{
PlaylistItem *item = currentItem();
if( !item && m_selCount == 1 )
item = *MyIt( this, MyIt::Visible | MyIt::Selected );
if( !item )
return;
PlaylistItem *prev_stopafter = m_stopAfterTrack;
if( m_stopAfterTrack == item ) {
m_stopAfterTrack = 0;
setStopAfterMode( DoNotStop );
}
else
{
setStopAfterItem( item );
item->setSelected( false );
item->update();
}
if( prev_stopafter )
prev_stopafter->update();
}
void Playlist::toggleStopAfterCurrentTrack()
{
PlaylistItem *item = currentTrack();
if( !item )
return;
PlaylistItem *prev_stopafter = m_stopAfterTrack;
if( m_stopAfterTrack == item ) {
setStopAfterMode( DoNotStop );
Amarok::OSD::instance()->OSDWidget::show( i18n("Stop Playing After Track: Off") );
}
else
{
setStopAfterItem( item );
item->setSelected( false );
item->update();
Amarok::OSD::instance()->OSDWidget::show( i18n("Stop Playing After Track: On") );
}
if( prev_stopafter )
prev_stopafter->update();
}
void Playlist::setStopAfterMode( int mode )
{
PlaylistItem *prevStopAfter = m_stopAfterTrack;
m_stopAfterMode = mode;
switch( mode )
{
case DoNotStop:
m_stopAfterTrack = 0;
break;
case StopAfterCurrent:
m_stopAfterTrack = m_currentTrack;
break;
case StopAfterQueue:
m_stopAfterTrack = m_nextTracks.count() ? m_nextTracks.getLast() : m_currentTrack;
break;
}
if( prevStopAfter )
prevStopAfter->update();
if( m_stopAfterTrack )
m_stopAfterTrack->update();
}
int Playlist::stopAfterMode()
{
if ( m_stopAfterMode != DoNotStop
&& m_stopAfterTrack && m_stopAfterTrack == m_currentTrack ) {
m_stopAfterMode = StopAfterCurrent;
}
return m_stopAfterMode;
}
void Playlist::generateInfo()
{
m_albums.clear();
if( Amarok::entireAlbums() )
for( MyIt it( this, MyIt::All ); *it; ++it )
(*it)->refAlbum();
m_total = 0;
if( Amarok::entireAlbums() || AmarokConfig::favorTracks() )
for( MyIt it( this, MyIt::Visible ); *it; ++it )
(*it)->incrementTotals();
}
void Playlist::doubleClicked( TQListViewItem *item )
{
/* We have to check if the item exists before calling activate, otherwise clicking on an empty
playlist space would stop playing (check BR #105106)*/
if( item && m_hoveredRating != item )
activate( item );
}
void
Playlist::slotCountChanged()
{
if( m_itemCountDirty )
emit itemCountChanged( totalTrackCount(), m_totalLength,
m_visCount, m_visLength,
m_selCount, m_selLength );
m_itemCountDirty = false;
}
bool
Playlist::checkFileStatus( PlaylistItem * item )
{
//DEBUG_BLOCK
//debug() << "uniqueid of item = " << item->uniqueId() << ", url = " << item->url().path() << endl;
if( !item->checkExists() )
{
//debug() << "not found, finding new url" << endl;
TQString path = TQString();
if( !item->uniqueId().isEmpty() )
{
path = CollectionDB::instance()->urlFromUniqueId( item->uniqueId() );
//debug() << "found path = " << path << endl;
}
else
{
//debug() << "Setting uniqueid of item and trying again" << endl;
item->setUniqueId();
if( !item->uniqueId().isEmpty() )
path = CollectionDB::instance()->urlFromUniqueId( item->uniqueId() );
}
if( !path.isEmpty() )
{
item->setUrl( KURL( path ) );
if( item->checkExists() )
item->setFilestatusEnabled( true );
else
item->setFilestatusEnabled( false );
}
else
item->setFilestatusEnabled( false );
}
else if( !item->isFilestatusEnabled() )
item->setFilestatusEnabled( true );
bool returnValue = item->isFilestatusEnabled();
return returnValue;
}
void
Playlist::activate( TQListViewItem *item )
{
///item will be played if possible, the playback may be delayed
///so we start the glow anyway and hope
//All internal requests for playback should come via
//this function please!
if( !item )
{
//we have reached the end of the playlist
EngineController::instance()->stop();
setCurrentTrack( 0 );
Amarok::OSD::instance()->OSDWidget::show( i18n("Playlist finished"),
TQImage( TDEIconLoader().iconPath( "amarok", -TDEIcon::SizeHuge ) ) );
return;
}
#define item static_cast<PlaylistItem*>(item)
if ( !checkFileStatus( item ) )
{
Amarok::StatusBar::instance()->shortMessage( i18n("Local file does not exist.") );
return;
}
if( dynamicMode() && !Amarok::repeatTrack() )
{
if( m_currentTrack && item->isDynamicEnabled() )
{
if( item != m_currentTrack )
this->moveItem( item, 0, m_currentTrack );
}
else
{
MyIt it( this, MyIt::Visible );
bool hasHistory = false;
if ( *it && !(*it)->isDynamicEnabled() )
{
hasHistory = true;
for( ; *it && !(*it)->isDynamicEnabled() ; ++it );
}
if( item->isDynamicEnabled() )
{
hasHistory ?
this->moveItem( item, 0, *it ) :
this->moveItem( item, 0, 0 );
}
else // !item->isDynamicEnabled()
{
hasHistory ?
insertMediaInternal( item->url(), *it ):
insertMediaInternal( item->url(), 0 );
m_dynamicDirt = true;
return;
}
}
if( !m_dynamicDirt && m_currentTrack && m_currentTrack != item )
{
m_currentTrack->setDynamicEnabled( false );
advanceDynamicTrack();
}
}
if( Amarok::entireAlbums() )
{
if( !item->nextInAlbum() )
appendToPreviousAlbums( item->m_album );
}
else
appendToPreviousTracks( item );
//if we are playing something from the next tracks
//list, remove it from the list
if( m_nextTracks.removeRef( item ) )
emit queueChanged( PLItemList(), PLItemList( item ) );
//looks bad painting selected and glowing
//only do when user explicitly activates an item though
item->setSelected( false );
setCurrentTrack( item );
m_dynamicDirt = false;
//use PlaylistItem::MetaBundle as it also updates the audioProps
EngineController::instance()->play( *item );
#undef item
}
TQPair<TQString, TQRect> Playlist::toolTipText( TQWidget*, const TQPoint &pos ) const
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( pos ) );
if( !item )
return TQPair<TQString, TQRect>( TQString(), TQRect() );
const TQPoint contentsPos = viewportToContents( pos );
const int col = header()->sectionAt( contentsPos.x() );
if( item == m_renameItem && col == m_renameColumn )
return TQPair<TQString, TQRect>( TQString(), TQRect() );
TQString text;
if( col == PlaylistItem::Rating )
text = item->ratingDescription( item->rating() );
else
text = item->text( col );
TQRect irect = itemRect( item );
const int headerPos = header()->sectionPos( col );
irect.setLeft( headerPos - 1 );
irect.setRight( headerPos + header()->sectionSize( col ) );
static TQFont f;
static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
if( minbearing == 2003 || f != font() )
{
f = font(); //getting your bearings can be expensive, so we cache them
minbearing = fontMetrics().minLeftBearing() + fontMetrics().minRightBearing();
}
int itemWidth = irect.width() - itemMargin() * 2 + minbearing - 2;
if( item->pixmap( col ) )
itemWidth -= item->pixmap( col )->width();
if( item == m_currentTrack )
{
if( col == m_firstColumn )
itemWidth -= 12;
if( col == mapToLogicalColumn( numVisibleColumns() - 1 ) )
itemWidth -= 12;
}
if( col != PlaylistItem::Rating && fontMetrics().width( text ) <= itemWidth )
return TQPair<TQString, TQRect>( TQString(), TQRect() );
TQRect globalRect( viewport()->mapToGlobal( irect.topLeft() ), irect.size() );
TQSimpleRichText t( text, font() );
int dright = TQApplication::desktop()->screenGeometry( qscrollview() ).topRight().x();
t.setWidth( dright - globalRect.left() );
if( col == PlaylistItem::Rating )
globalRect.setRight( kMin( dright, kMax( globalRect.left() + t.widthUsed(), globalRect.left() + ( StarManager::instance()->getGreyStar()->width() + 1 ) * ( ( item->rating() + 1 ) / 2 ) ) ) );
else
globalRect.setRight( kMin( globalRect.left() + t.widthUsed(), dright ) );
globalRect.setBottom( globalRect.top() + kMax( irect.height(), t.height() ) - 1 );
if( ( col == PlaylistItem::Rating && PlaylistItem::ratingAtPoint( contentsPos.x() ) <= item->rating() + 1 ) ||
( col != PlaylistItem::Rating ) )
{
text = text.replace( "&", "&amp;" ).replace( "<", "&lt;" ).replace( ">", "&gt;" );
if( item->isCurrent() )
{
text = TQString("<i>%1</i>").arg( text );
Amarok::ToolTip::s_hack = 1; //HACK for precise positioning
}
return TQPair<TQString, TQRect>( text, globalRect );
}
return TQPair<TQString, TQRect>( TQString(), TQRect() );
}
void
Playlist::activateByIndex( int index )
{
TQListViewItem* item = itemAtIndex( index );
if ( item )
activate(item);
}
void
Playlist::setCurrentTrack( PlaylistItem *item )
{
///mark item as the current track and make it glow
PlaylistItem *prev = m_currentTrack;
//FIXME best method would be to observe usage, especially don't shift if mouse is moving nearby
if( item && ( !prev || prev == currentItem() ) && !renameLineEdit()->isVisible() && m_selCount < 2 )
{
if( !prev )
//if nothing is current and then playback starts, we must show the currentTrack
ensureItemCentered( item ); //handles 0 gracefully
else {
const int prevY = itemPos( prev );
const int prevH = prev->height();
// check if the previous track is visible
if( prevY <= contentsY() + visibleHeight() && prevY + prevH >= contentsY() )
{
// in random mode always jump, if previous track is visible
if( AmarokConfig::randomMode() )
ensureItemCentered( item );
else if( prev && prev == currentItem() )
setCurrentItem( item );
//FIXME would be better to just never be annoying
// so if the user caused the track change, always show the new track
// but if it is automatic be careful
// if old item in view then try to keep the new one near the middle
const int y = itemPos( item );
const int h = item->height();
const int vh = visibleHeight();
const int amount = h * 3;
int d = y - contentsY();
if( d > 0 ) {
d += h;
d -= vh;
if( d > 0 && d <= amount )
// scroll down
setContentsPos( contentsX(), y - vh + amount );
}
else if( d >= -amount )
// scroll up
setContentsPos( contentsX(), y - amount );
}
}
}
m_currentTrack = item;
if ( m_currentTrack )
m_currentTrack->setIsNew(false);
if ( prev ) {
//reset to normal height
prev->invalidateHeight();
prev->setup();
//remove pixmap in first column
prev->setPixmap( m_firstColumn, TQPixmap() );
}
updateNextPrev();
setCurrentTrackPixmap();
Glow::reset();
slotGlowTimer();
}
int
Playlist::currentTrackIndex( bool onlyCountVisible )
{
int index = 0;
for( MyIt it( this, onlyCountVisible ? MyIt::Visible : MyIt::All ); *it; ++it )
{
if ( *it == m_currentTrack )
return index;
++index;
}
return -1;
}
int
Playlist::totalTrackCount() const
{
return m_totalCount;
}
BundleList
Playlist::nextTracks() const
{
BundleList list;
for( TQPtrListIterator<PlaylistItem> it( m_nextTracks ); *it; ++it )
list << (**it);
return list;
}
uint
Playlist::repeatAlbumTrackCount() const
{
if ( m_currentTrack && m_currentTrack->m_album )
return m_currentTrack->m_album->tracks.count();
else
return 0;
}
const DynamicMode*
Playlist::dynamicMode() const
{
return m_dynamicMode;
}
DynamicMode*
Playlist::modifyDynamicMode()
{
DynamicMode *m = m_dynamicMode;
if( !m )
return 0;
m_dynamicMode = new DynamicMode( *m );
return m;
}
void
Playlist::finishedModifying( DynamicMode *mode )
{
DynamicMode *m = m_dynamicMode;
setDynamicMode( mode );
delete m;
}
void
Playlist::setCurrentTrackPixmap( int state )
{
if( !m_currentTrack )
return;
TQString pixmap = TQString();
if( state < 0 )
state = EngineController::engine()->state();
if( state == Engine::Paused )
pixmap = "currenttrack_pause";
else if( state == Engine::Playing )
pixmap = "currenttrack_play";
m_currentTrack->setPixmap( m_firstColumn, pixmap.isNull() ? TQPixmap() : Amarok::getPNG( pixmap ) );
PlaylistItem::setPixmapChanged();
}
PlaylistItem*
Playlist::restoreCurrentTrack()
{
///It is always possible that the current track has been lost
///eg it was removed and then reinserted, here we check
const KURL url = EngineController::instance()->playingURL();
if ( !(m_currentTrack && ( m_currentTrack->url() == url || !m_currentTrack->url().isEmpty() && url.isEmpty() ) ) )
{
PlaylistItem* item;
for( item = firstChild();
item && item->url() != url;
item = item->nextSibling() )
{}
setCurrentTrack( item ); //set even if NULL
}
if( m_currentTrack && EngineController::instance()->engine()->state() == Engine::Playing && !Glow::timer.isActive() )
Glow::startTimer();
return m_currentTrack;
}
void
Playlist::countChanged()
{
if( !m_itemCountDirty )
{
m_itemCountDirty = true;
TQTimer::singleShot( 0, this, TQT_SLOT( slotCountChanged() ) );
}
}
bool
Playlist::isTrackAfter() const
{
///Is there a track after the current track?
//order is carefully crafted, remember count() is O(n)
//TODO randomMode will end if everything is in prevTracks
return !currentTrack() && !isEmpty() ||
!m_nextTracks.isEmpty() ||
currentTrack() && currentTrack()->itemBelow() ||
totalTrackCount() > 1 && ( AmarokConfig::randomMode() || Amarok::repeatPlaylist()
|| Amarok::repeatAlbum() && repeatAlbumTrackCount() > 1 );
}
bool
Playlist::isTrackBefore() const
{
//order is carefully crafted, remember count() is O(n)
return !isEmpty() &&
(
currentTrack() && (currentTrack()->itemAbove() || Amarok::repeatPlaylist() && totalTrackCount() > 1)
||
AmarokConfig::randomMode() && totalTrackCount() > 1
);
}
void
Playlist::updateNextPrev()
{
Amarok::actionCollection()->action( "play" )->setEnabled( !isEmpty() );
Amarok::actionCollection()->action( "prev" )->setEnabled( isTrackBefore() );
Amarok::actionCollection()->action( "next" )->setEnabled( isTrackAfter() );
Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( !isEmpty() );
Amarok::actionCollection()->action( "playlist_show" )->setEnabled( m_currentTrack );
if( m_currentTrack )
// ensure currentTrack is shown at correct height
m_currentTrack->setup();
}
////////////////////////////////////////////////////////////////////////////////
/// EngineObserver Reimplementation
////////////////////////////////////////////////////////////////////////////////
void
Playlist::engineNewMetaData( const MetaBundle &bundle, bool trackChanged )
{
if ( !bundle.podcastBundle() )
{
if ( m_currentTrack && !trackChanged ) {
//if the track hasn't changed then this is a meta-data update
if( stopAfterMode() == StopAfterCurrent || !m_nextTracks.isEmpty() )
Playlist::instance()->playNextTrack( true );
//this is a hack, I repeat a hack! FIXME FIXME
//we do it because often the stream title is from the pls file and is informative
//we don't want to lose it when we get the meta data
else if ( m_currentTrack->artist().isEmpty() ) {
TQString comment = m_currentTrack->title();
m_currentTrack->copyFrom( bundle );
m_currentTrack->setComment( comment );
}
else
m_currentTrack->copyFrom( bundle );
}
else
//ensure the currentTrack is set correctly and highlight it
restoreCurrentTrack();
}
else
//ensure the currentTrack is set correctly and highlight it
restoreCurrentTrack();
if( m_currentTrack )
m_currentTrack->filter( m_filter );
}
void
Playlist::engineStateChanged( Engine::State state, Engine::State /*oldState*/ )
{
switch( state )
{
case Engine::Playing:
Amarok::actionCollection()->action( "pause" )->setEnabled( true );
Amarok::actionCollection()->action( "stop" )->setEnabled( true );
Glow::startTimer();
break;
case Engine::Paused:
Amarok::actionCollection()->action( "pause" )->setEnabled( false );
Amarok::actionCollection()->action( "stop" )->setEnabled( true );
Glow::reset();
if( m_currentTrack )
slotGlowTimer(); //update glow state
break;
case Engine::Empty:
Amarok::actionCollection()->action( "pause" )->setEnabled( false );
Amarok::actionCollection()->action( "stop" )->setEnabled( false );
//leave the glow state at full colour
Glow::reset();
if ( m_currentTrack )
{
//remove pixmap in all columns
TQPixmap null;
for( int i = 0; i < header()->count(); i++ )
m_currentTrack->setPixmap( i, null );
PlaylistItem::setPixmapChanged();
//reset glow state
slotGlowTimer();
}
case Engine::Idle:
slotGlowTimer();
break;
}
//POSSIBLYAHACK
//apparently you can't rely on EngineController::engine()->state() == state here, so pass it explicitly
setCurrentTrackPixmap( state );
}
////////////////////////////////////////////////////////////////////////////////
/// TDEListView Reimplementation
////////////////////////////////////////////////////////////////////////////////
void
Playlist::appendMedia( const TQString &path )
{
appendMedia( KURL::fromPathOrURL( path ) );
}
void
Playlist::appendMedia( const KURL &url )
{
insertMedia( KURL::List( url ) );
}
void
Playlist::clear() //SLOT
{
if( isLocked() || renameLineEdit()->isVisible() ) return;
disableDynamicMode();
emit aboutToClear(); //will saveUndoState()
setCurrentTrack( 0 );
m_prevTracks.clear();
m_prevAlbums.clear();
if (m_stopAfterTrack) {
m_stopAfterTrack = 0;
if ( stopAfterMode() != StopAfterCurrent ) {
setStopAfterMode( DoNotStop );
}
}
const PLItemList prev = m_nextTracks;
m_nextTracks.clear();
emit queueChanged( PLItemList(), prev );
// Update player button states
Amarok::actionCollection()->action( "play" )->setEnabled( false );
Amarok::actionCollection()->action( "prev" )->setEnabled( false );
Amarok::actionCollection()->action( "next" )->setEnabled( false );
Amarok::actionCollection()->action( "playlist_clear" )->setEnabled( false );
ThreadManager::instance()->abortAllJobsNamed( "TagWriter" );
// something to bear in mind, if there is any event in the loop
// that depends on a PlaylistItem, we are about to crash Amarok
// never unlock() the Playlist until it is safe!
safeClear();
m_total = 0;
m_albums.clear();
setPlaylistName( i18n( "Untitled" ) );
ScriptManager::instance()->notifyPlaylistChange("cleared");
}
/**
* Workaround for TQt 3.3.5 bug in TQListView::clear()
* @see http://lists.kde.org/?l=kde-devel&m=113113845120155&w=2
* @see BUG 116004
*/
void
Playlist::safeClear()
{
/* 3.3.5 and 3.3.6 have bad TDEListView::clear() functions.
3.3.5 forgets to clear the pointer to the highlighted item.
3.3.6 forgets to clear the pointer to the last dragged item */
if ( strcmp( tqVersion(), "3.3.5" ) == 0
|| strcmp( tqVersion(), "3.3.6" ) == 0 )
{
bool block = signalsBlocked();
blockSignals( true );
clearSelection();
TQListViewItem *c = firstChild();
TQListViewItem *n;
while( c ) {
n = c->nextSibling();
if ( !static_cast<PlaylistItem *>( c )->isEmpty() ) //avoid deleting markers
delete c;
c = n;
}
blockSignals( block );
triggerUpdate();
}
else
TDEListView::clear();
}
void
Playlist::setSorting( int col, bool b )
{
saveUndoState();
//HACK There are reasons to allow sorting in dynamic mode, but
//it breaks other things that I don't have the time or patience
//to figure out...at least right now
if( !dynamicMode() )
TDEListView::setSorting( col, b );
}
void
Playlist::setColumnWidth( int col, int width )
{
TDEListView::setColumnWidth( col, width );
//FIXME this is because TQt doesn't by default disable resizing width 0 columns. GRRR!
//NOTE default column sizes are stored in default amarokrc so that restoreLayout() in ctor will
// call this function. This is necessary because addColumn() doesn't call setColumnWidth() GRRR!
header()->setResizeEnabled( width != 0, col );
}
void
Playlist::rename( TQListViewItem *item, int column ) //SLOT
{
if( !item )
return;
switch( column )
{
case PlaylistItem::Artist:
renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->artistList() );
break;
case PlaylistItem::Album:
renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->albumList() );
break;
case PlaylistItem::Genre:
renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->genreList() );
break;
case PlaylistItem::Composer:
renameLineEdit()->completionObject()->setItems( CollectionDB::instance()->composerList() );
break;
default:
renameLineEdit()->completionObject()->clear();
break;
}
renameLineEdit()->completionObject()->setCompletionMode( TDEGlobalSettings::CompletionPopupAuto );
renameLineEdit()->completionObject()->setIgnoreCase( true );
m_editOldTag = static_cast<PlaylistItem *>(item)->exactText( column );
if( m_selCount <= 1 )
{
if( currentItem() )
currentItem()->setSelected( false );
item->setSelected( true );
}
setCurrentItem( item );
TDEListView::rename( item, column );
m_renameItem = item;
m_renameColumn = column;
static_cast<PlaylistItem*>(item)->setIsBeingRenamed( true );
}
void
Playlist::writeTag( TQListViewItem *qitem, const TQString &, int column ) //SLOT
{
const bool dynamicEnabled = static_cast<PlaylistItem*>(qitem)->isDynamicEnabled();
if( m_itemsToChangeTagsFor.isEmpty() )
m_itemsToChangeTagsFor.append( static_cast<PlaylistItem*>( qitem ) );
const TQString newTag = static_cast<PlaylistItem*>( qitem )->exactText( column );
for( PlaylistItem *item = m_itemsToChangeTagsFor.first(); item; item = m_itemsToChangeTagsFor.next() )
{
if( !checkFileStatus( item ) )
continue;
const TQString oldTag = item == qitem ? m_editOldTag : item->exactText(column);
if( column == PlaylistItem::Score )
CollectionDB::instance()->setSongPercentage( item->url().path(), newTag.toInt() );
else if( column == PlaylistItem::Rating )
CollectionDB::instance()->setSongRating( item->url().path(), newTag.toInt() );
else
if (oldTag != newTag)
ThreadManager::instance()->queueJob( new TagWriter( item, oldTag, newTag, column ) );
else if( item->deleteAfterEditing() )
{
removeItem( item );
delete item;
}
}
if( dynamicMode() )
static_cast<PlaylistItem*>(qitem)->setDynamicEnabled( dynamicEnabled );
m_itemsToChangeTagsFor.clear();
m_editOldTag = TQString();
}
void
Playlist::columnOrderChanged() //SLOT
{
const uint prevColumn = m_firstColumn;
//determine first visible column
for ( m_firstColumn = 0; m_firstColumn < header()->count(); m_firstColumn++ )
if ( header()->sectionSize( header()->mapToSection( m_firstColumn ) ) )
break;
//convert to logical column
m_firstColumn = header()->mapToSection( m_firstColumn );
//force redraw of currentTrack
if( m_currentTrack )
{
m_currentTrack->setPixmap( prevColumn, TQPixmap() );
setCurrentTrackPixmap();
}
TQResizeEvent e( size(), TQSize() );
viewportResizeEvent( &e );
emit columnsChanged();
}
void
Playlist::paletteChange( const TQPalette &p )
{
using namespace Glow;
TQColor fg;
TQColor bg;
{
using namespace Base;
const uint steps = STEPS+5+5; //so we don't fade all the way to base, and all the way up to highlight either
fg = colorGroup().highlight();
bg = colorGroup().base();
dr = double(bg.red() - fg.red()) / steps;
dg = double(bg.green() - fg.green()) / steps;
db = double(bg.blue() - fg.blue()) / steps;
r = fg.red() + int(dr*5.0); //we add 5 steps so the default colour is slightly different to highlight
g = fg.green() + int(dg*5.0);
b = fg.blue() + int(db*5.0);
}
{
using namespace Text;
const uint steps = STEPS + 5; //so we don't fade all the way to base
fg = colorGroup().highlightedText();
bg = colorGroup().text();
dr = double(bg.red() - fg.red()) / steps;
dg = double(bg.green() - fg.green()) / steps;
db = double(bg.blue() - fg.blue()) / steps;
r = fg.red();
g = fg.green();
b = fg.blue();
}
TDEListView::paletteChange( p );
counter = 0; // reset the counter or apparently the text lacks contrast
slotGlowTimer(); // repaint currentTrack marker
}
void
Playlist::contentsDragEnterEvent( TQDragEnterEvent *e )
{
TQString data;
TQCString subtype;
TQTextDrag::decode( e, data, subtype );
e->accept(
e->source() == viewport() ||
subtype == "amarok-sql" ||
subtype == "uri-list" || //this is to prevent DelayedUrlLists from performing their queries
KURLDrag::canDecode( e ) );
}
void
Playlist::contentsDragMoveEvent( TQDragMoveEvent* e )
{
if( !e->isAccepted() ) return;
#if KDE_IS_VERSION( 3, 3, 91 )
const bool ctrlPressed = TDEApplication::keyboardMouseState() & TQt::ControlButton;
#else
const bool ctrlPressed = TDEApplication::keyboardModifiers() & ControlMask;
#endif
//Get the closest item _before_ the cursor
const TQPoint p = contentsToViewport( e->pos() );
TQListViewItem *item = itemAt( p );
if( !item || ctrlPressed ) item = lastItem();
else if( p.y() - itemRect( item ).top() < (item->height()/2) ) item = item->itemAbove();
if( item != m_marker ) {
//NOTE this if block prevents flicker
slotEraseMarker();
m_marker = item; //NOTE this is the correct place to set m_marker
viewportPaintEvent( 0 );
}
}
void
Playlist::contentsDragLeaveEvent( TQDragLeaveEvent* )
{
slotEraseMarker();
}
void
Playlist::contentsDropEvent( TQDropEvent *e )
{
DEBUG_BLOCK
//NOTE parent is always 0 currently, but we support it in case we start using trees
TQListViewItem *parent = 0;
TQListViewItem *after = m_marker;
//make sure to disable only if in dynamic mode and you're inserting
//at the beginning or in the middle of the disabled tracks
//Also, that the dynamic playlist has any tracks (suggested may not)
if( dynamicMode() && Playlist::instance()->firstChild() &&
( !m_marker || !( static_cast<PlaylistItem *>(m_marker)->isDynamicEnabled() ) ) &&
currentTrackIndex() != -1 )
{
// If marker is disabled, and there is a current track, or marker is not the last enabled track
// don't allow inserting
if( ( m_marker && ( m_currentTrack || ( m_marker->itemBelow() &&
!( static_cast<PlaylistItem *>(m_marker->itemBelow())->isDynamicEnabled() ) ) ) )
|| ( !m_marker ) )
{
slotEraseMarker();
return;
}
}
if( !after )
findDrop( e->pos(), parent, after ); //shouldn't happen, but you never know!
slotEraseMarker();
if ( e->source() == viewport() ) {
setSorting( NO_SORT ); //disableSorting and saveState()
movableDropEvent( parent, after );
TQPtrList<TQListViewItem> items = selectedItems();
if( dynamicMode() && after )
{
TQListViewItem *item;
bool enabled = static_cast<PlaylistItem *>(after)->isDynamicEnabled();
for( item = items.first(); item; item = items.next() )
static_cast<PlaylistItem *>(item)->setDynamicEnabled( enabled );
}
ScriptManager::instance()->notifyPlaylistChange("reordered");
}
else {
TQString data;
TQCString subtype;
TQTextDrag::decode( e, data, subtype );
debug() << "TQTextDrag::subtype(): " << subtype << endl;
if( subtype == "amarok-sql" ) {
setSorting( NO_SORT );
TQString query = data.section( "\n", 1 );
ThreadManager::instance()->queueJob( new SqlLoader( query, after ) );
ScriptManager::instance()->notifyPlaylistChange("changed");
}
else if( subtype == "dynamic" ) {
// Deserialize pointer
DynamicEntry* entry = reinterpret_cast<DynamicEntry*>( data.toULongLong() );
loadDynamicMode( entry );
}
else if( KURLDrag::canDecode( e ) )
{
debug() << "KURLDrag::canDecode" << endl;
KURL::List list;
KURLDrag::decode( e, list );
insertMediaInternal( list, static_cast<PlaylistItem*>( after ) );
}
else
e->ignore();
}
updateNextPrev();
}
TQDragObject*
Playlist::dragObject()
{
DEBUG_THREAD_FUNC_INFO
KURL::List list;
for( MyIt it( this, MyIt::Selected ); *it; ++it )
{
const PlaylistItem *item = static_cast<PlaylistItem*>( *it );
const KURL url = item->url();
list += url;
}
KURLDrag *drag = new KURLDrag( list, viewport() );
drag->setPixmap( CollectionDB::createDragPixmap( list ),
TQPoint( CollectionDB::DRAGPIXMAP_OFFSET_X, CollectionDB::DRAGPIXMAP_OFFSET_Y ) );
return drag;
}
#include <tqsimplerichtext.h>
void
Playlist::viewportPaintEvent( TQPaintEvent *e )
{
if( e ) TDEListView::viewportPaintEvent( e ); //we call with 0 in contentsDropEvent()
if ( m_marker ) {
TQPainter p( viewport() );
p.fillRect(
drawDropVisualizer( 0, 0, m_marker ),
TQBrush( colorGroup().highlight().dark(), TQBrush::Dense4Pattern ) );
}
else if( m_showHelp && isEmpty() ) {
TQPainter p( viewport() );
TQString minimumText(i18n(
"<div align=center>"
"<h3>The Playlist</h3>"
"This is the playlist. "
"To create a listing, "
"<b>drag</b> tracks from the browser-panels on the left, "
"<b>drop</b> them here and then <b>double-click</b> them to start playback."
"</div>" ) );
TQSimpleRichText *t = new TQSimpleRichText( minimumText +
i18n( "<div align=center>"
"<h3>The Browsers</h3>"
"The browsers are the source of all your music. "
"The collection-browser holds your collection. "
"The playlist-browser holds your pre-set playlistings. "
"The file-browser shows a file-selector which you can use to access any music on your computer. "
"</div>" ), TQApplication::font() );
if ( t->width()+30 >= viewport()->width() || t->height()+30 >= viewport()->height() ) {
// too big for the window, so let's cut part of the text
delete t;
t = new TQSimpleRichText( minimumText, TQApplication::font());
if ( t->width()+30 >= viewport()->width() || t->height()+30 >= viewport()->height() ) {
//still too big, giving up
delete t;
return;
}
}
const uint w = t->width();
const uint h = t->height();
const uint x = (viewport()->width() - w - 30) / 2 ;
const uint y = (viewport()->height() - h - 30) / 2 ;
p.setBrush( colorGroup().background() );
p.drawRoundRect( x, y, w+30, h+30, (8*200)/w, (8*200)/h );
t->draw( &p, x+15, y+15, TQRect(), colorGroup() );
delete t;
}
}
static uint negativeWidth = 0;
void
Playlist::viewportResizeEvent( TQResizeEvent *e )
{
if ( !m_smartResizing ) {
TDEListView::viewportResizeEvent( e );
return;
}
//only be clever with the sizing if there is not many items
//TODO don't allow an item to be made too small (ie less than 50% of ideal width)
//makes this much quicker
header()->blockSignals( true );
const double W = (double)e->size().width() - negativeWidth;
for( uint c = 0; c < m_columnFraction.size(); ++c ) {
switch( c ) {
case PlaylistItem::Track:
case PlaylistItem::Bitrate:
case PlaylistItem::SampleRate:
case PlaylistItem::Filesize:
case PlaylistItem::Score:
case PlaylistItem::Rating:
case PlaylistItem::Type:
case PlaylistItem::PlayCount:
case PlaylistItem::Length:
case PlaylistItem::Year:
case PlaylistItem::DiscNumber:
case PlaylistItem::Bpm:
break; //these columns retain their width - their items tend to have uniform size
default:
if( m_columnFraction[c] > 0 )
setColumnWidth( c, int(W * m_columnFraction[c]) );
}
}
header()->blockSignals( false );
//ensure that the listview scrollbars are updated etc.
triggerUpdate();
}
void
Playlist::columnResizeEvent( int col, int oldw, int neww )
{
if ( !m_smartResizing )
return;
//prevent recursion
header()->blockSignals( true );
//qlistview is stupid sometimes
if ( neww < 0 )
setColumnWidth( col, 0 );
if ( neww == 0 ) {
//the column in question has been hidden
//we need to adjust the other columns to fit
const double W = (double)width() - negativeWidth;
for( uint c = 0; c < m_columnFraction.size(); ++c ) {
if( c == (uint)col )
continue;
switch( c ) {
case PlaylistItem::Track:
case PlaylistItem::Bitrate:
case PlaylistItem::SampleRate:
case PlaylistItem::Filesize:
case PlaylistItem::Score:
case PlaylistItem::Rating:
case PlaylistItem::Type:
case PlaylistItem::PlayCount:
case PlaylistItem::Length:
case PlaylistItem::Year:
case PlaylistItem::DiscNumber:
case PlaylistItem::Bpm:
break;
default:
if( m_columnFraction[c] > 0 )
setColumnWidth( c, int(W * m_columnFraction[c]) );
}
}
}
else if( oldw != 0 ) {
//adjust the size of the column on the right side of this one
for( int section = col, index = header()->mapToIndex( section ); index < header()->count(); ) {
section = header()->mapToSection( ++index );
if ( header()->sectionSize( section ) ) {
int newSize = header()->sectionSize( section ) + oldw - neww;
if ( newSize > 5 ) {
setColumnWidth( section, newSize );
//we only want to adjust one column!
break;
}
}
}
}
header()->blockSignals( false );
negativeWidth = 0;
uint w = 0;
//determine width excluding the columns that have static size
for( uint x = 0; x < m_columnFraction.size(); ++x ) {
switch( x ) {
case PlaylistItem::Track:
case PlaylistItem::Bitrate:
case PlaylistItem::SampleRate:
case PlaylistItem::Filesize:
case PlaylistItem::Score:
case PlaylistItem::Rating:
case PlaylistItem::Type:
case PlaylistItem::PlayCount:
case PlaylistItem::Length:
case PlaylistItem::Year:
case PlaylistItem::DiscNumber:
case PlaylistItem::Bpm:
break;
default:
w += columnWidth( x );
}
negativeWidth += columnWidth( x );
}
//determine the revised column fractions
for( uint x = 0; x < m_columnFraction.size(); ++x )
m_columnFraction[x] = (double)columnWidth( x ) / double(w);
//negative width is an important property, honest!
negativeWidth -= w;
//we have to do this after we have established negativeWidth and set the columnFractions
if( neww == 0 || oldw == 0 ) {
//then this column has been inserted or removed, we need to update all the column widths
TQResizeEvent e( size(), TQSize() );
viewportResizeEvent( &e );
emit columnsChanged();
}
}
bool
Playlist::eventFilter( TQObject *o, TQEvent *e )
{
#define me TQT_TQMOUSEEVENT(e)
#define ke TQT_TQKEYEVENT(e)
if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(header()) && e->type() == TQEvent::MouseButtonPress && me->button() == Qt::RightButton )
{
enum { HIDE = 1000, SELECT, CUSTOM, SMARTRESIZING };
const int mouseOverColumn = header()->sectionAt( me->pos().x() );
TDEPopupMenu popup;
if( mouseOverColumn >= 0 )
popup.insertItem( i18n("&Hide %1").arg( columnText( mouseOverColumn ) ), HIDE ); //TODO
TDEPopupMenu sub;
for( int i = 0; i < columns(); ++i ) //columns() references a property
if( !columnWidth( i ) )
sub.insertItem( columnText( i ), i, i + 1 );
sub.setItemVisible( PlaylistItem::Score, AmarokConfig::useScores() );
sub.setItemVisible( PlaylistItem::Rating, AmarokConfig::useRatings() );
sub.setItemVisible( PlaylistItem::Mood, AmarokConfig::showMoodbar() );
popup.insertItem( i18n("&Show Column" ), &sub );
popup.insertItem( i18n("Select &Columns..."), SELECT );
popup.insertItem( i18n("&Fit to Width"), SMARTRESIZING );
popup.setItemChecked( SMARTRESIZING, m_smartResizing );
int col = popup.exec( TQT_TQMOUSEEVENT(e)->globalPos() );
switch( col ) {
case HIDE:
{
hideColumn( mouseOverColumn );
TQResizeEvent e( size(), TQSize() );
viewportResizeEvent( &e );
}
break;
case SELECT:
ColumnsDialog::display();
break;
case CUSTOM:
addCustomColumn();
break;
case SMARTRESIZING:
m_smartResizing = !m_smartResizing;
Amarok::config( "PlaylistWindow" )->writeEntry( "Smart Resizing", m_smartResizing );
if ( m_smartResizing )
columnResizeEvent( 0, 0, 0 ); //force refit. FIXME: It doesn't work perfectly
break;
default:
if( col != -1 )
{
adjustColumn( col );
header()->setResizeEnabled( true, col );
}
}
//determine first visible column again, since it has changed
columnOrderChanged();
//eat event
return true;
}
// not in slotMouseButtonPressed because we need to disable normal usage.
if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(viewport()) && e->type() == TQEvent::MouseButtonPress && me->state() == TQt::ControlButton && me->button() == Qt::RightButton )
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( me->pos() ) );
if( !item )
return true;
item->isSelected() ?
queueSelected():
queue( item );
return true; //yum!
}
// trigger in-place tag editing
else if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(viewport()) && e->type() == TQEvent::MouseButtonPress && me->button() == Qt::LeftButton )
{
m_clicktimer->stop();
m_itemToRename = 0;
int col = header()->sectionAt( viewportToContents( me->pos() ).x() );
if( col != PlaylistItem::Rating )
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( me->pos() ) );
bool edit = item
&& item->isSelected()
&& selectedItems().count()==1
&& (me->state() & ~Qt::LeftButton) == 0
&& item->url().isLocalFile();
if( edit )
{
m_clickPos = me->pos();
m_itemToRename = item;
m_columnToRename = col;
//return true;
}
}
}
else if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(viewport()) && e->type() == TQEvent::MouseButtonRelease && me->button() == Qt::LeftButton )
{
int col = header()->sectionAt( viewportToContents( me->pos() ).x() );
if( col != PlaylistItem::Rating )
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( me->pos() ) );
if( item == m_itemToRename && me->pos() == m_clickPos )
{
m_clicktimer->start( int( TQApplication::doubleClickInterval() ), true );
return true;
}
else
{
m_itemToRename = 0;
}
}
}
// avoid in-place tag editing upon double-clicks
else if( e->type() == TQEvent::MouseButtonDblClick && me->button() == Qt::LeftButton )
{
m_itemToRename = 0;
m_clicktimer->stop();
}
// Toggle play/pause if user middle-clicks on current track
else if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(viewport()) && e->type() == TQEvent::MouseButtonPress && me->button() == Qt::MidButton )
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( me->pos() ) );
if( item && item == m_currentTrack )
{
EngineController::instance()->playPause();
return true; //yum!
}
}
else if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(renameLineEdit()) && e->type() == 6 /*TQEvent::KeyPress*/ && m_renameItem )
{
const int visibleCols = numVisibleColumns();
int physicalColumn = visibleCols - 1;
while( mapToLogicalColumn( physicalColumn ) != m_renameColumn && physicalColumn >= 0 )
physicalColumn--;
if( physicalColumn < 0 )
{
warning() << "the column counting code is wrong! tell illissius." << endl;
return false;
}
int column = m_renameColumn;
TQListViewItem *item = m_renameItem;
if( ke->state() & TQt::AltButton )
{
if( ke->key() == TQt::Key_Up && m_visCount > 1 )
if( !( item = m_renameItem->itemAbove() ) )
{
item = *MyIt( this, MyIt::Visible );
while( item->itemBelow() )
item = item->itemBelow();
}
if( ke->key() == TQt::Key_Down && m_visCount > 1 )
if( !( item = m_renameItem->itemBelow() ) )
item = *MyIt( this, MyIt::Visible );
if( ke->key() == TQt::Key_Left )
do
{
if( physicalColumn == 0 )
physicalColumn = visibleCols - 1;
else
physicalColumn--;
column = mapToLogicalColumn( physicalColumn );
} while( !isRenameable( column ) );
if( ke->key() == TQt::Key_Right )
do
{
if( physicalColumn == visibleCols - 1 )
physicalColumn = 0;
else
physicalColumn++;
column = mapToLogicalColumn( physicalColumn );
} while( !isRenameable( column ) );
}
if( ke->key() == TQt::Key_Tab )
do
{
if( physicalColumn == visibleCols - 1 )
{
if( !( item = m_renameItem->itemBelow() ) )
item = *MyIt( this, MyIt::Visible );
physicalColumn = 0;
}
else
physicalColumn++;
column = mapToLogicalColumn( physicalColumn );
} while( !isRenameable( column ) );
if( ke->key() == TQt::Key_Backtab )
do
{
if( physicalColumn == 0 )
{
if( !( item = m_renameItem->itemAbove() ) )
{
item = *MyIt( this, MyIt::Visible );
while( item->itemBelow() )
item = item->itemBelow();
}
physicalColumn = visibleCols - 1;
}
else
physicalColumn--;
column = mapToLogicalColumn( physicalColumn );
} while( !isRenameable( column ) );
if( item != m_renameItem || column != m_renameColumn )
{
if( !item->isSelected() )
m_itemsToChangeTagsFor.clear();
//the item that actually got changed will get added back, in writeTag()
m_renameItem->setText( m_renameColumn, renameLineEdit()->text() );
doneEditing( m_renameItem, m_renameColumn );
rename( item, column );
return true;
}
}
else if( TQT_BASE_OBJECT(o) == TQT_BASE_OBJECT(renameLineEdit()) && ( e->type() == TQEvent::Hide || e->type() == TQEvent::Close ) )
{
m_renameItem = 0;
}
//allow the header to process this
return TDEListView::eventFilter( o, e );
#undef me
#undef ke
}
void
Playlist::slotSingleClick()
{
if( m_itemToRename )
{
rename( m_itemToRename, m_columnToRename );
}
m_itemToRename = 0;
}
void
Playlist::customEvent( TQCustomEvent *e )
{
if( e->type() == (int)UrlLoader::JobFinishedEvent ) {
refreshNextTracks( 0 );
PLItemList in, out;
// Disable help if playlist is populated
if ( !isEmpty() )
m_showHelp = false;
if ( !m_queueList.isEmpty() ) {
KURL::List::Iterator jt;
for( MyIt it( this, MyIt::All ); *it; ++it ) {
jt = m_queueList.find( (*it)->url() );
if ( jt != m_queueList.end() ) {
queue( *it );
( m_nextTracks.containsRef( *it ) ? in : out ).append( *it );
m_queueList.remove( jt );
}
}
m_queueList.clear();
}
if( m_dynamicDirt )
{
PlaylistItem *after = m_currentTrack;
if( !after )
{
after = firstChild();
while( after && !after->isDynamicEnabled() )
after = after->nextSibling();
}
else
after = static_cast<PlaylistItem *>( after->itemBelow() );
if( after )
{
PlaylistItem *prev = static_cast<PlaylistItem *>( after->itemAbove() );
if( prev && dynamicMode() )
prev->setDynamicEnabled( false );
s_dynamicADTMutex->lock();
if( m_insertFromADT > 0 )
{
if( EngineController::engine()->state() == Engine::Playing )
activate( after );
m_insertFromADT--;
}
else
activate( after );
s_dynamicADTMutex->unlock();
if( dynamicMode() && dynamicMode()->cycleTracks() )
adjustDynamicPrevious( dynamicMode()->previousCount() );
}
}
if( m_queueDirt )
{
PlaylistItem *after = 0;
m_nextTracks.isEmpty() ?
after = m_currentTrack :
after = m_nextTracks.last();
if( !after )
{
after = firstChild();
while( after && !after->isDynamicEnabled() )
after = after->nextSibling();
}
else
after = static_cast<PlaylistItem *>( after->itemBelow() );
if( after )
{
m_nextTracks.append( after );
in.append( after );
}
m_queueDirt = false;
}
if( !in.isEmpty() || !out.isEmpty() )
emit queueChanged( in, out );
//force redraw of currentTrack marker, play icon, etc.
restoreCurrentTrack();
}
updateNextPrev();
}
////////////////////////////////////////////////////////////////////////////////
/// Misc Public Methods
////////////////////////////////////////////////////////////////////////////////
bool
Playlist::saveM3U( const TQString &path, bool relative ) const
{
TQValueList<KURL> urls;
TQValueList<TQString> titles;
TQValueList<int> lengths;
for( MyIt it( firstChild(), MyIt::Visible ); *it; ++it )
{
urls << (*it)->url();
titles << (*it)->title();
lengths << (*it)->length();
}
return PlaylistBrowser::savePlaylist( path, urls, titles, lengths, relative );
}
void
Playlist::saveXML( const TQString &path )
{
DEBUG_BLOCK
TQFile file( path );
if( !file.open( IO_WriteOnly | IO_Truncate | IO_Raw ) ) return;
// Manual buffering since TQFile's is slow for whatever reason
const uint kWriteSize = 256 * 1024;
TQBuffer buffer;
buffer.open(IO_WriteOnly);
TQTextStream stream( &buffer );
stream.setEncoding( TQTextStream::UnicodeUTF8 );
stream << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
TQString dynamic;
if( dynamicMode() )
{
const TQString title = ( dynamicMode()->title() ).replace( "&", "&amp;" )
.replace( "<", "&lt;" )
.replace( ">", "&gt;" );
dynamic = TQString(" dynamicMode=\"%1\"").arg( title );
}
stream << TQString( "<playlist product=\"%1\" version=\"%2\"%3>\n" )
.arg( "Amarok" ).arg( Amarok::xmlVersion() ).arg( dynamic );
for( MyIt it( this, MyIt::All ); *it; ++it )
{
const PlaylistItem *item = *it;
if( item->isEmpty() ) continue; // Skip marker items and such
TQStringList attributes;
const int queueIndex = m_nextTracks.findRef( item );
if ( queueIndex != -1 )
attributes << "queue_index" << TQString::number( queueIndex + 1 );
else if ( item == currentTrack() )
attributes << "queue_index" << TQString::number( 0 );
if( !item->isDynamicEnabled() )
attributes << "dynamicdisabled" << "true";
if( m_stopAfterTrack == item )
attributes << "stop_after" << "true";
item->save( stream, attributes );
if ( buffer.at() >= kWriteSize )
{
file.writeBlock( buffer.buffer().data(), buffer.at() );
buffer.reset();
}
}
stream << "</playlist>\n";
file.writeBlock(buffer.buffer().data(), buffer.at());
file.close();
}
void
Playlist::burnPlaylist( int projectType )
{
KURL::List list;
TQListViewItemIterator it( this );
for( ; it.current(); ++it ) {
PlaylistItem *item = static_cast<PlaylistItem*>(*it);
KURL url = item->url();
if( url.isLocalFile() )
list << url;
}
K3bExporter::instance()->exportTracks( list, projectType );
}
void
Playlist::burnSelectedTracks( int projectType )
{
KURL::List list;
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
for( ; it.current(); ++it ) {
PlaylistItem *item = static_cast<PlaylistItem*>(*it);
KURL url = item->url();
if( url.isLocalFile() )
list << url;
}
K3bExporter::instance()->exportTracks( list, projectType );
}
void
Playlist::addCustomMenuItem( const TQString &submenu, const TQString &itemTitle ) //for dcop
{
m_customSubmenuItem[submenu] << itemTitle;
}
bool
Playlist::removeCustomMenuItem( const TQString &submenu, const TQString &itemTitle ) //for dcop
{
if( !m_customSubmenuItem.contains(submenu) )
return false;
if( m_customSubmenuItem[submenu].remove( itemTitle ) != 0 )
{
if( m_customSubmenuItem[submenu].count() == 0 )
m_customSubmenuItem.remove( submenu );
return true;
return true;
}
else
return false;
}
void
Playlist::customMenuClicked(int id) //adapted from burnSelectedTracks
{
TQString message = m_customIdItem[id];
TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
for( ; it.current(); ++it ) {
PlaylistItem *item = static_cast<PlaylistItem*>(*it);
KURL url = item->url().url();
message += ' ' + url.url();
}
ScriptManager::instance()->customMenuClicked( message );
}
void
Playlist::setDynamicMode( DynamicMode *mode ) //SLOT
{
// if mode == 0, then dynamic mode was just turned off.
DynamicMode* const prev = m_dynamicMode;
m_dynamicMode = mode;
if( mode )
AmarokConfig::setLastDynamicMode( mode->title() );
emit dynamicModeChanged( mode );
if( mode )
{
m_oldRandom = AmarokConfig::randomMode();
m_oldRepeat = AmarokConfig::repeat();
}
Amarok::actionCollection()->action( "random_mode" )->setEnabled( !mode );
Amarok::actionCollection()->action( "repeat" )->setEnabled( !mode );
Amarok::actionCollection()->action( "playlist_shuffle" )->setEnabled( !mode );
Amarok::actionCollection()->action( "repopulate" )->setEnabled( mode );
if( prev && mode )
{
if( prev->previousCount() != mode->previousCount() )
adjustDynamicPrevious( mode->previousCount(), true );
if( prev->upcomingCount() != mode->upcomingCount() )
adjustDynamicUpcoming( true );
}
else if( !prev )
{
if( mode )
adjustDynamicPrevious( mode->previousCount(), true );
setDynamicHistory( true ); // disable items!
}
else if( !mode ) // enable items again, dynamic mode is no more
setDynamicHistory( false );
}
void
Playlist::loadDynamicMode( DynamicMode *mode ) //SLOT
{
saveUndoState();
setDynamicMode( mode );
if( isEmpty() )
repopulate();
}
void
Playlist::editActiveDynamicMode() //SLOT
{
if( !m_dynamicMode )
return;
DynamicMode *m = modifyDynamicMode();
ConfigDynamic::editDynamicPlaylist( PlaylistWindow::self(), m );
m->rebuildCachedItemSet();
finishedModifying( m );
}
void
Playlist::disableDynamicMode() //SLOT
{
if( !m_dynamicMode )
return;
setDynamicMode( 0 );
AmarokConfig::setRandomMode( m_oldRandom );
AmarokConfig::setRepeat( m_oldRepeat );
static_cast<TDESelectAction*>(Amarok::actionCollection()->action( "random_mode" ))->setCurrentItem( m_oldRandom );
static_cast<TDESelectAction*>(Amarok::actionCollection()->action( "repeat" ))->setCurrentItem( m_oldRepeat );
}
void
Playlist::rebuildDynamicModeCache() //SLOT
{
if( !m_dynamicMode )
return;
DynamicMode *m = modifyDynamicMode();
m->rebuildCachedItemSet();
finishedModifying( m );
}
void
Playlist::repopulate() //SLOT
{
if( !m_dynamicMode )
return;
// Repopulate the upcoming tracks
MyIt it( this, MyIt::All );
TQPtrList<TQListViewItem> list;
for( ; *it; ++it )
{
PlaylistItem *item = static_cast<PlaylistItem *>(*it);
int queueIndex = m_nextTracks.findRef( item );
bool isQueued = queueIndex != -1;
bool isMarker = item->isEmpty();
// markers are used by playlistloader, and removing them is not good
if( !item->isDynamicEnabled() || item == m_currentTrack || isQueued || isMarker )
continue;
list.prepend( *it );
}
saveUndoState();
//remove the items
for( TQListViewItem *item = list.first(); item; item = list.next() )
{
removeItem( static_cast<PlaylistItem*>( item ) );
delete item;
}
//calling advanceDynamicTrack will remove an item too, which is undesirable
//block signals to avoid saveUndoState being called
blockSignals( true );
addDynamicModeTracks( dynamicMode()->upcomingCount() );
blockSignals( false );
}
void
Playlist::shuffle() //SLOT
{
if( dynamicMode() )
return;
TQPtrList<TQListViewItem> list;
setSorting( NO_SORT );
// shuffle only VISIBLE entries
for( MyIt it( this ); *it; ++it )
list.append( *it );
// we do it in two steps because the iterator doesn't seem
// to like it when we do takeItem and ++it in the same loop
for( TQListViewItem *item = list.first(); item; item = list.next() )
takeItem( item );
//shuffle
KRandomSequence( (long)TDEApplication::random() ).randomize( &list );
//reinsert in new order
for( TQListViewItem *item = list.first(); item; item = list.next() )
insertItem( item );
updateNextPrev();
ScriptManager::instance()->notifyPlaylistChange("reordered");
}
void
Playlist::removeSelectedItems() //SLOT
{
if( isLocked() ) return;
//assemble a list of what needs removing
//calling removeItem() iteratively is more efficient if they are in _reverse_ order, hence the prepend()
PLItemList queued, list;
int dontReplaceDynamic = 0;
for( PlaylistIterator it( this, MyIt::Selected ); *it; ++it )
{
if( !(*it)->isDynamicEnabled() )
dontReplaceDynamic++;
( m_nextTracks.contains( *it ) ? queued : list ).prepend( *it );
}
if( (int)list.count() == childCount() )
{
//clear() will saveUndoState for us.
clear(); // faster
return;
}
if( list.isEmpty() && queued.isEmpty() ) return;
saveUndoState();
if( dynamicMode() )
{
int currentTracks = childCount();
int minTracks = dynamicMode()->upcomingCount();
if( m_currentTrack )
currentTracks -= currentTrackIndex() + 1;
int difference = currentTracks - minTracks;
if( difference >= 0 )
difference -= list.count();
if( difference < 0 )
{
addDynamicModeTracks( -difference );
}
}
//remove the items
if( queued.count() )
{
for( TQListViewItem *item = queued.first(); item; item = queued.next() )
removeItem( static_cast<PlaylistItem*>( item ), true );
emit queueChanged( PLItemList(), queued );
for( TQListViewItem *item = queued.first(); item; item = queued.next() )
delete item;
}
for( TQListViewItem *item = list.first(); item; item = list.next() )
{
removeItem( static_cast<PlaylistItem*>( item ) );
delete item;
}
updateNextPrev();
ScriptManager::instance()->notifyPlaylistChange("changed");
//NOTE no need to emit childCountChanged(), removeItem() does that for us
//select next item in list
setSelected( currentItem(), true );
}
void
Playlist::deleteSelectedFiles() //SLOT
{
if( isLocked() ) return;
KURL::List urls;
//assemble a list of what needs removing
for( MyIt it( this, MyIt::Selected );
it.current();
urls << static_cast<PlaylistItem*>( *it )->url(), ++it );
if( DeleteDialog::showTrashDialog(this, urls) )
{
CollectionDB::instance()->removeSongs( urls );
removeSelectedItems();
foreachType( KURL::List, urls )
CollectionDB::instance()->emitFileDeleted( (*it).path() );
TQTimer::singleShot( 0, CollectionView::instance(), TQT_SLOT( renderView() ) );
}
}
void
Playlist::removeDuplicates() //SLOT
{
// Remove dead entries:
for( TQListViewItemIterator it( this ); it.current(); ) {
PlaylistItem* item = static_cast<PlaylistItem*>( *it );
const KURL url = item->url();
if ( url.isLocalFile() && !TQFile::exists( url.path() ) ) {
removeItem( item );
++it;
delete item;
}
else ++it;
}
// Remove dupes:
TQSortedList<PlaylistItem> list;
for( TQListViewItemIterator it( this ); it.current(); ++it )
list.prepend( static_cast<PlaylistItem*>( it.current() ) );
list.sort();
TQPtrListIterator<PlaylistItem> it( list );
PlaylistItem *item;
while( (item = it.current()) ) {
const KURL &compare = item->url();
++it;
if ( *it && compare == it.current()->url() ) {
removeItem( item );
delete item;
}
}
}
void
Playlist::copyToClipboard( const TQListViewItem *item ) const //SLOT
{
if( !item ) item = currentTrack();
if( item )
{
const PlaylistItem* playlistItem = static_cast<const PlaylistItem*>( item );
TQString text = playlistItem->prettyTitle();
// For streams add the streamtitle too
//TODO make prettyTitle do this
if ( playlistItem->url().protocol() == "http" )
text.append( " :: " + playlistItem->url().url() );
// Copy both to clipboard and X11-selection
TQApplication::clipboard()->setText( text, TQClipboard::Clipboard );
TQApplication::clipboard()->setText( text, TQClipboard::Selection );
Amarok::OSD::instance()->OSDWidget::show( i18n( "Copied: %1" ).arg( text ),
TQImage(CollectionDB::instance()->albumImage(*playlistItem )) );
}
}
void Playlist::undo() //SLOT
{
if( !isLocked() )
switchState( m_undoList, m_redoList );
}
void Playlist::redo() //SLOT
{
if( !isLocked() )
switchState( m_redoList, m_undoList );
}
void
Playlist::updateMetaData( const MetaBundle &mb ) //SLOT
{
SHOULD_BE_GUI
for( MyIt it( this, MyIt::All ); *it; ++it )
if( mb.url() == (*it)->url() )
{
(*it)->copyFrom( mb );
(*it)->filter( m_filter );
}
}
void
Playlist::adjustColumn( int n )
{
if( n == PlaylistItem::Rating )
setColumnWidth( n, PlaylistItem::ratingColumnWidth() );
else if( n == PlaylistItem::Mood )
setColumnWidth( n, 120 );
else
TDEListView::adjustColumn( n );
}
void
Playlist::showQueueManager()
{
DEBUG_BLOCK
// Only show the dialog once
if( QueueManager::instance() ) {
QueueManager::instance()->raise();
return;
}
QueueManager dialog;
if( dialog.exec() == TQDialog::Accepted )
{
changeFromQueueManager(dialog.newQueue());
}
}
void
Playlist::changeFromQueueManager(TQPtrList<PlaylistItem> list)
{
PLItemList oldQueue = m_nextTracks;
m_nextTracks = list;
PLItemList in, out;
// make sure we repaint items no longer queued
for( PlaylistItem* item = oldQueue.first(); item; item = oldQueue.next() )
if( !m_nextTracks.containsRef( item ) )
out << item;
for( PlaylistItem* item = m_nextTracks.first(); item; item = m_nextTracks.next() )
if( !oldQueue.containsRef( item ) )
in << item;
emit queueChanged( in, out );
// repaint newly queued or altered queue items
if( dynamicMode() )
sortQueuedItems();
else
refreshNextTracks();
}
void
Playlist::setFilterSlot( const TQString &query ) //SLOT
{
m_filtertimer->stop();
if( m_filter != query )
{
m_prevfilter = m_filter;
m_filter = query;
}
m_filtertimer->start( 50, true );
}
void
Playlist::setDelayedFilter() //SLOT
{
setFilter( m_filter );
//to me it seems sensible to do this, BUT if it seems annoying to you, remove it
showCurrentTrack();
}
void
Playlist::setFilter( const TQString &query ) //SLOT
{
const bool advanced = ExpressionParser::isAdvancedExpression( query );
MyIt it( this, ( !advanced && query.lower().contains( m_prevfilter.lower() ) )
? MyIt::Visible
: MyIt::All );
if( advanced )
{
ParsedExpression parsed = ExpressionParser::parse( query );
TQValueList<int> visible = visibleColumns();
for(; *it; ++it )
(*it)->setVisible( (*it)->matchesParsedExpression( parsed, visible ) );
}
else {
// optimized path
const TQStringList terms = TQStringList::split( ' ', query.lower() );
const MetaBundle::ColumnMask visible = getVisibleColumnMask();
for(; *it; ++it ) {
(*it)->setVisible( (*it)->matchesFast(terms, visible));
}
}
if( m_filter != query )
{
m_prevfilter = m_filter;
m_filter = query;
}
updateNextPrev();
}
void
Playlist::scoreChanged( const TQString &path, float score )
{
for( MyIt it( this, MyIt::All ); *it; ++it )
{
PlaylistItem *item = static_cast<PlaylistItem*>( *it );
if ( item->url().path() == path )
{
item->setScore( score );
item->setPlayCount( CollectionDB::instance()->getPlayCount( path ) );
item->setLastPlay( CollectionDB::instance()->getLastPlay( path ).toTime_t() );
item->filter( m_filter );
}
}
}
void
Playlist::ratingChanged( const TQString &path, int rating )
{
for( MyIt it( this, MyIt::All ); *it; ++it )
{
PlaylistItem *item = static_cast<PlaylistItem*>( *it );
if ( item->url().path() == path )
{
item->setRating( rating );
item->filter( m_filter );
}
}
}
void
Playlist::fileMoved( const TQString &srcPath, const TQString &dstPath )
{
// Make sure the MoodServer gets this signal first!
MoodServer::instance()->slotFileMoved( srcPath, dstPath );
for( MyIt it( this, MyIt::All ); *it; ++it )
{
PlaylistItem *item = static_cast<PlaylistItem*>( *it );
if ( item->url().path() == srcPath )
{
item->setUrl( KURL::fromPathOrURL( dstPath ) );
item->filter( m_filter );
}
}
}
void
Playlist::appendToPreviousTracks( PlaylistItem *item )
{
if( !m_prevTracks.containsRef( item ) )
{
m_total -= item->totalIncrementAmount();
m_prevTracks.append( item );
}
}
void
Playlist::appendToPreviousAlbums( PlaylistAlbum *album )
{
if( !m_prevAlbums.containsRef( album ) )
{
m_total -= album->total;
m_prevAlbums.append( album );
}
}
void
Playlist::removeFromPreviousTracks( PlaylistItem *item )
{
if( item )
{
if( m_prevTracks.removeRef( item ) )
m_total += item->totalIncrementAmount();
}
else if( (item = m_prevTracks.current()) != 0 )
if( m_prevTracks.remove() )
m_total += item->totalIncrementAmount();
}
void
Playlist::removeFromPreviousAlbums( PlaylistAlbum *album )
{
if( album )
{
if( m_prevAlbums.removeRef( album ) )
m_total += album->total;
}
else if( (album = m_prevAlbums.current()) != 0 )
if( m_prevAlbums.remove() )
m_total += album->total;
}
void
Playlist::showContextMenu( TQListViewItem *item, const TQPoint &p, int col ) //SLOT
{
//if clicked on an empty area
enum { REPOPULATE, ENABLEDYNAMIC };
if( item == 0 )
{
TDEPopupMenu popup;
Amarok::actionCollection()->action("playlist_save")->plug( &popup );
Amarok::actionCollection()->action("playlist_clear")->plug( &popup );
DynamicMode *m = 0;
if(dynamicMode())
popup.insertItem( SmallIconSet( Amarok::icon( "dynamic" ) ), i18n("Repopulate"), REPOPULATE);
else
{
Amarok::actionCollection()->action("playlist_shuffle")->plug( &popup );
m = PlaylistBrowser::instance()->findDynamicModeByTitle( AmarokConfig::lastDynamicMode() );
if( m )
popup.insertItem( SmallIconSet( Amarok::icon( "dynamic" ) ), i18n("L&oad %1").arg( m->title().replace( '&', "&&" ) ), ENABLEDYNAMIC);
}
switch(popup.exec(p))
{
case ENABLEDYNAMIC:
loadDynamicMode( m );
break;
case REPOPULATE: repopulate(); break;
}
return;
}
#define item static_cast<PlaylistItem*>(item)
enum {
PLAY, PLAY_NEXT, STOP_DONE, VIEW, EDIT, FILL_DOWN, COPY, CROP_PLAYLIST, SAVE_PLAYLIST, REMOVE, FILE_MENU, ORGANIZE, MOVE_TO_COLLECTION, COPY_TO_COLLECTION, DELETE,
TRASH, REPEAT, LAST }; //keep LAST last
const bool canRename = isRenameable( col ) && item->url().isLocalFile();
const bool isCurrent = (item == m_currentTrack);
const bool isPlaying = EngineController::engine()->state() == Engine::Playing;
const bool trackColumn = col == PlaylistItem::Track;
const bool isLastFm = item->url().protocol() == "lastfm";
const TQString tagName = columnText( col );
const TQString tag = item->text( col );
uint itemCount = 0;
for( MyIt it( this, MyIt::Selected ); *it; ++it )
itemCount++;
PrettyPopupMenu popup;
// if(itemCount==1)
// popup.insertTitle( KStringHandler::rsqueeze( MetaBundle( item ).prettyTitle(), 50 ));
// else
// popup.insertTitle(i18n("1 Track", "%n Selected Tracks", itemCount));
if( isCurrent && isLastFm )
{
TDEActionCollection *ac = Amarok::actionCollection();
if( ac->action( "skip" ) ) ac->action( "skip" )->plug( &popup );
if( ac->action( "love" ) ) ac->action( "love" )->plug( &popup );
if( ac->action( "ban" ) ) ac->action( "ban" )->plug( &popup );
popup.insertSeparator();
}
if( !isCurrent || !isPlaying )
popup.insertItem( SmallIconSet( Amarok::icon( "play" ) ), isCurrent && isPlaying
? i18n( "&Restart" )
: i18n( "&Play" ), PLAY );
if( isCurrent && !isLastFm && isPlaying )
Amarok::actionCollection()->action( "pause" )->plug( &popup );
// Begin queue entry logic
popup.insertItem( SmallIconSet( Amarok::icon( "queue_track" ) ), i18n("&Queue Selected Tracks"), PLAY_NEXT );
bool queueToggle = false;
MyIt it( this, MyIt::Selected );
bool firstQueued = ( m_nextTracks.findRef( *it ) != -1 );
for( ++it ; *it; ++it ) {
if ( ( m_nextTracks.findRef( *it ) != -1 ) != firstQueued ) {
queueToggle = true;
break;
}
}
if( itemCount == 1 )
{
if ( !firstQueued )
popup.changeItem( PLAY_NEXT, i18n( "&Queue Track" ) );
else
popup.changeItem( PLAY_NEXT, SmallIconSet( Amarok::icon( "dequeue_track" ) ), i18n("&Dequeue Track") );
} else {
if ( queueToggle )
popup.changeItem( PLAY_NEXT, i18n( "Toggle &Queue Status (1 track)", "Toggle &Queue Status (%n tracks)", itemCount ) );
else
// remember, queueToggled only gets set to false if there are items queued and not queued.
// so, if queueToggled is false, all items have the same queue status as the first item.
if ( !firstQueued )
popup.changeItem( PLAY_NEXT, i18n( "&Queue Selected Tracks" ) );
else
popup.changeItem( PLAY_NEXT, SmallIconSet( Amarok::icon( "dequeue_track" ) ), i18n("&Dequeue Selected Tracks") );
}
// End queue entry logic
bool afterCurrent = false;
if( !m_nextTracks.isEmpty() ? m_nextTracks.getLast() : m_currentTrack )
for( MyIt it( !m_nextTracks.isEmpty() ? m_nextTracks.getLast() : m_currentTrack, MyIt::Visible ); *it; ++it )
if( *it == item )
{
afterCurrent = true;
break;
}
if( itemCount == 1 )
{
Amarok::actionCollection()->action( "stop_after" )->plug( &popup );
dynamic_cast<TDEToggleAction *>( Amarok::actionCollection()->action( "stop_after" ) )->setChecked( m_stopAfterTrack == item );
}
if( isCurrent && itemCount == 1 )
{
popup.insertItem( SmallIconSet( Amarok::icon( "repeat_track" ) ), i18n( "&Repeat Track" ), REPEAT );
popup.setItemChecked( REPEAT, Amarok::repeatTrack() );
}
popup.insertSeparator();
if( itemCount > 1 )
{
popup.insertItem( SmallIconSet( Amarok::icon( "playlist" ) ), i18n("&Set as Playlist (Crop)"), CROP_PLAYLIST );
popup.insertItem( SmallIconSet( Amarok::icon( "save" ) ), i18n("S&ave as Playlist..."), SAVE_PLAYLIST );
}
popup.insertItem( SmallIconSet( Amarok::icon( "remove_from_playlist" ) ), i18n( "Re&move From Playlist" ), this, TQT_SLOT( removeSelectedItems() ), Key_Delete, REMOVE );
popup.insertSeparator();
TDEPopupMenu fileMenu;
if( CollectionDB::instance()->isDirInCollection( item->url().directory() ) )
{
fileMenu.insertItem( SmallIconSet( "document-save-as" ), i18n("&Organize File...", "&Organize %n Files...", itemCount), ORGANIZE );
}
else
{
fileMenu.insertItem( SmallIconSet( "document-save-as" ), i18n("&Copy Track to Collection...", "&Copy %n Tracks to Collection...", itemCount), COPY_TO_COLLECTION );
fileMenu.insertItem( SmallIconSet( "document-save-as" ), i18n("&Move Track to Collection...", "&Move %n Tracks to Collection...", itemCount), MOVE_TO_COLLECTION );
}
fileMenu.insertItem( SmallIconSet( Amarok::icon( "remove" ) ), i18n("&Delete File...", "&Delete %n Selected Files...", itemCount ), this, TQT_SLOT( deleteSelectedFiles() ), SHIFT+Key_Delete, DELETE );
popup.insertItem( SmallIconSet( Amarok::icon( "files" ) ), i18n("Manage &Files"), &fileMenu, FILE_MENU );
if( itemCount == 1 )
popup.insertItem( SmallIconSet( Amarok::icon( "edit-copy" ) ), i18n( "&Copy Tags to Clipboard" ), COPY );
if( itemCount > 1 )
popup.insertItem( trackColumn
? i18n("Iteratively Assign Track &Numbers")
: i18n("&Write '%1' for Selected Tracks")
.arg( KStringHandler::rsqueeze( tag, 30 ).replace( "&", "&&" ) ), FILL_DOWN );
popup.insertItem( SmallIconSet( Amarok::icon( "edit" ) ), (itemCount == 1
? i18n( "&Edit Tag '%1'" )
: i18n( "&Edit '%1' Tag for Selected Tracks" )).arg( tagName ), EDIT );
popup.insertItem( SmallIconSet( Amarok::icon( "info" ) )
, item->url().isLocalFile() ?
i18n( "Edit Track &Information...", "Edit &Information for %n Tracks...", itemCount):
i18n( "Track &Information...", "&Information for %n Tracks...", itemCount)
, VIEW );
popup.setItemEnabled( EDIT, canRename ); //only enable for columns that have editable tags
popup.setItemEnabled( FILL_DOWN, canRename );
popup.setItemEnabled( REMOVE, !isLocked() ); // can't remove things when playlist is locked,
popup.setItemEnabled( DELETE, !isLocked() && item->url().isLocalFile() );
popup.setItemEnabled( ORGANIZE, !isLocked() && item->isKioUrl() );
popup.setItemEnabled( MOVE_TO_COLLECTION, !isLocked() && item->isKioUrl() );
popup.setItemEnabled( COPY_TO_COLLECTION, !isLocked() && item->isKioUrl() );
popup.setItemEnabled( VIEW, item->url().isLocalFile() || itemCount == 1 ); // disable for CDAudio multiselection
if( m_customSubmenuItem.count() > 0 )
popup.insertSeparator();
TQValueList<TQString> submenuTexts = m_customSubmenuItem.keys();
for( TQValueList<TQString>::Iterator keyIt =submenuTexts.begin(); keyIt != submenuTexts.end(); ++keyIt )
{
TDEPopupMenu* menu;
if( (*keyIt) == "root")
menu = &popup;
else
{
menu = new TDEPopupMenu();
popup.insertItem( *keyIt, menu);
}
foreach(m_customSubmenuItem[*keyIt])
{
int id;
if(m_customIdItem.isEmpty())
id=LAST;
else
id=m_customIdItem.keys().last()+1;
menu->insertItem( (*it), id );
m_customIdItem[id]= (*keyIt) + ' ' + (*it);
}
}
const TQPoint pos( p.x() - popup.sidePixmapWidth(), p.y() + 3 );
int menuItemId = popup.exec( pos );
PLItemList in, out;
switch( menuItemId )
{
case PLAY:
if( itemCount == 1 )
{
//Restarting track on dynamic mode
if( isCurrent && isPlaying && dynamicMode() )
m_dynamicDirt = true;
activate( item );
}
else
{
MyIt it( this, MyIt::Selected );
activate( *it );
++it;
for( int i = 0; *it; ++i, ++it )
{
in.append( *it );
m_nextTracks.insert( i, *it );
}
emit queueChanged( in, out );
}
break;
case PLAY_NEXT:
queueSelected();
break;
case VIEW:
showTagDialog( selectedItems() );
break;
case EDIT:
// do this because TQListView sucks, if track change occurs during
// an edit event, the rename operation ends, BUT, the list is not
// cleared because writeTag is never called. Q/K ListView sucks
m_itemsToChangeTagsFor.clear();
if( !item->isSelected() )
m_itemsToChangeTagsFor.append( item );
else
for( MyIt it( this, MyIt::Selected ); *it; ++it )
m_itemsToChangeTagsFor.append( *it );
rename( item, col );
break;
case FILL_DOWN:
//Spreadsheet like fill-down
{
TQString newTag = item->exactText( col );
MyIt it( this, MyIt::Selected );
//special handling for track column
uint trackNo = (*it)->track();
//we should start at the next row if we are doing track number
//and the first row has a number set
if ( trackColumn && trackNo > 0 )
++it;
ThreadManager::JobList jobs;
bool updateView = true;
for( ; *it; ++it ) {
if ( trackColumn )
//special handling for track column
newTag = TQString::number( ++trackNo );
else if ( *it == item )
//skip the one we are copying
continue;
else if( col == PlaylistItem::Score )
{
CollectionDB::instance()->setSongPercentage( (*it)->url().path(), newTag.toInt() );
continue;
}
else if( col == PlaylistItem::Rating )
{
CollectionDB::instance()->setSongRating( (*it)->url().path(), newTag.toInt() );
continue;
}
if ( !(*it)->isEditing( col ) )
jobs.prepend( new TagWriter( *it, (*it)->exactText( col ), newTag, col, updateView ) );
updateView = false;
}
ThreadManager::instance()->queueJobs( jobs );
}
break;
case COPY:
copyToClipboard( item );
break;
case CROP_PLAYLIST:
if( !isLocked() )
{
//use "in" for the other just because it's there and not used otherwise
for( MyIt it( this, MyIt::Unselected | MyIt::Visible ); *it; ++it )
( m_nextTracks.containsRef( *it ) ? in : out ).append( *it );
if( !in.isEmpty() || !out.isEmpty() )
{
saveUndoState();
for( PlaylistItem *it = out.first(); it; it = out.next() )
removeItem( it, true );
if( !out.isEmpty() )
emit queueChanged( PLItemList(), out );
for( PlaylistItem *it = out.first(); it; it = out.next() )
delete it;
for( PlaylistItem *it = in.first(); it; it = in.next() )
{
removeItem( it );
delete it;
}
ScriptManager::instance()->notifyPlaylistChange("cleared");
}
}
break;
case SAVE_PLAYLIST:
saveSelectedAsPlaylist();
break;
case REPEAT:
// FIXME HACK Accessing AmarokConfig::Enum* yields compile errors with GCC 3.3.
static_cast<TDESelectAction*>( Amarok::actionCollection()->action( "repeat" ) )
->setCurrentItem( Amarok::repeatTrack()
? 0 /*AmarokConfig::EnumRepeat::Off*/
: 1 /*AmarokConfig::EnumRepeat::Track*/ );
break;
case ORGANIZE:
case MOVE_TO_COLLECTION:
case COPY_TO_COLLECTION:
{
KURL::List list;
for( TQListViewItemIterator it( this, TQListViewItemIterator::Selected );
it.current();
++it )
{
PlaylistItem *i= static_cast<PlaylistItem*>(*it);
KURL url = i->url();
list << url;
}
bool organize = CollectionDB::instance()->isDirInCollection( item->url().directory() );
bool move = menuItemId==MOVE_TO_COLLECTION;
CollectionView::instance()->organizeFiles( list,
organize ? i18n( "Organize Files" ) : move ? i18n( "Move Tracks to Collection" ) : i18n( "Copy Tracks to Collection"),
!organize && !move );
}
break;
default:
if(menuItemId < LAST)
break;
customMenuClicked(menuItemId);
break;
}
#undef item
}
////////////////////////////////////////////////////////////////////////////////
/// Misc Protected Methods
////////////////////////////////////////////////////////////////////////////////
void
Playlist::fontChange( const TQFont &old )
{
TDEListView::fontChange( old );
initStarPixmaps();
triggerUpdate();
}
void
Playlist::contentsMouseMoveEvent( TQMouseEvent *e )
{
if( e )
TDEListView::contentsMouseMoveEvent( e );
PlaylistItem *prev = m_hoveredRating;
const TQPoint pos = e ? e->pos() : viewportToContents( viewport()->mapFromGlobal( TQCursor::pos() ) );
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( contentsToViewport( pos ) ) );
if( item && pos.x() > header()->sectionPos( PlaylistItem::Rating ) &&
pos.x() < header()->sectionPos( PlaylistItem::Rating ) + header()->sectionSize( PlaylistItem::Rating ) )
{
m_hoveredRating = item;
m_hoveredRating->updateColumn( PlaylistItem::Rating );
}
else
m_hoveredRating = 0;
if( prev )
{
if( m_selCount > 1 && prev->isSelected() )
TQScrollView::updateContents( header()->sectionPos( PlaylistItem::Rating ) + 1, contentsY(),
header()->sectionSize( PlaylistItem::Rating ) - 2, visibleHeight() );
else
prev->updateColumn( PlaylistItem::Rating );
}
}
void Playlist::leaveEvent( TQEvent *e )
{
TDEListView::leaveEvent( e );
PlaylistItem *prev = m_hoveredRating;
m_hoveredRating = 0;
if( prev )
prev->updateColumn( PlaylistItem::Rating );
}
void Playlist::contentsMousePressEvent( TQMouseEvent *e )
{
PlaylistItem *item = static_cast<PlaylistItem*>( itemAt( contentsToViewport( e->pos() ) ) );
int beginRatingSection = header()->sectionPos( PlaylistItem::Rating );
int endRatingSection = beginRatingSection + header()->sectionSize( PlaylistItem::Rating );
/// Conditions on setting the rating of an item
if( item &&
!( e->state() & TQt::ControlButton || e->state() & TQt::ShiftButton ) && // skip if ctrl or shift held
( e->button() & Qt::LeftButton ) && // only on a left click
( e->pos().x() > beginRatingSection && e->pos().x() < endRatingSection ) ) // mouse over rating column
{
int rating = item->ratingAtPoint( e->pos().x() );
if( item->isSelected() )
setSelectedRatings( rating );
else // toggle half star
CollectionDB::instance()->setSongRating( item->url().path(), rating, true );
}
else
TDEListView::contentsMousePressEvent( e );
}
void Playlist::contentsWheelEvent( TQWheelEvent *e )
{
PlaylistItem* const item = static_cast<PlaylistItem*>( itemAt( contentsToViewport( e->pos() ) ) );
const int column = header()->sectionAt( e->pos().x() );
const int distance = header()->sectionPos( column ) + header()->sectionSize( column ) - e->pos().x();
const int maxdistance = fontMetrics().width( TQString::number( m_nextTracks.count() ) ) + 7;
if( item && column == m_firstColumn && distance <= maxdistance && item->isQueued() )
{
const int n = e->delta() / 120,
s = n / abs(n),
pos = item->queuePosition();
PLItemList changed;
for( int i = 1; i <= abs(n); ++i )
{
const int dest = pos + s*i;
if( kClamp( dest, 0, int( m_nextTracks.count() ) - 1 ) != dest )
break;
PlaylistItem* const p = m_nextTracks.at( dest );
if( changed.findRef( p ) == -1 )
changed << p;
if( changed.findRef( m_nextTracks.at( dest - s ) ) == -1 )
changed << m_nextTracks.at( dest - s );
m_nextTracks.replace( dest, m_nextTracks.at( dest - s ) );
m_nextTracks.replace( dest - s, p );
}
for( int i = 0, n = changed.count(); i < n; ++i )
changed.at(i)->update();
}
else
TDEListView::contentsWheelEvent( e );
}
////////////////////////////////////////////////////////////////////////////////
/// Misc Private Methods
////////////////////////////////////////////////////////////////////////////////
void
Playlist::lock()
{
if( m_lockStack == 0 ) {
m_clearButton->setEnabled( false );
m_undoButton->setEnabled( false );
m_redoButton->setEnabled( false );
}
m_lockStack++;
}
void
Playlist::unlock()
{
Q_ASSERT( m_lockStack > 0 );
m_lockStack--;
if( m_lockStack == 0 ) {
m_clearButton->setEnabled( true );
m_undoButton->setEnabled( !m_undoList.isEmpty() );
m_redoButton->setEnabled( !m_redoList.isEmpty() );
}
}
int
Playlist::numVisibleColumns() const
{
int r = 0, i = 1;
for( const int n = columns(); i <= n; ++i)
if( columnWidth( i - 1 ) )
++r;
return r;
}
TQValueList<int> Playlist::visibleColumns() const
{
TQValueList<int> r;
for( int i = 0, n = columns(); i < n; ++i)
if( columnWidth( i ) )
r.append( i );
return r;
}
MetaBundle::ColumnMask Playlist::getVisibleColumnMask() const {
MetaBundle::ColumnMask mask = 0;
for( int i = 0, n = columns(); i < n; ++i)
if( columnWidth( i ) ) mask = mask | (1 << i);
return mask;
}
int
Playlist::mapToLogicalColumn( int physical ) const
{
int logical = header()->mapToSection( physical );
//skip hidden columns
int n = 0;
for( int i = 0; i <= physical; ++i )
if( !header()->sectionSize( header()->mapToSection( physical - i ) ) )
++n;
while( n )
{
logical = header()->mapToSection( ++physical );
if( logical < 0 )
{
logical = header()->mapToSection( physical - 1 );
break;
}
if( header()->sectionSize( logical ) )
--n;
}
return logical;
}
void
Playlist::setColumns( TQValueList<int> order, TQValueList<int> visible )
{
for( int i = order.count() - 1; i >= 0; --i )
header()->moveSection( order[i], i );
for( int i = 0; i < PlaylistItem::NUM_COLUMNS; ++i )
{
if( visible.contains( i ) )
adjustColumn( i );
else
hideColumn( i );
}
columnOrderChanged();
}
void
Playlist::removeItem( PlaylistItem *item, bool multi )
{
// NOTE we don't check isLocked() here as it is assumed that if you call this function you
// really want to remove the item, there is no way the user can reach here without passing
// a lock() check, (currently...)
//this function ensures we don't have dangling pointers to items that are about to be removed
//for some reason using TQListView::takeItem() and TQListViewItem::takeItem() was ineffective
//NOTE we don't delete item for you! You must call delete item yourself :)
//TODO there must be a way to do this without requiring notification from the item dtor!
//NOTE orginally this was in ~PlaylistItem(), but that caused crashes due to clear() *shrug*
//NOTE items already removed by takeItem() will crash if you call nextSibling() on them
// taken items return 0 from listView()
//FIXME if you remove a series of items including the currentTrack and all the nextTracks
// then no new nextTrack will be selected and the playlist will resume from the begging
// next time
if( m_currentTrack == item )
{
setCurrentTrack( 0 );
//ensure the playlist doesn't start at the beginning after the track that's playing ends
//we don't need to do that in random mode, it's getting randomly selected anyways
if( m_nextTracks.isEmpty() && !AmarokConfig::randomMode() )
{
//*MyIt( item ) returns either "item" or if item is hidden, the next visible playlistitem
PlaylistItem* const next = *MyIt( item );
if( next )
{
m_nextTracks.append( next );
next->update();
}
}
}
if( m_stopAfterTrack == item ) {
m_stopAfterTrack = 0; //to be safe
if (stopAfterMode() != StopAfterCurrent)
setStopAfterMode( DoNotStop );
}
//cancel rename if it is pending (Bug: #147587)
if ( m_itemToRename == item ) {
m_clicktimer->stop();
m_itemToRename = 0;
}
//keep m_nextTracks queue synchronized
if( m_nextTracks.removeRef( item ) && !multi )
emit queueChanged( PLItemList(), PLItemList( item ) );
//keep recent buffer synchronized
removeFromPreviousTracks( item ); //removes all pointers to item
updateNextPrev();
}
void Playlist::ensureItemCentered( TQListViewItem *item )
{
if( !item )
return;
//HACK -- apparently the various metrics aren't reliable while the UI is still updating & stuff
m_itemToReallyCenter = item;
TQTimer::singleShot( 0, this, TQT_SLOT( reallyEnsureItemCentered() ) );
}
void
Playlist::reallyEnsureItemCentered()
{
if( TQListViewItem *item = m_itemToReallyCenter )
{
m_itemToReallyCenter = 0;
if( m_selCount == 1 )
{
PlaylistItem *previtem = *MyIt( this, MyIt::Selected );
if( previtem && previtem != item )
previtem->setSelected( false );
}
setCurrentItem( item );
ensureVisible( contentsX(), item->itemPos() + item->height() / 2, 0, visibleHeight() / 2 );
triggerUpdate();
}
}
void
Playlist::refreshNextTracks( int from )
{
// This function scans the m_nextTracks list starting from the 'from'
// position and from there on updates the progressive numbering on related
// items and repaints them. In short it performs an update subsequent to
// a renumbering/order changing at some point of the m_nextTracks list.
//start on the 'from'-th item of the list
for( PlaylistItem* item = (from == -1) ? m_nextTracks.current() : m_nextTracks.at( from );
item;
item = m_nextTracks.next() )
{
item->update();
}
}
void
Playlist::saveUndoState() //SLOT
{
if( saveState( m_undoList ) )
{
m_redoList.clear();
m_undoButton->setEnabled( true );
m_redoButton->setEnabled( false );
}
}
bool
Playlist::saveState( TQStringList &list )
{
//used by undo system, save state of playlist to undo/redo list
//do not change this! It's required by the undo/redo system to work!
//if you must change this, fix undo/redo first. Ask me what needs fixing <mxcl>
if( !isEmpty() )
{
TQString fileName;
m_undoCounter %= AmarokConfig::undoLevels();
fileName.setNum( m_undoCounter++ );
fileName.prepend( m_undoDir.absPath() + '/' );
fileName.append( ".xml" );
if ( list.count() >= (uint)AmarokConfig::undoLevels() )
{
m_undoDir.remove( list.first() );
list.pop_front();
}
saveXML( fileName );
list.append( fileName );
// Reset isNew state of all items in the playlist (determines font coloring)
PlaylistItem* item = static_cast<PlaylistItem*>( firstChild() );
while( item ) {
item->setIsNew( false );
item = item->nextSibling();
}
triggerUpdate();
return true;
}
return false;
}
void
Playlist::switchState( TQStringList &loadFromMe, TQStringList &saveToMe )
{
m_undoDirt = true;
//switch to a previously saved state, remember current state
KURL url; url.setPath( loadFromMe.last() );
loadFromMe.pop_back();
//save current state
saveState( saveToMe );
//this is clear() minus some parts, for instance we don't want to cause a saveUndoState() here
m_currentTrack = 0;
disableDynamicMode();
Glow::reset();
m_prevTracks.clear();
m_prevAlbums.clear();
const PLItemList prev = m_nextTracks;
m_nextTracks.clear();
emit queueChanged( PLItemList(), prev );
ThreadManager::instance()->abortAllJobsNamed( "TagWriter" );
safeClear();
m_total = 0;
m_albums.clear();
insertMediaInternal( url, 0, 0 ); //because the listview is empty, undoState won't be forced
m_undoButton->setEnabled( !m_undoList.isEmpty() );
m_redoButton->setEnabled( !m_redoList.isEmpty() );
if( dynamicMode() ) setDynamicHistory( true );
m_undoDirt = false;
}
void
Playlist::saveSelectedAsPlaylist()
{
MyIt it( this, MyIt::Visible | MyIt::Selected );
if( !(*it) )
return; //safety
const TQString album = (*it)->album(),
artist = (*it)->artist();
int suggestion = !album.stripWhiteSpace().isEmpty() ? 1 : !artist.stripWhiteSpace().isEmpty() ? 2 : 3;
while( *it )
{
if( suggestion == 1 && (*it)->album()->lower().stripWhiteSpace() != album.lower().stripWhiteSpace() )
suggestion = 2;
if( suggestion == 2 && (*it)->artist()->lower().stripWhiteSpace() != artist.lower().stripWhiteSpace() )
suggestion = 3;
if( suggestion == 3 )
break;
++it;
}
TQString path = PlaylistDialog::getSaveFileName( suggestion == 1 ? album
: suggestion == 2 ? artist
: i18n( "Untitled" ) );
if( path.isEmpty() )
return;
TQValueList<KURL> urls;
TQValueList<TQString> titles;
TQValueList<int> lengths;
for( it = MyIt( this, MyIt::Visible | MyIt::Selected ); *it; ++it )
{
urls << (*it)->url();
titles << (*it)->title();
lengths << (*it)->length();
}
if( PlaylistBrowser::savePlaylist( path, urls, titles, lengths ) )
PlaylistWindow::self()->showBrowser( "PlaylistBrowser" );
}
void Playlist::initStarPixmaps()
{
StarManager::instance()->reinitStars( fontMetrics().height(), itemMargin() );
}
void
Playlist::slotMouseButtonPressed( int button, TQListViewItem *after, const TQPoint &p, int col ) //SLOT
{
switch( button )
{
case Qt::MidButton:
{
const TQString path = TQApplication::clipboard()->text( TQClipboard::Selection );
const KURL url = KURL::fromPathOrURL( path );
if( url.isValid() )
insertMediaInternal( url, static_cast<PlaylistItem*>(after ? after : lastItem()) );
break;
}
case Qt::RightButton:
showContextMenu( after, p, col );
break;
default:
;
}
}
void Playlist::slotContentsMoving()
{
Amarok::ToolTip::hideTips();
TQTimer::singleShot( 0, this, TQT_SLOT( contentsMouseMoveEvent() ) );
}
void
Playlist::slotQueueChanged( const PLItemList &/*in*/, const PLItemList &out)
{
for( TQPtrListIterator<PlaylistItem> it( out ); *it; ++it )
(*it)->update();
refreshNextTracks( 0 );
updateNextPrev();
}
void
Playlist::slotUseScores( bool use )
{
if( !use && columnWidth( MetaBundle::Score ) )
hideColumn( MetaBundle::Score );
}
void
Playlist::slotUseRatings( bool use )
{
if( use && !columnWidth( MetaBundle::Rating ) )
adjustColumn( MetaBundle::Rating );
else if( !use && columnWidth( MetaBundle::Rating ) )
hideColumn( MetaBundle::Rating );
}
// This gets called when the user presses "Ok" or "Apply" in the
// config dialog.
void
Playlist::slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic )
{
(void) moodier; (void) alter; (void) withMusic;
if( !show && columnWidth( MetaBundle::Mood ) )
hideColumn( MetaBundle::Mood );
// Reset all of our moodbars, since they may have been permanently
// disabled before because the Moodbar was disabled. We need to
// do this even if the column is hidden.
if( show )
{
// No need to call moodbar().load(), since that will happen
// automatically next time it's displayed. We do have to
// repaint so that they get displayed though.
for( PlaylistIterator it( this, PlaylistIterator::All ) ; *it ; ++it )
{
(*it)->moodbar().reset();
repaintItem(*it);
}
}
}
void
Playlist::slotGlowTimer() //SLOT
{
if( !currentTrack() ) return;
using namespace Glow;
if( counter <= STEPS*2 )
{
// 0 -> STEPS -> 0
const double d = (counter > STEPS) ? 2*STEPS-counter : counter;
{
using namespace Base;
PlaylistItem::glowIntensity = d;
PlaylistItem::glowBase = TQColor( r, g, b );
}
{
using namespace Text;
PlaylistItem::glowText = TQColor( r + int(d*dr), g + int(d*dg), b + int(d*db) );
}
if( currentTrack() )
currentTrack()->update();
}
++counter &= 63; //built in bounds checking with &=
}
void
Playlist::slotRepeatTrackToggled( int /* mode */ )
{
if( m_currentTrack )
m_currentTrack->update();
}
void
Playlist::slotEraseMarker() //SLOT
{
if( m_marker )
{
const TQRect spot = drawDropVisualizer( 0, 0, m_marker );
m_marker = 0;
viewport()->repaint( spot, false );
}
}
void
Playlist::showTagDialog( TQPtrList<TQListViewItem> items )
{
/// the tag dialog was once modal, because we thought that damage would occur
/// when passing playlist items into the editor and it was removed from the playlist.
/// This is simply not the case, information is written to the URL, not the item.
// Playlist::lock();
if( items.isEmpty() ) return;
if ( items.count() == 1 )
{
PlaylistItem *item = static_cast<PlaylistItem*>( items.first() );
bool isDaap = item->url().protocol() == "daap";
if ( !item->url().isLocalFile() && !isDaap )
{
StreamEditor dialog( this, item->title(), item->url().prettyURL(), true );
if( item->url().protocol() == "cdda" )
dialog.setCaption( i18n( "CD Audio" ) );
else
dialog.setCaption( i18n( "Remote Media" ) );
dialog.exec();
}
else if ( isDaap ) // don't check if exists
{
// The tag dialog automatically disables the widgets if the file is not local, which it is not.
TagDialog *dialog = new TagDialog( *item, item, instance() );
dialog->show();
}
else if ( checkFileStatus( item ) )
{
TagDialog *dialog = new TagDialog( *item, item, instance() );
dialog->show();
}
else
KMessageBox::sorry( this, i18n( "This file does not exist:" ) + ' ' + item->url().path() );
}
else {
//edit multiple tracks in tag dialog
KURL::List urls;
for( TQListViewItem *item = items.first(); item; item = items.next() )
if ( item->isVisible() )
urls << static_cast<PlaylistItem*>( item )->url();
TagDialog *dialog = new TagDialog( urls, instance() );
dialog->show();
}
// Playlist::unlock();
}
#include <kactivelabel.h>
#include <kdialog.h>
#include <kpushbutton.h>
#include <tqgroupbox.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqprocess.h>
#include <unistd.h> //usleep()
// Moved outside the only function that uses it because
// gcc 2.95 doesn't like class declarations there.
class CustomColumnDialog : public KDialog
{
public:
CustomColumnDialog( TQWidget *parent )
: KDialog( parent )
{
TQLabel *textLabel1, *textLabel2, *textLabel3;
TQLineEdit *lineEdit1, *lineEdit2;
TQGroupBox *groupBox1;
textLabel1 = new TQLabel( i18n(
"<p>You can create a custom column that runs a shell command against each item in the playlist. "
"The shell command is run as the user <b>nobody</b>, this is for security reasons.\n"
"<p>You can only run the command against local files for the time being. "
"The fullpath is inserted at the position <b>%f</b> in the string. "
"If you do not specify <b>%f</b> it is appended." ), this );
textLabel2 = new TQLabel( i18n( "Column &name:" ), this );
textLabel3 = new TQLabel( i18n( "&Command:" ), this );
lineEdit1 = new TQLineEdit( this, "ColumnName" );
lineEdit2 = new TQLineEdit( this, "Command" );
groupBox1 = new TQGroupBox( 1, Qt::Vertical, i18n( "Examples" ), this );
groupBox1->layout()->setMargin( 11 );
new KActiveLabel( i18n( "file --brief %f\n" "ls -sh %f\n" "basename %f\n" "dirname %f" ), groupBox1 );
// buddies
textLabel2->setBuddy( lineEdit1 );
textLabel3->setBuddy( lineEdit2 );
// layouts
TQHBoxLayout *layout1 = new TQHBoxLayout( 0, 0, 6 );
layout1->addItem( new TQSpacerItem( 181, 20, TQSizePolicy::Expanding, TQSizePolicy::Minimum ) );
layout1->addWidget( new KPushButton( KStdGuiItem::ok(), this, "OkButton" ) );
layout1->addWidget( new KPushButton( KStdGuiItem::cancel(), this, "CancelButton" ) );
TQGridLayout *layout2 = new TQGridLayout( 0, 2, 2, 0, 6 );
layout2->TQLayout::add( textLabel2 );
layout2->TQLayout::add( lineEdit1 );
layout2->TQLayout::add( textLabel3 );
layout2->TQLayout::add( lineEdit2 );
TQVBoxLayout *Form1Layout = new TQVBoxLayout( this, 11, 6, "Form1Layout");
Form1Layout->addWidget( textLabel1 );
Form1Layout->addWidget( groupBox1 );
Form1Layout->addLayout( layout2 );
Form1Layout->addLayout( layout1 );
Form1Layout->addItem( new TQSpacerItem( 20, 231, TQSizePolicy::Minimum, TQSizePolicy::Expanding ) );
// properties
setCaption( i18n("Add Custom Column") );
// connects
connect(
child( "OkButton" ),
TQT_SIGNAL(clicked()),
TQT_SLOT(accept()) );
connect(
child( "CancelButton" ),
TQT_SIGNAL(clicked()),
TQT_SLOT(reject()) );
}
TQString command() { return static_cast<KLineEdit*>(TQT_TQWIDGET(child("Command")))->text(); }
TQString name() { return static_cast<KLineEdit*>(TQT_TQWIDGET(child("ColumnName")))->text(); }
};
void
Playlist::addCustomColumn()
{
CustomColumnDialog dialog( this );
if ( dialog.exec() == TQDialog::Accepted ) {
const int index = addColumn( dialog.name(), 100 );
TQStringList args = TQStringList::split( ' ', dialog.command() );
TQStringList::Iterator pcf = args.find( "%f" );
if ( pcf == args.end() ) {
//there is no %f, so add one on the end
//TODO prolly this is confusing, instead ask the user if we should add one
args += "%f";
--pcf;
}
debug() << args << endl;
//TODO need to do it with a %u for url and %f for file
//FIXME gets stuck it seems if you submit broken commands
//FIXME issues with the column resize stuff that cause freezing in eventFilters
for( MyIt it( this ); *it; ++it ) {
if( (*it)->url().protocol() != "file" )
continue;
*pcf = (*it)->url().path();
debug() << args << endl;
TQProcess p( args );
for( p.start(); p.isRunning(); /*kapp->processEvents()*/ )
::usleep( 5000 );
(*it)->setExactText( index, p.readStdout() );
}
}
}
#include <taglib/fileref.h>
#include <taglib/tag.h>
TagWriter::TagWriter( PlaylistItem *item, const TQString &oldTag, const TQString &newTag, const int col, const bool updateView )
: ThreadManager::Job( "TagWriter" )
, m_item( item )
, m_failed( true )
, m_oldTagString( oldTag )
, m_newTagString( newTag )
, m_tagType( col )
, m_updateView( updateView )
{
Playlist::instance()->lock();
item->setEditing( col );
}
TagWriter::~TagWriter()
{
Playlist::instance()->unlock();
}
bool
TagWriter::doJob()
{
MetaBundle mb( m_item->url(), true );
switch ( m_tagType )
{
case PlaylistItem::Title:
mb.setTitle( m_newTagString );
break;
case PlaylistItem::Artist:
mb.setArtist( m_newTagString );
break;
case PlaylistItem::Composer:
if ( !mb.hasExtendedMetaInformation() )
return true;
mb.setComposer( m_newTagString );
break;
case PlaylistItem::DiscNumber:
if ( !mb.hasExtendedMetaInformation() )
return true;
mb.setDiscNumber( m_newTagString.toInt() );
break;
case PlaylistItem::Bpm:
if ( !mb.hasExtendedMetaInformation() )
return true;
mb.setBpm( m_newTagString.toFloat() );
break;
case PlaylistItem::Album:
mb.setAlbum( m_newTagString );
break;
case PlaylistItem::Year:
mb.setYear( m_newTagString.toInt() );
break;
case PlaylistItem::Comment:
//FIXME how does this work for vorbis files?
//Are we likely to overwrite some other comments?
//Vorbis can have multiple comment fields..
mb.setComment( m_newTagString );
break;
case PlaylistItem::Genre:
mb.setGenre( m_newTagString );
break;
case PlaylistItem::Track:
mb.setTrack( m_newTagString.toInt() );
break;
default:
return true;
}
m_failed = !mb.save();
return true;
}
void
TagWriter::completeJob()
{
switch( m_failed ) {
case true:
// we write a space for some reason I cannot recall
m_item->setExactText( m_tagType, m_oldTagString.isEmpty() ? " " : m_oldTagString );
Amarok::StatusBar::instance()->longMessage( i18n(
"Sorry, the tag for %1 could not be changed." ).arg( m_item->url().fileName() ), KDE::StatusBar::Sorry );
break;
case false:
m_item->setExactText( m_tagType, m_newTagString.isEmpty() ? " " : m_newTagString );
CollectionDB::instance()->updateURL( m_item->url().path(), m_updateView );
}
m_item->setIsBeingRenamed( false );
m_item->filter( Playlist::instance()->m_filter );
if( m_item->deleteAfterEditing() )
{
Playlist::instance()->removeItem( m_item );
delete m_item;
}
}
#include "playlist.moc"