summaryrefslogtreecommitdiffstats
path: root/amarok/src/playlist.h
blob: 0973b4dc3106031220ef10682d219181b8216304 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
/***************************************************************************
                        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 <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 <tdelistview.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 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 <class FieldType>
// AtomicString Index<FieldType>::fieldString(const FieldType &field) { return AtomicString(field); }

// template<>
// AtomicString Index<KURL>::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<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 ) { TDEListView::setFont( f ); } //made public for convenience
        void unsetFont()               { TDEListView::unsetFont(); }

        PlaylistItem *firstChild() const { return static_cast<PlaylistItem*>( TDEListView::firstChild() ); }
        PlaylistItem *lastItem()   const { return static_cast<PlaylistItem*>( TDEListView::lastItem() ); }
        PlaylistItem *currentItem() const { return static_cast<PlaylistItem*>( TDEListView::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 TDEListView::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(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<TQString, TQRect> 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<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 );

    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<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 );

        /// 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<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


        /// 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<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;

        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<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> >
        {
          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<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 ( 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<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
{
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<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
template<>
inline AtomicString Playlist::Index<KURL>::fieldString( const KURL &url )
{
    return AtomicString( url.url() );
}

#endif //AMAROK_PLAYLIST_H