collectionlist.cpp 14.9 KB
Newer Older
1
/***************************************************************************
2
    begin                : Fri Sep 13 2002
3
    copyright            : (C) 2002 - 2004 by Scott Wheeler
4
    email                : wheeler@kde.org
5
 ***************************************************************************/
6 7 8 9 10 11 12 13 14 15

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

16 17
#include "collectionlist.h"

18
#include <klocale.h>
19
#include <kmessagebox.h>
20
#include <kdebug.h>
Laurent Montel's avatar
Laurent Montel committed
21
#include <kmenu.h>
22
#include <kconfig.h>
23
#include <kconfiggroup.h>
24
#include <kactioncollection.h>
Laurent Montel's avatar
Laurent Montel committed
25
#include <ktoolbarpopupaction.h>
26
#include <kdirwatch.h>
27

28
#include <QList>
29 30
#include <QDragMoveEvent>
#include <QDropEvent>
31 32
#include <QApplication>
#include <QClipboard>
33

34
#include "playlistcollection.h"
35
#include "splashscreen.h"
36
#include "stringshare.h"
37
#include "cache.h"
38
#include "actioncollection.h"
39 40
#include "tag.h"
#include "viewmode.h"
41
#include "coverinfo.h"
42
#include "covermanager.h"
43

44
using ActionCollection::action;
45

46 47 48 49
////////////////////////////////////////////////////////////////////////////////
// static methods
////////////////////////////////////////////////////////////////////////////////

50
CollectionList *CollectionList::m_list = 0;
51 52 53

CollectionList *CollectionList::instance()
{
54
    return m_list;
55 56
}

57
void CollectionList::initialize(PlaylistCollection *collection)
58
{
59
    if(m_list)
60
        return;
61 62 63 64 65 66 67

    // We have to delay initilaization here because dynamic_cast or comparing to
    // the collection instance won't work in the PlaylistBox::Item initialization
    // won't work until the CollectionList is fully constructed.

    m_list = new CollectionList(collection);
    m_list->setName(i18n("Collection List"));
68

69 70
    FileHandleHash::Iterator end = Cache::instance()->end();
    for(FileHandleHash::Iterator it = Cache::instance()->begin(); it != end; ++it)
71
        new CollectionListItem(*it);
72

73
    SplashScreen::update();
74 75 76 77

    // The CollectionList is created with sorting disabled for speed.  Re-enable
    // it here, and perform the sort.
    KConfigGroup config(KGlobal::config(), "Playlists");
78

79 80
    Qt::SortOrder order = Qt::DescendingOrder;
    if(config.readEntry("CollectionListSortAscending", true))
81
        order = Qt::AscendingOrder;
82 83

    m_list->setSortOrder(order);
84
    m_list->setSortColumn(config.readEntry("CollectionListSortColumn", 1));
85

86 87
    m_list->sort();

88
    collection->setupPlaylist(m_list, "folder_sound");
89 90 91 92 93 94
}

////////////////////////////////////////////////////////////////////////////////
// public methods
////////////////////////////////////////////////////////////////////////////////

Laurent Montel's avatar
Laurent Montel committed
95
PlaylistItem *CollectionList::createItem(const FileHandle &file, Q3ListViewItem *, bool)
96
{
97 98 99
    // It's probably possible to optimize the line below away, but, well, right
    // now it's more important to not load duplicate items.

100
    if(m_itemsDict.contains(file.absFilePath()))
101
        return 0;
102

103
    PlaylistItem *item = new CollectionListItem(file);
104

105
    if(!item->isValid()) {
106 107 108 109
        kError() << "CollectionList::createItem() -- A valid tag was not created for \""
                  << file.absFilePath() << "\"" << endl;
        delete item;
        return 0;
110
    }
111 112 113

    setupItem(item);

114
    return item;
115 116
}

117 118 119
void CollectionList::clearItems(const PlaylistItemList &items)
{
    for(PlaylistItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
120 121
        Cache::instance()->remove((*it)->file());
        clearItem(*it, false);
122 123
    }

124
    dataChanged();
125 126
}

