~ari-tczew/ubuntu/natty/clementine/lp-747113

« back to all changes in this revision

Viewing changes to src/playlist/playlist.cpp

  • Committer: Artur Rona
  • Date: 2011-04-04 20:05:33 UTC
  • Revision ID: ari-tczew@ubuntu.com-20110404200533-6aclzasj5pp8t1hq
* New upstream release. (LP: #747113)
* Drop all patches, have been applied upstream.
* Update debian/copyright.
* Refresh description in debian/control in order to avoid lintian error.
* Bump debhelper to 8.

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
#include "playlist.h"
19
19
#include "playlistbackend.h"
20
20
#include "playlistfilter.h"
 
21
#include "playlistitemmimedata.h"
21
22
#include "playlistundocommands.h"
22
23
#include "queue.h"
23
24
#include "songloaderinserter.h"
40
41
#include "smartplaylists/generatorinserter.h"
41
42
#include "smartplaylists/generatormimedata.h"
42
43
 
43
 
#include <QtDebug>
44
44
#include <QApplication>
45
 
#include <QMimeData>
46
45
#include <QBuffer>
 
46
#include <QDirIterator>
47
47
#include <QFileInfo>
48
 
#include <QDirIterator>
 
48
#include <QMimeData>
 
49
#include <QMutableListIterator>
 
50
#include <QSortFilterProxyModel>
49
51
#include <QUndoStack>
50
 
#include <QSortFilterProxyModel>
 
52
#include <QtConcurrentRun>
 
53
#include <QtDebug>
51
54
 
52
55
#include <boost/bind.hpp>
53
56
#include <algorithm>
54
57
 
55
 
#include <lastfm/ScrobblePoint>
56
 
 
57
58
using smart_playlists::Generator;
58
59
using smart_playlists::GeneratorInserter;
59
60
using smart_playlists::GeneratorPtr;
63
64
const char* Playlist::kRowsMimetype = "application/x-clementine-playlist-rows";
64
65
const char* Playlist::kPlayNowMimetype = "application/x-clementine-play-now";
65
66
 
 
67
const int Playlist::kInvalidSongPriority = 200;
 
68
const QRgb Playlist::kInvalidSongColor = qRgb(0xC0, 0xC0, 0xC0);
 
69
 
 
70
const int Playlist::kDynamicHistoryPriority = 100;
 
71
const QRgb Playlist::kDynamicHistoryColor = qRgb(0x80, 0x80, 0x80);
 
72
 
 
73
const char* Playlist::kSettingsGroup = "Playlist";
 
74
 
66
75
Playlist::Playlist(PlaylistBackend* backend,
67
76
                   TaskManager* task_manager,
68
77
                   LibraryBackend* library,
101
110
          SLOT(TracksEnqueued(const QModelIndex&, int, int)));
102
111
 
103
112
  connect(queue_, SIGNAL(layoutChanged()), SLOT(QueueLayoutChanged()));
 
113
 
 
114
  column_alignments_[Column_Length]
 
115
      = column_alignments_[Column_Track]
 
116
      = column_alignments_[Column_Disc]
 
117
      = column_alignments_[Column_Year]
 
118
      = column_alignments_[Column_BPM]
 
119
      = column_alignments_[Column_Bitrate]
 
120
      = column_alignments_[Column_Samplerate]
 
121
      = column_alignments_[Column_Filesize]
 
122
      = column_alignments_[Column_PlayCount]
 
123
      = column_alignments_[Column_SkipCount]
 
124
      = (Qt::AlignRight | Qt::AlignVCenter);
 
125
 
 
126
  column_alignments_[Column_Score] = (Qt::AlignCenter);
104
127
}
105
128
 
106
129
Playlist::~Playlist() {
109
132
}
110
133
 
111
134
template<typename T>
112
 
