playlist.cpp 28.5 KB
Newer Older
1 2 3 4 5
/***************************************************************************
                          playlist.cpp  -  description
                             -------------------
    begin                : Sat Feb 16 2002
    copyright            : (C) 2002 by Scott Wheeler
6
    email                : wheeler@kde.org
7 8 9 10 11 12 13 14 15 16 17
***************************************************************************/

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

Scott Wheeler's avatar
Scott Wheeler committed
18
#include <kconfig.h>
19
#include <kmessagebox.h>
20 21
#include <kurldrag.h>
#include <kiconloader.h>
22
#include <klineedit.h>
Scott Wheeler's avatar
Scott Wheeler committed
23
#include <kaction.h>
Nadeem Hasan's avatar
Nadeem Hasan committed
24
#include <kpopupmenu.h>
Scott Wheeler's avatar
Scott Wheeler committed
25
#include <klocale.h>
26 27
#include <kdebug.h>

28 29
#include <qheader.h>
#include <qcursor.h>
Scott Wheeler's avatar
Scott Wheeler committed
30
#include <qdir.h>
31

32
#include <stdlib.h>
33
#include <limits.h>
34 35
#include <time.h>

36
#include "playlist.h"
Scott Wheeler's avatar
Scott Wheeler committed
37 38
#include "playlistitem.h"
#include "playlistsearch.h"
39
#include "genrelistlist.h"
Scott Wheeler's avatar
Scott Wheeler committed
40
#include "collectionlist.h"
41
#include "mediafiles.h"
42

43 44 45 46
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings definition
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
47
bool Playlist::m_visibleChanged = false;
48

49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
/**
 * 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);

    /**
     * Apply the settings.
65
     */
66 67 68 69 70 71 72 73 74 75 76 77 78 79
    void apply(Playlist *l) const;

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

private:
    void writeConfig();

    static SharedSettings *m_instance;
    QValueList<int> m_columnOrder;
    QValueVector<bool> m_columnsVisible;
};

Scott Wheeler's avatar
Scott Wheeler committed
80 81
Playlist::SharedSettings *Playlist::SharedSettings::m_instance = 0;

82 83 84 85
////////////////////////////////////////////////////////////////////////////////
// Playlist::SharedSettings public members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
86 87 88 89 90 91 92 93
Playlist::SharedSettings *Playlist::SharedSettings::instance()
{
    if(!m_instance)
	m_instance = new SharedSettings;
    return m_instance;
}

void Playlist::SharedSettings::setColumnOrder(const Playlist *l)
94
{
Scott Wheeler's avatar
Scott Wheeler committed
95
    if(!l)
96 97
	return;

Scott Wheeler's avatar
Scott Wheeler committed
98
    m_columnOrder.clear();
99

Scott Wheeler's avatar
Scott Wheeler committed
100 101 102
    for(int i = 0; i < l->columns(); ++i)
	m_columnOrder.append(l->header()->mapToIndex(i));

103
    writeConfig();
Scott Wheeler's avatar
Scott Wheeler committed
104
}
105

106 107 108 109 110 111 112 113 114 115 116
void Playlist::SharedSettings::toggleColumnVisible(int column)
{
    if(column >= int(m_columnsVisible.size()))
	m_columnsVisible.resize(column + 1, true);

    m_columnsVisible[column] = !m_columnsVisible[column];

    writeConfig();
}

void Playlist::SharedSettings::apply(Playlist *l) const
Scott Wheeler's avatar
Scott Wheeler committed
117 118 119 120 121
{
    if(!l)
	return;

    int i = 0;
122
    for(QValueListConstIterator<int> it = m_columnOrder.begin(); it != m_columnOrder.end(); ++it)
Scott Wheeler's avatar
Scott Wheeler committed
123
	l->header()->moveSection(i++, *it);
124 125 126 127 128 129 130

    for(uint i = 0; i < m_columnsVisible.size(); i++) {
	if(m_columnsVisible[i] && ! l->isColumnVisible(i))
	    l->showColumn(i);
	else if(! m_columnsVisible[i] && l->isColumnVisible(i))
	    l->hideColumn(i);
    }
Scott Wheeler's avatar
Scott Wheeler committed
131 132

    l->updateLeftColumn();
Scott Wheeler's avatar
Scott Wheeler committed
133 134
}

