Files
cantata/online/podcastsearchdialog.cpp
Craig Drummond b6bd94c236 Update (c) year
2022-01-08 21:24:07 +00:00

779 lines
24 KiB
C++

/*
* Cantata
*
* Copyright (c) 2011-2022 Craig Drummond <craig.p.drummond@gmail.com>
*
* ----
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "podcastsearchdialog.h"
#include "support/pagewidget.h"
#include "support/lineedit.h"
#include "network/networkaccessmanager.h"
#include "opmlparser.h"
#include "widgets/icons.h"
#include "support/spinner.h"
#include "widgets/basicitemdelegate.h"
#include "podcastservice.h"
#include "support/utils.h"
#include "support/action.h"
#include "support/monoicon.h"
#include "widgets/textbrowser.h"
#include "support/messagewidget.h"
#include "support/flattoolbutton.h"
#include "gui/covers.h"
#include "rssparser.h"
#include "podcastservice.h"
#include "config.h"
#include <QLabel>
#include <QPushButton>
#include <QTreeWidget>
#include <QGridLayout>
#include <QBoxLayout>
#include <QHeaderView>
#include <QStringList>
#include <QFile>
#include <QFileDialog>
#include <QXmlStreamReader>
#include <QCryptographicHash>
#include <QCache>
#include <QImage>
#include <QBuffer>
#include <QCoreApplication>
#include <QDesktopServices>
#include <QNetworkRequest>
#include <QUrlQuery>
#include <QJsonDocument>
static int iCount=0;
static QCache<QUrl, QImage> imageCache(200*1024);
static int maxImageSize=-1;
static const char * constOrigUrlProperty="orig-url";
enum Roles {
IsPodcastRole = Qt::UserRole,
UrlRole,
ImageUrlRole,
DescriptionRole,
WebPageUrlRole
};
QString PodcastSearchDialog::constCacheDir=QLatin1String("podcast-directories");
QString PodcastSearchDialog::constExt=QLatin1String(".opml");
static QString generateCacheFileName(const QUrl &url, bool create)
{
QString hash=QCryptographicHash::hash(url.toString().toUtf8(), QCryptographicHash::Md5).toHex();
QString dir=Utils::cacheDir(PodcastSearchDialog::constCacheDir, create);
return dir.isEmpty() ? QString() : (dir+hash+PodcastSearchDialog::constExt);
}
class ITunesSearchPage : public PodcastSearchPage
{
public:
ITunesSearchPage(QWidget *p)
: PodcastSearchPage(p,
QLatin1String("iTunes"),
FontAwesome::apple,
QUrl(QLatin1String("http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStoreServices.woa/wa/wsSearch")),
QLatin1String("term"),
QStringList() << QLatin1String("country") << QLatin1String("US") << QLatin1String("media") << QLatin1String("podcast"))
{
}
void parse(const QVariant &data) override
{
for (const QVariant &resultVariant: data.toMap()[QLatin1String("results")].toList()) {
QVariantMap result(resultVariant.toMap());
if (result[QLatin1String("kind")].toString() != QLatin1String("podcast")) {
continue;
}
addPodcast(result[QLatin1String("trackName")].toString(),
result[QLatin1String("feedUrl")].toUrl(),
result[QLatin1String("artworkUrl100")].toUrl(),
QString(),
result[QLatin1String("collectionViewUrl")].toString(),
nullptr);
}
}
};
class GPodderSearchPage : public PodcastSearchPage
{
public:
GPodderSearchPage(QWidget *p)
: PodcastSearchPage(p,
QLatin1String("GPodder"),
FontAwesome::podcast,
QUrl(QLatin1String("http://gpodder.net/search.json")),
QLatin1String("q"))
{
}
void parse(const QVariant &data) override
{
QVariantList list=data.toList();
for (const QVariant &var: list) {
QVariantMap map=var.toMap();
addPodcast(map[QLatin1String("title")].toString(),
map[QLatin1String("url")].toUrl(),
map[QLatin1String("logo_url")].toUrl(),
map[QLatin1String("description")].toString(),
map[QLatin1String("website")].toString(),
nullptr);
}
}
};
PodcastPage::PodcastPage(QWidget *p, const QString &n)
: QWidget(p)
, pageName(n)
, job(nullptr)
, imageJob(nullptr)
{
tree = new QTreeWidget(this);
tree->setItemDelegate(new BasicItemDelegate(tree));
tree->header()->setVisible(false);
text=new TextBrowser(this);
spinner=new Spinner(this);
spinner->setWidget(tree);
imageSpinner=new Spinner(this);
imageSpinner->setWidget(text);
connect(tree, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged()));
tree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
text->setOpenLinks(false);
connect(text, SIGNAL(anchorClicked(QUrl)), SLOT(openLink(QUrl)));
updateText();
}
QUrl PodcastPage::currentRss() const
{
QList<QTreeWidgetItem *> selection=tree->selectedItems();
return selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl();
}
void PodcastPage::fetch(const QUrl &url)
{
cancel();
tree->clear();
spinner->start();
job=NetworkAccessManager::self()->get(url);
connect(job, SIGNAL(finished()), this, SLOT(jobFinished()));
}
void PodcastPage::fetchImage(const QUrl &url)
{
cancelImage();
imageSpinner->start();
imageJob=NetworkAccessManager::self()->get(url, 5000);
imageJob->setProperty(constOrigUrlProperty, url);
connect(imageJob, SIGNAL(finished()), this, SLOT(imageJobFinished()));
}
void PodcastPage::cancel()
{
spinner->stop();
if (job) {
job->cancelAndDelete();
job=nullptr;
}
}
void PodcastPage::cancelImage()
{
imageSpinner->stop();
if (imageJob) {
imageJob->cancelAndDelete();
imageJob=nullptr;
}
}
void PodcastPage::addPodcast(const QString &title, const QUrl &url, const QUrl &image, const QString &description, const QString &webPage, QTreeWidgetItem *p)
{
if (title.isEmpty() || url.isEmpty()) {
return;
}
QTreeWidgetItem *podItem=p ? new QTreeWidgetItem(p, QStringList() << title)
: new QTreeWidgetItem(tree, QStringList() << title);
podItem->setData(0, IsPodcastRole, true);
podItem->setData(0, UrlRole, url);
podItem->setData(0, ImageUrlRole, image);
podItem->setData(0, DescriptionRole, description);
podItem->setData(0, WebPageUrlRole, webPage);
podItem->setIcon(0, Icons::self()->audioListIcon);
}
void PodcastPage::addCategory(const OpmlParser::Category &cat, QTreeWidgetItem *p)
{
if (cat.categories.isEmpty() && cat.podcasts.isEmpty()) {
return;
}
QTreeWidgetItem *catItem=p ? new QTreeWidgetItem(p, QStringList() << cat.name)
: new QTreeWidgetItem(tree, QStringList() << cat.name);
catItem->setData(0, IsPodcastRole, false);
catItem->setIcon(0, Icons::self()->folderListIcon);
for (const OpmlParser::Podcast &pod: cat.podcasts) {
addPodcast(pod, catItem);
}
for (const OpmlParser::Category &subCat: cat.categories) {
addCategory(subCat, catItem);
}
}
void PodcastPage::addPodcast(const OpmlParser::Podcast &pod, QTreeWidgetItem *p)
{
addPodcast(pod.name, pod.url, pod.image, pod.description, pod.htmlUrl, p);
}
static QString encode(const QImage &img)
{
QByteArray bytes;
QBuffer buffer(&bytes);
buffer.open(QIODevice::WriteOnly);
img.save(&buffer, "PNG");
return QString("<br/><img src=\"data:image/png;base64,%1\"><br/>").arg(QString(buffer.data().toBase64()));
}
void PodcastPage::updateText()
{
QList<QTreeWidgetItem *> selection=tree->selectedItems();
if (!selection.isEmpty()) {
QTreeWidgetItem *item=selection.at(0);
if (item->data(0, IsPodcastRole).toBool()) {
QUrl url=item->data(0, UrlRole).toUrl();
QUrl imageUrl=item->data(0, ImageUrlRole).toUrl();
QString descr=item->data(0, DescriptionRole).toString();
QString web=item->data(0, WebPageUrlRole).toString();
QString str="<b>"+item->text(0)+"</b><br/>";
if (!imageUrl.isEmpty()) {
QImage *img=imageCache.object(imageUrl);
if (img) {
str+=encode(*img);
} else {
fetchImage(imageUrl);
}
}
if (!descr.isEmpty()) {
str+="<p>"+descr+"</p><br/>";
}
str+="<table><tr><td><b>"+tr("RSS:")+"</b></td><td><a href=\""+url.toString()+"\">"+url.toString()+"</a></td></tr>";
if (!web.isEmpty()) {
str+="<tr><td><b>"+tr("Website:")+"</b></td><td><a href=\""+web+"\">"+web+"</a></td></tr>";
}
str+="</table>";
text->setHtml(str);
return;
}
}
text->setHtml("<b>"+tr("Podcast details")+"</b><p><i>"+tr("Select a podcast to display its details")+"</i></p>");
}
void PodcastPage::selectionChanged()
{
cancelImage();
updateText();
QList<QTreeWidgetItem *> selection=tree->selectedItems();
emit rssSelected(selection.isEmpty() ? QUrl() : selection.at(0)->data(0, UrlRole).toUrl());
}
void PodcastPage::imageJobFinished()
{
NetworkJob *j=qobject_cast<NetworkJob *>(sender());
if (!j) {
return;
}
j->deleteLater();
if (j!=imageJob) {
return;
}
if (imageSpinner) {
imageSpinner->stop();
}
QByteArray data=imageJob->readAll();
QImage img=QImage::fromData(data, Covers::imageFormat(data));
if (!img.isNull()) {
if (img.width()>maxImageSize || img.height()>maxImageSize) {
img=img.scaled(maxImageSize, maxImageSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
imageCache.insert(imageJob->property(constOrigUrlProperty).toUrl(), new QImage(img), img.sizeInBytes());
updateText();
}
imageJob=nullptr;
}
void PodcastPage::jobFinished()
{
NetworkJob *j=qobject_cast<NetworkJob *>(sender());
if (!j) {
return;
}
j->deleteLater();
if (j!=job) {
return;
}
if (spinner) {
spinner->stop();
}
parseResonse(j->ok() ? j->actualJob() : nullptr);
job=nullptr;
}
void PodcastPage::openLink(const QUrl &url)
{
QDesktopServices::openUrl(url);
}
PodcastSearchPage::PodcastSearchPage(QWidget *p, const QString &n, int i, const QUrl &qu, const QString &qk, const QStringList &other)
: PodcastPage(p, n)
, queryUrl(qu)
, queryKey(qk)
, otherArgs(other)
{
QBoxLayout *searchLayout=new QBoxLayout(QBoxLayout::LeftToRight);
QBoxLayout *viewLayout=new QBoxLayout(QBoxLayout::LeftToRight);
QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, this);
searchLayout->setMargin(0);
viewLayout->setMargin(0);
mainLayout->setMargin(0);
search=new LineEdit(p);
search->setPlaceholderText(tr("Enter search term..."));
searchButton=new QPushButton(tr("Search"), p);
QWidget::setTabOrder(search, searchButton);
QWidget::setTabOrder(searchButton, tree);
searchLayout->addWidget(search);
searchLayout->addWidget(searchButton);
viewLayout->addWidget(tree, 1);
viewLayout->addWidget(text, 0);
mainLayout->addLayout(searchLayout);
mainLayout->addLayout(viewLayout);
connect(search, SIGNAL(returnPressed()), SLOT(doSearch()));
connect(searchButton, SIGNAL(clicked()), SLOT(doSearch()));
icn=MonoIcon::icon((FontAwesome::icon)i, Utils::monoIconColor());
}
void PodcastSearchPage::showEvent(QShowEvent *e)
{
search->setFocus();
QWidget::showEvent(e);
}
void PodcastSearchPage::doSearch()
{
QString text=search->text().trimmed();
if (text.isEmpty() || text==currentSearch) {
return;
}
currentSearch=text;
QUrl url=queryUrl;
QUrlQuery query;
query.addQueryItem(queryKey, text);
if (otherArgs.size()>1) {
for (int i=0; i<otherArgs.size()-1; i+=2) {
query.addQueryItem(otherArgs.at(i), otherArgs.at(i+1));
}
}
url.setQuery(query);
fetch(url);
}
void PodcastSearchPage::parseResonse(QIODevice *dev)
{
if (!dev) {
emit error(tr("Failed to fetch podcasts from %1").arg(name()));
return;
}
QVariant data=QJsonDocument::fromJson(dev->readAll()).toVariant();
if (data.isNull()) {
emit error(tr("There was a problem parsing the response from %1").arg(name()));
return;
}
parse(data);
}
OpmlBrowsePage::OpmlBrowsePage(QWidget *p, const QString &n, const QIcon &i, const QUrl &u)
: PodcastPage(p, n)
, loaded(false)
, url(u)
{
QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::LeftToRight, this);
mainLayout->setMargin(0);
mainLayout->addWidget(tree, 1);
mainLayout->addWidget(text, 0);
Action *act=new Action(tr("Reload"), this);
tree->addAction(act);
connect(act, SIGNAL(triggered()), this, SLOT(reload()));
tree->setContextMenuPolicy(Qt::ActionsContextMenu);
icn=i;
}
void OpmlBrowsePage::showEvent(QShowEvent *e)
{
if (!loaded) {
QString cacheFile=generateCacheFileName(url, false);
if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) {
QFile f(cacheFile);
if (f.open(QIODevice::ReadOnly)) {
parseResonse(&f);
if (tree->topLevelItemCount()>0) {
Utils::touchFile(cacheFile);
loaded=true;
return;
}
}
}
fetch(url);
loaded=true;
}
QWidget::showEvent(e);
}
void OpmlBrowsePage::reload()
{
QString cacheFile=generateCacheFileName(url, false);
if (!cacheFile.isEmpty() && QFile::exists(cacheFile)) {
QFile::remove(cacheFile);
}
fetch(url);
}
void OpmlBrowsePage::parseResonse(QIODevice *dev)
{
bool isLoadingFromCache=dynamic_cast<QFile *>(dev) ? true : false;
if (!dev) {
if (!isLoadingFromCache) {
emit error(tr("Failed to download directory listing"));
}
return;
}
QByteArray data=dev->readAll();
OpmlParser::Category parsed=OpmlParser::parse(data);
if (parsed.categories.isEmpty() && parsed.podcasts.isEmpty()) {
if (!isLoadingFromCache) {
emit error(tr("Failed to parse directory listing"));
}
return;
}
if (1==parsed.categories.count() && parsed.podcasts.isEmpty()) {
parsed=parsed.categories.at(0);
}
for (const OpmlParser::Category &cat: parsed.categories) {
addCategory(cat, nullptr);
}
for (const OpmlParser::Podcast &pod: parsed.podcasts) {
addPodcast(pod, nullptr);
}
if (!isLoadingFromCache && tree->topLevelItemCount()>0) {
QString cacheFile=generateCacheFileName(url, true);
if (!cacheFile.isEmpty()) {
QFile f(cacheFile);
if (f.open(QIODevice::WriteOnly)) {
f.write(data);
}
}
}
}
PodcastUrlPage::PodcastUrlPage(QWidget *p)
: PodcastPage(p, tr("URL"))
{
QBoxLayout *searchLayout=new QBoxLayout(QBoxLayout::LeftToRight);
QBoxLayout *viewLayout=new QBoxLayout(QBoxLayout::LeftToRight);
QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, this);
searchLayout->setMargin(0);
viewLayout->setMargin(0);
mainLayout->setMargin(0);
urlEntry=new LineEdit(p);
urlEntry->setPlaceholderText(tr("Enter podcast URL..."));
QPushButton *loadButton=new QPushButton(tr("Load"), p);
FlatToolButton *pathReq=new FlatToolButton(p);
pathReq->setIcon(MonoIcon::icon(FontAwesome::foldero, Utils::monoIconColor()));
pathReq->setToolTip(tr("Load local podcast file"));
QWidget::setTabOrder(urlEntry, loadButton);
QWidget::setTabOrder(loadButton, tree);
searchLayout->addWidget(urlEntry);
searchLayout->addWidget(loadButton);
searchLayout->addWidget(pathReq);
viewLayout->addWidget(tree, 1);
viewLayout->addWidget(text, 0);
mainLayout->addWidget(new QLabel(tr("Enter podcast URL below, and press 'Load', or press the folder icon to load a local podcast file."), this));
mainLayout->addLayout(searchLayout);
mainLayout->addLayout(viewLayout);
connect(urlEntry, SIGNAL(returnPressed()), SLOT(loadUrl()));
connect(loadButton, SIGNAL(clicked()), SLOT(loadUrl()));
connect(pathReq, SIGNAL(clicked()), SLOT(openPath()));
icn=Icons::self()->rssListIcon;
}
void PodcastUrlPage::showEvent(QShowEvent *e)
{
urlEntry->setFocus();
QWidget::showEvent(e);
}
void PodcastUrlPage::loadUrl()
{
QString text=urlEntry->text().trimmed();
if (text.isEmpty()) {
return;
}
QUrl url(PodcastService::fixUrl(text));
if (url==currentUrl) {
return;
}
if (!PodcastService::isUrlOk(url)) {
emit error(tr("Invalid URL!"));
} else {
currentUrl=url;
fetch(url);
}
}
void PodcastUrlPage::openPath()
{
QString path = QFileDialog::getOpenFileName(this, QString(), QDir::homePath(), tr("Podcasts (*.xml, *.rss, *.opml"));
if (!path.isEmpty()) {
QFile f(path);
if (f.open(QIODevice::ReadOnly|QIODevice::Text)) {
parse(&f);
} else {
emit error(tr("Failed to read file!"));
}
}
}
void PodcastUrlPage::parseResonse(QIODevice *dev)
{
if (!dev) {
emit error(tr("Failed to fetch podcast!"));
return;
}
parse(dev);
}
void PodcastUrlPage::parse(QIODevice *dev)
{
auto data = dev->readAll();
QBuffer buf(&data);
buf.open(QIODevice::ReadOnly|QIODevice::Text);
RssParser::Channel ch=RssParser::parse(&buf, false, true);
if (ch.isValid()) {
if (ch.video) {
emit error(tr("Cantata only supports audio podcasts! The URL entered contains only video podcasts."));
} else {
addPodcast(ch.name, currentUrl, ch.image, ch.description, QString(), nullptr);
}
} else {
OpmlParser::Category parsed=OpmlParser::parse(data);
if (parsed.categories.isEmpty() && parsed.podcasts.isEmpty()) {
emit error(tr("Failed to parse podcast."));
} else {
if (1==parsed.categories.count() && parsed.podcasts.isEmpty()) {
parsed=parsed.categories.at(0);
}
for (const OpmlParser::Category &cat: parsed.categories) {
addCategory(cat, nullptr);
}
for (const OpmlParser::Podcast &pod: parsed.podcasts) {
addPodcast(pod, nullptr);
}
}
}
}
int PodcastSearchDialog::instanceCount()
{
return iCount;
}
PodcastSearchDialog::PodcastSearchDialog(PodcastService *s, QWidget *parent)
: Dialog(parent, "PodcastSearchDialog", QSize(800, 600))
, service(s)
{
Utils::clearOldCache(constCacheDir, 2);
iCount++;
setButtons(User1|Close);
setButtonText(User1, tr("Subscribe"));
QWidget *mainWidget = new QWidget(this);
messageWidget = new MessageWidget(mainWidget);
spacer = new QWidget(this);
spacer->setFixedSize(Utils::layoutSpacing(this), 0);
QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, mainWidget);
layout->setMargin(0);
layout->setSpacing(0);
pageWidget = new PageWidget(mainWidget);
QList<PodcastPage *> pages;
layout->addWidget(messageWidget);
layout->addWidget(spacer);
layout->addWidget(pageWidget);
PodcastUrlPage *urlPage=new PodcastUrlPage(pageWidget);
pageWidget->addPage(urlPage, tr("Enter URL"), urlPage->icon(), tr("Manual podcast URL"));
pages << urlPage;
ITunesSearchPage *itunes=new ITunesSearchPage(pageWidget);
pageWidget->addPage(itunes, tr("Search %1").arg(itunes->name()), itunes->icon(), tr("Search for podcasts on %1").arg(itunes->name()));
pages << itunes;
GPodderSearchPage *gpodder=new GPodderSearchPage(pageWidget);
pageWidget->addPage(gpodder, tr("Search %1").arg(gpodder->name()), gpodder->icon(), tr("Search for podcasts on %1").arg(gpodder->name()));
pages << gpodder;
QSet<QString> loaded;
pages << loadDirectories(Utils::dataDir(), false, loaded);
pages << loadDirectories(QString(), true, loaded);
for (PodcastPage *p: pages) {
connect(p, SIGNAL(rssSelected(QUrl)), SLOT(rssSelected(QUrl)));
connect(p, SIGNAL(error(QString)), SLOT(showError(QString)));
}
setCaption(tr("Add Podcast Subscription"));
setMainWidget(mainWidget);
setAttribute(Qt::WA_DeleteOnClose);
enableButton(User1, false);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
if (-1==maxImageSize) {
maxImageSize=fontMetrics().height()*8;
}
connect(service, SIGNAL(newError(QString)), this, SLOT(showError(QString)));
connect(messageWidget, SIGNAL(visible(bool)), SLOT(msgWidgetVisible(bool)));
connect(pageWidget, SIGNAL(currentPageChanged()), this, SLOT(pageChanged()));
messageWidget->hide();
}
PodcastSearchDialog::~PodcastSearchDialog()
{
iCount--;
imageCache.clear();
}
void PodcastSearchDialog::rssSelected(const QUrl &url)
{
currentUrl=url;
enableButton(User1, currentUrl.isValid());
}
void PodcastSearchDialog::showError(const QString &msg)
{
messageWidget->setError(msg);
}
void PodcastSearchDialog::showInfo(const QString &msg)
{
messageWidget->setInformation(msg);
}
void PodcastSearchDialog::msgWidgetVisible(bool v)
{
spacer->setFixedSize(spacer->width(), v ? spacer->width() : 0);
}
void PodcastSearchDialog::pageChanged()
{
PageWidgetItem *pwi=pageWidget->currentPage();
PodcastPage *page=pwi ? qobject_cast<PodcastPage *>(pwi->widget()) : nullptr;
rssSelected(page ? page->currentRss() : QUrl());
}
QList<PodcastPage *> PodcastSearchDialog::loadDirectories(const QString &dir, bool isSystem, QSet<QString> &loaded)
{
QList<PodcastPage *> pages;
if (dir.isEmpty() && !isSystem) {
return pages;
}
QFile file(isSystem ? QLatin1String(":podcast_directories.xml") : (dir+QLatin1String("/podcast_directories.xml")));
if (file.open(QIODevice::ReadOnly)) {
QXmlStreamReader reader(&file);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement() && QLatin1String("directory")==reader.name()) {
QString url=reader.attributes().value(QLatin1String("url")).toString();
if (!loaded.contains(url)) {
QString iconName=reader.attributes().value(QLatin1String("icon")).toString();
QIcon icon;
if (iconName.isEmpty()) {
icon=MonoIcon::icon(FontAwesome::rsssquare, Utils::monoIconColor());
} else if (iconName.startsWith(":")) {
icon= MonoIcon::icon(iconName, Utils::monoIconColor());
} else {
icon.addFile(iconName);
}
if (icon.isNull()) {
icon=MonoIcon::icon(FontAwesome::rsssquare, Utils::monoIconColor());
}
OpmlBrowsePage *page=new OpmlBrowsePage(pageWidget,
reader.attributes().value(QLatin1String("name")).toString(),
icon,
QUrl(url));
pageWidget->addPage(page, tr("Browse %1").arg(page->name()), page->icon(), tr("Browse %1 podcasts").arg(page->name()));
pages << page;
loaded.insert(url);
}
}
}
}
return pages;
}
void PodcastSearchDialog::slotButtonClicked(int button)
{
switch (button) {
case User1: {
QUrl fixed=PodcastService::fixUrl(currentUrl);
if (service->subscribedToUrl(fixed)) {
showError(tr("You are already subscribed to this podcast!"));
} else {
service->addUrl(fixed);
showInfo(tr("Subscription added"));
}
break;
}
case Close:
reject();
// Need to call this - if not, when dialog is closed by window X control, it is not deleted!!!!
Dialog::slotButtonClicked(button);
break;
default:
break;
}
}
#include "moc_podcastsearchdialog.cpp"