playlist.cpp 68 KB
Newer Older
1
2
/***************************************************************************
    begin                : Sat Feb 16 2002
3
    copyright            : (C) 2002 - 2004 by Scott Wheeler
4
    email                : wheeler@kde.org
5
6
7

    copyright            : (C) 2008 by Michael Pyne
    email                : michael.pyne@kdemail.net
8
9
10
11
12
13
14
15
16
17
18
***************************************************************************/

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

19
#include "playlist.h"
20
#include "juk-exception.h"
21

Scott Wheeler's avatar
Scott Wheeler committed
22
#include <kconfig.h>
23
#include <kapplication.h>
24
#include <kmessagebox.h>
25
#include <k3urldrag.h>
26
#include <kiconloader.h>
27
#include <klineedit.h>
28
#include <k3popupmenu.h>
Scott Wheeler's avatar
Scott Wheeler committed
29
#include <klocale.h>
30
#include <kdebug.h>
31
#include <kfiledialog.h>
32
33
34
#include <kglobalsettings.h>
#include <kurl.h>
#include <kio/netaccess.h>
35
#include <kio/copyjob.h>
36
#include <kmenu.h>
37
38
39
#include <kactioncollection.h>
#include <kconfiggroup.h>
#include <ktoolbarpopupaction.h>
Laurent Montel's avatar
Poirt    
Laurent Montel committed
40
41
42
#include <kactionmenu.h>
#include <ktoggleaction.h>
#include <kselectaction.h>
43
44

#include <QCursor>
45
46
47
#include <QDir>
#include <QToolTip>
#include <QFile>
Laurent Montel's avatar
Laurent Montel committed
48
49
50
#include <QResizeEvent>
#include <QMouseEvent>
#include <QKeyEvent>
51
52
53
#include <QMimeData>
#include <QTimer>
#include <QClipboard>
Laurent Montel's avatar
Laurent Montel committed
54
55
56
#include <QTextStream>
#include <QDropEvent>
#include <QDragEnterEvent>
57
#include <QPixmap>
58
#include <QStackedWidget>
59
60
#include <id3v1genres.h>

61
#include <time.h>
62
#include <math.h>
63
#include <dirent.h>
64

Scott Wheeler's avatar
Scott Wheeler committed
65
#include "playlistitem.h"
66
#include "playlistcollection.h"
Scott Wheeler's avatar
Scott Wheeler committed
67
#include "playlistsearch.h"
68
#include "mediafiles.h"
69
#include "collectionlist.h"
70
#include "filerenamer.h"
71
#include "actioncollection.h"
72
#include "tracksequencemanager.h"
73
#include "tag.h"
74
#include "k3bexporter.h"
75
#include "upcomingplaylist.h"
76
#include "deletedialog.h"
77
#include "webimagefetcher.h"
78
#include "coverinfo.h"
79
#include "coverdialog.h"
80
#include "tagtransactionmanager.h"
81
#include "cache.h"
82

83
84
using namespace ActionCollection;

85
86
87
88
89
90
91
92
93
/**
 * Just a shortcut of sorts.
 */

static bool manualResize()
{
    return action<KToggleAction>("resizeColumnsManually")->isChecked();
}

94
95
96
97
/**
 * A tooltip specialized to show full filenames over the file name column.
 */

David Faure's avatar
David Faure committed
98
#ifdef __GNUC__
99
#warning disabling the tooltip for now
David Faure's avatar
David Faure committed
100
#endif
101
#if 0
102
103
104
105
class PlaylistToolTip : public QToolTip
{
public:
    PlaylistToolTip(QWidget *parent, Playlist *playlist) :
106
        QToolTip(parent), m_playlist(playlist) {}
107
108
109

