/*************************************************************************** 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. * * * ***************************************************************************/ #ifndef AMAROK_PLAYLIST_H #define AMAROK_PLAYLIST_H #include #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 //baseclass #include //KURL::List #include //stack allocated #include //stack allocated #include //stack allocated #include //stack allocated #include //stack allocated class TDEAction; class TDEActionCollection; 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 TDEListView 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 // AtomicString Index::fieldString(const FieldType &field) { return AtomicString(field); } // template<> // AtomicString Index::fieldString(const KURL &field); class Playlist : private TDEListView, public EngineObserver, public Amarok::ToolTipClient { Q_OBJECT public: ~Playlist(); 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( const_cast( 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 ) { TDEListView::setFont( f ); } //made public for convenience void unsetFont() { TDEListView::unsetFont(); } PlaylistItem *firstChild() const { return static_cast( TDEListView::firstChild() ); } PlaylistItem *lastItem() const { return static_cast( TDEListView::lastItem() ); } PlaylistItem *currentItem() const { return static_cast( TDEListView::currentItem() ); } int numVisibleColumns() const; TQValueList visibleColumns() const; MetaBundle::ColumnMask getVisibleColumnMask() const; int mapToLogicalColumn( int physical ) const; // Converts physical PlaylistItem column position to logical TQString columnText( int c ) const { return TDEListView::columnText( c ); }; void setColumns( TQValueList order, TQValueList 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(TDEConfig *config, const TQString &group) const; void restoreLayout(TDEConfig *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 public: TQPair toolTipText( TQWidget*, const TQPoint &pos ) const; signals: 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 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 &map ); protected: 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 ); private: 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 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 ); /// TDEListView Overloaded functions void contentsDropEvent ( TQDropEvent* ); void contentsDragEnterEvent( TQDragEnterEvent* ); void contentsDragMoveEvent ( TQDragMoveEvent* ); void contentsDragLeaveEvent( TQDragLeaveEvent* ); #ifdef PURIST //TDEListView imposes hand cursor so override it void contentsMouseMoveEvent( TQMouseEvent *e ) { TQListView::contentsMouseMoveEvent( e ); } #endif 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 AlbumMap; typedef TQMap 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 /// ATTRIBUTES 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 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; TDEAction *m_undoButton; TDEAction *m_redoButton; TDEAction *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 m_customSubmenuItem; TQMap 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 m_columnFraction; TQMap*> 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 Index : private TQMap > { public: // 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() || it.data().isEmpty() ? 0 : it.data().getFirst(); } void add( PlaylistItem *item ) { TQPtrList &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 ( it.data().removeRef( item ) ) { }; if ( it.data().isEmpty() ) erase( it ); } } private: FieldType (PlaylistItem::*m_getter) () const; const FieldType &(PlaylistItem::*m_refGetter) () const; bool m_useGetter; // because a valid *member can be zero in C++ }; Index 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 m_uniqueIndex; }; class PlaylistAlbum { public: 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 { public: 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( TQListViewItemIterator::operator*() ); } /// @return the next visible PlaylistItem after item static PlaylistItem *nextVisible( PlaylistItem *item ) { PlaylistIterator it( item ); return (*it == item) ? *static_cast(++it) : *it; } static PlaylistItem *prevVisible( PlaylistItem *item ) { PlaylistIterator it( item ); return (*it == item) ? *static_cast(--it) : *it; } }; // Specialization of Index::fieldString for URLs template<> inline AtomicString Playlist::Index::fieldString( const KURL &url ) { return AtomicString( url.url() ); } #endif //AMAROK_PLAYLIST_H