135 136 137 138
////////////////////////////////////////////////////////////////////////////////
// Playlist::ShareSettings protected members
////////////////////////////////////////////////////////////////////////////////

Scott Wheeler's avatar
Scott Wheeler committed
139 140 141 142
Playlist::SharedSettings::SharedSettings()
{
    KConfig *config = kapp->config();
    {
143
	KConfigGroupSaver saver(config, "PlaylistShared");
144 145

	// save column order
Scott Wheeler's avatar
Scott Wheeler committed
146
	m_columnOrder = config->readIntListEntry("ColumnOrder");
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169

	QValueList<int> l = config->readIntListEntry("VisibleColumns");
	m_columnsVisible.resize(l.size(), true);

	// save visible columns
	uint i = 0;
	for(QValueList<int>::Iterator it = l.begin(); it != l.end(); ++it) {
	    if(! bool(*it))
		m_columnsVisible[i] = bool(*it);
	    i++;
	}
    }
}

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

void Playlist::SharedSettings::writeConfig()
{
    KConfig *config = kapp->config();

    {
170
	KConfigGroupSaver saver(config, "PlaylistShared");
171 172 173 174 175
	config->writeEntry("ColumnOrder", m_columnOrder);

	QValueList<int> l;
	for(uint i = 0; i < m_columnsVisible.size(); i++)
	    l.append(int(m_columnsVisible[i]));
176

177
	config->writeEntry("VisibleColumns", l);
Scott Wheeler's avatar
Scott Wheeler committed
178
    }
179 180

    config->sync();
Scott Wheeler's avatar
Scott Wheeler committed
181
}
182

183
////////////////////////////////////////////////////////////////////////////////
184
// public members
185 186
////////////////////////////////////////////////////////////////////////////////

187 188 189
PlaylistItem *Playlist::m_playingItem = 0;
int Playlist::m_leftColumn = 0;

190
Playlist::Playlist(QWidget *parent, const QString &name) : KListView(parent, name.latin1()),
191
							   m_playlistName(name)
192

193
{
194
    setup();
195 196
}

197
Playlist::Playlist(const QFileInfo &playlistFile, QWidget *parent, const char *name) : KListView(parent, name),
198
										       m_fileName(playlistFile.absFilePath())
199
{
200
    setup();
201
    loadFile(m_fileName, playlistFile);
202 203 204 205 206 207 208
}

Playlist::~Playlist()
{

}

209
void Playlist::save()
210
{
211
    if(m_fileName.isEmpty())
212
	return saveAs();
213

214
    QFile file(m_fileName);
215 216

    if(!file.open(IO_WriteOnly))
217
	return KMessageBox::error(this, i18n("Could not save to file %1.").arg(m_fileName));
218

219 220 221 222 223 224
    QTextStream stream(&file);

    QStringList fileList = files();

    for(QStringList::Iterator it = fileList.begin(); it != fileList.end(); ++it)
	stream << *it << endl;
225

226
    file.close();
227 228 229 230
}

void Playlist::saveAs()
{
231
    m_fileName = MediaFiles::savePlaylistDialog(name(), this);
232

233 234
    if(!m_fileName.isEmpty()) {
	// If there's no playlist name set, use the file name.
235
	if(m_playlistName.isEmpty())
236
	    emit signalNameChanged(name());
237

238 239
	save();
    }
240 241 242 243
}

void Playlist::refresh()
{
Scott Wheeler's avatar
Scott Wheeler committed
244 245 246
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
	l = items();
247 248 249

    KApplication::setOverrideCursor(Qt::waitCursor);
    int j = 0;
250 251
    for(PlaylistItemList::Iterator it = l.begin(); it != l.end(); ++it) {
	(*it)->slotRefreshFromDisk();
252 253 254 255 256
	if(j % 5 == 0)
	    kapp->processEvents();
	j = j % 5 + 1;
    }
    KApplication::restoreOverrideCursor();
257 258
}