127 128
void CollectionList::setupTreeViewEntries(ViewMode *viewMode) const
{
129
    TreeViewMode *treeViewMode = dynamic_cast<TreeViewMode *>(viewMode);
130
    if(!treeViewMode) {
131 132
        kWarning(65432) << "Can't setup entries on a non-tree-view mode!\n";
        return;
133 134
    }

135
    QList<int> columnList;
136 137 138 139
    columnList << PlaylistItem::ArtistColumn;
    columnList << PlaylistItem::GenreColumn;
    columnList << PlaylistItem::AlbumColumn;

140 141
    foreach(int column, columnList)
        treeViewMode->addItems(m_columnTags[column]->keys(), column);
142 143
}

144
void CollectionList::slotNewItems(const KFileItemList &items)
145 146 147
{
    QStringList files;

148
    for(KFileItemList::ConstIterator it = items.begin(); it != items.end(); ++it)
149
        files.append((*it)->url().path());
150

151
    addFiles(files);
152 153 154 155 156
    update();
}

void CollectionList::slotRefreshItems(const KFileItemList &items)
{
157
    for(KFileItemList::ConstIterator it = items.begin(); it != items.end(); ++it) {
158
        CollectionListItem *item = lookup((*it)->url().path());
159

160 161
        if(item) {
            item->refreshFromDisk();
162

163
            // If the item is no longer on disk, remove it from the collection.
164

165 166 167 168 169
            if(item->file().fileInfo().exists())
                item->repaint();
            else
                delete item;
        }
170 171 172 173 174 175 176 177 178
    }

    update();
}

void CollectionList::slotDeleteItem(KFileItem *item)
{
    CollectionListItem *listItem = lookup(item->url().path());
    if(listItem)
179
        clearItem(listItem);
180 181
}

182 183 184 185
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

186 187 188 189 190
void CollectionList::paste()
{
    decode(QApplication::clipboard()->mimeData());
}

191 192
void CollectionList::clear()
{
193 194 195 196 197
    int result = KMessageBox::warningContinueCancel(this,
        i18n("Removing an item from the collection will also remove it from "
             "all of your playlists. Are you sure you want to continue?\n\n"
             "Note, however, that if the directory that these files are in is in "
             "your \"scan on startup\" list, they will be readded on startup."));
198 199

    if(result == KMessageBox::Continue) {
200 201
        Playlist::clear();
        emit signalCollectionChanged();
202
    }
203 204
}

205 206
void CollectionList::slotCheckCache()
{
207 208
    PlaylistItemList invalidItems;

209 210 211
    foreach(CollectionListItem *item, m_itemsDict) {
        if(!item->checkCurrent())
            invalidItems.append(item);
212
        processEvents();
213
    }
214 215

    clearItems(invalidItems);
216 217
}

218 219 220 221 222 223 224
void CollectionList::slotRemoveItem(const QString &file)
{
    clearItem(m_itemsDict[file]);
}

void CollectionList::slotRefreshItem(const QString &file)
{
225 226
    if(m_itemsDict[file])
        m_itemsDict[file]->refresh();
227 228
}

229 230 231 232
////////////////////////////////////////////////////////////////////////////////
// protected methods
////////////////////////////////////////////////////////////////////////////////

233 234
CollectionList::CollectionList(PlaylistCollection *collection) :
    Playlist(collection, true),
235
    m_columnTags(15, 0)
236
{
Laurent Montel's avatar
Laurent Montel committed
237 238
    QAction *spaction = ActionCollection::actions()->addAction("showPlaying");
    spaction->setText(i18n("Show Playing"));
Stephan Kulow's avatar
Stephan Kulow committed
239
    connect(spaction, SIGNAL(triggered(bool) ), SLOT(slotShowPlaying()));
240

Laurent Montel's avatar
Laurent Montel committed
241
    connect(action<KToolBarPopupAction>("back")->menu(), SIGNAL(aboutToShow()),
242
            this, SLOT(slotPopulateBackMenu()));
243 244
    connect(action<KToolBarPopupAction>("back")->menu(), SIGNAL(activated(QAction *)),
            this, SLOT(slotPlayFromBackMenu(QAction *)));
245 246
    connect(action<KToolBarPopupAction>("back")->menu(), SIGNAL(triggered(QAction *)),
            this, SLOT(slotPlayFromBackMenu(QAction *)));
247
    setSorting(-1); // Temporarily disable sorting to add items faster.
248

249 250 251
    m_columnTags[PlaylistItem::ArtistColumn] = new TagCountDict;
    m_columnTags[PlaylistItem::AlbumColumn] = new TagCountDict;
    m_columnTags[PlaylistItem::GenreColumn] = new TagCountDict;
252 253
}

