playlist.cpp 63.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 * Copyright (C) 2002-2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2008 Michael Pyne <mpyne@kde.org>
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
17

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

Michael Pyne's avatar
Michael Pyne committed
21 22
#include <KLocalizedString>
#include <KSharedConfig>
Scott Wheeler's avatar
Scott Wheeler committed
23
#include <kconfig.h>
24
#include <kmessagebox.h>
25
#include <kiconloader.h>
26
#include <klineedit.h>
27
#include <kio/copyjob.h>
28 29 30
#include <kactioncollection.h>
#include <kconfiggroup.h>
#include <ktoolbarpopupaction.h>
Laurent Montel's avatar
Poirt  
Laurent Montel committed
31 32 33
#include <kactionmenu.h>
#include <ktoggleaction.h>
#include <kselectaction.h>
34 35

#include <QCursor>
36
#include <QDir>
37
#include <QDirIterator>
38 39
#include <QToolTip>
#include <QFile>
Michael Pyne's avatar
Michael Pyne committed
40
#include <QFileDialog>
Laurent Montel's avatar
Laurent Montel committed
41 42 43
#include <QResizeEvent>
#include <QMouseEvent>
#include <QKeyEvent>
44
#include <QMimeData>
Michael Pyne's avatar
Michael Pyne committed
45
#include <QMenu>
46 47
#include <QTimer>
#include <QClipboard>
Laurent Montel's avatar
Laurent Montel committed
48 49 50
#include <QTextStream>
#include <QDropEvent>
#include <QDragEnterEvent>
51
#include <QPixmap>
52
#include <QStackedWidget>
53
#include <QScrollBar>
54 55
#include <id3v1genres.h>

56
#include <time.h>
57
#include <cmath>
58

Scott Wheeler's avatar
Scott Wheeler committed
59
#include "playlistitem.h"
60
#include "playlistcollection.h"
Scott Wheeler's avatar
Scott Wheeler committed
61
#include "playlistsearch.h"
62
#include "mediafiles.h"
63
#include "collectionlist.h"
64
#include "filerenamer.h"
65
#include "actioncollection.h"
66
#include "tracksequencemanager.h"
67
#include "tag.h"
68
#include "upcomingplaylist.h"
69
#include "deletedialog.h"
70
#include "webimagefetcher.h"
71
#include "coverinfo.h"
72
#include "coverdialog.h"
73
#include "tagtransactionmanager.h"
74
#include "cache.h"
Michael Pyne's avatar
Michael Pyne committed
75
#include "juk_debug.h"
76

77 78
using namespace ActionCollection;

79 80 81 82 83 84
/**
 * Used to give every track added in the program a unique identifier. See
 * PlaylistItem
 */
quint32 g_trackID = 0;

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

98 99 100 101
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings definition
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
102
bool Playlist::m_visibleChanged = false;
103
bool Playlist::m_shuttingDown = false;
104

105 106 107 108 109 110 111 112 113 114 115 116 117
/**
 * 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);
Michael Pyne's avatar
Michael Pyne committed
118
    void setInlineCompletionMode(KCompletion::CompletionMode mode);
119 120 121

    /**
     * Apply the settings.
122
     */
123
    void apply(Playlist *l) const;
124
    void sync() { writeConfig(); }
125 126 127 128 129 130 131 132 133

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

private:
    void writeConfig();

    static SharedSettings *m_instance;
134
    QList<int> m_columnOrder;
135
    QVector<bool> m_columnsVisible;
Michael Pyne's avatar
Michael Pyne committed
136
    KCompletion::CompletionMode m_inlineCompletion;
137 138
};

Scott Wheeler's avatar
Scott Wheeler committed
139 140
Playlist::SharedSettings *Playlist::SharedSettings::m_instance = 0;

141 142 143 144
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings public members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
145 146
Playlist::SharedSettings *Playlist::SharedSettings::instance()
{
147 148
    static SharedSettings settings;
    return &settings;
Scott Wheeler's avatar
Scott Wheeler committed
149 150 151
}