QModelIndex InsertSongItems(Playlist* playlist, const SongList& songs, int pos) {
 
135
void Playlist::InsertSongItems(const SongList& songs, int pos, bool play_now, bool enqueue) {
113
136
  PlaylistItemList items;
 
137
 
114
138
  foreach (const Song& song, songs) {
115
139
    items << PlaylistItemPtr(new T(song));
116
140
  }
117
 
  return playlist->InsertItems(items, pos);
 
141
 
 
142
  InsertItems(items, pos, play_now, enqueue);
118
143
}
119
144
 
120
145
QVariant Playlist::headerData(int section, Qt::Orientation, int role) const {
210
235
 
211
236
    case Role_CanSetRating:
212
237
      return index.column() == Column_Rating &&
213
 
             items_[index.row()]->IsLocalLibraryItem();
 
238
             items_[index.row()]->IsLocalLibraryItem() &&
 
239
             items_[index.row()]->Metadata().id() != -1;
214
240
 
215
241
    case Qt::EditRole:
216
242
    case Qt::ToolTipRole:
221
247
      // Don't forget to change Playlist::CompareItems when adding new columns
222
248
      switch (index.column()) {
223
249
        case Column_Title:
224
 
          if (!song.title().isEmpty())
225
 
            return song.title();
226
 
          if (!song.basefilename().isEmpty())
227
 
            return song.basefilename();
228
 
          return song.filename();
 
250
          return song.PrettyTitle();
229
251
        case Column_Artist:       return song.artist();
230
252
        case Column_Album:        return song.album();
231
 
        case Column_Length:       return song.length();
 
253
        case Column_Length:       return song.length_nanosec();
232
254
        case Column_Track:        return song.track();
233
255
        case Column_Disc:         return song.disc();
234
256
        case Column_Year:         return song.year();
260
282
    }
261
283
 
262
284
    case Qt::TextAlignmentRole:
263
 
      switch (index.column()) {
264
 
        case Column_Length:
265
 
        case Column_Track:
266
 
        case Column_Disc:
267
 
        case Column_Year:
268
 
        case Column_BPM:
269
 
        case Column_Bitrate:
270
 
        case Column_Samplerate:
271
 
        case Column_Filesize:
272
 
        case Column_PlayCount:
273
 
        case Column_SkipCount:
274
 
          return QVariant(Qt::AlignRight | Qt::AlignVCenter);
275
 
        case Column_Score:
276
 
          return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
277
 
        default:
278
 
          return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
279
 
      }
 
285
      return QVariant(column_alignments_.value(index.column(), (Qt::AlignLeft | Qt::AlignVCenter)));
280
286
 
281
287
    case Qt::ForegroundRole:
282
 
      if (items_[index.row()]->IsDynamicHistory())
283
 
        return QApplication::palette().brush(QPalette::Disabled, QPalette::Text);
 
288
      if (items_[index.row()]->HasCurrentForegroundColor()) {
 
289
        return QBrush(items_[index.row()]->GetCurrentForegroundColor());
 
290
      }
 
291
      return QVariant();
 
292
 
 
293
    case Qt::BackgroundRole:
 
294
      if (items_[index.row()]->HasCurrentBackgroundColor()) {
 
295
        return QBrush(items_[index.row()]->GetCurrentBackgroundColor());
 
296
      }
284
297
      return QVariant();
285
298
 
286
299
    default:
333
346
  }
334
347
}
335
348
 
336
 