254
CollectionList::~CollectionList()
255
{
256 257
    KConfigGroup config(KGlobal::config(), "Playlists");
    config.writeEntry("CollectionListSortColumn", sortColumn());
258
    config.writeEntry("CollectionListSortAscending", sortOrder() == Qt::AscendingOrder);
259

260 261 262 263 264
    // The CollectionListItems will try to remove themselves from the
    // m_columnTags member, so we must make sure they're gone before we
    // are.

    clearItems(items());
265 266 267

    qDeleteAll(m_columnTags);
    m_columnTags.clear();
268
}
269

270 271 272
void CollectionList::contentsDropEvent(QDropEvent *e)
{
    if(e->source() == this)
273 274 275
        return; // Don't rearrange in the CollectionList.
    else
        Playlist::contentsDropEvent(e);
276 277 278 279
}

void CollectionList::contentsDragMoveEvent(QDragMoveEvent *e)
{
Michael Pyne's avatar
Michael Pyne committed
280
    if(canDecode(e) && e->source() != this)
281
        e->setAccepted(true);
282
    else
283
        e->setAccepted(false);
284 285
}

286
QString CollectionList::addStringToDict(const QString &value, int column)
287
{
Laurent Montel's avatar
Laurent Montel committed
288
    if(column > m_columnTags.count() || value.trimmed().isEmpty())
289
        return QString();
290

291 292
    if(m_columnTags[column]->contains(value))
        ++((*m_columnTags[column])[value]);
293
    else {
294
        m_columnTags[column]->insert(value, 1);
295
        emit signalNewTag(value, column);
296 297 298 299 300 301
    }

    return value;
}

QStringList CollectionList::uniqueSet(UniqueSetType t) const
302
{
303 304 305 306 307 308 309
    int column;

    switch(t)
    {
    case Artists:
        column = PlaylistItem::ArtistColumn;
    break;
310

311 312 313
    case Albums:
        column = PlaylistItem::AlbumColumn;
    break;
314

315 316 317 318 319
    case Genres:
        column = PlaylistItem::GenreColumn;
    break;

    default:
320
        return QStringList();
321 322
    }

323 324
    return m_columnTags[column]->keys();
}
325

326 327 328
CollectionListItem *CollectionList::lookup(const QString &file) const
{
    return m_itemsDict.value(file, 0);
329 330
}

331
void CollectionList::removeStringFromDict(const QString &value, int column)
332
{
333
    if(column > m_columnTags.count() || value.trimmed().isEmpty())
334
        return;
335

336 337 338 339 340
    if(m_columnTags[column]->contains(value) &&
       --((*m_columnTags[column])[value])) // If the decrement goes to 0...
    {
        emit signalRemovedTag(value, column);
        m_columnTags[column]->remove(value);
341
    }
342 343
}

344 345 346 347 348 349 350 351 352 353
void CollectionList::addWatched(const QString &file)
{
    m_dirWatch->addFile(file);
}

void CollectionList::removeWatched(const QString &file)
{
    m_dirWatch->removeFile(file);
}

354
////////////////////////////////////////////////////////////////////////////////
355
// CollectionListItem public methods
356 357
////////////////////////////////////////////////////////////////////////////////

358
void CollectionListItem::refresh()
359
{
360 361
    int offset = static_cast<Playlist *>(listView())->columnOffset();
    int columns = lastColumn() + offset + 1;
362

363 364 365 366
    data()->local8Bit.resize(columns);
    data()->cachedWidths.resize(columns);

    for(int i = offset; i < columns; i++) {
367 368 369
        int id = i - offset;
        if(id != TrackNumberColumn && id != LengthColumn) {
            // All columns other than track num and length need local-encoded data for sorting
370

371
            QByteArray toLower = text(i).toLower().toLocal8Bit();
372

373
            // For some columns, we may be able to share some strings
374

375 376 377 378
            if((id == ArtistColumn) || (id == AlbumColumn) ||
               (id == GenreColumn)  || (id == YearColumn)  ||
               (id == CommentColumn))
            {
Dirk Mueller's avatar
Dirk Mueller committed
379
                toLower = StringShare::tryShare(toLower);
380

Dirk Mueller's avatar
Dirk Mueller committed
381
                if(id != YearColumn && id != CommentColumn && data()->local8Bit[id] != toLower) {
382 383 384 385
                    CollectionList::instance()->removeStringFromDict(data()->local8Bit[id], id);
                    CollectionList::instance()->addStringToDict(text(i), id);
                }
            }
386

Dirk Mueller's avatar
Dirk Mueller committed
387
            data()->local8Bit[id] = toLower;
388
        }
389

390 391
        int newWidth = width(listView()->fontMetrics(), listView(), i);
        data()->cachedWidths[i] = newWidth;
392

393 394
        if(newWidth != data()->cachedWidths[i])
            playlist()->slotWeightDirty(i);
395 396
    }

397 398
    file().coverInfo()->setCover();

399
    if(listView()->isVisible())
400
        repaint();
401 402

    for(PlaylistItemList::Iterator it = m_children.begin(); it != m_children.end(); ++it) {
403 404 405 406
        (*it)->playlist()->update();
        (*it)->playlist()->dataChanged();
        if((*it)->listView()->isVisible())
            (*it)->repaint();
407
    }
408 409 410

    CollectionList::instance()->dataChanged();
    emit CollectionList::instance()->signalCollectionChanged();
411
}
412