259 260 261 262
void Playlist::clearItem(PlaylistItem *item, bool emitChanged)
{
    emit signalAboutToRemove(item);
    m_members.remove(item->absFilePath());
263
    if (!m_randomList.isEmpty() && !m_visibleChanged)
264
        m_randomList.remove(item);
265 266 267 268 269
    item->deleteLater();
    if(emitChanged)
	emit signalNumberOfItemsChanged(this);
}

270
void Playlist::clearItems(const PlaylistItemList &items)
271
{
272 273 274
    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
	clearItem(*it, false);

275
    emit signalNumberOfItemsChanged(this);
276 277
}

278
QStringList Playlist::files() const
279
{
280 281 282 283
    QStringList list;
    for(PlaylistItem *i = static_cast<PlaylistItem *>(firstChild()); i; i = static_cast<PlaylistItem *>(i->itemBelow()))
	list.append(i->absFilePath());

284
    return list;
285 286
}

287 288 289 290 291 292 293 294 295 296
PlaylistItemList Playlist::items()
{
    PlaylistItemList list;
    for(QListViewItemIterator it(this); it.current(); ++it)
	list.append(static_cast<PlaylistItem *>(it.current()));

    return list;
}

PlaylistItemList Playlist::visibleItems() const
297 298
{
    PlaylistItemList list;
299 300 301 302 303 304
    for(PlaylistItem *i = static_cast<PlaylistItem *>(firstChild()); i; i = static_cast<PlaylistItem *>(i->itemBelow())) {
        //This check should be removed at some point since those items should all be already visible
        //at the time of writing there's a bug that leaves some invisible items in the list
        if(i->isVisible())
            list.append(i);
        else
Zack Rusin's avatar
Zack Rusin committed
305
            kdDebug(65432)<<"File shouldn't be in the list"<< i->fileName()<<endl;
306
    }
307

308
    return list;
309 310 311 312 313 314
}

PlaylistItemList Playlist::selectedItems() const
{
    PlaylistItemList list;
    for(PlaylistItem *i = static_cast<PlaylistItem *>(firstChild()); i; i = static_cast<PlaylistItem *>(i->itemBelow()))
315 316
        if(i->isSelected())
            list.append(i);
317

318
    return list;
319 320
}

321 322 323 324
PlaylistItemList Playlist::historyItems(PlaylistItem *current, bool random) const
{
    PlaylistItemList list;

Scott Wheeler's avatar
Scott Wheeler committed
325 326 327 328 329
    if (random) {
        PlaylistItemList::ConstIterator it = m_history.end();

        for(int j = 0; it != m_history.begin() && j < 10; --it, ++j)
            list.append(*it);
330
    }
Scott Wheeler's avatar
Scott Wheeler committed
331
    else if(current) {
332
        current = static_cast<PlaylistItem *>(current->itemAbove());
Scott Wheeler's avatar
Scott Wheeler committed
333
        for(int j = 0; current && j < 10; ++j) {
334
            list.append(current);
335
            current = static_cast<PlaylistItem *>(current->itemAbove());
Scott Wheeler's avatar
Scott Wheeler committed
336
	}
337 338 339 340 341
    }

    return list;
}

342
PlaylistItem *Playlist::nextItem(PlaylistItem *current, bool random)
343
{
344
    if(!current)
345
	return 0;
346

347 348 349
    PlaylistItem *i;

    if(random) {
350
        if (m_randomList.count() <= 1 || m_visibleChanged) {
351
            m_randomList = visibleItems();
352
            m_visibleChanged = false; // got the change
353 354 355 356
        }

        m_randomList.remove(current);

357
        m_history.append(current);
358 359

        i = current;
Scott Wheeler's avatar
Scott Wheeler committed
360 361 362 363
        if(!m_randomList.isEmpty()) {
	    while(i == current)
		i = m_randomList[KApplication::random() % m_randomList.count()];
	}
364 365
    }
    else
366 367
    {
        m_history.clear();
368
	i = static_cast<PlaylistItem *>(current->itemBelow());
369
    }
370

371
    return i;
372 373
}