int Playlist::current_index() const {
 
349
int Playlist::current_row() const {
337
350
  return current_item_index_.isValid() ? current_item_index_.row() : -1;
338
351
}
339
352
 
340
 
int Playlist::last_played_index() const {
 
353
const QModelIndex Playlist::current_index() const {
 
354
  return current_item_index_;
 
355
}
 
356
 
 
357
int Playlist::last_played_row() const {
341
358
  return last_played_item_index_.isValid() ? last_played_item_index_.row() : -1;
342
359
}
343
360
 
433
450
  return -1;
434
451
}
435
452
 
436
 
int Playlist::next_index() const {
 
453
int Playlist::next_row() const {
437
454
  // Did we want to stop after this track?
438
 
  if (stop_after_.isValid() && current_index() == stop_after_.row())
 
455
  if (stop_after_.isValid() && current_row() == stop_after_.row())
439
456
    return -1;
440
457
 
441
458
  // Any queued items take priority
461
478
  }
462
479
 
463
480
  // Still off the end?  Then just give up
464
 
  if (next_virtual_index >= virtual_items_.count())
 
481
  if (next_virtual_index < 0 || next_virtual_index >= virtual_items_.count())
465
482
    return -1;
466
483
 
467
484
  return virtual_items_[next_virtual_index];
468
485
}
469
486
 
470
 
int Playlist::previous_index() const {
 
487
int Playlist::previous_row() const {
471
488
  int prev_virtual_index = PreviousVirtualIndex(current_virtual_index_);
472
489
  if (prev_virtual_index < 0) {
473
490
    // We've gone off the beginning of the playlist.
492
509
  return virtual_items_[prev_virtual_index];
493
510
}
494
511
 
495
 
void Playlist::set_current_index(int i) {
 
512
void Playlist::set_current_row(int i) {
496
513
  QModelIndex old_current = current_item_index_;
497
514
  ClearStreamMetadata();
498
515
 
515
532
 
516
533
  if (old_current.isValid()) {
517
534
    if (dynamic_playlist_) {
518
 
      items_[old_current.row()]->SetDynamicHistory(true);
 
535
      items_[old_current.row()]->SetForegroundColor(kDynamicHistoryPriority,
 
536
                                                    kDynamicHistoryColor);
519
537
    }
520
538
 
521
539
    emit dataChanged(old_current, old_current.sibling(old_current.row(), ColumnCount-1));
522
540
  }
523
541
 
524
542
  if (current_item_index_.isValid()) {
525
 
    emit dataChanged(current_item_index_, current_item_index_.sibling(current_item_index_.row(), ColumnCount-1));
526
 
    emit CurrentSongChanged(current_item_metadata());
 
543
    InformOfCurrentSongChange(current_item_index_,
 
544
                              current_item_index_.sibling(current_item_index_.row(), ColumnCount-1),
 
545
                              current_item_metadata());
527
546
  }
528
547
 
529
548
  // Update the virtual index
553
572
      connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
554
573
      connect(inserter, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
555
574
 
556
 
      inserter->Load(this, -1, false, dynamic_playlist_, count);
 
575
      inserter->Load(this, -1, false, false, dynamic_playlist_, count);
557
576
    }
558
577
 
559
578
    // Remove the first item
592
611
 
593
612
  using smart_playlists::GeneratorMimeData;
594
613
 
 
614
  bool play_now = false;
 
615
  bool enqueue_now = false;
 
616
  if (const MimeData* mime_data = qobject_cast<const MimeData*>(data)) {
 
617
    if (mime_data->clear_first_) {
 
618
      Clear();
 
619
    }
 
620
    play_now = mime_data->play_now_;
 
621
    enqueue_now = mime_data->enqueue_now_;
 
622
  }
 
623
 
595
624
  if (const SongMimeData* song_data = qobject_cast<const SongMimeData*>(data)) {
596
625
    // Dragged from a library
597
626
    // We want to check if these songs are from the actual local file backend,
598
627
    // if they are we treat them differently.
599
628
    if (song_data->backend && song_data->backend->songs_table() == Library::kSongsTable)
600
 
      InsertSongItems<LibraryPlaylistItem>(this, song_data->songs, row);
 
629
      InsertSongItems<LibraryPlaylistItem>(song_data->songs, row, play_now, enqueue_now);
601
630
    else if (song_data->backend && song_data->backend->songs_table() == MagnatuneService::kSongsTable)
602
 
      InsertSongItems<MagnatunePlaylistItem>(this, song_data->songs, row);
 
631
      InsertSongItems<MagnatunePlaylistItem>(song_data->songs, row, play_now, enqueue_now);
603
632
    else if (song_data->backend && song_data->backend->songs_table() == JamendoService::kSongsTable)
604
 
      InsertSongItems<JamendoPlaylistItem>(this, song_data->songs, row);
 
633
      InsertSongItems<JamendoPlaylistItem>(song_data->songs, row, play_now, enqueue_now);
605
634
    else
606
 
      InsertSongItems<SongPlaylistItem>(this, song_data->songs, row);
 
635
      InsertSongItems<SongPlaylistItem>(song_data->songs, row, play_now, enqueue_now);
607
636
  } else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) {
608
637
    // Dragged from the Radio pane
609
 
    InsertRadioStations(radio_data->items, row, data->hasFormat(kPlayNowMimetype));
 
638
    InsertRadioStations(radio_data->model, radio_data->indexes,
 
639
                        row, play_now, enqueue_now);
610
640
  } else if (const GeneratorMimeData* generator_data = qobject_cast<const GeneratorMimeData*>(data)) {
611
 
    InsertSmartPlaylist(generator_data->generator_, row, data->hasFormat(kPlayNowMimetype));
 
641
    InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now);
 
642
  } else if (const PlaylistItemMimeData* item_data = qobject_cast<const PlaylistItemMimeData*>(data)) {
 
643
    InsertItems(item_data->items_, row, play_now, enqueue_now);
612
644
  } else if (data->hasFormat(kRowsMimetype)) {
613
645
    // Dragged from the playlist
614
646
    // Rearranging it is tricky...
642
674
    }
643
675
  } else if (data->hasUrls()) {
644
676
    // URL list dragged from the file list or some other app
645
 
    InsertUrls(data->urls(), false, row);
 
677
    InsertUrls(data->urls(), row, play_now, enqueue_now);
646
678
  }
647
679
 
648
680
  return true;
649
681
}
650
682
 
651
 
void Playlist::InsertUrls(const QList<QUrl> &urls, bool play_now, int pos) {
652
 
  SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_, this);
 
683
void Playlist::InsertUrls(const QList<QUrl> &urls, int pos, bool play_now, bool enqueue) {
 
684
  SongLoaderInserter* inserter = new SongLoaderInserter(task_manager_, library_);
653
685
  connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
654
 
  connect(inserter, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
655
686
 
656
 
  inserter->Load(this, pos, play_now, urls);
 
687
  inserter->Load(this, pos, play_now, enqueue, urls);
657
688
}
658
689
 
659
 
void Playlist::InsertSmartPlaylist(GeneratorPtr generator, int pos, bool play_now) {
 
690
void Playlist::InsertSmartPlaylist(GeneratorPtr generator, int pos, bool play_now, bool enqueue) {
 
691
  // Hack: If the generator hasn't got a library set then use the main one
 
692
  if (!generator->library()) {
 
693
    generator->set_library(library_);
 
694
  }
 
695
 
660
696
  GeneratorInserter* inserter = new GeneratorInserter(task_manager_, library_, this);
661
697
  connect(inserter, SIGNAL(Error(QString)), SIGNAL(LoadTracksError(QString)));
662
 
  connect(inserter, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex)));
663
698
 
664
 
  inserter->Load(this, pos, play_now, generator);
 
699
  inserter->Load(this, pos, play_now, enqueue, generator);
665
700
 
666
701
  if (generator->is_dynamic()) {
667
702
    TurnOnDynamicPlaylist(generator);
693
728
  // Put the items back in
694
729
  const int start = pos == -1 ? items_.count() : pos;
695
730
  for (int i=start ; i<start+moved_items.count() ; ++i) {
696
 
    moved_items[i - start]->SetDynamicHistory(false);
 
731
    moved_items[i - start]->RemoveForegroundColor(kDynamicHistoryPriority);
697
732
    items_.insert(i, moved_items[i - start]);
698
733
  }
699
734
 
715
750
      changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex()));
716
751
    }
