2 #include "playlist/PrettyItemDelegate.h"
3 #include "networkaccess.h"
4 #include "videowidget.h"
5 #include "minisplitter.h"
6 #include "flickcharm.h"
9 QMap<QString, QAction*>* globalActions();
10 QMap<QString, QMenu*>* globalMenus();
11 QNetworkAccessManager* networkAccessManager();
14 MediaView::MediaView(QWidget *parent) : QWidget(parent) {
16 reallyStopped = false;
18 QBoxLayout *layout = new QHBoxLayout();
21 splitter = new MiniSplitter(this);
22 splitter->setChildrenCollapsible(false);
24 sortBar = new THBlackBar(this);
25 mostRelevantAction = new QAction(tr("Most relevant"), this);
26 QKeySequence keySequence(Qt::CTRL + Qt::Key_1);
27 mostRelevantAction->setShortcut(keySequence);
28 mostRelevantAction->setStatusTip(mostRelevantAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
29 addAction(mostRelevantAction);
30 connect(mostRelevantAction, SIGNAL(triggered()), this, SLOT(searchMostRelevant()), Qt::QueuedConnection);
31 sortBar->addAction(mostRelevantAction);
32 mostRecentAction = new QAction(tr("Most recent"), this);
33 keySequence = QKeySequence(Qt::CTRL + Qt::Key_2);
34 mostRecentAction->setShortcut(keySequence);
35 mostRecentAction->setStatusTip(mostRecentAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
36 addAction(mostRecentAction);
37 connect(mostRecentAction, SIGNAL(triggered()), this, SLOT(searchMostRecent()), Qt::QueuedConnection);
38 sortBar->addAction(mostRecentAction);
39 mostViewedAction = new QAction(tr("Most viewed"), this);
40 keySequence = QKeySequence(Qt::CTRL + Qt::Key_3);
41 mostViewedAction->setShortcut(keySequence);
42 mostViewedAction->setStatusTip(mostViewedAction->text() + " (" + keySequence.toString(QKeySequence::NativeText) + ")");
43 addAction(mostViewedAction);
44 connect(mostViewedAction, SIGNAL(triggered()), this, SLOT(searchMostViewed()), Qt::QueuedConnection);
45 sortBar->addAction(mostViewedAction);
47 listView = new QListView(this);
48 listView->setItemDelegate(new PrettyItemDelegate(this));
49 listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
52 listView->setDragEnabled(true);
53 listView->setAcceptDrops(true);
54 listView->setDropIndicatorShown(true);
55 listView->setDragDropMode(QAbstractItemView::DragDrop);
58 listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
59 listView->setFrameShape( QFrame::NoFrame );
60 listView->setAttribute(Qt::WA_MacShowFocusRect, false);
61 listView->setMinimumSize(320,240);
62 listView->setUniformItemSizes(true);
64 // respond to the user doubleclicking a playlist item
65 connect(listView, SIGNAL(activated(const QModelIndex &)), this, SLOT(itemActivated(const QModelIndex &)));
67 listModel = new ListModel(this);
68 connect(listModel, SIGNAL(activeRowChanged(int)), this, SLOT(activeRowChanged(int)));
69 // needed to restore the selection after dragndrop
70 connect(listModel, SIGNAL(needSelectionFor(QList<Video*>)), this, SLOT(selectVideos(QList<Video*>)));
71 listView->setModel(listModel);
73 connect(listView->selectionModel(),
74 SIGNAL(selectionChanged ( const QItemSelection & , const QItemSelection & )),
75 this, SLOT(selectionChanged ( const QItemSelection & , const QItemSelection & )));
77 playlistWidget = new PlaylistWidget(this, sortBar, listView);
79 splitter->addWidget(playlistWidget);
81 videoAreaWidget = new VideoAreaWidget(this);
82 videoAreaWidget->setMinimumSize(320,240);
85 // mouse autohide does not work on the Mac (no mouseMoveEvent)
86 videoWidget = new Phonon::VideoWidget(this);
88 videoWidget = new VideoWidget(this);
91 videoAreaWidget->setVideoWidget(videoWidget);
92 videoAreaWidget->setListModel(listModel);
94 loadingWidget = new LoadingWidget(this);
95 videoAreaWidget->setLoadingWidget(loadingWidget);
97 splitter->addWidget(videoAreaWidget);
99 layout->addWidget(splitter);
102 // restore splitter state
104 splitter->restoreState(settings.value("splitter").toByteArray());
106 errorTimer = new QTimer(this);
107 errorTimer->setSingleShot(true);
108 errorTimer->setInterval(3000);
109 connect(errorTimer, SIGNAL(timeout()), SLOT(skipVideo()));
111 workaroundTimer = new QTimer(this);
112 workaroundTimer->setSingleShot(true);
113 workaroundTimer->setInterval(3000);
114 connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
116 // TODO Enable this on touch devices
117 // FlickCharm *flickCharm = new FlickCharm(this);
118 // flickCharm->activateOn(listView);
122 MediaView::~MediaView() {
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;
147 videoAreaWidget->clear();
148 workaroundTimer->stop();
151 this->searchParams = searchParams;
153 // start serching for videos
154 listModel->search(searchParams);
156 // this implies that the enum and the bar action order is the same
157 sortBar->setCheckedAction(searchParams->sortBy()-1);
159 listView->setFocus();
163 void MediaView::disappear() {
164 timerPlayFlag = true;
167 void MediaView::handleError(QString message) {
168 videoAreaWidget->showError(message);
169 skippedVideo = listModel->activeVideo();
170 // recover from errors by skipping to the next video
171 errorTimer->start(2000);
174 void MediaView::stateChanged(Phonon::State newState, Phonon::State /*oldState*/)
177 // qDebug() << "Phonon state: " << newState << oldState;
181 case Phonon::ErrorState:
182 qDebug() << "Phonon error:" << mediaObject->errorString() << mediaObject->errorType();
183 handleError(mediaObject->errorString());
186 case Phonon::PlayingState:
188 videoAreaWidget->showVideo();
191 case Phonon::StoppedState:
193 // play() has already been called when setting the source
194 // but Phonon on Linux needs a little more help to start playback
195 if (!reallyStopped) mediaObject->play();
198 // Workaround for Mac playback start problem
199 if (!timerPlayFlag) {
200 workaroundTimer->start();
206 case Phonon::PausedState:
210 case Phonon::BufferingState:
211 //qDebug("buffering");
214 case Phonon::LoadingState:
223 void MediaView::pause() {
224 // qDebug() << "pause() called" << mediaObject->state();
225 switch( mediaObject->state() ) {
226 case Phonon::PlayingState:
227 mediaObject->pause();
235 void MediaView::stop() {
236 listModel->abortSearch();
237 reallyStopped = true;
239 videoAreaWidget->clear();
240 workaroundTimer->stop();
242 listView->selectionModel()->clearSelection();
245 void MediaView::activeRowChanged(int row) {
246 if (reallyStopped) return;
248 Video *video = listModel->videoAt(row);
251 // now that we have a new video to play
252 // stop all the timers
253 workaroundTimer->stop();
256 // immediately show the loading widget
257 videoAreaWidget->showLoading(video);
259 connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
260 // TODO handle signal in a proper slot and impl item error status
261 connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
263 video->loadStreamUrl();
265 // reset the timer flag
266 timerPlayFlag = false;
268 // video title in the statusbar
269 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
270 if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
272 // see you in gotStreamUrl...
276 void MediaView::gotStreamUrl(QUrl streamUrl) {
277 if (reallyStopped) return;
280 mediaObject->setCurrentSource(streamUrl);
283 // ensure we always have 10 videos ahead
284 listModel->searchNeeded();
286 // ensure active item is visible
287 int row = listModel->activeRow();
289 QModelIndex index = listModel->index(row, 0, QModelIndex());
290 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
294 void MediaView::itemActivated(const QModelIndex &index) {
295 if (listModel->rowExists(index.row()))
296 listModel->setActiveRow(index.row());
297 // the user doubleclicked on the "Search More" item
298 else listModel->searchMore();
301 void MediaView::currentSourceChanged(const Phonon::MediaSource source) {
302 qDebug() << "Playing" << source.url().toString();
305 void MediaView::skipVideo() {
306 // skippedVideo is useful for DELAYED skip operations
307 // in order to be sure that we're skipping the video we wanted
308 // and not another one
310 if (listModel->activeVideo() != skippedVideo) {
311 qDebug() << "Skip of video canceled";
314 int nextRow = listModel->rowForVideo(skippedVideo);
316 if (nextRow == -1) return;
317 listModel->setActiveRow(nextRow);
321 void MediaView::skip() {
322 int nextRow = listModel->nextRow();
323 if (nextRow == -1) return;
324 listModel->setActiveRow(nextRow);
327 void MediaView::openWebPage() {
328 Video* video = listModel->activeVideo();
330 mediaObject->pause();
331 QDesktopServices::openUrl(video->webpage());
334 void MediaView::copyWebPage() {
335 Video* video = listModel->activeVideo();
337 QString address = video->webpage().toString();
338 address.remove("&feature=youtube_gdata");
339 QApplication::clipboard()->setText(address);
340 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
341 QString message = tr("You can now paste the YouTube link into another application");
342 if (mainWindow) mainWindow->statusBar()->showMessage(message);
345 void MediaView::copyVideoLink() {
346 Video* video = listModel->activeVideo();
348 QApplication::clipboard()->setText(video->getStreamUrl().toString());
349 QString message = tr("You can now paste the video stream URL into another application")
350 + ". " + tr("The link will be valid only for a limited time.");
351 QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
352 if (mainWindow) mainWindow->statusBar()->showMessage(message);
355 void MediaView::removeSelected() {
356 if (!listView->selectionModel()->hasSelection()) return;
357 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
358 listModel->removeIndexes(indexes);
361 void MediaView::selectVideos(QList<Video*> videos) {
362 foreach (Video *video, videos) {
363 QModelIndex index = listModel->indexForVideo(video);
364 listView->selectionModel()->select(index, QItemSelectionModel::Select);
365 listView->scrollTo(index, QAbstractItemView::EnsureVisible);
369 void MediaView::selectionChanged(const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) {
370 const bool gotSelection = listView->selectionModel()->hasSelection();
371 The::globalActions()->value("remove")->setEnabled(gotSelection);
372 The::globalActions()->value("moveUp")->setEnabled(gotSelection);
373 The::globalActions()->value("moveDown")->setEnabled(gotSelection);
376 void MediaView::moveUpSelected() {
377 if (!listView->selectionModel()->hasSelection()) return;
378 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
379 listModel->move(indexes, true);
382 void MediaView::moveDownSelected() {
383 if (!listView->selectionModel()->hasSelection()) return;
384 QModelIndexList indexes = listView->selectionModel()->selectedIndexes();
385 listModel->move(indexes, false);
388 void MediaView::showVideoContextMenu(QPoint point) {
389 The::globalMenus()->value("video")->popup(videoWidget->mapToGlobal(point));
392 void MediaView::searchMostRelevant() {
393 searchParams->setSortBy(SearchParams::SortByRelevance);
394 search(searchParams);
397 void MediaView::searchMostRecent() {
398 searchParams->setSortBy(SearchParams::SortByNewest);
399 search(searchParams);
402 void MediaView::searchMostViewed() {
403 searchParams->setSortBy(SearchParams::SortByViewCount);
404 search(searchParams);
407 void MediaView::setPlaylistVisible(bool visible) {
408 playlistWidget->setVisible(visible);
411 void MediaView::timerPlay() {
412 // Workaround Phonon bug on Mac OSX
413 // qDebug() << mediaObject->currentTime();
414 if (mediaObject->currentTime() <= 0 && mediaObject->state() == Phonon::PlayingState) {
415 // qDebug() << "Mac playback workaround";
416 mediaObject->pause();
417 // QTimer::singleShot(1000, mediaObject, SLOT(play()));
422 void MediaView::saveSplitterState() {
424 settings.setValue("splitter", splitter->saveState());