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) {
21 reallyStopped = false;
23 QBoxLayout *layout = new QHBoxLayout();
26 splitter = new MiniSplitter(this);
27 splitter->setChildrenCollapsible(false);
29 sortBar = new THBlackBar(this);
30 mostRelevantAction = new QAction(tr("Most relevant"), this);
31 QKeySequence keySequence(Qt::CTRL + Qt::Key_1);
32 mostRelevantAction->setShortcut(keySequence);
33 mostRelevantAction->setStatusTip(mostRelevantAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
34 addAction(mostRelevantAction);
35 connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
36 sortBar->addAction(mostRelevantAction);
37 mostRecentAction = new QAction(tr("Most recent"), this);
38 keySequence = QKeySequence(Qt::CTRL + Qt::Key_2);
39 mostRecentAction->setShortcut(keySequence);
40 mostRecentAction->setStatusTip(mostRecentAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
41 addAction(mostRecentAction);
42 connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
43 sortBar->addAction(mostRecentAction);
44 mostViewedAction = new QAction(tr("Most viewed"), this);
45 keySequence = QKeySequence(Qt::CTRL + Qt::Key_3);
46 mostViewedAction->setShortcut(keySequence);
47 mostViewedAction->setStatusTip(mostViewedAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
48 addAction(mostViewedAction);
49 connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
50 sortBar->addAction(mostViewedAction);
52 listView = new QListView(this);
53 listView->setItemDelegate(new PrettyItemDelegate(this));
54 listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
57 listView->setDragEnabled(true);
58 listView->setAcceptDrops(true);
59 listView->setDropIndicatorShown(true);
60 listView->setDragDropMode(QAbstractItemView::DragDrop);
63 listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
64 listView->setFrameShape( QFrame::NoFrame );
65 listView->setAttribute(Qt::WA_MacShowFocusRect, false);
66 listView->setMinimumSize(320,240);
67 listView->setUniformItemSizes(true);
69 // respond to the user doubleclicking a playlist item
70 connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
72 listModel = new ListModel(this);
73 connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
74 // needed to restore the selection after dragndrop
75 connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
76 listView->setModel(listModel);
78 connect(listView->selectionModel(),
79 SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
80 this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
82 playlistWidget = new PlaylistWidget(this, sortBar, listView);
84 splitter->addWidget(playlistWidget);
86 videoAreaWidget = new VideoAreaWidget(this);
87 videoAreaWidget->setMinimumSize(320,240);
90 // mouse autohide does not work on the Mac (no mouseMoveEvent)
91 videoWidget = new Phonon::VideoWidget(this);
93 videoWidget = new VideoWidget(this);
96 videoAreaWidget->setVideoWidget(videoWidget);
97 videoAreaWidget->setListModel(listModel);
99 loadingWidget = new LoadingWidget(this);
100 videoAreaWidget->setLoadingWidget(loadingWidget);
102 splitter->addWidget(videoAreaWidget);
104 layout->addWidget(splitter);
107 // restore splitter state
109 splitter->restoreState(settings.value("splitter").toByteArray());
111 errorTimer = new QTimer(this);
112 errorTimer->setSingleShot(true);
113 errorTimer->setInterval(3000);
114 connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
116 workaroundTimer = new QTimer(this);
117 workaroundTimer->setSingleShot(true);
118 workaroundTimer->setInterval(3000);
119 connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
123 void MediaView::initialize() {
124 connect(videoAreaWidget, SIGNAL(doubleClicked()), The::globalActions()->value("fullscreen"), SLOT(trigger()));
125 videoAreaWidget->setContextMenuPolicy(Qt::CustomContextMenu);
126 connect(videoAreaWidget, SIGNAL(customContextMenuRequested(QPoint)),
127 this, SLOT(showVideoContextMenu(QPoint)));
130 void MediaView::setMediaObject(Phonon::MediaObject *mediaObject) {
131 this->mediaObject = mediaObject;
132 Phonon::createPath(this->mediaObject, videoWidget);
133 connect(mediaObject, SIGNAL(finished()), this, SLOT(skip()));
134 connect(mediaObject, SIGNAL(stateChanged(Phonon::State, Phonon::State)),
135 this, SLOT(stateChanged(Phonon::State, Phonon::State)));
136 connect(mediaObject, SIGNAL(currentSourceChanged(Phonon::MediaSource)),
137 this, SLOT(currentSourceChanged(Phonon::MediaSource)));
138 connect(mediaObject, SIGNAL(bufferStatus(int)), loadingWidget, SLOT(bufferStatus(int)));
141 void MediaView::search(SearchParams *searchParams) {
142 reallyStopped = false;
148 videoAreaWidget->clear();
149 workaroundTimer->stop();
152 this->searchParams = searchParams;
154 // start serching for videos
155 listModel->search(searchParams);
157 // this implies that the enum and the bar action order is the same
158 sortBar->setCheckedAction(searchParams->sortBy()-1);
160 listView->setFocus();
164 void MediaView::disappear() {
165 timerPlayFlag = true;
168 void MediaView::handleError(QString message) {
169 videoAreaWidget->showError(message);
170 skippedVideo = listModel->activeVideo();
171 // recover from errors by skipping to the next video
172 errorTimer->start(2000);
175 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/)
178 // qDebug() << "Phonon state: " << newState << oldState;
182 case Phonon::ErrorState:
183 qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
184 handleError(mediaObject->errorString());
187 case Phonon::PlayingState:
189 videoAreaWidget->showVideo();
192 case Phonon::StoppedState:
194 // play() has already been called when setting the source
195 // but Phonon on Linux needs a little more help to start playback
196 if (!reallyStopped) mediaObject->play();
199 // Workaround for Mac playback start problem
200 if (!timerPlayFlag) {
201 workaroundTimer->start();
207 case Phonon::PausedState:
211 case Phonon::BufferingState:
212 //qDebug("buffering");
215 case Phonon::LoadingState:
224 void MediaView::pause() {
225 // qDebug() << "pause() called" << mediaObject->state();
226 switch( mediaObject->state() ) {
227 case Phonon::PlayingState:
228 mediaObject->pause();
236 void MediaView::stop() {
237 listModel->abortSearch();
238 reallyStopped = true;
240 videoAreaWidget->clear();
241 workaroundTimer->stop();
243 listView->selectionModel()->clearSelection();
246 void MediaView::activeRowChanged(int row) {
247 if (reallyStopped) return;
249 Video *video = listModel->videoAt(row);
252 // now that we have a new video to play
253 // stop all the timers
254 workaroundTimer->stop();
257 // immediately show the loading widget
258 videoAreaWidget->showLoading(video);
260 connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
261 // TODO handle signal in a proper slot and impl item error status
262 connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
264 video->loadStreamUrl();
266 // reset the timer flag
267 timerPlayFlag = false;
269 // video title in the statusbar
270 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
271 if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
273 The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
275 // see you in gotStreamUrl...
279 void MediaView::gotStreamUrl(QUrl streamUrl) {
280 if (reallyStopped) return;
282 Video *video = static_cast<Video *>(sender());
284 qDebug() << "Cannot get sender";
287 video->disconnect(this);
290 qDebug() << "Playing" << streamUrl.toString();
291 mediaObject->setCurrentSource(streamUrl);
294 // ensure we always have 10 videos ahead
295 listModel->searchNeeded();
297 // ensure active item is visible
298 int row = listModel->activeRow();
300 QModelIndex index = listModel->index(row, 0, QModelIndex());
301 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
305 if (tracksPlayed > 1) demoExpired();
311 void MediaView::itemActivated(const QModelIndex &index) {
312 if (listModel->rowExists(index.row()))
313 listModel->setActiveRow(index.row());
314 // the user doubleclicked on the "Search More" item
315 else listModel->searchMore();
318 void MediaView::currentSourceChanged(const Phonon::MediaSource /* source */ ) {
322 void MediaView::skipVideo() {
323 // skippedVideo is useful for DELAYED skip operations
324 // in order to be sure that we're skipping the video we wanted
325 // and not another one
327 if (listModel->activeVideo() != skippedVideo) {
328 qDebug() << "Skip of video canceled";
331 int nextRow = listModel->rowForVideo(skippedVideo);
333 if (nextRow == -1) return;
334 listModel->setActiveRow(nextRow);
338 void MediaView::skip() {
339 int nextRow = listModel->nextRow();
340 if (nextRow == -1) return;
341 listModel->setActiveRow(nextRow);
344 void MediaView::openWebPage() {
345 Video* video = listModel->activeVideo();
347 mediaObject->pause();
348 QDesktopServices::openUrl(video->webpage());
351 void MediaView::copyWebPage() {
352 Video* video = listModel->activeVideo();
354 QString address = video->webpage().toString();
355 address.remove("&feature=youtube_gdata");
356 QApplication::clipboard()->setText(address);
357 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
358 QString message = tr("You can now paste the YouTube link into another application");
359 if (mainWindow) mainWindow->statusBar()->showMessage(message);
362 void MediaView::copyVideoLink() {
363 Video* video = listModel->activeVideo();
365 QApplication::clipboard()->setText(video->getStreamUrl().toString());
366 QString message = tr("You can now paste the video stream URL into another application")
367 + ". " + tr("The link will be valid only for a limited time.");
368 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
369 if (mainWindow) mainWindow->statusBar()->showMessage(message);
372 void MediaView::removeSelected() {
373 if (!listView->selectionModel()->hasSelection()) return;
374 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
375 listModel->removeIndexes(indexes);
378 void MediaView::selectVideos(QList<Video*> videos) {
379 foreach (Video *video, videos) {
380 QModelIndex index = listModel->indexForVideo(video);
381 listView->selectionModel()->select(index, QItemSelectionModel::Select);
382 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
386 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
387 const bool gotSelection = listView->selectionModel()->hasSelection();
388 The::globalActions()->value("remove")->setEnabled(gotSelection);
389 The::globalActions()->value("moveUp")->setEnabled(gotSelection);
390 The::globalActions()->value("moveDown")->setEnabled(gotSelection);
393 void MediaView::moveUpSelected() {
394 if (!listView->selectionModel()->hasSelection()) return;
396 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
397 qStableSort(indexes.begin(), indexes.end());
398 listModel->move(indexes, true);
400 // set current index after row moves to something more intuitive
401 int row = indexes.first().row();
402 listView->selectionModel()->setCurrentIndex(listModel->index(row>1?row:1), QItemSelectionModel::NoUpdate);
405 void MediaView::moveDownSelected() {
406 if (!listView->selectionModel()->hasSelection()) return;
408 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
409 qStableSort(indexes.begin(), indexes.end(), qGreater<QModelIndex>());
410 listModel->move(indexes, false);
412 // set current index after row moves to something more intuitive (respect 1 static item on bottom)
413 int row = indexes.first().row()+1, max = listModel->rowCount() - 2;
414 listView->selectionModel()->setCurrentIndex(listModel->index(row>max?max:row), QItemSelectionModel::NoUpdate);
417 void MediaView::showVideoContextMenu(QPoint point) {
418 The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
421 void MediaView::searchMostRelevant() {
422 searchParams->setSortBy(SearchParams::SortByRelevance);
423 search(searchParams);
426 void MediaView::searchMostRecent() {
427 searchParams->setSortBy(SearchParams::SortByNewest);
428 search(searchParams);
431 void MediaView::searchMostViewed() {
432 searchParams->setSortBy(SearchParams::SortByViewCount);
433 search(searchParams);
436 void MediaView::setPlaylistVisible(bool visible) {
437 playlistWidget->setVisible(visible);
440 void MediaView::timerPlay() {
441 // Workaround Phonon bug on Mac OSX
442 // qDebug() << mediaObject->currentTime();
443 if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
444 // qDebug() << "Mac playback workaround";
445 mediaObject->pause();
446 // QTimer::singleShot(1000, mediaObject, SLOT(play()));
451 void MediaView::saveSplitterState() {
453 settings.setValue("splitter", splitter->saveState());
457 void MediaView::demoExpired() {
458 mediaObject->pause();
461 msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
462 msgBox.setText(tr("This is just the demo version of %1.").arg(Constants::APP_NAME) + " " +
463 tr("It allows you to test the application and see if it works for you.")
465 msgBox.setModal(true);
467 QPushButton *quitButton = msgBox.addButton(tr("Continue"), QMessageBox::RejectRole);
468 QPushButton *buyButton = msgBox.addButton(tr("Get the full version"), QMessageBox::ActionRole);
472 if (msgBox.clickedButton() == buyButton) {
473 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();