]> git.sur5r.net Git - minitube/commitdiff
Video downloads!
authorFlavio Tordini <flavio.tordini@gmail.com>
Sat, 18 Sep 2010 08:38:33 +0000 (10:38 +0200)
committerFlavio Tordini <flavio.tordini@gmail.com>
Sat, 18 Sep 2010 08:38:33 +0000 (10:38 +0200)
35 files changed:
CHANGES
TODO
minitube.pro
src/AboutView.cpp
src/ListModel.cpp
src/ListModel.h
src/MainWindow.cpp
src/MainWindow.h
src/MediaView.cpp
src/MediaView.h
src/SearchView.cpp
src/downloaditem.cpp [new file with mode: 0644]
src/downloaditem.h [new file with mode: 0644]
src/downloadlistview.cpp [new file with mode: 0644]
src/downloadlistview.h [new file with mode: 0644]
src/downloadmanager.cpp [new file with mode: 0644]
src/downloadmanager.h [new file with mode: 0644]
src/downloadmodel.cpp [new file with mode: 0644]
src/downloadmodel.h [new file with mode: 0644]
src/downloadsettings.cpp [new file with mode: 0644]
src/downloadsettings.h [new file with mode: 0644]
src/downloadview.cpp [new file with mode: 0644]
src/downloadview.h [new file with mode: 0644]
src/flickcharm.cpp [deleted file]
src/flickcharm.h [deleted file]
src/googlesuggest.cpp
src/iconloader/qticonloader.cpp
src/main.cpp
src/playlist/PrettyItemDelegate.cpp
src/playlist/PrettyItemDelegate.h
src/searchlineedit.cpp
src/thlibrary/thblackbar.cpp
src/video.cpp
src/video.h
src/youtubesearch.cpp

diff --git a/CHANGES b/CHANGES
index 889226777a0c1f6b73e02d21f43c6df72ae70ef7..6355232d4b860284024b8caadaf37f863822a425 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,8 @@
-1.2
-- Support for media keys on OS X and GNOME
+1.2 - Sep ??, 2010
+- Ability to download videos
+- Support for media keys on GNOME
+- More style, especially on the Mac
+- Fixed crash when trying delete or move the the las playlist item
 
 1.1 - Jul 27, 2010
 - Minitube now correctly plays cat and mouse with YouTube
diff --git a/TODO b/TODO
index 954bd98dc01840d9b4262adb92bc0b40d71e0aa1..62c8c002bbea41e101253102024e36b36e15acc9 100644 (file)
--- a/TODO
+++ b/TODO
@@ -2,11 +2,6 @@
 
 ## Killer features
 
-- Video download
-    Download status on the status bar. When clicked, a download manager view appears.
-    Videos should be downloaded in the current format
-    directly on the Desktop or Downloads dir without asking for a location.
-
 - YouTube related videos
     List of related videos identical to the playlist.
     When a related video is clicked Minitube will keep playing the next related videos.
@@ -21,7 +16,8 @@
 
 - Windows build
     Stefan Brueck has compiled on Windows but there are problems with Phonon's directX backend
-    Marco di Antonio tried with the Mplayer backend, but it is very unstable
+    Marco di Antonio tried with the Mplayer backend, but it is very unstable.
+    Waiting for the VLC backend to work.
 
 - Subtitles, see http://google2srt.sourceforge.net/
 
index 94fa98b3878997a7436dc1f56f1f33320c0867a7..bea0df2365237761eda4c7fa457da58a6a127116 100755 (executable)
@@ -1,6 +1,6 @@
 CONFIG += release
 TEMPLATE = app
-VERSION = 1.1
+VERSION = 1.2
 DEFINES += APP_VERSION="$$VERSION"
 INCLUDEPATH += /usr/include/phonon
 
@@ -10,9 +10,7 @@ TARGET = minitube
 QT += network \
     xml \
     phonon
-
 include(src/qtsingleapplication/qtsingleapplication.pri)
-
 HEADERS += src/MainWindow.h \
     src/SearchView.h \
     src/MediaView.h \
@@ -40,12 +38,17 @@ HEADERS += src/MainWindow.h \
     src/videoareawidget.h \
     src/googlesuggest.h \
     src/videowidget.h \
-    src/flickcharm.h \
     src/videodefinition.h \
     src/fontutils.h \
     src/thlibrary/thblackbar.h \
     src/globalshortcuts.h \
-    src/globalshortcutbackend.h
+    src/globalshortcutbackend.h \
+    src/downloadmanager.h \
+    src/downloaditem.h \
+    src/downloadview.h \
+    src/downloadmodel.h \
+    src/downloadlistview.h \
+    src/downloadsettings.h
 SOURCES += src/main.cpp \
     src/MainWindow.cpp \
     src/SearchView.cpp \
@@ -71,13 +74,18 @@ SOURCES += src/main.cpp \
     src/videoareawidget.cpp \
     src/googlesuggest.cpp \
     src/videowidget.cpp \
-    src/flickcharm.cpp \
     src/videodefinition.cpp \
     src/constants.cpp \
     src/fontutils.cpp \
     src/thlibrary/thblackbar.cpp \
     src/globalshortcuts.cpp \
-    src/globalshortcutbackend.cpp
+    src/globalshortcutbackend.cpp \
+    src/downloadmanager.cpp \
+    src/downloaditem.cpp \
+    src/downloadview.cpp \
+    src/downloadmodel.cpp \
+    src/downloadlistview.cpp \
+    src/downloadsettings.cpp
 RESOURCES += resources.qrc
 DESTDIR = build/target/
 OBJECTS_DIR = build/obj/
@@ -92,13 +100,10 @@ include(locale/locale.pri)
 # deploy
 DISTFILES += CHANGES \
     COPYING
