1 /*  smplayer, GUI front-end for mplayer.
2     Copyright (C) 2006-2021 Ricardo Villalba <ricardo@smplayer.info>
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 
19 #include "playlist.h"
20 
21 #include <QTableView>
22 #include <QStandardItemModel>
23 #include <QSortFilterProxyModel>
24 #include <QStyledItemDelegate>
25 #include <QToolBar>
26 #include <QFile>
27 #include <QTextStream>
28 #include <QDir>
29 #include <QFileInfo>
30 #include <QMessageBox>
31 #include <QPushButton>
32 #include <QMenu>
33 #include <QDateTime>
34 #include <QSettings>
35 #include <QInputDialog>
36 #include <QToolButton>
37 #include <QTimer>
38 #include <QVBoxLayout>
39 #include <QUrl>
40 #include <QDragEnterEvent>
41 #include <QDropEvent>
42 #include <QHeaderView>
43 #include <QTextCodec>
44 #include <QApplication>
45 #include <QMimeData>
46 #include <QDomDocument>
47 #include <QDesktopServices>
48 #include <QClipboard>
49 #include <QDebug>
50 
51 #if QT_VERSION >= 0x050000
52 #include <QUrlQuery>
53 #include "myscroller.h"
54 #endif
55 
56 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
57 #include <QRandomGenerator>
58 #endif
59 
60 #include "myaction.h"
61 #include "mylineedit.h"
62 #include "filedialog.h"
63 #include "helper.h"
64 #include "images.h"
65 #include "preferences.h"
66 #include "multilineinputdialog.h"
67 #include "version.h"
68 #include "extensions.h"
69 #include "guiconfig.h"
70 
71 #ifdef CHROMECAST_SUPPORT
72 #include "chromecast.h"
73 #endif
74 
75 #ifdef PLAYLIST_DOWNLOAD
76 #include "inputurl.h"
77 #include "youtube/loadpage.h"
78 #include "urlhistory.h"
79 #include <QNetworkAccessManager>
80 #include <QTemporaryFile>
81 #include <QMovie>
82 #endif
83 
84 #if defined(YT_PLAYLIST_SUPPORT) && QT_VERSION >= 0x050000
85 #include <QUrlQuery>
86 #endif
87 
88 #if USE_INFOPROVIDER
89 #include "infoprovider.h"
90 #endif
91 
92 #define DRAG_ITEMS 0
93 #define PL_ALLOW_DUPLICATES 1
94 #define SIMULATE_FILE_DELETION 0
95 #define USE_ITEM_DELEGATE 0
96 
97 #define COL_NUM 0
98 #define COL_NAME 1
99 #define COL_TIME 2
100 #define COL_FILENAME 3
101 #define COL_SHUFFLE 4
102 
103 #if USE_ITEM_DELEGATE
104 class PlaylistDelegate : public QStyledItemDelegate {
105 public:
PlaylistDelegate(QObject * parent=0)106 	PlaylistDelegate(QObject * parent = 0) : QStyledItemDelegate(parent) {};
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const107 	virtual void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
108 		QStyleOptionViewItem opt = option;
109 		initStyleOption(&opt, index);
110 		if (index.column() == COL_NAME) {
111 			bool played = index.data(PLItem::Role_Played).toBool();
112 			bool current = index.data(PLItem::Role_Current).toBool();
113 			if (current) opt.font.setBold(true);
114 			else
115 			if (played) opt.font.setItalic(true);
116 		}
117 		else
118 		if (index.column() == COL_FILENAME) {
119 			opt.textElideMode = Qt::ElideMiddle;
120 		}
121 		QStyledItemDelegate::paint(painter, opt, index);
122 	}
123 };
124 #endif
125 
126 /* ----------------------------------------------------------- */
127 
128 
PLItem()129 PLItem::PLItem() : QStandardItem() {
130 	col_num = new QStandardItem();
131 	col_duration = new QStandardItem();
132 	col_filename = new QStandardItem();
133 	col_shuffle = new QStandardItem();
134 
135 	setDuration(0);
136 	setPlayed(false);
137 	setCurrent(false);
138 
139 	col_num->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
140 }
141 
PLItem(const QString filename,const QString name,double duration)142 PLItem::PLItem(const QString filename, const QString name, double duration) : QStandardItem() {
143 	col_num = new QStandardItem();
144 	col_duration = new QStandardItem();
145 	col_filename = new QStandardItem();
146 	col_shuffle = new QStandardItem();
147 
148 	setFilename(filename);
149 	setName(name);
150 	setDuration(duration);
151 	setPlayed(false);
152 	setCurrent(false);
153 
154 	col_num->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
155 }
156 
~PLItem()157 PLItem::~PLItem() {
158 }
159 
setFilename(const QString filename)160 void PLItem::setFilename(const QString filename) {
161 	col_filename->setText(filename);
162 	col_filename->setToolTip(filename);
163 	col_filename->setData(filename);
164 
165 	if (!filename.contains("://") && filename.count() > 50) {
166 		QStringList parts = filename.split(QDir::separator());
167 		//if (!parts.isEmpty() && parts[0].isEmpty()) parts[0] = "/";
168 		//qDebug() << "PLItem::setFilename: parts count:" << parts.count() << "parts:" << parts;
169 		if (parts.count() >= 2) {
170 			QString s = parts[parts.count()-2] + QDir::separator() + parts[parts.count()-1];
171 			if (parts.count() > 2) s = QString("...") + QDir::separator() + s;
172 			col_filename->setText(s);
173 		}
174 	}
175 }
176 
setName(const QString name)177 void PLItem::setName(const QString name) {
178 	setText(name);
179 	setData(name);
180 	setToolTip(name);
181 }
182 
setDuration(double duration)183 void PLItem::setDuration(double duration) {
184 	col_duration->setData(duration);
185 	col_duration->setText(Helper::formatTime(duration));
186 }
187 
setPlayed(bool played)188 void PLItem::setPlayed(bool played) {
189 	setData(played, Role_Played);
190 #if !USE_ITEM_DELEGATE
191 	QFont f = font();
192 	f.setItalic(played);
193 	setFont(f);
194 #endif
195 }
196 
setPosition(int position)197 void PLItem::setPosition(int position) {
198 	//col_num->setText(QString("%1").arg(position, 4, 10, QChar('0')));
199 	col_num->setText(QString::number(position));
200 	col_num->setData(position);
201 }
202 
setShufflePosition(int position)203 void PLItem::setShufflePosition(int position) {
204 	col_shuffle->setText(QString::number(position));
205 	col_shuffle->setData(position);
206 }
207 
setCurrent(bool b)208 void PLItem::setCurrent(bool b) {
209 	setData(b, Role_Current);
210 #if !USE_ITEM_DELEGATE
211 	QFont f = font();
212 	f.setBold(b);
213 	f.setItalic(b ? false : played());
214 	setFont(f);
215 #endif
216 }
217 
filename()218 QString PLItem::filename() {
219 	return col_filename->data().toString();
220 }
221 
name()222 QString PLItem::name() {
223 	return text();
224 }
225 
duration()226 double PLItem::duration() {
227 	return col_duration->data().toDouble();
228 }
229 
played()230 bool PLItem::played() {
231 	return data(Role_Played).toBool();
232 }
233 
position()234 int PLItem::position() {
235 	return col_num->data().toInt();
236 }
237 
shufflePosition()238 int PLItem::shufflePosition() {
239 	return col_shuffle->data().toInt();
240 }
241 
isCurrent()242 bool PLItem::isCurrent() {
243 	return data(Role_Current).toBool();
244 }
245 
items()246 QList<QStandardItem *> PLItem::items() {
247 	QList<QStandardItem *> l;
248 	l << col_num << this << col_duration << col_filename << col_shuffle;
249 	return l;
250 }
251 
setExtraParams(const QStringList & pars)252 void PLItem::setExtraParams(const QStringList & pars) {
253 	setData(pars, Role_Params);
254 }
255 
extraParams()256 QStringList PLItem::extraParams() {
257 	return data(Role_Params).toStringList();
258 }
259 
setVideoURL(const QString & url)260 void PLItem::setVideoURL(const QString & url) {
261 	setData(url, Role_Video_URL);
262 }
263 
videoURL()264 QString PLItem::videoURL() {
265 	return data(Role_Video_URL).toString();
266 }
267 
setIconURL(const QString & url)268 void PLItem::setIconURL(const QString & url) {
269 	setData(url, Role_Icon_URL);
270 }
271 
iconURL()272 QString PLItem::iconURL() {
273 	return data(Role_Icon_URL).toString();
274 }
275 
276 /* ----------------------------------------------------------- */
277 
278 
Playlist(QWidget * parent,Qt::WindowFlags f)279 Playlist::Playlist(QWidget * parent, Qt::WindowFlags f)
280 	: QWidget(parent,f)
281 	, set(0)
282 	, modified(false)
283 	, recursive_add_directory(false)
284 	, automatically_get_info(false)
285 	, save_playlist_in_config(true)
286 	, play_files_from_start(true)
287 	, row_spacing(-1) // Default height
288 	, start_play_on_load(true)
289 	, automatically_play_next(true)
290 	, ignore_player_errors(false)
291 	, change_name(true)
292 	, save_dirs(true)
293 #ifdef PLAYLIST_DELETE_FROM_DISK
294 	, allow_delete_from_disk(false)
295 #endif
296 {
297 	playlist_path = "";
298 	latest_dir = "";
299 
300 	filter_edit = new MyLineEdit(this);
301 	connect(filter_edit, SIGNAL(textChanged(const QString &)), this, SLOT(filterEditChanged(const QString &)));
302 
303 	createTable();
304 	createActions();
305 	createToolbar();
306 
307 	QVBoxLayout *layout = new QVBoxLayout;
308 #ifdef PLAYLIST_DOUBLE_TOOLBAR
309 	layout->addWidget(toolbar);
310 	layout->addWidget(listView);
311 	layout->addWidget(toolbar2);
312 #else
313 	layout->addWidget(listView);
314 	layout->addWidget(toolbar);
315 	layout->addWidget(filter_edit);
316 	filter_edit->hide();
317 #endif
318 	setLayout(layout);
319 
320 	clear();
321 
322 	retranslateStrings();
323 
324 	// FIXME: check if this is really necessary
325 	if (isWindow()) { // No dockable
326 		setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Expanding );
327 		adjustSize();
328 	} else {
329 		//setSizePolicy( QSizePolicy::Maximum, QSizePolicy::Expanding );
330 		//setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
331 		setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
332 	}
333 
334 	setAcceptDrops(true);
335 	setAttribute(Qt::WA_NoMousePropagation);
336 
337 #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
338 	// Random seed
339 	QTime now = QTime::currentTime();
340 	qsrand(now.msec());
341 #endif
342 
343 	//loadSettings();
344 
345 	// Save config every 5 minutes.
346 	save_timer = new QTimer(this);
347 	connect( save_timer, SIGNAL(timeout()), this, SLOT(maybeSaveSettings()) );
348 	save_timer->start( 5 * 60000 );
349 
350 #ifdef PLAYLIST_DOWNLOAD
351 	downloader = new LoadPage(new QNetworkAccessManager(this), this);
352 	downloader->setUserAgent("SMPlayer");
353 	connect(downloader, SIGNAL(pageLoaded(QByteArray)), this, SLOT(playlistDownloaded(QByteArray)));
354 	connect(downloader, SIGNAL(errorOcurred(int, QString)), this, SLOT(errorOcurred(int, QString)));
355 
356 	history_urls = new URLHistory;
357 	history_urls->addUrl("http://smplayer.info/sample.m3u8");
358 #endif
359 
360 #ifdef DELAYED_PLAY
361 	last_timestamp = 0;
362 	play_later.seek = -1;
363 	play_later_timer = new QTimer(this);
364 	play_later_timer->setInterval(1000);
365 	play_later_timer->setSingleShot(true);
366 	connect(play_later_timer, SIGNAL(timeout()), this, SLOT(playItemLater()));
367 #endif
368 }
369 
~Playlist()370 Playlist::~Playlist() {
371 	saveSettings();
372 	if (set) delete set;
373 
374 #ifdef PLAYLIST_DOWNLOAD
375 	delete history_urls;
376 #endif
377 }
378 
setConfigPath(const QString & config_path)379 void Playlist::setConfigPath(const QString & config_path) {
380 	qDebug() << "Playlist::setConfigPath:" << config_path;
381 
382 	if (set) {
383 		delete set;
384 		set = 0;
385 	}
386 
387 	if (!config_path.isEmpty()) {
388 		QString inifile = config_path + "/playlist.ini";
389 		qDebug() << "Playlist::setConfigPath: ini file:" << inifile;
390 		set = new QSettings(inifile, QSettings::IniFormat);
391 		loadSettings();
392 	}
393 }
394 
updateWindowTitle()395 void Playlist::updateWindowTitle() {
396 	QString title;
397 
398 	title = playlist_filename;
399 	if (title.isEmpty()) title = tr("Untitled playlist");
400 	if (modified) title += " (*)";
401 
402 	qDebug() << "Playlist::updateWindowTitle:" << title;
403 
404 	setWindowTitle(title);
405 	emit windowTitleChanged(title);
406 }
407 
setPlaylistFilename(const QString & f)408 void Playlist::setPlaylistFilename(const QString & f) {
409 	playlist_filename = f;
410 	updateWindowTitle();
411 }
412 
setModified(bool mod)413 void Playlist::setModified(bool mod) {
414 	qDebug("Playlist::setModified: %d", mod);
415 
416 	modified = mod;
417 	emit modifiedChanged(modified);
418 	updateWindowTitle();
419 }
420 
createTable()421 void Playlist::createTable() {
422 	table = new QStandardItemModel(this);
423 	table->setColumnCount(COL_SHUFFLE + 1);
424 	//table->setSortRole(Qt::UserRole + 1);
425 
426 	proxy = new QSortFilterProxyModel(this);
427 	proxy->setSourceModel(table);
428 	proxy->setSortRole(Qt::UserRole + 1);
429 	proxy->setFilterRole(Qt::UserRole + 1);
430 	proxy->setFilterKeyColumn(-1); // All columns
431 
432 #if USE_ITEM_DELEGATE
433 	PlaylistDelegate * pl_delegate = new PlaylistDelegate(this);
434 #endif
435 
436 	listView = new QTableView(this);
437 	listView->setModel(proxy);
438 
439 #if USE_ITEM_DELEGATE
440 	listView->setItemDelegateForColumn(COL_NAME, pl_delegate);
441 	//listView->setItemDelegateForColumn(COL_FILENAME, pl_delegate);
442 #endif
443 
444 	listView->setObjectName("playlist_table");
445 	listView->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
446 	listView->setSelectionBehavior(QAbstractItemView::SelectRows);
447 	listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
448 	listView->setContextMenuPolicy( Qt::CustomContextMenu );
449 	listView->setShowGrid(false);
450 	listView->setSortingEnabled(true);
451 	listView->setWordWrap(false);
452 #if !USE_ITEM_DELEGATE
453 	listView->setAlternatingRowColors(true);
454 #endif
455 	listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
456 
457 	listView->verticalHeader()->hide();
458 
459 #if QT_VERSION >= 0x050000
460 	MyScroller::setScroller(listView->viewport());
461 
462 	listView->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
463 //	listView->horizontalHeader()->setSectionResizeMode(COL_NAME, QHeaderView::Stretch);
464 #else
465 	listView->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
466 //	listView->horizontalHeader()->setResizeMode(COL_NAME, QHeaderView::Stretch);
467 //	listView->horizontalHeader()->setResizeMode(COL_FILENAME, QHeaderView::Interactive);
468 #endif
469 	listView->horizontalHeader()->setStretchLastSection(true);
470 	listView->horizontalHeader()->setSortIndicator(0, Qt::AscendingOrder);
471 
472 	/*
473 	listView->horizontalHeader()->setResizeMode(COL_TIME, QHeaderView::ResizeToContents);
474 	listView->horizontalHeader()->setResizeMode(COL_PLAY, QHeaderView::ResizeToContents);
475 	*/
476 	//listView->setIconSize( Images::icon("ok").size() );
477 
478 #if DRAG_ITEMS
479 	listView->setSelectionMode(QAbstractItemView::SingleSelection);
480 	listView->setDragEnabled(true);
481 	listView->setAcceptDrops(true);
482 	listView->setDropIndicatorShown(true);
483 	listView->setDragDropMode(QAbstractItemView::InternalMove);
484 #endif
485 
486 	connect(listView, SIGNAL(activated(const QModelIndex &)),
487             this, SLOT(itemActivated(const QModelIndex &)) );
488 
489 	setFilenameColumnVisible(false);
490 	setShuffleColumnVisible(false);
491 }
492 
createActions()493 void Playlist::createActions() {
494 	openAct = new MyAction(this, "pl_open", false);
495 	connect( openAct, SIGNAL(triggered()), this, SLOT(load()) );
496 
497 #ifdef PLAYLIST_DOWNLOAD
498 	openUrlAct = new MyAction(this, "pl_open_url", false);
499 	connect( openUrlAct, SIGNAL(triggered()), this, SLOT(openUrl()) );
500 #endif
501 
502 	saveAct = new MyAction(this, "pl_save", false);
503 	connect( saveAct, SIGNAL(triggered()), this, SLOT(saveCurrentPlaylist()) );
504 
505 	saveAsAct = new MyAction(this, "pl_save_as", false);
506 	connect( saveAsAct, SIGNAL(triggered()), this, SLOT(save()) );
507 
508 	playAct = new MyAction(this, "pl_play", false);
509 	connect( playAct, SIGNAL(triggered()), this, SLOT(playCurrent()) );
510 
511 	nextAct = new MyAction(Qt::Key_N /*Qt::Key_Greater*/, this, "pl_next", false);
512 	connect( nextAct, SIGNAL(triggered()), this, SLOT(playNext()) );
513 
514 	prevAct = new MyAction(Qt::Key_P /*Qt::Key_Less*/, this, "pl_prev", false);
515 	connect( prevAct, SIGNAL(triggered()), this, SLOT(playPrev()) );
516 
517 	moveUpAct = new MyAction(this, "pl_move_up", false);
518 	connect( moveUpAct, SIGNAL(triggered()), this, SLOT(upItem()) );
519 
520 	moveDownAct = new MyAction(this, "pl_move_down", false);
521 	connect( moveDownAct, SIGNAL(triggered()), this, SLOT(downItem()) );
522 
523 	repeatAct = new MyAction(this, "pl_repeat", false);
524 	repeatAct->setCheckable(true);
525 
526 	shuffleAct = new MyAction(this, "pl_shuffle", false);
527 	shuffleAct->setCheckable(true);
528 	connect( shuffleAct, SIGNAL(toggled(bool)), this, SLOT(shuffle(bool)) );
529 
530 	// Add actions
531 	addCurrentAct = new MyAction(this, "pl_add_current", false);
532 	connect( addCurrentAct, SIGNAL(triggered()), this, SLOT(addCurrentFile()) );
533 
534 	addFilesAct = new MyAction(this, "pl_add_files", false);
535 	connect( addFilesAct, SIGNAL(triggered()), this, SLOT(addFiles()) );
536 
537 	addDirectoryAct = new MyAction(this, "pl_add_directory", false);
538 	connect( addDirectoryAct, SIGNAL(triggered()), this, SLOT(addDirectory()) );
539 
540 	addUrlsAct = new MyAction(this, "pl_add_urls", false);
541 	connect( addUrlsAct, SIGNAL(triggered()), this, SLOT(addUrls()) );
542 
543 	// Remove actions
544 	removeSelectedAct = new MyAction(this, "pl_remove_selected", false);
545 	connect( removeSelectedAct, SIGNAL(triggered()), this, SLOT(removeSelected()) );
546 
547 	removeAllAct = new MyAction(this, "pl_remove_all", false);
548 	connect( removeAllAct, SIGNAL(triggered()), this, SLOT(removeAll()) );
549 
550 	// Edit
551 	editAct = new MyAction(this, "pl_edit", false);
552 	connect( editAct, SIGNAL(triggered()), this, SLOT(editCurrentItem()) );
553 
554 #ifdef PLAYLIST_DELETE_FROM_DISK
555 	deleteSelectedFileFromDiskAct = new MyAction(this, "pl_delete_from_disk");
556 	connect( deleteSelectedFileFromDiskAct, SIGNAL(triggered()), this, SLOT(deleteSelectedFileFromDisk()));
557 #endif
558 
559 	copyURLAct = new MyAction(this, "pl_copy_url");
560 	connect( copyURLAct, SIGNAL(triggered()), this, SLOT(copyURL()));
561 
562 	openFolderAct = new MyAction(this, "pl_open_folder");
563 	connect( openFolderAct, SIGNAL(triggered()), this, SLOT(openFolder()));
564 
565 #ifdef CHROMECAST_SUPPORT
566 	playOnChromecastAct = new MyAction(this, "pl_chromecast");
567 	connect( playOnChromecastAct, SIGNAL(triggered()), this, SLOT(playOnChromecast()));
568 #else
569 	openURLInWebAct = new MyAction(this, "pl_url_in_web");
570 	connect( openURLInWebAct, SIGNAL(triggered()), this, SLOT(openURLInWeb()));
571 #endif
572 
573 	showSearchAct = new MyAction(this, "pl_show_search", false);
574 	showSearchAct->setCheckable(true);
575 	connect(showSearchAct, SIGNAL(toggled(bool)), filter_edit, SLOT(setVisible(bool)));
576 
577 	showPositionColumnAct = new MyAction(this, "pl_show_position_column");
578 	showPositionColumnAct->setCheckable(true);
579 	connect(showPositionColumnAct, SIGNAL(toggled(bool)), this, SLOT(setPositionColumnVisible(bool)));
580 
581 	showNameColumnAct = new MyAction(this, "pl_show_name_column");
582 	showNameColumnAct->setCheckable(true);
583 	connect(showNameColumnAct, SIGNAL(toggled(bool)), this, SLOT(setNameColumnVisible(bool)));
584 
585 	showDurationColumnAct = new MyAction(this, "pl_show_duration_column");
586 	showDurationColumnAct->setCheckable(true);
587 	connect(showDurationColumnAct, SIGNAL(toggled(bool)), this, SLOT(setDurationColumnVisible(bool)));
588 
589 	showFilenameColumnAct = new MyAction(this, "pl_show_filename_column");
590 	showFilenameColumnAct->setCheckable(true);
591 	connect(showFilenameColumnAct, SIGNAL(toggled(bool)), this, SLOT(setFilenameColumnVisible(bool)));
592 
593 	showShuffleColumnAct = new MyAction(this, "pl_show_shuffle_column");
594 	showShuffleColumnAct->setCheckable(true);
595 	connect(showShuffleColumnAct, SIGNAL(toggled(bool)), this, SLOT(setShuffleColumnVisible(bool)));
596 }
597 
createToolbar()598 void Playlist::createToolbar() {
599 	toolbar = new QToolBar(this);
600 	toolbar->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
601 	//toolbar->setIconSize(QSize(48,48));
602 
603 #ifdef PLAYLIST_DOUBLE_TOOLBAR
604 	toolbar2 = new QToolBar(this);
605 	toolbar2->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum );
606 #endif
607 
608 /*
609 	toolbar->addAction(openAct);
610 #ifdef PLAYLIST_DOWNLOAD
611 	toolbar->addAction(openUrlAct);
612 #endif
613 	toolbar->addAction(saveAct);;
614 	toolbar->addSeparator();
615 */
616 
617 	file_menu = new QMenu(this);
618 	file_menu->addAction(openAct);
619 	file_menu->addAction(saveAct);
620 	file_menu->addAction(saveAsAct);
621 #ifdef PLAYLIST_DOWNLOAD
622 	file_menu->addAction(openUrlAct);
623 #endif
624 
625 	file_button = new QToolButton(this);
626 	file_button->setMenu(file_menu);
627 	file_button->setPopupMode(QToolButton::InstantPopup);
628 
629 	add_menu = new QMenu( this );
630 	add_menu->addAction(addCurrentAct);
631 	add_menu->addAction(addFilesAct );
632 	add_menu->addAction(addDirectoryAct);
633 	add_menu->addAction(addUrlsAct);
634 
635 	add_button = new QToolButton( this );
636 	add_button->setMenu( add_menu );
637 	add_button->setPopupMode(QToolButton::InstantPopup);
638 
639 	remove_menu = new QMenu( this );
640 	remove_menu->addAction(removeSelectedAct);
641 	remove_menu->addAction(removeAllAct);
642 
643 	remove_button = new QToolButton( this );
644 	remove_button->setMenu( remove_menu );
645 	remove_button->setPopupMode(QToolButton::InstantPopup);
646 
647 
648 #ifdef PLAYLIST_DOWNLOAD
649 	QLabel * loading_label = new QLabel(this);
650 	animation = new QMovie();
651 	animation->setFileName(Images::file("pl_loading.gif"));
652 	loading_label->setMovie(animation);
653 #endif
654 
655 	toolbar->addWidget(file_button);
656 	toolbar->addSeparator();
657 	toolbar->addWidget(add_button);
658 	toolbar->addWidget(remove_button);
659 
660 	toolbar->addSeparator();
661 	toolbar->addAction(playAct);
662 	toolbar->addAction(prevAct);
663 	toolbar->addAction(nextAct);
664 #ifdef PLAYLIST_DOUBLE_TOOLBAR
665 	toolbar2->addAction(moveUpAct);
666 	toolbar2->addAction(moveDownAct);
667 	toolbar2->addAction(repeatAct);
668 	toolbar2->addAction(shuffleAct);
669 	toolbar2->addSeparator();
670 	toolbar2->addWidget(filter_edit);
671 	#ifdef PLAYLIST_DOWNLOAD
672 	loading_label_action = toolbar2->addWidget(loading_label);
673 	#endif
674 #else
675 	toolbar->addSeparator();
676 	toolbar->addAction(repeatAct);
677 	toolbar->addAction(shuffleAct);
678 	toolbar->addSeparator();
679 	toolbar->addAction(moveUpAct);
680 	toolbar->addAction(moveDownAct);
681 	toolbar->addSeparator();
682 	toolbar->addAction(showSearchAct);
683 	// toolbar->addWidget(filter_edit);
684 	#ifdef PLAYLIST_DOWNLOAD
685 	loading_label_action = toolbar->addWidget(loading_label);
686 	#endif
687 #endif
688 
689 #ifdef PLAYLIST_DOWNLOAD
690 	loading_label_action->setVisible(false);
691 #endif
692 
693 	// Popup menu
694 	popup = new QMenu(this);
695 	popup->addAction(playAct);
696 	popup->addAction(removeSelectedAct);
697 	popup->addAction(editAct);
698 #ifdef PLAYLIST_DELETE_FROM_DISK
699 	popup->addAction(deleteSelectedFileFromDiskAct);
700 #endif
701 	popup->addAction(copyURLAct);
702 	popup->addAction(openFolderAct);
703 #ifdef CHROMECAST_SUPPORT
704 	popup->addAction(playOnChromecastAct);
705 #else
706 	popup->addAction(openURLInWebAct);
707 #endif
708 	popup->addSeparator();
709 	popup->addAction(showPositionColumnAct);
710 	popup->addAction(showNameColumnAct);
711 	popup->addAction(showDurationColumnAct);
712 	popup->addAction(showFilenameColumnAct);
713 	popup->addAction(showShuffleColumnAct);
714 
715 	connect( listView, SIGNAL(customContextMenuRequested(const QPoint &)),
716              this, SLOT(showPopup(const QPoint &)) );
717 }
718 
retranslateStrings()719 void Playlist::retranslateStrings() {
720 	table->setHorizontalHeaderLabels(QStringList() << " " << tr("Name") << tr("Length") << tr("Filename / URL") << tr("Shuffle order") );
721 
722 	openAct->change( Images::icon("open"), tr("&Load...") );
723 #ifdef PLAYLIST_DOWNLOAD
724 	openUrlAct->change( Images::icon("url"), tr("Load playlist from &URL...") );
725 	openUrlAct->setToolTip(tr("Download playlist from URL"));
726 #endif
727 	saveAct->change( Images::icon("save"), tr("&Save") );
728 	saveAsAct->change( Images::icon("save"), tr("Save &as...") );
729 
730 	playAct->change( tr("&Play") );
731 
732 	nextAct->change( tr("&Next") );
733 	prevAct->change( tr("Pre&vious") );
734 
735 	playAct->setIcon( Images::icon("play") );
736 	nextAct->setIcon( Images::icon("next") );
737 	prevAct->setIcon( Images::icon("previous") );
738 
739 	moveUpAct->change( Images::icon("up"), tr("Move &up") );
740 	moveDownAct->change( Images::icon("down"), tr("Move &down") );
741 
742 	repeatAct->change( Images::icon("repeat"), tr("&Repeat") );
743 	shuffleAct->change( Images::icon("shuffle"), tr("S&huffle") );
744 
745 	// Add actions
746 	addCurrentAct->change( tr("Add &current file") );
747 	addFilesAct->change( tr("Add &file(s)") );
748 	addDirectoryAct->change( tr("Add &directory") );
749 	addUrlsAct->change( tr("Add &URL(s)") );
750 
751 	// Remove actions
752 	removeSelectedAct->change( Images::icon("delete"), tr("Remove &selected") );
753 	removeAllAct->change( tr("Remove &all") );
754 
755 #ifdef PLAYLIST_DELETE_FROM_DISK
756 	deleteSelectedFileFromDiskAct->change( tr("&Delete file from disk") );
757 #endif
758 
759 	copyURLAct->change( Images::icon("copy"), tr("&Copy file path to clipboard") );
760 	openFolderAct->change( Images::icon("openfolder"), tr("&Open source folder") );
761 
762 #ifdef CHROMECAST_SUPPORT
763 	playOnChromecastAct->change( Images::icon("chromecast"), tr("Play on Chromec&ast") );
764 #else
765 	openURLInWebAct->change( tr("Open stream in &a web browser") );
766 #endif
767 
768 	showSearchAct->change(Images::icon("find"), tr("Search"));
769 
770 	showPositionColumnAct->change(tr("Show position column"));
771 	showNameColumnAct->change(tr("Show name column"));
772 	showDurationColumnAct->change(tr("Show length column"));
773 	showFilenameColumnAct->change(tr("Show filename column"));
774 	showShuffleColumnAct->change(tr("Show shuffle column"));
775 
776 	// Edit
777 	editAct->change( tr("&Edit") );
778 
779 	// Tool buttons
780 	file_button->setIcon(Images::icon("open")); // FIXME: change icon
781 	file_button->setToolTip(tr("Load/Save"));
782 	add_button->setIcon( Images::icon("plus") );
783 	add_button->setToolTip( tr("Add...") );
784 	remove_button->setIcon( Images::icon("minus") );
785 	remove_button->setToolTip( tr("Remove...") );
786 
787 	// Filter edit
788 #if QT_VERSION >= 0x040700
789 	filter_edit->setPlaceholderText(tr("Search"));
790 #endif
791 
792 	// Icon
793 	setWindowIcon( Images::icon("logo", 64) );
794 	//setWindowTitle( tr( "SMPlayer - Playlist" ) );
795 }
796 
list()797 void Playlist::list() {
798 	qDebug("Playlist::list");
799 
800 	for (int n = 0; n < count(); n++) {
801 		PLItem * i = itemData(n);
802 		qDebug() << "Playlist::list: filename:" << i->filename() << "name:" << i->name() << "duration:" << i->duration();
803 	}
804 }
805 
setFilter(const QString & filter)806 void Playlist::setFilter(const QString & filter) {
807 	proxy->setFilterWildcard(filter.trimmed());
808 }
809 
filterEditChanged(const QString & text)810 void Playlist::filterEditChanged(const QString & text) {
811 	qDebug() << "Playlist::filterEditChanged:" << text;
812 	setFilter(text);
813 
814 	if (text.isEmpty()) {
815 		qApp->processEvents();
816 		listView->scrollTo(listView->currentIndex(), QAbstractItemView::PositionAtCenter);
817 	}
818 }
819 
setCurrentItem(int current)820 void Playlist::setCurrentItem(int current) {
821 	QModelIndex index = proxy->index(current, 0);
822 	QModelIndex s_index = proxy->mapToSource(index);
823 
824 	//qDebug() << "Playlist::setCurrentItem: index:" << index.row() << "s_index:" << s_index.row();
825 
826 	int s_current = s_index.row();
827 
828 	PLItem * item = 0;
829 	for (int n = 0; n < count(); n++) {
830 		item = itemData(n);
831 		if (n == s_current) {
832 			item->setPlayed(true);
833 		}
834 		item->setCurrent( (n == s_current) );
835 	}
836 
837 	listView->clearSelection();
838 	listView->selectionModel()->setCurrentIndex(listView->model()->index(current, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
839 }
840 
findCurrentItem()841 int Playlist::findCurrentItem() {
842 	//qDebug("Playlist::findCurrentItem");
843 
844 	static int last_current = -1;
845 
846 	// Check if the last found current is still the current item to save time
847 	PLItem * i = itemFromProxy(last_current);
848 	if (i && i->isCurrent()) {
849 		//qDebug() << "Playlist::findCurrentItem: return last_current:" << last_current;
850 		return last_current;
851 	}
852 
853 	for (int n = 0; n < proxy->rowCount(); n++) {
854 		if (itemFromProxy(n)->isCurrent()) {
855 			last_current = n;
856 			return n;
857 		}
858 	}
859 
860 	return -1;
861 }
862 
clear()863 void Playlist::clear() {
864 	table->setRowCount(0);
865 	setCurrentItem(0);
866 	setModified(false);
867 }
868 
count()869 int Playlist::count() {
870 	return table->rowCount();
871 }
872 
isEmpty()873 bool Playlist::isEmpty() {
874 	return (table->rowCount() == 0);
875 }
876 
existsItem(int row)877 bool Playlist::existsItem(int row) {
878 	return (row > -1 && row < table->rowCount());
879 }
880 
itemData(int row)881 PLItem * Playlist::itemData(int row) {
882 	QStandardItem * i = table->item(row, COL_NAME);
883 	return static_cast<PLItem*>(i);
884 }
885 
itemFromProxy(int row)886 PLItem * Playlist::itemFromProxy(int row) {
887 	QModelIndex index = proxy->index(row, 0);
888 	QModelIndex s_index = proxy->mapToSource(index);
889 	//qDebug() << "Playlist::itemFromProxy: index is valid:" << index.isValid() << "s_index is valid:" << s_index.isValid();
890 	if (index.isValid() && s_index.isValid()) {
891 		return itemData(s_index.row());
892 	} else {
893 		return 0;
894 	}
895 }
896 
897 /*
898 void Playlist::changeItem(int row, const QString & filename, const QString name, double duration, bool played, int pos) {
899 	PLItem * i = itemData(row);
900 
901 	int position = row + 1;
902 	if (pos != -1) position = pos;
903 	i->setPosition(position);
904 
905 	i->setFilename(filename);
906 	i->setName(name);
907 	i->setDuration(duration);
908 	i->setPlayed(played);
909 }
910 */
911 
addItem(QString filename,QString name,double duration,QStringList params,QString video_url,QString icon_url,int shuffle_pos)912 void Playlist::addItem(QString filename, QString name, double duration, QStringList params, QString video_url, QString icon_url, int shuffle_pos) {
913 	//qDebug() << "Playlist::addItem:" << filename;
914 
915 	#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
916 	filename = Helper::changeSlashes(filename);
917 	#endif
918 
919 		if (name.isEmpty()) {
920 			QFileInfo fi(filename);
921 			// Let's see if it looks like a file (no dvd://1 or something)
922 			if (filename.indexOf(QRegExp("^.*://.*")) == -1) {
923 				// Local file
924 				name = fi.fileName();
925 			} else {
926 				// Stream
927 				name = filename;
928 			}
929 		}
930 
931 	PLItem * i = new PLItem(filename, name, duration);
932 	i->setExtraParams(params);
933 	i->setVideoURL(video_url);
934 	i->setIconURL(icon_url);
935 	i->setPosition(count()+1);
936 	i->setShufflePosition(shuffle_pos);
937 	table->appendRow(i->items());
938 
939 	if (findCurrentItem() == -1) setCurrentItem(0);
940 
941 /*
942 #if !PL_ALLOW_DUPLICATES
943 	// Test if already is in the list
944 	bool exists = false;
945 	for ( int n = 0; n < pl.count(); n++) {
946 		if ( pl[n].filename() == filename ) {
947 			exists = true;
948 			int last_item =  pl.count()-1;
949 			pl.move(n, last_item);
950 			qDebug("Playlist::addItem: item already in list (%d), moved to %d", n, last_item);
951 			if (current_item > -1) {
952 				if (current_item > n) current_item--;
953 				else
954 				if (current_item == n) current_item = last_item;
955 			}
956 			break;
957 		}
958 	}
959 
960 	if (!exists) {
961 #endif
962 		if (name.isEmpty()) {
963 			QFileInfo fi(filename);
964 			// Let's see if it looks like a file (no dvd://1 or something)
965 			if (filename.indexOf(QRegExp("^.*://.*")) == -1) {
966 				// Local file
967 				name = fi.fileName(); //fi.baseName(true);
968 			} else {
969 				// Stream
970 				name = filename;
971 			}
972 		}
973 		pl.append( PlaylistItem(filename, name, duration) );
974 		//setModified( true ); // Better set the modified on a higher level
975 #if !PL_ALLOW_DUPLICATES
976 	} else {
977 		qDebug("Playlist::addItem: item not added, already in the list");
978 	}
979 #endif
980 */
981 }
982 
983 
load_m3u(QString file,M3UFormat format)984 void Playlist::load_m3u(QString file, M3UFormat format) {
985 	bool utf8 = false;
986 	if (format == DetectFormat) {
987 		utf8 = (QFileInfo(file).suffix().toLower() == "m3u8");
988 	} else {
989 		utf8 = (format == M3U8);
990 	}
991 
992 	qDebug() << "Playlist::load_m3u: utf8:" << utf8;
993 
994 	QRegExp m3u_id("^#EXTM3U|^#M3U");
995 	QRegExp rx_info("^#EXTINF:([.\\d]+).*tvg-logo=\"(.*)\",(.*)");
996 
997 	QFile f( file );
998 	if ( f.open( QIODevice::ReadOnly ) ) {
999 		playlist_path = QFileInfo(file).path();
1000 
1001 		clear();
1002 		QString filename="";
1003 		QString name="";
1004 		double duration=0;
1005 		QStringList extra_params;
1006 		QString icon_url;
1007 
1008 		QTextStream stream( &f );
1009 
1010 		if (utf8)
1011 			stream.setCodec("UTF-8");
1012 		else
1013 			stream.setCodec(QTextCodec::codecForLocale());
1014 
1015 		QString line;
1016 		while ( !stream.atEnd() ) {
1017 			line = stream.readLine().trimmed();
1018 			if (line.isEmpty()) continue; // Ignore empty lines
1019 
1020 			qDebug() << "Playlist::load_m3u: line:" << line;
1021 			if (m3u_id.indexIn(line)!=-1) {
1022 				//#EXTM3U
1023 				// Ignore line
1024 			}
1025 			else
1026 			if (rx_info.indexIn(line) != -1) {
1027 				duration = rx_info.cap(1).toDouble();
1028 				name = rx_info.cap(3);
1029 				icon_url = rx_info.cap(2);
1030 				qDebug() << "Playlist::load_m3u: name:" << name << "duration:" << duration << "icon_url:" << icon_url;
1031 			}
1032 			else
1033 			if (line.startsWith("#EXTINF:")) {
1034 				QStringList fields = line.mid(8).split(",");
1035 				//qDebug() << "Playlist::load_m3u: fields:" << fields;
1036 				if (fields.count() >= 1) duration = fields[0].toDouble();
1037 				if (fields.count() >= 2) name = fields[1];
1038 			}
1039 			else
1040 			if (line.startsWith("#EXTVLCOPT:")) {
1041 				QString par = line.mid(11);
1042 				qDebug() << "Playlist::load_m3u: EXTVLCOPT:" << par;
1043 				extra_params << par;
1044 			}
1045 			else
1046 			if (line.startsWith("#")) {
1047 				// Comment
1048 				// Ignore
1049 			} else {
1050 				filename = line;
1051 				QFileInfo fi(filename);
1052 				if (fi.exists()) {
1053 					filename = fi.absoluteFilePath();
1054 				}
1055 				if (!fi.exists()) {
1056 					if (QFileInfo( playlist_path + "/" + filename).exists() ) {
1057 						filename = playlist_path + "/" + filename;
1058 					}
1059 				}
1060 				name.replace("&#44;", ",");
1061 				//qDebug() << "Playlist::load_m3u: extra_params:" << extra_params;
1062 				addItem( filename, name, duration, extra_params, "", icon_url );
1063 				name = "";
1064 				duration = 0;
1065 				extra_params.clear();
1066 				icon_url = "";
1067 			}
1068 		}
1069 		f.close();
1070 		//list();
1071 
1072 		setPlaylistFilename(file);
1073 		setModified( false );
1074 
1075 		if (shuffleAct->isChecked()) shuffle(true);
1076 		if (start_play_on_load) startPlay();
1077 	}
1078 }
1079 
load_pls(QString file)1080 void Playlist::load_pls(QString file) {
1081 	qDebug("Playlist::load_pls");
1082 
1083 	if (!QFile::exists(file)) {
1084 		qDebug("Playlist::load_pls: '%s' doesn't exist, doing nothing", file.toUtf8().constData());
1085 		return;
1086 	}
1087 
1088 	playlist_path = QFileInfo(file).path();
1089 
1090 	QSettings set(file, QSettings::IniFormat);
1091 	set.beginGroup("playlist");
1092 
1093 	if (set.status() == QSettings::NoError) {
1094 		clear();
1095 		QString filename;
1096 		QString name;
1097 		double duration;
1098 
1099 		int num_items = set.value("NumberOfEntries", 0).toInt();
1100 
1101 		#if QT_VERSION >= 0x050000
1102 		// It seems Qt 5 is case sensitive
1103 		if (num_items == 0) num_items = set.value("numberofentries", 0).toInt();
1104 		#endif
1105 
1106 		for (int n=0; n < num_items; n++) {
1107 			filename = set.value("File"+QString::number(n+1), "").toString();
1108 			name = set.value("Title"+QString::number(n+1), "").toString();
1109 			duration = (double) set.value("Length"+QString::number(n+1), 0).toInt();
1110 
1111 			QFileInfo fi(filename);
1112 			if (fi.exists()) {
1113 				filename = fi.absoluteFilePath();
1114 			}
1115 			if (!fi.exists()) {
1116 				if (QFileInfo( playlist_path + "/" + filename).exists() ) {
1117 					filename = playlist_path + "/" + filename;
1118 				}
1119 			}
1120 			addItem( filename, name, duration );
1121 		}
1122 	}
1123 
1124 	set.endGroup();
1125 
1126 	//list();
1127 
1128 	setPlaylistFilename(file);
1129 	setModified( false );
1130 
1131 	if (shuffleAct->isChecked()) shuffle(true);
1132 	if (set.status() == QSettings::NoError && start_play_on_load) startPlay();
1133 }
1134 
loadXSPF(const QString & filename)1135 void Playlist::loadXSPF(const QString & filename) {
1136 	qDebug() << "Playlist::loadXSPF:" << filename;
1137 
1138 	QFile f(filename);
1139 	if (!f.open(QIODevice::ReadOnly)) {
1140 		return;
1141 	}
1142 
1143 	QDomDocument dom_document;
1144 	bool ok = dom_document.setContent(f.readAll());
1145 	qDebug() << "Playlist::loadXSPF: success:" << ok;
1146 	if (!ok) return;
1147 
1148 	QDomNode root = dom_document.documentElement();
1149 	qDebug() << "Playlist::loadXSPF: tagname:" << root.toElement().tagName();
1150 
1151 	QDomNode child = root.firstChildElement("trackList");
1152 	if (!child.isNull()) {
1153 		clear();
1154 
1155 		qDebug() << "Playlist::loadXSPF: child:" << child.nodeName();
1156 		QDomNode track = child.firstChildElement("track");
1157 		while (!track.isNull()) {
1158 			QString location = QUrl::fromPercentEncoding(track.firstChildElement("location").text().toLatin1());
1159 			QString title = track.firstChildElement("title").text();
1160 			int duration = track.firstChildElement("duration").text().toInt();
1161 
1162 			qDebug() << "Playlist::loadXSPF: location:" << location;
1163 			qDebug() << "Playlist::loadXSPF: title:" << title;
1164 			qDebug() << "Playlist::loadXSPF: duration:" << duration;
1165 
1166 			addItem( location, title, (double) duration / 1000 );
1167 
1168 			track = track.nextSiblingElement("track");
1169 		}
1170 
1171 		//list();
1172 		setPlaylistFilename(filename);
1173 		setModified( false );
1174 
1175 		if (shuffleAct->isChecked()) shuffle(true);
1176 		if (start_play_on_load) startPlay();
1177 	}
1178 }
1179 
1180 #ifdef YT_PLAYLIST_SUPPORT
loadYoutubeList(QList<itemMap> list)1181 void Playlist::loadYoutubeList(QList<itemMap> list) {
1182 	qDebug() << "Playlist::loadYoutubeList: list:" << list;
1183 	clear();
1184 	foreach(itemMap item, list) {
1185 		addItem(item["url"], item["title"], item["duration"].toDouble());
1186 	}
1187 	setModified( false );
1188 
1189 	if (shuffleAct->isChecked()) shuffle(true);
1190 	if (start_play_on_load) startPlay();
1191 }
1192 
isYTPlaylist(const QString & url)1193 bool Playlist::isYTPlaylist(const QString & url) {
1194 	return ((url.contains("http:") || url.contains("https:")) && url.contains("youtube") && url.contains("list=") && !url.contains("index="));
1195 }
1196 #endif
1197 
save_m3u(QString file)1198 bool Playlist::save_m3u(QString file) {
1199 	qDebug() << "Playlist::save_m3u:" << file;
1200 
1201 	QString dir_path = QFileInfo(file).path();
1202 	if (!dir_path.endsWith("/")) dir_path += "/";
1203 
1204 	#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
1205 	dir_path = Helper::changeSlashes(dir_path);
1206 	#endif
1207 
1208 	qDebug() << "Playlist::save_m3u: dir_path:" << dir_path;
1209 
1210 	bool utf8 = (QFileInfo(file).suffix().toLower() == "m3u8");
1211 
1212 	QFile f( file );
1213 	if ( f.open( QIODevice::WriteOnly ) ) {
1214 		QTextStream stream( &f );
1215 
1216 		if (utf8)
1217 			stream.setCodec("UTF-8");
1218 		else
1219 			stream.setCodec(QTextCodec::codecForLocale());
1220 
1221 		QString filename;
1222 		QString name;
1223 
1224 		stream << "#EXTM3U" << "\n";
1225 		stream << "# Playlist created by SMPlayer " << Version::printable() << " \n";
1226 
1227 		for (int n = 0; n < count(); n++) {
1228 			PLItem * i = itemData(n);
1229 			filename = i->filename();
1230 			#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
1231 			filename = Helper::changeSlashes(filename);
1232 			#endif
1233 			name = i->name();
1234 			name.replace(",", "&#44;");
1235 			QString icon_url = i->iconURL();
1236 			stream << "#EXTINF:";
1237 			stream << i->duration();
1238 			if (!icon_url.isEmpty()) stream << " tvg-logo=\"" + icon_url + "\"";
1239 			stream << ",";
1240 			stream << name << "\n";
1241 
1242 			// Save extra params
1243 			QStringList params = i->extraParams();
1244 			foreach(QString par, params) {
1245 				stream << "#EXTVLCOPT:" << par << "\n";
1246 			}
1247 
1248 			// Try to save the filename as relative instead of absolute
1249 			if (filename.startsWith( dir_path )) {
1250 				filename = filename.mid( dir_path.length() );
1251 			}
1252 			stream << filename << "\n";
1253 		}
1254 		f.close();
1255 
1256 		setPlaylistFilename(file);
1257 		setModified( false );
1258 		return true;
1259 	} else {
1260 		return false;
1261 	}
1262 }
1263 
1264 
save_pls(QString file)1265 bool Playlist::save_pls(QString file) {
1266 	qDebug() << "Playlist::save_pls:" << file;
1267 
1268 	QString dir_path = QFileInfo(file).path();
1269 	if (!dir_path.endsWith("/")) dir_path += "/";
1270 
1271 	#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
1272 	dir_path = Helper::changeSlashes(dir_path);
1273 	#endif
1274 
1275 	qDebug() << "Playlist::save_pls: dir_path:" << dir_path;
1276 
1277 	QSettings set(file, QSettings::IniFormat);
1278 	set.beginGroup( "playlist");
1279 
1280 	QString filename;
1281 
1282 	for (int n = 0; n < count(); n++) {
1283 		PLItem * i = itemData(n);
1284 		filename = i->filename();
1285 		#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
1286 		filename = Helper::changeSlashes(filename);
1287 		#endif
1288 
1289 		// Try to save the filename as relative instead of absolute
1290 		if (filename.startsWith( dir_path )) {
1291 			filename = filename.mid( dir_path.length() );
1292 		}
1293 
1294 		set.setValue("File"+QString::number(n+1), filename);
1295 		set.setValue("Title"+QString::number(n+1), i->name());
1296 		set.setValue("Length"+QString::number(n+1), (int) i->duration());
1297 	}
1298 
1299 	set.setValue("NumberOfEntries", count());
1300 	set.setValue("Version", 2);
1301 
1302 	set.endGroup();
1303 
1304 	set.sync();
1305 
1306 	bool ok = (set.status() == QSettings::NoError);
1307 	if (ok) {
1308 		setPlaylistFilename(file);
1309 		setModified( false );
1310 	}
1311 
1312 	return ok;
1313 }
1314 
saveXSPF(const QString & filename)1315 bool Playlist::saveXSPF(const QString & filename) {
1316 	qDebug() << "Playlist::saveXSPF:" << filename;
1317 
1318 	QFile f(filename);
1319 	if (f.open( QIODevice::WriteOnly)) {
1320 		QTextStream stream(&f);
1321 		stream.setCodec("UTF-8");
1322 
1323 		stream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
1324 		stream << "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n";
1325 		stream << "\t<trackList>\n";
1326 
1327 		for (int n = 0; n < count(); n++) {
1328 			PLItem * i = itemData(n);
1329 			QString location = i->filename();
1330 			qDebug() << "Playlist::saveXSPF:" << location;
1331 
1332 			bool is_local = QFile::exists(location);
1333 
1334 			#ifdef Q_OS_WIN
1335 			if (is_local) {
1336 				location.replace("\\", "/");
1337 			}
1338 			#endif
1339 			//qDebug() << "Playlist::saveXSPF:" << location;
1340 
1341 			QUrl url(location);
1342 			location = url.toEncoded();
1343 			//qDebug() << "Playlist::saveXSPF:" << location;
1344 
1345 			if (!location.startsWith("file:") && is_local) {
1346 				#ifdef Q_OS_WIN
1347 				location = "file:///" + location;
1348 				#else
1349 				location = "file://" + location;
1350 				#endif
1351 			}
1352 
1353 			QString title = i->name();
1354 			int duration = i->duration() * 1000;
1355 
1356 			#if QT_VERSION >= 0x050000
1357 			location = location.toHtmlEscaped();
1358 			title = title.toHtmlEscaped();
1359 			#else
1360 			location = Qt::escape(location);
1361 			title = Qt::escape(title);
1362 			#endif
1363 
1364 			stream << "\t\t<track>\n";
1365 			stream << "\t\t\t<location>" << location << "</location>\n";
1366 			stream << "\t\t\t<title>" << title << "</title>\n";
1367 			stream << "\t\t\t<duration>" << duration << "</duration>\n";
1368 			stream << "\t\t</track>\n";
1369 		}
1370 
1371 		stream << "\t</trackList>\n";
1372 		stream << "</playlist>\n";
1373 
1374 		setPlaylistFilename(filename);
1375 		setModified(false);
1376 		return true;
1377 	} else {
1378 		return false;
1379 	}
1380 }
1381 
1382 
load()1383 void Playlist::load() {
1384 	if (maybeSave()) {
1385 		Extensions e;
1386 		QString s = MyFileDialog::getOpenFileName(
1387                     this, tr("Choose a file"),
1388                     lastDir(),
1389                     tr("Playlists") + e.playlist().forFilter() + ";;" + tr("All files") +" (*)");
1390 
1391 		if (!s.isEmpty()) {
1392 			latest_dir = QFileInfo(s).absolutePath();
1393 
1394 			QString suffix = QFileInfo(s).suffix().toLower();
1395 			if (suffix == "pls") {
1396 				load_pls(s);
1397 			}
1398 			else
1399 			if (suffix == "xspf") {
1400 				loadXSPF(s);
1401 			}
1402 			else {
1403 				load_m3u(s);
1404 			}
1405 			//listView->resizeColumnsToContents();
1406 		}
1407 	}
1408 }
1409 
saveCurrentPlaylist()1410 bool Playlist::saveCurrentPlaylist() {
1411 	qDebug("Playlist::saveCurrentPlaylist");
1412 	return save(playlistFilename());
1413 }
1414 
save(const QString & filename)1415 bool Playlist::save(const QString & filename) {
1416 	qDebug() << "Playlist::save:" << filename;
1417 
1418 	QString s = filename;
1419 
1420 	if (s.isEmpty()) {
1421 		Extensions e;
1422 		s = MyFileDialog::getSaveFileName(
1423                     this, tr("Choose a filename"),
1424                     lastDir(),
1425                     tr("Playlists") + e.playlist().forFilter() + ";;" + tr("All files") +" (*)");
1426 	}
1427 
1428 	if (!s.isEmpty()) {
1429 		// If filename has no extension, add it
1430 		if (QFileInfo(s).suffix().isEmpty()) {
1431 			s = s + ".m3u";
1432 		}
1433 		if (QFileInfo(s).exists()) {
1434 			int res = QMessageBox::question( this,
1435 					tr("Confirm overwrite?"),
1436                     tr("The file %1 already exists.\n"
1437                        "Do you want to overwrite?").arg(s),
1438                     QMessageBox::Yes,
1439                     QMessageBox::No,
1440                     QMessageBox::NoButton);
1441 			if (res == QMessageBox::No ) {
1442             	return false;
1443 			}
1444 		}
1445 		latest_dir = QFileInfo(s).absolutePath();
1446 
1447 		QString suffix = QFileInfo(s).suffix().toLower();
1448 		if (suffix  == "pls") {
1449 			return save_pls(s);
1450 		}
1451 		else
1452 		if (suffix  == "xspf") {
1453 			return saveXSPF(s);
1454 		}
1455 		else {
1456 			return save_m3u(s);
1457 		}
1458 
1459 	} else {
1460 		return false;
1461 	}
1462 }
1463 
maybeSave()1464 bool Playlist::maybeSave() {
1465 	if (!isModified()) return true;
1466 
1467 	int res = QMessageBox::question( this,
1468 				tr("Playlist modified"),
1469                 tr("There are unsaved changes, do you want to save the playlist?"),
1470                 QMessageBox::Yes,
1471                 QMessageBox::No,
1472                 QMessageBox::Cancel);
1473 
1474 	switch (res) {
1475 		case QMessageBox::No : return true; // Discard changes
1476 		case QMessageBox::Cancel : return false; // Cancel operation
1477 		default : return save();
1478 	}
1479 }
1480 
playCurrent()1481 void Playlist::playCurrent() {
1482 	int current = listView->currentIndex().row();
1483 	if (current > -1) {
1484 		playItem(current);
1485 	}
1486 }
1487 
itemActivated(const QModelIndex & index)1488 void Playlist::itemActivated(const QModelIndex & index ) {
1489 	qDebug() << "Playlist::itemActivated: row:" << index.row();
1490 	playItem(index.row());
1491 }
1492 
showPopup(const QPoint & pos)1493 void Playlist::showPopup(const QPoint & pos) {
1494 	qDebug("Playlist::showPopup: x: %d y: %d", pos.x(), pos.y() );
1495 
1496 	QModelIndex index = listView->currentIndex();
1497 	if (!index.isValid()) {
1498 		playAct->setEnabled(false);
1499 		removeSelectedAct->setEnabled(false);
1500 		editAct->setEnabled(false);
1501 		#ifdef PLAYLIST_DELETE_FROM_DISK
1502 		deleteSelectedFileFromDiskAct->setEnabled(false);
1503 		#endif
1504 		copyURLAct->setEnabled(false);
1505 		openFolderAct->setEnabled(false);
1506 		#ifdef CHROMECAST_SUPPORT
1507 		playOnChromecastAct->setEnabled(false);
1508 		#else
1509 		openURLInWebAct->setEnabled(false);
1510 		#endif
1511 	} else {
1512 		playAct->setEnabled(true);
1513 		removeSelectedAct->setEnabled(true);
1514 		editAct->setEnabled(true);
1515 		#ifdef PLAYLIST_DELETE_FROM_DISK
1516 		deleteSelectedFileFromDiskAct->setEnabled(allow_delete_from_disk);
1517 		#endif
1518 		copyURLAct->setEnabled(true);
1519 		openFolderAct->setEnabled(true);
1520 		#ifdef CHROMECAST_SUPPORT
1521 		playOnChromecastAct->setEnabled(true);
1522 		#else
1523 		openURLInWebAct->setEnabled(true);
1524 		#endif
1525 
1526 		QModelIndex s_index = proxy->mapToSource(index);
1527 		int current = s_index.row();
1528 		PLItem * i = itemData(current);
1529 		QString filename = i->filename();
1530 		QFileInfo fi(filename);
1531 
1532 		if (fi.exists()) {
1533 			copyURLAct->setText( tr("&Copy file path to clipboard") );
1534 			#ifndef CHROMECAST_SUPPORT
1535 			openURLInWebAct->setEnabled(false);
1536 			#endif
1537 		} else {
1538 			copyURLAct->setText( tr("&Copy URL to clipboard") );
1539 			openFolderAct->setEnabled(false);
1540 			#ifdef PLAYLIST_DELETE_FROM_DISK
1541 			deleteSelectedFileFromDiskAct->setEnabled(false);
1542 			#endif
1543 		}
1544 	}
1545 
1546 	if (!popup->isVisible()) {
1547 		popup->move( listView->viewport()->mapToGlobal(pos) );
1548 		popup->show();
1549 	}
1550 }
1551 
startPlay()1552 void Playlist::startPlay() {
1553 	playItem(0);
1554 }
1555 
playItem(int n,bool later)1556 void Playlist::playItem(int n, bool later) {
1557 	qDebug("Playlist::playItem: %d (count: %d)", n, proxy->rowCount());
1558 
1559 	if ( (n >= proxy->rowCount()) || (n < 0) ) {
1560 		qDebug("Playlist::playItem: out of range");
1561 		emit playlistEnded();
1562 		return;
1563 	}
1564 
1565 	PLItem * i = itemFromProxy(n);
1566 	QString filename = i->filename();
1567 	QStringList params = i->extraParams();
1568 
1569 	if (!filename.isEmpty()) {
1570 		setCurrentItem(n);
1571 
1572 		if (!params.isEmpty()) {
1573 			#ifdef DELAYED_PLAY
1574 			if (later) {
1575 				play_later.filename = filename;
1576 				play_later.seek = -1;
1577 				play_later.params = params;
1578 				play_later_timer->start();
1579 			} else
1580 			#endif
1581 			{
1582 				emit requestToPlayStream(filename, params);
1583 			}
1584 		} else {
1585 			int seek = -1;
1586 			if (play_files_from_start) seek = 0;
1587 			#ifdef DELAYED_PLAY
1588 			if (later) {
1589 				play_later.filename = filename;
1590 				play_later.seek = seek;
1591 				play_later.params.clear();
1592 				play_later_timer->start();
1593 			} else
1594 			#endif
1595 			{
1596 				emit requestToPlayFile(filename, seek);
1597 			}
1598 		}
1599 	}
1600 }
1601 
playNext()1602 void Playlist::playNext() {
1603 	qDebug("Playlist::playNext");
1604 
1605 	bool delayed_play = false;
1606 	#ifdef DELAYED_PLAY
1607 	qint64 t_now = QDateTime::currentMSecsSinceEpoch();
1608 	qint64 diff_time = t_now - last_timestamp;
1609 	if (diff_time < 1000) delayed_play = true;
1610 	last_timestamp = t_now;
1611 	//qDebug() << "Playlist::playNext: diff:" << diff_time << "delayed_play:" << delayed_play;
1612 	#endif
1613 
1614 	int current = findCurrentItem();
1615 	bool finished_list = (current + 1 >= proxy->rowCount());
1616 	if (finished_list) clearPlayedTag();
1617 
1618 	if (repeatAct->isChecked() && finished_list) {
1619 		playItem(0, delayed_play);
1620 	} else {
1621 		playItem(current + 1, delayed_play);
1622 	}
1623 }
1624 
playPrev()1625 void Playlist::playPrev() {
1626 	qDebug("Playlist::playPrev");
1627 
1628 	bool delayed_play = false;
1629 	#ifdef DELAYED_PLAY
1630 	qint64 t_now = QDateTime::currentMSecsSinceEpoch();
1631 	qint64 diff_time = t_now - last_timestamp;
1632 	if (diff_time < 1000) delayed_play = true;
1633 	last_timestamp = t_now;
1634 	//qDebug() << "Playlist::playPrev: diff:" << diff_time << "delayed_play:" << delayed_play;
1635 	#endif
1636 
1637 	int current = findCurrentItem() - 1;
1638 	if (current >= 0) {
1639 		playItem(current, delayed_play);
1640 	} else {
1641 		if (proxy->rowCount() > 1) playItem(proxy->rowCount() - 1, delayed_play);
1642 	}
1643 }
1644 
playNextAuto()1645 void Playlist::playNextAuto() {
1646 	qDebug("Playlist::playNextAuto");
1647 	if (automatically_play_next) {
1648 		playNext();
1649 	} else {
1650 		emit playlistEnded();
1651 	}
1652 }
1653 
1654 #ifdef DELAYED_PLAY
playItemLater()1655 void Playlist::playItemLater() {
1656 	qDebug() << "Playlist::playItemLater" << play_later.filename << play_later.seek << play_later.params;
1657 	if (!play_later.params.isEmpty()) {
1658 		emit requestToPlayStream(play_later.filename, play_later.params);
1659 	} else {
1660 		emit requestToPlayFile(play_later.filename, play_later.seek);
1661 	}
1662 }
1663 #endif
1664 
resumePlay()1665 void Playlist::resumePlay() {
1666 	qDebug("Playlist::resumePlay");
1667 
1668 	if (count() > 0) {
1669 		int current = findCurrentItem();
1670 		if (current < 0) current = 0;
1671 		playItem(current);
1672 	}
1673 }
1674 
getMediaInfo(const MediaData & mdat)1675 void Playlist::getMediaInfo(const MediaData & mdat) {
1676 	qDebug("Playlist::getMediaInfo");
1677 
1678 	QString filename = mdat.filename;
1679 	double duration = mdat.duration;
1680 	QString artist = mdat.clip_artist;
1681 	QString video_url = mdat.stream_path;
1682 
1683 	#if defined(Q_OS_WIN) || defined(Q_OS_OS2)
1684 	filename = Helper::changeSlashes(filename);
1685 	#endif
1686 
1687 	QString name;
1688 	if (change_name) {
1689 		name = mdat.clip_name;
1690 		if (name.isEmpty()) name = mdat.stream_title;
1691 
1692 		if (name.isEmpty()) {
1693 			QFileInfo fi(filename);
1694 			if (fi.exists()) {
1695 				// Local file
1696 				name = fi.fileName();
1697 			} else {
1698 				// Stream
1699 				name = filename;
1700 			}
1701 		}
1702 		if (!artist.isEmpty()) name = artist + " - " + name;
1703 	}
1704 
1705 	for (int n = 0; n < count(); n++) {
1706 		PLItem * i = itemData(n);
1707 		if (i->filename() == filename) {
1708 			// Found item
1709 			bool modified_name = !(i->filename().endsWith(i->name()));
1710 			if (i->duration() < 1) {
1711 				if (!modified_name && !name.isEmpty()) {
1712 					i->setName(name);
1713 				}
1714 				i->setDuration(duration);
1715 			}
1716 			else
1717 			// Edited name (sets duration to 1)
1718 			if (i->duration() == 1) {
1719 				i->setDuration(duration);
1720 			}
1721 			i->setVideoURL(video_url);
1722 		}
1723 	}
1724 }
1725 
1726 // Add current file to playlist
addCurrentFile()1727 void Playlist::addCurrentFile() {
1728 	qDebug("Playlist::addCurrentFile");
1729 	emit requestToAddCurrentFile();
1730 }
1731 
addFiles()1732 void Playlist::addFiles() {
1733 	Extensions e;
1734 	QStringList files = MyFileDialog::getOpenFileNames(
1735                             this, tr("Select one or more files to open"),
1736                             lastDir(),
1737                             tr("Multimedia") + e.multimedia().forFilter() + ";;" +
1738                             tr("All files") +" (*.*)" );
1739 
1740 	if (files.count() != 0) {
1741 		addFiles(files);
1742 		setModified(true);
1743 	}
1744 }
1745 
addFiles(QStringList files,AutoGetInfo auto_get_info)1746 void Playlist::addFiles(QStringList files, AutoGetInfo auto_get_info) {
1747 	qDebug("Playlist::addFiles");
1748 
1749 	#if USE_INFOPROVIDER
1750 	bool get_info = (auto_get_info == GetInfo);
1751 	if (auto_get_info == UserDefined) {
1752 		get_info = automatically_get_info;
1753 	}
1754 
1755 	MediaData data;
1756 	setCursor(Qt::WaitCursor);
1757 	#endif
1758 
1759 	QString initial_file;
1760 	int new_current_item = -1;
1761 	if (count() == 1) {
1762 		initial_file = itemData(0)->filename();
1763 		for (int n = 0; n < files.count(); n++) {
1764 			if (files[n] == initial_file) new_current_item = n;
1765 		}
1766 		if (new_current_item != -1) clear();
1767 	}
1768 
1769 	for (int n = 0; n < files.count(); n++) {
1770 		QString name = "";
1771 		double duration = 0;
1772 		#if USE_INFOPROVIDER
1773 		if ( (get_info) && (QFile::exists(files[n])) ) {
1774 			data = InfoProvider::getInfo(files[n]);
1775 			name = data.displayName();
1776 			duration = data.duration;
1777 			//qApp->processEvents();
1778 		}
1779 		#endif
1780 
1781 		//qDebug() << "Playlist::addFiles: comparing:" << initial_file << "with" << files[n];
1782 
1783 		addItem(files[n], name, duration);
1784 
1785 		if (QFile::exists(files[n])) {
1786 			latest_dir = QFileInfo(files[n]).absolutePath();
1787 		}
1788 	}
1789 	#if USE_INFOPROVIDER
1790 	unsetCursor();
1791 	#endif
1792 
1793 	if (new_current_item != -1) setCurrentItem(new_current_item);
1794 
1795 	qDebug() << "Playlist::addFiles: latest_dir:" << latest_dir;
1796 }
1797 
addFile(QString file,AutoGetInfo auto_get_info)1798 void Playlist::addFile(QString file, AutoGetInfo auto_get_info) {
1799 	addFiles( QStringList() << file, auto_get_info );
1800 }
1801 
addDirectory()1802 void Playlist::addDirectory() {
1803 	QString s = MyFileDialog::getExistingDirectory(
1804                     this, tr("Choose a directory"),
1805                     lastDir() );
1806 
1807 	if (!s.isEmpty()) {
1808 		addDirectory(s);
1809 		latest_dir = s;
1810 	}
1811 }
1812 
addUrls()1813 void Playlist::addUrls() {
1814 	MultilineInputDialog d(this);
1815 	if (d.exec() == QDialog::Accepted) {
1816 		QStringList urls = d.lines();
1817 		foreach(QString u, urls) {
1818 			if (!u.isEmpty()) addItem( u, "", 0 );
1819 		}
1820 		setModified(true);
1821 	}
1822 }
1823 
addOneDirectory(QString dir)1824 void Playlist::addOneDirectory(QString dir) {
1825 	QStringList filelist;
1826 
1827 	Extensions e;
1828 	QRegExp rx_ext(e.multimedia().forRegExp());
1829 	rx_ext.setCaseSensitivity(Qt::CaseInsensitive);
1830 
1831 	QStringList dir_list = QDir(dir).entryList();
1832 
1833 	QString filename;
1834 	QStringList::Iterator it = dir_list.begin();
1835 	while( it != dir_list.end() ) {
1836 		filename = dir;
1837 		if (filename.right(1)!="/") filename += "/";
1838 		filename += (*it);
1839 		QFileInfo fi(filename);
1840 		if (!fi.isDir()) {
1841 			if (rx_ext.indexIn(fi.suffix()) > -1) {
1842 				filelist << filename;
1843 			}
1844 		}
1845 		++it;
1846 	}
1847 	addFiles(filelist);
1848 }
1849 
addDirectory(QString dir)1850 void Playlist::addDirectory(QString dir) {
1851 	addOneDirectory(dir);
1852 
1853 	if (recursive_add_directory) {
1854 		QFileInfoList dir_list = QDir(dir).entryInfoList(QStringList() << "*", QDir::AllDirs | QDir::NoDotAndDotDot);
1855 		for (int n=0; n < dir_list.count(); n++) {
1856 			if (dir_list[n].isDir()) {
1857 				qDebug("Playlist::addDirectory: adding directory: %s", dir_list[n].filePath().toUtf8().data());
1858 				addDirectory(dir_list[n].filePath());
1859 			}
1860 		}
1861 	}
1862 	setModified(true);
1863 }
1864 
1865 // Remove selected items
removeSelected()1866 void Playlist::removeSelected() {
1867 	qDebug("Playlist::removeSelected");
1868 
1869 	QModelIndexList indexes = listView->selectionModel()->selectedRows();
1870 	int count = indexes.count();
1871 
1872 	qDebug() << "Playlist::removeSelected: count:" << count;
1873 	if (count < 1) return;
1874 
1875 	std::sort(indexes.begin(), indexes.end());
1876 	for (int n = indexes.count() - 1; n > -1; --n) {
1877 		QModelIndex s_index = proxy->mapToSource(indexes.at(n));
1878 		int selected_row = indexes.at(n).row();
1879 		int actual_row = s_index.row();
1880 		qDebug() << "Playlist::removeSelected: selected row:" << selected_row << "actual row:" << actual_row;
1881 		table->removeRow(actual_row);
1882 	}
1883 	setModified(true);
1884 
1885 	if (isEmpty()) setModified(false);
1886 
1887 	if (findCurrentItem() == -1) {
1888 		int current = indexes.at(0).row() - 1;
1889 		if (current < 0) current = 0;
1890 		setCurrentItem(current);
1891 	}
1892 }
1893 
removeAll()1894 void Playlist::removeAll() {
1895 	clear();
1896 	setPlaylistFilename("");
1897 }
1898 
clearPlayedTag()1899 void Playlist::clearPlayedTag() {
1900 	for (int n = 0; n < count(); n++) {
1901 		itemData(n)->setPlayed(false);
1902 	}
1903 }
1904 
shuffle(bool enable)1905 void Playlist::shuffle(bool enable) {
1906 	if (enable) {
1907 		for (int n = 0; n < count(); n++) {
1908 			PLItem * i = itemData(n);
1909 			#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
1910 			i->setShufflePosition( QRandomGenerator::global()->generate() % 1000000 );
1911 			#else
1912 			i->setShufflePosition( qrand() % 1000000 );
1913 			#endif
1914 		}
1915 		listView->sortByColumn(COL_SHUFFLE, Qt::AscendingOrder);
1916 	} else {
1917 		listView->sortByColumn(COL_NUM, Qt::AscendingOrder);
1918 	}
1919 }
1920 
upItem()1921 void Playlist::upItem() {
1922 	QModelIndex index = listView->currentIndex();
1923 	QModelIndex s_index = proxy->mapToSource(index);
1924 
1925 	QModelIndex prev = listView->model()->index(index.row()-1, 0);
1926 	QModelIndex s_prev = proxy->mapToSource(prev);
1927 
1928 	qDebug() << "Playlist::upItem: row:" << index.row() << "source row:" << s_index.row();
1929 	qDebug() << "Playlist::upItem: previous row:" << prev.row() << "previous source row:" << s_prev.row();
1930 
1931 	if (s_index.isValid() && s_prev.isValid()) {
1932 		int row = s_index.row();
1933 		int prev_row = s_prev.row();
1934 
1935 		int pos_num_current = itemData(row)->position();
1936 		int pos_num_prev = itemData(prev_row)->position();
1937 
1938 		qDebug() << "Playlist::upItem: pos_num_current:" << pos_num_current << "pos_num_prev:" << pos_num_prev;
1939 
1940 		itemData(row)->setPosition(pos_num_prev);
1941 		itemData(prev_row)->setPosition(pos_num_current);
1942 
1943 		QList<QStandardItem*> cells = table->takeRow(row);
1944 		table->insertRow(s_prev.row(), cells);
1945 		listView->selectionModel()->setCurrentIndex(listView->model()->index(index.row()-1, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
1946 
1947 		setModified(true);
1948 	}
1949 }
1950 
downItem()1951 void Playlist::downItem() {
1952 	qDebug("Playlist::downItem");
1953 
1954 	QModelIndex index = listView->currentIndex();
1955 	QModelIndex s_index = proxy->mapToSource(index);
1956 
1957 	QModelIndex next = listView->model()->index(index.row()+1, 0);
1958 	QModelIndex s_next = proxy->mapToSource(next);
1959 
1960 	qDebug() << "Playlist::downItem: row:" << index.row() << "source row:" << s_index.row();
1961 	qDebug() << "Playlist::downItem: next row:" << next.row() << "next source row:" << s_next.row();
1962 
1963 	if (s_index.isValid() && s_next.isValid()) {
1964 		int row = s_index.row();
1965 		int next_row = s_next.row();
1966 
1967 		int pos_num_current = itemData(row)->position();
1968 		int pos_num_next = itemData(next_row)->position();
1969 
1970 		qDebug() << "Playlist::downItem: pos_num_current:" << pos_num_current << "pos_num_next:" << pos_num_next;
1971 
1972 		itemData(row)->setPosition(pos_num_next);
1973 		itemData(next_row)->setPosition(pos_num_current);
1974 
1975 		QList<QStandardItem*> cells = table->takeRow(row);
1976 		table->insertRow(s_next.row(), cells);
1977 		listView->selectionModel()->setCurrentIndex(listView->model()->index(index.row()+1, 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
1978 
1979 		setModified(true);
1980 	}
1981 }
1982 
editCurrentItem()1983 void Playlist::editCurrentItem() {
1984 	QModelIndex v_index = listView->currentIndex();
1985 	QModelIndex s_index = proxy->mapToSource(v_index);
1986 	qDebug() << "Playlist::editCurrentItem: row:" << v_index.row() << "source row:" << s_index.row();
1987 	int current = s_index.row();
1988 	if (current > -1) editItem(current);
1989 }
1990 
editItem(int row)1991 void Playlist::editItem(int row) {
1992 	qDebug() << "Playlist::editItem:" << row;
1993 
1994 	PLItem * i = itemData(row);
1995 	QString current_name = i->name();
1996 	if (current_name.isEmpty()) current_name = i->filename();
1997 
1998 	bool ok;
1999 	QString text = QInputDialog::getText( this,
2000             tr("Edit name"),
2001             tr("Type the name that will be displayed in the playlist for this file:"),
2002             QLineEdit::Normal,
2003             current_name, &ok );
2004 	if ( ok && !text.isEmpty() ) {
2005 		// user entered something and pressed OK
2006 		i->setName(text);
2007 
2008 		// If duration == 0 the name will be overwritten!
2009 		if (i->duration() < 1) i->setDuration(1);
2010 
2011 		setModified( true );
2012 	}
2013 }
2014 
2015 #ifdef PLAYLIST_DELETE_FROM_DISK
deleteSelectedFileFromDisk()2016 void Playlist::deleteSelectedFileFromDisk() {
2017 	qDebug("Playlist::deleteSelectedFileFromDisk");
2018 
2019 	if (!allow_delete_from_disk) return;
2020 
2021 	QModelIndex index = listView->currentIndex();
2022 	if (!index.isValid()) return;
2023 
2024 	QModelIndex s_index = proxy->mapToSource(index);
2025 
2026 	qDebug() << "Playlist::deleteSelectedFileFromDisk: row:" << index.row() << "source row:" << s_index.row();
2027 	int current = s_index.row();
2028 
2029 	// Select only the current row
2030 	listView->selectionModel()->setCurrentIndex(listView->model()->index(index.row(), 0), QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
2031 
2032 	QString filename = itemData(current)->filename();
2033 	qDebug() << "Playlist::deleteSelectedFileFromDisk: current file:" << filename;
2034 
2035 	QFileInfo fi(filename);
2036 	if (fi.exists() && fi.isFile() && fi.isWritable()) {
2037 		// Ask the user for confirmation
2038 		int res = QMessageBox::question(this, tr("Confirm deletion"),
2039 					tr("You're about to DELETE the file '%1' from your drive.").arg(filename) + "<br>"+
2040 					tr("This action cannot be undone. Are you sure you want to proceed?"),
2041 					QMessageBox::Yes, QMessageBox::No);
2042 
2043 		if (res == QMessageBox::Yes) {
2044 			// Delete file
2045 			#if SIMULATE_FILE_DELETION
2046 			bool success = true;
2047 			#else
2048 			bool success = QFile::remove(filename);
2049 			#endif
2050 
2051 			if (success) {
2052 				// Remove item from the playlist
2053 				table->removeRow(current);
2054 				if (findCurrentItem() == -1) {
2055 					if (current > 0) setCurrentItem(current-1); else setCurrentItem(0);
2056 				}
2057 			} else {
2058 				QMessageBox::warning(this, tr("Deletion failed"),
2059 					tr("It wasn't possible to delete '%1'").arg(filename));
2060 			}
2061 		}
2062 	} else {
2063 		qDebug("Playlist::deleteSelectedFileFromDisk: file doesn't exists, it's not a file or it's not writable");
2064 		QMessageBox::information(this, tr("Error deleting the file"),
2065 			tr("It's not possible to delete '%1' from the filesystem.").arg(filename));
2066 	}
2067 }
2068 #endif
2069 
copyURL()2070 void Playlist::copyURL() {
2071 	qDebug("Playlist::copyURL");
2072 
2073 	QModelIndexList indexes = listView->selectionModel()->selectedRows();
2074 	int count = indexes.count();
2075 
2076 	QString text;
2077 
2078 	for (int n = 0; n < count; n++) {
2079 		QModelIndex s_index = proxy->mapToSource(indexes.at(n));
2080 		int current = s_index.row();
2081 		text += itemData(current)->filename();
2082 		if (n < count-1) {
2083 			#ifdef Q_OS_WIN
2084 			text += "\r\n";
2085 			#else
2086 			text += "\n";
2087 			#endif
2088 		}
2089 	}
2090 
2091 	if (!text.isEmpty()) QApplication::clipboard()->setText(text);
2092 }
2093 
openFolder()2094 void Playlist::openFolder() {
2095 	qDebug("Playlist::openFolder");
2096 
2097 	QModelIndex index = listView->currentIndex();
2098 	if (!index.isValid()) return;
2099 	QModelIndex s_index = proxy->mapToSource(index);
2100 	int current = s_index.row();
2101 	PLItem * i = itemData(current);
2102 	QString filename = i->filename();
2103 
2104 	qDebug() << "Playlist::openFolder: filename:" << filename;
2105 
2106 	QFileInfo fi(filename);
2107 	if (fi.exists()) {
2108 		QString src_folder = fi.absolutePath();
2109 		QDesktopServices::openUrl(QUrl::fromLocalFile(src_folder));
2110 	}
2111 }
2112 
2113 #ifdef CHROMECAST_SUPPORT
playOnChromecast()2114 void Playlist::playOnChromecast() {
2115 	qDebug("Playlist::playOnChromecast");
2116 
2117 	QModelIndex index = listView->currentIndex();
2118 	if (!index.isValid()) return;
2119 	QModelIndex s_index = proxy->mapToSource(index);
2120 	int current = s_index.row();
2121 	PLItem * i = itemData(current);
2122 	QString filename = i->filename();
2123 	QString video_url = i->videoURL();
2124 
2125 	QString url = filename;
2126 	if (!video_url.isEmpty()) url = video_url;
2127 
2128 	if (QFile::exists(filename)) {
2129 		Chromecast::instance()->openLocal(url, i->name());
2130 	} else {
2131 		Chromecast::instance()->openStream(url, i->name());
2132 	}
2133 }
2134 #else
openURLInWeb()2135 void Playlist::openURLInWeb() {
2136 	qDebug("Playlist::openURLInWeb");
2137 
2138 	QModelIndex index = listView->currentIndex();
2139 	if (!index.isValid()) return;
2140 	QModelIndex s_index = proxy->mapToSource(index);
2141 	int current = s_index.row();
2142 	PLItem * i = itemData(current);
2143 	QString filename = i->filename();
2144 	QString video_url = i->videoURL();
2145 
2146 	QString url = filename;
2147 	if (!video_url.isEmpty()) url = video_url;
2148 
2149 	QDesktopServices::openUrl(QUrl(url));
2150 }
2151 #endif
2152 
2153 // Drag&drop
dragEnterEvent(QDragEnterEvent * e)2154 void Playlist::dragEnterEvent( QDragEnterEvent *e ) {
2155 	qDebug("Playlist::dragEnterEvent");
2156 
2157 	if (e->mimeData()->hasUrls()) {
2158 		e->acceptProposedAction();
2159 	}
2160 }
2161 
dropEvent(QDropEvent * e)2162 void Playlist::dropEvent( QDropEvent *e ) {
2163 	qDebug("Playlist::dropEvent");
2164 
2165 	QStringList files;
2166 
2167 	if (e->mimeData()->hasUrls()) {
2168 		QList <QUrl> l = e->mimeData()->urls();
2169 		QString s;
2170 		for (int n=0; n < l.count(); n++) {
2171 			if (l[n].isValid()) {
2172 				qDebug("Playlist::dropEvent: scheme: '%s'", l[n].scheme().toUtf8().data());
2173 				if (l[n].scheme() == "file")
2174 					s = l[n].toLocalFile();
2175 				else
2176 					s = l[n].toString();
2177 				/*
2178 				qDebug(" * '%s'", l[n].toString().toUtf8().data());
2179 				qDebug(" * '%s'", l[n].toLocalFile().toUtf8().data());
2180 				*/
2181 				qDebug("Playlist::dropEvent: file: '%s'", s.toUtf8().data());
2182 				files.append(s);
2183 			}
2184 		}
2185 	}
2186 
2187 	#ifdef Q_OS_WIN
2188 	files = Helper::resolveSymlinks(files); // Check for Windows shortcuts
2189 	#endif
2190 	files.sort();
2191 
2192 	QStringList only_files;
2193 	for (int n = 0; n < files.count(); n++) {
2194 		if ( QFileInfo( files[n] ).isDir() ) {
2195 			addDirectory( files[n] );
2196 		} else {
2197 			only_files.append( files[n] );
2198 		}
2199 	}
2200 
2201 	if (only_files.count() == 1) {
2202 		// Check if the file is a playlist
2203 		QString filename = only_files[0];
2204 		QFileInfo fi(filename);
2205 		QString extension = fi.suffix().toLower();
2206 		if (extension == "m3u8" || extension == "m3u") { load_m3u(filename); return; }
2207 		else
2208 		if (extension == "pls") { load_pls(filename); return; }
2209 		else
2210 		if (extension == "xspf") { loadXSPF(filename); return; }
2211 	}
2212 
2213 	addFiles( only_files );
2214 	setModified(true);
2215 }
2216 
2217 
hideEvent(QHideEvent *)2218 void Playlist::hideEvent( QHideEvent * ) {
2219 	emit visibilityChanged(false);
2220 }
2221 
showEvent(QShowEvent *)2222 void Playlist::showEvent( QShowEvent * ) {
2223 	emit visibilityChanged(true);
2224 }
2225 
closeEvent(QCloseEvent * e)2226 void Playlist::closeEvent( QCloseEvent * e )  {
2227 	saveSettings();
2228 	e->accept();
2229 }
2230 
playerFailed(QProcess::ProcessError e)2231 void Playlist::playerFailed(QProcess::ProcessError e) {
2232 	qDebug("Playlist::playerFailed");
2233 	if (ignore_player_errors) {
2234 		if (e != QProcess::FailedToStart) {
2235 			playNext();
2236 		}
2237 	}
2238 }
2239 
playerFinishedWithError(int e)2240 void Playlist::playerFinishedWithError(int e) {
2241 	qDebug("Playlist::playerFinishedWithError: %d", e);
2242 	if (ignore_player_errors) {
2243 		playNext();
2244 	}
2245 }
2246 
maybeSaveSettings()2247 void Playlist::maybeSaveSettings() {
2248 	qDebug("Playlist::maybeSaveSettings");
2249 	if (isModified()) saveSettings();
2250 }
2251 
saveSettings()2252 void Playlist::saveSettings() {
2253 	qDebug("Playlist::saveSettings");
2254 
2255 	if (!set) return;
2256 
2257 	set->beginGroup( "playlist");
2258 
2259 	set->setValue( "repeat", repeatAct->isChecked() );
2260 	set->setValue( "shuffle", shuffleAct->isChecked() );
2261 
2262 	set->setValue( "auto_get_info", automatically_get_info );
2263 	set->setValue( "recursive_add_directory", recursive_add_directory );
2264 	set->setValue( "save_playlist_in_config", save_playlist_in_config );
2265 	set->setValue( "play_files_from_start", play_files_from_start );
2266 	set->setValue( "start_play_on_load", start_play_on_load );
2267 	set->setValue( "automatically_play_next", automatically_play_next );
2268 	set->setValue( "ignore_player_errors", ignore_player_errors );
2269 	set->setValue( "change_name", change_name );
2270 
2271 	set->setValue( "row_spacing", row_spacing );
2272 
2273 	if (isWindow()) { // No dockable
2274 		set->setValue( "size", size() );
2275 	}
2276 
2277 #ifdef PLAYLIST_DELETE_FROM_DISK
2278 	set->setValue("allow_delete_from_disk", allow_delete_from_disk);
2279 #endif
2280 
2281 	set->setValue(QString("header_state/2/%1").arg(Helper::qtVersion()), listView->horizontalHeader()->saveState());
2282 
2283 	set->setValue( "sort_column", proxy->sortColumn() );
2284 	set->setValue( "sort_order", proxy->sortOrder() );
2285 	set->setValue( "filter_case_sensitive", filterCaseSensitive() );
2286 	set->setValue( "filter", filter_edit->text() );
2287 	set->setValue( "sort_case_sensitive", sortCaseSensitive() );
2288 	set->setValue( "auto_sort", autoSort() );
2289 
2290 	set->setValue( "show_search", showSearchAct->isChecked() );
2291 
2292 	set->endGroup();
2293 
2294 	set->beginGroup( "directories");
2295 	set->setValue("save_dirs", save_dirs);
2296 	set->setValue("latest_dir", save_dirs ? latest_dir : "" );
2297 	set->endGroup();
2298 
2299 	if (save_playlist_in_config) {
2300 		//Save current list
2301 		set->beginGroup("playlist_contents");
2302 		set->beginWriteArray("items");
2303 		//set->setValue( "count", count() );
2304 		for (int n = 0; n < count(); n++ ) {
2305 			set->setArrayIndex(n);
2306 			PLItem * i = itemData(n);
2307 			set->setValue( QString("item_%1_filename").arg(n), i->filename() );
2308 			set->setValue( QString("item_%1_duration").arg(n), i->duration() );
2309 			set->setValue( QString("item_%1_name").arg(n), i->name() );
2310 			set->setValue( QString("item_%1_params").arg(n), i->extraParams() );
2311 			set->setValue( QString("item_%1_video_url").arg(n), i->videoURL() );
2312 			set->setValue( QString("item_%1_icon_url").arg(n), i->iconURL() );
2313 			set->setValue( QString("item_%1_shuffle").arg(n), i->shufflePosition() );
2314 		}
2315 		set->endArray();
2316 		set->setValue( "current_item", findCurrentItem() );
2317 		set->setValue("filename", playlistFilename());
2318 		set->setValue( "modified", modified );
2319 
2320 		set->endGroup();
2321 	}
2322 
2323 #ifdef PLAYLIST_DOWNLOAD
2324 	set->beginGroup("history");
2325 	set->setValue("max_items", history_urls->maxItems());
2326 	set->setValue("urls", history_urls->toStringList());
2327 	set->endGroup();
2328 #endif
2329 
2330 	if (set->contains("playlist/change_title")) set->remove("playlist/change_title");
2331 	if (set->contains("playlist/sort_case_sensivity")) set->remove("playlist/sort_case_sensivity");
2332 	if (set->contains("playlist/filter_case_sensivity")) set->remove("playlist/filter_case_sensivity");
2333 }
2334 
loadSettings()2335 void Playlist::loadSettings() {
2336 	qDebug("Playlist::loadSettings");
2337 
2338 	if (!set) return;
2339 
2340 	set->beginGroup( "playlist");
2341 
2342 	repeatAct->setChecked( set->value( "repeat", repeatAct->isChecked() ).toBool() );
2343 	shuffleAct->setChecked( set->value( "shuffle", shuffleAct->isChecked() ).toBool() );
2344 
2345 	automatically_get_info = set->value( "auto_get_info", automatically_get_info ).toBool();
2346 	recursive_add_directory = set->value( "recursive_add_directory", recursive_add_directory ).toBool();
2347 	save_playlist_in_config = set->value( "save_playlist_in_config", save_playlist_in_config ).toBool();
2348 	play_files_from_start = set->value( "play_files_from_start", play_files_from_start ).toBool();
2349 	start_play_on_load = set->value( "start_play_on_load", start_play_on_load ).toBool();
2350 	automatically_play_next = set->value( "automatically_play_next", automatically_play_next ).toBool();
2351 	ignore_player_errors = set->value( "ignore_player_errors", ignore_player_errors ).toBool();
2352 	change_name = set->value( "change_name", change_name ).toBool();
2353 
2354 	row_spacing = set->value( "row_spacing", row_spacing ).toInt();
2355 
2356 	if (isWindow()) { // No dockable
2357 		resize( set->value("size", size()).toSize() );
2358 	}
2359 
2360 #ifdef PLAYLIST_DELETE_FROM_DISK
2361 	allow_delete_from_disk = set->value("allow_delete_from_disk", allow_delete_from_disk).toBool();
2362 #endif
2363 
2364 	listView->horizontalHeader()->restoreState(set->value(QString("header_state/2/%1").arg(Helper::qtVersion()), QByteArray()).toByteArray());
2365 
2366 	int sort_column = set->value("sort_column", COL_NUM).toInt();
2367 	int sort_order = set->value("sort_order", Qt::AscendingOrder).toInt();
2368 	bool filter_case_sensitive = set->value("filter_case_sensitive", false).toBool();
2369 	QString filter = set->value( "filter").toString();
2370 	bool sort_case_sensitive = set->value("sort_case_sensitive", false).toBool();
2371 	bool auto_sort = set->value("auto_sort", false).toBool();
2372 
2373 	showSearchAct->setChecked( set->value( "show_search", false).toBool() );
2374 
2375 	set->endGroup();
2376 
2377 	set->beginGroup( "directories");
2378 	save_dirs = set->value("save_dirs", save_dirs).toBool();
2379 	if (save_dirs) {
2380 		latest_dir = set->value("latest_dir", latest_dir).toString();
2381 	}
2382 	set->endGroup();
2383 
2384 	if (save_playlist_in_config) {
2385 		//Load latest list
2386 		set->beginGroup("playlist_contents");
2387 		int count = set->beginReadArray("items");
2388 
2389 		QString filename, name;
2390 		double duration;
2391 		for (int n = 0; n < count; n++) {
2392 			set->setArrayIndex(n);
2393 			filename = set->value( QString("item_%1_filename").arg(n), "" ).toString();
2394 			duration = set->value( QString("item_%1_duration").arg(n), -1 ).toDouble();
2395 			name = set->value( QString("item_%1_name").arg(n), "" ).toString();
2396 			QStringList params = set->value( QString("item_%1_params").arg(n), QStringList()).toStringList();
2397 			QString video_url = set->value( QString("item_%1_video_url").arg(n), "").toString();
2398 			QString icon_url = set->value( QString("item_%1_icon_url").arg(n), "").toString();
2399 			int shuffle_pos = set->value( QString("item_%1_shuffle").arg(n), n).toInt();
2400 			addItem( filename, name, duration, params, video_url, icon_url, shuffle_pos );
2401 		}
2402 		set->endArray();
2403 		setCurrentItem( set->value( "current_item", -1 ).toInt() );
2404 		setPlaylistFilename( set->value("filename", "").toString() );
2405 		setModified( set->value( "modified", false ).toBool() );
2406 
2407 		set->endGroup();
2408 		//listView->resizeColumnsToContents();
2409 	}
2410 
2411 #ifdef PLAYLIST_DOWNLOAD
2412 	set->beginGroup("history");
2413 	history_urls->setMaxItems(set->value("max_items", 50).toInt());
2414 	history_urls->fromStringList( set->value("urls", history_urls->toStringList()).toStringList() );
2415 	set->endGroup();
2416 #endif
2417 
2418 	setFilterCaseSensitive(filter_case_sensitive);
2419 	setSortCaseSensitive(sort_case_sensitive);
2420 	proxy->sort(sort_column, (Qt::SortOrder) sort_order);
2421 	filter_edit->setText(filter);
2422 	setAutoSort(auto_sort);
2423 
2424 	if (!listView->isColumnHidden(COL_NUM)) showPositionColumnAct->setChecked(true);
2425 	if (!listView->isColumnHidden(COL_NAME)) showNameColumnAct->setChecked(true);
2426 	if (!listView->isColumnHidden(COL_TIME)) showDurationColumnAct->setChecked(true);
2427 	if (!listView->isColumnHidden(COL_FILENAME)) showFilenameColumnAct->setChecked(true);
2428 	if (!listView->isColumnHidden(COL_SHUFFLE)) showShuffleColumnAct->setChecked(true);
2429 }
2430 
lastDir()2431 QString Playlist::lastDir() {
2432 	QString last_dir = latest_dir;
2433 	return last_dir;
2434 }
2435 
setPositionColumnVisible(bool b)2436 void Playlist::setPositionColumnVisible(bool b) {
2437 	listView->setColumnHidden(COL_NUM, !b);
2438 }
2439 
setNameColumnVisible(bool b)2440 void Playlist::setNameColumnVisible(bool b) {
2441 	listView->setColumnHidden(COL_NAME, !b);
2442 }
2443 
setDurationColumnVisible(bool b)2444 void Playlist::setDurationColumnVisible(bool b) {
2445 	listView->setColumnHidden(COL_TIME, !b);
2446 }
2447 
setFilenameColumnVisible(bool b)2448 void Playlist::setFilenameColumnVisible(bool b) {
2449 	listView->setColumnHidden(COL_FILENAME, !b);
2450 }
2451 
setShuffleColumnVisible(bool b)2452 void Playlist::setShuffleColumnVisible(bool b) {
2453 	listView->setColumnHidden(COL_SHUFFLE, !b);
2454 }
2455 
setAutoSort(bool b)2456 void Playlist::setAutoSort(bool b) {
2457 	proxy->setDynamicSortFilter(b);
2458 }
2459 
autoSort()2460 bool Playlist::autoSort() {
2461 	return proxy->dynamicSortFilter();
2462 }
2463 
setSortCaseSensitive(bool b)2464 void Playlist::setSortCaseSensitive(bool b) {
2465 	Qt::CaseSensitivity c = b ? Qt::CaseSensitive : Qt::CaseInsensitive;
2466 	proxy->setSortCaseSensitivity(c);
2467 }
2468 
sortCaseSensitive()2469 bool Playlist::sortCaseSensitive() {
2470 	return (proxy->sortCaseSensitivity() == Qt::CaseSensitive);
2471 }
2472 
setFilterCaseSensitive(bool b)2473 void Playlist::setFilterCaseSensitive(bool b) {
2474 	Qt::CaseSensitivity c = b ? Qt::CaseSensitive : Qt::CaseInsensitive;
2475 	proxy->setFilterCaseSensitivity(c);
2476 }
2477 
filterCaseSensitive()2478 bool Playlist::filterCaseSensitive() {
2479 	return (proxy->filterCaseSensitivity() == Qt::CaseSensitive);
2480 }
2481 
2482 #ifdef PLAYLIST_DOWNLOAD
openUrl()2483 void Playlist::openUrl() {
2484 	qDebug("Playlist::openUrl");
2485 
2486 	InputURL d(this);
2487 
2488 	// Get url from clipboard
2489 	QString clipboard_text = QApplication::clipboard()->text();
2490 	if (!clipboard_text.isEmpty() && clipboard_text.contains("://")) {
2491 		d.setURL(clipboard_text);
2492 	}
2493 
2494 	for (int n = 0; n < history_urls->count(); n++) {
2495 		d.setURL(history_urls->url(n));
2496 	}
2497 
2498 	if (d.exec() == QDialog::Accepted ) {
2499 		QString url = d.url();
2500 		if (!url.isEmpty()) {
2501 			history_urls->addUrl(url);
2502 			openUrl(url);
2503 		}
2504 	}
2505 }
2506 
openUrl(const QString & orig_url)2507 void Playlist::openUrl(const QString & orig_url) {
2508 	QString url = QString(orig_url);
2509 
2510 	qDebug() << "Playlist::openUrl:" << url;
2511 
2512 #ifdef YT_PLAYLIST_SUPPORT
2513 	if (isYTPlaylist(url)) {
2514 		setCursor(QCursor(Qt::WaitCursor));
2515 		RetrieveYoutubeUrl yt(this);
2516 		QList<itemMap> list = yt.getPlaylistItems(url);
2517 		loadYoutubeList(list);
2518 		unsetCursor();
2519 		return;
2520 	}
2521 #endif
2522 
2523 	downloader->fetchPage(url);
2524 	showLoadingAnimation(true);
2525 }
2526 
playlistDownloaded(QByteArray data)2527 void Playlist::playlistDownloaded(QByteArray data) {
2528 	qDebug("Playlist::playlistDownloaded");
2529 	// Save to a temporary file
2530 	QTemporaryFile tf;
2531 	tf.open();
2532 	tf.write(data);
2533 	tf.close();
2534 	QString tfile = tf.fileName();
2535 	qDebug() << "Playlist::playlistDownloaded: tfile:" << tfile;
2536 
2537 	if (data.contains("#EXTM3U")) {
2538 		load_m3u(tfile, M3U8);
2539 		setPlaylistFilename("");
2540 	}
2541 	else
2542 	if (data.contains("[playlist]")) {
2543 		load_pls(tfile);
2544 		setPlaylistFilename("");
2545 	}
2546 	else
2547 	if (data.contains("xspf.org")) {
2548 		loadXSPF(tfile);
2549 		setPlaylistFilename("");
2550 	}
2551 	else {
2552 		QMessageBox::warning(this, "SMPlayer", tr("It's not possible to load this playlist") +": "+ tr("Unrecognized format."));
2553 	}
2554 
2555 	showLoadingAnimation(false);
2556 }
2557 
errorOcurred(int error_number,QString error_str)2558 void Playlist::errorOcurred(int error_number, QString error_str) {
2559 	showLoadingAnimation(false);
2560 
2561 	qDebug() << "Playlist::errorOcurred:" << error_number << ":" << error_str;
2562 	QMessageBox::warning(this, "SMPlayer", error_str);
2563 }
2564 
showLoadingAnimation(bool b)2565 void Playlist::showLoadingAnimation(bool b) {
2566 	if (b) animation->start(); else animation->stop();
2567 	loading_label_action->setVisible(b);
2568 }
2569 
setMaxItemsUrlHistory(int max_items)2570 void Playlist::setMaxItemsUrlHistory(int max_items) {
2571 	history_urls->setMaxItems(max_items);
2572 }
2573 
maxItemsUrlHistory()2574 int Playlist::maxItemsUrlHistory() {
2575 	return history_urls->maxItems();
2576 }
2577 #endif
2578 
2579 // Language change stuff
changeEvent(QEvent * e)2580 void Playlist::changeEvent(QEvent *e) {
2581 	if (e->type() == QEvent::LanguageChange) {
2582 		retranslateStrings();
2583 	} else {
2584 		QWidget::changeEvent(e);
2585 	}
2586 }
2587 
2588 #include "moc_playlist.cpp"
2589