void Playlist::SharedSettings::setColumnOrder(const Playlist *l)
152
{
Scott Wheeler's avatar
Scott Wheeler committed
153
    if(!l)
154
        return;
155

Scott Wheeler's avatar
Scott Wheeler committed
156
    m_columnOrder.clear();
157

158
    for(int i = l->columnOffset(); i < l->columnCount(); ++i)
159
        m_columnOrder.append(l->header()->visualIndex(i)); // FIXME  MISMATCH with apply
Scott Wheeler's avatar
Scott Wheeler committed
160

161
    writeConfig();
Scott Wheeler's avatar
Scott Wheeler committed
162
}
163

164 165
void Playlist::SharedSettings::toggleColumnVisible(int column)
{
166 167
    if(column >= m_columnsVisible.size())
        m_columnsVisible.fill(true, column + 1);
168 169 170 171 172 173

    m_columnsVisible[column] = !m_columnsVisible[column];

    writeConfig();
}

Michael Pyne's avatar
Michael Pyne committed
174
void Playlist::SharedSettings::setInlineCompletionMode(KCompletion::CompletionMode mode)
175 176 177 178 179 180
{
    m_inlineCompletion = mode;
    writeConfig();
}


181
void Playlist::SharedSettings::apply(Playlist *l) const
Scott Wheeler's avatar
Scott Wheeler committed
182 183
{
    if(!l)
184
        return;
Scott Wheeler's avatar
Scott Wheeler committed
185

186
    int offset = l->columnOffset();
Scott Wheeler's avatar
Scott Wheeler committed
187
    int i = 0;
Kacper Kasper's avatar
Kacper Kasper committed
188
    //bool oldState = l->header()->blockSignals(true);
189
    foreach(int column, m_columnOrder)
Kacper Kasper's avatar
Kacper Kasper committed
190 191 192
        // FIXME this is broken
        l->header()->moveSection(i++ + offset, column + offset); // FIXME mismatch with setColumnOrder
    //l->header()->blockSignals(oldState);
193

194
    for(int i = 0; i < m_columnsVisible.size(); i++) {
Kacper Kasper's avatar
Kacper Kasper committed
195
        if(m_columnsVisible[i] && l->isColumnHidden(i + offset))
196
            l->showColumn(i + offset, false);
Kacper Kasper's avatar
Kacper Kasper committed
197
        else if(!m_columnsVisible[i] && !l->isColumnHidden(i + offset))
198
            l->hideColumn(i + offset, false);
199
    }
Scott Wheeler's avatar
Scott Wheeler committed
200 201

    l->updateLeftColumn();
202
    // FIXME rename
203
    //l->renameLineEdit()->setCompletionMode(m_inlineCompletion);
204
    l->slotColumnResizeModeChanged();
Scott Wheeler's avatar
Scott Wheeler committed
205 206
}

207 208 209 210
////////////////////////////////////////////////////////////////////////////////
// Playlist::ShareSettings protected members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
211 212
Playlist::SharedSettings::SharedSettings()
{
213
    KConfigGroup config(KSharedConfig::openConfig(), "PlaylistShared");
214

215
    bool resizeColumnsManually = config.readEntry("ResizeColumnsManually", false);
216
    action("resizeColumnsManually")->setChecked(resizeColumnsManually);
217

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

221
    // save column order
222
    m_columnOrder = config.readEntry("ColumnOrder", QList<int>());
223

224
    QList<int> l = config.readEntry("VisibleColumns", QList<int>());
225

226
    if(l.isEmpty()) {
227

228 229 230
        // Provide some default values for column visibility if none were
        // read from the configuration file.

231 232 233 234
        m_columnsVisible[PlaylistItem::BitrateColumn] = false;
        m_columnsVisible[PlaylistItem::CommentColumn] = false;
        m_columnsVisible[PlaylistItem::FileNameColumn] = false;
        m_columnsVisible[PlaylistItem::FullPathColumn] = false;
235 236
    }
    else {
237 238
        // Convert the int list into a bool list.

239
        m_columnsVisible.fill(false);
240 241
        for(int i = 0; i < l.size() && i < m_columnsVisible.size(); ++i)
            m_columnsVisible[i] = bool(l[i]);
242
    }
243

Michael Pyne's avatar
Michael Pyne committed
244 245
    m_inlineCompletion = KCompletion::CompletionMode(
        config.readEntry("InlineCompletionMode", int(KCompletion::CompletionAuto)));
246 247 248 249 250 251 252 253
}

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