-
-unix:!mac {
+unix:!mac { 
     QT += dbus
-
     HEADERS += src/gnomeglobalshortcutbackend.h
     SOURCES += src/gnomeglobalshortcutbackend.cpp
-
     isEmpty(PREFIX):PREFIX = /usr
     BINDIR = $$PREFIX/bin
     INSTALLS += target
index 42477becb6d937910ef77ce5a04f5264520d9cc7..961c867594b0e4db3e3e0294f93b754e35765c75 100644 (file)
@@ -17,7 +17,7 @@ AboutView::AboutView(QWidget *parent) : QWidget(parent) {
     layout->setSpacing(30);
     aboutlayout->addLayout(layout);
 
-    QString info = "<h1>" + QString(Constants::APP_NAME) + "</h1>"
+    QString info = "<html><style>a { color: palette(text); }</style><body><h1>" + QString(Constants::APP_NAME) + "</h1>"
                    "<p>" + tr("There's life outside the browser!") + "</p>"
                    "<p>" + tr("Version %1").arg(Constants::VERSION) + "</p>"
                    + QString("<p><a href=\"%1/\">%1</a></p>").arg(Constants::WEBSITE) +
@@ -65,7 +65,8 @@ AboutView::AboutView(QWidget *parent) : QWidget(parent) {
                    "<p>" + tr("Released under the <a href='%1'>GNU General Public License</a>")
                    .arg("http://www.gnu.org/licenses/gpl.html") + "</p>"
 #endif
-                   "<p>&copy; 2009-2010 " + Constants::ORG_NAME + "</p>";
+                   "<p>&copy; 2009-2010 " + Constants::ORG_NAME + "</p>"
+                   "</body></html>";;
     QLabel *infoLabel = new QLabel(info, this);
     infoLabel->setOpenExternalLinks(true);
     infoLabel->setWordWrap(true);
index 2b0d12f0f10070d52868655faa2b1016cbeb9858..761e5a4bff6c200c8ed49dba0bfdff35751b4408 100755 (executable)
@@ -78,29 +78,7 @@ QVariant ListModel::data(const QModelIndex &index, int role) const {
     case ActiveTrackRole:
         return video == m_activeVideo;
     case Qt::DisplayRole:
-    case Qt::StatusTipRole:
         return video->title();
-        /*
-        case Qt::ToolTipRole:
-          
-            QString tooltip;
-            if (!element.firstChildElement().text().isEmpty()) {
-                tooltip.append(QString("<b>").append(element.firstChildElement().text()).append("</b><br/>"));
-            }
-            if (!fromDate.isEmpty()) {
-                tooltip.append("<i>Pubblicato il</i> ").append(fromDate);
-            }
-            if (!toDate.isEmpty()) {
-                tooltip.append("<br/><i>Scadenza</i>: ").append(toDate);
-            }
-            tooltip.append("<br/><i>Tipo</i>: ").append(typeName)
-                .append("<br/><i>Id</i>: ").appen    QFont boldFont;
-    boldFont.setBold(true);d(id);
-            return tooltip;
-            */
-        
-        // case StreamUrlRole:
-        // return video->streamUrl();
     }
     
     return QVariant();
@@ -112,8 +90,6 @@ void ListModel::setActiveRow( int row) {
         m_activeRow = row;
         m_activeVideo = videoAt(row);
         
-        // setStateOfRow( row, Item::Played );
-        
         int oldactiverow = m_activeRow;
         
         if ( rowExists( oldactiverow ) )
@@ -268,6 +244,7 @@ void ListModel::removeIndexes(QModelIndexList &indexes) {
     QList<Video*> originalList(videos);
     QList<Video*> delitems;
     foreach (QModelIndex index, indexes) {
+        if (index.row() >= originalList.size()) continue;
         Video* video = originalList.at(index.row());
         int idx = videos.indexOf(video);
         if (idx != -1) {
@@ -381,6 +358,7 @@ void ListModel::move(QModelIndexList &indexes, bool up) {
 
     foreach (QModelIndex index, indexes) {
         int row = index.row();
+        if (row >= videos.size()) continue;
         // qDebug() << "index row" << row;
         Video *video = videoAt(row);
         movedVideos << video;
index c4c078f41451afadc1d862c9a1c0a843426e59ea..c84b6053a7ffd40de127b9b042c4809f8372a4ae 100755 (executable)
@@ -8,7 +8,11 @@
 enum DataRoles {
     ItemTypeRole = Qt::UserRole,
     VideoRole,
-    ActiveTrackRole
+    ActiveTrackRole,
+    DownloadItemRole,
+    HoveredItemRole,
+    DownloadButtonHoveredRole,
+    DownloadButtonPressedRole
 };
 
 enum ItemTypes {
index 33a8a2f6a42ce9e7b637206632e1c99d9975e514..e240745de0aa20ad3c3a9adcc06d10d00e8c26dd 100755 (executable)
 #include "gnomeglobalshortcutbackend.h"
 #endif
 #ifdef APP_MAC
-#include "local/mac/mac_startup.h"
+// #include "local/mac/mac_startup.h"
 #endif
+#include "downloadmanager.h"
 
 MainWindow::MainWindow() :
         aboutView(0),
+        downloadView(0),
         mediaObject(0),
         audioOutput(0),
         m_fullscreen(false) {
@@ -22,6 +24,7 @@ MainWindow::MainWindow() :
     // views mechanism
     history = new QStack<QWidget*>();
     views = new QStackedWidget(this);
+    setCentralWidget(views);
 
     // views
     searchView = new SearchView(this);
@@ -32,7 +35,6 @@ MainWindow::MainWindow() :
     views->addWidget(mediaView);
 
     toolbarSearch = new SearchLineEdit(this);
-    toolbarSearch->setFont(qApp->font());
     toolbarSearch->setMinimumWidth(toolbarSearch->fontInfo().pixelSize()*15);
     connect(toolbarSearch, SIGNAL(search(const QString&)), searchView, SLOT(watch(const QString&)));
 
@@ -51,21 +53,15 @@ MainWindow::MainWindow() :
     // mediaView init stuff thats needs actions
     mediaView->initialize();
 
-    // cool toolbar on the Mac
-    // this is too buggy to be enabled
-    // setUnifiedTitleAndToolBarOnMac(true);
-
     // event filter to block ugly toolbar tooltips
     qApp->installEventFilter(this);
 
-    // show the initial view
-    showWidget(searchView);
-
-    setCentralWidget(views);
-
     // restore window position
     readSettings();
 
+    // show the initial view
+    showWidget(searchView);
+
     // Global shortcuts
     GlobalShortcuts &shortcuts = GlobalShortcuts::instance();
 #ifdef Q_WS_X11
@@ -73,11 +69,16 @@ MainWindow::MainWindow() :
         shortcuts.setBackend(new GnomeGlobalShortcutBackend(&shortcuts));
 #endif
 #ifdef APP_MAC
-    mac::MacSetup();
+    // mac::MacSetup();
 #endif
     connect(&shortcuts, SIGNAL(PlayPause()), pauseAct, SLOT(trigger()));
     connect(&shortcuts, SIGNAL(Stop()), this, SLOT(stop()));
     connect(&shortcuts, SIGNAL(Next()), skipAct, SLOT(trigger()));
+
+    connect(DownloadManager::instance(), SIGNAL(statusMessageChanged(QString)),
+            SLOT(updateDownloadMessage(QString)));
+    connect(DownloadManager::instance(), SIGNAL(finished()),
+            SLOT(downloadsFinished()));
 }
 
 MainWindow::~MainWindow() {
@@ -101,6 +102,7 @@ void MainWindow::createActions() {
     stopAct = new QAction(QtIconLoader::icon("media-playback-stop"), tr("&Stop"), this);
     stopAct->setStatusTip(tr("Stop playback and go back to the search view"));
     stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
+    stopAct->setEnabled(false);
     actions->insert("stop", stopAct);
     connect(stopAct, SIGNAL(triggered()), this, SLOT(stop()));
 
@@ -122,6 +124,9 @@ void MainWindow::createActions() {
     fullscreenAct->setStatusTip(tr("Go full screen"));
     fullscreenAct->setShortcut(QKeySequence(Qt::ALT + Qt::Key_Return));
     fullscreenAct->setShortcutContext(Qt::ApplicationShortcut);
+#if QT_VERSION >= 0x040600
+    fullscreenAct->setPriority(QAction::LowPriority);
+#endif
     actions->insert("fullscreen", fullscreenAct);
     connect(fullscreenAct, SIGNAL(triggered()), this, SLOT(fullscreen()));
 
@@ -191,7 +196,7 @@ void MainWindow::createActions() {
     quitAct->setShortcuts(QList<QKeySequence>() << QKeySequence(tr("Ctrl+Q")) << QKeySequence(Qt::CTRL + Qt::Key_W));
     quitAct->setStatusTip(tr("Bye"));
     actions->insert("quit", quitAct);
-    connect(quitAct, SIGNAL(triggered()), this, SLOT(quit()));
+    connect(quitAct, SIGNAL(triggered()), this, SLOT(close()));
 
     siteAct = new QAction(tr("&Website"), this);
     siteAct->setShortcut(QKeySequence::HelpContents);
@@ -255,6 +260,37 @@ void MainWindow::createActions() {
     connect(definitionAct, SIGNAL(triggered()), SLOT(toggleDefinitionMode()));
     addAction(definitionAct);
 
+    QAction *action;
+
+    /*
+    action = new QAction(tr("&Autoplay"), this);
+    action->setStatusTip(tr("Automatically start playing videos"));
+    action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_P));
+    action->setCheckable(true);
+    connect(action, SIGNAL(toggled(bool)), SLOT(setAutoplay(bool)));
+    actions->insert("autoplay", action);
+    */
+
+    action = new QAction(tr("&Downloads"), this);
+    action->setStatusTip(tr("Show details about video downloads"));
+    action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));
+    action->setCheckable(true);
+    action->setIcon(QtIconLoader::icon("go-down"));
+    action->setVisible(false);
+    connect(action, SIGNAL(toggled(bool)), SLOT(toggleDownloads(bool)));
+    actions->insert("downloads", action);
+
+    action = new QAction(tr("&Download"), this);
+    action->setStatusTip(tr("Download the current video"));
+    action->setShortcut(QKeySequence::Save);
+    action->setIcon(QtIconLoader::icon("go-down"));
+    action->setEnabled(false);
+#if QT_VERSION >= 0x040600
+    action->setPriority(QAction::LowPriority);
+#endif
+    connect(action, SIGNAL(triggered()), mediaView, SLOT(downloadVideo()));
+    actions->insert("download", action);
+
     // common action properties
     foreach (QAction *action, actions->values()) {
 
@@ -307,12 +343,18 @@ void MainWindow::createMenus() {
     viewMenu->addAction(pauseAct);
     viewMenu->addAction(skipAct);
     viewMenu->addSeparator();
+    viewMenu->addAction(The::globalActions()->value("download"));
+    viewMenu->addSeparator();
     viewMenu->addAction(webPageAct);
     viewMenu->addAction(copyPageAct);
     viewMenu->addAction(copyLinkAct);
     viewMenu->addSeparator();
     viewMenu->addAction(compactViewAct);
     viewMenu->addAction(fullscreenAct);
+#ifdef APP_MAC
+    extern void qt_mac_set_dock_menu(QMenu *);
+    qt_mac_set_dock_menu(viewMenu);
+#endif
 
     helpMenu = menuBar()->addMenu(tr("&Help"));
     helpMenu->addAction(siteAct);
@@ -341,6 +383,7 @@ void MainWindow::createToolBars() {
     mainToolBar->addAction(pauseAct);
     mainToolBar->addAction(skipAct);
     mainToolBar->addAction(fullscreenAct);
+    mainToolBar->addAction(The::globalActions()->value("download"));
 
     mainToolBar->addWidget(new Spacer());
 
@@ -353,7 +396,7 @@ void MainWindow::createToolBars() {
 
     seekSlider = new Phonon::SeekSlider(this);
     seekSlider->setIconVisible(false);
-    seekSlider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+    seekSlider->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
     mainToolBar->addWidget(seekSlider);
 
     mainToolBar->addWidget(new Spacer());
@@ -397,6 +440,8 @@ void MainWindow::createStatusBar() {
     QToolBar *toolBar = new QToolBar(this);
     toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
     toolBar->setIconSize(QSize(16, 16));
+    toolBar->addAction(The::globalActions()->value("downloads"));
+    // toolBar->addAction(The::globalActions()->value("autoplay"));
     toolBar->addAction(The::globalActions()->value("definition"));
     statusBar()->addPermanentWidget(toolBar);
 
@@ -406,17 +451,24 @@ void MainWindow::createStatusBar() {
 void MainWindow::readSettings() {
     QSettings settings;
     restoreGeometry(settings.value("geometry").toByteArray());
+#ifdef APP_MAC
+    if (!isMaximized())
+        move(x(), y() + mainToolBar->height() + 8);
+#endif
     setDefinitionMode(settings.value("definition", VideoDefinition::getDefinitionNames().first()).toString());
     audioOutput->setVolume(settings.value("volume", 1).toDouble());
     audioOutput->setMuted(settings.value("volumeMute").toBool());
 }
 
 void MainWindow::writeSettings() {
-    // do not save geometry when in full screen
-    if (m_fullscreen)
-        return;
+
     QSettings settings;
-    settings.setValue("geometry", saveGeometry());
+
+    // do not save geometry when in full screen
+    if (!m_fullscreen) {
+        settings.setValue("geometry", saveGeometry());
+    }
+
     settings.setValue("volume", audioOutput->volume());
     settings.setValue("volumeMute", audioOutput->isMuted());
     mediaView->saveSplitterState();
@@ -459,9 +511,14 @@ void MainWindow::showWidget ( QWidget* widget ) {
     copyPageAct->setEnabled(widget == mediaView);
     copyLinkAct->setEnabled(widget == mediaView);
     aboutAct->setEnabled(widget != aboutView);
+    The::globalActions()->value("download")->setEnabled(widget == mediaView);
+    The::globalActions()->value("downloads")->setChecked(widget == downloadView);
 
     // toolbar only for the mediaView
-    mainToolBar->setVisible(widget == mediaView && !compactViewAct->isChecked());
+    /* mainToolBar->setVisible(
+            (widget == mediaView && !compactViewAct->isChecked())
+            || widget == downloadView
+            ); */
 
     setUpdatesEnabled(true);
 
@@ -513,19 +570,28 @@ void MainWindow::quit() {
 }
 
 void MainWindow::closeEvent(QCloseEvent *event) {
+    if (DownloadManager::instance()->activeItems() > 0) {
+        QMessageBox msgBox;
+        msgBox.setIconPixmap(QPixmap(":/images/app.png").scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+        msgBox.setText(tr("Do you want to exit %1 with a download in progress?").arg(Constants::APP_NAME));
+        msgBox.setInformativeText(tr("If you close %1 now, this download will be cancelled.").arg(Constants::APP_NAME));
+        msgBox.setModal(true);
+
+        msgBox.addButton(tr("Close and cancel download"), QMessageBox::RejectRole);
+        QPushButton *waitButton = msgBox.addButton(tr("Wait for download to finish"), QMessageBox::ActionRole);
+
+        msgBox.exec();
+
+        if (msgBox.clickedButton() == waitButton) {
+            event->ignore();
+            return;
+        }
+
+    }
     quit();
     QWidget::closeEvent(event);
 }
 
-/*
-void MainWindow::showSettings() {
-    if (!settingsView) {
-        settingsView = new SettingsView(this);
-        views->addWidget(settingsView);
-    }
-    showWidget(settingsView);
-}*/
-
 void MainWindow::showSearch() {
     showWidget(searchView);
     currentTime->clear();
@@ -559,11 +625,13 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         pauseAct->setText(tr("&Pause"));
         pauseAct->setStatusTip(tr("Pause playback") + " (" +  pauseAct->shortcut().toString(QKeySequence::NativeText) + ")");
         skipAct->setEnabled(true);
+        // stopAct->setEnabled(true);
         break;
 
          case Phonon::StoppedState:
         pauseAct->setEnabled(false);
         skipAct->setEnabled(false);
+        // stopAct->setEnabled(false);
         break;
 
          case Phonon::PausedState:
@@ -572,6 +640,7 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         pauseAct->setIcon(QtIconLoader::icon("media-playback-start"));
         pauseAct->setText(tr("&Play"));
         pauseAct->setStatusTip(tr("Resume playback") + " (" +  pauseAct->shortcut().toString(QKeySequence::NativeText) + ")");
+        // stopAct->setEnabled(true);
         break;
 
          case Phonon::BufferingState:
@@ -580,6 +649,7 @@ void MainWindow::stateChanged(Phonon::State newState, Phonon::State /* oldState
         pauseAct->setEnabled(false);
         currentTime->clear();
         totalTime->clear();
+        // stopAct->setEnabled(true);
         break;
 
          default:
@@ -594,8 +664,6 @@ void MainWindow::stop() {
 
 void MainWindow::fullscreen() {
 
-    setUpdatesEnabled(false);
-
     // No compact view action when in full screen
     compactViewAct->setVisible(m_fullscreen);
     compactViewAct->setChecked(false);
@@ -609,7 +677,7 @@ void MainWindow::fullscreen() {
 
     // workaround: prevent focus on the search bar
     // it steals the Space key needed for Play/Pause
-    mainToolBar->setEnabled(m_fullscreen);
+    toolbarSearch->setEnabled(m_fullscreen);
 
     // Hide anything but the video
     mediaView->setPlaylistVisible(m_fullscreen);
@@ -632,20 +700,33 @@ void MainWindow::fullscreen() {
 #endif
 
     if (m_fullscreen) {
-        // use setShortucs instead of setShortcut
+
+        // Exit full screen
+
+        // use setShortcuts instead of setShortcut
         // the latter seems not to work
         fullscreenAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::ALT + Qt::Key_Return));
         fullscreenAct->setText(tr("&Full Screen"));
         stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
 
+#ifdef APP_MAC
+        setCentralWidget(views);
+        views->showNormal();
+        show();
+        mediaView->setFocus();
+#else
         mainToolBar->show();
         if (m_maximized) showMaximized();
         else showNormal();
+#endif
 
-        // Make sure the window has focus (Mac)
+        // Make sure the window has focus
         activateWindow();
 
     } else {
+
+        // Enter full screen
+
         stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_MediaStop));
         fullscreenAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::ALT + Qt::Key_Return));
         fullscreenAct->setText(tr("Exit &Full Screen"));
@@ -655,21 +736,23 @@ void MainWindow::fullscreen() {
         // geometry won't be saved
         writeSettings();
 
+#ifdef APP_MAC
+        hide();
+        views->setParent(0);
+        QTimer::singleShot(0, views, SLOT(showFullScreen()));
+#else
         mainToolBar->hide();
         showFullScreen();
+#endif
+
     }
 
     m_fullscreen = !m_fullscreen;
 
-    setUpdatesEnabled(true);
 }
 
 void MainWindow::compactView(bool enable) {
 
-    setUpdatesEnabled(false);
-
-    mainToolBar->setVisible(!enable);
-    mainToolBar->setEnabled(!enable);
     mediaView->setPlaylistVisible(!enable);
     statusBar()->setVisible(!enable);
 
@@ -677,22 +760,20 @@ void MainWindow::compactView(bool enable) {
     menuBar()->setVisible(!enable);
 #endif
 
-    // ensure focus does not end up to the search box
-    // as it would steal the Space shortcut
-    // toolbarSearch->setEnabled(!enable);
-
     if (enable) {
-        stopAct->setShortcut(QString(""));
-        QList<QKeySequence> shortcuts;
-        // for some reason it is important that ESC comes first
-        shortcuts << QKeySequence(Qt::CTRL + Qt::Key_Return) << QKeySequence(Qt::Key_Escape);
-        compactViewAct->setShortcuts(shortcuts);
+        stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_MediaStop));
+        compactViewAct->setShortcuts(
+                QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_Return)
+                << QKeySequence(Qt::Key_Escape));
+
+        // ensure focus does not end up to the search box
+        // as it would steal the Space shortcut
+        mediaView->setFocus();
     } else {
-        compactViewAct->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));
-        stopAct->setShortcut(QKeySequence(Qt::Key_Escape));
+        compactViewAct->setShortcuts(QList<QKeySequence>() <<  QKeySequence(Qt::CTRL + Qt::Key_Return));
+        stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
     }
 
-    setUpdatesEnabled(true);
 }
 
 void MainWindow::searchFocus() {
@@ -835,3 +916,40 @@ void MainWindow::clearRecentKeywords() {
     searchView->updateRecentKeywords();
     statusBar()->showMessage(tr("Your privacy is now safe"));
 }
+
+/*
+ void MainWindow::setAutoplay(bool enabled) {
+     QSettings settings;
+     settings.setValue("autoplay", QVariant::fromValue(enabled));
+ }
+ */
+
+void MainWindow::updateDownloadMessage(QString message) {
+    The::globalActions()->value("downloads")->setText(message);
+}
+
+void MainWindow::downloadsFinished() {
+    The::globalActions()->value("downloads")->setText(tr("&Downloads"));
+    statusBar()->showMessage(tr("Downloads complete"));
+}
+
+void MainWindow::toggleDownloads(bool show) {
+
+    if (show) {
+        stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_MediaStop));
+        The::globalActions()->value("downloads")->setShortcuts(
+                QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J)
+                << QKeySequence(Qt::Key_Escape));
+    } else {
+        The::globalActions()->value("downloads")->setShortcuts(
+                QList<QKeySequence>() << QKeySequence(Qt::CTRL + Qt::Key_J));
+        stopAct->setShortcuts(QList<QKeySequence>() << QKeySequence(Qt::Key_Escape) << QKeySequence(Qt::Key_MediaStop));
+    }
+
+    if (!downloadView) {
+        downloadView = new DownloadView(this);
+        views->addWidget(downloadView);
+    }
+    if (show) showWidget(downloadView);
+    else goBack();
+}
index 1eb025102b174e359ce5c560b1a3c50b5e2a1f21..e90dbf83ce16e737c9586e2abc1723b68f0efe9e 100755 (executable)
@@ -12,6 +12,7 @@
 #include "SearchView.h"
 #include "MediaView.h"
 #include "AboutView.h"
+#include "downloadview.h"
 
 class MainWindow : public QMainWindow {
 
@@ -56,6 +57,11 @@ private slots:
     void showFullscreenToolbar(bool show);
     void showFullscreenPlaylist(bool show);
 
+    // void setAutoplay(bool enabled);
+    void updateDownloadMessage(QString);
+    void downloadsFinished();
+    void toggleDownloads(bool show);
+
 private:
     void initPhonon();
     void createActions();
@@ -76,6 +82,7 @@ private:
     SearchView *searchView;
     MediaView *mediaView;
     QWidget *aboutView;
+    QWidget *downloadView;
 
     // actions
     QAction *addGadgetAct;
@@ -95,7 +102,6 @@ private:
     QAction *webPageAct;
     QAction *copyPageAct;
     QAction *copyLinkAct;
-    QAction *downloadAct;
     QAction *volumeUpAct;
     QAction *volumeDownAct;
     QAction *volumeMuteAct;
index 855e275c75fed4001fd26057b75dcf91e918cc6c..7cda1cb58ed1bf98f393b02a0ec6acd9c9eb4c9e 100644 (file)
@@ -3,8 +3,8 @@
 #include "networkaccess.h"
 #include "videowidget.h"
 #include "minisplitter.h"
-#include "flickcharm.h"
 #include "constants.h"
+#include "downloadmanager.h"
 
 namespace The {
     QMap<QString, QAction*>* globalActions();
@@ -118,14 +118,6 @@ MediaView::MediaView(QWidget *parent) : QWidget(parent) {
     workaroundTimer->setInterval(3000);
     connect(workaroundTimer, SIGNAL(timeout()), SLOT(timerPlay()));
 
-    // TODO Enable this on touch devices
-    // FlickCharm *flickCharm = new FlickCharm(this);
-    // flickCharm->activateOn(listView);
-
-}
-
-MediaView::~MediaView() {
-
 }
 
 void MediaView::initialize() {
@@ -278,6 +270,8 @@ void MediaView::activeRowChanged(int row) {
     QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
     if (mainWindow) mainWindow->statusBar()->showMessage(video->title());
 
+    The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
+
     // see you in gotStreamUrl...
 
 }
@@ -285,6 +279,13 @@ void MediaView::activeRowChanged(int row) {
 void MediaView::gotStreamUrl(QUrl streamUrl) {
     if (reallyStopped) return;
 
+    Video *video = static_cast<Video *>(sender());
+    if (!video) {
+        qDebug() << "Cannot get sender";
+        return;
+    }
+    video->disconnect(this);
+
     // go!
     qDebug() << "Playing" << streamUrl.toString();
     mediaObject->setCurrentSource(streamUrl);
@@ -477,3 +478,25 @@ void MediaView::demoExpired() {
     tracksPlayed = 1;
 }
 #endif
+
+void MediaView::downloadVideo() {
+    Video* video = listModel->activeVideo();
+    if (!video) return;
+
+    DownloadManager::instance()->addItem(video);
+
+    // TODO animate
+
+    The::globalActions()->value("downloads")->setVisible(true);
+
+    // The::globalActions()->value("download")->setEnabled(DownloadManager::instance()->itemForVideo(video) == 0);
+
+    QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
+    QString message = tr("Downloading %1").arg(video->title());
+    if (mainWindow) mainWindow->statusBar()->showMessage(message);
+}
+
+void MediaView::fullscreen() {
+    videoAreaWidget->setParent(0);
+    videoAreaWidget->showFullScreen();
+}
index 04dd92c55ae40d75de31ba8f3bd620764200b3cd..90f066de7483d881d3ae64be74127171b86181a3 100644 (file)
 #include "loadingwidget.h"
 #include "videoareawidget.h"
 
+namespace The {
+    QMap<QString, QAction*>* globalActions();
+}
+
 class MediaView : public QWidget, public View {
     Q_OBJECT
 
 public:
     MediaView(QWidget *parent);
-    ~MediaView();
     void initialize();
 
     // View
-    void appear() {}
+    void appear() {
+        listView->setFocus();
+    }
     void disappear();
     QMap<QString, QVariant> metadata() {
         QMap<QString, QVariant> metadata;
@@ -49,6 +54,8 @@ public slots:
     void moveDownSelected();
     void setPlaylistVisible(bool visible=true);
     void saveSplitterState();
+    void downloadVideo();
+    void fullscreen();
 
 private slots:
     // list/model
index 345722e0b59f47ef2fa70470f4147491bacead25..d1907f0d68215f2d4f876273f6b664f0586b304e 100644 (file)
@@ -21,7 +21,7 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
 #endif
 
     QBoxLayout *mainLayout = new QVBoxLayout();
-    mainLayout->setMargin(PADDING);
+    mainLayout->setMargin(0);
     mainLayout->setSpacing(0);
 
     // hidden message widget
@@ -30,6 +30,7 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
     mainLayout->addWidget(message);
 
     mainLayout->addStretch();
+    mainLayout->addSpacing(PADDING);
 
     QBoxLayout *hLayout = new QHBoxLayout();
     hLayout->setAlignment(Qt::AlignCenter);
@@ -45,21 +46,22 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
     hLayout->addLayout(layout);
 
     QLabel *welcomeLabel =
-            new QLabel("<h1>" +
+            new QLabel("<h1 style='font-weight:normal'>" +
                        tr("Welcome to <a href='%1'>%2</a>,")
-                       .replace("<a ", "<a style='color:palette(text)'")
+                       // .replace("<a ", "<a style='color:palette(text)'")
+                       .replace("<a href", "<a style='text-decoration:none; color:palette(text); font-weight:bold' href")
                        .arg(Constants::WEBSITE, Constants::APP_NAME)
                        + "</h1>", this);
     welcomeLabel->setOpenExternalLinks(true);
     layout->addWidget(welcomeLabel);
 
-    layout->addSpacing(PADDING);
+    layout->addSpacing(PADDING / 2);
 
     QLabel *tipLabel = new QLabel(tr("Enter a keyword to start watching videos."), this);
     tipLabel->setFont(biggerFont);
     layout->addWidget(tipLabel);
 
-    layout->addSpacing(10);
+    layout->addSpacing(PADDING / 2);
 
     QHBoxLayout *searchLayout = new QHBoxLayout();
     searchLayout->setAlignment(Qt::AlignVCenter);
@@ -84,16 +86,23 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
 
     layout->addItem(searchLayout);
 
-    layout->addSpacing(PADDING);
+    layout->addSpacing(PADDING / 2);
 
     QHBoxLayout *otherLayout = new QHBoxLayout();
+    otherLayout->setMargin(0);
 
     recentKeywordsLayout = new QVBoxLayout();
     recentKeywordsLayout->setSpacing(5);
     recentKeywordsLayout->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
     recentKeywordsLabel = new QLabel(tr("Recent keywords").toUpper(), this);
-    recentKeywordsLabel->hide();
+#ifdef APP_MAC
+    QPalette palette = recentKeywordsLabel->palette();
+    palette.setColor(QPalette::WindowText, QColor(0x65, 0x71, 0x80));
+    recentKeywordsLabel->setPalette(palette);
+#else
     recentKeywordsLabel->setForegroundRole(QPalette::Dark);
+#endif
+    recentKeywordsLabel->hide();
     recentKeywordsLabel->setFont(smallerFont);
     recentKeywordsLayout->addWidget(recentKeywordsLabel);
 
@@ -101,6 +110,7 @@ SearchView::SearchView(QWidget *parent) : QWidget(parent) {
 
     layout->addLayout(otherLayout);
 
+    mainLayout->addSpacing(PADDING);
     mainLayout->addStretch();
 
     setLayout(mainLayout);
@@ -187,6 +197,7 @@ void SearchView::checkForUpdate() {
 void SearchView::gotNewVersion(QString version) {
     message->setText(
             tr("A new version of %1 is available. Please <a href='%2'>update to version %3</a>")
+            .replace("<a href", "<a style='text-decoration:none; color:palette(text); font-weight:bold' href")
             .arg(
                     Constants::APP_NAME,
                     QString(Constants::WEBSITE).append("#download"),
@@ -194,9 +205,11 @@ void SearchView::gotNewVersion(QString version) {
             );
     message->setOpenExternalLinks(true);
     message->setMargin(10);
-    message->setBackgroundRole(QPalette::ToolTipBase);
-    message->setForegroundRole(QPalette::ToolTipText);
-    message->setAutoFillBackground(true);
+    message->setAlignment(Qt::AlignCenter);
+    // message->setBackgroundRole(QPalette::ToolTipBase);
+    // message->setForegroundRole(QPalette::ToolTipText);
+    // message->setAutoFillBackground(true);
+    message->setStyleSheet("QLabel { border-bottom: 1px solid palette(mid); }");
     message->show();
     if (updateChecker) delete updateChecker;
 }
diff --git a/src/downloaditem.cpp b/src/downloaditem.cpp
new file mode 100644 (file)
index 0000000..ab92fc3
--- /dev/null
@@ -0,0 +1,257 @@
+#include "downloaditem.h"
+#include "networkaccess.h"
+#include "video.h"
+
+#include <QDesktopServices>
+
+namespace The {
+    NetworkAccess* http();
+}
+
+DownloadItem::DownloadItem(Video *video, QUrl url, QString filename, QObject *parent)
+    : QObject(parent)
+    , m_bytesReceived(0)
+    , m_startedSaving(false)
+    , m_finishedDownloading(false)
+    , m_url(url)
+    , m_file(filename)
+    , m_reply(0)
+    , video(video)
+    , m_status(Idle)
+{ }
+
+void DownloadItem::start() {
+    m_reply = The::http()->simpleGet(m_url);
+    init();
+}
+
+void DownloadItem::init() {
+    if (!m_reply)
+        return;
+
+    m_status = Starting;
+
+    m_startedSaving = false;
+    m_finishedDownloading = false;
+
+    // attach to the m_reply
+    m_url = m_reply->url();
+    connect(m_reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
+    connect(m_reply, SIGNAL(error(QNetworkReply::NetworkError)),
+            this, SLOT(error(QNetworkReply::NetworkError)));
+    connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
+            this, SLOT(downloadProgress(qint64, qint64)));
+    connect(m_reply, SIGNAL(metaDataChanged()),
+            this, SLOT(metaDataChanged()));
+    connect(m_reply, SIGNAL(finished()),
+            this, SLOT(requestFinished()));
+
+    // start timer for the download estimation
+    m_downloadTime.start();
+
+    if (m_reply->error() != QNetworkReply::NoError) {
+        error(m_reply->error());
+        requestFinished();
+    }
+}
+
+
+void DownloadItem::stop() {
+    if (m_reply)
+        m_reply->abort();
+    m_status = Idle;
+    emit statusChanged();
+}
+
+void DownloadItem::open() {
+    QFileInfo info(m_file);
+    QUrl url = QUrl::fromLocalFile(info.absoluteFilePath());
+    QDesktopServices::openUrl(url);
+}
+
+void DownloadItem::openFolder() {
+    QFileInfo info(m_file);
+    QUrl url = QUrl::fromLocalFile(info.absolutePath());
+    QDesktopServices::openUrl(url);
+}
+
+void DownloadItem::tryAgain() {
+    if (m_reply)
+        m_reply->abort();
+
+    if (m_file.exists())
+        m_file.remove();
+
+    m_reply = The::http()->simpleGet(m_url);
+    init();
+    emit statusChanged();
+}
+
+void DownloadItem::downloadReadyRead() {
+
+    if (!m_file.isOpen()) {
+        if (!m_file.open(QIODevice::WriteOnly)) {
+            qDebug() << QString("Error opening output file: %1").arg(m_file.errorString());
+            stop();
+            emit statusChanged();
+            return;
+        }
+        emit statusChanged();
+    }
+
+    m_status = Downloading;
+    if (-1 == m_file.write(m_reply->readAll())) {
+        /*
+        downloadInfoLabel->setText(tr("Error saving: %1")
+                                   .arg(m_output.errorString()));
+        stopButton->click();
+        */
+    } else {
+        m_startedSaving = true;
+        if (m_finishedDownloading)
+            requestFinished();
+    }
+}
+
+void DownloadItem::error(QNetworkReply::NetworkError) {
+
+#ifdef DOWNLOADMANAGER_DEBUG
+    qDebug() << "DownloadItem::" << __FUNCTION__ << m_reply->errorString() << m_url;
+#endif
+
+    m_errorMessage = m_reply->errorString();
+    m_reply = 0;
+    m_status = Failed;
+
+    emit finished();
+}
+
+QString DownloadItem::errorMessage() const {
+    return m_errorMessage;
+}
+
+void DownloadItem::metaDataChanged() {
+    QVariant locationHeader = m_reply->header(QNetworkRequest::LocationHeader);
+    if (locationHeader.isValid()) {
+        m_url = locationHeader.toUrl();
+        m_reply->deleteLater();
+        m_reply = The::http()->simpleGet(m_url);
+        init();
+        return;
+    }
+
+#ifdef DOWNLOADMANAGER_DEBUG
+    qDebug() << "DownloadItem::" << __FUNCTION__ << "not handled.";
+#endif
+}
+
+void DownloadItem::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
+    QTime now = QTime::currentTime();
+    if (m_lastProgressTime.msecsTo(now) < 200)
+        return;
+
+    m_lastProgressTime = now;
+
+    m_bytesReceived = bytesReceived;
+    if (bytesTotal > 0) {
+        percent = bytesReceived * 100 / bytesTotal;
+    }
+
+    emit progress(percent);
+    // emit statusChanged();
+}
+
+qint64 DownloadItem::bytesTotal() const {
+    if (!m_reply) return 0;
+    return m_reply->header(QNetworkRequest::ContentLengthHeader).toULongLong();
+}
+
+qint64 DownloadItem::bytesReceived() const {
+    return m_bytesReceived;
+}
+
+double DownloadItem::remainingTime() const {
+    if (m_finishedDownloading)
+        return -1.0;
+
+    double timeRemaining = ((double)(bytesTotal() - bytesReceived())) / currentSpeed();
+
+    // When downloading the eta should never be 0
+    if (timeRemaining == 0)
+        timeRemaining = 1;
+
+    return timeRemaining;
+}
+
+double DownloadItem::currentSpeed() const {
+    if (m_finishedDownloading)
+        return -1.0;
+
+    return m_bytesReceived * 1000.0 / m_downloadTime.elapsed();
+}
+
+void DownloadItem::requestFinished() {
+    m_reply = 0;
+    m_finishedDownloading = true;
+    if (!m_startedSaving) {
+        return;
+    }
+    m_file.close();
+    m_status = Finished;
+    emit statusChanged();
+    emit finished();
+}
+
+QString DownloadItem::formattedFilesize(qint64 size) {
+    /*
+    if (size < 1024) return tr("%1 bytes").arg(size);
+    else if (size < 1024*1024) return tr("%1 KB").arg(size/1024);
+    else if (size < 1024*1024*1024) return tr("%1 MB").arg(size/1024/1024);
+    else return tr("%1 GB").arg(size/1024/1024/1024);
+    */
+    QString unit;
+    if (size < 1024) {
+        unit = tr("bytes");
+    } else if (size < 1024*1024) {
+        size /= 1024;
+        unit = tr("KB");
+    } else {
+        size /= 1024*1024;
+        unit = tr("MB");
+    }
+    return QString(QLatin1String("%1 %2")).arg(size).arg(unit);
+}
+
+QString DownloadItem::formattedSpeed(double speed) {
+    /*
+    static const int K = 1024;
+    if (speed < K) return tr("%1 bytes/s").arg(speed);
+    else if (speed < K*K) return tr("%1 KB/s").arg(speed/K);
+    else if (speed < K*K*K) return tr("%1 MB/s").arg(speed/K/K);
+    else return tr("%1 GB/s").arg(speed/K/K/K);
+    */
+    int speedInt = (int) speed;
+    QString unit;
+    if (speedInt < 1024) {
+        unit = tr("bytes/sec");
+    } else if (speedInt < 1024*1024) {
+        speedInt /= 1024;
+        unit = tr("KB/sec");
+    } else {
+        speedInt /= 1024*1024;
+        unit = tr("MB/sec");
+    }
+    return QString(QLatin1String("%1 %2")).arg(speedInt).arg(unit);
+}
+
+QString DownloadItem::formattedTime(double timeRemaining) {
+    QString timeRemainingString = tr("seconds");
+    if (timeRemaining > 60) {
+        timeRemaining = timeRemaining / 60;
+        timeRemainingString = tr("minutes");
+    }
+    timeRemaining = floor(timeRemaining);
+    return tr("%4 %5 remaining")
+            .arg(timeRemaining)
+            .arg(timeRemainingString);
+}
diff --git a/src/downloaditem.h b/src/downloaditem.h
new file mode 100644 (file)
index 0000000..1d6b1f1
--- /dev/null
@@ -0,0 +1,80 @@
+#ifndef DOWNLOADITEM_H
+#define DOWNLOADITEM_H
+
+#include <QtCore>
+#include <QNetworkReply>
+
+class Video;
+
+enum DownloadItemStatus {
+    Idle = 0,
+    Starting,
+    Downloading,
+    Finished,
+    Failed
+};
+
+class DownloadItem : public QObject {
+
+    Q_OBJECT
+
+signals:
+    void statusChanged();
+    void progress(int percent);
+    void finished();
+
+public:
+    DownloadItem(Video *video, QUrl url, QString filename, QObject *parent = 0);
+    qint64 bytesTotal() const;
+    qint64 bytesReceived() const;
+    double remainingTime() const;
+    double currentSpeed() const;
+    int currentPercent() const { return percent; }
+    Video* getVideo() const { return video; }
+    DownloadItemStatus status() const { return m_status; }
+    static QString formattedFilesize(qint64 size);
+    static QString formattedSpeed(double speed);
+    static QString formattedTime(double time);
+    QString errorMessage() const;
+
+public slots:
+    void start();
+    void stop();
+    void tryAgain();
+    void open();
+    void openFolder();
+
+private slots:
+    void downloadReadyRead();
+    void error(QNetworkReply::NetworkError code);
+    void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+    void metaDataChanged();
+    void requestFinished();
+
+private:
+    void init();
+
+    qint64 m_bytesReceived;
+    QTime m_downloadTime;
+    bool m_startedSaving;
+    bool m_finishedDownloading;
+    QTime m_lastProgressTime;
+    int percent;
+
+    QUrl m_url;
+
+    QFile m_file;
+    QNetworkReply *m_reply;
+    Video *video;
+
+    DownloadItemStatus m_status;
+    QString m_errorMessage;
+
+};
+
+// This is required in order to use QPointer<DownloadItem> as a QVariant
+// as used by the Model/View playlist
+typedef QPointer<DownloadItem> DownloadItemPointer;
+Q_DECLARE_METATYPE(DownloadItemPointer)
+
+#endif // DOWNLOADITEM_H
diff --git a/src/downloadlistview.cpp b/src/downloadlistview.cpp
new file mode 100644 (file)
index 0000000..3a87956
--- /dev/null
@@ -0,0 +1,63 @@
+#include "downloadlistview.h"
+#include "downloadmodel.h"
+#include "playlist/PrettyItemDelegate.h"
+#include <QtGui>
+
+DownloadListView::DownloadListView(QWidget *parent) : QListView(parent) {
+
+    // playIconHovered = false;
+    // setMouseTracking(true);
+
+}
+
+void DownloadListView::leaveEvent(QEvent * /* event */) {
+    DownloadModel *downloadModel = dynamic_cast<DownloadModel *>(model());
+    if (downloadModel) downloadModel->clearHover();
+}
+
+void DownloadListView::mouseMoveEvent(QMouseEvent *event) {
+    // qDebug() << "DownloadListView::mouseMoveEvent" << event->pos();
+
+    QListView::mouseMoveEvent(event);
+
+    if (isHoveringPlayIcon(event)) {
+        QMetaObject::invokeMethod(model(), "enterPlayIconHover", Qt::DirectConnection);
+    } else {
+        QMetaObject::invokeMethod(model(), "exitPlayIconHover", Qt::DirectConnection);
+    }
+
+}
+
+void DownloadListView::mousePressEvent(QMouseEvent *event) {
+    if (event->button() == Qt::LeftButton
+        && isHoveringPlayIcon(event)) {
+        QMetaObject::invokeMethod(model(), "enterPlayIconPressed", Qt::DirectConnection);
+    } else {
+        QListView::mousePressEvent(event);
+    }
+}
+
+void DownloadListView::mouseReleaseEvent(QMouseEvent *event) {
+    if (event->button() == Qt::LeftButton) {
+        QMetaObject::invokeMethod(model(), "exitPlayIconPressed", Qt::DirectConnection);
+        if (isHoveringPlayIcon(event))
+            emit downloadButtonPushed(indexAt(event->pos()));
+    } else {
+        QListView::mousePressEvent(event);
+    }
+}
+
+bool DownloadListView::isHoveringPlayIcon(QMouseEvent *event) {
+    const QModelIndex itemIndex = indexAt(event->pos());
+    const QRect itemRect = visualRect(itemIndex);
+    // qDebug() << " itemRect.x()" <<  itemRect.x();
+
+    PrettyItemDelegate *delegate = dynamic_cast<PrettyItemDelegate *>(itemDelegate());
+    if (!delegate) return false;
+
+    QRect buttonRect = delegate->downloadButtonRect(itemRect);
+
+    const int x = event->x() - itemRect.x() - buttonRect.x();
+    const int y = event->y() - itemRect.y() - buttonRect.y();
+    return x > 0 && x < buttonRect.width() && y > 0 && y < buttonRect.height();
+}
diff --git a/src/downloadlistview.h b/src/downloadlistview.h
new file mode 100644 (file)
index 0000000..2aae0ff
--- /dev/null
@@ -0,0 +1,26 @@
+#ifndef DOWNLOADLISTVIEW_H
+#define DOWNLOADLISTVIEW_H
+
+#include <QListView>
+#include <QModelIndex>
+
+class DownloadListView : public QListView {
+
+    Q_OBJECT
+
+public:
+    DownloadListView(QWidget *parent = 0);
+
+protected:
+    void leaveEvent(QEvent *event);
+    void mouseMoveEvent(QMouseEvent *event);
+    void mousePressEvent(QMouseEvent *event);
+    void mouseReleaseEvent(QMouseEvent *event);
+    bool isHoveringPlayIcon(QMouseEvent *event);
+
+signals:
+    void downloadButtonPushed(QModelIndex index);
+
+};
+
+#endif // DOWNLOADLISTVIEW_H
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
new file mode 100644 (file)
index 0000000..011b5bb
--- /dev/null
@@ -0,0 +1,121 @@
+#include "downloadmanager.h"
+#include "downloaditem.h"
+#include "downloadmodel.h"
+#include "video.h"
+
+static DownloadManager *downloadManagerInstance = 0;
+
+DownloadManager::DownloadManager(QObject *parent) :
+        QObject(parent),
+        downloadModel(new DownloadModel(this, this))
+{ }
+
+DownloadManager* DownloadManager::instance() {
+    if (!downloadManagerInstance) downloadManagerInstance = new DownloadManager();
+    return downloadManagerInstance;
+}
+
+void DownloadManager::clear() {
+    qDeleteAll(items);
+    items.clear();
+    updateStatusMessage();
+}
+
+int DownloadManager::activeItems() {
+    int num = 0;
+    foreach (DownloadItem *item, items) {
+        if (item->status() == Downloading || item->status() == Starting) num++;
+    }
+    return num;
+}
+
+DownloadItem* DownloadManager::itemForVideo(Video* video) {
+    foreach (DownloadItem *item, items) {
+        if (item->getVideo()->id() == video->id()) return item;
+    }
+    return 0;
+}
+
+void DownloadManager::addItem(Video *video) {
+    // qDebug() << __FUNCTION__ << video->title();
+
+    DownloadItem *item = itemForVideo(video);
+    if (item != 0) {
+        if (item->status() == Failed || item->status() == Idle) {
+            qDebug() << "Restarting download" << video->title();
+            item->tryAgain();
+        } else {
+            qDebug() << "Already downloading video" << video->title();
+        }
+        return;
+    }
+
+    connect(video, SIGNAL(gotStreamUrl(QUrl)), SLOT(gotStreamUrl(QUrl)));
+    // TODO handle signal errors
+    // connect(video, SIGNAL(errorStreamUrl(QString)), SLOT(handleError(QString)));
+    video->loadStreamUrl();
+
+    // see you in gotStreamUrl()
+}
+
+void DownloadManager::gotStreamUrl(QUrl url) {
+
+    Video *video = static_cast<Video*>(sender());
+    if (!video) {
+        qDebug() << "Cannot get video in" << __FUNCTION__;
+        return;
+    }
+
+    video->disconnect(this);
+
+    QString path = currentDownloadFolder();
+
+    // TODO ensure all chars are filename compatible
+    QString basename = video->title().simplified();
+    basename.replace('(', '[');
+    basename.replace(')', ']');
+    basename.replace('/', '-');
+    basename.replace('\\', '-');
+    QString filename = path + "/" + basename + ".mp4";
+
+    Video *videoCopy = video->clone();
+    DownloadItem *item = new DownloadItem(videoCopy, url, filename, this);
+
+    int row = items.count();
+    downloadModel->beginInsertRows(QModelIndex(), row, row);
+    items.append(item);
+    downloadModel->endInsertRows();
+
+    // connect(item, SIGNAL(statusChanged()), SLOT(updateStatusMessage()));
+    connect(item, SIGNAL(finished()), SLOT(itemFinished()));
+    item->start();
+
+    updateStatusMessage();
+}
+
+void DownloadManager::itemFinished() {
+    if (activeItems() == 0) emit finished();
+}
+
+void DownloadManager::updateStatusMessage() {
+    QString message = tr("%n Download(s)", "", items.size());
+    emit statusMessageChanged(message);
+}
+
+QString DownloadManager::defaultDownloadFolder() {
+    // download in the Movies system folder
+    QString path = QDesktopServices::storageLocation(QDesktopServices::MoviesLocation);
+    QDir moviesDir(path);
+    if (!moviesDir.exists()) {
+        // fallback to Desktop
+        path = QDesktopServices::storageLocation(QDesktopServices::DesktopLocation);
+    }
+    return path;
+}
+
+QString DownloadManager::currentDownloadFolder() {
+    QSettings settings;
+    QString path = settings.value("downloadFolder").toString();
+    if (path.isEmpty()) path = defaultDownloadFolder();
+    return path;
+}
diff --git a/src/downloadmanager.h b/src/downloadmanager.h
new file mode 100644 (file)
index 0000000..15baa7f
--- /dev/null
@@ -0,0 +1,42 @@
+#ifndef DOWNLOADMANAGER_H
+#define DOWNLOADMANAGER_H
+
+#include <QtGui>
+
+class DownloadItem;
+class DownloadModel;
+class Video;
+
+class DownloadManager : public QObject {
+
+    Q_OBJECT
+
+public:
+    static DownloadManager* instance();
+    void clear();
+    void addItem(Video *video);
+    const QList<DownloadItem*> getItems() { return items; }
+    DownloadModel* getModel() { return downloadModel; }
+    DownloadItem* itemForVideo(Video *video);
+    int activeItems();
+    QString defaultDownloadFolder();
+    QString currentDownloadFolder();
+
+signals:
+    void finished();
+    void statusMessageChanged(QString status);
+
+private slots:
+    void itemFinished();
+    void updateStatusMessage();
+    void gotStreamUrl(QUrl url);
+
+private:
+    DownloadManager(QObject *parent = 0);
+
+    QList<DownloadItem*> items;
+    DownloadModel *downloadModel;
+
+};
+
+#endif // DOWNLOADMANAGER_H
diff --git a/src/downloadmodel.cpp b/src/downloadmodel.cpp
new file mode 100644 (file)
index 0000000..d68db0e
--- /dev/null
@@ -0,0 +1,90 @@
+#include "downloadmodel.h"
+#include "downloadmanager.h"
+#include "downloaditem.h"
+#include "video.h"
+#include "ListModel.h"
+
+DownloadModel::DownloadModel(DownloadManager *downloadManager, QObject *parent) :
+        QAbstractListModel(parent),
+        downloadManager(downloadManager) {
+    hoveredRow = -1;
+    playIconHovered = false;
+    playIconPressed = false;
+}
+
+int DownloadModel::rowCount(const QModelIndex &/*parent*/) const {
+    return downloadManager->getItems().size();
+}
+
+QVariant DownloadModel::data(const QModelIndex &index, int role) const {
+
+    int row = index.row();
+    if (row < 0 || row >= rowCount()) return QVariant();
+
+    QList<DownloadItem*> items = downloadManager->getItems();
+    if (items.isEmpty()) return QVariant();
+
+    switch (role) {
+    case ItemTypeRole:
+        return ItemTypeVideo;
+    case VideoRole:
+        return QVariant::fromValue(QPointer<Video>(items.at(row)->getVideo()));
+    case DownloadItemRole:
+        return QVariant::fromValue(QPointer<DownloadItem>(items.at(row)));
+    case ActiveTrackRole:
+        return false;
+    case HoveredItemRole:
+        return hoveredRow == index.row();
+    case DownloadButtonHoveredRole:
+        return playIconHovered;
+    case DownloadButtonPressedRole:
+        return playIconPressed;
+    }
+
+    return QVariant();
+}
+
+void DownloadModel::sendReset() {
+    reset();
+}
+
+void DownloadModel::setHoveredRow(int row) {
+    int oldRow = hoveredRow;
+    hoveredRow = row;
+    emit dataChanged( createIndex( oldRow, 0 ), createIndex( oldRow, columnCount() - 1 ) );
+    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+}
+
+void DownloadModel::clearHover() {
+    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+    hoveredRow = -1;
+}
+
+void DownloadModel::enterPlayIconHover() {
+    if (playIconHovered) return;
+    playIconHovered = true;
+    updatePlayIcon();
+}
+
+void DownloadModel::exitPlayIconHover() {
+    if (!playIconHovered) return;
+    playIconHovered = false;
+    updatePlayIcon();
+    setHoveredRow(hoveredRow);
+}
+
+void DownloadModel::enterPlayIconPressed() {
+    if (playIconPressed) return;
+    playIconPressed = true;
+    updatePlayIcon();
+}
+
+void DownloadModel::exitPlayIconPressed() {
+    if (!playIconPressed) return;
+    playIconPressed = false;
+    updatePlayIcon();
+}
+
+void DownloadModel::updatePlayIcon() {
+    emit dataChanged( createIndex( hoveredRow, 0 ), createIndex( hoveredRow, columnCount() - 1 ) );
+}
diff --git a/src/downloadmodel.h b/src/downloadmodel.h
new file mode 100644 (file)
index 0000000..26a43de
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef DOWNLOADMODEL_H
+#define DOWNLOADMODEL_H
+
+#include <QAbstractListModel>
+
+class DownloadManager;
+
+class DownloadModel : public QAbstractListModel {
+
+    Q_OBJECT
+
+public:
+    DownloadModel(DownloadManager *downloadManager, QObject *parent);
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    QVariant data(const QModelIndex &index, int role) const;
+    friend class DownloadManager;
+    void setHoveredRow(int row);
+
+public slots:
+    void clearHover();
+    void enterPlayIconHover();
+    void exitPlayIconHover();
+    void enterPlayIconPressed();
+    void exitPlayIconPressed();
+    void sendReset();
+    void updatePlayIcon();
+
+private:
+    int columnCount() { return 1; }
+
+    DownloadManager *downloadManager;
+    int hoveredRow;
+    bool playIconHovered;
+    bool playIconPressed;
+
+};
+
+#endif // DOWNLOADMODEL_H
diff --git a/src/downloadsettings.cpp b/src/downloadsettings.cpp
new file mode 100644 (file)
index 0000000..90cf1ae
--- /dev/null
@@ -0,0 +1,66 @@
+#include "downloadsettings.h"
+#include "downloadmanager.h"
+
+DownloadSettings::DownloadSettings(QWidget *parent) : QWidget(parent) {
+
+    QBoxLayout *layout = new QHBoxLayout(this);
+    layout->setMargin(10);
+
+    message = new QLabel(this);
+    message->setOpenExternalLinks(true);
+    layout->addWidget(message);
+
+    changeFolderButton = new QPushButton(this);
+    changeFolderButton->setText(tr("Change folder..."));
+    changeFolderButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
+    connect(changeFolderButton, SIGNAL(clicked()), SLOT(changeFolder()));
+    layout->addWidget(changeFolderButton);
+
+    updateMessage();
+}
+
+void DownloadSettings::paintEvent(QPaintEvent * /*event*/) {
+    QPainter painter(this);
+#ifdef APP_MAC
+    QBrush brush;
+    if (window()->isActiveWindow()) {
+        brush = QBrush(QColor(0xdd, 0xe4, 0xeb));
+    } else {
+        brush = palette().window();
+    }
+    painter.fillRect(0, 0, width(), height(), brush);
+#endif
+    painter.setPen(palette().color(QPalette::Mid));
+    painter.drawLine(0, 0, width(), 0);
+}
+
+void DownloadSettings::changeFolder() {
+    QString dir = QFileDialog::getExistingDirectory(this, tr("Where's your music collection?"),
+                                                    QDesktopServices::storageLocation(QDesktopServices::HomeLocation),
+                                                    QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
+
+    if (!dir.isEmpty()) {
+        QSettings settings;
+        settings.setValue("downloadFolder", dir);
+        updateMessage();
+        QMainWindow* mainWindow = dynamic_cast<QMainWindow*>(window());
+        if (mainWindow) {
+            QString status;
+            status = tr("Download folder changed.");
+            if (DownloadManager::instance()->activeItems() > 0)
+                status += " " + tr("Current downloads will still go in the previous folder.");
+            mainWindow->statusBar()->showMessage(status);
+        }
+    }
+}
+
+void DownloadSettings::updateMessage() {
+    QString path = DownloadManager::instance()->currentDownloadFolder();
+    QString home = QDesktopServices::storageLocation(QDesktopServices::HomeLocation) + "/";
+    QString displayPath = path;
+    displayPath = displayPath.remove(home);
+    message->setText(
+            tr("Downloading to: %1")
+            .arg("<a href='file://%1' style='text-decoration:none; color:palette(text); font-weight:bold'>%2</a>")
+            .arg(path, displayPath));
+}
diff --git a/src/downloadsettings.h b/src/downloadsettings.h
new file mode 100644 (file)
index 0000000..6a60102
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef DOWNLOADSETTINGS_H
+#define DOWNLOADSETTINGS_H
+
+#include <QtGui>
+
+class DownloadSettings : public QWidget {
+
+    Q_OBJECT
+
+public:
+    DownloadSettings(QWidget *parent = 0);
+
+protected:
+    void paintEvent(QPaintEvent *event);
+
+private slots:
+    void changeFolder();
+
+private:
+    void updateMessage();
+
+    QLabel *message;
+    QPushButton *changeFolderButton;
+
+};
+
+#endif // DOWNLOADSETTINGS_H
diff --git a/src/downloadview.cpp b/src/downloadview.cpp
new file mode 100644 (file)
index 0000000..9ab12a9
--- /dev/null
@@ -0,0 +1,90 @@
+#include "downloadview.h"
+#include "downloadmodel.h"
+#include "downloadmanager.h"
+#include "downloadlistview.h"
+#include "downloaditem.h"
+#include "downloadsettings.h"
+#include "ListModel.h"
+#include "playlist/PrettyItemDelegate.h"
+#include "thlibrary/thblackbar.h"
+
+DownloadView::DownloadView(QWidget *parent) : QWidget(parent) {
+
+    QBoxLayout *layout = new QVBoxLayout(this);
+    layout->setMargin(0);
+    layout->setSpacing(0);
+
+    bar = new THBlackBar(this);
+    QAction *action = new QAction(tr("Downloads"), this);
+    bar->addAction(action);
+    layout->addWidget(bar);
+
+    listView = new DownloadListView(this);
+#ifdef APP_MAC
+    listView->setAlternatingRowColors(true);
+#endif
+    /*
+    QPalette p = listView->palette();
+    p.setColor(QPalette::Base, palette().color(QPalette::Window));
+    listView->setPalette(p);
+    */
+    PrettyItemDelegate *delegate = new PrettyItemDelegate(this, true);
+    listView->setItemDelegate(delegate);
+    listView->setSelectionMode(QAbstractItemView::NoSelection);
+
+    // cosmetics
+    listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
+    listView->setFrameShape(QFrame::NoFrame);
+    listView->setAttribute(Qt::WA_MacShowFocusRect, false);
+    listView->setMinimumSize(320,240);
+    listView->setUniformItemSizes(true);
+
+    listModel = DownloadManager::instance()->getModel();
+    listView->setModel(listModel);
+    connect(listView, SIGNAL(downloadButtonPushed(QModelIndex)), SLOT(buttonPushed(QModelIndex)));
+    connect(listView, SIGNAL(entered(const QModelIndex &)), SLOT(itemEntered(const QModelIndex &)));
+
+    layout->addWidget(listView);
+
+    updateTimer = new QTimer(this);
+    updateTimer->setInterval(1000);
+    connect(updateTimer, SIGNAL(timeout()), listModel, SLOT(sendReset()));
+
+    downloadSettings = new DownloadSettings(this);
+    layout->addWidget(downloadSettings);
+}
+
+void DownloadView::appear() {
+    listView->setEnabled(true);
+    listModel->sendReset();
+    listView->setMouseTracking(true);
+    updateTimer->start();
+}
+
+void DownloadView::disappear() {
+    listView->setEnabled(false);
+    listView->setMouseTracking(false);
+}
+
+void DownloadView::itemEntered(const QModelIndex &index) {
+    listModel->setHoveredRow(index.row());
+}
+
+void DownloadView::buttonPushed(QModelIndex index) {
+    const DownloadItemPointer downloadItemPointer = index.data(DownloadItemRole).value<DownloadItemPointer>();
+    DownloadItem *downloadItem = downloadItemPointer.data();
+
+    switch (downloadItem->status()) {
+    case Downloading:
+    case Starting:
+        downloadItem->stop();
+        break;
+    case Idle:
+    case Failed:
+        downloadItem->tryAgain();
+        break;
+    case Finished:
+        downloadItem->openFolder();
+    }
+
+}
diff --git a/src/downloadview.h b/src/downloadview.h
new file mode 100644 (file)
index 0000000..10f5589
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef DOWNLOADVIEW_H
+#define DOWNLOADVIEW_H
+
+#include <QtGui>
+#include "View.h"
+
+class THBlackBar;
+class DownloadModel;
+class DownloadListView;
+class DownloadSettings;
+
+class DownloadView : public QWidget, public View {
+
+    Q_OBJECT
+
+public:
+    DownloadView(QWidget *parent);
+    void appear();
+    void disappear();
+    QMap<QString, QVariant> metadata() {
+        QMap<QString, QVariant> metadata;
+        metadata.insert("title", tr("Downloads"));
+        metadata.insert("description", "");
+        return metadata;
+    }
+
+public slots:
+    void itemEntered(const QModelIndex &index);
+    void buttonPushed(QModelIndex index);
+
+private:
+    THBlackBar *bar;
+    DownloadListView *listView;
+    DownloadModel *listModel;
+    QTimer *updateTimer;
+    DownloadSettings *downloadSettings;
+
+};
+
+#endif // DOWNLOADVIEW_H
diff --git a/src/flickcharm.cpp b/src/flickcharm.cpp
deleted file mode 100644 (file)
index 1f5175b..0000000
+++ /dev/null
@@ -1,327 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
-** Contact: Qt Software Information (qt-info@nokia.com)
-**
-** This file is part of the Graphics Dojo project on Qt Labs.
-**
-** This file may be used under the terms of the GNU General Public
-** License version 2.0 or 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of
-** this file.  Please review the following information to ensure GNU
-** General Public Licensing requirements will be met:
-** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
-** http://www.gnu.org/copyleft/gpl.html.
-**
-** If you are unsure which license is appropriate for your use, please
-** contact the sales department at qt-sales@nokia.com.
-**
-** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
-** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-**
-****************************************************************************/
-
-#include "flickcharm.h"
-
-#include <QAbstractScrollArea>
-#include <QApplication>
-#include <QBasicTimer>
-#include <QEvent>
-#include <QHash>
-#include <QList>
-#include <QMouseEvent>
-#include <QScrollBar>
-
-#include <QDebug>
-
-struct FlickData {
-    typedef enum { Steady, Pressed, ManualScroll, AutoScroll, Stop } State;
-    State state;
-    QWidget *widget;
-    QPoint pressPos;
-    QPoint offset;
-    QPoint dragPos;
-    QPoint speed;
-    QList<QEvent*> ignored;
-};
-
-class FlickCharmPrivate
-{
-public:
-    QHash<QWidget*, FlickData*> flickData;
-    QBasicTimer ticker;
-};
-
-FlickCharm::FlickCharm(QObject *parent): QObject(parent)
-{
-    d = new FlickCharmPrivate;
-}
-
-FlickCharm::~FlickCharm()
-{
-    delete d;
-}
-
-void FlickCharm::activateOn(QWidget *widget)
-{
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-
-        QWidget *viewport = scrollArea->viewport();
-
-        viewport->installEventFilter(this);
-        scrollArea->installEventFilter(this);
-
-        d->flickData.remove(viewport);
-        d->flickData[viewport] = new FlickData;
-        d->flickData[viewport]->widget = widget;
-        d->flickData[viewport]->state = FlickData::Steady;
-
-        return;
-    }
-
-    qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
-}
-
-void FlickCharm::deactivateFrom(QWidget *widget)
-{
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        QWidget *viewport = scrollArea->viewport();
-
-        viewport->removeEventFilter(this);
-        scrollArea->removeEventFilter(this);
-
-        delete d->flickData[viewport];
-        d->flickData.remove(viewport);
-
-        return;
-    }
-}
-
-static QPoint scrollOffset(QWidget *widget)
-{
-    int x = 0, y = 0;
-
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        x = scrollArea->horizontalScrollBar()->value();
-        y = scrollArea->verticalScrollBar()->value();
-    }
-
-    return QPoint(x, y);
-}
-
-static void setScrollOffset(QWidget *widget, const QPoint &p)
-{
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        scrollArea->horizontalScrollBar()->setValue(p.x());
-        scrollArea->verticalScrollBar()->setValue(p.y());
-    }
-}
-
-static QPoint deaccelerate(const QPoint &speed, int a = 1, int max = 64)
-{
-    int x = qBound(-max, speed.x(), max);
-    int y = qBound(-max, speed.y(), max);
-    x = (x == 0) ? x : (x > 0) ? qMax(0, x - a) : qMin(0, x + a);
-    y = (y == 0) ? y : (y > 0) ? qMax(0, y - a) : qMin(0, y + a);
-    return QPoint(x, y);
-}
-
-bool FlickCharm::eventFilter(QObject *object, QEvent *event)
-{
-
-    if (!object->isWidgetType())
-        return false;
-
-    QEvent::Type type = event->type();
-    if (type != QEvent::MouseButtonPress &&
-        type != QEvent::MouseButtonRelease &&
-        type != QEvent::MouseMove)
-        return false;
-
-    QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);
-    if (!mouseEvent || mouseEvent->modifiers() != Qt::NoModifier)
-        return false;
-
-    QWidget *viewport = dynamic_cast<QWidget*>(object);
-    FlickData *data = d->flickData.value(viewport);
-    if (!viewport || !data || data->ignored.removeAll(event))
-        return false;
-
-    QWidget *scrollArea = dynamic_cast<QWidget*>(object);
-
-    bool consumed = false;
-    switch (data->state) {
-
-    case FlickData::Steady:
-        if (mouseEvent->type() == QEvent::MouseButtonPress)
-            if (mouseEvent->buttons() == Qt::LeftButton) {
-            consumed = true;
-            data->state = FlickData::Pressed;
-            data->pressPos = mouseEvent->pos();
-            data->offset = scrollOffset(data->widget);
-        }
-        break;
-
-    case FlickData::Pressed:
-        if (mouseEvent->type() == QEvent::MouseButtonRelease) {
-            consumed = true;
-            data->state = FlickData::Steady;
-
-            QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
-                                                  data->pressPos, Qt::LeftButton,
-                                                  Qt::LeftButton, Qt::NoModifier);
-            QMouseEvent *event2 = new QMouseEvent(*mouseEvent);
-
-            data->ignored << event1;
-            data->ignored << event2;
-            QApplication::postEvent(object, event1);
-            QApplication::postEvent(object, event2);
-        }
-        if (mouseEvent->type() == QEvent::MouseMove) {
-
-            consumed = true;
-            data->state = FlickData::ManualScroll;
-            data->dragPos = QCursor::pos();
-            if (!d->ticker.isActive())
-                d->ticker.start(20, this);
-
-        }
-        break;
-
-    case FlickData::ManualScroll:
-        if (mouseEvent->type() == QEvent::MouseMove) {
-            QPoint pos = scrollArea->mapFromGlobal(QCursor::pos());
-            if (pos.x() > scrollArea->width() || pos.x() < 0) {
-                pos.setX(1);
-                QMouseEvent *event1 = new QMouseEvent(QEvent::MouseButtonPress,
-                                                      pos, Qt::LeftButton,
-                                                      Qt::LeftButton, Qt::NoModifier);
-                QMouseEvent *event2 = new QMouseEvent(QEvent::MouseMove,
-                                                      pos, Qt::LeftButton,
-                                                      Qt::LeftButton, Qt::NoModifier);
-
-                data->ignored << event1;
-                data->ignored << event2;
-                QApplication::postEvent(object, event1);
-                QApplication::postEvent(object, event2);
-                data->state = FlickData::Steady;
-                consumed = true;
-            } else {
-                consumed = true;
-                QPoint delta = mouseEvent->pos() - data->pressPos;
-                setScrollOffset(data->widget, data->offset - delta);
-            }
-        }
-        if (mouseEvent->type() == QEvent::MouseButtonRelease) {
-            consumed = true;
-            data->state = FlickData::AutoScroll;
-        }
-        break;
-
-    case FlickData::AutoScroll:
-        if (mouseEvent->type() == QEvent::MouseButtonPress) {
-            consumed = true;
-            data->state = FlickData::Stop;
-            data->speed = QPoint(0, 0);
-            data->pressPos = mouseEvent->pos();
-            data->offset = scrollOffset(data->widget);
-        }
-        if (mouseEvent->type() == QEvent::MouseButtonRelease) {
-            consumed = true;
-            data->state = FlickData::Steady;
-            data->speed = QPoint(0, 0);
-        }
-        break;
-
-    case FlickData::Stop:
-        if (mouseEvent->type() == QEvent::MouseButtonRelease) {
-            consumed = true;
-            data->state = FlickData::Steady;
-        }
-        if (mouseEvent->type() == QEvent::MouseMove) {
-            consumed = true;
-            data->state = FlickData::ManualScroll;
-            data->dragPos = QCursor::pos();
-            if (!d->ticker.isActive())
-                d->ticker.start(20, this);
-        }
-        break;
-
-    default:
-        break;
-    }
-
-    return consumed;
-}
-
-void FlickCharm::timerEvent(QTimerEvent *event)
-{
-    int count = 0;
-    QHashIterator<QWidget*, FlickData*> item(d->flickData);
-    while (item.hasNext()) {
-        item.next();
-        FlickData *data = item.value();
-
-        const bool scrolling = data->state == FlickData::ManualScroll || data->state == FlickData::AutoScroll;
-        scrollBarShow(data->widget, scrolling);
-        // data->widget->setUpdatesEnabled(!scrolling);
-
-        if (data->state == FlickData::ManualScroll) {
-            count++;
-            data->speed = QCursor::pos() - data->dragPos;
-            data->dragPos = QCursor::pos();
-        }
-
-        if (data->state == FlickData::AutoScroll) {
-            count++;
-            data->speed = deaccelerate(data->speed);
-            QPoint p = scrollOffset(data->widget);
-            setScrollOffset(data->widget, p - data->speed);
-            if (data->speed == QPoint(0, 0))
-                data->state = FlickData::Steady;
-        }
-    }
-
-    if (!count)
-        d->ticker.stop();
-
-    QObject::timerEvent(event);
-}
-
-void FlickCharm::showScrollBars(QWidget *widget) {
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        // scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
-        scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
-    }
-}
-
-void FlickCharm::hideScrollBars(QWidget *widget) {
-    QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(widget);
-    if (scrollArea) {
-        scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-    }
-}
-
-void FlickCharm::scrollBarShow(QWidget *widget, bool show) {
-    static bool shown = false;
-
-    if (show) {
-        if (!shown) {
-            showScrollBars(widget);
-            shown = true;
-        }
-    } else {
-        if (shown) {
-            hideScrollBars(widget);
-            shown = false;
-        }
-    }
-}
diff --git a/src/flickcharm.h b/src/flickcharm.h
deleted file mode 100644 (file)
index 2a1bfea..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
-** Contact: Qt Software Information (qt-info@nokia.com)
-**
-** This file is part of the Graphics Dojo project on Qt Labs.
-**
-** This file may be used under the terms of the GNU General Public
-** License version 2.0 or 3.0 as published by the Free Software Foundation
-** and appearing in the file LICENSE.GPL included in the packaging of
-** this file.  Please review the following information to ensure GNU
-** General Public Licensing requirements will be met:
-** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
-** http://www.gnu.org/copyleft/gpl.html.
-**
-** If you are unsure which license is appropriate for your use, please
-** contact the sales department at qt-sales@nokia.com.
-**
-** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
-** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
-**
-****************************************************************************/
-
-#ifndef FLICKCHARM_H
-#define FLICKCHARM_H
-
-#include <QObject>
-
-class FlickCharmPrivate;
-class QWidget;
-
-class FlickCharm: public QObject
-{
-    Q_OBJECT
-public:
-    FlickCharm(QObject *parent = 0);
-    ~FlickCharm();
-    void activateOn(QWidget *widget);
-    void deactivateFrom(QWidget *widget);
-    bool eventFilter(QObject *object, QEvent *event);
-
-protected:
-    void timerEvent(QTimerEvent *event);
-
-private:
-    void scrollBarShow(QWidget *widget, bool show);
-    void hideScrollBars(QWidget *widget);
-    void showScrollBars(QWidget *widget);
-    FlickCharmPrivate *d;
-};
-
-#endif // FLICKCHARM_H
index 0002ed08e547ae904ee373d6cb1ccc943e77e279..656c6512876a84e808b9ddd4e4eda09d9b85023b 100644 (file)
@@ -16,6 +16,7 @@ GSuggestCompletion::GSuggestCompletion(QWidget *parent, QLineEdit *editor):
     popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
     popup->installEventFilter(this);
     popup->setMouseTracking(true);
+    popup->setWindowOpacity(.9);
 
     connect(popup, SIGNAL(itemClicked(QListWidgetItem*)),
             SLOT(doneCompletion()));
index 0649c5cc03ec0fa59de2c508a868b33fd3fac46c..6e15c107aa7df50730df41f4752f3e22efdd35c4 100644 (file)
@@ -116,6 +116,10 @@ Q_GLOBAL_STATIC(QtIconLoaderImplementation, iconLoaderInstance)
 #endif
 #else
         icon = QIcon(QString(":/images/%1.png").arg(name));
+        if (!icon.isNull()) {
+            icon.addPixmap(QString(":/images/%1_active.png").arg(name), QIcon::Active);
+            icon.addPixmap(QString(":/images/%1_selected.png").arg(name), QIcon::Selected);
+        }
 #endif
 
     return icon;
index ec2d77ce5251f36637669bbd0b1b2c782086eeea..6e87178f1695e4d19b35becb67389958f90f8d12 100755 (executable)
@@ -3,13 +3,13 @@
 #include "constants.h"
 #include "MainWindow.h"
 #ifdef APP_MAC
-#include "local/mac/mac_startup.h"
+// #include "local/mac/mac_startup.h"
 #endif
 
 int main(int argc, char **argv) {
 
 #ifdef APP_MAC
-    mac::MacMain();
+    // mac::MacMain();
 #endif
 
     QtSingleApplication app(argc, argv);
index cc25c4dcd898f861daf4d81e65eb3ba65da383d1..4f64c9907c3fd29c872ae0563e804cd92b443703 100644 (file)
@@ -1,6 +1,8 @@
 #include "PrettyItemDelegate.h"
 #include "../ListModel.h"
 #include "../fontutils.h"
+#include "../downloaditem.h"
+#include "../iconloader/qticonloader.h"
 
 #include <QFontMetricsF>
 #include <QPainter>
@@ -9,11 +11,21 @@ const qreal PrettyItemDelegate::THUMB_HEIGHT = 90.0;
 const qreal PrettyItemDelegate::THUMB_WIDTH = 120.0;
 const qreal PrettyItemDelegate::PADDING = 10.0;
 
-PrettyItemDelegate::PrettyItemDelegate( QObject* parent ) : QStyledItemDelegate( parent ) {
+PrettyItemDelegate::PrettyItemDelegate(QObject* parent, bool downloadInfo)
+    : QStyledItemDelegate(parent),
+    downloadInfo(downloadInfo) {
     boldFont.setBold(true);
     smallerBoldFont = FontUtils::smallBold();
     smallerFont = FontUtils::small();
-    createPlayIcon();
+
+    if (downloadInfo) {
+        progressBar = new QProgressBar(qApp->activeWindow());
+        QPalette palette = progressBar->palette();
+        palette.setColor(QPalette::Window, Qt::transparent);
+        progressBar->setPalette(palette);
+        progressBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
+        progressBar->hide();
+    } else createPlayIcon();
 }
 
 void PrettyItemDelegate::createPlayIcon() {
@@ -61,7 +73,8 @@ void PrettyItemDelegate::paintBody( QPainter* painter,
     painter->translate( option.rect.topLeft() );
 
 
-    const QRectF line(0, 0, option.rect.width(), option.rect.height());
+    QRectF line(0, 0, option.rect.width(), option.rect.height());
+    if (downloadInfo) line.setWidth(line.width() / 2);
     painter->setClipRect(line);
 
     const bool isActive = index.data( ActiveTrackRole ).toBool();
@@ -96,8 +109,6 @@ void PrettyItemDelegate::paintBody( QPainter* painter,
     }
 
     if (isActive) painter->setFont(boldFont);
-    const QFontMetricsF fm(painter->font());
-    const QFontMetricsF boldMetrics(boldFont);
 
     // text color
     if (isSelected)
@@ -158,28 +169,17 @@ void PrettyItemDelegate::paintBody( QPainter* painter,
     */
 
     // separator
+    painter->setClipping(false);
     painter->setPen(option.palette.color(QPalette::Midlight));
-    painter->drawLine(THUMB_WIDTH, THUMB_HEIGHT, line.width(), THUMB_HEIGHT);
+    painter->drawLine(THUMB_WIDTH, THUMB_HEIGHT, option.rect.width(), THUMB_HEIGHT);
     if (!video->thumbnail().isNull())
         painter->setPen(Qt::black);
     painter->drawLine(0, THUMB_HEIGHT, THUMB_WIDTH-1, THUMB_HEIGHT);
 
     painter->restore();
 
-}
-
-QPointF PrettyItemDelegate::centerImage( const QPixmap& pixmap, const QRectF& rect ) const {
-    qreal pixmapRatio = ( qreal )pixmap.width() / ( qreal )pixmap.height();
-
-    qreal moveByX = 0.0;
-    qreal moveByY = 0.0;
-
-    if ( pixmapRatio >= 1 )
-        moveByY = ( rect.height() - ( rect.width() / pixmapRatio ) ) / 2.0;
-    else
-        moveByX = ( rect.width() - ( rect.height() * pixmapRatio ) ) / 2.0;
+    if (downloadInfo) paintDownloadInfo(painter, option, index);
 
-    return QPointF( moveByX, moveByY );
 }
 
 void PrettyItemDelegate::paintActiveOverlay( QPainter *painter, qreal x, qreal y, qreal w, qreal h ) const {
@@ -238,3 +238,120 @@ void PrettyItemDelegate::drawTime(QPainter *painter, QString time, QRectF line)
     painter->drawText(textBox, Qt::AlignCenter, time);
     painter->restore();
 }
+
+void PrettyItemDelegate::paintDownloadInfo( QPainter* painter,
+                                            const QStyleOptionViewItem& option,
+                                            const QModelIndex& index ) const {
+
+    // get the video metadata
+    const DownloadItemPointer downloadItemPointer = index.data(DownloadItemRole).value<DownloadItemPointer>();
+    const DownloadItem *downloadItem = downloadItemPointer.data();
+
+    painter->save();
+
+    const QRect line(0, 0, option.rect.width() / 2, option.rect.height());
+
+    painter->translate(option.rect.topLeft());
+    painter->translate(line.width(), 0);
+
+    QString message;
+    DownloadItemStatus status = downloadItem->status();
+
+    if (status == Downloading) {
+        QString downloaded = DownloadItem::formattedFilesize(downloadItem->bytesReceived());
+        QString total = DownloadItem::formattedFilesize(downloadItem->bytesTotal());
+        QString speed = DownloadItem::formattedSpeed(downloadItem->currentSpeed());
+        QString eta = DownloadItem::formattedTime(downloadItem->remainingTime());
+
+        message = tr("%1 of %2 (%3) — %4").arg(
+                downloaded,
+                total,
+                speed,
+                eta
+                );
+    } else if (status == Starting) {
+        message = tr("Preparing");
+    } else if (status == Failed) {
+        message = tr("Failed") + " — " + downloadItem->errorMessage();
+    } else if (status == Finished) {
+        message = tr("Completed");
+    } else if (status == Idle) {
+        message = tr("Stopped");
+    }
+
+    // progressBar->setPalette(option.palette);
+    if (status == Finished) {
+        progressBar->setValue(100);
+        progressBar->setEnabled(true);
+    } else if (status == Downloading) {
+        progressBar->setValue(downloadItem->currentPercent());
+        progressBar->setEnabled(true);
+    } else {
+        progressBar->setValue(0);
+        progressBar->setEnabled(false);
+    }
+
+    int progressBarWidth = line.width() - PADDING*4 - 16;
+    progressBar->setMaximumWidth(progressBarWidth);
+    progressBar->setMinimumWidth(progressBarWidth);
+    painter->save();
+    painter->translate(PADDING, PADDING);
+    progressBar->render(painter);
+    painter->restore();
+
+    bool downloadButtonHovered = false;
+    bool downloadButtonPressed = false;
+    const bool isHovered = index.data(HoveredItemRole).toBool();
+    if (isHovered) {
+        downloadButtonHovered = index.data(DownloadButtonHoveredRole).toBool();
+        downloadButtonPressed = index.data(DownloadButtonPressedRole).toBool();
+    }
+    QIcon::Mode iconMode;
+    if (downloadButtonPressed) iconMode = QIcon::Selected;
+    else if (downloadButtonHovered) iconMode = QIcon::Active;
+    else iconMode = QIcon::Normal;
+
+    if (status != Finished && status != Failed && status != Idle) {
+        if (downloadButtonHovered) message = tr("Stop downloading");
+        painter->save();
+        QIcon closeIcon = QtIconLoader::icon("window-close");
+        painter->drawPixmap(downloadButtonRect(line), closeIcon.pixmap(16, 16, iconMode));
+        painter->restore();
+    }
+
+    else if (status == Finished) {
+        if (downloadButtonHovered)
+#ifdef APP_MAC
+        message = tr("Show in %1").arg("Finder");
+#else
+        message = tr("Open parent folder");
+#endif
+        painter->save();
+        QIcon searchIcon = QtIconLoader::icon("system-search");
+        painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
+        painter->restore();
+    }
+
+    else if (status == Failed || status == Idle) {
+        if (downloadButtonHovered) message = tr("Restart downloading");
+        painter->save();
+        QIcon searchIcon = QtIconLoader::icon("view-refresh");
+        painter->drawPixmap(downloadButtonRect(line), searchIcon.pixmap(16, 16, iconMode));
+        painter->restore();
+    }
+
+    QRectF textBox = line.adjusted(PADDING, PADDING*2 + progressBar->sizeHint().height(), -2 * PADDING, -PADDING);
+    textBox = painter->boundingRect( textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
+    painter->drawText(textBox, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
+
+    painter->restore();
+
+}
+
+QRect PrettyItemDelegate::downloadButtonRect(QRect line) const {
+    return QRect(
+            line.width() - PADDING*2 - 16,
+            PADDING + progressBar->sizeHint().height() / 2 - 8,
+            16,
+            16);
+}
index 6fc547b894f8a230428dec88929ed506b2f416c8..42605587a1920ff347ec234698c2a7321c72af1d 100644 (file)
@@ -5,22 +5,26 @@
 #include <QStyledItemDelegate>
 
 class QPainter;
+class QProgressBar;
 
 class PrettyItemDelegate : public QStyledItemDelegate {
 
     Q_OBJECT
 
 public:
-    PrettyItemDelegate( QObject* parent = 0 );
+    PrettyItemDelegate(QObject* parent, bool downloadInfo = false);
     ~PrettyItemDelegate();
 
     QSize sizeHint( const QStyleOptionViewItem&, const QModelIndex& ) const;
     void paint( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const;
+    QRect downloadButtonRect(QRect line) const;
 
 private:
     void createPlayIcon();
     void paintBody( QPainter*, const QStyleOptionViewItem&, const QModelIndex& ) const;
-    QPointF centerImage( const QPixmap&, const QRectF& ) const;
+    void paintDownloadInfo( QPainter* painter,
+                                        const QStyleOptionViewItem& option,
+                                        const QModelIndex& index ) const;
 
     // active track painting
     void paintActiveOverlay( QPainter *painter, qreal x, qreal y, qreal w, qreal h ) const;
@@ -37,6 +41,9 @@ private:
     QFont boldFont;
     QFont smallerFont;
     QFont smallerBoldFont;
+
+    bool downloadInfo;
+    QProgressBar *progressBar;
 };
 
 #endif
index 42c2aeee3726e07708d31500b805ec02a631790a..3066415ca0c7edd55690ed78225e7c5515e78e18 100644 (file)
@@ -173,6 +173,11 @@ m_searchButton(new SearchButton(this))
     connect(lineEdit(), SIGNAL(returnPressed()),
             this, SLOT(returnPressed()));
 
+#ifdef APP_MAC_NO
+    setAttribute(Qt::WA_MacShowFocusRect, false);
+    setStyleSheet("SearchLineEdit { border: 1px inset gray; border-radius: 15px; background: palette(base)} SearchLineEdit::focus { border: 2px solid #6fa7e0 }");
+#endif
+
     setLeftWidget(m_searchButton);
     m_inactiveText = tr("Search");
 
index 8ec74383ae2a1fe16968e51469e5d18360fb8f71..4101be04650a7705d50b1547cfd48a466ca442b8 100644 (file)
@@ -67,8 +67,8 @@ QSize THBlackBar::minimumSizeHint (void) const {
  *  PROTECTED Methods
  */
 void THBlackBar::paintEvent (QPaintEvent *event) {
-    int height = event->rect().height();
-    int width = event->rect().width();
+    int height = rect().height();
+    int width = rect().width();
     // int mh = (height / 2);
 
     // THPainter p(this);
@@ -244,8 +244,11 @@ void THBlackBar::drawButton (      QPainter *painter,
 
     painter->fillRect(0, 0, width, mh, QBrush(gradient));
     painter->fillRect(0, mh, width, mh, color);
+#ifdef APP_MAC
+    painter->drawRect(-1, -1, width+1, height);
+#else
     painter->drawRect(0, 0, width, height);
-
+#endif
     QFont smallerBoldFont = FontUtils::smallBold();
     painter->setFont(smallerBoldFont);
     painter->setPen(QPen(QColor(0xff, 0xff, 0xff), 1));
index 6e902485d811b0092c7151fd8d85b13b353237ff..35e837109f7fd927321dfe56d4666ab75b38de94 100644 (file)
@@ -10,7 +10,27 @@ namespace The {
 Video::Video() : m_duration(0),
 m_viewCount(-1),
 definitionCode(0),
-elIndex(0) { }
+elIndex(0),
+loadingStreamUrl(false)
+{ }
+
+Video* Video::clone() {
+    Video* cloneVideo = new Video();
+    cloneVideo->m_title = m_title;
+    cloneVideo->m_description = m_description;
+    cloneVideo->m_author = m_author;
+    cloneVideo->m_webpage = m_webpage;
+    cloneVideo->m_streamUrl = m_streamUrl;
+    cloneVideo->m_thumbnail = m_thumbnail;
+    cloneVideo->m_thumbnailUrls = m_thumbnailUrls;
+    cloneVideo->m_duration = m_duration;
+    cloneVideo->m_published = m_published;
+    cloneVideo->m_viewCount = m_viewCount;
+    cloneVideo->videoId = videoId;
+    cloneVideo->videoToken = videoToken;
+    cloneVideo->definitionCode = definitionCode;
+    return cloneVideo;
+}
 
 void Video::preloadThumbnail() {
     if (m_thumbnailUrls.isEmpty()) return;
@@ -28,6 +48,11 @@ const QImage Video::thumbnail() const {
 }
 
 void Video::loadStreamUrl() {
+    if (loadingStreamUrl) {
+        qDebug() << "Already loading stream URL for" << this->title();
+        return;
+    }
+    loadingStreamUrl = true;
 
     // https://develop.participatoryculture.org/trac/democracy/browser/trunk/tv/portable/flashscraper.py
 
@@ -38,6 +63,7 @@ void Video::loadStreamUrl() {
     bool match = re.exactMatch(m_webpage.toString());
     if (!match || re.numCaptures() < 1) {
         emit errorStreamUrl(QString("Cannot get video id for %1").arg(m_webpage.toString()));
+        loadingStreamUrl = false;
         return;
     }
     videoId = re.cap(1);
@@ -89,7 +115,7 @@ void  Video::gotVideoInfo(QByteArray data) {
     QString videoToken = re.cap(1);
     while (videoToken.contains('%'))
         videoToken = QByteArray::fromPercentEncoding(videoToken.toAscii());
-    qDebug() << "videoToken" << videoToken;
+    // qDebug() << "videoToken" << videoToken;
     this->videoToken = videoToken;
 
     /*
@@ -132,6 +158,7 @@ void  Video::gotVideoInfo(QByteArray data) {
 }
 
 void Video::foundVideoUrl(QString videoToken, int definitionCode) {
+    // qDebug() << "foundVideoUrl" << videoToken << definitionCode;
 
     QUrl videoUrl = QUrl(QString(
             "http://www.youtube.com/get_video?video_id=%1&t=%2&eurl=&el=&ps=&asv=&fmt=%3"
@@ -139,10 +166,12 @@ void Video::foundVideoUrl(QString videoToken, int definitionCode) {
 
     m_streamUrl = videoUrl;
     emit gotStreamUrl(videoUrl);
+    loadingStreamUrl = false;
 }
 
 void Video::errorVideoInfo(QNetworkReply *reply) {
     emit errorStreamUrl(tr("Network error: %1 for %2").arg(reply->errorString(), reply->url().toString()));
+    loadingStreamUrl = false;
 }
 
 void Video::scrapeWebPage(QByteArray data) {
@@ -154,6 +183,7 @@ void Video::scrapeWebPage(QByteArray data) {
     // on regexp failure, stop and report error
     if (!match || re.numCaptures() < 1) {
         emit errorStreamUrl("Error parsing video page");
+        loadingStreamUrl = false;
         return;
     }
 
index 2702d7ae3f814e1e7635a4cdfbf1fbd3e2bf21be..68a17f542f0ef08420524983df0adbe445e7b163 100644 (file)
@@ -10,6 +10,7 @@ class Video : public QObject {
 
 public:
     Video();
+    Video* clone();
 
     const QString title() const { return m_title; }
     void setTitle( QString title ) { m_title = title; }
@@ -45,6 +46,8 @@ public:
     void loadStreamUrl();
     QUrl getStreamUrl() { return m_streamUrl; }
 
+    QString id() { return videoId; }
+
 public slots:
     void setThumbnail(QByteArray bytes);
 
@@ -85,6 +88,8 @@ private:
     // current index for the elTypes list
     // needed to iterate on elTypes
     int elIndex;
+    
+    bool loadingStreamUrl;
 };
 
 // This is required in order to use QPointer<Video> as a QVariant
index 45b2ad457e251aa8faa94a52514c16647ec25bf1..6bb66cb33a36eafa147a743e20a83256f95a94c1 100644 (file)
@@ -18,9 +18,9 @@ void YouTubeSearch::search(SearchParams *searchParams, int max, int skip) {
 
     // Useful to test with a local webserver
     /*
-    urlString = QString("http://localhost/oringo/video.xml?q=%1&max-results=%2&start-index=%3")
+    urlString = QString("http://localhost/~flavio/text.xml?q=%1&max-results=%2&start-index=%3")
                 .arg(searchParams->keywords(), QString::number(max), QString::number(skip));
-                */
+    */
 
     switch (searchParams->sortBy()) {
     case SearchParams::SortByNewest: