playlistitem.cpp 13.1 KB
Newer Older
1 2
/***************************************************************************
    begin                : Sun Feb 17 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 <config.h>

18
#include <kdebug.h>
19
#include <kaction.h>
20
#include <kiconloader.h>
21 22

#include "playlistitem.h"
23
#include "collectionlist.h"
24
#include "musicbrainzquery.h"
25
#include "tag.h"
26
#include "actioncollection.h"
27
#include "ktrm.h"
28
#include "coverinfo.h"
29
#include "tagtransactionmanager.h"
Laurent Montel's avatar
Laurent Montel committed
30 31 32
//Added by qt3to4:
#include <QPixmap>
#include <Q3ValueList>
33

34 35
PlaylistItemList PlaylistItem::m_playingItems; // static

36 37
static void startMusicBrainzQuery(const FileHandle &file)
{
38
#ifdef HAVE_TUNEPIMP
Scott Wheeler's avatar
Scott Wheeler committed
39
    // This deletes itself when finished.
40
    new MusicBrainzLookup(file);
41 42 43 44
#else
    Q_UNUSED(file)
#endif
}
45 46

////////////////////////////////////////////////////////////////////////////////
47
// PlaylistItem public methods
48 49
////////////////////////////////////////////////////////////////////////////////

50
PlaylistItem::~PlaylistItem()
51
{
52 53 54 55 56
    // Although this isn't the most efficient way to accomplish the task of
    // stopping playback when deleting the item being played, it has the
    // stark advantage of working reliably.  I'll tell anyone who tries to
    // optimize this, the timing issues can be *hard*. -- mpyne

57
    m_collectionItem->removeChildItem(this);
58 59

    if(m_playingItems.find(this) != m_playingItems.end()) {
60 61 62
        m_playingItems.remove(this);
        if(m_playingItems.isEmpty())
            playlist()->setPlaying(0);
63
    }
64 65

    if(m_watched)
66
        Pointer::clear(this);
67 68
}

69
void PlaylistItem::setFile(const FileHandle &file)
70
{
71
    m_collectionItem->updateCollectionDict(d->fileHandle.absFilePath(), file.absFilePath());
72
    d->fileHandle = file;
73
    refresh();
74 75
}

76 77 78 79 80 81 82 83
void PlaylistItem::setFile(const QString &file)
{
    QString oldPath = d->fileHandle.absFilePath();
    d->fileHandle.setFile(file);
    m_collectionItem->updateCollectionDict(oldPath, d->fileHandle.absFilePath());
    refresh();
}

84
FileHandle PlaylistItem::file() const
85
{
86
    return d->fileHandle;
87 88
}

89
const QPixmap *PlaylistItem::pixmap(int column) const
90
{
91 92 93
    static QPixmap image(SmallIcon("image"));
    static QPixmap playing(UserIcon("playing"));

94 95
    int offset = playlist()->columnOffset();

96 97 98
    if((column - offset) == CoverColumn && d->fileHandle.coverInfo()->hasCover())
        return &image;

99 100
    if(column == playlist()->leftColumn() &&
       m_playingItems.contains(const_cast<PlaylistItem *>(this)))
101
        return &playing;
102

Laurent Montel's avatar
Laurent Montel committed
103
    return K3ListViewItem::pixmap(column);
104 105
}

106 107
QString PlaylistItem::text(int column) const
{
108
    if(!d->fileHandle.tag())
109
        return QString::null;
110

111
    int offset = playlist()->columnOffset();
112 113

    switch(column - offset) {
114
    case TrackColumn:
115
        return d->fileHandle.tag()->title();
116
    case ArtistColumn:
117
        return d->fileHandle.tag()->artist();
118
    case AlbumColumn:
119
        return d->fileHandle.tag()->album();
120
    case CoverColumn:
121
        return QString::null;
122
    case TrackNumberColumn:
123 124 125
        return d->fileHandle.tag()->track() > 0
            ? QString::number(d->fileHandle.tag()->track())
            : QString::null;
126
    case GenreColumn:
127
        return d->fileHandle.tag()->genre();
128
    case YearColumn:
129 130 131
        return d->fileHandle.tag()->year() > 0
            ? QString::number(d->fileHandle.tag()->year())
            : QString::null;
132
    case LengthColumn:
133
        return d->fileHandle.tag()->lengthString();
134
    case BitrateColumn:
135
        return QString::number(d->fileHandle.tag()->bitrate());
136
    case CommentColumn:
137
        return d->fileHandle.tag()->comment();
138
    case FileNameColumn:
139
        return d->fileHandle.fileInfo().fileName();
140
    case FullPathColumn:
141
        return d->fileHandle.fileInfo().absFilePath();
142
    default:
Laurent Montel's avatar
Laurent Montel committed
143
        return K3ListViewItem::text(column);
144 145 146
    }
}

147 148
void PlaylistItem::setText(int column, const QString &text)
{
149
    int offset = playlist()->columnOffset();
150
    if(column - offset >= 0 && column + offset <= lastColumn()) {
Laurent Montel's avatar
Laurent Montel committed
151
        K3ListViewItem::setText(column, QString::null);
152
        return;
153 154
    }

Laurent Montel's avatar
Laurent Montel committed
155
    K3ListViewItem::setText(column, text);
156
    playlist()->slotWeightDirty(column);
157
}
158

159
void PlaylistItem::setPlaying(bool playing, bool master)
160
{
161
    m_playingItems.remove(this);
162

163
    if(playing) {
164 165 166 167
        if(master)
            m_playingItems.prepend(this);
        else
            m_playingItems.append(this);
168
    }
169 170
    else {

171 172
        // This is a tricky little recursion, but it
        // in fact does clear the list.
173

174 175
        if(!m_playingItems.isEmpty())
            m_playingItems.front()->setPlaying(false);
176
    }
177 178

    listView()->triggerUpdate();
179 180
}

181 182
void PlaylistItem::setSelected(bool selected)
{
183
    playlist()->markItemSelected(this, selected);
Laurent Montel's avatar
Laurent Montel committed
184
    K3ListViewItem::setSelected(selected);
185 186
}

187
void PlaylistItem::guessTagInfo(TagGuesser::Type type)
188
{
189 190 191
    switch(type) {
    case TagGuesser::FileName:
    {
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
        TagGuesser guesser(d->fileHandle.absFilePath());
        Tag *tag = TagTransactionManager::duplicateTag(d->fileHandle.tag());

        if(!guesser.title().isNull())
            tag->setTitle(guesser.title());
        if(!guesser.artist().isNull())
            tag->setArtist(guesser.artist());
        if(!guesser.album().isNull())
            tag->setAlbum(guesser.album());
        if(!guesser.track().isNull())
            tag->setTrack(guesser.track().toInt());
        if(!guesser.comment().isNull())
            tag->setComment(guesser.comment());

        TagTransactionManager::instance()->changeTagOnItem(this, tag);
        break;
208 209
    }
    case TagGuesser::MusicBrainz:
210 211
        startMusicBrainzQuery(d->fileHandle);
        break;
212
    }
213 214
}

215 216 217 218 219
Playlist *PlaylistItem::playlist() const
{
    return static_cast<Playlist *>(listView());
}

Laurent Montel's avatar
Laurent Montel committed
220
Q3ValueVector<int> PlaylistItem::cachedWidths() const
221
{
222
    return d->cachedWidths;
223 224
}

225
void PlaylistItem::refresh()
226
{
227
    m_collectionItem->refresh();
228 229
}

230
void PlaylistItem::refreshFromDisk()
231
{
232
    d->fileHandle.refresh();
233
    refresh();
234 235
}

236
void PlaylistItem::clear()
237
{
238
    playlist()->clearItem(this);
239 240
}

241
////////////////////////////////////////////////////////////////////////////////
242
// PlaylistItem protected methods
243 244
////////////////////////////////////////////////////////////////////////////////

245
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent) :
Laurent Montel's avatar
Laurent Montel committed
246
    K3ListViewItem(parent),
247 248
    d(0),
    m_watched(0)
249
{
250
    setup(item);
251 252
}

Laurent Montel's avatar
Laurent Montel committed
253
PlaylistItem::PlaylistItem(CollectionListItem *item, Playlist *parent, Q3ListViewItem *after) :
Laurent Montel's avatar
Laurent Montel committed
254
    K3ListViewItem(parent, after),
255 256
    d(0),
    m_watched(0)
257
{
258
    setup(item);
259 260
}

261 262

// This constructor should only be used by the CollectionList subclass.
263

264
PlaylistItem::PlaylistItem(CollectionList *parent) :
Laurent Montel's avatar
Laurent Montel committed
265
    K3ListViewItem(parent),
266
    m_watched(0)
267
{
268 269
    d = new Data;
    m_collectionItem = static_cast<CollectionListItem *>(this);
270
    setDragEnabled(true);
271 272
}

273 274
void PlaylistItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int align)
{
275
    if(!m_playingItems.contains(this))
Laurent Montel's avatar
Laurent Montel committed
276
        return K3ListViewItem::paintCell(p, cg, column, width, align);
277

Stephan Kulow's avatar
Stephan Kulow committed
278
    QPalette colorGroup = cg;
279

Stephan Kulow's avatar
Stephan Kulow committed
280 281
    QColor base = colorGroup.color( QPalette::Base );
    QColor selection = colorGroup.color( QPalette::Highlight );
282 283 284 285 286 287 288

    int r = (base.red() + selection.red()) / 2;
    int b = (base.blue() + selection.blue()) / 2;
    int g = (base.green() + selection.green()) / 2;

    QColor c(r, g, b);

289
    colorGroup.setColor(QPalette::Base, c);
Laurent Montel's avatar
Laurent Montel committed
290
    Q3ListViewItem::paintCell(p, colorGroup, column, width, align);
291 292
}

Laurent Montel's avatar
Laurent Montel committed
293
int PlaylistItem::compare(Q3ListViewItem *item, int column, bool ascending) const
294 295 296
{
    // reimplemented from QListViewItem

297
    int offset = playlist()->columnOffset();
298

299
    if(!item)
300
        return 0;
301

302
    PlaylistItem *playlistItem = static_cast<PlaylistItem *>(item);
303 304 305 306 307 308

    // The following statments first check to see if you can sort based on the
    // specified column.  If the values for the two PlaylistItems are the same
    // in that column it then trys to sort based on columns 1, 2, 3 and 0,
    // (artist, album, track number, track name) in that order.

309 310 311
    int c = compare(this, playlistItem, column, ascending);

    if(c != 0)
312
        return c;
313
    else {
314 315 316 317 318 319 320 321 322 323 324 325 326
        // Loop through the columns doing comparisons until something is differnt.
        // If all else is the same, compare the track name.

        int last = playlist()->isColumnVisible(AlbumColumn + offset) ? TrackNumberColumn : ArtistColumn;

        for(int i = ArtistColumn; i <= last; i++) {
            if(playlist()->isColumnVisible(i + offset)) {
                c = compare(this, playlistItem, i, ascending);
                if(c != 0)
                    return c;
            }
        }
        return compare(this, playlistItem, TrackColumn + offset, ascending);
327 328 329
    }
}

330
int PlaylistItem::compare(const PlaylistItem *firstItem, const PlaylistItem *secondItem, int column, bool) const
331
{
332
    int offset = playlist()->columnOffset();
333

334
    if(column < 0 || column > lastColumn() + offset)
335
        return 0;
336

337
    if(column < offset) {
338 339 340
        QString first = firstItem->text(column).lower();
        QString second = secondItem->text(column).lower();
        return first.localeAwareCompare(second);
341 342
    }

343 344
    switch(column - offset) {
    case TrackNumberColumn:
345
        if(firstItem->d->fileHandle.tag()->track() > secondItem->d->fileHandle.tag()->track())
346
            return 1;
347
        else if(firstItem->d->fileHandle.tag()->track() < secondItem->d->fileHandle.tag()->track())
348
            return -1;
349
        else
350
            return 0;
351
        break;
352
    case LengthColumn:
353
        if(firstItem->d->fileHandle.tag()->seconds() > secondItem->d->fileHandle.tag()->seconds())
354
            return 1;
355
        else if(firstItem->d->fileHandle.tag()->seconds() < secondItem->d->fileHandle.tag()->seconds())
356
            return -1;
357
        else
358
            return 0;
359
        break;
360 361 362 363 364 365 366
    case BitrateColumn:
        if(firstItem->d->fileHandle.tag()->bitrate() > secondItem->d->fileHandle.tag()->bitrate())
            return 1;
        else if(firstItem->d->fileHandle.tag()->bitrate() < secondItem->d->fileHandle.tag()->bitrate())
            return -1;
        else
            return 0;
367
        break;
368 369 370 371 372 373 374 375
    case CoverColumn:
        if(firstItem->d->fileHandle.coverInfo()->hasCover() == secondItem->d->fileHandle.coverInfo()->hasCover())
            return 0;
        else if (firstItem->d->fileHandle.coverInfo()->hasCover())
            return -1;
        else
            return 1;
        break;
376
    default:
377 378
        return strcoll(firstItem->d->local8Bit[column - offset],
                       secondItem->d->local8Bit[column - offset]);
379
    }
380
}
381

382
bool PlaylistItem::isValid() const
383
{
384
    return bool(d->fileHandle.tag());
385 386
}

387 388 389 390
////////////////////////////////////////////////////////////////////////////////
// PlaylistItem private methods
////////////////////////////////////////////////////////////////////////////////

391
void PlaylistItem::setup(CollectionListItem *item)
392
{
393 394
    m_collectionItem = item;

395 396
    d = item->d;
    item->addChildItem(this);
397 398
    setDragEnabled(true);
}
399 400 401 402 403

////////////////////////////////////////////////////////////////////////////////
// PlaylistItem::Pointer implementation
////////////////////////////////////////////////////////////////////////////////

Laurent Montel's avatar
Laurent Montel committed
404
QMap<PlaylistItem *, Q3ValueList<PlaylistItem::Pointer *> > PlaylistItem::Pointer::m_map; // static
405 406 407 408 409

PlaylistItem::Pointer::Pointer(PlaylistItem *item) :
    m_item(item)
{
    if(!m_item)
410 411
        return;

412 413 414 415 416 417 418 419 420 421 422 423 424
    m_item->m_watched = true;
    m_map[m_item].append(this);
}

PlaylistItem::Pointer::Pointer(const Pointer &p) :
    m_item(p.m_item)
{
    m_map[m_item].append(this);
}

PlaylistItem::Pointer::~Pointer()
{
    if(!m_item)
425
        return;
426 427 428

    m_map[m_item].remove(this);
    if(m_map[m_item].isEmpty()) {
429 430
        m_map.remove(m_item);
        m_item->m_watched = false;
431 432 433 434 435 436
    }
}

PlaylistItem::Pointer &PlaylistItem::Pointer::operator=(PlaylistItem *item)
{
    if(item == m_item)
437
        return *this;
438 439

    if(m_item) {
440 441 442 443 444
        m_map[m_item].remove(this);
        if(m_map[m_item].isEmpty()) {
            m_map.remove(m_item);
            m_item->m_watched = false;
        }
445 446 447
    }

    if(item) {
448 449
        m_map[item].append(this);
        item->m_watched = true;
450 451 452 453 454 455 456 457 458 459
    }

    m_item = item;

    return *this;
}

void PlaylistItem::Pointer::clear(PlaylistItem *item) // static
{
    if(!item)
460
        return;
461

Laurent Montel's avatar
Laurent Montel committed
462 463
    Q3ValueList<Pointer *> l = m_map[item];
    for(Q3ValueList<Pointer *>::Iterator it = l.begin(); it != l.end(); ++it)
464
        (*it)->m_item = 0;
465 466 467
    m_map.remove(item);
    item->m_watched = false;
}
468 469

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