playlistbox.cpp 17.7 KB
Newer Older
1 2 3 4
/***************************************************************************
                          playlistbox.cpp  -  description
                             -------------------
    begin                : Thu Sep 12 2002
5
    copyright            : (C) 2002, 2003 by Scott Wheeler,
6
    email                : wheeler@kde.org
7 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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <kiconloader.h>
19
#include <kurldrag.h>
20
#include <kmessagebox.h>
Nadeem Hasan's avatar
Nadeem Hasan committed
21
#include <kinputdialog.h>
Nadeem Hasan's avatar
Nadeem Hasan committed
22
#include <kpopupmenu.h>
23
#include <kaction.h>
24
#include <kdebug.h>
25

26
#include <qheader.h>
27 28
#include <qpainter.h>
#include <qregexp.h>
29
#include <qwidgetstack.h>
30 31

#include "playlistbox.h"
32
#include "playlist.h"
33 34
#include "collectionlist.h"
#include "dynamicplaylist.h"
35
#include "viewmode.h"
36
#include "searchplaylist.h"
37
#include "actioncollection.h"
38
#include "cache.h"
39
#include "k3bexporter.h"
40 41

using namespace ActionCollection;
42 43 44 45 46

////////////////////////////////////////////////////////////////////////////////
// PlaylistBox public methods
////////////////////////////////////////////////////////////////////////////////

47 48
PlaylistBox::PlaylistBox(QWidget *parent, QWidgetStack *playlistStack,
			 const QString &name) :
49
    KListView(parent, name.latin1()),
50
    PlaylistCollection(playlistStack),
51 52
    m_updatePlaylistStack(true),
    m_viewModeIndex(0),
53
    m_hasSelection(false),
54
    m_doingMultiSelect(false),
55 56
    m_dropItem(0),
    m_dynamicPlaylist(0)
57
{
58
    readConfig();
59
    addColumn("Playlists", width());
60 61
    header()->hide();
    setSorting(0);
62 63
    setFullWidth(true);
    setItemMargin(3);
Laurent Montel's avatar
Laurent Montel committed
64

65 66
    setAcceptDrops(true);
    setSelectionModeExt(Extended);
Laurent Montel's avatar
Laurent Montel committed
67

68
    m_contextMenu = new KPopupMenu(this);
69

70 71 72
    K3bPlaylistExporter *exporter = new K3bPlaylistExporter(this);
    m_k3bAction = exporter->action();
    
73 74
    action("file_new")->plug(m_contextMenu);
    action("renamePlaylist")->plug(m_contextMenu);
75
    action("editSearch")->plug(m_contextMenu);
76 77 78 79 80
    action("duplicatePlaylist")->plug(m_contextMenu);
    action("reloadPlaylist")->plug(m_contextMenu);
    action("deleteItemPlaylist")->plug(m_contextMenu);
    action("file_save")->plug(m_contextMenu);
    action("file_save_as")->plug(m_contextMenu);
81 82
    if(m_k3bAction)
	m_k3bAction->plug(m_contextMenu);
Laurent Montel's avatar
Laurent Montel committed
83

84 85
    m_contextMenu->insertSeparator();

86
    // add the view modes stuff
Laurent Montel's avatar
Laurent Montel committed
87

88 89
    m_viewModeAction = new KSelectAction(i18n("View Modes"), "view_choose",
					 KShortcut(), actions(), "viewModeMenu");
Laurent Montel's avatar
Laurent Montel committed
90

91 92 93 94 95 96
    m_viewModes.append(new ViewMode(this));
    m_viewModes.append(new CompactViewMode(this));
    m_viewModes.append(new TreeViewMode(this));

    QStringList modeNames;

97
    for(QValueListIterator<ViewMode *> it = m_viewModes.begin(); it != m_viewModes.end(); ++it)
98
	modeNames.append((*it)->name());
99 100

    m_viewModeAction->setItems(modeNames);
101 102 103 104 105 106

    QPopupMenu *p = m_viewModeAction->popupMenu();
    p->changeItem(0, SmallIconSet("view_detailed"), modeNames[0]);
    p->changeItem(1, SmallIconSet("view_text"), modeNames[1]);
    p->changeItem(2, SmallIconSet("view_tree"), modeNames[2]);

107
    m_viewModeAction->setCurrentItem(m_viewModeIndex);
108
    m_viewModes[m_viewModeIndex]->setShown(true);
Laurent Montel's avatar
Laurent Montel committed
109

110
    m_viewModeAction->plug(m_contextMenu);
111 112
    connect(m_viewModeAction, SIGNAL(activated(int)), this, SLOT(slotSetViewMode(int)));

113
    connect(this, SIGNAL(selectionChanged()),
114
	    this, SLOT(slotPlaylistChanged()));
Laurent Montel's avatar
Laurent Montel committed
115 116

    connect(this, SIGNAL(doubleClicked(QListViewItem *)),
117
	    this, SLOT(slotDoubleClicked(QListViewItem *)));
Laurent Montel's avatar
Laurent Montel committed
118

119 120
    connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)),
	    this, SLOT(slotShowContextMenu(QListViewItem *, const QPoint &, int)));
121 122 123 124

    CollectionList::initialize(this);
    Cache::loadPlaylists(this);
    raise(CollectionList::instance());
125 126 127 128
}

PlaylistBox::~PlaylistBox()
{
129
    Cache::savePlaylists(playlists());
130
    saveConfig();
131 132
}

133 134
void PlaylistBox::createSearchItem(SearchPlaylist *playlist, const QString &searchCategory)
{
135
    m_viewModes[m_viewModeIndex]->createSearchItem(this, playlist, searchCategory);
136 137
}

138 139 140 141 142
void PlaylistBox::raise(Playlist *playlist)
{
    if(!playlist)
	return;

143
    Item *i = m_playlistDict.find(playlist);
144 145 146

    clearSelection();
    setSelected(i, true);
147

148
    setSingleItem(i);
149 150 151
    ensureCurrentVisible();
}

152
PlaylistList PlaylistBox::playlists()
153
{
154
    PlaylistList l;
155

156 157 158
    CollectionList *collection = CollectionList::instance();

    Item *i = static_cast<Item *>(firstChild());
159
    for(; i; i = static_cast<Item *>(i->nextSibling()))
160
	if(i->playlist() && i->playlist() != collection)
161
	    l.append(i->playlist());
162 163 164 165 166

    return l;
}

void PlaylistBox::duplicate()
167
{
Scott Wheeler's avatar
Scott Wheeler committed
168
    duplicate(static_cast<Item *>(currentItem()));
169 170
}

171 172 173 174
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox public slots
////////////////////////////////////////////////////////////////////////////////

175 176
void PlaylistBox::paste()
{
Scott Wheeler's avatar
Scott Wheeler committed
177
    Item *i = static_cast<Item *>(currentItem());
178 179 180
    decode(kapp->clipboard()->data(), i);
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox protected methods
////////////////////////////////////////////////////////////////////////////////

Playlist *PlaylistBox::currentPlaylist() const
{
    if(m_dynamicPlaylist)
	return m_dynamicPlaylist;

    return currentItem() ? static_cast<Item *>(currentItem())->playlist() : 0;
}

void PlaylistBox::setupPlaylist(Playlist *playlist, const QString &iconName)
{
    PlaylistCollection::setupPlaylist(playlist, iconName);

    new Item(this, iconName, playlist->name(), playlist);
}


201 202 203 204
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox private methods
////////////////////////////////////////////////////////////////////////////////

205 206 207 208 209
void PlaylistBox::readConfig()
{
    KConfig *config = kapp->config();
    {
	KConfigGroupSaver saver(config, "PlaylistBox");
210
	m_viewModeIndex = config->readNumEntry("ViewMode", 0);
211 212 213 214 215 216 217 218 219 220 221 222 223
    }
}

void PlaylistBox::saveConfig()
{
    KConfig *config = kapp->config();
    {
	KConfigGroupSaver saver(config, "PlaylistBox");
	config->writeEntry("ViewMode", m_viewModeAction->currentItem());
	config->sync();
    }
}

224
void PlaylistBox::duplicate(Item *item)
225
{
Laurent Montel's avatar
Laurent Montel committed
226
    if(item && item->playlist()) {
227 228 229 230
	bool ok;

	// If this text is changed, please also change it in PlaylistSplitter::createPlaylist().

Laurent Montel's avatar
Laurent Montel committed
231 232
	QString name = KInputDialog::getText(i18n("New Playlist"),
					     i18n("Please enter a name for the new playlist:"),
233
					     uniquePlaylistName(item->text(0)), &ok);
Nadeem Hasan's avatar
Nadeem Hasan committed
234

235
	if(ok) {
236
	    Playlist *p = new Playlist(this, name);
237
	    p->createItems(item->playlist()->items());
238 239 240 241
	}
    }
}

242 243 244 245 246 247 248 249 250 251 252 253 254 255
void PlaylistBox::deleteItem(Playlist *playlist)
{
    Item *i = m_playlistDict.find(playlist);

    if(!i)
	return;

    ItemList l;
    l.append(i);

    deleteItems(l, false);
}

void PlaylistBox::deleteItems(const ItemList &items, bool confirm)
256
{
257
    if(items.isEmpty())
258 259
	return;

260 261 262 263 264 265 266
    QStringList files;

    for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
	if(*it && (*it)->playlist() && !(*it)->playlist()->fileName().isEmpty())
	    files.append((*it)->playlist()->fileName());
    }

267 268 269 270
    if(confirm) {
	if(!files.isEmpty()) {
	    int remove = KMessageBox::warningYesNoCancelList(
		this, i18n("Do you want to delete these files from the disk as well?"), files);
Laurent Montel's avatar
Laurent Montel committed
271

272 273 274 275 276 277 278 279 280 281 282 283
	    if(remove == KMessageBox::Yes) {
		QStringList couldNotDelete;
		for(QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) {
		    if(!QFile::remove(*it))
			couldNotDelete.append(*it);
		}

		// Would be nice if there were a KMessageBox::sorryList() to use with
		// couldNotDelete.

		if(!couldNotDelete.isEmpty())
		    KMessageBox::sorry(this, i18n("Could not delete all of the specified files."));
284
	    }
285 286 287 288 289 290
	    else if(remove == KMessageBox::Cancel)
		return;
	}
	else {
	    if(KMessageBox::warningYesNo(this, i18n("Are you sure you want to remove these items?")) == KMessageBox::No)
		return;
291
	}
292
    }
293

294 295
    QValueList< QPair<Item *, Playlist *> > removeQueue;

296
    for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
Laurent Montel's avatar
Laurent Montel committed
297
	if(*it != Item::collectionItem() &&
298 299 300
	   (*it)->playlist() &&
	   (!confirm || !(*it)->playlist()->readOnly()))
	{
301 302 303 304
	    m_names.remove((*it)->text(0));
	    m_playlistDict.remove((*it)->playlist());
	    removeQueue.append(qMakePair(*it, (*it)->playlist()));
	}
305
    }
306

307 308 309 310 311 312 313
    if(items.back()->nextSibling() && static_cast<Item *>(items.back()->nextSibling())->playlist())
	setSingleItem(items.back()->nextSibling());
    else {
	Item *i = static_cast<Item *>(items.front()->itemAbove());
	while(i && !i->playlist())
	    i = static_cast<Item *>(i->itemAbove());

314 315
	if(!i)
	    i = Item::collectionItem();
316 317 318

	setSingleItem(i);
    }
319 320 321 322 323 324

    QValueListConstIterator< QPair<Item *, Playlist *> > it = removeQueue.begin();
    for(; it != removeQueue.end(); ++it) {
	delete (*it).first;
	delete (*it).second;
    }
325 326
}

327
void PlaylistBox::decode(QMimeSource *s, Item *item)
328
{
329
    if(!s || (item && item->playlist() && item->playlist()->readOnly()))
330 331
	return;

332
    KURL::List urls;
Laurent Montel's avatar
Laurent Montel committed
333

334
    if(KURLDrag::decode(s, urls) && !urls.isEmpty()) {
335
	QStringList files;
Laurent Montel's avatar
Laurent Montel committed
336

337 338
	for(KURL::List::Iterator it = urls.begin(); it != urls.end(); it++)
	    files.append((*it).path());
339 340

	if(item && item->playlist())
341
	    item->playlist()->addFiles(files, importPlaylists());
342 343
	else
	    emit signalCreatePlaylist(files);
344 345 346
    }
}

347
void PlaylistBox::contentsDropEvent(QDropEvent *e)
348
{
349
    Item *i = static_cast<Item *>(itemAt(contentsToViewport(e->pos())));
350
    decode(e, i);
351 352 353 354 355 356

    if(m_dropItem) {
	Item *old = m_dropItem;
	m_dropItem = 0;
	old->repaint();
    }
357 358
}

359
void PlaylistBox::contentsDragMoveEvent(QDragMoveEvent *e)
360 361
{
    // If we can decode the input source, there is a non-null item at the "move"
Laurent Montel's avatar
Laurent Montel committed
362
    // position, the playlist for that Item is non-null, is not the
363 364 365
    // selected playlist and is not the CollectionList, then accept the event.
    //
    // Otherwise, do not accept the event.
Laurent Montel's avatar
Laurent Montel committed
366

367 368 369 370 371
    if(!KURLDrag::canDecode(e)) {
	e->accept(false);
	return;
    }

372 373 374
    Item *target = static_cast<Item *>(itemAt(contentsToViewport(e->pos())));

    if(target) {
375

376 377 378
	if(target->playlist() && target->playlist()->readOnly())
	    return;

379 380
	// This is a semi-dirty hack to check if the items are coming from within
	// JuK.  If they are not coming from a Playlist (or subclass) then the
Laurent Montel's avatar
Laurent Montel committed
381
	// dynamic_cast will fail and we can safely assume that the item is
382 383 384
	// coming from outside of JuK.

	if(dynamic_cast<Playlist *>(e->source())) {
385 386 387 388
	    if(target->playlist() &&
	       target->playlist() != CollectionList::instance() &&
	       !target->isSelected())
	    {
389
		e->accept(true);
390
	    }
391 392
	    else
		e->accept(false);
393 394 395
	}
	else // the dropped items are coming from outside of JuK
	    e->accept(true);
396 397 398 399 400 401 402 403 404 405 406 407 408 409

	if(m_dropItem != target) {
	    Item *old = m_dropItem;

	    if(e->isAccepted()) {
		m_dropItem = target;
		target->repaint();
	    }
	    else
		m_dropItem = 0;

	    if(old)
		old->repaint();
	}
410
    }
411 412 413 414 415 416 417
    else {

	// We're dragging over the whitespace.  We'll use this case to make it
	// possible to create new lists.

	e->accept(true);
    }
418 419
}

420 421 422 423 424 425 426 427 428 429
void PlaylistBox::contentsDragLeaveEvent(QDragLeaveEvent *e)
{
    if(m_dropItem) {
	Item *old = m_dropItem;
	m_dropItem = 0;
	old->repaint();
    }
    KListView::contentsDragLeaveEvent(e);
}

430 431
void PlaylistBox::contentsMousePressEvent(QMouseEvent *e)
{
432
    if(e->button() == LeftButton)
433
	m_doingMultiSelect = true;
434 435 436 437 438
    KListView::contentsMousePressEvent(e);
}

void PlaylistBox::contentsMouseReleaseEvent(QMouseEvent *e)
{
439
    if(e->button() == LeftButton) {
440
	m_doingMultiSelect = false;
441 442
	slotPlaylistChanged();
    }
443 444 445
    KListView::contentsMouseReleaseEvent(e);
}

446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
void PlaylistBox::keyPressEvent(QKeyEvent *e)
{
    if((e->key() == Key_Up || e->key() == Key_Down) && e->state() == ShiftButton)
	m_doingMultiSelect = true;
    KListView::keyPressEvent(e);
}

void PlaylistBox::keyReleaseEvent(QKeyEvent *e)
{
    if(m_doingMultiSelect && e->key() == Key_Shift) {
	m_doingMultiSelect = false;
	slotPlaylistChanged();
    }
    KListView::keyReleaseEvent(e);
}

462
PlaylistBox::ItemList PlaylistBox::selectedItems()
463
{
464
    ItemList l;
465

466 467 468
    for(QListViewItemIterator it(this); it.current(); ++it) {
	if(isSelected(*it))
	    l.append(static_cast<Item *>(*it));
469
    }
470

471 472 473
    return l;
}

474
void PlaylistBox::setSingleItem(QListViewItem *item)
475
{
476 477 478
    setSelectionModeExt(Single);
    KListView::setCurrentItem(item);
    setSelectionModeExt(Extended);
479 480
}

481 482 483 484
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox private slots
////////////////////////////////////////////////////////////////////////////////

485
void PlaylistBox::slotPlaylistChanged()
486
{
487 488
    // Don't update while the mouse is pressed down.

489
    if(m_doingMultiSelect)
490 491 492 493 494 495 496 497
	return;

    ItemList items = selectedItems();
    m_hasSelection = !items.isEmpty();

    if(!m_updatePlaylistStack)
	return;

498 499
    bool allowReload = false;

500
    PlaylistList playlists;
501
    for(ItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
502

503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
	Playlist *p = (*it)->playlist();
	if(p) {
	    if(p == CollectionList::instance() || !p->fileName().isNull())
		allowReload = true;
	    playlists.append(p);
	}
    }

    if(playlists.isEmpty() ||
       (playlists.count() == 1 &&
	(playlists.front() == CollectionList::instance() ||
	 playlists.front()->readOnly())))
    {
        action("file_save")->setEnabled(false);
        action("file_save_as")->setEnabled(false);
        action("renamePlaylist")->setEnabled(false);
        action("deleteItemPlaylist")->setEnabled(false);
    }
    else {
        action("file_save")->setEnabled(true);
        action("file_save_as")->setEnabled(true);
        action("renamePlaylist")->setEnabled(playlists.count() == 1);
        action("deleteItemPlaylist")->setEnabled(true);
526
    }
527 528
    action("reloadPlaylist")->setEnabled(allowReload);
    action("duplicatePlaylist")->setEnabled(!playlists.isEmpty());
529
    m_k3bAction->setEnabled(!playlists.isEmpty());
530

531 532 533 534 535
    bool searchList =
	playlists.count() == 1 && dynamic_cast<SearchPlaylist *>(playlists.front());

    action("editSearch")->setEnabled(searchList);

536 537
    if(playlists.count() == 1)
	playlistStack()->raiseWidget(playlists.front());
538 539
}

540
void PlaylistBox::slotDoubleClicked(QListViewItem *)
541
{
542 543
    action("stop")->activate();
    action("play")->activate();
544 545
}

546
void PlaylistBox::slotShowContextMenu(QListViewItem *, const QPoint &point, int)
547
{
548
    m_contextMenu->popup(point);
549 550
}

551
void PlaylistBox::slotSetViewMode(int index)
552
{
553 554 555 556 557
    if(index == m_viewModeIndex)
	return;

    viewMode()->setShown(false);
    m_viewModeIndex = index;
Laurent Montel's avatar
Laurent Montel committed
558
    viewMode()->setShown(true);
559 560
}

561
void PlaylistBox::setupItem(Item *item)
562
{
563 564
    m_playlistDict.insert(item->playlist(), item);
    viewMode()->queueRefresh();
565 566
}

567
////////////////////////////////////////////////////////////////////////////////
568
// PlaylistBox::Item protected methods
569 570
////////////////////////////////////////////////////////////////////////////////

571 572
PlaylistBox::Item *PlaylistBox::Item::m_collectionItem = 0;

573
PlaylistBox::Item::Item(PlaylistBox *listBox, const QString &icon, const QString &text, Playlist *l)
574
    : QObject(listBox), KListViewItem(listBox, text),
575
      m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false)
576
{
577 578
    init();
}
579

580
PlaylistBox::Item::Item(Item *parent, const QString &icon, const QString &text, Playlist *l)
581
    : QObject(parent->listView()), KListViewItem(parent, text),
582
    m_playlist(l), m_text(text), m_iconName(icon), m_sortedFirst(false)
583 584
{
    init();
585 586
}

587
PlaylistBox::Item::~Item()
588 589 590 591
{

}

592 593
int PlaylistBox::Item::compare(QListViewItem *i, int col, bool) const
{
594 595 596
    Item *otherItem = static_cast<Item *>(i);

    if(m_sortedFirst && !otherItem->m_sortedFirst)
597
	return -1;
598
    else if(otherItem->m_sortedFirst && !m_sortedFirst)
599 600 601 602 603
	return 1;

    return text(col).lower().localeAwareCompare(i->text(col).lower());
}

604 605
void PlaylistBox::Item::paintCell(QPainter *painter, const QColorGroup &colorGroup, int column, int width, int align)
{
606 607
    PlaylistBox *playlistBox = static_cast<PlaylistBox *>(listView());
    playlistBox->viewMode()->paintCell(this, painter, colorGroup, column, width, align);
608 609 610 611 612 613 614 615
}

void PlaylistBox::Item::setText(int column, const QString &text)
{
    m_text = text;
    KListViewItem::setText(column, text);
}

616 617 618 619 620 621 622 623 624 625 626
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox::Item protected slots
////////////////////////////////////////////////////////////////////////////////

void PlaylistBox::Item::slotSetName(const QString &name)
{
    if(listView()) {
	listView()->m_names.remove(text(0));
	listView()->m_names.append(name);

	setText(0, name);
627 628 629 630
	setSelected(true);

	listView()->sort();
	listView()->ensureCurrentVisible();
631 632 633
    }
}

634 635 636 637 638 639
////////////////////////////////////////////////////////////////////////////////
// PlaylistBox::Item private methods
////////////////////////////////////////////////////////////////////////////////

void PlaylistBox::Item::init()
{
640 641 642 643 644
    PlaylistBox *list = static_cast<PlaylistBox *>(listView());

    list->setupItem(this);

    int iconSize = list->viewModeIndex() == 0 ? 32 : 16;
645
    setPixmap(0, SmallIcon(m_iconName, iconSize));
646
    list->addName(m_text);
647

648 649 650
    if(m_playlist)
	connect(m_playlist, SIGNAL(signalNameChanged(const QString &)),
		this, SLOT(slotSetName(const QString &)));
651

652 653
    if(m_playlist == CollectionList::instance()) {
	m_sortedFirst = true;
654
	m_collectionItem = this;
655
    }
656 657
}

658
#include "playlistbox.moc"