374 375 376
PlaylistItem *Playlist::previousItem(PlaylistItem *current, bool random)
{
    if(!current)
377
        return 0;
378

Scott Wheeler's avatar
Scott Wheeler committed
379 380 381 382
    if(random && !m_history.isEmpty()) {
        PlaylistItemList::Iterator last = m_history.fromLast();
        PlaylistItem *item = *last;
        m_history.remove(last);
383 384
        return item;
    }
385

386
    m_history.clear();
387 388 389 390
    if(!current->itemAbove())
        return current;

    return static_cast<PlaylistItem *>(current->itemAbove());
391 392
}

393 394
QString Playlist::name() const
{
395
    if(m_playlistName.isNull())
396
	return m_fileName.section(QDir::separator(), -1).section('.', 0, -2);
397
    else
398
	return m_playlistName;
399 400 401 402
}

void Playlist::setName(const QString &n)
{
403
    m_playlistName = n;
404
    emit signalNameChanged(m_playlistName);
405 406
}

Scott Wheeler's avatar
Scott Wheeler committed
407 408 409 410 411 412 413 414 415 416 417 418 419
void Playlist::updateLeftColumn()
{
    int newLeftColumn = leftMostVisibleColumn();

    if(m_leftColumn != newLeftColumn) {
	if(m_playingItem) {
	    m_playingItem->setPixmap(m_leftColumn, QPixmap(0, 0));
	    m_playingItem->setPixmap(newLeftColumn, QPixmap(UserIcon("playing")));
	}
	m_leftColumn = newLeftColumn;
    }
}

420
void Playlist::setItemsVisible(const PlaylistItemList &items, bool visible) // static
421
{
422
    m_visibleChanged = true;
423 424 425 426
    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
	(*it)->setVisible(visible);
}

427 428 429 430
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

431
void Playlist::slotSetNext()
432
{
433 434 435
    if(!selectedItems().isEmpty())
	emit signalSetNext(selectedItems().first());
}
436

437 438 439 440 441 442 443 444 445 446 447 448
void Playlist::copy()
{
    kapp->clipboard()->setData(dragObject(0), QClipboard::Clipboard);
}

void Playlist::paste()
{
    decode(kapp->clipboard()->data());
}

void Playlist::clear()
{
449 450 451 452 453
    PlaylistItemList l = selectedItems();
    if(l.isEmpty())
	l = items();

    clearItems(l);
454 455
}

456 457 458 459 460 461 462 463 464
void Playlist::slotRenameFile()
{
    KApplication::setOverrideCursor(Qt::waitCursor);
    PlaylistItemList items = selectedItems();
    for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
        (*it)->renameFile();
    KApplication::restoreOverrideCursor();
}

465 466 467 468 469 470 471 472 473 474
void Playlist::slotGuessTagInfoFile()
{
    KApplication::setOverrideCursor(Qt::waitCursor);
    PlaylistItemList items = selectedItems();
    for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
        (*it)->guessTagInfoFromFile();
    KApplication::restoreOverrideCursor();
}

void Playlist::slotGuessTagInfoInternet()
475
{
476 477
    //not sure if the cursor stuff makes sense
    //since guessing will be asynchronous anyway
478
    KApplication::setOverrideCursor(Qt::waitCursor);
479
    PlaylistItemList items = selectedItems();
480
    for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it)
481
        (*it)->guessTagInfoFromInternet();
482
    KApplication::restoreOverrideCursor();
483 484
}

485 486 487 488 489
void Playlist::slotReload()
{
    QFileInfo fileInfo(m_fileName);
    if(!fileInfo.exists() || !fileInfo.isFile() || !fileInfo.isReadable())
	return;
490

491 492 493 494
    clear();
    loadFile(m_fileName, fileInfo);
}

495
////////////////////////////////////////////////////////////////////////////////
496
// protected members
497 498
////////////////////////////////////////////////////////////////////////////////