413
PlaylistItem *CollectionListItem::itemForPlaylist(const Playlist *playlist)
414
{
415
    if(playlist == CollectionList::instance())
416
        return this;
417

418 419
    PlaylistItemList::ConstIterator it;
    for(it = m_children.begin(); it != m_children.end(); ++it)
420 421
        if((*it)->playlist() == playlist)
            return *it;
422 423 424
    return 0;
}

425 426 427 428 429
void CollectionListItem::updateCollectionDict(const QString &oldPath, const QString &newPath)
{
    CollectionList *collection = CollectionList::instance();

    if(!collection)
430
        return;
431 432 433 434 435

    collection->removeFromDict(oldPath);
    collection->addToDict(newPath, this);
}

436 437
void CollectionListItem::repaint() const
{
Laurent Montel's avatar
Laurent Montel committed
438
    Q3ListViewItem::repaint();
439
    for(PlaylistItemList::ConstIterator it = m_children.begin(); it != m_children.end(); ++it)
440
        (*it)->repaint();
441 442
}

443
////////////////////////////////////////////////////////////////////////////////
444
// CollectionListItem protected methods
445 446
////////////////////////////////////////////////////////////////////////////////

447
CollectionListItem::CollectionListItem(const FileHandle &file) :
448
    PlaylistItem(CollectionList::instance()),
449
    m_shuttingDown(false)
450 451 452
{
    CollectionList *l = CollectionList::instance();
    if(l) {
453
        l->addToDict(file.absFilePath(), this);
454

455
        data()->fileHandle = file;
456

457 458 459 460 461 462 463
        if(file.tag()) {
            refresh();
            l->dataChanged();
            // l->addWatched(m_path);
        }
        else
            kError() << "CollectionListItem::CollectionListItem() -- Tag() could not be created." << endl;
464 465
    }
    else
466 467
        kError(65432) << "CollectionListItems should not be created before "
                       << "CollectionList::initialize() has been called." << endl;
468 469

    SplashScreen::increment();
470 471 472 473
}

CollectionListItem::~CollectionListItem()
{
474 475 476
    m_shuttingDown = true;

    for(PlaylistItemList::ConstIterator it = m_children.begin();
477 478
        it != m_children.end();
        ++it)
479
    {
480
        delete *it;
481 482
    }

483
    CollectionList *l = CollectionList::instance();
484
    if(l) {
485 486 487 488
        l->removeFromDict(file().absFilePath());
        l->removeStringFromDict(file().tag()->album(), AlbumColumn);
        l->removeStringFromDict(file().tag()->artist(), ArtistColumn);
        l->removeStringFromDict(file().tag()->genre(), GenreColumn);
489
    }
490 491 492 493
}

void CollectionListItem::addChildItem(PlaylistItem *child)
{
494 495 496 497 498 499
    m_children.append(child);
}

void CollectionListItem::removeChildItem(PlaylistItem *child)
{
    if(!m_shuttingDown)
500
        m_children.removeAll(child);
501 502
}

503
bool CollectionListItem::checkCurrent()
504
{
505
    if(!file().fileInfo().exists() || !file().fileInfo().isFile())
506
        return false;
507

508
    if(!file().current()) {
509 510
        file().refresh();
        refresh();
511
    }
512 513

    return true;
514 515
}

516
#include "collectionlist.moc"
517 518

// vim: set et sw=4 tw=0 sta: