1 /*
2 This file is part of Akregator.
3
4 SPDX-FileCopyrightText: 2004 Teemu Rytilahti <tpr@d5k.net>
5
6 SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-Qt-exception
7 */
8
9 #include "konqfeedicon.h"
10
11 #include "pluginutil.h"
12 #include "akregatorplugindebug.h"
13
14 #include <kpluginfactory.h>
15 #include <KLocalizedString>
16 #include <kiconloader.h>
17 #include <kparts/part.h>
18 #include <kparts/statusbarextension.h>
19 #include <KParts/ReadOnlyPart>
20 #include <KParts/HtmlExtension>
21 #include <KParts/SelectorInterface>
22 #include <kio/job.h>
23 #include <kurllabel.h>
24 #include <kprotocolinfo.h>
25
26 #include <QApplication>
27 #include <qstatusbar.h>
28 #include <QStyle>
29
30 using namespace Akregator;
31
K_PLUGIN_FACTORY(KonqFeedIconFactory,registerPlugin<KonqFeedIcon> ();)32 K_PLUGIN_FACTORY(KonqFeedIconFactory, registerPlugin<KonqFeedIcon>();)
33
34 static QUrl baseUrl(KParts::ReadOnlyPart *part)
35 {
36 QUrl url;
37 KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(part);
38 if (ext) {
39 url = ext->baseUrl();
40 }
41 return url;
42 }
43
KonqFeedIcon(QObject * parent,const QVariantList & args)44 KonqFeedIcon::KonqFeedIcon(QObject *parent, const QVariantList &args)
45 : KParts::Plugin(parent),
46 m_part(nullptr),
47 m_feedIcon(nullptr),
48 m_statusBarEx(nullptr),
49 m_menu(nullptr)
50 {
51 Q_UNUSED(args);
52
53 // make our icon foundable by the KIconLoader
54 KIconLoader::global()->addAppDir(QStringLiteral("akregator"));
55
56 KParts::ReadOnlyPart *part = qobject_cast<KParts::ReadOnlyPart *>(parent);
57 if (part) {
58 KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(part);
59 KParts::SelectorInterface *selectorInterface = qobject_cast<KParts::SelectorInterface *>(ext);
60 if (selectorInterface) {
61 m_part = part;
62 connect(m_part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &KonqFeedIcon::addFeedIcon);
63 connect(m_part, QOverload<bool>::of(&KParts::ReadOnlyPart::completed), this, &KonqFeedIcon::addFeedIcon);
64 connect(m_part, &KParts::ReadOnlyPart::started, this, &KonqFeedIcon::removeFeedIcon);
65 }
66 }
67 }
68
~KonqFeedIcon()69 KonqFeedIcon::~KonqFeedIcon()
70 {
71 m_statusBarEx = KParts::StatusBarExtension::childObject(m_part);
72 if (m_statusBarEx) {
73 m_statusBarEx->removeStatusBarItem(m_feedIcon);
74 // if the statusbar extension is deleted, the icon is deleted as well (being the child of the status bar)
75 delete m_feedIcon;
76 }
77 // the icon is deleted in every case
78 m_feedIcon = nullptr;
79 delete m_menu;
80 m_menu = nullptr;
81 }
82
feedFound()83 bool KonqFeedIcon::feedFound()
84 {
85 // Ensure that it is safe to use the URL, before doing anything else with it
86 const QUrl partUrl(m_part->url());
87 if (!partUrl.isValid() || partUrl.scheme().isEmpty()) {
88 return false;
89 }
90 // Since attempting to determine feed info for about:blank crashes khtml,
91 // lets prevent such look up for local urls (about, file, man, etc...)
92 if (KProtocolInfo::protocolClass(partUrl.scheme()).compare(QLatin1String(":local"), Qt::CaseInsensitive) == 0) {
93 return false;
94 }
95
96 KParts::HtmlExtension *ext = KParts::HtmlExtension::childObject(m_part);
97 KParts::SelectorInterface *selectorInterface = qobject_cast<KParts::SelectorInterface *>(ext);
98 QString doc;
99 if (selectorInterface) {
100 QList<KParts::SelectorInterface::Element> linkNodes = selectorInterface->querySelectorAll(QStringLiteral("head > link[rel=\"alternate\"]"), KParts::SelectorInterface::EntireContent);
101 for (int i = 0; i < linkNodes.count(); i++) {
102 const KParts::SelectorInterface::Element element = linkNodes.at(i);
103
104 // TODO parse the attributes directly here, rather than re-assembling
105 // and then re-parsing in extractFromLinkTags!
106 doc += QLatin1String("<link ");
107 Q_FOREACH (const QString &attrName, element.attributeNames()) {
108 doc += attrName + "=\"";
109 doc += element.attribute(attrName).toHtmlEscaped().replace(QLatin1String("\""), QLatin1String("""));
110 doc += QLatin1String("\" ");
111 }
112 doc += QLatin1String("/>");
113 }
114 qCDebug(AKREGATORPLUGIN_LOG) << doc;
115 }
116
117 m_feedList = FeedDetector::extractFromLinkTags(doc);
118 return m_feedList.count() != 0;
119 }
120
contextMenu()121 void KonqFeedIcon::contextMenu()
122 {
123 delete m_menu;
124 m_menu = new QMenu(m_part->widget());
125 if (m_feedList.count() == 1) {
126 m_menu->setTitle(m_feedList.first().title());
127 m_menu->addAction(QIcon::fromTheme("bookmark-new"), i18n("Add Feed to Akregator"), this, SLOT(addAllFeeds()));
128 } else {
129 m_menu->setTitle(i18n("Add Feeds to Akregator"));
130 int id = 0;
131 for (FeedDetectorEntryList::Iterator it = m_feedList.begin(); it != m_feedList.end(); ++it) {
132 QAction *action = m_menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), (*it).title(), this, SLOT(addFeed()));
133 action->setData(QVariant::fromValue(id));
134 id++;
135 }
136 //disconnect(m_menu, SIGNAL(activated(int)), this, SLOT(addFeed(int)));
137 m_menu->addSeparator();
138 m_menu->addAction(QIcon::fromTheme(QStringLiteral("bookmark-new")), i18n("Add All Found Feeds to Akregator"), this, SLOT(addAllFeeds()));
139 }
140 m_menu->popup(QCursor::pos());
141 }
142
addFeedIcon()143 void KonqFeedIcon::addFeedIcon()
144 {
145 if (!feedFound() || m_feedIcon) {
146 return;
147 }
148
149 m_statusBarEx = KParts::StatusBarExtension::childObject(m_part);
150 if (!m_statusBarEx) {
151 return;
152 }
153
154 m_feedIcon = new KUrlLabel(m_statusBarEx->statusBar());
155
156 // from khtmlpart's ualabel
157 m_feedIcon->setFixedHeight(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize));
158 m_feedIcon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
159 m_feedIcon->setUseCursor(false);
160
161 m_feedIcon->setPixmap(KIconLoader::global()->loadIcon(QStringLiteral("feed"), KIconLoader::User));
162 m_feedIcon->setToolTip(i18n("Subscribe to site updates (using news feed)"));
163
164 m_statusBarEx->addStatusBarItem(m_feedIcon, 0, true);
165
166 connect(m_feedIcon, QOverload<>::of(&KUrlLabel::leftClickedUrl), this, &KonqFeedIcon::contextMenu);
167 }
168
removeFeedIcon()169 void KonqFeedIcon::removeFeedIcon()
170 {
171 m_feedList.clear();
172 if (m_feedIcon && m_statusBarEx) {
173 m_statusBarEx->removeStatusBarItem(m_feedIcon);
174 delete m_feedIcon;
175 m_feedIcon = nullptr;
176 }
177
178 // Close the popup if it's open, otherwise we crash
179 delete m_menu;
180 m_menu = nullptr;
181 }
182
addFeed()183 void KonqFeedIcon::addFeed()
184 {
185 bool ok = false;
186 const int id = sender() ? qobject_cast<QAction *>(sender())->data().toInt(&ok) : -1;
187 if (!ok || id == -1) {
188 return;
189 }
190 PluginUtil::addFeeds(QStringList(PluginUtil::fixRelativeURL(m_feedList[id].url(), baseUrl(m_part))));
191 }
192
193 // from akregatorplugin.cpp
addAllFeeds()194 void KonqFeedIcon::addAllFeeds()
195 {
196 QStringList list;
197 foreach (const FeedDetectorEntry &it, m_feedList) {
198 list.append(PluginUtil::fixRelativeURL(it.url(), baseUrl(m_part)));
199 }
200 PluginUtil::addFeeds(list);
201 }
202
203 #include "konqfeedicon.moc"
204