    virtual void maybeTip(const QPoint &p)
    {
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
        PlaylistItem *item = static_cast<PlaylistItem *>(m_playlist->itemAt(p));

        if(!item)
            return;

        QPoint contentsPosition = m_playlist->viewportToContents(p);

        int column = m_playlist->header()->sectionAt(contentsPosition.x());

        if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn ||
           item->cachedWidths()[column] > m_playlist->columnWidth(column) ||
           (column == m_playlist->columnOffset() + PlaylistItem::CoverColumn &&
            item->file().coverInfo()->hasCover()))
        {
            QRect r = m_playlist->itemRect(item);
            int headerPosition = m_playlist->header()->sectionPos(column);
            r.setLeft(headerPosition);
            r.setRight(headerPosition + m_playlist->header()->sectionSize(column));

            if(column == m_playlist->columnOffset() + PlaylistItem::FileNameColumn)
                tip(r, item->file().absFilePath());
            else if(column == m_playlist->columnOffset() + PlaylistItem::CoverColumn) {
                Q3MimeSourceFactory *f = Q3MimeSourceFactory::defaultFactory();
                f->setImage("coverThumb",
                            QImage(item->file().coverInfo()->pixmap(CoverInfo::Thumbnail).convertToImage()));
                tip(r, "<center><img source=\"coverThumb\"/></center>");
            }
            else
                tip(r, item->text(column));
        }
140
141
142
143
144
    }

private:
    Playlist *m_playlist;
};
145
#endif
146

147
148
149
150
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings definition
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
151
bool Playlist::m_visibleChanged = false;
152
bool Playlist::m_shuttingDown = false;
153

154
155
156
157
158
159
160
161
162
163
164
165
166
/**
 * Shared settings between the playlists.
 */

class Playlist::SharedSettings
{
public:
    static SharedSettings *instance();
    /**
     * Sets the default column order to that of Playlist @param p.
     */
    void setColumnOrder(const Playlist *l);
    void toggleColumnVisible(int column);
167
    void setInlineCompletionMode(KGlobalSettings::Completion mode);
168
169
170

    /**
     * Apply the settings.
171
     */
172
    void apply(Playlist *l) const;
173
    void sync() { writeConfig(); }
174
175
176
177
178
179
180
181
182

protected:
    SharedSettings();
    ~SharedSettings() {}

private:
    void writeConfig();

    static SharedSettings *m_instance;
183
    QList<int> m_columnOrder;
184
    QVector<bool> m_columnsVisible;
185
    KGlobalSettings::Completion m_inlineCompletion;
186
187
};

Scott Wheeler's avatar
Scott Wheeler committed
188
189
Playlist::SharedSettings *Playlist::SharedSettings::m_instance = 0;

190
191
192
193
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings public members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
194
195
Playlist::SharedSettings *Playlist::SharedSettings::instance()
{
196
197
    static SharedSettings settings;
    return &settings;
Scott Wheeler's avatar
Scott Wheeler committed
198
199
200
}

void Playlist::SharedSettings::setColumnOrder(const Playlist *l)
201
{
Scott Wheeler's avatar
Scott Wheeler committed
202
    if(!l)
203
        return;
204

Scott Wheeler's avatar
Scott Wheeler committed
205
    m_columnOrder.clear();
206

207
    for(int i = l->columnOffset(); i < l->columns(); ++i)
208
        m_columnOrder.append(l->header()->mapToIndex(i));
Scott Wheeler's avatar
Scott Wheeler committed
209

210
    writeConfig();
Scott Wheeler's avatar
Scott Wheeler committed
211
}
212

213
214
void Playlist::SharedSettings::toggleColumnVisible(int column)
{
215
216
    if(column >= m_columnsVisible.size())
        m_columnsVisible.fill(true, column + 1);
217
218
219
220
221
222

    m_columnsVisible[column] = !m_columnsVisible[column];

    writeConfig();
}

223
224
225
226
227
228
229
void Playlist::SharedSettings::setInlineCompletionMode(KGlobalSettings::Completion mode)
{
    m_inlineCompletion = mode;
    writeConfig();
}


230
void Playlist::SharedSettings::apply(Playlist *l) const
Scott Wheeler's avatar
Scott Wheeler committed
231
232
{
    if(!l)
233
        return;
Scott Wheeler's avatar
Scott Wheeler committed
234

235
    int offset = l->columnOffset();
Scott Wheeler's avatar
Scott Wheeler committed
236
    int i = 0;
Laurent Montel's avatar
Laurent Montel committed
237
    for(QList<int>::ConstIterator it = m_columnOrder.constBegin(); it != m_columnOrder.constEnd(); ++it)
238
        l->header()->moveSection(i++ + offset, *it + offset);
239

240
    for(int i = 0; i < m_columnsVisible.size(); i++) {
241
242
243
244
        if(m_columnsVisible[i] && !l->isColumnVisible(i + offset))
            l->showColumn(i + offset, false);
        else if(!m_columnsVisible[i] && l->isColumnVisible(i + offset))
            l->hideColumn(i + offset, false);
245
    }
Scott Wheeler's avatar
Scott Wheeler committed
246
247

    l->updateLeftColumn();
248
    l->renameLineEdit()->setCompletionMode(m_inlineCompletion);
249
    l->slotColumnResizeModeChanged();
Scott Wheeler's avatar
Scott Wheeler committed
250
251
}

