From 25b2e77aa2395ba9143683a5ce1a27b99ee7a211 Mon Sep 17 00:00:00 2001 From: Ken Moore Date: Wed, 4 Jan 2017 16:44:55 -0500 Subject: Create a new "lumina-desktop-unified" core subproject (DO NOT USE) This is just a staging area for the merging of the desktop, window manager, etc.. into a single unified application. It is highly fragmented right now and will not build *AT ALL* for a while. --- .../desktop-plugins/rssreader/RSSFeedPlugin.cpp | 363 ++++++++++++++ .../desktop-plugins/rssreader/RSSFeedPlugin.h | 72 +++ .../desktop-plugins/rssreader/RSSFeedPlugin.ui | 552 +++++++++++++++++++++ .../desktop-plugins/rssreader/RSSObjects.cpp | 287 +++++++++++ .../src-DE/desktop-plugins/rssreader/RSSObjects.h | 105 ++++ 5 files changed, 1379 insertions(+) create mode 100644 src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp create mode 100644 src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h create mode 100644 src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui create mode 100644 src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp create mode 100644 src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.h (limited to 'src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader') diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp new file mode 100644 index 00000000..c330d6c0 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.cpp @@ -0,0 +1,363 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "RSSFeedPlugin.h" +#include "ui_RSSFeedPlugin.h" + +#include +#include "LSession.h" +#include +#include +#include +#include +#include + +RSSFeedPlugin::RSSFeedPlugin(QWidget* parent, QString ID) : LDPlugin(parent, ID), ui(new Ui::RSSFeedPlugin()){ + ui->setupUi(this); + //Load the global settings + setprefix = "rssreader/"; //this structure/prefix should be used for *all* plugins of this type + RSS = new RSSReader(this, setprefix); + ui->text_feed->setContextMenuPolicy(Qt::NoContextMenu); + //Create the options menu + optionsMenu = new QMenu(this); + ui->tool_options->setMenu(optionsMenu); + presetMenu = new QMenu(this); + ui->tool_add_preset->setMenu(presetMenu); + + //Setup any signal/slot connections + connect(ui->push_back1, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_back2, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_back3, SIGNAL(clicked()), this, SLOT(backToFeeds()) ); + connect(ui->push_save_settings, SIGNAL(clicked()), this, SLOT(saveSettings()) ); + connect(RSS, SIGNAL(rssChanged(QString)), this, SLOT(RSSItemChanged(QString)) ); + connect(RSS, SIGNAL(newChannelsAvailable()), this, SLOT(UpdateFeedList())); + connect(ui->tool_gotosite, SIGNAL(clicked()), this, SLOT(openFeedPage()) ); + connect(ui->push_rm_feed, SIGNAL(clicked()), this, SLOT(removeFeed()) ); + connect(ui->push_add_url, SIGNAL(clicked()), this, SLOT(addNewFeed()) ); + connect(ui->combo_feed, SIGNAL(currentIndexChanged(int)), this, SLOT(currentFeedChanged()) ); + + connect(presetMenu, SIGNAL(triggered(QAction*)), this, SLOT(loadPreset(QAction*)) ); + + updateOptionsMenu(); + QTimer::singleShot(0,this, SLOT(ThemeChange()) ); + //qDebug() << " - Done with init"; + QStringList feeds; + if( !LSession::handle()->DesktopPluginSettings()->contains(setprefix+"currentfeeds") ){ + //First-time run of the plugin - automatically load the default feeds + feeds = LOS::RSSFeeds(); + for(int i=0; iDesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + }else{ + feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + } + RSS->addUrls(feeds); + backToFeeds(); //always load the first page +} + +RSSFeedPlugin::~RSSFeedPlugin(){ + +} + +//================ +// PRIVATE +//================ +void RSSFeedPlugin::updateOptionsMenu(){ + optionsMenu->clear(); + optionsMenu->addAction(LXDG::findIcon("list-add",""), tr("Add RSS Feed"), this, SLOT(openFeedNew()) ); + optionsMenu->addAction(LXDG::findIcon("help-about",""), tr("View Feed Details"), this, SLOT(openFeedInfo()) ); + optionsMenu->addAction(LXDG::findIcon("configure",""), tr("Settings"), this, SLOT(openSettings()) ); + optionsMenu->addSeparator(); + optionsMenu->addAction(LXDG::findIcon("download",""), tr("Update Feeds Now"), this, SLOT(resyncFeeds()) ); + + presetMenu->clear(); + QStringList feeds = LOS::RSSFeeds(); + feeds << tr("Lumina Desktop RSS")+"::::http://lumina-desktop.org/?feed=rss2"; + feeds.sort(); + for(int i=0; iaddAction(feeds[i].section("::::",0,0) ); + tmp->setWhatsThis( feeds[i].section("::::",1,-1) ); + } +} + +void RSSFeedPlugin::checkFeedNotify(){ + bool notify = false; + for(int i=0; icombo_feed->count() && !notify; i++){ + if( !ui->combo_feed->itemData(i, Qt::WhatsThisRole).toString().isEmpty()){ notify = true; } + } + QString style; + if(notify){ style = "QComboBox{ background-color: rgba(255,0,0,120); }"; } + ui->combo_feed->setStyleSheet(style); +} + +//Simplification functions for loading feed info onto widgets +void RSSFeedPlugin::updateFeed(QString ID){ + //Now clear/update the feed viewer (HTML) + ui->text_feed->clear(); + if(ID.isEmpty()){ return; } //nothing to show + + //Save the datetime this feed was read + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"feedReads/"+ID, QDateTime::currentDateTime() ); + //Get the color to use for hyperlinks (need to specify in html) + QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies) + QString html; + RSSchannel data = RSS->dataForID(ID); + ui->label_lastupdate->setText( data.lastsync.toString(Qt::DefaultLocaleShortDate) ); + // - generate the html + // html.append("
    \n"); + for(int i=0; i"); + html.append("

    "+data.items[i].title+"

    "); + if(!data.items[i].pubdate.isNull() || !data.items[i].author.isEmpty()){ + html.append("("); + if(!data.items[i].pubdate.isNull()){ html.append(data.items[i].pubdate.toString(Qt::DefaultLocaleShortDate)); } + if(!data.items[i].author.isEmpty()){ + if(!html.endsWith("(")){ html.append(", "); } //spacing between date/author + if(!data.items[i].author_email.isEmpty()){ html.append(""+data.items[i].author+""); } + else{ html.append(data.items[i].author); } + } + html.append(")
    "); + } + html.append(data.items[i].description); + //html.append("\n"); + if(i+1 < data.items.length()){ html.append("
    "); } + } + //html.append("
\n"); + // - load that html into the viewer + ui->text_feed->setHtml(html); +} + +void RSSFeedPlugin::updateFeedInfo(QString ID){ + ui->page_feed_info->setWhatsThis(ID); + ui->text_feed_info->clear(); + if(ID.isEmpty()){ return; } + //Get the color to use for hyperlinks (need to specify in html) + QString color = ui->text_feed->palette().text().color().name(); //keep the hyperlinks the same color as the main text (different formatting still applies) + QString html; + RSSchannel data = RSS->dataForID(ID); + // - generate the html + // "+TEXT+" + html.append( QString(tr("Feed URL: %1")).arg(""+data.originalURL+"") +"

"); + html.append( QString(tr("Title: %1")).arg(data.title) +"
"); + html.append( QString(tr("Description: %1")).arg(data.description) +"
"); + html.append( QString(tr("Website: %1")).arg(""+data.link+"") +"

"); + if(!data.lastBuildDate.isNull()){ html.append( QString(tr("Last Build Date: %1")).arg(data.lastBuildDate.toString(Qt::DefaultLocaleShortDate)) +"
"); } + html.append( QString(tr("Last Sync: %1")).arg(data.lastsync.toString(Qt::DefaultLocaleShortDate)) +"
"); + html.append( QString(tr("Next Sync: %1")).arg(data.nextsync.toString(Qt::DefaultLocaleShortDate)) +"
"); + // - load that html into the viewer + ui->text_feed_info->setHtml(html); +} + +//================ +// PRIVATE SLOTS +//================ +void RSSFeedPlugin::loadIcons(){ + ui->tool_options->setIcon( LXDG::findIcon("configure","") ); + ui->tool_gotosite->setIcon( LXDG::findIcon("applications-internet","") ); + ui->push_back1->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_back2->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_back3->setIcon( LXDG::findIcon("go-previous","") ); + ui->push_rm_feed->setIcon( LXDG::findIcon("list-remove","") ); + ui->push_add_url->setIcon( LXDG::findIcon("list-add","") ); + ui->push_save_settings->setIcon( LXDG::findIcon("document-save","") ); + ui->tool_add_preset->setIcon( LXDG::findIcon("bookmark-new-list","") ); +} + +//GUI slots +// - Page management +void RSSFeedPlugin::backToFeeds(){ + ui->stackedWidget->setCurrentWidget(ui->page_feed); +} + +void RSSFeedPlugin::openFeedInfo(){ + QString ID = ui->combo_feed->currentData().toString(); + if(ID.isEmpty()){ return; } + updateFeedInfo(ID); + ui->stackedWidget->setCurrentWidget(ui->page_feed_info); + +} + +void RSSFeedPlugin::openFeedNew(){ + ui->line_new_url->setText(""); + ui->stackedWidget->setCurrentWidget(ui->page_new_feed); +} + +void RSSFeedPlugin::openSettings(){ + //Sync the widget with the current settings + QSettings *set = LSession::handle()->DesktopPluginSettings(); + + ui->check_manual_sync->setChecked( set->value(setprefix+"manual_sync_only", false).toBool() ); + int DI = set->value(setprefix+"default_interval_minutes", 60).toInt(); + if(DI<1){ DI = 60; } + if( (DI%60) == 0 ){DI = DI/60; ui->combo_sync_units->setCurrentIndex(1); } //hourly setting + else{ ui->combo_sync_units->setCurrentIndex(1); } //minutes setting + ui->spin_synctime->setValue(DI); + + //Now show the page + ui->stackedWidget->setCurrentWidget(ui->page_settings); +} + +// - Feed Management +void RSSFeedPlugin::addNewFeed(){ + if(ui->line_new_url->text().isEmpty()){ return; } //nothing to add + //Validate the URL + QUrl url(ui->line_new_url->text()); + if(!url.isValid()){ + ui->line_new_url->setFocus(); + return; + } + //Add the URL to the settings file for next login + QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + feeds << url.toString(); + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + + //Set this URL as the current selection + ui->combo_feed->setWhatsThis(url.toString()); //hidden field - will trigger an update in a moment + //Add the URL to the backend + RSS->addUrls(QStringList() << url.toString()); + //UpdateFeedList(); //now re-load the feeds which are available + + //Now go back to the main page + backToFeeds(); +} + +void RSSFeedPlugin::loadPreset(QAction *act){ + ui->line_new_url->setText( act->whatsThis() ); +} + +void RSSFeedPlugin::removeFeed(){ + QString ID = ui->page_feed_info->whatsThis(); + if(ID.isEmpty()){ return; } + //Remove from the RSS feed object + RSSchannel info = RSS->dataForID(ID); + RSS->removeUrl(ID); + //Remove the URL from the settings file for next login + QStringList feeds = LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList(); + feeds.removeAll(info.originalURL); + LSession::handle()->DesktopPluginSettings()->setValue(setprefix+"currentfeeds", feeds); + LSession::handle()->DesktopPluginSettings()->remove(setprefix+"feedReads/"+ID); + //Now go back to the main page + backToFeeds(); +} + +void RSSFeedPlugin::resyncFeeds(){ + RSS->addUrls( LSession::handle()->DesktopPluginSettings()->value(setprefix+"currentfeeds",QStringList()).toStringList() ); + RSS->syncNow(); +} + +// - Feed Interactions +void RSSFeedPlugin::currentFeedChanged(){ + QString ID = ui->combo_feed->currentData().toString(); + //Remove the "unread" color/flag from the feed + ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), QBrush(Qt::transparent) , Qt::BackgroundRole); + ui->combo_feed->setItemData( ui->combo_feed->currentIndex(), "", Qt::WhatsThisRole); + checkFeedNotify(); + updateFeed(ID); +} + +void RSSFeedPlugin::openFeedPage(){ //Open main website for feed + QString ID = ui->combo_feed->currentData().toString(); + //Find the data associated with this feed + RSSchannel data = RSS->dataForID(ID); + QString url = data.link; + //qDebug() << "Open Feed Page:" << url; + //Now launch the browser + if(!url.isEmpty()){ + LSession::LaunchApplication("lumina-open \""+url+"\""); + } +} + +void RSSFeedPlugin::saveSettings(){ + QSettings *set = LSession::handle()->DesktopPluginSettings(); + set->setValue(setprefix+"manual_sync_only", ui->check_manual_sync->isChecked() ); + int DI = ui->spin_synctime->value(); + if(ui->combo_sync_units->currentIndex()==1){ DI = DI*60; } //convert from hours to minutes + set->setValue(setprefix+"default_interval_minutes", DI); + set->sync(); + + //Now go back to the feeds + backToFeeds(); +} + +//Feed Object interactions +void RSSFeedPlugin::UpdateFeedList(){ + + QString activate = ui->combo_feed->whatsThis(); + if(!activate.isEmpty()){ ui->combo_feed->setWhatsThis(""); } + if(activate.isEmpty()){ activate = ui->combo_feed->currentData().toString(); } // keep current item selected + //Now get/list all the available feeds + QStringList IDS = RSS->channels(); //this is pre-sorted by title of the feed + //qDebug() << "Update RSS Feed List:" << IDS << activate; + for(int i=0; icombo_feed->count()<=i){ newitem = true; } + else{ + QString cid = ui->combo_feed->itemData(i).toString(); + if(IDS[i]!=cid){ + if(IDS.contains(cid)){ newitem = true; } //this item is just out of order + else{ ui->combo_feed->removeItem(i); } //item no longer is valid + } + } + if(newitem){ + //Need to add a new item at this point in the menu + RSSchannel info = RSS->dataForID(IDS[i]); + if(info.title.isEmpty()){ + //invalid/empty channel + ui->combo_feed->insertItem(i, IDS[i], IDS[i]); //just show the URL + }else{ + ui->combo_feed->insertItem(i, info.icon, info.title, IDS[i]); + } + } + } + //Remove any extra items on the end of the list + for(int i=IDS.length(); icombo_feed->count(); i++){ + ui->combo_feed->removeItem(i); i--; + } + //Now activate the proper item as needed + if(IDS.contains(activate)){ + ui->combo_feed->setCurrentIndex( IDS.indexOf(activate) ); + } + checkFeedNotify(); +} + +void RSSFeedPlugin::RSSItemChanged(QString ID){ + for(int i=0; icombo_feed->count(); i++){ + if(ui->combo_feed->itemData(i).toString()!=ID){ continue; } + RSSchannel info = RSS->dataForID(ID); + if(info.title.isEmpty()){ + ui->combo_feed->setItemText(i, ID); + ui->combo_feed->setItemIcon(i, LXDG::findIcon("dialog-cancel","") ); + }else{ + ui->combo_feed->setItemText(i, info.title); + ui->combo_feed->setItemIcon(i, info.icon ); + QColor color(Qt::transparent); + if( info.lastBuildDate > LSession::handle()->DesktopPluginSettings()->value(setprefix+"feedReads/"+ID,QDateTime()).toDateTime() ){ + color = QColor(255,10,10,100); //semi-transparent red + ui->combo_feed->setItemData(i, "notify", Qt::WhatsThisRole); + }else{ + ui->combo_feed->setItemData(i, "", Qt::WhatsThisRole); + } + ui->combo_feed->setItemData(i, QBrush(color) , Qt::BackgroundRole); + } + } + if(ID == ui->combo_feed->currentData().toString()){ + currentFeedChanged(); //re-load the current feed + }else{ + checkFeedNotify(); + } +} + +//================== +// PUBLIC SLOTS +//================== +void RSSFeedPlugin::LocaleChange(){ + ui->retranslateUi(this); + updateOptionsMenu(); +} +void RSSFeedPlugin::ThemeChange(){ + QTimer::singleShot(0,this, SLOT(loadIcons())); + updateOptionsMenu(); +} diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h new file mode 100644 index 00000000..68b36760 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.h @@ -0,0 +1,72 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// This plugin is a simple RSS feed reader for the desktop +//=========================================== +#ifndef _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H +#define _LUMINA_DESKTOP_RSS_FEEDER_PLUGIN_H + +#include +#include "../LDPlugin.h" + +#include "RSSObjects.h" + +namespace Ui{ + class RSSFeedPlugin; +}; + +class RSSFeedPlugin : public LDPlugin{ + Q_OBJECT +public: + RSSFeedPlugin(QWidget* parent, QString ID); + ~RSSFeedPlugin(); + + virtual QSize defaultPluginSize(){ + // The returned QSize is in grid points (typically 100 or 200 pixels square) + return QSize(3,3); + } +private: + Ui::RSSFeedPlugin *ui; + QMenu *optionsMenu, *presetMenu; + QString setprefix; //settings prefix + RSSReader *RSS; + + void updateOptionsMenu(); + void checkFeedNotify(); //check if unread feeds are available and change the styling a bit as needed + + //Simplification functions for loading feed info onto widgets + void updateFeed(QString ID); + void updateFeedInfo(QString ID); + +private slots: + void loadIcons(); + + //GUI slots + // - Page management + void backToFeeds(); + void openFeedInfo(); + void openFeedNew(); + void openSettings(); + // - Feed Management + void addNewFeed(); // the "add" button (current url in widget on page) + void loadPreset(QAction*); //the add-preset menu + void removeFeed(); // the "remove" button (current feed for page) + void resyncFeeds(); + // - Feed Interactions + void currentFeedChanged(); + void openFeedPage(); //Open the website in a browser + void saveSettings(); + + //Feed Object interactions + void UpdateFeedList(); + void RSSItemChanged(QString ID); + +public slots: + void LocaleChange(); + void ThemeChange(); + +}; +#endif diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui new file mode 100644 index 00000000..dc7acd0b --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSFeedPlugin.ui @@ -0,0 +1,552 @@ + + + RSSFeedPlugin + + + + 0 + 0 + 238 + 278 + + + + Form + + + + 0 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + 1 + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + View Options + + + + + + QToolButton::InstantPopup + + + true + + + + + + + + + + + + + + + + + + Open Website + + + More + + + true + + + Qt::NoArrow + + + + + + + + + false + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + true + + + true + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 5 + + + + + + + Back to Feeds + + + true + + + + + + + + + + 0 + 0 + + + + Feed Information + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + true + + + + + + + + + + + + Remove Feed + + + true + + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Back to Feeds + + + true + + + + + + + + + + 0 + 0 + + + + New Feed Subscription + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + RSS URL + + + Qt::AlignCenter + + + + + + + + + + + + Load a preset RSS Feed + + + + + + QToolButton::InstantPopup + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Add to Feeds + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Back to Feeds + + + true + + + + + + + + + + 0 + 0 + + + + Feed Reader Settings + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + Manual Sync Only + + + + + + + Some RSS feeds may request custom update intervals instead of using this setting + + + Default Sync Interval + + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + + + 1 + + + 60 + + + + + + + Hour(s) + + + 1 + + + + Minutes + + + + + Hour(s) + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Save Settings + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + diff --git a/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp new file mode 100644 index 00000000..0a805252 --- /dev/null +++ b/src-qt5/core/lumina-desktop-unified/src-DE/desktop-plugins/rssreader/RSSObjects.cpp @@ -0,0 +1,287 @@ +//=========================================== +// Lumina-DE source code +// Copyright (c) 2016, Ken Moore +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "RSSObjects.h" +#include +#include + +#include "LSession.h" + +//============ +// PUBLIC +//============ +RSSReader::RSSReader(QObject *parent, QString settingsPrefix) : QObject(parent){ + NMAN = new QNetworkAccessManager(this); + connect(NMAN, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)) ); + connect(NMAN, SIGNAL(sslErrors(QNetworkReply*, const QList&)), this, SLOT(sslErrors(QNetworkReply*, const QList&)) ); + + setprefix = settingsPrefix; + syncTimer = new QTimer(this); + syncTimer->setInterval(300000); //5 minutes + connect(syncTimer, SIGNAL(timeout()), this, SLOT(checkTimes())); + syncTimer->start(); +} + +RSSReader::~RSSReader(){ + +} + +//Information retrieval +QStringList RSSReader::channels(){ + QStringList urls = hash.keys(); + QStringList ids; + //sort all the channels by title before output + for(int i=0; iget( QNetworkRequest( QUrl(url) ) ); + outstandingURLS << url; + } +} + +//RSS parsing functions +RSSchannel RSSReader::readRSS(QByteArray bytes){ + //Note: We could expand this later to support multiple "channel"s per Feed + // but it seems like there is normally only one channel anyway + //qDebug() << "Read RSS:" << bytes.left(100); + QXmlStreamReader xml(bytes); + RSSchannel rssinfo; + //qDebug() << "Can Read XML Stream:" << !xml.hasError(); + if(xml.readNextStartElement()){ + //qDebug() << " - RSS Element:" << xml.name(); + if(xml.name() == "rss" && (xml.attributes().value("version") =="2.0" || xml.attributes().value("version") =="0.91") ){ + while(xml.readNextStartElement()){ + //qDebug() << " - RSS Element:" << xml.name(); + if(xml.name()=="channel"){ rssinfo = readRSSChannel(&xml); } + else{ xml.skipCurrentElement(); } + } + } + } + if(xml.hasError()){ qDebug() << " - XML Read Error:" << xml.errorString() << "\n" << bytes; } + return rssinfo; +} +RSSchannel RSSReader::readRSSChannel(QXmlStreamReader *rss){ + RSSchannel info; + info.timetolive = -1; + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (channel):" <name(); + if(rss->name()=="item"){ info.items << readRSSItem(rss); } + else if(rss->name()=="title"){ info.title = rss->readElementText(); } + else if(rss->name()=="link"){ + QString txt = rss->readElementText(); + if(!txt.isEmpty()){ info.link = txt; } + } + else if(rss->name()=="description"){ info.description = rss->readElementText(); } + else if(rss->name()=="lastBuildDate"){ info.lastBuildDate = RSSDateTime(rss->readElementText()); } + else if(rss->name()=="pubDate"){ info.lastPubDate = RSSDateTime(rss->readElementText()); } + else if(rss->name()=="image"){ readRSSImage(&info, rss); } + //else if(rss->name()=="skipHours"){ info.link = rss->readElementText(); } + //else if(rss->name()=="skipDays"){ info.link = rss->readElementText(); } + else if(rss->name()=="ttl"){ info.timetolive = rss->readElementText().toInt(); } + else{ rss->skipCurrentElement(); } + } + return info; +} + +RSSitem RSSReader::readRSSItem(QXmlStreamReader *rss){ + RSSitem item; + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (Item):" << rss->name(); + if(rss->name()=="title"){ item.title = rss->readElementText(); } + else if(rss->name()=="link"){ item.link = rss->readElementText(); } + else if(rss->name()=="description"){ item.description = rss->readElementText(); } + else if(rss->name()=="comments"){ item.comments_url = rss->readElementText(); } + else if(rss->name()=="author"){ + //Special handling - this field can contain both email and name + QString raw = rss->readElementText(); + if(raw.contains("@")){ + item.author_email = raw.split(" ").filter("@").first(); + item.author = raw.remove(item.author_email).remove("(").remove(")").simplified(); //the name is often put within parentheses after the email + }else{ item.author = raw; } + } + else if(rss->name()=="guid"){ item.guid = rss->readElementText(); } + else if(rss->name()=="pubDate"){ item.pubdate = RSSDateTime(rss->readElementText()); } + else{ rss->skipCurrentElement(); } + } + return item; +} + +void RSSReader::readRSSImage(RSSchannel *item, QXmlStreamReader *rss){ + while(rss->readNextStartElement()){ + //qDebug() << " - RSS Element (Image):" << rss->name(); + if(rss->name()=="url"){ item->icon_url = rss->readElementText(); } + else if(rss->name()=="title"){ item->icon_title = rss->readElementText(); } + else if(rss->name()=="link"){ item->icon_link = rss->readElementText(); } + else if(rss->name()=="width"){ item->icon_size.setWidth(rss->readElementText().toInt()); } + else if(rss->name()=="height"){ item->icon_size.setHeight(rss->readElementText().toInt()); } + else if(rss->name()=="description"){ item->icon_description = rss->readElementText(); } + } + //Go ahead and kick off the request for the icon + if(!item->icon_url.isEmpty()){ requestRSS(item->icon_url); } +} + +QDateTime RSSReader::RSSDateTime(QString datetime){ + return QDateTime::fromString(datetime, Qt::RFC2822Date); +} + +//================= +// PRIVATE SLOTS +//================= +void RSSReader::replyFinished(QNetworkReply *reply){ + QString url = reply->request().url().toString(); + //qDebug() << "Got Reply:" << url; + QString key = keyForUrl(url); //current hash key for this URL + QByteArray data = reply->readAll(); + outstandingURLS.removeAll(url); + if(data.isEmpty()){ + //qDebug() << "No data returned:" << url; + //see if the URL can be adjusted for known issues + bool handled = false; + QUrl redirecturl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + if(redirecturl.isValid() && (redirecturl.toString() != url )){ + //New URL redirect - make the change and send a new request + QString newurl = redirecturl.toString(); + //qDebug() << " - Redirect to:" << newurl; + if(hash.contains(key) && !hash.contains(newurl)){ + hash.insert(newurl, hash.take(key) ); //just move the data over to the new url + requestRSS(newurl); + emit newChannelsAvailable(); + handled = true; + } + } + if(!handled && hash.contains(key) ){ + emit rssChanged(hash[key].originalURL); + } + return; + } + + if(!hash.contains(key)){ + //qDebug() << " - hash does not contain URL:" << url; + //URL removed from list while a request was outstanding? + //Could also be an icon fetch response + QStringList keys = hash.keys(); + for(int i=0; ideleteLater(); + }else{ + //RSS reply + RSSchannel info = readRSS(data); //QNetworkReply can be used as QIODevice + reply->deleteLater(); //clean up + //Validate the info and announce any changes + if(info.title.isEmpty() || info.link.isEmpty() || info.description.isEmpty()){ + qDebug() << "Missing XML Information:" << url << info.title << info.link << info.description; + return; + } //bad info/read + //Update the bookkeeping elements of the info + if(info.timetolive<=0){ info.timetolive = LSession::handle()->DesktopPluginSettings()->value(setprefix+"default_interval_minutes", 60).toInt(); } + if(info.timetolive <=0){ info.timetolive = 60; } //error in integer conversion from settings? + info.lastsync = QDateTime::currentDateTime(); info.nextsync = info.lastsync.addSecs(info.timetolive * 60); + //Now see if anything changed and save the info into the hash + bool changed = (hash[key].lastBuildDate.isNull() || (hash[key].lastBuildDate < info.lastBuildDate) ); + bool newinfo = false; + if(changed){ newinfo = hash[key].title.isEmpty(); } //no previous info from this URL + info.originalURL = hash[key].originalURL; //make sure this info gets preserved across updates + if(!hash[key].icon.isNull()){ info.icon = hash[key].icon; } //copy over the icon from the previous reply + hash.insert(key, info); + if(newinfo){ emit newChannelsAvailable(); } //new channel + else if(changed){ emit rssChanged(info.originalURL); } //update to existing channel + } +} + +void RSSReader::sslErrors(QNetworkReply *reply, const QList &errors){ + int ok = 0; + qDebug() << "SSL Errors Detected (RSS Reader):" << reply->url(); + for(int i=0; iurl(); reply->ignoreSslErrors(); } + else{ qDebug() << " - Denied:" << reply->url(); } +} + +void RSSReader::checkTimes(){ + if(LSession::handle()->DesktopPluginSettings()->value(setprefix+"manual_sync_only", false).toBool()){ return; } + QStringList urls = hash.keys(); + QDateTime cdt = QDateTime::currentDateTime(); + for(int i=0; i +#include +#include +#include +#include +#include +#include +#include //Contained in the Qt "core" module - don't need the full "xml" module for this +#include + +struct RSSitem{ + //Required Fields + QString title, link, description; + + //Optional Fields + QString comments_url, author_email, author, guid; + QDateTime pubdate; //when the item was published + //IGNORED INFO from RSS2 spec: "category", "source", "enclosure" +}; + +struct RSSchannel{ + //Required fields + QString title, link, description; + + //Optional Fields + QDateTime lastBuildDate, lastPubDate; //last build/publish dates + // - channel refresh information + int timetolive; //in minutes - time until next sync should be performed + //QList skiphours; + //QStringList skipdays; + // - icon info + QIcon icon; + QString icon_url, icon_title, icon_link, icon_description; + QSize icon_size; + //All items within this channel + QList items; + + //Optional RSS2 elements ignored: + // "cloud", "textInput", "rating", "language", "copyright", "managingEditor", "webMaster", + // "category", "generator", "docs" + + //Internal data for bookkeeping + QDateTime lastsync, nextsync; + QString originalURL; //in case it was redirected to some "fixed" url later +}; + +class RSSReader : public QObject{ + Q_OBJECT +public: + RSSReader(QObject *parent, QString settingsPrefix); + ~RSSReader(); + + //Information retrieval + QStringList channels(); //returns all ID's + RSSchannel dataForID(QString ID); + + //Initial setup + void addUrls(QStringList urls); + void removeUrl(QString ID); + +public slots: + void syncNow(); //not generally needed + +private: + //Internal data objects + QHash hash; // ID/data + QString setprefix; + QTimer *syncTimer; + QNetworkAccessManager *NMAN; + QStringList outstandingURLS; + + + //Simple hash data search functions + QString keyForUrl(QString url); + + //Network request function + void requestRSS(QString url); + + //RSS parsing functions + RSSchannel readRSS(QByteArray bytes); + RSSchannel readRSSChannel(QXmlStreamReader *rss); + RSSitem readRSSItem(QXmlStreamReader *rss); + void readRSSImage(RSSchannel *item, QXmlStreamReader *rss); + QDateTime RSSDateTime(QString datetime); + +private slots: + void replyFinished(QNetworkReply *reply); + void sslErrors(QNetworkReply *reply, const QList &errors); + void checkTimes(); + +signals: + void rssChanged(QString); //ID + void newChannelsAvailable(); +}; + +#endif -- cgit