void Playlist::SharedSettings::writeConfig()
{
254
    KConfigGroup config(KSharedConfig::openConfig(), "PlaylistShared");
255
    config.writeEntry("ColumnOrder", m_columnOrder);
256

257 258
    QList<int> l;
    for(int i = 0; i < m_columnsVisible.size(); i++)
259
        l.append(int(m_columnsVisible[i]));
260

261
    config.writeEntry("VisibleColumns", l);
262
    config.writeEntry("InlineCompletionMode", int(m_inlineCompletion));
263

264
    config.writeEntry("ResizeColumnsManually", manualResize());
265

266
    KSharedConfig::openConfig()->sync();
Scott Wheeler's avatar
Scott Wheeler committed
267
}
268

269
////////////////////////////////////////////////////////////////////////////////
270
// public members
271 272
////////////////////////////////////////////////////////////////////////////////

273
PlaylistItemList Playlist::m_history;
274
QVector<PlaylistItem *> Playlist::m_backMenuItems;
275 276
int Playlist::m_leftColumn = 0;

277
Playlist::Playlist(PlaylistCollection *collection, const QString &name,
278
                   const QString &iconName) :
279
    QTreeWidget(collection->playlistStack()),
280
    m_collection(collection),
281
    m_fetcher(new WebImageFetcher(this)),
282
    m_allowDuplicates(true),
283
    m_applySharedSettings(true),
284
    m_columnWidthModeChanged(false),
285
    m_disableColumnWidthUpdates(true),
286
    m_time(0),
287
    m_widthsDirty(true),
288
    m_searchEnabled(true),
289
    m_playlistName(name),
290
    m_rmbMenu(0),
291 292
    m_toolTip(0),
    m_blockDataChanged(false)
293
{
294
    setup();
295
    collection->setupPlaylist(this, iconName);
296 297
}

298
Playlist::Playlist(PlaylistCollection *collection, const PlaylistItemList &items,
299
                   const QString &name, const QString &iconName) :
300
    QTreeWidget(collection->playlistStack()),
301
    m_collection(collection),
302
    m_fetcher(new WebImageFetcher(this)),
303
    m_allowDuplicates(true),
304
    m_applySharedSettings(true),
305
    m_columnWidthModeChanged(false),
306
    m_disableColumnWidthUpdates(true),
307
    m_time(0),
308
    m_widthsDirty(true),
309 310
    m_searchEnabled(true),
    m_playlistName(name),
311
    m_rmbMenu(0),
312 313
    m_toolTip(0),
    m_blockDataChanged(false)
314 315 316 317 318 319 320
{
    setup();
    collection->setupPlaylist(this, iconName);
    createItems(items);
}

Playlist::Playlist(PlaylistCollection *collection, const QFileInfo &playlistFile,
321
                   const QString &iconName) :
322
    QTreeWidget(collection->playlistStack()),
323
    m_collection(collection),
324
    m_fetcher(new WebImageFetcher(this)),
325
    m_allowDuplicates(true),
326
    m_applySharedSettings(true),
327
    m_columnWidthModeChanged(false),
328
    m_disableColumnWidthUpdates(true),
329
    m_time(0),
330 331
    m_widthsDirty(true),
    m_searchEnabled(true),
332
    m_fileName(playlistFile.canonicalFilePath()),
333
    m_rmbMenu(0),
334 335
    m_toolTip(0),
    m_blockDataChanged(false)
336
{
337
    setup();
338
    loadFile(m_fileName, playlistFile);
339 340 341
    collection->setupPlaylist(this, iconName);
}

342
Playlist::Playlist(PlaylistCollection *collection, bool delaySetup, int extraColumns) :
343
    QTreeWidget(collection->playlistStack()),
344
    m_collection(collection),
345
    m_fetcher(new WebImageFetcher(this)),
346
    m_allowDuplicates(true),
347
    m_applySharedSettings(true),
348
    m_columnWidthModeChanged(false),
349
    m_disableColumnWidthUpdates(true),
350
    m_time(0),
351
    m_widthsDirty(true),
352
    m_searchEnabled(true),
353
    m_rmbMenu(0),
354 355
    m_toolTip(0),
    m_blockDataChanged(false)
356
{
357 358 359 360
    for(int i = 0; i < extraColumns; ++i) {
        addColumn(i18n("JuK")); // Placeholder text!
    }

361 362 363
    setup();

    if(!delaySetup)
Michael Pyne's avatar
Michael Pyne committed
364
        collection->setupPlaylist(this, "audio-midi");
365 366 367 368
}