252
253
254
255
////////////////////////////////////////////////////////////////////////////////
// Playlist::ShareSettings protected members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
256
257
Playlist::SharedSettings::SharedSettings()
{
258
    KConfigGroup config(KGlobal::config(), "PlaylistShared");
259

260
    bool resizeColumnsManually = config.readEntry("ResizeColumnsManually", false);
261
    action("resizeColumnsManually")->setChecked(resizeColumnsManually);
262

263
264
265
    // Preallocate spaces so we don't need to check later.
    m_columnsVisible.fill(true, PlaylistItem::lastColumn() + 1);

266
    // save column order
267
    m_columnOrder = config.readEntry("ColumnOrder", QList<int>());
268

269
    QList<int> l = config.readEntry("VisibleColumns", QList<int>());
270

271
    if(l.isEmpty()) {
272

273
274
275
        // Provide some default values for column visibility if none were
        // read from the configuration file.

276
277
278
279
        m_columnsVisible[PlaylistItem::BitrateColumn] = false;
        m_columnsVisible[PlaylistItem::CommentColumn] = false;
        m_columnsVisible[PlaylistItem::FileNameColumn] = false;
        m_columnsVisible[PlaylistItem::FullPathColumn] = false;
280
281
    }
    else {
282
283
        // Convert the int list into a bool list.

284
        m_columnsVisible.fill(false);
285
286
        for(int i = 0; i < l.size() && i < m_columnsVisible.size(); ++i)
            m_columnsVisible[i] = bool(l[i]);
287
    }
288
289

    m_inlineCompletion = KGlobalSettings::Completion(
290
        config.readEntry("InlineCompletionMode", int(KGlobalSettings::CompletionAuto)));
291
292
293
294
295
296
297
298
}

////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings private members
////////////////////////////////////////////////////////////////////////////////

void Playlist::SharedSettings::writeConfig()
{
299
300
    KConfigGroup config(KGlobal::config(), "PlaylistShared");
    config.writeEntry("ColumnOrder", m_columnOrder);
301

302
303
    QList<int> l;
    for(int i = 0; i < m_columnsVisible.size(); i++)
304
        l.append(int(m_columnsVisible[i]));
305

306
    config.writeEntry("VisibleColumns", l);
307
    config.writeEntry("InlineCompletionMode", int(m_inlineCompletion));
308

309
    config.writeEntry("ResizeColumnsManually", manualResize());
310

311
    KGlobal::config()->sync();
Scott Wheeler's avatar
Scott Wheeler committed
312
}
313

314
////////////////////////////////////////////////////////////////////////////////
315
// public members
316
317
////////////////////////////////////////////////////////////////////////////////

318
PlaylistItemList Playlist::m_history;
319
QMap<int, PlaylistItem *> Playlist::m_backMenuItems;
320
321
int Playlist::m_leftColumn = 0;

322
Playlist::Playlist(PlaylistCollection *collection, const QString &name,
323
                   const QString &iconName) :
Laurent Montel's avatar
Laurent Montel committed
324
    K3ListView(collection->playlistStack()),
325
    m_collection(collection),
326
    m_fetcher(new WebImageFetcher(this)),
327
    m_selectedCount(0),
328
    m_allowDuplicates(false),
329
    m_applySharedSettings(true),
330
    m_columnWidthModeChanged(false),
331
    m_disableColumnWidthUpdates(true),
332
    m_time(0),
333
    m_widthsDirty(true),
334
    m_searchEnabled(true),
335
336
    m_lastSelected(0),
    m_playlistName(name),
337
    m_rmbMenu(0),
338
339
    m_toolTip(0),
    m_blockDataChanged(false)
340
{
341
    setup();
342
    collection->setupPlaylist(this, iconName);
343
344
}

345
Playlist::Playlist(PlaylistCollection *collection, const PlaylistItemList &items,
346
                   const QString &name, const QString &iconName) :
Laurent Montel's avatar
Laurent Montel committed
347
    K3ListView(collection->playlistStack()),
348
    m_collection(collection),
