diff options
Diffstat (limited to 'src/qmmpui/playlistmodel.cpp')
| -rw-r--r-- | src/qmmpui/playlistmodel.cpp | 910 |
1 files changed, 910 insertions, 0 deletions
diff --git a/src/qmmpui/playlistmodel.cpp b/src/qmmpui/playlistmodel.cpp new file mode 100644 index 000000000..ffde91ac8 --- /dev/null +++ b/src/qmmpui/playlistmodel.cpp @@ -0,0 +1,910 @@ +/*************************************************************************** + * Copyright(C) 2006-2008 by Ilya Kotov * + * forkotov02@hotmail.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., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include <QWidget> +#include <QFile> +#include <QDir> +#include <QtAlgorithms> +#include <QFileInfo> +#include <QTextStream> +#include <QPluginLoader> +#include <QApplication> +#include <QTimer> +#include <QSettings> +#include <QMessageBox> +#include <QBuffer> + +#include <time.h> + +#include <qmmp/decoder.h> +#include <qmmp/decoderfactory.h> +#include <qmmpui/playlistparser.h> +#include <qmmpui/playlistformat.h> + +#include "fileloader.h" +#include "playlistmodel.h" +#include "playlistitem.h" +#include "playstate.h" + +#include <QMetaType> + +#define INVALID_ROW -1 + +TagUpdater::TagUpdater(QObject* o,PlayListItem* item):m_observable(o),m_item(item) +{ + m_item->setFlag(PlayListItem::EDITING); + connect(m_observable, SIGNAL(destroyed(QObject *)),SLOT(updateTag())); + connect(m_observable, SIGNAL(destroyed(QObject *)),SLOT(deleteLater())); +} + +void TagUpdater::updateTag() +{ + if (m_item->flag() == PlayListItem::SCHEDULED_FOR_DELETION) + { + delete m_item; + m_item = NULL; + } + else + { + m_item->updateTags(); + m_item->setFlag(PlayListItem::FREE); + } +} + + +PlayListModel::PlayListModel(QObject *parent) + : QObject(parent) , m_selection() +{ + qsrand(time(0)); + m_total_length = 0; + m_current = 0; + m_block_update_signals = false; + is_repeatable_list = false; + m_play_state = new NormalPlayState(this); + //readSettings(); +} + +PlayListModel::~PlayListModel() +{ + writeSettings(); + clear(); + delete m_play_state; + //qDeleteAll(m_registered_pl_formats); + + foreach(GuardedFileLoader l,m_running_loaders) + { + if (!l.isNull()) + { + l->finish(); + l->wait(); + } + } +} + +void PlayListModel::load(PlayListItem *item) +{ + if (m_items.isEmpty()) + m_currentItem = item; + + m_total_length += item->length(); + m_items << item; + + if (m_items.size() == 1) + emit firstAdded(); + + if (!m_block_update_signals) + emit listChanged(); +} + +int PlayListModel::count() +{ + return m_items.size(); +} + +PlayListItem* PlayListModel::currentItem() +{ + if (m_items.isEmpty()) + return 0; + else + return m_items.at(qMin(m_items.size() - 1, m_current)); +} + +int PlayListModel::currentRow() +{ + return m_current; +} + +bool PlayListModel::setCurrent(int c) +{ + if (c > count()-1 || c < 0) + return FALSE; + m_current = c; + m_currentItem = m_items.at(c); + emit currentChanged(); + emit listChanged(); + return TRUE; +} + + +bool PlayListModel::next() +{ + if (isFileLoaderRunning()) + m_play_state->prepare(); + + return m_play_state->next(); +} + +bool PlayListModel::previous() +{ + if (isFileLoaderRunning()) + m_play_state->prepare(); + + return m_play_state->previous();//) +} + +void PlayListModel::clear() +{ + foreach(GuardedFileLoader l,m_running_loaders) + { + if (!l.isNull()) + { + l->finish(); + l->wait(); + } + } + + m_running_loaders.clear(); + + m_current = 0; + while (!m_items.isEmpty()) + { + PlayListItem* mf = m_items.takeFirst(); + + if (mf->flag() == PlayListItem::FREE) + { + delete mf; + } + else if (mf->flag() == PlayListItem::EDITING) + { + mf->setFlag(PlayListItem::SCHEDULED_FOR_DELETION); + } + } + + m_total_length = 0; + m_play_state->resetState(); + emit listChanged(); +} + +void PlayListModel::clearSelection() +{ + for (int i = 0; i<m_items.size(); ++i) + m_items.at(i)->setSelected(FALSE); + emit listChanged(); +} + +QList <QString> PlayListModel::getTitles(int b,int l) +{ + QList <QString> m_titles; + for (int i = b;(i < b + l) &&(i < m_items.size()); ++i) + m_titles << m_items.at(i)->text(); + return m_titles; +} + +QList <QString> PlayListModel::getTimes(int b,int l) +{ + QList <QString> m_times; + for (int i = b;(i < b + l) &&(i < m_items.size()); ++i) + m_times << QString("%1").arg(m_items.at(i)->length() /60) +":" + +QString("%1").arg(m_items.at(i)->length() %60/10) + + QString("%1").arg(m_items.at(i)->length() %60%10); + return m_times; +} + +bool PlayListModel::isSelected(int row) +{ + if (m_items.count() > row && row >= 0) + return m_items.at(row)->isSelected(); + + return false; +} + +void PlayListModel::setSelected(int row, bool yes) +{ + if (m_items.count() > row && row >= 0) + m_items.at(row)->setSelected(yes); +} + +void PlayListModel::removeSelected() +{ + removeSelection(false); +} + +void PlayListModel::removeUnselected() +{ + removeSelection(true); +} + +void PlayListModel::removeSelection(bool inverted) +{ + int i = 0; + + int select_after_delete = -1; + + while (!m_items.isEmpty() && i<m_items.size()) + { + if (m_items.at(i)->isSelected() ^ inverted) + { + PlayListItem* f = m_items.takeAt(i); + m_total_length -= f->length(); + if (m_total_length < 0) + m_total_length = 0; + + if (f->flag() == PlayListItem::FREE) + { + delete f; + f = NULL; + } + else if (f->flag() == PlayListItem::EDITING) + f->setFlag(PlayListItem::SCHEDULED_FOR_DELETION); + + select_after_delete = i; + + if (m_current >= i && m_current!=0) + m_current--; + } + else + i++; + } + + if (!m_items.isEmpty()) + m_currentItem = m_items.at(m_current); + + if (select_after_delete >= m_items.count()) + select_after_delete = m_items.count() - 1; + + setSelected(select_after_delete,true); + + m_play_state->prepare(); + + emit listChanged(); +} + +void PlayListModel::invertSelection() +{ + for (int i = 0; i<m_items.size(); ++i) + m_items.at(i)->setSelected(!m_items.at(i)->isSelected()); + emit listChanged(); +} + +void PlayListModel::selectAll() +{ + for (int i = 0; i<m_items.size(); ++i) + m_items.at(i)->setSelected(TRUE); + emit listChanged(); +} + +void PlayListModel::showDetails() +{ + for (int i = 0; i<m_items.size(); ++i) + { + if (m_items.at(i)->isSelected()) + { + if (!QFile::exists(m_items.at(i)->url())) + { + PlayListItem *item = m_items.at(i); + QString str; + str.append(tr("Url:") + " %1\n"); + str.append(tr("Title:") + " %2\n"); + str.append(tr("Artist:") + " %3\n"); + str.append(tr("Album:") + " %4\n"); + str.append(tr("Comment:") + " %5"); + str = str.arg(item->url()) + .arg(item->title().isEmpty() ? item->text() : item->title()) + .arg(item->artist()) + .arg(item->album()) + .arg(item->comment()); + QMessageBox::information(0, m_items.at(i)->url(), str); + return; + } + + DecoderFactory *fact = Decoder::findByPath(m_items.at(i)->url()); + if (fact) + { + QObject* o = fact->showDetails(0, m_items.at(i)->url()); + if (o) + { + TagUpdater *updater = new TagUpdater(o,m_items.at(i)); + m_editing_items.append(m_items.at(i)); + connect(updater, SIGNAL(destroyed(QObject *)),SIGNAL(listChanged())); + } + } + return; + } + } +} + +void PlayListModel::readSettings() +{ + QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat); + m_current = settings.value("Playlist/current",0).toInt(); + + QString line, param, value; + int s; + QList <FileInfo *> infoList; + QFile file(QDir::homePath() +"/.qmmp/playlist.txt"); + file.open(QIODevice::ReadOnly); + QByteArray array = file.readAll(); + file.close(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + while (!buffer.atEnd()) + { + line = QString::fromUtf8(buffer.readLine()).trimmed(); + if ((s = line.indexOf("=")) < 0) + continue; + + param = line.left(s); + value = line.right(line.size() - s - 1); + + if (param == "file") + infoList << new FileInfo(value); + else if (infoList.isEmpty()) + continue; + else if (param == "title") + infoList.last()->setMetaData(Qmmp::TITLE, value); + else if (param == "artist") + infoList.last()->setMetaData(Qmmp::ARTIST, value); + else if (param == "album") + infoList.last()->setMetaData(Qmmp::ALBUM, value); + else if (param == "comment") + infoList.last()->setMetaData(Qmmp::COMMENT, value); + else if (param == "genre") + infoList.last()->setMetaData(Qmmp::GENRE, value); + else if (param == "year") + infoList.last()->setMetaData(Qmmp::YEAR, value); + else if (param == "track") + infoList.last()->setMetaData(Qmmp::TRACK, value); + else if (param == "length") + infoList.last()->setLength(value.toInt()); + } + buffer.close(); + if (m_current > infoList.count() - 1) + m_current = 0; + m_block_update_signals = TRUE; + foreach(FileInfo *info, infoList) + load(new PlayListItem(info, &settings)); //using one and the same settings object for all playlist items + m_block_update_signals = FALSE; + doCurrentVisibleRequest(); +} + +void PlayListModel::writeSettings() +{ + QFile file(QDir::homePath() +"/.qmmp/playlist.txt"); + file.open(QIODevice::WriteOnly); + foreach(PlayListItem* m, m_items) + { + file.write(QString("file=%1").arg(m->url()).toUtf8() +"\n"); + file.write(QString("title=%1").arg(m->title()).toUtf8() +"\n"); + file.write(QString("artist=%1").arg(m->artist()).toUtf8() +"\n"); + file.write(QString("album=%1").arg(m->album()).toUtf8() +"\n"); + file.write(QString("comment=%1").arg(m->comment()).toUtf8() +"\n"); + file.write(QString("genre=%1").arg(m->genre()).toUtf8() +"\n"); + file.write(QString("year=%1").arg(m->year()).toUtf8() +"\n"); + file.write(QString("track=%1").arg(m->track()).toUtf8() +"\n"); + file.write(QString("length=%1").arg(m->length()).toUtf8() +"\n"); + } + file.close(); + QSettings settings(QDir::homePath()+"/.qmmp/qmmprc", QSettings::IniFormat); + settings.setValue("Playlist/current", m_current); +} + +void PlayListModel::addFile(const QString& path) +{ + if (path.isEmpty()) + return; + /*if(path.startsWith("http://")) + load(new PlayListItem(path)); + else if(Decoder::supports(path)) + load(new PlayListItem(path));*/ + QList <FileInfo *> playList = Decoder::createPlayList(path); + foreach(FileInfo *info, playList) + emit load(new PlayListItem(info)); + + m_play_state->prepare(); +} + +FileLoader * PlayListModel::createFileLoader() +{ + FileLoader* f_loader = new FileLoader(this); +// f_loader->setStackSize(20 * 1024 * 1024); + m_running_loaders << f_loader; + connect(f_loader,SIGNAL(newPlayListItem(PlayListItem*)),this,SLOT(load(PlayListItem*)),Qt::QueuedConnection); + connect(f_loader,SIGNAL(finished()),this,SLOT(preparePlayState())); + connect(f_loader,SIGNAL(finished()),f_loader,SLOT(deleteLater())); + return f_loader; +} + +void PlayListModel::addFiles(const QStringList &files) +{ + FileLoader* f_loader = createFileLoader(); + f_loader->setFilesToLoad(files); + f_loader->start(QThread::IdlePriority); +} + +void PlayListModel::addDirectory(const QString& s) +{ + FileLoader* f_loader = createFileLoader(); + f_loader->setDirectoryToLoad(s); + f_loader->start(QThread::IdlePriority); +} + +void PlayListModel::addFileList(const QStringList &l) +{ +// qWarning("void// PlayListModel::addFileList(const QStringList &l)"); + foreach(QString str,l) + { + QFileInfo f_info(str); + if (f_info.exists()) + { + if (f_info.isDir()) + addDirectory(str); + else + { + addFile(str); + loadPlaylist(str); + } + } + // Do processing the rest of events to avoid GUI freezing + QApplication::processEvents(QEventLoop::AllEvents,10); + } +} + +bool PlayListModel::setFileList(const QStringList & l) +{ + bool model_cleared = FALSE; + foreach(QString str,l) + { + QFileInfo f_info(str); + if (f_info.exists()) + { + if (!model_cleared) + { + clear(); + model_cleared = TRUE; + } + if (f_info.isDir()) + addDirectory(str); + else + { + addFile(str); + loadPlaylist(str); + } + } + // Do processing the rest of events to avoid GUI freezing + QApplication::processEvents(QEventLoop::AllEvents,10); + } + + return model_cleared; +} + +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; +} + +void PlayListModel::moveItems(int from, int to) +{ + // Get rid of useless work + if (from == to) + return; + + QList<int> selected_rows = getSelectedRows(); + + if (!(bottommostInSelection(from) == INVALID_ROW || + from == INVALID_ROW || + topmostInSelection(from) == INVALID_ROW) + ) + { + if (from > to) + foreach(int i, selected_rows) + if (i + to - from < 0) + break; + else + m_items.move(i,i + to - from); + else + for (int i = selected_rows.count() - 1; i >= 0; i--) + if (selected_rows[i] + to -from >= m_items.count()) + break; + else + m_items.move(selected_rows[i],selected_rows[i] + to - from); + + m_current = m_items.indexOf(m_currentItem); + + emit listChanged(); + } +} + + + +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 >= m_items.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_anchor = row; + m_selection.m_bottom = bottommostInSelection(row); + m_selection.m_selected_rows = getSelectedRows(); + return m_selection; +} + +QList<int> PlayListModel::getSelectedRows() const +{ + QList<int>selected_rows; + for (int i = 0;i<m_items.count();i++) + { + if (m_items[i]->isSelected()) + { + selected_rows.append(i); + } + } + return selected_rows; +} + +QList< PlayListItem * > PlayListModel::getSelectedItems() const +{ + QList<PlayListItem*>selected_items; + for (int i = 0;i<m_items.count();i++) + { + if (m_items[i]->isSelected()) + { + selected_items.append(m_items[i]); + } + } + return selected_items; +} + +void PlayListModel::addToQueue() +{ + QList<PlayListItem*> selected_items = getSelectedItems(); + foreach(PlayListItem* file,selected_items) + {/* + if(isQueued(file)) + m_queued_songs.removeAt(m_queued_songs.indexOf(file)); + else + m_queued_songs.append(file); + */ + setQueued(file); + } + emit listChanged(); +} + +void PlayListModel::setQueued(PlayListItem* file) +{ + if (isQueued(file)) + m_queued_songs.removeAt(m_queued_songs.indexOf(file)); + else + m_queued_songs.append(file); + + emit listChanged(); +} + +bool PlayListModel::isQueued(PlayListItem* f) const +{ + return m_queued_songs.contains(f); +} + +void PlayListModel::setCurrentToQueued() +{ + setCurrent(row(m_queued_songs.at(0))); + m_queued_songs.pop_front(); +} + +bool PlayListModel::isEmptyQueue() const +{ + return m_queued_songs.isEmpty(); +} + +void PlayListModel::randomizeList() +{ + for (int i = 0;i < m_items.size();i++) + m_items.swap(qrand()%m_items.size(),qrand()%m_items.size()); + + m_current = m_items.indexOf(m_currentItem); + emit listChanged(); +} + +void PlayListModel::reverseList() +{ + for (int i = 0;i < m_items.size()/2;i++) + m_items.swap(i,m_items.size() - i - 1); + + m_current = m_items.indexOf(m_currentItem); + emit listChanged(); +} + +////===============THE BEGINNING OF SORT IMPLEMENTATION =======================//// + +// First we'll implement bundle of static compare procedures +// to sort items in different ways +static bool _titleLessComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->title() < s2->title(); +} + +static bool _titleGreaterComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->title() > s2->title(); +} + +static bool _pathAndFilenameLessComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->url() < s2->url(); +} + +static bool _pathAndFilenameGreaterComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->url() > s2->url(); +} + +static bool _filenameLessComparator(PlayListItem* s1,PlayListItem* s2) +{ + QFileInfo i_s1(s1->url()); + QFileInfo i_s2(s2->url()); + return i_s1.baseName() < i_s2.baseName(); +} + +static bool _filenameGreaterComparator(PlayListItem* s1,PlayListItem* s2) +{ + QFileInfo i_s1(s1->url()); + QFileInfo i_s2(s2->url()); + return i_s1.baseName() > i_s2.baseName(); +} + +static bool _dateLessComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->year() < s2->year(); +} + +static bool _dateGreaterComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->year() > s2->year(); +} + +static bool _trackLessComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->track() < s2->track(); +} + +static bool _trackGreaterComparator(PlayListItem* s1,PlayListItem* s2) +{ + return s1->track() > s2->track(); +} + +// This is main sort method +void PlayListModel::doSort(int sort_mode,QList<PlayListItem*>& list_to_sort) +{ + QList<PlayListItem*>::iterator begin; + QList<PlayListItem*>::iterator end; + + begin = list_to_sort.begin(); + end = list_to_sort.end(); + + bool(*compareLessFunc)(PlayListItem*,PlayListItem*) = 0; + bool(*compareGreaterFunc)(PlayListItem*,PlayListItem*) = 0; + + switch (sort_mode) + { + case TITLE: + compareLessFunc = _titleLessComparator; + compareGreaterFunc = _titleGreaterComparator; + break; + case FILENAME: + compareLessFunc = _filenameLessComparator; + compareGreaterFunc = _filenameGreaterComparator; + break; + case PATH_AND_FILENAME: + compareLessFunc = _pathAndFilenameLessComparator; + compareGreaterFunc = _pathAndFilenameGreaterComparator; + break; + case DATE: + compareLessFunc = _dateLessComparator; + compareGreaterFunc = _dateGreaterComparator; + break; + //qWarning("TODO Sort by Date: %s\t%d",__FILE__,__LINE__); + case TRACK: + compareLessFunc = _trackLessComparator; + compareGreaterFunc = _trackGreaterComparator; + break; + default: + compareLessFunc = _titleLessComparator; + compareGreaterFunc = _titleGreaterComparator; + } + + static bool sorted_asc = false; + if (!sorted_asc) + { + qSort(begin,end,compareLessFunc); + sorted_asc = true; + } + else + { + qSort(begin,end,compareGreaterFunc); + sorted_asc = false; + } + + m_current = m_items.indexOf(m_currentItem); +} + +void PlayListModel::sortSelection(int mode) +{ + QList<PlayListItem*>selected_items = getSelectedItems(); + QList<int>selected_rows = getSelectedRows(); + + doSort(mode,selected_items); + + for (int i = 0;i < selected_rows.count();i++) + m_items.replace(selected_rows[i],selected_items[i]); + + m_current = m_items.indexOf(m_currentItem); + emit listChanged(); +} + +void PlayListModel::sort(int mode) +{ + doSort(mode,m_items); + emit listChanged(); +} + +////=============== THE END OF SORT IMPLEMENTATION =======================//// + +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::prepareForRepeatablePlaying(bool val) +{ + is_repeatable_list = val; +} + +void PlayListModel::doCurrentVisibleRequest() +{ + emit currentChanged(); + emit listChanged(); +} + +void PlayListModel::setUpdatesEnabled(bool yes) +{ + if (yes) + { + m_block_update_signals = false; + emit listChanged(); + } + else + { + m_block_update_signals = true; + } +} + +void PlayListModel::loadPlaylist(const QString &f_name) +{ + PlaylistFormat* prs = PlaylistParser::instance()->findByPath(f_name); + if (prs) + { + QFile file(f_name); + if (file.open(QIODevice::ReadOnly)) + { + //clear(); + QStringList list = prs->decode(QTextStream(&file).readAll()); + for (int i = 0; i < list.size(); ++i) + { + if (QFileInfo(list.at(i)).isRelative() && !list.at(i).contains("://")) + QString path = list[i].prepend(QFileInfo(f_name).canonicalPath () + QDir::separator ()); + } + addFiles(list); + file.close(); + } + else + qWarning("Error opening %s",f_name.toLocal8Bit().data()); + } +} + +void PlayListModel::savePlaylist(const QString & f_name) +{ + PlaylistFormat* prs = PlaylistParser::instance()->findByPath(f_name); + if (prs) + { + QFile file(f_name); + if (file.open(QIODevice::WriteOnly)) + { + QTextStream ts(&file); + QList <AbstractPlaylistItem *> songs; + foreach(PlayListItem* item, m_items) + songs << item; + ts << prs->encode(songs); + file.close(); + } + else + qWarning("Error opening %s",f_name.toLocal8Bit().data()); + } +} + +bool PlayListModel::isFileLoaderRunning() const +{ + foreach(FileLoader* l,m_running_loaders) + if (l && l->isRunning()) + return TRUE; + + return FALSE; +} + +void PlayListModel::preparePlayState() +{ + m_play_state->prepare(); +} |