499 500 501 502 503
void Playlist::deleteFromDisk(const PlaylistItemList &items)
{
    if(isVisible() && !items.isEmpty()) {

        QStringList files;
504 505
	for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
            files.append((*it)->fileName());
506 507 508 509 510 511 512

	QString message;

	if(files.count() == 1)
	    message = i18n("Do you really want to delete this item from your disk?");
	else
	    message = i18n("Do you really want to delete these %1 items from your disk?").arg(QString::number(files.count()));
513

514
	if(KMessageBox::questionYesNoList(this, message, files) == KMessageBox::Yes) {
515 516 517
	    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
		if(QFile::remove((*it)->filePath())) {
		    emit signalAboutToRemove(*it);
518
                    if(!m_randomList.isEmpty() && !m_visibleChanged)
519
                        m_randomList.remove(*it);
520
		    delete *it;
521 522
		}
		else
523
		    KMessageBox::sorry(this, i18n("Could not delete ") + (*it)->fileName() + ".");
524 525 526 527 528 529 530
	    }

	}
	emit signalNumberOfItemsChanged(this);
    }
}

531
QDragObject *Playlist::dragObject(QWidget *parent)
532
{
533
    PlaylistItemList items = selectedItems();
534
    KURL::List urls;
535
    for(PlaylistItemList::Iterator it = items.begin(); it != items.end(); ++it) {
536
	KURL url;
537
	url.setPath((*it)->absFilePath());
538 539
	urls.append(url);
    }
540

541
    KURLDrag *drag = new KURLDrag(urls, parent, "Playlist Items");
542 543
    drag->setPixmap(SmallIcon("sound"));

544
    return drag;
545 546
}

547 548 549 550 551 552
bool Playlist::canDecode(QMimeSource *s)
{
    KURL::List urls;
    return KURLDrag::decode(s, urls) && !urls.isEmpty();
}

553 554 555
void Playlist::decode(QMimeSource *s)
{
    KURL::List urls;
556

557 558
    if(!KURLDrag::decode(s, urls) || urls.isEmpty())
	return;
559

560
    QStringList fileList;
561

562 563
    for(KURL::List::Iterator it = urls.begin(); it != urls.end(); it++)
	fileList.append((*it).path());
564

565
    emit signalFilesDropped(fileList, this);
566 567
}

568 569 570
bool Playlist::eventFilter(QObject* watched, QEvent* e)
{
    if(watched->inherits("QHeader")) { // Gotcha!
571

572
	if(e->type() == QEvent::MouseButtonPress) {
573

574 575 576
	    QMouseEvent *me = static_cast<QMouseEvent*>(e);

	    if(me->button() == Qt::RightButton) {
577
		m_headerMenu->popup(QCursor::pos());
578 579 580 581 582 583 584 585
		return true;
	    }
	}
    }

    return KListView::eventFilter(watched, e);
}

586 587 588 589 590 591 592 593 594 595 596
void Playlist::contentsDropEvent(QDropEvent *e)
{
    QListViewItem *moveAfter = itemAt(e->pos());
    if(!moveAfter)
	moveAfter = lastItem();

    // This is slightly more efficient since it doesn't have to cast everything
    // to PlaylistItem.

    if(e->source() == this) {
	QPtrList<QListViewItem> items = KListView::selectedItems();
597

598
	for(QPtrListIterator<QListViewItem> it(items); it.current(); ++it) {
599 600 601 602
	    (*it)->moveItem(moveAfter);
	    moveAfter = *it;
	}
    }
603 604
    else
	decode(e);
605 606 607 608
}

void Playlist::contentsDragMoveEvent(QDragMoveEvent *e)
{
609
    e->accept(KURLDrag::canDecode(e));
610 611
}

Scott Wheeler's avatar
Scott Wheeler committed
612 613
void Playlist::showEvent(QShowEvent *e)
{
614
    SharedSettings::instance()->apply(this);
Scott Wheeler's avatar
Scott Wheeler committed
615 616 617
    KListView::showEvent(e);
}

