/* * Copyright (C) 2007, 2008 Petri Damsten * * 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 . */ #include "news.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *BEGIN = "\n" "\n" "\n" "\n" " \n" " \n" " Plasma News\n" "\n" "\n"; static const char *BEGIN_TABLE = "\n"; static const char *HEADER_LINE = "" "\n"; static const char *DATE_LINE = "\n"; static const char *LINE = "" "\n"; static const char *TIME_LINE = "" "\n"; static const char *TITLE = "%1"; static const char *TITLE_DESC = "%1
"; static const char *DESCRIPTION = "%1"; static const char *END_TABLE = "
%2
%1
%3
%2%4
\n"; static const char *END = "\n\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 News::akregatorFeeds() { QMap 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 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); } #include "moc_news.cpp"