717
752
  }
718
 
  current_virtual_index_ = virtual_items_.indexOf(current_index());
 
753
  current_virtual_index_ = virtual_items_.indexOf(current_row());
719
754
 
720
755
  layoutChanged();
721
756
  Save();
759
794
      changePersistentIndex(pidx, index(pidx.row() + d, pidx.column(), QModelIndex()));
760
795
    }
761
796
  }
762
 
  current_virtual_index_ = virtual_items_.indexOf(current_index());
 
797
  current_virtual_index_ = virtual_items_.indexOf(current_row());
763
798
 
764
799
  layoutChanged();
765
800
  Save();
766
801
}
767
802
 
768
 
QModelIndex Playlist::InsertItems(const PlaylistItemList& items, int pos) {
769
 
  if (items.isEmpty())
770
 
    return QModelIndex();
 
803
void Playlist::InsertItems(const PlaylistItemList& itemsIn, int pos, bool play_now, bool enqueue) {
 
804
  if (itemsIn.isEmpty())
 
805
    return;
 
806
 
 
807
  PlaylistItemList items = itemsIn;
 
808
 
 
809
  // exercise vetoes
 
810
  SongList songs;
 
811
 
 
812
  foreach(PlaylistItemPtr item, items) {
 
813
    songs << item.get()->Metadata();
 
814
  }
 
815
 
 
816
  QList<Song> vetoed;
 
817
  foreach(SongInsertVetoListener* listener, veto_listeners_) {
 
818
    foreach(const Song& song, listener->AboutToInsertSongs(GetAllSongs(), songs)) {
 
819
      vetoed.append(song);
 
820
    }
 
821
  }
 
822
 
 
823
  if(!vetoed.isEmpty()) {
 
824
    QMutableListIterator<PlaylistItemPtr> it(items);
 
825
    while (it.hasNext()) {
 
826
      PlaylistItemPtr item = it.next();
 
827
      const Song& current = item.get()->Metadata();
 
828
 
 
829
      if(vetoed.contains(current)) {
 
830
        vetoed.removeOne(current);
 
831
        it.remove();
 
832
      }
 
833
    }
 
834
 
 
835
    // check for empty items once again after veto
 
836
    if(items.isEmpty()) {
 
837
      return;
 
838
    }
 
839
  }
771
840
 
772
841
  const int start = pos == -1 ? items_.count() : pos;
773
 
  undo_stack_->push(new PlaylistUndoCommands::InsertItems(this, items, pos));
 
842
  undo_stack_->push(new PlaylistUndoCommands::InsertItems(this, items, pos, enqueue));
774
843
 
775
 
  return index(start, 0);
 
844
  if (play_now)
 
845
    emit PlayRequested(index(start, 0));
776
846
}
777
847
 
778
 
QModelIndex Playlist::InsertItemsWithoutUndo(const PlaylistItemList& items,
779
 
                                             int pos) {
 
848
void Playlist::InsertItemsWithoutUndo(const PlaylistItemList& items,
 
849
                                      int pos, bool enqueue) {
780
850
  if (items.isEmpty())
781
 
    return QModelIndex();
 
851
    return;
782
852
 
783
853
  const int start = pos == -1 ? items_.count() : pos;
784
854
  const int end = start + items.count() - 1;
804
874
  }
805
875
  endInsertRows();
806
876
 
 
877
  if (enqueue) {
 
878
    QModelIndexList indexes;
 
879
    for (int i=start ; i<=end ; ++i) {
 
880
      indexes << index(i, 0);
 
881
    }
 
882
    queue_->ToggleTracks(indexes);
 
883
  }
 
884
 
807
885
  Save();
808
886
  ReshuffleIndices();
809
 
 
810
 
  return index(start, 0);
811
 
}
812
 
 
813
 
QModelIndex Playlist::InsertLibraryItems(const SongList& songs, int pos) {
814
 
  return InsertSongItems<LibraryPlaylistItem>(this, songs, pos);
815
 
}
816
 
 
817
 
QModelIndex Playlist::InsertSongs(const SongList& songs, int pos) {
818
 
  return InsertSongItems<SongPlaylistItem>(this, songs, pos);
819
 
}
820
 
 
821
 
QModelIndex Playlist::InsertSongsOrLibraryItems(const SongList& songs, int pos) {
 
887
}
 
888
 
 
889
void Playlist::InsertLibraryItems(const SongList& songs, int pos, bool play_now, bool enqueue) {
 
890
  InsertSongItems<LibraryPlaylistItem>(songs, pos, play_now, enqueue);
 
891
}
 
892
 
 
893
void Playlist::InsertSongs(const SongList& songs, int pos, bool play_now, bool enqueue) {
 
894
  InsertSongItems<SongPlaylistItem>(songs, pos, play_now, enqueue);
 
895
}
 
896
 
 
897
void Playlist::InsertSongsOrLibraryItems(const SongList& songs, int pos, bool play_now, bool enqueue) {
822
898
  PlaylistItemList items;
823
899
  foreach (const Song& song, songs) {
824
900
    if (song.id() == -1)
826
902
    else
827
903
      items << PlaylistItemPtr(new LibraryPlaylistItem(song));
828
904
  }
829
 
  return InsertItems(items, pos);
 
905
  InsertItems(items, pos, play_now, enqueue);
830
906
}
831
907
 
832
 
QModelIndex Playlist::InsertRadioStations(const QList<RadioItem*>& items, int pos, bool play_now) {
 
908
void Playlist::InsertRadioStations(const RadioModel* model,
 
909
                                   const QModelIndexList& items,
 
910
                                   int pos, bool play_now, bool enqueue) {
833
911
  PlaylistItemList playlist_items;
834
912
  QList<QUrl> song_urls;
835
 
  foreach (RadioItem* item, items) {
836
 
    if (!item->playable)
837
 
      continue;
838
 
 
839
 
    if (item->use_song_loader) {
840
 
      song_urls << item->Url();
841
 
    } else {
842
 
      playlist_items << shared_ptr<PlaylistItem>(
843
 
          new RadioPlaylistItem(item->service, item->Url(), item->Title(), item->Artist()));
 
913
 
 
914
  foreach (const QModelIndex& item, items) {
 
915
    switch (item.data(RadioModel::Role_PlayBehaviour).toInt()) {
 
916
    case RadioModel::PlayBehaviour_SingleItem:
 
917
      playlist_items << shared_ptr<PlaylistItem>(new RadioPlaylistItem(
 
918
          model->ServiceForIndex(item),
 
919
          item.data(RadioModel::Role_Url).toUrl(),
 
920
          item.data(RadioModel::Role_Title).toString(),
 
921
          item.data(RadioModel::Role_Artist).toString()));
 
922
      break;
 
923
 
 
924
    case RadioModel::PlayBehaviour_UseSongLoader:
 
925
      song_urls << item.data(RadioModel::Role_Url).toUrl();
 
926
      break;
844
927
    }
845
928
  }
846
929
 
847
930
  if (!song_urls.isEmpty()) {
848
 
    InsertUrls(song_urls, play_now, pos);
 
931
    InsertUrls(song_urls, pos, play_now, enqueue);
 
932
    play_now = false;
849
933
  }
850
934
 
851
 
  return InsertItems(playlist_items, pos);
 
935
  InsertItems(playlist_items, pos, play_now, enqueue);
852
936
}
853
937
 
854
938
QMimeData* Playlist::mimeData(const QModelIndexList& indexes) const {
891
975
    case Column_Title:        strcmp(title);
892
976
    case Column_Artist:       strcmp(artist);
893
977
    case Column_Album:        strcmp(album);
894
 
    case Column_Length:       cmp(length);
 
978
    case Column_Length:       cmp(length_nanosec);
895
979
    case Column_Track:        cmp(track);
896
980
    case Column_Disc:         cmp(disc);
897
981
    case Column_Year:         cmp(year);
1020
1104
  if (!backend_ || is_loading_)
1021
1105
    return;
1022
1106
 
1023
 
  backend_->SavePlaylistAsync(id_, items_, last_played_index(), dynamic_playlist_);
 
1107
  backend_->SavePlaylistAsync(id_, items_, last_played_row(), dynamic_playlist_);
1024
1108
}
1025
1109
 
1026
1110
namespace {
1046
1130
  watcher->deleteLater();
1047
1131
 
1048
1132
  PlaylistItemList items = watcher->future().results();
 
1133
 
 
1134
  // backend returns empty elements for library items which it couldn't 
 
1135
  // match (because they got deleted); we don't need those
 
1136
  QMutableListIterator<PlaylistItemPtr> it(items);
 
1137
  while (it.hasNext()) {
 
1138
    PlaylistItemPtr item = it.next();
 
1139
 
 
1140
    if (item->IsLocalLibraryItem() && item->Metadata().filename().isEmpty()) {
 
1141
      it.remove();
 
1142
    }
 
1143
  }
 
1144
 
1049
1145
  is_loading_ = true;
1050
1146
  InsertItems(items, 0);
1051
1147
  is_loading_ = false;
1052
1148
 
1053
1149
  PlaylistBackend::Playlist p = backend_->GetPlaylist(id_);
1054
1150
 
1055
 
  last_played_item_index_ =
1056
 
      p.last_played == -1 ? QModelIndex() : index(p.last_played);
 
1151
  // the newly loaded list of items might be shorter than it was before so
 
1152
  // look out for a bad last_played index
 
1153
  last_played_item_index_ = p.last_played == -1 || p.last_played >= rowCount()
 
1154
                                ? QModelIndex()
 
1155
                                : index(p.last_played);
1057
1156
 
1058
1157
  if (!p.dynamic_type.isEmpty()) {
1059
1158
    GeneratorPtr gen = Generator::Create(p.dynamic_type);
1074
1173
      }
1075
1174
    }
1076
1175
  }
 
1176
 
 
1177
  emit RestoreFinished();
 
1178
 
 
1179
  QSettings s;
 
1180
  s.beginGroup(kSettingsGroup);
 
1181
 
 
1182
  // should we gray out deleted songs asynchronously on startup?
 
1183
  if(s.value("greyoutdeleted", false).toBool()) {
 
1184
    QtConcurrent::run(this, &Playlist::InvalidateDeletedSongs);
 
1185
  }
 
1186
}
 
1187
 
 
1188
static bool DescendingIntLessThan(int a, int b) {
 
1189
  return a > b;
 
1190
}
 
1191
 
 
1192
void Playlist::RemoveItemsWithoutUndo(const QList<int>& indicesIn) {
 
1193
  // Sort the indices descending because removing elements 'backwards'
 
1194
  // is easier - indices don't 'move' in the process.
 
1195
  QList<int> indices = indicesIn;
 
1196
  qSort(indices.begin(), indices.end(), DescendingIntLessThan);
 
1197
 
 
1198
  for(int j = 0; j < indices.count(); j++) {
 
1199
    int beginning = indices[j], end = indices[j];
 
1200
 
 
1201
    // Splits the indices into sequences. For example this: [1, 2, 4],
 
1202
    // will get split into [1, 2] and [4].
 
1203
    while(j != indices.count() - 1 && indices[j] == indices[j + 1] + 1) {
 
1204
      beginning--;
 
1205
      j++;
 
1206
    }
 
1207
 
 
1208
    // Remove the current sequence.
 
1209
    removeRows(beginning, end - beginning + 1);
 
1210
  }
1077
1211
}
1078
1212
 
1079
1213
bool Playlist::removeRows(int row, int count, const QModelIndex& parent) {
1118
1252
  }
1119
1253
 
1120
1254
  // Reset current_virtual_index_
1121
 
  if (current_index() == -1)
 
1255
  if (current_row() == -1)
1122
1256
    current_virtual_index_ = -1;
1123
1257
  else
1124
 
    current_virtual_index_ = virtual_items_.indexOf(current_index());
 
1258
    current_virtual_index_ = virtual_items_.indexOf(current_row());
1125
1259
 
1126
1260
  Save();
1127
1261
  return ret;
1156
1290
  current_item_->SetTemporaryMetadata(song);
1157
1291
  UpdateScrobblePoint();
1158
1292
 
1159
 
  emit dataChanged(index(current_item_index_.row(), 0), index(current_item_index_.row(), ColumnCount-1));
1160
 
  emit CurrentSongChanged(song);
 
1293
  InformOfCurrentSongChange(index(current_item_index_.row(), 0),
 
1294
                            index(current_item_index_.row(), ColumnCount-1),
 
1295
                            song);
1161
1296
}
1162
1297
 
1163
1298
void Playlist::ClearStreamMetadata() {
1190
1325
}
1191
1326
 
1192
1327
void Playlist::UpdateScrobblePoint() {
1193
 
  int length = qMin(current_item_metadata().length(), 240);
1194
 
 
1195
 
  ScrobblePoint point(length / 2);
1196
 
  scrobble_point_ = point;
 
1328
  const qint64 length = current_item_metadata().length_nanosec();
 
1329
 
 
1330
  if (length == 0) {
 
1331
    scrobble_point_ = 240ll * kNsecPerSec; // 4 minutes
 
1332
  } else {
 
1333
    scrobble_point_ = qBound(31ll * kNsecPerSec,
 
1334
                             length/2,
 
1335
                             240ll * kNsecPerSec);
 
1336
  }
 
1337
 
1197
1338
  has_scrobbled_ = false;
1198
1339
}
1199
1340
 
1258
1399
 
1259
1400
void Playlist::ReloadItems(const QList<int>& rows) {
1260
1401
  foreach (int row, rows) {
1261
 
    item_at(row)->Reload();
1262
 
    emit dataChanged(index(row, 0), index(row, ColumnCount-1));
1263
 
  }
 
1402
    PlaylistItemPtr item = item_at(row);
 
1403
 
 
1404
    item->Reload();
 
1405
    InformOfCurrentSongChange(index(row, 0), index(row, ColumnCount-1),
 
1406
                              item->Metadata());
 
1407
  }
 
1408
}
 
