/* 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 <tdeio/job.h> //deleteSelectedFiles()
# include <klineedit.h> //setCurrentTrack()
# include <klocale.h>
# include <kmessagebox.h>
# include <kpopupmenu.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 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 ( 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 ( 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 ) ;
}
////////////////////////////////////////////////////////////////////////////////
/// 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 ( " & " , " & " )
. replace ( " < " , " < " )
. replace ( " > " , " > " ) ;
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 ( " filesaveas " ) , i18n ( " &Organize File... " , " &Organize %n Files... " , itemCount ) , ORGANIZE ) ;
}
else
{
fileMenu . insertItem ( SmallIconSet ( " filesaveas " ) , i18n ( " &Copy Track to Collection... " , " &Copy %n Tracks to Collection... " , itemCount ) , COPY_TO_COLLECTION ) ;
fileMenu . insertItem ( SmallIconSet ( " filesaveas " ) , 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 ( " editcopy " ) ) , 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"