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 ¤t 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(",", ",");
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(",", ",");
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