1409
 
 
1410
void Playlist::RateSong(const QModelIndex& index, double rating) {
 
1411
  int row = index.row();
 
1412
 
 
1413
  if(has_item_at(row)) {
 
1414
    PlaylistItemPtr item = item_at(row);
 
1415
    if (item && item->IsLocalLibraryItem() && item->Metadata().id() != -1) {
 
1416
      library_->UpdateSongRatingAsync(item->Metadata().id(), rating);
 
1417
    }
 
1418
  }
 
1419
}
 
1420
 
 
1421
void Playlist::AddSongInsertVetoListener(SongInsertVetoListener* listener) {
 
1422
  veto_listeners_.append(listener);
 
1423
  connect(listener, SIGNAL(destroyed()), this, SLOT(SongInsertVetoListenerDestroyed()));
 
1424
}
 
1425
 
 
1426
void Playlist::RemoveSongInsertVetoListener(SongInsertVetoListener* listener) {
 
1427
  disconnect(listener, SIGNAL(destroyed()), this, SLOT(SongInsertVetoListenerDestroyed()));
 
1428
  veto_listeners_.removeAll(listener);
 
1429
}
 
1430
 
 
1431
void Playlist::SongInsertVetoListenerDestroyed() {
 
1432
  // qobject_cast returns NULL here for Python SIP listeners.
 
1433
  veto_listeners_.removeAll(static_cast<SongInsertVetoListener*>(sender()));
1264
1434
}
1265
1435
 
