]> git.sur5r.net Git - minitube/blob - src/mediaview.cpp
2.1.2 stuff
[minitube] / src / mediaview.cpp
1 /* $BEGIN_LICENSE
2
3 This file is part of Minitube.
4 Copyright 2009, Flavio Tordini <flavio.tordini@gmail.com>
5
6 Minitube is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Minitube is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Minitube.  If not, see <http://www.gnu.org/licenses/>.
18
19 $END_LICENSE */
20
21 #include "mediaview.h"
22 #include "playlistmodel.h"
23 #include "playlistview.h"
24 #include "loadingwidget.h"
25 #include "videoareawidget.h"
26 #include "networkaccess.h"
27 #include "videowidget.h"
28 #include "minisplitter.h"
29 #include "constants.h"
30 #include "downloadmanager.h"
31 #include "downloaditem.h"
32 #include "mainwindow.h"
33 #include "temporary.h"
34 #include "refinesearchwidget.h"
35 #include "sidebarwidget.h"
36 #include "sidebarheader.h"
37 #ifdef APP_ACTIVATION
38 #include "activation.h"
39 #endif
40 #ifdef APP_EXTRA
41 #include "extra.h"
42 #endif
43 #include "videosource.h"
44 #include "ytsearch.h"
45 #include "searchparams.h"
46 #include "ytsinglevideosource.h"
47 #include "channelaggregator.h"
48 #include "utils.h"
49 #include "ytuser.h"
50
51 namespace The {
52 NetworkAccess* http();
53 QHash<QString, QAction*>* globalActions();
54 QHash<QString, QMenu*>* globalMenus();
55 QNetworkAccessManager* networkAccessManager();
56 }
57
58 MediaView* MediaView::instance() {
59     static MediaView *i = new MediaView();
60     return i;
61 }
62
63 MediaView::MediaView(QWidget *parent) : QWidget(parent),
64     stopped(false),
65     downloadItem(0) { }
66
67 void MediaView::initialize() {
68     QBoxLayout *layout = new QVBoxLayout(this);
69     layout->setMargin(0);
70
71     splitter = new MiniSplitter();
72
73     playlistView = new PlaylistView(this);
74     // respond to the user doubleclicking a playlist item
75     connect(playlistView, SIGNAL(activated(const QModelIndex &)),
76             SLOT(itemActivated(const QModelIndex &)));
77
78     playlistModel = new PlaylistModel();
79     connect(playlistModel, SIGNAL(activeRowChanged(int)),
80             SLOT(activeRowChanged(int)));
81     // needed to restore the selection after dragndrop
82     connect(playlistModel, SIGNAL(needSelectionFor(QList<Video*>)),
83             SLOT(selectVideos(QList<Video*>)));
84     playlistView->setModel(playlistModel);
85
86     connect(playlistView->selectionModel(),
87             SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
88             SLOT(selectionChanged(const QItemSelection &, const QItemSelection &)));
89
90     connect(playlistView, SIGNAL(authorPushed(QModelIndex)), SLOT(authorPushed(QModelIndex)));
91
92     sidebar = new SidebarWidget(this);
93     sidebar->setPlaylist(playlistView);
94     connect(sidebar->getRefineSearchWidget(), SIGNAL(searchRefined()),
95             SLOT(searchAgain()));
96     connect(playlistModel, SIGNAL(haveSuggestions(const QStringList &)),
97             sidebar, SLOT(showSuggestions(const QStringList &)));
98     connect(sidebar, SIGNAL(suggestionAccepted(QString)),
99             MainWindow::instance(), SLOT(startToolbarSearch(QString)));
100     splitter->addWidget(sidebar);
101
102     videoAreaWidget = new VideoAreaWidget(this);
103     // videoAreaWidget->setMinimumSize(320,240);
104     videoWidget = new Phonon::VideoWidget(this);
105     videoAreaWidget->setVideoWidget(videoWidget);
106     videoAreaWidget->setListModel(playlistModel);
107
108     loadingWidget = new LoadingWidget(this);
109     videoAreaWidget->setLoadingWidget(loadingWidget);
110
111     splitter->addWidget(videoAreaWidget);
112
113     splitter->setStretchFactor(0, 0);
114     splitter->setStretchFactor(1, 8);
115
116     // restore splitter state
117     QSettings settings;
118     splitter->restoreState(settings.value("splitter").toByteArray());
119     splitter->setChildrenCollapsible(false);
120
121     layout->addWidget(splitter);
122
123     errorTimer = new QTimer(this);
124     errorTimer->setSingleShot(true);
125     errorTimer->setInterval(3000);
126     connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
127
128 #ifdef APP_ACTIVATION
129     demoTimer = new QTimer(this);
130     demoTimer->setSingleShot(true);
131     connect(demoTimer, SIGNAL(timeout()), SLOT(demoMessage()));
132 #endif
133
134     connect(videoAreaWidget, SIGNAL(doubleClicked()),
135             The::globalActions()->value("fullscreen"), SLOT(trigger()));
136
137     QAction* refineSearchAction = The::globalActions()->value("refine-search");
138     connect(refineSearchAction, SIGNAL(toggled(bool)),
139             sidebar, SLOT(toggleRefineSearch(bool)));
140
141     currentVideoActions
142             << The::globalActions()->value("webpage")
143             << The::globalActions()->value("pagelink")
144             << The::globalActions()->value("videolink")
145             << The::globalActions()->value("findVideoParts")
146             << The::globalActions()->value("skip")
147             << The::globalActions()->value("previous")
148             // << The::globalActions()->value("download")
149             << The::globalActions()->value("stopafterthis")
150             << The::globalActions()->value("related-videos")
151             << The::globalActions()->value("refine-search")
152             << The::globalActions()->value("twitter")
153             << The::globalActions()->value("facebook")
154             << The::globalActions()->value("buffer")
155             << The::globalActions()->value("email");
156 }
157
158 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
159     this->mediaObject = mediaObject;
160     Phonon::createPath(mediaObject, videoWidget);
161     connect(mediaObject, SIGNAL(finished()), SLOT(playbackFinished()));
162     connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
163             SLOT(stateChanged(Phonon::State, Phonon::State)));
164     /*
165     const char* s = Constants::NAME;
166     const int l = strlen(s);
167     int t = The::globalActions()->count();
168     for (int i = 0; i < l; i++) {
169         t += s[i];
170         qDebug() << t << The::globalActions()->count();
171     }
172     qDebug() << t << The::globalActions()->count();
173     if (t != s[0]) return;
174     */
175     connect(mediaObject, SIGNAL(aboutToFinish()), SLOT(aboutToFinish()));
176 }
177
178 SearchParams* MediaView::getSearchParams() {
179     VideoSource *videoSource = playlistModel->getVideoSource();
180     if (videoSource && videoSource->metaObject()->className() == QLatin1String("YTSearch")) {
181         YTSearch *search = dynamic_cast<YTSearch *>(videoSource);
182         return search->getSearchParams();
183     }
184     return 0;
185 }
186
187 void MediaView::search(SearchParams *searchParams) {
188     if (!searchParams->keywords().isEmpty()) {
189         if (searchParams->keywords().startsWith("http://") ||
190                 searchParams->keywords().startsWith("https://")) {
191             QString videoId = YTSearch::videoIdFromUrl(searchParams->keywords());
192             if (!videoId.isEmpty()) {
193                 YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource(this);
194                 singleVideoSource->setVideoId(videoId);
195                 setVideoSource(singleVideoSource);
196                 return;
197             }
198         }
199     }
200     setVideoSource(new YTSearch(searchParams, this));
201 }
202
203 void MediaView::setVideoSource(VideoSource *videoSource, bool addToHistory) {
204     stopped = false;
205
206 #ifdef APP_ACTIVATION
207     demoTimer->stop();
208 #endif
209     errorTimer->stop();
210
211     // qDebug() << "Adding VideoSource" << videoSource->getName() << videoSource;
212
213     if (addToHistory) {
214         int currentIndex = getHistoryIndex();
215         if (currentIndex >= 0 && currentIndex < history.size() - 1) {
216             while (history.size() > currentIndex + 1) {
217                 VideoSource *vs = history.takeLast();
218                 if (!vs->parent()) {
219                     qDebug() << "Deleting VideoSource" << vs->getName() << vs;
220                     delete vs;
221                 }
222             }
223         }
224         history.append(videoSource);
225     }
226
227     playlistModel->setVideoSource(videoSource);
228
229     sidebar->showPlaylist();
230     sidebar->getRefineSearchWidget()->setSearchParams(getSearchParams());
231     sidebar->hideSuggestions();
232     sidebar->getHeader()->updateInfo();
233
234     SearchParams *searchParams = getSearchParams();
235     bool isChannel = searchParams && !searchParams->author().isEmpty();
236     playlistView->setClickableAuthors(!isChannel);
237 }
238
239 void MediaView::searchAgain() {
240     VideoSource *currentVideoSource = playlistModel->getVideoSource();
241     setVideoSource(currentVideoSource, false);
242 }
243
244 bool MediaView::canGoBack() {
245     return getHistoryIndex() > 0;
246 }
247
248 void MediaView::goBack() {
249     if (history.size() > 1) {
250         int currentIndex = getHistoryIndex();
251         if (currentIndex > 0) {
252             VideoSource *previousVideoSource = history.at(currentIndex - 1);
253             setVideoSource(previousVideoSource, false);
254         }
255     }
256 }
257
258 bool MediaView::canGoForward() {
259     int currentIndex = getHistoryIndex();
260     return currentIndex >= 0 && currentIndex < history.size() - 1;
261 }
262
263 void MediaView::goForward() {
264     if (canGoForward()) {
265         int currentIndex = getHistoryIndex();
266         VideoSource *nextVideoSource = history.at(currentIndex + 1);
267         setVideoSource(nextVideoSource, false);
268     }
269 }
270
271 int MediaView::getHistoryIndex() {
272     return history.lastIndexOf(playlistModel->getVideoSource());
273 }
274
275 void MediaView::appear() {
276     playlistView->setFocus();
277     Video *currentVideo = playlistModel->activeVideo();
278     if (currentVideo) {
279         MainWindow::instance()->setWindowTitle(
280                     currentVideo->title() + " - " + Constants::NAME);
281         MainWindow::instance()->showMessage(currentVideo->description());
282     }
283 }
284
285 void MediaView::disappear() {
286
287 }
288
289 void MediaView::handleError(QString /* message */) {
290     QTimer::singleShot(500, this, SLOT(startPlaying()));
291 }
292
293 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/) {
294     if (newState == Phonon::PlayingState)
295         videoAreaWidget->showVideo();
296     else if (newState == Phonon::ErrorState) {
297         qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
298         if (mediaObject->errorType() == Phonon::FatalError)
299             handleError(mediaObject->errorString());
300     }
301 }
302
303 void MediaView::pause() {
304     switch( mediaObject->state() ) {
305     case Phonon::PlayingState:
306         mediaObject->pause();
307         break;
308     default:
309         mediaObject->play();
310         break;
311     }
312 }
313
314 QRegExp MediaView::wordRE(QString s) {
315     return QRegExp("\\W" + s + "\\W?", Qt::CaseInsensitive);
316 }
317
318 void MediaView::stop() {
319     stopped = true;
320
321     while (!history.isEmpty()) {
322         VideoSource *videoSource = history.takeFirst();
323         if (!videoSource->parent()) delete videoSource;
324     }
325
326     playlistModel->abortSearch();
327     videoAreaWidget->clear();
328     videoAreaWidget->update();
329     errorTimer->stop();
330     playlistView->selectionModel()->clearSelection();
331     if (downloadItem) {
332         downloadItem->stop();
333         delete downloadItem;
334         downloadItem = 0;
335     }
336     The::globalActions()->value("refine-search")->setChecked(false);
337     updateSubscriptionAction(0, false);
338 #ifdef APP_ACTIVATION
339     demoTimer->stop();
340 #endif
341
342     foreach (QAction *action, currentVideoActions)
343         action->setEnabled(false);
344
345     QAction *a = The::globalActions()->value("download");
346     a->setEnabled(false);
347     a->setVisible(false);
348
349     mediaObject->stop();
350     currentVideoId.clear();
351 }
352
353 const QString & MediaView::getCurrentVideoId() {
354     return currentVideoId;
355 }
356
357 void MediaView::activeRowChanged(int row) {
358     if (stopped) return;
359
360     errorTimer->stop();
361
362     mediaObject->stop();
363     if (downloadItem) {
364         downloadItem->stop();
365         delete downloadItem;
366         downloadItem = 0;
367     }
368
369     Video *video = playlistModel->videoAt(row);
370     if (!video) return;
371
372     videoAreaWidget->showLoading(video);
373
374     connect(video, SIGNAL(gotStreamUrl(QUrl)),
375             SLOT(gotStreamUrl(QUrl)), Qt::UniqueConnection);
376     connect(video, SIGNAL(errorStreamUrl(QString)),
377             SLOT(handleError(QString)), Qt::UniqueConnection);
378     video->loadStreamUrl();
379
380     // video title in titlebar
381     MainWindow::instance()->setWindowTitle(video->title() + " - " + Constants::NAME);
382     MainWindow::instance()->showMessage(video->description());
383
384     // ensure active item is visible
385     if (row != -1) {
386         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
387         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
388     }
389
390     // enable/disable actions
391     The::globalActions()->value("download")->setEnabled(
392                 DownloadManager::instance()->itemForVideo(video) == 0);
393     // The::globalActions()->value("skip")->setEnabled(true);
394     The::globalActions()->value("previous")->setEnabled(row > 0);
395     The::globalActions()->value("stopafterthis")->setEnabled(true);
396     The::globalActions()->value("related-videos")->setEnabled(true);
397
398 #ifndef APP_NO_DOWNLOADS
399     bool enableDownload = video->license() == Video::LicenseCC;
400 #ifdef APP_ACTIVATION
401     enableDownload = enableDownload || Activation::instance().isLegacy();
402 #endif
403 #ifdef APP_DOWNLOADS
404     enableDownload = true;
405 #endif
406     QAction *a = The::globalActions()->value("download");
407     a->setEnabled(enableDownload);
408     a->setVisible(enableDownload);
409 #endif
410
411     updateSubscriptionAction(video, YTUser::isSubscribed(video->userId()));
412
413     foreach (QAction *action, currentVideoActions)
414         action->setEnabled(true);
415
416     // see you in gotStreamUrl...
417 }
418
419 void MediaView::gotStreamUrl(QUrl streamUrl) {
420     if (stopped) return;
421     if (!streamUrl.isValid()) {
422         skip();
423         return;
424     }
425
426     Video *video = static_cast<Video *>(sender());
427     if (!video) {
428         qDebug() << "Cannot get sender in" << __PRETTY_FUNCTION__;
429         return;
430     }
431     video->disconnect(this);
432
433     currentVideoId = video->id();
434
435 #ifdef Q_WS_X11_NO
436     mediaObject->setCurrentSource(streamUrl);
437     mediaObject->play();
438 #else
439     QString tempFile = Temporary::filename();
440     Video *videoCopy = video->clone();
441     if (downloadItem) {
442         downloadItem->stop();
443         delete downloadItem;
444     }
445     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
446     connect(downloadItem, SIGNAL(statusChanged()),
447             SLOT(downloadStatusChanged()), Qt::UniqueConnection);
448     // connect(downloadItem, SIGNAL(progress(int)), SLOT(downloadProgress(int)));
449     connect(downloadItem, SIGNAL(bufferProgress(int)),
450             loadingWidget, SLOT(bufferStatus(int)), Qt::UniqueConnection);
451     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
452     connect(video, SIGNAL(errorStreamUrl(QString)),
453             SLOT(handleError(QString)), Qt::UniqueConnection);
454     connect(downloadItem, SIGNAL(error(QString)),
455             SLOT(handleError(QString)), Qt::UniqueConnection);
456     downloadItem->start();
457 #endif
458
459     // ensure we always have 10 videos ahead
460     playlistModel->searchNeeded();
461
462     // ensure active item is visible
463     int row = playlistModel->activeRow();
464     if (row != -1) {
465         QModelIndex index = playlistModel->index(row, 0, QModelIndex());
466         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
467     }
468
469 #ifdef APP_ACTIVATION
470     if (!Activation::instance().isActivated())
471         demoTimer->start(180000);
472 #endif
473
474 #ifdef APP_EXTRA
475     Extra::notify(video->title(), video->author(), video->formattedDuration());
476 #endif
477
478     ChannelAggregator::instance()->videoWatched(video);
479 }
480
481 /*
482 void MediaView::downloadProgress(int percent) {
483     MainWindow* mainWindow = dynamic_cast<MainWindow*>(window());
484
485     mainWindow->getSeekSlider()->setStyleSheet(" QSlider::groove:horizontal {"
486         "border: 1px solid #999999;"
487         // "border-left: 50px solid rgba(255, 0, 0, 128);"
488         "height: 8px;"
489         "background: qlineargradient(x1:0, y1:0, x2:.5, y2:0, stop:0 rgba(255, 0, 0, 92), stop:"
490         + QString::number(percent/100.0) +
491
492         " rgba(255, 0, 0, 92), stop:" + QString::number((percent+1)/100.0) + " transparent, stop:1 transparent);"
493         "margin: 2px 0;"
494     "}"
495     "QSlider::handle:horizontal {"
496         "background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f8f8f);"
497         "border: 1px solid #5c5c5c;"
498         "width: 16px;"
499         "height: 16px;"
500         "margin: -2px 0;"
501         "border-radius: 8px;"
502     "}"
503
504     );
505 }
506 */
507
508 void MediaView::downloadStatusChanged() {
509     switch(downloadItem->status()) {
510     case Downloading:
511         startPlaying();
512         break;
513     case Starting:
514         // qDebug() << "Starting";
515         break;
516     case Finished:
517         // qDebug() << "Finished" << mediaObject->state();
518         // if (mediaObject->state() == Phonon::StoppedState) startPlaying();
519 #ifdef Q_WS_X11
520         MainWindow::instance()->getSeekSlider()->setEnabled(mediaObject->isSeekable());
521 #endif
522         break;
523     case Failed:
524         // qDebug() << "Failed";
525     case Idle:
526         // qDebug() << "Idle";
527         break;
528     }
529 }
530
531 void MediaView::startPlaying() {
532     if (stopped) return;
533     if (!downloadItem) {
534         skip();
535         return;
536     }
537
538     // go!
539     QString source = downloadItem->currentFilename();
540     // qDebug() << "Playing" << source;
541     mediaObject->setCurrentSource(source);
542     mediaObject->play();
543 #ifdef Q_WS_X11
544     MainWindow::instance()->getSeekSlider()->setEnabled(false);
545 #endif
546
547 }
548
549 void MediaView::itemActivated(const QModelIndex &index) {
550     if (playlistModel->rowExists(index.row())) {
551
552         // if it's the current video, just rewind and play
553         Video *activeVideo = playlistModel->activeVideo();
554         Video *video = playlistModel->videoAt(index.row());
555         if (activeVideo && video && activeVideo == video) {
556             mediaObject->seek(0);
557             mediaObject->play();
558         } else playlistModel->setActiveRow(index.row());
559
560         // the user doubleclicked on the "Search More" item
561     } else {
562         playlistModel->searchMore();
563         playlistView->selectionModel()->clearSelection();
564     }
565 }
566
567 void MediaView::skipVideo() {
568     // skippedVideo is useful for DELAYED skip operations
569     // in order to be sure that we're skipping the video we wanted
570     // and not another one
571     if (skippedVideo) {
572         if (playlistModel->activeVideo() != skippedVideo) {
573             qDebug() << "Skip of video canceled";
574             return;
575         }
576         int nextRow = playlistModel->rowForVideo(skippedVideo);
577         nextRow++;
578         if (nextRow == -1) return;
579         playlistModel->setActiveRow(nextRow);
580     }
581 }
582
583 void MediaView::skip() {
584     int nextRow = playlistModel->nextRow();
585     if (nextRow == -1) return;
586     playlistModel->setActiveRow(nextRow);
587 }
588
589 void MediaView::skipBackward() {
590     int prevRow = playlistModel->previousRow();
591     if (prevRow == -1) return;
592     playlistModel->setActiveRow(prevRow);
593 }
594
595 void MediaView::aboutToFinish() {
596     qint64 currentTime = mediaObject->currentTime();
597     qint64 totalTime = mediaObject->totalTime();
598     qDebug() << __PRETTY_FUNCTION__ << currentTime << totalTime;
599     if (totalTime < 1 || currentTime + 10000 < totalTime) {
600         // mediaObject->seek(mediaObject->currentTime());
601         // QTimer::singleShot(500, this, SLOT(playbackResume()));
602         mediaObject->seek(currentTime);
603         mediaObject->play();
604     }
605 }
606
607 void MediaView::playbackFinished() {
608     if (stopped) return;
609     const int totalTime = mediaObject->totalTime();
610     const int currentTime = mediaObject->currentTime();
611     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime() << totalTime;
612     // add 10 secs for imprecise Phonon backends (VLC, Xine)
613     if (totalTime < 1 || (currentTime > 0 && currentTime + 10000 < totalTime)) {
614         // mediaObject->seek(currentTime);
615         QTimer::singleShot(500, this, SLOT(playbackResume()));
616     } else {
617         QAction* stopAfterThisAction = The::globalActions()->value("stopafterthis");
618         if (stopAfterThisAction->isChecked()) {
619             stopAfterThisAction->setChecked(false);
620         } else skip();
621     }
622 }
623
624 void MediaView::playbackResume() {
625     if (stopped) return;
626     qDebug() << __PRETTY_FUNCTION__ << mediaObject->currentTime();
627     mediaObject->seek(mediaObject->currentTime());
628     mediaObject->play();
629 }
630
631 void MediaView::openWebPage() {
632     Video* video = playlistModel->activeVideo();
633     if (!video) return;
634     mediaObject->pause();
635     QDesktopServices::openUrl(video->webpage());
636 }
637
638 void MediaView::copyWebPage() {
639     Video* video = playlistModel->activeVideo();
640     if (!video) return;
641     QString address = video->webpage().toString();
642     QApplication::clipboard()->setText(address);
643     QString message = tr("You can now paste the YouTube link into another application");
644     MainWindow::instance()->showMessage(message);
645 }
646
647 void MediaView::copyVideoLink() {
648     Video* video = playlistModel->activeVideo();
649     if (!video) return;
650     QApplication::clipboard()->setText(video->getStreamUrl().toEncoded());
651     QString message = tr("You can now paste the video stream URL into another application")
652             + ". " + tr("The link will be valid only for a limited time.");
653     MainWindow::instance()->showMessage(message);
654 }
655
656 void MediaView::removeSelected() {
657     if (!playlistView->selectionModel()->hasSelection()) return;
658     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
659     playlistModel->removeIndexes(indexes);
660 }
661
662 void MediaView::selectVideos(QList<Video*> videos) {
663     foreach (Video *video, videos) {
664         QModelIndex index = playlistModel->indexForVideo(video);
665         playlistView->selectionModel()->select(index, QItemSelectionModel::Select);
666         playlistView->scrollTo(index, QAbstractItemView::EnsureVisible);
667     }
668 }
669
670 void MediaView::selectionChanged(const QItemSelection & /*selected*/,
671                                  const QItemSelection & /*deselected*/) {
672     const bool gotSelection = playlistView->selectionModel()->hasSelection();
673     The::globalActions()->value("remove")->setEnabled(gotSelection);
674     The::globalActions()->value("moveUp")->setEnabled(gotSelection);
675     The::globalActions()->value("moveDown")->setEnabled(gotSelection);
676 }
677
678 void MediaView::moveUpSelected() {
679     if (!playlistView->selectionModel()->hasSelection()) return;
680
681     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
682     qStableSort(indexes.begin(), indexes.end());
683     playlistModel->move(indexes, true);
684
685     // set current index after row moves to something more intuitive
686     int row = indexes.first().row();
687     playlistView->selectionModel()->setCurrentIndex(playlistModel->index(row>1?row:1),
688                                                     QItemSelectionModel::NoUpdate);
689 }
690
691 void MediaView::moveDownSelected() {
692     if (!playlistView->selectionModel()->hasSelection()) return;
693
694     QModelIndexList indexes = playlistView->selectionModel()->selectedIndexes();
695     qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
696     playlistModel->move(indexes, false);
697
698     // set current index after row moves to something more intuitive
699     // (respect 1 static item on bottom)
700     int row = indexes.first().row()+1, max = playlistModel->rowCount() - 2;
701     playlistView->selectionModel()->setCurrentIndex(
702                 playlistModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
703 }
704
705 void MediaView::setPlaylistVisible(bool visible) {
706     if (splitter->widget(0)->isVisible() == visible) return;
707     splitter->widget(0)->setVisible(visible);
708     playlistView->setFocus();
709 }
710
711 bool MediaView::isPlaylistVisible() {
712     return splitter->widget(0)->isVisible();
713 }
714
715 void MediaView::saveSplitterState() {
716     QSettings settings;
717     settings.setValue("splitter", splitter->saveState());
718 }
719
720 #ifdef APP_ACTIVATION
721
722 static QPushButton *continueButton;
723
724 void MediaView::demoMessage() {
725     if (mediaObject->state() != Phonon::PlayingState) return;
726     mediaObject->pause();
727
728     QMessageBox msgBox(this);
729     msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
730     msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::NAME));
731     msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
732     msgBox.setModal(true);
733     // make it a "sheet" on the Mac
734     msgBox.setWindowModality(Qt::WindowModal);
735
736     continueButton = msgBox.addButton("5", QMessageBox::RejectRole);
737     continueButton->setEnabled(false);
738     QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
739
740     QTimeLine *timeLine = new QTimeLine(6000, this);
741     timeLine->setCurveShape(QTimeLine::LinearCurve);
742     timeLine->setFrameRange(5, 0);
743     connect(timeLine, SIGNAL(frameChanged(int)), SLOT(updateContinueButton(int)));
744     timeLine->start();
745
746     msgBox.exec();
747
748     if (msgBox.clickedButton() == buyButton) {
749         MainWindow::instance()->showActivationView();
750     } else {
751         mediaObject->play();
752         demoTimer->start(600000);
753     }
754
755     delete timeLine;
756
757 }
758
759 void MediaView::updateContinueButton(int value) {
760     if (value == 0) {
761         continueButton->setText(tr("Continue"));
762         continueButton->setEnabled(true);
763     } else {
764         continueButton->setText(QString::number(value));
765     }
766 }
767
768 #endif
769
770 void MediaView::downloadVideo() {
771     Video* video = playlistModel->activeVideo();
772     if (!video) return;
773     DownloadManager::instance()->addItem(video);
774     The::globalActions()->value("downloads")->setVisible(true);
775     QString message = tr("Downloading %1").arg(video->title());
776     MainWindow::instance()->showMessage(message);
777 }
778
779 /*
780 void MediaView::snapshot() {
781     QImage image = videoWidget->snapshot();
782     qDebug() << image.size();
783
784     const QPixmap& pixmap = QPixmap::grabWindow(videoWidget->winId());
785     // qDebug() << pixmap.size();
786     videoAreaWidget->showSnapshotPreview(pixmap);
787 }
788 */
789
790 void MediaView::fullscreen() {
791     videoAreaWidget->setParent(0);
792     videoAreaWidget->showFullScreen();
793 }
794
795 /*
796 void MediaView::setSlider(QSlider *slider) {
797     this->slider = slider;
798     // slider->setEnabled(false);
799     slider->setTracking(false);
800     // connect(slider, SIGNAL(valueChanged(int)), SLOT(sliderMoved(int)));
801 }
802
803 void MediaView::sliderMoved(int value) {
804     qDebug() << __func__;
805     int sliderPercent = (value * 100) / (slider->maximum() - slider->minimum());
806     qDebug() << slider->minimum() << value << slider->maximum();
807     if (sliderPercent <= downloadItem->currentPercent()) {
808         qDebug() << sliderPercent << downloadItem->currentPercent();
809         mediaObject->seek(value);
810     } else {
811         seekTo(value);
812     }
813 }
814
815 void MediaView::seekTo(int value) {
816     qDebug() << __func__;
817     mediaObject->pause();
818     errorTimer->stop();
819     // mediaObject->clear();
820
821     QString tempDir = QDesktopServices::storageLocation(QDesktopServices::TempLocation);
822     QString tempFile = tempDir + "/minitube" + QString::number(value) + ".mp4";
823     if (!QFile::remove(tempFile)) {
824         qDebug() << "Cannot remove temp file";
825     }
826     Video *videoCopy = downloadItem->getVideo()->clone();
827     QUrl streamUrl = videoCopy->getStreamUrl();
828     streamUrl.addQueryItem("begin", QString::number(value));
829     if (downloadItem) delete downloadItem;
830     downloadItem = new DownloadItem(videoCopy, streamUrl, tempFile, this);
831     connect(downloadItem, SIGNAL(statusChanged()), SLOT(downloadStatusChanged()));
832     // connect(downloadItem, SIGNAL(finished()), SLOT(itemFinished()));
833     downloadItem->start();
834
835     // slider->setMinimum(value);
836
837 }
838
839 */
840
841 void MediaView::findVideoParts() {
842
843     // parts
844     Video* video = playlistModel->activeVideo();
845     if (!video) return;
846
847     QString query = video->title();
848
849     static QString optionalSpace = "\\s*";
850     static QString staticCounterSeparators = "[\\/\\-]";
851     QString counterSeparators = "( of | " +
852             tr("of", "Used in video parts, as in '2 of 3'") +
853             " |" + staticCounterSeparators + ")";
854
855     // numbers from 1 to 15
856     static QString counterNumber = "([1-9]|1[0-5])";
857
858     // query.remove(QRegExp(counterSeparators + optionalSpace + counterNumber));
859     query.remove(QRegExp(counterNumber + optionalSpace +
860                          counterSeparators + optionalSpace + counterNumber));
861     query.remove(wordRE("pr?t\\.?" + optionalSpace + counterNumber));
862     query.remove(wordRE("ep\\.?" + optionalSpace + counterNumber));
863     query.remove(wordRE("part" + optionalSpace + counterNumber));
864     query.remove(wordRE("episode" + optionalSpace + counterNumber));
865     query.remove(wordRE(tr("part", "This is for video parts, as in 'Cool video - part 1'") +
866                         optionalSpace + counterNumber));
867     query.remove(wordRE(tr("episode",
868                            "This is for video parts, as in 'Cool series - episode 1'") +
869                         optionalSpace + counterNumber));
870     query.remove(QRegExp("[\\(\\)\\[\\]]"));
871
872 #define NUMBERS "one|two|three|four|five|six|seven|eight|nine|ten"
873
874     QRegExp englishNumberRE = QRegExp(QLatin1String(".*(") + NUMBERS + ").*",
875                                       Qt::CaseInsensitive);
876     // bool numberAsWords = englishNumberRE.exactMatch(query);
877     query.remove(englishNumberRE);
878
879     QRegExp localizedNumberRE = QRegExp(QLatin1String(".*(") + tr(NUMBERS) + ").*",
880                                         Qt::CaseInsensitive);
881     // if (!numberAsWords) numberAsWords = localizedNumberRE.exactMatch(query);
882     query.remove(localizedNumberRE);
883
884     SearchParams *searchParams = new SearchParams();
885     searchParams->setTransient(true);
886     searchParams->setKeywords(query);
887     searchParams->setAuthor(video->author());
888
889     /*
890     if (!numberAsWords) {
891         qDebug() << "We don't have number as words";
892         // searchParams->setSortBy(SearchParams::SortByNewest);
893         // TODO searchParams->setReverseOrder(true);
894         // TODO searchParams->setMax(50);
895     }
896     */
897
898     search(searchParams);
899
900 }
901
902 void MediaView::relatedVideos() {
903     Video* video = playlistModel->activeVideo();
904     if (!video) return;
905     YTSingleVideoSource *singleVideoSource = new YTSingleVideoSource();
906     singleVideoSource->setVideoId(video->id());
907     setVideoSource(singleVideoSource);
908     The::globalActions()->value("related-videos")->setEnabled(false);
909 }
910
911 void MediaView::shareViaTwitter() {
912     Video* video = playlistModel->activeVideo();
913     if (!video) return;
914     QUrl url("https://twitter.com/intent/tweet");
915     url.addQueryItem("via", "minitubeapp");
916     url.addQueryItem("text", video->title());
917     url.addQueryItem("url", video->webpage().toString());
918     QDesktopServices::openUrl(url);
919 }
920
921 void MediaView::shareViaFacebook() {
922     Video* video = playlistModel->activeVideo();
923     if (!video) return;
924     QUrl url("https://www.facebook.com/sharer.php");
925     url.addQueryItem("t", video->title());
926     url.addQueryItem("u", video->webpage().toString());
927     QDesktopServices::openUrl(url);
928 }
929
930 void MediaView::shareViaBuffer() {
931     Video* video = playlistModel->activeVideo();
932     if (!video) return;
933     QUrl url("http://bufferapp.com/add");
934     url.addQueryItem("via", "minitubeapp");
935     url.addQueryItem("text", video->title());
936     url.addQueryItem("url", video->webpage().toString());
937     url.addQueryItem("picture", video->thumbnailUrl());
938     QDesktopServices::openUrl(url);
939 }
940
941 void MediaView::shareViaEmail() {
942     Video* video = playlistModel->activeVideo();
943     if (!video) return;
944     QUrl url("mailto:");
945     url.addQueryItem("subject", video->title());
946     QString body = video->title() + "\n" +
947             video->webpage().toString() + "\n\n" +
948             tr("Sent from %1").arg(Constants::NAME) + "\n" +
949             Constants::WEBSITE;
950     url.addQueryItem("body", body);
951     QDesktopServices::openUrl(url);
952 }
953
954 void MediaView::authorPushed(QModelIndex index) {
955     Video* video = playlistModel->videoAt(index.row());
956     if (!video) return;
957
958     QString channel = video->userId();
959     if (channel.isEmpty()) channel = video->author();
960     if (channel.isEmpty()) return;
961
962     SearchParams *searchParams = new SearchParams();
963     searchParams->setAuthor(channel);
964     searchParams->setSortBy(SearchParams::SortByNewest);
965
966     // go!
967     search(searchParams);
968 }
969
970 void MediaView::updateSubscriptionAction(Video *video, bool subscribed) {
971     QAction *subscribeAction = The::globalActions()->value("subscribe-channel");
972
973     QString subscribeTip;
974     QString subscribeText;
975     if (!video) {
976         subscribeText = subscribeAction->property("originalText").toString();
977         subscribeAction->setEnabled(false);
978     } else if (subscribed) {
979         subscribeText = tr("Unsubscribe from %1").arg(video->author());
980         subscribeTip = subscribeText;
981         subscribeAction->setEnabled(true);
982     } else {
983         subscribeText = tr("Subscribe to %1").arg(video->author());
984         subscribeTip = subscribeText;
985         subscribeAction->setEnabled(true);
986     }
987     subscribeAction->setText(subscribeText);
988     subscribeAction->setStatusTip(subscribeTip);
989
990     if (subscribed) {
991 #ifdef Q_WS_X11
992         static QIcon tintedIcon;
993         if (tintedIcon.isNull()) {
994             QList<QSize> sizes;
995             sizes << QSize(16, 16);
996             tintedIcon = Utils::tintedIcon("bookmark-new", QColor(254, 240, 0), sizes);
997         }
998         subscribeAction->setIcon(tintedIcon);
999 #else
1000         subscribeAction->setIcon(Utils::icon("bookmark-remove"));
1001 #endif
1002     } else {
1003         subscribeAction->setIcon(Utils::icon("bookmark-new"));
1004     }
1005
1006     Utils::setupAction(subscribeAction);
1007 }
1008
1009 void MediaView::toggleSubscription() {
1010     Video *video = playlistModel->activeVideo();
1011     if (!video) return;
1012     QString userId = video->userId();
1013     if (userId.isEmpty()) return;
1014     bool subscribed = YTUser::isSubscribed(userId);
1015     if (subscribed) YTUser::unsubscribe(userId);
1016     else YTUser::subscribe(userId);
1017     updateSubscriptionAction(video, !subscribed);
1018 }