Playlist::~Playlist()
{
369 370 371 372 373 374
    // 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();

375 376 377 378
    // clearItem() will take care of removing the items from the history,
    // so call clearItems() to make sure it happens.

    clearItems(items());
379

380
    /* delete m_toolTip; */
381

382
    if(!m_shuttingDown)
383
        m_collection->removePlaylist(this);
384 385
}

386 387
QString Playlist::name() const
{
388
    if(m_playlistName.isEmpty())
389
        return m_fileName.section(QDir::separator(), -1).section('.', 0, -2);
390
    else
391
        return m_playlistName;
392 393 394 395
}

FileHandle Playlist::currentFile() const
{
396
    return playingItem() ? playingItem()->file() : FileHandle::null();
397 398 399 400
}

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

    if(!m_addTime.isEmpty()) {
404 405 406
        foreach(const PlaylistItem *item, m_addTime) {
            if(item)
                m_time += item->file().tag()->seconds();
407
        }
408

409
        m_addTime.clear();
410 411 412
    }

    if(!m_subtractTime.isEmpty()) {
413 414 415
        foreach(const PlaylistItem *item, m_subtractTime) {
            if(item)
                m_time -= item->file().tag()->seconds();
416
        }
417

418
        m_subtractTime.clear();
419
    }
420 421

    return m_time;
422 423
}

424 425
void Playlist::playFirst()
{
426
    TrackSequenceManager::instance()->setNextItem(static_cast<PlaylistItem *>(
427
        *QTreeWidgetItemIterator(const_cast<Playlist *>(this), QTreeWidgetItemIterator::NotHidden)));
David Faure's avatar
David Faure committed
428
    action("forward")->trigger();
429 430
}

431 432 433 434
void Playlist::playNextAlbum()
{
    PlaylistItem *current = TrackSequenceManager::instance()->currentItem();
    if(!current)
435
        return; // No next album if we're not already playing.
436 437 438 439 440

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

    while(current && current->file().tag()->album() == currentAlbum)
441
        current = TrackSequenceManager::instance()->nextItem();
442 443

    TrackSequenceManager::instance()->setNextItem(current);
David Faure's avatar
David Faure committed
444
    action("forward")->trigger();
445 446
}

447 448
void Playlist::playNext()
{
449
    TrackSequenceManager::instance()->setCurrentPlaylist(this);
450
    setPlaying(TrackSequenceManager::instance()->nextItem());
451 452 453 454
}

void Playlist::stop()
{
455
    m_history.clear();
456 457 458 459 460
    setPlaying(0);
}

void Playlist::playPrevious()
{
461
    if(!playingItem())
462
        return;
463 464 465 466 467 468

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

    PlaylistItem *previous = 0;

    if(random && !m_history.isEmpty()) {
469
        PlaylistItemList::Iterator last = --m_history.end();
470
        previous = *last;
471
        m_history.erase(last);
472 473
    }
    else {
474 475
        m_history.clear();
        previous = TrackSequenceManager::instance()->previousItem();
476 477 478
    }

    if(!previous)
479
        previous = static_cast<PlaylistItem *>(playingItem()->itemAbove());
480

481
    setPlaying(previous, false);
482 483 484 485
}

void Playlist::setName(const QString &n)
{
486 487
    m_collection->addNameToDict(n);
    m_collection->removeNameFromDict(m_playlistName);
488

489 490 491 492
    m_playlistName = n;
    emit signalNameChanged(m_playlistName);
}

493
void Playlist::save()
494
{
495
    if(m_fileName.isEmpty())
496
        return saveAs();
497

498
    QFile file(m_fileName);
499

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

503 504 505 506
    QTextStream stream(&file);

    QStringList fileList = files();

507 508
    foreach(const QString &file, fileList)
        stream << file << endl;
509

510
    file.close();
511 512 513 514
}

void Playlist::saveAs()
{
515 516
    m_collection->removeFileFromDict(m_fileName);

517
    m_fileName = MediaFiles::savePlaylistDialog(name(), this);
518

519
    if(!m_fileName.isEmpty()) {
520
        m_collection->addFileToDict(m_fileName);
521

522 523 524 525
        // If there's no playlist name set, use the file name.
        if(m_playlistName.isEmpty())
            emit signalNameChanged(name());
        save();
526
    }
527 528
}