618
PlaylistItem *Playlist::createItem(const QFileInfo &file, const QString &absFilePath, QListViewItem *after, bool emitChanged)
619
{
620
    QString filePath;
621

622 623 624 625
    if(absFilePath.isNull())
	filePath = resolveSymLinks(file);
    else
	filePath = absFilePath;
626

627
    CollectionListItem *item = CollectionList::instance()->lookup(filePath);
628

629
    if(!item) {
630
	item = new CollectionListItem(file, filePath);
631

632 633 634 635 636 637 638
	// If a valid tag was not created, destroy the CollectionListItem.
	if(!item->isValid()) {
	    kdError() << "Playlist::createItem() -- A valid tag was not created for \"" << file.filePath() << "\"" << endl;
	    delete item;
	    return 0;
	}
    }
639

640
    if(item && !m_members.insert(filePath) || m_allowDuplicates) {
641
	PlaylistItem *i;
642
	if(after)
643
	    i = new PlaylistItem(item, this, after);
644
	else
645
	    i = new PlaylistItem(item, this);
646
        if(!m_randomList.isEmpty() && !m_visibleChanged)
647
            m_randomList.append(i);
648
	emit signalNumberOfItemsChanged(this);
649
	connect(item, SIGNAL(destroyed()), i, SLOT(deleteLater()));
650 651 652 653

	if(emitChanged)
	    emit signalNumberOfItemsChanged(this);

654
	return i;
655 656
    }
    else
657
	return 0;
658 659
}

660 661 662
void Playlist::createItems(const PlaylistItemList &siblings)
{
    PlaylistItem *previous = 0;
663

664 665 666 667
    for(PlaylistItemList::ConstIterator it = siblings.begin(); it != siblings.end(); ++it) {

	if(!m_members.insert(resolveSymLinks((*it)->absFilePath()))) {
	    previous = new PlaylistItem((*it)->collectionItem(), this, previous);
668
	    connect((*it)->collectionItem(), SIGNAL(destroyed()), *it, SLOT(deleteLater()));
669 670
	}
    }
671
    emit signalNumberOfItemsChanged(this);
672 673
}

674 675
void Playlist::hideColumn(int c)
{
676
    m_headerMenu->setItemChecked(c, false);
677 678 679 680 681

    setColumnWidthMode(c, Manual);
    setColumnWidth(c, 0);
    setResizeMode(QListView::LastColumn);
    triggerUpdate();
682 683 684 685 686 687 688 689

    if(c == m_leftColumn) {
	if(m_playingItem) {
	    m_playingItem->setPixmap(m_leftColumn, QPixmap(0, 0));
	    m_playingItem->setPixmap(leftMostVisibleColumn(), QPixmap(UserIcon("playing")));
	}
	m_leftColumn = leftMostVisibleColumn();
    }
690
    emit signalVisibleColumnsChanged();
691 692 693 694
}

void Playlist::showColumn(int c)
{
695
    m_headerMenu->setItemChecked(c, true);
696 697

    setColumnWidthMode(c, Maximum);
698

699 700
    int w = 0;
    QListViewItemIterator it(this);
701
    for (; it.current(); ++it )
702
	w = QMAX(it.current()->width(fontMetrics(), this, c), w);
703

704 705
    setColumnWidth(c, w);
    triggerUpdate();
706 707 708 709 710 711 712 713

    if(c == leftMostVisibleColumn()) {
	if(m_playingItem) {
	    m_playingItem->setPixmap(m_leftColumn, QPixmap(0, 0));
	    m_playingItem->setPixmap(leftMostVisibleColumn(), QPixmap(UserIcon("playing")));
	}
	m_leftColumn = leftMostVisibleColumn();
    }
714
    emit signalVisibleColumnsChanged();
715 716 717 718
}

bool Playlist::isColumnVisible(int c) const
{
719
    return columnWidth(c) != 0;
720 721
}

722 723 724
// Though it's somewhat obvious, this function will stat the file, so only use it when
// you're out of a performance critical loop.