349
    m_fetcher(new WebImageFetcher(this)),
350
351
    m_selectedCount(0),
    m_allowDuplicates(false),
352
    m_applySharedSettings(true),
353
    m_columnWidthModeChanged(false),
354
    m_disableColumnWidthUpdates(true),
355
    m_time(0),
356
    m_widthsDirty(true),
357
358
359
    m_searchEnabled(true),
    m_lastSelected(0),
    m_playlistName(name),
360
    m_rmbMenu(0),
361
362
    m_toolTip(0),
    m_blockDataChanged(false)
363
364
365
366
367
368
369
{
    setup();
    collection->setupPlaylist(this, iconName);
    createItems(items);
}

Playlist::Playlist(PlaylistCollection *collection, const QFileInfo &playlistFile,
370
                   const QString &iconName) :
Laurent Montel's avatar
Laurent Montel committed
371
    K3ListView(collection->playlistStack()),
372
    m_collection(collection),
373
    m_fetcher(new WebImageFetcher(this)),
374
375
376
    m_selectedCount(0),
    m_allowDuplicates(false),
    m_applySharedSettings(true),
377
    m_columnWidthModeChanged(false),
378
    m_disableColumnWidthUpdates(true),
379
    m_time(0),
380
381
    m_widthsDirty(true),
    m_searchEnabled(true),
382
    m_lastSelected(0),
383
    m_fileName(playlistFile.absoluteFilePath()),
384
    m_rmbMenu(0),
385
386
    m_toolTip(0),
    m_blockDataChanged(false)
387
{
388
    setup();
389
    loadFile(m_fileName, playlistFile);
390
391
392
393
    collection->setupPlaylist(this, iconName);
}

Playlist::Playlist(PlaylistCollection *collection, bool delaySetup) :
Laurent Montel's avatar
Laurent Montel committed
394
    K3ListView(collection->playlistStack()),
395
    m_collection(collection),
396
    m_fetcher(new WebImageFetcher(this)),
397
398
399
    m_selectedCount(0),
    m_allowDuplicates(false),
    m_applySharedSettings(true),
400
    m_columnWidthModeChanged(false),
401
    m_disableColumnWidthUpdates(true),
402
    m_time(0),
403
    m_widthsDirty(true),
404
    m_searchEnabled(true),
405
    m_lastSelected(0),
406
    m_rmbMenu(0),
407
408
    m_toolTip(0),
    m_blockDataChanged(false)
409
410
411
412
{
    setup();

    if(!delaySetup)
Michael Pyne's avatar
Michael Pyne committed
413
        collection->setupPlaylist(this, "audio-midi");
414
415
416
417
}

Playlist::~Playlist()
{
418
419
420
421
422
423
    // In some situations the dataChanged signal from clearItems will cause observers to
    // subsequently try to access a deleted item.  Since we're going away just remove all
    // observers.

    clearObservers();

424
425
426
427
    // clearItem() will take care of removing the items from the history,
    // so call clearItems() to make sure it happens.

    clearItems(items());
428

429
    /* delete m_toolTip; */
430

431
    if(!m_shuttingDown)
432
        m_collection->removePlaylist(this);
433
434
}

435
436
QString Playlist::name() const
{
437
    if(m_playlistName.isEmpty())
438
        return m_fileName.section(QDir::separator(), -1).section('.', 0, -2);
439
    else
440
        return m_playlistName;
441
442
443
444
}

FileHandle Playlist::currentFile() const
{
445
    return playingItem() ? playingItem()->file() : FileHandle::null();
446
447
448
449
}

int Playlist::time() const
{
450
451
452
    // Since this method gets a lot of traffic, let's optimize for such.

    if(!m_addTime.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
453
454
        for(PlaylistItemList::ConstIterator it = m_addTime.constBegin();
            it != m_addTime.constEnd(); ++it)
455
456
457
458
        {
            if(*it)
                m_time += (*it)->file().tag()->seconds();
        }
459

460
        m_addTime.clear();
461
462
463
    }

    if(!m_subtractTime.isEmpty()) {
Laurent Montel's avatar
Laurent Montel committed
464
465
        for(PlaylistItemList::ConstIterator it = m_subtractTime.constBegin();
            it != m_subtractTime.constEnd(); ++it)
466
467
468
469
        {
            if(*it)
                m_time -= (*it)->file().tag()->seconds();
        }
470

471
        m_subtractTime.clear();
472
    }
473
474

    return m_time;
475
476
}

