diff options
Diffstat (limited to 'src/ui/playlistmodel.cpp')
| -rw-r--r-- | src/ui/playlistmodel.cpp | 899 |
1 files changed, 899 insertions, 0 deletions
diff --git a/src/ui/playlistmodel.cpp b/src/ui/playlistmodel.cpp new file mode 100644 index 000000000..196608438 --- /dev/null +++ b/src/ui/playlistmodel.cpp @@ -0,0 +1,899 @@ +/*************************************************************************** + * Copyright (C) 2006 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 <time.h> + +#include <decoder.h> +#include <decoderfactory.h> + +#include "fileloader.h" +#include "playlistmodel.h" +#include "mediafile.h" +#include "playlistformat.h" +#include "playstate.h" + +#include <QMetaType> + +#define INVALID_ROW -1 + +TagUpdater::TagUpdater(QObject* o,MediaFile* f):m_observable(o),m_file(f) +{ + m_file->setFlag(MediaFile::EDITING); + connect (m_observable, SIGNAL(destroyed (QObject * )),SLOT(updateTag())); + connect (m_observable, SIGNAL(destroyed (QObject * )),SLOT(deleteLater())); +} + +void TagUpdater::updateTag() +{ + if(m_file->flag() == MediaFile::SCHEDULED_FOR_DELETION) + { + delete m_file; + m_file = NULL; + } + else + { + m_file->updateTags(); + m_file->setFlag(MediaFile::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(); + + registerPlaylistFormat( new PLSPlaylistFormat); + registerPlaylistFormat( new M3UPlaylistFormat); +#ifndef XSPF_PLUGIN + registerPlaylistFormat( new XSPFPlaylistFormat); +#endif + loadExternalPlaylistFormats(); +} + +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 ( MediaFile *file ) +{ + if (m_files.isEmpty()) + m_currentItem = file; + + m_total_length += file->length(); + m_files << file; + + //if (!m_block_update_signals) + emit listChanged(); +} + +int PlayListModel::count() +{ + return m_files.size(); +} + +MediaFile* PlayListModel::currentItem() +{ + if ( m_files.isEmpty() ) + return 0; + else + return m_files.at ( qMin(m_files.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_files.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_files.isEmpty() ) + { + MediaFile* mf = m_files.takeFirst(); + + if(mf->flag() == MediaFile::FREE) + { + delete mf; + } + else if(mf->flag() == MediaFile::EDITING) + { + mf->setFlag(MediaFile::SCHEDULED_FOR_DELETION); + } + } + + m_total_length = 0; + m_play_state->resetState(); + emit listChanged(); +} + +void PlayListModel::clearSelection() +{ + for ( int i = 0; i<m_files.size(); ++i ) + m_files.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_files.size() ); ++i ) + m_titles << m_files.at ( i )->title(); + return m_titles; +} + +QList <QString> PlayListModel::getTimes ( int b,int l ) +{ + QList <QString> m_times; + for ( int i = b; ( i < b + l ) && ( i < m_files.size() ); ++i ) + m_times << QString ( "%1" ).arg ( m_files.at ( i )->length() /60 ) +":" + +QString ( "%1" ).arg ( m_files.at ( i )->length() %60/10 ) + + QString ( "%1" ).arg ( m_files.at ( i )->length() %60%10 ); + return m_times; +} + +bool PlayListModel::isSelected ( int row ) +{ + if (m_files.count() > row && row >= 0) + return m_files.at ( row )->isSelected(); + + return false; +} + +void PlayListModel::setSelected ( int row, bool yes ) +{ + if (m_files.count() > row && row >= 0) + m_files.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_files.isEmpty() && i<m_files.size() ) + { + if ( m_files.at ( i )->isSelected() ^ inverted ) + { + MediaFile* f = m_files.takeAt ( i ); + m_total_length -= f->length(); + if (m_total_length < 0) + m_total_length = 0; + + if(f->flag() == MediaFile::FREE) + { + delete f; + f = NULL; + } + else if(f->flag() == MediaFile::EDITING) + f->setFlag(MediaFile::SCHEDULED_FOR_DELETION); + + select_after_delete = i; + + if ( m_current >= i && m_current!=0 ) + m_current--; + } + else + i++; + } + + if (!m_files.isEmpty()) + m_currentItem = m_files.at(m_current); + + if (select_after_delete >= m_files.count()) + select_after_delete = m_files.count() - 1; + + setSelected(select_after_delete,true); + + m_play_state->prepare(); + + emit listChanged(); +} + +void PlayListModel::invertSelection() +{ + for ( int i = 0; i<m_files.size(); ++i ) + m_files.at ( i )->setSelected ( !m_files.at ( i )->isSelected() ); + emit listChanged(); +} + +void PlayListModel::selectAll() +{ + for ( int i = 0; i<m_files.size(); ++i ) + m_files.at ( i )->setSelected ( TRUE ); + emit listChanged(); +} + +void PlayListModel::showDetails() +{ + for ( int i = 0; i<m_files.size(); ++i ) + { + if ( m_files.at ( i )->isSelected() ) + { + DecoderFactory *fact = Decoder::findByPath ( m_files.at ( i )->path() ); + if ( fact ) + { + QObject* o = fact->showDetails ( 0, m_files.at ( i )->path() ); + if(o) + { + TagUpdater *updater = new TagUpdater(o,m_files.at(i)); + m_editing_files.append(m_files.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(); + + QFile file ( QDir::homePath() +"/.qmmp/playlist.txt" ); + file.open ( QIODevice::ReadOnly ); + + QStringList files; + QByteArray line; + m_files.clear(); + + while (!file.atEnd ()) + { + line = file.readLine(); + files << QString::fromUtf8 ( line ).trimmed (); + } + + file.close (); + + if(m_current > files.count() - 1) + m_current = 0; + + int preload = (files.count() < 100) ? files.count() : 100; + + for (int i = 0;i < preload;i++) + { + load(new MediaFile(files.takeAt(0))); + } + + if (files.isEmpty()) + { + doCurrentVisibleRequest (); + return; + } + + FileLoader* f_loader = createFileLoader(); + connect(f_loader, SIGNAL(finished ()), SLOT(doCurrentVisibleRequest ())); + + f_loader->setFilesToLoad(files); + //f_loader->start(QThread::IdlePriority); + QTimer::singleShot(1000,f_loader,SLOT(start())); + //m_play_state->prepare(); +} + +void PlayListModel::writeSettings() +{ + QFile file ( QDir::homePath() +"/.qmmp/playlist.txt" ); + file.open ( QIODevice::WriteOnly ); + foreach ( MediaFile* m, m_files ) + file.write ( m->path().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 MediaFile(path)); + else if (Decoder::supports(path)) + load(new MediaFile(path)); + + 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(newMediaFile(MediaFile*)),this,SLOT(load(MediaFile*)),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); + } + // 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); + } + // 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_files.move(i,i + to - from); + else + for (int i = selected_rows.count() - 1; i >= 0; i--) + if (selected_rows[i] + to -from >= m_files.count()) + break; + else + m_files.move(selected_rows[i],selected_rows[i] + to - from); + + m_current = m_files.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_files.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_files.count();i++) + { + if (m_files[i]->isSelected()) + { + selected_rows.append(i); + } + } + return selected_rows; +} + +QList< MediaFile * > PlayListModel::getSelectedItems() const +{ + QList<MediaFile*>selected_items; + for (int i = 0;i<m_files.count();i++) + { + if (m_files[i]->isSelected()) + { + selected_items.append(m_files[i]); + } + } + return selected_items; +} + +void PlayListModel::addToQueue() +{ + QList<MediaFile*> selected_items = getSelectedItems(); + foreach(MediaFile* 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(MediaFile* file) +{ + if (isQueued(file)) + m_queued_songs.removeAt(m_queued_songs.indexOf(file)); + else + m_queued_songs.append(file); + + emit listChanged(); +} + +bool PlayListModel::isQueued(MediaFile* 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_files.size();i++) + m_files.swap(qrand()%m_files.size(),qrand()%m_files.size()); + + m_current = m_files.indexOf(m_currentItem); + emit listChanged(); +} + +void PlayListModel::reverseList() +{ + for (int i = 0;i < m_files.size()/2;i++) + m_files.swap(i,m_files.size() - i - 1); + + m_current = m_files.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(MediaFile* s1,MediaFile* s2) +{ + return s1->title() < s2->title(); +} + +static bool _titleGreaterComparator(MediaFile* s1,MediaFile* s2) +{ + return s1->title() > s2->title(); +} + +static bool _pathAndFilenameLessComparator(MediaFile* s1,MediaFile* s2) +{ + return s1->path() < s2->path(); +} + +static bool _pathAndFilenameGreaterComparator(MediaFile* s1,MediaFile* s2) +{ + return s1->path() > s2->path(); +} + +static bool _filenameLessComparator(MediaFile* s1,MediaFile* s2) +{ + QFileInfo i_s1(s1->path()); + QFileInfo i_s2(s2->path()); + return i_s1.baseName() < i_s2.baseName(); +} + +static bool _filenameGreaterComparator(MediaFile* s1,MediaFile* s2) +{ + QFileInfo i_s1(s1->path()); + QFileInfo i_s2(s2->path()); + return i_s1.baseName() > i_s2.baseName(); +} + +static bool _dateLessComparator(MediaFile* s1,MediaFile* s2) +{ + return s1->year() < s2->year(); +} + +static bool _dateGreaterComparator(MediaFile* s1,MediaFile* s2) +{ + return s1->year() > s2->year(); +} + +// This is main sort method +void PlayListModel::doSort(int sort_mode,QList<MediaFile*>& list_to_sort) +{ + QList<MediaFile*>::iterator begin; + QList<MediaFile*>::iterator end; + + begin = list_to_sort.begin(); + end = list_to_sort.end(); + + bool (*compareLessFunc)(MediaFile*,MediaFile*) = 0; + bool (*compareGreaterFunc)(MediaFile*,MediaFile*) = 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__); + 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_files.indexOf(m_currentItem); +} + +void PlayListModel::sortSelection(int mode) +{ + QList<MediaFile*>selected_items = getSelectedItems(); + QList<int>selected_rows = getSelectedRows(); + + doSort(mode,selected_items); + + for (int i = 0;i < selected_rows.count();i++) + m_files.replace(selected_rows[i],selected_items[i]); + + m_current = m_files.indexOf(m_currentItem); + emit listChanged(); +} + +void PlayListModel::sort(int mode) +{ + doSort(mode,m_files); + 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) +{ + + foreach(PlaylistFormat* prs,m_registered_pl_formats.values()) + { + if (prs->hasFormat(QFileInfo(f_name).completeSuffix().toLower())) + { + QFile file(f_name); + if (file.open(QIODevice::ReadOnly)) + { + clear(); + addFiles(prs->decode(QTextStream(&file).readAll())); + file.close(); + } + else + qWarning("Error opening %s",f_name.toLocal8Bit().data()); + } + } +} + +void PlayListModel::savePlaylist(const QString & f_name) +{ + foreach(PlaylistFormat* prs,m_registered_pl_formats.values()) + { + if (prs->hasFormat(QFileInfo(f_name).completeSuffix().toLower())) + { + QFile file(f_name); + if (file.open(QIODevice::WriteOnly)) + { + QTextStream ts(&file); + ts << prs->encode(m_files); + file.close(); + } + else + qWarning("Error opening %s",f_name.toLocal8Bit().data()); + } + } +} + + +void PlayListModel::loadExternalPlaylistFormats() +{ + QDir pluginsDir (QDir::homePath()+"/.qmmp/plugins/PlaylistFormats"); + //pluginsDir.cdUp(); + //pluginsDir.cd("plugins/PlaylistFormats"); + foreach (QString fileName, pluginsDir.entryList(QDir::Files)) + { + QPluginLoader loader(pluginsDir.absoluteFilePath(fileName)); + QObject *plugin = loader.instance(); + if (loader.isLoaded()) + qDebug("PlaylistFormat: plugin loaded - %s", qPrintable(fileName)); + + PlaylistFormat *fmt = 0; + if (plugin) + fmt = qobject_cast<PlaylistFormat *>(plugin); + + if (fmt) + if (!registerPlaylistFormat(fmt)) + qDebug("Warning: Plugin with name %s is already registered...", + qPrintable(fmt->name())); + } +} + +bool PlayListModel::registerPlaylistFormat(PlaylistFormat* p) +{ + QString name = p->name(); + if (!m_registered_pl_formats.contains(name)) + { + m_registered_pl_formats.insert(name,p); + return true; + } + return false; +} + +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(); +} + + + + + + + + + + + + + + |
