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

551 lines
23 KiB

Playlist.h - description
begin : Don Dez 5 2002
copyright : (C) 2002 by Mark Kretschmann
(C) 2005 Ian Monroe
(C) 2005 by Gábor Lehel
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
#include <config.h>
#include "amarok_export.h"
#include "amarokconfig.h"
#include "amarokdcophandler.h"
#include "engineobserver.h" //baseclass
#include "dynamicmode.h"
#include "playlistwindow.h" //friend
#include "playlistitem.h"
#include "metabundle.h"
#include "tooltip.h" //baseclass
#include "tracktooltip.h"
#include <klistview.h> //baseclass
#include <kurl.h> //KURL::List
#include <tqdir.h> //stack allocated
#include <tqpoint.h> //stack allocated
#include <tqptrlist.h> //stack allocated
#include <tqstringlist.h> //stack allocated
#include <vector> //stack allocated
class KAction;
class KActionCollection;
class PlaylistItem;
class PlaylistEntry;
class PlaylistLoader;
class PlaylistAlbum;
class TagWriter;
class TQBoxLayout;
class TQLabel;
class TQTimer;
class Medium;
* @authors Mark Kretschmann && Max Howell
* Playlist inherits KListView privately and thus is no longer a ListView
* Instead it is a part of PlaylistWindow and they interact in harmony. The change
* was necessary as it is too dangerous to allow public access to PlaylistItems
* due to the multi-threading environment.
* Unfortunately, since TQObject is now inaccessible you have to connect slots
* via one of PlaylistWindow's friend members or in Playlist
* If you want to add new playlist type functionality you should implement it
* inside this class or inside PlaylistWindow.
// template <class FieldType>
// AtomicString Index<FieldType>::fieldString(const FieldType &field) { return AtomicString(field); }
// template<>
// AtomicString Index<KURL>::fieldString(const KURL &field);
class Playlist : private KListView, public EngineObserver, public Amarok::ToolTipClient
LIBAMAROK_EXPORT static Playlist *instance() { return s_instance; }
static TQString defaultPlaylistPath();
static const int NO_SORT = 200;
static const int Append = 1; /// inserts media after the last item in the playlist
static const int Queue = 2; /// inserts media after the currentTrack
static const int Clear = 4; /// clears the playlist first
static const int Replace = Clear;
static const int DirectPlay = 8; /// start playback of the first item in the list
static const int Unique = 16; /// don't insert anything already in the playlist
static const int StartPlay = 32; /// start playback of the first item in the list if nothing else playing
static const int Colorize = 64; /// colorize newly added items
static const int DefaultOptions = Append | Unique | StartPlay;
// it's really just the *ListView parts we want to hide...
TQScrollView *qscrollview() const
return reinterpret_cast<TQScrollView*>( const_cast<Playlist*>( this ) );
/** Add media to the playlist
* @param options you can OR these together, see the enum
* @param sql Sql program to execute */
LIBAMAROK_EXPORT void insertMedia( const KURL::List &, int options = Append );
void insertMediaSql( const TQString& sql, int options = Append );
// Dynamic mode functions
void addDynamicModeTracks( uint songCount );
void adjustDynamicUpcoming( bool saveUndo = false );
void adjustDynamicPrevious( uint songCount, bool saveUndo = false );
void advanceDynamicTrack();
void setDynamicHistory( bool enable = true );
void burnPlaylist ( int projectType = -1 );
void burnSelectedTracks( int projectType = -1 );
int currentTrackIndex( bool onlyCountVisible = true );
bool isEmpty() const { return childCount() == 0; }
LIBAMAROK_EXPORT bool isTrackBefore() const;
LIBAMAROK_EXPORT bool isTrackAfter() const;
void restoreSession(); // called during initialisation
void setPlaylistName( const TQString &name, bool proposeOverwriting = false ) { m_playlistName = name; m_proposeOverwriting = proposeOverwriting; }
void proposePlaylistName( const TQString &name, bool proposeOverwriting = false ) { if( isEmpty() || m_playlistName==i18n("Untitled") ) m_playlistName = name; m_proposeOverwriting = proposeOverwriting; }
const TQString &playlistName() const { return m_playlistName; }
bool proposeOverwriteOnSave() const { return m_proposeOverwriting; }
bool saveM3U( const TQString&, bool relative = AmarokConfig::relativePlaylist() ) const;
void saveXML( const TQString& );
int totalTrackCount() const;
BundleList nextTracks() const;
uint repeatAlbumTrackCount() const; //returns number of tracks from same album
//as current track that are in playlist (may require Play Albums in Order on).
//If the information is not available, returns 0.
//const so you don't change it behind Playlist's back, use modifyDynamicMode() for that
const DynamicMode *dynamicMode() const;
//modify the returned DynamicMode, then finishedModifying() it when done
DynamicMode *modifyDynamicMode();
//call this every time you modifyDynamicMode(), otherwise you'll get memory leaks and/or crashes
void finishedModifying( DynamicMode *mode );
int stopAfterMode();
void addCustomMenuItem ( const TQString &submenu, const TQString &itemTitle );
void customMenuClicked ( int id );
bool removeCustomMenuItem( const TQString &submenu, const TQString &itemTitle );
void setFont( const TQFont &f ) { KListView::setFont( f ); } //made public for convenience
void unsetFont() { KListView::unsetFont(); }
PlaylistItem *firstChild() const { return static_cast<PlaylistItem*>( KListView::firstChild() ); }
PlaylistItem *lastItem() const { return static_cast<PlaylistItem*>( KListView::lastItem() ); }
PlaylistItem *currentItem() const { return static_cast<PlaylistItem*>( KListView::currentItem() ); }
int numVisibleColumns() const;
TQValueList<int> visibleColumns() const;
MetaBundle::ColumnMask getVisibleColumnMask() const;
int mapToLogicalColumn( int physical ) const; // Converts physical PlaylistItem column position to logical
TQString columnText( int c ) const { return KListView::columnText( c ); };
void setColumns( TQValueList<int> order, TQValueList<int> visible );
/** Call this to prevent items being removed from the playlist, it is mostly for internal use only
* Don't forget to unlock() !! */
void lock();
void unlock();
//reimplemented to save columns by name instead of index, to be more resilient to reorderings and such
void saveLayout(KConfig *config, const TQString &group) const;
void restoreLayout(KConfig *config, const TQString &group);
//AFT-related functions
bool checkFileStatus( PlaylistItem * item );
void addToUniqueMap( const TQString uniqueid, PlaylistItem* item );
void removeFromUniqueMap( const TQString uniqueid, PlaylistItem* item );
enum RequestType { Prev = -1, Current = 0, Next = 1 };
enum StopAfterMode { DoNotStop, StopAfterCurrent, StopAfterQueue, StopAfterOther };
class TQDragObject *dragObject();
friend class PlaylistItem;
friend class UrlLoader;
friend class QueueManager;
friend class QueueLabel;
friend class PlaylistWindow;
friend class ColumnList;
friend void Amarok::DcopPlaylistHandler::removeCurrentTrack(); //calls removeItem() and currentTrack()
friend void Amarok::DcopPlaylistHandler::removeByIndex( int ); //calls removeItem()
friend class TagWriter; //calls removeItem()
friend void PlaylistWindow::init(); //setting up connections etc.
friend TrackToolTip::TrackToolTip();
friend bool PlaylistWindow::eventFilter( TQObject*, TQEvent* ); //for convenience we handle some playlist events here
TQPair<TQString, TQRect> toolTipText( TQWidget*, const TQPoint &pos ) const;
void aboutToClear();
void itemCountChanged( int newCount, int newLength, int visCount, int visLength, int selCount, int selLength );
void queueChanged( const PLItemList &queued, const PLItemList &dequeued );
void columnsChanged();
void dynamicModeChanged( const DynamicMode *newMode );
public slots:
void activateByIndex(int);
void addCustomColumn();
void appendMedia( const KURL &url );
void appendMedia( const TQString &path );
void clear();
void copyToClipboard( const TQListViewItem* = 0 ) const;
void deleteSelectedFiles();
void ensureItemCentered( TQListViewItem* item );
void playCurrentTrack();
void playNextTrack( const bool forceNext = true );
void playPrevTrack();
void queueSelected();
void setSelectedRatings( int rating );
void redo();
void removeDuplicates();
void removeSelectedItems();
void setDynamicMode( DynamicMode *mode );
void loadDynamicMode( DynamicMode *mode ); //saveUndoState() + setDynamicMode()
void disableDynamicMode();
void editActiveDynamicMode();
void rebuildDynamicModeCache();
void repopulate();
void safeClear();
void scoreChanged( const TQString &path, float score );
void ratingChanged( const TQString &path, int rating );
void fileMoved( const TQString &srcPath, const TQString &dstPath );
void selectAll() { TQListView::selectAll( true ); }
void setFilter( const TQString &filter );
void setFilterSlot( const TQString &filter ); //uses a delay where applicable
void setStopAfterCurrent( bool on );
void setStopAfterItem( PlaylistItem *item );
void toggleStopAfterCurrentItem();
void toggleStopAfterCurrentTrack();
void setStopAfterMode( int mode );
void showCurrentTrack() { ensureItemCentered( m_currentTrack ); }
void showQueueManager();
void changeFromQueueManager(TQPtrList<PlaylistItem> list);
void shuffle();
void undo();
void updateMetaData( const MetaBundle& );
void adjustColumn( int n );
void updateEntriesUrl( const TQString &oldUrl, const TQString &newUrl, const TQString &uniqueid );
void updateEntriesUniqueId( const TQString &url, const TQString &oldid, const TQString &newid );
void updateEntriesStatusDeleted( const TQString &absPath, const TQString &uniqueid );
void updateEntriesStatusAdded( const TQString &absPath, const TQString &uniqueid );
void updateEntriesStatusAdded( const TQMap<TQString,TQString> &map );
virtual void fontChange( const TQFont &old );
protected slots:
void contentsMouseMoveEvent( TQMouseEvent *e = 0 );
void leaveEvent( TQEvent *e );
void contentsMousePressEvent( TQMouseEvent *e );
void contentsWheelEvent( TQWheelEvent *e );
private slots:
void mediumChange( int );
void slotCountChanged();
void activate( TQListViewItem* );
void columnOrderChanged();
void columnResizeEvent( int, int, int );
void doubleClicked( TQListViewItem* );
void generateInfo(); //generates info for Random Albums
/* the only difference multi makes is whether it emits queueChanged(). (if multi, then no)
if you're queue()ing many items, consider passing true and emitting queueChanged() yourself. */
/* if invertQueue then queueing an already queued song dequeues it */
void queue( TQListViewItem*, bool multi = false, bool invertQueue = true );
void saveUndoState();
void setDelayedFilter(); //after the delay is over
void showContextMenu( TQListViewItem*, const TQPoint&, int );
void slotEraseMarker();
void slotGlowTimer();
void reallyEnsureItemCentered();
void slotMouseButtonPressed( int, TQListViewItem*, const TQPoint&, int );
void slotSingleClick();
void slotContentsMoving();
void slotRepeatTrackToggled( int mode );
void slotQueueChanged( const PLItemList &in, const PLItemList &out);
void slotUseScores( bool use );
void slotUseRatings( bool use );
void slotMoodbarPrefs( bool show, bool moodier, int alter, bool withMusic );
void updateNextPrev();
void writeTag( TQListViewItem*, const TQString&, int );
Playlist( TQWidget* );
Playlist( const Playlist& ); //not defined
LIBAMAROK_EXPORT static Playlist *s_instance;
void countChanged();
PlaylistItem *currentTrack() const { return m_currentTrack; }
PlaylistItem *restoreCurrentTrack();
void insertMediaInternal( const KURL::List&, PlaylistItem*, int options = 0 );
bool isAdvancedQuery( const TQString &query );
void refreshNextTracks( int = -1 );
void removeItem( PlaylistItem*, bool = false );
bool saveState( TQStringList& );
void setCurrentTrack( PlaylistItem* );
void setCurrentTrackPixmap( int state = -1 );
void showTagDialog( TQPtrList<TQListViewItem> items );
void sortQueuedItems();
void switchState( TQStringList&, TQStringList& );
void saveSelectedAsPlaylist();
void initStarPixmaps();
//engine observer functions
void engineNewMetaData( const MetaBundle&, bool );
void engineStateChanged( Engine::State, Engine::State = Engine::Empty );
/// KListView Overloaded functions
void contentsDropEvent ( TQDropEvent* );
void contentsDragEnterEvent( TQDragEnterEvent* );
void contentsDragMoveEvent ( TQDragMoveEvent* );
void contentsDragLeaveEvent( TQDragLeaveEvent* );
#ifdef PURIST //KListView imposes hand cursor so override it
void contentsMouseMoveEvent( TQMouseEvent *e ) { TQListView::contentsMouseMoveEvent( e ); }
void customEvent( TQCustomEvent* );
bool eventFilter( TQObject*, TQEvent* );
void paletteChange( const TQPalette& );
void rename( TQListViewItem*, int );
void setColumnWidth( int, int );
void setSorting( int, bool = true );
void viewportPaintEvent( TQPaintEvent* );
void viewportResizeEvent( TQResizeEvent* );
void appendToPreviousTracks( PlaylistItem *item );
void appendToPreviousAlbums( PlaylistAlbum *album );
void removeFromPreviousTracks( PlaylistItem *item = 0 );
void removeFromPreviousAlbums( PlaylistAlbum *album = 0 );
typedef TQMap<AtomicString, PlaylistAlbum*> AlbumMap;
typedef TQMap<AtomicString, AlbumMap> ArtistAlbumMap;
ArtistAlbumMap m_albums;
uint m_startupTime_t; //TQDateTime::currentDateTime().toTime_t as of startup
uint m_oldestTime_t; //the createdate of the oldest song in the collection
PlaylistItem *m_currentTrack; //the track that is playing
TQListViewItem *m_marker; //track that has the drag/drop marker under it
PlaylistItem *m_hoveredRating; //if the mouse is hovering over the rating of an item
//NOTE these container types were carefully chosen
TQPtrList<PlaylistAlbum> m_prevAlbums; //the previously played albums in Entire Albums mode
PLItemList m_prevTracks; //the previous history
PLItemList m_nextTracks; //the tracks to be played after the current track
TQString m_filter;
TQString m_prevfilter;
TQTimer *m_filtertimer;
PLItemList m_itemsToChangeTagsFor;
bool m_smartResizing;
int m_firstColumn;
int m_totalCount;
int m_totalLength;
int m_selCount;
int m_selLength;
int m_visCount;
int m_visLength;
TQ_INT64 m_total; //for Favor Tracks
bool m_itemCountDirty;
KAction *m_undoButton;
KAction *m_redoButton;
KAction *m_clearButton;
TQDir m_undoDir;
TQStringList m_undoList;
TQStringList m_redoList;
uint m_undoCounter;
DynamicMode *m_dynamicMode;
KURL::List m_queueList;
PlaylistItem *m_stopAfterTrack;
int m_stopAfterMode;
bool m_showHelp;
bool m_dynamicDirt; //So we don't call advanceDynamicTrack() on activate()
bool m_queueDirt; //When queuing disabled items, we need to place the marker on the newly inserted item
bool m_undoDirt; //Make sure we don't repopulate the playlist when dynamic mode and undo()
int m_insertFromADT; //Don't automatically start playing if a user hits Next in dynamic mode when not already playing
static TQMutex *s_dynamicADTMutex;
TQListViewItem *m_itemToReallyCenter;
TQListViewItem *m_renameItem;
int m_renameColumn;
TQTimer *m_clicktimer;
TQListViewItem *m_itemToRename;
TQPoint m_clickPos;
int m_columnToRename;
TQMap<TQString, TQStringList> m_customSubmenuItem;
TQMap<int, TQString> m_customIdItem;
bool isLocked() const { return m_lockStack > 0; }
/// stack counter for PLaylist::lock() and unlock()
int m_lockStack;
TQString m_editOldTag; //text before inline editing ( the new tag is written only if it's changed )
std::vector<double> m_columnFraction;
TQMap<TQString,TQPtrList<PlaylistItem>*> m_uniqueMap;
int m_oldRandom;
int m_oldRepeat;
TQString m_playlistName;
bool m_proposeOverwriting;
// indexing stuff
// An index of playlist items by some field. The index is backed by AtomicStrings, to avoid
// duplication thread-safely.
template <class FieldType>
class Index : private TQMap<AtomicString, TQPtrList<PlaylistItem> >
// constructors take the PlaylistItem getter to index by
Index( FieldType (PlaylistItem::*getter)( ) const)
: m_getter( getter ), m_useGetter( true ) { };
Index( const FieldType &(PlaylistItem::*refGetter)() const)
: m_refGetter( refGetter ), m_useGetter( false ) { };
// we specialize this method, below, for KURLs
AtomicString fieldString( const FieldType &field) { return AtomicString( field ); }
AtomicString keyOf( const PlaylistItem &item) {
return m_useGetter ? fieldString( ( item.*m_getter ) () )
: fieldString( ( item.*m_refGetter ) () );
bool contains( const FieldType &key ) { return contains( fieldString( key ) ); }
// Just first match, or NULL
PlaylistItem *getFirst( const FieldType &field ) {
Iterator it = find( fieldString( field ) );
return it == end() || ? 0 :;
void add( PlaylistItem *item ) {
TQPtrList<PlaylistItem> &row = operator[]( keyOf( *item ) ); // adds one if needed
if ( !row.containsRef(item) ) row.append( item );
void remove( PlaylistItem *item ) {
Iterator it = find( keyOf( *item ) );
if (it != end()) {
while ( item ) ) { };
if ( ) erase( it );
FieldType (PlaylistItem::*m_getter) () const;
const FieldType &(PlaylistItem::*m_refGetter) () const;
bool m_useGetter; // because a valid *member can be zero in C++
Index<KURL> m_urlIndex;
// TODO: we can convert m_unique to this, to remove some code and for uniformity and thread
// safety
// TODO: we should just store the url() as AtomicString, it will save headaches (e.g. at least a
// crash with multicore enabled traces back to KURL refcounting)
//Index<TQString> m_uniqueIndex;
class PlaylistAlbum
PLItemList tracks;
int refcount;
TQ_INT64 total; //for Favor Tracks
PlaylistAlbum(): refcount( 0 ), total( 0 ) { }
* Iterator class that only edits visible items! Preferentially always use
* this! Invisible items should not be operated on! To iterate over all
* items use MyIt::All as the flags parameter. MyIt::All cannot be OR'd,
* sorry.
class PlaylistIterator : public TQListViewItemIterator
PlaylistIterator( TQListViewItem *item, int flags = 0 )
//TQListViewItemIterator is not great and doesn't allow you to see everything if you
//mask both Visible and Invisible :( instead just visible items are returned
: TQListViewItemIterator( item, flags == All ? 0 : flags | Visible )
PlaylistIterator( TQListView *view, int flags = 0 )
: TQListViewItemIterator( view, flags == All ? 0 : flags | Visible )
//FIXME! Dirty hack for enabled/disabled items.
enum IteratorFlag {
Visible = TQListViewItemIterator::Visible,
All = TQListViewItemIterator::Invisible
inline PlaylistItem *operator*() { return static_cast<PlaylistItem*>( TQListViewItemIterator::operator*() ); }
/// @return the next visible PlaylistItem after item
static PlaylistItem *nextVisible( PlaylistItem *item )
PlaylistIterator it( item );
return (*it == item) ? *static_cast<PlaylistIterator&>(++it) : *it;
static PlaylistItem *prevVisible( PlaylistItem *item )
PlaylistIterator it( item );
return (*it == item) ? *static_cast<PlaylistIterator&>(--it) : *it;
// Specialization of Index::fieldString for URLs
inline AtomicString Playlist::Index<KURL>::fieldString( const KURL &url )
return AtomicString( url.url() );