725
QString Playlist::resolveSymLinks(const QFileInfo &file) // static
726
{
727 728 729 730
    char real[PATH_MAX];
    if(file.exists() && realpath(QFile::encodeName(file.absFilePath()).data(), real))
	return QFile::decodeName(real);
    else
731
	return file.filePath();
732

733 734
}

735
////////////////////////////////////////////////////////////////////////////////
736
// private members
737 738 739 740 741 742 743 744 745 746 747
////////////////////////////////////////////////////////////////////////////////

void Playlist::setup()
{
    addColumn(i18n("Track Name"));
    addColumn(i18n("Artist"));
    addColumn(i18n("Album"));
    addColumn(i18n("Track"));
    addColumn(i18n("Genre"));
    addColumn(i18n("Year"));
    addColumn(i18n("Length"));
748
    addColumn(i18n("Comment"));
749 750
    addColumn(i18n("File Name"));

751 752 753 754 755
    // These settings aren't really respected in KDE < 3.1.1, fixed in CVS

    setRenameable(PlaylistItem::TrackColumn, true);
    setRenameable(PlaylistItem::ArtistColumn, true);
    setRenameable(PlaylistItem::AlbumColumn, true);
756
    setRenameable(PlaylistItem::TrackNumberColumn, true);
757
    setRenameable(PlaylistItem::GenreColumn, true);
758
    setRenameable(PlaylistItem::YearColumn, true);
759

760 761 762
    setAllColumnsShowFocus(true);
    setSelectionMode(QListView::Extended);
    setShowSortIndicator(true);
763
    setDropVisualizer(true);
764 765
    setItemMargin(3);

766

767
    setSorting(1);
768

769 770 771 772
    //////////////////////////////////////////////////
    // setup header RMB menu
    //////////////////////////////////////////////////

773
    m_columnVisibleAction = new KActionMenu(i18n("&Show Columns"), this, "showColumns");
774 775 776
    m_headerMenu = m_columnVisibleAction->popupMenu();
    m_headerMenu->insertTitle(i18n("Show"));
    m_headerMenu->setCheckable(true);
777 778 779

    for(int i =0; i < header()->count(); ++i) {

780
	m_headerMenu->insertItem(header()->label(i), i);
781

782
	m_headerMenu->setItemChecked(i, true);
783 784
    }

785
    connect(m_headerMenu, SIGNAL(activated(int)), this, SLOT(slotToggleColumnVisible(int)));
786

787 788 789 790
    //////////////////////////////////////////////////
    // hide some columns by default
    //////////////////////////////////////////////////

791

792 793 794
    hideColumn(PlaylistItem::CommentColumn);
    hideColumn(PlaylistItem::FileNameColumn);

795 796 797 798
    //////////////////////////////////////////////////
    // setup playlist RMB menu
    //////////////////////////////////////////////////

799
    m_rmbMenu = new KPopupMenu(this);
800

801
    m_rmbMenu->insertItem(SmallIcon("player_play"), i18n("Play Next"), this, SLOT(slotSetNext()));
802
    m_rmbMenu->insertSeparator();
803 804 805 806
    m_rmbMenu->insertItem(SmallIcon("editcut"), i18n("Cut"), this, SLOT(cut()));
    m_rmbMenu->insertItem(SmallIcon("editcopy"), i18n("Copy"), this, SLOT(copy()));
    m_rmbPasteID = m_rmbMenu->insertItem(SmallIcon("editpaste"), i18n("Paste"), this, SLOT(paste()));
    m_rmbMenu->insertItem(SmallIcon("editclear"), i18n("Clear"), this, SLOT(clear()));
807

808
    m_rmbMenu->insertSeparator();
809

810
    m_rmbMenu->insertItem(SmallIcon("editdelete"), i18n("Remove From Disk"), this, SLOT(slotDeleteSelectedItems()));
811

812
    m_rmbEditID = m_rmbMenu->insertItem(SmallIcon("edittool"), i18n("Edit"), this, SLOT(slotRenameTag()));
813 814

    connect(this, SIGNAL(selectionChanged()),
815
	    this, SLOT(slotEmitSelected()));
816
    connect(this, SIGNAL(doubleClicked(QListViewItem *)),
817
	    this, SLOT(slotEmitDoubleClicked(QListViewItem *)));
818
    connect(this, SIGNAL(contextMenuRequested( QListViewItem *, const QPoint&, int)),
819
	    this, SLOT(slotShowRMBMenu(QListViewItem *, const QPoint &, int)));
820
    connect(this, SIGNAL(itemRenamed(QListViewItem *, const QString &, int)),
821
	    this, SLOT(slotApplyModification(QListViewItem *, const QString &, int)));
822 823

    //////////////////////////////////////////////////
824

825 826
    addColumn(QString::null);
    setResizeMode(QListView::LastColumn);
827

828
    setAcceptDrops(true);
829
    m_allowDuplicates = false;
830 831

    connect(header(), SIGNAL(indexChange(int, int, int)), this, SLOT(slotColumnOrderChanged(int, int, int)));
832 833 834

    connect(this, SIGNAL(signalDataChanged()), this, SIGNAL(signalChanged()));
    connect(this, SIGNAL(signalNumberOfItemsChanged(Playlist *)), this, SIGNAL(signalChanged()));
835 836
}

