1 /*
2 * Bittorrent Client using Qt and libtorrent.
3 * Copyright (C) 2017 Vladimir Golovnev <glassez@yandex.ru>
4 * Copyright (C) 2006 Christophe Dumez <chris@qbittorrent.org>
5 * Copyright (C) 2006 Arnaud Demaiziere <arnaud@qbittorrent.org>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 * In addition, as a special exception, the copyright holders give permission to
22 * link this program with the OpenSSL project's "OpenSSL" library (or with
23 * modified versions of it that use the same license as the "OpenSSL" library),
24 * and distribute the linked executables. You must obey the GNU General Public
25 * License in all respects for all of the code used other than "OpenSSL". If you
26 * modify file(s), you may extend this exception to your version of the file(s),
27 * but you are not obligated to do so. If you do not wish to do so, delete this
28 * exception statement from your version.
29 */
30
31 #include "rsswidget.h"
32
33 #include <QClipboard>
34 #include <QDesktopServices>
35 #include <QDragMoveEvent>
36 #include <QMenu>
37 #include <QMessageBox>
38 #include <QRegularExpression>
39 #include <QShortcut>
40 #include <QString>
41
42 #include "base/bittorrent/session.h"
43 #include "base/global.h"
44 #include "base/net/downloadmanager.h"
45 #include "base/preferences.h"
46 #include "base/rss/rss_article.h"
47 #include "base/rss/rss_feed.h"
48 #include "base/rss/rss_folder.h"
49 #include "base/rss/rss_session.h"
50 #include "gui/addnewtorrentdialog.h"
51 #include "gui/autoexpandabledialog.h"
52 #include "gui/uithememanager.h"
53 #include "articlelistwidget.h"
54 #include "automatedrssdownloader.h"
55 #include "feedlistwidget.h"
56 #include "ui_rsswidget.h"
57
RSSWidget(QWidget * parent)58 RSSWidget::RSSWidget(QWidget *parent)
59 : QWidget(parent)
60 , m_ui(new Ui::RSSWidget)
61 {
62 m_ui->setupUi(this);
63
64 // Icons
65 m_ui->actionCopyFeedURL->setIcon(UIThemeManager::instance()->getIcon("edit-copy"));
66 m_ui->actionDelete->setIcon(UIThemeManager::instance()->getIcon("edit-delete"));
67 m_ui->actionDownloadTorrent->setIcon(UIThemeManager::instance()->getIcon("download"));
68 m_ui->actionMarkItemsRead->setIcon(UIThemeManager::instance()->getIcon("mail-mark-read"));
69 m_ui->actionNewFolder->setIcon(UIThemeManager::instance()->getIcon("folder-new"));
70 m_ui->actionNewSubscription->setIcon(UIThemeManager::instance()->getIcon("list-add"));
71 m_ui->actionOpenNewsURL->setIcon(UIThemeManager::instance()->getIcon("application-x-mswinurl"));
72 m_ui->actionRename->setIcon(UIThemeManager::instance()->getIcon("edit-rename"));
73 m_ui->actionUpdate->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
74 m_ui->actionUpdateAllFeeds->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
75 #ifndef Q_OS_MACOS
76 m_ui->newFeedButton->setIcon(UIThemeManager::instance()->getIcon("list-add"));
77 m_ui->markReadButton->setIcon(UIThemeManager::instance()->getIcon("mail-mark-read"));
78 m_ui->updateAllButton->setIcon(UIThemeManager::instance()->getIcon("view-refresh"));
79 m_ui->rssDownloaderBtn->setIcon(UIThemeManager::instance()->getIcon("download"));
80 #endif
81
82 m_articleListWidget = new ArticleListWidget(m_ui->splitterMain);
83 m_ui->splitterMain->insertWidget(0, m_articleListWidget);
84 connect(m_articleListWidget, &ArticleListWidget::customContextMenuRequested, this, &RSSWidget::displayItemsListMenu);
85 connect(m_articleListWidget, &ArticleListWidget::currentItemChanged, this, &RSSWidget::handleCurrentArticleItemChanged);
86 connect(m_articleListWidget, &ArticleListWidget::itemDoubleClicked, this, &RSSWidget::downloadSelectedTorrents);
87
88 m_feedListWidget = new FeedListWidget(m_ui->splitterSide);
89 m_ui->splitterSide->insertWidget(0, m_feedListWidget);
90 connect(m_feedListWidget, &QAbstractItemView::doubleClicked, this, &RSSWidget::renameSelectedRSSItem);
91 connect(m_feedListWidget, &QTreeWidget::currentItemChanged, this, &RSSWidget::handleCurrentFeedItemChanged);
92 connect(m_feedListWidget, &QWidget::customContextMenuRequested, this, &RSSWidget::displayRSSListMenu);
93 loadFoldersOpenState();
94 m_feedListWidget->setCurrentItem(m_feedListWidget->stickyUnreadItem());
95
96 const auto *editHotkey = new QShortcut(Qt::Key_F2, m_feedListWidget, nullptr, nullptr, Qt::WidgetShortcut);
97 connect(editHotkey, &QShortcut::activated, this, &RSSWidget::renameSelectedRSSItem);
98 const auto *deleteHotkey = new QShortcut(QKeySequence::Delete, m_feedListWidget, nullptr, nullptr, Qt::WidgetShortcut);
99 connect(deleteHotkey, &QShortcut::activated, this, &RSSWidget::deleteSelectedItems);
100
101 // Feeds list actions
102 connect(m_ui->actionDelete, &QAction::triggered, this, &RSSWidget::deleteSelectedItems);
103 connect(m_ui->actionRename, &QAction::triggered, this, &RSSWidget::renameSelectedRSSItem);
104 connect(m_ui->actionUpdate, &QAction::triggered, this, &RSSWidget::refreshSelectedItems);
105 connect(m_ui->actionNewFolder, &QAction::triggered, this, &RSSWidget::askNewFolder);
106 connect(m_ui->actionNewSubscription, &QAction::triggered, this, &RSSWidget::on_newFeedButton_clicked);
107 connect(m_ui->actionUpdateAllFeeds, &QAction::triggered, this, &RSSWidget::refreshAllFeeds);
108 connect(m_ui->updateAllButton, &QAbstractButton::clicked, this, &RSSWidget::refreshAllFeeds);
109 connect(m_ui->actionCopyFeedURL, &QAction::triggered, this, &RSSWidget::copySelectedFeedsURL);
110 connect(m_ui->actionMarkItemsRead, &QAction::triggered, this, &RSSWidget::on_markReadButton_clicked);
111
112 // News list actions
113 connect(m_ui->actionOpenNewsURL, &QAction::triggered, this, &RSSWidget::openSelectedArticlesUrls);
114 connect(m_ui->actionDownloadTorrent, &QAction::triggered, this, &RSSWidget::downloadSelectedTorrents);
115
116 // Restore sliders position
117 restoreSlidersPosition();
118 // Bind saveSliders slots
119 connect(m_ui->splitterMain, &QSplitter::splitterMoved, this, &RSSWidget::saveSlidersPosition);
120 connect(m_ui->splitterSide, &QSplitter::splitterMoved, this, &RSSWidget::saveSlidersPosition);
121
122 if (RSS::Session::instance()->isProcessingEnabled())
123 m_ui->labelWarn->hide();
124 connect(RSS::Session::instance(), &RSS::Session::processingStateChanged
125 , this, &RSSWidget::handleSessionProcessingStateChanged);
126 connect(RSS::Session::instance()->rootFolder(), &RSS::Folder::unreadCountChanged
127 , this, &RSSWidget::handleUnreadCountChanged);
128 }
129
~RSSWidget()130 RSSWidget::~RSSWidget()
131 {
132 // we need it here to properly mark latest article
133 // as read without having additional code
134 m_articleListWidget->clear();
135
136 saveFoldersOpenState();
137
138 delete m_feedListWidget;
139 delete m_ui;
140 }
141
142 // display a right-click menu
displayRSSListMenu(const QPoint & pos)143 void RSSWidget::displayRSSListMenu(const QPoint &pos)
144 {
145 if (!m_feedListWidget->indexAt(pos).isValid())
146 // No item under the mouse, clear selection
147 m_feedListWidget->clearSelection();
148
149 QMenu *menu = new QMenu(this);
150 menu->setAttribute(Qt::WA_DeleteOnClose);
151
152 const QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
153 if (!selectedItems.isEmpty())
154 {
155 menu->addAction(m_ui->actionUpdate);
156 menu->addAction(m_ui->actionMarkItemsRead);
157 menu->addSeparator();
158
159 if (selectedItems.size() == 1)
160 {
161 if (selectedItems.first() != m_feedListWidget->stickyUnreadItem())
162 {
163 menu->addAction(m_ui->actionRename);
164 menu->addAction(m_ui->actionDelete);
165 menu->addSeparator();
166 if (m_feedListWidget->isFolder(selectedItems.first()))
167 menu->addAction(m_ui->actionNewFolder);
168 }
169 }
170 else
171 {
172 menu->addAction(m_ui->actionDelete);
173 menu->addSeparator();
174 }
175
176 menu->addAction(m_ui->actionNewSubscription);
177
178 if (m_feedListWidget->isFeed(selectedItems.first()))
179 {
180 menu->addSeparator();
181 menu->addAction(m_ui->actionCopyFeedURL);
182 }
183 }
184 else
185 {
186 menu->addAction(m_ui->actionNewSubscription);
187 menu->addAction(m_ui->actionNewFolder);
188 menu->addSeparator();
189 menu->addAction(m_ui->actionUpdateAllFeeds);
190 }
191
192 menu->popup(QCursor::pos());
193 }
194
displayItemsListMenu(const QPoint &)195 void RSSWidget::displayItemsListMenu(const QPoint &)
196 {
197 bool hasTorrent = false;
198 bool hasLink = false;
199 for (const QListWidgetItem *item : asConst(m_articleListWidget->selectedItems()))
200 {
201 auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
202 Q_ASSERT(article);
203
204 if (!article->torrentUrl().isEmpty())
205 hasTorrent = true;
206 if (!article->link().isEmpty())
207 hasLink = true;
208 if (hasTorrent && hasLink)
209 break;
210 }
211
212 QMenu *myItemListMenu = new QMenu(this);
213 myItemListMenu->setAttribute(Qt::WA_DeleteOnClose);
214
215 if (hasTorrent)
216 myItemListMenu->addAction(m_ui->actionDownloadTorrent);
217 if (hasLink)
218 myItemListMenu->addAction(m_ui->actionOpenNewsURL);
219
220 if (!myItemListMenu->isEmpty())
221 myItemListMenu->popup(QCursor::pos());
222 }
223
askNewFolder()224 void RSSWidget::askNewFolder()
225 {
226 bool ok = false;
227 QString newName = AutoExpandableDialog::getText(
228 this, tr("Please choose a folder name"), tr("Folder name:"), QLineEdit::Normal
229 , tr("New folder"), &ok);
230 if (!ok) return;
231
232 newName = newName.trimmed();
233 if (newName.isEmpty()) return;
234
235 // Determine destination folder for new item
236 QTreeWidgetItem *destItem = nullptr;
237 QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
238 if (!selectedItems.empty())
239 {
240 destItem = selectedItems.first();
241 if (!m_feedListWidget->isFolder(destItem))
242 destItem = destItem->parent();
243 }
244 // Consider the case where the user clicked on Unread item
245 RSS::Folder *rssDestFolder = ((!destItem || (destItem == m_feedListWidget->stickyUnreadItem()))
246 ? RSS::Session::instance()->rootFolder()
247 : qobject_cast<RSS::Folder *>(m_feedListWidget->getRSSItem(destItem)));
248
249 QString error;
250 const QString newFolderPath = RSS::Item::joinPath(rssDestFolder->path(), newName);
251 if (!RSS::Session::instance()->addFolder(newFolderPath, &error))
252 QMessageBox::warning(this, "qBittorrent", error, QMessageBox::Ok);
253
254 // Expand destination folder to display new feed
255 if (destItem && (destItem != m_feedListWidget->stickyUnreadItem()))
256 destItem->setExpanded(true);
257 // As new RSS items are added synchronously, we can do the following here.
258 m_feedListWidget->setCurrentItem(m_feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFolderPath)));
259 }
260
261 // add a stream by a button
on_newFeedButton_clicked()262 void RSSWidget::on_newFeedButton_clicked()
263 {
264 // Ask for feed URL
265 const QString clipText = qApp->clipboard()->text();
266 const QString defaultURL = Net::DownloadManager::hasSupportedScheme(clipText) ? clipText : "http://";
267
268 bool ok = false;
269 QString newURL = AutoExpandableDialog::getText(
270 this, tr("Please type a RSS feed URL"), tr("Feed URL:"), QLineEdit::Normal, defaultURL, &ok);
271 if (!ok) return;
272
273 newURL = newURL.trimmed();
274 if (newURL.isEmpty()) return;
275
276 // Determine destination folder for new item
277 QTreeWidgetItem *destItem = nullptr;
278 QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
279 if (!selectedItems.empty())
280 {
281 destItem = selectedItems.first();
282 if (!m_feedListWidget->isFolder(destItem))
283 destItem = destItem->parent();
284 }
285 // Consider the case where the user clicked on Unread item
286 RSS::Folder *rssDestFolder = ((!destItem || (destItem == m_feedListWidget->stickyUnreadItem()))
287 ? RSS::Session::instance()->rootFolder()
288 : qobject_cast<RSS::Folder *>(m_feedListWidget->getRSSItem(destItem)));
289
290 QString error;
291 // NOTE: We still add feed using legacy way (with URL as feed name)
292 const QString newFeedPath = RSS::Item::joinPath(rssDestFolder->path(), newURL);
293 if (!RSS::Session::instance()->addFeed(newURL, newFeedPath, &error))
294 QMessageBox::warning(this, "qBittorrent", error, QMessageBox::Ok);
295
296 // Expand destination folder to display new feed
297 if (destItem && (destItem != m_feedListWidget->stickyUnreadItem()))
298 destItem->setExpanded(true);
299 // As new RSS items are added synchronously, we can do the following here.
300 m_feedListWidget->setCurrentItem(m_feedListWidget->mapRSSItem(RSS::Session::instance()->itemByPath(newFeedPath)));
301 }
302
deleteSelectedItems()303 void RSSWidget::deleteSelectedItems()
304 {
305 const QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
306 if (selectedItems.isEmpty())
307 return;
308 if ((selectedItems.size() == 1) && (selectedItems.first() == m_feedListWidget->stickyUnreadItem()))
309 return;
310
311 QMessageBox::StandardButton answer = QMessageBox::question(
312 this, tr("Deletion confirmation"), tr("Are you sure you want to delete the selected RSS feeds?")
313 , QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
314 if (answer == QMessageBox::No)
315 return;
316
317 for (QTreeWidgetItem *item : selectedItems)
318 if (item != m_feedListWidget->stickyUnreadItem())
319 RSS::Session::instance()->removeItem(m_feedListWidget->itemPath(item));
320 }
321
loadFoldersOpenState()322 void RSSWidget::loadFoldersOpenState()
323 {
324 const QStringList openedFolders = Preferences::instance()->getRssOpenFolders();
325 for (const QString &varPath : openedFolders)
326 {
327 QTreeWidgetItem *parent = nullptr;
328 for (const QString &name : asConst(varPath.split('\\')))
329 {
330 int nbChildren = (parent ? parent->childCount() : m_feedListWidget->topLevelItemCount());
331 for (int i = 0; i < nbChildren; ++i)
332 {
333 QTreeWidgetItem *child = (parent ? parent->child(i) : m_feedListWidget->topLevelItem(i));
334 if (m_feedListWidget->getRSSItem(child)->name() == name)
335 {
336 parent = child;
337 parent->setExpanded(true);
338 break;
339 }
340 }
341 }
342 }
343 }
344
saveFoldersOpenState()345 void RSSWidget::saveFoldersOpenState()
346 {
347 QStringList openedFolders;
348 for (QTreeWidgetItem *item : asConst(m_feedListWidget->getAllOpenedFolders()))
349 openedFolders << m_feedListWidget->itemPath(item);
350 Preferences::instance()->setRssOpenFolders(openedFolders);
351 }
352
refreshAllFeeds()353 void RSSWidget::refreshAllFeeds()
354 {
355 RSS::Session::instance()->refresh();
356 }
357
downloadSelectedTorrents()358 void RSSWidget::downloadSelectedTorrents()
359 {
360 for (QListWidgetItem *item : asConst(m_articleListWidget->selectedItems()))
361 {
362 auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
363 Q_ASSERT(article);
364
365 // Mark as read
366 article->markAsRead();
367
368 if (!article->torrentUrl().isEmpty())
369 {
370 if (AddNewTorrentDialog::isEnabled())
371 AddNewTorrentDialog::show(article->torrentUrl(), window());
372 else
373 BitTorrent::Session::instance()->addTorrent(article->torrentUrl());
374 }
375 }
376 }
377
378 // open the url of the selected RSS articles in the Web browser
openSelectedArticlesUrls()379 void RSSWidget::openSelectedArticlesUrls()
380 {
381 for (QListWidgetItem *item : asConst(m_articleListWidget->selectedItems()))
382 {
383 auto article = reinterpret_cast<RSS::Article *>(item->data(Qt::UserRole).value<quintptr>());
384 Q_ASSERT(article);
385
386 // Mark as read
387 article->markAsRead();
388
389 if (!article->link().isEmpty())
390 QDesktopServices::openUrl(QUrl(article->link()));
391 }
392 }
393
renameSelectedRSSItem()394 void RSSWidget::renameSelectedRSSItem()
395 {
396 QList<QTreeWidgetItem *> selectedItems = m_feedListWidget->selectedItems();
397 if (selectedItems.size() != 1) return;
398
399 QTreeWidgetItem *item = selectedItems.first();
400 if (item == m_feedListWidget->stickyUnreadItem())
401 return;
402
403 RSS::Item *rssItem = m_feedListWidget->getRSSItem(item);
404 const QString parentPath = RSS::Item::parentPath(rssItem->path());
405 bool ok = false;
406 do
407 {
408 QString newName = AutoExpandableDialog::getText(
409 this, tr("Please choose a new name for this RSS feed"), tr("New feed name:")
410 , QLineEdit::Normal, rssItem->name(), &ok);
411 // Check if name is already taken
412 if (!ok) return;
413
414 QString error;
415 if (!RSS::Session::instance()->moveItem(rssItem, RSS::Item::joinPath(parentPath, newName), &error))
416 {
417 QMessageBox::warning(nullptr, tr("Rename failed"), error);
418 ok = false;
419 }
420 } while (!ok);
421 }
422
refreshSelectedItems()423 void RSSWidget::refreshSelectedItems()
424 {
425 for (QTreeWidgetItem *item : asConst(m_feedListWidget->selectedItems()))
426 {
427 if (item == m_feedListWidget->stickyUnreadItem())
428 {
429 refreshAllFeeds();
430 return;
431 }
432
433 m_feedListWidget->getRSSItem(item)->refresh();
434 }
435 }
436
copySelectedFeedsURL()437 void RSSWidget::copySelectedFeedsURL()
438 {
439 QStringList URLs;
440 for (QTreeWidgetItem *item : asConst(m_feedListWidget->selectedItems()))
441 {
442 if (auto feed = qobject_cast<RSS::Feed *>(m_feedListWidget->getRSSItem(item)))
443 URLs << feed->url();
444 }
445 qApp->clipboard()->setText(URLs.join('\n'));
446 }
447
handleCurrentFeedItemChanged(QTreeWidgetItem * currentItem)448 void RSSWidget::handleCurrentFeedItemChanged(QTreeWidgetItem *currentItem)
449 {
450 m_articleListWidget->setRSSItem(m_feedListWidget->getRSSItem(currentItem)
451 , (currentItem == m_feedListWidget->stickyUnreadItem()));
452 }
453
on_markReadButton_clicked()454 void RSSWidget::on_markReadButton_clicked()
455 {
456 for (QTreeWidgetItem *item : asConst(m_feedListWidget->selectedItems()))
457 {
458 m_feedListWidget->getRSSItem(item)->markAsRead();
459 if (item == m_feedListWidget->stickyUnreadItem())
460 break; // all items was read
461 }
462 }
463
464 // display a news
handleCurrentArticleItemChanged(QListWidgetItem * currentItem,QListWidgetItem * previousItem)465 void RSSWidget::handleCurrentArticleItemChanged(QListWidgetItem *currentItem, QListWidgetItem *previousItem)
466 {
467 m_ui->textBrowser->clear();
468
469 if (previousItem)
470 {
471 auto article = m_articleListWidget->getRSSArticle(previousItem);
472 Q_ASSERT(article);
473 article->markAsRead();
474 }
475
476 if (!currentItem) return;
477
478 auto article = m_articleListWidget->getRSSArticle(currentItem);
479 Q_ASSERT(article);
480
481 const QString highlightedBaseColor = m_ui->textBrowser->palette().color(QPalette::Highlight).name();
482 const QString highlightedBaseTextColor = m_ui->textBrowser->palette().color(QPalette::HighlightedText).name();
483 const QString alternateBaseColor = m_ui->textBrowser->palette().color(QPalette::AlternateBase).name();
484
485 QString html =
486 QString::fromLatin1("<div style='border: 2px solid red; margin-left: 5px; margin-right: 5px; margin-bottom: 5px;'>") +
487 QString::fromLatin1("<div style='background-color: \"%1\"; font-weight: bold; color: \"%2\";'>%3</div>").arg(highlightedBaseColor, highlightedBaseTextColor, article->title());
488 if (article->date().isValid())
489 html += QString::fromLatin1("<div style='background-color: \"%1\";'><b>%2</b>%3</div>").arg(alternateBaseColor, tr("Date: "), QLocale::system().toString(article->date().toLocalTime()));
490 if (!article->author().isEmpty())
491 html += QString::fromLatin1("<div style='background-color: \"%1\";'><b>%2</b>%3</div>").arg(alternateBaseColor, tr("Author: "), article->author());
492 html += "</div>"
493 "<div style='margin-left: 5px; margin-right: 5px;'>";
494 if (Qt::mightBeRichText(article->description()))
495 {
496 html += article->description();
497 }
498 else
499 {
500 QString description = article->description();
501 QRegularExpression rx;
502 // If description is plain text, replace BBCode tags with HTML and wrap everything in <pre></pre> so it looks nice
503 rx.setPatternOptions(QRegularExpression::InvertedGreedinessOption
504 | QRegularExpression::CaseInsensitiveOption);
505
506 rx.setPattern("\\[img\\](.+)\\[/img\\]");
507 description = description.replace(rx, "<img src=\"\\1\">");
508
509 rx.setPattern("\\[url=(\")?(.+)\\1\\]");
510 description = description.replace(rx, "<a href=\"\\2\">");
511 description = description.replace("[/url]", "</a>", Qt::CaseInsensitive);
512
513 rx.setPattern("\\[(/)?([bius])\\]");
514 description = description.replace(rx, "<\\1\\2>");
515
516 rx.setPattern("\\[color=(\")?(.+)\\1\\]");
517 description = description.replace(rx, "<span style=\"color:\\2\">");
518 description = description.replace("[/color]", "</span>", Qt::CaseInsensitive);
519
520 rx.setPattern("\\[size=(\")?(.+)\\d\\1\\]");
521 description = description.replace(rx, "<span style=\"font-size:\\2px\">");
522 description = description.replace("[/size]", "</span>", Qt::CaseInsensitive);
523
524 html += "<pre>" + description + "</pre>";
525 }
526 html += "</div>";
527 m_ui->textBrowser->setHtml(html);
528 }
529
saveSlidersPosition()530 void RSSWidget::saveSlidersPosition()
531 {
532 // Remember sliders positions
533 Preferences *const pref = Preferences::instance();
534 pref->setRssSideSplitterState(m_ui->splitterSide->saveState());
535 pref->setRssMainSplitterState(m_ui->splitterMain->saveState());
536 }
537
restoreSlidersPosition()538 void RSSWidget::restoreSlidersPosition()
539 {
540 const Preferences *const pref = Preferences::instance();
541 const QByteArray stateSide = pref->getRssSideSplitterState();
542 if (!stateSide.isEmpty())
543 m_ui->splitterSide->restoreState(stateSide);
544 const QByteArray stateMain = pref->getRssMainSplitterState();
545 if (!stateMain.isEmpty())
546 m_ui->splitterMain->restoreState(stateMain);
547 }
548
updateRefreshInterval(int val) const549 void RSSWidget::updateRefreshInterval(int val) const
550 {
551 RSS::Session::instance()->setRefreshInterval(val);
552 }
553
on_rssDownloaderBtn_clicked()554 void RSSWidget::on_rssDownloaderBtn_clicked()
555 {
556 auto *downloader = new AutomatedRssDownloader(this);
557 downloader->setAttribute(Qt::WA_DeleteOnClose);
558 downloader->open();
559 }
560
handleSessionProcessingStateChanged(bool enabled)561 void RSSWidget::handleSessionProcessingStateChanged(bool enabled)
562 {
563 m_ui->labelWarn->setVisible(!enabled);
564 }
565
handleUnreadCountChanged()566 void RSSWidget::handleUnreadCountChanged()
567 {
568 emit unreadCountUpdated(RSS::Session::instance()->rootFolder()->unreadCount());
569 }
570