529
void Playlist::updateDeletedItem(PlaylistItem *item)
530
{
531
    m_members.remove(item->file().absFilePath());
532 533
    m_search.clearItem(item);

534 535 536
    m_history.removeAll(item);
    m_addTime.removeAll(item);
    m_subtractTime.removeAll(item);
537
}
538

539
void Playlist::clearItem(PlaylistItem *item)
540 541
{
    // Automatically updates internal structs via updateDeletedItem
542
    delete item;
543

544
    playlistItemsChanged();
545 546
}

547
void Playlist::clearItems(const PlaylistItemList &items)
548
{
549 550
    foreach(PlaylistItem *item, items)
        delete item;
551

552
    playlistItemsChanged();
553 554
}

555 556
PlaylistItem *Playlist::playingItem() // static
{
557
    return PlaylistItem::playingItems().isEmpty() ? 0 : PlaylistItem::playingItems().front();
558 559
}

560
QStringList Playlist::files() const
561
{
562
    QStringList list;
563

564
    for(QTreeWidgetItemIterator it(const_cast<Playlist *>(this)); *it; ++it)
565
        list.append(static_cast<PlaylistItem *>(*it)->file().absFilePath());
566

567
    return list;
568 569
}

570 571
PlaylistItemList Playlist::items()
{
572
    return items(QTreeWidgetItemIterator::IteratorFlag(0));
573 574
}

575
PlaylistItemList Playlist::visibleItems()
576
{
577
    return items(QTreeWidgetItemIterator::NotHidden);
578 579
}

580
PlaylistItemList Playlist::selectedItems()
581
{
Kacper Kasper's avatar
Kacper Kasper committed
582
    return items(QTreeWidgetItemIterator::Selected | QTreeWidgetItemIterator::NotHidden);
583 584
}

585 586
PlaylistItem *Playlist::firstChild() const
{
587
    return static_cast<PlaylistItem *>(topLevelItem(0));
588 589
}

Scott Wheeler's avatar
Scott Wheeler committed
590 591 592 593 594
void Playlist::updateLeftColumn()
{
    int newLeftColumn = leftMostVisibleColumn();

    if(m_leftColumn != newLeftColumn) {
595 596
        updatePlaying();
        m_leftColumn = newLeftColumn;
Scott Wheeler's avatar
Scott Wheeler committed
597 598 599
    }
}

600
void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static
601
{
602
    m_visibleChanged = true;
603 604

    foreach(PlaylistItem *playlistItem, items)
605
        playlistItem->setHidden(!visible);
606 607
}

608 609 610 611 612
void Playlist::setSearch(const PlaylistSearch &s)
{
    m_search = s;

    if(!m_searchEnabled)
613
        return;
614 615 616

    setItemsVisible(s.matchedItems(), true);
    setItemsVisible(s.unmatchedItems(), false);
617 618

    TrackSequenceManager::instance()->iterator()->playlistChanged();
619 620 621 622 623
}

void Playlist::setSearchEnabled(bool enabled)
{
    if(m_searchEnabled == enabled)
624
        return;
625 626 627 628

    m_searchEnabled = enabled;

    if(enabled) {
629 630
        setItemsVisible(m_search.matchedItems(), true);
        setItemsVisible(m_search.unmatchedItems(), false);
631 632
    }
    else
633
        setItemsVisible(items(), true);
634 635
}

636 637
void Playlist::synchronizePlayingItems(const PlaylistList &sources, bool setMaster)
{
638 639
    foreach(const Playlist *p, sources) {
        if(p->playing()) {
640
            CollectionListItem *base = playingItem()->collectionItem();
641 642
            for(QTreeWidgetItemIterator itemIt(this); *itemIt; ++itemIt) {
                PlaylistItem *item = static_cast<PlaylistItem *>(*itemIt);
643 644
                if(base == item->collectionItem()) {
                    item->setPlaying(true, setMaster);
645 646
                    PlaylistItemList playing = PlaylistItem::playingItems();
                    TrackSequenceManager::instance()->setCurrent(item);
647 648 649 650 651 652 653 654
                    return;
                }
            }
            return;
        }
    }
}

655 656 657 658
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

