Add an Add Podcast dialog
This commit is contained in:
parent
90bbed1ec9
commit
c91acdb3f1
@ -338,5 +338,7 @@
|
||||
<file>schema/schema-36.sql</file>
|
||||
<file>grooveshark-valicert-ca.pem</file>
|
||||
<file>schema/schema-37.sql</file>
|
||||
<file>providers/podcast16.png</file>
|
||||
<file>providers/podcast32.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
data/providers/podcast16.png
Executable file
BIN
data/providers/podcast16.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 764 B |
BIN
data/providers/podcast32.png
Executable file
BIN
data/providers/podcast32.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
@ -227,10 +227,17 @@ set(SOURCES
|
||||
playlistparsers/xmlparser.cpp
|
||||
playlistparsers/xspfparser.cpp
|
||||
|
||||
podcasts/addpodcastbyurl.cpp
|
||||
podcasts/addpodcastdialog.cpp
|
||||
podcasts/addpodcastpage.cpp
|
||||
podcasts/podcast.cpp
|
||||
podcasts/podcastbackend.cpp
|
||||
podcasts/podcastdiscoverymodel.cpp
|
||||
podcasts/podcastepisode.cpp
|
||||
podcasts/podcastinfowidget.cpp
|
||||
podcasts/podcastservice.cpp
|
||||
podcasts/podcastparser.cpp
|
||||
podcasts/podcasturlloader.cpp
|
||||
|
||||
smartplaylists/generator.cpp
|
||||
smartplaylists/generatorinserter.cpp
|
||||
@ -467,7 +474,14 @@ set(HEADERS
|
||||
playlistparsers/plsparser.h
|
||||
playlistparsers/xspfparser.h
|
||||
|
||||
podcasts/addpodcastbyurl.h
|
||||
podcasts/addpodcastdialog.h
|
||||
podcasts/addpodcastpage.h
|
||||
podcasts/podcastbackend.h
|
||||
podcasts/podcastdiscoverymodel.h
|
||||
podcasts/podcastinfowidget.h
|
||||
podcasts/podcastservice.h
|
||||
podcasts/podcasturlloader.h
|
||||
|
||||
smartplaylists/generator.h
|
||||
smartplaylists/generatorinserter.h
|
||||
@ -586,6 +600,10 @@ set(UI
|
||||
playlist/playlistsequence.ui
|
||||
playlist/queuemanager.ui
|
||||
|
||||
podcasts/addpodcastbyurl.ui
|
||||
podcasts/addpodcastdialog.ui
|
||||
podcasts/podcastinfowidget.ui
|
||||
|
||||
remote/remotesettingspage.ui
|
||||
|
||||
smartplaylists/querysearchpage.ui
|
||||
|
@ -450,6 +450,16 @@ QStringList Updateify(const QStringList& list) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString DecodeHtmlEntities(const QString& text) {
|
||||
QString copy(text);
|
||||
copy.replace("&", "&");
|
||||
copy.replace(""", "\"");
|
||||
copy.replace("'", "'");
|
||||
copy.replace("<", "<");
|
||||
copy.replace(">", ">");
|
||||
return copy;
|
||||
}
|
||||
|
||||
int SetThreadIOPriority(IoPriority priority) {
|
||||
#ifdef Q_OS_LINUX
|
||||
return syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, GetThreadId(),
|
||||
|
@ -89,6 +89,9 @@ namespace Utilities {
|
||||
// Parses a string containing an RFC822 time and date.
|
||||
QDateTime ParseRFC822DateTime(const QString& text);
|
||||
|
||||
// Replaces some HTML entities with their normal characters.
|
||||
QString DecodeHtmlEntities(const QString& text);
|
||||
|
||||
// Shortcut for getting a Qt-aware enum value as a string.
|
||||
// Pass in the QMetaObject of the class that owns the enum, the string name of
|
||||
// the enum and a valid value from that enum.
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "groovesharkservice.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mergedproxymodel.h"
|
||||
#include "podcasts/podcastservice.h"
|
||||
#include "smartplaylists/generatormimedata.h"
|
||||
|
||||
#ifdef HAVE_LIBLASTFM
|
||||
@ -65,6 +66,7 @@ InternetModel::InternetModel(Application* app, QObject* parent)
|
||||
#endif
|
||||
AddService(new GroovesharkService(app, this));
|
||||
AddService(new MagnatuneService(app, this));
|
||||
AddService(new PodcastService(app, this));
|
||||
AddService(new SavedRadio(app, this));
|
||||
AddService(new SkyFmService(app, this));
|
||||
AddService(new SomaFMService(app, this));
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "ui_magnatunedownloaddialog.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
#include "widgets/progressitemdelegate.h"
|
||||
|
||||
#include <QCloseEvent>
|
||||
@ -176,8 +177,7 @@ void MagnatuneDownloadDialog::MetadataFinished() {
|
||||
}
|
||||
|
||||
// Munge the URL a bit
|
||||
QString url_text = re.cap(1);
|
||||
url_text.replace("&", "&");
|
||||
QString url_text = Utilities::DecodeHtmlEntities(re.cap(1));
|
||||
|
||||
QUrl url = QUrl(url_text);
|
||||
url.setUserName(service_->username());
|
||||
|
70
src/podcasts/addpodcastbyurl.cpp
Normal file
70
src/podcasts/addpodcastbyurl.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "addpodcastbyurl.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "ui_addpodcastbyurl.h"
|
||||
#include "core/closure.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QMessageBox>
|
||||
|
||||
AddPodcastByUrl::AddPodcastByUrl(Application* app, QWidget* parent)
|
||||
: AddPodcastPage(app, parent),
|
||||
ui_(new Ui_AddPodcastByUrl),
|
||||
loader_(new PodcastUrlLoader(this))
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
connect(ui_->go, SIGNAL(clicked()), SLOT(GoClicked()));
|
||||
}
|
||||
|
||||
AddPodcastByUrl::~AddPodcastByUrl() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void AddPodcastByUrl::GoClicked() {
|
||||
emit Busy(true);
|
||||
model()->clear();
|
||||
ui_->go->setEnabled(false);
|
||||
ui_->url->setEnabled(false);
|
||||
|
||||
PodcastUrlLoaderReply* reply = loader_->Load(ui_->url->text());
|
||||
ui_->url->setText(reply->url().toString());
|
||||
|
||||
NewClosure(reply, SIGNAL(Finished(bool)),
|
||||
this, SLOT(RequestFinished(PodcastUrlLoaderReply*)),
|
||||
reply);
|
||||
}
|
||||
|
||||
void AddPodcastByUrl::RequestFinished(PodcastUrlLoaderReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
emit Busy(false);
|
||||
ui_->go->setEnabled(true);
|
||||
ui_->url->setEnabled(true);
|
||||
|
||||
if (!reply->is_success()) {
|
||||
QMessageBox::warning(this, tr("Failed to load podcast"),
|
||||
reply->error_text(), QMessageBox::Close);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (const Podcast& podcast, reply->results()) {
|
||||
model()->appendRow(model()->CreatePodcastItem(podcast));
|
||||
}
|
||||
}
|
44
src/podcasts/addpodcastbyurl.h
Normal file
44
src/podcasts/addpodcastbyurl.h
Normal file
@ -0,0 +1,44 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADDPODCASTBYURL_H
|
||||
#define ADDPODCASTBYURL_H
|
||||
|
||||
#include "addpodcastpage.h"
|
||||
|
||||
class AddPodcastPage;
|
||||
class PodcastUrlLoader;
|
||||
class PodcastUrlLoaderReply;
|
||||
class Ui_AddPodcastByUrl;
|
||||
|
||||
class AddPodcastByUrl : public AddPodcastPage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AddPodcastByUrl(Application* app, QWidget* parent = 0);
|
||||
~AddPodcastByUrl();
|
||||
|
||||
private slots:
|
||||
void GoClicked();
|
||||
void RequestFinished(PodcastUrlLoaderReply* reply);
|
||||
|
||||
private:
|
||||
Ui_AddPodcastByUrl* ui_;
|
||||
PodcastUrlLoader* loader_;
|
||||
};
|
||||
|
||||
#endif // ADDPODCASTBYURL_H
|
68
src/podcasts/addpodcastbyurl.ui
Normal file
68
src/podcasts/addpodcastbyurl.ui
Normal file
@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AddPodcastByUrl</class>
|
||||
<widget class="QWidget" name="AddPodcastByUrl">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>431</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Enter a URL</string>
|
||||
</property>
|
||||
<property name="windowIcon">
|
||||
<iconset resource="../../data/data.qrc">
|
||||
<normaloff>:/providers/podcast32.png</normaloff>:/providers/podcast32.png</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>If you know the URL of a podcast, enter it below and press Go.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="url"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="go">
|
||||
<property name="text">
|
||||
<string>Go</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>url</sender>
|
||||
<signal>returnPressed()</signal>
|
||||
<receiver>go</receiver>
|
||||
<slot>click()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>109</x>
|
||||
<y>24</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>429</x>
|
||||
<y>49</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
102
src/podcasts/addpodcastdialog.cpp
Normal file
102
src/podcasts/addpodcastdialog.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
#include "addpodcastbyurl.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "ui_addpodcastdialog.h"
|
||||
#include "core/application.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QPushButton>
|
||||
|
||||
AddPodcastDialog::AddPodcastDialog(Application* app, QWidget* parent)
|
||||
: QDialog(parent),
|
||||
ui_(new Ui_AddPodcastDialog)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
ui_->details->SetApplication(app);
|
||||
ui_->results_stack->setCurrentWidget(ui_->results_page);
|
||||
|
||||
connect(ui_->provider_list, SIGNAL(currentRowChanged(int)), SLOT(ChangePage(int)));
|
||||
|
||||
// Create an Add Podcast button
|
||||
add_button_ = new QPushButton(IconLoader::Load("list-add"), tr("Add Podcast"), this);
|
||||
add_button_->setEnabled(false);
|
||||
connect(add_button_, SIGNAL(clicked()), SLOT(AddPodcast()));
|
||||
ui_->button_box->addButton(add_button_, QDialogButtonBox::AcceptRole);
|
||||
|
||||
// Add providers
|
||||
AddPage(new AddPodcastByUrl(app, this));
|
||||
|
||||
ui_->provider_list->setCurrentRow(0);
|
||||
}
|
||||
|
||||
AddPodcastDialog::~AddPodcastDialog() {
|
||||
delete ui_;
|
||||
}
|
||||
|
||||
void AddPodcastDialog::AddPage(AddPodcastPage* page) {
|
||||
pages_.append(page);
|
||||
page_is_busy_.append(false);
|
||||
|
||||
ui_->stack->addWidget(page);
|
||||
new QListWidgetItem(page->windowIcon(), page->windowTitle(), ui_->provider_list);
|
||||
|
||||
connect(page, SIGNAL(Busy(bool)), SLOT(PageBusyChanged(bool)));
|
||||
}
|
||||
|
||||
void AddPodcastDialog::ChangePage(int index) {
|
||||
AddPodcastPage* page = pages_[index];
|
||||
|
||||
ui_->stack->setCurrentIndex(index);
|
||||
ui_->results->setModel(page->model());
|
||||
ui_->results->setRootIsDecorated(page->model()->is_tree());
|
||||
|
||||
ui_->results_stack->setCurrentWidget(
|
||||
page_is_busy_[index] ? ui_->busy_page : ui_->results_page);
|
||||
|
||||
connect(ui_->results->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)),
|
||||
SLOT(ChangePodcast(QModelIndex)));
|
||||
ChangePodcast(QModelIndex());
|
||||
PageBusyChanged(page_is_busy_[index]);
|
||||
}
|
||||
|
||||
void AddPodcastDialog::ChangePodcast(const QModelIndex& current) {
|
||||
if (!current.isValid()) {
|
||||
ui_->details->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
ui_->details->show();
|
||||
ui_->details->SetPodcast(current.data(PodcastDiscoveryModel::Role_Podcast).value<Podcast>());
|
||||
}
|
||||
|
||||
void AddPodcastDialog::PageBusyChanged(bool busy) {
|
||||
const int index = pages_.indexOf(qobject_cast<AddPodcastPage*>(sender()));
|
||||
if (index == -1)
|
||||
return;
|
||||
|
||||
page_is_busy_[index] = busy;
|
||||
|
||||
if (index == ui_->provider_list->currentRow()) {
|
||||
ui_->results_stack->setCurrentWidget(busy ? ui_->busy_page : ui_->results_page);
|
||||
}
|
||||
}
|
||||
|
||||
void AddPodcastDialog::AddPodcast() {
|
||||
}
|
54
src/podcasts/addpodcastdialog.h
Normal file
54
src/podcasts/addpodcastdialog.h
Normal file
@ -0,0 +1,54 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADDPODCASTDIALOG_H
|
||||
#define ADDPODCASTDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class AddPodcastPage;
|
||||
class Application;
|
||||
class Ui_AddPodcastDialog;
|
||||
|
||||
class QModelIndex;
|
||||
|
||||
class AddPodcastDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AddPodcastDialog(Application* app, QWidget* parent = 0);
|
||||
~AddPodcastDialog();
|
||||
|
||||
private slots:
|
||||
void AddPodcast();
|
||||
void ChangePage(int index);
|
||||
void ChangePodcast(const QModelIndex& current);
|
||||
|
||||
void PageBusyChanged(bool busy);
|
||||
|
||||
private:
|
||||
void AddPage(AddPodcastPage* page);
|
||||
|
||||
private:
|
||||
Ui_AddPodcastDialog* ui_;
|
||||
QPushButton* add_button_;
|
||||
|
||||
QList<AddPodcastPage*> pages_;
|
||||
QList<bool> page_is_busy_;
|
||||
};
|
||||
|
||||
#endif // ADDPODCASTDIALOG_H
|
179
src/podcasts/addpodcastdialog.ui
Normal file
179
src/podcasts/addpodcastdialog.ui
Normal file
@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AddPodcastDialog</class>
|
||||
<widget class="QDialog" name="AddPodcastDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>887</width>
|
||||
<height>447</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListWidget" name="provider_list">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>32</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="uniformItemSizes">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="stack">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QStackedWidget" name="results_stack">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="results_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTreeView" name="results">
|
||||
<attribute name="headerVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="busy_page">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="BusyIndicator" name="widget" native="true">
|
||||
<property name="text" stdset="0">
|
||||
<string>Loading...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>367</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="PodcastInfoWidget" name="details">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>BusyIndicator</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/busyindicator.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>PodcastInfoWidget</class>
|
||||
<extends>QFrame</extends>
|
||||
<header>podcasts/podcastinfowidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>AddPodcastDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>827</x>
|
||||
<y>436</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>AddPodcastDialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>876</x>
|
||||
<y>436</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
25
src/podcasts/addpodcastpage.cpp
Normal file
25
src/podcasts/addpodcastpage.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "addpodcastpage.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
|
||||
AddPodcastPage::AddPodcastPage(Application* app, QWidget* parent)
|
||||
: QWidget(parent),
|
||||
model_(new PodcastDiscoveryModel(app, this))
|
||||
{
|
||||
}
|
41
src/podcasts/addpodcastpage.h
Normal file
41
src/podcasts/addpodcastpage.h
Normal file
@ -0,0 +1,41 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ADDPODCASTPAGE_H
|
||||
#define ADDPODCASTPAGE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class Application;
|
||||
class PodcastDiscoveryModel;
|
||||
|
||||
class AddPodcastPage : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AddPodcastPage(Application* app, QWidget* parent = 0);
|
||||
|
||||
PodcastDiscoveryModel* model() const { return model_; }
|
||||
|
||||
signals:
|
||||
void Busy(bool busy);
|
||||
|
||||
private:
|
||||
PodcastDiscoveryModel* model_;
|
||||
};
|
||||
|
||||
#endif // ADDPODCASTPAGE_H
|
67
src/podcasts/podcastdiscoverymodel.cpp
Normal file
67
src/podcasts/podcastdiscoverymodel.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcast.h"
|
||||
#include "podcastdiscoverymodel.h"
|
||||
#include "core/application.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
|
||||
#include <QIcon>
|
||||
#include <QSet>
|
||||
|
||||
PodcastDiscoveryModel::PodcastDiscoveryModel(Application* app, QObject* parent)
|
||||
: QStandardItemModel(parent),
|
||||
app_(app),
|
||||
is_tree_(false),
|
||||
default_icon_(":providers/podcast32.png")
|
||||
{
|
||||
cover_options_.desired_height_ = 32;
|
||||
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64,QImage)),
|
||||
SLOT(ImageLoaded(quint64,QImage)));
|
||||
connect(this, SIGNAL(modelAboutToBeReset()), SLOT(CancelPendingImages()));
|
||||
}
|
||||
|
||||
QStandardItem* PodcastDiscoveryModel::CreatePodcastItem(const Podcast& podcast) {
|
||||
QStandardItem* item = new QStandardItem;
|
||||
item->setIcon(default_icon_);
|
||||
item->setText(podcast.title());
|
||||
item->setData(QVariant::fromValue(podcast), Role_Podcast);
|
||||
item->setData(Type_Podcast, Role_Type);
|
||||
|
||||
if (podcast.image_url().isValid()) {
|
||||
// Start loading an image for this item.
|
||||
quint64 id = app_->album_cover_loader()->LoadImageAsync(
|
||||
cover_options_, podcast.image_url().toString(), QString());
|
||||
pending_covers_[id] = item;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void PodcastDiscoveryModel::ImageLoaded(quint64 id, const QImage& image) {
|
||||
QStandardItem* item = pending_covers_.take(id);
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
item->setIcon(QIcon(QPixmap::fromImage(image)));
|
||||
}
|
||||
|
||||
void PodcastDiscoveryModel::CancelPendingImages() {
|
||||
app_->album_cover_loader()->CancelTasks(QSet<quint64>::fromList(pending_covers_.keys()));
|
||||
pending_covers_.clear();
|
||||
}
|
63
src/podcasts/podcastdiscoverymodel.h
Normal file
63
src/podcasts/podcastdiscoverymodel.h
Normal file
@ -0,0 +1,63 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PODCASTDISCOVERYMODEL_H
|
||||
#define PODCASTDISCOVERYMODEL_H
|
||||
|
||||
#include "covers/albumcoverloaderoptions.h"
|
||||
|
||||
#include <QStandardItemModel>
|
||||
|
||||
class Application;
|
||||
class Podcast;
|
||||
|
||||
class PodcastDiscoveryModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PodcastDiscoveryModel(Application* app, QObject* parent = 0);
|
||||
|
||||
enum Type {
|
||||
Type_Folder,
|
||||
Type_Podcast
|
||||
};
|
||||
|
||||
enum Role {
|
||||
Role_Podcast = Qt::UserRole,
|
||||
Role_Type
|
||||
};
|
||||
|
||||
bool is_tree() const { return is_tree_; }
|
||||
void set_is_tree(bool v) { is_tree_ = v; }
|
||||
|
||||
QStandardItem* CreatePodcastItem(const Podcast& podcast);
|
||||
|
||||
private slots:
|
||||
void CancelPendingImages();
|
||||
void ImageLoaded(quint64 id, const QImage& image);
|
||||
|
||||
private:
|
||||
Application* app_;
|
||||
|
||||
bool is_tree_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_options_;
|
||||
QIcon default_icon_;
|
||||
QMap<quint64, QStandardItem*> pending_covers_;
|
||||
};
|
||||
|
||||
#endif // PODCASTDISCOVERYMODEL_H
|
106
src/podcasts/podcastinfowidget.cpp
Normal file
106
src/podcasts/podcastinfowidget.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcastinfowidget.h"
|
||||
#include "ui_podcastinfowidget.h"
|
||||
#include "core/application.h"
|
||||
#include "covers/albumcoverloader.h"
|
||||
|
||||
PodcastInfoWidget::PodcastInfoWidget(QWidget* parent)
|
||||
: QFrame(parent),
|
||||
ui_(new Ui_PodcastInfoWidget),
|
||||
app_(NULL),
|
||||
image_id_(0)
|
||||
{
|
||||
ui_->setupUi(this);
|
||||
|
||||
setFrameShape(QFrame::StyledPanel);
|
||||
|
||||
setMaximumWidth(220);
|
||||
cover_options_.desired_height_ = 200;
|
||||
ui_->image->setFixedSize(cover_options_.desired_height_,
|
||||
cover_options_.desired_height_);
|
||||
|
||||
// Set the colour of all the labels
|
||||
const bool light = palette().color(QPalette::Base).value() > 128;
|
||||
const QColor color = palette().color(QPalette::Dark);
|
||||
QPalette label_palette(palette());
|
||||
label_palette.setColor(QPalette::WindowText, light ? color.darker(150) : color.lighter(125));
|
||||
|
||||
foreach (QLabel* label, findChildren<QLabel*>()) {
|
||||
if (label->property("field_label").toBool()) {
|
||||
label->setPalette(label_palette);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PodcastInfoWidget::~PodcastInfoWidget() {
|
||||
}
|
||||
|
||||
void PodcastInfoWidget::SetApplication(Application* app) {
|
||||
app_ = app;
|
||||
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64,QImage)),
|
||||
SLOT(ImageLoaded(quint64,QImage)));
|
||||
}
|
||||
|
||||
void PodcastInfoWidget::SetPodcast(const Podcast& podcast) {
|
||||
if (image_id_ != 0) {
|
||||
app_->album_cover_loader()->CancelTask(image_id_);
|
||||
}
|
||||
|
||||
podcast_ = podcast;
|
||||
|
||||
if (podcast.image_url().isValid()) {
|
||||
// Start loading an image for this item.
|
||||
image_id_ = app_->album_cover_loader()->LoadImageAsync(
|
||||
cover_options_, podcast.image_url().toString(), QString());
|
||||
}
|
||||
|
||||
ui_->image->hide();
|
||||
|
||||
SetText(podcast.title(), ui_->title);
|
||||
SetText(podcast.description(), ui_->description);
|
||||
SetText(podcast.copyright(), ui_->copyright, ui_->copyright_label);
|
||||
SetText(podcast.author(), ui_->author, ui_->author_label);
|
||||
SetText(podcast.owner_name(), ui_->owner, ui_->owner_label);
|
||||
SetText(podcast.link().toString(), ui_->website, ui_->website_label);
|
||||
}
|
||||
|
||||
void PodcastInfoWidget::SetText(const QString& value, QLabel* label, QLabel* buddy_label) {
|
||||
const bool visible = !value.isEmpty();
|
||||
|
||||
label->setVisible(visible);
|
||||
if (buddy_label) {
|
||||
buddy_label->setVisible(visible);
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
label->setText(value);
|
||||
}
|
||||
}
|
||||
|
||||
void PodcastInfoWidget::ImageLoaded(quint64 id, const QImage& image) {
|
||||
if (id != image_id_) {
|
||||
return;
|
||||
}
|
||||
image_id_ = 0;
|
||||
|
||||
if (!image.isNull()) {
|
||||
ui_->image->setPixmap(QPixmap::fromImage(image));
|
||||
ui_->image->show();
|
||||
}
|
||||
}
|
58
src/podcasts/podcastinfowidget.h
Normal file
58
src/podcasts/podcastinfowidget.h
Normal file
@ -0,0 +1,58 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PODCASTINFOWIDGET_H
|
||||
#define PODCASTINFOWIDGET_H
|
||||
|
||||
#include "podcast.h"
|
||||
#include "covers/albumcoverloaderoptions.h"
|
||||
|
||||
#include <QFrame>
|
||||
|
||||
class Application;
|
||||
class Ui_PodcastInfoWidget;
|
||||
|
||||
class QLabel;
|
||||
|
||||
class PodcastInfoWidget : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PodcastInfoWidget(QWidget* parent = 0);
|
||||
~PodcastInfoWidget();
|
||||
|
||||
void SetApplication(Application* app);
|
||||
|
||||
void SetPodcast(const Podcast& podcast);
|
||||
|
||||
private slots:
|
||||
void ImageLoaded(quint64 id, const QImage& image);
|
||||
|
||||
private:
|
||||
void SetText(const QString& value, QLabel* label, QLabel* buddy_label = NULL);
|
||||
|
||||
private:
|
||||
Ui_PodcastInfoWidget* ui_;
|
||||
|
||||
AlbumCoverLoaderOptions cover_options_;
|
||||
|
||||
Application* app_;
|
||||
Podcast podcast_;
|
||||
quint64 image_id_;
|
||||
};
|
||||
|
||||
#endif // PODCASTINFOWIDGET_H
|
163
src/podcasts/podcastinfowidget.ui
Normal file
163
src/podcasts/podcastinfowidget.ui
Normal file
@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PodcastInfoWidget</class>
|
||||
<widget class="QWidget" name="PodcastInfoWidget">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>398</width>
|
||||
<height>551</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">#title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#description {
|
||||
font-size: smaller;
|
||||
}</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="image">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="title">
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="description">
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="copyright_label">
|
||||
<property name="text">
|
||||
<string>Copyright</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="owner_label">
|
||||
<property name="text">
|
||||
<string>Owner</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="copyright">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="author_label">
|
||||
<property name="text">
|
||||
<string>Author</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="author">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="owner">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="website_label">
|
||||
<property name="text">
|
||||
<string>Website</string>
|
||||
</property>
|
||||
<property name="field_label" stdset="0">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="website">
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -25,20 +25,31 @@ const char* PodcastParser::kAtomNamespace = "http://www.w3.org/2005/Atom";
|
||||
const char* PodcastParser::kItunesNamespace = "http://www.itunes.com/dtds/podcast-1.0.dtd";
|
||||
|
||||
PodcastParser::PodcastParser() {
|
||||
supported_mime_types_ << "application/rss+xml"
|
||||
<< "application/xml"
|
||||
<< "text/xml";
|
||||
}
|
||||
|
||||
Podcast PodcastParser::Load(QIODevice* device, const QUrl& url) const {
|
||||
Podcast ret;
|
||||
ret.set_url(url);
|
||||
bool PodcastParser::SupportsContentType(const QString& content_type) const {
|
||||
foreach (const QString& mime_type, supported_mime_types()) {
|
||||
if (content_type.contains(mime_type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PodcastParser::Load(QIODevice* device, const QUrl& url, Podcast* ret) const {
|
||||
ret->set_url(url);
|
||||
|
||||
QXmlStreamReader reader(device);
|
||||
if (!Utilities::ParseUntilElement(&reader, "rss") ||
|
||||
!Utilities::ParseUntilElement(&reader, "channel")) {
|
||||
return ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseChannel(&reader, &ret);
|
||||
return ret;
|
||||
ParseChannel(&reader, ret);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PodcastParser::ParseChannel(QXmlStreamReader* reader, Podcast* ret) const {
|
||||
|
@ -18,6 +18,8 @@
|
||||
#ifndef PODCASTPARSER_H
|
||||
#define PODCASTPARSER_H
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
#include "podcast.h"
|
||||
|
||||
class QXmlStreamReader;
|
||||
@ -30,13 +32,19 @@ public:
|
||||
static const char* kAtomNamespace;
|
||||
static const char* kItunesNamespace;
|
||||
|
||||
Podcast Load(QIODevice* device, const QUrl& url) const;
|
||||
const QStringList& supported_mime_types() const { return supported_mime_types_; }
|
||||
bool SupportsContentType(const QString& content_type) const;
|
||||
|
||||
bool Load(QIODevice* device, const QUrl& url, Podcast* ret) const;
|
||||
|
||||
private:
|
||||
void ParseChannel(QXmlStreamReader* reader, Podcast* ret) const;
|
||||
void ParseImage(QXmlStreamReader* reader, Podcast* ret) const;
|
||||
void ParseItunesOwner(QXmlStreamReader* reader, Podcast* ret) const;
|
||||
void ParseItem(QXmlStreamReader* reader, Podcast* ret) const;
|
||||
|
||||
private:
|
||||
QStringList supported_mime_types_;
|
||||
};
|
||||
|
||||
#endif // PODCASTPARSER_H
|
||||
|
67
src/podcasts/podcastservice.cpp
Normal file
67
src/podcasts/podcastservice.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "addpodcastdialog.h"
|
||||
#include "podcastservice.h"
|
||||
#include "internet/internetmodel.h"
|
||||
#include "ui/iconloader.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
const char* PodcastService::kServiceName = "Podcasts";
|
||||
const char* PodcastService::kSettingsGroup = "Podcasts";
|
||||
|
||||
PodcastService::PodcastService(Application* app, InternetModel* parent)
|
||||
: InternetService(kServiceName, app, parent, parent),
|
||||
context_menu_(NULL),
|
||||
root_(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
PodcastService::~PodcastService() {
|
||||
}
|
||||
|
||||
QStandardItem* PodcastService::CreateRootItem() {
|
||||
root_ = new QStandardItem(QIcon(":providers/podcast16.png"), tr("Podcasts"));
|
||||
return root_;
|
||||
}
|
||||
|
||||
void PodcastService::LazyPopulate(QStandardItem* parent) {
|
||||
}
|
||||
|
||||
void PodcastService::ShowContextMenu(const QModelIndex& index,
|
||||
const QPoint& global_pos) {
|
||||
if (!context_menu_) {
|
||||
context_menu_ = new QMenu;
|
||||
context_menu_->addAction(IconLoader::Load("list-add"), tr("Add podcast..."),
|
||||
this, SLOT(AddPodcast()));
|
||||
}
|
||||
|
||||
context_menu_->popup(global_pos);
|
||||
}
|
||||
|
||||
QModelIndex PodcastService::GetCurrentIndex() {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
void PodcastService::AddPodcast() {
|
||||
if (!add_podcast_dialog_) {
|
||||
add_podcast_dialog_.reset(new AddPodcastDialog(app_));
|
||||
}
|
||||
|
||||
add_podcast_dialog_->show();
|
||||
}
|
56
src/podcasts/podcastservice.h
Normal file
56
src/podcasts/podcastservice.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PODCASTSERVICE_H
|
||||
#define PODCASTSERVICE_H
|
||||
|
||||
#include "internet/internetservice.h"
|
||||
|
||||
#include <QScopedPointer>
|
||||
|
||||
class AddPodcastDialog;
|
||||
|
||||
|
||||
class PodcastService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PodcastService(Application* app, InternetModel* parent);
|
||||
~PodcastService();
|
||||
|
||||
static const char* kServiceName;
|
||||
static const char* kSettingsGroup;
|
||||
|
||||
QStandardItem* CreateRootItem();
|
||||
void LazyPopulate(QStandardItem* parent);
|
||||
|
||||
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
|
||||
|
||||
protected:
|
||||
QModelIndex GetCurrentIndex();
|
||||
|
||||
private slots:
|
||||
void AddPodcast();
|
||||
|
||||
private:
|
||||
QMenu* context_menu_;
|
||||
QStandardItem* root_;
|
||||
|
||||
QScopedPointer<AddPodcastDialog> add_podcast_dialog_;
|
||||
};
|
||||
|
||||
#endif // PODCASTSERVICE_H
|
204
src/podcasts/podcasturlloader.cpp
Normal file
204
src/podcasts/podcasturlloader.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "podcastparser.h"
|
||||
#include "podcasturlloader.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/network.h"
|
||||
#include "core/utilities.h"
|
||||
|
||||
#include <QNetworkReply>
|
||||
|
||||
const int PodcastUrlLoader::kMaxRedirects = 5;
|
||||
|
||||
|
||||
PodcastUrlLoader::PodcastUrlLoader(QObject* parent)
|
||||
: QObject(parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
parser_(new PodcastParser),
|
||||
html_link_re_("<link (.*)>"),
|
||||
html_link_rel_re_("rel\\s*=\\s*['\"]?\\s*alternate"),
|
||||
html_link_type_re_("type\\s*=\\s*['\"]?([^'\" ]+)"),
|
||||
html_link_href_re_("href\\s*=\\s*['\"]?([^'\" ]+)")
|
||||
{
|
||||
html_link_re_.setMinimal(true);
|
||||
html_link_re_.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
// Thanks gpodder!
|
||||
quick_prefixes_ << QuickPrefix("fb:", "http://feeds.feedburner.com/%1")
|
||||
<< QuickPrefix("yt:", "http://www.youtube.com/rss/user/%1/videos.rss")
|
||||
<< QuickPrefix("sc:", "http://soundcloud.com/%1")
|
||||
<< QuickPrefix("fm4od:", "http://onapp1.orf.at/webcam/fm4/fod/%1.xspf")
|
||||
<< QuickPrefix("ytpl:", "http://gdata.youtube.com/feeds/api/playlists/%1");
|
||||
}
|
||||
|
||||
PodcastUrlLoader::~PodcastUrlLoader() {
|
||||
delete parser_;
|
||||
}
|
||||
|
||||
QUrl PodcastUrlLoader::FixPodcastUrl(const QString& url_text) const {
|
||||
QString url_text_copy(url_text);
|
||||
|
||||
// Check if it matches one of the quick prefixes.
|
||||
for (QuickPrefixList::const_iterator it = quick_prefixes_.constBegin() ;
|
||||
it != quick_prefixes_.constEnd() ; ++it) {
|
||||
if (url_text_copy.startsWith(it->first)) {
|
||||
url_text_copy = it->second.arg(url_text_copy.mid(it->first.length()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!url_text_copy.contains("://")) {
|
||||
url_text_copy.prepend("http://");
|
||||
}
|
||||
|
||||
// Replace schemes
|
||||
QUrl url(url_text_copy);
|
||||
if (url.scheme().isEmpty() || url.scheme() == "feed" ||
|
||||
url.scheme() == "itpc" || url.scheme() == "itms") {
|
||||
url.setScheme("http");
|
||||
} else if (url.scheme() == "zune" && url.host() == "subscribe" &&
|
||||
!url.queryItems().isEmpty()) {
|
||||
url = QUrl(url.queryItems()[0].second);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
PodcastUrlLoaderReply* PodcastUrlLoader::Load(const QString& url_text) {
|
||||
QUrl url(FixPodcastUrl(url_text));
|
||||
|
||||
// Create a reply
|
||||
PodcastUrlLoaderReply* reply = new PodcastUrlLoaderReply(url, this);
|
||||
|
||||
// Create a state object to track this request
|
||||
RequestState* state = new RequestState;
|
||||
state->redirects_remaining_ = kMaxRedirects + 1;
|
||||
state->reply_ = reply;
|
||||
|
||||
// Start the first request
|
||||
NextRequest(url, state);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
void PodcastUrlLoader::SendErrorAndDelete(const QString& error_text, RequestState* state) {
|
||||
state->reply_->SetFinished(error_text);
|
||||
delete state;
|
||||
}
|
||||
|
||||
void PodcastUrlLoader::NextRequest(const QUrl& url, RequestState* state) {
|
||||
// Stop the request if there have been too many redirects already.
|
||||
if (state->redirects_remaining_-- == 0) {
|
||||
SendErrorAndDelete(tr("Too many redirects"), state);
|
||||
return;
|
||||
}
|
||||
|
||||
qLog(Debug) << "Loading URL" << url;
|
||||
|
||||
QNetworkRequest req(url);
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
QNetworkReply* network_reply = network_->get(req);
|
||||
|
||||
NewClosure(network_reply, SIGNAL(finished()),
|
||||
this, SLOT(RequestFinished(RequestState*, QNetworkReply*)),
|
||||
state, network_reply);
|
||||
}
|
||||
|
||||
void PodcastUrlLoader::RequestFinished(RequestState* state, QNetworkReply* reply) {
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->attribute(QNetworkRequest::RedirectionTargetAttribute).isValid()) {
|
||||
const QUrl next_url = reply->url().resolved(
|
||||
reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl());
|
||||
|
||||
NextRequest(next_url, state);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for errors.
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
SendErrorAndDelete(reply->errorString(), state);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() != 200) {
|
||||
SendErrorAndDelete(QString("HTTP %1: %2").arg(
|
||||
reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toString(),
|
||||
reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString()), state);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the mime type.
|
||||
const QString content_type = reply->header(QNetworkRequest::ContentTypeHeader).toString();
|
||||
if (parser_->SupportsContentType(content_type)) {
|
||||
Podcast podcast;
|
||||
if (!parser_->Load(reply, reply->url(), &podcast)) {
|
||||
SendErrorAndDelete(tr("Failed to parse the XML for this RSS feed"), state);
|
||||
return;
|
||||
}
|
||||
|
||||
state->reply_->SetFinished(PodcastList() << podcast);
|
||||
delete state;
|
||||
return;
|
||||
} else if (content_type.contains("text/html")) {
|
||||
// I don't want a full HTML parser here, so do this the dirty way.
|
||||
const QString page_text = QString::fromUtf8(reply->readAll());
|
||||
int pos = 0;
|
||||
while ((pos = html_link_re_.indexIn(page_text, pos)) != -1) {
|
||||
const QString link = html_link_re_.cap(1).toLower();
|
||||
pos += html_link_re_.matchedLength();
|
||||
|
||||
if (html_link_rel_re_.indexIn(link) == -1 ||
|
||||
html_link_type_re_.indexIn(link) == -1 ||
|
||||
html_link_href_re_.indexIn(link) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString link_type = html_link_type_re_.cap(1);
|
||||
const QString href = Utilities::DecodeHtmlEntities(html_link_href_re_.cap(1));
|
||||
|
||||
if (parser_->supported_mime_types().contains(link_type)) {
|
||||
NextRequest(QUrl(href), state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SendErrorAndDelete(tr("HTML page did not contain any RSS feeds"), state);
|
||||
} else {
|
||||
SendErrorAndDelete(tr("Unknown content-type") + ": " + content_type, state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PodcastUrlLoaderReply::PodcastUrlLoaderReply(const QUrl& url, QObject* parent)
|
||||
: QObject(parent),
|
||||
url_(url),
|
||||
finished_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void PodcastUrlLoaderReply::SetFinished(const PodcastList& results) {
|
||||
results_ = results;
|
||||
finished_ = true;
|
||||
emit Finished(true);
|
||||
}
|
||||
|
||||
void PodcastUrlLoaderReply::SetFinished(const QString& error_text) {
|
||||
error_text_ = error_text;
|
||||
finished_ = true;
|
||||
emit Finished(false);
|
||||
}
|
98
src/podcasts/podcasturlloader.h
Normal file
98
src/podcasts/podcasturlloader.h
Normal file
@ -0,0 +1,98 @@
|
||||
/* This file is part of Clementine.
|
||||
Copyright 2012, David Sansome <me@davidsansome.com>
|
||||
|
||||
Clementine is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Clementine is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef PODCASTURLLOADER_H
|
||||
#define PODCASTURLLOADER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "podcast.h"
|
||||
|
||||
class PodcastParser;
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
class PodcastUrlLoaderReply : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PodcastUrlLoaderReply(const QUrl& url, QObject* parent);
|
||||
|
||||
const QUrl& url() const { return url_; }
|
||||
bool is_finished() const { return finished_; }
|
||||
bool is_success() const { return error_text_.isEmpty(); }
|
||||
const QString& error_text() const { return error_text_; }
|
||||
const PodcastList& results() const { return results_; }
|
||||
|
||||
void SetFinished(const QString& error_text);
|
||||
void SetFinished(const PodcastList& results);
|
||||
|
||||
signals:
|
||||
void Finished(bool success);
|
||||
|
||||
private:
|
||||
QUrl url_;
|
||||
bool finished_;
|
||||
QString error_text_;
|
||||
PodcastList results_;
|
||||
};
|
||||
|
||||
|
||||
class PodcastUrlLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PodcastUrlLoader(QObject* parent = 0);
|
||||
~PodcastUrlLoader();
|
||||
|
||||
static const int kMaxRedirects;
|
||||
|
||||
PodcastUrlLoaderReply* Load(const QString& url_text);
|
||||
|
||||
private:
|
||||
struct RequestState {
|
||||
int redirects_remaining_;
|
||||
PodcastUrlLoaderReply* reply_;
|
||||
};
|
||||
|
||||
typedef QPair<QString, QString> QuickPrefix;
|
||||
typedef QList<QuickPrefix> QuickPrefixList;
|
||||
|
||||
private slots:
|
||||
void RequestFinished(RequestState* state, QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
QUrl FixPodcastUrl(const QString& url_text) const;
|
||||
void SendErrorAndDelete(const QString& error_text, RequestState* state);
|
||||
void NextRequest(const QUrl& url, RequestState* state);
|
||||
|
||||
private:
|
||||
QNetworkAccessManager* network_;
|
||||
PodcastParser* parser_;
|
||||
|
||||
QuickPrefixList quick_prefixes_;
|
||||
|
||||
QRegExp html_link_re_;
|
||||
QRegExp whitespace_re_;
|
||||
QRegExp html_link_rel_re_;
|
||||
QRegExp html_link_type_re_;
|
||||
QRegExp html_link_href_re_;
|
||||
};
|
||||
|
||||
#endif // PODCASTURLLOADER_H
|
Loading…
x
Reference in New Issue
Block a user