aboutsummaryrefslogtreecommitdiff
path: root/src-qt5/core/lumina-desktop/desktop-plugins/rssreader/RSSFeedPlugin.cpp
blob: c330d6c0e5f7ca8c5ba1bdc2ab3daede829c4471 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
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 <LuminaXDG.h>
#include "LSession.h"
#include <LUtils.h>
#include <QDir>
#include <QFileDialog>
#include <QInputDialog>
#include <QtConcurrent>

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; i<feeds.length(); i++){ feeds[i] = feeds[i].section("::::",1,-1); } //just need url right now
    feeds << "http://lumina-desktop.org/?feed=rss2"; //Lumina Desktop blog feed
    LSession::handle()->DesktopPluginSettings()->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; i<feeds.length(); i++){ 
    QAction *tmp = presetMenu->addAction(feeds[i].section("::::",0,0) );
    tmp->setWhatsThis( feeds[i].section("::::",1,-1) );
  }
}

void RSSFeedPlugin::checkFeedNotify(){
  bool notify = false;
  for(int i=0; i<ui->combo_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("<ul style=\"margin-left: 3px;\">\n");
  for(int i=0; i<data.items.length(); i++){
    //html.append("<li>");
    html.append("<h4><a href=\""+data.items[i].link+"\" style=\"color: "+color+";\">"+data.items[i].title+"</a></h4>");
    if(!data.items[i].pubdate.isNull() || !data.items[i].author.isEmpty()){
      html.append("<i>(");
      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("<a href=\"mailto:"+data.items[i].author_email+"\" style=\"color: "+color+";\">"+data.items[i].author+"</a>"); }
        else{ html.append(data.items[i].author); }
      }
      html.append(")</i><br>");
    }
    html.append(data.items[i].description);
    //html.append("</li>\n");
    if(i+1 < data.items.length()){ html.append("<br>"); }
  }
  //html.append("</ul>\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
  // <a href=\""+LINK+"\" style=\"color: "+color+";\">"+TEXT+"</a>
  html.append( QString(tr("Feed URL: %1")).arg("<a href=\""+data.originalURL+"\" style=\"color: "+color+";\">"+data.originalURL+"</a>") +"<br><hr>");
  html.append( QString(tr("Title: %1")).arg(data.title) +"<br>");
  html.append( QString(tr("Description: %1")).arg(data.description) +"<br>");
  html.append( QString(tr("Website: %1")).arg("<a href=\""+data.link+"\" style=\"color: "+color+";\">"+data.link+"</a>") +"<br><hr>");
  if(!data.lastBuildDate.isNull()){ html.append( QString(tr("Last Build Date: %1")).arg(data.lastBuildDate.toString(Qt::DefaultLocaleShortDate)) +"<br>"); }
  html.append( QString(tr("Last Sync: %1")).arg(data.lastsync.toString(Qt::DefaultLocaleShortDate)) +"<br>");
  html.append( QString(tr("Next Sync: %1")).arg(data.nextsync.toString(Qt::DefaultLocaleShortDate)) +"<br>");
  // - 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; i<IDS.length(); i++){
    bool newitem = false;
    if(ui->combo_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(); i<ui->combo_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; i<ui->combo_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();
}
bgstack15