/* smplayer, GUI front-end for mplayer. Copyright (C) 2007 Ricardo Villalba 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 "playlist.h" #include "core.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "images.h" #include "preferences.h" #include "keys.h" #include "version.h" #include "global.h" /* #define COL_POS 0 #define COL_NAME 1 #define COL_TIME 2 */ #define COL_PLAY 0 #define COL_NAME 1 #define COL_TIME 2 Playlist::Playlist(Core *c, QWidget * parent, const char* name, WFlags fl) : PlaylistBase(parent, name, fl ) { core = c; listView->setNumCols(COL_TIME + 1); listView->setReadOnly(TRUE); listView->setFocusStyle(QTable::FollowStyle); listView->setSelectionMode(QTable::MultiRow); //listView->verticalHeader()->hide(); listView->setSorting(FALSE); connect( listView, SIGNAL(doubleClicked(int,int,int,const QPoint &)), this, SLOT(itemDoubleClicked(int)) ); connect( core, SIGNAL(mediaFinished()), this, SLOT(playNext()) ); connect( core, SIGNAL(mediaLoaded()), this, SLOT(getMediaInfo()) ); add_menu = new QPopupMenu(this); add_button->setPopup( add_menu ); remove_menu = new QPopupMenu(this); remove_button->setPopup( remove_menu ); popup = new QPopupMenu(this); connect( listView, SIGNAL(contextMenuRequested(int,int,const QPoint &)), this, SLOT(showPopup(int,int,const QPoint &)) ); prev_button->setAccel( key_list->find("PLAYLIST_PREV") ); next_button->setAccel( key_list->find("PLAYLIST_NEXT") ); playlist_path = ""; latest_dir = ""; clear(); languageChange(); setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding ); adjustSize(); setAcceptDrops(TRUE); // Random seed QTime t; t.start(); srand( t.hour() * 3600 + t.minute() * 60 + t.second() ); loadSettings(); } Playlist::~Playlist() { saveSettings(); } void Playlist::languageChange() { PlaylistBase::languageChange(); //listView->horizontalHeader()->setLabel( COL_POS, tr( "#" ) ); listView->horizontalHeader()->setLabel( COL_PLAY, " " ); listView->horizontalHeader()->setLabel( COL_NAME, tr("Name") ); listView->horizontalHeader()->setLabel( COL_TIME, tr("Length") ); // Icons load_button->setText(""); save_button->setText(""); add_button->setText(""); remove_button->setText(""); prev_button->setText(""); next_button->setText(""); repeat_button->setText(""); shuffle_button->setText(""); up_button->setText(""); down_button->setText(""); play_button->setText(""); setIcon( Images::icon("logo", 64) ); load_button->setPixmap( Images::icon("open") ); save_button->setPixmap( Images::icon("save") ); add_button->setPixmap( Images::icon("plus") ); remove_button->setPixmap( Images::icon("minus") ); prev_button->setPixmap( Images::icon("previous") ); next_button->setPixmap( Images::icon("next") ); repeat_button->setPixmap( Images::icon("repeat") ); shuffle_button->setPixmap( Images::icon("shuffle") ); up_button->setPixmap( Images::icon("up") ); down_button->setPixmap( Images::icon("down") ); play_button->setPixmap( Images::icon("play") ); // Menus add_menu->clear(); add_menu->insertItem( tr("&Current file"), this, SLOT(addCurrentFile()) ); add_menu->insertItem( tr("&File(s)"), this, SLOT(addFiles()) ); add_menu->insertItem( tr("&Directory"), this, SLOT(addDirectory()) ); remove_menu->clear(); remove_menu->insertItem( tr("&Selected"), this, SLOT(removeSelected()) ); remove_menu->insertItem( tr("&All"), this, SLOT(removeAll()) ); popup->clear(); popup->insertItem( Images::icon("play"), tr("&Play"), this, SLOT(playCurrent()) ); popup->insertItem( Images::icon("delete"), tr("&Remove selected"), this, SLOT(removeSelected()) ); popup->insertItem( Images::icon("edit"), tr("&Edit"), this, SLOT(editCurrentItem()) ); } void Playlist::list() { qDebug("Playlist::list"); PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { qDebug( "filename: '%s', name: '%s' duration: %f", (*it).filename().utf8().data(), (*it).name().utf8().data(), (*it).duration() ); } } void Playlist::updateView() { listView->setNumRows( pl.count() ); int n=0; QString number; QString name; QString time; PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { number.sprintf("%03d", n +1 ); name = (*it).name(); if (name.isEmpty()) name = (*it).filename(); time = Helper::formatTime( (int) (*it).duration() ); //listView->setText(n, COL_POS, number); listView->setText(n, COL_NAME, name); listView->setText(n, COL_TIME, time); if ((*it).played()) { listView->setPixmap(n, COL_PLAY, Images::icon("ok_small") ); } else { listView->setPixmap(n, COL_PLAY, QPixmap() ); } n++; } for (n=0; n <= COL_TIME; n++) listView->adjustColumn(n); setCurrentItem(current_item); //adjustSize(); } void Playlist::setCurrentItem(int current) { int old_current = current_item; current_item = current; if ( (old_current >= 0) && (old_current < listView->numRows()) ) { listView->setPixmap(old_current, COL_PLAY, QPixmap() ); } if ( (current_item >= 0) && (current_item < listView->numRows()) ) { listView->setPixmap(current_item, COL_PLAY, Images::icon("play") ); } } void Playlist::clear() { pl.clear(); listView->setNumRows(0); setCurrentItem(0); } void Playlist::addItem(QString filename, QString name, double duration) { qDebug("Playlist::addItem: '%s'", filename.utf8().data()); #ifdef Q_OS_WIN filename = Helper::changeSlashes(filename); #endif // Test if already is in the list bool exists = FALSE; PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { if ( (*it).filename() == filename ) { exists = TRUE; break; } } if (!exists) { if (name.isEmpty()) { QFileInfo fi(filename); if (fi.exists()) { // Local file name = fi.fileName(); //fi.baseName(TRUE); } else { // Stream name = filename; } } pl.append( PlaylistItem(filename, name, duration) ); } else { qDebug(" Not added. File already in the list"); } } void Playlist::load_m3u(QString file) { qDebug("Playlist::load_m3u"); bool utf8 = (QFileInfo(file).extension(FALSE).lower() == "m3u8"); QRegExp m3u_id("^#EXTM3U|^#M3U"); QRegExp info("^#EXTINF:(.*),(.*)"); QFile f( file ); if ( f.open( IO_ReadOnly ) ) { playlist_path = QFileInfo(file).dirPath(); clear(); QString filename=""; QString name=""; double duration=0; QTextStream stream( &f ); if (utf8) stream.setEncoding(QTextStream::UnicodeUTF8); else stream.setEncoding(QTextStream::Locale); QString line; while ( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' qDebug( " * line: '%s'", line.utf8().data() ); if (m3u_id.search(line)!=-1) { //#EXTM3U // Ignore line } else if (info.search(line)!=-1) { duration = info.cap(1).toDouble(); name = info.cap(2); qDebug(" * name: '%s', duration: %f", name.utf8().data(), duration ); } else if (line.startsWith("#")) { // Comment // Ignore } else { filename = line; QFileInfo fi(filename); if (fi.exists()) { filename = fi.absFilePath(); } if (!fi.exists()) { if (QFileInfo( playlist_path + "/" + filename).exists() ) { filename = playlist_path + "/" + filename; } } addItem( filename, name, duration ); name=""; duration = 0; } } f.close(); list(); updateView(); startPlay(); } } void Playlist::save_m3u(QString file) { qDebug("Playlist::save_m3u: '%s'", file.utf8().data()); QString dir_path = QFileInfo(file).dirPath(); if (!dir_path.endsWith("/")) dir_path += "/"; #ifdef Q_OS_WIN dir_path = Helper::changeSlashes(dir_path); #endif qDebug(" * dirPath: '%s'", dir_path.utf8().data()); bool utf8 = (QFileInfo(file).extension(FALSE).lower() == "m3u8"); QFile f( file ); if ( f.open( IO_WriteOnly ) ) { QTextStream stream( &f ); if (utf8) stream.setEncoding(QTextStream::UnicodeUTF8); else stream.setEncoding(QTextStream::Locale); QString filename; stream << "#EXTM3U" << "\n"; stream << "# Playlist created by SMPlayer " << VERSION << " \n"; PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { filename = (*it).filename(); #ifdef Q_OS_WIN filename = Helper::changeSlashes(filename); #endif stream << "#EXTINF:"; stream << (*it).duration() << ","; stream << (*it).name() << "\n"; // Try to save the filename as relative instead of absolute if (filename.startsWith( dir_path )) { filename = filename.mid( dir_path.length() ); } stream << filename << "\n"; } f.close(); } } void Playlist::load() { QString s = QFileDialog::getOpenFileName( lastDir(), tr("Playlists") +" (*.m3u *.m3u8)", this, "open file dialog", tr("Choose a file") ); if (!s.isEmpty()) { latest_dir = QFileInfo(s).dirPath(TRUE); load_m3u(s); } } void Playlist::save() { QString s = QFileDialog::getSaveFileName( lastDir(), tr("Playlists") +" (*.m3u *.m3u8)", this, "save file dialog", tr("Choose a filename") ); if (!s.isEmpty()) { // If filename has no extension, add it if (QFileInfo(s).extension().isEmpty()) { s = s + ".m3u"; } if (QFileInfo(s).exists()) { int res = QMessageBox::question( this, tr("Confirm overwrite?"), tr("The file %1 already exists.\n" "Do you want to overwrite?").arg(s), QMessageBox::Yes, QMessageBox::No, QMessageBox::NoButton); if (res == QMessageBox::No ) { return; } } latest_dir = QFileInfo(s).dirPath(TRUE); save_m3u(s); } } void Playlist::playCurrent() { int current = listView->currentRow(); if (current > -1) { playItem(current); } } void Playlist::itemDoubleClicked(int row) { qDebug("Playlist::itemDoubleClicked: row: %d", row ); playItem(row); } void Playlist::showPopup(int row, int col, const QPoint & pos) { qDebug("Playlist::showPopup: row: %d col: %d", row, col ); if (!popup->isVisible()) { popup->move( pos ); popup->show(); } } void Playlist::startPlay() { // Start to play if ( shuffle_button->isOn() ) playItem( chooseRandomItem() ); else playItem(0); } void Playlist::playItem( int n ) { qDebug("Playlist::playItem: %d (count:%d)", n, pl.count()); if ( (n >= pl.count()) || (n < 0) ) { qDebug(" out of range"); emit playlistEnded(); return; } qDebug(" playlist_path: '%s'", playlist_path.utf8().data() ); QString filename = pl[n].filename(); QString filename_with_path = playlist_path + "/" + filename; if (!filename.isEmpty()) { pl[n].setPlayed(TRUE); setCurrentItem(n); /* if (QFileInfo( filename ).exists()) { core->openFile( filename, 0 ); } else { core->openStream( filename ); } */ core->open(filename, 0); } } void Playlist::playNext() { qDebug("Playlist::playNext"); if (shuffle_button->isOn()) { // Shuffle int chosen_item = chooseRandomItem(); if (chosen_item == -1) { clearPlayedTag(); if (repeat_button->isOn()) chosen_item = chooseRandomItem(); } playItem( chosen_item ); } else { bool finished_list = (current_item+1 >= pl.count()); if (finished_list) clearPlayedTag(); if ( (repeat_button->isOn()) && (finished_list) ) { playItem(0); } else { playItem( current_item+1 ); } } } void Playlist::playPrev() { qDebug("Playlist::playPrev"); playItem( current_item-1 ); } void Playlist::getMediaInfo() { qDebug("Playlist:: getMediaInfo"); QString filename = core->mdat.filename; double duration = core->mdat.duration; QString name = core->mdat.clip_name; QString artist = core->mdat.clip_artist; #ifdef Q_OS_WIN filename = Helper::changeSlashes(filename); #endif if (name.isEmpty()) { QFileInfo fi(filename); if (fi.exists()) { // Local file name = fi.fileName(); } else { // Stream name = filename; } } if (!artist.isEmpty()) name = artist + " - " + name; int pos=0; PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { if ( (*it).filename() == filename ) { if ((*it).duration()<1) { if (!name.isEmpty()) { (*it).setName(name); } (*it).setDuration(duration); } else // Edited name (sets duration to 1) if ((*it).duration()==1) { (*it).setDuration(duration); } setCurrentItem(pos); } pos++; } updateView(); } // Add current file to playlist void Playlist::addCurrentFile() { qDebug("Playlist::addCurrentFile"); if (!core->mdat.filename.isEmpty()) { addItem( core->mdat.filename, "", 0 ); getMediaInfo(); } } void Playlist::addFiles() { QStringList files = QFileDialog::getOpenFileNames( tr("All files") +" (*.*)", lastDir(), this, "open files dialog", tr("Select one or more files to open") ); if (files.count()!=0) addFiles(files); } void Playlist::addFiles(QStringList files) { qDebug("Playlist::addFiles"); QStringList::Iterator it = files.begin(); while( it != files.end() ) { addItem( (*it), "", 0 ); latest_dir = QFileInfo((*it)).dirPath(TRUE); ++it; } updateView(); qDebug( " * latest_dir: '%s'", latest_dir.utf8().data() ); } void Playlist::addDirectory() { QString s = QFileDialog::getExistingDirectory( lastDir(), this, "get existing directory", tr("Choose a directory"), TRUE ); if (!s.isEmpty()) { addDirectory(s); latest_dir = s; } } void Playlist::addDirectory(QString dir) { QStringList dir_list = QDir(dir).entryList(); QString filename; QStringList::Iterator it = dir_list.begin(); while( it != dir_list.end() ) { filename = dir; if (filename.right(1)!="/") filename += "/"; filename += (*it); if (!QFileInfo(filename).isDir()) { addItem( filename, "", 0 ); } ++it; } updateView(); } // Remove selected items void Playlist::removeSelected() { qDebug("Playlist::removeSelected"); /* QStringList items_to_remove; for (int n=0; n < listView->numRows(); n++) { if (listView->isRowSelected(n)) { qDebug(" row %d selected", n); // Cannot remove directy because order changes! //pl.erase( pl.at(n) ); items_to_remove.append( (*pl.at(n)).filename() ); } } PlaylistItemList::iterator it; QStringList::Iterator str_it; for ( it = pl.begin(); it != pl.end(); ++it ) { for (str_it=items_to_remove.begin(); str_it!=items_to_remove.end(); ++str_it) { if ((*it).filename() == (*str_it)) { qDebug("Remove %s", (*it).filename().utf8().data()); it = pl.remove(it); } } } */ for (int n=0; n < listView->numRows(); n++) { if (listView->isRowSelected(n)) { qDebug(" row %d selected", n); pl[n].setMarkForDeletion(TRUE); } } PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { if ( (*it).markedForDeletion() ) { qDebug("Remove '%s'", (*it).filename().utf8().data()); it = pl.remove(it); it--; } } updateView(); } void Playlist::removeAll() { pl.clear(); updateView(); } void Playlist::clearPlayedTag() { PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { (*it).setPlayed(FALSE); } updateView(); } int Playlist::chooseRandomItem() { qDebug( "Playlist::chooseRandomItem"); QValueList fi; //List of not played items (free items) int n=0; PlaylistItemList::iterator it; for ( it = pl.begin(); it != pl.end(); ++it ) { if (! (*it).played() ) fi.append(n); n++; } qDebug(" * free items: %d", fi.count() ); if (fi.count()==0) return -1; // none free qDebug(" * items: "); for (int i=0; i < fi.count(); i++) { qDebug(" * item: %d", fi[i]); } int selected = (int) ((double) fi.count() * rand()/(RAND_MAX+1.0)); qDebug(" * selected item: %d (%d)", selected, fi[selected]); return fi[selected]; } void Playlist::swapItems(int item1, int item2 ) { PlaylistItem it1 = pl[item1]; pl[item1] = pl[item2]; pl[item2] = it1; } void Playlist::upItem() { qDebug("Playlist::upItem"); int current = listView->currentRow(); qDebug(" currentRow: %d", current ); if (current >= 1) { swapItems( current, current-1 ); if (current_item == (current-1)) current_item = current; else if (current_item == current) current_item = current-1; updateView(); listView->clearSelection(); listView->setCurrentCell( current-1, 0); } } void Playlist::downItem() { qDebug("Playlist::downItem"); int current = listView->currentRow(); qDebug(" currentRow: %d", current ); if ( (current > -1) && (current < (pl.count()-1)) ) { swapItems( current, current+1 ); if (current_item == (current+1)) current_item = current; else if (current_item == current) current_item = current+1; updateView(); listView->clearSelection(); listView->setCurrentCell( current+1, 0); } } void Playlist::editCurrentItem() { int current = listView->currentRow(); if (current > -1) editItem(current); } void Playlist::editItem(int item) { QString current_name = pl[item].name(); if (current_name.isEmpty()) current_name = pl[item].filename(); bool ok; QString text = QInputDialog::getText( tr("Edit name"), tr("Type the name that will be displayed in the playlist for this file:"), QLineEdit::Normal, current_name, &ok, this ); if ( ok && !text.isEmpty() ) { // user entered something and pressed OK pl[item].setName(text); // If duration == 0 the name will be overwritten! if (pl[item].duration()<1) pl[item].setDuration(1); updateView(); } } // Drag&drop void Playlist::dragEnterEvent( QDragEnterEvent *e ) { qDebug("Playlist::dragEnterEvent"); #if QT_VERSION < 0x040000 e->accept( (QUriDrag::canDecode(e) || QTextDrag::canDecode(e)) ); #else e->accept(QUriDrag::canDecode(e)); #endif } void Playlist::dropEvent( QDropEvent *e ) { qDebug("Playlist::dropEvent"); QStringList files; if (QUriDrag::canDecode(e)) { QStrList l; QUriDrag::decode(e, l); QString s; for ( unsigned int i= 0; i < l.count(); ++i ) { s = l.at(i); qDebug(" * '%s'", s.utf8().data() ); QUrl u(s); qDebug(" * protocol: '%s'", u.protocol().utf8().data()); qDebug(" * path: '%s'", u.path().utf8().data()); //qDebug(" filename:='%s'", u.fileName().utf8().data()); if (u.protocol()=="file") { s = u.path(); } files.append( s ); } } #if QT_VERSION < 0x040000 else if (QTextDrag::canDecode(e)) { QString s; if (QTextDrag::decode(e, s)) { qDebug(" * '%s'", s.utf8().data() ); files.append( s ); } } #endif addFiles(files); } void Playlist::hideEvent( QHideEvent * ) { emit visibilityChanged(); } void Playlist::showEvent( QShowEvent * ) { emit visibilityChanged(); } void Playlist::closeEvent( QCloseEvent * e ) { saveSettings(); e->accept(); } void Playlist::saveSettings() { qDebug("Playlist::saveSettings"); QSettings * set = settings; set->beginGroup( "playlist"); set->writeEntry( "repeat", repeat_button->isOn() ); set->writeEntry( "shuffle", shuffle_button->isOn() ); set->writeEntry( "window_width", size().width() ); set->writeEntry( "window_height", size().height() ); set->writeEntry( "latest_dir", latest_dir ); set->endGroup(); } void Playlist::loadSettings() { qDebug("Playlist::loadSettings"); QSettings * set = settings; set->beginGroup( "playlist"); repeat_button->setOn( set->readBoolEntry( "repeat", repeat_button->isOn() ) ); shuffle_button->setOn( set->readBoolEntry( "shuffle", shuffle_button->isOn() ) ); QSize s; s.setWidth( set->readNumEntry( "window_width", size().width() ) ); s.setHeight( set->readNumEntry( "window_height", size().height() ) ); resize( s ); latest_dir = set->readEntry( "latest_dir", latest_dir ); set->endGroup(); } QString Playlist::lastDir() { QString last_dir = latest_dir; if (last_dir.isEmpty()) last_dir = pref->latest_dir; return last_dir; }