659 660
void Playlist::copy()
{
661
    PlaylistItemList items = selectedItems();
662
    QList<QUrl> urls;
663 664

    foreach(PlaylistItem *item, items) {
665
        urls << QUrl::fromLocalFile(item->file().absFilePath());
666 667 668
    }

    QMimeData *mimeData = new QMimeData;
669
    mimeData->setUrls(urls);
670 671

    QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
672 673 674 675
}

void Playlist::paste()
{
676
    decode(QApplication::clipboard()->mimeData(), static_cast<PlaylistItem *>(currentItem()));
677 678 679 680
}

void Playlist::clear()
{
681 682
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
683
        l = items();
684 685

    clearItems(l);
686 687
}

688 689 690 691
void Playlist::slotRefresh()
{
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
692
        l = visibleItems();
693

Luigi Toscano's avatar
Luigi Toscano committed
694
    QApplication::setOverrideCursor(Qt::WaitCursor);
695 696
    foreach(PlaylistItem *item, l) {
        item->refreshFromDisk();
697

698
        if(!item->file().tag() || !item->file().fileInfo().exists()) {
Michael Pyne's avatar
Michael Pyne committed
699
            qCDebug(JUK_LOG) << "Error while trying to refresh the tag.  "
700
                           << "This file has probably been removed.";
701
            delete item->collectionItem();
702
        }
703

704
        processEvents();
705
    }
Luigi Toscano's avatar
Luigi Toscano committed
706
    QApplication::restoreOverrideCursor();
707 708
}

709 710
void Playlist::slotRenameFile()
{
711
    FileRenamer renamer;
712
    PlaylistItemList items = selectedItems();
713 714

    if(items.isEmpty())
715 716
        return;

717 718
    emit signalEnableDirWatch(false);

719
    m_blockDataChanged = true;
720
    renamer.rename(items);
721
    m_blockDataChanged = false;
722
    playlistItemsChanged();
723

724
    emit signalEnableDirWatch(true);
725 726
}

727 728
void Playlist::slotViewCover()
{
729
    const PlaylistItemList items = selectedItems();
730 731
    if (items.isEmpty())
        return;
732 733
    foreach(const PlaylistItem *item, items)
        item->file().coverInfo()->popup();
734 735 736 737 738
}

void Playlist::slotRemoveCover()
{
    PlaylistItemList items = selectedItems();
739
    if(items.isEmpty())
740
        return;
741
    int button = KMessageBox::warningContinueCancel(this,
742
                                                    i18n("Are you sure you want to delete these covers?"),
743
                                                    QString(),
Laurent Montel's avatar
Laurent Montel committed
744
                                                    KGuiItem(i18n("&Delete Covers")));
745
    if(button == KMessageBox::Continue)
746
        refreshAlbums(items);
747 748
}

749 750 751 752 753
void Playlist::slotShowCoverManager()
{
    static CoverDialog *managerDialog = 0;

    if(!managerDialog)
754
        managerDialog = new CoverDialog(this);
755 756 757 758

    managerDialog->show();
}

759 760 761 762 763 764 765
void Playlist::slotAddCover(bool retrieveLocal)
{
    PlaylistItemList items = selectedItems();

    if(items.isEmpty())
        return;

766
    if(!retrieveLocal) {
767
        m_fetcher->setFile((*items.begin())->file());
768
        m_fetcher->searchCover();
769
        return;
770 771
    }

772 773 774 775 776 777
    QUrl file = QFileDialog::getOpenFileUrl(
        this, i18n("Select Cover Image File"),
        QUrl::fromLocalFile(QDir::home().path()),
        i18n("Images (*.png *.jpg)"), nullptr,
        0, QStringList() << QStringLiteral("file")
        );
778 779 780
    if(file.isEmpty())
        return;

781 782 783
    QString artist = items.front()->file().tag()->artist();
    QString album = items.front()->file().tag()->album();

784 785 786 787
    coverKey newId = CoverManager::addCover(file, artist, album);

    if(newId != CoverManager::NoMatch)
        refreshAlbums(items, newId);
788 789
}

790 791 792
// Called when image fetcher has added a new cover.
void Playlist::slotCoverChanged(int coverId)
{
Michael Pyne's avatar
Michael Pyne committed
793
    qCDebug(JUK_LOG) << "Refreshing information for newly changed covers.\n";
794 795 796
    refreshAlbums(selectedItems(), coverId);
}

797
void Playlist::slotGuessTagInfo(TagGuesser::Type type)