1266
1436
void Playlist::Shuffle() {
1279
1449
        changePersistentIndex(pidx, index(i, pidx.column(), QModelIndex()));
1280
1450
    }
1281
1451
  }
1282
 
  current_virtual_index_ = virtual_items_.indexOf(current_index());
 
1452
  current_virtual_index_ = virtual_items_.indexOf(current_row());
1283
1453
 
1284
1454
  layoutChanged();
1285
1455
 
1293
1463
void Playlist::ReshuffleIndices() {
1294
1464
  if (!is_shuffled_) {
1295
1465
    std::sort(virtual_items_.begin(), virtual_items_.end());
1296
 
    if (current_index() != -1)
1297
 
      current_virtual_index_ = virtual_items_.indexOf(current_index());
 
1466
    if (current_row() != -1)
 
1467
      current_virtual_index_ = virtual_items_.indexOf(current_row());
1298
1468
  } else {
1299
1469
    QList<int>::iterator begin = virtual_items_.begin();
1300
1470
    if (current_virtual_index_ != -1)
1324
1494
  return ret;
1325
1495
}
1326
1496
 
 
1497
PlaylistItemList Playlist::GetAllItems() const {
 
1498
  return items_;
 
1499
}
 
1500
 
1327
1501
quint64 Playlist::GetTotalLength() const {
1328
1502
  quint64 ret = 0;
1329
1503
  foreach (PlaylistItemPtr item, items_) {
1330
 
    int length = item->Metadata().length();
 
1504
    quint64 length = item->Metadata().length_nanosec();
1331
1505
    if (length > 0)
1332
1506
      ret += length;
1333
1507
  }
1382
1556
    }
1383
1557
  }