477
478
void Playlist::playFirst()
{
479
    TrackSequenceManager::instance()->setNextItem(static_cast<PlaylistItem *>(
480
        Q3ListViewItemIterator(const_cast<Playlist *>(this), Q3ListViewItemIterator::Visible).current()));
David Faure's avatar
David Faure committed
481
    action("forward")->trigger();
482
483
}

484
485
486
487
void Playlist::playNextAlbum()
{
    PlaylistItem *current = TrackSequenceManager::instance()->currentItem();
    if(!current)
488
        return; // No next album if we're not already playing.
489
490
491
492
493

    QString currentAlbum = current->file().tag()->album();
    current = TrackSequenceManager::instance()->nextItem();

    while(current && current->file().tag()->album() == currentAlbum)
494
        current = TrackSequenceManager::instance()->nextItem();
495
496

    TrackSequenceManager::instance()->setNextItem(current);
David Faure's avatar
David Faure committed
497
    action("forward")->trigger();
498
499
}

500
501
void Playlist::playNext()
{
502
    TrackSequenceManager::instance()->setCurrentPlaylist(this);
503
    setPlaying(TrackSequenceManager::instance()->nextItem());
504
505
506
507
}

void Playlist::stop()
{
508
    m_history.clear();
509
510
511
512
513
    setPlaying(0);
}

void Playlist::playPrevious()
{
514
    if(!playingItem())
515
        return;
516
517
518
519
520
521

    bool random = action("randomPlay") && action<KToggleAction>("randomPlay")->isChecked();

    PlaylistItem *previous = 0;

    if(random && !m_history.isEmpty()) {
522
        PlaylistItemList::Iterator last = --m_history.end();
523
        previous = *last;
524
        m_history.erase(last);
525
526
    }
    else {
527
528
        m_history.clear();
        previous = TrackSequenceManager::instance()->previousItem();
529
530
531
    }

    if(!previous)
532
        previous = static_cast<PlaylistItem *>(playingItem()->itemAbove());
533

534
    setPlaying(previous, false);
535
536
537
538
}

void Playlist::setName(const QString &n)
{
539
540
    m_collection->addNameToDict(n);
    m_collection->removeNameFromDict(m_playlistName);
541

542
543
544
545
    m_playlistName = n;
    emit signalNameChanged(m_playlistName);
}

546
void Playlist::save()
547
{
548
    if(m_fileName.isEmpty())
549
        return saveAs();
550

551
    QFile file(m_fileName);
552

Laurent Montel's avatar
Laurent Montel committed
553
    if(!file.open(QIODevice::WriteOnly))
554
        return KMessageBox::error(this, i18n("Could not save to file %1.", m_fileName));
555

556
557
558
559
    QTextStream stream(&file);

    QStringList fileList = files();

Laurent Montel's avatar
Laurent Montel committed
560
    for(QStringList::const_iterator it = fileList.constBegin(); it != fileList.constEnd(); ++it)
561
        stream << *it << endl;
562

563
    file.close();
564
565
566
567
}

void Playlist::saveAs()
{
568
569
    m_collection->removeFileFromDict(m_fileName);

570
    m_fileName = MediaFiles::savePlaylistDialog(name(), this);
571

572
    if(!m_fileName.isEmpty()) {
573
        m_collection->addFileToDict(m_fileName);
574

575
576
577
578
        // If there's no playlist name set, use the file name.
        if(m_playlistName.isEmpty())
            emit signalNameChanged(name());
        save();
579
    }
580
581
}

582
void Playlist::updateDeletedItem(PlaylistItem *item)
583
{
584
    m_members.remove(item->file().absFilePath());
585
586
    m_search.clearItem(item);

587
588
589
    m_history.removeAll(item);
    m_addTime.removeAll(item);
    m_subtractTime.removeAll(item);
590
}
591

592
593
594
void Playlist::clearItem(PlaylistItem *item, bool emitChanged)
{
    // Automatically updates internal structs via updateDeletedItem
595
    delete item;
596
597

    dataChanged();
598
599
}

600
void Playlist::clearItems(const PlaylistItemList &items)
601
{
602
603
    foreach(PlaylistItem *item, items)
        delete item;
604

605
    dataChanged();
606
607
}