837 838 839 840 841 842 843 844 845 846 847 848 849 850
void Playlist::loadFile(const QString &fileName, const QFileInfo &fileInfo)
{
    QFile file(fileName);
    if(!file.open(IO_ReadOnly))
	return;

    QTextStream stream(&file);

    // turn off non-explicit sorting
    setSorting(columns() + 1);

    PlaylistItem *after = 0;

    while(!stream.atEnd()) {
851
	QString itemName = stream.readLine().stripWhiteSpace();
852 853 854 855 856 857

	QFileInfo item(itemName);

	if(item.isRelative())
	    item.setFile(QDir::cleanDirPath(fileInfo.dirPath(true) + "/" + itemName));

Scott Wheeler's avatar
Scott Wheeler committed
858
	if(MediaFiles::isMediaFile(item.fileName()) && item.exists() && item.isFile() && item.isReadable()) {
859
	    if(after)
860
		after = createItem(item, QString::null, after, false);
861
	    else
862
		after = createItem(item, QString::null, 0, false);
863 864 865 866
	}
    }

    file.close();
867 868

    emit signalNumberOfItemsChanged(this);
869 870
}

871
void Playlist::setPlaying(PlaylistItem *item, bool p)
872
{
873
    if(p) {
874 875 876 877 878 879 880
	m_playingItem = item;
	item->setPixmap(m_leftColumn, QPixmap(UserIcon("playing")));
    }
    else {
	m_playingItem = 0;
	item->setPixmap(m_leftColumn, QPixmap(0, 0));
    }
881

882
    item->setPlaying(p);
883 884
}

Scott Wheeler's avatar
Scott Wheeler committed
885 886 887 888 889
bool Playlist::playing() const
{
    return m_playingItem && this == static_cast<Playlist *>(m_playingItem->listView());
}

890 891 892 893
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////

894
void Playlist::slotShowRMBMenu(QListViewItem *item, const QPoint &point, int column)
895
{
896 897 898
    if(!item)
	return;

899
    m_rmbMenu->setItemEnabled(m_rmbPasteID, canDecode(kapp->clipboard()->data()));
900

901 902 903
    bool showEdit =
	(column == PlaylistItem::TrackColumn) ||
	(column == PlaylistItem::ArtistColumn) ||
904
	(column == PlaylistItem::AlbumColumn) ||
905 906 907
	(column == PlaylistItem::TrackNumberColumn) ||
	(column == PlaylistItem::GenreColumn) ||
	(column == PlaylistItem::YearColumn);
908

909
    m_rmbMenu->setItemEnabled(m_rmbEditID, showEdit);
910

911 912
    m_rmbMenu->popup(point);
    m_currentColumn = column;
913 914
}

915
void Playlist::slotRenameTag()
916
{
917 918
    // kdDebug(65432) << "Playlist::slotRenameTag()" << endl;