2015-01-15 17:07:43 +00:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2007, 2008 Petri Damsten <damu@iki.fi>
|
|
|
|
*
|
|
|
|
* 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. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "news.h"
|
|
|
|
|
|
|
|
#include <QGraphicsSceneDragDropEvent>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QColor>
|
|
|
|
#include <QDomDocument>
|
|
|
|
#include <QGraphicsLinearLayout>
|
|
|
|
#include <QWebFrame>
|
|
|
|
#include <QWebPage>
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
|
|
|
#include <KConfigDialog>
|
|
|
|
#include <KColorScheme>
|
|
|
|
#include <KDebug>
|
|
|
|
#include <KStandardDirs>
|
|
|
|
#include <KRun>
|
|
|
|
|
|
|
|
#include <Plasma/Containment>
|
|
|
|
#include <Plasma/Theme>
|
|
|
|
#include <Plasma/WebView>
|
|
|
|
|
|
|
|
static const char *BEGIN =
|
|
|
|
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
|
|
|
|
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
|
|
|
|
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
|
|
|
|
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
|
|
|
|
"<head>\n"
|
|
|
|
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
|
|
|
|
" <link rel=\"stylesheet\" type=\"text/css\" href=\"news.css\" />\n"
|
|
|
|
" <title>Plasma News</title>\n"
|
|
|
|
"</head>\n"
|
|
|
|
"<body>\n";
|
|
|
|
|
|
|
|
static const char *BEGIN_TABLE = "<table>\n";
|
|
|
|
static const char *HEADER_LINE = "<tr class=\"header_line\">"
|
|
|
|
"<td colspan=\"2\" class=\"header\"><a href=\"%1\">%2</a></td></tr>\n";
|
|
|
|
static const char *DATE_LINE = "<tr class=\"date_line\"><td colspan=\"2\" class=\"date\">%1</td></tr>\n";
|
|
|
|
static const char *LINE = "<tr class=\"line-%1\">"
|
|
|
|
"<td colspan=\"2\" class=\"text\"><a href=\"%2\">%3</a></td></tr>\n";
|
|
|
|
static const char *TIME_LINE = "<tr class=\"line-%1\"><td class=\"time\">%2</td>"
|
|
|
|
"<td class=\"text\"><a href=\"%3\">%4</a></td></tr>\n";
|
|
|
|
static const char *TITLE = "<span class=\"title\">%1</span>";
|
|
|
|
static const char *TITLE_DESC = "<span class=\"title_with_description\">%1</span><br/>";
|
|
|
|
static const char *DESCRIPTION = "<span class=\"description\">%1</span>";
|
|
|
|
static const char *END_TABLE = "</table>\n";
|
|
|
|
static const char *END = "</body>\n</html>\n";
|
|
|
|
|
|
|
|
static const char *CSS =
|
|
|
|
".header { font-size:11pt; font-weight:bold; color:%1; background-color:%2; }\n"
|
|
|
|
".date { font-size:10pt; font-weight:bold; color:%1; background-color:%2; }\n"
|
|
|
|
".line-0 { font-size:10pt; color:%1; background-color:%3; }\n"
|
|
|
|
".line-1 { font-size:10pt; color:%1; background-color:%4; }\n"
|
|
|
|
".time, .title, .description { }\n"
|
|
|
|
".text { width:100%; }\n"
|
|
|
|
".title_with_description { font-weight:bold; }\n"
|
|
|
|
"a { text-decoration:none; color:%1 }\n"
|
|
|
|
"table { width:100%; border-spacing:0px; }\n"
|
|
|
|
"td { vertical-align: top; }\n"
|
|
|
|
"body { margin:0px; background:none }\n";
|
|
|
|
|
|
|
|
News::News(QObject *parent, const QVariantList &args)
|
|
|
|
: Plasma::PopupApplet(parent, args),
|
|
|
|
m_graphicsWidget(0)
|
|
|
|
{
|
|
|
|
setHasConfigurationInterface(true);
|
|
|
|
setAspectRatioMode(Plasma::IgnoreAspectRatio);
|
|
|
|
|
|
|
|
setPopupIcon("application-rss+xml");
|
|
|
|
|
|
|
|
setAssociatedApplication("akregator");
|
|
|
|
|
|
|
|
resize(300,300);
|
|
|
|
}
|
|
|
|
|
|
|
|
News::~News()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QGraphicsWidget *News::graphicsWidget()
|
|
|
|
{
|
|
|
|
if (m_graphicsWidget) {
|
|
|
|
return m_graphicsWidget;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_layout = new QGraphicsLinearLayout();
|
|
|
|
m_layout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
m_layout->setSpacing(0);
|
|
|
|
m_layout->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
|
|
|
|
|
|
|
|
m_news = new Plasma::WebView(this);
|
|
|
|
m_news->setDragToScroll(true);
|
|
|
|
m_news->installEventFilter(this);
|
|
|
|
m_news->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
|
|
|
|
connect(m_news->page(), SIGNAL(linkClicked(QUrl)),
|
|
|
|
this, SLOT(linkActivated(QUrl)));
|
|
|
|
m_news->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
|
|
|
|
m_layout->addItem(m_news);
|
|
|
|
|
|
|
|
|
|
|
|
setAcceptDrops(true);
|
|
|
|
makeStylesheet();
|
|
|
|
|
|
|
|
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(makeStylesheet()));
|
|
|
|
|
|
|
|
m_graphicsWidget = new QGraphicsWidget(this);
|
|
|
|
m_graphicsWidget->setLayout(m_layout);
|
|
|
|
m_graphicsWidget->setPreferredSize(370,440);
|
|
|
|
//a tiny minimum size, a panel 48px high is enough to display the whole applet
|
|
|
|
m_graphicsWidget->setMinimumSize(150, 150);
|
|
|
|
QPalette palette = m_graphicsWidget->palette();
|
|
|
|
palette.setBrush(QPalette::Base, Qt::transparent);
|
|
|
|
m_graphicsWidget->setPalette(palette);
|
|
|
|
|
|
|
|
configChanged();
|
|
|
|
|
|
|
|
return m_graphicsWidget;
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::configChanged()
|
|
|
|
{
|
|
|
|
kDebug();
|
|
|
|
KConfigGroup cg = config();
|
|
|
|
m_interval = cg.readEntry("interval", 30);
|
|
|
|
m_showTimestamps = cg.readEntry("showTimestamps", true);
|
|
|
|
m_showTitles = cg.readEntry("showTitles", true);
|
|
|
|
m_showDescriptions = cg.readEntry("showDescriptions", false);
|
|
|
|
|
|
|
|
m_feeds = cg.readEntry("feeds", QStringList("http://www.kde.org/dotkdeorg.rdf"));
|
|
|
|
|
|
|
|
connectToEngine();
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::connectToEngine()
|
|
|
|
{
|
|
|
|
if (!m_feeds.isEmpty()) {
|
|
|
|
Plasma::DataEngine* engine = dataEngine("rss");
|
|
|
|
if( engine) {
|
|
|
|
|
|
|
|
QString feedstring;
|
|
|
|
|
|
|
|
foreach (const QString& feed, m_feeds) {
|
|
|
|
QByteArray ba = QUrl::toPercentEncoding(feed, ":/&?=");
|
|
|
|
feedstring.append(ba);
|
|
|
|
feedstring.append(" ");
|
|
|
|
}
|
|
|
|
m_feedstring = feedstring;
|
|
|
|
kDebug() << feedstring;
|
|
|
|
engine->connectSource(feedstring, this, m_interval * 60 * 1000);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
kDebug()<<"Can not connect to dataengine rss";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::makeStylesheet()
|
|
|
|
{
|
|
|
|
const QString search = "desktoptheme/" + Plasma::Theme::defaultTheme()->themeName() +
|
|
|
|
"/stylesheets/news.css";
|
|
|
|
const QString path = KStandardDirs::locate("data", search);
|
|
|
|
if (path.isEmpty()) {
|
|
|
|
QColor backgroundColor =
|
|
|
|
Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor);
|
|
|
|
QColor textColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
|
|
|
|
QColor altBackgroundColor = backgroundColor;
|
|
|
|
altBackgroundColor.setHsv(altBackgroundColor.hue(), altBackgroundColor.saturation(),
|
|
|
|
(altBackgroundColor.value() < 128)
|
|
|
|
? backgroundColor.value() + 40 : backgroundColor.value() - 40,
|
|
|
|
altBackgroundColor.alpha());
|
|
|
|
QString css = QString(CSS).arg(textColor.name())
|
|
|
|
.arg(QString("rgba(%1, %2, %3, 0.45)").arg(backgroundColor.red()).arg( backgroundColor.green()).arg( backgroundColor.blue()))
|
|
|
|
.arg(QString("rgba(%1, %2, %3, 0.25)").arg(backgroundColor.red()).arg( backgroundColor.green()).arg( backgroundColor.blue()))
|
|
|
|
.arg(QString("rgba(%1, %2, %3, 0.25)").arg(altBackgroundColor.red()).arg( altBackgroundColor.green()).arg( altBackgroundColor.blue()));
|
|
|
|
KStandardDirs dirs;
|
|
|
|
m_cssDir = dirs.saveLocation("data", "plasma_applet_news");
|
|
|
|
|
|
|
|
QFile file(m_cssDir + "/news.css");
|
|
|
|
if (file.open(QIODevice::WriteOnly)) {
|
|
|
|
file.write(css.toUtf8());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
m_cssDir = QFileInfo(path).absolutePath() + '/';
|
|
|
|
}
|
|
|
|
|
|
|
|
QWebFrame* frame = m_news->mainFrame();
|
|
|
|
if (frame) {
|
|
|
|
frame->setHtml(frame->toHtml(), m_cssDir);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QMap<QString, QString> News::akregatorFeeds()
|
|
|
|
{
|
|
|
|
QMap<QString, QString> result;
|
|
|
|
const QString file = KStandardDirs::locate("data", "akregator/data/feeds.opml");
|
|
|
|
if (!file.isEmpty()) {
|
|
|
|
QFile f(file);
|
|
|
|
if (f.open(QIODevice::ReadOnly)) {
|
|
|
|
QDomDocument doc;
|
|
|
|
if (doc.setContent(&f)) {
|
|
|
|
QDomNodeList nodes = doc.elementsByTagName("outline");
|
|
|
|
for (int i = 0; i < nodes.count(); ++i) {
|
|
|
|
QDomElement element = nodes.item(i).toElement();
|
|
|
|
QString title = element.attribute("title");
|
|
|
|
if (!title.isEmpty()) {
|
|
|
|
result[title] = element.attribute("xmlUrl");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::createConfigurationInterface(KConfigDialog *parent)
|
|
|
|
{
|
|
|
|
QWidget *widget = new QWidget();
|
|
|
|
ui.setupUi(widget);
|
|
|
|
QWidget *fWidget = new QWidget(0);
|
|
|
|
feedsUi.setupUi(fWidget);
|
|
|
|
parent->addPage(widget, i18n("General"), icon());
|
|
|
|
parent->addPage(fWidget, i18n("Feeds"), "application-rss+xml");
|
|
|
|
|
|
|
|
connect(feedsUi.feedComboBox, SIGNAL(editTextChanged(QString)),
|
|
|
|
this, SLOT(feedTextChanged(QString)));
|
|
|
|
connect(parent, SIGNAL(accepted()), this, SLOT(configAccepted()));
|
|
|
|
connect(feedsUi.addFeed, SIGNAL(clicked()), this, SLOT(addFeed()));
|
|
|
|
connect(feedsUi.removeFeed, SIGNAL(clicked()), this, SLOT(removeFeed()));
|
|
|
|
|
|
|
|
m_defaultFeeds = akregatorFeeds();
|
|
|
|
feedsUi.feedComboBox->clear();
|
|
|
|
feedTextChanged(QString());
|
|
|
|
feedsUi.feedList->clear();
|
|
|
|
foreach (const QString& name, m_defaultFeeds.keys()) {
|
|
|
|
feedsUi.feedComboBox->addItem(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
ui.intervalSpinBox->setValue(m_interval);
|
|
|
|
ui.intervalSpinBox->setSuffix(ki18np(" minute", " minutes"));
|
|
|
|
|
|
|
|
ui.timestampCheckBox->setChecked(m_showTimestamps);
|
|
|
|
ui.titlesCheckBox->setChecked(m_showTitles);
|
|
|
|
ui.descriptionsCheckBox->setChecked(m_showDescriptions);
|
|
|
|
|
|
|
|
feedsUi.feedList->addItems(m_feeds);
|
|
|
|
connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()));
|
|
|
|
connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted()));
|
|
|
|
connect(ui.intervalSpinBox, SIGNAL(valueChanged(int)), parent, SLOT(settingsModified()));
|
|
|
|
connect(ui.timestampCheckBox, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
|
|
connect(ui.titlesCheckBox, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
|
|
connect(ui.descriptionsCheckBox, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
|
|
connect(feedsUi.feedComboBox, SIGNAL(editTextChanged(QString)), parent, SLOT(settingsModified()));
|
|
|
|
connect(feedsUi.addFeed, SIGNAL(released()), parent, SLOT(settingsModified()));
|
|
|
|
connect(feedsUi.removeFeed, SIGNAL(released()), parent, SLOT(settingsModified()));
|
|
|
|
connect(feedsUi.feedList, SIGNAL(itemSelectionChanged()), parent, SLOT(settingsModified()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::feedTextChanged(const QString& text)
|
|
|
|
{
|
|
|
|
feedsUi.addFeed->setEnabled(!text.isEmpty());
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::addFeed()
|
|
|
|
{
|
|
|
|
if (!feedsUi.feedComboBox->currentText().isEmpty()) {
|
|
|
|
QString url = feedsUi.feedComboBox->currentText();
|
|
|
|
if (m_defaultFeeds.keys().contains(url)) {
|
|
|
|
url = m_defaultFeeds[url];
|
|
|
|
}
|
|
|
|
bool found = false;
|
|
|
|
for (int i = 0; i < feedsUi.feedList->count(); i++) {
|
|
|
|
QString feed = feedsUi.feedList->item(i)->text();
|
|
|
|
if (feed == url) {
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
feedsUi.feedList->addItem(url);
|
|
|
|
}
|
|
|
|
feedsUi.removeFeed->setEnabled(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::removeFeed()
|
|
|
|
{
|
|
|
|
int row = feedsUi.feedList->currentRow();
|
|
|
|
if (row != -1) {
|
|
|
|
feedsUi.feedList->takeItem(row);
|
|
|
|
if (feedsUi.feedList->count() == 0) {
|
|
|
|
feedsUi.removeFeed->setEnabled(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::configAccepted()
|
|
|
|
{
|
|
|
|
m_interval = ui.intervalSpinBox->value();
|
|
|
|
m_showTimestamps = ui.timestampCheckBox->isChecked();
|
|
|
|
m_showTitles = ui.titlesCheckBox->isChecked();
|
|
|
|
m_showDescriptions = ui.descriptionsCheckBox->isChecked();
|
|
|
|
|
|
|
|
m_feeds.clear();
|
|
|
|
QString feed;
|
|
|
|
for (int i = 0; i < feedsUi.feedList->count(); i++) {
|
|
|
|
feed = feedsUi.feedList->item(i)->text();
|
|
|
|
if (m_defaultFeeds.keys().contains(feed)) {
|
|
|
|
feed = m_defaultFeeds[feed];
|
|
|
|
}
|
|
|
|
m_feeds.append(feed);
|
|
|
|
}
|
|
|
|
|
|
|
|
KConfigGroup cg = config();
|
|
|
|
cg.writeEntry("feeds", m_feeds);
|
|
|
|
cg.writeEntry("interval", m_interval);
|
|
|
|
cg.writeEntry("showTimestamps", m_showTimestamps);
|
|
|
|
cg.writeEntry("showTitles", m_showTitles);
|
|
|
|
cg.writeEntry("showDescriptions", m_showDescriptions);
|
|
|
|
|
|
|
|
dataUpdated(m_feedstring, m_dataCache);
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::dropEvent(QGraphicsSceneDragDropEvent *event)
|
|
|
|
{
|
|
|
|
if (KUrl::List::canDecode(event->mimeData())) {
|
|
|
|
KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
|
|
|
|
|
|
|
|
if (!urls.isEmpty()) {
|
|
|
|
event->accept();
|
|
|
|
m_feeds.clear();
|
|
|
|
foreach (const KUrl &feed, urls) {
|
|
|
|
m_feeds << feed.prettyUrl();
|
|
|
|
}
|
|
|
|
m_interval = 30;
|
|
|
|
connectToEngine();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool News::eventFilter(QObject *receiver, QEvent *event)
|
|
|
|
{
|
|
|
|
if (receiver == m_news && event->type() == QEvent::GraphicsSceneContextMenu) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data)
|
|
|
|
{
|
|
|
|
if (source == m_feedstring && !data.isEmpty()) {
|
|
|
|
m_dataCache = data;
|
|
|
|
QDateTime previous = QDateTime::currentDateTime();
|
|
|
|
QString html;
|
|
|
|
QVariantList items = data["items"].toList();
|
|
|
|
uint columns = 1;
|
|
|
|
uint i = 0;
|
|
|
|
|
|
|
|
html += BEGIN;
|
|
|
|
html += BEGIN_TABLE;
|
|
|
|
html += QString(HEADER_LINE).arg(data["link"].toString())
|
|
|
|
.arg(data["title"].toString());
|
|
|
|
|
|
|
|
foreach (const QVariant &tmp, items) {
|
|
|
|
QMap<QString, QVariant> item = tmp.toMap();
|
|
|
|
if (item["time"].toUInt() != 0) {
|
|
|
|
columns = 2;
|
|
|
|
} else {
|
|
|
|
columns = 1;
|
|
|
|
}
|
|
|
|
uint timeStamp = item["time"].toUInt();
|
|
|
|
QString title = item["title"].toString();
|
|
|
|
QString description = item["description"].toString().remove(QRegExp("<[^>]*>"));
|
|
|
|
QString line;
|
|
|
|
|
|
|
|
if (title != 0 && m_showTitles) {
|
|
|
|
if (description != 0 && m_showDescriptions) {
|
|
|
|
line += QString(TITLE_DESC).arg(title);
|
|
|
|
} else {
|
|
|
|
line += QString(TITLE).arg(title);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (description != 0 && m_showDescriptions) {
|
|
|
|
line += QString(DESCRIPTION).arg(description);
|
|
|
|
}
|
|
|
|
if (timeStamp != 0 && m_showTimestamps) {
|
|
|
|
QDateTime time;
|
|
|
|
time.setTime_t(timeStamp);
|
|
|
|
if (previous.date() != time.date()) {
|
|
|
|
html += QString(DATE_LINE).arg(time.date().toString());
|
|
|
|
i = 0;
|
|
|
|
}
|
|
|
|
html += QString(TIME_LINE).arg(i % 2)
|
|
|
|
.arg(time.time().toString("HH:mm"))
|
|
|
|
.arg(item["link"].toString())
|
|
|
|
.arg(line);
|
|
|
|
previous = time;
|
|
|
|
} else {
|
|
|
|
html += QString(LINE).arg(i % 2)
|
|
|
|
.arg(item["link"].toString())
|
|
|
|
.arg(line);
|
|
|
|
}
|
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
html += END_TABLE;
|
|
|
|
html += END;
|
|
|
|
m_graphicsWidget->setPreferredSize(-1, -1);
|
|
|
|
|
|
|
|
m_news->setHtml(html, m_cssDir);
|
|
|
|
emit sizeHintChanged(Qt::PreferredSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void News::linkActivated(const QUrl& link)
|
|
|
|
{
|
|
|
|
KRun::runUrl(KUrl(link), "text/html", 0);
|
|
|
|
}
|
|
|
|
|
2015-02-27 11:02:43 +00:00
|
|
|
#include "moc_news.cpp"
|