608
609
PlaylistItem *Playlist::playingItem() // static
{
610
    return PlaylistItem::playingItems().isEmpty() ? 0 : PlaylistItem::playingItems().front();
611
612
}

613
QStringList Playlist::files() const
614
{
615
    QStringList list;
616

Laurent Montel's avatar
Laurent Montel committed
617
    for(Q3ListViewItemIterator it(const_cast<Playlist *>(this)); it.current(); ++it)
618
        list.append(static_cast<PlaylistItem *>(*it)->file().absFilePath());
619

620
    return list;
621
622
}

623
624
PlaylistItemList Playlist::items()
{
Laurent Montel's avatar
Laurent Montel committed
625
    return items(Q3ListViewItemIterator::IteratorFlag(0));
626
627
}

628
PlaylistItemList Playlist::visibleItems()
629
{
Laurent Montel's avatar
Laurent Montel committed
630
    return items(Q3ListViewItemIterator::Visible);
631
632
}

633
PlaylistItemList Playlist::selectedItems()
634
635
{
    PlaylistItemList list;
636
637
638

    switch(m_selectedCount) {
    case 0:
639
640
641
642
        break;
        // case 1:
        // list.append(m_lastSelected);
        // break;
643
    default:
644
645
646
        list = items(Q3ListViewItemIterator::IteratorFlag(Q3ListViewItemIterator::Selected |
                                                         Q3ListViewItemIterator::Visible));
        break;
647
    }
648

649
    return list;
650
651
}

652
653
PlaylistItem *Playlist::firstChild() const
{
Laurent Montel's avatar
Laurent Montel committed
654
    return static_cast<PlaylistItem *>(K3ListView::firstChild());
655
656
}

Scott Wheeler's avatar
Scott Wheeler committed
657
658
659
660
661
void Playlist::updateLeftColumn()
{
    int newLeftColumn = leftMostVisibleColumn();

    if(m_leftColumn != newLeftColumn) {
662
663
        updatePlaying();
        m_leftColumn = newLeftColumn;
Scott Wheeler's avatar
Scott Wheeler committed
664
665
666
    }
}

667
void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static
668
{
669
    m_visibleChanged = true;
Laurent Montel's avatar
Laurent Montel committed
670
    for(PlaylistItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it)
671
        (*it)->setVisible(visible);
672
673
}

674
675
676
677
678
void Playlist::setSearch(const PlaylistSearch &s)
{
    m_search = s;

    if(!m_searchEnabled)
679
        return;
680
681
682

    setItemsVisible(s.matchedItems(), true);
    setItemsVisible(s.unmatchedItems(), false);
683
684

    TrackSequenceManager::instance()->iterator()->playlistChanged();
685
686
687
688
689
}

void Playlist::setSearchEnabled(bool enabled)
{
    if(m_searchEnabled == enabled)
690
        return;
691
692
693
694

    m_searchEnabled = enabled;

    if(enabled) {
695
696
        setItemsVisible(m_search.matchedItems(), true);
        setItemsVisible(m_search.unmatchedItems(), false);
697
698
    }
    else
699
        setItemsVisible(items(), true);
700
701
}

702
703
704
void Playlist::markItemSelected(PlaylistItem *item, bool selected)
{
    if(selected && !item->isSelected()) {
705
706
        m_selectedCount++;
        m_lastSelected = item;
707
708
    }
    else if(!selected && item->isSelected())
709
        m_selectedCount--;
710
711
}

712
713
void Playlist::synchronizePlayingItems(const PlaylistList &sources, bool setMaster)
{
Laurent Montel's avatar
Laurent Montel committed
714
    for(PlaylistList::ConstIterator it = sources.constBegin(); it != sources.constEnd(); ++it) {
715
716
        if((*it)->playing()) {
            CollectionListItem *base = playingItem()->collectionItem();
Laurent Montel's avatar
Laurent Montel committed
717
            for(Q3ListViewItemIterator itemIt(this); itemIt.current(); ++itemIt) {
718
719
720
                PlaylistItem *item = static_cast<PlaylistItem *>(itemIt.current());
                if(base == item->collectionItem()) {
                    item->setPlaying(true, setMaster);
721
722
                    PlaylistItemList playing = PlaylistItem::playingItems();
                    TrackSequenceManager::instance()->setCurrent(item);
723
724
725
726
727
728
729
730
                    return;
                }
            }
            return;
        }
    }
}

