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.
4982 lines
156 KiB
4982 lines
156 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 <kaction.h>
|
|
#include <kapplication.h>
|
|
#include <kcursor.h> //setOverrideCursor()
|
|
#include <kdialogbase.h>
|
|
#include <kglobalsettings.h> //rename()
|
|
#include <kiconeffect.h>
|
|
#include <kiconloader.h> //slotShowContextMenu()
|
|
#include <kio/job.h> //deleteSelectedFiles()
|
|
#include <klineedit.h> //setCurrentTrack()
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kpopupmenu.h>
|
|
#include <krandomsequence.h> //random Mode
|
|
#include <kstandarddirs.h> //KGlobal::dirs()
|
|
#include <kstdaction.h>
|
|
#include <kstringhandler.h> //::showContextMenu()
|
|
#include <kurldrag.h>
|
|
|
|
#include <cstdlib> // abs
|
|
|
|
extern "C"
|
|
{
|
|
#if KDE_VERSION < KDE_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 )
|
|
: KListView( 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()) );
|
|
|
|
|
|
KActionCollection* const ac = Amarok::actionCollection();
|
|
KAction *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 KAction( 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 KAction( i18n( "&Repopulate" ), Amarok::icon( "playlist_refresh" ), 0, TQT_TQOBJECT(this), TQT_SLOT( repopulate() ), ac, "repopulate" );
|
|
new KAction( i18n( "S&huffle" ), "rebuild", CTRL+Key_H, TQT_TQOBJECT(this), TQT_SLOT( shuffle() ), ac, "playlist_shuffle" );
|
|
KAction *gotoCurrent = new KAction( i18n( "&Go To Current Track" ), Amarok::icon( "music" ), CTRL+Key_J, TQT_TQOBJECT(this), TQT_SLOT( showCurrentTrack() ), ac, "playlist_show" );
|
|
new KAction( i18n( "&Remove Duplicate && Dead Entries" ), 0, TQT_TQOBJECT(this), TQT_SLOT( removeDuplicates() ), ac, "playlist_remove_duplicates" );
|
|
new KAction( i18n( "&Queue Selected Tracks" ), Amarok::icon( "queue_track" ), CTRL+Key_D, TQT_TQOBJECT(this), TQT_SLOT( queueSelected() ), ac, "queue_selected" );
|
|
KToggleAction *stopafter = new KToggleAction( i18n( "&Stop Playing After Track" ), Amarok::icon( "stop" ), CTRL+ALT+Key_V,
|
|
TQT_TQOBJECT(this), TQT_SLOT( toggleStopAfterCurrentItem() ), ac, "stop_after" );
|
|
|
|
{ // KAction idiocy -- shortcuts don't work until they've been plugged into a menu
|
|
KPopupMenu 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( KGlobal::config(), "PlaylistColumnsLayout" );
|
|
|
|
// Sorting must be disabled when current.xml is being loaded. See BUG 113042
|
|
KListView::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( KGlobal::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 klistview.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(KConfig *config, const TQString &group) const
|
|
{
|
|
KConfigGroupSaver 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(KConfig *config, const TQString &group)
|
|
{
|
|
KConfigGroupSaver 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( KApplication::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( ( KApplication::random() / pow( 2, sizeof( int ) * 8 ) )
|
|
* pow( 2, 64 ) );
|
|
random = r % total;
|
|
}
|
|
else
|
|
random = KApplication::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( KIconLoader().iconPath( "amarok", -KIcon::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( "&", "&" ).replace( "<", "<" ).replace( ">", ">" );
|
|
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 );
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// KListView 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 KListView::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( qVersion(), "3.3.5" ) == 0
|
|
|| strcmp( qVersion(), "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
|
|
KListView::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() )
|
|
KListView::setSorting( col, b );
|
|
}
|
|
|
|
void
|
|
Playlist::setColumnWidth( int col, int width )
|
|
{
|
|
|
|
KListView::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( KGlobalSettings::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 );
|
|
KListView::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();
|
|
}
|
|
|
|
KListView::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 = KApplication::keyboardMouseState() & TQt::ControlButton;
|
|
#else
|
|
const bool ctrlPressed = KApplication::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 ) KListView::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() ) |