1384
1558
}
 
1559
 
 
1560
void Playlist::set_column_alignments(const ColumnAlignmentMap& column_alignments) {
 
1561
  column_alignments_ = column_alignments;
 
1562
}
 
1563
 
 
1564
void Playlist::set_column_align_left(int column) {
 
1565
  column_alignments_[column] = (Qt::AlignLeft | Qt::AlignVCenter);
 
1566
}
 
1567
 
 
1568
void Playlist::set_column_align_center(int column) {
 
1569
  column_alignments_[column] = Qt::AlignCenter;
 
1570
}
 
1571
 
 
1572
void Playlist::set_column_align_right(int column) {
 
1573
  column_alignments_[column] = (Qt::AlignRight | Qt::AlignVCenter);
 
1574
}
 
1575
 
 
1576
void Playlist::InformOfCurrentSongChange(const QModelIndex& top_left, const QModelIndex& bottom_right,
 
1577
                                         const Song& metadata) {
 
1578
  emit dataChanged(top_left, bottom_right);
 
1579
  // if the song is invalid, we won't play it - there's no point in
 
1580
  // informing anybody about the change
 
1581
  if(metadata.is_valid()) {
 
1582
    emit CurrentSongChanged(metadata);
 
1583
  }
 
1584
}
 
1585
 
 
1586
void Playlist::InvalidateDeletedSongs() {
 
1587
  QList<int> invalidated_rows;
 
1588
 
 
1589
  for (int row = 0; row < items_.count(); ++row) {
 
1590
    PlaylistItemPtr item = items_[row];
 
1591
    Song song = item->Metadata();
 
1592
 
 
1593
    if(!song.is_stream()) {
 
1594
      bool exists = QFile::exists(song.filename());
 
1595
 
 
1596
      if(!exists && !item->HasForegroundColor(kInvalidSongPriority)) {
 
1597
        // gray out the song if it's not there
 
1598
        item->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
 
1599
        invalidated_rows.append(row);
 
1600
      } else if(exists && item->HasForegroundColor(kInvalidSongPriority)) {
 
1601
        item->RemoveForegroundColor(kInvalidSongPriority);
 
1602
        invalidated_rows.append(row);
 
1603
      }
 
1604
    }
 
1605
  }
 
1606
 
 
1607
  ReloadItems(invalidated_rows);
 
1608
}
 
1609
 
 
1610
bool Playlist::ApplyValidityOnCurrentSong(const QUrl& url, bool valid) {
 
1611
  PlaylistItemPtr current = current_item();
 
1612
 
 
1613
  if(current) {
 
1614
    Song current_song = current->Metadata();
 
1615
 
 
1616
    // if validity has changed, reload the item
 
1617
    if(!current_song.is_stream() &&
 
1618
        current_song.filename() == url.toLocalFile() &&
 
1619
        current_song.is_valid() != QFile::exists(current_song.filename())) {
 
1620
      ReloadItems(QList<int>() << current_row());
 
1621
    }
 
1622
 
 
1623
    // gray out the song if it's now broken; otherwise undo the gray color
 
1624
    if(valid) {
 
1625
      current->RemoveForegroundColor(kInvalidSongPriority);
 
1626
    } else {
 
1627
      current->SetForegroundColor(kInvalidSongPriority, kInvalidSongColor);
 
1628
    }
 
1629
  }
 
1630
 
 
1631
  return current;
 
1632
}