731
732
733
734
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

735
736
void Playlist::copy()
{
737
738
739
740
741
742
743
744
745
746
747
    PlaylistItemList items = selectedItems();
    KUrl::List urls;

    foreach(PlaylistItem *item, items) {
        urls << KUrl::fromPath(item->file().absFilePath());
    }

    QMimeData *mimeData = new QMimeData;
    urls.populateMimeData(mimeData);

    QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
748
749
750
751
}

void Playlist::paste()
{
752
    decode(QApplication::clipboard()->mimeData(), static_cast<PlaylistItem *>(currentItem()));
753
754
755
756
}

void Playlist::clear()
{
757
758
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
759
        l = items();
760
761

    clearItems(l);
762
763
}

764
765
766
767
void Playlist::slotRefresh()
{
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
768
        l = visibleItems();
769

770
    KApplication::setOverrideCursor(Qt::waitCursor);
771
    for(PlaylistItemList::Iterator it = l.begin(); it != l.end(); ++it) {
772
        (*it)->refreshFromDisk();
773

774
775
776
777
778
779
        if(!(*it)->file().tag() || !(*it)->file().fileInfo().exists()) {
            kDebug(65432) << "Error while trying to refresh the tag.  "
                           << "This file has probably been removed."
                           << endl;
            delete (*it)->collectionItem();
        }
780

781
        processEvents();
782
783
784
785
    }
    KApplication::restoreOverrideCursor();
}

786
787
void Playlist::slotRenameFile()
{
788
    FileRenamer renamer;
789
    PlaylistItemList items = selectedItems();
790
791

    if(items.isEmpty())
792
793
        return;

794
795
    emit signalEnableDirWatch(false);

796
    m_blockDataChanged = true;
797
    renamer.rename(items);
798
    m_blockDataChanged = false;
799
800
    dataChanged();

801
    emit signalEnableDirWatch(true);
802
803
}

804
805
void Playlist::slotViewCover()
{
806
    const PlaylistItemList items = selectedItems();
807
808
    if (items.isEmpty())
        return;
Laurent Montel's avatar
Laurent Montel committed
809
    for(PlaylistItemList::ConstIterator it = items.constBegin(); it != items.constEnd(); ++it)
810
        (*it)->file().coverInfo()->popup();
811
812
813
814
815
}

void Playlist::slotRemoveCover()
{
    PlaylistItemList items = selectedItems();
816
    if(items.isEmpty())
817
        return;
818
    int button = KMessageBox::warningContinueCancel(this,
819
                                                    i18n("Are you sure you want to delete these covers?"),
820
                                                    QString(),
Laurent Montel's avatar
Laurent Montel committed
821
                                                    KGuiItem(i18n("&Delete Covers")));
822
    if(button == KMessageBox::Continue)
823
        refreshAlbums(items);
824
825
}

826
827
828
829
830
void Playlist::slotShowCoverManager()
{
    static CoverDialog *managerDialog = 0;

    if(!managerDialog)
831
        managerDialog = new CoverDialog(this);
832
833
834
835

    managerDialog->show();
}

836
837
838
839
840
841
842
void Playlist::slotAddCover(bool retrieveLocal)
{
    PlaylistItemList items = selectedItems();

    if(items.isEmpty())
        return;

843
    if(!retrieveLocal) {
844
845
846
        m_fetcher->setFile((*items.begin())->file());
        m_fetcher->chooseCover();
        return;
847
848
    }

849
850
    KUrl file = KFileDialog::getImageOpenUrl(
        KUrl( "kfiledialog://homedir" ), this, i18n("Select Cover Image File"));
851

852
853
854
    if(file.isEmpty())
        return;

855
856
857
    QString artist = items.front()->file().tag()->artist();
    QString album = items.front()->file().tag()->album();

858
859
860
861
    coverKey newId = CoverManager::addCover(file, artist, album);

    if(newId != CoverManager::NoMatch)
        refreshAlbums(items, newId);
862
863
}

864
865
866
867
868
869
870
// Called when image fetcher has added a new cover.
void Playlist::slotCoverChanged(int coverId)
{
    kDebug(65432) << "Refreshing information for newly changed covers.\n";
    refreshAlbums(selectedItems(), coverId);
}

871
void Playlist::slotGuessTagInfo(TagGuesser::Type type)
872
{
873
    KApplication::setOverrideCursor(Qt::waitCursor);