2 #include "playlist/PrettyItemDelegate.h"
3 #include "networkaccess.h"
4 #include "videowidget.h"
5 #include "minisplitter.h"
7 #include "downloadmanager.h"
10 QMap<QString, QAction*>* globalActions();
11 QMap<QString, QMenu*>* globalMenus();
12 QNetworkAccessManager* networkAccessManager();
15 MediaView::MediaView(QWidget *parent) : QWidget(parent) {
17 reallyStopped = false;
19 QBoxLayout *layout = new QHBoxLayout();
22 splitter = new MiniSplitter(this);
23 splitter->setChildrenCollapsible(false);
25 sortBar = new THBlackBar(this);
26 mostRelevantAction = new QAction(tr("Most relevant"), this);
27 QKeySequence keySequence(Qt::CTRL + Qt::Key_1);
28 mostRelevantAction->setShortcut(keySequence);
29 mostRelevantAction->setStatusTip(mostRelevantAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
30 addAction(mostRelevantAction);
31 connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
32 sortBar->addAction(mostRelevantAction);
33 mostRecentAction = new QAction(tr("Most recent"), this);
34 keySequence = QKeySequence(Qt::CTRL + Qt::Key_2);
35 mostRecentAction->setShortcut(keySequence);
36 mostRecentAction->setStatusTip(mostRecentAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
37 addAction(mostRecentAction);
38 connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
39 sortBar->addAction(mostRecentAction);
40 mostViewedAction = new QAction(tr("Most viewed"), this);
41 keySequence = QKeySequence(Qt::CTRL + Qt::Key_3);
42 mostViewedAction->setShortcut(keySequence);
43 mostViewedAction->setStatusTip(mostViewedAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
44 addAction(mostViewedAction);
45 connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
46 sortBar->addAction(mostViewedAction);
48 listView = new QListView(this);
49 listView->setItemDelegate(new PrettyItemDelegate(this));
50 listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
53 listView->setDragEnabled(true);
54 listView->setAcceptDrops(true);
55 listView->setDropIndicatorShown(true);
56 listView->setDragDropMode(QAbstractItemView::DragDrop);
59 listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
60 listView->setFrameShape( QFrame::NoFrame );
61 listView->setAttribute(Qt::WA_MacShowFocusRect, false);
62 listView->setMinimumSize(320,240);
63 listView->setUniformItemSizes(true);
65 // respond to the user doubleclicking a playlist item
66 connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
68 listModel = new ListModel(this);
69 connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
70 // needed to restore the selection after dragndrop
71 connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
72 listView->setModel(listModel);
74 connect(listView->selectionModel(),
75 SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
76 this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
78 playlistWidget = new PlaylistWidget(this, sortBar, listView);
80 splitter->addWidget(playlistWidget);
82 videoAreaWidget = new VideoAreaWidget(this);
83 videoAreaWidget->setMinimumSize(320,240);
86 // mouse autohide does not work on the Mac (no mouseMoveEvent)
87 videoWidget = new Phonon::VideoWidget(this);
89 videoWidget = new VideoWidget(this);
92 videoAreaWidget->setVideoWidget(videoWidget);
93 videoAreaWidget->setListModel(listModel);
95 loadingWidget = new LoadingWidget(this);
96 videoAreaWidget->setLoadingWidget(loadingWidget);
98 splitter->addWidget(videoAreaWidget);
100 layout->addWidget(splitter);
103 // restore splitter state
105 splitter->restoreState(settings.value("splitter").toByteArray());
107 errorTimer = new QTimer(this);
108 errorTimer->setSingleShot(true);
109 errorTimer->setInterval(3000);
110 connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
112 workaroundTimer = new QTimer(this);
113 workaroundTimer->setSingleShot(true);
114 workaroundTimer->setInterval(3000);
115 connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
118 demoTimer = new QTimer(this);
119 demoTimer->setSingleShot(true);
120 demoTimer->setInterval(60000);
121 connect(demoTimer, SIGNAL(timeout()), SLOT(demoMessage()));
126 void MediaView::initialize() {
127 connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
128 videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu);
129 connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)),
130 this, SLOT(showVideoContextMenu(QPoint)));
133 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
134 this->mediaObject = mediaObject;
135 Phonon::createPath(this->mediaObject, videoWidget);
136 connect(mediaObject, SIGNAL(finished()), this, SLOT(skip()));
137 connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
138 this, SLOT(stateChanged(Phonon::State, Phonon::State)));
139 connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
140 this, SLOT(currentSourceChanged(Phonon::MediaSource)));
141 connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
144 void MediaView::search(SearchParams *searchParams) {
145 reallyStopped = false;
151 videoAreaWidget->clear();
152 workaroundTimer->stop();
155 this->searchParams = searchParams;
157 // start serching for videos
158 listModel->search(searchParams);
160 // this implies that the enum and the bar action order is the same
161 sortBar->setCheckedAction(searchParams->sortBy()-1);
163 listView->setFocus();
167 void MediaView::disappear() {
168 timerPlayFlag = true;
171 void MediaView::handleError(QString message) {
172 videoAreaWidget->showError(message);
173 skippedVideo = listModel->activeVideo();
174 // recover from errors by skipping to the next video
175 errorTimer->start(2000);
178 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/)
181 // qDebug() << "Phonon state: " << newState << oldState;
185 case Phonon::ErrorState:
186 qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
187 handleError(mediaObject->errorString());
190 case Phonon::PlayingState:
192 videoAreaWidget->showVideo();
195 case Phonon::StoppedState:
197 // play() has already been called when setting the source
198 // but Phonon on Linux needs a little more help to start playback
199 if (!reallyStopped) mediaObject->play();
202 // Workaround for Mac playback start problem
203 if (!timerPlayFlag) {
204 workaroundTimer->start();
210 case Phonon::PausedState:
214 case Phonon::BufferingState:
215 //qDebug("buffering");
218 case Phonon::LoadingState:
227 void MediaView::pause() {
228 // qDebug() << "pause() called" << mediaObject->state();
229 switch( mediaObject->state() ) {
230 case Phonon::PlayingState:
231 mediaObject->pause();
239 void MediaView::stop() {
240 listModel->abortSearch();
241 reallyStopped = true;
243 videoAreaWidget->clear();
244 workaroundTimer->stop();
246 listView->selectionModel()->clearSelection();
249 void MediaView::activeRowChanged(int row) {
250 if (reallyStopped) return;
252 Video *video = listModel->videoAt(row);
255 // now that we have a new video to play
256 // stop all the timers
257 workaroundTimer->stop();
260 // immediately show the loading widget
261 videoAreaWidget->showLoading(video);
263 connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
264 // TODO handle signal in a proper slot and impl item error status
265 connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
267 video->loadStreamUrl();
269 // reset the timer flag
270 timerPlayFlag = false;
272 // video title in the statusbar
273 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
274 if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
276 The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
278 // see you in gotStreamUrl...
282 void MediaView::gotStreamUrl(QUrl streamUrl) {
283 if (reallyStopped) return;
285 Video *video = static_cast<Video *>(sender());
287 qDebug() << "Cannot get sender";
290 video->disconnect(this);
293 qDebug() << "Playing" << streamUrl.toString();
294 mediaObject->setCurrentSource(streamUrl);
297 // ensure we always have 10 videos ahead
298 listModel->searchNeeded();
300 // ensure active item is visible
301 int row = listModel->activeRow();
303 QModelIndex index = listModel->index(row, 0, QModelIndex());
304 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
313 void MediaView::itemActivated(const QModelIndex &index) {
314 if (listModel->rowExists(index.row()))
315 listModel->setActiveRow(index.row());
316 // the user doubleclicked on the "Search More" item
317 else listModel->searchMore();
320 void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) {
324 void MediaView::skipVideo() {
325 // skippedVideo is useful for DELAYED skip operations
326 // in order to be sure that we're skipping the video we wanted
327 // and not another one
329 if (listModel->activeVideo() != skippedVideo) {
330 qDebug() << "Skip of video canceled";
333 int nextRow = listModel->rowForVideo(skippedVideo);
335 if (nextRow == -1) return;
336 listModel->setActiveRow(nextRow);
340 void MediaView::skip() {
341 int nextRow = listModel->nextRow();
342 if (nextRow == -1) return;
343 listModel->setActiveRow(nextRow);
346 void MediaView::openWebPage() {
347 Video* video = listModel->activeVideo();
349 mediaObject->pause();
350 QDesktopServices::openUrl(video->webpage());
353 void MediaView::copyWebPage() {
354 Video* video = listModel->activeVideo();
356 QString address = video->webpage().toString();
357 address.remove("&feature=youtube_gdata");
358 QApplication::clipboard()->setText(address);
359 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
360 QString message = tr("You can now paste the YouTube link into another application");
361 if (mainWindow) mainWindow->statusBar()->showMessage(message);
364 void MediaView::copyVideoLink() {
365 Video* video = listModel->activeVideo();
367 QApplication::clipboard()->setText(video->getStreamUrl().toString());
368 QString message = tr("You can now paste the video stream URL into another application")
369 + ". " + tr("The link will be valid only for a limited time.");
370 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
371 if (mainWindow) mainWindow->statusBar()->showMessage(message);
374 void MediaView::removeSelected() {
375 if (!listView->selectionModel()->hasSelection()) return;
376 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
377 listModel->removeIndexes(indexes);
380 void MediaView::selectVideos(QList<Video*> videos) {
381 foreach (Video *video, videos) {
382 QModelIndex index = listModel->indexForVideo(video);
383 listView->selectionModel()->select(index, QItemSelectionModel::Select);
384 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
388 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
389 const bool gotSelection = listView->selectionModel()->hasSelection();
390 The::globalActions()->value("remove")->setEnabled(gotSelection);
391 The::globalActions()->value("moveUp")->setEnabled(gotSelection);
392 The::globalActions()->value("moveDown")->setEnabled(gotSelection);
395 void MediaView::moveUpSelected() {
396 if (!listView->selectionModel()->hasSelection()) return;
398 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
399 qStableSort(indexes.begin(), indexes.end());
400 listModel->move(indexes, true);
402 // set current index after row moves to something more intuitive
403 int row = indexes.first().row();
404 listView->selectionModel()->setCurrentIndex(listModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
407 void MediaView::moveDownSelected() {
408 if (!listView->selectionModel()->hasSelection()) return;
410 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
411 qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
412 listModel->move(indexes, false);
414 // set current index after row moves to something more intuitive (respect 1 static item on bottom)
415 int row = indexes.first().row()+1, max = listModel->rowCount() - 2;
416 listView->selectionModel()->setCurrentIndex(listModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
419 void MediaView::showVideoContextMenu(QPoint point) {
420 The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
423 void MediaView::searchMostRelevant() {
424 searchParams->setSortBy(SearchParams::SortByRelevance);
425 search(searchParams);
428 void MediaView::searchMostRecent() {
429 searchParams->setSortBy(SearchParams::SortByNewest);
430 search(searchParams);
433 void MediaView::searchMostViewed() {
434 searchParams->setSortBy(SearchParams::SortByViewCount);
435 search(searchParams);
438 void MediaView::setPlaylistVisible(bool visible) {
439 playlistWidget->setVisible(visible);
442 void MediaView::timerPlay() {
443 // Workaround Phonon bug on Mac OSX
444 // qDebug() << mediaObject->currentTime();
445 if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
446 // qDebug() << "Mac playback workaround";
447 mediaObject->pause();
448 // QTimer::singleShot(1000, mediaObject, SLOT(play()));
453 void MediaView::saveSplitterState() {
455 settings.setValue("splitter", splitter->saveState());
459 void MediaView::demoMessage() {
460 if (mediaObject->state() != Phonon::PlayingState) return;
461 mediaObject->pause();
464 msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
465 msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::APP_NAME));
466 msgBox.setInformativeText(tr("It allows you to test the application and see if it works for you."));
467 msgBox.setModal(true);
469 QPushButton *quitButton = msgBox.addButton(tr("Continue"), QMessageBox::RejectRole);
470 QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
474 if (msgBox.clickedButton() == buyButton) {
475 QDesktopServices::openUrl(QString(Constants::WEBSITE) + "#download");
482 void MediaView::downloadVideo() {
483 Video* video = listModel->activeVideo();
486 DownloadManager::instance()->addItem(video);
490 The::globalActions()->value("downloads")->setVisible(true);
492 // The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
494 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
495 QString message = tr("Downloading %1").arg(video->title());
496 if (mainWindow) mainWindow->statusBar()->showMessage(message);
499 void MediaView::fullscreen() {
500 videoAreaWidget->setParent(0);
501 videoAreaWidget->showFullScreen();