-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
## 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.
- 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/
CONFIG += release
TEMPLATE = app
-VERSION = 1.1
+VERSION = 1.2
DEFINES += APP_VERSION="$$VERSION"
INCLUDEPATH += /usr/include/phonon
QT += network \
xml \
phonon
-
include(src/qtsingleapplication/qtsingleapplication.pri)
-
HEADERS += src/MainWindow.h \
src/SearchView.h \
src/MediaView.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 \
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/
# 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
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) +
"<p>" + tr("Released under the <a href='%1'>GNU General Public License</a>")
.arg("http://www.gnu.org/licenses/gpl.html") + "</p>"
#endif
- "<p>© 2009-2010 " + Constants::ORG_NAME + "</p>";
+ "<p>© 2009-2010 " + Constants::ORG_NAME + "</p>"
+ "</body></html>";;
QLabel *infoLabel = new QLabel(info, this);
infoLabel->setOpenExternalLinks(true);
infoLabel->setWordWrap(true);
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();
m_activeRow = row;
m_activeVideo = videoAt(row);
- // setStateOfRow( row, Item::Played );
-
int oldactiverow = m_activeRow;
if ( rowExists( oldactiverow ) )
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) {
foreach (QModelIndex index, indexes) {
int row = index.row();
+ if (row >= videos.size()) continue;
// qDebug() << "index row" << row;
Video *video = videoAt(row);
movedVideos << video;
enum DataRoles {
ItemTypeRole = Qt::UserRole,
VideoRole,
- ActiveTrackRole
+ ActiveTrackRole,
+ DownloadItemRole,
+ HoveredItemRole,
+ DownloadButtonHoveredRole,
+ DownloadButtonPressedRole
};
enum ItemTypes {
#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) {
// views mechanism
history = new QStack<QWidget*>();
views = new QStackedWidget(this);
+ setCentralWidget(views);
// views
searchView = new SearchView(this);
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&)));
// 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
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() {
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()));
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()));
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);
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()) {
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);
mainToolBar->addAction(pauseAct);
mainToolBar->addAction(skipAct);
mainToolBar->addAction(fullscreenAct);
+ mainToolBar->addAction(The::globalActions()->value("download"));
mainToolBar->addWidget(new Spacer());
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());
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);
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();
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);
}
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();
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:
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:
pauseAct->setEnabled(false);
currentTime->clear();
totalTime->clear();
+ // stopAct->setEnabled(true);
break;
default:
void MainWindow::fullscreen() {
- setUpdatesEnabled(false);
-
// No compact view action when in full screen
compactViewAct->setVisible(m_fullscreen);
compactViewAct->setChecked(false);
// 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);
#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"));
// 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);
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() {
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();
+}
#include "SearchView.h"
#include "MediaView.h"
#include "AboutView.h"
+#include "downloadview.h"
class MainWindow : public QMainWindow {
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();
SearchView *searchView;
MediaView *mediaView;
QWidget *aboutView;
+ QWidget *downloadView;
// actions
QAction *addGadgetAct;
QAction *webPageAct;
QAction *copyPageAct;
QAction *copyLinkAct;
- QAction *downloadAct;
QAction *volumeUpAct;
QAction *volumeDownAct;
QAction *volumeMuteAct;
#include "networkaccess.h"
#include "videowidget.h"
#include "minisplitter.h"
-#include "flickcharm.h"
#include "constants.h"
+#include "downloadmanager.h"
namespace The {
QMap<QString, QAction*>* globalActions();
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() {
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...
}
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);
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();
+}
#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;
void moveDownSelected();
void setPlaylistVisible(bool visible=true);
void saveSplitterState();
+ void downloadVideo();
+ void fullscreen();
private slots:
// list/model
#endif
QBoxLayout *mainLayout = new QVBoxLayout();
- mainLayout->setMargin(PADDING);
+ mainLayout->setMargin(0);
mainLayout->setSpacing(0);
// hidden message widget
mainLayout->addWidget(message);
mainLayout->addStretch();
+ mainLayout->addSpacing(PADDING);
QBoxLayout *hLayout = new QHBoxLayout();
hLayout->setAlignment(Qt::AlignCenter);
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);
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);
layout->addLayout(otherLayout);
+ mainLayout->addSpacing(PADDING);
mainLayout->addStretch();
setLayout(mainLayout);
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"),
);
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;
}
--- /dev/null
+#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);
+}
--- /dev/null
+#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
--- /dev/null
+#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();
+}
--- /dev/null
+#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
--- /dev/null
+#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;
+}
--- /dev/null
+#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
--- /dev/null
+#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 ) );
+}
--- /dev/null
+#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
--- /dev/null
+#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));
+}
--- /dev/null
+#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
--- /dev/null
+#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();
+ }
+
+}
--- /dev/null
+#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
+++ /dev/null
-/****************************************************************************
-**
-** 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;
- }
- }
-}
+++ /dev/null
-/****************************************************************************
-**
-** 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
popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
popup->installEventFilter(this);
popup->setMouseTracking(true);
+ popup->setWindowOpacity(.9);
connect(popup, SIGNAL(itemClicked(QListWidgetItem*)),
SLOT(doneCompletion()));
#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;
#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);
#include "PrettyItemDelegate.h"
#include "../ListModel.h"
#include "../fontutils.h"
+#include "../downloaditem.h"
+#include "../iconloader/qticonloader.h"
#include <QFontMetricsF>
#include <QPainter>
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() {
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();
}
if (isActive) painter->setFont(boldFont);
- const QFontMetricsF fm(painter->font());
- const QFontMetricsF boldMetrics(boldFont);
// text color
if (isSelected)
*/
// 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 {
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);
+}
#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;
QFont boldFont;
QFont smallerFont;
QFont smallerBoldFont;
+
+ bool downloadInfo;
+ QProgressBar *progressBar;
};
#endif
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");
* 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);
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));
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;
}
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
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);
QString videoToken = re.cap(1);
while (videoToken.contains('%'))
videoToken = QByteArray::fromPercentEncoding(videoToken.toAscii());
- qDebug() << "videoToken" << videoToken;
+ // qDebug() << "videoToken" << videoToken;
this->videoToken = videoToken;
/*
}
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"
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) {
// on regexp failure, stop and report error
if (!match || re.numCaptures() < 1) {
emit errorStreamUrl("Error parsing video page");
+ loadingStreamUrl = false;
return;
}
public:
Video();
+ Video* clone();
const QString title() const { return m_title; }
void setTitle( QString title ) { m_title = title; }
void loadStreamUrl();
QUrl getStreamUrl() { return m_streamUrl; }
+ QString id() { return videoId; }
+
public slots:
void setThumbnail(QByteArray bytes);
// 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
// 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: