/*************************************************************************** * Copyright(C) 2006-2017 by Ilya Kotov * * forkotov02@ya.ru * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include "metadatahelper_p.h" #include "playlistparser.h" #include "playlistformat.h" #include "playlistcontainer_p.h" #include "groupedcontainer_p.h" #include "normalcontainer_p.h" #include "playlisttask_p.h" #include "fileloader_p.h" #include "playstate_p.h" #include "detailsdialog.h" #include "tagupdater_p.h" #include "qmmpuisettings.h" #include "playlistmodel.h" #define INVALID_INDEX -1 PlayListModel::PlayListModel(const QString &name, QObject *parent) : QObject(parent) , m_selection() { qsrand(time(0)); m_ui_settings = QmmpUiSettings::instance(); m_total_duration = 0; m_current = 0; m_stop_track = 0; m_name = name; m_loader = new FileLoader(this); m_task = new PlayListTask(this); if(m_ui_settings->isGroupsEnabled()) m_container = new GroupedContainer; else m_container = new NormalContainer; if(m_ui_settings->isShuffle()) m_play_state = new ShufflePlayState(this); else m_play_state = new NormalPlayState(this); connect(m_ui_settings, SIGNAL(groupsChanged(bool)), SLOT(prepareGroups(bool))); connect(m_ui_settings, SIGNAL(shuffleChanged(bool)), SLOT(prepareForShufflePlaying(bool))); connect(m_loader, SIGNAL(newTracksToInsert(PlayListItem*, QList)), SLOT(insert(PlayListItem*, QList)), Qt::QueuedConnection); connect(m_loader, SIGNAL(finished()), SLOT(preparePlayState())); connect(m_loader, SIGNAL(finished()), SIGNAL(loaderFinished())); connect(m_task, SIGNAL(finished()), SLOT(onTaskFinished())); } PlayListModel::~PlayListModel() { blockSignals(true); m_loader->finish(); clear(); delete m_play_state; delete m_container; } QString PlayListModel::name() const { return m_name; } void PlayListModel::setName(const QString &name) { if(m_name != name) { m_name = name; emit nameChanged(name); } } void PlayListModel::add(PlayListTrack *track) { m_container->addTrack(track); m_total_duration += track->duration(); int flags = 0; if(m_container->trackCount() == 1) { m_current_track = track; m_current = m_container->indexOf(track); flags |= CURRENT; } else if(m_ui_settings->isGroupsEnabled()) { //update current index for grouped container only m_current = m_container->indexOf(m_current_track); } flags |= STRUCTURE; emit listChanged(flags); } void PlayListModel::add(QList tracks) { if(tracks.isEmpty()) return; int flags = 0; m_container->addTracks(tracks); if(m_container->trackCount() == tracks.count()) { m_current_track = tracks.first(); m_current = m_container->indexOf(m_current_track); flags |= CURRENT; } else if(m_ui_settings->isGroupsEnabled()) { //update current index for grouped container only m_current = m_container->indexOf(m_current_track); } foreach(PlayListTrack *track, tracks) { m_total_duration += track->duration(); emit trackAdded(track); } preparePlayState(); flags |= STRUCTURE; emit listChanged(flags); } void PlayListModel::add(const QString &path) { m_loader->add(path); } void PlayListModel::add(const QStringList &paths) { m_loader->add(paths); } void PlayListModel::insert(int index, PlayListTrack *track) { m_container->insertTrack(index, track); m_total_duration += track->duration(); int flags = 0; if(m_container->trackCount() == 1) { m_current_track = track; m_current = m_container->indexOf(track); flags |= CURRENT; } else { //update current index m_current = m_container->indexOf(m_current_track); } emit trackAdded(track); flags |= STRUCTURE; emit listChanged(flags); } void PlayListModel::insert(PlayListItem *before, PlayListTrack *track) { if(before) insert(m_container->indexOf(before), track); else add(track); } void PlayListModel::insert(int index, QList tracks) { if(tracks.isEmpty()) return; int flags = 0; foreach(PlayListTrack *track, tracks) { m_container->insertTrack(index, track); m_total_duration += track->duration(); if(m_container->trackCount() == 1) { m_current_track = track; m_current = m_container->indexOf(track); flags |= CURRENT; } emit trackAdded(track); } //update current index m_current = m_container->indexOf(m_current_track); preparePlayState(); flags |= STRUCTURE; emit listChanged(flags); } void PlayListModel::insert(PlayListItem *before, QList tracks) { if(before) insert(m_container->indexOf(before), tracks); else add(tracks); } void PlayListModel::insert(int index, const QString &path) { insert(index, QStringList() << path); } void PlayListModel::insert(int index, const QStringList &paths) { if(index < 0 || index >= m_container->count()) add(paths); else { PlayListItem *before = m_container->item(index); m_loader->insert(before, paths); } } void PlayListModel::insert(int index, const QList &urls) { QStringList paths; foreach (QUrl url, urls) { if(url.scheme() == "file") paths.append(QFileInfo(url.toLocalFile()).canonicalFilePath()); else paths.append(url.toString()); } insert(index, paths); } int PlayListModel::count() const { return m_container->count(); } int PlayListModel::trackCount() const { return m_container->trackCount(); } bool PlayListModel::isEmpty() const { return m_container->isEmpty(); } int PlayListModel::columnCount() const { return MetaDataHelper::instance()->columnCount(); } PlayListTrack* PlayListModel::currentTrack() const { return m_container->isEmpty() ? 0 : m_current_track; } PlayListTrack *PlayListModel::nextTrack() const { if(m_container->isEmpty() || !m_play_state) return 0; if(m_stop_track && m_stop_track == currentTrack()) return 0; if(!isEmptyQueue()) return m_queued_songs.at(0); int index = m_play_state->nextIndex(); if(index < 0 || (index + 1 > m_container->count())) return 0; return m_container->track(index); } int PlayListModel::indexOf(PlayListItem* item) const { return m_container->indexOf(item); } PlayListItem* PlayListModel::item(int index) const { return m_container->item(index); } PlayListTrack* PlayListModel::track(int index) const { return m_container->track(index); } PlayListGroup* PlayListModel::group(int index) const { return m_container->group(index); } int PlayListModel::currentIndex() const { return m_current; } bool PlayListModel::setCurrent(int index) { if (index > count()-1 || index < 0) return false; PlayListItem *item = m_container->item(index); if(item->isGroup()) { index++; item = m_container->item(index); } m_current = index; m_current_track = dynamic_cast (item); emit listChanged(CURRENT); return true; } bool PlayListModel::setCurrent(PlayListTrack *track) { if(!m_container->contains(track)) return false; return setCurrent(m_container->indexOf(track)); } bool PlayListModel::isTrack(int index) const { if (index > count()-1 || index < 0) return false; return !m_container->item(index)->isGroup(); } bool PlayListModel::isGroup(int index) const { if (index > count()-1 || index < 0) return false; return m_container->item(index)->isGroup(); } bool PlayListModel::next() { if(m_stop_track == currentTrack()) { m_stop_track = 0; emit listChanged(STOP_AFTER); return false; } if (!m_queued_songs.isEmpty()) { m_current_track = m_queued_songs.dequeue(); m_current = m_container->indexOf(m_current_track); emit listChanged(CURRENT | QUEUE); return true; } if(m_loader->isRunning()) m_play_state->prepare(); return m_play_state->next(); } bool PlayListModel::previous() { if (m_loader->isRunning()) m_play_state->prepare(); return m_play_state->previous(); } void PlayListModel::clear() { m_loader->finish(); m_current = 0; m_stop_track = 0; m_container->clear(); m_queued_songs.clear(); m_total_duration = 0; m_play_state->resetState(); emit listChanged(STRUCTURE | QUEUE | STOP_AFTER | CURRENT | SELECTION); } void PlayListModel::clearSelection() { m_container->clearSelection(); emit listChanged(SELECTION); } QList PlayListModel::mid(int pos, int count) const { return m_container->mid(pos, count); } bool PlayListModel::isSelected(int index) const { return m_container->isSelected(index); } bool PlayListModel::contains(const QString &url) { for(int i = 0; i < m_container->count(); ++i) { PlayListTrack *t = track(i); if(!t) continue; if(t->path() == url) return true; } return false; } int PlayListModel::indexOfTrack(int index) const { return m_container->indexOfTrack(index); } PlayListTrack *PlayListModel::findTrack(int track_index) const { return m_container->findTrack(track_index); } QList PlayListModel::findTracks(const QString &str) const { QList items; PlayListItem *item = 0; if(str.isEmpty()) return items; for(int i = 0; i < m_container->count(); ++i) { item = m_container->item(i); if(item->isGroup()) continue; if(!item->formattedTitles().filter(str, Qt::CaseInsensitive).isEmpty()) items.append(item); } return items; } void PlayListModel::setSelected(int index, bool selected) { m_container->setSelected(index, selected); emit listChanged(SELECTION); } void PlayListModel::setSelected(QList tracks, bool selected) { foreach(PlayListTrack *t, tracks) t->setSelected(selected); emit listChanged(SELECTION); } void PlayListModel::setSelected(QList items, bool selected) { foreach(PlayListItem *i, items) i->setSelected(selected); emit listChanged(SELECTION); } void PlayListModel::setSelected(int first, int last, bool selected) { if(first > last) { setSelected(last, first, selected); return; } for(int index = first; index <= last; ++index) { PlayListItem *i = item(index); if(!i) continue; i->setSelected(selected); } emit listChanged(SELECTION); } void PlayListModel::setSelected(QList indexes, bool selected) { foreach (int idx, indexes) m_container->setSelected(idx, selected); emit listChanged(SELECTION); } void PlayListModel::removeSelected() { removeSelection(false); } void PlayListModel::removeUnselected() { removeSelection(true); } void PlayListModel::removeTrack (int i) { int flags = removeTrackInternal(i); if(flags) emit listChanged(flags); } void PlayListModel::removeTrack (PlayListItem *track) { if(m_container->contains(track)) removeTrack (m_container->indexOf(track)); } void PlayListModel::removeSelection(bool inverted) { int i = 0; int select_after_delete = -1; int flags = 0; while (!m_container->isEmpty() && i < m_container->count()) { PlayListItem *item = m_container->item(i); if (!item->isGroup() && item->isSelected() ^ inverted) { flags |= removeTrackInternal(i); if(m_container->isEmpty()) continue; select_after_delete = i; } else i++; } select_after_delete = qMin(select_after_delete, m_container->count() - 1); if(select_after_delete >= 0) { m_container->setSelected(select_after_delete, true); flags |= SELECTION; } m_play_state->prepare(); if(flags) emit listChanged(flags); } int PlayListModel::removeTrackInternal(int i) { if((i < 0) || (i >= count())) return 0; int flags = 0; PlayListTrack* track = m_container->track(i); if(!track) return flags; if(m_queued_songs.removeAll(track) > 0) flags |= QUEUE; m_container->removeTrack(track); if(m_stop_track == track) { flags |= STOP_AFTER; m_stop_track = 0; } if(track->isSelected()) flags |= SELECTION; m_total_duration -= track->duration(); m_total_duration = qMax(Q_INT64_C(0), m_total_duration); if(m_current_track == track) { flags |= CURRENT; if(m_container->isEmpty()) m_current_track = 0; else { m_current = i > 0 ? qMin(i - 1, m_container->count() - 1) : 0; if(!(m_current_track = m_container->track(m_current))) { m_current_track = m_current > 0 ? m_container->track(m_current - 1) : m_container->track(1); } } } if (track->isUsed()) track->deleteLater(); else delete track; m_current = m_current_track ? m_container->indexOf(m_current_track) : -1; m_play_state->prepare(); flags |= STRUCTURE; return flags; } void PlayListModel::invertSelection() { for (int i = 0; i < m_container->count(); ++i) m_container->setSelected(i, !m_container->isSelected(i)); emit listChanged(SELECTION); } void PlayListModel::selectAll() { for (int i = 0; i < m_container->count(); ++i) m_container->setSelected(i, true); emit listChanged(SELECTION); } void PlayListModel::showDetails(QWidget *parent) { QList selected_tracks; for (int i = 0; i < m_container->count(); ++i) { if(!m_container->isSelected(i)) continue; PlayListTrack *track = m_container->track(i); if(track) selected_tracks.append(track); } if(!selected_tracks.isEmpty()) { QDialog *d = new DetailsDialog(selected_tracks, parent); TagUpdater *updater = new TagUpdater(d, selected_tracks); connect(updater, SIGNAL(destroyed(QObject *)),SLOT(updateMetaData())); d->show(); } } void PlayListModel::showDetailsForCurrent(QWidget *parent) { if (m_current_track) { QList l; l.append(m_current_track); QDialog *d = new DetailsDialog(l, parent); TagUpdater *updater = new TagUpdater(d, l); connect(updater, SIGNAL(destroyed(QObject *)),SLOT(updateMetaData())); d->show(); } } int PlayListModel::firstSelectedUpper(int row) { for (int i = row - 1;i >= 0;i--) { if (isSelected(i)) return i; } return -1; } int PlayListModel::firstSelectedLower(int row) { for (int i = row + 1;i < count() ;i++) { if (isSelected(i)) return i; } return -1; } int PlayListModel::totalDuration() const { return m_total_duration; } void PlayListModel::moveItems(int from, int to) { // Get rid of useless work if (from == to) return; QList selected_indexes = selectedIndexes(); if(selected_indexes.isEmpty()) return; foreach(int i, selected_indexes) //do no move groups { if(!isTrack(i)) return; } if (bottommostInSelection(from) == INVALID_INDEX || from == INVALID_INDEX || topmostInSelection(from) == INVALID_INDEX) return; if(m_container->move(selected_indexes, from, to)) { m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); } } int PlayListModel::topmostInSelection(int row) { if (row == 0) return 0; for (int i = row - 1;i >= 0;i--) { if (isSelected(i)) continue; else return i + 1; } return 0; } int PlayListModel::bottommostInSelection(int row) { if (row >= count() - 1) return row; for (int i = row + 1; i < count(); i++) { if (isSelected(i)) continue; else return i - 1; } return count() - 1; } const SimpleSelection& PlayListModel::getSelection(int row) { m_selection.m_top = topmostInSelection(row); m_selection.m_bottom = bottommostInSelection(row); m_selection.m_selected_indexes = selectedIndexes(); return m_selection; } QList PlayListModel::selectedIndexes() const { QList selected_rows; for (int i = 0; i < m_container->count(); i++) { if (m_container->item(i)->isSelected()) { selected_rows.append(i); } } return selected_rows; } QList PlayListModel::selectedTracks() const { QList selected_tracks; foreach(PlayListItem *item, m_container->items()) { if(!item->isGroup() && item->isSelected()) selected_tracks.append(dynamic_cast(item)); } return selected_tracks; } QList PlayListModel::items() const { return m_container->items(); } void PlayListModel::addToQueue() { QList selected_tracks = selectedTracks(); blockSignals(true); foreach(PlayListTrack* track, selected_tracks) setQueued(track); blockSignals(false); emit listChanged(QUEUE); } void PlayListModel::setQueued(PlayListTrack *item) { if (isQueued(item)) m_queued_songs.removeAll(item); else m_queued_songs.enqueue(item); emit listChanged(QUEUE); } bool PlayListModel::isQueued(PlayListTrack *f) const { return m_queued_songs.contains(f); } bool PlayListModel::isEmptyQueue() const { return m_queued_songs.isEmpty(); } int PlayListModel::queuedIndex(PlayListTrack *track) const { return m_queued_songs.indexOf(track); } int PlayListModel::queueSize() const { return m_queued_songs.size(); } bool PlayListModel::isStopAfter(PlayListItem* item) const { return m_stop_track == item; } void PlayListModel::randomizeList() { if(m_container->isEmpty()) return; m_container->randomizeList(); m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); } void PlayListModel::reverseList() { if(m_container->isEmpty()) return; m_container->reverseList(); m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); } void PlayListModel::sortSelection(int mode) { if(m_container->isEmpty()) return; m_task->sortSelection(m_container->tracks(), mode); } void PlayListModel::sort(int mode) { if(m_container->isEmpty()) return; m_task->sort(m_container->tracks(), mode); } void PlayListModel::sortByColumn(int column) { if(m_container->isEmpty()) return; if(column < 0 || column >= columnCount()) return; m_task->sortByColumn(m_container->tracks(), column); } void PlayListModel::prepareForShufflePlaying(bool val) { if (m_play_state) delete m_play_state; if (val) m_play_state = new ShufflePlayState(this); else m_play_state = new NormalPlayState(this); } void PlayListModel::prepareGroups(bool enabled) { PlayListContainer *container = 0; if(enabled) container = new GroupedContainer; else container = new NormalContainer; container->addTracks(m_container->takeAllTracks()); delete m_container; m_container = container; if(!m_container->isEmpty()) m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); } void PlayListModel::updateMetaData() { emit listChanged(METADATA); } void PlayListModel::onTaskFinished() { if(m_task->isChanged(m_container)) //update unchanged container only { m_task->clear(); return; } if(m_task->type() == PlayListTask::SORT || m_task->type() == PlayListTask::SORT_SELECTION) { m_container->replaceTracks(m_task->takeResults(&m_current_track)); m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); } else if(m_task->type() == PlayListTask::SORT_BY_COLUMN) { m_container->replaceTracks(m_task->takeResults(&m_current_track)); m_current = m_container->indexOf(m_current_track); emit listChanged(STRUCTURE); emit sortingByColumnFinished(m_task->column(), m_task->isReverted()); } else if(m_task->type() == PlayListTask::REMOVE_INVALID || m_task->type() == PlayListTask::REMOVE_DUPLICATES || m_task->type() == PlayListTask::REFRESH) { PlayListTrack *prev_current_track = m_current_track; bool prev_count = m_container->count(); m_container->replaceTracks(m_task->takeResults(&m_current_track)); if(prev_count != m_container->count()) { int flags = STRUCTURE; m_current = m_container->indexOf(m_current_track); if(prev_current_track != m_current_track) flags |= CURRENT; if(m_stop_track && !m_container->contains(m_stop_track)) { m_stop_track = 0; flags |= STOP_AFTER; } foreach (PlayListTrack *t, m_queued_songs) { if(!m_container->contains(t)) { flags |= QUEUE; m_queued_songs.removeAll(t); } } emit listChanged(flags); } } } void PlayListModel::doCurrentVisibleRequest() { if(!m_container->isEmpty() && m_current >= 0) emit scrollToRequest(currentIndex()); } void PlayListModel::scrollTo(int index) { if(index >= 0 && index < m_container->count()) emit scrollToRequest(index); } void PlayListModel::loadPlaylist(const QString &f_name) { m_loader->add(f_name); } void PlayListModel::loadPlaylist(const QString &fmt, const QByteArray &data) { m_loader->addPlayList(fmt, data); } void PlayListModel::savePlaylist(const QString &f_name) { QList songs; for(int i = 0; i < m_container->count(); ++i) { if(isTrack(i)) songs << m_container->track(i); } PlayListParser::savePlayList(songs, f_name); } bool PlayListModel::isLoaderRunning() const { return m_loader->isRunning(); } void PlayListModel::preparePlayState() { m_play_state->prepare(); } void PlayListModel::removeInvalidTracks() { m_task->removeInvalidTracks(m_container->tracks(), m_current_track); } void PlayListModel::removeDuplicates() { m_task->removeDuplicates(m_container->tracks(), m_current_track); } void PlayListModel::refresh() { m_task->refresh(m_container->tracks(), m_current_track); } void PlayListModel::clearQueue() { m_queued_songs.clear(); m_stop_track = 0; emit listChanged(QUEUE); } void PlayListModel::stopAfterSelected() { QList selected_tracks = selectedTracks(); int flags = STOP_AFTER; if(!m_queued_songs.isEmpty()) { m_stop_track = m_stop_track != m_queued_songs.last() ? m_queued_songs.last() : 0; } else if(selected_tracks.count() == 1) { m_stop_track = m_stop_track != selected_tracks.at(0) ? selected_tracks.at(0) : 0; } else if(selected_tracks.count() > 1) { blockSignals(true); addToQueue(); blockSignals(false); flags |= QUEUE; m_stop_track = m_queued_songs.last(); } else return; emit listChanged(flags); } void PlayListModel::rebuildGroups() { if(m_ui_settings->isGroupsEnabled()) prepareGroups(true); }